home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright 1989, 1990, John F. Haugh II
- * All rights reserved.
- *
- * Permission is granted to copy and create derivative works for any
- * non-commercial purpose, provided this copyright notice is preserved
- * in all copies of source code, or included in human readable form
- * and conspicuously displayed on all copies of object code or
- * distribution media.
- */
-
- #include <sys/types.h>
- #include <time.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <syslog.h>
-
- #ifndef lint
- static char sccsid[] = "@(#)passwd.c 3.1 09:00:47 2/8/91";
- #endif
-
- /*
- * Set up some BSD defines so that all the BSD ifdef's are
- * kept right here
- */
-
- #ifndef BSD
- #include <string.h>
- #include <memory.h>
- #define bzero(a,n) memset(a, 0, n)
- #else
- #include <strings.h>
- #define strchr index
- #define strrchr rindex
- #endif
-
- #include "config.h"
- #include "pwd.h"
- #include "lastlog.h"
- #include "shadow.h"
-
- /*
- * Password aging constants
- *
- * DAY - seconds in a day
- * WEEK - seconds in a week
- * SCALE - convert from clock to aging units
- */
-
- #define DAY (24L*3600L)
- #define WEEK (7L*DAY)
-
- #ifdef ITI_AGING
- #define SCALE (1)
- #else
- #define SCALE DAY
- #endif
-
- /*
- * Global variables
- */
-
- char name[32]; /* The user's name */
- char *Prog; /* Program name */
- int amroot; /* The real UID was 0 */
-
- /*
- * External identifiers
- */
-
- extern char *getpass();
- extern char *pw_encrypt();
- extern char *getlogin();
- extern int optind; /* Index into argv[] for current option */
- extern char *optarg; /* Pointer to current option value */
- #ifdef NDBM
- extern int sp_dbm_mode;
- extern int pw_dbm_mode;
- #endif
-
- /*
- * #defines for messages. This facilities foreign language conversion
- * since all messages are defined right here.
- */
-
- #define USAGE "usage: %s [ -f | -s ] [ name ]\n"
- #define ADMUSAGE \
- " %s [ -x max ] [ -n min ] [ -w warn ] [ -i inact ] name\n"
- #define ADMUSAGE2 \
- " %s { -l | -d | -S } name\n"
- #define OLDPASS "Old Password:"
- #define NEWPASSMSG \
- "Enter the new password (minimum of 5 characters)\n\
- Please use a combination of upper and lower case letters and numbers.\n"
- #define NEWPASS "New Password:"
- #define NEWPASS2 "Re-enter new password:"
- #define WRONGPWD "Incorrect password for %s.\n"
- #define WRONGPWD2 "incorrect password for `%s'\n"
- #define NOMATCH "They don't match; try again.\n"
- #define CANTCHANGE "The password for %s cannot be changed.\n"
- #define CANTCHANGE2 "password locked for `%s'\n"
- #define TOOSOON "Sorry, the password for %s cannot be changed yet.\n"
- #define TOOSOON2 "now < sp_min for `%s'\n"
- #define EXECFAILED "%s: Cannot execute %s"
- #define EXECFAILED2 "cannot execute %s\n"
- #define WHOAREYOU "%s: Cannot determine you user name.\n"
- #define UNKUSER "%s: Unknown user %s\n"
- #define NOPERM "You may not change the password for %s.\n"
- #define NOPERM2 "can't change pwd for `%s'\n"
- #define UNCHANGED "The password for %s is unchanged.\n"
- #define SPWDBUSY "Cannot lock the password file; try again later.\n"
- #define SPWDBUSY2 "can't lock /etc/shadow\n"
- #define OPNERROR "Cannot open the password file.\n"
- #define OPNERROR2 "can't open /etc/shadow\n"
- #define UPDERROR "Error updating the password entry.\n"
- #define UPDERROR2 "error updating shadow entry\n"
- #define DBMERROR "Error updating the DBM password entry.\n"
- #define DBMERROR2 "error updating DBM shadow entry.\n"
- #define NOTROOT "Cannot change ID to root.\n"
- #define NOTROOT2 "can't setuid(0).\n"
- #define CLSERROR "Cannot commit shadow file changes.\n"
- #define CLSERROR2 "can't rewrite /etc/shadow.\n"
- #define UNLKERROR "Cannot unlock the shadow file.\n"
- #define UNLKERROR2 "can't unlock /etc/shadow.\n"
- #define TRYAGAIN "Try again.\n"
- #define CHGPASSWD "changed password for `%s'\n"
-
- /*
- * usage - print command usage and exit
- */
-
- void
- usage ()
- {
- fprintf (stderr, USAGE, Prog);
- if (amroot) {
- fprintf (stderr, ADMUSAGE, Prog);
- fprintf (stderr, ADMUSAGE2, Prog);
- }
- exit (1);
- }
-
- /*
- * new_password - validate old password and replace with new
- */
-
- int
- new_password (pw, sp)
- struct passwd *pw;
- struct spwd *sp;
- {
- char *clear; /* Pointer to clear text */
- char *cipher; /* Pointer to cipher text */
- char *cp; /* Pointer to getpass() response */
- char orig[BUFSIZ]; /* Original password */
- char pass[BUFSIZ]; /* New password */
- int i; /* Counter for retries */
-
- /*
- * Authenticate the user. The user will be prompted for their
- * own password.
- */
-
- if (! amroot && sp->sp_pwdp[0]) {
- bzero (orig, sizeof orig);
-
- if (! (clear = getpass (OLDPASS)))
- return -1;
-
- cipher = pw_encrypt (clear, sp->sp_pwdp);
- if (strcmp (cipher, sp->sp_pwdp) != 0) {
- sleep (1);
- fprintf (stderr, WRONGPWD, sp->sp_namp);
- syslog (LOG_WARN, WRONGPWD2, sp->sp_namp);
- return -1;
- }
- strcpy (orig, clear);
- }
-
- /*
- * Get the new password. The user is prompted for the new password
- * and has three 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.
- */
-
- printf (NEWPASSMSG);
- for (i = 0;i < 3;i++) {
- if (! (cp = getpass (NEWPASS)))
- return -1;
- else
- strcpy (pass, cp);
-
- if (! amroot && ! obscure (orig, pass)) {
- printf (TRYAGAIN);
- continue;
- }
- if (! (cp = getpass (NEWPASS2)))
- return -1;
-
- if (strcmp (cp, pass))
- fprintf (stderr, NOMATCH);
- else
- break;
- }
- if (i == 3)
- return -1;
-
- /*
- * Encrypt the password. The new password is encrypted and
- * the shadow password structure updated to reflect the change.
- */
-
- sp->sp_pwdp = pw_encrypt (pass, (char *) 0);
- sp->sp_lstchg = time ((time_t *) 0) / SCALE;
-
- 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.
- */
-
- void
- check_password (pw, sp)
- struct passwd *pw;
- struct spwd *sp;
- {
- time_t now = time ((time_t *) 0) / SCALE;
-
- /*
- * Root can change any password any time.
- */
-
- if (amroot)
- return;
-
- /*
- * 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_expire > 0 && now >= sp->sp_expire) ||
- (sp->sp_inact >= 0 && sp->sp_max >= 0 &&
- now >= (sp->sp_lstchg + sp->sp_inact + sp->sp_max)) ||
- strcmp (sp->sp_pwdp, "!") == 0 ||
- sp->sp_min > sp->sp_max) {
- fprintf (stderr, CANTCHANGE, sp->sp_namp);
- syslog (LOG_WARN, CANTCHANGE2, sp->sp_namp);
- exit (1);
- }
-
- /*
- * Passwords may only be changed after sp_min time is up.
- */
-
- if (sp->sp_min >= 0 && now < (sp->sp_lstchg + sp->sp_min)) {
- fprintf (stderr, TOOSOON, sp->sp_namp);
- syslog (LOG_WARN, TOOSOON2, sp->sp_namp);
- exit (1);
- }
- }
-
- /*
- * pwd_to_spwd - create entries for new spwd structure
- *
- * pwd_to_spwd() creates a new (struct spwd) containing the
- * information in the pointed-to (struct passwd).
- */
-
- void
- pwd_to_spwd (pw, sp)
- struct passwd *pw;
- struct spwd *sp;
- {
- time_t t;
-
- /*
- * Nice, easy parts first. The name and passwd map directly
- * from the old password structure to the new one.
- */
-
- sp->sp_namp = strdup (pw->pw_name);
- sp->sp_pwdp = strdup (pw->pw_passwd);
- #ifdef ATT_AGE
-
- /*
- * AT&T-style password aging maps the sp_min, sp_max, and
- * sp_lstchg information from the pw_age field, which appears
- * after the encrypted password.
- */
-
- if (pw->pw_age[0]) {
- t = (c64i (pw->pw_age[0]) * WEEK) / SCALE;
- sp->sp_max = t;
-
- if (pw->pw_age[1]) {
- t = (c64i (pw->pw_age[1]) * WEEK) / SCALE;
- sp->sp_min = t;
- } else
- sp->sp_min = (10000L * DAY) / SCALE;
-
- if (pw->pw_age[1] && pw->pw_age[2]) {
- t = (a64l (pw->pw_age + 2) * WEEK) / SCALE;
- sp->sp_lstchg = t;
- } else
- sp->sp_lstchg = time ((time_t *) 0) / SCALE;
- } else {
- sp->sp_min = 0;
- sp->sp_max = (10000L * DAY) / SCALE;
- sp->sp_lstchg = time ((time_t *) 0) / SCALE;
- }
- #else
- /*
- * BSD does not use the pw_age field and has no aging information
- * anywheres. The default values are used to initialize the
- * fields which are in the missing pw_age field;
- */
-
- sp->sp_min = 0;
- sp->sp_max = (10000L * DAY) / SCALE;
- sp->sp_lstchg = time ((time_t *) 0) / SCALE;
- #endif
-
- /*
- * These fields have no corresponding information in the password
- * file. They are set to uninitialized values.
- */
-
- sp->sp_warn = -1;
- sp->sp_inact = -1;
- sp->sp_expire = -1;
- sp->sp_flag = -1;
- }
-
- /*
- * print_status - print current password status
- */
-
- void
- print_status (sp)
- struct spwd *sp;
- {
- struct tm *tm;
- time_t time;
-
- time = sp->sp_lstchg * SCALE;
- tm = gmtime (&time);
-
- printf ("%s ", sp->sp_namp);
- printf ("%s ",
- sp->sp_pwdp[0] ? (sp->sp_pwdp[0] == '!' ? "L":"P"):"NP");
- printf ("%02.2d/%02.2d/%02.2d ",
- tm->tm_mon + 1, tm->tm_mday, tm->tm_year % 100);
- printf ("%d %d %d %d\n",
- (sp->sp_min * SCALE) / DAY, (sp->sp_max * SCALE) / DAY,
- (sp->sp_warn * SCALE) / DAY, (sp->sp_inact * SCALE) / DAY);
- }
-
- /*
- * 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
- *
- * -l lock the named account (*)
- * -d delete the password for the named account (*)
- * -x # set sp_max to # days (*)
- * -n # set sp_min to # days (*)
- * -w # set sp_warn to # days (*)
- * -i # set sp_inact to # days (*)
- * -S show password status of named account (*)
- * -g execute gpasswd command to interpret flags
- * -f execute chfn command to interpret flags
- * -s execute chsh command to interpret flags
- *
- * (*) 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 (argc, argv)
- int argc;
- char **argv;
- {
- char buf[BUFSIZ]; /* I/O buffer for messages, etc. */
- char *cp; /* Miscellaneous character pointing */
- time_t min; /* Minimum days before change */
- time_t max; /* Maximum days until change */
- time_t warn; /* Warning days before change */
- time_t inact; /* Days without change before locked */
- int i; /* Loop control variable */
- int flag; /* Current option to process */
- int lflg = 0; /* -l - lock account option */
- int dflg = 0; /* -d - delete password option */
- int xflg = 0; /* -x - set maximum days */
- int nflg = 0; /* -n - set minimum days */
- int wflg = 0; /* -w - set warning days */
- int iflg = 0; /* -i - set inactive days */
- int Sflg = 0; /* -S - show password status */
- struct passwd *pw; /* Password file entry for user */
- struct spwd *sp; /* Shadow file entry for user */
- struct spwd tspwd; /* New shadow file entry if none */
-
- /*
- * The program behaves differently when executed by root
- * than when executed by a normal user.
- */
-
- amroot = getuid () == 0;
- #ifdef NDBM
- sp_dbm_mode = O_RDWR;
- pw_dbm_mode = O_RDWR;
- #endif
-
- /*
- * Get the program name. The program name is used as a
- * prefix to most error messages. It is also used as input
- * to the openlog() function for error logging.
- */
-
- if (Prog = strrchr (argv[0], '/'))
- Prog++;
- else
- Prog = argv[0];
-
- openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
-
- /*
- * Start with the flags which cause another command to be
- * executed. The effective UID will be set back to the
- * real UID and the new command executed with the flags
- */
-
- if (argc > 1 && argv[1][0] == '-' && strchr ("gfs", argv[1][1])) {
- setuid (getuid ());
- switch (argv[1][1]) {
- case 'g':
- argv[1] = "gpasswd";
- execv ("/bin/gpasswd", &argv[1]);
- break;
- case 'f':
- argv[1] = "chfn";
- execv ("/bin/chfn", &argv[1]);
- break;
- case 's':
- argv[1] = "chsh";
- execv ("/bin/chsh", &argv[1]);
- break;
- default:
- usage ();
- }
- sprintf (buf, EXECFAILED, Prog, argv[1]);
- perror (buf);
- syslog (LOG_CRIT, EXECFAILED2, argv[1]);
- exit (1);
- }
-
- /*
- * The remaining arguments will be processed one by one and
- * executed by this command. The name is the last argument
- * if it does not begin with a "-", otherwise the name is
- * determined from the environment and must agree with the
- * real UID. Also, the UID will be checked for any commands
- * which are restricted to root only.
- */
-
- while ((flag = getopt (argc, argv, "ldx:n:w:i:S")) != EOF) {
- switch (flag) {
- case 'x':
- max = strtol (optarg, &cp, 10);
- if (*cp || getuid ())
- usage ();
-
- xflg++;
- break;
- case 'n':
- min = strtol (optarg, &cp, 10);
- if (*cp || getuid ())
- usage ();
-
- nflg++;
- break;
- case 'w':
- warn = strtol (optarg, &cp, 10);
- if (*cp || getuid ())
- usage ();
-
- wflg++;
- break;
- case 'i':
- inact = strtol (optarg, &cp, 10);
- if (*cp || getuid ())
- usage ();
-
- iflg++;
- break;
- case 'S':
- if (getuid ())
- usage ();
-
- Sflg++;
- break;
- case 'd':
- dflg++;
- break;
- case 'l':
- lflg++;
- break;
- default:
- usage ();
- }
- }
-
- /*
- * 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, on -x, -n, -m, and
- * -i may appear with each other. -d, -l and -S must appear alone.
- */
-
- if ((dflg || lflg || xflg || nflg ||
- wflg || iflg || Sflg) && optind >= argc)
- usage ();
-
- if ((dflg + lflg + (xflg || nflg || wflg || iflg) + Sflg) > 1)
- usage ();
-
- /*
- * 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.
- */
-
- if (optind < argc) {
- strncpy (name, argv[optind], sizeof name);
- name[sizeof name - 1] = '\0';
- } else if (cp = getlogin ()) {
- strncpy (name, cp, sizeof name);
- name[sizeof name - 1] = '\0';
- } else {
- fprintf (stderr, WHOAREYOU, Prog);
- exit (1);
- }
-
- /*
- * Now I have a name, let's see if the UID for the name
- * matches the current real UID.
- */
-
- if (! (pw = getpwnam (name))) {
- fprintf (stderr, UNKUSER, Prog, name);
- exit (1);
- }
- if (! amroot && pw->pw_uid != getuid ()) {
- fprintf (stderr, NOPERM, name);
- syslog (LOG_WARN, NOPERM2, name);
- exit (1);
- }
-
- /*
- * The user name is valid, so let's get the shadow file
- * entry.
- */
-
- if (! (sp = getspnam (name)))
- pwd_to_spwd (pw, sp = &tspwd);
-
- /*
- * Save the shadow entry off to the side so it doesn't
- * get changed by any of the following code.
- */
-
- if (sp != &tspwd) {
- tspwd = *sp;
- sp = &tspwd;
- }
- tspwd.sp_namp = strdup (sp->sp_namp);
- tspwd.sp_pwdp = strdup (sp->sp_pwdp);
-
- if (Sflg) {
- print_status (sp);
- exit (0);
- }
-
- /*
- * If there are no other flags, just change the password.
- */
-
- if (! (dflg || lflg || xflg || nflg || wflg || iflg)) {
-
- /*
- * See if the user is permitted to change the password.
- * Otherwise, go ahead and set a new password.
- */
-
- check_password (pw, sp);
-
- if (new_password (pw, sp)) {
- fprintf (stderr, UNCHANGED, name);
- exit (1);
- }
- }
-
- /*
- * The other options are incredibly simple. Just modify the
- * field in the shadow file entry.
- */
-
- if (dflg) /* Set password to blank */
- sp->sp_pwdp = "";
-
- if (lflg) /* Set password to "locked" value */
- sp->sp_pwdp = "!";
-
- if (xflg)
- sp->sp_max = (max * DAY) / SCALE;
-
- if (nflg)
- sp->sp_min = (min * DAY) / SCALE;
-
- if (wflg)
- sp->sp_warn = (warn * DAY) / SCALE;
-
- if (iflg)
- sp->sp_inact = (inact * DAY) / SCALE;
-
- /*
- * 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.
- */
-
- ulimit (2, 30000);
- if (setuid (0)) {
- fprintf (stderr, NOTROOT);
- syslog (LOG_ERR, NOTROOT2);
- exit (1);
- }
- signal (SIGHUP, SIG_IGN);
- signal (SIGINT, SIG_IGN);
- signal (SIGQUIT, SIG_IGN);
- #ifdef SIGTSTP
- signal (SIGTSTP, SIG_IGN);
- #endif
-
- /*
- * The shadow entry is now ready to be committed back to
- * the shadow file. Get a lock on the file and open it.
- */
-
- for (i = 0;i < 30;i++)
- if (spw_lock ())
- break;
-
- if (i == 30) {
- fprintf (stderr, SPWDBUSY);
- syslog (LOG_WARN, SPWDBUSY2);
- exit (1);
- }
- if (! spw_open (O_RDWR)) {
- fprintf (stderr, OPNERROR);
- syslog (LOG_ERR, OPNERROR2);
- (void) spw_unlock ();
- exit (1);
- }
-
- /*
- * Update the shadow file entry. If there is a DBM file,
- * update that entry as well.
- */
-
- if (! spw_update (sp)) {
- fprintf (stderr, UPDERROR);
- syslog (LOG_ERR, UPDERROR2);
- (void) spw_unlock ();
- exit (1);
- }
- #ifdef NDBM
- if (access ("/etc/shadow.pag", 0) == 0 && ! sp_dbm_update (sp)) {
- fprintf (stderr, DBMERROR);
- syslog (LOG_ERR, DBMERROR2);
- (void) spw_unlock ();
- exit (1);
- }
- #endif
-
- /*
- * Changes have all been made, so commit them and unlock the
- * file.
- */
-
- if (! spw_close ()) {
- fprintf (stderr, CLSERROR);
- syslog (LOG_ERR, CLSERROR2);
- (void) spw_unlock ();
- exit (1);
- }
- if (! spw_unlock ()) {
- fprintf (stderr, UNLKERROR);
- syslog (LOG_ERR, UNLKERROR2);
- exit (1);
- }
- syslog (LOG_INFO, CHGPASSWD, name);
- exit (0);
- }
-