* Copyright (c) 1989 - 1994, Julianne Frances Haugh
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
* Copyright (c) 2000 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2009, Nicolas François
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#include <sys/types.h>
#include "prototypes.h"
#include "defines.h"
-#include "exitcodes.h"
#include "pwauth.h"
#include "getdef.h"
#ifdef USE_PAM
#include "pam_defs.h"
-#endif
+#endif /* USE_PAM */
+/*@-exitarg@*/
+#include "exitcodes.h"
+
/*
* Assorted #defines to control su's behavior
*/
/*
* Global variables
*/
+char *Prog;
+/* PID of the child, in case it needs to be killed */
+static pid_t pid_child = 0;
+
/* not needed by sulog.c anymore */
static char name[BUFSIZ];
static char oldname[BUFSIZ];
static bool caught = false;
#endif
-static char *Prog;
extern struct passwd pwent;
/*
/* local function prototypes */
+static void execve_shell (const char *shellstr,
+ char *args[],
+ char *const envp[]);
+static RETSIGTYPE kill_child (int s);
#ifndef USE_PAM
-
static RETSIGTYPE die (int);
static int iswheel (const char *);
+#endif /* !USE_PAM */
+#ifndef USE_PAM
/*
* die - set or reset termio modes.
*
if (killed) {
closelog ();
- exit (killed);
+ exit (128+killed);
}
}
struct group *grp;
grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
- if (!grp || !grp->gr_mem)
+ if ( (NULL ==grp)
+ || (NULL == grp->gr_mem)) {
return 0;
+ }
return is_on_list (grp->gr_mem, username);
}
#endif /* !USE_PAM */
+static RETSIGTYPE kill_child (int unused(s))
+{
+ if (0 != pid_child) {
+ (void) kill (pid_child, SIGKILL);
+ (void) fputs (_(" ...killed.\n"), stderr);
+ } else {
+ (void) fputs (_(" ...waiting for child to terminate.\n"),
+ stderr);
+ }
+ exit (255);
+}
+
/* borrowed from GNU sh-utils' "su.c" */
static bool restricted_shell (const char *shellstr)
{
static void su_failure (const char *tty)
{
- sulog (tty, 0, oldname, name); /* log failed attempt */
+ sulog (tty, false, oldname, name); /* log failed attempt */
#ifdef USE_SYSLOG
- if (getdef_bool ("SYSLOG_SU_ENAB"))
+ if (getdef_bool ("SYSLOG_SU_ENAB")) {
SYSLOG (((0 != pwent.pw_uid) ? LOG_INFO : LOG_NOTICE,
"- %s %s:%s", tty,
('\0' != oldname[0]) ? oldname : "???",
('\0' != name[0]) ? name : "???"));
+ }
closelog ();
#endif
exit (1);
}
+/*
+ * execve_shell - Execute a shell with execve, or interpret it with
+ * /bin/sh
+ */
+static void execve_shell (const char *shellstr,
+ char *args[],
+ char *const envp[])
+{
+ int err;
+ (void) execve (shellstr, (char **) args, envp);
+ err = errno;
+
+ if (access (shellstr, R_OK|X_OK) == 0) {
+ /*
+ * Assume this is a shell script (with no shebang).
+ * Interpret it with /bin/sh
+ */
+ size_t n_args = 0;
+ char **targs;
+ while (NULL != args[n_args]) {
+ n_args++;
+ }
+ targs = (char **) xmalloc ((n_args + 3) * sizeof (args[0]));
+ targs[0] = "sh";
+ targs[1] = "-";
+ targs[2] = xstrdup (shellstr);
+ targs[n_args+2] = NULL;
+ while (1 != n_args) {
+ targs[n_args+1] = args[n_args - 1];
+ n_args--;
+ }
+
+ (void) execve (SHELL, targs, envp);
+ } else {
+ errno = err;
+ }
+}
#ifdef USE_PAM
/* Signal handler for parent process later */
* su.c from shadow.
*/
static void run_shell (const char *shellstr, char *args[], bool doshell,
- char *const envp[])
+ char *const envp[])
{
pid_t child;
sigset_t ourset;
if (doshell) {
(void) shell (shellstr, (char *) args[0], envp);
} else {
- (void) execve (shellstr, (char **) args, envp);
+ execve_shell (shellstr, (char **) args, envp);
}
+
exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
} else if ((pid_t)-1 == child) {
- (void) fprintf (stderr, "%s: Cannot fork user shell\n", Prog);
+ (void) fprintf (stderr,
+ _("%s: Cannot fork user shell\n"),
+ Prog);
SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
closelog ();
exit (1);
}
/* parent only */
+ pid_child = child;
sigfillset (&ourset);
if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
- (void) fprintf (stderr, "%s: signal malfunction\n", Prog);
+ (void) fprintf (stderr,
+ _("%s: signal malfunction\n"),
+ Prog);
caught = true;
}
if (!caught) {
|| (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
) {
fprintf (stderr,
- "%s: signal masking malfunction\n", Prog);
+ _("%s: signal masking malfunction\n"),
+ Prog);
caught = true;
}
}
}
if (caught) {
- fprintf (stderr, "\nSession terminated, killing shell...");
- kill (child, SIGTERM);
+ (void) fputs ("\n", stderr);
+ (void) fputs (_("Session terminated, terminating shell..."),
+ stderr);
+ (void) kill (child, SIGTERM);
}
ret = pam_close_session (pamh, 0);
ret = pam_end (pamh, PAM_SUCCESS);
if (caught) {
- sleep (2);
- kill (child, SIGKILL);
- fprintf (stderr, " ...killed.\n");
- exit (-1);
+ (void) signal (SIGALRM, kill_child);
+ (void) alarm (2);
+
+ (void) wait (&status);
+ (void) fputs (_(" ...terminated.\n"), stderr);
}
exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
/*
* usage - print command line syntax and exit
*/
-static void usage (void)
+static void usage (int status)
{
fputs (_("Usage: su [options] [LOGIN]\n"
"\n"
" --preserve-environment do not reset environment variables, and\n"
" keep the same shell\n"
" -s, --shell SHELL use SHELL instead of the default in passwd\n"
- "\n"), stderr);
- exit (E_USAGE);
+ "\n"), (E_SUCCESS != status) ? stderr : stdout);
+ exit (status);
}
/*
* su - switch user id
*
* su changes the user's ids to the values for the specified user. if
- * no new user name is specified, "root" is used by default.
+ * no new user name is specified, "root" or UID 0 is used by default.
*
* Any additional arguments are passed to the user's shell. In
* particular, the argument "-c" will cause the next argument to be
*/
int main (int argc, char **argv)
{
- char *cp;
+ const char *cp;
const char *tty = NULL; /* Name of tty SU is run from */
bool doshell = false;
bool fakelogin = false;
command = optarg;
break;
case 'h':
- usage ();
+ usage (E_SUCCESS);
break;
case 'l':
fakelogin = true;
shellstr = optarg;
break;
default:
- usage (); /* NOT REACHED */
+ usage (E_USAGE); /* NOT REACHED */
}
}
* Get the tty name. Entries will be logged indicating that the user
* tried to change to the named new user from the current terminal.
*/
- cp = ttyname (0);
- if ((isatty (0) != 0) && (NULL != cp)) {
- if (strncmp (cp, "/dev/", 5) == 0) {
- tty = cp + 5;
- } else {
- tty = cp;
- }
+ tty = ttyname (0);
+ if ((isatty (0) != 0) && (NULL != tty)) {
#ifndef USE_PAM
is_console = console (tty);
#endif
*/
if (!amroot) {
fprintf (stderr,
- _("%s: must be run from a terminal\n"), Prog);
+ _("%s: must be run from a terminal\n"),
+ Prog);
exit (1);
}
tty = "???";
optind++;
}
}
- if ('\0' == name[0]) { /* use default user ID */
- (void) strcpy (name, "root");
+ if ('\0' == name[0]) { /* use default user */
+ struct passwd *root_pw = getpwnam ("root");
+ if ((NULL != root_pw) && (0 == root_pw->pw_uid)) {
+ (void) strcpy (name, "root");
+ } else {
+ root_pw = getpwuid (0);
+ if (NULL == root_pw) {
+ SYSLOG ((LOG_CRIT, "There is no UID 0 user."));
+ su_failure (tty);
+ }
+ (void) strcpy (name, root_pw->pw_name);
+ }
}
doshell = (argc == optind); /* any arguments remaining? */
*/
pw = get_my_pwent ();
if (NULL == pw) {
- SYSLOG ((LOG_CRIT, "Unknown UID: %u", my_uid));
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) my_uid));
su_failure (tty);
}
STRFCPY (oldname, pw->pw_name);
ret = pam_start ("su", name, &conv, &pamh);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
- fprintf (stderr, _("%s: pam_start: error %d\n"),
- Prog, ret));
+ fprintf (stderr,
+ _("%s: pam_start: error %d\n"),
+ Prog, ret));
exit (1);
}
* (note: in the case of a subsystem, the shell will be restricted,
* and this won't be executed on the first pass)
*/
- if (fakelogin && change_environment) {
+ if (change_environment && fakelogin) {
/*
* The terminal type will be left alone if it is present in
* the environment already.
if (NULL != cp) {
addenv ("TERM", cp);
}
+
+ /*
+ * For some terminals COLORTERM seems to be the only way
+ * for checking for that specific terminal. For instance,
+ * gnome-terminal sets its TERM as "xterm" but its
+ * COLORTERM as "gnome-terminal". The COLORTERM variable
+ * is also of use when running GNU screen since it sets
+ * TERM to "screen" but doesn't touch COLORTERM.
+ */
+ cp = getenv ("COLORTERM");
+ if (NULL != cp) {
+ addenv ("COLORTERM", cp);
+ }
+
#ifndef USE_PAM
cp = getdef_str ("ENV_TZ");
if (NULL != cp) {
if (NULL != cp) {
addenv (cp, NULL); /* set the default $HZ, if one */
}
+#endif /* !USE_PAM */
/*
* Also leave DISPLAY and XAUTHORITY if present, else
if (NULL != cp) {
addenv ("XAUTHORITY", cp);
}
-#endif /* !USE_PAM */
} else {
while (NULL != *envp) {
addenv (*envp, NULL);
&& getdef_bool ("SU_WHEEL_ONLY")
&& !iswheel (oldname)) {
fprintf (stderr,
- _("You are not authorized to su %s\n"), name);
+ _("You are not authorized to su %s\n"),
+ name);
exit (1);
}
#ifdef SU_ACCESS
break;
default: /* access denied (-1) or unexpected value */
fprintf (stderr,
- _("You are not authorized to su %s\n"), name);
+ _("You are not authorized to su %s\n"),
+ name);
exit (1);
}
#endif /* SU_ACCESS */
* Set the default shell.
*/
if ((NULL == shellstr) || ('\0' == shellstr[0])) {
- shellstr = "/bin/sh";
+ shellstr = SHELL;
}
(void) signal (SIGINT, SIG_IGN);
ret = pam_acct_mgmt (pamh, 0);
if (PAM_SUCCESS != ret) {
if (amroot) {
- fprintf (stderr, _("%s: %s\n(Ignored)\n"), Prog,
- pam_strerror (pamh, ret));
+ fprintf (stderr,
+ _("%s: %s\n(Ignored)\n"),
+ Prog, pam_strerror (pamh, ret));
} else if (PAM_NEW_AUTHTOK_REQD == ret) {
ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
pam_strerror (pamh, ret)));
- fprintf (stderr, _("%s: %s\n"), Prog,
- pam_strerror (pamh, ret));
+ fprintf (stderr,
+ _("%s: %s\n"),
+ Prog, pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
su_failure (tty);
}
} else {
SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
pam_strerror (pamh, ret)));
- fprintf (stderr, _("%s: %s\n"), Prog,
- pam_strerror (pamh, ret));
+ fprintf (stderr,
+ _("%s: %s\n"),
+ Prog, pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
su_failure (tty);
}
* Set up a signal handler in case the user types QUIT.
*/
die (0);
- (void) oldsig = signal (SIGQUIT, die);
+ oldsig = signal (SIGQUIT, die);
/*
* See if the system defined authentication method is being used.
*/
if (!amroot && pw_auth (pwent.pw_passwd, name, PW_SU, (char *) 0)) {
SYSLOG ((pwent.pw_uid ? LOG_NOTICE : LOG_WARN,
- "Authentication failed for %s", name));
+ "Authentication failed for %s", name));
+ fprintf(stderr, _("%s: Authentication failure\n"), Prog);
su_failure (tty);
}
(void) signal (SIGQUIT, oldsig);
if (!amroot) {
if (!isttytime (pwent.pw_name, "SU", time ((time_t *) 0))) {
SYSLOG (((0 != pwent.pw_uid) ? LOG_WARN : LOG_CRIT,
- "SU by %s to restricted account %s",
- oldname, name));
+ "SU by %s to restricted account %s",
+ oldname, name));
+ fprintf (stderr,
+ _("%s: You are not authorized to su at that time\n"),
+ Prog);
su_failure (tty);
}
}
*/
argv[-1] = shellstr;
#ifndef USE_PAM
- (void) execve (shellstr, &argv[-1], environ);
+ execve_shell (shellstr, &argv[-1], environ);
err = errno;
(void) fputs (_("No shell\n"), stderr);
SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));