1 #include <opensrf/transport_session.h>
4 @file transport_session.c
5 @brief Routines to manage a connection to a Jabber server.
7 In all cases, a transport_session acts as a client with regard to Jabber.
10 #define CONNECTING_1 1 /**< just starting the connection to Jabber */
11 #define CONNECTING_2 2 /**< XML stream opened but not yet logged in */
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 */
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);
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);
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);
42 static void parseWarningHandler( void *session, const char* msg, ... );
43 static void parseErrorHandler( void *session, const char* msg, ... );
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 */
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 */
66 characterHandler, /* characters */
67 NULL, /* ignorableWhitespace */
68 NULL, /* processingInstruction */
70 parseWarningHandler, /* xmlParserWarning */
71 parseErrorHandler, /* xmlParserError */
72 NULL, /* xmlParserFatalError : unused */
73 NULL, /* getParameterEntity */
74 NULL, /* cdataBlock; */
75 NULL, /* externalSubset; */
78 NULL, /* startElementNs */
79 NULL, /* endElementNs */
80 NULL /* xmlStructuredErrorFunc */
83 // ---------------------------------------------------------------------------------
84 // Our SAX handler pointer.
85 // ---------------------------------------------------------------------------------
86 static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
89 #define HOST_NAME_MAX 256
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 );
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.
106 This function initializes memory but does not open any sockets or otherwise access
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.
112 The calling code is responsible for freeing the transport_session by calling
115 transport_session* init_transport( const char* server,
116 int port, const char* unix_path, void* user_data, int component ) {
121 /* create the session struct */
122 transport_session* session =
123 (transport_session*) safe_malloc( sizeof(transport_session) );
125 session->user_data = user_data;
127 session->component = component;
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 );
139 session->message_error_code = 0;
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 );
148 session->router_broadcast = 0;
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;
164 /* initialize the sax push parser */
165 session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
167 /* initialize the socket_manager structure */
168 session->sock_mgr = (socket_manager*) safe_malloc( sizeof(socket_manager) );
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;
175 session->port = port;
176 session->server = strdup(server);
178 session->unix_path = strdup(unix_path);
179 else session->unix_path = NULL;
181 session->sock_id = 0;
182 session->message_callback = NULL;
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.
192 The only error condition is a NULL pointer argument.
194 int session_free( transport_session* session ) {
198 session_disconnect( session );
199 return session_discard( session );
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.
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).
212 The only error condition is a NULL pointer argument.
214 int session_discard( transport_session* session ) {
218 if(session->sock_mgr)
219 socket_manager_free(session->sock_mgr);
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);
227 xmlCleanupCharEncodingHandlers();
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);
245 free(session->server);
246 free(session->unix_path);
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.
257 int session_connected( transport_session* session ) {
258 return session ? session->state_machine->connected : 0;
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.
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
273 Read all available input from the socket and pass it through grab_incoming() (a
274 callback function previously installed in the socket_manager).
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.
280 int session_wait( transport_session* session, int timeout ) {
281 if( ! session || ! session->sock_mgr ) {
285 int ret = socket_wait( session->sock_mgr, timeout, session->sock_id );
288 osrfLogDebug(OSRF_LOG_MARK, "socket_wait returned error code %d", ret);
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.
299 int session_send_msg(
300 transport_session* session, transport_message* msg ) {
302 if( ! session ) { return -1; }
304 if( ! session->state_machine->connected ) {
305 osrfLogWarning(OSRF_LOG_MARK, "State machine is not connected in send_msg()");
309 message_prepare_xml( msg );
310 return socket_send( session->sock_id, msg->msg_xml );
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.
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.
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.
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.
337 At this writing, we only use AUTH_DIGEST.
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 ) {
345 osrfLogWarning(OSRF_LOG_MARK, "session is null in session_connect()" );
349 if( session->sock_id != 0 ) {
350 osrfLogWarning(OSRF_LOG_MARK, "transport session is already open, on socket %d",
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;
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;
371 osrfLogWarning( OSRF_LOG_MARK, "Can't open session: no port or unix path" );
375 const char* server = session->server;
380 We establish the session in two stages.
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.
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.
391 If all goes well, the Jabber server responds with a <handshake> or <iq> stanza of its own,
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>).
397 if( session->component ) {
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 );
410 /* send the first stanze */
411 session->state_machine->connecting = CONNECTING_1;
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;
421 socket_wait(session->sock_mgr, connect_timeout, session->sock_id);
423 /* server acknowledges our existence, now see if we can login */
424 if( session->state_machine->connecting == CONNECTING_2 ) {
426 int ss = osrf_buffer_length( session->session_id ) + strlen( password ) + 5;
428 snprintf( hashstuff, sizeof(hashstuff), "%s%s",
429 OSRF_BUFFER_C_STR( session->session_id ), password );
431 char* hash = shahash( hashstuff );
432 size2 = 100 + strlen( hash );
433 char stanza2[ size2 ];
434 snprintf( stanza2, sizeof(stanza2), "<handshake>%s</handshake>", hash );
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;
444 } else { /* we're not a component */
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'>",
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;
465 socket_wait( session->sock_mgr, connect_timeout, session->sock_id ); /* make the timeout smarter XXX */
467 if( auth_type == AUTH_PLAIN ) {
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 );
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;
487 } else if( auth_type == AUTH_DIGEST ) {
489 int ss = osrf_buffer_length( session->session_id ) + strlen( password ) + 5;
491 snprintf( hashstuff, sizeof(hashstuff), "%s%s", OSRF_BUFFER_C_STR( session->session_id ), password );
493 char* hash = shahash( hashstuff );
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 );
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;
514 osrfLogWarning(OSRF_LOG_MARK, "Invalid auth_type parameter: %d",
516 socket_disconnect( session->sock_mgr, session->sock_id );
517 session->sock_id = 0;
524 /* wait for reply to login request */
525 socket_wait( session->sock_mgr, connect_timeout, session->sock_id );
527 if( session->state_machine->connected ) {
531 socket_disconnect( session->sock_mgr, session->sock_id );
532 session->sock_id = 0;
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.
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
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);
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.
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.
566 static void startElementHandler(
567 void *session, const xmlChar *name, const xmlChar **atts) {
569 transport_session* ses = (transport_session*) session;
570 if( ! ses ) { return; }
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;
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" ) );
587 if( ses->state_machine->in_message ) {
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" );
597 ses->router_broadcast = atoi( broadcast );
602 if( strcmp( (char*) name, "body" ) == 0 ) {
603 ses->state_machine->in_message_body = 1;
607 if( strcmp( (char*) name, "subject" ) == 0 ) {
608 ses->state_machine->in_subject = 1;
612 if( strcmp( (char*) name, "thread" ) == 0 ) {
613 ses->state_machine->in_thread = 1;
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" ) );
626 if( strcmp( (char*) name, "status" ) == 0 ) {
627 ses->state_machine->in_status = 1;
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" );
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") );
649 if( strcmp( (char*) name, "handshake" ) == 0 ) {
650 ses->state_machine->connected = 1;
651 ses->state_machine->connecting = 0;
656 if( strcmp( (char*) name, "error" ) == 0 ) {
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" );
662 ses->message_error_code = atoi( code );
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 );
670 if ( ses->state_machine->in_message_error == 1 && isXMPPError == 1 ) {
671 ses->message_error_code = get_xmpp_error_code( name );
676 if( strcmp( (char*) name, "iq" ) == 0 ) {
677 ses->state_machine->in_iq = 1;
679 const char* type = get_xml_attr(atts, "type");
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;
688 if( strcmp( type, "error") == 0 ) {
689 osrfLogWarning( OSRF_LOG_MARK, "Error connecting to jabber" );
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.
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.
705 static const char* get_xml_attr( const xmlChar** atts, const char* attr_name ) {
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];
720 @brief Return the value of the legacy XMPP Error Code
721 @param name Pointer to the name of the tag
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.
726 static int get_xmpp_error_code( const xmlChar* name ) {
727 const char *cname = (const char *) name;
729 if (strcmp( cname, "not-authorized" ) == 0)
731 if (strcmp( cname, "service-unavailable" ) == 0)
733 if (strcmp( cname, "bad-request" ) == 0)
735 if (strcmp( cname, "conflict" ) == 0)
737 if (strcmp( cname, "feature-not-implemented" ) == 0)
739 if (strcmp( cname, "forbidden" ) == 0)
741 if (strcmp( cname, "gone" ) == 0)
743 if (strcmp( cname, "internal-server-error" ) == 0)
745 if (strcmp( cname, "item-not-found" ) == 0)
747 if (strcmp( cname, "jid-malformed" ) == 0)
749 if (strcmp( cname, "not-acceptable" ) == 0)
751 if (strcmp( cname, "not-allowed" ) == 0)
753 if (strcmp( cname, "payment-required" ) == 0)
755 if (strcmp( cname, "recipient-unavailable" ) == 0)
757 if (strcmp( cname, "redirect" ) == 0)
759 if (strcmp( cname, "registration-required" ) == 0)
761 if (strcmp( cname, "remote-server-not-found" ) == 0)
763 if (strcmp( cname, "remote-server-timeout" ) == 0)
765 if (strcmp( cname, "resource-constraint" ) == 0)
767 if (strcmp( cname, "subscription-required" ) == 0)
769 if (strcmp( cname, "undefined-condition" ) == 0)
771 if (strcmp( cname, "unexpected-request" ) == 0)
774 return 500; // Assume undefined-condition.
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.
782 See what kind of tag is closing, and respond accordingly.
784 static void endElementHandler( void *session, const xmlChar *name) {
785 transport_session* ses = (transport_session*) session;
786 if( ! ses ) { return; }
788 // Bypass a level of indirection, since we'll examine the machine repeatedly:
789 jabber_machine* machine = ses->state_machine;
791 if( machine->in_message && strcmp( (char*) name, "message" ) == 0 ) {
793 /* pass off the message info the callback */
794 if( ses->message_callback ) {
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 ) );
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 );
810 message_set_osrf_xid( msg, ses->osrf_xid_buffer->buf );
812 if( ses->message_error_type->n_used > 0 ) {
813 set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
816 if( msg == NULL ) { return; }
817 ses->message_callback( ses->user_data, msg );
820 machine->in_message = 0;
821 reset_session_buffers( session );
825 if( machine->in_message_body && strcmp( (const char*) name, "body" ) == 0 ) {
826 machine->in_message_body = 0;
830 if( machine->in_subject && strcmp( (const char*) name, "subject" ) == 0 ) {
831 machine->in_subject = 0;
835 if( machine->in_thread && strcmp( (const char*) name, "thread" ) == 0 ) {
836 machine->in_thread = 0;
840 if( machine->in_iq && strcmp( (const char*) name, "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" );
846 osrfLogWarning( OSRF_LOG_MARK, "Error in IQ packet: code %d",
847 ses->message_error_code );
849 reset_session_buffers( session );
853 if( machine->in_presence && strcmp( (const char*) name, "presence" ) == 0 ) {
854 machine->in_presence = 0;
856 if( ses->presence_callback ) {
857 // call the callback with the status, etc.
860 reset_session_buffers( session );
864 if( machine->in_status && strcmp( (const char*) name, "status" ) == 0 ) {
865 machine->in_status = 0;
869 if( machine->in_message_error && strcmp( (const char*) name, "error" ) == 0 ) {
870 machine->in_message_error = 0;
874 if( machine->in_error && strcmp( (const char*) name, "stream:error" ) == 0 ) {
875 machine->in_error = 0;
881 @brief Clear all the buffers of a transport_session.
882 @param ses Pointer to the transport_session whose buffers are to be cleared.
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 );
900 // ------------------------------------------------------------------
901 // takes data out of the body of the message and pushes it into
902 // the appropriate buffer
903 // ------------------------------------------------------------------
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.
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.
914 static void characterHandler(
915 void *session, const xmlChar *ch, int len) {
917 const char* p = (const char*) ch;
919 transport_session* ses = (transport_session*) session;
920 if( ! ses ) { return; }
922 jabber_machine* machine = ses->state_machine;
924 /* set the various message parts */
925 if( machine->in_message ) {
927 if( machine->in_message_body ) {
928 osrf_buffer_add_n( ses->body_buffer, p, len );
931 if( machine->in_subject ) {
932 osrf_buffer_add_n( ses->subject_buffer, p, len );
935 if( machine->in_thread ) {
936 osrf_buffer_add_n( ses->thread_buffer, p, len );
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 );
945 if( machine->in_error ) {
947 strncpy( msg, p, len );
949 osrfLogWarning( OSRF_LOG_MARK,
950 "Text of error message received from Jabber: %s", msg );
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.
960 The XML parser calls this function when it wants to warn about something in the XML.
962 static void parseWarningHandler( void *session, const char* msg, ... ) {
963 VA_LIST_TO_STRING(msg);
964 osrfLogWarning( OSRF_LOG_MARK, VA_BUF );
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.
973 The XML parser calls this function when it finds an error in the XML.
975 static void parseErrorHandler( void *session, const char* msg, ... ){
976 VA_LIST_TO_STRING(msg);
977 osrfLogError( OSRF_LOG_MARK, VA_BUF );
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.
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;