gloox  1.0
clientbase.cpp
1 /*
2  Copyright (c) 2005-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 
14 
15 #include "config.h"
16 
17 #include "clientbase.h"
18 #include "connectionbase.h"
19 #include "tlsbase.h"
20 #include "compressionbase.h"
21 #include "connectiontcpclient.h"
22 #include "disco.h"
23 #include "messagesessionhandler.h"
24 #include "tag.h"
25 #include "iq.h"
26 #include "message.h"
27 #include "subscription.h"
28 #include "presence.h"
29 #include "connectionlistener.h"
30 #include "iqhandler.h"
31 #include "messagehandler.h"
32 #include "presencehandler.h"
33 #include "rosterlistener.h"
34 #include "subscriptionhandler.h"
35 #include "loghandler.h"
36 #include "taghandler.h"
37 #include "mucinvitationhandler.h"
38 #include "mucroom.h"
39 #include "jid.h"
40 #include "base64.h"
41 #include "error.h"
42 #include "md5.h"
43 #include "util.h"
44 #include "tlsdefault.h"
45 #include "compressionzlib.h"
46 #include "stanzaextensionfactory.h"
47 #include "eventhandler.h"
48 #include "event.h"
49 
50 #include <cstdlib>
51 #include <string>
52 #include <map>
53 #include <list>
54 #include <algorithm>
55 #include <cmath>
56 #include <ctime>
57 #include <cstdio>
58 
59 #include <string.h> // for memset()
60 
61 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
62 #include <tchar.h>
63 #endif
64 
65 namespace gloox
66 {
67 
68  // ---- ClientBase::Ping ----
69  ClientBase::Ping::Ping()
70  : StanzaExtension( ExtPing )
71  {
72  }
73 
74  ClientBase::Ping::~Ping()
75  {
76  }
77 
78  const std::string& ClientBase::Ping::filterString() const
79  {
80  static const std::string filter = "/iq/ping[@xmlns='" + XMLNS_XMPP_PING + "']";
81  return filter;
82  }
83  // ---- ~ClientBase::Ping ----
84 
85  // ---- ClientBase ----
86  ClientBase::ClientBase( const std::string& ns, const std::string& server, int port )
87  : m_connection( 0 ), m_encryption( 0 ), m_compression( 0 ), m_disco( 0 ), m_namespace( ns ),
88  m_xmllang( "en" ), m_server( server ), m_compressionActive( false ), m_encryptionActive( false ),
89  m_compress( true ), m_authed( false ), m_block( false ), m_sasl( true ), m_tls( TLSOptional ), m_port( port ),
90  m_availableSaslMechs( SaslMechAll ),
91  m_statisticsHandler( 0 ), m_mucInvitationHandler( 0 ),
92  m_messageSessionHandlerChat( 0 ), m_messageSessionHandlerGroupchat( 0 ),
93  m_messageSessionHandlerHeadline( 0 ), m_messageSessionHandlerNormal( 0 ),
94  m_parser( this ), m_seFactory( 0 ), m_authError( AuthErrorUndefined ),
95  m_streamError( StreamErrorUndefined ), m_streamErrorAppCondition( 0 ),
96  m_selectedSaslMech( SaslMechNone ), m_autoMessageSession( false )
97  {
98  init();
99  }
100 
101  ClientBase::ClientBase( const std::string& ns, const std::string& password,
102  const std::string& server, int port )
103  : m_connection( 0 ), m_encryption( 0 ), m_compression( 0 ), m_disco( 0 ), m_namespace( ns ),
104  m_password( password ),
105  m_xmllang( "en" ), m_server( server ), m_compressionActive( false ), m_encryptionActive( false ),
106  m_compress( true ), m_authed( false ), m_block( false ), m_sasl( true ), m_tls( TLSOptional ),
107  m_port( port ), m_availableSaslMechs( SaslMechAll ),
108  m_statisticsHandler( 0 ), m_mucInvitationHandler( 0 ),
109  m_messageSessionHandlerChat( 0 ), m_messageSessionHandlerGroupchat( 0 ),
110  m_messageSessionHandlerHeadline( 0 ), m_messageSessionHandlerNormal( 0 ),
111  m_parser( this ), m_seFactory( 0 ), m_authError( AuthErrorUndefined ),
112  m_streamError( StreamErrorUndefined ), m_streamErrorAppCondition( 0 ),
113  m_selectedSaslMech( SaslMechNone ), m_autoMessageSession( false )
114  {
115  init();
116  }
117 
118  void ClientBase::init()
119  {
120  if( !m_disco )
121  {
122  m_disco = new Disco( this );
123  m_disco->setVersion( "based on gloox", GLOOX_VERSION );
125  }
126 
127  registerStanzaExtension( new Error() );
128  registerStanzaExtension( new Ping() );
129  registerIqHandler( this, ExtPing );
130 
131  m_streamError = StreamErrorUndefined;
132  m_block = false;
133  memset( &m_stats, 0, sizeof( m_stats ) );
134  cleanup();
135  }
136 
138  {
139  delete m_connection;
140  delete m_encryption;
141  delete m_compression;
142  delete m_seFactory;
143  m_seFactory = 0; // to avoid usage when Disco gets deleted below
144  delete m_disco;
145  m_disco = 0;
146 
147  util::clearList( m_messageSessions );
148 
149  PresenceJidHandlerList::const_iterator it1 = m_presenceJidHandlers.begin();
150  for( ; it1 != m_presenceJidHandlers.end(); ++it1 )
151  delete (*it1).jid;
152  }
153 
155  {
157  return ConnNotConnected;
158 
159  return m_connection->recv( timeout );
160  }
161 
162  bool ClientBase::connect( bool block )
163  {
164  if( m_server.empty() )
165  return false;
166 
167  if( !m_connection )
168  m_connection = new ConnectionTCPClient( this, m_logInstance, m_server, m_port );
169 
171  return true;
172 
173  if( !m_encryption )
174  m_encryption = getDefaultEncryption();
175 
176  if( !m_compression )
177  m_compression = getDefaultCompression();
178 
179  m_logInstance.dbg( LogAreaClassClientbase, "This is gloox " + GLOOX_VERSION + ", connecting to "
180  + m_server + ":" + util::int2string( m_port ) + "..." );
181  m_block = block;
183  if( ret != ConnNoError )
184  return false;
185 
186  if( m_block )
188 
189  return true;
190  }
191 
193  {
194  if( !tag )
195  {
196  logInstance().dbg( LogAreaClassClientbase, "stream closed" );
198  return;
199  }
200 
201  logInstance().dbg( LogAreaXmlIncoming, tag->xml() );
202  ++m_stats.totalStanzasReceived;
203 
204  if( tag->name() == "stream" && tag->xmlns() == XMLNS_STREAM )
205  {
206  const std::string& version = tag->findAttribute( "version" );
207  if( !checkStreamVersion( version ) )
208  {
209  logInstance().dbg( LogAreaClassClientbase, "This server is not XMPP-compliant"
210  " (it does not send a 'version' attribute). Please fix it or try another one.\n" );
212  return;
213  }
214 
215  m_sid = tag->findAttribute( "id" );
216  handleStartNode();
217  }
218  else if( tag->name() == "error" && tag->xmlns() == XMLNS_STREAM )
219  {
220  handleStreamError( tag );
222  }
223  else
224  {
225  if( !handleNormalNode( tag ) )
226  {
227  if( tag->xmlns().empty() || tag->xmlns() == XMLNS_CLIENT )
228  {
229  if( tag->name() == "iq" )
230  {
231  IQ iq( tag );
232  m_seFactory->addExtensions( iq, tag );
233  notifyIqHandlers( iq );
234  ++m_stats.iqStanzasReceived;
235  }
236  else if( tag->name() == "message" )
237  {
238  Message msg( tag );
239  m_seFactory->addExtensions( msg, tag );
240  notifyMessageHandlers( msg );
241  ++m_stats.messageStanzasReceived;
242  }
243  else if( tag->name() == "presence" )
244  {
245  const std::string& type = tag->findAttribute( TYPE );
246  if( type == "subscribe" || type == "unsubscribe"
247  || type == "subscribed" || type == "unsubscribed" )
248  {
249  Subscription sub( tag );
250  m_seFactory->addExtensions( sub, tag );
251  notifySubscriptionHandlers( sub );
252  ++m_stats.s10nStanzasReceived;
253  }
254  else
255  {
256  Presence pres( tag );
257  m_seFactory->addExtensions( pres, tag );
258  notifyPresenceHandlers( pres );
259  ++m_stats.presenceStanzasReceived;
260  }
261  }
262  else
263  m_logInstance.err( LogAreaClassClientbase, "Received invalid stanza." );
264  }
265  else
266  {
267  notifyTagHandlers( tag );
268  }
269  }
270  }
271 
272  if( m_statisticsHandler )
273  m_statisticsHandler->handleStatistics( getStatistics() );
274  }
275 
276  void ClientBase::handleCompressedData( const std::string& data )
277  {
279  m_encryption->encrypt( data );
280  else if( m_connection )
281  m_connection->send( data );
282  else
283  m_logInstance.err( LogAreaClassClientbase, "Compression finished, but chain broken" );
284  }
285 
286  void ClientBase::handleDecompressedData( const std::string& data )
287  {
288  parse( data );
289  }
290 
291  void ClientBase::handleEncryptedData( const TLSBase* /*base*/, const std::string& data )
292  {
293  if( m_connection )
294  m_connection->send( data );
295  else
296  m_logInstance.err( LogAreaClassClientbase, "Encryption finished, but chain broken" );
297  }
298 
299  void ClientBase::handleDecryptedData( const TLSBase* /*base*/, const std::string& data )
300  {
302  m_compression->decompress( data );
303  else
304  parse( data );
305  }
306 
307  void ClientBase::handleHandshakeResult( const TLSBase* /*base*/, bool success, CertInfo &certinfo )
308  {
309  if( success )
310  {
311  if( !notifyOnTLSConnect( certinfo ) )
312  {
313  logInstance().err( LogAreaClassClientbase, "Server's certificate rejected!" );
315  }
316  else
317  {
318  logInstance().dbg( LogAreaClassClientbase, "connection encryption active" );
319  header();
320  }
321  }
322  else
323  {
324  logInstance().err( LogAreaClassClientbase, "TLS handshake failed!" );
326  }
327  }
328 
329  void ClientBase::handleReceivedData( const ConnectionBase* /*connection*/, const std::string& data )
330  {
332  m_encryption->decrypt( data );
333  else if( m_compression && m_compressionActive )
334  m_compression->decompress( data );
335  else
336  parse( data );
337  }
338 
339  void ClientBase::handleConnect( const ConnectionBase* /*connection*/ )
340  {
341  header();
342  }
343 
344  void ClientBase::handleDisconnect( const ConnectionBase* /*connection*/, ConnectionError reason )
345  {
346  if( m_connection )
348 
349  if( m_encryption )
351 
352  if( m_compression )
354 
355  m_encryptionActive = false;
356  m_compressionActive = false;
357 
358  notifyOnDisconnect( reason );
359  }
360 
362  {
364  return;
365 
366  if( reason != ConnTlsFailed )
367  send( "</stream:stream>" );
368 
371 
372  if( m_encryption )
374 
375  if( m_compression )
377 
378  m_encryptionActive = false;
379  m_compressionActive = false;
380 
381  notifyOnDisconnect( reason );
382  }
383 
384  void ClientBase::parse( const std::string& data )
385  {
386  std::string copy = data;
387  int i = 0;
388  if( ( i = m_parser.feed( copy ) ) >= 0 )
389  {
390  std::string error = "parse error (at pos ";
391  error += util::int2string( i );
392  error += "): ";
393  m_logInstance.err( LogAreaClassClientbase, error + copy );
394  Tag* e = new Tag( "stream:error" );
395  new Tag( e, "restricted-xml", "xmlns", XMLNS_XMPP_STREAM );
396  send( e );
398  }
399  }
400 
402  {
403  std::string head = "<?xml version='1.0' ?>";
404  head += "<stream:stream to='" + m_jid.server() + "' xmlns='" + m_namespace + "' ";
405  head += "xmlns:stream='http://etherx.jabber.org/streams' xml:lang='" + m_xmllang + "' ";
406  head += "version='" + XMPP_STREAM_VERSION_MAJOR + "." + XMPP_STREAM_VERSION_MINOR + "'>";
407  send( head );
408  }
409 
411  {
412 #if defined( HAVE_GNUTLS ) || defined( HAVE_OPENSSL ) || defined( HAVE_WINTLS )
413  return true;
414 #else
415  return false;
416 #endif
417  }
418 
420  {
421  send( new Tag( "starttls", XMLNS, XMLNS_STREAM_TLS ) );
422  }
423 
424  void ClientBase::setServer( const std::string &server )
425  {
426  m_server = server;
427  if( m_connection )
428  m_connection->setServer( server );
429  }
430 
431  void ClientBase::setClientCert( const std::string& clientKey, const std::string& clientCerts )
432  {
433  m_clientKey = clientKey;
434  m_clientCerts = clientCerts;
435  }
436 
438  {
439  m_selectedSaslMech = type;
440 
441  Tag* a = new Tag( "auth", XMLNS, XMLNS_STREAM_SASL );
442 
443  switch( type )
444  {
445  case SaslMechDigestMd5:
446  a->addAttribute( "mechanism", "DIGEST-MD5" );
447  break;
448  case SaslMechPlain:
449  {
450  a->addAttribute( "mechanism", "PLAIN" );
451 
452  std::string tmp;
453  if( m_authzid )
454  tmp += m_authzid.bare();
455 
456  tmp += '\0';
457  if( !m_authcid.empty() )
458  tmp += m_authcid;
459  else
460  tmp += m_jid.username();
461  tmp += '\0';
462  tmp += m_password;
463  a->setCData( Base64::encode64( tmp ) );
464  break;
465  }
466  case SaslMechAnonymous:
467  a->addAttribute( "mechanism", "ANONYMOUS" );
468  break;
469  case SaslMechExternal:
470  a->addAttribute( "mechanism", "EXTERNAL" );
472  break;
473  case SaslMechGssapi:
474  {
475 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
476  a->addAttribute( "mechanism", "GSSAPI" );
477 // The client calls GSS_Init_sec_context, passing in 0 for
478 // input_context_handle (initially) and a targ_name equal to output_name
479 // from GSS_Import_Name called with input_name_type of
480 // GSS_C_NT_HOSTBASED_SERVICE and input_name_string of
481 // "service@hostname" where "service" is the service name specified in
482 // the protocol's profile, and "hostname" is the fully qualified host
483 // name of the server. The client then responds with the resulting
484 // output_token.
485  std::string token;
486  a->setCData( Base64::encode64( token ) );
487 // etc... see gssapi-sasl-draft.txt
488 #else
490  "SASL GSSAPI is not supported on this platform. You should never see this." );
491 #endif
492  break;
493  }
494  case SaslMechNTLM:
495  {
496 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
497  a->addAttribute( "mechanism", "NTLM" );
498  SEC_WINNT_AUTH_IDENTITY identity, *ident = 0;
499  memset( &identity, 0, sizeof( identity ) );
500  if( m_jid.username().length() > 0 )
501  {
502  identity.User = (unsigned char*)m_jid.username().c_str();
503  identity.UserLength = (unsigned long)m_jid.username().length();
504  identity.Domain = (unsigned char*)m_ntlmDomain.c_str();
505  identity.DomainLength = (unsigned long)m_ntlmDomain.length();
506  identity.Password = (unsigned char*)m_password.c_str();
507  identity.PasswordLength = (unsigned long)m_password.length();
508  identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
509  ident = &identity;
510  }
511  AcquireCredentialsHandle( 0, _T( "NTLM" ), SECPKG_CRED_OUTBOUND, 0, ident, 0, 0, &m_credHandle, 0 );
512 #else
514  "SASL NTLM is not supported on this platform. You should never see this." );
515 #endif
516  break;
517  }
518  default:
519  break;
520  }
521 
522  send( a );
523  }
524 
525  void ClientBase::processSASLChallenge( const std::string& challenge )
526  {
527  Tag* t = new Tag( "response", XMLNS, XMLNS_STREAM_SASL );
528 
529  const std::string& decoded = Base64::decode64( challenge );
530 
531  switch( m_selectedSaslMech )
532  {
533  case SaslMechDigestMd5:
534  {
535  if( !decoded.compare( 0, 7, "rspauth" ) )
536  break;
537 
538  std::string realm;
539  std::string::size_type end = 0;
540  std::string::size_type pos = decoded.find( "realm=" );
541  if( pos != std::string::npos )
542  {
543  end = decoded.find( '"', pos + 7 );
544  realm = decoded.substr( pos + 7, end - ( pos + 7 ) );
545  }
546  else
547  realm = m_jid.server();
548 
549  pos = decoded.find( "nonce=" );
550  if( pos == std::string::npos )
551  return;
552 
553  end = decoded.find( '"', pos + 7 );
554  while( decoded[end-1] == '\\' )
555  end = decoded.find( '"', end + 1 );
556  std::string nonce = decoded.substr( pos + 7, end - ( pos + 7 ) );
557 
558  std::string cnonce;
559  char cn[4*8+1];
560  for( int i = 0; i < 4; ++i )
561  sprintf( cn + i*8, "%08x", rand() );
562  cnonce.assign( cn, 4*8 );
563 
564  MD5 md5;
565  md5.feed( m_jid.username() );
566  md5.feed( ":" );
567  md5.feed( realm );
568  md5.feed( ":" );
569  md5.feed( m_password );
570  md5.finalize();
571  const std::string& a1_h = md5.binary();
572  md5.reset();
573  md5.feed( a1_h );
574  md5.feed( ":" );
575  md5.feed( nonce );
576  md5.feed( ":" );
577  md5.feed( cnonce );
578  md5.finalize();
579  const std::string& a1 = md5.hex();
580  md5.reset();
581  md5.feed( "AUTHENTICATE:xmpp/" );
582  md5.feed( m_jid.server() );
583  md5.finalize();
584  const std::string& a2 = md5.hex();
585  md5.reset();
586  md5.feed( a1 );
587  md5.feed( ":" );
588  md5.feed( nonce );
589  md5.feed( ":00000001:" );
590  md5.feed( cnonce );
591  md5.feed( ":auth:" );
592  md5.feed( a2 );
593  md5.finalize();
594 
595  std::string response = "username=\"";
596  response += m_jid.username();
597  response += "\",realm=\"";
598  response += realm;
599  response += "\",nonce=\"";
600  response += nonce;
601  response += "\",cnonce=\"";
602  response += cnonce;
603  response += "\",nc=00000001,qop=auth,digest-uri=\"xmpp/";
604  response += m_jid.server();
605  response += "\",response=";
606  response += md5.hex();
607  response += ",charset=utf-8";
608 
609  if( m_authzid )
610  response += ",authzid=" + m_authzid.bare();
611 
612  t->setCData( Base64::encode64( response ) );
613 
614  break;
615  }
616  case SaslMechGssapi:
617 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
618  // see gssapi-sasl-draft.txt
619 #else
620  m_logInstance.err( LogAreaClassClientbase,
621  "Huh, received GSSAPI challenge?! This should have never happened!" );
622 #endif
623  break;
624  case SaslMechNTLM:
625  {
626 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
627  bool type1 = ( decoded.length() < 7 ) ? true : false;
628 
629  SecBuffer bufferIn = { type1 ? 0 : (unsigned long)decoded.length(),
630  SECBUFFER_TOKEN,
631  (void*)decoded.c_str() };
632  SecBufferDesc secIn = { 0, 1, &bufferIn };
633 
634  char buffer[4096];
635 
636  SecBuffer bufferOut = { sizeof( buffer ), SECBUFFER_TOKEN, buffer };
637  SecBufferDesc secOut = { 0, 1, &bufferOut };
638 
639  TimeStamp timestamp;
640  unsigned long contextAttr;
641 
642  SECURITY_STATUS status = InitializeSecurityContext( &m_credHandle, type1 ? 0 : &m_ctxtHandle,
643  0, ISC_REQ_MUTUAL_AUTH, 0, 0, &secIn, 0,
644  &m_ctxtHandle, &secOut, &contextAttr,
645  &timestamp );
646  std::string response;
647  if( SUCCEEDED( status ) )
648  {
649  response = std::string( (const char *)bufferOut.pvBuffer, bufferOut.cbBuffer );
650  }
651  else
652  {
654  "InitializeSecurityContext() failed, return value "
655  + util::int2string( status ) );
656  }
657 
658  t->setCData( Base64::encode64( response ) );
659 #else
660  m_logInstance.err( LogAreaClassClientbase,
661  "Huh, received NTLM challenge?! This should have never happened!" );
662 #endif
663  break;
664  }
665 
666  default:
667  // should never happen.
668  break;
669  }
670 
671  send( t );
672  }
673 
675  {
676  if( tag->hasChild( "aborted" ) )
677  m_authError = SaslAborted;
678  else if( tag->hasChild( "incorrect-encoding" ) )
679  m_authError = SaslIncorrectEncoding;
680  else if( tag->hasChild( "invalid-authzid" ) )
681  m_authError = SaslInvalidAuthzid;
682  else if( tag->hasChild( "invalid-mechanism" ) )
683  m_authError = SaslInvalidMechanism;
684  else if( tag->hasChild( "malformed-request" ) )
685  m_authError = SaslMalformedRequest;
686  else if( tag->hasChild( "mechanism-too-weak" ) )
687  m_authError = SaslMechanismTooWeak;
688  else if( tag->hasChild( "not-authorized" ) )
689  m_authError = SaslNotAuthorized;
690  else if( tag->hasChild( "temporary-auth-failure" ) )
691  m_authError = SaslTemporaryAuthFailure;
692 
693 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
694  if( m_selectedSaslMech == SaslMechNTLM )
695  {
696  FreeCredentialsHandle( &m_credHandle );
697  DeleteSecurityContext( &m_ctxtHandle );
698  }
699 #endif
700  }
701 
703  {
704 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
705  if( m_selectedSaslMech == SaslMechNTLM )
706  {
707  FreeCredentialsHandle( &m_credHandle );
708  DeleteSecurityContext( &m_ctxtHandle );
709  }
710 #endif
711  }
712 
713  void ClientBase::send( IQ& iq, IqHandler* ih, int context, bool del )
714  {
715  if( ih && ( iq.subtype() == IQ::Set || iq.subtype() == IQ::Get ) )
716  {
717  if( iq.id().empty() )
718  iq.setID( getID() );
719 
720  TrackStruct track;
721  track.ih = ih;
722  track.context = context;
723  track.del = del;
724  m_iqHandlerMapMutex.lock();
725  m_iqIDHandlers[iq.id()] = track;
726  m_iqHandlerMapMutex.unlock();
727  }
728 
729  send( iq );
730  }
731 
732  void ClientBase::send( const IQ& iq )
733  {
734  ++m_stats.iqStanzasSent;
735  Tag* tag = iq.tag();
736  addFrom( tag );
737  addNamespace( tag );
738  send( tag );
739  }
740 
741  void ClientBase::send( const Message& msg )
742  {
743  ++m_stats.messageStanzasSent;
744  Tag* tag = msg.tag();
745  addFrom( tag );
746  addNamespace( tag );
747  send( tag );
748  }
749 
750  void ClientBase::send( const Subscription& sub )
751  {
752  ++m_stats.s10nStanzasSent;
753  Tag* tag = sub.tag();
754  addFrom( tag );
755  addNamespace( tag );
756  send( tag );
757  }
758 
760  {
761  ++m_stats.presenceStanzasSent;
762  Tag* tag = pres.tag();
763  StanzaExtensionList::const_iterator it = m_presenceExtensions.begin();
764  for( ; it != m_presenceExtensions.end(); ++it )
765  tag->addChild( (*it)->tag() );
766  addFrom( tag );
767  addNamespace( tag );
768  send( tag );
769  }
770 
771  void ClientBase::send( Tag* tag )
772  {
773  if( !tag )
774  return;
775 
776  send( tag->xml() );
777 
778  ++m_stats.totalStanzasSent;
779 
780  if( m_statisticsHandler )
781  m_statisticsHandler->handleStatistics( getStatistics() );
782 
783  delete tag;
784  }
785 
786  void ClientBase::send( const std::string& xml )
787  {
789  {
791  m_compression->compress( xml );
792  else if( m_encryption && m_encryptionActive )
793  m_encryption->encrypt( xml );
794  else
795  m_connection->send( xml );
796 
798  }
799  }
800 
801  void ClientBase::addFrom( Tag* tag )
802  {
803  if( !m_authed /*for IQ Auth */ || !tag || tag->hasAttribute( "from" ) )
804  return;
805 
806  if ( m_selectedResource.empty() )
807  tag->addAttribute( "from", m_jid.bare() );
808  else
809  tag->addAttribute( "from", m_jid.bare() + '/' + m_selectedResource );
810  }
811 
812  void ClientBase::addNamespace( Tag* tag )
813  {
814  if( !tag || !tag->xmlns().empty() )
815  return;
816 
817  tag->setXmlns( m_namespace );
818  }
819 
821  {
822  if( !m_seFactory )
823  m_seFactory = new StanzaExtensionFactory();
824 
825  m_seFactory->registerExtension( ext );
826  }
827 
829  {
830  if( !m_seFactory )
831  return false;
832 
833  return m_seFactory->removeExtension( ext );
834  }
835 
837  {
838  if( m_connection )
840 
841  return m_stats;
842  }
843 
845  {
847  }
848 
850  {
851  send( " " );
852  }
853 
854  void ClientBase::xmppPing( const JID& to, EventHandler* eh )
855  {
856  const std::string& id = getID();
857  IQ iq( IQ::Get, to, id );
858  iq.addExtension( new Ping() );
859  m_dispatcher.registerEventHandler( eh, id );
860  send( iq, this, XMPPPing );
861  }
862 
863  bool ClientBase::handleIq( const IQ& iq )
864  {
865  const Ping* p = iq.findExtension<Ping>( ExtPing );
866  if( !p || iq.subtype() != IQ::Get )
867  return false;
868 
869  m_dispatcher.dispatch( Event( Event::PingPing, iq ) );
870  IQ re( IQ::Result, iq.from(), iq.id() );
871  send( re );
872 
873  return true;
874  }
875 
876  void ClientBase::handleIqID( const IQ& iq, int context )
877  {
878  if( context == XMPPPing )
879  m_dispatcher.dispatch( Event( ( iq.subtype() == IQ::Result ) ? Event::PingPong
880  : Event::PingError, iq ),
881  iq.id(), true );
882  else
883  handleIqIDForward( iq, context );
884  }
885 
886  const std::string ClientBase::getID()
887  {
888  static unsigned int uniqueBaseID = (unsigned int)time( 0 );
889  char r[21+1];
890  sprintf( r, "uid:%08x:%08x", uniqueBaseID, rand() );
891  std::string ret( r, 21 );
892  return ret;
893  }
894 
895  bool ClientBase::checkStreamVersion( const std::string& version )
896  {
897  if( version.empty() )
898  return false;
899 
900  int major = 0;
901  int minor = 0;
902  int myMajor = atoi( XMPP_STREAM_VERSION_MAJOR.c_str() );
903 
904  size_t dot = version.find( '.' );
905  if( !version.empty() && dot && dot != std::string::npos )
906  {
907  major = atoi( version.substr( 0, dot ).c_str() );
908  minor = atoi( version.substr( dot ).c_str() );
909  }
910 
911  return myMajor >= major;
912  }
913 
915  {
916  if( m_connection )
917  {
918  delete m_connection;
919  }
920  m_connection = cb;
921  }
922 
924  {
925  if( m_encryption )
926  {
927  delete m_encryption;
928  }
929  m_encryption = tb;
930  }
931 
933  {
934  if( m_compression )
935  {
936  delete m_compression;
937  }
938  m_compression = cb;
939  }
940 
941  void ClientBase::handleStreamError( Tag* tag )
942  {
944  const TagList& c = tag->children();
945  TagList::const_iterator it = c.begin();
946  for( ; it != c.end(); ++it )
947  {
948  const std::string& name = (*it)->name();
949  if( name == "bad-format" )
950  err = StreamErrorBadFormat;
951  else if( name == "bad-namespace-prefix" )
953  else if( name == "conflict" )
954  err = StreamErrorConflict;
955  else if( name == "connection-timeout" )
957  else if( name == "host-gone" )
958  err = StreamErrorHostGone;
959  else if( name == "host-unknown" )
961  else if( name == "improper-addressing" )
963  else if( name == "internal-server-error" )
965  else if( name == "invalid-from" )
967  else if( name == "invalid-id" )
968  err = StreamErrorInvalidId;
969  else if( name == "invalid-namespace" )
971  else if( name == "invalid-xml" )
972  err = StreamErrorInvalidXml;
973  else if( name == "not-authorized" )
975  else if( name == "policy-violation" )
977  else if( name == "remote-connection-failed" )
979  else if( name == "resource-constraint" )
981  else if( name == "restricted-xml" )
983  else if( name == "see-other-host" )
984  {
986  m_streamErrorCData = tag->findChild( "see-other-host" )->cdata();
987  }
988  else if( name == "system-shutdown" )
990  else if( name == "undefined-condition" )
992  else if( name == "unsupported-encoding" )
994  else if( name == "unsupported-stanza-type" )
996  else if( name == "unsupported-version" )
998  else if( name == "xml-not-well-formed" )
1000  else if( name == "text" )
1001  {
1002  const std::string& lang = (*it)->findAttribute( "xml:lang" );
1003  if( !lang.empty() )
1004  m_streamErrorText[lang] = (*it)->cdata();
1005  else
1006  m_streamErrorText["default"] = (*it)->cdata();
1007  }
1008  else
1009  m_streamErrorAppCondition = (*it);
1010 
1011  if( err != StreamErrorUndefined && (*it)->hasAttribute( XMLNS, XMLNS_XMPP_STREAM ) )
1012  m_streamError = err;
1013  }
1014  }
1015 
1016  const std::string& ClientBase::streamErrorText( const std::string& lang ) const
1017  {
1018  StringMap::const_iterator it = m_streamErrorText.find( lang );
1019  return ( it != m_streamErrorText.end() ) ? (*it).second : EmptyString;
1020  }
1021 
1023  {
1024  if( types & Message::Chat || types == 0 )
1025  m_messageSessionHandlerChat = msh;
1026 
1027  if( types & Message::Normal || types == 0 )
1028  m_messageSessionHandlerNormal = msh;
1029 
1030  if( types & Message::Groupchat || types == 0 )
1031  m_messageSessionHandlerGroupchat = msh;
1032 
1033  if( types & Message::Headline || types == 0 )
1034  m_messageSessionHandlerHeadline = msh;
1035  }
1036 
1038  {
1039  if( ph )
1040  m_presenceHandlers.push_back( ph );
1041  }
1042 
1044  {
1045  if( ph )
1046  m_presenceHandlers.remove( ph );
1047  }
1048 
1050  {
1051  if( ph && jid )
1052  {
1053  JidPresHandlerStruct jph;
1054  jph.jid = new JID( jid.bare() );
1055  jph.ph = ph;
1056  m_presenceJidHandlers.push_back( jph );
1057  }
1058  }
1059 
1061  {
1062  PresenceJidHandlerList::iterator t;
1063  PresenceJidHandlerList::iterator it = m_presenceJidHandlers.begin();
1064  while( it != m_presenceJidHandlers.end() )
1065  {
1066  t = it;
1067  ++it;
1068  if( ( !ph || (*t).ph == ph ) && (*t).jid->bare() == jid.bare() )
1069  {
1070  delete (*t).jid;
1071  m_presenceJidHandlers.erase( t );
1072  }
1073  }
1074  }
1075 
1077  {
1078  IqTrackMap::iterator t;
1079  m_iqHandlerMapMutex.lock();
1080  IqTrackMap::iterator it = m_iqIDHandlers.begin();
1081  while( it != m_iqIDHandlers.end() )
1082  {
1083  t = it;
1084  ++it;
1085  if( ih == (*t).second.ih )
1086  m_iqIDHandlers.erase( t );
1087  }
1088  m_iqHandlerMapMutex.unlock();
1089  }
1090 
1091  void ClientBase::registerIqHandler( IqHandler* ih, int exttype )
1092  {
1093  if( !ih )
1094  return;
1095 
1096  typedef IqHandlerMap::const_iterator IQci;
1097  std::pair<IQci, IQci> g = m_iqExtHandlers.equal_range( exttype );
1098  for( IQci it = g.first; it != g.second; ++it )
1099  if( (*it).second == ih )
1100  return;
1101 
1102  m_iqExtHandlers.insert( std::make_pair( exttype, ih ) );
1103  }
1104 
1105  void ClientBase::removeIqHandler( IqHandler* ih, int exttype )
1106  {
1107  if( !ih )
1108  return;
1109 
1110  typedef IqHandlerMap::iterator IQi;
1111  std::pair<IQi, IQi> g = m_iqExtHandlers.equal_range( exttype );
1112  IQi it2;
1113  IQi it = g.first;
1114  while( it != g.second )
1115  {
1116  it2 = it++;
1117  if( (*it2).second == ih )
1118  m_iqExtHandlers.erase( it2 );
1119  }
1120  }
1121 
1123  {
1124  if( session )
1125  m_messageSessions.push_back( session );
1126  }
1127 
1129  {
1130  if( !session )
1131  return;
1132 
1133  MessageSessionList::iterator it = std::find( m_messageSessions.begin(),
1134  m_messageSessions.end(),
1135  session );
1136  if( it != m_messageSessions.end() )
1137  {
1138  delete (*it);
1139  m_messageSessions.erase( it );
1140  }
1141  }
1142 
1144  {
1145  if( mh )
1146  m_messageHandlers.push_back( mh );
1147  }
1148 
1150  {
1151  if( mh )
1152  m_messageHandlers.remove( mh );
1153  }
1154 
1156  {
1157  if( sh )
1158  m_subscriptionHandlers.push_back( sh );
1159  }
1160 
1162  {
1163  if( sh )
1164  m_subscriptionHandlers.remove( sh );
1165  }
1166 
1167  void ClientBase::registerTagHandler( TagHandler* th, const std::string& tag, const std::string& xmlns )
1168  {
1169  if( th && !tag.empty() )
1170  {
1171  TagHandlerStruct ths;
1172  ths.tag = tag;
1173  ths.xmlns = xmlns;
1174  ths.th = th;
1175  m_tagHandlers.push_back( ths );
1176  }
1177  }
1178 
1179  void ClientBase::removeTagHandler( TagHandler* th, const std::string& tag, const std::string& xmlns )
1180  {
1181  if( th )
1182  {
1183  TagHandlerList::iterator it = m_tagHandlers.begin();
1184  for( ; it != m_tagHandlers.end(); ++it )
1185  {
1186  if( (*it).th == th && (*it).tag == tag && (*it).xmlns == xmlns )
1187  m_tagHandlers.erase( it );
1188  }
1189  }
1190  }
1191 
1193  {
1194  if( sh )
1195  m_statisticsHandler = sh;
1196  }
1197 
1199  {
1200  m_statisticsHandler = 0;
1201  }
1202 
1204  {
1205  if( mih )
1206  {
1207  m_mucInvitationHandler = mih;
1209  }
1210  }
1211 
1213  {
1214  m_mucInvitationHandler = 0;
1216  }
1217 
1219  {
1220  if( cl )
1221  m_connectionListeners.push_back( cl );
1222  }
1223 
1225  {
1226  if( cl )
1227  m_connectionListeners.remove( cl );
1228  }
1229 
1231  {
1232  util::ForEach( m_connectionListeners, &ConnectionListener::onConnect );
1233  }
1234 
1235  void ClientBase::notifyOnDisconnect( ConnectionError e )
1236  {
1237  util::ForEach( m_connectionListeners, &ConnectionListener::onDisconnect, e );
1238  init();
1239  }
1240 
1242  {
1243  ConnectionListenerList::const_iterator it = m_connectionListeners.begin();
1244  for( ; it != m_connectionListeners.end() && (*it)->onTLSConnect( info ); ++it )
1245  ;
1246  return m_stats.encryption = ( it == m_connectionListeners.end() );
1247  }
1248 
1250  {
1251  util::ForEach( m_connectionListeners, &ConnectionListener::onResourceBindError, error );
1252  }
1253 
1254  void ClientBase::notifyOnResourceBind( const std::string& resource )
1255  {
1256  util::ForEach( m_connectionListeners, &ConnectionListener::onResourceBind, resource );
1257  }
1258 
1260  {
1261  util::ForEach( m_connectionListeners, &ConnectionListener::onSessionCreateError, error );
1262  }
1263 
1265  {
1266  util::ForEach( m_connectionListeners, &ConnectionListener::onStreamEvent, event );
1267  }
1268 
1269  void ClientBase::notifyPresenceHandlers( Presence& pres )
1270  {
1271  bool match = false;
1272  PresenceJidHandlerList::const_iterator t;
1273  PresenceJidHandlerList::const_iterator itj = m_presenceJidHandlers.begin();
1274  while( itj != m_presenceJidHandlers.end() )
1275  {
1276  t = itj++;
1277  if( (*t).jid->bare() == pres.from().bare() && (*t).ph )
1278  {
1279  (*t).ph->handlePresence( pres );
1280  match = true;
1281  }
1282  }
1283  if( match )
1284  return;
1285 
1286  // FIXME remove this for() for 1.1:
1287  PresenceHandlerList::const_iterator it = m_presenceHandlers.begin();
1288  for( ; it != m_presenceHandlers.end(); ++it )
1289  {
1290  (*it)->handlePresence( pres );
1291  }
1292  // FIXME and reinstantiate this:
1293 // util::ForEach( m_presenceHandlers, &PresenceHandler::handlePresence, pres );
1294  }
1295 
1296  void ClientBase::notifySubscriptionHandlers( Subscription& s10n )
1297  {
1298  // FIXME remove this for() for 1.1:
1299  SubscriptionHandlerList::const_iterator it = m_subscriptionHandlers.begin();
1300  for( ; it != m_subscriptionHandlers.end(); ++it )
1301  {
1302  (*it)->handleSubscription( s10n );
1303  }
1304  // FIXME and reinstantiate this:
1305 // util::ForEach( m_subscriptionHandlers, &SubscriptionHandler::handleSubscription, s10n );
1306  }
1307 
1308  void ClientBase::notifyIqHandlers( IQ& iq )
1309  {
1310  m_iqHandlerMapMutex.lock();
1311  IqTrackMap::iterator it_id = m_iqIDHandlers.find( iq.id() );
1312  m_iqHandlerMapMutex.unlock();
1313  if( it_id != m_iqIDHandlers.end() && iq.subtype() & ( IQ::Result | IQ::Error ) )
1314  {
1315  (*it_id).second.ih->handleIqID( iq, (*it_id).second.context );
1316  if( (*it_id).second.del )
1317  delete (*it_id).second.ih;
1318  m_iqHandlerMapMutex.lock();
1319  m_iqIDHandlers.erase( it_id );
1320  m_iqHandlerMapMutex.unlock();
1321  return;
1322  }
1323 
1324  if( iq.extensions().empty() )
1325  return;
1326 
1327  bool res = false;
1328 
1329  // FIXME remove for 1.1
1330 // typedef IqHandlerMapXmlns::const_iterator IQciXmlns
1331 // Tag *tag = iq.tag()->xmlns();
1332 // std::pair<IQciXmlns, IQciXmlns> g = m_iqNSHandlers.equal_range( tag->xmlns() );
1333 // for( IQciXmlns it = g.first; it != g.second; ++it )
1334 // {
1335 // if( (*it).second->handleIq( iq ) )
1336 // res = true;
1337 // }
1338 // delete tag;
1339 
1340  typedef IqHandlerMap::const_iterator IQci;
1341  const StanzaExtensionList& sel = iq.extensions();
1342  StanzaExtensionList::const_iterator itse = sel.begin();
1343  for( ; itse != sel.end(); ++itse )
1344  {
1345  std::pair<IQci, IQci> g = m_iqExtHandlers.equal_range( (*itse)->extensionType() );
1346  for( IQci it = g.first; it != g.second; ++it )
1347  {
1348  if( (*it).second->handleIq( iq ) )
1349  res = true;
1350  }
1351  }
1352 
1353  if( !res && iq.subtype() & ( IQ::Get | IQ::Set ) )
1354  {
1355  IQ re( IQ::Error, iq.from(), iq.id() );
1356  re.addExtension( new Error( StanzaErrorTypeCancel, StanzaErrorServiceUnavailable ) );
1357  send( re );
1358  }
1359  }
1360 
1361  void ClientBase::notifyMessageHandlers( Message& msg )
1362  {
1363  if( m_mucInvitationHandler )
1364  {
1365  const MUCRoom::MUCUser* mu = msg.findExtension<MUCRoom::MUCUser>( ExtMUCUser );
1366  if( mu && mu->operation() != MUCRoom::OpInviteTo )
1367  {
1368 
1369  m_mucInvitationHandler->handleMUCInvitation( msg.from(),
1370  mu->jid() ? JID( *(mu->jid()) ) : JID(),
1371  mu->reason() ? *(mu->reason()) : EmptyString,
1372  msg.body(),
1373  mu->password() ? *(mu->password()) : EmptyString,
1374  mu->continued(),
1375  mu->thread() ? *(mu->thread()) : EmptyString );
1376  return;
1377  }
1378  }
1379 
1380  MessageSessionList::const_iterator it1 = m_messageSessions.begin();
1381  for( ; it1 != m_messageSessions.end(); ++it1 )
1382  {
1383  if( (*it1)->target().full() == msg.from().full() &&
1384  ( msg.thread().empty()
1385  || (*it1)->threadID() == msg.thread()
1386  || (*it1)->honorThreadID() ) &&
1387 // FIXME don't use '== 0' here
1388  ( (*it1)->types() & msg.subtype() || (*it1)->types() == 0 ) )
1389  {
1390  (*it1)->handleMessage( msg );
1391  return;
1392  }
1393  }
1394 
1395  it1 = m_messageSessions.begin();
1396  for( ; it1 != m_messageSessions.end(); ++it1 )
1397  {
1398  if( (*it1)->target().bare() == msg.from().bare() &&
1399  ( msg.thread().empty()
1400  || (*it1)->threadID() == msg.thread()
1401  || (*it1)->honorThreadID() ) &&
1402 // FIXME don't use '== 0' here
1403  ( (*it1)->types() & msg.subtype() || (*it1)->types() == 0 ) )
1404  {
1405  (*it1)->handleMessage( msg );
1406  return;
1407  }
1408  }
1409 
1410  MessageSessionHandler* msHandler = 0;
1411 
1412  switch( msg.subtype() )
1413  {
1414  case Message::Chat:
1415  msHandler = m_messageSessionHandlerChat;
1416  break;
1417  case Message::Normal:
1418  msHandler = m_messageSessionHandlerNormal;
1419  break;
1420  case Message::Groupchat:
1421  msHandler = m_messageSessionHandlerGroupchat;
1422  break;
1423  case Message::Headline:
1424  msHandler = m_messageSessionHandlerHeadline;
1425  break;
1426  default:
1427  break;
1428  }
1429 
1430  if( msHandler )
1431  {
1432  if( msg.subtype() == Message::Chat && msg.body().empty() )
1433  return; // don't want a new MS for empty messages
1434  MessageSession* session = new MessageSession( this, msg.from(), true, msg.subtype() );
1435  msHandler->handleMessageSession( session );
1436  session->handleMessage( msg );
1437  }
1438  else
1439  {
1440  // FIXME remove this for() for 1.1:
1441  MessageHandlerList::const_iterator it = m_messageHandlers.begin();
1442  for( ; it != m_messageHandlers.end(); ++it )
1443  {
1444  (*it)->handleMessage( msg );
1445  }
1446  // FIXME and reinstantiate this:
1447 // util::ForEach( m_messageHandlers, &MessageHandler::handleMessage, msg ); // FIXME remove for 1.1
1448  }
1449  }
1450 
1451  void ClientBase::notifyTagHandlers( Tag* tag )
1452  {
1453  TagHandlerList::const_iterator it = m_tagHandlers.begin();
1454  for( ; it != m_tagHandlers.end(); ++it )
1455  {
1456  if( (*it).tag == tag->name() && tag->hasAttribute( XMLNS, (*it).xmlns ) )
1457  (*it).th->handleTag( tag );
1458  }
1459  }
1460 
1462  {
1463  if( !se )
1464  return;
1465 
1467  m_presenceExtensions.push_back( se );
1468  }
1469 
1471  {
1472  StanzaExtensionList::iterator it = m_presenceExtensions.begin();
1473  for( ; it != m_presenceExtensions.end(); ++it )
1474  {
1475  if( (*it)->extensionType() == type )
1476  {
1477  delete (*it);
1478  m_presenceExtensions.erase( it );
1479  return true;
1480  }
1481  }
1482 
1483  return false;
1484  }
1485 
1486  CompressionBase* ClientBase::getDefaultCompression()
1487  {
1488  if( !m_compress )
1489  return 0;
1490 
1491 #ifdef HAVE_ZLIB
1492  CompressionBase* cmp = new CompressionZlib( this );
1493  if( cmp->init() )
1494  return cmp;
1495 
1496  delete cmp;
1497 #endif
1498  return 0;
1499  }
1500 
1501  TLSBase* ClientBase::getDefaultEncryption()
1502  {
1503  if( m_tls == TLSDisabled || !hasTls() )
1504  return 0;
1505 
1506  TLSDefault* tls = new TLSDefault( this, m_server );
1507  if( tls->init( m_clientKey, m_clientCerts, m_cacerts ) )
1508  return tls;
1509  else
1510  {
1511  delete tls;
1512  return 0;
1513  }
1514  }
1515 
1516 }