787 lines
21 KiB
C
787 lines
21 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <pwd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/termios.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <assert.h>
|
|
#if __INTERIX
|
|
#include <rpc/types.h> /* for INADDR_LOOPBACK */
|
|
#else
|
|
#include <stdint.h>
|
|
#endif
|
|
|
|
#if __INTERIX
|
|
/* Under SFU, openpty(), and therefore forkpty(), require root privileges.
|
|
* So instead, we write our own forkpty() which uses /dev/ptmx. I have
|
|
* no why the builtin openpty() cannot do this internally.
|
|
*/
|
|
#include "ptyfork.h"
|
|
#else
|
|
/* On Cygwin, forkpty() works without privileges */
|
|
#include <pty.h>
|
|
#define pty_fork forkpty
|
|
#endif
|
|
|
|
#include "cthelper.h"
|
|
#include "buffer.h"
|
|
#include "message.h"
|
|
|
|
#include "debug.h"
|
|
|
|
/* Buffer sizes */
|
|
enum {
|
|
CTLBUF = 32,
|
|
PTOBUF = 256,
|
|
PTIBUF = 256,
|
|
};
|
|
|
|
static int
|
|
connect_cygterm(unsigned int port)
|
|
{
|
|
int sock;
|
|
struct sockaddr_in sa;
|
|
|
|
DBUG_ENTER("connect_cygterm");
|
|
if (0 <= (sock = socket(PF_INET, SOCK_STREAM, 0))) {
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(port);
|
|
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
DBUG_PRINT("startup", ("connecting to cygterm on port %u", port));
|
|
if (0 != connect(sock, (struct sockaddr *)&sa, sizeof(sa))) {
|
|
close(sock);
|
|
DBUG_PRINT("startup", ("connect failed: %s", strerror(errno)));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
DBUG_RETURN(sock);
|
|
}
|
|
|
|
static int
|
|
resize(int pty, int h, int w)
|
|
{
|
|
struct winsize ws;
|
|
DBUG_ENTER("resize");
|
|
if (0 != ioctl(pty, TIOCGWINSZ, (void *)&ws)) {
|
|
DBUG_PRINT("msg", ("TIOCGWINSZ failed: %s", strerror(errno)));
|
|
DBUG_RETURN(-1);
|
|
}
|
|
ws.ws_row = h;
|
|
ws.ws_col = w;
|
|
DBUG_PRINT("msg", ("resizing terminal: %d x %d", ws.ws_col, ws.ws_row));
|
|
if (0 != ioctl(pty, TIOCSWINSZ, (void *)&ws))
|
|
DBUG_PRINT("msg", ("TIOCSWINSZ failed: %s", strerror(errno)));
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
static int
|
|
parse_char(const char *desc)
|
|
{
|
|
int ch;
|
|
char *err;
|
|
|
|
DBUG_ENTER("parse_char");
|
|
if (desc[0] == '\0' || desc[0] == ':')
|
|
ch = -1;
|
|
else if (desc[1] == '\0' || desc[1] == ':')
|
|
ch = desc[0];
|
|
else if (desc[0] == '^') {
|
|
ch = toupper(desc[1]);
|
|
if ('?' <= ch && ch <= '_')
|
|
ch ^= '@';
|
|
else
|
|
ch = -1;
|
|
}
|
|
else {
|
|
ch = strtol(desc, &err, 0);
|
|
if (!err || err == desc)
|
|
ch = -1;
|
|
}
|
|
DBUG_RETURN(ch);
|
|
}
|
|
|
|
/* attr is a colon-separated sequence. Each element of this sequence may be:
|
|
* size=width,height to set the terminal size
|
|
* charname=char to set a special character
|
|
* [-]flag to enable or disable a terminal characteristic
|
|
* Supported characters and characteristics are listed below.
|
|
*/
|
|
static void
|
|
init_pty(int pty, const char *attr)
|
|
{
|
|
enum flag_type_t { T_SIZE, T_K, T_I, T_O, T_C, T_L };
|
|
static const struct flag_s {
|
|
const char *name;
|
|
enum flag_type_t type;
|
|
long v;
|
|
} flag[] = {
|
|
{ "brkint", T_I, BRKINT }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 500
|
|
{ "bs0", T_O, BS0 }, /* SUSv2 Ex */
|
|
{ "bs1", T_O, BS1 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "clocal", T_C, CLOCAL }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 500
|
|
{ "cr0", T_O, CR0 }, /* SUSv2 Ex */
|
|
{ "cr1", T_O, CR1 }, /* SUSv2 Ex */
|
|
{ "cr2", T_O, CR2 }, /* SUSv2 Ex */
|
|
{ "cr3", T_O, CR3 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "cread", T_C, CREAD }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "crterase", T_L, CRTERASE }, /* SUSv3 */
|
|
{ "crtkill", T_L, CRTKILL }, /* SUSv3 */
|
|
#endif
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "crtscts", T_C, CRTSCTS }, /* SUSv3 */
|
|
#endif
|
|
{ "cs5", T_C, CS5 }, /* SUSv2 */
|
|
{ "cs6", T_C, CS6 }, /* SUSv2 */
|
|
{ "cs7", T_C, CS7 }, /* SUSv2 */
|
|
{ "cs8", T_C, CS8 }, /* SUSv2 */
|
|
{ "cstopb", T_C, CSTOPB }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "ctlecho", T_L, CTLECHO }, /* SUSv3 */
|
|
#endif
|
|
{ "echo", T_L, ECHO }, /* SUSv2 */
|
|
{ "echoctl", T_L, ECHOCTL }, /* SUSv3 */
|
|
{ "echoe", T_L, ECHOE }, /* SUSv2 */
|
|
{ "echok", T_L, ECHOK }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "echoke", T_L, ECHOKE }, /* SUSv3 */
|
|
#endif
|
|
{ "echonl", T_L, ECHONL }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "echoprt", T_L, ECHOPRT }, /* SUSv3 */
|
|
#endif
|
|
{ "eof", T_K, VEOF }, /* SUSv2 */
|
|
{ "eol", T_K, VEOL }, /* SUSv2 */
|
|
{ "erase", T_K, VERASE }, /* SUSv2 */
|
|
#if !__INTERIX
|
|
{ "ff0", T_O, FF0 }, /* SUSv2 Ex */
|
|
{ "ff1", T_O, FF1 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "hup", T_C, HUPCL }, /* SUSv2 */
|
|
{ "hupcl", T_C, HUPCL }, /* SUSv2 */
|
|
{ "icanon", T_L, ICANON }, /* SUSv2 */
|
|
{ "icrnl", T_I, ICRNL }, /* SUSv2 */
|
|
{ "iexten", T_L, IEXTEN }, /* SUSv2 */
|
|
{ "ignbrk", T_I, IGNBRK }, /* SUSv2 */
|
|
{ "igncr", T_I, IGNCR }, /* SUSv2 */
|
|
{ "ignpar", T_I, IGNPAR }, /* SUSv2 */
|
|
{ "imaxbel", T_I, IMAXBEL },
|
|
{ "inlcr", T_I, INLCR }, /* SUSv2 */
|
|
{ "inpck", T_I, INPCK },
|
|
{ "intr", T_K, VINTR }, /* SUSv2 */
|
|
{ "isig", T_L, ISIG }, /* SUSv2 */
|
|
{ "istrip", T_I, ISTRIP }, /* SUSv2 */
|
|
#if 0
|
|
{ "iuclc", T_I, IUCLC }, /* SUSv2 LEGACY */
|
|
#endif
|
|
{ "ixany", T_I, IXANY }, /* SUSv2 Ex */
|
|
{ "ixoff", T_I, IXOFF },
|
|
{ "ixon", T_I, IXON },
|
|
{ "kill", T_K, VKILL }, /* SUSv2 */
|
|
#if !__INTERIX
|
|
{ "nl0", T_O, NL0 }, /* SUSv2 Ex */
|
|
{ "nl1", T_O, NL1 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "noflsh", T_L, NOFLSH }, /* SUSv2 */
|
|
{ "ocrnl", T_O, OCRNL }, /* SUSv2 Ex */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "ofdel", T_O, OFDEL }, /* SUSv3 */
|
|
#endif
|
|
#if !__INTERIX
|
|
{ "ofill", T_O, OFILL }, /* SUSv2 Ex */
|
|
#endif
|
|
#if 0
|
|
{ "olcuc", T_O, OLCUC }, /* SUSv2 LEGACY */
|
|
#endif
|
|
{ "onlcr", T_O, ONLCR }, /* SUSv2 Ex */
|
|
{ "onlret", T_O, ONLRET }, /* SUSv2 Ex */
|
|
{ "onocr", T_O, ONOCR }, /* SUSv2 Ex */
|
|
{ "opost", T_O, OPOST }, /* SUSv2 */
|
|
{ "parenb", T_C, PARENB }, /* SUSv2 */
|
|
{ "parmrk", T_I, PARMRK }, /* SUSv2 */
|
|
{ "parodd", T_C, PARODD }, /* SUSv2 */
|
|
#if _XOPEN_SOURCE >= 600
|
|
{ "prterase", T_L, PRTERASE }, /* SUSv3 */
|
|
#endif
|
|
{ "quit", T_K, VQUIT }, /* SUSv2 */
|
|
{ "size", T_SIZE, 0 },
|
|
{ "start", T_K, VSTART }, /* SUSv2 */
|
|
{ "stop", T_K, VSTOP }, /* SUSv2 */
|
|
{ "susp", T_K, VSUSP }, /* SUSv2 */
|
|
#if !__INTERIX
|
|
{ "tab0", T_O, TAB0 }, /* SUSv2 Ex */
|
|
{ "tab1", T_O, TAB1 }, /* SUSv2 Ex */
|
|
{ "tab2", T_O, TAB2 }, /* SUSv2 Ex */
|
|
{ "tab3", T_O, TAB3 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "tostop", T_L, TOSTOP }, /* SUSv2 */
|
|
#if !__INTERIX
|
|
{ "vt0", T_O, VT0 }, /* SUSv2 Ex */
|
|
{ "vt1", T_O, VT1 }, /* SUSv2 Ex */
|
|
#endif
|
|
{ "werase", T_K, VWERASE },
|
|
#if 0
|
|
{ "xcase", T_L, XCASE }, /* SUSv2 LEGACY */
|
|
#endif
|
|
{ 0, 0, 0 },
|
|
};
|
|
struct termios ts;
|
|
|
|
DBUG_ENTER("init_pty");
|
|
|
|
/* get terminal attributes */
|
|
tcgetattr(pty, &ts);
|
|
|
|
/* parse attribute string */
|
|
for (; attr && *attr; (attr = strchr(attr, ':')) && ++attr) {
|
|
size_t attr_len;
|
|
int negate;
|
|
const struct flag_s *s;
|
|
tcflag_t *which;
|
|
int ch;
|
|
int cmp;
|
|
|
|
if (*attr == ':')
|
|
continue;
|
|
|
|
/* check for negated attributes */
|
|
if ((negate = *attr == '-'))
|
|
attr++;
|
|
attr_len = strcspn(attr, "=");
|
|
|
|
/* find attribute by name */
|
|
for (s = flag; s->name; s++) {
|
|
if ((cmp = s->name[0] - *attr) < 0)
|
|
continue;
|
|
if ((cmp = strncmp(s->name, attr, attr_len)) < 0)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
if (cmp != 0) {
|
|
DBUG_PRINT("error", ("invalid attribute name %s", attr));
|
|
continue;
|
|
}
|
|
|
|
DBUG_PRINT("info", ("attr=%s", s->name));
|
|
|
|
/* attribute found */
|
|
switch (s->type) {
|
|
case T_SIZE: { /* set terminal size */
|
|
int h, w;
|
|
if (2 == sscanf(attr+attr_len, "=%d,%d", &h, &w))
|
|
resize(pty, h, w);
|
|
which = 0;
|
|
break;
|
|
}
|
|
case T_K: /* set special character */
|
|
if (attr[attr_len] == '=') {
|
|
ch = parse_char(attr + attr_len + 1);
|
|
DBUG_PRINT("info", ("parse_char() returns %d", ch));
|
|
ts.c_cc[s->v] = (cc_t)ch;
|
|
}
|
|
which = 0;
|
|
break;
|
|
case T_I: which = &ts.c_iflag; break;
|
|
case T_O: which = &ts.c_oflag; break;
|
|
case T_C: which = &ts.c_cflag; break;
|
|
case T_L: which = &ts.c_lflag; break;
|
|
}
|
|
if (which) {
|
|
if (negate) /* negate terminal setting */
|
|
*which &= ~(tcflag_t)s->v;
|
|
else /* set terminal setting */
|
|
*which |= (tcflag_t)s->v;
|
|
}
|
|
}
|
|
|
|
tcsetattr(pty, TCSANOW, &ts);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
static char *
|
|
str_dup(const char *src)
|
|
{
|
|
size_t len = strlen(src) + 1;
|
|
char *p;
|
|
if ((p = malloc(len)))
|
|
memcpy(p, src, len);
|
|
return p;
|
|
}
|
|
|
|
static int
|
|
setup_child(pid_t *pid, const char *term, const char *attr, char *const *argv)
|
|
{
|
|
int master;
|
|
char ttyname[20];
|
|
|
|
DBUG_ENTER("setup_child");
|
|
switch ((*pid = pty_fork(&master, ttyname, 0, 0))) {
|
|
case -1:
|
|
DBUG_PRINT("startup", ("forkpty: failed"));
|
|
DBUG_RETURN(-errno);
|
|
/*NOTREACHED*/
|
|
case 0: {
|
|
const char *shell;
|
|
DBUG_PROCESS("child");
|
|
|
|
DBUG_PRINT("info", ("TERM=%s", term));
|
|
if (term) setenv("TERM", term, 1);
|
|
|
|
DBUG_PRINT("info", ("attributes=%s", attr));
|
|
if (attr) init_pty(0, attr);
|
|
|
|
if (!(shell = argv[0])) {
|
|
char *s0;
|
|
uid_t uid;
|
|
struct passwd *pw;
|
|
shell = "/bin/bash";
|
|
s0 = "-bash";
|
|
/* get user's login shell */
|
|
if (!(pw = getpwuid(uid = getuid()))) {
|
|
DBUG_PRINT("error", ("getpwuid(%ld) failed: %s",
|
|
uid, strerror(errno)));
|
|
}
|
|
else if (!(shell = pw->pw_shell) || *shell != '/') {
|
|
DBUG_PRINT("error", ("bad shell for user id %ld", uid));
|
|
}
|
|
else {
|
|
DBUG_PRINT("info", ("got shell %s", shell));
|
|
s0 = strrchr(shell, '/');
|
|
s0 = str_dup(s0);
|
|
assert(s0 != 0);
|
|
s0[0] = '-';
|
|
}
|
|
DBUG_PRINT("info", ("startup %s (%s)", shell, s0));
|
|
execl(shell, s0, (char *)0);
|
|
}
|
|
else {
|
|
DBUG_PRINT("info", ("startup %s", *argv));
|
|
execvp(*argv, argv);
|
|
}
|
|
|
|
DBUG_PRINT("error", ("exec* failed: %s", strerror(errno)));
|
|
perror(shell);
|
|
exit(111);
|
|
/*NOTREACHED*/ }
|
|
}
|
|
DBUG_PRINT("startup", ("forkpty:pid=%ld:master=%d:ttyname=%s",
|
|
(long int)*pid, master, ttyname));
|
|
DBUG_RETURN(master);
|
|
}
|
|
|
|
static void
|
|
process_message(Buffer b, int pty)
|
|
{
|
|
Message m;
|
|
|
|
DBUG_ENTER("process_message");
|
|
switch (message_get(&m, b->data, b->len)) {
|
|
case -1:
|
|
DBUG_PRINT("msg", ("invalid message"));
|
|
buffer_consumed(b, b->len);
|
|
break;
|
|
case 0:
|
|
DBUG_PRINT("msg", ("message too small"));
|
|
break;
|
|
default:
|
|
switch (m.type) {
|
|
/* the only message type yet supported */
|
|
case MSG_RESIZE:
|
|
if (pty > 0)
|
|
resize(pty, m.msg.resize.height, m.msg.resize.width);
|
|
else
|
|
DBUG_PRINT("msg", ("ignoring RESIZE on closed pty"));
|
|
break;
|
|
default:
|
|
DBUG_PRINT("msg", ("unknown message type: %d", m.type));
|
|
break;
|
|
}
|
|
buffer_consumed(b, m.size);
|
|
break;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/* These need to be global so that the signal handler has access to them. */
|
|
static int t; /* pty descriptor */
|
|
static pid_t child;
|
|
static volatile sig_atomic_t child_signalled;
|
|
static int exit_status;
|
|
|
|
#define PID_NONE 1
|
|
/* child is set to 1 when it is dead. Hopefully, an accidental kill(1,HUP)
|
|
* will have no effect as pid 1 (init) does not exist under Cygwin.
|
|
*/
|
|
#define child_alive() (child != PID_NONE)
|
|
|
|
static void
|
|
handle_sigchld(int sig)
|
|
{
|
|
child_signalled = sig;
|
|
}
|
|
|
|
static void
|
|
check_child(void)
|
|
{
|
|
int status;
|
|
DBUG_ENTER("check_child");
|
|
DBUG_PRINT("sig", ("child signalled: %d", child_signalled));
|
|
switch (waitpid(child, &status, WNOHANG)) {
|
|
case -1:
|
|
case 0: break;
|
|
default:
|
|
if (WIFEXITED(status))
|
|
exit_status = WEXITSTATUS(status);
|
|
else
|
|
exit_status = 112;
|
|
}
|
|
DBUG_PRINT("sig", ("child is gone"));
|
|
child = PID_NONE;
|
|
child_signalled = 0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
static void
|
|
setnonblock(int d)
|
|
{
|
|
fcntl(d, F_SETFL, O_NONBLOCK | fcntl(d, F_GETFL));
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
static struct termios save_termios;
|
|
static void raw(void)
|
|
{
|
|
struct termios termios;
|
|
assert(tcgetattr(STDOUT_FILENO, &save_termios) == 0);
|
|
termios = save_termios;
|
|
termios.c_lflag &= ~(ECHO | ICANON);
|
|
termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
termios.c_oflag &= ~(OPOST);
|
|
termios.c_cc[VMIN] = 1;
|
|
termios.c_cc[VTIME] = 0;
|
|
assert(tcsetattr(STDOUT_FILENO, TCSAFLUSH, &termios) == 0);
|
|
}
|
|
static void restore(void)
|
|
{
|
|
assert(tcsetattr(STDOUT_FILENO, TCSAFLUSH, &save_termios) == 0);
|
|
}
|
|
#endif
|
|
|
|
#define obligatory_max(a,b) ((a)>(b)?(a):(b))
|
|
|
|
int
|
|
main(int argc, char *const *argv)
|
|
{
|
|
int
|
|
c, /* control descriptor (stdin) */
|
|
s; /* socket descriptor (PuTTY) */
|
|
Buffer
|
|
cbuf, /* control buffer */
|
|
pbuf, /* pty buffer */
|
|
sbuf; /* socket buffer */
|
|
|
|
DBUG_INIT_ENV("main",argv[0],"DBUG_OPTS");
|
|
|
|
#ifndef DBUG_OFF
|
|
setvbuf(DBUG_FILE, 0, _IONBF, 0);
|
|
#endif
|
|
|
|
/* General steps:
|
|
1. connect to cygterm backend
|
|
2. create pty
|
|
3. fork child process (/bin/bash)
|
|
4. select on pty, cygterm backend forwarding pty data and messages
|
|
*/
|
|
|
|
if (argc < 4) {
|
|
DBUG_PRINT("error", ("Too few arguments"));
|
|
DBUG_RETURN(CthelperInvalidUsage);
|
|
}
|
|
|
|
DBUG_PRINT("startup", ("isatty: (%d,%d,%d)",
|
|
isatty(STDIN_FILENO), isatty(STDOUT_FILENO), isatty(STDERR_FILENO)));
|
|
DBUG_PRINT("startup", (
|
|
"cmdline: [%s] %s %s %s ...", argv[0], argv[1], argv[2], argv[3]));
|
|
{
|
|
extern char **environ;
|
|
char **envp;
|
|
for (envp = environ; *envp; envp++)
|
|
DBUG_PRINT("startup", ("%s", *envp));
|
|
}
|
|
|
|
/* It is not necessary to close all open descriptors. There are no
|
|
* files inherited from the PuTTY process except standard input.
|
|
*/
|
|
#ifndef DEBUG
|
|
close(STDERR_FILENO);
|
|
#endif
|
|
|
|
/* Duplicate c and open /dev/null as 0 so that 0 can mean "closed". */
|
|
c = dup(STDIN_FILENO); close(STDIN_FILENO);
|
|
open("/dev/null", O_RDWR);
|
|
|
|
/* Command line:
|
|
* argv[1] = port number
|
|
* argv[2] = terminal name
|
|
* argv[3] = terminal characteristics string
|
|
* Any remaining arguments are the command to execute. If there are no
|
|
* other arguments, use the user's default login shell with a - prefix
|
|
* for its argv[0].
|
|
*/
|
|
/*
|
|
cthelper command line parameters:
|
|
|
|
cthelper PORT TERM ATTRS [COMMAND [ARGS]]
|
|
|
|
PORT
|
|
port number for PuTTY pty input data socket
|
|
TERM
|
|
name of terminal (set TERM environment variable)
|
|
ATTRS
|
|
a colon-separated list of terminal attributes
|
|
See init_pty() for details.
|
|
COMMAND
|
|
Runs COMMAND with ARGS as child process. If COMMAND is not
|
|
supplied, cthelper will run the user's login shell as specified in
|
|
/etc/passwd specifying "-" for its argv[0] as typical.
|
|
*/
|
|
|
|
|
|
/* connect to cygterm */
|
|
{
|
|
int ct_port = strtol(argv[1], 0, 0);
|
|
#ifdef DEBUG
|
|
if (ct_port == 0) {
|
|
/* For debugging purposes, make the tty we are started
|
|
* in the "socket". This allows to test cthelper without
|
|
* putty.exe */
|
|
assert(isatty(STDOUT_FILENO));
|
|
raw();
|
|
atexit(restore);
|
|
c = open("/dev/null", O_RDONLY);
|
|
s = dup(STDOUT_FILENO);
|
|
}
|
|
else
|
|
#endif
|
|
if (ct_port <= 0) {
|
|
DBUG_PRINT("startup", ("invalid port"));
|
|
DBUG_RETURN(CthelperInvalidPort);
|
|
}
|
|
DBUG_PRINT("startup", ("connect cygterm"));
|
|
if (0 > (s = connect_cygterm(ct_port))) {
|
|
DBUG_PRINT("startup", ("connect_cygterm: bad"));
|
|
DBUG_RETURN(CthelperConnectFailed);
|
|
}
|
|
DBUG_PRINT("startup", ("OK"));
|
|
}
|
|
|
|
/* initialize buffers */
|
|
DBUG_PRINT("startup", ("initialize buffers"));
|
|
BUFFER_ALLOCA(cbuf, CTLBUF);
|
|
BUFFER_ALLOCA(pbuf, PTOBUF);
|
|
BUFFER_ALLOCA(sbuf, PTIBUF);
|
|
|
|
/* set up signal handling */
|
|
signal(SIGCHLD, handle_sigchld);
|
|
|
|
/* start child process */
|
|
if (0 > (t = setup_child(&child, argv[2], argv[3], argv + 4))) {
|
|
DBUG_PRINT("startup", ("setup_child failed: %s", strerror(-t)));
|
|
DBUG_RETURN(CthelperPtyforkFailure);
|
|
}
|
|
|
|
/* To explain what is happening here:
|
|
* 's' is the socket between PuTTY and cthelper; it is read to get
|
|
* input for the tty and written to display output from the pty.
|
|
* 't' is the pseudo terminal; it is read to get pty input which is sent to
|
|
* PuTTY and written to pass input from PuTTY to the pty.
|
|
* 'c' is standard input, which is a one-way anonymous pipe from PuTTY.
|
|
* It is read to receive special messages from PuTTY such as
|
|
* terminal resize events.
|
|
*
|
|
* This is the flow of data through the buffers:
|
|
* s => sbuf => t
|
|
* t => pbuf => s
|
|
* c => cbuf => process_message()
|
|
*
|
|
* When 't' is closed, we close(s) to signal PuTTY we are done.
|
|
* When 's' is closed, we kill(child, HUP) to kill the child process.
|
|
*/
|
|
|
|
setnonblock(c);
|
|
setnonblock(s);
|
|
setnonblock(t);
|
|
|
|
DBUG_PRINT("info", ("c==%d, s==%d, t==%d", c, s, t));
|
|
/* allow easy select() and FD_ISSET() stuff */
|
|
assert(0 < c && c < s && s < t);
|
|
DBUG_PRINT("startup", ("starting select loop"));
|
|
while (s || t) {
|
|
int n = 0;
|
|
fd_set r, w;
|
|
DBUG_ENTER("select");
|
|
FD_ZERO(&r); FD_ZERO(&w);
|
|
if (c && !buffer_isfull(cbuf)) { FD_SET(c, &r); n = c; }
|
|
if (s && !buffer_isfull(sbuf)) { FD_SET(s, &r); n = s; }
|
|
if (s && !buffer_isempty(pbuf)) { FD_SET(s, &w); n = s; }
|
|
if (t && !buffer_isfull(pbuf)) { FD_SET(t, &r); n = t; }
|
|
if (t && !buffer_isempty(sbuf)) { FD_SET(t, &w); n = t; }
|
|
switch (n = select(n + 1, &r, &w, 0, 0)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("%s", strerror(errno)));
|
|
if (errno != EINTR) {
|
|
/* Something bad happened */
|
|
close(c); c = 0;
|
|
close(s); s = 0;
|
|
close(t); t = 0;
|
|
}
|
|
break;
|
|
case 0:
|
|
DBUG_PRINT("info", ("select timeout"));
|
|
break;
|
|
default:
|
|
DBUG_PRINT("info", ("%d ready descriptors [[r==%lx,w==%lx]]", n, *(unsigned long *)&r, *(unsigned long *)&w));
|
|
if (FD_ISSET(c, &r)) {
|
|
DBUG_ENTER("c=>cbuf");
|
|
switch (buffer_read(cbuf, c)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("error reading c: %s", strerror(errno)));
|
|
if (errno == EINTR) break;
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
/* PuTTY closed the message pipe */
|
|
DBUG_PRINT("io", ("c closed"));
|
|
close(c); c = 0;
|
|
break;
|
|
default:
|
|
DBUG_PRINT("io", ("cbuf => process_message()"));
|
|
process_message(cbuf, t);
|
|
break;
|
|
}
|
|
DBUG_LEAVE;
|
|
if (!--n) break;
|
|
}
|
|
if (FD_ISSET(s, &r)) {
|
|
DBUG_ENTER("s=>sbuf");
|
|
switch (buffer_read(sbuf, s)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("error reading s: %s", strerror(errno)));
|
|
if (errno == EINTR) break;
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
/* PuTTY closed the socket */
|
|
DBUG_PRINT("io", ("s closed"));
|
|
close(s); s = 0;
|
|
break;
|
|
default:
|
|
FD_SET(t, &w);
|
|
break;
|
|
}
|
|
DBUG_LEAVE;
|
|
if (!--n) break;
|
|
}
|
|
if (FD_ISSET(t, &r)) {
|
|
DBUG_ENTER("t=>pbuf");
|
|
switch (buffer_read(pbuf, t)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("error reading t: %s", strerror(errno)));
|
|
if (errno == EINTR) break;
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
/* pty closed */
|
|
DBUG_PRINT("io", ("t closed"));
|
|
if (!FD_ISSET(t, &w)) {
|
|
close(t); t = 0;
|
|
}
|
|
break;
|
|
default:
|
|
FD_SET(s, &w);
|
|
break;
|
|
}
|
|
DBUG_LEAVE;
|
|
if (!--n) break;
|
|
}
|
|
if (FD_ISSET(t, &w)) {
|
|
DBUG_ENTER("sbuf=>t");
|
|
switch (buffer_write(sbuf, t)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("error writing t: %s", strerror(errno)));
|
|
if (errno == EINTR) break;
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
/* pty closed */
|
|
DBUG_PRINT("io", ("t closed"));
|
|
close(t); t = 0;
|
|
break;
|
|
}
|
|
DBUG_LEAVE;
|
|
if (!--n) break;
|
|
}
|
|
if (FD_ISSET(s, &w)) {
|
|
DBUG_ENTER("pbuf=>s");
|
|
switch (buffer_write(pbuf, s)) {
|
|
case -1:
|
|
DBUG_PRINT("error", ("error writing s: %s", strerror(errno)));
|
|
if (errno == EINTR) break;
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
/* PuTTY closed the socket */
|
|
DBUG_PRINT("io", ("s closed"));
|
|
close(s); s = 0;
|
|
break;
|
|
}
|
|
DBUG_LEAVE;
|
|
if (!--n) break;
|
|
}
|
|
DBUG_PRINT("info", ("[[n==%d,r==%lx,w==%lx]]", n, *(unsigned long *)&r, *(unsigned long *)&w));
|
|
assert(n == 0);
|
|
break;
|
|
}
|
|
|
|
if (child_signalled) check_child();
|
|
|
|
if (!t && buffer_isempty(pbuf)) {
|
|
DBUG_PRINT("info", ("shutdown socket"));
|
|
shutdown(s, SHUT_WR);
|
|
}
|
|
|
|
if (!s && buffer_isempty(sbuf) && child_alive()) {
|
|
DBUG_PRINT("sig", ("kill child"));
|
|
kill(child, SIGHUP);
|
|
/* handle_sigchld() will close(t) */
|
|
}
|
|
DBUG_LEAVE;
|
|
}
|
|
DBUG_PRINT("info", ("end of select loop"));
|
|
|
|
/* ensure child process killed */
|
|
/* XXX I'm not sure if all of this is necessary, but it probably won't
|
|
* hurt anything. */
|
|
if (child_alive() && sleep(1) == 0) {
|
|
DBUG_PRINT("sig", ("waiting for child"));
|
|
waitpid(child, 0, WNOHANG);
|
|
}
|
|
|
|
DBUG_PRINT("info", ("goodbye"));
|
|
if (exit_status == 111)
|
|
DBUG_RETURN(CthelperExecFailure);
|
|
DBUG_RETURN(EXIT_SUCCESS);
|
|
}
|
|
|
|
/* ex:set sw=4 smarttab: */
|