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