/* * psiaccount.cpp - handles a Psi account * Copyright (C) 2001-2005 Justin Karneges * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * You can also redistribute and/or modify this program under the * terms of the Psi License, specified in the accompanied COPYING * file, as published by the Psi Project; either dated January 1st, * 2005, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include"psiaccount.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include"psicon.h" #include"profiles.h" #include"im.h" //#include"xmpp_client.h" //#include"xmpp_stream.h" //#include"xmpp_message.h" #include"xmpp_tasks.h" #include"xmpp_jidlink.h" #include"s5b.h" #include"filetransfer.h" #include"accountdlg.h" #include"changepwdlg.h" #include"xmlconsole.h" #include"userlist.h" #include"eventdlg.h" #include"chatdlg.h" #include"contactview.h" #include"groupchatdlg.h" #include"statusdlg.h" #include"infodlg.h" #include"adduserdlg.h" #include"historydlg.h" #include"servicesdlg.h" #include"discodlg.h" #include"eventdb.h" #include"jltest.h" #include"passphrasedlg.h" #include"vcardfactory.h" //#include"qssl.h" #include"sslcertdlg.h" #include"qwextend.h" #include"psipopup.h" #include"fancylabel.h" #include"iconwidget.h" #include"base64.h" #include"filetransdlg.h" #include"avatars.h" #include"tabdlg.h" #if defined(Q_WS_MAC) && defined(HAVE_GROWL) #include "psigrowlnotifier.h" #endif #include"bsocket.h" /*#ifdef Q_WS_WIN #include typedef int socklen_t; #else #include #include #endif*/ //---------------------------------------------------------------------------- // AccountLabel //---------------------------------------------------------------------------- AccountLabel::AccountLabel(PsiAccount *_pa, QWidget *par, bool smode) :QLabel(par) { pa = _pa; simpleMode = smode; setFrameStyle( QFrame::Panel | QFrame::Sunken ); updateName(); connect(pa, SIGNAL(updatedAccount()), this, SLOT(updateName())); connect(pa, SIGNAL(destroyed()), this, SLOT(deleteMe())); } AccountLabel::~AccountLabel() { } void AccountLabel::updateName() { setText(simpleMode ? pa->name() : pa->nameWithJid()); } void AccountLabel::deleteMe() { delete this; } //---------------------------------------------------------------------------- // PGPTransaction //---------------------------------------------------------------------------- static int transid = 0; class PGPTransaction::Private { public: Private() {} int id; Message m; QDomElement e; Jid j; }; PGPTransaction::PGPTransaction(OpenPGP::Engine *pgp) :OpenPGP::Request(pgp) { d = new Private; d->id = transid++; } PGPTransaction::~PGPTransaction() { delete d; } int PGPTransaction::id() const { return d->id; } void PGPTransaction::setMessage(const Message &m) { d->m = m; } const Message & PGPTransaction::message() const { return d->m; } const QDomElement & PGPTransaction::xml() const { return d->e; } void PGPTransaction::setXml(const QDomElement &e) { d->e = e; } Jid PGPTransaction::jid() const { return d->j; } void PGPTransaction::setJid(const Jid &j) { d->j = j; } //---------------------------------------------------------------------------- // BlockTransportPopup -- blocks popups on transport status changes //---------------------------------------------------------------------------- class BlockTransportPopupList; class BlockTransportPopup : public QObject { Q_OBJECT public: BlockTransportPopup(QObject *, const Jid &); Jid jid() const; private slots: void timeout(); private: Jid j; int userCounter; friend class BlockTransportPopupList; }; BlockTransportPopup::BlockTransportPopup(QObject *parent, const Jid &_j) : QObject(parent) { j = _j; userCounter = 0; // Hack for ICQ SMS if ( j.host().left(3) == "icq" ) { new BlockTransportPopup(parent, "sms." + j.host()); // sms.icq.host.com new BlockTransportPopup(parent, "sms" + j.host().right(j.host().length() - 3)); // sms.host.com } QTimer::singleShot(15000, this, SLOT(timeout())); } void BlockTransportPopup::timeout() { if ( userCounter > 1 ) { QTimer::singleShot(15000, this, SLOT(timeout())); userCounter = 0; } else deleteLater(); } Jid BlockTransportPopup::jid() const { return j; } //---------------------------------------------------------------------------- // BlockTransportPopupList //---------------------------------------------------------------------------- class BlockTransportPopupList : public QObject { Q_OBJECT public: BlockTransportPopupList(); bool find(const Jid &, bool online = false); }; BlockTransportPopupList::BlockTransportPopupList() : QObject() { } bool BlockTransportPopupList::find(const Jid &j, bool online) { if ( j.user().isEmpty() ) // always show popups for transports return false; QObjectList *list = queryList("BlockTransportPopup"); QObjectListIt it( *list ); BlockTransportPopup *btp; for ( ; it.current(); ++it) { btp = (BlockTransportPopup *)it.current(); if ( j.host() == btp->jid().host() ) { if ( online ) btp->userCounter++; return true; } } delete list; return false; } //---------------------------------------------------------------------------- // PsiAccount //---------------------------------------------------------------------------- struct item_dialog2 { QWidget *widget; QString className; Jid jid; }; QMap pgp_passphrases; class PsiAccount::Private : public QObject { Q_OBJECT public: Private(PsiAccount *parent) { account = parent; } PsiCon *psi; PsiAccount *account; Client *client; ContactProfile *cp; UserAccount acc, accnext; Jid jid, nextJid; Status loginStatus; QPtrList dialogList; EventQueue *eventQueue; XmlConsole *xmlConsole; UserList userList; UserListItem self; int lastIdle; Status lastStatus, origStatus; bool nickFromVCard; QString cur_pgpSecretKeyID; QPtrList messageQueue; BlockTransportPopupList *blockTransportPopupList; int userCounter; // Avatars AvatarFactory* avatarFactory; // pgp related PassphraseDlg *ppdlg; QGuardedPtr ppreq; QPtrList gcbank; QStringList groupchats; AdvancedConnector *conn; ClientStream *stream; QCA::TLS *tls; QCATLSHandler *tlsHandler; QPtrList certList; bool usingSSL; bool doPopups; QHostAddress localAddress; QString pathToProfileEvents() { return pathToProfile(activeProfile) + "/events-" + acc.name + ".xml"; } public slots: void queueChanged() { eventQueue->toFile(pathToProfileEvents()); } void loadQueue() { bool soundEnabled = useSound; useSound = FALSE; // disable the sound and popups doPopups = FALSE; QFileInfo fi( pathToProfileEvents() ); if ( fi.exists() ) eventQueue->fromFile(pathToProfileEvents()); useSound = soundEnabled; doPopups = TRUE; } void setEnabled( bool e ) { acc.opt_enabled = e; psi->enableAccount(account, e); cp->setEnabled(e); account->cpUpdate(self); // signals account->enabledChanged(); account->updatedAccount(); } }; PsiAccount::PsiAccount(const UserAccount &acc, PsiCon *parent) :QObject(0) { d = new Private( this ); d->psi = parent; d->client = 0; d->cp = 0; d->userCounter = 0; #ifdef AVATARS d->avatarFactory = new AvatarFactory(this); #endif d->blockTransportPopupList = new BlockTransportPopupList(); d->doPopups = true; v_isActive = false; isDisconnecting = false; notifyOnlineOk = false; doReconnect = false; usingAutoStatus = false; presenceSent = false; d->loginStatus = Status("", ""); d->lastIdle = 0; d->lastStatus = Status("", "", 0, false); d->dialogList.setAutoDelete(true); d->eventQueue = new EventQueue(this); connect(d->eventQueue, SIGNAL(queueChanged()), SIGNAL(queueChanged())); connect(d->eventQueue, SIGNAL(queueChanged()), d, SLOT(queueChanged())); connect(d->eventQueue, SIGNAL(handleEvent(PsiEvent *)), SLOT(handleEvent(PsiEvent *))); d->userList.setAutoDelete(true); d->self = UserListItem(true); d->self.setSubscription(Subscription::Both); d->nickFromVCard = false; #ifdef AVATARS d->self.setAvatarFactory(d->avatarFactory); #endif d->messageQueue.setAutoDelete(true); d->ppdlg = 0; d->ppreq = 0; d->gcbank.setAutoDelete(true); // we need to copy groupState, because later initialization will depend on that d->acc.groupState = acc.groupState; // stream d->conn = 0; d->tls = 0; d->tlsHandler = 0; d->stream = 0; d->certList.setAutoDelete(true); d->usingSSL = false; // create Jabber::Client d->client = new Client; d->client->setOSName(getOSName()); d->client->setTimeZone(getTZString(), getTZOffset()); d->client->setClientName(PROG_NAME); d->client->setClientVersion(PROG_VERSION); d->client->setFileTransferEnabled(true); //connect(d->client, SIGNAL(connected()), SLOT(client_connected())); //connect(d->client, SIGNAL(handshaken()), SLOT(client_handshaken())); //connect(d->client, SIGNAL(error(const StreamError &)), SLOT(client_error(const StreamError &))); //connect(d->client, SIGNAL(sslCertReady(const QSSLCert &)), SLOT(client_sslCertReady(const QSSLCert &))); //connect(d->client, SIGNAL(closeFinished()), SLOT(client_closeFinished())); //connect(d->client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(client_authFinished(bool, int, const QString &))); connect(d->client, SIGNAL(rosterRequestFinished(bool, int, const QString &)), SLOT(client_rosterRequestFinished(bool, int, const QString &))); connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemAdded(const RosterItem &))); connect(d->client, SIGNAL(rosterItemAdded(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &))); connect(d->client, SIGNAL(rosterItemUpdated(const RosterItem &)), SLOT(client_rosterItemUpdated(const RosterItem &))); connect(d->client, SIGNAL(rosterItemRemoved(const RosterItem &)), SLOT(client_rosterItemRemoved(const RosterItem &))); connect(d->client, SIGNAL(resourceAvailable(const Jid &, const Resource &)), SLOT(client_resourceAvailable(const Jid &, const Resource &))); connect(d->client, SIGNAL(resourceUnavailable(const Jid &, const Resource &)), SLOT(client_resourceUnavailable(const Jid &, const Resource &))); connect(d->client, SIGNAL(presenceError(const Jid &, int, const QString &)), SLOT(client_presenceError(const Jid &, int, const QString &))); connect(d->client, SIGNAL(messageReceived(const Message &)), SLOT(client_messageReceived(const Message &))); connect(d->client, SIGNAL(subscription(const Jid &, const QString &)), SLOT(client_subscription(const Jid &, const QString &))); connect(d->client, SIGNAL(debugText(const QString &)), SLOT(client_debugText(const QString &))); connect(d->client, SIGNAL(groupChatJoined(const Jid &)), SLOT(client_groupChatJoined(const Jid &))); connect(d->client, SIGNAL(groupChatLeft(const Jid &)), SLOT(client_groupChatLeft(const Jid &))); connect(d->client, SIGNAL(groupChatPresence(const Jid &, const Status &)), SLOT(client_groupChatPresence(const Jid &, const Status &))); connect(d->client, SIGNAL(groupChatError(const Jid &, int, const QString &)), SLOT(client_groupChatError(const Jid &, int, const QString &))); connect(d->client, SIGNAL(incomingJidLink()), SLOT(client_incomingJidLink())); connect(d->client->fileTransferManager(), SIGNAL(incomingReady()), SLOT(client_incomingFileTransfer())); // contactprofile context d->cp = new ContactProfile(this, acc.name, d->psi->contactView()); connect(d->cp, SIGNAL(actionDefault(const Jid &)),SLOT(actionDefault(const Jid &))); connect(d->cp, SIGNAL(actionRecvEvent(const Jid &)),SLOT(actionRecvEvent(const Jid &))); connect(d->cp, SIGNAL(actionSendMessage(const Jid &)),SLOT(actionSendMessage(const Jid &))); connect(d->cp, SIGNAL(actionSendMessage(const JidList &)),SLOT(actionSendMessage(const JidList &))); connect(d->cp, SIGNAL(actionSendUrl(const Jid &)),SLOT(actionSendUrl(const Jid &))); connect(d->cp, SIGNAL(actionRemove(const Jid &)),SLOT(actionRemove(const Jid &))); connect(d->cp, SIGNAL(actionRename(const Jid &, const QString &)),SLOT(actionRename(const Jid &, const QString &))); connect(d->cp, SIGNAL(actionGroupRename(const QString &, const QString &)),SLOT(actionGroupRename(const QString &, const QString &))); connect(d->cp, SIGNAL(actionHistory(const Jid &)),SLOT(actionHistory(const Jid &))); connect(d->cp, SIGNAL(actionOpenChat(const Jid &)),SLOT(actionOpenChat(const Jid &))); connect(d->cp, SIGNAL(actionOpenChatSpecific(const Jid &)),SLOT(actionOpenChatSpecific(const Jid &))); connect(d->cp, SIGNAL(actionAgentSetStatus(const Jid &, Status &)),SLOT(actionAgentSetStatus(const Jid &, Status &))); connect(d->cp, SIGNAL(actionInfo(const Jid &)),SLOT(actionInfo(const Jid &))); connect(d->cp, SIGNAL(actionAuth(const Jid &)),SLOT(actionAuth(const Jid &))); connect(d->cp, SIGNAL(actionAuthRequest(const Jid &)),SLOT(actionAuthRequest(const Jid &))); connect(d->cp, SIGNAL(actionAuthRemove(const Jid &)),SLOT(actionAuthRemove(const Jid &))); connect(d->cp, SIGNAL(actionAdd(const Jid &)),SLOT(actionAdd(const Jid &))); connect(d->cp, SIGNAL(actionGroupAdd(const Jid &, const QString &)),SLOT(actionGroupAdd(const Jid &, const QString &))); connect(d->cp, SIGNAL(actionGroupRemove(const Jid &, const QString &)),SLOT(actionGroupRemove(const Jid &, const QString &))); connect(d->cp, SIGNAL(actionTest(const Jid &)),SLOT(actionTest(const Jid &))); connect(d->cp, SIGNAL(actionSendFile(const Jid &)),SLOT(actionSendFile(const Jid &))); connect(d->cp, SIGNAL(actionSendFiles(const Jid &, const QStringList&)),SLOT(actionSendFiles(const Jid &, const QStringList&))); connect(d->cp, SIGNAL(actionDisco(const Jid &, const QString &)),SLOT(actionDisco(const Jid &, const QString &))); connect(d->cp, SIGNAL(actionInvite(const Jid &, const QString &)),SLOT(actionInvite(const Jid &, const QString &))); connect(d->cp, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &))); connect(d->cp, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &))); // restore cached roster for(Roster::ConstIterator it = acc.roster.begin(); it != acc.roster.end(); ++it) client_rosterItemUpdated(*it); // restore pgp key bindings for(VarList::ConstIterator kit = acc.keybind.begin(); kit != acc.keybind.end(); ++kit) { const VarListItem &i = *kit; UserListItem *u = find(Jid(i.key())); if(u) { u->setPublicKeyID(i.data()); cpUpdate(*u); } } setUserAccount(acc); d->psi->link(this); connect(d->psi, SIGNAL(emitOptionsUpdate()), SLOT(optionsUpdate())); connect(d->psi, SIGNAL(pgpToggled(bool)), SLOT(pgpToggled(bool))); connect(d->psi, SIGNAL(pgpKeysUpdated()), SLOT(pgpKeysUpdated())); d->psi->setToggles(d->acc.tog_offline, d->acc.tog_away, d->acc.tog_agents, d->acc.tog_hidden,d->acc.tog_self); d->setEnabled(d->acc.opt_enabled); // auto-login ? if(d->acc.opt_auto && d->acc.opt_enabled) setStatus(Status("", "", d->acc.priority)); //printf("PsiAccount: [%s] loaded\n", name().latin1()); d->xmlConsole = new XmlConsole(this); if(option.xmlConsoleOnLogin && d->acc.opt_enabled) { this->showXmlConsole(); d->xmlConsole->enable(); } // load event queue from disk QTimer::singleShot(0, d, SLOT(loadQueue())); } PsiAccount::~PsiAccount() { logout(true); QString str = name(); d->messageQueue.clear(); // nuke all related dialogs deleteAllDialogs(); d->psi->ftdlg()->killTransfers(this); delete d->cp; delete d->client; cleanupStream(); delete d->eventQueue; delete d->blockTransportPopupList; d->psi->unlink(this); delete d; //printf("PsiAccount: [%s] unloaded\n", str.latin1()); } void PsiAccount::cleanupStream() { delete d->stream; d->stream = 0; delete d->tls; d->tls = 0; d->tlsHandler = 0; delete d->conn; d->conn = 0; d->certList.clear(); d->usingSSL = false; d->localAddress = QHostAddress(); } bool PsiAccount::enabled() const { return d->acc.opt_enabled; } void PsiAccount::setEnabled(bool e) { if ( d->acc.opt_enabled == e ) return; if (!e) { if (eventQueue()->count()) { QMessageBox::information(0, tr("Error"), tr("Unable to disable the account, as it has pending events.")); return; } if (isActive()) { 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) { logout(); } else { return; } } } d->setEnabled( e ); } bool PsiAccount::isActive() const { return v_isActive; } bool PsiAccount::isConnected() const { return (d->stream && d->stream->isAuthenticated()); } const QString & PsiAccount::name() const { return d->acc.name; } const UserAccount & PsiAccount::userAccount() const { d->psi->getToggles(&d->acc.tog_offline, &d->acc.tog_away, &d->acc.tog_agents, &d->acc.tog_hidden,&d->acc.tog_self); // save the roster and pgp key bindings d->acc.roster.clear(); d->acc.keybind.clear(); UserListIt it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { if(u->inList()) d->acc.roster += *u; if(!u->publicKeyID().isEmpty()) d->acc.keybind.set(u->jid().full(), u->publicKeyID()); } return d->acc; } UserList *PsiAccount::userList() const { return &d->userList; } Client *PsiAccount::client() const { return d->client; } ContactProfile *PsiAccount::contactProfile() const { return d->cp; } EventQueue *PsiAccount::eventQueue() const { return d->eventQueue; } EDB *PsiAccount::edb() const { return d->psi->edb(); } PsiCon *PsiAccount::psi() const { return d->psi; } AvatarFactory *PsiAccount::avatarFactory() const { return d->avatarFactory; } QString PsiAccount::pgpKey() const { return d->cur_pgpSecretKeyID; } QHostAddress *PsiAccount::localAddress() const { QString s = d->localAddress.toString(); if(s == "0.0.0.0") return 0; return &d->localAddress; } void PsiAccount::setUserAccount(const UserAccount &acc) { bool renamed = false; QString oldfname; if(d->acc.name != acc.name) { renamed = true; oldfname = d->pathToProfileEvents(); } d->acc = acc; // rename queue file? if(renamed) { QFileInfo oldfi(oldfname); QFileInfo newfi(d->pathToProfileEvents()); if(oldfi.exists()) { QDir dir = oldfi.dir(); dir.rename(oldfi.fileName(), newfi.fileName()); } } if(d->stream) { if(d->acc.opt_keepAlive) d->stream->setNoopTime(55000); // prevent NAT timeouts every minute else d->stream->setNoopTime(0); } d->cp->setName(d->acc.name); Jid j = acc.jid; d->nextJid = j; if(!isActive()) { d->jid = j; d->self.setJid(j); d->cp->updateEntry(d->self); } if(!d->nickFromVCard) setNick(j.user()); d->self.setPublicKeyID(d->acc.pgpSecretKeyID); if(d->psi->pgp()) { if(d->acc.pgpSecretKeyID != d->cur_pgpSecretKeyID && loggedIn()) { d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID; d->loginStatus.setXSigned(""); setStatusDirect(d->loginStatus); pgpKeyChanged(); } } cpUpdate(d->self); updatedAccount(); } void PsiAccount::deleteQueueFile() { QFileInfo fi(d->pathToProfileEvents()); if(fi.exists()) { QDir dir = fi.dir(); dir.remove(fi.fileName()); } } const Jid & PsiAccount::jid() const { return d->jid; } QString PsiAccount::nameWithJid() const { return (name() + " (" + jid().full() + ')'); } static QCA::Cert readCertXml(const QDomElement &e) { QCA::Cert cert; // there should be one child data tag QDomElement data = e.elementsByTagName("data").item(0).toElement(); if(!data.isNull()) cert.fromDER(Base64::stringToArray(data.text())); return cert; } static QPtrList getRootCerts(const QStringList &stores) { QPtrList list; for(QStringList::ConstIterator dit = stores.begin(); dit != stores.end(); ++dit) { QDir dir(*dit); if(!dir.exists()) continue; dir.setNameFilter("*.xml"); QStringList entries = dir.entryList(); for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { QFile f(dir.filePath(*it)); if(!f.open(IO_ReadOnly)) continue; QDomDocument doc; bool ok = doc.setContent(&f); f.close(); if(!ok) continue; QDomElement base = doc.documentElement(); if(base.tagName() != "store") continue; QDomNodeList cl = base.elementsByTagName("certificate"); int num = 0; for(int n = 0; n < (int)cl.count(); ++n) { QCA::Cert *cert = new QCA::Cert(readCertXml(cl.item(n).toElement())); if(cert->isNull()) { delete cert; continue; } ++num; list.append(cert); } } } return list; } // logs on with the active account settings void PsiAccount::login() { if(isActive() && !doReconnect) return; if(d->acc.opt_ssl && !QCA::isSupported(QCA::CAP_TLS)) { QMessageBox::information(0, tr("%1: SSL Error").arg(name()), tr("Cannot login: SSL is enabled but no SSL/TLS (plugin) support is available.")); return; } d->jid = d->nextJid; if(d->psi->pgp()) { d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID; pgpKeyChanged(); } v_isActive = true; isDisconnecting = false; notifyOnlineOk = false; rosterDone = false; presenceSent = false; stateChanged(); QString host; int port; if(d->acc.opt_host) { host = d->acc.host; port = d->acc.port; } else { host = d->jid.host(); if(d->acc.opt_ssl) port = 5223; else port = 5222; } AdvancedConnector::Proxy p; if(d->acc.proxy_index > 0) { const ProxyItem &pi = d->psi->proxy()->getItem(d->acc.proxy_index-1); if(pi.type == "http") // HTTP Connect p.setHttpConnect(pi.settings.host, pi.settings.port); else if(pi.type == "socks") // SOCKS p.setSocks(pi.settings.host, pi.settings.port); else if(pi.type == "poll") { // HTTP Poll QUrl u = pi.settings.url; if(u.query().isEmpty()) { QString v = host + ':' + QString::number(port); QUrl::encode(v); u.setQuery(QString("server=") + v); } p.setHttpPoll(pi.settings.host, pi.settings.port, u.toString()); p.setPollInterval(2); } if(pi.settings.useAuth) p.setUserPass(pi.settings.user, pi.settings.pass); } // stream d->conn = new AdvancedConnector; if(d->acc.opt_ssl) { QStringList certDirs; certDirs += g.pathHome + "/certs"; certDirs += g.pathBase + "/certs"; d->certList = getRootCerts(certDirs); d->tls = new QCA::TLS; d->tls->setCertificateStore(d->certList); d->tlsHandler = new QCATLSHandler(d->tls); connect(d->tlsHandler, SIGNAL(tlsHandshaken()), SLOT(tls_handshaken())); } d->conn->setProxy(p); d->conn->setOptHostPort(host, port); d->conn->setOptSSL(d->acc.opt_ssl); d->stream = new ClientStream(d->conn, d->tlsHandler); d->stream->setOldOnly(true); d->stream->setAllowPlain(d->acc.opt_plain); if(d->acc.opt_keepAlive) d->stream->setNoopTime(55000); // prevent NAT timeouts every minute else d->stream->setNoopTime(0); connect(d->stream, SIGNAL(connected()), SLOT(cs_connected())); connect(d->stream, SIGNAL(securityLayerActivated(int)), SLOT(cs_securityLayerActivated())); connect(d->stream, SIGNAL(needAuthParams(bool, bool, bool)), SLOT(cs_needAuthParams(bool, bool, bool))); connect(d->stream, SIGNAL(authenticated()), SLOT(cs_authenticated())); connect(d->stream, SIGNAL(connectionClosed()), SLOT(cs_connectionClosed())); connect(d->stream, SIGNAL(delayedCloseFinished()), SLOT(cs_delayedCloseFinished())); connect(d->stream, SIGNAL(warning(int)), SLOT(cs_warning(int))); connect(d->stream, SIGNAL(error(int)), SLOT(cs_error(int))); Jid j = d->jid.withResource(d->acc.resource); d->client->connectToServer(d->stream, j); } // disconnect or stop reconnecting void PsiAccount::logout(bool fast, const Status &s) { if(!isActive()) return; // cancel reconnect doReconnect = false; if(loggedIn()) { // send logout status d->client->setPresence(s); } isDisconnecting = true; if(!fast) simulateRosterOffline(); v_isActive = false; stateChanged(); QTimer::singleShot(0, this, SLOT(disconnect())); } // skz note: I had to split logout() because server seem to need some time to store status // before stream is closed (weird, I know) void PsiAccount::disconnect() { // disconnect d->client->close(); cleanupStream(); disconnected(); } bool PsiAccount::loggedIn() const { return (v_isActive && presenceSent); } void PsiAccount::tls_handshaken() { QCA::Cert cert = d->tls->peerCertificate(); int r = d->tls->certificateValidityResult(); if(r != QCA::TLS::Valid && !d->acc.opt_ignoreSSLWarnings) { QString str = resultToString(r); while(1) { int n = QMessageBox::warning(0, tr("%1: Server Authentication").arg(name()), tr("The %1 certificate failed the authenticity test.").arg(d->jid.host()) + '\n' + tr("Reason: %1").arg(str), tr("&Details..."), tr("Co&ntinue"), tr("&Cancel"), 0, 2); if(n == 0) { SSLCertDlg::showCert(cert, r); } else if(n == 1) { d->tlsHandler->continueAfterHandshake(); break; } else if(n == 2) { logout(); break; } } } else d->tlsHandler->continueAfterHandshake(); } void PsiAccount::cs_connected() { // get IP address ByteStream *bs = d->conn->stream(); if(bs->inherits("BSocket") || bs->inherits("XMPP::BSocket")) { //#if QT_VERSION >= 0x030300 d->localAddress = ((BSocket *)bs)->address(); /*#else int s = ((BSocket *)bs)->socket(); struct sockaddr addr; socklen_t size = sizeof(struct sockaddr); if(!getsockname(s, &addr, &size)) { Q_UINT32 ipv4addr; struct sockaddr_in *in = (struct sockaddr_in *)&addr; memcpy(&ipv4addr, &in->sin_addr.s_addr, 4); ipv4addr = ntohl(ipv4addr); d->localAddress = QHostAddress(ipv4addr); } #endif*/ } } void PsiAccount::cs_securityLayerActivated() { d->usingSSL = true; stateChanged(); } void PsiAccount::cs_needAuthParams(bool, bool pass, bool) { if(pass) d->stream->setPassword(d->acc.pass); d->stream->continueAfterParams(); } void PsiAccount::cs_authenticated() { d->conn->changePollInterval(10); // for http poll, slow down after login d->client->start(d->jid.host(), d->jid.user(), d->acc.pass, d->acc.resource); //printf("PsiAccount: [%s] authenticated\n", name().latin1()); // flag roster for delete UserListIt it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { if(u->inList()) u->setFlagForDelete(true); } // ask for roster d->client->rosterRequest(); } void PsiAccount::cs_connectionClosed() { cs_error(-1); } void PsiAccount::cs_delayedCloseFinished() { //printf("PsiAccount: [%s] connection closed\n", name().latin1()); } void PsiAccount::cs_warning(int) { d->stream->continueAfterWarning(); } void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn) { QString str; bool reconn = false; if(err == -1) { str = tr("Disconnected"); reconn = true; } else if(err == XMPP::ClientStream::ErrParse) { str = tr("XML Parsing Error"); reconn = true; } else if(err == XMPP::ClientStream::ErrProtocol) { str = tr("XMPP Protocol Error"); reconn = true; } else if(err == XMPP::ClientStream::ErrStream) { int x = stream->errorCondition(); QString s; reconn = true; if(x == XMPP::Stream::GenericStreamError) s = tr("Generic stream error"); else if(x == XMPP::ClientStream::Conflict) { s = tr("Conflict (remote login replacing this one)"); reconn = false; } else if(x == XMPP::ClientStream::ConnectionTimeout) s = tr("Timed out from inactivity"); else if(x == XMPP::ClientStream::InternalServerError) s = tr("Internal server error"); else if(x == XMPP::ClientStream::InvalidXml) s = tr("Invalid XML"); else if(x == XMPP::ClientStream::PolicyViolation) { s = tr("Policy violation"); reconn = false; } else if(x == XMPP::ClientStream::ResourceConstraint) { s = tr("Server out of resources"); reconn = false; } else if(x == XMPP::ClientStream::SystemShutdown) s = tr("Server is shutting down"); str = tr("XMPP Stream Error: %1").arg(s); } else if(err == XMPP::ClientStream::ErrConnection) { int x = conn->errorCode(); QString s; reconn = true; if(x == XMPP::AdvancedConnector::ErrConnectionRefused) s = tr("Unable to connect to server"); else if(x == XMPP::AdvancedConnector::ErrHostNotFound) s = tr("Host not found"); else if(x == XMPP::AdvancedConnector::ErrProxyConnect) s = tr("Error connecting to proxy"); else if(x == XMPP::AdvancedConnector::ErrProxyNeg) s = tr("Error during proxy negotiation"); else if(x == XMPP::AdvancedConnector::ErrProxyAuth) { s = tr("Proxy authentication failed"); reconn = false; } else if(x == XMPP::AdvancedConnector::ErrStream) s = tr("Socket/stream error"); str = tr("Connection Error: %1").arg(s); } else if(err == XMPP::ClientStream::ErrNeg) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::HostGone) s = tr("Host no longer hosted"); else if(x == XMPP::ClientStream::HostUnknown) s = tr("Host unknown"); else if(x == XMPP::ClientStream::RemoteConnectionFailed) { s = tr("A required remote connection failed"); reconn = true; } else if(x == XMPP::ClientStream::SeeOtherHost) s = tr("See other host: %1").arg(stream->errorText()); else if(x == XMPP::ClientStream::UnsupportedVersion) s = tr("Server does not support proper XMPP version"); str = tr("Stream Negotiation Error: %1").arg(s); } else if(err == XMPP::ClientStream::ErrTLS) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::TLSStart) s = tr("Server rejected STARTTLS"); else if(x == XMPP::ClientStream::TLSFail) { int t = tlsHandler->tlsError(); if(t == QCA::TLS::ErrHandshake) s = tr("TLS handshake error"); else s = tr("Broken security layer (TLS)"); } str = s; } else if(err == XMPP::ClientStream::ErrAuth) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::GenericAuthError) s = tr("Unable to login"); else if(x == XMPP::ClientStream::NoMech) s = tr("No appropriate mechanism available for given security settings"); else if(x == XMPP::ClientStream::BadProto) s = tr("Bad server response"); else if(x == XMPP::ClientStream::BadServ) s = tr("Server failed mutual authentication"); else if(x == XMPP::ClientStream::EncryptionRequired) s = tr("Encryption required for chosen SASL mechanism"); else if(x == XMPP::ClientStream::InvalidAuthzid) s = tr("Invalid account information"); else if(x == XMPP::ClientStream::InvalidMech) s = tr("Invalid SASL mechanism"); else if(x == XMPP::ClientStream::InvalidRealm) s = tr("Invalid realm"); else if(x == XMPP::ClientStream::MechTooWeak) s = tr("SASL mechanism too weak for this account"); else if(x == XMPP::ClientStream::NotAuthorized) s = tr("Not authorized"); else if(x == XMPP::ClientStream::TemporaryAuthFailure) s = tr("Temporary auth failure"); str = tr("Authentication error: %1").arg(s); } else if(err == XMPP::ClientStream::ErrSecurityLayer) str = tr("Broken security layer (SASL)"); else str = tr("None"); //printf("str[%s], reconn=%d\n", str.latin1(), reconn); *_str = str; *_reconn = reconn; } void PsiAccount::cs_error(int err) { QString str; bool reconn; getErrorInfo(err, d->conn, d->stream, d->tlsHandler, &str, &reconn); d->client->close(); cleanupStream(); //printf("Error: [%s]\n", str.latin1()); isDisconnecting = true; if ( loggedIn() ) { // FIXME: is this condition okay? simulateRosterOffline(); } presenceSent = false; // this stops the idle detector?? (FIXME) // Auto-Reconnect? if(d->acc.opt_reconn && reconn) { // reconnect in 5 seconds doReconnect = true; stateChanged(); QTimer::singleShot(5000, this, SLOT(reconnect())); return; } v_isActive = false; stateChanged(); disconnected(); QMessageBox::critical(0, tr("%1: Server Error").arg(name()), tr("There was an error communicating with the Jabber server.\nDetails: %1").arg(str)); } void PsiAccount::client_rosterRequestFinished(bool success, int, const QString &) { if(success) { //printf("PsiAccount: [%s] roster retrieved ok. %d entries.\n", name().latin1(), d->client->roster().count()); // delete flagged items UserListIt it(d->userList); for(UserListItem *u; (u = it.current());) { if(u->flagForDelete()) { //QMessageBox::information(0, "blah", QString("deleting: [%1]").arg(u->jid().full())); d->eventQueue->clear(u->jid()); updateReadNext(u->jid()); d->cp->removeEntry(u->jid()); d->userList.removeRef(u); } else ++it; } } else { //printf("PsiAccount: [%s] error retrieving roster: [%d, %s]\n", name().latin1(), code, str.latin1()); } rosterDone = true; setStatusDirect(d->loginStatus); } void PsiAccount::resolveContactName() { JT_VCard *j = (JT_VCard *)sender(); if ( j->success() ) { QString nick = j->vcard().nickName(); if ( !nick.isEmpty() ) { actionRename( j->jid(), nick ); } } } void PsiAccount::client_rosterItemAdded(const RosterItem &r) { if ( r.isPush() && r.name().isEmpty() && option.autoResolveNicksOnAdd ) { // automatically resolve nickname from vCard, if newly added item doesn't have any VCardFactory::getVCard(r.jid(), d->client->rootTask(), this, SLOT(resolveContactName())); } } void PsiAccount::client_rosterItemUpdated(const RosterItem &r) { // see if the item added is already in our local list UserListItem *u = d->userList.find(r.jid()); if(u) { u->setFlagForDelete(false); u->setRosterItem(r); } else { // we don't have it at all, so add it u = new UserListItem; u->setRosterItem(r); #ifdef AVATARS u->setAvatarFactory(d->avatarFactory); #endif d->userList.append(u); } u->setInList(true); d->cp->updateEntry(*u); } void PsiAccount::client_rosterItemRemoved(const RosterItem &r) { UserListItem *u = d->userList.find(r.jid()); if(!u) return; simulateContactOffline(u); // if the item has messages queued, then move them to 'not in list' if(d->eventQueue->count(r.jid()) > 0) { u->setInList(false); d->cp->updateEntry(*u); } // else remove them for good! else { d->cp->removeEntry(u->jid()); d->userList.removeRef(u); } } void PsiAccount::tryVerify(UserListItem *u, UserResource *ur) { if(d->psi->pgp()) verifyStatus(u->jid().withResource(ur->name()), ur->status()); } void PsiAccount::client_resourceAvailable(const Jid &j, const Resource &r) { enum PopupType { PopupOnline = 0, PopupStatusChange = 1 }; PopupType popupType = PopupOnline; if ( j.user().isEmpty() ) new BlockTransportPopup(d->blockTransportPopupList, j); bool doSound = false; bool doPopup = false; QPtrList list = findRelavent(j); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { bool doAnim = false; bool local = false; if(u->isSelf() && r.name() == d->client->resource()) local = true; // add/update the resource QString oldStatus, oldKey; UserResource *rp; UserResourceList::Iterator rit = u->userResourceList().find(j.resource()); bool found = (rit == u->userResourceList().end()) ? false: true; if(!found) { popupType = PopupOnline; UserResource ur(r); //ur.setSecurityEnabled(true); if(local) ur.setClient(PROG_NAME,PROG_VERSION,getOSName()); rp = &(*u->userResourceList().append(ur)); if(notifyOnlineOk && !local) { doAnim = true; if (!u->isHidden()) { doSound = true; doPopup = true; } } if(!local && option.autoVersion && !status().isInvisible()) { // do a client-version request (too easy!) JT_ClientVersion *jcv = new JT_ClientVersion(d->client->rootTask()); connect(jcv, SIGNAL(finished()), SLOT(slotClientVersionFinished())); jcv->get(j); jcv->go(true); } } else { if ( !doPopup ) popupType = PopupStatusChange; oldStatus = (*rit).status().status(); oldKey = (*rit).status().keyID(); rp = &(*rit); (*rit).setResource(r); if (!local && !u->isHidden()) doPopup = true; } rp->setPGPVerifyStatus(-1); if(!rp->status().xsigned().isEmpty()) tryVerify(u, rp); u->setPresenceError(""); cpUpdate(*u, r.name(), true); if(doAnim && option.rosterAnim) d->cp->animateNick(u->jid()); } if(doSound) playSound(option.onevent[eOnline]); #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus)) #endif if(notifyOnlineOk && doPopup && d->doPopups && !d->blockTransportPopupList->find(j, popupType == PopupOnline) && makeSTATUS(status()) != STATUS_DND ) { QString name; UserListItem *u = findFirstRelavent(j); PsiPopup::PopupType pt = PsiPopup::AlertNone; if ( popupType == PopupOnline ) pt = PsiPopup::AlertOnline; else if ( popupType == PopupStatusChange ) pt = PsiPopup::AlertStatusChange; if ((popupType == PopupOnline && option.ppOnline) || (popupType == PopupStatusChange && option.ppStatus)) { PsiPopup *popup = new PsiPopup(pt, this); popup->setData(j, r, u); } #if defined(Q_WS_MAC) && defined(HAVE_GROWL) PsiGrowlNotifier::instance()->popup(this, pt, j, r, u); #endif } else if ( !notifyOnlineOk ) d->userCounter++; } void PsiAccount::client_resourceUnavailable(const Jid &j, const Resource &r) { bool doSound = false; bool doPopup = false; if ( j.user().isEmpty() ) new BlockTransportPopup(d->blockTransportPopupList, j); QPtrList list = findRelavent(j); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { bool local = false; if(u->isSelf() && r.name() == d->client->resource()) local = true; // remove resource UserResourceList::Iterator rit = u->userResourceList().find(j.resource()); bool found = (rit == u->userResourceList().end()) ? false: true; if(found) { u->setLastUnavailableStatus(r.status()); u->userResourceList().remove(rit); if(!u->isAvailable()) u->setLastAvailable(QDateTime::currentDateTime()); if(!u->isAvailable() || u->isSelf()) { // don't sound for our own resource if(!isDisconnecting && !local && !u->isHidden()) { doSound = true; doPopup = true; } } } u->setPresenceError(""); cpUpdate(*u, r.name(), true); } if(doSound) playSound(option.onevent[eOffline]); #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) if (option.ppOffline) #endif if(doPopup && d->doPopups && !d->blockTransportPopupList->find(j) && makeSTATUS(status()) != STATUS_DND ) { QString name; UserListItem *u = findFirstRelavent(j); if (option.ppOffline) { PsiPopup *popup = new PsiPopup(PsiPopup::AlertOffline, this); popup->setData(j, r, u); } #if defined(Q_WS_MAC) && defined(HAVE_GROWL) PsiGrowlNotifier::instance()->popup(this, PsiPopup::AlertOffline, j, r, u); #endif } } void PsiAccount::client_presenceError(const Jid &j, int, const QString &str) { QPtrList list = findRelavent(j); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { simulateContactOffline(u); u->setPresenceError(str); cpUpdate(*u, j.resource(), false); } } void PsiAccount::client_messageReceived(const Message &m) { //check if it's a server message without a from, and set the from appropriately Message _m(m); if (_m.from().isEmpty()) { _m.setFrom(jid().domain()); } // if the sender is already in the queue, then queue this message also QPtrListIterator it(d->messageQueue); for(Message *mi; (mi = it.current()); ++it) { if(mi->from().compare(_m.from())) { Message *m = new Message(_m); d->messageQueue.append(m); return; } } // encrypted message? if(!pgpKey().isEmpty() && !_m.xencrypted().isEmpty()) { Message *m = new Message(_m); d->messageQueue.append(m); processMessageQueue(); return; } processIncomingMessage(_m); } void PsiAccount::processIncomingMessage(const Message &_m) { // skip empty messages if(_m.body().isEmpty() && _m.urlList().isEmpty() && _m.invite().isEmpty() && !_m.containsEvents()) return; // skip headlines? if(_m.type() == "headline" && option.ignoreHeadline) return; //skip if on ignore list? KEVIN, must not forget if(_m.type() == "groupchat") { GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost())); if(w) w->message(_m); return; } // only toggle if not an invite or body is not empty if(_m.invite().isEmpty() && !_m.body().isEmpty()) toggleSecurity(_m.from(), _m.wasEncrypted()); UserListItem *u = findFirstRelavent(_m.from()); if(u) { if(_m.type() == "chat") u->setLastMessageType(1); else u->setLastMessageType(0); } Message m = _m; // smartchat: try to match up the incoming event to an existing chat // (prior to 0.9, m.from() always contained a resource) Jid j; ChatDlg *c; QPtrList ul = findRelavent(m.from()); // ignore events from non-roster JIDs? if (ul.isEmpty() && option.ignoreNonRoster) { if (option.excludeGroupChatsFromIgnore) { GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost())); if(!w) { return; } } else { return; } } if(ul.isEmpty()) j = m.from().userHost(); else j = ul.first()->jid(); c = (ChatDlg *)dialogFind("ChatDlg", j); if(!c) c = (ChatDlg *)dialogFind("ChatDlg", m.from().full()); if(m.type() == "error") m.setBody(m.error().text + "\n------\n" + m.body()); // change the type? if(m.type() != "headline" && m.invite().isEmpty()) { if(option.incomingAs == 1) m.setType(""); else if(option.incomingAs == 2) m.setType("chat"); else if(option.incomingAs == 3) { if(c != NULL && !c->isHidden()) m.setType("chat"); else m.setType(""); } } // urls or subject on a chat message? convert back to regular message //if(m.type() == "chat" && (!m.urlList().isEmpty() || !m.subject().isEmpty())) // m.setType(""); MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(false); handleEvent(me); } void PsiAccount::client_subscription(const Jid &j, const QString &str) { // if they remove our subscription, then we lost presence if(str == "unsubscribed") { UserListItem *u = d->userList.find(j); if(u) simulateContactOffline(u); } AuthEvent *ae = new AuthEvent(j, str, this); ae->setTimeStamp(QDateTime::currentDateTime()); handleEvent(ae); } void PsiAccount::client_debugText(const QString &) { //printf("%s", str.latin1()); //fflush(stdout); } void PsiAccount::client_incomingJidLink() { JidLink *jl = d->client->jidLinkManager()->takeIncoming(); if(!jl) return; //if(link_test) { // printf("[%s] -- Incoming JidLink request from [%s]\n", name().latin1(), jl->peer().full().latin1()); // JLTestDlg *w = new JLTestDlg(jl->peer(), jl, this); // w->show(); //} //else jl->deleteLater(); } void PsiAccount::client_incomingFileTransfer() { FileTransfer *ft = d->client->fileTransferManager()->takeIncoming(); if(!ft) return; /*printf("psiaccount: incoming file transfer:\n"); printf(" From: [%s]\n", ft->peer().full().latin1()); printf(" Name: [%s]\n", ft->fileName().latin1()); printf(" Size: %d bytes\n", ft->fileSize());*/ FileEvent *fe = new FileEvent(ft->peer().full(), ft, this); fe->setTimeStamp(QDateTime::currentDateTime()); handleEvent(fe); } void PsiAccount::reconnect() { if(doReconnect) { //printf("PsiAccount: [%s] reconnecting...\n", name().latin1()); v_isActive = false; doReconnect = false; login(); } } Status PsiAccount::status() const { return d->loginStatus; } void PsiAccount::setStatus(const Status &_s) { // Block all transports' contacts' status change popups from popping { Roster::ConstIterator rit = d->acc.roster.begin(); for ( ; rit != d->acc.roster.end(); ++rit) { const RosterItem &i = *rit; if ( i.jid().user().isEmpty() /*&& i.jid().resource() == "registered"*/ ) // it is very likely then, that it's transport new BlockTransportPopup(d->blockTransportPopupList, i.jid()); } } // cancel auto-status and reconnect usingAutoStatus = false; doReconnect = false; Status s = _s; s.setPriority(d->acc.priority); d->loginStatus = s; if(s.isAvailable()) { // if client is not active then attempt to login if(!isActive()) { Jid j = d->jid; if(d->acc.resource.isEmpty() || !j.isValid()) { QMessageBox::information(0, CAP(tr("Error")), tr("Unable to login. Ensure your account information is filled out.")); modify(); return; } if(!d->acc.opt_pass) { bool ok = false; QString text = QInputDialog::getText( tr("Need Password"), tr("Please enter the password for %1:").arg(j.full()), QLineEdit::Password, QString::null, &ok, 0); if(ok && !text.isEmpty()) d->acc.pass = text; else return; } login(); } // change status else { if(rosterDone) setStatusDirect(s); if(s.isInvisible()) {//&&Pass invis to transports KEVIN //this is a nasty hack to let the transports know we're invisible, since they get an offline packet when we go invisible QPtrListIterator it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { if(u->isTransport()) { JT_Presence *j = new JT_Presence(d->client->rootTask()); j->pres(u->jid(), s); j->go(true); } } } } } else { if(isActive()) logout(false, s); } } void PsiAccount::setStatusDirect(const Status &_s) { Status s = _s; s.setPriority(d->acc.priority); //printf("setting status to [%s]\n", s.status().latin1()); // using pgp? if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty()) { d->loginStatus = s; // sign presence trySignPresence(); } else { /*if(d->psi->pgp() && !d->cur_pgpSecretKeyID.isEmpty()) s.setKeyID(d->cur_pgpSecretKeyID); else s.setKeyID("");*/ // send presence normally setStatusActual(s); } } void PsiAccount::setStatusActual(const Status &s) { d->loginStatus = s; d->client->setPresence(s); if(presenceSent) { stateChanged(); } else { presenceSent = true; stateChanged(); QTimer::singleShot(15000, this, SLOT(enableNotifyOnline())); const VCard *vcard = VCardFactory::vcard(d->jid); if ( option.autoVCardOnLogin || !vcard || vcard->isEmpty() || vcard->nickName().isEmpty() ) VCardFactory::getVCard(d->jid, d->client->rootTask(), this, SLOT(slotCheckVCard())); else { d->nickFromVCard = true; setNick( vcard->nickName() ); } } } void PsiAccount::secondsIdle(int x) { if(!loggedIn()) return; int lastIdle = d->lastIdle; Status lastStatus = d->lastStatus; Resource r = *(d->client->resourceList().find(d->client->resource())); Status ls = r.status(); //must avoid status change when invisible KEVIN if(ls.isInvisible()) return; if(ls.isAvailable()) { // no longer idle? if(lastIdle > x) { if(ls.isAway() && usingAutoStatus) { lastStatus = d->origStatus; setStatusDirect(lastStatus); usingAutoStatus = false; } } else if( !(ls.isAway() && !usingAutoStatus) ) { int minutes = x / 60; if(option.use_asOffline && option.asOffline > 0 && minutes >= option.asOffline) { lastStatus = Status("", "", d->acc.priority, false); usingAutoStatus = false; logout(); } else if(option.use_asXa && option.asXa > 0 && minutes >= option.asXa) { if(ls.show() != "xa" && lastStatus.show() != "xa") { lastStatus = Status("xa", option.asMessage, d->acc.priority); if(!usingAutoStatus) d->origStatus = d->loginStatus; setStatusDirect(lastStatus); usingAutoStatus = true; } } else if(option.use_asAway && option.asAway > 0 && minutes >= option.asAway) { if(ls.show() != "away" && lastStatus.show() != "away") { lastStatus = Status("away", option.asMessage, d->acc.priority); if(!usingAutoStatus) d->origStatus = d->loginStatus; setStatusDirect(lastStatus); usingAutoStatus = true; } } } } d->lastIdle = x; d->lastStatus = lastStatus; } void PsiAccount::playSound(const QString &str) { if(str.isEmpty()) return; int s = STATUS_OFFLINE; if(loggedIn()) s = makeSTATUS(status()); if(s == STATUS_DND) return; // no away sounds? if(option.noAwaySound && (s == STATUS_AWAY || s == STATUS_XA)) return; d->psi->playSound(str); } QWidget *PsiAccount::dialogFind(const char *className, const Jid &j) { QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current()); ++it) { // does the classname and jid match? if(i->className == className && i->jid.compare(j)) { return i->widget; } } return 0; } void PsiAccount::dialogRegister(QWidget *w, const Jid &j) { item_dialog2 *i = new item_dialog2; i->widget = w; i->className = w->className(); i->jid = j; d->dialogList.append(i); } void PsiAccount::dialogUnregister(QWidget *w) { QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current()); ++it) { if(i->widget == w) { d->dialogList.removeRef(i); return; } } } void PsiAccount::deleteAllDialogs() { QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current());) delete i->widget; d->dialogList.clear(); } bool PsiAccount::checkConnected(QWidget *par) { if(!loggedIn()) { QMessageBox::information(par, CAP(tr("Error")), tr("You must be connected to the server in order to do this.")); return false; } return true; } void PsiAccount::modify() { AccountModifyDlg *w = (AccountModifyDlg *)dialogFind("AccountModifyDlg"); if(w) bringToFront(w); else { w = new AccountModifyDlg(this, 0); w->show(); } } void PsiAccount::changeVCard() { actionInfo(d->jid); } void PsiAccount::changePW() { if(!checkConnected()) return; ChangePasswordDlg *w = (ChangePasswordDlg *)dialogFind("ChangePasswordDlg"); if(w) bringToFront(w); else { w = new ChangePasswordDlg(this); w->show(); } } void PsiAccount::showXmlConsole() { bringToFront(d->xmlConsole); } void PsiAccount::openAddUserDlg() { if(!checkConnected()) return; AddUserDlg *w = (AddUserDlg *)dialogFind("AddUserDlg"); if(w) bringToFront(w); else { QStringList gl, services, names; UserListIt it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { if(u->isTransport()) { services += u->jid().full(); names += jidnick(u->jid().full(), u->name()); } const QStringList &groups = u->groups(); if(groups.isEmpty()) continue; for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git) { if(qstringlistmatch(gl, *git) == -1) gl.append(*git); } } w = new AddUserDlg(services, names, gl, this); connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool))); w->show(); } } void PsiAccount::doDisco() { actionDisco(d->jid.host(), ""); } void PsiAccount::actionDisco(const Jid &j, const QString &node) { DiscoDlg *w = new DiscoDlg(this, j, node); connect(w, SIGNAL(featureActivated(QString, Jid, QString)), SLOT(featureActivated(QString, Jid, QString))); w->show(); } void PsiAccount::featureActivated(QString feature, Jid jid, QString node) { Features f(feature); if ( f.canRegister() ) actionRegister(jid); else if ( f.canSearch() ) actionSearch(jid); else if ( f.canGroupchat() ) actionJoin(jid); else if ( f.canDisco() ) actionDisco(jid, node); else if ( f.isGateway() ) ; // TODO else if ( f.haveVCard() ) actionInfo(jid); else if ( f.id() == Features::FID_Add ) { QStringList sl; dj_add(jid, QString::null, sl, true); } } void PsiAccount::actionJoin(const Jid &j) { GCJoinDlg *w = new GCJoinDlg(psi(), this); w->le_host->setText ( j.host() ); w->le_room->setText ( j.user() ); w->show(); } void PsiAccount::stateChanged() { if(loggedIn()) d->cp->setState(makeSTATUS(status())); else { if(isActive()) { d->cp->setState(-1); if(d->usingSSL) d->cp->setUsingSSL(true); else d->cp->setUsingSSL(false); } else { d->cp->setState(STATUS_OFFLINE); d->cp->setUsingSSL(false); } } updatedActivity(); } void PsiAccount::simulateContactOffline(UserListItem *u) { UserResourceList rl = u->userResourceList(); u->setPresenceError(""); if(!rl.isEmpty()) { for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) { const UserResource &r = *rit; Jid j = u->jid(); if(u->jid().resource().isEmpty()) j.setResource(r.name()); client_resourceUnavailable(j, r); } } else cpUpdate(*u); } void PsiAccount::simulateRosterOffline() { UserListIt it(d->userList); for(UserListItem *u; (u = it.current()); ++it) simulateContactOffline(u); // self { UserListItem *u = &d->self; UserResourceList rl = u->userResourceList(); for(UserResourceList::ConstIterator rit = rl.begin(); rit != rl.end(); ++rit) { Jid j = u->jid(); if(u->jid().resource().isEmpty()) j.setResource((*rit).name()); u->setPresenceError(""); client_resourceUnavailable(j, *rit); } } d->gcbank.clear(); } void PsiAccount::enableNotifyOnline() { if ( d->userCounter > 1 ) { QTimer::singleShot(15000, this, SLOT(enableNotifyOnline())); d->userCounter = 0; } else notifyOnlineOk = true; } void PsiAccount::slotClientVersionFinished() { JT_ClientVersion *j = (JT_ClientVersion *)sender(); if(j->success()) { QPtrList list = findRelavent(j->jid()); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { UserResourceList::Iterator rit = u->userResourceList().find(j->jid().resource()); bool found = (rit == u->userResourceList().end()) ? false: true; if(!found) continue; (*rit).setClient(j->name(),j->version(),j->os()); cpUpdate(*u); /*if(u->isSelf()) { if(d->cp->self()) d->cp->updateSelf(*u); } else d->cp->updateEntry(*u);*/ } } } QPtrList PsiAccount::findRelavent(const Jid &j) const { QPtrList list; // self? if(j.compare(d->self.jid(), false)) list.append(&d->self); else { QPtrListIterator it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { if(!u->jid().compare(j, false)) continue; if(!u->jid().resource().isEmpty()) { if(u->jid().resource() != j.resource()) continue; } list.append(u); } } return list; } UserListItem *PsiAccount::findFirstRelavent(const Jid &j) const { QPtrList list = findRelavent(j); if(list.isEmpty()) return 0; else return list.first(); } UserListItem *PsiAccount::find(const Jid &j) const { UserListItem *u; if(j.compare(d->self.jid())) u = &d->self; else u = d->userList.find(j); return u; } void PsiAccount::cpUpdate(const UserListItem &u, const QString &rname, bool fromPresence) { PsiEvent *e = d->eventQueue->peek(u.jid()); d->cp->updateEntry(u); if(e) { d->cp->setAlert(u.jid(), is->event2icon(e)); } else d->cp->clearAlert(u.jid()); updateContact(u); Jid j = u.jid(); if(!rname.isEmpty()) j.setResource(rname); updateContact(j); updateContact(j, fromPresence); d->psi->updateContactGlobal(this, j); } QLabel *PsiAccount::accountLabel(QWidget *par, bool simpleMode) { AccountLabel *al = new AccountLabel(this, par, simpleMode); return al; } EventDlg *PsiAccount::ensureEventDlg(const Jid &j) { EventDlg *w = (EventDlg *)dialogFind("EventDlg", j); if(!w) { w = new EventDlg(j, this, true); connect(w, SIGNAL(aReadNext(const Jid &)), SLOT(processReadNext(const Jid &))); connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&))); connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &))); connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &))); connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate())); connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &))); } return w; } ChatDlg *PsiAccount::ensureChatDlg(const Jid &j) { ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j); if(!c) { // create the chatbox c = new ChatDlg(j, this); if (option.useTabs) { //get a tab from the mainwin d->psi->getTabs()->addChat(c); } connect(c, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &))); connect(c, SIGNAL(messagesRead(const Jid &)), SLOT(chatMessagesRead(const Jid &))); connect(c, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &))); connect(c, SIGNAL(aHistory(const Jid &)), SLOT(actionHistory(const Jid &))); connect(c, SIGNAL(aFile(const Jid &)), SLOT(actionSendFile(const Jid &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), c, SLOT(optionsUpdate())); connect(this, SIGNAL(updateContact(const Jid &, bool)), c, SLOT(updateContact(const Jid &, bool))); } else { // on X11, do a special reparent to open on the right desktop #ifdef Q_WS_X11 /* KIS added an exception for tabs here. We do *not* want chats flying * randomlyi, it pulls them out of tabsets. So instead, we move the * tabset instead. It's just as filthy, unfortunately, but it's the * only way */ //TODO: This doesn't work as expected atm, it doesn't seem to reparent the tabset QWidget *window=c; if ( option.useTabs ) window = d->psi->getManagingTabs(c); if(window && window->isHidden()) { const QPixmap *pp = c->icon(); QPixmap p; if(pp) p = *pp; reparent_good(window, 0, false); if(!p.isNull()) c->setIcon(p); } #endif } return c; } void PsiAccount::changeStatus(int x) { if(x == STATUS_OFFLINE && !option.askOffline) { setStatus(Status("","Logged out",0,false)); } else { if(x == STATUS_ONLINE && !option.askOnline) { setStatus(Status()); } else if(x == STATUS_INVISIBLE){ Status s("","",0,true); s.setIsInvisible(true); setStatus(s); } else { StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, "")); connect(w, SIGNAL(set(const Status &)), SLOT(setStatus(const Status &))); w->show(); } } } void PsiAccount::actionTest(const Jid &j) { UserListItem *u = find(j); if(!u) return; Jid j2 = j; if(j.resource().isEmpty()) { if(u->isAvailable()) j2.setResource((*u->userResourceList().priority()).name()); } //S5BConnection *c = new S5BConnection(d->client->s5bManager()); //c->connectToJid(j2, d->client->s5bManager()->genUniqueSID(j2)); JLTestDlg *w = new JLTestDlg(j2, this); w->show(); } void PsiAccount::actionSendFile(const Jid &j) { QStringList l; actionSendFiles(j, l); } void PsiAccount::actionSendFiles(const Jid &j, const QStringList& l) { Jid j2 = j; if(j.resource().isEmpty()) { UserListItem *u = find(j); if(u && u->isAvailable()) j2.setResource((*u->userResourceList().priority()).name()); } // Create a dialog for each file in the list. Once the xfer dialog itself // supports multiple files, only the 'else' branch needs to stay. if (!l.isEmpty()) { for (QStringList::ConstIterator f = l.begin(); f != l.end(); ++f ) { QStringList fl(*f); FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, fl); w->show(); } } else { FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, l); w->show(); } } void PsiAccount::actionDefault(const Jid &j) { UserListItem *u = find(j); if(!u) return; if(d->eventQueue->count(u->jid()) > 0) openNextEvent(*u); else { if(option.defaultAction == 0) actionSendMessage(u->jid()); else actionOpenChat(u->jid()); } } void PsiAccount::actionRecvEvent(const Jid &j) { UserListItem *u = find(j); if(!u) return; openNextEvent(*u); } void PsiAccount::actionSendMessage(const Jid &j) { EventDlg *w = d->psi->createEventDlg(j.full(), this); w->show(); } void PsiAccount::actionSendMessage(const JidList &j) { QString str; bool first = true; for(QValueList::ConstIterator it = j.begin(); it != j.end(); ++it) { if(!first) str += ", "; first = false; str += (*it).full(); } EventDlg *w = d->psi->createEventDlg(str, this); w->show(); } void PsiAccount::actionSendUrl(const Jid &j) { EventDlg *w = d->psi->createEventDlg(j.full(), this); w->setUrlOnShow(); w->show(); } void PsiAccount::actionRemove(const Jid &j) { #ifdef AVATARS avatarFactory()->removeManualAvatar(j); #endif dj_remove(j); } void PsiAccount::actionRename(const Jid &j, const QString &name) { dj_rename(j, name); } void PsiAccount::actionGroupRename(const QString &oldname, const QString &newname) { UserListIt it(d->userList); QPtrList nu; for(UserListItem *u; (u = it.current()); ++it) { if(u->inGroup(oldname)) { u->removeGroup(oldname); u->addGroup(newname); cpUpdate(*u); if(u->inList()) nu.append(u); } } if(!nu.isEmpty()) { JT_Roster *r = new JT_Roster(d->client->rootTask()); QPtrListIterator it(nu); for(UserListItem *u; (u = it.current()); ++it) r->set(u->jid(), u->name(), u->groups()); r->go(true); } } void PsiAccount::actionHistory(const Jid &j) { HistoryDlg *w = (HistoryDlg *)dialogFind("HistoryDlg", j); if(w) bringToFront(w); else { w = new HistoryDlg(j, this); connect(w, SIGNAL(openEvent(PsiEvent *)), SLOT(actionHistoryBox(PsiEvent *))); w->show(); } } void PsiAccount::actionHistoryBox(PsiEvent *e) { EventDlg *w = new EventDlg(e->from(), this, false); connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat(const Jid&))); connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &))); connect(w, SIGNAL(aAuth(const Jid &)), SLOT(dj_addAuth(const Jid &))); connect(w, SIGNAL(aDeny(const Jid &)), SLOT(dj_deny(const Jid &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate())); connect(this, SIGNAL(updateContact(const Jid &)), w, SLOT(updateContact(const Jid &))); w->updateEvent(e); w->show(); } void PsiAccount::actionOpenChat(const Jid &j) { UserListItem *u = find(j); if(!u) return; // if 'j' is bare, we might want to switch to a specific resource QString res; if(j.resource().isEmpty()) { // first, are there any queued chats? /*PsiEvent *e = d->eventQueue->peekFirstChat(j, false); if(e) { res = e->from().resource(); // if we have a bare chat, change to 'res' ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j); if(c) c->setJid(j.withResource(res)); } // else, is there a priority chat window available? else*/ if(u->isAvailable()) { QString pr = (*u->userResourceList().priority()).name(); if(!pr.isEmpty() && dialogFind("ChatDlg", j.withResource(pr))) res = pr; } else { QStringList list = hiddenChats(j); if(!list.isEmpty()) res = list.first(); } } if(!res.isEmpty()) openChat(j.withResource(res)); else openChat(j); } void PsiAccount::actionOpenChatSpecific(const Jid &j) { openChat(j); } void PsiAccount::actionAgentSetStatus(const Jid &j, Status &s) { if ( j.user().isEmpty() ) // add all transport popups to block list new BlockTransportPopup(d->blockTransportPopupList, j); JT_Presence *p = new JT_Presence(d->client->rootTask()); p->pres(j, s); p->go(true); } void PsiAccount::actionInfo(const Jid &_j) { bool useCache = true; Jid j; if(findGCContact(_j)) { useCache = false; j = _j; } else { j = _j.userHost(); } InfoDlg *w = (InfoDlg *)dialogFind("InfoDlg", j); if(w) { w->updateStatus(); bringToFront(w); } else { const VCard *vcard = VCardFactory::vcard(j); VCard tmp; if ( vcard ) tmp = *vcard; w = new InfoDlg(j.compare(d->jid) ? InfoDlg::Self : InfoDlg::Contact, j, tmp, this, 0, 0, useCache); w->show(); // automatically retrieve info if it doesn't exist if(!vcard && loggedIn()) w->doRefresh(); } } void PsiAccount::actionAuth(const Jid &j) { dj_auth(j); } void PsiAccount::actionAuthRequest(const Jid &j) { dj_authReq(j); } void PsiAccount::actionAuthRemove(const Jid &j) { dj_deny(j); } void PsiAccount::actionAdd(const Jid &j) { dj_addAuth(j); } void PsiAccount::actionGroupAdd(const Jid &j, const QString &g) { UserListItem *u = d->userList.find(j); if(!u) return; if(!u->addGroup(g)) return; cpUpdate(*u); JT_Roster *r = new JT_Roster(d->client->rootTask()); r->set(u->jid(), u->name(), u->groups()); r->go(true); } void PsiAccount::actionGroupRemove(const Jid &j, const QString &g) { UserListItem *u = d->userList.find(j); if(!u) return; if(!u->removeGroup(g)) return; cpUpdate(*u); JT_Roster *r = new JT_Roster(d->client->rootTask()); r->set(u->jid(), u->name(), u->groups()); r->go(true); } void PsiAccount::actionRegister(const Jid &j) { if(!checkConnected()) return; RegistrationDlg *w = (RegistrationDlg *)dialogFind("RegistrationDlg", j); if(w) bringToFront(w); else { w = new RegistrationDlg(j, this); w->show(); } } void PsiAccount::actionSearch(const Jid &j) { if(!checkConnected()) return; SearchDlg *w = (SearchDlg *)dialogFind("SearchDlg", j); if(w) bringToFront(w); else { w = new SearchDlg(j, this); connect(w, SIGNAL(add(const Jid &, const QString &, const QStringList &, bool)), SLOT(dj_add(const Jid &, const QString &, const QStringList &, bool))); connect(w, SIGNAL(aInfo(const Jid &)), SLOT(actionInfo(const Jid &))); w->show(); } } void PsiAccount::actionInvite(const Jid &j, const QString &gc) { Message m; m.setTo(j); m.setInvite(gc); m.setBody(tr("You have been invited to %1").arg(gc)); m.setTimeStamp(QDateTime::currentDateTime()); dj_sendMessage(m); } void PsiAccount::actionAssignKey(const Jid &j) { if(ensureKey(j)) { UserListItem *u = findFirstRelavent(j); if(u) cpUpdate(*u); } } void PsiAccount::actionUnassignKey(const Jid &j) { UserListItem *u = findFirstRelavent(j); if(u) { u->setPublicKeyID(""); cpUpdate(*u); } } void PsiAccount::dj_sendMessage(const Message &m, bool log) { UserListItem *u = findFirstRelavent(m.to()); Message nm = m; if(option.incomingAs == 3) { if(u) { switch(u->lastMessageType()) { case 0: nm.setType(""); break; case 1: nm.setType("chat"); break; } } } d->client->sendMessage(nm); // only toggle if not an invite or body is not empty if(m.invite().isEmpty() && !m.body().isEmpty()) toggleSecurity(m.to(), m.wasEncrypted()); // don't log groupchat, private messages, or encrypted messages if(d->acc.opt_log && log) { if(m.type() != "groupchat" && m.xencrypted().isEmpty() && !findGCContact(m.to())) { MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(true); me->setTimeStamp(QDateTime::currentDateTime()); logEvent(m.to(), me); delete me; } } // don't sound when sending groupchat messages or message events if(m.type() != "groupchat" && !m.body().isEmpty()) playSound(option.onevent[eSend]); // auto close an open messagebox (if non-chat) if(m.type() != "chat" && !m.body().isEmpty()) { UserListItem *u = findFirstRelavent(m.to()); if(u) { EventDlg *e = (EventDlg *)dialogFind("EventDlg", u->jid()); if(e) e->closeAfterReply(); } } } void PsiAccount::dj_composeMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread) { EventDlg *w = d->psi->createEventDlg(jid.full(), this); if(!body.isEmpty()) w->setText(qstrquote(body)); if(!subject.isEmpty() && subject.left(3) != "Re:") w->setSubject("Re: " + subject); else if (subject.left(3) == "Re:") w->setSubject(subject); if(!thread.isEmpty()) w->setThread(thread); w->show(); } void PsiAccount::dj_composeMessage(const Jid &j, const QString &body) { dj_composeMessage(j, body, QString::null, QString::null); } void PsiAccount::dj_addAuth(const Jid &j) { QString name; QStringList groups; UserListItem *u = d->userList.find(j); if(u) { name = u->name(); groups = u->groups(); } dj_add(j, name, groups, true); dj_auth(j); } void PsiAccount::dj_add(const Jid &j, const QString &name, const QStringList &groups, bool authReq) { JT_Roster *r = new JT_Roster(d->client->rootTask()); r->set(j, name, groups); r->go(true); if(authReq) dj_authReq(j); } void PsiAccount::dj_authReq(const Jid &j) { d->client->sendSubscription(j, "subscribe"); } void PsiAccount::dj_auth(const Jid &j) { d->client->sendSubscription(j, "subscribed"); } void PsiAccount::dj_deny(const Jid &j) { d->client->sendSubscription(j, "unsubscribed"); } void PsiAccount::dj_rename(const Jid &j, const QString &name) { UserListItem *u = d->userList.find(j); if(!u) return; QString str; if(name == u->jid().full()) str = ""; else str = name; // strange workaround to avoid a null string ?? QString uname; if(u->name().isEmpty()) uname = ""; else uname = u->name(); if(uname == str) return; u->setName(str); cpUpdate(*u); if(u->inList()) { JT_Roster *r = new JT_Roster(d->client->rootTask()); r->set(u->jid(), u->name(), u->groups()); r->go(true); } } void PsiAccount::dj_remove(const Jid &j) { UserListItem *u = d->userList.find(j); if(!u) return; // remove all events from the queue d->eventQueue->clear(j); updateReadNext(j); // TODO: delete the item immediately (to simulate local change) if(!u->inList()) { //simulateContactOffline(u); d->userList.removeRef(u); } else { JT_Roster *r = new JT_Roster(d->client->rootTask()); r->remove(j); r->go(true); // if it looks like a transport, unregister (but not if it is the server!!) if(u->isTransport() && !Jid(d->client->host()).compare(u->jid())) { JT_UnRegister *ju = new JT_UnRegister(d->client->rootTask()); ju->unreg(j); ju->go(true); } } } // handle an incoming event void PsiAccount::handleEvent(PsiEvent *e) { if ( e ) setEnabled(); bool doPopup = false; bool putToQueue = true; PsiPopup::PopupType popupType = PsiPopup::AlertNone; // find someone to accept the event Jid j; QPtrList ul = findRelavent(e->from()); if(ul.isEmpty()) { // if groupchat, then we want the full JID if(findGCContact(e->from())) { j = e->from(); } else { Jid bare = e->from().userHost(); Jid reg = bare.withResource("registered"); // see if we have a "registered" variant of the jid if(findFirstRelavent(reg)) { j = reg; e->setFrom(reg); // HACK!! } // retain full jid if sent to "registered" else if(e->from().resource() == "registered") j = e->from(); // otherwise don't use the resource for new entries else j = bare; } } else j = ul.first()->jid(); e->setJid(j); if(d->acc.opt_log) { if(e->type() == PsiEvent::Message || e->type() == PsiEvent::Auth) { // don't log private messages if(!findGCContact(e->from()) && !(e->type() == PsiEvent::Message && ((MessageEvent *)e)->message().body().isEmpty())) logEvent(e->from(), e); } } if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); // Pass message events to chat window if (m.containsEvents() && m.body().isEmpty()) { if (option.messageEvents) { ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from()); if(!c) c = (ChatDlg *)dialogFind("ChatDlg", e->jid()); if (c) c->incomingMessage(m); } return; } // pass chat messages directly to a chat window if possible (and deal with sound) if(m.type() == "chat") { ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", e->from()); if(!c) c = (ChatDlg *)dialogFind("ChatDlg", e->jid()); if(c) c->setJid(e->from()); if ( !c || !c->isActiveWindow() || option.alertOpenChats ) { doPopup = true; popupType = PsiPopup::AlertChat; } //if the chat exists, and is either open in a tab, //or in a window if( c && ( d->psi->isChatTabbed(c) || !c->isHidden() ) ) { c->incomingMessage(m); playSound(option.onevent[eChat2]); if(option.alertOpenChats && !d->psi->isChatActiveWindow(c)) { // to alert the chat also, we put it in the queue me->setSentToChatWindow(true); } else putToQueue = false; } else { bool firstChat = !d->eventQueue->hasChats(e->from()); playSound(option.onevent[firstChat ? eChat1: eChat2]); } } // /chat else if (m.type() == "headline") { playSound(option.onevent[eHeadline]); doPopup = true; popupType = PsiPopup::AlertHeadline; } // /headline else if (m.type() == "") { playSound(option.onevent[eMessage]); if (m.type() == "") { doPopup = true; popupType = PsiPopup::AlertMessage; } } // /"" else playSound(option.onevent[eSystem]); if(m.type() == "error") { // FIXME: handle message errors //msg.text = QString(tr("[Error Message]
%1").arg(plain2rich(msg.text))); } } else if(e->type() == PsiEvent::File) { playSound(option.onevent[eIncomingFT]); doPopup = true; popupType = PsiPopup::AlertFile; } else { playSound(option.onevent[eSystem]); AuthEvent *ae = (AuthEvent *)e; if(ae->authType() == "subscribe") { if(option.autoAuth) { // Check if we want to request auth as well UserListItem *u = d->userList.find(ae->from()); if (!u || (u->subscription().type() != Subscription::Both && u->subscription().type() != Subscription::To)) { dj_addAuth(ae->from()); } else { dj_auth(ae->from()); } putToQueue = false; } } else if(ae->authType() == "subscribed") { if(!option.notifyAuth) putToQueue = false; } else if(ae->authType() == "unsubscribe") { putToQueue = false; } } #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) if ((popupType == PsiPopup::AlertChat && option.ppChat) || (popupType == PsiPopup::AlertMessage && option.ppMessage) || (popupType == PsiPopup::AlertHeadline && option.ppHeadline) || (popupType == PsiPopup::AlertFile && option.ppFile)) #endif if ( doPopup && d->doPopups && makeSTATUS(status()) != STATUS_DND ) { Resource r; UserListItem *u = findFirstRelavent(j); if ( u ) r = *(u->priority()); 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) { PsiPopup *popup = new PsiPopup(popupType, this); popup->setData(j, r, u, e); } #if defined(Q_WS_MAC) && defined(HAVE_GROWL) PsiGrowlNotifier::instance()->popup(this, popupType, j, r, u, e); #endif } if ( putToQueue ) queueEvent(e); else delete e; } // put an event into the event queue, and update the related alerts void PsiAccount::queueEvent(PsiEvent *e) { // do we have roster item for this? UserListItem *u = find(e->jid()); if(!u) { // create item u = new UserListItem; u->setJid(e->jid()); u->setInList(false); #ifdef AVATARS u->setAvatarFactory(d->avatarFactory); #endif // is it a private groupchat? Jid j = u->jid(); GCContact *c = findGCContact(j); if(c) { u->setName(j.resource()); u->setPrivate(true); // make a resource so the contact appears online UserResource ur; ur.setName(j.resource()); ur.setStatus(c->status); u->userResourceList().append(ur); } // treat it like a push [pushinfo] //VCard info; //if(readUserInfo(item->jid, &info) && !info.field[vNickname].isEmpty()) // item->nick = info.field[vNickname]; //else { // if(localStatus != STATUS_OFFLINE) // serv->getVCard(item->jid); //} d->userList.append(u); } //printf("queuing message from [%s] for [%s].\n", e->from().full().latin1(), e->jid().full().latin1()); d->eventQueue->enqueue(e); updateReadNext(e->jid()); if(option.raise) d->psi->raiseMainwin(); // udpate the roster cpUpdate(*u); bool noPopup = false; if(d->loginStatus.isAvailable()) { QString show = d->loginStatus.show(); if(show == "dnd") noPopup = true; else if((show == "away" || show == "xa") && option.noAwayPopup) noPopup = true; } if (!noPopup) { bool doPopup = false; // Check to see if we need to popup if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); if (m.type() == "chat") doPopup = option.popupChats; else if (m.type() == "headline") doPopup = option.popupHeadlines; else doPopup = option.popupMsgs; } else if (e->type() == PsiEvent::File) { doPopup = option.popupFiles; } else doPopup = option.popupMsgs; // Popup if (doPopup) { UserListItem *u = find(e->jid()); if (u && (!option.noUnlistedPopup || u->inList())) openNextEvent(*u); } } } // take the next event from the queue and display it void PsiAccount::openNextEvent(const UserListItem &u) { PsiEvent *e = d->eventQueue->peek(u.jid()); if(!e) return; psi()->processEvent(e); } void PsiAccount::openNextEvent() { PsiEvent *e = d->eventQueue->peekNext(); if(!e) return; if(e->type() == PsiEvent::PGP) { psi()->processEvent(e); return; } UserListItem *u = find(e->jid()); if(!u) return; openNextEvent(*u); } void PsiAccount::updateReadNext(const Jid &j) { // update eventdlg's read-next EventDlg *w = (EventDlg *)dialogFind("EventDlg", j); if(w) { Icon *nextAnim = 0; int nextAmount = d->eventQueue->count(j); if(nextAmount > 0) nextAnim = is->event2icon(d->eventQueue->peek(j)); w->updateReadNext(nextAnim, nextAmount); } queueChanged(); } void PsiAccount::processReadNext(const Jid &j) { UserListItem *u = find(j); if(u) processReadNext(*u); } void PsiAccount::processReadNext(const UserListItem &u) { EventDlg *w = (EventDlg *)dialogFind("EventDlg", u.jid()); if(!w) { // this should NEVER happen return; } // peek the event PsiEvent *e = d->eventQueue->peek(u.jid()); if(!e) return; bool isChat = false; if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); if(m.type() == "chat") isChat = true; } // if it's a chat message, just open the chat window. there is no need to do // further processing. the chat window will remove it from the queue, update // the cvlist, etc etc. if(isChat) { openChat(e->from()); return; } // remove from queue e = d->eventQueue->dequeue(u.jid()); // update the eventdlg w->updateEvent(e); delete e; // update the contact cpUpdate(u); updateReadNext(u.jid()); } void PsiAccount::processChats(const Jid &j) { //printf("processing chats for [%s]\n", j.full().latin1()); ChatDlg *c = (ChatDlg *)dialogFind("ChatDlg", j); if(!c) return; // extract the chats QPtrList chatList; d->eventQueue->extractChats(&chatList, j); chatList.setAutoDelete(true); if(!chatList.isEmpty()) { // dump the chats into the chat window, and remove the related cvlist alerts QPtrListIterator it(chatList); for(PsiEvent *e; (e = it.current()); ++it) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); // process the message if(!me->sentToChatWindow()) c->incomingMessage(m); } QPtrList ul = findRelavent(j); if(!ul.isEmpty()) { UserListItem *u = ul.first(); cpUpdate(*u); updateReadNext(u->jid()); } } } void PsiAccount::openChat(const Jid &j) { ChatDlg *c = ensureChatDlg(j); processChats(j); if ( option.useTabs ) { if ( !d->psi->isChatTabbed(c) ) { //get a tab from the psicon d->psi->getTabs()->addChat(c); } TabDlg* tabSet = d->psi->getManagingTabs(c); tabSet->selectTab(c); } bringToFront(c); } void PsiAccount::chatMessagesRead(const Jid &j) { if(option.alertOpenChats) processChats(j); } void PsiAccount::logEvent(const Jid &j, PsiEvent *e) { EDBHandle *h = new EDBHandle(d->psi->edb()); connect(h, SIGNAL(finished()), SLOT(edb_finished())); h->append(j, e); } void PsiAccount::edb_finished() { EDBHandle *h = (EDBHandle *)sender(); delete h; } void PsiAccount::openGroupChat(const Jid &j) { QString str = j.userHost(); bool found = false; for(QStringList::ConstIterator it = d->groupchats.begin(); it != d->groupchats.end(); ++it) { if((*it) == str) { found = true; break; } } if(!found) d->groupchats += str; GCMainDlg *w = new GCMainDlg(this, j); connect(w, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate())); w->show(); } bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick) { return d->client->groupChatJoin(host, room, nick); } void PsiAccount::groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &s) { d->client->groupChatChangeNick(host, room, nick, s); } void PsiAccount::groupChatSetStatus(const QString &host, const QString &room, const Status &s) { d->client->groupChatSetStatus(host, room, s); } void PsiAccount::groupChatLeave(const QString &host, const QString &room) { d->groupchats.remove(room + '@' + host); d->client->groupChatLeave(host, room); } GCContact *PsiAccount::findGCContact(const Jid &j) { QPtrListIterator it(d->gcbank); for(GCContact *c; (c = it.current()); ++it) { if(c->jid.compare(j)) return c; } return 0; } QStringList PsiAccount::groupchats() const { return d->groupchats; } void PsiAccount::client_groupChatJoined(const Jid &j) { d->client->groupChatSetStatus(j.host(), j.user(), d->loginStatus); GCMainDlg *m = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost())); if(m) { m->joined(); return; } GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j); if(!w) return; w->joined(); openGroupChat(j); } void PsiAccount::client_groupChatLeft(const Jid &j) { // remove all associated groupchat contacts from the bank QPtrListIterator it(d->gcbank); for(GCContact *c; (c = it.current());) { // contact from this room? if(!c->jid.compare(j, false)) { ++it; continue; } UserListItem *u = find(c->jid); if(!u) { ++it; continue; } simulateContactOffline(u); d->gcbank.removeRef(c); } } void PsiAccount::client_groupChatPresence(const Jid &j, const Status &s) { GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost())); if(!w) return; GCContact *c = findGCContact(j); if(!c) { c = new GCContact; c->jid = j; c->status = s; d->gcbank.append(c); } w->presence(j.resource(), s); // pass through the core presence handling also Resource r; r.setName(j.resource()); r.setStatus(s); if(s.isAvailable()) client_resourceAvailable(j, r); else client_resourceUnavailable(j, j.resource()); } void PsiAccount::client_groupChatError(const Jid &j, int code, const QString &str) { GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(j.userHost())); if(w) { w->error(code, str); } else { GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j); if(w) { w->error(code, str); } } } QStringList PsiAccount::hiddenChats(const Jid &j) const { QStringList list; QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current()); ++it) { if(i->className == "ChatDlg" && i->jid.compare(j, false)) list += i->jid.resource(); } return list; } void PsiAccount::slotCheckVCard() { JT_VCard *j = (JT_VCard *)sender(); if(!j->success() && j->statusCode() == Task::ErrDisc) { setNick(d->jid.user()); return; } if(j->vcard().isEmpty()) { changeVCard(); return; } if(!j->vcard().nickName().isEmpty()) { d->nickFromVCard = true; setNick(j->vcard().nickName()); } else setNick(d->jid.user()); } void PsiAccount::setNick(const QString &nick) { d->self.setName(nick); cpUpdate(d->self); nickChanged(); } QString PsiAccount::nick() const { return d->self.name(); } void PsiAccount::pgpToggled(bool b) { QString oldkey = d->cur_pgpSecretKeyID; // gaining pgp? if(b) d->cur_pgpSecretKeyID = d->acc.pgpSecretKeyID; // losing it? else { d->cur_pgpSecretKeyID = ""; } if(oldkey != d->cur_pgpSecretKeyID) { pgpKeyChanged(); // resend status if online if(loggedIn()) setStatusDirect(d->loginStatus); } } void PsiAccount::pgpKeysUpdated() { OpenPGP::KeyList list = d->psi->pgp()->publicKeys(); // are there any sigs that need verifying? QPtrListIterator it(d->userList); for(UserListItem *u; (u = it.current()); ++it) { UserResourceList &rl = u->userResourceList(); for(UserResourceList::Iterator rit = rl.begin(); rit != rl.end(); ++rit) { UserResource &r = *rit; if(!r.status().xsigned().isEmpty() && r.pgpVerifyStatus() == OpenPGP::VerifyNoKey) { bool haveKey = false; QString key = r.publicKeyID(); for(OpenPGP::KeyList::ConstIterator kit = list.begin(); kit != list.end(); ++kit) { if((*kit).keyID() == key) { haveKey = true; break; } } if(haveKey) tryVerify(u, &r); } } } } void PsiAccount::verifyStatus(const Jid &j, const Status &s) { PGPTransaction *t = new PGPTransaction(d->psi->pgp()); t->setJid(j); connect(t, SIGNAL(finished(bool)), SLOT(pgp_verifyFinished(bool))); QCString cs = s.status().utf8(); QByteArray buf(cs.length()); memcpy(buf.data(), cs.data(), buf.size()); t->verify(buf, OpenPGP::addHeaderFooter(s.xsigned(), 1)); //printf("%s: verifying\n", j.full().latin1()); } void PsiAccount::trySignPresence() { OpenPGP::Request *r = new OpenPGP::Request(d->psi->pgp()); connect(r, SIGNAL(finished(bool)), SLOT(pgp_signFinished(bool))); connect(r, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase())); QCString cs = d->loginStatus.status().utf8(); QByteArray buf(cs.length()); memcpy(buf.data(), cs.data(), buf.size()); r->sign(buf, d->cur_pgpSecretKeyID); } void PsiAccount::pgp_needPassphrase() { OpenPGP::Request *r = (OpenPGP::Request *)sender(); d->ppreq = r; if(pgp_passphrases.contains(d->cur_pgpSecretKeyID)) r->submitPassphrase(pgp_passphrases[d->cur_pgpSecretKeyID]); else promptPassphrase(); } void PsiAccount::promptPassphrase() { if(d->ppdlg) d->ppdlg->unblock(); else { PassphraseDlg *w = new PassphraseDlg(0); connect(w, SIGNAL(submitPassphrase(const QString &)), SLOT(submitPassphrase(const QString &))); connect(w, SIGNAL(rejectPassphrase()), SLOT(rejectPassphrase())); w->setCaption(tr("%1: OpenPGP Passphrase").arg(name())); w->show(); d->ppdlg = w; } } void PsiAccount::submitPassphrase(const QString &pp) { d->ppreq->submitPassphrase(pp); } void PsiAccount::rejectPassphrase() { d->ppdlg = 0; // cancel request if(d->ppreq) d->ppreq->deleteLater(); logout(); } void PsiAccount::pgp_signFinished(bool ok) { OpenPGP::Request *r = (OpenPGP::Request *)sender(); bool badPassphrase = r->badPassphrase(); QString sig; if(ok) sig = r->signature(); if(d->ppdlg) { pgp_passphrases[d->cur_pgpSecretKeyID] = d->ppdlg->passphrase(); } r->deleteLater(); if(ok) { Status s = d->loginStatus; s.setXSigned(OpenPGP::stripHeaderFooter(sig)); setStatusActual(s); } else { if(badPassphrase) { pgp_passphrases.erase(d->cur_pgpSecretKeyID); QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("You entered a bad passphrase. Please try again.")); QTimer::singleShot(0, this, SLOT(trySignPresence())); return; } else { QMessageBox::information(d->ppdlg ? d->ppdlg : 0, CAP(tr("Error")), tr("There was an error during OpenPGP processing. Check your settings and try again.")); logout(); } } // remove the dialog if it is there if(d->ppdlg) { d->ppdlg->deleteLater(); d->ppdlg = 0; } } void PsiAccount::pgp_verifyFinished(bool b) { PGPTransaction *t = (PGPTransaction *)sender(); Jid j = t->jid(); //printf("%s: verify complete\n", j.full().latin1()); QPtrList list = findRelavent(j); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { UserResourceList::Iterator rit = u->userResourceList().find(j.resource()); bool found = (rit == u->userResourceList().end()) ? false: true; if(!found) continue; UserResource &ur = *rit; if(b) { //printf("vergood\n"); ur.setPublicKeyID(t->keyID()); ur.setPGPVerifyStatus(t->verifyResult()); ur.setSigTimestamp(t->timestamp()); // if the key doesn't match the assigned key, unassign it if(t->keyID() != u->publicKeyID()) u->setPublicKeyID(""); } else { //QMessageBox::information(0, "sig verify error", QString("error verifying [%1]").arg(u->jid().full())); ur.setPGPVerifyStatus(OpenPGP::VerifyError); } //printf("updating [%s]\n", u->jid().full().latin1()); cpUpdate(*u); } t->deleteLater(); } int PsiAccount::sendMessageEncrypted(const Message &_m) { if(!ensureKey(_m.to())) return -1; QString key = findFirstRelavent(_m.to())->publicKeyID(); PGPTransaction *pt = new PGPTransaction(d->psi->pgp()); Message m = _m; pt->setMessage(m); // keep a copy //QByteArray a = m.generateEncryptablePayload(d->client->doc()); QCString cs = m.body().utf8(); QByteArray a(cs.length()); memcpy(a.data(), cs.data(), a.size()); // encrypt QStringList rcpt; rcpt += key; connect(pt, SIGNAL(finished(bool)), SLOT(pgp_finished(bool))); pt->encrypt(a, rcpt); return pt->id(); } void PsiAccount::pgp_finished(bool b) { PGPTransaction *pt = (PGPTransaction *)sender(); int x = pt->id(); if(pt->type() == OpenPGP::Encrypt) { if(b) { Message m = pt->message(); // log the message here, before we encrypt it if(d->acc.opt_log) { MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(true); me->setTimeStamp(QDateTime::currentDateTime()); logEvent(m.to(), me); delete me; } Message mwrap; mwrap.setTo(m.to()); mwrap.setType(m.type()); QString enc = OpenPGP::stripHeaderFooter(pt->encrypted()); mwrap.setBody(tr("[ERROR: This message is encrypted, and you are unable to decrypt it.]")); mwrap.setXEncrypted(enc); mwrap.setWasEncrypted(true); // FIXME: Should be done cleaner, with an extra method in Iris if (m.containsEvent(OfflineEvent)) mwrap.addEvent(OfflineEvent); if (m.containsEvent(DeliveredEvent)) mwrap.addEvent(DeliveredEvent); if (m.containsEvent(DisplayedEvent)) mwrap.addEvent(DisplayedEvent); if (m.containsEvent(ComposingEvent)) mwrap.addEvent(ComposingEvent); if (m.containsEvent(CancelEvent)) mwrap.addEvent(CancelEvent); dj_sendMessage(mwrap); } encryptedMessageSent(x, b); } pt->deleteLater(); } void PsiAccount::pgp_decryptFinished(bool b) { PGPTransaction *pt = (PGPTransaction *)sender(); bool tryAgain = false; if(b) { Message m = pt->message(); //if(m.applyDecryptedPayload(pt->decrypted(), d->client->doc())) QByteArray buf = pt->decrypted(); QCString cs(buf.size()+1); memcpy(cs.data(), buf.data(), buf.size()); QString str = QString::fromUtf8(cs); m.setBody(str); m.setXEncrypted(""); m.setWasEncrypted(true); processIncomingMessage(m); //else // QMessageBox::information(0, CAP(tr("Error")), tr("A successful decryption operation resulted in an invalid message, so it has been ignored.")); } else { if(loggedIn()) { Message m; m.setTo(pt->message().from()); m.setType("error"); m.setBody(pt->message().body()); Stanza::Error err; err.condition = 500; err.text = "Unable to decrypt"; m.setError(err); d->client->sendMessage(m); } } pt->deleteLater(); if(tryAgain) { processEncryptedMessageNext(); } else { processEncryptedMessageDone(); } } void PsiAccount::processEncryptedMessage(const Message &m) { // decrypt PGPTransaction *t = new PGPTransaction(d->psi->pgp()); t->setMessage(m); // keep a copy connect(t, SIGNAL(needPassphrase()), SLOT(pgp_needPassphrase())); connect(t, SIGNAL(finished(bool)), SLOT(pgp_decryptFinished(bool))); QString str = OpenPGP::addHeaderFooter(m.xencrypted(), 0); t->decrypt(str); } void PsiAccount::processMessageQueue() { while(!d->messageQueue.isEmpty()) { Message *mp = d->messageQueue.getFirst(); // encrypted? if(d->psi->pgp() && !mp->xencrypted().isEmpty()) { processEncryptedMessageNext(); break; } processIncomingMessage(*mp); d->messageQueue.removeRef(mp); } } void PsiAccount::processEncryptedMessageNext() { // 'peek' and try to process it Message *mp = d->messageQueue.getFirst(); processEncryptedMessage(*mp); } void PsiAccount::processEncryptedMessageDone() { // 'pop' the message Message *mp = d->messageQueue.getFirst(); d->messageQueue.removeRef(mp); // do the rest of the queue processMessageQueue(); } void PsiAccount::optionsUpdate() { d->cp->updateEntry(d->self); } QString PsiAccount::resultToString(int result) { QString s; switch(result) { case QCA::TLS::NoCert: s = tr("The server did not present a certificate."); break; case QCA::TLS::Valid: s = tr("Certificate is valid."); break; case QCA::TLS::HostMismatch: s = tr("The hostname does not match the one the certificate was issued to."); break; case QCA::TLS::Rejected: s = tr("Root CA is marked to reject the specified purpose."); break; case QCA::TLS::Untrusted: s = tr("Certificate not trusted for the required purpose."); break; case QCA::TLS::SignatureFailed: s = tr("Invalid signature."); break; case QCA::TLS::InvalidCA: s = tr("Invalid CA certificate."); break; case QCA::TLS::InvalidPurpose: s = tr("Invalid certificate purpose."); break; case QCA::TLS::SelfSigned: s = tr("Certificate is self-signed."); break; case QCA::TLS::Revoked: s = tr("Certificate has been revoked."); break; case QCA::TLS::PathLengthExceeded: s = tr("Maximum certificate chain length exceeded."); break; case QCA::TLS::Expired: s = tr("Certificate has expired."); break; case QCA::TLS::Unknown: default: s = tr("General certificate validation error."); break; } return s; } void PsiAccount::invokeGCMessage(const Jid &j) { GCContact *c = findGCContact(j); if(!c) return; // create dummy item, open chat, then destroy item. HORRIBLE HACK! UserListItem *u = new UserListItem; u->setJid(j); u->setInList(false); u->setName(j.resource()); u->setPrivate(true); #ifdef AVATARS u->setAvatarFactory(d->avatarFactory); #endif // make a resource so the contact appears online UserResource ur; ur.setName(j.resource()); ur.setStatus(c->status); u->userResourceList().append(ur); d->userList.append(u); actionSendMessage(j); d->userList.remove(u); } void PsiAccount::invokeGCChat(const Jid &j) { GCContact *c = findGCContact(j); if(!c) return; // create dummy item, open chat, then destroy item. HORRIBLE HACK! UserListItem *u = new UserListItem; u->setJid(j); u->setInList(false); u->setName(j.resource()); u->setPrivate(true); #ifdef AVATARS u->setAvatarFactory(d->avatarFactory); #endif // make a resource so the contact appears online UserResource ur; ur.setName(j.resource()); ur.setStatus(c->status); u->userResourceList().append(ur); d->userList.append(u); actionOpenChat(j); d->userList.remove(u); } void PsiAccount::invokeGCInfo(const Jid &j) { actionInfo(j); } void PsiAccount::invokeGCFile(const Jid &j) { actionSendFile(j); } void PsiAccount::toggleSecurity(const Jid &j, bool b) { UserListItem *u = findFirstRelavent(j); if(!u) return; bool isBare = j.resource().isEmpty(); bool isPriority = false; // sick sick sick sick sick sick UserResource *r1, *r2; r1 = r2 = 0; UserResourceList::Iterator it1 = u->userResourceList().find(j.resource()); UserResourceList::Iterator it2 = u->userResourceList().priority(); r1 = (it1 != u->userResourceList().end() ? &(*it1) : 0); r2 = (it2 != u->userResourceList().end() ? &(*it2) : 0); if(r1 && (r1 == r2)) isPriority = true; bool needUpdate = false; bool sec = u->isSecure(j.resource()); if(sec != b) { u->setSecure(j.resource(), b); needUpdate = true; } if(isBare && r2) { sec = u->isSecure(r2->name()); if(sec != b) { u->setSecure(r2->name(), b); needUpdate = true; } } if(isPriority) { sec = u->isSecure(""); if(sec != b) { u->setSecure("", b); needUpdate = true; } } if(needUpdate) cpUpdate(*u); } bool PsiAccount::ensureKey(const Jid &j) { if(!d->psi->pgp()) return false; UserListItem *u = findFirstRelavent(j); if(!u) return false; // no key? if(u->publicKeyID().isEmpty()) { // does the user have any presence signed with a key? QString akey; const UserResourceList &rl = u->userResourceList(); for(UserResourceList::ConstIterator it = rl.begin(); it != rl.end(); ++it) { const UserResource &r = *it; if(!r.publicKeyID().isEmpty()) { akey = r.publicKeyID(); break; } } bool inList = false; OpenPGP::KeyList list = d->psi->pgp()->publicKeys(); for(OpenPGP::KeyList::ConstIterator lit = list.begin(); lit != list.end(); ++lit) { const OpenPGP::Key &k = *lit; if(k.keyID() == akey) { inList = true; break; } } if(akey.isEmpty() || !inList) { int n = QMessageBox::information(0, CAP(tr("No key")), tr( "

