Clickatell http driver patch

"Rene Kluwen" <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Hi Alex,

As you suggested, I agree that we better move this thread to the ML.
I posted the patch to the users ML. I will attach it to this post again for
your convenience.

I will fix your points 1, 2 & 4. Probably before the end of this week.

About the authentication: Clickatell does not send a username or password
along.
But I will see if it is possible to make a check on api_id.

Besides that, also connect-allow-ip can be used. From what I have heard from
the Clickatell techs, their callback HTTP farm connects from behind one
single IP address.

Cheers,

Rene Kluwen
Chimit


-----------------------------------------------------------------------
 alex - 03-14-06 19:31 GMT
-----------------------------------------------------------------------
Hi Rene,
I agree with you that we should support as much http interfaces as
possible. As to you patch... It's to hard to comment on this patch in
mantis. Please post it to ML and I will review it. At least now some
comments:
1) please fix comments (they mention Brunet but your patch should support
Clickatell ;))
2) please kill commented blocks which are there due to copy&paste from
brunet /* e.g. XSER */
3) I don't see what should prevent me to send your MO message instead of
Clickatell server (not auth. for MO and DLR)
4) minor: please fix coding style (indents)

Thanks,
Alex
clickatell_MO.patch (application/octet-stream, 11.2 KB)
Index: gw/smsc/smsc_http.c
===================================================================
RCS file: /home/cvs/gateway/gw/smsc/smsc_http.c,v
retrieving revision 1.44
diff -u -r1.44 smsc_http.c
--- gw/smsc/smsc_http.c	19 Sep 2005 22:07:33 -0000	1.44
+++ gw/smsc/smsc_http.c	14 Mar 2006 17:44:58 -0000
@@ -137,6 +137,7 @@
     long open_sends;
     Octstr *username;   /* if needed */
     Octstr *password;   /* as said */
+    Octstr *system_id;	/* api id for clickatell */
     int no_sender;      /* ditto */
     int no_coding;      /* this, too */
     int no_sep;         /* not to mention this */
@@ -162,6 +163,7 @@
     octstr_destroy(conndata->username);
     octstr_destroy(conndata->password);
     octstr_destroy(conndata->proxy);
+    octstr_destroy(conndata->system_id);
 
     gw_free(conndata);
 }
