Browse Source

tm: rpc_uac* support

fifo/unixsock uac revived in rpc form and updated to the latest
tm:
 - tm.t_uac_start  - fifo_uac rpc equivalent with the following
   differences:
    * extra send_socket parameter for kamailio compatibility.
    * empty parameters can be specified either by '.' (backwards
       compatibility) or by "" (empty string).
    * it doesn't wait for the final reply and it doesn't include
      the sip reply in the rpc reply (it sends immediately an
      empty reply indicating only success or failure)
 - tm.t_uac_wait - like above, but it's supposed to wait and
   return the final reply. However right now it will only return
   an error (it's not yet fully implemented).
Andrei Pelinescu-Onciul 16 years ago
parent
commit
d6f5b4b051
3 changed files with 525 additions and 0 deletions
  1. 482 0
      modules/tm/rpc_uac.c
  2. 40 0
      modules/tm/rpc_uac.h
  3. 3 0
      modules/tm/tm.c

+ 482 - 0
modules/tm/rpc_uac.c

@@ -0,0 +1,482 @@
+/* 
+ * $Id$
+ * 
+ * Copyright (C) 2009 iptelorg GmbH
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * modules/tm/rpc_uac.c
+ */
+/*
+ * History:
+ * --------
+ *  2009-07-20  initial version (andrei)
+*/
+
+#include "rpc_uac.h"
+#include "../../rpc.h"
+#include "../../socket_info.h"
+#include "../../ut.h"
+#include "../../parser/parse_from.h"
+#include "ut.h"
+#include "dlg.h"
+#include "uac.h"
+#include "callid.h"
+
+
+
+/* RPC substitution char (used in rpc_t_uac headers) */
+#define SUBST_CHAR '!'
+
+
+
+#define skip_hf(_hf) (             \
+    ((_hf)->type == HDR_FROM_T)   || \
+    ((_hf)->type == HDR_TO_T)     || \
+    ((_hf)->type == HDR_CALLID_T) || \
+    ((_hf)->type == HDR_CSEQ_T)      \
+)
+
+
+
+struct str_list {
+	str s;
+	struct str_list *next;
+};
+
+
+
+const char* rpc_t_uac_start_doc[2] = {
+	"starts a tm uac using  a list of string parameters: method, ruri, dst_uri"
+		", send_sock, headers (CRLF separated) and body (optional)",
+	0
+};
+
+const char* rpc_t_uac_wait_doc[2] = {
+	"starts a tm uac and waits for the final reply, using a list of string "
+		"parameters: method, ruri, dst_uri send_sock, headers (CRLF separated)"
+		" and body (optional)",
+	0
+};
+
+
+
+/** make sure the rpc user created the msg properly.
+ * Make sure that the FIFO user created the message
+ * correctly and fill some extra parameters in function
+ * of the message contents.
+ * @param rpc - rpc handle
+ * @param  c  - rpc context handle
+ * @param msg - faked sip msg
+ * @param method 
+ * @param body
+ * @param fromtag - filled on success (1 if fromtag present, 0 if not)
+ * @param cseq_is - filled on success (1 if cseq present, 0 if not)
+ * @param cseq    - filled on success with the cseq number
+ * @callid        - filled on success with a pointer to the callid in the msg.
+ * @return -1 on error (and sends the rpc reply), 0 on success
+ */
+static int rpc_uac_check_msg(rpc_t *rpc, void* c,
+								struct sip_msg* msg,
+								str* method, str* body, 
+								int* fromtag, int *cseq_is, int* cseq,
+								str* callid)
+{
+	struct to_body* parsed_from;
+	struct cseq_body *parsed_cseq;
+	int i;
+	char ch;
+
+	if (body->len && !msg->content_type) {
+		rpc->fault(c, 400, "Content-Type missing");
+		goto err;
+	}
+
+	if (body->len && msg->content_length) {
+		rpc->fault(c, 400, "Content-Length disallowed");
+		goto err;
+	}
+
+	if (!msg->to) {
+		rpc->fault(c, 400,  "To missing");
+		goto err;
+	}
+
+	if (!msg->from) {
+		rpc->fault(c, 400, "From missing");
+		goto err;
+	}
+
+	/* we also need to know if there is from-tag and add it otherwise */
+	if (parse_from_header(msg) < 0) {
+		rpc->fault(c, 400, "Error in From");
+		goto err;
+	}
+
+	parsed_from = (struct to_body*)msg->from->parsed;
+	*fromtag = parsed_from->tag_value.s && parsed_from->tag_value.len;
+
+	*cseq = 0;
+	if (msg->cseq && (parsed_cseq = get_cseq(msg))) {
+		*cseq_is = 1;
+		for (i = 0; i < parsed_cseq->number.len; i++) {
+			ch = parsed_cseq->number.s[i];
+			if (ch >= '0' && ch <= '9' ) {
+				*cseq = (*cseq) * 10 + ch - '0';
+			} else {
+			 	DBG("check_msg: Found non-numerical in CSeq: <%i>='%c'\n",
+							(unsigned int)ch, ch);
+				rpc->fault(c, 400,  "Non-numerical CSeq");
+				goto err;
+			}
+		}
+		
+		if (parsed_cseq->method.len != method->len || 
+				memcmp(parsed_cseq->method.s, method->s, method->len) !=0 ) {
+			rpc->fault(c, 400, "CSeq method mismatch");
+			goto err;
+		}
+	} else {
+		*cseq_is = 0;
+	}
+
+	if (msg->callid) {
+		callid->s = msg->callid->body.s;
+		callid->len = msg->callid->body.len;
+	} else {
+		callid->s = 0;
+		callid->len = 0;
+	}
+	return 0;
+
+err:
+	return -1;
+}
+
+
+
+static inline struct str_list *new_str(char *s, int len,
+										struct str_list **last, int *total)
+{
+	struct str_list *new;
+	new = pkg_malloc(sizeof(struct str_list));
+	if (!new) {
+		LOG(L_ERR, "new_str: Not enough mem\n");
+		return 0;
+	}
+	new->s.s = s;
+	new->s.len = len;
+	new->next = 0;
+
+	(*last)->next = new;
+	*last = new;
+	*total += len;
+	return new;
+}
+
+
+
+/** construct a "header block" from a header list.
+  *
+  * @return pkg_malloc'ed header block on success (with *l set to its length),
+  *         0 on error.
+  */
+static char *get_hfblock(str *uri, struct hdr_field *hf, int proto,
+							struct socket_info* ssock, int* l)
+{
+	struct str_list sl, *last, *i, *foo;
+	int p, frag_len, total_len;
+	char *begin, *needle, *dst, *ret, *d;
+	str *sock_name, *portname;
+	struct dest_info di;
+
+	ret = 0; /* pessimist: assume failure */
+	total_len = 0;
+	last = &sl;
+	last->next = 0;
+	if (ssock){
+		sock_name = &ssock->address_str;
+		portname = &ssock->port_no_str;
+	}else{
+		sock_name = 0;
+		portname = 0;
+	}
+
+	for (; hf; hf = hf->next) {
+		if (skip_hf(hf)) continue;
+
+		begin = needle = hf->name.s; 
+		p = hf->len;
+
+		     /* substitution loop */
+		while(p) {
+			d = q_memchr(needle, SUBST_CHAR, p);
+			if (!d || d + 1 >= needle + p) { /* nothing to substitute */
+				if (!new_str(begin, p, &last, &total_len)) goto error;
+				break;
+			} else {
+				frag_len = d - begin;
+				d++; /* d not at the second substitution char */
+				switch(*d) {
+				case SUBST_CHAR: /* double SUBST_CHAR: IP */
+					     /* string before substitute */
+					if (!new_str(begin, frag_len, &last, &total_len))
+						goto error;
+					     /* substitute */
+					if (!sock_name) {
+						if (
+#ifdef USE_DNS_FAILOVER
+								uri2dst(0, &di, 0, uri, proto)
+#else
+								uri2dst(&di, 0, uri, proto)
+#endif /* USE_DNS_FAILOVER */
+									== 0 ){
+							LOG(L_ERR, "ERROR: get_hfblock: send_sock"
+										" failed\n");
+							goto error;
+						}
+						sock_name = &di.send_sock->address_str;
+						portname = &di.send_sock->port_no_str;
+					}
+					if (!new_str(sock_name->s, sock_name->len, &last,
+									&total_len))
+						goto error;
+					/* inefficient - FIXME --andrei*/
+					if (!new_str(":", 1, &last, &total_len)) goto error;
+					if (!new_str(portname->s, portname->len, &last,
+								&total_len)) goto error;
+					/* keep going ... */
+					begin = needle = d + 1;
+					p -= frag_len + 2;
+					continue;
+				default:
+					/* no valid substitution char -- keep going */
+					p -= frag_len + 1;
+					needle = d;
+				}
+			} /* possible substitute */
+		} /* substitution loop */
+		DBG("get_hfblock: one more hf processed\n");
+	} /* header loop */
+	
+	     /* construct a single header block now */
+	ret = pkg_malloc(total_len);
+	if (!ret) {
+		LOG(L_ERR, "get_hfblock: no pkg mem for hf block\n");
+		goto error;
+	}
+	i = sl.next;
+	dst = ret;
+	while(i) {
+		foo = i;
+		i = i->next;
+		memcpy(dst, foo->s.s, foo->s.len);
+		dst += foo->s.len;
+		pkg_free(foo);
+	}
+	*l = total_len;
+	return ret;
+	
+ error:
+	i = sl.next;
+	while(i) {
+		foo = i;
+		i = i->next;
+		pkg_free(foo);
+	}
+	*l = 0;
+	return 0;
+}
+
+
+
+/** rpc t_uac version-
+  * It expects the following list of strings as parameters:
+  *  method
+  *  request_uri
+  *  dst_uri (next hop) -- can be empty (either "" or ".", which is still
+  *                        supported for backwards compatibility with fifo)
+  *  send_socket (socket from which the message will be sent)
+  *  headers (message headers separated by CRLF, at least From and To
+  *           must be present)
+  *  body (optional, might be null or completely missing)
+  *
+  * If all the parameters are ok it will call t_uac() using them.
+  * Note: this version will  wait for the transaction final reply
+  * only if reply_wait is set to 1. Otherwise the rpc reply will be sent 
+  * immediately and it will be success if the paremters were ok and t_uac did
+  * not report any error.
+  * Note: reply waiting (reply_wait==1) is not yet supported.
+  * @param rpc - rpc handle
+  * @param  c - rpc current context
+  * @param reply_wait - if 1 do not generate a rpc reply until final response
+  *                     for the transaction arrives, if 0 immediately send
+  *                     an rpc reply (see above).
+  */
+static void rpc_t_uac(rpc_t* rpc, void* c, int reply_wait)
+{
+	/* rpc params */
+	str method, ruri, nexthop, send_socket, headers, body;
+	/* other internal vars.*/
+	str hfb, callid;
+	struct sip_uri p_uri, pnexthop;
+	struct sip_msg faked_msg;
+	struct socket_info* ssock;
+	str saddr;
+	int sport, sproto;
+	int ret, sip_error, err_ret, fromtag, cseq_is, cseq;
+	char err_buf[MAX_REASON_LEN];
+	dlg_t dlg;
+	uac_req_t uac_req;
+	
+	body.s=0;
+	body.len=0;
+	if (reply_wait){
+		rpc->fault(c, 600, "Reply wait/async mode not supported");
+		return;
+	}
+	ret=rpc->scan(c, "SSSSS*S",
+					&method, &ruri, &nexthop, &send_socket, &headers, &body);
+	if (ret<5 && ! (-ret == 5)){
+		rpc->fault(c, 400, "too few parameters (%d/5)", ret?ret:-ret);
+		return;
+	}
+	/* check and parse parameters */
+	if (method.len==0){
+		rpc->fault(c, 400, "Empty method");
+		return;
+	}
+	if (parse_uri(ruri.s, ruri.len, &p_uri)<0){
+		rpc->fault(c, 400, "Invalid request uri \"%s\"", ruri.s);
+		return;
+	}
+	/* old fifo & unixsock backwards compatibility for nexthop: '.' is still
+	   allowed */
+	if (nexthop.len==1 && nexthop.s[0]=='.'){
+		/* empty nextop */
+		nexthop.len=0;
+		nexthop.s=0;
+	}else if (nexthop.len==0){
+		nexthop.s=0;
+	}else if (parse_uri(nexthop.s, nexthop.len, &pnexthop)<0){
+		rpc->fault(c, 400, "Invalid next-hop uri \"%s\"", nexthop.s);
+		return;
+	}
+	/* kamailio backwards compatibility for send_socket: '.' is still
+	   allowed for an empty socket */
+	ssock=0;
+	saddr.s=0;
+	saddr.len=0;
+	if (send_socket.len==1 && send_socket.s[0]=='.'){
+		/* empty send socket */
+		send_socket.len=0;
+	}else if (send_socket.len &&
+				(parse_phostport(send_socket.s, &saddr.s, &saddr.len,
+								&sport, &sproto)!=0 ||
+				 				/* check also if it's not a MH addr. */
+				 				saddr.len==0 || saddr.s[0]=='(')
+				){
+		rpc->fault(c, 400, "Invalid send socket \"%s\"", send_socket.s);
+		return;
+	}else if (saddr.len && (ssock=grep_sock_info(&saddr, sport, sproto))==0){
+		rpc->fault(c, 400, "No local socket for \"%s\"", send_socket.s);
+		return;
+	}
+	/* check headers using the SIP parser to look in the header list */
+	memset(&faked_msg, 0, sizeof(struct sip_msg));
+	faked_msg.len=headers.len;
+	faked_msg.buf=faked_msg.unparsed=headers.s;
+	if (parse_headers(&faked_msg, HDR_EOH_F, 0)==-1){
+		rpc->fault(c, 400, "Invalid headers");
+		return;
+	}
+	/* at this moment all the parameters are parsed => more sanity checks */
+	if (rpc_uac_check_msg(rpc, c, &faked_msg, &method, &body, &fromtag,
+							&cseq_is, &cseq, &callid)<0)
+		goto error;
+	hfb.s=get_hfblock(nexthop.len? &nexthop: &ruri, faked_msg.headers,
+						PROTO_NONE, ssock, &hfb.len);
+	if (hfb.s==0){
+		rpc->fault(c, 500, "out of memory");
+		goto error;
+	}
+	/* proceed to transaction creation */
+	memset(&dlg, 0, sizeof(dlg_t));
+	/* fill call-id if call-id present or else generate a callid */
+	if (callid.s && callid.len) dlg.id.call_id=callid;
+	else generate_callid(&dlg.id.call_id);
+	
+	/* We will not fill in dlg->id.rem_tag because
+	 * if present it will be printed within To HF
+	 */
+	
+	/* Generate fromtag if not present */
+	if (!fromtag) {
+		generate_fromtag(&dlg.id.loc_tag, &dlg.id.call_id);
+	}
+	
+	/* Fill in CSeq */
+	if (cseq_is) dlg.loc_seq.value = cseq;
+	else dlg.loc_seq.value = DEFAULT_CSEQ;
+	dlg.loc_seq.is_set = 1;
+	
+	dlg.loc_uri = faked_msg.from->body;
+	dlg.rem_uri = faked_msg.to->body;
+	dlg.rem_target = ruri;
+	dlg.dst_uri = nexthop;
+	dlg.send_sock=ssock;
+	
+	memset(&uac_req, 0, sizeof(uac_req));
+	uac_req.method=&method;
+	uac_req.headers=&hfb;
+	uac_req.body=body.len?&body:0;
+	uac_req.dialog=&dlg;
+	ret = t_uac(&uac_req);
+	
+	if (ret <= 0) {
+		err_ret = err2reason_phrase(ret, &sip_error, err_buf,
+			sizeof(err_buf), "RPC/UAC") ;
+		if (err_ret > 0 )
+		{
+			rpc->fault(c, sip_error, "%s", err_buf);
+		} else {
+			rpc->fault(c, 500, "RPC/UAC error");
+		}
+		goto error01;
+	}
+error01:
+	if (hfb.s) pkg_free(hfb.s);
+error:
+	if (faked_msg.headers) free_hdr_field_lst(faked_msg.headers);
+}
+
+
+
+/** t_uac with no reply waiting.
+  * @see rpc_t_uac.
+  */
+void rpc_t_uac_start(rpc_t* rpc, void* c)
+{
+	rpc_t_uac(rpc, c, 0);
+}
+
+/** t_uac with reply waiting.
+  * @see rpc_t_uac.
+  */
+void rpc_t_uac_wait(rpc_t* rpc, void* c)
+{
+	rpc_t_uac(rpc, c, 1);
+}
+
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */

