summaryrefslogtreecommitdiffstats
path: root/src/passwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/passwd.c')
-rw-r--r--src/passwd.c1168
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;
+}
+