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 - 2009, 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.
35 #ident "$Id: chage.c 2851 2009-04-30 21:39:38Z nekral-guest $"
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 "prototypes.h"
60 #include "exitcodes.h"
68 dflg = false, /* set last password change date */
69 Eflg = false, /* set account expiration date */
70 Iflg = false, /* set password inactive after expiration */
71 lflg = false, /* show account aging information */
72 mflg = false, /* set minimum number of days before password change */
73 Mflg = false, /* set maximum number of days before password change */
74 Wflg = false; /* set expiration warning days */
75 static bool amroot = false;
77 static bool pw_locked = false; /* Indicate if the password file is locked */
78 static bool spw_locked = false; /* Indicate if the shadow file is locked */
79 /* The name and UID of the user being worked on */
80 static char user_name[BUFSIZ] = "";
81 static uid_t user_uid = -1;
85 static long lstchgdate;
87 static long inactdays;
90 #define EPOCH "1969-12-31"
92 /* local function prototypes */
93 static bool isnum (const char *s);
94 static void usage (void);
95 static void date_to_str (char *buf, size_t maxsize, time_t date);
96 static int new_fields (void);
97 static void print_date (time_t date);
98 static void list_fields (void);
99 static void process_flags (int argc, char **argv);
100 static void check_flags (int argc, int opt_index);
101 static void check_perms (void);
102 static void open_files (bool readonly);
103 static void close_files (void);
104 static void fail_exit (int code);
107 * fail_exit - do some cleanup and exit with the given error code
109 static void fail_exit (int code)
112 if (spw_unlock () == 0) {
113 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
114 SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
119 if (pw_unlock () == 0) {
120 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
121 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
128 if (E_SUCCESS != code) {
129 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
131 user_name, (unsigned int) user_uid, 0);
139 * isnum - determine whether or not a string is a number
141 static bool isnum (const char *s)
153 * usage - print command line syntax and exit
155 static void usage (void)
157 fputs (_("Usage: chage [options] [LOGIN]\n"
160 " -d, --lastday LAST_DAY set date of last password change to LAST_DAY\n"
161 " -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE\n"
162 " -h, --help display this help message and exit\n"
163 " -I, --inactive INACTIVE set password inactive after expiration\n"
165 " -l, --list show account aging information\n"
166 " -m, --mindays MIN_DAYS set minimum number of days before password\n"
167 " change to MIN_DAYS\n"
168 " -M, --maxdays MAX_DAYS set maximim number of days before password\n"
169 " change to MAX_DAYS\n"
170 " -W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"
175 static void date_to_str (char *buf, size_t maxsize, time_t date)
181 (void) strftime (buf, maxsize, "%Y-%m-%d", tp);
183 (void) snprintf (buf, maxsize, "%04d-%02d-%02d",
184 tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
185 #endif /* HAVE_STRFTIME */
189 * new_fields - change the user's password aging information interactively.
191 * prompt the user for all of the password age values. set the fields
192 * from the user's response, or leave alone if nothing was entered. The
193 * value (-1) is used to indicate the field should be removed if possible.
194 * any other negative value is an error. very large positive values will
195 * be handled elsewhere.
197 static int new_fields (void)
201 (void) puts (_("Enter the new value, or press ENTER for the default"));
204 snprintf (buf, sizeof buf, "%ld", mindays);
205 change_field (buf, sizeof buf, _("Minimum Password Age"));
206 if ( (getlong (buf, &mindays) == 0)
211 snprintf (buf, sizeof buf, "%ld", maxdays);
212 change_field (buf, sizeof buf, _("Maximum Password Age"));
213 if ( (getlong (buf, &maxdays) == 0)
218 date_to_str (buf, sizeof buf, lstchgdate * SCALE);
220 change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)"));
222 if (strcmp (buf, EPOCH) == 0) {
225 lstchgdate = strtoday (buf);
226 if (lstchgdate == -1) {
231 snprintf (buf, sizeof buf, "%ld", warndays);
232 change_field (buf, sizeof buf, _("Password Expiration Warning"));
233 if ( (getlong (buf, &warndays) == 0)
234 || (warndays < -1)) {
238 snprintf (buf, sizeof buf, "%ld", inactdays);
239 change_field (buf, sizeof buf, _("Password Inactive"));
240 if ( (getlong (buf, &inactdays) == 0)
241 || (inactdays < -1)) {
245 date_to_str (buf, sizeof buf, expdate * SCALE);
247 change_field (buf, sizeof buf,
248 _("Account Expiration Date (YYYY-MM-DD)"));
250 if (strcmp (buf, EPOCH) == 0) {
253 expdate = strtoday (buf);
262 static void print_date (time_t date)
270 (void) printf ("time_t: %lu\n", date);
272 (void) strftime (buf, sizeof buf, "%b %d, %Y", tp);
284 (void) printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
286 (void) printf ("time_t: %lu\n", date);
292 * list_fields - display the current values of the expiration fields
294 * display the password age information from the password fields. Date
295 * values will be displayed as a calendar date, or the word "never" if
296 * the date is 1/1/70, which is day number 0.
298 static void list_fields (void)
304 * The "last change" date is either "never" or the date the password
305 * was last modified. The date is the number of days since 1/1/1970.
307 (void) fputs (_("Last password change\t\t\t\t\t: "), stdout);
308 if (lstchgdate < 0) {
309 (void) puts (_("never"));
310 } else if (lstchgdate == 0) {
311 (void) puts (_("password must be changed"));
313 changed = lstchgdate * SCALE;
314 print_date ((time_t) changed);
318 * The password expiration date is determined from the last change
319 * date plus the number of days the password is valid for.
321 (void) fputs (_("Password expires\t\t\t\t\t: "), stdout);
322 if (lstchgdate == 0) {
323 (void) puts (_("password must be changed"));
324 } else if ( (lstchgdate < 0)
325 || (maxdays >= (10000 * (DAY / SCALE)))
327 (void) puts (_("never"));
329 expires = changed + maxdays * SCALE;
330 print_date ((time_t) expires);
334 * The account becomes inactive if the password is expired for more
335 * than "inactdays". The expiration date is calculated and the
336 * number of inactive days is added. The resulting date is when the
337 * active will be disabled.
339 (void) fputs (_("Password inactive\t\t\t\t\t: "), stdout);
340 if (lstchgdate == 0) {
341 (void) puts (_("password must be changed"));
342 } else if ( (lstchgdate < 0)
344 || (maxdays >= (10000 * (DAY / SCALE)))
346 (void) puts (_("never"));
348 expires = changed + (maxdays + inactdays) * SCALE;
349 print_date ((time_t) expires);
353 * The account will expire on the given date regardless of the
354 * password expiring or not.
356 (void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout);
358 (void) puts (_("never"));
360 expires = expdate * SCALE;
361 print_date ((time_t) expires);
365 * Start with the easy numbers - the number of days before the
366 * password can be changed, the number of days after which the
367 * password must be chaged, the number of days before the password
368 * expires that the user is told, and the number of days after the
369 * password expires that the account becomes unusable.
371 printf (_("Minimum number of days between password change\t\t: %ld\n"),
373 printf (_("Maximum number of days between password change\t\t: %ld\n"),
375 printf (_("Number of days of warning before password expires\t: %ld\n"),
380 * process_flags - parse the command line options
382 * It will not return if an error is encountered.
384 static void process_flags (int argc, char **argv)
387 * Parse the command line options.
389 int option_index = 0;
391 static struct option long_options[] = {
392 {"lastday", required_argument, NULL, 'd'},
393 {"expiredate", required_argument, NULL, 'E'},
394 {"help", no_argument, NULL, 'h'},
395 {"inactive", required_argument, NULL, 'I'},
396 {"list", no_argument, NULL, 'l'},
397 {"mindays", required_argument, NULL, 'm'},
398 {"maxdays", required_argument, NULL, 'M'},
399 {"warndays", required_argument, NULL, 'W'},
400 {NULL, 0, NULL, '\0'}
404 getopt_long (argc, argv, "d:E:hI:lm:M:W:", long_options,
405 &option_index)) != -1) {
409 if (!isnum (optarg)) {
410 lstchgdate = strtoday (optarg);
411 } else if ( (getlong (optarg, &lstchgdate) == 0)
412 || (lstchgdate < -1)) {
414 _("%s: invalid date '%s'\n"),
421 if (!isnum (optarg)) {
422 expdate = strtoday (optarg);
423 } else if ( (getlong (optarg, &expdate) == 0)
426 _("%s: invalid date '%s'\n"),
436 if ( (getlong (optarg, &inactdays) == 0)
437 || (inactdays < -1)) {
439 _("%s: invalid numeric argument '%s'\n"),
449 if ( (getlong (optarg, &mindays) == 0)
452 _("%s: invalid numeric argument '%s'\n"),
459 if ( (getlong (optarg, &maxdays) == 0)
462 _("%s: invalid numeric argument '%s'\n"),
469 if ( (getlong (optarg, &warndays) == 0)
470 || (warndays < -1)) {
472 _("%s: invalid numeric argument '%s'\n"),
482 check_flags (argc, optind);
486 * check_flags - check flags and parameters consistency
488 * It will not return if an error is encountered.
490 static void check_flags (int argc, int opt_index)
493 * Make certain the flags do not conflict and that there is a user
494 * name on the command line.
497 if (argc != opt_index + 1) {
501 if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
503 _("%s: do not include \"l\" with other flags\n"),
510 * check_perms - check if the caller is allowed to add a group
512 * Non-root users are only allowed to display their aging information.
513 * (we will later make sure that the user is only listing her aging
516 * With PAM support, the setuid bit can be set on chage to allow
517 * non-root users to groups.
518 * Without PAM support, only users who can write in the group databases
521 * It will not return if the user is not allowed.
523 static void check_perms (void)
525 #ifdef ACCT_TOOLS_SETUID
527 pam_handle_t *pamh = NULL;
528 struct passwd *pampw;
531 #endif /* ACCT_TOOLS_SETUID */
534 * An unprivileged user can ask for their own aging information, but
535 * only root can change it, or list another user's aging
539 if (!amroot && !lflg) {
540 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
541 fail_exit (E_NOPERM);
544 #ifdef ACCT_TOOLS_SETUID
546 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
549 _("%s: Cannot determine your user name.\n"),
554 retval = pam_start ("chage", pampw->pw_name, &conv, &pamh);
556 if (PAM_SUCCESS == retval) {
557 retval = pam_authenticate (pamh, 0);
560 if (PAM_SUCCESS == retval) {
561 retval = pam_acct_mgmt (pamh, 0);
565 (void) pam_end (pamh, retval);
567 if (PAM_SUCCESS != retval) {
568 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
569 fail_exit (E_NOPERM);
572 #endif /* ACCT_TOOLS_SETUID */
576 * open_files - open the shadow database
578 * In read-only mode, the databases are not locked and are opened
581 static void open_files (bool readonly)
584 * Lock and open the password file. This loads all of the password
585 * file entries into memory. Then we get a pointer to the password
586 * file entry for the requested user.
589 if (pw_lock () == 0) {
591 _("%s: cannot lock %s; try again later.\n"),
593 fail_exit (E_NOPERM);
597 if (pw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
598 fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
599 SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
600 fail_exit (E_NOPERM);
604 * For shadow password files we have to lock the file and read in
605 * the entries as was done for the password file. The user entries
606 * does not have to exist in this case; a new entry will be created
607 * for this user if one does not exist already.
610 if (spw_lock () == 0) {
612 _("%s: cannot lock %s; try again later.\n"),
613 Prog, spw_dbname ());
614 fail_exit (E_NOPERM);
618 if (spw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
620 _("%s: cannot open %s\n"), Prog, spw_dbname ());
621 SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
622 fail_exit (E_NOPERM);
627 * close_files - close and unlock the password/shadow databases
629 static void close_files (void)
632 * Now close the shadow password file, which will cause all of the
633 * entries to be re-written.
635 if (spw_close () == 0) {
637 _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
638 SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
639 fail_exit (E_NOPERM);
643 * Close the password file. If any entries were modified, the file
644 * will be re-written.
646 if (pw_close () == 0) {
647 fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
648 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
649 fail_exit (E_NOPERM);
651 if (spw_unlock () == 0) {
652 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
653 SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
657 if (pw_unlock () == 0) {
658 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
659 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
666 * update_age - update the aging information in the database
668 * It will not return in case of error
670 static void update_age (const struct spwd *sp, const struct passwd *pw)
675 * There was no shadow entry. The new entry will have the encrypted
676 * password transferred from the normal password file along with the
680 struct passwd pwent = *pw;
682 memzero (&spwent, sizeof spwent);
683 spwent.sp_namp = xstrdup (pw->pw_name);
684 spwent.sp_pwdp = xstrdup (pw->pw_passwd);
685 spwent.sp_flag = SHADOW_SP_FLAG_UNSET;
687 pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
688 if (pw_update (&pwent) == 0) {
690 _("%s: failed to prepare the new %s entry '%s'\n"), Prog, pw_dbname (), pwent.pw_name);
691 fail_exit (E_NOPERM);
694 spwent.sp_namp = xstrdup (sp->sp_namp);
695 spwent.sp_pwdp = xstrdup (sp->sp_pwdp);
696 spwent.sp_flag = sp->sp_flag;
700 * Copy the fields back to the shadow file entry and write the
701 * modified entry back to the shadow file. Closing the shadow and
702 * password files will commit any changes that have been made.
704 spwent.sp_max = maxdays;
705 spwent.sp_min = mindays;
706 spwent.sp_lstchg = lstchgdate;
707 spwent.sp_warn = warndays;
708 spwent.sp_inact = inactdays;
709 spwent.sp_expire = expdate;
711 if (spw_update (&spwent) == 0) {
713 _("%s: failed to prepare the new %s entry '%s'\n"), Prog, spw_dbname (), spwent.sp_namp);
714 fail_exit (E_NOPERM);
720 * get_defaults - get the value of the fields not set from the command line
722 static void get_defaults (const struct spwd *sp)
725 * Set the fields that aren't being set from the command line from
730 maxdays = sp->sp_max;
733 mindays = sp->sp_min;
736 lstchgdate = sp->sp_lstchg;
739 warndays = sp->sp_warn;
742 inactdays = sp->sp_inact;
745 expdate = sp->sp_expire;
749 * Use default values that will not change the behavior of the
774 * chage - change a user's password aging information
776 * This command controls the password aging information.
778 * The valid options are
780 * -d set last password change date (*)
781 * -E set account expiration date (*)
782 * -I set password inactive after expiration (*)
783 * -l show account aging information
784 * -M set maximim number of days before password change (*)
785 * -m set minimum number of days before password change (*)
786 * -W set expiration warning days (*)
788 * (*) requires root permission to execute.
790 * All of the time fields are entered in the internal format which is
791 * either seconds or days.
794 int main (int argc, char **argv)
796 const struct spwd *sp;
799 const struct passwd *pw;
805 (void) setlocale (LC_ALL, "");
806 (void) bindtextdomain (PACKAGE, LOCALEDIR);
807 (void) textdomain (PACKAGE);
811 amroot = (ruid == 0);
813 if (amroot && (is_selinux_enabled () > 0)) {
814 amroot = (selinux_check_passwd_access (PASSWD__ROOTOK) == 0);
819 * Get the program name so that error messages can use it.
821 Prog = Basename (argv[0]);
823 process_flags (argc, argv);
829 if (!spw_file_present ()) {
831 _("%s: the shadow password file is not present\n"),
833 SYSLOG ((LOG_WARN, "can't find the shadow password file"));
835 exit (E_SHADOW_NOTFOUND);
839 /* Drop privileges */
840 if (lflg && ( (setregid (rgid, rgid) != 0)
841 || (setreuid (ruid, ruid) != 0))) {
842 fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
843 Prog, strerror (errno));
844 fail_exit (E_NOPERM);
847 pw = pw_locate (argv[optind]);
849 fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
850 Prog, argv[optind], pw_dbname ());
855 STRFCPY (user_name, pw->pw_name);
856 user_uid = pw->pw_uid;
858 sp = spw_locate (argv[optind]);
862 * Print out the expiration fields if the user has requested the
866 if (!amroot && (ruid != user_uid)) {
867 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
868 fail_exit (E_NOPERM);
871 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
872 "display aging info",
873 user_name, (unsigned int) user_uid, 1);
876 fail_exit (E_SUCCESS);
880 * If none of the fields were changed from the command line, let the
881 * user interactively change them.
883 if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) {
884 printf (_("Changing the aging information for %s\n"),
886 if (new_fields () == 0) {
887 fprintf (stderr, _("%s: error changing fields\n"),
889 fail_exit (E_NOPERM);
893 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
894 "change all aging information",
895 user_name, (unsigned int) user_uid, 1);
901 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
903 user_name, (unsigned int) user_uid, 1);
906 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
908 user_name, (unsigned int) user_uid, 1);
911 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
912 "change last change date",
913 user_name, (unsigned int) user_uid, 1);
916 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
917 "change passwd warning",
918 user_name, (unsigned int) user_uid, 1);
921 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
922 "change inactive days",
923 user_name, (unsigned int) user_uid, 1);
926 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
927 "change passwd expiration",
928 user_name, (unsigned int) user_uid, 1);
937 SYSLOG ((LOG_INFO, "changed password expiry for %s", user_name));