]> git.saurik.com Git - apple/shell_cmds.git/blobdiff - su/su.c
shell_cmds-216.60.1.tar.gz
[apple/shell_cmds.git] / su / su.c
diff --git a/su/su.c b/su/su.c
index aad3cad292e484cc7b350ba4e860b392bf0ed298..8ad7434611653a8e44ba895a447c085821907d65 100644 (file)
--- a/su/su.c
+++ b/su/su.c
@@ -1,4 +1,34 @@
 /*
+ * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*-
  * Copyright (c) 1988, 1993, 1994
  *     The Regents of the University of California.  All rights reserved.
  *
@@ -37,22 +67,31 @@ static const char copyright[] =
        The Regents of the University of California.  All rights reserved.\n";
 #endif /* not lint */
 
-#ifndef lint
 #if 0
+#ifndef lint
 static char sccsid[] = "@(#)su.c       8.3 (Berkeley) 4/2/94";
-#endif
-static const char rcsid[] =
-  "$FreeBSD: src/usr.bin/su/su.c,v 1.48 2002/01/24 16:20:17 des Exp $";
 #endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/su/su.c,v 1.91 2009/12/13 03:14:06 delphij Exp $");
 
 #include <sys/param.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/wait.h>
 
+#ifdef USE_BSM_AUDIT
+#include <bsm/libbsm.h>
+#include <bsm/audit_uevents.h>
+#endif
+
 #include <err.h>
 #include <errno.h>
 #include <grp.h>
+#ifndef __APPLE__
+#include <login_cap.h>
+#endif /* !__APPLE__ */
 #include <paths.h>
 #include <pwd.h>
 #include <signal.h>
@@ -61,47 +100,57 @@ static const char rcsid[] =
 #include <string.h>
 #include <syslog.h>
 #include <unistd.h>
-
-#include <pam/pam_appl.h>
-#include <pam/pam_misc.h>
-
-#define PAM_END() do {                                         \
-       int local_ret;                                          \
-       if (pamh != NULL && creds_set) {                        \
-               local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
-               if (local_ret != PAM_SUCCESS)                   \
-                       syslog(LOG_ERR, "pam_setcred: %s",      \
-                               pam_strerror(pamh, local_ret)); \
-               local_ret = pam_close_session(pamh, 0);         \
-               local_ret = pam_end(pamh, local_ret);           \
-               if (local_ret != PAM_SUCCESS)                   \
-                       syslog(LOG_ERR, "pam_end: %s",          \
-                               pam_strerror(pamh, local_ret)); \
-       }                                                       \
+#include <stdarg.h>
+
+#include <security/pam_appl.h>
+#include <security/openpam.h>
+
+#ifdef __APPLE__
+#include <bsm/audit_session.h>
+#endif /* __APPLE__ */
+
+#define PAM_END() do {                                                 \
+       int local_ret;                                                  \
+       if (pamh != NULL) {                                             \
+               local_ret = pam_setcred(pamh, PAM_DELETE_CRED);         \
+               if (local_ret != PAM_SUCCESS)                           \
+                       syslog(LOG_ERR, "pam_setcred: %s",              \
+                               pam_strerror(pamh, local_ret));         \
+               if (asthem) {                                           \
+                       local_ret = pam_close_session(pamh, 0);         \
+                       if (local_ret != PAM_SUCCESS)                   \
+                               syslog(LOG_ERR, "pam_close_session: %s",\
+                                       pam_strerror(pamh, local_ret)); \
+               }                                                       \
+               local_ret = pam_end(pamh, local_ret);                   \
+               if (local_ret != PAM_SUCCESS)                           \
+                       syslog(LOG_ERR, "pam_end: %s",                  \
+                               pam_strerror(pamh, local_ret));         \
+       }                                                               \
 } while (0)
 
 
