Print this page
Reduce lint
OS-5388 zoneadmd comes up asynchronously so zlogin can happen too soon
Reviewed by: Josh Wilsdon <jwilsdon@joyent.com>
OS-5198 zlogin must handle args when su binary is missing
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-5185 zlogin fails to execute command when 'su' is missing
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-4863 regression in zlogin non-tty mode handling [tighten parsing]
OS-4863 regression in zlogin non-tty mode handling
Reviewed by: Josh Wilsdon <jwilsdon@joyent.com>
OS-4792 zlogin errors when trying to attach to a container using log-driver
OS-4569 zlogin doesn't properly quote arguments
OS-4577 regression in zlogin argument handling [backout OS-4569]
OS-4569 zlogin doesn't properly quote arguments
OS-4136 would like SIGUSR to zlogin to switch in and out of '-N' modes
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-4166 zlogin to zfd needs TIOCSWINSZ support
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-4109 'zlogin -I' should close /dev/zfd/0 when it exits
OS-3876 custr could allow management of a static buffer
OS-3879 want custr to have a printf function
Reviewed by: Joshua M. Clulow <josh@sysmgr.org>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-3869 'zlogin -Q -i <zonename> <command>' does not return exit status of <command>
OS-3777 zlogin -I needs to work with docker run when in logging mode
OS-3764 'zlogin -i <zonename> /native/usr/vm/sbin/dockerexec /bin/sh' merges argv[] into single argument
OS-3741 zlogin -I has strange echo behavior
OS-3727 Would like to be able to selectively close descriptors when using zlogin -I
OS-3728 zlogin -I and zoneadmd zfd logging can be streamlined
OS-3718 non-interactive zlogin fails to open /dev/tty, tcgetpgrp() on stderr fails
OS-3524 in order to support interaction with docker containers, need to be able to connect to stdio for init from GZ
OS-3525 in order to support 'docker logs' need to be able to get stdio from zone to log file
OS-3529 would like zlogin -i
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-3388 'zlogin <zonename> <command>' does not work for LX branded zones
OS-2834 ship lx brand
        
@@ -21,14 +21,15 @@
 /*
  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2013 DEY Storage Systems, Inc.
  * Copyright (c) 2014 Gary Mills
  * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
  */
 
 /*
- * zlogin provides three types of login which allow users in the global
+ * zlogin provides five types of login which allow users in the global
  * zone to access non-global zones.
  *
  * - "interactive login" is similar to rlogin(1); for example, the user could
  *   issue 'zlogin my-zone' or 'zlogin -e ^ -l me my-zone'.   The user is
  *   granted a new pty (which is then shoved into the zone), and an I/O
@@ -40,16 +41,26 @@
  * - "non-interactive login" is similar to su(1M); the user could issue
  *   'zlogin my-zone ls -l' and the command would be run as specified.
  *   In this mode, zlogin sets up pipes as the communication channel, and
  *   'su' is used to do the login setup work.
  *
+ * - "interactive command" is a combination of the above two modes where
+ *   a command is provide like the non-interactive case, but the -i option is
+ *   also provided to make things interactive. For example, the user could
+ *   issue 'zlogin -i my-zone /bin/sh'. In this mode neither 'login -c' nor
+ *   'su root -c' is prepended to the command invocation. Because of this
+ *   there will be no wtmpx login record within the zone.
+ *
  * - "console login" is the equivalent to accessing the tip line for a
  *   zone.  For example, the user can issue 'zlogin -C my-zone'.
  *   In this mode, zlogin contacts the zoneadmd process via unix domain
  *   socket.  If zoneadmd is not running, it starts it.  This allows the
  *   console to be available anytime the zone is installed, regardless of
  *   whether it is running.
+ *
+ * - "standalone-processs interactive" is specified with -I and connects to
+ *   the zone's stdin, stdout and stderr zfd(7D) devices.
  */
 
 #include <sys/socket.h>
 #include <sys/termios.h>
 #include <sys/utsname.h>
@@ -90,25 +101,27 @@
 #include <libbrand.h>
 #include <auth_list.h>
 #include <auth_attr.h>
 #include <secdb.h>
 
-static int masterfd;
+static int masterfd = -1;
+static int ctlfd = -1;
 static struct termios save_termios;
 static struct termios effective_termios;
 static int save_fd;
 static struct winsize winsize;
 static volatile int dead;
 static volatile pid_t child_pid = -1;
 static int interactive = 0;
 static priv_set_t *dropprivs;
