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