home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3340 / chsh.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-05-17  |  9.1 KB  |  422 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.  
  18. #ifndef    lint
  19. static    char    sccsid[] = "@(#)chsh.c    3.3    11:23:29    12/19/90";
  20. #endif
  21.  
  22. /*
  23.  * Set up some BSD defines so that all the BSD ifdef's are
  24.  * kept right here 
  25.  */
  26.  
  27. #ifndef    BSD
  28. #include <string.h>
  29. #include <memory.h>
  30. #else
  31. #include <strings.h>
  32. #define    strchr    index
  33. #define    strrchr    rindex
  34. #endif
  35.  
  36. #include "config.h"
  37. #include "pwd.h"
  38.  
  39. /*
  40.  * Global variables.
  41.  */
  42.  
  43. char    *Progname;            /* Program name */
  44. int    amroot;                /* Real UID is root */
  45. char    loginsh[BUFSIZ];        /* Name of new login shell */
  46.  
  47. /*
  48.  * External identifiers
  49.  */
  50.  
  51. extern    struct    passwd    *getpwuid ();
  52. extern    struct    passwd    *getpwnam ();
  53. extern    int    optind;
  54. extern    char    *optarg;
  55. extern    char    *getlogin ();
  56. #ifdef    NDBM
  57. extern    int    pw_dbm_mode;
  58. #endif
  59.  
  60. /*
  61.  * #defines for messages.  This facilities foreign language conversion
  62.  * since all messages are defined right here.
  63.  */
  64.  
  65. #define    USAGE        "Usage: %s [ -s shell ] [ name ]\n"
  66. #define    WHOAREYOU    "%s: Cannot determine you user name.\n"
  67. #define    UNKUSER        "%s: Unknown user %s\n"
  68. #define    NOPERM        "You may not change the shell for %s.\n"
  69. #define    NOPERM2        "can't change shell for `%s'\n"
  70. #define    NEWSHELLMSG    "Changing the login shell for %s\n"
  71. #define    NEWSHELL    "Login Shell"
  72. #define    NEWSHELLMSG2 \
  73.     "Enter the new value, or press return for the default\n\n"
  74. #define    BADSHELL    "%s is an invalid shell.\n"
  75. #define    BADFIELD    "%s: Invalid entry: %s\n"
  76. #define    PWDBUSY        "Cannot lock the password file; try again later.\n"
  77. #define    PWDBUSY2    "can't lock /etc/passwd\n"
  78. #define    OPNERROR    "Cannot open the password file.\n"
  79. #define    OPNERROR2    "can't open /etc/passwd\n"
  80. #define    UPDERROR    "Error updating the password entry.\n"
  81. #define    UPDERROR2    "error updating passwd entry\n"
  82. #define    DBMERROR    "Error updating the DBM password entry.\n"
  83. #define    DBMERROR2    "error updating DBM passwd entry.\n"
  84. #define    NOTROOT        "Cannot change ID to root.\n"
  85. #define    NOTROOT2    "can't setuid(0).\n"
  86. #define    CLSERROR    "Cannot commit password file changes.\n"
  87. #define    CLSERROR2    "can't rewrite /etc/passwd.\n"
  88. #define    UNLKERROR    "Cannot unlock the password file.\n"
  89. #define    UNLKERROR2    "can't unlock /etc/passwd.\n"
  90. #define    CHGSHELL    "changed user `%s' shell to `%s'\n"
  91.  
  92. /*
  93.  * usage - print command line syntax and exit
  94.  */
  95.  
  96. void
  97. usage ()
  98. {
  99.     fprintf (stderr, USAGE, Progname);
  100.     exit (1);
  101. }
  102.  
  103. /*
  104.  * new_fields - change the user's login shell information interactively
  105.  *
  106.  * prompt the user for the login shell and change it according to the
  107.  * response, or leave it alone if nothing was entered.
  108.  */
  109.  
  110. new_fields ()
  111. {
  112.     printf (NEWSHELLMSG2);
  113.     change_field (loginsh, NEWSHELL);
  114. }
  115.  
  116. /*
  117.  * check_shell - see if the user's login shell is listed in /etc/shells
  118.  *
  119.  * The /etc/shells file is read for valid names of login shells.  If the
  120.  * /etc/shells file does not exist the user cannot set any shell unless
  121.  * they are root.
  122.  */
  123.  
  124. check_shell (shell)
  125. char    *shell;
  126. {
  127.     char    buf[BUFSIZ];
  128.     char    *cp;
  129.     int    found = 0;
  130.     FILE    *fp;
  131.  
  132.     if (amroot)
  133.         return 1;
  134.  
  135.     if ((fp = fopen ("/etc/shells", "r")) == (FILE *) 0)
  136.         return 0;
  137.  
  138.     while (fgets (buf, BUFSIZ, fp) && ! found) {
  139.         if (cp = strrchr (buf, '\n'))
  140.             *cp = '\0';
  141.  
  142.         if (strcmp (buf, shell) == 0)
  143.             found = 1;
  144.     }
  145.     fclose (fp);
  146.  
  147.     return found;
  148. }
  149.  
  150. /*
  151.  * restricted_shell - return true if the named shell begins with 'r' or 'R'
  152.  *
  153.  * If the first letter of the filename is 'r' or 'R', the shell is
  154.  * considered to be restricted.
  155.  */
  156.  
  157. int
  158. restricted_shell (shell)
  159. char    *shell;
  160. {
  161.     char    *cp;
  162.  
  163.     if (cp = strrchr (shell, '/'))
  164.         cp++;
  165.     else
  166.         cp = shell;
  167.  
  168.     return *cp == 'r' || *cp == 'R';
  169. }
  170.  
  171. /*
  172.  * chsh - this command controls changes to the user's shell
  173.  *
  174.  *    The only suppoerted option is -s which permits the
  175.  *    the login shell to be set from the command line.
  176.  */
  177.  
  178. int
  179. main (argc, argv)
  180. int    argc;
  181. char    **argv;
  182. {
  183.     char    user[BUFSIZ];        /* User name                         */
  184.     int    flag;            /* Current command line flag         */
  185.     int    sflg = 0;        /* -s - set shell from command line  */
  186.     int    i;            /* Loop control variable             */
  187.     char    *cp;            /* Miscellaneous character pointer   */
  188.     struct    passwd    *pw;        /* Password entry from /etc/passwd   */
  189.     struct    passwd    pwent;        /* New password entry                */
  190.  
  191.     /*
  192.      * This command behaves different for root and non-root
  193.      * users.
  194.      */
  195.  
  196.     amroot = getuid () == 0;
  197. #ifdef    NDBM
  198.     pw_dbm_mode = O_RDWR;
  199. #endif
  200.  
  201.     /*
  202.      * Get the program name.  The program name is used as a
  203.      * prefix to most error messages.  It is also used as input
  204.      * to the openlog() function for error logging.
  205.      */
  206.  
  207.     if (Progname = strrchr (argv[0], '/'))
  208.         Progname++;
  209.     else
  210.         Progname = argv[0];
  211.  
  212.     openlog (Progname, LOG_PID, LOG_AUTH);
  213.  
  214.     /*
  215.      * There is only one option, but use getopt() anyway to
  216.      * keep things consistent.
  217.      */
  218.  
  219.     while ((flag = getopt (argc, argv, "s:")) != EOF) {
  220.         switch (flag) {
  221.             case 's':
  222.                 sflg++;
  223.                 strcpy (loginsh, optarg);
  224.                 break;
  225.             default:
  226.                 usage ();
  227.         }
  228.     }
  229.  
  230.     /*
  231.      * There should be only one remaining argument at most
  232.      * and it should be the user's name.
  233.      */
  234.  
  235.     if (argc > optind + 1)
  236.         usage ();
  237.  
  238.     /*
  239.      * Get the name of the user to check.  It is either
  240.      * the command line name, or the name getlogin()
  241.      * returns.
  242.      */
  243.  
  244.     if (optind < argc) {
  245.         strncpy (user, argv[optind], sizeof user);
  246.         pw = getpwnam (user);
  247.     } else if (cp = getlogin ()) {
  248.         strncpy (user, cp, sizeof user);
  249.         pw = getpwnam (user);
  250.     } else {
  251.         fprintf (stderr, WHOAREYOU, Progname);
  252.         exit (1);
  253.     }
  254.  
  255.     /*
  256.      * Make certain there was a password entry for the
  257.      * user.
  258.      */
  259.  
  260.     if (! pw) {
  261.         fprintf (stderr, UNKUSER, Progname, user);
  262.         exit (1);
  263.     }
  264.  
  265.     /*
  266.      * Non-privileged users are only allowed to change the
  267.      * shell if the UID of the user matches the current
  268.      * real UID.
  269.      */
  270.  
  271.     if (! amroot && pw->pw_uid != getuid ()) {
  272.         fprintf (stderr, NOPERM, user);
  273.         syslog (LOG_WARN, NOPERM2, user);
  274.         exit (1);
  275.     }
  276.  
  277.     /*
  278.      * Non-privileged users are only allowed to change the
  279.      * shell if it is not a restricted one.
  280.      */
  281.  
  282.     if (! amroot && restricted_shell (pw->pw_shell)) {
  283.         fprintf (stderr, NOPERM, user);
  284.         syslog (LOG_WARN, NOPERM2, user);
  285.         exit (1);
  286.     }
  287.  
  288.     /*
  289.      * Make a copy of the user's password file entry so it
  290.      * can be modified without worrying about it be modified
  291.      * elsewhere.
  292.      */
  293.  
  294.     pwent = *pw;
  295.     pwent.pw_name = strdup (pw->pw_name);
  296.     pwent.pw_passwd = strdup (pw->pw_passwd);
  297. #ifdef    ATT_AGE
  298.     pwent.pw_age = strdup (pw->pw_age);
  299. #endif
  300. #ifdef    ATT_COMMENT
  301.     pwent.pw_comment = strdup (pw->pw_comment);
  302. #endif
  303.     pwent.pw_dir = strdup (pw->pw_dir);
  304.     pwent.pw_gecos = strdup (pw->pw_gecos);
  305.  
  306.     /*
  307.      * Now get the login shell.  Either get it from the password
  308.      * file, or use the value from the command line.
  309.      */
  310.  
  311.     if (! sflg)
  312.         strcpy (loginsh, pw->pw_shell);
  313.  
  314.     /*
  315.      * If the login shell was not set on the command line,
  316.      * let the user interactively change it.
  317.      */
  318.  
  319.     if (! sflg) {
  320.         printf (NEWSHELLMSG, user);
  321.         new_fields ();
  322.     }
  323.  
  324.     /*
  325.      * Check all of the fields for valid information.  The shell
  326.      * field may not contain any illegal characters.  Non-privileged
  327.      * users are restricted to using the shells in /etc/shells.
  328.      */
  329.  
  330.     if (valid_field (loginsh, ":,=")) {
  331.         fprintf (stderr, BADFIELD, Progname, loginsh);
  332.         exit (1);
  333.     }
  334.     if (! check_shell (loginsh)) {
  335.         fprintf (stderr, BADSHELL, loginsh);
  336.         exit (1);
  337.     }
  338.     pwent.pw_shell = loginsh;
  339.     pw = &pwent;
  340.  
  341.     /*
  342.      * Before going any further, raise the ulimit to prevent
  343.      * colliding into a lowered ulimit, and set the real UID
  344.      * to root to protect against unexpected signals.  Any
  345.      * keyboard signals are set to be ignored.
  346.      */
  347.  
  348.     ulimit (2, 30000);
  349.     if (setuid (0)) {
  350.         fprintf (stderr, NOTROOT);
  351.         syslog (LOG_ERR, NOTROOT2);
  352.         exit (1);
  353.     }
  354.     signal (SIGHUP, SIG_IGN);
  355.     signal (SIGINT, SIG_IGN);
  356.     signal (SIGQUIT, SIG_IGN);
  357. #ifdef    SIGTSTP
  358.     signal (SIGTSTP, SIG_IGN);
  359. #endif
  360.  
  361.     /*
  362.      * The passwd entry is now ready to be committed back to
  363.      * the password file.  Get a lock on the file and open it.
  364.      */
  365.  
  366.     for (i = 0;i < 30;i++)
  367.         if (pw_lock ())
  368.             break;
  369.  
  370.     if (i == 30) {
  371.         fprintf (stderr, PWDBUSY);
  372.         syslog (LOG_WARN, PWDBUSY2);
  373.         exit (1);
  374.     }
  375.     if (! pw_open (O_RDWR)) {
  376.         fprintf (stderr, OPNERROR);
  377.         syslog (LOG_ERR, OPNERROR2);
  378.         (void) pw_unlock ();
  379.         exit (1);
  380.     }
  381.  
  382.     /*
  383.      * Update the passwd file entry.  If there is a DBM file,
  384.      * update that entry as well.
  385.      */
  386.  
  387.     if (! pw_update (pw)) {
  388.         fprintf (stderr, UPDERROR);
  389.         syslog (LOG_ERR, UPDERROR2);
  390.         (void) pw_unlock ();
  391.         exit (1);
  392.     }
  393. #if defined(DBM) || defined(NDBM)
  394.     if (access ("/etc/passwd.pag", 0) == 0 && ! pw_dbm_update (pw)) {
  395.         fprintf (stderr, DBMERROR);
  396.         syslog (LOG_ERR, DBMERROR2);
  397.         (void) pw_unlock ();
  398.         exit (1);
  399.     }
  400. #endif
  401.  
  402.     /*
  403.      * Changes have all been made, so commit them and unlock the
  404.      * file.
  405.      */
  406.  
  407.     if (! pw_close ()) {
  408.         fprintf (stderr, CLSERROR);
  409.         syslog (LOG_ERR, CLSERROR2);
  410.         (void) pw_unlock ();
  411.         exit (1);
  412.     }
  413.     if (! pw_unlock ()) {
  414.         fprintf (stderr, UNLKERROR);
  415.         syslog (LOG_ERR, UNLKERROR2);
  416.         exit (1);
  417.     }
  418.     syslog (LOG_INFO, CHGSHELL, user, pwent.pw_shell);
  419.     closelog ();
  420.     exit (0);
  421. }
  422.