source: psi/trunk/src/psiaccount.cpp@ 16

Last change on this file since 16 was 2, checked in by dmik, 19 years ago

Imported original Psi 0.10 sources from Affinix

File size: 98.2 KB
Line 
1/*
2 * psiaccount.cpp - handles a Psi account
3 * Copyright (C) 2001-2005 Justin Karneges
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * You can also redistribute and/or modify this program under the
11 * terms of the Psi License, specified in the accompanied COPYING
12 * file, as published by the Psi Project; either dated January 1st,
13 * 2005, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 */
25
26#include"psiaccount.h"
27
28#include<qinputdialog.h>
29#include<qptrlist.h>
30#include<qcstring.h>
31#include<qtimer.h>
32#include<qmessagebox.h>
33#include<qguardedptr.h>
34#include<qapplication.h>
35#include<qpushbutton.h>
36#include<qlayout.h>
37#include<qobjectlist.h>
38#include<qurl.h>
39#include<qmap.h>
40#include<qca.h>
41#include<qfileinfo.h>
42
43#include"psicon.h"
44#include"profiles.h"
45#include"im.h"
46//#include"xmpp_client.h"
47//#include"xmpp_stream.h"
48//#include"xmpp_message.h"
49#include"xmpp_tasks.h"
50#include"xmpp_jidlink.h"
51#include"s5b.h"
52#include"filetransfer.h"
53#include"accountdlg.h"
54#include"changepwdlg.h"
55#include"xmlconsole.h"
56#include"userlist.h"
57#include"eventdlg.h"
58#include"chatdlg.h"
59#include"contactview.h"
60#include"groupchatdlg.h"
61#include"statusdlg.h"
62#include"infodlg.h"
63#include"adduserdlg.h"
64#include"historydlg.h"
65#include"servicesdlg.h"
66#include"discodlg.h"
67#include"eventdb.h"
68#include"jltest.h"
69#include"passphrasedlg.h"
70#include"vcardfactory.h"
71//#include"qssl.h"
72#include"sslcertdlg.h"
73#include"qwextend.h"
74#include"psipopup.h"
75#include"fancylabel.h"
76#include"iconwidget.h"
77#include"base64.h"
78#include"filetransdlg.h"
79#include"avatars.h"
80#include"tabdlg.h"
81
82#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
83#include "psigrowlnotifier.h"
84#endif
85
86#include"bsocket.h"
87/*#ifdef Q_WS_WIN
88#include<windows.h>
89typedef int socklen_t;
90#else
91#include<sys/socket.h>
92#include<netinet/in.h>
93#endif*/
94
95//----------------------------------------------------------------------------
96// AccountLabel
97//----------------------------------------------------------------------------
98AccountLabel::AccountLabel(PsiAccount *_pa, QWidget *par, bool smode)
99:QLabel(par)
100{
101 pa = _pa;
102 simpleMode = smode;
103 setFrameStyle( QFrame::Panel | QFrame::Sunken );
104
105 updateName();
106 connect(pa, SIGNAL(updatedAccount()), this, SLOT(updateName()));
107 connect(pa, SIGNAL(destroyed()), this, SLOT(deleteMe()));
108}
109
110AccountLabel::~AccountLabel()
111{
112}
113
114void AccountLabel::updateName()
115{
116 setText(simpleMode ? pa->name() : pa->nameWithJid());
117}
118
119void AccountLabel::deleteMe()
120{
121 delete this;
122}
123
124
125//----------------------------------------------------------------------------
126// PGPTransaction
127//----------------------------------------------------------------------------
128static int transid = 0;
129class PGPTransaction::Private
130{
131public:
132 Private() {}
133
134 int id;
135 Message m;
136 QDomElement e;
137 Jid j;
138};
139
140PGPTransaction::PGPTransaction(OpenPGP::Engine *pgp)
141:OpenPGP::Request(pgp)
142{
143 d = new Private;
144 d->id = transid++;
145}
146
147PGPTransaction::~PGPTransaction()
148{
149 delete d;
150}
151
152int PGPTransaction::id() const
153{
154 return d->id;
155}
156
157void PGPTransaction::setMessage(const Message &m)
158{
159 d->m = m;
160}
161
162const Message & PGPTransaction::message() const
163{
164 return d->m;
165}
166
167const QDomElement & PGPTransaction::xml() const
168{
169 return d->e;
170}
171
172void PGPTransaction::setXml(const QDomElement &e)
173{
174 d->e = e;
175}
176
177Jid PGPTransaction::jid() const
178{
179 return d->j;
180}
181
182void PGPTransaction::setJid(const Jid &j)
183{
184 d->j = j;
185}
186
187//----------------------------------------------------------------------------
188// BlockTransportPopup -- blocks popups on transport status changes
189//----------------------------------------------------------------------------
190
191class BlockTransportPopupList;
192
193class BlockTransportPopup : public QObject
194{
195 Q_OBJECT
196public:
197 BlockTransportPopup(QObject *, const Jid &);
198
199 Jid jid() const;
200
201private slots:
202 void timeout();
203
204private:
205 Jid j;
206 int userCounter;
207 friend class BlockTransportPopupList;
208};
209
210BlockTransportPopup::BlockTransportPopup(QObject *parent, const Jid &_j)
211: QObject(parent)
212{
213 j = _j;
214 userCounter = 0;
215
216 // Hack for ICQ SMS
217 if ( j.host().left(3) == "icq" ) {
218 new BlockTransportPopup(parent, "sms." + j.host()); // sms.icq.host.com
219 new BlockTransportPopup(parent, "sms" + j.host().right(j.host().length() - 3)); // sms.host.com
220 }
221
222 QTimer::singleShot(15000, this, SLOT(timeout()));
223}
224
225void BlockTransportPopup::timeout()
226{
227 if ( userCounter > 1 ) {
228 QTimer::singleShot(15000, this, SLOT(timeout()));
229 userCounter = 0;
230 }
231 else
232 deleteLater();
233}
234
235Jid BlockTransportPopup::jid() const
236{
237 return j;
238}
239
240//----------------------------------------------------------------------------
241// BlockTransportPopupList
242//----------------------------------------------------------------------------
243
244class BlockTransportPopupList : public QObject
245{
246 Q_OBJECT
247public:
248 BlockTransportPopupList();
249
250 bool find(const Jid &, bool online = false);
251};
252
253BlockTransportPopupList::BlockTransportPopupList()
254: QObject()
255{
256}
257
258bool BlockTransportPopupList::find(const Jid &j, bool online)
259{
260 if ( j.user().isEmpty() ) // always show popups for transports
261 return false;
262
263 QObjectList *list = queryList("BlockTransportPopup");
264 QObjectListIt it( *list );
265
266 BlockTransportPopup *btp;
267 for ( ; it.current(); ++it) {
268 btp = (BlockTransportPopup *)it.current();
269 if ( j.host() == btp->jid().host() ) {
270 if ( online )
271 btp->userCounter++;
272 return true;
273 }
274 }
275 delete list;
276
277 return false;
278}
279
280//----------------------------------------------------------------------------
281// PsiAccount
282//----------------------------------------------------------------------------
283struct item_dialog2
284{
285 QWidget *widget;
286 QString className;
287 Jid jid;
288};
289
290QMap<QString,QString> pgp_passphrases;
291
292class PsiAccount::Private : public QObject
293{
294 Q_OBJECT
295public:
296 Private(PsiAccount *parent) {
297 account = parent;
298 }
299
300 PsiCon *psi;
301 PsiAccount *account;
302 Client *client;
303 ContactProfile *cp;
304 UserAccount acc, accnext;
305 Jid jid, nextJid;
306 Status loginStatus;
307 QPtrList<item_dialog2> dialogList;
308 EventQueue *eventQueue;
309 XmlConsole *xmlConsole;
310 UserList userList;
311 UserListItem self;
312 int lastIdle;
313 Status lastStatus, origStatus;
314 bool nickFromVCard;
315 QString cur_pgpSecretKeyID;
316 QPtrList<Message> messageQueue;
317 BlockTransportPopupList *blockTransportPopupList;
318 int userCounter;
319
320 // Avatars
321 AvatarFactory* avatarFactory;
322
323 // pgp related
324 PassphraseDlg *ppdlg;
325 QGuardedPtr<OpenPGP::Request> ppreq;
326
327 QPtrList<GCContact> gcbank;
328 QStringList groupchats;
329
330 AdvancedConnector *conn;
331 ClientStream *stream;
332 QCA::TLS *tls;
333 QCATLSHandler *tlsHandler;
334 QPtrList<QCA::Cert> certList;
335 bool usingSSL;
336 bool doPopups;
337
338 QHostAddress localAddress;
339
340 QString pathToProfileEvents()
341 {
342 return pathToProfile(activeProfile) + "/events-" + acc.name + ".xml";
343 }
344
345public slots:
346 void queueChanged()
347 {
348 eventQueue->toFile(pathToProfileEvents());
349 }
350
351 void loadQueue()
352 {
353 bool soundEnabled = useSound;
354 useSound = FALSE; // disable the sound and popups
355 doPopups = FALSE;
356
357 QFileInfo fi( pathToProfileEvents() );
358 if ( fi.exists() )
359 eventQueue->fromFile(pathToProfileEvents());
360
361 useSound = soundEnabled;
362 doPopups = TRUE;
363 }
364
365 void setEnabled( bool e )
366 {
367 acc.opt_enabled = e;
368 psi->enableAccount(account, e);
369 cp->setEnabled(e);
370 account->cpUpdate(self);
371
372 // signals
373 account->enabledChanged();
374 account->updatedAccount();
375 }
376};
377
378PsiAccount::PsiAccount(const UserAccount &acc, PsiCon *parent)
379:QObject(0)
380{
381 d = new Private( this );
382 d->psi = parent;
383 d->client = 0;
384 d->cp = 0;
385 d->userCounter = 0;
386
387#ifdef AVATARS
388 d->avatarFactory = new AvatarFactory(this);
389#endif
390
391 d->blockTransportPopupList = new BlockTransportPopupList();
392
393 d->doPopups = true;
394 v_isActive = false;
395 isDisconnecting = false;
396 notifyOnlineOk = false;
397 doReconnect = false;
398 usingAutoStatus = false;
399 presenceSent = false;
400
401 d->loginStatus = Status("", "");
402 d->lastIdle = 0;
403 d->lastStatus = Status("", "", 0, false);
404
405 d->dialogList.setAutoDelete(true);
406 d->eventQueue = new EventQueue(this);
407 connect(d->eventQueue, SIGNAL(queueChanged()), SIGNAL(queueChanged()));
408 connect(d->eventQueue, SIGNAL(queueChanged()), d, SLOT(queueChanged()));
409 connect(d->eventQueue, SIGNAL(handleEvent(PsiEvent *)), SLOT(handleEvent(PsiEvent *)));
410 d->userList.setAutoDelete(true);
411 d->self = UserListItem(true);
412 d->self.setSubscription(Subscription::Both);
413 d->nickFromVCard = false;
414
415#ifdef AVATARS
416 d->self.setAvatarFactory(d->avatarFactory);
417#endif
418
419 d->messageQueue.setAutoDelete(true);
420 d->ppdlg = 0;
421 d->ppreq = 0;
422
423 d->gcbank.setAutoDelete(true);
424
425 // we need to copy groupState, because later initialization will depend on that
426 d->acc.groupState = acc.groupState;
427
428 // stream
429 d->conn = 0;
430 d->tls = 0;
431 d->tlsHandler = 0;
432 d->stream = 0;
433 d->certList.setAutoDelete(true);
434 d->usingSSL = false;
435
436 // create Jabber::Client
437 d->client = new Client;
438 d->client->setOSName(getOSName());
439 d->client->setTimeZone(getTZString(), getTZOffset());
440 d->client->setClientName(PROG_NAME);
441 d->client->setClientVersion(PROG_VERSION);
442
443 d->client->setFileTransferEnabled(true);
444
445 //connect(d->client, SIGNAL(connected()), SLOT(client_connected()));
446 //connect(d->client, SIGNAL(handshaken()), SLOT(client_handshaken()));
447 //connect(d->client, SIGNAL(error(const StreamError &)), SLOT(client_error(const StreamError &)));
448 //connect(d->client, SIGNAL(sslCertReady(const QSSLCert &)), SLOT(client_sslCertReady(const QSSLCert &)));
449 //connect(d->client, SIGNAL(closeFinished()), SLOT(client_closeFinished()));
450 //connect(d->client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(client_authFinished(bool, int, const QString &)));
451 connect(d->client, SIGNAL(rosterRequestFinished(bool, int, const QString &)), SLOT(client_rosterRequestFinished(bool, int, const QString &)));
452 connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemAdded(const RosterItem &)));
453 connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &)));
454 connect(d->client, SIGNAL(rosterItemUpdated(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &)));
455 connect(d->client, SIGNAL(rosterItemRemoved(const RosterItem &)), SLOT(client_rosterItemRemoved(const RosterItem &)));
456 connect(d->client, SIGNAL(resourceAvailable(const Jid &, const Resource &)), SLOT(client_resourceAvailable(const Jid &, const Resource &)));
457 connect(d->client, SIGNAL(resourceUnavailable(const Jid &, const Resource &)), SLOT(client_resourceUnavailable(const Jid &, const Resource &)));
458 connect(d->client, SIGNAL(presenceError(const Jid &, int, const QString &)), SLOT(client_presenceError(const Jid &, int, const QString &)));
459 connect(d->client, SIGNAL(messageReceived(const Message &)), SLOT(client_messageReceived(const Message &)));
460 connect(d->client, SIGNAL(subscription(const Jid &, const QString &)), SLOT(client_subscription(const Jid &, const QString &)));
461 connect(d->client, SIGNAL(debugText(const QString &)), SLOT(client_debugText(const QString &)));
462 connect(d->client, SIGNAL(groupChatJoined(const Jid &)), SLOT(client_groupChatJoined(const Jid &)));
463 connect(d->client, SIGNAL(groupChatLeft(const Jid &)), SLOT(client_groupChatLeft(const Jid &)));
464 connect(d->client, SIGNAL(groupChatPresence(const Jid &, const Status &)), SLOT(client_groupChatPresence(const Jid &, const Status &)));
465 connect(d->client, SIGNAL(groupChatError(const Jid &, int, const QString &)), SLOT(client_groupChatError(const Jid &, int, const QString &)));
466 connect(d->client, SIGNAL(incomingJidLink()), SLOT(client_incomingJidLink()));
467 connect(d->client->fileTransferManager(), SIGNAL(incomingReady()), SLOT(client_incomingFileTransfer()));
468
469 // contactprofile context
470 d->cp = new ContactProfile(this, acc.name, d->psi->contactView());
471 connect(d->cp, SIGNAL(actionDefault(const Jid &)),SLOT(actionDefault(const Jid &)));
472 connect(d->cp, SIGNAL(actionRecvEvent(const Jid &)),SLOT(actionRecvEvent(const Jid &)));
473 connect(d->cp, SIGNAL(actionSendMessage(const Jid &)),SLOT(actionSendMessage(const Jid &)));
474 connect(d->cp, SIGNAL(actionSendMessage(const JidList &)),SLOT(actionSendMessage(const JidList &)));
475 connect(d->cp, SIGNAL(actionSendUrl(const Jid &)),SLOT(actionSendUrl(const Jid &)));
476 connect(d->cp, SIGNAL(actionRemove(const Jid &)),SLOT(actionRemove(const Jid &)));
477 connect(d->cp, SIGNAL(actionRename(const Jid &, const QString &)),SLOT(actionRename(const Jid &, const QString &)));
478 connect(d->cp, SIGNAL(actionGroupRename(const QString &, const QString &)),SLOT(actionGroupRename(const QString &, const QString &)));
479 connect(d->cp, SIGNAL(actionHistory(const Jid &)),SLOT(actionHistory(const Jid &)));
480 connect(d->cp, SIGNAL(actionOpenChat(const Jid &)),SLOT(actionOpenChat(const Jid &)));
481 connect(d->cp, SIGNAL(actionOpenChatSpecific(const Jid &)),SLOT(actionOpenChatSpecific(const Jid &)));
482 connect(d->cp, SIGNAL(actionAgentSetStatus(const Jid &, Status &)),SLOT(actionAgentSetStatus(const Jid &, Status &)));
483 connect(d->cp, SIGNAL(actionInfo(const Jid &)),SLOT(actionInfo(const Jid &)));
484 connect(d->cp, SIGNAL(actionAuth(const Jid &)),SLOT(actionAuth(const Jid &)));
485 connect(d->cp, SIGNAL(actionAuthRequest(const Jid &)),SLOT(actionAuthRequest(const Jid &)));
486 connect(d->cp, SIGNAL(actionAuthRemove(const Jid &)),SLOT(actionAuthRemove(const Jid &)));
487 connect(d->cp, SIGNAL(actionAdd(const Jid &)),SLOT(actionAdd(const Jid &)));
488 connect(d->cp, SIGNAL(actionGroupAdd(const Jid &, const QString &)),SLOT(actionGroupAdd(const Jid &, const QString &)));
489 connect(d->cp, SIGNAL(actionGroupRemove(const Jid &, const QString &)),SLOT(actionGroupRemove(const Jid &, const QString &)));
490 connect(d->cp, SIGNAL(actionTest(const Jid &)),SLOT(actionTest(const Jid &)));
491 connect(d->cp, SIGNAL(actionSendFile(const Jid &)),SLOT(actionSendFile(const Jid &)));
492 connect(d->cp, SIGNAL(actionSendFiles(const Jid &, const QStringList&)),SLOT(actionSendFiles(const Jid &, const QStringList&)));
493 connect(d->cp, SIGNAL(actionDisco(const Jid &, const QString &)),SLOT(actionDisco(const Jid &, const QString &)));
494 connect(d->cp, SIGNAL(actionInvite(const Jid &, const QString &)),SLOT(actionInvite(const Jid &, const QString &)));
495 connect(d->cp, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &)));
496 connect(d->cp, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &)));
497
498 // restore cached roster
499 for(Roster::ConstIterator it = acc.roster.begin(); it != acc.roster.end(); ++it)
500 client_rosterItemUpdated(*it);
501
502 // restore pgp key bindings
503 for(VarList::ConstIterator kit = acc.keybind.begin(); kit != acc.keybind.end(); ++kit) {
504 const VarListItem &i = *kit;
505 UserListItem *u = find(Jid(i.key()));
506 if(u) {
507 u->setPublicKeyID(i.data());
508 cpUpdate(*u);
509 }
510 }
511
512 setUserAccount(acc);
513
514 d->psi->link(this);
515 connect(d->psi, SIGNAL(emitOptionsUpdate()), SLOT(optionsUpdate()));
516 connect(d->psi, SIGNAL(pgpToggled(bool)), SLOT(pgpToggled(bool)));
517 connect(d->psi, SIGNAL(pgpKeysUpdated()), SLOT(pgpKeysUpdated()));
518
519 d->psi->setToggles(d->acc.tog_offline, d->acc.tog_away, d->acc.tog_agents, d->acc.tog_hidden,d->acc.tog_self);
520
521 d->setEnabled(d->acc.opt_enabled);
522
523 // auto-login ?
524 if(d->acc.opt_auto && d->acc.opt_enabled)
525 setStatus(Status("", "", d->acc.priority));
526
527 //printf("PsiAccount: [%s] loaded\n", name().latin1());
528 d->xmlConsole = new XmlConsole(this);
529 if(option.xmlConsoleOnLogin && d->acc.opt_enabled) {
530 this->showXmlConsole();
531 d->xmlConsole->enable();
532 }
533
534 // load event queue from disk
535 QTimer::singleShot(0, d, SLOT(loadQueue()));
536}
537
538PsiAccount::~PsiAccount()
539{
540 logout(true);
541 QString str = name();
542
543 d->messageQueue.clear();
544
545 // nuke all related dialogs
546 deleteAllDialogs();
547
548 d->psi->ftdlg()->killTransfers(this);
549
550 delete d->cp;
551 delete d->client;
552 cleanupStream();
553 delete d->eventQueue;
554
555 delete d->blockTransportPopupList;
556
557 d->psi->unlink(this);
558 delete d;
559
560 //printf("PsiAccount: [%s] unloaded\n", str.latin1());
561}
562
563void PsiAccount::cleanupStream()
564{
565 delete d->stream;
566 d->stream = 0;
567
568 delete d->tls;
569 d->tls = 0;
570 d->tlsHandler = 0;
571
572 delete d->conn;
573 d->conn = 0;
574
575 d->certList.clear();
576 d->usingSSL = false;
577
578 d->localAddress = QHostAddress();
579}
580
581bool PsiAccount::enabled() const
582{
583 return d->acc.opt_enabled;
584}
585
586void PsiAccount::setEnabled(bool e)
587{
588 if ( d->acc.opt_enabled == e )
589 return;
590
591 if (!e) {
592 if (eventQueue()->count()) {
593 QMessageBox::information(0, tr("Error"), tr("Unable to disable the account, as it has pending events."));
594 return;
595 }
596 if (isActive()) {
597 if (QMessageBox::information(0, tr("Disable Account"), tr("The account is currently active.\nDo you want to log out ?"),QMessageBox::Yes,QMessageBox::No | QMessageBox::Default | QMessageBox::Escape, QMessageBox::NoButton) == QMessageBox::Yes) {
598 logout();
599 }
600 else {
601 return;
602 }
603 }
604 }
605
606 d->setEnabled( e );
607}
608
609bool PsiAccount::isActive() const
610{
611 return v_isActive;
612}
613
614bool PsiAccount::isConnected() const
615{
616 return (d->stream && d->stream->isAuthenticated());
617}
618
619const QString & PsiAccount::name() const
620{
621 return d->acc.name;
622}
623
624const UserAccount & PsiAccount::userAccount() const
625{
626 d->psi->getToggles(&d->acc.tog_offline, &d->acc.tog_away, &d->acc.tog_agents, &d->acc.tog_hidden,&d->acc.tog_self);
627
628 // save the roster and pgp key bindings
629 d->acc.roster.clear();
630 d->acc.keybind.clear();
631 UserListIt it(d->userList);
632 for(UserListItem *u; (u = it.current()); ++it) {
633 if(u->inList())
634 d->acc.roster += *u;
635
636 if(!u->publicKeyID().isEmpty())
637 d->acc.keybind.set(u->jid().full(), u->publicKeyID());
638 }
639
640 return d->acc;
641}
642
643UserList *PsiAccount::userList() const
644{
645 return &d->userList;
646}
647
648Client *PsiAccount::client() const
649{
650 return d->client;
651}
652
653ContactProfile *PsiAccount::contactProfile() const
654{
655 return d->cp;
656}
657
658EventQueue *PsiAccount::eventQueue() const
659{
660 return d->eventQueue;
661}
662
663EDB *PsiAccount::edb() const
664{
665 return d->psi->edb();
666}
667
668PsiCon *PsiAccount::psi() const
669{
670 return d->psi;
671}
672
673AvatarFactory *PsiAccount::avatarFactory() const
674{
675 return d->avatarFactory;
676}
677
678QString PsiAccount::pgpKey() const
679{
680 return d->cur_pgpSecretKeyID;
681}
682
683QHostAddress *PsiAccount::localAddress() const
684{
685 QString s = d->localAddress.toString();
686 if(s == "0.0.0.0")
687 return 0;
688 return &d->localAddress;
689}
690
691void PsiAccount::setUserAccount(const UserAccount &acc)
692{
693 bool renamed = false;
694 QString oldfname;
695 if(d->acc.name != acc.name) {
696 renamed = true;
697 oldfname = d->pathToProfileEvents();
698 }
699
700 d->acc = acc;
701
702 // rename queue file?
703 if(renamed) {
704 QFileInfo oldfi(oldfname);
705 QFileInfo newfi(d->pathToProfileEvents());
706 if(oldfi.exists()) {
707 QDir dir = oldfi.dir();
708 dir.rename(oldfi.fileName(), newfi.fileName());
709 }
710 }
711
712 if(d->stream) {
713 if(d->acc.opt_keepAlive)
714 d->stream->setNoopTime(55000); // prevent NAT timeouts every minute
715 else
716 d->stream->setNoopTime(0);
717 }
718
719 d->cp->setName(d->acc.name);
720
721 Jid j = acc.jid;
722 d->nextJid = j;
723 if(!isActive()) {
724 d->jid = j;
725 d->self.setJid(j);
726 d->cp->updateEntry(d->self);
727 }
728 if(!d->nickFromVCard)
729 setNick(j.user());
730
731 d->self.setPublicKeyID(d->acc.pgpSecretKeyID);
732 if(d->psi->pgp()) {
733 if(d->acc.pgpSecretKeyID != d->cur_pgpSecretKeyID && loggedIn()) {
734 d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
735 d->loginStatus.setXSigned("");
736 setStatusDirect(d->loginStatus);
737 pgpKeyChanged();
738 }
739 }
740
741 cpUpdate(d->self);
742 updatedAccount();
743}
744
745void PsiAccount::deleteQueueFile()
746{
747 QFileInfo fi(d->pathToProfileEvents());
748 if(fi.exists()) {
749 QDir dir = fi.dir();
750 dir.remove(fi.fileName());
751 }
752}
753
754const Jid & PsiAccount::jid() const
755{
756 return d->jid;
757}
758
759QString PsiAccount::nameWithJid() const
760{
761 return (name() + " (" + jid().full() + ')');
762}
763
764static QCA::Cert readCertXml(const QDomElement &e)
765{
766 QCA::Cert cert;
767 // there should be one child data tag
768 QDomElement data = e.elementsByTagName("data").item(0).toElement();
769 if(!data.isNull())
770 cert.fromDER(Base64::stringToArray(data.text()));
771 return cert;
772}
773
774static QPtrList<QCA::Cert> getRootCerts(const QStringList &stores)
775{
776 QPtrList<QCA::Cert> list;
777
778 for(QStringList::ConstIterator dit = stores.begin(); dit != stores.end(); ++dit) {
779 QDir dir(*dit);
780 if(!dir.exists())
781 continue;
782 dir.setNameFilter("*.xml");
783 QStringList entries = dir.entryList();
784 for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
785 QFile f(dir.filePath(*it));
786 if(!f.open(IO_ReadOnly))
787 continue;
788 QDomDocument doc;
789 bool ok = doc.setContent(&f);
790 f.close();
791 if(!ok)
792 continue;
793
794 QDomElement base = doc.documentElement();
795 if(base.tagName() != "store")
796 continue;
797 QDomNodeList cl = base.elementsByTagName("certificate");
798
799 int num = 0;
800 for(int n = 0; n < (int)cl.count(); ++n) {
801 QCA::Cert *cert = new QCA::Cert(readCertXml(cl.item(n).toElement()));
802 if(cert->isNull()) {
803 delete cert;
804 continue;
805 }
806
807 ++num;
808 list.append(cert);
809 }
810 }
811 }
812
813 return list;
814}
815
816// logs on with the active account settings
817void PsiAccount::login()
818{
819 if(isActive() && !doReconnect)
820 return;
821
822 if(d->acc.opt_ssl && !QCA::isSupported(QCA::CAP_TLS)) {
823 QMessageBox::information(0, tr("%1: SSL Error").arg(name()), tr("Cannot login: SSL is enabled but no SSL/TLS (plugin) support is available."));
824 return;
825 }
826
827 d->jid = d->nextJid;
828 if(d->psi->pgp()) {
829 d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
830 pgpKeyChanged();
831 }
832
833 v_isActive = true;
834 isDisconnecting = false;
835 notifyOnlineOk = false;
836 rosterDone = false;
837 presenceSent = false;
838
839 stateChanged();
840
841 QString host;
842 int port;
843 if(d->acc.opt_host) {
844 host = d->acc.host;
845 port = d->acc.port;
846 }
847 else {
848 host = d->jid.host();
849 if(d->acc.opt_ssl)
850 port = 5223;
851 else
852 port = 5222;
853 }
854
855 AdvancedConnector::Proxy p;
856 if(d->acc.proxy_index > 0) {
857 const ProxyItem &pi = d->psi->proxy()->getItem(d->acc.proxy_index-1);
858 if(pi.type == "http") // HTTP Connect
859 p.setHttpConnect(pi.settings.host, pi.settings.port);
860 else if(pi.type == "socks") // SOCKS
861 p.setSocks(pi.settings.host, pi.settings.port);
862 else if(pi.type == "poll") { // HTTP Poll
863 QUrl u = pi.settings.url;
864 if(u.query().isEmpty()) {
865 QString v = host + ':' + QString::number(port);
866 QUrl::encode(v);
867 u.setQuery(QString("server=") + v);
868 }
869 p.setHttpPoll(pi.settings.host, pi.settings.port, u.toString());
870 p.setPollInterval(2);
871 }
872
873 if(pi.settings.useAuth)
874 p.setUserPass(pi.settings.user, pi.settings.pass);
875 }
876
877 // stream
878 d->conn = new AdvancedConnector;
879 if(d->acc.opt_ssl) {
880 QStringList certDirs;
881 certDirs += g.pathHome + "/certs";
882 certDirs += g.pathBase + "/certs";
883 d->certList = getRootCerts(certDirs);
884
885 d->tls = new QCA::TLS;
886 d->tls->setCertificateStore(d->certList);
887 d->tlsHandler = new QCATLSHandler(d->tls);
888 connect(d->tlsHandler, SIGNAL(tlsHandshaken()), SLOT(tls_handshaken()));
889 }
890 d->conn->setProxy(p);
891 d->conn->setOptHostPort(host, port);
892 d->conn->setOptSSL(d->acc.opt_ssl);
893
894 d->stream = new ClientStream(d->conn, d->tlsHandler);
895 d->stream->setOldOnly(true);
896 d->stream->setAllowPlain(d->acc.opt_plain);
897 if(d->acc.opt_keepAlive)
898 d->stream->setNoopTime(55000); // prevent NAT timeouts every minute
899 else
900 d->stream->setNoopTime(0);
901 connect(d->stream, SIGNAL(connected()), SLOT(cs_connected()));
902 connect(d->stream, SIGNAL(securityLayerActivated(int)), SLOT(cs_securityLayerActivated()));
903 connect(d->stream, SIGNAL(needAuthParams(bool, bool, bool)), SLOT(cs_needAuthParams(bool, bool, bool)));
904 connect(d->stream, SIGNAL(authenticated()), SLOT(cs_authenticated()));
905 connect(d->stream, SIGNAL(connectionClosed()), SLOT(cs_connectionClosed()));
906 connect(d->stream, SIGNAL(delayedCloseFinished()), SLOT(cs_delayedCloseFinished()));
907 connect(d->stream, SIGNAL(warning(int)), SLOT(cs_warning(int)));
908 connect(d->stream, SIGNAL(error(int)), SLOT(cs_error(int)));
909
910 Jid j = d->jid.withResource(d->acc.resource);
911 d->client->connectToServer(d->stream, j);
912}
913
914// disconnect or stop reconnecting
915void PsiAccount::logout(bool fast, const Status &s)
916{
917 if(!isActive())
918 return;
919
920 // cancel reconnect
921 doReconnect = false;
922
923 if(loggedIn()) {
924 // send logout status
925 d->client->setPresence(s);
926 }
927
928 isDisconnecting = true;
929
930 if(!fast)
931 simulateRosterOffline();
932 v_isActive = false;
933 stateChanged();
934
935 QTimer::singleShot(0, this, SLOT(disconnect()));
936}
937
938// skz note: I had to split logout() because server seem to need some time to store status
939// before stream is closed (weird, I know)
940void PsiAccount::disconnect()
941{
942 // disconnect
943 d->client->close();
944 cleanupStream();
945
946 disconnected();
947}
948
949bool PsiAccount::loggedIn() const
950{
951 return (v_isActive && presenceSent);
952}
953
954void PsiAccount::tls_handshaken()
955{
956 QCA::Cert cert = d->tls->peerCertificate();
957 int r = d->tls->certificateValidityResult();
958 if(r != QCA::TLS::Valid && !d->acc.opt_ignoreSSLWarnings) {
959 QString str = resultToString(r);
960 while(1) {
961 int n = QMessageBox::warning(0,
962 tr("%1: Server Authentication").arg(name()),
963 tr("The %1 certificate failed the authenticity test.").arg(d->jid.host()) + '\n' + tr("Reason: %1").arg(str),
964 tr("&Details..."),
965 tr("Co&ntinue"),
966 tr("&Cancel"), 0, 2);
967 if(n == 0) {
968 SSLCertDlg::showCert(cert, r);
969 }
970 else if(n == 1) {
971 d->tlsHandler->continueAfterHandshake();
972 break;
973 }
974 else if(n == 2) {
975 logout();
976 break;
977 }
978 }
979 }
980 else
981 d->tlsHandler->continueAfterHandshake();
982}
983
984void PsiAccount::cs_connected()
985{
986 // get IP address
987 ByteStream *bs = d->conn->stream();
988 if(bs->inherits("BSocket") || bs->inherits("XMPP::BSocket")) {
989//#if QT_VERSION >= 0x030300
990 d->localAddress = ((BSocket *)bs)->address();
991/*#else
992 int s = ((BSocket *)bs)->socket();
993 struct sockaddr addr;
994 socklen_t size = sizeof(struct sockaddr);
995 if(!getsockname(s, &addr, &size)) {
996 Q_UINT32 ipv4addr;
997 struct sockaddr_in *in = (struct sockaddr_in *)&addr;
998 memcpy(&ipv4addr, &in->sin_addr.s_addr, 4);
999 ipv4addr = ntohl(ipv4addr);
1000 d->localAddress = QHostAddress(ipv4addr);
1001 }
1002#endif*/
1003 }
1004}
1005
1006void PsiAccount::cs_securityLayerActivated()
1007{
1008 d->usingSSL = true;
1009 stateChanged();
1010}
1011
1012void PsiAccount::cs_needAuthParams(bool, bool pass, bool)
1013{
1014 if(pass)
1015 d->stream->setPassword(d->acc.pass);
1016 d->stream->continueAfterParams();
1017}
1018
1019void PsiAccount::cs_authenticated()
1020{
1021 d->conn->changePollInterval(10); // for http poll, slow down after login
1022
1023 d->client->start(d->jid.host(), d->jid.user(), d->acc.pass, d->acc.resource);
1024
1025 //printf("PsiAccount: [%s] authenticated\n", name().latin1());
1026
1027 // flag roster for delete
1028 UserListIt it(d->userList);
1029 for(UserListItem *u; (u = it.current()); ++it) {
1030 if(u->inList())
1031 u->setFlagForDelete(true);
1032 }
1033
1034 // ask for roster
1035 d->client->rosterRequest();
1036}
1037
1038void PsiAccount::cs_connectionClosed()
1039{
1040 cs_error(-1);
1041}
1042
1043void PsiAccount::cs_delayedCloseFinished()
1044{
1045 //printf("PsiAccount: [%s] connection closed\n", name().latin1());
1046}
1047
1048void PsiAccount::cs_warning(int)
1049{
1050 d->stream->continueAfterWarning();
1051}
1052
1053void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn)
1054{
1055 QString str;
1056 bool reconn = false;
1057
1058 if(err == -1) {
1059 str = tr("Disconnected");
1060 reconn = true;
1061 }
1062 else if(err == XMPP::ClientStream::ErrParse) {
1063 str = tr("XML Parsing Error");
1064 reconn = true;
1065 }
1066 else if(err == XMPP::ClientStream::ErrProtocol) {
1067 str = tr("XMPP Protocol Error");
1068 reconn = true;
1069 }
1070 else if(err == XMPP::ClientStream::ErrStream) {
1071 int x = stream->errorCondition();
1072 QString s;
1073 reconn = true;
1074 if(x == XMPP::Stream::GenericStreamError)
1075 s = tr("Generic stream error");
1076 else if(x == XMPP::ClientStream::Conflict) {
1077 s = tr("Conflict (remote login replacing this one)");
1078 reconn = false;
1079 }
1080 else if(x == XMPP::ClientStream::ConnectionTimeout)
1081 s = tr("Timed out from inactivity");
1082 else if(x == XMPP::ClientStream::InternalServerError)
1083 s = tr("Internal server error");
1084 else if(x == XMPP::ClientStream::InvalidXml)
1085 s = tr("Invalid XML");
1086 else if(x == XMPP::ClientStream::PolicyViolation) {
1087 s = tr("Policy violation");
1088 reconn = false;
1089 }
1090 else if(x == XMPP::ClientStream::ResourceConstraint) {
1091 s = tr("Server out of resources");
1092 reconn = false;
1093 }
1094 else if(x == XMPP::ClientStream::SystemShutdown)
1095 s = tr("Server is shutting down");
1096 str = tr("XMPP Stream Error: %1").arg(s);
1097 }
1098 else if(err == XMPP::ClientStream::ErrConnection) {
1099 int x = conn->errorCode();
1100 QString s;
1101 reconn = true;
1102 if(x == XMPP::AdvancedConnector::ErrConnectionRefused)
1103 s = tr("Unable to connect to server");
1104 else if(x == XMPP::AdvancedConnector::ErrHostNotFound)
1105 s = tr("Host not found");
1106 else if(x == XMPP::AdvancedConnector::ErrProxyConnect)
1107 s = tr("Error connecting to proxy");
1108 else if(x == XMPP::AdvancedConnector::ErrProxyNeg)
1109 s = tr("Error during proxy negotiation");
1110 else if(x == XMPP::AdvancedConnector::ErrProxyAuth) {
1111 s = tr("Proxy authentication failed");
1112 reconn = false;
1113 }
1114 else if(x == XMPP::AdvancedConnector::ErrStream)
1115 s = tr("Socket/stream error");
1116 str = tr("Connection Error: %1").arg(s);
1117 }
1118 else if(err == XMPP::ClientStream::ErrNeg) {
1119 int x = stream->errorCondition();
1120 QString s;
1121 if(x == XMPP::ClientStream::HostGone)
1122 s = tr("Host no longer hosted");
1123 else if(x == XMPP::ClientStream::HostUnknown)
1124 s = tr("Host unknown");
1125 else if(x == XMPP::ClientStream::RemoteConnectionFailed) {
1126 s = tr("A required remote connection failed");
1127 reconn = true;
1128 }
1129 else if(x == XMPP::ClientStream::SeeOtherHost)
1130 s = tr("See other host: %1").arg(stream->errorText());
1131 else if(x == XMPP::ClientStream::UnsupportedVersion)
1132 s = tr("Server does not support proper XMPP version");
1133 str = tr("Stream Negotiation Error: %1").arg(s);
1134 }
1135 else if(err == XMPP::ClientStream::ErrTLS) {
1136 int x = stream->errorCondition();
1137 QString s;
1138 if(x == XMPP::ClientStream::TLSStart)
1139 s = tr("Server rejected STARTTLS");
1140 else if(x == XMPP::ClientStream::TLSFail) {
1141 int t = tlsHandler->tlsError();
1142 if(t == QCA::TLS::ErrHandshake)
1143 s = tr("TLS handshake error");
1144 else
1145 s = tr("Broken security layer (TLS)");
1146 }
1147 str = s;
1148 }
1149 else if(err == XMPP::ClientStream::ErrAuth) {
1150 int x = stream->errorCondition();
1151 QString s;
1152 if(x == XMPP::ClientStream::GenericAuthError)
1153 s = tr("Unable to login");
1154 else if(x == XMPP::ClientStream::NoMech)
1155 s = tr("No appropriate mechanism available for given security settings");
1156 else if(x == XMPP::ClientStream::BadProto)
1157 s = tr("Bad server response");
1158 else if(x == XMPP::ClientStream::BadServ)
1159 s = tr("Server failed mutual authentication");
1160 else if(x == XMPP::ClientStream::EncryptionRequired)
1161 s = tr("Encryption required for chosen SASL mechanism");
1162 else if(x == XMPP::ClientStream::InvalidAuthzid)
1163 s = tr("Invalid account information");
1164 else if(x == XMPP::ClientStream::InvalidMech)
1165 s = tr("Invalid SASL mechanism");
1166 else if(x == XMPP::ClientStream::InvalidRealm)
1167 s = tr("Invalid realm");
1168 else if(x == XMPP::ClientStream::MechTooWeak)
1169 s = tr("SASL mechanism too weak for this account");
1170 else if(x == XMPP::ClientStream::NotAuthorized)
1171 s = tr("Not authorized");
1172 else if(x == XMPP::ClientStream::TemporaryAuthFailure)
1173 s = tr("Temporary auth failure");
1174 str = tr("Authentication error: %1").arg(s);
1175 }
1176 else if(err == XMPP::ClientStream::ErrSecurityLayer)
1177 str = tr("Broken security layer (SASL)");
1178 else
1179 str = tr("None");
1180 //printf("str[%s], reconn=%d\n", str.latin1(), reconn);
1181 *_str = str;
1182 *_reconn = reconn;
1183}
1184
1185void PsiAccount::cs_error(int err)
1186{
1187 QString str;
1188 bool reconn;
1189
1190 getErrorInfo(err, d->conn, d->stream, d->tlsHandler, &str, &reconn);
1191
1192 d->client->close();
1193 cleanupStream();
1194
1195 //printf("Error: [%s]\n", str.latin1());
1196
1197 isDisconnecting = true;
1198
1199 if ( loggedIn() ) { // FIXME: is this condition okay?
1200 simulateRosterOffline();
1201 }
1202
1203 presenceSent = false; // this stops the idle detector?? (FIXME)
1204
1205 // Auto-Reconnect?
1206 if(d->acc.opt_reconn && reconn) {
1207 // reconnect in 5 seconds
1208 doReconnect = true;
1209 stateChanged();
1210 QTimer::singleShot(5000, this, SLOT(reconnect()));
1211 return;
1212 }
1213
1214 v_isActive = false;
1215 stateChanged();
1216 disconnected();
1217
1218 QMessageBox::critical(0, tr("%1: Server Error").arg(name()), tr("There was an error communicating with the Jabber server.\nDetails: %1").arg(str));
1219}
1220
1221void PsiAccount::client_rosterRequestFinished(bool success, int, const QString &)
1222{
1223 if(success) {
1224 //printf("PsiAccount: [%s] roster retrieved ok. %d entries.\n", name().latin1(), d->client->roster().count());
1225
1226 // delete flagged items
1227 UserListIt it(d->userList);
1228 for(UserListItem *u; (u = it.current());) {
1229 if(u->flagForDelete()) {
1230 //QMessageBox::information(0, "blah", QString("deleting: [%1]").arg(u->jid().full()));
1231
1232 d->eventQueue->clear(u->jid());
1233 updateReadNext(u->jid());
1234
1235 d->cp->removeEntry(u->jid());
1236 d->userList.removeRef(u);
1237 }
1238 else
1239 ++it;
1240 }
1241 }
1242 else {
1243 //printf("PsiAccount: [%s] error retrieving roster: [%d, %s]\n", name().latin1(), code, str.latin1());
1244 }
1245
1246 rosterDone = true;
1247 setStatusDirect(d->loginStatus);
1248}
1249
1250void PsiAccount::resolveContactName()
1251{
1252 JT_VCard *j = (JT_VCard *)sender();
1253 if ( j->success() ) {
1254 QString nick = j->vcard().nickName();
1255 if ( !nick.isEmpty() ) {
1256 actionRename( j->jid(), nick );
1257 }
1258 }
1259}
1260
1261void PsiAccount::client_rosterItemAdded(const RosterItem &r)
1262{
1263 if ( r.isPush() && r.name().isEmpty() && option.autoResolveNicksOnAdd ) {
1264 // automatically resolve nickname from vCard, if newly added item doesn't have any
1265 VCardFactory::getVCard(r.jid(), d->client->rootTask(), this, SLOT(resolveContactName()));
1266 }
1267}
1268
1269void PsiAccount::client_rosterItemUpdated(const RosterItem &r)
1270{
1271 // see if the item added is already in our local list
1272 UserListItem *u = d->userList.find(r.jid());
1273 if(u) {
1274 u->setFlagForDelete(false);
1275 u->setRosterItem(r);
1276 }
1277 else {
1278 // we don't have it at all, so add it
1279 u = new UserListItem;
1280 u->setRosterItem(r);
1281#ifdef AVATARS
1282 u->setAvatarFactory(d->avatarFactory);
1283#endif
1284 d->userList.append(u);
1285 }
1286 u->setInList(true);
1287
1288 d->cp->updateEntry(*u);
1289}
1290
1291void PsiAccount::client_rosterItemRemoved(const RosterItem &r)
1292{
1293 UserListItem *u = d->userList.find(r.jid());
1294 if(!u)
1295 return;
1296
1297 simulateContactOffline(u);
1298
1299 // if the item has messages queued, then move them to 'not in list'
1300 if(d->eventQueue->count(r.jid()) > 0) {
1301 u->setInList(false);
1302 d->cp->updateEntry(*u);
1303 }
1304 // else remove them for good!
1305 else {
1306 d->cp->removeEntry(u->jid());
1307 d->userList.removeRef(u);
1308 }
1309}
1310
1311void PsiAccount::tryVerify(UserListItem *u, UserResource *ur)
1312{
1313 if(d->psi->pgp())
1314 verifyStatus(u->jid().withResource(ur->name()), ur->status());
1315}
1316
1317void PsiAccount::client_resourceAvailable(const Jid &j, const Resource &r)
1318{
1319 enum PopupType {
1320 PopupOnline = 0,
1321 PopupStatusChange = 1
1322 };
1323 PopupType popupType = PopupOnline;
1324
1325 if ( j.user().isEmpty() )
1326 new BlockTransportPopup(d->blockTransportPopupList, j);
1327
1328 bool doSound = false;
1329 bool doPopup = false;
1330 QPtrList<UserListItem> list = findRelavent(j);
1331 QPtrListIterator<UserListItem> it(list);
1332 for(UserListItem *u; (u = it.current()); ++it) {
1333 bool doAnim = false;
1334 bool local = false;
1335 if(u->isSelf() && r.name() == d->client->resource())
1336 local = true;
1337
1338 // add/update the resource
1339 QString oldStatus, oldKey;
1340 UserResource *rp;
1341 UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
1342 bool found = (rit == u->userResourceList().end()) ? false: true;
1343 if(!found) {
1344 popupType = PopupOnline;
1345
1346 UserResource ur(r);
1347 //ur.setSecurityEnabled(true);
1348 if(local)
1349 ur.setClient(PROG_NAME,PROG_VERSION,getOSName());
1350 rp = &(*u->userResourceList().append(ur));
1351
1352 if(notifyOnlineOk && !local) {
1353 doAnim = true;
1354 if (!u->isHidden()) {
1355 doSound = true;
1356 doPopup = true;
1357 }
1358 }
1359
1360 if(!local && option.autoVersion && !status().isInvisible()) {
1361 // do a client-version request (too easy!)
1362 JT_ClientVersion *jcv = new JT_ClientVersion(d->client->rootTask());
1363 connect(jcv, SIGNAL(finished()), SLOT(slotClientVersionFinished()));
1364 jcv->get(j);
1365 jcv->go(true);
1366 }
1367 }
1368 else {
1369 if ( !doPopup )
1370 popupType = PopupStatusChange;
1371
1372 oldStatus = (*rit).status().status();
1373 oldKey = (*rit).status().keyID();
1374 rp = &(*rit);
1375
1376 (*rit).setResource(r);
1377
1378 if (!local && !u->isHidden())
1379 doPopup = true;
1380 }
1381
1382 rp->setPGPVerifyStatus(-1);
1383 if(!rp->status().xsigned().isEmpty())
1384 tryVerify(u, rp);
1385
1386 u->setPresenceError("");
1387 cpUpdate(*u, r.name(), true);
1388
1389 if(doAnim && option.rosterAnim)
1390 d->cp->animateNick(u->jid());
1391 }
1392
1393 if(doSound)
1394 playSound(option.onevent[eOnline]);
1395
1396#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
1397 // Do the popup test earlier (to avoid needless JID lookups)
1398 if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus))
1399#endif
1400 if(notifyOnlineOk && doPopup && d->doPopups && !d->blockTransportPopupList->find(j, popupType == PopupOnline) && makeSTATUS(status()) != STATUS_DND ) {
1401 QString name;
1402 UserListItem *u = findFirstRelavent(j);
1403
1404 PsiPopup::PopupType pt = PsiPopup::AlertNone;
1405 if ( popupType == PopupOnline )
1406 pt = PsiPopup::AlertOnline;
1407 else if ( popupType == PopupStatusChange )
1408 pt = PsiPopup::AlertStatusChange;
1409
1410 if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus)) {
1411 PsiPopup *popup = new PsiPopup(pt, this);
1412 popup->setData(j, r, u);
1413 }
1414#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
1415 PsiGrowlNotifier::instance()->popup(this, pt, j, r, u);
1416#endif
1417 }
1418 else if ( !notifyOnlineOk )
1419 d->userCounter++;
1420}
1421
1422void PsiAccount::client_resourceUnavailable(const Jid &j, const Resource &r)
1423{
1424 bool doSound = false;
1425 bool doPopup = false;
1426
1427 if ( j.user().isEmpty() )
1428 new BlockTransportPopup(d->blockTransportPopupList, j);
1429
1430 QPtrList<UserListItem> list = findRelavent(j);
1431 QPtrListIterator<UserListItem> it(list);
1432 for(UserListItem *u; (u = it.current()); ++it) {
1433 bool local = false;
1434 if(u->isSelf() && r.name() == d->client->resource())
1435 local = true;
1436
1437 // remove resource
1438 UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
1439 bool found = (rit == u->userResourceList().end()) ? false: true;
1440 if(found) {
1441 u->setLastUnavailableStatus(r.status());
1442 u->userResourceList().remove(rit);
1443
1444 if(!u->isAvailable())
1445 u->setLastAvailable(QDateTime::currentDateTime());
1446
1447 if(!u->isAvailable() || u->isSelf()) {
1448 // don't sound for our own resource
1449 if(!isDisconnecting && !local && !u->isHidden()) {
1450 doSound = true;
1451 doPopup = true;
1452 }
1453 }
1454 }
1455
1456 u->setPresenceError("");
1457 cpUpdate(*u, r.name(), true);
1458 }
1459 if(doSound)
1460 playSound(option.onevent[eOffline]);
1461
1462#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
1463 // Do the popup test earlier (to avoid needless JID lookups)
1464 if (option.ppOffline)
1465#endif
1466 if(doPopup && d->doPopups && !d->blockTransportPopupList->find(j) && makeSTATUS(status()) != STATUS_DND ) {
1467 QString name;
1468 UserListItem *u = findFirstRelavent(j);
1469
1470 if (option.ppOffline) {
1471 PsiPopup *popup = new PsiPopup(PsiPopup::AlertOffline, this);
1472 popup->setData(j, r, u);
1473 }
1474#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
1475 PsiGrowlNotifier::instance()->popup(this, PsiPopup::AlertOffline, j, r, u);
1476#endif
1477 }
1478}
1479
1480void PsiAccount::client_presenceError(const Jid &j, int, const QString &str)
1481{
1482 QPtrList<UserListItem> list = findRelavent(j);
1483 QPtrListIterator<UserListItem> it(list);
1484 for(UserListItem *u; (u = it.current()); ++it) {
1485 simulateContactOffline(u);
1486 u->setPresenceError(str);
1487 cpUpdate(*u, j.resource(), false);
1488 }
1489}
1490
1491void PsiAccount::client_messageReceived(const Message &m)
1492{
1493 //check if it's a server message without a from, and set the from appropriately
1494 Message _m(m);
1495 if (_m.from().isEmpty())
1496 {
1497 _m.setFrom(jid().domain());
1498 }
1499
1500 // if the sender is already in the queue, then queue this message also
1501 QPtrListIterator<Message> it(d->messageQueue);
1502 for(Message *mi; (mi = it.current()); ++it) {
1503 if(mi->from().compare(_m.from())) {
1504 Message *m = new Message(_m);
1505 d->messageQueue.append(m);
1506 return;
1507 }
1508 }
1509
1510 // encrypted message?
1511 if(!pgpKey().isEmpty() && !_m.xencrypted().isEmpty()) {
1512 Message *m = new Message(_m);
1513 d->messageQueue.append(m);
1514 processMessageQueue();
1515 return;
1516 }
1517
1518 processIncomingMessage(_m);
1519}
1520
1521void PsiAccount::processIncomingMessage(const Message &_m)
1522{
1523 // skip empty messages
1524 if(_m.body().isEmpty() && _m.urlList().isEmpty() && _m.invite().isEmpty() && !_m.containsEvents())
1525 return;
1526
1527 // skip headlines?
1528 if(_m.type() == "headline" && option.ignoreHeadline)
1529 return;
1530
1531 //skip if on ignore list? KEVIN, must not forget
1532
1533 if(_m.type() == "groupchat") {
1534 GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost()));
1535 if(w)
1536 w->message(_m);
1537 return;
1538 }
1539
1540 // only toggle if not an invite or body is not empty
1541 if(_m.invite().isEmpty() && !_m.body().isEmpty())
1542 toggleSecurity(_m.from(), _m.wasEncrypted());
1543
1544 UserListItem *u = findFirstRelavent(_m.from());
1545 if(u) {
1546 if(_m.type() == "chat") u->setLastMessageType(1);
1547 else u->setLastMessageType(0);
1548 }
1549
1550 Message m = _m;
1551
1552 // smartchat: try to match up the incoming event to an existing chat
1553 // (prior to 0.9, m.from() always contained a resource)
1554 Jid j;
1555 ChatDlg *c;
1556 QPtrList<UserListItem> ul = findRelavent(m.from());
1557
1558 // ignore events from non-roster JIDs?
1559 if (ul.isEmpty() && option.ignoreNonRoster)
1560 {
1561 if (option.excludeGroupChatsFromIgnore)
1562 {
1563 GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost()));
1564 if(!w)
1565 {
1566 return;
1567 }
1568
1569 }
1570 else
1571 {
1572 return;
1573 }
1574 }
1575
1576 if(ul.isEmpty())
1577 j = m.from().userHost();
1578 else
1579 j = ul.first()->jid();
1580
1581 c = (ChatDlg *)dialogFind("ChatDlg", j);
1582 if(!c)
1583 c = (ChatDlg *)dialogFind("ChatDlg", m.from().full());
1584
1585 if(m.type() == "error")
1586 m.setBody(m.error().text + "\n------\n" + m.body());
1587
1588 // change the type?
1589 if(m.type() != "headline" && m.invite().isEmpty()) {
1590 if(option.incomingAs == 1)
1591 m.setType("");
1592 else if(option.incomingAs == 2)
1593 m.setType("chat");
1594 else if(option.incomingAs == 3) {
1595 if(c != NULL && !c->isHidden())
1596 m.setType("chat");
1597 else
1598 m.setType("");
1599 }
1600 }
1601
1602 // urls or subject on a chat message? convert back to regular message
1603 //if(m.type() == "chat" && (!m.urlList().isEmpty() || !m.subject().isEmpty()))
1604 // m.setType("");
1605
1606 MessageEvent *me = new MessageEvent(m, this);
1607 me->setOriginLocal(false);
1608 handleEvent(me);
1609}
1610
1611void PsiAccount::client_subscription(const Jid &j, const QString &str)
1612{
1613 // if they remove our subscription, then we lost presence
1614 if(str == "unsubscribed") {
1615 UserListItem *u = d->userList.find(j);
1616 if(u)
1617 simulateContactOffline(u);
1618 }
1619
1620 AuthEvent *ae = new AuthEvent(j, str, this);
1621 ae->setTimeStamp(QDateTime::currentDateTime());
1622 handleEvent(ae);
1623}
1624
1625void PsiAccount::client_debugText(const QString &)
1626{
1627 //printf("%s", str.latin1());
1628 //fflush(stdout);
1629}
1630
1631void PsiAccount::client_incomingJidLink()
1632{
1633 JidLink *jl = d->client->jidLinkManager()->takeIncoming();
1634 if(!jl)
1635 return;
1636 //if(link_test) {
1637 // printf("[%s] -- Incoming JidLink request from [%s]\n", name().latin1(), jl->peer().full().latin1());
1638 // JLTestDlg *w = new JLTestDlg(jl->peer(), jl, this);
1639 // w->show();
1640 //}
1641 //else
1642 jl->deleteLater();
1643}
1644
1645void PsiAccount::client_incomingFileTransfer()
1646{
1647 FileTransfer *ft = d->client->fileTransferManager()->takeIncoming();
1648 if(!ft)
1649 return;
1650
1651 /*printf("psiaccount: incoming file transfer:\n");
1652 printf(" From: [%s]\n", ft->peer().full().latin1());
1653 printf(" Name: [%s]\n", ft->fileName().latin1());
1654 printf(" Size: %d bytes\n", ft->fileSize());*/
1655
1656 FileEvent *fe = new FileEvent(ft->peer().full(), ft, this);
1657 fe->setTimeStamp(QDateTime::currentDateTime());
1658 handleEvent(fe);
1659}
1660
1661void PsiAccount::reconnect()
1662{
1663 if(doReconnect) {
1664 //printf("PsiAccount: [%s] reconnecting...\n", name().latin1());
1665 v_isActive = false;
1666 doReconnect = false;
1667 login();
1668 }
1669}
1670
1671Status PsiAccount::status() const
1672{
1673 return d->loginStatus;
1674}
1675
1676void PsiAccount::setStatus(const Status &_s)
1677{
1678 // Block all transports' contacts' status change popups from popping
1679 {
1680 Roster::ConstIterator rit = d->acc.roster.begin();
1681 for ( ; rit != d->acc.roster.end(); ++rit) {
1682 const RosterItem &i = *rit;
1683 if ( i.jid().user().isEmpty() /*&& i.jid().resource() == "registered"*/ ) // it is very likely then, that it's transport
1684 new BlockTransportPopup(d->blockTransportPopupList, i.jid());
1685 }
1686 }
1687
1688 // cancel auto-status and reconnect
1689 usingAutoStatus = false;
1690 doReconnect = false;
1691
1692 Status s = _s;
1693 s.setPriority(d->acc.priority);
1694
1695 d->loginStatus = s;
1696
1697 if(s.isAvailable()) {
1698 // if client is not active then attempt to login
1699 if(!isActive()) {
1700 Jid j = d->jid;
1701 if(d->acc.resource.isEmpty() || !j.isValid()) {
1702 QMessageBox::information(0, CAP(tr("Error")), tr("Unable to login. Ensure your account information is filled out."));
1703 modify();
1704 return;
1705 }
1706 if(!d->acc.opt_pass) {
1707 bool ok = false;
1708 QString text = QInputDialog::getText(
1709 tr("Need Password"),
1710 tr("Please enter the password for %1:").arg(j.full()),
1711 QLineEdit::Password, QString::null, &ok, 0);
1712 if(ok && !text.isEmpty())
1713 d->acc.pass = text;
1714 else
1715 return;
1716 }
1717
1718 login();
1719 }
1720 // change status
1721 else {
1722 if(rosterDone)
1723 setStatusDirect(s);
1724
1725 if(s.isInvisible()) {//&&Pass invis to transports KEVIN
1726 //this is a nasty hack to let the transports know we're invisible, since they get an offline packet when we go invisible
1727 QPtrListIterator<UserListItem> it(d->userList);
1728 for(UserListItem *u; (u = it.current()); ++it) {
1729 if(u->isTransport()) {
1730 JT_Presence *j = new JT_Presence(d->client->rootTask());
1731 j->pres(u->jid(), s);
1732 j->go(true);
1733 }
1734 }
1735 }
1736 }
1737 }
1738 else {
1739 if(isActive())
1740 logout(false, s);
1741 }
1742}
1743
1744void PsiAccount::setStatusDirect(const Status &_s)
1745{
1746 Status s = _s;
1747 s.setPriority(d->acc.priority);
1748
1749 //printf("setting status to [%s]\n", s.status().latin1());
1750
1751 // using pgp?
1752 if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty()) {
1753 d->loginStatus = s;
1754
1755 // sign presence
1756 trySignPresence();
1757 }
1758 else {
1759 /*if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty())
1760 s.setKeyID(d->cur_pgpSecretKeyID);
1761 else
1762 s.setKeyID("");*/
1763
1764 // send presence normally
1765 setStatusActual(s);
1766 }
1767}
1768
1769void PsiAccount::setStatusActual(const Status &s)
1770{
1771 d->loginStatus = s;
1772
1773 d->client->setPresence(s);
1774 if(presenceSent) {
1775 stateChanged();
1776 }
1777 else {
1778 presenceSent = true;
1779 stateChanged();
1780 QTimer::singleShot(15000, this, SLOT(enableNotifyOnline()));
1781
1782 const VCard *vcard = VCardFactory::vcard(d->jid);
1783 if ( option.autoVCardOnLogin || !vcard || vcard->isEmpty() || vcard->nickName().isEmpty() )
1784 VCardFactory::getVCard(d->jid, d->client->rootTask(), this, SLOT(slotCheckVCard()));
1785 else {
1786 d->nickFromVCard = true;
1787 setNick( vcard->nickName() );
1788 }
1789 }
1790}
1791
1792void PsiAccount::secondsIdle(int x)
1793{
1794 if(!loggedIn())
1795 return;
1796
1797 int lastIdle = d->lastIdle;
1798 Status lastStatus = d->lastStatus;
1799
1800 Resource r = *(d->client->resourceList().find(d->client->resource()));
1801 Status ls = r.status();
1802
1803 //must avoid status change when invisible KEVIN
1804 if(ls.isInvisible())
1805 return;
1806
1807 if(ls.isAvailable()) {
1808 // no longer idle?
1809 if(lastIdle > x) {
1810 if(ls.isAway() && usingAutoStatus) {
1811 lastStatus = d->origStatus;
1812 setStatusDirect(lastStatus);
1813 usingAutoStatus = false;
1814 }
1815 }
1816 else if( !(ls.isAway() && !usingAutoStatus) ) {
1817 int minutes = x / 60;
1818
1819 if(option.use_asOffline && option.asOffline > 0 && minutes >= option.asOffline) {
1820 lastStatus = Status("", "", d->acc.priority, false);
1821 usingAutoStatus = false;
1822 logout();
1823 }
1824 else if(option.use_asXa && option.asXa > 0 && minutes >= option.asXa) {
1825 if(ls.show() != "xa" && lastStatus.show() != "xa") {
1826 lastStatus = Status("xa", option.asMessage, d->acc.priority);
1827 if(!usingAutoStatus)
1828 d->origStatus = d->loginStatus;
1829 setStatusDirect(lastStatus);
1830 usingAutoStatus = true;
1831 }
1832 }
1833 else if(option.use_asAway && option.asAway > 0 && minutes >= option.asAway) {
1834 if(ls.show() != "away" && lastStatus.show() != "away") {
1835 lastStatus = Status("away", option.asMessage, d->acc.priority);
1836 if(!usingAutoStatus)
1837 d->origStatus = d->loginStatus;
1838 setStatusDirect(lastStatus);
1839 usingAutoStatus = true;
1840 }
1841 }
1842 }
1843 }
1844
1845 d->lastIdle = x;
1846 d->lastStatus = lastStatus;
1847}
1848
1849void PsiAccount::playSound(const QString &str)
1850{
1851 if(str.isEmpty())
1852 return;
1853
1854 int s = STATUS_OFFLINE;
1855 if(loggedIn())
1856 s = makeSTATUS(status());
1857
1858 if(s == STATUS_DND)
1859 return;
1860
1861 // no away sounds?
1862 if(option.noAwaySound && (s == STATUS_AWAY || s == STATUS_XA))
1863 return;
1864
1865 d->psi->playSound(str);
1866}
1867
1868QWidget *PsiAccount::dialogFind(const char *className, const Jid &j)
1869{
1870 QPtrListIterator<item_dialog2> it(d->dialogList);
1871 for(item_dialog2 *i; (i = it.current()); ++it) {
1872 // does the classname and jid match?
1873 if(i->className == className && i->jid.compare(j)) {
1874 return i->widget;
1875 }
1876 }
1877 return 0;
1878}
1879
1880void PsiAccount::dialogRegister(QWidget *w, const Jid &j)
1881{
1882 item_dialog2 *i = new item_dialog2;
1883 i->widget = w;
1884 i->className = w->className();
1885 i->jid = j;
1886 d->dialogList.append(i);
1887}
1888
1889void PsiAccount::dialogUnregister(QWidget *w)
1890{
1891 QPtrListIterator<item_dialog2> it(d->dialogList);
1892 for(item_dialog2 *i; (i = it.current()); ++it) {
1893 if(i->widget == w) {
1894 d->dialogList.removeRef(i);
1895 return;
1896 }
1897 }
1898}
1899
1900void PsiAccount::deleteAllDialogs()
1901{
1902 QPtrListIterator<item_dialog2> it(d->dialogList);
1903 for(item_dialog2 *i; (i = it.current());)
1904 delete i->widget;
1905 d->dialogList.clear();
1906}
1907
1908bool PsiAccount::checkConnected(QWidget *par)
1909{
1910 if(!loggedIn()) {
1911 QMessageBox::information(par, CAP(tr("Error")), tr("You must be connected to the server in order to do this."));
1912 return false;
1913 }
1914
1915 return true;
1916}
1917
1918void PsiAccount::modify()
1919{
1920 AccountModifyDlg *w = (AccountModifyDlg *)dialogFind("AccountModifyDlg");
1921 if(w)
1922 bringToFront(w);
1923 else {
1924 w = new AccountModifyDlg(this, 0);
1925 w->show();
1926 }
1927}
1928
1929void PsiAccount::changeVCard()
1930{
1931 actionInfo(d->jid);
1932}
1933
1934void PsiAccount::changePW()
1935{
1936 if(!checkConnected())
1937 return;
1938
1939 ChangePasswordDlg *w = (ChangePasswordDlg *)dialogFind("ChangePasswordDlg");
1940 if(w)
1941 bringToFront(w);
1942 else {
1943 w = new ChangePasswordDlg(this);
1944 w->show();
1945 }
1946}
1947
1948void PsiAccount::showXmlConsole()
1949{
1950 bringToFront(d->xmlConsole);
1951}
1952
1953void PsiAccount::openAddUserDlg()
1954{
1955 if(!checkConnected())
1956 return;
1957
1958 AddUserDlg *w = (AddUserDlg *)dialogFind("AddUserDlg");
1959 if(w)
1960 bringToFront(w);
1961 else {
1962 QStringList gl, services, names;
1963 UserListIt it(d->userList);
1964 for(UserListItem *u; (u = it.current()); ++it) {
1965 if(u->isTransport()) {
1966 services += u->jid().full();
1967 names += jidnick(u->jid().full(), u->name());
1968 }
1969 const QStringList &groups = u->groups();
1970 if(groups.isEmpty())
1971 continue;
1972 for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git) {
1973 if(qstringlistmatch(gl, *git) == -1)
1974 gl.append(*git);
1975 }
1976 }
1977
1978 w = new AddUserDlg(services, names, gl, this);
1979 connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool)));
1980 w->show();
1981 }
1982}
1983
1984void PsiAccount::doDisco()
1985{
1986 actionDisco(d->jid.host(), "");
1987}
1988
1989void PsiAccount::actionDisco(const Jid &j, const QString &node)
1990{
1991 DiscoDlg *w = new DiscoDlg(this, j, node);
1992 connect(w, SIGNAL(featureActivated(QString, Jid, QString)), SLOT(featureActivated(QString, Jid, QString)));
1993 w->show();
1994}
1995
1996void PsiAccount::featureActivated(QString feature, Jid jid, QString node)
1997{
1998 Features f(feature);
1999
2000 if ( f.canRegister() )
2001 actionRegister(jid);
2002 else if ( f.canSearch() )
2003 actionSearch(jid);
2004 else if ( f.canGroupchat() )
2005 actionJoin(jid);
2006 else if ( f.canDisco() )
2007 actionDisco(jid, node);
2008 else if ( f.isGateway() )
2009 ; // TODO
2010 else if ( f.haveVCard() )
2011 actionInfo(jid);
2012 else if ( f.id() == Features::FID_Add ) {
2013 QStringList sl;
2014 dj_add(jid, QString::null, sl, true);
2015 }
2016}
2017
2018void PsiAccount::actionJoin(const Jid &j)
2019{
2020 GCJoinDlg *w = new GCJoinDlg(psi(), this);
2021
2022 w->le_host->setText ( j.host() );
2023 w->le_room->setText ( j.user() );
2024
2025 w->show();
2026}
2027
2028void PsiAccount::stateChanged()
2029{
2030 if(loggedIn())
2031 d->cp->setState(makeSTATUS(status()));
2032 else {
2033 if(isActive()) {
2034 d->cp->setState(-1);
2035 if(d->usingSSL)
2036 d->cp->setUsingSSL(true);
2037 else
2038 d->cp->setUsingSSL(false);
2039 }
2040 else {
2041 d->cp->setState(STATUS_OFFLINE);
2042 d->cp->setUsingSSL(false);
2043 }
2044 }
2045
2046 updatedActivity();
2047}
2048
2049void PsiAccount::simulateContactOffline(UserListItem *u)
2050{
2051 UserResourceList rl = u->userResourceList();
2052 u->setPresenceError("");
2053 if(!rl.isEmpty()) {
2054 for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) {
2055 const UserResource &r = *rit;
2056 Jid j = u->jid();
2057 if(u->jid().resource().isEmpty())
2058 j.setResource(r.name());
2059 client_resourceUnavailable(j, r);
2060 }
2061 }
2062 else
2063 cpUpdate(*u);
2064}
2065
2066void PsiAccount::simulateRosterOffline()
2067{
2068 UserListIt it(d->userList);
2069 for(UserListItem *u; (u = it.current()); ++it)
2070 simulateContactOffline(u);
2071
2072 // self
2073 {
2074 UserListItem *u = &d->self;
2075 UserResourceList rl = u->userResourceList();
2076 for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) {
2077 Jid j = u->jid();
2078 if(u->jid().resource().isEmpty())
2079 j.setResource((*rit).name());
2080 u->setPresenceError("");
2081 client_resourceUnavailable(j, *rit);
2082 }
2083 }
2084
2085 d->gcbank.clear();
2086}
2087
2088void PsiAccount::enableNotifyOnline()
2089{
2090 if ( d->userCounter > 1 ) {
2091 QTimer::singleShot(15000, this, SLOT(enableNotifyOnline()));
2092 d->userCounter = 0;
2093 }
2094 else
2095 notifyOnlineOk = true;
2096}
2097
2098void PsiAccount::slotClientVersionFinished()
2099{
2100 JT_ClientVersion *j = (JT_ClientVersion *)sender();
2101 if(j->success()) {
2102 QPtrList<UserListItem> list = findRelavent(j->jid());
2103 QPtrListIterator<UserListItem> it(list);
2104 for(UserListItem *u; (u = it.current()); ++it) {
2105 UserResourceList::Iterator rit = u->userResourceList().find(j->jid().resource());
2106 bool found = (rit == u->userResourceList().end()) ? false: true;
2107 if(!found)
2108 continue;
2109
2110 (*rit).setClient(j->name(),j->version(),j->os());
2111
2112 cpUpdate(*u);
2113 /*if(u->isSelf()) {
2114 if(d->cp->self())
2115 d->cp->updateSelf(*u);
2116 }
2117 else
2118 d->cp->updateEntry(*u);*/
2119 }
2120 }
2121}
2122
2123QPtrList<UserListItem> PsiAccount::findRelavent(const Jid &j) const
2124{
2125 QPtrList<UserListItem> list;
2126
2127 // self?
2128 if(j.compare(d->self.jid(), false))
2129 list.append(&d->self);
2130 else {
2131 QPtrListIterator<UserListItem> it(d->userList);
2132 for(UserListItem *u; (u = it.current()); ++it) {
2133 if(!u->jid().compare(j, false))
2134 continue;
2135
2136 if(!u->jid().resource().isEmpty()) {
2137 if(u->jid().resource() != j.resource())
2138 continue;
2139 }
2140 list.append(u);
2141 }
2142 }
2143
2144 return list;
2145}
2146
2147UserListItem *PsiAccount::findFirstRelavent(const Jid &j) const
2148{
2149 QPtrList<UserListItem> list = findRelavent(j);
2150 if(list.isEmpty())
2151 return 0;
2152 else
2153 return list.first();
2154}
2155
2156UserListItem *PsiAccount::find(const Jid &j) const
2157{
2158 UserListItem *u;
2159 if(j.compare(d->self.jid()))
2160 u = &d->self;
2161 else
2162 u = d->userList.find(j);
2163
2164 return u;
2165}
2166
2167void PsiAccount::cpUpdate(const UserListItem &u, const QString &rname, bool fromPresence)
2168{
2169 PsiEvent *e = d->eventQueue->peek(u.jid());
2170
2171 d->cp->updateEntry(u);
2172
2173 if(e) {
2174 d->cp->setAlert(u.jid(), is->event2icon(e));
2175 }
2176 else
2177 d->cp->clearAlert(u.jid());
2178
2179 updateContact(u);
2180 Jid j = u.jid();
2181 if(!rname.isEmpty())
2182 j.setResource(rname);
2183 updateContact(j);
2184 updateContact(j, fromPresence);
2185 d->psi->updateContactGlobal(this, j);
2186}
2187
2188QLabel *PsiAccount::accountLabel(QWidget *par, bool simpleMode)
2189{
2190 AccountLabel *al = new AccountLabel(this, par, simpleMode);
2191 return al;
2192}
2193
2194EventDlg *PsiAccount::ensureEventDlg(const Jid &j)
2195{
2196 EventDlg *w = (EventDlg *)dialogFind("EventDlg", j);
2197 if(!w) {
2198 w = new EventDlg(j, this, true);
2199 connect(w, SIGNAL(aReadNext(const Jid &)), SLOT(processReadNext(const Jid &)));
2200 connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&)));
2201 connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &)));
2202 connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &)));
2203 connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &)));
2204 connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
2205 connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &)));
2206 }
2207
2208 return w;
2209}
2210
2211ChatDlg *PsiAccount::ensureChatDlg(const Jid &j)
2212{
2213 ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
2214 if(!c) {
2215 // create the chatbox
2216 c = new ChatDlg(j, this);
2217 if (option.useTabs)
2218 {
2219 //get a tab from the mainwin
2220 d->psi->getTabs()->addChat(c);
2221 }
2222 connect(c, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &)));
2223 connect(c, SIGNAL(messagesRead(const Jid &)), SLOT(chatMessagesRead(const Jid &)));
2224 connect(c, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &)));
2225 connect(c, SIGNAL(aHistory(const Jid &)), SLOT(actionHistory(const Jid &)));
2226 connect(c, SIGNAL(aFile(const Jid &)), SLOT(actionSendFile(const Jid &)));
2227 connect(d->psi, SIGNAL(emitOptionsUpdate()), c, SLOT(optionsUpdate()));
2228 connect(this, SIGNAL(updateContact(const Jid &, bool)), c, SLOT(updateContact(const Jid &, bool)));
2229 }
2230 else {
2231 // on X11, do a special reparent to open on the right desktop
2232#ifdef Q_WS_X11
2233 /* KIS added an exception for tabs here. We do *not* want chats flying
2234 * randomlyi, it pulls them out of tabsets. So instead, we move the
2235 * tabset instead. It's just as filthy, unfortunately, but it's the
2236 * only way */
2237 //TODO: This doesn't work as expected atm, it doesn't seem to reparent the tabset
2238 QWidget *window=c;
2239 if ( option.useTabs )
2240 window = d->psi->getManagingTabs(c);
2241 if(window && window->isHidden()) {
2242 const QPixmap *pp = c->icon();
2243 QPixmap p;
2244 if(pp)
2245 p = *pp;
2246 reparent_good(window, 0, false);
2247 if(!p.isNull())
2248 c->setIcon(p);
2249 }
2250#endif
2251 }
2252
2253 return c;
2254}
2255
2256void PsiAccount::changeStatus(int x)
2257{
2258 if(x == STATUS_OFFLINE && !option.askOffline) {
2259 setStatus(Status("","Logged out",0,false));
2260 }
2261 else {
2262 if(x == STATUS_ONLINE && !option.askOnline) {
2263 setStatus(Status());
2264 }
2265 else if(x == STATUS_INVISIBLE){
2266 Status s("","",0,true);
2267 s.setIsInvisible(true);
2268 setStatus(s);
2269 }
2270 else {
2271 StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, ""));
2272 connect(w, SIGNAL(set(const Status &)), SLOT(setStatus(const Status &)));
2273 w->show();
2274 }
2275 }
2276}
2277
2278void PsiAccount::actionTest(const Jid &j)
2279{
2280 UserListItem *u = find(j);
2281 if(!u)
2282 return;
2283 Jid j2 = j;
2284 if(j.resource().isEmpty()) {
2285 if(u->isAvailable())
2286 j2.setResource((*u->userResourceList().priority()).name());
2287 }
2288
2289 //S5BConnection *c = new S5BConnection(d->client->s5bManager());
2290 //c->connectToJid(j2, d->client->s5bManager()->genUniqueSID(j2));
2291 JLTestDlg *w = new JLTestDlg(j2, this);
2292 w->show();
2293}
2294
2295void PsiAccount::actionSendFile(const Jid &j)
2296{
2297 QStringList l;
2298 actionSendFiles(j, l);
2299}
2300
2301void PsiAccount::actionSendFiles(const Jid &j, const QStringList& l)
2302{
2303 Jid j2 = j;
2304 if(j.resource().isEmpty()) {
2305 UserListItem *u = find(j);
2306 if(u && u->isAvailable())
2307 j2.setResource((*u->userResourceList().priority()).name());
2308 }
2309
2310 // Create a dialog for each file in the list. Once the xfer dialog itself
2311 // supports multiple files, only the 'else' branch needs to stay.
2312 if (!l.isEmpty()) {
2313 for (QStringList::ConstIterator f = l.begin(); f != l.end(); ++f ) {
2314 QStringList fl(*f);
2315 FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, fl);
2316 w->show();
2317 }
2318 }
2319 else {
2320 FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, l);
2321 w->show();
2322 }
2323}
2324
2325void PsiAccount::actionDefault(const Jid &j)
2326{
2327 UserListItem *u = find(j);
2328 if(!u)
2329 return;
2330
2331 if(d->eventQueue->count(u->jid()) > 0)
2332 openNextEvent(*u);
2333 else {
2334 if(option.defaultAction == 0)
2335 actionSendMessage(u->jid());
2336 else
2337 actionOpenChat(u->jid());
2338 }
2339}
2340
2341void PsiAccount::actionRecvEvent(const Jid &j)
2342{
2343 UserListItem *u = find(j);
2344 if(!u)
2345 return;
2346
2347 openNextEvent(*u);
2348}
2349
2350void PsiAccount::actionSendMessage(const Jid &j)
2351{
2352 EventDlg *w = d->psi->createEventDlg(j.full(), this);
2353 w->show();
2354}
2355
2356void PsiAccount::actionSendMessage(const JidList &j)
2357{
2358 QString str;
2359 bool first = true;
2360 for(QValueList<Jid>::ConstIterator it = j.begin(); it != j.end(); ++it) {
2361 if(!first)
2362 str += ", ";
2363 first = false;
2364
2365 str += (*it).full();
2366 }
2367
2368 EventDlg *w = d->psi->createEventDlg(str, this);
2369 w->show();
2370}
2371
2372void PsiAccount::actionSendUrl(const Jid &j)
2373{
2374 EventDlg *w = d->psi->createEventDlg(j.full(), this);
2375 w->setUrlOnShow();
2376 w->show();
2377}
2378
2379void PsiAccount::actionRemove(const Jid &j)
2380{
2381#ifdef AVATARS
2382 avatarFactory()->removeManualAvatar(j);
2383#endif
2384 dj_remove(j);
2385}
2386
2387void PsiAccount::actionRename(const Jid &j, const QString &name)
2388{
2389 dj_rename(j, name);
2390}
2391
2392void PsiAccount::actionGroupRename(const QString &oldname, const QString &newname)
2393{
2394 UserListIt it(d->userList);
2395 QPtrList<UserListItem> nu;
2396 for(UserListItem *u; (u = it.current()); ++it) {
2397 if(u->inGroup(oldname)) {
2398 u->removeGroup(oldname);
2399 u->addGroup(newname);
2400 cpUpdate(*u);
2401 if(u->inList())
2402 nu.append(u);
2403 }
2404 }
2405
2406 if(!nu.isEmpty()) {
2407 JT_Roster *r = new JT_Roster(d->client->rootTask());
2408
2409 QPtrListIterator<UserListItem> it(nu);
2410 for(UserListItem *u; (u = it.current()); ++it)
2411 r->set(u->jid(), u->name(), u->groups());
2412
2413 r->go(true);
2414 }
2415}
2416
2417void PsiAccount::actionHistory(const Jid &j)
2418{
2419 HistoryDlg *w = (HistoryDlg *)dialogFind("HistoryDlg", j);
2420 if(w)
2421 bringToFront(w);
2422 else {
2423 w = new HistoryDlg(j, this);
2424 connect(w, SIGNAL(openEvent(PsiEvent *)), SLOT(actionHistoryBox(PsiEvent *)));
2425 w->show();
2426 }
2427}
2428
2429void PsiAccount::actionHistoryBox(PsiEvent *e)
2430{
2431 EventDlg *w = new EventDlg(e->from(), this, false);
2432 connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&)));
2433 connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &)));
2434 connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &)));
2435 connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &)));
2436 connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
2437 connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &)));
2438 w->updateEvent(e);
2439 w->show();
2440}
2441
2442void PsiAccount::actionOpenChat(const Jid &j)
2443{
2444 UserListItem *u = find(j);
2445 if(!u)
2446 return;
2447
2448 // if 'j' is bare, we might want to switch to a specific resource
2449 QString res;
2450 if(j.resource().isEmpty()) {
2451 // first, are there any queued chats?
2452 /*PsiEvent *e = d->eventQueue->peekFirstChat(j, false);
2453 if(e) {
2454 res = e->from().resource();
2455 // if we have a bare chat, change to 'res'
2456 ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
2457 if(c)
2458 c->setJid(j.withResource(res));
2459 }
2460 // else, is there a priority chat window available?
2461 else*/ if(u->isAvailable()) {
2462 QString pr = (*u->userResourceList().priority()).name();
2463 if(!pr.isEmpty() && dialogFind("ChatDlg", j.withResource(pr)))
2464 res = pr;
2465 }
2466 else {
2467 QStringList list = hiddenChats(j);
2468 if(!list.isEmpty())
2469 res = list.first();
2470 }
2471 }
2472
2473 if(!res.isEmpty())
2474 openChat(j.withResource(res));
2475 else
2476 openChat(j);
2477}
2478
2479void PsiAccount::actionOpenChatSpecific(const Jid &j)
2480{
2481 openChat(j);
2482}
2483
2484void PsiAccount::actionAgentSetStatus(const Jid &j, Status &s)
2485{
2486 if ( j.user().isEmpty() ) // add all transport popups to block list
2487 new BlockTransportPopup(d->blockTransportPopupList, j);
2488
2489 JT_Presence *p = new JT_Presence(d->client->rootTask());
2490 p->pres(j, s);
2491 p->go(true);
2492}
2493
2494void PsiAccount::actionInfo(const Jid &_j)
2495{
2496 bool useCache = true;
2497 Jid j;
2498 if(findGCContact(_j)) {
2499 useCache = false;
2500 j = _j;
2501 }
2502 else {
2503 j = _j.userHost();
2504 }
2505
2506 InfoDlg *w = (InfoDlg *)dialogFind("InfoDlg", j);
2507 if(w) {
2508 w->updateStatus();
2509 bringToFront(w);
2510 }
2511 else {
2512 const VCard *vcard = VCardFactory::vcard(j);
2513
2514 VCard tmp;
2515 if ( vcard )
2516 tmp = *vcard;
2517 w = new InfoDlg(j.compare(d->jid) ? InfoDlg::Self : InfoDlg::Contact, j, tmp, this, 0, 0, useCache);
2518 w->show();
2519
2520 // automatically retrieve info if it doesn't exist
2521 if(!vcard && loggedIn())
2522 w->doRefresh();
2523 }
2524}
2525
2526void PsiAccount::actionAuth(const Jid &j)
2527{
2528 dj_auth(j);
2529}
2530
2531void PsiAccount::actionAuthRequest(const Jid &j)
2532{
2533 dj_authReq(j);
2534}
2535
2536void PsiAccount::actionAuthRemove(const Jid &j)
2537{
2538 dj_deny(j);
2539}
2540
2541void PsiAccount::actionAdd(const Jid &j)
2542{
2543 dj_addAuth(j);
2544}
2545
2546void PsiAccount::actionGroupAdd(const Jid &j, const QString &g)
2547{
2548 UserListItem *u = d->userList.find(j);
2549 if(!u)
2550 return;
2551
2552 if(!u->addGroup(g))
2553 return;
2554 cpUpdate(*u);
2555
2556 JT_Roster *r = new JT_Roster(d->client->rootTask());
2557 r->set(u->jid(), u->name(), u->groups());
2558 r->go(true);
2559}
2560
2561void PsiAccount::actionGroupRemove(const Jid &j, const QString &g)
2562{
2563 UserListItem *u = d->userList.find(j);
2564 if(!u)
2565 return;
2566
2567 if(!u->removeGroup(g))
2568 return;
2569 cpUpdate(*u);
2570
2571 JT_Roster *r = new JT_Roster(d->client->rootTask());
2572 r->set(u->jid(), u->name(), u->groups());
2573 r->go(true);
2574}
2575
2576void PsiAccount::actionRegister(const Jid &j)
2577{
2578 if(!checkConnected())
2579 return;
2580
2581 RegistrationDlg *w = (RegistrationDlg *)dialogFind("RegistrationDlg", j);
2582 if(w)
2583 bringToFront(w);
2584 else {
2585 w = new RegistrationDlg(j, this);
2586 w->show();
2587 }
2588}
2589
2590void PsiAccount::actionSearch(const Jid &j)
2591{
2592 if(!checkConnected())
2593 return;
2594
2595 SearchDlg *w = (SearchDlg *)dialogFind("SearchDlg", j);
2596 if(w)
2597 bringToFront(w);
2598 else {
2599 w = new SearchDlg(j, this);
2600 connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool)));
2601 connect(w, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &)));
2602 w->show();
2603 }
2604}
2605
2606void PsiAccount::actionInvite(const Jid &j, const QString &gc)
2607{
2608 Message m;
2609 m.setTo(j);
2610 m.setInvite(gc);
2611 m.setBody(tr("You have been invited to %1").arg(gc));
2612 m.setTimeStamp(QDateTime::currentDateTime());
2613 dj_sendMessage(m);
2614}
2615
2616void PsiAccount::actionAssignKey(const Jid &j)
2617{
2618 if(ensureKey(j)) {
2619 UserListItem *u = findFirstRelavent(j);
2620 if(u)
2621 cpUpdate(*u);
2622 }
2623}
2624
2625void PsiAccount::actionUnassignKey(const Jid &j)
2626{
2627 UserListItem *u = findFirstRelavent(j);
2628 if(u) {
2629 u->setPublicKeyID("");
2630 cpUpdate(*u);
2631 }
2632}
2633
2634void PsiAccount::dj_sendMessage(const Message &m, bool log)
2635{
2636 UserListItem *u = findFirstRelavent(m.to());
2637 Message nm = m;
2638
2639 if(option.incomingAs == 3) {
2640 if(u) {
2641 switch(u->lastMessageType()) {
2642 case 0: nm.setType(""); break;
2643 case 1: nm.setType("chat"); break;
2644 }
2645 }
2646 }
2647
2648 d->client->sendMessage(nm);
2649
2650 // only toggle if not an invite or body is not empty
2651 if(m.invite().isEmpty() && !m.body().isEmpty())
2652 toggleSecurity(m.to(), m.wasEncrypted());
2653
2654 // don't log groupchat, private messages, or encrypted messages
2655 if(d->acc.opt_log && log) {
2656 if(m.type() != "groupchat" && m.xencrypted().isEmpty() && !findGCContact(m.to())) {
2657 MessageEvent *me = new MessageEvent(m, this);
2658 me->setOriginLocal(true);
2659 me->setTimeStamp(QDateTime::currentDateTime());
2660 logEvent(m.to(), me);
2661 delete me;
2662 }
2663 }
2664
2665 // don't sound when sending groupchat messages or message events
2666 if(m.type() != "groupchat" && !m.body().isEmpty())
2667 playSound(option.onevent[eSend]);
2668
2669 // auto close an open messagebox (if non-chat)
2670 if(m.type() != "chat" && !m.body().isEmpty()) {
2671 UserListItem *u = findFirstRelavent(m.to());
2672 if(u) {
2673 EventDlg *e = (EventDlg *)dialogFind("EventDlg", u->jid());
2674 if(e)
2675 e->closeAfterReply();
2676 }
2677 }
2678}
2679
2680void PsiAccount::dj_composeMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread)
2681{
2682 EventDlg *w = d->psi->createEventDlg(jid.full(), this);
2683 if(!body.isEmpty())
2684 w->setText(qstrquote(body));
2685
2686 if(!subject.isEmpty() && subject.left(3) != "Re:")
2687 w->setSubject("Re: " + subject);
2688 else if (subject.left(3) == "Re:")
2689 w->setSubject(subject);
2690
2691 if(!thread.isEmpty())
2692 w->setThread(thread);
2693
2694 w->show();
2695}
2696
2697void PsiAccount::dj_composeMessage(const Jid &j, const QString &body)
2698{
2699 dj_composeMessage(j, body, QString::null, QString::null);
2700}
2701
2702void PsiAccount::dj_addAuth(const Jid &j)
2703{
2704 QString name;
2705 QStringList groups;
2706 UserListItem *u = d->userList.find(j);
2707 if(u) {
2708 name = u->name();
2709 groups = u->groups();
2710 }
2711
2712 dj_add(j, name, groups, true);
2713 dj_auth(j);
2714}
2715
2716void PsiAccount::dj_add(const Jid &j, const QString &name, const QStringList &groups, bool authReq)
2717{
2718 JT_Roster *r = new JT_Roster(d->client->rootTask());
2719 r->set(j, name, groups);
2720 r->go(true);
2721
2722 if(authReq)
2723 dj_authReq(j);
2724}
2725
2726void PsiAccount::dj_authReq(const Jid &j)
2727{
2728 d->client->sendSubscription(j, "subscribe");
2729}
2730
2731void PsiAccount::dj_auth(const Jid &j)
2732{
2733 d->client->sendSubscription(j, "subscribed");
2734}
2735
2736void PsiAccount::dj_deny(const Jid &j)
2737{
2738 d->client->sendSubscription(j, "unsubscribed");
2739}
2740
2741void PsiAccount::dj_rename(const Jid &j, const QString &name)
2742{
2743 UserListItem *u = d->userList.find(j);
2744 if(!u)
2745 return;
2746
2747 QString str;
2748 if(name == u->jid().full())
2749 str = "";
2750 else
2751 str = name;
2752
2753 // strange workaround to avoid a null string ??
2754 QString uname;
2755 if(u->name().isEmpty())
2756 uname = "";
2757 else
2758 uname = u->name();
2759
2760 if(uname == str)
2761 return;
2762 u->setName(str);
2763
2764 cpUpdate(*u);
2765
2766 if(u->inList()) {
2767 JT_Roster *r = new JT_Roster(d->client->rootTask());
2768 r->set(u->jid(), u->name(), u->groups());
2769 r->go(true);
2770 }
2771}
2772
2773void PsiAccount::dj_remove(const Jid &j)
2774{
2775 UserListItem *u = d->userList.find(j);
2776 if(!u)
2777 return;
2778
2779 // remove all events from the queue
2780 d->eventQueue->clear(j);
2781 updateReadNext(j);
2782
2783 // TODO: delete the item immediately (to simulate local change)
2784 if(!u->inList()) {
2785 //simulateContactOffline(u);
2786 d->userList.removeRef(u);
2787 }
2788 else {
2789 JT_Roster *r = new JT_Roster(d->client->rootTask());
2790 r->remove(j);
2791 r->go(true);
2792
2793 // if it looks like a transport, unregister (but not if it is the server!!)
2794 if(u->isTransport() && !Jid(d->client->host()).compare(u->jid())) {
2795 JT_UnRegister *ju = new JT_UnRegister(d->client->rootTask());
2796 ju->unreg(j);
2797 ju->go(true);
2798 }
2799 }
2800}
2801
2802// handle an incoming event
2803void PsiAccount::handleEvent(PsiEvent *e)
2804{
2805 if ( e )
2806 setEnabled();
2807
2808 bool doPopup = false;
2809 bool putToQueue = true;
2810 PsiPopup::PopupType popupType = PsiPopup::AlertNone;
2811
2812 // find someone to accept the event
2813 Jid j;
2814 QPtrList<UserListItem> ul = findRelavent(e->from());
2815 if(ul.isEmpty()) {
2816 // if groupchat, then we want the full JID
2817 if(findGCContact(e->from())) {
2818 j = e->from();
2819 }
2820 else {
2821 Jid bare = e->from().userHost();
2822 Jid reg = bare.withResource("registered");
2823
2824 // see if we have a "registered" variant of the jid
2825 if(findFirstRelavent(reg)) {
2826 j = reg;
2827 e->setFrom(reg); // HACK!!
2828 }
2829 // retain full jid if sent to "registered"
2830 else if(e->from().resource() == "registered")
2831 j = e->from();
2832 // otherwise don't use the resource for new entries
2833 else
2834 j = bare;
2835 }
2836 }
2837 else
2838 j = ul.first()->jid();
2839 e->setJid(j);
2840
2841 if(d->acc.opt_log) {
2842 if(e->type() == PsiEvent::Message || e->type() == PsiEvent::Auth) {
2843 // don't log private messages
2844 if(!findGCContact(e->from()) && !(e->type() == PsiEvent::Message && ((MessageEvent *)e)->message().body().isEmpty()))
2845 logEvent(e->from(), e);
2846 }
2847 }
2848
2849 if(e->type() == PsiEvent::Message) {
2850 MessageEvent *me = (MessageEvent *)e;
2851 const Message &m = me->message();
2852
2853 // Pass message events to chat window
2854 if (m.containsEvents() && m.body().isEmpty()) {
2855 if (option.messageEvents) {
2856 ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from());
2857 if(!c)
2858 c = (ChatDlg *)dialogFind("ChatDlg", e->jid());
2859 if (c)
2860 c->incomingMessage(m);
2861 }
2862 return;
2863 }
2864
2865 // pass chat messages directly to a chat window if possible (and deal with sound)
2866 if(m.type() == "chat") {
2867 ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from());
2868 if(!c)
2869 c = (ChatDlg *)dialogFind("ChatDlg", e->jid());
2870
2871 if(c)
2872 c->setJid(e->from());
2873
2874 if ( !c || !c->isActiveWindow() || option.alertOpenChats ) {
2875 doPopup = true;
2876 popupType = PsiPopup::AlertChat;
2877 }
2878
2879 //if the chat exists, and is either open in a tab,
2880 //or in a window
2881 if( c && ( d->psi->isChatTabbed(c) || !c->isHidden() ) ) {
2882 c->incomingMessage(m);
2883 playSound(option.onevent[eChat2]);
2884 if(option.alertOpenChats && !d->psi->isChatActiveWindow(c)) {
2885 // to alert the chat also, we put it in the queue
2886 me->setSentToChatWindow(true);
2887 }
2888 else
2889 putToQueue = false;
2890 }
2891 else {
2892 bool firstChat = !d->eventQueue->hasChats(e->from());
2893 playSound(option.onevent[firstChat ? eChat1: eChat2]);
2894 }
2895 } // /chat
2896 else if (m.type() == "headline") {
2897 playSound(option.onevent[eHeadline]);
2898 doPopup = true;
2899 popupType = PsiPopup::AlertHeadline;
2900 } // /headline
2901 else if (m.type() == "") {
2902 playSound(option.onevent[eMessage]);
2903 if (m.type() == "") {
2904 doPopup = true;
2905 popupType = PsiPopup::AlertMessage;
2906 }
2907 } // /""
2908 else
2909 playSound(option.onevent[eSystem]);
2910
2911 if(m.type() == "error") {
2912 // FIXME: handle message errors
2913 //msg.text = QString(tr("<big>[Error Message]</big><br>%1").arg(plain2rich(msg.text)));
2914 }
2915 }
2916 else if(e->type() == PsiEvent::File) {
2917 playSound(option.onevent[eIncomingFT]);
2918 doPopup = true;
2919 popupType = PsiPopup::AlertFile;
2920 }
2921 else {
2922 playSound(option.onevent[eSystem]);
2923
2924 AuthEvent *ae = (AuthEvent *)e;
2925 if(ae->authType() == "subscribe") {
2926 if(option.autoAuth) {
2927 // Check if we want to request auth as well
2928 UserListItem *u = d->userList.find(ae->from());
2929 if (!u || (u->subscription().type() != Subscription::Both && u->subscription().type() != Subscription::To)) {
2930 dj_addAuth(ae->from());
2931 }
2932 else {
2933 dj_auth(ae->from());
2934 }
2935 putToQueue = false;
2936 }
2937 }
2938 else if(ae->authType() == "subscribed") {
2939 if(!option.notifyAuth)
2940 putToQueue = false;
2941 }
2942 else if(ae->authType() == "unsubscribe") {
2943 putToQueue = false;
2944 }
2945 }
2946
2947#if !defined(Q_WS_MAC) || !defined(HAVE_GROWL)
2948 // Do the popup test earlier (to avoid needless JID lookups)
2949 if ((popupType == PsiPopup::AlertChat && option.ppChat) || (popupType == PsiPopup::AlertMessage && option.ppMessage) || (popupType == PsiPopup::AlertHeadline && option.ppHeadline) || (popupType == PsiPopup::AlertFile && option.ppFile))
2950#endif
2951 if ( doPopup && d->doPopups && makeSTATUS(status()) != STATUS_DND ) {
2952 Resource r;
2953 UserListItem *u = findFirstRelavent(j);
2954 if ( u )
2955 r = *(u->priority());
2956
2957 if (((popupType == PsiPopup::AlertChat && option.ppChat) || (popupType == PsiPopup::AlertMessage && option.ppMessage) || (popupType == PsiPopup::AlertHeadline && option.ppHeadline) || (popupType == PsiPopup::AlertFile && option.ppFile)) && makeSTATUS(status()) != STATUS_DND) {
2958 PsiPopup *popup = new PsiPopup(popupType, this);
2959 popup->setData(j, r, u, e);
2960 }
2961#if defined(Q_WS_MAC) && defined(HAVE_GROWL)
2962 PsiGrowlNotifier::instance()->popup(this, popupType, j, r, u, e);
2963#endif
2964 }
2965
2966 if ( putToQueue )
2967 queueEvent(e);
2968 else
2969 delete e;
2970}
2971
2972// put an event into the event queue, and update the related alerts
2973void PsiAccount::queueEvent(PsiEvent *e)
2974{
2975 // do we have roster item for this?
2976 UserListItem *u = find(e->jid());
2977 if(!u) {
2978 // create item
2979 u = new UserListItem;
2980 u->setJid(e->jid());
2981 u->setInList(false);
2982#ifdef AVATARS
2983 u->setAvatarFactory(d->avatarFactory);
2984#endif
2985
2986 // is it a private groupchat?
2987 Jid j = u->jid();
2988 GCContact *c = findGCContact(j);
2989 if(c) {
2990 u->setName(j.resource());
2991 u->setPrivate(true);
2992
2993 // make a resource so the contact appears online
2994 UserResource ur;
2995 ur.setName(j.resource());
2996 ur.setStatus(c->status);
2997 u->userResourceList().append(ur);
2998 }
2999
3000 // treat it like a push [pushinfo]
3001 //VCard info;
3002 //if(readUserInfo(item->jid, &info) && !info.field[vNickname].isEmpty())
3003 // item->nick = info.field[vNickname];
3004 //else {
3005 // if(localStatus != STATUS_OFFLINE)
3006 // serv->getVCard(item->jid);
3007 //}
3008
3009 d->userList.append(u);
3010 }
3011
3012 //printf("queuing message from [%s] for [%s].\n", e->from().full().latin1(), e->jid().full().latin1());
3013 d->eventQueue->enqueue(e);
3014
3015 updateReadNext(e->jid());
3016 if(option.raise)
3017 d->psi->raiseMainwin();
3018
3019 // udpate the roster
3020 cpUpdate(*u);
3021
3022 bool noPopup = false;
3023 if(d->loginStatus.isAvailable()) {
3024 QString show = d->loginStatus.show();
3025 if(show == "dnd")
3026 noPopup = true;
3027 else if((show == "away" || show == "xa") && option.noAwayPopup)
3028 noPopup = true;
3029 }
3030
3031 if (!noPopup) {
3032 bool doPopup = false;
3033
3034 // Check to see if we need to popup
3035 if(e->type() == PsiEvent::Message) {
3036 MessageEvent *me = (MessageEvent *)e;
3037 const Message &m = me->message();
3038 if (m.type() == "chat")
3039 doPopup = option.popupChats;
3040 else if (m.type() == "headline")
3041 doPopup = option.popupHeadlines;
3042 else
3043 doPopup = option.popupMsgs;
3044 } else if (e->type() == PsiEvent::File) {
3045 doPopup = option.popupFiles;
3046 }
3047 else
3048 doPopup = option.popupMsgs;
3049
3050 // Popup
3051 if (doPopup) {
3052 UserListItem *u = find(e->jid());
3053 if (u && (!option.noUnlistedPopup || u->inList()))
3054 openNextEvent(*u);
3055 }
3056
3057 }
3058}
3059
3060// take the next event from the queue and display it
3061void PsiAccount::openNextEvent(const UserListItem &u)
3062{
3063 PsiEvent *e = d->eventQueue->peek(u.jid());
3064 if(!e)
3065 return;
3066
3067 psi()->processEvent(e);
3068}
3069
3070void PsiAccount::openNextEvent()
3071{
3072 PsiEvent *e = d->eventQueue->peekNext();
3073 if(!e)
3074 return;
3075
3076 if(e->type() == PsiEvent::PGP) {
3077 psi()->processEvent(e);
3078 return;
3079 }
3080
3081 UserListItem *u = find(e->jid());
3082 if(!u)
3083 return;
3084 openNextEvent(*u);
3085}
3086
3087void PsiAccount::updateReadNext(const Jid &j)
3088{
3089 // update eventdlg's read-next
3090 EventDlg *w = (EventDlg *)dialogFind("EventDlg", j);
3091 if(w) {
3092 Icon *nextAnim = 0;
3093 int nextAmount = d->eventQueue->count(j);
3094 if(nextAmount > 0)
3095 nextAnim = is->event2icon(d->eventQueue->peek(j));
3096 w->updateReadNext(nextAnim, nextAmount);
3097 }
3098
3099 queueChanged();
3100}
3101
3102void PsiAccount::processReadNext(const Jid &j)
3103{
3104 UserListItem *u = find(j);
3105 if(u)
3106 processReadNext(*u);
3107}
3108
3109void PsiAccount::processReadNext(const UserListItem &u)
3110{
3111 EventDlg *w = (EventDlg *)dialogFind("EventDlg", u.jid());
3112 if(!w) {
3113 // this should NEVER happen
3114 return;
3115 }
3116
3117 // peek the event
3118 PsiEvent *e = d->eventQueue->peek(u.jid());
3119 if(!e)
3120 return;
3121
3122 bool isChat = false;
3123 if(e->type() == PsiEvent::Message) {
3124 MessageEvent *me = (MessageEvent *)e;
3125 const Message &m = me->message();
3126 if(m.type() == "chat")
3127 isChat = true;
3128 }
3129
3130 // if it's a chat message, just open the chat window. there is no need to do
3131 // further processing. the chat window will remove it from the queue, update
3132 // the cvlist, etc etc.
3133 if(isChat) {
3134 openChat(e->from());
3135 return;
3136 }
3137
3138 // remove from queue
3139 e = d->eventQueue->dequeue(u.jid());
3140
3141 // update the eventdlg
3142 w->updateEvent(e);
3143 delete e;
3144
3145 // update the contact
3146 cpUpdate(u);
3147
3148 updateReadNext(u.jid());
3149}
3150
3151void PsiAccount::processChats(const Jid &j)
3152{
3153 //printf("processing chats for [%s]\n", j.full().latin1());
3154 ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j);
3155 if(!c)
3156 return;
3157
3158 // extract the chats
3159 QPtrList<PsiEvent> chatList;
3160 d->eventQueue->extractChats(&chatList, j);
3161 chatList.setAutoDelete(true);
3162
3163 if(!chatList.isEmpty()) {
3164 // dump the chats into the chat window, and remove the related cvlist alerts
3165 QPtrListIterator<PsiEvent> it(chatList);
3166 for(PsiEvent *e; (e = it.current()); ++it) {
3167 MessageEvent *me = (MessageEvent *)e;
3168 const Message &m = me->message();
3169
3170 // process the message
3171 if(!me->sentToChatWindow())
3172 c->incomingMessage(m);
3173 }
3174
3175 QPtrList<UserListItem> ul = findRelavent(j);
3176 if(!ul.isEmpty()) {
3177 UserListItem *u = ul.first();
3178 cpUpdate(*u);
3179 updateReadNext(u->jid());
3180 }
3181 }
3182}
3183
3184void PsiAccount::openChat(const Jid &j)
3185{
3186 ChatDlg *c = ensureChatDlg(j);
3187 processChats(j);
3188 if ( option.useTabs )
3189 {
3190 if ( !d->psi->isChatTabbed(c) )
3191 {
3192 //get a tab from the psicon
3193 d->psi->getTabs()->addChat(c);
3194 }
3195 TabDlg* tabSet = d->psi->getManagingTabs(c);
3196 tabSet->selectTab(c);
3197 }
3198 bringToFront(c);
3199}
3200
3201void PsiAccount::chatMessagesRead(const Jid &j)
3202{
3203 if(option.alertOpenChats)
3204 processChats(j);
3205}
3206
3207void PsiAccount::logEvent(const Jid &j, PsiEvent *e)
3208{
3209 EDBHandle *h = new EDBHandle(d->psi->edb());
3210 connect(h, SIGNAL(finished()), SLOT(edb_finished()));
3211 h->append(j, e);
3212}
3213
3214void PsiAccount::edb_finished()
3215{
3216 EDBHandle *h = (EDBHandle *)sender();
3217 delete h;
3218}
3219
3220void PsiAccount::openGroupChat(const Jid &j)
3221{
3222 QString str = j.userHost();
3223 bool found = false;
3224 for(QStringList::ConstIterator it = d->groupchats.begin(); it != d->groupchats.end(); ++it) {
3225 if((*it) == str) {
3226 found = true;
3227 break;
3228 }
3229 }
3230 if(!found)
3231 d->groupchats += str;
3232
3233 GCMainDlg *w = new GCMainDlg(this, j);
3234 connect(w, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &)));
3235 connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate()));
3236 w->show();
3237}
3238
3239bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick)
3240{
3241 return d->client->groupChatJoin(host, room, nick);
3242}
3243
3244void PsiAccount::groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &s)
3245{
3246 d->client->groupChatChangeNick(host, room, nick, s);
3247}
3248
3249void PsiAccount::groupChatSetStatus(const QString &host, const QString &room, const Status &s)
3250{
3251 d->client->groupChatSetStatus(host, room, s);
3252}
3253
3254void PsiAccount::groupChatLeave(const QString &host, const QString &room)
3255{
3256 d->groupchats.remove(room + '@' + host);
3257 d->client->groupChatLeave(host, room);
3258}
3259
3260GCContact *PsiAccount::findGCContact(const Jid &j)
3261{
3262 QPtrListIterator<GCContact> it(d->gcbank);
3263 for(GCContact *c; (c = it.current()); ++it) {
3264 if(c->jid.compare(j))
3265 return c;
3266 }
3267 return 0;
3268}
3269
3270QStringList PsiAccount::groupchats() const
3271{
3272 return d->groupchats;
3273}
3274
3275void PsiAccount::client_groupChatJoined(const Jid &j)
3276{
3277 d->client->groupChatSetStatus(j.host(), j.user(), d->loginStatus);
3278
3279 GCMainDlg *m = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
3280 if(m) {
3281 m->joined();
3282 return;
3283 }
3284 GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j);
3285 if(!w)
3286 return;
3287 w->joined();
3288
3289 openGroupChat(j);
3290}
3291
3292void PsiAccount::client_groupChatLeft(const Jid &j)
3293{
3294 // remove all associated groupchat contacts from the bank
3295 QPtrListIterator<GCContact> it(d->gcbank);
3296 for(GCContact *c; (c = it.current());) {
3297 // contact from this room?
3298 if(!c->jid.compare(j, false)) {
3299 ++it;
3300 continue;
3301 }
3302 UserListItem *u = find(c->jid);
3303 if(!u) {
3304 ++it;
3305 continue;
3306 }
3307
3308 simulateContactOffline(u);
3309 d->gcbank.removeRef(c);
3310 }
3311}
3312
3313void PsiAccount::client_groupChatPresence(const Jid &j, const Status &s)
3314{
3315 GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
3316 if(!w)
3317 return;
3318
3319 GCContact *c = findGCContact(j);
3320 if(!c) {
3321 c = new GCContact;
3322 c->jid = j;
3323 c->status = s;
3324 d->gcbank.append(c);
3325 }
3326
3327 w->presence(j.resource(), s);
3328
3329 // pass through the core presence handling also
3330 Resource r;
3331 r.setName(j.resource());
3332 r.setStatus(s);
3333 if(s.isAvailable())
3334 client_resourceAvailable(j, r);
3335 else
3336 client_resourceUnavailable(j, j.resource());
3337}
3338
3339void PsiAccount::client_groupChatError(const Jid &j, int code, const QString &str)
3340{
3341 GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost()));
3342 if(w) {
3343 w->error(code, str);
3344 }
3345 else {
3346 GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j);
3347 if(w) {
3348 w->error(code, str);
3349 }
3350 }
3351}
3352
3353QStringList PsiAccount::hiddenChats(const Jid &j) const
3354{
3355 QStringList list;
3356
3357 QPtrListIterator<item_dialog2> it(d->dialogList);
3358 for(item_dialog2 *i; (i = it.current()); ++it) {
3359 if(i->className == "ChatDlg" && i->jid.compare(j, false))
3360 list += i->jid.resource();
3361 }
3362
3363 return list;
3364}
3365
3366void PsiAccount::slotCheckVCard()
3367{
3368 JT_VCard *j = (JT_VCard *)sender();
3369 if(!j->success() && j->statusCode() == Task::ErrDisc) {
3370 setNick(d->jid.user());
3371 return;
3372 }
3373
3374 if(j->vcard().isEmpty()) {
3375 changeVCard();
3376 return;
3377 }
3378
3379 if(!j->vcard().nickName().isEmpty()) {
3380 d->nickFromVCard = true;
3381 setNick(j->vcard().nickName());
3382 }
3383 else
3384 setNick(d->jid.user());
3385}
3386
3387void PsiAccount::setNick(const QString &nick)
3388{
3389 d->self.setName(nick);
3390 cpUpdate(d->self);
3391 nickChanged();
3392}
3393
3394QString PsiAccount::nick() const
3395{
3396 return d->self.name();
3397}
3398
3399void PsiAccount::pgpToggled(bool b)
3400{
3401 QString oldkey = d->cur_pgpSecretKeyID;
3402
3403 // gaining pgp?
3404 if(b)
3405 d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID;
3406 // losing it?
3407 else {
3408 d->cur_pgpSecretKeyID = "";
3409 }
3410
3411 if(oldkey != d->cur_pgpSecretKeyID) {
3412 pgpKeyChanged();
3413 // resend status if online
3414 if(loggedIn())
3415 setStatusDirect(d->loginStatus);
3416 }
3417}
3418
3419void PsiAccount::pgpKeysUpdated()
3420{
3421 OpenPGP::KeyList list = d->psi->pgp()->publicKeys();
3422
3423 // are there any sigs that need verifying?
3424 QPtrListIterator<UserListItem> it(d->userList);
3425 for(UserListItem *u; (u = it.current()); ++it) {
3426 UserResourceList &rl = u->userResourceList();
3427 for(UserResourceList::Iterator rit = rl.begin(); rit != rl.end(); ++rit) {
3428 UserResource &r = *rit;
3429 if(!r.status().xsigned().isEmpty() && r.pgpVerifyStatus() == OpenPGP::VerifyNoKey) {
3430 bool haveKey = false;
3431 QString key = r.publicKeyID();
3432 for(OpenPGP::KeyList::ConstIterator kit = list.begin(); kit != list.end(); ++kit) {
3433 if((*kit).keyID() == key) {
3434 haveKey = true;
3435 break;
3436 }
3437 }
3438 if(haveKey)
3439 tryVerify(u, &r);
3440 }
3441 }
3442 }
3443}
3444
3445void PsiAccount::verifyStatus(const Jid &j, const Status &s)
3446{
3447 PGPTransaction *t = new PGPTransaction(d->psi->pgp());
3448 t->setJid(j);
3449 connect(t, SIGNAL(finished(bool)), SLOT(pgp_verifyFinished(bool)));
3450 QCString cs = s.status().utf8();
3451 QByteArray buf(cs.length());
3452 memcpy(buf.data(), cs.data(), buf.size());
3453 t->verify(buf, OpenPGP::addHeaderFooter(s.xsigned(), 1));
3454
3455 //printf("%s: verifying\n", j.full().latin1());
3456}
3457
3458void PsiAccount::trySignPresence()
3459{
3460 OpenPGP::Request *r = new OpenPGP::Request(d->psi->pgp());
3461 connect(r, SIGNAL(finished(bool)), SLOT(pgp_signFinished(bool)));
3462 connect(r, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase()));
3463 QCString cs = d->loginStatus.status().utf8();
3464 QByteArray buf(cs.length());
3465 memcpy(buf.data(), cs.data(), buf.size());
3466 r->sign(buf, d->cur_pgpSecretKeyID);
3467}
3468
3469void PsiAccount::pgp_needPassphrase()
3470{
3471 OpenPGP::Request *r = (OpenPGP::Request *)sender();
3472 d->ppreq = r;
3473
3474 if(pgp_passphrases.contains(d->cur_pgpSecretKeyID))
3475 r->submitPassphrase(pgp_passphrases[d->cur_pgpSecretKeyID]);
3476 else
3477 promptPassphrase();
3478}
3479
3480void PsiAccount::promptPassphrase()
3481{
3482 if(d->ppdlg)
3483 d->ppdlg->unblock();
3484 else {
3485 PassphraseDlg *w = new PassphraseDlg(0);
3486 connect(w, SIGNAL(submitPassphrase(const QString &)), SLOT(submitPassphrase(const QString &)));
3487 connect(w, SIGNAL(rejectPassphrase()), SLOT(rejectPassphrase()));
3488 w->setCaption(tr("%1: OpenPGP Passphrase").arg(name()));
3489 w->show();
3490
3491 d->ppdlg = w;
3492 }
3493}
3494
3495void PsiAccount::submitPassphrase(const QString &pp)
3496{
3497 d->ppreq->submitPassphrase(pp);
3498}
3499
3500void PsiAccount::rejectPassphrase()
3501{
3502 d->ppdlg = 0;
3503
3504 // cancel request
3505 if(d->ppreq)
3506 d->ppreq->deleteLater();
3507
3508 logout();
3509}
3510
3511void PsiAccount::pgp_signFinished(bool ok)
3512{
3513 OpenPGP::Request *r = (OpenPGP::Request *)sender();
3514 bool badPassphrase = r->badPassphrase();
3515 QString sig;
3516 if(ok)
3517 sig = r->signature();
3518 if(d->ppdlg) {
3519 pgp_passphrases[d->cur_pgpSecretKeyID] = d->ppdlg->passphrase();
3520 }
3521 r->deleteLater();
3522
3523 if(ok) {
3524 Status s = d->loginStatus;
3525 s.setXSigned(OpenPGP::stripHeaderFooter(sig));
3526 setStatusActual(s);
3527 }
3528 else {
3529 if(badPassphrase) {
3530 pgp_passphrases.erase(d->cur_pgpSecretKeyID);
3531 QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("You entered a bad passphrase. Please try again."));
3532 QTimer::singleShot(0, this, SLOT(trySignPresence()));
3533 return;
3534 }
3535 else {
3536 QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("There was an error during OpenPGP processing. Check your settings and try again."));
3537 logout();
3538 }
3539 }
3540
3541 // remove the dialog if it is there
3542 if(d->ppdlg) {
3543 d->ppdlg->deleteLater();
3544 d->ppdlg = 0;
3545 }
3546}
3547
3548void PsiAccount::pgp_verifyFinished(bool b)
3549{
3550 PGPTransaction *t = (PGPTransaction *)sender();
3551
3552 Jid j = t->jid();
3553 //printf("%s: verify complete\n", j.full().latin1());
3554 QPtrList<UserListItem> list = findRelavent(j);
3555 QPtrListIterator<UserListItem> it(list);
3556 for(UserListItem *u; (u = it.current()); ++it) {
3557 UserResourceList::Iterator rit = u->userResourceList().find(j.resource());
3558 bool found = (rit == u->userResourceList().end()) ? false: true;
3559 if(!found)
3560 continue;
3561 UserResource &ur = *rit;
3562
3563 if(b) {
3564 //printf("vergood\n");
3565 ur.setPublicKeyID(t->keyID());
3566 ur.setPGPVerifyStatus(t->verifyResult());
3567 ur.setSigTimestamp(t->timestamp());
3568
3569 // if the key doesn't match the assigned key, unassign it
3570 if(t->keyID() != u->publicKeyID())
3571 u->setPublicKeyID("");
3572 }
3573 else {
3574 //QMessageBox::information(0, "sig verify error", QString("error verifying [%1]").arg(u->jid().full()));
3575 ur.setPGPVerifyStatus(OpenPGP::VerifyError);
3576 }
3577
3578 //printf("updating [%s]\n", u->jid().full().latin1());
3579 cpUpdate(*u);
3580 }
3581
3582 t->deleteLater();
3583}
3584
3585int PsiAccount::sendMessageEncrypted(const Message &_m)
3586{
3587 if(!ensureKey(_m.to()))
3588 return -1;
3589 QString key = findFirstRelavent(_m.to())->publicKeyID();
3590
3591 PGPTransaction *pt = new PGPTransaction(d->psi->pgp());
3592 Message m = _m;
3593 pt->setMessage(m); // keep a copy
3594 //QByteArray a = m.generateEncryptablePayload(d->client->doc());
3595 QCString cs = m.body().utf8();
3596 QByteArray a(cs.length());
3597 memcpy(a.data(), cs.data(), a.size());
3598
3599 // encrypt
3600 QStringList rcpt;
3601 rcpt += key;
3602 connect(pt, SIGNAL(finished(bool)), SLOT(pgp_finished(bool)));
3603 pt->encrypt(a, rcpt);
3604
3605 return pt->id();
3606}
3607
3608void PsiAccount::pgp_finished(bool b)
3609{
3610 PGPTransaction *pt = (PGPTransaction *)sender();
3611
3612 int x = pt->id();
3613 if(pt->type() == OpenPGP::Encrypt) {
3614 if(b) {
3615 Message m = pt->message();
3616 // log the message here, before we encrypt it
3617 if(d->acc.opt_log) {
3618 MessageEvent *me = new MessageEvent(m, this);
3619 me->setOriginLocal(true);
3620 me->setTimeStamp(QDateTime::currentDateTime());
3621 logEvent(m.to(), me);
3622 delete me;
3623 }
3624
3625 Message mwrap;
3626 mwrap.setTo(m.to());
3627 mwrap.setType(m.type());
3628 QString enc = OpenPGP::stripHeaderFooter(pt->encrypted());
3629 mwrap.setBody(tr("[ERROR: This message is encrypted, and you are unable to decrypt it.]"));
3630 mwrap.setXEncrypted(enc);
3631 mwrap.setWasEncrypted(true);
3632 // FIXME: Should be done cleaner, with an extra method in Iris
3633 if (m.containsEvent(OfflineEvent)) mwrap.addEvent(OfflineEvent);
3634 if (m.containsEvent(DeliveredEvent)) mwrap.addEvent(DeliveredEvent);
3635 if (m.containsEvent(DisplayedEvent)) mwrap.addEvent(DisplayedEvent);
3636 if (m.containsEvent(ComposingEvent)) mwrap.addEvent(ComposingEvent);
3637 if (m.containsEvent(CancelEvent)) mwrap.addEvent(CancelEvent);
3638 dj_sendMessage(mwrap);
3639 }
3640 encryptedMessageSent(x, b);
3641 }
3642
3643 pt->deleteLater();
3644}
3645
3646void PsiAccount::pgp_decryptFinished(bool b)
3647{
3648 PGPTransaction *pt = (PGPTransaction *)sender();
3649
3650 bool tryAgain = false;
3651 if(b) {
3652 Message m = pt->message();
3653 //if(m.applyDecryptedPayload(pt->decrypted(), d->client->doc()))
3654 QByteArray buf = pt->decrypted();
3655 QCString cs(buf.size()+1);
3656 memcpy(cs.data(), buf.data(), buf.size());
3657 QString str = QString::fromUtf8(cs);
3658 m.setBody(str);
3659 m.setXEncrypted("");
3660 m.setWasEncrypted(true);
3661 processIncomingMessage(m);
3662
3663 //else
3664 // QMessageBox::information(0, CAP(tr("Error")), tr("A successful decryption operation resulted in an invalid message, so it has been ignored."));
3665 }
3666 else {
3667 if(loggedIn()) {
3668 Message m;
3669 m.setTo(pt->message().from());
3670 m.setType("error");
3671 m.setBody(pt->message().body());
3672 Stanza::Error err;
3673 err.condition = 500;
3674 err.text = "Unable to decrypt";
3675 m.setError(err);
3676 d->client->sendMessage(m);
3677 }
3678 }
3679
3680 pt->deleteLater();
3681
3682 if(tryAgain) {
3683 processEncryptedMessageNext();
3684 }
3685 else {
3686 processEncryptedMessageDone();
3687 }
3688}
3689
3690void PsiAccount::processEncryptedMessage(const Message &m)
3691{
3692 // decrypt
3693 PGPTransaction *t = new PGPTransaction(d->psi->pgp());
3694 t->setMessage(m); // keep a copy
3695 connect(t, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase()));
3696 connect(t, SIGNAL(finished(bool)), SLOT(pgp_decryptFinished(bool)));
3697 QString str = OpenPGP::addHeaderFooter(m.xencrypted(), 0);
3698 t->decrypt(str);
3699}
3700
3701void PsiAccount::processMessageQueue()
3702{
3703 while(!d->messageQueue.isEmpty()) {
3704 Message *mp = d->messageQueue.getFirst();
3705
3706 // encrypted?
3707 if(d->psi->pgp() && !mp->xencrypted().isEmpty()) {
3708 processEncryptedMessageNext();
3709 break;
3710 }
3711
3712 processIncomingMessage(*mp);
3713 d->messageQueue.removeRef(mp);
3714 }
3715}
3716
3717void PsiAccount::processEncryptedMessageNext()
3718{
3719 // 'peek' and try to process it
3720 Message *mp = d->messageQueue.getFirst();
3721 processEncryptedMessage(*mp);
3722}
3723
3724void PsiAccount::processEncryptedMessageDone()
3725{
3726 // 'pop' the message
3727 Message *mp = d->messageQueue.getFirst();
3728 d->messageQueue.removeRef(mp);
3729
3730 // do the rest of the queue
3731 processMessageQueue();
3732}
3733
3734void PsiAccount::optionsUpdate()
3735{
3736 d->cp->updateEntry(d->self);
3737}
3738
3739QString PsiAccount::resultToString(int result)
3740{
3741 QString s;
3742 switch(result) {
3743 case QCA::TLS::NoCert:
3744 s = tr("The server did not present a certificate.");
3745 break;
3746 case QCA::TLS::Valid:
3747 s = tr("Certificate is valid.");
3748 break;
3749 case QCA::TLS::HostMismatch:
3750 s = tr("The hostname does not match the one the certificate was issued to.");
3751 break;
3752 case QCA::TLS::Rejected:
3753 s = tr("Root CA is marked to reject the specified purpose.");
3754 break;
3755 case QCA::TLS::Untrusted:
3756 s = tr("Certificate not trusted for the required purpose.");
3757 break;
3758 case QCA::TLS::SignatureFailed:
3759 s = tr("Invalid signature.");
3760 break;
3761 case QCA::TLS::InvalidCA:
3762 s = tr("Invalid CA certificate.");
3763 break;
3764 case QCA::TLS::InvalidPurpose:
3765 s = tr("Invalid certificate purpose.");
3766 break;
3767 case QCA::TLS::SelfSigned:
3768 s = tr("Certificate is self-signed.");
3769 break;
3770 case QCA::TLS::Revoked:
3771 s = tr("Certificate has been revoked.");
3772 break;
3773 case QCA::TLS::PathLengthExceeded:
3774 s = tr("Maximum certificate chain length exceeded.");
3775 break;
3776 case QCA::TLS::Expired:
3777 s = tr("Certificate has expired.");
3778 break;
3779 case QCA::TLS::Unknown:
3780 default:
3781 s = tr("General certificate validation error.");
3782 break;
3783 }
3784 return s;
3785}
3786
3787void PsiAccount::invokeGCMessage(const Jid &j)
3788{
3789 GCContact *c = findGCContact(j);
3790 if(!c)
3791 return;
3792
3793 // create dummy item, open chat, then destroy item. HORRIBLE HACK!
3794 UserListItem *u = new UserListItem;
3795 u->setJid(j);
3796 u->setInList(false);
3797 u->setName(j.resource());
3798 u->setPrivate(true);
3799#ifdef AVATARS
3800 u->setAvatarFactory(d->avatarFactory);
3801#endif
3802
3803 // make a resource so the contact appears online
3804 UserResource ur;
3805 ur.setName(j.resource());
3806 ur.setStatus(c->status);
3807 u->userResourceList().append(ur);
3808
3809 d->userList.append(u);
3810 actionSendMessage(j);
3811 d->userList.remove(u);
3812}
3813
3814void PsiAccount::invokeGCChat(const Jid &j)
3815{
3816 GCContact *c = findGCContact(j);
3817 if(!c)
3818 return;
3819
3820 // create dummy item, open chat, then destroy item. HORRIBLE HACK!
3821 UserListItem *u = new UserListItem;
3822 u->setJid(j);
3823 u->setInList(false);
3824 u->setName(j.resource());
3825 u->setPrivate(true);
3826#ifdef AVATARS
3827 u->setAvatarFactory(d->avatarFactory);
3828#endif
3829
3830 // make a resource so the contact appears online
3831 UserResource ur;
3832 ur.setName(j.resource());
3833 ur.setStatus(c->status);
3834 u->userResourceList().append(ur);
3835
3836 d->userList.append(u);
3837 actionOpenChat(j);
3838 d->userList.remove(u);
3839}
3840
3841void PsiAccount::invokeGCInfo(const Jid &j)
3842{
3843 actionInfo(j);
3844}
3845
3846void PsiAccount::invokeGCFile(const Jid &j)
3847{
3848 actionSendFile(j);
3849}
3850
3851void PsiAccount::toggleSecurity(const Jid &j, bool b)
3852{
3853 UserListItem *u = findFirstRelavent(j);
3854 if(!u)
3855 return;
3856
3857 bool isBare = j.resource().isEmpty();
3858 bool isPriority = false;
3859
3860 // sick sick sick sick sick sick
3861 UserResource *r1, *r2;
3862 r1 = r2 = 0;
3863 UserResourceList::Iterator it1 = u->userResourceList().find(j.resource());
3864 UserResourceList::Iterator it2 = u->userResourceList().priority();
3865 r1 = (it1 != u->userResourceList().end() ? &(*it1) : 0);
3866 r2 = (it2 != u->userResourceList().end() ? &(*it2) : 0);
3867 if(r1 && (r1 == r2))
3868 isPriority = true;
3869
3870 bool needUpdate = false;
3871 bool sec = u->isSecure(j.resource());
3872 if(sec != b) {
3873 u->setSecure(j.resource(), b);
3874 needUpdate = true;
3875 }
3876 if(isBare && r2) {
3877 sec = u->isSecure(r2->name());
3878 if(sec != b) {
3879 u->setSecure(r2->name(), b);
3880 needUpdate = true;
3881 }
3882 }
3883 if(isPriority) {
3884 sec = u->isSecure("");
3885 if(sec != b) {
3886 u->setSecure("", b);
3887 needUpdate = true;
3888 }
3889 }
3890
3891 if(needUpdate)
3892 cpUpdate(*u);
3893}
3894
3895bool PsiAccount::ensureKey(const Jid &j)
3896{
3897 if(!d->psi->pgp())
3898 return false;
3899
3900 UserListItem *u = findFirstRelavent(j);
3901 if(!u)
3902 return false;
3903
3904 // no key?
3905 if(u->publicKeyID().isEmpty()) {
3906 // does the user have any presence signed with a key?
3907 QString akey;
3908 const UserResourceList &rl = u->userResourceList();
3909 for(UserResourceList::ConstIterator it = rl.begin(); it != rl.end(); ++it) {
3910 const UserResource &r = *it;
3911 if(!r.publicKeyID().isEmpty()) {
3912 akey = r.publicKeyID();
3913 break;
3914 }
3915 }
3916
3917 bool inList = false;
3918 OpenPGP::KeyList list = d->psi->pgp()->publicKeys();
3919 for(OpenPGP::KeyList::ConstIterator lit = list.begin(); lit != list.end(); ++lit) {
3920 const OpenPGP::Key &k = *lit;
3921 if(k.keyID() == akey) {
3922 inList = true;
3923 break;
3924 }
3925 }
3926
3927 if(akey.isEmpty() || !inList) {
3928 int n = QMessageBox::information(0, CAP(tr("No key")), tr(
3929 "<p>Psi was unable to locate the OpenPGP key to use for <b>%1</b>.<br>"
3930 "<br>"
3931 "This can happen if you do not have the key that the contact is advertising "
3932 "via signed presence, or if the contact is not advertising any key at all.</p>"
3933 ).arg(u->jid().full()), tr("&Choose key manually"), tr("Do &nothing"));
3934 if(n != 0)
3935 return false;
3936 }
3937
3938 PGPKeyDlg *w = new PGPKeyDlg(list, akey, 0);
3939 w->setCaption(tr("Public Key: %1").arg(j.full()));
3940 int r = w->exec();
3941 QString key;
3942 if(r == QDialog::Accepted)
3943 key = w->keyID();
3944 delete w;
3945 if(key.isEmpty())
3946 return false;
3947 u->setPublicKeyID(key);
3948 cpUpdate(*u);
3949 }
3950
3951 return true;
3952}
3953
3954
3955//----------------------------------------------------------------------------
3956// PGPKeyDlg
3957//----------------------------------------------------------------------------
3958class KeyViewItem : public QListViewItem
3959{
3960public:
3961 KeyViewItem(const QString &_keyID, QListView *par)
3962 :QListViewItem(par)
3963 {
3964 keyID = _keyID;
3965 }
3966
3967 QString keyID;
3968};
3969
3970class PGPKeyDlg::Private
3971{
3972public:
3973 Private() {}
3974
3975 QString keyID;
3976 QString userID;
3977};
3978
3979PGPKeyDlg::PGPKeyDlg(const OpenPGP::KeyList &list, const QString &choose, QWidget *parent, const char *name)
3980:PGPKeyUI(parent, name, true)
3981{
3982 d = new Private;
3983
3984 connect(lv_keys, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *)));
3985 connect(pb_ok, SIGNAL(clicked()), SLOT(do_accept()));
3986 connect(pb_cancel, SIGNAL(clicked()), SLOT(reject()));
3987
3988 QListViewItem *isel = 0;
3989 for(OpenPGP::KeyList::ConstIterator it = list.begin(); it != list.end(); ++it) {
3990 const OpenPGP::Key &k = *it;
3991 KeyViewItem *i = new KeyViewItem(k.keyID(), lv_keys);
3992 //i->setPixmap(0, IconsetFactory::icon("psi/gpg-yes"));
3993 i->setText(0, k.keyID().right(8));
3994 i->setText(1, k.userID());
3995
3996 if(!choose.isEmpty() && k.keyID() == choose) {
3997 lv_keys->setSelected(i, true);
3998 isel = i;
3999 }
4000 }
4001 if(lv_keys->childCount() > 0 && !isel)
4002 lv_keys->setSelected(lv_keys->firstChild(), true);
4003 else if(isel)
4004 lv_keys->ensureItemVisible(isel);
4005}
4006
4007PGPKeyDlg::~PGPKeyDlg()
4008{
4009 delete d;
4010}
4011
4012QString PGPKeyDlg::keyID() const
4013{
4014 return d->keyID;
4015}
4016
4017QString PGPKeyDlg::userID() const
4018{
4019 return d->userID;
4020}
4021
4022void PGPKeyDlg::qlv_doubleClicked(QListViewItem *i)
4023{
4024 lv_keys->setSelected(i, true);
4025 do_accept();
4026}
4027
4028void PGPKeyDlg::do_accept()
4029{
4030 KeyViewItem *i = (KeyViewItem *)lv_keys->selectedItem();
4031 if(!i) {
4032 QMessageBox::information(this, tr("Error"), tr("Please select a key."));
4033 return;
4034 }
4035 d->keyID = i->keyID;
4036 d->userID = i->text(1);
4037 accept();
4038}
4039
4040#include "psiaccount.moc"
Note: See TracBrowser for help on using the repository browser.