Initial revision
[OpenSRF.git] / src / libtransport / transport_session.c
1 #include "transport_session.h"
2
3
4
5 // ---------------------------------------------------------------------------------
6 // returns a built and allocated transport_session object.
7 // This codes does no network activity, only memory initilization
8 // ---------------------------------------------------------------------------------
9 transport_session* init_transport(  char* server, int port, void* user_data ) {
10
11         /* create the session struct */
12         transport_session* session = 
13                 (transport_session*) safe_malloc( sizeof(transport_session) );
14
15         session->user_data = user_data;
16
17         /* initialize the data buffers */
18         session->body_buffer                    = buffer_init( JABBER_BODY_BUFSIZE );
19         session->subject_buffer         = buffer_init( JABBER_SUBJECT_BUFSIZE );
20         session->thread_buffer          = buffer_init( JABBER_THREAD_BUFSIZE );
21         session->from_buffer                    = buffer_init( JABBER_JID_BUFSIZE );
22         session->status_buffer          = buffer_init( JABBER_STATUS_BUFSIZE );
23         session->recipient_buffer       = buffer_init( JABBER_JID_BUFSIZE );
24         session->message_error_type = buffer_init( JABBER_JID_BUFSIZE );
25
26         /* for OILS extensions */
27         session->router_to_buffer               = buffer_init( JABBER_JID_BUFSIZE );
28         session->router_from_buffer     = buffer_init( JABBER_JID_BUFSIZE );
29         session->router_class_buffer    = buffer_init( JABBER_JID_BUFSIZE );
30         session->router_command_buffer  = buffer_init( JABBER_JID_BUFSIZE );
31
32
33
34         if(     session->body_buffer            == NULL || session->subject_buffer       == NULL        ||
35                         session->thread_buffer  == NULL || session->from_buffer          == NULL        ||
36                         session->status_buffer  == NULL || session->recipient_buffer == NULL ||
37                         session->router_to_buffer       == NULL || session->router_from_buffer   == NULL ||
38                         session->router_class_buffer == NULL || session->router_command_buffer == NULL ) { 
39
40                 fatal_handler( "init_transport(): buffer_init returned NULL" );
41                 return 0;
42         }
43
44
45         /* initialize the jabber state machine */
46         session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
47
48         /* initialize the sax push parser */
49         session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
50
51         /* initialize the transport_socket structure */
52         session->sock_obj = (transport_socket*) safe_malloc( sizeof(transport_socket) );
53
54         //int serv_size = strlen( server );
55         session->sock_obj->server = server;
56         session->sock_obj->port = port;
57         session->sock_obj->data_received_callback = &grab_incoming;
58
59         /* this will be handed back to us in callbacks */
60         session->sock_obj->user_data = session; 
61
62         return session;
63 }
64
65 /* XXX FREE THE BUFFERS */
66 int session_free( transport_session* session ) {
67         if( ! session ) { return 0; }
68
69         if( session->sock_obj ) 
70                 free( session->sock_obj );
71
72         if( session->state_machine ) free( session->state_machine );
73         if( session->parser_ctxt) {
74                 xmlCleanupCharEncodingHandlers();
75                 xmlFreeDoc( session->parser_ctxt->myDoc );
76                 xmlFreeParserCtxt(session->parser_ctxt);
77         }
78         xmlCleanupParser();
79
80         buffer_free(session->body_buffer);
81         buffer_free(session->subject_buffer);
82         buffer_free(session->thread_buffer);
83         buffer_free(session->from_buffer);
84         buffer_free(session->recipient_buffer);
85         buffer_free(session->status_buffer);
86         buffer_free(session->message_error_type);
87         buffer_free(session->router_to_buffer);
88         buffer_free(session->router_from_buffer);
89         buffer_free(session->router_class_buffer);
90         buffer_free(session->router_command_buffer);
91
92
93         free( session );
94         return 1;
95 }
96
97
98 int session_wait( transport_session* session, int timeout ) {
99         if( ! session || ! session->sock_obj ) {
100                 return 0;
101         }
102         int ret =  tcp_wait( session->sock_obj, timeout );
103         if( ! ret ) {
104                 session->state_machine->connected = 0;
105         }
106         return ret;
107 }
108
109 int session_send_msg( 
110                 transport_session* session, transport_message* msg ) {
111
112         if( ! session ) { return 0; }
113
114         if( ! session->state_machine->connected ) {
115                 warning_handler("State machine is not connected in send_msg()");
116                 return 0;
117         }
118
119         message_prepare_xml( msg );
120         tcp_send( session->sock_obj, msg->msg_xml );
121
122         return 1;
123
124 }
125
126
127 /* connects to server and connects to jabber */
128 int session_connect( transport_session* session, 
129                 const char* username, const char* password, const char* resource, int connect_timeout ) {
130
131         int size1 = 0;
132         int size2 = 0;
133
134         if( ! session ) { 
135                 warning_handler( "session is null in connect" );
136                 return 0; 
137         }
138
139
140         /*
141         session->state_machine->connected = 
142                 tcp_connected( session->sock_obj );
143
144         if( session->state_machine->connected ) {
145                 return 1;
146         }
147         */
148
149
150         char* server = session->sock_obj->server;
151
152         if( ! session->sock_obj ) {
153                 return 0;
154         }
155
156         if( ! session->sock_obj->connected ) {
157                 if( ! tcp_connect( session->sock_obj ))
158                         return 0;
159         }
160
161         /* the first Jabber connect stanza */
162         size1 = 100 + strlen( server );
163         char stanza1[ size1 ]; 
164         memset( stanza1, 0, size1 );
165         sprintf( stanza1, 
166                         "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>",
167                         server );
168         
169         /* the second jabber connect stanza including login info*/
170         /* currently, we only support plain text login */
171         size2 = 150 + strlen( username ) + strlen(password) + strlen(resource);
172         char stanza2[ size2 ];
173         memset( stanza2, 0, size2 );
174
175         sprintf( stanza2, 
176                         "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
177                         username, password, resource );
178
179         /* send the first stanze */
180         session->state_machine->connecting = CONNECTING_1;
181         if( ! tcp_send( session->sock_obj, stanza1 ) ) {
182                 warning_handler("error sending");
183                 return 0;
184         }
185
186         /* wait for reply */
187         tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
188
189         /* server acknowledges our existence, now see if we can login */
190         if( session->state_machine->connecting == CONNECTING_2 ) {
191                 if( ! tcp_send( session->sock_obj, stanza2 )  ) {
192                         warning_handler("error sending");
193                         return 0;
194                 }
195         }
196
197         /* wait for reply */
198         tcp_wait( session->sock_obj, connect_timeout );
199
200
201         if( session->state_machine->connected ) {
202                 /* yar! */
203                 return 1;
204         }
205
206         return 0;
207 }
208
209 // ---------------------------------------------------------------------------------
210 // TCP data callback. Shove the data into the push parser.
211 // ---------------------------------------------------------------------------------
212 void grab_incoming( void * session, char* data ) {
213         transport_session* ses = (transport_session*) session;
214         if( ! ses ) { return; }
215         xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
216 }
217
218
219 void startElementHandler(
220         void *session, const xmlChar *name, const xmlChar **atts) {
221
222         transport_session* ses = (transport_session*) session;
223         if( ! ses ) { return; }
224
225         
226         if( strcmp( name, "message" ) == 0 ) {
227                 ses->state_machine->in_message = 1;
228                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
229                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
230                 buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
231                 buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
232                 buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
233                 buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
234                 char* broadcast = get_xml_attr( atts, "broadcast" );
235                 if( broadcast )
236                         ses->router_broadcast = atoi( broadcast );
237
238                 return;
239         }
240
241         if( ses->state_machine->in_message ) {
242
243                 if( strcmp( name, "body" ) == 0 ) {
244                         ses->state_machine->in_message_body = 1;
245                         return;
246                 }
247         
248                 if( strcmp( name, "subject" ) == 0 ) {
249                         ses->state_machine->in_subject = 1;
250                         return;
251                 }
252         
253                 if( strcmp( name, "thread" ) == 0 ) {
254                         ses->state_machine->in_thread = 1;
255                         return;
256                 }
257
258         }
259
260         if( strcmp( name, "presence" ) == 0 ) {
261                 ses->state_machine->in_presence = 1;
262                 buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
263                 buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
264                 return;
265         }
266
267         if( strcmp( name, "status" ) == 0 ) {
268                 ses->state_machine->in_status = 1;
269                 return;
270         }
271
272
273         if( strcmp( name, "stream:error" ) == 0 ) {
274                 ses->state_machine->in_error = 1;
275                 warning_handler( "Received <stream:error> message from Jabber server" );
276                 return;
277         }
278
279
280         /* first server response from a connect attempt */
281         if( strcmp( name, "stream:stream" ) == 0 ) {
282                 if( ses->state_machine->connecting == CONNECTING_1 ) {
283                         ses->state_machine->connecting = CONNECTING_2;
284                 }
285         }
286
287         if( strcmp( name, "error" ) == 0 ) {
288                 ses->state_machine->in_message_error = 1;
289                 buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
290                 ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
291                 warning_handler( "Received <error> message" );
292                 return;
293         }
294
295         if( strcmp( name, "iq" ) == 0 ) {
296                 ses->state_machine->in_iq = 1;
297
298                 if( strcmp( get_xml_attr(atts, "type"), "result") == 0 
299                                 && ses->state_machine->connecting == CONNECTING_2 ) {
300                         ses->state_machine->connected = 1;
301                         ses->state_machine->connecting = 0;
302                         return;
303                 }
304
305                 if( strcmp( get_xml_attr(atts, "type"), "error") == 0 ) {
306                         warning_handler( "Error connecting to jabber" );
307                         return;
308                 }
309         }
310 }
311
312 char* get_xml_attr( const xmlChar** atts, char* attr_name ) {
313         int i;
314         if (atts != NULL) {
315                 for(i = 0;(atts[i] != NULL);i++) {
316                         if( strcmp( atts[i++], attr_name ) == 0 ) {
317                                 if( atts[i] != NULL ) {
318                                         return (char*) atts[i];
319                                 }
320                         }
321                 }
322         }
323         return NULL;
324 }
325
326
327 // ------------------------------------------------------------------
328 // See which tags are ending
329 // ------------------------------------------------------------------
330 void endElementHandler( void *session, const xmlChar *name) {
331         transport_session* ses = (transport_session*) session;
332         if( ! ses ) { return; }
333
334         if( strcmp( name, "message" ) == 0 ) {
335
336                 /* pass off the message info the callback */
337                 if( ses->message_callback ) {
338
339                         /* here it's ok to pass in the raw buffers because
340                                 message_init allocates new space for the chars 
341                                 passed in */
342                         transport_message* msg =  message_init( 
343                                 ses->body_buffer->buf, 
344                                 ses->subject_buffer->buf,
345                                 ses->thread_buffer->buf, 
346                                 ses->recipient_buffer->buf, 
347                                 ses->from_buffer->buf );
348
349                         message_set_router_info( msg, 
350                                 ses->router_from_buffer->buf, 
351                                 ses->router_to_buffer->buf, 
352                                 ses->router_class_buffer->buf,
353                                 ses->router_command_buffer->buf,
354                                 ses->router_broadcast );
355
356                         if( ses->message_error_type->n_used > 0 ) {
357                                 set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
358                         }
359
360                         if( msg == NULL ) { return; }
361                         ses->message_callback( ses->user_data, msg );
362                 }
363
364                 ses->state_machine->in_message = 0;
365                 reset_session_buffers( session );
366                 return;
367         }
368         
369         if( strcmp( name, "body" ) == 0 ) {
370                 ses->state_machine->in_message_body = 0;
371                 return;
372         }
373
374         if( strcmp( name, "subject" ) == 0 ) {
375                 ses->state_machine->in_subject = 0;
376                 return;
377         }
378
379         if( strcmp( name, "thread" ) == 0 ) {
380                 ses->state_machine->in_thread = 0;
381                 return;
382         }
383         
384         if( strcmp( name, "iq" ) == 0 ) {
385                 ses->state_machine->in_iq = 0;
386                 if( ses->message_error_code > 0 ) {
387                         warning_handler( "Error in IQ packet: code %d",  ses->message_error_code );
388                         warning_handler( "Error 401 means not authorized" );
389                 }
390                 reset_session_buffers( session );
391                 return;
392         }
393
394         if( strcmp( name, "presence" ) == 0 ) {
395                 ses->state_machine->in_presence = 0;
396                 /*
397                 if( ses->presence_callback ) {
398                         // call the callback with the status, etc.
399                 }
400                 */
401                 reset_session_buffers( session );
402                 return;
403         }
404
405         if( strcmp( name, "status" ) == 0 ) {
406                 ses->state_machine->in_status = 0;
407                 return;
408         }
409
410         if( strcmp( name, "error" ) == 0 ) {
411                 ses->state_machine->in_message_error = 0;
412                 return;
413         }
414
415         if( strcmp( name, "error:error" ) == 0 ) {
416                 ses->state_machine->in_error = 0;
417                 return;
418         }
419 }
420
421 int reset_session_buffers( transport_session* ses ) {
422         buffer_reset( ses->body_buffer );
423         buffer_reset( ses->subject_buffer );
424         buffer_reset( ses->thread_buffer );
425         buffer_reset( ses->from_buffer );
426         buffer_reset( ses->recipient_buffer );
427         buffer_reset( ses->router_from_buffer );
428         buffer_reset( ses->router_to_buffer );
429         buffer_reset( ses->router_class_buffer );
430         buffer_reset( ses->router_command_buffer );
431         buffer_reset( ses->message_error_type );
432
433         return 1;
434 }
435
436 // ------------------------------------------------------------------
437 // takes data out of the body of the message and pushes it into
438 // the appropriate buffer
439 // ------------------------------------------------------------------
440 void characterHandler(
441                 void *session, const xmlChar *ch, int len) {
442
443         char data[len+1];
444         memset( data, 0, len+1 );
445         strncpy( data, (char*) ch, len );
446         data[len] = 0;
447
448         //printf( "Handling characters: %s\n", data );
449         transport_session* ses = (transport_session*) session;
450         if( ! ses ) { return; }
451
452         /* set the various message parts */
453         if( ses->state_machine->in_message ) {
454
455                 if( ses->state_machine->in_message_body ) {
456                         buffer_add( ses->body_buffer, data );
457                 }
458
459                 if( ses->state_machine->in_subject ) {
460                         buffer_add( ses->subject_buffer, data );
461                 }
462
463                 if( ses->state_machine->in_thread ) {
464                         buffer_add( ses->thread_buffer, data );
465                 }
466         }
467
468         /* set the presence status */
469         if( ses->state_machine->in_presence && ses->state_machine->in_status ) {
470                 buffer_add( ses->status_buffer, data );
471         }
472
473         if( ses->state_machine->in_error ) {
474                 /* for now... */
475                 warning_handler( "ERROR Xml fragment: %s\n", ch );
476         }
477
478 }
479
480 /* XXX change to warning handlers */
481 void  parseWarningHandler( void *session, const char* msg, ... ) {
482
483         va_list args;
484         va_start(args, msg);
485         fprintf(stdout, "WARNING");
486         vfprintf(stdout, msg, args);
487         va_end(args);
488         fprintf(stderr, "XML WARNING: %s\n", msg ); 
489 }
490
491 void  parseErrorHandler( void *session, const char* msg, ... ){
492
493         va_list args;
494         va_start(args, msg);
495         fprintf(stdout, "ERROR");
496         vfprintf(stdout, msg, args);
497         va_end(args);
498         fprintf(stderr, "XML ERROR: %s\n", msg ); 
499
500 }
501
502 int session_disconnect( transport_session* session ) {
503         if( session == NULL ) { return 0; }
504         tcp_send( session->sock_obj, "</stream:stream>");
505         return tcp_disconnect( session->sock_obj );
506 }
507