From d9795df0b88eae8730e7cc37144b6762a6960880 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Mon, 16 Apr 2012 15:02:02 +0200 Subject: [PATCH] RFC3265 Event notification (SUBSCRIBE/NOTIFY) support - SEMS as susbcriber Development sponsored by Sipwise GmbH. Conflicts: apps/dsm/DSMCall.cpp apps/dsm/DSMStateEngine.h core/sip/defs.h --- apps/dsm/DSMCall.cpp | 19 + apps/dsm/DSMCoreModule.cpp | 3 + apps/dsm/DSMSession.h | 2 + apps/dsm/DSMStateEngine.h | 4 +- apps/dsm/SystemDSM.cpp | 21 +- apps/dsm/mods/mod_subscription/Makefile | 10 + .../mods/mod_subscription/ModSubscription.cpp | 102 +++++ .../mods/mod_subscription/ModSubscription.h | 41 ++ core/AmEvent.h | 5 +- core/AmSipHeaders.h | 4 + core/AmSipSubscription.cpp | 396 ++++++++++++++++++ core/AmSipSubscription.h | 222 ++++++++++ core/AmSipSubscriptionContainer.cpp | 157 +++++++ core/AmSipSubscriptionContainer.h | 59 +++ doc/dsm/dsm_syntax.txt | 6 + doc/dsm/mods/Readme.mod_subscription.txt | 43 ++ 16 files changed, 1090 insertions(+), 4 deletions(-) create mode 100644 apps/dsm/mods/mod_subscription/Makefile create mode 100644 apps/dsm/mods/mod_subscription/ModSubscription.cpp create mode 100644 apps/dsm/mods/mod_subscription/ModSubscription.h create mode 100644 core/AmSipSubscription.cpp create mode 100644 core/AmSipSubscription.h create mode 100644 core/AmSipSubscriptionContainer.cpp create mode 100644 core/AmSipSubscriptionContainer.h create mode 100644 doc/dsm/mods/Readme.mod_subscription.txt diff --git a/apps/dsm/DSMCall.cpp b/apps/dsm/DSMCall.cpp index ba9132fc..a996af0b 100644 --- a/apps/dsm/DSMCall.cpp +++ b/apps/dsm/DSMCall.cpp @@ -29,6 +29,7 @@ #include "AmUtils.h" #include "AmMediaProcessor.h" #include "DSM.h" +#include "AmSipSubscription.h" #include "../apps/jsonrpc/JsonRPCEvents.h" // todo! @@ -440,6 +441,24 @@ void DSMCall::process(AmEvent* event) } + if (event->event_id == E_SIP_SUBSCRIPTION) { + SIPSubscriptionEvent* sub_ev = dynamic_cast(event); + if (sub_ev) { + DBG("DSM Call received SIP Subscription Event\n"); + map params; + params["status"] = sub_ev->getStatusText(); + params["code"] = int2str(sub_ev->code); + params["reason"] = sub_ev->reason; + params["expires"] = int2str(sub_ev->expires); + params["has_body"] = (!sub_ev->notify_body.empty())?"true":"false"; + if (!sub_ev->notify_body.empty()) { + avar[DSM_AVAR_SIPSUBSCRIPTION_BODY] = AmArg(sub_ev->notify_body); + } + engine.runEvent(this, this, DSMCondition::SIPSubscription, ¶ms); + avar.erase(DSM_AVAR_SIPSUBSCRIPTION_BODY); + } + } + AmB2BCallerSession::process(event); } diff --git a/apps/dsm/DSMCoreModule.cpp b/apps/dsm/DSMCoreModule.cpp index c7ea2ce5..0c9d3a9a 100644 --- a/apps/dsm/DSMCoreModule.cpp +++ b/apps/dsm/DSMCoreModule.cpp @@ -197,6 +197,9 @@ DSMCondition* DSMCoreModule::getCondition(const string& from_str) { if (cmd == "jsonRpcResponse") return new TestDSMCondition(params, DSMCondition::JsonRpcResponse); + if (cmd == "subscription") + return new TestDSMCondition(params, DSMCondition::SIPSubscription); + if (cmd == "startup") return new TestDSMCondition(params, DSMCondition::Startup); diff --git a/apps/dsm/DSMSession.h b/apps/dsm/DSMSession.h index a7475b2f..d816ab40 100644 --- a/apps/dsm/DSMSession.h +++ b/apps/dsm/DSMSession.h @@ -66,6 +66,8 @@ using std::map; #define DSM_AVAR_JSONRPCRESPONSEDATA "JsonRpcResponseParameters" #define DSM_AVAR_JSONRPCRESPONSEUDATA "JsonRpcResponseUData" +#define DSM_AVAR_SIPSUBSCRIPTION_BODY "SipSubscriptionBody" + #define DSM_ERRNO_FILE "file" #define DSM_ERRNO_UNKNOWN_ARG "arg" #define DSM_ERRNO_SCRIPT "script" diff --git a/apps/dsm/DSMStateEngine.h b/apps/dsm/DSMStateEngine.h index e3351094..04d5d6ad 100644 --- a/apps/dsm/DSMStateEngine.h +++ b/apps/dsm/DSMStateEngine.h @@ -90,7 +90,9 @@ class DSMCondition Startup, Reload, - System + System, + + SIPSubscription }; bool invert; diff --git a/apps/dsm/SystemDSM.cpp b/apps/dsm/SystemDSM.cpp index ae2dc08b..22ebd149 100644 --- a/apps/dsm/SystemDSM.cpp +++ b/apps/dsm/SystemDSM.cpp @@ -6,6 +6,7 @@ #include "DSMStateDiagramCollection.h" #include "../apps/jsonrpc/JsonRPCEvents.h" // todo! +#include "AmSipSubscription.h" SystemDSM::SystemDSM(const DSMScriptConfig& config, const string& startDiagName, @@ -127,10 +128,28 @@ void SystemDSM::process(AmEvent* event) { } + if (event->event_id == E_SIP_SUBSCRIPTION) { + SIPSubscriptionEvent* sub_ev = dynamic_cast(event); + if (sub_ev) { + DBG("SystemDSM received SIP Subscription Event\n"); + map params; + params["status"] = sub_ev->getStatusText(); + params["code"] = int2str(sub_ev->code); + params["reason"] = sub_ev->reason; + params["expires"] = int2str(sub_ev->expires); + params["has_body"] = (!sub_ev->notify_body.empty())?"true":"false"; + if (!sub_ev->notify_body.empty()) { + avar[DSM_AVAR_SIPSUBSCRIPTION_BODY] = AmArg(sub_ev->notify_body); + } + engine.runEvent(&dummy_session, this, DSMCondition::SIPSubscription, ¶ms); + avar.erase(DSM_AVAR_SIPSUBSCRIPTION_BODY); + } + } + if (event->event_id == E_SYSTEM) { AmSystemEvent* sys_ev = dynamic_cast(event); if(sys_ev){ - DBG("SystemDSM received system Event\n"); + DBG("SystemDSM received system Event\n"); map params; params["type"] = AmSystemEvent::getDescription(sys_ev->sys_event); engine.runEvent(&dummy_session, this, DSMCondition::System, ¶ms); diff --git a/apps/dsm/mods/mod_subscription/Makefile b/apps/dsm/mods/mod_subscription/Makefile new file mode 100644 index 00000000..9381e846 --- /dev/null +++ b/apps/dsm/mods/mod_subscription/Makefile @@ -0,0 +1,10 @@ +plug_in_name = mod_subscription + +DSMPATH ?= ../.. + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" -I$(DSMPATH) + +COREPATH ?=$(DSMPATH)/../../core +lib_full_name = $(DSMPATH)/mods/lib/$(lib_name) +include $(DSMPATH)/mods/Makefile.dsm_module diff --git a/apps/dsm/mods/mod_subscription/ModSubscription.cpp b/apps/dsm/mods/mod_subscription/ModSubscription.cpp new file mode 100644 index 00000000..4a05d2fc --- /dev/null +++ b/apps/dsm/mods/mod_subscription/ModSubscription.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * Development sponsored by Sipwise GmbH. + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 "ModSubscription.h" +#include "log.h" +#include "AmUtils.h" + +#include "AmSipSubscriptionContainer.h" + +SC_EXPORT(MOD_CLS_NAME); + +MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) { + + DEF_CMD("subscription.create", SIPSUBCreateAction); + DEF_CMD("subscription.refresh", SIPSUBRefreshAction); + DEF_CMD("subscription.remove", SIPSUBRemoveAction); + +} MOD_ACTIONEXPORT_END; + +MOD_CONDITIONEXPORT_NONE(MOD_CLS_NAME); + +CONST_ACTION_2P(SIPSUBCreateAction, ',', true); +EXEC_ACTION_START(SIPSUBCreateAction) { + string params_s = resolveVars(par1, sess, sc_sess, event_params); +// string basedir = resolveVars(par2, sess, sc_sess, event_params); + + AmSipSubscriptionInfo info(sc_sess->var[params_s+".domain"], + sc_sess->var[params_s+".user"], + sc_sess->var[params_s+".from_user"], + sc_sess->var[params_s+".pwd"], + sc_sess->var[params_s+".proxy"], + sc_sess->var[params_s+".event"]); + info.accept = sc_sess->var[params_s+".accept"]; + info.id = sc_sess->var[params_s+".id"]; + unsigned int expires = 0; + + if (sc_sess->var.find(params_s+".expires") != sc_sess->var.end()) { + str2i(sc_sess->var[params_s+".expires"], expires); + } + + string handle = AmSipSubscriptionContainer::instance()-> + createSubscription(info, sess->getLocalTag(), expires); + + DBG("got handle '%s'\n", handle.c_str()); + sc_sess->var[params_s+".handle"] = handle; + if (handle.empty()) { + sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); + sc_sess->SET_STRERROR("could not create subscription\n"); + } else { + sc_sess->CLR_ERRNO; + } +} EXEC_ACTION_END; + + + +CONST_ACTION_2P(SIPSUBRefreshAction, ',', true); +EXEC_ACTION_START(SIPSUBRefreshAction) { + string handle = resolveVars(par1, sess, sc_sess, event_params); + string expires = resolveVars(par2, sess, sc_sess, event_params); + unsigned int expires_i = 0; + if (!expires.empty()) + str2i(expires, expires_i); + + DBG("refreshing subscription with handle '%s'\n", handle.c_str()); + if (!AmSipSubscriptionContainer::instance()->refreshSubscription(handle, expires_i)) { + sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL); + sc_sess->SET_STRERROR("could not refresh subscription\n"); + } else { + sc_sess->CLR_ERRNO; + } +} EXEC_ACTION_END; + +EXEC_ACTION_START(SIPSUBRemoveAction) { + string handle = resolveVars(arg, sess, sc_sess, event_params); + DBG("removing subscription with handle '%s'\n", handle.c_str()); + AmSipSubscriptionContainer::instance()->removeSubscription(handle); +} EXEC_ACTION_END; diff --git a/apps/dsm/mods/mod_subscription/ModSubscription.h b/apps/dsm/mods/mod_subscription/ModSubscription.h new file mode 100644 index 00000000..4557c229 --- /dev/null +++ b/apps/dsm/mods/mod_subscription/ModSubscription.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * Development sponsored by Sipwise GmbH. + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 + */ +#ifndef _MOD_SUBSCRIPTION_H +#define _MOD_SUBSCRIPTION_H +#include "DSMModule.h" +#include "DSMSession.h" + +#define MOD_CLS_NAME SCSubscriptionModule + +DECLARE_MODULE(MOD_CLS_NAME); + +DEF_ACTION_2P(SIPSUBCreateAction); +DEF_ACTION_2P(SIPSUBRefreshAction); +DEF_ACTION_1P(SIPSUBRemoveAction); +#endif diff --git a/core/AmEvent.h b/core/AmEvent.h index 4fbacf26..36ae590f 100644 --- a/core/AmEvent.h +++ b/core/AmEvent.h @@ -33,8 +33,9 @@ #include using std::string; -#define E_PLUGIN 100 -#define E_SYSTEM 101 +#define E_PLUGIN 100 +#define E_SYSTEM 101 +#define E_SIP_SUBSCRIPTION 102 /** \brief base event class */ struct AmEvent diff --git a/core/AmSipHeaders.h b/core/AmSipHeaders.h index a773eab6..911a3a44 100644 --- a/core/AmSipHeaders.h +++ b/core/AmSipHeaders.h @@ -9,6 +9,7 @@ #define SIP_METH_UPDATE "UPDATE" #define SIP_METH_BYE "BYE" #define SIP_METH_ACK "ACK" +#define SIP_METH_SUBSCRIBE "SUBSCRIBE" #define SIP_METH_NOTIFY "NOTIFY" #define SIP_HDR_FROM "From" @@ -36,6 +37,9 @@ #define SIP_HDR_WWW_AUTHENTICATE "WWW-Authenticate" #define SIP_HDR_ALLOW "Allow" #define SIP_HDR_RETRY_AFTER "Retry-After" +#define SIP_HDR_ACCEPT "Accept" +#define SIP_HDR_EVENT "Event" +#define SIP_HDR_SUBSCRIPTION_STATE "Subscription-State" #define SIP_HDR_COL(_hdr) _hdr ":" #define SIP_HDR_COLSP(_hdr) SIP_HDR_COL(_hdr) " " diff --git a/core/AmSipSubscription.cpp b/core/AmSipSubscription.cpp new file mode 100644 index 00000000..2825581a --- /dev/null +++ b/core/AmSipSubscription.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 "AmSipSubscription.h" +#include "AmSession.h" +#include "AmSessionContainer.h" +AmSipSubscription::AmSipSubscription(const string& handle, + const AmSipSubscriptionInfo& info, + const string& sess_link) + : info(info), + dlg(sess_checkpoint, this), + cred(info.domain, info.from_user, info.pwd), + sub_begin(0), + sub_expires(0), + wanted_expires(0), + sess_link(sess_link), + seh(NULL), + sub_state(SipSubStateIdle) +{ + + setReqFromInfo(); + req.from_tag = handle; + + // clear dlg.callid? ->reregister? +#ifdef HAVE_SEMS_OA + dlg.setOAEnabled(false); + dlg.initFromLocalRequest(req); +#else + dlg.updateStatusFromLocalRequest(req); +#endif + dlg.cseq = 50; +} + +AmSipSubscription::~AmSipSubscription() { + setSessionEventHandler(NULL); +} + +void AmSipSubscription::setReqFromInfo() { + req.user = info.user; + req.method = "SUBSCRIBE"; + req.domain = info.domain; + req.r_uri = "sip:"+info.user+"@"+info.domain; + req.from = ""; + req.from_uri = "sip:"+info.from_user+"@"+info.domain; + req.to = ""; + req.to_tag = ""; + req.callid = AmSession::getNewId(); +} + +void AmSipSubscription::setSubscriptionInfo(const AmSipSubscriptionInfo& _info) { + DBG("updating subscription info for '%s@%s'\n", + _info.user.c_str(), _info.domain.c_str()); + info = _info; + + setReqFromInfo(); + + // to trigger setting dlg identifiers + dlg.callid.clear(); + dlg.contact_uri.clear(); + +#ifdef HAVE_SEMS_OA + dlg.initFromLocalRequest(req); +#else + dlg.updateStatusFromLocalRequest(req); +#endif +} + +void AmSipSubscription::setExpiresInterval(unsigned int desired_expires) { + wanted_expires = desired_expires; +} + +void AmSipSubscription::setSessionEventHandler(AmSessionEventHandler* new_seh) { + if (seh) + delete seh; + seh = new_seh; +} + +string AmSipSubscription::getDescription() { + return "'"+info.user+"@"+info.domain+", Event: "+info.event+"'"; +} + +string AmSipSubscription::getSubscribeHdrs() { + string hdrs; + hdrs += SIP_HDR_COLSP(SIP_HDR_EVENT) + info.event; + if (!info.id.empty()) + hdrs += ";id="+info.id; + hdrs += CRLF; + if (!info.accept.empty()) { + hdrs += SIP_HDR_COLSP(SIP_HDR_ACCEPT) + info.accept + CRLF; + } + if (wanted_expires) { + hdrs += SIP_HDR_COLSP(SIP_HDR_EXPIRES) + int2str(wanted_expires) + CRLF; + } + return hdrs; +} + +bool AmSipSubscription::doSubscribe() +{ + bool res = true; + + req.to_tag = ""; + dlg.remote_tag = ""; + dlg.remote_uri = req.r_uri; + + // set outbound proxy as next hop + if (!info.proxy.empty()) { + dlg.outbound_proxy = info.proxy; + } else if (!AmConfig::OutboundProxy.empty()) { + dlg.outbound_proxy = AmConfig::OutboundProxy; + } + + if (dlg.sendRequest(req.method, +#ifdef HAVE_SEMS_OA + NULL, +#else + "", "", +#endif + getSubscribeHdrs()) < 0) { + WARN("failed to send subscription to '%s' (proxy '%s').\n", + dlg.remote_uri.c_str(), dlg.outbound_proxy.c_str()); + return false; + } + + switch (sub_state) { + case SipSubStateIdle: + case SipSubStatePending: + case SipSubStateTerminated: + sub_state = SipSubStatePending; + break; + case SipSubStateSubscribed: break; + } + + return res; +} + +bool AmSipSubscription::reSubscribe() +{ + if (dlg.sendRequest(req.method, NULL, getSubscribeHdrs()) < 0) { + WARN("failed to send subscription to '%s' (proxy '%s').\n", + dlg.remote_uri.c_str(), dlg.outbound_proxy.c_str()); + return false; + } + + return true; +} + +bool AmSipSubscription::doUnsubscribe() +{ + if (sub_state == SipSubStateTerminated || sub_state == SipSubStateIdle) { + DBG("not subscribed - not unsubscribing\n"); + return true; + } + + req.to_tag = ""; + dlg.remote_tag = ""; + dlg.remote_uri = req.r_uri; + + // set outbound proxy as next hop + if (!info.proxy.empty()) { + dlg.outbound_proxy = info.proxy; + } else if (!AmConfig::OutboundProxy.empty()) + dlg.outbound_proxy = AmConfig::OutboundProxy; + //else + // dlg.outbound_proxy = ""; + + string hdrs; + hdrs += SIP_HDR_COLSP(SIP_HDR_EVENT) + info.event; + if (!info.id.empty()) + hdrs += ";id="+info.id; + hdrs += CRLF; + if (!info.accept.empty()) { + hdrs += SIP_HDR_COLSP(SIP_HDR_ACCEPT) + info.accept + CRLF; + } + hdrs += SIP_HDR_COLSP(SIP_HDR_EXPIRES) "0" CRLF; + + if (dlg.sendRequest(req.method, NULL, hdrs) < 0) { + WARN("failed to send unsubscription to '%s' (proxy '%s').\n", + dlg.remote_uri.c_str(), dlg.outbound_proxy.c_str()); + return false; + } + + return true; +} +#ifdef HAVE_SEMS_OA +void AmSipSubscription::onSendRequest(AmSipRequest& req, int flags) +{ + if (seh) + seh->onSendRequest(req,flags); +} + +void AmSipSubscription::onSendReply(AmSipReply& reply, int flags) { + if (seh) + seh->onSendReply(reply,flags); +} +#else +void AmSipSubscription::onSendRequest(const string& method, const string& content_type, + const string& body, string& hdrs, int flags, + unsigned int cseq) { + if (seh) + seh->onSendRequest(method, content_type, body, hdrs, flags, cseq); +} + +void AmSipSubscription::onSendReply(const AmSipRequest& req, unsigned int code, + const string& reason, const string& content_type, + const string& body, string& hdrs, int flags) { + if (seh) + seh->onSendReply(req, code, reason, content_type, body, hdrs, flags); +} +#endif + +AmSipSubscription::SipSubscriptionState AmSipSubscription::getState() { + return sub_state; +} + +unsigned int AmSipSubscription::getExpiresLeft() { + if (sub_state != SipSubStateSubscribed) + return 0; + + long diff = sub_begin + sub_expires - time(NULL); + if (diff < 0) + return 0; + + return diff; +} + +void AmSipSubscription::onSubscriptionExpired() { + if (sess_link.length()) { + AmSessionContainer::instance()-> + postEvent(sess_link, new SIPSubscriptionEvent(SIPSubscriptionEvent:: + SubscriptionTimeout, req.from_tag)); + } + DBG("Subscription '%s' expired.\n", getDescription().c_str()); +} + +void AmSipSubscription::onRxReply(const AmSipReply& reply) { +#ifdef HAVE_SEMS_OA + dlg.onRxReply(reply); +#else + dlg.updateStatus(reply); +#endif +} + +void AmSipSubscription::onRxRequest(const AmSipRequest& req) { +#ifdef HAVE_SEMS_OA + dlg.onRxRequest(req); +#else + dlg.updateStatus(req); +#endif +} + +void AmSipSubscription::onSipReply(const AmSipReply& reply, +#ifdef HAVE_SEMS_OA + AmSipDialog::Status old_dlg_status +#else + int old_dlg_status, const string& trans_method +#endif + ) +{ + if ((seh!=NULL) && seh->onSipReply(reply, old_dlg_status +#ifndef HAVE_SEMS_OA + , trans_method +#endif +)) + return; + + if (reply.code < 200) { + DBG("Provisional reply received for subscription '%s'\n", getDescription().c_str()); + return; + } + + if ((reply.code>=200)&&(reply.code<300)) { + DBG("Positive reply to SUBSCRIBE\n"); + + unsigned int expires_i = 0; + string expires = getHeader(reply.hdrs, SIP_HDR_EXPIRES); + if (expires.length()) { + str2i(trim(expires, " \t"), expires_i); + } + + AmSessionContainer::instance()-> + postEvent(sess_link, + new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscribeActive, + req.from_tag, expires_i, + reply.code, reply.reason)); + } else if (reply.code >= 300) { + DBG("Subscription '%s' failed\n", getDescription().c_str()); + if (sess_link.length()) { + AmSessionContainer::instance()-> + postEvent(sess_link, + new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscribeFailed, + req.from_tag, 0, + reply.code, reply.reason)); + } + sub_state = SipSubStateTerminated; + } +} + +void AmSipSubscription::onSipRequest(const AmSipRequest& req) { + DBG("SIP Request received: '%s'\n", req.print().c_str()); + if (req.method != SIP_METH_NOTIFY) { + WARN("received %s Request in subscription???\n", req.method.c_str()); + dlg.reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); // todo: other err code? + sub_state = SipSubStateTerminated; + return; + } + + string h_subscription_state = getHeader(req.hdrs, SIP_HDR_SUBSCRIPTION_STATE); + if (h_subscription_state.empty()) { + WARN("received NOTIFY without Subscription-State (hdrs='%s')\n", req.hdrs.c_str()); + dlg.reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); // todo: other err code? + sub_state = SipSubStateTerminated; + return; + } + size_t pos = h_subscription_state.find(";"); + string subscription_state = h_subscription_state; + string subscription_state_params; + unsigned int expires = 0; + string reason; + + if (pos != string::npos) { + subscription_state = h_subscription_state.substr(0, pos); + subscription_state_params = h_subscription_state.substr(pos+1); + } + vector params = explode(subscription_state_params, ";"); + for (vector::iterator it=params.begin(); it != params.end(); it++) { + vector pv = explode(trim(*it, " \t"), "="); + if (pv.size()) { + if (trim(pv[0], " \t")=="expires") { + if (pv.size()>1) + str2i(trim(pv[1], " \t"), expires); + } else if (trim(pv[0], " \t")=="reason") { + if (pv.size()>1) + reason = trim(pv[1], " \t"); + } + } + } + + DBG("subscription state: '%s', expires: %u, reason: '%s'\n", + subscription_state.c_str(), expires, reason.c_str()); + + SIPSubscriptionEvent* sub_ev = + new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscribeFailed, req.from_tag); + + if (subscription_state == "active") { + sub_begin = time(0); + sub_ev->expires = sub_expires = expires; + sub_state = SipSubStateSubscribed; + sub_ev->status = SIPSubscriptionEvent::SubscribeActive; + } else if (subscription_state == "pending") { + sub_begin = time(0); + sub_expires = expires; + sub_state = SipSubStatePending; + sub_ev->status = SIPSubscriptionEvent::SubscribePending; + } else if (subscription_state == "terminated") { + sub_expires = 0; + sub_state = SipSubStateTerminated; + sub_ev->status = SIPSubscriptionEvent::SubscribeTerminated; + } else { + ERROR("unknown subscription_state '%s'\n", subscription_state.c_str()); + } + +#ifdef HAVE_SEMS_OA + if (!req.body.empty()) + sub_ev->notify_body.reset(new AmMimeBody(req.body)); +#else + sub_ev->notify_body = req.body; +#endif + + DBG("posting event to session '%s'\n", sess_link.c_str()); + AmSessionContainer::instance()->postEvent(sess_link, sub_ev); + + dlg.reply(req, 200, "OK"); +} diff --git a/core/AmSipSubscription.h b/core/AmSipSubscription.h new file mode 100644 index 00000000..95db8bca --- /dev/null +++ b/core/AmSipSubscription.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * Development sponsored by Sipwise GmbH. + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 + */ + +#ifndef _AmSipSubscription_h_ +#define _AmSipSubscription_h_ + +#include +using std::string; + +#include +#include + +#include "ampi/UACAuthAPI.h" +#include "AmSessionEventHandler.h" +#include "AmSdp.h" + +#ifdef HAVE_SEMS_OA +# include "AmMimeBody.h" +#endif + +struct AmSipSubscriptionInfo { + string domain; + string user; + string from_user; + string pwd; + string proxy; + string event; + string accept; + string id; + + AmSipSubscriptionInfo(const string& domain, + const string& user, + const string& from_user, + const string& pwd, + const string& proxy, + const string& event) + : domain(domain),user(user), + from_user(from_user),pwd(pwd),proxy(proxy), + event(event) + { } +}; + +struct SIPSubscriptionEvent : public AmEvent { + + enum SubscriptionStatus { + SubscribeActive=0, + SubscribeFailed, + SubscribeTerminated, + SubscribePending, + SubscriptionTimeout + }; + + string handle; + unsigned int code; + string reason; + SubscriptionStatus status; + unsigned int expires; +#ifdef HAVE_SEMS_OA + std::auto_ptr notify_body; +#else + string notify_body; +#endif + + SIPSubscriptionEvent(SubscriptionStatus status, const string& handle, + unsigned int expires = 0, + unsigned int code=0, const string& reason="") + : AmEvent(E_SIP_SUBSCRIPTION), status(status), handle(handle), + expires(expires), code(code), reason(reason) +#ifdef HAVE_SEMS_OA + ,notify_body(0) +#endif + {} + + const char* getStatusText() { + switch (status) { + case SubscribeActive: return "active"; + case SubscribeFailed: return "failed"; + case SubscribeTerminated: return "terminated"; + case SubscribePending: return "pending"; + case SubscriptionTimeout: return "timeout"; + } + return "unknown"; + } + +}; + +class AmSipSubscription +: public AmSipDialogEventHandler, + public DialogControl, + public CredentialHolder +{ + + public: + enum SipSubscriptionState { + SipSubStateIdle, + SipSubStatePending, + SipSubStateSubscribed, + SipSubStateTerminated, + }; + + protected: + + AmSipDialog dlg; + AmSessionCheckpoint sess_checkpoint; + UACAuthCred cred; + + AmSipSubscriptionInfo info; + + // session to post events to + string sess_link; + + AmSessionEventHandler* seh; + + AmSipRequest req; + + SipSubscriptionState sub_state; + time_t sub_begin; + unsigned int sub_expires; + + unsigned int wanted_expires; + + void setReqFromInfo(); + string getSubscribeHdrs(); + + void onSubscriptionExpired(); + + public: + AmSipSubscription(const string& handle, + const AmSipSubscriptionInfo& info, + const string& sess_link); + ~AmSipSubscription(); + + void setSubscriptionInfo(const AmSipSubscriptionInfo& _info); + string getDescription(); + + void setSessionEventHandler(AmSessionEventHandler* new_seh); + + void setExpiresInterval(unsigned int desired_expires); + + /** subscribe with the info set */ + bool doSubscribe(); + /** re-subscribe with the info set */ + bool reSubscribe(); + /** unsubscribe */ + bool doUnsubscribe(); + + /** return the state of the subscription */ + SipSubscriptionState getState(); + + /** return the expires left for the subscription */ + unsigned int getExpiresLeft(); + + const string& getEventSink() { return sess_link; } + const string& getHandle() { return req.from_tag; } + + void onRxReply(const AmSipReply& reply); + void onRxRequest(const AmSipRequest& req); + +#ifdef HAVE_SEMS_OA + void onSipRequest(const AmSipRequest& req); + void onSipReply(const AmSipReply& reply, AmSipDialog::Status old_dlg_status); +#else + void onSipRequest(const AmSipRequest& req); + void onSipReply(const AmSipReply& reply, int old_dlg_status, const string& trans_method); + + void onSendRequest(const string& method, const string& content_type, const string& body, + string& hdrs, int flags, unsigned int cseq); + void onSendReply(const AmSipRequest& req, unsigned int code, const string& reason, + const string& content_type, const string& body, string& hdrs, int flags); +#endif + + // DialogControl if + AmSipDialog* getDlg() { return &dlg; } + // CredentialHolder + UACAuthCred* getCredentials() { return &cred; } + + void onInvite2xx(const AmSipReply&) {} + void onRemoteDisappeared(const AmSipReply&) {} + void onInvite1xxRel(const AmSipReply &){} + void onNoAck(unsigned int) {} + void onNoPrack(const AmSipRequest &, const AmSipReply &) {} + void onPrack2xx(const AmSipReply &){} + void onFailure(AmSipDialogEventHandler::FailureCause cause, + const AmSipRequest*, const AmSipReply*){} + bool getSdpOffer(AmSdp&) {return false;} + bool getSdpAnswer(const AmSdp&, AmSdp&) {return false;} + int onSdpCompleted(const AmSdp&, const AmSdp&) {return -1;} + void onEarlySessionStart() {} + void onSessionStart() {} +}; + +typedef std::map AmSipSubscriptionMap; +typedef AmSipSubscriptionMap::iterator AmSipSubscriptionMapIter; +typedef AmSipSubscriptionMap::const_iterator AmSipSubscriptionMapConstIter; + +#endif diff --git a/core/AmSipSubscriptionContainer.cpp b/core/AmSipSubscriptionContainer.cpp new file mode 100644 index 00000000..fb3bdd96 --- /dev/null +++ b/core/AmSipSubscriptionContainer.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * Development sponsored by Sipwise GmbH. + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 "AmSipSubscriptionContainer.h" +#include "AmSession.h" +#include "AmEventDispatcher.h" + +#define SUBSCRIPTION_CONTAINER_EVQ_ID "_subscription_container_" + +_AmSipSubscriptionContainer::_AmSipSubscriptionContainer() + : initialized(false) +{ +} + +_AmSipSubscriptionContainer::~_AmSipSubscriptionContainer() { +} + +void _AmSipSubscriptionContainer::initialize() { + if (!initialized) { + // AmEventDispatcher::instance()->addEventQueue(SUBSCRIPTION_CONTAINER_EVQ_ID, this); + } +} + +string _AmSipSubscriptionContainer::createSubscription(const AmSipSubscriptionInfo& info, + const string& sess_link, + unsigned int wanted_expires) { + initialize(); + string handle = AmSession::getNewId(); + subscriptions[handle] = new AmSipSubscription(handle, info, sess_link); + subscriptions[handle]->setExpiresInterval(wanted_expires); + AmEventDispatcher::instance()->addEventQueue(handle, this); + if (!subscriptions[handle]->doSubscribe()) { + DBG("subscribe failed - removing subscription\b"); + AmEventDispatcher::instance()->delEventQueue(handle); + delete subscriptions[handle]; + subscriptions.erase(handle); + return ""; + } + return handle; +} + +bool _AmSipSubscriptionContainer::refreshSubscription(const string& sub_handle, + unsigned int wanted_expires) { + bool res = true; + subscriptions_mut.lock(); + AmSipSubscriptionMapIter it = subscriptions.find(sub_handle); + if (it != subscriptions.end()) { + DBG("refreshing subscription '%s'\n", sub_handle.c_str()); + it->second->setExpiresInterval(wanted_expires); + res = it->second->reSubscribe(); + } else { + DBG("subscription '%s' already removed\n", sub_handle.c_str()); + res = false; + } + subscriptions_mut.unlock(); + return res; +} + +void _AmSipSubscriptionContainer::removeSubscription(const string& sub_handle) { + subscriptions_mut.lock(); + AmSipSubscriptionMapIter it = subscriptions.find(sub_handle); + if (it != subscriptions.end()) { + DBG("unsubscribing subscription '%s'\n", sub_handle.c_str()); + it->second->doUnsubscribe(); + } else { + DBG("subscription '%s' already removed - ignoring\n", sub_handle.c_str()); + } + subscriptions_mut.unlock(); +} + +// AmEventQueueInterface +void _AmSipSubscriptionContainer::postEvent(AmEvent* event) { + auto_ptr l_event(event); + + AmSipRequestEvent* sip_req_ev = dynamic_cast(event); + if (sip_req_ev) { + // DBG("got SIP request: '%s'\n", sip_req_ev->req.print().c_str()); + DBG("got SIP request: %s %s\n", + sip_req_ev->req.method.c_str(), sip_req_ev->req.r_uri.c_str()); + string ltag = sip_req_ev->req.to_tag; + + subscriptions_mut.lock(); + AmSipSubscriptionMapIter it = subscriptions.find(ltag); + if (it == subscriptions.end()) { + subscriptions_mut.unlock(); + WARN("got SIP request '%s' for unknown subscription '%s'\n", + sip_req_ev->req.print().c_str(), ltag.c_str()); + AmSipDialog::reply_error(sip_req_ev->req, 481, SIP_REPLY_NOT_EXIST); + return; + } + it->second->onRxRequest(sip_req_ev->req); + if (it->second->getState() == AmSipSubscription::SipSubStateTerminated) { + DBG("subscription '%s' terminated - removing\n", it->second->getDescription().c_str()); + delete it->second; + subscriptions.erase(it); + } + subscriptions_mut.unlock(); + return; + } + + AmSipReplyEvent* sip_reply_ev = dynamic_cast(event); + if (sip_reply_ev) { + DBG("got SIP reply: '%s'\n", sip_reply_ev->reply.print().c_str()); + string ltag = +#ifdef HAVE_SEMS_OA + sip_reply_ev->reply.from_tag; +#else + sip_reply_ev->reply.local_tag; +#endif + subscriptions_mut.lock(); + AmSipSubscriptionMapIter it = subscriptions.find(ltag); + if (it == subscriptions.end()) { + subscriptions_mut.unlock(); + WARN("got SIP reply '%s' for unknown subscription '%s'\n", + sip_reply_ev->reply.print().c_str(), ltag.c_str()); + + return; + } + it->second->onRxReply(sip_reply_ev->reply); + if (it->second->getState() == AmSipSubscription::SipSubStateTerminated) { + DBG("subscription '%s' terminated - removing\n", it->second->getDescription().c_str()); + delete it->second; + subscriptions.erase(it); + } + subscriptions_mut.unlock(); + return; + } + +} + + diff --git a/core/AmSipSubscriptionContainer.h b/core/AmSipSubscriptionContainer.h new file mode 100644 index 00000000..09dbe7d2 --- /dev/null +++ b/core/AmSipSubscriptionContainer.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 FRAFOS GmbH + * + * Development sponsored by Sipwise GmbH. + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 released under + * the GPL with the additional exemption that compiling, linking, + * and/or using OpenSSL is allowed. + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS 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 + */ +#ifndef _AmSipSubscriptionContainer_h_ +#define _AmSipSubscriptionContainer_h_ + +#include "AmSipSubscription.h" +#include "AmEventQueue.h" +#include "AmThread.h" + +class _AmSipSubscriptionContainer +: public AmEventQueueInterface +{ + AmSipSubscriptionMap subscriptions; + AmMutex subscriptions_mut; + + void initialize(); + bool initialized; + public: + _AmSipSubscriptionContainer(); + ~_AmSipSubscriptionContainer(); + + string createSubscription(const AmSipSubscriptionInfo& info, const string& sess_link, + unsigned int wanted_expires=0); + bool refreshSubscription(const string& sub_handle, unsigned int wanted_expires=0); + void removeSubscription(const string& sub_handle); + + // AmEventQueueInterface + void postEvent(AmEvent* event); +}; + +typedef singleton<_AmSipSubscriptionContainer> AmSipSubscriptionContainer; + +#endif diff --git a/doc/dsm/dsm_syntax.txt b/doc/dsm/dsm_syntax.txt index 8ddf9d21..9c1121f3 100644 --- a/doc/dsm/dsm_syntax.txt +++ b/doc/dsm/dsm_syntax.txt @@ -410,6 +410,12 @@ brackets match. #params.* - parameters array +subscription - SIP subscription status + #status - status: "active", "pending", "failed", "terminated", "timeout" + #code - SIP response code (if failed) + #reason - SIP response reason (if failed) + NOTIFY body in avar[DSM_AVAR_SIPSUBSCRIPTION_BODY] (if #status=="active") + jsonRpcResponse - json-rpc response received #ev_type - JsonRpcResponse #id - response ID diff --git a/doc/dsm/mods/Readme.mod_subscription.txt b/doc/dsm/mods/Readme.mod_subscription.txt new file mode 100644 index 00000000..50d69bbb --- /dev/null +++ b/doc/dsm/mods/Readme.mod_subscription.txt @@ -0,0 +1,43 @@ +SIP Specific Event Notification (RFC3265) - support for DSM + + subscription.create(info_struct) + create new subscription. Parameters: + $info_struct.domain - SIP domain (RURI, From, To) + $info_struct.user - user (RURI, To) + $info_struct.from_user - user (From) + $info_struct.event - Event package (e.g. "reg") + + $info_struct.pwd - optional: password (for SIP auth) + $info_struct.proxy - optional: proxy URI (e.g. sip:10.0.0.1) + $info_struct.accept - optional: Accept content + $info_struct.id - optional: id (subscription ID to be used) + $info_struct.expires - optional: desired subscription expiration + + returns: + $info_struct.handle - handle for subscription (sub dialog ltag) + + subscription.refresh(handle [, expires]) + refresh subscription + + subscription.remove(handle) + unsubscribe & remove subscription + + + +Example: + set($r.domain="192.168.5.110"); + set($r.user="300"); + set($r.from_user="200"); + set($r.pwd="verysecret"); + -- set($r.proxy="sip:192.168.5.110"); + set($r.event="reg"); + -- set($r.id="115"); + -- set($r.expires="50"); + subscription.create(r); + + if test($r.handle!="") { + log(1, $r.handle); + } else { + log(1, "subscription failed:"); + logVars(); + }