/* * Copyright Red Hat, Inc., 1998, 1999, 2001, 2002, 2015. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Written by Cristian Gafton */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pwdb.h" #ifdef WITH_SELINUX #include "selinux_utils.h" #else #define selinux_init(x) 0 #define selinux_check_root() 0 #endif #ifdef WITH_AUDIT #include #else #define audit_log_acct_message(d,ty,p,o,n,i,h,a,t,r) do { ; } while(0) static int audit_open(void) { errno = EPROTONOSUPPORT; return -1; } #endif #include #include static int audit_fd = -1; /* conversation function & corresponding structure */ static struct pam_conv conv = { misc_conv, NULL }; const char *username = NULL; /* username specified on the command line */ const char *progname = NULL; /* the name of the program */ int passwd_flags = 0; /* flags specified by root */ #define PASSWD_KEEP 0x0001 /* keep un-expired tokens */ #define PASSWD_LOCK 0x0002 /* lock the password */ #define PASSWD_UNLOCK 0x0004 /* unlock the password, if locked */ #define PASSWD_DELETE 0x0008 /* delete the user's password */ #define PASSWD_STATUS 0x0010 /* report the password status */ #define PASSWD_FORCE 0x0020 /* force change of expired token */ #define PASSWD_STDIN 0x0040 /* read the password from stdin (root only) */ #define PASSWD_EXPIRE 0x0080 /* expire the password */ #define PASSWD_ROOT 0x009E /* options which are mutually exclusive */ #define PASSWD_MIN 0x0100 /* set the minimum password lifetime */ #define PASSWD_MAX 0x0200 /* set the maximum password lifetime */ #define PASSWD_WARN 0x0400 /* set the password warning */ #define PASSWD_INACT 0x0800 /* set the inactive time */ #define PASSWD_AGING 0x0F00 /* aging options */ #ifdef HAVE_PAM_FAIL_DELAY #define PASSWD_FAIL_DELAY 2000000 /* usec delay on failure */ #endif /* A conversation function which uses an internally-stored value for * the responses. */ static int stdin_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { struct pam_response *reply; int count; /* Sanity test. */ if (num_msg <= 0) { return PAM_CONV_ERR; } /* Allocate memory for the responses. */ reply = calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { return PAM_CONV_ERR; } /* Each prompt elicits the same response. */ for (count = 0; count < num_msg; ++count) { if (msgm[count]->msg_style == PAM_PROMPT_ECHO_OFF) { reply[count].resp_retcode = 0; reply[count].resp = strdup(appdata_ptr); } else { reply[count].resp_retcode = 0; reply[count].resp = strdup(""); } } /* Set the pointers in the response structure and return. */ *response = reply; return PAM_SUCCESS; } /* Parse command-line arguments, rejecting conflicting flags and performing * various other initialization tasks. */ static void parse_args(int argc, const char **argv, long *min, long *max, long *warn, long *inact) { poptContext optCon; int delete = 0, force = 0, keep = 0, lock = 0, status = 0, unlock = 0; int expire = 0; int use_stdin = 0; int rc; const char **extraArgs; struct poptOption options[] = { {"keep-tokens", 'k', POPT_ARG_NONE, &keep, 0, _("keep non-expired authentication tokens"), NULL}, {"delete", 'd', POPT_ARG_NONE, &delete, 0, _("delete the password for the named account (root only); " "also removes password lock if any"), NULL}, {"lock", 'l', POPT_ARG_NONE, &lock, 0, _("lock the password for the named account (root only)"), NULL}, {"unlock", 'u', POPT_ARG_NONE, &unlock, 0, _("unlock the password for the named account (root only)"), NULL}, {"expire", 'e', POPT_ARG_NONE, &expire, 0, _("expire the password for the named account (root only)"), NULL}, {"force", 'f', POPT_ARG_NONE, &force, 0, _("force operation"), NULL}, {"maximum", 'x', POPT_ARG_LONG, max, 0, _("maximum password lifetime (root only)"), "DAYS"}, {"minimum", 'n', POPT_ARG_LONG, min, 0, _("minimum password lifetime (root only)"), "DAYS"}, {"warning", 'w', POPT_ARG_LONG, warn, 0, _("number of days warning users receives before password " "expiration (root only)"), "DAYS"}, {"inactive", 'i', POPT_ARG_LONG, inact, 0, _("number of days after password expiration when an account " "becomes disabled (root only)"), "DAYS"}, {"status", 'S', POPT_ARG_NONE, &status, 0, _("report password status on the named account (root only)"), NULL}, {"stdin", '\0', POPT_ARG_NONE, &use_stdin, 0, _("read new tokens from stdin (root only)"), NULL}, POPT_AUTOHELP POPT_TABLEEND }; struct passwd *pw; *min = *max = *warn = *inact = -2; optCon = poptGetContext("passwd", argc, argv, options, 0); poptSetOtherOptionHelp(optCon, _("[OPTION...] ")); if ((rc = poptGetNextOpt(optCon)) < -1) { fprintf(stderr, _("%s: bad argument %s: %s\n"), progname, poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(rc)); exit(-3); } extraArgs = poptGetArgs(optCon); if (keep) { passwd_flags |= PASSWD_KEEP; } if (lock) { passwd_flags |= PASSWD_LOCK; } if (unlock) { passwd_flags |= PASSWD_UNLOCK; } if (expire) { passwd_flags |= PASSWD_EXPIRE; } if (status) { passwd_flags |= PASSWD_STATUS; } if (delete) { passwd_flags |= PASSWD_DELETE; } if (force) { passwd_flags |= PASSWD_FORCE; } if (use_stdin) { passwd_flags |= PASSWD_STDIN; } if (*min != -2) { passwd_flags |= PASSWD_MIN; } if (*max != -2) { passwd_flags |= PASSWD_MAX; } if (*warn != -2) { passwd_flags |= PASSWD_WARN; } if (*inact != -2) { passwd_flags |= PASSWD_INACT; } /* The rest of the flags are mutually-exclusive, except for --force. */ if (passwd_flags) { int tmp; int count; tmp = passwd_flags & PASSWD_ROOT; count = 0; /* Check the rightmost bit and shift right. */ while (tmp != 0) { if (tmp & 0x01) { count++; } tmp = tmp >> 1; } /* Error if other bits are set. */ if (count > 1) { fprintf(stderr, _("%s: Only one of -l, -u, -d, -S may be specified.\n"), progname); exit(-2); } /* Error out if we had -l/-u/-d/-S and an aging option. */ if (count > 0) { tmp = passwd_flags & PASSWD_AGING; if (tmp != 0) { fprintf(stderr, _("%s: Cannot mix one of -l, -u, -d, -S and one of -i, -n, -w, -x.\n"), progname); exit(-2); } } } /* The only flag which unprivileged users get to use is -k. */ if ((passwd_flags & ~PASSWD_KEEP) && (getuid() != 0)) { /* Auditing is not needed for displaying status */ if (passwd_flags != PASSWD_STATUS) { audit_log_acct_message(audit_fd, AUDIT_USER_CHAUTHTOK, NULL, "attempted-to-change-password-attribute", NULL, getuid(), NULL, NULL, NULL, 0); } fprintf(stderr, _("Only root can do that.\n")); exit(-2); } /* Only root gets to specify a user name. */ username = NULL; if ((extraArgs != NULL) && (extraArgs[0] != NULL)) { if (getuid() != 0) { /* The invoking user was not root. */ audit_log_acct_message(audit_fd, AUDIT_USER_CHAUTHTOK, NULL, "attempted-to-change-password", extraArgs[0], getuid(), NULL, NULL, NULL, 0); fprintf(stderr, _("%s: Only root can specify a user name.\n"), progname); exit(-3); } else { /* The invoking user was root. */ username = extraArgs[0]; /* Sanity-check the user name */ if (strlen(username) > MAX_USERNAMESIZE) { fprintf(stderr, _("%s: The user name supplied is too long.\n"), progname); exit(-3); } } /* If there is more than one unrecognized argument, we suddenly * get confused. */ if (extraArgs[1] != NULL) { fprintf(stderr, _("%s: Only one user name may be specified.\n"), progname); exit(-3); } } /* Now if any of the -l, -u, -d, -S, -i, -n, -w, or -x options were * given, and a username was not specified, bail out. */ if ((passwd_flags & ~PASSWD_KEEP) && (username == NULL)) { fprintf(stderr, _("%s: This option requires a user name.\n"), progname); exit(-2); } /* Determine the name of the user whose account we're operating on, * and make sure the account exists. */ if (username == NULL) { /* The invoking user. */ pw = getpwuid(getuid()); if (pw == NULL) { fprintf(stderr, _("%s: Can not identify you!\n"), progname); exit(-3); } username = strdup(pw->pw_name); } else { /* The name specified on the command-line. */ pw = getpwnam(username); if (pw == NULL) { fprintf(stderr, _("%s: Unknown user name '%s'.\n"), progname, username); exit(-4); } } } int main(int argc, const char **argv) { int retval; long min, max, warn, inact; pam_handle_t *pamh = NULL; struct passwd *pwd; char *tty_name, *ttyn; setlocale(LC_ALL, ""); bindtextdomain("passwd", "/usr/share/locale"); textdomain("passwd"); audit_fd = audit_open(); if (audit_fd < 0 && !(errno == EINVAL || errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT)) { /* The above error codes are only given when the kernel doesn't * have audit compiled in. */ fprintf(stderr, "Error - unable to connect to audit system\n"); exit(1); } /* Parse command-line arguments. */ progname = basename(argv[0]); parse_args(argc, argv, &min, &max, &warn, &inact); pwd = getpwnam(username); if (pwd == NULL) { fprintf(stderr, _("%s: Unknown user name '%s'.\n"), progname, username); exit(-4); } if (audit_fd >= 0) selinux_init(audit_fd); if (selinux_check_root() != 0) { fprintf(stderr, _("%s: SELinux denying access due to security policy.\n"), progname); audit_log_acct_message(audit_fd, AUDIT_USER_CHAUTHTOK, NULL, "attempted-to-change-password", NULL, pwd->pw_uid, NULL, NULL, NULL, 0); exit(1); } /* Handle account locking request. */ if (passwd_flags & PASSWD_LOCK) { printf(_("Locking password for user %s.\n"), username); retval = pwdb_lock_password(username); printf("%s: %s\n", progname, retval == 0 ? _("Success") : _("Error (password not set?)")); audit_log_acct_message(audit_fd, AUDIT_ACCT_LOCK, NULL, "locked-password", NULL, pwd->pw_uid, NULL, NULL, NULL, retval == 0); return retval; } /* Handle account unlocking request. */ if (passwd_flags & PASSWD_UNLOCK) { printf(_("Unlocking password for user %s.\n"), username); retval = pwdb_unlock_password(username, passwd_flags & PASSWD_FORCE); printf("%s: %s\n", progname, retval == 0 ? _("Success") : retval == -2 ? _("Unsafe operation (use -f to force)") : _("Error (password not set?)")); audit_log_acct_message(audit_fd, AUDIT_ACCT_UNLOCK, NULL, "unlocked-password", NULL, pwd->pw_uid, NULL, NULL, NULL, retval == 0); return retval; } /* Handle password expiration request. */ if (passwd_flags & PASSWD_EXPIRE) { printf(_("Expiring password for user %s.\n"), username); retval = pwdb_update_aging(username, -2, -2, -2, -2, 0); printf("%s: %s\n", progname, retval == 0 ? _("Success") : _("Error")); audit_log_acct_message(audit_fd, AUDIT_USER_MGMT, NULL, "expired-password", NULL, pwd->pw_uid, NULL, NULL, NULL, retval == 0); return retval; } /* Handle password clearing request. */ if (passwd_flags & PASSWD_DELETE) { printf(_("Removing password for user %s.\n"), username); retval = pwdb_clear_password(username); printf("%s: %s\n", progname, (retval == 0) ? _("Success") : _("Error")); audit_log_acct_message(audit_fd, AUDIT_USER_CHAUTHTOK, NULL, "deleted-password", NULL, pwd->pw_uid, NULL, NULL, NULL, retval == 0); return retval; } /* Display account status. */ if (passwd_flags & PASSWD_STATUS) { /* Auditing is not needed for displaying status */ retval = pwdb_display_status(username); return retval; } /* Adjust aging parameters. */ if (passwd_flags & PASSWD_AGING) { char aubuf[PATH_MAX]; printf(_("Adjusting aging data for user %s.\n"), username); retval = pwdb_update_aging(username, min, max, warn, inact, -2); printf("%s: %s\n", progname, (retval == 0) ? _("Success") : _("Error")); snprintf(aubuf, sizeof(aubuf), "changed-password-aging" " min=%li max=%li warn=%li inact=%li", min, max, warn, inact); audit_log_acct_message(audit_fd, AUDIT_USER_MGMT, NULL, aubuf, NULL, pwd->pw_uid, NULL, NULL, NULL, retval == 0); return retval; } /* The standard behavior follows. At this point we know for whom * we are going to change a password, so let the invoking user * know what's going on. */ printf(_("Changing password for user %s.\n"), username); /* If we need to read the new password from stdin, read it and switch * to the really-quiet stdin conversation function. */ if (passwd_flags & PASSWD_STDIN) { /* PAM's documentation says that PAM_MAX_RESP_SIZE is the * maximum supported length of the password, but in practice * the code (including examples in the OSF RFC) often truncates * data at PAM_MAX_RESP_SIZE - 1. So, refuse to use anything * longer than PAM_MAX_RESP_SIZE - 1, to prevent users from * setting a password they won't be able to use to log in. */ char *ptr, newPassword[PAM_MAX_RESP_SIZE]; int i; i = read(STDIN_FILENO, newPassword, sizeof(newPassword)); if (i < 0) { fprintf(stderr, _("%s: error reading from stdin: %s\n"), progname, strerror(errno)); exit(1); } if (i == sizeof(newPassword)) { if (newPassword[i - 1] != '\n') { fprintf(stderr, _("%s: password too long, maximum is %zu"), progname, sizeof(newPassword) - 1); exit(1); } i--; } newPassword[i] = '\0'; ptr = strchr(newPassword, '\n'); if (ptr) *ptr = 0; conv.conv = stdin_conv; conv.appdata_ptr = strdup(newPassword); } /* Start up PAM. */ retval = pam_start("passwd", username, &conv, &pamh); if (retval != PAM_SUCCESS) { fprintf(stderr, _("%s: unable to start pam: %s\n"), progname, pam_strerror(pamh, retval)); exit(1); } if ((ttyn = ttyname(0)) != NULL) { if (strncmp(ttyn, "/dev/", 5) == 0) tty_name = ttyn+5; else tty_name = ttyn; retval = pam_set_item(pamh, PAM_TTY, tty_name); if (retval != PAM_SUCCESS) { fprintf(stderr, _("%s: unable to set tty for pam: %s\n"), progname, pam_strerror(pamh, retval)); pam_end(pamh, retval); exit(1); } } #ifdef HAVE_PAM_FAIL_DELAY /* We have to pause on failure, so tell libpam the minimum amount * of time it should wait after a failure. */ retval = pam_fail_delay(pamh, PASSWD_FAIL_DELAY); if (retval != PAM_SUCCESS) { fprintf(stderr, _("%s: unable to set failure delay: %s\n"), progname, pam_strerror(pamh, retval)); exit(1); } #endif /* Go for it. Note: pam will send audit event. */ retval = pam_chauthtok(pamh, (passwd_flags & PASSWD_KEEP) ? PAM_CHANGE_EXPIRED_AUTHTOK : 0); if (retval == PAM_SUCCESS) { /* We're done. Tell the invoking user that it worked. */ retval = pam_end(pamh, PAM_SUCCESS); if (passwd_flags & PASSWD_KEEP) printf(_("%s: expired authentication tokens updated successfully.\n"), progname); else printf(_("%s: all authentication tokens updated successfully.\n"), progname); retval = 0; } else { /* Horrors! It failed. Relay the bad news. */ fprintf(stderr, "%s: %s\n", progname, pam_strerror(pamh, retval)); pam_end(pamh, retval); retval = 1; } return retval; }