-#define PAM_SET_ITEM(what, item) do {                          \
-       int local_ret;                                          \
-       local_ret = pam_set_item(pamh, what, item);             \
-       if (local_ret != PAM_SUCCESS) {                         \
-               syslog(LOG_ERR, "pam_set_item(" #what "): %s",  \
-                       pam_strerror(pamh, local_ret));         \
-               errx(1, "pam_set_item(" #what "): %s",          \
-                       pam_strerror(pamh, local_ret));         \
-       }                                                       \
+#define PAM_SET_ITEM(what, item) do {                                  \
+       int local_ret;                                                  \
+       local_ret = pam_set_item(pamh, what, item);                     \
+       if (local_ret != PAM_SUCCESS) {                                 \
+               syslog(LOG_ERR, "pam_set_item(" #what "): %s",          \
+                       pam_strerror(pamh, local_ret));                 \
+               errx(1, "pam_set_item(" #what "): %s",                  \
+                       pam_strerror(pamh, local_ret));                 \
+               /* NOTREACHED */                                        \
+       }                                                               \
 } while (0)
 
 enum tristate { UNSET, YES, NO };
 
 static pam_handle_t *pamh = NULL;
-static int     creds_set = 0;
 static char    **environ_pam;
 
 static char    *ontty(void);
-static int     chshell(char *);
-static void    usage(void);
-static int     export_pam_environment(void);
+static int     chshell(const char *);
+static void    usage(void) __dead2;
+static void    export_pam_environment(void);
 static int     ok_to_export(const char *);
 
 extern char    **environ;
@@ -109,26 +158,50 @@ extern char       **environ;
 int
 main(int argc, char *argv[])
 {
+       static char     *cleanenv;
        struct passwd   *pwd;
-       struct pam_conv conv = {misc_conv, NULL};
+       struct pam_conv conv = { openpam_ttyconv, NULL };
        enum tristate   iscsh;
+#ifndef __APPLE__
+       login_cap_t     *lc;
+#endif /* !__APPLE__ */
        union {
                const char      **a;
                char            * const *b;
-       }               np;
+       }               np;
        uid_t           ruid;
-       gid_t           gid;
-       int             asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
-                       statusp, child_pid, child_pgrp, ret_pid;
-       char            *username, *cleanenv, *class, shellbuf[MAXPATHLEN];
+       pid_t           child_pid, child_pgrp, pid;
+       int             asme, ch, asthem, fastlogin, prio, i, retcode,
+                       statusp, setmaclabel;
+#ifndef __APPLE__
+       u_int           setwhat;
+#endif /* !__APPLE__ */
+       char            *username, *class, shellbuf[MAXPATHLEN];
        const char      *p, *user, *shell, *mytty, **nargv;
+       const void      *v;
+       struct sigaction sa, sa_int, sa_quit, sa_pipe;
+       int temp, fds[2];
+#ifdef USE_BSM_AUDIT
+       const char      *aerr;
+       au_id_t          auid;
+#endif
+#ifdef __APPLE__
+       /* 4043304 */
+       const char      *avshell;
+       char            avshellbuf[MAXPATHLEN];
+#endif /* __APPLE__ */
 
        shell = class = cleanenv = NULL;
        asme = asthem = fastlogin = statusp = 0;
        user = "root";
        iscsh = UNSET;
+       setmaclabel = 0;
 
-       while ((ch = getopt(argc, argv, "-flmc:")) != -1)
+#ifdef __APPLE__
+       while ((ch = getopt(argc, argv, "-flm")) != -1)
+#else
+       while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
+#endif /* __APPLE__ */
                switch ((char)ch) {
                case 'f':
                        fastlogin = 1;
@@ -142,12 +215,18 @@ main(int argc, char *argv[])
                        asme = 1;
                        asthem = 0;
                        break;
+#ifndef __APPLE__
+               case 's':
+                       setmaclabel = 1;
+                       break;
                case 'c':
                        class = optarg;
                        break;
+#endif /* !__APPLE__ */
                case '?':
                default:
                        usage();
+               /* NOTREACHED */
                }
 
        if (optind < argc)
