FreeSwitchVoiceModule.cs 40 KB

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