chpasswd.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. /*++
  2. Copyright (c) 2015 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. chpasswd.c
  5. Abstract:
  6. This module implements the chpasswd command, which allows passwords to be
  7. changed in bulk.
  8. Author:
  9. Evan Green 12-Mar-2015
  10. Environment:
  11. POSIX
  12. --*/
  13. //
  14. // ------------------------------------------------------------------- Includes
  15. //
  16. #include <minoca/lib/types.h>
  17. #include <assert.h>
  18. #include <ctype.h>
  19. #include <errno.h>
  20. #include <fcntl.h>
  21. #include <getopt.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <syslog.h>
  25. #include <unistd.h>
  26. #include "../swlib.h"
  27. #include "lutil.h"
  28. //
  29. // ---------------------------------------------------------------- Definitions
  30. //
  31. #define CHPASSWD_VERSION_MAJOR 1
  32. #define CHPASSWD_VERSION_MINOR 0
  33. #define CHPASSWD_USAGE \
  34. "usage: chpasswd [options]\n" \
  35. "The chpasswd utility changes user passwords in bulk by reading from \n" \
  36. "standard in lines in the form of user:newpassword. Options are:\n" \
  37. " -c, --crypt-method=method -- Use the specified method to encrypt \n" \
  38. " passwords. Valid values are md5, sha256, and sha512.\n" \
  39. " -e, --encrypted -- Specifies that incoming passwords are already " \
  40. "encrypted.\n" \
  41. " -S --stdout -- Report encrypted passwords to stdout instead of \n" \
  42. " updating the password file.\n" \
  43. " -m, --md5 -- Use the MD5 hashing algorithm.\n" \
  44. " -R, --root=dir -- Chroot into the given directory before operating.\n" \
  45. " -s, --sha-rounds=rounds -- Use the specified number of rounds to \n" \
  46. " encrypt the passwords. 0 uses the default.\n" \
  47. " --help -- Displays this help text and exits.\n" \
  48. " --version -- Displays the application version and exits.\n"
  49. #define CHPASSWD_OPTIONS_STRING "c:eSmR:sHV"
  50. //
  51. // Define application options.
  52. //
  53. //
  54. // Set this option if the passwords are already encrypted.
  55. //
  56. #define CHPASSWD_OPTION_PRE_ENCRYPTED 0x00000001
  57. //
  58. // Set this option to write the results to stdout.
  59. //
  60. #define CHPASSWD_OPTION_WRITE_STDOUT 0x00000002
  61. //
  62. // ------------------------------------------------------ Data Type Definitions
  63. //
  64. //
  65. // ----------------------------------------------- Internal Function Prototypes
  66. //
  67. //
  68. // -------------------------------------------------------------------- Globals
  69. //
  70. struct option ChpasswdLongOptions[] = {
  71. {"crypt-method", required_argument, 0, 'c'},
  72. {"encrypted", no_argument, 0, 'e'},
  73. {"stdout", required_argument, 0, 'S'},
  74. {"md5", no_argument, 0, 'm'},
  75. {"root", required_argument, 0, 'R'},
  76. {"sha-rounds", required_argument, 0, 's'},
  77. {"help", no_argument, 0, 'H'},
  78. {"version", no_argument, 0, 'V'},
  79. {NULL, 0, 0, 0},
  80. };
  81. //
  82. // ------------------------------------------------------------------ Functions
  83. //
  84. INT
  85. ChpasswdMain (
  86. INT ArgumentCount,
  87. CHAR **Arguments
  88. )
  89. /*++
  90. Routine Description:
  91. This routine is the main entry point for the chpasswd utility, which
  92. allows passwords to be changed in bulk.
  93. Arguments:
  94. ArgumentCount - Supplies the number of command line arguments the program
  95. was invoked with.
  96. Arguments - Supplies a tokenized array of command line arguments.
  97. Return Value:
  98. Returns an integer exit code. 0 for success, nonzero otherwise.
  99. --*/
  100. {
  101. PSTR AfterScan;
  102. PSTR Algorithm;
  103. PPASSWD_ALGORITHM AlgorithmEntry;
  104. ULONG ArgumentIndex;
  105. char *Line;
  106. size_t LineBufferSize;
  107. ULONGLONG LineNumber;
  108. ssize_t LineSize;
  109. PSTR NewPassword;
  110. INT Option;
  111. ULONG Options;
  112. PSTR Password;
  113. int RandomSource;
  114. PSTR RootDirectory;
  115. ULONG Rounds;
  116. struct spwd *Shadow;
  117. int Status;
  118. int TotalStatus;
  119. struct passwd *User;
  120. PSTR UserName;
  121. Algorithm = PASSWD_DEFAULT_ALGORITHM;
  122. Line = NULL;
  123. LineBufferSize = 0;
  124. NewPassword = NULL;
  125. Options = 0;
  126. Password = NULL;
  127. RandomSource = -1;
  128. RootDirectory = NULL;
  129. Rounds = 0;
  130. TotalStatus = 0;
  131. UserName = NULL;
  132. openlog("chpasswd", 0, LOG_AUTH);
  133. //
  134. // Process the control arguments.
  135. //
  136. while (TRUE) {
  137. Option = getopt_long(ArgumentCount,
  138. Arguments,
  139. CHPASSWD_OPTIONS_STRING,
  140. ChpasswdLongOptions,
  141. NULL);
  142. if (Option == -1) {
  143. break;
  144. }
  145. if ((Option == '?') || (Option == ':')) {
  146. Status = 1;
  147. goto MainEnd;
  148. }
  149. switch (Option) {
  150. case 'm':
  151. optarg = "md5";
  152. //
  153. // Fall through.
  154. //
  155. case 'c':
  156. if (strcasecmp(optarg, "des") == 0) {
  157. SwPrintError(0, NULL, "The DES algorithm has been deprecated");
  158. Status = 1;
  159. goto MainEnd;
  160. }
  161. AlgorithmEntry = &(SwPasswordAlgorithms[0]);
  162. while (AlgorithmEntry->Name != NULL) {
  163. if (strcasecmp(optarg, AlgorithmEntry->Name) == 0) {
  164. Algorithm = AlgorithmEntry->Id;
  165. break;
  166. }
  167. AlgorithmEntry += 1;
  168. }
  169. if (AlgorithmEntry->Name == NULL) {
  170. SwPrintError(0, optarg, "Unknown algorithm");
  171. Status = 1;
  172. goto MainEnd;
  173. }
  174. break;
  175. case 'e':
  176. Options |= CHPASSWD_OPTION_PRE_ENCRYPTED;;
  177. break;
  178. case 'S':
  179. Options |= CHPASSWD_OPTION_WRITE_STDOUT;
  180. break;
  181. case 'R':
  182. RootDirectory = optarg;
  183. break;
  184. case 's':
  185. Rounds = strtoul(optarg, &AfterScan, 10);
  186. if (AfterScan == optarg) {
  187. SwPrintError(0, optarg, "Invalid rounds");
  188. Status = 1;
  189. goto MainEnd;
  190. }
  191. break;
  192. case 'V':
  193. SwPrintVersion(CHPASSWD_VERSION_MAJOR, CHPASSWD_VERSION_MINOR);
  194. return 1;
  195. case 'H':
  196. printf(CHPASSWD_USAGE);
  197. return 1;
  198. default:
  199. assert(FALSE);
  200. Status = 1;
  201. goto MainEnd;
  202. }
  203. }
  204. ArgumentIndex = optind;
  205. if (ArgumentIndex > ArgumentCount) {
  206. ArgumentIndex = ArgumentCount;
  207. }
  208. if (ArgumentIndex != ArgumentCount) {
  209. SwPrintError(0, NULL, "Unexpected arguments");
  210. Status = 1;
  211. goto MainEnd;
  212. }
  213. if (getuid() != 0) {
  214. SwPrintError(0, NULL, "You must be root to do this");
  215. Status = 1;
  216. goto MainEnd;
  217. }
  218. //
  219. // Try to open /dev/urandom before chrooting in case the root doesn't have
  220. // it. Don't freak out if this fails, as maybe the root does have it.
  221. //
  222. RandomSource = open(URANDOM_PATH, O_RDONLY);
  223. //
  224. // Chroot if requested. Warm up crypt first in case libcrypt isn't in the
  225. // chrooted environment.
  226. //
  227. if (RootDirectory != NULL) {
  228. SwCrypt(NULL, NULL);
  229. Status = chroot(RootDirectory);
  230. if (Status != 0) {
  231. Status = errno;
  232. SwPrintError(Status, RootDirectory, "Failed to chroot");
  233. goto MainEnd;
  234. }
  235. Status = chdir("/");
  236. if (Status != 0) {
  237. Status = errno;
  238. SwPrintError(Status, RootDirectory, "Failed to chdir");
  239. goto MainEnd;
  240. }
  241. }
  242. //
  243. // Loop reading lines and changing passwords.
  244. //
  245. LineNumber = 1;
  246. while (TRUE) {
  247. if (Line != NULL) {
  248. memset(Line, 0, strlen(Line));
  249. }
  250. if ((NewPassword != Password) && (NewPassword != NULL)) {
  251. memset(NewPassword, 0, strlen(NewPassword));
  252. NewPassword = NULL;
  253. }
  254. LineSize = getline(&Line, &LineBufferSize, stdin);
  255. if (LineSize < 0) {
  256. break;
  257. }
  258. while ((LineSize != 0) && (isspace(Line[LineSize - 1]))) {
  259. LineSize -= 1;
  260. }
  261. UserName = Line;
  262. UserName[LineSize] = '\0';
  263. //
  264. // Get past whitespace.
  265. //
  266. while (isspace(*UserName)) {
  267. UserName += 1;
  268. }
  269. //
  270. // Skip any commented lines.
  271. //
  272. if ((*UserName == '\0') || (*UserName == '#')) {
  273. LineNumber += 1;
  274. continue;
  275. }
  276. Password = strchr(UserName, ':');
  277. if (Password == NULL) {
  278. SwPrintError(0, NULL, "Line %I64d missing password", LineNumber);
  279. TotalStatus = 1;
  280. LineNumber += 1;
  281. continue;
  282. }
  283. if (Password == UserName) {
  284. SwPrintError(0, NULL, "Line %I64d missing username", LineNumber);
  285. TotalStatus = 1;
  286. LineNumber += 1;
  287. continue;
  288. }
  289. *Password = '\0';
  290. Password += 1;
  291. User = getpwnam(UserName);
  292. if (User == NULL) {
  293. SwPrintError(0,
  294. NULL,
  295. "User %s not found (line %I64d)",
  296. UserName,
  297. LineNumber);
  298. TotalStatus = 1;
  299. LineNumber += 1;
  300. continue;
  301. }
  302. errno = 0;
  303. Shadow = getspnam(UserName);
  304. if ((Shadow == NULL) && (errno != ENOENT)) {
  305. SwPrintError(0,
  306. NULL,
  307. "Shadow entry not found for user %s on line %I64d",
  308. UserName,
  309. LineNumber);
  310. TotalStatus = 1;
  311. LineNumber += 1;
  312. continue;
  313. }
  314. //
  315. // Get the new password, either it's pre-encrypted, or a hashed
  316. // password is created.
  317. //
  318. if ((Options & CHPASSWD_OPTION_PRE_ENCRYPTED) != 0) {
  319. NewPassword = Password;
  320. if (strchr(NewPassword, ':') != NULL) {
  321. SwPrintError(0,
  322. NULL,
  323. "Supposedly encrypted password has a colon");
  324. TotalStatus = 1;
  325. LineNumber += 1;
  326. continue;
  327. }
  328. } else {
  329. NewPassword = SwCreateHashedPassword(Algorithm,
  330. RandomSource,
  331. Rounds,
  332. Password);
  333. }
  334. //
  335. // Update shadow if it exists or just plain passwd if not.
  336. //
  337. if (Shadow != NULL) {
  338. Shadow->sp_pwdp = NewPassword;
  339. Shadow->sp_lstchg = time(NULL) / (3600 * 24);
  340. User->pw_passwd = PASSWORD_SHADOWED;
  341. } else {
  342. User->pw_passwd = NewPassword;
  343. }
  344. //
  345. // If writing to stdout, just print.
  346. //
  347. if ((Options & CHPASSWD_OPTION_WRITE_STDOUT) != 0) {
  348. printf("%s:%s\n", UserName, NewPassword);
  349. //
  350. // Actually change the password.
  351. //
  352. } else {
  353. Status = SwUpdatePasswordLine(User,
  354. Shadow,
  355. UpdatePasswordUpdateLine);
  356. if (Status != 0) {
  357. syslog(LOG_ERR,
  358. "Failed to change password for user %s on line %lld",
  359. UserName,
  360. LineNumber);
  361. SwPrintError(0,
  362. NULL,
  363. "Failed to change password for user %s on line "
  364. "%I64d",
  365. User->pw_name,
  366. LineNumber);
  367. TotalStatus = Status;
  368. } else {
  369. syslog(LOG_NOTICE, "Changed password for user %s", UserName);
  370. }
  371. }
  372. LineNumber += 1;
  373. }
  374. Status = 0;
  375. MainEnd:
  376. if (RandomSource >= 0) {
  377. close(RandomSource);
  378. }
  379. if (Line != NULL) {
  380. memset(Line, 0, strlen(Line));
  381. free(Line);
  382. }
  383. if (NewPassword != NULL) {
  384. memset(NewPassword, 0, strlen(NewPassword));
  385. }
  386. closelog();
  387. if ((TotalStatus == 0) && (Status != 0)) {
  388. TotalStatus = Status;
  389. }
  390. return TotalStatus;
  391. }
  392. //
  393. // --------------------------------------------------------- Internal Functions
  394. //