FreeswitchService.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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.Text;
  29. using System.Reflection;
  30. using Nini.Config;
  31. using log4net;
  32. using OpenSim.Framework;
  33. using OpenSim.Data;
  34. using OpenSim.Services.Interfaces;
  35. using OpenMetaverse;
  36. using OpenMetaverse.StructuredData;
  37. using System.Collections;
  38. namespace OpenSim.Services.FreeswitchService
  39. {
  40. public class FreeswitchService : FreeswitchServiceBase, IFreeswitchService
  41. {
  42. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  43. public FreeswitchService(IConfigSource config) : base(config)
  44. {
  45. // Perform initilialization here
  46. }
  47. public Hashtable HandleDialplanRequest(Hashtable request)
  48. {
  49. m_log.DebugFormat("[FreeSwitchVoice]: HandleDialplanRequest called with {0}",request.ToString());
  50. Hashtable response = new Hashtable();
  51. // foreach (DictionaryEntry item in request)
  52. // {
  53. //// m_log.InfoFormat("[FreeSwitchDirectory]: requestBody item {0} {1}",item.Key, item.Value);
  54. // }
  55. string requestcontext = (string) request["Hunt-Context"];
  56. response["content_type"] = "text/xml";
  57. response["keepalive"] = false;
  58. response["int_response_code"] = 200;
  59. if (m_freeSwitchContext != String.Empty && m_freeSwitchContext != requestcontext)
  60. {
  61. m_log.Debug("[FreeSwitchDirectory]: returning empty as it's for another context");
  62. response["str_response_string"] = "";
  63. }
  64. else
  65. {
  66. response["str_response_string"] = String.Format(@"<?xml version=""1.0"" encoding=""utf-8""?>
  67. <document type=""freeswitch/xml"">
  68. <section name=""dialplan"">
  69. <context name=""{0}"">" +
  70. /* <!-- dial via SIP uri -->
  71. <extension name=""sip_uri"">
  72. <condition field=""destination_number"" expression=""^sip:(.*)$"">
  73. <action application=""bridge"" data=""sofia/${use_profile}/$1""/>
  74. <!--<action application=""bridge"" data=""$1""/>-->
  75. </condition>
  76. </extension>*/
  77. @"<extension name=""opensim_conferences"">
  78. <condition field=""destination_number"" expression=""^confctl-(.*)$"">
  79. <action application=""answer""/>
  80. <action application=""conference"" data=""$1-{1}@{0}""/>
  81. </condition>
  82. </extension>
  83. <extension name=""opensim_conf"">
  84. <condition field=""destination_number"" expression=""^conf-(.*)$"">
  85. <action application=""answer""/>
  86. <action application=""conference"" data=""$1-{1}@{0}""/>
  87. </condition>
  88. </extension>
  89. <extension name=""avatar"">
  90. <condition field=""destination_number"" expression=""^(x.*)$"">
  91. <action application=""bridge"" data=""user/$1""/>
  92. </condition>
  93. </extension>
  94. </context>
  95. </section>
  96. </document>", m_freeSwitchContext, m_freeSwitchRealm);
  97. }
  98. return response;
  99. }
  100. public Hashtable HandleDirectoryRequest(Hashtable request)
  101. {
  102. Hashtable response = new Hashtable();
  103. string domain = (string) request["domain"];
  104. if (domain != m_freeSwitchRealm)
  105. {
  106. response["content_type"] = "text/xml";
  107. response["keepalive"] = false;
  108. response["int_response_code"] = 200;
  109. response["str_response_string"] = "";
  110. }
  111. else
  112. {
  113. // m_log.DebugFormat("[FreeSwitchDirectory]: HandleDirectoryRequest called with {0}",request.ToString());
  114. // information in the request we might be interested in
  115. // Request 1 sip_auth for users account
  116. //Event-Calling-Function=sofia_reg_parse_auth
  117. //Event-Calling-Line-Number=1494
  118. //action=sip_auth
  119. //sip_user_agent=Vivox-SDK-2.1.3010.6151-Mac%20(Feb-11-2009/16%3A42%3A41)
  120. //sip_auth_username=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
  121. //sip_auth_realm=9.20.151.43
  122. //sip_contact_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
  123. //sip_contact_host=192.168.0.3 // this shouldnt really be a local IP, investigate STUN servers
  124. //sip_to_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
  125. //sip_to_host=9.20.151.43
  126. //sip_auth_method=REGISTER
  127. //user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
  128. //domain=9.20.151.43
  129. //ip=9.167.220.137 // this is the correct IP rather than sip_contact_host above when through a vpn or NAT setup
  130. // foreach (DictionaryEntry item in request)
  131. // m_log.DebugFormat("[FreeSwitchDirectory]: requestBody item {0} {1}", item.Key, item.Value);
  132. string eventCallingFunction = (string) request["Event-Calling-Function"];
  133. if (eventCallingFunction == null)
  134. {
  135. eventCallingFunction = "sofia_reg_parse_auth";
  136. }
  137. if (eventCallingFunction.Length == 0)
  138. {
  139. eventCallingFunction = "sofia_reg_parse_auth";
  140. }
  141. if (eventCallingFunction == "sofia_reg_parse_auth")
  142. {
  143. string sipAuthMethod = (string)request["sip_auth_method"];
  144. if (sipAuthMethod == "REGISTER")
  145. {
  146. response = HandleRegister(m_freeSwitchContext, m_freeSwitchRealm, request);
  147. }
  148. else if (sipAuthMethod == "INVITE")
  149. {
  150. response = HandleInvite(m_freeSwitchContext, m_freeSwitchRealm, request);
  151. }
  152. else
  153. {
  154. m_log.ErrorFormat("[FreeSwitchVoice]: HandleDirectoryRequest unknown sip_auth_method {0}",sipAuthMethod);
  155. response["int_response_code"] = 404;
  156. response["content_type"] = "text/xml";
  157. response["str_response_string"] = "";
  158. }
  159. }
  160. else if (eventCallingFunction == "switch_xml_locate_user")
  161. {
  162. response = HandleLocateUser(m_freeSwitchRealm, request);
  163. }
  164. else if (eventCallingFunction == "user_data_function") // gets called when an avatar to avatar call is made
  165. {
  166. response = HandleLocateUser(m_freeSwitchRealm, request);
  167. }
  168. else if (eventCallingFunction == "user_outgoing_channel")
  169. {
  170. response = HandleRegister(m_freeSwitchContext, m_freeSwitchRealm, request);
  171. }
  172. else if (eventCallingFunction == "config_sofia") // happens once on freeswitch startup
  173. {
  174. response = HandleConfigSofia(m_freeSwitchContext, m_freeSwitchRealm, request);
  175. }
  176. else if (eventCallingFunction == "switch_load_network_lists")
  177. {
  178. //response = HandleLoadNetworkLists(request);
  179. response["int_response_code"] = 404;
  180. response["keepalive"] = false;
  181. response["content_type"] = "text/xml";
  182. response["str_response_string"] = "";
  183. }
  184. else
  185. {
  186. m_log.ErrorFormat("[FreeSwitchVoice]: HandleDirectoryRequest unknown Event-Calling-Function {0}",eventCallingFunction);
  187. response["int_response_code"] = 404;
  188. response["keepalive"] = false;
  189. response["content_type"] = "text/xml";
  190. response["str_response_string"] = "";
  191. }
  192. }
  193. return response;
  194. }
  195. private Hashtable HandleRegister(string Context, string Realm, Hashtable request)
  196. {
  197. m_log.Info("[FreeSwitchDirectory]: HandleRegister called");
  198. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  199. string password = "1234";
  200. string domain = (string) request["domain"];
  201. string user = (string) request["user"];
  202. Hashtable response = new Hashtable();
  203. response["content_type"] = "text/xml";
  204. response["keepalive"] = false;
  205. response["int_response_code"] = 200;
  206. response["str_response_string"] = String.Format(
  207. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  208. "<document type=\"freeswitch/xml\">\r\n" +
  209. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  210. "<domain name=\"{0}\">\r\n" +
  211. "<user id=\"{1}\">\r\n" +
  212. "<params>\r\n" +
  213. "<param name=\"password\" value=\"{2}\" />\r\n" +
  214. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  215. "</params>\r\n" +
  216. "<variables>\r\n" +
  217. "<variable name=\"user_context\" value=\"{3}\" />\r\n" +
  218. "<variable name=\"presence_id\" value=\"{1}@{0}\"/>"+
  219. "</variables>\r\n" +
  220. "</user>\r\n" +
  221. "</domain>\r\n" +
  222. "</section>\r\n" +
  223. "</document>\r\n",
  224. domain , user, password, Context);
  225. return response;
  226. }
  227. private Hashtable HandleInvite(string Context, string Realm, Hashtable request)
  228. {
  229. m_log.Info("[FreeSwitchDirectory]: HandleInvite called");
  230. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  231. string password = "1234";
  232. string domain = (string) request["domain"];
  233. string user = (string) request["user"];
  234. string sipRequestUser = (string) request["sip_request_user"];
  235. Hashtable response = new Hashtable();
  236. response["content_type"] = "text/xml";
  237. response["keepalive"] = false;
  238. response["int_response_code"] = 200;
  239. response["str_response_string"] = String.Format(
  240. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  241. "<document type=\"freeswitch/xml\">\r\n" +
  242. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  243. "<domain name=\"{0}\">\r\n" +
  244. "<user id=\"{1}\">\r\n" +
  245. "<params>\r\n" +
  246. "<param name=\"password\" value=\"{2}\" />\r\n" +
  247. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${1}@${{dialed_domain}}}}${{sofia_contact(${1}@${{dialed_domain}})}}\"/>\r\n" +
  248. "</params>\r\n" +
  249. "<variables>\r\n" +
  250. "<variable name=\"user_context\" value=\"{4}\" />\r\n" +
  251. "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
  252. "</variables>\r\n" +
  253. "</user>\r\n" +
  254. "<user id=\"{3}\">\r\n" +
  255. "<params>\r\n" +
  256. "<param name=\"password\" value=\"{2}\" />\r\n" +
  257. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${3}@${{dialed_domain}}}}${{sofia_contact(${3}@${{dialed_domain}})}}\"/>\r\n" +
  258. "</params>\r\n" +
  259. "<variables>\r\n" +
  260. "<variable name=\"user_context\" value=\"{4}\" />\r\n" +
  261. "<variable name=\"presence_id\" value=\"{3}@$${{domain}}\"/>"+
  262. "</variables>\r\n" +
  263. "</user>\r\n" +
  264. "</domain>\r\n" +
  265. "</section>\r\n" +
  266. "</document>\r\n",
  267. domain , user, password,sipRequestUser, Context);
  268. return response;
  269. }
  270. private Hashtable HandleLocateUser(String Realm, Hashtable request)
  271. {
  272. m_log.Info("[FreeSwitchDirectory]: HandleLocateUser called");
  273. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  274. string domain = (string) request["domain"];
  275. string user = (string) request["user"];
  276. Hashtable response = new Hashtable();
  277. response["content_type"] = "text/xml";
  278. response["keepalive"] = false;
  279. response["int_response_code"] = 200;
  280. response["str_response_string"] = String.Format(
  281. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  282. "<document type=\"freeswitch/xml\">\r\n" +
  283. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  284. "<domain name=\"{0}\">\r\n" +
  285. "<params>\r\n" +
  286. "<param name=\"dial-string\" value=\"{{sip_contact_user=${{dialed_user}}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  287. "</params>\r\n" +
  288. "<user id=\"{1}\">\r\n" +
  289. "<variables>\r\n"+
  290. "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
  291. "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
  292. "</variables>\r\n"+
  293. "</user>\r\n" +
  294. "</domain>\r\n" +
  295. "</section>\r\n" +
  296. "</document>\r\n",
  297. domain , user);
  298. return response;
  299. }
  300. private Hashtable HandleConfigSofia(string Context, string Realm, Hashtable request)
  301. {
  302. m_log.Info("[FreeSwitchDirectory]: HandleConfigSofia called.");
  303. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  304. string domain = (string) request["domain"];
  305. Hashtable response = new Hashtable();
  306. response["content_type"] = "text/xml";
  307. response["keepalive"] = false;
  308. response["int_response_code"] = 200;
  309. response["str_response_string"] = String.Format(
  310. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  311. "<document type=\"freeswitch/xml\">\r\n" +
  312. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  313. "<domain name=\"{0}\">\r\n" +
  314. "<params>\r\n" +
  315. "<param name=\"dial-string\" value=\"{{sip_contact_user=${{dialed_user}}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  316. "</params>\r\n" +
  317. "<groups name=\"default\">\r\n"+
  318. "<users>\r\n"+
  319. "<user id=\"$${{default_provider}}\">\r\n"+
  320. "<gateways>\r\n"+
  321. "<gateway name=\"$${{default_provider}}\">\r\n"+
  322. "<param name=\"username\" value=\"$${{default_provider_username}}\"/>\r\n"+
  323. "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
  324. "<param name=\"from-user\" value=\"$${{default_provider_username}}\"/>\r\n"+
  325. "<param name=\"from-domain\" value=\"$${{default_provider_from_domain}}\"/>\r\n"+
  326. "<param name=\"expire-seconds\" value=\"600\"/>\r\n"+
  327. "<param name=\"register\" value=\"$${{default_provider_register}}\"/>\r\n"+
  328. "<param name=\"retry-seconds\" value=\"30\"/>\r\n"+
  329. "<param name=\"extension\" value=\"$${{default_provider_contact}}\"/>\r\n"+
  330. "<param name=\"contact-params\" value=\"domain_name=$${{domain}}\"/>\r\n"+
  331. "<param name=\"context\" value=\"{1}\"/>\r\n"+
  332. "</gateway>\r\n"+
  333. "</gateways>\r\n"+
  334. "<params>\r\n"+
  335. "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
  336. "</params>\r\n"+
  337. "</user>\r\n"+
  338. "</users>"+
  339. "</groups>\r\n" +
  340. "<variables>\r\n"+
  341. "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
  342. "</variables>\r\n"+
  343. "</domain>\r\n" +
  344. "</section>\r\n" +
  345. "</document>\r\n",
  346. domain, Context);
  347. return response;
  348. }
  349. public string GetJsonConfig()
  350. {
  351. OSDMap map = new OSDMap(9);
  352. map.Add("Realm", m_freeSwitchRealm);
  353. map.Add("SIPProxy", m_freeSwitchSIPProxy);
  354. map.Add("AttemptUseSTUN", m_freeSwitchAttemptUseSTUN);
  355. map.Add("EchoServer", m_freeSwitchEchoServer);
  356. map.Add("EchoPort", m_freeSwitchEchoPort);
  357. map.Add("DefaultWellKnownIP", m_freeSwitchDefaultWellKnownIP);
  358. map.Add("DefaultTimeout", m_freeSwitchDefaultTimeout);
  359. map.Add("Context", m_freeSwitchContext);
  360. map.Add("APIPrefix", m_freeSwitchAPIPrefix);
  361. return OSDParser.SerializeJsonString(map);
  362. }
  363. }
  364. }