opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 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 | } |