/* * Copyright 1989 - 1994, Julianne Frances Haugh * 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. Neither the name of Julianne F. Haugh nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH 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 JULIE HAUGH 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 #include "rcsid.h" RCSID (PKG_VER "$Id: chage.c,v 1.27 2002/01/05 15:41:43 kloczek Exp $") #include #include #include #include #include #include #include "prototypes.h" #include "defines.h" #ifdef USE_PAM #include #include #endif /* USE_PAM */ #include /* * Global variables */ static char *Prog; static int locks; static long mindays; static long maxdays; static long lastday; #ifdef SHADOWPWD static long warndays; static long inactdays; static long expdays; #endif /* * External identifiers */ extern long a64l (); extern char *l64a (); #include "pwio.h" #ifdef SHADOWPWD #include "shadowio.h" #endif #ifdef NDBM extern int pw_dbm_mode; #ifdef SHADOWPWD extern int sp_dbm_mode; #endif #endif #define EPOCH "1969-12-31" /* local function prototypes */ static void usage (void); static void date_to_str (char *, size_t, time_t); static int new_fields (void); static void print_date (time_t); static void list_fields (void); static void cleanup (int); /* * isnum - determine whether or not a string is a number */ int isnum (const char *s) { while (*s) { if (!isdigit (*s)) return 0; s++; } return 1; } /* * usage - print command line syntax and exit */ static void usage (void) { #ifdef SHADOWPWD fprintf (stderr, _("Usage: %s [-l] [-m min_days] [-M max_days] [-W warn]\n" "\t[-I inactive] [-E expire] [-d last_day] user\n"), Prog); #else fprintf (stderr, _ ("Usage: %s [-l] [-m min_days] [-M max_days] [-d last_day] user\n"), Prog); #endif exit (1); } static void date_to_str (char *buf, size_t maxsize, time_t date) { struct tm *tp; tp = gmtime (&date); #ifdef HAVE_STRFTIME strftime (buf, maxsize, "%Y-%m-%d", tp); #else snprintf (buf, maxsize, "%04d-%02d-%02d", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday); #endif /* HAVE_STRFTIME */ } /* * 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. */ static int new_fields (void) { char buf[200]; char *cp; printf (_ ("Enter the new value, or press ENTER for the default\n\n")); snprintf (buf, sizeof buf, "%ld", mindays); change_field (buf, sizeof buf, _("Minimum Password Age")); if (((mindays = strtol (buf, &cp, 10)) == 0 && *cp) || mindays < -1) return 0; snprintf (buf, sizeof buf, "%ld", maxdays); change_field (buf, sizeof buf, _("Maximum Password Age")); if (((maxdays = strtol (buf, &cp, 10)) == 0 && *cp) || maxdays < -1) return 0; date_to_str (buf, sizeof buf, lastday * SCALE); change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)")); if (strcmp (buf, EPOCH) == 0) lastday = -1; else if ((lastday = strtoday (buf)) == -1) return 0; #ifdef SHADOWPWD snprintf (buf, sizeof buf, "%ld", warndays); change_field (buf, sizeof buf, _("Password Expiration Warning")); if (((warndays = strtol (buf, &cp, 10)) == 0 && *cp) || warndays < -1) return 0; snprintf (buf, sizeof buf, "%ld", inactdays); change_field (buf, sizeof buf, _("Password Inactive")); if (((inactdays = strtol (buf, &cp, 10)) == 0 && *cp) || inactdays < -1) return 0; date_to_str (buf, sizeof buf, expdays * SCALE); change_field (buf, sizeof buf, _("Account Expiration Date (YYYY-MM-DD)")); if (strcmp (buf, EPOCH) == 0) expdays = -1; else if ((expdays = strtoday (buf)) == -1) return 0; #endif /* SHADOWPWD */ return 1; } static void print_date (time_t date) { #ifdef HAVE_STRFTIME struct tm *tp; char buf[80]; tp = gmtime (&date); strftime (buf, sizeof buf, "%b %d, %Y", tp); puts (buf); #else struct tm *tp; char *cp; tp = gmtime (&date); cp = asctime (tp); printf ("%6.6s, %4.4s\n", cp + 4, cp + 20); #endif } /* * 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. */ static void list_fields (void) { long changed = 0; 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%ld\n"), mindays); printf (_("Maximum:\t%ld\n"), maxdays); #ifdef SHADOWPWD printf (_("Warning:\t%ld\n"), warndays); printf (_("Inactive:\t%ld\n"), inactdays); #endif /* * 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 (lastday <= 0) { printf (_("Never\n")); } else { changed = lastday * SCALE; print_date (changed); } /* * 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 (lastday <= 0 || maxdays >= 10000 * (DAY / SCALE) || maxdays <= 0) { printf (_("Never\n")); } else { expires = changed + maxdays * SCALE; print_date (expires); } #ifdef SHADOWPWD /* * 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 (lastday <= 0 || inactdays <= 0 || maxdays >= 10000 * (DAY / SCALE) || maxdays <= 0) { printf (_("Never\n")); } else { expires = changed + (maxdays + inactdays) * SCALE; print_date (expires); } /* * 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; print_date (expires); } #endif } #ifdef USE_PAM static struct pam_conv conv = { misc_conv, NULL }; #endif /* USE_PAM */ /* * 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 password aging information * -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. * * The options -W, -I and -E all depend on the SHADOWPWD macro being * defined. */ int main (int argc, char **argv) { int flag; int lflg = 0; int mflg = 0; int Mflg = 0; int dflg = 0; #ifdef SHADOWPWD int Wflg = 0; int Iflg = 0; int Eflg = 0; const struct spwd *sp; struct spwd spwd; #else char new_age[5]; #endif uid_t ruid; int amroot, pwrw; const struct passwd *pw; struct passwd pwent; char name[BUFSIZ]; #ifdef USE_PAM pam_handle_t *pamh = NULL; struct passwd *pampw; int retval; #endif sanitize_env (); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); ruid = getuid (); amroot = (ruid == 0); /* * Get the program name so that error messages can use it. */ Prog = Basename (argv[0]); OPENLOG ("chage"); #ifdef NDBM #ifdef SHADOWPWD sp_dbm_mode = O_RDWR; #endif 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. */ #ifdef SHADOWPWD #define FLAGS "lm:M:W:I:E:d:" #else #define FLAGS "lm:M:d:" #endif while ((flag = getopt (argc, argv, FLAGS)) != EOF) { #undef FLAGS 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++; if (!isnum (optarg)) lastday = strtoday (optarg); else lastday = strtol (optarg, 0, 10); break; #ifdef SHADOWPWD case 'W': Wflg++; warndays = strtol (optarg, 0, 10); break; case 'I': Iflg++; inactdays = strtol (optarg, 0, 10); break; case 'E': Eflg++; if (!isnum (optarg)) expdays = strtoday (optarg); else expdays = strtol (optarg, 0, 10); break; #endif 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 (); #ifdef SHADOWPWD if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) #else if (lflg && (mflg || Mflg || dflg)) #endif { fprintf (stderr, _("%s: do not include \"l\" with other flags\n"), Prog); closelog (); 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 (!amroot && !lflg) { fprintf (stderr, _("%s: permission denied\n"), Prog); closelog (); exit (1); } #ifdef USE_PAM retval = PAM_SUCCESS; pampw = getpwuid (getuid ()); if (pampw == NULL) { retval = PAM_USER_UNKNOWN; } if (retval == PAM_SUCCESS) { retval = pam_start ("chage", pampw->pw_name, &conv, &pamh); } if (retval == PAM_SUCCESS) { retval = pam_authenticate (pamh, 0); if (retval != PAM_SUCCESS) { pam_end (pamh, retval); } } if (retval == PAM_SUCCESS) { retval = pam_acct_mgmt (pamh, 0); if (retval != PAM_SUCCESS) { pam_end (pamh, retval); } } if (retval != PAM_SUCCESS) { fprintf (stderr, _("%s: PAM authentication failed\n"), Prog); exit (1); } OPENLOG ("chage"); #endif /* USE_PAM */ /* * We use locks for read-write accesses only (locks implies amroot, * but amroot doesn't imply locks). */ locks = !lflg; /* * 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. */ #ifndef SHADOWPWD if (locks && !pw_lock ()) { fprintf (stderr, _("%s: can't lock password file\n"), Prog); SYSLOG ((LOG_ERR, "failed locking %s", PASSWD_FILE)); closelog (); exit (1); } pwrw = locks; #else pwrw = 0; #endif if (!pw_open (pwrw ? O_RDWR : O_RDONLY)) { fprintf (stderr, _("%s: can't open password file\n"), Prog); cleanup (1); SYSLOG ((LOG_ERR, "failed opening %s", PASSWD_FILE)); closelog (); exit (1); } if (!(pw = pw_locate (argv[optind]))) { fprintf (stderr, _("%s: unknown user: %s\n"), Prog, argv[optind]); cleanup (1); closelog (); exit (1); } pwent = *pw; STRFCPY (name, pwent.pw_name); #ifdef SHADOWPWD /* * 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 (locks && !spw_lock ()) { fprintf (stderr, _("%s: can't lock shadow password file"), Prog); cleanup (1); SYSLOG ((LOG_ERR, "failed locking %s", SHADOW_FILE)); closelog (); exit (1); } if (!spw_open (locks ? O_RDWR : O_RDONLY)) { fprintf (stderr, _("%s: can't open shadow password file"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed opening %s", SHADOW_FILE)); closelog (); exit (1); } if (lflg && (setgid (getgid ()) || setuid (ruid))) { fprintf (stderr, "%s: failed to drop privileges (%s)\n", Prog, strerror (errno)); exit (1); } sp = spw_locate (argv[optind]); /* * Set the fields that aren't being set from the command line from * the password file. */ if (sp) { spwd = *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; } #ifdef ATT_AGE else #endif /* ATT_AGE */ #endif /* SHADOWPWD */ #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; } #ifdef SHADOWPWD warndays = inactdays = expdays = -1; #endif /* SHADOWPWD */ } #endif /* ATT_AGE */ /* * Print out the expiration fields if the user has requested the * list option. */ if (lflg) { if (!amroot && (ruid != pwent.pw_uid)) { fprintf (stderr, _("%s: permission denied\n"), Prog); closelog (); exit (1); } list_fields (); cleanup (2); closelog (); exit (0); } /* * If none of the fields were changed from the command line, let the * user interactively change them. */ #ifdef SHADOWPWD if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) #else if (!mflg && !Mflg && !dflg) #endif { printf (_("Changing the aging information for %s\n"), name); if (!new_fields ()) { fprintf (stderr, _("%s: error changing fields\n"), Prog); cleanup (2); closelog (); exit (1); } } #ifdef SHADOWPWD /* * 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; memzero (&spwd, sizeof spwd); spwd.sp_namp = xstrdup (pwent.pw_name); spwd.sp_pwdp = xstrdup (pwent.pw_passwd); spwd.sp_flag = -1; pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */ #ifdef ATT_AGE pwent.pw_age = ""; #endif if (!pw_update (&pwent)) { fprintf (stderr, _("%s: can't update password file\n"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed updating %s", PASSWD_FILE)); closelog (); exit (1); } #ifdef NDBM (void) pw_dbm_update (&pwent); endpwent (); #endif } #endif /* SHADOWPWD */ #ifdef SHADOWPWD /* * 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. */ spwd.sp_max = maxdays; spwd.sp_min = mindays; spwd.sp_lstchg = lastday; spwd.sp_warn = warndays; spwd.sp_inact = inactdays; spwd.sp_expire = expdays; if (!spw_update (&spwd)) { fprintf (stderr, _("%s: can't update shadow password file\n"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed updating %s", SHADOW_FILE)); closelog (); exit (1); } #else /* !SHADOWPWD */ /* * fill in the new_age string with the new values */ if (maxdays > (63 * 7) && mindays == 0) { new_age[0] = '\0'; } else { if (maxdays > (63 * 7)) maxdays = 63 * 7; if (mindays > (63 * 7)) mindays = 63 * 7; new_age[0] = i64c (maxdays / 7); new_age[1] = i64c ((mindays + 6) / 7); if (lastday == 0) new_age[2] = '\0'; else strcpy (new_age + 2, l64a (lastday / 7)); } pwent.pw_age = new_age; if (!pw_update (&pwent)) { fprintf (stderr, _("%s: can't update password file\n"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed updating %s", PASSWD_FILE)); closelog (); exit (1); } #endif /* SHADOWPWD */ #ifdef NDBM #ifdef SHADOWPWD /* * See if the shadow DBM file exists and try to update it. */ if (sp_dbm_present () && !sp_dbm_update (&spwd)) { fprintf (stderr, _("Error updating the DBM password entry.\n")); cleanup (2); SYSLOG ((LOG_ERR, "error updating DBM passwd entry")); closelog (); exit (1); } endspent (); #else /* !SHADOWPWD */ /* * See if the password DBM file exists and try to update it. */ if (pw_dbm_present () && !pw_dbm_update (&pwent)) { fprintf (stderr, _("Error updating the DBM password entry.\n")); cleanup (2); SYSLOG ((LOG_ERR, "error updating DBM passwd entry")); closelog (); exit (1); } endpwent (); #endif /* SHADOWPWD */ #endif /* NDBM */ #ifdef SHADOWPWD /* * Now close the shadow password file, which will cause all of the * entries to be re-written. */ if (!spw_close ()) { fprintf (stderr, _("%s: can't rewrite shadow password file\n"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed rewriting %s", SHADOW_FILE)); closelog (); exit (1); } #endif /* SHADOWPWD */ /* * Close the password file. If any entries were modified, the file * will be re-written. */ if (!pw_close ()) { fprintf (stderr, _("%s: can't rewrite password file\n"), Prog); cleanup (2); SYSLOG ((LOG_ERR, "failed rewriting %s", PASSWD_FILE)); closelog (); exit (1); } cleanup (2); SYSLOG ((LOG_INFO, "changed password expiry for %s", name)); #ifdef USE_PAM if (!lflg) { if (retval == PAM_SUCCESS) { retval = pam_chauthtok (pamh, 0); if (retval != PAM_SUCCESS) { pam_end (pamh, retval); } } if (retval != PAM_SUCCESS) { fprintf (stderr, _("%s: PAM chauthtok failed\n"), Prog); exit (1); } } if (retval == PAM_SUCCESS) pam_end (pamh, PAM_SUCCESS); #endif /* USE_PAM */ closelog (); exit (0); /*NOTREACHED*/} /* * cleanup - unlock any locked password files */ static void cleanup (int state) { switch (state) { case 2: #ifdef SHADOWPWD if (locks) spw_unlock (); #endif case 1: #ifndef SHADOWPWD if (locks) pw_unlock (); #endif case 0: break; } }