gloox  1.0.1
client.cpp
1 /*
2  Copyright (c) 2004-2012 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 #include <cstdio>
34 
35 namespace gloox
36 {
37 
38  // ---- Client::ResourceBind ----
39  Client::ResourceBind::ResourceBind( const std::string& resource, bool bind )
40  : StanzaExtension( ExtResourceBind ), m_jid( JID() ), m_bind( bind )
41  {
42  prep::resourceprep( resource, m_resource );
43  m_valid = true;
44  }
45 
46  Client::ResourceBind::ResourceBind( const Tag* tag )
47  : StanzaExtension( ExtResourceBind ), m_resource( EmptyString ), m_bind( true )
48  {
49  if( !tag )
50  return;
51 
52  if( tag->name() == "unbind" )
53  m_bind = false;
54  else if( tag->name() == "bind" )
55  m_bind = true;
56  else
57  return;
58 
59  if( tag->hasChild( "jid" ) )
60  m_jid.setJID( tag->findChild( "jid" )->cdata() );
61  else if( tag->hasChild( "resource" ) )
62  m_resource = tag->findChild( "resource" )->cdata();
63 
64  m_valid = true;
65  }
66 
67  Client::ResourceBind::~ResourceBind()
68  {
69  }
70 
71  const std::string& Client::ResourceBind::filterString() const
72  {
73  static const std::string filter = "/iq/bind[@xmlns='" + XMLNS_STREAM_BIND + "']"
74  "|/iq/unbind[@xmlns='" + XMLNS_STREAM_BIND + "']";
75  return filter;
76  }
77 
78  Tag* Client::ResourceBind::tag() const
79  {
80  if( !m_valid )
81  return 0;
82 
83  Tag* t = new Tag( m_bind ? "bind" : "unbind" );
84  t->setXmlns( XMLNS_STREAM_BIND );
85 
86  if( m_bind && m_resource.empty() && m_jid )
87  new Tag( t, "jid", m_jid.full() );
88  else
89  new Tag( t, "resource", m_resource );
90 
91  return t;
92  }
93  // ---- ~Client::ResourceBind ----
94 
95  // ---- Client::SessionCreation ----
96  Tag* Client::SessionCreation::tag() const
97  {
98  Tag* t = new Tag( "session" );
99  t->setXmlns( XMLNS_STREAM_SESSION );
100  return t;
101  }
102  // ---- Client::SessionCreation ----
103 
104  // ---- Client ----
105  Client::Client( const std::string& server )
106  : ClientBase( XMLNS_CLIENT, server ),
107  m_rosterManager( 0 ), m_auth( 0 ),
108  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
109  m_forceNonSasl( false ), m_manageRoster( true ),
110  m_streamFeatures( 0 )
111  {
112  m_jid.setServer( server );
113  init();
114  }
115 
116  Client::Client( const JID& jid, const std::string& password, int port )
117  : ClientBase( XMLNS_CLIENT, password, EmptyString, port ),
118  m_rosterManager( 0 ), m_auth( 0 ),
119  m_presence( Presence::Available, JID() ), m_resourceBound( false ),
120  m_forceNonSasl( false ), m_manageRoster( true ),
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 & StreamFeatureBind )
180  {
182  bindResource( resource() );
183  }
184  }
185  else if( !username().empty() && !password().empty() )
186  {
187  if( !login() )
188  {
189  logInstance().err( LogAreaClassClient, "The server doesn't support"
190  " any auth mechanisms we know about" );
192  }
193  }
194  else if( !m_clientCerts.empty() && !m_clientKey.empty()
195  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
196  {
199  }
200 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
201  else if( m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
202  {
204  startSASL( SaslMechGssapi );
205  }
206  else if( m_streamFeatures & SaslMechNTLM && m_availableSaslMechs & SaslMechNTLM )
207  {
209  startSASL( SaslMechNTLM );
210  }
211 #endif
212  else if( m_streamFeatures & SaslMechAnonymous
214  {
216  startSASL( SaslMechAnonymous );
217  }
218  else
219  {
221  connected();
222  }
223  }
225  && ( m_streamFeatures & StreamFeatureCompressZlib ) )
226  {
228  negotiateCompression( StreamFeatureCompressZlib );
229  }
230 // else if( ( m_streamFeatures & StreamFeatureCompressDclz )
231 // && m_connection->initCompression( StreamFeatureCompressDclz ) )
232 // {
233 // negotiateCompression( StreamFeatureCompressDclz );
234 // }
235  else if( m_streamFeatures & StreamFeatureIqAuth )
236  {
238  nonSaslLogin();
239  }
240  else
241  {
242  logInstance().err( LogAreaClassClient, "fallback: the server doesn't "
243  "support any auth mechanisms we know about" );
245  }
246  }
247  else
248  {
249  const std::string& name = tag->name(),
250  xmlns = tag->findAttribute( XMLNS );
251  if( name == "proceed" && xmlns == XMLNS_STREAM_TLS )
252  {
253  logInstance().dbg( LogAreaClassClient, "starting TLS handshake..." );
254 
255  if( m_encryption )
256  {
257  m_encryptionActive = true;
259  }
260  }
261  else if( name == "failure" )
262  {
263  if( xmlns == XMLNS_STREAM_TLS )
264  {
265  logInstance().err( LogAreaClassClient, "TLS handshake failed (server-side)!" );
267  }
268  else if( xmlns == XMLNS_COMPRESSION )
269  {
270  logInstance().err( LogAreaClassClient, "Stream compression init failed!" );
272  }
273  else if( xmlns == XMLNS_STREAM_SASL )
274  {
275  logInstance().err( LogAreaClassClient, "SASL authentication failed!" );
276  processSASLError( tag );
278  }
279  }
280  else if( name == "compressed" && xmlns == XMLNS_COMPRESSION )
281  {
282  logInstance().dbg( LogAreaClassClient, "Stream compression initialized" );
283  m_compressionActive = true;
284  header();
285  }
286  else if( name == "challenge" && xmlns == XMLNS_STREAM_SASL )
287  {
288  logInstance().dbg( LogAreaClassClient, "Processing SASL challenge" );
289  processSASLChallenge( tag->cdata() );
290  }
291  else if( name == "success" && xmlns == XMLNS_STREAM_SASL )
292  {
293  logInstance().dbg( LogAreaClassClient, "SASL authentication successful" );
295  setAuthed( true );
296  header();
297  }
298  else
299  return false;
300  }
301 
302  return true;
303  }
304 
305  int Client::getStreamFeatures( Tag* tag )
306  {
307  if( tag->name() != "features" || tag->xmlns() != XMLNS_STREAM )
308  return 0;
309 
310  int features = 0;
311 
312  if( tag->hasChild( "starttls", XMLNS, XMLNS_STREAM_TLS ) )
313  features |= StreamFeatureStartTls;
314 
315  if( tag->hasChild( "mechanisms", XMLNS, XMLNS_STREAM_SASL ) )
316  features |= getSaslMechs( tag->findChild( "mechanisms" ) );
317 
318  if( tag->hasChild( "bind", XMLNS, XMLNS_STREAM_BIND ) )
319  features |= StreamFeatureBind;
320 
321  if( tag->hasChild( "unbind", XMLNS, XMLNS_STREAM_BIND ) )
322  features |= StreamFeatureUnbind;
323 
324  if( tag->hasChild( "session", XMLNS, XMLNS_STREAM_SESSION ) )
325  features |= StreamFeatureSession;
326 
327  if( tag->hasChild( "auth", XMLNS, XMLNS_STREAM_IQAUTH ) )
328  features |= StreamFeatureIqAuth;
329 
330  if( tag->hasChild( "register", XMLNS, XMLNS_STREAM_IQREGISTER ) )
331  features |= StreamFeatureIqRegister;
332 
333  if( tag->hasChild( "compression", XMLNS, XMLNS_STREAM_COMPRESS ) )
334  features |= getCompressionMethods( tag->findChild( "compression" ) );
335 
336  if( features == 0 )
337  features = StreamFeatureIqAuth;
338 
339  return features;
340  }
341 
342  int Client::getSaslMechs( Tag* tag )
343  {
344  int mechs = SaslMechNone;
345 
346  const std::string mech = "mechanism";
347 
348  if( tag->hasChildWithCData( mech, "DIGEST-MD5" ) )
349  mechs |= SaslMechDigestMd5;
350 
351  if( tag->hasChildWithCData( mech, "PLAIN" ) )
352  mechs |= SaslMechPlain;
353 
354  if( tag->hasChildWithCData( mech, "ANONYMOUS" ) )
355  mechs |= SaslMechAnonymous;
356 
357  if( tag->hasChildWithCData( mech, "EXTERNAL" ) )
358  mechs |= SaslMechExternal;
359 
360  if( tag->hasChildWithCData( mech, "GSSAPI" ) )
361  mechs |= SaslMechGssapi;
362 
363  if( tag->hasChildWithCData( mech, "NTLM" ) )
364  mechs |= SaslMechNTLM;
365 
366  return mechs;
367  }
368 
369  int Client::getCompressionMethods( Tag* tag )
370  {
371  int meths = 0;
372 
373  if( tag->hasChildWithCData( "method", "zlib" ) )
374  meths |= StreamFeatureCompressZlib;
375 
376  if( tag->hasChildWithCData( "method", "lzw" ) )
377  meths |= StreamFeatureCompressDclz;
378 
379  return meths;
380  }
381 
383  {
384  bool retval = true;
385 
386  if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
387  && !m_forceNonSasl )
388  {
391  }
392  else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
393  && !m_forceNonSasl )
394  {
397  }
398  else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
399  {
401  nonSaslLogin();
402  }
403  else
404  retval = false;
405 
406  return retval;
407  }
408 
409  void Client::handleIqIDForward( const IQ& iq, int context )
410  {
411  switch( context )
412  {
413  case CtxResourceUnbind:
414  // we don't store known resources anyway
415  break;
416  case CtxResourceBind:
417  processResourceBind( iq );
418  break;
419  case CtxSessionEstablishment:
420  processCreateSession( iq );
421  break;
422  default:
423  break;
424  }
425  }
426 
427  bool Client::bindOperation( const std::string& resource, bool bind )
428  {
429  if( !( m_streamFeatures & StreamFeatureUnbind ) && m_resourceBound )
430  return false;
431 
432  IQ iq( IQ::Set, JID(), getID() );
433  iq.addExtension( new ResourceBind( resource, bind ) );
434 
435  send( iq, this, bind ? CtxResourceBind : CtxResourceUnbind );
436  return true;
437  }
438 
439  bool Client::selectResource( const std::string& resource )
440  {
441  m_selectedResource = resource; // TODO: remove for 1.1
442  m_jid.setResource( resource );
443 
444  if( !( m_streamFeatures & StreamFeatureUnbind ) )
445  return false;
446 
447  return true;
448  }
449 
450  void Client::processResourceBind( const IQ& iq )
451  {
452  switch( iq.subtype() )
453  {
454  case IQ::Result:
455  {
456  const ResourceBind* rb = iq.findExtension<ResourceBind>( ExtResourceBind );
457  if( !rb || !rb->jid() )
458  {
460  break;
461  }
462 
463  m_jid = rb->jid();
464  m_resourceBound = true;
467 
468  if( m_streamFeatures & StreamFeatureSession )
469  createSession();
470  else
471  connected();
472  break;
473  }
474  case IQ::Error:
475  {
477  break;
478  }
479  default:
480  break;
481  }
482  }
483 
484  void Client::createSession()
485  {
487  IQ iq( IQ::Set, JID(), getID() );
488  iq.addExtension( new SessionCreation() );
489  send( iq, this, CtxSessionEstablishment );
490  }
491 
492  void Client::processCreateSession( const IQ& iq )
493  {
494  switch( iq.subtype() )
495  {
496  case IQ::Result:
497  connected();
498  break;
499  case IQ::Error:
500  notifyOnSessionCreateError( iq.error() );
501  break;
502  default:
503  break;
504  }
505  }
506 
507  void Client::negotiateCompression( StreamFeature method )
508  {
509  Tag* t = new Tag( "compress", XMLNS, XMLNS_COMPRESSION );
510 
511  if( method == StreamFeatureCompressZlib )
512  new Tag( t, "method", "zlib" );
513 
514  if( method == StreamFeatureCompressDclz )
515  new Tag( t, "method", "lzw" );
516 
517  send( t );
518  }
519 
520  void Client::setPresence( Presence::PresenceType pres, int priority,
521  const std::string& status )
522  {
523  m_presence.setPresence( pres );
524  m_presence.setPriority( priority );
525  m_presence.addStatus( status );
526  sendPresence( m_presence );
527  }
528 
529  void Client::setPresence( const JID& to, Presence::PresenceType pres, int priority,
530  const std::string& status )
531  {
532  Presence p( pres, to, status, priority );
533  sendPresence( p );
534  }
535 
536  void Client::sendPresence( Presence& pres )
537  {
538  if( state() < StateConnected )
539  return;
540 
541  send( pres );
542  }
543 
545  {
546  m_manageRoster = false;
547  delete m_rosterManager;
548  m_rosterManager = 0;
549  }
550 
552  {
553  if( !m_auth )
554  m_auth = new NonSaslAuth( this );
555  m_auth->doAuth( m_sid );
556  }
557 
558  void Client::connected()
559  {
560  if( m_authed )
561  {
562  if( m_manageRoster )
563  {
565  m_rosterManager->fill();
566  }
567  else
568  rosterFilled();
569  }
570  else
571  {
573  notifyOnConnect();
574  }
575  }
576 
577  void Client::rosterFilled()
578  {
579  sendPresence( m_presence );
581  notifyOnConnect();
582  }
583 
585  {
587  }
588 
589  void Client::disconnect( ConnectionError reason )
590  {
591  m_resourceBound = false;
592  m_authed = false;
593  m_streamFeatures = 0;
594  ClientBase::disconnect( reason );
595  }
596 
597  void Client::cleanup()
598  {
599  m_authed = false;
600  m_resourceBound = false;
601  m_streamFeatures = 0;
602  }
603 
604 }