diff --git a/conf.d/yjinglechan.conf.sample b/conf.d/yjinglechan.conf.sample index 8184819c..427a25d9 100644 --- a/conf.d/yjinglechan.conf.sample +++ b/conf.d/yjinglechan.conf.sample @@ -95,6 +95,17 @@ ; resource.notify: integer: Override the priority of module's resource.notify message handler ;resource.notify= +; redirectcount: integer: Default value for outgoing calls redirect counter +; This parameter controls the behaviour of an outgoing call terminated +; with 'redirect' reason containing a non empty target +; If non 0 the counter is decreased. If the target is an XMPP uri the call will +; be re-executed internally. +; If the counter is 0 when terminated, the call parameters list will be filled with +; session parameters to let the PBX deal with it. +; This parameter can be overridden from routing +;redirectcount=0 + + [codecs] ; This section allows to individually enable or disable the codecs diff --git a/modules/yjinglechan.cpp b/modules/yjinglechan.cpp index 402e6ad5..5664362b 100644 --- a/modules/yjinglechan.cpp +++ b/modules/yjinglechan.cpp @@ -285,6 +285,8 @@ private: void handleAudioInfoEvent(JGEvent* event); // Check jingle version override from call.execute or resource caps void overrideJingleVersion(const NamedList& list, bool caps); + // Copy chan/session parameters to a destination list + void copySessionParams(NamedList& list, bool redirect = true); Mutex m_mutex; // Lock transport and session State m_state; // Connection state @@ -303,6 +305,7 @@ private: String m_localip; // Local address bool m_offerRawTransport; // Offer RAW transport on outgoing session bool m_offerIceTransport; // Offer ICE transport on outgoing session + unsigned int m_redirectCount; // Redirect counter // Crypto (for contents created by us) bool m_secure; // The channel is using crypto bool m_secureRequired; // Crypto is mandatory @@ -506,6 +509,7 @@ static bool s_acceptRelay = false; static bool s_offerRawTransport = true; // Offer RAW UDP transport on outgoing sessions static bool s_offerIceTransport = true; // Offer ICE UDP transport on outgoing sessions static int s_priority = 0; // Resource priority for presence generated by this module +static unsigned int s_redirectCount = 0; // Redirect counter static JGSession::Version s_sessVersion = JGSession::VersionUnknown; // Default jingle session version for outgoing calls static String s_capsNode = "http://yate.null.ro/yate/jingle/caps"; // node for entity capabilities static bool s_serverMode = true; // Server/client mode @@ -572,6 +576,18 @@ static inline void addValidParam(Message& m, const char* param, const char* valu m.addParam(param,value); } +// Add a parameter to a list. +// Optionally add it to a copy params string +static inline void jingleAddParam(NamedList& list, const char* param, const char* value, + String* copy, bool emptyOk = true) +{ + if (TelEngine::null(param)) + return; + list.addParam(param,value,emptyOk); + if (copy) + copy->append(param,","); +} + // Add formats to a list of jingle payloads static void setMedia(JGRtpMediaList& dest, const String& formats, const JGRtpMediaList& src) @@ -636,10 +652,15 @@ void YJGEngine::processEvent(JGEvent* event) delete event; return; } + plugin.lock(); YJGConnection* conn = static_cast(session->userData()); + if (conn) + conn->ref(); + plugin.unlock(); if (conn) { if (!conn->handleEvent(event) || event->final()) conn->disconnect(event->reason()); + TelEngine::destruct(conn); } else { if (event->type() == JGEvent::Jingle && @@ -700,11 +721,14 @@ YJGConnection::YJGConnection(Message& msg, const char* caller, const char* calle m_callerPrompt(msg.getValue("callerprompt")), m_localip(localip), m_offerRawTransport(true), m_offerIceTransport(true), + m_redirectCount(s_redirectCount), m_secure(s_useCrypto), m_secureRequired(s_cryptoMandatory), m_hangup(false), m_timeout(0), m_transferring(false), m_recvTransferStanza(0), m_dataFlags(0), m_ftStatus(FTNone), m_ftHostDirection(FTHostNone), m_connSocksServer(msg.getBoolValue("socksserver",true)) { + int redir = msg.getIntValue("redirectcount",m_redirectCount); + m_redirectCount = (redir >= 0) ? redir : 0; m_secure = msg.getBoolValue("secure",m_secure); m_secureRequired = msg.getBoolValue("secure_required",m_secureRequired); overrideJingleVersion(msg,false); @@ -790,7 +814,7 @@ YJGConnection::YJGConnection(JGEvent* event) m_sessVersion(event->session()->version()), m_local(event->session()->local()), m_remote(event->session()->remote()), m_audioContent(0), - m_offerRawTransport(true), m_offerIceTransport(true), + m_offerRawTransport(true), m_offerIceTransport(true), m_redirectCount(0), m_secure(s_useCrypto), m_secureRequired(s_cryptoMandatory), m_hangup(false), m_timeout(0), m_transferring(false), m_recvTransferStanza(0), m_dataFlags(0), m_ftStatus(FTNone), m_ftHostDirection(FTHostNone), @@ -1323,6 +1347,51 @@ bool YJGConnection::handleEvent(JGEvent* event) } if (event->type() == JGEvent::Terminated) { + // Handle redirect + if (isOutgoing() && event->reason() == "redirect" && event->text()) { + bool validCounter = false; + if (m_redirectCount) { + m_redirectCount--; + validCounter = true; + } + // Handle here XMPP targets + // Let the pbx deal with other targets + if (validCounter && event->text().startsWith("xmpp:",false)) { + JabberID callto(event->text().substr(5)); + if (callto.bare()) { + if (callto == m_remote) { + Debug(this,DebugNote,"Got redirect to the same remote party! [%p]",this); + callto.clear(); + } + } + else { + Debug(this,DebugNote,"Got redirect to incomplete jid=%s [%p]", + event->text().c_str(),this); + callto.clear(); + } + String id; + if (callto && getPeerId(id)) { + Message m("chan.masquerade"); + m.addParam("message","call.execute"); + m.addParam("id",id); + m.addParam("callto",plugin.prefix() + callto); + m.addParam("caller",m_local,false); + copySessionParams(m); + Debug(this,DebugCall,"Redirecting to '%s' [%p]",callto.c_str(),this); + lock.drop(); + Engine::dispatch(m); + } + } + else { + parameters().clearParams(); + URI uri(event->text()); + parameters().addParam("called",uri.getUser()); + parameters().addParam("calledname",uri.getDescription(),false); + parameters().addParam("calleduri",event->text()); + parameters().addParam("copyparams",""); + copySessionParams(parameters()); + } + } const char* reason = event->reason(); Debug(this,DebugInfo, "Session terminated with reason='%s' text='%s' [%p]", @@ -2906,6 +2975,52 @@ void YJGConnection::overrideJingleVersion(const NamedList& list, bool caps) } } +// Copy chan/session params to a destination list +void YJGConnection::copySessionParams(NamedList& list, bool redirect) +{ + String* copy = list.getParam("copyparams"); + if (redirect) { + list.addParam("redirect",String::boolText(true)); + jingleAddParam(list,"redirectcount",String(m_redirectCount),copy); + list.addParam("diverter",m_remote,false); + } + if (m_ftStatus == FTNone) + jingleAddParam(list,"formats",m_formats,copy,false); + else + jingleAddParam(list,"format","data",copy); + // Jingle session params + jingleAddParam(list,"line",m_line,copy,false); + jingleAddParam(list,"ojingle_version", + JGSession::lookupVersion(m_sessVersion,""),copy,false); + jingleAddParam(list,"callerprompt",m_callerPrompt,copy,false); + jingleAddParam(list,"subject",m_subject,copy,false); + jingleAddParam(list,"secure",String::boolText(m_secure),copy); + jingleAddParam(list,"secure_required",String::boolText(m_secureRequired),copy); + jingleAddParam(list,"offerrawudp",String::boolText(m_offerRawTransport),copy); + jingleAddParam(list,"offericeudp",String::boolText(m_offerIceTransport),copy); + // File transfer + JGSessionContent* c = firstFTContent(); + if (!c) + return; + const char* oper = 0; + if (c->type() == JGSessionContent::FileBSBOffer) + oper = "send"; + else if (c->type() == JGSessionContent::FileBSBRequest) + oper = "receive"; + else + return; + const String& file = c->m_fileTransfer["name"]; + if (!file) + return; + jingleAddParam(list,"operation",oper,copy); + jingleAddParam(list,"file_name",file,copy); + jingleAddParam(list,"file_size",c->m_fileTransfer.getValue("size"),copy,false); + jingleAddParam(list,"file_md5",c->m_fileTransfer.getValue("hash"),copy,false); + unsigned int t = XMPPUtils::decodeDateTimeSec(c->m_fileTransfer["date"]); + if (t != (unsigned int)-1) + jingleAddParam(list,"file_time",String(t),copy); +} + /* * Transfer thread (route and execute) @@ -3183,6 +3298,8 @@ void YJGDriver::initialize() m_localAddress = sect->getValue("localip"); s_offerRawTransport = sect->getBoolValue("offerrawudp",true); s_offerIceTransport = sect->getBoolValue("offericeudp",true); + int redir = sect->getIntValue("redirectcount"); + s_redirectCount = (redir >= 0) ? redir : 0; int prio = sect->getIntValue("resource_priority"); if (prio < -128) @@ -3271,6 +3388,7 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) msg.setParam("error","failure"); return false; } + bool checkCalled = msg.getBoolValue("checkcalled",true); const char* line = msg.getValue("line"); String localip; // Set caller @@ -3322,11 +3440,11 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) else { // Get line data if (!TelEngine::null(line)) { - Message* m = plugin.checkAccount(line,true,&called); + Message* m = plugin.checkAccount(line,true,checkCalled ? &called : 0); if (m) { caller.set(m->getValue("jid")); if (caller.isFull()) { - if (called && !called.resource()) + if (checkCalled && called && !called.resource()) called.resource(m->getValue("instance")); // Copy resource caps unsigned int n = m->length(); @@ -3354,7 +3472,7 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) return false; } // Called party must always be full in client mode - if (!(s_serverMode || called.isFull())) { + if (checkCalled && !(s_serverMode || called.isFull())) { Debug(this,DebugNote,"Jingle call failed. Incomplete called '%s'", called.c_str()); msg.setParam("error","failure"); @@ -3379,7 +3497,7 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) } } - bool online = !called.resource().null(); + bool online = !(checkCalled && called.resource().null()); bool local = (caller.domain() == called.domain()); if (!online) { bool reqSub = false;