PageRenderTime 58ms CodeModel.GetById 15ms 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

Large files files are truncated, but you can click here to view the full file

  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. GoogleVo

Large files files are truncated, but you can click here to view the full file