FreeswitchService.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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. response["content_type"] = "text/xml";
  106. response["keepalive"] = false;
  107. response["int_response_code"] = 200;
  108. response["str_response_string"] = "";
  109. } else {
  110. m_log.DebugFormat("[FreeSwitchDirectory] HandleDirectoryRequest called with {0}",request.ToString());
  111. // information in the request we might be interested in
  112. // Request 1 sip_auth for users account
  113. //Event-Calling-Function=sofia_reg_parse_auth
  114. //Event-Calling-Line-Number=1494
  115. //action=sip_auth
  116. //sip_user_agent=Vivox-SDK-2.1.3010.6151-Mac%20(Feb-11-2009/16%3A42%3A41)
  117. //sip_auth_username=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
  118. //sip_auth_realm=9.20.151.43
  119. //sip_contact_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D (==)
  120. //sip_contact_host=192.168.0.3 // this shouldnt really be a local IP, investigate STUN servers
  121. //sip_to_user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
  122. //sip_to_host=9.20.151.43
  123. //sip_auth_method=REGISTER
  124. //user=xhZuXKmRpECyr2AARJYyGgg%3D%3D
  125. //domain=9.20.151.43
  126. //ip=9.167.220.137 // this is the correct IP rather than sip_contact_host above when through a vpn or NAT setup
  127. foreach (DictionaryEntry item in request)
  128. {
  129. m_log.InfoFormat("[FreeSwitchDirectory] requestBody item {0} {1}", item.Key, item.Value);
  130. }
  131. string eventCallingFunction = (string) request["Event-Calling-Function"];
  132. if (eventCallingFunction == null)
  133. {
  134. eventCallingFunction = "sofia_reg_parse_auth";
  135. }
  136. if (eventCallingFunction.Length == 0)
  137. {
  138. eventCallingFunction = "sofia_reg_parse_auth";
  139. }
  140. if (eventCallingFunction == "sofia_reg_parse_auth")
  141. {
  142. string sipAuthMethod = (string)request["sip_auth_method"];
  143. if (sipAuthMethod == "REGISTER")
  144. {
  145. response = HandleRegister(m_freeSwitchContext, m_freeSwitchRealm, request);
  146. }
  147. else if (sipAuthMethod == "INVITE")
  148. {
  149. response = HandleInvite(m_freeSwitchContext, m_freeSwitchRealm, request);
  150. }
  151. else
  152. {
  153. m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown sip_auth_method {0}",sipAuthMethod);
  154. response["int_response_code"] = 404;
  155. response["content_type"] = "text/xml";
  156. response["str_response_string"] = "";
  157. }
  158. }
  159. else if (eventCallingFunction == "switch_xml_locate_user")
  160. {
  161. response = HandleLocateUser(m_freeSwitchRealm, request);
  162. }
  163. else if (eventCallingFunction == "user_data_function") // gets called when an avatar to avatar call is made
  164. {
  165. response = HandleLocateUser(m_freeSwitchRealm, request);
  166. }
  167. else if (eventCallingFunction == "user_outgoing_channel")
  168. {
  169. response = HandleRegister(m_freeSwitchContext, m_freeSwitchRealm, request);
  170. }
  171. else if (eventCallingFunction == "config_sofia") // happens once on freeswitch startup
  172. {
  173. response = HandleConfigSofia(m_freeSwitchContext, m_freeSwitchRealm, request);
  174. }
  175. else if (eventCallingFunction == "switch_load_network_lists")
  176. {
  177. //response = HandleLoadNetworkLists(request);
  178. response["int_response_code"] = 404;
  179. response["keepalive"] = false;
  180. response["content_type"] = "text/xml";
  181. response["str_response_string"] = "";
  182. }
  183. else
  184. {
  185. m_log.ErrorFormat("[FreeSwitchVoice] HandleDirectoryRequest unknown Event-Calling-Function {0}",eventCallingFunction);
  186. response["int_response_code"] = 404;
  187. response["keepalive"] = false;
  188. response["content_type"] = "text/xml";
  189. response["str_response_string"] = "";
  190. }
  191. }
  192. return response;
  193. }
  194. private Hashtable HandleRegister(string Context, string Realm, Hashtable request)
  195. {
  196. m_log.Info("[FreeSwitchDirectory] HandleRegister called");
  197. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  198. string password = "1234";
  199. string domain = (string) request["domain"];
  200. string user = (string) request["user"];
  201. Hashtable response = new Hashtable();
  202. response["content_type"] = "text/xml";
  203. response["keepalive"] = false;
  204. response["int_response_code"] = 200;
  205. response["str_response_string"] = String.Format(
  206. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  207. "<document type=\"freeswitch/xml\">\r\n" +
  208. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  209. "<domain name=\"{0}\">\r\n" +
  210. "<user id=\"{1}\">\r\n" +
  211. "<params>\r\n" +
  212. "<param name=\"password\" value=\"{2}\" />\r\n" +
  213. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  214. "</params>\r\n" +
  215. "<variables>\r\n" +
  216. "<variable name=\"user_context\" value=\"{3}\" />\r\n" +
  217. "<variable name=\"presence_id\" value=\"{1}@{0}\"/>"+
  218. "</variables>\r\n" +
  219. "</user>\r\n" +
  220. "</domain>\r\n" +
  221. "</section>\r\n" +
  222. "</document>\r\n",
  223. domain , user, password, Context);
  224. return response;
  225. }
  226. private Hashtable HandleInvite(string Context, string Realm, Hashtable request)
  227. {
  228. m_log.Info("[FreeSwitchDirectory] HandleInvite called");
  229. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  230. string password = "1234";
  231. string domain = (string) request["domain"];
  232. string user = (string) request["user"];
  233. string sipRequestUser = (string) request["sip_request_user"];
  234. Hashtable response = new Hashtable();
  235. response["content_type"] = "text/xml";
  236. response["keepalive"] = false;
  237. response["int_response_code"] = 200;
  238. response["str_response_string"] = String.Format(
  239. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  240. "<document type=\"freeswitch/xml\">\r\n" +
  241. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  242. "<domain name=\"{0}\">\r\n" +
  243. "<user id=\"{1}\">\r\n" +
  244. "<params>\r\n" +
  245. "<param name=\"password\" value=\"{2}\" />\r\n" +
  246. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${1}@${{dialed_domain}}}}${{sofia_contact(${1}@${{dialed_domain}})}}\"/>\r\n" +
  247. "</params>\r\n" +
  248. "<variables>\r\n" +
  249. "<variable name=\"user_context\" value=\"{4}\" />\r\n" +
  250. "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
  251. "</variables>\r\n" +
  252. "</user>\r\n" +
  253. "<user id=\"{3}\">\r\n" +
  254. "<params>\r\n" +
  255. "<param name=\"password\" value=\"{2}\" />\r\n" +
  256. "<param name=\"dial-string\" value=\"{{sip_contact_user={1}}}{{presence_id=${3}@${{dialed_domain}}}}${{sofia_contact(${3}@${{dialed_domain}})}}\"/>\r\n" +
  257. "</params>\r\n" +
  258. "<variables>\r\n" +
  259. "<variable name=\"user_context\" value=\"{4}\" />\r\n" +
  260. "<variable name=\"presence_id\" value=\"{3}@$${{domain}}\"/>"+
  261. "</variables>\r\n" +
  262. "</user>\r\n" +
  263. "</domain>\r\n" +
  264. "</section>\r\n" +
  265. "</document>\r\n",
  266. domain , user, password,sipRequestUser, Context);
  267. return response;
  268. }
  269. private Hashtable HandleLocateUser(String Realm, Hashtable request)
  270. {
  271. m_log.Info("[FreeSwitchDirectory] HandleLocateUser called");
  272. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  273. string domain = (string) request["domain"];
  274. string user = (string) request["user"];
  275. Hashtable response = new Hashtable();
  276. response["content_type"] = "text/xml";
  277. response["keepalive"] = false;
  278. response["int_response_code"] = 200;
  279. response["str_response_string"] = String.Format(
  280. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  281. "<document type=\"freeswitch/xml\">\r\n" +
  282. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  283. "<domain name=\"{0}\">\r\n" +
  284. "<params>\r\n" +
  285. "<param name=\"dial-string\" value=\"{{sip_contact_user=${{dialed_user}}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  286. "</params>\r\n" +
  287. "<user id=\"{1}\">\r\n" +
  288. "<variables>\r\n"+
  289. "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
  290. "<variable name=\"presence_id\" value=\"{1}@$${{domain}}\"/>"+
  291. "</variables>\r\n"+
  292. "</user>\r\n" +
  293. "</domain>\r\n" +
  294. "</section>\r\n" +
  295. "</document>\r\n",
  296. domain , user);
  297. return response;
  298. }
  299. private Hashtable HandleConfigSofia(string Context, string Realm, Hashtable request)
  300. {
  301. m_log.Info("[FreeSwitchDirectory] HandleConfigSofia called");
  302. // TODO the password we return needs to match that sent in the request, this is hard coded for now
  303. string domain = (string) request["domain"];
  304. Hashtable response = new Hashtable();
  305. response["content_type"] = "text/xml";
  306. response["keepalive"] = false;
  307. response["int_response_code"] = 200;
  308. response["str_response_string"] = String.Format(
  309. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
  310. "<document type=\"freeswitch/xml\">\r\n" +
  311. "<section name=\"directory\" description=\"User Directory\">\r\n" +
  312. "<domain name=\"{0}\">\r\n" +
  313. "<params>\r\n" +
  314. "<param name=\"dial-string\" value=\"{{sip_contact_user=${{dialed_user}}}}{{presence_id=${{dialed_user}}@${{dialed_domain}}}}${{sofia_contact(${{dialed_user}}@${{dialed_domain}})}}\"/>\r\n" +
  315. "</params>\r\n" +
  316. "<groups name=\"default\">\r\n"+
  317. "<users>\r\n"+
  318. "<user id=\"$${{default_provider}}\">\r\n"+
  319. "<gateways>\r\n"+
  320. "<gateway name=\"$${{default_provider}}\">\r\n"+
  321. "<param name=\"username\" value=\"$${{default_provider_username}}\"/>\r\n"+
  322. "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
  323. "<param name=\"from-user\" value=\"$${{default_provider_username}}\"/>\r\n"+
  324. "<param name=\"from-domain\" value=\"$${{default_provider_from_domain}}\"/>\r\n"+
  325. "<param name=\"expire-seconds\" value=\"600\"/>\r\n"+
  326. "<param name=\"register\" value=\"$${{default_provider_register}}\"/>\r\n"+
  327. "<param name=\"retry-seconds\" value=\"30\"/>\r\n"+
  328. "<param name=\"extension\" value=\"$${{default_provider_contact}}\"/>\r\n"+
  329. "<param name=\"contact-params\" value=\"domain_name=$${{domain}}\"/>\r\n"+
  330. "<param name=\"context\" value=\"{1}\"/>\r\n"+
  331. "</gateway>\r\n"+
  332. "</gateways>\r\n"+
  333. "<params>\r\n"+
  334. "<param name=\"password\" value=\"$${{default_provider_password}}\"/>\r\n"+
  335. "</params>\r\n"+
  336. "</user>\r\n"+
  337. "</users>"+
  338. "</groups>\r\n" +
  339. "<variables>\r\n"+
  340. "<variable name=\"default_gateway\" value=\"$${{default_provider}}\"/>\r\n"+
  341. "</variables>\r\n"+
  342. "</domain>\r\n" +
  343. "</section>\r\n" +
  344. "</document>\r\n",
  345. domain, Context);
  346. return response;
  347. }
  348. public string GetJsonConfig()
  349. {
  350. OSDMap map = new OSDMap(9);
  351. map.Add("Realm", m_freeSwitchRealm);
  352. map.Add("SIPProxy", m_freeSwitchSIPProxy);
  353. map.Add("AttemptUseSTUN", m_freeSwitchAttemptUseSTUN);
  354. map.Add("EchoServer", m_freeSwitchEchoServer);
  355. map.Add("EchoPort", m_freeSwitchEchoPort);
  356. map.Add("DefaultWellKnownIP", m_freeSwitchDefaultWellKnownIP);
  357. map.Add("DefaultTimeout", m_freeSwitchDefaultTimeout);
  358. map.Add("Context", m_freeSwitchContext);
  359. map.Add("APIPrefix", m_freeSwitchAPIPrefix);
  360. return OSDParser.SerializeJsonString(map);
  361. }
  362. }
  363. }