@@ -155,11 +234,31 @@ main(int argc, char *argv[])
 
        if (user == NULL)
                usage();
+       /* NOTREACHED */
+
+       /*
+        * Try to provide more helpful debugging output if su(1) is running
+        * non-setuid, or was run from a file system not mounted setuid.
+        */
+       if (geteuid() != 0)
+               errx(1, "not running setuid");
 
-       if (strlen(user) > MAXLOGNAME - 1)
+#ifdef USE_BSM_AUDIT
+       if (getauid(&auid) < 0 && errno != ENOSYS) {
+               syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
+               errx(1, "Permission denied");
+       }
+#endif
+       if (strlen(user) > MAXLOGNAME - 1) {
+#ifdef USE_BSM_AUDIT
+               if (audit_submit(AUE_su, auid,
+                   EPERM, 1, "username too long: '%s'", user))
+                       errx(1, "Permission denied");
+#endif
                errx(1, "username too long");
+       }
 
-       nargv = malloc(sizeof(char *) * (argc + 4));
+       nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
        if (nargv == NULL)
                errx(1, "malloc failure");
 
@@ -184,9 +283,14 @@ main(int argc, char *argv[])
        pwd = getpwnam(username);
        if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
                pwd = getpwuid(ruid);
-       if (pwd == NULL)
+       if (pwd == NULL) {
+#ifdef USE_BSM_AUDIT
+               if (audit_submit(AUE_su, auid, EPERM, 1,
+                   "unable to determine invoking subject: '%s'", username))
+                       errx(1, "Permission denied");
+#endif
                errx(1, "who are you?");
-       gid = pwd->pw_gid;
+       }
 
        username = strdup(pwd->pw_name);
        if (username == NULL)
@@ -212,7 +316,7 @@ main(int argc, char *argv[])
                errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
        }
 
-       PAM_SET_ITEM(PAM_RUSER, getlogin());
+       PAM_SET_ITEM(PAM_RUSER, username);
 
        mytty = ttyname(STDERR_FILENO);
        if (!mytty)
@@ -221,45 +325,96 @@ main(int argc, char *argv[])
 
        retcode = pam_authenticate(pamh, 0);
        if (retcode != PAM_SUCCESS) {
-               syslog(LOG_ERR, "pam_authenticate: %s",
-                   pam_strerror(pamh, retcode));
+#ifdef USE_BSM_AUDIT
+               if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
+                   username, user, mytty))
+                       errx(1, "Permission denied");
+#endif
+               syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
+                   username, user, mytty);
                errx(1, "Sorry");
        }
-       retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
+#ifdef USE_BSM_AUDIT
+       if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
+               errx(1, "Permission denied");
+#endif
+       retcode = pam_get_item(pamh, PAM_USER, &v);
        if (retcode == PAM_SUCCESS)
-               user = p;
+               user = v;
        else
                syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
                    pam_strerror(pamh, retcode));
+       pwd = getpwnam(user);
+       if (pwd == NULL) {
+#ifdef USE_BSM_AUDIT
+               if (audit_submit(AUE_su, auid, EPERM, 1,
+                   "unknown subject: %s", user))
+                       errx(1, "Permission denied");
+#endif
+               errx(1, "unknown login: %s", user);
+       }
 
        retcode = pam_acct_mgmt(pamh, 0);
        if (retcode == PAM_NEW_AUTHTOK_REQD) {
                retcode = pam_chauthtok(pamh,
                        PAM_CHANGE_EXPIRED_AUTHTOK);
                if (retcode != PAM_SUCCESS) {
+#ifdef USE_BSM_AUDIT
+                       aerr = pam_strerror(pamh, retcode);
+                       if (aerr == NULL)
+                               aerr = "Unknown PAM error";
+                       if (audit_submit(AUE_su, auid, EPERM, 1,
+                           "pam_chauthtok: %s", aerr))
+                               errx(1, "Permission denied");
+#endif
                        syslog(LOG_ERR, "pam_chauthtok: %s",
                            pam_strerror(pamh, retcode));
                        errx(1, "Sorry");
                }
        }
        if (retcode != PAM_SUCCESS) {
+#ifdef USE_BSM_AUDIT
+               if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
+                   pam_strerror(pamh, retcode)))
+                       errx(1, "Permission denied");
+#endif
                syslog(LOG_ERR, "pam_acct_mgmt: %s",
                        pam_strerror(pamh, retcode));
                errx(1, "Sorry");
        }
 
