home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3343 / chage.c next >
Encoding:
C/C++ Source or Header  |  1991-05-17  |  14.9 KB  |  676 lines

  1. /*
  2.  * Copyright 1989, 1990, John F. Haugh II
  3.  * All rights reserved.
  4.  *
  5.  * Permission is granted to copy and create derivative works for any
  6.  * non-commercial purpose, provided this copyright notice is preserved
  7.  * in all copies of source code, or included in human readable form
  8.  * and conspicuously displayed on all copies of object code or
  9.  * distribution media.
  10.  */
  11.  
  12. #include <sys/types.h>
  13. #include <syslog.h>
  14. #include <stdio.h>
  15. #include <fcntl.h>
  16. #include <signal.h>
  17. #include <ctype.h>
  18. #include <time.h>
  19.  
  20. #ifndef    lint
  21. static    char    sccsid[] = "%W%    %U%    %G%";
  22. #endif
  23.  
  24. /*
  25.  * Set up some BSD defines so that all the BSD ifdef's are
  26.  * kept right here 
  27.  */
  28.  
  29. #ifndef    BSD
  30. #include <string.h>
  31. #include <memory.h>
  32. #define    bzero(a,n)    memset(a, 0, n)
  33. #else
  34. #include <strings.h>
  35. #define    strchr    index
  36. #define    strrchr    rindex
  37. #endif
  38.  
  39. #include "config.h"
  40. #include "pwd.h"
  41. #include "shadow.h"
  42.  
  43. /*
  44.  * Global variables
  45.  */
  46.  
  47. char    *Prog;
  48. long    mindays;
  49. long    maxdays;
  50. long    lastday;
  51. long    warndays;
  52. long    inactdays;
  53. long    expdays;
  54. void    cleanup();
  55.  
  56. /*
  57.  * External identifiers
  58.  */
  59.  
  60. extern    long    a64l();
  61. extern    int    pw_lock(), pw_open(),
  62.         pw_unlock(), pw_close(),
  63.         pw_update();
  64. extern    struct    passwd    *pw_locate();
  65. extern    int    spw_lock(), spw_open(),
  66.         spw_unlock(), spw_close(),
  67.         spw_update();
  68. extern    struct    spwd    *spw_locate();
  69. extern    int    optind;
  70. extern    char    *optarg;
  71. extern    char    *getlogin ();
  72. #ifdef    NDBM
  73. extern    int    pw_dbm_mode;
  74. extern    int    sp_dbm_mode;
  75. #endif
  76.  
  77. /*
  78.  * Password aging constants
  79.  *
  80.  *    DAY - seconds in a day
  81.  *    WEEK - seconds in a week
  82.  *    SCALE - convert from clock to aging units
  83.  */
  84.  
  85. #define    DAY    (24L*3600L)
  86. #define    WEEK    (7*DAY)
  87.  
  88. #ifdef    ITI_AGING
  89. #define    SCALE    (1)
  90. #else
  91. #define    SCALE    (DAY)
  92. #endif
  93.  
  94. /*
  95.  * days and juldays are used to compute the number of days in the
  96.  * current month, and the cummulative number of days in the preceding
  97.  * months.  they are declared so that january is 1, not 0.
  98.  */
  99.  
  100. static    short    days[13] = { 0,
  101.     31,    28,    31,    30,    31,    30,    /* JAN - JUN */
  102.     31,    31,    30,    31,    30,    31 };    /* JUL - DEC */
  103.  
  104. static    short    juldays[13] = { 0,
  105.     0,    31,    59,    90,    120,    151,    /* JAN - JUN */
  106.     181,    212,    243,    273,    304,    334 };    /* JUL - DEC */
  107.  
  108. /*
  109.  * #defines for messages.  This facilities foreign language conversion
  110.  * since all messages are defined right here.
  111.  */
  112.  
  113. #define    USAGE \
  114. "Usage: %s [ -l ] [ -m min_days ] [ -M max_days ] [ -W warn ]\n\
  115.        [ -I inactive ] [ -E expire ] [ -d last_day ] user\n"
  116. #define    DBMERROR    "Error updating the DBM password entry.\n"
  117. #define    DBMERROR2    "error updating DBM shadow entry.\n"
  118.  
  119. /*
  120.  * usage - print command line syntax and exit
  121.  */
  122.  
  123. void
  124. usage ()
  125. {
  126.     fprintf (stderr, USAGE, Prog);
  127.     exit (1);
  128. }
  129.  
  130. /*
  131.  * strtoday - compute the number of days since 1970.
  132.  *
  133.  * the total number of days prior to the current date is
  134.  * computed.  january 1, 1970 is used as the origin with
  135.  * it having a day number of 0.  the gmtime() routine is
  136.  * used to prevent confusion regarding time zones.
  137.  */
  138.  
  139. long
  140. strtoday (str)
  141. char    *str;
  142. {
  143.     char    slop[2];
  144.     int    month;
  145.     int    day;
  146.     int    year;
  147.     long    total;
  148.  
  149.     /*
  150.      * start by separating the month, day and year.  this is
  151.      * a chauvanistic program - it only takes date input in
  152.      * the standard USA format.
  153.      */
  154.  
  155.     if (sscanf (str, "%d/%d/%d%c", &month, &day, &year, slop) != 3)
  156.         return -1;
  157.  
  158.     /*
  159.      * the month, day of the month, and year are checked for
  160.      * correctness and the year adjusted so it falls between
  161.      * 1970 and 2069.
  162.      */
  163.  
  164.     if (month < 1 || month > 12)
  165.         return -1;
  166.  
  167.     if (day < 1)
  168.         return -1;
  169.  
  170.     if ((month != 2 || (year % 4) != 0) && day > days[month])
  171.         return -1;
  172.     else if ((month == 2 && (year % 4) == 0) && day > 29)
  173.         return -1;
  174.  
  175.     if (year < 0)
  176.         return -1;
  177.     else if (year < 69)
  178.         year += 2000;
  179.     else if (year < 99)
  180.         year += 1900;
  181.  
  182.     if (year < 1970 || year > 2069)
  183.         return -1;
  184.  
  185.     /*
  186.      * the total number of days is the total number of days in all
  187.      * the whole years, plus the number of leap days, plus the
  188.      * number of days in the whole months preceding, plus the number
  189.      * of days so far in the month.
  190.      */
  191.  
  192.     total = ((year - 1970) * 365) + (((year + 1) - 1970) / 4);
  193.     total += juldays[month] + (month > 2 && (year % 4) == 0 ? 1:0);
  194.     total += day - 1;
  195.  
  196.     return total;
  197. }
  198.  
  199. /*
  200.  * new_fields - change the user's password aging information interactively.
  201.  *
  202.  * prompt the user for all of the password age values.  set the fields
  203.  * from the user's response, or leave alone if nothing was entered.  the
  204.  * value (-1) is used to indicate the field should be removed if possible.
  205.  * any other negative value is an error.  very large positive values will
  206.  * be handled elsewhere.
  207.  */
  208.  
  209. int
  210. new_fields ()
  211. {
  212.     char    buf[BUFSIZ];
  213.     char    *cp;
  214.     long    value;
  215.     struct    tm    *tp;
  216.  
  217.     printf ("Enter the new value, or press return for the default\n\n");
  218.  
  219.     sprintf (buf, "%ld", mindays);
  220.     change_field (buf, "Minimum Password Age");
  221.     if (((mindays = strtol (buf, &cp, 10)) == 0 && *cp) || mindays < -1)
  222.         return 0;
  223.  
  224.     sprintf (buf, "%ld", maxdays);
  225.     change_field (buf, "Maximum Password Age");
  226.     if (((maxdays = strtol (buf, &cp, 10)) == 0 && *cp) || maxdays < -1)
  227.         return 0;
  228.  
  229.     value = lastday * SCALE;
  230.     tp = gmtime (&value);
  231.     sprintf (buf, "%02d/%02d/%02d",
  232.         tp->tm_mon + 1, tp->tm_mday, tp->tm_year);
  233.     change_field (buf, "Last Password Change (MM/DD/YY)");
  234.     if (strcmp (buf, "12/31/69") == 0)
  235.         lastday = -1;
  236.     else if ((lastday = strtoday (buf)) == -1)
  237.         return 0;
  238.  
  239.     sprintf (buf, "%ld", warndays);
  240.     change_field (buf, "Password Expiration Warning");
  241.     if (((warndays = strtol (buf, &cp, 10)) == 0 && *cp) || warndays < -1)
  242.         return 0;
  243.  
  244.     sprintf (buf, "%ld", inactdays);
  245.     change_field (buf, "Password Inactive");
  246.     if (((inactdays = strtol (buf, &cp, 10)) == 0 && *cp) || inactdays < -1)
  247.         return 0;
  248.  
  249.     value = expdays * SCALE;
  250.     tp = gmtime (&value);
  251.     sprintf (buf, "%02d/%02d/%02d",
  252.         tp->tm_mon + 1, tp->tm_mday, tp->tm_year);
  253.     change_field (buf, "Account Expiration Date (MM/DD/YY)");
  254.     if (strcmp (buf, "12/31/69") == 0)
  255.         expdays = -1;
  256.     else if ((expdays = strtoday (buf)) == -1)
  257.         return 0;
  258.  
  259.     return 1;
  260. }
  261.  
  262. /*
  263.  * list_fields - display the current values of the expiration fields
  264.  *
  265.  * display the password age information from the password fields.  date
  266.  * values will be displayed as a calendar date, or the word "Never" if
  267.  * the date is 1/1/70, which is day number 0.
  268.  */
  269.  
  270. void
  271. list_fields ()
  272. {
  273.     struct    tm    *tp;
  274.     char    *cp;
  275.     long    changed;
  276.     long    expires;
  277.  
  278.     /*
  279.      * Start with the easy numbers - the number of days before the
  280.      * password can be changed, the number of days after which the
  281.      * password must be chaged, the number of days before the
  282.      * password expires that the user is told, and the number of
  283.      * days after the password expires that the account becomes
  284.      * unusable.
  285.      */
  286.  
  287.     printf ("Minimum:\t%d\n", mindays);
  288.     printf ("Maximum:\t%d\n", maxdays);
  289.     printf ("Warning:\t%d\n", warndays);
  290.     printf ("Inactive:\t%d\n", inactdays);
  291.  
  292.     /*
  293.      * The "last change" date is either "Never" or the date the
  294.      * password was last modified.  The date is the number of
  295.      * days since 1/1/1970.
  296.      */
  297.  
  298.     printf ("Last Change:\t\t");
  299.     if (changed <= 0) {
  300.         printf ("Never\n");
  301.     } else {
  302.         changed = lastday * SCALE;
  303.         tp = gmtime (&changed);
  304.         cp = asctime (tp);
  305.         printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
  306.     }
  307.  
  308.     /*
  309.      * The password expiration date is determined from the last
  310.      * change date plus the number of days the password is valid
  311.      * for.
  312.      */
  313.  
  314.     printf ("Password Expires:\t");
  315.     if (changed <= 0 || maxdays >= 10000*(DAY/SCALE) || maxdays <= 0) {
  316.         printf ("Never\n");
  317.     } else {
  318.         expires = changed + maxdays * SCALE;
  319.         tp = gmtime (&expires);
  320.         cp = asctime (tp);
  321.         printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
  322.     }
  323.  
  324.     /*
  325.      * The account becomes inactive if the password is expired
  326.      * for more than "inactdays".  The expiration date is calculated
  327.      * and the number of inactive days is added.  The resulting date
  328.      * is when the active will be disabled.
  329.      */
  330.  
  331.     printf ("Password Inactive:\t");
  332.     if (changed <= 0 || inactdays <= 0 ||
  333.             maxdays >= 10000*(DAY/SCALE) || maxdays <= 0) {
  334.         printf ("Never\n");
  335.     } else {
  336.         expires = changed + (maxdays + inactdays) * SCALE;
  337.         tp = gmtime (&expires);
  338.         cp = asctime (tp);
  339.         printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
  340.     }
  341.  
  342.     /*
  343.      * The account will expire on the given date regardless of the
  344.      * password expiring or not.
  345.      */
  346.  
  347.     printf ("Account Expires:\t");
  348.     if (expdays <= 0) {
  349.         printf ("Never\n");
  350.     } else {
  351.         expires = expdays * SCALE;
  352.         tp = gmtime (&expires);
  353.         cp = asctime (tp);
  354.         printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
  355.     }
  356. }
  357.  
  358. /*
  359.  * chage - change a user's password aging information
  360.  *
  361.  *    This command controls the password aging information.
  362.  *
  363.  *    The valid options are
  364.  *
  365.  *    -m    minimum number of days before password change (*)
  366.  *    -M    maximim number of days before password change (*)
  367.  *    -d    last password change date (*)
  368.  *    -l    last password change date
  369.  *    -W    expiration warning days (*)
  370.  *    -I    password inactive after expiration (*)
  371.  *    -E    account expiration date (*)
  372.  *
  373.  *    (*) requires root permission to execute.
  374.  *
  375.  *    All of the time fields are entered in the internal format
  376.  *    which is either seconds or days.
  377.  */
  378.  
  379. int
  380. main (argc, argv)
  381. int    argc;
  382. char    **argv;
  383. {
  384.     int    flag;
  385.     int    lflg;
  386.     int    mflg;
  387.     int    Mflg;
  388.     int    dflg;
  389.     int    Wflg;
  390.     int    Iflg;
  391.     int    Eflg;
  392.     int    ruid = getuid ();
  393.     struct    passwd    *pw;
  394.     struct    passwd    pwent;
  395.     struct    spwd    *sp;
  396.     struct    spwd    spwd;
  397.     char    name[BUFSIZ];
  398.  
  399.     /*
  400.      * Get the program name so that error messages can use it.
  401.      */
  402.  
  403.     if (Prog = strrchr (argv[0], '/'))
  404.         Prog++;
  405.     else
  406.         Prog = argv[0];
  407.  
  408.     openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
  409. #ifdef    NDBM
  410.     sp_dbm_mode = O_RDWR;
  411.     pw_dbm_mode = O_RDWR;
  412. #endif
  413.  
  414.     /*
  415.      * Parse the flags.  The difference between password file
  416.      * formats includes the number of fields, and whether the
  417.      * dates are entered as days or weeks.  Shadow password
  418.      * file info =must= be entered in days, while regular
  419.      * password file info =must= be entered in weeks.
  420.      */
  421.  
  422.     while ((flag = getopt (argc, argv, "lm:M:W:I:E:d:")) != EOF) {
  423.         switch (flag) {
  424.             case 'l':
  425.                 lflg++;
  426.                 break;
  427.             case 'm':
  428.                 mflg++;
  429.                 mindays = strtol (optarg, 0, 10);
  430.                 break;
  431.             case 'M':
  432.                 Mflg++;
  433.                 maxdays = strtol (optarg, 0, 10);
  434.                 break;
  435.             case 'd':
  436.                 dflg++;
  437.                 lastday = strtol (optarg, 0, 10);
  438.                 break;
  439.             case 'W':
  440.                 Wflg++;
  441.                 warndays = strtol (optarg, 0, 10);
  442.                 break;
  443.             case 'I':
  444.                 Iflg++;
  445.                 inactdays = strtol (optarg, 0, 10);
  446.                 break;
  447.             case 'E':
  448.                 Eflg++;
  449.                 expdays = strtol (optarg, 0, 10);
  450.                 break;
  451.             default:
  452.                 usage ();
  453.         }
  454.     }
  455.  
  456.     /*
  457.      * Make certain the flags do not conflict and that there is
  458.      * a user name on the command line.
  459.      */
  460.  
  461.     if (argc != optind + 1)
  462.         usage ();
  463.  
  464.     if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
  465.         fprintf (stderr, "%s: do not include \"l\" with other flags\n",
  466.             Prog);
  467.         usage ();
  468.     }
  469.  
  470.     /*
  471.      * An unprivileged user can ask for their own aging information,
  472.      * but only root can change it, or list another user's aging
  473.      * information.
  474.      */
  475.  
  476.     if (ruid != 0 && ! lflg) {
  477.         fprintf (stderr, "%s: permission denied\n", Prog);
  478.         exit (1);
  479.     }
  480.  
  481.     /*
  482.      * Lock and open the password file.  This loads all of the
  483.      * password file entries into memory.  Then we get a pointer
  484.      * to the password file entry for the requested user.
  485.      */
  486.  
  487.     if (! pw_lock ()) {
  488.         fprintf (stderr, "%s: can't lock password file\n", Prog);
  489.         exit (1);
  490.     }
  491.     if (! pw_open (ruid != 0 || lflg ? O_RDONLY:O_RDWR)) {
  492.         fprintf (stderr, "%s: can't open password file\n", Prog);
  493.         cleanup (1);
  494.         exit (1);
  495.     }
  496.     if (! (pw = pw_locate (argv[optind]))) {
  497.         fprintf (stderr, "%s: unknown user: %s\n", Prog, argv[optind]);
  498.         cleanup (1);
  499.         exit (1);
  500.     }
  501.  
  502.     /*
  503.      * For shadow password files we have to lock the file and
  504.      * read in the entries as was done for the password file.
  505.      * The user entries does not have to exist in this case;
  506.      * a new entry will be created for this user if one does
  507.      * not exist already.
  508.      */
  509.  
  510.     if (! spw_lock ()) {
  511.         fprintf (stderr, "%s: can't lock shadow file\n", Prog);
  512.         cleanup (1);
  513.         exit (1);
  514.     }
  515.     if (! spw_open ((ruid != 0 || lflg) ? O_RDONLY:O_RDWR)) {
  516.         fprintf (stderr, "%s: can't open shadow file\n", Prog);
  517.         cleanup (2);
  518.         exit (1);
  519.     }
  520.     if (sp = spw_locate (argv[optind]))
  521.         spwd = *sp;
  522.  
  523.     strcpy (name, pw->pw_name);
  524.     pwent = *pw;
  525.  
  526.     /*
  527.      * Set the fields that aren't being set from the command line
  528.      * from the password file.
  529.      */
  530.  
  531.     if (sp) {
  532.         if (! Mflg)
  533.             maxdays = spwd.sp_max;
  534.         if (! mflg)
  535.             mindays = spwd.sp_min;
  536.         if (! dflg)
  537.             lastday = spwd.sp_lstchg;
  538.         if (! Wflg)
  539.             warndays = spwd.sp_warn;
  540.         if (! Iflg)
  541.             inactdays = spwd.sp_inact;
  542.         if (! Eflg)
  543.             expdays = spwd.sp_expire;
  544.     } else
  545. #ifdef    ATT_AGE
  546.     {
  547.         if (pwent.pw_age && strlen (pwent.pw_age) >= 2) {
  548.             if (! Mflg)
  549.                 maxdays = c64i (pwent.pw_age[0]) * (WEEK/SCALE);
  550.             if (! mflg)
  551.                 mindays = c64i (pwent.pw_age[1]) * (WEEK/SCALE);
  552.             if (! dflg && strlen (pwent.pw_age) == 4)
  553.                 lastday = a64l (pwent.pw_age+2) * (WEEK/SCALE);
  554.         } else {
  555.             mindays = 0;
  556.             maxdays = 10000L * (DAY/SCALE);
  557.             lastday = -1;
  558.         }
  559.         warndays = inactdays = expdays = -1;
  560.     }
  561. #endif
  562.  
  563.     /*
  564.      * Print out the expiration fields if the user has
  565.      * requested the list option.
  566.      */
  567.  
  568.     if (lflg) {
  569.         if (ruid != 0 && ruid != pw->pw_uid) {
  570.             fprintf (stderr, "%s: permission denied\n", Prog);
  571.             exit (1);
  572.         }
  573.         list_fields ();
  574.         cleanup (2);
  575.         exit (0);
  576.     }
  577.  
  578.     /*
  579.      * If none of the fields were changed from the command line,
  580.      * let the user interactively change them.
  581.      */
  582.  
  583.     if (! mflg && ! Mflg && ! dflg && ! Wflg && ! Iflg && ! Eflg) {
  584.         printf ("Changing the aging information for %s\n", name);
  585.         if (! new_fields ()) {
  586.             fprintf (stderr, "%s: error changing fields\n", Prog);
  587.             cleanup (2);
  588.             exit (1);
  589.         }
  590.     }
  591.  
  592.     /*
  593.      * There was no shadow entry.  The new entry will have the
  594.      * encrypted password transferred from the normal password
  595.      * file along with the aging information.
  596.      */
  597.  
  598.     if (sp == 0) {
  599.         sp = &spwd;
  600.         bzero (&spwd, sizeof spwd);
  601.  
  602.         sp->sp_namp = pw->pw_name;
  603.         sp->sp_pwdp = pw->pw_passwd;
  604.         sp->sp_flag = -1;
  605.  
  606.         pwent.pw_passwd = "!";
  607. #ifdef    ATT_AGE
  608.         pwent.pw_age = "";
  609. #endif
  610.         if (! pw_update (&pwent)) {
  611.             fprintf (stderr, "%s: can't update password file\n",
  612.                 Prog);
  613.             cleanup (2);
  614.             exit (1);
  615.         }
  616. #if defined(DBM) || defined(NDBM)
  617.         (void) pw_dbm_update (&pwent);
  618. #endif
  619.     }
  620.  
  621.     /*
  622.      * Copy the fields back to the shadow file entry and
  623.      * write the modified entry back to the shadow file.
  624.      * Closing the shadow and password files will commit
  625.      * any changes that have been made.
  626.      */
  627.  
  628.     sp->sp_max = maxdays;
  629.     sp->sp_min = mindays;
  630.     sp->sp_lstchg = lastday;
  631.     sp->sp_warn = warndays;
  632.     sp->sp_inact = inactdays;
  633.     sp->sp_expire = expdays;
  634.  
  635.     if (! spw_update (sp)) {
  636.         fprintf (stderr, "%s: can't update shadow file\n", Prog);
  637.         cleanup (2);
  638.         exit (1);
  639.     }
  640. #ifdef    NDBM
  641.     if (access ("/etc/shadow.pag", 0) == 0 && ! sp_dbm_update (sp)) {
  642.         fprintf (stderr, DBMERROR);
  643.         syslog (LOG_ERR, DBMERROR2);
  644.         (void) spw_unlock ();
  645.         exit (1);
  646.     }
  647. #endif    /* NDBM */
  648.     if (! spw_close ()) {
  649.         fprintf (stderr, "%s: can't rewrite shadow file\n", Prog);
  650.         cleanup (2);
  651.         exit (1);
  652.     }
  653.     (void) pw_close ();
  654.     cleanup (2);
  655.     exit (0);
  656.     /*NOTREACHED*/
  657. }
  658.  
  659. /*
  660.  * cleanup - unlock any locked password files
  661.  */
  662.  
  663. void
  664. cleanup (state)
  665. int    state;
  666. {
  667.     switch (state) {
  668.         case 2:
  669.             spw_unlock ();
  670.         case 1:
  671.             pw_unlock ();
  672.         case 0:
  673.             break;
  674.     }
  675. }
  676.