]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su.c
(usage): Use EXIT_SUCCESS, not 0, for clarity.
[thirdparty/util-linux.git] / login-utils / su.c
1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2004 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 Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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
79 /* Hide any system prototype for getusershell.
80 This is necessary because some Cray systems have a conflicting
81 prototype (returning `int') in <unistd.h>. */
82 #define getusershell _getusershell_sys_proto_
83
84 #include "system.h"
85 #include "dirname.h"
86
87 #undef getusershell
88
89 #if HAVE_SYSLOG_H && HAVE_SYSLOG
90 # include <syslog.h>
91 #else
92 # undef SYSLOG_SUCCESS
93 # undef SYSLOG_FAILURE
94 # undef SYSLOG_NON_ROOT
95 #endif
96
97 #if HAVE_SYS_PARAM_H
98 # include <sys/param.h>
99 #endif
100
101 #ifndef HAVE_ENDGRENT
102 # define endgrent() ((void) 0)
103 #endif
104
105 #ifndef HAVE_ENDPWENT
106 # define endpwent() ((void) 0)
107 #endif
108
109 #if HAVE_SHADOW_H
110 # include <shadow.h>
111 #endif
112
113 #include "error.h"
114
115 /* The official name of this program (e.g., no `g' prefix). */
116 #define PROGRAM_NAME "su"
117
118 #define AUTHORS "David MacKenzie"
119
120 #if HAVE_PATHS_H
121 # include <paths.h>
122 #endif
123
124 /* The default PATH for simulated logins to non-superuser accounts. */
125 #ifdef _PATH_DEFPATH
126 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
127 #else
128 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
129 #endif
130
131 /* The default PATH for simulated logins to superuser accounts. */
132 #ifdef _PATH_DEFPATH_ROOT
133 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
134 #else
135 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
136 #endif
137
138 /* The shell to run if none is given in the user's passwd entry. */
139 #define DEFAULT_SHELL "/bin/sh"
140
141 /* The user to become if none is specified. */
142 #define DEFAULT_USER "root"
143
144 char *crypt ();
145 char *getpass ();
146 char *getusershell ();
147 void endusershell ();
148 void setusershell ();
149
150 extern char **environ;
151
152 static void run_shell (const char *, const char *, char **)
153 ATTRIBUTE_NORETURN;
154
155 /* The name this program was run with. */
156 char *program_name;
157
158 /* If nonzero, pass the `-f' option to the subshell. */
159 static int fast_startup;
160
161 /* If nonzero, simulate a login instead of just starting a shell. */
162 static int simulate_login;
163
164 /* If nonzero, change some environment vars to indicate the user su'd to. */
165 static int change_environment;
166
167 static struct option const longopts[] =
168 {
169 {"command", required_argument, 0, 'c'},
170 {"fast", no_argument, NULL, 'f'},
171 {"login", no_argument, NULL, 'l'},
172 {"preserve-environment", no_argument, &change_environment, 0},
173 {"shell", required_argument, 0, 's'},
174 {GETOPT_HELP_OPTION_DECL},
175 {GETOPT_VERSION_OPTION_DECL},
176 {0, 0, 0, 0}
177 };
178
179 /* Add VAL to the environment, checking for out of memory errors. */
180
181 static void
182 xputenv (char *val)
183 {
184 if (putenv (val))
185 xalloc_die ();
186 }
187
188 /* Return a newly-allocated string whose contents concatenate
189 those of S1, S2, S3. */
190
191 static char *
192 concat (const char *s1, const char *s2, const char *s3)
193 {
194 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
195 char *result = xmalloc (len1 + len2 + len3 + 1);
196
197 strcpy (result, s1);
198 strcpy (result + len1, s2);
199 strcpy (result + len1 + len2, s3);
200 result[len1 + len2 + len3] = 0;
201
202 return result;
203 }
204
205 /* Return the number of elements in ARR, a null-terminated array. */
206
207 static int
208 elements (char **arr)
209 {
210 int n = 0;
211
212 for (n = 0; *arr; ++arr)
213 ++n;
214 return n;
215 }
216
217 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
218 /* Log the fact that someone has run su to the user given by PW;
219 if SUCCESSFUL is nonzero, they gave the correct password, etc. */
220
221 static void
222 log_su (const struct passwd *pw, int successful)
223 {
224 const char *new_user, *old_user, *tty;
225
226 # ifndef SYSLOG_NON_ROOT
227 if (pw->pw_uid)
228 return;
229 # endif
230 new_user = pw->pw_name;
231 /* The utmp entry (via getlogin) is probably the best way to identify
232 the user, especially if someone su's from a su-shell. */
233 old_user = getlogin ();
234 if (old_user == NULL)
235 {
236 /* getlogin can fail -- usually due to lack of utmp entry.
237 Resort to getpwuid. */
238 struct passwd *pwd = getpwuid (getuid ());
239 old_user = (pwd ? pwd->pw_name : "");
240 }
241 tty = ttyname (2);
242 if (tty == NULL)
243 tty = "none";
244 /* 4.2BSD openlog doesn't have the third parameter. */
245 openlog (base_name (program_name), 0
246 # ifdef LOG_AUTH
247 , LOG_AUTH
248 # endif
249 );
250 syslog (LOG_NOTICE,
251 # ifdef SYSLOG_NON_ROOT
252 "%s(to %s) %s on %s",
253 # else
254 "%s%s on %s",
255 # endif
256 successful ? "" : "FAILED SU ",
257 # ifdef SYSLOG_NON_ROOT
258 new_user,
259 # endif
260 old_user, tty);
261 closelog ();
262 }
263 #endif
264
265 /* Ask the user for a password.
266 Return 1 if the user gives the correct password for entry PW,
267 0 if not. Return 1 without asking for a password if run by UID 0
268 or if PW has an empty password. */
269
270 static int
271 correct_password (const struct passwd *pw)
272 {
273 char *unencrypted, *encrypted, *correct;
274 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
275 /* Shadow passwd stuff for SVR3 and maybe other systems. */
276 struct spwd *sp = getspnam (pw->pw_name);
277
278 endspent ();
279 if (sp)
280 correct = sp->sp_pwdp;
281 else
282 #endif
283 correct = pw->pw_passwd;
284
285 if (getuid () == 0 || correct == 0 || correct[0] == '\0')
286 return 1;
287
288 unencrypted = getpass (_("Password:"));
289 if (unencrypted == NULL)
290 {
291 error (0, 0, _("getpass: cannot open /dev/tty"));
292 return 0;
293 }
294 encrypted = crypt (unencrypted, correct);
295 memset (unencrypted, 0, strlen (unencrypted));
296 return strcmp (encrypted, correct) == 0;
297 }
298
299 /* Update `environ' for the new shell based on PW, with SHELL being
300 the value for the SHELL environment variable. */
301
302 static void
303 modify_environment (const struct passwd *pw, const char *shell)
304 {
305 char *term;
306
307 if (simulate_login)
308 {
309 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
310 Unset all other environment variables. */
311 term = getenv ("TERM");
312 environ = xmalloc (2 * sizeof (char *));
313 environ[0] = 0;
314 if (term)
315 xputenv (concat ("TERM", "=", term));
316 xputenv (concat ("HOME", "=", pw->pw_dir));
317 xputenv (concat ("SHELL", "=", shell));
318 xputenv (concat ("USER", "=", pw->pw_name));
319 xputenv (concat ("LOGNAME", "=", pw->pw_name));
320 xputenv (concat ("PATH", "=", (pw->pw_uid
321 ? DEFAULT_LOGIN_PATH
322 : DEFAULT_ROOT_LOGIN_PATH)));
323 }
324 else
325 {
326 /* Set HOME, SHELL, and if not becoming a super-user,
327 USER and LOGNAME. */
328 if (change_environment)
329 {
330 xputenv (concat ("HOME", "=", pw->pw_dir));
331 xputenv (concat ("SHELL", "=", shell));
332 if (pw->pw_uid)
333 {
334 xputenv (concat ("USER", "=", pw->pw_name));
335 xputenv (concat ("LOGNAME", "=", pw->pw_name));
336 }
337 }
338 }
339 }
340
341 /* Become the user and group(s) specified by PW. */
342
343 static void
344 change_identity (const struct passwd *pw)
345 {
346 #ifdef HAVE_INITGROUPS
347 errno = 0;
348 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
349 error (EXIT_FAIL, errno, _("cannot set groups"));
350 endgrent ();
351 #endif
352 if (setgid (pw->pw_gid))
353 error (EXIT_FAIL, errno, _("cannot set group id"));
354 if (setuid (pw->pw_uid))
355 error (EXIT_FAIL, errno, _("cannot set user id"));
356 }
357
358 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
359 If COMMAND is nonzero, pass it to the shell with the -c option.
360 If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
361 arguments. */
362
363 static void
364 run_shell (const char *shell, const char *command, char **additional_args)
365 {
366 const char **args;
367 int argno = 1;
368
369 if (additional_args)
370 args = xmalloc (sizeof (char *)
371 * (10 + elements (additional_args)));
372 else
373 args = xmalloc (sizeof (char *) * 10);
374 if (simulate_login)
375 {
376 char *arg0;
377 char *shell_basename;
378
379 shell_basename = base_name (shell);
380 arg0 = xmalloc (strlen (shell_basename) + 2);
381 arg0[0] = '-';
382 strcpy (arg0 + 1, shell_basename);
383 args[0] = arg0;
384 }
385 else
386 args[0] = base_name (shell);
387 if (fast_startup)
388 args[argno++] = "-f";
389 if (command)
390 {
391 args[argno++] = "-c";
392 args[argno++] = command;
393 }
394 if (additional_args)
395 for (; *additional_args; ++additional_args)
396 args[argno++] = *additional_args;
397 args[argno] = NULL;
398 execv (shell, (char **) args);
399
400 {
401 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
402 error (0, errno, "%s", shell);
403 exit (exit_status);
404 }
405 }
406
407 /* Return 1 if SHELL is a restricted shell (one not returned by
408 getusershell), else 0, meaning it is a standard shell. */
409
410 static int
411 restricted_shell (const char *shell)
412 {
413 char *line;
414
415 setusershell ();
416 while ((line = getusershell ()) != NULL)
417 {
418 if (*line != '#' && strcmp (line, shell) == 0)
419 {
420 endusershell ();
421 return 0;
422 }
423 }
424 endusershell ();
425 return 1;
426 }
427
428 void
429 usage (int status)
430 {
431 if (status != EXIT_SUCCESS)
432 fprintf (stderr, _("Try `%s --help' for more information.\n"),
433 program_name);
434 else
435 {
436 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
437 fputs (_("\
438 Change the effective user id and group id to that of USER.\n\
439 \n\
440 -, -l, --login make the shell a login shell\n\
441 -c, --commmand=COMMAND pass a single COMMAND to the shell with -c\n\
442 -f, --fast pass -f to the shell (for csh or tcsh)\n\
443 -m, --preserve-environment do not reset environment variables\n\
444 -p same as -m\n\
445 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
446 "), stdout);
447 fputs (HELP_OPTION_DESCRIPTION, stdout);
448 fputs (VERSION_OPTION_DESCRIPTION, stdout);
449 fputs (_("\
450 \n\
451 A mere - implies -l. If USER not given, assume root.\n\
452 "), stdout);
453 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
454 }
455 exit (status);
456 }
457
458 int
459 main (int argc, char **argv)
460 {
461 int optc;
462 const char *new_user = DEFAULT_USER;
463 char *command = 0;
464 char **additional_args = 0;
465 char *shell = 0;
466 struct passwd *pw;
467 struct passwd pw_copy;
468
469 initialize_main (&argc, &argv);
470 program_name = argv[0];
471 setlocale (LC_ALL, "");
472 bindtextdomain (PACKAGE, LOCALEDIR);
473 textdomain (PACKAGE);
474
475 initialize_exit_failure (EXIT_FAIL);
476 atexit (close_stdout);
477
478 fast_startup = 0;
479 simulate_login = 0;
480 change_environment = 1;
481
482 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
483 {
484 switch (optc)
485 {
486 case 0:
487 break;
488
489 case 'c':
490 command = optarg;
491 break;
492
493 case 'f':
494 fast_startup = 1;
495 break;
496
497 case 'l':
498 simulate_login = 1;
499 break;
500
501 case 'm':
502 case 'p':
503 change_environment = 0;
504 break;
505
506 case 's':
507 shell = optarg;
508 break;
509
510 case_GETOPT_HELP_CHAR;
511
512 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
513
514 default:
515 usage (EXIT_FAIL);
516 }
517 }
518
519 if (optind < argc && !strcmp (argv[optind], "-"))
520 {
521 simulate_login = 1;
522 ++optind;
523 }
524 if (optind < argc)
525 new_user = argv[optind++];
526 if (optind < argc)
527 additional_args = argv + optind;
528
529 pw = getpwnam (new_user);
530 if (pw == 0)
531 error (EXIT_FAIL, 0, _("user %s does not exist"), new_user);
532 endpwent ();
533
534 /* Make sure pw->pw_shell is non-NULL. It may be NULL when NEW_USER
535 is a username that is retrieved via NIS (YP), but that doesn't have
536 a default shell listed. */
537 if (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
538 pw->pw_shell = (char *) DEFAULT_SHELL;
539
540 /* Make a copy of the password information and point pw at the local
541 copy instead. Otherwise, some systems (e.g. Linux) would clobber
542 the static data through the getlogin call from log_su. */
543 pw_copy = *pw;
544 pw = &pw_copy;
545 pw->pw_name = xstrdup (pw->pw_name);
546 pw->pw_dir = xstrdup (pw->pw_dir);
547 pw->pw_shell = xstrdup (pw->pw_shell);
548
549 if (!correct_password (pw))
550 {
551 #ifdef SYSLOG_FAILURE
552 log_su (pw, 0);
553 #endif
554 error (EXIT_FAIL, 0, _("incorrect password"));
555 }
556 #ifdef SYSLOG_SUCCESS
557 else
558 {
559 log_su (pw, 1);
560 }
561 #endif
562
563 if (shell == 0 && change_environment == 0)
564 shell = getenv ("SHELL");
565 if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
566 {
567 /* The user being su'd to has a nonstandard shell, and so is
568 probably a uucp account or has restricted access. Don't
569 compromise the account by allowing access with a standard
570 shell. */
571 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
572 shell = 0;
573 }
574 if (shell == 0)
575 {
576 shell = xstrdup (pw->pw_shell);
577 }
578 modify_environment (pw, shell);
579
580 change_identity (pw);
581 if (simulate_login && chdir (pw->pw_dir))
582 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
583
584 run_shell (shell, command, additional_args);
585 }