gloox  1.0.9
client.cpp
1 /*
2  Copyright (c) 2004-2013 by Jakob Schroeter <js@camaya.net>
3  This file is part of the gloox library. http://camaya.net/gloox
4 
5  This software is distributed under a license. The full license
6  agreement can be found in the file LICENSE in this distribution.
7  This software may not be copied, modified, sold or distributed
8  other than expressed in the named license agreement.
9 
10  This software is distributed without any warranty.
11 */
12 
13 #include "config.h"
14 
15 #include "client.h"
16 #include "capabilities.h"
17 #include "rostermanager.h"
18 #include "disco.h"
19 #include "error.h"
20 #include "logsink.h"
21 #include "nonsaslauth.h"
22 #include "prep.h"
23 #include "stanzaextensionfactory.h"
24 #include "stanzaextension.h"
25 #include "tag.h"
26 #include "tlsbase.h"
27 #include "util.h"
28 
29 #if !defined( _WIN32 ) && !defined( _WIN32_WCE )
30 # include <unistd.h>
31 #endif
32 
33 namespace gloox
34 {
35 
36  // ---- Client::ResourceBind ----
37  Client::ResourceBind::ResourceBind( const std::string& resource, bool bind )
38  : StanzaExtension( ExtResourceBind ), m_jid( JID() ), m_bind( bind )
39  {
40  prep::resourceprep( resource, m_resource );
41  m_valid = true;
42  }
43 
44  Client::ResourceBind::ResourceBind( const Tag* tag )
45  : StanzaExtension( ExtResourceBind ), m_resource( EmptyString ), m_bind( true )
46  {
47  if( !tag )
48  return;
49 
50  if( tag->name() == "unbind" )
51  m_bind = false;
52  else if( tag->name() == "bind" )
53  m_bind = true;
54  else
55  return;
56 
57  if( tag->hasChild( "jid" ) )
58  m_jid.setJID( tag->findChild( "jid" )->cdata() );
59  else if( tag->hasChild( "resource" ) )
60  m_resource = tag->findChild( "resource" )->cdata();
61 
62  m_valid = true;
63  }
64 
65  Client::ResourceBind::~ResourceBind()
66  {
67  }
68 
69  const std::string& Client::ResourceBind::filterString() const
70  {
71  static const std::string filter = "/iq/bind[@xmlns='" + XMLNS_STREAM_BIND + "']"
72  "|/iq/unbind[@xmlns='" + XMLNS_STREAM_BIND + "']";
73  return filter;
74  }
75 
76  Tag* Client::ResourceBind::tag() const
77  {
78  if( !m_valid )
79  return 0;
80 
81  Tag* t = new Tag( m_bind ? "bind" : "unbind" );
82  t->setXmlns( XMLNS_STREAM_BIND );
83 
84  if( m_bind && m_resource.empty() && m_jid )
85  new Tag( t, "jid", m_jid.full() );
86  else
87  new Tag( t, "resource", m_resource );
88 
89  return t;
90  }
91  // ---- ~Client::ResourceBind ----
92 
93  // ---- Client::SessionCreation ----
94  Tag* Client::SessionCreation::tag() const
95  {
96  Tag* t = new Tag( "session" );
97  t->setXmlns( XMLNS_STREAM_SESSION );
98  return t;
99  }
100  // ---- Client::SessionCreation ----
101 
102  // ---- Client ----
103  Client::Client( const std::string& server )
104  : ClientBase( XMLNS_CLIENT, server ),
105  m_rosterManager( 0 ), m_auth( 0 ),
106  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
107  m_forceNonSasl( false ), m_manageRoster( true ),
108  m_smId( EmptyString ), m_smLocation( EmptyString ), m_smResume( false ), m_smMax( 0 ),
109  m_streamFeatures( 0 )
110  {
111  m_jid.setServer( server );
112  init();
113  }
114 
115  Client::Client( const JID& jid, const std::string& password, int port )
116  : ClientBase( XMLNS_CLIENT, password, EmptyString, port ),
117  m_rosterManager( 0 ), m_auth( 0 ),
118  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
119  m_forceNonSasl( false ), m_manageRoster( true ),
120  m_smId( EmptyString ), m_smLocation( EmptyString ), m_smResume( false ), m_smMax( 0 ),
121  m_streamFeatures( 0 )
122  {
123  m_jid = jid;
125  init();
126  }
127 
129  {
130  delete m_rosterManager;
131  delete m_auth;
132  }
133 
134  void Client::init()
135  {
136  m_rosterManager = new RosterManager( this );
137  m_disco->setIdentity( "client", "bot" );
138  registerStanzaExtension( new ResourceBind( 0 ) );
140  m_presenceExtensions.push_back( new Capabilities( m_disco ) );
141  }
142 
143  void Client::setUsername( const std::string &username )
144  {
145  m_jid.setUsername( username );
146  }
147 
148  bool Client::handleNormalNode( Tag* tag )
149  {
150  if( tag->name() == "features" && tag->xmlns() == XMLNS_STREAM )
151  {
152  m_streamFeatures = getStreamFeatures( tag );
153 
155  && ( !m_encryption || !( m_streamFeatures & StreamFeatureStartTls ) ) )
156  {
157  logInstance().err( LogAreaClassClient, "Client is configured to require"
158  " TLS but either the server didn't offer TLS or"
159  " TLS support is not compiled in." );
161  }
163  && ( m_streamFeatures & StreamFeatureStartTls ) )
164  {
166  startTls();
167  }
169  && ( m_streamFeatures & StreamFeatureCompressZlib ) )
170  {
172  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." );
173  negotiateCompression( StreamFeatureCompressZlib );
174  }
175  else if( m_sasl )
176  {
177  if( m_authed )
178  {
179  if( m_streamFeatures & StreamFeatureStreamManagement && m_smWanted && m_smContext >= CtxSMEnabled )
180  {
181  sendStreamManagement();
182  }
183  else if( m_streamFeatures & StreamFeatureBind && m_smContext < CtxSMEnabled )
184  {
186  bindResource( resource() );
187  }
188  }
189  else if( !username().empty() && !password().empty() )
190  {
191  if( !login() )
192  {
193  logInstance().err( LogAreaClassClient, "The server doesn't support"
194  " any auth mechanisms we know about" );
196  }
197  }
198  else if( !m_clientCerts.empty() && !m_clientKey.empty()
199  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
200  {
203  }
204 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
205  else if( m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
206  {
208  startSASL( SaslMechGssapi );
209  }
210  else if( m_streamFeatures & SaslMechNTLM && m_availableSaslMechs & SaslMechNTLM )
211  {
213  startSASL( SaslMechNTLM );
214  }
215 #endif
216  else if( m_streamFeatures & SaslMechAnonymous
218  {
220  startSASL( SaslMechAnonymous );
221  }
222  else
223  {
225  connected();
226  }
227  }
229  && ( m_streamFeatures & StreamFeatureCompressZlib ) )
230  {
232  negotiateCompression( StreamFeatureCompressZlib );
233  }
234 // else if( ( m_streamFeatures & StreamFeatureCompressDclz )
235 // && m_connection->initCompression( StreamFeatureCompressDclz ) )
236 // {
237 // negotiateCompression( StreamFeatureCompressDclz );
238 // }
239  else if( m_streamFeatures & StreamFeatureIqAuth )
240  {
242  nonSaslLogin();
243  }
244  else
245  {
246  logInstance().err( LogAreaClassClient, "fallback: the server doesn't "
247  "support any auth mechanisms we know about" );
249  }
250  }
251  else
252  {
253  const std::string& name = tag->name(),
254  xmlns = tag->findAttribute( XMLNS );
255  if( name == "proceed" && xmlns == XMLNS_STREAM_TLS )
256  {
257  logInstance().dbg( LogAreaClassClient, "starting TLS handshake..." );
258 
259  if( m_encryption )
260  {
261  m_encryptionActive = true;
263  }
264  }
265  else if( name == "failure" )
266  {
267  if( xmlns == XMLNS_STREAM_TLS )
268  {
269  logInstance().err( LogAreaClassClient, "TLS handshake failed (server-side)!" );
271  }
272  else if( xmlns == XMLNS_COMPRESSION )
273  {
274  logInstance().err( LogAreaClassClient, "Stream compression init failed!" );
276  }
277  else if( xmlns == XMLNS_STREAM_SASL )
278  {
279  logInstance().err( LogAreaClassClient, "SASL authentication failed!" );
280  processSASLError( tag );
282  }
283  }
284  else if( name == "compressed" && xmlns == XMLNS_COMPRESSION )
285  {
286  logInstance().dbg( LogAreaClassClient, "Stream compression initialized" );
287  m_compressionActive = true;
288  header();
289  }
290  else if( name == "challenge" && xmlns == XMLNS_STREAM_SASL )
291  {
292  logInstance().dbg( LogAreaClassClient, "Processing SASL challenge" );
293  processSASLChallenge( tag->cdata() );
294  }
295  else if( name == "success" && xmlns == XMLNS_STREAM_SASL )
296  {
297  if( !processSASLSuccess( tag->cdata() ) )
298  {
299  logInstance().err( LogAreaClassClient, "The Server response could not be verified!" );
301  return false;
302  }
303 
304  logInstance().dbg( LogAreaClassClient, "SASL authentication successful" );
305  setAuthed( true );
306  header();
307  }
308  else if( name == "enabled" && xmlns == XMLNS_STREAM_MANAGEMENT )
309  {
311  m_smMax = atoi( tag->findAttribute( "max" ).c_str() );
312  m_smId = tag->findAttribute( "id" );
313  const std::string res = tag->findAttribute( "resume" );
314  m_smResume = ( ( res == "true" || res == "1" ) && !m_smId.empty() ) ? true : false;
315  m_smLocation = tag->findAttribute( "location" );
316 
317  if( m_streamFeatures & StreamFeatureSession )
318  createSession();
319  else
320  connected();
321  }
322  else if( name == "resumed" && xmlns == XMLNS_STREAM_MANAGEMENT && m_smContext == CtxSMResume )
323  {
324  if( tag->findAttribute( "previd" ) == m_smId )
325  {
328  int h = atoi( tag->findAttribute( "h" ).c_str() );
329  connected();
330  checkQueue( h, true );
331  }
332  }
333  else if( name == "a" && xmlns == XMLNS_STREAM_MANAGEMENT && m_smContext >= CtxSMEnabled )
334  {
335  int h = atoi( tag->findAttribute( "h" ).c_str() );
336  checkQueue( h, false );
337  }
338  else if( name == "r" && xmlns == XMLNS_STREAM_MANAGEMENT )
339  {
341  }
342  else if( name == "failed" && xmlns == XMLNS_STREAM_MANAGEMENT )
343  {
344  switch( m_smContext )
345  {
346  case CtxSMEnable:
348  break;
349  case CtxSMResume:
351  break;
352  default:
353  break;
354  }
356  }
357  else
358  return false;
359  }
360 
361  return true;
362  }
363 
364  int Client::getStreamFeatures( Tag* tag )
365  {
366  if( tag->name() != "features" || tag->xmlns() != XMLNS_STREAM )
367  return 0;
368 
369  int features = 0;
370 
371  if( tag->hasChild( "starttls", XMLNS, XMLNS_STREAM_TLS ) )
372  features |= StreamFeatureStartTls;
373 
374  if( tag->hasChild( "mechanisms", XMLNS, XMLNS_STREAM_SASL ) )
375  features |= getSaslMechs( tag->findChild( "mechanisms" ) );
376 
377  if( tag->hasChild( "bind", XMLNS, XMLNS_STREAM_BIND ) )
378  features |= StreamFeatureBind;
379 
380  if( tag->hasChild( "unbind", XMLNS, XMLNS_STREAM_BIND ) )
381  features |= StreamFeatureUnbind;
382 
383  if( tag->hasChild( "session", XMLNS, XMLNS_STREAM_SESSION ) )
384  features |= StreamFeatureSession;
385 
386  if( tag->hasChild( "auth", XMLNS, XMLNS_STREAM_IQAUTH ) )
387  features |= StreamFeatureIqAuth;
388 
389  if( tag->hasChild( "register", XMLNS, XMLNS_STREAM_IQREGISTER ) )
390  features |= StreamFeatureIqRegister;
391 
392  if( tag->hasChild( "compression", XMLNS, XMLNS_STREAM_COMPRESS ) )
393  features |= getCompressionMethods( tag->findChild( "compression" ) );
394 
395  if( tag->hasChild( "sm", XMLNS, XMLNS_STREAM_MANAGEMENT ) )
396  features |= StreamFeatureStreamManagement;
397 
398  if( features == 0 )
399  features = StreamFeatureIqAuth;
400 
401  return features;
402  }
403 
404  int Client::getSaslMechs( Tag* tag )
405  {
406  int mechs = SaslMechNone;
407 
408  const std::string mech = "mechanism";
409 
410  if( tag->hasChildWithCData( mech, "SCRAM-SHA-1-PLUS" ) )
411  mechs |= SaslMechScramSha1Plus;
412 
413  if( tag->hasChildWithCData( mech, "SCRAM-SHA-1" ) )
414  mechs |= SaslMechScramSha1;
415 
416  if( tag->hasChildWithCData( mech, "DIGEST-MD5" ) )
417  mechs |= SaslMechDigestMd5;
418 
419  if( tag->hasChildWithCData( mech, "PLAIN" ) )
420  mechs |= SaslMechPlain;
421 
422  if( tag->hasChildWithCData( mech, "ANONYMOUS" ) )
423  mechs |= SaslMechAnonymous;
424 
425  if( tag->hasChildWithCData( mech, "EXTERNAL" ) )
426  mechs |= SaslMechExternal;
427 
428  if( tag->hasChildWithCData( mech, "GSSAPI" ) )
429  mechs |= SaslMechGssapi;
430 
431  if( tag->hasChildWithCData( mech, "NTLM" ) )
432  mechs |= SaslMechNTLM;
433 
434  return mechs;
435  }
436 
437  int Client::getCompressionMethods( Tag* tag )
438  {
439  int meths = 0;
440 
441  if( tag->hasChildWithCData( "method", "zlib" ) )
442  meths |= StreamFeatureCompressZlib;
443 
444  if( tag->hasChildWithCData( "method", "lzw" ) )
445  meths |= StreamFeatureCompressDclz;
446 
447  return meths;
448  }
449 
451  {
452  bool retval = true;
453 
456  && !m_forceNonSasl )
457  {
460  }
461  else if( m_streamFeatures & SaslMechScramSha1 && m_availableSaslMechs & SaslMechScramSha1
462  && !m_forceNonSasl )
463  {
466  }
467  else if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
468  && !m_forceNonSasl )
469  {
472  }
473  else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
474  && !m_forceNonSasl )
475  {
478  }
479  else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
480  {
482  nonSaslLogin();
483  }
484  else
485  retval = false;
486 
487  return retval;
488  }
489 
490  void Client::handleIqIDForward( const IQ& iq, int context )
491  {
492  switch( context )
493  {
494  case CtxResourceUnbind:
495  // we don't store known resources anyway
496  break;
497  case CtxResourceBind:
498  processResourceBind( iq );
499  break;
500  case CtxSessionEstablishment:
501  processCreateSession( iq );
502  break;
503  default:
504  break;
505  }
506  }
507 
508  bool Client::bindOperation( const std::string& resource, bool bind )
509  {
510  if( !( m_streamFeatures & StreamFeatureUnbind ) && m_resourceBound )
511  return false;
512 
513  IQ iq( IQ::Set, JID(), getID() );
514  iq.addExtension( new ResourceBind( resource, bind ) );
515 
516  send( iq, this, bind ? CtxResourceBind : CtxResourceUnbind );
517  return true;
518  }
519 
520  bool Client::selectResource( const std::string& resource )
521  {
522  m_selectedResource = resource; // TODO: remove for 1.1
523  m_jid.setResource( resource );
524 
525  if( !( m_streamFeatures & StreamFeatureUnbind ) )
526  return false;
527 
528  return true;
529  }
530 
531  void Client::processResourceBind( const IQ& iq )
532  {
533  switch( iq.subtype() )
534  {
535  case IQ::Result:
536  {
537  const ResourceBind* rb = iq.findExtension<ResourceBind>( ExtResourceBind );
538  if( !rb || !rb->jid() )
539  {
541  break;
542  }
543 
544  m_jid = rb->jid();
545  m_resourceBound = true;
546  m_selectedResource = m_jid.resource(); // TODO: remove for 1.1
548 
549  if( m_streamFeatures & StreamFeatureStreamManagement && m_smWanted )
550  sendStreamManagement();
551  else if( m_streamFeatures & StreamFeatureSession )
552  createSession();
553  else
554  connected();
555  break;
556  }
557  case IQ::Error:
558  {
560  break;
561  }
562  default:
563  break;
564  }
565  }
566 
567  void Client::setStreamManagement( bool enable, bool resume )
568  {
569  m_smWanted = enable;
570  m_smResume = resume;
571 
572  if( !m_smWanted )
573  {
574  m_smId = EmptyString;
575  m_smLocation = EmptyString;
576  m_smMax = 0;
577  m_smResume = false;
578  return;
579  }
580 
581  if( m_smWanted && m_resourceBound )
582  sendStreamManagement();
583  }
584 
585  void Client::sendStreamManagement()
586  {
587  if( !m_smWanted )
588  return;
589 
590  if( m_smContext == CtxSMInvalid )
591  {
593  Tag* e = new Tag( "enable" );
595  if( m_smResume )
596  e->addAttribute( "resume", "true" );
597  send( e );
599  m_smHandled = 0;
600  }
601  else if( m_smContext == CtxSMEnabled )
602  {
604  Tag* r = new Tag( "resume" );
605  r->setXmlns( XMLNS_STREAM_MANAGEMENT );
606  r->addAttribute( "h", m_smHandled );
607  r->addAttribute( "previd", m_smId );
608  send( r );
610  }
611  }
612 
614  {
615  if( m_smContext >= CtxSMEnabled )
616  {
617  Tag* a = new Tag( "a", "xmlns", XMLNS_STREAM_MANAGEMENT );
618  a->addAttribute( "h", m_smHandled );
619  send( a );
620  }
621  }
622 
624  {
625  if( m_smContext >= CtxSMEnabled )
626  {
627  Tag* r = new Tag( "r", "xmlns", XMLNS_STREAM_MANAGEMENT );
628  send( r );
629  }
630  }
631 
632  void Client::createSession()
633  {
635  IQ iq( IQ::Set, JID(), getID() );
636  iq.addExtension( new SessionCreation() );
637  send( iq, this, CtxSessionEstablishment );
638  }
639 
640  void Client::processCreateSession( const IQ& iq )
641  {
642  switch( iq.subtype() )
643  {
644  case IQ::Result:
645  connected();
646  break;
647  case IQ::Error:
648  notifyOnSessionCreateError( iq.error() );
649  break;
650  default:
651  break;
652  }
653  }
654 
655  void Client::negotiateCompression( StreamFeature method )
656  {
657  Tag* t = new Tag( "compress", XMLNS, XMLNS_COMPRESSION );
658 
659  if( method == StreamFeatureCompressZlib )
660  new Tag( t, "method", "zlib" );
661 
662  if( method == StreamFeatureCompressDclz )
663  new Tag( t, "method", "lzw" );
664 
665  send( t );
666  }
667 
668  void Client::setPresence( Presence::PresenceType pres, int priority,
669  const std::string& status )
670  {
671  m_presence.setPresence( pres );
672  m_presence.setPriority( priority );
673  m_presence.addStatus( status );
674  sendPresence( m_presence );
675  }
676 
677  void Client::setPresence( const JID& to, Presence::PresenceType pres, int priority,
678  const std::string& status )
679  {
680  Presence p( pres, to, status, priority );
681  sendPresence( p );
682  }
683 
684  void Client::sendPresence( Presence& pres )
685  {
686  if( state() < StateConnected )
687  return;
688 
689  send( pres );
690  }
691 
693  {
694  m_manageRoster = false;
695  delete m_rosterManager;
696  m_rosterManager = 0;
697  }
698 
700  {
701  if( !m_auth )
702  m_auth = new NonSaslAuth( this );
703  m_auth->doAuth( m_sid );
704  }
705 
706  void Client::connected()
707  {
708  if( m_authed && m_smContext != CtxSMResumed )
709  {
710  if( m_manageRoster )
711  {
713  m_rosterManager->fill();
714  }
715  else
716  rosterFilled();
717  }
718  else
719  {
721  notifyOnConnect();
722  }
723  }
724 
725  void Client::rosterFilled()
726  {
727  sendPresence( m_presence );
729  notifyOnConnect();
730  }
731 
733  {
735  m_smHandled = 0;
736  m_smId = EmptyString;
737  m_smLocation = EmptyString;
738  m_smMax = 0;
739  m_smResume = false;
740  m_smWanted = false;
741 
743  }
744 
745  void Client::disconnect( ConnectionError reason )
746  {
747  m_resourceBound = false;
748  m_authed = false;
749  m_streamFeatures = 0;
750  ClientBase::disconnect( reason );
751  }
752 
753  void Client::cleanup()
754  {
755  m_authed = false;
756  m_resourceBound = false;
757  m_streamFeatures = 0;
758  }
759 
760 }