4fa04ea5eaf97e209ff936b242d94f02d9de4748
[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         // Sanity checks
338         if( ! session ) {
339                 osrfLogWarning(OSRF_LOG_MARK, "session is null in session_connect()" );
340                 return 0;
341         }
342
343         if( session->sock_id != 0 ) {
344                 osrfLogWarning(OSRF_LOG_MARK, "transport session is already open, on socket %d",
345                         session->sock_id );
346                 return 0;
347         }
348
349         // Open a client socket connecting to the Jabber server
350         if(session->port > 0) {   // use TCP
351                 session->sock_id = socket_open_tcp_client( 
352                                 session->sock_mgr, session->port, session->server );
353                 if( session->sock_id <= 0 ) {
354                         session->sock_id = 0;
355                         return 0;
356                 }
357         } else if(session->unix_path != NULL) {  // use UNIX domain
358                 session->sock_id = socket_open_unix_client( session->sock_mgr, session->unix_path );
359                 if( session->sock_id <= 0 ) {
360                         session->sock_id = 0;
361                         return 0;
362                 }
363         }
364         else {
365                 osrfLogWarning( OSRF_LOG_MARK, "Can't open session: no port or unix path" );
366                 return 0;
367         }
368
369         const char* server = session->server;
370         int size1 = 0;
371         int size2 = 0;
372
373         /*
374         We establish the session in two stages.
375
376         First we establish an XMPP stream with the Jabber server by sending an opening tag of
377         stream:stream.  This is not a complete XML document.  We don't send the corresponding
378         closing tag until we close the session.
379
380         If the Jabber server responds by sending an opening stream:stream tag of its own, we can
381         proceed to the second stage by sending a second stanza to log in.  This stanza is an XML
382         element with the tag <handshake> (if we're a Jabber component) or <iq> (if we're not),
383         enclosing the username, password, and resource.
384
385         If all goes well, the Jabber server responds with a <handshake> or <iq> stanza of its own,
386         and we're logged in.
387
388         If authentication fails, the Jabber server returns a <stream:error> (if we used a <handshake>
389         or an <iq> of type "error" (if we used an <iq>).
390         */
391         if( session->component ) {
392
393                 /* the first Jabber connect stanza */
394                 char our_hostname[HOST_NAME_MAX + 1] = "";
395                 gethostname(our_hostname, sizeof(our_hostname) );
396                 our_hostname[HOST_NAME_MAX] = '\0';
397                 size1 = 150 + strlen( username ) + strlen( our_hostname );
398                 char stanza1[ size1 ];
399                 snprintf( stanza1, sizeof(stanza1),
400                                 "<stream:stream version='1.0' xmlns:stream='http://etherx.jabber.org/streams' "
401                                 "xmlns='jabber:component:accept' to='%s' from='%s' xml:lang='en'>",
402                                 username, our_hostname );
403
404                 /* send the first stanze */
405                 session->state_machine->connecting = CONNECTING_1;
406
407                 if( socket_send( session->sock_id, stanza1 ) ) {
408                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
409                         socket_disconnect( session->sock_mgr, session->sock_id );
410                         session->sock_id = 0;
411                         return 0;
412                 }
413
414                 /* wait for reply */
415                 socket_wait(session->sock_mgr, connect_timeout, session->sock_id);
416
417                 /* server acknowledges our existence, now see if we can login */
418                 if( session->state_machine->connecting == CONNECTING_2 ) {
419
420                         int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
421                         char hashstuff[ss];
422                         snprintf( hashstuff, sizeof(hashstuff), "%s%s", OSRF_BUFFER_C_STR( session->session_id ), password );
423
424                         char* hash = shahash( hashstuff );
425                         size2 = 100 + strlen( hash );
426                         char stanza2[ size2 ];
427                         snprintf( stanza2, sizeof(stanza2), "<handshake>%s</handshake>", hash );
428
429                         if( socket_send( session->sock_id, stanza2 )  ) {
430                                 osrfLogWarning(OSRF_LOG_MARK, "error sending");
431                                 socket_disconnect( session->sock_mgr, session->sock_id );
432                                 session->sock_id = 0;
433                                 return 0;
434                         }
435                 }
436
437         } else { /* we're not a component */
438
439                 /* the first Jabber connect stanza */
440                 size1 = 100 + strlen( server );
441                 char stanza1[ size1 ];
442                 snprintf( stanza1, sizeof(stanza1),
443                                 "<stream:stream to='%s' xmlns='jabber:client' "
444                                 "xmlns:stream='http://etherx.jabber.org/streams'>",
445                         server );
446
447                 /* send the first stanze */
448                 session->state_machine->connecting = CONNECTING_1;
449                 if( socket_send( session->sock_id, stanza1 ) ) {
450                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
451                         socket_disconnect( session->sock_mgr, session->sock_id );
452                         session->sock_id = 0;
453                         return 0;
454                 }
455
456
457                 /* wait for reply */
458                 socket_wait( session->sock_mgr, connect_timeout, session->sock_id ); /* make the timeout smarter XXX */
459
460                 if( auth_type == AUTH_PLAIN ) {
461
462                         /* the second jabber connect stanza including login info*/
463                         size2 = 150 + strlen( username ) + strlen( password ) + strlen( resource );
464                         char stanza2[ size2 ];
465                         snprintf( stanza2, sizeof(stanza2),
466                                         "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
467                                         "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
468                                         username, password, resource );
469
470                         /* server acknowledges our existence, now see if we can login */
471                         if( session->state_machine->connecting == CONNECTING_2 ) {
472                                 if( socket_send( session->sock_id, stanza2 )  ) {
473                                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
474                                         socket_disconnect( session->sock_mgr, session->sock_id );
475                                         session->sock_id = 0;
476                                         return 0;
477                                 }
478                         }
479
480                 } else if( auth_type == AUTH_DIGEST ) {
481
482                         int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
483                         char hashstuff[ss];
484                         snprintf( hashstuff, sizeof(hashstuff), "%s%s", OSRF_BUFFER_C_STR( session->session_id ), password );
485
486                         char* hash = shahash( hashstuff );
487
488                         /* the second jabber connect stanza including login info */
489                         size2 = 150 + strlen( username ) + strlen( hash ) + strlen(resource);
490                         char stanza2[ size2 ];
491                         snprintf( stanza2, sizeof(stanza2),
492                                         "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
493                                         "<username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>",
494                                         username, hash, resource );
495
496                         /* server acknowledges our existence, now see if we can login */
497                         if( session->state_machine->connecting == CONNECTING_2 ) {
498                                 if( socket_send( session->sock_id, stanza2 )  ) {
499                                         osrfLogWarning(OSRF_LOG_MARK, "error sending");
500                                         socket_disconnect( session->sock_mgr, session->sock_id );
501                                         session->sock_id = 0;
502                                         return 0;
503                                 }
504                         }
505
506                 } else {
507                         osrfLogWarning(OSRF_LOG_MARK, "Invalid auth_type parameter: %d",
508                                         (int) auth_type );
509                         socket_disconnect( session->sock_mgr, session->sock_id );
510                         session->sock_id = 0;
511                         return 0;
512                 }
513
514         } // not component
515
516
517         /* wait for reply to login request */
518         socket_wait( session->sock_mgr, connect_timeout, session->sock_id );
519
520         if( session->state_machine->connected ) {
521                 /* yar! */
522                 return 1;
523         } else {
524                 socket_disconnect( session->sock_mgr, session->sock_id );
525                 session->sock_id = 0;
526                 return 0;
527         }
528 }
529
530 /**
531         @brief Callback function: push a buffer of XML into an XML parser.
532         @param blob Void pointer pointing to the transport_session.
533         @param mgr Pointer to the socket_manager (not used).
534         @param sockid Socket file descriptor (not used)
535         @param data Pointer to a buffer of received data, as a nul-terminated string.
536         @param parent Not applicable.
537
538         The socket_manager calls this function when it reads a buffer's worth of data from
539         the Jabber socket.  The XML parser calls other callback functions when it sees various
540         features of the XML.
541 */
542 static void grab_incoming(void* blob, socket_manager* mgr, int sockid, char* data, int parent) {
543         transport_session* ses = (transport_session*) blob;
544         if( ! ses ) { return; }
545         xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
546 }
547
548
549 /**
550         @brief Respond to the beginning of an XML element.
551         @param session Pointer to the transport_session, cast to a void pointer.
552         @param name Name of the XML element.
553         @param atts Pointer to a ragged array containing attributes and values.
554
555         The XML parser calls this when it sees the beginning of an XML element.  We note what
556         element it is by setting the corresponding switch in the state machine, and grab whatever
557         attributes we expect to find.
558 */
559 static void startElementHandler(
560         void *session, const xmlChar *name, const xmlChar **atts) {
561
562         transport_session* ses = (transport_session*) session;
563         if( ! ses ) { return; }
564
565
566         if( strcmp( (char*) name, "message" ) == 0 ) {
567                 ses->state_machine->in_message = 1;
568                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
569                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
570                 buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
571                 buffer_add( ses->osrf_xid_buffer, get_xml_attr( atts, "osrf_xid" ) );
572                 buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
573                 buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
574                 buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
575                 const char* broadcast = get_xml_attr( atts, "broadcast" );
576                 if( broadcast )
577                         ses->router_broadcast = atoi( broadcast );
578
579                 return;
580         }
581
582         if( ses->state_machine->in_message ) {
583
584                 if( strcmp( (char*) name, "body" ) == 0 ) {
585                         ses->state_machine->in_message_body = 1;
586                         return;
587                 }
588
589                 if( strcmp( (char*) name, "subject" ) == 0 ) {
590                         ses->state_machine->in_subject = 1;
591                         return;
592                 }
593
594                 if( strcmp( (char*) name, "thread" ) == 0 ) {
595                         ses->state_machine->in_thread = 1;
596                         return;
597                 }
598
599         }
600
601         if( strcmp( (char*) name, "presence" ) == 0 ) {
602                 ses->state_machine->in_presence = 1;
603                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
604                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
605                 return;
606         }
607
608         if( strcmp( (char*) name, "status" ) == 0 ) {
609                 ses->state_machine->in_status = 1;
610                 return;
611         }
612
613
614         if( strcmp( (char*) name, "stream:error" ) == 0 ) {
615                 ses->state_machine->in_error = 1;
616                 ses->state_machine->connected = 0;
617                 osrfLogWarning(  OSRF_LOG_MARK, "Received <stream:error> message from Jabber server" );
618                 return;
619         }
620
621
622         /* first server response from a connect attempt */
623         if( strcmp( (char*) name, "stream:stream" ) == 0 ) {
624                 if( ses->state_machine->connecting == CONNECTING_1 ) {
625                         ses->state_machine->connecting = CONNECTING_2;
626                         buffer_add( ses->session_id, get_xml_attr(atts, "id") );
627                 }
628                 return;
629         }
630
631         if( strcmp( (char*) name, "handshake" ) == 0 ) {
632                 ses->state_machine->connected = 1;
633                 ses->state_machine->connecting = 0;
634                 return;
635         }
636
637
638         if( strcmp( (char*) name, "error" ) == 0 ) {
639                 ses->state_machine->in_message_error = 1;
640                 buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
641                 ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
642                 osrfLogInfo( OSRF_LOG_MARK, "Received <error> message with type %s and code %d",
643                         OSRF_BUFFER_C_STR( ses->message_error_type ), ses->message_error_code );
644                 return;
645         }
646
647         if( strcmp( (char*) name, "iq" ) == 0 ) {
648                 ses->state_machine->in_iq = 1;
649
650                 const char* type = get_xml_attr(atts, "type");
651
652                 if( strcmp( type, "result") == 0
653                                 && ses->state_machine->connecting == CONNECTING_2 ) {
654                         ses->state_machine->connected = 1;
655                         ses->state_machine->connecting = 0;
656                         return;
657                 }
658
659                 if( strcmp( type, "error") == 0 ) {
660                         osrfLogWarning( OSRF_LOG_MARK,  "Error connecting to jabber" );
661                         return;
662                 }
663         }
664 }
665
666 /**
667         @brief Return the value of a given XML attribute.
668         @param atts Pointer to a NULL terminated array of strings.
669         @param attr_name Name of the attribute you're looking for.
670         @return The value of the attribute if found, or NULL if not.
671
672         In the array to which @a atts points, the zeroth entry is an attribute name, and the 
673         one after that is its value.  Subsequent entries alternate between names and values.
674         The last entry is NULL to terminate the list.
675 */
676 static const char* get_xml_attr( const xmlChar** atts, const char* attr_name ) {
677         int i;
678         if (atts != NULL) {
679                 for(i = 0;(atts[i] != NULL);i++) {
680                         if( strcmp( (const char*) atts[i++], attr_name ) == 0 ) {
681                                 if( atts[i] != NULL ) {
682                                         return (const char*) atts[i];
683                                 }
684                         }
685                 }
686         }
687         return NULL;
688 }
689
690
691 /**
692         @brief React to the closing of an XML tag.
693         @param session Pointer to a transport_session, cast to a void pointer.
694         @param name Pointer to the name of the tag that is closing.
695
696         See what kind of tag is closing, and respond accordingly.
697 */
698 static void endElementHandler( void *session, const xmlChar *name) {
699         transport_session* ses = (transport_session*) session;
700         if( ! ses ) { return; }
701
702         // Bypass a level of indirection, since we'll examine the machine repeatedly:
703         jabber_machine* machine = ses->state_machine;
704
705         if( machine->in_message && strcmp( (char*) name, "message" ) == 0 ) {
706
707                 /* pass off the message info the callback */
708                 if( ses->message_callback ) {
709
710                         transport_message* msg =  message_init(
711                                 OSRF_BUFFER_C_STR( ses->body_buffer ),
712                                 OSRF_BUFFER_C_STR( ses->subject_buffer ),
713                                 OSRF_BUFFER_C_STR( ses->thread_buffer ),
714                                 OSRF_BUFFER_C_STR( ses->recipient_buffer ),
715                                 OSRF_BUFFER_C_STR( ses->from_buffer ) );
716
717                         message_set_router_info( msg,
718                                 ses->router_from_buffer->buf,
719                                 ses->router_to_buffer->buf,
720                                 ses->router_class_buffer->buf,
721                                 ses->router_command_buffer->buf,
722                                 ses->router_broadcast );
723
724                         message_set_osrf_xid( msg, ses->osrf_xid_buffer->buf );
725
726                         if( ses->message_error_type->n_used > 0 ) {
727                                 set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
728                         }
729
730                         if( msg == NULL ) { return; }
731                         ses->message_callback( ses->user_data, msg );
732                 }
733
734                 machine->in_message = 0;
735                 reset_session_buffers( session );
736                 return;
737         }
738
739         if( machine->in_message_body && strcmp( (const char*) name, "body" ) == 0 ) {
740                 machine->in_message_body = 0;
741                 return;
742         }
743
744         if( machine->in_subject && strcmp( (const char*) name, "subject" ) == 0 ) {
745                 machine->in_subject = 0;
746                 return;
747         }
748
749         if( machine->in_thread && strcmp( (const char*) name, "thread" ) == 0 ) {
750                 machine->in_thread = 0;
751                 return;
752         }
753
754         if( machine->in_iq && strcmp( (const char*) name, "iq" ) == 0 ) {
755                 machine->in_iq = 0;
756                 if( ses->message_error_code > 0 ) {
757                         osrfLogWarning( OSRF_LOG_MARK,  "Error in IQ packet: code %d",  ses->message_error_code );
758                         osrfLogWarning( OSRF_LOG_MARK,  "Error 401 means not authorized" );
759                 }
760                 reset_session_buffers( session );
761                 return;
762         }
763
764         if( machine->in_presence && strcmp( (const char*) name, "presence" ) == 0 ) {
765                 machine->in_presence = 0;
766                 /*
767                 if( ses->presence_callback ) {
768                         // call the callback with the status, etc.
769                 }
770                 */
771                 reset_session_buffers( session );
772                 return;
773         }
774
775         if( machine->in_status && strcmp( (const char*) name, "status" ) == 0 ) {
776                 machine->in_status = 0;
777                 return;
778         }
779
780         if( machine->in_message_error && strcmp( (const char*) name, "error" ) == 0 ) {
781                 machine->in_message_error = 0;
782                 return;
783         }
784
785         if( machine->in_error && strcmp( (const char*) name, "stream:error" ) == 0 ) {
786                 machine->in_error = 0;
787                 return;
788         }
789 }
790
791 /**
792         @brief Clear all the buffers of a transport_session.
793         @param ses Pointer to the transport_session whose buffers are to be cleared.
794 */
795 static void reset_session_buffers( transport_session* ses ) {
796         OSRF_BUFFER_RESET( ses->body_buffer );
797         OSRF_BUFFER_RESET( ses->subject_buffer );
798         OSRF_BUFFER_RESET( ses->thread_buffer );
799         OSRF_BUFFER_RESET( ses->from_buffer );
800         OSRF_BUFFER_RESET( ses->recipient_buffer );
801         OSRF_BUFFER_RESET( ses->router_from_buffer );
802         OSRF_BUFFER_RESET( ses->osrf_xid_buffer );
803         OSRF_BUFFER_RESET( ses->router_to_buffer );
804         OSRF_BUFFER_RESET( ses->router_class_buffer );
805         OSRF_BUFFER_RESET( ses->router_command_buffer );
806         OSRF_BUFFER_RESET( ses->message_error_type );
807         OSRF_BUFFER_RESET( ses->session_id );
808         OSRF_BUFFER_RESET( ses->status_buffer );
809 }
810
811 // ------------------------------------------------------------------
812 // takes data out of the body of the message and pushes it into
813 // the appropriate buffer
814 // ------------------------------------------------------------------
815 /**
816         @brief Copy XML text (outside of tags) into the appropriate buffer.
817         @param session Pointer to the transport_session.
818         @param ch Pointer to the text to be copied.
819         @param len How many characters to be copied.
820
821         The XML parser calls this as a callback when it finds text outside of a tag,  We check
822         the state machine to figure out what kind of text it is, and then append it to the
823         corresponding buffer.
824 */
825 static void characterHandler(
826                 void *session, const xmlChar *ch, int len) {
827
828         const char* p = (const char*) ch;
829
830         transport_session* ses = (transport_session*) session;
831         if( ! ses ) { return; }
832
833         jabber_machine* machine = ses->state_machine;
834
835         /* set the various message parts */
836         if( machine->in_message ) {
837
838                 if( machine->in_message_body ) {
839                         buffer_add_n( ses->body_buffer, p, len );
840                 }
841
842                 if( machine->in_subject ) {
843                         buffer_add_n( ses->subject_buffer, p, len );
844                 }
845
846                 if( machine->in_thread ) {
847                         buffer_add_n( ses->thread_buffer, p, len );
848                 }
849         }
850
851         /* set the presence status */
852         if( machine->in_presence && ses->state_machine->in_status ) {
853                 buffer_add_n( ses->status_buffer, p, len );
854         }
855
856         if( machine->in_error ) {
857                 /* for now... */
858                 osrfLogWarning( OSRF_LOG_MARK,  "ERROR XML fragment: %s\n", ch );
859         }
860
861 }
862
863 /**
864         @brief Report a warning from the XML parser.
865         @param session Pointer to a transport_session, cast to a void pointer (not used).
866         @param msg Pointer to a printf-style format string.  Subsequent messages, if any, are
867                 formatted and inserted into the expanded string.
868
869         The XML parser calls this function when it wants to warn about something in the XML.
870 */
871 static void  parseWarningHandler( void *session, const char* msg, ... ) {
872
873         va_list args;
874         va_start(args, msg);
875         fprintf(stdout, "transport_session XML WARNING");
876         vfprintf(stdout, msg, args);
877         va_end(args);
878         fprintf(stderr, "XML WARNING: %s\n", msg );
879 }
880
881 /**
882         @brief Report an error from the XML parser.
883         @param session Pointer to a transport_session, cast to a void pointer (not used).
884         @param msg Pointer to a printf-style format string.  Subsequent messages, if any, are
885                 formatted and inserted into the expanded string.
886
887         The XML parser calls this function when it finds an error in the XML.
888 */
889 static void  parseErrorHandler( void *session, const char* msg, ... ){
890
891         va_list args;
892         va_start(args, msg);
893         fprintf(stdout, "transport_session XML ERROR");
894         vfprintf(stdout, msg, args);
895         va_end(args);
896         fprintf(stderr, "XML ERROR: %s\n", msg );
897 }
898
899 /**
900         @brief Disconnect from Jabber, and close the socket.
901         @param session Pointer to the transport_session to be disconnected.
902         @return 0 in all cases.
903 */
904 int session_disconnect( transport_session* session ) {
905         if( session && session->sock_id != 0 ) {
906                 socket_send(session->sock_id, "</stream:stream>");
907                 socket_disconnect(session->sock_mgr, session->sock_id);
908                 session->sock_id = 0;
909         }
910         return 0;
911 }
912