/** * yiaxchan.cpp * This file is part of the YATE Project http://YATE.null.ro * * IAX channel * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2023 Null Team * Author: Marian Podgoreanu * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * 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. */ #include #include #include #include #include #include #include #include using namespace TelEngine; namespace { // anonymous static const TokenDict dict_payloads[] = { {"gsm", IAXFormat::GSM}, {"ilbc30", IAXFormat::ILBC}, {"speex", IAXFormat::SPEEX}, {"lpc10", IAXFormat::LPC10}, {"mulaw", IAXFormat::ULAW}, {"alaw", IAXFormat::ALAW}, {"g723", IAXFormat::G723_1}, {"g729", IAXFormat::G729}, {"adpcm", IAXFormat::ADPCM}, {"g726", IAXFormat::G726}, {"slin", IAXFormat::SLIN}, {"g722/16000", IAXFormat::G722}, {"amr", IAXFormat::AMR}, {"gsmhr", IAXFormat::GSM_HR}, {0, 0} }; static const TokenDict dict_payloads_video[] = { {"h261", IAXFormat::H261}, {"h263", IAXFormat::H263}, {"h263p", IAXFormat::H263p}, {"h264", IAXFormat::H264}, {0, 0} }; static const TokenDict dict_tos[] = { { "lowdelay", Socket::LowDelay }, { "throughput", Socket::MaxThroughput }, { "reliability", Socket::MaxReliability }, { "mincost", Socket::MinCost }, { 0, 0 } }; class YIAXLineContainer; class YIAXEngine; class IAXURI; /* * Keep a single registration line */ class YIAXLine : public RefObject, public Mutex { friend class YIAXLineContainer; public: YIAXLine(const String& name); virtual ~YIAXLine(); virtual const String& toString() const { return m_name; } inline bool registered() const { return m_registered; } inline const String& username() const { return m_username; } inline const String& password() const { return m_password; } inline const String& callingNo() const { return m_callingNo; } inline const String& callingName() const { return m_callingName; } inline unsigned int expire() const { return m_expire; } inline const String& remoteAddr() const { return m_remoteAddr; } inline int remotePort() const { return m_remotePort; } inline const SocketAddr& remote() const { return m_remote; } inline bool callToken() const { return m_callToken; } inline bool trunking() const { return m_trunking; } inline const NamedList& trunkInfo() const { return m_trunkInfo; } inline YIAXEngine* engine() const { return m_engine; } inline IAXTransaction* transaction() const { return m_transaction; } protected: virtual void destroyed() { if (m_transaction) { m_transaction->setUserData(0); m_transaction = 0; } m_engine = 0; RefObject::destroyed(); } private: void setRegistered(bool registered, const char* reason = 0, const char* error = 0); inline void setNextRegFromExpire(unsigned int val1, unsigned int val2) { m_nextReg = Time::now() + 1000000 * (m_expire * (val1 ? val1 : 1) / (val2 ? val2 : 1)); } String m_name; int m_oper; // Requested operation String m_username; // Username String m_password; // Password String m_callingNo; // Calling number String m_callingName; // Calling name bool m_callToken; // Advertise CALLTOKEN support unsigned int m_expire; // Expire time String m_remoteAddr; int m_remotePort; SocketAddr m_remote; u_int64_t m_nextReg; // Time to next registration u_int64_t m_nextKeepAlive; // Time to next keep alive signal unsigned int m_keepAliveInterval; // Keep alive interval bool m_registered; // Registered flag. If true the line is registered RefPointer m_transaction; RefPointer m_engine; // The engine to use NamedList m_trunkInfo; // Trunk info parameters bool m_trunking; // Enable trunking NamedList m_engineToUse; // Parameters used to identify the engine }; /* * Line container: Add/Delete/Update/Register/Unregister lines */ class YIAXLineContainer : public Mutex { public: enum Operation { Unknown = 0, Create, Login, // Login Logout, // Logout and remain Delete, // Logout and delete after }; inline YIAXLineContainer() : Mutex(true,"YIAXLineContainer"), m_lineTransMutex(false,"YIAXLineTransMutex") {} inline bool busy(const YIAXLine* line) { Lock lck(m_lineTransMutex); return line && 0 != line->transaction(); } // Logout and remove all lines // Process only lines using a specified engine if given void clear(bool final = false, YIAXEngine* eng = 0); // Update a line from a message bool updateLine(Message &msg); // Re-check lines engine void updateEngine(); // Event handler for a registration. void handleEvent(IAXEvent* event); // Terminate notification of a Register/Unregister operation void regTerminate(IAXEvent* event); // Process an authentication request for a Register/Unregister operation void regAuthReq(IAXEvent* event); // Timer notification void evTimer(Time& time); // Fill parameters with information taken from line void fillList(YIAXLine& line, NamedList& dest, SocketAddr& addr); // Find a line by name inline bool findLine(RefPointer& line, const String& name) { Lock lck(this); line = findLine(name); return line != 0; } // Check if a line exists inline bool hasLine(const String& line) { Lock lock(this); return findLine(line) != 0; } // Retrieve the lines list inline ObjList& lines() { return m_lines;} Mutex m_lineTransMutex; protected: // Set a line's transaction void setTransaction(YIAXLine* line, IAXTransaction* tr = 0, bool abortReg = true); // Change a line String parameter. Unregister it if changed inline void changeLine(bool& changed, YIAXLine* line, String& dest, const String& src) { if (!line || dest == src) return; unregisterLineNonBusy(line); dest = src; changed = true; } inline void changeLine(bool& changed, YIAXLine* line, unsigned int& dest, unsigned int src) { if (!line || dest == src) return; unregisterLineNonBusy(line); dest = src; changed = true; } inline void changeLine(bool& changed, YIAXLine* line, int& dest, int src) { if (!line || dest == src) return; unregisterLineNonBusy(line); dest = src; changed = true; } // Unregister a non busy registered line. Return true if unregistering inline bool unregisterLineNonBusy(YIAXLine* line) { if (!line || !line->m_registered || busy(line)) return false; return registerLine(line,false); } // Find a line by name inline YIAXLine* findLine(const String& name) { ObjList* o = m_lines.find(name); return o ? static_cast(o->get()) : 0; } // Find a line by address, return same if found inline YIAXLine* findLine(YIAXLine* line) { return (line && m_lines.find(line)) ? line : 0; } // Find line from event. Clear event transaction if line is not found // Return referenced pointer YIAXLine* findLine(IAXEvent* ev); // (Un)register a line bool registerLine(YIAXLine* line, bool reg); private: ObjList m_lines; }; // // Engine thread // class YIAX_API YIAXThread : public Thread, public GenObject { public: enum Operation { Listener, GetEvent, Trunking }; inline YIAXThread(YIAXEngine* engine, int oper, Priority prio = Normal) : Thread(lookup(oper,s_operThName),prio), m_oper(oper), m_engine(engine) {} ~YIAXThread() { notify(true); } inline const char* operThName() const { return lookup(m_oper,s_operThName); } virtual void cleanup() { notify(true); } virtual void run(); static const TokenDict s_operThName[]; protected: void notify(bool final = false); int m_oper; YIAXEngine* m_engine; }; /* * The IAX engine for this driver */ class YIAXEngine : public IAXEngine, public RefObject { friend class YIAXDriver; friend class YIAXThread; public: enum Status { Idle, Listening, Disabled, Removed, Exiting, BindFailed }; YIAXEngine(const String& name, const char* iface, int port, const NamedList* params); virtual ~YIAXEngine() {} inline bool bound() const { return m_status != BindFailed && m_status != Idle; } inline bool valid() const { return bound() && !exiting(); } inline const char* statusName() { return statusName(m_status); } inline void readSocket() { SocketAddr addr; IAXEngine::readSocket(addr); } inline bool timeout(const Time& now) { return m_timeout && m_timeout < now; } void changeStatus(int stat); bool bind(const NamedList& params); // Schedule for termination void setTerminate(int stat); // Setup a given transaction from parameters void initTransaction(IAXTransaction* tr, const NamedList& params, YIAXLine* line = 0); // Process media from remote peer virtual void processMedia(IAXTransaction* transaction, DataBlock& data, u_int32_t tStamp, int type, bool mark); // Initiate (un)register transaction inline IAXTransaction* reg(bool regreq, const SocketAddr& addr, IAXIEList& ieList) { if (regreq) return startLocalTransaction(IAXTransaction::RegReq,addr,ieList,true,false); return startLocalTransaction(IAXTransaction::RegRel,addr,ieList,true,false); } // Initiate an aoutgoing call. IAXTransaction* call(SocketAddr& addr, NamedList& params); // Initiate a test of existence of a remote IAX peer. IAXTransaction* poke(SocketAddr& addr); // Cancel threads void cancelThreads(unsigned int waitTerminateMs = 100); // Add engine data to message inline void fillMessage(Message& msg, bool addRoute = true) { msg.setParam("connection_id",toString()); msg.setParam("connection_reliable",String::boolText(false)); if (addRoute) { msg.setParam("route_params","oconnection_id"); msg.setParam("oconnection_id",toString()); } } virtual const String& toString() const { return name(); } static inline const char* statusName(int stat) { return lookup(stat,s_statusName); } static const TokenDict s_statusName[]; protected: virtual void destroyed(); // Event handler for transaction with a connection virtual void processEvent(IAXEvent* event); // Process a new format received with a full frame virtual bool mediaFormatChanged(IAXTransaction* trans, int type, u_int32_t format); // Event handler for incoming registration transactions void processRemoteReg(IAXEvent* event,bool first); // Send Register/Unregister messages to Engine // Return the message on success Message* userreg(IAXTransaction* tr, bool regrel = true); // Create threads void createThreads(const NamedList& params); void createThreads(unsigned int count, int oper, Thread::Priority prio); private: void threadTerminated(YIAXThread* th); bool m_initialized; bool m_default; ObjList m_threads; u_int64_t m_timeout; // Timeout: destroy the engine int m_status; // Engine status String m_bindAddr; // Requested bind addr int m_bindPort; // Requested bind port }; /* * YIAXDriver */ class YIAXDriver : public Driver { public: enum Relay { EngineStop = Private }; YIAXDriver(); virtual ~YIAXDriver(); virtual void initialize(); virtual bool hasLine(const String& line) const; virtual bool msgExecute(Message& msg, String& dest); virtual bool msgRoute(Message& msg); virtual bool received(Message& msg, int id); // Find a channel. Return a refferenced pointer inline Channel* findChan(Channel* chan) { if (!chan) return 0; Lock lck(this); return channels().find(chan) && chan->ref() ? chan : 0; } // Find an engine from parameters. Return a refferenced pointer bool findEngine(RefPointer& eng, const NamedList& params, const String& prefix = String::empty(), YIAXLine* line = 0, NamedList* copyParams = 0); // Find an engine by name. Return true if found inline bool findEngine(RefPointer& engine, const String& name) { if (!name) return false; Lock lck(m_enginesMutex); engine = findEngine(name); return engine != 0; } // Update codecs a list of parameters // Return false if the result is 0 (no intersection) bool updateCodecsFromRoute(u_int32_t& codecs, const NamedList& params, int type); // Dispatch user.auth // tr The IAX transaction // response True if it is a response. // requestAuth True on exit: the caller should request authentication // invalidAuth True on exit: authentication response is incorrect // billid The billing ID parameter if available // Return false if not authenticated bool userAuth(IAXTransaction* tr, bool response, bool& requestAuth, bool& invalidAuth, const char* billid = 0); // Start/stop engine list changes void engineListChanging(bool start); // Setup an engine void setupEngine(const String& name, bool& valid, bool enable, bool def, const NamedList& params, const NamedList* defaults = 0); // Terminate exiting engines void checkEngineTerminate(const Time& now); protected: virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord); virtual void msgStatus(Message& msg); virtual void statusParams(String& str); void msgStatusAccounts(Message& msg); void msgStatusListeners(Message& msg); virtual void genUpdate(Message& msg); // Update default engine void updateDefaultEngine(); // Terminate an engine. Terminate all if no engine is given void terminateEngine(YIAXEngine* eng, bool final, int reason); // Initialize formats from config void initFormats(const NamedList& params); // Find an engine by name inline YIAXEngine* findEngine(const String& name) { if (!name) return 0; for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) { YIAXEngine* e = static_cast(o->get()); if (e->name() == name) return e; } return 0; } // Find an engine by local ip/port inline YIAXEngine* findEngine(const String& addr, int port) { for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) { YIAXEngine* e = static_cast(o->get()); if (e->addr().host() == addr && e->addr().port() == port) return e; } return 0; } ObjList m_engines; Mutex m_enginesMutex; YIAXEngine* m_defaultEngine; // The default engine unsigned int m_haveEngTerminate; // The number of terminating engines u_int32_t m_format; // The default media format u_int32_t m_formatVideo; // Default video format u_int32_t m_capability; // The media capability unsigned int m_failedAuths; bool m_init; }; /* * YIAXRegDataHandler */ class YIAXRegDataHandler : public MessageHandler { public: YIAXRegDataHandler(const char* trackName) : MessageHandler("user.login",150,trackName) { } virtual bool received(Message &msg); }; class YIAXConnection; /* * Base class for data consumer/source */ class YIAXData { public: inline YIAXData(YIAXConnection* conn, u_int32_t format, int type) : m_connection(conn), m_total(0), m_format(format), m_type(type) {} inline u_int32_t format() { return m_format; } protected: YIAXConnection* m_connection; unsigned m_total; u_int32_t m_format; // in IAX coding int m_type; }; /* * Connection's data consumer */ class YIAXConsumer : public DataConsumer, public YIAXData { YCLASS(YIAXConsumer,DataConsumer) public: YIAXConsumer(YIAXConnection* conn, u_int32_t format, const char* formatText, int type); ~YIAXConsumer(); virtual unsigned long Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags); }; /* * Connection's data source */ class YIAXSource : public DataSource, public YIAXData { YCLASS(YIAXSource,DataSource) public: YIAXSource(YIAXConnection* conn, u_int32_t format, const char* formatText, int type); ~YIAXSource(); unsigned long Forward(const DataBlock &data, unsigned long tStamp, unsigned long flags); }; /* * The connection */ class YIAXConnection : public Channel { friend class YIAXDriver; public: YIAXConnection(IAXTransaction* transaction, Message* msg = 0, NamedList* params = 0); virtual ~YIAXConnection(); virtual void callAccept(Message& msg); virtual void callRejected(const char* error, const char* reason = 0, const Message* msg = 0); virtual bool callPrerouted(Message& msg, bool handled); virtual bool callRouted(Message& msg); virtual bool msgProgress(Message& msg); virtual bool msgRinging(Message& msg); virtual bool msgAnswered(Message& msg); virtual bool msgTone(Message& msg, const char* tone); virtual bool msgText(Message& msg, const char* text); virtual void disconnected(bool final, const char* reason); bool disconnect(const char* reason = 0); inline IAXTransaction* transaction() const { return m_transaction; } inline bool mutedIn() const { return m_mutedIn; } inline bool mutedOut() const { return m_mutedOut; } void handleEvent(IAXEvent* event); bool route(IAXEvent* event, bool authenticated = false); // Set reason inline void setReason(const char* reason) { Lock lck(m_mutexChan); m_reason = reason; } // Retrieve the data consumer from IAXFormat media type YIAXConsumer* getConsumer(int type); // Retrieve the data source from IAXFormat media type YIAXSource* getSource(int type); // Retrieve the data source from IAXFormat DataSource* getSourceMedia(int type); protected: // location: 0: from peer, else: from protocol void hangup(int location, const char* error = 0, const char* reason = 0, bool reject = false); void resetTransaction(NamedList* params = 0, bool reject = false); void startMedia(bool in, int type = IAXFormat::Audio); void clearMedia(bool in, int type = IAXFormat::Audio); void evAuthRep(IAXEvent* event); void evAuthReq(IAXEvent* event); // Safe deref the connection if the reference counter was increased during registration void safeDeref(); bool safeRefIncrease(); private: RefPointer m_transaction; // IAX transaction String m_password; // Password for client authentication bool m_mutedIn; // No remote media accepted bool m_mutedOut; // No local media accepted bool m_audio; // Audio endpoint was used bool m_video; // Video endpoint was used String m_reason; // Call end reason text bool m_hangup; // Need to send chan.hangup message Mutex m_mutexChan; Mutex m_mutexTrans; // Safe m_transaction operations Mutex m_mutexRefIncreased; // Safe ref/deref connection bool m_refIncreased; // If true, the reference counter was increased unsigned int m_routeCount; // Incoming: route counter RefPointer m_sources[IAXFormat::TypeCount]; // Keep all types of sources }; // // An IAX URI parser // [iax[2]:][username@]host[:port][/called_number[@called_context]] // class IAXURI : public String { public: inline IAXURI(String& s) : String(s), m_port(0), m_parsed(false) {} inline IAXURI(const char* s) : String(s), m_port(0), m_parsed(false) {} IAXURI(const char* user, const char* host, const char* calledNo, const char* calledContext, int port = 4569); inline ~IAXURI() {} void parse(); bool fillList(NamedList& dest); bool setAddr(SocketAddr& dest); inline const String& username() const { return m_username; } inline const String& host() const { return m_host; } inline int port() const { return m_port; } inline const String& calledNo() const { return m_calledNo; } inline const String& calledContext() const { return m_calledContext; } protected: inline IAXURI() {} private: String m_username; String m_host; int m_port; String m_calledNo; String m_calledContext; bool m_parsed; }; /* * Local data */ static YIAXLineContainer s_lines; // Lines static YIAXDriver iplugin; // Init the driver static bool s_callTokenOut = true; // Send an empty call token on outgoing calls static unsigned int s_extendEngTerminate = 1000; // Extend engine terminate interval (milliseconds) static unsigned int s_expires_min = 60; static unsigned int s_expires_def = 60; static unsigned int s_expires_max = 3600; static bool s_authReq = true; static unsigned int s_engineStop = 0; static const String s_iaxEngineName = "iaxengine"; // Engine parameters to delete when using defaults // They can't be inherited static const String s_delEngParams[] = {"enable","default","addr","port","force_bind",""}; const TokenDict YIAXEngine::s_statusName[] = { {"Listening", Listening}, {"Disabled", Disabled}, {"Removed", Removed}, {"Exiting", Exiting}, {"BindFailed", BindFailed}, {"Idle", Idle}, {0,0} }; const TokenDict YIAXThread::s_operThName[] = { {"YIAXListener", Listener}, {"YIAXGetEvent", GetEvent}, {"YIAXTrunking", Trunking}, {0,0} }; static inline bool isGeneralEngine(const String& name) { return name == s_iaxEngineName; } // Retrieve data endpoint name from IAXFormat type static inline const String& dataEpName(int type) { return IAXFormat::typeNameStr(type); } // Retrieve format name from IAXFormat type static const char* lookupFormat(u_int32_t format, int type) { if (type == IAXFormat::Audio) return lookup(format,dict_payloads); if (type == IAXFormat::Video) return lookup(format,dict_payloads_video); return 0; } // Retrieve a parameter, find its value in a dictionary // Return true if a non empty parameter was found static inline bool getKeyword(const NamedList& list, const String& param, const TokenDict* tokens, int& val) { const char* s = list.getValue(param); if (s) val = lookup(s,tokens); return (s != 0); } // Retrieve a configuration section static inline const NamedList& safeSect(Configuration& cfg, const String& name) { NamedList* sect = cfg.getSection(name); if (sect) return *sect; return NamedList::empty(); } // Clear multiple list parameters static void clearListParams(NamedList& list, const String* params) { if (!params) return; for (; *params; params++) list.clearParam(*params); } // Retrieve bind addr/port parameters static inline void getBindAddr(const NamedList& params, String& addr, int& port, bool* force = 0) { addr = params.getValue(YSTRING("addr")); port = IAXEngine::getPort(params); if (force) *force = params.getBoolValue("force_bind",true); } // // YIAXLine // // Create an idle line YIAXLine::YIAXLine(const String& name) : Mutex(true,"IAX:Line"), m_name(name), m_oper(YIAXLineContainer::Create), m_callToken(false), m_expire(60), m_remotePort(4569), m_nextReg(0), m_nextKeepAlive(0), m_keepAliveInterval(0), m_registered(false), m_transaction(0), m_trunkInfo(""), m_trunking(false), m_engineToUse("") { Debug(&iplugin,DebugAll,"Line(%s) created [%p]",m_name.c_str(),this); } YIAXLine::~YIAXLine() { Debug(&iplugin,DebugAll,"Line(%s) destroyed [%p]",m_name.c_str(),this); } // Set the registered status, emits user.notify messages if necessary void YIAXLine::setRegistered(bool registered, const char* reason, const char* error) { if (m_registered && registered) return; m_registered = registered; if (!m_registered) { m_nextKeepAlive = 0; m_remote.clear(); } Message* m = new Message("user.notify"); m->addParam("account",toString()); m->addParam("protocol","iax"); m->addParam("username",m_username); m->addParam("registered",String::boolText(registered)); m->addParam("reason",reason,false); m->addParam("error",error,false); Engine::enqueue(m); } // // YIAXLineContainer // // Set a line's transaction void YIAXLineContainer::setTransaction(YIAXLine* line, IAXTransaction* tr, bool abortReg) { if (!line) return; Lock lck(m_lineTransMutex); if (line->transaction() == tr) return; if (line->transaction()) { Debug(&iplugin,DebugAll,"Line(%s) clearing transaction (%p) [%p]", line->toString().c_str(),line->transaction(),line); RefPointer trans = line->m_transaction; line->m_transaction = 0; if (trans) trans->setUserData(0); lck.drop(); if (abortReg && trans) trans->abortReg(); trans = 0; } if (!tr) return; line->m_transaction = tr; tr->setUserData(line); Debug(&iplugin,DebugAll, "Line(%s) set transaction (%p) listener='%s' callno=%u [%p]", line->toString().c_str(),tr,tr->getEngine()->name().c_str(), tr->localCallNo(),line); } // Find and update a line with parameters from message, create if needed bool YIAXLineContainer::updateLine(Message& msg) { const String& name = msg[YSTRING("account")]; if (!name) return false; int oper = Unknown; const String& op = msg[YSTRING("operation")]; if (op == YSTRING("login")) oper = Login; else if (op == YSTRING("create")) oper = Create; else if (op == YSTRING("logout")) oper = Logout; else if (op == YSTRING("delete")) oper = Delete; else return false; Lock lck(this); Debug(&iplugin,DebugAll,"Updating line '%s' oper=%s",name.c_str(),op.c_str()); RefPointer line = findLine(name); bool oldLine = true; if (!line) { if (oper != Login && oper != Create) return false; line = new YIAXLine(name); m_lines.append((YIAXLine*)line); oldLine = false; } lck.drop(); line->lock(); if (oper == Login || oper == Create) { bool changed = (line->m_oper != oper); String addr = msg.getValue("server",msg.getValue("domain")); int pos = addr.find(":"); int port = -1; if (pos >= 0) { port = addr.substr(pos + 1).toInteger(); addr = addr.substr(0,pos); } else port = IAXEngine::getPort(msg); if (port > 0) changeLine(changed,line,line->m_remotePort,port); line->m_keepAliveInterval = msg.getIntValue("keepalive",25,0); changeLine(changed,line,line->m_remoteAddr,addr); changeLine(changed,line,line->m_username,msg.getValue("username")); changeLine(changed,line,line->m_password,msg.getValue("password")); changeLine(changed,line,line->m_callingNo,msg.getValue("caller")); changeLine(changed,line,line->m_callingName,msg.getValue("callername")); unsigned int interval = msg.getIntValue("interval",60,60,0xffff); changeLine(changed,line,line->m_expire,interval); line->m_callToken = msg.getBoolValue("calltoken",s_callTokenOut); line->m_trunking = msg.getBoolValue("trunking"); line->m_trunkInfo.clearParams(); line->m_trunkInfo.copySubParams(msg,"trunk_"); line->m_engineToUse.clearParams(); RefPointer engine; iplugin.findEngine(engine,msg,String::empty(),0,&line->m_engineToUse); if (engine != line->engine()) { if (line->engine()) { // Registering: reset transaction and unregister // Unregistering: let the transaction finish if (line->registered()) { setTransaction(line); registerLine(line,false); } } line->m_engine = engine; } if (changed || oper == Login) line->m_oper = Login; else if (line->m_oper != Login && line->m_oper != Create) line->m_oper = oper; if (engine) { if (!busy(line) && (changed || line->m_oper == Login)) registerLine(line,true); } else if (line->m_oper == Login) Debug(&iplugin,DebugNote,"No listener to register line '%s'",name.c_str()); } else { if (line->m_registered) unregisterLineNonBusy(line); line->m_oper = oper; } line->unlock(); if (oper == Delete) { if (!busy(line)) m_lines.remove(line); } else if (oldLine) { // The line might be removed in regTerminate() Lock lck(this); if (!findLine((YIAXLine*)line) && line->ref()) m_lines.append(m_lines.append((YIAXLine*)line)); } line = 0; return true; } // Re-check lines engine void YIAXLineContainer::updateEngine() { lock(); ListIterator iter(m_lines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { RefPointer line = static_cast(gen); if (!line) continue; unlock(); line->lock(); if (!line->engine()) iplugin.findEngine(line->m_engine,line->m_engineToUse); if (line->engine() && line->m_oper == Login && !line->m_registered && !line->m_nextReg && !busy(line)) registerLine(line,true); line->unlock(); line = 0; lock(); } unlock(); } // Handle registration related transaction terminations void YIAXLineContainer::regTerminate(IAXEvent* event) { YIAXLine* line = findLine(event); DDebug(&iplugin,DebugAll,"YIAXLineContainer::regTerminate() ev=%d line=%p (%s)", event ? event->type() : -1,line,line ? line->toString().c_str() : ""); if (!line) return; IAXTransaction* tr = event->getTransaction(); line->lock(); String reason; bool accepted = false; bool rejected = false; switch (event->type()) { case IAXEvent::Accept: accepted = true; break; case IAXEvent::Reject: rejected = true; event->getList().getString(IAXInfoElement::CAUSE,reason); break; case IAXEvent::Timeout: reason = "timeout"; break; case IAXEvent::Terminated: reason = "failure"; break; default: line->unlock(); TelEngine::destruct(line); return; } line->m_remote.clear(); // Line is going to perform another operation if (line->engine()) { // Engine changed ? if (line->engine() == tr->getEngine()) { setTransaction(line,0,false); bool reg = (tr->type() == IAXTransaction::RegReq); bool notify = true; // Operation changed ? if (line->m_oper == Login) { if (reg) { if (accepted) { // Honor server registration refresh. Assume default (60) if missing unsigned int interval = 60; if (event->getList().getNumeric(IAXInfoElement::REFRESH,interval) && !interval) interval = 60; if (line->m_expire != interval) { DDebug(&iplugin,DebugNote, "Line(%s) changed expire interval from %d to %u [%p]", line->toString().c_str(),line->m_expire,interval,line); line->m_expire = interval; } if (tr->callingNo()) line->m_callingNo = tr->callingNo(); if (tr->callingName()) line->m_callingName = tr->callingName(); // Re-register at 75% of the expire time line->setNextRegFromExpire(3,4); Lock lck(tr); line->m_remote = tr->remoteAddr(); } else if (rejected) // retry at 25% of the expire time line->setNextRegFromExpire(1,4); else // retry at 50% of the expire time line->setNextRegFromExpire(1,2); } else { notify = false; registerLine(line,true); } } else if (line->m_oper == Logout || line->m_oper == Delete) { if (reg && accepted) { line->m_registered = true; notify = false; registerLine(line,false); } else line->m_nextReg = 0; } if (notify) { const char* what = (line->m_oper == Login) ? "logon" : "logoff"; const char* tf = (line->m_oper == Login) ? "to" : "from"; if (accepted) Debug(&iplugin,DebugCall,"IAX line '%s' %s success %s %s:%d", line->toString().c_str(),what,tf,line->m_remoteAddr.c_str(), line->m_remotePort); else Debug(&iplugin,DebugWarn,"IAX line '%s' %s failure %s %s:%d reason=%s", line->toString().c_str(),what,tf,line->m_remoteAddr.c_str(), line->m_remotePort,reason.safe()); line->setRegistered(reg && accepted,reason,rejected ? "noauth" : 0); } } else { bool process = true; // Unregister from the old one if registered if (accepted && tr->type() == IAXTransaction::RegReq) { RefPointer tmp = line->engine(); line->m_engine = static_cast(tr->getEngine()); setTransaction(line,0,false); process = !(line->engine() && registerLine(line,false)); line->m_engine = tmp; tmp = 0; } // Register to the new one if (process) { setTransaction(line,0,false); if (line->m_oper == Login) registerLine(line,true); } } } else { // Line has no engine // Unregister from the old one if registered if (accepted && tr->type() == IAXTransaction::RegReq) line->m_engine = static_cast(tr->getEngine()); setTransaction(line,0,false); if (!(line->engine() && registerLine(line,false))) line->setRegistered(false,reason,rejected ? "noauth" : 0); line->m_engine = 0; line->m_nextReg = 0; } bool remove = (line->m_oper == Delete && !busy(line)); line->unlock(); if (remove) { Lock lock(this); m_lines.remove(line); } TelEngine::destruct(line); } // Handle registration related authentication void YIAXLineContainer::regAuthReq(IAXEvent* event) { YIAXLine* line = findLine(event); DDebug(&iplugin,DebugAll,"YIAXLineContainer::regAuthReq() ev=%d line=%p", event ? event->type() : -1,line); IAXTransaction* tr = event->getTransaction(); if (!line) { if (tr) tr->setDestroy(); return; } line->lock(); String response; tr->getEngine()->getMD5FromChallenge(response,tr->challenge(),line->password()); line->unlock(); tr->sendAuthReply(response); TelEngine::destruct(line); } // Handle registration related events void YIAXLineContainer::handleEvent(IAXEvent* event) { switch (event->type()) { case IAXEvent::Accept: case IAXEvent::Reject: case IAXEvent::Timeout: case IAXEvent::Terminated: regTerminate(event); break; case IAXEvent::AuthReq: regAuthReq(event); break; default: if (event->getTransaction()) event->getTransaction()->getEngine()->defaultEventHandler(event); } } // Tick the timer for all lines, send keepalives and reregister void YIAXLineContainer::evTimer(Time& time) { lock(); ListIterator iter(m_lines); GenObject* gen = 0; while (0 != (gen = iter.get())) { RefPointer line = static_cast(gen); if (!line) continue; unlock(); line->lock(); if (line->m_nextReg && line->m_nextReg <= time) registerLine(line,line->m_oper == Login); else if (line->m_registered && line->m_keepAliveInterval && time > line->m_nextKeepAlive) { if (line->m_nextKeepAlive && line->remote().host() && line->engine() && line->engine()->valid()) { DDebug(&iplugin,DebugAll,"Line(%s) sending keep alive to %s:%d [%p]", line->toString().c_str(),line->remote().host().c_str(), line->remote().port(),(YIAXLine*)line); line->engine()->keepAlive(line->remote()); } line->m_nextKeepAlive = time + line->m_keepAliveInterval * 1000000; } else if (line->m_oper == Delete && !busy(line)) m_lines.remove((YIAXLine*)line); line->unlock(); line = 0; lock(); } unlock(); } // Fill parameters with information taken from line void YIAXLineContainer::fillList(YIAXLine& line, NamedList& dest, SocketAddr& addr) { line.lock(); dest.setParam("username",line.username()); dest.setParam("password",line.password()); if (line.callingNo()) dest.setParam("caller",line.callingNo()); if (line.callingName()) dest.setParam("callername",line.callingName()); String a = line.remoteAddr(); int p = line.remotePort(); line.unlock(); addr.host(a); addr.port(p); } // Find line from event. Clear event transaction if line is not found // Return referenced pointer YIAXLine* YIAXLineContainer::findLine(IAXEvent* ev) { if (!ev) return 0; IAXTransaction* tr = ev->getTransaction(); if (!tr) return 0; Lock2 lck(this,&m_lineTransMutex); YIAXLine* line = findLine(static_cast(tr->getUserData())); bool ok = line && line->m_transaction == tr && line->ref(); if (!ok) tr->setUserData(0); lck.drop(); if (!ok) { tr->abortReg(); line = 0; } return line; } // Initiate line (un)register bool YIAXLineContainer::registerLine(YIAXLine* line, bool reg) { if (!line) return false; line->m_nextReg = 0; line->m_nextKeepAlive = 0; setTransaction(line); if (!reg) line->m_registered = false; if (!(line->engine() && line->engine()->bound())) return false; // Deny register on exiting engines if (reg && line->engine()->exiting()) { line->m_engine = 0; return false; } SocketAddr addr(AF_INET); addr.host(line->remoteAddr()); addr.port(line->remotePort()); Debug(&iplugin,DebugAll,"%s line '%s' listener='%s' server=%s:%d [%p]", reg ? "Registering" : "Unregistering",line->toString().c_str(), line->engine()->name().c_str(),addr.host().c_str(),addr.port(),line); // Create IE list IAXIEList ieList; ieList.appendString(IAXInfoElement::USERNAME,line->username()); ieList.appendString(IAXInfoElement::PASSWORD,line->password()); ieList.appendNumeric(IAXInfoElement::REFRESH,line->expire(),2); if (line->callToken()) ieList.appendBinary(IAXInfoElement::CALLTOKEN,0,0); IAXTransaction* tr = line->engine()->reg(reg,addr,ieList); if (!tr) return false; setTransaction(line,tr); tr->start(); TelEngine::destruct(tr); return true; } // Unregister all lines void YIAXLineContainer::clear(bool final, YIAXEngine* eng) { if (final && !eng) { Lock lock(this); m_lines.clear(); return; } lock(); ListIterator iter(m_lines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { RefPointer line = static_cast(gen); if (!line) continue; unlock(); line->lock(); if (!eng || eng == line->engine()) { if (final) { setTransaction(line); line->m_engine = 0; if (line->registered()) line->setRegistered(false,"notransport"); } else if (line->registered()) { setTransaction(line); registerLine(line,false); } } line->unlock(); line = 0; lock(); } unlock(); } // // Engine thread // void YIAXThread::run() { if (!m_engine) return; DDebug(m_engine,DebugAll,"%s started [%p]",currentName(),this); switch (m_oper) { case Listener: m_engine->readSocket(); break; case GetEvent: m_engine->runGetEvents(); break; case Trunking: m_engine->runProcessTrunkFrames(); break; default: DDebug(m_engine,DebugStub,"YIAXThread::run() not defined oper %d",m_oper); } notify(false); } void YIAXThread::notify(bool final) { if (!m_engine) return; if (!final) DDebug(m_engine,DebugAll,"Thread '%s' terminated [%p]",operThName(),this); else Debug(m_engine,DebugWarn,"Thread '%s' abnormally terminated [%p]", operThName(),this); m_engine->threadTerminated(this); m_engine = 0; } // // YIAXEngine // YIAXEngine::YIAXEngine(const String& name, const char* iface, int port, const NamedList* params) : IAXEngine(iface,port,0,0,params,name), m_initialized(true), m_default(false), m_timeout(0), m_status(Idle), m_bindAddr(iface), m_bindPort(port) { if (socket().valid()) changeStatus(Listening); else changeStatus(BindFailed); } void YIAXEngine::changeStatus(int stat) { if (stat == m_status) return; Debug(this,DebugInfo,"Status changed %s -> %s [%p]", statusName(),statusName(stat),this); m_status = stat; } bool YIAXEngine::bind(const NamedList& params) { bool force = false; getBindAddr(params,m_bindAddr,m_bindPort,&force); if (IAXEngine::bind(m_bindAddr,m_bindPort,force)) { changeStatus(Listening); return true; } changeStatus(BindFailed); return false; } // Schedule for termination void YIAXEngine::setTerminate(int stat) { setExiting(); Lock lck(this); changeStatus(stat); RefPointer ti; trunkInfo(ti); unsigned int tout = ti ? overallTout(ti->m_retransInterval,ti->m_retransCount) : overallTout(); ti = 0; tout += s_extendEngTerminate; m_timeout = Time::now() + tout * 1000; Debug(this,DebugAll,"Set terminate timeout=%ums status=%s [%p]", tout,statusName(),this); } // Setup a given transaction from parameters void YIAXEngine::initTransaction(IAXTransaction* tr, const NamedList& params, YIAXLine* line) { if (!tr) return; String prefix = tr->outgoing() ? "trunkout" : "trunkin"; String pref = prefix + "_"; IAXTrunkInfo* trunk = 0; Lock lckLine(line); bool trunkOut = params.getBoolValue(prefix,line && line->trunking()); if (line && line->trunkInfo().getParam(0)) { RefPointer def; tr->getEngine()->trunkInfo(def); trunk = new IAXTrunkInfo; trunk->initTrunking(line->trunkInfo(),String::empty(),def,trunkOut,true); trunk->updateTrunking(params,pref,trunkOut,true); } lckLine.drop(); tr->getEngine()->initOutDataAdjust(params,tr); if (trunkOut) { if (trunk) enableTrunking(tr,*trunk); else enableTrunking(tr,¶ms,pref); } if (trunk) initTrunkIn(tr,*trunk); else initTrunkIn(tr,¶ms,pref); TelEngine::destruct(trunk); } // Handle received voice data, forward it to connection's source void YIAXEngine::processMedia(IAXTransaction* transaction, DataBlock& data, u_int32_t tStamp, int type, bool mark) { if (transaction) { YIAXConnection* conn = static_cast(transaction->getUserData()); if (conn) { DataSource* src = conn->getSourceMedia(type); if (src) { unsigned long flags = 0; if (mark) flags = DataNode::DataMark; src->Forward(data,tStamp,flags); } else DDebug(this,DebugAll,"processMedia. No media source [%p]",this); } else DDebug(this,DebugAll, "processMedia. Transaction doesn't have a connection [%p]",this); } else DDebug(this,DebugAll,"processMedia. No transaction [%p]",this); } // Create a new call transaction from target address and message params IAXTransaction* YIAXEngine::call(SocketAddr& addr, NamedList& params) { Debug(this,DebugAll, "Outgoing Call: username=%s host=%s:%d called=%s called context=%s [%p]", params.getValue("username"),addr.host().c_str(),addr.port(), params.getValue("called"),params.getValue("calledname"),this); // Set format and capabilities u_int32_t audioCaps = capability(); u_int32_t videoCaps = 0; iplugin.updateCodecsFromRoute(audioCaps,params,IAXFormat::Audio); if (params.getBoolValue("media_video")) { videoCaps = capability(); iplugin.updateCodecsFromRoute(videoCaps,params,IAXFormat::Video); } u_int32_t fmtAudio = IAXFormat::pickFormat(audioCaps,format(true)); u_int32_t fmtVideo = IAXFormat::pickFormat(videoCaps,format(false)); u_int32_t codecs = audioCaps | videoCaps; u_int32_t fmt = fmtAudio | fmtVideo; if (!fmt) { DDebug(this,DebugNote,"Outgoing call failed: No preffered format [%p]",this); params.setParam("error","nomedia"); return 0; } // Create IE list IAXIEList ieList; ieList.appendString(IAXInfoElement::USERNAME,params.getValue("username")); ieList.appendString(IAXInfoElement::PASSWORD,params.getValue("password")); ieList.appendString(IAXInfoElement::CALLING_NUMBER,params.getValue("caller")); int tmp = 0; if (getKeyword(params,YSTRING("callernumtype"),IAXInfoElement::s_typeOfNumber,tmp)) ieList.appendNumeric(IAXInfoElement::CALLINGTON,tmp,1); u_int8_t cPres = callingPres(); if (getKeyword(params,YSTRING("callerpres"),IAXInfoElement::s_presentation,tmp)) cPres = (cPres & 0x0f) | tmp; if (getKeyword(params,YSTRING("callerscreening"),IAXInfoElement::s_screening,tmp)) cPres = (cPres & 0xf0) | tmp; ieList.appendNumeric(IAXInfoElement::CALLINGPRES,cPres,1); ieList.appendString(IAXInfoElement::CALLING_NAME,params.getValue("callername")); ieList.appendString(IAXInfoElement::CALLED_NUMBER,params.getValue("called")); ieList.appendString(IAXInfoElement::CALLED_CONTEXT,params.getValue("iaxcontext")); ieList.appendNumeric(IAXInfoElement::FORMAT,fmt,4); ieList.appendNumeric(IAXInfoElement::CAPABILITY,codecs,4); if (params.getBoolValue("calltoken_out",s_callTokenOut)) ieList.appendBinary(IAXInfoElement::CALLTOKEN,0,0); return startLocalTransaction(IAXTransaction::New,addr,ieList,true,false); } // Create a POKE transaction IAXTransaction* YIAXEngine::poke(SocketAddr& addr) { Debug(this,DebugAll,"Outgoing POKE: Host: %s Port: %d [%p]", addr.host().c_str(),addr.port(),this); IAXIEList ieList; return startLocalTransaction(IAXTransaction::Poke,addr,ieList); } // Cancel threads void YIAXEngine::cancelThreads(unsigned int waitTerminateMs) { Lock lck(this); ObjList* o = m_threads.skipNull(); if (!o) return; for (; o; o = o->skipNext()) (static_cast(o->get()))->cancel(false); if (!waitTerminateMs) return; lck.drop(); unsigned int intervals = waitTerminateMs / Thread::idleMsec(); if (!intervals) intervals++; for (; intervals; intervals--) { Thread::idle(false); if (Thread::check(false)) break; Lock lck(this); if (!m_threads.skipNull()) break; } } void YIAXEngine::destroyed() { cancelThreads(); Debug(this,DebugAll,"Destroyed [%p]",this); RefObject::destroyed(); } // Process all IAX events void YIAXEngine::processEvent(IAXEvent* event) { IAXTransaction* tr = event ? event->getTransaction() : 0; if (!tr) { if (event) delete event; return; } if (tr->type() == IAXTransaction::New) { if (tr->getUserData()) { Channel* chan = static_cast(tr->getUserData()); YIAXConnection* conn = static_cast(iplugin.findChan(chan)); if (conn) { // We already have a channel for this call conn->handleEvent(event); if (event->final()) { // Final event: disconnect DDebug(this,DebugAll,"processEvent. Disconnecting (%p): '%s' [%p]", conn,conn->id().c_str(),this); conn->disconnect(); } TelEngine::destruct(conn); } else { Debug(this,DebugNote, "No connection (%p) for transaction (%p) callno=%u [%p]", conn,tr,tr->localCallNo(),this); tr->setDestroy(); } } else if (event->type() == IAXEvent::New) { // Incoming request for a new call if (iplugin.canAccept(true) && !exiting()) { YIAXConnection* conn = new YIAXConnection(tr); conn->initChan(); tr->setUserData(conn); if (!conn->route(event)) tr->setUserData(0); } else { Debug(&iplugin,DebugWarn,"Refusing new IAX call, full or exiting [%p]", this); // Cause code 42: switch congestion tr->sendReject(0,42); } } } else if (tr->type() == IAXTransaction::RegReq || tr->type() == IAXTransaction::RegRel) { if (tr->outgoing()) s_lines.handleEvent(event); else if (event->type() == IAXEvent::New || event->type() == IAXEvent::AuthRep) processRemoteReg(event,(event->type() == IAXEvent::New)); } delete event; } // Process a new format received with a full frame bool YIAXEngine::mediaFormatChanged(IAXTransaction* trans, int type, u_int32_t format) { if (!trans) return false; Debug(this,DebugNote,"Refusing media change for transaction (%p) [%p]",trans,this); // TODO: implement return false; } // Process events for remote users registering to us void YIAXEngine::processRemoteReg(IAXEvent* event, bool first) { IAXTransaction* tr = event->getTransaction(); DDebug(this,DebugAll,"processRemoteReg: %s username: '%s' [%p]", tr->type() == IAXTransaction::RegReq?"Register":"Unregister", tr->username().c_str(),this); // Check for automatomatically authentication request if it's the first request if (first && s_authReq) { DDebug(this,DebugAll,"processRemoteReg. Requesting authentication [%p]",this); tr->sendAuth(); return; } // Authenticated: register/unregister bool requestAuth = false, invalidAuth = false; if (iplugin.userAuth(tr,!first,requestAuth,invalidAuth)) { // Authenticated. Try to (un)register const char* user = tr->username(); bool regrel = (event->subclass() == IAXControl::RegRel); Message* msg = userreg(tr,regrel); if (msg) { unsigned int exp = 0; if (!regrel) { int expires = msg->getIntValue("expires"); if (expires < 1) expires = s_expires_def; exp = expires; Debug(&iplugin,DebugNote,"Registered user '%s' expires in %us [%p]", user,exp,this); } else Debug(&iplugin,DebugNote,"Unregistered user '%s' [%p]",user,this); tr->sendAccept(&exp); TelEngine::destruct(msg); } else { Debug(&iplugin,DebugNote,"%s failed for authenticated user '%s' [%p]", regrel ? "Unregister" : "Register",user,this); tr->sendReject("not registered"); } return; } // First request: check if we should request auth if (first && requestAuth) { DDebug(this,DebugAll,"processRemoteReg. Requesting authentication [%p]",this); tr->sendAuth(); return; } const char* reason = 0; if (invalidAuth) reason = IAXTransaction::s_iax_modInvalidAuth; if (!reason) reason = "not authenticated"; Debug(&iplugin,DebugNote,"User '%s' addr=%s:%d not authenticated reason=%s' [%p]", tr->username().c_str(),tr->remoteAddr().host().c_str(), tr->remoteAddr().port(),reason,this); tr->sendReject(reason); } // Build and dispatch the user.(un)register message Message* YIAXEngine::userreg(IAXTransaction* tr, bool regrel) { DDebug(this,DebugAll,"YIAXEngine - userreg. %s username: '%s' [%p]", regrel ? "Unregistering":"Registering",tr->username().c_str(),this); Message* msg = new Message(regrel ? "user.unregister" : "user.register"); msg->addParam("username",tr->username()); msg->addParam("driver","iax"); if (!regrel) { IAXURI uri(tr->username(),tr->remoteAddr().host(),tr->calledNo(), tr->calledContext(),tr->remoteAddr().port()); String data = "iax/" + uri; msg->addParam("data",data); unsigned int exp = tr->expire(); if (!exp) exp = s_expires_def; else if (exp < s_expires_min) exp = s_expires_min; else if (exp > s_expires_max) exp = s_expires_max; msg->addParam("expires",String(exp)); } msg->addParam("ip_host",tr->remoteAddr().host()); msg->addParam("ip_port",String(tr->remoteAddr().port())); fillMessage(*msg); if (!Engine::dispatch(*msg)) TelEngine::destruct(msg); return msg; } void YIAXEngine::createThreads(const NamedList& params) { unsigned int read = params.getIntValue("read_threads",Engine::clientMode() ? 1 : 3,1); unsigned int event = params.getIntValue("event_threads",Engine::clientMode() ? 1 : 3,1); unsigned int trunking = params.getIntValue("trunk_threads",1,0); Thread::Priority prio = Thread::priority(params.getValue("thread")); createThreads(read,YIAXThread::Listener,prio); createThreads(event,YIAXThread::GetEvent,Thread::Normal); createThreads(trunking,YIAXThread::Trunking,prio); } void YIAXEngine::createThreads(unsigned int count, int oper, Thread::Priority prio) { if (!count) return; Lock lck(this); unsigned int n = 0; for (unsigned int i = 0; i < count; i++) { YIAXThread* th = new YIAXThread(this,oper,prio); if (!th->startup()) continue; m_threads.append(th)->setDelete(false); n++; } if (n && oper == YIAXThread::Trunking) m_trunking = -1; if (n == count) Debug(this,DebugAll,"Created %u '%s' threads [%p]", n,lookup(oper,YIAXThread::s_operThName),this); else Debug(this,DebugNote,"Created %u/%u '%s' threads [%p]", n,count,lookup(oper,YIAXThread::s_operThName),this); } void YIAXEngine::threadTerminated(YIAXThread* th) { if (!th) return; Lock lck(this); if (m_threads.remove(th,false)) Debug(this,DebugAll,"Thread (%p) '%s' terminated [%p]",th,th->operThName(),this); } // // YIAXRegDataHandler // // Handler for outgoing registration messages bool YIAXRegDataHandler::received(Message& msg) { String tmp(msg.getValue("protocol")); if (tmp != "iax") return false; tmp = msg.getValue("account"); if (tmp.null()) return false; s_lines.updateLine(msg); return true; } // // YIAXDriver // YIAXDriver::YIAXDriver() : Driver("iax","varchans"), m_enginesMutex(false,"IAXEngineList"), m_defaultEngine(0), m_haveEngTerminate(0), m_format(0), m_formatVideo(0), m_capability(0), m_failedAuths(0), m_init(true) { Output("Loaded module YIAX"); } YIAXDriver::~YIAXDriver() { Output("Unloading module YIAX"); lock(); channels().clear(); unlock(); s_lines.clear(true); m_engines.clear(); } void YIAXDriver::initialize() { Output("Initializing module YIAX"); // Load configuration Configuration cfg(Engine::configFile("yiaxchan")); const NamedList& gen = safeSect(cfg,"general"); const NamedList& registrar = safeSect(cfg,"registrar"); s_callTokenOut = gen.getBoolValue("calltoken_out",true); maxChans(gen.getIntValue("maxchans",maxChans())); s_expires_min = registrar.getIntValue("expires_min",60,1); s_expires_max = registrar.getIntValue("expires_max",3600,s_expires_min); s_expires_def = registrar.getIntValue("expires_def",60,s_expires_min,s_expires_max); s_authReq = registrar.getBoolValue("auth_required",true); initFormats(safeSect(cfg,"formats")); DDebug(this,DebugAll, "Initialized calltoken_out=%s expires_min=%u expires_max=%u expires_def=%u", String::boolText(s_callTokenOut),s_expires_min,s_expires_max,s_expires_def); if (m_init) { setup(); installRelay(Halt); installRelay(Route); installRelay(Progress); installRelay(Status); installRelay(EngineStop,"engine.stop"); Engine::install(new YIAXRegDataHandler(name())); m_init = false; } engineListChanging(true); bool upd = false; setupEngine(s_iaxEngineName,upd,true,gen.getBoolValue("default",true),gen); unsigned int n = cfg.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* p = cfg.getSection(i); if (!p) continue; String name = *p; if (!name.startSkip("listener ",false)) continue; name.trimBlanks(); if (!name) continue; if (isGeneralEngine(name)) { Debug(this,DebugConf,"Ignoring section '%s': '%s' is reserved",p->c_str(),name.c_str()); continue; } bool enable = p->getBoolValue("enable",true); bool def = p->getBoolValue("default"); setupEngine(name,upd,enable,def,*p,&gen); } engineListChanging(false); if (upd) s_lines.updateEngine(); } // Check if we have a line bool YIAXDriver::hasLine(const String& line) const { return line && s_lines.hasLine(line); } // Route calls that use a line owned by this driver bool YIAXDriver::msgRoute(Message& msg) { if (!isE164(msg.getValue("called"))) return false; return Driver::msgRoute(msg); } bool YIAXDriver::msgExecute(Message& msg, String& dest) { CallEndpoint* ch = YOBJECT(CallEndpoint,msg.userData()); if (!ch) { Debug(this,DebugAll,"IAX call failed: no endpoint"); return false; } NamedList params(msg); SocketAddr addr(AF_INET); RefPointer line; if (isE164(dest)) { const String& lName = params["line"]; if (!lName) { Debug(this,DebugNote,"IAX call to '%s' failed: no line",dest.c_str()); msg.setParam("error","failure"); return false; } if (!s_lines.findLine(line,lName)) { Debug(this,DebugNote,"IAX call failed: no line '%s'",lName.c_str()); msg.setParam("error","offline"); return false; } if (!line->registered()) { Debug(this,DebugNote,"IAX call failed: line '%s' is not registered", lName.c_str()); msg.setParam("error","offline"); return false; } s_lines.fillList(*line,params,addr); params.setParam("called",dest); } else { // dest should be an URI IAXURI uri(dest); uri.parse(); uri.fillList(params); uri.setAddr(addr); } if (!addr.host().length()) { Debug(this,DebugNote,"IAX call failed: no remote address"); msg.setParam("error","failure"); return false; } RefPointer engine; if (!(findEngine(engine,msg,"o",line) && engine->valid())) { Debug(this,DebugNote,"IAX call failed: no listener"); msg.setParam("error","notransport"); engine = 0; return false; } IAXTransaction* tr = engine->call(addr,params); if (!tr) { msg.copyParams(params,"error"); engine = 0; return false; } YIAXConnection* conn = new YIAXConnection(tr,&msg,¶ms); conn->initChan(); tr->setUserData(conn); engine->initTransaction(tr,msg,line); tr->start(); if (conn->connect(ch,msg.getValue("reason"))) { conn->callConnect(msg); msg.setParam("peerid",conn->id()); msg.setParam("targetid",conn->id()); } else { tr->setUserData(0); tr->setDestroy(); } TelEngine::destruct(tr); line = 0; engine = 0; conn->deref(); return true; } bool YIAXDriver::received(Message& msg, int id) { if (id == Timer) { s_lines.evTimer(msg.msgTime()); if (m_haveEngTerminate) checkEngineTerminate(msg.msgTime()); return Driver::received(msg,id); } if (id == Status) { String target = msg.getValue("module"); if (target && target.startsWith(name(),true) && !target.startsWith(prefix())) { msgStatus(msg); return false; } return Driver::received(msg,id); } if (id == EngineStop) { s_engineStop++; if (s_engineStop == 1) terminateEngine(0,false,YIAXEngine::Exiting); if (m_haveEngTerminate) checkEngineTerminate(msg.msgTime()); if (m_haveEngTerminate) Debug(this,DebugAll,"Returning true from '%s' handler",msg.c_str()); return m_haveEngTerminate != 0; } if (id == Halt) { dropAll(msg); lock(); channels().clear(); unlock(); s_lines.clear(true); terminateEngine(0,true,YIAXEngine::Exiting); return Driver::received(msg,id); } return Driver::received(msg,id); } // Find an engine from parameters. Return a refferenced pointer bool YIAXDriver::findEngine(RefPointer& eng, const NamedList& params, const String& prefix, YIAXLine* line, NamedList* copyParams) { if (copyParams) { if (prefix) copyParams->copySubParams(params,prefix); else copyParams->copyParams(params, "connection_id,ip_transport_localip,ip_transport_localport"); } Lock lck(m_enginesMutex); const String& engName = params[prefix + "connection_id"]; if (engName) { eng = findEngine(engName); if (eng) return true; } String addr = params[prefix + "ip_transport_localip"]; addr.trimBlanks(); if (addr) { int port = IAXEngine::getPort(params,prefix + "ip_transport_localport"); eng = findEngine(addr,port); if (eng) return true; } // Done if engine name and/or local ip were given if (engName || addr) return false; // Check for line engine if (line) { Lock lck(line); eng = line->engine(); if (eng) return true; } eng = m_defaultEngine; return eng != 0; } // Extract individual codecs from 'formats' // Check if IAXFormat contains it // Before exiting: update 'codecs' bool YIAXDriver::updateCodecsFromRoute(u_int32_t& codecs, const NamedList& params, int type) { String* formats = 0; if (type == IAXFormat::Audio) formats = params.getParam("formats"); if (TelEngine::null(formats)) { String tmp("formats_"); tmp << dataEpName(type); formats = params.getParam(tmp); } XDebug(this,DebugAll,"updateCodecsFromRoute(%u,%d) formats=%s", codecs,type,TelEngine::c_safe(formats)); if (formats) { // Set intersection if (type == IAXFormat::Audio) codecs &= IAXFormat::encode(*formats,dict_payloads); else if (type == IAXFormat::Video) codecs &= IAXFormat::encode(*formats,dict_payloads_video); else codecs = 0; } else { // Reset formats for non audio // They must be explicitely set if (type != IAXFormat::Audio) codecs = 0; } return codecs != 0; } bool YIAXDriver::userAuth(IAXTransaction* tr, bool response, bool& requestAuth, bool& invalidAuth, const char* billid) { bool newCall = (tr->type() == IAXTransaction::New); DDebug(this,DebugAll,"YIAXDriver::userAuth(%p,%u) newcall=%u",tr,response,newCall); requestAuth = invalidAuth = false; // Create and dispatch user.auth Message msg("user.auth"); msg.addParam("protocol","iax"); msg.addParam("username",tr->username()); msg.addParam("called",tr->calledNo()); msg.addParam("caller",tr->callingNo()); msg.addParam("callername",tr->callingName()); msg.addParam("ip_host",tr->remoteAddr().host()); msg.addParam("ip_port",String(tr->remoteAddr().port())); if (response) { msg.addParam("nonce",tr->challenge()); msg.addParam("response",tr->authdata()); } msg.addParam("billid",billid,false); msg.addParam("newcall",String::boolText(newCall)); if (!Engine::dispatch(msg)) return false; String pwd = msg.retValue(); // We have a password if (pwd) { // Not a response: request authentication if (!response) { requestAuth = true; return false; } // Check response if (!IAXEngine::isMD5ChallengeCorrect(tr->authdata(),tr->challenge(),pwd)) { invalidAuth = true; m_failedAuths++; changed(); Message* fail = new Message(msg); *fail = "user.authfail"; fail->retValue().clear(); Engine::enqueue(fail); return false; } } return true; } // Start stop engine list changes void YIAXDriver::engineListChanging(bool start) { m_enginesMutex.lock(); if (start) for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) (static_cast(o->get()))->m_initialized = false; else { ListIterator iter(m_engines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { YIAXEngine* engine = static_cast(gen); if (engine->m_initialized) continue; RefPointer eng = engine; if (!eng) continue; m_enginesMutex.unlock(); terminateEngine(eng,false,YIAXEngine::Removed); eng = 0; m_enginesMutex.lock(); } updateDefaultEngine(); } m_enginesMutex.unlock(); } // Setup an engine void YIAXDriver::setupEngine(const String& name, bool& valid, bool enable, bool def, const NamedList& params, const NamedList* defaults) { if (!name) return; Lock lck(m_enginesMutex); NamedList p(""); const NamedList* init = ¶ms; if (enable && defaults) { p.copyParams(*defaults); if (!isGeneralEngine(name)) clearListParams(p,s_delEngParams); p.copyParams(params); init = &p; } #ifdef XDEBUG String s; init->dump(s,"\r\n"); Debug(this,DebugAll,"setupEngine(%s,%u,%u)%s",name.c_str(),enable,def,s.safe()); #endif YIAXEngine* eng = findEngine(name); bool bound = false; if (eng) { eng->m_initialized = true; eng->m_default = def; if (!enable) { if (!eng->exiting() && eng->ref()) { lck.drop(); terminateEngine(eng,false,YIAXEngine::Disabled); eng->deref(); } return; } if (eng->exiting()) { Debug(this,DebugConf, "Ignoring init for listener '%s': scheduled for termination",name.c_str()); return; } if (!eng->bound()) { bound = eng->bind(params); if (!bound) return; } eng->initialize(*init); } else if (enable) { String addr; int port = 0; getBindAddr(params,addr,port); // Create the engine eng = new YIAXEngine(name,addr,port,init); eng->debugChain(this); eng->m_default = def; bound = eng->bound(); m_engines.append(eng); Debug(this,DebugInfo,"Added listener (%p) '%s' status='%s'", eng,eng->name().c_str(),eng->statusName()); } else return; if (bound) { int tos = init->getIntValue("tos",dict_tos,0); if (tos && !eng->socket().setTOS(tos)) Debug(eng,DebugWarn,"Could not set IP TOS to 0x%02x",tos); eng->createThreads(*init); valid = true; } // Init engine parameters from module eng->setFormats(m_capability,m_format,m_formatVideo); } // Terminate exiting engines void YIAXDriver::checkEngineTerminate(const Time& now) { m_enginesMutex.lock(); if (!m_haveEngTerminate) { m_enginesMutex.unlock(); return; } ListIterator iter(m_engines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { YIAXEngine* engine = static_cast(gen); if (!engine->exiting()) continue; RefPointer eng = engine; if (!eng) continue; m_enginesMutex.unlock(); if (eng->timeout(now) || !eng->haveTransactions()) terminateEngine(eng,true,eng->m_status); eng = 0; m_enginesMutex.lock(); } m_enginesMutex.unlock(); } bool YIAXDriver::commandComplete(Message& msg, const String& partLine, const String& partWord) { if (partLine == ("status " + name()) || partLine == ("status overview " + name())) { itemComplete(msg.retValue(),"accounts",partWord); itemComplete(msg.retValue(),"listeners",partWord); } return Driver::commandComplete(msg,partLine,partWord); } void YIAXDriver::msgStatus(Message& msg) { String str = msg.getValue("module"); while (str.startSkip(name())) { str.trimBlanks(); if (str.null()) break; if (str.startSkip("accounts")) { msgStatusAccounts(msg); return; } if (str.startSkip("listeners")) { msgStatusListeners(msg); return; } } Driver::msgStatus(msg); } void YIAXDriver::statusParams(String& str) { Driver::statusParams(str); m_enginesMutex.lock(); unsigned int cnt = 0; ListIterator iter(m_engines); while (YIAXEngine* eng = static_cast(iter.get())) cnt += eng->transactionCount(); m_enginesMutex.unlock(); str.append("transactions=",",") << cnt; } void YIAXDriver::msgStatusAccounts(Message& msg) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=IAX"; msg.retValue() << ",format=Username|Status;"; unsigned int n = 0; String det; bool details = msg.getBoolValue("details",true); s_lines.lock(); for (ObjList* o = s_lines.lines().skipNull(); o; o = o->skipNext()) { n++; if (!details) continue; YIAXLine* line = static_cast(o->get()); Lock lckLine(line); det.append(line->toString(),",") << "="; det.append(line->username()) << "|"; det << (line->registered() ? "online" : "offline"); } s_lines.unlock(); msg.retValue() << "accounts=" << n; msg.retValue().append(det,";"); msg.retValue() << "\r\n"; } void YIAXDriver::msgStatusListeners(Message& msg) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=IAX"; msg.retValue() << ",format=Address|Status|Transactions;"; unsigned int n = 0; String det; bool details = msg.getBoolValue("details",true); String def; m_enginesMutex.lock(); ListIterator iter(m_engines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { RefPointer e = static_cast(gen); if (!e) continue; n++; if (e == m_defaultEngine) def = e->name(); if (!details) continue; det.append(e->toString(),",") << "="; if (e->bound()) det << e->addr().host() << ":" << e->addr().port(); else det << e->m_bindAddr << ":" << e->m_bindPort; det << "|" << e->statusName(); m_enginesMutex.unlock(); det << "|" << e->transactionCount(); m_enginesMutex.lock(); } m_enginesMutex.unlock(); msg.retValue() << "listeners=" << n << ",default=" << def; msg.retValue().append(det,";"); msg.retValue() << "\r\n"; } // Add specific module update parameters void YIAXDriver::genUpdate(Message& msg) { unsigned int tmp = m_failedAuths; m_failedAuths = 0; msg.setParam("failed_auths",String(tmp)); } // Update default engine void YIAXDriver::updateDefaultEngine() { YIAXEngine* def = 0; for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) { YIAXEngine* eng = static_cast(o->get()); if (!eng->valid()) continue; if (eng->m_default && !def) { def = eng; break; } } m_defaultEngine = def; if (s_engineStop) return; if (m_defaultEngine) Debug(this,DebugInfo,"Default listener is '%s'",def->name().c_str()); else Debug(this,DebugNote,"No default listener set"); } // Terminate an engine void YIAXDriver::terminateEngine(YIAXEngine* eng, bool final, int reason) { XDebug(this,DebugAll,"terminateEngine(%p,%u,%d)",eng,final,reason); if (!eng) { m_enginesMutex.lock(); ListIterator iter(m_engines); for (GenObject* gen = 0; 0 != (gen = iter.get());) { RefPointer e = static_cast(gen); if (!e) continue; m_enginesMutex.unlock(); terminateEngine(e,final,reason); e = 0; m_enginesMutex.lock(); } m_enginesMutex.unlock(); return; } // Already set to terminate ? if (!final && eng->exiting()) return; if (!eng->exiting()) { eng->setTerminate(reason); // Unregister all users registered using this engine Message* m = new Message("user.unregister"); m->addParam("module",name()); m->addParam("connection_id",eng->toString()); Engine::enqueue(m); } // Drop all calls using the engine lock(); ListIterator iter(channels()); for (GenObject* gen = 0; 0 != (gen = iter.get());) { RefPointer c = static_cast(gen); if (!c) continue; unlock(); c->m_mutexTrans.lock(); if (c->m_transaction && c->m_transaction->getEngine() == eng) { c->disconnect("notransport"); if (final) c->resetTransaction(); } c->m_mutexTrans.unlock(); c = 0; lock(); } unlock(); // Unregister all lines using the engine s_lines.clear(final,eng); Lock lck(m_enginesMutex); // Remove the engine from list if (final && m_engines.remove(eng,false)) { Debug(this,DebugInfo,"Removed listener (%p) '%s' status='%s'", eng,eng->name().c_str(),eng->statusName()); if (eng == m_defaultEngine) updateDefaultEngine(); TelEngine::destruct(eng); } // Update engine terminate count unsigned int n = 0; for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) if ((static_cast(o->get()))->exiting()) n++; m_haveEngTerminate = n; } // Initialize formats from config void YIAXDriver::initFormats(const NamedList& params) { Lock lck(m_enginesMutex); u_int32_t codecsAudio = 0; u_int32_t codecsVideo = 0; u_int32_t defAudio = 0; u_int32_t defVideo = 0; bool def = params.getBoolValue("default",true); const TokenDict* dicts[2] = {dict_payloads,dict_payloads_video}; for (int i = 0; i < 2; i++) { u_int32_t fallback = 0; bool audio = (i == 0); const char* media = audio ? "audio" : "video"; const String& preferred = params[audio ? "preferred" : "preferred_video"]; u_int32_t& codecs = audio ? codecsAudio : codecsVideo; u_int32_t& defaultCodec = audio ? defAudio : defVideo; for (const TokenDict* d = dicts[i]; d->token; d++) { bool defVal = def && DataTranslator::canConvert(d->token); if (!params.getBoolValue(d->token,defVal)) continue; XDebug(this,DebugAll,"Adding supported %s codec %u: '%s'.", media,d->value,d->token); codecs |= d->value; fallback = d->value; // Set default (desired) codec if (!defaultCodec && preferred == d->token) defaultCodec = d->value; } // If desired codec is disabled fall back to last in list if (!defaultCodec) defaultCodec = fallback; if (codecs) { String tmp; IAXFormat::formatList(tmp,codecs,dicts[i]); Debug(this,DebugAll,"Enabled %s format(s) '%s' default=%s", media,tmp.c_str(),lookup(defaultCodec,dicts[i],"")); } else Debug(this,audio ? DebugWarn : DebugAll,"No %s format(s) available",media); } m_format = defAudio; m_formatVideo = defVideo; m_capability = codecsAudio | codecsVideo; } // // IAXConsumer // YIAXConsumer::YIAXConsumer(YIAXConnection* conn, u_int32_t format, const char* formatText, int type) : DataConsumer(formatText), YIAXData(conn,format,type) { Debug(m_connection,DebugAll,"YIAXConsumer '%s' format=%s [%p]", IAXFormat::typeName(m_type),formatText,this); } YIAXConsumer::~YIAXConsumer() { Debug(m_connection,DebugAll,"YIAXConsumer '%s' destroyed total=%u [%p]", IAXFormat::typeName(m_type),m_total,this); } unsigned long YIAXConsumer::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags) { unsigned long sent = 0; if (m_connection && !m_connection->mutedOut()) { m_total += data.length(); if (m_connection->transaction()) { bool mark = (flags & DataMark) != 0; sent = m_connection->transaction()->sendMedia(data,tStamp,format(),m_type,mark); } } return sent; } // // YIAXSource // YIAXSource::YIAXSource(YIAXConnection* conn, u_int32_t format, const char* formatText, int type) : DataSource(formatText), YIAXData(conn,format,type) { Debug(m_connection,DebugAll,"YIAXSource '%s' format=%s [%p]", IAXFormat::typeName(m_type),formatText,this); } YIAXSource::~YIAXSource() { Debug(m_connection,DebugAll,"YIAXSource '%s' destroyed total=%u [%p]", IAXFormat::typeName(m_type),m_total,this); } unsigned long YIAXSource::Forward(const DataBlock& data, unsigned long tStamp, unsigned long flags) { if (m_connection && m_connection->mutedIn()) return invalidStamp(); m_total += data.length(); return DataSource::Forward(data,tStamp,flags); } // // YIAXConnection // YIAXConnection::YIAXConnection(IAXTransaction* tr, Message* msg, NamedList* params) : Channel(&iplugin,0,tr->outgoing()), m_transaction(tr), m_mutedIn(false), m_mutedOut(false), m_audio(false), m_video(false), m_hangup(true), m_mutexChan(true,"YIAXConnection"), m_mutexTrans(true,"YIAXConnection::trans"), m_mutexRefIncreased(true,"YIAXConnection::refIncreased"), m_refIncreased(false), m_routeCount(0) { Debug(this,DebugAll,"%s call. Transaction (%p) callno=%u [%p]", isOutgoing() ? "Outgoing" : "Incoming",tr,tr->localCallNo(),this); setMaxcall(msg); if (msg) { setMaxPDD(*msg); setChanParams(*msg); } if (tr) m_address << tr->remoteAddr().host() << ":" << tr->remoteAddr().port(); if (msg) m_targetid = msg->getValue("id"); Message* m = message("chan.startup",msg); m->addParam("username",tr->username(),false); if (params) { // outgoing call m_password = params->getValue("password"); m->copyParams(*params,"caller,callername,called,billid,callto,username"); } else { // incoming call m->addParam("called",tr->calledNo(),false); m->addParam("caller",tr->callingNo(),false); m->addParam("callername",tr->callingName(),false); } Engine::enqueue(m); } YIAXConnection::~YIAXConnection() { status("destroyed"); setConsumer(); setSource(); hangup(0); m_transaction = 0; Debug(this,DebugAll,"Destroyed with reason '%s' [%p]",m_reason.safe(),this); } // Incoming call accepted, possibly set trunking on this connection void YIAXConnection::callAccept(Message& msg) { DDebug(this,DebugCall,"callAccept [%p]",this); m_mutexTrans.lock(); if (m_transaction) { YIAXEngine* eng = static_cast(m_transaction->getEngine()); u_int32_t codecs = eng->capability(); if (msg.getValue("formats")) { u_int32_t ca = IAXFormat::mask(codecs,IAXFormat::Audio); iplugin.updateCodecsFromRoute(ca,msg,IAXFormat::Audio); eng->acceptFormatAndCapability(m_transaction,&ca); } u_int32_t cv = IAXFormat::mask(codecs,IAXFormat::Video); iplugin.updateCodecsFromRoute(cv,msg,IAXFormat::Video); eng->acceptFormatAndCapability(m_transaction,&cv,IAXFormat::Video); u_int32_t ci = IAXFormat::mask(codecs,IAXFormat::Image); iplugin.updateCodecsFromRoute(ci,msg,IAXFormat::Image); eng->acceptFormatAndCapability(m_transaction,&ci,IAXFormat::Image); eng->initTransaction(m_transaction,msg); m_transaction->sendAccept(); } m_mutexTrans.unlock(); Channel::callAccept(msg); } // Call rejected, check if we have to authenticate caller void YIAXConnection::callRejected(const char* error, const char* reason, const Message* msg) { if (!error) error = reason; Lock lock(m_mutexTrans); if (m_routeCount == 1 && m_transaction && error && String(error) == "noauth") { if (safeRefIncrease()) { Debug(this,DebugAll,"Requesting authentication [%p]",this); m_transaction->sendAuth(); return; } error = "temporary-failure"; } lock.drop(); Channel::callRejected(error,reason,msg); hangup(0,error,reason,true); } bool YIAXConnection::callRouted(Message& msg) { // check if the caller did abort the call while routing if (!m_transaction) { Debug(this,DebugMild,"callRouted. No transaction: ABORT [%p]",this); return false; } DDebug(this,DebugAll,"callRouted [%p]",this); return true; } bool YIAXConnection::msgProgress(Message& msg) { Lock lock(&m_mutexTrans); if (m_transaction) { m_transaction->sendProgress(); // only start audio output for early media startMedia(false); return Channel::msgProgress(msg); } return false; } bool YIAXConnection::msgRinging(Message& msg) { Lock lock(&m_mutexTrans); if (m_transaction) { m_transaction->sendRinging(); // only start audio output for early media startMedia(false); return Channel::msgRinging(msg); } return false; } bool YIAXConnection::msgAnswered(Message& msg) { Lock lock(&m_mutexTrans); if (m_transaction) { m_transaction->sendAnswer(); // fully start media startMedia(true); startMedia(false); startMedia(true,IAXFormat::Video); startMedia(false,IAXFormat::Video); return Channel::msgAnswered(msg); } return false; } bool YIAXConnection::msgTone(Message& msg, const char* tone) { Lock lock(&m_mutexTrans); if (m_transaction) { while (tone && *tone) m_transaction->sendDtmf(*tone++); return true; } return false; } bool YIAXConnection::msgText(Message& msg, const char* text) { Lock lock(&m_mutexTrans); if (m_transaction) { m_transaction->sendText(text); return true; } return false; } void YIAXConnection::disconnected(bool final, const char* reason) { DDebug(this,DebugAll,"Disconnected. Final: %s . Reason: '%s' [%p]", String::boolText(final),reason,this); hangup(0,reason); Channel::disconnected(final,reason); safeDeref(); } bool YIAXConnection::disconnect(const char* reason) { Lock lck(m_mutexChan); if (!m_reason) m_reason = reason; String res = m_reason; lck.drop(); return Channel::disconnect(res,parameters()); } bool YIAXConnection::callPrerouted(Message& msg, bool handled) { // check if the caller did abort the call while prerouting if (!m_transaction) { Debug(this,DebugMild,"callPrerouted. No transaction: ABORT [%p]",this); return false; } DDebug(this,DebugAll,"callPrerouted [%p]",this); return true; } void YIAXConnection::handleEvent(IAXEvent* event) { switch(event->type()) { case IAXEvent::Text: { String text; event->getList().getString(IAXInfoElement::textframe,text); DDebug(this,DebugInfo,"TEXT: '%s' [%p]",text.safe(),this); Message* m = message("chan.text"); m->addParam("text",text); Engine::enqueue(m); } break; case IAXEvent::Dtmf: { String dtmf((char)event->subclass()); dtmf.toUpper(); DDebug(this,DebugCall,"DTMF: %s [%p]",dtmf.safe(),this); Message* m = message("chan.dtmf"); m->addParam("text",dtmf); m->addParam("detected","iax-event"); dtmfEnqueue(m); } break; case IAXEvent::Noise: DDebug(this,DebugInfo,"NOISE: %u [%p]",event->subclass(),this); break; case IAXEvent::Progressing: DDebug(this,DebugInfo,"CALL PROGRESSING [%p]",this); Engine::enqueue(message("call.progress",false,true)); break; case IAXEvent::Accept: DDebug(this,DebugCall,"ACCEPT [%p]",this); startMedia(true); break; case IAXEvent::Answer: if (isAnswered()) break; DDebug(this,DebugCall,"ANSWER [%p]",this); status("answered"); startMedia(true); startMedia(false); startMedia(true,IAXFormat::Video); startMedia(false,IAXFormat::Video); Engine::enqueue(message("call.answered",false,true)); break; case IAXEvent::Quelch: DDebug(this,DebugCall,"QUELCH [%p]",this); m_mutedOut = true; break; case IAXEvent::Unquelch: DDebug(this,DebugCall,"UNQUELCH [%p]",this); m_mutedOut = false; break; case IAXEvent::Ringing: DDebug(this,DebugInfo,"RINGING [%p]",this); startMedia(true); Engine::enqueue(message("call.ringing",false,true)); break; case IAXEvent::Hangup: case IAXEvent::Reject: { String error; String reason; event->getList().getString(IAXInfoElement::CAUSE,reason); u_int32_t code = 0; event->getList().getNumeric(IAXInfoElement::CAUSECODE,code); if (code) { const char* s = IAXInfoElement::causeName(code); if (s) error = s; else error = (unsigned int)code; } DDebug(this,DebugCall,"REJECT/HANGUP: error='%s' reason='%s' [%p]", error.c_str(),reason.c_str(),this); hangup(event->local() ? 1 : -1,error,reason); } break; case IAXEvent::Timeout: DDebug(this,DebugNote,"TIMEOUT. Transaction: %u,%u, Frame: %u,%u [%p]", event->getTransaction()->localCallNo(),event->getTransaction()->remoteCallNo(), event->frameType(),event->subclass(),this); if (event->final()) setReason("offline"); break; case IAXEvent::Busy: DDebug(this,DebugCall,"BUSY [%p]",this); if (event->final()) setReason("busy"); break; case IAXEvent::AuthRep: evAuthRep(event); break; case IAXEvent::AuthReq: evAuthReq(event); break; default: if (m_transaction) { Lock lck(m_mutexTrans); if (m_transaction) m_transaction->getEngine()->defaultEventHandler(event); } if (!m_transaction) event->setFinal(); } if (event->final()) { safeDeref(); m_transaction = 0; } } void YIAXConnection::hangup(int location, const char* error, const char* reason, bool reject) { Lock lck(m_mutexChan); if (!m_hangup) return; m_hangup = false; if (!m_reason) m_reason = error ? error : reason; if (!m_reason && location != -1) m_reason = Engine::exiting() ? "shutdown" : ""; const char* loc = location ? (location > 0 ? "internal" : "remote") : "peer"; clearMedia(true); clearMedia(false); clearMedia(true,IAXFormat::Video); clearMedia(false,IAXFormat::Video); Message* m = message("chan.hangup"); m->setParam("status","hangup"); m->setParam("reason",m_reason); Debug(this,DebugCall,"Hangup location=%s reason=%s [%p]",loc,m_reason.safe(),this); lck.drop(); resetTransaction(m,reject); Engine::enqueue(m); } void YIAXConnection::resetTransaction(NamedList* params, bool reject) { Lock lckChan(m_mutexChan); String reason = m_reason; lckChan.drop(); Lock lck(m_mutexTrans); if (!m_transaction) return; if (params) { IAXMediaData* d = m_audio ? m_transaction->getData(IAXFormat::Audio) : 0; if (d) { String tmp; d->print(tmp); params->addParam("iax_stats",tmp); } d = m_video ? m_transaction->getData(IAXFormat::Video) : 0; if (d) { String tmp; d->print(tmp); params->addParam("iax_stats_video",tmp); } } u_int8_t code = 0; if (reason) { int val = IAXInfoElement::causeCode(reason); if (val) code = val; else code = reason.toInteger(0,0,0,127); } if (reject) m_transaction->sendReject(reason,code); else m_transaction->sendHangup(reason,code); m_transaction->setUserData(0); m_transaction = 0; } bool YIAXConnection::route(IAXEvent* ev, bool authenticated) { Lock lck(&m_mutexTrans); if (!m_transaction) return false; if (m_routeCount >= 2) return false; m_routeCount++; Message* m = message("call.preroute",false,true); if (authenticated) { DDebug(this,DebugAll,"Route pass 2: Password accepted [%p]",this); m_refIncreased = false; m->addParam("username",m_transaction->username()); } else { DDebug(this,DebugAll,"Route pass 1: No username [%p]",this); if (!m_transaction->getEngine()->acceptFormatAndCapability(m_transaction)) { hangup(0,"nomedia",0,true); return false; } // Advertise the not yet authenticated username if (m_transaction->username()) m->addParam("authname",m_transaction->username()); } m->addParam("called",m_transaction->calledNo()); m->addParam("caller",m_transaction->callingNo()); if (ev) { u_int32_t val = 0; if (ev->getList().getNumeric(IAXInfoElement::CALLINGTON,val)) IAXEngine::addKeyword(*m,"callernumtype",IAXInfoElement::s_typeOfNumber,val); if (ev->getList().getNumeric(IAXInfoElement::CALLINGPRES,val)) { IAXEngine::addKeyword(*m,"callerpres",IAXInfoElement::s_presentation,val & 0xf0); IAXEngine::addKeyword(*m,"callerscreening",IAXInfoElement::s_screening,val & 0x0f); } } m->addParam("callername",m_transaction->callingName()); m->addParam("ip_host",m_transaction->remoteAddr().host()); m->addParam("ip_port",String(m_transaction->remoteAddr().port())); String fmtsAudio; IAXFormat::formatList(fmtsAudio,m_transaction->capability(),dict_payloads); m->addParam("formats",fmtsAudio); String fmtsVideo; IAXFormat::formatList(fmtsVideo,m_transaction->capability(),dict_payloads_video); if (fmtsVideo) { if (fmtsAudio) { m->addParam("media_audio",String::boolText(true)); m->addParam("formats_audio",fmtsAudio); } m->addParam("media_video",String::boolText(true)); m->addParam("formats_video",fmtsVideo); } (static_cast(m_transaction->getEngine()))->fillMessage(*m,false); lck.drop(); return startRouter(m); } // Retrieve the data consumer from IAXFormat media type YIAXConsumer* YIAXConnection::getConsumer(int type) { const String& name = dataEpName(type); return YOBJECT(YIAXConsumer,Channel::getConsumer(name)); } // Retrieve the data source from IAXFormat media type YIAXSource* YIAXConnection::getSource(int type) { const String& name = dataEpName(type); return YOBJECT(YIAXSource,Channel::getSource(name)); } DataSource* YIAXConnection::getSourceMedia(int type) { const String& name = dataEpName(type); DataSource* src = Channel::getSource(name); if (m_sources[type] != src) { m_sources[type] = 0; DataEndpoint::commonMutex().lock(); m_sources[type] = Channel::getSource(name); DataEndpoint::commonMutex().unlock(); } return m_sources[type]; } // Create audio source with the proper format void YIAXConnection::startMedia(bool in, int type) { u_int32_t format = 0; m_mutexTrans.lock(); if (m_transaction) { format = in ? m_transaction->formatIn(type) : m_transaction->formatOut(type); if (format) { if (type == IAXFormat::Audio) m_audio = true; else if (type == IAXFormat::Video) m_video = true; } } m_mutexTrans.unlock(); bool exists = false; if (in) { YIAXSource* src = getSource(type); if (src && src->format() == format) return; exists = (src != 0); } else { YIAXConsumer* cons = getConsumer(type); if (cons && cons->format() == format) return; exists = (cons != 0); } clearMedia(in,type); const String& epName = dataEpName(type); const char* formatText = format ? lookupFormat(format,type) : 0; const char* dir = in ? "incoming" : "outgoing"; if (exists || format) Debug(this,DebugAll,"Starting %s media '%s' format '%s' (%u) [%p]", dir,epName.c_str(),formatText,format,this); bool ok = true; if (in) { YIAXSource* src = 0; if (formatText) src = new YIAXSource(this,format,formatText,type); setSource(src,epName); TelEngine::destruct(src); ok = !format || getSource(type); } else { YIAXConsumer* cons = 0; if (formatText) cons = new YIAXConsumer(this,format,formatText,type); setConsumer(cons,epName); TelEngine::destruct(cons); ok = !format || getConsumer(type); } if (!ok) Debug(this,DebugNote,"Failed to start %s media '%s' format '%s' (%u) [%p]", dir,epName.c_str(),formatText,format,this); } void YIAXConnection::clearMedia(bool in, int type) { const String& epName = dataEpName(type); DDebug(this,DebugAll,"Clearing %s %s media [%p]", in ? "incoming" : "outgoing",epName.c_str(),this); if (in) setSource(0,epName); else setConsumer(0,epName); } void YIAXConnection::evAuthRep(IAXEvent* event) { DDebug(this,DebugAll,"AUTHREP [%p]",this); bool requestAuth, invalidAuth; const char* reason = "noauth"; if (iplugin.userAuth(event->getTransaction(),true,requestAuth,invalidAuth,billid())) { if (route(event,true)) return; Debug(this,DebugNote,"Failed to route. Rejecting [%p]",this); reason = "temporary-failure"; } else Debug(this,DebugNote,"Not authenticated. Rejecting [%p]",this); event->setFinal(); hangup(1,reason,0,true); } void YIAXConnection::evAuthReq(IAXEvent* event) { DDebug(this,DebugAll,"AUTHREQ [%p]",this); String response; Lock lck(m_mutexTrans); if (m_transaction) { m_transaction->getEngine()->getMD5FromChallenge(response, m_transaction->challenge(),m_password); m_transaction->sendAuthReply(response); } } // Get rid of the extra reference void YIAXConnection::safeDeref() { m_mutexRefIncreased.lock(); bool bref = m_refIncreased; m_refIncreased = false; m_mutexRefIncreased.unlock(); if (bref) deref(); } // Keep an extra reference to prevent destroying the connection bool YIAXConnection::safeRefIncrease() { bool ok = false; m_mutexRefIncreased.lock(); if (!m_refIncreased && ref()) m_refIncreased = ok = true; m_mutexRefIncreased.unlock(); return ok; } // // IAXURI // IAXURI::IAXURI(const char* user, const char* host, const char* calledNo, const char* calledContext, int port) : m_username(user), m_host(host), m_port(port), m_calledNo(calledNo), m_calledContext(calledContext), m_parsed(true) { *this << "iax2:"; if (m_username) *this << m_username << "@"; *this << m_host; if (m_port) *this << ":" << m_port; if (m_calledNo) { *this << "/" << m_calledNo; if (m_calledContext) *this << "@" << m_calledContext; } } void IAXURI::parse() { /* proto: user@ host :port /calledno @context proto: user@ host :port /calledno ?context */ if (m_parsed) return; String tmp(*this), _port; static const Regexp r("^\\([Ii][Aa][Xx]2\\?:\\)\\?\\([^[:space:][:cntrl:]@]\\+@\\)\\?\\([[:alnum:]._-]\\+\\)\\(:[0-9]\\+\\)\\?\\(/[[:alnum:]]*\\)\\?\\([@?][^@?:/]*\\)\\?$"); if (tmp.matches(r)) { m_username = tmp.matchString(2); m_username = m_username.substr(0,m_username.length() -1); m_host = tmp.matchString(3).toLower(); _port = tmp.matchString(4); m_port = _port.substr(1,_port.length()).toInteger(); m_calledNo = tmp.matchString(5); m_calledNo = m_calledNo.substr(1,m_calledNo.length()); m_calledContext = tmp.matchString(6); m_calledContext = m_calledContext.substr(1,m_calledContext.length()); } else { m_username = ""; m_host = ""; m_port = 0; m_calledNo = ""; m_calledContext = ""; } m_parsed = true; } // Pick URI parameters from a message or setting bool IAXURI::fillList(NamedList& dest) { if (!m_parsed) return false; if (m_username.length()) dest.setParam("username",m_username); if (m_calledNo.length()) dest.setParam("called",m_calledNo); if (m_calledContext.length()) dest.setParam("iaxcontext",m_calledContext); return true; } bool IAXURI::setAddr(SocketAddr& dest) { parse(); if (!m_host.length()) return false; dest.host(m_host); dest.port(m_port ? m_port : 0); return true; } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */