diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:22:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:22:06 +0000 |
commit | 741c1ef7a4f2ac316ad6e557ddbe03023413478d (patch) | |
tree | 38890f681daa26c57e865b4feca10d0ca53e1046 /src/passwd.c | |
parent | Initial commit. (diff) | |
download | shadow-upstream.tar.xz shadow-upstream.zip |
Adding upstream version 1:4.5.upstream/1%4.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/passwd.c')
-rw-r--r-- | src/passwd.c | 1168 |
1 files changed, 1168 insertions, 0 deletions
diff --git a/src/passwd.c b/src/passwd.c new file mode 100644 index 0000000..3af3e65 --- /dev/null +++ b/src/passwd.c @@ -0,0 +1,1168 @@ +/* + * Copyright (c) 1989 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2000, Marek Michałkiewicz + * Copyright (c) 2001 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2011, Nicolas François + * All rights reserved. + * + * 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, this list of conditions and the following disclaimer. + * 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 copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``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 COPYRIGHT + * HOLDERS OR CONTRIBUTORS 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. + */ + +#include <config.h> + +#ident "$Id$" + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#ifdef WITH_SELINUX +#include <selinux/selinux.h> +#include <selinux/flask.h> +#include <selinux/av_permissions.h> +#include <selinux/context.h> +#endif /* WITH_SELINUX */ +#include <time.h> +#include "defines.h" +#include "getdef.h" +#include "nscd.h" +#include "prototypes.h" +#include "pwauth.h" +#include "pwio.h" +#include "shadowio.h" + +/* + * exit status values + */ +/*@-exitarg@*/ +#define E_SUCCESS 0 /* success */ +#define E_NOPERM 1 /* permission denied */ +#define E_USAGE 2 /* invalid combination of options */ +#define E_FAILURE 3 /* unexpected failure, nothing done */ +#define E_MISSING 4 /* unexpected failure, passwd file missing */ +#define E_PWDBUSY 5 /* passwd file busy, try again later */ +#define E_BAD_ARG 6 /* invalid argument to option */ +/* + * Global variables + */ +const char *Prog; /* Program name */ + +static char *name; /* The name of user whose password is being changed */ +static char *myname; /* The current user's name */ +static bool amroot; /* The caller's real UID was 0 */ + +static bool + aflg = false, /* -a - show status for all users */ + dflg = false, /* -d - delete password */ + eflg = false, /* -e - force password change */ + iflg = false, /* -i - set inactive days */ + kflg = false, /* -k - change only if expired */ + lflg = false, /* -l - lock the user's password */ + nflg = false, /* -n - set minimum days */ + qflg = false, /* -q - quiet mode */ + Sflg = false, /* -S - show password status */ + uflg = false, /* -u - unlock the user's password */ + wflg = false, /* -w - set warning days */ + xflg = false; /* -x - set maximum days */ + +/* + * set to 1 if there are any flags which require root privileges, + * and require username to be specified + */ +static bool anyflag = false; + +static long age_min = 0; /* Minimum days before change */ +static long age_max = 0; /* Maximum days until change */ +static long warn = 0; /* Warning days before change */ +static long inact = 0; /* Days without change before locked */ + +#ifndef USE_PAM +static bool do_update_age = false; +#endif /* ! USE_PAM */ + +static bool pw_locked = false; +static bool spw_locked = false; + +#ifndef USE_PAM +/* + * Size of the biggest passwd: + * $6$ 3 + * rounds= 7 + * 999999999 9 + * $ 1 + * salt 16 + * $ 1 + * SHA512 123 + * nul 1 + * + * total 161 + */ +static char crypt_passwd[256]; +static bool do_update_pwd = false; +#endif /* !USE_PAM */ + +/* + * External identifiers + */ + +/* local function prototypes */ +static /*@noreturn@*/void usage (int); + +#ifndef USE_PAM +static bool reuse (const char *, const struct passwd *); +static int new_password (const struct passwd *); + +static void check_password (const struct passwd *, const struct spwd *); +#endif /* !USE_PAM */ +static /*@observer@*/const char *date_to_str (time_t); +static /*@observer@*/const char *pw_status (const char *); +static void print_status (const struct passwd *); +static /*@noreturn@*/void fail_exit (int); +static /*@noreturn@*/void oom (void); +static char *update_crypt_pw (char *); +static void update_noshadow (void); + +static void update_shadow (void); +#ifdef WITH_SELINUX +static int check_selinux_access (const char *changed_user, + uid_t changed_uid, + access_vector_t requested_access); +#endif /* WITH_SELINUX */ + +/* + * usage - print command usage and exit + */ +static /*@noreturn@*/void usage (int status) +{ + FILE *usageout = (E_SUCCESS != status) ? stderr : stdout; + (void) fprintf (usageout, + _("Usage: %s [options] [LOGIN]\n" + "\n" + "Options:\n"), + Prog); + (void) fputs (_(" -a, --all report password status on all accounts\n"), usageout); + (void) fputs (_(" -d, --delete delete the password for the named account\n"), usageout); + (void) fputs (_(" -e, --expire force expire the password for the named account\n"), usageout); + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -k, --keep-tokens change password only if expired\n"), usageout); + (void) fputs (_(" -i, --inactive INACTIVE set password inactive after expiration\n" + " to INACTIVE\n"), usageout); + (void) fputs (_(" -l, --lock lock the password of the named account\n"), usageout); + (void) fputs (_(" -n, --mindays MIN_DAYS set minimum number of days before password\n" + " change to MIN_DAYS\n"), usageout); + (void) fputs (_(" -q, --quiet quiet mode\n"), usageout); + (void) fputs (_(" -r, --repository REPOSITORY change password in REPOSITORY repository\n"), usageout); + (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); + (void) fputs (_(" -S, --status report password status on the named account\n"), usageout); + (void) fputs (_(" -u, --unlock unlock the password of the named account\n"), usageout); + (void) fputs (_(" -w, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"), usageout); + (void) fputs (_(" -x, --maxdays MAX_DAYS set maximum number of days before password\n" + " change to MAX_DAYS\n"), usageout); + (void) fputs ("\n", usageout); + exit (status); +} + +#ifndef USE_PAM +static bool reuse (const char *pass, const struct passwd *pw) +{ +#ifdef HAVE_LIBCRACK_HIST + const char *reason; + +#ifdef HAVE_LIBCRACK_PW + const char *FascistHistoryPw (const char *, const struct passwd *); + + reason = FascistHistory (pass, pw); +#else /* !HAVE_LIBCRACK_PW */ + const char *FascistHistory (const char *, int); + + reason = FascistHistory (pass, pw->pw_uid); +#endif /* !HAVE_LIBCRACK_PW */ + if (NULL != reason) { + (void) printf (_("Bad password: %s. "), reason); + return true; + } +#endif /* HAVE_LIBCRACK_HIST */ + return false; +} + +/* + * new_password - validate old password and replace with new (both old and + * new in global "char crypt_passwd[128]") + */ +static int new_password (const struct passwd *pw) +{ + char *clear; /* Pointer to clear text */ + char *cipher; /* Pointer to cipher text */ + const char *salt; /* Pointer to new salt */ + char *cp; /* Pointer to getpass() response */ + char orig[200]; /* Original password */ + char pass[200]; /* New password */ + int i; /* Counter for retries */ + bool warned; + int pass_max_len = -1; + const char *method; + +#ifdef HAVE_LIBCRACK_HIST + int HistUpdate (const char *, const char *); +#endif /* HAVE_LIBCRACK_HIST */ + + /* + * Authenticate the user. The user will be prompted for their own + * password. + */ + + if (!amroot && ('\0' != crypt_passwd[0])) { + clear = getpass (_("Old password: ")); + if (NULL == clear) { + return -1; + } + + cipher = pw_encrypt (clear, crypt_passwd); + + if (NULL == cipher) { + strzero (clear); + fprintf (stderr, + _("%s: failed to crypt password with previous salt: %s\n"), + Prog, strerror (errno)); + SYSLOG ((LOG_INFO, + "Failed to crypt password with previous salt of user '%s'", + pw->pw_name)); + return -1; + } + + if (strcmp (cipher, crypt_passwd) != 0) { + strzero (clear); + strzero (cipher); + SYSLOG ((LOG_WARN, "incorrect password for %s", + pw->pw_name)); + (void) sleep (1); + (void) fprintf (stderr, + _("Incorrect password for %s.\n"), + pw->pw_name); + return -1; + } + STRFCPY (orig, clear); + strzero (clear); + strzero (cipher); + } else { + orig[0] = '\0'; + } + + /* + * Get the new password. The user is prompted for the new password + * and has five tries to get it right. The password will be tested + * for strength, unless it is the root user. This provides an escape + * for initial login passwords. + */ + method = getdef_str ("ENCRYPT_METHOD"); + if (NULL == method) { + if (!getdef_bool ("MD5_CRYPT_ENAB")) { + pass_max_len = getdef_num ("PASS_MAX_LEN", 8); + } + } else { + if ( (strcmp (method, "MD5") == 0) +#ifdef USE_SHA_CRYPT + || (strcmp (method, "SHA256") == 0) + || (strcmp (method, "SHA512") == 0) +#endif /* USE_SHA_CRYPT */ + ) { + pass_max_len = -1; + } else { + pass_max_len = getdef_num ("PASS_MAX_LEN", 8); + } + } + if (!qflg) { + if (pass_max_len == -1) { + (void) printf (_( +"Enter the new password (minimum of %d characters)\n" +"Please use a combination of upper and lower case letters and numbers.\n"), + getdef_num ("PASS_MIN_LEN", 5)); + } else { + (void) printf (_( +"Enter the new password (minimum of %d, maximum of %d characters)\n" +"Please use a combination of upper and lower case letters and numbers.\n"), + getdef_num ("PASS_MIN_LEN", 5), pass_max_len); + } + } + + warned = false; + for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) { + cp = getpass (_("New password: ")); + if (NULL == cp) { + memzero (orig, sizeof orig); + return -1; + } + if (warned && (strcmp (pass, cp) != 0)) { + warned = false; + } + STRFCPY (pass, cp); + strzero (cp); + + if (!amroot && (!obscure (orig, pass, pw) || reuse (pass, pw))) { + (void) puts (_("Try again.")); + continue; + } + + /* + * If enabled, warn about weak passwords even if you are + * root (enter this password again to use it anyway). + * --marekm + */ + if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN") + && (!obscure (orig, pass, pw) || reuse (pass, pw))) { + (void) puts (_("\nWarning: weak password (enter it again to use it anyway).")); + warned = true; + continue; + } + cp = getpass (_("Re-enter new password: ")); + if (NULL == cp) { + memzero (orig, sizeof orig); + return -1; + } + if (strcmp (cp, pass) != 0) { + (void) fputs (_("They don't match; try again.\n"), stderr); + } else { + strzero (cp); + break; + } + } + memzero (orig, sizeof orig); + + if (i == 0) { + memzero (pass, sizeof pass); + return -1; + } + + /* + * Encrypt the password, then wipe the cleartext password. + */ + salt = crypt_make_salt (NULL, NULL); + cp = pw_encrypt (pass, salt); + memzero (pass, sizeof pass); + + if (NULL == cp) { + fprintf (stderr, + _("%s: failed to crypt password with salt '%s': %s\n"), + Prog, salt, strerror (errno)); + return -1; + } + +#ifdef HAVE_LIBCRACK_HIST + HistUpdate (pw->pw_name, crypt_passwd); +#endif /* HAVE_LIBCRACK_HIST */ + STRFCPY (crypt_passwd, cp); + return 0; +} + +/* + * check_password - test a password to see if it can be changed + * + * check_password() sees if the invoker has permission to change the + * password for the given user. + */ +static void check_password (const struct passwd *pw, const struct spwd *sp) +{ + time_t now; + int exp_status; + + exp_status = isexpired (pw, sp); + + /* + * If not expired and the "change only if expired" option (idea from + * PAM) was specified, do nothing. --marekm + */ + if (kflg && (0 == exp_status)) { + exit (E_SUCCESS); + } + + /* + * Root can change any password any time. + */ + if (amroot) { + return; + } + + (void) time (&now); + + /* + * Expired accounts cannot be changed ever. Passwords which are + * locked may not be changed. Passwords where min > max may not be + * changed. Passwords which have been inactive too long cannot be + * changed. + */ + if ( (sp->sp_pwdp[0] == '!') + || (exp_status > 1) + || ( (sp->sp_max >= 0) + && (sp->sp_min > sp->sp_max))) { + (void) fprintf (stderr, + _("The password for %s cannot be changed.\n"), + sp->sp_namp); + SYSLOG ((LOG_WARN, "password locked for '%s'", sp->sp_namp)); + closelog (); + exit (E_NOPERM); + } + + /* + * Passwords may only be changed after sp_min time is up. + */ + if (sp->sp_lstchg > 0) { + time_t ok; + ok = (time_t) sp->sp_lstchg * SCALE; + if (sp->sp_min > 0) { + ok += (time_t) sp->sp_min * SCALE; + } + + if (now < ok) { + (void) fprintf (stderr, + _("The password for %s cannot be changed yet.\n"), + pw->pw_name); + SYSLOG ((LOG_WARN, "now < minimum age for '%s'", pw->pw_name)); + closelog (); + exit (E_NOPERM); + } + } +} +#endif /* !USE_PAM */ + +static /*@observer@*/const char *date_to_str (time_t t) +{ + static char buf[80]; + struct tm *tm; + + tm = gmtime (&t); +#ifdef HAVE_STRFTIME + (void) strftime (buf, sizeof buf, "%m/%d/%Y", tm); +#else /* !HAVE_STRFTIME */ + (void) snprintf (buf, sizeof buf, "%02d/%02d/%04d", + tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900); +#endif /* !HAVE_STRFTIME */ + return buf; +} + +static /*@observer@*/const char *pw_status (const char *pass) +{ + if (*pass == '*' || *pass == '!') { + return "L"; + } + if (*pass == '\0') { + return "NP"; + } + return "P"; +} + +/* + * print_status - print current password status + */ +static void print_status (const struct passwd *pw) +{ + struct spwd *sp; + + sp = getspnam (pw->pw_name); /* local, no need for xgetspnam */ + if (NULL != sp) { + (void) printf ("%s %s %s %lld %lld %lld %lld\n", + pw->pw_name, + pw_status (sp->sp_pwdp), + date_to_str (sp->sp_lstchg * SCALE), + ((long long)sp->sp_min * SCALE) / DAY, + ((long long)sp->sp_max * SCALE) / DAY, + ((long long)sp->sp_warn * SCALE) / DAY, + ((long long)sp->sp_inact * SCALE) / DAY); + } else { + (void) printf ("%s %s\n", + pw->pw_name, pw_status (pw->pw_passwd)); + } +} + + +static /*@noreturn@*/void fail_exit (int status) +{ + if (pw_locked) { + if (pw_unlock () == 0) { + (void) fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + } + + if (spw_locked) { + if (spw_unlock () == 0) { + (void) fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + /* continue */ + } + } + + exit (status); +} + +static /*@noreturn@*/void oom (void) +{ + (void) fprintf (stderr, _("%s: out of memory\n"), Prog); + fail_exit (E_FAILURE); +} + +static char *update_crypt_pw (char *cp) +{ +#ifndef USE_PAM + if (do_update_pwd) { + cp = xstrdup (crypt_passwd); + } +#endif /* !USE_PAM */ + + if (dflg) { + *cp = '\0'; + } + + if (uflg && *cp == '!') { + if (cp[1] == '\0') { + (void) fprintf (stderr, + _("%s: unlocking the password would result in a passwordless account.\n" + "You should set a password with usermod -p to unlock the password of this account.\n"), + Prog); + fail_exit (E_FAILURE); + } else { + cp++; + } + } + + if (lflg && *cp != '!') { + char *newpw = xmalloc (strlen (cp) + 2); + + strcpy (newpw, "!"); + strcat (newpw, cp); + cp = newpw; + } + return cp; +} + + +static void update_noshadow (void) +{ + const struct passwd *pw; + struct passwd *npw; + + if (pw_lock () == 0) { + (void) fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, pw_dbname ()); + exit (E_PWDBUSY); + } + pw_locked = true; + if (pw_open (O_CREAT | O_RDWR) == 0) { + (void) fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, pw_dbname ()); + SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ())); + fail_exit (E_MISSING); + } + pw = pw_locate (name); + if (NULL == pw) { + (void) fprintf (stderr, + _("%s: user '%s' does not exist in %s\n"), + Prog, name, pw_dbname ()); + fail_exit (E_NOPERM); + } + npw = __pw_dup (pw); + if (NULL == npw) { + oom (); + } + npw->pw_passwd = update_crypt_pw (npw->pw_passwd); + if (pw_update (npw) == 0) { + (void) fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, pw_dbname (), npw->pw_name); + fail_exit (E_FAILURE); + } + if (pw_close () == 0) { + (void) fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ())); + fail_exit (E_FAILURE); + } + if (pw_unlock () == 0) { + (void) fprintf (stderr, + _("%s: failed to unlock %s\n"), + Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + pw_locked = false; +} + +static void update_shadow (void) +{ + const struct spwd *sp; + struct spwd *nsp; + + if (spw_lock () == 0) { + (void) fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, spw_dbname ()); + exit (E_PWDBUSY); + } + spw_locked = true; + if (spw_open (O_CREAT | O_RDWR) == 0) { + (void) fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, spw_dbname ()); + SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ())); + fail_exit (E_FAILURE); + } + sp = spw_locate (name); + if (NULL == sp) { + /* Try to update the password in /etc/passwd instead. */ + (void) spw_close (); + update_noshadow (); + if (spw_unlock () == 0) { + (void) fprintf (stderr, + _("%s: failed to unlock %s\n"), + Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + /* continue */ + } + spw_locked = false; + return; + } + nsp = __spw_dup (sp); + if (NULL == nsp) { + oom (); + } + nsp->sp_pwdp = update_crypt_pw (nsp->sp_pwdp); + if (xflg) { + nsp->sp_max = (age_max * DAY) / SCALE; + } + if (nflg) { + nsp->sp_min = (age_min * DAY) / SCALE; + } + if (wflg) { + nsp->sp_warn = (warn * DAY) / SCALE; + } + if (iflg) { + nsp->sp_inact = (inact * DAY) / SCALE; + } +#ifndef USE_PAM + if (do_update_age) { + nsp->sp_lstchg = (long) gettime () / SCALE; + if (0 == nsp->sp_lstchg) { + /* Better disable aging than requiring a password + * change */ + nsp->sp_lstchg = -1; + } + } +#endif /* !USE_PAM */ + + /* + * Force change on next login, like SunOS 4.x passwd -e or Solaris + * 2.x passwd -f. Solaris 2.x seems to do the same thing (set + * sp_lstchg to 0). + */ + if (eflg) { + nsp->sp_lstchg = 0; + } + + if (spw_update (nsp) == 0) { + (void) fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, spw_dbname (), nsp->sp_namp); + fail_exit (E_FAILURE); + } + if (spw_close () == 0) { + (void) fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ())); + fail_exit (E_FAILURE); + } + if (spw_unlock () == 0) { + (void) fprintf (stderr, + _("%s: failed to unlock %s\n"), + Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + /* continue */ + } + spw_locked = false; +} + +#ifdef WITH_SELINUX +static int check_selinux_access (const char *changed_user, + uid_t changed_uid, + access_vector_t requested_access) +{ + int status = -1; + security_context_t user_context; + context_t c; + const char *user; + + /* if in permissive mode then allow the operation */ + if (security_getenforce() == 0) { + return 0; + } + + /* get the context of the process which executed passwd */ + if (getprevcon(&user_context) != 0) { + return -1; + } + + /* get the "user" portion of the context (the part before the first + colon) */ + c = context_new(user_context); + user = context_user_get(c); + + /* if changing a password for an account with UID==0 or for an account + where the identity matches then return success */ + if (changed_uid != 0 && strcmp(changed_user, user) == 0) { + status = 0; + } else { + struct av_decision avd; + int retval; + retval = security_compute_av(user_context, + user_context, + SECCLASS_PASSWD, + requested_access, + &avd); + if ((retval == 0) && + ((requested_access & avd.allowed) == requested_access)) { + status = 0; + } + } + context_free(c); + freecon(user_context); + return status; +} + +#endif /* WITH_SELINUX */ + +/* + * passwd - change a user's password file information + * + * This command controls the password file and commands which are used + * to modify it. + * + * The valid options are + * + * -d delete the password for the named account (*) + * -e expire the password for the named account (*) + * -f execute chfn command to interpret flags + * -g execute gpasswd command to interpret flags + * -i # set sp_inact to # days (*) + * -k change password only if expired + * -l lock the password of the named account (*) + * -n # set sp_min to # days (*) + * -r # change password in # repository + * -s execute chsh command to interpret flags + * -S show password status of named account + * -u unlock the password of the named account (*) + * -w # set sp_warn to # days (*) + * -x # set sp_max to # days (*) + * + * (*) requires root permission to execute. + * + * All of the time fields are entered in days and converted to the + * appropriate internal format. For finer resolute the chage + * command must be used. + */ +int main (int argc, char **argv) +{ + const struct passwd *pw; /* Password file entry for user */ + +#ifndef USE_PAM + char *cp; /* Miscellaneous character pointing */ + + const struct spwd *sp; /* Shadow file entry for user */ +#endif /* !USE_PAM */ + + sanitize_env (); + + /* + * Get the program name. The program name is used as a prefix to + * most error messages. + */ + Prog = Basename (argv[0]); + + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + process_root_flag ("-R", argc, argv); + + /* + * The program behaves differently when executed by root than when + * executed by a normal user. + */ + amroot = (getuid () == 0); + + OPENLOG ("passwd"); + + { + /* + * Parse the command line options. + */ + int c; + static struct option long_options[] = { + {"all", no_argument, NULL, 'a'}, + {"delete", no_argument, NULL, 'd'}, + {"expire", no_argument, NULL, 'e'}, + {"help", no_argument, NULL, 'h'}, + {"inactive", required_argument, NULL, 'i'}, + {"keep-tokens", no_argument, NULL, 'k'}, + {"lock", no_argument, NULL, 'l'}, + {"mindays", required_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"repository", required_argument, NULL, 'r'}, + {"root", required_argument, NULL, 'R'}, + {"status", no_argument, NULL, 'S'}, + {"unlock", no_argument, NULL, 'u'}, + {"warndays", required_argument, NULL, 'w'}, + {"maxdays", required_argument, NULL, 'x'}, + {NULL, 0, NULL, '\0'} + }; + + while ((c = getopt_long (argc, argv, "adehi:kln:qr:R:Suw:x:", + long_options, NULL)) != -1) { + switch (c) { + case 'a': + aflg = true; + break; + case 'd': + dflg = true; + anyflag = true; + break; + case 'e': + eflg = true; + anyflag = true; + break; + case 'h': + usage (E_SUCCESS); + /*@notreached@*/break; + case 'i': + if ( (getlong (optarg, &inact) == 0) + || (inact < -1)) { + fprintf (stderr, + _("%s: invalid numeric argument '%s'\n"), + Prog, optarg); + usage (E_BAD_ARG); + } + iflg = true; + anyflag = true; + break; + case 'k': + /* change only if expired, like Linux-PAM passwd -k. */ + kflg = true; /* ok for users */ + break; + case 'l': + lflg = true; + anyflag = true; + break; + case 'n': + if ( (getlong (optarg, &age_min) == 0) + || (age_min < -1)) { + fprintf (stderr, + _("%s: invalid numeric argument '%s'\n"), + Prog, optarg); + usage (E_BAD_ARG); + } + nflg = true; + anyflag = true; + break; + case 'q': + qflg = true; /* ok for users */ + break; + case 'r': + /* -r repository (files|nis|nisplus) */ + /* only "files" supported for now */ + if (strcmp (optarg, "files") != 0) { + fprintf (stderr, + _("%s: repository %s not supported\n"), + Prog, optarg); + exit (E_BAD_ARG); + } + break; + case 'R': /* no-op, handled in process_root_flag () */ + break; + case 'S': + Sflg = true; /* ok for users */ + break; + case 'u': + uflg = true; + anyflag = true; + break; + case 'w': + if ( (getlong (optarg, &warn) == 0) + || (warn < -1)) { + (void) fprintf (stderr, + _("%s: invalid numeric argument '%s'\n"), + Prog, optarg); + usage (E_BAD_ARG); + } + wflg = true; + anyflag = true; + break; + case 'x': + if ( (getlong (optarg, &age_max) == 0) + || (age_max < -1)) { + (void) fprintf (stderr, + _("%s: invalid numeric argument '%s'\n"), + Prog, optarg); + usage (E_BAD_ARG); + } + xflg = true; + anyflag = true; + break; + default: + usage (E_BAD_ARG); + } + } + } + + /* + * Now I have to get the user name. The name will be gotten from the + * command line if possible. Otherwise it is figured out from the + * environment. + */ + pw = get_my_pwent (); + if (NULL == pw) { + (void) fprintf (stderr, + _("%s: Cannot determine your user name.\n"), + Prog); + SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)", + (unsigned long) getuid ())); + exit (E_NOPERM); + } + myname = xstrdup (pw->pw_name); + if (optind < argc) { + name = argv[optind]; + } else { + name = myname; + } + + /* + * Make sure that at most one username was specified. + */ + if (argc > (optind+1)) { + usage (E_USAGE); + } + + /* + * The -a flag requires -S, no other flags, no username, and + * you must be root. --marekm + */ + if (aflg) { + if (anyflag || !Sflg || (optind < argc)) { + usage (E_USAGE); + } + if (!amroot) { + (void) fprintf (stderr, + _("%s: Permission denied.\n"), + Prog); + exit (E_NOPERM); + } + setpwent (); + while ( (pw = getpwent ()) != NULL ) { + print_status (pw); + } + endpwent (); + exit (E_SUCCESS); + } +#if 0 + /* + * Allow certain users (administrators) to change passwords of + * certain users. Not implemented yet. --marekm + */ + if (may_change_passwd (myname, name)) + amroot = 1; +#endif + + /* + * If any of the flags were given, a user name must be supplied on + * the command line. Only an unadorned command line doesn't require + * the user's name be given. Also, -x, -n, -w, -i, -e, -d, + * -l, -u may appear with each other. -S, -k must appear alone. + */ + + /* + * -S now ok for normal users (check status of my own account), and + * doesn't require username. --marekm + */ + if (anyflag && optind >= argc) { + usage (E_USAGE); + } + + if ( (Sflg && kflg) + || (anyflag && (Sflg || kflg))) { + usage (E_USAGE); + } + + if (anyflag && !amroot) { + (void) fprintf (stderr, _("%s: Permission denied.\n"), Prog); + exit (E_NOPERM); + } + + pw = xgetpwnam (name); + if (NULL == pw) { + (void) fprintf (stderr, + _("%s: user '%s' does not exist\n"), + Prog, name); + exit (E_NOPERM); + } +#ifdef WITH_SELINUX + /* only do this check when getuid()==0 because it's a pre-condition for + changing a password without entering the old one */ + if ((is_selinux_enabled() > 0) && (getuid() == 0) && + (check_selinux_access (name, pw->pw_uid, PASSWD__PASSWD) != 0)) { + security_context_t user_context = NULL; + const char *user = "Unknown user context"; + if (getprevcon (&user_context) == 0) { + user = user_context; /* FIXME: use context_user_get? */ + } + SYSLOG ((LOG_ALERT, + "%s is not authorized to change the password of %s", + user, name)); + (void) fprintf(stderr, + _("%s: %s is not authorized to change the password of %s\n"), + Prog, user, name); + if (NULL != user_context) { + freecon (user_context); + } + exit (E_NOPERM); + } +#endif /* WITH_SELINUX */ + + /* + * If the UID of the user does not match the current real UID, + * check if I'm root. + */ + if (!amroot && (pw->pw_uid != getuid ())) { + (void) fprintf (stderr, + _("%s: You may not view or modify password information for %s.\n"), + Prog, name); + SYSLOG ((LOG_WARN, + "%s: can't view or modify password information for %s", + Prog, name)); + closelog (); + exit (E_NOPERM); + } + + if (Sflg) { + print_status (pw); + exit (E_SUCCESS); + } +#ifndef USE_PAM + /* + * The user name is valid, so let's get the shadow file entry. + */ + sp = getspnam (name); /* !USE_PAM, no need for xgetspnam */ + if (NULL == sp) { + if (errno == EACCES) { + (void) fprintf (stderr, + _("%s: Permission denied.\n"), + Prog); + exit (E_NOPERM); + } + sp = pwd_to_spwd (pw); + } + + cp = sp->sp_pwdp; + + /* + * If there are no other flags, just change the password. + */ + if (!anyflag) { + STRFCPY (crypt_passwd, cp); + + /* + * See if the user is permitted to change the password. + * Otherwise, go ahead and set a new password. + */ + check_password (pw, sp); + + /* + * Let the user know whose password is being changed. + */ + if (!qflg) { + (void) printf (_("Changing password for %s\n"), name); + } + + if (new_password (pw) != 0) { + (void) fprintf (stderr, + _("The password for %s is unchanged.\n"), + name); + closelog (); + exit (E_NOPERM); + } + do_update_pwd = true; + do_update_age = true; + } +#endif /* !USE_PAM */ + /* + * Before going any further, raise the ulimit to prevent colliding + * into a lowered ulimit, and set the real UID to root to protect + * against unexpected signals. Any keyboard signals are set to be + * ignored. + */ + pwd_init (); + +#ifdef USE_PAM + /* + * Don't set the real UID for PAM... + */ + if (!anyflag) { + do_pam_passwd (name, qflg, kflg); + exit (E_SUCCESS); + } +#endif /* USE_PAM */ + if (setuid (0) != 0) { + (void) fputs (_("Cannot change ID to root.\n"), stderr); + SYSLOG ((LOG_ERR, "can't setuid(0)")); + closelog (); + exit (E_NOPERM); + } + if (spw_file_present ()) { + update_shadow (); + } else { + update_noshadow (); + } + + nscd_flush_cache ("passwd"); + nscd_flush_cache ("group"); + + SYSLOG ((LOG_INFO, "password for '%s' changed by '%s'", name, myname)); + closelog (); + if (!qflg) { + if (!anyflag) { +#ifndef USE_PAM + (void) printf (_("%s: password changed.\n"), Prog); +#endif /* USE_PAM */ + } else { + (void) printf (_("%s: password expiry information changed.\n"), Prog); + } + } + + return E_SUCCESS; +} + |