/* * Copyright (C) 2010-2011 Stefan Sayer * * 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 */ /* SBC - feature-wishlist - accounting (MySQL DB, cassandra DB) - RTP transcoding mode (bridging) - overload handling (parallel call to target thresholds) - call distribution - select profile on monitoring in-mem DB record */ #include "SBC.h" #include "SBCCallControlAPI.h" #include "log.h" #include "AmUtils.h" #include "AmAudio.h" #include "AmPlugIn.h" #include "AmMediaProcessor.h" #include "AmConfigReader.h" #include "AmSessionContainer.h" #include "AmSipHeaders.h" #include "SBCSimpleRelay.h" #include "RegisterDialog.h" #include "SubscriptionDialog.h" #include "sip/pcap_logger.h" #include "sip/sip_parser.h" #include "sip/sip_trans.h" #include "HeaderFilter.h" #include "ParamReplacer.h" #include "SDPFilter.h" #include "SBCCallLeg.h" #include "AmEventQueueProcessor.h" #include "SubscriptionDialog.h" #include "RegisterDialog.h" #include "RegisterCache.h" #include using std::map; #define MOD_NAME "sbc" EXPORT_MODULE_FACTORY(SBCFactory); DEFINE_MODULE_INSTANCE(SBCFactory, MOD_NAME); // helper functions bool getCCInterfaces(CCInterfaceListT& cc_interfaces, vector& cc_modules) { for (CCInterfaceListIteratorT cc_it = cc_interfaces.begin(); cc_it != cc_interfaces.end(); cc_it++) { string& cc_module = cc_it->cc_module; if (cc_module.empty()) { ERROR("using call control but empty cc_module for '%s'!\n", cc_it->cc_name.c_str()); return false; } AmDynInvokeFactory* cc_fact = AmPlugIn::instance()->getFactory4Di(cc_module); if (NULL == cc_fact) { ERROR("cc_module '%s' not loaded\n", cc_module.c_str()); return false; } AmDynInvoke* cc_di = cc_fact->getInstance(); if(NULL == cc_di) { ERROR("could not get a DI reference\n"); return false; } cc_modules.push_back(cc_di); } return true; } void assertEndCRLF(string& s) { if (s[s.size()-2] != '\r' || s[s.size()-1] != '\n') { while ((s[s.size()-1] == '\r') || (s[s.size()-1] == '\n')) s.erase(s.size()-1); s += "\r\n"; } } /////////////////////////////////////////////////////////////////////////////////////////// SBCCallLeg* CallLegCreator::create(const SBCCallProfile& call_profile) { return new SBCCallLeg(call_profile, new AmSipDialog()); } SBCCallLeg* CallLegCreator::create(SBCCallLeg* caller) { return new SBCCallLeg(caller); } SimpleRelayCreator::Relay SimpleRelayCreator::createRegisterRelay(SBCCallProfile& call_profile, vector &cc_modules) { return SimpleRelayCreator::Relay(new RegisterDialog(call_profile, cc_modules), new RegisterDialog(call_profile, cc_modules)); } SimpleRelayCreator::Relay SimpleRelayCreator::createSubscriptionRelay(SBCCallProfile& call_profile, vector &cc_modules) { return SimpleRelayCreator::Relay(new SubscriptionDialog(call_profile, cc_modules), new SubscriptionDialog(call_profile, cc_modules)); } SimpleRelayCreator::Relay SimpleRelayCreator::createGenericRelay(SBCCallProfile& call_profile, vector &cc_modules) { return SimpleRelayCreator::Relay(new SimpleRelayDialog(call_profile, cc_modules), new SimpleRelayDialog(call_profile, cc_modules)); } SBCFactory::SBCFactory(const string& _app_name) : AmSessionFactory(_app_name), AmDynInvokeFactory(_app_name), core_options_handling(false), callLegCreator(new CallLegCreator()), simpleRelayCreator(new SimpleRelayCreator()) { } SBCFactory::~SBCFactory() { RegisterCache::dispose(); } int SBCFactory::onLoad() { if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) { ERROR("No configuration for sbc present (%s)\n", (AmConfig::ModConfigPath + string(MOD_NAME ".conf")).c_str() ); return -1; } string load_cc_plugins = cfg.getParameter("load_cc_plugins"); if (!load_cc_plugins.empty()) { INFO("loading call control plugins '%s' from '%s'\n", load_cc_plugins.c_str(), AmConfig::PlugInPath.c_str()); if (AmPlugIn::instance()->load(AmConfig::PlugInPath, load_cc_plugins) < 0) { ERROR("loading call control plugins '%s' from '%s'\n", load_cc_plugins.c_str(), AmConfig::PlugInPath.c_str()); return -1; } } session_timer_fact = AmPlugIn::instance()->getFactory4Seh("session_timer"); if(!session_timer_fact) { WARN("session_timer plug-in not loaded - " "SIP Session Timers will not be supported\n"); } vector profiles_names = explode(cfg.getParameter("profiles"), ","); for (vector::iterator it = profiles_names.begin(); it != profiles_names.end(); it++) { string profile_file_name = AmConfig::ModConfigPath + *it + ".sbcprofile.conf"; if (!call_profiles[*it].readFromConfiguration(*it, profile_file_name)) { ERROR("configuring SBC call profile from '%s'\n", profile_file_name.c_str()); return -1; } } active_profile = explode(cfg.getParameter("active_profile"), ","); if (active_profile.empty()) { ERROR("active_profile not set.\n"); return -1; } string active_profile_s; for (vector::iterator it = active_profile.begin(); it != active_profile.end(); it++) { if (it->empty()) continue; if (((*it)[0] != '$') && call_profiles.find(*it) == call_profiles.end()) { ERROR("call profile active_profile '%s' not loaded!\n", it->c_str()); return -1; } active_profile_s+=*it; if (it != active_profile.end()-1) active_profile_s+=", "; } INFO("SBC: active profile: '%s'\n", active_profile_s.c_str()); vector regex_maps = explode(cfg.getParameter("regex_maps"), ","); for (vector::iterator it = regex_maps.begin(); it != regex_maps.end(); it++) { string regex_map_file_name = AmConfig::ModConfigPath + *it + ".conf"; RegexMappingVector v; if (!read_regex_mapping(regex_map_file_name, "=>", ("SBC regex mapping " + *it+":").c_str(), v)) { ERROR("reading regex mapping from '%s'\n", regex_map_file_name.c_str()); return -1; } regex_mappings.setRegexMap(*it, v); INFO("loaded regex mapping '%s'\n", it->c_str()); } core_options_handling = cfg.getParameter("core_options_handling") == "yes"; DBG("OPTIONS messages handled by the core: %s\n", core_options_handling?"yes":"no"); if (!AmPlugIn::registerApplication(MOD_NAME, this)) { ERROR("registering " MOD_NAME " application\n"); return -1; } if (!AmPlugIn::registerDIInterface(MOD_NAME, this)) { ERROR("registering " MOD_NAME " DI interface\n"); return -1; } subnot_processor.addThreads(cfg.getParameterInt("out_of_dialog_threads", DEFAULT_OOD_THREADS)); RegisterCache::instance()->start(); return 0; } /** get the first matching profile name from active profiles */ SBCCallProfile* SBCFactory::getActiveProfileMatch(const AmSipRequest& req, ParamReplacerCtx& ctx) { string profile, profile_rule; vector::const_iterator it = active_profile.begin(); for (; it != active_profile.end(); it++) { if (it->empty()) continue; if (*it == "$(paramhdr)") profile = get_header_keyvalue(ctx.app_param,"profile"); else if (*it == "$(ruri.user)") profile = req.user; else profile = ctx.replaceParameters(*it, "active_profile", req); if (!profile.empty()) { profile_rule = *it; break; } } DBG("active profile = %s\n", profile.c_str()); map::iterator prof_it = call_profiles.find(profile); if (prof_it==call_profiles.end()) { ERROR("could not find call profile '%s'" " (matching active_profile rule: '%s')\n", profile.c_str(), profile_rule.c_str()); return NULL; } DBG("using call profile '%s' (from matching active_profile rule '%s')\n", profile.c_str(), profile_rule.c_str()); return &prof_it->second; } AmSession* SBCFactory::onInvite(const AmSipRequest& req, const string& app_name, const map& app_params) { ParamReplacerCtx ctx; ctx.app_param = getHeader(req.hdrs, PARAM_HDR, true); profiles_mut.lock(); const SBCCallProfile* p_call_profile = getActiveProfileMatch(req, ctx); if(!p_call_profile) { profiles_mut.unlock(); throw AmSession::Exception(500,SIP_REPLY_SERVER_INTERNAL_ERROR); } const SBCCallProfile& call_profile = *p_call_profile; if(!call_profile.refuse_with.empty()) { if(call_profile.refuse(ctx, req) < 0) { profiles_mut.unlock(); throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); } profiles_mut.unlock(); return NULL; } SBCCallLeg* b2b_dlg = callLegCreator->create(call_profile); msg_logger* logger = b2b_dlg->getCallProfile().get_logger(req); if (logger && call_profile.log_sip) req.log(logger); if (call_profile.auth_aleg_enabled) { // adding auth handler AmSessionEventHandlerFactory* uac_auth_f = AmPlugIn::instance()->getFactory4Seh("uac_auth"); if (NULL == uac_auth_f) { INFO("uac_auth module not loaded. uac auth for caller session NOT enabled.\n"); } else { AmSessionEventHandler* h = uac_auth_f->getHandler(b2b_dlg); // we cannot use the generic AmSessionEventHandler hooks, // because the hooks don't work in AmB2BSession b2b_dlg->setAuthHandler(h); DBG("uac auth enabled for caller session.\n"); } } profiles_mut.unlock(); return b2b_dlg; } /** out-of-dialog request handling terminated */ void oodHandlingTerminated(const AmSipRequest &req, vector& cc_modules, SBCCallProfile& call_profile) { for (vector::iterator m = cc_modules.begin(); m != cc_modules.end(); ++m) { AmArg args,ret; args.push((AmObject*)&call_profile); args.push((AmObject*)&req); try { (*m)->invoke("ood_handling_terminated", args, ret); } catch (...) { /* ignore */ } } } void SBCFactory::onOoDRequest(const AmSipRequest& req) { DBG("processing message %s %s\n", req.method.c_str(), req.r_uri.c_str()); if (core_options_handling && req.method == SIP_METH_OPTIONS) { DBG("processing OPTIONS in core\n"); AmSessionFactory::onOoDRequest(req); return; } profiles_mut.lock(); ParamReplacerCtx ctx; ctx.app_param = getHeader(req.hdrs, PARAM_HDR, true); string profile_rule; const SBCCallProfile* p_call_profile = getActiveProfileMatch(req, ctx); if(!p_call_profile) { profiles_mut.unlock(); throw AmSession::Exception(500,SIP_REPLY_SERVER_INTERNAL_ERROR); } SBCCallProfile call_profile(*p_call_profile); profiles_mut.unlock(); msg_logger* logger = call_profile.get_logger(req); if (logger && call_profile.log_sip) req.log(logger); ctx.call_profile = &call_profile; call_profile.eval_cc_list(ctx,req); vector cc_modules; if(!getCCInterfaces(call_profile.cc_interfaces,cc_modules)) { ERROR("could not get CC interfaces\n"); return; } // fix up variables call_profile.replace_cc_values(ctx,req,NULL); if(!SBCFactory::CCRoute(req,cc_modules,call_profile)) { oodHandlingTerminated(req, cc_modules, call_profile); //ERROR("routing failed\n"); return; } if (!call_profile.refuse_with.empty()) { oodHandlingTerminated(req, cc_modules, call_profile); if(call_profile.refuse(ctx, req) < 0) { throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); } return; } if(req.max_forwards == 0) { AmSipDialog::reply_error(req, 483, SIP_REPLY_TOO_MANY_HOPS); return; } call_profile.fix_append_hdrs(ctx, req); SimpleRelayCreator::Relay relay(NULL,NULL); if(req.method == SIP_METH_REGISTER) { relay = simpleRelayCreator->createRegisterRelay(call_profile, cc_modules); } else if((req.method == SIP_METH_SUBSCRIBE) || (req.method == SIP_METH_REFER)){ relay = simpleRelayCreator->createSubscriptionRelay(call_profile, cc_modules); } else { relay = simpleRelayCreator->createGenericRelay(call_profile, cc_modules); } if (call_profile.log_sip) { relay.first->setMsgLogger(call_profile.get_logger(req)); relay.second->setMsgLogger(call_profile.get_logger(req)); } if(SBCSimpleRelay::start(relay,req,call_profile)) { AmSipDialog::reply_error(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR, "", call_profile.log_sip ? call_profile.get_logger(req): NULL); delete relay.first; delete relay.second; } } void SBCFactory::invoke(const string& method, const AmArg& args, AmArg& ret) { if (method == "listProfiles"){ listProfiles(args, ret); } else if (method == "reloadProfiles"){ reloadProfiles(args,ret); } else if (method == "loadProfile"){ args.assertArrayFmt("u"); loadProfile(args,ret); } else if (method == "reloadProfile"){ args.assertArrayFmt("u"); reloadProfile(args,ret); } else if (method == "getActiveProfile"){ getActiveProfile(args,ret); } else if (method == "setActiveProfile"){ args.assertArrayFmt("u"); setActiveProfile(args,ret); } else if (method == "getRegexMapNames"){ getRegexMapNames(args,ret); } else if (method == "setRegexMap"){ args.assertArrayFmt("u"); setRegexMap(args,ret); } else if (method == "loadCallcontrolModules"){ args.assertArrayFmt("s"); loadCallcontrolModules(args,ret); } else if (method == "postControlCmd"){ args.assertArrayFmt("ss"); // at least call-ltag, cmd postControlCmd(args,ret); } else if(method == "_list"){ ret.push(AmArg("listProfiles")); ret.push(AmArg("reloadProfiles")); ret.push(AmArg("reloadProfile")); ret.push(AmArg("loadProfile")); ret.push(AmArg("getActiveProfile")); ret.push(AmArg("setActiveProfile")); ret.push(AmArg("getRegexMapNames")); ret.push(AmArg("setRegexMap")); ret.push(AmArg("loadCallcontrolModules")); ret.push(AmArg("postControlCmd")); ret.push(AmArg("printCallStats")); } else if(method == "printCallStats"){ B2BMediaStatistics::instance()->getReport(args, ret); } else throw AmDynInvoke::NotImplemented(method); } void SBCFactory::listProfiles(const AmArg& args, AmArg& ret) { profiles_mut.lock(); for (std::map::iterator it= call_profiles.begin(); it != call_profiles.end(); it++) { AmArg p; p["name"] = it->first; p["md5"] = it->second.md5hash; p["path"] = it->second.profile_file; ret.push((p)); } profiles_mut.unlock(); } void SBCFactory::reloadProfiles(const AmArg& args, AmArg& ret) { std::map new_call_profiles; bool failed = false; string res = "OK"; AmArg profile_list; profiles_mut.lock(); for (std::map::iterator it= call_profiles.begin(); it != call_profiles.end(); it++) { new_call_profiles[it->first] = SBCCallProfile(); if (!new_call_profiles[it->first].readFromConfiguration(it->first, it->second.profile_file)) { ERROR("reading call profile file '%s'\n", it->second.profile_file.c_str()); res = "Error reading call profile for "+it->first+" from "+it->second.profile_file+ +"; no profiles reloaded"; failed = true; break; } AmArg p; p["name"] = it->first; p["md5"] = it->second.md5hash; p["path"] = it->second.profile_file; profile_list.push(p); } if (!failed) { call_profiles = new_call_profiles; ret.push(200); } else { ret.push(500); } ret.push(res); ret.push(profile_list); profiles_mut.unlock(); } void SBCFactory::reloadProfile(const AmArg& args, AmArg& ret) { bool failed = false; string res = "OK"; AmArg p; if (!args[0].hasMember("name")) { ret.push(400); ret.push("Parameters error: expected ['name': profile_name] "); return; } profiles_mut.lock(); std::map::iterator it= call_profiles.find(args[0]["name"].asCStr()); if (it == call_profiles.end()) { res = "profile '"+string(args[0]["name"].asCStr())+"' not found"; failed = true; } else { SBCCallProfile new_cp; if (!new_cp.readFromConfiguration(it->first, it->second.profile_file)) { ERROR("reading call profile file '%s'\n", it->second.profile_file.c_str()); res = "Error reading call profile for "+it->first+" from "+it->second.profile_file; failed = true; } else { it->second = new_cp; p["name"] = it->first; p["md5"] = it->second.md5hash; p["path"] = it->second.profile_file; } } profiles_mut.unlock(); if (!failed) { ret.push(200); ret.push(res); ret.push(p); } else { ret.push(500); ret.push(res); } } void SBCFactory::loadProfile(const AmArg& args, AmArg& ret) { if (!args[0].hasMember("name") || !args[0].hasMember("path")) { ret.push(400); ret.push("Parameters error: expected ['name': profile_name] " "and ['path': profile_path]"); return; } SBCCallProfile cp; if (!cp.readFromConfiguration(args[0]["name"].asCStr(), args[0]["path"].asCStr())) { ret.push(500); ret.push("Error reading sbc call profile for "+string(args[0]["name"].asCStr())+ " from file "+string(args[0]["path"].asCStr())); return; } profiles_mut.lock(); call_profiles[args[0]["name"].asCStr()] = cp; profiles_mut.unlock(); ret.push(200); ret.push("OK"); AmArg p; p["name"] = args[0]["name"]; p["md5"] = cp.md5hash; p["path"] = args[0]["path"]; ret.push(p); } void SBCFactory::getActiveProfile(const AmArg& args, AmArg& ret) { profiles_mut.lock(); AmArg p; for (vector::iterator it=active_profile.begin(); it != active_profile.end(); it++) { p["active_profile"].push(*it); } profiles_mut.unlock(); ret.push(200); ret.push("OK"); ret.push(p); } void SBCFactory::setActiveProfile(const AmArg& args, AmArg& ret) { if (!args[0].hasMember("active_profile")) { ret.push(400); ret.push("Parameters error: expected ['active_profile': ] "); return; } profiles_mut.lock(); active_profile = explode(args[0]["active_profile"].asCStr(), ","); profiles_mut.unlock(); ret.push(200); ret.push("OK"); AmArg p; p["active_profile"] = args[0]["active_profile"]; ret.push(p); } void SBCFactory::getRegexMapNames(const AmArg& args, AmArg& ret) { AmArg p; vector reg_names = regex_mappings.getNames(); for (vector::iterator it=reg_names.begin(); it != reg_names.end(); it++) { p["regex_maps"].push(*it); } ret.push(200); ret.push("OK"); ret.push(p); } void SBCFactory::setRegexMap(const AmArg& args, AmArg& ret) { if (!args[0].hasMember("name") || !args[0].hasMember("file") || !isArgCStr(args[0]["name"]) || !isArgCStr(args[0]["file"])) { ret.push(400); ret.push("Parameters error: expected ['name': , 'file': ]"); return; } string m_name = args[0]["name"].asCStr(); string m_file = args[0]["file"].asCStr(); RegexMappingVector v; if (!read_regex_mapping(m_file, "=>", "SBC regex mapping", v)) { ERROR("reading regex mapping from '%s'\n", m_file.c_str()); ret.push(401); ret.push("Error reading regex mapping from file"); return; } regex_mappings.setRegexMap(m_name, v); ret.push(200); ret.push("OK"); } void SBCFactory::loadCallcontrolModules(const AmArg& args, AmArg& ret) { string load_cc_plugins = args[0].asCStr(); if (!load_cc_plugins.empty()) { INFO("loading call control plugins '%s' from '%s'\n", load_cc_plugins.c_str(), AmConfig::PlugInPath.c_str()); if (AmPlugIn::instance()->load(AmConfig::PlugInPath, load_cc_plugins) < 0) { ERROR("loading call control plugins '%s' from '%s'\n", load_cc_plugins.c_str(), AmConfig::PlugInPath.c_str()); ret.push(500); ret.push("Failed - please see server logs\n"); return; } } ret.push(200); ret.push("OK"); } void SBCFactory::postControlCmd(const AmArg& args, AmArg& ret) { SBCControlEvent* evt; if (args.size()<3) { evt = new SBCControlEvent(args[1].asCStr()); } else { evt = new SBCControlEvent(args[1].asCStr(), args[2]); } if (!AmSessionContainer::instance()->postEvent(args[0].asCStr(), evt)) { ret.push(404); ret.push("Not found"); } else { ret.push(202); ret.push("Accepted"); } } bool SBCFactory::CCRoute(const AmSipRequest& req, vector& cc_modules, SBCCallProfile& call_profile) { vector::iterator cc_mod=cc_modules.begin(); for (CCInterfaceListIteratorT cc_it=call_profile.cc_interfaces.begin(); cc_it != call_profile.cc_interfaces.end(); cc_it++) { CCInterface& cc_if = *cc_it; AmArg di_args,ret; di_args.push(cc_if.cc_name); di_args.push(""); //getLocalTag() di_args.push((AmObject*)&call_profile); di_args.push((AmObject*)&req); di_args.push(AmArg()); di_args.back().push((int) 0); di_args.back().push((int) 0); di_args.push(AmArg()); AmArg& vals = di_args.back(); vals.assertStruct(); for (map::iterator it = cc_if.cc_values.begin(); it != cc_if.cc_values.end(); it++) { vals[it->first] = it->second; } di_args.push(0); // current timer ID try { (*cc_mod)->invoke("route", di_args, ret); } catch (const AmArg::OutOfBoundsException& e) { ERROR("OutOfBoundsException executing call control interface route " "module '%s' named '%s', parameters '%s'\n", cc_if.cc_module.c_str(), cc_if.cc_name.c_str(), AmArg::print(di_args).c_str()); AmBasicSipDialog::reply_error(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); return false; } catch (const AmArg::TypeMismatchException& e) { ERROR("TypeMismatchException executing call control interface route " "module '%s' named '%s', parameters '%s'\n", cc_if.cc_module.c_str(), cc_if.cc_name.c_str(), AmArg::print(di_args).c_str()); AmBasicSipDialog::reply_error(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); return false; } catch (...) { ERROR("Exception occured when executing call control interface route " "module '%s' named '%s', parameters '%s'\n", cc_if.cc_module.c_str(), cc_if.cc_name.c_str(), AmArg::print(di_args).c_str()); AmBasicSipDialog::reply_error(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); return false; } // evaluate ret if (isArgArray(ret)) { for (size_t i=0;i SBC_CC_REFUSE_HEADERS) { for (size_t h=0;h