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