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.

2273 lines
65 KiB

  1. /*++
  2. Copyright (c) 1987-1996 Microsoft Corporation
  3. Module Name:
  4. subauth.c
  5. Abstract:
  6. Sample SubAuthentication Package.
  7. Author:
  8. Cliff Van Dyke (cliffv) 23-May-1994
  9. Revisions:
  10. Andy Herron (andyhe) 21-Jun-1994 Added code to read domain/user info
  11. Philippe Choquier (phillich) 6-Jun-1996 Adapted for IIS
  12. Environment:
  13. User mode only.
  14. Contains NT-specific code.
  15. Requires ANSI C extensions: slash-slash comments, long external names.
  16. --*/
  17. #if ( _MSC_VER >= 800 )
  18. #pragma warning ( 3 : 4100 ) // enable "Unreferenced formal parameter"
  19. #pragma warning ( 3 : 4219 ) // enable "trailing ',' used for variable argument list"
  20. #pragma warning ( disable : 4005 )
  21. #endif
  22. # include <nt.h>
  23. # include <ntrtl.h>
  24. # include <nturtl.h>
  25. #define WIN32_NO_STATUS
  26. #include <windef.h>
  27. #undef WIN32_NO_STATUS
  28. # include <windows.h>
  29. #include <lmcons.h>
  30. #include <lmaccess.h>
  31. #include <lmapibuf.h>
  32. #include <lsarpc.h>
  33. #include <samrpc.h>
  34. #include <crypt.h>
  35. #include <sspi.h>
  36. #include <secpkg.h>
  37. #include <samisrv.h>
  38. #include <debnot.h>
  39. #include "subauth.h" // local copy :(
  40. #include "md5.h"
  41. UNICODE_STRING EmptyString = { 0, 0, NULL };
  42. BOOLEAN
  43. EqualComputerName(
  44. IN PUNICODE_STRING String1,
  45. IN PUNICODE_STRING String2
  46. );
  47. NTSTATUS
  48. QuerySystemTime (
  49. OUT PLARGE_INTEGER SystemTime
  50. );
  51. BOOL
  52. GetPasswordExpired(
  53. IN LARGE_INTEGER PasswordLastSet,
  54. IN LARGE_INTEGER MaxPasswordAge
  55. );
  56. NTSTATUS
  57. AccountRestrictions(
  58. IN ULONG UserRid,
  59. IN PUNICODE_STRING LogonWorkStation,
  60. IN PUNICODE_STRING WorkStations,
  61. IN PLOGON_HOURS LogonHours,
  62. OUT PLARGE_INTEGER LogoffTime,
  63. OUT PLARGE_INTEGER KickoffTime
  64. );
  65. LARGE_INTEGER
  66. NetpSecondsToDeltaTime(
  67. IN ULONG Seconds
  68. );
  69. VOID
  70. InitUnicodeString(
  71. OUT PUNICODE_STRING DestinationString,
  72. IN PCWSTR SourceString OPTIONAL
  73. );
  74. VOID
  75. CopyUnicodeString(
  76. OUT PUNICODE_STRING DestinationString,
  77. IN PUNICODE_STRING SourceString OPTIONAL
  78. );
  79. BOOL
  80. SubaAllocateString(
  81. STRING *pS,
  82. UINT cS
  83. )
  84. {
  85. if ( pS->Buffer = (CHAR*)RtlAllocateHeap( RtlProcessHeap(),
  86. HEAP_ZERO_MEMORY,
  87. cS ) )
  88. {
  89. pS->MaximumLength = (USHORT)cS;
  90. pS->Length = 0;
  91. return TRUE;
  92. }
  93. pS->MaximumLength = 0;
  94. pS->Length = 0;
  95. return FALSE;
  96. }
  97. NTSTATUS
  98. ToHex16(
  99. LPBYTE pSrc,
  100. PANSI_STRING pDst
  101. )
  102. {
  103. char achH[16*2+1];
  104. UINT x,y;
  105. #define TOAHEX(a) ((a)>=10 ? 'a'+(a)-10 : '0'+(a))
  106. for ( x = 0, y = 0 ; x < 16 ; ++x )
  107. {
  108. UINT v;
  109. v = pSrc[x]>>4;
  110. achH[y++] = TOAHEX( v );
  111. v = pSrc[x]&0x0f;
  112. achH[y++] = TOAHEX( v );
  113. }
  114. achH[y] = '\0';
  115. return RtlAppendAsciizToString( (PSTRING)pDst, achH );
  116. }
  117. BOOL Extract(
  118. CHAR **ppch,
  119. LPSTR* ppszTok
  120. )
  121. {
  122. CHAR *pch = *ppch;
  123. if ( *pch )
  124. {
  125. *ppszTok = pch;
  126. while ( *pch && *pch != '"' )
  127. {
  128. ++pch;
  129. }
  130. if ( *pch == '"' )
  131. {
  132. *pch++ = '\0';
  133. }
  134. *ppch = pch;
  135. return TRUE;
  136. }
  137. return FALSE;
  138. }
  139. BOOL
  140. WINAPI
  141. NetUserCookieW(
  142. LPWSTR lpwszUserName,
  143. UINT cNameLen,
  144. DWORD dwSeed,
  145. LPWSTR lpwszCookieBuff,
  146. DWORD dwBuffSize
  147. )
  148. /*++
  149. Routine Description:
  150. Compute logon validator ( to be used as password )
  151. for IISSuba
  152. Arguments:
  153. lpszUsername -- user name
  154. dwSeed -- start value of cookie
  155. Returns:
  156. TRUE if success, FALSE if error
  157. --*/
  158. {
  159. UINT x,y,v;
  160. #define TOHEX(a) ((a)>=10 ? L'a'+(a)-10 : L'0'+(a))
  161. if ( dwBuffSize < sizeof(dwSeed)*2*sizeof(WCHAR) + sizeof(WCHAR) )
  162. {
  163. SetLastError( ERROR_INSUFFICIENT_BUFFER );
  164. return FALSE;
  165. }
  166. while ( cNameLen-- )
  167. {
  168. dwSeed = ((dwSeed << 5) | ( dwSeed >> 27 )) ^ ((*lpwszUserName++)&0xff);
  169. }
  170. for ( x = 0, y = 0 ; x < sizeof(dwSeed) ; ++x )
  171. {
  172. v = ((LPBYTE)&dwSeed)[x]>>4;
  173. lpwszCookieBuff[y++] = TOHEX( v );
  174. v = ((LPBYTE)&dwSeed)[x]&0x0f;
  175. lpwszCookieBuff[y++] = TOHEX( v );
  176. }
  177. lpwszCookieBuff[y] = '\0';
  178. return TRUE;
  179. }
  180. NTSTATUS
  181. Msv1_0SubAuthenticationRoutineEx(
  182. IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
  183. IN PVOID LogonInformation,
  184. IN ULONG Flags,
  185. IN PUSER_ALL_INFORMATION UserAll,
  186. IN SAM_HANDLE UserHandle,
  187. IN OUT PMSV1_0_VALIDATION_INFO ValidationInfo,
  188. OUT PULONG ActionsPerformed
  189. )
  190. /*++
  191. Routine Description:
  192. The subauthentication routine does client/server specific authentication
  193. of a user. The credentials of the user are passed in addition to all the
  194. information from SAM defining the user. This routine decides whether to
  195. let the user log on.
  196. Arguments:
  197. LogonLevel -- Specifies the level of information given in
  198. LogonInformation.
  199. LogonInformation -- Specifies the description for the user
  200. logging on. The LogonDomainName field should be ignored.
  201. Flags - Flags describing the circumstances of the logon.
  202. MSV1_0_PASSTHRU -- This is a PassThru authenication. (i.e., the
  203. user isn't connecting to this machine.)
  204. MSV1_0_GUEST_LOGON -- This is a retry of the logon using the GUEST
  205. user account.
  206. UserAll -- The description of the user as returned from SAM.
  207. WhichFields -- Returns which fields from UserAllInfo are to be written
  208. back to SAM. The fields will only be written if MSV returns success
  209. to it's caller. Only the following bits are valid.
  210. USER_ALL_PARAMETERS - Write UserAllInfo->Parameters back to SAM. If
  211. the size of the buffer is changed, Msv1_0SubAuthenticationRoutine
  212. must delete the old buffer using MIDL_user_free() and reallocate the
  213. buffer using MIDL_user_allocate().
  214. UserFlags -- Returns UserFlags to be returned from LsaLogonUser in the
  215. LogonProfile. The following bits are currently defined:
  216. LOGON_GUEST -- This was a guest logon
  217. LOGON_NOENCRYPTION -- The caller didn't specify encrypted credentials
  218. SubAuthentication packages should restrict themselves to returning
  219. bits in the high order byte of UserFlags. However, this convention
  220. isn't enforced giving the SubAuthentication package more flexibility.
  221. Authoritative -- Returns whether the status returned is an
  222. authoritative status which should be returned to the original
  223. caller. If not, this logon request may be tried again on another
  224. domain controller. This parameter is returned regardless of the
  225. status code.
  226. LogoffTime - Receives the time at which the user should log off the
  227. system. This time is specified as a GMT relative NT system time.
  228. KickoffTime - Receives the time at which the user should be kicked
  229. off the system. This time is specified as a GMT relative system
  230. time. Specify, a full scale positive number if the user isn't to
  231. be kicked off.
  232. Return Value:
  233. STATUS_SUCCESS: if there was no error.
  234. STATUS_NO_SUCH_USER: The specified user has no account.
  235. STATUS_WRONG_PASSWORD: The password was invalid.
  236. STATUS_INVALID_INFO_CLASS: LogonLevel is invalid.
  237. STATUS_ACCOUNT_LOCKED_OUT: The account is locked out
  238. STATUS_ACCOUNT_DISABLED: The account is disabled
  239. STATUS_ACCOUNT_EXPIRED: The account has expired.
  240. STATUS_PASSWORD_MUST_CHANGE: Account is marked as Password must change
  241. on next logon.
  242. STATUS_PASSWORD_EXPIRED: The Password is expired.
  243. STATUS_INVALID_LOGON_HOURS - The user is not authorized to log on at
  244. this time.
  245. STATUS_INVALID_WORKSTATION - The user is not authorized to log on to
  246. the specified workstation.
  247. --*/
  248. {
  249. NTSTATUS Status;
  250. ULONG UserAccountControl;
  251. LARGE_INTEGER LogonTime;
  252. LARGE_INTEGER PasswordDateSet;
  253. UNICODE_STRING LocalWorkstation;
  254. WCHAR achCookie[64];
  255. ANSI_STRING strA1;
  256. ANSI_STRING strA2;
  257. ANSI_STRING strDigest;
  258. ANSI_STRING AnsiPwd;
  259. MD5_CTX md5;
  260. CHAR *pch;
  261. LPSTR pszRealm;
  262. LPSTR pszUri;
  263. LPSTR pszMethod;
  264. LPSTR pszNonce;
  265. LPSTR pszServerNonce;
  266. LPSTR pszDigest;
  267. LPSTR pszDigestUsername;
  268. LPSTR pszQOP;
  269. LPSTR pszCNonce;
  270. LPSTR pszNC;
  271. PNETLOGON_NETWORK_INFO LogonNetworkInfo;
  272. UINT l;
  273. PUNICODE_STRING pPwd = NULL;
  274. UNICODE_STRING PackageName;
  275. UNICODE_STRING UserPwd;
  276. VOID *pvPlainPwd = NULL;
  277. ULONG ulLength = 0;
  278. BOOL fNTDigest = FALSE;
  279. CHAR achAnsiPwdBuffer[MAX_PASSWD_LEN + 1];
  280. strA1.Buffer = NULL;
  281. strA2.Buffer = NULL;
  282. strDigest.Buffer = NULL;
  283. AnsiPwd.Buffer = achAnsiPwdBuffer;
  284. AnsiPwd.Length = AnsiPwd.MaximumLength = MAX_PASSWD_LEN;
  285. //
  286. // Check whether the SubAuthentication package supports this type
  287. // of logon.
  288. //
  289. (VOID) QuerySystemTime( &LogonTime );
  290. switch ( LogonLevel ) {
  291. case NetlogonInteractiveInformation:
  292. case NetlogonServiceInformation:
  293. //
  294. // This SubAuthentication package only supports network logons.
  295. //
  296. return STATUS_INVALID_INFO_CLASS;
  297. case NetlogonNetworkInformation:
  298. //
  299. // This SubAuthentication package doesn't support access via machine
  300. // accounts.
  301. //
  302. UserAccountControl = USER_NORMAL_ACCOUNT;
  303. //
  304. // Local user (Temp Duplicate) accounts are only used on the machine
  305. // being directly logged onto.
  306. // (Nor are interactive or service logons allowed to them.)
  307. //
  308. if ( (Flags & MSV1_0_PASSTHRU) == 0 ) {
  309. UserAccountControl |= USER_TEMP_DUPLICATE_ACCOUNT;
  310. }
  311. LogonNetworkInfo = (PNETLOGON_NETWORK_INFO) LogonInformation;
  312. break;
  313. default:
  314. return STATUS_INVALID_INFO_CLASS;
  315. }
  316. //
  317. // Check the password.
  318. //
  319. #define IIS_SUBAUTH_SEED 0x8467fd31
  320. switch( ((WCHAR*)(LogonNetworkInfo->NtChallengeResponse.Buffer))[0] )
  321. {
  322. case L'0':
  323. if ( !NetUserCookieW( LogonNetworkInfo->Identity.UserName.Buffer,
  324. LogonNetworkInfo->Identity.UserName.Length/sizeof(WCHAR),
  325. IIS_SUBAUTH_SEED,
  326. achCookie,
  327. sizeof(achCookie ))
  328. || memcmp( (LPBYTE)achCookie,
  329. ((WCHAR*)LogonNetworkInfo->NtChallengeResponse.Buffer)+2,
  330. wcslen(achCookie)*sizeof(WCHAR)
  331. ) )
  332. {
  333. wrong_pwd:
  334. Status = STATUS_WRONG_PASSWORD;
  335. }
  336. else
  337. {
  338. Status = STATUS_SUCCESS;
  339. }
  340. goto Cleanup;
  341. break;
  342. case L'1':
  343. // NTLM digest authentication
  344. fNTDigest = TRUE;
  345. //fall through
  346. case L'2':
  347. //"Normal" digest authentication
  348. // break fields
  349. pch = LogonNetworkInfo->LmChallengeResponse.Buffer;
  350. if ( !Extract( &pch, &pszRealm ) || // skip 1st field
  351. !Extract( &pch, &pszRealm ) ||
  352. !Extract( &pch, &pszUri ) ||
  353. !Extract( &pch, &pszMethod ) ||
  354. !Extract( &pch, &pszNonce ) ||
  355. !Extract( &pch, &pszServerNonce ) ||
  356. !Extract( &pch, &pszDigest ) ||
  357. !Extract( &pch, &pszDigestUsername ) ||
  358. !Extract( &pch, &pszQOP ) ||
  359. !Extract( &pch, &pszCNonce ) ||
  360. !Extract( &pch, &pszNC ) )
  361. {
  362. Status = STATUS_ACCESS_DENIED;
  363. goto Cleanup;
  364. }
  365. //
  366. // For NTLM Digest, use the NT hash of the password passed in
  367. // For 'normal' Digest, use the cleartext
  368. //
  369. if ( fNTDigest )
  370. {
  371. if ( UserAll->NtPasswordPresent )
  372. {
  373. pPwd = &UserAll->NtPassword;
  374. }
  375. else if ( UserAll->LmPasswordPresent )
  376. {
  377. pPwd = &UserAll->LmPassword;
  378. }
  379. else
  380. {
  381. pPwd = &EmptyString;
  382. }
  383. }
  384. else
  385. {
  386. //
  387. // Retrieve the plaintext password
  388. //
  389. // NOTE : On NT 5, this API only works on Domain Controllers !!
  390. //
  391. PackageName.Buffer = SAM_CLEARTEXT_CREDENTIAL_NAME;
  392. PackageName.Length = PackageName.MaximumLength = (USHORT)
  393. wcslen( SAM_CLEARTEXT_CREDENTIAL_NAME ) * sizeof(WCHAR);
  394. Status = SamIRetrievePrimaryCredentials( (SAMPR_HANDLE) UserHandle,
  395. &PackageName,
  396. &pvPlainPwd,
  397. &ulLength );
  398. if ( !NT_SUCCESS( Status ) )
  399. {
  400. #if DBG
  401. CHAR achErrorString[256];
  402. wsprintf(achErrorString, "Failed to retrieve plaintext password, error 0x%x\n",
  403. Status);
  404. OutputDebugString(achErrorString);
  405. #endif //DBG
  406. //
  407. // Explicitly set the status to be "wrong password" instead of whatever
  408. // is returned by SamIRetrievePrimaryCredentials
  409. //
  410. Status = STATUS_WRONG_PASSWORD;
  411. goto Cleanup;
  412. }
  413. else
  414. {
  415. PSAMPR_USER_INFO_BUFFER pUserInfo = NULL;
  416. //
  417. // Need to differentiate between an empty password and
  418. // a non-existant/unaccessible password
  419. //
  420. if ( ulLength == 0 )
  421. {
  422. Status = SamrQueryInformationUser( UserHandle,
  423. UserAllInformation,
  424. &pUserInfo );
  425. if ( !NT_SUCCESS( Status ) )
  426. {
  427. Status = STATUS_ACCESS_DENIED;
  428. goto Cleanup;
  429. }
  430. if ( pUserInfo->All.LmPasswordPresent ||
  431. pUserInfo->All.NtPasswordPresent )
  432. {
  433. Status = STATUS_WRONG_PASSWORD;
  434. SamIFree_SAMPR_USER_INFO_BUFFER( pUserInfo,
  435. UserAllInformation );
  436. goto Cleanup;
  437. }
  438. SamIFree_SAMPR_USER_INFO_BUFFER( pUserInfo,
  439. UserAllInformation );
  440. }
  441. UserPwd.Buffer = (USHORT *) pvPlainPwd;
  442. UserPwd.Length = UserPwd.MaximumLength = (USHORT) ulLength;
  443. }
  444. //
  445. // The unicode password has to be converted to ANSI
  446. //
  447. if ( !NT_SUCCESS( Status = RtlUnicodeStringToAnsiString( &AnsiPwd,
  448. &UserPwd,
  449. FALSE ) ) )
  450. {
  451. goto Cleanup;
  452. }
  453. }
  454. //
  455. // A1 = username:realm:password
  456. //
  457. SubaAllocateString( &strA1, strlen( pszDigestUsername ) +
  458. //wcslen(UserAll->UserName.Buffer) +
  459. strlen(pszRealm) +
  460. ( fNTDigest ? 32 : AnsiPwd.Length ) +
  461. +2 +1 +32 );
  462. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, pszDigestUsername ) ) ||
  463. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, ":" ) ) ||
  464. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, pszRealm ) ) ||
  465. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, ":" )) ||
  466. (fNTDigest ? !NT_SUCCESS( Status = ToHex16( (LPBYTE)(pPwd->Buffer), &strA1 ) ) :
  467. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strA1, AnsiPwd.Buffer ) ) ) )
  468. {
  469. goto Cleanup;
  470. }
  471. //
  472. // A2 = Method:URI
  473. //
  474. if ( !SubaAllocateString( &strA2, strlen(pszMethod)+1+strlen(pszUri)+1+32 ) )
  475. {
  476. Status = STATUS_NO_MEMORY;
  477. goto Cleanup;
  478. }
  479. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, pszMethod ) ) ||
  480. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, ":" ) ) ||
  481. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, pszUri ) ) )
  482. {
  483. goto Cleanup;
  484. }
  485. if ( !SubaAllocateString( &strDigest, 32 + 1 + strlen(pszNonce) + 1 + 32 +1 +32 + strlen(pszCNonce) + 32) )
  486. {
  487. Status = STATUS_NO_MEMORY;
  488. goto Cleanup;
  489. }
  490. //
  491. // build response digest as per Digest Auth spec
  492. // Response Digest = KD( H(A1), nonce : H(A2) )
  493. // = H( H(A1) : nonce : H(A2) )
  494. // In our case, the hash function is MD5
  495. //
  496. // H(A1)
  497. MD5Init( &md5 );
  498. MD5Update( &md5, (LPBYTE)strA1.Buffer, strA1.Length );
  499. MD5Final( &md5 );
  500. if ( !NT_SUCCESS( Status = ToHex16( md5.digest, &strDigest ) ) )
  501. {
  502. goto Cleanup;
  503. }
  504. // ":" nonce ":"
  505. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, ":" ) ) ||
  506. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, pszNonce ) ) ||
  507. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, ":" ) ) )
  508. {
  509. goto Cleanup;
  510. }
  511. if ( strcmp( pszQOP, "none" ) )
  512. {
  513. if ( strcmp( pszQOP, "auth" ) )
  514. {
  515. Status = STATUS_ACCESS_DENIED;
  516. goto Cleanup;
  517. }
  518. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, pszNC ) ) ||
  519. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, ":" ) ) ||
  520. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, pszCNonce ) ) ||
  521. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, ":" ) ) ||
  522. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, pszQOP ) ) ||
  523. !NT_SUCCESS( Status = RtlAppendAsciizToString( &strDigest, ":" ) ) )
  524. {
  525. goto Cleanup;
  526. }
  527. }
  528. // H(A2)
  529. MD5Init( &md5 );
  530. MD5Update( &md5, (LPBYTE)strA2.Buffer, strA2.Length );
  531. MD5Final( &md5 );
  532. if ( !NT_SUCCESS( ToHex16( md5.digest, &strDigest ) ) )
  533. {
  534. goto Cleanup;
  535. }
  536. // H( H(A1) ":" nonce ":" H(A2) ) if QOP not set
  537. // H( H(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" H(A2) if set
  538. MD5Init( &md5 );
  539. MD5Update( &md5, (LPBYTE)strDigest.Buffer, strDigest.Length );
  540. MD5Final( &md5 );
  541. strDigest.Length = 0;
  542. if ( !NT_SUCCESS( Status = ToHex16( md5.digest, &strDigest ) ) )
  543. {
  544. goto Cleanup;
  545. }
  546. if ( memcmp( strDigest.Buffer, pszDigest, strDigest.Length ) )
  547. {
  548. Status = STATUS_WRONG_PASSWORD;
  549. goto Cleanup;
  550. }
  551. else
  552. {
  553. Status = STATUS_SUCCESS;
  554. goto Cleanup;
  555. }
  556. // checking for stalled nonce must be made by caller
  557. break;
  558. default:
  559. goto wrong_pwd;
  560. }
  561. //
  562. // Cleanup up before returning.
  563. //
  564. Cleanup:
  565. if ( strA1.Buffer )
  566. {
  567. RtlFreeHeap(RtlProcessHeap(), 0, strA1.Buffer );
  568. }
  569. if ( strA2.Buffer )
  570. {
  571. RtlFreeHeap(RtlProcessHeap(), 0, strA2.Buffer );
  572. }
  573. if ( strDigest.Buffer )
  574. {
  575. RtlFreeHeap(RtlProcessHeap(), 0, strDigest.Buffer );
  576. }
  577. if ( pvPlainPwd )
  578. {
  579. RtlFreeHeap(RtlProcessHeap(), 0, pvPlainPwd);
  580. }
  581. //
  582. // the only thing we did was check the password
  583. //
  584. ValidationInfo;
  585. UserHandle;
  586. *ActionsPerformed = MSV1_0_SUBAUTH_PASSWORD;
  587. return Status;
  588. } // Msv1_0SubAuthenticationRoutineEx
  589. BOOL
  590. GetPasswordExpired (
  591. IN LARGE_INTEGER PasswordLastSet,
  592. IN LARGE_INTEGER MaxPasswordAge
  593. )
  594. /*++
  595. Routine Description:
  596. This routine returns true if the password is expired, false otherwise.
  597. Arguments:
  598. PasswordLastSet - Time when the password was last set for this user.
  599. MaxPasswordAge - Maximum password age for any password in the domain.
  600. Return Value:
  601. Returns true if password is expired. False if not expired.
  602. --*/
  603. {
  604. LARGE_INTEGER PasswordMustChange;
  605. NTSTATUS Status;
  606. BOOLEAN rc;
  607. LARGE_INTEGER TimeNow;
  608. //
  609. // Compute the expiration time as the time the password was
  610. // last set plus the maximum age.
  611. //
  612. if ( PasswordLastSet.QuadPart < 0 || MaxPasswordAge.QuadPart > 0 ) {
  613. rc = TRUE; // default for invalid times is that it is expired.
  614. } else {
  615. __try {
  616. PasswordMustChange.QuadPart =
  617. PasswordLastSet.QuadPart - MaxPasswordAge.QuadPart;
  618. //
  619. // Limit the resultant time to the maximum valid absolute time
  620. //
  621. if ( PasswordMustChange.QuadPart < 0 ) {
  622. rc = FALSE;
  623. } else {
  624. Status = QuerySystemTime( &TimeNow );
  625. if (NT_SUCCESS(Status)) {
  626. if ( TimeNow.QuadPart >= PasswordMustChange.QuadPart ) {
  627. rc = TRUE;
  628. } else {
  629. rc = FALSE;
  630. }
  631. } else {
  632. rc = FALSE; // won't fail if QuerySystemTime failed.
  633. }
  634. }
  635. } __except(EXCEPTION_EXECUTE_HANDLER) {
  636. rc = TRUE;
  637. }
  638. }
  639. return rc;
  640. } // GetPasswordExpired
  641. NTSTATUS
  642. QuerySystemTime (
  643. OUT PLARGE_INTEGER SystemTime
  644. )
  645. /*++
  646. Routine Description:
  647. This function returns the absolute system time. The time is in units of
  648. 100nsec ticks since the base time which is midnight January 1, 1601.
  649. Arguments:
  650. SystemTime - Supplies the address of a variable that will receive the
  651. current system time.
  652. Return Value:
  653. STATUS_SUCCESS is returned if the service is successfully executed.
  654. STATUS_ACCESS_VIOLATION is returned if the output parameter for the
  655. system time cannot be written.
  656. --*/
  657. {
  658. SYSTEMTIME CurrentTime;
  659. GetSystemTime( &CurrentTime );
  660. if ( !SystemTimeToFileTime( &CurrentTime, (LPFILETIME) SystemTime ) ) {
  661. return STATUS_ACCESS_VIOLATION;
  662. }
  663. return STATUS_SUCCESS;
  664. }
  665. NTSTATUS
  666. SampMatchworkstation(
  667. IN PUNICODE_STRING LogonWorkStation,
  668. IN PUNICODE_STRING WorkStations
  669. )
  670. /*++
  671. Routine Description:
  672. Check if the given workstation is a member of the list of workstations
  673. given.
  674. Arguments:
  675. LogonWorkStations - UNICODE name of the workstation that the user is
  676. trying to log into.
  677. WorkStations - API list of workstations that the user is allowed to
  678. log into.
  679. Return Value:
  680. STATUS_SUCCESS - The user is allowed to log into the workstation.
  681. --*/
  682. {
  683. PWCHAR WorkStationName;
  684. UNICODE_STRING Unicode;
  685. NTSTATUS NtStatus;
  686. WCHAR Buffer[256];
  687. USHORT LocalBufferLength = 256;
  688. UNICODE_STRING WorkStationsListCopy;
  689. BOOLEAN BufferAllocated = FALSE;
  690. PWCHAR TmpBuffer;
  691. //
  692. // Local workstation is always allowed
  693. // If WorkStations field is 0 everybody is allowed
  694. //
  695. if ( ( LogonWorkStation == NULL ) ||
  696. ( LogonWorkStation->Length == 0 ) ||
  697. ( WorkStations->Length == 0 ) ) {
  698. return( STATUS_SUCCESS );
  699. }
  700. //
  701. // Assume failure; change status only if we find the string.
  702. //
  703. NtStatus = STATUS_INVALID_WORKSTATION;
  704. //
  705. // WorkStationApiList points to our current location in the list of
  706. // WorkStations.
  707. //
  708. if ( WorkStations->Length > LocalBufferLength ) {
  709. WorkStationsListCopy.Buffer = LocalAlloc( 0, WorkStations->Length );
  710. BufferAllocated = TRUE;
  711. if ( WorkStationsListCopy.Buffer == NULL ) {
  712. NtStatus = STATUS_INSUFFICIENT_RESOURCES;
  713. return( NtStatus );
  714. }
  715. WorkStationsListCopy.MaximumLength = WorkStations->Length;
  716. } else {
  717. WorkStationsListCopy.Buffer = Buffer;
  718. WorkStationsListCopy.MaximumLength = LocalBufferLength;
  719. }
  720. CopyUnicodeString( &WorkStationsListCopy, WorkStations );
  721. //
  722. // wcstok requires a string the first time it's called, and NULL
  723. // for all subsequent calls. Use a temporary variable so we
  724. // can do this.
  725. //
  726. TmpBuffer = WorkStationsListCopy.Buffer;
  727. while( WorkStationName = wcstok(TmpBuffer, L",") ) {
  728. TmpBuffer = NULL;
  729. InitUnicodeString( &Unicode, WorkStationName );
  730. if (EqualComputerName( &Unicode, LogonWorkStation )) {
  731. NtStatus = STATUS_SUCCESS;
  732. break;
  733. }
  734. }
  735. if ( BufferAllocated ) {
  736. LocalFree( WorkStationsListCopy.Buffer );
  737. }
  738. return( NtStatus );
  739. }
  740. NTSTATUS
  741. AccountRestrictions(
  742. IN ULONG UserRid,
  743. IN PUNICODE_STRING LogonWorkStation,
  744. IN PUNICODE_STRING WorkStations,
  745. IN PLOGON_HOURS LogonHours,
  746. OUT PLARGE_INTEGER LogoffTime,
  747. OUT PLARGE_INTEGER KickoffTime
  748. )
  749. /*++
  750. Routine Description:
  751. Validate a user's ability to log on at this time and at the workstation
  752. being logged onto.
  753. Arguments:
  754. UserRid - The user id of the user to operate on.
  755. LogonWorkStation - The name of the workstation the logon is being
  756. attempted at.
  757. WorkStations - The list of workstations the user may log on to. This
  758. information comes from the user's account information. It must
  759. be in API list format.
  760. LogonHours - The times the user may logon. This information comes
  761. from the user's account information.
  762. LogoffTime - Receives the time at which the user should log off the
  763. system.
  764. KickoffTime - Receives the time at which the user should be kicked
  765. off the system.
  766. Return Value:
  767. STATUS_SUCCESS - Logon is permitted.
  768. STATUS_INVALID_LOGON_HOURS - The user is not authorized to log on at
  769. this time.
  770. STATUS_INVALID_WORKSTATION - The user is not authorized to log on to
  771. the specified workstation.
  772. --*/
  773. {
  774. static BOOLEAN GetForceLogoff = TRUE;
  775. static LARGE_INTEGER ForceLogoff = { 0x7fffffff, 0xFFFFFFF};
  776. #define MILLISECONDS_PER_WEEK 7 * 24 * 60 * 60 * 1000
  777. SYSTEMTIME CurrentTimeFields;
  778. LARGE_INTEGER CurrentTime, CurrentUTCTime;
  779. LARGE_INTEGER MillisecondsIntoWeekXUnitsPerWeek;
  780. LARGE_INTEGER LargeUnitsIntoWeek;
  781. LARGE_INTEGER Delta100Ns;
  782. NTSTATUS NtStatus = STATUS_SUCCESS;
  783. ULONG CurrentMsIntoWeek;
  784. ULONG LogoffMsIntoWeek;
  785. ULONG DeltaMs;
  786. ULONG MillisecondsPerUnit;
  787. ULONG CurrentUnitsIntoWeek;
  788. ULONG LogoffUnitsIntoWeek;
  789. USHORT i;
  790. TIME_ZONE_INFORMATION TimeZoneInformation;
  791. DWORD TimeZoneId;
  792. LARGE_INTEGER BiasIn100NsUnits;
  793. LONG BiasInMinutes;
  794. //
  795. // Only check for users other than the builtin ADMIN
  796. //
  797. if ( UserRid != DOMAIN_USER_RID_ADMIN) {
  798. //
  799. // Scan to make sure the workstation being logged into is in the
  800. // list of valid workstations - or if the list of valid workstations
  801. // is null, which means that all are valid.
  802. //
  803. NtStatus = SampMatchworkstation( LogonWorkStation, WorkStations );
  804. if ( NT_SUCCESS( NtStatus ) ) {
  805. //
  806. // Check to make sure that the current time is a valid time to log
  807. // on in the LogonHours.
  808. //
  809. // We need to validate the time taking into account whether we are
  810. // in daylight savings time or standard time. Thus, if the logon
  811. // hours specify that we are able to log on between 9am and 5pm,
  812. // this means 9am to 5pm standard time during the standard time
  813. // period, and 9am to 5pm daylight savings time when in the
  814. // daylight savings time. Since the logon hours stored by SAM are
  815. // independent of daylight savings time, we need to add in the
  816. // difference between standard time and daylight savings time to
  817. // the current time before checking whether this time is a valid
  818. // time to log on. Since this difference (or bias as it is called)
  819. // is actually held in the form
  820. //
  821. // Standard time = Daylight savings time + Bias
  822. //
  823. // the Bias is a negative number. Thus we actually subtract the
  824. // signed Bias from the Current Time.
  825. //
  826. // First, get the Time Zone Information.
  827. //
  828. TimeZoneId = GetTimeZoneInformation(
  829. (LPTIME_ZONE_INFORMATION) &TimeZoneInformation
  830. );
  831. //
  832. // Next, get the appropriate bias (signed integer in minutes) to subtract from
  833. // the Universal Time Convention (UTC) time returned by NtQuerySystemTime
  834. // to get the local time. The bias to be used depends whether we're
  835. // in Daylight Savings time or Standard Time as indicated by the
  836. // TimeZoneId parameter.
  837. //
  838. // local time = UTC time - bias in 100Ns units
  839. //
  840. switch (TimeZoneId) {
  841. case TIME_ZONE_ID_UNKNOWN:
  842. //
  843. // There is no differentiation between standard and
  844. // daylight savings time. Proceed as for Standard Time
  845. //
  846. BiasInMinutes = TimeZoneInformation.StandardBias;
  847. break;
  848. case TIME_ZONE_ID_STANDARD:
  849. BiasInMinutes = TimeZoneInformation.StandardBias;
  850. break;
  851. case TIME_ZONE_ID_DAYLIGHT:
  852. BiasInMinutes = TimeZoneInformation.DaylightBias;
  853. break;
  854. default:
  855. //
  856. // Something is wrong with the time zone information. Fail
  857. // the logon request.
  858. //
  859. NtStatus = STATUS_INVALID_LOGON_HOURS;
  860. break;
  861. }
  862. if (NT_SUCCESS(NtStatus)) {
  863. //
  864. // Convert the Bias from minutes to 100ns units
  865. //
  866. BiasIn100NsUnits.QuadPart = ((LONGLONG)BiasInMinutes)
  867. * 60 * 10000000;
  868. //
  869. // Get the UTC time in 100Ns units used by Windows Nt. This
  870. // time is GMT.
  871. //
  872. NtStatus = QuerySystemTime( &CurrentUTCTime );
  873. }
  874. if ( NT_SUCCESS( NtStatus ) ) {
  875. CurrentTime.QuadPart = CurrentUTCTime.QuadPart -
  876. BiasIn100NsUnits.QuadPart;
  877. FileTimeToSystemTime( (PFILETIME)&CurrentTime, &CurrentTimeFields );
  878. CurrentMsIntoWeek = (((( CurrentTimeFields.wDayOfWeek * 24 ) +
  879. CurrentTimeFields.wHour ) * 60 +
  880. CurrentTimeFields.wMinute ) * 60 +
  881. CurrentTimeFields.wSecond ) * 1000 +
  882. CurrentTimeFields.wMilliseconds;
  883. MillisecondsIntoWeekXUnitsPerWeek.QuadPart =
  884. ((LONGLONG)CurrentMsIntoWeek) *
  885. ((LONGLONG)LogonHours->UnitsPerWeek);
  886. LargeUnitsIntoWeek.QuadPart =
  887. MillisecondsIntoWeekXUnitsPerWeek.QuadPart / ((ULONG) MILLISECONDS_PER_WEEK);
  888. CurrentUnitsIntoWeek = LargeUnitsIntoWeek.LowPart;
  889. if ( !( LogonHours->LogonHours[ CurrentUnitsIntoWeek / 8] &
  890. ( 0x01 << ( CurrentUnitsIntoWeek % 8 ) ) ) ) {
  891. NtStatus = STATUS_INVALID_LOGON_HOURS;
  892. } else {
  893. //
  894. // Determine the next time that the user is NOT supposed to be logged
  895. // in, and return that as LogoffTime.
  896. //
  897. i = 0;
  898. LogoffUnitsIntoWeek = CurrentUnitsIntoWeek;
  899. do {
  900. i++;
  901. LogoffUnitsIntoWeek = ( LogoffUnitsIntoWeek + 1 ) % LogonHours->UnitsPerWeek;
  902. } while ( ( i <= LogonHours->UnitsPerWeek ) &&
  903. ( LogonHours->LogonHours[ LogoffUnitsIntoWeek / 8 ] &
  904. ( 0x01 << ( LogoffUnitsIntoWeek % 8 ) ) ) );
  905. if ( i > LogonHours->UnitsPerWeek ) {
  906. //
  907. // All times are allowed, so there's no logoff
  908. // time. Return forever for both LogoffTime and
  909. // KickoffTime.
  910. //
  911. LogoffTime->HighPart = 0x7FFFFFFF;
  912. LogoffTime->LowPart = 0xFFFFFFFF;
  913. KickoffTime->HighPart = 0x7FFFFFFF;
  914. KickoffTime->LowPart = 0xFFFFFFFF;
  915. } else {
  916. //
  917. // LogoffUnitsIntoWeek points at which time unit the
  918. // user is to log off. Calculate actual time from
  919. // the unit, and return it.
  920. //
  921. // CurrentTimeFields already holds the current
  922. // time for some time during this week; just adjust
  923. // to the logoff time during this week and convert
  924. // to time format.
  925. //
  926. MillisecondsPerUnit = MILLISECONDS_PER_WEEK / LogonHours->UnitsPerWeek;
  927. LogoffMsIntoWeek = MillisecondsPerUnit * LogoffUnitsIntoWeek;
  928. if ( LogoffMsIntoWeek < CurrentMsIntoWeek ) {
  929. DeltaMs = MILLISECONDS_PER_WEEK - ( CurrentMsIntoWeek - LogoffMsIntoWeek );
  930. } else {
  931. DeltaMs = LogoffMsIntoWeek - CurrentMsIntoWeek;
  932. }
  933. Delta100Ns.QuadPart = (LONGLONG) DeltaMs * 10000;
  934. LogoffTime->QuadPart = CurrentUTCTime.QuadPart +
  935. Delta100Ns.QuadPart;
  936. //
  937. // Grab the domain's ForceLogoff time.
  938. //
  939. if ( GetForceLogoff ) {
  940. NET_API_STATUS NetStatus;
  941. LPUSER_MODALS_INFO_0 UserModals0;
  942. NetStatus = NetUserModalsGet( NULL,
  943. 0,
  944. (LPBYTE *)&UserModals0 );
  945. if ( NetStatus == 0 ) {
  946. GetForceLogoff = FALSE;
  947. ForceLogoff = NetpSecondsToDeltaTime( UserModals0->usrmod0_force_logoff );
  948. NetApiBufferFree( UserModals0 );
  949. }
  950. }
  951. //
  952. // Subtract Domain->ForceLogoff from LogoffTime, and return
  953. // that as KickoffTime. Note that Domain->ForceLogoff is a
  954. // negative delta. If its magnitude is sufficiently large
  955. // (in fact, larger than the difference between LogoffTime
  956. // and the largest positive large integer), we'll get overflow
  957. // resulting in a KickOffTime that is negative. In this
  958. // case, reset the KickOffTime to this largest positive
  959. // large integer (i.e. "never") value.
  960. //
  961. KickoffTime->QuadPart = LogoffTime->QuadPart - ForceLogoff.QuadPart;
  962. if (KickoffTime->QuadPart < 0) {
  963. KickoffTime->HighPart = 0x7FFFFFFF;
  964. KickoffTime->LowPart = 0xFFFFFFFF;
  965. }
  966. }
  967. }
  968. }
  969. }
  970. } else {
  971. //
  972. // Never kick administrators off
  973. //
  974. LogoffTime->HighPart = 0x7FFFFFFF;
  975. LogoffTime->LowPart = 0xFFFFFFFF;
  976. KickoffTime->HighPart = 0x7FFFFFFF;
  977. KickoffTime->LowPart = 0xFFFFFFFF;
  978. }
  979. return( NtStatus );
  980. }
  981. LARGE_INTEGER
  982. NetpSecondsToDeltaTime(
  983. IN ULONG Seconds
  984. )
  985. /*++
  986. Routine Description:
  987. Convert a number of seconds to an NT delta time specification
  988. Arguments:
  989. Seconds - a positive number of seconds
  990. Return Value:
  991. Returns the NT Delta time. NT delta time is a negative number
  992. of 100ns units.
  993. --*/
  994. {
  995. LARGE_INTEGER DeltaTime;
  996. LARGE_INTEGER LargeSeconds;
  997. LARGE_INTEGER Answer;
  998. //
  999. // Special case TIMEQ_FOREVER (return a full scale negative)
  1000. //
  1001. if ( Seconds == TIMEQ_FOREVER ) {
  1002. DeltaTime.LowPart = 0;
  1003. DeltaTime.HighPart = (LONG) 0x80000000;
  1004. //
  1005. // Convert seconds to 100ns units simply by multiplying by 10000000.
  1006. //
  1007. // Convert to delta time by negating.
  1008. //
  1009. } else {
  1010. LargeSeconds.LowPart = Seconds;
  1011. LargeSeconds.HighPart = 0;
  1012. Answer.QuadPart = LargeSeconds.QuadPart * 10000000;
  1013. if ( Answer.QuadPart < 0 ) {
  1014. DeltaTime.LowPart = 0;
  1015. DeltaTime.HighPart = (LONG) 0x80000000;
  1016. } else {
  1017. DeltaTime.QuadPart = -Answer.QuadPart;
  1018. }
  1019. }
  1020. return DeltaTime;
  1021. } // NetpSecondsToDeltaTime
  1022. BOOLEAN
  1023. EqualComputerName(
  1024. IN PUNICODE_STRING String1,
  1025. IN PUNICODE_STRING String2
  1026. )
  1027. /*++
  1028. Routine Description:
  1029. Compare two computer names for equality.
  1030. Arguments:
  1031. String1 - Name of first computer.
  1032. String2 - Name of second computer.
  1033. Return Value:
  1034. TRUE if the names, converted to OEM, compare case-insensitively,
  1035. FALSE if they don't compare or can't be converted to OEM.
  1036. --*/
  1037. {
  1038. WCHAR Computer1[CNLEN+1];
  1039. WCHAR Computer2[CNLEN+1];
  1040. CHAR OemComputer1[CNLEN+1];
  1041. CHAR OemComputer2[CNLEN+1];
  1042. //
  1043. // Make sure the names are not too long
  1044. //
  1045. if ((String1->Length > CNLEN*sizeof(WCHAR)) ||
  1046. (String2->Length > CNLEN*sizeof(WCHAR))) {
  1047. return(FALSE);
  1048. }
  1049. //
  1050. // Copy them to null terminated strings
  1051. //
  1052. CopyMemory(
  1053. Computer1,
  1054. String1->Buffer,
  1055. String1->Length
  1056. );
  1057. Computer1[String1->Length/sizeof(WCHAR)] = L'\0';
  1058. CopyMemory(
  1059. Computer2,
  1060. String2->Buffer,
  1061. String2->Length
  1062. );
  1063. Computer2[String2->Length/sizeof(WCHAR)] = L'\0';
  1064. //
  1065. // Convert the computer names to OEM
  1066. //
  1067. if (!CharToOemW(
  1068. Computer1,
  1069. OemComputer1
  1070. )) {
  1071. return(FALSE);
  1072. }
  1073. if (!CharToOemW(
  1074. Computer2,
  1075. OemComputer2
  1076. )) {
  1077. return(FALSE);
  1078. }
  1079. //
  1080. // Do a case insensitive comparison of the oem computer names.
  1081. //
  1082. if (lstrcmpiA(OemComputer1, OemComputer2) == 0)
  1083. {
  1084. return(TRUE);
  1085. }
  1086. else
  1087. {
  1088. return(FALSE);
  1089. }
  1090. }
  1091. VOID
  1092. InitUnicodeString(
  1093. OUT PUNICODE_STRING DestinationString,
  1094. IN PCWSTR SourceString OPTIONAL
  1095. )
  1096. /*++
  1097. Routine Description:
  1098. The InitUnicodeString function initializes an NT counted
  1099. unicode string. The DestinationString is initialized to point to
  1100. the SourceString and the Length and MaximumLength fields of
  1101. DestinationString are initialized to the length of the SourceString,
  1102. which is zero if SourceString is not specified.
  1103. Arguments:
  1104. DestinationString - Pointer to the counted string to initialize
  1105. SourceString - Optional pointer to a null terminated unicode string that
  1106. the counted string is to point to.
  1107. Return Value:
  1108. None.
  1109. --*/
  1110. {
  1111. ULONG Length;
  1112. DestinationString->Buffer = (PWSTR)SourceString;
  1113. if (SourceString != NULL) {
  1114. Length = wcslen( SourceString ) * sizeof( WCHAR );
  1115. DestinationString->Length = (USHORT)Length;
  1116. DestinationString->MaximumLength = (USHORT)(Length + sizeof(UNICODE_NULL));
  1117. }
  1118. else {
  1119. DestinationString->MaximumLength = 0;
  1120. DestinationString->Length = 0;
  1121. }
  1122. }
  1123. VOID
  1124. CopyUnicodeString(
  1125. OUT PUNICODE_STRING DestinationString,
  1126. IN PUNICODE_STRING SourceString OPTIONAL
  1127. )
  1128. /*++
  1129. Routine Description:
  1130. The CopyString function copies the SourceString to the
  1131. DestinationString. If SourceString is not specified, then
  1132. the Length field of DestinationString is set to zero. The
  1133. MaximumLength and Buffer fields of DestinationString are not
  1134. modified by this function.
  1135. The number of bytes copied from the SourceString is either the
  1136. Length of SourceString or the MaximumLength of DestinationString,
  1137. whichever is smaller.
  1138. Arguments:
  1139. DestinationString - Pointer to the destination string.
  1140. SourceString - Optional pointer to the source string.
  1141. Return Value:
  1142. None.
  1143. --*/
  1144. {
  1145. UNALIGNED WCHAR *src, *dst;
  1146. ULONG n;
  1147. if (SourceString != NULL) {
  1148. dst = DestinationString->Buffer;
  1149. src = SourceString->Buffer;
  1150. n = SourceString->Length;
  1151. if ((USHORT)n > DestinationString->MaximumLength) {
  1152. n = DestinationString->MaximumLength;
  1153. }
  1154. DestinationString->Length = (USHORT)n;
  1155. CopyMemory(dst, src, n);
  1156. if (DestinationString->Length < DestinationString->MaximumLength) {
  1157. dst[n / sizeof(WCHAR)] = UNICODE_NULL;
  1158. }
  1159. } else {
  1160. DestinationString->Length = 0;
  1161. }
  1162. return;
  1163. }
  1164. #if 0
  1165. NTSTATUS
  1166. Msv1_0SubAuthenticationRoutine (
  1167. IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
  1168. IN PVOID LogonInformation,
  1169. IN ULONG Flags,
  1170. IN PUSER_ALL_INFORMATION UserAll,
  1171. OUT PULONG WhichFields,
  1172. OUT PULONG UserFlags,
  1173. OUT PBOOLEAN Authoritative,
  1174. OUT PLARGE_INTEGER LogoffTime,
  1175. OUT PLARGE_INTEGER KickoffTime
  1176. )
  1177. /*++
  1178. Routine Description:
  1179. The subauthentication routine does client/server specific authentication
  1180. of a user. The credentials of the user are passed in addition to all the
  1181. information from SAM defining the user. This routine decides whether to
  1182. let the user log on.
  1183. Arguments:
  1184. LogonLevel -- Specifies the level of information given in
  1185. LogonInformation.
  1186. LogonInformation -- Specifies the description for the user
  1187. logging on. The LogonDomainName field should be ignored.
  1188. Flags - Flags describing the circumstances of the logon.
  1189. MSV1_0_PASSTHRU -- This is a PassThru authenication. (i.e., the
  1190. user isn't connecting to this machine.)
  1191. MSV1_0_GUEST_LOGON -- This is a retry of the logon using the GUEST
  1192. user account.
  1193. UserAll -- The description of the user as returned from SAM.
  1194. WhichFields -- Returns which fields from UserAllInfo are to be written
  1195. back to SAM. The fields will only be written if MSV returns success
  1196. to it's caller. Only the following bits are valid.
  1197. USER_ALL_PARAMETERS - Write UserAllInfo->Parameters back to SAM. If
  1198. the size of the buffer is changed, Msv1_0SubAuthenticationRoutine
  1199. must delete the old buffer using MIDL_user_free() and reallocate the
  1200. buffer using MIDL_user_allocate().
  1201. UserFlags -- Returns UserFlags to be returned from LsaLogonUser in the
  1202. LogonProfile. The following bits are currently defined:
  1203. LOGON_GUEST -- This was a guest logon
  1204. LOGON_NOENCRYPTION -- The caller didn't specify encrypted credentials
  1205. SubAuthentication packages should restrict themselves to returning
  1206. bits in the high order byte of UserFlags. However, this convention
  1207. isn't enforced giving the SubAuthentication package more flexibility.
  1208. Authoritative -- Returns whether the status returned is an
  1209. authoritative status which should be returned to the original
  1210. caller. If not, this logon request may be tried again on another
  1211. domain controller. This parameter is returned regardless of the
  1212. status code.
  1213. LogoffTime - Receives the time at which the user should log off the
  1214. system. This time is specified as a GMT relative NT system time.
  1215. KickoffTime - Receives the time at which the user should be kicked
  1216. off the system. This time is specified as a GMT relative system
  1217. time. Specify, a full scale positive number if the user isn't to
  1218. be kicked off.
  1219. Return Value:
  1220. STATUS_SUCCESS: if there was no error.
  1221. STATUS_NO_SUCH_USER: The specified user has no account.
  1222. STATUS_WRONG_PASSWORD: The password was invalid.
  1223. STATUS_INVALID_INFO_CLASS: LogonLevel is invalid.
  1224. STATUS_ACCOUNT_LOCKED_OUT: The account is locked out
  1225. STATUS_ACCOUNT_DISABLED: The account is disabled
  1226. STATUS_ACCOUNT_EXPIRED: The account has expired.
  1227. STATUS_PASSWORD_MUST_CHANGE: Account is marked as Password must change
  1228. on next logon.
  1229. STATUS_PASSWORD_EXPIRED: The Password is expired.
  1230. STATUS_INVALID_LOGON_HOURS - The user is not authorized to log on at
  1231. this time.
  1232. STATUS_INVALID_WORKSTATION - The user is not authorized to log on to
  1233. the specified workstation.
  1234. --*/
  1235. {
  1236. NTSTATUS Status;
  1237. ULONG UserAccountControl;
  1238. LARGE_INTEGER LogonTime;
  1239. LARGE_INTEGER PasswordDateSet;
  1240. UNICODE_STRING LocalWorkstation;
  1241. WCHAR achCookie[64];
  1242. ANSI_STRING strA1;
  1243. ANSI_STRING strA2;
  1244. ANSI_STRING strDigest;
  1245. ANSI_STRING strPassword;
  1246. MD5_CTX md5;
  1247. CHAR *pch;
  1248. LPSTR pszRealm;
  1249. LPSTR pszUri;
  1250. LPSTR pszMethod;
  1251. LPSTR pszNonce;
  1252. LPSTR pszServerNonce;
  1253. LPSTR pszDigest;
  1254. LPSTR pszDigestUsername;
  1255. PNETLOGON_NETWORK_INFO LogonNetworkInfo;
  1256. UINT l;
  1257. PUNICODE_STRING pPwd;
  1258. strA1.Buffer = NULL;
  1259. strA2.Buffer = NULL;
  1260. strDigest.Buffer = NULL;
  1261. strPassword.Buffer = NULL;
  1262. //
  1263. // Check whether the SubAuthentication package supports this type
  1264. // of logon.
  1265. //
  1266. *Authoritative = TRUE;
  1267. *UserFlags = 0;
  1268. *WhichFields = 0;
  1269. (VOID) QuerySystemTime( &LogonTime );
  1270. switch ( LogonLevel ) {
  1271. case NetlogonInteractiveInformation:
  1272. case NetlogonServiceInformation:
  1273. //
  1274. // This SubAuthentication package only supports network logons.
  1275. //
  1276. return STATUS_INVALID_INFO_CLASS;
  1277. case NetlogonNetworkInformation:
  1278. //
  1279. // This SubAuthentication package doesn't support access via machine
  1280. // accounts.
  1281. //
  1282. UserAccountControl = USER_NORMAL_ACCOUNT;
  1283. //
  1284. // Local user (Temp Duplicate) accounts are only used on the machine
  1285. // being directly logged onto.
  1286. // (Nor are interactive or service logons allowed to them.)
  1287. //
  1288. if ( (Flags & MSV1_0_PASSTHRU) == 0 ) {
  1289. UserAccountControl |= USER_TEMP_DUPLICATE_ACCOUNT;
  1290. }
  1291. LogonNetworkInfo = (PNETLOGON_NETWORK_INFO) LogonInformation;
  1292. break;
  1293. default:
  1294. *Authoritative = TRUE;
  1295. return STATUS_INVALID_INFO_CLASS;
  1296. }
  1297. //
  1298. // If the account type isn't allowed,
  1299. // Treat this as though the User Account doesn't exist.
  1300. //
  1301. if ( (UserAccountControl & UserAll->UserAccountControl) == 0 ) {
  1302. *Authoritative = FALSE;
  1303. Status = STATUS_NO_SUCH_USER;
  1304. goto Cleanup;
  1305. }
  1306. //
  1307. // This SubAuthentication package doesn't allow guest logons.
  1308. //
  1309. if ( Flags & MSV1_0_GUEST_LOGON ) {
  1310. *Authoritative = FALSE;
  1311. Status = STATUS_NO_SUCH_USER;
  1312. goto Cleanup;
  1313. }
  1314. //
  1315. // Ensure the account isn't locked out.
  1316. //
  1317. if ( UserAll->UserId != DOMAIN_USER_RID_ADMIN &&
  1318. (UserAll->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) ) {
  1319. //
  1320. // Since the UI strongly encourages admins to disable user
  1321. // accounts rather than delete them. Treat disabled acccount as
  1322. // non-authoritative allowing the search to continue for other
  1323. // accounts by the same name.
  1324. //
  1325. if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
  1326. *Authoritative = FALSE;
  1327. } else {
  1328. *Authoritative = TRUE;
  1329. }
  1330. Status = STATUS_ACCOUNT_LOCKED_OUT;
  1331. goto Cleanup;
  1332. }
  1333. //
  1334. // Check the password.
  1335. //
  1336. #define IIS_SUBAUTH_SEED 0x8467fd31
  1337. switch( ((WCHAR*)(LogonNetworkInfo->NtChallengeResponse.Buffer))[0] )
  1338. {
  1339. case L'0':
  1340. if ( !NetUserCookieW( LogonNetworkInfo->Identity.UserName.Buffer,
  1341. LogonNetworkInfo->Identity.UserName.Length/sizeof(WCHAR),
  1342. IIS_SUBAUTH_SEED,
  1343. achCookie,
  1344. sizeof(achCookie ))
  1345. || memcmp( (LPBYTE)achCookie,
  1346. ((WCHAR*)LogonNetworkInfo->NtChallengeResponse.Buffer)+2,
  1347. wcslen(achCookie)*sizeof(WCHAR)
  1348. ) )
  1349. {
  1350. wrong_pwd:
  1351. Status = STATUS_WRONG_PASSWORD;
  1352. //
  1353. // Since the UI strongly encourages admins to disable user
  1354. // accounts rather than delete them. Treat disabled acccount as
  1355. // non-authoritative allowing the search to continue for other
  1356. // accounts by the same name.
  1357. //
  1358. if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
  1359. *Authoritative = FALSE;
  1360. } else {
  1361. *Authoritative = TRUE;
  1362. }
  1363. goto Cleanup;
  1364. }
  1365. break;
  1366. case L'1':
  1367. // digest NTLM authentication
  1368. // break fields
  1369. pch = LogonNetworkInfo->LmChallengeResponse.Buffer;
  1370. if ( !Extract( &pch, &pszRealm ) || // skip 1st field
  1371. !Extract( &pch, &pszRealm ) ||
  1372. !Extract( &pch, &pszUri ) ||
  1373. !Extract( &pch, &pszMethod ) ||
  1374. !Extract( &pch, &pszNonce ) ||
  1375. !Extract( &pch, &pszServerNonce ) ||
  1376. !Extract( &pch, &pszDigest ) ||
  1377. !Extract( &pch, &pszDigestUsername ) )
  1378. {
  1379. Status = STATUS_INVALID_INFO_CLASS;
  1380. goto Cleanup;
  1381. }
  1382. if ( UserAll->NtPasswordPresent )
  1383. {
  1384. pPwd = &UserAll->NtPassword;
  1385. }
  1386. else if ( UserAll->LmPasswordPresent )
  1387. {
  1388. pPwd = &UserAll->LmPassword;
  1389. }
  1390. else
  1391. {
  1392. pPwd = &EmptyString;
  1393. }
  1394. // build A1 & A2 as per Digest-NTLM auth spec
  1395. SubaAllocateString( &strA1, strlen( pszDigestUsername ) +
  1396. //wcslen(UserAll->UserName.Buffer) +
  1397. strlen(pszRealm) +
  1398. 32 //wcslen(pPwd)
  1399. +2 +1 +32
  1400. );
  1401. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, pszDigestUsername ) ) ||
  1402. //!NT_SUCCESS( Status = RtlUnicodeStringToAnsiString( &strA1, &UserAll->UserName, FALSE )) ||
  1403. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, ":" ) ) ||
  1404. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, pszRealm ) ) ||
  1405. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA1, ":" )) ||
  1406. !NT_SUCCESS( ToHex16( (LPBYTE)(pPwd->Buffer), &strA1 ) ) )
  1407. //!NT_SUCCESS( Status = RtlUnicodeStringToAnsiString( &strPassword, pPwd, TRUE ) ) ||
  1408. //!NT_SUCCESS( Status = RtlAppendStringToString( (PSTRING)&strA1, (PSTRING)&strPassword ) ) )
  1409. {
  1410. goto Cleanup;
  1411. }
  1412. SubaAllocateString( &strA2, strlen(pszMethod)+1+strlen(pszUri)+1+32 );
  1413. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, pszMethod ) ) ||
  1414. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, ":" ) ) ||
  1415. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strA2, pszUri ) ) )
  1416. {
  1417. goto Cleanup;
  1418. }
  1419. SubaAllocateString( &strDigest, 32 + 1 + strlen(pszNonce) + 1 + 32 +1 +32 );
  1420. // build response digest as per Digest Auth spec
  1421. // H(A1)
  1422. MD5Init( &md5 );
  1423. MD5Update( &md5, (LPBYTE)strA1.Buffer, strA1.Length );
  1424. MD5Final( &md5 );
  1425. if ( !NT_SUCCESS( ToHex16( md5.digest, &strDigest ) ) )
  1426. {
  1427. goto Cleanup;
  1428. }
  1429. // ":" nonce ":"
  1430. if ( !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, ":" ) ) ||
  1431. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, pszNonce ) ) ||
  1432. !NT_SUCCESS( Status = RtlAppendAsciizToString ( &strDigest, ":" ) ) )
  1433. {
  1434. goto Cleanup;
  1435. }
  1436. // H(A2)
  1437. MD5Init( &md5 );
  1438. MD5Update( &md5, (LPBYTE)strA2.Buffer, strA2.Length );
  1439. MD5Final( &md5 );
  1440. if ( !NT_SUCCESS( ToHex16( md5.digest, &strDigest ) ) )
  1441. {
  1442. goto Cleanup;
  1443. }
  1444. // H( H(A1) ":" nonce ":" H(A2) )
  1445. MD5Init( &md5 );
  1446. MD5Update( &md5, (LPBYTE)strDigest.Buffer, strDigest.Length );
  1447. MD5Final( &md5 );
  1448. strDigest.Length = 0;
  1449. if ( !NT_SUCCESS( ToHex16( md5.digest, &strDigest ) ) )
  1450. {
  1451. goto Cleanup;
  1452. }
  1453. if ( memcmp( strDigest.Buffer, pszDigest, strDigest.Length ) )
  1454. {
  1455. goto wrong_pwd;
  1456. }
  1457. // checking for stalled nonce must be made by caller
  1458. break;
  1459. default:
  1460. goto wrong_pwd;
  1461. }
  1462. //
  1463. // Prevent some things from effecting the Administrator user
  1464. //
  1465. if (UserAll->UserId == DOMAIN_USER_RID_ADMIN) {
  1466. //
  1467. // The administrator account doesn't have a forced logoff time.
  1468. //
  1469. LogoffTime->HighPart = 0x7FFFFFFF;
  1470. LogoffTime->LowPart = 0xFFFFFFFF;
  1471. KickoffTime->HighPart = 0x7FFFFFFF;
  1472. KickoffTime->LowPart = 0xFFFFFFFF;
  1473. } else {
  1474. //
  1475. // Check if the account is disabled.
  1476. //
  1477. if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
  1478. //
  1479. // Since the UI strongly encourages admins to disable user
  1480. // accounts rather than delete them. Treat disabled acccount as
  1481. // non-authoritative allowing the search to continue for other
  1482. // accounts by the same name.
  1483. //
  1484. *Authoritative = FALSE;
  1485. Status = STATUS_ACCOUNT_DISABLED;
  1486. goto Cleanup;
  1487. }
  1488. //
  1489. // Check if the account has expired.
  1490. //
  1491. if ( UserAll->AccountExpires.QuadPart != 0 &&
  1492. LogonTime.QuadPart >= UserAll->AccountExpires.QuadPart ) {
  1493. *Authoritative = TRUE;
  1494. Status = STATUS_ACCOUNT_EXPIRED;
  1495. goto Cleanup;
  1496. }
  1497. //
  1498. // If your using SAM's password expiration date, use this code, otherwise
  1499. // use the code below and supply your own password set date...
  1500. //
  1501. #if 1
  1502. //
  1503. // The password is valid, check to see if the password is expired.
  1504. // (SAM will have appropriately set PasswordMustChange to reflect
  1505. // USER_DONT_EXPIRE_PASSWORD)
  1506. //
  1507. // If the password checked above is not the SAM password, you may
  1508. // want to consider not checking the SAM password expiration times here.
  1509. //
  1510. if ( LogonTime.QuadPart >= UserAll->PasswordMustChange.QuadPart ) {
  1511. if ( UserAll->PasswordLastSet.QuadPart == 0 ) {
  1512. Status = STATUS_PASSWORD_MUST_CHANGE;
  1513. } else {
  1514. Status = STATUS_PASSWORD_EXPIRED;
  1515. }
  1516. *Authoritative = TRUE;
  1517. goto Cleanup;
  1518. }
  1519. #elif 0
  1520. //
  1521. // Response is correct. So, check if the password has expired or not
  1522. //
  1523. if (! (UserAll->UserAccountControl & USER_DONT_EXPIRE_PASSWORD)) {
  1524. LARGE_INTEGER MaxPasswordAge;
  1525. MaxPasswordAge.HighPart = 0x7FFFFFFF;
  1526. MaxPasswordAge.LowPart = 0xFFFFFFFF;
  1527. //
  1528. // PasswordDateSet should be modified to hold the last date the
  1529. // user's password was set.
  1530. //
  1531. PasswordDateSet.LowPart = 0;
  1532. PasswordDateSet.HighPart = 0;
  1533. if ( GetPasswordExpired( PasswordDateSet,
  1534. MaxPasswordAge )) {
  1535. Status = STATUS_PASSWORD_EXPIRED;
  1536. goto Cleanup;
  1537. }
  1538. }
  1539. #endif
  1540. #if 1
  1541. //
  1542. // Validate the workstation the user logged on from.
  1543. //
  1544. // Ditch leading \\ on workstation name before passing it to SAM.
  1545. //
  1546. LocalWorkstation = LogonNetworkInfo->Identity.Workstation;
  1547. if ( LocalWorkstation.Length > 0 &&
  1548. LocalWorkstation.Buffer[0] == L'\\' &&
  1549. LocalWorkstation.Buffer[1] == L'\\' ) {
  1550. LocalWorkstation.Buffer += 2;
  1551. LocalWorkstation.Length -= 2*sizeof(WCHAR);
  1552. LocalWorkstation.MaximumLength -= 2*sizeof(WCHAR);
  1553. }
  1554. //
  1555. // To validate the user's logon hours as SAM does it, use this code,
  1556. // otherwise, supply your own checks below this code.
  1557. //
  1558. Status = AccountRestrictions( UserAll->UserId,
  1559. &LocalWorkstation,
  1560. (PUNICODE_STRING) &UserAll->WorkStations,
  1561. &UserAll->LogonHours,
  1562. LogoffTime,
  1563. KickoffTime );
  1564. if ( !NT_SUCCESS( Status )) {
  1565. goto Cleanup;
  1566. }
  1567. #elif 0
  1568. //
  1569. // Validate the user's logon hours.
  1570. //
  1571. if ( TRUE /* VALIDATE THE LOGON HOURS */ ) {
  1572. //
  1573. // All times are allowed, so there's no logoff
  1574. // time. Return forever for both logofftime and
  1575. // kickofftime.
  1576. //
  1577. LogoffTime->HighPart = 0x7FFFFFFF;
  1578. LogoffTime->LowPart = 0xFFFFFFFF;
  1579. KickoffTime->HighPart = 0x7FFFFFFF;
  1580. KickoffTime->LowPart = 0xFFFFFFFF;
  1581. } else {
  1582. Status = STATUS_INVALID_LOGON_HOURS;
  1583. *Authoritative = TRUE;
  1584. goto Cleanup;
  1585. }
  1586. #endif
  1587. #if 0
  1588. //
  1589. // Validate if the user can log on from this workstation.
  1590. // (Supply subauthentication package specific code here.)
  1591. if ( LogonNetworkInfo->Identity.Workstation.Buffer == NULL ) {
  1592. Status = STATUS_INVALID_WORKSTATION;
  1593. *Authoritative = TRUE;
  1594. goto Cleanup;
  1595. }
  1596. #endif
  1597. }
  1598. //
  1599. // The user is valid.
  1600. //
  1601. *Authoritative = TRUE;
  1602. Status = STATUS_SUCCESS;
  1603. //
  1604. // Cleanup up before returning.
  1605. //
  1606. Cleanup:
  1607. if ( strA1.Buffer )
  1608. {
  1609. RtlFreeHeap(RtlProcessHeap(), 0, strA1.Buffer );
  1610. }
  1611. if ( strA2.Buffer )
  1612. {
  1613. RtlFreeHeap(RtlProcessHeap(), 0, strA2.Buffer );
  1614. }
  1615. if ( strDigest.Buffer )
  1616. {
  1617. RtlFreeHeap(RtlProcessHeap(), 0, strDigest.Buffer );
  1618. }
  1619. if ( strPassword.Buffer )
  1620. {
  1621. RtlFreeAnsiString( &strPassword );
  1622. }
  1623. return Status;
  1624. } // Msv1_0SubAuthenticationRoutine
  1625. NTSTATUS
  1626. Msv1_0SubAuthenticationFilter (
  1627. IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
  1628. IN PVOID LogonInformation,
  1629. IN ULONG Flags,
  1630. IN PUSER_ALL_INFORMATION UserAll,
  1631. OUT PULONG WhichFields,
  1632. OUT PULONG UserFlags,
  1633. OUT PBOOLEAN Authoritative,
  1634. OUT PLARGE_INTEGER LogoffTime,
  1635. OUT PLARGE_INTEGER KickoffTime
  1636. )
  1637. {
  1638. return( Msv1_0SubAuthenticationRoutine(
  1639. LogonLevel,
  1640. LogonInformation,
  1641. Flags,
  1642. UserAll,
  1643. WhichFields,
  1644. UserFlags,
  1645. Authoritative,
  1646. LogoffTime,
  1647. KickoffTime
  1648. ) );
  1649. }
  1650. #endif
  1651. static const CHAR c_szKeyName[] = "SYSTEM\\CurrentControlSet\\Control\\Lsa\\MSV1_0";
  1652. static const CHAR c_szValueName[] = "Auth132";
  1653. static const CHAR c_szIISSUBA[] = "IISSUBA";
  1654. VOID CALLBACK
  1655. RegisterIISSUBA(
  1656. HWND hwnd, // handle to owner window
  1657. HINSTANCE hinst, // instance handle for the DLL
  1658. LPTSTR lpCmdLine, // string the DLL will parse
  1659. int nCmdShow // show state
  1660. )
  1661. {
  1662. HRESULT hr = S_OK;
  1663. LONG lRet;
  1664. HKEY hKey = NULL;
  1665. UNREFERENCED_PARM(hwnd);
  1666. UNREFERENCED_PARM(hinst);
  1667. UNREFERENCED_PARM(lpCmdLine);
  1668. UNREFERENCED_PARM(nCmdShow);
  1669. // Open the key
  1670. lRet = RegOpenKeyExA( HKEY_LOCAL_MACHINE,
  1671. c_szKeyName,
  1672. 0,
  1673. KEY_ALL_ACCESS,
  1674. &hKey );
  1675. if ( lRet != ERROR_SUCCESS )
  1676. {
  1677. hr = HRESULT_FROM_WIN32( lRet );
  1678. goto exit;
  1679. }
  1680. // Create Auth132 = IISSUBA
  1681. lRet = RegSetValueEx( hKey,
  1682. c_szValueName,
  1683. 0,
  1684. REG_SZ,
  1685. (BYTE*)c_szIISSUBA,
  1686. sizeof( c_szIISSUBA ) );
  1687. if ( lRet != ERROR_SUCCESS )
  1688. {
  1689. hr = HRESULT_FROM_WIN32( lRet );
  1690. goto exit;
  1691. }
  1692. exit:
  1693. // Cleanup
  1694. if ( hKey != NULL )
  1695. {
  1696. RegCloseKey( hKey );
  1697. hKey = NULL;
  1698. }
  1699. }
  1700. VOID CALLBACK
  1701. UnregisterIISSUBA(
  1702. HWND hwnd, // handle to owner window
  1703. HINSTANCE hinst, // instance handle for the DLL
  1704. LPTSTR lpCmdLine, // string the DLL will parse
  1705. int nCmdShow // show state
  1706. )
  1707. {
  1708. HRESULT hr = S_OK;
  1709. LONG lRet;
  1710. HKEY hKey = NULL;
  1711. UNREFERENCED_PARM(hwnd);
  1712. UNREFERENCED_PARM(hinst);
  1713. UNREFERENCED_PARM(lpCmdLine);
  1714. UNREFERENCED_PARM(nCmdShow);
  1715. // Open the key
  1716. lRet = RegOpenKeyExA( HKEY_LOCAL_MACHINE,
  1717. c_szKeyName,
  1718. 0,
  1719. KEY_ALL_ACCESS,
  1720. &hKey );
  1721. if ( lRet != ERROR_SUCCESS )
  1722. {
  1723. hr = HRESULT_FROM_WIN32( lRet );
  1724. goto exit;
  1725. }
  1726. // Delete Auth132
  1727. lRet = RegDeleteValue( hKey,
  1728. c_szValueName );
  1729. if ( lRet != ERROR_SUCCESS )
  1730. {
  1731. hr = HRESULT_FROM_WIN32( lRet );
  1732. goto exit;
  1733. }
  1734. exit:
  1735. // Cleanup
  1736. if ( hKey != NULL )
  1737. {
  1738. RegCloseKey( hKey );
  1739. hKey = NULL;
  1740. }
  1741. }
  1742. // subauth.c eof