modules/pua_reginfo/notify.c
552425a4
 /*
  * pua_reginfo module - Presence-User-Agent Handling of reg events
  *
542eb50f
  * Copyright (C) 2011-2012 Carsten Bock, carsten@ng-voice.com
552425a4
  * http://www.ng-voice.com
  *
  * This file is part of Kamailio, a free SIP server.
  *
  * Kamailio is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
  * Kamailio is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License 
  * along with this program; if not, write to the Free Software 
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "notify.h"
 #include "../../parser/parse_from.h"
 #include "../../parser/parse_content.h"
 #include "../../parser/parse_uri.h"
63a09d85
 #include "../../modules/usrloc/usrloc.h"
552425a4
 #include <libxml/parser.h>
 #include "pua_reginfo.h"
 
 /*<?xml version="1.0"?>
 <reginfo xmlns="urn:ietf:params:xml:ns:reginfo" version="0" state="full">
 .<registration aor="sip:carsten@ng-voice.com" id="0xb33fa860" state="active">
 ..<contact id="0xb33fa994" state="active" event="registered" expires="3600">
 ...<uri>sip:carsten@10.157.87.36:43582;transport=udp</uri>
 ...<unknown-param name="+g.3gpp.cs-voice"></unknown-param>
 ...<unknown-param name="+g.3gpp.icsi-ref">urn0X0.0041FB74E7B54P-1022urn-70X0P+03gpp-application.ims.iari.gsma-vs</unknown-param>
 ...<unknown-param name="audio"></unknown-param>
 ...<unknown-param name="+g.oma.sip-im.large-message"></unknown-param>
 ...<unknown-param name="+g.3gpp.smsip"></unknown-param>
 ...<unknown-param name="language">en,fr</unknown-param>
 ...<unknown-param name="+g.oma.sip-im"></unknown-param>
 ...<unknown-param name="expires">600000</unknown-param>
 ..</contact>
 .</registration>
 </reginfo> */
 
 #define STATE_ACTIVE 1
 #define STATE_TERMINATED 0
 #define STATE_UNKNOWN -1
 
 #define EVENT_UNKNOWN -1
 #define EVENT_REGISTERED 0
 #define EVENT_UNREGISTERED 1
 #define EVENT_TERMINATED 2
 #define EVENT_CREATED 3
 #define EVENT_REFRESHED 4
 #define EVENT_EXPIRED 5
 
 #define RESULT_ERROR -1
 #define RESULT_CONTACTS_FOUND 1
 #define RESULT_NO_CONTACTS 2
 
 int process_contact(udomain_t * domain, urecord_t ** ul_record, str aor, str callid, int cseq, int expires, int event, str contact_uri) {
 	str no_str = {0, 0};
 	static str no_ua = str_init("n/a");
 	static ucontact_info_t ci;
 	ucontact_t * ul_contact;
 
 	if (*ul_record == NULL) {
 		switch(event) {
 			case EVENT_REGISTERED:
 			case EVENT_CREATED:
 			case EVENT_REFRESHED:
 				/* In case, no record exists and new one should be created,
 				   create a new entry for this user in the usrloc-DB */
 				if (ul.insert_urecord(domain, &aor, ul_record) < 0) {
 					LM_ERR("failed to insert new user-record\n");
 					return RESULT_ERROR;
 				}
 				break;
 			default:
 				/* No entry in usrloc and the contact is expired, deleted, unregistered, whatever:
                                    We do not need to do anything. */
 				return RESULT_NO_CONTACTS;
 				break;
 		}
 	}
 	
 	/* Make sure, no crap is in the structure: */
 	memset( &ci, 0, sizeof(ucontact_info_t));	
 	/* Get callid of the message */
 	ci.callid = &callid;
 	/* Get CSeq number of the message */
 	ci.cseq = cseq;
 	ci.sock = NULL;
 	/* additional info from message */
 	ci.user_agent = &no_ua;
 	ci.last_modified = time(0);
 
 	/* set expire time */
 	ci.expires = time(0) + expires;
 
 	/* Now we start looking for the contact: */
 	if (((*ul_record)->contacts == 0)
542eb50f
 		|| (ul.get_ucontact(*ul_record, &contact_uri, &callid, &no_str, cseq+1, &ul_contact) != 0)) {
 		if (ul.insert_ucontact(*ul_record, &contact_uri, &ci, &ul_contact) < 0) {
552425a4
 			LM_ERR("failed to insert new contact\n");
 			return RESULT_ERROR;
 		}
 	} else {
 		if (ul.update_ucontact(*ul_record, ul_contact, &ci) < 0) {
 			LM_ERR("failed to update contact\n");
 			return RESULT_ERROR;
 		}
 	}
 	ul_contact = (*ul_record)->contacts;
 	while (ul_contact) {
 		if (VALID_CONTACT(ul_contact, time(0))) return RESULT_CONTACTS_FOUND;
 		ul_contact = ul_contact->next;
 	}
 
 	return RESULT_NO_CONTACTS;
 }
 
 xmlNodePtr xmlGetNodeByName(xmlNodePtr parent, const char *name) {
 	xmlNodePtr cur = parent;
 	xmlNodePtr match = NULL;
 	while (cur) {
 		if (xmlStrcasecmp(cur->name, (unsigned char*)name) == 0)
 			return cur;
 		match = xmlGetNodeByName(cur->children, name);
 		if (match)
 			return match;
 		cur = cur->next;
 	}
 	return NULL;
 }
 
 char * xmlGetAttrContentByName(xmlNodePtr node, const char *name) {
 	xmlAttrPtr attr = node->properties;
 	while (attr) {
 		if (xmlStrcasecmp(attr->name, (unsigned char*)name) == 0)
 			return (char*)xmlNodeGetContent(attr->children);
 		attr = attr->next;
 	}
 	return NULL;
 }
 
 int reginfo_parse_state(char * s) {
 	if (s == NULL) {
 		return STATE_UNKNOWN;
 	}
 	switch (strlen(s)) {
 		case 6:
 			if (strncmp(s, "active", 6) ==  0) return STATE_ACTIVE;
 			break;
 		case 10:
 			if (strncmp(s, "terminated", 10) ==  0) return STATE_TERMINATED;
 			break;
 		default:
 			LM_ERR("Unknown State %s\n", s);
 			return STATE_UNKNOWN;
 	}
 	LM_ERR("Unknown State %s\n", s);
 	return STATE_UNKNOWN;
 }
 
 int reginfo_parse_event(char * s) {
 	if (s == NULL) {
 		return EVENT_UNKNOWN;
 	}
 	switch (strlen(s)) {
 		case 7:
 			if (strncmp(s, "created", 7) ==  0) return EVENT_CREATED;
 			if (strncmp(s, "expired", 7) ==  0) return EVENT_EXPIRED;
 			break;
 		case 9:
 			if (strncmp(s, "refreshed", 9) ==  0) return EVENT_CREATED;
 			break;
 		case 10:
 			if (strncmp(s, "registered", 10) ==  0) return EVENT_REGISTERED;
 			if (strncmp(s, "terminated", 10) ==  0) return EVENT_TERMINATED;
 			break;
 		case 12:
 			if (strncmp(s, "unregistered", 12) ==  0) return EVENT_UNREGISTERED;
 			break;
 		default:
 			LM_ERR("Unknown Event %s\n", s);
 			return EVENT_UNKNOWN;
 	}
 	LM_ERR("Unknown Event %s\n", s);
 	return EVENT_UNKNOWN;
 }
 
 int process_body(str notify_body, udomain_t * domain) {
 	xmlDocPtr doc= NULL;
 	xmlNodePtr doc_root = NULL, registrations = NULL, contacts = NULL, uris = NULL;
 	str aor = {0, 0};
 	str callid = {0, 0};
 	str contact_uri = {0, 0};
542eb50f
 	str received = {0,0};
 	str path = {0,0};
 	str user_agent = {0, 0};
552425a4
 	int state, event, expires, result, final_result = RESULT_ERROR;
 	char * expires_char,  * cseq_char;
 	int cseq = 0;
 	urecord_t * ul_record;
 	ucontact_t * ul_contact;
 	struct sip_uri parsed_aor;
 
 	/* Temporary */
 	int mem_only = 1;
 
 	doc = xmlParseMemory(notify_body.s, notify_body.len);
 	if(doc== NULL)  {
 		LM_ERR("Error while parsing the xml body message, Body is:\n%.*s\n",
 			notify_body.len, notify_body.s);
 		return -1;
 	}
 	doc_root = xmlGetNodeByName(doc->children, "reginfo");
 	if(doc_root == NULL) {
 		LM_ERR("while extracting the reginfo node\n");
 		goto error;
 	}
 	registrations = doc_root->children;
 	while (registrations) {
 		/* Only process registration sub-items */
 		if (xmlStrcasecmp(registrations->name, BAD_CAST "registration") != 0)
 			goto next_registration;
 		state = reginfo_parse_state(xmlGetAttrContentByName(registrations, "state"));
 		if (state == STATE_UNKNOWN) {
 			LM_ERR("No state for this contact!\n");		
 			goto next_registration;
 		}
 		aor.s = xmlGetAttrContentByName(registrations, "aor");
 		if (aor.s == NULL) {
 			LM_ERR("No AOR for this contact!\n");		
 			goto next_registration;
 		}
 		aor.len = strlen(aor.s);
 		LM_DBG("AOR %.*s has state \"%d\"\n", aor.len, aor.s, state);
 
 		/* Get username part of the AOR, search for @ in the AOR. */
 		if (parse_uri(aor.s, aor.len, &parsed_aor) < 0) {
 			LM_ERR("failed to parse Address of Record (%.*s)\n",
 				aor.len, aor.s);
 			goto next_registration;
 		}
 
 		/* Now let's lock that domain for this AOR: */		
542eb50f
 		ul.lock_udomain(domain, &aor);
552425a4
 		/* and retrieve the user-record for this user: */
542eb50f
 		result = ul.get_urecord(domain, &aor, &ul_record);
552425a4
 		if (result < 0) {
542eb50f
 			ul.unlock_udomain(domain, &aor);
 			LM_ERR("failed to query usrloc (AOR %.*s)\n",
 				aor.len, aor.s);
552425a4
 			goto next_registration;
 		}
 		/* If no contacts found, then set the ul_record to NULL */
 		if (result != 0) ul_record = NULL;
 
 		/* If the state is terminated, we just can delete all bindings */
 		if (state == STATE_TERMINATED) {
 			if (ul_record) {
 				ul_contact = ul_record->contacts;
 				while(ul_contact) {
 					if (mem_only) {
 						ul_contact->flags |= FL_MEM;
 					} else {
 						ul_contact->flags &= ~FL_MEM;
 					}
 					ul_contact = ul_contact->next;
 				}
542eb50f
 				if (ul.delete_urecord(domain, &aor, ul_record) < 0) {
552425a4
 					LM_ERR("failed to remove record from usrloc\n");
 				}
 				/* If already a registration with contacts was found, then keep that result.
 				   otherwise the result is now "No contacts found" */
 				if (final_result != RESULT_CONTACTS_FOUND) final_result = RESULT_NO_CONTACTS;
 			}
 		/* Otherwise, process the content */
 		} else {
 			/* Now lets process the Contact's from this Registration: */
 			contacts = registrations->children;
 			while (contacts) {
 				if (xmlStrcasecmp(contacts->name, BAD_CAST "contact") != 0)
 					goto next_contact;
 				callid.s = xmlGetAttrContentByName(contacts, "callid");
 				if (callid.s == NULL) {
 					LM_ERR("No Call-ID for this contact!\n");		
 					goto next_contact;
 				}
 				callid.len = strlen(callid.s);
542eb50f
 				received.s = xmlGetAttrContentByName(contacts, "received");
 				if (received.s == NULL) {
                                         LM_DBG("No received for this contact!\n");
                                 }
 				received.len - strlen(received.s);
 
 				path.s = xmlGetAttrContentByName(contacts, "path");	
 				if (received.s == NULL) {
                                         LM_DBG("No path for this contact!\n");
                                 }
 				path.len = strlen(path.s);
 
 				user_agent.s = xmlGetAttrContentByName(contacts, "user_agent");
 				if (received.s == NULL) {
                                         LM_DBG("No user_agent for this contact!\n");
                                 }
 				user_agent.len = strlen(user_agent.s);
552425a4
 
 				event = reginfo_parse_event(xmlGetAttrContentByName(contacts, "event"));
 				if (event == EVENT_UNKNOWN) {
 					LM_ERR("No event for this contact!\n");		
 					goto next_contact;
 				}
 				expires_char = xmlGetAttrContentByName(contacts, "expires");
 				if (expires_char == NULL) {
 					LM_ERR("No expires for this contact!\n");		
 					goto next_contact;
 				}
 				expires = atoi(expires_char);
 				if (expires < 0) {
 					LM_ERR("No valid expires for this contact!\n");		
 					goto next_contact;
 				}
 				LM_DBG("%.*s: Event \"%d\", expires %d\n",
 					callid.len, callid.s, event, expires);
 
 				cseq_char = xmlGetAttrContentByName(contacts, "cseq");
 				if (cseq_char == NULL) {
 					LM_WARN("No cseq for this contact!\n");		
 				} else {
 					cseq = atoi(cseq_char);
 					if (cseq < 0) {
 						LM_WARN("No valid cseq for this contact!\n");		
 					}
 				}
 
 				/* Now lets process the URI's from this Contact: */
 				uris = contacts->children;
 				while (uris) {
 					if (xmlStrcasecmp(uris->name, BAD_CAST "uri") != 0)
 						goto next_uri;
 					contact_uri.s = (char*)xmlNodeGetContent(uris);	
 					if (contact_uri.s == NULL) {
 						LM_ERR("No URI for this contact!\n");		
 						goto next_registration;
 					}
 					contact_uri.len = strlen(contact_uri.s);
 					LM_DBG("Contact: %.*s\n",
 						contact_uri.len, contact_uri.s);
 
 					/* Add to Usrloc: */
 					result = process_contact(domain, &ul_record, parsed_aor.user, callid, cseq, expires, event, contact_uri);
 				
 					/* Process the result */
 					if (final_result != RESULT_CONTACTS_FOUND) final_result = result;
 next_uri:
 					uris = uris->next;
 				}
 next_contact:
 				contacts = contacts->next;
 			}
 		}
 next_registration:
 		// if (ul_record) ul.release_urecord(ul_record);		
 		/* Unlock the domain for this AOR: */
542eb50f
 		ul.unlock_udomain(domain, &aor);
552425a4
 
 		registrations = registrations->next;
 	}
 error:
 	/* Free the XML-Document */
     	if(doc) xmlFreeDoc(doc);
 	return final_result;
 }
 
 int reginfo_handle_notify(struct sip_msg* msg, char* domain, char* s2) {
  	str body;
 	int result = 1;
 
   	/* If not done yet, parse the whole message now: */
   	if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
   		LM_ERR("Error parsing headers\n");
   		return -1;
   	}
 	if (get_content_length(msg) == 0) {
    		LM_DBG("Content length = 0\n");
 		/* No Body? Then there is no published information available, which is ok. */
    		return 1;
    	} else {
    		body.s=get_body(msg);
    		if (body.s== NULL) {
    			LM_ERR("cannot extract body from msg\n");
    			return -1;
    		}
    		body.len = get_content_length(msg);
    	}
 
 	LM_DBG("Body is %.*s\n", body.len, body.s);
 	
 	result = process_body(body, (udomain_t*)domain);
 
 	return result;
 }