mirror of https://github.com/sipwise/sems.git
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.
401 lines
11 KiB
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");
|
|
}
|