-       /* get target login information, default to root */
-       pwd = getpwnam(user);
-       if (pwd == NULL)
-               errx(1, "unknown login: %s", user);
+#ifndef __APPLE__
+       /* get target login information */
+       if (class == NULL)
+               lc = login_getpwclass(pwd);
+       else {
+               if (ruid != 0) {
+#ifdef USE_BSM_AUDIT
+                       if (audit_submit(AUE_su, auid, EPERM, 1,
+                           "only root may use -c"))
+                               errx(1, "Permission denied");
+#endif
+                       errx(1, "only root may use -c");
+               }
+               lc = login_getclass(class);
+               if (lc == NULL)
+                       errx(1, "unknown class: %s", class);
+       }
+#endif /* !__APPLE__ */
 
        /* if asme and non-standard target shell, must be root */
        if (asme) {
                if (ruid != 0 && !chshell(pwd->pw_shell))
-                       errx(1, "permission denied (shell).");
+                       errx(1, "permission denied (shell)");
        }
        else if (pwd->pw_shell && *pwd->pw_shell) {
+#ifdef __APPLE__
+               /* 3825554 */
+               shell = strncpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
+               shellbuf[sizeof(shellbuf) - 1] = '\0';
+#else
                shell = pwd->pw_shell;
+#endif /* __APPLE__ */
                iscsh = UNSET;
        }
        else {
@@ -282,61 +437,154 @@ main(int argc, char *argv[])
         * PAM modules might add supplementary groups in pam_setcred(), so
         * initialize them first.
         */
-       if( initgroups(user, pwd->pw_gid) )
-               err(1, "initgroups failed");
-
-       retcode = pam_open_session(pamh, 0);
-       if( retcode != PAM_SUCCESS ) {
-               syslog(LOG_ERR, "pam_open_session(pamh, 0): %s", 
-                       pam_strerror(pamh, retcode));
+#ifdef __APPLE__
+       if (initgroups(user, pwd->pw_gid))
+               err(1, "initgroups");
+#else
+       if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
+               err(1, "setusercontext");
+#endif /* __APPLE__ */
+
+#ifdef __APPLE__
+       /* 8530846 */
+       if (asthem) {
+        auditinfo_addr_t auinfo = {
+            .ai_termid = { .at_type = AU_IPv4 },
+            .ai_asid = AU_ASSIGN_ASID,
+            .ai_auid = getuid(),
+            .ai_flags = 0,
+        };
+        if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
+            char session[16];
+            snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
+            setenv("SECURITYSESSIONID", session, 1);
+        } else {
+                       errx(1, "failed to create session.");
+        }
        }
+#endif /* __APPLE__ */
 
        retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
-       if (retcode != PAM_SUCCESS)
-               syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
+       if (retcode != PAM_SUCCESS) {
+               syslog(LOG_ERR, "pam_setcred: %s",
                    pam_strerror(pamh, retcode));
-       else
-               creds_set = 1;
+               errx(1, "failed to establish credentials.");
+       }
+       if (asthem) {
+               retcode = pam_open_session(pamh, 0);
+               if (retcode != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_open_session: %s",
+                           pam_strerror(pamh, retcode));
+                       errx(1, "failed to open session.");
+               }
+       }
 
        /*
         * We must fork() before setuid() because we need to call
         * pam_setcred(pamh, PAM_DELETE_CRED) as root.
         */
