home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume38 / shadow / part05 / chsh.c next >
C/C++ Source or Header  |  1993-08-14  |  10KB  |  487 lines

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