Added two new plugins:

- 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-fb5ebe64c474
sayer/1.4-spce2.6
Andreas Granig 19 years ago
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…
Cancel
Save