2 * Copyright 1989 - 1994, Julianne Frances Haugh
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 RCSID (PKG_VER "$Id: passwd.c,v 1.30 2003/12/17 09:43:30 kloczek Exp $")
34 #include "prototypes.h"
36 #include <sys/types.h>
42 #ifndef GPASSWD_PROGRAM
43 #define GPASSWD_PROGRAM "gpasswd"
46 #define CHFN_PROGRAM "chfn"
49 #define CHSH_PROGRAM "chsh"
62 #define E_SUCCESS 0 /* success */
63 #define E_NOPERM 1 /* permission denied */
64 #define E_USAGE 2 /* invalid combination of options */
65 #define E_FAILURE 3 /* unexpected failure, nothing done */
66 #define E_MISSING 4 /* unexpected failure, passwd file missing */
67 #define E_PWDBUSY 5 /* passwd file busy, try again later */
68 #define E_BAD_ARG 6 /* invalid argument to option */
72 static char *name; /* The name of user whose password is being changed */
73 static char *myname; /* The current user's name */
74 static char *Prog; /* Program name */
75 static int amroot; /* The real UID was 0 */
79 eflg = 0, /* -e - force password change */
80 iflg = 0, /* -i - set inactive days */
81 kflg = 0, /* -k - change only if expired */
82 nflg = 0, /* -n - set minimum days */
83 wflg = 0, /* -w - set warning days */
84 xflg = 0, /* -x - set maximum days */
86 aflg = 0, /* -a - show status for all users */
87 dflg = 0, /* -d - delete password */
88 lflg = 0, /* -l - lock account */
89 qflg = 0, /* -q - quiet mode */
90 Sflg = 0, /* -S - show password status */
91 uflg = 0; /* -u - unlock account */
94 * set to 1 if there are any flags which require root privileges,
95 * and require username to be specified
97 static int anyflag = 0;
100 static long age_min = 0; /* Minimum days before change */
101 static long age_max = 0; /* Maximum days until change */
102 static long warn = 0; /* Warning days before change */
103 static long inact = 0; /* Days without change before locked */
106 static int do_update_age = 0;
109 static char crypt_passwd[128]; /* The "old-style" password, if present */
110 static int do_update_pwd = 0;
114 * External identifiers
118 extern int sp_dbm_mode;
119 extern int pw_dbm_mode;
122 /* local function prototypes */
123 static void usage (int);
126 static int reuse (const char *, const struct passwd *);
127 static int new_password (const struct passwd *);
130 static void check_password (const struct passwd *, const struct spwd *);
131 #else /* !SHADOWPWD */
132 static void check_password (const struct passwd *);
133 #endif /* !SHADOWPWD */
134 static char *insert_crypt_passwd (const char *, const char *);
135 #endif /* !USE_PAM */
136 static char *date_to_str (time_t);
137 static const char *pw_status (const char *);
138 static void print_status (const struct passwd *);
139 static void fail_exit (int);
140 static void oom (void);
141 static char *update_crypt_pw (char *);
142 static void update_noshadow (void);
145 static void update_shadow (void);
147 static long getnumber (const char *);
150 * usage - print command usage and exit
153 static void usage (int status)
155 fprintf (stderr, _("Usage: %s [-f|-s] [name]\n"), Prog);
159 (" %s [-x max] [-n min] [-w warn] [-i inact] name\n"),
161 fprintf (stderr, _(" %s {-l|-u|-d|-S|-e} name\n"),
168 static int reuse (const char *pass, const struct passwd *pw)
170 #ifdef HAVE_LIBCRACK_HIST
173 #ifdef HAVE_LIBCRACK_PW
174 const char *FascistHistoryPw (const char *, const struct passwd *);
176 reason = FascistHistory (pass, pw);
178 const char *FascistHistory (const char *, int);
180 reason = FascistHistory (pass, pw->pw_uid);
183 printf (_("Bad password: %s. "), reason);
191 * new_password - validate old password and replace with new (both old and
192 * new in global "char crypt_passwd[128]")
195 /*ARGSUSED*/ static int new_password (const struct passwd *pw)
197 char *clear; /* Pointer to clear text */
198 char *cipher; /* Pointer to cipher text */
199 char *cp; /* Pointer to getpass() response */
200 char orig[200]; /* Original password */
201 char pass[200]; /* New password */
202 int i; /* Counter for retries */
206 #ifdef HAVE_LIBCRACK_HIST
207 int HistUpdate (const char *, const char *);
211 * Authenticate the user. The user will be prompted for their own
215 if (!amroot && crypt_passwd[0]) {
216 if (!(clear = getpass (_("Old password: "))))
219 cipher = pw_encrypt (clear, crypt_passwd);
220 if (strcmp (cipher, crypt_passwd) != 0) {
221 SYSLOG ((LOG_WARN, "incorrect password for `%s'",
225 _("Incorrect password for `%s'\n"),
229 STRFCPY (orig, clear);
237 * Get the new password. The user is prompted for the new password
238 * and has five tries to get it right. The password will be tested
239 * for strength, unless it is the root user. This provides an escape
240 * for initial login passwords.
243 if (getdef_bool ("MD5_CRYPT_ENAB"))
246 pass_max_len = getdef_num ("PASS_MAX_LEN", 8);
250 Enter the new password (minimum of %d, maximum of %d characters)\n\
251 Please use a combination of upper and lower case letters and numbers.\n"), getdef_num ("PASS_MIN_LEN", 5), pass_max_len);
254 for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) {
255 if (!(cp = getpass (_("New password: ")))) {
256 memzero (orig, sizeof orig);
259 if (warned && strcmp (pass, cp) != 0)
265 && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
266 printf (_("Try again.\n"));
270 * If enabled, warn about weak passwords even if you are
271 * root (enter this password again to use it anyway).
274 if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN")
275 && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
277 ("\nWarning: weak password (enter it again to use it anyway).\n"));
281 if (!(cp = getpass (_("Re-enter new password: ")))) {
282 memzero (orig, sizeof orig);
285 if (strcmp (cp, pass))
287 _("They don't match; try again.\n"));
293 memzero (orig, sizeof orig);
296 memzero (pass, sizeof pass);
301 * Encrypt the password, then wipe the cleartext password.
304 cp = pw_encrypt (pass, crypt_make_salt ());
305 memzero (pass, sizeof pass);
307 #ifdef HAVE_LIBCRACK_HIST
308 HistUpdate (pw->pw_name, crypt_passwd);
310 STRFCPY (crypt_passwd, cp);
315 * check_password - test a password to see if it can be changed
317 * check_password() sees if the invoker has permission to change the
318 * password for the given user.
322 static void check_password (const struct passwd *pw, const struct spwd *sp)
325 static void check_password (const struct passwd *pw)
328 time_t now, last, ok;
332 exp_status = isexpired (pw, sp);
334 exp_status = isexpired (pw);
338 * If not expired and the "change only if expired" option (idea from
339 * PAM) was specified, do nothing. --marekm
341 if (kflg && exp_status == 0)
345 * Root can change any password any time.
355 * Expired accounts cannot be changed ever. Passwords which are
356 * locked may not be changed. Passwords where min > max may not be
357 * changed. Passwords which have been inactive too long cannot be
361 if (sp->sp_pwdp[0] == '!' || exp_status > 1 ||
362 (sp->sp_max >= 0 && sp->sp_min > sp->sp_max)) {
364 _("The password for %s cannot be changed.\n"),
366 SYSLOG ((LOG_WARN, "password locked for `%s'",
373 * Passwords may only be changed after sp_min time is up.
376 last = sp->sp_lstchg * SCALE;
377 ok = last + (sp->sp_min > 0 ? sp->sp_min * SCALE : 0);
379 #else /* !SHADOWPWD */
380 if (pw->pw_passwd[0] == '!' || exp_status > 1) {
382 _("The password for %s cannot be changed.\n"),
384 SYSLOG ((LOG_WARN, "password locked for `%s'",
392 #endif /* !SHADOWPWD */
396 ("Sorry, the password for %s cannot be changed yet.\n"),
398 SYSLOG ((LOG_WARN, "now < minimum age for `%s'",
406 * insert_crypt_passwd - add an "old-style" password to authentication
407 * string result now malloced to avoid overflow, just in case. --marekm
409 static char *insert_crypt_passwd (const char *string, const char *passwd)
411 return xstrdup (passwd);
413 #endif /* !USE_PAM */
415 static char *date_to_str (time_t t)
422 strftime (buf, sizeof buf, "%m/%d/%Y", tm);
424 snprintf (buf, sizeof buf, "%02d/%02d/%04d",
425 tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900);
430 static const char *pw_status (const char *pass)
432 if (*pass == '*' || *pass == '!')
440 * print_status - print current password status
443 static void print_status (const struct passwd *pw)
450 sp = getspnam (pw->pw_name);
452 printf ("%s %s %s %ld %ld %ld %ld\n",
454 pw_status (sp->sp_pwdp),
455 date_to_str (sp->sp_lstchg * SCALE),
456 (sp->sp_min * SCALE) / DAY,
457 (sp->sp_max * SCALE) / DAY,
458 (sp->sp_warn * SCALE) / DAY,
459 (sp->sp_inact * SCALE) / DAY);
463 printf ("%s %s\n", pw->pw_name, pw_status (pw->pw_passwd));
468 static void fail_exit (int status)
477 static void oom (void)
479 fprintf (stderr, _("%s: out of memory\n"), Prog);
480 fail_exit (E_FAILURE);
483 static char *update_crypt_pw (char *cp)
487 cp = insert_crypt_passwd (cp, crypt_passwd);
491 cp = ""; /* XXX warning: const */
493 if (uflg && *cp == '!')
496 if (lflg && *cp != '!') {
497 char *newpw = xmalloc (strlen (cp) + 2);
507 static void update_noshadow (void)
509 const struct passwd *pw;
515 ("Cannot lock the password file; try again later.\n"));
516 SYSLOG ((LOG_WARN, "can't lock password file"));
519 if (!pw_open (O_RDWR)) {
520 fprintf (stderr, _("Cannot open the password file.\n"));
521 SYSLOG ((LOG_ERR, "can't open password file"));
522 fail_exit (E_MISSING);
524 pw = pw_locate (name);
526 fprintf (stderr, _("%s: %s not found in /etc/passwd\n"),
528 fail_exit (E_NOPERM);
533 npw->pw_passwd = update_crypt_pw (npw->pw_passwd);
534 if (!pw_update (npw)) {
536 _("Error updating the password entry.\n"));
537 SYSLOG ((LOG_ERR, "error updating password entry"));
538 fail_exit (E_FAILURE);
541 if (pw_dbm_present () && !pw_dbm_update (npw)) {
543 _("Error updating the DBM password entry.\n"));
544 SYSLOG ((LOG_ERR, "error updaring dbm password entry"));
545 fail_exit (E_FAILURE);
551 _("Cannot commit password file changes.\n"));
552 SYSLOG ((LOG_ERR, "can't rewrite password file"));
553 fail_exit (E_FAILURE);
559 static void update_shadow (void)
561 const struct spwd *sp;
567 ("Cannot lock the password file; try again later.\n"));
568 SYSLOG ((LOG_WARN, "can't lock password file"));
571 if (!spw_open (O_RDWR)) {
572 fprintf (stderr, _("Cannot open the password file.\n"));
573 SYSLOG ((LOG_ERR, "can't open password file"));
574 fail_exit (E_FAILURE);
576 sp = spw_locate (name);
578 /* Try to update the password in /etc/passwd instead. */
584 nsp = __spw_dup (sp);
587 nsp->sp_pwdp = update_crypt_pw (nsp->sp_pwdp);
589 nsp->sp_max = (age_max * DAY) / SCALE;
591 nsp->sp_min = (age_min * DAY) / SCALE;
593 nsp->sp_warn = (warn * DAY) / SCALE;
595 nsp->sp_inact = (inact * DAY) / SCALE;
597 nsp->sp_lstchg = time ((time_t *) 0) / SCALE;
599 * Force change on next login, like SunOS 4.x passwd -e or Solaris
600 * 2.x passwd -f. Solaris 2.x seems to do the same thing (set
606 if (!spw_update (nsp)) {
608 _("Error updating the password entry.\n"));
609 SYSLOG ((LOG_ERR, "error updating password entry"));
610 fail_exit (E_FAILURE);
613 if (sp_dbm_present () && !sp_dbm_update (nsp)) {
615 _("Error updating the DBM password entry.\n"));
616 SYSLOG ((LOG_ERR, "error updaring dbm password entry"));
617 fail_exit (E_FAILURE);
623 _("Cannot commit password file changes.\n"));
624 SYSLOG ((LOG_ERR, "can't rewrite password file"));
625 fail_exit (E_FAILURE);
629 #endif /* SHADOWPWD */
631 static long getnumber (const char *str)
636 val = strtol (str, &cp, 10);
643 * passwd - change a user's password file information
645 * This command controls the password file and commands which are used
648 * The valid options are
650 * -l lock the named account (*)
651 * -u unlock the named account (*)
652 * -d delete the password for the named account (*)
653 * -e expire the password for the named account (*)
654 * -x # set sp_max to # days (*)
655 * -n # set sp_min to # days (*)
656 * -w # set sp_warn to # days (*)
657 * -i # set sp_inact to # days (*)
658 * -S show password status of named account
659 * -g execute gpasswd command to interpret flags
660 * -f execute chfn command to interpret flags
661 * -s execute chsh command to interpret flags
662 * -k change password only if expired
664 * (*) requires root permission to execute.
666 * All of the time fields are entered in days and converted to the
667 * appropriate internal format. For finer resolute the chage
668 * command must be used.
671 int main (int argc, char **argv)
673 int flag; /* Current option to process */
674 const struct passwd *pw; /* Password file entry for user */
677 char *cp; /* Miscellaneous character pointing */
680 const struct spwd *sp; /* Shadow file entry for user */
684 setlocale (LC_ALL, "");
685 bindtextdomain (PACKAGE, LOCALEDIR);
686 textdomain (PACKAGE);
689 * The program behaves differently when executed by root than when
690 * executed by a normal user.
692 amroot = (getuid () == 0);
695 * Get the program name. The program name is used as a prefix to
696 * most error messages.
698 Prog = Basename (argv[0]);
705 * Start with the flags which cause another command to be executed.
706 * The effective UID will be set back to the real UID and the new
707 * command executed with the flags
709 * These flags are deprecated, may change in a future release.
710 * Please run these programs directly. --marekm
713 if (argc > 1 && argv[1][0] == '-' && strchr ("gfs", argv[1][1])) {
717 switch (argv[1][1]) {
719 argv[1] = GPASSWD_PROGRAM; /* XXX warning: const */
722 argv[1] = CHFN_PROGRAM; /* XXX warning: const */
725 argv[1] = CHSH_PROGRAM; /* XXX warning: const */
730 snprintf (buf, sizeof buf, _("%s: Cannot execute %s"),
732 execvp (argv[1], &argv[1]);
734 SYSLOG ((LOG_ERR, "cannot execute %s", argv[1]));
740 * The remaining arguments will be processed one by one and executed
741 * by this command. The name is the last argument if it does not
742 * begin with a "-", otherwise the name is determined from the
743 * environment and must agree with the real UID. Also, the UID will
744 * be checked for any commands which are restricted to root only.
748 #define FLAGS "adlqr:uSekn:x:i:w:"
750 # define FLAGS "adlqr:uS"
753 while ((flag = getopt (argc, argv, FLAGS)) != EOF) {
758 age_max = getnumber (optarg);
763 age_min = getnumber (optarg);
768 warn = getnumber (optarg);
774 inact = getnumber (optarg);
784 /* change only if expired, like Linux-PAM passwd -k. */
785 kflg++; /* ok for users */
787 #endif /* SHADOWPWD */
792 qflg++; /* ok for users */
795 Sflg++; /* ok for users */
810 /* -r repository (files|nis|nisplus) */
811 /* only "files" supported for now */
812 if (strcmp (optarg, "files") != 0) {
815 ("%s: repository %s not supported\n"),
826 * Now I have to get the user name. The name will be gotten from the
827 * command line if possible. Otherwise it is figured out from the
831 pw = get_my_pwent ();
834 _("%s: Cannot determine your user name.\n"),
838 myname = xstrdup (pw->pw_name);
845 * The -a flag requires -S, no other flags, no username, and
846 * you must be root. --marekm
850 if (anyflag || !Sflg || (optind < argc))
853 fprintf (stderr, _("%s: Permission denied.\n"),
858 while ((pw = getpwent ()))
864 * Allow certain users (administrators) to change passwords of
865 * certain users. Not implemented yet. --marekm
867 if (may_change_passwd (myname, name))
872 * If any of the flags were given, a user name must be supplied on
873 * the command line. Only an unadorned command line doesn't require
874 * the user's name be given. Also, -x, -n, -w, -i, -e, -d,
875 * -l, -u may appear with each other. -S, -k must appear alone.
879 * -S now ok for normal users (check status of my own account), and
880 * doesn't require username. --marekm
883 if (anyflag && optind >= argc)
886 if (anyflag + Sflg + kflg > 1)
889 if (anyflag && !amroot) {
890 fprintf (stderr, _("%s: Permission denied\n"), Prog);
895 pw_dbm_mode = O_RDWR;
897 sp_dbm_mode = O_RDWR;
901 pw = getpwnam (name);
903 fprintf (stderr, _("%s: Unknown user %s\n"), Prog, name);
908 * Now I have a name, let's see if the UID for the name matches the
912 if (!amroot && pw->pw_uid != getuid ()) {
914 _("You may not change the password for %s.\n"),
916 SYSLOG ((LOG_WARN, "can't change pwd for `%s'", name));
928 * The user name is valid, so let's get the shadow file entry.
931 sp = getspnam (name);
933 sp = pwd_to_spwd (pw);
941 * If there are no other flags, just change the password.
945 STRFCPY (crypt_passwd, cp);
948 * See if the user is permitted to change the password.
949 * Otherwise, go ahead and set a new password.
953 check_password (pw, sp);
959 * Let the user know whose password is being changed.
962 printf (_("Changing password for %s\n"), name);
964 if (new_password (pw)) {
966 _("The password for %s is unchanged.\n"),
974 #endif /* !USE_PAM */
976 * Before going any further, raise the ulimit to prevent colliding
977 * into a lowered ulimit, and set the real UID to root to protect
978 * against unexpected signals. Any keyboard signals are set to be
985 * Don't set the real UID for PAM...
989 do_pam_passwd (name, qflg, kflg);
992 #endif /* SHADOWPWD */
994 fprintf (stderr, _("Cannot change ID to root.\n"));
995 SYSLOG ((LOG_ERR, "can't setuid(0)"));
1000 if (spw_file_present ())
1006 nscd_flush_cache ("passwd");
1007 nscd_flush_cache ("group");
1009 nscd_flush_cache ("shadow");
1012 SYSLOG ((LOG_INFO, "password for `%s' changed by `%s'", name,
1016 printf (_("Password changed.\n"));