+static unsigned int connect_flags = 0;
 
 static int nocmdchar = 0;
 static int failsafe = 0;
-static int disconnect = 0;
 static char cmdchar = '~';
 static int quiet = 0;
+static char zonebrand[MAXNAMELEN];
 
 static int pollerr = 0;
 
 static const char *pname;
 static char *username;
@@ -121,15 +134,19 @@
 
 #if !defined(TEXT_DOMAIN)               /* should be defined by cc -D */
 #define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it wasn't */
 #endif
 
-#define SUPATH  "/usr/bin/su"
+#define SUPATH1 "/usr/bin/su"
+#define SUPATH2 "/bin/su"
 #define FAILSAFESHELL   "/sbin/sh"
 #define DEFAULTSHELL    "/sbin/sh"
 #define DEF_PATH        "/usr/sbin:/usr/bin"
+#define LX_DEF_PATH     "/bin:/usr/sbin:/usr/bin"
 
+#define MAX_RETRY       30
+
 #define CLUSTER_BRAND_NAME      "cluster"
 
 /*
  * The ZLOGIN_BUFSIZ is larger than PIPE_BUF so we can be sure we're clearing
  * out the pipe when the child is exiting.  The ZLOGIN_RDBUFSIZ must be less
@@ -151,11 +168,11 @@
 #define CANONIFY_LEN 5
 
 static void
 usage(void)
 {
-        (void) fprintf(stderr, gettext("usage: %s [ -dnQCES ] [ -e cmdchar ] "
+        (void) fprintf(stderr, gettext("usage: %s [-dinCEINQS] [-e cmdchar] "
             "[-l user] zonename [command [args ...] ]\n"), pname);
         exit(2);
 }
 
 static const char *
@@ -246,90 +263,115 @@
                 zperror(gettext("Warning: could not set inheritable "
                     "privileges"));
         }
 }
 
-/*
- * Create the unix domain socket and call the zoneadmd server; handshake
- * with it to determine whether it will allow us to connect.
- */
 static int
-get_console_master(const char *zname)
+connect_zone_sock(const char *zname, const char *suffix, boolean_t verbose)
 {
         int sockfd = -1;
         struct sockaddr_un servaddr;
-        char clientid[MAXPATHLEN];
-        char handshake[MAXPATHLEN], c;
-        int msglen;
-        int i = 0, err = 0;
 
         if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+                if (verbose)
                 zperror(gettext("could not create socket"));
                 return (-1);
         }
 
         bzero(&servaddr, sizeof (servaddr));
         servaddr.sun_family = AF_UNIX;
         (void) snprintf(servaddr.sun_path, sizeof (servaddr.sun_path),
-            "%s/%s.console_sock", ZONES_TMPDIR, zname);
-
+            "%s/%s.%s", ZONES_TMPDIR, zname, suffix);
         if (connect(sockfd, (struct sockaddr *)&servaddr,
             sizeof (servaddr)) == -1) {
-                zperror(gettext("Could not connect to zone console"));
-                goto bad;
+                if (verbose)
+                        zperror(gettext("Could not connect to zone"));
+                (void) close(sockfd);
+                return (-1);
         }
-        masterfd = sockfd;
+        return (sockfd);
+}
 
-        msglen = snprintf(clientid, sizeof (clientid), "IDENT %lu %s %d\n",
-            getpid(), setlocale(LC_MESSAGES, NULL), disconnect);
 
