/*************************************************************************** jabberclient.cpp - Generic Jabber Client Class ------------------- begin : Sat May 25 2005 copyright : (C) 2005 by Till Gerken (C) 2006 by Michaƫl Larouche Kopete (C) 2001-2006 Kopete developers . ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "jabberclient.h" #include #include #include #include #include #include #include "jabberconnector.h" #define JABBER_PENALTY_TIME 2 class JabberClient::Private { public: Private() : jabberClient(0L), jabberClientStream(0L), jabberClientConnector(0L), jabberTLS(0L), jabberTLSHandler(0L) {} ~Private() { if ( jabberClient ) { jabberClient->close (); } delete jabberClient; delete jabberClientStream; delete jabberClientConnector; delete jabberTLSHandler; delete jabberTLS; } // connection details XMPP::Jid jid; TQString password; // XMPP backend XMPP::Client *jabberClient; XMPP::ClientStream *jabberClientStream; JabberConnector *jabberClientConnector; TQCA::TLS *jabberTLS; XMPP::TQCATLSHandler *jabberTLSHandler; // ignore TLS warnings bool ignoreTLSWarnings; // current S5B server instance static XMPP::S5BServer *s5bServer; // address list being handled by the S5B server instance static TQStringList s5bAddressList; // port of S5B server static int s5bServerPort; // local IP address TQString localAddress; // whether TLS (or direct SSL in case of the old protocol) should be used bool forceTLS; // whether direct SSL connections should be used bool useSSL; // use XMPP 1.0 or the older protocol version bool useXMPP09; // whether SSL support should be probed in case the old protocol is used bool probeSSL; // override the default server name and port (only pre-XMPP 1.0) bool overrideHost; TQString server; int port; // allow transmission of plaintext passwords bool allowPlainTextPassword; // enable file transfers bool fileTransfersEnabled; // current penalty time int currentPenaltyTime; // client information TQString clientName, clientVersion, osName; // timezone information TQString timeZoneName; int timeZoneOffset; // Caps(JEP-0115: Entity Capabilities) information TQString capsNode, capsVersion; DiscoItem::Identity discoIdentity; }; XMPP::S5BServer *JabberClient::Private::s5bServer = 0L; TQStringList JabberClient::Private::s5bAddressList; int JabberClient::Private::s5bServerPort = 8010; JabberClient::JabberClient () { d = new Private(); cleanUp (); // initiate penalty timer TQTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, TQT_SLOT ( slotUpdatePenaltyTime () ) ); } JabberClient::~JabberClient () { delete d; } void JabberClient::cleanUp () { if ( d->jabberClient ) { d->jabberClient->close (); } delete d->jabberClient; delete d->jabberClientStream; delete d->jabberClientConnector; delete d->jabberTLSHandler; delete d->jabberTLS; d->jabberClient = 0L; d->jabberClientStream = 0L; d->jabberClientConnector = 0L; d->jabberTLSHandler = 0L; d->jabberTLS = 0L; d->currentPenaltyTime = 0; d->jid = XMPP::Jid (); d->password = TQString(); setForceTLS ( false ); setUseSSL ( false ); setUseXMPP09 ( false ); setProbeSSL ( false ); setOverrideHost ( false ); setAllowPlainTextPassword ( true ); setFileTransfersEnabled ( false ); setS5BServerPort ( 8010 ); setClientName ( TQString() ); setClientVersion ( TQString() ); setOSName ( TQString() ); setTimeZone ( "UTC", 0 ); setIgnoreTLSWarnings ( false ); } void JabberClient::slotUpdatePenaltyTime () { if ( d->currentPenaltyTime >= JABBER_PENALTY_TIME ) d->currentPenaltyTime -= JABBER_PENALTY_TIME; else d->currentPenaltyTime = 0; TQTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, TQT_SLOT ( slotUpdatePenaltyTime () ) ); } void JabberClient::setIgnoreTLSWarnings ( bool flag ) { d->ignoreTLSWarnings = flag; } bool JabberClient::ignoreTLSWarnings () { return d->ignoreTLSWarnings; } bool JabberClient::setS5BServerPort ( int port ) { d->s5bServerPort = port; if ( fileTransfersEnabled () ) { return s5bServer()->start ( port ); } return true; } int JabberClient::s5bServerPort () const { return d->s5bServerPort; } XMPP::S5BServer *JabberClient::s5bServer () { if ( !d->s5bServer ) { d->s5bServer = new XMPP::S5BServer (); TQObject::connect ( d->s5bServer, TQT_SIGNAL ( destroyed () ), this, TQT_SLOT ( slotS5BServerGone () ) ); /* * Try to start the server at the default port here. * We have no way of notifying the caller of an error. * However, since the caller will usually also * use setS5BServerPort() to ensure the correct * port, we can return an error code there. */ if ( fileTransfersEnabled () ) { s5bServer()->start ( d->s5bServerPort ); } } return d->s5bServer; } void JabberClient::slotS5BServerGone () { d->s5bServer = 0L; if ( d->jabberClient ) d->jabberClient->s5bManager()->setServer( 0L ); } void JabberClient::addS5BServerAddress ( const TQString &address ) { TQStringList newList; d->s5bAddressList.append ( address ); // now filter the list without dupes for ( TQStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) { if ( !newList.contains ( *it ) ) newList.append ( *it ); } s5bServer()->setHostList ( newList ); } void JabberClient::removeS5BServerAddress ( const TQString &address ) { TQStringList newList; TQStringList::iterator it = d->s5bAddressList.find ( address ); if ( it != d->s5bAddressList.end () ) { d->s5bAddressList.remove ( it ); } if ( d->s5bAddressList.isEmpty () ) { delete d->s5bServer; d->s5bServer = 0L; } else { // now filter the list without dupes for ( TQStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) { if ( !newList.contains ( *it ) ) newList.append ( *it ); } s5bServer()->setHostList ( newList ); } } void JabberClient::setForceTLS ( bool flag ) { d->forceTLS = flag; } bool JabberClient::forceTLS () const { return d->forceTLS; } void JabberClient::setUseSSL ( bool flag ) { d->useSSL = flag; } bool JabberClient::useSSL () const { return d->useSSL; } void JabberClient::setUseXMPP09 ( bool flag ) { d->useXMPP09 = flag; } bool JabberClient::useXMPP09 () const { return d->useXMPP09; } void JabberClient::setProbeSSL ( bool flag ) { d->probeSSL = flag; } bool JabberClient::probeSSL () const { return d->probeSSL; } void JabberClient::setOverrideHost ( bool flag, const TQString &server, int port ) { d->overrideHost = flag; d->server = server; d->port = port; } bool JabberClient::overrideHost () const { return d->overrideHost; } void JabberClient::setAllowPlainTextPassword ( bool flag ) { d->allowPlainTextPassword = flag; } bool JabberClient::allowPlainTextPassword () const { return d->allowPlainTextPassword; } void JabberClient::setFileTransfersEnabled ( bool flag, const TQString &localAddress ) { d->fileTransfersEnabled = flag; d->localAddress = localAddress; } TQString JabberClient::localAddress () const { return d->localAddress; } bool JabberClient::fileTransfersEnabled () const { return d->fileTransfersEnabled; } void JabberClient::setClientName ( const TQString &clientName ) { d->clientName = clientName; } TQString JabberClient::clientName () const { return d->clientName; } void JabberClient::setClientVersion ( const TQString &clientVersion ) { d->clientVersion = clientVersion; } TQString JabberClient::clientVersion () const { return d->clientVersion; } void JabberClient::setOSName ( const TQString &osName ) { d->osName = osName; } TQString JabberClient::osName () const { return d->osName; } void JabberClient::setCapsNode( const TQString &capsNode ) { d->capsNode = capsNode; } TQString JabberClient::capsNode() const { return d->capsNode; } void JabberClient::setCapsVersion( const TQString &capsVersion ) { d->capsVersion = capsVersion; } TQString JabberClient::capsVersion() const { return d->capsVersion; } TQString JabberClient::capsExt() const { if(d->jabberClient) { return d->jabberClient->capsExt(); } return TQString(); } void JabberClient::setDiscoIdentity( DiscoItem::Identity identity ) { d->discoIdentity = identity; } DiscoItem::Identity JabberClient::discoIdentity() const { return d->discoIdentity; } void JabberClient::setTimeZone ( const TQString &timeZoneName, int timeZoneOffset ) { d->timeZoneName = timeZoneName; d->timeZoneOffset = timeZoneOffset; } TQString JabberClient::timeZoneName () const { return d->timeZoneName; } int JabberClient::timeZoneOffset () const { return d->timeZoneOffset; } int JabberClient::getPenaltyTime () { int currentTime = d->currentPenaltyTime; d->currentPenaltyTime += JABBER_PENALTY_TIME; return currentTime; } XMPP::Client *JabberClient::client () const { return d->jabberClient; } XMPP::ClientStream *JabberClient::clientStream () const { return d->jabberClientStream; } JabberConnector *JabberClient::clientConnector () const { return d->jabberClientConnector; } XMPP::Task *JabberClient::rootTask () const { if ( client () ) { return client()->rootTask (); } else { return 0l; } } XMPP::FileTransferManager *JabberClient::fileTransferManager () const { if ( client () ) { return client()->fileTransferManager (); } else { return 0L; } } XMPP::Jid JabberClient::jid () const { return d->jid; } JabberClient::ErrorCode JabberClient::connect ( const XMPP::Jid &jid, const TQString &password, bool auth ) { /* * Close any existing connection. */ if ( d->jabberClient ) { d->jabberClient->close (); } d->jid = jid; d->password = password; /* * Return an error if we should force TLS but it's not available. */ if ( ( forceTLS () || useSSL () || probeSSL () ) && !TQCA::isSupported ( TQCA::CAP_TLS ) ) { return NoTLS; } /* * Instantiate connector, responsible for dealing with the socket. * This class uses KDE's socket code, which in turn makes use of * the global proxy settings. */ d->jabberClientConnector = new JabberConnector; d->jabberClientConnector->setOptSSL ( useSSL () ); if ( useXMPP09 () ) { if ( overrideHost () ) { d->jabberClientConnector->setOptHostPort ( d->server, d->port ); } d->jabberClientConnector->setOptProbe ( probeSSL () ); } /* * Setup authentication layer */ if ( TQCA::isSupported ( TQCA::CAP_TLS ) ) { d->jabberTLS = new TQCA::TLS; d->jabberTLSHandler = new XMPP::TQCATLSHandler ( d->jabberTLS ); { using namespace XMPP; TQObject::connect ( d->jabberTLSHandler, TQT_SIGNAL ( tlsHandshaken() ), this, TQT_SLOT ( slotTLSHandshaken () ) ); } TQPtrList certStore; d->jabberTLS->setCertificateStore ( certStore ); } /* * Instantiate client stream which handles the network communication by referring * to a connector (proxying etc.) and a TLS handler (security layer) */ d->jabberClientStream = new XMPP::ClientStream ( d->jabberClientConnector, d->jabberTLSHandler ); { using namespace XMPP; TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( needAuthParams(bool, bool, bool) ), this, TQT_SLOT ( slotCSNeedAuthParams (bool, bool, bool) ) ); TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( authenticated () ), this, TQT_SLOT ( slotCSAuthenticated () ) ); TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( connectionClosed () ), this, TQT_SLOT ( slotCSDisconnected () ) ); TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( delayedCloseFinished () ), this, TQT_SLOT ( slotCSDisconnected () ) ); TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( warning (int) ), this, TQT_SLOT ( slotCSWarning (int) ) ); TQObject::connect ( d->jabberClientStream, TQT_SIGNAL ( error (int) ), this, TQT_SLOT ( slotCSError (int) ) ); } d->jabberClientStream->setOldOnly ( useXMPP09 () ); /* * Initiate anti-idle timer (will be triggered every 55 seconds). */ d->jabberClientStream->setNoopTime ( 55000 ); /* * Allow plaintext password authentication or not? */ d->jabberClientStream->setAllowPlain( allowPlainTextPassword () ); /* * Setup client layer. */ d->jabberClient = new XMPP::Client ( this ); /* * Enable file transfer (IP and server will be set after connection * has been established. */ if ( fileTransfersEnabled () ) { d->jabberClient->setFileTransferEnabled ( true ); { using namespace XMPP; TQObject::connect ( d->jabberClient->fileTransferManager(), TQT_SIGNAL ( incomingReady() ), this, TQT_SLOT ( slotIncomingFileTransfer () ) ); } } /* This should only be done here to connect the signals, otherwise it is a * bad idea. */ { using namespace XMPP; TQObject::connect ( d->jabberClient, TQT_SIGNAL ( subscription (const Jid &, const TQString &) ), this, TQT_SLOT ( slotSubscription (const Jid &, const TQString &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( rosterRequestFinished ( bool, int, const TQString & ) ), this, TQT_SLOT ( slotRosterRequestFinished ( bool, int, const TQString & ) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( rosterItemAdded (const RosterItem &) ), this, TQT_SLOT ( slotNewContact (const RosterItem &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( rosterItemUpdated (const RosterItem &) ), this, TQT_SLOT ( slotContactUpdated (const RosterItem &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( rosterItemRemoved (const RosterItem &) ), this, TQT_SLOT ( slotContactDeleted (const RosterItem &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( resourceAvailable (const Jid &, const Resource &) ), this, TQT_SLOT ( slotResourceAvailable (const Jid &, const Resource &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( resourceUnavailable (const Jid &, const Resource &) ), this, TQT_SLOT ( slotResourceUnavailable (const Jid &, const Resource &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( messageReceived (const Message &) ), this, TQT_SLOT ( slotReceivedMessage (const Message &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( groupChatJoined (const Jid &) ), this, TQT_SLOT ( slotGroupChatJoined (const Jid &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( groupChatLeft (const Jid &) ), this, TQT_SLOT ( slotGroupChatLeft (const Jid &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( groupChatPresence (const Jid &, const Status &) ), this, TQT_SLOT ( slotGroupChatPresence (const Jid &, const Status &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( groupChatError (const Jid &, int, const TQString &) ), this, TQT_SLOT ( slotGroupChatError (const Jid &, int, const TQString &) ) ); //TQObject::connect ( d->jabberClient, TQT_SIGNAL (debugText (const TQString &) ), // this, TQT_SLOT ( slotPsiDebug (const TQString &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( xmlIncoming(const TQString& ) ), this, TQT_SLOT ( slotIncomingXML (const TQString &) ) ); TQObject::connect ( d->jabberClient, TQT_SIGNAL ( xmlOutgoing(const TQString& ) ), this, TQT_SLOT ( slotOutgoingXML (const TQString &) ) ); } d->jabberClient->setClientName ( clientName () ); d->jabberClient->setClientVersion ( clientVersion () ); d->jabberClient->setOSName ( osName () ); // Set caps information d->jabberClient->setCapsNode( capsNode() ); d->jabberClient->setCapsVersion( capsVersion() ); // Set Disco Identity d->jabberClient->setIdentity( discoIdentity() ); d->jabberClient->setTimeZone ( timeZoneName (), timeZoneOffset () ); d->jabberClient->connectToServer ( d->jabberClientStream, jid, auth ); return Ok; } void JabberClient::disconnect () { if ( d->jabberClient ) { d->jabberClient->close (); } else { cleanUp (); } } void JabberClient::disconnect( XMPP::Status &reason ) { if ( d->jabberClient ) { if ( d->jabberClientStream->isActive() ) { XMPP::JT_Presence *pres = new JT_Presence(rootTask()); reason.setIsAvailable( false ); pres->pres( reason ); pres->go(); d->jabberClientStream->close(); d->jabberClient->close(); } } else { cleanUp(); } } bool JabberClient::isConnected () const { if ( d->jabberClient ) { return d->jabberClient->isActive (); } return false; } void JabberClient::joinGroupChat ( const TQString &host, const TQString &room, const TQString &nick ) { client()->groupChatJoin ( host, room, nick ); } void JabberClient::joinGroupChat ( const TQString &host, const TQString &room, const TQString &nick, const TQString &password ) { client()->groupChatJoin ( host, room, nick, password ); } void JabberClient::leaveGroupChat ( const TQString &host, const TQString &room ) { client()->groupChatLeave ( host, room ); } void JabberClient::setGroupChatStatus( const TQString & host, const TQString & room, const XMPP::Status & status ) { client()->groupChatSetStatus( host, room, status); } void JabberClient::changeGroupChatNick( const TQString & host, const TQString & room, const TQString & nick, const XMPP::Status & status ) { client()->groupChatChangeNick( host, room, nick, status ); } void JabberClient::sendMessage ( const XMPP::Message &message ) { client()->sendMessage ( message ); } void JabberClient::send ( const TQString &packet ) { client()->send ( packet ); } void JabberClient::requestRoster () { client()->rosterRequest (); } void JabberClient::slotPsiDebug ( const TQString & _msg ) { TQString msg = _msg; msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); emit debugMessage ( "Psi: " + msg ); } void JabberClient::slotIncomingXML ( const TQString & _msg ) { TQString msg = _msg; msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); emit debugMessage ( "XML IN: " + msg ); } void JabberClient::slotOutgoingXML ( const TQString & _msg ) { TQString msg = _msg; msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); msg = msg.replace( TQRegExp( "[^<]*\n" ), "[Filtered]\n" ); emit debugMessage ( "XML OUT: " + msg ); } void JabberClient::slotTLSHandshaken () { emit debugMessage ( "TLS handshake done, testing certificate validity..." ); // FIXME: in the future, this should be handled by KDE, not TQCA int validityResult = d->jabberTLS->certificateValidityResult (); if ( validityResult == TQCA::TLS::Valid ) { emit debugMessage ( "Certificate is valid, continuing." ); // valid certificate, continue d->jabberTLSHandler->continueAfterHandshake (); } else { emit debugMessage ( "Certificate is not valid, asking user what to do next." ); // certificate is not valid, query the user if ( ignoreTLSWarnings () ) { emit debugMessage ( "We are supposed to ignore TLS warnings, continuing." ); d->jabberTLSHandler->continueAfterHandshake (); } emit tlsWarning ( validityResult ); } } void JabberClient::continueAfterTLSWarning () { if ( d->jabberTLSHandler ) { d->jabberTLSHandler->continueAfterHandshake (); } } void JabberClient::slotCSNeedAuthParams ( bool user, bool pass, bool realm ) { emit debugMessage ( "Sending auth credentials..." ); if ( user ) { d->jabberClientStream->setUsername ( jid().node () ); } if ( pass ) { d->jabberClientStream->setPassword ( d->password ); } if ( realm ) { d->jabberClientStream->setRealm ( jid().domain () ); } d->jabberClientStream->continueAfterParams (); } void JabberClient::slotCSAuthenticated () { emit debugMessage ( "Connected to Jabber server." ); /* * Determine local IP address. * FIXME: This is ugly! */ if ( localAddress().isEmpty () ) { // code for Iris-type bytestreams ByteStream *irisByteStream = d->jabberClientConnector->stream(); if ( irisByteStream->inherits ( "BSocket" ) || irisByteStream->inherits ( "XMPP::BSocket" ) ) { d->localAddress = ( (BSocket *)irisByteStream )->address().toString (); } // code for the KDE-type bytestream JabberByteStream *kdeByteStream = dynamic_cast(d->jabberClientConnector->stream()); if ( kdeByteStream ) { d->localAddress = kdeByteStream->socket()->localAddress().nodeName (); } } if ( fileTransfersEnabled () ) { // setup file transfer addS5BServerAddress ( localAddress () ); d->jabberClient->s5bManager()->setServer ( s5bServer () ); } // start the client operation d->jabberClient->start ( jid().domain (), jid().node (), d->password, jid().resource () ); emit connected (); } void JabberClient::slotCSDisconnected () { /* FIXME: * We should delete the XMPP::Client instance here, * but timers etc prevent us from doing so. (Psi does * not like to be deleted from a slot). */ emit debugMessage ( "Disconnected, freeing up file transfer port..." ); // delete local address from S5B server removeS5BServerAddress ( localAddress () ); emit csDisconnected (); } void JabberClient::slotCSWarning ( int warning ) { emit debugMessage ( "Client stream warning." ); /* * FIXME: process all other warnings */ switch ( warning ) { //case XMPP::ClientStream::WarnOldVersion: case XMPP::ClientStream::WarnNoTLS: if ( forceTLS () ) { disconnect (); emit error ( NoTLS ); return; } break; } d->jabberClientStream->continueAfterWarning (); } void JabberClient::slotCSError ( int error ) { emit debugMessage ( "Client stream error." ); emit csError ( error ); } void JabberClient::slotRosterRequestFinished ( bool success, int /*statusCode*/, const TQString &/*statusString*/ ) { emit rosterRequestFinished ( success ); } void JabberClient::slotIncomingFileTransfer () { emit incomingFileTransfer (); } void JabberClient::slotNewContact ( const XMPP::RosterItem &item ) { emit newContact ( item ); } void JabberClient::slotContactDeleted ( const RosterItem &item ) { emit contactDeleted ( item ); } void JabberClient::slotContactUpdated ( const RosterItem &item ) { emit contactUpdated ( item ); } void JabberClient::slotResourceAvailable ( const Jid &jid, const Resource &resource ) { emit resourceAvailable ( jid, resource ); } void JabberClient::slotResourceUnavailable ( const Jid &jid, const Resource &resource ) { emit resourceUnavailable ( jid, resource ); } void JabberClient::slotReceivedMessage ( const Message &message ) { emit messageReceived ( message ); } void JabberClient::slotGroupChatJoined ( const Jid &jid ) { emit groupChatJoined ( jid ); } void JabberClient::slotGroupChatLeft ( const Jid &jid ) { emit groupChatLeft ( jid ); } void JabberClient::slotGroupChatPresence ( const Jid &jid, const Status &status) { emit groupChatPresence ( jid, status ); } void JabberClient::slotGroupChatError ( const Jid &jid, int error, const TQString &reason) { emit groupChatError ( jid, error, reason ); } void JabberClient::slotSubscription ( const Jid &jid, const TQString &type ) { emit subscription ( jid, type ); } #include "jabberclient.moc"