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 <syslog.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <ctype.h>
- #include <time.h>
-
- #ifndef lint
- static char sccsid[] = "%W% %U% %G%";
- #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 "shadow.h"
-
- /*
- * Global variables
- */
-
- char *Prog;
- long mindays;
- long maxdays;
- long lastday;
- long warndays;
- long inactdays;
- long expdays;
- void cleanup();
-
- /*
- * External identifiers
- */
-
- extern long a64l();
- extern int pw_lock(), pw_open(),
- pw_unlock(), pw_close(),
- pw_update();
- extern struct passwd *pw_locate();
- extern int spw_lock(), spw_open(),
- spw_unlock(), spw_close(),
- spw_update();
- extern struct spwd *spw_locate();
- extern int optind;
- extern char *optarg;
- extern char *getlogin ();
- #ifdef NDBM
- extern int pw_dbm_mode;
- extern int sp_dbm_mode;
- #endif
-
- /*
- * 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 (7*DAY)
-
- #ifdef ITI_AGING
- #define SCALE (1)
- #else
- #define SCALE (DAY)
- #endif
-
- /*
- * days and juldays are used to compute the number of days in the
- * current month, and the cummulative number of days in the preceding
- * months. they are declared so that january is 1, not 0.
- */
-
- static short days[13] = { 0,
- 31, 28, 31, 30, 31, 30, /* JAN - JUN */
- 31, 31, 30, 31, 30, 31 }; /* JUL - DEC */
-
- static short juldays[13] = { 0,
- 0, 31, 59, 90, 120, 151, /* JAN - JUN */
- 181, 212, 243, 273, 304, 334 }; /* JUL - DEC */
-
- /*
- * #defines for messages. This facilities foreign language conversion
- * since all messages are defined right here.
- */
-
- #define USAGE \
- "Usage: %s [ -l ] [ -m min_days ] [ -M max_days ] [ -W warn ]\n\
- [ -I inactive ] [ -E expire ] [ -d last_day ] user\n"
- #define DBMERROR "Error updating the DBM password entry.\n"
- #define DBMERROR2 "error updating DBM shadow entry.\n"
-
- /*
- * usage - print command line syntax and exit
- */
-
- void
- usage ()
- {
- fprintf (stderr, USAGE, Prog);
- exit (1);
- }
-
- /*
- * strtoday - compute the number of days since 1970.
- *
- * the total number of days prior to the current date is
- * computed. january 1, 1970 is used as the origin with
- * it having a day number of 0. the gmtime() routine is
- * used to prevent confusion regarding time zones.
- */
-
- long
- strtoday (str)
- char *str;
- {
- char slop[2];
- int month;
- int day;
- int year;
- long total;
-
- /*
- * start by separating the month, day and year. this is
- * a chauvanistic program - it only takes date input in
- * the standard USA format.
- */
-
- if (sscanf (str, "%d/%d/%d%c", &month, &day, &year, slop) != 3)
- return -1;
-
- /*
- * the month, day of the month, and year are checked for
- * correctness and the year adjusted so it falls between
- * 1970 and 2069.
- */
-
- if (month < 1 || month > 12)
- return -1;
-
- if (day < 1)
- return -1;
-
- if ((month != 2 || (year % 4) != 0) && day > days[month])
- return -1;
- else if ((month == 2 && (year % 4) == 0) && day > 29)
- return -1;
-
- if (year < 0)
- return -1;
- else if (year < 69)
- year += 2000;
- else if (year < 99)
- year += 1900;
-
- if (year < 1970 || year > 2069)
- return -1;
-
- /*
- * the total number of days is the total number of days in all
- * the whole years, plus the number of leap days, plus the
- * number of days in the whole months preceding, plus the number
- * of days so far in the month.
- */
-
- total = ((year - 1970) * 365) + (((year + 1) - 1970) / 4);
- total += juldays[month] + (month > 2 && (year % 4) == 0 ? 1:0);
- total += day - 1;
-
- return total;
- }
-
- /*
- * new_fields - change the user's password aging information interactively.
- *
- * prompt the user for all of the password age values. set the fields
- * from the user's response, or leave alone if nothing was entered. the
- * value (-1) is used to indicate the field should be removed if possible.
- * any other negative value is an error. very large positive values will
- * be handled elsewhere.
- */
-
- int
- new_fields ()
- {
- char buf[BUFSIZ];
- char *cp;
- long value;
- struct tm *tp;
-
- printf ("Enter the new value, or press return for the default\n\n");
-
- sprintf (buf, "%ld", mindays);
- change_field (buf, "Minimum Password Age");
- if (((mindays = strtol (buf, &cp, 10)) == 0 && *cp) || mindays < -1)
- return 0;
-
- sprintf (buf, "%ld", maxdays);
- change_field (buf, "Maximum Password Age");
- if (((maxdays = strtol (buf, &cp, 10)) == 0 && *cp) || maxdays < -1)
- return 0;
-
- value = lastday * SCALE;
- tp = gmtime (&value);
- sprintf (buf, "%02d/%02d/%02d",
- tp->tm_mon + 1, tp->tm_mday, tp->tm_year);
- change_field (buf, "Last Password Change (MM/DD/YY)");
- if (strcmp (buf, "12/31/69") == 0)
- lastday = -1;
- else if ((lastday = strtoday (buf)) == -1)
- return 0;
-
- sprintf (buf, "%ld", warndays);
- change_field (buf, "Password Expiration Warning");
- if (((warndays = strtol (buf, &cp, 10)) == 0 && *cp) || warndays < -1)
- return 0;
-
- sprintf (buf, "%ld", inactdays);
- change_field (buf, "Password Inactive");
- if (((inactdays = strtol (buf, &cp, 10)) == 0 && *cp) || inactdays < -1)
- return 0;
-
- value = expdays * SCALE;
- tp = gmtime (&value);
- sprintf (buf, "%02d/%02d/%02d",
- tp->tm_mon + 1, tp->tm_mday, tp->tm_year);
- change_field (buf, "Account Expiration Date (MM/DD/YY)");
- if (strcmp (buf, "12/31/69") == 0)
- expdays = -1;
- else if ((expdays = strtoday (buf)) == -1)
- return 0;
-
- return 1;
- }
-
- /*
- * list_fields - display the current values of the expiration fields
- *
- * display the password age information from the password fields. date
- * values will be displayed as a calendar date, or the word "Never" if
- * the date is 1/1/70, which is day number 0.
- */
-
- void
- list_fields ()
- {
- struct tm *tp;
- char *cp;
- long changed;
- long expires;
-
- /*
- * Start with the easy numbers - the number of days before the
- * password can be changed, the number of days after which the
- * password must be chaged, the number of days before the
- * password expires that the user is told, and the number of
- * days after the password expires that the account becomes
- * unusable.
- */
-
- printf ("Minimum:\t%d\n", mindays);
- printf ("Maximum:\t%d\n", maxdays);
- printf ("Warning:\t%d\n", warndays);
- printf ("Inactive:\t%d\n", inactdays);
-
- /*
- * The "last change" date is either "Never" or the date the
- * password was last modified. The date is the number of
- * days since 1/1/1970.
- */
-
- printf ("Last Change:\t\t");
- if (changed <= 0) {
- printf ("Never\n");
- } else {
- changed = lastday * SCALE;
- tp = gmtime (&changed);
- cp = asctime (tp);
- printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
- }
-
- /*
- * The password expiration date is determined from the last
- * change date plus the number of days the password is valid
- * for.
- */
-
- printf ("Password Expires:\t");
- if (changed <= 0 || maxdays >= 10000*(DAY/SCALE) || maxdays <= 0) {
- printf ("Never\n");
- } else {
- expires = changed + maxdays * SCALE;
- tp = gmtime (&expires);
- cp = asctime (tp);
- printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
- }
-
- /*
- * The account becomes inactive if the password is expired
- * for more than "inactdays". The expiration date is calculated
- * and the number of inactive days is added. The resulting date
- * is when the active will be disabled.
- */
-
- printf ("Password Inactive:\t");
- if (changed <= 0 || inactdays <= 0 ||
- maxdays >= 10000*(DAY/SCALE) || maxdays <= 0) {
- printf ("Never\n");
- } else {
- expires = changed + (maxdays + inactdays) * SCALE;
- tp = gmtime (&expires);
- cp = asctime (tp);
- printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
- }
-
- /*
- * The account will expire on the given date regardless of the
- * password expiring or not.
- */
-
- printf ("Account Expires:\t");
- if (expdays <= 0) {
- printf ("Never\n");
- } else {
- expires = expdays * SCALE;
- tp = gmtime (&expires);
- cp = asctime (tp);
- printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
- }
- }
-
- /*
- * chage - change a user's password aging information
- *
- * This command controls the password aging information.
- *
- * The valid options are
- *
- * -m minimum number of days before password change (*)
- * -M maximim number of days before password change (*)
- * -d last password change date (*)
- * -l last password change date
- * -W expiration warning days (*)
- * -I password inactive after expiration (*)
- * -E account expiration date (*)
- *
- * (*) requires root permission to execute.
- *
- * All of the time fields are entered in the internal format
- * which is either seconds or days.
- */
-
- int
- main (argc, argv)
- int argc;
- char **argv;
- {
- int flag;
- int lflg;
- int mflg;
- int Mflg;
- int dflg;
- int Wflg;
- int Iflg;
- int Eflg;
- int ruid = getuid ();
- struct passwd *pw;
- struct passwd pwent;
- struct spwd *sp;
- struct spwd spwd;
- char name[BUFSIZ];
-
- /*
- * Get the program name so that error messages can use it.
- */
-
- if (Prog = strrchr (argv[0], '/'))
- Prog++;
- else
- Prog = argv[0];
-
- openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
- #ifdef NDBM
- sp_dbm_mode = O_RDWR;
- pw_dbm_mode = O_RDWR;
- #endif
-
- /*
- * Parse the flags. The difference between password file
- * formats includes the number of fields, and whether the
- * dates are entered as days or weeks. Shadow password
- * file info =must= be entered in days, while regular
- * password file info =must= be entered in weeks.
- */
-
- while ((flag = getopt (argc, argv, "lm:M:W:I:E:d:")) != EOF) {
- switch (flag) {
- case 'l':
- lflg++;
- break;
- case 'm':
- mflg++;
- mindays = strtol (optarg, 0, 10);
- break;
- case 'M':
- Mflg++;
- maxdays = strtol (optarg, 0, 10);
- break;
- case 'd':
- dflg++;
- lastday = strtol (optarg, 0, 10);
- break;
- case 'W':
- Wflg++;
- warndays = strtol (optarg, 0, 10);
- break;
- case 'I':
- Iflg++;
- inactdays = strtol (optarg, 0, 10);
- break;
- case 'E':
- Eflg++;
- expdays = strtol (optarg, 0, 10);
- break;
- default:
- usage ();
- }
- }
-
- /*
- * Make certain the flags do not conflict and that there is
- * a user name on the command line.
- */
-
- if (argc != optind + 1)
- usage ();
-
- if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
- fprintf (stderr, "%s: do not include \"l\" with other flags\n",
- Prog);
- usage ();
- }
-
- /*
- * An unprivileged user can ask for their own aging information,
- * but only root can change it, or list another user's aging
- * information.
- */
-
- if (ruid != 0 && ! lflg) {
- fprintf (stderr, "%s: permission denied\n", Prog);
- exit (1);
- }
-
- /*
- * Lock and open the password file. This loads all of the
- * password file entries into memory. Then we get a pointer
- * to the password file entry for the requested user.
- */
-
- if (! pw_lock ()) {
- fprintf (stderr, "%s: can't lock password file\n", Prog);
- exit (1);
- }
- if (! pw_open (ruid != 0 || lflg ? O_RDONLY:O_RDWR)) {
- fprintf (stderr, "%s: can't open password file\n", Prog);
- cleanup (1);
- exit (1);
- }
- if (! (pw = pw_locate (argv[optind]))) {
- fprintf (stderr, "%s: unknown user: %s\n", Prog, argv[optind]);
- cleanup (1);
- exit (1);
- }
-
- /*
- * For shadow password files we have to lock the file and
- * read in the entries as was done for the password file.
- * The user entries does not have to exist in this case;
- * a new entry will be created for this user if one does
- * not exist already.
- */
-
- if (! spw_lock ()) {
- fprintf (stderr, "%s: can't lock shadow file\n", Prog);
- cleanup (1);
- exit (1);
- }
- if (! spw_open ((ruid != 0 || lflg) ? O_RDONLY:O_RDWR)) {
- fprintf (stderr, "%s: can't open shadow file\n", Prog);
- cleanup (2);
- exit (1);
- }
- if (sp = spw_locate (argv[optind]))
- spwd = *sp;
-
- strcpy (name, pw->pw_name);
- pwent = *pw;
-
- /*
- * Set the fields that aren't being set from the command line
- * from the password file.
- */
-
- if (sp) {
- if (! Mflg)
- maxdays = spwd.sp_max;
- if (! mflg)
- mindays = spwd.sp_min;
- if (! dflg)
- lastday = spwd.sp_lstchg;
- if (! Wflg)
- warndays = spwd.sp_warn;
- if (! Iflg)
- inactdays = spwd.sp_inact;
- if (! Eflg)
- expdays = spwd.sp_expire;
- } else
- #ifdef ATT_AGE
- {
- if (pwent.pw_age && strlen (pwent.pw_age) >= 2) {
- if (! Mflg)
- maxdays = c64i (pwent.pw_age[0]) * (WEEK/SCALE);
- if (! mflg)
- mindays = c64i (pwent.pw_age[1]) * (WEEK/SCALE);
- if (! dflg && strlen (pwent.pw_age) == 4)
- lastday = a64l (pwent.pw_age+2) * (WEEK/SCALE);
- } else {
- mindays = 0;
- maxdays = 10000L * (DAY/SCALE);
- lastday = -1;
- }
- warndays = inactdays = expdays = -1;
- }
- #endif
-
- /*
- * Print out the expiration fields if the user has
- * requested the list option.
- */
-
- if (lflg) {
- if (ruid != 0 && ruid != pw->pw_uid) {
- fprintf (stderr, "%s: permission denied\n", Prog);
- exit (1);
- }
- list_fields ();
- cleanup (2);
- exit (0);
- }
-
- /*
- * If none of the fields were changed from the command line,
- * let the user interactively change them.
- */
-
- if (! mflg && ! Mflg && ! dflg && ! Wflg && ! Iflg && ! Eflg) {
- printf ("Changing the aging information for %s\n", name);
- if (! new_fields ()) {
- fprintf (stderr, "%s: error changing fields\n", Prog);
- cleanup (2);
- exit (1);
- }
- }
-
- /*
- * There was no shadow entry. The new entry will have the
- * encrypted password transferred from the normal password
- * file along with the aging information.
- */
-
- if (sp == 0) {
- sp = &spwd;
- bzero (&spwd, sizeof spwd);
-
- sp->sp_namp = pw->pw_name;
- sp->sp_pwdp = pw->pw_passwd;
- sp->sp_flag = -1;
-
- pwent.pw_passwd = "!";
- #ifdef ATT_AGE
- pwent.pw_age = "";
- #endif
- if (! pw_update (&pwent)) {
- fprintf (stderr, "%s: can't update password file\n",
- Prog);
- cleanup (2);
- exit (1);
- }
- #if defined(DBM) || defined(NDBM)
- (void) pw_dbm_update (&pwent);
- #endif
- }
-
- /*
- * Copy the fields back to the shadow file entry and
- * write the modified entry back to the shadow file.
- * Closing the shadow and password files will commit
- * any changes that have been made.
- */
-
- sp->sp_max = maxdays;
- sp->sp_min = mindays;
- sp->sp_lstchg = lastday;
- sp->sp_warn = warndays;
- sp->sp_inact = inactdays;
- sp->sp_expire = expdays;
-
- if (! spw_update (sp)) {
- fprintf (stderr, "%s: can't update shadow file\n", Prog);
- cleanup (2);
- 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 /* NDBM */
- if (! spw_close ()) {
- fprintf (stderr, "%s: can't rewrite shadow file\n", Prog);
- cleanup (2);
- exit (1);
- }
- (void) pw_close ();
- cleanup (2);
- exit (0);
- /*NOTREACHED*/
- }
-
- /*
- * cleanup - unlock any locked password files
- */
-
- void
- cleanup (state)
- int state;
- {
- switch (state) {
- case 2:
- spw_unlock ();
- case 1:
- pw_unlock ();
- case 0:
- break;
- }
- }
-