2 * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3 * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4 * Copyright (c) 2000 - 2006, Tomasz Kłoczko
5 * Copyright (c) 2007 - 2008, Nicolas François
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the copyright holders or contributors may not be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 #include <sys/types.h>
45 #ifdef ACCT_TOOLS_SETUID
49 #endif /* ACCT_TOOLS_SETUID */
52 #include <selinux/selinux.h>
53 #include <selinux/av_permissions.h>
55 #include "exitcodes.h"
56 #include "prototypes.h"
66 dflg = false, /* set last password change date */
67 Eflg = false, /* set account expiration date */
68 Iflg = false, /* set password inactive after expiration */
69 lflg = false, /* show account aging information */
70 mflg = false, /* set minimum number of days before password change */
71 Mflg = false, /* set maximum number of days before password change */
72 Wflg = false; /* set expiration warning days */
73 static bool amroot = false;
75 static bool pw_locked = false; /* Indicate if the password file is locked */
76 static bool spw_locked = false; /* Indicate if the shadow file is locked */
77 /* The name and UID of the user being worked on */
78 static char user_name[BUFSIZ] = "";
79 static uid_t user_uid = -1;
85 static long inactdays;
88 #define EPOCH "1969-12-31"
90 /* local function prototypes */
91 static bool isnum (const char *s);
92 static void usage (void);
93 static void date_to_str (char *buf, size_t maxsize, time_t date);
94 static int new_fields (void);
95 static void print_date (time_t date);
96 static void list_fields (void);
97 static void process_flags (int argc, char **argv);
98 static void check_flags (int argc, int opt_index);
99 static void check_perms (void);
100 static void open_files (bool readonly);
101 static void close_files (void);
102 static void fail_exit (int code);
105 * fail_exit - do some cleanup and exit with the given error code
107 static void fail_exit (int code)
110 if (spw_unlock () == 0) {
111 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
112 SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
117 if (pw_unlock () == 0) {
118 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
119 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
126 if (E_SUCCESS != code) {
127 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
129 user_name, (unsigned int) user_uid, 0);
137 * isnum - determine whether or not a string is a number
139 static bool isnum (const char *s)
151 * usage - print command line syntax and exit
153 static void usage (void)
155 fputs (_("Usage: chage [options] [LOGIN]\n"
158 " -d, --lastday LAST_DAY set last password change to LAST_DAY\n"
159 " -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE\n"
160 " -h, --help display this help message and exit\n"
161 " -I, --inactive INACTIVE set password inactive after expiration\n"
163 " -l, --list show account aging information\n"
164 " -m, --mindays MIN_DAYS set minimum number of days before password\n"
165 " change to MIN_DAYS\n"
166 " -M, --maxdays MAX_DAYS set maximim number of days before password\n"
167 " change to MAX_DAYS\n"
168 " -W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"
173 static void date_to_str (char *buf, size_t maxsize, time_t date)
179 (void) strftime (buf, maxsize, "%Y-%m-%d", tp);
181 (void) snprintf (buf, maxsize, "%04d-%02d-%02d",
182 tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
183 #endif /* HAVE_STRFTIME */
187 * new_fields - change the user's password aging information interactively.
189 * prompt the user for all of the password age values. set the fields
190 * from the user's response, or leave alone if nothing was entered. The
191 * value (-1) is used to indicate the field should be removed if possible.
192 * any other negative value is an error. very large positive values will
193 * be handled elsewhere.
195 static int new_fields (void)
200 (void) puts (_("Enter the new value, or press ENTER for the default"));
203 snprintf (buf, sizeof buf, "%ld", mindays);
204 change_field (buf, sizeof buf, _("Minimum Password Age"));
205 mindays = strtol (buf, &cp, 10);
206 if ( ((0 == mindays) && ('\0' != *cp))
211 snprintf (buf, sizeof buf, "%ld", maxdays);
212 change_field (buf, sizeof buf, _("Maximum Password Age"));
213 maxdays = strtol (buf, &cp, 10);
214 if ( ((0 == maxdays) && ('\0' != *cp))
219 date_to_str (buf, sizeof buf, lastday * SCALE);
221 change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)"));
223 if (strcmp (buf, EPOCH) == 0) {
226 lastday = strtoday (buf);
232 snprintf (buf, sizeof buf, "%ld", warndays);
233 change_field (buf, sizeof buf, _("Password Expiration Warning"));
234 warndays = strtol (buf, &cp, 10);
235 if ( ((warndays == 0) && ('\0' != *cp))
236 || (warndays < -1)) {
240 snprintf (buf, sizeof buf, "%ld", inactdays);
241 change_field (buf, sizeof buf, _("Password Inactive"));
242 inactdays = strtol (buf, &cp, 10);
243 if ( ((inactdays == 0) && ('\0' != *cp))
244 || (inactdays < -1)) {
248 date_to_str (buf, sizeof buf, expdays * SCALE);
250 change_field (buf, sizeof buf,
251 _("Account Expiration Date (YYYY-MM-DD)"));
253 if (strcmp (buf, EPOCH) == 0) {
256 expdays = strtoday (buf);
265 static void print_date (time_t date)
273 (void) printf ("time_t: %lu\n", date);
275 (void) strftime (buf, sizeof buf, "%b %d, %Y", tp);
287 (void) printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
289 (void) printf ("time_t: %lu\n", date);
295 * list_fields - display the current values of the expiration fields
297 * display the password age information from the password fields. Date
298 * values will be displayed as a calendar date, or the word "never" if
299 * the date is 1/1/70, which is day number 0.
301 static void list_fields (void)
307 * The "last change" date is either "never" or the date the password
308 * was last modified. The date is the number of days since 1/1/1970.
310 (void) fputs (_("Last password change\t\t\t\t\t: "), stdout);
312 (void) puts (_("never"));
313 } else if (lastday == 0) {
314 (void) puts (_("password must be changed"));
316 changed = lastday * SCALE;
317 print_date ((time_t) changed);
321 * The password expiration date is determined from the last change
322 * date plus the number of days the password is valid for.
324 (void) fputs (_("Password expires\t\t\t\t\t: "), stdout);
326 (void) puts (_("password must be changed"));
327 } else if ( (lastday < 0)
328 || (maxdays >= (10000 * (DAY / SCALE)))
330 (void) puts (_("never"));
332 expires = changed + maxdays * SCALE;
333 print_date ((time_t) expires);
337 * The account becomes inactive if the password is expired for more
338 * than "inactdays". The expiration date is calculated and the
339 * number of inactive days is added. The resulting date is when the
340 * active will be disabled.
342 (void) fputs (_("Password inactive\t\t\t\t\t: "), stdout);
344 (void) puts (_("password must be changed"));
345 } else if ( (lastday < 0)
347 || (maxdays >= (10000 * (DAY / SCALE)))
349 (void) puts (_("never"));
351 expires = changed + (maxdays + inactdays) * SCALE;
352 print_date ((time_t) expires);
356 * The account will expire on the given date regardless of the
357 * password expiring or not.
359 (void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout);
361 (void) puts (_("never"));
363 expires = expdays * SCALE;
364 print_date ((time_t) expires);
368 * Start with the easy numbers - the number of days before the
369 * password can be changed, the number of days after which the
370 * password must be chaged, the number of days before the password
371 * expires that the user is told, and the number of days after the
372 * password expires that the account becomes unusable.
374 printf (_("Minimum number of days between password change\t\t: %ld\n"),
376 printf (_("Maximum number of days between password change\t\t: %ld\n"),
378 printf (_("Number of days of warning before password expires\t: %ld\n"),
383 * process_flags - parse the command line options
385 * It will not return if an error is encountered.
387 static void process_flags (int argc, char **argv)
390 * Parse the command line options.
392 int option_index = 0;
394 static struct option long_options[] = {
395 {"lastday", required_argument, NULL, 'd'},
396 {"expiredate", required_argument, NULL, 'E'},
397 {"help", no_argument, NULL, 'h'},
398 {"inactive", required_argument, NULL, 'I'},
399 {"list", no_argument, NULL, 'l'},
400 {"mindays", required_argument, NULL, 'm'},
401 {"maxdays", required_argument, NULL, 'M'},
402 {"warndays", required_argument, NULL, 'W'},
403 {NULL, 0, NULL, '\0'}
407 getopt_long (argc, argv, "d:E:hI:lm:M:W:", long_options,
408 &option_index)) != -1) {
412 if (!isnum (optarg)) {
413 lastday = strtoday (optarg);
415 lastday = strtol (optarg, 0, 10);
420 if (!isnum (optarg)) {
421 expdays = strtoday (optarg);
423 expdays = strtol (optarg, 0, 10);
431 inactdays = strtol (optarg, 0, 10);
438 mindays = strtol (optarg, 0, 10);
442 maxdays = strtol (optarg, 0, 10);
446 warndays = strtol (optarg, 0, 10);
453 check_flags (argc, optind);
457 * check_flags - check flags and parameters consistency
459 * It will not return if an error is encountered.
461 static void check_flags (int argc, int opt_index)
464 * Make certain the flags do not conflict and that there is a user
465 * name on the command line.
468 if (argc != opt_index + 1) {
472 if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
474 _("%s: do not include \"l\" with other flags\n"),
481 * check_perms - check if the caller is allowed to add a group
483 * Non-root users are only allowed to display their aging information.
484 * (we will later make sure that the user is only listing her aging
487 * With PAM support, the setuid bit can be set on chage to allow
488 * non-root users to groups.
489 * Without PAM support, only users who can write in the group databases
492 * It will not return if the user is not allowed.
494 static void check_perms (void)
496 #ifdef ACCT_TOOLS_SETUID
498 pam_handle_t *pamh = NULL;
499 struct passwd *pampw;
502 #endif /* ACCT_TOOLS_SETUID */
505 * An unprivileged user can ask for their own aging information, but
506 * only root can change it, or list another user's aging
510 if (!amroot && !lflg) {
511 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
512 fail_exit (E_NOPERM);
515 #ifdef ACCT_TOOLS_SETUID
517 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
520 _("%s: Cannot determine your user name.\n"),
525 retval = pam_start ("chage", pampw->pw_name, &conv, &pamh);
527 if (PAM_SUCCESS == retval) {
528 retval = pam_authenticate (pamh, 0);
531 if (PAM_SUCCESS == retval) {
532 retval = pam_acct_mgmt (pamh, 0);
536 (void) pam_end (pamh, retval);
538 if (PAM_SUCCESS != retval) {
539 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
540 fail_exit (E_NOPERM);
543 #endif /* ACCT_TOOLS_SETUID */
547 * open_files - open the shadow database
549 * In read-only mode, the databases are not locked and are opened
552 static void open_files (bool readonly)
555 * Lock and open the password file. This loads all of the password
556 * file entries into memory. Then we get a pointer to the password
557 * file entry for the requested user.
560 if (pw_lock () == 0) {
562 _("%s: cannot lock %s; try again later.\n"),
564 fail_exit (E_NOPERM);
568 if (pw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
569 fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
570 SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
571 fail_exit (E_NOPERM);
575 * For shadow password files we have to lock the file and read in
576 * the entries as was done for the password file. The user entries
577 * does not have to exist in this case; a new entry will be created
578 * for this user if one does not exist already.
581 if (spw_lock () == 0) {
583 _("%s: cannot lock %s; try again later.\n"),
584 Prog, spw_dbname ());
585 fail_exit (E_NOPERM);
589 if (spw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
591 _("%s: cannot open %s\n"), Prog, spw_dbname ());
592 SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
593 fail_exit (E_NOPERM);
598 * close_files - close and unlock the password/shadow databases
600 static void close_files (void)
603 * Now close the shadow password file, which will cause all of the
604 * entries to be re-written.
606 if (spw_close () == 0) {
608 _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
609 SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
610 fail_exit (E_NOPERM);
614 * Close the password file. If any entries were modified, the file
615 * will be re-written.
617 if (pw_close () == 0) {
618 fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
619 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
620 fail_exit (E_NOPERM);
622 if (spw_unlock () == 0) {
623 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
624 SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
628 if (pw_unlock () == 0) {
629 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
630 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
637 * update_age - update the aging information in the database
639 * It will not return in case of error
641 static void update_age (const struct spwd *sp, const struct passwd *pw)
646 * There was no shadow entry. The new entry will have the encrypted
647 * password transferred from the normal password file along with the
651 struct passwd pwent = *pw;
653 memzero (&spwent, sizeof spwent);
654 spwent.sp_namp = xstrdup (pw->pw_name);
655 spwent.sp_pwdp = xstrdup (pw->pw_passwd);
656 spwent.sp_flag = SHADOW_SP_FLAG_UNSET;
658 pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
659 if (pw_update (&pwent) == 0) {
661 _("%s: failed to prepare the new %s entry '%s'\n"), Prog, pw_dbname (), pwent.pw_name);
662 fail_exit (E_NOPERM);
665 spwent.sp_namp = xstrdup (sp->sp_namp);
666 spwent.sp_pwdp = xstrdup (sp->sp_pwdp);
667 spwent.sp_flag = sp->sp_flag;
671 * Copy the fields back to the shadow file entry and write the
672 * modified entry back to the shadow file. Closing the shadow and
673 * password files will commit any changes that have been made.
675 spwent.sp_max = maxdays;
676 spwent.sp_min = mindays;
677 spwent.sp_lstchg = lastday;
678 spwent.sp_warn = warndays;
679 spwent.sp_inact = inactdays;
680 spwent.sp_expire = expdays;
682 if (spw_update (&spwent) == 0) {
684 _("%s: failed to prepare the new %s entry '%s'\n"), Prog, spw_dbname (), spwent.sp_namp);
685 fail_exit (E_NOPERM);
691 * get_defaults - get the value of the fields not set from the command line
693 static void get_defaults (const struct spwd *sp)
696 * Set the fields that aren't being set from the command line from
701 maxdays = sp->sp_max;
704 mindays = sp->sp_min;
707 lastday = sp->sp_lstchg;
710 warndays = sp->sp_warn;
713 inactdays = sp->sp_inact;
716 expdays = sp->sp_expire;
720 * Use default values that will not change the behavior of the
745 * chage - change a user's password aging information
747 * This command controls the password aging information.
749 * The valid options are
751 * -d set last password change date (*)
752 * -E set account expiration date (*)
753 * -I set password inactive after expiration (*)
754 * -l show account aging information
755 * -M set maximim number of days before password change (*)
756 * -m set minimum number of days before password change (*)
757 * -W set expiration warning days (*)
759 * (*) requires root permission to execute.
761 * All of the time fields are entered in the internal format which is
762 * either seconds or days.
765 int main (int argc, char **argv)
767 const struct spwd *sp;
770 const struct passwd *pw;
776 (void) setlocale (LC_ALL, "");
777 (void) bindtextdomain (PACKAGE, LOCALEDIR);
778 (void) textdomain (PACKAGE);
782 amroot = (ruid == 0);
784 if (amroot && (is_selinux_enabled () > 0)) {
785 amroot = (selinux_check_passwd_access (PASSWD__ROOTOK) == 0);
790 * Get the program name so that error messages can use it.
792 Prog = Basename (argv[0]);
794 process_flags (argc, argv);
800 if (!spw_file_present ()) {
802 _("%s: the shadow password file is not present\n"),
804 SYSLOG ((LOG_WARN, "can't find the shadow password file"));
806 exit (E_SHADOW_NOTFOUND);
810 /* Drop privileges */
811 if (lflg && ( (setregid (rgid, rgid) != 0)
812 || (setreuid (ruid, ruid) != 0))) {
813 fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
814 Prog, strerror (errno));
815 fail_exit (E_NOPERM);
818 pw = pw_locate (argv[optind]);
820 fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
821 Prog, argv[optind], pw_dbname ());
826 STRFCPY (user_name, pw->pw_name);
827 user_uid = pw->pw_uid;
829 sp = spw_locate (argv[optind]);
833 * Print out the expiration fields if the user has requested the
837 if (!amroot && (ruid != user_uid)) {
838 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
839 fail_exit (E_NOPERM);
842 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
843 "display aging info",
844 user_name, (unsigned int) user_uid, 1);
847 fail_exit (E_SUCCESS);
851 * If none of the fields were changed from the command line, let the
852 * user interactively change them.
854 if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) {
855 printf (_("Changing the aging information for %s\n"),
857 if (new_fields () == 0) {
858 fprintf (stderr, _("%s: error changing fields\n"),
860 fail_exit (E_NOPERM);
864 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
865 "change all aging information",
866 user_name, (unsigned int) user_uid, 1);
872 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
874 user_name, (unsigned int) user_uid, 1);
877 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
879 user_name, (unsigned int) user_uid, 1);
882 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
883 "change last change date",
884 user_name, (unsigned int) user_uid, 1);
887 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
888 "change passwd warning",
889 user_name, (unsigned int) user_uid, 1);
892 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
893 "change inactive days",
894 user_name, (unsigned int) user_uid, 1);
897 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
898 "change passwd expiration",
899 user_name, (unsigned int) user_uid, 1);
908 SYSLOG ((LOG_INFO, "changed password expiry for %s", user_name));