gloox  0.9.9.12
client.cpp
1 /*
2  Copyright (c) 2004-2008 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 #ifdef _WIN32
14 # include "../config.h.win"
15 #elif defined( _WIN32_WCE )
16 # include "../config.h.win"
17 #else
18 # include "config.h"
19 #endif
20 
21 #include "client.h"
22 #include "rostermanager.h"
23 #include "disco.h"
24 #include "logsink.h"
25 #include "nonsaslauth.h"
26 #include "tag.h"
27 #include "stanzaextensionfactory.h"
28 #include "stanzaextension.h"
29 #include "tlsbase.h"
30 
31 #if !defined( _WIN32 ) && !defined( _WIN32_WCE )
32 # include <unistd.h>
33 #endif
34 
35 #ifndef _WIN32_WCE
36 # include <iostream>
37 # include <sstream>
38 #else
39 # include <stdio.h>
40 #endif
41 
42 namespace gloox
43 {
44 
45  Client::Client( const std::string& server )
46  : ClientBase( XMLNS_CLIENT, server ),
47  m_rosterManager( 0 ), m_auth( 0 ),
48  m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
49  m_manageRoster( true ), m_doAuth( false ),
50  m_streamFeatures( 0 ), m_priority( 0 )
51  {
52  m_jid.setServer( server );
53  init();
54  }
55 
56  Client::Client( const JID& jid, const std::string& password, int port )
57  : ClientBase( XMLNS_CLIENT, password, "", port ),
58  m_rosterManager( 0 ), m_auth( 0 ),
59  m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
60  m_manageRoster( true ), m_doAuth( true ),
61  m_streamFeatures( 0 ), m_priority( 0 )
62  {
63  m_jid = jid;
64  m_server = m_jid.serverRaw();
65  init();
66  }
67 
68  Client::Client( const std::string& username, const std::string& password,
69  const std::string& server, const std::string& resource, int port )
70  : ClientBase( XMLNS_CLIENT, password, server, port ),
71  m_rosterManager( 0 ), m_auth( 0 ),
72  m_presence( PresenceAvailable ), m_resourceBound( false ), m_forceNonSasl( false ),
73  m_manageRoster( true ), m_doAuth( true ),
74  m_streamFeatures( 0 ), m_priority( 0 )
75  {
76  m_jid.setUsername( username );
77  m_jid.setServer( server );
78  m_jid.setResource( resource );
79 
80  init();
81  }
82 
84  {
86  delete m_rosterManager;
87  delete m_auth;
88  }
89 
90  void Client::init()
91  {
92  m_rosterManager = new RosterManager( this );
93  m_disco->setIdentity( "client", "bot" );
94  }
95 
96  void Client::setUsername( const std::string &username )
97  {
98  m_jid.setUsername( username );
99  m_doAuth = true;
100  }
101 
102  bool Client::handleNormalNode( Stanza *stanza )
103  {
104  if( stanza->name() == "stream:features" )
105  {
106  m_streamFeatures = getStreamFeatures( stanza );
107 
108  if( m_tls == TLSRequired && !m_encryptionActive
109  && ( !m_encryption || !( m_streamFeatures & StreamFeatureStartTls ) ) )
110  {
112  "Client is configured to require TLS but either the server didn't offer TLS or "
113  "TLS support is not compiled in." );
115  }
116  else if( m_tls > TLSDisabled && m_encryption && !m_encryptionActive
117  && ( m_streamFeatures & StreamFeatureStartTls ) )
118  {
119  notifyStreamEvent( StreamEventEncryption );
120  startTls();
121  }
122  else if( m_sasl )
123  {
124  if( m_authed )
125  {
126  if( m_streamFeatures & StreamFeatureBind )
127  {
128  notifyStreamEvent( StreamEventResourceBinding );
129  bindResource();
130  }
131  }
132  else if( m_doAuth && !username().empty() && !password().empty() )
133  {
134  if( !login() )
135  {
137  "the server doesn't support any auth mechanisms we know about" );
139  }
140  }
141  else if( m_doAuth && !m_clientCerts.empty() && !m_clientKey.empty()
142  && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal )
143  {
144  notifyStreamEvent( StreamEventAuthentication );
145  startSASL( SaslMechExternal );
146  }
147 #ifdef _WIN32
148  else if( m_doAuth && m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi )
149  {
150  notifyStreamEvent( StreamEventAuthentication );
151  startSASL( SaslMechGssapi );
152  }
153 #endif
154  else if( m_doAuth && m_streamFeatures & SaslMechAnonymous
155  && m_availableSaslMechs & SaslMechAnonymous )
156  {
157  notifyStreamEvent( StreamEventAuthentication );
158  startSASL( SaslMechAnonymous );
159  }
160  else
161  {
162  notifyStreamEvent( StreamEventFinished );
163  connected();
164  }
165  }
166  else if( m_compress && m_compression && !m_compressionActive
167  && ( m_streamFeatures & StreamFeatureCompressZlib ) )
168  {
169  notifyStreamEvent( StreamEventCompression );
170  negotiateCompression( StreamFeatureCompressZlib );
171  }
172 // else if( ( m_streamFeatures & StreamFeatureCompressDclz )
173 // && m_connection->initCompression( StreamFeatureCompressDclz ) )
174 // {
175 // negotiateCompression( StreamFeatureCompressDclz );
176 // }
177  else if( m_streamFeatures & StreamFeatureIqAuth )
178  {
179  notifyStreamEvent( StreamEventAuthentication );
180  nonSaslLogin();
181  }
182  else
183  {
185  "fallback: the server doesn't support any auth mechanisms we know about" );
187  }
188  }
189  else if( ( stanza->name() == "proceed" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_TLS ) )
190  {
191  logInstance().log( LogLevelDebug, LogAreaClassClient, "starting TLS handshake..." );
192 
193  if( m_encryption )
194  {
195  m_encryptionActive = true;
196  m_encryption->handshake();
197  }
198  }
199  else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_TLS ) )
200  {
201  logInstance().log( LogLevelError, LogAreaClassClient, "TLS handshake failed (server-side)!" );
203  }
204  else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_COMPRESSION ) )
205  {
206  logInstance().log( LogLevelError, LogAreaClassClient, "stream compression init failed!" );
208  }
209  else if( ( stanza->name() == "compressed" ) && stanza->hasAttribute( "xmlns", XMLNS_COMPRESSION ) )
210  {
211  logInstance().log( LogLevelDebug, LogAreaClassClient, "stream compression inited" );
212  m_compressionActive = true;
213  header();
214  }
215  else if( ( stanza->name() == "challenge" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
216  {
217  logInstance().log( LogLevelDebug, LogAreaClassClient, "processing SASL challenge" );
218  processSASLChallenge( stanza->cdata() );
219  }
220  else if( ( stanza->name() == "failure" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
221  {
222  logInstance().log( LogLevelError, LogAreaClassClient, "SASL authentication failed!" );
223  processSASLError( stanza );
225  }
226  else if( ( stanza->name() == "success" ) && stanza->hasAttribute( "xmlns", XMLNS_STREAM_SASL ) )
227  {
228  logInstance().log( LogLevelDebug, LogAreaClassClient, "SASL authentication successful" );
229  setAuthed( true );
230  header();
231  }
232  else
233  {
234  if( ( stanza->name() == "iq" ) && stanza->hasAttribute( "id", "bind" ) )
235  {
236  processResourceBind( stanza );
237  }
238  else if( ( stanza->name() == "iq" ) && stanza->hasAttribute( "id", "session" ) )
239  {
240  processCreateSession( stanza );
241  }
242  else
243  return false;
244  }
245 
246  return true;
247  }
248 
249  int Client::getStreamFeatures( Stanza *stanza )
250  {
251  if( stanza->name() != "stream:features" )
252  return 0;
253 
254  int features = 0;
255 
256  if( stanza->hasChild( "starttls", "xmlns", XMLNS_STREAM_TLS ) )
257  features |= StreamFeatureStartTls;
258 
259  if( stanza->hasChild( "mechanisms", "xmlns", XMLNS_STREAM_SASL ) )
260  features |= getSaslMechs( stanza->findChild( "mechanisms" ) );
261 
262  if( stanza->hasChild( "bind", "xmlns", XMLNS_STREAM_BIND ) )
263  features |= StreamFeatureBind;
264 
265  if( stanza->hasChild( "session", "xmlns", XMLNS_STREAM_SESSION ) )
266  features |= StreamFeatureSession;
267 
268  if( stanza->hasChild( "auth", "xmlns", XMLNS_STREAM_IQAUTH ) )
269  features |= StreamFeatureIqAuth;
270 
271  if( stanza->hasChild( "register", "xmlns", XMLNS_STREAM_IQREGISTER ) )
272  features |= StreamFeatureIqRegister;
273 
274  if( stanza->hasChild( "compression", "xmlns", XMLNS_STREAM_COMPRESS ) )
275  features |= getCompressionMethods( stanza->findChild( "compression" ) );
276 
277  if( features == 0 )
278  features = StreamFeatureIqAuth;
279 
280  return features;
281  }
282 
283  int Client::getSaslMechs( Tag *tag )
284  {
285  int mechs = SaslMechNone;
286 
287  if( tag->hasChildWithCData( "mechanism", "DIGEST-MD5" ) )
288  mechs |= SaslMechDigestMd5;
289 
290  if( tag->hasChildWithCData( "mechanism", "PLAIN" ) )
291  mechs |= SaslMechPlain;
292 
293  if( tag->hasChildWithCData( "mechanism", "ANONYMOUS" ) )
294  mechs |= SaslMechAnonymous;
295 
296  if( tag->hasChildWithCData( "mechanism", "EXTERNAL" ) )
297  mechs |= SaslMechExternal;
298 
299  if( tag->hasChildWithCData( "mechanism", "GSSAPI" ) )
300  mechs |= SaslMechGssapi;
301 
302  return mechs;
303  }
304 
305  int Client::getCompressionMethods( Tag *tag )
306  {
307  int meths = 0;
308 
309  if( tag->hasChildWithCData( "method", "zlib" ) )
310  meths |= StreamFeatureCompressZlib;
311 
312  if( tag->hasChildWithCData( "method", "lzw" ) )
313  meths |= StreamFeatureCompressDclz;
314 
315  return meths;
316  }
317 
319  {
320  bool retval = true;
321 
322  if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5
323  && !m_forceNonSasl )
324  {
325  notifyStreamEvent( StreamEventAuthentication );
326  startSASL( SaslMechDigestMd5 );
327  }
328  else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain
329  && !m_forceNonSasl )
330  {
331  notifyStreamEvent( StreamEventAuthentication );
332  startSASL( SaslMechPlain );
333  }
334  else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl )
335  {
336  notifyStreamEvent( StreamEventAuthentication );
337  nonSaslLogin();
338  }
339  else
340  retval = false;
341 
342  return retval;
343  }
344 
346  {
347  if( !m_resourceBound )
348  {
349  Tag *iq = new Tag( "iq" );
350  iq->addAttribute( "type", "set" );
351  iq->addAttribute( "id", "bind" );
352  Tag *b = new Tag( iq, "bind" );
353  b->addAttribute( "xmlns", XMLNS_STREAM_BIND );
354  if( !resource().empty() )
355  new Tag( b, "resource", resource() );
356 
357  send( iq );
358  }
359  }
360 
361  void Client::processResourceBind( Stanza *stanza )
362  {
363  switch( stanza->subtype() )
364  {
365  case StanzaIqResult:
366  {
367  Tag *bind = stanza->findChild( "bind" );
368  Tag *jid = bind->findChild( "jid" );
369  m_jid.setJID( jid->cdata() );
370  m_resourceBound = true;
371 
372  if( m_streamFeatures & StreamFeatureSession )
373  createSession();
374  else
375  connected();
376  break;
377  }
378  case StanzaIqError:
379  {
380  Tag *error = stanza->findChild( "error" );
381  if( stanza->hasChild( "error", "type", "modify" )
382  && error->hasChild( "bad-request", "xmlns", XMLNS_XMPP_STANZAS ) )
383  {
384  notifyOnResourceBindError( RbErrorBadRequest );
385  }
386  else if( stanza->hasChild( "error", "type", "cancel" ) )
387  {
388  if( error->hasChild( "not-allowed", "xmlns", XMLNS_XMPP_STANZAS ) )
389  notifyOnResourceBindError( RbErrorNotAllowed );
390  else if( error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
391  notifyOnResourceBindError( RbErrorConflict );
392  else
393  notifyOnResourceBindError( RbErrorUnknownError );
394  }
395  else
396  notifyOnResourceBindError( RbErrorUnknownError );
397  break;
398  }
399  default:
400  break;
401  }
402  }
403 
404  void Client::createSession()
405  {
406  notifyStreamEvent( StreamEventSessionCreation );
407  Tag *iq = new Tag( "iq" );
408  iq->addAttribute( "type", "set" );
409  iq->addAttribute( "id", "session" );
410  Tag *s = new Tag( iq, "session" );
411  s->addAttribute( "xmlns", XMLNS_STREAM_SESSION );
412 
413  send( iq );
414  }
415 
416  void Client::processCreateSession( Stanza *stanza )
417  {
418  switch( stanza->subtype() )
419  {
420  case StanzaIqResult:
421  {
422  connected();
423  break;
424  }
425  case StanzaIqError:
426  {
427  Tag *error = stanza->findChild( "error" );
428  if( stanza->hasChild( "error", "type", "wait" )
429  && error->hasChild( "internal-server-error", "xmlns", XMLNS_XMPP_STANZAS ) )
430  {
431  notifyOnSessionCreateError( ScErrorInternalServerError );
432  }
433  else if( stanza->hasChild( "error", "type", "auth" )
434  && error->hasChild( "forbidden", "xmlns", XMLNS_XMPP_STANZAS ) )
435  {
436  notifyOnSessionCreateError( ScErrorForbidden );
437  }
438  else if( stanza->hasChild( "error", "type", "cancel" )
439  && error->hasChild( "conflict", "xmlns", XMLNS_XMPP_STANZAS ) )
440  {
441  notifyOnSessionCreateError( ScErrorConflict );
442  }
443  else
444  notifyOnSessionCreateError( ScErrorUnknownError );
445  break;
446  }
447  default:
448  break;
449  }
450  }
451 
452  void Client::negotiateCompression( StreamFeature method )
453  {
454  Tag *t = new Tag( "compress" );
455  t->addAttribute( "xmlns", XMLNS_COMPRESSION );
456 
457  if( method == StreamFeatureCompressZlib )
458  new Tag( t, "method", "zlib" );
459 
460  if( method == StreamFeatureCompressDclz )
461  new Tag( t, "method", "lzw" );
462 
463  send( t );
464  }
465 
467  {
468  m_presenceExtensions.push_back( se );
469  }
470 
472  {
473  StanzaExtensionList::iterator it = m_presenceExtensions.begin();
474  for( ; it != m_presenceExtensions.end(); ++it )
475  {
476  delete (*it);
477  }
478  m_presenceExtensions.clear();
479  }
480 
481  void Client::setPresence( Presence presence, int priority, const std::string& status )
482  {
483  m_presence = presence;
484  m_status = status;
485 
486  if( priority < -128 )
487  m_priority = -128;
488  if( priority > 127 )
489  m_priority = 127;
490  else
491  m_priority = priority;
492 
493  sendPresence();
494  }
495 
497  {
498  m_manageRoster = false;
499  delete m_rosterManager;
500  m_rosterManager = 0;
501  }
502 
504  {
505  if( !m_auth )
506  m_auth = new NonSaslAuth( this );
507  m_auth->doAuth( m_sid );
508  }
509 
510  void Client::sendPresence()
511  {
512  if( m_presence != PresenceUnknown &&
513  state() >= StateConnected )
514  {
515  JID jid;
516  Stanza *p = Stanza::createPresenceStanza( jid, m_status, m_presence );
517 #ifdef _WIN32_WCE
518  char tmp[5];
519  tmp[4] = '\0';
520  sprintf( tmp, "%s", m_priority );
521  new Tag( p, "priority", tmp );
522 #else
523  std::ostringstream oss;
524  oss << m_priority;
525  new Tag( p, "priority", oss.str() );
526 #endif
527  StanzaExtensionList::const_iterator it = m_presenceExtensions.begin();
528  for( ; it != m_presenceExtensions.end(); ++it )
529  {
530  p->addChild( (*it)->tag() );
531  }
532 
533  send( p );
534  }
535  }
536 
537  void Client::connected()
538  {
539  if( m_authed )
540  {
541  if( m_manageRoster )
542  {
543  notifyStreamEvent( StreamEventRoster );
544  m_rosterManager->fill();
545  }
546  else
547  rosterFilled();
548  }
549  else
550  {
551  notifyStreamEvent( StreamEventFinished );
552  notifyOnConnect();
553  }
554  }
555 
556  void Client::rosterFilled()
557  {
558  sendPresence();
559  notifyStreamEvent( StreamEventFinished );
560  notifyOnConnect();
561  }
562 
564  {
566  }
567 
568  void Client::disconnect( ConnectionError reason )
569  {
570  m_resourceBound = false;
571  m_authed = false;
572  m_streamFeatures = 0;
573  ClientBase::disconnect( reason );
574  }
575 
576  void Client::cleanup()
577  {
578  m_authed = false;
579  m_resourceBound = false;
580  m_streamFeatures = 0;
581  }
582 
583 }