Source code of Windows XP (NT5)
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.

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