lost: adds HELD (RFC6155) and LOST (RFC5222) queries for location-based routing
[kamailio] / src / modules / lost / functions.c
1 /*\r
2  * lost module functions\r
3  *\r
4  * Copyright (C) 2019 Wolfgang Kampichler\r
5  * DEC112, FREQUENTIS AG\r
6  *\r
7  * This file is part of Kamailio, a free SIP server.\r
8  *\r
9  * Kamailio is free software; you can redistribute it and/or modify\r
10  * it under the terms of the GNU General Public License as published by\r
11  * the Free Software Foundation; either version 2 of the License, or\r
12  * (at your option) any later version\r
13  *\r
14  * Kamailio is distributed in the hope that it will be useful,\r
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
17  * GNU General Public License for more details.\r
18  *\r
19  * You should have received a copy of the GNU General Public License\r
20  * along with this program; if not, write to the Free Software\r
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\r
22  *\r
23  */\r
24 \r
25 /*!\r
26  * \file\r
27  * \brief Kamailio lost :: functions\r
28  * \ingroup lost\r
29  * Module: \ref lost\r
30  */\r
31 /*****************/\r
32 \r
33 #include "../../modules/http_client/curl_api.h"\r
34 \r
35 #include "../../core/mod_fix.h"\r
36 #include "../../core/pvar.h"\r
37 #include "../../core/route_struct.h"\r
38 #include "../../core/ut.h"\r
39 #include "../../core/trim.h"\r
40 #include "../../core/mem/mem.h"\r
41 #include "../../core/parser/msg_parser.h"\r
42 #include "../../core/parser/parse_body.h"\r
43 #include "../../core/lvalue.h"\r
44 \r
45 #include "pidf.h"\r
46 #include "utilities.h"\r
47 \r
48 #define LOST_SUCCESS 200\r
49 #define LOST_CLIENT_ERROR 400\r
50 #define LOST_SERVER_ERROR 500\r
51 \r
52 extern httpc_api_t httpapi;\r
53 \r
54 char mtheld[] = "application/held+xml;charset=utf-8";\r
55 char mtlost[] = "application/lost+xml;charset=utf-8";\r
56 \r
57 char uri_element[] = "uri";\r
58 char name_element[] = "displayName";\r
59 char errors_element[] = "errors";\r
60 \r
61 /*\r
62  * lost_function_held(msg, con, pidf, url, err, id)\r
63  * assembles and runs HELD locationRequest, parses results\r
64  */\r
65 int lost_function_held(struct sip_msg *_m, char *_con, char *_pidf, char *_url,\r
66                 char *_err, char *_id)\r
67 {\r
68         pv_spec_t *pspidf;\r
69         pv_spec_t *psurl;\r
70         pv_spec_t *pserr;\r
71 \r
72         pv_value_t pvpidf;\r
73         pv_value_t pvurl;\r
74         pv_value_t pverr;\r
75 \r
76         xmlDocPtr doc = NULL;\r
77         xmlNodePtr root = NULL;\r
78 \r
79         str did = {NULL, 0};\r
80         str que = {NULL, 0};\r
81         str con = {NULL, 0};\r
82         str geo = {NULL, 0};\r
83         str err = {NULL, 0};\r
84         str res = {NULL, 0};\r
85         str idhdr = {NULL, 0};\r
86 \r
87         if(_con == NULL || _pidf == NULL || _url == NULL || _err == NULL) {\r
88                 LM_ERR("invalid parameter\n");\r
89                 goto err;\r
90         }\r
91         /* connection from parameter */\r
92         if(fixup_get_svalue(_m, (gparam_p)_con, &con) != 0) {\r
93                 LM_ERR("cannot get connection string\n");\r
94                 goto err;\r
95         }\r
96         /* id from parameter */\r
97         if(_id) {\r
98                 if(fixup_get_svalue(_m, (gparam_p)_id, &did) != 0) {\r
99                         LM_ERR("cannot get device id\n");\r
100                         goto err;\r
101                 }\r
102                 if(!did.s) {\r
103                         LM_ERR("device id not found\n");\r
104                         goto err;\r
105                 }\r
106         } else {\r
107                 /* id from P-A-I header */\r
108                 idhdr.s = lost_get_pai_header(_m, &idhdr.len);\r
109                 if(idhdr.len == 0) {\r
110                         LM_WARN("P-A-I header not found, trying From header ...\n");\r
111                         /* id from From header */\r
112                         idhdr.s = lost_get_from_header(_m, &idhdr.len);\r
113                         if(idhdr.len == 0) {\r
114                                 LM_ERR("device id not found\n");\r
115                                 goto err;\r
116                         }\r
117                 }\r
118                 did.s = idhdr.s;\r
119                 did.len = idhdr.len;\r
120         }\r
121         LM_INFO("id - [%.*s]\n", did.len, did.s);\r
122 \r
123         /* check if connection exists */\r
124         if(httpapi.http_connection_exists(&con) == 0) {\r
125                 LM_ERR("connection: [%s] does not exist\n", con.s);\r
126                 goto err;\r
127         }\r
128 \r
129         /* assemble locationRequest */\r
130         que.s = lost_held_location_request(did.s, &que.len);\r
131         /* free memory */\r
132         if(idhdr.s) {\r
133                 pkg_free(idhdr.s);\r
134                 idhdr.len = 0;\r
135                 did.s = NULL;\r
136                 did.len = 0;\r
137         }\r
138         if(!que.s) {\r
139                 LM_ERR("held request document error\n");\r
140                 goto err;\r
141         }\r
142 \r
143         LM_DBG("held location request: \n[%s]\n", que.s);\r
144 \r
145         /* send locationRequest to location server - HTTP POST */\r
146         httpapi.http_connect(_m, &con, NULL, &res, mtheld, &que);\r
147         /* free memory */\r
148         pkg_free(que.s);\r
149         que.len = 0;\r
150         /* read and parse the returned xml */\r
151         doc = xmlReadMemory(res.s, res.len, 0, NULL,\r
152                         XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_NONET\r
153                                         | XML_PARSE_NOCDATA);\r
154         if(!doc) {\r
155                 LM_WARN("invalid xml document: \n[%.*s]\n", res.len, res.s);\r
156                 doc = xmlRecoverMemory(res.s, res.len);\r
157                 if(!doc) {\r
158                         LM_ERR("xml document recovery failed on: \n[%.*s]\n", res.len,\r
159                                         res.s);\r
160                         goto err;\r
161                 }\r
162                 LM_DBG("xml document recovered\n");\r
163         }\r
164         root = xmlDocGetRootElement(doc);\r
165         if(!root) {\r
166                 LM_ERR("empty xml document\n");\r
167                 goto err;\r
168         }\r
169         /* check the root element, shall be locationResponse, or errors */\r
170         if(!xmlStrcmp(root->name, (const xmlChar *)"locationResponse")) {\r
171 \r
172                 LM_DBG("HELD location response: \n[%.*s]\n", res.len, res.s);\r
173 \r
174                 /* get the locationUri element */\r
175                 geo.s = lost_get_content(root, (char *)"locationURI", &geo.len);\r
176                 if(!geo.s) {\r
177                         LM_ERR("no locationURI element found\n");\r
178                         goto err;\r
179                 }\r
180         } else if(!xmlStrcmp(root->name, (const xmlChar *)"error")) {\r
181 \r
182                 LM_DBG("HELD error response: \n[%.*s]\n", res.len, res.s);\r
183 \r
184                 /* get the error patterm */\r
185                 err.s = lost_get_property(root, (char *)"code", &err.len);\r
186                 if(!err.s) {\r
187                         LM_ERR("error - code property not found: \n[%.*s]\n", res.len,\r
188                                         res.s);\r
189                         goto err;\r
190                 }\r
191                 LM_WARN("locationRequest error response: [%.*s]\n", err.len, err.s);\r
192         } else {\r
193                 LM_ERR("root element is not valid: \n[%.*s]\n", res.len, res.s);\r
194                 goto err;\r
195         }\r
196         xmlFreeDoc(doc);\r
197 \r
198         /* set writeable pvars */\r
199         pvpidf.rs = res;\r
200         pvpidf.rs.s = res.s;\r
201         pvpidf.rs.len = res.len;\r
202 \r
203         pvpidf.flags = PV_VAL_STR;\r
204         pspidf = (pv_spec_t *)_pidf;\r
205         pspidf->setf(_m, &pspidf->pvp, (int)EQ_T, &pvpidf);\r
206 \r
207         pvurl.rs = geo;\r
208         pvurl.rs.s = geo.s;\r
209         pvurl.rs.len = geo.len;\r
210 \r
211         pvurl.flags = PV_VAL_STR;\r
212         psurl = (pv_spec_t *)_url;\r
213         psurl->setf(_m, &psurl->pvp, (int)EQ_T, &pvurl);\r
214 \r
215         pverr.rs = err;\r
216         pverr.rs.s = err.s;\r
217         pverr.rs.len = err.len;\r
218 \r
219         pverr.flags = PV_VAL_STR;\r
220         pserr = (pv_spec_t *)_err;\r
221         pserr->setf(_m, &pserr->pvp, (int)EQ_T, &pverr);\r
222 \r
223         return (err.len > 0) ? LOST_SERVER_ERROR : LOST_SUCCESS;\r
224 \r
225 err:\r
226         if(doc)\r
227                 xmlFreeDoc(doc);\r
228         return LOST_CLIENT_ERROR;\r
229 }\r
230 \r
231 /*\r
232  * lost_function(msg, con, pidf, uri, name, err, pidf, urn)\r
233  * assembles and runs LOST findService request, parses results\r
234  */\r
235 int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,\r
236                 char *_err, char *_pidf, char *_urn)\r
237 {\r
238         pv_spec_t *psname;\r
239         pv_spec_t *psuri;\r
240         pv_spec_t *pserr;\r
241 \r
242         pv_value_t pvname;\r
243         pv_value_t pvuri;\r
244         pv_value_t pverr;\r
245 \r
246         p_loc_t loc = NULL;\r
247 \r
248         xmlDocPtr doc = NULL;\r
249         xmlNodePtr root = NULL;\r
250 \r
251         str uri = {NULL, 0};\r
252         str urn = {NULL, 0};\r
253         str err = {NULL, 0};\r
254         str res = {NULL, 0};\r
255         str con = {NULL, 0};\r
256         str ret = {NULL, 0};\r
257         str geo = {NULL, 0};\r
258         str name = {NULL, 0};\r
259         str pidf = {NULL, 0};\r
260         str pidf_h = {NULL, 0};\r
261 \r
262         char *search = NULL;\r
263         struct msg_start *fl;\r
264 \r
265         if(_con == NULL || _uri == NULL || _name == NULL || _err == NULL) {\r
266                 LM_ERR("invalid parameter\n");\r
267                 goto err;\r
268         }\r
269         if(fixup_get_svalue(_m, (gparam_p)_con, &con) != 0) {\r
270                 LM_ERR("cannot get connection string\n");\r
271                 goto err;\r
272         }\r
273         /* urn from parameter */\r
274         if(_urn) {\r
275                 if(fixup_get_svalue(_m, (gparam_p)_urn, &urn) != 0) {\r
276                         LM_ERR("cannot get service urn\n");\r
277                         goto err;\r
278                 }\r
279         }\r
280         /* urn from request line */\r
281         if(urn.len == 0) {\r
282                 LM_WARN("no sevice urn parameter, trying request line ...\n");\r
283                 fl = &(_m->first_line);\r
284                 urn.len = fl->u.request.uri.len;\r
285                 urn.s = fl->u.request.uri.s;\r
286         }\r
287         /* check urn scheme */\r
288         if(urn.len > 3) {\r
289                 search = urn.s;\r
290                 if(((*(search + 0) == 'u') || (*(search + 0) == 'U'))\r
291                                 && ((*(search + 1) == 'r') || (*(search + 1) == 'R'))\r
292                                 && ((*(search + 2) == 'n') || (*(search + 2) == 'N'))\r
293                                 && (*(search + 3) == ':')) {\r
294                         LM_INFO("urn - [%.*s]\n", urn.len, urn.s);\r
295                 } else {\r
296                         LM_ERR("service urn not found\n");\r
297                         goto err;\r
298                 }\r
299         } else {\r
300                 LM_ERR("service urn not found\n");\r
301                 goto err;\r
302         }\r
303         /* pidf from parameter */\r
304         if(_pidf) {\r
305                 if(fixup_get_svalue(_m, (gparam_p)_pidf, &pidf) != 0) {\r
306                         LM_ERR("cannot get pidf-lo\n");\r
307                         goto err;\r
308                 }\r
309         }\r
310         /* pidf from geolocation header */\r
311         if(pidf.len == 0) {\r
312                 LM_WARN("no pidf parameter, trying Geolocation header ...\n");\r
313                 geo.s = lost_get_geolocation_header(_m, &geo.len);\r
314                 if(!geo.s) {\r
315                         LM_ERR("geolocation header not found\n");\r
316                         goto err;\r
317                 } else {\r
318                         /* pidf from multipart body, check cid scheme */\r
319                         search = geo.s;\r
320                         if((*(search + 0) == '<')\r
321                                         && ((*(search + 1) == 'c') || (*(search + 1) == 'C'))\r
322                                         && ((*(search + 2) == 'i') || (*(search + 2) == 'I'))\r
323                                         && ((*(search + 3) == 'd') || (*(search + 3) == 'D'))\r
324                                         && (*(search + 4) == ':')) {\r
325                                 search += 4;\r
326                                 *search = '<';\r
327                                 geo.s = search;\r
328                                 geo.len = geo.len - 4;\r
329 \r
330                                 LM_DBG("cid: \n[%.*s]\n", geo.len, geo.s);\r
331 \r
332                                 /* get body part - filter=>content id */\r
333                                 pidf.s = get_body_part_by_filter(\r
334                                                 _m, 0, 0, geo.s, NULL, &pidf.len);\r
335                                 if(!pidf.s) {\r
336                                         LM_ERR("no multipart body found\n");\r
337                                         goto err;\r
338                                 }\r
339                         }\r
340                         /* no pidf-lo so far ... check http(s) scheme */\r
341                         if(((*(search + 0) == 'h') || (*(search + 0) == 'H'))\r
342                                         && ((*(search + 1) == 't') || (*(search + 1) == 'T'))\r
343                                         && ((*(search + 2) == 't') || (*(search + 2) == 'T'))\r
344                                         && ((*(search + 3) == 'p') || (*(search + 3) == 'P'))\r
345                                         && (*(search + 4) == ':')) {\r
346 \r
347                                 LM_DBG("url: \n[%.*s]\n", geo.len, geo.s);\r
348 \r
349                                 /* ! dereference pidf.lo at location server - HTTP GET */\r
350                                 /* ! requires hack in http_client module */\r
351                                 /* ! functions.c => http_client_query => query_params.oneline = 0; */\r
352                                 httpapi.http_client_query(_m, geo.s, &pidf_h, NULL, NULL);\r
353 \r
354                                 /* free memory */\r
355                                 pkg_free(geo.s);\r
356                                 geo.len = 0;\r
357 \r
358                                 if(!pidf_h.s) {\r
359                                         LM_ERR("dereferencing location failed\n");\r
360                                         goto err;\r
361                                 }\r
362                                 pidf.s = pidf_h.s;\r
363                                 pidf.len = pidf_h.len;\r
364                         }\r
365                 }\r
366         }\r
367 \r
368         /* no pidf-lo return error */\r
369         if(!pidf.s) {\r
370                 LM_ERR("pidf-lo not found\n");\r
371                 goto err;\r
372         }\r
373 \r
374         LM_DBG("pidf-lo: [%.*s]\n", pidf.len, pidf.s);\r
375 \r
376         /* read and parse pidf-lo */\r
377         doc = xmlReadMemory(pidf.s, pidf.len, 0, NULL,\r
378                         XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_NONET\r
379                                         | XML_PARSE_NOCDATA);\r
380 \r
381         if(!doc) {\r
382                 LM_WARN("invalid xml (pidf-lo): \n[%.*s]\n", pidf.len, pidf.s);\r
383                 doc = xmlRecoverMemory(pidf.s, pidf.len);\r
384                 if(!doc) {\r
385                         LM_ERR("xml (pidf-lo) recovery failed on: \n[%.*s]\n", pidf.len,\r
386                                         pidf.s);\r
387                         goto err;\r
388                 }\r
389 \r
390                 LM_DBG("xml (pidf-lo) recovered\n");\r
391         }\r
392 \r
393         root = xmlDocGetRootElement(doc);\r
394         if(!root) {\r
395                 LM_ERR("empty pidf-lo document\n");\r
396                 goto err;\r
397         }\r
398         if((!xmlStrcmp(root->name, (const xmlChar *)"presence"))\r
399                         || (!xmlStrcmp(root->name, (const xmlChar *)"locationResponse"))) {\r
400                 /* get the geolocation: point or circle, urn, ... */\r
401                 loc = lost_new_loc(urn);\r
402                 if(lost_parse_location_info(root, loc) < 0) {\r
403                         LM_ERR("location element not found\n");\r
404                         goto err;\r
405                 }\r
406         } else {\r
407                 LM_ERR("findServiceResponse or presence element not found in "\r
408                            "\n[%.*s]\n",\r
409                                 pidf.len, pidf.s);\r
410                 goto err;\r
411         }\r
412 \r
413         /* free memory */\r
414         if(pidf_h.s) {\r
415                 pkg_free(pidf_h.s);\r
416                 pidf_h.len = 0;\r
417                 pidf.s = NULL;\r
418                 pidf.len = 0;\r
419         }\r
420 \r
421         /* check if connection exits */\r
422         if(httpapi.http_connection_exists(&con) == 0) {\r
423                 LM_ERR("connection: [%.*s] does not exist\n", con.len, con.s);\r
424                 goto err;\r
425         }\r
426         /* assemble findService request */\r
427         res.s = lost_find_service_request(loc, &res.len);\r
428         /* free memory */\r
429         lost_free_loc(loc);\r
430         xmlFreeDoc(doc);\r
431 \r
432         if(!res.s) {\r
433                 LM_ERR("lost request failed\n");\r
434                 goto err;\r
435         }\r
436 \r
437         LM_DBG("findService request: \n[%.*s]\n", res.len, res.s);\r
438 \r
439         /* send findService request to mapping server - HTTP POST */\r
440         httpapi.http_connect(_m, &con, NULL, &ret, mtlost, &res);\r
441         pkg_free(res.s);\r
442         res.len = 0;\r
443         if(!ret.s) {\r
444                 LM_ERR("findService request failed\n");\r
445                 goto err;\r
446         }\r
447 \r
448         LM_DBG("findService response: \n[%.*s]\n", ret.len, ret.s);\r
449 \r
450         /* read and parse the returned xml */\r
451         doc = xmlReadMemory(ret.s, ret.len, 0, 0,\r
452                         XML_PARSE_NOBLANKS | XML_PARSE_NONET | XML_PARSE_NOCDATA);\r
453 \r
454         if(!doc) {\r
455                 LM_ERR("invalid xml document: \n[%.*s]\n", ret.len, ret.s);\r
456                 doc = xmlRecoverMemory(ret.s, ret.len);\r
457                 if(!doc) {\r
458                         LM_ERR("xml document recovery failed on: \n[%.*s]\n", ret.len,\r
459                                         ret.s);\r
460                         goto err;\r
461                 }\r
462 \r
463                 LM_DBG("xml document recovered\n");\r
464         }\r
465         root = xmlDocGetRootElement(doc);\r
466         if(!root) {\r
467                 LM_ERR("empty xml document: \n[%.*s]\n", ret.len, ret.s);\r
468                 goto err;\r
469         }\r
470         /* check the root element, shall be findServiceResponse, or errors */\r
471         if((!xmlStrcmp(root->name, (const xmlChar *)"findServiceResponse"))) {\r
472                 /* get the uri element */\r
473                 uri.s = lost_get_content(root, uri_element, &uri.len);\r
474                 if(!uri.s) {\r
475                         LM_ERR("uri element not found: \n[%.*s]\n", ret.len, ret.s);\r
476                         goto err;\r
477                 }\r
478                 LM_INFO("uri - [%.*s]\n", uri.len, uri.s);\r
479                 /* get the displayName element */\r
480                 name.s = lost_get_content(root, name_element, &name.len);\r
481                 if(!name.s) {\r
482                         LM_ERR("displayName element not found: \n[%.*s]\n", ret.len, ret.s);\r
483                         goto err;\r
484                 }\r
485                 LM_INFO("name - [%.*s]\n", name.len, name.s);\r
486         } else if((!xmlStrcmp(root->name, (const xmlChar *)"errors"))) {\r
487 \r
488                 LM_DBG("findService error response received\n");\r
489 \r
490                 /* get the error patterm */\r
491                 err.s = lost_get_childname(root, errors_element, &err.len);\r
492                 if(!err.s) {\r
493                         LM_ERR("error pattern element not found: \n[%.*s]\n", ret.len,\r
494                                         ret.s);\r
495                         goto err;\r
496                 }\r
497                 LM_WARN("findService error response: [%.*s]\n", err.len, err.s);\r
498         } else {\r
499                 LM_ERR("root element is not valid: \n[%.*s]\n", ret.len, ret.s);\r
500                 goto err;\r
501         }\r
502 \r
503         /* free memory */\r
504         xmlFreeDoc(doc);\r
505         if(ret.s) {\r
506                 pkg_free(ret.s);\r
507                 ret.len = 0;\r
508         }\r
509 \r
510         /* set writeable pvars */\r
511         pvname.rs = name;\r
512         pvname.rs.s = name.s;\r
513         pvname.rs.len = name.len;\r
514 \r
515         pvname.flags = PV_VAL_STR;\r
516         psname = (pv_spec_t *)_name;\r
517         psname->setf(_m, &psname->pvp, (int)EQ_T, &pvname);\r
518 \r
519         pvuri.rs = uri;\r
520         pvuri.rs.s = uri.s;\r
521         pvuri.rs.len = uri.len;\r
522 \r
523         pvuri.flags = PV_VAL_STR;\r
524         psuri = (pv_spec_t *)_uri;\r
525         psuri->setf(_m, &psuri->pvp, (int)EQ_T, &pvuri);\r
526 \r
527         pverr.rs = err;\r
528         pverr.rs.s = err.s;\r
529         pverr.rs.len = err.len;\r
530 \r
531         pverr.flags = PV_VAL_STR;\r
532         pserr = (pv_spec_t *)_err;\r
533         pserr->setf(_m, &pserr->pvp, (int)EQ_T, &pverr);\r
534 \r
535         return (err.len > 0) ? LOST_SERVER_ERROR : LOST_SUCCESS;\r
536 \r
537 err:\r
538         if(loc)\r
539                 lost_free_loc(loc);\r
540         if(doc)\r
541                 xmlFreeDoc(doc);\r
542         if(pidf_h.s) {\r
543                 pkg_free(pidf_h.s);\r
544                 pidf_h.len = 0;\r
545         }\r
546         if(ret.s) {\r
547                 pkg_free(ret.s);\r
548                 ret.len = 0;\r
549         }\r
550         return LOST_CLIENT_ERROR;\r
551 }\r