]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su.c
make su build as part of util-linux
[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-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 #ifdef USE_PAM
42
43 Actually, with PAM, su has nothing to do with whether or not a
44 wheel group is enforced by su. RMS tries to restrict your access
45 to a su which implements the wheel group, but PAM considers that
46 to be fascist, and gives the user/sysadmin the opportunity to
47 enforce a wheel group by proper editing of /etc/pam.d/su
48
49 #endif
50
51 Compile-time options:
52 -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog.
53 -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog.
54
55 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
56 Never logs attempted su's to nonexistent accounts.
57
58 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
59
60 #ifndef MAX
61 # define MAX(a,b) ((a) > (b) ? (a) : (b))
62 #endif
63
64 /* Exit statuses for programs like 'env' that exec other programs.
65 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
66 enum
67 {
68 EXIT_FAIL = 1,
69 EXIT_CANNOT_INVOKE = 126,
70 EXIT_ENOENT = 127
71 };
72
73 #include <config.h>
74 #include <stdio.h>
75 #include <getopt.h>
76 #include <sys/types.h>
77 #include <pwd.h>
78 #include <grp.h>
79 #ifdef USE_PAM
80 # include <security/pam_appl.h>
81 # include <security/pam_misc.h>
82 # include <signal.h>
83 # include <sys/wait.h>
84 # include <sys/fsuid.h>
85 #endif
86
87 #if HAVE_SYSLOG_H && HAVE_SYSLOG
88 # include <syslog.h>
89 # define SYSLOG_SUCCESS 1
90 # define SYSLOG_FAILURE 1
91 # define SYSLOG_NON_ROOT 1
92 #else
93 # undef SYSLOG_SUCCESS
94 # undef SYSLOG_FAILURE
95 # undef SYSLOG_NON_ROOT
96 #endif
97
98 #if HAVE_SYS_PARAM_H
99 # include <sys/param.h>
100 #endif
101
102 #ifndef HAVE_ENDGRENT
103 # define endgrent() ((void) 0)
104 #endif
105
106 #ifndef HAVE_ENDPWENT
107 # define endpwent() ((void) 0)
108 #endif
109
110 #if HAVE_SHADOW_H
111 # include <shadow.h>
112 #endif
113
114 #include "error.h"
115
116 #include <stdbool.h>
117 #include "xalloc.h"
118 #include "nls.h"
119
120 /* The official name of this program (e.g., no `g' prefix). */
121 #define PROGRAM_NAME "su"
122
123 #define AUTHORS "David MacKenzie"
124
125 #if HAVE_PATHS_H
126 # include <paths.h>
127 #endif
128
129 #include "getdef.h"
130
131 /* The default PATH for simulated logins to non-superuser accounts. */
132 #define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin"
133
134 /* The default PATH for simulated logins to superuser accounts. */
135 #define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin"
136
137 /* The shell to run if none is given in the user's passwd entry. */
138 #define DEFAULT_SHELL "/bin/sh"
139
140 /* The user to become if none is specified. */
141 #define DEFAULT_USER "root"
142
143 #ifndef USE_PAM
144 char *crypt ();
145 #endif
146
147 extern char **environ;
148
149 static void run_shell (char const *, char const *, char **, size_t)
150 __attribute__ ((__noreturn__));
151
152 /* The name this program was run with. */
153 char *program_name;
154
155 /* If true, pass the `-f' option to the subshell. */
156 static bool fast_startup;
157
158 /* If true, simulate a login instead of just starting a shell. */
159 static bool simulate_login;
160
161 /* If true, change some environment vars to indicate the user su'd to. */
162 static bool change_environment;
163
164 /* If true, then don't call setsid() with a command. */
165 int same_session = 0;
166
167 #ifdef USE_PAM
168 static bool _pam_session_opened;
169 static bool _pam_cred_established;
170 #endif
171
172
173 static struct option const longopts[] =
174 {
175 {"command", required_argument, NULL, 'c'},
176 {"session-command", required_argument, NULL, 'C'},
177 {"fast", no_argument, NULL, 'f'},
178 {"login", no_argument, NULL, 'l'},
179 {"preserve-environment", no_argument, NULL, 'p'},
180 {"shell", required_argument, NULL, 's'},
181 {"help", no_argument, 0, 'u'},
182 {"version", no_argument, 0, 'v'},
183 {NULL, 0, NULL, 0}
184 };
185
186 /* Add NAME=VAL to the environment, checking for out of memory errors. */
187
188 static void
189 xsetenv (char const *name, char const *val)
190 {
191 size_t namelen = strlen (name);
192 size_t vallen = strlen (val);
193 char *string = xmalloc (namelen + 1 + vallen + 1);
194 strcpy (string, name);
195 string[namelen] = '=';
196 strcpy (string + namelen + 1, val);
197 if (putenv (string) != 0)
198 error (EXIT_FAILURE, 0, _("out of memory"));
199 }
200
201 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
202 /* Log the fact that someone has run su to the user given by PW;
203 if SUCCESSFUL is true, they gave the correct password, etc. */
204
205 static void
206 log_su (struct passwd const *pw, bool successful)
207 {
208 const char *new_user, *old_user, *tty;
209
210 # ifndef SYSLOG_NON_ROOT
211 if (pw->pw_uid)
212 return;
213 # endif
214 new_user = pw->pw_name;
215 /* The utmp entry (via getlogin) is probably the best way to identify
216 the user, especially if someone su's from a su-shell. */
217 old_user = getlogin ();
218 if (!old_user)
219 {
220 /* getlogin can fail -- usually due to lack of utmp entry.
221 Resort to getpwuid. */
222 struct passwd *pwd = getpwuid (getuid ());
223 old_user = (pwd ? pwd->pw_name : "");
224 }
225 tty = ttyname (STDERR_FILENO);
226 if (!tty)
227 tty = "none";
228 /* 4.2BSD openlog doesn't have the third parameter. */
229 openlog (basename (program_name), 0
230 # ifdef LOG_AUTH
231 , LOG_AUTH
232 # endif
233 );
234 syslog (LOG_NOTICE,
235 # ifdef SYSLOG_NON_ROOT
236 "%s(to %s) %s on %s",
237 # else
238 "%s%s on %s",
239 # endif
240 successful ? "" : "FAILED SU ",
241 # ifdef SYSLOG_NON_ROOT
242 new_user,
243 # endif
244 old_user, tty);
245 closelog ();
246 }
247 #endif
248
249 #ifdef USE_PAM
250 # define PAM_SERVICE_NAME PROGRAM_NAME
251 # define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
252 static sig_atomic_t volatile caught_signal = false;
253 static pam_handle_t *pamh = NULL;
254 static int retval;
255 static struct pam_conv conv =
256 {
257 misc_conv,
258 NULL
259 };
260
261 # define PAM_BAIL_P(a) \
262 if (retval) \
263 { \
264 pam_end (pamh, retval); \
265 a; \
266 }
267
268 static void
269 cleanup_pam (int retcode)
270 {
271 if (_pam_session_opened)
272 pam_close_session (pamh, 0);
273
274 if (_pam_cred_established)
275 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
276
277 pam_end(pamh, retcode);
278 }
279
280 /* Signal handler for parent process. */
281 static void
282 su_catch_sig (int sig)
283 {
284 caught_signal = true;
285 }
286
287 /* Export env variables declared by PAM modules. */
288 static void
289 export_pamenv (void)
290 {
291 char **env;
292
293 /* This is a copy but don't care to free as we exec later anyways. */
294 env = pam_getenvlist (pamh);
295 while (env && *env)
296 {
297 if (putenv (*env) != 0)
298 error (EXIT_FAILURE, 0, _("out of memory"));
299 env++;
300 }
301 }
302
303 static void
304 create_watching_parent (void)
305 {
306 pid_t child;
307 sigset_t ourset;
308 int status = 0;
309
310 retval = pam_open_session (pamh, 0);
311 if (retval != PAM_SUCCESS)
312 {
313 cleanup_pam (retval);
314 error (EXIT_FAILURE, 0, _("cannot not open session: %s"),
315 pam_strerror (pamh, retval));
316 }
317 else
318 _pam_session_opened = 1;
319
320 child = fork ();
321 if (child == (pid_t) -1)
322 {
323 cleanup_pam (PAM_ABORT);
324 error (EXIT_FAILURE, errno, _("cannot create child process"));
325 }
326
327 /* the child proceeds to run the shell */
328 if (child == 0)
329 return;
330
331 /* In the parent watch the child. */
332
333 /* su without pam support does not have a helper that keeps
334 sitting on any directory so let's go to /. */
335 if (chdir ("/") != 0)
336 error (0, errno, _("warning: cannot change directory to %s"), "/");
337
338 sigfillset (&ourset);
339 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
340 {
341 error (0, errno, _("cannot block signals"));
342 caught_signal = true;
343 }
344 if (!caught_signal)
345 {
346 struct sigaction action;
347 action.sa_handler = su_catch_sig;
348 sigemptyset (&action.sa_mask);
349 action.sa_flags = 0;
350 sigemptyset (&ourset);
351 if (!same_session)
352 {
353 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
354 {
355 error (0, errno, _("cannot set signal handler"));
356 caught_signal = true;
357 }
358 }
359 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
360 || sigaddset(&ourset, SIGALRM)
361 || sigaction(SIGTERM, &action, NULL)
362 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
363 error (0, errno, _("cannot set signal handler"));
364 caught_signal = true;
365 }
366 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, NULL)
367 || sigaction(SIGQUIT, &action, NULL)))
368 {
369 error (0, errno, _("cannot set signal handler"));
370 caught_signal = true;
371 }
372 }
373 if (!caught_signal)
374 {
375 pid_t pid;
376 for (;;)
377 {
378 pid = waitpid (child, &status, WUNTRACED);
379
380 if (pid != (pid_t)-1 && WIFSTOPPED (status))
381 {
382 kill (getpid (), SIGSTOP);
383 /* once we get here, we must have resumed */
384 kill (pid, SIGCONT);
385 }
386 else
387 break;
388 }
389 if (pid != (pid_t)-1)
390 if (WIFSIGNALED (status))
391 status = WTERMSIG (status) + 128;
392 else
393 status = WEXITSTATUS (status);
394 else
395 status = 1;
396 }
397 else
398 status = 1;
399
400 if (caught_signal)
401 {
402 fprintf (stderr, _("\nSession terminated, killing shell..."));
403 kill (child, SIGTERM);
404 }
405
406 cleanup_pam (PAM_SUCCESS);
407
408 if (caught_signal)
409 {
410 sleep (2);
411 kill (child, SIGKILL);
412 fprintf (stderr, _(" ...killed.\n"));
413 }
414 exit (status);
415 }
416 #endif
417
418 /* Ask the user for a password.
419 If PAM is in use, let PAM ask for the password if necessary.
420 Return true if the user gives the correct password for entry PW,
421 false if not. Return true without asking for a password if run by UID 0
422 or if PW has an empty password. */
423
424 static bool
425 correct_password (const struct passwd *pw)
426 {
427 #ifdef USE_PAM
428 const struct passwd *lpw;
429 const char *cp;
430
431 retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
432 pw->pw_name, &conv, &pamh);
433 PAM_BAIL_P (return false);
434
435 if (isatty (0) && (cp = ttyname (0)) != NULL)
436 {
437 const char *tty;
438
439 if (strncmp (cp, "/dev/", 5) == 0)
440 tty = cp + 5;
441 else
442 tty = cp;
443 retval = pam_set_item (pamh, PAM_TTY, tty);
444 PAM_BAIL_P (return false);
445 }
446 # if 0 /* Manpage discourages use of getlogin. */
447 cp = getlogin ();
448 if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ()))
449 # endif
450 lpw = getpwuid (getuid ());
451 if (lpw && lpw->pw_name)
452 {
453 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
454 PAM_BAIL_P (return false);
455 }
456 retval = pam_authenticate (pamh, 0);
457 PAM_BAIL_P (return false);
458 retval = pam_acct_mgmt (pamh, 0);
459 if (retval == PAM_NEW_AUTHTOK_REQD)
460 {
461 /* Password has expired. Offer option to change it. */
462 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
463 PAM_BAIL_P (return false);
464 }
465 PAM_BAIL_P (return false);
466 /* Must be authenticated if this point was reached. */
467 return true;
468 #else /* !USE_PAM */
469 char *unencrypted, *encrypted, *correct;
470 # if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
471 /* Shadow passwd stuff for SVR3 and maybe other systems. */
472 const struct spwd *sp = getspnam (pw->pw_name);
473
474 endspent ();
475 if (sp)
476 correct = sp->sp_pwdp;
477 else
478 # endif
479 correct = pw->pw_passwd;
480
481 if (getuid () == 0 || !correct || correct[0] == '\0')
482 return true;
483
484 unencrypted = getpass (_("Password:"));
485 if (!unencrypted)
486 {
487 error (0, 0, _("getpass: cannot open /dev/tty"));
488 return false;
489 }
490 encrypted = crypt (unencrypted, correct);
491 memset (unencrypted, 0, strlen (unencrypted));
492 return !strcmp (encrypted, correct);
493 #endif /* !USE_PAM */
494 }
495
496 /* Add or clear /sbin and /usr/sbin for the su command
497 used without `-'. */
498
499 /* Set if /sbin is found in path. */
500 #define SBIN_MASK 0x01
501 /* Set if /usr/sbin is found in path. */
502 #define USBIN_MASK 0x02
503
504 static char *
505 addsbin (const char *const path)
506 {
507 unsigned char smask = 0;
508 char *ptr, *tmp, *cur, *ret = NULL;
509 size_t len;
510
511 if (!path || *path == 0)
512 return NULL;
513
514 tmp = xstrdup (path);
515 cur = tmp;
516 for (ptr = strsep (&cur, ":"); ptr != NULL; ptr = strsep (&cur, ":"))
517 {
518 if (!strcmp (ptr, "/sbin"))
519 smask |= SBIN_MASK;
520 if (!strcmp (ptr, "/usr/sbin"))
521 smask |= USBIN_MASK;
522 }
523
524 if ((smask & (USBIN_MASK|SBIN_MASK)) == (USBIN_MASK|SBIN_MASK))
525 {
526 free (tmp);
527 return NULL;
528 }
529
530 len = strlen (path);
531 if (!(smask & USBIN_MASK))
532 len += strlen ("/usr/sbin:");
533
534 if (!(smask & SBIN_MASK))
535 len += strlen (":/sbin");
536
537 ret = xmalloc (len + 1);
538 strcpy (tmp, path);
539
540 *ret = 0;
541 cur = tmp;
542 for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
543 {
544 if (!strcmp (ptr, "."))
545 continue;
546 if (*ret)
547 strcat (ret, ":");
548 if (!(smask & USBIN_MASK) && !strcmp (ptr, "/bin"))
549 {
550 strcat (ret, "/usr/sbin:");
551 strcat (ret, ptr);
552 smask |= USBIN_MASK;
553 continue;
554 }
555 if (!(smask & SBIN_MASK) && !strcmp (ptr, "/usr/bin"))
556 {
557 strcat (ret, ptr);
558 strcat (ret, ":/sbin");
559 smask |= SBIN_MASK;
560 continue;
561 }
562 strcat (ret, ptr);
563 }
564 free (tmp);
565
566 if (!(smask & USBIN_MASK))
567 strcat (ret, ":/usr/sbin");
568
569 if (!(smask & SBIN_MASK))
570 strcat (ret, ":/sbin");
571
572 return ret;
573 }
574
575 static char *
576 clearsbin (const char *const path)
577 {
578 char *ptr, *tmp, *cur, *ret = NULL;
579
580 if (!path || *path == 0)
581 return NULL;
582
583 tmp = strdup (path);
584 if (!tmp)
585 return NULL;
586
587 ret = xmalloc (strlen (path) + 1);
588 *ret = 0;
589 cur = tmp;
590 for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
591 {
592 if (!strcmp (ptr, "/sbin"))
593 continue;
594 if (!strcmp (ptr, "/usr/sbin"))
595 continue;
596 if (!strcmp (ptr, "/usr/local/sbin"))
597 continue;
598 if (*ret)
599 strcat (ret, ":");
600 strcat (ret, ptr);
601 }
602 free (tmp);
603
604 return ret;
605 }
606
607 /* Update `environ' for the new shell based on PW, with SHELL being
608 the value for the SHELL environment variable. */
609
610 static void
611 modify_environment (const struct passwd *pw, const char *shell)
612 {
613 if (simulate_login)
614 {
615 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
616 Unset all other environment variables. */
617 char const *term = getenv ("TERM");
618 if (term)
619 term = xstrdup (term);
620 environ = xmalloc ((6 + !!term) * sizeof (char *));
621 environ[0] = NULL;
622 if (term)
623 xsetenv ("TERM", term);
624 xsetenv ("HOME", pw->pw_dir);
625 xsetenv ("SHELL", shell);
626 xsetenv ("USER", pw->pw_name);
627 xsetenv ("LOGNAME", pw->pw_name);
628 xsetenv ("PATH", (pw->pw_uid
629 ? getdef_str ("PATH", DEFAULT_LOGIN_PATH)
630 : getdef_str ("SUPATH", DEFAULT_ROOT_LOGIN_PATH)));
631 }
632 else
633 {
634 /* Set HOME, SHELL, and if not becoming a super-user,
635 USER and LOGNAME. */
636 if (change_environment)
637 {
638 xsetenv ("HOME", pw->pw_dir);
639 xsetenv ("SHELL", shell);
640 if (getdef_bool ("ALWAYS_SET_PATH", 0))
641 xsetenv ("PATH", (pw->pw_uid
642 ? getdef_str ("PATH",
643 DEFAULT_LOGIN_PATH)
644 : getdef_str ("SUPATH",
645 DEFAULT_ROOT_LOGIN_PATH)));
646 else
647 {
648 char const *path = getenv ("PATH");
649 char *new = NULL;
650
651 if (pw->pw_uid)
652 new = clearsbin (path);
653 else
654 new = addsbin (path);
655
656 if (new)
657 {
658 xsetenv ("PATH", new);
659 free (new);
660 }
661 }
662 if (pw->pw_uid)
663 {
664 xsetenv ("USER", pw->pw_name);
665 xsetenv ("LOGNAME", pw->pw_name);
666 }
667 }
668 }
669
670 #ifdef USE_PAM
671 export_pamenv ();
672 #endif
673 }
674
675 /* Become the user and group(s) specified by PW. */
676
677 static void
678 init_groups (const struct passwd *pw)
679 {
680 errno = 0;
681 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
682 {
683 # ifdef USE_PAM
684 cleanup_pam (PAM_ABORT);
685 # endif
686 error (EXIT_FAIL, errno, _("cannot set groups"));
687 }
688 endgrent ();
689
690 #ifdef USE_PAM
691 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
692 if (retval != PAM_SUCCESS)
693 error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval));
694 else
695 _pam_cred_established = 1;
696 #endif
697 }
698
699 static void
700 change_identity (const struct passwd *pw)
701 {
702 if (setgid (pw->pw_gid))
703 error (EXIT_FAIL, errno, _("cannot set group id"));
704 if (setuid (pw->pw_uid))
705 error (EXIT_FAIL, errno, _("cannot set user id"));
706 }
707
708 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
709 If COMMAND is nonzero, pass it to the shell with the -c option.
710 Pass ADDITIONAL_ARGS to the shell as more arguments; there
711 are N_ADDITIONAL_ARGS extra arguments. */
712
713 static void
714 run_shell (char const *shell, char const *command, char **additional_args,
715 size_t n_additional_args)
716 {
717 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
718 char const **args = xcalloc (n_args, sizeof *args);
719 size_t argno = 1;
720
721 if (simulate_login)
722 {
723 char *arg0;
724 char *shell_basename;
725
726 shell_basename = basename (shell);
727 arg0 = xmalloc (strlen (shell_basename) + 2);
728 arg0[0] = '-';
729 strcpy (arg0 + 1, shell_basename);
730 args[0] = arg0;
731 }
732 else
733 args[0] = basename (shell);
734 if (fast_startup)
735 args[argno++] = "-f";
736 if (command)
737 {
738 args[argno++] = "-c";
739 args[argno++] = command;
740 }
741 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
742 args[argno + n_additional_args] = NULL;
743 execv (shell, (char **) args);
744
745 {
746 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
747 error (0, errno, "%s", shell);
748 exit (exit_status);
749 }
750 }
751
752 /* Return true if SHELL is a restricted shell (one not returned by
753 getusershell), else false, meaning it is a standard shell. */
754
755 static bool
756 restricted_shell (const char *shell)
757 {
758 char *line;
759
760 setusershell ();
761 while ((line = getusershell ()) != NULL)
762 {
763 if (*line != '#' && !strcmp (line, shell))
764 {
765 endusershell ();
766 return false;
767 }
768 }
769 endusershell ();
770 return true;
771 }
772
773 void
774 usage (int status)
775 {
776 if (status != EXIT_SUCCESS)
777 fprintf (stderr, _("Try `%s --help' for more information.\n"),
778 program_name);
779 else
780 {
781 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
782 fputs (_("\
783 Change the effective user id and group id to that of USER.\n\
784 \n\
785 -, -l, --login make the shell a login shell\n\
786 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
787 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
788 and do not create a new session\n\
789 -f, --fast pass -f to the shell (for csh or tcsh)\n\
790 -m, --preserve-environment do not reset environment variables\n\
791 -p same as -m\n\
792 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
793 "), stdout);
794 fputs (_(" -u, --help display this help and exit\n"), stdout);
795 fputs (_(" -v, --version output version information and exit\n"), stdout);
796 fputs (_("\
797 \n\
798 A mere - implies -l. If USER not given, assume root.\n\
799 "), stdout);
800 }
801 exit (status);
802 }
803
804 int
805 main (int argc, char **argv)
806 {
807 int optc;
808 const char *new_user = DEFAULT_USER;
809 char *command = NULL;
810 int request_same_session = 0;
811 char *shell = NULL;
812 struct passwd *pw;
813 struct passwd pw_copy;
814
815 program_name = argv[0];
816 setlocale (LC_ALL, "");
817 bindtextdomain (PACKAGE, LOCALEDIR);
818 textdomain (PACKAGE);
819
820 fast_startup = false;
821 simulate_login = false;
822 change_environment = true;
823
824 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
825 {
826 switch (optc)
827 {
828 case 'c':
829 command = optarg;
830 break;
831
832 case 'C':
833 command = optarg;
834 request_same_session = 1;
835 break;
836
837 case 'f':
838 fast_startup = true;
839 break;
840
841 case 'l':
842 simulate_login = true;
843 break;
844
845 case 'm':
846 case 'p':
847 change_environment = false;
848 break;
849
850 case 's':
851 shell = optarg;
852 break;
853
854 case 'u':
855 usage(0);
856
857 case 'v':
858 printf(UTIL_LINUX_VERSION);
859 exit(EXIT_SUCCESS);
860
861 default:
862 usage (EXIT_FAIL);
863 }
864 }
865
866 if (optind < argc && !strcmp (argv[optind], "-"))
867 {
868 simulate_login = true;
869 ++optind;
870 }
871 if (optind < argc)
872 new_user = argv[optind++];
873
874 pw = getpwnam (new_user);
875 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
876 && pw->pw_passwd))
877 error (EXIT_FAIL, 0, _("user %s does not exist"), new_user);
878
879 /* Make a copy of the password information and point pw at the local
880 copy instead. Otherwise, some systems (e.g. Linux) would clobber
881 the static data through the getlogin call from log_su.
882 Also, make sure pw->pw_shell is a nonempty string.
883 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
884 but that doesn't have a default shell listed. */
885 pw_copy = *pw;
886 pw = &pw_copy;
887 pw->pw_name = xstrdup (pw->pw_name);
888 pw->pw_passwd = xstrdup (pw->pw_passwd);
889 pw->pw_dir = xstrdup (pw->pw_dir);
890 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
891 ? pw->pw_shell
892 : DEFAULT_SHELL);
893 endpwent ();
894
895 if (!correct_password (pw))
896 {
897 #ifdef SYSLOG_FAILURE
898 log_su (pw, false);
899 #endif
900 sleep (getdef_num ("FAIL_DELAY", 1));
901 error (EXIT_FAIL, 0, _("incorrect password"));
902 }
903 #ifdef SYSLOG_SUCCESS
904 else
905 {
906 log_su (pw, true);
907 }
908 #endif
909
910 if (request_same_session || !command || !pw->pw_uid)
911 same_session = 1;
912
913 if (!shell && !change_environment)
914 shell = getenv ("SHELL");
915 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
916 {
917 /* The user being su'd to has a nonstandard shell, and so is
918 probably a uucp account or has restricted access. Don't
919 compromise the account by allowing access with a standard
920 shell. */
921 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
922 shell = NULL;
923 }
924 shell = xstrdup (shell ? shell : pw->pw_shell);
925
926 init_groups (pw);
927
928 #ifdef USE_PAM
929 create_watching_parent ();
930 /* Now we're in the child. */
931 #endif
932
933 change_identity (pw);
934 if (!same_session)
935 setsid ();
936
937 /* Set environment after pam_open_session, which may put KRB5CCNAME
938 into the pam_env, etc. */
939
940 modify_environment (pw, shell);
941
942 if (simulate_login && chdir (pw->pw_dir) != 0)
943 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
944
945 run_shell (shell, command, argv + optind, MAX (0, argc - optind));
946 }
947
948 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1