/* * Copyright 1989 - 1994, Julianne Frances Haugh * 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. Neither the name of Julianne F. Haugh nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH 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 JULIE HAUGH 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 #ident "$Id$" #include #include #include #include #include #include #include #ifdef WITH_SELINUX #include #include #endif #include #include "defines.h" #include "getdef.h" #include "nscd.h" #include "prototypes.h" #include "pwauth.h" #include "pwio.h" #include "shadowio.h" /* * exit status values */ #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 */ static char *name; /* The name of user whose password is being changed */ static char *myname; /* The current user's name */ static char *Prog; /* Program name */ static int amroot; /* The real UID was 0 */ static int aflg = 0, /* -a - show status for all users */ dflg = 0, /* -d - delete password */ eflg = 0, /* -e - force password change */ iflg = 0, /* -i - set inactive days */ kflg = 0, /* -k - change only if expired */ lflg = 0, /* -l - lock account */ nflg = 0, /* -n - set minimum days */ qflg = 0, /* -q - quiet mode */ Sflg = 0, /* -S - show password status */ uflg = 0, /* -u - unlock account */ wflg = 0, /* -w - set warning days */ xflg = 0; /* -x - set maximum days */ /* * set to 1 if there are any flags which require root privileges, * and require username to be specified */ static int anyflag = 0; 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 */ static int do_update_age = 0; #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 int do_update_pwd = 0; #endif /* * External identifiers */ /* local function prototypes */ static void usage (int); #ifndef USE_PAM static int reuse (const char *, const struct passwd *); static int new_password (const struct passwd *); static void check_password (const struct passwd *, const struct spwd *); static char *insert_crypt_passwd (const char *, const char *); #endif /* !USE_PAM */ static char *date_to_str (time_t); static const char *pw_status (const char *); static void print_status (const struct passwd *); static void fail_exit (int); static void oom (void); static char *update_crypt_pw (char *); static void update_noshadow (void); static void update_shadow (void); static long getnumber (const char *); /* * usage - print command usage and exit */ static void usage (int status) { fprintf (stderr, _("Usage: passwd [options] [LOGIN]\n" "\n" "Options:\n" " -a, --all report password status on all accounts\n" " -d, --delete delete the password for the named account\n" " -e, --expire force expire the password for the named account\n" " -h, --help display this help message and exit\n" " -k, --keep-tokens change password only if expired\n" " -i, --inactive INACTIVE set password inactive after expiration\n" " to INACTIVE\n" " -l, --lock lock the named account\n" " -n, --mindays MIN_DAYS set minimum number of days before password\n" " change to MIN_DAYS\n" " -q, --quiet quiet mode\n" " -r, --repository REPOSITORY change password in REPOSITORY repository\n" " -S, --status report password status on the named account\n" " -u, --unlock unlock the named account\n" " -w, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n" " -x, --maxdays MAX_DAYS set maximim number of days before password\n" " change to MAX_DAYS\n" "\n")); exit (status); } #ifndef USE_PAM static int 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 const char *FascistHistory (const char *, int); reason = FascistHistory (pass, pw->pw_uid); #endif if (reason) { printf (_("Bad password: %s. "), reason); return 1; } #endif return 0; } /* * 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 */ char *cp; /* Pointer to getpass() response */ char orig[200]; /* Original password */ char pass[200]; /* New password */ int i; /* Counter for retries */ int warned; int pass_max_len = -1; char *method; #ifdef HAVE_LIBCRACK_HIST int HistUpdate (const char *, const char *); #endif /* * Authenticate the user. The user will be prompted for their own * password. */ if (!amroot && crypt_passwd[0]) { if (!(clear = getpass (_("Old password: ")))) return -1; cipher = pw_encrypt (clear, crypt_passwd); if (strcmp (cipher, crypt_passwd) != 0) { SYSLOG ((LOG_WARN, "incorrect password for %s", pw->pw_name)); sleep (1); 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. */ if ((method = getdef_str ("ENCRYPT_METHOD")) == NULL) { if (!getdef_bool ("MD5_CRYPT_ENAB")) pass_max_len = getdef_num ("PASS_MAX_LEN", 8); } else { if ( !strcmp (method, "MD5") #ifdef USE_SHA_CRYPT || !strcmp (method, "SHA256") || !strcmp (method, "SHA512") #endif ) pass_max_len = -1; else pass_max_len = getdef_num ("PASS_MAX_LEN", 8); } if (!qflg) { if (pass_max_len == -1) { 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 { 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 = 0; for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) { if (!(cp = getpass (_("New password: ")))) { memzero (orig, sizeof orig); return -1; } if (warned && strcmp (pass, cp) != 0) warned = 0; STRFCPY (pass, cp); strzero (cp); if (!amroot && (!obscure (orig, pass, pw) || reuse (pass, pw))) { printf (_("Try again.\n")); 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))) { printf (_ ("\nWarning: weak password (enter it again to use it anyway).\n")); warned++; continue; } if (!(cp = getpass (_("Re-enter new password: ")))) { memzero (orig, sizeof orig); return -1; } if (strcmp (cp, pass)) fprintf (stderr, _("They don't match; try again.\n")); 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. */ cp = pw_encrypt (pass, crypt_make_salt (NULL, NULL)); memzero (pass, sizeof pass); #ifdef HAVE_LIBCRACK_HIST HistUpdate (pw->pw_name, crypt_passwd); #endif 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, last, ok; 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 && exp_status == 0) exit (E_SUCCESS); /* * Root can change any password any time. */ if (amroot) return; 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)) { 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. */ last = sp->sp_lstchg * SCALE; ok = last + (sp->sp_min > 0 ? sp->sp_min * SCALE : 0); if (now < ok) { 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); } } /* * insert_crypt_passwd - add an "old-style" password to authentication * string result now malloced to avoid overflow, just in case. --marekm */ static char *insert_crypt_passwd (const char *string, const char *passwd) { return xstrdup (passwd); } #endif /* !USE_PAM */ static char *date_to_str (time_t t) { static char buf[80]; struct tm *tm; tm = gmtime (&t); #ifdef HAVE_STRFTIME strftime (buf, sizeof buf, "%m/%d/%Y", tm); #else snprintf (buf, sizeof buf, "%02d/%02d/%04d", tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900); #endif return buf; } static 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 (sp) { printf ("%s %s %s %ld %ld %ld %ld\n", pw->pw_name, pw_status (sp->sp_pwdp), date_to_str (sp->sp_lstchg * SCALE), (sp->sp_min * SCALE) / DAY, (sp->sp_max * SCALE) / DAY, (sp->sp_warn * SCALE) / DAY, (sp->sp_inact * SCALE) / DAY); } else { printf ("%s %s\n", pw->pw_name, pw_status (pw->pw_passwd)); } } static void fail_exit (int status) { pw_unlock (); spw_unlock (); exit (status); } static void oom (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 = insert_crypt_passwd (cp, crypt_passwd); #endif if (dflg) cp = ""; /* XXX warning: const */ if (uflg && *cp == '!') { if (cp[1] == '\0') { fprintf (stderr, _("%s: unlocking the user would result in a passwordless account.\n" "You should set a password with usermod -p to unlock this user account.\n"), Prog); } 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 ()) { fprintf (stderr, _ ("Cannot lock the password file; try again later.\n")); SYSLOG ((LOG_WARN, "can't lock password file")); exit (E_PWDBUSY); } if (!pw_open (O_RDWR)) { fprintf (stderr, _("Cannot open the password file.\n")); SYSLOG ((LOG_ERR, "can't open password file")); fail_exit (E_MISSING); } pw = pw_locate (name); if (!pw) { fprintf (stderr, _("%s: %s not found in /etc/passwd\n"), Prog, name); fail_exit (E_NOPERM); } npw = __pw_dup (pw); if (!npw) oom (); npw->pw_passwd = update_crypt_pw (npw->pw_passwd); if (!pw_update (npw)) { fprintf (stderr, _("Error updating the password entry.\n")); SYSLOG ((LOG_ERR, "error updating password entry")); fail_exit (E_FAILURE); } if (!pw_close ()) { fprintf (stderr, _("Cannot commit password file changes.\n")); SYSLOG ((LOG_ERR, "can't rewrite password file")); fail_exit (E_FAILURE); } pw_unlock (); } static void update_shadow (void) { const struct spwd *sp; struct spwd *nsp; if (!spw_lock ()) { fprintf (stderr, _ ("Cannot lock the password file; try again later.\n")); SYSLOG ((LOG_WARN, "can't lock password file")); exit (E_PWDBUSY); } if (!spw_open (O_RDWR)) { fprintf (stderr, _("Cannot open the password file.\n")); SYSLOG ((LOG_ERR, "can't open password file")); fail_exit (E_FAILURE); } sp = spw_locate (name); if (!sp) { /* Try to update the password in /etc/passwd instead. */ spw_close (); update_noshadow (); spw_unlock (); return; } nsp = __spw_dup (sp); if (!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; if (do_update_age) nsp->sp_lstchg = time ((time_t *) 0) / SCALE; if (lflg) { /* Set the account expiry field to 1. * Some PAM implementation consider zero as a non expired * account. */ nsp->sp_expire = 1; } if (uflg) nsp->sp_expire = -1; /* * 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)) { fprintf (stderr, _("Error updating the password entry.\n")); SYSLOG ((LOG_ERR, "error updating password entry")); fail_exit (E_FAILURE); } if (!spw_close ()) { fprintf (stderr, _("Cannot commit password file changes.\n")); SYSLOG ((LOG_ERR, "can't rewrite password file")); fail_exit (E_FAILURE); } spw_unlock (); } static long getnumber (const char *numstr) { long val; char *errptr; val = strtol (numstr, &errptr, 10); if (*errptr || errno == ERANGE) { fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog, numstr); exit (E_BAD_ARG); } return val; } /* * 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 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 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 setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); /* * The program behaves differently when executed by root than when * executed by a normal user. */ amroot = (getuid () == 0); /* * Get the program name. The program name is used as a prefix to * most error messages. */ Prog = Basename (argv[0]); sanitize_env (); OPENLOG ("passwd"); { /* * Parse the command line options. */ int option_index = 0; 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'}, {"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, "adei:kln:qr:Suw:x:", long_options, &option_index)) != -1) { switch (c) { case 'a': aflg++; break; case 'd': dflg++; anyflag = 1; break; case 'e': eflg++; anyflag = 1; break; case 'i': inact = getnumber (optarg); if (inact >= -1) iflg++; anyflag = 1; break; case 'k': /* change only if expired, like Linux-PAM passwd -k. */ kflg++; /* ok for users */ break; case 'l': lflg++; anyflag = 1; break; case 'n': age_min = getnumber (optarg); nflg++; anyflag = 1; break; case 'q': qflg++; /* 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 'S': Sflg++; /* ok for users */ break; case 'u': uflg++; anyflag = 1; break; case 'w': warn = getnumber (optarg); if (warn >= -1) wflg++; anyflag = 1; break; case 'x': age_max = getnumber (optarg); xflg++; anyflag = 1; 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 (!pw) { fprintf (stderr, _("%s: Cannot determine your user name.\n"), Prog); 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) { fprintf (stderr, _("%s: Permission denied.\n"), Prog); exit (E_NOPERM); } setpwent (); while ((pw = getpwent ())) print_status (pw); 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 (anyflag + Sflg + kflg > 1) usage (E_USAGE); if (anyflag && !amroot) { fprintf (stderr, _("%s: Permission denied.\n"), Prog); exit (E_NOPERM); } pw = xgetpwnam (name); if (!pw) { fprintf (stderr, _("%s: unknown user %s\n"), Prog, name); exit (E_NOPERM); } #ifdef WITH_SELINUX /* * If the UID of the user does not match the current real UID, * check if the change is allowed by SELinux policy. */ if ((pw->pw_uid != getuid ()) && (is_selinux_enabled () > 0 ? (selinux_check_passwd_access (PASSWD__PASSWD) != 0) : !amroot)) { #else /* * If the UID of the user does not match the current real UID, * check if I'm root. */ if (!amroot && pw->pw_uid != getuid ()) { #endif 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 (!sp) 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) printf (_("Changing password for %s\n"), name); if (new_password (pw)) { fprintf (stderr, _("The password for %s is unchanged.\n"), name); closelog (); exit (E_NOPERM); } do_update_pwd = 1; do_update_age = 1; } #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)) { fprintf (stderr, _("Cannot change ID to root.\n")); 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 (!eflg) printf (_("Password changed.\n")); else printf (_("Password set to expire.\n")); } exit (E_SUCCESS); /* NOT REACHED */ }