+ 40 - 0
modules/tm/rpc_uac.h

@@ -0,0 +1,40 @@
+/* 
+ * $Id$
+ * 
+ * Copyright (C) 2009 iptelorg GmbH
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * modules/tm/rpc_uac.h
+ */
+/*
+ * History:
+ * --------
+ *  2009-07-20  initial version (andrei)
+*/
+
+#ifndef __rpc_uac_h
+#define __rpc_uac_h
+
+#include "../../rpc.h"
+
+extern const char* rpc_t_uac_start_doc[2];
+extern const char* rpc_t_uac_wait_doc[2];
+
+void rpc_t_uac_start(rpc_t* rpc, void* c);
+void rpc_t_uac_wait(rpc_t* rpc, void* c);
+
+#endif /*__rpc_uac_h*/
+
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */

+ 3 - 0
modules/tm/tm.c

@@ -133,6 +133,7 @@
 #include "t_msgbuilder.h"
 #include "select.h"
 #include "t_serial.h"
+#include "rpc_uac.h"
 
 MODULE_VERSION
 
@@ -1715,6 +1716,8 @@ static rpc_export_t tm_rpc[] = {
 	{"tm.reply",  rpc_reply,    rpc_reply_doc,    0},
 	{"tm.stats",  tm_rpc_stats, tm_rpc_stats_doc, 0},
 	{"tm.hash_stats",  tm_rpc_hash_stats, tm_rpc_hash_stats_doc, 0},
+	{"tm.t_uac_start", rpc_t_uac_start, rpc_t_uac_start_doc, 0 },
+	{"tm.t_uac_wait",  rpc_t_uac_wait,  rpc_t_uac_wait_doc, RET_ARRAY},
 	{0, 0, 0, 0}
 };