client.cpp

00001 /*
00002   Copyright (c) 2004-2007 by Jakob Schroeter <js@camaya.net>
00003   This file is part of the gloox library. http://camaya.net/gloox
00004 
00005   This software is distributed under a license. The full license
00006   agreement can be found in the file LICENSE in this distribution.
00007   This software may not be copied, modified, sold or distributed
00008   other than expressed in the named license agreement.
00009 
00010   This software is distributed without any warranty.
00011 */
00012 
00013 #ifdef _WIN32
00014 # include "../config.h.win"
00015 #elif defined( _WIN32_WCE )
00016 # include "../config.h.win"
00017 #else
00018 # include "config.h"
00019 #endif
00020 
00021 #include "client.h"
00022 #include "rostermanager.h"
00023 #include "disco.h"
00024 #include "logsink.h"
00025 #include "nonsaslauth.h"
00026 #include "tag.h"
00027 #include "stanzaextensionfactory.h"
00028 #include "stanzaextension.h"
00029 #include "tlsbase.h"
00030 
00031 #if !defined( _WIN32 ) && !defined( _WIN32_WCE )
00032 # include <unistd.h>
00033 #endif
00034 
00035 #ifndef _WIN32_WCE
00036 # include <iostream>
00037 # include <sstream>
00038 #else
00039 # include <stdio.h>
00040 #endif
00041 
00042 namespace gloox
00043 {
00044 
00045   Client::Client( const std::string& server )
00046     : ClientBase( XMLNS_CLIENT, server ),
00047       m_rosterManager( 0 ), m_auth( 0 ),
00048       m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
00049       m_manageRoster( true ), m_doAuth( false ),
00050       m_streamFeatures( 0 ), m_priority( 0 )
00051   {
00052     m_jid.setServer( server );
00053     init();
00054   }
00055 
00056   Client::Client( const JID& jid, const std::string& password, int port )
00057     : ClientBase( XMLNS_CLIENT, password, "", port ),
00058       m_rosterManager( 0 ), m_auth( 0 ),
00059       m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
00060       m_manageRoster( true ), m_doAuth( true ),
00061       m_streamFeatures( 0 ), m_priority( 0 )
00062   {
00063     m_jid = jid;
00064     m_server = m_jid.serverRaw();
00065     init();
00066   }
00067 
00068   Client::Client( const std::string& username, const std::string& password,
00069                   const std::string& server, const std::string& resource, int port )
00070     : ClientBase( XMLNS_CLIENT, password, server, port ),
00071       m_rosterManager( 0 ), m_auth( 0 ),
00072       m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
00073       m_manageRoster( true ), m_doAuth( true ),
00074       m_streamFeatures( 0 ), m_priority( 0 )
00075   {
00076     m_jid.setUsername( username );
00077     m_jid.setServer( server );
00078     m_jid.setResource( resource );
00079 
00080     init();
00081   }
00082 
00083   Client::~Client()
00084   {
00085     removePresenceExtensions();
00086     delete m_rosterManager;
00087     delete m_auth;
00088   }
00089 
00090   void Client::init()
00091   {
00092     m_rosterManager = new RosterManager( this );
00093     m_disco->setIdentity( "client", "bot" );
00094   }
00095 
00096   void Client::setUsername( const std::string &username )
00097   {
00098     m_jid.setUsername( username );
00099     m_doAuth = true;
00100   }
00101 
00102   bool Client::handleNormalNode( Stanza *stanza )
00103   {
00104     if( stanza->name() == "stream:features" )
00105     {
00106       m_streamFeatures = getStreamFeatures( stanza );
00107 
00108       if( m_tls == TLSRequired
00109           && ( !m_encryption || !( m_streamFeatures & StreamFeatureStartTls ) ) )
00110       {
00111         logInstance().log( LogLevelError, LogAreaClassClient,
00112                     "Client is configured to require TLS but either the server didn't offer TLS or "
00113                     "TLS support is not compiled in." );
00114         disconnect( ConnTlsNotAvailable );
00115       }
00116       else if( m_tls > TLSDisabled && m_encryption && !m_encryptionActive
00117           && ( m_streamFeatures & StreamFeatureStartTls ) )
00118       {
00119         notifyStreamEvent( StreamEventEncryption );
00120         startTls();
00121       }
00122       else if( m_sasl )
00123       {
00124         if( m_authed )
00125         {
00126           if( m_streamFeatures & StreamFeatureBind )
00127           {
00128             notifyStreamEvent( StreamEventResourceBinding );
00129             bindResource();
00130           }
00131         }
00132         else if( m_doAuth && !username().empty() && !password().empty() )
00133         {
00134           if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
00135               && !m_forceNonSasl )
00136           {
00137             notifyStreamEvent( StreamEventAuthentication );
00138             startSASL( SaslMechDigestMd5 );
00139           }
00140           else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
00141                    && !m_forceNonSasl )
00142           {
00143             notifyStreamEvent( StreamEventAuthentication );
00144             startSASL( SaslMechPlain );
00145           }
00146           else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
00147           {
00148             notifyStreamEvent( StreamEventAuthentication );
00149             nonSaslLogin();
00150           }
00151           else
00152           {
00153             logInstance().log( LogLevelError, LogAreaClassClient,
00154                                      "the server doesn't support any auth mechanisms we know about" );
00155             disconnect( ConnNoSupportedAuth );
00156           }
00157         }
00158         else if( m_doAuth && !m_clientCerts.empty() && !m_clientKey.empty()
00159                  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
00160         {
00161           notifyStreamEvent( StreamEventAuthentication );
00162           startSASL( SaslMechExternal );
00163         }
00164 #ifdef _WIN32
00165         else if( m_doAuth && m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
00166         {
00167           notifyStreamEvent( StreamEventAuthentication );
00168           startSASL( SaslMechGssapi );
00169         }
00170 #endif
00171         else if( m_doAuth && m_streamFeatures & SaslMechAnonymous
00172                  && m_availableSaslMechs & SaslMechAnonymous )
00173         {
00174           notifyStreamEvent( StreamEventAuthentication );
00175           startSASL( SaslMechAnonymous );
00176         }
00177         else
00178         {
00179           notifyStreamEvent( StreamEventFinished );
00180           connected();
00181         }
00182       }
00183       else if( m_compress && m_compression && !m_compressionActive
00184           && ( m_streamFeatures & StreamFeatureCompressZlib ) )
00185       {
00186         notifyStreamEvent( StreamEventCompression );
00187         negotiateCompression( StreamFeatureCompressZlib );
00188       }
00189 //       else if( ( m_streamFeatures & StreamFeatureCompressDclz )
00190 //               && m_connection->initCompression( StreamFeatureCompressDclz ) )
00191 //       {
00192 //         negotiateCompression( StreamFeatureCompressDclz );
00193 //       }
00194       else if( m_streamFeatures & StreamFeatureIqAuth )
00195       {
00196         notifyStreamEvent( StreamEventAuthentication );
00197         nonSaslLogin();
00198       }
00199       else
00200       {
00201         logInstance().log( LogLevelError, LogAreaClassClient,
00202                            "fallback: the server doesn't support any auth mechanisms we know about" );
00203         disconnect( ConnNoSupportedAuth );
00204       }
00205     }
00206     else if( ( stanza->name() == "proceed" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_TLS ) )
00207     {
00208       logInstance().log( LogLevelDebug, LogAreaClassClient, "starting TLS handshake..." );
00209 
00210       if( m_encryption )
00211       {
00212         m_encryptionActive = true;
00213         m_encryption->handshake();
00214       }
00215     }
00216     else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_TLS ) )
00217     {
00218       logInstance().log( LogLevelError, LogAreaClassClient, "TLS handshake failed (server-side)!" );
00219       disconnect( ConnTlsFailed );
00220     }
00221     else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_COMPRESSION ) )
00222     {
00223       logInstance().log( LogLevelError, LogAreaClassClient, "stream compression init failed!" );
00224       disconnect( ConnCompressionFailed );
00225     }
00226     else if( ( stanza->name() == "compressed" ) && stanza->hasAttribute( "xmlns", XMLNS_COMPRESSION ) )
00227     {
00228       logInstance().log( LogLevelDebug, LogAreaClassClient, "stream compression inited" );
00229       m_compressionActive = true;
00230       header();
00231     }
00232     else if( ( stanza->name() == "challenge" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
00233     {
00234       logInstance().log( LogLevelDebug, LogAreaClassClient, "processing SASL challenge" );
00235       processSASLChallenge( stanza->cdata() );
00236     }
00237     else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
00238     {
00239       logInstance().log( LogLevelError, LogAreaClassClient, "SASL authentication failed!" );
00240       processSASLError( stanza );
00241       disconnect( ConnAuthenticationFailed );
00242     }
00243     else if( ( stanza->name() == "success" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
00244     {
00245       logInstance().log( LogLevelDebug, LogAreaClassClient, "SASL authentication successful" );
00246       setAuthed( true );
00247       header();
00248     }
00249     else
00250     {
00251       if( ( stanza->name() == "iq" ) && stanza->hasAttribute( "id", "bind" ) )
00252       {
00253         processResourceBind( stanza );
00254       }
00255       else if( ( stanza->name() == "iq" ) && stanza->hasAttribute( "id", "session" ) )
00256       {
00257         processCreateSession( stanza );
00258       }
00259       else
00260         return false;
00261     }
00262 
00263     return true;
00264   }
00265 
00266   int Client::getStreamFeatures( Stanza *stanza )
00267   {
00268     if( stanza->name() != "stream:features" )
00269       return 0;
00270 
00271     int features = 0;
00272 
00273     if( stanza->hasChild( "starttls", "xmlns", XMLNS_STREAM_TLS ) )
00274       features |= StreamFeatureStartTls;
00275 
00276     if( stanza->hasChild( "mechanisms", "xmlns", XMLNS_STREAM_SASL ) )
00277       features |= getSaslMechs( stanza->findChild( "mechanisms" ) );
00278 
00279     if( stanza->hasChild( "bind", "xmlns", XMLNS_STREAM_BIND ) )
00280       features |= StreamFeatureBind;
00281 
00282     if( stanza->hasChild( "session", "xmlns", XMLNS_STREAM_SESSION ) )
00283       features |= StreamFeatureSession;
00284 
00285     if( stanza->hasChild( "auth", "xmlns", XMLNS_STREAM_IQAUTH ) )
00286       features |= StreamFeatureIqAuth;
00287 
00288     if( stanza->hasChild( "register", "xmlns", XMLNS_STREAM_IQREGISTER ) )
00289       features |= StreamFeatureIqRegister;
00290 
00291     if( stanza->hasChild( "compression", "xmlns", XMLNS_STREAM_COMPRESS ) )
00292       features |= getCompressionMethods( stanza->findChild( "compression" ) );
00293 
00294     if( features == 0 )
00295       features = StreamFeatureIqAuth;
00296 
00297     return features;
00298   }
00299 
00300   int Client::getSaslMechs( Tag *tag )
00301   {
00302     int mechs = SaslMechNone;
00303 
00304     if( tag->hasChildWithCData( "mechanism", "DIGEST-MD5" ) )
00305       mechs |= SaslMechDigestMd5;
00306 
00307     if( tag->hasChildWithCData( "mechanism", "PLAIN" ) )
00308       mechs |= SaslMechPlain;
00309 
00310     if( tag->hasChildWithCData( "mechanism", "ANONYMOUS" ) )
00311       mechs |= SaslMechAnonymous;
00312 
00313     if( tag->hasChildWithCData( "mechanism", "EXTERNAL" ) )
00314       mechs |= SaslMechExternal;
00315 
00316     if( tag->hasChildWithCData( "mechanism", "GSSAPI" ) )
00317       mechs |= SaslMechGssapi;
00318 
00319     return mechs;
00320   }
00321 
00322   int Client::getCompressionMethods( Tag *tag )
00323   {
00324     int meths = 0;
00325 
00326     if( tag->hasChildWithCData( "method", "zlib" ) )
00327       meths |= StreamFeatureCompressZlib;
00328 
00329     if( tag->hasChildWithCData( "method", "lzw" ) )
00330       meths |= StreamFeatureCompressDclz;
00331 
00332     return meths;
00333   }
00334 
00335   void Client::bindResource()
00336   {
00337     if( !m_resourceBound )
00338     {
00339       Tag *iq = new Tag( "iq" );
00340       iq->addAttribute( "type", "set" );
00341       iq->addAttribute( "id", "bind" );
00342       Tag *b = new Tag( iq, "bind" );
00343       b->addAttribute( "xmlns", XMLNS_STREAM_BIND );
00344       if( !resource().empty() )
00345         new Tag( b, "resource", resource() );
00346 
00347       send( iq );
00348     }
00349   }
00350 
00351   void Client::processResourceBind( Stanza *stanza )
00352   {
00353     switch( stanza->subtype() )
00354     {
00355       case StanzaIqResult:
00356       {
00357         Tag *bind = stanza->findChild( "bind" );
00358         Tag *jid = bind->findChild( "jid" );
00359         m_jid.setJID( jid->cdata() );
00360         m_resourceBound = true;
00361 
00362         if( m_streamFeatures & StreamFeatureSession )
00363           createSession();
00364         else
00365           connected();
00366         break;
00367       }
00368       case StanzaIqError:
00369       {
00370         Tag *error = stanza->findChild( "error" );
00371         if( stanza->hasChild( "error", "type", "modify" )
00372             && error->hasChild( "bad-request", "xmlns", XMLNS_XMPP_STANZAS ) )
00373         {
00374           notifyOnResourceBindError( RbErrorBadRequest );
00375         }
00376         else if( stanza->hasChild( "error", "type", "cancel" ) )
00377         {
00378           if( error->hasChild( "not-allowed", "xmlns", XMLNS_XMPP_STANZAS ) )
00379             notifyOnResourceBindError( RbErrorNotAllowed );
00380           else if( error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
00381             notifyOnResourceBindError( RbErrorConflict );
00382           else
00383             notifyOnResourceBindError( RbErrorUnknownError );
00384         }
00385         else
00386           notifyOnResourceBindError( RbErrorUnknownError );
00387         break;
00388       }
00389       default:
00390         break;
00391     }
00392   }
00393 
00394   void Client::createSession()
00395   {
00396     notifyStreamEvent( StreamEventSessionCreation );
00397     Tag *iq = new Tag( "iq" );
00398     iq->addAttribute( "type", "set" );
00399     iq->addAttribute( "id", "session" );
00400     Tag *s = new Tag( iq, "session" );
00401     s->addAttribute( "xmlns", XMLNS_STREAM_SESSION );
00402 
00403     send( iq );
00404   }
00405 
00406   void Client::processCreateSession( Stanza *stanza )
00407   {
00408     switch( stanza->subtype() )
00409     {
00410       case StanzaIqResult:
00411       {
00412         connected();
00413         break;
00414       }
00415       case StanzaIqError:
00416       {
00417         Tag *error = stanza->findChild( "error" );
00418         if( stanza->hasChild( "error", "type", "wait" )
00419             && error->hasChild( "internal-server-error", "xmlns", XMLNS_XMPP_STANZAS ) )
00420         {
00421           notifyOnSessionCreateError( ScErrorInternalServerError );
00422         }
00423         else if( stanza->hasChild( "error", "type", "auth" )
00424                  && error->hasChild( "forbidden", "xmlns", XMLNS_XMPP_STANZAS ) )
00425         {
00426           notifyOnSessionCreateError( ScErrorForbidden );
00427         }
00428         else if( stanza->hasChild( "error", "type", "cancel" )
00429                  && error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
00430         {
00431           notifyOnSessionCreateError( ScErrorConflict );
00432         }
00433         else
00434           notifyOnSessionCreateError( ScErrorUnknownError );
00435         break;
00436       }
00437       default:
00438         break;
00439     }
00440   }
00441 
00442   void Client::negotiateCompression( StreamFeature method )
00443   {
00444     Tag *t = new Tag( "compress" );
00445     t->addAttribute( "xmlns", XMLNS_COMPRESSION );
00446 
00447     if( method == StreamFeatureCompressZlib )
00448       new Tag( t, "method", "zlib" );
00449 
00450     if( method == StreamFeatureCompressDclz )
00451       new Tag( t, "method", "lzw" );
00452 
00453     send( t );
00454   }
00455 
00456   void Client::addPresenceExtension( StanzaExtension *se )
00457   {
00458     m_presenceExtensions.push_back( se );
00459   }
00460 
00461   void Client::removePresenceExtensions()
00462   {
00463     StanzaExtensionList::iterator it = m_presenceExtensions.begin();
00464     for( ; it != m_presenceExtensions.end(); ++it )
00465     {
00466       delete (*it);
00467     }
00468   }
00469 
00470   void Client::setPresence( Presence presence, int priority, const std::string& status )
00471   {
00472     m_presence = presence;
00473     m_status = status;
00474 
00475     if( priority < -128 )
00476       m_priority = -128;
00477     if( priority > 127 )
00478       m_priority = 127;
00479     else
00480       m_priority = priority;
00481 
00482     sendPresence();
00483   }
00484 
00485   void Client::disableRoster()
00486   {
00487     m_manageRoster = false;
00488     delete m_rosterManager;
00489     m_rosterManager = 0;
00490   }
00491 
00492   void Client::nonSaslLogin()
00493   {
00494     if( !m_auth )
00495       m_auth = new NonSaslAuth( this );
00496     m_auth->doAuth( m_sid );
00497   }
00498 
00499   void Client::sendPresence()
00500   {
00501     if( m_presence != PresenceUnknown &&
00502         state() >= StateConnected )
00503     {
00504       JID jid;
00505       Stanza *p = Stanza::createPresenceStanza( jid, m_status, m_presence );
00506 #ifdef _WIN32_WCE
00507       char tmp[5];
00508       tmp[4] = '\0';
00509       sprintf( tmp, "%s", m_priority );
00510       new Tag( p, "priority", tmp );
00511 #else
00512       std::ostringstream oss;
00513       oss << m_priority;
00514       new Tag( p, "priority", oss.str() );
00515 #endif
00516       StanzaExtensionList::const_iterator it = m_presenceExtensions.begin();
00517       for( ; it != m_presenceExtensions.end(); ++it )
00518       {
00519         p->addChild( (*it)->tag() );
00520       }
00521 
00522       send( p );
00523     }
00524   }
00525 
00526   void Client::connected()
00527   {
00528     if( m_authed )
00529     {
00530       if( m_manageRoster )
00531       {
00532         notifyStreamEvent( StreamEventRoster );
00533         m_rosterManager->fill();
00534       }
00535       else
00536         rosterFilled();
00537     }
00538     else
00539     {
00540       notifyStreamEvent( StreamEventFinished );
00541       notifyOnConnect();
00542     }
00543   }
00544 
00545   void Client::rosterFilled()
00546   {
00547     sendPresence();
00548     notifyStreamEvent( StreamEventFinished );
00549     notifyOnConnect();
00550   }
00551 
00552   void Client::disconnect()
00553   {
00554     disconnect( ConnUserDisconnected );
00555   }
00556 
00557   void Client::disconnect( ConnectionError reason )
00558   {
00559     m_resourceBound = false;
00560     m_authed = false;
00561     m_streamFeatures = 0;
00562     ClientBase::disconnect( reason );
00563   }
00564 
00565   void Client::cleanup()
00566   {
00567     m_authed = false;
00568     m_resourceBound = false;
00569     m_streamFeatures = 0;
00570   }
00571 
00572 }

Generated on Sat Nov 10 08:50:26 2007 for gloox by  doxygen 1.5.3-20071008