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