From dfd03bb91cc87ca903e738c3b38949092f298e96 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Sun, 14 Oct 2007 14:14:59 +0000 Subject: [PATCH] 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 --- apps/click2dial/Click2Dial.cpp | 288 ++++++++++++++++++ apps/click2dial/Click2Dial.h | 90 ++++++ apps/click2dial/Makefile | 10 + apps/click2dial/TODO | 4 + apps/click2dial/etc/click2dial.conf | 2 + apps/click2dial/example.py | 35 +++ apps/sw_prepaid_sip/Makefile | 7 + apps/sw_prepaid_sip/SWPrepaidSIP.cpp | 319 ++++++++++++++++++++ apps/sw_prepaid_sip/SWPrepaidSIP.h | 91 ++++++ apps/sw_prepaid_sip/etc/sw_prepaid_sip.conf | 2 + doc/Readme.click2dial | 20 ++ doc/Readme.sw_prepaid_sip | 176 +++++++++++ 12 files changed, 1044 insertions(+) create mode 100644 apps/click2dial/Click2Dial.cpp create mode 100644 apps/click2dial/Click2Dial.h create mode 100644 apps/click2dial/Makefile create mode 100644 apps/click2dial/TODO create mode 100644 apps/click2dial/etc/click2dial.conf create mode 100644 apps/click2dial/example.py create mode 100644 apps/sw_prepaid_sip/Makefile create mode 100644 apps/sw_prepaid_sip/SWPrepaidSIP.cpp create mode 100644 apps/sw_prepaid_sip/SWPrepaidSIP.h create mode 100644 apps/sw_prepaid_sip/etc/sw_prepaid_sip.conf create mode 100644 doc/Readme.click2dial create mode 100644 doc/Readme.sw_prepaid_sip diff --git a/apps/click2dial/Click2Dial.cpp b/apps/click2dial/Click2Dial.cpp new file mode 100644 index 00000000..48e37f43 --- /dev/null +++ b/apps/click2dial/Click2Dial.cpp @@ -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(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"); + } + } + } +} diff --git a/apps/click2dial/Click2Dial.h b/apps/click2dial/Click2Dial.h new file mode 100644 index 00000000..dcb69fb4 --- /dev/null +++ b/apps/click2dial/Click2Dial.h @@ -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 +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 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 cred; + void setAuthHandler(); + + public: + + C2DCalleeDialog(const AmB2BCallerSession* caller, UACAuthCred* credentials = NULL); + inline UACAuthCred* getCredentials() { return cred.get(); } +}; +#endif // _CLICK_2_DIAL_H_ diff --git a/apps/click2dial/Makefile b/apps/click2dial/Makefile new file mode 100644 index 00000000..b7ec3b26 --- /dev/null +++ b/apps/click2dial/Makefile @@ -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 + diff --git a/apps/click2dial/TODO b/apps/click2dial/TODO new file mode 100644 index 00000000..75674474 --- /dev/null +++ b/apps/click2dial/TODO @@ -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). diff --git a/apps/click2dial/etc/click2dial.conf b/apps/click2dial/etc/click2dial.conf new file mode 100644 index 00000000..56a38b99 --- /dev/null +++ b/apps/click2dial/etc/click2dial.conf @@ -0,0 +1,2 @@ +announce_path=/usr/local/lib/sems/audio/ +default_announce=default_en.wav diff --git a/apps/click2dial/example.py b/apps/click2dial/example.py new file mode 100644 index 00000000..8548d463 --- /dev/null +++ b/apps/click2dial/example.py @@ -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) diff --git a/apps/sw_prepaid_sip/Makefile b/apps/sw_prepaid_sip/Makefile new file mode 100644 index 00000000..1aff8add --- /dev/null +++ b/apps/sw_prepaid_sip/Makefile @@ -0,0 +1,7 @@ +plug_in_name = sw_prepaid_sip + +module_ldflags = +module_cflags = + +COREPATH ?= ../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/sw_prepaid_sip/SWPrepaidSIP.cpp b/apps/sw_prepaid_sip/SWPrepaidSIP.cpp new file mode 100644 index 00000000..3b77c1d5 --- /dev/null +++ b/apps/sw_prepaid_sip/SWPrepaidSIP.cpp @@ -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 +#include + +#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(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(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); + } +} diff --git a/apps/sw_prepaid_sip/SWPrepaidSIP.h b/apps/sw_prepaid_sip/SWPrepaidSIP.h new file mode 100644 index 00000000..919693b8 --- /dev/null +++ b/apps/sw_prepaid_sip/SWPrepaidSIP.h @@ -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 +#include +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 diff --git a/apps/sw_prepaid_sip/etc/sw_prepaid_sip.conf b/apps/sw_prepaid_sip/etc/sw_prepaid_sip.conf new file mode 100644 index 00000000..2bd1ab1f --- /dev/null +++ b/apps/sw_prepaid_sip/etc/sw_prepaid_sip.conf @@ -0,0 +1,2 @@ +# the plugin to be used as accounting backend: +acc_plugin = cc_acc diff --git a/doc/Readme.click2dial b/doc/Readme.click2dial new file mode 100644 index 00000000..ea60a45e --- /dev/null +++ b/doc/Readme.click2dial @@ -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 +# +######################################################################## + +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. + diff --git a/doc/Readme.sw_prepaid_sip b/doc/Readme.sw_prepaid_sip new file mode 100644 index 00000000..833d01e5 --- /dev/null +++ b/doc/Readme.sw_prepaid_sip @@ -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 +# +######################################################################## + +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.