From 1ce93f3d61e1b050a1ba3a4035cc68b46ed047b1 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Sat, 24 Nov 2007 12:03:15 +0000 Subject: [PATCH] callback application git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@562 8eb893ce-cfd4-0310-b710-fb5ebe64c474 --- apps/callback/CallBack.cpp | 335 ++++++++++++++++++++++++++++++++ apps/callback/CallBack.h | 140 +++++++++++++ apps/callback/Makefile | 9 + apps/callback/etc/callback.conf | 18 ++ 4 files changed, 502 insertions(+) create mode 100644 apps/callback/CallBack.cpp create mode 100644 apps/callback/CallBack.h create mode 100644 apps/callback/Makefile create mode 100644 apps/callback/etc/callback.conf diff --git a/apps/callback/CallBack.cpp b/apps/callback/CallBack.cpp new file mode 100644 index 00000000..c64fcc06 --- /dev/null +++ b/apps/callback/CallBack.cpp @@ -0,0 +1,335 @@ +/* + * $Id: CallBack.cpp 288 2007-03-28 16:32:02Z sayer $ + * + * Copyright (C) 2007 iptego GmbH + * + * This file is part of sems, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 "CallBack.h" +#include "AmUtils.h" +#include "log.h" +#include "AmUAC.h" +#include "AmPlugIn.h" + +#include + +EXPORT_SESSION_FACTORY(CallBackFactory,MOD_NAME); +string CallBackFactory::gw_user; +string CallBackFactory::gw_domain; +string CallBackFactory::auth_user; +string CallBackFactory::auth_pwd; + +CallBackFactory::CallBackFactory(const string& _app_name) + : AmSessionFactory(_app_name), + configured(false) +{ +} + +PlayoutType CallBackFactory::m_PlayoutType = ADAPTIVE_PLAYOUT; + +int CallBackFactory::onLoad() +{ + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME)+ ".conf")) + return -1; + + // get application specific global parameters + configureModule(cfg); + + // get prompts + AM_PROMPT_START; + AM_PROMPT_ADD(WELCOME_PROMPT, WELCOME_PROMPT ".wav"); + AM_PROMPT_END(prompts, cfg, MOD_NAME); + + string DigitsDir = cfg.getParameter("digits_dir"); + if (DigitsDir.length() && DigitsDir[DigitsDir.length()-1]!='/') + DigitsDir+='/'; + + if (!DigitsDir.length()) { + ERROR("No digits_dir specified in configuration.\n"); + } + for (int i=0;i<10;i++) + prompts.setPrompt(int2str(i), DigitsDir+int2str(i)+".wav", MOD_NAME); + + string playout_type = cfg.getParameter("playout_type"); + if (playout_type == "simple") { + m_PlayoutType = SIMPLE_PLAYOUT; + DBG("Using simple (fifo) buffer as playout technique.\n"); + } else if (playout_type == "adaptive_jb") { + m_PlayoutType = JB_PLAYOUT; + DBG("Using adaptive jitter buffer as playout technique.\n"); + } else { + DBG("Using adaptive playout buffer as playout technique.\n"); + } + + string accept_caller_re_str = cfg.getParameter(ACCEPT_CALLER_RE); + if (!accept_caller_re_str.length()) { + ERROR("no '" ACCEPT_CALLER_RE "' set.\n"); + return -1; + } else { + if (regcomp(&accept_caller_re, accept_caller_re_str.c_str(), + REG_EXTENDED|REG_NOSUB)) { + ERROR("unable to compile caller RE '%s'.\n", + accept_caller_re_str.c_str()); + return -1; + } + } + + gw_user = cfg.getParameter("gw_user"); + if (!gw_user.length()) { + ERROR("need gw_user configured!\n"); + return -1; + } + + gw_domain = cfg.getParameter("gw_domain"); + if (!gw_domain.length()) { + ERROR("need gw_domain configured!\n"); + return -1; + } + + auth_user = cfg.getParameter("auth_user"); + if (!auth_user.length()) + auth_user = gw_user; // default to user + + auth_pwd = cfg.getParameter("auth_pwd"); + if (!auth_pwd.length()) { + ERROR("need auth_pwd configured!\n"); + return -1; + } + + cb_wait = cfg.getParameterInt("cb_wait", 5); + DBG("cb_wait set to %d\n", cb_wait); + + DBG("starting callback thread. (%ld)\n", (long)this); + start(); + + return 0; +} + +// incoming calls +AmSession* CallBackFactory::onInvite(const AmSipRequest& req) +{ + // or req.from -> with display name ? + DBG("received INVITE from '%s'\n", req.from_uri.c_str()); + if (!regexec(&accept_caller_re, req.from_uri.c_str(), 0,0,0)) { + DBG("accept_caller_re matched.\n"); + time_t now; + time(&now); + // q&d + string from_user = req.from_uri.substr(req.from_uri.find("sip:")+4); + from_user = from_user.substr(0, from_user.find("@")); + DBG("INVITE user '%s'\n", from_user.c_str()); + if (from_user.length()) { + scheduled_calls_mut.lock(); + scheduled_calls.insert(make_pair(now + cb_wait, from_user)); + scheduled_calls_mut.unlock(); + } + + DBG("inserted into callback thread. (%ld)\n", (long)this); + // or some other reason + throw AmSession::Exception(486, "Busy here (call you back l8r)"); + } else { + DBG("accept_caller_re not matched.\n"); + // or something else + throw AmSession::Exception(603, "Decline"); + } + + return 0; +} + +// outgoing calls +AmSession* CallBackFactory::onInvite(const AmSipRequest& req, + AmArg& session_params) +{ + UACAuthCred* cred = NULL; + if (session_params.getType() == AmArg::AObject) { + ArgObject* cred_obj = session_params.asObject(); + if (cred_obj) + cred = dynamic_cast(cred_obj); + } + + AmSession* s = new CallBackDialog(prompts, cred); + addAuthHandler(s); + return s; +} + +// this could have been made easier with a user_timer. +void CallBackFactory::run() { + DBG("running CallBack thread.\n"); + while (true) { + scheduled_calls_mut.lock(); + vector todo; + time_t now; + time(&now); + multimap::iterator it = + scheduled_calls.begin(); + while (it != scheduled_calls.end()) { + if (it->first > now) + break; + todo.push_back(it->second); + scheduled_calls.erase(it); + it = scheduled_calls.begin(); + } + scheduled_calls_mut.unlock(); + + for (vector::iterator it=todo.begin(); + it != todo.end(); it++) { + createCall(*it); + } + + sleep(1); + } +} + +void CallBackFactory::on_stop() { +} + +void CallBackFactory::createCall(const string& number) { + AmArg* a = new AmArg(); + a->setBorrowedPointer(new UACAuthCred("", auth_user, auth_pwd)); + + string luser = "cb"; + string to = "sip:"+ number + "@" + gw_domain; + string from = "sip:"+ gw_user + "@" + gw_domain; + + AmUAC::dialout(luser, + MOD_NAME, + to, + "<" + from + ">", from, + "<" + to + ">", + string(""), // local tag + string("X-Extra: fancy\r\n"), // hdrs + a); +} + +CallBackDialog::CallBackDialog(AmPromptCollection& prompts, + UACAuthCred* cred) + : play_list(this), prompts(prompts), cred(cred), + state(CBNone) +{ + // set configured playout type + rtp_str.setPlayoutType(CallBackFactory::m_PlayoutType); +} + +CallBackDialog::~CallBackDialog() +{ + prompts.cleanup((long)this); + play_list.close(false); +} + + +void CallBackDialog::onSessionStart(const AmSipRequest& req) { + ERROR("incoming calls not supported!\n"); + setStopped(); + dlg.bye(); +} + +void CallBackDialog::onSessionStart(const AmSipReply& rep) { + state = CBEnteringNumber; + prompts.addToPlaylist(WELCOME_PROMPT, (long)this, play_list); + // set the playlist as input and output + setInOut(&play_list,&play_list); +} + +void CallBackDialog::onDtmf(int event, int duration) +{ + DBG("CallBackDialog::onDtmf: event %d duration %d\n", + event, duration); + + if (CBEnteringNumber == state) { + // not yet in conference + if (event<10) { + call_number += int2str(event); + DBG("added '%s': number is now '%s'.\n", + int2str(event).c_str(), call_number.c_str()); + } else if (event==10 || event==11) { + // pound and star key + // if required add checking of pin here... + if (!call_number.length()) { + prompts.addToPlaylist(WELCOME_PROMPT, (long)this, play_list); + } else { + state = CBTellingNumber; + play_list.close(); + for (size_t i=0;i(ev); + if (audio_ev && + audio_ev->event_id == AmAudioEvent::noAudio) { + DBG("########## noAudio event #########\n"); + if (CBTellingNumber == state) { + state = CBConnecting; + string callee = "sip:" + call_number + "@" + CallBackFactory::gw_domain; + string caller = "sip:" + CallBackFactory::gw_user + "@" + CallBackFactory::gw_domain; + connectCallee(callee, callee, + caller, caller); + } + return; + } + + AmB2ABSession::process(ev); +} + +// need this to pass credentials... +AmB2ABCalleeSession* CallBackDialog::createCalleeSession() { + CallBackCalleeDialog* sess = new CallBackCalleeDialog(getLocalTag(), cred); + addAuthHandler(sess); + return sess; +} + +CallBackCalleeDialog::CallBackCalleeDialog(const string& other_tag, + UACAuthCred* cred) + : AmB2ABCalleeSession(other_tag), cred(cred) +{ + setDtmfDetectionEnabled(false); +} + +CallBackCalleeDialog::~CallBackCalleeDialog() { +} + + +void addAuthHandler(AmSession* s) { + AmSessionEventHandlerFactory* uac_auth_f = + AmPlugIn::instance()->getFactory4Seh("uac_auth"); + if (uac_auth_f != NULL) { + DBG("UAC Auth enabled for new session.\n"); + AmSessionEventHandler* h = uac_auth_f->getHandler(s); + if (h != NULL ) + s->addHandler(h); + } else { + ERROR("uac_auth interface not accessible. " + "Load uac_auth for authenticated calls.\n"); + } +} diff --git a/apps/callback/CallBack.h b/apps/callback/CallBack.h new file mode 100644 index 00000000..5a464268 --- /dev/null +++ b/apps/callback/CallBack.h @@ -0,0 +1,140 @@ +/* + * $Id: CallBack.h 288 2007-03-28 16:32:02Z sayer $ + * + * Copyright (C) 2007 iptego GmbH + * + * This file is part of sems, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 _CALLBACK_H_ +#define _CALLBACK_H_ + +#include "AmApi.h" +#include "AmB2ABSession.h" +#include "AmAudio.h" +#include "AmPlaylist.h" +#include "AmPromptCollection.h" +#include "ampi/UACAuthAPI.h" + +#include +#include +using std::map; +using std::string; + +#include +#include + +// configuration parameter names +#define WELCOME_PROMPT "welcome_prompt" +#define DIGITS_DIR "digits_dir" +#define ACCEPT_CALLER_RE "accept_caller_re" + +class CallBackFactory + : public AmSessionFactory, + public AmThread +{ + AmPromptCollection prompts; + + bool configured; + + regex_t accept_caller_re; + + multimap scheduled_calls; + AmMutex scheduled_calls_mut; + // seconds to wait before calling back + int cb_wait; + + void createCall(const string& number); + + +public: + static string gw_user; + static string gw_domain; + static string auth_user; + static string auth_pwd; + + static string DigitsDir; + static PlayoutType m_PlayoutType; + + CallBackFactory(const string& _app_name); + AmSession* onInvite(const AmSipRequest&); + AmSession* onInvite(const AmSipRequest& req, + AmArg& session_params); + int onLoad(); + + void run(); + void on_stop(); +}; + +enum CBState { + CBNone = 0, + CBEnteringNumber, + CBTellingNumber, + CBConnecting, + CBConnected +}; + +class CallBackDialog + : public AmB2ABCallerSession, + public CredentialHolder +{ + +private: + AmPlaylist play_list; + + AmPromptCollection& prompts; + + string call_number; + UACAuthCred* cred; + + CBState state; +public: + CallBackDialog(AmPromptCollection& prompts, + UACAuthCred* cred); + ~CallBackDialog(); + + void process(AmEvent* ev); + void onSessionStart(const AmSipRequest& req); + void onSessionStart(const AmSipReply& rep); + void onDtmf(int event, int duration); + + UACAuthCred* getCredentials() { return cred; } + AmB2ABCalleeSession* createCalleeSession(); + +}; + +class CallBackCalleeDialog + : public AmB2ABCalleeSession, + public CredentialHolder +{ + UACAuthCred* cred; +public: + CallBackCalleeDialog(const string& other_tag, UACAuthCred* cred); + ~CallBackCalleeDialog(); + UACAuthCred* getCredentials() { return cred; } +}; + +void addAuthHandler(AmSession* s); +#endif +// Local Variables: +// mode:C++ +// End: + diff --git a/apps/callback/Makefile b/apps/callback/Makefile new file mode 100644 index 00000000..90cfbce8 --- /dev/null +++ b/apps/callback/Makefile @@ -0,0 +1,9 @@ +plug_in_name = callback + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" + +extra_install = $(plug_in_name)_audio + +COREPATH ?=../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/callback/etc/callback.conf b/apps/callback/etc/callback.conf new file mode 100644 index 00000000..cb06a572 --- /dev/null +++ b/apps/callback/etc/callback.conf @@ -0,0 +1,18 @@ +# "please type in the number you want to call, followed by the pund key" +welcome_prompt=/usr/local/lib/sems/audio/webconference/pin_prompt.wav + +digits_dir=/usr/local/lib/sems/audio/webconference/ + +playout_type=adaptive_playout + +# regexp of allowed cb numbers +accept_caller_re=016213* + +# n sec wait before calling back +cb_wait=10 + +# GW used to call back +gw_user=somegwuser +gw_domain=sparvoip.de +auth_pwd=somesecret +#auth_user=neededifdifferentfromgw_user