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