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/apps/dsm/mods/mod_dlg/ModDlg.cpp

623 lines
20 KiB

/*
* Copyright (C) 2008 iptego 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 "ModDlg.h"
#include "log.h"
#include "AmUtils.h"
#include "DSMSession.h"
#include "AmSession.h"
#include "AmB2BSession.h"
#include <string.h>
#include "AmSipHeaders.h"
#include "AmUAC.h"
#include "ampi/UACAuthAPI.h"
SC_EXPORT(MOD_CLS_NAME);
MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("dlg.reply", DLGReplyAction);
DEF_CMD("dlg.replyRequest", DLGReplyRequestAction);
DEF_CMD("dlg.acceptInvite", DLGAcceptInviteAction);
DEF_CMD("dlg.bye", DLGByeAction);
DEF_CMD("dlg.connectCalleeRelayed", DLGConnectCalleeRelayedAction);
DEF_CMD("dlg.dialout", DLGDialoutAction);
DEF_CMD("dlg.getRequestBody", DLGGetRequestBodyAction)
DEF_CMD("dlg.getReplyBody", DLGGetReplyBodyAction)
DEF_CMD("dlg.getOtherId", DLGGetOtherIdAction)
DEF_CMD("dlg.getRtpRelayMode", DLGGetRtpRelayModeAction)
DEF_CMD("dlg.refer", DLGReferAction);
DEF_CMD("dlg.info", DLGInfoAction);
DEF_CMD("dlg.relayError", DLGB2BRelayErrorAction);
DEF_CMD("dlg.addReplyBodyPart", DLGAddReplyBodyPartAction);
DEF_CMD("dlg.deleteReplyBodyPart", DLGDeleteReplyBodyPartAction);
} MOD_ACTIONEXPORT_END;
//MOD_CONDITIONEXPORT_NONE(MOD_CLS_NAME);
MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) {
if (cmd == "dlg.replyHasContentType")
return new DLGReplyHasContentTypeCondition(params, false);
if (cmd == "dlg.requestHasContentType")
return new DLGRequestHasContentTypeCondition(params, false);
} MOD_CONDITIONEXPORT_END;
bool DLGModule::onInvite(const AmSipRequest& req, DSMSession* sess) {
// save inivital invite to last_req
// todo: save this in avar
sess->last_req.reset(new AmSipRequest(req));
return true;
}
string replaceLineEnds(string input)
{
string result;
size_t last = 0;
size_t pos;
while ((pos = input.find("\\r\\n", last)) != string::npos) {
result += input.substr(last, pos-last);
result += "\r\n";
last = pos + 4;
}
if (!input.substr(last).empty()) {
result += input.substr(last);
result += "\r\n";
}
return result;
}
// todo: convert errors to exceptions
void replyRequest(DSMSession* sc_sess, AmSession* sess,
EventParamT* event_params,
const string& par1, const string& par2,
const AmSipRequest& req) {
string code = resolveVars(par1, sess, sc_sess, event_params);
string reason = resolveVars(par2, sess, sc_sess, event_params);
string hdrs = replaceLineEnds(resolveVars("$dlg.reply.hdrs", sess, sc_sess, event_params));
unsigned int code_i;
if (str2int(code, code_i)) {
ERROR("decoding reply code '%s'\n", code.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
return;
}
DBG("replying with %i %s, hdrs='%s'\n", code_i, reason.c_str(), hdrs.c_str());
if (sess->dlg->reply(req, code_i, reason, NULL, hdrs)) {
sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
sc_sess->SET_STRERROR("error sending reply");
} else
sc_sess->CLR_ERRNO;
}
CONST_ACTION_2P(DLGReplyAction, ',', true);
EXEC_ACTION_START(DLGReplyAction) {
if (!sc_sess->last_req.get()) {
ERROR("no last request to reply\n");
sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
sc_sess->SET_STRERROR("no last request to reply");
return false;
}
replyRequest(sc_sess, sess, event_params, par1, par2, *sc_sess->last_req.get());
} EXEC_ACTION_END;
// todo (?) move replyRequest to core module (?)
CONST_ACTION_2P(DLGReplyRequestAction, ',', true);
EXEC_ACTION_START(DLGReplyRequestAction) {
DSMSipRequest* sip_req;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no request");
}
replyRequest(sc_sess, sess, event_params, par1, par2, *sip_req->req);
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGAcceptInviteAction, ',', true);
EXEC_ACTION_START(DLGAcceptInviteAction) {
// defaults to 200 OK
unsigned int code_i=200;
string reason = "OK";
string code = resolveVars(par1, sess, sc_sess, event_params);
string hdrs = replaceLineEnds(resolveVars("$dlg.reply.hdrs", sess, sc_sess, event_params));
if (code.length()) {
reason = resolveVars(par2, sess, sc_sess, event_params);
if (str2int(code, code_i)) {
ERROR("decoding reply code '%s'\n", code.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
sc_sess->SET_STRERROR("decoding reply code '"+
code+"%s'\n");
return false;
}
}
DBG("replying with %i %s, hdrs='%s'\n", code_i, reason.c_str(), hdrs.c_str());
if (!sc_sess->last_req.get()) {
ERROR("no last request to reply\n");
sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
sc_sess->SET_STRERROR("no last request to reply");
return false;
}
try {
AmMimeBody sdp_body;
/* save generated SDP body for this session as `established_body` */
if(sess->dlg->reply(*sc_sess->last_req.get(),
code_i,
reason,
sdp_body.addPart(SIP_APPLICATION_SDP),
hdrs,
SIP_FLAGS_SAVE_ESTB_SDP) != 0)
{
throw AmSession::Exception(500,"could not send response");
}
}catch(const AmSession::Exception& e){
ERROR("%i %s\n",e.code,e.reason.c_str());
sess->setStopped();
sess->dlg->reply(*sc_sess->last_req.get(),e.code,e.reason);
sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
sc_sess->SET_STRERROR("Error accepting call: "+ int2str(e.code) + " "+ e.reason);
}
} EXEC_ACTION_END;
EXEC_ACTION_START(DLGByeAction) {
string hdrs = resolveVars(arg, sess, sc_sess, event_params);
if (sess->dlg->bye(hdrs)) {
sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
sc_sess->SET_STRERROR("Error sending bye");
} else {
sc_sess->SET_ERRNO(DSM_ERRNO_OK);
}
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGConnectCalleeRelayedAction,',', false);
EXEC_ACTION_START(DLGConnectCalleeRelayedAction) {
string remote_party = resolveVars(par1, sess, sc_sess, event_params);
string remote_uri = resolveVars(par2, sess, sc_sess, event_params);
// if (sc_sess->last_req.get()) {
// sc_sess->B2BaddReceivedRequest(*sc_sess->last_req.get());
// } else {
// WARN("internal error: initial INVITE request missing.\n");
// }
// AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
// if (b2b_sess)
// b2b_sess->set_sip_relay_only(true);
// else
// ERROR("getting B2B session.\n");
sc_sess->B2BconnectCallee(remote_party, remote_uri, true);
} EXEC_ACTION_END;
EXEC_ACTION_START(DLGDialoutAction) {
string arrayname = resolveVars(arg, sess, sc_sess, event_params);
#define GET_VARIABLE_MANDATORY(varname_suffix, outvar) \
it = sc_sess->var.find(arrayname+varname_suffix); \
if (it == sc_sess->var.end()) { \
WARN("%s", std::string("need " + arrayname + varname_suffix " set for dlg.dialoutSimple("+arrayname+")").c_str()); \
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); \
return false; \
} \
outvar = it->second;
#define GET_VARIABLE_OPTIONAL(varname_suffix, outvar) \
it = sc_sess->var.find(arrayname+varname_suffix); \
if (it != sc_sess->var.end()) \
outvar = it->second;
map<string, string>::iterator it;
string v_from;
GET_VARIABLE_MANDATORY("_caller", v_from);
string v_to;
GET_VARIABLE_MANDATORY("_callee", v_to);
string v_domain;
GET_VARIABLE_MANDATORY("_domain", v_domain);
string app_name;
GET_VARIABLE_MANDATORY("_app", app_name);
string user = v_from;
string r_uri = "sip:"+v_to+"@"+v_domain;
GET_VARIABLE_OPTIONAL("_r_uri", r_uri);
string from = "<sip:"+v_from+"@"+v_domain+">";
GET_VARIABLE_OPTIONAL("_from", from);
string from_uri = "sip:"+v_from+"@"+v_domain;
GET_VARIABLE_OPTIONAL("_from_uri", from_uri);
string to = "<sip:"+v_to+"@"+v_domain+">";
GET_VARIABLE_OPTIONAL("_to", to);
string auth_user;
GET_VARIABLE_OPTIONAL("_auth_user", auth_user);
string auth_pwd;
GET_VARIABLE_OPTIONAL("_auth_pwd", auth_pwd);
string ltag;
GET_VARIABLE_OPTIONAL("_ltag", ltag);
string hdrs;
GET_VARIABLE_OPTIONAL("_hdrs", hdrs);
if (hdrs.length()) {
size_t crpos;
while ((crpos=hdrs.find("\\r\\n")) != string::npos) {
hdrs.replace(crpos, 4, "\r\n");
}
}
#undef GET_VARIABLE_MANDATORY
#undef GET_VARIABLE_OPTIONAL
DBG("placing UAC call: user <%s>, app <%s>, ruri <%s>, from <%s> "
"from_uri <%s>, to <%s>, ltag <%s>, hdrs <%s>, auth_user <%s>, auth_pwd <not shown>\n",
user.c_str(), app_name.c_str(), r_uri.c_str(), from.c_str(),
from_uri.c_str(), to.c_str(), ltag.c_str(), hdrs.c_str(), auth_user.c_str());
AmArg* sess_params = new AmArg();
bool has_auth = false;
if (!auth_user.empty() && !auth_pwd.empty()) {
AmArg auth_param;
auth_param.setBorrowedPointer(new UACAuthCred("", auth_user,auth_pwd));
sess_params->push(auth_param);
has_auth = true;
}
AmArg var_struct;
string varprefix = arrayname+"_var.";
bool has_vars = false;
map<string, string>::iterator lb = sc_sess->var.lower_bound(varprefix);
while (lb != sc_sess->var.end()) {
if ((lb->first.length() < varprefix.length()) ||
strncmp(lb->first.c_str(), varprefix.c_str(),varprefix.length()))
break;
string varname = lb->first.substr(varprefix.length());
if (!has_auth) // sess_params is variable struct
(*sess_params)[std::move(varname)] = lb->second;
else // variable struct is in sess_params array
var_struct[std::move(varname)] = lb->second;
lb++;
has_vars = true;
}
if (has_vars && has_auth)
sess_params->push(var_struct);
DBG("sess_params: '%s'\n", AmArg::print(*sess_params).c_str());
string new_sess_tag = AmUAC::dialout(user, app_name, r_uri, from, from_uri, to, ltag, hdrs, sess_params);
if (!new_sess_tag.empty()) {
sc_sess->var[arrayname + "_ltag"] = std::move(new_sess_tag);
} else {
sc_sess->var[arrayname + "_ltag"] = "";
sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
}
} EXEC_ACTION_END;
MATCH_CONDITION_START(DLGReplyHasContentTypeCondition) {
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
if (it == sc_sess->avar.end()) {
ERROR("DSM script error: dlg.replyHasContentType condition used for "
"other event than sipReply event\n");
return false;
}
DSMSipReply* dsm_reply = NULL;
if (!isArgAObject(sc_sess->avar[DSM_AVAR_REPLY]) ||
(NULL ==
(dsm_reply = dynamic_cast<DSMSipReply*>(sc_sess->avar[DSM_AVAR_REPLY].asObject())))) {
ERROR("internal: DSM could not get DSMSipReply\n");
return false;
}
bool res = dsm_reply->reply->body.hasContentType(arg);
DBG("checking for content_type '%s': %s\n", arg.c_str(), res?"has it":"doesn't have it");
return res;
} MATCH_CONDITION_END;
MATCH_CONDITION_START(DLGRequestHasContentTypeCondition) {
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
if (it == sc_sess->avar.end()) {
ERROR("DSM script error: dlg.requestHasContentType condition used for "
"other event than sipRequest event\n");
return false;
}
DSMSipRequest* dsm_req = NULL;
if (!isArgAObject(sc_sess->avar[DSM_AVAR_REQUEST]) ||
(NULL ==
(dsm_req = dynamic_cast<DSMSipRequest*>(sc_sess->avar[DSM_AVAR_REQUEST].asObject())))) {
ERROR("internal: DSM could not get DSMSipRequest\n");
return false;
}
bool res = dsm_req->req->body.hasContentType(arg);
DBG("checking for content_type '%s': %s\n", arg.c_str(), res?"has it":"doesn't have it");
return res;
} MATCH_CONDITION_END;
CONST_ACTION_2P(DLGGetRequestBodyAction, ',', false);
EXEC_ACTION_START(DLGGetRequestBodyAction) {
DSMSipRequest* sip_req;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no request");
}
string content_type = resolveVars(par1, sess, sc_sess, event_params);
string dstvar = resolveVars(par2, sess, sc_sess, event_params);
const AmMimeBody* msg_body = sip_req->req->body.hasContentType(content_type);
if (NULL == msg_body) {
DBG("body with content_type %s not found\n", content_type.c_str());
sc_sess->var.erase(dstvar);
} else {
sc_sess->var[dstvar] = msg_body->getPayload();
DBG("set $%s='%s'\n", dstvar.c_str(), sc_sess->var[dstvar].c_str());
}
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGGetReplyBodyAction, ',', false);
EXEC_ACTION_START(DLGGetReplyBodyAction) {
DSMSipReply* sip_req;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_req = dynamic_cast<DSMSipReply*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no reply");
}
string content_type = resolveVars(par1, sess, sc_sess, event_params);
string dstvar = resolveVars(par2, sess, sc_sess, event_params);
const AmMimeBody* msg_body = sip_req->reply->body.hasContentType(content_type);
if (NULL == msg_body) {
DBG("body with content_type %s not found\n", content_type.c_str());
sc_sess->var.erase(dstvar);
} else {
sc_sess->var[dstvar] = msg_body->getPayload();
DBG("set $%s='%s'\n", dstvar.c_str(), sc_sess->var[dstvar].c_str());
}
} EXEC_ACTION_END;
EXEC_ACTION_START(DLGGetOtherIdAction) {
string varname = arg;
AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
if (NULL == b2b_sess) {
DBG("script writer error: dlg.getOtherId used without B2B session object.\n");
EXEC_ACTION_STOP;
}
if (varname.size() && varname[0] == '$')
varname.erase(0, 1);
sc_sess->var[varname] = b2b_sess->getOtherId();
} EXEC_ACTION_END;
EXEC_ACTION_START(DLGGetRtpRelayModeAction) {
string varname = arg;
AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
if (NULL == b2b_sess) {
DBG("script writer error: dlg.getOtherId used without B2B session object.\n");
EXEC_ACTION_STOP;
}
if (varname.size() && varname[0] == '$')
varname.erase(0, 1);
switch (b2b_sess->getRtpRelayMode()) {
case AmB2BSession::RTP_Direct: sc_sess->var[varname] = "RTP_Direct"; break;
case AmB2BSession::RTP_Relay: sc_sess->var[varname] = "RTP_Relay"; break;
case AmB2BSession::RTP_Transcoding: sc_sess->var[varname] = "RTP_Transcoding"; break;
default: sc_sess->var[varname] = "Unknown"; break;
}
DBG("get RTP relay mode: %s='%s'\n", varname.c_str(), sc_sess->var[varname].c_str());
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGReferAction, ',', true);
EXEC_ACTION_START(DLGReferAction) {
AmSession* b2b_sess = dynamic_cast<AmSession*>(sess);
if (NULL == b2b_sess) {
throw DSMException("sbc", "type", "param", "cause",
"dlg.refer used on non-session");
}
string refer_to = resolveVars(par1, sess, sc_sess, event_params);
string expires_s = resolveVars(par2, sess, sc_sess, event_params);
int expires = -1;
if (!expires_s.empty()) {
if (!str2int(expires_s, expires)) {
throw DSMException("sbc", "type", "param", "cause",
"expires "+expires_s+" not valid");
}
}
if (NULL == b2b_sess->dlg) {
throw DSMException("sbc", "type", "param", "cause",
"call doesn't have SIP dialog (OOPS!)");
}
if (b2b_sess->dlg->refer(refer_to, expires)) {
sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
sc_sess->SET_STRERROR("sending REFER failed");
} else {
sc_sess->CLR_ERRNO;
}
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGInfoAction, ',', true);
EXEC_ACTION_START(DLGInfoAction) {
AmSession* b2b_sess = dynamic_cast<AmSession*>(sess);
if (NULL == b2b_sess) {
throw DSMException("sbc", "type", "param", "cause",
"dlg.info used on non-session");
}
string content_type = resolveVars(par1, sess, sc_sess, event_params);
string body_str = resolveVars(par2, sess, sc_sess, event_params);
if (NULL == b2b_sess->dlg) {
throw DSMException("sbc", "type", "param", "cause",
"call doesn't have SIP dialog (OOPS!)");
}
string body_crlf = std::move(body_str);
AmMimeBody *body = new AmMimeBody();
if (!content_type.empty()) {
DBG("body_crlf is '%s'\n", body_crlf.c_str());
while (true) {
size_t p = body_crlf.find("\\r\\n");
if (p==string::npos)
break;
body_crlf.replace(p, 4, "\r\n");
}
DBG("-> body_crlf is '%s'\n", body_crlf.c_str());
if (body->parse(content_type,
body_crlf.c_str(),
body_crlf.length())) {
throw DSMException("sbc", "type", "param", "cause",
"parsing of INFO body failed");
}
}
if (b2b_sess->dlg->info("", body)) {
sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
sc_sess->SET_STRERROR("sending INFO failed");
} else {
sc_sess->CLR_ERRNO;
}
} EXEC_ACTION_END;
#define GET_B2B_SESSION(action) \
AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess); \
if (NULL == b2b_sess) { \
throw DSMException("sbc", "type", "param", "cause", \
#action " used on non-b2b-session"); \
}
CONST_ACTION_2P(DLGB2BRelayErrorAction, ',', false);
EXEC_ACTION_START(DLGB2BRelayErrorAction) {
DSMSipRequest* sip_req;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no request");
}
GET_B2B_SESSION(dlg.refer);
string code = resolveVars(par1, sess, sc_sess, event_params);
string reason = resolveVars(par2, sess, sc_sess, event_params);
unsigned int code_i;
if (str2int(code, code_i)) {
ERROR("decoding reply code '%s'\n", code.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
EXEC_ACTION_STOP;
}
b2b_sess->relayError(sip_req->req->method, sip_req->req->cseq, true, code_i, reason.c_str());
} EXEC_ACTION_END;
CONST_ACTION_2P(DLGAddReplyBodyPartAction, ',', false);
EXEC_ACTION_START(DLGAddReplyBodyPartAction) {
DSMMutableSipReply* sip_reply;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_reply = dynamic_cast<DSMMutableSipReply*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no reply");
}
string content_type = resolveVars(par1, sess, sc_sess, event_params);
string body_part = resolveVars(par2, sess, sc_sess, event_params);
AmMimeBody* new_part;
new_part = sip_reply->mutable_reply->body.addPart(content_type);
new_part->setPayload(body_part.c_str(),
body_part.length());
DBG("added to reply body part %s='%s'\n",
content_type.c_str(), body_part.c_str());
} EXEC_ACTION_END;
EXEC_ACTION_START(DLGDeleteReplyBodyPartAction) {
DSMMutableSipReply* sip_reply;
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
if (it == sc_sess->avar.end() ||
!isArgAObject(it->second) ||
!(sip_reply = dynamic_cast<DSMMutableSipReply*>(it->second.asObject()))) {
throw DSMException("dlg", "cause", "no reply");
}
if (sip_reply->mutable_reply->body.deletePart(arg)) {
DBG("failed to delete reply body part '%s'\n", arg.c_str());
} else {
DBG("deleted reply body part '%s'\n", arg.c_str());
}
} EXEC_ACTION_END;