PageRenderTime 74ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/sipsorcery-core/SIPSorcery.AppServer.DialPlan/DialPlanFacades/DialPlanScriptFacade.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 1951 lines | 1477 code | 176 blank | 298 comment | 219 complexity | b3bac3cb7511a95d249b088063d7731f MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. // ============================================================================
  2. // FileName: DialPlanScriptFacade.cs
  3. //
  4. // Description:
  5. // Dial plan script facade or helper methods for dial plan scripts.
  6. //
  7. // Author(s):
  8. // Aaron Clauson
  9. //
  10. // History:
  11. // 16 Sep 2008 Aaron Clauson Created (extracted from SIPDialPlan).
  12. //
  13. // License:
  14. // This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
  15. //
  16. // Copyright (c) 2010 Aaron Clauson (aaron@sipsorcery.com), SIP Sorcery Pty Ltd
  17. // All rights reserved.
  18. //
  19. // Redistribution and use in source and binary forms, with or without modification, are permitted provided that
  20. // the following conditions are met:
  21. //
  22. // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  23. // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  24. // disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd.
  25. // nor the names of its contributors may be used to endorse or promote products derived from this software without specific
  26. // prior written permission.
  27. //
  28. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  29. // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  30. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  31. // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  32. // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  33. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. // POSSIBILITY OF SUCH DAMAGE.
  35. // ============================================================================
  36. using System;
  37. using System.Collections;
  38. using System.Collections.Generic;
  39. using System.Collections.Specialized;
  40. using System.Data;
  41. using System.IO;
  42. using System.Linq;
  43. using System.Net;
  44. using System.Net.Sockets;
  45. using System.Security;
  46. using System.Security.Principal;
  47. using System.Text;
  48. using System.Text.RegularExpressions;
  49. using System.Threading;
  50. using System.Web;
  51. using SIPSorcery.Net;
  52. using SIPSorcery.Persistence;
  53. using SIPSorcery.SIP;
  54. using SIPSorcery.SIP.App;
  55. using SIPSorcery.Sys;
  56. using System.Transactions;
  57. using Heijden.DNS;
  58. using log4net;
  59. using agsXMPP;
  60. using agsXMPP.protocol;
  61. using agsXMPP.protocol.client;
  62. using GaDotNet.Common.Data;
  63. using GaDotNet.Common.Helpers;
  64. using GaDotNet.Common;
  65. #if UNITTEST
  66. using NUnit.Framework;
  67. #endif
  68. namespace SIPSorcery.AppServer.DialPlan
  69. {
  70. /// <summary>
  71. /// Helper functions for use in dial plan scripts.
  72. /// </summary>
  73. public class DialPlanScriptFacade
  74. {
  75. private const int DEFAULT_CREATECALL_RINGTIME = 60;
  76. private const int ENUM_LOOKUP_TIMEOUT = 5; // Default timeout in seconds for ENUM lookups.
  77. private const string DEFAULT_LOCAL_DOMAIN = "local";
  78. private const int MAX_BYTES_WEB_GET = 8192; // The maximum number of bytes that will be read from the response stream in the WebGet application.
  79. private const string EMAIL_FROM_ADDRESS = "dialplan@sipsorcery.com"; // The from address that will be set for emails sent from the dialplan.
  80. private const int ALLOWED_ADDRESSES_PER_EMAIL = 5; // The maximum number of addresses that can be used in an email.
  81. private const int ALLOWED_EMAILS_PER_EXECUTION = 3; // The maximum number of emails that can be sent pre dialplan execution.
  82. private const int MAX_EMAIL_SUBJECT_LENGTH = 256;
  83. private const int MAX_EMAIL_BODY_LENGTH = 2048;
  84. private const string USERDATA_DBTYPE_KEY = "UserDataDBType";
  85. private const string USERDATA_DBCONNSTR_KEY = "UserDataDBConnStr";
  86. private const int MAX_DATA_ENTRIES_PER_USER = 100;
  87. private const int MAX_CALLS_ALLOWED = 20; // The maximum number of outgoing call requests that will be allowed per dialplan execution.
  88. private const int MAX_CALLBACKS_ALLOWED = 3; // The maximum number of callback method calls that will be alowed per dialplan instance.
  89. private const int WEBGET_MAXIMUM_TIMEOUT = 300; // The maximum number of seconds a web get can be set to wait for a response.
  90. private const int DEFAULT_GOOGLEVOICE_PHONETYPE = 2;
  91. private const int GTALK_DEFAULT_CONNECTION_TIMEOUT = 5000;
  92. private const int GTALK_MAX_CONNECTION_TIMEOUT = 30000;
  93. private const string GOOGLE_ANALYTICS_KEY = "GoogleAnalyticsAccountCode"; // If this key does not exist in App.Config there's no point sending Google Analytics requests.
  94. private static int m_maxRingTime = SIPTimings.MAX_RING_TIME;
  95. private static readonly StorageTypes m_userDataDBType = StorageTypes.Unknown;
  96. private static readonly string m_userDataDBConnStr;
  97. private static bool m_sendAnalytics = true;
  98. private static ILog logger = AppState.logger;
  99. private SIPMonitorLogDelegate m_dialPlanLogDelegate;
  100. private SIPTransport m_sipTransport;
  101. private DialPlanExecutingScript m_executingScript;
  102. private List<SIPProvider> m_sipProviders;
  103. private DialogueBridgeCreatedDelegate CreateBridge_External;
  104. private GetCanonicalDomainDelegate m_getCanonicalDomainDelegate;
  105. private SIPRequest m_sipRequest; // This is a copy of the SIP request from m_clientTransaction.
  106. private ForkCall m_currentCall;
  107. private SIPEndPoint m_outboundProxySocket; // If this app forwards calls via an outbound proxy this value will be set.
  108. private List<string> m_customSIPHeaders = new List<string>(); // Allows a dialplan user to add or customise SIP headers for forwarded requests.
  109. private string m_customContent; // If set will be used by the Dial command as the INVITE body on forwarded requests.
  110. private string m_customContentType;
  111. private string m_customFromName;
  112. private string m_customFromUser;
  113. private string m_customFromHost;
  114. private SIPCallDirection m_callDirection;
  115. private DialStringParser m_dialStringParser;
  116. private bool m_clientCallCancelled;
  117. private ManualResetEvent m_waitForCallCompleted;
  118. // Deprecated, use LastFailureReason.
  119. public string LastFailureMessage
  120. {
  121. get { return LastFailureReason; }
  122. }
  123. // The error message from the first call leg on the final dial attempt used when the call fails to provide a reason.
  124. public string LastFailureReason
  125. {
  126. get { return m_executingScript.LastFailureReason; }
  127. set { m_executingScript.LastFailureReason = value; }
  128. }
  129. public SIPResponseStatusCodesEnum LastFailureStatus
  130. {
  131. get { return m_executingScript.LastFailureStatus; }
  132. set { m_executingScript.LastFailureStatus = value; }
  133. }
  134. private int m_emailCount = 0; // Keeps count of the emails that have been sent during this dialpan execution.
  135. private int m_callInitialisationCount = 0; // Keeps count of the number of call initialisations that have been attempted by a dialplan execution.
  136. private int m_callbackRequests = 0; // Keeps count of the number of call back requests that have been attempted by a dialplan execution.
  137. private IDbConnection m_userDataDBConnection;
  138. private IDbTransaction m_userDataDBTransaction;
  139. private Dictionary<string, XmppClientConnection> m_gtalkConnections = new Dictionary<string, XmppClientConnection>();
  140. private SIPAssetPersistor<SIPAccount> m_sipAccountPersistor;
  141. private SIPAssetPersistor<SIPDialPlan> m_sipDialPlanPersistor;
  142. private SIPAssetPersistor<SIPDialogueAsset> m_sipDialoguePersistor;
  143. private SIPAssetGetListDelegate<SIPRegistrarBinding> GetSIPAccountBindings_External; // This event must be wired up to an external function in order to be able to lookup bindings that have been registered for a SIP account.
  144. private ISIPCallManager m_callManager;
  145. private bool m_autoCleanup = true; // Set to false if the Ruby dialplan wants to take care of handling the cleanup from a rescue or ensure block.
  146. private bool m_hasBeenCleanedUp;
  147. private DialPlanContext m_dialPlanContext;
  148. public DialPlanContext DialPlanContext
  149. {
  150. get { return m_dialPlanContext; }
  151. }
  152. private string m_username;
  153. public string Username
  154. {
  155. get { return m_username; }
  156. }
  157. private string m_adminMemberId;
  158. public bool Out
  159. {
  160. get { return m_callDirection == SIPCallDirection.Out; }
  161. }
  162. public bool In
  163. {
  164. get { return m_callDirection == SIPCallDirection.In; }
  165. }
  166. public List<SIPTransaction> LastDialled;
  167. public bool Trace
  168. {
  169. get { return m_dialPlanContext.SendTrace; }
  170. set { m_dialPlanContext.SendTrace = value; }
  171. }
  172. public static IPAddress PublicIPAddress; // If the app server is behind a NAT then it can set this address to be used in mangled SDP.
  173. static DialPlanScriptFacade()
  174. {
  175. try
  176. {
  177. m_userDataDBType = (AppState.GetConfigSetting(USERDATA_DBTYPE_KEY) != null) ? StorageTypesConverter.GetStorageType(AppState.GetConfigSetting(USERDATA_DBTYPE_KEY)) : StorageTypes.Unknown;
  178. m_userDataDBConnStr = AppState.GetConfigSetting(USERDATA_DBCONNSTR_KEY);
  179. m_sendAnalytics = !AppState.GetConfigSetting(GOOGLE_ANALYTICS_KEY).IsNullOrBlank();
  180. }
  181. catch (Exception excp)
  182. {
  183. logger.Error("Exception DialPlanScriptHelper (static ctor). " + excp.Message);
  184. }
  185. }
  186. public DialPlanScriptFacade(
  187. SIPTransport sipTransport,
  188. DialPlanExecutingScript executingScript,
  189. SIPMonitorLogDelegate logDelegate,
  190. DialogueBridgeCreatedDelegate createBridge,
  191. SIPRequest sipRequest,
  192. SIPCallDirection callDirection,
  193. DialPlanContext dialPlanContext,
  194. GetCanonicalDomainDelegate getCanonicalDomain,
  195. ISIPCallManager callManager,
  196. SIPAssetPersistor<SIPAccount> sipAccountPersistor,
  197. SIPAssetPersistor<SIPDialPlan> sipDialPlanPersistor,
  198. SIPAssetPersistor<SIPDialogueAsset> sipDialoguePersistor,
  199. SIPAssetGetListDelegate<SIPRegistrarBinding> getSIPAccountBindings,
  200. SIPEndPoint outboundProxySocket
  201. )
  202. {
  203. m_sipTransport = sipTransport;
  204. m_executingScript = executingScript;
  205. m_dialPlanLogDelegate = logDelegate;
  206. CreateBridge_External = createBridge;
  207. m_sipRequest = sipRequest;
  208. m_callDirection = callDirection;
  209. m_dialPlanContext = dialPlanContext;
  210. m_getCanonicalDomainDelegate = getCanonicalDomain;
  211. m_callManager = callManager;
  212. m_sipAccountPersistor = sipAccountPersistor;
  213. m_sipDialPlanPersistor = sipDialPlanPersistor;
  214. m_sipDialoguePersistor = sipDialoguePersistor;
  215. GetSIPAccountBindings_External = getSIPAccountBindings;
  216. m_outboundProxySocket = outboundProxySocket;
  217. m_executingScript.Cleanup = CleanupDialPlanScript;
  218. if (m_dialPlanContext != null)
  219. {
  220. m_username = dialPlanContext.Owner;
  221. m_adminMemberId = dialPlanContext.AdminMemberId;
  222. m_sipProviders = dialPlanContext.SIPProviders;
  223. m_dialPlanContext.TraceLog.AppendLine("DialPlan=> Dialplan trace commenced at " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff") + ".");
  224. m_dialPlanContext.CallCancelledByClient += ClientCallTerminated;
  225. SIPAssetGetDelegate<SIPAccount> getSIPAccount = null;
  226. if (m_sipAccountPersistor != null)
  227. {
  228. getSIPAccount = m_sipAccountPersistor.Get;
  229. }
  230. m_dialStringParser = new DialStringParser(m_sipTransport, m_dialPlanContext.Owner, m_dialPlanContext.SIPAccount, m_sipProviders, getSIPAccount, GetSIPAccountBindings_External, m_getCanonicalDomainDelegate, logDelegate, m_dialPlanContext.SIPDialPlan.DialPlanName);
  231. }
  232. }
  233. public void TurnOffAutoCleanup()
  234. {
  235. m_autoCleanup = false;
  236. }
  237. public void Cleanup()
  238. {
  239. m_autoCleanup = true;
  240. CleanupDialPlanScript();
  241. }
  242. /// <summary>
  243. /// A function that gets attached to the executing thread object and that will be called when the dialplan script is complete and
  244. /// immediately prior to a possible thread abortion.
  245. /// </summary>
  246. private void CleanupDialPlanScript()
  247. {
  248. try
  249. {
  250. if (m_hasBeenCleanedUp)
  251. {
  252. Log("Dialplan cleanup has already been run, ignoring subsequent cleanup call for " + Username + ".");
  253. }
  254. else if (m_autoCleanup)
  255. {
  256. m_hasBeenCleanedUp = true;
  257. Log("Dialplan cleanup for " + Username + ".");
  258. if (m_userDataDBConnection != null)
  259. {
  260. Log("Closing user database connection for " + Username + ".");
  261. m_userDataDBConnection.Close();
  262. }
  263. if (m_gtalkConnections.Count > 0)
  264. {
  265. foreach (KeyValuePair<string, XmppClientConnection> connection in m_gtalkConnections)
  266. {
  267. Log("Closing gTalk connection for " + connection.Key + ".");
  268. connection.Value.Close();
  269. }
  270. }
  271. }
  272. }
  273. catch (Exception excp)
  274. {
  275. logger.Error("Exception DialPlanScriptFacade Cleanup. " + excp.Message);
  276. Log("Exception DialPlanScriptFacade Cleanup. " + excp.Message);
  277. }
  278. }
  279. public string WhoAmI()
  280. {
  281. return WindowsIdentity.GetCurrent().Name;
  282. }
  283. /// <remarks>
  284. /// This method will be called on the thread that owns the dialplan context object so it's critical that Thread abort
  285. /// is not called in it or from it.
  286. /// </remarks>
  287. /// <param name="cancelCause"></param>
  288. private void ClientCallTerminated(CallCancelCause cancelCause)
  289. {
  290. try
  291. {
  292. m_clientCallCancelled = true;
  293. Log("Dialplan call was terminated by client side due to " + cancelCause + ".");
  294. if (m_currentCall != null)
  295. {
  296. m_currentCall.CancelNotRequiredCallLegs(cancelCause);
  297. }
  298. if (m_waitForCallCompleted != null)
  299. {
  300. m_waitForCallCompleted.Set();
  301. }
  302. else
  303. {
  304. m_executingScript.StopExecution();
  305. }
  306. }
  307. catch (Exception excp)
  308. {
  309. logger.Error("Exception ClientCallTerminated. " + excp.Message);
  310. }
  311. }
  312. /// <summary>
  313. /// Attempts to dial a series of forwards and bridges the first one that connects with the client call.
  314. /// </summary>
  315. /// <param name="data">The dial string containing the list of call legs to attempt to forward the call to.</param>
  316. /// <returns>A code that best represents how the dial command ended.</returns>
  317. public DialPlanAppResult Dial(string data)
  318. {
  319. return Dial(data, m_maxRingTime);
  320. }
  321. /// <summary>
  322. /// Attempts to dial a series of forwards and bridges the first one that connects with the client call.
  323. /// </summary>
  324. /// <param name="data">The dial string containing the list of call legs to attempt to forward the call to.</param>
  325. /// <param name="ringTimeout">The period in seconds to perservere with the dial command attempt without a final response before giving up.</param>
  326. /// <returns>A code that best represents how the dial command ended.</returns>
  327. public DialPlanAppResult Dial(string data, int ringTimeout)
  328. {
  329. return Dial(data, ringTimeout, 0);
  330. }
  331. /// <summary>
  332. /// Attempts to dial a series of forwards and bridges the first one that connects with the client call.
  333. /// </summary>
  334. /// <param name="data">The dial string containing the list of call legs to attempt to forward the call to.</param>
  335. /// /// <param name="answeredCallLimit">If greater than 0 this specifies the period in seconds an answered call will be hungup after.</param>
  336. /// <param name="ringTimeout">The period in seconds to perservere with the dial command attempt without a final response before giving up.</param>
  337. /// <returns>A code that best represents how the dial command ended.</returns>
  338. public DialPlanAppResult Dial(string data, int ringTimeout, int answeredCallLimit)
  339. {
  340. return Dial(data, ringTimeout, answeredCallLimit, m_sipRequest, null);
  341. }
  342. //public DialPlanAppResult Dial(string data, CRMHeaders contact)
  343. //{
  344. // return Dial(data, m_maxRingTime, 0, m_sipRequest, null);
  345. //}
  346. public DialPlanAppResult Dial(string data, int ringTimeout, int answeredCallLimit, CRMHeaders contact)
  347. {
  348. return Dial(data, ringTimeout, answeredCallLimit, m_sipRequest, contact);
  349. }
  350. /// <summary>
  351. ///
  352. /// </summary>
  353. /// <param name="data"></param>
  354. /// <param name="ringTimeout"></param>
  355. /// <param name="answeredCallLimit"></param>
  356. /// <param name="redirectMode"></param>
  357. /// <param name="clientTransaction"></param>
  358. /// <param name="keepScriptAlive">If false will let the dial plan engine know the script has finished and the call is answered. For applications
  359. /// like Callback which need to have two calls answered it will be true.</param>
  360. /// <returns></returns>
  361. private DialPlanAppResult Dial(
  362. string data,
  363. int ringTimeout,
  364. int answeredCallLimit,
  365. SIPRequest clientRequest,
  366. CRMHeaders contact)
  367. {
  368. if (m_dialPlanContext.IsAnswered)
  369. {
  370. Log("The call has already been answered the Dial command was not processed.");
  371. return DialPlanAppResult.AlreadyAnswered;
  372. }
  373. else if (data.IsNullOrBlank())
  374. {
  375. Log("The dial string cannot be empty when calling Dial.");
  376. return DialPlanAppResult.Error;
  377. }
  378. else if (m_callInitialisationCount > MAX_CALLS_ALLOWED)
  379. {
  380. Log("You have exceeded the maximum allowed calls for a dialplan execution.");
  381. return DialPlanAppResult.Error;
  382. }
  383. else
  384. {
  385. Log("Commencing Dial with: " + data + ".");
  386. DialPlanAppResult result = DialPlanAppResult.Unknown;
  387. m_waitForCallCompleted = new ManualResetEvent(false);
  388. SIPResponseStatusCodesEnum answeredStatus = SIPResponseStatusCodesEnum.None;
  389. string answeredReason = null;
  390. string answeredContentType = null;
  391. string answeredBody = null;
  392. SIPDialogue answeredDialogue = null;
  393. SIPDialogueTransferModesEnum uasTransferMode = SIPDialogueTransferModesEnum.Default;
  394. int numberLegs = 0;
  395. m_currentCall = new ForkCall(m_sipTransport, FireProxyLogEvent, m_callManager.QueueNewCall, m_dialStringParser, Username, m_adminMemberId, m_outboundProxySocket, m_callManager, out LastDialled);
  396. m_currentCall.CallProgress += m_dialPlanContext.CallProgress;
  397. m_currentCall.CallFailed += (status, reason, headers) =>
  398. {
  399. LastFailureStatus = status;
  400. LastFailureReason = reason;
  401. result = DialPlanAppResult.Failed;
  402. m_waitForCallCompleted.Set();
  403. };
  404. m_currentCall.CallAnswered += (status, reason, toTag, headers, contentType, body, dialogue, transferMode) =>
  405. {
  406. answeredStatus = status;
  407. answeredReason = reason;
  408. answeredContentType = contentType;
  409. answeredBody = body;
  410. answeredDialogue = dialogue;
  411. uasTransferMode = transferMode;
  412. result = DialPlanAppResult.Answered;
  413. m_waitForCallCompleted.Set();
  414. };
  415. try
  416. {
  417. Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString(
  418. DialPlanContextsEnum.Script,
  419. clientRequest,
  420. data,
  421. m_customSIPHeaders,
  422. m_customContentType,
  423. m_customContent,
  424. m_dialPlanContext.CallersNetworkId,
  425. m_customFromName,
  426. m_customFromUser,
  427. m_customFromHost,
  428. contact);
  429. List<SIPCallDescriptor>[] callListArray = callsQueue.ToArray();
  430. callsQueue.ToList().ForEach((list) => numberLegs += list.Count);
  431. if (numberLegs == 0)
  432. {
  433. Log("The dial string did not result in any call legs.");
  434. return DialPlanAppResult.Error;
  435. }
  436. else
  437. {
  438. m_callInitialisationCount += numberLegs;
  439. if (m_callInitialisationCount > MAX_CALLS_ALLOWED)
  440. {
  441. Log("You have exceeded the maximum allowed calls for a dialplan execution.");
  442. return DialPlanAppResult.Error;
  443. }
  444. }
  445. m_currentCall.Start(callsQueue);
  446. // Wait for an answer.
  447. if (ringTimeout <= 0 || ringTimeout * 1000 > m_maxRingTime)
  448. {
  449. ringTimeout = m_maxRingTime;
  450. }
  451. else
  452. {
  453. ringTimeout = ringTimeout * 1000;
  454. }
  455. ExtendScriptTimeout(ringTimeout/1000 + DEFAULT_CREATECALL_RINGTIME);
  456. DateTime startTime = DateTime.Now;
  457. if (m_waitForCallCompleted.WaitOne(ringTimeout, false))
  458. {
  459. if (!m_clientCallCancelled)
  460. {
  461. if (result == DialPlanAppResult.Answered)
  462. {
  463. // The call limit duration is only used if there hasn't already been a per leg duration set on the call.
  464. if (answeredCallLimit > 0 && answeredDialogue.CallDurationLimit == 0)
  465. {
  466. answeredDialogue.CallDurationLimit = answeredCallLimit;
  467. }
  468. m_dialPlanContext.CallAnswered(answeredStatus, answeredReason, null, null, answeredContentType, answeredBody, answeredDialogue, uasTransferMode);
  469. // Dial plan script stops once there is an answered call to bridge to or the client call is cancelled.
  470. Log("Dial command was successfully answered in " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("0.00") + "s.");
  471. // Do some Google Analytics call tracking.
  472. if (answeredDialogue.RemoteUserField != null)
  473. {
  474. SendGoogleAnalyticsEvent("Call", "Answered", answeredDialogue.RemoteUserField.URI.Host, 1);
  475. }
  476. m_executingScript.StopExecution();
  477. }
  478. }
  479. }
  480. else
  481. {
  482. if (!m_clientCallCancelled)
  483. {
  484. // Call timed out.
  485. m_currentCall.CancelNotRequiredCallLegs(CallCancelCause.TimedOut);
  486. result = DialPlanAppResult.TimedOut;
  487. }
  488. }
  489. if (m_clientCallCancelled)
  490. {
  491. Log("Dial command was halted by cancellation of client call after " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("#.00") + "s.");
  492. m_executingScript.StopExecution();
  493. }
  494. return result;
  495. }
  496. catch (ThreadAbortException)
  497. {
  498. return DialPlanAppResult.Unknown;
  499. }
  500. catch (Exception excp)
  501. {
  502. logger.Error("Exception DialPlanScriptHelper Dial. " + excp.Message);
  503. return DialPlanAppResult.Error;
  504. }
  505. }
  506. }
  507. /// <summary>
  508. /// Logs a message with the proxy. Typically this records the message in the database and also prints it out
  509. /// on the proxy monitor telnet console.
  510. /// </summary>
  511. /// <param name="message"></param>
  512. public void Log(string message)
  513. {
  514. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, message, Username));
  515. }
  516. /// <summary>
  517. /// See Callback method below.
  518. /// </summary>
  519. public void Callback(string dest1, string dest2)
  520. {
  521. Callback(dest1, dest2, 0);
  522. }
  523. public void Callback(string dest1, string dest2, int delaySeconds)
  524. {
  525. m_callbackRequests++;
  526. if (m_callbackRequests > MAX_CALLBACKS_ALLOWED)
  527. {
  528. Log("You have exceeded the maximum allowed callbacks for a dialplan execution.");
  529. }
  530. else
  531. {
  532. CallbackApp callbackApp = new CallbackApp(m_sipTransport, m_callManager, m_dialStringParser, FireProxyLogEvent, m_username, m_adminMemberId, m_outboundProxySocket);
  533. ThreadPool.QueueUserWorkItem(delegate { callbackApp.Callback(dest1, dest2, delaySeconds); });
  534. }
  535. }
  536. public void Respond(int statusCode, string reason)
  537. {
  538. Respond(statusCode, reason, null);
  539. }
  540. /// <summary>
  541. /// Sends a SIP response to the client call. If a final response is sent then the client call will hang up.
  542. /// </summary>
  543. /// <param name="statusCode"></param>
  544. /// <param name="reason"></param>
  545. /// <param name="customerHeaders">Optional list of pipe '|' delimited custom headers.</param>
  546. public void Respond(int statusCode, string reason, string customHeaders)
  547. {
  548. try
  549. {
  550. if (m_dialPlanContext.IsAnswered)
  551. {
  552. Log("The call has already been answered the Respond command was not processed.");
  553. }
  554. else if (statusCode >= 200 && statusCode < 300)
  555. {
  556. Log("Respond cannot be used for 2xx responses.");
  557. }
  558. else
  559. {
  560. string[] customHeadersList = null;
  561. if (!customHeaders.IsNullOrBlank())
  562. {
  563. customHeadersList = customHeaders.Split('|');
  564. }
  565. SIPResponseStatusCodesEnum status = SIPResponseStatusCodes.GetStatusTypeForCode(statusCode);
  566. if (statusCode >= 300)
  567. {
  568. m_dialPlanContext.CallFailed(status, reason, customHeadersList);
  569. m_executingScript.StopExecution();
  570. }
  571. else if (statusCode < 200)
  572. {
  573. m_dialPlanContext.CallProgress(status, reason, customHeadersList, null, null, null);
  574. }
  575. }
  576. }
  577. catch (ThreadAbortException) { }
  578. catch (Exception excp)
  579. {
  580. Log("Exception Respond. " + excp.Message);
  581. }
  582. }
  583. /// <summary>
  584. /// Trys an ENUM lookup on the specified number.
  585. /// </summary>
  586. /// <param name="number"></param>
  587. /// <returns></returns>
  588. public string ENUMLookup(string number)
  589. {
  590. try
  591. {
  592. string e164DottedNumber = FormatForENUMLookup(number);
  593. if (number == null)
  594. {
  595. logger.Warn("The ENUMLookup number format was not recognised needs to be number.domain.");
  596. return null;
  597. }
  598. else
  599. {
  600. logger.Debug("Starting ENUM lookup for " + e164DottedNumber + ".");
  601. DNSResponse enumResponse = DNSManager.Lookup(e164DottedNumber, DNSQType.NAPTR, ENUM_LOOKUP_TIMEOUT, null, false, false);
  602. if (enumResponse.Answers != null && enumResponse.RecordNAPTR != null)
  603. {
  604. foreach (RecordNAPTR naptr in enumResponse.RecordNAPTR)
  605. {
  606. logger.Debug("NAPTR result=" + naptr.ToString() + " (ttl=" + naptr.RR.TTL + ").");
  607. }
  608. string enumURI = ApplyENUMRules(number, enumResponse.RecordNAPTR);
  609. if (enumURI != null)
  610. {
  611. logger.Debug("ENUM URI found for " + number + "=" + enumURI);
  612. return enumURI;
  613. }
  614. else
  615. {
  616. return null;
  617. }
  618. }
  619. else
  620. {
  621. logger.Debug("No NAPTR records found for " + number + ".");
  622. return null;
  623. }
  624. }
  625. }
  626. catch (Exception excp)
  627. {
  628. logger.Error("Exception ENUMLookup. " + excp.Message);
  629. return null;
  630. }
  631. }
  632. /// <summary>
  633. /// Checks whether the dialplan owner's default SIP account is online (has any current bindings).
  634. /// </summary>
  635. public bool IsAvailable()
  636. {
  637. return IsAvailable(Username, DEFAULT_LOCAL_DOMAIN);
  638. }
  639. /// <summary>
  640. /// Checks whether SIP account belonging to the default server domain is online (has any current bindings).
  641. /// </summary>
  642. public bool IsAvailable(string username)
  643. {
  644. return IsAvailable(username, DEFAULT_LOCAL_DOMAIN);
  645. }
  646. /// <summary>
  647. /// Checks whether the specified SIP account is online (has any current bindings).
  648. /// </summary>
  649. public bool IsAvailable(string username, string domain)
  650. {
  651. try
  652. {
  653. string canonicalDomain = m_getCanonicalDomainDelegate(domain, false);
  654. if (canonicalDomain.IsNullOrBlank())
  655. {
  656. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "The " + domain + " is not a serviced domain.", Username));
  657. return false;
  658. }
  659. else
  660. {
  661. SIPAccount sipAccount = m_sipAccountPersistor.Get(s => s.SIPUsername == username && s.SIPDomain == canonicalDomain);
  662. if (sipAccount == null)
  663. {
  664. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No sip account exists in IsAvailable for " + username + "@" + canonicalDomain + ".", Username));
  665. return false;
  666. }
  667. else
  668. {
  669. SIPRegistrarBinding[] bindings = GetBindings(username, canonicalDomain);
  670. return (bindings != null && bindings.Length > 0);
  671. }
  672. }
  673. }
  674. catch (Exception excp)
  675. {
  676. Log("Exception IsAvailable. " + excp.Message);
  677. return false;
  678. }
  679. }
  680. /// <summary>
  681. /// Checks whether the specified SIP account and default domain belongs to the dialplan owner.
  682. /// </summary>
  683. public bool IsMine(string username)
  684. {
  685. return IsAvailable(username, DEFAULT_LOCAL_DOMAIN);
  686. }
  687. /// <summary>
  688. /// Checks whether the specified SIP account belongs to the dialplan owner.
  689. /// </summary>
  690. public bool IsMine(string username, string domain)
  691. {
  692. try
  693. {
  694. string canonicalDomain = m_getCanonicalDomainDelegate(domain, false);
  695. if (canonicalDomain.IsNullOrBlank())
  696. {
  697. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "The " + domain + " is not a serviced domain.", Username));
  698. return false;
  699. }
  700. else
  701. {
  702. SIPAccount sipAccount = m_sipAccountPersistor.Get(s => s.SIPUsername == username && s.SIPDomain == canonicalDomain);
  703. if (sipAccount == null)
  704. {
  705. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No sip account exists in IsMine for " + username + "@" + canonicalDomain + ".", Username));
  706. return false;
  707. }
  708. else
  709. {
  710. return (sipAccount.Owner == Username);
  711. }
  712. }
  713. }
  714. catch (Exception excp)
  715. {
  716. Log("Exception IsMine. " + excp.Message);
  717. return false;
  718. }
  719. }
  720. /// <summary>
  721. /// Used to check for the existence of a SIP account in the default domain.
  722. /// </summary>
  723. /// <param name="username">The SIP account username to check for.</param>
  724. /// <returns>Returns true if the SIP account exists, false otherwise.</returns>
  725. public bool DoesSIPAccountExist(string username)
  726. {
  727. return DoesSIPAccountExist(username, DEFAULT_LOCAL_DOMAIN);
  728. }
  729. /// <summary>
  730. /// Used to check for the existence of a SIP account in the specified domain.
  731. /// </summary>
  732. /// <param name="username">The SIP account username to check for.</param>
  733. /// <param name="domain">The SIP domain to check for the account in.</param>
  734. /// <returns>Returns true if the SIP account exists, false otherwise.</returns>
  735. public bool DoesSIPAccountExist(string username, string domain)
  736. {
  737. string canonicalDomain = m_getCanonicalDomainDelegate(domain, false);
  738. if (!canonicalDomain.IsNullOrBlank())
  739. {
  740. return (m_sipAccountPersistor.Count(s => s.SIPUsername == username && s.SIPDomain == canonicalDomain) > 0);
  741. }
  742. else
  743. {
  744. return false;
  745. }
  746. }
  747. /// <summary>
  748. /// Gets an array of the registered contacts for the dialplan owner's SIP account.
  749. /// </summary>
  750. public SIPRegistrarBinding[] GetBindings()
  751. {
  752. string canonicalDomain = m_getCanonicalDomainDelegate(DEFAULT_LOCAL_DOMAIN, true);
  753. return GetBindings(Username, canonicalDomain);
  754. }
  755. /// <summary>
  756. /// Gets an array of the registered contacts for the specified SIP account. Only the owner of the SIP account
  757. /// will be allowed to retrieve a list of bindings for it.
  758. /// </summary>
  759. public SIPRegistrarBinding[] GetBindings(string username, string domain)
  760. {
  761. try
  762. {
  763. string canonicalDomain = m_getCanonicalDomainDelegate(domain, false);
  764. if (canonicalDomain.IsNullOrBlank())
  765. {
  766. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "The " + domain + " is not a serviced domain.", Username));
  767. return null;
  768. }
  769. else
  770. {
  771. SIPAccount sipAccount = m_sipAccountPersistor.Get(s => s.SIPUsername == username && s.SIPDomain == domain);
  772. if (sipAccount == null)
  773. {
  774. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No sip account exists in GetBindings for " + username + "@" + domain + ".", Username));
  775. return null;
  776. }
  777. else if (sipAccount.Owner != m_username)
  778. {
  779. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "You are not authorised to call GetBindings for " + username + "@" + domain + ".", Username));
  780. return null;
  781. }
  782. else
  783. {
  784. List<SIPRegistrarBinding> bindings = GetSIPAccountBindings_External(s => s.SIPAccountId == sipAccount.Id, null, 0, Int32.MaxValue);
  785. if (bindings != null)
  786. {
  787. return bindings.ToArray();
  788. }
  789. else
  790. {
  791. return null;
  792. }
  793. }
  794. }
  795. }
  796. catch (Exception excp)
  797. {
  798. Log("Exception GetBindings. " + excp);
  799. return null;
  800. }
  801. }
  802. /// <summary>
  803. /// Allows a hostname to be tested to determine if it is a sipsorcery serviced domain and if it is the canonical domain
  804. /// will be returned.
  805. /// </summary>
  806. /// <param name="host">The hostname to attempt to retrieve the canonical domain for.</param>
  807. /// <returns>If the host is a sipsorcery serviced domain the canonical domain for the host will be returned, otherwise null.</returns>
  808. public string GetCanonicalDomain(string host)
  809. {
  810. if (host.IsNullOrBlank())
  811. {
  812. return null;
  813. }
  814. else
  815. {
  816. return m_getCanonicalDomainDelegate(host, false);
  817. }
  818. }
  819. /// <summary>
  820. /// Adds a name value pair to the custom SIP headers list. The custom headers will be added to any forwarded call requests.
  821. /// </summary>
  822. /// <param name="headerName">The name of the SIP header to add.</param>
  823. /// <param name="headerValue">The value of the SIP header to add.</param>
  824. public void SetCustomSIPHeader(string headerName, string headerValue)
  825. {
  826. if (headerName.IsNullOrBlank())
  827. {
  828. Log("The name of the header to set was empty, the header was not added.");
  829. }
  830. else if (Regex.Match(headerName.Trim(), @"^(Via|To|From|Contact|CSeq|Call-ID|Max-Forwards|Content-Length)$", RegexOptions.IgnoreCase).Success)
  831. {
  832. Log("The name of the header to set is not permitted, the header was not added.");
  833. }
  834. else
  835. {
  836. string trimmedName = headerName.Trim();
  837. string trimmedValue = (headerValue != null) ? headerValue.Trim() : String.Empty;
  838. /*if (m_customSIPHeaders.Contains(trimmedName)) {
  839. m_customSIPHeaders[trimmedName] = trimmedValue;
  840. }
  841. else {
  842. m_customSIPHeaders.Add(trimmedName, trimmedValue);
  843. }*/
  844. m_customSIPHeaders.Add(trimmedName + ": " + trimmedValue);
  845. Log("Custom SIP header " + trimmedName + " successfully added to list.");
  846. }
  847. }
  848. /// <summary>
  849. /// If present removes a SIP header from the list of custom headers.
  850. /// </summary>
  851. /// <param name="headerName">The name of the SIP header to remove.</param>
  852. public void RemoveCustomSIPHeader(string headerName)
  853. {
  854. if (!headerName.IsNullOrBlank() && m_customSIPHeaders.Contains(headerName.Trim()))
  855. {
  856. m_customSIPHeaders.Remove(headerName.Trim());
  857. Log("Custom SIP header " + headerName.Trim() + " successfully removed.");
  858. }
  859. else
  860. {
  861. Log("Custom SIP header " + headerName.Trim() + " was not in the list.");
  862. }
  863. }
  864. /// <summary>
  865. /// Clears all the custom SIP header values from the list.
  866. /// </summary>
  867. public void ClearCustomSIPHeaders()
  868. {
  869. m_customSIPHeaders.Clear();
  870. }
  871. /// <summary>
  872. /// Dumps the currently stored custom SIP headers to the console or monitoring screen to allow
  873. /// users to troubleshoot.
  874. /// </summary>
  875. public void PrintCustomSIPHeaders()
  876. {
  877. Log("Custom SIP Header List:");
  878. foreach (string customHeader in m_customSIPHeaders)
  879. {
  880. Log(" " + customHeader);
  881. }
  882. }
  883. /// <summary>
  884. /// Sets the value of part or all of the From header that will be set on forwarded calls. Leaving a part of the
  885. /// header as null will result in the corresponding value from the originating request being used.
  886. /// </summary>
  887. /// <param name="fromName">The custom From header display name to set.</param>
  888. /// <param name="fromUser">The custom From header URI user value to set.</param>
  889. /// <param name="fromHost">The custom From header URI host value to set.</param>
  890. public void SetFromHeader(string fromName, string fromUser, string fromHost)
  891. {
  892. m_customFromName = fromName;
  893. m_customFromUser = fromUser;
  894. m_customFromHost = fromHost;
  895. }
  896. /// <summary>
  897. /// Reset the custom From header values so that the corresponding values from the originating request will
  898. /// be used.
  899. /// </summary>
  900. public void ClearFromHeader()
  901. {
  902. m_customFromName = null;
  903. m_customFromUser = null;
  904. m_customFromHost = null;
  905. }
  906. /// <summary>
  907. /// Sets the custom body that will override the incoming request body for forwarded INVITE requests.
  908. /// </summary>
  909. /// <param name="body">The custom body that will be sent in forwarded INVITE requests.</param>
  910. public void SetCustomContent(string content)
  911. {
  912. m_customContent = content;
  913. }
  914. /// <summary>
  915. /// Sets the custom body that will override the incoming request body for forwarded INVITE requests.
  916. /// </summary>
  917. /// <param name="body">The custom body that will be sent in forwarded INVITE requests.</param>
  918. public void SetCustomContent(string contentType, string content)
  919. {
  920. m_customContentType = contentType;
  921. m_customContent = content;
  922. }
  923. /// <summary>
  924. /// Clears the custom body so that the incoming request body will again be used on forwarded requests.
  925. /// </summary>
  926. public void ClearCustomBody()
  927. {
  928. m_customContentType = null;
  929. m_customContent = null;
  930. }
  931. public void GTalk(string username, string password, string sendToUser, string message)
  932. {
  933. GTalk(username, password, sendToUser, message, GTALK_DEFAULT_CONNECTION_TIMEOUT);
  934. }
  935. /// <summary>
  936. /// Attempts to send a gTalk IM to the specified account.
  937. /// </summary>
  938. public void GTalk(string username, string password, string sendToUser, string message, int connectionTimeout)
  939. {
  940. try
  941. {
  942. if (connectionTimeout > GTALK_MAX_CONNECTION_TIMEOUT)
  943. {
  944. connectionTimeout = GTALK_MAX_CONNECTION_TIMEOUT;
  945. }
  946. else if (connectionTimeout < GTALK_DEFAULT_CONNECTION_TIMEOUT)
  947. {
  948. connectionTimeout = GTALK_DEFAULT_CONNECTION_TIMEOUT;
  949. }
  950. XmppClientConnection xmppCon = null;
  951. if (m_gtalkConnections.ContainsKey(username))
  952. {
  953. xmppCon = m_gtalkConnections[username];
  954. Log("Using existing gTalk connection for " + username + "@gmail.com.");
  955. xmppCon.Send(new Message(new Jid(sendToUser + "@gmail.com"), MessageType.chat, message));
  956. }
  957. else
  958. {
  959. xmppCon = new XmppClientConnection();
  960. xmppCon.Password = password;
  961. xmppCon.Username = username;
  962. xmppCon.Server = "gmail.com";
  963. xmppCon.ConnectServer = "talk.google.com";
  964. xmppCon.AutoAgents = false;
  965. xmppCon.AutoPresence = false;
  966. xmppCon.AutoRoster = false;
  967. xmppCon.AutoResolveConnectServer = true;
  968. Log("Attempting to connect to gTalk for " + username + ".");
  969. ManualResetEvent waitForConnect = new ManualResetEvent(false);
  970. xmppCon.OnLogin += new ObjectHandler((sender) => waitForConnect.Set());
  971. xmppCon.Open();
  972. if (waitForConnect.WaitOne(connectionTimeout, false))
  973. {
  974. Log("Connected to gTalk for " + username + "@gmail.com.");
  975. if (!m_gtalkConnections.ContainsKey(username))
  976. {
  977. m_gtalkConnections.Add(username, xmppCon);
  978. }
  979. xmppCon.Send(new Message(new Jid(sendToUser + "@gmail.com"), MessageType.chat, message));
  980. }
  981. else
  982. {
  983. Log("Connection to gTalk for " + username + " timed out.");
  984. }
  985. }
  986. //xmppCon.Close();
  987. }
  988. catch (Exception excp)
  989. {
  990. logger.Error("Exception GTalk. " + excp.Message);
  991. Log("Exception GTalk. " + excp.Message);
  992. }
  993. }
  994. public void GoogleVoiceCall(string emailAddress, string password, string forwardingNumber, string destinationNumber)
  995. {
  996. GoogleVoiceCall(emailAddress, password, forwardingNumber, destinationNumber, null, DEFAULT_GOOGLEVOICE_PHONETYPE, 0);
  997. }
  998. public void GoogleVoiceCall(string emailAddress, string password, string forwardingNumber, string destinationNumber, string fromURIUserToMatch)
  999. {
  1000. GoogleVoiceCall(emailAddress, password, forwardingNumber, destinationNumber, fromURIUserToMatch, DEFAULT_GOOGLEVOICE_PHONETYPE, 0);
  1001. }
  1002. public void GoogleVoiceCall(string emailAddress, string password, string forwardingNumber, string destinationNumber, string fromURIUserToMatch, int phoneType)
  1003. {
  1004. GoogleVoiceCall(emailAddress, password, forwardingNumber, destinationNumber, fromURIUserToMatch, phoneType, 0);
  1005. }
  1006. public void GoogleVoiceCall(string emailAddress, string password, string forwardingNumber, string destinationNumber, string fromURIUserToMatch, int phoneType, int waitForCallbackTimeout)
  1007. {
  1008. try
  1009. {
  1010. DateTime startTime = DateTime.Now;
  1011. ExtendScriptTimeout(DEFAULT_CREATECALL_RINGTIME);
  1012. GoogleVoiceCall googleCall = new GoogleVoiceCall(m_sipTransport, m_callManager, m_dialPlanLogDelegate, m_username, m_adminMemberId, m_outboundProxySocket);
  1013. m_dialPlanContext.CallCancelledByClient += googleCall.ClientCallTerminated;
  1014. googleCall.CallProgress += m_dialPlanContext.CallProgress;
  1015. string content = m_sipRequest.Body;
  1016. IPAddress requestSDPAddress = (PublicIPAddress != null) ? PublicIPAddress : SIPPacketMangler.GetRequestIPAddress(m_sipRequest);
  1017. IPEndPoint sdpEndPoint = (content.IsNullOrBlank()) ? null : SDP.GetSDPRTPEndPoint(content);
  1018. if (sdpEndPoint != null)
  1019. {
  1020. if (!IPSocket.IsPrivateAddress(sdpEndPoint.Address.ToString()))
  1021. {
  1022. Log("SDP on GoogleVoiceCall call had public IP not mangled, RTP socket " + sdpEndPoint.ToString() + ".");
  1023. }
  1024. else
  1025. {
  1026. bool wasSDPMangled = false;
  1027. if (requestSDPAddress != null)
  1028. {
  1029. if (sdpEndPoint != null)
  1030. {
  1031. content = SIPPacketMangler.MangleSDP(content, requestSDPAddress.ToString(), out wasSDPMangled);
  1032. }
  1033. }
  1034. if (wasSDPMangled)
  1035. {
  1036. Log("SDP on GoogleVoiceCall call had RTP socket mangled from " + sdpEndPoint.ToString() + " to " + requestSDPAddress.ToString() + ":" + sdpEndPoint.Port + ".");
  1037. }
  1038. else if (sdpEndPoint != null)
  1039. {
  1040. Log("SDP on GoogleVoiceCall could not be mangled, using original RTP socket of " + sdpEndPoint.ToString() + ".");
  1041. }
  1042. }
  1043. }
  1044. else
  1045. {
  1046. Log("SDP RTP socket on GoogleVoiceCall call could not be determined.");
  1047. }
  1048. SIPDialogue answeredDialogue = googleCall.InitiateCall(emailAddress, password, forwardingNumber, destinationNumber, fromURIUserToMatch, phoneType, waitForCallbackTimeout, m_sipRequest.Header.ContentType, content);
  1049. if (answeredDialogue != null)
  1050. {
  1051. m_dialPlanContext.CallAnswered(SIPResponseStatusCodesEnum.Ok, null, null, null, answeredDialogue.ContentType, answeredDialogue.RemoteSDP, answeredDialogue, SIPDialogueTransferModesEnum.Default);
  1052. // Dial plan script stops once there is an answered call to bridge to or the client call is cancelled.
  1053. Log("Google Voice Call was successfully answered in " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("0.00") + "s.");
  1054. m_executingScript.StopExecution();
  1055. }
  1056. }
  1057. catch (ThreadAbortException) { }
  1058. catch (Exception excp)
  1059. {
  1060. Log("Exception on GoogleVoiceCall. " + excp.Message);
  1061. }
  1062. }
  1063. public void GoogleVoiceSMS(string emailAddress, string password, string destinationNumber, string message)
  1064. {
  1065. try
  1066. {
  1067. GoogleVoiceSMS googleSMS = new GoogleVoiceSMS(m_dialPlanLogDelegate, m_username, m_adminMemberId);
  1068. googleSMS.SendSMS(emailAddress, password, destinationNumber, message);
  1069. }
  1070. catch (ThreadAbortException) { }
  1071. catch (Exception excp)
  1072. {
  1073. Log("Exception on GoogleVoiceSMS. " + excp.Message);
  1074. }
  1075. }
  1076. /// <summary>
  1077. /// Executes a HTTP GET request and if succesful returns up to the first 1024 bytes read from the
  1078. /// response to the caller.
  1079. /// </summary>
  1080. /// <param name="url">The URL of the server to call.</param>
  1081. /// <returns>The first 1024 bytes read from the response.</returns>
  1082. public string WebGet(string url, int timeout)
  1083. {
  1084. Timer cancelTimer = null;
  1085. try
  1086. {
  1087. if (!url.IsNullOrBlank())
  1088. {
  1089. using (WebClient webClient = new WebClient())
  1090. {
  1091. if (timeout > 0)
  1092. {
  1093. timeout = (timeout > WEBGET_MAXIMUM_TIMEOUT) ? timeout = WEBGET_MAXIMUM_TIMEOUT : timeout;
  1094. cancelTimer = new Timer(delegate
  1095. {
  1096. Log("WebGet to " + url + " timed out after " + timeout + " seconds.");
  1097. webClient.CancelAsync();
  1098. },
  1099. null, timeout * 1000, Timeout.Infinite);
  1100. }
  1101. Log("WebGet attempting to read from " + url + ".");
  1102. System.IO.Stream responseStream = webClient.OpenRead(url);
  1103. if (responseStream != null)
  1104. {
  1105. byte[] buffer = new byte[MAX_BYTES_WEB_GET];
  1106. int bytesRead = responseStream.Read(buffer, 0, MAX_BYTES_WEB_GET);
  1107. responseStream.Close();
  1108. return Encoding.UTF8.GetString(buffer, 0, bytesRead);
  1109. }
  1110. }
  1111. }
  1112. return null;
  1113. }
  1114. catch (Exception excp)
  1115. {
  1116. logger.Error("Exception WebGet. " + excp.Message);
  1117. Log("Error in WebGet for " + url + ".");
  1118. return null;
  1119. }
  1120. finally
  1121. {
  1122. if (cancelTimer != null)
  1123. {
  1124. cancelTimer.Dispose();
  1125. }
  1126. }
  1127. }
  1128. public string WebGet(string url)
  1129. {
  1130. return WebGet(url, 0);
  1131. }
  1132. /// <summary>
  1133. /// Sends an email from the dialplan. There are a number of restrictions put in place and this is a privileged
  1134. /// application that users must be manually authorised for.
  1135. /// </summary>
  1136. /// <param name="to">The list of addressees to send the email to. Limited to a maximum of ALLOWED_ADDRESSES_PER_EMAIL.</param>
  1137. /// <param name="subject">The email subject. Limited to a maximum length of MAX_EMAIL_SUBJECT_LENGTH.</param>
  1138. /// <param name="body">The email body. Limited to a maximum length of MAX_EMAIL_BODY_LENGTH.</param>
  1139. public void Email(string to, string subject, string body)
  1140. {
  1141. try
  1142. {
  1143. if (!IsAppAuthorised(m_dialPlanContext.SIPDialPlan.AuthorisedApps, "email"))
  1144. {
  1145. Log("You are not authorised to use the Email application, please contact admin@sipsorcery.com.");
  1146. }
  1147. else if (m_emailCount >= ALLOWED_EMAILS_PER_EXECUTION)
  1148. {
  1149. Log("The maximum number of emails have been sent for this dialplan execution, email not sent.");
  1150. }
  1151. else
  1152. {
  1153. if (to.IsNullOrBlank())
  1154. {
  1155. Log("The To field was blank, email not be sent.");
  1156. }
  1157. else if (subject.IsNullOrBlank())
  1158. {
  1159. Log("The Subject field was blank, email not be sent.");
  1160. }
  1161. else if (body.IsNullOrBlank())
  1162. {
  1163. Log("The Body was empty, email not be sent.");
  1164. }
  1165. else
  1166. {
  1167. string[] addressees = to.Split(';');
  1168. if (addressees.Length > ALLOWED_ADDRESSES_PER_EMAIL)
  1169. {
  1170. Log("The number of Email addressees is to high, only the first " + ALLOWED_ADDRESSES_PER_EMAIL + " will be used.");
  1171. to = null;
  1172. for (int index = 0; index < ALLOWED_ADDRESSES_PER_EMAIL; index++)
  1173. {
  1174. to += addressees[index] + ";";
  1175. }
  1176. }
  1177. m_emailCount++;
  1178. subject = (subject.Length > MAX_EMAIL_SUBJECT_LENGTH) ? subject.Substring(0, MAX_EMAIL_SUBJECT_LENGTH) : subject;
  1179. body = (body.Length > MAX_EMAIL_BODY_LENGTH) ? body.Substring(0, MAX_EMAIL_BODY_LENGTH) : body;
  1180. SIPSorcerySMTP.SendEmail(to, EMAIL_FROM_ADDRESS, subject, body);
  1181. Log("Email sent to " + to + " with subject of \"" + subject + "\".");
  1182. }
  1183. }
  1184. }
  1185. catch (Exception excp)
  1186. {
  1187. logger.Error("Exception Email. " + excp.Message);
  1188. Log("Error sending Email to " + to + " with subject of \"" + subject + "\".");
  1189. }
  1190. }
  1191. /// <summary>
  1192. /// Gets the number of currently active calls for the dial plan owner.
  1193. /// </summary>
  1194. /// <returns>The number of active calls or -1 if there is an error.</returns>
  1195. public int GetCurrentCallCount()
  1196. {
  1197. return m_callManager.GetCurrentCallCount(m_username);
  1198. }
  1199. /// <summary>
  1200. /// Gets a list of currently active calls for the dial plan owner.
  1201. /// </summary>
  1202. /// <returns>A list of currently active calls.</returns>
  1203. public List<SIPDialogueAsset> GetCurrentCalls()
  1204. {
  1205. return m_sipDialoguePersistor.Get(d => d.Owner == m_username, null, 0, Int32.MaxValue);
  1206. }
  1207. private IDbConnection GetDatabaseConnection(StorageTypes storageType, string dbConnStr)
  1208. {
  1209. IDbConnection dbConn = StorageLayer.GetDbConnection(storageType, dbConnStr);
  1210. dbConn.Open();
  1211. return dbConn;
  1212. }
  1213. public void DBWrite(string key, string value)
  1214. {
  1215. if (m_userDataDBType == StorageTypes.Unknown || m_userDataDBConnStr.IsNullOrBlank())
  1216. {
  1217. Log("DBWrite failed as no default user database settings are configured. As an alternative you can specify your own database type and connection string.");
  1218. }
  1219. else
  1220. {
  1221. DBWrite(m_userDataDBType, m_userDataDBConnStr, key, value);
  1222. }
  1223. }
  1224. public void DBWrite(string dbType, string dbConnStr, string key, string value)
  1225. {
  1226. StorageTypes storageType = GetStorageType(dbType);
  1227. if (storageType != StorageTypes.Unknown)
  1228. {
  1229. DBWrite(storageType, dbConnStr, key, value);
  1230. }
  1231. }
  1232. private void DBWrite(StorageTypes storageType, string dbConnStr, string key, string value)
  1233. {
  1234. try
  1235. {
  1236. /* StorageLayer storageLayer = new StorageLayer(storageType, dbConnStr);
  1237. Dictionary<string, object> parameters = new Dictionary<string, object>();
  1238. parameters.Add("dataowner", m_username);
  1239. int ownerKeyCount = Convert.ToInt32(storageLayer.ExecuteScalar("select count(*) from dialplandata where dataowner = @dataowner", parameters));
  1240. if (ownerKeyCount == MAX_DATA_ENTRIES_PER_USER)
  1241. {
  1242. Log("DBWrite failed, you have reached the maximum number of database entries allowed.");
  1243. }
  1244. else
  1245. {*/
  1246. /*parameters.Add("datakey", key);
  1247. int count = Convert.ToInt32(storageLayer.ExecuteScalar("select count(*) from dialplandata where datakey = @datakey and dataowner = @dataowner", parameters));
  1248. parameters.Add("datavalue", value);
  1249. if (count == 0)
  1250. {
  1251. storageLayer.ExecuteNonQuery(storageType, dbConnStr, "insert into dialplandata (dataowner, datakey, datavalue) values (@dataowner, @datakey, @datavalue)", parameters);
  1252. }
  1253. else
  1254. {
  1255. storageLayer.ExecuteNonQuery(storageType, dbConnStr, "update dialplandata set datavalue = @datavalue where dataowner = @dataowner and datakey = @datakey", parameters);
  1256. }
  1257. Log("DBWrite sucessful for datakey \"" + key + "\".");*/
  1258. if (m_userDataDBConnection == null)
  1259. {
  1260. m_userDataDBConnection = GetDatabaseConnection(storageType, dbConnStr);
  1261. }
  1262. IDataParameter dataOwnerParameter = StorageLayer.GetDbParameter(storageType, "dataowner", m_username);
  1263. IDataParameter dataKeyParameter = StorageLayer.GetDbParameter(storageType, "datakey", key);
  1264. IDataParameter dataValueParameter = StorageLayer.GetDbParameter(storageType, "datavalue", value);
  1265. IDbCommand countCommand = StorageLayer.GetDbCommand(storageType, m_userDataDBConnection, "select count(*) from dialplandata where datakey = @datakey and dataowner = @dataowner");
  1266. countCommand.Parameters.Add(dataOwnerParameter);
  1267. countCommand.Parameters.Add(dataKeyParameter);
  1268. int count = Convert.ToInt32(countCommand.ExecuteScalar());
  1269. string sqlCommand = (count == 0) ? "insert into dialplandata (dataowner, datakey, datavalue) values (@dataowner, @datakey, @datavalue)" : "update dialplandata set datavalue = @datavalue where dataowner = @dataowner and datakey = @datakey";
  1270. IDbCommand dbCommand = StorageLayer.GetDbCommand(storageType, m_userDataDBConnection, sqlCommand);
  1271. dbCommand.Parameters.Add(dataOwnerParameter);
  1272. dbCommand.Parameters.Add(dataKeyParameter);
  1273. dbCommand.Parameters.Add(dataValueParameter);
  1274. dbCommand.ExecuteNonQuery();
  1275. Log("DBWrite sucessful for datakey \"" + key + "\".");
  1276. //}
  1277. }
  1278. catch (Exception excp)
  1279. {
  1280. Log("Exception DBWrite. " + excp.Message);
  1281. }
  1282. }
  1283. public void DBDelete(string key)
  1284. {
  1285. if (m_userDataDBType == StorageTypes.Unknown || m_userDataDBConnStr.IsNullOrBlank())
  1286. {
  1287. Log("DBDelete failed as no default user database settings are configured. As an alternative you can specify your own database type and connection string.");
  1288. }
  1289. else
  1290. {
  1291. DBDelete(m_userDataDBType, m_userDataDBConnStr, key);
  1292. }
  1293. }
  1294. public void DBDelete(string dbType, string dbConnStr, string key)
  1295. {
  1296. StorageTypes storageType = GetStorageType(dbType);
  1297. if (storageType != StorageTypes.Unknown)
  1298. {
  1299. DBDelete(storageType, dbConnStr, key);
  1300. }
  1301. }
  1302. private void DBDelete(StorageTypes storageType, string dbConnStr, string key)
  1303. {
  1304. try
  1305. {
  1306. /*StorageLayer storageLayer = new StorageLayer(storageType, dbConnStr);
  1307. Dictionary<string, object> parameters = new Dictionary<string, object>();
  1308. parameters.Add("dataowner", m_username);
  1309. parameters.Add("datakey", key);
  1310. storageLayer.ExecuteNonQuery(storageType, dbConnStr, "delete from dialplandata where dataowner = @dataowner and datakey = @datakey", parameters);
  1311. Log("DBDelete sucessful for datakey \"" + key + "\".");*/
  1312. //using (IDbConnection dbConn = StorageLayer.GetDbConnection(storageType, dbConnStr))
  1313. //{
  1314. // dbConn.Open();
  1315. if (m_userDataDBConnection == null)
  1316. {
  1317. m_userDataDBConnection = GetDatabaseConnection(storageType, dbConnStr);
  1318. }
  1319. IDataParameter dataOwnerParameter = StorageLayer.GetDbParameter(storageType, "dataowner", m_username);
  1320. IDataParameter dataKeyParameter = StorageLayer.GetDbParameter(storageType, "datakey", key);
  1321. IDbCommand dbCommand = StorageLayer.GetDbCommand(storageType, m_userDataDBConnection, "delete from dialplandata where dataowner = @dataowner and datakey = @datakey");
  1322. dbCommand.Parameters.Add(dataOwnerParameter);
  1323. dbCommand.Parameters.Add(dataKeyParameter);
  1324. dbCommand.ExecuteNonQuery();
  1325. //}
  1326. Log("DBDelete sucessful for datakey \"" + key + "\".");
  1327. }
  1328. catch (Exception excp)
  1329. {
  1330. Log("Exception DBDelete. " + excp.Message);
  1331. }
  1332. }
  1333. public string DBRead(string key)
  1334. {
  1335. if (m_userDataDBType == StorageTypes.Unknown || m_userDataDBConnStr.IsNullOrBlank())
  1336. {
  1337. Log("DBRead failed as no default user database settings are configured. As an alternative you can specify your own database type and connection string.");
  1338. return null;
  1339. }
  1340. else
  1341. {
  1342. return DBRead(m_userDataDBType, m_userDataDBConnStr, key, false);
  1343. }
  1344. }
  1345. public string DBRead(string dbType, string dbConnStr, string key)
  1346. {
  1347. StorageTypes storageType = GetStorageType(dbType);
  1348. if (storageType != StorageTypes.Unknown)
  1349. {
  1350. return DBRead(storageType, dbConnStr, key, false);
  1351. }
  1352. return null;
  1353. }
  1354. public string DBReadForUpdate(string key)
  1355. {
  1356. if (m_userDataDBType == StorageTypes.Unknown || m_userDataDBConnStr.IsNullOrBlank())
  1357. {
  1358. Log("DBRead failed as no default user database settings are configured. As an alternative you can specify your own database type and connection string.");
  1359. return null;
  1360. }
  1361. else
  1362. {
  1363. return DBRead(m_userDataDBType, m_userDataDBConnStr, key, true);
  1364. }
  1365. }
  1366. private string DBRead(StorageTypes storageType, string dbConnStr, string key, bool forUpdate)
  1367. {
  1368. try
  1369. {
  1370. /*StorageLayer storageLayer = new StorageLayer(storageType, dbConnStr);
  1371. Dictionary<string, object> parameters = new Dictionary<string, object>();
  1372. parameters.Add("dataowner", m_username);
  1373. parameters.Add("datakey", key);
  1374. string result = storageLayer.ExecuteScalar("select datavalue from dialplandata where dataowner = @dataowner and datakey = @datakey", parameters) as string;
  1375. Log("DBRead sucessful for datakey \"" + key + "\", value=" + result + ".");
  1376. return result;*/
  1377. string result = null;
  1378. //using (IDbConnection dbConn = StorageLayer.GetDbConnection(storageType, dbConnStr))
  1379. //{
  1380. // dbConn.Open();
  1381. if (m_userDataDBConnection == null)
  1382. {
  1383. m_userDataDBConnection = GetDatabaseConnection(storageType, dbConnStr);
  1384. }
  1385. IDataParameter dataOwnerParameter = StorageLayer.GetDbParameter(storageType, "dataowner", m_username);
  1386. IDataParameter dataKeyParameter = StorageLayer.GetDbParameter(storageType, "datakey", key);
  1387. string sqlCommandText = "select datavalue from dialplandata where dataowner = @dataowner and datakey = @datakey";
  1388. if(forUpdate)
  1389. {
  1390. sqlCommandText += " for update";
  1391. }
  1392. IDbCommand dbCommand = StorageLayer.GetDbCommand(storageType, m_userDataDBConnection, sqlCommandText);
  1393. dbCommand.Parameters.Add(dataOwnerParameter);
  1394. dbCommand.Parameters.Add(dataKeyParameter);
  1395. result = dbCommand.ExecuteScalar() as string;
  1396. //}
  1397. if (forUpdate)
  1398. {
  1399. Log("DBReadForUpdate sucessful for datakey \"" + key + "\", value=" + result + ".");
  1400. }
  1401. else
  1402. {
  1403. Log("DBRead sucessful for datakey \"" + key + "\", value=" + result + ".");
  1404. }
  1405. return result;
  1406. }
  1407. catch (Exception excp)
  1408. {
  1409. Log("Exception DBRead. " + excp.Message);
  1410. return null;
  1411. }
  1412. }
  1413. public void StartTransaction()
  1414. {
  1415. if (m_userDataDBTransaction == null)
  1416. {
  1417. if (m_userDataDBConnection == null)
  1418. {
  1419. m_userDataDBConnection = GetDatabaseConnection(m_userDataDBType, m_userDataDBConnStr);
  1420. }
  1421. m_userDataDBTransaction = m_userDataDBConnection.BeginTransaction();
  1422. Log("Transaction started.");
  1423. }
  1424. else
  1425. {
  1426. Log("An existing transaction was already in progress no new transaction was started.");
  1427. }
  1428. }
  1429. public void CommitTransaction()
  1430. {
  1431. if (m_userDataDBTransaction != null)
  1432. {
  1433. m_userDataDBTransaction.Commit();
  1434. m_userDataDBTransaction = null;
  1435. Log("Transaction committed.");
  1436. }
  1437. }
  1438. public void RollbackTransaction()
  1439. {
  1440. if (m_userDataDBTransaction != null)
  1441. {
  1442. m_userDataDBTransaction.Rollback();
  1443. m_userDataDBTransaction = null;
  1444. Log("Transaction rolled back.");
  1445. }
  1446. }
  1447. public void DBExecuteNonQuery(string dbType, string dbConnStr, string query)
  1448. {
  1449. try
  1450. {
  1451. if (!IsAppAuthorised(m_dialPlanContext.SIPDialPlan.AuthorisedApps, "dbexecutenonquery"))
  1452. {
  1453. Log("You are not authorised to use the DBExecuteNonQuery application, please contact admin@sipsorcery.com.");
  1454. }
  1455. else
  1456. {
  1457. StorageTypes storageType = GetStorageType(dbType);
  1458. if (storageType != StorageTypes.Unknown)
  1459. {
  1460. StorageLayer storageLayer = new StorageLayer(storageType, dbConnStr);
  1461. storageLayer.ExecuteNonQuery(storageType, dbConnStr, query);
  1462. Log("DBExecuteNonQuery successful for " + query + ".");
  1463. }
  1464. else
  1465. {
  1466. Log("Exception DBExecuteNonQuery did not recognise database type " + dbType + ".");
  1467. }
  1468. }
  1469. }
  1470. catch (Exception excp)
  1471. {
  1472. Log("Exception DBExecuteNonQuery. " + excp.Message);
  1473. }
  1474. }
  1475. public string DBExecuteScalar(string dbType, string dbConnStr, string query)
  1476. {
  1477. try
  1478. {
  1479. if (!IsAppAuthorised(m_dialPlanContext.SIPDialPlan.AuthorisedApps, "dbexecutescalar"))
  1480. {
  1481. Log("You are not authorised to use the DBExecuteScalar application, please contact admin@sipsorcery.com.");
  1482. return null;
  1483. }
  1484. else
  1485. {
  1486. StorageTypes storageType = GetStorageType(dbType);
  1487. if (storageType != StorageTypes.Unknown)
  1488. {
  1489. StorageLayer storageLayer = new StorageLayer(storageType, dbConnStr);
  1490. string result = storageLayer.ExecuteScalar(storageType, dbConnStr, query) as string;
  1491. Log("DBExecuteScalar successful result=" + result + ".");
  1492. return result;
  1493. }
  1494. else
  1495. {
  1496. Log("Exception DBExecuteScalar did not recognise database type " + dbType + ".");
  1497. return null;
  1498. }
  1499. }
  1500. }
  1501. catch (Exception excp)
  1502. {
  1503. Log("Exception DBExecuteScalar. " + excp.Message);
  1504. return null;
  1505. }
  1506. }
  1507. private StorageTypes GetStorageType(string dbType)
  1508. {
  1509. if (dbType.IsNullOrBlank())
  1510. {
  1511. Log("The database type was empty for DBWrite or DBRead.");
  1512. return StorageTypes.Unknown;
  1513. }
  1514. else if (Regex.Match(dbType, "mysql", RegexOptions.IgnoreCase).Success)
  1515. {
  1516. return StorageTypes.MySQL;
  1517. }
  1518. else if (Regex.Match(dbType, "(pgsql|postgres)", RegexOptions.IgnoreCase).Success)
  1519. {
  1520. return StorageTypes.Postgresql;
  1521. }
  1522. else
  1523. {
  1524. Log("Database type " + dbType + " is not supported in DBWrite and DBRead.");
  1525. return StorageTypes.Unknown;
  1526. }
  1527. }
  1528. /// <summary>
  1529. /// Applies a set of NAPTR rules obtained from an ENUM lookup to attempt to get a SIP URI.
  1530. /// This functionality should be moved closer to the DNS classes once it becomes more mature and universal.
  1531. /// See RFC 2915.
  1532. /// </summary>
  1533. /// <param name="naptrRecords"></param>
  1534. /// <returns></returns>
  1535. private string ApplyENUMRules(string number, RecordNAPTR[] naptrRecords)
  1536. {
  1537. try
  1538. {
  1539. RecordNAPTR priorityRecord = null;
  1540. foreach (RecordNAPTR naptrRecord in naptrRecords)
  1541. {
  1542. if (naptrRecord.Service != null && naptrRecord.Service.ToUpper() == RecordNAPTR.SIP_SERVICE_KEY)
  1543. {
  1544. if (priorityRecord == null)
  1545. {
  1546. priorityRecord = naptrRecord;
  1547. }
  1548. else if (naptrRecord.Order < priorityRecord.Order)
  1549. {
  1550. priorityRecord = naptrRecord;
  1551. }
  1552. else if (naptrRecord.Order == priorityRecord.Order && naptrRecord.Preference < priorityRecord.Preference)
  1553. {
  1554. priorityRecord = naptrRecord;
  1555. }
  1556. }
  1557. }
  1558. if (priorityRecord != null && priorityRecord.Rule != null && Regex.Match(priorityRecord.Rule, "!.+!.+!").Success)
  1559. {
  1560. //logger.Debug("rule=" + priorityRecord.Rule + ".");
  1561. Match match = Regex.Match(priorityRecord.Rule, "!(?<pattern>.+?)!(?<substitute>.+?)!(?<options>.*)");
  1562. if (match.Success)
  1563. {
  1564. string pattern = match.Result("${pattern}");
  1565. //logger.Debug("pattern=" + pattern + ".");
  1566. string substitute = Regex.Replace(match.Result("${substitute}"), @"\\(?<digit>\d)", @"${${digit}}");
  1567. string options = match.Result("${options}");
  1568. logger.Debug("ENUM rule: s/" + pattern + "/" + substitute + "/" + options);
  1569. string domainlessNumber = number.Substring(0, number.IndexOf('.'));
  1570. Regex enumRegex = new Regex(pattern);
  1571. if (enumRegex.Match(domainlessNumber).Success)
  1572. {
  1573. //{
  1574. // Log("enum substitute /" + number + "/" + pattern + "/" + substitute + "/");
  1575. // return Regex.Replace(number, pattern, substitute);
  1576. //}
  1577. //else
  1578. //{
  1579. // Remove the domain from number and match.
  1580. Log("enum substitute /" + domainlessNumber + "/" + pattern + "/" + substitute + "/");
  1581. return Regex.Replace(domainlessNumber, pattern, substitute);
  1582. }
  1583. else if (enumRegex.Match("+" + domainlessNumber).Success)
  1584. {
  1585. // Remove the domain from number and match.
  1586. Log("enum substitute /+" + domainlessNumber + "/" + pattern + "/" + substitute + "/");
  1587. return Regex.Replace("+" + domainlessNumber, pattern, substitute);
  1588. }
  1589. }
  1590. else
  1591. {
  1592. logger.Warn("Priority rule for an ENUM lookup was not recognised: " + priorityRecord.Rule + ".");
  1593. }
  1594. }
  1595. return null;
  1596. }
  1597. catch (Exception excp)
  1598. {
  1599. logger.Error("Exception ApplyENUMRules. " + excp.Message);
  1600. return null;
  1601. }
  1602. }
  1603. /// <summary>
  1604. /// Attempts to put a number into e164 format for an ENUM lookup.
  1605. /// </summary>
  1606. /// <param name="number"></param>
  1607. /// <returns></returns>
  1608. private string FormatForENUMLookup(string number)
  1609. {
  1610. try
  1611. {
  1612. if (number == null || number.Trim().Length == 0)
  1613. {
  1614. return null;
  1615. }
  1616. else if (Regex.Match(number, @"(\d\.){5}").Success)
  1617. {
  1618. logger.Debug("Number looks like it's already in the required format.");
  1619. return number;
  1620. }
  1621. else
  1622. {
  1623. //number = number.Trim().Trim(new char[] { '+', '0' });
  1624. Match match = Regex.Match(number, @"(?<number>[^\.]+)\.(?<domain>.+)");
  1625. if (match.Success)
  1626. {
  1627. char[] enumNumber = match.Result("${number}").ToCharArray();
  1628. string domain = match.Result("${domain}");
  1629. string result = null;
  1630. for (int index = enumNumber.Length - 1; index >= 0; index--)
  1631. {
  1632. result += enumNumber[index] + ".";
  1633. }
  1634. return result + domain;
  1635. }
  1636. else
  1637. {
  1638. logger.Error("ENUM lookup number was not in the correct format, must be number.domain");
  1639. return null;
  1640. }
  1641. }
  1642. }
  1643. catch (Exception excp)
  1644. {
  1645. logger.Error("Exception FormatForENUMLookup. " + excp);
  1646. return number;
  1647. }
  1648. }
  1649. public void ExtendScriptTimeout(int seconds)
  1650. {
  1651. m_executingScript.EndTime = DateTime.Now.AddSeconds(seconds + DialPlanExecutingScript.MAX_SCRIPTPROCESSING_SECONDS);
  1652. }
  1653. /// <summary>
  1654. /// Used to authorise calls to privileged dialplan applications. For example sending an email requires that the dialplan has the "email" in
  1655. /// its list of authorised apps.
  1656. /// </summary>
  1657. /// <param name="authorisedApps">A semi-colon delimited list of authorised applications for this dialplan.</param>
  1658. /// <param name="applicationName">The name of the dialplan application checking for authorisation.</param>
  1659. /// <returns>True if authorised, false otherwise.</returns>
  1660. private bool IsAppAuthorised(string authorisedApps, string applicationName)
  1661. {
  1662. try
  1663. {
  1664. logger.Debug("Checking IsAppAuthorised with authorisedApps=" + authorisedApps + ", applicationName=" + applicationName + ".");
  1665. if (authorisedApps.IsNullOrBlank() || applicationName.IsNullOrBlank())
  1666. {
  1667. return false;
  1668. }
  1669. else if (authorisedApps == SIPDialPlan.ALL_APPS_AUTHORISED)
  1670. {
  1671. return true;
  1672. }
  1673. else
  1674. {
  1675. string[] authorisedAppsSplit = authorisedApps.Split(';');
  1676. foreach (string app in authorisedAppsSplit)
  1677. {
  1678. if (!app.IsNullOrBlank() && app.Trim().ToLower() == applicationName.Trim().ToLower())
  1679. {
  1680. return true;
  1681. }
  1682. }
  1683. return false;
  1684. }
  1685. }
  1686. catch (Exception excp)
  1687. {
  1688. logger.Error("Exception IsAppAuthorised. " + excp.Message);
  1689. return false;
  1690. }
  1691. }
  1692. private void SendGoogleAnalyticsEvent(string category, string action, string label, int? value)
  1693. {
  1694. try
  1695. {
  1696. if (m_sendAnalytics)
  1697. {
  1698. GoogleEvent googleEvent = new GoogleEvent("www.sipsorcery.com",
  1699. category,
  1700. action,
  1701. label,
  1702. value);
  1703. TrackingRequest request = new RequestFactory().BuildRequest(googleEvent);
  1704. ThreadPool.QueueUserWorkItem(delegate { GoogleTracking.FireTrackingEvent(request); });
  1705. }
  1706. }
  1707. catch (Exception excp)
  1708. {
  1709. logger.Error("Exception SendGoogleAnalyticsEvent. " + excp.Message);
  1710. }
  1711. }
  1712. private void FireProxyLogEvent(SIPMonitorEvent monitorEvent)
  1713. {
  1714. try
  1715. {
  1716. if (monitorEvent is SIPMonitorConsoleEvent)
  1717. {
  1718. SIPMonitorConsoleEvent consoleEvent = monitorEvent as SIPMonitorConsoleEvent;
  1719. if (m_dialPlanContext.TraceLog != null)
  1720. {
  1721. m_dialPlanContext.TraceLog.AppendLine(consoleEvent.EventType + "=> " + monitorEvent.Message);
  1722. }
  1723. }
  1724. if (m_dialPlanLogDelegate != null)
  1725. {
  1726. m_dialPlanLogDelegate(monitorEvent);
  1727. }
  1728. }
  1729. catch (Exception excp)
  1730. {
  1731. logger.Error("Exception FireProxyLogEvent DialPlanScriptHelper. " + excp.Message);
  1732. }
  1733. }
  1734. #region Unit testing.
  1735. #if UNITTEST
  1736. [TestFixture]
  1737. public class DialPlanScriptHelperUnitTest
  1738. {
  1739. [TestFixtureSetUp]
  1740. public void Init() {
  1741. log4net.Config.BasicConfigurator.Configure();
  1742. }
  1743. [TestFixtureTearDown]
  1744. public void Dispose()
  1745. { }
  1746. [Test]
  1747. public void SampleTest()
  1748. {
  1749. Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
  1750. Assert.IsTrue(true, "True was false.");
  1751. }
  1752. /// <summary>
  1753. /// Tests applying an ENUM rule.
  1754. /// </summary>
  1755. [Test]
  1756. public void ApplyENUMRuleUnitTest() {
  1757. Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
  1758. //DialPlanContext dialPlanContext = new DialPlanContext(null, null, null, null, null, null, null, null, null, Guid.Empty);
  1759. SIPRequest request = new SIPRequest(SIPMethodsEnum.INVITE, "sip:1234@host.com");
  1760. DialPlanScriptHelper helper = new DialPlanScriptHelper(null, null, (e) => { Console.WriteLine(e.Message); }, null, request, SIPCallDirection.None,
  1761. null, null, null, null, null, null, null);
  1762. string result = helper.ENUMLookup("+18005551212.e164.org");
  1763. Console.WriteLine("lookup result=" + result + ".");
  1764. Assert.IsTrue(true, "True was false.");
  1765. }
  1766. }
  1767. #endif
  1768. #endregion
  1769. }
  1770. }