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.

659 lines
20 KiB

  1. #include <wininetp.h>
  2. #include <splugin.hxx>
  3. #include "auth.h"
  4. #include "sspspm.h"
  5. #include "winctxt.h"
  6. extern SspData *g_pSspData;
  7. /*-----------------------------------------------------------------------------
  8. PLUG_CTX
  9. -----------------------------------------------------------------------------*/
  10. /*---------------------------------------------------------------------------
  11. Load
  12. ---------------------------------------------------------------------------*/
  13. DWORD PLUG_CTX::Load()
  14. {
  15. INET_ASSERT(_pSPMData == _pCreds->pSPM);
  16. DWORD_PTR dwAuthCode = 0;
  17. dwAuthCode = SSPI_InitScheme (GetScheme());
  18. if (!dwAuthCode)
  19. {
  20. _pSPMData->eState = STATE_ERROR;
  21. return ERROR_WINHTTP_INTERNAL_ERROR;
  22. }
  23. _pSPMData->eState = STATE_LOADED;
  24. return ERROR_SUCCESS;
  25. }
  26. /*---------------------------------------------------------------------------
  27. ClearAuthUser
  28. ---------------------------------------------------------------------------*/
  29. DWORD PLUG_CTX::ClearAuthUser(LPVOID *ppvContext, LPSTR szServer)
  30. {
  31. if (GetState() == AUTHCTX::STATE_LOADED)
  32. {
  33. __try
  34. {
  35. UnloadAuthenticateUser(ppvContext, szServer, GetScheme());
  36. }
  37. __except (EXCEPTION_EXECUTE_HANDLER)
  38. {
  39. DEBUG_PRINT(HTTP, ERROR,
  40. ("UnloadAuthenticateUser call down faulted\n"));
  41. }
  42. ENDEXCEPT
  43. }
  44. *ppvContext = 0;
  45. return ERROR_SUCCESS;
  46. }
  47. /*-----------------------------------------------------------------------------
  48. wQueryHeadersAlloc
  49. Routine Description:
  50. Allocates a HTTP Header String, and queries the HTTP handle for it.
  51. Arguments:
  52. hRequestMapped - An open HTTP request handle
  53. where headers can be quiered
  54. dwQuery - The Query Type to pass to HttpQueryHeaders
  55. lpdwQueryIndex - The Index of the header to pass to HttpQueryHeaders,
  56. make sure to inialize to 0.
  57. lppszOutStr - On success, a pointer to Allocated string with header string,
  58. lpdwSize - size of the string returned in lppszOutStr
  59. Return Value:
  60. DWORD
  61. Success - ERROR_SUCCESS
  62. Failure - One of Several Error codes defined in winerror.h or wininet.w
  63. Comments:
  64. On Error, lppszOutStr may still contain an allocated string that will need to be
  65. freed.
  66. -----------------------------------------------------------------------------*/
  67. DWORD PLUG_CTX::wQueryHeadersAlloc
  68. (
  69. IN HINTERNET hRequestMapped,
  70. IN DWORD dwQuery,
  71. OUT LPDWORD lpdwQueryIndex,
  72. OUT LPSTR *lppszOutStr,
  73. OUT LPDWORD lpdwSize
  74. )
  75. {
  76. LPSTR lpszRawHeaderBuf = NULL;
  77. DWORD dwcbRawHeaderBuf = 0;
  78. DWORD error;
  79. DWORD length;
  80. HTTP_REQUEST_HANDLE_OBJECT * pHttpRequest;
  81. INET_ASSERT(lppszOutStr);
  82. INET_ASSERT(hRequestMapped);
  83. INET_ASSERT(lpdwSize);
  84. INET_ASSERT((dwQuery & HTTP_QUERY_HEADER_MASK) != HTTP_QUERY_CUSTOM);
  85. *lppszOutStr = NULL;
  86. error = ERROR_SUCCESS;
  87. pHttpRequest = (HTTP_REQUEST_HANDLE_OBJECT *) hRequestMapped;
  88. // Attempt to determine whether our header is there.
  89. length = 0;
  90. if (pHttpRequest->QueryInfo(dwQuery, NULL, NULL, &length, lpdwQueryIndex)
  91. != ERROR_INSUFFICIENT_BUFFER)
  92. {
  93. // no authentication happening, we're done
  94. error = ERROR_HTTP_HEADER_NOT_FOUND;
  95. goto quit;
  96. }
  97. // Allocate a Fixed Size Buffer
  98. lpszRawHeaderBuf = (LPSTR) ALLOCATE_MEMORY(LPTR, length);
  99. dwcbRawHeaderBuf = length;
  100. if ( lpszRawHeaderBuf == NULL )
  101. {
  102. error = ERROR_NOT_ENOUGH_MEMORY;
  103. goto quit;
  104. }
  105. error = pHttpRequest->QueryInfo
  106. (dwQuery, NULL, lpszRawHeaderBuf, &dwcbRawHeaderBuf, lpdwQueryIndex);
  107. INET_ASSERT(error != ERROR_INSUFFICIENT_BUFFER );
  108. INET_ASSERT(error != ERROR_HTTP_HEADER_NOT_FOUND );
  109. quit:
  110. if ( error != ERROR_SUCCESS )
  111. {
  112. dwcbRawHeaderBuf = 0;
  113. if ( lpszRawHeaderBuf )
  114. *lpszRawHeaderBuf = '\0';
  115. }
  116. *lppszOutStr = lpszRawHeaderBuf;
  117. *lpdwSize = dwcbRawHeaderBuf;
  118. return error;
  119. }
  120. /*-----------------------------------------------------------------------------
  121. CrackAuthenticationHeader
  122. Routine Description:
  123. Attempts to decode a HTTP 1.1 Authentication header into its
  124. components.
  125. Arguments:
  126. hRequestMapped - Mapped Request handle
  127. fIsProxy - Whether proxy or server auth
  128. lpdwAuthenticationIndex - Index of current HTTP header. ( initally called with 0 )
  129. lppszAuthHeader - allocated pointer which should be freed by client
  130. lppszAuthScheme - Pointer to Authentication scheme string.
  131. lppszRealm - Pointer to Realm string,
  132. lpExtra - Pointer to any Extra String data in the header that is not
  133. part of the Realm
  134. lpdwExtra - Pointer to Size of Extra data.
  135. lppszAuthScheme
  136. Return Value:
  137. DWORD
  138. Success - ERROR_SUCCESS
  139. Failure - ERROR_NOT_ENOUGH_MEMORY,
  140. ERROR_HTTP_HEADER_NOT_FOUND
  141. Comments:
  142. -----------------------------------------------------------------------------*/
  143. DWORD PLUG_CTX::CrackAuthenticationHeader
  144. (
  145. IN HINTERNET hRequestMapped,
  146. IN BOOL fIsProxy,
  147. IN DWORD dwAuthenticationIndex,
  148. IN OUT LPSTR *lppszAuthHeader,
  149. IN OUT LPSTR *lppszExtra,
  150. IN OUT DWORD *lpdwExtra,
  151. OUT LPSTR *lppszAuthScheme
  152. )
  153. {
  154. DWORD error = ERROR_SUCCESS;
  155. LPSTR lpszAuthHeader = NULL;
  156. DWORD cbAuthHeader = 0;
  157. LPSTR lpszExtra = NULL;
  158. LPSTR lpszAuthScheme = NULL;
  159. LPDWORD lpdwAuthenticationIndex = &dwAuthenticationIndex;
  160. INET_ASSERT(lpdwExtra);
  161. INET_ASSERT(lppszExtra);
  162. INET_ASSERT(lpdwAuthenticationIndex);
  163. DWORD dwQuery = fIsProxy?
  164. HTTP_QUERY_PROXY_AUTHENTICATE : HTTP_QUERY_WWW_AUTHENTICATE;
  165. error = wQueryHeadersAlloc (hRequestMapped, dwQuery,
  166. lpdwAuthenticationIndex, &lpszAuthHeader, &cbAuthHeader);
  167. if ( error != ERROR_SUCCESS )
  168. {
  169. INET_ASSERT(*lpdwAuthenticationIndex
  170. || error == ERROR_HTTP_HEADER_NOT_FOUND );
  171. goto quit;
  172. }
  173. //
  174. // Parse Header for Scheme type
  175. //
  176. lpszAuthScheme = lpszAuthHeader;
  177. while ( *lpszAuthScheme == ' ' ) // strip spaces
  178. lpszAuthScheme++;
  179. lpszExtra = strchr(lpszAuthScheme, ' ');
  180. if (lpszExtra)
  181. *lpszExtra++ = '\0';
  182. if (lstrcmpi(GetScheme(), lpszAuthScheme))
  183. {
  184. DEBUG_PRINT(HTTP, ERROR,
  185. ("Authentication: HTTP Scheme has changed!: Scheme=%q\n",
  186. lpszAuthScheme));
  187. goto quit;
  188. }
  189. DEBUG_PRINT (HTTP, INFO,
  190. ("Authentication: found in headers: Scheme=%q, Extra=%q\n",
  191. lpszAuthScheme, lpszExtra));
  192. quit:
  193. *lppszExtra = lpszExtra;
  194. *lpdwExtra = lpszExtra ? lstrlen(lpszExtra) : 0;
  195. *lppszAuthHeader = lpszAuthHeader;
  196. *lppszAuthScheme = lpszAuthScheme;
  197. return error;
  198. }
  199. /*---------------------------------------------------------------------------
  200. ResolveProtocol
  201. ---------------------------------------------------------------------------*/
  202. VOID PLUG_CTX::ResolveProtocol()
  203. {
  204. SECURITY_STATUS ssResult;
  205. PWINCONTEXT pWinContext;
  206. SecPkgContext_NegotiationInfo SecPkgCtxtInfo;
  207. INET_ASSERT(GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE);
  208. SecPkgCtxtInfo.PackageInfo = NULL;
  209. // Call QueryContextAttributes on the context handle.
  210. pWinContext = (PWINCONTEXT) (_pvContext);
  211. ssResult = (*(g_pSspData->pFuncTbl->QueryContextAttributes))
  212. (pWinContext->pSspContextHandle, SECPKG_ATTR_NEGOTIATION_INFO, &SecPkgCtxtInfo);
  213. if (ssResult == SEC_E_OK
  214. && (SecPkgCtxtInfo.NegotiationState == SECPKG_NEGOTIATION_COMPLETE
  215. || (SecPkgCtxtInfo.NegotiationState == SECPKG_NEGOTIATION_OPTIMISTIC)))
  216. {
  217. // Resolve actual auth protocol from package name.
  218. // update both the auth context and Creds entry.
  219. if (!lstrcmpi(SecPkgCtxtInfo.PackageInfo->Name, "NTLM"))
  220. {
  221. _eSubScheme = WINHTTP_AUTH_SCHEME_NTLM;
  222. _dwSubFlags = PLUGIN_AUTH_FLAGS_NO_REALM;
  223. }
  224. else if (!lstrcmpi(SecPkgCtxtInfo.PackageInfo->Name, "Kerberos"))
  225. {
  226. _eSubScheme = WINHTTP_AUTH_SCHEME_KERBEROS;
  227. _dwSubFlags = PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED | PLUGIN_AUTH_FLAGS_NO_REALM;
  228. }
  229. // BUGBUG - This faults.
  230. //
  231. }
  232. if (SecPkgCtxtInfo.PackageInfo)
  233. {
  234. (*(g_pSspData->pFuncTbl->FreeContextBuffer))(SecPkgCtxtInfo.PackageInfo);
  235. }
  236. }
  237. /*---------------------------------------------------------------------------
  238. Constructor
  239. ---------------------------------------------------------------------------*/
  240. PLUG_CTX::PLUG_CTX(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy,
  241. SPMData *pSPM, AUTH_CREDS* pCreds)
  242. : AUTHCTX(pSPM, pCreds)
  243. {
  244. _fIsProxy = fIsProxy;
  245. _pRequest = pRequest;
  246. _szAlloc = NULL;
  247. _szData = NULL;
  248. _cbData = 0;
  249. _pRequest->SetAuthState(AUTHSTATE_NONE);
  250. _fNTLMProxyAuth = _fIsProxy && (GetSchemeType() == WINHTTP_AUTH_SCHEME_NTLM);
  251. _pszFQDN = NULL;
  252. }
  253. /*---------------------------------------------------------------------------
  254. Destructor
  255. ---------------------------------------------------------------------------*/
  256. PLUG_CTX::~PLUG_CTX()
  257. {
  258. if (GetState() == AUTHCTX::STATE_LOADED)
  259. {
  260. if (_pCreds)
  261. {
  262. if (_CtxCriSec.Lock())
  263. {
  264. ClearAuthUser(&_pvContext, _pCreds->lpszHost);
  265. _CtxCriSec.Unlock();
  266. }
  267. }
  268. }
  269. if (_pRequest)
  270. {
  271. _pRequest->SetAuthState(AUTHSTATE_NONE);
  272. }
  273. if (_pszFQDN)
  274. {
  275. FREE_MEMORY(_pszFQDN);
  276. }
  277. }
  278. /*---------------------------------------------------------------------------
  279. PreAuthUser
  280. ---------------------------------------------------------------------------*/
  281. DWORD PLUG_CTX::PreAuthUser(OUT LPSTR pBuf, IN OUT LPDWORD pcbBuf)
  282. {
  283. if (!_CtxCriSec.Lock())
  284. {
  285. return ERROR_NOT_ENOUGH_MEMORY;
  286. }
  287. INET_ASSERT(_pSPMData == _pCreds->pSPM);
  288. DWORD dwError;
  289. SECURITY_STATUS ssResult;
  290. // Make sure the auth provider is loaded.
  291. if (GetState() != AUTHCTX::STATE_LOADED)
  292. {
  293. if (GetState() != AUTHCTX::STATE_ERROR )
  294. Load();
  295. if (GetState() != AUTHCTX::STATE_LOADED)
  296. {
  297. dwError = ERROR_WINHTTP_INTERNAL_ERROR;
  298. goto exit;
  299. }
  300. }
  301. BOOL fCanUseLogon = _fIsProxy
  302. || _pRequest->SilentLogonOK(_pCreds->lpszHost);
  303. LPSTR lpszFQDN = GetFQDN(_pCreds->lpszHost);
  304. LPSTR lpszHostName = lpszFQDN ? lpszFQDN : _pCreds->lpszHost;
  305. __try
  306. {
  307. ssResult = SEC_E_INTERNAL_ERROR;
  308. dwError = PreAuthenticateUser(&_pvContext,
  309. lpszHostName,
  310. GetScheme(),
  311. fCanUseLogon,
  312. 0, // dwFlags
  313. pBuf,
  314. pcbBuf,
  315. _pCreds->lpszUser,
  316. _pCreds->lpszPass,
  317. &ssResult);
  318. // Transit to the correct auth state.
  319. if (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED)
  320. {
  321. if (GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE)
  322. ResolveProtocol();
  323. // Kerberos + SEC_E_OK or SEC_I_CONTINUE_NEEDED transits to challenge.
  324. // Negotiate does not transit to challenge.
  325. // Any other protocol + SEC_E_OK only transits to challenge.
  326. if ((GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS
  327. && (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED))
  328. || (GetSchemeType() != WINHTTP_AUTH_SCHEME_NEGOTIATE && ssResult == SEC_E_OK))
  329. {
  330. _pRequest->SetAuthState(AUTHSTATE_CHALLENGE);
  331. }
  332. }
  333. }
  334. __except(EXCEPTION_EXECUTE_HANDLER)
  335. {
  336. DEBUG_PRINT (HTTP, ERROR, ("preAuthenticateUser call down faulted\n"));
  337. _pSPMData->eState = STATE_ERROR;
  338. dwError = ERROR_WINHTTP_INTERNAL_ERROR;
  339. }
  340. ENDEXCEPT
  341. exit:
  342. _CtxCriSec.Unlock();
  343. return dwError;
  344. }
  345. /*---------------------------------------------------------------------------
  346. UpdateFromHeaders
  347. ---------------------------------------------------------------------------*/
  348. DWORD PLUG_CTX::UpdateFromHeaders(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy)
  349. {
  350. DWORD dwError, cbExtra, dwAuthIdx;
  351. LPSTR szAuthHeader, szExtra, szScheme;
  352. // Get the auth header index corresponding to the scheme of this ctx.
  353. if ((dwError = FindHdrIdxFromScheme(&dwAuthIdx)) != ERROR_SUCCESS)
  354. goto quit;
  355. // Get the scheme and any extra data.
  356. if ((dwError = CrackAuthenticationHeader(pRequest, fIsProxy, dwAuthIdx,
  357. &szAuthHeader, &szExtra, &cbExtra, &szScheme)) != ERROR_SUCCESS)
  358. goto quit;
  359. if (!cbExtra)
  360. _pRequest->SetAuthState(AUTHSTATE_NEGOTIATE);
  361. // Check if auth scheme requires keep-alive.
  362. if (!(GetFlags() & PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED))
  363. {
  364. // if in negotiate phase check if we are going via proxy.
  365. if (pRequest->GetAuthState() == AUTHSTATE_NEGOTIATE)
  366. {
  367. // BUGBUG: if via proxy, we are not going to get keep-alive
  368. // connection to the server. It would be nice if we knew
  369. // a priori the whether proxy would allow us to tunnel to
  370. // http port on the server. Otherwise if we try and fail,
  371. // we look bad vs. other browsers who are ignorant of ntlm
  372. // and fall back to basic.
  373. CHAR szBuffer[64];
  374. DWORD dwBufferLength = sizeof(szBuffer);
  375. DWORD dwIndex = 0;
  376. BOOL fSessionBasedAuth = FALSE;
  377. if (pRequest->QueryResponseHeader(HTTP_QUERY_PROXY_SUPPORT,
  378. szBuffer, &dwBufferLength,
  379. 0, &dwIndex) == ERROR_SUCCESS)
  380. {
  381. if (!_stricmp(szBuffer, "Session-Based-Authentication"))
  382. {
  383. fSessionBasedAuth = TRUE;
  384. }
  385. }
  386. if (!fIsProxy && pRequest->IsRequestUsingProxy()
  387. && !pRequest->IsTalkingToSecureServerViaProxy() && !fSessionBasedAuth)
  388. {
  389. // Ignore NTLM via proxy since we won't get k-a to server.
  390. dwError = ERROR_HTTP_HEADER_NOT_FOUND;
  391. goto quit;
  392. }
  393. }
  394. // Else if in challenge phase, we require a persistent connection.
  395. else
  396. {
  397. // If we don't have a keep-alive connection ...
  398. if (!(pRequest->IsPersistentConnection (fIsProxy)))
  399. {
  400. dwError = ERROR_HTTP_HEADER_NOT_FOUND;
  401. goto quit;
  402. }
  403. }
  404. } // end if keep-alive required
  405. quit:
  406. if (dwError == ERROR_SUCCESS)
  407. {
  408. // If no password cache is set in the auth context,
  409. // find or create one and set it in the handle.
  410. if (!_pCreds)
  411. {
  412. _pCreds = CreateCreds(pRequest, fIsProxy, _pSPMData, NULL);
  413. if (!_pCreds)
  414. {
  415. INET_ASSERT(FALSE);
  416. dwError = ERROR_WINHTTP_INTERNAL_ERROR;
  417. }
  418. else
  419. {
  420. INET_ASSERT(_pCreds->pSPM == _pSPMData);
  421. }
  422. }
  423. }
  424. if (dwError == ERROR_SUCCESS)
  425. {
  426. // Point to allocated data.
  427. _szAlloc = szAuthHeader;
  428. _szData = szExtra;
  429. _cbData = cbExtra;
  430. }
  431. else
  432. {
  433. // Free allocated data.
  434. if (_szAlloc)
  435. delete _szAlloc;
  436. _szAlloc = NULL;
  437. _szData = NULL;
  438. _cbData = 0;
  439. }
  440. // Return of non-success will cancel auth session.
  441. return dwError;
  442. }
  443. /*---------------------------------------------------------------------------
  444. PostAuthUser
  445. ---------------------------------------------------------------------------*/
  446. DWORD PLUG_CTX::PostAuthUser()
  447. {
  448. if (!_CtxCriSec.Lock())
  449. {
  450. return ERROR_NOT_ENOUGH_MEMORY;
  451. }
  452. INET_ASSERT(_pSPMData == _pCreds->pSPM);
  453. DWORD dwError;
  454. // Make sure the auth provider is loaded.
  455. if (GetState() != AUTHCTX::STATE_LOADED)
  456. {
  457. if (GetState() != AUTHCTX::STATE_ERROR )
  458. Load();
  459. if (GetState() != AUTHCTX::STATE_LOADED)
  460. {
  461. dwError = ERROR_WINHTTP_INTERNAL_ERROR;
  462. goto exit;
  463. }
  464. }
  465. BOOL fCanUseLogon = _fIsProxy
  466. || _pRequest->SilentLogonOK(_pCreds->lpszHost);
  467. LPSTR lpszFQDN = GetFQDN(_pCreds->lpszHost);
  468. LPSTR lpszHostName = lpszFQDN ? lpszFQDN : _pCreds->lpszHost;
  469. SECURITY_STATUS ssResult;
  470. __try
  471. {
  472. ssResult = SEC_E_INTERNAL_ERROR;
  473. dwError = AuthenticateUser(&_pvContext,
  474. lpszHostName,
  475. GetScheme(),
  476. fCanUseLogon,
  477. _szData,
  478. _cbData,
  479. _pCreds->lpszUser,
  480. _pCreds->lpszPass,
  481. &ssResult);
  482. // Kerberos package can get into a bad state.
  483. if (GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS && ssResult == SEC_E_WRONG_PRINCIPAL)
  484. dwError = ERROR_WINHTTP_INCORRECT_PASSWORD;
  485. // Transit to the correct auth state.
  486. if (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED)
  487. {
  488. if (GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE)
  489. ResolveProtocol();
  490. // Kerberos + SEC_E_OK or SEC_I_CONTINUE_NEEDED transits to challenge.
  491. // Negotiate does not transit to challenge.
  492. // Any other protocol + SEC_E_OK only transits to challenge.
  493. if ((GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS
  494. && (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED))
  495. || (GetSchemeType() != WINHTTP_AUTH_SCHEME_NEGOTIATE && ssResult == SEC_E_OK))
  496. {
  497. _pRequest->SetAuthState(AUTHSTATE_CHALLENGE);
  498. }
  499. }
  500. }
  501. __except (EXCEPTION_EXECUTE_HANDLER)
  502. {
  503. DEBUG_PRINT (HTTP, ERROR, ("AuthenticateUser faulted!\n"));
  504. dwError = ERROR_BAD_FORMAT;
  505. _pSPMData->eState = STATE_ERROR;
  506. }
  507. ENDEXCEPT
  508. if (_szAlloc)
  509. {
  510. delete _szAlloc;
  511. _szAlloc = NULL;
  512. _szData = NULL;
  513. }
  514. _cbData = 0;
  515. exit:
  516. _CtxCriSec.Unlock();
  517. return dwError;
  518. }
  519. LPSTR PLUG_CTX::GetFQDN(LPSTR lpszHostName)
  520. {
  521. if (lstrcmpi(GetScheme(), "Negotiate")) // only need to get FQDN for Kerberos
  522. {
  523. return NULL;
  524. }
  525. if (_pszFQDN)
  526. {
  527. return _pszFQDN;
  528. }
  529. SERIALIZED_LIST* pResolverCache = GetRootHandle(_pRequest)->GetResolverCache()->GetResolverCacheList();
  530. LPHOSTENT lpHostent;
  531. DWORD TTL;
  532. if (QueryHostentCache(pResolverCache,
  533. (LPSTR)lpszHostName,
  534. NULL,
  535. &lpHostent,
  536. &TTL))
  537. {
  538. _pszFQDN = (lpHostent->h_name ? NewString(lpHostent->h_name) : NULL);
  539. ReleaseHostentCacheEntry(pResolverCache, lpHostent);
  540. return _pszFQDN;
  541. }
  542. return NULL;
  543. }