-
+       sa.sa_flags = SA_RESTART;
+       sa.sa_handler = SIG_IGN;
+       sigemptyset(&sa.sa_mask);
+       sigaction(SIGINT, &sa, &sa_int);
+       sigaction(SIGQUIT, &sa, &sa_quit);
+       sigaction(SIGPIPE, &sa, &sa_pipe);
+       sa.sa_handler = SIG_DFL;
+       sigaction(SIGTSTP, &sa, NULL);
        statusp = 1;
+       if (pipe(fds) == -1) {
+               PAM_END();
+               err(1, "pipe");
+       }
        child_pid = fork();
        switch (child_pid) {
        default:
-               while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
+               sa.sa_handler = SIG_IGN;
+               sigaction(SIGTTOU, &sa, NULL);
+               close(fds[0]);
+               setpgid(child_pid, child_pid);
+               if (tcgetpgrp(STDERR_FILENO) == getpgrp())
+                       tcsetpgrp(STDERR_FILENO, child_pid);
+               close(fds[1]);
+               sigaction(SIGPIPE, &sa_pipe, NULL);
+               while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
                        if (WIFSTOPPED(statusp)) {
-                               child_pgrp = tcgetpgrp(1);
+                               child_pgrp = getpgid(child_pid);
+                               if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
+                                       tcsetpgrp(STDERR_FILENO, getpgrp());
                                kill(getpid(), SIGSTOP);
-                               tcsetpgrp(1, child_pgrp);
-                               kill(child_pid, SIGCONT); 
+                               if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
+                                       child_pgrp = getpgid(child_pid);
+                                       tcsetpgrp(STDERR_FILENO, child_pgrp);
+                               }
+                               kill(child_pid, SIGCONT);
                                statusp = 1;
                                continue;
                        }
                        break;
                }
-               if (ret_pid == -1)
+               tcsetpgrp(STDERR_FILENO, getpgrp());
+               if (pid == -1)
                        err(1, "waitpid");
                PAM_END();
                exit(WEXITSTATUS(statusp));
        case -1:
-               err(1, "fork");
                PAM_END();
-               exit(1);
+               err(1, "fork");
        case 0:
-               if( setgid(pwd->pw_gid) )
+               close(fds[1]);
+               read(fds[0], &temp, 1);
+               close(fds[0]);
+               sigaction(SIGPIPE, &sa_pipe, NULL);
+               sigaction(SIGINT, &sa_int, NULL);
+               sigaction(SIGQUIT, &sa_quit, NULL);
+
+#ifdef __APPLE__
+               if (setgid(pwd->pw_gid))
                        err(1, "setgid");
-               if( setuid(pwd->pw_uid) )
+               /* Call initgroups(2) after setgid(2) to re-establish memberd */
+               if (initgroups(user, pwd->pw_gid))
+                       err(1, "initgroups");
+               if (setuid(pwd->pw_uid))
                        err(1, "setuid");
+#else
+               /*
+                * Set all user context except for: Environmental variables
+                * Umask Login records (wtmp, etc) Path
+                */
+               setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
+                          LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
+                          LOGIN_SETMAC);
+               /*
+                * If -s is present, also set the MAC label.
+                */
+               if (setmaclabel)
+                       setwhat |= LOGIN_SETMAC;
+               /*
+                * Don't touch resource/priority settings if -m has been used
+                * or -l and -c hasn't, and we're not su'ing to root.
+                */
+               if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
+                       setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
+               if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
+                       err(1, "setusercontext");
+#endif /* __APPLE__ */
 
                if (!asme) {
                        if (asthem) {
                                p = getenv("TERM");
-                               *environ = NULL;
+                               environ = &cleanenv;
+                       }
 
+                       if (asthem || pwd->pw_uid)
+                               setenv("USER", pwd->pw_name, 1);
+                       setenv("HOME", pwd->pw_dir, 1);
+                       setenv("SHELL", shell, 1);
+#ifdef __APPLE__
+                       unsetenv("TMPDIR");
+#endif /* __APPLE__ */
+
+                       if (asthem) {
                                /*
                                 * Add any environmental variables that the
                                 * PAM modules may have set.
@@ -345,16 +593,26 @@ main(int argc, char *argv[])
                                if (environ_pam)
                                        export_pam_environment();
 
+#ifdef __APPLE__
+                               /* 5276965: As documented, set $PATH. */
+                               setenv("PATH", "/bin:/usr/bin", 1);
+#else
+                               /* set the su'd user's environment & umask */
+                               setusercontext(lc, pwd, pwd->pw_uid,
+                                       LOGIN_SETPATH | LOGIN_SETUMASK |
+                                       LOGIN_SETENV);
+#endif /* __APPLE__ */
                                if (p)
                                        setenv("TERM", p, 1);
-                               if (chdir(pwd->pw_dir) < 0)
+
+                               p = pam_getenv(pamh, "HOME");
+                               if (chdir(p ? p : pwd->pw_dir) < 0)
                                        errx(1, "no directory");
                        }
-                       if (asthem || pwd->pw_uid)
-                               setenv("USER", pwd->pw_name, 1);
-                       setenv("HOME", pwd->pw_dir, 1);
-                       setenv("SHELL", shell, 1);
                }
