Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
18 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corporation
  3. Module Name:
  4. asynccon.cxx
  5. Abstract:
  6. Author:
  7. --*/
  8. #define INCL_INETSRV_INCS
  9. #include "smtpinc.h"
  10. #include "remoteq.hxx"
  11. #include <asynccon.hxx>
  12. #include <dnsreci.h>
  13. #include <cdns.h>
  14. #include "smtpdns.hxx"
  15. #include "asyncmx.hxx"
  16. #include "smtpmsg.h"
  17. #include <tran_evntlog.h>
  18. CDnsLogger *g_pDnsLogger = NULL;
  19. extern DWORD g_DnsErrorsBeforeFailover;
  20. extern DWORD g_DnsConnectsInProbation;
  21. extern BOOL QueueCallBackFunction(PVOID ThisPtr, BOOLEAN fTimedOut);
  22. extern void DeleteDnsRec(PSMTPDNS_RECS pDnsRec);
  23. extern PSMTPDNS_RECS GetDnsRecordsFromLiteral(const char * HostName);
  24. CPool CAsyncSmtpDns::Pool(SMTP_ASYNCMX_SIGNATURE);
  25. CAsyncSmtpDns::CAsyncSmtpDns(
  26. SMTP_SERVER_INSTANCE * pServiceInstance,
  27. ISMTPConnection *pSmtpConnection,
  28. RETRYPARAMS *pRetryParams,
  29. char *MyFQDN)
  30. : CAsyncMxDns(MyFQDN)
  31. {
  32. TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::CAsyncSmtpDns");
  33. DebugTrace((LPARAM) this, "Creating CAsyncSmtpDns object = 0x%08x", this);
  34. m_Signature = SMTP_ASYNCMX_SIGNATURE;
  35. m_DomainOptions = 0;
  36. m_fConnectToSmartHost = FALSE;
  37. m_pServiceInstance = pServiceInstance;
  38. m_pISMTPConnection = pSmtpConnection;
  39. m_pDNS_RESOLVER_RECORD = NULL;
  40. m_fInitCalled = FALSE;
  41. m_pszSSLVerificationName = NULL;
  42. //
  43. // By default we fail in a retryable fashion. This is the generic failure code. If the
  44. // query succeeds this should be set to ERROR_SUCCESS. This may also set this to a more
  45. // specific error code at the point of failure.
  46. //
  47. m_dwDiagnostic = AQUEUE_E_HOST_NOT_FOUND;
  48. //
  49. // pRetryParams encapsulates parameters for a failed message if this DNS query
  50. // is on an SMTP 4xx error failover path
  51. //
  52. if(!pRetryParams)
  53. ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
  54. else
  55. {
  56. DebugTrace((LPARAM) this, "Failover mailmsg");
  57. CopyMemory(&m_RetryParams, pRetryParams, sizeof(m_RetryParams));
  58. }
  59. pServiceInstance->InsertAsyncDnsObject(this);
  60. }
  61. BOOL CAsyncSmtpDns::Init (LPSTR pszSSLVerificationName)
  62. {
  63. BOOL fRet = FALSE;
  64. TraceFunctEnterEx ((LPARAM) this, "CAsyncSmtpDns::Init");
  65. m_fInitCalled = TRUE;
  66. if (pszSSLVerificationName) {
  67. m_pszSSLVerificationName = new char [lstrlen(pszSSLVerificationName) + 1];
  68. if (!m_pszSSLVerificationName)
  69. goto Exit;
  70. lstrcpy (m_pszSSLVerificationName, pszSSLVerificationName);
  71. }
  72. fRet = TRUE;
  73. Exit:
  74. TraceFunctLeaveEx ((LPARAM) this);
  75. return fRet;
  76. }
  77. BOOL CAsyncSmtpDns::IsShuttingDown()
  78. {
  79. return m_pServiceInstance->IsShuttingDown();
  80. }
  81. BOOL CAsyncSmtpDns::IsAddressMine(DWORD dwIp)
  82. {
  83. return m_pServiceInstance->IsAddressMine(dwIp, 25);
  84. }
  85. CAsyncSmtpDns::~CAsyncSmtpDns()
  86. {
  87. DWORD dwAck = 0;
  88. MessageAck MsgAck;
  89. TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::~CAsyncSmtpDns");
  90. DebugTrace((LPARAM) this, "Destructing CAsyncSmtpDns object = 0x%08x", this);
  91. _ASSERT (m_fInitCalled && "Init not called on CAsyncSmtpDns");
  92. if(m_fMxLoopBack)
  93. m_dwDiagnostic = AQUEUE_E_LOOPBACK_DETECTED;
  94. DeleteDnsRec(m_AuxList);
  95. //
  96. // If we did not succeed, we need to ack the connection here (m_dwDiagnostic holds
  97. // the error code to use). On the other hand, if we succeeded, then HandleCompletedData
  98. // must have kicked off an async connection to the server SMTP, and the ISMTPConnection
  99. // will be acked by the "async connect" code -- we don't need to do anything. The
  100. // m_pISMTPConnection may also be legally set to NULL (see HandleCompletedData).
  101. //
  102. if(m_dwDiagnostic != ERROR_SUCCESS && m_pISMTPConnection)
  103. {
  104. if(m_RetryParams.m_pIMsg)
  105. {
  106. DebugTrace((LPARAM) this, "Acking connection on MX failover");
  107. MsgAck.dwMsgStatus = MESSAGE_STATUS_RETRY_ALL;
  108. MsgAck.pvMsgContext = (PDWORD) m_RetryParams.m_pAdvQContext;
  109. MsgAck.pIMailMsgProperties = (IMailMsgProperties *) m_RetryParams.m_pIMsg;
  110. m_pISMTPConnection->AckMessage(&MsgAck);
  111. MsgAck.pIMailMsgProperties->Release();
  112. ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
  113. m_pISMTPConnection->AckConnection(CONNECTION_STATUS_DROPPED);
  114. m_pISMTPConnection->SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, NULL, NULL);
  115. m_pISMTPConnection->Release();
  116. m_pISMTPConnection = NULL;
  117. }
  118. else
  119. {
  120. if(AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND == m_dwDiagnostic)
  121. dwAck = CONNECTION_STATUS_FAILED_NDR_UNDELIVERED;
  122. else if(AQUEUE_E_LOOPBACK_DETECTED == m_dwDiagnostic)
  123. dwAck = CONNECTION_STATUS_FAILED_LOOPBACK;
  124. else
  125. dwAck = CONNECTION_STATUS_FAILED;
  126. DebugTrace((LPARAM) this, "Connection status: %d, Failure: %d", dwAck, m_dwDiagnostic);
  127. m_pISMTPConnection->AckConnection(dwAck);
  128. m_pISMTPConnection->SetDiagnosticInfo(m_dwDiagnostic, NULL, NULL);
  129. m_pISMTPConnection->Release();
  130. m_pISMTPConnection = NULL;
  131. }
  132. }
  133. if(m_pDNS_RESOLVER_RECORD != NULL)
  134. {
  135. DebugTrace((LPARAM) this, "Deleting DNS_RESOLVER_RECORD in Async SMTP obj");
  136. delete m_pDNS_RESOLVER_RECORD;
  137. m_pDNS_RESOLVER_RECORD = NULL;
  138. }
  139. DBG_CODE(else DebugTrace((LPARAM) this, "No DNS_RESOLVER_RECORD set for Async SMTP obj"));
  140. if(m_pszSSLVerificationName)
  141. delete [] m_pszSSLVerificationName;
  142. m_pServiceInstance->RemoveAsyncDnsObject(this);
  143. TraceFunctLeaveEx((LPARAM) this);
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Description:
  147. // HandleCompletedData is called when the DNS resolve is finished. It
  148. // does the final processing after DNS is finished, and sets the
  149. // m_dwDiagnostic flag appropriately. It does one of three things based
  150. // on the DnsStatus and m_AuxList:
  151. //
  152. // (1) If the resolve was successful, it kicks off a connection to the
  153. // server and set the m_dwDiagnostic to ERROR_SUCCESS.
  154. // (2) If the resolve failed authoritatively, it set the m_dwDiagnostic
  155. // to NDR the messages (after checking for a smarthost) ==
  156. // AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND.
  157. // (3) If the resolve failed (from dwDnsStatus and m_AuxList) or if
  158. // something fails during HandleCompletedData, the m_dwDiagnostic is
  159. // not modified (it remains initialized to AQUEUE_E_DNS_FAILURE, the
  160. // default error code).
  161. //
  162. // m_dwDiagnostic is examined in ~CAsyncSmtpDns.
  163. // Arguments:
  164. // DNS_STATUS dwDnsStatus - Status code from DnsParseMessage
  165. // Returns:
  166. // Nothing.
  167. //-----------------------------------------------------------------------------
  168. void CAsyncSmtpDns::HandleCompletedData(DNS_STATUS dwDnsStatus)
  169. {
  170. BOOL fRet = FALSE;
  171. CAsyncMx * pAsyncCon = NULL;
  172. MXPARAMS Params;
  173. DWORD dwPostDnsSmartHost = INADDR_NONE;
  174. // +3 for enclosing [] and NULL termination
  175. CHAR szPostDnsSmartHost[IP_ADDRESS_STRING_LENGTH + 3];
  176. TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::HandleCompletedData");
  177. //
  178. // The DNS lookup failed authoritatively. The messages will be NDR'ed unless there
  179. // is a smarthost configured. If there is a smarthost, we will kick off a resolve
  180. // for it.
  181. //
  182. if(ERROR_NOT_FOUND == dwDnsStatus)
  183. {
  184. if(m_fConnectToSmartHost)
  185. {
  186. char szSmartHost[MAX_PATH+1];
  187. m_pServiceInstance->GetSmartHost(szSmartHost);
  188. ((REMOTE_QUEUE *)m_pServiceInstance->QueryRemoteQObj())->StartAsyncConnect(szSmartHost,
  189. m_pISMTPConnection, m_DomainOptions, FALSE);
  190. //Do not release this ISMTPConnection object! We passed it on to
  191. //StartAsyncConnect so that it can try to associate this object with
  192. //a connection with the smart host. We set it to null here so that we
  193. //will not release it or ack it in the destructor of this object.
  194. m_pISMTPConnection = NULL;
  195. m_dwDiagnostic = ERROR_SUCCESS;
  196. TraceFunctLeaveEx((LPARAM) this);
  197. return;
  198. } else {
  199. //No smart host, messages will be NDR'ed. Return value is meaningless.
  200. m_dwDiagnostic = AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND;
  201. TraceFunctLeaveEx((LPARAM) this);
  202. return;
  203. }
  204. }
  205. //Successful DNS lookup.
  206. if(dwDnsStatus == ERROR_SUCCESS && m_AuxList)
  207. {
  208. //
  209. // The post-DNS smart host is useful for testing DNS. It allows us
  210. // to exercise the DNS resolution codepath and yet send to a smarthost.
  211. // If a smarthost is specified, we will allocate a new TempList struct
  212. // and fill it in with the IP address.
  213. //
  214. if(m_pServiceInstance->GetPostDnsSmartHost(
  215. szPostDnsSmartHost, sizeof(szPostDnsSmartHost)))
  216. {
  217. DeleteDnsRec(m_AuxList);
  218. // Note: Literal IP must be enclosed in brackets: []
  219. m_AuxList = GetDnsRecordsFromLiteral(szPostDnsSmartHost);
  220. if(!m_AuxList)
  221. {
  222. ErrorTrace((LPARAM) this, "Can't convert %s to IP", szPostDnsSmartHost);
  223. TraceFunctLeaveEx((LPARAM) this);
  224. return;
  225. }
  226. }
  227. if(m_RetryParams.m_pIMsg)
  228. {
  229. m_AuxList->pMailMsgObj = m_RetryParams.m_pIMsg;
  230. m_AuxList->pAdvQContext = m_RetryParams.m_pAdvQContext;
  231. m_AuxList->pRcptIdxList = m_RetryParams.m_pRcptIdxList;
  232. m_AuxList->dwNumRcpts = m_RetryParams.m_dwNumRcpts;
  233. }
  234. CrashOnInvalidSMTPConn(m_pISMTPConnection);
  235. Params.HostName = "";
  236. Params.PortNum = m_pServiceInstance->GetRemoteSmtpPort();
  237. Params.TimeOut = INFINITE;
  238. Params.CallBack = QueueCallBackFunction;
  239. Params.pISMTPConnection = m_pISMTPConnection;
  240. Params.pInstance = m_pServiceInstance;
  241. Params.pDnsRec = m_AuxList;
  242. Params.pDNS_RESOLVER_RECORD = m_pDNS_RESOLVER_RECORD;
  243. pAsyncCon = new CAsyncMx (&Params);
  244. if(pAsyncCon != NULL)
  245. {
  246. // Abdicate responsibility for deleting outbound params
  247. m_pDNS_RESOLVER_RECORD = NULL;
  248. m_AuxList = NULL;
  249. if(m_RetryParams.m_pIMsg)
  250. ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
  251. // Outbound SSL: Set name against which server cert. should matched
  252. fRet = pAsyncCon->Init(m_pszSSLVerificationName);
  253. if(!fRet)
  254. {
  255. delete pAsyncCon;
  256. goto Exit;
  257. }
  258. if(!m_fConnectToSmartHost)
  259. {
  260. pAsyncCon->SetTriedOnFailHost();
  261. }
  262. pAsyncCon->SetDomainOptions(m_DomainOptions);
  263. fRet = pAsyncCon->InitializeAsyncConnect();
  264. if(!fRet)
  265. {
  266. delete pAsyncCon;
  267. }
  268. else
  269. {
  270. // We should not Ack this in the destructor
  271. m_pISMTPConnection = NULL;
  272. m_dwDiagnostic = ERROR_SUCCESS;
  273. }
  274. }
  275. else
  276. {
  277. DeleteDnsRec(m_AuxList);
  278. }
  279. }
  280. Exit:
  281. TraceFunctLeaveEx((LPARAM) this);
  282. return;
  283. }
  284. //------------------------------------------------------------------------------
  285. // Description:
  286. // Simple wrapper function for DnsQueryAsync. This is a virtual function
  287. // called from CAsyncDns but implemented in CAsyncSmtpDns. In order to retry
  288. // a DNS query we need all the parameters of the old query. These are members
  289. // of CAsyncSmtpDns. Thus the virtual function based implementation.
  290. //
  291. // Arguments:
  292. // BOOL fUdp -- Use UDP as transport for retry query?
  293. //
  294. // Returns:
  295. // TRUE on success. In this situation the ISMTPConnection ack (and release of
  296. // pDNS_RESOLVER_RECORD) is handled by the new CAsyncSmtpDns object created
  297. // by DnsQueryAsync. The diagnostic code of this object is cleared.
  298. //
  299. // FALSE on error. In this case, the cleanup for ISMTPConnection and
  300. // pDNS_RESOLVER_RECORD must be done by "this" CAsyncSmtpDns. The
  301. // diagnostic code is not touched.
  302. //------------------------------------------------------------------------------
  303. BOOL CAsyncSmtpDns::RetryAsyncDnsQuery(BOOL fUdp)
  304. {
  305. TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::RetryAsyncDnsQuery");
  306. BOOL fRet = FALSE;
  307. RETRYPARAMS *pRetryParams = NULL;
  308. //
  309. // If all DNS servers are down, we shouldn't call DnsQueryAsync. This is
  310. // because DnsQueryAsync guarantees that if nothing is up, it will try to
  311. // query one of the down DNS servers (so that *something* happens). This
  312. // is fine when the resolve is happening for the first time (after getting
  313. // a remote-queue from AQueue), but on the retry code-path, this will cause
  314. // us to loop. That is why we return immediately if there are no DNS servers.
  315. //
  316. if(GetDnsList()->GetUpServerCount() == 0)
  317. {
  318. // How to ack the connection
  319. m_dwDiagnostic = AQUEUE_E_NO_DNS_SERVERS;
  320. return FALSE;
  321. }
  322. //
  323. // If we do not have a connection object, then the requery attempt
  324. // is doomed to fail. This can happen when we disconnect and
  325. // ATQ calls our completion function with ERROR_OPERATION_ABORTED
  326. // If we don't have a connection object, there is no way to
  327. // ack the connection or get messages to send.
  328. //
  329. if (!m_pISMTPConnection) {
  330. DebugTrace((LPARAM) this,
  331. "RetryAsyncDnsQuery called without connection object - aborting");
  332. //should be cleared by same code path
  333. _ASSERT(!m_pDNS_RESOLVER_RECORD);
  334. fRet = FALSE; //there is nothing to Ack.
  335. goto Exit;
  336. }
  337. // Pass in failover message params if any
  338. if(m_RetryParams.m_pIMsg)
  339. pRetryParams = &m_RetryParams;
  340. fRet = DnsQueryAsync(
  341. m_pServiceInstance,
  342. m_HostName,
  343. m_FQDNToDrop,
  344. m_pISMTPConnection,
  345. m_dwFlags,
  346. m_DomainOptions,
  347. m_fConnectToSmartHost,
  348. m_pDNS_RESOLVER_RECORD,
  349. m_pszSSLVerificationName,
  350. pRetryParams,
  351. fUdp);
  352. if(fRet) {
  353. m_pDNS_RESOLVER_RECORD = NULL;
  354. m_pISMTPConnection = NULL;
  355. m_dwDiagnostic = ERROR_SUCCESS;
  356. ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
  357. }
  358. Exit:
  359. TraceFunctLeave();
  360. return fRet;
  361. }
  362. DWORD CTcpRegIpList::ConnectsAllowedInProbation()
  363. {
  364. return g_DnsConnectsInProbation;
  365. }
  366. DWORD CTcpRegIpList::ErrorsBeforeFailover()
  367. {
  368. return g_DnsErrorsBeforeFailover;
  369. }
  370. void CTcpRegIpList::LogServerDown(
  371. DWORD dwServerIp,
  372. BOOL fUdp,
  373. DWORD dwErr,
  374. DWORD cUpServers)
  375. {
  376. const CHAR *pszProtocol = NULL;
  377. LPSTR pszServerIp = NULL;
  378. in_addr inAddrIpServer;
  379. const CHAR *apszSubStrings[2];
  380. CHAR szEventKey[32];
  381. int nBytes = 0;
  382. TraceFunctEnterEx((LPARAM)this, "CTcpRegIpList::LogServerDown");
  383. // Log event informing that DNS server is out
  384. CopyMemory(&inAddrIpServer, &dwServerIp, sizeof(DWORD));
  385. pszServerIp = inet_ntoa(inAddrIpServer);
  386. if(!pszServerIp)
  387. {
  388. ErrorTrace((LPARAM)this, "Unable to allocate pszServerIp");
  389. return;
  390. }
  391. pszProtocol = fUdp ? "UDP" : "TCP";
  392. apszSubStrings[0] = pszServerIp;
  393. apszSubStrings[1] = pszProtocol;
  394. //
  395. // Generate a unique key for this event. If a given server goes down
  396. // we will end up logging this warning every 10 minutes (the retry
  397. // time for a down server). This will spam the log. Therefore, we
  398. // call TriggerLogEvent with the PERIODIC flag. This causes all events
  399. // with the same szEventKey to be logged only once per hour. The
  400. // following key is unique for a server-IP/tranport combination.
  401. //
  402. nBytes = _snprintf(szEventKey, sizeof(szEventKey), "DNS %08x %1x",
  403. inAddrIpServer, fUdp ? 1 : 0);
  404. // Guard against future errors. Currently nBytes is guaranteed to be 14
  405. if(nBytes < 0) {
  406. szEventKey[(sizeof(szEventKey)) - 1] = '\0';
  407. _ASSERT(0 && "szEventKey buffer too small");
  408. }
  409. g_EventLog.LogEvent(
  410. SMTP_DNS_SERVER_DOWN, // Message ID
  411. 2, // # of substrings
  412. apszSubStrings, // Substrings
  413. EVENTLOG_WARNING_TYPE, // Type of event
  414. dwErr, // Error code
  415. LOGEVENT_LEVEL_MEDIUM, // Logging level
  416. szEventKey, // Key to this event
  417. LOGEVENT_FLAG_PERIODIC, // Logging option
  418. (-1), // Substring index for dwErr (unused)
  419. GetModuleHandle("SMTPSVC")); // Module
  420. ErrorTrace((LPARAM) this,
  421. "Received error %d connecting to DNS server %s over %s",
  422. dwErr, pszServerIp, pszProtocol);
  423. if(cUpServers == 0) {
  424. // Log this only once an hour
  425. g_EventLog.LogEvent(
  426. SMTP_NO_DNS_SERVERS, // Message ID
  427. 0, // # of substrings
  428. NULL, // Type of event
  429. EVENTLOG_ERROR_TYPE, // Type of event
  430. DNS_ERROR_NO_DNS_SERVERS, // Error code
  431. LOGEVENT_LEVEL_MEDIUM, // Logging level
  432. "DNS No Servers", // Key to this event
  433. LOGEVENT_FLAG_PERIODIC, // Logging option
  434. (-1), // Substring index for dwErr (unused)
  435. GetModuleHandle("SMTPSVC")); // Module
  436. ErrorTrace((LPARAM) this, "All DNS servers are down");
  437. }
  438. TraceFunctLeaveEx((LPARAM)this);
  439. return;
  440. }