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