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); }