FreeSwitchVoiceModule.cs 40 KB

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