Psi was unable to locate the OpenPGP key to use for %1.
" "
" "This can happen if you do not have the key that the contact is advertising " "via signed presence, or if the contact is not advertising any key at all.

" ).arg(u->jid().full()), tr("&Choose key manually"), tr("Do ¬hing")); if(n != 0) return false; } PGPKeyDlg *w = new PGPKeyDlg(list, akey, 0); w->setCaption(tr("Public Key: %1").arg(j.full())); int r = w->exec(); QString key; if(r == QDialog::Accepted) key = w->keyID(); delete w; if(key.isEmpty()) return false; u->setPublicKeyID(key); cpUpdate(*u); } return true; } //---------------------------------------------------------------------------- // PGPKeyDlg //---------------------------------------------------------------------------- class KeyViewItem : public QListViewItem { public: KeyViewItem(const QString &_keyID, QListView *par) :QListViewItem(par) { keyID = _keyID; } QString keyID; }; class PGPKeyDlg::Private { public: Private() {} QString keyID; QString userID; }; PGPKeyDlg::PGPKeyDlg(const OpenPGP::KeyList &list, const QString &choose, QWidget *parent, const char *name) :PGPKeyUI(parent, name, true) { d = new Private; connect(lv_keys, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *))); connect(pb_ok, SIGNAL(clicked()), SLOT(do_accept())); connect(pb_cancel, SIGNAL(clicked()), SLOT(reject())); QListViewItem *isel = 0; for(OpenPGP::KeyList::ConstIterator it = list.begin(); it != list.end(); ++it) { const OpenPGP::Key &k = *it; KeyViewItem *i = new KeyViewItem(k.keyID(), lv_keys); //i->setPixmap(0, IconsetFactory::icon("psi/gpg-yes")); i->setText(0, k.keyID().right(8)); i->setText(1, k.userID()); if(!choose.isEmpty() && k.keyID() == choose) { lv_keys->setSelected(i, true); isel = i; } } if(lv_keys->childCount() > 0 && !isel) lv_keys->setSelected(lv_keys->firstChild(), true); else if(isel) lv_keys->ensureItemVisible(isel); } PGPKeyDlg::~PGPKeyDlg() { delete d; } QString PGPKeyDlg::keyID() const { return d->keyID; } QString PGPKeyDlg::userID() const { return d->userID; } void PGPKeyDlg::qlv_doubleClicked(QListViewItem *i) { lv_keys->setSelected(i, true); do_accept(); } void PGPKeyDlg::do_accept() { KeyViewItem *i = (KeyViewItem *)lv_keys->selectedItem(); if(!i) { QMessageBox::information(this, tr("Error"), tr("Please select a key.")); return; } d->keyID = i->keyID; d->userID = i->text(1); accept(); } #include "psiaccount.moc"