FreeSwitchVoiceModule.cs 39 KB

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