1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 92, 93, 1994 Free Software Foundation, Inc.
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)
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.
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. */
18 /* Run a shell with the real and effective UID and GID and groups
19 of USER, default `root'.
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.
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.
30 If one or more ARGs are given, they are passed as additional
31 arguments to the subshell.
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.
37 This program intentionally does not support a "wheel group" that
38 restricts who can su to UID 0 accounts. RMS considers that to
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
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
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.
67 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
68 Never logs attempted su's to nonexistent accounts.
70 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
73 #if defined (CONFIG_BROKETS)
74 /* We use <config.h> instead of "config.h" so that a compilation
75 using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
76 (which it would do because it found this file in $srcdir). */
85 #include <sys/types.h>
92 static void log_su ();
93 #else /* !HAVE_SYSLOG_H */
100 #ifdef SYSLOG_NON_ROOT
101 #undef SYSLOG_NON_ROOT
103 #endif /* !HAVE_SYSLOG_H */
105 #ifdef _POSIX_VERSION
110 #define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
111 #else /* not _POSIX_VERSION */
112 struct passwd *getpwuid ();
113 struct group *getgrgid ();
115 #include <sys/param.h>
116 #if !defined(NGROUPS_MAX) && defined(NGROUPS)
117 #define NGROUPS_MAX NGROUPS
119 #endif /* not _POSIX_VERSION */
132 /* The default PATH for simulated logins to non-superuser accounts. */
133 #define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
135 /* The default PATH for simulated logins to superuser accounts. */
136 #define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
138 /* The shell to run if none is given in the user's passwd entry. */
139 #define DEFAULT_SHELL "/bin/sh"
141 /* The user to become if none is specified. */
142 #define DEFAULT_USER "root"
146 char *getusershell ();
147 void endusershell ();
148 void setusershell ();
155 static char *concat ();
156 static int correct_password ();
157 static int elements ();
158 static int restricted_shell ();
159 static void change_identity ();
160 static void modify_environment ();
161 static void run_shell ();
162 static void usage ();
163 static void xputenv ();
165 extern char **environ;
167 /* The name this program was run with. */
170 /* If non-zero, display usage information and exit. */
171 static int show_help;
173 /* If non-zero, print the version on standard output and exit. */
174 static int show_version;
176 /* If nonzero, pass the `-f' option to the subshell. */
177 static int fast_startup;
179 /* If nonzero, simulate a login instead of just starting a shell. */
180 static int simulate_login;
182 /* If nonzero, change some environment vars to indicate the user su'd to. */
183 static int change_environment;
185 static struct option const longopts[] =
187 {"command", required_argument, 0, 'c'},
188 {"fast", no_argument, &fast_startup, 1},
189 {"help", no_argument, &show_help, 1},
190 {"login", no_argument, &simulate_login, 1},
191 {"preserve-environment", no_argument, &change_environment, 0},
192 {"shell", required_argument, 0, 's'},
193 {"version", no_argument, &show_version, 1},
203 char *new_user = DEFAULT_USER;
205 char **additional_args = 0;
209 program_name = argv[0];
212 change_environment = 1;
214 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
236 change_environment = 0;
250 printf ("%s\n", version_string);
257 if (optind < argc && !strcmp (argv[optind], "-"))
263 new_user = argv[optind++];
265 additional_args = argv + optind;
267 pw = getpwnam (new_user);
269 error (1, 0, "user %s does not exist", new_user);
271 if (!correct_password (pw))
273 #ifdef SYSLOG_FAILURE
276 error (1, 0, "incorrect password");
278 #ifdef SYSLOG_SUCCESS
285 if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
286 pw->pw_shell = DEFAULT_SHELL;
287 if (shell == 0 && change_environment == 0)
288 shell = getenv ("SHELL");
289 if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
291 /* The user being su'd to has a nonstandard shell, and so is
292 probably a uucp account or has restricted access. Don't
293 compromise the account by allowing access with a standard
295 error (0, 0, "using restricted shell %s", pw->pw_shell);
299 shell = pw->pw_shell;
300 shell = strcpy (xmalloc (strlen (shell) + 1), shell);
301 modify_environment (pw, shell);
303 change_identity (pw);
304 if (simulate_login && chdir (pw->pw_dir))
305 error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
306 run_shell (shell, command, additional_args);
309 /* Ask the user for a password.
310 Return 1 if the user gives the correct password for entry PW,
311 0 if not. Return 1 without asking for a password if run by UID 0
312 or if PW has an empty password. */
315 correct_password (pw)
318 char *unencrypted, *encrypted, *correct;
320 /* Shadow passwd stuff for SVR3 and maybe other systems. */
321 struct spwd *sp = getspnam (pw->pw_name);
325 correct = sp->sp_pwdp;
328 correct = pw->pw_passwd;
330 if (getuid () == 0 || correct == 0 || correct[0] == '\0')
333 unencrypted = getpass ("Password:");
334 if (unencrypted == NULL)
336 error (0, 0, "getpass: cannot open /dev/tty");
339 encrypted = crypt (unencrypted, correct);
340 bzero (unencrypted, strlen (unencrypted));
341 return strcmp (encrypted, correct) == 0;
344 /* Update `environ' for the new shell based on PW, with SHELL being
345 the value for the SHELL environment variable. */
348 modify_environment (pw, shell)
356 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
357 Unset all other environment variables. */
358 term = getenv ("TERM");
359 environ = (char **) xmalloc (2 * sizeof (char *));
362 xputenv (concat ("TERM", "=", term));
363 xputenv (concat ("HOME", "=", pw->pw_dir));
364 xputenv (concat ("SHELL", "=", shell));
365 xputenv (concat ("USER", "=", pw->pw_name));
366 xputenv (concat ("LOGNAME", "=", pw->pw_name));
367 xputenv (concat ("PATH", "=", pw->pw_uid
368 ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
372 /* Set HOME, SHELL, and if not becoming a super-user,
374 if (change_environment)
376 xputenv (concat ("HOME", "=", pw->pw_dir));
377 xputenv (concat ("SHELL", "=", shell));
380 xputenv (concat ("USER", "=", pw->pw_name));
381 xputenv (concat ("LOGNAME", "=", pw->pw_name));
387 /* Become the user and group(s) specified by PW. */
395 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
396 error (1, errno, "cannot set groups");
399 if (setgid (pw->pw_gid))
400 error (1, errno, "cannot set group id");
401 if (setuid (pw->pw_uid))
402 error (1, errno, "cannot set user id");
405 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
406 If COMMAND is nonzero, pass it to the shell with the -c option.
407 If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
411 run_shell (shell, command, additional_args)
414 char **additional_args;
420 args = (char **) xmalloc (sizeof (char *)
421 * (10 + elements (additional_args)));
423 args = (char **) xmalloc (sizeof (char *) * 10);
426 args[0] = xmalloc (strlen (shell) + 2);
428 strcpy (args[0] + 1, basename (shell));
431 args[0] = basename (shell);
433 args[argno++] = "-f";
436 args[argno++] = "-c";
437 args[argno++] = command;
440 for (; *additional_args; ++additional_args)
441 args[argno++] = *additional_args;
444 error (1, errno, "cannot run %s", shell);
447 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
448 /* Log the fact that someone has run su to the user given by PW;
449 if SUCCESSFUL is nonzero, they gave the correct password, etc. */
452 log_su (pw, successful)
456 char *new_user, *old_user, *tty;
458 #ifndef SYSLOG_NON_ROOT
462 new_user = pw->pw_name;
463 /* The utmp entry (via getlogin) is probably the best way to identify
464 the user, especially if someone su's from a su-shell. */
465 old_user = getlogin ();
471 /* 4.2BSD openlog doesn't have the third parameter. */
472 openlog (basename (program_name), 0
478 #ifdef SYSLOG_NON_ROOT
479 "%s(to %s) %s on %s",
483 successful ? "" : "FAILED SU ",
484 #ifdef SYSLOG_NON_ROOT
492 /* Return 1 if SHELL is a restricted shell (one not returned by
493 getusershell), else 0, meaning it is a standard shell. */
496 restricted_shell (shell)
502 while ((line = getusershell ()) != NULL)
504 if (*line != '#' && strcmp (line, shell) == 0)
514 /* Return the number of elements in ARR, a null-terminated array. */
522 for (n = 0; *arr; ++arr)
527 /* Add VAL to the environment, checking for out of memory errors. */
534 error (1, 0, "virtual memory exhausted");
537 /* Return a newly-allocated string whose contents concatenate
538 those of S1, S2, S3. */
544 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
545 char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
548 strcpy (result + len1, s2);
549 strcpy (result + len1 + len2, s3);
550 result[len1 + len2 + len3] = 0;
560 fprintf (stderr, "Try `%s --help' for more information.\n",
564 printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
567 -l, --login make the shell a login shell\n\
568 -c, --commmand=COMMAND pass a single COMMAND to the shell with -c\n\
569 -f, --fast pass -f to the shell (for csh or tcsh)\n\
570 -m, --preserve-environment do not reset environment variables\n\
572 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
573 --help display this help and exit\n\
574 --version output version information and exit\n\
576 A mere - implies -l. If USER not given, assume root.\n\