mirror of https://github.com/sipwise/sems.git
- click2dial: a c2d application with uac-auth support - sw_prepaid_sip: the signalling part for a prepaid engine git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@527 8eb893ce-cfd4-0310-b710-fb5ebe64c474sayer/1.4-spce2.6
parent
1a5d475c6a
commit
dfd03bb91c
@ -0,0 +1,288 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2007 Sipwise GmbH
|
||||
* Based on the concept of "announcement", Copyright (C) 2002-2003 Fhg Fokus
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 "Click2Dial.h"
|
||||
#include "AmSessionContainer.h"
|
||||
#include "AmConfig.h"
|
||||
#include "AmUtils.h"
|
||||
#include "AmApi.h"
|
||||
#include "AmPlugIn.h"
|
||||
|
||||
#include "AmMediaProcessor.h"
|
||||
|
||||
#include "sems.h"
|
||||
#include "log.h"
|
||||
|
||||
#define MOD_NAME "click2dial"
|
||||
|
||||
EXPORT_SESSION_FACTORY(Click2DialFactory, MOD_NAME);
|
||||
|
||||
string Click2DialFactory::AnnouncePath;
|
||||
string Click2DialFactory::AnnounceFile;
|
||||
|
||||
Click2DialFactory::Click2DialFactory(const string& _app_name)
|
||||
: AmSessionFactory(_app_name)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int Click2DialFactory::onLoad()
|
||||
{
|
||||
AmConfigReader cfg;
|
||||
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
|
||||
return -1;
|
||||
|
||||
// get application specific global parameters
|
||||
configureModule(cfg);
|
||||
|
||||
AnnouncePath = cfg.getParameter("announce_path",ANNOUNCE_PATH);
|
||||
if(!AnnouncePath.empty() && AnnouncePath[AnnouncePath.length()-1] != '/')
|
||||
AnnouncePath += "/";
|
||||
|
||||
AnnounceFile = cfg.getParameter("default_announce",ANNOUNCE_FILE);
|
||||
DBG("AnnounceFile = %s\n",AnnounceFile.c_str());
|
||||
|
||||
string announce_file = AnnouncePath + AnnounceFile;
|
||||
if(!file_exists(announce_file)) {
|
||||
ERROR("default file for ann_b2b module does not exist ('%s').\n",
|
||||
announce_file.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
string Click2DialFactory::getAnnounceFile(const AmSipRequest& req)
|
||||
{
|
||||
string announce_path = AnnouncePath;
|
||||
string announce_file = announce_path + req.domain
|
||||
+ "/" + req.user + ".wav";
|
||||
|
||||
DBG("trying '%s'\n",announce_file.c_str());
|
||||
if(file_exists(announce_file))
|
||||
goto end;
|
||||
|
||||
announce_file = announce_path + req.user + ".wav";
|
||||
DBG("trying '%s'\n",announce_file.c_str());
|
||||
if(file_exists(announce_file))
|
||||
goto end;
|
||||
announce_file = AnnouncePath + AnnounceFile;
|
||||
|
||||
end:
|
||||
return announce_file;
|
||||
}
|
||||
|
||||
|
||||
AmSession* Click2DialFactory::onInvite(const AmSipRequest& req, AmArg& session_params)
|
||||
{
|
||||
UACAuthCred* cred = NULL;
|
||||
string callee_uri, a_realm, a_user, a_pwd;
|
||||
|
||||
if(session_params.size() != 4) {
|
||||
ERROR("Need 4 parameters, got %d\n", session_params.size());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(session_params.get(0).getType() == AmArg::CStr) {
|
||||
a_realm = string(session_params.get(0).asCStr());
|
||||
}
|
||||
else {
|
||||
ERROR("All arguments have to be CStr\n");
|
||||
return NULL;
|
||||
}
|
||||
if(session_params.get(1).getType() == AmArg::CStr) {
|
||||
a_user = string(session_params.get(1).asCStr());
|
||||
}
|
||||
else {
|
||||
ERROR("All arguments have to be CStr\n");
|
||||
return NULL;
|
||||
}
|
||||
if (session_params.get(2).getType() == AmArg::CStr) {
|
||||
a_pwd = string(session_params.get(2).asCStr());
|
||||
}
|
||||
else {
|
||||
ERROR("All arguments have to be CStr\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cred = new UACAuthCred(a_realm, a_user, a_pwd);
|
||||
if(cred == NULL) {
|
||||
ERROR("Failed to create authentication handle\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (session_params.get(3).getType() == AmArg::CStr) {
|
||||
callee_uri = string(session_params.get(3).asCStr());
|
||||
}
|
||||
else {
|
||||
ERROR("All arguments have to be CStr\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AmSession* s = new C2DCallerDialog(req, getAnnounceFile(req), callee_uri, cred);
|
||||
if(s == NULL) {
|
||||
ERROR("Failed to create a click2dial dialog");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AmSessionEventHandlerFactory* uac_auth_f =
|
||||
AmPlugIn::instance()->getFactory4Seh("uac_auth");
|
||||
if(uac_auth_f != NULL) {
|
||||
DBG("UAC Auth enabled for new announcement session.\n");
|
||||
AmSessionEventHandler *h = uac_auth_f->getHandler(s);
|
||||
if (h != NULL) {
|
||||
s->addHandler(h);
|
||||
}
|
||||
else {
|
||||
ERROR("Failed to get authentication event handler");
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ERROR("uac_auth interface not accessible. "
|
||||
"Load uac_auth for authenticated dialout.\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
AmSession* Click2DialFactory::onInvite(const AmSipRequest& req)
|
||||
{
|
||||
return new C2DCallerDialog(req, getAnnounceFile(req), NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
C2DCallerDialog::C2DCallerDialog(const AmSipRequest& req,
|
||||
const string& filename, const string& c_uri, UACAuthCred* credentials)
|
||||
: filename(filename), callee_uri(c_uri), cred(credentials),
|
||||
AmB2BCallerSession()
|
||||
{
|
||||
sip_relay_only = false;
|
||||
}
|
||||
|
||||
|
||||
void C2DCallerDialog::onSessionStart(const AmSipReply& rep)
|
||||
{
|
||||
setReceiving(false);
|
||||
invite_req.body = rep.body;
|
||||
|
||||
if(wav_file.open(filename,AmAudioFile::Read))
|
||||
throw string("AnnouncementDialog::onSessionStart: Cannot open file\n");
|
||||
setOutput(&wav_file);
|
||||
}
|
||||
|
||||
|
||||
void C2DCallerDialog::onSessionStart(const AmSipRequest& req)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void C2DCallerDialog::process(AmEvent* event)
|
||||
{
|
||||
AmAudioEvent* audio_event = dynamic_cast<AmAudioEvent*>(event);
|
||||
if(audio_event && audio_event->event_id == AmAudioEvent::cleared) {
|
||||
|
||||
if(getCalleeStatus() != None)
|
||||
return;
|
||||
AmMediaProcessor::instance()->removeSession(this);
|
||||
|
||||
connectCallee(string("<") + callee_uri + ">", callee_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
AmB2BCallerSession::process(event);
|
||||
}
|
||||
|
||||
|
||||
void C2DCallerDialog::createCalleeSession()
|
||||
{
|
||||
UACAuthCred* c = new UACAuthCred(cred.get()->realm,
|
||||
cred.get()->user, cred.get()->pwd);
|
||||
|
||||
AmB2BCalleeSession* callee_session = new C2DCalleeDialog(this, c);
|
||||
AmSipDialog& callee_dlg = callee_session->dlg;
|
||||
|
||||
other_id = AmSession::getNewId();
|
||||
|
||||
callee_dlg.local_tag = other_id;
|
||||
callee_dlg.callid = AmSession::getNewId() + "@" + AmConfig::LocalIP;
|
||||
callee_dlg.local_party = dlg.local_party;
|
||||
callee_dlg.remote_party = dlg.remote_party;
|
||||
callee_dlg.remote_uri = dlg.remote_uri;
|
||||
|
||||
callee_session->start();
|
||||
|
||||
AmSessionContainer* sess_cont = AmSessionContainer::instance();
|
||||
sess_cont->addSession(other_id,callee_session);
|
||||
}
|
||||
|
||||
|
||||
void C2DCallerDialog::onB2BEvent(B2BEvent* ev)
|
||||
{
|
||||
if(ev->event_id == B2BSipReply) {
|
||||
AmSipReply& reply = ((B2BSipReplyEvent*)ev)->reply;
|
||||
|
||||
if(reply.code == 407 && cred.get() != NULL) {
|
||||
AmB2BSession::onB2BEvent(ev);
|
||||
return;
|
||||
}
|
||||
}
|
||||
AmB2BCallerSession::onB2BEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
C2DCalleeDialog::C2DCalleeDialog(const AmB2BCallerSession* caller, UACAuthCred* credentials)
|
||||
: AmB2BCalleeSession(caller), cred(credentials)
|
||||
{
|
||||
setAuthHandler();
|
||||
}
|
||||
|
||||
|
||||
void C2DCalleeDialog::setAuthHandler()
|
||||
{
|
||||
if(cred.get() != NULL) {
|
||||
AmSessionEventHandlerFactory* uac_auth_f =
|
||||
AmPlugIn::instance()->getFactory4Seh("uac_auth");
|
||||
if (uac_auth_f != NULL) {
|
||||
AmSessionEventHandler *h = uac_auth_f->getHandler(this);
|
||||
if (h != NULL ) {
|
||||
DBG("uac-auth enabled for new callee session.\n");
|
||||
addHandler(h);
|
||||
}
|
||||
else {
|
||||
ERROR("uac_auth interface not accessible. "
|
||||
"Load uac_auth for authenticated dialout.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2007 Sipwise GmbH
|
||||
* Based on the concept of "announcement", Copyright (C) 2002-2003 Fhg Fokus
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 _CLICK_2_DIAL_H_
|
||||
#define _CLICK_2_DIAL_H_
|
||||
|
||||
#include "AmSession.h"
|
||||
#include "AmConfigReader.h"
|
||||
#include "AmSipRequest.h"
|
||||
#include "AmAudio.h"
|
||||
#include "AmB2BSession.h"
|
||||
|
||||
#include "ampi/UACAuthAPI.h"
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
class Click2DialFactory: public AmSessionFactory
|
||||
{
|
||||
string getAnnounceFile(const AmSipRequest& req);
|
||||
|
||||
public:
|
||||
|
||||
static string AnnouncePath;
|
||||
static string AnnounceFile;
|
||||
static AmSessionEventHandler* AuthHandler;
|
||||
|
||||
Click2DialFactory(const string& _app_name);
|
||||
|
||||
int onLoad();
|
||||
AmSession* onInvite(const AmSipRequest& req);
|
||||
AmSession* onInvite(const AmSipRequest& req, AmArg& session_params);
|
||||
};
|
||||
|
||||
class C2DCallerDialog: public AmB2BCallerSession, public CredentialHolder
|
||||
{
|
||||
AmAudioFile wav_file;
|
||||
string filename;
|
||||
string callee_uri;
|
||||
std::auto_ptr<UACAuthCred> cred;
|
||||
|
||||
public:
|
||||
|
||||
C2DCallerDialog(const AmSipRequest& req, const string& filename,
|
||||
const string& callee_uri, UACAuthCred* credentials = NULL);
|
||||
|
||||
void process(AmEvent* event);
|
||||
void onSessionStart(const AmSipRequest& req);
|
||||
void onSessionStart(const AmSipReply& rep);
|
||||
void createCalleeSession();
|
||||
inline UACAuthCred* getCredentials() { return cred.get(); }
|
||||
void onB2BEvent(B2BEvent*);
|
||||
};
|
||||
|
||||
class C2DCalleeDialog : public AmB2BCalleeSession, public CredentialHolder
|
||||
{
|
||||
std::auto_ptr<UACAuthCred> cred;
|
||||
void setAuthHandler();
|
||||
|
||||
public:
|
||||
|
||||
C2DCalleeDialog(const AmB2BCallerSession* caller, UACAuthCred* credentials = NULL);
|
||||
inline UACAuthCred* getCredentials() { return cred.get(); }
|
||||
};
|
||||
#endif // _CLICK_2_DIAL_H_
|
||||
@ -0,0 +1,10 @@
|
||||
plug_in_name = click2dial
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
extra_install = $(plug_in_name)_audio
|
||||
|
||||
COREPATH ?=../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
2007-10-13 agranig:
|
||||
Add a mechanism to kind of poll for the status
|
||||
of the current call (e.g. to be used to visualize
|
||||
the progress on a web site).
|
||||
@ -0,0 +1,2 @@
|
||||
announce_path=/usr/local/lib/sems/audio/
|
||||
default_announce=default_en.wav
|
||||
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
########################################################################
|
||||
# An example using python to initiate a call via di_dialer and xmlrpc2di
|
||||
#
|
||||
# In this case, the R-Uris of leg (a) and leg(b) are looking like
|
||||
# sip:user@proxy-ip;sw_domain=domain to route the call
|
||||
# via an outbound proxy "proxy". The proxy in question has to
|
||||
# extract the value of sw_domain and place it into the domain
|
||||
# of the R-Uri to correctly route it to the destination.
|
||||
########################################################################
|
||||
|
||||
proxy = "192.168.100.10"
|
||||
xmlrpc_url = "http://127.0.0.1:8090"
|
||||
|
||||
caller_user = "foo"
|
||||
caller_domain = "iptel.org"
|
||||
callee_user = "bar"
|
||||
callee_domain = "iptel.org"
|
||||
|
||||
auth_user = "foo"
|
||||
auth_pass = "foopass"
|
||||
auth_realm = "iptel.org"
|
||||
|
||||
announce_file = "default_en"
|
||||
|
||||
from xmlrpclib import *
|
||||
s = ServerProxy(xmlrpc_url)
|
||||
s.dial_auth_b2b(
|
||||
"click2dial", announce_file,
|
||||
"sip:" + caller_user + "@" + caller_domain,
|
||||
"sip:" + callee_user + "@" + callee_domain,
|
||||
"sip:" + caller_user + "@" + proxy + ";sw_domain=" + caller_domain,
|
||||
"sip:" + callee_user + "@" + proxy + ";sw_domain=" + callee_domain,
|
||||
auth_realm, auth_user, auth_pass)
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = sw_prepaid_sip
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?= ../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2007 Sipwise GmbH
|
||||
* Based on the concept of mycc, Copyright (C) 2002-2003 Fhg Fokus
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 "SWPrepaidSIP.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "AmUtils.h"
|
||||
#include "AmAudio.h"
|
||||
#include "AmPlugIn.h"
|
||||
#include "AmMediaProcessor.h"
|
||||
#include "AmSipDialog.h"
|
||||
#include "AmConfigReader.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#define MOD_NAME "sw_prepaid_sip"
|
||||
|
||||
#define TIMERID_CREDIT_TIMEOUT 1
|
||||
#define ACC_PLUGIN "sw_prepaid_acc"
|
||||
|
||||
EXPORT_SESSION_FACTORY(SWPrepaidSIPFactory,MOD_NAME);
|
||||
|
||||
SWPrepaidSIPFactory::SWPrepaidSIPFactory(const string& _app_name)
|
||||
: AmSessionFactory(_app_name), user_timer_fact(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int SWPrepaidSIPFactory::onLoad()
|
||||
{
|
||||
AmConfigReader cfg;
|
||||
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
|
||||
return -1;
|
||||
|
||||
string acc_plugin = cfg.getParameter("acc_plugin", ACC_PLUGIN);
|
||||
|
||||
user_timer_fact = AmPlugIn::instance()->getFactory4Di("user_timer");
|
||||
if(!user_timer_fact) {
|
||||
ERROR("could not load user_timer from session_timer plug-in\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DBG("use acc plugin '%s'", acc_plugin.c_str());
|
||||
cc_acc_fact = AmPlugIn::instance()->getFactory4Di(acc_plugin);
|
||||
if(!cc_acc_fact) {
|
||||
ERROR("could not load accounting plugin '%s', please provide a valid module name\n",
|
||||
acc_plugin.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
AmSession* SWPrepaidSIPFactory::onInvite(const AmSipRequest& req)
|
||||
{
|
||||
AmDynInvoke* user_timer = user_timer_fact->getInstance();
|
||||
if(!user_timer) {
|
||||
ERROR("could not get a user timer reference\n");
|
||||
throw AmSession::Exception(500,"could not get a user timer reference");
|
||||
}
|
||||
|
||||
AmDynInvoke* cc_acc = cc_acc_fact->getInstance();
|
||||
if(!cc_acc) {
|
||||
ERROR("could not get an accounting reference\n");
|
||||
throw AmSession::Exception(500,"could not get an acc reference");
|
||||
}
|
||||
|
||||
return new SWPrepaidSIPDialog(cc_acc, user_timer);
|
||||
}
|
||||
|
||||
|
||||
SWPrepaidSIPDialog::SWPrepaidSIPDialog(AmDynInvoke* cc_acc, AmDynInvoke* user_timer)
|
||||
: m_state(CC_Init),
|
||||
m_cc_acc(cc_acc), m_user_timer(user_timer),
|
||||
AmB2BCallerSession()
|
||||
|
||||
{
|
||||
sip_relay_only = false;
|
||||
memset(&m_acc_start, 0, sizeof(struct timeval));
|
||||
}
|
||||
|
||||
|
||||
SWPrepaidSIPDialog::~SWPrepaidSIPDialog()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::onInvite(const AmSipRequest& req)
|
||||
{
|
||||
// TODO: do reinvites get here? if so, don't set a timer then
|
||||
|
||||
// TODO: errors thrown as exception don't seem to trigger a reply?
|
||||
|
||||
setReceiving(false);
|
||||
AmMediaProcessor::instance()->removeSession(this);
|
||||
|
||||
m_uuid = getHeader(req.hdrs,"P-Caller-Uuid");
|
||||
if(!m_uuid.length()) {
|
||||
ERROR("Application header P-Caller-Uuid not found\n");
|
||||
throw AmSession::Exception(500, "could not get UUID parameter");
|
||||
}
|
||||
|
||||
m_proxy = getHeader(req.hdrs,"P-Proxy");
|
||||
if(!m_proxy.length()) {
|
||||
ERROR("Application header P-Proxy not found\n");
|
||||
throw AmSession::Exception(500, "could not get PROXY parameter");
|
||||
}
|
||||
|
||||
m_ruri = getHeader(req.hdrs,"P-R-Uri");
|
||||
if(!m_ruri.length()) {
|
||||
ERROR("Application header P-R-Uri not found\n");
|
||||
throw AmSession::Exception(500, "could not get RURI parameter");
|
||||
}
|
||||
|
||||
m_dest = getHeader(req.hdrs,"P-Acc-Dest");
|
||||
|
||||
if(!m_dest.length()) {
|
||||
ERROR("Application header P-Acc-Dest not found\n");
|
||||
throw AmSession::Exception(500, "could not get destination pattern parameter");
|
||||
}
|
||||
|
||||
DBG("UUID '%s' and pattern '%s' prepared for prepaid processing\n", m_uuid.c_str(), m_dest.c_str());
|
||||
|
||||
m_starttime = time(NULL);
|
||||
m_localreq = req;
|
||||
|
||||
AmArg di_args,ret;
|
||||
di_args.push(m_uuid.c_str());
|
||||
di_args.push((int)m_starttime);
|
||||
di_args.push(m_dest.c_str());
|
||||
m_cc_acc->invoke("getCredit", di_args, ret);
|
||||
m_credit = ret.get(0).asInt();
|
||||
|
||||
if(m_credit == -1) {
|
||||
ERROR("Failed to fetch credit from accounting module\n");
|
||||
if(dlg.reply(req, 500, "Failed to fetch credit") != 0) {
|
||||
throw AmSession::Exception(500,"Failed to fetch credit");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if(m_credit == 0) {
|
||||
DBG("No credit left\n");
|
||||
m_state = CC_Teardown;
|
||||
if(dlg.reply(req, 402, "Unsufficient Credit") != 0) {
|
||||
throw AmSession::Exception(500,"Failed to reply 402");
|
||||
}
|
||||
setStopped();
|
||||
//throw AmSession::Exception(402, "Unsufficient Credit");
|
||||
}
|
||||
else {
|
||||
DBG("Credit for UUID %s is %d seconds, calling %s now\n", m_uuid.c_str(), m_credit, req.r_uri.c_str());
|
||||
m_state = CC_Dialing;
|
||||
|
||||
if(dlg.reply(req, 101, "Connecting") != 0) {
|
||||
throw AmSession::Exception(500,"Failed to reply 101");
|
||||
}
|
||||
|
||||
invite_req = req;
|
||||
dlg.updateStatus(req);
|
||||
recvd_req.insert(make_pair(req.cseq,req));
|
||||
|
||||
connectCallee("<" + m_ruri + ">", m_proxy + ";sw_prepaid", true);
|
||||
sip_relay_only = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::process(AmEvent* ev)
|
||||
{
|
||||
AmAudioEvent* audio_ev = dynamic_cast<AmAudioEvent*>(ev);
|
||||
if(audio_ev && (audio_ev->event_id == AmAudioEvent::noAudio) && m_state == CC_Teardown) {
|
||||
DBG("SWPrepaidSIPDialog::process: Playlist is empty!\n");
|
||||
terminateLeg();
|
||||
|
||||
ev->processed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(ev);
|
||||
if(plugin_event && plugin_event->name == "timer_timeout") {
|
||||
int timer_id = plugin_event->data.get(0).asInt();
|
||||
if (timer_id == TIMERID_CREDIT_TIMEOUT) {
|
||||
DBG("timer timeout, no more credit\n");
|
||||
stopAccounting();
|
||||
terminateOtherLeg();
|
||||
terminateLeg();
|
||||
|
||||
ev->processed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AmB2BCallerSession::process(ev);
|
||||
}
|
||||
|
||||
|
||||
bool SWPrepaidSIPDialog::onOtherReply(const AmSipReply& reply)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (m_state == CC_Dialing) {
|
||||
if (reply.code < 200) {
|
||||
DBG("Callee is trying... code %d\n", reply.code);
|
||||
}
|
||||
else if(reply.code < 300) {
|
||||
if(getCalleeStatus() == Connected) {
|
||||
m_state = CC_Connected;
|
||||
startAccounting();
|
||||
setInOut(NULL, NULL);
|
||||
|
||||
// set the call timer
|
||||
AmArg di_args,ret;
|
||||
di_args.push(TIMERID_CREDIT_TIMEOUT);
|
||||
di_args.push(m_credit); // in seconds
|
||||
di_args.push(dlg.local_tag.c_str());
|
||||
m_user_timer->invoke("setTimer", di_args, ret);
|
||||
}
|
||||
}
|
||||
else if(reply.code == 487 && dlg.getStatus() == AmSipDialog::Pending) {
|
||||
DBG("Canceling leg A on 487 from B");
|
||||
dlg.reply(m_localreq, 487, "Call terminated");
|
||||
setStopped();
|
||||
ret = true;
|
||||
}
|
||||
else if (reply.code >= 300 && dlg.getStatus() == AmSipDialog::Connected) {
|
||||
DBG("Callee final error in connected state with code %d\n",reply.code);
|
||||
terminateLeg();
|
||||
}
|
||||
else {
|
||||
DBG("Callee final error with code %d\n",reply.code);
|
||||
AmB2BCallerSession::onOtherReply(reply);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::onOtherBye(const AmSipRequest& req)
|
||||
{
|
||||
stopAccounting();
|
||||
AmB2BCallerSession::onOtherBye(req);
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::onBye(const AmSipRequest& req)
|
||||
{
|
||||
if (m_state == CC_Connected) {
|
||||
stopAccounting();
|
||||
}
|
||||
terminateOtherLeg();
|
||||
setStopped();
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::onCancel()
|
||||
{
|
||||
if(dlg.getStatus() == AmSipDialog::Pending) {
|
||||
DBG("Wait for leg B to terminate");
|
||||
}
|
||||
else {
|
||||
DBG("Canceling leg A on CANCEL since dialog is not pending");
|
||||
dlg.reply(m_localreq, 487, "Call terminated");
|
||||
setStopped();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::startAccounting()
|
||||
{
|
||||
gettimeofday(&m_acc_start, NULL);
|
||||
DBG("start accounting at %ld\n", m_acc_start.tv_sec);
|
||||
}
|
||||
|
||||
|
||||
void SWPrepaidSIPDialog::stopAccounting()
|
||||
{
|
||||
if(m_acc_start.tv_sec != 0 || m_acc_start.tv_usec != 0) {
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
timersub(&now, &m_acc_start, &now);
|
||||
if(now.tv_usec > 500000)
|
||||
now.tv_sec++;
|
||||
DBG("Call lasted %ld seconds\n", now.tv_sec);
|
||||
|
||||
AmArg di_args,ret;
|
||||
di_args.push(m_uuid.c_str());
|
||||
di_args.push((int)m_starttime);
|
||||
di_args.push((int)now.tv_sec);
|
||||
di_args.push(m_dest.c_str());
|
||||
m_cc_acc->invoke("subtractCredit", di_args, ret);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2007 Sipwise GmbH
|
||||
* Based on the concept of mycc, Copyright (C) 2002-2003 Fhg Fokus
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 _SW_PREPAID_SIP_H
|
||||
#define _SW_PREPAID_SIP_H
|
||||
|
||||
#include "AmB2BSession.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
class SWPrepaidSIPFactory: public AmSessionFactory
|
||||
{
|
||||
AmDynInvokeFactory* user_timer_fact;
|
||||
AmDynInvokeFactory* cc_acc_fact;
|
||||
|
||||
public:
|
||||
SWPrepaidSIPFactory(const string& _app_name);
|
||||
|
||||
int onLoad();
|
||||
AmSession* onInvite(const AmSipRequest& req);
|
||||
};
|
||||
|
||||
class SWPrepaidSIPDialog : public AmB2BCallerSession
|
||||
{
|
||||
enum {
|
||||
CC_Init = 0,
|
||||
CC_Dialing,
|
||||
CC_Connected,
|
||||
CC_Teardown
|
||||
} CallerState;
|
||||
|
||||
int m_state;
|
||||
AmSipRequest m_localreq;
|
||||
|
||||
string m_uuid;
|
||||
string m_ruri;
|
||||
string m_proxy;
|
||||
string m_dest;
|
||||
time_t m_starttime;
|
||||
int m_credit;
|
||||
|
||||
void startAccounting();
|
||||
void stopAccounting();
|
||||
struct timeval m_acc_start;
|
||||
|
||||
AmDynInvoke* m_user_timer;
|
||||
AmDynInvoke* m_cc_acc;
|
||||
|
||||
public:
|
||||
|
||||
SWPrepaidSIPDialog(AmDynInvoke* cc_acc, AmDynInvoke* user_timer);
|
||||
~SWPrepaidSIPDialog();
|
||||
|
||||
void process(AmEvent* ev);
|
||||
void onBye(const AmSipRequest& req);
|
||||
void onInvite(const AmSipRequest& req);
|
||||
void onCancel();
|
||||
|
||||
protected:
|
||||
|
||||
bool onOtherReply(const AmSipReply& reply);
|
||||
void onOtherBye(const AmSipRequest& req);
|
||||
};
|
||||
#endif // _SW_PREPAID_SIP_H
|
||||
@ -0,0 +1,2 @@
|
||||
# the plugin to be used as accounting backend:
|
||||
acc_plugin = cc_acc
|
||||
@ -0,0 +1,20 @@
|
||||
#######################################################################
|
||||
#
|
||||
# click2dial, an xmlrpc-enabled way to initiate authenticated calls
|
||||
# Copyright (C) 2007 Sipwise GmbH
|
||||
# Based on "announcement", Copyright (C) 2002-2003 Fhg Fokus
|
||||
#
|
||||
# Author: Andreas Granig <agranig@sipwise.com>
|
||||
#
|
||||
########################################################################
|
||||
|
||||
How click2dial works
|
||||
------------------------
|
||||
|
||||
Simply issue an xmlrpc request as shown in apps/click2dial/example.py
|
||||
to initiate an authenticated call.
|
||||
|
||||
First, call leg (a) is connected, and an announcemnet defined in
|
||||
click2dial.conf is played. Then call leg (b) is contacted, and on success
|
||||
the two call legs are connected.
|
||||
|
||||
@ -0,0 +1,176 @@
|
||||
#######################################################################
|
||||
#
|
||||
# sw_prepaid_sip, the signalling-only prepaid engine
|
||||
# Copyright (C) 2007 Sipwise GmbH
|
||||
# Based on mycc, Copyright (C) 2002-2003 Fhg Fokus
|
||||
#
|
||||
# Author: Andreas Granig <agranig@sipwise.com>
|
||||
#
|
||||
########################################################################
|
||||
|
||||
How sw_prepaid_sip works
|
||||
------------------------
|
||||
|
||||
The plugin extracts the headers "P-Caller-Uuid" and "P-Acc-Dest" as
|
||||
caller and callee identifiers, respectively. Using these values,
|
||||
it calls an accounting backend, which can defined in sw_prepaid_sip.conf,
|
||||
and fetches the maximum call duration (per default, cc_acc is used as
|
||||
billing backend, which is just a dummy plugin to show the most basic
|
||||
concept of a billing backend).
|
||||
|
||||
If the credit is > 0, it uses the Request-Uri passed in the header "P-R-Uri"
|
||||
and connects the callee using the outbound proxy passed in the header "P-Proxy".
|
||||
|
||||
When the call duration exceeds the credit, the call is terminated by SEMS.
|
||||
|
||||
|
||||
A typical call flow would look like this, where (a) is the caller leg and
|
||||
(b) the callee leg:
|
||||
|
||||
Caller Proxy SEMS Callee
|
||||
| | | |
|
||||
| INV(a) | | |
|
||||
|--------->| | |
|
||||
| 407(a) | | |
|
||||
|<---------| | |
|
||||
| INV(a) | | |
|
||||
|--------->| | |
|
||||
| 100(a) | | |
|
||||
|<---------| | |
|
||||
| | INV(a) | |
|
||||
| |--------->| |
|
||||
| | 101(a) | |
|
||||
| |<---------| |
|
||||
| | INV(b) | |
|
||||
| |<---------| |
|
||||
| | INV(b) | |
|
||||
| |-------------------->|
|
||||
| | | 180(b) |
|
||||
| |<--------------------|
|
||||
| | 180(b) | |
|
||||
| |--------->| |
|
||||
| | 180(a) | |
|
||||
| |<---------| |
|
||||
| 180(a) | | |
|
||||
|<---------| | |
|
||||
| | | 200(b) |
|
||||
| |<--------------------|
|
||||
| | 200(b) | |
|
||||
| |--------->| |
|
||||
| | 200(a) | |
|
||||
| |<---------| |
|
||||
| 200(a) | | |
|
||||
|<---------| | |
|
||||
| ACK(a) | | |
|
||||
|--------->| | |
|
||||
| | ACK(a) | |
|
||||
| |--------->| |
|
||||
| | ACK(b) | |
|
||||
| |<---------| |
|
||||
| | ACK(b) | |
|
||||
| |-------------------->|
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
|
||||
How to configure SEMS' sip proxy
|
||||
--------------------------------
|
||||
|
||||
Modify the "tw_append" parameter of the "tm" option to pass the mandatory headers to
|
||||
SEMS:
|
||||
|
||||
modparam("tm", "tw_append",
|
||||
"prepaid_headers:hdr[P-Caller-Uuid];hdr[P-Proxy];hdr[P-R-Uri];hdr[P-Acc-Dest]")
|
||||
|
||||
Then add a proper block to pass prepaid calls to SEMS. In this example,
|
||||
we assume that calls prefixed by "pre" are considered prepaid requests.
|
||||
For ser-0.9.6 as SEMS' sip proxy, use something like:
|
||||
|
||||
if(uri =~ "sip:pre.+@")
|
||||
{
|
||||
strip(3);
|
||||
if(!t_write_unix("/tmp/sems.sock","sw_prepaid_sip/prepaid_headers"))
|
||||
{
|
||||
xlog("L_ERR", "Error contacting SEMS - M=%rm R=%ru F=%fu T=%tu ID=%ci\n");
|
||||
t_reply("500","Could not contact B2BUA server");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
If you need to pass other headers from leg (a) to leg (b), you can add them to "tw_append"
|
||||
and they are copied to the other leg. This is for example necessary to pass the destination
|
||||
URI to leg (b), which can then be extracted from the proxy and set accordingly.
|
||||
|
||||
How to configure a proxy to interact with sw_prepaid_sip
|
||||
--------------------------------------------------------
|
||||
|
||||
If your proxy runs on IP 192.168.100.10:5060 and your SEMS' proxy on 192.168.100.10:5070, you
|
||||
can pass leg (a) to the prepaid engine like this (assuming you use OpenSER >= 1.2.0 as
|
||||
sip proxy):
|
||||
|
||||
if(/* caller is prepaid */)
|
||||
{
|
||||
/* use authentication user as caller uuid in prepaid engine */
|
||||
append_hf("P-Caller-Uuid: $au\r\n");
|
||||
|
||||
/* use simplified R-URI as destination pattern in prepaid engine;
|
||||
NOTE that cc_acc doesn't evaluate this field, but more
|
||||
advanced backends might do, so sw_prepaid_sip requires
|
||||
this header */
|
||||
append_hf("P-Acc-Dest: $rU@$rd\r\n");
|
||||
|
||||
/* the URI which is to be used by SEMS as outbound proxy for
|
||||
call leg (b) - use our own here */
|
||||
append_hf("P-Proxy: sip:192.168.100.10:5060\r\n");
|
||||
|
||||
/* the R-URI to be used to connect call leg (b) */
|
||||
append_hf("P-R-Uri: $ru\r\n");
|
||||
|
||||
if($du != null)
|
||||
{
|
||||
/* if there's a D-Uri set, also send it to SEMS to correctly contact
|
||||
call leg (b). NOTE you have to add this header in SEMS's sip proxy
|
||||
config as "tw_append" value. */
|
||||
append_hf("P-D-Uri: $du\r\n");
|
||||
}
|
||||
|
||||
/* point the R-Uri to SEMS' proxy and relay; this is just the most simple
|
||||
example, but you can also use the LCR module for SEMS load balancing
|
||||
and failover. */
|
||||
rewriteuri("sip:192.168.100.10:5070");
|
||||
t_relay();
|
||||
}
|
||||
|
||||
In the above block, call leg (a) is passed to SEMS. Now we prepare a block to catch
|
||||
call leg (b) coming from SEMS. Add this at the very beginning of your proxy configuration.
|
||||
|
||||
/* usual sanity checks like msg-size and max-fwd, then do something like: */
|
||||
|
||||
if(src_ip == 192.168.100.10 && src_port == 5070 /* check if from SEMS */
|
||||
&& uri =~ ";sw_prepaid" /* this uri param is added by SEMS to detect prepaid calls */
|
||||
&& method == "INVITE" && !has_totag()) /* only process initial invites here */
|
||||
{
|
||||
/* recover the R-Uri (maybe check for existence first!) */
|
||||
$ru = $hdr(P-R-Uri);
|
||||
|
||||
/* recover destination Uri */
|
||||
if(is_present_hf("P-D-Uri"))
|
||||
{
|
||||
$du = $hdr(P-D-Uri);
|
||||
insert_hf("Route: <$hdr(P-D-Uri)>\r\n");
|
||||
}
|
||||
|
||||
/* you might also consider removing the helper headers
|
||||
by calling remove_hf(...) */
|
||||
|
||||
/* relay to callee */
|
||||
t_relay();
|
||||
}
|
||||
|
||||
Please note that the above example is a very basic one. For example, if you want to route
|
||||
to PSTN gateways, you might want to add an informational header in leg (a) and evaluate
|
||||
it in leg (b) to load a set of PSTN gateways instead of relaying to the R-Uri.
|
||||
|
||||
IMPORTANT: make sure to prevent black-hats from spoofing the SEMS proxy address to place
|
||||
unauthenticated calls by using a private address for SEMS' sip proxy and by adding
|
||||
proper firewall rules on your ingress router.
|
||||
Loading…
Reference in new issue