shadow/src/passwd.c
Chris Lamb cb610d54b4 Make the sp_lstchg shadow field reproducible.
The third field in the /etc/shadow file (sp_lstchg) contains the date of
the last password change expressed as the number of days since Jan 1, 1970.
As this is a relative time, creating a user today will result in:

   username:17238:0:99999:7:::

whilst creating the same user tomorrow will result in:

    username:17239:0:99999:7:::

This has an impact for the Reproducible Builds[0] project where we aim to
be independent of as many elements the build environment as possible,
including the current date.

This patch changes the behaviour to use the SOURCE_DATE_EPOCH[1]
environment variable (instead of Jan 1, 1970) if valid.

 [0] https://reproducible-builds.org/
 [1] https://reproducible-builds.org/specs/source-date-epoch/

Signed-off-by: Chris Lamb <lamby@debian.org>
2017-04-10 22:29:21 +01:00

1169 lines
32 KiB
C

/*
* 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;
}