passwd.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. /*++
  2. Copyright (c) 2015 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. passwd.c
  5. Abstract:
  6. This module implements the passwd utility, which allows a user to change
  7. his or her password (or the superuser to change any password).
  8. Author:
  9. Evan Green 11-Mar-2015
  10. Environment:
  11. POSIX
  12. --*/
  13. //
  14. // ------------------------------------------------------------------- Includes
  15. //
  16. #include <minoca/lib/types.h>
  17. #include <assert.h>
  18. #include <dirent.h>
  19. #include <errno.h>
  20. #include <getopt.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <syslog.h>
  24. #include <unistd.h>
  25. #include "../swlib.h"
  26. #include "lutil.h"
  27. //
  28. // ---------------------------------------------------------------- Definitions
  29. //
  30. #define PASSWD_VERSION_MAJOR 1
  31. #define PASSWD_VERSION_MINOR 0
  32. #define PASSWD_USAGE \
  33. "usage: passwd [options] username\n" \
  34. "The passwd utility allows a user to change his or her password, or \n" \
  35. "allows the superuser to change any password. Options are:\n" \
  36. " -A, --algorithm -- Specifies the password algorighm to use.\n" \
  37. " The default is SHA512.\n" \
  38. " -d, --delete -- Delete a user's password (make it empty). This means\n" \
  39. " no password is necessary to log in to the account.\n" \
  40. " -l, --lock -- Lock the password, disabling password-based\n " \
  41. " authentication to this account.\n" \
  42. " -R, --root=dir -- Chroot into the given directory before operation.\n" \
  43. " -u, --unlock -- Unlock the password.\n" \
  44. " --help -- Displays this help text and exits.\n" \
  45. " --version -- Displays the application version and exits.\n"
  46. #define PASSWD_OPTIONS_STRING "A:dlR:uHV"
  47. //
  48. // Define application options.
  49. //
  50. //
  51. // Set this option to delete the password.
  52. //
  53. #define PASSWD_OPTION_DELETE 0x00000001
  54. //
  55. // Set this option to lock the password.
  56. //
  57. #define PASSWD_OPTION_LOCK 0x00000002
  58. //
  59. // Set this option to unlock the password.
  60. //
  61. #define PASSWD_OPTION_UNLOCK 0x00000004
  62. //
  63. // Define the set of password options that need root.
  64. //
  65. #define PASSWD_OPTIONS_ROOT \
  66. (PASSWD_OPTION_DELETE | PASSWD_OPTION_LOCK | PASSWD_OPTION_UNLOCK)
  67. //
  68. // Define the number of times to attempt to get a matching and different new
  69. // password.
  70. //
  71. #define PASSWD_NEW_ATTEMPTS 3
  72. //
  73. // ------------------------------------------------------ Data Type Definitions
  74. //
  75. //
  76. // ----------------------------------------------- Internal Function Prototypes
  77. //
  78. PSTR
  79. PasswdGetNewPassword (
  80. struct passwd *User,
  81. struct spwd *Shadow,
  82. uid_t CurrentUid,
  83. PSTR Algorithm
  84. );
  85. VOID
  86. PasswdLogMessage (
  87. int Priority,
  88. PSTR Format,
  89. ...
  90. );
  91. //
  92. // -------------------------------------------------------------------- Globals
  93. //
  94. struct option PasswdLongOptions[] = {
  95. {"algorithm", required_argument, 0, 'A'},
  96. {"delete", no_argument, 0, 'd'},
  97. {"lock", no_argument, 0, 'l'},
  98. {"root", required_argument, 0, 'R'},
  99. {"unlock", no_argument, 0, 'u'},
  100. {"help", no_argument, 0, 'H'},
  101. {"version", no_argument, 0, 'V'},
  102. {NULL, 0, 0, 0},
  103. };
  104. //
  105. // ------------------------------------------------------------------ Functions
  106. //
  107. INT
  108. PasswdMain (
  109. INT ArgumentCount,
  110. CHAR **Arguments
  111. )
  112. /*++
  113. Routine Description:
  114. This routine is the main entry point for the passwd utility.
  115. Arguments:
  116. ArgumentCount - Supplies the number of command line arguments the program
  117. was invoked with.
  118. Arguments - Supplies a tokenized array of command line arguments.
  119. Return Value:
  120. Returns an integer exit code. 0 for success, nonzero otherwise.
  121. --*/
  122. {
  123. PSTR Algorithm;
  124. PPASSWD_ALGORITHM AlgorithmEntry;
  125. ULONG ArgumentIndex;
  126. size_t Length;
  127. struct spwd LocalShadow;
  128. PSTR NewPassword;
  129. PSTR OldPassword;
  130. INT Option;
  131. ULONG Options;
  132. PSTR RootDirectory;
  133. struct spwd *Shadow;
  134. UPDATE_PASSWORD_OPERATION ShadowOperation;
  135. int Status;
  136. PSTR ThisUserName;
  137. int TotalStatus;
  138. struct passwd *User;
  139. uid_t UserId;
  140. PSTR UserName;
  141. Algorithm = PASSWD_DEFAULT_ALGORITHM;
  142. NewPassword = NULL;
  143. Options = 0;
  144. RootDirectory = NULL;
  145. ShadowOperation = UpdatePasswordUpdateLine;
  146. ThisUserName = NULL;
  147. TotalStatus = 0;
  148. UserName = NULL;
  149. //
  150. // Process the control arguments.
  151. //
  152. while (TRUE) {
  153. Option = getopt_long(ArgumentCount,
  154. Arguments,
  155. PASSWD_OPTIONS_STRING,
  156. PasswdLongOptions,
  157. NULL);
  158. if (Option == -1) {
  159. break;
  160. }
  161. if ((Option == '?') || (Option == ':')) {
  162. Status = 1;
  163. goto MainEnd;
  164. }
  165. switch (Option) {
  166. //
  167. // Select a new password hashing algorithm.
  168. //
  169. case 'A':
  170. if (strcasecmp(optarg, "des") == 0) {
  171. SwPrintError(0, NULL, "The DES algorithm has been deprecated");
  172. Status = 1;
  173. goto MainEnd;
  174. }
  175. AlgorithmEntry = &(SwPasswordAlgorithms[0]);
  176. while (AlgorithmEntry->Name != NULL) {
  177. if (strcasecmp(optarg, AlgorithmEntry->Name) == 0) {
  178. Algorithm = AlgorithmEntry->Id;
  179. break;
  180. }
  181. AlgorithmEntry += 1;
  182. }
  183. if (AlgorithmEntry->Name == NULL) {
  184. SwPrintError(0, optarg, "Unknown algorithm");
  185. Status = 1;
  186. goto MainEnd;
  187. }
  188. break;
  189. case 'd':
  190. Options |= PASSWD_OPTION_DELETE;
  191. break;
  192. case 'l':
  193. Options |= PASSWD_OPTION_LOCK;
  194. Options &= ~PASSWD_OPTION_UNLOCK;
  195. break;
  196. case 'R':
  197. RootDirectory = optarg;
  198. break;
  199. case 'u':
  200. Options |= PASSWD_OPTION_UNLOCK;
  201. Options &= ~PASSWD_OPTION_LOCK;
  202. break;
  203. case 'V':
  204. SwPrintVersion(PASSWD_VERSION_MAJOR, PASSWD_VERSION_MINOR);
  205. return 1;
  206. case 'H':
  207. printf(PASSWD_USAGE);
  208. return 1;
  209. default:
  210. assert(FALSE);
  211. Status = 1;
  212. goto MainEnd;
  213. }
  214. }
  215. ArgumentIndex = optind;
  216. if (ArgumentIndex > ArgumentCount) {
  217. ArgumentIndex = ArgumentCount;
  218. }
  219. //
  220. // Chroot if requested. Warm up libcrypt in case it's not in the chrooted
  221. // environment.
  222. //
  223. if (RootDirectory != NULL) {
  224. SwCrypt(NULL, NULL);
  225. Status = chroot(RootDirectory);
  226. if (Status != 0) {
  227. Status = errno;
  228. SwPrintError(Status, RootDirectory, "Failed to chroot");
  229. goto MainEnd;
  230. }
  231. Status = chdir("/");
  232. if (Status != 0) {
  233. Status = errno;
  234. SwPrintError(Status, RootDirectory, "Failed to chdir");
  235. goto MainEnd;
  236. }
  237. }
  238. if (ArgumentIndex < ArgumentCount) {
  239. UserName = Arguments[ArgumentIndex];
  240. ArgumentIndex += 1;
  241. }
  242. if (ArgumentIndex != ArgumentCount) {
  243. SwPrintError(0, NULL, "Unexpected additional arguments");
  244. return 1;
  245. }
  246. openlog("passwd", 0, LOG_AUTH);
  247. UserId = getuid();
  248. //
  249. // Only root can delete, lock, or unlock a password.
  250. //
  251. if (UserId != 0) {
  252. if ((Options & PASSWD_OPTIONS_ROOT) != 0) {
  253. SwPrintError(0, NULL, "-l, -u, and -d require root privileges");
  254. Status = 1;
  255. goto MainEnd;
  256. }
  257. }
  258. //
  259. // Get the current user's name.
  260. //
  261. User = getpwuid(UserId);
  262. if (User == NULL) {
  263. SwPrintError(0, NULL, "User %ld not found", (long int)UserId);
  264. Status = ENOENT;
  265. goto MainEnd;
  266. }
  267. ThisUserName = strdup(User->pw_name);
  268. if (ThisUserName == NULL) {
  269. Status = ENOMEM;
  270. goto MainEnd;
  271. }
  272. //
  273. // If the user specified a login name on the command line, get that one.
  274. //
  275. if (UserName != NULL) {
  276. User = getpwnam(UserName);
  277. }
  278. //
  279. // The user can only change their own password, except for the superuser.
  280. //
  281. if ((UserId != 0) && (User->pw_uid != UserId)) {
  282. PasswdLogMessage(LOG_WARNING,
  283. "passwd: User %s cannot change password for "
  284. "account %s",
  285. ThisUserName,
  286. User->pw_name);
  287. Status = EPERM;
  288. goto MainEnd;
  289. }
  290. //
  291. // Get the shadow data.
  292. //
  293. errno = 0;
  294. Shadow = getspnam(User->pw_name);
  295. if ((Shadow == NULL) && (errno != ENOENT)) {
  296. Status = errno;
  297. PasswdLogMessage(LOG_WARNING,
  298. "passwd: warning: No shadow record of user %s, "
  299. "creating one",
  300. User->pw_name,
  301. strerror(Status));
  302. memcpy(&LocalShadow, &SwShadowTemplate, sizeof(struct spwd));
  303. LocalShadow.sp_namp = User->pw_name;
  304. LocalShadow.sp_lstchg = time(NULL) / (3600 * 24);
  305. Shadow = &LocalShadow;
  306. ShadowOperation = UpdatePasswordAddLine;
  307. }
  308. if (Shadow != NULL) {
  309. OldPassword = Shadow->sp_pwdp;
  310. } else {
  311. OldPassword = User->pw_passwd;
  312. }
  313. //
  314. // Potentially lock the password.
  315. //
  316. if ((Options & PASSWD_OPTION_LOCK) != 0) {
  317. if (OldPassword[0] != '!') {
  318. Length = strlen(OldPassword) + 2;
  319. NewPassword = malloc(Length);
  320. if (NewPassword != NULL) {
  321. NewPassword[0] = '!';
  322. strncpy(NewPassword + 1, OldPassword, Length - 1);
  323. }
  324. }
  325. //
  326. // Potentially unlock the password.
  327. //
  328. } else if ((Options & PASSWD_OPTION_UNLOCK) != 0) {
  329. if (OldPassword[0] == '!') {
  330. NewPassword = strdup(OldPassword + 1);
  331. }
  332. } else if ((Options & PASSWD_OPTION_DELETE) != 0) {
  333. NewPassword = strdup("");
  334. //
  335. // This is not a lock, unlock, or delete, just a regular password change.
  336. //
  337. } else {
  338. if ((UserId != 0) && (OldPassword[0] == '!')) {
  339. PasswdLogMessage(LOG_WARNING,
  340. "passwd: Cannot change password for %s: "
  341. "Account locked",
  342. User->pw_name);
  343. Status = EPERM;
  344. goto MainEnd;
  345. }
  346. NewPassword = PasswdGetNewPassword(User, Shadow, UserId, Algorithm);
  347. }
  348. if (NewPassword == NULL) {
  349. SwPrintError(0,
  350. NULL,
  351. "passwd: Password for %s is unchanged",
  352. User->pw_name);
  353. Status = 1;
  354. goto MainEnd;
  355. }
  356. if (Shadow != NULL) {
  357. Shadow->sp_pwdp = NewPassword;
  358. User->pw_passwd = PASSWORD_SHADOWED;
  359. Shadow->sp_lstchg = time(NULL) / (3600 * 24);
  360. } else {
  361. User->pw_passwd = NewPassword;
  362. }
  363. Status = SwUpdatePasswordLine(User, Shadow, ShadowOperation);
  364. if (Status < 0) {
  365. PasswdLogMessage(LOG_ERR,
  366. "passwd: Unable to change password for %s: %s",
  367. User->pw_name,
  368. strerror(Status));
  369. } else {
  370. PasswdLogMessage(LOG_NOTICE,
  371. "passwd: Password changed for user %s",
  372. User->pw_name);
  373. }
  374. MainEnd:
  375. closelog();
  376. if (NewPassword != NULL) {
  377. memset(NewPassword, 0, strlen(NewPassword));
  378. free(NewPassword);
  379. }
  380. if (ThisUserName != NULL) {
  381. free(ThisUserName);
  382. }
  383. if ((TotalStatus == 0) && (Status != 0)) {
  384. TotalStatus = Status;
  385. }
  386. return TotalStatus;
  387. }
  388. //
  389. // --------------------------------------------------------- Internal Functions
  390. //
  391. PSTR
  392. PasswdGetNewPassword (
  393. struct passwd *User,
  394. struct spwd *Shadow,
  395. uid_t CurrentUid,
  396. PSTR Algorithm
  397. )
  398. /*++
  399. Routine Description:
  400. This routine reads a new password in for the user.
  401. Arguments:
  402. User - Supplies a pointer to the user structure.
  403. Shadow - Supplies an optional pointer to the shadow structure.
  404. CurrentUid - Supplies the current running user ID.
  405. Algorithm - Supplies a pointer to the algorithm to use.
  406. Return Value:
  407. Returns a pointer to an allocated hashed password on success.
  408. NULL on failure.
  409. --*/
  410. {
  411. UINTN Attempt;
  412. BOOL Correct;
  413. PSTR CurrentPassword;
  414. int Match;
  415. PSTR NewHashedPassword;
  416. PSTR NewHashedPasswordCopy;
  417. PSTR NewPassword;
  418. PSTR NewPasswordCopy;
  419. PSTR OldPasswordHash;
  420. if (Shadow != NULL) {
  421. OldPasswordHash = Shadow->sp_pwdp;
  422. } else {
  423. OldPasswordHash = User->pw_passwd;
  424. }
  425. //
  426. // Validate the old password first.
  427. //
  428. if ((CurrentUid != 0) && (OldPasswordHash != NULL) &&
  429. (OldPasswordHash[0] != '\0')) {
  430. CurrentPassword = getpass("Old password: ");
  431. if (CurrentPassword == NULL) {
  432. return NULL;
  433. }
  434. Correct = SwCheckPassword(CurrentPassword, OldPasswordHash);
  435. //
  436. // Zero out the password buffer.
  437. //
  438. memset(CurrentPassword, 0, strlen(CurrentPassword));
  439. if (Correct == FALSE) {
  440. sleep(LOGIN_FAIL_DELAY);
  441. PasswdLogMessage(LOG_WARNING,
  442. "Incorrect password for %s",
  443. User->pw_name);
  444. return NULL;
  445. }
  446. }
  447. for (Attempt = 0; Attempt < PASSWD_NEW_ATTEMPTS; Attempt += 1) {
  448. //
  449. // Ask for the new password.
  450. //
  451. NewPassword = getpass("New password: ");
  452. if (NewPassword == NULL) {
  453. return NULL;
  454. }
  455. NewPasswordCopy = strdup(NewPassword);
  456. memset(NewPassword, 0, strlen(NewPassword));
  457. if (NewPasswordCopy == NULL) {
  458. return NULL;
  459. }
  460. //
  461. // Ask for it again.
  462. //
  463. NewPassword = getpass("Retype new password: ");
  464. if (NewPassword == NULL) {
  465. memset(NewPasswordCopy, 0, strlen(NewPasswordCopy));
  466. free(NewPasswordCopy);
  467. NewPasswordCopy = NULL;
  468. return NULL;
  469. }
  470. //
  471. // Complain if they don't match, it's the same as the old password,
  472. // or it's empty.
  473. //
  474. Match = strcmp(NewPassword, NewPasswordCopy);
  475. memset(NewPassword, 0, strlen(NewPassword));
  476. Correct = SwCheckPassword(NewPasswordCopy, OldPasswordHash);
  477. if ((Match != 0) ||
  478. (Correct != FALSE) ||
  479. (NewPasswordCopy[0] == '\0')) {
  480. if (Match != 0) {
  481. SwPrintError(0, NULL, "Passwords don't match");
  482. } else if (NewPasswordCopy[0] == '\0') {
  483. SwPrintError(0,
  484. NULL,
  485. "Error: Password is empty, use -d to delete a "
  486. "password");
  487. } else {
  488. SwPrintError(0,
  489. NULL,
  490. "New password is the same as the old one");
  491. }
  492. memset(NewPasswordCopy, 0, strlen(NewPasswordCopy));
  493. free(NewPasswordCopy);
  494. NewPasswordCopy = NULL;
  495. continue;
  496. }
  497. break;
  498. }
  499. //
  500. // Hash up the password.
  501. //
  502. NewHashedPassword = NULL;
  503. NewHashedPasswordCopy = NULL;
  504. if (NewPasswordCopy != NULL) {
  505. NewHashedPassword = SwCreateHashedPassword(Algorithm,
  506. -1,
  507. 0,
  508. NewPasswordCopy);
  509. memset(NewPasswordCopy, 0, strlen(NewPasswordCopy));
  510. free(NewPasswordCopy);
  511. //
  512. // Return a copy of that hash to get it out of the static buffer.
  513. //
  514. NewHashedPasswordCopy = strdup(NewHashedPassword);
  515. memset(NewHashedPassword, 0, strlen(NewHashedPassword));
  516. }
  517. return NewHashedPasswordCopy;
  518. }
  519. VOID
  520. PasswdLogMessage (
  521. int Priority,
  522. PSTR Format,
  523. ...
  524. )
  525. /*++
  526. Routine Description:
  527. This routine logs a message to both stderr and the syslog.
  528. Arguments:
  529. Priority - Supplies the priority of the log message. See LOG_* definitions.
  530. Format - Supplies the printf-style format string.
  531. ... - Supplies the additional arguments.
  532. Return Value:
  533. None.
  534. --*/
  535. {
  536. va_list ArgumentList;
  537. va_start(ArgumentList, Format);
  538. vsyslog(Priority, Format, ArgumentList);
  539. va_end(ArgumentList);
  540. va_start(ArgumentList, Format);
  541. vfprintf(stderr, Format, ArgumentList);
  542. va_end(ArgumentList);
  543. fputc('\n', stderr);
  544. return;
  545. }