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