+static int
+handshake_zone_sock(int sockfd, unsigned int flags)
+{
+        char clientid[MAXPATHLEN];
+        char handshake[MAXPATHLEN], c;
+        int msglen;
+        int i = 0, err = 0;
+
+        msglen = snprintf(clientid, sizeof (clientid), "IDENT %s %u\n",
+            setlocale(LC_MESSAGES, NULL), flags);
+
         if (msglen >= sizeof (clientid) || msglen < 0) {
                 zerror("protocol error");
-                goto bad;
+                return (-1);
         }
 
-        if (write(masterfd, clientid, msglen) != msglen) {
+        if (write(sockfd, clientid, msglen) != msglen) {
                 zerror("protocol error");
-                goto bad;
+                return (-1);
         }
 
-        bzero(handshake, sizeof (handshake));
-
         /*
          * Take care not to accumulate more than our fill, and leave room for
          * the NUL at the end.
          */
-        while ((err = read(masterfd, &c, 1)) == 1) {
+        bzero(handshake, sizeof (handshake));
+        while ((err = read(sockfd, &c, 1)) == 1) {
                 if (i >= (sizeof (handshake) - 1))
                         break;
                 if (c == '\n')
                         break;
                 handshake[i] = c;
                 i++;
         }
 
         /*
-         * If something went wrong during the handshake we bail; perhaps
-         * the server died off.
+         * If something went wrong during the handshake we bail.
+         * Perhaps the server died off.
          */
         if (err == -1) {
-                zperror(gettext("Could not connect to zone console"));
-                goto bad;
+                zperror(gettext("Could not connect to zone"));
+                return (-1);
         }
 
-        if (strncmp(handshake, "OK", sizeof (handshake)) == 0)
-                return (0);
-
-        zerror(gettext("Console is already in use by process ID %s."),
+        if (strncmp(handshake, "OK", sizeof (handshake)) != 0) {
+                zerror(gettext("Zone is already in use by process ID %s."),
             handshake);
-bad:
-        (void) close(sockfd);
-        masterfd = -1;
         return (-1);
+        }
+
+        return (0);
 }
 
-
+static int
+send_ctl_sock(const char *buf, size_t len)
+{
+        char rbuf[BUFSIZ];
+        int i;
+        if (ctlfd == -1) {
+                return (-1);
+        }
+        if (write(ctlfd, buf, len) != len) {
+                return (-1);
+        }
+        /* read the response */
+        for (i = 0; i < (BUFSIZ - 1); i++) {
+                char c;
+                if (read(ctlfd, &c, 1) != 1 || c == '\n' || c == '\0') {
+                        break;
+                }
+                rbuf[i] = c;
+        }
+        rbuf[i+1] = '\0';
+        if (strncmp("OK", rbuf, BUFSIZ) != 0) {
+                return (-1);
+        }
+        return (0);
+}
 /*
  * Routines to handle pty creation upon zone entry and to shuttle I/O back
  * and forth between the two terminals.  We also compute and store the
  * name of the slave terminal associated with the master side.
  */
@@ -514,14 +556,38 @@
 static void
 sigwinch(int s)
 {
         struct winsize ws;
 
-        if (ioctl(0, TIOCGWINSZ, &ws) == 0)
+        if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
+                if (ctlfd != -1) {
+                        char buf[BUFSIZ];
+                        (void) snprintf(buf, sizeof (buf),
+                            "TIOCSWINSZ %hu %hu\n", ws.ws_row, ws.ws_col);
+                        (void) send_ctl_sock(buf, strlen(buf));
+                } else {
                 (void) ioctl(masterfd, TIOCSWINSZ, &ws);
+                }
+        }
 }
 
+/*
+ * Toggle zfd EOF mode and notify zoneadmd
+ */
+/*ARGSUSED*/
+static void
+sigusr1(int s)
+{
+        connect_flags ^= ZLOGIN_ZFD_EOF;
+        if (ctlfd != -1) {
+                char buf[BUFSIZ];
+                (void) snprintf(buf, sizeof (buf), "SETFLAGS %u\n",
+                    connect_flags);
+                (void) send_ctl_sock(buf, strlen(buf));
+        }
+}
+
 static volatile int close_on_sig = -1;
 
 static void
 /*ARGSUSED*/
 sigcld(int s)
@@ -860,32 +926,32 @@
 
                 if (errno == EINTR && dead) {
                         break;
                 }
 
-                /* event from master side stdout */
-                if (pollfds[0].revents) {
-                        if (pollfds[0].revents &
+                /* event from master side stderr */
+                if (pollfds[1].revents) {
+                        if (pollfds[1].revents &
                             (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
-                                if (process_output(stdout_fd, STDOUT_FILENO)
+                                if (process_output(stderr_fd, STDERR_FILENO)
                                     != 0)
                                         break;
                         } else {
-                                pollerr = pollfds[0].revents;
+                                pollerr = pollfds[1].revents;
                                 break;
                         }
                 }
 
-                /* event from master side stderr */
-                if (pollfds[1].revents) {
-                        if (pollfds[1].revents &
+                /* event from master side stdout */
+                if (pollfds[0].revents) {
+                        if (pollfds[0].revents &
                             (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
-                                if (process_output(stderr_fd, STDERR_FILENO)
+                                if (process_output(stdout_fd, STDOUT_FILENO)
                                     != 0)
                                         break;
                         } else {
-                                pollerr = pollfds[1].revents;
+                                pollerr = pollfds[0].revents;
                                 break;
                         }
                 }
 
                 /* event from user STDIN side */
@@ -1051,11 +1117,11 @@
          * we're not doing the execution via a shell we'll need to convert
          * the exec string to an array of strings.  We'll do that here
          * but we're going to be very simplistic about it and break stuff
          * up based on spaces.  We're not even going to support any kind
          * of quoting or escape characters.  It's truly amazing that
-         * there is no library function in OpenSolaris to do this for us.
+         * there is no library function in Illumos to do this for us.
          */
 
         /*
          * Be paranoid.  Since we're deliniating based on spaces make
          * sure there are no adjacent spaces.
@@ -1090,74 +1156,160 @@
         assert(n == a);
         return (new_argv);
 }
 
 /*
- * Prepare argv array for exec'd process; if we're passing commands to the
- * new process, then use su(1M) to do the invocation.  Otherwise, use
+ * Prepare argv array for exec'd process.  If commands are passed to the new
+ * process and su(1M) is avalable, use it for the invocation.  Otherwise, use
  * 'login -z <from_zonename> -f' (-z is an undocumented option which tells
  * login that we're coming from another zone, and to disregard its CONSOLE
  * checks).
  */
 static char **
-prep_args(brand_handle_t bh, const char *login, char **argv)
+prep_args(brand_handle_t bh, char *zonename, const char *login, char **argv)
 {
-        int argc = 0, a = 0, i, n = -1;
-        char **new_argv;
-
-        if (argv != NULL) {
+        int argc = 0, i;
                 size_t subshell_len = 1;
-                char *subshell;
+        char *subshell = NULL, *supath = NULL;
+        char **new_argv = NULL;
 
-                while (argv[argc] != NULL)
-                        argc++;
+        if (argv == NULL) {
+                if (failsafe) {
+                        if ((new_argv = malloc(sizeof (char *) * 2)) == NULL)
+                                return (NULL);
+                        new_argv[0] = FAILSAFESHELL;
+                        new_argv[1] = NULL;
+                } else {
+                        new_argv = zone_login_cmd(bh, login);
+                }
+                return (new_argv);
+        }
 
-                for (i = 0; i < argc; i++) {
-                        subshell_len += strlen(argv[i]) + 1;
+        /*
+         * Attempt to locate a 'su' binary if not using the failsafe shell.
+         */
+        if (!failsafe) {
+                struct stat sb;
+                char zonepath[MAXPATHLEN];
+                char supath_check[MAXPATHLEN];
+
+                if (zone_get_zonepath(zonename, zonepath,
+                    sizeof (zonepath)) != Z_OK) {
+                        zerror(gettext("unable to determine zone "
+                            "path"));
+                        return (NULL);
                 }
-                if ((subshell = calloc(1, subshell_len)) == NULL)
+
+                (void) snprintf(supath_check, sizeof (supath), "%s/root/%s",
+                    zonepath, SUPATH1);
+                if (stat(supath_check, &sb) == 0) {
+                        supath = SUPATH1;
+                } else {
+                        (void) snprintf(supath_check, sizeof (supath_check),
+                            "%s/root/%s", zonepath, SUPATH2);
+                        if (stat(supath_check, &sb) == 0) {
+                                supath = SUPATH2;
+                        }
+                }
+        }
+
+        /*
+         * With no failsafe shell or supath to wrap the incoming command, the
+         * arguments are passed straight through.
+         */
+        if (!failsafe && supath == NULL) {
+                /*
+                 * Such an outcome is not acceptable, however, if the caller
+                 * expressed a desire to switch users.
+                 */
+                if (strcmp(login, "root") != 0) {
+                        zerror(gettext("unable to find 'su' command"));
                         return (NULL);
+                }
+                return (argv);
+        }
 
+        /*
+         * Inventory arguments and allocate a buffer to escape them for the
+         * subshell.
+         */
+        while (argv[argc] != NULL) {
+                /*
+                 * Allocate enough space for the delimiter and 2
+                 * quotes which might be needed.
+                 */
+                subshell_len += strlen(argv[argc]) + 3;
+                argc++;
+        }
+        if ((subshell = calloc(1, subshell_len)) == NULL) {
+                return (NULL);
+        }
+
+        /*
+         * The handling of quotes in the following block may seem unusual, but
+         * it is done this way for backward compatibility.
+         * When running a command, zlogin is documented as:
+         *    zlogin zonename command args
+         * However, some code has come to depend on the following usage:
+         *    zlogin zonename 'command args'
+         * This relied on the fact that the single argument would be re-parsed
+         * within the zone and excuted as a command with an argument. To remain
+         * compatible with this (incorrect) usage, if there is only a single
+         * argument, it is not quoted, even if it has embedded spaces.
+         *
+         * Here are two examples which both need to work:
+         * 1) zlogin foo 'echo hello'
+         *    This has a single argv member with a space in it but will not be
+         *    quoted on the command passed into the zone.
+         * 2) zlogin foo bash -c 'echo hello'
+         *    This has 3 argv members. The 3rd arg has a space and must be
+         *    quoted on the command passed into the zone.
+         */
                 for (i = 0; i < argc; i++) {
-                        (void) strcat(subshell, argv[i]);
+                if (i > 0)
                         (void) strcat(subshell, " ");
+
+                if (argc > 1 && (strchr(argv[i], ' ') != NULL ||
+                    strchr(argv[i], '\t') != NULL)) {
+                        (void) strcat(subshell, "'");
+                        (void) strcat(subshell, argv[i]);
+                        (void) strcat(subshell, "'");
+                } else {
+                        (void) strcat(subshell, argv[i]);
                 }
+        }
 
                 if (failsafe) {
-                        n = 4;
+                int a = 0, n = 4;
+
                         if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
                                 return (NULL);
 
                         new_argv[a++] = FAILSAFESHELL;
+                new_argv[a++] = "-c";
+                new_argv[a++] = subshell;
+                new_argv[a++] = NULL;
+                assert(a == n);
                 } else {
-                        n = 5;
+                int a = 0, n = 6;
+
+                assert(supath != NULL);
                         if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
                                 return (NULL);
 
-                        new_argv[a++] = SUPATH;
+                new_argv[a++] = supath;
                         if (strcmp(login, "root") != 0) {
                                 new_argv[a++] = "-";
-                                n++;
+                } else {
+                        n--;
                         }
                         new_argv[a++] = (char *)login;
-                }
                 new_argv[a++] = "-c";
                 new_argv[a++] = subshell;
                 new_argv[a++] = NULL;
                 assert(a == n);
-        } else {
-                if (failsafe) {
-                        n = 2;
-                        if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
-                                return (NULL);
-                        new_argv[a++] = FAILSAFESHELL;
-                        new_argv[a++] = NULL;
-                        assert(n == a);
-                } else {
-                        new_argv = zone_login_cmd(bh, login);
                 }
-        }
 
         return (new_argv);
 }
 
 /*
@@ -1183,10 +1335,11 @@
 prep_env()
 {
         int e = 0, size = 1;
         char **new_env, *estr;
         char *term = getenv("TERM");
+        char *path;
 
         size++; /* for $PATH */
         if (term != NULL)
                 size++;
 
@@ -1199,11 +1352,16 @@
                 size += 2;
 
         if ((new_env = malloc(sizeof (char *) * size)) == NULL)
                 return (NULL);
 
-        if ((estr = add_env("PATH", DEF_PATH)) == NULL)
+        if (strcmp(zonebrand, "lx") == 0)
+                path = LX_DEF_PATH;
+        else
+                path = DEF_PATH;
+
+        if ((estr = add_env("PATH", path)) == NULL)
                 return (NULL);
         new_env[e++] = estr;
 
         if (term != NULL) {
                 if ((estr = add_env("TERM", term)) == NULL)
@@ -1721,28 +1879,65 @@
                 _exit(1);
         }
         return (nptr->pw_name);
 }
 
+static boolean_t
+zlog_mode_logging(char *zonename, boolean_t *found)
+{
+        boolean_t lm = B_FALSE;
+        zone_dochandle_t handle;
+        struct zone_attrtab attr;
+
+        *found = B_FALSE;
+        if ((handle = zonecfg_init_handle()) == NULL)
+                return (lm);
+
+        if (zonecfg_get_handle(zonename, handle) != Z_OK)
+                goto done;
+
+        if (zonecfg_setattrent(handle) != Z_OK)
+                goto done;
+        while (zonecfg_getattrent(handle, &attr) == Z_OK) {
+                if (strcmp("zlog-mode", attr.zone_attr_name) == 0) {
+                        int len = strlen(attr.zone_attr_value);
+
+                        *found = B_TRUE;
+                        if (strncmp("log", attr.zone_attr_value, 3) == 0 ||
+                            strncmp("nolog", attr.zone_attr_value, 5) == 0 ||
+                            (len >= 3 && attr.zone_attr_value[len - 2] == '-'))
+                                lm = B_TRUE;
+                        break;
+                }
+        }
+        (void) zonecfg_endattrent(handle);
+
+done:
+        zonecfg_fini_handle(handle);
+        return (lm);
+}
+
 int
 main(int argc, char **argv)
 {
-        int arg, console = 0;
+        int arg, console = 0, imode = 0;
+        int estatus = 0;
         zoneid_t zoneid;
         zone_state_t st;
         char *login = "root";
+        int iflag = 0;
         int lflag = 0;
         int nflag = 0;
         char *zonename = NULL;
         char **proc_args = NULL;
         char **new_args, **new_env;
         sigset_t block_cld;
+        siginfo_t si;
         char devroot[MAXPATHLEN];
         char *slavename, slaveshortname[MAXPATHLEN];
         priv_set_t *privset;
         int tmpl_fd;
-        char zonebrand[MAXNAMELEN];
         char default_brand[MAXNAMELEN];
         struct stat sb;
         char kernzone[ZONENAME_MAX];
         brand_handle_t bh;
         char user_cmd[MAXPATHLEN];
@@ -1752,18 +1947,28 @@
         (void) textdomain(TEXT_DOMAIN);
 
         (void) getpname(argv[0]);
         username = get_username();
 
-        while ((arg = getopt(argc, argv, "dnECR:Se:l:Q")) != EOF) {
+        while ((arg = getopt(argc, argv, "diNnECIR:Se:l:Q")) != EOF) {
                 switch (arg) {
                 case 'C':
                         console = 1;
                         break;
                 case 'E':
                         nocmdchar = 1;
                         break;
+                case 'I':
+                        /*
+                         * interactive mode is just a slight variation on the
+                         * console mode.
+                         */
+                        console = 1;
+                        imode = 1;
+                        /* The default is HUP, disconnect on EOF */
+                        connect_flags ^= ZLOGIN_ZFD_EOF;
+                        break;
                 case 'R':       /* undocumented */
                         if (*optarg != '/') {
                                 zerror(gettext("root path must be absolute."));
                                 exit(2);
                         }
@@ -1779,19 +1984,26 @@
                         break;
                 case 'S':
                         failsafe = 1;
                         break;
                 case 'd':
-                        disconnect = 1;
+                        connect_flags |= ZLOGIN_DISCONNECT;
                         break;
                 case 'e':
                         set_cmdchar(optarg);
                         break;
+                case 'i':
+                        iflag = 1;
+                        break;
                 case 'l':
                         login = optarg;
                         lflag = 1;
                         break;
+                case 'N':
+                        /* NOHUP - do not send EOF */
+                        connect_flags ^= ZLOGIN_ZFD_EOF;
+                        break;
                 case 'n':
                         nflag = 1;
                         break;
                 default:
                         usage();
@@ -1798,10 +2010,16 @@
                 }
         }
 
         if (console != 0) {
 
+                /*
+                 * The only connect option in console mode is ZLOGIN_DISCONNECT
+                 */
+                if (imode == 0)
+                        connect_flags &= ZLOGIN_DISCONNECT;
+
                 if (lflag != 0) {
                         zerror(gettext(
                             "-l may not be specified for console login"));
                         usage();
                 }
@@ -1824,21 +2042,31 @@
                         exit(2);
                 }
 
         }
 
+        if (iflag != 0 && nflag != 0) {
+                zerror(gettext("-i and -n flags are incompatible"));
+                usage();
+        }
+
         if (failsafe != 0 && lflag != 0) {
                 zerror(gettext("-l may not be specified for failsafe login"));
                 usage();
         }
 
-        if (!console && disconnect != 0) {
+        if (!console && (connect_flags & ZLOGIN_DISCONNECT) != 0) {
                 zerror(gettext(
                     "-d may only be specified with console login"));
                 usage();
         }
 
+        if (imode == 0 && (connect_flags & ZLOGIN_ZFD_EOF) != 0) {
+                zerror(gettext("-N may only be specified with -I"));
+                usage();
+        }
+
         if (optind == (argc - 1)) {
                 /*
                  * zone name, no process name; this should be an interactive
                  * as long as STDIN is really a tty.
                  */
@@ -1857,11 +2085,12 @@
                         usage();
                 }
                 /* zone name and process name, and possibly some args */
                 zonename = argv[optind];
                 proc_args = &argv[optind + 1];
-                interactive = 0;
+                if (iflag && isatty(STDIN_FILENO))
+                        interactive = 1;
         } else {
                 usage();
         }
 
         if (getzoneid() != GLOBAL_ZONEID) {
@@ -1943,49 +2172,120 @@
         } else {
                 forced_login = B_TRUE;
         }
 
         /*
-         * The console is a separate case from the rest of the code; handle
-         * it first.
+         * The console (or standalong interactive mode) is a separate case from
+         * the rest of the code; handle it first.
          */
         if (console) {
+                int gz_stderr_fd = -1;
+                int retry;
+                boolean_t set_raw = B_TRUE;
+
+                if (imode) {
+                        boolean_t has_zfd_config;
+
+                        if (zlog_mode_logging(zonename, &has_zfd_config))
+                                set_raw = B_FALSE;
+
                 /*
+                         * Asked for standalone interactive mode but the
+                         * zlog-mode attribute is not configured on the zone.
+                         */
+                        if (!has_zfd_config) {
+                                zerror(gettext("'%s' is not configured on "
+                                    "the zone"), "zlog-mode");
+                                return (1);
+                        }
+                }
+
+                /*
                  * Ensure that zoneadmd for this zone is running.
                  */
                 if (start_zoneadmd(zonename) == -1)
                         return (1);
 
                 /*
                  * Make contact with zoneadmd.
+                 *
+                 * Handshake with the control socket first. We handle retries
+                 * here since the relevant thread in zoneadmd might not have
+                 * finished setting up yet.
                  */
-                if (get_console_master(zonename) == -1)
+                for (retry = 0; retry < MAX_RETRY; retry++) {
+                        masterfd = connect_zone_sock(zonename,
+                            (imode ? "server_ctl" : "console_sock"), B_FALSE);
+                        if (masterfd != -1)
+                                break;
+                        (void) sleep(1);
+                }
+
+                if (retry == MAX_RETRY) {
+                        zerror(gettext("unable to connect for %d seconds"),
+                            MAX_RETRY);
                         return (1);
+                }
 
-                if (!quiet)
-                        (void) printf(
-                            gettext("[Connected to zone '%s' console]\n"),
-                            zonename);
+                if (handshake_zone_sock(masterfd, connect_flags) != 0) {
+                        (void) close(masterfd);
+                        return (1);
+                }
 
-                if (set_tty_rawmode(STDIN_FILENO) == -1) {
+                if (imode) {
+                        ctlfd = masterfd;
+
+                        /* Now open the io-related sockets */
+                        masterfd = connect_zone_sock(zonename, "server_out",
+                            B_TRUE);
+                        gz_stderr_fd = connect_zone_sock(zonename,
+                            "server_err", B_TRUE);
+                        if (masterfd == -1 || gz_stderr_fd == -1) {
+                                (void) close(ctlfd);
+                                (void) close(masterfd);
+                                (void) close(gz_stderr_fd);
+                                return (1);
+                        }
+                }
+
+                if (!quiet) {
+                        if (imode)
+                                (void) printf(gettext("[Connected to zone '%s' "
+                                    "interactively]\n"), zonename);
+                        else
+                                (void) printf(gettext("[Connected to zone '%s' "
+                                    "console]\n"), zonename);
+                }
+
+                if (set_raw && set_tty_rawmode(STDIN_FILENO) == -1) {
                         reset_tty();
                         zperror(gettext("failed to set stdin pty to raw mode"));
                         return (1);
                 }
 
                 (void) sigset(SIGWINCH, sigwinch);
                 (void) sigwinch(0);
 
+                if (imode) {
+                        /* Allow EOF mode toggling via SIGUSR1 */
+                        (void) sigset(SIGUSR1, sigusr1);
+                }
+
                 /*
                  * Run the I/O loop until we get disconnected.
                  */
-                doio(masterfd, -1, masterfd, -1, -1, B_FALSE);
+                doio(masterfd, -1, masterfd, gz_stderr_fd, -1, B_FALSE);
                 reset_tty();
-                if (!quiet)
-                        (void) printf(
-                            gettext("\n[Connection to zone '%s' console "
-                            "closed]\n"), zonename);
+                if (!quiet) {
+                        if (imode)
+                                (void) printf(gettext("\n[Interactive "
+                                    "connection to zone '%s' closed]\n"),
+                                    zonename);
+                        else
+                                (void) printf(gettext("\n[Connection to zone "
+                                    "'%s' console closed]\n"), zonename);
+                }
 
                 return (0);
         }
 
         if (st != ZONE_STATE_RUNNING && st != ZONE_STATE_MOUNTED) {
@@ -2049,15 +2349,27 @@
         if ((bh = brand_open(zonebrand)) == NULL) {
                 zerror(gettext("could not open brand for zone %s"), zonename);
                 return (1);
         }
 
-        if ((new_args = prep_args(bh, login, proc_args)) == NULL) {
+        /*
+         * The 'interactive' parameter (-i option) indicates that we're running
+         * a command interactively. In this case we skip prep_args so that we
+         * don't prepend the 'su root -c' preamble to the command invocation
+         * since the 'su' command typically will execute a setpgrp which will
+         * disassociate the actual command from the controlling terminal that
+         * we (zlogin) setup.
+         */
+        if (!iflag) {
+                if ((new_args = prep_args(bh, zonename, login, proc_args))
+                    == NULL) {
                 zperror(gettext("could not assemble new arguments"));
                 brand_close(bh);
                 return (1);
         }
+        }
+
         /*
          * Get the brand specific user_cmd.  This command is used to get
          * a passwd(4) entry for login.
          */
         if (!interactive && !failsafe) {
@@ -2199,10 +2511,12 @@
                         zerror(gettext("could not enter zone %s: %s"),
                             zonename, strerror(errno));
                         return (1);
                 }
 
+                /* Note: we're now inside the zone, can't use gettext anymore */
+
                 if (slavefd != STDERR_FILENO)
                         (void) close(STDERR_FILENO);
 
                 /*
                  * We take pains to get this process into a new process
@@ -2240,28 +2554,42 @@
                 }
 
                 /*
                  * In failsafe mode, we don't use login(1), so don't try
                  * setting up a utmpx entry.
+                 *
+                 * A branded zone may have very different utmpx semantics.
+                 * At the moment, we only have two brand types:
+                 * Illumos-like (native, sn1) and Linux.  In the Illumos
+                 * case, we know exactly how to do the necessary utmpx
+                 * setup.  Fortunately for us, the Linux /bin/login is
+                 * prepared to deal with a non-initialized utmpx entry, so
+                 * we can simply skip it.  If future brands don't fall into
+                 * either category, we'll have to add a per-brand utmpx
+                 * setup hook.
                  */
-                if (!failsafe)
+                if (!failsafe && (strcmp(zonebrand, "lx") != 0))
                         if (setup_utmpx(slaveshortname) == -1)
                                 return (1);
 
                 /*
                  * The child needs to run as root to
                  * execute the brand's login program.
                  */
                 if (setuid(0) == -1) {
-                        zperror(gettext("insufficient privilege"));
+                        zperror("insufficient privilege");
                         return (1);
                 }
 
+                if (iflag) {
+                        (void) execve(proc_args[0], proc_args, new_env);
+                } else {
                 (void) execve(new_args[0], new_args, new_env);
-                zperror(gettext("exec failure"));
-                return (1);
         }
+                zperror("exec failure");
+                return (ENOEXEC);
+        }
 
         (void) ct_tmpl_clear(tmpl_fd);
         (void) close(tmpl_fd);
 
         /*
@@ -2281,10 +2609,21 @@
                     zonename, slaveshortname);
 
         if (pollerr != 0) {
                 (void) fprintf(stderr, gettext("Error: connection closed due "
                     "to unexpected pollevents=0x%x.\n"), pollerr);
-                return (1);
+                return (EPIPE);
         }
 
-        return (0);
+        /* reap child and get its status */
+        if (waitid(P_PID, child_pid, &si, WEXITED | WNOHANG) == -1) {
+                estatus = errno;
+        } else if (si.si_pid == 0) {
+                estatus = ECHILD;
+        } else if (si.si_code == CLD_EXITED) {
+                estatus = si.si_status;
+        } else {
+                estatus = ECONNABORTED;
+        }
+
+        return (estatus);
 }