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