/* * 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 #include #include"psicon.h" #include"../adhoc.h" #include"../rc.h" #include"../adhoc_fileserver.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; // Ad-hoc commands AHCServerManager* ahcManager; // Avatars AvatarFactory* avatarFactory; // pgp related PassphraseDlg *ppdlg; QGuardedPtr ppreq; QPtrList gcbank; QStringList groupchats; QValueVector AutoReplayJIDsList; 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; d->avatarFactory = new AvatarFactory(this); d->blockTransportPopupList = new BlockTransportPopupList(); d->doPopups = true; v_isActive = false; isDisconnecting = false; notifyOnlineOk = false; doReconnect = false; usingAutoStatus = false; usingEmergencyStatus = false; // k 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; d->self.setAvatarFactory(d->avatarFactory); 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->setTimeZone(getTZString(), getTZOffset()); if (option.spoofClient) { d->client->setOSName(option.spoofClientOS); d->client->setClientName(option.spoofClientName); d->client->setClientVersion(option.spoofClientVersion); } else { d->client->setOSName(getOSName()); 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->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,&d->acc.tog_statusmsg); // 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; d->psi->setToggles(d->acc.tog_offline, d->acc.tog_away, d->acc.tog_agents, d->acc.tog_hidden,d->acc.tog_self, d->acc.tog_statusmsg); // 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); u->setAvatarFactory(d->avatarFactory); 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) { // we will clean the whitespace in the status // use res instead of r Resource res = r; Status cleanStatus = r.status(); cleanStatus.setStatus(cleanStatus.status().stripWhiteSpace()); enum PopupType { PopupOnline = 0, PopupStatusChange = 1 }; PopupType popupType = PopupOnline; if ( j.user().isEmpty() ) new BlockTransportPopup(d->blockTransportPopupList, j); bool doSound = false; bool doPopup = false; bool local = false; QPtrList list = findRelavent(j); QPtrListIterator it(list); for(UserListItem *u; (u = it.current()); ++it) { bool doAnim = false; local = false; if(u->isSelf() && res.name() == d->client->resource()) local = true; // add/update the resource UserResource *rp; UserResourceList::Iterator rit = u->userResourceList().find(j.resource()); bool found = (rit == u->userResourceList().end()) ? false: true; if(!found) { popupType = PopupOnline; UserResource ur(res); //ur.setSecurityEnabled(true); if(local) { if (option.spoofClient) ur.setClient(option.spoofClientName, option.spoofClientVersion, option.spoofClientOS); else 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; rp = &(*rit); (*rit).setResource(res); if (!local && !u->isHidden()) doPopup = true; } rp->setPGPVerifyStatus(-1); if(!rp->status().xsigned().isEmpty()) tryVerify(u, rp); u->setPresenceError(""); cpUpdate(*u, res.name(), true); if(doAnim && option.rosterAnim) d->cp->animateNick(u->jid()); } if(doSound) playSound(option.onevent[eOnline]); if (option.ppOnline && notifyOnlineOk) doPopup = true; // custom contact availability popup notification if (option.contactPopupAvail.contains(j.bare()) && !local && notifyOnlineOk) { // custom value is set if (option.contactPopupAvail[j.bare()] == 1) doPopup = true; else if (option.contactPopupAvail[j.bare()] == 0) doPopup = false; } #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) if ((popupType == PopupOnline && doPopup) || (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, res, 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) { // we will clean the whitespace in the status // use res instead of r Resource res = r; Status cleanStatus = r.status(); cleanStatus.setStatus(cleanStatus.status().stripWhiteSpace()); res.setStatus(cleanStatus); bool doSound = false; bool doPopup = false; bool local = 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 doAnim = false; local = false; if(u->isSelf() && res.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(res.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; if (notifyOnlineOk) doAnim = true; } } } if(doAnim && option.rosterAnim) d->cp->animateNick(u->jid()); u->setPresenceError(""); cpUpdate(*u, res.name(), true); } if(doSound) playSound(option.onevent[eOffline]); // custom contact availability popup notification if (option.contactPopupAvail.contains(j.bare()) && !local && !isDisconnecting) { // custom value is set if (option.contactPopupAvail[j.bare()] == 1) doPopup = true; else if (option.contactPopupAvail[j.bare()] == 0) doPopup = false; } #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, res, 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()); } // check to see if message was forwarded from another resource if (jid().compare(_m.from(),false)) { AddressList oFrom = _m.addressFind(OriginalFrom); AddressList oTo = _m.addressFind(OriginalTo); if ((oFrom.count() > 0) && (oTo.count() > 0)) { // might want to store current values in MessageEvent object // replace out the from and to addresses with the original addresses _m.setFrom(oFrom[0].jid()); _m.setTo(oTo[0].jid()); } } // 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(""); // +k if ( (option.useEAAutoResponder) && (!m.body().isEmpty()) && (usingEmergencyStatus) ) { QValueVector::const_iterator it = qFind( d->AutoReplayJIDsList.begin(), d->AutoReplayJIDsList.end(), m.from().full() ); if (it == d->AutoReplayJIDsList.end()) { Message arm(m.from()); arm.setType(m.type()); arm.setBody( option.eaArMessage ); arm.setTimeStamp(QDateTime::currentDateTime()); arm.setEvil(true); dj_sendMessage(arm, false); d->AutoReplayJIDsList.push_back(m.from().full()); } } // -k 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, bool withPriority) { // 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; usingEmergencyStatus = false; // k doReconnect = false; Status s = _s; if (!withPriority) 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, withPriority); 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, bool withPriority) { Status s = _s; if (!withPriority) s.setPriority(d->acc.priority); // remove all starting and ending whitespace in the status message s.setStatus(s.status().stripWhiteSpace()); //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->loginStatus.setNickname(nick()); d->client->setPresence(d->loginStatus); 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() ); } } } // k void PsiAccount::setEmergencyStatus() { if(!usingEmergencyStatus) { d->origStatus = d->loginStatus; setStatus(Status("away", expandStatusTemplate(option.eaMessage), d->acc.priority)); usingEmergencyStatus = true; QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current()); ++it) { if ( i->className == "ChatDlg" ) { ChatDlg *dlg = (ChatDlg*)(i->widget); if ( dlg ) dlg->toggleEmergencyAwayStatus(true); } } } else if(usingEmergencyStatus) { setStatus(d->origStatus); usingEmergencyStatus = false; QPtrListIterator it(d->dialogList); for(item_dialog2 *i; (i = it.current()); ++it) { if ( i->className == "ChatDlg" ) { ChatDlg *dlg = (ChatDlg*)(i->widget); if ( dlg ) dlg->toggleEmergencyAwayStatus(false); d->AutoReplayJIDsList.clear(); } } } } // k bool PsiAccount::getEmergencyAwayStatusStatus() { return usingEmergencyStatus; } QString PsiAccount::expandStatusTemplate(const QString &statusTemplate) { QString res; QRegExp rx("<([^:]*):originalStatus:([^>]*)>"); rx.setCaseSensitive(false); const QString orig = d->origStatus.status(); int start = 0; int end = 0; while ( (start=rx.search(statusTemplate, end)) != -1 ) { res += statusTemplate.mid(end, start-end); if ( !orig.isEmpty() ) { res += rx.cap(1); res += orig; res += rx.cap(2); } end = start + rx.matchedLength(); } res += statusTemplate.mid(end); return res; } 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() || (ls.isDnd() && option.use_asWhileDnd) || usingAutoStatus ) { int minutes = x / 60; if(option.use_asOffline && option.asOffline > 0 && minutes >= option.asOffline) { lastStatus = Status("", "", d->acc.priority, false); usingAutoStatus = false; usingEmergencyStatus = false; // k logout(); } else if(option.use_asXa && option.asXa > 0 && minutes >= option.asXa) { if(ls.show() != "xa" && lastStatus.show() != "xa") { if(!usingAutoStatus) d->origStatus = d->loginStatus; lastStatus = Status("xa", expandStatusTemplate((option.use_asMessage)? option.asMessage : status().status()), d->acc.priority); setStatusDirect(lastStatus); usingAutoStatus = true; } } else if(option.use_asAway && option.asAway > 0 && minutes >= option.asAway) { if(ls.show() != "away" && lastStatus.show() != "away") { if(!usingAutoStatus) d->origStatus = d->loginStatus; lastStatus = Status("away", expandStatusTemplate((option.use_asMessage)? option.asMessage : status().status()), d->acc.priority); setStatusDirect(lastStatus); usingAutoStatus = true; } } } } d->lastIdle = x; d->lastStatus = lastStatus; } void PsiAccount::playSound(const QString &str, bool force) { if(str.isEmpty()) return; // removed with checkboxes-cousnd-options patch // 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); // find the position in opt.onevent to see if the sound is active int i = 0; bool found = false; do { if(str == option.onevent[i]) // if found, exit found = true; else // if not found, go on searching i++; } while (i<10 && !found); // afther this bucle i contains the position of the sound // in opt.onevent (thus, in opt.oneventIsActive) // (see applyOptions and restoreOptions methods in opt_sound.cpp) if(option.oneventIsActive[i]) { int s = STATUS_OFFLINE; if(loggedIn()) s = makeSTATUS(status()); // no away sounds? if(!force && option.noAwaySound && (s == STATUS_AWAY || s == STATUS_XA || s == STATUS_DND)) return; d->psi->playSound(str, force); } } 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() { Jid j; QStringList sl; openAddUserDlg(j, "", sl); } void PsiAccount::openAddUserDlg(const Jid& jid, const QString& name, const QStringList& groups) { if(!checkConnected()) return; AddUserDlg *w = (AddUserDlg *)dialogFind("AddUserDlg"); if(w) { bringToFront(w); w->setJid(jid.full()); w->setName(name); w->setGroups(groups); } 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); w->setJid(jid.full()); w->setName(name); w->setGroups(groups); 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::logStatusEvent(const UserListItem &u, bool force) { /* Retrieve status from dark structures. */ UserResourceList::ConstIterator rit = u.userResourceList().priority(); QString StatusString = (*rit).status().status(); int status = makeSTATUS((*rit).status()); /* Create line */ if (!force && (StatusString == "") && !option.clLogEmptyStatus) return; QString Content = status2txt(status); if (StatusString != "") Content += " [" + StatusString + "]"; /* Create Message */ Message m; m.setTimeStamp(QDateTime::currentDateTime()); m.setType(""); /* or error or headline */ m.setFrom(u.jid()); m.setBody(Content); /* Create event from message */ MessageEvent *Event = new MessageEvent(m, 0); Event->setFrom(u.jid()); /* Add event to history */ EDBFlatFile::File h(u.jid()); QDateTime Time = QDateTime::currentDateTime(); Event->setTimeStamp(Time); logEvent(u.jid(), Event); } 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); // more properties - status messages logging if (option.contactLogStatus.contains(j.bare()) && option.contactLogStatus[j.bare()] == 0) return; else if (option.contactLogStatus.contains(j.bare()) && option.contactLogStatus[j.bare()] == 2) logStatusEvent(u, true); else if ((fromPresence && option.clLogStatus) || (option.contactLogStatus.contains(j.bare()) && option.contactLogStatus[j.bare()] == 1)) logStatusEvent(u, false); } 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(aCompose(const Jid &, const QString &, const QString &, const QString &, ComposeType, const Jid &, const Jid &, const QDateTime &, const UrlList &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &, ComposeType, const Jid &, const Jid &, const QDateTime &, const UrlList &))); 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(c, SIGNAL(aAdHoc(const Jid &, const QString &)), SLOT(actionExecuteCommandSpecific(const Jid &, const QString &))); 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::sendFiles(const Jid& j, const QStringList& l, bool direct) { 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, direct); w->show(); } } else { FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, l, direct); w->show(); } } void PsiAccount::actionSendFile(const Jid &j) { QStringList l; sendFiles(j,l); } void PsiAccount::actionSendFiles(const Jid &j, const QStringList& l) { sendFiles(j, l); } void PsiAccount::actionExecuteCommand(const Jid& j, const QString &node) { printf("Non-specific\n"); Jid j2 = j; if(j.resource().isEmpty()) { UserListItem *u = find(j); if(u && u->isAvailable()) j2.setResource((*u->userResourceList().priority()).name()); } actionExecuteCommandSpecific(j2, node); } void PsiAccount::actionExecuteCommandSpecific(const Jid& j, const QString &node) { if (node.isEmpty()) { AHCommandDlg *w = new AHCommandDlg(this,j); w->show(); } else { AHCommandDlg::executeCommand(this,j,node); } } 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); if(option.useMessageSignature) w->setText(expandSignatureTemplate(option.messageSignature, option.autoMessageSignatureDelimiter), option.cursorAtTopInEventDlg); 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); if(option.useMessageSignature) w->setText(expandSignatureTemplate(option.messageSignature, option.autoMessageSignatureDelimiter), option.cursorAtTopInEventDlg); w->show(); } void PsiAccount::actionSendUrl(const Jid &j) { EventDlg *w = d->psi->createEventDlg(j.full(), this); w->setUrlOnShow(); if(option.useMessageSignature) w->setText(expandSignatureTemplate(option.messageSignature, option.autoMessageSignatureDelimiter), option.cursorAtTopInEventDlg); w->show(); } void PsiAccount::actionRemove(const Jid &j) { avatarFactory()->removeManualAvatar(j); 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(aCompose(const Jid &, const QString &, const QString &, const QString &, ComposeType, const Jid &, const Jid &,const QDateTime &, const UrlList &)), SLOT(dj_composeMessage(const Jid &, const QString &, const QString &, const QString &, ComposeType, const Jid &, const Jid &,const QDateTime &, const UrlList &))); 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(); } } } // machekku: smart forward and reply /*! Expands quote header template \a headerTemplate for a message with subject \a subject, sender's JID \a jidSender, user's JID \a jidMe, sent on \a originalMessageTime. */ QString PsiAccount::expandQuoteHeadlineTemplate(const Template &headerTemplate, const QString &subject, const Jid &jidSender, const Jid &jidMe, const QDateTime &originalMessageTime) { QString header; if ( !headerTemplate.isEmpty() ) { QPtrList ul = findRelavent(jidSender); UserListItem *uSender = 0; if(!ul.isEmpty()) uSender = ul.first(); ul = findRelavent(jidMe); UserListItem *uMe = 0; if(!ul.isEmpty()) uMe = ul.first(); Template::ExpandData values; values["Message.DateTime"] = originalMessageTime.toString(LocalDate); values["Message.subject"] = subject; values["Sender.jid.full"] = jidSender.full(); values["Sender.jid.bare"] = jidSender.bare(); values["Sender.nick"] = (uSender)? uSender->name() : ""; values["Sender.nick-jid"] = jidnick(jidSender.bare(), (uSender)? uSender->name() : ""); values["Me.jid.full"] = jidMe.resource().isEmpty()? jidMe.full() + "/" + d->client->resource() : jidMe.full(); values["Me.jid.bare"] = jidMe.bare(); values["Me.nick"] = (uMe)? uMe->name() : "";; values["Me.nick-jid"] = jidnick(jidMe.bare(), (uMe)? uMe->name() : ""); header = headerTemplate.expand(values); } return header; } /*! Expands signature template \a signatureTemplate. If \a ensureDelimiter is true and signature template does not start with signature delimiter ("-- \n"), it will be added at the beginning of expanded string. If \a signatureForNewMessage is true, this method will make sure that there will be two empty lines before the real signature starts. */ QString PsiAccount::expandSignatureTemplate(const Template &signatureTemplate, bool ensureDelimiter, bool signatureForNewMessage /* = TRUE */) { QString signature; if ( !signatureTemplate.isEmpty() ) { Template::ExpandData values; values["fortune"] = (option.fortunes.count())? option.fortunes[rand()%option.fortunes.count()] : ""; signature = signatureTemplate.expand(values); // add delimiter if(ensureDelimiter && !signature.startsWith("-- \n")) signature = "-- \n" + signature; if(signatureForNewMessage && !signature.startsWith("\n\n")) { // new message - two empty lines before sig if (signature[0]=='\n') signature = "\n" + signature; else signature = "\n\n" + signature; } } return signature; } /*! Composes a message to \a jid with body \a body, subject \a subject and thread \a thread. If compose type \ct is ctNew (new message is being created), rest of the arguments is ignored. In other case \a originalJid_Sender is sender of the original message and \a originalJid_Sender is receiver of the original message, which was sent on \a originalMessageTime and contained \a originalMessageAttachements attachements. */ void PsiAccount::dj_composeMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread, ComposeType ct, const Jid &originalJid_Sender, const Jid &originalJid_Me, const QDateTime &originalMessageTime, const UrlList &originalMessageAttachements) { EventDlg *w = d->psi->createEventDlg(jid.full(), this); // header QString headerTemplate = ""; if ( ct == ctQuote && option.useQuoteHeaderQuote ) { headerTemplate = option.quoteHeaderQuote; } else if ( ct == ctReply && option.useQuoteHeaderReply ) { headerTemplate = option.quoteHeaderReply; } else if ( ct == ctForward && option.useQuoteHeaderForward ) { headerTemplate = option.quoteHeaderForward; } QString header = expandQuoteHeadlineTemplate(headerTemplate, subject, originalJid_Sender, originalJid_Me, originalMessageTime); // body QString messageBody = ""; if ( ct == ctNew ) { messageBody = body; } else if ( ct != ctReply ) { // ctReply -> empty body messageBody = qstrquote(body, option.wrapAtColumn, option.quoteEmptyLines, option.stripEmptyLines, option.removeSignature); } // header/body separator // we want to have one '\n' between header and body QString sepBody = ""; if (!header.isEmpty() && !messageBody.isEmpty() && !header.endsWith("\n") && !messageBody.startsWith("\n")) sepBody = "\n"; // signature QString signature = (option.useMessageSignature)? expandSignatureTemplate(option.messageSignature, option.autoMessageSignatureDelimiter,FALSE) : ""; // body/signature separator // we want to have one empty line between body and signature (at least two '\n'); // or, when body is empty, an empty line between header and signature QString sepSig = ""; const QString &separated = (messageBody.isEmpty())? header : messageBody; if (!signature.isEmpty()) { int nNewLines = 0; if ( separated.endsWith("\n\n") ) nNewLines = 2; else if ( separated.endsWith("\n") ) nNewLines = 1; if ( signature.startsWith("\n\n") ) nNewLines += 2; else if ( signature.startsWith("\n") ) ++nNewLines; if ( nNewLines == 1 ) sepSig = "\n"; else if ( nNewLines == 0 ) sepSig = "\n\n"; } // message text is ready w->setText(header + sepBody + messageBody + sepSig + signature, option.cursorAtTopInEventDlg); // subject QString prefix; if ( ct == ctForward ) { prefix = "Fw:"; } else if ( ct == ctReply || ct == ctQuote ) { prefix = "Re:"; } if (!prefix.isEmpty()) { const int pl = prefix.length(); if(!subject.isEmpty() && subject.left(pl) != prefix) w->setSubject(prefix + " " + subject); else if (subject.left(pl) == prefix) w->setSubject(subject); } // thread - copy thread only for reply/quote, not for forward if(!thread.isEmpty() && (ct == ctReply || ct == ctQuote)) w->setThread(thread); // attachements - copy when forwarding if (ct == ctForward) { for(QValueList::ConstIterator it = originalMessageAttachements.begin(); it != originalMessageAttachements.end(); ++it) { const Url &u = *it; w->addAttachement(u.url(), u.desc()); } } w->show(); } void PsiAccount::dj_composeMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread) { Jid jDummy; QDateTime qdtDummy; UrlList ulDummy; dj_composeMessage(jid, body, subject, thread, ctQuote , jDummy, jDummy, qdtDummy, ulDummy); } void PsiAccount::dj_composeMessage(const Jid &j, const QString &body) { Jid jDummy; QDateTime qdtDummy; UrlList ulDummy; dj_composeMessage(j, body, QString::null, QString::null, ctNew, jDummy, jDummy, qdtDummy, ulDummy); } 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); if (option.contactSoundMsg.contains(e->from().bare()) && option.contactSoundMsg[e->from().bare()] != -1) { if (option.contactSoundMsg[e->from().bare()] == 1) playSound(option.onevent[eChat2], true); } else { 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()); if (option.contactSoundMsg.contains(e->from().bare()) && option.contactSoundMsg[e->from().bare()] != -1) { if (option.contactSoundMsg[e->from().bare()] == 1) playSound(option.onevent[firstChat ? eChat1: eChat2], true); } else { playSound(option.onevent[firstChat ? eChat1: eChat2]); } } } // /chat else if (m.type() == "headline") { if (option.contactSoundMsg.contains(e->from().bare()) && option.contactSoundMsg[e->from().bare()] != -1) { if (option.contactSoundMsg[e->from().bare()] == 1) playSound(option.onevent[eHeadline], true); } else { playSound(option.onevent[eHeadline]); } doPopup = true; popupType = PsiPopup::AlertHeadline; } // /headline else if (m.type() == "") { if (option.contactSoundMsg.contains(e->from().bare()) && option.contactSoundMsg[e->from().bare()] != -1) { if (option.contactSoundMsg[e->from().bare()] == 1) playSound(option.onevent[eMessage], true); } else { playSound(option.onevent[eMessage]); } if (m.type() == "") { doPopup = true; popupType = PsiPopup::AlertMessage; } } // /"" else { if (option.contactSoundMsg.contains(e->from().bare()) && option.contactSoundMsg[e->from().bare()] != -1) { if (option.contactSoundMsg[e->from().bare()] == 1) playSound(option.onevent[eSystem], true); } 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); u->setAvatarFactory(d->avatarFactory); // 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); } int PsiAccount::forwardPendingEvents(const Jid &jid) { QPtrList chatList; d->eventQueue->extractMessages(&chatList); QPtrListIterator it(chatList); for(PsiEvent *e; (e = it.current()); ++it) { MessageEvent *me = (MessageEvent *)e; // process the message Message m = me->message(); AddressList oFrom = m.addressFind(OriginalFrom); AddressList oTo = m.addressFind(OriginalTo); if (oFrom.count() == 0) m.addressAdd(Address(OriginalFrom, m.from())); if (oTo.count() == 0) m.addressAdd(Address(OriginalTo, m.to())); m.setTimeStamp(m.timeStamp(), true); m.setTo(jid); m.setFrom(""); d->client->sendMessage(m); // update the eventdlg UserListItem *u = find(e->jid()); delete e; // update the contact if(u) cpUpdate(*u); updateReadNext(u->jid()); } return it.count(); } 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(); setStatusActual(d->loginStatus); } 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); u->setAvatarFactory(d->avatarFactory); // 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); u->setAvatarFactory(d->avatarFactory); // 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"