Handle sip subscribe requests and send event state notifications

git-svn-id: http://voip.null.ro/svn/yate@1378 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2007-07-04 16:17:11 +00:00
parent fc2e812a61
commit 1254ebb186
2 changed files with 533 additions and 0 deletions

View File

@ -0,0 +1,27 @@
; This file configures the SIP feature module
[general]
; General
; expires_min: int: Minimum allowed expiring time in seconds
;expires_min=60
; expires_def: int: Default expiring time if not present in requests
;expires_def=600
; expires_max: int: Value used to limit the expiring time to something sane
;expires_max=3600
[allow_events]
; Restrict/allow subscriptions to individual events
; If an event is disabled, no subscriptions to it will be allowed
; default: boolean: Default value if an event is not listed here
;default=enable
; dialog: boolean: Enable/disable subscriptions to dialog state events
;dialog=enable
; dialog: boolean: Enable/disable subscriptions to message waiting events
;message-summary=enable

506
modules/sipfeatures.cpp Normal file
View File

@ -0,0 +1,506 @@
/**
* sipfeatures.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Additional SIP features
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program 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.
*
* This program 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
#include <stdlib.h>
using namespace TelEngine;
namespace { // anonymous
// sip.subscribe handler
class YSipSubscribeHandler : public MessageHandler
{
public:
YSipSubscribeHandler(int prio = 100)
: MessageHandler("sip.subscribe",prio)
{}
virtual bool received(Message &msg);
// Get the event from a received message. Set the content type
// Set 'code' parameter of the message if false is returned
bool getEventData(Message& msg, int& event, String& evName, String& content);
};
// resource.notify handler
class YSipNotifyHandler : public MessageHandler
{
public:
YSipNotifyHandler(int prio = 100)
: MessageHandler("resource.notify",prio)
{}
virtual bool received(Message &msg);
// Create the body for 'dialog' event notification
void createDialogBody(String& dest, const Message& src, const String& entity);
// Create the body for 'message-summary' event notification
void createMWIBody(String& dest, const Message& src);
};
// Features module
class YSipFeatures : public Module
{
public:
enum Event {
Dialog = 0, // dialog
MWI = 1, // message-sumary
};
enum Content {
AppDlgInfoXml, // application/dialog-info+xml
AppSimpleMsgSummary, // application/simple-message-summary
};
YSipFeatures();
virtual ~YSipFeatures();
virtual void initialize();
inline bool forceDlgID() const
{ return m_forceDlgID; }
// Check expiring time from a received message.
// Look first at 'sip_expires' parameter and, if missing or emtpy, look at optional 'param'
// @param noExpires Default value to use if failed to get an expiring value. -1 to use the default one
// Return a negative value on failure or the expiring time on success
// Failure: set the response to 423 (interval too brief) and to 'osip_Min-Expires' parameter of msg
int checkExpire(Message& msg, int noExpires = -1, const char* param = 0);
private:
int m_expiresMin; // Minimum accepted value for expires
int m_expiresMax; // Maximum accepted value for expires
int m_expiresDef; // Default value for expires
bool m_forceDlgID; // Append dialog data if missing on dialog state notifications
};
// Expiring time values: min/default/max
#define EXPIRES_MIN 60
#define EXPIRES_DEF 600
#define EXPIRES_MAX 3600
#define KNOWN_EVENTS 2
static YSipFeatures s_module;
static TokenDict s_allowedEvents[KNOWN_EVENTS+1];
// List of known events
static TokenDict s_events[KNOWN_EVENTS+1] = {
{"dialog", YSipFeatures::Dialog},
{"message-summary", YSipFeatures::MWI},
{0,0}
};
// List of known content types
static TokenDict s_contents[] = {
{"application/dialog-info+xml", YSipFeatures::AppDlgInfoXml},
{"application/simple-message-summary", YSipFeatures::AppSimpleMsgSummary},
{0,0}
};
// Escape a string to be packed into another
inline void appendEsc(String& dest, const String& src1, const String& src2, const char sep = ' ')
{
String tmp;
tmp << src1 << sep << src2;
dest << tmp.msgEscape() << "\n";
}
// Force a parameter of a message
inline void forceParam(Message& msg, const char* param, const char* value)
{
const char* p = msg.getValue(param);
if (!(p && *p))
msg.setParam(param,value);
}
/**
* YSipSubscribeHandler
*/
bool YSipSubscribeHandler::received(Message &msg)
{
// Check received data
String evName, content;
int event;
if (!getEventData(msg,event,evName,content))
return false;
NamedString* contact = msg.getParam("sip_contact");
if (!(contact && *contact)) {
Debug(&s_module,DebugNote,"SUBSCRIBE with missing or empty contact");
msg.setParam("code","400");
return false;
}
// Expiration time
int expires = -1;
// Set default expiring time
// See draft-ietf-sipping-dialog-package-06.txt (dialog), RFC3842 (message-summary)
switch (event) {
case YSipFeatures::Dialog:
case YSipFeatures::MWI:
expires = 3600;
break;
}
expires = s_module.checkExpire(msg,expires);
if (expires == -1)
return false;
String sExpires = expires;
Message m("resource.subscribe");
m.addParam("event",evName);
if (expires) {
m.addParam("operation","subscribe");
m.addParam("expires",sExpires);
}
else
m.addParam("operation","unsubscribe");
m.addParam("subscriber",msg.getValue("username"));
URI uriContact(contact);
String notifyto;
notifyto << "sip:" << uriContact.getUser() << "@" << uriContact.getHost();
m.addParam("notifyto",notifyto);
URI uriRequest = msg.getValue("sip_uri");
m.addParam("notifier",uriRequest.getUser());
// Pack data parameters
String data;
appendEsc(data,"host",msg.getValue("ip_host"));
appendEsc(data,"port",msg.getValue("ip_port"));
appendEsc(data,"uri",msg.getValue("sip_contact"));
String from = msg.getValue("sip_to");
if (-1 == from.find("tag="))
from << ";tag=" << msg.getValue("xsip_dlgtag");
appendEsc(data,"sip_From",from);
appendEsc(data,"sip_To",msg.getValue("sip_from"));
appendEsc(data,"sip_Call-ID",msg.getValue("sip_callid"));
String c;
c << "<" << notifyto << ">";
appendEsc(data,"sip_Contact",c);
appendEsc(data,"sip_Event",evName);
if (content)
appendEsc(data,"xsip_type",content);
m.addParam("data",data);
XDebug(&s_module,DebugAll,
"SUBSCRIBE. notifier=%s subscriber=%s event=%s notifyto=%s",
m.getValue("notifier"),m.getValue("subscriber"),
evName.c_str(),notifyto.c_str());
if (!Engine::dispatch(m))
return false;
msg.setParam("osip_Expires",sExpires);
msg.setParam("code","200");
return true;
}
// Get the event from a received message. Set the content type
bool YSipSubscribeHandler::getEventData(Message& msg, int& event, String& evName,
String& content)
{
NamedString* tmp = msg.getParam("sip_event");
evName = tmp ? tmp->c_str() : "";
event = lookup(evName,s_allowedEvents,-1);
// RFC3265 3.1.2: An Event header MUST be present
// draft-ietf-sipping-dialog-package-06.txt The Event header for 'dialog' event
// may contain dialog identifier(s). Reject them
if (!(tmp && *tmp && event != -1)) {
Debug(&s_module,DebugNote,"SUBSCRIBE. Can't handle event='%s'%s",
evName.c_str(),!tmp?". Event header is missing":"");
msg.setParam("code",tmp?"489":"400"); // Bad event or request
return false;
}
// Set content type
int defType = -1;
switch (event) {
case YSipFeatures::Dialog:
defType = YSipFeatures::AppDlgInfoXml; // draft-ietf-sipping-dialog-package-06.txt
break;
case YSipFeatures::MWI:
defType = YSipFeatures::AppSimpleMsgSummary; // RFC3842
break;
}
content = lookup(defType,s_contents);
// Check if an Accept header is present: if so, it MUST contain the content
// type we know how to handle
String accept = msg.getValue("sip_Accept");
if (content && accept) {
ObjList* obj = accept.split(',');
if (!obj->find(content))
content = "";
TelEngine::destruct(obj);
}
if (content)
return true;
// Can't choose a content
Debug(&s_module,DebugNote,
"SUBSCRIBE. Can't handle content type. accept='%s' event='%s'",
accept.c_str(),evName.c_str());
msg.setParam("code","406"); // Not acceptable
return false;
}
/**
* YSipNotifyHandler
*/
bool YSipNotifyHandler::received(Message &msg)
{
String notifyto = msg.getValue("notifyto");
if (!notifyto.startsWith("sip:"))
return false;
NamedString* event = msg.getParam("event");
int evType = event ? lookup(*event,s_allowedEvents,-1) : -1;
Message m("xsip.generate");
m.addParam("method","NOTIFY");
// Copy notify parameters
String data = msg.getValue("data");
ObjList* obj = data.split('\n');
for (ObjList* o = obj->skipNull(); o; o = o->skipNext()) {
String s = (static_cast<String*>(o->get()))->msgUnescape();
int pos = s.find(" ");
if (pos == -1)
continue;
m.addParam(s.substr(0,pos),s.substr(pos+1));
}
TelEngine::destruct(obj);
String ss = msg.getValue("subscriptionstate");
if (ss) {
const char* res = msg.getValue("terminatereason");
if (res)
ss << ";" << res;
m.addParam("sip_Subscription-State",ss);
}
else
m.addParam("sip_Subscription-State","active");
const char* exp = msg.getValue("expires");
if (exp && *exp)
m.addParam("sip_Expires",exp);
// Check event & Create body
String body;
switch (evType) {
case YSipFeatures::Dialog: {
URI uri(m.getValue("sip_From"));
String entity;
entity << "sip:" << uri.getUser() << "@" << uri.getHost();
if (s_module.forceDlgID()) {
String id = msg.getValue("dialog.id");
if (id) {
forceParam(msg,"dialog.callid",id);
forceParam(msg,"dialog.localtag",id);
forceParam(msg,"dialog.remotetag",id);
forceParam(msg,"dialog.remoteuri",entity);
}
}
createDialogBody(body,msg,entity);
}
break;
case YSipFeatures::MWI:
createMWIBody(body,msg);
break;
default:
Debug(&s_module,DebugNote,"NOTIFY. Invalid event='%s'",
event?event->c_str():"");
return false;
}
m.addParam("xsip_body",body);
XDebug(&s_module,DebugAll,
"NOTIFY. notifier=%s subscriber=%s event=%s notifyto=%s",
msg.getValue("notifier"),msg.getValue("subscriber"),
event->c_str(),msg.getValue("notifyto"));
return Engine::dispatch(m);
}
inline void addStartTag(String& dest, const char* tag, bool close, const char* indent = 0)
{
dest << indent << "<" << tag;
if (close)
dest << ">";
}
inline void addEndTag(String& dest, const char* tag = 0, const char* indent = 0, bool eol = true)
{
dest << indent;
if (tag)
dest << "</" << tag;
dest << ">";
if (eol)
dest << "\r\n";
}
inline void addAttr(String& dest, const char* attr, const String& value)
{
if (value)
dest << " " << attr << "=\"" << value << "\"";
}
// Create the body for 'dialog' event notification
void YSipNotifyHandler::createDialogBody(String& dest, const Message& src,
const String& entity)
{
// NOTE: Line separators and indentations are inserted for debug purposes
const char* indent1 = " ";
const char* indent2 = " ";
dest = "";
dest << "<?xml version=\"1.0\"?>\r\n";
addStartTag(dest,"dialog-info",false);
addAttr(dest,"xmlns","urn:ietf:params:xml:ns:dialog-info");
addAttr(dest,"version",src.getValue("notifyseq"));
// We always send partial data (only dialogs changed since last notification)
// state will be 'full' if we send data for all active dialogs
const char* id = src.getValue("dialog.id");
addAttr(dest,"notify-state",id ? "partial" : "full");
addAttr(dest,"entity",entity);
addEndTag(dest);
// Append dialog data
const char* state = src.getValue("dialog.state");
if (id && *id && state && *state) {
// dialog tag and attributes
addStartTag(dest,"dialog",false,indent1);
addAttr(dest,"id",id);
addAttr(dest,"call-id",src.getValue("dialog.callid"));
addAttr(dest,"local-tag",src.getValue("dialog.localtag"));
addAttr(dest,"remote-tag",src.getValue("dialog.remotetag"));
String dir = src.getValue("dialog.direction");
if (dir == "incoming")
dir = "recipient";
else if (dir == "outgoing")
dir = "initiator";
else
dir = "";
addAttr(dest,"direction",dir);
addEndTag(dest);
// "state" child of "dialog"
addStartTag(dest,"state",true,indent2);
dest << state;
addEndTag(dest,"state",0);
// "remote" child of "dialog"
String tmp = src.getValue("dialog.remoteuri");
if (tmp) {
addStartTag(dest,"remote",true,indent2);
addStartTag(dest,"target",false);
addAttr(dest,"uri",tmp);
addEndTag(dest,0,0,false);
addEndTag(dest,"remote",0);
}
addEndTag(dest,"dialog",indent1);
}
addEndTag(dest,"dialog-info");
}
// Create the body for 'message-summary' event notification
void YSipNotifyHandler::createMWIBody(String& dest, const Message& src)
{
// See RFC3458 6.2 for message classes
dest = "Message-Waiting: ";
unsigned int n = (unsigned int)src.getIntValue("message-summary.voicenew",0);
unsigned int o = (unsigned int)src.getIntValue("message-summary.voiceold",0);
if (n || o) {
dest << (n ? "yes" : "no");
dest << "\r\nVoice-Message: " << n << "/" << o << "\r\n";
}
else
dest << "no\r\n";
}
/**
* YSipFeatures
*/
YSipFeatures::YSipFeatures()
: Module("sipfeatures","misc")
{
Output("Loaded module SIP Features");
}
YSipFeatures::~YSipFeatures()
{
Output("Unloading module SIP Features");
}
void YSipFeatures::initialize()
{
Output("Initializing module SIP Features");
static bool first = true;
Configuration cfg(Engine::configFile("sipfeatures"));
m_expiresMin = cfg.getIntValue("general","expires_min",EXPIRES_MIN);
m_expiresMax = cfg.getIntValue("general","expires_max",EXPIRES_MAX);
m_expiresDef = cfg.getIntValue("general","expires_def",EXPIRES_DEF);
m_forceDlgID = cfg.getBoolValue("general","forcedialogdata",true);
// Build the list of allowed events
NamedList* evs = cfg.getSection("allow_events");
bool def = evs ? evs->getBoolValue("default",true) : true;
int iAllowed = 0;
for (int i= 0; i < KNOWN_EVENTS; i++) {
if (!s_events[i].token)
break;
NamedString* ns = evs ? evs->getParam(s_events[i].token) : 0;
bool allowed = ns ? ns->toBoolean(def) : def;
if (allowed) {
s_allowedEvents[iAllowed].token = s_events[i].token;
s_allowedEvents[iAllowed++].value = s_events[i].value;
}
}
s_allowedEvents[iAllowed].token = 0;
s_allowedEvents[iAllowed].value = 0;
if (debugAt(DebugNote)) {
String tmp;
for (int i= 0; s_allowedEvents[i].token; i++)
tmp.append(s_allowedEvents[i].token,",");
if (tmp)
Debug(this,DebugAll,"Allowed subscriptions: %s",tmp.c_str());
else
Debug(this,DebugNote,"Subscriptions not allowed");
}
// Done with reload options
if (!first)
return;
first = false;
setup();
Engine::install(new YSipSubscribeHandler);
Engine::install(new YSipNotifyHandler);
}
// Check expiring time from a received message.
int YSipFeatures::checkExpire(Message& msg, int noExpires, const char* param)
{
String tmp(msg.getValue("sip_expires"));
if (!tmp && param)
tmp = msg.getValue(param);
int expires = tmp.toInteger(-1);
if (expires < 0)
expires = (noExpires < 0 ? m_expiresDef : noExpires);
if (expires > m_expiresMax)
expires = m_expiresMax;
if (expires && (expires < m_expiresMin)) {
msg.setParam("osip_Min-Expires",String(m_expiresMin));
msg.setParam("code","423"); // Interval too brief
return -1;
}
return expires;
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */