diff options
author | UbitUmarov | 2015-09-01 11:43:07 +0100 |
---|---|---|
committer | UbitUmarov | 2015-09-01 11:43:07 +0100 |
commit | fb78b182520fc9bb0f971afd0322029c70278ea6 (patch) | |
tree | b4e30d383938fdeef8c92d1d1c2f44bb61d329bd /OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice | |
parent | lixo (diff) | |
parent | Mantis #7713: fixed bug introduced by 1st MOSES patch. (diff) | |
download | opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.zip opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.gz opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.bz2 opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.xz |
Merge remote-tracking branch 'os/master'
Diffstat (limited to 'OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice')
-rw-r--r-- | OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs | 899 |
1 files changed, 899 insertions, 0 deletions
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..45af212 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs | |||
@@ -0,0 +1,899 @@ | |||
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 OpenSimulator 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 | |||
28 | using System; | ||
29 | using System.IO; | ||
30 | using System.Net; | ||
31 | using System.Net.Security; | ||
32 | using System.Web; | ||
33 | using System.Security.Cryptography.X509Certificates; | ||
34 | using System.Text; | ||
35 | using System.Xml; | ||
36 | using System.Collections; | ||
37 | using System.Collections.Generic; | ||
38 | using System.Reflection; | ||
39 | using OpenMetaverse; | ||
40 | using OpenMetaverse.StructuredData; | ||
41 | using log4net; | ||
42 | using Nini.Config; | ||
43 | using Nwc.XmlRpc; | ||
44 | using OpenSim.Framework; | ||
45 | using Mono.Addins; | ||
46 | |||
47 | using OpenSim.Framework.Capabilities; | ||
48 | using OpenSim.Framework.Servers; | ||
49 | using OpenSim.Framework.Servers.HttpServer; | ||
50 | using OpenSim.Region.Framework.Interfaces; | ||
51 | using OpenSim.Region.Framework.Scenes; | ||
52 | using Caps = OpenSim.Framework.Capabilities.Caps; | ||
53 | using System.Text.RegularExpressions; | ||
54 | using OpenSim.Server.Base; | ||
55 | using OpenSim.Services.Interfaces; | ||
56 | using OSDMap = OpenMetaverse.StructuredData.OSDMap; | ||
57 | |||
58 | namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice | ||
59 | { | ||
60 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FreeSwitchVoiceModule")] | ||
61 | public class FreeSwitchVoiceModule : ISharedRegionModule, IVoiceModule | ||
62 | { | ||
63 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
64 | |||
65 | // Capability string prefixes | ||
66 | private static readonly string m_parcelVoiceInfoRequestPath = "0207/"; | ||
67 | private static readonly string m_provisionVoiceAccountRequestPath = "0208/"; | ||
68 | //private static readonly string m_chatSessionRequestPath = "0209/"; | ||
69 | |||
70 | // Control info | ||
71 | private static bool m_Enabled = false; | ||
72 | |||
73 | // FreeSwitch server is going to contact us and ask us all | ||
74 | // sorts of things. | ||
75 | |||
76 | // SLVoice client will do a GET on this prefix | ||
77 | private static string m_freeSwitchAPIPrefix; | ||
78 | |||
79 | // We need to return some information to SLVoice | ||
80 | // figured those out via curl | ||
81 | // http://vd1.vivox.com/api2/viv_get_prelogin.php | ||
82 | // | ||
83 | // need to figure out whether we do need to return ALL of | ||
84 | // these... | ||
85 | private static string m_freeSwitchRealm; | ||
86 | private static string m_freeSwitchSIPProxy; | ||
87 | private static bool m_freeSwitchAttemptUseSTUN; | ||
88 | private static string m_freeSwitchEchoServer; | ||
89 | private static int m_freeSwitchEchoPort; | ||
90 | private static string m_freeSwitchDefaultWellKnownIP; | ||
91 | private static int m_freeSwitchDefaultTimeout; | ||
92 | private static string m_freeSwitchUrlResetPassword; | ||
93 | private uint m_freeSwitchServicePort; | ||
94 | private string m_openSimWellKnownHTTPAddress; | ||
95 | // private string m_freeSwitchContext; | ||
96 | |||
97 | private readonly Dictionary<string, string> m_UUIDName = new Dictionary<string, string>(); | ||
98 | private Dictionary<string, string> m_ParcelAddress = new Dictionary<string, string>(); | ||
99 | |||
100 | private IConfig m_Config; | ||
101 | |||
102 | private IFreeswitchService m_FreeswitchService; | ||
103 | |||
104 | public void Initialise(IConfigSource config) | ||
105 | { | ||
106 | m_Config = config.Configs["FreeSwitchVoice"]; | ||
107 | |||
108 | if (m_Config == null) | ||
109 | return; | ||
110 | |||
111 | if (!m_Config.GetBoolean("Enabled", false)) | ||
112 | return; | ||
113 | |||
114 | try | ||
115 | { | ||
116 | string serviceDll = m_Config.GetString("LocalServiceModule", | ||
117 | String.Empty); | ||
118 | |||
119 | if (serviceDll == String.Empty) | ||
120 | { | ||
121 | m_log.Error("[FreeSwitchVoice]: No LocalServiceModule named in section FreeSwitchVoice. Not starting."); | ||
122 | return; | ||
123 | } | ||
124 | |||
125 | Object[] args = new Object[] { config }; | ||
126 | m_FreeswitchService = ServerUtils.LoadPlugin<IFreeswitchService>(serviceDll, args); | ||
127 | |||
128 | string jsonConfig = m_FreeswitchService.GetJsonConfig(); | ||
129 | //m_log.Debug("[FreeSwitchVoice]: Configuration string: " + jsonConfig); | ||
130 | OSDMap map = (OSDMap)OSDParser.DeserializeJson(jsonConfig); | ||
131 | |||
132 | m_freeSwitchAPIPrefix = map["APIPrefix"].AsString(); | ||
133 | m_freeSwitchRealm = map["Realm"].AsString(); | ||
134 | m_freeSwitchSIPProxy = map["SIPProxy"].AsString(); | ||
135 | m_freeSwitchAttemptUseSTUN = map["AttemptUseSTUN"].AsBoolean(); | ||
136 | m_freeSwitchEchoServer = map["EchoServer"].AsString(); | ||
137 | m_freeSwitchEchoPort = map["EchoPort"].AsInteger(); | ||
138 | m_freeSwitchDefaultWellKnownIP = map["DefaultWellKnownIP"].AsString(); | ||
139 | m_freeSwitchDefaultTimeout = map["DefaultTimeout"].AsInteger(); | ||
140 | m_freeSwitchUrlResetPassword = String.Empty; | ||
141 | // m_freeSwitchContext = map["Context"].AsString(); | ||
142 | |||
143 | if (String.IsNullOrEmpty(m_freeSwitchRealm) || | ||
144 | String.IsNullOrEmpty(m_freeSwitchAPIPrefix)) | ||
145 | { | ||
146 | m_log.Error("[FreeSwitchVoice]: Freeswitch service mis-configured. Not starting."); | ||
147 | return; | ||
148 | } | ||
149 | |||
150 | // set up http request handlers for | ||
151 | // - prelogin: viv_get_prelogin.php | ||
152 | // - signin: viv_signin.php | ||
153 | // - buddies: viv_buddy.php | ||
154 | // - ???: viv_watcher.php | ||
155 | // - signout: viv_signout.php | ||
156 | MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), | ||
157 | FreeSwitchSLVoiceGetPreloginHTTPHandler); | ||
158 | |||
159 | MainServer.Instance.AddHTTPHandler(String.Format("{0}/freeswitch-config", m_freeSwitchAPIPrefix), FreeSwitchConfigHTTPHandler); | ||
160 | |||
161 | // RestStreamHandler h = new | ||
162 | // RestStreamHandler("GET", | ||
163 | // String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), FreeSwitchSLVoiceGetPreloginHTTPHandler); | ||
164 | // MainServer.Instance.AddStreamHandler(h); | ||
165 | |||
166 | MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_signin.php", m_freeSwitchAPIPrefix), | ||
167 | FreeSwitchSLVoiceSigninHTTPHandler); | ||
168 | |||
169 | MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_buddy.php", m_freeSwitchAPIPrefix), | ||
170 | FreeSwitchSLVoiceBuddyHTTPHandler); | ||
171 | |||
172 | MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_watcher.php", m_freeSwitchAPIPrefix), | ||
173 | FreeSwitchSLVoiceWatcherHTTPHandler); | ||
174 | |||
175 | m_log.InfoFormat("[FreeSwitchVoice]: using FreeSwitch server {0}", m_freeSwitchRealm); | ||
176 | |||
177 | m_Enabled = true; | ||
178 | |||
179 | m_log.Info("[FreeSwitchVoice]: plugin enabled"); | ||
180 | } | ||
181 | catch (Exception e) | ||
182 | { | ||
183 | m_log.ErrorFormat("[FreeSwitchVoice]: plugin initialization failed: {0} {1}", e.Message, e.StackTrace); | ||
184 | return; | ||
185 | } | ||
186 | |||
187 | // This here is a region module trying to make a global setting. | ||
188 | // Not really a good idea but it's Windows only, so I can't test. | ||
189 | try | ||
190 | { | ||
191 | ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidation; | ||
192 | } | ||
193 | catch (NotImplementedException) | ||
194 | { | ||
195 | try | ||
196 | { | ||
197 | #pragma warning disable 0612, 0618 | ||
198 | // Mono does not implement the ServicePointManager.ServerCertificateValidationCallback yet! Don't remove this! | ||
199 | ServicePointManager.CertificatePolicy = new MonoCert(); | ||
200 | #pragma warning restore 0612, 0618 | ||
201 | } | ||
202 | catch (Exception) | ||
203 | { | ||
204 | // COmmented multiline spam log message | ||
205 | //m_log.Error("[FreeSwitchVoice]: Certificate validation handler change not supported. You may get ssl certificate validation errors teleporting from your region to some SSL regions."); | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | |||
210 | public void PostInitialise() | ||
211 | { | ||
212 | } | ||
213 | |||
214 | public void AddRegion(Scene scene) | ||
215 | { | ||
216 | // We generate these like this: The region's external host name | ||
217 | // as defined in Regions.ini is a good address to use. It's a | ||
218 | // dotted quad (or should be!) and it can reach this host from | ||
219 | // a client. The port is grabbed from the region's HTTP server. | ||
220 | m_openSimWellKnownHTTPAddress = scene.RegionInfo.ExternalHostName; | ||
221 | m_freeSwitchServicePort = MainServer.Instance.Port; | ||
222 | |||
223 | if (m_Enabled) | ||
224 | { | ||
225 | // we need to capture scene in an anonymous method | ||
226 | // here as we need it later in the callbacks | ||
227 | scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps) | ||
228 | { | ||
229 | OnRegisterCaps(scene, agentID, caps); | ||
230 | }; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | public void RemoveRegion(Scene scene) | ||
235 | { | ||
236 | } | ||
237 | |||
238 | public void RegionLoaded(Scene scene) | ||
239 | { | ||
240 | if (m_Enabled) | ||
241 | { | ||
242 | m_log.Info("[FreeSwitchVoice]: registering IVoiceModule with the scene"); | ||
243 | |||
244 | // register the voice interface for this module, so the script engine can call us | ||
245 | scene.RegisterModuleInterface<IVoiceModule>(this); | ||
246 | } | ||
247 | } | ||
248 | |||
249 | public void Close() | ||
250 | { | ||
251 | } | ||
252 | |||
253 | public string Name | ||
254 | { | ||
255 | get { return "FreeSwitchVoiceModule"; } | ||
256 | } | ||
257 | |||
258 | public Type ReplaceableInterface | ||
259 | { | ||
260 | get { return null; } | ||
261 | } | ||
262 | |||
263 | // <summary> | ||
264 | // implementation of IVoiceModule, called by osSetParcelSIPAddress script function | ||
265 | // </summary> | ||
266 | public void setLandSIPAddress(string SIPAddress,UUID GlobalID) | ||
267 | { | ||
268 | m_log.DebugFormat("[FreeSwitchVoice]: setLandSIPAddress parcel id {0}: setting sip address {1}", | ||
269 | GlobalID, SIPAddress); | ||
270 | |||
271 | lock (m_ParcelAddress) | ||
272 | { | ||
273 | if (m_ParcelAddress.ContainsKey(GlobalID.ToString())) | ||
274 | { | ||
275 | m_ParcelAddress[GlobalID.ToString()] = SIPAddress; | ||
276 | } | ||
277 | else | ||
278 | { | ||
279 | m_ParcelAddress.Add(GlobalID.ToString(), SIPAddress); | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | |||
284 | // <summary> | ||
285 | // OnRegisterCaps is invoked via the scene.EventManager | ||
286 | // everytime OpenSim hands out capabilities to a client | ||
287 | // (login, region crossing). We contribute two capabilities to | ||
288 | // the set of capabilities handed back to the client: | ||
289 | // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest. | ||
290 | // | ||
291 | // ProvisionVoiceAccountRequest allows the client to obtain | ||
292 | // the voice account credentials for the avatar it is | ||
293 | // controlling (e.g., user name, password, etc). | ||
294 | // | ||
295 | // ParcelVoiceInfoRequest is invoked whenever the client | ||
296 | // changes from one region or parcel to another. | ||
297 | // | ||
298 | // Note that OnRegisterCaps is called here via a closure | ||
299 | // delegate containing the scene of the respective region (see | ||
300 | // Initialise()). | ||
301 | // </summary> | ||
302 | public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps) | ||
303 | { | ||
304 | m_log.DebugFormat( | ||
305 | "[FreeSwitchVoice]: OnRegisterCaps() called with agentID {0} caps {1} in scene {2}", | ||
306 | agentID, caps, scene.RegionInfo.RegionName); | ||
307 | |||
308 | string capsBase = "/CAPS/" + caps.CapsObjectPath; | ||
309 | caps.RegisterHandler( | ||
310 | "ProvisionVoiceAccountRequest", | ||
311 | new RestStreamHandler( | ||
312 | "POST", | ||
313 | capsBase + m_provisionVoiceAccountRequestPath, | ||
314 | (request, path, param, httpRequest, httpResponse) | ||
315 | => ProvisionVoiceAccountRequest(scene, request, path, param, agentID, caps), | ||
316 | "ProvisionVoiceAccountRequest", | ||
317 | agentID.ToString())); | ||
318 | |||
319 | caps.RegisterHandler( | ||
320 | "ParcelVoiceInfoRequest", | ||
321 | new RestStreamHandler( | ||
322 | "POST", | ||
323 | capsBase + m_parcelVoiceInfoRequestPath, | ||
324 | (request, path, param, httpRequest, httpResponse) | ||
325 | => ParcelVoiceInfoRequest(scene, request, path, param, agentID, caps), | ||
326 | "ParcelVoiceInfoRequest", | ||
327 | agentID.ToString())); | ||
328 | |||
329 | //caps.RegisterHandler( | ||
330 | // "ChatSessionRequest", | ||
331 | // new RestStreamHandler( | ||
332 | // "POST", | ||
333 | // capsBase + m_chatSessionRequestPath, | ||
334 | // (request, path, param, httpRequest, httpResponse) | ||
335 | // => ChatSessionRequest(scene, request, path, param, agentID, caps), | ||
336 | // "ChatSessionRequest", | ||
337 | // agentID.ToString())); | ||
338 | } | ||
339 | |||
340 | /// <summary> | ||
341 | /// Callback for a client request for Voice Account Details | ||
342 | /// </summary> | ||
343 | /// <param name="scene">current scene object of the client</param> | ||
344 | /// <param name="request"></param> | ||
345 | /// <param name="path"></param> | ||
346 | /// <param name="param"></param> | ||
347 | /// <param name="agentID"></param> | ||
348 | /// <param name="caps"></param> | ||
349 | /// <returns></returns> | ||
350 | public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param, | ||
351 | UUID agentID, Caps caps) | ||
352 | { | ||
353 | m_log.DebugFormat( | ||
354 | "[FreeSwitchVoice][PROVISIONVOICE]: ProvisionVoiceAccountRequest() request: {0}, path: {1}, param: {2}", request, path, param); | ||
355 | |||
356 | ScenePresence avatar = scene.GetScenePresence(agentID); | ||
357 | if (avatar == null) | ||
358 | { | ||
359 | System.Threading.Thread.Sleep(2000); | ||
360 | avatar = scene.GetScenePresence(agentID); | ||
361 | |||
362 | if (avatar == null) | ||
363 | return "<llsd>undef</llsd>"; | ||
364 | } | ||
365 | string avatarName = avatar.Name; | ||
366 | |||
367 | try | ||
368 | { | ||
369 | //XmlElement resp; | ||
370 | string agentname = "x" + Convert.ToBase64String(agentID.GetBytes()); | ||
371 | string password = "1234";//temp hack//new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16); | ||
372 | |||
373 | // XXX: we need to cache the voice credentials, as | ||
374 | // FreeSwitch is later going to come and ask us for | ||
375 | // those | ||
376 | agentname = agentname.Replace('+', '-').Replace('/', '_'); | ||
377 | |||
378 | lock (m_UUIDName) | ||
379 | { | ||
380 | if (m_UUIDName.ContainsKey(agentname)) | ||
381 | { | ||
382 | m_UUIDName[agentname] = avatarName; | ||
383 | } | ||
384 | else | ||
385 | { | ||
386 | m_UUIDName.Add(agentname, avatarName); | ||
387 | } | ||
388 | } | ||
389 | |||
390 | // LLSDVoiceAccountResponse voiceAccountResponse = | ||
391 | // new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, "http://etsvc02.hursley.ibm.com/api"); | ||
392 | LLSDVoiceAccountResponse voiceAccountResponse = | ||
393 | new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, | ||
394 | String.Format("http://{0}:{1}{2}/", m_openSimWellKnownHTTPAddress, | ||
395 | m_freeSwitchServicePort, m_freeSwitchAPIPrefix)); | ||
396 | |||
397 | string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse); | ||
398 | |||
399 | // m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r); | ||
400 | |||
401 | return r; | ||
402 | } | ||
403 | catch (Exception e) | ||
404 | { | ||
405 | m_log.ErrorFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}, retry later", avatarName, e.Message); | ||
406 | m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1} failed", avatarName, e.ToString()); | ||
407 | |||
408 | return "<llsd>undef</llsd>"; | ||
409 | } | ||
410 | } | ||
411 | |||
412 | /// <summary> | ||
413 | /// Callback for a client request for ParcelVoiceInfo | ||
414 | /// </summary> | ||
415 | /// <param name="scene">current scene object of the client</param> | ||
416 | /// <param name="request"></param> | ||
417 | /// <param name="path"></param> | ||
418 | /// <param name="param"></param> | ||
419 | /// <param name="agentID"></param> | ||
420 | /// <param name="caps"></param> | ||
421 | /// <returns></returns> | ||
422 | public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param, | ||
423 | UUID agentID, Caps caps) | ||
424 | { | ||
425 | m_log.DebugFormat( | ||
426 | "[FreeSwitchVoice][PARCELVOICE]: ParcelVoiceInfoRequest() on {0} for {1}", | ||
427 | scene.RegionInfo.RegionName, agentID); | ||
428 | |||
429 | ScenePresence avatar = scene.GetScenePresence(agentID); | ||
430 | string avatarName = avatar.Name; | ||
431 | |||
432 | // - check whether we have a region channel in our cache | ||
433 | // - if not: | ||
434 | // create it and cache it | ||
435 | // - send it to the client | ||
436 | // - send channel_uri: as "sip:regionID@m_sipDomain" | ||
437 | try | ||
438 | { | ||
439 | LLSDParcelVoiceInfoResponse parcelVoiceInfo; | ||
440 | string channelUri; | ||
441 | |||
442 | if (null == scene.LandChannel) | ||
443 | throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available", | ||
444 | scene.RegionInfo.RegionName, avatarName)); | ||
445 | |||
446 | // get channel_uri: check first whether estate | ||
447 | // settings allow voice, then whether parcel allows | ||
448 | // voice, if all do retrieve or obtain the parcel | ||
449 | // voice channel | ||
450 | LandData land = scene.GetLandData(avatar.AbsolutePosition); | ||
451 | |||
452 | //m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}", | ||
453 | // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param); | ||
454 | |||
455 | // TODO: EstateSettings don't seem to get propagated... | ||
456 | // if (!scene.RegionInfo.EstateSettings.AllowVoice) | ||
457 | // { | ||
458 | // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings", | ||
459 | // scene.RegionInfo.RegionName); | ||
460 | // channel_uri = String.Empty; | ||
461 | // } | ||
462 | // else | ||
463 | |||
464 | if ((land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0) | ||
465 | { | ||
466 | // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel", | ||
467 | // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName); | ||
468 | channelUri = String.Empty; | ||
469 | } | ||
470 | else | ||
471 | { | ||
472 | channelUri = ChannelUri(scene, land); | ||
473 | } | ||
474 | |||
475 | // fill in our response to the client | ||
476 | Hashtable creds = new Hashtable(); | ||
477 | creds["channel_uri"] = channelUri; | ||
478 | |||
479 | parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds); | ||
480 | string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo); | ||
481 | |||
482 | // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}", | ||
483 | // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r); | ||
484 | return r; | ||
485 | } | ||
486 | catch (Exception e) | ||
487 | { | ||
488 | m_log.ErrorFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later", | ||
489 | scene.RegionInfo.RegionName, avatarName, e.Message); | ||
490 | m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed", | ||
491 | scene.RegionInfo.RegionName, avatarName, e.ToString()); | ||
492 | |||
493 | return "<llsd>undef</llsd>"; | ||
494 | } | ||
495 | } | ||
496 | |||
497 | /// <summary> | ||
498 | /// Callback for a client request for ChatSessionRequest | ||
499 | /// </summary> | ||
500 | /// <param name="scene">current scene object of the client</param> | ||
501 | /// <param name="request"></param> | ||
502 | /// <param name="path"></param> | ||
503 | /// <param name="param"></param> | ||
504 | /// <param name="agentID"></param> | ||
505 | /// <param name="caps"></param> | ||
506 | /// <returns></returns> | ||
507 | public string ChatSessionRequest(Scene scene, string request, string path, string param, | ||
508 | UUID agentID, Caps caps) | ||
509 | { | ||
510 | ScenePresence avatar = scene.GetScenePresence(agentID); | ||
511 | string avatarName = avatar.Name; | ||
512 | |||
513 | m_log.DebugFormat("[FreeSwitchVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}", | ||
514 | avatarName, request, path, param); | ||
515 | |||
516 | return "<llsd>true</llsd>"; | ||
517 | } | ||
518 | |||
519 | public Hashtable ForwardProxyRequest(Hashtable request) | ||
520 | { | ||
521 | m_log.Debug("[PROXYING]: -------------------------------proxying request"); | ||
522 | Hashtable response = new Hashtable(); | ||
523 | response["content_type"] = "text/xml"; | ||
524 | response["str_response_string"] = ""; | ||
525 | response["int_response_code"] = 200; | ||
526 | |||
527 | string forwardaddress = "https://www.bhr.vivox.com/api2/"; | ||
528 | string body = (string)request["body"]; | ||
529 | string method = (string) request["http-method"]; | ||
530 | string contenttype = (string) request["content-type"]; | ||
531 | string uri = (string) request["uri"]; | ||
532 | uri = uri.Replace("/api/", ""); | ||
533 | forwardaddress += uri; | ||
534 | |||
535 | |||
536 | string fwdresponsestr = ""; | ||
537 | int fwdresponsecode = 200; | ||
538 | string fwdresponsecontenttype = "text/xml"; | ||
539 | |||
540 | HttpWebRequest forwardreq = (HttpWebRequest)WebRequest.Create(forwardaddress); | ||
541 | forwardreq.Method = method; | ||
542 | forwardreq.ContentType = contenttype; | ||
543 | forwardreq.KeepAlive = false; | ||
544 | |||
545 | if (method == "POST") | ||
546 | { | ||
547 | byte[] contentreq = Util.UTF8.GetBytes(body); | ||
548 | forwardreq.ContentLength = contentreq.Length; | ||
549 | Stream reqStream = forwardreq.GetRequestStream(); | ||
550 | reqStream.Write(contentreq, 0, contentreq.Length); | ||
551 | reqStream.Close(); | ||
552 | } | ||
553 | |||
554 | using (HttpWebResponse fwdrsp = (HttpWebResponse)forwardreq.GetResponse()) | ||
555 | { | ||
556 | Encoding encoding = Util.UTF8; | ||
557 | |||
558 | using (Stream s = fwdrsp.GetResponseStream()) | ||
559 | { | ||
560 | using (StreamReader fwdresponsestream = new StreamReader(s)) | ||
561 | { | ||
562 | fwdresponsestr = fwdresponsestream.ReadToEnd(); | ||
563 | fwdresponsecontenttype = fwdrsp.ContentType; | ||
564 | fwdresponsecode = (int)fwdrsp.StatusCode; | ||
565 | } | ||
566 | } | ||
567 | } | ||
568 | |||
569 | response["content_type"] = fwdresponsecontenttype; | ||
570 | response["str_response_string"] = fwdresponsestr; | ||
571 | response["int_response_code"] = fwdresponsecode; | ||
572 | |||
573 | return response; | ||
574 | } | ||
575 | |||
576 | public Hashtable FreeSwitchSLVoiceGetPreloginHTTPHandler(Hashtable request) | ||
577 | { | ||
578 | m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceGetPreloginHTTPHandler called"); | ||
579 | |||
580 | Hashtable response = new Hashtable(); | ||
581 | response["content_type"] = "text/xml"; | ||
582 | response["keepalive"] = false; | ||
583 | |||
584 | response["str_response_string"] = String.Format( | ||
585 | "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + | ||
586 | "<VCConfiguration>\r\n"+ | ||
587 | "<DefaultRealm>{0}</DefaultRealm>\r\n" + | ||
588 | "<DefaultSIPProxy>{1}</DefaultSIPProxy>\r\n"+ | ||
589 | "<DefaultAttemptUseSTUN>{2}</DefaultAttemptUseSTUN>\r\n"+ | ||
590 | "<DefaultEchoServer>{3}</DefaultEchoServer>\r\n"+ | ||
591 | "<DefaultEchoPort>{4}</DefaultEchoPort>\r\n"+ | ||
592 | "<DefaultWellKnownIP>{5}</DefaultWellKnownIP>\r\n"+ | ||
593 | "<DefaultTimeout>{6}</DefaultTimeout>\r\n"+ | ||
594 | "<UrlResetPassword>{7}</UrlResetPassword>\r\n"+ | ||
595 | "<UrlPrivacyNotice>{8}</UrlPrivacyNotice>\r\n"+ | ||
596 | "<UrlEulaNotice/>\r\n"+ | ||
597 | "<App.NoBottomLogo>false</App.NoBottomLogo>\r\n"+ | ||
598 | "</VCConfiguration>", | ||
599 | m_freeSwitchRealm, m_freeSwitchSIPProxy, m_freeSwitchAttemptUseSTUN, | ||
600 | m_freeSwitchEchoServer, m_freeSwitchEchoPort, | ||
601 | m_freeSwitchDefaultWellKnownIP, m_freeSwitchDefaultTimeout, | ||
602 | m_freeSwitchUrlResetPassword, ""); | ||
603 | |||
604 | response["int_response_code"] = 200; | ||
605 | |||
606 | //m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler return {0}",response["str_response_string"]); | ||
607 | return response; | ||
608 | } | ||
609 | |||
610 | public Hashtable FreeSwitchSLVoiceBuddyHTTPHandler(Hashtable request) | ||
611 | { | ||
612 | m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceBuddyHTTPHandler called"); | ||
613 | |||
614 | Hashtable response = new Hashtable(); | ||
615 | response["int_response_code"] = 200; | ||
616 | response["str_response_string"] = string.Empty; | ||
617 | response["content-type"] = "text/xml"; | ||
618 | |||
619 | Hashtable requestBody = ParseRequestBody((string)request["body"]); | ||
620 | |||
621 | if (!requestBody.ContainsKey("auth_token")) | ||
622 | return response; | ||
623 | |||
624 | string auth_token = (string)requestBody["auth_token"]; | ||
625 | //string[] auth_tokenvals = auth_token.Split(':'); | ||
626 | //string username = auth_tokenvals[0]; | ||
627 | int strcount = 0; | ||
628 | |||
629 | string[] ids = new string[strcount]; | ||
630 | |||
631 | int iter = -1; | ||
632 | lock (m_UUIDName) | ||
633 | { | ||
634 | strcount = m_UUIDName.Count; | ||
635 | ids = new string[strcount]; | ||
636 | foreach (string s in m_UUIDName.Keys) | ||
637 | { | ||
638 | iter++; | ||
639 | ids[iter] = s; | ||
640 | } | ||
641 | } | ||
642 | StringBuilder resp = new StringBuilder(); | ||
643 | resp.Append("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><response xmlns=\"http://www.vivox.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"/xsd/buddy_list.xsd\">"); | ||
644 | |||
645 | resp.Append(string.Format(@"<level0> | ||
646 | <status>OK</status> | ||
647 | <cookie_name>lib_session</cookie_name> | ||
648 | <cookie>{0}</cookie> | ||
649 | <auth_token>{0}</auth_token> | ||
650 | <body> | ||
651 | <buddies>",auth_token)); | ||
652 | /* | ||
653 | <cookie_name>lib_session</cookie_name> | ||
654 | <cookie>{0}:{1}:9303959503950::</cookie> | ||
655 | <auth_token>{0}:{1}:9303959503950::</auth_token> | ||
656 | */ | ||
657 | for (int i=0;i<ids.Length;i++) | ||
658 | { | ||
659 | DateTime currenttime = DateTime.Now; | ||
660 | string dt = currenttime.ToString("yyyy-MM-dd HH:mm:ss.0zz"); | ||
661 | resp.Append( | ||
662 | string.Format(@"<level3> | ||
663 | <bdy_id>{1}</bdy_id> | ||
664 | <bdy_data></bdy_data> | ||
665 | <bdy_uri>sip:{0}@{2}</bdy_uri> | ||
666 | <bdy_nickname>{0}</bdy_nickname> | ||
667 | <bdy_username>{0}</bdy_username> | ||
668 | <bdy_domain>{2}</bdy_domain> | ||
669 | <bdy_status>A</bdy_status> | ||
670 | <modified_ts>{3}</modified_ts> | ||
671 | <b2g_group_id></b2g_group_id> | ||
672 | </level3>", ids[i], i ,m_freeSwitchRealm, dt)); | ||
673 | } | ||
674 | |||
675 | resp.Append("</buddies><groups></groups></body></level0></response>"); | ||
676 | |||
677 | response["str_response_string"] = resp.ToString(); | ||
678 | // Regex normalizeEndLines = new Regex(@"(\r\n|\n)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.Multiline); | ||
679 | // | ||
680 | // m_log.DebugFormat( | ||
681 | // "[FREESWITCH]: FreeSwitchSLVoiceBuddyHTTPHandler() response {0}", | ||
682 | // normalizeEndLines.Replace((string)response["str_response_string"],"")); | ||
683 | |||
684 | return response; | ||
685 | } | ||
686 | |||
687 | public Hashtable FreeSwitchSLVoiceWatcherHTTPHandler(Hashtable request) | ||
688 | { | ||
689 | m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceWatcherHTTPHandler called"); | ||
690 | |||
691 | Hashtable response = new Hashtable(); | ||
692 | response["int_response_code"] = 200; | ||
693 | response["content-type"] = "text/xml"; | ||
694 | |||
695 | Hashtable requestBody = ParseRequestBody((string)request["body"]); | ||
696 | |||
697 | string auth_token = (string)requestBody["auth_token"]; | ||
698 | //string[] auth_tokenvals = auth_token.Split(':'); | ||
699 | //string username = auth_tokenvals[0]; | ||
700 | |||
701 | StringBuilder resp = new StringBuilder(); | ||
702 | resp.Append("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><response xmlns=\"http://www.vivox.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"/xsd/buddy_list.xsd\">"); | ||
703 | |||
704 | // FIXME: This is enough of a response to stop viewer 2 complaining about a login failure and get voice to work. If we don't | ||
705 | // give an OK response, then viewer 2 engages in an continuous viv_signin.php, viv_buddy.php, viv_watcher.php loop | ||
706 | // Viewer 1 appeared happy to ignore the lack of reply and still works with this reply. | ||
707 | // | ||
708 | // However, really we need to fill in whatever watcher data should be here (whatever that is). | ||
709 | resp.Append(string.Format(@"<level0> | ||
710 | <status>OK</status> | ||
711 | <cookie_name>lib_session</cookie_name> | ||
712 | <cookie>{0}</cookie> | ||
713 | <auth_token>{0}</auth_token> | ||
714 | <body/></level0></response>", auth_token)); | ||
715 | |||
716 | response["str_response_string"] = resp.ToString(); | ||
717 | |||
718 | // Regex normalizeEndLines = new Regex(@"(\r\n|\n)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.Multiline); | ||
719 | // | ||
720 | // m_log.DebugFormat( | ||
721 | // "[FREESWITCH]: FreeSwitchSLVoiceWatcherHTTPHandler() response {0}", | ||
722 | // normalizeEndLines.Replace((string)response["str_response_string"],"")); | ||
723 | |||
724 | return response; | ||
725 | } | ||
726 | |||
727 | public Hashtable FreeSwitchSLVoiceSigninHTTPHandler(Hashtable request) | ||
728 | { | ||
729 | m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceSigninHTTPHandler called"); | ||
730 | // string requestbody = (string)request["body"]; | ||
731 | // string uri = (string)request["uri"]; | ||
732 | // string contenttype = (string)request["content-type"]; | ||
733 | |||
734 | Hashtable requestBody = ParseRequestBody((string)request["body"]); | ||
735 | |||
736 | //string pwd = (string) requestBody["pwd"]; | ||
737 | string userid = (string) requestBody["userid"]; | ||
738 | |||
739 | string avatarName = string.Empty; | ||
740 | int pos = -1; | ||
741 | lock (m_UUIDName) | ||
742 | { | ||
743 | if (m_UUIDName.ContainsKey(userid)) | ||
744 | { | ||
745 | avatarName = m_UUIDName[userid]; | ||
746 | foreach (string s in m_UUIDName.Keys) | ||
747 | { | ||
748 | pos++; | ||
749 | if (s == userid) | ||
750 | break; | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | |||
755 | //m_log.DebugFormat("[FreeSwitchVoice]: AUTH, URI: {0}, Content-Type:{1}, Body{2}", uri, contenttype, | ||
756 | // requestbody); | ||
757 | Hashtable response = new Hashtable(); | ||
758 | response["str_response_string"] = string.Format(@"<response xsi:schemaLocation=""/xsd/signin.xsd""> | ||
759 | <level0> | ||
760 | <status>OK</status> | ||
761 | <body> | ||
762 | <code>200</code> | ||
763 | <cookie_name>lib_session</cookie_name> | ||
764 | <cookie>{0}:{1}:9303959503950::</cookie> | ||
765 | <auth_token>{0}:{1}:9303959503950::</auth_token> | ||
766 | <primary>1</primary> | ||
767 | <account_id>{1}</account_id> | ||
768 | <displayname>{2}</displayname> | ||
769 | <msg>auth successful</msg> | ||
770 | </body> | ||
771 | </level0> | ||
772 | </response>", userid, pos, avatarName); | ||
773 | |||
774 | response["int_response_code"] = 200; | ||
775 | |||
776 | // m_log.DebugFormat("[FreeSwitchVoice]: Sending FreeSwitchSLVoiceSigninHTTPHandler response"); | ||
777 | |||
778 | return response; | ||
779 | } | ||
780 | |||
781 | public Hashtable ParseRequestBody(string body) | ||
782 | { | ||
783 | Hashtable bodyParams = new Hashtable(); | ||
784 | // split string | ||
785 | string [] nvps = body.Split(new Char [] {'&'}); | ||
786 | |||
787 | foreach (string s in nvps) | ||
788 | { | ||
789 | if (s.Trim() != "") | ||
790 | { | ||
791 | string [] nvp = s.Split(new Char [] {'='}); | ||
792 | bodyParams.Add(HttpUtility.UrlDecode(nvp[0]), HttpUtility.UrlDecode(nvp[1])); | ||
793 | } | ||
794 | } | ||
795 | |||
796 | return bodyParams; | ||
797 | } | ||
798 | |||
799 | private string ChannelUri(Scene scene, LandData land) | ||
800 | { | ||
801 | string channelUri = null; | ||
802 | |||
803 | string landUUID; | ||
804 | string landName; | ||
805 | |||
806 | // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same | ||
807 | // as the directory ID. Otherwise, it reflects the parcel's ID. | ||
808 | |||
809 | lock (m_ParcelAddress) | ||
810 | { | ||
811 | if (m_ParcelAddress.ContainsKey(land.GlobalID.ToString())) | ||
812 | { | ||
813 | m_log.DebugFormat("[FreeSwitchVoice]: parcel id {0}: using sip address {1}", | ||
814 | land.GlobalID, m_ParcelAddress[land.GlobalID.ToString()]); | ||
815 | return m_ParcelAddress[land.GlobalID.ToString()]; | ||
816 | } | ||
817 | } | ||
818 | |||
819 | if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0) | ||
820 | { | ||
821 | landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name); | ||
822 | landUUID = land.GlobalID.ToString(); | ||
823 | m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}", | ||
824 | landName, land.LocalID, landUUID); | ||
825 | } | ||
826 | else | ||
827 | { | ||
828 | landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName); | ||
829 | landUUID = scene.RegionInfo.RegionID.ToString(); | ||
830 | m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}", | ||
831 | landName, land.LocalID, landUUID); | ||
832 | } | ||
833 | |||
834 | // slvoice handles the sip address differently if it begins with confctl, hiding it from the user in the friends list. however it also disables | ||
835 | // the personal speech indicators as well unless some siren14-3d codec magic happens. we dont have siren143d so we'll settle for the personal speech indicator. | ||
836 | channelUri = String.Format("sip:conf-{0}@{1}", "x" + Convert.ToBase64String(Encoding.ASCII.GetBytes(landUUID)), m_freeSwitchRealm); | ||
837 | |||
838 | lock (m_ParcelAddress) | ||
839 | { | ||
840 | if (!m_ParcelAddress.ContainsKey(land.GlobalID.ToString())) | ||
841 | { | ||
842 | m_ParcelAddress.Add(land.GlobalID.ToString(),channelUri); | ||
843 | } | ||
844 | } | ||
845 | |||
846 | return channelUri; | ||
847 | } | ||
848 | |||
849 | private static bool CustomCertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error) | ||
850 | { | ||
851 | return true; | ||
852 | } | ||
853 | |||
854 | public Hashtable FreeSwitchConfigHTTPHandler(Hashtable request) | ||
855 | { | ||
856 | Hashtable response = new Hashtable(); | ||
857 | response["str_response_string"] = string.Empty; | ||
858 | response["content_type"] = "text/plain"; | ||
859 | response["keepalive"] = false; | ||
860 | response["int_response_code"] = 500; | ||
861 | |||
862 | Hashtable requestBody = ParseRequestBody((string)request["body"]); | ||
863 | |||
864 | string section = (string) requestBody["section"]; | ||
865 | |||
866 | if (section == "directory") | ||
867 | { | ||
868 | string eventCallingFunction = (string)requestBody["Event-Calling-Function"]; | ||
869 | m_log.DebugFormat( | ||
870 | "[FreeSwitchVoice]: Received request for config section directory, event calling function '{0}'", | ||
871 | eventCallingFunction); | ||
872 | |||
873 | response = m_FreeswitchService.HandleDirectoryRequest(requestBody); | ||
874 | } | ||
875 | else if (section == "dialplan") | ||
876 | { | ||
877 | m_log.DebugFormat("[FreeSwitchVoice]: Received request for config section dialplan"); | ||
878 | |||
879 | response = m_FreeswitchService.HandleDialplanRequest(requestBody); | ||
880 | } | ||
881 | else | ||
882 | m_log.WarnFormat("[FreeSwitchVoice]: Unknown section {0} was requested from config.", section); | ||
883 | |||
884 | return response; | ||
885 | } | ||
886 | } | ||
887 | |||
888 | public class MonoCert : ICertificatePolicy | ||
889 | { | ||
890 | #region ICertificatePolicy Members | ||
891 | |||
892 | public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) | ||
893 | { | ||
894 | return true; | ||
895 | } | ||
896 | |||
897 | #endregion | ||
898 | } | ||
899 | } | ||