smsc_soap.c fixed - comments?
"Alex Kinch" <[email protected]>
| Newsgroups | gmane.comp.mobile.kannel.devel |
|---|---|
| Message-ID | <0a3601c31f23$dd10d490$6601a8c0@alex> |
Hi, After a bit of fiddling I think I've managed to fix smsc_soap.c to take into account dlr and http changes. I've attached it to this email for your comments. It runs ok on my build, but I haven't actually managed to get it process an XML post yet, still wresting with the template formats. Alex PS: This is my first bit of C hacking, so bear with me :-)
smsc_soap.c
(application/octet-stream, 68.1 KB)
/* * smsc_soap.c - Implementation of SOAP (XML over HTTP) as a Kannel module * * Oded Arbel, m-Wise inc ([email protected]) * * * Changelog: * * 20/02/2002: started - copied smsc_mam.c for starting * 25/02/2002: implemented MT sending * 09/05/2002: fixed problem crash when HTTP connection fails. * send message back to bearerbox on HTTP failure instead of local queue * 19/05/2002: strip leading + from international numbers * 20/05/2002: fixed previous change * changed Transaction Id returned to support 64 bit integers * 27/05/2002: changed DLR creation to store the transaction ID instead of timestamp * added parsing of human readable time in DLR * 28/05/2002: added multi thread sending support * 02/06/2002: changed validity computing to accept minutes instead of seconds * 04/06/2002: Changed callbacks to take into account that they might be called while the connection * is dead. * 04/06/2002: Started to implement generic parsing engine. * 09/06/2002: Removed hardcoded XML generation and parsing * 22/07/2002: Removed wrong assignment of charset_convert return code to msg->sms.coding * 30/07/2002: fixed wrong format for year in soap_write_date * additional debug and process for invalid charset conversion * 04/08/2002: forced chraset_conversion to/from UCS-2 to use big endianity * added curly bracing support to XML data tokens * * TO-DO: * - add a configuration option to the max number of messages a client can send, and use * and implement KeepAlive in the clients. * - support XML generation through DTD * - support XML parsing through DTD * * * Usage: add the following to kannel.conf: * * group = smsc * smsc = soap * send-url = <URI> - URI to send SOAP bubbles at (mandatory) * receive-port = <number> - port number to bind our server on (Default: disabled - MT only) * xml-files = "MT.xml;MO.xml;DLR.xml" - XML templates for generation of MT messages and MO and DLR responses * xmlspec-files = "MT.spec;MO.spec;DLR.spec" - XML path spec files for parsing of MT response and MO and DLR submission * alt-charset = "character map" - charset in which a text message is received (default UTF-8) **/ #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <limits.h> #include "gwlib/gwlib.h" #include "smscconn.h" #include "smscconn_p.h" #include "bb_smscconn_cb.h" #include "msg.h" #include "sms.h" #include "dlr.h" // libxml include #include <libxml/xmlmemory.h> #include <libxml/parser.h> /* * Defines and defaults **/ #define SOAP_SLEEP_TIME 0.01 #define SOAP_MAX_MESSAGE_PER_ROUND 1 #define SOAP_DEFAULT_SENDER_STRING "m-Wise inc." #define SOAP_DEFAULT_VALIDITY 60 // URIs for MOs and delivery reports #define SOAP_MO_URI "/mo" #define SOAP_DLR_URI "/dlr" // default reponses to HTTP queries #define SOAP_DEFAULT_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>No method by that name</Error>" #define SOAP_ERROR_NO_DLR_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>Sorry - no DLR for that MT</Error>" #define SOAP_ERROR_DLR_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>Fatal error while trying to parse delivery report</Error>" #define SOAP_ERROR_MO_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>Fatal error while trying to incoming MO</Error>" #define SOAP_ERROR_NO_DATA_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>No data received</Error>" #define SOAP_ERROR_MALFORMED_DATA_MESSAGE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Error>Malformed data received</Error>" // error codes for HTTP queries #define SOAP_ERROR_NO_DLR_CODE 405 #define SOAP_DEFAULT_CODE 404 #define SOAP_ERROR_DLR_CODE 500 #define SOAP_ERROR_MO_CODE 500 #define SOAP_ERROR_NO_DATA_CODE 501 #define SOAP_ERROR_MALFORMED_DATA_CODE 502 #define SOAP_QUERY_OK 200 // compile time configuration defines #undef HUMAN_TIME #define MIN_SOAP_CLIENTS 5 #define MAX_SOAP_CLIENTS 50 #define CLIENT_BUSY_TIME 5 #define CLIENT_TEARDOWN_TIME 600 #define CLIENT_BUSY_LOAD 5 // private data store for the SOAP module typedef struct privdata { List *outgoing_queue; // queue to hold unsent messages long listener_thread; // SOAP HTTP client and module managment long server_thread; // SOAP HTTP server int shutdown; // Internal signal to shut down int soap_server; // internal signal to shut down the server long port; // listener port int ssl; // flag whether to use SSL for the server Octstr *uri; // URI to send MTs on Octstr *allow_ip, *deny_ip; // connection allowed mask List* soap_client; // list to hold callers Octstr* name; // connection name for use in private functions that want to do logging // SOAP configurtion Octstr* form_variable; // variable name used in post int form_urlencoded; // whether to send the data urlencoded or multipart Octstr* alt_charset; // alt-charset to use Octstr* mt_xml_file; Octstr* mt_spec_file; Octstr* mo_xml_file; Octstr* mo_spec_file; Octstr* dlr_xml_file; Octstr* dlr_spec_file; } PrivData; // struct to hold one HTTP client connection (I hope) typedef struct client_data { time_t last_access; unsigned long requests; HTTPCaller* caller; } ClientData; // struct useful for the XML mapping routines typedef struct argument_map { Octstr* name; Octstr* path; Octstr* attribute; Octstr* sscan_type; void* store; } ArgumentMap; // useful macros go here (some of these were ripped of other modules, // so maybe its better to put them in a shared file) #define O_DESTROY(a) { if(a) octstr_destroy(a); a=NULL; } typedef long long int64; /* * SOAP module public API towards bearerbox **/ // module entry point - will also be defined in smsCconn_p.h int smsc_soap_create(SMSCConn *conn, CfgGroup *cfg); // callback for bearerbox to add messages to our queue static int soap_add_msg_cb(SMSCConn *conn, Msg *sms); // callback for bearerbox to signal a shutdown static int soap_shutdown_cb(SMSCConn *conn, int finish_sending); // callback for bearerbox to signal us to start the connection static void soap_start_cb(SMSCConn *conn); // callback for bearerbox to signal us to drop the connection static void soap_stop_cb(SMSCConn *conn); // callback for bearerbox to query on the number of messages in our queue static long soap_queued_cb(SMSCConn *conn); /* * SOAP module thread functions (created by smsc_soap_create()) **/ // SOAP module thread for launching HTTP clients. static void soap_listener(void *arg); // SOAP HTTP server thread for incoming MO static void soap_server(void *arg); /* * SOAP module internal protocol implementation functions **/ // start the loop to send all messages in the queue static void soap_send_loop(SMSCConn *conn); // function used to send a single MT message static void soap_send(PrivData* privdata, Octstr* xmlbuffer, Msg* msgid); // called to retrieve HTTP responses from the HTTP library static void soap_read_response(SMSCConn *conn); // format a messages structure as an XML buffer static Octstr* soap_format_xml(Octstr * xml_file, Msg* msg); // parse a response from the SOAP server to get the message ID static int64 soap_parse_response(PrivData* privdata, Octstr *xmlResponse); // parse an incoming MO xml static long soap_parse_mo(SMSCConn *conn, Octstr *request, Octstr **response); // parse an incoming derlivery report static long soap_parse_dlr(SMSCConn *conn, Octstr *request, Octstr **response); /* * SOAP internal utility functions **/ // parse an integer out of an XML node int soap_xmlnode_get_long(xmlNodePtr cur, long* out); // parse an int64 out of an XML node int soap_xmlnode_get_int64(xmlNodePtr cur, int64* out); // parse a string out of an XML node int soap_xmlnode_get_octstr(xmlNodePtr cur, Octstr **out); // convert a one2one date format to epoch time time_t soap_read_date(Octstr* dateString); // convert a epoch time to one2one date format static Octstr* soap_write_date(time_t date); // start the SOAP server int soap_server_start(SMSCConn *conn); // stop the SOAP server static void soap_server_stop(PrivData* privdata); // create a new SOAP client caller static ClientData* soap_create_client_data(); // destroy a SOAP client caller static void soap_destroy_client_data(void* data); // start an HTTP query static void soap_client_init_query(PrivData* privdata, List* headers, Octstr* data, Msg* msg); // return a caller from the pool that has responses waiting static ClientData* soap_client_have_response(List* client_list); // return data from a message according to its name static Octstr* soap_convert_token(Msg* msg, Octstr* name); // convert a XML parsing spec file and a list of recognized keywords to an argument map List* soap_create_map(Octstr* spec, long count, char* keywords[], char* types[], void* storage[]); // destroy a map structure void soap_destroy_map(void *item); // map content in an XML structure to a list of variable using a spec file int soap_map_xml_data(xmlNodePtr xml, List* maps); /**************************************************************************************/ /* * Implementation **/ /* * function smsc_soap_create() * called to create and initalize the module's internal data. * if needed also will start the connection threads * Input: SMSCConn pointer to connection data, cfgGroup pointer to configuration data * Returns: status (0 = OK, -1 = failed) */ int smsc_soap_create(SMSCConn *conn, CfgGroup *cfg) { PrivData *privdata; Octstr* temp = NULL; List* filenames = NULL; // allocate and init internat data structure privdata = gw_malloc(sizeof(PrivData)); privdata->outgoing_queue = list_create(); // privdata->pending_ack_queue = list_create(); privdata->shutdown = 0; privdata->soap_client = NULL; privdata->soap_server = 0; // read configuration data if (cfg_get_integer(&(privdata->port), cfg, octstr_imm("receive-port-ssl")) == -1) if (cfg_get_integer(&(privdata->port), cfg, octstr_imm("receive-port")) == -1) privdata->port = 0; else privdata->ssl = 0; else privdata->ssl = 1; privdata->uri = cfg_get(cfg, octstr_imm("send-url")); privdata->allow_ip = cfg_get(cfg, octstr_imm("connect-allow-ip")); if (privdata->allow_ip) privdata->deny_ip = octstr_create("*.*.*.*"); else privdata->deny_ip = NULL; // read XML configuration privdata->form_variable = cfg_get(cfg, octstr_imm("form-variable")); cfg_get_bool(&(privdata->form_urlencoded), cfg, octstr_imm("form-urlencoded")); privdata->alt_charset = cfg_get(cfg, octstr_imm("alt-charset")); if (!privdata->alt_charset) privdata->alt_charset = octstr_create("utf-8"); // check validity of stuff if (privdata->port <= 0 || privdata->port > 65535) { error(0, "invalid port definition for SOAP server (%ld) - aborting", privdata->port); goto error; } if (!privdata->uri) { error(0, "invalid or missing send-url definition for SOAP - aborting."); goto error; } if (!privdata->form_variable) { error(0, "invalid or missing form variable name definition for SOAP - aborting."); goto error; } // load XML templates and specs filenames = octstr_split(temp = cfg_get(cfg,octstr_imm("xml-files")), octstr_imm(";")); octstr_destroy(temp); if (list_len(filenames) < 3) { error(0,"Not enough template files for XML generation, you need 3 - aborting"); goto error; } if ( !(privdata->mt_xml_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load XML template for MT - aborting"); goto error; } octstr_destroy(temp); if ( !(privdata->mo_xml_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load XML template for MO - aborting"); goto error; } octstr_destroy(temp); if ( !(privdata->dlr_xml_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load XML template for DLR - aborting"); goto error; } octstr_destroy(temp); list_destroy(filenames, octstr_destroy_item); filenames = octstr_split(temp = cfg_get(cfg,octstr_imm("xmlspec-files")), octstr_imm(";")); octstr_destroy(temp); if (list_len(filenames) < 3) { error(0,"Not enough spec files for XML parsing, you need 3 - aborting"); goto error; } if ( !(privdata->mt_spec_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load spec for MT parsing - aborting"); goto error; } octstr_destroy(temp); if ( !(privdata->mo_spec_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load spec for MO parsing - aborting"); goto error; } octstr_destroy(temp); if ( !(privdata->dlr_spec_file = octstr_read_file(octstr_get_cstr(temp = list_extract_first(filenames))))) { error(0,"Can't load spec for DLR parsing - aborting"); goto error; } octstr_destroy(temp); list_destroy(filenames, octstr_destroy_item); debug("bb.soap.create",0,"Connecting to %s", octstr_get_cstr(privdata->uri)); // store private data struct in connection data conn->data = privdata; // state my name conn->name = octstr_format("SOAP: %s", octstr_get_cstr(privdata->uri) ); privdata->name = octstr_duplicate(conn->id); // init status vars conn->status = SMSCCONN_CONNECTING; conn->connect_time = time(NULL); // set up call backs for bearerbox conn->shutdown = soap_shutdown_cb; conn->queued = soap_queued_cb; conn->start_conn = soap_start_cb; conn->stop_conn = soap_stop_cb; conn->send_msg = soap_add_msg_cb; privdata->listener_thread = 0; privdata->server_thread = 0; // check whether we can start right away if (! conn->is_stopped) // yes, we can conn->status = SMSCCONN_CONNECTING; else conn->status = SMSCCONN_DISCONNECTED; // any which way - start the connection thread if ( (privdata->listener_thread = gwthread_create(soap_listener, conn)) == -1) { error(0, "soap_create failed to spawn thread - aborting"); goto error; } return 0; // done - ok error: // oh oh, problems error(0, "Failed to create SOAP smsc connection"); // release stuff if (privdata != NULL) { list_destroy(privdata->outgoing_queue, NULL); // list_destroy(privdata->pending_ack_queue, NULL); O_DESTROY(privdata->uri); O_DESTROY(privdata->allow_ip); O_DESTROY(privdata->deny_ip); } gw_free(privdata); octstr_destroy(temp); list_destroy(filenames, octstr_destroy_item); // notify bearerbox conn->why_killed = SMSCCONN_KILLED_CANNOT_CONNECT; conn->status = SMSCCONN_DEAD; info(0, "exiting"); return -1; // I'm dead } /* * Callbacks **/ /* * function soap_add_msg_cb() * get a message and copy it to the queue. note that message must be copied * as I don't know what bearerbox wants to do with it after I return * Input: SMSCConn connection state data, Msg to send * Returns: status - 0 on success, -1 on fail. **/ static int soap_add_msg_cb(SMSCConn *conn, Msg *sms) { PrivData *privdata = conn->data; Msg *copy; // I'm dead and cannot take any calls at the moment, please don't leave a message if (conn->status == SMSCCONN_DEAD) return -1; copy = msg_duplicate(sms); // copy the message list_append(privdata->outgoing_queue, copy); // put it in the queue debug("bb.soap.add_msg",0,"SOAP[%s]: got a new MT from %s, list has now %ld MTs", octstr_get_cstr(privdata->name), octstr_get_cstr(sms->sms.sender), list_len(privdata->outgoing_queue)); gwthread_wakeup(privdata->listener_thread); return 0; } /* * function soap_shutdown_cb() * called by bearerbox to signal the module to shutdown. sets the shutdown flags, * wakes up the listener thread and exits (if we add more threads, we need to handle those too) * Input: SMSCConn connection state data, flag indicating whether we can finish sending messages * in the queue first. * Returns: status - 0 on success, -1 on fail. **/ static int soap_shutdown_cb(SMSCConn *conn, int finish_sending) { PrivData *privdata = conn->data; long thread; // I'm dead, there's really no point in killing me again, is it ? if (conn->status == SMSCCONN_DEAD) return -1; debug("bb.soap.cb", 0, "SOAP[%s]: Shutting down SMSCConn, %s", octstr_get_cstr(privdata->name), finish_sending ? "slow" : "instant"); // Documentation claims this would have been done by smscconn.c, but isn't when this code is being written. conn->why_killed = SMSCCONN_KILLED_SHUTDOWN; // Separate from why_killed to avoid locking, as why_killed may be changed from outside? privdata->shutdown = 1; if (finish_sending == 0) { Msg *msg; while ((msg = list_extract_first(privdata->outgoing_queue)) != NULL) bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_SHUTDOWN); } thread = privdata->listener_thread; gwthread_wakeup(thread); gwthread_join(thread); return 0; } /* * function soap_start_cb() * called by bearerbox when the module is allowed to start working * Input: SMSCConn connection state data * Returns: status - 0 on success, -1 on fail. **/ static void soap_start_cb(SMSCConn *conn) { PrivData *privdata = conn->data; debug("smsc.soap.start", 0, "SOAP[%s]: start called", octstr_get_cstr(privdata->name)); // set the status so that connection_thread will know what to do conn->status = SMSCCONN_CONNECTING; // start connection_thread, in case its not started. if ( (! privdata->listener_thread) && ( (privdata->listener_thread = gwthread_create(soap_listener, conn)) == -1 ) ) { error(0, "soap_start failed to spawn thread - aborting"); conn->why_killed = SMSCCONN_KILLED_CANNOT_CONNECT; conn->status = SMSCCONN_DEAD; privdata->shutdown = 1; return; } // gwthread_wakeup(privdata->listener_thread); debug ("smsc.soap.start",0,"SOAP[%s]: starting OK", octstr_get_cstr(privdata->name)); } /* * function soap_stop_cb() * this function may be used to 'pause' the module. it should cause the connection * to logout, but not to be destroyed, so it will be restarted later. * Input: SMSCConn connection state data **/ static void soap_stop_cb(SMSCConn *conn) { PrivData *privdata = conn->data; // I'm dead, its really too late to take a break now if (conn->status == SMSCCONN_DEAD) return; debug("smsc.soap.stop", 0, "SOAP[%s]: stop called", octstr_get_cstr(privdata->name)); // make connection thread disconnect conn->status = SMSCCONN_DISCONNECTED; } /* * function soap_queued_cb() * called by bearerbox to query the number of messages pending send. * the number returned includes the number of messages sent, but for which no ACK was yet received. * Input: SMSCConn connection state data * Returns: number of messages still waiting to be sent **/ static long soap_queued_cb(SMSCConn *conn) { PrivData *privdata = conn->data; long ret; // I'm dead, so I have no queues - well there ! if (conn->status == SMSCCONN_DEAD) return -1; ret = list_len(privdata->outgoing_queue); // + list_len(privdata->pending_ack_queue); // use internal queue as load, maybe something else later conn->load = ret; return ret; } /* * SOAP module thread functions (created by smsc_soap_create()) **/ /* * function soap_listener() * entry point to the listenr thread. this thread listenes on the MO port (if * needed, and is also reposnsible for invoking "MT threads" (HTTP clients) to * to send MTs. * Input: SMSCConn connection state data **/ static void soap_listener(void *arg) { SMSCConn *conn = arg; PrivData *privdata = conn->data; Msg *msg = NULL; debug("bb.soap.listener",0,"SOAP[%s]: listener entering", octstr_get_cstr(privdata->name)); while (!privdata->shutdown) { // check connection status switch (conn->status) { case SMSCCONN_RECONNECTING: case SMSCCONN_CONNECTING: if (privdata->soap_server) { soap_server_stop(privdata); } if (soap_server_start(conn)) { privdata->shutdown = 1; error(0, "SOAP[%s]: failed to start HTTP server!", octstr_get_cstr(privdata->name)); break; } mutex_lock(conn->flow_mutex); conn->status = SMSCCONN_ACTIVE; mutex_unlock(conn->flow_mutex); bb_smscconn_connected(conn); break; case SMSCCONN_DISCONNECTED: if (privdata->soap_server) soap_server_stop(privdata); break; case SMSCCONN_ACTIVE: if (!privdata->soap_server) { mutex_lock(conn->flow_mutex); conn->status = SMSCCONN_RECONNECTING; mutex_unlock(conn->flow_mutex); break; } // run the normal send/receive loop if (list_len(privdata->outgoing_queue) > 0) { // we have messages to send soap_send_loop(conn); // send any messages in queue } break; case SMSCCONN_DEAD: // this shouldn't happen here - I'm the only one allowed to set SMSCCONN_DEAD default: break; } soap_read_response(conn); // collect HTTP responses gwthread_sleep(SOAP_SLEEP_TIME); // sleep for a while so I wont busy-loop } debug("bb.soap.connection",0,"SOAP[%s]: connection shutting down", octstr_get_cstr(privdata->name)); soap_server_stop(privdata); // send all queued messages to bearerbox for recycling debug("bb.soap.connection",0,"SOAP[%s]: sending messages back to bearerbox", octstr_get_cstr(privdata->name)); while ((msg = list_extract_first(privdata->outgoing_queue)) != NULL) bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_SHUTDOWN); // lock module public state data mutex_lock(conn->flow_mutex); debug("bb.soap.connection",0,"SOAP[%s]: playing dead", octstr_get_cstr(privdata->name)); conn->status = SMSCCONN_DEAD; // set state // destroy lists debug("bb.soap.connection",0,"SOAP[%s]: don't need the queue anymore", octstr_get_cstr(privdata->name)); list_destroy(privdata->outgoing_queue, NULL); //list_destroy(privdata->pending_ack_queue, NULL); // clear the soap client collection debug("bb.soap.connection",0,"SOAP[%s]: tell caller to stop", octstr_get_cstr(privdata->name)); if (privdata->soap_client) list_destroy(privdata->soap_client, soap_destroy_client_data); // destroy private data stores debug("bb.soap.connection",0,"SOAP[%s]: done with privdata", octstr_get_cstr(privdata->name)); O_DESTROY(privdata->uri); O_DESTROY(privdata->allow_ip); O_DESTROY(privdata->deny_ip); gw_free(privdata); conn->data = NULL; mutex_unlock(conn->flow_mutex); debug("bb.soap.connection", 0, "SOAP: module has completed shutdown."); bb_smscconn_killed(); } /* * function soap_server() * server thread - accepts incoming MOs * Input: SMSCConn connection state data **/ static void soap_server(void* arg) { SMSCConn* conn = (SMSCConn*)arg; PrivData* privdata = conn->data; // PrivData* privdata = (PrivData*)arg; HTTPClient* remote_client = NULL; List *request_headers = NULL, *response_headers = NULL; List *cgivars = NULL; Octstr *client_ip = NULL, *request_uri = NULL, *request_body = NULL; Octstr *response_body = NULL; Octstr *timebuf = NULL; int http_response_status; debug("bb.soap.server",0,"SOAP[%s]: Server starting", octstr_get_cstr(privdata->name)); // create basic headers response_headers = http_create_empty_headers(); http_header_add(response_headers, "Content-type","text/xml"); //http_header_add(response_headers, "Content-type","application/x-www-form-urlencoded"); //http_header_add(response_headers,"Connection", "Close"); http_header_add(response_headers, "Server","Kannel"); while (privdata->soap_server) { if ( (remote_client = http_accept_request(privdata->port, &client_ip, &request_uri, &request_headers, &request_body, &cgivars)) ) { debug("bb.soap.server",0,"SOAP[%s]: server got a request for %s from %s, with body <%s>", octstr_get_cstr(privdata->name), octstr_get_cstr(request_uri),octstr_get_cstr(client_ip), request_body?octstr_get_cstr(request_body):"<null>"); // parse request if (!octstr_compare(request_uri,octstr_imm(SOAP_MO_URI))) { // this is an incoming MO if ((http_response_status = soap_parse_mo(conn,request_body, &response_body)) == -1) { // fatal error parsing MO error(0,"SOAP[%s]: fatal error parsing MO", octstr_get_cstr(privdata->name)); response_body = octstr_create(SOAP_ERROR_MO_MESSAGE); http_response_status = SOAP_ERROR_MO_CODE; } } else if (!octstr_compare(request_uri,octstr_imm(SOAP_DLR_URI))) { // a delivery report if ((http_response_status = soap_parse_dlr(conn,request_body, &response_body)) == -1) { // fatal error parsing MO error(0,"SOAP[%s]: fatal error parsing DLR", octstr_get_cstr(privdata->name)); response_body = octstr_create(SOAP_ERROR_DLR_MESSAGE); http_response_status = SOAP_ERROR_DLR_CODE; } } else { // unknown command send default message response_body = octstr_create(SOAP_DEFAULT_MESSAGE); http_response_status = SOAP_DEFAULT_CODE; } // create response /* response_body = octstr_create("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<!DOCTYPE SMSCACCESS_REPLY SYSTEM \"http://superion/~oded/smsc_reply-1_0.dtd\">\n" "<SMSCACCESS_REPLY>\n" " <SUBSCRIBER>447951718145</SUBSCRIBER>\n" " <DATE_RECEIVED>22/01/2002:15:12</DATE_RECEIVED>\n" " <RETURN_CODE>00</RETURN_CODE>\n" "</SMSCACCESS_REPLY>\n"); */ // encode date in headers timebuf = date_format_http(time(NULL)); http_header_add(response_headers, "Date", octstr_get_cstr(timebuf)); O_DESTROY(timebuf); // http_header_dump(response_headers); // send response back to client http_send_reply(remote_client,http_response_status,response_headers, response_body); // destroy response data //http_destroy_headers(response_headers); O_DESTROY(response_body); // destroy request data O_DESTROY(request_uri); O_DESTROY(request_body); O_DESTROY(client_ip); http_destroy_headers(request_headers); list_destroy(cgivars, NULL); } gwthread_sleep(SOAP_SLEEP_TIME); } debug("bb.soap.server",0,"SOAP[%s]: server going down", octstr_get_cstr(privdata->name)); // privdata->server_thread = 0; } /* * SOAP module internal protocol implementation functions **/ /* * function soap_send_loop() * called when there are messages in the queue waiting to be sent * Input: SMSCConn connection state data **/ static void soap_send_loop(SMSCConn* conn) { PrivData* privdata = conn->data; Msg *msg; Octstr* xmldata = NULL; int counter = 0; size_t ret; debug("bb.soap.client",0,"SOAP[%s]: client - entering", octstr_get_cstr(privdata->name)); while ( (counter < SOAP_MAX_MESSAGE_PER_ROUND) && (msg = list_extract_first(privdata->outgoing_queue)) ) { // as long as we have some messages ++counter; if (!msg->sms.id) /* generate a message id */ msg->sms.id = gw_generate_id(); /* convert message data to target encoding */ if (msg->sms.coding == DC_UCS2) { debug("bb.soap.send_loop", 0, "converting from USC-2 to %s", octstr_get_cstr(privdata->alt_charset)); ret = charset_convert(msg->sms.msgdata, "UCS-2BE", octstr_get_cstr(privdata->alt_charset)); } else { debug("bb.soap.send_loop", 0, "converting from ISO-8859-1 to %s", octstr_get_cstr(privdata->alt_charset)); ret = charset_convert(msg->sms.msgdata, "ISO-8859-1", octstr_get_cstr(privdata->alt_charset)); } if (ret == -1) { error(0, "charset_convert failed"); octstr_dump(msg->sms.msgdata, 0); bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_MALFORMED); continue; } /* format the messages as a character buffer to send */ if (!(xmldata = soap_format_xml(privdata->mt_xml_file, msg))) { debug("bb.soap.client",1,"SOAP[%s]: client - failed to format message for sending", octstr_get_cstr(privdata->name)); bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_MALFORMED); continue; } debug("bb.soap.client",0,"SOAP[%s]: client - Sending message <%s>", octstr_get_cstr(privdata->name), octstr_get_cstr(msg->sms.msgdata)); debug("bb.soap.client",0,"SOAP[%s]: data dump: <%s>", octstr_get_cstr(privdata->name), octstr_get_cstr(xmldata)); // send to the server soap_send(privdata, xmldata, msg); // store in the second queue so that soap_read_response will know what to do //list_append(privdata->pending_ack_queue,msg); // don't need this anymore O_DESTROY(xmldata); } } /* * function soap_format_xml() * fill in the fields in an XML template with data from a message * Input: Octstr containing an XML template, Msg structure * Returns: Octstr xml formated data or NULL on error **/ static Octstr* soap_format_xml(Octstr* xml_file, Msg* msg) { Octstr* xml; long t; long start = -1; int curly_enclose = 0; xml = octstr_create(""); for (t = 0; t < octstr_len(xml_file); ++t) { unsigned char c; if ((c = octstr_get_char(xml_file,t)) == '%') { // found start of token start = t+1; continue; } if (c == '{' && start == t) { // the token is enclosed in curlys ++start; // make sure the token is read from the next char curly_enclose=1; } if (start < 0) octstr_append_char(xml,c); else if ( (curly_enclose && (c == '}')) // end of token in case of curly enclosure || (!curly_enclose && !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) ) { // found end of token Octstr *data, *token; token = octstr_copy(xml_file,start,(t-start)); if ((data = soap_convert_token(msg, token))) { octstr_append(xml, data); octstr_destroy(data); } else error(0,"SOAP: format_xml - failed to format token %s using message",octstr_get_cstr(token)); octstr_destroy(token); start = -1; if (!curly_enclose) --t; // I want to get that char again, to let the normal behaviour deal with it - only if it's not the ending curly else curly_enclose = 0; } } return xml; } /* * function soap_send() * send an XML buffer using POST to the SOAP server. * Input: PrivData connection state, Octstr XML formatted data buffer, Message pointer to store with request **/ static void soap_send(PrivData* privdata, Octstr* xmlbuffer, Msg* msg) { List *requestHeaders; Octstr* postdata; // create request headers requestHeaders = http_create_empty_headers(); http_header_add(requestHeaders, "User-Agent", "Kannel " VERSION); if (privdata->form_urlencoded) { http_header_add(requestHeaders, "Content-Type", "application/x-www-form-urlencoded"); postdata = octstr_format("%S=%E", privdata->form_variable, xmlbuffer); } else { http_header_add(requestHeaders, "Content-Type", "multipart/form-data, boundary=AaB03x"); postdata = octstr_format("--AaB03x\r\n" "content-disposition: form-data; name=\"%S\"\r\n\r\n%S", privdata->form_variable, xmlbuffer); } // send the request along soap_client_init_query(privdata, requestHeaders, postdata, msg); O_DESTROY(postdata); // done with that http_destroy_headers(requestHeaders); return; } /* * function soap_read_response() * check my HTTP caller for responses and act on them * Input: PrivData connection state **/ static void soap_read_response(SMSCConn *conn) { PrivData *privdata = conn->data; Msg* msg; Octstr *responseBody, *responseURL; List* responseHeaders; int responseStatus; int64 msgID; ClientData* cd; // don't get in here unless I have some callers // (I shouldn't have one before I start sending messages) if (!list_len(privdata->soap_client)) return; // see if we have any responses pending if (!(cd = soap_client_have_response(privdata->soap_client))) return; cd->requests--; msg = http_receive_result(cd->caller, &responseStatus, &responseURL, &responseHeaders, &responseBody); if (!msg) // no responses here { debug("bb.soap.read_response",0,"SOAP[%s]: sorry, no response", octstr_get_cstr(privdata->name)); return; } if (responseStatus == -1) { debug("bb.soap.read_response",0,"SOAP[%s]: HTTP connection failed - blame the server (requeing msg)", octstr_get_cstr(privdata->name)); bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_TEMPORARILY); /* list_append(privdata->outgoing_queue, msg); */ return; } debug("bb.soap.read_response",0,"SOAP[%s]: got a response %d=<%s>", octstr_get_cstr(privdata->name), responseStatus, responseBody?octstr_get_cstr(responseBody):octstr_get_cstr(octstr_imm("NULL"))); // got a message from HTTP, parse it if ( (msgID = soap_parse_response(privdata, responseBody)) != -1) { // ack char tmpid[30]; sprintf(tmpid,"%lld",msgID); debug("bb.soap.read_response",0,"SOAP[%s]: ACK - id: %lld", octstr_get_cstr(privdata->name), msgID); dlr_add(octstr_get_cstr(conn->id), tmpid, octstr_get_cstr(msg->sms.sender), octstr_get_cstr(msg->sms.receiver), octstr_get_cstr(msg->sms.service), octstr_get_cstr(msg->sms.dlr_url), msg->sms.dlr_mask, octstr_get_cstr(msg->sms.boxc_id)); // generate SMSC success DLR if (msg->sms.dlr_mask & DLR_SUCCESS) { Msg* dlrmsg; dlrmsg = dlr_find(octstr_get_cstr(conn->id),tmpid, octstr_get_cstr(msg->sms.receiver), /* destination */ DLR_SUCCESS); if (dlrmsg) { debug("bb.soap.read_response",0,"SOAP[%s]: sending DLR success", octstr_get_cstr(privdata->name)); octstr_insert_data(dlrmsg->sms.msgdata, 0, "Success/",8); bb_smscconn_receive(conn, dlrmsg); } else error(0,"SOAP[%s]: Got SMSC_ACK but couldnt find message", octstr_get_cstr(privdata->name)); } // send msg back to bearerbox for recycling bb_smscconn_sent(conn, msg); } else { // nack debug("bb.soap.read_response",0,"SOAP[%s]: NACK", octstr_get_cstr(privdata->name)); if (msg->sms.dlr_mask & DLR_SMSC_FAIL) { Msg* dlrmsg; char tmpid[30]; sprintf(tmpid,"%lld",msgID); dlr_add(octstr_get_cstr(conn->id), tmpid, octstr_get_cstr(msg->sms.sender), octstr_get_cstr(msg->sms.receiver), octstr_get_cstr(msg->sms.service), octstr_get_cstr(msg->sms.dlr_url), msg->sms.dlr_mask, octstr_get_cstr(msg->sms.boxc_id)); dlrmsg = dlr_find(octstr_get_cstr(conn->id),tmpid, octstr_get_cstr(msg->sms.receiver), /* destination */ DLR_SMSC_FAIL); if (dlrmsg) { debug("bb.soap.read_response",0,"SOAP[%s]: sending DLR failed", octstr_get_cstr(privdata->name)); octstr_insert_data(dlrmsg->sms.msgdata, 0, "Failed/",7); bb_smscconn_receive(conn, dlrmsg); } else error(0,"SOAP[%s]: Got SMSC_NACK but couldnt find DLR message", octstr_get_cstr(privdata->name)); } // send msg back to bearerbox for recycling bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_MALFORMED); } http_destroy_headers(responseHeaders); O_DESTROY(responseBody); O_DESTROY(responseURL); } /* * function soap_parse_response() * parse the response from the server to find the message ID * Input: Connection session data, Octstr xml buffer * Returns: message ID parsed or -1 if parsing failed (for example - a NACK received) * * Possible bug : I use list_get() liberaly here, after checking that I have enough items, * but if list_get() returns NULL for an empty item, things might break - and * not in a nice way. **/ static int64 soap_parse_response(PrivData* privdata, Octstr* xmlResponse) { int64 msgID = -1; long responseStatus = -1; xmlDocPtr responseDoc; xmlNodePtr root; List* maps; char* keywords[] = { "id", "result" }; char* sscans[] = { "%lld", "%ld" }; void* pointers[] = { &msgID, &responseStatus }; if (!xmlResponse) return -1; // FIXME: do something here // parse XML if ( !(responseDoc = xmlParseDoc(octstr_get_cstr(xmlResponse))) ) { error(0,"SOAP[%s]: couldn't parse XML response <%s> in MT parsing", octstr_get_cstr(privdata->name), octstr_get_cstr(xmlResponse)); return -1; } // get root element if ( ! (root = xmlDocGetRootElement(responseDoc)) ) { error(0,"SOAP[%s]: couldn't get XML root element in MT parsing", octstr_get_cstr(privdata->name)); xmlFreeDoc(responseDoc); return -1; } // create the argument map maps = soap_create_map(privdata->mt_spec_file, 2, keywords, sscans, pointers); // run the map and the xml through the parser if (soap_map_xml_data(root, maps) < 2) { error(0,"SOAP[%s]: failed to map all the arguments from the XML data", octstr_get_cstr(privdata->name)); } list_destroy(maps, soap_destroy_map); // done with the document xmlFreeDoc(responseDoc); if (msgID == -1) { error(0,"SOAP: parse_response - failed to parse response"); return -1; } if (responseStatus != 0) { error(0,"SOAP: parse_response - response code isn't 0 ! (%ld)", responseStatus); return -1; } return msgID; } /* * function soap_parse_mo() * parse an incoming MO xml request, build a message from it and sent it. * also generate the reponse text and status code * Input: module public state data, request body * Output: response body * Returns: HTTP status code on successful parse or -1 on failure **/ static long soap_parse_mo(SMSCConn *conn, Octstr *request, Octstr **response) { PrivData *privdata = conn->data; xmlDocPtr requestDoc; xmlNodePtr root; Msg* msg; int pos = 0; List* maps; char receiver[30], sender[30], msgtype[30], msgdata[255], date[30]; int64 msgid = -1; char* keywords[] = { "receiver", "sender", "msgtype", "msgdata", "date", "id" }; char* sscans[] = { "%s", "%s", "%s", "%s", "%s", "%lld" }; void* pointers[] = { &receiver, &sender, &msgtype, &msgdata, &date, &msgid }; receiver[0] = sender[0] = msgtype[0] = msgdata[0] = date[0] = '\0'; if (!response) // how am I supposed to return a response now ? return -1; if (!request) { *response = octstr_create(SOAP_ERROR_NO_DATA_MESSAGE); return SOAP_ERROR_NO_DATA_CODE; } // find the POST parameter name if ( (pos = octstr_search_char(request,'=',0)) < 0) { // didn't find it - *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } // cut of the parameter name - I'm not really interested in it octstr_delete(request,0,pos+1); // decode the URL encoded data if (octstr_url_decode(request) < 0) { // probably not URL encoded *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } debug("bb.soap.parse_mo",0,"SOAP[%s]: parse_mo - MO request dump <%s>", octstr_get_cstr(privdata->name),octstr_get_cstr(request)); // parse XML if ( !(requestDoc = xmlParseDoc(octstr_get_cstr(request))) ) { error(0,"SOAP[%s]: parse_mo couldn't parse XML response", octstr_get_cstr(privdata->name)); return -1; } // get root element if ( ! (root = xmlDocGetRootElement(requestDoc)) ) { error(0,"SOAP[%s]: parse_mo couldn't get XML root element for request", octstr_get_cstr(privdata->name)); xmlFreeDoc(requestDoc); return -1; } // create the argument map maps = soap_create_map(privdata->mo_spec_file, 6, keywords, sscans, pointers); // run the map and the xml through the parser if (soap_map_xml_data(root, maps) < list_len(maps)) { error(0,"SOAP[%s]: parse_mo failed to map all the arguments from the XML data", octstr_get_cstr(privdata->name)); } list_destroy(maps, soap_destroy_map); // done with the document xmlFreeDoc(requestDoc); if (msgid == -1) { error(0,"SOAP: parse_mo - failed to get message ID"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } if (strlen(receiver) == 0) { error(0,"SOAP: parse_mo - failed to get receiver"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } if (strlen(sender) == 0) { error(0,"SOAP: parse_mo - failed to get sender"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } if (strlen(msgtype) == 0) { // not an error = ENCODING should be considered implicit. strcpy(msgtype,"A"); } if (strlen(msgdata) == 0) { error(0,"SOAP: parse_mo - failed to get message content"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } // create me a message to store data in it msg = msg_create(sms); // fill in the fields from the parsed arguments msg->sms.sender = octstr_create(sender); msg->sms.receiver = octstr_create(receiver); msg->sms.id = msgid; msg->sms.msgdata = octstr_create(msgdata); // fill in the date if (strlen(date)) { struct universaltime tm; Octstr* temp = octstr_create(date); if (date_parse_iso(&tm, temp)) /* failed to parse the date */ msg->sms.time = time(NULL); else msg->sms.time = date_convert_universal(&tm); octstr_destroy(temp); } else msg->sms.time = time(NULL); // check message data type - B stands for "base 64 encoded" in Team Mobile if (!strcmp(msgtype, "B")) { octstr_base64_to_binary(msg->sms.msgdata); msg->sms.coding = DC_8BIT; } else if (!strcmp(msgtype, "binary")) { octstr_hex_to_binary(msg->sms.msgdata); msg->sms.coding = DC_8BIT; } else { /* not gonna play this game - just convert from whatever alt_charset is set to, to UCS2 // scan message for unicode chars (utf-8 encoded) pos = 0; while (pos < octstr_len(msg->sms.msgdata)) { if (octstr_get_char(msg->sms.msgdata,pos) & 128) break; ++pos; } if (pos < octstr_len(msg->sms.msgdata)) { // message has some unicode - we need to convert to UCS-2 first Octstr* temp = msg->sms.msgdata; msg->sms.coding = DC_UCS2; if (charset_from_utf8(temp, &(msg->sms.msgdata), octstr_imm("UCS-2")) < 0) { error(0,"SOAP[%s]: parse_mo couldn't convert msg text from UTF-8 to UCS-2. leaving as is.", octstr_get_cstr(privdata->name)); O_DESTROY(msg->sms.msgdata); msg->sms.msgdata = octstr_duplicate(temp); // set coding to 8bit and hope for the best msg->sms.coding = DC_8BIT; } octstr_destroy(temp); } else // not unicode : 7bit msg->sms.coding = DC_7BIT; */ /* if it's not binary, then assume unicode and convert from alt_charset to UCS2 */ msg->sms.coding = DC_UCS2; if (!octstr_case_compare(privdata->alt_charset, octstr_imm("UCS-2"))) charset_convert(msg->sms.msgdata, octstr_get_cstr(privdata->alt_charset), "UCS-2BE"); msg->sms.charset = octstr_create("UCS-2"); } debug("bb.soap.parse_mo",0,"SOAP[%s]: message decoded -", octstr_get_cstr(privdata->name)); octstr_dump(msg->sms.msgdata,0); // check that we have all the fields necessary if (!(msg->sms.sender) || !(msg->sms.msgdata)) { // generate error message *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } // setup defaults if (msg->sms.time <= 0) msg->sms.time = time(NULL); if (!msg->sms.receiver) msg->sms.receiver = octstr_create(SOAP_DEFAULT_SENDER_STRING); if (!msg->sms.smsc_id) msg->sms.smsc_id = octstr_duplicate(conn->id); *response = soap_format_xml(privdata->mo_xml_file,msg); debug("bb.soap.reponse_dlr",0,"SOAP[%s]: data dump: <%s>", octstr_get_cstr(privdata->name), octstr_get_cstr(*response)); bb_smscconn_receive(conn,msg); return SOAP_QUERY_OK; } /* * function soap_parse_dlr() * parse an incoming DLR xml request, build a message from it and sent it. * also generate the reponse text and status code * Input: module public state data, request body * Output: response body * Returns: HTTP status code on successful parse or -1 on failure **/ static long soap_parse_dlr(SMSCConn *conn, Octstr *request, Octstr **response) { PrivData *privdata = conn->data; xmlDocPtr requestDoc; xmlNodePtr root; Msg* dlrmsg = NULL; long dlrtype; int pos; List* maps; char receiver[30], soapdate[30], msgid[30]; long result = -1; char* keywords[] = { "receiver", "soapdate", "msgid", "result" }; char* sscans[] = { "%s", "%s", "%s", "%ld" }; void* pointers[] = { &receiver, &soapdate, &msgid, &result }; receiver[0] = soapdate[0] = msgid[0] = '\0'; if (!response) // how am I supposed to return a response now ? return -1; if (!request) { *response = octstr_create(SOAP_ERROR_NO_DATA_MESSAGE); return SOAP_ERROR_NO_DATA_CODE; } // find the POST parameter name if ( (pos = octstr_search_char(request,'=',0)) < 0) { // didn't find it - *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } // cut of the parameter name - I'm not really interested in it octstr_delete(request,0,pos+1); // decode the URL encoded data if (octstr_url_decode(request) < 0) { // probably not URL encoded *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } debug("bb.soap.parse_dlr",0,"SOAP[%s]: parse_dlr - DLR request dump <%s>", octstr_get_cstr(privdata->name),octstr_get_cstr(request)); // parse XML if ( !(requestDoc = xmlParseDoc(octstr_get_cstr(request))) ) { error(0,"SOAP[%s]: parse_dlr couldn't parse XML response", octstr_get_cstr(privdata->name)); return -1; } // get root element if ( ! (root = xmlDocGetRootElement(requestDoc)) ) { error(0,"SOAP[%s]: parse_dlr couldn't get XML root element for request", octstr_get_cstr(privdata->name)); xmlFreeDoc(requestDoc); return -1; } // create the argument map maps = soap_create_map(privdata->dlr_spec_file, 4, keywords, sscans, pointers); // run the map and the xml through the parser if (soap_map_xml_data(root, maps) < 4) { error(0,"SOAP[%s]: parse_dlr failed to map all the arguments from the XML data", octstr_get_cstr(privdata->name)); } list_destroy(maps, soap_destroy_map); // done with the document xmlFreeDoc(requestDoc); if (strlen(msgid) == 0) { error(0,"SOAP: parse_dlr - failed to get message ID"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } if (result == -1) { error(0,"SOAP: parse_dlr - failed to get delivery code"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } if (strlen(receiver) == 0) { error(0,"SOAP: parse_mo - failed to get receiver"); *response = octstr_create(SOAP_ERROR_MALFORMED_DATA_MESSAGE); return SOAP_ERROR_MALFORMED_DATA_CODE; } // log the delivery code - this could be used to determine dlrtype (or so I hope) debug("bb.soap.parse_dlr",0,"SOAP[%s]: parse_dlr DELIVERY_CODE : %ld", octstr_get_cstr(privdata->name),result); if (result == 0) dlrtype = DLR_SUCCESS; else dlrtype = DLR_FAIL; // fetch the DLR dlrmsg = dlr_find(octstr_get_cstr(conn->id),msgid, receiver, /* destination */ dlrtype); if (!dlrmsg) { error(0,"SOAP[%s]: parse_dlr invoked (%ld), but no DLR found for MsgID %s", octstr_get_cstr(privdata->name),dlrtype,msgid); *response = octstr_create(SOAP_ERROR_NO_DLR_MESSAGE); return SOAP_ERROR_NO_DLR_CODE; } debug("bb.soap.parse_dlr",0,"SOAP[%s]: parse_dlr found dlr", octstr_get_cstr(privdata->name)); switch (dlrtype) { // change message according to DLR type case DLR_SUCCESS: octstr_insert_data(dlrmsg->sms.msgdata, 0, "Delivered/",10); break; case DLR_BUFFERED: octstr_insert_data(dlrmsg->sms.msgdata, 0, "Buffered/",9); break; case DLR_FAIL: octstr_insert_data(dlrmsg->sms.msgdata, 0, "Failed/",7); break; default: break; } if (dlrmsg->sms.receiver) { octstr_destroy(dlrmsg->sms.sender); dlrmsg->sms.sender = dlrmsg->sms.receiver; } dlrmsg->sms.receiver = octstr_create(receiver); dlrmsg->sms.id = strtol(msgid, NULL, 10); debug("bb.soap.parse_dlr",0,"SOAP[%s]: parse_dlr sent dlr <%s>", octstr_get_cstr(privdata->name),octstr_get_cstr(dlrmsg->sms.msgdata)); *response = soap_format_xml(privdata->dlr_xml_file, dlrmsg); debug("bb.soap.reponse_dlr",0,"SOAP[%s]: data dump: <%s>", octstr_get_cstr(privdata->name), octstr_get_cstr(*response)); // send to bearerbox bb_smscconn_receive(conn, dlrmsg); return SOAP_QUERY_OK; } /* * SOAP internal utility functions **/ /* * function soap_xmlnode_get_long() * parse the content of an XML node and return it as an integer * Input: xmlNodePtr to node * Output: long parsed * Returns: 0 on success, -1 on failure **/ int soap_xmlnode_get_long(xmlNodePtr cur, long* out) { xmlChar* nodeContent; char* endPointer; if (!out) // sanity check return -1; // get content of tag if (!(nodeContent = xmlNodeGetContent(cur))) { error(0,"get_long - xml Node has content !"); return -1; } // read the content into output *out = strtol(nodeContent,&endPointer,10); xmlFree(nodeContent); if (endPointer == (char*)nodeContent) { error(0,"get_long - node has non-numeric content <%s>", nodeContent); return -1; } return 0; } /* * function soap_xmlnode_get_int64() * parse the content of an XML node and return it as an int64 * Input: xmlNodePtr to node * Output: long parsed * Returns: 0 on success, -1 on failure **/ int soap_xmlnode_get_int64(xmlNodePtr cur, int64* out) { xmlChar* nodeContent; char* endPointer; if (!out) // sanity check return -1; // get content of tag if (!(nodeContent = xmlNodeGetContent(cur))) { error(0,"get_long - xml Node has content !"); return -1; } // read the content into output *out = strtoll(nodeContent,&endPointer,10); xmlFree(nodeContent); if (endPointer == (char*)nodeContent) { error(0,"get_long - node has non-numeric content <%s>", nodeContent); return -1; } return 0; } /* * function soap_xmlnode_get_octstr() * parse the content of an XML node and return it as an Octstr* * Input: xmlNodePtr to node * Output: Octstr to feel with data * Returns: 0 on success, -1 on failure **/ int soap_xmlnode_get_octstr(xmlNodePtr cur, Octstr **out) { xmlChar* nodeContent; if (!out) // sanity check return -1; // get content of tag if (!(nodeContent = xmlNodeGetContent(cur))) { error(0,"get_octstr - xml Node has content !"); return -1; } // store the content into output *out = octstr_create(nodeContent); xmlFree(nodeContent); if (*out) return 0; else return -1; } /* * function soap_read_date() * convert a date string in one2one obiquis format (%Y/%M/%d:%h:%m) to epoch time * Input: Octstr date * Returns: epoch time on success or -1 on failure **/ time_t soap_read_date(Octstr* dateString) { int pos, count; struct universaltime stTime; long arTime[5]; if (!dateString) // sanity check return -1; pos = count = 0; // tricky control structures are my favourite among complicated expressions ;-) while (count < 5 && pos < octstr_len(dateString) && (pos = octstr_parse_long(&(arTime[count++]),dateString, pos,10)) && pos != -1) ++pos; if (count < 5) { // error parsing the date debug("bb.soap.read_date",0,"read_date failed parsing the date value <%s>", octstr_get_cstr(dateString)); return -1; } stTime.day = arTime[0]; stTime.month = arTime[1]; stTime.year = arTime[2]; stTime.hour = arTime[3]; stTime.minute = arTime[4]; stTime.second = 0; return date_convert_universal(&stTime); } /* * function soap_write_date() * convert an epoch time value to a date string in one2one obiquis format (%Y/%M/%d:%h:%m) * Input: time_t epoch time * Returns: an Octstr containing the date - this must be freed by the caller **/ static Octstr* soap_write_date(time_t date) { struct tm date_parts; Octstr* out; if (date < 0) // sanity check - I don't think it should ever happen, but I don't want to get // support calls at 2am because some gateway in the UK went bananas. return octstr_create("ERROR"); // split up epoch time to elements gmtime_r(&date, &date_parts); out = octstr_format("%d/%02d/%02d:%02d:%02d", date_parts.tm_year + 1900, date_parts.tm_mon + 1, date_parts.tm_mday, date_parts.tm_hour, date_parts.tm_min); // again if (out) return out; else return octstr_create("ERROR"); // assuming octstr_create never fails, unlike octstr_format. this is not the case currently (both cannot fail), but it may change } /* * function soap_server_start() * init and start the SOAP HTTP server * Input: Module public connection state data * Returns: 0 on success, -1 on failure **/ int soap_server_start(SMSCConn *conn) { PrivData* privdata = conn->data; debug("bb.soap.server_stop",0,"SOAP[%s]: Starting HTTP server", octstr_get_cstr(privdata->name)); // start the HTTP server if (http_open_port(privdata->port,privdata->ssl)) { return -1; } // raise server flag privdata->soap_server = 1; if ( (privdata->server_thread = gwthread_create(soap_server, conn)) == -1) { error(0, "SOAP[%s]: server_start failed to create server thread!", octstr_get_cstr(privdata->name)); http_close_port(privdata->port); return -1; } return 0; } /* * function soap_server_stop() * tears down and stops the SOAP HTTP server * Input: Module connection state data **/ static void soap_server_stop(PrivData* privdata) { // time_t start = time(NULL); debug("bb.soap.server_stop",0,"SOAP[%s]: Stopping HTTP server", octstr_get_cstr(privdata->name)); // signal the server thread to stop privdata->soap_server = 0; // close the http server thread http_close_port(privdata->port); if (privdata->server_thread) { gwthread_wakeup(privdata->server_thread); gwthread_join(privdata->server_thread); privdata->server_thread = 0; } /* // wait upto 5 minutes for our server thread to shutdown while (privdata->server_thread && (start + 300 > time(NULL))) gwthread_sleep(SOAP_SLEEP_TIME); if (privdata->server_thread) { error(0,"SOAP[%s]: our server refuses to die!", octstr_get_cstr(privdata->name)); privdata->server_thread = 0; // dump it either way }*/ debug("bb.soap.server_stop",0,"SOAP[%s]: Done stopping HTTP server", octstr_get_cstr(privdata->name)); } /* * function soap_client_init_query() * start an HTTP query, load balance callers, and manage caller pool * Input: Module state, list of headers to send, data to send, message to store **/ static void soap_client_init_query(PrivData* privdata, List* headers, Octstr* data, Msg* msg) { ClientData *cur_client = NULL; long index; // no list yet, generate one if (!privdata->soap_client) privdata->soap_client = list_create(); // I'm going to change the list, so lock it list_lock(privdata->soap_client); // find the next live caller for (index = list_len(privdata->soap_client) - 1 ; index >= 0; --index) { cur_client = list_get(privdata->soap_client, index); if ( cur_client->last_access + CLIENT_BUSY_TIME < time(NULL) && cur_client->requests < CLIENT_BUSY_LOAD ) { debug("bb.soap.init_query",0,"SOAP[%s]: init_query getting a client",octstr_get_cstr(privdata->name)); // client is not busy - get it list_delete(privdata->soap_client, index, 1); break; } cur_client = NULL; } if (!cur_client) { if (list_len(privdata->soap_client) > MAX_SOAP_CLIENTS) { debug("bb.soap.init_query",0,"SOAP[%s]: init_query all clients are busy, getting the first client",octstr_get_cstr(privdata->name)); // query not dispatched, and we have the max number of callers - // grab the first caller (least used) from the list cur_client = list_extract_first(privdata->soap_client); } else { // query not dispatched, and we don't have enough callers - // start a new one debug("bb.soap.init_query",0,"SOAP[%s]: init_query creates a new client",octstr_get_cstr(privdata->name)); cur_client = soap_create_client_data(); } } // dispatch query to selected client http_start_request(cur_client->caller, HTTP_METHOD_GET, privdata->uri, headers, data, 1, msg, NULL); cur_client->requests++; cur_client->last_access = time(NULL); list_append(privdata->soap_client, cur_client); list_unlock(privdata->soap_client); } /* * function soap_create_client_data() * creates a new SOAP client data structure and caller * Returns: an initialized client data structure with a live caller **/ static ClientData* soap_create_client_data() { ClientData *cd = gw_malloc(sizeof(ClientData)); cd->last_access = 0; cd->requests = 0; cd->caller = http_caller_create(); return cd; } /* * function soap_destroy_client_data() * destroy a SOAP client caller * Input: pointer to a client data structure with a live caller **/ static void soap_destroy_client_data(void* data) { ClientData *cd = (ClientData*) data; // signal the caller to stop and then kill it if (cd->caller) { http_caller_signal_shutdown(cd->caller); http_caller_destroy(cd->caller); } } /* * function soap_client_have_response() * return a caller from the pool that has responses waiting * Input: ClientData pool * Returns: a client data structure that has a caller with responses waiting, * or NULL if none are found **/ static ClientData* soap_client_have_response(List* client_list) { long index; ClientData* cd; if (!client_list) return NULL; // lock the list so nobody removes or adds clients while I'm looping on the list list_lock(client_list); for (index = list_len(client_list) - 1; index >= 0; --index) { cd = list_get(client_list,index); if (list_len(cd->caller)) { list_unlock(client_list); return list_get(client_list, index); } } list_unlock(client_list); return NULL; } /* * function soap_convert_token() * convert a member of the message structure and return it as octstr * Input: member name * Returns: an Octstr containing the content of the data member from the message structure * or NULL if an error occured. **/ static Octstr* soap_convert_token(Msg* msg, Octstr* name) { char buf[20]; // first check for special tokens : if (msg->type == sms && !octstr_str_compare(name,"validity30")) // validity in 30 minutes increment return octstr_format("%ld",(msg->sms.validity?msg->sms.validity:SOAP_DEFAULT_VALIDITY) / 30); else if (msg->type == sms && !octstr_str_compare(name, "validity_date")) /* date on which the message's validity expires */ return date_create_iso(msg->sms.time+(60*msg->sms.validity)); else if (msg->type == sms && !octstr_str_compare(name, "dlrmask_smsc_yn")) // "Y" for any of the SMSC generated DLRs, "N" otherwise return octstr_create(msg->sms.dlr_mask & (DLR_FAIL | DLR_SUCCESS | DLR_BUFFERED) ? "Y" : "N"); else if (msg->type == sms && !octstr_str_compare(name, "121date")) return soap_write_date(msg->sms.time); else if (msg->type == sms && !octstr_str_compare(name, "date")) return date_create_iso(msg->sms.time); else if (msg->type == sms && !octstr_str_compare(name, "rand")) return octstr_format("%d",gw_rand()); else if (msg->type == sms && !octstr_str_compare(name, "dlrmask_success_01")) // "1" for any of the SMSC generated DLRs, "0" otherwise return octstr_create(msg->sms.dlr_mask & (DLR_SUCCESS) ? "0" : "1"); #define INTEGER(fieldname) \ if (!octstr_str_compare(name, #fieldname)) { \ sprintf(buf,"%ld", p->fieldname); \ return octstr_create(buf); \ } #define INT64(fieldname) \ if (!octstr_str_compare(name, #fieldname)) { \ sprintf(buf,"%lld", p->fieldname); \ return octstr_create(buf); \ } #define OCTSTR(fieldname) \ if (!octstr_str_compare(name, #fieldname)) \ return octstr_duplicate(p->fieldname); #define MSG(type, stmt) \ case type: { struct type *p = &msg->type; stmt } break; switch (msg->type) { #include "msg-decl.h" default: error(0, "Internal error: unknown message type %d", msg->type); return NULL; } return NULL; } /* * function soap_create_map() * convert a XML parsing spec file and a list of recognized keywords to an argument map * Input: XML parsing spec buffer and lists of keywords, types and pointers * Returns: number of variables successfuly mapped **/ List* soap_create_map(Octstr* spec, long count, char* keywords[], char* types[], void* storage[]) { List *parse_items, *out; out = list_create(); // read the list of items from the spec file parse_items = octstr_split(spec, octstr_imm("\n")); while (list_len(parse_items)) { ArgumentMap* map; int index; Octstr* temp = list_extract_first(parse_items); List* item = octstr_split_words(temp); // make sure we have at least two things in the item : a keyword and a path if (list_len(item) < 2) { debug("bb.soap.parse_create_map",0,"SOAP: broken spec file line <%s> in soap_create_map", octstr_get_cstr(temp)); octstr_destroy(temp); list_destroy(item, octstr_destroy_item); continue; } // check that the keyword matches something in the list of keywords for (index = 0; index < count; ++index) { if (!octstr_str_compare(list_get(item,0), keywords[index])) { // allocate the structure map = gw_malloc(sizeof(ArgumentMap)); map->name = list_extract_first(item); map->path = list_extract_first(item); map->attribute = list_extract_first(item); // could be NULL, but that is ok map->sscan_type = octstr_create(types[index]); map->store = storage[index]; list_append(out, map); break; } } // destroy temporary variables; list_destroy(item, octstr_destroy_item); octstr_destroy(temp); } list_destroy(parse_items, octstr_destroy_item); return out; } /* * function soap_destroy_map() * destroy a map structure. used in list_destroy(calls); * Input: pointer to a map structure; **/ void soap_destroy_map(void *item) { ArgumentMap* map = item; octstr_destroy(map->name); octstr_destroy(map->path); octstr_destroy(map->attribute); octstr_destroy(map->sscan_type); gw_free(map); } /* * function soap_map_xml_data() * maps content of an XML structure to a list of variables using a map * Input: XML document and an argument map * Returns: number of variables successfuly mapped **/ int soap_map_xml_data(xmlNodePtr xml, List* maps) { int mapindex = 0, args = 0; xmlNodePtr node, parent; // step through the items on the map while (mapindex < list_len(maps)) { Octstr* temp; int index = 0; ArgumentMap* map = list_get(maps,mapindex); // split the path elements List* path_elements = octstr_split(map->path, octstr_imm("/")); // walk the message tree down the path parent = NULL; node = xml; while (index < list_len(path_elements)) { int found = 0; // get the next path element temp = list_get(path_elements, index); do { if (!octstr_str_compare(temp,node->name)) { // found what we're looking for if (!(node->xmlChildrenNode) && index < list_len(path_elements)) { // while this is indeed the item we are looking for, it's not the end // of the path, and this item has no children debug("bb.soap.map_xml_data",0,"SOAP: error parsing XML, looking for <%s>, but element <%s> has no children", octstr_get_cstr(map->path), octstr_get_cstr(temp)); } else { ++index; // go down the path parent = node; // remember where I came from node = node->xmlChildrenNode; // trace into the node ++found; break; // escape to the next level } } } while ((node = node->next)); if (!found) { // didn't find anything - back track node = parent; parent = node->parent; if (--index < 0) // I backtracked too much up the tree, nowhere to go to break; if (!(node = node->next)) // no more childs under the main tree to look under, abort break; } } if (index < list_len(path_elements)) { // didn't find the full path debug("bb.soap.map_xml_data",0,"SOAP: didn't find element for keyword <%s> in XML data", octstr_get_cstr(map->name)); list_destroy(path_elements, octstr_destroy_item); ++mapindex; continue; } // found the correct node (it's stored in parent) if (map->attribute) { // The user wants to get an attribute xmlChar* content; content = xmlGetProp(parent, octstr_get_cstr(map->attribute)); if (content) temp = octstr_create(content); else // dont treat an empty or non-existant attribute as an error right away temp = octstr_create(""); xmlFree(content); } else { // the user wants to get the content xmlChar* content; content = xmlNodeGetContent(parent); if (content) temp = octstr_create(content); else // don't treat an empty tag an error right away temp = octstr_create(""); xmlFree(content); } // parse the content using sscan_type from the map octstr_strip_blanks(temp); if (!octstr_str_compare(map->sscan_type,"%s")) { // special processing of %s - this means the whole string, while sscanf stops at spaces strcpy(map->store,octstr_get_cstr(temp)); ++args; } else { if (!sscanf(octstr_get_cstr(temp), octstr_get_cstr(map->sscan_type), map->store)) { debug("bb.soap.map_xml_data",0,"SOAP: failed to scan content '%s' for '%s' in xml parsing", octstr_get_cstr(temp), octstr_get_cstr(map->sscan_type)); } else { ++args; } } // done for this item octstr_destroy(temp); list_destroy(path_elements, octstr_destroy_item); ++mapindex; } return args; }