1 /*
2 * presence_dialoginfo module
3 *
4 * Copyright (C) 2006 Voice Sistem S.R.L.
5 * Copyright (C) 2008 Klaus Darilion, IPCom
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 /*! \file
26 * \brief Kamailio Presence_XML :: Notify BODY handling
27 * \ingroup presence_xml
28 */
29
30 #define MAX_INT_LEN 11 /* 2^32: 10 chars + 1 char sign */
31 #define DEF_TRYING_NODE 1
32 #define DEF_PROCEEDING_NODE 2
33 #define DEF_EARLY_NODE 4
34 #define DEF_CONFIRMED_NODE 8
35 #define DEF_TERMINATED_NODE 16
36
37
38 #include <string.h>
39 #include <stdlib.h>
40 #include <libxml/parser.h>
41
42 #include "../../core/mem/mem.h"
43 #include "../presence/utils_func.h"
44 #include "../presence/hash.h"
45 #include "../presence/event_list.h"
46 #include "../presence/presence.h"
47 #include "../presence/presentity.h"
48 #include "notify_body.h"
49 #include "pidf.h"
50
51 str* agregate_xmls(str* pres_user, str* pres_domain, str** body_array, int n);
52 int check_relevant_state (xmlChar * dialog_id, xmlDocPtr * xml_array,
53 int total_nodes);
54
55 extern int force_single_dialog;
56 extern int force_dummy_dialog;
57
free_xml_body(char * body)58 void free_xml_body(char* body)
59 {
60 if(body== NULL)
61 return;
62
63 xmlFree(body);
64 body= NULL;
65 }
66
67 #define DIALOGINFO_EMPTY_BODY "<dialog-info>\
68 <dialog id=\"615293b33c62dec073e05d9421e9f48b\" direction=\"recipient\">\
69 <state>terminated</state>\
70 </dialog>\
71 </dialog-info>"
72
73 #define DIALOGINFO_EMPTY_BODY_SIZE 512
74
dlginfo_agg_nbody_empty(str * pres_user,str * pres_domain)75 str* dlginfo_agg_nbody_empty(str* pres_user, str* pres_domain)
76 {
77 str* n_body= NULL;
78 str* body_array;
79 char* body;
80
81 LM_DBG("creating empty dialog for [pres_user]=%.*s [pres_domain]= %.*s\n",
82 pres_user->len, pres_user->s, pres_domain->len, pres_domain->s);
83
84 /* dcm: note to double check - pkg allocation might not be needed */
85 body_array = (str*)pkg_malloc(sizeof(str));
86 if(body_array==NULL) {
87 LM_ERR("No more pkg\n");
88 return NULL;
89 }
90 body = (char*)pkg_malloc(DIALOGINFO_EMPTY_BODY_SIZE);
91 if(body==NULL) {
92 LM_ERR("No more pkg\n");
93 pkg_free(body_array);
94 return NULL;
95 }
96
97 sprintf(body, DIALOGINFO_EMPTY_BODY);
98 body_array->s = body;
99 body_array->len = strlen(body);
100
101
102 n_body= agregate_xmls(pres_user, pres_domain, &body_array, 1);
103 LM_DBG("[n_body]=%p\n", n_body);
104 if(n_body!=NULL) {
105 LM_DBG("[*n_body]=%.*s\n",n_body->len, n_body->s);
106 } else {
107 LM_ERR("issues while aggregating body\n");
108 }
109
110 pkg_free(body);
111 pkg_free(body_array);
112
113
114 xmlCleanupParser();
115 xmlMemoryDump();
116
117 return n_body;
118 }
119
dlginfo_agg_nbody(str * pres_user,str * pres_domain,str ** body_array,int n,int off_index)120 str* dlginfo_agg_nbody(str* pres_user, str* pres_domain, str** body_array,
121 int n, int off_index)
122 {
123 str* n_body= NULL;
124
125 LM_DBG("[pres_user]=%.*s [pres_domain]= %.*s, [n]=%d\n",
126 pres_user->len, pres_user->s, pres_domain->len, pres_domain->s, n);
127
128 if(body_array== NULL && (!force_dummy_dialog))
129 return NULL;
130
131 if(body_array== NULL)
132 return dlginfo_agg_nbody_empty(pres_user, pres_domain);
133
134 n_body= agregate_xmls(pres_user, pres_domain, body_array, n);
135 LM_DBG("[n_body]=%p\n", n_body);
136 if(n_body) {
137 LM_DBG("[*n_body]=%.*s\n",
138 n_body->len, n_body->s);
139 }
140 if(n_body== NULL && n!= 0)
141 {
142 LM_ERR("while aggregating body\n");
143 }
144
145 xmlCleanupParser();
146 xmlMemoryDump();
147
148 return n_body;
149 }
150
agregate_xmls(str * pres_user,str * pres_domain,str ** body_array,int n)151 str* agregate_xmls(str* pres_user, str* pres_domain, str** body_array, int n)
152 {
153 int i, j= 0;
154
155 xmlDocPtr doc = NULL;
156 xmlNodePtr root_node = NULL;
157 xmlNsPtr namespace = NULL;
158 /*int winner_priority = -1, priority;*/
159
160 xmlNodePtr p_root= NULL;
161 xmlDocPtr* xml_array ;
162 xmlNodePtr node = NULL;
163 xmlNodePtr terminated_node = NULL;
164 xmlNodePtr early_node = NULL;
165 xmlNodePtr confirmed_node = NULL;
166 xmlNodePtr proceed_node = NULL;
167 xmlNodePtr trying_node = NULL;
168 xmlNodePtr next_node = NULL;
169
170
171 char *state = NULL;
172 xmlChar *dialog_id = NULL;
173 int node_id = -1;
174
175 xmlNodePtr winner_dialog_node = NULL ;
176 str *body= NULL;
177
178 char buf[MAX_URI_SIZE+1];
179
180 LM_DBG("[pres_user]=%.*s [pres_domain]= %.*s, [n]=%d\n",
181 pres_user->len, pres_user->s, pres_domain->len, pres_domain->s, n);
182
183 xml_array = (xmlDocPtr*)pkg_malloc( n*sizeof(xmlDocPtr));
184 if(xml_array== NULL)
185 {
186 LM_ERR("while allocating memory");
187 return NULL;
188 }
189 memset(xml_array, 0, n*sizeof(xmlDocPtr)) ;
190
191 /* parse all the XML documents */
192 for(i=0; i<n; i++)
193 {
194 if(body_array[i] == NULL )
195 continue;
196
197 xml_array[j] = NULL;
198 xml_array[j] = xmlParseMemory( body_array[i]->s, body_array[i]->len );
199
200 /* LM_DBG("parsing XML body: [n]=%d, [i]=%d, [j]=%d xml_array[j]=%p\n",
201 * n, i, j, xml_array[j] ); */
202
203 if( xml_array[j]== NULL)
204 {
205 LM_ERR("while parsing xml body message\n");
206 goto error;
207 }
208 j++;
209
210 }
211
212 if(j== 0) /* no body */
213 {
214 LM_DBG("no body to be built\n");
215 goto error;
216 }
217
218 /* n: number of bodies in total */
219 /* j: number of useful bodies; created XML structures */
220 /* i: loop counter */
221 /* LM_DBG("number of bodies in total [n]=%d, number of"
222 * " useful bodies [j]=%d\n", n, j ); */
223
224 /* create the new NOTIFY body */
225 if ( (pres_user->len + pres_domain->len + 1 + 4 + 1) >= MAX_URI_SIZE) {
226 LM_ERR("entity URI too long, maximum=%d\n", MAX_URI_SIZE);
227 goto error;
228 }
229 memcpy(buf, "sip:", 4);
230 memcpy(buf+4, pres_user->s, pres_user->len);
231 buf[pres_user->len+4] = '@';
232 memcpy(buf + pres_user->len + 5, pres_domain->s, pres_domain->len);
233 buf[pres_user->len + 5 + pres_domain->len]= '\0';
234
235 doc = xmlNewDoc(BAD_CAST "1.0");
236 if(doc==0) {
237 LM_ERR("unable to create xml document\n");
238 goto error;
239 }
240
241 root_node = xmlNewNode(NULL, BAD_CAST "dialog-info");
242 if(root_node==0)
243 goto error;
244
245 xmlDocSetRootElement(doc, root_node);
246 namespace = xmlNewNs(root_node,
247 BAD_CAST "urn:ietf:params:xml:ns:dialog-info", NULL);
248 if (!namespace) {
249 LM_ERR("creating namespace failed\n");
250 }
251 xmlSetNs(root_node, namespace);
252 /* The version must be increased for each new document and is a 32bit int.
253 * As the version is different for each watcher, we can not set here the
254 * correct value. Thus, we just put here a placeholder which will be
255 * replaced by the correct value in the aux_body_processing callback.
256 * Thus we have CPU intensive XML aggregation only once and can use
257 * quick search&replace in the per-watcher aux_body_processing callback.
258 * We use 11 chracters as an signed int (although RFC says unsigned int we
259 * use signed int as presence module stores "version" in DB as
260 * signed int) has max. 10 characters + 1 character for the sign
261 */
262 xmlNewProp(root_node, BAD_CAST "version", BAD_CAST "00000000000");
263 xmlNewProp(root_node, BAD_CAST "state", BAD_CAST "full" );
264 xmlNewProp(root_node, BAD_CAST "entity", BAD_CAST buf);
265
266 /* loop over all bodies and create the aggregated body */
267 for(i=0; i<j; i++)
268 {
269 /* LM_DBG("[n]=%d, [i]=%d, [j]=%d xml_array[i]=%p\n",
270 * n, i, j, xml_array[j] ); */
271 p_root= xmlDocGetRootElement(xml_array[i]);
272 if(p_root ==NULL) {
273 LM_ERR("the xml_tree root element is null\n");
274 goto error;
275 }
276 if (p_root->children) {
277 for (node = p_root->children; node; node = next_node) {
278 next_node = node->next;
279 if (node->type != XML_ELEMENT_NODE) {
280 continue;
281 }
282 LM_DBG("node type: Element, name: %s\n", node->name);
283 /* we do not copy the node, but unlink it and then add it ot the new node
284 * this destroys the original document but we do not need it anyway.
285 * using "copy" instead of "unlink" would also copy the namespace which
286 * would then be declared redundant (libxml unfortunately can not remove
287 * namespaces)
288 */
289 if(!force_single_dialog || (j == 1)) {
290 xmlUnlinkNode(node);
291 if(xmlAddChild(root_node, node) == NULL) {
292 xmlFreeNode(node);
293 LM_ERR("while adding child\n");
294 goto error;
295 }
296 } else {
297 /* try to put only the most important into the XML document
298 * order of importance: terminated->trying->proceeding->confirmed->early
299 * - check first the relevant states and jump priority for them
300 */
301 if(strcasecmp((char *)node->name, "dialog") == 0) {
302 if(dialog_id) {
303 xmlFree(dialog_id);
304 }
305 dialog_id = xmlGetProp(node, (const xmlChar *)"id");
306 if(dialog_id) {
307 node_id = i;
308 LM_DBG("Dialog id for this node : %s\n", dialog_id);
309 } else {
310 LM_DBG("No dialog id for this node - index: %d\n",
311 i);
312 }
313 }
314 state = xmlNodeGetNodeContentByName(node, "state", NULL);
315 if(state) {
316 LM_DBG("state element content = %s\n", state);
317 if(strcasecmp(state, "terminated") == 0) {
318 LM_DBG("found terminated state\n");
319 terminated_node = node;
320 } else if(strcasecmp(state, "confirmed") == 0
321 && node_id == i) {
322 /* here we check if confirmed is terminated or not
323 * if it is not we are in the middle of the conversation
324 */
325 if(check_relevant_state(dialog_id, xml_array, j)
326 >= DEF_TERMINATED_NODE) {
327 LM_DBG("confirmed state for dialog %s, but it "
328 "is not latest state\n",
329 dialog_id);
330 } else {
331 LM_DBG("confirmed state for dialog %s and "
332 "latest state for this dialog\n",
333 dialog_id);
334 confirmed_node = node;
335 }
336 } else if(strcasecmp(state, "early") == 0
337 && node_id == i) {
338 if(check_relevant_state(dialog_id, xml_array, j)
339 >= DEF_CONFIRMED_NODE) {
340 LM_DBG("early state for dialog %s, but it is "
341 "not latest state\n",
342 dialog_id);
343 } else {
344 LM_DBG("early state for dialog %s and latest "
345 "state for this dialog\n",
346 dialog_id);
347 early_node = node;
348 }
349 } else if(strcasecmp(state, "proceeding") == 0
350 && node_id == i) {
351 if(check_relevant_state(dialog_id, xml_array, j)
352 >= DEF_EARLY_NODE) {
353 LM_DBG("proceeding state for dialog %s, but it "
354 "is not latest state\n",
355 dialog_id);
356 } else {
357 LM_DBG("proceeding state for dialog %s and "
358 "latest state for this dialog\n",
359 dialog_id);
360 proceed_node = node;
361 }
362 } else if(strcasecmp(state, "trying") == 0
363 && node_id == i) {
364 if(check_relevant_state(dialog_id, xml_array, j)
365 >= DEF_PROCEEDING_NODE) {
366 LM_DBG("trying state for dialog %s, but it is "
367 "not latest state\n",
368 dialog_id);
369 } else {
370 LM_DBG("trying state for dialog %s and latest "
371 "state for this dialog\n",
372 dialog_id);
373 trying_node = node;
374 }
375 }
376 if(early_node != NULL) {
377 winner_dialog_node = early_node;
378 } else if(confirmed_node != NULL) {
379 winner_dialog_node = confirmed_node;
380 } else if(proceed_node != NULL) {
381 winner_dialog_node = proceed_node;
382 } else if(trying_node != NULL) {
383 winner_dialog_node = trying_node;
384 } else if(terminated_node != NULL) {
385 winner_dialog_node = terminated_node;
386 } else {
387 /* assume a failure somewhere and all above nodes are NULL */
388 winner_dialog_node = node;
389 }
390
391 xmlFree(state);
392 }
393 }
394 }
395 }
396 }
397
398 if (force_single_dialog && (j!=1)) {
399 if(winner_dialog_node == NULL) {
400 LM_ERR("no winning node found\n");
401 goto error;
402 }
403 xmlUnlinkNode(winner_dialog_node);
404 if(xmlAddChild(root_node, winner_dialog_node)== NULL) {
405 xmlFreeNode(winner_dialog_node);
406 LM_ERR("while adding winner-child\n");
407 goto error;
408 }
409 }
410
411 body = (str*)pkg_malloc(sizeof(str));
412 if(body == NULL) {
413 ERR_MEM(PKG_MEM_STR);
414 }
415
416 xmlDocDumpFormatMemory(doc,(xmlChar**)(void*)&body->s,
417 &body->len, 1);
418
419 if(dialog_id!=NULL) xmlFree(dialog_id);
420 for(i=0; i<j; i++) {
421 if(xml_array[i]!=NULL)
422 xmlFreeDoc( xml_array[i]);
423 }
424 if(doc) xmlFreeDoc(doc);
425 if(xml_array) pkg_free(xml_array);
426
427 xmlCleanupParser();
428 xmlMemoryDump();
429
430 return body;
431
432 error:
433 if(xml_array!=NULL)
434 {
435 for(i=0; i<j; i++)
436 {
437 if(xml_array[i]!=NULL)
438 xmlFreeDoc( xml_array[i]);
439 }
440 pkg_free(xml_array);
441 }
442 if(body) pkg_free(body);
443 if(dialog_id) xmlFree(dialog_id);
444 if(doc) xmlFreeDoc(doc);
445
446 return NULL;
447 }
448
449
450 /* returns 16 -> terminated, 8 -> confirmed, 4 -> early */
check_relevant_state(xmlChar * dialog_id,xmlDocPtr * xml_array,int total_nodes)451 int check_relevant_state (xmlChar * dialog_id, xmlDocPtr * xml_array,
452 int total_nodes)
453 {
454 int result = 0;
455 int i = 0;
456 int node_id = -1;
457 char *state;
458 xmlChar *dialog_id_tmp = NULL;
459 xmlNodePtr p_root;
460 xmlNodePtr node = NULL;
461
462 if(dialog_id==NULL) {
463 LM_WARN("dialog id is null\n");
464 return 0;
465 }
466
467 for (i = 0; i < total_nodes; i++)
468 {
469 p_root = xmlDocGetRootElement (xml_array[i]);
470 if (p_root == NULL)
471 {
472 LM_DBG ("the xml_tree root element is null\n");
473 } else {
474 if (p_root->children) {
475 for (node = p_root->children; node; node = node->next)
476 {
477 if (node->type != XML_ELEMENT_NODE) {
478 continue;
479 }
480 if(strcasecmp((char *)node->name, "dialog") == 0) {
481 /* Getting the node id so we would be sure
482 * that terminate state from same one the same */
483 if(dialog_id_tmp)
484 xmlFree(dialog_id_tmp);
485 dialog_id_tmp = xmlGetProp(node, (const xmlChar *)"id");
486 if(dialog_id_tmp)
487 node_id = i;
488 }
489 if(dialog_id_tmp == NULL) {
490 continue;
491 }
492 state = xmlNodeGetNodeContentByName(node, "state", NULL);
493 if(state) {
494 /* check if state is terminated for this dialog. */
495 if((strcasecmp(state, "terminated") == 0)
496 && (node_id == i) && (node_id >= 0)
497 && (strcasecmp((char *)dialog_id_tmp,
498 (char *)dialog_id)
499 == 0)) {
500 LM_DBG("Found terminated in dialog %s\n",
501 dialog_id);
502 result += DEF_TERMINATED_NODE;
503 }
504 /* check if state is confirmed for this dialog. */
505 if((strcasecmp(state, "confirmed") == 0)
506 && (node_id == i) && (node_id >= 0)
507 && (strcasecmp((char *)dialog_id_tmp,
508 (char *)dialog_id)
509 == 0)) {
510 LM_DBG("Found confirmed in dialog %s\n", dialog_id);
511 result += DEF_CONFIRMED_NODE;
512 }
513 if((strcasecmp(state, "early") == 0) && (node_id == i)
514 && (node_id >= 0)
515 && (strcasecmp((char *)dialog_id_tmp,
516 (char *)dialog_id)
517 == 0)) {
518 LM_DBG("Found early in dialog %s\n", dialog_id);
519 result += DEF_EARLY_NODE;
520 }
521 if((strcasecmp(state, "proceeding") == 0)
522 && (node_id == i) && (node_id >= 0)
523 && (strcasecmp((char *)dialog_id_tmp,
524 (char *)dialog_id)
525 == 0)) {
526 LM_DBG("Found proceeding in dialog %s\n",
527 dialog_id);
528 result += DEF_PROCEEDING_NODE;
529 }
530 if((strcasecmp(state, "trying") == 0) && (node_id == i)
531 && (node_id >= 0)
532 && (strcasecmp((char *)dialog_id_tmp,
533 (char *)dialog_id)
534 == 0)) {
535 LM_DBG("Found trying in dialog %s\n", dialog_id);
536 result += DEF_TRYING_NODE;
537 }
538 xmlFree(state);
539 }
540 }
541 }
542 }
543 }
544 if(dialog_id_tmp) xmlFree(dialog_id_tmp);
545 LM_DBG ("result cheching dialog %s is %d\n", dialog_id, result);
546 return result;
547 }
548
549
dlginfo_body_setversion(subs_t * subs,str * body)550 str *dlginfo_body_setversion(subs_t *subs, str *body)
551 {
552 char *version_start=0;
553 char version[MAX_INT_LEN + 2]; /* +2 becasue of trailing " and \0 */
554 int version_len;
555 str* aux_body = NULL;
556
557 if (!body) {
558 return NULL;
559 }
560
561 /* xmlDocDumpFormatMemory creates \0 terminated string */
562 /* version parameters starts at minimum at character 34 */
563 if (body->len < 41) {
564 LM_ERR("body string too short!\n");
565 return NULL;
566 }
567 version_start = strstr(body->s + 34, "version=");
568 if (!version_start) {
569 LM_ERR("version string not found!\n");
570 return NULL;
571 }
572 version_start += 9;
573
574 /* safety check for placeholder - if it is body not set by the module,
575 * don't update the version */
576 if(strncmp(version_start, "00000000000\"", 12)!=0)
577 return NULL;
578
579 version_len = snprintf(version, MAX_INT_LEN + 2,"%d\"", subs->version);
580 if (version_len >= MAX_INT_LEN + 2) {
581 LM_ERR("failed to convert 'version' to string\n");
582 return NULL;
583 }
584
585 aux_body= (str*)pkg_malloc(sizeof(str));
586 if(aux_body== NULL)
587 {
588 LM_ERR("error allocating memory for aux body str\n");
589 return NULL;
590 }
591 memset(aux_body, 0, sizeof(str));
592 aux_body->s= (char*)pkg_malloc( body->len * sizeof(char));
593 if(aux_body->s== NULL)
594 {
595 pkg_free(aux_body);
596 LM_ERR("error allocating memory for aux body buffer\n");
597 return NULL;
598 }
599 memcpy(aux_body->s, body->s, body->len);
600 aux_body->len= body->len;
601
602 /* again but on the copied str, no checks needed */
603 version_start = strstr(aux_body->s + 34, "version=");
604 version_start += 9;
605 /* Replace the placeholder 00000000000 with the version.
606 * Put the padding behind the ""
607 */
608 LM_DBG("replace version with \"%s\n",version);
609 memcpy(version_start, version, version_len);
610 memset(version_start + version_len, ' ', 12 - version_len);
611
612 xmlDocPtr doc = xmlReadMemory(aux_body->s, aux_body->len, "noname.xml",
613 NULL, 0);
614 if (doc == NULL) {
615 LM_ERR("error allocation xmldoc\n");
616 pkg_free(aux_body->s);
617 pkg_free(aux_body);
618 return NULL;
619 }
620 pkg_free(aux_body->s);
621 xmlDocDumpFormatMemory(doc,(xmlChar**)(void*)&aux_body->s,
622 &aux_body->len, 1);
623 xmlFreeDoc(doc);
624 xmlCleanupParser();
625 xmlMemoryDump();
626
627 return aux_body;
628 }
629