home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / security / shadow-3.1.4 / chsh.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-28  |  10.0 KB  |  484 lines

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