You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sems/core/AmSipSubscription.cpp

401 lines
11 KiB

/*
* 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(
#ifdef WITH_REPLICATION
sess_checkpoint,
#endif
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 = "<sip:"+info.from_user+"@"+info.domain+">";
req.from_uri = "sip:"+info.from_user+"@"+info.domain;
req.to = "<sip:"+info.user+"@"+info.domain+">";
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;
}
#ifdef HAVE_SEMS_OA
# define DLG_SENDREQ_EMPTY_BODY NULL
#else
# define DLG_SENDREQ_EMPTY_BODY "", ""
#endif
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, DLG_SENDREQ_EMPTY_BODY, 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, DLG_SENDREQ_EMPTY_BODY, 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, DLG_SENDREQ_EMPTY_BODY, 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<string> params = explode(subscription_state_params, ";");
for (vector<string>::iterator it=params.begin(); it != params.end(); it++) {
vector<string> 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");
}