ims_isc: add-on for third-party registration
[kamailio] / src / modules / ims_isc / checker.c
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 "checker.h"
47
48 /**
49  *      Check if a Service Point Trigger for Header matches the SDP body
50  *      @param spt - the service point trigger
51  *      @param headers - the headers of the message
52  *      @returns - 1 on success, 0 on failure
53  */
54 static int isc_check_headers(ims_spt *spt, struct hdr_field *headers) {
55         struct hdr_field *i;
56         char c, ch;
57         char buf[256];
58         regex_t header_comp, content_comp;
59         i = headers;
60
61     if (spt->sip_header.header.len >= sizeof(buf)) {
62         LM_ERR("Header name \"%.*s\" is to long to be processed (max %d bytes)\n", spt->sip_header.header.len, spt->sip_header.header.s, (int) (sizeof(buf) - 1));
63         return FALSE;
64     }
65     if (spt->sip_header.content.len >= sizeof(buf)) {
66         LM_ERR("Header content \"%.*s\" is to long to be processed (max %d bytes)\n", spt->sip_header.content.len, spt->sip_header.content.s, (int) (sizeof(buf) - 1));
67         return FALSE;
68     }
69
70         /* compile the regex for header name */
71         memcpy(buf, spt->sip_header.header.s, spt->sip_header.header.len);
72         buf[spt->sip_header.header.len] = 0;
73         if (regcomp(&(header_comp), buf, REG_ICASE | REG_EXTENDED) != 0) {
74             LM_ERR("Error compiling the following regexp for header name: %.*s\n", spt->sip_header.header.len, spt->sip_header.header.s);
75             return FALSE;
76         }
77
78         /* compile the regex for content */
79         memcpy(buf, spt->sip_header.content.s, spt->sip_header.content.len);
80         buf[spt->sip_header.content.len] = 0;
81         if(regcomp(&(content_comp), buf, REG_ICASE | REG_EXTENDED) != 0) {
82             LM_ERR("Error compiling the following regexp for header content: %.*s\n", spt->sip_header.content.len, spt->sip_header.content.s);
83             regfree(&(header_comp));
84             return FALSE;
85         }
86
87         LM_DBG("isc_check_headers: Looking for Header[%.*s(%d)] %.*s \n",
88                         spt->sip_header.header.len, spt->sip_header.header.s, spt->sip_header.type, spt->sip_header.content.len, spt->sip_header.content.s);
89         while (i != NULL) {
90                 ch = i->name.s[i->name.len];
91                 i->name.s[i->name.len] = 0;
92
93                 if ((spt->sip_header.type > 0 && spt->sip_header.type == i->type) || //matches known type
94                                 (regexec(&(header_comp), i->name.s, 0, NULL, 0) == 0) //or matches the name
95                                 ) {
96
97                         i->name.s[i->name.len] = ch;
98                         LM_DBG("isc_check_headers: Found Header[%.*s(%d)] %.*s \n",
99                                         i->name.len, i->name.s, i->type, i->body.len, i->body.s);
100                         //if the header should be absent but found it
101
102                         if (spt->sip_header.content.s == NULL)
103                                 if (spt->condition_negated) {
104                                         regfree(&(header_comp));
105                                         regfree(&(content_comp));
106                                         return FALSE;
107                                 }
108
109                         //check regex
110                         c = i->body.s[i->body.len];
111                         i->body.s[i->body.len] = 0;
112
113                         if (regexec(&(content_comp), i->body.s, 0, NULL, 0) == 0) //regex match
114                         {
115                                 regfree(&(header_comp));
116                                 regfree(&(content_comp));
117                                 i->body.s[i->body.len] = c;
118                                 return TRUE;
119                         }
120
121                         i->body.s[i->body.len] = c;
122                 } else
123                         i->name.s[i->name.len] = ch;
124                 i = i->next;
125         }
126
127         regfree(&(header_comp));
128         regfree(&(content_comp));
129         return FALSE;
130 }
131
132 static str sdp = { "application/sdp", 15 };
133
134 /**
135  *      Check if a Service Point Trigger for Session Description matches the SDP body
136  *      @param spt - the service point trigger
137  *      @param msg - the message
138  *      @returns - 1 on success, 0 on failure
139  */
140 static int isc_check_session_desc(ims_spt *spt, struct sip_msg *msg) {
141         int len;
142         char *body, c;
143         char *x;
144         regex_t comp;
145
146         if (msg->content_type == NULL)
147                 return FALSE;
148         if (strncasecmp(msg->content_type->body.s, sdp.s,
149                         msg->content_type->body.len) != 0)
150                 return FALSE;
151         LM_DBG("ifc_check_session_desc:      Found Content-Type == appliction/sdp\n");
152         //check for sdp line
153         body = get_body(msg);
154         if (body == 0)
155                 return FALSE;
156         if (msg->content_length->parsed == NULL) {
157                 parse_content_length(msg->content_length->body.s,
158                                 msg->content_length->body.s + msg->content_length->body.len,
159                                 &len);
160                 msg->content_length->parsed = (void*) (long) len;
161         } else
162                 len = (long) msg->content_length->parsed;
163
164         c = body[len];
165         body[len] = 0;
166         x =     pkg_malloc(spt->session_desc.line.len + 2 + spt->session_desc.content.len);
167         sprintf(x, "%.*s=%.*s", spt->session_desc.line.len,
168                         spt->session_desc.line.s, spt->session_desc.content.len,
169                         spt->session_desc.content.s);
170         /* compile the whole  regexp */
171         regcomp(&(comp), x, REG_ICASE | REG_EXTENDED);
172         if (regexec(&(comp), body, 0, NULL, 0) == 0) //regex match
173         {
174                 body[len] = c;
175                 LM_DBG("ifc_check_session_desc:      Found Session Desc. > %s\n", body);
176                 pkg_free(x);
177                 return TRUE;
178         }
179         body[len] = c;
180         pkg_free(x);
181         return FALSE;
182 }
183
184 /**
185  *      Check if a Service Point Trigger for RURI matches the RURI of a message
186  *      @param spt - the service point trigger
187  *      @param msg - the message
188  *      @returns - 1 on success, 0 on failure
189  */
190 static int isc_check_ruri(ims_spt *spt, struct sip_msg *msg) {
191         char buf[256];
192         regex_t comp;
193
194         if (spt->request_uri.len >= sizeof(buf)) {
195             LM_ERR("RURI \"%.*s\" is to long to be processed (max %d bytes)\n", spt->request_uri.len, spt->request_uri.s, (int) (sizeof(buf) - 1));
196             return FALSE;
197         }
198
199         /* compile the regex for content */
200         memcpy(buf, spt->request_uri.s, spt->request_uri.len);
201         buf[spt->request_uri.len] = 0;
202         if(regcomp(&(comp), buf, REG_ICASE | REG_EXTENDED) != 0) {
203             LM_ERR("Error compiling the following regexp for RURI content: %.*s\n", spt->request_uri.len, spt->request_uri.s);
204             return FALSE;
205         }
206
207         if (regexec(&(comp), buf, 0, NULL, 0) == 0) //regex match
208         {
209                 regfree(&(comp));
210                 return TRUE;
211         }
212         regfree(&(comp));
213         return FALSE;
214 }
215
216
217
218 /**
219  *      Check if a Service Point Trigger matches a message 
220  *      @param spt - the service point trigger
221  *      @param msg - the message
222  *      @param direction - if filter criteria is for originating/terminating/terminating_unregistered
223  *      @param registration_type - if the message is initial/re/de registration
224  *      @returns - 1 on success, 0 on failure
225  */
226 static int isc_check_spt(ims_spt *spt, struct sip_msg *msg, char direction,
227                 char registration_type) {
228         int r = FALSE;
229         switch (spt->type) {
230         case IFC_REQUEST_URI:
231                 LM_DBG("ifc_check_spt:             SPT type %d -> RequestURI == %.*s ?\n",
232                                 spt->type, spt->request_uri.len, spt->request_uri.s);
233                 LM_DBG("ifc_check_spt:               Found Request URI %.*s \n",
234                                 msg->first_line.u.request.uri.len, msg->first_line.u.request.uri.s);
235                 r = isc_check_ruri(spt, msg);
236                 break;
237         case IFC_METHOD:
238                 LM_DBG("ifc_check_spt:             SPT type %d -> Method == %.*s ?\n",
239                                 spt->type, spt->method.len, spt->method.s);
240                 LM_DBG("ifc_check_spt:               Found method %.*s \n",
241                                 msg->first_line.u.request.method.len, msg->first_line.u.request.method.s);
242                 r = (strncasecmp(spt->method.s, msg->first_line.u.request.method.s,
243                                 spt->method.len) == 0);
244                 if (r && spt->method.len == 8
245                                 && strncasecmp(spt->method.s, "REGISTER", 8) == 0
246                                 && !(spt->registration_type == 0
247                                                 || (registration_type & spt->registration_type)))
248                         r = 0;
249                 break;
250         case IFC_SIP_HEADER:
251                 LM_DBG("ifc_check_spt:             SPT type %d -> Header[%.*s]  %%= %.*s ?\n",
252                                 spt->type, spt->sip_header.header.len, spt->sip_header.header.s, spt->sip_header.content.len, spt->sip_header.content.s);
253                 if (parse_headers(msg, HDR_EOH_F, 0) != 0) {
254                         LM_ERR("ifc_checker: can't parse all headers\n");
255                         r = FALSE;
256                 } else
257                         r = isc_check_headers(spt, msg->headers);
258                 break;
259         case IFC_SESSION_CASE:
260                 LM_DBG("ifc_check_spt:             SPT type %d -> Session Case  == %d ?\n",
261                                 spt->type, spt->session_case);
262                 LM_DBG("ifc_check_spt:               Found session_case %d \n",
263                                 direction);
264                 r = (direction == spt->session_case);
265                 break;
266         case IFC_SESSION_DESC:
267                 LM_DBG("ifc_check_spt:             SPT type %d -> Session Desc.[%.*s]  %%= %.*s ?\n",
268                                 spt->type, spt->session_desc.line.len, spt->session_desc.line.s, spt->session_desc.content.len, spt->session_desc.content.s);
269
270                 if (parse_headers(msg, HDR_CONTENTTYPE_F | HDR_CONTENTLENGTH_F, 0) != 0) {
271                         LM_ERR("ifc_checker: can't parse all headers \n");
272                         r = FALSE;
273                 }
274                 r = isc_check_session_desc(spt, msg);
275                 break;
276         default:
277                 LM_ERR("ifc_checker: unknown spt type %d \n", spt->type);
278                 return FALSE;
279         }
280         if (spt->condition_negated)
281                 return !r;
282         else
283                 return r;
284 }
285
286 /**
287  *      Check if an entire filter criteria matches a message 
288  *      @param fc - the filter criteria
289  *      @param msg - the message
290  *      @param direction - if filter criteria is for originating/terminating/terminating_unregistered
291  *      @param registration_type - if the message is initial/re/de registration
292  *      @returns - 1 on success, 0 on failure
293  */
294 static int isc_check_filter_criteria(ims_filter_criteria *fc,
295                 struct sip_msg *msg, char direction, char registration_type) {
296
297         int i, partial, total, inside, outside, group;
298         ims_trigger_point *t;
299         t = fc->trigger_point;
300
301         /* If the trigger is missing -> always fwd */
302         if (t == NULL)
303                 return TRUE;
304         /* This shouldn't happen */
305         if (msg == NULL)
306                 return FALSE;
307
308         if (t->condition_type_cnf == IFC_CNF) { //CNF
309                 inside = TRUE;
310                 outside = FALSE;
311                 partial = FALSE;
312                 total = TRUE;
313         } else { //DNF
314                 inside = FALSE;
315                 outside = TRUE;
316                 partial = TRUE;
317                 total = FALSE;
318         }
319         LM_DBG("ifc_checker_trigger: Starting expression check: \n");
320         group = t->spt[0].group;
321         for (i = 0; i < t->spt_cnt; i++) {
322                 if (group != t->spt[i].group) { //jump to other group
323                         total = t->condition_type_cnf == IFC_CNF ?
324                                         total && partial : total || partial;
325                         if (total == outside) {
326                                 LM_DBG("ifc_checker_trigger: Total compromised, aborting...\n");
327                                 return outside; // will never match from now on, so get out
328                         }
329
330                         group = t->spt[i].group;
331                         partial = isc_check_spt(t->spt + i, msg, direction,
332                                         registration_type);
333                         LM_DBG("ifc_checker_trigger:  - group %d => %d. \n", group, partial);
334                 } else { //in same group
335                         partial = t->condition_type_cnf == IFC_CNF ? partial || isc_check_spt(t->spt + i, msg, direction, registration_type) : partial
336                                                                         && isc_check_spt(t->spt + i, msg, direction, registration_type);
337                 }
338
339                 if (partial == inside) { // can't change partial from now, so next group
340                         LM_DBG("ifc_checker_trigger:       - group compromised, skipping to next group\n");
341                         while (i + 1 < t->spt_cnt && t->spt[i + 1].group == group)
342                                 i++;
343                         continue;
344                 }
345         }
346         total = t->condition_type_cnf == IFC_CNF ?
347                         total && partial : total || partial;
348         LM_DBG("ifc_checker_trigger: Check finished => %d\n", total);
349         return total;
350 }
351
352 /**
353  * Create a new matching instance
354  * @param fc - filter criteria that match
355  * @param index - index of the filter that matches
356  * @returns the new isc_match* structure or NULL on error 
357  */
358 static inline isc_match* isc_new_match(ims_filter_criteria *fc, int index) {
359         isc_match *r = 0;
360
361         r = pkg_malloc(sizeof (isc_match));
362         if (!r) {
363                 LM_ERR("isc_new_match(): error allocating %lx bytes\n", sizeof (isc_match));
364                 return 0;
365         }
366         memset(r, 0, sizeof(isc_match));
367         if (fc->application_server.server_name.len) {
368                 r->server_name.s = pkg_malloc(fc->application_server.server_name.len);
369                 if (!r->server_name.s) {
370                         LM_ERR("isc_new_match(): error allocating %d bytes\n",
371                                         fc->application_server.server_name.len);
372                         pkg_free(r);
373                         return 0;
374                 }
375                 r->server_name.len = fc->application_server.server_name.len;
376                 memcpy(r->server_name.s, fc->application_server.server_name.s,
377                                 fc->application_server.server_name.len);
378         }
379         r->default_handling = fc->application_server.default_handling;
380         if (fc->application_server.service_info.len) {
381                 r->service_info.s = pkg_malloc(fc->application_server.service_info.len);
382                 if (!r->service_info.s) {
383                         LM_ERR("isc_new_match(): error allocating %d bytes\n",
384                                         fc->application_server.service_info.len);
385                         if (r->server_name.s) {
386                                 pkg_free(r->server_name.s);
387                         }
388                         pkg_free(r);
389                         return 0;
390                 }
391                 r->service_info.len = fc->application_server.service_info.len;
392                 memcpy(r->service_info.s, fc->application_server.service_info.s,
393                                 fc->application_server.service_info.len);
394         }
395         r->index = index;
396         r->include_register_request = fc->application_server.include_register_request;
397         r->include_register_response = fc->application_server.include_register_response;
398         return r;
399 }
400
401 /**
402  * Find the next match and fill up the ifc_match structure with the position of the match
403  * @param uri - URI of the user for which to apply the IFC
404  * @param direction - direction of the session
405  * @param skip - how many IFCs to skip because already matched
406  * @param msg - the SIP initial request to check on 
407  * @return - TRUE if found, FALSE if none found, end of search space 
408  */
409 isc_match* isc_checker_find(str uri, char direction, int skip,
410                 struct sip_msg *msg, int registered, udomain_t *d) {
411         int expires;
412         char registration_type;
413         int i, j, k, cnt, si, sj, next;
414
415         impurecord_t *p;
416         int ret;
417
418         ims_service_profile *sp;
419         ims_filter_criteria *fc;
420         isc_match *r;
421
422         if (skip == 0)
423                 LM_DBG("isc_checker_find: starting search\n");
424         else
425                 LM_DBG("isc_checker_find: resuming search from %d\n", skip);
426
427         expires = cscf_get_expires(msg);
428         if (!registered)
429                 registration_type = IFC_INITIAL_REGISTRATION;
430         else if (expires > 0)
431                 registration_type = IFC_RE_REGISTRATION;
432         else
433                 registration_type = IFC_DE_REGISTRATION;
434
435         isc_ulb.lock_udomain(d, &uri);
436
437         //need to get the urecord
438         if ((ret = isc_ulb.get_impurecord(d, &uri, &p)) != 0) {
439                 isc_ulb.unlock_udomain(d, &uri);
440                 LM_ERR("Failure getting IMPU record for [%.*s] - ISC checker find METHOD: [%.*s]\n", uri.len, uri.s, msg->first_line.u.request.method.len, msg->first_line.u.request.method.s);
441                 return 0;
442         };
443
444         LM_DBG("isc_checker_find(): got a r_public for the user %.*s\n",
445                         uri.len, uri.s);
446         if (!p->s) {
447                 LM_DBG("isc_checker_find() : got an user without a subscription\n");
448                 //need to free the record somewhere
449                 //isc_ulb.release_impurecord(p);
450                 //need to do an unlock on the domain somewhere
451                 isc_ulb.unlock_udomain(d, &uri);
452                 return 0;
453         }
454
455         /* find the starting fc as the skip-th one*/
456         cnt = 0;
457         si = 0;
458         sj = 0;
459
460         LM_DBG("About to try p->s->service_profiles_cnt!! #profiles is %d\n",
461                         p->s->service_profiles_cnt);
462         while (si < p->s->service_profiles_cnt) {
463                 LM_DBG("About to try p->s->service_profiles[a].filter_criterai_cnt\n");
464                 next = cnt + p->s->service_profiles[si].filter_criteria_cnt;
465                 if (cnt <= skip && skip < next) {
466                         sj = skip - cnt;
467                         cnt += sj;
468                         break;
469                 }
470                 cnt = next;
471                 si++;
472         }
473
474         LM_DBG("DEBUG ISC: SECOND TIME About to try p->s->service_profiles_cnt!!\n");
475         /* iterate through the rest and check for matches */
476         i = si;
477         while (i < p->s->service_profiles_cnt) {
478                 LM_DBG("DEBUG ISC : About to try p->s->service_profiles\n");
479                 sp = p->s->service_profiles + i;
480                 k = 0;
481                 LM_DBG("DEBUG ISC : About to try public identities\n");
482                 for (j = 0; j < sp->public_identities_cnt; j++) {
483
484                         LM_DBG("DEBUG ISC : About to try WPSI\n");
485                         if (p->s->wpsi) {
486                                 // here i should regexec again!
487                                 // to check this , but anyway if i already got p
488                                 // from the get_r_public , that is already checked...
489                                 // or not if there is no wildcardPSI but ... then ...
490                                 //isc_check_wpsi_match();
491                                 k = 1;
492                                 break;
493
494                         } else {
495                                 if (sp->public_identities[j].public_identity.len == uri.len
496                                                 && strncasecmp(
497                                                                 sp->public_identities[j].public_identity.s,
498                                                                 uri.s, uri.len) == 0) {
499                                         k = 1;
500                                         break;
501                                 }
502                         }
503                 }
504
505                 if (!k) {/* this sp is not for this id */
506
507                         cnt += sp->filter_criteria_cnt;
508                 } else {
509
510                         for (j = sj; j < sp->filter_criteria_cnt; j++) {
511                                 fc = sp->filter_criteria + j;
512                                 if (fc->profile_part_indicator) {
513                                         if (((registered == IMS_USER_REGISTERED)
514                                                         && (*fc->profile_part_indicator))
515                                                         || ((registered == IMS_USER_UNREGISTERED)
516                                                                         && !(*fc->profile_part_indicator))) {
517                                                 LM_DBG("isc_checker_find: this one is not good... ppindicator wrong \n");
518                                                 cnt++;
519                                                 continue;
520                                         }
521                                 }
522
523                                 if (isc_check_filter_criteria(fc, msg, direction, registration_type)) {
524                                         LM_DBG("isc_checker_find: MATCH -> %.*s (%.*s) handling %d \n",
525                                                         fc->application_server.server_name.len, fc->application_server.server_name.s, fc->application_server.service_info.len, fc->application_server.service_info.s, fc->application_server.default_handling);
526                                         r = isc_new_match(fc, cnt);
527
528                                         //need to free the record somewhere
529                                         //isc_ulb.release_urecord(p);
530                                         //need to do an unlock on the domain somewhere
531                                         isc_ulb.unlock_udomain(d, &uri);
532
533                                         return r;
534                                 } else {
535                                         cnt++;
536                                         continue;
537                                 }
538                         }
539                 }
540                 i++;
541                 sj = 0;
542         }
543         //need to free the record somewhere
544 //      isc_ulb.release_urecord(p);
545         //need to do an unlock on the domain somewhere
546         isc_ulb.unlock_udomain(d, &uri);
547
548         return 0;
549 }
550
551 /**
552  *      Free up all memory taken by a isc_match.
553  * @param m - match to deallocate
554  */
555 void isc_free_match(isc_match *m) {
556         if (m) {
557                 if (m->server_name.s)
558                         pkg_free(m->server_name.s);
559                 if (m->service_info.s)
560                         pkg_free(m->service_info.s);
561                 pkg_free(m);
562         }
563         LM_DBG("isc_match_free: match position freed\n");
564 }
565 /**
566  *      Find if user is registered or not => TRUE/FALSE.
567  * This uses the S-CSCF registrar to get the state.
568  * @param uri - uri of the user to check
569  * @returns the reg_state
570  */
571 int isc_is_registered(str *uri, udomain_t *d) {
572     int result = 0;
573     int ret = 0;
574     impurecord_t *p;
575
576     isc_ulb.lock_udomain(d, uri);
577
578     LM_DBG("Searching in usrloc\n");
579     //need to get the urecord
580     if ((ret = isc_ulb.get_impurecord(d, uri, &p)) != 0) {
581         LM_DBG("no record exists for [%.*s]\n", uri->len, uri->s);
582         isc_ulb.unlock_udomain(d, uri);
583         return result;
584     }
585
586     LM_DBG("Finished searching usrloc\n");
587     result = p->reg_state;
588     isc_ulb.unlock_udomain(d, uri);
589
590     return result;
591 }
592