aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorSean Dague2009-04-17 20:00:30 +0000
committerSean Dague2009-04-17 20:00:30 +0000
commit7f30be17d0ee841961262ee9e9b8fab27ccf6d83 (patch)
tree77a48e8135b5086ba4fd9225f72ce77f702011ce
parentCorrect detected rotation to return the same value as llGetRot in the object ... (diff)
downloadopensim-SC-7f30be17d0ee841961262ee9e9b8fab27ccf6d83.zip
opensim-SC-7f30be17d0ee841961262ee9e9b8fab27ccf6d83.tar.gz
opensim-SC-7f30be17d0ee841961262ee9e9b8fab27ccf6d83.tar.bz2
opensim-SC-7f30be17d0ee841961262ee9e9b8fab27ccf6d83.tar.xz
experimental freeswitch code, imported from Rob Smart's tree
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDialplan.cs88
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchDirectory.cs335
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs571
3 files changed, 994 insertions, 0 deletions
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 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using log4net;
29using System.Reflection;
30using System.Collections;
31
32namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice
33{
34 public class FreeSwitchDialplan
35 {
36 private static readonly ILog m_log =
37 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
38
39
40 public Hashtable HandleDialplanRequest(Hashtable request)
41 {
42 m_log.DebugFormat("[FreeSwitchVoice] HandleDialplanRequest called with {0}",request.ToString());
43
44 Hashtable response = new Hashtable();
45
46 foreach(DictionaryEntry item in request)
47 {
48 m_log.InfoFormat("[FreeSwitchDirectory] requestBody item {0} {1}",item.Key, item.Value);
49 }
50
51 response["content_type"] = "text/xml";
52 response["keepalive"] = false;
53 response["int_response_code"]=200;
54 response["str_response_string"] = @"<?xml version=""1.0"" encoding=""utf-8""?>
55 <document type=""freeswitch/xml"">
56 <section name=""dialplan"">
57 <context name=""default"">
58
59 <!-- dial via SIP uri -->
60 <extension name=""sip_uri"">
61 <condition field=""destination_number"" expression=""^sip:(.*)$"">
62 <action application=""bridge"" data=""sofia/${use_profile}/$1""/>
63 <!--<action application=""bridge"" data=""$1""/>-->
64 </condition>
65 </extension>
66
67 <extension name=""opensim_conferences"">
68 <condition field=""destination_number"" expression=""^confctl-(.*)$"">
69 <action application=""answer""/>
70 <action application=""conference"" data=""$1-${domain_name}@default""/>
71 </condition>
72 </extension>
73
74 <extension name=""avatar"">
75 <condition field=""destination_number"" expression=""^(x.*)$"">
76 <action application=""bridge"" data=""user/$1""/>
77 </condition>
78 </extension>
79
80 </context>
81 </section>
82 </document>";
83
84 return response;
85 }
86 }
87
88} \ 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 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using log4net;
29using System;
30using System.Reflection;
31using System.Text;
32using System.Collections;
33
34namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice
35{
36 public class FreeSwitchDirectory
37 {
38 private static readonly ILog m_log =
39 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
40
41 public Hashtable HandleDirectoryRequest(Hashtable request)
42 {
43 m_log.DebugFormat("[FreeSwitchDirectory] HandleDirectoryRequest called with {0}",request.ToString());
44
45 Hashtable response = new Hashtable();
46
47 // information in the request we might be interested in
48
49 // Request 1 sip_auth for users account
50
51 //Event-Calling-Function=sofia_reg_parse_auth
52 //Event-Calling-Line-Number=1494
53 //action=sip_auth
54 //sip_user_agent=Vivox-SDK-2.1.3010.6151-Mac%20(Feb-11-2009/16%3A42%3A41)
55 //sip_auth_username=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
56 //sip_auth_realm=9.20.151.43
57 //sip_contact_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
58 //sip_contact_host=192.168.0.3 // this shouldnt really be a local IP, investigate STUN servers
59 //sip_to_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
60 //sip_to_host=9.20.151.43
61 //sip_auth_method=REGISTER
62 //user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
63 //domain=9.20.151.43
64 //ip=9.167.220.137 // this is the correct IP rather than sip_contact_host above when through a vpn or NAT setup
65
66 foreach(DictionaryEntry item in request)
67 {
68 m_log.InfoFormat("[FreeSwitchDirectory] requestBody item {0} {1}",item.Key, item.Value);
69 }
70
71 string eventCallingFunction = (string) request["Event-Calling-Function"];
72
73
74 if(eventCallingFunction=="sofia_reg_parse_auth")
75 {
76 string sipAuthMethod = (string)request["sip_auth_method"];
77
78 if(sipAuthMethod=="REGISTER")
79 {
80 response = HandleRegister(request);
81 }
82 else if(sipAuthMethod=="INVITE")
83 {
84 response = HandleInvite(request);
85 }
86 else
87 {
88 m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown sip_auth_method {0}",sipAuthMethod);
89 response["int_response_code"]=404;
90 }
91 }
92 else if(eventCallingFunction=="switch_xml_locate_user")
93 {
94 response = HandleLocateUser(request);
95 }
96 else if(eventCallingFunction=="user_data_function") // gets called when an avatar to avatar call is made
97 {
98 response = HandleLocateUser(request);
99 }
100 else if(eventCallingFunction=="user_outgoing_channel")
101 {
102 response = HandleRegister(request);
103 }
104 else if(eventCallingFunction=="config_sofia") // happens once on freeswitch startup
105 {
106 response = HandleConfigSofia(request);
107 }
108 else if(eventCallingFunction=="switch_load_network_lists")
109 {
110 //response = HandleLoadNetworkLists(request);
111 response["int_response_code"]=404;
112 response["keepalive"] = false;
113 }
114 else
115 {
116 m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown Event-Calling-Function {0}",eventCallingFunction);
117 response["int_response_code"]=404;
118 response["keepalive"] = false;
119 }
120
121
122
123 return response;
124 }
125
126 private Hashtable HandleRegister(Hashtable request)
127 {
128 m_log.Info("[FreeSwitchDirectory] HandleRegister called");
129
130 // TODO the password we return needs to match that sent in the request, this is hard coded for now
131 string password = "1234";
132 string domain = (string) request["domain"];
133 string user = (string) request["user"];
134
135 Hashtable response = new Hashtable();
136 response["content_type"] = "text/xml";
137 response["keepalive"] = false;
138 response["int_response_code"]=200;
139 response["str_response_string"] = String.Format(
140 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
141 "<document type=\"freeswitch/xml\">\r\n" +
142 "<section name=\"directory\" description=\"User Directory\">\r\n" +
143 "<domain name=\"{0}\">\r\n" +
144 "<user id=\"{1}\">\r\n" +
145 "<params>\r\n" +
146 "<param name=\"password\" value=\"{2}\" />\r\n" +
147 "<param name=\"dial-string\" value=\"{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
148 "</params>\r\n" +
149 "<variables>\r\n" +
150 "<variable name=\"user_context\" value=\"default\" />\r\n" +
151 "<variable name=\"presence_id\" value=\"{1}@{0}\"/>"+
152 "</variables>\r\n" +
153 "</user>\r\n" +
154 "</domain>\r\n" +
155 "</section>\r\n" +
156 "</document>\r\n"
157 , domain , user, password);
158
159 return response;
160
161 }
162
163 private Hashtable HandleInvite(Hashtable request)
164 {
165 m_log.Info("[FreeSwitchDirectory] HandleInvite called");
166
167 // TODO the password we return needs to match that sent in the request, this is hard coded for now
168 string password = "1234";
169 string domain = (string) request["domain"];
170 string user = (string) request["user"];
171 string sipRequestUser = (string) request["sip_request_user"];
172
173 Hashtable response = new Hashtable();
174 response["content_type"] = "text/xml";
175 response["keepalive"] = false;
176 response["int_response_code"]=200;
177 response["str_response_string"] = String.Format(
178 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
179 "<document type=\"freeswitch/xml\">\r\n" +
180 "<section name=\"directory\" description=\"User Directory\">\r\n" +
181 "<domain name=\"{0}\">\r\n" +
182 "<user id=\"{1}\">\r\n" +
183 "<params>\r\n" +
184 "<param name=\"password\" value=\"{2}\" />\r\n" +
185 "<param name=\"dial-string\" value=\"{{presence_id=${1}@${{dialed_domain}}}}${{sofia_contact(${1}@${{dialed_domain}})}}\"/>\r\n" +
186 "</params>\r\n" +
187 "<variables>\r\n" +
188 "<variable name=\"user_context\" value=\"default\" />\r\n" +
189 "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
190 "</variables>\r\n" +
191 "</user>\r\n" +
192 "<user id=\"{3}\">\r\n" +
193 "<params>\r\n" +
194 "<param name=\"password\" value=\"{2}\" />\r\n" +
195 "<param name=\"dial-string\" value=\"{{presence_id=${3}@${{dialed_domain}}}}${{sofia_contact(${3}@${{dialed_domain}})}}\"/>\r\n" +
196 "</params>\r\n" +
197 "<variables>\r\n" +
198 "<variable name=\"user_context\" value=\"default\" />\r\n" +
199 "<variable name=\"presence_id\" value=\"{3}@$${{domain}}\"/>"+
200 "</variables>\r\n" +
201 "</user>\r\n" +
202 "</domain>\r\n" +
203 "</section>\r\n" +
204 "</document>\r\n"
205 , domain , user, password,sipRequestUser);
206
207 return response;
208 }
209
210
211 private Hashtable HandleLocateUser(Hashtable request)
212 {
213 m_log.Info("[FreeSwitchDirectory] HandleLocateUser called");
214
215 // TODO the password we return needs to match that sent in the request, this is hard coded for now
216 string domain = (string) request["domain"];
217 string user = (string) request["user"];
218
219 Hashtable response = new Hashtable();
220 response["content_type"] = "text/xml";
221 response["keepalive"] = false;
222 response["int_response_code"]=200;
223 response["str_response_string"] = String.Format(
224 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
225 "<document type=\"freeswitch/xml\">\r\n" +
226 "<section name=\"directory\" description=\"User Directory\">\r\n" +
227 "<domain name=\"{0}\">\r\n" +
228 "<params>\r\n" +
229 "<param name=\"dial-string\" value=\"{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
230 "</params>\r\n" +
231 "<user id=\"{1}\">\r\n" +
232 "<variables>\r\n"+
233 "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
234 "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
235 "</variables>\r\n"+
236 "</user>\r\n" +
237 "</domain>\r\n" +
238 "</section>\r\n" +
239 "</document>\r\n"
240 , domain , user);
241
242
243
244 return response;
245 }
246
247 private Hashtable HandleConfigSofia(Hashtable request)
248 {
249 m_log.Info("[FreeSwitchDirectory] HandleConfigSofia called");
250
251 // TODO the password we return needs to match that sent in the request, this is hard coded for now
252 string domain = (string) request["domain"];
253
254 Hashtable response = new Hashtable();
255 response["content_type"] = "text/xml";
256 response["keepalive"] = false;
257 response["int_response_code"]=200;
258 response["str_response_string"] = String.Format(
259 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
260 "<document type=\"freeswitch/xml\">\r\n" +
261 "<section name=\"directory\" description=\"User Directory\">\r\n" +
262 "<domain name=\"{0}\">\r\n" +
263 "<params>\r\n" +
264 "<param name=\"dial-string\" value=\"{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
265 "</params>\r\n" +
266 "<groups name=\"default\">\r\n"+
267 "<users>\r\n"+
268 "<user id=\"$${{default_provider}}\">\r\n"+
269 "<gateways>\r\n"+
270 "<gateway name=\"$${{default_provider}}\">\r\n"+
271 "<param name=\"username\" value=\"$${{default_provider_username}}\"/>\r\n"+
272 "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
273 "<param name=\"from-user\" value=\"$${{default_provider_username}}\"/>\r\n"+
274 "<param name=\"from-domain\" value=\"$${{default_provider_from_domain}}\"/>\r\n"+
275 "<param name=\"expire-seconds\" value=\"600\"/>\r\n"+
276 "<param name=\"register\" value=\"$${{default_provider_register}}\"/>\r\n"+
277 "<param name=\"retry-seconds\" value=\"30\"/>\r\n"+
278 "<param name=\"extension\" value=\"$${{default_provider_contact}}\"/>\r\n"+
279 "<param name=\"contact-params\" value=\"domain_name=$${{domain}}\"/>\r\n"+
280 "<param name=\"context\" value=\"public\"/>\r\n"+
281 "</gateway>\r\n"+
282 "</gateways>\r\n"+
283 "<params>\r\n"+
284 "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
285 "</params>\r\n"+
286 "</user>\r\n"+
287 "</users>"+
288 "</groups>\r\n" +
289 "<variables>\r\n"+
290 "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
291 "</variables>\r\n"+
292 "</domain>\r\n" +
293 "</section>\r\n" +
294 "</document>\r\n"
295 , domain);
296
297
298 return response;
299 }
300
301 private Hashtable HandleLoadNetworkLists(Hashtable request)
302 {
303 m_log.Info("[FreeSwitchDirectory] HandleLoadNetworkLists called");
304
305 // TODO the password we return needs to match that sent in the request, this is hard coded for now
306 string domain = (string) request["domain"];
307
308 Hashtable response = new Hashtable();
309 response["content_type"] = "text/xml";
310 response["keepalive"] = false;
311 response["int_response_code"]=200;
312 response["str_response_string"] = String.Format(
313 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
314 "<document type=\"freeswitch/xml\">\r\n" +
315 "<section name=\"directory\" description=\"User Directory\">\r\n" +
316 "<domain name=\"{0}\">\r\n" +
317 "<params>\r\n" +
318 "<param name=\"dial-string\" value=\"{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
319 "</params>\r\n" +
320 "<groups name=\"default\"><users/></groups>\r\n" +
321 "<variables>\r\n"+
322 "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
323 "</variables>\r\n"+
324 "</domain>\r\n" +
325 "</section>\r\n" +
326 "</document>\r\n"
327 , domain);
328
329
330 return response;
331 }
332
333 }
334
335} \ 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 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.IO;
30using System.Net;
31using System.Web;
32using System.Text;
33using System.Xml;
34using System.Collections;
35using System.Collections.Generic;
36using System.Reflection;
37using OpenMetaverse;
38using log4net;
39using Nini.Config;
40using Nwc.XmlRpc;
41using OpenSim.Framework;
42using OpenSim.Framework.Communications.Cache;
43using OpenSim.Framework.Communications.Capabilities;
44using OpenSim.Framework.Servers;
45using OpenSim.Region.Framework.Interfaces;
46using OpenSim.Region.Framework.Scenes;
47using Caps = OpenSim.Framework.Communications.Capabilities.Caps;
48
49namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice
50{
51 public class FreeSwitchVoiceModule : IRegionModule
52 {
53
54 // Infrastructure
55 private static readonly ILog m_log =
56 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
57 private static readonly bool DUMP = true;
58
59 // Capability string prefixes
60 private static readonly string m_parcelVoiceInfoRequestPath = "0007/";
61 private static readonly string m_provisionVoiceAccountRequestPath = "0008/";
62 private static readonly string m_chatSessionRequestPath = "0009/";
63
64 // Control info
65 private static bool m_WOF = true;
66 private static bool m_pluginEnabled = false;
67
68 // FreeSwitch server is going to contact us and ask us all
69 // sorts of things.
70 private static string m_freeSwitchServerUser;
71 private static string m_freeSwitchServerPass;
72
73 // SLVoice client will do a GET on this prefix
74 private static string m_freeSwitchAPIPrefix;
75
76 // We need to return some information to SLVoice
77 // figured those out via curl
78 // http://vd1.vivox.com/api2/viv_get_prelogin.php
79 //
80 // need to figure out whether we do need to return ALL of
81 // these...
82 private static string m_freeSwitchRealm;
83 private static string m_freeSwitchSIPProxy;
84 private static bool m_freeSwitchAttemptUseSTUN;
85 private static string m_freeSwitchSTUNServer;
86 private static string m_freeSwitchEchoServer;
87 private static int m_freeSwitchEchoPort;
88 private static string m_freeSwitchDefaultWellKnownIP;
89 private static int m_freeSwitchDefaultTimeout;
90 private static int m_freeSwitchSubscribeRetry;
91 private static string m_freeSwitchUrlResetPassword;
92 private static IPEndPoint m_FreeSwitchServiceIP;
93
94 private FreeSwitchDirectory m_FreeSwitchDirectory;
95 private FreeSwitchDialplan m_FreeSwitchDialplan;
96
97 private IConfig m_config;
98
99 public void Initialise(Scene scene, IConfigSource config)
100 {
101
102 m_config = config.Configs["FreeSwitchVoice"];
103
104 if (null == m_config)
105 {
106 m_log.Info("[FreeSwitchVoice] no config found, plugin disabled");
107 return;
108 }
109
110 if (!m_config.GetBoolean("enabled", false))
111 {
112 m_log.Info("[FreeSwitchVoice] plugin disabled by configuration");
113 return;
114 }
115
116 // This is only done the FIRST time this method is invoked.
117 if (m_WOF)
118 {
119 m_pluginEnabled = true;
120 m_WOF = false;
121
122 try
123 {
124 m_freeSwitchServerUser = m_config.GetString("freeswitch_server_user", String.Empty);
125 m_freeSwitchServerPass = m_config.GetString("freeswitch_server_pass", String.Empty);
126 m_freeSwitchAPIPrefix = m_config.GetString("freeswitch_api_prefix", String.Empty);
127
128 // 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)
129
130 string serviceIP = m_config.GetString("freeswitch_service_server", String.Empty);
131 int servicePort = m_config.GetInt("freeswitch_service_port", 80);
132 IPAddress serviceIPAddress = IPAddress.Parse(serviceIP);
133 m_FreeSwitchServiceIP = new IPEndPoint(serviceIPAddress, servicePort);
134
135 m_freeSwitchRealm = m_config.GetString("freeswitch_realm", String.Empty);
136 m_freeSwitchSIPProxy = m_config.GetString("freeswitch_sip_proxy", m_freeSwitchRealm);
137 m_freeSwitchAttemptUseSTUN = m_config.GetBoolean("freeswitch_attempt_stun", true);
138 m_freeSwitchSTUNServer = m_config.GetString("freeswitch_stun_server", m_freeSwitchRealm);
139 m_freeSwitchEchoServer = m_config.GetString("freeswitch_echo_server", m_freeSwitchRealm);
140 m_freeSwitchEchoPort = m_config.GetInt("freeswitch_echo_port", 50505);
141 m_freeSwitchDefaultWellKnownIP = m_config.GetString("freeswitch_well_known_ip", m_freeSwitchRealm);
142 m_freeSwitchDefaultTimeout = m_config.GetInt("freeswitch_default_timeout", 5000);
143 m_freeSwitchSubscribeRetry = m_config.GetInt("freeswitch_subscribe_retry", 120);
144 m_freeSwitchUrlResetPassword = m_config.GetString("freeswitch_password_reset_url", String.Empty);
145
146
147
148
149 if (String.IsNullOrEmpty(m_freeSwitchServerUser) ||
150 String.IsNullOrEmpty(m_freeSwitchServerPass) ||
151 String.IsNullOrEmpty(m_freeSwitchRealm) ||
152 String.IsNullOrEmpty(m_freeSwitchAPIPrefix))
153 {
154 m_log.Error("[FreeSwitchVoice] plugin mis-configured");
155 m_log.Info("[FreeSwitchVoice] plugin disabled: incomplete configuration");
156 return;
157 }
158
159 // set up http request handlers for
160 // - prelogin: viv_get_prelogin.php
161 // - signin: viv_signin.php
162 scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix),
163 FreeSwitchSLVoiceGetPreloginHTTPHandler);
164
165 // RestStreamHandler h = new RestStreamHandler("GET", String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), FreeSwitchSLVoiceGetPreloginHTTPHandler);
166 // scene.CommsManager.HttpServer.AddStreamHandler(h);
167
168
169
170 scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/viv_signin.php", m_freeSwitchAPIPrefix),
171 FreeSwitchSLVoiceSigninHTTPHandler);
172
173 // set up http request handlers to provide
174 // on-demand FreeSwitch configuration to
175 // FreeSwitch's mod_curl_xml
176 scene.CommsManager.HttpServer.AddHTTPHandler(String.Format("{0}/freeswitch-config", m_freeSwitchAPIPrefix),
177 FreeSwitchConfigHTTPHandler);
178
179 m_log.InfoFormat("[FreeSwitchVoice] using FreeSwitch server {0}", m_freeSwitchRealm);
180
181 m_FreeSwitchDirectory = new FreeSwitchDirectory();
182 m_FreeSwitchDialplan = new FreeSwitchDialplan();
183
184 m_pluginEnabled = true;
185 m_WOF = false;
186
187 m_log.Info("[FreeSwitchVoice] plugin enabled");
188 }
189 catch (Exception e)
190 {
191 m_log.ErrorFormat("[FreeSwitchVoice] plugin initialization failed: {0}", e.Message);
192 m_log.DebugFormat("[FreeSwitchVoice] plugin initialization failed: {0}", e.ToString());
193 return;
194 }
195 }
196
197 if (m_pluginEnabled)
198 {
199 // we need to capture scene in an anonymous method
200 // here as we need it later in the callbacks
201 scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps)
202 {
203 OnRegisterCaps(scene, agentID, caps);
204 };
205
206
207
208 }
209 }
210
211 public void PostInitialise()
212 {
213 }
214
215 public void Close()
216 {
217 }
218
219 public string Name
220 {
221 get { return "FreeSwitchVoiceModule"; }
222 }
223
224 public bool IsSharedModule
225 {
226 get { return true; }
227 }
228
229
230 // <summary>
231 // OnRegisterCaps is invoked via the scene.EventManager
232 // everytime OpenSim hands out capabilities to a client
233 // (login, region crossing). We contribute two capabilities to
234 // the set of capabilities handed back to the client:
235 // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest.
236 //
237 // ProvisionVoiceAccountRequest allows the client to obtain
238 // the voice account credentials for the avatar it is
239 // controlling (e.g., user name, password, etc).
240 //
241 // ParcelVoiceInfoRequest is invoked whenever the client
242 // changes from one region or parcel to another.
243 //
244 // Note that OnRegisterCaps is called here via a closure
245 // delegate containing the scene of the respective region (see
246 // Initialise()).
247 // </summary>
248 public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
249 {
250 m_log.DebugFormat("[FreeSwitchVoice] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
251
252 string capsBase = "/CAPS/" + caps.CapsObjectPath;
253 caps.RegisterHandler("ProvisionVoiceAccountRequest",
254 new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath,
255 delegate(string request, string path, string param,
256 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
257 {
258 return ProvisionVoiceAccountRequest(scene, request, path, param,
259 agentID, caps);
260 }));
261 caps.RegisterHandler("ParcelVoiceInfoRequest",
262 new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath,
263 delegate(string request, string path, string param,
264 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
265 {
266 return ParcelVoiceInfoRequest(scene, request, path, param,
267 agentID, caps);
268 }));
269 caps.RegisterHandler("ChatSessionRequest",
270 new RestStreamHandler("POST", capsBase + m_chatSessionRequestPath,
271 delegate(string request, string path, string param,
272 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
273 {
274 return ChatSessionRequest(scene, request, path, param,
275 agentID, caps);
276 }));
277 }
278
279 /// <summary>
280 /// Callback for a client request for Voice Account Details
281 /// </summary>
282 /// <param name="scene">current scene object of the client</param>
283 /// <param name="request"></param>
284 /// <param name="path"></param>
285 /// <param name="param"></param>
286 /// <param name="agentID"></param>
287 /// <param name="caps"></param>
288 /// <returns></returns>
289 public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param,
290 UUID agentID, Caps caps)
291 {
292 ScenePresence avatar = scene.GetScenePresence(agentID);
293 string avatarName = avatar.Name;
294
295 try
296 {
297 m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
298 request, path, param);
299
300 //XmlElement resp;
301 string agentname = "x" + Convert.ToBase64String(agentID.GetBytes());
302 string password = "1234";//temp hack//new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16);
303
304 // XXX: we need to cache the voice credentials, as
305 // FreeSwitch is later going to come and ask us for
306 // those
307
308 agentname = agentname.Replace('+', '-').Replace('/', '_');
309
310 // LLSDVoiceAccountResponse voiceAccountResponse =
311 // new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, "http://etsvc02.hursley.ibm.com/api");
312 LLSDVoiceAccountResponse voiceAccountResponse =
313 new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm,
314 String.Format("http://{0}/{1}/", m_FreeSwitchServiceIP,
315 m_freeSwitchAPIPrefix));
316
317 string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
318
319 m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r);
320
321 return r;
322 }
323 catch (Exception e)
324 {
325 m_log.ErrorFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}, retry later", avatarName, e.Message);
326 m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1} failed", avatarName, e.ToString());
327
328 return "<llsd>undef</llsd>";
329 }
330 }
331
332 /// <summary>
333 /// Callback for a client request for ParcelVoiceInfo
334 /// </summary>
335 /// <param name="scene">current scene object of the client</param>
336 /// <param name="request"></param>
337 /// <param name="path"></param>
338 /// <param name="param"></param>
339 /// <param name="agentID"></param>
340 /// <param name="caps"></param>
341 /// <returns></returns>
342 public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param,
343 UUID agentID, Caps caps)
344 {
345 ScenePresence avatar = scene.GetScenePresence(agentID);
346 string avatarName = avatar.Name;
347
348 // - check whether we have a region channel in our cache
349 // - if not:
350 // create it and cache it
351 // - send it to the client
352 // - send channel_uri: as "sip:regionID@m_sipDomain"
353 try
354 {
355 LLSDParcelVoiceInfoResponse parcelVoiceInfo;
356 string channelUri;
357
358 if (null == scene.LandChannel)
359 throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available",
360 scene.RegionInfo.RegionName, avatarName));
361
362
363
364 // get channel_uri: check first whether estate
365 // settings allow voice, then whether parcel allows
366 // voice, if all do retrieve or obtain the parcel
367 // voice channel
368 LandData land = scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y);
369
370 m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}",
371 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param);
372
373 // TODO: EstateSettings don't seem to get propagated...
374 // if (!scene.RegionInfo.EstateSettings.AllowVoice)
375 // {
376 // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings",
377 // scene.RegionInfo.RegionName);
378 // channel_uri = String.Empty;
379 // }
380 // else
381
382 if ((land.Flags & (uint)Parcel.ParcelFlags.AllowVoiceChat) == 0)
383 {
384 m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel",
385 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName);
386 channelUri = String.Empty;
387 }
388 else
389 {
390 channelUri = ChannelUri(scene, land);
391 }
392
393 // fill in our response to the client
394 Hashtable creds = new Hashtable();
395 creds["channel_uri"] = channelUri;
396
397 parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds);
398 string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
399
400 m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}",
401 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r);
402 return r;
403 }
404 catch (Exception e)
405 {
406 m_log.ErrorFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later",
407 scene.RegionInfo.RegionName, avatarName, e.Message);
408 m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed",
409 scene.RegionInfo.RegionName, avatarName, e.ToString());
410
411 return "<llsd>undef</llsd>";
412 }
413 }
414
415
416 /// <summary>
417 /// Callback for a client request for ChatSessionRequest
418 /// </summary>
419 /// <param name="scene">current scene object of the client</param>
420 /// <param name="request"></param>
421 /// <param name="path"></param>
422 /// <param name="param"></param>
423 /// <param name="agentID"></param>
424 /// <param name="caps"></param>
425 /// <returns></returns>
426 public string ChatSessionRequest(Scene scene, string request, string path, string param,
427 UUID agentID, Caps caps)
428 {
429 ScenePresence avatar = scene.GetScenePresence(agentID);
430 string avatarName = avatar.Name;
431
432 m_log.DebugFormat("[FreeSwitchVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}",
433 avatarName, request, path, param);
434 return "<llsd>true</llsd>";
435 }
436
437
438 public Hashtable FreeSwitchSLVoiceGetPreloginHTTPHandler(Hashtable request)
439 {
440 m_log.Debug("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler called");
441
442 Hashtable response = new Hashtable();
443 response["content_type"] = "text/xml";
444 response["keepalive"] = false;
445
446 response["str_response_string"] = String.Format(
447 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
448 "<VCConfiguration>\r\n"+
449 "<DefaultRealm>{0}</DefaultRealm>\r\n" +
450 "<DefaultSIPProxy>{1}</DefaultSIPProxy>\r\n"+
451 "<DefaultAttemptUseSTUN>{2}</DefaultAttemptUseSTUN>\r\n"+
452 "<DefaultEchoServer>{3}</DefaultEchoServer>\r\n"+
453 "<DefaultEchoPort>{4}</DefaultEchoPort>\r\n"+
454 "<DefaultWellKnownIP>{5}</DefaultWellKnownIP>\r\n"+
455 "<DefaultTimeout>{6}</DefaultTimeout>\r\n"+
456 "<UrlResetPassword>{7}</UrlResetPassword>\r\n"+
457 "<UrlPrivacyNotice>{8}</UrlPrivacyNotice>\r\n"+
458 "<UrlEulaNotice/>\r\n"+
459 "<App.NoBottomLogo>false</App.NoBottomLogo>\r\n"+
460 "</VCConfiguration>"
461 ,
462 m_freeSwitchRealm,m_freeSwitchSIPProxy,m_freeSwitchAttemptUseSTUN,
463 m_freeSwitchSTUNServer,m_freeSwitchEchoServer,m_freeSwitchEchoPort,
464 m_freeSwitchDefaultWellKnownIP,m_freeSwitchDefaultTimeout,m_freeSwitchUrlResetPassword,"");
465
466 response["int_response_code"] = 200;
467
468 m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler return {0}",response["str_response_string"]);
469 return response;
470 }
471
472 public Hashtable FreeSwitchSLVoiceSigninHTTPHandler(Hashtable request)
473 {
474 m_log.Debug("[FreeSwitchVoice] FreeSwitchSLVoiceSigninHTTPHandler called");
475
476 Hashtable response = new Hashtable();
477 response["str_response_string"] = @"<response xsi:schemaLocation=""/xsd/error.xsd"">
478 <level0>
479 <status>OK</status>
480 <body>
481 <code>200</code>
482 <msg>auth successful</msg>
483 </body>
484 </level0>
485 </response>";
486 response["int_response_code"] = 200;
487 return response;
488 }
489
490
491 public Hashtable FreeSwitchConfigHTTPHandler(Hashtable request)
492 {
493 m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchConfigHTTPHandler called with {0}",request.ToString());
494
495 Hashtable response = new Hashtable();
496
497 // all the params come as NVPs in the request body
498 Hashtable requestBody = parseRequestBody((string) request["body"]);
499
500 // is this a dialplan or directory request
501 string section = (string) requestBody["section"];
502
503 if(section=="directory")
504 response = m_FreeSwitchDirectory.HandleDirectoryRequest(requestBody);
505 else if (section=="dialplan")
506 response = m_FreeSwitchDialplan.HandleDialplanRequest(requestBody);
507
508 // XXX: re-generate dialplan:
509 // - conf == region UUID
510 // - conf number = region port
511 // -> TODO Initialise(): keep track of regions via events
512 // re-generate accounts for all avatars
513 // -> TODO Initialise(): keep track of avatars via events
514 m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchConfigHTTPHandler return {0}",response["str_response_string"]);
515 return response;
516 }
517
518 public Hashtable parseRequestBody(string body)
519 {
520 Hashtable bodyParams = new Hashtable();
521 // split string
522 string [] nvps = body.Split(new Char [] {'&'});
523
524 foreach (string s in nvps) {
525
526 if (s.Trim() != "")
527 {
528 string [] nvp = s.Split(new Char [] {'='});
529 bodyParams.Add(HttpUtility.UrlDecode(nvp[0]),HttpUtility.UrlDecode(nvp[1]));
530 }
531 }
532
533 return bodyParams;
534
535 }
536
537 private string ChannelUri(Scene scene, LandData land)
538 {
539
540 string channelUri = null;
541
542 string landUUID;
543 string landName;
544
545 // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same
546 // as the directory ID. Otherwise, it reflects the parcel's ID.
547
548 if (land.LocalID != 1 && (land.Flags & (uint)Parcel.ParcelFlags.UseEstateVoiceChan) == 0)
549 {
550 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name);
551 landUUID = land.GlobalID.ToString();
552 m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
553 landName, land.LocalID, landUUID);
554 }
555 else
556 {
557 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName);
558 landUUID = scene.RegionInfo.RegionID.ToString();
559 m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
560 landName, land.LocalID, landUUID);
561 }
562 System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
563 channelUri = String.Format("sip:confctl-{0}@{1}", "x" + Convert.ToBase64String(encoding.GetBytes(landUUID)), m_freeSwitchRealm);
564
565 //channelUri="sip:confctl-3001@9.20.151.43";
566 //channelUri="sip:opensimconf-3001@9.20.151.43";
567
568 return channelUri;
569 }
570 }
571}