123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- /*++
- Copyright (c) 2015 Minoca Corp. All Rights Reserved
- Module Name:
- chpasswd.c
- Abstract:
- This module implements the chpasswd command, which allows passwords to be
- changed in bulk.
- Author:
- Evan Green 12-Mar-2015
- Environment:
- POSIX
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/lib/types.h>
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <getopt.h>
- #include <stdlib.h>
- #include <string.h>
- #include <syslog.h>
- #include <unistd.h>
- #include "../swlib.h"
- #include "lutil.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define CHPASSWD_VERSION_MAJOR 1
- #define CHPASSWD_VERSION_MINOR 0
- #define CHPASSWD_USAGE \
- "usage: chpasswd [options]\n" \
- "The chpasswd utility changes user passwords in bulk by reading from \n" \
- "standard in lines in the form of user:newpassword. Options are:\n" \
- " -c, --crypt-method=method -- Use the specified method to encrypt \n" \
- " passwords. Valid values are md5, sha256, and sha512.\n" \
- " -e, --encrypted -- Specifies that incoming passwords are already " \
- "encrypted.\n" \
- " -S --stdout -- Report encrypted passwords to stdout instead of \n" \
- " updating the password file.\n" \
- " -m, --md5 -- Use the MD5 hashing algorithm.\n" \
- " -R, --root=dir -- Chroot into the given directory before operating.\n" \
- " -s, --sha-rounds=rounds -- Use the specified number of rounds to \n" \
- " encrypt the passwords. 0 uses the default.\n" \
- " --help -- Displays this help text and exits.\n" \
- " --version -- Displays the application version and exits.\n"
- #define CHPASSWD_OPTIONS_STRING "c:eSmR:sHV"
- //
- // Define application options.
- //
- //
- // Set this option if the passwords are already encrypted.
- //
- #define CHPASSWD_OPTION_PRE_ENCRYPTED 0x00000001
- //
- // Set this option to write the results to stdout.
- //
- #define CHPASSWD_OPTION_WRITE_STDOUT 0x00000002
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- //
- // -------------------------------------------------------------------- Globals
- //
- struct option ChpasswdLongOptions[] = {
- {"crypt-method", required_argument, 0, 'c'},
- {"encrypted", no_argument, 0, 'e'},
- {"stdout", required_argument, 0, 'S'},
- {"md5", no_argument, 0, 'm'},
- {"root", required_argument, 0, 'R'},
- {"sha-rounds", required_argument, 0, 's'},
- {"help", no_argument, 0, 'H'},
- {"version", no_argument, 0, 'V'},
- {NULL, 0, 0, 0},
- };
- //
- // ------------------------------------------------------------------ Functions
- //
- INT
- ChpasswdMain (
- INT ArgumentCount,
- CHAR **Arguments
- )
- /*++
- Routine Description:
- This routine is the main entry point for the chpasswd utility, which
- allows passwords to be changed in bulk.
- Arguments:
- ArgumentCount - Supplies the number of command line arguments the program
- was invoked with.
- Arguments - Supplies a tokenized array of command line arguments.
- Return Value:
- Returns an integer exit code. 0 for success, nonzero otherwise.
- --*/
- {
- PSTR AfterScan;
- PSTR Algorithm;
- PPASSWD_ALGORITHM AlgorithmEntry;
- ULONG ArgumentIndex;
- char *Line;
- size_t LineBufferSize;
- ULONGLONG LineNumber;
- ssize_t LineSize;
- PSTR NewPassword;
- INT Option;
- ULONG Options;
- PSTR Password;
- int RandomSource;
- PSTR RootDirectory;
- ULONG Rounds;
- struct spwd *Shadow;
- int Status;
- int TotalStatus;
- struct passwd *User;
- PSTR UserName;
- Algorithm = PASSWD_DEFAULT_ALGORITHM;
- Line = NULL;
- LineBufferSize = 0;
- NewPassword = NULL;
- Options = 0;
- Password = NULL;
- RandomSource = -1;
- RootDirectory = NULL;
- Rounds = 0;
- TotalStatus = 0;
- UserName = NULL;
- openlog("chpasswd", 0, LOG_AUTH);
- //
- // Process the control arguments.
- //
- while (TRUE) {
- Option = getopt_long(ArgumentCount,
- Arguments,
- CHPASSWD_OPTIONS_STRING,
- ChpasswdLongOptions,
- NULL);
- if (Option == -1) {
- break;
- }
- if ((Option == '?') || (Option == ':')) {
- Status = 1;
- goto MainEnd;
- }
- switch (Option) {
- case 'm':
- optarg = "md5";
- //
- // Fall through.
- //
- case 'c':
- if (strcasecmp(optarg, "des") == 0) {
- SwPrintError(0, NULL, "The DES algorithm has been deprecated");
- Status = 1;
- goto MainEnd;
- }
- AlgorithmEntry = &(SwPasswordAlgorithms[0]);
- while (AlgorithmEntry->Name != NULL) {
- if (strcasecmp(optarg, AlgorithmEntry->Name) == 0) {
- Algorithm = AlgorithmEntry->Id;
- break;
- }
- AlgorithmEntry += 1;
- }
- if (AlgorithmEntry->Name == NULL) {
- SwPrintError(0, optarg, "Unknown algorithm");
- Status = 1;
- goto MainEnd;
- }
- break;
- case 'e':
- Options |= CHPASSWD_OPTION_PRE_ENCRYPTED;;
- break;
- case 'S':
- Options |= CHPASSWD_OPTION_WRITE_STDOUT;
- break;
- case 'R':
- RootDirectory = optarg;
- break;
- case 's':
- Rounds = strtoul(optarg, &AfterScan, 10);
- if (AfterScan == optarg) {
- SwPrintError(0, optarg, "Invalid rounds");
- Status = 1;
- goto MainEnd;
- }
- break;
- case 'V':
- SwPrintVersion(CHPASSWD_VERSION_MAJOR, CHPASSWD_VERSION_MINOR);
- return 1;
- case 'H':
- printf(CHPASSWD_USAGE);
- return 1;
- default:
- assert(FALSE);
- Status = 1;
- goto MainEnd;
- }
- }
- ArgumentIndex = optind;
- if (ArgumentIndex > ArgumentCount) {
- ArgumentIndex = ArgumentCount;
- }
- if (ArgumentIndex != ArgumentCount) {
- SwPrintError(0, NULL, "Unexpected arguments");
- Status = 1;
- goto MainEnd;
- }
- if (getuid() != 0) {
- SwPrintError(0, NULL, "You must be root to do this");
- Status = 1;
- goto MainEnd;
- }
- //
- // Try to open /dev/urandom before chrooting in case the root doesn't have
- // it. Don't freak out if this fails, as maybe the root does have it.
- //
- RandomSource = open(URANDOM_PATH, O_RDONLY);
- //
- // Chroot if requested. Warm up crypt first in case libcrypt isn't in the
- // chrooted environment.
- //
- if (RootDirectory != NULL) {
- SwCrypt(NULL, NULL);
- Status = chroot(RootDirectory);
- if (Status != 0) {
- Status = errno;
- SwPrintError(Status, RootDirectory, "Failed to chroot");
- goto MainEnd;
- }
- Status = chdir("/");
- if (Status != 0) {
- Status = errno;
- SwPrintError(Status, RootDirectory, "Failed to chdir");
- goto MainEnd;
- }
- }
- //
- // Loop reading lines and changing passwords.
- //
- LineNumber = 1;
- while (TRUE) {
- if (Line != NULL) {
- memset(Line, 0, strlen(Line));
- }
- if ((NewPassword != Password) && (NewPassword != NULL)) {
- memset(NewPassword, 0, strlen(NewPassword));
- NewPassword = NULL;
- }
- LineSize = getline(&Line, &LineBufferSize, stdin);
- if (LineSize < 0) {
- break;
- }
- while ((LineSize != 0) && (isspace(Line[LineSize - 1]))) {
- LineSize -= 1;
- }
- UserName = Line;
- UserName[LineSize] = '\0';
- //
- // Get past whitespace.
- //
- while (isspace(*UserName)) {
- UserName += 1;
- }
- //
- // Skip any commented lines.
- //
- if ((*UserName == '\0') || (*UserName == '#')) {
- LineNumber += 1;
- continue;
- }
- Password = strchr(UserName, ':');
- if (Password == NULL) {
- SwPrintError(0, NULL, "Line %I64d missing password", LineNumber);
- TotalStatus = 1;
- LineNumber += 1;
- continue;
- }
- if (Password == UserName) {
- SwPrintError(0, NULL, "Line %I64d missing username", LineNumber);
- TotalStatus = 1;
- LineNumber += 1;
- continue;
- }
- *Password = '\0';
- Password += 1;
- User = getpwnam(UserName);
- if (User == NULL) {
- SwPrintError(0,
- NULL,
- "User %s not found (line %I64d)",
- UserName,
- LineNumber);
- TotalStatus = 1;
- LineNumber += 1;
- continue;
- }
- errno = 0;
- Shadow = getspnam(UserName);
- if ((Shadow == NULL) && (errno != ENOENT)) {
- SwPrintError(0,
- NULL,
- "Shadow entry not found for user %s on line %I64d",
- UserName,
- LineNumber);
- TotalStatus = 1;
- LineNumber += 1;
- continue;
- }
- //
- // Get the new password, either it's pre-encrypted, or a hashed
- // password is created.
- //
- if ((Options & CHPASSWD_OPTION_PRE_ENCRYPTED) != 0) {
- NewPassword = Password;
- if (strchr(NewPassword, ':') != NULL) {
- SwPrintError(0,
- NULL,
- "Supposedly encrypted password has a colon");
- TotalStatus = 1;
- LineNumber += 1;
- continue;
- }
- } else {
- NewPassword = SwCreateHashedPassword(Algorithm,
- RandomSource,
- Rounds,
- Password);
- }
- //
- // Update shadow if it exists or just plain passwd if not.
- //
- if (Shadow != NULL) {
- Shadow->sp_pwdp = NewPassword;
- Shadow->sp_lstchg = time(NULL) / (3600 * 24);
- User->pw_passwd = PASSWORD_SHADOWED;
- } else {
- User->pw_passwd = NewPassword;
- }
- //
- // If writing to stdout, just print.
- //
- if ((Options & CHPASSWD_OPTION_WRITE_STDOUT) != 0) {
- printf("%s:%s\n", UserName, NewPassword);
- //
- // Actually change the password.
- //
- } else {
- Status = SwUpdatePasswordLine(User,
- Shadow,
- UpdatePasswordUpdateLine);
- if (Status != 0) {
- syslog(LOG_ERR,
- "Failed to change password for user %s on line %lld",
- UserName,
- LineNumber);
- SwPrintError(0,
- NULL,
- "Failed to change password for user %s on line "
- "%I64d",
- User->pw_name,
- LineNumber);
- TotalStatus = Status;
- } else {
- syslog(LOG_NOTICE, "Changed password for user %s", UserName);
- }
- }
- LineNumber += 1;
- }
- Status = 0;
- MainEnd:
- if (RandomSource >= 0) {
- close(RandomSource);
- }
- if (Line != NULL) {
- memset(Line, 0, strlen(Line));
- free(Line);
- }
- if (NewPassword != NULL) {
- memset(NewPassword, 0, strlen(NewPassword));
- }
- closelog();
- if ((TotalStatus == 0) && (Status != 0)) {
- TotalStatus = Status;
- }
- return TotalStatus;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
|