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