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.

1798 lines
46 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. acctlist.c
  5. Abstract:
  6. This code builds a list of domains and queries each of them to
  7. locate an account. It is different than LookupAccountName because
  8. it returns accounts for each match instead of just the first match.
  9. Author:
  10. Jim Schmidt (jimschm) 26-Jun-1997
  11. Revision History:
  12. ovidiut 14-Mar-2000 Added support for encrypted passwords
  13. jimschm 23-Sep-1998 UI changes
  14. jimschm 11-Jun-1998 User Profile Path now stored in account
  15. list. Added GetProfilePathForUser.
  16. jimschm 18-Mar-1998 Added support for random passwords and
  17. auto-logon.
  18. jimschm 17-Feb-1998 Updated share security for NT 5 changes
  19. marcw 10-Dec-1997 Added unattended local account password
  20. support.
  21. --*/
  22. #include "pch.h"
  23. #include "migmainp.h"
  24. #include "security.h"
  25. #define DBG_ACCOUNTS "AcctList"
  26. POOLHANDLE g_UserPool;
  27. PVOID g_UserTable;
  28. typedef struct {
  29. PCWSTR Domain;
  30. PSID Sid;
  31. PCWSTR ProfilePath;
  32. } USERDETAILS, *PUSERDETAILS;
  33. BOOL g_DomainProblem;
  34. BOOL g_RandomPassword = FALSE;
  35. BOOL
  36. pAddUser (
  37. IN PCWSTR User,
  38. IN PCWSTR Domain
  39. );
  40. BOOL
  41. pAddLocalGroup (
  42. IN PCWSTR Group
  43. );
  44. BOOL
  45. pAddDomainGroup (
  46. IN PCWSTR Group
  47. );
  48. VOID
  49. pGenerateRandomPassword (
  50. OUT PTSTR Password
  51. );
  52. VOID
  53. pResolveUserDomains (
  54. VOID
  55. );
  56. BOOL
  57. pMakeSureAccountsAreValid (
  58. VOID
  59. );
  60. BOOL
  61. pWasWin9xOnTheNet (
  62. VOID
  63. );
  64. VOID
  65. FindAccountInit (
  66. VOID
  67. )
  68. /*++
  69. Routine Description:
  70. Initialization routine for account management routines, initialized
  71. when the migration module begins and terminated when the migration
  72. module ends.
  73. Arguments:
  74. none
  75. Return value:
  76. none
  77. --*/
  78. {
  79. g_UserPool = PoolMemInitNamedPool ("User Accounts");
  80. g_UserTable = pSetupStringTableInitializeEx (sizeof (USERDETAILS), 0);
  81. }
  82. VOID
  83. FindAccountTerminate (
  84. VOID
  85. )
  86. /*++
  87. Routine Description:
  88. Termination routine for the account management routines, called when
  89. migmain's entry point is called for cleanup.
  90. Arguments:
  91. none
  92. Return value:
  93. none
  94. --*/
  95. {
  96. PushError();
  97. //
  98. // Free user list
  99. //
  100. PoolMemDestroyPool (g_UserPool);
  101. pSetupStringTableDestroy (g_UserTable);
  102. //
  103. // Restore error value
  104. //
  105. PopError();
  106. }
  107. BOOL
  108. SearchDomainsForUserAccounts (
  109. VOID
  110. )
  111. /*++
  112. Routine Description:
  113. Routine to resolve all account names into fully qualified
  114. domain names with SIDs, and user profile paths.
  115. The account names come from the Autosearch and KnownDomain
  116. categories in memdb. They are validated and placed in a
  117. string table called the user list. Functions in this source
  118. file access the user list.
  119. Arguments:
  120. none
  121. Return value:
  122. TRUE if the account list was resolved, or FALSE if a failure
  123. occurs. In the failure case, the account list may not be
  124. accurate, but the installer is informed when network problems
  125. occur, and the installer also has several chances to correct
  126. the problem.
  127. --*/
  128. {
  129. MEMDB_ENUM e;
  130. PTSTR p, UserName;
  131. TCHAR DomainName[MAX_SERVER_NAME];
  132. BOOL FallbackToLocal = FALSE;
  133. BOOL LocalizeWarning = FALSE;
  134. BOOL b = FALSE;
  135. INFCONTEXT ic;
  136. TCHAR Buffer[256];
  137. __try {
  138. //
  139. // Put the Administrator password in the list
  140. //
  141. if (MemDbGetValueEx (&e, MEMDB_CATEGORY_STATE, MEMDB_ITEM_ADMIN_PASSWORD, NULL)) {
  142. MemDbSetValueEx (
  143. MEMDB_CATEGORY_USERPASSWORD,
  144. g_AdministratorStr,
  145. e.szName,
  146. NULL,
  147. e.dwValue,
  148. NULL
  149. );
  150. } else {
  151. MYASSERT (FALSE);
  152. }
  153. //
  154. // Create the status dialog (initially hidden)
  155. //
  156. CreateStatusPopup();
  157. //
  158. // Prepare the account list
  159. //
  160. InitAccountList();
  161. //
  162. // Get all trusted domains
  163. //
  164. if (!BuildDomainList()) {
  165. FallbackToLocal = TRUE;
  166. }
  167. //
  168. // Put all users who need autosearch to resolve their domain names
  169. // in the unknown domain.
  170. //
  171. if (MemDbEnumItems (&e, MEMDB_CATEGORY_AUTOSEARCH)) {
  172. do {
  173. if (FallbackToLocal) {
  174. if (!pAddUser (e.szName, NULL)) {
  175. LOG ((
  176. LOG_ERROR,
  177. "Can't create local account for %s, this user can't be processed.",
  178. e.szName
  179. ));
  180. }
  181. } else {
  182. AddUserToDomainList (e.szName, S_UNKNOWN_DOMAIN);
  183. }
  184. } while (MemDbEnumNextValue (&e));
  185. }
  186. //
  187. // Put all users whos domain is known in the appropriate domain. If
  188. // the add fails, the domain does not exist and the account must be
  189. // added to the autosearch (causing a silent repair if possible).
  190. //
  191. if (MemDbGetValueEx (&e, MEMDB_CATEGORY_KNOWNDOMAIN, NULL, NULL)) {
  192. do {
  193. if(_tcslen(e.szName) >= ARRAYSIZE(DomainName)){
  194. DEBUGMSG((DBG_WARNING, "SearchDomainsForUserAccounts does not provide enough buffer for string copy %s", e.szName));
  195. continue;
  196. }
  197. StackStringCopy (DomainName, e.szName);
  198. p = _tcschr (DomainName, TEXT('\\'));
  199. if (!p) {
  200. DEBUGMSG ((
  201. DBG_WHOOPS,
  202. "Unexpected string in %s: %s",
  203. MEMDB_CATEGORY_KNOWNDOMAIN,
  204. e.szName
  205. ));
  206. continue;
  207. }
  208. UserName = _tcsinc (p);
  209. *p = 0;
  210. //
  211. // Verify that this isn't some irrelavent user
  212. //
  213. if (!GetUserDatLocation (UserName, NULL)) {
  214. DEBUGMSG ((
  215. DBG_WARNING,
  216. "Ignoring irrelavent user specified in UserDomain of unattend.txt: %s",
  217. e.szName
  218. ));
  219. continue;
  220. }
  221. if (p == DomainName || FallbackToLocal) {
  222. //
  223. // This user has a local account
  224. //
  225. if (!pAddUser (UserName, NULL)) {
  226. LOG ((
  227. LOG_ERROR,
  228. "Can't create local account for %s, this user can't be processed. (2)",
  229. e.szName
  230. ));
  231. }
  232. } else {
  233. //
  234. // This user's domain name needs verification
  235. //
  236. if (!AddUserToDomainList (UserName, DomainName)) {
  237. AddUserToDomainList (UserName, S_UNKNOWN_DOMAIN);
  238. }
  239. }
  240. } while (MemDbEnumNextValue (&e));
  241. }
  242. //
  243. // Now resolve all the domain names
  244. //
  245. if (!FallbackToLocal) {
  246. do {
  247. g_DomainProblem = FALSE;
  248. pResolveUserDomains();
  249. PrepareForRetry();
  250. } while (pMakeSureAccountsAreValid());
  251. }
  252. //
  253. // If we had no choice but to make some accounts local,
  254. // put a message in the PSS log.
  255. //
  256. if (FallbackToLocal) {
  257. if (pWasWin9xOnTheNet() && !g_ConfigOptions.UseLocalAccountOnError) {
  258. LOG ((LOG_WARNING, (PCSTR)MSG_ALL_USERS_ARE_LOCAL, g_ComputerName));
  259. }
  260. }
  261. //
  262. // Make sure Administrator is in the account list
  263. //
  264. if (!GetSidForUser (g_AdministratorStr)) {
  265. if (!pAddUser (g_AdministratorStr, NULL)) {
  266. LOG ((LOG_ERROR, "Account name mismatch: %s", g_AdministratorStr));
  267. LocalizeWarning = TRUE;
  268. }
  269. }
  270. //
  271. // Add local "Everyone" for network account case
  272. //
  273. if (!pAddLocalGroup (g_EveryoneStr)) {
  274. LOG ((LOG_ERROR, "Account name mismatch: %s", g_EveryoneStr));
  275. LocalizeWarning = TRUE;
  276. __leave;
  277. }
  278. //
  279. // Add Administrators group
  280. //
  281. if (!pAddLocalGroup (g_AdministratorsGroupStr)) {
  282. LOG ((LOG_ERROR, "Account name mismatch: %s", g_AdministratorsGroupStr));
  283. LocalizeWarning = TRUE;
  284. __leave;
  285. }
  286. //
  287. // Add domain users group if domain is enabled, or add "none" group otherwise
  288. //
  289. if (!FallbackToLocal) {
  290. if (!pAddDomainGroup (g_DomainUsersGroupStr)) {
  291. DEBUGMSG ((DBG_WARNING, "Domain enabled but GetPrimaryDomainName failed"));
  292. }
  293. } else {
  294. if (!pAddLocalGroup (g_NoneGroupStr)) {
  295. LOG ((LOG_ERROR, "Account name mismatch: %s", g_NoneGroupStr));
  296. LocalizeWarning = TRUE;
  297. __leave;
  298. }
  299. }
  300. //
  301. // All user accounts and SIDs now exist in a user table, so we do not
  302. // need the account list anymore.
  303. //
  304. TerminateAccountList();
  305. DestroyStatusPopup();
  306. b = TRUE;
  307. }
  308. __finally {
  309. #ifdef PRERELEASE
  310. if (LocalizeWarning) {
  311. LOG ((
  312. LOG_ERROR,
  313. "Account name mismatches are usually caused by improper localization. "
  314. "Make sure w95upgnt.dll account names match the LSA database."
  315. ));
  316. }
  317. #endif
  318. }
  319. return b;
  320. }
  321. VOID
  322. AutoStartProcessing (
  323. VOID
  324. )
  325. /*++
  326. Routine Description:
  327. AutoStartProcessing fills in the winlogon key's auto-admin logon, and sets
  328. migpwd.exe in Run.
  329. If necessary, Winlogon will prompt the user for their password. As a backup,
  330. a RunOnce and Run entry is made. (If some bug caused winlogon not to run
  331. this app, then it would be impossible to log on.)
  332. If an administrator password was already set via the AdminPassword line in
  333. [GUIUnattended], then the migpwd.exe entry is not used.
  334. Arguments:
  335. None.
  336. Return Value:
  337. None.
  338. --*/
  339. {
  340. HKEY WinlogonKey;
  341. HKEY RunKey;
  342. TCHAR Buf[32];
  343. PCTSTR MigPwdPath;
  344. BOOL AutoLogonOk = TRUE;
  345. LONG rc;
  346. MEMDB_ENUM e;
  347. DWORD One = 1;
  348. //
  349. // Enable auto-logon if random password was used
  350. //
  351. if (!MemDbGetValueEx (&e, MEMDB_CATEGORY_STATE, MEMDB_ITEM_ADMIN_PASSWORD, NULL)) {
  352. MYASSERT (FALSE);
  353. e.dwValue = PASSWORD_ATTR_RANDOM;
  354. e.szName[0] = 0;
  355. ClearAdminPassword();
  356. }
  357. MigPwdPath = JoinPaths (g_System32Dir, S_MIGPWD_EXE);
  358. //if ((e.dwValue & PASSWORD_ATTR_RANDOM) == PASSWORD_ATTR_RANDOM || g_RandomPassword) {
  359. //
  360. // Set AutoAdminLogon, DefaultUser, DefaultUserDomain and DefaultPassword
  361. //
  362. WinlogonKey = OpenRegKeyStr (S_WINLOGON_REGKEY);
  363. if (WinlogonKey) {
  364. rc = RegSetValueEx (
  365. WinlogonKey,
  366. S_DEFAULT_USER_NAME_VALUE,
  367. 0,
  368. REG_SZ,
  369. (PBYTE) g_AdministratorStr,
  370. SizeOfString (g_AdministratorStr)
  371. );
  372. if (rc != ERROR_SUCCESS) {
  373. DEBUGMSG ((DBG_WHOOPS, "Can't set default user name"));
  374. AutoLogonOk = FALSE;
  375. }
  376. rc = RegSetValueEx (
  377. WinlogonKey,
  378. S_DEFAULT_DOMAIN_NAME_VALUE,
  379. 0,
  380. REG_SZ,
  381. (PBYTE) g_ComputerName,
  382. SizeOfString (g_ComputerName)
  383. );
  384. if (rc != ERROR_SUCCESS) {
  385. DEBUGMSG ((DBG_WHOOPS, "Can't set default domain name"));
  386. AutoLogonOk = FALSE;
  387. }
  388. rc = RegSetValueEx (
  389. WinlogonKey,
  390. S_DEFAULT_PASSWORD_VALUE,
  391. 0,
  392. REG_SZ,
  393. (PBYTE) e.szName,
  394. SizeOfString (e.szName)
  395. );
  396. if (rc != ERROR_SUCCESS) {
  397. DEBUGMSG ((DBG_WHOOPS, "Can't set administrator password"));
  398. AutoLogonOk = FALSE;
  399. }
  400. wsprintf (Buf, TEXT("%u"), 1);
  401. rc = RegSetValueEx (
  402. WinlogonKey,
  403. S_AUTOADMIN_LOGON_VALUE,
  404. 0,
  405. REG_SZ,
  406. (PBYTE) Buf,
  407. SizeOfString (Buf)
  408. );
  409. if (rc != ERROR_SUCCESS) {
  410. DEBUGMSG ((DBG_WHOOPS, "Can't turn on auto logon"));
  411. AutoLogonOk = FALSE;
  412. }
  413. rc = RegSetValueEx (
  414. WinlogonKey,
  415. TEXT("SetWin9xUpgradePasswords"),
  416. 0,
  417. REG_DWORD,
  418. (PBYTE) &One,
  419. sizeof (One)
  420. );
  421. if (rc != ERROR_SUCCESS) {
  422. DEBUGMSG ((DBG_WHOOPS, "Can't enable boot-time password prompt"));
  423. }
  424. CloseRegKey (WinlogonKey);
  425. } else {
  426. DEBUGMSG ((DBG_WHOOPS, "Can't open winlogon key"));
  427. AutoLogonOk = FALSE;
  428. }
  429. //
  430. // Add migpwd.exe to Run.
  431. //
  432. RunKey = OpenRegKeyStr (S_RUN_KEY);
  433. if (RunKey) {
  434. rc = RegSetValueEx (
  435. RunKey,
  436. S_MIGPWD,
  437. 0,
  438. REG_SZ,
  439. (PBYTE) MigPwdPath,
  440. SizeOfString (MigPwdPath)
  441. );
  442. if (rc != ERROR_SUCCESS) {
  443. DEBUGMSG ((DBG_WHOOPS, "Can't set Run key value"));
  444. AutoLogonOk = FALSE;
  445. }
  446. CloseRegKey (RunKey);
  447. } else {
  448. DEBUGMSG ((DBG_WHOOPS, "Can't open Run key"));
  449. AutoLogonOk = FALSE;
  450. }
  451. RunKey = OpenRegKeyStr (S_RUNONCE_KEY);
  452. if (RunKey) {
  453. rc = RegSetValueEx (
  454. RunKey,
  455. S_MIGPWD,
  456. 0,
  457. REG_SZ,
  458. (PBYTE) MigPwdPath,
  459. SizeOfString (MigPwdPath)
  460. );
  461. if (rc != ERROR_SUCCESS) {
  462. DEBUGMSG ((DBG_WHOOPS, "Can't set RunOnce key value"));
  463. AutoLogonOk = FALSE;
  464. }
  465. CloseRegKey (RunKey);
  466. } else {
  467. DEBUGMSG ((DBG_WHOOPS, "Can't open RunOnce key"));
  468. AutoLogonOk = FALSE;
  469. }
  470. #if 0
  471. } else {
  472. DEBUGMSG ((DBG_ACCOUNTS, "Deleting %s because it is not needed", MigPwdPath));
  473. DeleteFile (MigPwdPath);
  474. }
  475. #endif
  476. if (!AutoLogonOk) {
  477. LOG ((
  478. LOG_ACCOUNTS,
  479. "An error occurred preparing autologon. There will be password problems."
  480. ));
  481. //
  482. // Set the Admin password to blank
  483. //
  484. ClearAdminPassword();
  485. }
  486. FreePathString (MigPwdPath);
  487. }
  488. VOID
  489. pResolveUserDomains (
  490. VOID
  491. )
  492. /*++
  493. Routine Description:
  494. This private function searches the network for all users with unknown
  495. domains as well as users with manually entered domains. It resolves
  496. as many users as it can, and if no network problems occur, all users
  497. are resolved.
  498. Arguments:
  499. none
  500. Return value:
  501. none
  502. --*/
  503. {
  504. ACCT_ENUM KnownDomain, UnknownDomain;
  505. ACCT_ENUM UserEnum;
  506. BOOL UnknownFlag, KnownFlag;
  507. PCWSTR Domain;
  508. UINT TotalDomains;
  509. UINT CurrentDomain;
  510. PCTSTR Message;
  511. PCTSTR ArgArray[3];
  512. BOOL FlashSuppress = TRUE;
  513. //
  514. // Determine if there are any users who do not have domains
  515. //
  516. UnknownFlag = FindDomainInList (&UnknownDomain, S_UNKNOWN_DOMAIN);
  517. if (UnknownFlag && !CountUsersInDomain (&UnknownDomain)) {
  518. UnknownFlag = FALSE;
  519. }
  520. //
  521. // Count the number of domains
  522. //
  523. Domain = ListFirstDomain (&KnownDomain);
  524. TotalDomains = 0;
  525. while (Domain) {
  526. //
  527. // We will query a domain when:
  528. //
  529. // - it is trusted
  530. // - there is one or more users who we think is in the domain
  531. // - or, there is one or more users which we don't know the domain
  532. //
  533. if (IsTrustedDomain (&KnownDomain)) {
  534. if (UnknownFlag) {
  535. TotalDomains++;
  536. } else if (CountUsersInDomain (&KnownDomain)) {
  537. TotalDomains++;
  538. }
  539. }
  540. Domain = ListNextDomain (&KnownDomain);
  541. }
  542. //
  543. // Enumerate each trusted domain
  544. //
  545. Domain = ListFirstDomain (&KnownDomain);
  546. CurrentDomain = 0;
  547. if (TotalDomains <= 4) {
  548. //
  549. // No status on small nets
  550. //
  551. HideStatusPopup (INFINITE);
  552. FlashSuppress = FALSE;
  553. }
  554. while (Domain) {
  555. if (IsTrustedDomain (&KnownDomain)) {
  556. //
  557. // Determine if there are any users for the current domain
  558. //
  559. KnownFlag = CountUsersInDomain (&KnownDomain) != 0;
  560. //
  561. // Process only if this domain needs to be queried -- either
  562. // domain is unknown, or it is known but unverified.
  563. //
  564. if (UnknownFlag || KnownFlag) {
  565. //
  566. // Update the status window
  567. //
  568. CurrentDomain++;
  569. ArgArray[0] = Domain;
  570. ArgArray[1] = (PCTSTR) CurrentDomain;
  571. ArgArray[2] = (PCTSTR) TotalDomains;
  572. Message = ParseMessageID (MSG_DOMAIN_STATUS_POPUP, ArgArray);
  573. UpdateStatusPopup (Message);
  574. FreeStringResource (Message);
  575. if (FlashSuppress) {
  576. if (IsStatusPopupVisible()) {
  577. FlashSuppress = FALSE;
  578. } else if ((TotalDomains - CurrentDomain) <= 3) {
  579. //
  580. // Not much left, we better suspend the status dialog
  581. //
  582. HideStatusPopup (INFINITE);
  583. FlashSuppress = FALSE;
  584. }
  585. }
  586. //
  587. // Enumerate all users with unknown domains and look for them
  588. // in this domain, unless user wants to abort search.
  589. //
  590. if (g_RetryCount != DOMAIN_RETRY_ABORT) {
  591. if (ListFirstUserInDomain (&UnknownDomain, &UserEnum)) {
  592. do {
  593. if (QueryDomainForUser (&KnownDomain, &UserEnum)) {
  594. UserMayBeInDomain (&UserEnum, &KnownDomain);
  595. }
  596. if (g_RetryCount == DOMAIN_RETRY_ABORT) {
  597. break;
  598. }
  599. } while (ListNextUserInDomain (&UserEnum));
  600. }
  601. }
  602. //
  603. // Enumerate all users that are supposed to be in this domain,
  604. // unless user wants to abort search.
  605. //
  606. if (ListFirstUserInDomain (&KnownDomain, &UserEnum)) {
  607. do {
  608. if (g_RetryCount == DOMAIN_RETRY_ABORT ||
  609. !QueryDomainForUser (&KnownDomain, &UserEnum)) {
  610. //
  611. // User was not found! Put them in the "failed" domain
  612. //
  613. MoveUserToNewDomain (&UserEnum, S_FAILED_DOMAIN);
  614. }
  615. } while (ListNextUserInDomain (&UserEnum));
  616. }
  617. }
  618. }
  619. Domain = ListNextDomain (&KnownDomain);
  620. }
  621. }
  622. VOID
  623. pAddUserToRegistryList (
  624. PCTSTR User,
  625. BOOL DomainFixList
  626. )
  627. {
  628. HKEY Key;
  629. PCTSTR KeyStr;
  630. KeyStr = DomainFixList ? S_WINLOGON_USER_LIST_KEY : S_USER_LIST_KEY;
  631. Key = CreateRegKeyStr (KeyStr);
  632. if (Key) {
  633. RegSetValueEx (
  634. Key,
  635. User,
  636. 0,
  637. REG_SZ,
  638. (PBYTE) S_EMPTY,
  639. sizeof (TCHAR)
  640. );
  641. CloseRegKey (Key);
  642. }
  643. ELSE_DEBUGMSG ((DBG_WHOOPS, "Can't create %s", KeyStr));
  644. }
  645. BOOL
  646. pAddUser (
  647. IN PCWSTR User,
  648. IN PCWSTR Domain OPTIONAL
  649. )
  650. /*++
  651. Routine Description:
  652. Adds a user and domain to our user list. It obtains the SID for the user
  653. and saves the user name, domain and SID in a string table. The profile
  654. path for the user is obtained via a call to CreateUserProfile (in userenv.dll).
  655. This function also maintains g_RandomPassword - a flag that is set to TRUE
  656. if a random password is used.
  657. Arguments:
  658. User - Specifies fixed user name
  659. Domain - Specifies domain where user account exists, NULL for local machine
  660. Return value:
  661. TRUE if no errors occur, or FALSE if an unexpected problem occurs and the
  662. user cannot be added.
  663. --*/
  664. {
  665. BOOL DupDomain = TRUE;
  666. USERDETAILS UserDetails;
  667. ACCOUNTPROPERTIES Account;
  668. DWORD rc;
  669. GROWBUFFER SidBuf = GROWBUF_INIT;
  670. TCHAR UserProfilePath[MAX_TCHAR_PATH];
  671. BOOL CreateAccountFlag;
  672. MEMDB_ENUM e;
  673. DWORD attribs = PASSWORD_ATTR_DEFAULT;
  674. TCHAR pattern[MEMDB_MAX];
  675. TCHAR randomPwd[16];
  676. TCHAR copyPwd[MEMDB_MAX];
  677. PCTSTR ArgList[1];
  678. CreateAccountFlag = MemDbGetValueEx (
  679. &e,
  680. MEMDB_CATEGORY_USER_DAT_LOC,
  681. User,
  682. NULL
  683. );
  684. ZeroMemory (&UserDetails, sizeof (UserDetails));
  685. //
  686. // Make sure the administrator account is created
  687. //
  688. if (CreateAccountFlag || StringIMatch (User, g_AdministratorStr)) {
  689. //
  690. // If a local account, create it
  691. //
  692. if (!Domain) {
  693. Account.User = User;
  694. Account.FullName = User;
  695. ArgList[0] = g_Win95Name;
  696. Account.AdminComment = ParseMessageID (MSG_MIGRATED_ACCOUNT_DESCRIPTION, ArgList);
  697. Account.EncryptedPassword = NULL;
  698. //
  699. // Set password. If installer specified a password for this user via an unattend
  700. // setting, we'll use that. Otherwise, if they have specified a default
  701. // password, we'll use that. Finally, if neither of those settings were
  702. // specified, we'll use a random password.
  703. //
  704. MemDbBuildKey (pattern, MEMDB_CATEGORY_USERPASSWORD, User, TEXT("*"), NULL);
  705. if (MemDbEnumFirstValue (
  706. &e,
  707. pattern,
  708. MEMDB_ALL_SUBLEVELS,
  709. MEMDB_ENDPOINTS_ONLY
  710. )) {
  711. StackStringCopy (copyPwd, e.szName);
  712. attribs = e.dwValue;
  713. if (attribs & PASSWORD_ATTR_ENCRYPTED) {
  714. //
  715. // cannot use this hashed pwd to create the account
  716. // create a random one that will be replaced
  717. // using OWF hashing functions
  718. //
  719. pGenerateRandomPassword (randomPwd);
  720. Account.Password = randomPwd;
  721. Account.EncryptedPassword = copyPwd;
  722. DEBUGMSG ((
  723. DBG_ACCOUNTS,
  724. "Per-user encrypted password specified for %s: %s",
  725. Account.User,
  726. Account.EncryptedPassword
  727. ));
  728. } else {
  729. Account.Password = copyPwd;
  730. DEBUGMSG ((
  731. DBG_ACCOUNTS,
  732. "Per-user password specified for %s: %s",
  733. Account.User,
  734. Account.Password
  735. ));
  736. }
  737. } else {
  738. if (g_ConfigOptions.DefaultPassword && !StringMatch (g_ConfigOptions.DefaultPassword, TEXT("*"))) {
  739. if (g_ConfigOptions.EncryptedUserPasswords) {
  740. pGenerateRandomPassword (randomPwd);
  741. Account.Password = randomPwd;
  742. Account.EncryptedPassword = g_ConfigOptions.DefaultPassword;
  743. DEBUGMSG ((
  744. DBG_ACCOUNTS,
  745. "Default encrypted password specified for %s: %s",
  746. Account.User,
  747. Account.EncryptedPassword
  748. ));
  749. } else {
  750. Account.Password = g_ConfigOptions.DefaultPassword;
  751. DEBUGMSG ((
  752. DBG_ACCOUNTS,
  753. "Default password specified for %s: %s",
  754. Account.User,
  755. Account.Password
  756. ));
  757. }
  758. } else {
  759. MemDbBuildKey (pattern, MEMDB_CATEGORY_USERPASSWORD, S_DEFAULTUSER, TEXT("*"), NULL);
  760. if (MemDbEnumFirstValue (
  761. &e,
  762. pattern,
  763. MEMDB_ALL_SUBLEVELS,
  764. MEMDB_ENDPOINTS_ONLY
  765. )) {
  766. //
  767. // check for the placeholder
  768. //
  769. if (StringMatch (e.szName, TEXT("*"))) {
  770. e.szName[0] = 0;
  771. }
  772. StackStringCopy (copyPwd, e.szName);
  773. attribs = e.dwValue;
  774. if (attribs & PASSWORD_ATTR_ENCRYPTED) {
  775. //
  776. // cannot use this hashed pwd to create the account
  777. // create a random one that will be replaced
  778. // using OWF hashing functions
  779. //
  780. pGenerateRandomPassword (randomPwd);
  781. Account.Password = randomPwd;
  782. Account.EncryptedPassword = copyPwd;
  783. DEBUGMSG ((
  784. DBG_ACCOUNTS,
  785. "Default encrypted password specified for %s: %s",
  786. Account.User,
  787. Account.EncryptedPassword
  788. ));
  789. } else {
  790. Account.Password = copyPwd;
  791. DEBUGMSG ((
  792. DBG_ACCOUNTS,
  793. "Per-user password specified for %s: %s",
  794. Account.User,
  795. Account.Password
  796. ));
  797. }
  798. } else {
  799. //
  800. // Random password generation code removed, blank
  801. // password used instead
  802. //
  803. //pGenerateRandomPassword (randomPwd);
  804. //Account.Password = randomPwd;
  805. //LOG ((LOG_ACCOUNTS, "Random password for %s is %s", Account.User, Account.Password));
  806. //
  807. //g_RandomPassword = TRUE;
  808. //attribs = PASSWORD_ATTR_RANDOM;
  809. //pAddUserToRegistryList (Account.User, FALSE);
  810. Account.Password = TEXT("");
  811. }
  812. }
  813. }
  814. Account.PasswordAttribs = attribs;
  815. //
  816. // put this user in the domain fix list if necessary
  817. //
  818. if (StringIMatch (User, g_AdministratorStr)) {
  819. if (!MemDbGetValueEx (&e, MEMDB_CATEGORY_STATE, MEMDB_ITEM_ADMIN_PASSWORD, NULL)) {
  820. MYASSERT (FALSE);
  821. e.dwValue = PASSWORD_ATTR_RANDOM;
  822. }
  823. if (e.dwValue & PASSWORD_ATTR_RANDOM) {
  824. //
  825. // change the password for admin, too, because it was randomly generated
  826. //
  827. pAddUserToRegistryList (g_AdministratorStr, FALSE);
  828. }
  829. //
  830. // don't change admin password now if the account is already created
  831. //
  832. Account.PasswordAttribs |= PASSWORD_ATTR_DONT_CHANGE_IF_EXIST;
  833. } else if (pWasWin9xOnTheNet()) {
  834. pAddUserToRegistryList (Account.User, TRUE);
  835. }
  836. //
  837. // Now create the local account
  838. //
  839. rc = CreateLocalAccount (&Account, NULL);
  840. FreeStringResource (Account.AdminComment);
  841. if (rc != ERROR_SUCCESS) {
  842. if (rc != ERROR_PASSWORD_RESTRICTION && rc != ERROR_INVALID_PARAMETER) {
  843. //
  844. // account could not be created
  845. //
  846. SetLastError (rc);
  847. LOG ((LOG_ERROR, (PCSTR)MSG_CREATE_ACCOUNT_FAILED, User));
  848. return FALSE;
  849. }
  850. //
  851. // this user's password must be re-set
  852. //
  853. pAddUserToRegistryList (Account.User, FALSE);
  854. }
  855. DupDomain = FALSE;
  856. }
  857. } else {
  858. DupDomain = FALSE;
  859. }
  860. if (DupDomain) {
  861. UserDetails.Domain = PoolMemDuplicateString (g_UserPool, Domain);
  862. }
  863. //
  864. // Get SID, looping until it's valid
  865. //
  866. do {
  867. if (GetUserSid (User, Domain, &SidBuf)) {
  868. //
  869. // User SID was found, so we copy it to our pool and throw away the
  870. // grow buffer.
  871. //
  872. UserDetails.Sid = (PSID) PoolMemGetMemory (g_UserPool, SidBuf.End);
  873. CopyMemory (UserDetails.Sid, SidBuf.Buf, SidBuf.End);
  874. FreeGrowBuffer (&SidBuf);
  875. }
  876. else {
  877. //
  878. // User SID was not found. We ask the user if they wish to retry.
  879. //
  880. PCWSTR ArgArray[1];
  881. ArgArray[0] = User;
  882. if (!RetryMessageBox (MSG_SID_RETRY, ArgArray)) {
  883. if (!Domain) {
  884. LOG ((LOG_ERROR, (PCSTR)MSG_SID_LOOKUP_FAILED, User));
  885. return FALSE;
  886. } else {
  887. LOG ((LOG_ERROR, "Can't get SID for %s on %s, going to local account", User, Domain));
  888. return pAddUser (User, NULL);
  889. }
  890. }
  891. }
  892. } while (!UserDetails.Sid);
  893. if (CreateAccountFlag) {
  894. //
  895. // Get the user profile path
  896. //
  897. if (!CreateUserProfile (UserDetails.Sid, User, NULL, UserProfilePath, MAX_TCHAR_PATH)) {
  898. LOG ((LOG_ERROR, (PCSTR)MSG_PROFILE_LOOKUP_FAILED, User));
  899. return FALSE;
  900. }
  901. UserDetails.ProfilePath = PoolMemDuplicateString (g_UserPool, UserProfilePath);
  902. DEBUGMSG ((DBG_ACCOUNTS, "User %s has profile path %s", User, UserProfilePath));
  903. }
  904. //
  905. // Save user details in string table
  906. //
  907. pSetupStringTableAddStringEx (
  908. g_UserTable,
  909. (PWSTR) User,
  910. STRTAB_CASE_INSENSITIVE,
  911. (PBYTE) &UserDetails,
  912. sizeof (USERDETAILS)
  913. );
  914. DEBUGMSG_IF ((Domain != NULL, DBG_ACCOUNTS, "%s\\%s added to user list", Domain, User));
  915. DEBUGMSG_IF ((Domain == NULL, DBG_ACCOUNTS, "%s added to user list", User));
  916. return TRUE;
  917. }
  918. BOOL
  919. pAddLocalGroup (
  920. IN PCWSTR Group
  921. )
  922. /*++
  923. Routine Description:
  924. Adds a local group to the array of users. This function allows local accounts
  925. to be added to the user list.
  926. Arguments:
  927. Group - Specifies the name of the local group to add to the list
  928. Return value:
  929. TRUE if add was successful
  930. --*/
  931. {
  932. USERDETAILS UserDetails;
  933. GROWBUFFER SidBuf = GROWBUF_INIT;
  934. UserDetails.Domain = NULL;
  935. UserDetails.Sid = NULL;
  936. if (GetUserSid (Group, NULL, &SidBuf)) {
  937. //
  938. // User SID was found, so we copy it to our pool and throw away the
  939. // grow buffer.
  940. //
  941. UserDetails.Sid = (PSID) PoolMemGetMemory (g_UserPool, SidBuf.End);
  942. CopyMemory (UserDetails.Sid, SidBuf.Buf, SidBuf.End);
  943. FreeGrowBuffer (&SidBuf);
  944. } else {
  945. LOG ((LOG_ERROR, "%s is not a local group", Group));
  946. return FALSE;
  947. }
  948. pSetupStringTableAddStringEx (
  949. g_UserTable,
  950. (PWSTR) Group,
  951. STRTAB_CASE_INSENSITIVE,
  952. (PBYTE) &UserDetails,
  953. sizeof (USERDETAILS)
  954. );
  955. DEBUGMSG ((DBG_ACCOUNTS, "%s added to user list", Group));
  956. return TRUE;
  957. }
  958. BOOL
  959. pAddDomainGroup (
  960. IN PCWSTR Group
  961. )
  962. /*++
  963. Routine Description:
  964. Adds a domain group to the array of users. This function is used to
  965. add network domain groups to the user list.
  966. Arguments:
  967. Group - Specifies the name of the domain group to add to the list
  968. Return value:
  969. TRUE if add was successful
  970. --*/
  971. {
  972. TCHAR DomainName[MAX_SERVER_NAME];
  973. BYTE SidBuffer[MAX_SID_SIZE];
  974. USERDETAILS UserDetails;
  975. GROWBUFFER SidBuf = GROWBUF_INIT;
  976. if (!GetPrimaryDomainName (DomainName)) {
  977. DEBUGMSG ((DBG_WARNING, "Can't get primary domain name"));
  978. return FALSE;
  979. }
  980. if (!GetPrimaryDomainSid (SidBuffer, sizeof (SidBuffer))) {
  981. LOG ((LOG_ERROR, "Can't get primary domain SID of %s", DomainName));
  982. return FALSE;
  983. }
  984. UserDetails.Domain = DomainName;
  985. UserDetails.Sid = (PSID) SidBuffer;
  986. if (GetUserSid (Group, DomainName, &SidBuf)) {
  987. //
  988. // User SID was found, so we copy it to our pool and throw away the
  989. // grow buffer.
  990. //
  991. UserDetails.Sid = (PSID) PoolMemGetMemory (g_UserPool, SidBuf.End);
  992. CopyMemory (UserDetails.Sid, SidBuf.Buf, SidBuf.End);
  993. FreeGrowBuffer (&SidBuf);
  994. } else {
  995. DEBUGMSG ((DBG_WARNING, "Can't get SID of %s in %s", Group, DomainName));
  996. FreeGrowBuffer (&SidBuf);
  997. return FALSE;
  998. }
  999. pSetupStringTableAddStringEx (
  1000. g_UserTable,
  1001. (PWSTR) Group,
  1002. STRTAB_CASE_INSENSITIVE,
  1003. (PBYTE) &UserDetails,
  1004. sizeof (USERDETAILS)
  1005. );
  1006. DEBUGMSG ((DBG_ACCOUNTS, "%s\\%s added to user list", DomainName, Group));
  1007. return TRUE;
  1008. }
  1009. VOID
  1010. pResolveMultipleDomains (
  1011. VOID
  1012. )
  1013. /*++
  1014. Routine Description:
  1015. Perpares an array of users and presents a dialog allowing the installer
  1016. to choose an action for each user. A user may be a local user, multiple domains
  1017. may be resolved, or the installer may choose to retry the account search.
  1018. The account list is updated based on the installer's choices.
  1019. Arguments:
  1020. none
  1021. Return value:
  1022. none
  1023. --*/
  1024. {
  1025. GROWBUFFER UserList = GROWBUF_INIT;
  1026. POOLHANDLE UserListData = NULL;
  1027. ACCT_ENUM UserEnum;
  1028. DWORD PossibleDomains;
  1029. PRESOLVE_ACCOUNTS_ARRAY Array = NULL;
  1030. PCTSTR User;
  1031. PCTSTR Domain;
  1032. PCTSTR *DomainList;
  1033. __try {
  1034. UserListData = PoolMemInitNamedPool ("UserListData");
  1035. //
  1036. // Create list of user accounts and allow the installer to decide weather
  1037. // to retry the search, use a local account, or choose a domain when
  1038. // multiple choices exist.
  1039. //
  1040. if (FindDomainInList (&UserEnum, S_UNKNOWN_DOMAIN)) {
  1041. User = ListFirstUserInDomain (&UserEnum, &UserEnum);
  1042. while (User) {
  1043. PossibleDomains = CountPossibleDomains (&UserEnum);
  1044. Array = (PRESOLVE_ACCOUNTS_ARRAY) GrowBuffer (
  1045. &UserList,
  1046. sizeof (RESOLVE_ACCOUNTS_ARRAY)
  1047. );
  1048. ZeroMemory (Array, sizeof (RESOLVE_ACCOUNTS_ARRAY));
  1049. Array->UserName = PoolMemDuplicateString (UserListData, User);
  1050. Array->DomainArray = (PCTSTR *) PoolMemGetAlignedMemory (
  1051. UserListData,
  1052. (PossibleDomains + 1) * sizeof (PCTSTR)
  1053. );
  1054. DomainList = Array->DomainArray;
  1055. Domain = ListFirstPossibleDomain (&UserEnum, &UserEnum);
  1056. while (Domain) {
  1057. *DomainList = PoolMemDuplicateString (UserListData, Domain);
  1058. DomainList++;
  1059. Domain = ListNextPossibleDomain (&UserEnum);
  1060. }
  1061. *DomainList = NULL;
  1062. //
  1063. // If UseLocalAccountOnError config option is TRUE, we set OutboundDomain
  1064. // to NULL, ensuring a Local Account.
  1065. //
  1066. if (g_ConfigOptions.UseLocalAccountOnError) {
  1067. Array->OutboundDomain = NULL;
  1068. Array->RetryFlag = FALSE;
  1069. }
  1070. else {
  1071. Array->OutboundDomain = *Array->DomainArray;
  1072. Array->RetryFlag = TRUE;
  1073. }
  1074. User = ListNextUserInDomain (&UserEnum);
  1075. }
  1076. }
  1077. if (!Array) {
  1078. //
  1079. // No unresolved users
  1080. //
  1081. __leave;
  1082. }
  1083. //
  1084. // If UseLocalAccountOnError is specified, we've already set all unresolved accounts to
  1085. // Local account and we aren't going to throw up any UI. Otherwise, we'll give the user
  1086. // the resolve UI.
  1087. //
  1088. if (!g_ConfigOptions.UseLocalAccountOnError) {
  1089. Array = (PRESOLVE_ACCOUNTS_ARRAY) GrowBuffer (
  1090. &UserList,
  1091. sizeof (RESOLVE_ACCOUNTS_ARRAY)
  1092. );
  1093. ZeroMemory (Array, sizeof (RESOLVE_ACCOUNTS_ARRAY));
  1094. ResolveAccounts ((PRESOLVE_ACCOUNTS_ARRAY) UserList.Buf);
  1095. }
  1096. //
  1097. // Now process the installer's changes to the user list
  1098. //
  1099. Array = (PRESOLVE_ACCOUNTS_ARRAY) UserList.Buf;
  1100. FindDomainInList (&UserEnum, S_UNKNOWN_DOMAIN);
  1101. while (Array->UserName) {
  1102. if (!FindUserInDomain (&UserEnum, &UserEnum, Array->UserName)) {
  1103. DEBUGMSG ((DBG_WHOOPS, "Could not find user name %s in array!", Array->UserName));
  1104. }
  1105. else if (!Array->RetryFlag) {
  1106. //
  1107. // The installer chose either to make the account local, or to
  1108. // use one of the several possible domains.
  1109. //
  1110. pAddUser (Array->UserName, Array->OutboundDomain);
  1111. DeleteUserFromDomainList (&UserEnum);
  1112. } else {
  1113. ClearPossibleDomains (&UserEnum);
  1114. }
  1115. Array++;
  1116. }
  1117. }
  1118. __finally {
  1119. FreeGrowBuffer (&UserList);
  1120. PoolMemDestroyPool (UserListData);
  1121. }
  1122. }
  1123. BOOL
  1124. pMakeSureAccountsAreValid (
  1125. VOID
  1126. )
  1127. /*++
  1128. Routine Description:
  1129. Scans the domain lists and adds all valid users to our user list. Any
  1130. invalid users are put back in the unknown domain.
  1131. Arguments:
  1132. none
  1133. Return value:
  1134. TRUE if unresolved users exist (meaning the installer wants to retry
  1135. searching), or FALSE if all domains for each user has been resolved.
  1136. --*/
  1137. {
  1138. ACCT_ENUM UserEnum;
  1139. INT PossibleDomains;
  1140. PCWSTR Domain;
  1141. PCWSTR User;
  1142. BOOL b;
  1143. //
  1144. // Scan the unknown list for matches where one domain was found
  1145. //
  1146. if (FindDomainInList (&UserEnum, S_UNKNOWN_DOMAIN)) {
  1147. User = ListFirstUserInDomain (&UserEnum, &UserEnum);
  1148. while (User) {
  1149. PossibleDomains = CountPossibleDomains (&UserEnum);
  1150. if (PossibleDomains == 1 && !g_DomainProblem) {
  1151. Domain = ListFirstPossibleDomain (&UserEnum, &UserEnum);
  1152. pAddUser (User, Domain);
  1153. DeleteUserFromDomainList (&UserEnum);
  1154. }
  1155. User = ListNextUserInDomain (&UserEnum);
  1156. }
  1157. }
  1158. //
  1159. // Add all correct known accounts to our user list
  1160. //
  1161. Domain = ListFirstDomain (&UserEnum);
  1162. while (Domain) {
  1163. if (!IsTrustedDomain (&UserEnum)) {
  1164. Domain = ListNextDomain (&UserEnum);
  1165. continue;
  1166. }
  1167. User = ListFirstUserInDomain (&UserEnum, &UserEnum);
  1168. while (User) {
  1169. pAddUser (User, Domain);
  1170. DeleteUserFromDomainList (&UserEnum);
  1171. User = ListNextUserInDomain (&UserEnum);
  1172. }
  1173. Domain = ListNextDomain (&UserEnum);
  1174. }
  1175. //
  1176. // Move the failed list to the unknown list
  1177. //
  1178. if (FindDomainInList (&UserEnum, S_FAILED_DOMAIN)) {
  1179. User = ListFirstUserInDomain (&UserEnum, &UserEnum);
  1180. while (User) {
  1181. if (g_ConfigOptions.UseLocalAccountOnError) {
  1182. pAddUser (User, NULL);
  1183. DeleteUserFromDomainList (&UserEnum);
  1184. } else {
  1185. MoveUserToNewDomain (&UserEnum, S_UNKNOWN_DOMAIN);
  1186. }
  1187. User = ListNextUserInDomain (&UserEnum);
  1188. }
  1189. }
  1190. //
  1191. // Provide UI for remaining unknowns, allowing manual decision
  1192. // weather to make account local, or to decide between multiple
  1193. // domain matches.
  1194. //
  1195. HideStatusPopup (INFINITE);
  1196. pResolveMultipleDomains();
  1197. //
  1198. // Return TRUE if the unknown domain is not empty
  1199. //
  1200. b = FindDomainInList (&UserEnum, S_UNKNOWN_DOMAIN);
  1201. if (b) {
  1202. b = CountUsersInDomain (&UserEnum) != 0;
  1203. if (b) {
  1204. HideStatusPopup (STATUS_DELAY);
  1205. }
  1206. }
  1207. return b;
  1208. }
  1209. BOOL
  1210. pGetUserDetails (
  1211. IN PCWSTR User,
  1212. OUT PUSERDETAILS DetailsPtr
  1213. )
  1214. /*++
  1215. Routine Description:
  1216. A common routine that locates the USERDETAILS structure for a
  1217. given user.
  1218. Arguments:
  1219. User - Specifies fixed user name
  1220. DetailsPtr - Receives the structure for the user
  1221. Return value:
  1222. TRUE if the user details were found, or FALSE if the user does
  1223. not exist.
  1224. --*/
  1225. {
  1226. return (-1 != pSetupStringTableLookUpStringEx (
  1227. g_UserTable,
  1228. (PWSTR) User,
  1229. STRTAB_CASE_INSENSITIVE,
  1230. (PBYTE) DetailsPtr,
  1231. sizeof(USERDETAILS)
  1232. ));
  1233. }
  1234. PCWSTR
  1235. GetDomainForUser (
  1236. IN PCWSTR User
  1237. )
  1238. /*++
  1239. Routine Description:
  1240. Given a user name, this function returns a pointer to the domain name
  1241. maintained in the user list for the specified user.
  1242. Since Windows 95 does not support multiple domains for a single user,
  1243. the user list doesn't either.
  1244. Arguments:
  1245. User - Specifies fixed user name
  1246. Return value:
  1247. A pointer to the domain name for the user, or NULL if the user was not
  1248. found.
  1249. --*/
  1250. {
  1251. USERDETAILS Details;
  1252. if (pGetUserDetails (User, &Details)) {
  1253. return Details.Domain;
  1254. }
  1255. return NULL;
  1256. }
  1257. PSID
  1258. GetSidForUser (
  1259. PCWSTR User
  1260. )
  1261. /*++
  1262. Routine Description:
  1263. Given a user name, this function returns a pointer to the SID maintained
  1264. in the user list for the specified user.
  1265. Arguments:
  1266. User - Specifies fixed user name
  1267. Return value:
  1268. A pointer to the SID for the user, or NULL if the user was not found.
  1269. --*/
  1270. {
  1271. USERDETAILS Details;
  1272. if (pGetUserDetails (User, &Details)) {
  1273. return Details.Sid;
  1274. }
  1275. return NULL;
  1276. }
  1277. PCWSTR
  1278. GetProfilePathForUser (
  1279. IN PCWSTR User
  1280. )
  1281. /*++
  1282. Routine Description:
  1283. Given a user name, this function returns a pointer to the user's
  1284. profile, maintained in the user list for the specified user.
  1285. Arguments:
  1286. User - Specifies fixed user name
  1287. Return value:
  1288. A pointer to the user's profile path, or NULL if the specified user
  1289. was not in the list.
  1290. --*/
  1291. {
  1292. USERDETAILS Details;
  1293. if (pGetUserDetails (User, &Details)) {
  1294. return Details.ProfilePath;
  1295. }
  1296. return NULL;
  1297. }
  1298. BOOL
  1299. pWasWin9xOnTheNet (
  1300. VOID
  1301. )
  1302. /*++
  1303. Routine Description:
  1304. Queries memdb to determine if the machine had the MS Networking Client
  1305. installed.
  1306. Arguments:
  1307. none
  1308. Return value:
  1309. TRUE if the Win9x configuration had MSNP32 installed, FALSE if not.
  1310. --*/
  1311. {
  1312. TCHAR Node[MEMDB_MAX];
  1313. MemDbBuildKey (Node, MEMDB_CATEGORY_STATE, MEMDB_ITEM_MSNP32, NULL, NULL);
  1314. return MemDbGetValue (Node, NULL);
  1315. }
  1316. VOID
  1317. pGenerateRandomPassword (
  1318. OUT PTSTR Password
  1319. )
  1320. /*++
  1321. Routine Description:
  1322. pGenerateRandomPassword creates a password of upper-case, lower-case and
  1323. numeric letters. The password has a length between 8 and 14
  1324. characters.
  1325. Arguments:
  1326. Password - Receives the generated password
  1327. Return Value:
  1328. none
  1329. --*/
  1330. {
  1331. INT Length;
  1332. TCHAR Offset;
  1333. INT Limit;
  1334. PTSTR p;
  1335. //
  1336. // Generate a random length based on the tick count
  1337. //
  1338. srand (GetTickCount());
  1339. Length = (rand() % 6) + 8;
  1340. p = Password;
  1341. while (Length) {
  1342. Limit = rand() % 3;
  1343. Offset = TEXT(' ');
  1344. if (Limit == 0) {
  1345. Limit = 10;
  1346. Offset = TEXT('0');
  1347. } else if (Limit == 1) {
  1348. Limit = 26;
  1349. Offset = TEXT('a');
  1350. } else if (Limit == 2) {
  1351. Limit = 26;
  1352. Offset = TEXT('A');
  1353. }
  1354. *p = Offset + (rand() % Limit);
  1355. p++;
  1356. Length--;
  1357. }
  1358. *p = 0;
  1359. DEBUGMSG ((DBG_ACCOUNTS, "Generated password: %s", Password));
  1360. }