diff --git a/apps/examples/b2b_connect/Makefile b/apps/examples/b2b_connect/Makefile new file mode 100644 index 00000000..29ea0dd8 --- /dev/null +++ b/apps/examples/b2b_connect/Makefile @@ -0,0 +1,7 @@ +plug_in_name = b2b_connect + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" + +COREPATH ?= ../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/b2b_connect/Readme.b2b_connect b/apps/examples/b2b_connect/Readme.b2b_connect new file mode 100644 index 00000000..3d7b44e9 --- /dev/null +++ b/apps/examples/b2b_connect/Readme.b2b_connect @@ -0,0 +1,28 @@ +b2b_connect application + +This application is a full back2back user agent (i.e. both signaling and +media is handled by SEMS and each user agent, caller and callee, only +see the SEMs IP address). +Thus, the outgoing call is connected in B2BUA mode with media relay ('b2abua'), +so technically the two legs are independent sip dialogs and media sessions. + +The outgoing calls (B leg) are optionally SIP authenticated, so for example, +normal gateways can be used. The uac_auth module must be loaded for this +to work. If the uac_auth module is not loaded, the outgoing calls are not +authenticated. + +P-App-Param in the INVITE sent to SEMS contains information required +to do the second call from SEMS to callee, together with the request URI. +The request URI username part should contain the user to call, while the +P-App-Param should contain the domain SEMS should add to the username, +as well as credentials (if the outgoing call must be authenticated). + +Example for SER: +R-URI: john@semsip:5070 +append_hf("P-App-Param: u=semsuser;d=ser.mydomain.com:5060;p=password\r\n"); + +This will result in sems trying to set up a call to john@ser.mydomain.com:5060. +If a 407 Authentication required is received, semsuser and 'password' will be +used as credentials. + +Improvements are of course welcome as svn diff to the semsdev mailing list. diff --git a/apps/examples/b2b_connect/b2b_connect.cpp b/apps/examples/b2b_connect/b2b_connect.cpp new file mode 100644 index 00000000..3bc82bb6 --- /dev/null +++ b/apps/examples/b2b_connect/b2b_connect.cpp @@ -0,0 +1,315 @@ +/* + * $Id$ + * + * Copyright (C) 2008 Greger Viken Teigre + * Based on auth_b2b, Copyright (C) 2008 Iptego Gmbh + * Based on the concept of mycc, Copyright (C) 2007 Sipwise GmbH + * Based on the concept of sw_prepaid_sip, 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. + * + * 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 "b2b_connect.h" + +#include "log.h" +#include "AmUtils.h" +#include "AmAudio.h" +#include "AmPlugIn.h" +#include "AmMediaProcessor.h" +//#include "AmConfigReader.h" +#include "AmSessionContainer.h" + +EXPORT_SESSION_FACTORY(b2b_connectFactory,MOD_NAME); + +b2b_connectFactory::b2b_connectFactory(const string& _app_name) +: AmSessionFactory(_app_name) +// , user_timer_fact(NULL) +{ +} + + +int b2b_connectFactory::onLoad() +{ +// AmConfigReader cfg; +// if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) +// return -1; + +// 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; +// } + + return 0; +} + + +AmSession* b2b_connectFactory::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"); +// } + + return new b2b_connectDialog(); //user_timer); +} + + +b2b_connectDialog::b2b_connectDialog() // AmDynInvoke* user_timer) +: m_state(BB_Init), + //m_user_timer(user_timer), + AmB2ABCallerSession() + +{ + rtp_str.setPlayoutType(JB_PLAYOUT); +} + +b2b_connectDialog::~b2b_connectDialog() +{ +} + +void b2b_connectDialog::onInvite(const AmSipRequest& req) +{ + // TODO: do reinvites get here? if so, don't set a timer then + // -> yes, they do. + + // TODO: errors thrown as exception don't seem to trigger a reply? + // -> only in SessionFactory::onInvite they do. todo: move the logic to + // session factory + + //setReceiving(false); + //AmMediaProcessor::instance()->removeSession(this); + + string app_param = getHeader(req.hdrs, PARAM_HDR); + + if (!app_param.length()) { + AmSession::Exception(500, "b2b_connect: parameters not found"); + } + + domain = get_header_keyvalue(app_param,"d"); + user = get_header_keyvalue(app_param,"u"); + password = get_header_keyvalue(app_param,"p"); + + from = "sip:"+user+"@"+domain; + to = "sip:"+req.user+"@"+domain; + +// DBG("-----------------------------------------------------------------\n"); +// DBG("domain = %s, user = %s, pwd = %s, from = %s, to = %s;", +// domain.c_str(), user.c_str(), password.c_str(), from.c_str(), to.c_str()); +// DBG("-----------------------------------------------------------------\n"); + + m_state = BB_Dialing; + + if(dlg.reply(req, 100, "Connecting") != 0) { + throw AmSession::Exception(500,"Failed to reply 100"); + } + + invite_req = req; + size_t pos1, pos2, hdr_start; + + if (findHeader(invite_req.hdrs,PARAM_HDR, pos1, pos2, + hdr_start)) { + while (invite_req.hdrs[pos2]=='\r' ||invite_req.hdrs[pos2]=='\n') + pos2++; + + hdr_start -= 11; //"P-App-Param" + invite_req.hdrs.erase(hdr_start, pos2-hdr_start); + } + + if (findHeader(invite_req.hdrs,"P-App-Name", pos1, pos2, + hdr_start)) { + while (invite_req.hdrs[pos2]=='\r' ||invite_req.hdrs[pos2]=='\n') + pos2++; + hdr_start -= 10; //"P-App-Name" + invite_req.hdrs.erase(hdr_start, pos2-hdr_start); + } + + dlg.updateStatus(req); + recvd_req.insert(std::make_pair(req.cseq,req)); + + connectCallee("<" + to + ">", to, from, from); +} + +void b2b_connectDialog::onSessionStart(const AmSipRequest& req) +{ + AmB2ABCallerSession::onSessionStart(req); +} + +void b2b_connectDialog::onB2ABEvent(B2ABEvent* ev) +{ + + if (ev->event_id == B2ABConnectAudio) { + // delayed processing of first INVITE request + AmSession::onInvite(invite_req); + } + AmB2ABCallerSession::onB2ABEvent(ev); +} + +void b2b_connectDialog::process(AmEvent* ev) +{ +// 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; +// } +// } + + AmAudioEvent* audio_event = dynamic_cast(ev); + + if(audio_event && (audio_event->event_id == AmAudioEvent::cleared)){ + DBG("ignoring end of prompt.\n"); + return; + } + + AmB2ABCallerSession::process(ev); +} + +void b2b_connectDialog::onDtmf(int event, int duration) +{ + DBG("DTMF event %d duration %d\n", event, duration); + return; + +} + + +void b2b_connectDialog::onBye(const AmSipRequest& req) +{ + if (m_state == BB_Connected) { +// stopAccounting(); + } + terminateOtherLeg(); + setStopped(); +} + + +void b2b_connectDialog::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(invite_req, 487, "Request terminated"); + setStopped(); + } +} + + +AmB2ABCalleeSession* b2b_connectDialog::createCalleeSession() +{ + b2b_connectCalleeSession* sess = new b2b_connectCalleeSession(getLocalTag(), user, password); + + AmSessionEventHandlerFactory* uac_auth_f = + AmPlugIn::instance()->getFactory4Seh("uac_auth"); + + if (NULL != uac_auth_f) { + DBG("UAC Auth enabled for new b2b_connect session.\n"); + AmSessionEventHandler* h = uac_auth_f->getHandler(sess); + if (h != NULL ) + sess->addHandler(h); + else { + ERROR("unable to set SIP UAC auth for new session."); + } + } else { + ERROR("unable to get SIP UAC auth." + "(uac_auth module loaded?)\n"); + } + + AmSipDialog& callee_dlg = sess->dlg; + + other_id = AmSession::getNewId(); + + callee_dlg.local_tag = other_id; + callee_dlg.callid = AmSession::getNewId() + "@" + AmConfig::LocalIP; + + // this will be overwritten by ConnectLeg event + callee_dlg.remote_party = dlg.local_party; + callee_dlg.remote_uri = dlg.local_uri; + + // if given as parameters, use these + callee_dlg.local_party = from; + callee_dlg.local_uri = from; + + DBG("Created B2BUA callee leg, From: %s\n", + from.c_str()); + + sess->start(); + + AmSessionContainer* sess_cont = AmSessionContainer::instance(); + sess_cont->addSession(other_id,sess); + return sess; +} + +b2b_connectCalleeSession::b2b_connectCalleeSession(const string& other_tag, + const string& user, const string& pwd) + : auth(NULL), + credentials("", user, pwd), // domain (realm) is unused in credentials + AmB2ABCalleeSession(other_tag) { + + rtp_str.setPlayoutType(JB_PLAYOUT); + setDtmfDetectionEnabled(false); +} + +b2b_connectCalleeSession::~b2b_connectCalleeSession() { + if (auth) + delete auth; +} + +inline UACAuthCred* b2b_connectCalleeSession::getCredentials() { + return &credentials; +} + +void b2b_connectCalleeSession::onSipReply(const AmSipReply& reply) { + AmB2ABCalleeSession::onSipReply(reply); +} + +void b2b_connectCalleeSession::onSendRequest(const string& method, const string& content_type, + const string& body, string& hdrs, int flags, unsigned int cseq) +{ + AmB2ABCalleeSession::onSendRequest(method, content_type, + body, hdrs, flags, cseq); +} + +void b2b_connectCalleeSession::onB2ABEvent(B2ABEvent* ev) +{ + + if (ev->event_id == B2ABConnectAudio) { + // delayed processing of first INVITE request + AmSession::onInvite(invite_req); + } + AmB2ABCalleeSession::onB2ABEvent(ev); +} + +void b2b_connectCalleeSession::process(AmEvent* ev) +{ + AmAudioEvent* audio_event = dynamic_cast(ev); + if(audio_event && (audio_event->event_id == AmAudioEvent::noAudio)){ + DBG("connecting audio\n"); + connectSession(); + return; + } + + AmB2ABCalleeSession::process(ev); +} diff --git a/apps/examples/b2b_connect/b2b_connect.h b/apps/examples/b2b_connect/b2b_connect.h new file mode 100644 index 00000000..664569c4 --- /dev/null +++ b/apps/examples/b2b_connect/b2b_connect.h @@ -0,0 +1,111 @@ +/* + * $Id$ + * + * Copyright (C) 2008 iptego GmbH + * Based on the concept of sw_prepaid_sip, 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 _B2B_CONNECT_H +#define _B2B_CONNECT_H + +#include "AmB2ABSession.h" +#include "ampi/UACAuthAPI.h" + +using std::string; + +class b2b_connectFactory: public AmSessionFactory +{ +/* AmDynInvokeFactory* user_timer_fact; */ + + public: + b2b_connectFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class b2b_connectDialog : public AmB2ABCallerSession +{ + enum { + BB_Init = 0, + BB_Dialing, + BB_Connected, + BB_Teardown + } CallerState; + + int m_state; + + string domain; + string user; + string password; + string from; + string to; + AmSipRequest invite_req; + +/* AmDynInvoke* m_user_timer; */ + + public: + + b2b_connectDialog(); //AmDynInvoke* user_timer); + ~b2b_connectDialog(); + + void onSessionStart(const AmSipRequest& req); + void onB2ABEvent(B2ABEvent* ev); + void process(AmEvent* ev); + void onDtmf(int event, int duration); + void onBye(const AmSipRequest& req); + void onInvite(const AmSipRequest& req); + void onCancel(); + + + protected: + + AmB2ABCalleeSession* createCalleeSession(); +}; + +class b2b_connectCalleeSession +: public AmB2ABCalleeSession, public CredentialHolder +{ + UACAuthCred credentials; + AmSessionEventHandler* auth; + AmSipRequest invite_req; + + protected: + + void onSipReply(const AmSipReply& reply); + void onSendRequest(const string& method, const string& content_type, + const string& body, string& hdrs, int flags, unsigned int cseq); + + public: + b2b_connectCalleeSession(const string& other_tag, const string& user, const string& pwd); + ~b2b_connectCalleeSession(); + void onB2ABEvent(B2ABEvent* ev); + void process(AmEvent* ev); + + inline UACAuthCred* getCredentials(); + + void setAuthHandler(AmSessionEventHandler* h) { auth = h; } +}; +#endif