client.cpp

00001 /*
00002   Copyright (c) 2004-2008 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 && !m_encryptionActive
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   bool Client::login() // FIXME this is c'n'p from above
00336   {
00337     bool retval = true;
00338     notifyStreamEvent( StreamEventAuthentication );
00339 
00340     if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
00341         && !m_forceNonSasl )
00342     {
00343       startSASL( SaslMechDigestMd5 );
00344     }
00345     else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
00346              && !m_forceNonSasl )
00347     {
00348       startSASL( SaslMechPlain );
00349     }
00350     else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
00351     {
00352       nonSaslLogin();
00353     }
00354     else
00355       retval = false;
00356 
00357     return retval;
00358   }
00359 
00360   void Client::bindResource()
00361   {
00362     if( !m_resourceBound )
00363     {
00364       Tag *iq = new Tag( "iq" );
00365       iq->addAttribute( "type", "set" );
00366       iq->addAttribute( "id", "bind" );
00367       Tag *b = new Tag( iq, "bind" );
00368       b->addAttribute( "xmlns", XMLNS_STREAM_BIND );
00369       if( !resource().empty() )
00370         new Tag( b, "resource", resource() );
00371 
00372       send( iq );
00373     }
00374   }
00375 
00376   void Client::processResourceBind( Stanza *stanza )
00377   {
00378     switch( stanza->subtype() )
00379     {
00380       case StanzaIqResult:
00381       {
00382         Tag *bind = stanza->findChild( "bind" );
00383         Tag *jid = bind->findChild( "jid" );
00384         m_jid.setJID( jid->cdata() );
00385         m_resourceBound = true;
00386 
00387         if( m_streamFeatures & StreamFeatureSession )
00388           createSession();
00389         else
00390           connected();
00391         break;
00392       }
00393       case StanzaIqError:
00394       {
00395         Tag *error = stanza->findChild( "error" );
00396         if( stanza->hasChild( "error", "type", "modify" )
00397             && error->hasChild( "bad-request", "xmlns", XMLNS_XMPP_STANZAS ) )
00398         {
00399           notifyOnResourceBindError( RbErrorBadRequest );
00400         }
00401         else if( stanza->hasChild( "error", "type", "cancel" ) )
00402         {
00403           if( error->hasChild( "not-allowed", "xmlns", XMLNS_XMPP_STANZAS ) )
00404             notifyOnResourceBindError( RbErrorNotAllowed );
00405           else if( error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
00406             notifyOnResourceBindError( RbErrorConflict );
00407           else
00408             notifyOnResourceBindError( RbErrorUnknownError );
00409         }
00410         else
00411           notifyOnResourceBindError( RbErrorUnknownError );
00412         break;
00413       }
00414       default:
00415         break;
00416     }
00417   }
00418 
00419   void Client::createSession()
00420   {
00421     notifyStreamEvent( StreamEventSessionCreation );
00422     Tag *iq = new Tag( "iq" );
00423     iq->addAttribute( "type", "set" );
00424     iq->addAttribute( "id", "session" );
00425     Tag *s = new Tag( iq, "session" );
00426     s->addAttribute( "xmlns", XMLNS_STREAM_SESSION );
00427 
00428     send( iq );
00429   }
00430 
00431   void Client::processCreateSession( Stanza *stanza )
00432   {
00433     switch( stanza->subtype() )
00434     {
00435       case StanzaIqResult:
00436       {
00437         connected();
00438         break;
00439       }
00440       case StanzaIqError:
00441       {
00442         Tag *error = stanza->findChild( "error" );
00443         if( stanza->hasChild( "error", "type", "wait" )
00444             && error->hasChild( "internal-server-error", "xmlns", XMLNS_XMPP_STANZAS ) )
00445         {
00446           notifyOnSessionCreateError( ScErrorInternalServerError );
00447         }
00448         else if( stanza->hasChild( "error", "type", "auth" )
00449                  && error->hasChild( "forbidden", "xmlns", XMLNS_XMPP_STANZAS ) )
00450         {
00451           notifyOnSessionCreateError( ScErrorForbidden );
00452         }
00453         else if( stanza->hasChild( "error", "type", "cancel" )
00454                  && error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
00455         {
00456           notifyOnSessionCreateError( ScErrorConflict );
00457         }
00458         else
00459           notifyOnSessionCreateError( ScErrorUnknownError );
00460         break;
00461       }
00462       default:
00463         break;
00464     }
00465   }
00466 
00467   void Client::negotiateCompression( StreamFeature method )
00468   {
00469     Tag *t = new Tag( "compress" );
00470     t->addAttribute( "xmlns", XMLNS_COMPRESSION );
00471 
00472     if( method == StreamFeatureCompressZlib )
00473       new Tag( t, "method", "zlib" );
00474 
00475     if( method == StreamFeatureCompressDclz )
00476       new Tag( t, "method", "lzw" );
00477 
00478     send( t );
00479   }
00480 
00481   void Client::addPresenceExtension( StanzaExtension *se )
00482   {
00483     m_presenceExtensions.push_back( se );
00484   }
00485 
00486   void Client::removePresenceExtensions()
00487   {
00488     StanzaExtensionList::iterator it = m_presenceExtensions.begin();
00489     for( ; it != m_presenceExtensions.end(); ++it )
00490     {
00491       delete (*it);
00492     }
00493     m_presenceExtensions.clear();
00494   }
00495 
00496   void Client::setPresence( Presence presence, int priority, const std::string& status )
00497   {
00498     m_presence = presence;
00499     m_status = status;
00500 
00501     if( priority < -128 )
00502       m_priority = -128;
00503     if( priority > 127 )
00504       m_priority = 127;
00505     else
00506       m_priority = priority;
00507 
00508     sendPresence();
00509   }
00510 
00511   void Client::disableRoster()
00512   {
00513     m_manageRoster = false;
00514     delete m_rosterManager;
00515     m_rosterManager = 0;
00516   }
00517 
00518   void Client::nonSaslLogin()
00519   {
00520     if( !m_auth )
00521       m_auth = new NonSaslAuth( this );
00522     m_auth->doAuth( m_sid );
00523   }
00524 
00525   void Client::sendPresence()
00526   {
00527     if( m_presence != PresenceUnknown &&
00528         state() >= StateConnected )
00529     {
00530       JID jid;
00531       Stanza *p = Stanza::createPresenceStanza( jid, m_status, m_presence );
00532 #ifdef _WIN32_WCE
00533       char tmp[5];
00534       tmp[4] = '\0';
00535       sprintf( tmp, "%s", m_priority );
00536       new Tag( p, "priority", tmp );
00537 #else
00538       std::ostringstream oss;
00539       oss << m_priority;
00540       new Tag( p, "priority", oss.str() );
00541 #endif
00542       StanzaExtensionList::const_iterator it = m_presenceExtensions.begin();
00543       for( ; it != m_presenceExtensions.end(); ++it )
00544       {
00545         p->addChild( (*it)->tag() );
00546       }
00547 
00548       send( p );
00549     }
00550   }
00551 
00552   void Client::connected()
00553   {
00554     if( m_authed )
00555     {
00556       if( m_manageRoster )
00557       {
00558         notifyStreamEvent( StreamEventRoster );
00559         m_rosterManager->fill();
00560       }
00561       else
00562         rosterFilled();
00563     }
00564     else
00565     {
00566       notifyStreamEvent( StreamEventFinished );
00567       notifyOnConnect();
00568     }
00569   }
00570 
00571   void Client::rosterFilled()
00572   {
00573     sendPresence();
00574     notifyStreamEvent( StreamEventFinished );
00575     notifyOnConnect();
00576   }
00577 
00578   void Client::disconnect()
00579   {
00580     disconnect( ConnUserDisconnected );
00581   }
00582 
00583   void Client::disconnect( ConnectionError reason )
00584   {
00585     m_resourceBound = false;
00586     m_authed = false;
00587     m_streamFeatures = 0;
00588     ClientBase::disconnect( reason );
00589   }
00590 
00591   void Client::cleanup()
00592   {
00593     m_authed = false;
00594     m_resourceBound = false;
00595     m_streamFeatures = 0;
00596   }
00597 
00598 }

Generated on Mon Dec 7 13:28:18 2009 for gloox by  doxygen 1.6.1