]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/transport_session.c
183d976574aeea3eb05067bbbe2c0a109ccc9fe9
[OpenSRF.git] / src / libopensrf / transport_session.c
1 #include <opensrf/transport_session.h>
2
3 /**
4         @file transport_session.c
5         @brief Routines to manage a connection to a Jabber server.
6
7         In all cases, a transport_session acts as a client with regard to Jabber.
8 */
9
10 #define CONNECTING_1 1   /**< just starting the connection to Jabber */
11 #define CONNECTING_2 2   /**< XML stream opened but not yet logged in */
12
13
14 /* Note. these are growing buffers, so all that's necessary is a sane starting point */
15 #define JABBER_BODY_BUFSIZE    4096  /**< buffer size for message body */
16 #define JABBER_SUBJECT_BUFSIZE   64  /**< buffer size for message subject */
17 #define JABBER_THREAD_BUFSIZE    64  /**< buffer size for message thread */
18 #define JABBER_JID_BUFSIZE       64  /**< buffer size for various ids */
19 #define JABBER_STATUS_BUFSIZE    16  /**< buffer size for status code */
20
21 // ---------------------------------------------------------------------------------
22 // Jabber state machine.  This is how we know where we are in the Jabber
23 // conversation.
24 // ---------------------------------------------------------------------------------
25 struct jabber_state_machine_struct {
26         int connected;
27         int connecting;
28         int in_message;
29         int in_message_body;
30         int in_thread;
31         int in_subject;
32         int in_error;
33         int in_message_error;
34         int in_iq;
35         int in_presence;
36         int in_status;
37 };
38
39 // ---------------------------------------------------------------------------------
40 // Callback for handling the startElement event.  Much of the jabber logic occurs
41 // in this and the characterHandler callbacks.
42 // Here we check for the various top level jabber elements: body, iq, etc.
43 // ---------------------------------------------------------------------------------
44 static void startElementHandler(
45                 void *session, const xmlChar *name, const xmlChar **atts);
46
47 // ---------------------------------------------------------------------------------
48 // Callback for handling the endElement event.  Updates the Jabber state machine
49 // to let us know the element is over.
50 // ---------------------------------------------------------------------------------
51 static void endElementHandler( void *session, const xmlChar *name);
52
53 // ---------------------------------------------------------------------------------
54 // This is where we extract XML text content.  In particular, this is useful for
55 // extracting Jabber message bodies.
56 // ---------------------------------------------------------------------------------
57 static void characterHandler(
58                 void *session, const xmlChar *ch, int len);
59
60 static void parseWarningHandler( void *session, const char* msg, ... );
61 static void parseErrorHandler( void *session, const char* msg, ... );
62
63 // ---------------------------------------------------------------------------------
64 // Tells the SAX parser which functions will be used as event callbacks
65 // ---------------------------------------------------------------------------------
66 static xmlSAXHandler SAXHandlerStruct = {
67    NULL,                                                        /* internalSubset */
68    NULL,                                                        /* isStandalone */
69    NULL,                                                        /* hasInternalSubset */
70    NULL,                                                        /* hasExternalSubset */
71    NULL,                                                        /* resolveEntity */
72    NULL,                                                        /* getEntity */
73    NULL,                                                        /* entityDecl */
74    NULL,                                                        /* notationDecl */
75    NULL,                                                        /* attributeDecl */
76    NULL,                                                        /* elementDecl */
77    NULL,                                                        /* unparsedEntityDecl */
78    NULL,                                                        /* setDocumentLocator */
79    NULL,                                                        /* startDocument */
80    NULL,                                                        /* endDocument */
81    startElementHandler,         /* startElement */
82    endElementHandler,           /* endElement */
83    NULL,                                                        /* reference */
84    characterHandler,                    /* characters */
85    NULL,                                                        /* ignorableWhitespace */
86    NULL,                                                        /* processingInstruction */
87    NULL,                                                        /* comment */
88    parseWarningHandler,         /* xmlParserWarning */
89    parseErrorHandler,           /* xmlParserError */
90    NULL,                                                        /* xmlParserFatalError : unused */
91    NULL,                                                        /* getParameterEntity */
92    NULL,                                                        /* cdataBlock; */
93    NULL,                                                        /* externalSubset; */
94    1,
95    NULL,
96    NULL,                                                        /* startElementNs */
97    NULL,                                                        /* endElementNs */
98    NULL                                                 /* xmlStructuredErrorFunc */
99 };
100
101 // ---------------------------------------------------------------------------------
102 // Our SAX handler pointer.
103 // ---------------------------------------------------------------------------------
104 static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
105
106 #ifndef HOST_NAME_MAX
107 #define HOST_NAME_MAX 256
108 #endif
109
110 static void grab_incoming(void* blob, socket_manager* mgr, int sockid, char* data, int parent);
111 static void reset_session_buffers( transport_session* session );
112 static const char* get_xml_attr( const xmlChar** atts, const char* attr_name );
113
114 /**
115         @brief Allocate and initialize a transport_session.
116         @param server Hostname or IP address where the Jabber server resides.
117         @param port Port used for connecting to Jabber (0 if using UNIX domain socket).
118         @param unix_path Name of Jabber's socket in file system (if using UNIX domain socket).
119         @param user_data An opaque pointer stored on behalf of the calling code.
120         @param component Boolean; true if we're a component.
121         @return Pointer to a newly allocated transport_session.
122
123         This function initializes memory but does not open any sockets or otherwise access
124         the network.
125
126         If @a port is greater than zero, we will use TCP to connect to Jabber, and ignore
127         @a unix_path.  Otherwise we will open a UNIX domain socket using @a unix_path.
128
129         The calling code is responsible for freeing the transport_session by calling
130         session_free().
131 */
132 transport_session* init_transport( const char* server,
133         int port, const char* unix_path, void* user_data, int component ) {
134
135         if( ! server )
136                 server = "";
137
138         /* create the session struct */
139         transport_session* session =
140                 (transport_session*) safe_malloc( sizeof(transport_session) );
141
142         session->user_data = user_data;
143
144         session->component = component;
145
146         /* initialize the data buffers */
147         session->body_buffer            = buffer_init( JABBER_BODY_BUFSIZE );
148         session->subject_buffer         = buffer_init( JABBER_SUBJECT_BUFSIZE );
149         session->thread_buffer          = buffer_init( JABBER_THREAD_BUFSIZE );
150         session->from_buffer            = buffer_init( JABBER_JID_BUFSIZE );
151         session->status_buffer          = buffer_init( JABBER_STATUS_BUFSIZE );
152         session->recipient_buffer       = buffer_init( JABBER_JID_BUFSIZE );
153         session->message_error_type = buffer_init( JABBER_JID_BUFSIZE );
154         session->session_id                     = buffer_init( 64 );
155
156         session->message_error_code = 0;
157
158         /* for OpenSRF extensions */
159         session->router_to_buffer       = buffer_init( JABBER_JID_BUFSIZE );
160         session->router_from_buffer     = buffer_init( JABBER_JID_BUFSIZE );
161         session->osrf_xid_buffer        = buffer_init( JABBER_JID_BUFSIZE );
162         session->router_class_buffer    = buffer_init( JABBER_JID_BUFSIZE );
163         session->router_command_buffer  = buffer_init( JABBER_JID_BUFSIZE );
164
165         session->router_broadcast   = 0;
166
167         /* initialize the jabber state machine */
168         session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
169         session->state_machine->connected        = 0;
170         session->state_machine->connecting       = 0;
171         session->state_machine->in_message       = 0;
172         session->state_machine->in_message_body  = 0;
173         session->state_machine->in_thread        = 0;
174         session->state_machine->in_subject       = 0;
175         session->state_machine->in_error         = 0;
176         session->state_machine->in_message_error = 0;
177         session->state_machine->in_iq            = 0;
178         session->state_machine->in_presence      = 0;
179         session->state_machine->in_status        = 0;
180
181         /* initialize the sax push parser */
182         session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
183
184         /* initialize the socket_manager structure */
185         session->sock_mgr = (socket_manager*) safe_malloc( sizeof(socket_manager) );
186
187         session->sock_mgr->data_received = &grab_incoming;
188         session->sock_mgr->on_socket_closed = NULL;
189         session->sock_mgr->socket = NULL;
190         session->sock_mgr->blob = session;
191
192         session->port = port;
193         session->server = strdup(server);
194         if(unix_path)
195                 session->unix_path = strdup(unix_path);
196         else session->unix_path = NULL;
197
198         session->sock_id = 0;
199         session->message_callback = NULL;
200
201         return session;
202 }
203
204
205 /**
206         @brief Destroy a transport_session, and close its socket.
207         @param session Pointer to the transport_session to be destroyed.
208         @return 1 if successful, or 0 if not.
209
210         The only error condition is a NULL pointer argument.
211 */
212 int session_free( transport_session* session ) {
213         if( ! session ) { return 0; }
214
215         if( session->sock_id )
216                 session_disconnect( session );
217
218         if(session->sock_mgr)
219                 socket_manager_free(session->sock_mgr);
220
221         if( session->state_machine ) free( session->state_machine );
222         if( session->parser_ctxt) {
223                 xmlFreeDoc( session->parser_ctxt->myDoc );
224                 xmlFreeParserCtxt(session->parser_ctxt);
225         }
226
227         xmlCleanupCharEncodingHandlers();
228         xmlDictCleanup();
229         xmlCleanupParser();
230
231         buffer_free(session->body_buffer);
232         buffer_free(session->subject_buffer);
233         buffer_free(session->thread_buffer);
234         buffer_free(session->from_buffer);
235         buffer_free(session->recipient_buffer);
236         buffer_free(session->status_buffer);
237         buffer_free(session->message_error_type);
238         buffer_free(session->router_to_buffer);
239         buffer_free(session->router_from_buffer);
240         buffer_free(session->osrf_xid_buffer);
241         buffer_free(session->router_class_buffer);
242         buffer_free(session->router_command_buffer);
243         buffer_free(session->session_id);
244
245         free(session->server);
246         free(session->unix_path);
247
248         free( session );
249         return 1;
250 }
251
252 /**
253         @brief Determine whether a transport_session is connected.
254         @param session Pointer to the transport_session to be tested.
255         @return 1 if connected, or 0 if not.
256 */
257 int session_connected( transport_session* session ) {
258         return session ? session->state_machine->connected : 0;
259 }
260
261 /**
262         @brief Wait on the client socket connected to Jabber, and process any resulting input.
263         @param session Pointer to the transport_session.
264         @param timeout How seconds to wait before timing out (see notes).
265         @return 0 if successful, or -1 if a timeout or other error occurs, or if the server
266                 closes the connection at the other end.
267
268         If @a timeout is -1, wait indefinitely for input activity to appear.  If @a timeout is
269         zero, don't wait at all.  If @a timeout is positive, wait that number of seconds
270         before timing out.  If @a timeout has a negative value other than -1, the results are not
271         well defined.
272
273         Read all available input from the socket and pass it through grab_incoming() (a
274         callback function previously installed in the socket_manager).
275
276         There is no guarantee that we will get a complete message from a single call.  As a
277         result, the calling code should call this function in a loop until it gets a complete
278         message, or until an error occurs.
279 */
280 int session_wait( transport_session* session, int timeout ) {
281         if( ! session || ! session->sock_mgr ) {
282                 return 0;
283         }
284
285         int ret =  socket_wait( session->sock_mgr, timeout, session->sock_id );
286
287         if( ret ) {
288                 osrfLogDebug(OSRF_LOG_MARK, "socket_wait returned error code %d", ret);
289                 session->state_machine->connected = 0;
290         }
291         return ret;
292 }
293
294 /**
295         @brief Wrap a message in XML and send it to Jabber.
296         @param session Pointer to the transport_session.
297         @param msg Pointer to a transport_message enclosing the message.
298         @return 0 if successful, or -1 upon error.
299 */
300 int session_send_msg(
301                 transport_session* session, transport_message* msg ) {
302
303         if( ! session ) { return -1; }
304
305         if( ! session->state_machine->connected ) {
306                 osrfLogWarning(OSRF_LOG_MARK, "State machine is not connected in send_msg()");
307                 return -1;
308         }
309
310         message_prepare_xml( msg );
311         return socket_send( session->sock_id, msg->msg_xml );
312
313 }
314
315
316 /**
317         @brief Connect to the Jabber server as a client and open a Jabber session.
318         @param session Pointer to a transport_session.
319         @param username Jabber user name.
320         @param password Jabber password.
321         @param resource name of Jabber resource.
322         @param connect_timeout Timeout interval, in seconds, for receiving data (see notes).
323         @param auth_type An enum: either AUTH_PLAIN or AUTH_DIGEST (see notes).
324         @return 1 if successful, or 0 upon error.
325
326         If @a connect_timeout is -1, wait indefinitely for input activity to appear.  If
327         @a connect_timeout is zero, don't wait at all.  If @a timeout is positive, wait that
328         number of seconds before timing out.  If @a connect_timeout has a negative value other
329         than -1, the results are not well defined.
330
331         If we connect as a Jabber component, we send the password as an SHA1 hash.  Otherwise
332         we look at the @a auth_type.  If it's AUTH_PLAIN, we send the password as plaintext; if
333         it's AUTH_DIGEST, we send it as a hash.
334
335         At this writing, we only use AUTH_DIGEST.
336 */
337 int session_connect( transport_session* session,
338                 const char* username, const char* password,
339                 const char* resource, int connect_timeout, enum TRANSPORT_AUTH_TYPE auth_type ) {
340
341         // Sanity checks
342         if( ! session ) {
343                 osrfLogWarning(OSRF_LOG_MARK, "session is null in session_connect()" );
344                 return 0;
345         }
346
347         if( session->sock_id != 0 ) {
348                 osrfLogWarning(OSRF_LOG_MARK, "transport session is already open, on socket %d",
349                         session->sock_id );
350                 return 0;
351         }
352
353         // Open a client socket connecting to the Jabber server
354         if(session->port > 0) {   // use TCP
355                 session->sock_id = socket_open_tcp_client( 
356                                 session->sock_mgr, session->port, session->server );
357                 if( session->sock_id <= 0 ) {
358                         session->sock_id = 0;
359                         return 0;
360                 }
361         } else if(session->unix_path != NULL) {  // use UNIX domain
362                 session->sock_id = socket_open_unix_client( session->sock_mgr, session->unix_path );
363                 if( session->sock_id <= 0 ) {
364                         session->sock_id = 0;
365                         return 0;
366                 }
367         }
368         else {
369                 osrfLogWarning( OSRF_LOG_MARK, "Can't open session: no port or unix path" );
370                 return 0;
371         }
372
373         const char* server = session->server;
374         int size1 = 0;
375         int size2 = 0;
376
377         /*
378         We establish the session in two stages.
379
380         First we establish an XMPP stream with the Jabber server by sending an opening tag of
381         stream:stream.  This is not a complete XML document.  We don't send the corresponding
382         closing tag until we close the session.
383
384         If the Jabber server responds by sending an opening stream:stream tag of its own, we can
385         proceed to the second stage by sending a second stanza to log in.  This stanza is an XML
386         element with the tag <handshake> (if we're a Jabber component) or <iq> (if we're not),
387         enclosing the username, password, and resource.
388
389         If all goes well, the Jabber server responds with a <handshake> or <iq> stanza of its own,
390         and we're logged in.
391
392         If authentication fails, the Jabber server returns a <stream:error> (if we used a <handshake>
393         or an <iq> of type "error" (if we used an <iq>).
394         */
395         if( session->component ) {
396
397                 /* the first Jabber connect stanza */
398                 char our_hostname[HOST_NAME_MAX + 1] = "";
399                 gethostname(our_hostname, sizeof(our_hostname) );
400                 our_hostname[HOST_NAME_MAX] = '\0';
401                 size1 = 150 + strlen( username ) + strlen( our_hostname );
402                 char stanza1[ size1 ];
403                 snprintf( stanza1, sizeof(stanza1),
404                                 "<stream:stream version='1.0' xmlns:stream='http://etherx.jabber.org/streams' "
405                                 "xmlns='jabber:component:accept' to='%s' from='%s' xml:lang='en'>",
406                                 username, our_hostname );
407
408                 /* send the first stanze */
409                 session->state_machine->connecting = CONNECTING_1;
410
411                 if( socket_send( session->sock_id, stanza1 ) ) {
412                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
413                         socket_disconnect( session->sock_mgr, session->sock_id );
414                         session->sock_id = 0;
415                         return 0;
416                 }
417
418                 /* wait for reply */
419                 socket_wait(session->sock_mgr, connect_timeout, session->sock_id);
420
421                 /* server acknowledges our existence, now see if we can login */
422                 if( session->state_machine->connecting == CONNECTING_2 ) {
423
424                         int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
425                         char hashstuff[ss];
426                         snprintf( hashstuff, sizeof(hashstuff), "%s%s",
427                                         OSRF_BUFFER_C_STR( session->session_id ), password );
428
429                         char* hash = shahash( hashstuff );
430                         size2 = 100 + strlen( hash );
431                         char stanza2[ size2 ];
432                         snprintf( stanza2, sizeof(stanza2), "<handshake>%s</handshake>", hash );
433
434                         if( socket_send( session->sock_id, stanza2 )  ) {
435                                 osrfLogWarning(OSRF_LOG_MARK, "error sending");
436                                 socket_disconnect( session->sock_mgr, session->sock_id );
437                                 session->sock_id = 0;
438                                 return 0;
439                         }
440                 }
441
442         } else { /* we're not a component */
443
444                 /* the first Jabber connect stanza */
445                 size1 = 100 + strlen( server );
446                 char stanza1[ size1 ];
447                 snprintf( stanza1, sizeof(stanza1),
448                                 "<stream:stream to='%s' xmlns='jabber:client' "
449                                 "xmlns:stream='http://etherx.jabber.org/streams'>",
450                         server );
451
452                 /* send the first stanze */
453                 session->state_machine->connecting = CONNECTING_1;
454                 if( socket_send( session->sock_id, stanza1 ) ) {
455                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
456                         socket_disconnect( session->sock_mgr, session->sock_id );
457                         session->sock_id = 0;
458                         return 0;
459                 }
460
461
462                 /* wait for reply */
463                 socket_wait( session->sock_mgr, connect_timeout, session->sock_id ); /* make the timeout smarter XXX */
464
465                 if( auth_type == AUTH_PLAIN ) {
466
467                         /* the second jabber connect stanza including login info*/
468                         size2 = 150 + strlen( username ) + strlen( password ) + strlen( resource );
469                         char stanza2[ size2 ];
470                         snprintf( stanza2, sizeof(stanza2),
471                                         "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
472                                         "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
473                                         username, password, resource );
474
475                         /* server acknowledges our existence, now see if we can login */
476                         if( session->state_machine->connecting == CONNECTING_2 ) {
477                                 if( socket_send( session->sock_id, stanza2 )  ) {
478                                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
479                                         socket_disconnect( session->sock_mgr, session->sock_id );
480                                         session->sock_id = 0;
481                                         return 0;
482                                 }
483                         }
484
485                 } else if( auth_type == AUTH_DIGEST ) {
486
487                         int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
488                         char hashstuff[ss];
489                         snprintf( hashstuff, sizeof(hashstuff), "%s%s", OSRF_BUFFER_C_STR( session->session_id ), password );
490
491                         char* hash = shahash( hashstuff );
492
493                         /* the second jabber connect stanza including login info */
494                         size2 = 150 + strlen( username ) + strlen( hash ) + strlen(resource);
495                         char stanza2[ size2 ];
496                         snprintf( stanza2, sizeof(stanza2),
497                                         "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
498                                         "<username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>",
499                                         username, hash, resource );
500
501                         /* server acknowledges our existence, now see if we can login */
502                         if( session->state_machine->connecting == CONNECTING_2 ) {
503                                 if( socket_send( session->sock_id, stanza2 )  ) {
504                                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
505                                         socket_disconnect( session->sock_mgr, session->sock_id );
506                                         session->sock_id = 0;
507                                         return 0;
508                                 }
509                         }
510
511                 } else {
512                         osrfLogWarning(OSRF_LOG_MARK, "Invalid auth_type parameter: %d",
513                                         (int) auth_type );
514                         socket_disconnect( session->sock_mgr, session->sock_id );
515                         session->sock_id = 0;
516                         return 0;
517                 }
518
519         } // not component
520
521
522         /* wait for reply to login request */
523         socket_wait( session->sock_mgr, connect_timeout, session->sock_id );
524
525         if( session->state_machine->connected ) {
526                 /* yar! */
527                 return 1;
528         } else {
529                 socket_disconnect( session->sock_mgr, session->sock_id );
530                 session->sock_id = 0;
531                 return 0;
532         }
533 }
534
535 /**
536         @brief Callback function: push a buffer of XML into an XML parser.
537         @param blob Void pointer pointing to the transport_session.
538         @param mgr Pointer to the socket_manager (not used).
539         @param sockid Socket file descriptor (not used)
540         @param data Pointer to a buffer of received data, as a nul-terminated string.
541         @param parent Not applicable.
542
543         The socket_manager calls this function when it reads a buffer's worth of data from
544         the Jabber socket.  The XML parser calls other callback functions when it sees various
545         features of the XML.
546 */
547 static void grab_incoming(void* blob, socket_manager* mgr, int sockid, char* data, int parent) {
548         transport_session* ses = (transport_session*) blob;
549         if( ! ses ) { return; }
550         xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
551 }
552
553
554 /**
555         @brief Respond to the beginning of an XML element.
556         @param session Pointer to the transport_session, cast to a void pointer.
557         @param name Name of the XML element.
558         @param atts Pointer to a ragged array containing attributes and values.
559
560         The XML parser calls this when it sees the beginning of an XML element.  We note what
561         element it is by setting the corresponding switch in the state machine, and grab whatever
562         attributes we expect to find.
563 */
564 static void startElementHandler(
565         void *session, const xmlChar *name, const xmlChar **atts) {
566
567         transport_session* ses = (transport_session*) session;
568         if( ! ses ) { return; }
569
570
571         if( strcmp( (char*) name, "message" ) == 0 ) {
572                 ses->state_machine->in_message = 1;
573                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
574                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
575                 buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
576                 buffer_add( ses->osrf_xid_buffer, get_xml_attr( atts, "osrf_xid" ) );
577                 buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
578                 buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
579                 buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
580                 const char* broadcast = get_xml_attr( atts, "broadcast" );
581                 if( broadcast )
582                         ses->router_broadcast = atoi( broadcast );
583
584                 return;
585         }
586
587         if( ses->state_machine->in_message ) {
588
589                 if( strcmp( (char*) name, "body" ) == 0 ) {
590                         ses->state_machine->in_message_body = 1;
591                         return;
592                 }
593
594                 if( strcmp( (char*) name, "subject" ) == 0 ) {
595                         ses->state_machine->in_subject = 1;
596                         return;
597                 }
598
599                 if( strcmp( (char*) name, "thread" ) == 0 ) {
600                         ses->state_machine->in_thread = 1;
601                         return;
602                 }
603
604         }
605
606         if( strcmp( (char*) name, "presence" ) == 0 ) {
607                 ses->state_machine->in_presence = 1;
608                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
609                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
610                 return;
611         }
612
613         if( strcmp( (char*) name, "status" ) == 0 ) {
614                 ses->state_machine->in_status = 1;
615                 return;
616         }
617
618
619         if( strcmp( (char*) name, "stream:error" ) == 0 ) {
620                 ses->state_machine->in_error = 1;
621                 ses->state_machine->connected = 0;
622                 osrfLogWarning(  OSRF_LOG_MARK, "Received <stream:error> message from Jabber server" );
623                 return;
624         }
625
626
627         /* first server response from a connect attempt */
628         if( strcmp( (char*) name, "stream:stream" ) == 0 ) {
629                 if( ses->state_machine->connecting == CONNECTING_1 ) {
630                         ses->state_machine->connecting = CONNECTING_2;
631                         buffer_add( ses->session_id, get_xml_attr(atts, "id") );
632                 }
633                 return;
634         }
635
636         if( strcmp( (char*) name, "handshake" ) == 0 ) {
637                 ses->state_machine->connected = 1;
638                 ses->state_machine->connecting = 0;
639                 return;
640         }
641
642
643         if( strcmp( (char*) name, "error" ) == 0 ) {
644                 ses->state_machine->in_message_error = 1;
645                 buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
646                 ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
647                 osrfLogInfo( OSRF_LOG_MARK, "Received <error> message with type %s and code %d",
648                         OSRF_BUFFER_C_STR( ses->message_error_type ), ses->message_error_code );
649                 return;
650         }
651
652         if( strcmp( (char*) name, "iq" ) == 0 ) {
653                 ses->state_machine->in_iq = 1;
654
655                 const char* type = get_xml_attr(atts, "type");
656
657                 if( strcmp( type, "result") == 0
658                                 && ses->state_machine->connecting == CONNECTING_2 ) {
659                         ses->state_machine->connected = 1;
660                         ses->state_machine->connecting = 0;
661                         return;
662                 }
663
664                 if( strcmp( type, "error") == 0 ) {
665                         osrfLogWarning( OSRF_LOG_MARK,  "Error connecting to jabber" );
666                         return;
667                 }
668         }
669 }
670
671 /**
672         @brief Return the value of a given XML attribute.
673         @param atts Pointer to a NULL terminated array of strings.
674         @param attr_name Name of the attribute you're looking for.
675         @return The value of the attribute if found, or NULL if not.
676
677         In the array to which @a atts points, the zeroth entry is an attribute name, and the 
678         one after that is its value.  Subsequent entries alternate between names and values.
679         The last entry is NULL to terminate the list.
680 */
681 static const char* get_xml_attr( const xmlChar** atts, const char* attr_name ) {
682         int i;
683         if (atts != NULL) {
684                 for(i = 0;(atts[i] != NULL);i++) {
685                         if( strcmp( (const char*) atts[i++], attr_name ) == 0 ) {
686                                 if( atts[i] != NULL ) {
687                                         return (const char*) atts[i];
688                                 }
689                         }
690                 }
691         }
692         return NULL;
693 }
694
695
696 /**
697         @brief React to the closing of an XML tag.
698         @param session Pointer to a transport_session, cast to a void pointer.
699         @param name Pointer to the name of the tag that is closing.
700
701         See what kind of tag is closing, and respond accordingly.
702 */
703 static void endElementHandler( void *session, const xmlChar *name) {
704         transport_session* ses = (transport_session*) session;
705         if( ! ses ) { return; }
706
707         // Bypass a level of indirection, since we'll examine the machine repeatedly:
708         jabber_machine* machine = ses->state_machine;
709
710         if( machine->in_message && strcmp( (char*) name, "message" ) == 0 ) {
711
712                 /* pass off the message info the callback */
713                 if( ses->message_callback ) {
714
715                         transport_message* msg =  message_init(
716                                 OSRF_BUFFER_C_STR( ses->body_buffer ),
717                                 OSRF_BUFFER_C_STR( ses->subject_buffer ),
718                                 OSRF_BUFFER_C_STR( ses->thread_buffer ),
719                                 OSRF_BUFFER_C_STR( ses->recipient_buffer ),
720                                 OSRF_BUFFER_C_STR( ses->from_buffer ) );
721
722                         message_set_router_info( msg,
723                                 ses->router_from_buffer->buf,
724                                 ses->router_to_buffer->buf,
725                                 ses->router_class_buffer->buf,
726                                 ses->router_command_buffer->buf,
727                                 ses->router_broadcast );
728
729                         message_set_osrf_xid( msg, ses->osrf_xid_buffer->buf );
730
731                         if( ses->message_error_type->n_used > 0 ) {
732                                 set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
733                         }
734
735                         if( msg == NULL ) { return; }
736                         ses->message_callback( ses->user_data, msg );
737                 }
738
739                 machine->in_message = 0;
740                 reset_session_buffers( session );
741                 return;
742         }
743
744         if( machine->in_message_body && strcmp( (const char*) name, "body" ) == 0 ) {
745                 machine->in_message_body = 0;
746                 return;
747         }
748
749         if( machine->in_subject && strcmp( (const char*) name, "subject" ) == 0 ) {
750                 machine->in_subject = 0;
751                 return;
752         }
753
754         if( machine->in_thread && strcmp( (const char*) name, "thread" ) == 0 ) {
755                 machine->in_thread = 0;
756                 return;
757         }
758
759         if( machine->in_iq && strcmp( (const char*) name, "iq" ) == 0 ) {
760                 machine->in_iq = 0;
761                 if( ses->message_error_code > 0 ) {
762                         if( 401 == ses->message_error_code )
763                                 osrfLogWarning( OSRF_LOG_MARK, "Error 401 in IQ packet: not authorized" );
764                         else
765                                 osrfLogWarning( OSRF_LOG_MARK, "Error in IQ packet: code %d",
766                                                 ses->message_error_code );
767                 }
768                 reset_session_buffers( session );
769                 return;
770         }
771
772         if( machine->in_presence && strcmp( (const char*) name, "presence" ) == 0 ) {
773                 machine->in_presence = 0;
774                 /*
775                 if( ses->presence_callback ) {
776                         // call the callback with the status, etc.
777                 }
778                 */
779                 reset_session_buffers( session );
780                 return;
781         }
782
783         if( machine->in_status && strcmp( (const char*) name, "status" ) == 0 ) {
784                 machine->in_status = 0;
785                 return;
786         }
787
788         if( machine->in_message_error && strcmp( (const char*) name, "error" ) == 0 ) {
789                 machine->in_message_error = 0;
790                 return;
791         }
792
793         if( machine->in_error && strcmp( (const char*) name, "stream:error" ) == 0 ) {
794                 machine->in_error = 0;
795                 return;
796         }
797 }
798
799 /**
800         @brief Clear all the buffers of a transport_session.
801         @param ses Pointer to the transport_session whose buffers are to be cleared.
802 */
803 static void reset_session_buffers( transport_session* ses ) {
804         OSRF_BUFFER_RESET( ses->body_buffer );
805         OSRF_BUFFER_RESET( ses->subject_buffer );
806         OSRF_BUFFER_RESET( ses->thread_buffer );
807         OSRF_BUFFER_RESET( ses->from_buffer );
808         OSRF_BUFFER_RESET( ses->recipient_buffer );
809         OSRF_BUFFER_RESET( ses->router_from_buffer );
810         OSRF_BUFFER_RESET( ses->osrf_xid_buffer );
811         OSRF_BUFFER_RESET( ses->router_to_buffer );
812         OSRF_BUFFER_RESET( ses->router_class_buffer );
813         OSRF_BUFFER_RESET( ses->router_command_buffer );
814         OSRF_BUFFER_RESET( ses->message_error_type );
815         OSRF_BUFFER_RESET( ses->session_id );
816         OSRF_BUFFER_RESET( ses->status_buffer );
817 }
818
819 // ------------------------------------------------------------------
820 // takes data out of the body of the message and pushes it into
821 // the appropriate buffer
822 // ------------------------------------------------------------------
823 /**
824         @brief Copy XML text (outside of tags) into the appropriate buffer.
825         @param session Pointer to the transport_session.
826         @param ch Pointer to the text to be copied.
827         @param len How many characters to be copied.
828
829         The XML parser calls this as a callback when it finds text outside of a tag,  We check
830         the state machine to figure out what kind of text it is, and then append it to the
831         corresponding buffer.
832 */
833 static void characterHandler(
834                 void *session, const xmlChar *ch, int len) {
835
836         const char* p = (const char*) ch;
837
838         transport_session* ses = (transport_session*) session;
839         if( ! ses ) { return; }
840
841         jabber_machine* machine = ses->state_machine;
842
843         /* set the various message parts */
844         if( machine->in_message ) {
845
846                 if( machine->in_message_body ) {
847                         buffer_add_n( ses->body_buffer, p, len );
848                 }
849
850                 if( machine->in_subject ) {
851                         buffer_add_n( ses->subject_buffer, p, len );
852                 }
853
854                 if( machine->in_thread ) {
855                         buffer_add_n( ses->thread_buffer, p, len );
856                 }
857         }
858
859         /* set the presence status */
860         if( machine->in_presence && ses->state_machine->in_status ) {
861                 buffer_add_n( ses->status_buffer, p, len );
862         }
863
864         if( machine->in_error ) {
865                 /* for now... */
866                 osrfLogWarning( OSRF_LOG_MARK,  "ERROR XML fragment: %s\n", ch );
867         }
868
869 }
870
871 /**
872         @brief Log a warning from the XML parser.
873         @param session Pointer to a transport_session, cast to a void pointer (not used).
874         @param msg Pointer to a printf-style format string.  Subsequent messages, if any, are
875                 formatted and inserted into the expanded string.
876
877         The XML parser calls this function when it wants to warn about something in the XML.
878 */
879 static void  parseWarningHandler( void *session, const char* msg, ... ) {
880         VA_LIST_TO_STRING(msg);
881         osrfLogWarning( OSRF_LOG_MARK, VA_BUF );
882 }
883
884 /**
885         @brief Log an error from the XML parser.
886         @param session Pointer to a transport_session, cast to a void pointer (not used).
887         @param msg Pointer to a printf-style format string.  Subsequent messages, if any, are
888                 formatted and inserted into the expanded string.
889
890         The XML parser calls this function when it finds an error in the XML.
891 */
892 static void  parseErrorHandler( void *session, const char* msg, ... ){
893         VA_LIST_TO_STRING(msg);
894         osrfLogError( OSRF_LOG_MARK, VA_BUF );
895 }
896
897 /**
898         @brief Disconnect from Jabber, and close the socket.
899         @param session Pointer to the transport_session to be disconnected.
900         @return 0 in all cases.
901 */
902 int session_disconnect( transport_session* session ) {
903         if( session && session->sock_id != 0 ) {
904                 socket_send(session->sock_id, "</stream:stream>");
905                 socket_disconnect(session->sock_mgr, session->sock_id);
906                 session->sock_id = 0;
907         }
908         return 0;
909 }
910