client.cpp

00001 /*
00002   Copyright (c) 2004-2009 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 #include "config.h"
00014 
00015 #include "client.h"
00016 #include "capabilities.h"
00017 #include "rostermanager.h"
00018 #include "disco.h"
00019 #include "error.h"
00020 #include "logsink.h"
00021 #include "nonsaslauth.h"
00022 #include "prep.h"
00023 #include "stanzaextensionfactory.h"
00024 #include "stanzaextension.h"
00025 #include "tag.h"
00026 #include "tlsbase.h"
00027 #include "util.h"
00028 
00029 #if !defined( _WIN32 ) && !defined( _WIN32_WCE )
00030 # include <unistd.h>
00031 #endif
00032 
00033 #include <cstdio>
00034 
00035 namespace gloox
00036 {
00037 
00038   // ---- Client::ResourceBind ----
00039   Client::ResourceBind::ResourceBind( const std::string& resource, bool bind )
00040     : StanzaExtension( ExtResourceBind ), m_jid( JID() ), m_bind( bind )
00041   {
00042     prep::resourceprep( resource, m_resource );
00043     m_valid = true;
00044   }
00045 
00046   Client::ResourceBind::ResourceBind( const Tag* tag )
00047     : StanzaExtension( ExtResourceBind ), m_resource( EmptyString ), m_bind( true )
00048   {
00049     if( !tag )
00050       return;
00051 
00052     if( tag->name() == "unbind" )
00053       m_bind = false;
00054     else if( tag->name() == "bind" )
00055       m_bind = true;
00056     else
00057       return;
00058 
00059     if( tag->hasChild( "jid" ) )
00060       m_jid.setJID( tag->findChild( "jid" )->cdata() );
00061     else if( tag->hasChild( "resource" ) )
00062       m_resource = tag->findChild( "resource" )->cdata();
00063 
00064     m_valid = true;
00065   }
00066 
00067   Client::ResourceBind::~ResourceBind()
00068   {
00069   }
00070 
00071   const std::string& Client::ResourceBind::filterString() const
00072   {
00073     static const std::string filter = "/iq/bind[@xmlns='" + XMLNS_STREAM_BIND + "']"
00074         "|/iq/unbind[@xmlns='" + XMLNS_STREAM_BIND + "']";
00075     return filter;
00076   }
00077 
00078   Tag* Client::ResourceBind::tag() const
00079   {
00080     if( !m_valid )
00081       return 0;
00082 
00083     Tag* t = new Tag( m_bind ? "bind" : "unbind" );
00084     t->setXmlns( XMLNS_STREAM_BIND );
00085 
00086     if( m_bind && m_resource.empty() && m_jid )
00087       new Tag( t, "jid", m_jid.full() );
00088     else
00089       new Tag( t, "resource", m_resource );
00090 
00091     return t;
00092   }
00093   // ---- ~Client::ResourceBind ----
00094 
00095   // ---- Client::SessionCreation ----
00096   Tag* Client::SessionCreation::tag() const
00097   {
00098     Tag* t = new Tag( "session" );
00099     t->setXmlns( XMLNS_STREAM_SESSION );
00100     return t;
00101   }
00102   // ---- Client::SessionCreation ----
00103 
00104   // ---- Client ----
00105   Client::Client( const std::string& server )
00106     : ClientBase( XMLNS_CLIENT, server ),
00107       m_rosterManager( 0 ), m_auth( 0 ),
00108       m_presence( Presence::Available, JID() ), m_resourceBound( false ),
00109       m_forceNonSasl( false ), m_manageRoster( true ),
00110       m_streamFeatures( 0 )
00111   {
00112     m_jid.setServer( server );
00113     init();
00114   }
00115 
00116   Client::Client( const JID& jid, const std::string& password, int port )
00117     : ClientBase( XMLNS_CLIENT, password, EmptyString, port ),
00118       m_rosterManager( 0 ), m_auth( 0 ),
00119       m_presence( Presence::Available, JID() ), m_resourceBound( false ),
00120       m_forceNonSasl( false ), m_manageRoster( true ),
00121       m_streamFeatures( 0 )
00122   {
00123     m_jid = jid;
00124     m_server = m_jid.serverRaw();
00125     init();
00126   }
00127 
00128   Client::~Client()
00129   {
00130     delete m_rosterManager;
00131     delete m_auth;
00132   }
00133 
00134   void Client::init()
00135   {
00136     m_rosterManager = new RosterManager( this );
00137     m_disco->setIdentity( "client", "bot" );
00138     registerStanzaExtension( new ResourceBind( 0 ) );
00139     registerStanzaExtension( new Capabilities() );
00140     m_presenceExtensions.push_back( new Capabilities( m_disco ) );
00141   }
00142 
00143   void Client::setUsername( const std::string &username )
00144   {
00145     m_jid.setUsername( username );
00146   }
00147 
00148   bool Client::handleNormalNode( Tag* tag )
00149   {
00150     if( tag->name() == "features" && tag->xmlns() == XMLNS_STREAM )
00151     {
00152       m_streamFeatures = getStreamFeatures( tag );
00153 
00154       if( m_tls == TLSRequired && !m_encryptionActive
00155           && ( !m_encryption || !( m_streamFeatures & StreamFeatureStartTls ) ) )
00156       {
00157         logInstance().err( LogAreaClassClient, "Client is configured to require"
00158                                 " TLS but either the server didn't offer TLS or"
00159                                 " TLS support is not compiled in." );
00160         disconnect( ConnTlsNotAvailable );
00161       }
00162       else if( m_tls > TLSDisabled && m_encryption && !m_encryptionActive
00163           && ( m_streamFeatures & StreamFeatureStartTls ) )
00164       {
00165         notifyStreamEvent( StreamEventEncryption );
00166         startTls();
00167       }
00168       else if( m_compress && m_compression && !m_compressionActive
00169           && ( m_streamFeatures & StreamFeatureCompressZlib ) )
00170       {
00171         notifyStreamEvent( StreamEventCompression );
00172         logInstance().warn( LogAreaClassClient, "The server offers compression, but negotiating Compression at this stage is not recommended. See XEP-0170 for details. We'll continue anyway." );
00173         negotiateCompression( StreamFeatureCompressZlib );
00174       }
00175       else if( m_sasl )
00176       {
00177         if( m_authed )
00178         {
00179           if( m_streamFeatures & StreamFeatureBind )
00180           {
00181             notifyStreamEvent( StreamEventResourceBinding );
00182             bindResource( resource() );
00183           }
00184         }
00185         else if( !username().empty() && !password().empty() )
00186         {
00187           if( !login() )
00188           {
00189             logInstance().err( LogAreaClassClient, "The server doesn't support"
00190                                            " any auth mechanisms we know about" );
00191             disconnect( ConnNoSupportedAuth );
00192           }
00193         }
00194         else if( !m_clientCerts.empty() && !m_clientKey.empty()
00195                  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
00196         {
00197           notifyStreamEvent( StreamEventAuthentication );
00198           startSASL( SaslMechExternal );
00199         }
00200 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00201         else if( m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
00202         {
00203           notifyStreamEvent( StreamEventAuthentication );
00204           startSASL( SaslMechGssapi );
00205         }
00206         else if( m_streamFeatures & SaslMechNTLM && m_availableSaslMechs & SaslMechNTLM )
00207         {
00208           notifyStreamEvent( StreamEventAuthentication );
00209           startSASL( SaslMechNTLM );
00210         }
00211 #endif
00212         else if( m_streamFeatures & SaslMechAnonymous
00213                  && m_availableSaslMechs & SaslMechAnonymous )
00214         {
00215           notifyStreamEvent( StreamEventAuthentication );
00216           startSASL( SaslMechAnonymous );
00217         }
00218         else
00219         {
00220           notifyStreamEvent( StreamEventFinished );
00221           connected();
00222         }
00223       }
00224       else if( m_compress && m_compression && !m_compressionActive
00225           && ( m_streamFeatures & StreamFeatureCompressZlib ) )
00226       {
00227         notifyStreamEvent( StreamEventCompression );
00228         negotiateCompression( StreamFeatureCompressZlib );
00229       }
00230 //       else if( ( m_streamFeatures & StreamFeatureCompressDclz )
00231 //               && m_connection->initCompression( StreamFeatureCompressDclz ) )
00232 //       {
00233 //         negotiateCompression( StreamFeatureCompressDclz );
00234 //       }
00235       else if( m_streamFeatures & StreamFeatureIqAuth )
00236       {
00237         notifyStreamEvent( StreamEventAuthentication );
00238         nonSaslLogin();
00239       }
00240       else
00241       {
00242         logInstance().err( LogAreaClassClient, "fallback: the server doesn't "
00243                                    "support any auth mechanisms we know about" );
00244         disconnect( ConnNoSupportedAuth );
00245       }
00246     }
00247     else
00248     {
00249       const std::string& name  = tag->name(),
00250                          xmlns = tag->findAttribute( XMLNS );
00251       if( name == "proceed" && xmlns == XMLNS_STREAM_TLS )
00252       {
00253         logInstance().dbg( LogAreaClassClient, "starting TLS handshake..." );
00254 
00255         if( m_encryption )
00256         {
00257           m_encryptionActive = true;
00258           m_encryption->handshake();
00259         }
00260       }
00261       else if( name == "failure" )
00262       {
00263         if( xmlns == XMLNS_STREAM_TLS )
00264         {
00265           logInstance().err( LogAreaClassClient, "TLS handshake failed (server-side)!" );
00266           disconnect( ConnTlsFailed );
00267         }
00268         else if( xmlns == XMLNS_COMPRESSION )
00269         {
00270           logInstance().err( LogAreaClassClient, "Stream compression init failed!" );
00271           disconnect( ConnCompressionFailed );
00272         }
00273         else if( xmlns == XMLNS_STREAM_SASL )
00274         {
00275           logInstance().err( LogAreaClassClient, "SASL authentication failed!" );
00276           processSASLError( tag );
00277           disconnect( ConnAuthenticationFailed );
00278         }
00279       }
00280       else if( name == "compressed" && xmlns == XMLNS_COMPRESSION )
00281       {
00282         logInstance().dbg( LogAreaClassClient, "Stream compression initialized" );
00283         m_compressionActive = true;
00284         header();
00285       }
00286       else if( name == "challenge" && xmlns == XMLNS_STREAM_SASL )
00287       {
00288         logInstance().dbg( LogAreaClassClient, "Processing SASL challenge" );
00289         processSASLChallenge( tag->cdata() );
00290       }
00291       else if( name == "success" && xmlns == XMLNS_STREAM_SASL )
00292       {
00293         logInstance().dbg( LogAreaClassClient, "SASL authentication successful" );
00294         processSASLSuccess();
00295         setAuthed( true );
00296         header();
00297       }
00298       else
00299         return false;
00300     }
00301 
00302     return true;
00303   }
00304 
00305   int Client::getStreamFeatures( Tag* tag )
00306   {
00307     if( tag->name() != "features" || tag->xmlns() != XMLNS_STREAM )
00308       return 0;
00309 
00310     int features = 0;
00311 
00312     if( tag->hasChild( "starttls", XMLNS, XMLNS_STREAM_TLS ) )
00313       features |= StreamFeatureStartTls;
00314 
00315     if( tag->hasChild( "mechanisms", XMLNS, XMLNS_STREAM_SASL ) )
00316       features |= getSaslMechs( tag->findChild( "mechanisms" ) );
00317 
00318     if( tag->hasChild( "bind", XMLNS, XMLNS_STREAM_BIND ) )
00319       features |= StreamFeatureBind;
00320 
00321     if( tag->hasChild( "unbind", XMLNS, XMLNS_STREAM_BIND ) )
00322       features |= StreamFeatureUnbind;
00323 
00324     if( tag->hasChild( "session", XMLNS, XMLNS_STREAM_SESSION ) )
00325       features |= StreamFeatureSession;
00326 
00327     if( tag->hasChild( "auth", XMLNS, XMLNS_STREAM_IQAUTH ) )
00328       features |= StreamFeatureIqAuth;
00329 
00330     if( tag->hasChild( "register", XMLNS, XMLNS_STREAM_IQREGISTER ) )
00331       features |= StreamFeatureIqRegister;
00332 
00333     if( tag->hasChild( "compression", XMLNS, XMLNS_STREAM_COMPRESS ) )
00334       features |= getCompressionMethods( tag->findChild( "compression" ) );
00335 
00336     if( features == 0 )
00337       features = StreamFeatureIqAuth;
00338 
00339     return features;
00340   }
00341 
00342   int Client::getSaslMechs( Tag* tag )
00343   {
00344     int mechs = SaslMechNone;
00345 
00346     const std::string mech = "mechanism";
00347 
00348     if( tag->hasChildWithCData( mech, "DIGEST-MD5" ) )
00349       mechs |= SaslMechDigestMd5;
00350 
00351     if( tag->hasChildWithCData( mech, "PLAIN" ) )
00352       mechs |= SaslMechPlain;
00353 
00354     if( tag->hasChildWithCData( mech, "ANONYMOUS" ) )
00355       mechs |= SaslMechAnonymous;
00356 
00357     if( tag->hasChildWithCData( mech, "EXTERNAL" ) )
00358       mechs |= SaslMechExternal;
00359 
00360     if( tag->hasChildWithCData( mech, "GSSAPI" ) )
00361       mechs |= SaslMechGssapi;
00362 
00363     if( tag->hasChildWithCData( mech, "NTLM" ) )
00364       mechs |= SaslMechNTLM;
00365 
00366     return mechs;
00367   }
00368 
00369   int Client::getCompressionMethods( Tag* tag )
00370   {
00371     int meths = 0;
00372 
00373     if( tag->hasChildWithCData( "method", "zlib" ) )
00374       meths |= StreamFeatureCompressZlib;
00375 
00376     if( tag->hasChildWithCData( "method", "lzw" ) )
00377       meths |= StreamFeatureCompressDclz;
00378 
00379     return meths;
00380   }
00381 
00382   bool Client::login()
00383   {
00384     bool retval = true;
00385 
00386     if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
00387         && !m_forceNonSasl )
00388     {
00389       notifyStreamEvent( StreamEventAuthentication );
00390       startSASL( SaslMechDigestMd5 );
00391     }
00392     else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
00393              && !m_forceNonSasl )
00394     {
00395       notifyStreamEvent( StreamEventAuthentication );
00396       startSASL( SaslMechPlain );
00397     }
00398     else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
00399     {
00400       notifyStreamEvent( StreamEventAuthentication );
00401       nonSaslLogin();
00402     }
00403     else
00404       retval = false;
00405 
00406     return retval;
00407   }
00408 
00409   void Client::handleIqIDForward( const IQ& iq, int context )
00410   {
00411     switch( context )
00412     {
00413       case CtxResourceUnbind:
00414         // we don't store known resources anyway
00415         break;
00416       case CtxResourceBind:
00417         processResourceBind( iq );
00418         break;
00419       case CtxSessionEstablishment:
00420         processCreateSession( iq );
00421         break;
00422       default:
00423         break;
00424     }
00425   }
00426 
00427   bool Client::bindOperation( const std::string& resource, bool bind )
00428   {
00429     if( !( m_streamFeatures & StreamFeatureUnbind ) && m_resourceBound )
00430       return false;
00431 
00432     IQ iq( IQ::Set, JID(), getID() );
00433     iq.addExtension( new ResourceBind( resource, bind ) );
00434 
00435     send( iq, this, bind ? CtxResourceBind : CtxResourceUnbind );
00436     return true;
00437   }
00438 
00439   bool Client::selectResource( const std::string& resource )
00440   {
00441     if( !( m_streamFeatures & StreamFeatureUnbind ) )
00442       return false;
00443 
00444     m_selectedResource = resource;
00445 
00446     return true;
00447   }
00448 
00449   void Client::processResourceBind( const IQ& iq )
00450   {
00451     switch( iq.subtype() )
00452     {
00453       case IQ::Result:
00454       {
00455         const ResourceBind* rb = iq.findExtension<ResourceBind>( ExtResourceBind );
00456         if( !rb || !rb->jid() )
00457         {
00458           notifyOnResourceBindError( 0 );
00459           break;
00460         }
00461 
00462         m_jid = rb->jid();
00463         m_resourceBound = true;
00464         m_selectedResource = m_jid.resource();
00465         notifyOnResourceBind( m_jid.resource() );
00466 
00467         if( m_streamFeatures & StreamFeatureSession )
00468           createSession();
00469         else
00470           connected();
00471         break;
00472       }
00473       case IQ::Error:
00474       {
00475         notifyOnResourceBindError( iq.error() );
00476         break;
00477       }
00478       default:
00479         break;
00480     }
00481   }
00482 
00483   void Client::createSession()
00484   {
00485     notifyStreamEvent( StreamEventSessionCreation );
00486     IQ iq( IQ::Set, JID(), getID() );
00487     iq.addExtension( new SessionCreation() );
00488     send( iq, this, CtxSessionEstablishment );
00489   }
00490 
00491   void Client::processCreateSession( const IQ& iq )
00492   {
00493     switch( iq.subtype() )
00494     {
00495       case IQ::Result:
00496         connected();
00497         break;
00498       case IQ::Error:
00499         notifyOnSessionCreateError( iq.error() );
00500         break;
00501       default:
00502         break;
00503     }
00504   }
00505 
00506   void Client::negotiateCompression( StreamFeature method )
00507   {
00508     Tag* t = new Tag( "compress", XMLNS, XMLNS_COMPRESSION );
00509 
00510     if( method == StreamFeatureCompressZlib )
00511       new Tag( t, "method", "zlib" );
00512 
00513     if( method == StreamFeatureCompressDclz )
00514       new Tag( t, "method", "lzw" );
00515 
00516     send( t );
00517   }
00518 
00519   void Client::setPresence( Presence::PresenceType pres, int priority,
00520                             const std::string& status )
00521   {
00522     m_presence.setPresence( pres );
00523     m_presence.setPriority( priority );
00524     m_presence.addStatus( status );
00525     sendPresence( m_presence );
00526   }
00527 
00528   void Client::setPresence( const JID& to, Presence::PresenceType pres, int priority,
00529                             const std::string& status )
00530   {
00531     Presence p( pres, to, status, priority );
00532     sendPresence( p );
00533   }
00534 
00535   void Client::sendPresence( Presence& pres )
00536   {
00537     if( state() < StateConnected )
00538       return;
00539 
00540     send( pres );
00541   }
00542 
00543   void Client::disableRoster()
00544   {
00545     m_manageRoster = false;
00546     delete m_rosterManager;
00547     m_rosterManager = 0;
00548   }
00549 
00550   void Client::nonSaslLogin()
00551   {
00552     if( !m_auth )
00553       m_auth = new NonSaslAuth( this );
00554     m_auth->doAuth( m_sid );
00555   }
00556 
00557   void Client::connected()
00558   {
00559     if( m_authed )
00560     {
00561       if( m_manageRoster )
00562       {
00563         notifyStreamEvent( StreamEventRoster );
00564         m_rosterManager->fill();
00565       }
00566       else
00567         rosterFilled();
00568     }
00569     else
00570     {
00571       notifyStreamEvent( StreamEventFinished );
00572       notifyOnConnect();
00573     }
00574   }
00575 
00576   void Client::rosterFilled()
00577   {
00578     sendPresence( m_presence );
00579     notifyStreamEvent( StreamEventFinished );
00580     notifyOnConnect();
00581   }
00582 
00583   void Client::disconnect()
00584   {
00585     disconnect( ConnUserDisconnected );
00586   }
00587 
00588   void Client::disconnect( ConnectionError reason )
00589   {
00590     m_resourceBound = false;
00591     m_authed = false;
00592     m_streamFeatures = 0;
00593     ClientBase::disconnect( reason );
00594   }
00595 
00596   void Client::cleanup()
00597   {
00598     m_authed = false;
00599     m_resourceBound = false;
00600     m_streamFeatures = 0;
00601   }
00602 
00603 }
Generated on Tue Jun 8 23:37:53 2010 for gloox by  doxygen 1.6.3