From 7f30be17d0ee841961262ee9e9b8fab27ccf6d83 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Fri, 17 Apr 2009 20:00:30 +0000 Subject: [PATCH] experimental freeswitch code, imported from Rob Smart's tree --- .../FreeSwitchVoice/FreeSwitchDialplan.cs | 88 +++ .../FreeSwitchVoice/FreeSwitchDirectory.cs | 335 ++++++++++ .../FreeSwitchVoice/FreeSwitchVoiceModule.cs | 571 ++++++++++++++++++ 3 files changed, 994 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDialplan.cs create mode 100644 OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDirectory.cs create mode 100644 OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDialplan.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDialplan.cs new file mode 100644 index 0000000000..2a2b4a353f --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDialplan.cs @@ -0,0 +1,88 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using log4net; +using System.Reflection; +using System.Collections; + +namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice +{ + public class FreeSwitchDialplan + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + + public Hashtable HandleDialplanRequest(Hashtable request) + { + m_log.DebugFormat("[FreeSwitchVoice] HandleDialplanRequest called with {0}",request.ToString()); + + Hashtable response = new Hashtable(); + + foreach(DictionaryEntry item in request) + { + m_log.InfoFormat("[FreeSwitchDirectory] requestBody item {0} {1}",item.Key, item.Value); + } + + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = @" + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
"; + + return response; + } + } + +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDirectory.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDirectory.cs new file mode 100644 index 0000000000..9959d11c77 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDirectory.cs @@ -0,0 +1,335 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using log4net; +using System; +using System.Reflection; +using System.Text; +using System.Collections; + +namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice +{ + public class FreeSwitchDirectory + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public Hashtable HandleDirectoryRequest(Hashtable request) + { + m_log.DebugFormat("[FreeSwitchDirectory] HandleDirectoryRequest called with {0}",request.ToString()); + + Hashtable response = new Hashtable(); + + // information in the request we might be interested in + + // Request 1 sip_auth for users account + + //Event-Calling-Function=sofia_reg_parse_auth + //Event-Calling-Line-Number=1494 + //action=sip_auth + //sip_user_agent=Vivox-SDK-2.1.3010.6151-Mac%20(Feb-11-2009/16%3A42%3A41) + //sip_auth_username=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==) + //sip_auth_realm=9.20.151.43 + //sip_contact_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==) + //sip_contact_host=192.168.0.3 // this shouldnt really be a local IP, investigate STUN servers + //sip_to_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D + //sip_to_host=9.20.151.43 + //sip_auth_method=REGISTER + //user=xhZuXKmRpECyr2AARJYyGgg%3D%3D + //domain=9.20.151.43 + //ip=9.167.220.137 // this is the correct IP rather than sip_contact_host above when through a vpn or NAT setup + + foreach(DictionaryEntry item in request) + { + m_log.InfoFormat("[FreeSwitchDirectory] requestBody item {0} {1}",item.Key, item.Value); + } + + string eventCallingFunction = (string) request["Event-Calling-Function"]; + + + if(eventCallingFunction=="sofia_reg_parse_auth") + { + string sipAuthMethod = (string)request["sip_auth_method"]; + + if(sipAuthMethod=="REGISTER") + { + response = HandleRegister(request); + } + else if(sipAuthMethod=="INVITE") + { + response = HandleInvite(request); + } + else + { + m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown sip_auth_method {0}",sipAuthMethod); + response["int_response_code"]=404; + } + } + else if(eventCallingFunction=="switch_xml_locate_user") + { + response = HandleLocateUser(request); + } + else if(eventCallingFunction=="user_data_function") // gets called when an avatar to avatar call is made + { + response = HandleLocateUser(request); + } + else if(eventCallingFunction=="user_outgoing_channel") + { + response = HandleRegister(request); + } + else if(eventCallingFunction=="config_sofia") // happens once on freeswitch startup + { + response = HandleConfigSofia(request); + } + else if(eventCallingFunction=="switch_load_network_lists") + { + //response = HandleLoadNetworkLists(request); + response["int_response_code"]=404; + response["keepalive"] = false; + } + else + { + m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown Event-Calling-Function {0}",eventCallingFunction); + response["int_response_code"]=404; + response["keepalive"] = false; + } + + + + return response; + } + + private Hashtable HandleRegister(Hashtable request) + { + m_log.Info("[FreeSwitchDirectory] HandleRegister called"); + + // TODO the password we return needs to match that sent in the request, this is hard coded for now + string password = "1234"; + string domain = (string) request["domain"]; + string user = (string) request["user"]; + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n" + + "
\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ""+ + "\r\n" + + "\r\n" + + "\r\n" + + "
\r\n" + + "
\r\n" + , domain , user, password); + + return response; + + } + + private Hashtable HandleInvite(Hashtable request) + { + m_log.Info("[FreeSwitchDirectory] HandleInvite called"); + + // TODO the password we return needs to match that sent in the request, this is hard coded for now + string password = "1234"; + string domain = (string) request["domain"]; + string user = (string) request["user"]; + string sipRequestUser = (string) request["sip_request_user"]; + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n" + + "
\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ""+ + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ""+ + "\r\n" + + "\r\n" + + "\r\n" + + "
\r\n" + + "
\r\n" + , domain , user, password,sipRequestUser); + + return response; + } + + + private Hashtable HandleLocateUser(Hashtable request) + { + m_log.Info("[FreeSwitchDirectory] HandleLocateUser called"); + + // TODO the password we return needs to match that sent in the request, this is hard coded for now + string domain = (string) request["domain"]; + string user = (string) request["user"]; + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n" + + "
\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n"+ + "\r\n"+ + ""+ + "\r\n"+ + "\r\n" + + "\r\n" + + "
\r\n" + + "
\r\n" + , domain , user); + + + + return response; + } + + private Hashtable HandleConfigSofia(Hashtable request) + { + m_log.Info("[FreeSwitchDirectory] HandleConfigSofia called"); + + // TODO the password we return needs to match that sent in the request, this is hard coded for now + string domain = (string) request["domain"]; + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n" + + "
\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n"+ + ""+ + "\r\n" + + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n" + + "
\r\n" + + "
\r\n" + , domain); + + + return response; + } + + private Hashtable HandleLoadNetworkLists(Hashtable request) + { + m_log.Info("[FreeSwitchDirectory] HandleLoadNetworkLists called"); + + // TODO the password we return needs to match that sent in the request, this is hard coded for now + string domain = (string) request["domain"]; + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + response["int_response_code"]=200; + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n" + + "
\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n"+ + "\r\n"+ + "\r\n"+ + "\r\n" + + "
\r\n" + + "
\r\n" + , domain); + + + return response; + } + + } + +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs new file mode 100644 index 0000000000..a8f9de6b64 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs @@ -0,0 +1,571 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Net; +using System.Web; +using System.Text; +using System.Xml; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice +{ + public class FreeSwitchVoiceModule : IRegionModule + { + + // Infrastructure + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly bool DUMP = true; + + // Capability string prefixes + private static readonly string m_parcelVoiceInfoRequestPath = "0007/"; + private static readonly string m_provisionVoiceAccountRequestPath = "0008/"; + private static readonly string m_chatSessionRequestPath = "0009/"; + + // Control info + private static bool m_WOF = true; + private static bool m_pluginEnabled = false; + + // FreeSwitch server is going to contact us and ask us all + // sorts of things. + private static string m_freeSwitchServerUser; + private static string m_freeSwitchServerPass; + + // SLVoice client will do a GET on this prefix + private static string m_freeSwitchAPIPrefix; + + // We need to return some information to SLVoice + // figured those out via curl + // http://vd1.vivox.com/api2/viv_get_prelogin.php + // + // need to figure out whether we do need to return ALL of + // these... + private static string m_freeSwitchRealm; + private static string m_freeSwitchSIPProxy; + private static bool m_freeSwitchAttemptUseSTUN; + private static string m_freeSwitchSTUNServer; + private static string m_freeSwitchEchoServer; + private static int m_freeSwitchEchoPort; + private static string m_freeSwitchDefaultWellKnownIP; + private static int m_freeSwitchDefaultTimeout; + private static int m_freeSwitchSubscribeRetry; + private static string m_freeSwitchUrlResetPassword; + private static IPEndPoint m_FreeSwitchServiceIP; + + private FreeSwitchDirectory m_FreeSwitchDirectory; + private FreeSwitchDialplan m_FreeSwitchDialplan; + + private IConfig m_config; + + public void Initialise(Scene scene, IConfigSource config) + { + + m_config = config.Configs["FreeSwitchVoice"]; + + if (null == m_config) + { + m_log.Info("[FreeSwitchVoice] no config found, plugin disabled"); + return; + } + + if (!m_config.GetBoolean("enabled", false)) + { + m_log.Info("[FreeSwitchVoice] plugin disabled by configuration"); + return; + } + + // This is only done the FIRST time this method is invoked. + if (m_WOF) + { + m_pluginEnabled = true; + m_WOF = false; + + try + { + m_freeSwitchServerUser = m_config.GetString("freeswitch_server_user", String.Empty); + m_freeSwitchServerPass = m_config.GetString("freeswitch_server_pass", String.Empty); + m_freeSwitchAPIPrefix = m_config.GetString("freeswitch_api_prefix", String.Empty); + + // XXX: get IP address of HTTP server. (This can be this OpenSim server or another, or could be a dedicated grid service or may live on the freeswitch server) + + string serviceIP = m_config.GetString("freeswitch_service_server", String.Empty); + int servicePort = m_config.GetInt("freeswitch_service_port", 80); + IPAddress serviceIPAddress = IPAddress.Parse(serviceIP); + m_FreeSwitchServiceIP = new IPEndPoint(serviceIPAddress, servicePort); + + m_freeSwitchRealm = m_config.GetString("freeswitch_realm", String.Empty); + m_freeSwitchSIPProxy = m_config.GetString("freeswitch_sip_proxy", m_freeSwitchRealm); + m_freeSwitchAttemptUseSTUN = m_config.GetBoolean("freeswitch_attempt_stun", true); + m_freeSwitchSTUNServer = m_config.GetString("freeswitch_stun_server", m_freeSwitchRealm); + m_freeSwitchEchoServer = m_config.GetString("freeswitch_echo_server", m_freeSwitchRealm); + m_freeSwitchEchoPort = m_config.GetInt("freeswitch_echo_port", 50505); + m_freeSwitchDefaultWellKnownIP = m_config.GetString("freeswitch_well_known_ip", m_freeSwitchRealm); + m_freeSwitchDefaultTimeout = m_config.GetInt("freeswitch_default_timeout", 5000); + m_freeSwitchSubscribeRetry = m_config.GetInt("freeswitch_subscribe_retry", 120); + m_freeSwitchUrlResetPassword = m_config.GetString("freeswitch_password_reset_url", String.Empty); + + + + + if (String.IsNullOrEmpty(m_freeSwitchServerUser) || + String.IsNullOrEmpty(m_freeSwitchServerPass) || + String.IsNullOrEmpty(m_freeSwitchRealm) || + String.IsNullOrEmpty(m_freeSwitchAPIPrefix)) + { + m_log.Error("[FreeSwitchVoice] plugin mis-configured"); + m_log.Info("[FreeSwitchVoice] plugin disabled: incomplete configuration"); + return; + } + + // set up http request handlers for + // - prelogin: viv_get_prelogin.php + // - signin: viv_signin.php + scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), + FreeSwitchSLVoiceGetPreloginHTTPHandler); + + // RestStreamHandler h = new RestStreamHandler("GET", String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), FreeSwitchSLVoiceGetPreloginHTTPHandler); + // scene.CommsManager.HttpServer.AddStreamHandler(h); + + + + scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/viv_signin.php", m_freeSwitchAPIPrefix), + FreeSwitchSLVoiceSigninHTTPHandler); + + // set up http request handlers to provide + // on-demand FreeSwitch configuration to + // FreeSwitch's mod_curl_xml + scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/freeswitch-config", m_freeSwitchAPIPrefix), + FreeSwitchConfigHTTPHandler); + + m_log.InfoFormat("[FreeSwitchVoice] using FreeSwitch server {0}", m_freeSwitchRealm); + + m_FreeSwitchDirectory = new FreeSwitchDirectory(); + m_FreeSwitchDialplan = new FreeSwitchDialplan(); + + m_pluginEnabled = true; + m_WOF = false; + + m_log.Info("[FreeSwitchVoice] plugin enabled"); + } + catch (Exception e) + { + m_log.ErrorFormat("[FreeSwitchVoice] plugin initialization failed: {0}", e.Message); + m_log.DebugFormat("[FreeSwitchVoice] plugin initialization failed: {0}", e.ToString()); + return; + } + } + + if (m_pluginEnabled) + { + // we need to capture scene in an anonymous method + // here as we need it later in the callbacks + scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps) + { + OnRegisterCaps(scene, agentID, caps); + }; + + + + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "FreeSwitchVoiceModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + + // + // OnRegisterCaps is invoked via the scene.EventManager + // everytime OpenSim hands out capabilities to a client + // (login, region crossing). We contribute two capabilities to + // the set of capabilities handed back to the client: + // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest. + // + // ProvisionVoiceAccountRequest allows the client to obtain + // the voice account credentials for the avatar it is + // controlling (e.g., user name, password, etc). + // + // ParcelVoiceInfoRequest is invoked whenever the client + // changes from one region or parcel to another. + // + // Note that OnRegisterCaps is called here via a closure + // delegate containing the scene of the respective region (see + // Initialise()). + // + public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps) + { + m_log.DebugFormat("[FreeSwitchVoice] OnRegisterCaps: agentID {0} caps {1}", agentID, caps); + + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("ProvisionVoiceAccountRequest", + new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return ProvisionVoiceAccountRequest(scene, request, path, param, + agentID, caps); + })); + caps.RegisterHandler("ParcelVoiceInfoRequest", + new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return ParcelVoiceInfoRequest(scene, request, path, param, + agentID, caps); + })); + caps.RegisterHandler("ChatSessionRequest", + new RestStreamHandler("POST", capsBase + m_chatSessionRequestPath, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return ChatSessionRequest(scene, request, path, param, + agentID, caps); + })); + } + + /// + /// Callback for a client request for Voice Account Details + /// + /// current scene object of the client + /// + /// + /// + /// + /// + /// + public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param, + UUID agentID, Caps caps) + { + ScenePresence avatar = scene.GetScenePresence(agentID); + string avatarName = avatar.Name; + + try + { + m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}", + request, path, param); + + //XmlElement resp; + string agentname = "x" + Convert.ToBase64String(agentID.GetBytes()); + string password = "1234";//temp hack//new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16); + + // XXX: we need to cache the voice credentials, as + // FreeSwitch is later going to come and ask us for + // those + + agentname = agentname.Replace('+', '-').Replace('/', '_'); + + // LLSDVoiceAccountResponse voiceAccountResponse = + // new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, "http://etsvc02.hursley.ibm.com/api"); + LLSDVoiceAccountResponse voiceAccountResponse = + new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, + String.Format("http://{0}/{1}/", m_FreeSwitchServiceIP, + m_freeSwitchAPIPrefix)); + + string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse); + + m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r); + + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}, retry later", avatarName, e.Message); + m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1} failed", avatarName, e.ToString()); + + return "undef"; + } + } + + /// + /// Callback for a client request for ParcelVoiceInfo + /// + /// current scene object of the client + /// + /// + /// + /// + /// + /// + public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param, + UUID agentID, Caps caps) + { + ScenePresence avatar = scene.GetScenePresence(agentID); + string avatarName = avatar.Name; + + // - check whether we have a region channel in our cache + // - if not: + // create it and cache it + // - send it to the client + // - send channel_uri: as "sip:regionID@m_sipDomain" + try + { + LLSDParcelVoiceInfoResponse parcelVoiceInfo; + string channelUri; + + if (null == scene.LandChannel) + throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available", + scene.RegionInfo.RegionName, avatarName)); + + + + // get channel_uri: check first whether estate + // settings allow voice, then whether parcel allows + // voice, if all do retrieve or obtain the parcel + // voice channel + LandData land = scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + + m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}", + scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param); + + // TODO: EstateSettings don't seem to get propagated... + // if (!scene.RegionInfo.EstateSettings.AllowVoice) + // { + // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings", + // scene.RegionInfo.RegionName); + // channel_uri = String.Empty; + // } + // else + + if ((land.Flags & (uint)Parcel.ParcelFlags.AllowVoiceChat) == 0) + { + m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel", + scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName); + channelUri = String.Empty; + } + else + { + channelUri = ChannelUri(scene, land); + } + + // fill in our response to the client + Hashtable creds = new Hashtable(); + creds["channel_uri"] = channelUri; + + parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds); + string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo); + + m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}", + scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r); + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later", + scene.RegionInfo.RegionName, avatarName, e.Message); + m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed", + scene.RegionInfo.RegionName, avatarName, e.ToString()); + + return "undef"; + } + } + + + /// + /// Callback for a client request for ChatSessionRequest + /// + /// current scene object of the client + /// + /// + /// + /// + /// + /// + public string ChatSessionRequest(Scene scene, string request, string path, string param, + UUID agentID, Caps caps) + { + ScenePresence avatar = scene.GetScenePresence(agentID); + string avatarName = avatar.Name; + + m_log.DebugFormat("[FreeSwitchVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}", + avatarName, request, path, param); + return "true"; + } + + + public Hashtable FreeSwitchSLVoiceGetPreloginHTTPHandler(Hashtable request) + { + m_log.Debug("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler called"); + + Hashtable response = new Hashtable(); + response["content_type"] = "text/xml"; + response["keepalive"] = false; + + response["str_response_string"] = String.Format( + "\r\n" + + "\r\n"+ + "{0}\r\n" + + "{1}\r\n"+ + "{2}\r\n"+ + "{3}\r\n"+ + "{4}\r\n"+ + "{5}\r\n"+ + "{6}\r\n"+ + "{7}\r\n"+ + "{8}\r\n"+ + "\r\n"+ + "false\r\n"+ + "" + , + m_freeSwitchRealm,m_freeSwitchSIPProxy,m_freeSwitchAttemptUseSTUN, + m_freeSwitchSTUNServer,m_freeSwitchEchoServer,m_freeSwitchEchoPort, + m_freeSwitchDefaultWellKnownIP,m_freeSwitchDefaultTimeout,m_freeSwitchUrlResetPassword,""); + + response["int_response_code"] = 200; + + m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler return {0}",response["str_response_string"]); + return response; + } + + public Hashtable FreeSwitchSLVoiceSigninHTTPHandler(Hashtable request) + { + m_log.Debug("[FreeSwitchVoice] FreeSwitchSLVoiceSigninHTTPHandler called"); + + Hashtable response = new Hashtable(); + response["str_response_string"] = @" + + OK + + 200 + auth successful + + + "; + response["int_response_code"] = 200; + return response; + } + + + public Hashtable FreeSwitchConfigHTTPHandler(Hashtable request) + { + m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchConfigHTTPHandler called with {0}",request.ToString()); + + Hashtable response = new Hashtable(); + + // all the params come as NVPs in the request body + Hashtable requestBody = parseRequestBody((string) request["body"]); + + // is this a dialplan or directory request + string section = (string) requestBody["section"]; + + if(section=="directory") + response = m_FreeSwitchDirectory.HandleDirectoryRequest(requestBody); + else if (section=="dialplan") + response = m_FreeSwitchDialplan.HandleDialplanRequest(requestBody); + + // XXX: re-generate dialplan: + // - conf == region UUID + // - conf number = region port + // -> TODO Initialise(): keep track of regions via events + // re-generate accounts for all avatars + // -> TODO Initialise(): keep track of avatars via events + m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchConfigHTTPHandler return {0}",response["str_response_string"]); + return response; + } + + public Hashtable parseRequestBody(string body) + { + Hashtable bodyParams = new Hashtable(); + // split string + string [] nvps = body.Split(new Char [] {'&'}); + + foreach (string s in nvps) { + + if (s.Trim() != "") + { + string [] nvp = s.Split(new Char [] {'='}); + bodyParams.Add(HttpUtility.UrlDecode(nvp[0]),HttpUtility.UrlDecode(nvp[1])); + } + } + + return bodyParams; + + } + + private string ChannelUri(Scene scene, LandData land) + { + + string channelUri = null; + + string landUUID; + string landName; + + // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same + // as the directory ID. Otherwise, it reflects the parcel's ID. + + if (land.LocalID != 1 && (land.Flags & (uint)Parcel.ParcelFlags.UseEstateVoiceChan) == 0) + { + landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name); + landUUID = land.GlobalID.ToString(); + m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}", + landName, land.LocalID, landUUID); + } + else + { + landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName); + landUUID = scene.RegionInfo.RegionID.ToString(); + m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}", + landName, land.LocalID, landUUID); + } + System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); + channelUri = String.Format("sip:confctl-{0}@{1}", "x" + Convert.ToBase64String(encoding.GetBytes(landUUID)), m_freeSwitchRealm); + + //channelUri="sip:confctl-3001@9.20.151.43"; + //channelUri="sip:opensimconf-3001@9.20.151.43"; + + return channelUri; + } + } +}