lost: adds HELD (RFC6155) and LOST (RFC5222) queries for location-based routing
[sip-router] / 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         int curlres = 0;\r
88 \r
89         if(_con == NULL || _pidf == NULL || _url == NULL || _err == NULL) {\r
90                 LM_ERR("invalid parameter\n");\r
91                 goto err;\r
92         }\r
93         /* connection from parameter */\r
94         if(fixup_get_svalue(_m, (gparam_p)_con, &con) != 0) {\r
95                 LM_ERR("cannot get connection string\n");\r
96                 goto err;\r
97         }\r
98         /* id from parameter */\r
99         if(_id) {\r
100                 if(fixup_get_svalue(_m, (gparam_p)_id, &did) != 0) {\r
101                         LM_ERR("cannot get device id\n");\r
102                         goto err;\r
103                 }\r
104                 if(!did.s) {\r
105                         LM_ERR("device id not found\n");\r
106                         goto err;\r
107                 }\r
108         } else {\r
109                 /* id from P-A-I header */\r
110                 idhdr.s = lost_get_pai_header(_m, &idhdr.len);\r
111                 if(idhdr.len == 0) {\r
112                         LM_WARN("P-A-I header not found, trying From header ...\n");\r
113                         /* id from From header */\r
114                         idhdr.s = lost_get_from_header(_m, &idhdr.len);\r
115                         if(idhdr.len == 0) {\r
116                                 LM_ERR("device id not found\n");\r
117                                 goto err;\r
118                         }\r
119                 }\r
120                 did.s = idhdr.s;\r
121                 did.len = idhdr.len;\r
122         }\r
123         LM_INFO("id - [%.*s]\n", did.len, did.s);\r
124 \r
125         /* check if connection exists */\r
126         if(httpapi.http_connection_exists(&con) == 0) {\r
127                 LM_ERR("connection: [%s] does not exist\n", con.s);\r
128                 goto err;\r
129         }\r
130 \r
131         /* assemble locationRequest */\r
132         que.s = lost_held_location_request(did.s, &que.len);\r
133         /* free memory */\r
134         lost_free_string(&idhdr);\r
135         did.s = NULL;\r
136         did.len = 0;\r
137         if(!que.s) {\r
138                 LM_ERR("held request document error\n");\r
139                 goto err;\r
140         }\r
141 \r
142         LM_DBG("held location request: \n[%s]\n", que.s);\r
143 \r
144         /* send locationRequest to location server - HTTP POST */\r
145         curlres = httpapi.http_connect(_m, &con, NULL, &res, mtheld, &que);\r
146         /* only HTTP 2xx responses are accepted */ \r
147         if(curlres >= 300 || curlres < 100) {\r
148                 LM_ERR("[%.*s] failed with error: %d\n", con.len, con.s, curlres);\r
149                 res.s = NULL;\r
150                 res.len = 0;\r
151                 goto err;\r
152         }\r
153 \r
154         LM_DBG("[%.*s] returned: %d\n", con.len, con.s, curlres);\r
155 \r
156         /* free memory */\r
157         lost_free_string(&que);\r
158         /* read and parse the returned xml */\r
159         doc = xmlReadMemory(res.s, res.len, 0, NULL,\r
160                         XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_NONET\r
161                                         | XML_PARSE_NOCDATA);\r
162         if(!doc) {\r
163                 LM_WARN("invalid xml document: \n[%.*s]\n", res.len, res.s);\r
164                 doc = xmlRecoverMemory(res.s, res.len);\r
165                 if(!doc) {\r
166                         LM_ERR("xml document recovery failed on: \n[%.*s]\n", res.len,\r
167                                         res.s);\r
168                         goto err;\r
169                 }\r
170 \r
171                 LM_DBG("xml document recovered\n");\r
172         }\r
173         root = xmlDocGetRootElement(doc);\r
174         if(!root) {\r
175                 LM_ERR("empty xml document\n");\r
176                 goto err;\r
177         }\r
178         /* check the root element, shall be locationResponse, or errors */\r
179         if(!xmlStrcmp(root->name, (const xmlChar *)"locationResponse")) {\r
180 \r
181                 LM_DBG("HELD location response: \n[%.*s]\n", res.len, res.s);\r
182 \r
183                 /* get the locationUri element */\r
184                 geo.s = lost_get_content(root, (char *)"locationURI", &geo.len);\r
185                 if(!geo.s) {\r
186                         LM_ERR("no locationURI element found\n");\r
187                         goto err;\r
188                 }\r
189         } else if(!xmlStrcmp(root->name, (const xmlChar *)"error")) {\r
190 \r
191                 LM_DBG("HELD error response: \n[%.*s]\n", res.len, res.s);\r
192 \r
193                 /* get the error patterm */\r
194                 err.s = lost_get_property(root, (char *)"code", &err.len);\r
195                 if(!err.s) {\r
196                         LM_ERR("error - code property not found: \n[%.*s]\n", res.len,\r
197                                         res.s);\r
198                         goto err;\r
199                 }\r
200                 LM_WARN("locationRequest error response: [%.*s]\n", err.len, err.s);\r
201         } else {\r
202                 LM_ERR("root element is not valid: \n[%.*s]\n", res.len, res.s);\r
203                 goto err;\r
204         }\r
205         xmlFreeDoc(doc);\r
206 \r
207         /* set writeable pvars */\r
208         pvpidf.rs = res;\r
209         pvpidf.rs.s = res.s;\r
210         pvpidf.rs.len = res.len;\r
211 \r
212         pvpidf.flags = PV_VAL_STR;\r
213         pspidf = (pv_spec_t *)_pidf;\r
214         pspidf->setf(_m, &pspidf->pvp, (int)EQ_T, &pvpidf);\r
215 \r
216         pvurl.rs = geo;\r
217         pvurl.rs.s = geo.s;\r
218         pvurl.rs.len = geo.len;\r
219 \r
220         pvurl.flags = PV_VAL_STR;\r
221         psurl = (pv_spec_t *)_url;\r
222         psurl->setf(_m, &psurl->pvp, (int)EQ_T, &pvurl);\r
223 \r
224         pverr.rs = err;\r
225         pverr.rs.s = err.s;\r
226         pverr.rs.len = err.len;\r
227 \r
228         pverr.flags = PV_VAL_STR;\r
229         pserr = (pv_spec_t *)_err;\r
230         pserr->setf(_m, &pserr->pvp, (int)EQ_T, &pverr);\r
231 \r
232         return (err.len > 0) ? LOST_SERVER_ERROR : LOST_SUCCESS;\r
233 \r
234 err:\r
235         if(doc)\r
236                 xmlFreeDoc(doc);\r
237         \r
238         lost_free_string(&idhdr);\r
239         lost_free_string(&que);\r
240 \r
241         return LOST_CLIENT_ERROR;\r
242 }\r
243 \r
244 /*\r
245  * lost_function(msg, con, pidf, uri, name, err, pidf, urn)\r
246  * assembles and runs LOST findService request, parses results\r
247  */\r
248 int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,\r
249                 char *_err, char *_pidf, char *_urn)\r
250 {\r
251         pv_spec_t *psname;\r
252         pv_spec_t *psuri;\r
253         pv_spec_t *pserr;\r
254 \r
255         pv_value_t pvname;\r
256         pv_value_t pvuri;\r
257         pv_value_t pverr;\r
258 \r
259         p_loc_t loc = NULL;\r
260 \r
261         xmlDocPtr doc = NULL;\r
262         xmlNodePtr root = NULL;\r
263 \r
264         str uri = {NULL, 0};\r
265         str urn = {NULL, 0};\r
266         str err = {NULL, 0};\r
267         str res = {NULL, 0};\r
268         str con = {NULL, 0};\r
269         str ret = {NULL, 0};\r
270         str geo = {NULL, 0};\r
271         str geohdr = {NULL, 0};\r
272         str name = {NULL, 0};\r
273         str pidf = {NULL, 0};\r
274         str pidfhdr = {NULL, 0};\r
275 \r
276         struct msg_start *fl;\r
277         char *search = NULL;\r
278         int curlres = 0;\r
279         \r
280         if(_con == NULL || _uri == NULL || _name == NULL || _err == NULL) {\r
281                 LM_ERR("invalid parameter\n");\r
282                 goto err;\r
283         }\r
284         if(fixup_get_svalue(_m, (gparam_p)_con, &con) != 0) {\r
285                 LM_ERR("cannot get connection string\n");\r
286                 goto err;\r
287         }\r
288         /* urn from parameter */\r
289         if(_urn) {\r
290                 if(fixup_get_svalue(_m, (gparam_p)_urn, &urn) != 0) {\r
291                         LM_ERR("cannot get service urn\n");\r
292                         goto err;\r
293                 }\r
294         }\r
295         /* urn from request line */\r
296         if(urn.len == 0) {\r
297                 LM_WARN("no sevice urn parameter, trying request line ...\n");\r
298                 fl = &(_m->first_line);\r
299                 urn.len = fl->u.request.uri.len;\r
300                 urn.s = fl->u.request.uri.s;\r
301         }\r
302         /* check urn scheme */\r
303         if(urn.len > 3) {\r
304                 search = urn.s;\r
305                 if(((*(search + 0) == 'u') || (*(search + 0) == 'U'))\r
306                                 && ((*(search + 1) == 'r') || (*(search + 1) == 'R'))\r
307                                 && ((*(search + 2) == 'n') || (*(search + 2) == 'N'))\r
308                                 && (*(search + 3) == ':')) {\r
309                         LM_INFO("urn - [%.*s]\n", urn.len, urn.s);\r
310                 } else {\r
311                         LM_ERR("service urn not found\n");\r
312                         goto err;\r
313                 }\r
314         } else {\r
315                 LM_ERR("service urn not found\n");\r
316                 goto err;\r
317         }\r
318         /* pidf from parameter */\r
319         if(_pidf) {\r
320                 if(fixup_get_svalue(_m, (gparam_p)_pidf, &pidf) != 0) {\r
321                         LM_ERR("cannot get pidf-lo\n");\r
322                         goto err;\r
323                 }\r
324         }\r
325         /* pidf from geolocation header */\r
326         if(pidf.len == 0) {\r
327                 LM_WARN("no pidf parameter, trying Geolocation header ...\n");\r
328                 geohdr.s = lost_get_geolocation_header(_m, &geohdr.len);\r
329                 if(!geohdr.s) {\r
330                         LM_ERR("geolocation header not found\n");\r
331                         goto err;\r
332                 } else {\r
333                         /* pidf from multipart body, check cid scheme */\r
334                         search = geohdr.s;\r
335                         if((*(search + 0) == '<')\r
336                                         && ((*(search + 1) == 'c') || (*(search + 1) == 'C'))\r
337                                         && ((*(search + 2) == 'i') || (*(search + 2) == 'I'))\r
338                                         && ((*(search + 3) == 'd') || (*(search + 3) == 'D'))\r
339                                         && (*(search + 4) == ':')) {\r
340                                 search += 4;\r
341                                 *search = '<';\r
342                                 geo.s = search;\r
343                                 geo.len = geo.len - 4;\r
344 \r
345                                 LM_DBG("cid: \n[%.*s]\n", geo.len, geo.s);\r
346 \r
347                                 /* get body part - filter=>content id */\r
348                                 pidf.s = get_body_part_by_filter(\r
349                                                 _m, 0, 0, geo.s, NULL, &pidf.len);\r
350                                 if(!pidf.s) {\r
351                                         LM_ERR("no multipart body found\n");\r
352                                         goto err;\r
353                                 }\r
354                         }\r
355                         /* no pidf-lo so far ... check http(s) scheme */\r
356                         if(((*(search + 0) == 'h') || (*(search + 0) == 'H'))\r
357                                         && ((*(search + 1) == 't') || (*(search + 1) == 'T'))\r
358                                         && ((*(search + 2) == 't') || (*(search + 2) == 'T'))\r
359                                         && ((*(search + 3) == 'p') || (*(search + 3) == 'P'))\r
360                                         && (*(search + 4) == ':')) {\r
361                                 geo.s = geohdr.s;\r
362                                 geo.len = geohdr.len;\r
363 \r
364                                 LM_DBG("url: \n[%.*s]\n", geo.len, geo.s);\r
365 \r
366                                 /* ! dereference pidf.lo at location server - HTTP GET */\r
367                                 /* ! requires hack in http_client module */\r
368                                 /* ! functions.c => http_client_query => query_params.oneline = 0; */\r
369                                 curlres = httpapi.http_client_query(_m, geo.s, &pidfhdr, NULL, NULL);\r
370                                 /* free memory */\r
371                                 lost_free_string(&geohdr);\r
372                                 geo.s = NULL;\r
373                                 geo.len = 0;\r
374                                 /* only HTTP 2xx responses are accepted */ \r
375                                 if(curlres >= 300 || curlres < 100) {\r
376                                         LM_ERR("http GET failed with error: %d\n", curlres);\r
377                                         pidfhdr.s = NULL;\r
378                                         pidfhdr.len = 0;\r
379                                         goto err;\r
380                                 }\r
381 \r
382                                 LM_DBG("http GET returned: %d\n", curlres);\r
383 \r
384                                 if(!pidfhdr.s) {\r
385                                         LM_ERR("dereferencing location failed\n");\r
386                                         goto err;\r
387                                 }\r
388                                 pidf.s = pidfhdr.s;\r
389                                 pidf.len = pidfhdr.len;\r
390                         }\r
391                         /* free memory */\r
392                         lost_free_string(&geohdr);\r
393                 }\r
394         }\r
395 \r
396         /* no pidf-lo return error */\r
397         if(!pidf.s) {\r
398                 LM_ERR("pidf-lo not found\n");\r
399                 goto err;\r
400         }\r
401 \r
402         LM_DBG("pidf-lo: [%.*s]\n", pidf.len, pidf.s);\r
403 \r
404         /* read and parse pidf-lo */\r
405         doc = xmlReadMemory(pidf.s, pidf.len, 0, NULL,\r
406                         XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_NONET\r
407                                         | XML_PARSE_NOCDATA);\r
408 \r
409         if(!doc) {\r
410                 LM_WARN("invalid xml (pidf-lo): \n[%.*s]\n", pidf.len, pidf.s);\r
411                 doc = xmlRecoverMemory(pidf.s, pidf.len);\r
412                 if(!doc) {\r
413                         LM_ERR("xml (pidf-lo) recovery failed on: \n[%.*s]\n", pidf.len,\r
414                                         pidf.s);\r
415                         goto err;\r
416                 }\r
417 \r
418                 LM_DBG("xml (pidf-lo) recovered\n");\r
419         }\r
420 \r
421         root = xmlDocGetRootElement(doc);\r
422         if(!root) {\r
423                 LM_ERR("empty pidf-lo document\n");\r
424                 goto err;\r
425         }\r
426         if((!xmlStrcmp(root->name, (const xmlChar *)"presence"))\r
427                         || (!xmlStrcmp(root->name, (const xmlChar *)"locationResponse"))) {\r
428                 /* get the geolocation: point or circle, urn, ... */\r
429                 loc = lost_new_loc(urn);\r
430                 if(!loc) {\r
431                         LM_ERR("location object allocation failed\n");\r
432                         goto err;                       \r
433                 }\r
434                 if(lost_parse_location_info(root, loc) < 0) {\r
435                         LM_ERR("location element not found\n");\r
436                         goto err;\r
437                 }\r
438         } else {\r
439                 LM_ERR("findServiceResponse or presence element not found in "\r
440                            "\n[%.*s]\n",\r
441                                 pidf.len, pidf.s);\r
442                 goto err;\r
443         }\r
444 \r
445         /* free memory */\r
446         lost_free_string(&pidfhdr);\r
447         pidf.s = NULL;\r
448         pidf.len = 0;\r
449 \r
450         /* check if connection exits */\r
451         if(httpapi.http_connection_exists(&con) == 0) {\r
452                 LM_ERR("connection: [%.*s] does not exist\n", con.len, con.s);\r
453                 goto err;\r
454         }\r
455         /* assemble findService request */\r
456         res.s = lost_find_service_request(loc, &res.len);\r
457         /* free memory */\r
458         if(loc) {\r
459                 lost_free_loc(loc);\r
460                 loc = NULL;\r
461         }\r
462         xmlFreeDoc(doc);\r
463         doc = NULL;\r
464 \r
465         if(!res.s) {\r
466                 LM_ERR("lost request failed\n");\r
467                 goto err;\r
468         }\r
469 \r
470         LM_DBG("findService request: \n[%.*s]\n", res.len, res.s);\r
471 \r
472         /* send findService request to mapping server - HTTP POST */\r
473         curlres = httpapi.http_connect(_m, &con, NULL, &ret, mtlost, &res);\r
474         /* only HTTP 2xx responses are accepted */ \r
475         if(curlres >= 300 || curlres < 100) {\r
476                 LM_ERR("[%.*s] failed with error: %d\n", con.len, con.s, curlres);\r
477                 ret.s = NULL;\r
478                 ret.len = 0;\r
479                 goto err;\r
480         }\r
481 \r
482         LM_DBG("[%.*s] returned: %d\n", con.len, con.s, curlres);\r
483 \r
484         /* free memory */\r
485         lost_free_string(&res);\r
486 \r
487         if(!ret.s) {\r
488                 LM_ERR("findService request failed\n");\r
489                 goto err;\r
490         }\r
491 \r
492         LM_DBG("findService response: \n[%.*s]\n", ret.len, ret.s);\r
493 \r
494         /* read and parse the returned xml */\r
495         doc = xmlReadMemory(ret.s, ret.len, 0, 0,\r
496                         XML_PARSE_NOBLANKS | XML_PARSE_NONET | XML_PARSE_NOCDATA);\r
497 \r
498         if(!doc) {\r
499                 LM_ERR("invalid xml document: \n[%.*s]\n", ret.len, ret.s);\r
500                 doc = xmlRecoverMemory(ret.s, ret.len);\r
501                 if(!doc) {\r
502                         LM_ERR("xml document recovery failed on: \n[%.*s]\n", ret.len,\r
503                                         ret.s);\r
504                         goto err;\r
505                 }\r
506 \r
507                 LM_DBG("xml document recovered\n");\r
508         }\r
509         root = xmlDocGetRootElement(doc);\r
510         if(!root) {\r
511                 LM_ERR("empty xml document: \n[%.*s]\n", ret.len, ret.s);\r
512                 goto err;\r
513         }\r
514         /* check the root element, shall be findServiceResponse, or errors */\r
515         if((!xmlStrcmp(root->name, (const xmlChar *)"findServiceResponse"))) {\r
516                 /* get the uri element */\r
517                 uri.s = lost_get_content(root, uri_element, &uri.len);\r
518                 if(!uri.s) {\r
519                         LM_ERR("uri element not found: \n[%.*s]\n", ret.len, ret.s);\r
520                         goto err;\r
521                 }\r
522                 LM_INFO("uri - [%.*s]\n", uri.len, uri.s);\r
523                 /* get the displayName element */\r
524                 name.s = lost_get_content(root, name_element, &name.len);\r
525                 if(!name.s) {\r
526                         LM_ERR("displayName element not found: \n[%.*s]\n", ret.len, ret.s);\r
527                         goto err;\r
528                 }\r
529                 LM_INFO("name - [%.*s]\n", name.len, name.s);\r
530         } else if((!xmlStrcmp(root->name, (const xmlChar *)"errors"))) {\r
531 \r
532                 LM_DBG("findService error response received\n");\r
533 \r
534                 /* get the error patterm */\r
535                 err.s = lost_get_childname(root, errors_element, &err.len);\r
536                 if(!err.s) {\r
537                         LM_ERR("error pattern element not found: \n[%.*s]\n", ret.len,\r
538                                         ret.s);\r
539                         goto err;\r
540                 }\r
541                 LM_WARN("findService error response: [%.*s]\n", err.len, err.s);\r
542         } else {\r
543                 LM_ERR("root element is not valid: \n[%.*s]\n", ret.len, ret.s);\r
544                 goto err;\r
545         }\r
546 \r
547         /* free memory */\r
548         xmlFreeDoc(doc);\r
549         doc = NULL;\r
550         lost_free_string(&ret);\r
551 \r
552         /* set writable pvars */\r
553         pvname.rs = name;\r
554         pvname.rs.s = name.s;\r
555         pvname.rs.len = name.len;\r
556 \r
557         pvname.flags = PV_VAL_STR;\r
558         psname = (pv_spec_t *)_name;\r
559         psname->setf(_m, &psname->pvp, (int)EQ_T, &pvname);\r
560 \r
561         pvuri.rs = uri;\r
562         pvuri.rs.s = uri.s;\r
563         pvuri.rs.len = uri.len;\r
564 \r
565         pvuri.flags = PV_VAL_STR;\r
566         psuri = (pv_spec_t *)_uri;\r
567         psuri->setf(_m, &psuri->pvp, (int)EQ_T, &pvuri);\r
568 \r
569         pverr.rs = err;\r
570         pverr.rs.s = err.s;\r
571         pverr.rs.len = err.len;\r
572 \r
573         pverr.flags = PV_VAL_STR;\r
574         pserr = (pv_spec_t *)_err;\r
575         pserr->setf(_m, &pserr->pvp, (int)EQ_T, &pverr);\r
576 \r
577         return (err.len > 0) ? LOST_SERVER_ERROR : LOST_SUCCESS;\r
578 \r
579 err:\r
580         if(loc)\r
581                 lost_free_loc(loc);\r
582         if(doc)\r
583                 xmlFreeDoc(doc);\r
584 \r
585         lost_free_string(&pidfhdr);\r
586         lost_free_string(&geohdr);\r
587         lost_free_string(&ret);\r
588 \r
589         return LOST_CLIENT_ERROR;\r
590 }\r