+#ifndef __APPLE__
+               login_close(lc);
+#endif /* !__APPLE__ */
 
                if (iscsh == YES) {
                        if (fastlogin)
@@ -362,8 +620,25 @@ main(int argc, char *argv[])
                        if (asme)
                                *np.a-- = "-m";
                }
+#ifdef __APPLE__
+               /* 4043304 */
+               if ((p = strrchr(shell, '/')) != NULL)
+                       avshell = p + 1;
+               else
+                       avshell = shell;
+
+               if (asthem) {
+                       avshellbuf[0] = '-';
+                       strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
+                       avshell = avshellbuf;
+               }
+
                /* csh *no longer* strips the first character... */
-               *np.a = asthem ? "-su" : "su";
+               *np.a = avshell;
+#else
+               /* csh strips the first character... */
+               *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
+#endif /* __APPLE__ */
 
                if (ruid != 0)
                        syslog(LOG_NOTICE, "%s to %s%s", username, user,
@@ -374,17 +649,20 @@ main(int argc, char *argv[])
        }
 }
 
-static int
+static void
 export_pam_environment(void)
 {
        char    **pp;
+       char    *p;
 
        for (pp = environ_pam; *pp != NULL; pp++) {
-               if (ok_to_export(*pp))
-                       putenv(*pp);
+               if (ok_to_export(*pp)) {
+                       p = strchr(*pp, '=');
+                       *p = '\0';
+                       setenv(*pp, p + 1, 1);
+               }
                free(*pp);
        }
-       return PAM_SUCCESS;
 }
 
 /*
@@ -393,12 +671,14 @@ export_pam_environment(void)
  * - Make sure the string doesn't run on too long.
  * - Do not export certain variables.  This list was taken from the
  *   Solaris pam_putenv(3) man page.
+ * Note that if the user is chrooted, PAM may have a better idea than we
+ * do of where her home directory is.
  */
 static int
 ok_to_export(const char *s)
 {
        static const char *noexport[] = {
-               "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
+               "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
                "IFS", "PATH", NULL
        };
        const char **pp;
@@ -420,22 +700,25 @@ static void
 usage(void)
 {
 
-       fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
+#ifdef __APPLE__
+       fprintf(stderr, "usage: su [-] [-flm] [login [args]]\n");
+#else
+       fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
+#endif /* __APPLE__ */
        exit(1);
+       /* NOTREACHED */
 }
 
 static int
-chshell(char *sh)
+chshell(const char *sh)
 {
        int r;
        char *cp;
 
        r = 0;
        setusershell();
-       do {
-               cp = getusershell();
-               r = strcmp(cp, sh);
-       } while (!r && cp != NULL);
+       while ((cp = getusershell()) != NULL && !r)
+           r = (strcmp(cp, sh) == 0);
        endusershell();
        return r;
 }