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,34 ****
  /*
   * 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.
   */
  
  /*
!  * zlogin provides three 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
--- 21,35 ----
  /*
   * 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 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,55 ****
--- 41,66 ----
   * - "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,114 ****
  #include <libbrand.h>
  #include <auth_list.h>
  #include <auth_attr.h>
  #include <secdb.h>
  
! static int masterfd;
  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 int nocmdchar = 0;
  static int failsafe = 0;
- static int disconnect = 0;
  static char cmdchar = '~';
  static int quiet = 0;
  
  static int pollerr = 0;
  
  static const char *pname;
  static char *username;
--- 101,127 ----
  #include <libbrand.h>
  #include <auth_list.h>
  #include <auth_attr.h>
  #include <secdb.h>
  
! 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 char cmdchar = '~';
  static int quiet = 0;
+ static char zonebrand[MAXNAMELEN];
  
  static int pollerr = 0;
  
  static const char *pname;
  static char *username;
*** 121,135 ****
  
  #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 FAILSAFESHELL   "/sbin/sh"
  #define DEFAULTSHELL    "/sbin/sh"
  #define DEF_PATH        "/usr/sbin:/usr/bin"
  
  #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
--- 134,152 ----
  
  #if !defined(TEXT_DOMAIN)               /* should be defined by cc -D */
  #define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it wasn't */
  #endif
  
! #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,161 ****
  #define CANONIFY_LEN 5
  
  static void
  usage(void)
  {
!         (void) fprintf(stderr, gettext("usage: %s [ -dnQCES ] [ -e cmdchar ] "
              "[-l user] zonename [command [args ...] ]\n"), pname);
          exit(2);
  }
  
  static const char *
--- 168,178 ----
  #define CANONIFY_LEN 5
  
  static void
  usage(void)
  {
!         (void) fprintf(stderr, gettext("usage: %s [-dinCEINQS] [-e cmdchar] "
              "[-l user] zonename [command [args ...] ]\n"), pname);
          exit(2);
  }
  
  static const char *
*** 246,335 ****
                  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)
  {
          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) {
                  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);
! 
          if (connect(sockfd, (struct sockaddr *)&servaddr,
              sizeof (servaddr)) == -1) {
!                 zperror(gettext("Could not connect to zone console"));
!                 goto bad;
          }
!         masterfd = sockfd;
  
-         msglen = snprintf(clientid, sizeof (clientid), "IDENT %lu %s %d\n",
-             getpid(), setlocale(LC_MESSAGES, NULL), disconnect);
  
          if (msglen >= sizeof (clientid) || msglen < 0) {
                  zerror("protocol error");
!                 goto bad;
          }
  
!         if (write(masterfd, clientid, msglen) != msglen) {
                  zerror("protocol error");
!                 goto bad;
          }
  
-         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) {
                  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 (err == -1) {
!                 zperror(gettext("Could not connect to zone console"));
!                 goto bad;
          }
  
!         if (strncmp(handshake, "OK", sizeof (handshake)) == 0)
!                 return (0);
! 
!         zerror(gettext("Console is already in use by process ID %s."),
              handshake);
- bad:
-         (void) close(sockfd);
-         masterfd = -1;
          return (-1);
  }
  
! 
  /*
   * 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.
   */
--- 263,377 ----
                  zperror(gettext("Warning: could not set inheritable "
                      "privileges"));
          }
  }
  
  static int
! connect_zone_sock(const char *zname, const char *suffix, boolean_t verbose)
  {
          int sockfd = -1;
          struct sockaddr_un servaddr;
  
          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.%s", ZONES_TMPDIR, zname, suffix);
          if (connect(sockfd, (struct sockaddr *)&servaddr,
              sizeof (servaddr)) == -1) {
!                 if (verbose)
!                         zperror(gettext("Could not connect to zone"));
!                 (void) close(sockfd);
!                 return (-1);
          }
!         return (sockfd);
! }
  
  
+ 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");
!                 return (-1);
          }
  
