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