gloox  1.1-svn
client.cpp
1 /*
2  Copyright (c) 2004-2009 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 "connectiontls.h"
27 #include "connectioncompression.h"
28 #include "util.h"
29 
30 #if !defined( _WIN32 ) && !defined( _WIN32_WCE )
31 # include <unistd.h>
32 #endif
33 
34 #include <cstdio>
35 
36 namespace gloox
37 {
38 
39  // ---- Client::ResourceBind ----
40  Client::ResourceBind::ResourceBind( const std::string& resource, bool bind )
41  : StanzaExtension( ExtResourceBind ), m_jid( JID() ), m_bind( bind )
42  {
43  prep::resourceprep( resource, m_resource );
44  m_valid = true;
45  }
46 
47  Client::ResourceBind::ResourceBind( const Tag* tag )
48  : StanzaExtension( ExtResourceBind ), m_resource( EmptyString ), m_bind( true )
49  {
50  if( !tag )
51  return;
52 
53  if( tag->name() == "unbind" )
54  m_bind = false;
55  else if( tag->name() == "bind" )
56  m_bind = true;
57  else
58  return;
59 
60  if( tag->hasChild( "jid" ) )
61  m_jid.setJID( tag->findChild( "jid" )->cdata() );
62  else if( tag->hasChild( "resource" ) )
63  m_resource = tag->findChild( "resource" )->cdata();
64 
65  m_valid = true;
66  }
67 
68  Client::ResourceBind::~ResourceBind()
69  {
70  }
71 
72  const std::string& Client::ResourceBind::filterString() const
73  {
74  static const std::string filter = "/iq/bind[@xmlns='" + XMLNS_STREAM_BIND + "']"
75  "|/iq/unbind[@xmlns='" + XMLNS_STREAM_BIND + "']";
76  return filter;
77  }
78 
79  Tag* Client::ResourceBind::tag() const
80  {
81  if( !m_valid )
82  return 0;
83 
84  Tag* t = new Tag( m_bind ? "bind" : "unbind" );
85  t->setXmlns( XMLNS_STREAM_BIND );
86 
87  if( m_bind && m_resource.empty() && m_jid )
88  new Tag( t, "jid", m_jid.full() );
89  else
90  new Tag( t, "resource", m_resource );
91 
92  return t;
93  }
94  // ---- ~Client::ResourceBind ----
95 
96  // ---- Client::SessionCreation ----
97  Tag* Client::SessionCreation::tag() const
98  {
99  Tag* t = new Tag( "session" );
100  t->setXmlns( XMLNS_STREAM_SESSION );
101  return t;
102  }
103  // ---- Client::SessionCreation ----
104 
105  // ---- Client ----
106  Client::Client( const std::string& server )
107  : ClientBase( XMLNS_CLIENT, server ),
108  m_rosterManager( 0 ), m_auth( 0 ),
109  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
110  m_forceNonSasl( false ), m_manageRoster( true ),
111  m_streamFeatures( 0 )
112  {
113  m_jid.setServer( server );
114  init();
115  }
116 
117  Client::Client( const JID& jid, const std::string& password, int port )
118  : ClientBase( XMLNS_CLIENT, password, EmptyString, port ),
119  m_rosterManager( 0 ), m_auth( 0 ),
120  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
121  m_forceNonSasl( false ), m_manageRoster( true ),
122  m_streamFeatures( 0 )
123  {
124  m_jid = jid;
126  init();
127  }
128 
130  {
131  delete m_rosterManager;
132  delete m_auth;
133  }
134 
135  void Client::init()
136  {
137  m_rosterManager = new RosterManager( this );
138  m_disco->setIdentity( "client", "bot" );
139  registerStanzaExtension( new ResourceBind( 0 ) );
141  m_presenceExtensions.push_back( new Capabilities( m_disco ) );
142  }
143 
144  void Client::setUsername( const std::string &username )
145  {
146  m_jid.setUsername( username );
147  }
148 
149  bool Client::handleNormalNode( Tag* tag )
150  {
151  if( tag->name() == "features" && tag->xmlns() == XMLNS_STREAM )
152  {
153  m_streamFeatures = getStreamFeatures( tag );
154 
156  && ( !hasTls() || ( ( m_streamFeatures & StreamFeatureStartTls ) != StreamFeatureStartTls ) ) )
157  {
158  if( !hasTls() )
159  logInstance().err( LogAreaClassClient, "Client is configured to require"
160  " TLS but TLS support is not compiled into gloox." );
161  else
162  logInstance().err( LogAreaClassClient, "Client is configured to require"
163  " TLS but the server didn't offer TLS." );
165  }
166  else if( m_tls > TLSDisabled && hasTls() && !m_encryptionActive
168  && ( ( m_streamFeatures & StreamFeatureStartTls ) == StreamFeatureStartTls ) )
169  {
171  startTls();
172  }
173  else if( hasCompression() && !m_compressionActive
175  && ( ( m_streamFeatures & StreamFeatureCompressZlib ) == StreamFeatureCompressZlib ) )
176  {
178  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." );
179  negotiateCompression( StreamFeatureCompressZlib );
180  }
181  else if( m_sasl )
182  {
183  if( m_authed )
184  {
185  if( m_streamFeatures & StreamFeatureBind )
186  {
188  bindResource( resource() );
189  }
190  }
191  else if( !username().empty() && !password().empty() )
192  {
193  if( !login() )
194  {
195  logInstance().err( LogAreaClassClient, "The server doesn't support"
196  " any auth mechanisms we know about" );
198  }
199  }
200  else if( !m_clientCerts.empty() && !m_clientKey.empty()
201  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
202  {
205  }
206 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
207  else if( m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
208  {
210  startSASL( SaslMechGssapi );
211  }
212  else if( m_streamFeatures & SaslMechNTLM && m_availableSaslMechs & SaslMechNTLM )
213  {
215  startSASL( SaslMechNTLM );
216  }
217 #endif
218  else if( m_streamFeatures & SaslMechAnonymous
220  {
222  startSASL( SaslMechAnonymous );
223  }
224  else
225  {
227  connected();
228  }
229  }
230  else if( hasCompression()
232  && ( m_streamFeatures & StreamFeatureCompressZlib ) )
233  {
235  negotiateCompression( StreamFeatureCompressZlib );
236  }
237 // else if( ( m_streamFeatures & StreamFeatureCompressDclz )
238 // && m_connection->initCompression( StreamFeatureCompressDclz ) )
239 // {
240 // negotiateCompression( StreamFeatureCompressDclz );
241 // }
242  else if( m_streamFeatures & StreamFeatureIqAuth )
243  {
245  nonSaslLogin();
246  }
247  else
248  {
249  logInstance().err( LogAreaClassClient, "fallback: the server doesn't "
250  "support any auth mechanisms we know about" );
252  }
253  }
254  else
255  {
256  const std::string& name = tag->name();
257  const std::string& xmlns = tag->xmlns();
258  if( name == "proceed" && xmlns == XMLNS_STREAM_TLS && hasTls() )
259  {
260  logInstance().dbg( LogAreaClassClient, "Starting TLS handshake..." );
261  if( m_encryption )
263  else
264  {
265  m_encryption = new ConnectionTLS( this, m_connection, m_logInstance );
267  }
269  m_encryption->setCACerts( m_cacerts );
272  }
273  else if( name == "failure" )
274  {
275  if( xmlns == XMLNS_STREAM_TLS )
276  {
277  logInstance().err( LogAreaClassClient, "TLS handshake failed (server-side)!" );
279  }
280  else if( xmlns == XMLNS_COMPRESSION )
281  {
282  logInstance().err( LogAreaClassClient, "Stream compression init failed!" );
284  }
285  else if( xmlns == XMLNS_STREAM_SASL )
286  {
287  logInstance().err( LogAreaClassClient, "SASL authentication failed!" );
288  processSASLError( tag );
290  }
291  }
292  else if( name == "compressed" && xmlns == XMLNS_COMPRESSION )
293  {
294  logInstance().dbg( LogAreaClassClient, "Stream compression initialized" );
295  if( m_compression )
297  else
298  m_compression = new ConnectionCompression( this, m_connection, m_logInstance );
300  m_compression->connect();
301  header();
302  }
303  else if( name == "challenge" && xmlns == XMLNS_STREAM_SASL )
304  {
305  logInstance().dbg( LogAreaClassClient, "Processing SASL challenge" );
306  processSASLChallenge( tag->cdata() );
307  }
308  else if( name == "success" && xmlns == XMLNS_STREAM_SASL )
309  {
310  logInstance().dbg( LogAreaClassClient, "SASL authentication successful" );
312  setAuthed( true );
313  header();
314  }
315  else
316  return false;
317  }
318 
319  return true;
320  }
321 
322  int Client::getStreamFeatures( Tag* tag )
323  {
324  if( tag->name() != "features" || tag->xmlns() != XMLNS_STREAM )
325  return 0;
326 
327  int features = 0;
328 
329  if( tag->hasChild( "starttls", XMLNS, XMLNS_STREAM_TLS ) )
330  features |= StreamFeatureStartTls;
331 
332  if( tag->hasChild( "mechanisms", XMLNS, XMLNS_STREAM_SASL ) )
333  features |= getSaslMechs( tag->findChild( "mechanisms" ) );
334 
335  if( tag->hasChild( "bind", XMLNS, XMLNS_STREAM_BIND ) )
336  features |= StreamFeatureBind;
337 
338  if( tag->hasChild( "unbind", XMLNS, XMLNS_STREAM_BIND ) )
339  features |= StreamFeatureUnbind;
340 
341  if( tag->hasChild( "session", XMLNS, XMLNS_STREAM_SESSION ) )
342  features |= StreamFeatureSession;
343 
344  if( tag->hasChild( "auth", XMLNS, XMLNS_STREAM_IQAUTH ) )
345  features |= StreamFeatureIqAuth;
346 
347  if( tag->hasChild( "register", XMLNS, XMLNS_STREAM_IQREGISTER ) )
348  features |= StreamFeatureIqRegister;
349 
350  if( tag->hasChild( "compression", XMLNS, XMLNS_STREAM_COMPRESS ) )
351  features |= getCompressionMethods( tag->findChild( "compression" ) );
352 
353  if( features == 0 )
354  features = StreamFeatureIqAuth;
355 
356  return features;
357  }
358 
359  int Client::getSaslMechs( Tag* tag )
360  {
361  int mechs = SaslMechNone;
362 
363  const std::string mech = "mechanism";
364 
365  if( tag->hasChildWithCData( mech, "DIGEST-MD5" ) )
366  mechs |= SaslMechDigestMd5;
367 
368  if( tag->hasChildWithCData( mech, "PLAIN" ) )
369  mechs |= SaslMechPlain;
370 
371  if( tag->hasChildWithCData( mech, "ANONYMOUS" ) )
372  mechs |= SaslMechAnonymous;
373 
374  if( tag->hasChildWithCData( mech, "EXTERNAL" ) )
375  mechs |= SaslMechExternal;
376 
377  if( tag->hasChildWithCData( mech, "GSSAPI" ) )
378  mechs |= SaslMechGssapi;
379 
380  if( tag->hasChildWithCData( mech, "NTLM" ) )
381  mechs |= SaslMechNTLM;
382 
383  return mechs;
384  }
385 
386  int Client::getCompressionMethods( Tag* tag )
387  {
388  int meths = 0;
389 
390  if( tag->hasChildWithCData( "method", "zlib" ) )
391  meths |= StreamFeatureCompressZlib;
392 
393  if( tag->hasChildWithCData( "method", "lzw" ) )
394  meths |= StreamFeatureCompressDclz;
395 
396  return meths;
397  }
398 
400  {
401  bool retval = true;
402 
403  if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
404  && !m_forceNonSasl )
405  {
408  }
409  else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
410  && !m_forceNonSasl )
411  {
414  }
415  else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
416  {
418  nonSaslLogin();
419  }
420  else
421  retval = false;
422 
423  return retval;
424  }
425 
426  void Client::handleIqIDForward( const IQ& iq, int context )
427  {
428  switch( context )
429  {
430  case CtxResourceUnbind:
431  // we don't store known resources anyway
432  break;
433  case CtxResourceBind:
434  processResourceBind( iq );
435  break;
436  case CtxSessionEstablishment:
437  processCreateSession( iq );
438  break;
439  default:
440  break;
441  }
442  }
443 
444  bool Client::bindOperation( const std::string& resource, bool bind )
445  {
446  if( !( m_streamFeatures & StreamFeatureUnbind ) && m_resourceBound )
447  return false;
448 
449  IQ iq( IQ::Set, JID(), getID() );
450  iq.addExtension( new ResourceBind( resource, bind ) );
451 
452  send( iq, this, bind ? CtxResourceBind : CtxResourceUnbind );
453  return true;
454  }
455 
456  bool Client::selectResource( const std::string& resource )
457  {
458  m_selectedResource = resource; // TODO: remove for 1.1
459  m_jid.setResource( resource );
460 
461  if( !( m_streamFeatures & StreamFeatureUnbind ) )
462  return false;
463 
464  return true;
465  }
466 
467  void Client::processResourceBind( const IQ& iq )
468  {
469  switch( iq.subtype() )
470  {
471  case IQ::Result:
472  {
473  const ResourceBind* rb = iq.findExtension<ResourceBind>( ExtResourceBind );
474  if( !rb || !rb->jid() )
475  {
477  break;
478  }
479 
480  m_jid = rb->jid();
481  m_resourceBound = true;
484 
485  if( m_streamFeatures & StreamFeatureSession )
486  createSession();
487  else
488  connected();
489  break;
490  }
491  case IQ::Error:
492  {
494  break;
495  }
496  default:
497  break;
498  }
499  }
500 
501  void Client::createSession()
502  {
504  IQ iq( IQ::Set, JID(), getID() );
505  iq.addExtension( new SessionCreation() );
506  send( iq, this, CtxSessionEstablishment );
507  }
508 
509  void Client::processCreateSession( const IQ& iq )
510  {
511  switch( iq.subtype() )
512  {
513  case IQ::Result:
514  connected();
515  break;
516  case IQ::Error:
517  notifyOnSessionCreateError( iq.error() );
518  break;
519  default:
520  break;
521  }
522  }
523 
524  void Client::negotiateCompression( StreamFeature method )
525  {
526  Tag* t = new Tag( "compress", XMLNS, XMLNS_COMPRESSION );
527 
528  if( method == StreamFeatureCompressZlib )
529  new Tag( t, "method", "zlib" );
530 
531  if( method == StreamFeatureCompressDclz )
532  new Tag( t, "method", "lzw" );
533 
534  send( t );
535  }
536 
537  void Client::setPresence( Presence::PresenceType pres, int priority,
538  const std::string& status )
539  {
540  m_presence.setPresence( pres );
541  m_presence.setPriority( priority );
542  m_presence.addStatus( status );
543  sendPresence( m_presence );
544  }
545 
546  void Client::setPresence( const JID& to, Presence::PresenceType pres, int priority,
547  const std::string& status )
548  {
549  Presence p( pres, to, status, priority );
550  sendPresence( p );
551  }
552 
553  void Client::sendPresence( Presence& pres )
554  {
555  if( state() < StateConnected )
556  return;
557 
558  send( pres );
559  }
560 
562  {
563  m_manageRoster = false;
564  delete m_rosterManager;
565  m_rosterManager = 0;
566  }
567 
569  {
570  if( !m_auth )
571  m_auth = new NonSaslAuth( this );
572  m_auth->doAuth( m_sid );
573  }
574 
575  void Client::connected()
576  {
577  if( m_authed )
578  {
579  if( m_manageRoster )
580  {
582  m_rosterManager->fill();
583  }
584  else
585  rosterFilled();
586  }
587  else
588  {
590  notifyOnConnect();
591  }
592  }
593 
594  void Client::rosterFilled()
595  {
596  sendPresence( m_presence );
598  notifyOnConnect();
599  }
600 
602  {
604  }
605 
606  void Client::disconnect( ConnectionError reason )
607  {
608  m_resourceBound = false;
609  m_authed = false;
610  m_streamFeatures = 0;
611  ClientBase::disconnect( reason );
612  }
613 
614  void Client::cleanup()
615  {
616  m_authed = false;
617  m_resourceBound = false;
618  m_streamFeatures = 0;
619  }
620 
621 }