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