summaryrefslogtreecommitdiff
path: root/utils/login.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/login.c')
-rw-r--r--utils/login.c898
1 files changed, 898 insertions, 0 deletions
diff --git a/utils/login.c b/utils/login.c
new file mode 100644
index 00000000..275cfe52
--- /dev/null
+++ b/utils/login.c
@@ -0,0 +1,898 @@
+/* Hurdish login
+
+ Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+
+ Written by Miles Bader <miles@gnu.ai.mit.edu>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <hurd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <paths.h>
+#include <ctype.h>
+#include <utmp.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <time.h>
+#include <assert.h>
+#include <version.h>
+#include <sys/mman.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <sys/fcntl.h>
+
+#include <argp.h>
+#include <argz.h>
+#include <envz.h>
+#include <idvec.h>
+#include <error.h>
+#include <timefmt.h>
+#include <hurd/lookup.h>
+#include <ugids.h>
+
+const char *argp_program_version = STANDARD_HURD_VERSION (login);
+
+extern error_t
+exec_reauth (auth_t auth, int secure, int must_reauth,
+ mach_port_t *ports, unsigned num_ports,
+ mach_port_t *fds, unsigned num_fds);
+extern error_t
+get_nonsugid_ids (struct idvec *uids, struct idvec *gids);
+
+/* Defaults for various login parameters. */
+char *default_args[] = {
+ "SHELL=/bin/bash",
+ /* A ':' separated list of what to try if can't exec user's shell. */
+ "BACKUP_SHELLS=/bin/bash:" _PATH_BSHELL,
+ "HOME=/etc/login", /* Initial WD. */
+ "USER=login",
+ "UMASK=0",
+ "NAME=Not logged in",
+ "HUSHLOGIN=.hushlogin", /* Looked up relative new home dir. */
+ "MOTD=/etc/motd",
+ "PATH=/bin",
+ "NOBODY=login",
+ "NOAUTH_TIMEOUT=300", /* seconds before unauthed sessions die. */
+ 0
+};
+/* Default values for the new environment. */
+char *default_env[] = {
+ "PATH=/bin",
+ 0
+};
+
+/* Which things are copied from the login parameters into the environment. */
+char *copied_args[] = {
+ "USER", "SHELL", "HOME", "NAME", "VIA", "VIA_ADDR", "PATH", 0
+};
+
+static struct argp_option options[] =
+{
+ {"arg0", '0', "ARG", 0, "Make ARG the shell's argv[0]"},
+ {"envvar", 'e', "ENTRY", 0, "Add ENTRY to the environment"},
+ {"envvar-default", 'E', "ENTRY", 0, "Use ENTRY as a default environment variable"},
+ {"no-args", 'x', 0, 0, "Don't put login args into the environment"},
+ {"arg", 'a', "ARG", 0, "Add login parameter ARG"},
+ {"arg-default", 'A', "ARG", 0, "Use ARG as a default login parameter"},
+ {"no-environment-args", 'X', 0, 0, "Don't add the parent environment as default login params"},
+ {"no-login", 'L', 0, 0, "Don't modify the shells argv[0] to look"
+ " like a login shell"},
+ {"preserve-environment", 'p', 0, 0, "Inherit the parent's environment"},
+ {"via", 'h', "HOST", 0, "This login is from HOST"},
+ {"no-passwd", 'f', 0, 0, "Don't ask for passwords"},
+ {"paranoid", 'P', 0, 0, "Don't admit that a user doesn't exist"},
+ {"save", 's', 0, 0, "Keep the old available ids, and save the old"
+ " effective ids as available ids"},
+ {"shell-from-args", 'S', 0, 0, "Use the first shell arg as the shell to invoke"},
+ {"retry", 'R', "ARG", OPTION_ARG_OPTIONAL,
+ "Re-exec login with no users after non-fatal errors; if ARG is supplied,"
+ "add it to the list of args passed to login when retrying"},
+ {0, 0}
+};
+static struct argp_child child_argps[] =
+{
+ { &ugids_argp, 0, "Adding individual user/group ids:" },
+ { 0 }
+};
+static char *args_doc = "[USER [ARG...]]";
+static char *doc =
+"Exec a program with uids and/or the environment changed appropriately.\v"
+"To give args to the shell without specifying a user, use - for USER.\n"
+"Current login parameters include HOME, SHELL, USER, NAME, and ROOT.";
+
+/* Outputs whatever can be read from the io_t NODE to standard output, and
+ then close it. If NODE is MACH_PORT_NULL, assumes an error happened, and
+ prints an error message using ERRNO and STR. */
+static void
+cat (mach_port_t node, char *str)
+{
+ error_t err;
+ if (node == MACH_PORT_NULL)
+ err = errno;
+ else
+ for (;;)
+ {
+ char buf[1024], *data = buf;
+ mach_msg_type_number_t data_len = sizeof (buf);
+
+ err = io_read (node, &data, &data_len, -1, 16384);
+ if (err || data_len == 0)
+ break;
+ else
+ {
+ write (0, data, data_len);
+ if (data != buf)
+ munmap (data, data_len);
+ }
+ }
+ if (err)
+ error (0, errno, "%s", str);
+}
+
+/* Add a utmp entry based on the parameters in ARGS & ARGS_LEN. If
+ INHERIT_HOST is true, the host parameters in ARGS aren't to be trusted, so
+ try to get the host from the existing utmp entry (this only works if
+ re-logging in during an existing session). */
+static void
+add_utmp_entry (char *args, unsigned args_len, int inherit_host)
+{
+ struct utmp utmp;
+ char const *host = 0;
+ long addr = 0;
+
+ bzero (&utmp, sizeof (utmp));
+
+ gettimeofday (&utmp.ut_tv, 0);
+ strncpy (utmp.ut_name, envz_get (args, args_len, "USER") ?: "",
+ sizeof (utmp.ut_name));
+
+ if (! inherit_host)
+ {
+ char *via_addr = envz_get (args, args_len, "VIA_ADDR");
+ host = envz_get (args, args_len, "VIA");
+ if (host && strlen (host) > sizeof (utmp.ut_host))
+ host = via_addr ?: host;
+ if (via_addr)
+ addr = inet_addr (via_addr);
+ }
+
+ if (!host || !addr)
+ /* Get the host from the `existing utmp entry'. This is a crock. */
+ {
+ int tty_fd = 0;
+ char *tty = 0;
+
+ /* Search for a file descriptor naming a tty. */
+ while (!tty && tty_fd < 3)
+ tty = ttyname (tty_fd++);
+ if (tty)
+ /* Find the old utmp entry for TTY, and grab its host parameters. */
+ {
+ struct utmp *old_utmp;
+ strncpy (utmp.ut_line, basename (tty), sizeof (utmp.ut_line));
+ setutent ();
+ old_utmp = getutline (&utmp);
+ endutent ();
+ if (old_utmp)
+ {
+ if (! host)
+ host = old_utmp->ut_host;
+ if (! addr)
+ addr = old_utmp->ut_addr;
+ }
+ }
+ }
+
+ strncpy (utmp.ut_host, host ?: "", sizeof (utmp.ut_host));
+ utmp.ut_addr = addr;
+
+ login (&utmp);
+}
+
+/* Lookup the host HOST, and add entries for VIA (the host name), and
+ VIA_ADDR (the dotted decimal address) to ARGS & ARGS_LEN. */
+static error_t
+add_canonical_host (char **args, unsigned *args_len, char *host)
+{
+ struct hostent *he = gethostbyname (host);
+
+ if (he)
+ {
+ char *addr = 0;
+
+ /* Try and get an ascii version of the numeric host address. */
+ switch (he->h_addrtype)
+ {
+ case AF_INET:
+ addr = strdup (inet_ntoa (*(struct in_addr *)he->h_addr));
+ break;
+ }
+
+ if (addr && strcmp (he->h_name, addr) == 0)
+ /* gethostbyname() cheated! Lookup the host name via the address
+ this time to get the actual host name. */
+ he = gethostbyaddr (he->h_addr, he->h_length, he->h_addrtype);
+
+ if (he)
+ host = he->h_name;
+
+ if (addr)
+ {
+ envz_add (args, args_len, "VIA_ADDR", addr);
+ free (addr);
+ }
+ }
+
+ return envz_add (args, args_len, "VIA", host);
+}
+
+/* Add the `=' separated environment entry ENTRY to ENV & ENV_LEN, exiting
+ with an error message if we can't. */
+static void
+add_entry (char **env, unsigned *env_len, char *entry)
+{
+ char *name = strsep (&entry, "=");
+ error_t err = envz_add (env, env_len, name, entry);
+ if (err)
+ error (8, err, "Adding %s", entry);
+}
+
+/* Return in OWNED whether PID has an owner, or an error. */
+static error_t
+check_owned (process_t proc_server, pid_t pid, int *owned)
+{
+ int flags = PI_FETCH_TASKINFO;
+ char *waits = 0;
+ mach_msg_type_number_t num_waits = 0;
+ struct procinfo _pi, *pi = &_pi;
+ mach_msg_type_number_t pi_size = sizeof pi;
+ error_t err =
+ proc_getprocinfo (proc_server, pid, &flags, (procinfo_t *)&pi, &pi_size,
+ &waits, &num_waits);
+
+ if (! err)
+ {
+ *owned = !(pi->state & PI_NOTOWNED);
+ if (pi != &_pi)
+ munmap (pi, pi_size);
+ }
+
+ return err;
+}
+
+/* Kills the login session PID with signal SIG. */
+static void
+kill_login (process_t proc_server, pid_t pid, int sig)
+{
+ error_t err;
+ size_t num_pids;
+ pid_t self = getpid ();
+
+ do
+ {
+ pid_t _pids[num_pids = 20], *pids = _pids;
+ err = proc_getloginpids (proc_server, pid, &pids, &num_pids);
+ if (! err)
+ {
+ size_t i;
+ for (i = 0; i < num_pids; i++)
+ if (pids[i] != self)
+ kill (pids[i], sig);
+ if (pids != _pids)
+ munmap (pids, num_pids);
+ }
+ }
+ while (!err && num_pids > 0);
+}
+
+/* Looks at the login collection LID. If the root process (PID == LID) is
+ owned by someone, then exit (0), otherwise, if it's exited, exit (42). */
+static void
+check_login (process_t proc_server, int lid)
+{
+ int owned;
+ error_t err = check_owned (proc_server, lid, &owned);
+
+ if (err == ESRCH)
+ exit (42); /* Nothing left to watch. */
+ else
+ assert_perror (err);
+
+ if (owned)
+ exit (0); /* Our task is done. */
+}
+
+/* Forks a process which will kill the login session headed by PID after
+ TIMEOUT seconds if PID still has no owner. */
+static void
+dog (time_t timeout, pid_t pid, char **argv)
+{
+ if (fork () == 0)
+ {
+ char buf[25]; /* Be gratuitously pretty. */
+ char *name = basename (argv[0]);
+ time_t left = timeout;
+ struct timeval tv = { 0, 0 };
+ process_t proc_server = getproc ();
+
+ while (left)
+ {
+ time_t interval = left < 5 ? left : 5;
+
+ tv.tv_sec = left;
+
+ /* Frob ARGV so that ps show something nice. */
+ fmt_named_interval (&tv, 0, buf, sizeof buf);
+ asprintf (&argv[0], "(watchdog for %s %d: %s remaining)",
+ name, pid, buf);
+ argv[1] = 0;
+
+ sleep (interval);
+ left -= interval;
+
+ check_login (proc_server, pid);
+ }
+
+ check_login (proc_server, pid);
+
+ /* Give you-forgot-to-login message. */
+ tv.tv_sec = timeout;
+ fmt_named_interval (&tv, 0, buf, sizeof buf);
+
+ putc ('\n', stderr); /* Make sure our message starts a line. */
+ error (0, 0, "Timed out after %s.", buf);
+
+ /* Kill login session, trying to be nice about it. */
+ kill_login (proc_server, pid, SIGHUP);
+ sleep (5);
+ kill_login (proc_server, pid, SIGKILL);
+ exit (0);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ io_t node;
+ char *arg;
+ char *path;
+ error_t err = 0;
+ char *args = 0; /* The login parameters */
+ unsigned args_len = 0;
+ char *args_defs = 0; /* Defaults for login parameters. */
+ unsigned args_defs_len = 0;
+ char *env = 0; /* The new environment. */
+ unsigned env_len = 0;
+ char *env_defs = 0; /* Defaults for the environment. */
+ unsigned env_defs_len = 0;
+ char *parent_env = 0; /* The environment we got from our parent */
+ unsigned parent_env_len = 0;
+ int no_environ = 0; /* If false, use the env as default params. */
+ int no_args = 0; /* If false, put login params in the env. */
+ int inherit_environ = 0; /* True if we shouldn't clear our env. */
+ int no_passwd = 0; /* Don't bother verifying what we're doing. */
+ int no_login = 0; /* Don't prepend `-' to the shells argv[0]. */
+ int paranoid = 0; /* Admit no knowledge. */
+ int retry = 0; /* For some failures, exec a login shell. */
+ char *retry_args = 0; /* Args passed when retrying. */
+ unsigned retry_args_len = 0;
+ char *shell = 0; /* The shell program to run. */
+ char *sh_arg0 = 0; /* The shell's argv[0]. */
+ char *sh_args = 0; /* The args to the shell. */
+ unsigned sh_args_len = 0;
+ int shell_arg = 0; /* If there are shell args, use the first as
+ the shell name. */
+ struct ugids ugids = UGIDS_INIT; /* Authorization of the new shell. */
+ struct ugids_argp_params ugids_argp_params = { &ugids, 0, 0, 0, -1, 0 };
+ struct idvec parent_uids = IDVEC_INIT; /* Parent uids, -SETUID. */
+ struct idvec parent_gids = IDVEC_INIT; /* Parent gids, -SETGID. */
+ mach_port_t exec; /* The shell executable. */
+ mach_port_t cwd; /* The child's CWD. */
+ mach_port_t root; /* The child's root directory. */
+ mach_port_t ports[INIT_PORT_MAX]; /* Init ports for the new process. */
+ int ints[INIT_INT_MAX]; /* Init ints for it. */
+ mach_port_t fds[3]; /* File descriptors passed. */
+ mach_port_t auth; /* The new shell's authentication. */
+ mach_port_t proc_server = getproc ();
+ pid_t pid = getpid (), sid;
+
+ /* These three functions are to do child-authenticated lookups. See
+ <hurd/lookup.h> for an explanation. */
+ error_t use_child_init_port (int which, error_t (*operate)(mach_port_t))
+ {
+ return (*operate)(ports[which]);
+ }
+ mach_port_t get_child_fd_port (int fd)
+ {
+ return fd < 0 || fd > 2 ? __hurd_fail (EBADF) : fds[fd];
+ }
+ mach_port_t child_lookup (char *name, char *path, int flags)
+ {
+ mach_port_t port = MACH_PORT_NULL;
+ errno =
+ hurd_file_name_path_lookup (use_child_init_port, get_child_fd_port, 0,
+ name, path, flags, 0, &port, 0);
+ return port;
+ }
+
+ /* Print an error message with FMT, STR and ERR. Then, if RETRY is on,
+ exec a default login shell, otherwise exit with CODE (must be non-0). */
+ void fail (int code, error_t err, char *fmt, const char *str)
+ {
+ int retry_argc;
+ char **retry_argv;
+ char *via = envz_get (args, args_len, "VIA");
+ extern void _argp_unlock_xxx (); /* Secret unknown function. */
+
+ if (fmt)
+ error (retry ? 0 : code, err, fmt, str); /* May exit... */
+ else if (! retry)
+ exit (code);
+
+ if (via)
+ envz_add (&retry_args, &retry_args_len, "--via", via);
+ argz_insert (&retry_args, &retry_args_len, retry_args, argv[0]);
+
+ retry_argc = argz_count (retry_args, retry_args_len);
+ retry_argv = alloca ((retry_argc + 1) * sizeof (char *));
+ argz_extract (retry_args, retry_args_len, retry_argv);
+
+ /* Reinvoke ourselves with no userids or anything; shouldn't return. */
+ _argp_unlock_xxx (); /* Hack to get around problems with getopt. */
+ main (retry_argc, retry_argv);
+ exit (code); /* But if it does... */
+ }
+
+ /* Parse our options... */
+ error_t parse_opt (int key, char *arg, struct argp_state *state)
+ {
+ switch (key)
+ {
+ case 'p': inherit_environ = 1; break;
+ case 'x': no_args = 1; break;
+ case 'X': no_environ = 1; break;
+ case 'e': add_entry (&env, &env_len, arg); break;
+ case 'E': add_entry (&env_defs, &env_defs_len, arg); break;
+ case 'a': add_entry (&args, &args_len, arg); break;
+ case 'A': add_entry (&args_defs, &args_defs_len, arg); break;
+ case '0': sh_arg0 = arg; break;
+ case 'L': no_login = 1; break;
+ case 'f': no_passwd = 1; break;
+ case 'P': paranoid = 1; break;
+ case 'S': shell_arg = 1; break;
+
+ case 'R':
+ retry = 1;
+ if (arg)
+ {
+ err = argz_add (&retry_args, &retry_args_len, arg);
+ if (err)
+ error (10, err, "Adding retry arg %s", arg);
+ }
+ break;
+
+ case 'h':
+ add_canonical_host (&args, &args_len, arg);
+ retry = 1;
+ break;
+
+ case 's':
+ idvec_merge (&ugids.avail_uids, &parent_uids);
+ idvec_merge (&ugids.avail_gids, &parent_gids);
+ break;
+
+ case ARGP_KEY_ARG:
+ if (state->arg_num > 0)
+ /* Program arguments. */
+ {
+ err = argz_create (state->argv + state->next - 1,
+ &sh_args, &sh_args_len);
+ state->next = state->argc; /* Consume all args */
+ if (err)
+ error (9, err, "Adding %s", arg);
+ break;
+ }
+
+ if (strcmp (arg, "-") == 0)
+ /* An explicit no-user-specified (so remaining args can be used
+ to set the program args). */
+ break;
+
+ if (isdigit (*arg))
+ err = ugids_set_posix_user (&ugids, atoi (arg));
+ else
+ {
+ struct passwd *pw = getpwnam (arg);
+ if (pw)
+ err = ugids_set_posix_user (&ugids, pw->pw_uid);
+ else if (paranoid)
+ /* Add a bogus uid so that password verification will
+ fail. */
+ idvec_add (&ugids.eff_uids, -1);
+ else
+ fail (10, 0, "%s: Unknown user", arg);
+ }
+
+ if (err)
+ fail (11, err, "%s: Can't set user!", arg);
+
+ break;
+
+ case ARGP_KEY_INIT:
+ state->child_inputs[0] = &ugids_argp_params;
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+ }
+ struct argp argp = { options, parse_opt, args_doc, doc, child_argps };
+
+ /* Don't allow logins if the nologin file exists. */
+ node = file_name_lookup (_PATH_NOLOGIN, O_RDONLY, 0);
+ if (node != MACH_PORT_NULL)
+ {
+ cat (node, _PATH_NOLOGIN);
+ exit (40);
+ }
+
+ /* Put in certain last-ditch defaults. */
+ err = argz_create (default_args, &args_defs, &args_defs_len);
+ if (! err)
+ err = argz_create (default_env, &env_defs, &env_defs_len);
+ if (! err)
+ /* Set the default path using confstr() if possible. */
+ {
+ size_t path_len = confstr (_CS_PATH, 0, 0);
+ if (path_len > 0)
+ {
+ char path[path_len];
+ path_len = confstr (_CS_PATH, path, path_len);
+ if (path_len > 0)
+ err = envz_add (&env_defs, &env_defs_len, "PATH", path);
+ }
+ }
+ if (err)
+ error (23, err, "adding defaults");
+
+ err = argz_create (environ, &parent_env, &parent_env_len);
+
+ /* Get authentication of our parent, minus any setuid. */
+ get_nonsugid_ids (&parent_uids, &parent_gids);
+
+ /* Parse our options. */
+ argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0);
+
+ /* Check passwords where necessary. If no_passwd is set, then our parent
+ guarantees identity itself (where it is allowed), but otherwise
+ we want every UID fully checked. */
+ err = ugids_verify_make_auth (&ugids,
+ no_passwd ? &parent_uids : 0,
+ no_passwd ? &parent_gids : 0,
+ 0, 0, 0, 0, &auth);
+ if (err == EACCES)
+ fail (5, 0, "Invalid password", 0);
+ else if (err)
+ error (5, err, "Authentication failure");
+
+ /* Now that we've parsed the command line, put together all these
+ environments we've gotten from various places. There are two targets:
+ (1) the login parameters, and (2) the child environment.
+
+ The login parameters come from these sources (in priority order):
+ a) User specified (with the --arg option)
+ b) From the passwd file entry for the user being logged in as
+ c) From the parent environment, if --no-environ wasn't specified
+ d) From the user-specified defaults (--arg-default)
+ e) From last-ditch defaults given by the DEFAULT_* defines above
+
+ The child environment (constructed later) is from:
+ a) User specified (--environ)
+ b) From the login parameters (if --no-args wasn't specified)
+ c) From the parent environment, if --inherit-environ was specified
+ d) From the user-specified default env values (--environ-default)
+ e) From last-ditch defaults given by the DEFAULT_* defines above
+ */
+ {
+ struct passwd *pw;
+ char *passwd = 0; /* Login parameters from /etc/passwd */
+ unsigned passwd_len = 0;
+
+ /* Decide which password entry to get parameters from. */
+ if (ugids.eff_uids.num > 0)
+ pw = getpwuid (ugids.eff_uids.ids[0]); /* Effective uid */
+ else if (ugids.avail_uids.num > 0)
+ pw = getpwuid (ugids.avail_uids.ids[0]); /* Auxiliary uid */
+ else
+ /* No user! Try to used the `not-logged-in' user to set various
+ parameters. */
+ pw = getpwnam (envz_get (args, args_len, "NOBODY")
+ ?: envz_get (args_defs, args_defs_len, "NOBODY")
+ ?: "login");
+
+ if (pw)
+ {
+ envz_add (&passwd, &passwd_len, "HOME", pw->pw_dir);
+ envz_add (&passwd, &passwd_len, "SHELL", pw->pw_shell);
+ envz_add (&passwd, &passwd_len, "NAME", pw->pw_gecos);
+ envz_add (&passwd, &passwd_len, "USER", pw->pw_name);
+ }
+
+ /* Merge the login parameters. */
+ err = envz_merge (&args, &args_len, passwd, passwd_len, 0);
+ if (! err && ! no_environ)
+ err = envz_merge (&args, &args_len, parent_env, parent_env_len, 0);
+ if (! err)
+ err = envz_merge (&args, &args_len, args_defs, args_defs_len, 0);
+ if (err)
+ error (24, err, "merging parameters");
+
+ free (passwd);
+ }
+
+ err = proc_getsid (proc_server, pid, &sid);
+ assert_perror (err); /* This should never fail. */
+
+ if (!no_login
+ && (parent_uids.num != 0
+ || ugids.eff_uids.num + ugids.avail_uids.num > 0))
+ /* Make a new login collection (but only for real users). */
+ {
+ char *user = envz_get (args, args_len, "USER");
+ if (user && *user)
+ setlogin (user);
+ proc_make_login_coll (proc_server);
+
+ if (ugids.eff_uids.num + ugids.avail_uids.num == 0)
+ /* We're transiting from having some uids to having none, which means
+ this is probably a new login session. Unless specified otherwise,
+ set a timer to kill this session if it hasn't aquired any ids by
+ then. Note that we fork off the timer process before clearing the
+ process owner: because we're interested in killing unowned
+ processes, proc's in-same-login-session rule should apply to us
+ (allowing us to kill them), and this way they can't kill the
+ watchdog (because it *does* have an owner). */
+ {
+ char *to = envz_get (args, args_len, "NOAUTH_TIMEOUT");
+ time_t timeout = to ? atoi (to) : 0;
+ if (timeout)
+ dog (timeout, pid, argv);
+ }
+ }
+
+ if (ugids.eff_uids.num > 0)
+ proc_setowner (proc_server, ugids.eff_uids.ids[0], 0);
+ else
+ proc_setowner (proc_server, 0, 1); /* Clear the owner. */
+
+ /* Now start constructing the exec arguments. */
+ bzero (ints, sizeof (*ints) * INIT_INT_MAX);
+ arg = envz_get (args, args_len, "UMASK");
+ ints[INIT_UMASK] = arg && *arg ? strtoul (arg, 0, 8) : umask (0);
+
+ for (i = 0; i < 3; i++)
+ fds[i] = getdport (i);
+
+ for (i = 0; i < INIT_PORT_MAX; i++)
+ ports[i] = MACH_PORT_NULL;
+ ports[INIT_PORT_PROC] = getproc ();
+ ports[INIT_PORT_CTTYID] = getcttyid ();
+ ports[INIT_PORT_CRDIR] = getcrdir (); /* May be replaced below. */
+ ports[INIT_PORT_CWDIR] = getcwdir (); /* " */
+
+ /* Now reauthenticate all of the ports we're passing to the child. */
+ err = exec_reauth (auth, 0, 1, ports, INIT_PORT_MAX, fds, 3);
+ if (err)
+ error (40, err, "Port reauth failure");
+
+ /* These are the default values for the child's root/cwd. We don't want to
+ modify PORTS just yet, because we use it to do child-authenticated
+ lookups. */
+ root = ports[INIT_PORT_CRDIR];
+ cwd = ports[INIT_PORT_CWDIR];
+
+ /* Find the shell executable (we copy the name, as ARGS may be changed). */
+ if (shell_arg && sh_args && *sh_args)
+ /* Special case for su mode: get the shell from the args if poss. */
+ {
+ shell = strdup (sh_args);
+ argz_delete (&sh_args, &sh_args_len, sh_args); /* Get rid of it. */
+ }
+ else
+ {
+ arg = envz_get (args, args_len, "SHELL");
+ if (arg && *arg)
+ shell = strdup (arg);
+ else
+ shell = 0;
+ }
+
+ path = envz_get (args, args_len, "PATH");
+ exec = shell ? child_lookup (shell, path, O_EXEC) : MACH_PORT_NULL;
+ if (exec == MACH_PORT_NULL)
+ {
+ char *backup = 0;
+ char *backups = envz_get (args, args_len, "BACKUP_SHELLS");
+ err = errno; /* Save original lookup errno. */
+
+ if (backups && *backups)
+ {
+ backups = strdupa (backups); /* Copy so we can trash it. */
+ while (exec == MACH_PORT_NULL && backups)
+ {
+ backup = strsep (&backups, ":, ");
+ if (*backup && (!shell || strcmp (shell, backup) != 0))
+ exec = child_lookup (backup, path, O_EXEC);
+ }
+ }
+
+ /* Give the error message, but only exit if we couldn't default. */
+ if (exec == MACH_PORT_NULL)
+ fail (1, err, "%s", shell);
+ else
+ error (0, err, "%s", shell);
+
+ /* If we get here, we looked up the default shell ok. */
+ shell = strdup (backup);
+ error (0, 0, "Using SHELL=%s", shell);
+ envz_add (&args, &args_len, "SHELL", shell);
+ err = 0; /* Don't emit random err msgs later! */
+ }
+
+ /* Now maybe change the cwd/root in the child. */
+
+ arg = envz_get (args, args_len, "HOME");
+ if (arg && *arg)
+ {
+ cwd = child_lookup (arg, 0, O_RDONLY);
+ if (cwd == MACH_PORT_NULL)
+ {
+ error (0, errno, "%s", arg);
+ error (0, 0, "Using HOME=/");
+ envz_add (&args, &args_len, "HOME", "/");
+ }
+ }
+
+ arg = envz_get (args, args_len, "ROOT");
+ if (arg && *arg)
+ {
+ root = child_lookup (arg, 0, O_RDONLY);
+ if (root == MACH_PORT_NULL)
+ fail (40, errno, "%s", arg);
+ }
+
+ /* Build the child environment. */
+ if (! no_args)
+ /* We can't just merge ARGS, because it may contain the parent
+ environment, which we don't always want in the child environment, so
+ we pick out only those values of args which actually *are* args. */
+ {
+ char **name;
+ char *user = envz_get (args, args_len, "USER");
+
+ for (name = copied_args; *name && !err; name++)
+ if (! envz_get (env, env_len, *name))
+ {
+ char *val = envz_get (args, args_len, *name);
+ if (val && *val)
+ err = envz_add (&env, &env_len, *name, val);
+ }
+
+ if (user)
+ /* Copy the user arg into the environment as LOGNAME. */
+ err = envz_add (&env, &env_len, "LOGNAME", user);
+ }
+ if (! err && inherit_environ)
+ err = envz_merge (&env, &env_len, parent_env, parent_env_len, 0);
+ if (! err)
+ err = envz_merge (&env, &env_len, env_defs, env_defs_len, 0);
+ if (err)
+ error (24, err, "Can't build environment");
+
+ if (! sh_arg0)
+ /* The shells argv[0] defaults to the basename of the shell. */
+ {
+ char *shell_base = rindex (shell, '/');
+ if (shell_base)
+ shell_base++;
+ else
+ shell_base = shell;
+
+ if (no_login)
+ sh_arg0 = shell_base;
+ else if (ugids.eff_uids.num + ugids.avail_uids.num == 0)
+ /* Use a special format for the argv[0] of a login prompt shell,
+ so that `ps' shows something informative in the COMMAND field.
+ This string must begin with a `-', the convention to tell the
+ shell to be a login shell (i.e. run .profile and the like). */
+ err = (asprintf (&sh_arg0, "-login prompt (%s)", shell_base) == -1
+ ? ENOMEM : 0);
+ else
+ /* Prepend a `-' to the name, which is the ancient canonical
+ way to tell the shell that it's a login shell. */
+ err = asprintf (&sh_arg0, "-%s", shell_base) == -1 ? ENOMEM : 0;
+ }
+ if (! err)
+ err = argz_insert (&sh_args, &sh_args_len, sh_args, sh_arg0);
+ if (err)
+ error (21, err, "Error building shell args");
+
+ /* Maybe output the message of the day. Note that we we the child's
+ authentication to do it, so that this program can't be used to read
+ arbitrary files! */
+ arg = envz_get (args, args_len, "MOTD");
+ if (arg && *arg)
+ {
+ char *hush = envz_get (args, args_len, "HUSHLOGIN");
+ mach_port_t hush_node =
+ (hush && *hush) ? child_lookup (hush, 0, O_RDONLY) : MACH_PORT_NULL;
+ if (hush_node == MACH_PORT_NULL)
+ {
+ mach_port_t motd_node = child_lookup (arg, 0, O_RDONLY);
+ if (motd_node != MACH_PORT_NULL)
+ cat (motd_node, arg);
+ }
+ else
+ mach_port_deallocate (mach_task_self (), hush_node);
+ }
+
+ /* Now that we don't need to use PORTS for lookups anymore, put the correct
+ ROOT and CWD in. */
+ ports[INIT_PORT_CRDIR] = root;
+ ports[INIT_PORT_CWDIR] = cwd;
+
+ /* Get rid of any accumulated null entries in env. */
+ envz_strip (&env, &env_len);
+
+ /* No more authentications to fail, so cross our fingers and add our utmp
+ entry. */
+
+ if (pid == sid)
+ /* Only add utmp entries for the session leader. */
+ add_utmp_entry (args, args_len, !idvec_contains (&parent_uids, 0));
+
+ if ((ugids.eff_uids.num | ugids.eff_gids.num) && !no_login)
+ {
+ char *tty = ttyname (0);
+ if (tty)
+ {
+ /* Change the terminal to be owned by the user. */
+ err = chown (tty,
+ ugids.eff_uids.num ? ugids.eff_uids.ids[0] : -1,
+ ugids.eff_gids.num ? ugids.eff_gids.ids[0] : -1);
+ if (err)
+ error (0, errno, "chown: %s", tty);
+ }
+ }
+
+ err = file_exec (exec, mach_task_self (), EXEC_DEFAULTS,
+ sh_args, sh_args_len, env, env_len,
+ fds, MACH_MSG_TYPE_COPY_SEND, 3,
+ ports, MACH_MSG_TYPE_COPY_SEND, INIT_PORT_MAX,
+ ints, INIT_INT_MAX,
+ 0, 0, 0, 0);
+ if (err)
+ error(5, err, "%s", shell);
+
+ return 0;
+}