1 /*
2  * $Id$
3  *
4  * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
5  * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
6  *
7  * The initial version of this code was written by Dragos Vingarzan
8  * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
9  * Fruanhofer Institute. It was and still is maintained in a separate
10  * branch of the original SER. We are therefore migrating it to
11  * Kamailio/SR and look forward to maintaining it from here on out.
12  * 2011/2012 Smile Communications, Pty. Ltd.
13  * ported/maintained/improved by
14  * Jason Penton (jason(dot)penton(at)smilecoms.com and
15  * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
16  * effort to add full IMS support to Kamailio/SR using a new and
17  * improved architecture
18  *
19  * NB: Alot of this code was originally part of OpenIMSCore,
20  * FhG Fokus.
21  * Copyright (C) 2004-2006 FhG Fokus
22  * Thanks for great work! This is an effort to
23  * break apart the various CSCF functions into logically separate
24  * components. We hope this will drive wider use. We also feel
25  * that in this way the architecture is more complete and thereby easier
26  * to manage in the Kamailio/SR environment
27  *
28  * This file is part of Kamailio, a free SIP server.
29  *
30  * Kamailio is free software; you can redistribute it and/or modify
31  * it under the terms of the GNU General Public License as published by
32  * the Free Software Foundation; either version 2 of the License, or
33  * (at your option) any later version
34  *
35  * Kamailio is distributed in the hope that it will be useful,
36  * but WITHOUT ANY WARRANTY; without even the implied warranty of
37  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
38  * GNU General Public License for more details.
39  *
40  * You should have received a copy of the GNU General Public License
41  * along with this program; if not, write to the Free Software
42  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
43  *
44  */
45 
46 #include "ims_isc_mod.h"
47 
48 #include "checker.h"
49 #include "mark.h"
50 #include "third_party_reg.h"
51 #include "isc.h"
52 
53 MODULE_VERSION
54 
55 struct tm_binds isc_tmb;
56 usrloc_api_t isc_ulb; /*!< Structure containing pointers to usrloc functions*/
57 
58 /* fixed parameter storage */
59 str isc_my_uri = str_init("scscf.ims.smilecoms.com:6060"); /**< Uri of myself to loop the message in str	*/
60 str isc_my_uri_sip = {0, 0}; /**< Uri of myself to loop the message in str with leading "sip:" */
61 int isc_expires_grace = 120; /**< expires value to add to the expires in the 3rd party register*/
62 int isc_fr_timeout = 5000; /**< default ISC response timeout in ms */
63 int isc_fr_inv_timeout = 20000; /**< default ISC invite response timeout in ms */
64 int add_p_served_user = 0; /**< should the P-Served-User header be inserted? */
65 
66 /** module functions */
67 static int mod_init(void);
68 int isc_match_filter(struct sip_msg *msg, char *str1, udomain_t* d);
69 int isc_match_filter_reg(struct sip_msg *msg, char *str1, udomain_t* d);
70 int isc_from_as(struct sip_msg *msg, char *str1, char *str2);
71 
72 /*! \brief Fixup functions */
73 static int domain_fixup(void** param, int param_no);
74 static int w_isc_match_filter_reg(struct sip_msg* _m, char* str1, char* str2);
75 static int w_isc_match_filter(struct sip_msg* _m, char* str1, char* str2);
76 
77 static cmd_export_t cmds[] = {
78     { "isc_match_filter_reg", (cmd_function) w_isc_match_filter_reg, 2, domain_fixup, 0, REQUEST_ROUTE},
79     { "isc_from_as", (cmd_function) isc_from_as, 1, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
80     { "isc_match_filter", (cmd_function) w_isc_match_filter, 2, domain_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
81     { 0, 0, 0, 0, 0, 0}
82 };
83 
84 static param_export_t params[] = {
85     { "my_uri", PARAM_STR, &isc_my_uri}, /**< SIP Uri of myself for getting the messages back */
86     { "expires_grace", INT_PARAM, &isc_expires_grace}, /**< expires value to add to the expires in the 3rd party register to prevent expiration in AS */
87     { "isc_fr_timeout", INT_PARAM, &isc_fr_timeout}, /**< Time in ms that we are waiting for a AS response until we
88  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 consider it dead. Has to be lower than SIP transaction timeout
89  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 to prevent downstream timeouts. Not too small though because
90  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 AS are usually slow as hell... */
91     { "isc_fr_inv_timeout", INT_PARAM, &isc_fr_inv_timeout}, /**< Time in ms that we are waiting for a AS INVITE response until we
92  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 consider it dead. Has to be lower than SIP transaction timeout
93  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 to prevent downstream timeouts. Not too small though because
94  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 AS are usually slow as hell... */
95     { "add_p_served_user", INT_PARAM, &add_p_served_user}, /**< boolean indicating if the P-Served-User (RFC5502) should be added on the ISC interface or not */
96     { 0, 0, 0}
97 };
98 
99 /** module exports */
100 struct module_exports exports = {
101 	"ims_isc",
102 	DEFAULT_DLFLAGS, /* dlopen flags */
103     	cmds, 		 /* Exported functions */
104     	params,
105 	0, 		 /* exported RPC methods */
106 	0, 		 /* exported pseudo-variables */
107 	0, 		 /* response handling function */
108 	mod_init, 	 /* module initialization function */
109 	0, 		 /* per-child init function */
110 	0                /* module destroy function */
111 };
112 
113 /*! \brief
114  * Convert char* parameter to udomain_t* pointer
115  */
domain_fixup(void ** param,int param_no)116 static int domain_fixup(void** param, int param_no) {
117     udomain_t* d;
118 
119     if (param_no == 2) {
120         if (isc_ulb.register_udomain((char*) *param, &d) < 0) {
121             LM_ERR("failed to register domain\n");
122             return E_UNSPEC;
123         }
124         *param = (void*) d;
125     }
126     return 0;
127 }
128 
129 /*! \brief
130  * Wrapper to do isc match filter refg
131  */
w_isc_match_filter_reg(struct sip_msg * _m,char * str1,char * str2)132 static int w_isc_match_filter_reg(struct sip_msg* _m, char* str1, char* str2) {
133     return isc_match_filter_reg(_m, str1, (udomain_t*) str2);
134 }
135 
136 /*! \brief
137  * Wrapper to do isc match filter
138  */
w_isc_match_filter(struct sip_msg * _m,char * str1,char * str2)139 static int w_isc_match_filter(struct sip_msg* _m, char* str1, char* str2) {
140     return isc_match_filter(_m, str1, (udomain_t*) str2);
141 }
142 
fix_parameters()143 static int fix_parameters() {
144     return 1;
145 }
146 
147 /**
148  * init module function
149  */
mod_init(void)150 static int mod_init(void) {
151 
152     bind_usrloc_t bind_usrloc;
153     /* fix the parameters */
154     if (!fix_parameters())
155         goto error;
156 
157     /* load the TM API */
158     if (load_tm_api(&isc_tmb) != 0) {
159         LM_ERR("can't load TM API\n");
160         goto error;
161     }
162 
163     bind_usrloc = (bind_usrloc_t) find_export("ul_bind_usrloc", 1, 0);
164     if (!bind_usrloc) {
165         LM_ERR("can't bind usrloc\n");
166         return -1;
167     }
168 
169     if (bind_usrloc(&isc_ulb) < 0) {
170         return -1;
171     }
172 
173     /* Init the isc_my_uri parameter */
174     if (!isc_my_uri.s || isc_my_uri.len<=0) {
175         LM_CRIT("mandatory parameter \"isc_my_uri\" found empty\n");
176         goto error;
177     }
178 
179     isc_my_uri_sip.len = 4 + isc_my_uri.len;
180     isc_my_uri_sip.s = shm_malloc(isc_my_uri_sip.len + 1);
181     memcpy(isc_my_uri_sip.s, "sip:", 4);
182     memcpy(isc_my_uri_sip.s + 4, isc_my_uri.s, isc_my_uri.len);
183     isc_my_uri_sip.s[isc_my_uri_sip.len] = 0;
184 
185     LM_DBG("ISC module successfully initialised\n");
186 
187     return 0;
188 error:
189     LM_ERR("Failed to initialise ISC module\n");
190     return -1;
191 }
192 
193 /**
194  * Returns the direction of the dialog as int dialog_direction from a string.
195  * @param direction - "orig" or "term"
196  * @returns DLG_MOBILE_ORIGINATING, DLG_MOBILE_TERMINATING if successful, or
197  * DLG_MOBILE_UNKNOWN on error
198  */
get_dialog_direction(char * direction)199 static inline enum dialog_direction get_dialog_direction(char *direction) {
200     switch (direction[0]) {
201         case 'o':
202         case 'O':
203         case '0':
204             return DLG_MOBILE_ORIGINATING;
205         case 't':
206         case 'T':
207         case '1':
208             return DLG_MOBILE_TERMINATING;
209         default:
210             LM_ERR("Unknown direction %s", direction);
211             return DLG_MOBILE_UNKNOWN;
212     }
213 }
214 
215 /**
216  * Checks if there is a match.
217  * Inserts route headers and set the dst_uri
218  * @param msg - the message to check
219  * @param str1 - the direction of the request orig/term
220  * @param str2 - not used
221  * @returns #ISC_RETURN_TRUE if found, #ISC_RETURN_FALSE if not, #ISC_RETURN_BREAK on error
222  */
isc_match_filter(struct sip_msg * msg,char * str1,udomain_t * d)223 int isc_match_filter(struct sip_msg *msg, char *str1, udomain_t* d) {
224     int k = 0;
225     isc_match *m = NULL;
226     str s = {0, 0};
227 
228     //sometimes s is populated by an ims_getter method cscf_get_terminating_user that alloc memory that must be free-ed at the end
229     int free_s = 0;
230 
231     //the callback from the Cx interface in case of unreg terminating initial message is a FAILURE_ROUTE. Hence we need an addl. flag
232     int firstflag = 0;
233 
234     int ret = ISC_RETURN_FALSE;
235     isc_mark new_mark, old_mark;
236 
237     enum dialog_direction dir = get_dialog_direction(str1);
238 
239     LM_INFO("Checking triggers\n");
240 
241     if (dir == DLG_MOBILE_UNKNOWN)
242         return ISC_RETURN_BREAK;
243 
244     if (!cscf_is_initial_request(msg))
245         return ISC_RETURN_FALSE;
246 
247     /* starting or resuming? */
248     memset(&old_mark, 0, sizeof (isc_mark));
249     memset(&new_mark, 0, sizeof (isc_mark));
250     if (isc_mark_get_from_msg(msg, &old_mark)) {
251         LM_DBG("Message returned s=%d;h=%d;d=%d;a=%.*s\n", old_mark.skip, old_mark.handling, old_mark.direction, old_mark.aor.len, old_mark.aor.s);
252     } else {
253         LM_DBG("Starting triggering\n");
254         firstflag = 1;
255     }
256 
257     if (is_route_type(FAILURE_ROUTE) && !firstflag) {
258         /* need to find the handling for the failed trigger */
259         if (dir == DLG_MOBILE_ORIGINATING) {
260             k = cscf_get_originating_user(msg, &s);
261             if (k) {
262                 k = isc_is_registered(&s, d);
263                 if (k == IMPU_NOT_REGISTERED) {
264                     ret = ISC_RETURN_FALSE;
265                     goto done;
266                 }
267                 new_mark.direction = IFC_ORIGINATING_SESSION;
268                 LM_DBG("Orig User <%.*s> [%d]\n", s.len, s.s, k);
269             } else
270                 goto done;
271         }
272         if (dir == DLG_MOBILE_TERMINATING) {
273             k = cscf_get_terminating_user(msg, &s);
274             //sometimes s is populated by an ims_getter method cscf_get_terminating_user that alloc memory that must be free-ed at the end
275             free_s = 1;
276 
277             if (k) {
278                 k = isc_is_registered(&s, d);
279                 //LOG(L_DBG,"after isc_is_registered in ISC_match_filter\n");
280                 if (k == IMPU_REGISTERED) {
281                     new_mark.direction = IFC_TERMINATING_SESSION;
282                 } else {
283                     new_mark.direction = IFC_TERMINATING_UNREGISTERED;
284                 }
285                 LM_DBG("Term User <%.*s> [%d]\n", s.len, s.s, k);
286             } else {
287                 goto done;
288             }
289         }
290         struct cell * t = isc_tmb.t_gett();
291         LM_CRIT("SKIP: %d\n", old_mark.skip);
292         int index = old_mark.skip;
293         for (k = 0; k < t->nr_of_outgoings; k++) {
294             m = isc_checker_find(s, new_mark.direction, index, msg,
295                     isc_is_registered(&s, d), d);
296             if (m) {
297                 index = m->index;
298                 if (k < t->nr_of_outgoings - 1)
299                     isc_free_match(m);
300             } else {
301                 LM_ERR("On failure, previously matched trigger no longer matches?!\n");
302                 ret = ISC_RETURN_BREAK;
303                 goto done;
304             }
305         }
306         if (m->default_handling == IFC_SESSION_TERMINATED) {
307             /* Terminate the session */
308             LM_DBG("Terminating session.\n");
309             isc_tmb.t_reply(msg, IFC_AS_UNAVAILABLE_STATUS_CODE, "AS Contacting Failed - iFC terminated dialog");
310             LM_DBG("Responding with %d to URI: %.*s\n",
311                     IFC_AS_UNAVAILABLE_STATUS_CODE, msg->first_line.u.request.uri.len, msg->first_line.u.request.uri.s);
312             isc_free_match(m);
313             ret = ISC_RETURN_BREAK;
314             goto done;
315         }
316         /* skip the failed triggers (IFC_SESSION_CONTINUED) */
317         old_mark.skip = index + 1;
318 
319         isc_free_match(m);
320         isc_mark_drop_route(msg);
321     }
322 
323     LM_DBG("Checking if ISC is for originating user\n");
324     /* originating leg */
325     if (dir == DLG_MOBILE_ORIGINATING) {
326         k = cscf_get_originating_user(msg, &s);
327         LM_DBG("ISC is for Orig user\n");
328         if (k) {
329             LM_DBG("Orig user is [%.*s]\n", s.len, s.s);
330             k = isc_is_registered(&s, d);
331             if (k == IMPU_NOT_REGISTERED) {
332                 LM_DBG("User is not registered\n");
333                 return ISC_RETURN_FALSE;
334             }
335 
336             LM_DBG("Orig User <%.*s> [%d]\n", s.len, s.s, k);
337             //CHECK if this is a new call (According to spec if the new uri and old mark URI are different then this is a new call and should
338             //be triggered accordingly
339             LM_DBG("Checking if RURI has changed...comparing: <%.*s> and <%.*s>\n",
340                     old_mark.aor.len, old_mark.aor.s,
341                     s.len, s.s);
342             if ((old_mark.aor.len == s.len) && memcmp(old_mark.aor.s, s.s, s.len) != 0) {
343                 LM_DBG("This is a new call....... trigger accordingly\n");
344                 m = isc_checker_find(s, old_mark.direction, 0, msg,
345                         isc_is_registered(&s, d), d);
346             } else {
347                 m = isc_checker_find(s, old_mark.direction, old_mark.skip, msg,
348                         isc_is_registered(&s, d), d);
349             }
350             if (m) {
351                 new_mark.direction = IFC_ORIGINATING_SESSION;
352                 new_mark.skip = m->index + 1;
353                 new_mark.handling = m->default_handling;
354                 new_mark.aor = s;
355                 ret = isc_forward(msg, m, &new_mark, firstflag);
356                 isc_free_match(m);
357                 goto done;
358             }
359         }
360         goto done;
361     }
362     LM_DBG("Checking if ISC is for terminating user\n");
363     /* terminating leg */
364     if (dir == DLG_MOBILE_TERMINATING) {
365         k = cscf_get_terminating_user(msg, &s);
366         //sometimes s is populated by an ims_getter method cscf_get_terminating_user that alloc memory that must be free-ed at the end
367         free_s = 1;
368         LM_DBG("ISC is for Term user\n");
369         if (k) {
370             k = isc_is_registered(&s, d);
371             if (k == IMPU_REGISTERED) {
372                 new_mark.direction = IFC_TERMINATING_SESSION;
373             } else {
374                 new_mark.direction = IFC_TERMINATING_UNREGISTERED;
375             }
376             LM_DBG("Term User <%.*s> [%d]\n", s.len, s.s, k);
377             //CHECK if this is a new call (According to spec if the new uri and old mark URI are different then this is a new call and should
378             //be triggered accordingly
379             LM_DBG("Checking if RURI has changed...comparing: <%.*s> and <%.*s>\n",
380                     old_mark.aor.len, old_mark.aor.s,
381                     s.len, s.s);
382             if ((old_mark.aor.len == s.len) && memcmp(old_mark.aor.s, s.s, s.len) != 0) {
383                 LM_DBG("This is a new call....... trigger accordingly\n");
384                 m = isc_checker_find(s, new_mark.direction, 0, msg, isc_is_registered(&s, d), d);
385             } else {
386                 LM_DBG("Resuming triggering\n");
387                 m = isc_checker_find(s, new_mark.direction, old_mark.skip, msg, isc_is_registered(&s, d), d);
388             }
389             if (m) {
390                 new_mark.skip = m->index + 1;
391                 new_mark.handling = m->default_handling;
392                 new_mark.aor = s;
393                 ret = isc_forward(msg, m, &new_mark, firstflag);
394                 isc_free_match(m);
395                 goto done;
396             }
397         }
398         goto done;
399     }
400 
401 done:
402 
403     if (s.s && free_s == 1)
404         shm_free(s.s); // shm_malloc in cscf_get_terminating_user
405 
406     if (old_mark.aor.s)
407         pkg_free(old_mark.aor.s);
408     return ret;
409 }
410 
clean_impu_str(str * impu_s)411 void clean_impu_str(str* impu_s) {
412     char *p;
413 
414     if ((p = memchr(impu_s->s, ';', impu_s->len))) {
415 	impu_s->len = p - impu_s->s;
416     }
417 }
418 /**
419  * Checks if there is a match on REGISTER.
420  * Inserts route headers and set the dst_uri
421  * @param msg - the message to check
422  * @param str1 - if the user was previously registered 0 - for initial registration, 1 for re/de-registration
423  * @param str2 - not used
424  * @returns #ISC_RETURN_TRUE if found, #ISC_RETURN_FALSE if not
425  */
isc_match_filter_reg(struct sip_msg * msg,char * str1,udomain_t * d)426 int isc_match_filter_reg(struct sip_msg *msg, char *str1, udomain_t* d) {
427     int k;
428     isc_match *m;
429     str s = {0, 0};
430     int ret = ISC_RETURN_FALSE;
431     isc_mark old_mark;
432 
433     enum dialog_direction dir = DLG_MOBILE_ORIGINATING;
434 
435     LM_DBG("Checking triggers\n");
436 
437     /* starting or resuming? */
438     memset(&old_mark, 0, sizeof (isc_mark));
439     LM_DBG("Starting triggering\n");
440 
441     /* originating leg */
442     if (dir == DLG_MOBILE_ORIGINATING) {
443         k = cscf_get_originating_user(msg, &s);
444         if (k) {
445             if (str1 == 0 || strlen(str1) != 1) {
446                 LM_ERR("wrong parameter - must be \"0\" (initial registration) or \"1\"(previously registered) \n");
447                 return ret;
448             } else if (str1[0] == '0')
449                 k = 0;
450             else
451                 k = 1;
452 
453 	    LM_DBG("Orig User before clean: <%.*s> [%d]\n", s.len, s.s, k);
454 	    clean_impu_str(&s);
455             LM_DBG("Orig User after clean: <%.*s> [%d]\n", s.len, s.s, k);
456 
457             m = isc_checker_find(s, old_mark.direction, old_mark.skip, msg, k, d);
458             while (m) {
459                 LM_DBG("REGISTER match found in filter criteria\n");
460                 ret = isc_third_party_reg(msg, m, &old_mark, d);
461                 old_mark.skip = m->index + 1;
462                 isc_free_match(m);
463                 m = isc_checker_find(s, old_mark.direction, old_mark.skip, msg, k, d);
464             }
465 
466             if (ret == ISC_RETURN_FALSE)
467                 LM_DBG("No REGISTER match found in filter criteria\n");
468         }
469     }
470     return ret;
471 }
472 
473 /**
474  * Check if the message is from the AS.
475  * Inserts route headers and set the dst_uri
476  * @param msg - the message to check
477  * @param str1 - the direction of the request orig/term
478  * @param str2 - not used
479  * @returns #ISC_RETURN_TRUE if from AS, #ISC_RETURN_FALSE if not, #ISC_RETURN_BREAK on error
480  */
isc_from_as(struct sip_msg * msg,char * str1,char * str2)481 int isc_from_as(struct sip_msg *msg, char *str1, char *str2) {
482     int ret = ISC_RETURN_FALSE;
483     isc_mark old_mark;
484     str s = {0, 0};
485     //sometimes s is populated by an ims_getter method cscf_get_terminating_user that alloc memory that must be free-ed at the end
486     int free_s = 0;
487 
488 
489     enum dialog_direction dir = get_dialog_direction(str1);
490 
491     if (dir == DLG_MOBILE_UNKNOWN)
492         return ISC_RETURN_BREAK;
493 
494     if (!cscf_is_initial_request(msg))
495         return ISC_RETURN_FALSE;
496 
497     /* starting or resuming? */
498     if (isc_mark_get_from_msg(msg, &old_mark)) {
499         LM_DBG("Message returned s=%d;h=%d;d=%d\n", old_mark.skip, old_mark.handling, old_mark.direction);
500 
501         /*according to spec 24.229, 5.4.3.3 if the URI is different then the RURI is retargeted and we can do one of 2 things,
502          * a) mark as originating session and forward according to normal RURI procedures,
503          * b) run IFC criteria on new retrageturi  and route accordingly.
504          *
505          * We will therefore leave it in the hands of the config writer to decide. We will make them aware here that retargeting has happened
506          * by retirning a special error code ISC_RETURN_RETARGET(-2).
507          */
508         if (dir == DLG_MOBILE_TERMINATING) {
509             cscf_get_terminating_user(msg, &s);
510             //sometimes s is populated by an ims_getter method cscf_get_terminating_user that alloc memory that must be free-ed at the end
511             free_s = 1;
512             if (memcmp(old_mark.aor.s, s.s, s.len) != 0) {
513                 LM_DBG("This is a new call....... RURI has been retargeted\n");
514                 return ISC_RETURN_RETARGET;
515             }
516         }
517         if (old_mark.direction == IFC_ORIGINATING_SESSION
518                 && dir != DLG_MOBILE_ORIGINATING)
519             ret = ISC_RETURN_FALSE;
520         else if ((old_mark.direction == IFC_TERMINATING_SESSION
521                 || old_mark.direction == IFC_TERMINATING_UNREGISTERED)
522                 && dir != DLG_MOBILE_TERMINATING)
523             ret = ISC_RETURN_FALSE;
524         else
525             ret = ISC_RETURN_TRUE;
526     } else {
527         ret = ISC_RETURN_FALSE;
528     }
529     if (old_mark.aor.s)
530         pkg_free(old_mark.aor.s);
531 
532     if (s.s && free_s == 1)
533         shm_free(s.s); // shm_malloc in cscf_get_terminating_user
534 
535     return ret;
536 }
537 
538