home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / sh-utils-1.12-src.tgz / tar.out / fsf / sh-utils / src / su.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  15KB  |  592 lines

  1. /* su for GNU.  Run a shell with substitute user and group IDs.
  2.    Copyright (C) 92, 93, 1994 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Run a shell with the real and effective UID and GID and groups
  19.    of USER, default `root'.
  20.  
  21.    The shell run is taken from USER's password entry, /bin/sh if
  22.    none is specified there.  If the account has a password, su
  23.    prompts for a password unless run by a user with real UID 0.
  24.  
  25.    Does not change the current directory.
  26.    Sets `HOME' and `SHELL' from the password entry for USER, and if
  27.    USER is not root, sets `USER' and `LOGNAME' to USER.
  28.    The subshell is not a login shell.
  29.  
  30.    If one or more ARGs are given, they are passed as additional
  31.    arguments to the subshell.
  32.  
  33.    Does not handle /bin/sh or other shells specially
  34.    (setting argv[0] to "-su", passing -c only to certain shells, etc.).
  35.    I don't see the point in doing that, and it's ugly.
  36.  
  37.    This program intentionally does not support a "wheel group" that
  38.    restricts who can su to UID 0 accounts.  RMS considers that to
  39.    be fascist.
  40.  
  41.    Options:
  42.    -, -l, --login    Make the subshell a login shell.
  43.             Unset all environment variables except
  44.             TERM, HOME and SHELL (set as above), and USER
  45.             and LOGNAME (set unconditionally as above), and
  46.             set PATH to a default value.
  47.             Change to USER's home directory.
  48.             Prepend "-" to the shell's name.
  49.    -c, --commmand=COMMAND
  50.             Pass COMMAND to the subshell with a -c option
  51.             instead of starting an interactive shell.
  52.    -f, --fast        Pass the -f option to the subshell.
  53.    -m, -p, --preserve-environment
  54.             Do not change HOME, USER, LOGNAME, SHELL.
  55.             Run $SHELL instead of USER's shell from /etc/passwd
  56.             unless not the superuser and USER's shell is
  57.             restricted.
  58.             Overridden by --login and --shell.
  59.    -s, --shell=shell    Run SHELL instead of USER's shell from /etc/passwd
  60.             unless not the superuser and USER's shell is
  61.             restricted.
  62.  
  63.    Compile-time options:
  64.    -DSYSLOG_SUCCESS    Log successful su's (by default, to root) with syslog.
  65.    -DSYSLOG_FAILURE    Log failed su's (by default, to root) with syslog.
  66.  
  67.    -DSYSLOG_NON_ROOT    Log all su's, not just those to root (UID 0).
  68.    Never logs attempted su's to nonexistent accounts.
  69.  
  70.    Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
  71.  
  72. #include <config.h>
  73. #include <stdio.h>
  74. #include <getopt.h>
  75. #include <sys/types.h>
  76. #include <pwd.h>
  77. #include <grp.h>
  78. #include "system.h"
  79.  
  80. #if defined(HAVE_SYSLOG_H) && defined(HAVE_SYSLOG)
  81. #include <syslog.h>
  82. static void log_su ();
  83. #else /* !HAVE_SYSLOG_H */
  84. #ifdef SYSLOG_SUCCESS
  85. #undef SYSLOG_SUCCESS
  86. #endif
  87. #ifdef SYSLOG_FAILURE
  88. #undef SYSLOG_FAILURE
  89. #endif
  90. #ifdef SYSLOG_NON_ROOT
  91. #undef SYSLOG_NON_ROOT
  92. #endif
  93. #endif /* !HAVE_SYSLOG_H */
  94.  
  95. #ifdef _POSIX_VERSION
  96. #include <limits.h>
  97. #ifdef NGROUPS_MAX
  98. #undef NGROUPS_MAX
  99. #endif
  100. #define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
  101. #else /* not _POSIX_VERSION */
  102. struct passwd *getpwuid ();
  103. struct group *getgrgid ();
  104. uid_t getuid ();
  105. #include <sys/param.h>
  106. #if !defined(NGROUPS_MAX) && defined(NGROUPS)
  107. #define NGROUPS_MAX NGROUPS
  108. #endif
  109. #endif /* not _POSIX_VERSION */
  110.  
  111. #ifndef HAVE_ENDGRENT
  112. #define endgrent()
  113. #endif
  114.  
  115. #ifndef HAVE_ENDPWENT
  116. #define endpwent()
  117. #endif
  118.  
  119. #ifdef HAVE_SHADOW_H
  120. #include <shadow.h>
  121. #endif
  122.  
  123. #include "version.h"
  124.  
  125. /* The default PATH for simulated logins to non-superuser accounts.  */
  126. #define DEFAULT_LOGIN_PATH ":/bin"
  127.  
  128. /* The default PATH for simulated logins to superuser accounts.  */
  129. #define DEFAULT_ROOT_LOGIN_PATH "/ade"
  130.  
  131. /* The shell to run if none is given in the user's passwd entry.  */
  132. #define DEFAULT_SHELL "/bin/sh"
  133.  
  134. /* The user to become if none is specified.  */
  135. #define DEFAULT_USER "root"
  136.  
  137. char *crypt ();
  138. char *getpass ();
  139. char *getusershell ();
  140. void endusershell ();
  141. void setusershell ();
  142.  
  143. char *basename ();
  144. char *xmalloc ();
  145. char *xrealloc ();
  146. char *xstrdup ();
  147. void error ();
  148.  
  149. static char *concat ();
  150. static int correct_password ();
  151. static int elements ();
  152. static int restricted_shell ();
  153. static void change_identity ();
  154. static void modify_environment ();
  155. static void run_shell ();
  156. static void usage ();
  157. static void xputenv ();
  158.  
  159. extern char **environ;
  160.  
  161. /* The name this program was run with.  */
  162. char *program_name;
  163.  
  164. /* If non-zero, display usage information and exit.  */
  165. static int show_help;
  166.  
  167. /* If non-zero, print the version on standard output and exit.  */
  168. static int show_version;
  169.  
  170. /* If nonzero, pass the `-f' option to the subshell.  */
  171. static int fast_startup;
  172.  
  173. /* If nonzero, simulate a login instead of just starting a shell.  */
  174. static int simulate_login;
  175.  
  176. /* If nonzero, change some environment vars to indicate the user su'd to.  */
  177. static int change_environment;
  178.  
  179. static struct option const longopts[] =
  180. {
  181.   {"command", required_argument, 0, 'c'},
  182.   {"fast", no_argument, &fast_startup, 1},
  183.   {"help", no_argument, &show_help, 1},
  184.   {"login", no_argument, &simulate_login, 1},
  185.   {"preserve-environment", no_argument, &change_environment, 0},
  186.   {"shell", required_argument, 0, 's'},
  187.   {"version", no_argument, &show_version, 1},
  188.   {0, 0, 0, 0}
  189. };
  190.  
  191. main (argc, argv)
  192.      int argc;
  193.      char **argv;
  194. {
  195.   int optc;
  196.   const char *new_user = DEFAULT_USER;
  197.   char *command = 0;
  198.   char **additional_args = 0;
  199.   char *shell = 0;
  200.   struct passwd *pw;
  201.   struct passwd pw_copy;
  202.  
  203.   program_name = argv[0];
  204.   fast_startup = 0;
  205.   simulate_login = 0;
  206.   change_environment = 1;
  207.  
  208.   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
  209.      != EOF)
  210.     {
  211.       switch (optc)
  212.     {
  213.     case 0:
  214.       break;
  215.  
  216.     case 'c':
  217.       command = optarg;
  218.       break;
  219.  
  220.     case 'f':
  221.       fast_startup = 1;
  222.       break;
  223.  
  224.     case 'l':
  225.       simulate_login = 1;
  226.       break;
  227.  
  228.     case 'm':
  229.     case 'p':
  230.       change_environment = 0;
  231.       break;
  232.  
  233.     case 's':
  234.       shell = optarg;
  235.       break;
  236.  
  237.     default:
  238.       usage (1);
  239.     }
  240.     }
  241.  
  242.   if (show_version)
  243.     {
  244.       printf ("su - %s\n", version_string);
  245.       exit (0);
  246.     }
  247.  
  248.   if (show_help)
  249.     usage (0);
  250.  
  251.   if (optind < argc && !strcmp (argv[optind], "-"))
  252.     {
  253.       simulate_login = 1;
  254.       ++optind;
  255.     }
  256.   if (optind < argc)
  257.     new_user = argv[optind++];
  258.   if (optind < argc)
  259.     additional_args = argv + optind;
  260.  
  261.   pw = getpwnam (new_user);
  262.   if (pw == 0)
  263.     error (1, 0, "user %s does not exist", new_user);
  264.   endpwent ();
  265.  
  266.   /* Make a copy of the password information and point pw at the local
  267.      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
  268.      the static data through the getlogin call from log_su.  */
  269.   pw_copy = *pw;
  270.   pw = &pw_copy;
  271.   pw->pw_name = xstrdup (pw->pw_name);
  272.   pw->pw_dir = xstrdup (pw->pw_dir);
  273.   pw->pw_shell = xstrdup (pw->pw_shell);
  274.  
  275.   if (!correct_password (pw))
  276.     {
  277. #ifdef SYSLOG_FAILURE
  278.       log_su (pw, 0);
  279. #endif
  280.       error (1, 0, "incorrect password");
  281.     }
  282. #ifdef SYSLOG_SUCCESS
  283.   else
  284.     {
  285.       log_su (pw, 1);
  286.     }
  287. #endif
  288.  
  289.   if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
  290.     pw->pw_shell = (char *) DEFAULT_SHELL;
  291.   if (shell == 0 && change_environment == 0)
  292.     shell = getenv ("SHELL");
  293.   if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
  294.     {
  295.       /* The user being su'd to has a nonstandard shell, and so is
  296.      probably a uucp account or has restricted access.  Don't
  297.      compromise the account by allowing access with a standard
  298.      shell.  */
  299.       error (0, 0, "using restricted shell %s", pw->pw_shell);
  300.       shell = 0;
  301.     }
  302.   if (shell == 0)
  303.     {
  304.       shell = xstrdup (pw->pw_shell);
  305.     }
  306.   modify_environment (pw, shell);
  307.  
  308.   change_identity (pw);
  309.   if (simulate_login && chdir (pw->pw_dir))
  310.     error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
  311.  
  312.   run_shell (shell, command, additional_args);
  313. }
  314.  
  315. /* Ask the user for a password.
  316.    Return 1 if the user gives the correct password for entry PW,
  317.    0 if not.  Return 1 without asking for a password if run by UID 0
  318.    or if PW has an empty password.  */
  319.  
  320. static int
  321. correct_password (pw)
  322.      struct passwd *pw;
  323. {
  324.   char *unencrypted, *encrypted, *correct;
  325. #ifdef HAVE_SHADOW_H
  326.   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
  327.   struct spwd *sp = getspnam (pw->pw_name);
  328.  
  329.   endspent ();
  330.   if (sp)
  331.     correct = sp->sp_pwdp;
  332.   else
  333. #endif
  334.   correct = pw->pw_passwd;
  335.  
  336.   if (getuid () == 0 || correct == 0 || correct[0] == '\0')
  337.     return 1;
  338.  
  339.   unencrypted = getpass ("Password:");
  340.   if (unencrypted == NULL)
  341.     {
  342.       error (0, 0, "getpass: cannot open /dev/tty");
  343.       return 0;
  344.     }
  345.   encrypted = crypt (unencrypted, correct);
  346.   bzero (unencrypted, strlen (unencrypted));
  347.   return strcmp (encrypted, correct) == 0;
  348. }
  349.  
  350. /* Update `environ' for the new shell based on PW, with SHELL being
  351.    the value for the SHELL environment variable.  */
  352.  
  353. static void
  354. modify_environment (pw, shell)
  355.      struct passwd *pw;
  356.      char *shell;
  357. {
  358.   char *term;
  359.  
  360.   if (simulate_login)
  361.     {
  362.       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
  363.          Unset all other environment variables.  */
  364.       term = getenv ("TERM");
  365.       environ = (char **) xmalloc (2 * sizeof (char *));
  366.       environ[0] = 0;
  367.       if (term)
  368.     xputenv (concat ("TERM", "=", term));
  369.       xputenv (concat ("HOME", "=", pw->pw_dir));
  370.       xputenv (concat ("SHELL", "=", shell));
  371.       xputenv (concat ("USER", "=", pw->pw_name));
  372.       xputenv (concat ("LOGNAME", "=", pw->pw_name));
  373.       xputenv (concat ("PATH", "=", pw->pw_uid
  374.                ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
  375.     }
  376.   else
  377.     {
  378.       /* Set HOME, SHELL, and if not becoming a super-user,
  379.      USER and LOGNAME.  */
  380.       if (change_environment)
  381.     {
  382.       xputenv (concat ("HOME", "=", pw->pw_dir));
  383.       xputenv (concat ("SHELL", "=", shell));
  384.       if (pw->pw_uid)
  385.         {
  386.           xputenv (concat ("USER", "=", pw->pw_name));
  387.           xputenv (concat ("LOGNAME", "=", pw->pw_name));
  388.         }
  389.     }
  390.     }
  391. }
  392.  
  393. /* Become the user and group(s) specified by PW.  */
  394.  
  395. static void
  396. change_identity (pw)
  397.      struct passwd *pw;
  398. {
  399. #ifdef HAVE_INITGROUPS
  400.   errno = 0;
  401.   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
  402.     error (1, errno, "cannot set groups");
  403.   endgrent ();
  404. #endif
  405.   if (setgid (pw->pw_gid))
  406.     error (1, errno, "cannot set group id");
  407.   if (setuid (pw->pw_uid))
  408.     error (1, errno, "cannot set user id");
  409. }
  410.  
  411. /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
  412.    If COMMAND is nonzero, pass it to the shell with the -c option.
  413.    If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
  414.    arguments.  */
  415.  
  416. static void
  417. run_shell (shell, command, additional_args)
  418.      char *shell;
  419.      char *command;
  420.      char **additional_args;
  421. {
  422.   const char **args;
  423.   int argno = 1;
  424.  
  425.   if (additional_args)
  426.     args = (const char **) xmalloc (sizeof (char *)
  427.                     * (10 + elements (additional_args)));
  428.   else
  429.     args = (const char **) xmalloc (sizeof (char *) * 10);
  430.   if (simulate_login)
  431.     {
  432.       char *arg0;
  433.       char *shell_basename;
  434.  
  435.       shell_basename = basename (shell);
  436.       arg0 = xmalloc (strlen (shell_basename) + 2);
  437.       arg0[0] = '-';
  438.       strcpy (arg0 + 1, shell_basename);
  439.       args[0] = arg0;
  440.     }
  441.   else
  442.     args[0] = basename (shell);
  443.   if (fast_startup)
  444.     args[argno++] = "-f";
  445.   if (command)
  446.     {
  447.       args[argno++] = "-c";
  448.       args[argno++] = command;
  449.     }
  450.   if (additional_args)
  451.     for (; *additional_args; ++additional_args)
  452.       args[argno++] = *additional_args;
  453.   args[argno] = NULL;
  454.   execv (shell, (char **) args);
  455.   error (1, errno, "cannot run %s", shell);
  456. }
  457.  
  458. #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
  459. /* Log the fact that someone has run su to the user given by PW;
  460.    if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
  461.  
  462. static void
  463. log_su (pw, successful)
  464.      struct passwd *pw;
  465.      int successful;
  466. {
  467.   const char *new_user, *old_user, *tty;
  468.  
  469. #ifndef SYSLOG_NON_ROOT
  470.   if (pw->pw_uid)
  471.     return;
  472. #endif
  473.   new_user = pw->pw_name;
  474.   /* The utmp entry (via getlogin) is probably the best way to identify
  475.      the user, especially if someone su's from a su-shell.  */
  476.   old_user = getlogin ();
  477.   if (old_user == NULL)
  478.     old_user = "";
  479.   tty = ttyname (2);
  480.   if (tty == NULL)
  481.     tty = "";
  482.   /* 4.2BSD openlog doesn't have the third parameter.  */
  483.   openlog (basename (program_name), 0
  484. #ifdef LOG_AUTH
  485.        , LOG_AUTH
  486. #endif
  487.        );
  488.   syslog (LOG_NOTICE,
  489. #ifdef SYSLOG_NON_ROOT
  490.       "%s(to %s) %s on %s",
  491. #else
  492.       "%s%s on %s",
  493. #endif
  494.       successful ? "" : "FAILED SU ",
  495. #ifdef SYSLOG_NON_ROOT
  496.       new_user,
  497. #endif
  498.       old_user, tty);
  499.   closelog ();
  500. }
  501. #endif
  502.  
  503. /* Return 1 if SHELL is a restricted shell (one not returned by
  504.    getusershell), else 0, meaning it is a standard shell.  */
  505.  
  506. static int
  507. restricted_shell (shell)
  508.      char *shell;
  509. {
  510.   char *line;
  511.  
  512.   setusershell ();
  513.   while ((line = getusershell ()) != NULL)
  514.     {
  515.       if (*line != '#' && strcmp (line, shell) == 0)
  516.     {
  517.       endusershell ();
  518.       return 0;
  519.     }
  520.     }
  521.   endusershell ();
  522.   return 1;
  523. }
  524.  
  525. /* Return the number of elements in ARR, a null-terminated array.  */
  526.  
  527. static int
  528. elements (arr)
  529.      char **arr;
  530. {
  531.   int n = 0;
  532.  
  533.   for (n = 0; *arr; ++arr)
  534.     ++n;
  535.   return n;
  536. }
  537.  
  538. /* Add VAL to the environment, checking for out of memory errors.  */
  539.  
  540. static void
  541. xputenv (val)
  542.      char *val;
  543. {
  544.   if (putenv (val))
  545.     error (1, 0, "virtual memory exhausted");
  546. }
  547.  
  548. /* Return a newly-allocated string whose contents concatenate
  549.    those of S1, S2, S3.  */
  550.  
  551. static char *
  552. concat (s1, s2, s3)
  553.      char *s1, *s2, *s3;
  554. {
  555.   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
  556.   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
  557.  
  558.   strcpy (result, s1);
  559.   strcpy (result + len1, s2);
  560.   strcpy (result + len1 + len2, s3);
  561.   result[len1 + len2 + len3] = 0;
  562.  
  563.   return result;
  564. }
  565.  
  566. static void
  567. usage (status)
  568.      int status;
  569. {
  570.   if (status != 0)
  571.     fprintf (stderr, "Try `%s --help' for more information.\n",
  572.          program_name);
  573.   else
  574.     {
  575.       printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
  576.       printf ("\
  577. \n\
  578.   -l, --login                  make the shell a login shell\n\
  579.   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
  580.   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
  581.   -m, --preserve-environment   do not reset environment variables\n\
  582.   -p                           same as -m\n\
  583.   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
  584.       --help                   display this help and exit\n\
  585.       --version                output version information and exit\n\
  586. \n\
  587. A mere - implies -l.   If USER not given, assume root.\n\
  588. ");
  589.     }
  590.   exit (status);
  591. }
  592.