glooxd  0.3-svn
streambase.cpp
1 /*
2  Copyright (c) 2008-2009 by Jakob Schroeter <js@camaya.net>
3  This file is part of the glooxd library. http://camaya.net/glooxd
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 #include "glooxd.h"
15 #include "authenticationhandler.h"
16 #include "c2s.h"
17 #include "configmanager.h"
18 #include "streambase.h"
19 #include "streamfeature.h"
20 
21 #include <gloox/adhoc.h>
22 #include <gloox/base64.h>
23 #include <gloox/connectionbase.h>
24 #include <gloox/connectiontls.h>
25 #include <gloox/dataform.h>
26 #include <gloox/dataformfield.h>
27 #include <gloox/iq.h>
28 #include <gloox/jid.h>
29 #include <gloox/logsink.h>
30 #include <gloox/sha.h>
31 #include <gloox/tlsbase.h>
32 #include <gloox/util.h>
33 
34 #include <cstdio>
35 #include <ctime>
36 #include <stdio.h>
37 
38 namespace glooxd
39 {
40 
41  static const char* streamErrorValues[] = {
42  "bad-format",
43  "bad-namespace-prefix",
44  "conflict",
45  "connection-timeout",
46  "host-gone",
47  "host-unknown",
48  "improper-addressing",
49  "internal-server-error",
50  "invalid-from",
51  "invalid-id",
52  "invalid-namespace",
53  "invalid-xml",
54  "not-authorized",
55  "policy-violation",
56  "remote-connection-failed",
57  "resource-constraint",
58  "restricted-xml",
59  "see-other-host",
60  "system-shutdown",
61  "undefined-condition",
62  "unsupported-encoding",
63  "unsupported-stanza-type",
64  "unsupported-version",
65  "xml-not-well-formed"
66  };
67 
68  static inline const std::string streamErrorValue( gloox::StreamError se )
69  {
70  return gloox::util::lookup( se, streamErrorValues );
71  }
72 
73  StreamBase::StreamBase( C2S& parent, ConfigManager& cm, gloox::ConnectionBase* conn,
74  const gloox::LogSink& log )
75  : m_parent( parent ), m_cm( cm ),
76  m_connection( conn ), m_logInstance( log ),
77  m_parser( this, false ), m_exclusiveHandler( 0 ),
78  m_threading( false ), m_fakePresence( true ), m_strictXMPP( true )
79  {
80  if( m_connection )
81  {
82  gloox::SHA sha;
83  sha.feed( m_connection->server() );
84  sha.feed( gloox::util::int2string( m_connection->port() ) );
85  sha.feed( gloox::util::int2string( time( 0 ) ) );
86  m_id = sha.hex();
87  m_state = ( StateTransportConnected | StateReset );
88  m_connection->registerConnectionDataHandler( this );
89  m_logInstance.dbg( static_cast<gloox::LogArea>( LogAreaStreamBase ),
90  "Assigning ID: " + m_id );
91  }
92  else
93  m_state = StateDisconnected;
94  }
95 
97  {
98  printf( "in ~StreamBase\n" );
99  delete m_connection;
100  gloox::util::clearList( m_features );
101  gloox::util::clearList( m_incomingQueue );
102  gloox::util::clearList( m_outgoingQueue );
103  printf( "leaving ~StreamBase\n" );
104  }
105 
107  {
108  if( !m_threading && !m_incomingQueue.size()
109  && !m_outgoingQueue.size() )
110  return;
111 
112  do
113  {
114  m_queueSemaphore.wait();
115  if( m_incomingQueue.size() )
116  {
117  gloox::Tag* t = m_incomingQueue.front();
118  m_incomingQueueMutex.lock();
119  m_incomingQueue.pop_front();
120  m_incomingQueueMutex.unlock();
121  handleTag( t );
122  }
123  else if( m_outgoingQueue.size() )
124  {
125  gloox::Tag* t = m_outgoingQueue.front();
126  m_outgoingQueueMutex.lock();
127  m_outgoingQueue.pop_front();
128  m_outgoingQueueMutex.unlock();
129  send( t );
130  }
131  } while( m_threading );
132  }
133 
134  gloox::ConnectionError StreamBase::recv( int timeout )
135  {
136  if( m_connection )
137  return m_connection->recv( timeout );
138  else
139  return gloox::ConnNotConnected;
140  }
141 
142  void StreamBase::disconnect( gloox::ConnectionError e,
143  gloox::StreamError se,
144  const std::string& text )
145  {
146  if( m_fakePresence && !m_self.resource().empty() )
147  {
148  gloox::Tag* b = new gloox::Tag( "presence" );
149  b->addAttribute( "type", "unavailable" );
150  stampTag( b );
151  }
152 
153  if( se != gloox::StreamErrorUndefined )
154  {
155  gloox::Tag* st = 0;
156  if( m_state == ( StateTransportConnected | StateReset ) )
157  {
158  st = new gloox::Tag( "stream" );
159  st->setPrefix( "stream" );
160  st->setXmlns( gloox::XMLNS_STREAM, "stream" );
161  st->addAttribute( "version", "1.0" );
162  }
163  gloox::Tag* s = new gloox::Tag( st, "error" );
164  s->setPrefix( "stream" );
165  gloox::Tag* err = new gloox::Tag( s, streamErrorValue( se ) );
166  err->setXmlns( gloox::XMLNS_XMPP_STREAM );
167  if( !text.empty() )
168  {
169  gloox::Tag* txt = new gloox::Tag( err, "text", text );
170  txt->setXmlns( gloox::XMLNS_XMPP_STREAM );
171  }
172  if( st )
173  send( st );
174  else
175  {
176  send( s );
177  send( "</stream:stream>" );
178  }
179  }
180 
181 #ifdef DEBUG
182  printf( "disconnecting with reason %d\n", e );
183 #endif
184  int oldState = m_state;
185 
186  m_state = StateDisconnected;
187 
188  if( m_connection )
189  m_connection->disconnect();
190 
191  m_parent.disconnect( this );
192 
193  if( ( oldState & StateReallyDone ) == StateReallyDone )
194  m_cm.handleClientDisconnected( m_self, e, se );
195  }
196 
197  void StreamBase::handleReceivedData( const gloox::ConnectionBase* /*conn*/, const std::string& data )
198  {
199  if( m_state == StateDisconnected )
200  return;
201 
202  std::string copy = data;
203  int i = 0;
204  if( ( i = m_parser.feed( copy ) ) >= 0 )
205  {
206  m_logInstance.warn( static_cast<gloox::LogArea>( LogAreaStreamBase ),
207  "parse error (at pos "
208  + gloox::util::int2string( i )
209  + "): " + copy.c_str() );
210  disconnect( gloox::ConnParseError, gloox::StreamErrorXmlNotWellFormed, "syntax error" );
211  }
212  }
213 
214  void StreamBase::send( gloox::Tag* tag )
215  {
216  if( !tag )
217  return;
218 
219  send( tag->xml() );
220  delete tag;
221  }
222 
223  void StreamBase::send( const gloox::IQ& iq )
224  {
225  send( iq.tag() );
226  }
227 
228  void StreamBase::send( const std::string& xml )
229  {
230  if( m_state == StateDisconnected || !m_connection )
231  return;
232 
233  m_logInstance.dbg( gloox::LogAreaXmlOutgoing, xml );
234  m_connection->send( xml );
235  }
236 
238  gloox::ConnectionBase* conn )
239  {
240  m_state |= state;
241 
242  m_exclusiveHandler = 0;
243  if( conn )
244  m_connection = conn;
245  }
246 
247  const gloox::JID StreamBase::name() const
248  {
249  gloox::JID j = m_parent.name().bare();
250  j.setResource( m_id );
251  return j;
252  }
253 
254  void StreamBase::handleConnect( const gloox::ConnectionBase* /*conn*/ )
255  {
256 #ifdef DEBUG
257  printf( "should not happen\n" );
258 #endif
259  }
260 
261  void StreamBase::handleDisconnect( const gloox::ConnectionBase* /*conn*/, gloox::ConnectionError /*reason*/ )
262  {
263 #ifdef DEBUG
264  printf( "detected disconnect\n" );
265 #endif
266  disconnect( gloox::ConnUserDisconnected );
267  }
268 
269  void StreamBase::handleIncomingTag( gloox::Tag* tag )
270  {
271  if( !tag )
272  return;
273 
274  if( tag->name() == "disconnect" && tag->xmlns() == XMLNS_GLOOXD )
275  {
276  disconnect( gloox::ConnStreamError,
277  static_cast<gloox::StreamError>( strtol( tag->cdata().c_str(), 0, 10 ) ) );
278  delete tag;
279  return;
280  }
281  m_incomingQueueMutex.lock();
282  m_incomingQueue.push_back( tag );
283  m_incomingQueueMutex.unlock();
284  m_queueSemaphore.post();
285  }
286 
287  void StreamBase::handleOutgoingTag( gloox::Tag* tag )
288  {
289  if( !tag )
290  return;
291 
292  m_outgoingQueueMutex.lock();
293  m_outgoingQueue.push_back( tag );
294  m_outgoingQueueMutex.unlock();
295  m_queueSemaphore.post();
296  }
297 
298  void StreamBase::handleTag( gloox::Tag* tag )
299  {
300  if( !tag )
301  return;
302 
303  if( m_state == StateDisconnected )
304  {
305  delete tag;
306  return;
307  }
308 
309  m_logInstance.dbg( gloox::LogAreaXmlIncoming, tag->xml() );
310 
311  if( ( m_state & StateReallyDone ) == StateReallyDone ) // shortcut
312  {
313  stampTag( tag );
314  return;
315  }
316 
317  if( m_exclusiveHandler )
318  {
319  m_exclusiveHandler->handleTag( tag );
320  delete tag;
321  return;
322  }
323 
324  const std::string tname = tag->name(); // no reference!
325 
326  if( tname == "stream" && tag->xmlns() == gloox::XMLNS_STREAM )
327  {
328 #ifdef DEBUG
329  printf( "greeting\n" );
330 #endif
331  handleStreamGreeting( tag );
332  delete tag;
333  return;
334  }
335  else if( ( m_state & StateReallyDone ) != StateReallyDone )
336  {
337  FeatureList::const_iterator it = m_features.begin();
338  for( ; it != m_features.end(); ++it )
339  {
340  if( tag->findTag( (*it)->filterString() ) )
341  {
342  m_exclusiveHandler = (*it);
343  (*it)->handleTag( tag );
344  delete tag;
345  return;
346  }
347  }
348 
349  if( ( m_state & StateDone ) == StateDone )
350  {
351  m_cm.handleClientConnected( m_self, m_connection->localInterface(), m_connection->localPort(),
352  m_connection->server(), m_connection->port() );
353  m_state |= StateReallyDone;
354  gloox::util::clearList( m_features ); // not needed anymore
355  }
356  else
357  {
358  disconnect( gloox::ConnStreamError, gloox::StreamErrorNotAuthorized );
359  delete tag;
360  return;
361  }
362  }
363 
364  const std::string type = tag->findAttribute( gloox::TYPE ); // no reference!
365 
366  stampTag( tag );
367 
368  if( tname == "presence" && type == "unavailable" )
369  {
370  m_fakePresence = false;
371  disconnect( gloox::ConnUserDisconnected );
372  }
373  }
374 
375  void StreamBase::handleStreamGreeting( const gloox::Tag* tag )
376  {
377  if( !tag )
378  return;
379 
380  const std::string& to = tag->findAttribute( "to" );
381 
382  if( ( m_state & StateReset ) != StateReset )
383  {
384  disconnect( gloox::ConnStreamError, gloox::StreamErrorPolicyViolation );
385  return;
386  }
387 
388  if ( !tag->hasAttribute( "version", "1.0" ) )
389  {
390  if( m_strictXMPP )
391  {
392  m_logInstance.err( static_cast<gloox::LogArea>( LogAreaStreamBase ),
393  "StrictXMPP: Stream " + m_id + " does not send 'version' attribute."
394  " Disconnecting. See C2S::setStrictXMPP()." );
395  disconnect( gloox::ConnStreamError, gloox::StreamErrorUnsupportedVersion,
396  "This server mandates XMPP version 1.0 which your client does not seem to support." );
397  return;
398  }
399  else
400  {
401  m_logInstance.dbg( static_cast<gloox::LogArea>( LogAreaStreamBase ),
402  "StrictXMPP: Stream " + m_id + " does not send 'version' attribute."
403  " Ignoring. See C2S::setStrictXMPP()." );
404  }
405  }
406 
407  if( m_state == ( StateTransportConnected | StateReset ) )
408  {
409  if( !m_cm.checkDomain( to, m_connection->localInterface(),
410  m_connection->localPort() ) )
411  {
412  disconnect( gloox::ConnStreamError, gloox::StreamErrorHostUnknown );
413  return;
414  }
415  }
416  else if( m_self.server() != to )
417  {
418  disconnect( gloox::ConnStreamError, gloox::StreamErrorHostUnknown );
419  return;
420  }
421 
422  m_self.setServer( to );
423 
424  m_state &= ~StateReset;
425 
426  std::string reply = "<stream:stream xmlns:stream='";
427  reply += gloox::XMLNS_STREAM;
428  reply += "' xmlns='";
429  reply += gloox::XMLNS_CLIENT;
430  reply += "' from='";
431  reply += to;
432  reply += "' version='1.0' id='glooxd001'>";
433  send( reply );
434 
435  gloox::Tag* f = new gloox::Tag( "features" );
436  f->setPrefix( "stream" );
437  f->setXmlns( gloox::XMLNS_STREAM, "stream" );
438 
439  FeatureList::const_iterator it = m_features.begin();
440  for( ; it != m_features.end(); ++it )
441  f->addChild( (*it)->tag( m_state, to ) );
442 
443  send( f );
444  }
445 
446  void StreamBase::stampTag( gloox::Tag* tag )
447  {
448  if( !tag )
449  return;
450 
451  if( !tag->hasAttribute( "from" )
452  || tag->findAttribute( "from") != m_self.full() )
453  tag->addAttribute( "from", m_self.full() );
454 
455  routeTag( tag );
456  }
457 
458  void StreamBase::routeTag( gloox::Tag* tag )
459  {
460  if( !tag )
461  return;
462 
463  m_parent.handleIncomingTag( tag );
464  }
465 
466 }