From 7f30be17d0ee841961262ee9e9b8fab27ccf6d83 Mon Sep 17 00:00:00 2001
From: Sean Dague
Date: Fri, 17 Apr 2009 20:00:30 +0000
Subject: experimental freeswitch code, imported from Rob Smart's tree
---
.../Voice/FreeSwitchVoice/FreeSwitchDialplan.cs | 88 ++++
.../Voice/FreeSwitchVoice/FreeSwitchDirectory.cs | 335 ++++++++++++
.../Voice/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
(limited to 'OpenSim/Region')
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 0000000..2a2b4a3
--- /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 0000000..9959d11
--- /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 0000000..a8f9de6
--- /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;
+ }
+ }
+}
--
cgit v1.1