/** * register.cpp * This file is part of the YATE Project http://YATE.null.ro * * Registration, authentication, authorization and accounting from a database. * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2006 Null Team * * This program 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. * * This program 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include using namespace TelEngine; namespace { // anonymous static Configuration s_cfg(Engine::configFile("register")); static bool s_critical = false; static u_int32_t s_nextTime = 0; static int s_expire = 30; static bool s_errOffline = true; static ObjList s_handlers; static NamedList s_statusaccounts("StatusAccounts"); static HashList s_fallbacklist; class AAAHandler : public MessageHandler { YCLASS(AAAHandler,MessageHandler) public: enum { Regist, UnRegist, Auth, PreRoute, Route, Cdr, Timer, Init, DialogNotify, MWINotify, Subscribe, SubscribeTimer }; AAAHandler(const char* hname, int type, int prio = 50); virtual ~AAAHandler(); void loadAccount(); static void prepareQuery(Message& msg, const String& account, const String& query, bool results); virtual const String& name() const; virtual bool received(Message& msg); virtual bool loadQuery(); virtual void initQuery(); virtual void chkConfig(); protected: void indirectQuery(String& query); int m_type; String m_query; String m_result; String m_account; }; class CDRHandler : public AAAHandler { public: CDRHandler(const char* hname, int prio = 50); virtual ~CDRHandler(); virtual const String& name() const; virtual bool received(Message& msg); virtual bool loadQuery(); protected: String m_name; String m_queryInitialize; String m_queryUpdate; bool m_critical; }; // Base class for event notification handlers class EventNotify : public AAAHandler { public: EventNotify(const char* hname, int type, const char* event, int prio = 50); virtual ~EventNotify() {} virtual const String& name() const; virtual bool loadQuery(); protected: // Fill account/query and dispatch the message // Return the data array if valid or 0 if message dispatch fails or no data Array* queryDatabase(Message& msg, const String& notifier, unsigned int& rows); // Create a notify message, fill it with notifier, event, subscription data // and additional parameters Message* message(const String& notifier, Array& subscriptions, unsigned int row, NamedList& params); // Notify all subscribers returned from a database message inline void notifyAll(const String& notifier, Array& subscriptions, unsigned int rows, NamedList& params) { for (unsigned int row = 1; row <= rows; row++) Engine::enqueue(message(notifier,subscriptions,row,params)); } String m_name; // Section name for this handler String m_event; // Event to notify String m_querySubs; // Query used to get subscriptions }; // call.cdr. Notify subscribers to 'dialog' event on call state changes class DialogNotify : public EventNotify { public: inline DialogNotify(const char* hname, int prio = 50) : EventNotify(hname,AAAHandler::DialogNotify,"dialog",prio) {} virtual bool received(Message& msg); }; // user.notify. Notify subscribers to 'message-summary' event on user message status class MWINotify : public EventNotify { public: inline MWINotify(const char* hname, int prio = 50) : EventNotify(hname,AAAHandler::MWINotify,"message-summary",prio) {} virtual bool received(Message& msg); }; class SubscribeHandler : public AAAHandler { public: SubscribeHandler(const char* hname, int type, int prio = 50); virtual ~SubscribeHandler(); virtual const String& name() const; virtual bool received(Message& msg); virtual bool loadQuery(); protected: String m_name; String m_querySubscribe; String m_queryUnsubscribe; }; class SubscribeTimerHandler : public AAAHandler { public: SubscribeTimerHandler(const char* hname, int type, int prio = 50); virtual ~SubscribeTimerHandler(); virtual const String& name() const; virtual bool received(Message& msg); virtual bool loadQuery(); protected: String m_name; int m_expireTime; u_int32_t m_nextTime; String m_queryExpire; }; class AccountsModule; class FallBackHandler; class RegistModule : public Module { public: RegistModule(); ~RegistModule(); protected: virtual void initialize(); virtual void statusParams(String& str); virtual bool received(Message& msg, int id); private: static int getPriority(const String& name); static void addHandler(const char *name, int type); static void addHandler(AAAHandler* handler); static void addHandler(FallBackHandler* handler); bool m_init; AccountsModule *m_accountsmodule; }; class FallBackRoute : public String { public: inline FallBackRoute(const String& id) : String(id) {} // add a message to the end of the routes inline void append(Message* msg) { m_msglist.append(msg); } // get the topmost message and remove it from list inline Message* get() { return static_cast(m_msglist.remove(false)); } private: ObjList m_msglist; }; class FallBackHandler : public MessageHandler { public: enum { Answered = 100, Disconnect, Hangup }; inline FallBackHandler(const char* hname, int type, int prio = 50) : MessageHandler(hname,prio),m_type(type) { m_stoperror = s_cfg.getValue("general","stoperror"); } virtual ~FallBackHandler() { s_handlers.remove(this,false); } virtual bool received(Message &msg); private: int m_type; Regexp m_stoperror; }; class AccountsModule : public MessageReceiver { public: enum { Notify=50, Timer, }; AccountsModule(); ~AccountsModule(); protected: virtual bool received(Message &msg, int id); virtual void initialize(); private: bool m_init; u_int32_t m_nextTime; String m_queryInit; String m_queryTimer; String m_updateStatus; String m_account; }; static RegistModule module; // copy parameters from SQL result to a Message static void copyParams2(Message &msg, Array* a, int row = 0) { if ((!a) || (!row)) return; for (int i = 0; i < a->getColumns(); i++) { String* s = YOBJECT(String,a->get(i,0)); if (!(s && *s)) continue; String name = *s; s = YOBJECT(String,a->get(i,row)); if (!s) continue; msg.setParam(name,*s); } } // copy parameters from multiple SQL result rows to a Message // returns true if resultName was found in columns static bool copyParams(Message &msg, Array *a, const String& resultName) { if (!a) return false; bool ok = false; FallBackRoute* fallback = 0; for (int j=1; j getRows();j++) { Message* m = (j <= 1) ? &msg : new Message(msg); for (int i=0; igetColumns();i++) { const String* name = YOBJECT(String,a->get(i,0)); if (!(name && *name)) continue; bool res = (*name == resultName); ok = ok || res; const String* s = YOBJECT(String,a->get(i,j)); if (!s) continue; if (res) m->retValue() = *s; else m->setParam(*name,*s); } if (j>1) { if (m->retValue().null()) { Debug(&module,DebugWarn,"Skipping void route #%d",j); m->destruct(); continue; } if (!fallback) fallback = new FallBackRoute(msg.getValue("id")); *m = "call.execute"; m->setParam("callto",m->retValue()); m->retValue().clear(); m->clearParam("error"); fallback->append(m); } } if (fallback) { Message mlocate("chan.locate"); mlocate.addParam("id",msg.getValue("id")); if (static_cast(Engine::dispatch(mlocate) ? mlocate.userData() : 0)) s_fallbacklist.append(fallback); else fallback->destruct(); } return ok; } AAAHandler::AAAHandler(const char* hname, int type, int prio) : MessageHandler(hname,prio),m_type(type) { } AAAHandler::~AAAHandler() { s_handlers.remove(this,false); } void AAAHandler::loadAccount() { m_result = s_cfg.getValue(name(),"result"); m_account = s_cfg.getValue(name(),"account",s_cfg.getValue("default","account")); } const String& AAAHandler::name() const { return *this; } bool AAAHandler::loadQuery() { m_query = s_cfg.getValue(name(),"query"); indirectQuery(m_query); return !m_query.null(); } // replace a "@query" with the result of that query void AAAHandler::indirectQuery(String& query) { if (m_account.null()) return; if (!query.startSkip("@",false)) return; Engine::runParams().replaceParams(query,true); query.trimBlanks(); if (query.null()) return; Message m("database"); prepareQuery(m,m_account,query,true); query.clear(); // query must return exactly one row, one column if (!Engine::dispatch(m) || (m.getIntValue("rows") != 1) || (m.getIntValue("columns") != 1)) return; Array* a = static_cast(m.userObject("Array")); if (!a) return; query = YOBJECT(String,a->get(0,1)); Debug(&module,DebugInfo,"For '%s' fetched query '%s'",name().c_str(),query.c_str()); } // add the account and query to the "database" message void AAAHandler::prepareQuery(Message& msg, const String& account, const String& query, bool results) { Debug(&module,DebugInfo,"On account '%s' performing query '%s'%s", account.c_str(),query.c_str(),(results ? " expects results" : "")); msg.setParam("account",account); msg.setParam("query",query); msg.setParam("results",String::boolText(results)); } // run the initialization query void AAAHandler::initQuery() { if (m_account.null()) return; String query = s_cfg.getValue(name(),"initquery"); indirectQuery(query); Engine::runParams().replaceParams(query,true); if (query.null()) return; // no error check needed as we can't fix - enqueue the query and we're out Message* m = new Message("database"); prepareQuery(*m,m_account,query,false); Engine::enqueue(m); } void AAAHandler::chkConfig() { if (m_query && m_account.null()) Debug(&module,DebugMild,"Missing database account for '%s'",name().c_str()); } // little helper function to make code cleaner static bool failure(Message* m) { if (m) m->setParam("error","failure"); return false; } bool AAAHandler::received(Message& msg) { if (m_query.null() || m_account.null()) return false; String query(m_query); String account(m_account); msg.replaceParams(query,true); msg.replaceParams(account,true); switch (m_type) { case Regist: { if (s_critical) return failure(&msg); Message m("database"); prepareQuery(m,account,query,true); if (Engine::dispatch(m)) if (m.getIntValue("affected") >= 1 || m.getIntValue("rows") >=1) return true; return false; } break; case Auth: { Message m("database"); prepareQuery(m,account,query,true); if (Engine::dispatch(m)) if (m.getIntValue("rows") >=1) { Array* a = static_cast(m.userObject("Array")); if (!copyParams(msg,a,m_result)) { Debug(&module,DebugWarn,"Misconfigured result column for '%s'",name().c_str()); msg.setParam("error","failure"); return false; } return true; } return false; } break; case PreRoute: { if (s_critical) return failure(&msg); Message m("database"); prepareQuery(m,account,query,true); if (Engine::dispatch(m)) if (m.getIntValue("rows") >=1) { Array* a = static_cast(m.userObject("Array")); copyParams(msg,a,m_result); } return false; } break; case Route: { if (s_critical) return failure(&msg); Message m("database"); prepareQuery(m,account,query,true); if (Engine::dispatch(m)) if (m.getIntValue("rows") >=1) { Array* a = static_cast(m.userObject("Array")); copyParams(msg,a,m_result); if (msg.retValue().null()) { // we know about the user but has no address of record if (s_errOffline) { msg.retValue() = "-"; msg.setParam("error","offline"); msg.setParam("reason","Offline"); } return false; } return true; } return false; } break; case UnRegist: { // no error check needed on unregister - we return false Message m("database"); prepareQuery(m,account,query,true); // we don't enqueue the message because we must assure ourselves that this message is processed synchronously Engine::dispatch(m); } break; case Timer: { u_int32_t t = msg.msgTime().sec(); if (t >= s_nextTime) // we expire users every 30 seconds s_nextTime = t + s_expire; else return false; // no error check needed - we enqueue the query and return false Message* m = new Message("database"); prepareQuery(*m,account,query,false); Engine::enqueue(m); } break; } return false; } CDRHandler::CDRHandler(const char* hname, int prio) : AAAHandler("call.cdr",Cdr,prio), m_name(hname) { m_critical = s_cfg.getBoolValue(m_name,"critical",(m_name == "call.cdr")); } CDRHandler::~CDRHandler() { } const String& CDRHandler::name() const { return m_name; } bool CDRHandler::loadQuery() { m_queryInitialize = s_cfg.getValue(name(),"cdr_initialize"); m_queryUpdate = s_cfg.getValue(name(),"cdr_update"); m_query = s_cfg.getValue(name(),"cdr_finalize"); if (m_query.null()) m_query = s_cfg.getValue(name(),"query"); indirectQuery(m_queryInitialize); indirectQuery(m_queryUpdate); indirectQuery(m_query); return m_queryInitialize || m_queryUpdate || m_query; } bool CDRHandler::received(Message& msg) { if (m_account.null()) return false; // Don't update CDR if told so if (!msg.getBoolValue("cdrwrite",true)) return false; String query(msg.getValue("operation")); if (query == "initialize") query = m_queryInitialize; else if (query == "update") query = m_queryUpdate; else if (query == "finalize") query = m_query; else return false; if (query.null()) return false; String account(m_account); msg.replaceParams(query,true); msg.replaceParams(account,true); // failure while accounting is critical Message m("database"); prepareQuery(m,account,query,true); bool error = !Engine::dispatch(m) || m.getParam("error"); if (m_critical && (s_critical != error)) { s_critical = error; module.changed(); } if (error) failure(&msg); return false; } // EventNotify EventNotify::EventNotify(const char* hname, int type, const char* event, int prio) : AAAHandler(hname,type,prio), m_name("resource.subscribe"), m_event(event) { } const String& EventNotify::name() const { return m_name; } bool EventNotify::loadQuery() { m_querySubs = s_cfg.getValue(m_name,"subscribe_notify"); indirectQuery(m_querySubs); if (!m_querySubs) Debug(&module,DebugNote, "Notify(%s). Invalid 'subscribe_notify' in section '%s'", m_event.c_str(),m_name.c_str()); return !m_querySubs.null(); } // Fill account/query and dispatch the message // Return false if message dispatch fails or no data is returned Array* EventNotify::queryDatabase(Message& msg, const String& notifier, unsigned int& rows) { NamedList nl(""); nl.addParam("notifier",notifier); nl.addParam("event",m_event); String query = m_querySubs; String account = m_account; nl.replaceParams(query,true); nl.replaceParams(account,true); prepareQuery(msg,account,query,true); if (!Engine::dispatch(msg)) return 0; rows = msg.getIntValue("rows",0); Array* subscriptions = static_cast(msg.userObject("Array")); if (!(subscriptions && rows)) return 0; DDebug(&module,DebugAll, "Notify(%s). Found %u subscriber(s) for '%s' notifier", m_event.c_str(),rows,notifier.c_str()); return subscriptions; } // Create a notify message, fill it with notifier, event, subscription data // and additional parameters Message* EventNotify::message(const String& notifier, Array& subscriptions, unsigned int row, NamedList& params) { Message* notify = new Message("resource.notify"); notify->addParam("notifier",notifier); notify->addParam("event",m_event); copyParams2(*notify,&subscriptions,row); unsigned int n = params.count(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = params.getParam(i); if (ns) notify->addParam(ns->name(),*ns); } return notify; } // call.cdr handler: Notifications on dialog state changes bool DialogNotify::received(Message& msg) { if(!(m_account && m_querySubs)) return false; XDebug(&module,DebugAll, "Notify(dialog). operation=%s external=%s status=%s chan=%s", msg.getValue("operation"),msg.getValue("external"), msg.getValue("status"),msg.getValue("chan")); // Get call id and state to be notified String callState; String operation(msg.getValue("operation")); if (operation == "update") { String status = msg.getValue("status"); if (!status) return false; if (status == "connected" || status == "answered") callState = "confirmed"; else if (status == "calling" || status == "ringing" || status == "progressing" || status == "incoming" || status == "outgoing") callState = "early"; else if (status == "redirected") callState = "rejected"; else if (status == "destroyed") callState = "terminated"; } else if (operation == "initialize") callState = "trying"; else if (operation == "finalize") callState = "terminated"; if (!callState) return false; String id = msg.getValue("chan"); if (!id) return false; // Get notifier from message and its subscriptions from database String notifier = msg.getValue("external"); Message m("database"); unsigned int rows; Array* subscriptions = queryDatabase(m,notifier,rows); // Notify if (subscriptions) { NamedList nl(""); nl.addParam("dialog.id",id); nl.addParam("dialog.direction", msg.getValue("direction")); nl.addParam("dialog.state",callState); notifyAll(notifier,*subscriptions,rows,nl); } return false; } // user.notify handler: Notifications on message(s) state changes bool MWINotify::received(Message& msg) { if(!(m_account && m_querySubs)) return false; const char* v = msg.getValue("voicemail"); if (!v) return false; // TODO: change debug Debug(&module,DebugNote,"Notify(message-summary). username=%s",msg.getValue("username")); String notifier = msg.getValue("username"); if (!notifier) return false; Message m("database"); unsigned int rows; Array* subscriptions = queryDatabase(m,notifier,rows); if (subscriptions) { NamedList nl(""); nl.addParam("message-summary.voicenew",msg.getValue("voicenew")); nl.addParam("message-summary.voiceold",msg.getValue("voiceold")); notifyAll(notifier,*subscriptions,rows,nl); } return false; } SubscribeHandler::SubscribeHandler(const char* hname, int type, int prio) : AAAHandler(hname,Subscribe, prio), m_name(hname) { } SubscribeHandler::~SubscribeHandler() { } const String& SubscribeHandler::name() const { return m_name; } bool SubscribeHandler::loadQuery() { m_querySubscribe = s_cfg.getValue(name(),"subscribe_subscribe"); indirectQuery(m_querySubscribe); if (!m_querySubscribe) Debug(&module,DebugNote, "Invalid 'subscribe_subscribe' in section '%s'",m_name.c_str()); m_queryUnsubscribe = s_cfg.getValue(name(),"subscribe_unsubscribe"); indirectQuery(m_queryUnsubscribe); if (!m_queryUnsubscribe) Debug(&module,DebugNote, "Invalid 'subscribe_unsubscribe' in section '%s'",m_name.c_str()); return m_querySubscribe || m_queryUnsubscribe; } bool SubscribeHandler::received(Message& msg) { if (!m_account) return false; DDebug(&module,DebugAll, "Subscribe. operation=%s notifier=%s subscriber=%s event=%s notifyto=%s", msg.getValue("operation"),msg.getValue("notifier"), msg.getValue("subscriber"),msg.getValue("event"),msg.getValue("notifyto")); String query = msg.getValue("operation"); bool subscribe = true; if(query == "subscribe") query = m_querySubscribe; else if (query == "unsubscribe") { subscribe = false; query = m_queryUnsubscribe; } else query = ""; if (!query) return false; String account = m_account; msg.replaceParams(query,true); msg.replaceParams(account,true); Message m("database"); prepareQuery(m,account,query,true); int rows = 0; if(!Engine::dispatch(m)) { msg.setParam("reason","failure"); return false; } if(1 != (rows = m.getIntValue("rows",0))) { msg.setParam("reason","forbidden"); return false; } Message* notify = new Message("resource.notify"); Array* a = static_cast(m.userObject("Array")); if (subscribe) { copyParams2(*notify,a,1); notify->addParam("subscriptionstate","active"); } else { String* s = YOBJECT(String, a ? a->get(0,0) : 0); int count = s ? s->toInteger() : 0; if(count != 1) { msg.setParam("reason","forbidden"); TelEngine::destruct(notify); return false; } notify->copyParams(msg,"subscriber,notifier,notifyto,event,data"); notify->addParam("subscriptionstate","terminated"); } Engine::enqueue(notify); return true; } SubscribeTimerHandler::SubscribeTimerHandler(const char* hname, int type, int prio) : AAAHandler("engine.timer", type, prio), m_name("resource.subscribe") { m_expireTime = s_cfg.getIntValue(m_name,"expires",s_cfg.getIntValue("general","expires",30)); m_nextTime = 0; } SubscribeTimerHandler::~SubscribeTimerHandler() { } const String& SubscribeTimerHandler::name() const { return m_name; } bool SubscribeTimerHandler::loadQuery() { m_queryExpire = s_cfg.getValue(name(), "subscribe_expire"); indirectQuery(m_queryExpire); if (!m_queryExpire) Debug(&module,DebugNote, "Invalid 'subscribe_expire' in section '%s'",name().safe()); return !m_queryExpire.null(); } bool SubscribeTimerHandler::received(Message& msg) { if(!(m_account && m_queryExpire)) return false; u_int32_t t = msg.msgTime().sec(); if( t >= m_nextTime) m_nextTime = t + m_expireTime; else return false; if(!m_queryExpire) return false; Message m("database"); String account = m_account; String query = m_queryExpire; msg.replaceParams(query,true); msg.replaceParams(account,true); prepareQuery(m,account,query,true); if(!Engine::dispatch(m)) return false; int rows = m.getIntValue("rows",0); Array* a = static_cast(m.userObject("Array")); if(!a || rows < 1) return false; for(int i = 1; i <= rows; i++) { Message* notify = new Message("resource.notify"); copyParams2(*notify,a,i); notify->addParam("subscriptionstate","terminated"); notify->addParam("terminatereason","timeout"); DDebug(&module,DebugNote,"Subscription expired: notifier=%s subscriber=%s event=%s", notify->getValue("notifier"),notify->getValue("subscriber"),notify->getValue("event")); Engine::enqueue(notify); } return false; } RegistModule::RegistModule() : Module("register","database"), m_init(false), m_accountsmodule(0) { Output("Loaded module Register for database"); } RegistModule::~RegistModule() { TelEngine::destruct(m_accountsmodule); Output("Unloading module Register for database"); } void RegistModule::statusParams(String& str) { NamedString* names; str.append("critical=",",") << s_critical; for (unsigned int i=0; i < s_statusaccounts.count(); i++) { names = s_statusaccounts.getParam(i); if (names) str << "," << names->name() << "=" << names->at(0); } } bool RegistModule::received(Message& msg, int id) { if (id == Private) { if (s_cfg.getBoolValue("general","accounts")) m_accountsmodule= new AccountsModule(); ObjList* l = s_handlers.skipNull(); for (; l; l=l->skipNext()) { AAAHandler* h = YOBJECT(AAAHandler,l->get()); if (h) h->initQuery(); } return false; } return Module::received(msg,id); } int RegistModule::getPriority(const String& name) { bool fb = (name == "chan.disconnected") || (name == "call.answered") || (name == "chan.hangup"); // allow to default enable all fallback related messages in a single place if (!s_cfg.getBoolValue("general",name,fb && s_cfg.getBoolValue("general","fallback"))) return -1; int prio = s_cfg.getIntValue("default","priority",50); // also allow a 2nd default priority for fallback messages if (fb) prio = s_cfg.getIntValue("fallback","priority",prio); return s_cfg.getIntValue(name,"priority",prio); } void RegistModule::addHandler(AAAHandler* handler) { handler->loadAccount(); s_handlers.append(handler); handler->loadQuery(); handler->chkConfig(); Engine::install(handler); } void RegistModule::addHandler(FallBackHandler* handler) { s_handlers.append(handler); Engine::install(handler); } void RegistModule::addHandler(const char *name, int type) { int prio = getPriority(name); if (prio >= 0) { switch (type) { case FallBackHandler::Disconnect: case FallBackHandler::Answered: case FallBackHandler::Hangup: addHandler(new FallBackHandler(name,type,prio)); break; case AAAHandler::Cdr: addHandler(new CDRHandler(name,prio)); break; case AAAHandler::DialogNotify: addHandler(new DialogNotify(name,prio)); break; case AAAHandler::MWINotify: addHandler(new MWINotify(name,prio)); break; case AAAHandler::Subscribe: addHandler(new SubscribeHandler(name, type, prio)); break; case AAAHandler::SubscribeTimer: addHandler(new SubscribeTimerHandler(name, type, prio)); break; default: addHandler(new AAAHandler(name,type,prio)); } } } void RegistModule::initialize() { s_critical = false; if (m_init) return; m_init = true; setup(); Output("Initializing module Register for database"); s_expire = s_cfg.getIntValue("general","expires",s_expire); s_errOffline = s_cfg.getBoolValue("call.route","offlineauto",true); Engine::install(new MessageRelay("engine.start",this,Private,150)); addHandler("call.cdr",AAAHandler::Cdr); addHandler("linetracker",AAAHandler::Cdr); addHandler("user.auth",AAAHandler::Auth); addHandler("engine.timer",AAAHandler::Timer); addHandler("user.unregister",AAAHandler::UnRegist); addHandler("user.register",AAAHandler::Regist); addHandler("call.preroute",AAAHandler::PreRoute); addHandler("call.route",AAAHandler::Route); addHandler("chan.disconnected",FallBackHandler::Disconnect); addHandler("chan.hangup",FallBackHandler::Hangup); addHandler("call.answered",FallBackHandler::Answered); if (s_cfg.getBoolValue("general","subscriptions",false)) { addHandler("call.cdr",AAAHandler::DialogNotify); addHandler("user.notify",AAAHandler::MWINotify); addHandler("resource.subscribe", AAAHandler::Subscribe); addHandler("engine.timer", AAAHandler::SubscribeTimer); } } bool FallBackHandler::received(Message &msg) { switch (m_type) { case Answered: { GenObject* route = s_fallbacklist[msg.getValue("targetid")]; s_fallbacklist.remove(route); return false; } break; case Hangup: { GenObject* route = s_fallbacklist[msg.getValue("id")]; s_fallbacklist.remove(route); return false; } break; case Disconnect: { String reason=msg.getValue("reason"); if (m_stoperror && m_stoperror.matches(reason)) { //stop fallback on this error GenObject* route = s_fallbacklist[msg.getValue("id")]; s_fallbacklist.remove(route); return false; } FallBackRoute* route = static_cast(s_fallbacklist[msg.getValue("id")]); if (route) { Message* r = route->get(); if (r) { r->userData(msg.userData()); Engine::enqueue(r); return true; } s_fallbacklist.remove(route); } return false; } break; } return false; } AccountsModule::AccountsModule() : m_init(false), m_nextTime(0) { Output("Loaded modules Accounts for database"); m_account = s_cfg.getValue("accounts","account", s_cfg.getValue("default","account")); m_queryInit = s_cfg.getValue("accounts","initquery"); m_queryTimer = s_cfg.getValue("accounts","timerquery"); m_updateStatus = s_cfg.getValue("accounts","statusquery"); initialize(); } AccountsModule::~AccountsModule() { Output("Unloading module Accounts for database"); } bool AccountsModule::received(Message &msg, int id) { if (id == Notify) { String name(msg.getValue("account")); if (name.null()) return false; name << "(" << msg.getValue("protocol") << ")"; s_statusaccounts.setParam(name,msg.getValue("registered")); Message *m = new Message("database"); String account(m_account); msg.replaceParams(account,true); String query(m_updateStatus); String status; if (msg.getBoolValue("registered")) status="online"; else status="offline"; m->addParam("status",status); m->addParam("internalaccount",msg.getValue("account")); m->replaceParams(query,true); AAAHandler::prepareQuery(*m,account,query,false); Engine::enqueue(m); return false; } if (id == Timer) { if (m_account.null()) return false; u_int32_t t = msg.msgTime().sec(); if (t >= m_nextTime) // we look for account changes every 30 seconds m_nextTime = t + s_expire; else return false; String query; if (m_init) query = m_queryTimer; else { query = m_queryInit; m_init=true; } if (query.null()) return false; Message m("database"); String account(m_account); msg.replaceParams(account,true); AAAHandler::prepareQuery(m,account,query,true); if (Engine::dispatch(m)) { int rows = m.getIntValue("rows"); if (rows>0) { for (int i=1 ; i<=rows ; i++) { Message *m1= new Message("user.login"); Array* a = static_cast(m.userObject("Array")); copyParams2(*m1,a, i); Engine::enqueue(m1); } return false; } } return false; } return false; } void AccountsModule::initialize() { if (s_cfg.getBoolValue("general","accounts")) { Engine::install(new MessageRelay("user.notify",this,Notify,100)); Engine::install(new MessageRelay("engine.timer",this,Timer,100)); } } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */