2 * Copyright 1989 - 1994, Julianne Frances Haugh
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 RCSID (PKG_VER "$Id: su.c,v 1.21 2002/01/05 15:41:44 kloczek Exp $")
34 #include <sys/types.h>
38 static const struct pam_conv conv = {
43 static pam_handle_t *pamh = NULL;
46 #include "prototypes.h"
56 * Assorted #defines to control su's behavior
63 /* not needed by sulog.c anymore */
64 static char name[BUFSIZ];
65 static char oldname[BUFSIZ];
72 * External identifiers
75 extern char **newenvp;
76 extern size_t newenvc;
78 extern char **environ;
80 /* local function prototypes */
84 static RETSIGTYPE die (int);
85 static int iswheel (const char *);
88 * die - set or reset termio modes.
90 * die() is called before processing begins. signal() is then called
91 * with die() as the signal handler. If signal later calls die() with a
92 * signal number, the terminal modes are then reset.
95 static RETSIGTYPE die (int killed)
110 static int iswheel (const char *username)
115 if (!grp || !grp->gr_mem)
117 return is_on_list (grp->gr_mem, username);
119 #endif /* !USE_PAM */
122 static void su_failure (const char *tty)
124 sulog (tty, 0, oldname, name); /* log failed attempt */
126 if (getdef_bool ("SYSLOG_SU_ENAB"))
127 SYSLOG ((pwent.pw_uid ? LOG_INFO : LOG_NOTICE,
129 oldname[0] ? oldname : "???",
130 name[0] ? name : "???"));
139 * su - switch user id
141 * su changes the user's ids to the values for the specified user. if
142 * no new user name is specified, "root" is used by default.
144 * The only valid option is a "-" character, which is interpreted as
145 * requiring a new login session to be simulated.
147 * Any additional arguments are passed to the user's shell. In
148 * particular, the argument "-c" will cause the next argument to be
149 * interpreted as a command by the common shell programs.
152 int main (int argc, char **argv)
155 const char *tty = 0; /* Name of tty SU is run from */
160 struct passwd *pw = 0;
161 char **envp = environ;
166 RETSIGTYPE (*oldsig) ();
170 struct spwd *spwd = 0;
175 #endif /* !USE_PAM */
179 setlocale (LC_ALL, "");
180 bindtextdomain (PACKAGE, LOCALEDIR);
181 textdomain (PACKAGE);
184 * Get the program name. The program name is used as a prefix to
185 * most error messages.
188 Prog = Basename (argv[0]);
195 amroot = (my_uid == 0);
198 * Get the tty name. Entries will be logged indicating that the user
199 * tried to change to the named new user from the current terminal.
202 if (isatty (0) && (cp = ttyname (0))) {
203 if (strncmp (cp, "/dev/", 5) == 0)
208 is_console = console (tty);
212 * Be more paranoid, like su from SimplePAMApps. --marekm
216 _("%s: must be run from a terminal\n"),
224 * Process the command line arguments.
228 argv++; /* shift out command name */
230 if (argc > 0 && strcmp (argv[0], "-") == 0) {
233 argv++; /* shift ... */
237 * If a new login is being set up, the old environment will be
238 * ignored and a new one created later on.
242 if ((cp = getdef_str ("ENV_TZ")))
243 addenv (*cp == '/' ? tz (cp) : cp, NULL);
245 * The clock frequency will be reset to the login value if required
247 if ((cp = getdef_str ("ENV_HZ")))
248 addenv (cp, NULL); /* set the default $HZ, if one */
250 * The terminal type will be left alone if it is present in
251 * the environment already.
253 if ((cp = getenv ("TERM")))
257 addenv (*envp++, NULL);
261 * The next argument must be either a user ID, or some flag to a
262 * subshell. Pretty sticky since you can't have an argument which
263 * doesn't start with a "-" unless you specify the new user name.
264 * Any remaining arguments will be passed to the user's login shell.
267 if (argc > 0 && argv[0][0] != '-') {
268 STRFCPY (name, argv[0]); /* use this login id */
270 argv++; /* shift ... */
272 if (!name[0]) /* use default user ID */
273 (void) strcpy (name, "root");
275 doshell = argc == 0; /* any arguments remaining? */
278 * Get the user's real name. The current UID is used to determine
279 * who has executed su. That user ID must exist.
282 pw = get_my_pwent ();
284 SYSLOG ((LOG_CRIT, "Unknown UID: %u", my_uid));
287 STRFCPY (oldname, pw->pw_name);
292 * Sort out the password of user calling su, in case needed later
296 if ((spwd = getspnam (oldname)))
297 pw->pw_passwd = spwd->sp_pwdp;
299 oldpass = xstrdup (pw->pw_passwd);
300 #endif /* SU_ACCESS */
303 ret = pam_start ("su", name, &conv, &pamh);
304 if (ret != PAM_SUCCESS) {
305 SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
306 fprintf (stderr, _("%s: pam_start: error %d\n"),
311 ret = pam_set_item (pamh, PAM_TTY, (const void *) tty);
312 if (ret == PAM_SUCCESS)
314 pam_set_item (pamh, PAM_RUSER, (const void *) oldname);
315 if (ret != PAM_SUCCESS) {
316 SYSLOG ((LOG_ERR, "pam_set_item: %s",
317 pam_strerror (pamh, ret)));
318 fprintf (stderr, "%s: %s\n", Prog,
319 pam_strerror (pamh, ret));
327 * This is the common point for validating a user whose name is
328 * known. It will be reached either by normal processing, or if the
329 * user is to be logged into a subsystem root.
331 * The password file entries for the user is gotten and the account
335 if (!(pw = getpwnam (name))) {
336 (void) fprintf (stderr, _("Unknown id: %s\n"), name);
343 if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0
344 && (spwd = getspnam (name)))
345 pw->pw_passwd = spwd->sp_pwdp;
347 #endif /* !USE_PAM */
352 * BSD systems only allow "wheel" to SU to root. USG systems don't,
353 * so we make this a configurable option.
356 /* The original Shadow 3.3.2 did this differently. Do it like BSD:
358 * - check for uid 0 instead of name "root" - there are systems with
359 * several root accounts under different names,
361 * - check the contents of /etc/group instead of the current group
362 * set (you must be listed as a member, GID 0 is not sufficient).
364 * In addition to this traditional feature, we now have complete su
365 * access control (allow, deny, no password, own password). Thanks
366 * to Chris Evans <lady0110@sable.ox.ac.uk>.
370 if (pwent.pw_uid == 0 && getdef_bool ("SU_WHEEL_ONLY")
371 && !iswheel (oldname)) {
373 _("You are not authorized to su %s\n"),
378 switch (check_su_auth (oldname, name)) {
379 case 0: /* normal su, require target user's password */
381 case 1: /* require no password */
382 pwent.pw_passwd = ""; /* XXX warning: const */
384 case 2: /* require own password */
385 puts (_("(Enter your own password.)"));
386 pwent.pw_passwd = oldpass;
388 default: /* access denied (-1) or unexpected value */
390 _("You are not authorized to su %s\n"),
394 #endif /* SU_ACCESS */
396 #endif /* !USE_PAM */
399 * Set the default shell.
402 if (pwent.pw_shell[0] == '\0')
403 pwent.pw_shell = "/bin/sh"; /* XXX warning: const */
406 ret = pam_authenticate (pamh, 0);
407 if (ret != PAM_SUCCESS) {
408 SYSLOG ((LOG_ERR, "pam_authenticate: %s",
409 pam_strerror (pamh, ret)));
410 fprintf (stderr, "%s: %s\n", Prog,
411 pam_strerror (pamh, ret));
416 ret = pam_acct_mgmt (pamh, 0);
417 if (ret != PAM_SUCCESS) {
419 fprintf (stderr, _("%s: %s\n(Ignored)\n"), Prog,
420 pam_strerror (pamh, ret));
422 SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
423 pam_strerror (pamh, ret)));
424 fprintf (stderr, "%s: %s\n", Prog,
425 pam_strerror (pamh, ret));
432 * Set up a signal handler in case the user types QUIT.
436 oldsig = signal (SIGQUIT, die);
439 * See if the system defined authentication method is being used.
440 * The first character of an administrator defined method is an '@'
444 if (!amroot && pw_auth (pwent.pw_passwd, name, PW_SU, (char *) 0)) {
445 SYSLOG ((pwent.pw_uid ? LOG_NOTICE : LOG_WARN,
446 "Authentication failed for %s", name));
449 signal (SIGQUIT, oldsig);
452 * Check to see if the account is expired. root gets to ignore any
453 * expired accounts, but normal users can't become a user with an
460 spwd = pwd_to_spwd (&pwent);
462 if (isexpired (&pwent, spwd)) {
463 SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
464 "Expired account %s", name));
469 if (pwent.pw_age[0] && isexpired (&pwent)) {
470 SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
471 "Expired account %s", name));
479 * Check to see if the account permits "su". root gets to ignore any
480 * restricted accounts, but normal users can't become a user if
481 * there is a "SU" entry in the /etc/porttime file denying access to
486 if (!isttytime (pwent.pw_name, "SU", time ((time_t *) 0))) {
487 SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
488 "SU by %s to restricted account %s",
493 #endif /* !USE_PAM */
495 signal (SIGINT, SIG_DFL);
496 cp = getdef_str ((pwent.pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
498 addenv (cp ? cp : "PATH=/bin:/usr/bin", NULL);
500 /* XXX very similar code duplicated in libmisc/setupenv.c */
502 addenv ("PATH=/bin:/usr/bin", NULL);
503 } else if (strchr (cp, '=')) {
510 environ = newenvp; /* make new environment active */
512 if (getenv ("IFS")) /* don't export user IFS ... */
513 addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */
515 if (pwent.pw_shell[0] == '*') { /* subsystem root required */
516 pwent.pw_shell++; /* skip the '*' */
517 subsystem (&pwent); /* figure out what to execute */
525 sulog (tty, 1, oldname, name); /* save SU information */
531 if (getdef_bool ("SYSLOG_SU_ENAB"))
532 SYSLOG ((LOG_INFO, "+ %s %s-%s", tty,
533 oldname[0] ? oldname : "???",
534 name[0] ? name : "???"));
538 /* set primary group id and supplementary groups */
539 if (setup_groups (&pwent)) {
540 pam_end (pamh, PAM_ABORT);
545 * pam_setcred() may do things like resource limits, console groups,
546 * and much more, depending on the configured modules
548 ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
549 if (ret != PAM_SUCCESS) {
550 SYSLOG ((LOG_ERR, "pam_setcred: %s",
551 pam_strerror (pamh, ret)));
552 fprintf (stderr, "%s: %s\n", Prog,
553 pam_strerror (pamh, ret));
558 /* become the new user */
559 if (change_uid (&pwent)) {
560 pam_setcred (pamh, PAM_DELETE_CRED);
561 pam_end (pamh, PAM_ABORT);
565 /* now we are done using PAM */
566 pam_end (pamh, PAM_SUCCESS);
569 if (!amroot) /* no limits if su from root */
570 setup_limits (&pwent);
572 if (setup_uid_gid (&pwent, is_console))
574 #endif /* !USE_PAM */
578 #if 1 /* Suggested by Joey Hess. XXX - is this right? */
580 addenv ("HOME", pwent.pw_dir);
584 * This is a workaround for Linux libc bug/feature (?) - the
585 * /dev/log file descriptor is open without the close-on-exec flag
586 * and used to be passed to the new shell. There is "fcntl(LogFile,
587 * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
588 * least in 5.4.33). Why? --marekm
593 * See if the user has extra arguments on the command line. In that
594 * case they will be provided to the new user's shell as arguments.
600 #if 0 /* XXX - GNU su doesn't do this. --marekm */
601 if (!hushed (&pwent)) {
606 cp = getdef_str ("SU_NAME");
608 cp = Basename (pwent.pw_shell);
610 arg0 = xmalloc (strlen (cp) + 2);
612 strcpy (arg0 + 1, cp);
615 cp = Basename (pwent.pw_shell);
620 * Use new user's shell from /etc/passwd and create an argv
621 * with the rest of the command line included.
624 argv[-1] = pwent.pw_shell;
625 (void) execv (pwent.pw_shell, &argv[-1]);
626 (void) fprintf (stderr, _("No shell\n"));
627 SYSLOG ((LOG_WARN, "Cannot execute %s", pwent.pw_shell));
632 shell (pwent.pw_shell, cp);
633 /*NOTREACHED*/ exit (1);