!         if (write(sockfd, clientid, msglen) != msglen) {
                  zerror("protocol error");
!                 return (-1);
          }
  
          /*
           * Take care not to accumulate more than our fill, and leave room for
           * the NUL at the end.
           */
!         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 (err == -1) {
!                 zperror(gettext("Could not connect to zone"));
!                 return (-1);
          }
  
!         if (strncmp(handshake, "OK", sizeof (handshake)) != 0) {
!                 zerror(gettext("Zone is already in use by process ID %s."),
                      handshake);
                  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,527 ****
  static void
  sigwinch(int s)
  {
          struct winsize ws;
  
!         if (ioctl(0, TIOCGWINSZ, &ws) == 0)
                  (void) ioctl(masterfd, TIOCSWINSZ, &ws);
  }
  
  static volatile int close_on_sig = -1;
  
  static void
  /*ARGSUSED*/
  sigcld(int s)
--- 556,593 ----
  static void
  sigwinch(int s)
  {
          struct winsize ws;
  
!         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,891 ****
  
                  if (errno == EINTR && dead) {
                          break;
                  }
  
!                 /* event from master side stdout */
!                 if (pollfds[0].revents) {
!                         if (pollfds[0].revents &
                              (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
!                                 if (process_output(stdout_fd, STDOUT_FILENO)
                                      != 0)
                                          break;
                          } else {
!                                 pollerr = pollfds[0].revents;
                                  break;
                          }
                  }
  
!                 /* event from master side stderr */
!                 if (pollfds[1].revents) {
!                         if (pollfds[1].revents &
                              (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
!                                 if (process_output(stderr_fd, STDERR_FILENO)
                                      != 0)
                                          break;
                          } else {
!                                 pollerr = pollfds[1].revents;
                                  break;
                          }
                  }
  
                  /* event from user STDIN side */
--- 926,957 ----
  
                  if (errno == EINTR && dead) {
                          break;
                  }
  
!                 /* event from master side stderr */
!                 if (pollfds[1].revents) {
!                         if (pollfds[1].revents &
                              (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
!                                 if (process_output(stderr_fd, STDERR_FILENO)
                                      != 0)
                                          break;
                          } else {
!                                 pollerr = pollfds[1].revents;
                                  break;
                          }
                  }
  
!                 /* event from master side stdout */
!                 if (pollfds[0].revents) {
!                         if (pollfds[0].revents &
                              (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
!                                 if (process_output(stdout_fd, STDOUT_FILENO)
                                      != 0)
                                          break;
                          } else {
!                                 pollerr = pollfds[0].revents;
                                  break;
                          }
                  }
  
                  /* event from user STDIN side */
*** 1051,1061 ****
           * 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.
           */
  
          /*
           * Be paranoid.  Since we're deliniating based on spaces make
           * sure there are no adjacent spaces.
--- 1117,1127 ----
           * 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 Illumos to do this for us.
           */
  
          /*
           * Be paranoid.  Since we're deliniating based on spaces make
           * sure there are no adjacent spaces.
*** 1090,1163 ****
          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
   * '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)
  {
!         int argc = 0, a = 0, i, n = -1;
!         char **new_argv;
! 
!         if (argv != NULL) {
                  size_t subshell_len = 1;
!                 char *subshell;
  
!                 while (argv[argc] != NULL)
!                         argc++;
  
!                 for (i = 0; i < argc; i++) {
!                         subshell_len += strlen(argv[i]) + 1;
                  }
!                 if ((subshell = calloc(1, subshell_len)) == NULL)
                          return (NULL);
  
                  for (i = 0; i < argc; i++) {
!                         (void) strcat(subshell, argv[i]);
                          (void) strcat(subshell, " ");
                  }
  
                  if (failsafe) {
!                         n = 4;
                          if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
                                  return (NULL);
  
                          new_argv[a++] = FAILSAFESHELL;
                  } else {
!                         n = 5;
                          if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
                                  return (NULL);
  
!                         new_argv[a++] = SUPATH;
                          if (strcmp(login, "root") != 0) {
                                  new_argv[a++] = "-";
!                                 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);
  }
  
  /*
--- 1156,1315 ----
          assert(n == a);
          return (new_argv);
  }
  
  /*
!  * 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, char *zonename, const char *login, char **argv)
  {
!         int argc = 0, i;
          size_t subshell_len = 1;
!         char *subshell = NULL, *supath = NULL;
!         char **new_argv = NULL;
  
!         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);
!         }
  
!         /*
!          * 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);
                  }
! 
!                 (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++) {
!                 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) {
!                 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 {
!                 int a = 0, n = 6;
! 
!                 assert(supath != NULL);
                  if ((new_argv = malloc(sizeof (char *) * n)) == NULL)
                          return (NULL);
  
!                 new_argv[a++] = supath;
                  if (strcmp(login, "root") != 0) {
                          new_argv[a++] = "-";
!                 } else {
!                         n--;
                  }
                  new_argv[a++] = (char *)login;
                  new_argv[a++] = "-c";
                  new_argv[a++] = subshell;
                  new_argv[a++] = NULL;
                  assert(a == n);
          }
  
          return (new_argv);
  }
  
  /*
*** 1183,1192 ****
--- 1335,1345 ----
  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,1209 ****
                  size += 2;
  
          if ((new_env = malloc(sizeof (char *) * size)) == NULL)
                  return (NULL);
  
!         if ((estr = add_env("PATH", DEF_PATH)) == NULL)
                  return (NULL);
          new_env[e++] = estr;
  
          if (term != NULL) {
                  if ((estr = add_env("TERM", term)) == NULL)
--- 1352,1367 ----
                  size += 2;
  
          if ((new_env = malloc(sizeof (char *) * size)) == NULL)
                  return (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,1748 ****
                  _exit(1);
          }
          return (nptr->pw_name);
  }
  
  int
  main(int argc, char **argv)
  {
!         int arg, console = 0;
          zoneid_t zoneid;
          zone_state_t st;
          char *login = "root";
          int lflag = 0;
          int nflag = 0;
          char *zonename = NULL;
          char **proc_args = NULL;
          char **new_args, **new_env;
          sigset_t block_cld;
          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];
--- 1879,1943 ----
                  _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, 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 default_brand[MAXNAMELEN];
          struct stat sb;
          char kernzone[ZONENAME_MAX];
          brand_handle_t bh;
          char user_cmd[MAXPATHLEN];
*** 1752,1769 ****
          (void) textdomain(TEXT_DOMAIN);
  
          (void) getpname(argv[0]);
          username = get_username();
  
!         while ((arg = getopt(argc, argv, "dnECR:Se:l:Q")) != EOF) {
                  switch (arg) {
                  case 'C':
                          console = 1;
                          break;
                  case 'E':
                          nocmdchar = 1;
                          break;
                  case 'R':       /* undocumented */
                          if (*optarg != '/') {
                                  zerror(gettext("root path must be absolute."));
                                  exit(2);
                          }
--- 1947,1974 ----
          (void) textdomain(TEXT_DOMAIN);
  
          (void) getpname(argv[0]);
          username = get_username();
  
!         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,1797 ****
                          break;
                  case 'S':
                          failsafe = 1;
                          break;
                  case 'd':
!                         disconnect = 1;
                          break;
                  case 'e':
                          set_cmdchar(optarg);
                          break;
                  case 'l':
                          login = optarg;
                          lflag = 1;
                          break;
                  case 'n':
                          nflag = 1;
                          break;
                  default:
                          usage();
--- 1984,2009 ----
                          break;
                  case 'S':
                          failsafe = 1;
                          break;
                  case 'd':
!                         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,1807 ****
--- 2010,2025 ----
                  }
          }
  
          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,1844 ****
                          exit(2);
                  }
  
          }
  
          if (failsafe != 0 && lflag != 0) {
                  zerror(gettext("-l may not be specified for failsafe login"));
                  usage();
          }
  
!         if (!console && disconnect != 0) {
                  zerror(gettext(
                      "-d may only be specified with console login"));
                  usage();
          }
  
          if (optind == (argc - 1)) {
                  /*
                   * zone name, no process name; this should be an interactive
                   * as long as STDIN is really a tty.
                   */
--- 2042,2072 ----
                          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 && (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,1867 ****
                          usage();
                  }
                  /* zone name and process name, and possibly some args */
                  zonename = argv[optind];
                  proc_args = &argv[optind + 1];
!                 interactive = 0;
          } else {
                  usage();
          }
  
          if (getzoneid() != GLOBAL_ZONEID) {
--- 2085,2096 ----
                          usage();
                  }
                  /* zone name and process name, and possibly some args */
                  zonename = argv[optind];
                  proc_args = &argv[optind + 1];
!                 if (iflag && isatty(STDIN_FILENO))
!                         interactive = 1;
          } else {
                  usage();
          }
  
          if (getzoneid() != GLOBAL_ZONEID) {
*** 1943,1991 ****
          } else {
                  forced_login = B_TRUE;
          }
  
          /*
!          * The console is a separate case from the rest of the code; handle
!          * it first.
           */
          if (console) {
                  /*
                   * Ensure that zoneadmd for this zone is running.
                   */
                  if (start_zoneadmd(zonename) == -1)
                          return (1);
  
                  /*
                   * Make contact with zoneadmd.
                   */
!                 if (get_console_master(zonename) == -1)
                          return (1);
  
!                 if (!quiet)
!                         (void) printf(
!                             gettext("[Connected to zone '%s' console]\n"),
!                             zonename);
  
!                 if (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);
  
                  /*
                   * Run the I/O loop until we get disconnected.
                   */
!                 doio(masterfd, -1, masterfd, -1, -1, B_FALSE);
                  reset_tty();
!                 if (!quiet)
!                         (void) printf(
!                             gettext("\n[Connection to zone '%s' console "
!                             "closed]\n"), zonename);
  
                  return (0);
          }
  
          if (st != ZONE_STATE_RUNNING && st != ZONE_STATE_MOUNTED) {
--- 2172,2291 ----
          } else {
                  forced_login = B_TRUE;
          }
  
          /*
!          * 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.
                   */
!                 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 (handshake_zone_sock(masterfd, connect_flags) != 0) {
!                         (void) close(masterfd);
!                         return (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, gz_stderr_fd, -1, B_FALSE);
                  reset_tty();
!                 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,2063 ****
          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) {
                  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) {
--- 2349,2375 ----
          if ((bh = brand_open(zonebrand)) == NULL) {
                  zerror(gettext("could not open brand for zone %s"), zonename);
                  return (1);
          }
  
!         /*
!          * 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,2208 ****
--- 2511,2522 ----
                          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,2267 ****
                  }
  
                  /*
                   * In failsafe mode, we don't use login(1), so don't try
                   * setting up a utmpx entry.
                   */
!                 if (!failsafe)
                          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"));
                          return (1);
                  }
  
                  (void) execve(new_args[0], new_args, new_env);
-                 zperror(gettext("exec failure"));
-                 return (1);
          }
  
          (void) ct_tmpl_clear(tmpl_fd);
          (void) close(tmpl_fd);
  
          /*
--- 2554,2595 ----
                  }
  
                  /*
                   * 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 && (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("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("exec failure");
+                 return (ENOEXEC);
+         }
  
          (void) ct_tmpl_clear(tmpl_fd);
          (void) close(tmpl_fd);
  
          /*
*** 2281,2290 ****
                      zonename, slaveshortname);
  
          if (pollerr != 0) {
                  (void) fprintf(stderr, gettext("Error: connection closed due "
                      "to unexpected pollevents=0x%x.\n"), pollerr);
!                 return (1);
          }
  
!         return (0);
  }
--- 2609,2629 ----
                      zonename, slaveshortname);
  
          if (pollerr != 0) {
                  (void) fprintf(stderr, gettext("Error: connection closed due "
                      "to unexpected pollevents=0x%x.\n"), pollerr);
!                 return (EPIPE);
          }
  
!         /* 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);
  }