1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992 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>. */
74 #include <sys/types.h>
80 static void log_su ();
88 #ifdef SYSLOG_NON_ROOT
89 #undef SYSLOG_NON_ROOT
98 #define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
99 #else /* not _POSIX_VERSION */
100 struct passwd *getpwuid ();
101 struct group *getgrgid ();
103 #include <sys/param.h>
104 #if !defined(NGROUPS_MAX) && defined(NGROUPS)
105 #define NGROUPS_MAX NGROUPS
107 #endif /* not _POSIX_VERSION */
118 /* The default PATH for simulated logins to non-superuser accounts. */
119 #define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
121 /* The default PATH for simulated logins to superuser accounts. */
122 #define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
124 /* The shell to run if none is given in the user's passwd entry. */
125 #define DEFAULT_SHELL "/bin/sh"
127 /* The user to become if none is specified. */
128 #define DEFAULT_USER "root"
132 char *getusershell ();
133 void endusershell ();
134 void setusershell ();
141 static char *concat ();
142 static int correct_password ();
143 static int elements ();
144 static int restricted_shell ();
145 static void change_identity ();
146 static void modify_environment ();
147 static void run_shell ();
148 static void usage ();
149 static void xputenv ();
151 extern char **environ;
153 /* The name this program was run with. */
156 /* If nonzero, pass the `-f' option to the subshell. */
157 static int fast_startup;
159 /* If nonzero, simulate a login instead of just starting a shell. */
160 static int simulate_login;
162 /* If nonzero, change some environment vars to indicate the user su'd to. */
163 static int change_environment;
165 static struct option const longopts[] =
167 {"command", 1, 0, 'c'},
168 {"fast", 0, &fast_startup, 1},
169 {"login", 0, &simulate_login, 1},
170 {"preserve-environment", 0, &change_environment, 0},
171 {"shell", 1, 0, 's'},
181 char *new_user = DEFAULT_USER;
183 char **additional_args = 0;
187 program_name = argv[0];
190 change_environment = 1;
192 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
210 change_environment = 0;
219 if (optind < argc && !strcmp (argv[optind], "-"))
225 new_user = argv[optind++];
227 additional_args = argv + optind;
229 pw = getpwnam (new_user);
231 error (1, 0, "user %s does not exist", new_user);
233 if (!correct_password (pw))
235 #ifdef SYSLOG_FAILURE
238 error (1, 0, "incorrect password");
240 #ifdef SYSLOG_SUCCESS
247 if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
248 pw->pw_shell = DEFAULT_SHELL;
249 if (shell == 0 && change_environment == 0)
250 shell = getenv ("SHELL");
251 if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
253 /* The user being su'd to has a nonstandard shell, and so is
254 probably a uucp account or has restricted access. Don't
255 compromise the account by allowing access with a standard
257 error (0, 0, "using restricted shell %s", pw->pw_shell);
261 shell = pw->pw_shell;
262 shell = strcpy (xmalloc (strlen (shell) + 1), shell);
263 modify_environment (pw, shell);
265 change_identity (pw);
266 if (simulate_login && chdir (pw->pw_dir))
267 error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
268 run_shell (shell, command, additional_args);
271 /* Ask the user for a password.
272 Return 1 if the user gives the correct password for entry PW,
273 0 if not. Return 1 without asking for a password if run by UID 0
274 or if PW has an empty password. */
277 correct_password (pw)
280 char *unencrypted, *encrypted, *correct;
282 /* Shadow passwd stuff for SVR3 and maybe other systems. */
283 struct spwd *sp = getspnam (pw->pw_name);
287 correct = sp->sp_pwdp;
290 correct = pw->pw_passwd;
292 if (getuid () == 0 || correct == 0 || correct[0] == '\0')
295 unencrypted = getpass ("Password:");
296 encrypted = crypt (unencrypted, correct);
297 bzero (unencrypted, strlen (unencrypted));
298 return strcmp (encrypted, correct) == 0;
301 /* Update `environ' for the new shell based on PW, with SHELL being
302 the value for the SHELL environment variable. */
305 modify_environment (pw, shell)
313 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
314 Unset all other environment variables. */
315 term = getenv ("TERM");
316 environ = (char **) xmalloc (2 * sizeof (char *));
319 xputenv (concat ("TERM", "=", term));
320 xputenv (concat ("HOME", "=", pw->pw_dir));
321 xputenv (concat ("SHELL", "=", shell));
322 xputenv (concat ("USER", "=", pw->pw_name));
323 xputenv (concat ("LOGNAME", "=", pw->pw_name));
324 xputenv (concat ("PATH", "=", pw->pw_uid
325 ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
329 /* Set HOME, SHELL, and if not becoming a super-user,
331 if (change_environment)
333 xputenv (concat ("HOME", "=", pw->pw_dir));
334 xputenv (concat ("SHELL", "=", shell));
337 xputenv (concat ("USER", "=", pw->pw_name));
338 xputenv (concat ("LOGNAME", "=", pw->pw_name));
344 /* Become the user and group(s) specified by PW. */
352 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
353 error (1, errno, "cannot set groups");
356 if (setgid (pw->pw_gid))
357 error (1, errno, "cannot set group id");
358 if (setuid (pw->pw_uid))
359 error (1, errno, "cannot set user id");
362 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
363 If COMMAND is nonzero, pass it to the shell with the -c option.
364 If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
368 run_shell (shell, command, additional_args)
371 char **additional_args;
377 args = (char **) xmalloc (sizeof (char *)
378 * (10 + elements (additional_args)));
380 args = (char **) xmalloc (sizeof (char *) * 10);
383 args[0] = xmalloc (strlen (shell) + 2);
385 strcpy (args[0] + 1, basename (shell));
388 args[0] = basename (shell);
390 args[argno++] = "-f";
393 args[argno++] = "-c";
394 args[argno++] = command;
397 for (; *additional_args; ++additional_args)
398 args[argno++] = *additional_args;
401 error (1, errno, "cannot run %s", shell);
404 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
405 /* Log the fact that someone has run su to the user given by PW;
406 if SUCCESSFUL is nonzero, they gave the correct password, etc. */
409 log_su (pw, successful)
413 char *new_user, *old_user, *tty;
415 #ifndef SYSLOG_NON_ROOT
419 new_user = pw->pw_name;
420 /* The utmp entry (via getlogin) is probably the best way to identify
421 the user, especially if someone su's from a su-shell. */
422 old_user = getlogin ();
428 /* 4.2BSD openlog doesn't have the third parameter. */
429 openlog (basename (program_name), 0
435 #ifdef SYSLOG_NON_ROOT
436 "%s(to %s) %s on %s",
440 successful ? "" : "FAILED SU ",
441 #ifdef SYSLOG_NON_ROOT
449 /* Return 1 if SHELL is a restricted shell (one not returned by
450 getusershell), else 0, meaning it is a standard shell. */
453 restricted_shell (shell)
459 while ((line = getusershell ()) != NULL)
461 if (*line != '#' && strcmp (line, shell) == 0)
471 /* Return the number of elements in ARR, a null-terminated array. */
479 for (n = 0; *arr; ++arr)
484 /* Add VAL to the environment, checking for out of memory errors. */
491 error (1, 0, "virtual memory exhausted");
494 /* Return a newly-allocated string whose contents concatenate
495 those of S1, S2, S3. */
501 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
502 char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
505 strcpy (result + len1, s2);
506 strcpy (result + len1 + len2, s3);
507 result[len1 + len2 + len3] = 0;
516 Usage: %s [-flmp] [-c command] [-s shell] [--login] [--fast]\n\
517 [--preserve-environment] [--command=command] [--shell=shell] [-]\n\
518 [user [arg...]]\n", program_name);