@@ -510,6 +512,278 @@
 
 
 /*----------------------------------------------------------------
+ * Clickatell - http://api.clickatell.com/
+ *
+ * Rene Kluwen <[email protected]>
+ */
+
+/* MT related function */
+static void clickatell_send_sms(SMSCConn *conn, Msg *sms)
+{
+    ConnData *conndata = conn->data;
+    Octstr *url, *tid, *xser;
+    List *headers;
+    char id[UUID_STR_LEN + 1];
+    int dcs;
+
+    /* 
+     * Construct TransactionId.
+     * Beware that brunet needs an "clean" octstr representation, 
+     * without the dashes in the string. So remove them.
+     */
+    uuid_unparse(sms->sms.id, id);
+    tid = octstr_create(id);
+    octstr_replace(tid, octstr_imm("-"), octstr_imm(""));
+
+    /* form the basic URL */
+    url = octstr_format("%S/sendmsg?to=%E&from=%E&api_id=%E&user=%E&password=%E",
+        conndata->send_url, sms->sms.receiver, sms->sms.sender, conndata->system_id, conndata->username, conndata->password);
+    
+    /* 
+     * We use &binfo=<foobar> from sendsms interface to encode
+     * additional paramters. If a mandatory value is not set,
+     * a default value is applied
+     */
+    /*
+    if (octstr_len(sms->sms.binfo)) {
+        octstr_url_decode(sms->sms.binfo);
+        octstr_format_append(url, "&%S", sms->sms.binfo);
+    }
+    */
+
+    /* if coding is not set and UDH exists, assume DC_8BIT
+     * else default to DC_7BIT */
+    /*
+    if (sms->sms.coding == DC_UNDEF)
+        sms->sms.coding = octstr_len(sms->sms.udhdata) > 0 ? DC_8BIT : DC_7BIT;
+    */
+
+    dcs = fields_to_dcs(sms,
+        (sms->sms.alt_dcs != SMS_PARAM_UNDEFINED ? sms->sms.alt_dcs : 0));
+
+    /* XSer processing */    
+    xser = octstr_create("");
+    /* XSer DCS values */
+    /*
+    if (dcs != 0 && dcs != 4)
+        octstr_format_append(xser, "0201%02x", dcs & 0xff);
+    */
+    /* add UDH header */
+    if (octstr_len(sms->sms.udhdata)) {
+        octstr_format_append(url, "&data=%H", sms->sms.msgdata);
+        octstr_format_append(url, "&udh=%H", sms->sms.udhdata);
+    }
+    else {
+        octstr_format_append(url, "&text=%E", sms->sms.msgdata);
+    }
+    if (DLR_IS_ENABLED_DEVICE(sms->sms.dlr_mask)) {
+	octstr_format_append(url, "&callback=3&deliv_ack=1");
+    }
+    /*
+    if (octstr_len(xser) > 0)
+        octstr_format_append(url, "%S", xser);
+    octstr_destroy(xser);
+    */
+
+    headers = http_create_empty_headers();
+    debug("smsc.http.clickatell", 0, "HTTP[%s]: Sending request <%s>",
+          octstr_get_cstr(conn->id), octstr_get_cstr(url));
+
+    /* 
+     * Clickatell requires an SSL-enabled HTTP client call, this is handled
+     * transparently by the Kannel HTTP layer module.
+     */
+    http_start_request(conndata->http_ref, HTTP_METHOD_GET, url, headers, 
+                       NULL, 0, sms, NULL);
+
+    octstr_destroy(url);
+    octstr_destroy(tid);
+    http_destroy_headers(headers);
+}
+
+
+/*
+ * Parse a line in the format: <name=value name=value ...>
+ * and return a Dict with the name as key and the value as value,
+ * otherwise return NULL if a parsing error occures.
+ */
+static Dict *clickatell_parse_body(Octstr *body)
+{
+    Dict *param = NULL;
+    List *words = NULL;
+    long len;
+    Octstr *word, *value;
+
+    words = octstr_split_words(body);
+    if ((len = gwlist_len(words)) > 1) {
+	word = gwlist_extract_first(words);
+	if (octstr_compare(word, octstr_imm("ID:")) == 0) {
+		value = gwlist_extract_first(words);
+		param = dict_create(4, NULL);
+		dict_put(param, octstr_imm("ID"), value);
+		info(0, "ID == %s", octstr_get_cstr(value));
+	}
+	else if (octstr_compare(word, octstr_imm("ERR:")) == 0) {
+		/* todo */
+	}
+        octstr_destroy(word);
+    }
+    gwlist_destroy(words, (void(*)(void *)) octstr_destroy);
+
+    return param;
+}
+
+
+static void clickatell_parse_reply(SMSCConn *conn, Msg *msg, int status,
+                               List *headers, Octstr *body)
+{
+    if (status == HTTP_OK || status == HTTP_ACCEPTED) {
+        Dict *param;
+        Octstr *status, *msgid;
+
+        if ((param = clickatell_parse_body(body)) != NULL &&
+            (msgid = dict_get(param, octstr_imm("ID"))) != NULL &&
+            msgid != NULL) {
+
+            /* SMSC ACK.. now we have the message id. */
+            if (DLR_IS_ENABLED_DEVICE(msg->sms.dlr_mask))
+                dlr_add(conn->id, msgid, msg);
+
+            bb_smscconn_sent(conn, msg, NULL);
+
+        } else {
+            error(0, "HTTP[%s]: Message was malformed. SMSC response `%s'.",
+                  octstr_get_cstr(conn->id), octstr_get_cstr(body));
+            bb_smscconn_send_failed(conn, msg,
+	                SMSCCONN_FAILED_MALFORMED, octstr_duplicate(body));
+        }
+        dict_destroy(param);
+
+    } else {
+        error(0, "HTTP[%s]: Message was rejected. SMSC reponse `%s'.",
+              octstr_get_cstr(conn->id), octstr_get_cstr(body));
+        bb_smscconn_send_failed(conn, msg,
+	            SMSCCONN_FAILED_REJECTED, octstr_duplicate(body));
+    }
+}
+
+/* MO related function */
+static void clickatell_receive_sms(SMSCConn *conn, HTTPClient *client,
+                               List *headers, Octstr *body, List *cgivars)
+{
+    ConnData *conndata = conn->data;
+    List *reply_headers;
+    int ret;
+    Octstr *apimsgid, *status, *timestamp, *retmsg, *dest, *charge;
+    Octstr *api_id, *from, *to, *text, *charset, *udh;
+    int httpstatus = HTTP_UNAUTHORIZED, dlrstat;
+    Msg *dlrmsg, *momsg;
+    struct tm tm;
+
+    /* dlr parameters */
+    apimsgid = http_cgi_variable(cgivars, "apiMsgId");
+    status = http_cgi_variable(cgivars, "status");
+    timestamp = http_cgi_variable(cgivars, "timestamp"); /* timestamp is for both DLR & MO */
+    dest = http_cgi_variable(cgivars, "to");
+    charge = http_cgi_variable(cgivars, "charge");
+    /* MO parameters */
+    api_id = http_cgi_variable(cgivars, "api_id");
+    from = http_cgi_variable(cgivars, "from");
+    to = http_cgi_variable(cgivars, "to");
+    text = http_cgi_variable(cgivars, "text");
+    charset = http_cgi_variable(cgivars, "charset");
+    udh = http_cgi_variable(cgivars, "udh");
+
+    debug("smsc.http.clickatell", 0, "HTTP[%s]: Received a request",
+          octstr_get_cstr(conn->id));
+ 
+    if (api_id != NULL && from != NULL && to != NULL && timestamp != NULL && text != NULL && charset != NULL && udh != NULL) {
+	/* we received an MO message */
+	info(0, "Received MO message from %s: <%s>", octstr_get_cstr(from), octstr_get_cstr(text));
+	momsg = msg_create(sms);
+	momsg->sms.sms_type = mo;
+	momsg->sms.sender = octstr_duplicate(from);
+	momsg->sms.receiver = octstr_duplicate(to);
+	momsg->sms.msgdata = octstr_duplicate(text);
+	momsg->sms.charset = octstr_duplicate(charset);
+	if (octstr_compare(udh, octstr_imm("")) != 0) {
+		momsg->sms.udhdata = octstr_duplicate(udh);
+	}
+        strptime(octstr_get_cstr(timestamp), "%Y-%m-%d %H:%M:%S", &tm);
+	momsg->sms.time = gw_mktime(&tm);
+ 
+	/* note: implicit msg_destroy */
+	ret = bb_smscconn_receive(conn, momsg);
+	retmsg = octstr_create("Thanks");
+    }
+    else if (apimsgid == NULL || status == NULL || timestamp == NULL || dest == NULL) {
+        error(0, "HTTP[%s]: Insufficient args.",
+              octstr_get_cstr(conn->id));
+        retmsg = octstr_create("Insufficient arguments, rejected.");
+    }
+    else {
+	switch (atoi(octstr_get_cstr(status))) {
+	case  1: /* message unknown */
+	case  5: /* error with message */
+	case  6: /* user cancelled message */
+	case  7: /* error delivering message */
+	case  9: /* routing error */
+	case 10: /* message expired */
+		dlrstat = 2; /* delivery failure */
+		break;
+	case  2: /* message queued */
+	case  3: /* delivered */
+	case 11: /* message queued for later delivery */
+		dlrstat = 4; /* message buffered */
+		break;
+	case  4: /* received by recipient */
+	case  8: /* OK */
+		dlrstat = 1; /* message received */
+		break;
+	default: /* unknown status code */
+		dlrstat = 16; /* smsc reject */
+		break;
+	}
+        dlrmsg = dlr_find(conn->id,
+            apimsgid, /* smsc message id */
+            dest , /* destination */
+            dlrstat);
+
+        if (dlrmsg != NULL) {
+            /* dlrmsg->sms.msgdata = octstr_duplicate(apimsgid); */
+            dlrmsg->sms.sms_type = report_mo;
+	    dlrmsg->sms.time = atoi(octstr_get_cstr(timestamp));
+	    if (charge) {
+		/* unsure if smsbox relays the binfo field to dlrs.
+		   But it is here in case they will start to do it. */
+	    	dlrmsg->sms.binfo = octstr_duplicate(charge);
+	    }
+            
+            ret = bb_smscconn_receive(conn, dlrmsg);
+            httpstatus = (ret == 0 ? HTTP_OK : HTTP_FORBIDDEN);
+	    retmsg = octstr_create("Sent");
+        } else {
+            error(0,"HTTP[%s]: got DLR but could not find message or was not interested "
+                    "in it id<%s> dst<%s>, type<%d>",
+                octstr_get_cstr(conn->id), octstr_get_cstr(apimsgid),
+                octstr_get_cstr(dest), dlrstat);
+            httpstatus = HTTP_OK;
+	    retmsg = octstr_create("Thanks");
+        }
+    }
+
+    reply_headers = gwlist_create();
+    http_header_add(reply_headers, "Content-Type", "text/plain");
+    debug("smsc.http.clickatell", 0, "HTTP[%s]: Sending reply `%s'.",
+          octstr_get_cstr(conn->id), octstr_get_cstr(retmsg));
+    http_send_reply(client, httpstatus, reply_headers, retmsg);
+
+    octstr_destroy(retmsg);
+    http_destroy_headers(reply_headers);
+}
+
+
+/*----------------------------------------------------------------
  * Brunet - A german aggregator (mainly doing T-Mobil D1 connections)
  *
  *  o bruHTT v1.3L (for MO traffic) 
@@ -1135,6 +1409,7 @@
     conndata->send_url = cfg_get(cfg, octstr_imm("send-url"));
     conndata->username = cfg_get(cfg, octstr_imm("smsc-username"));
     conndata->password = cfg_get(cfg, octstr_imm("smsc-password"));
+    conndata->system_id = cfg_get(cfg, octstr_imm("system-id"));
     cfg_get_bool(&conndata->no_sender, cfg, octstr_imm("no-sender"));
     cfg_get_bool(&conndata->no_coding, cfg, octstr_imm("no-coding"));
     cfg_get_bool(&conndata->no_sep, cfg, octstr_imm("no-sep"));
@@ -1189,6 +1464,11 @@
         conndata->send_sms = wapme_smsproxy_send_sms;
         conndata->parse_reply = wapme_smsproxy_parse_reply;
     }
+    else if (octstr_case_compare(type, octstr_imm("clickatell")) == 0) {
+	conndata->receive_sms = clickatell_receive_sms;
+	conndata->send_sms = clickatell_send_sms;
+	conndata->parse_reply = clickatell_parse_reply;
+    }
     /*
      * ADD NEW HTTP SMSC TYPES HERE
      */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.