558 lines
12 KiB
C
558 lines
12 KiB
C
#include <stdio.h> /* sprintf */
|
|
#include <string.h>
|
|
#include <limits.h> /* INT_MAX */
|
|
#include "putty.h"
|
|
#include "cthelper/cthelper.h"
|
|
#include "cthelper/message.h"
|
|
|
|
#define CYGTERM_MAX_BACKLOG 16384
|
|
|
|
#define CYGTERM_NAME "Cygterm"
|
|
|
|
#ifdef __INTERIX
|
|
#define CTHELPER "posix.exe /u /c cthelper.exe"
|
|
#else
|
|
#define CTHELPER "cthelper"
|
|
#endif
|
|
|
|
#if !defined(DEBUG)
|
|
#define cygterm_debug(f,...)
|
|
#elif !defined(cygterm_debug)
|
|
#define cygterm_debug(f,...) debug(("%s:%d:%s: "f"\n",__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__))
|
|
#endif
|
|
|
|
#define putenv _putenv
|
|
|
|
typedef struct cygterm_backend_data {
|
|
const struct plug_function_table *fn;
|
|
void *frontend;
|
|
Socket a;
|
|
Socket s;
|
|
PROCESS_INFORMATION pi;
|
|
HANDLE ctl;
|
|
Config cfg;
|
|
int bufsize;
|
|
int editing, echoing;
|
|
int exitcode;
|
|
} *Local;
|
|
|
|
|
|
/* Plug functions for cthelper data connection */
|
|
static void
|
|
cygterm_log(Plug p, int type, SockAddr addr, int port, const char *error_msg, int error_code)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
|
|
static int
|
|
cygterm_closing(Plug plug, const char *error_msg, int error_code, int calling_back)
|
|
{
|
|
Local local = (Local)plug;
|
|
cygterm_debug("top");
|
|
if (local->s) {
|
|
sk_close(local->s);
|
|
local->s = NULL;
|
|
}
|
|
/* check for errors from cthelper */
|
|
CloseHandle(local->ctl);
|
|
/* wait for cthelper */
|
|
if (local->pi.hProcess != INVALID_HANDLE_VALUE) {
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(local->pi.hProcess, 2000)) {
|
|
DWORD status;
|
|
GetExitCodeProcess(local->pi.hProcess, &status);
|
|
switch (status) {
|
|
case CthelperSuccess:
|
|
break;
|
|
case CthelperInvalidUsage:
|
|
case CthelperInvalidPort:
|
|
case CthelperConnectFailed:
|
|
local->exitcode = INT_MAX;
|
|
error_msg = "Internal error";
|
|
break;
|
|
case CthelperPtyforkFailure:
|
|
local->exitcode = INT_MAX;
|
|
error_msg = "Failed to allocate pseudoterminal";
|
|
break;
|
|
case CthelperExecFailure:
|
|
local->exitcode = INT_MAX;
|
|
error_msg = "Failed to execute command";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* this calls cygterm_exitcode() */
|
|
notify_remote_exit(local->frontend);
|
|
if (error_msg) {
|
|
cygterm_debug("error_msg: %s", error_msg);
|
|
connection_fatal(local->frontend, "%s", error_msg);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cygterm_receive(Plug plug, int urgent, char *data, int len)
|
|
{
|
|
Local local = (Local)plug;
|
|
int backlog;
|
|
cygterm_debug("backend -> display %u", len);
|
|
// dmemdump(data, len);
|
|
backlog = from_backend(local->frontend, 0, data, len);
|
|
// dmemdumpl(data, len);
|
|
sk_set_frozen(local->s, backlog > CYGTERM_MAX_BACKLOG);
|
|
cygterm_debug("OK");
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
cygterm_sent(Plug plug, int bufsize)
|
|
{
|
|
Local local = (Local)plug;
|
|
local->bufsize = bufsize;
|
|
}
|
|
|
|
static void
|
|
cygterm_size(void *handle, int width, int height);
|
|
|
|
static int
|
|
cygterm_accepting(Plug plug, OSSocket sock)
|
|
{
|
|
Local local = (Local)plug;
|
|
cygterm_debug("top");
|
|
local->s = sk_register(sock, plug);
|
|
sk_set_frozen(local->s, 0);
|
|
/* Reset terminal size */
|
|
cygterm_size(local, local->cfg.width, local->cfg.height);
|
|
cygterm_debug("OK");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static char *getCygwinBin(void);
|
|
static void appendPath(const char *append);
|
|
static size_t makeAttributes(char *buf, Config *cfg);
|
|
static const char *spawnChild(char *cmd, LPPROCESS_INFORMATION ppi, PHANDLE pin);
|
|
|
|
/* Backend functions for the cygterm backend */
|
|
|
|
static const char *
|
|
cygterm_init(void *frontend_handle, void **backend_handle,
|
|
Config *cfg,
|
|
char *unused_host, int unused_port,
|
|
char **realhost, int nodelay, int keepalive)
|
|
{
|
|
/* XXX: I'm not sure if it is OK to overload Plug like this.
|
|
* cygterm_accepting should only be used for the listening socket
|
|
* (local->a) while the cygterm_closing, cygterm_receive, and cygterm_sent
|
|
* should be used only for the actual connection (local->s).
|
|
*/
|
|
static const struct plug_function_table fn_table = {
|
|
cygterm_log,
|
|
cygterm_closing,
|
|
cygterm_receive,
|
|
cygterm_sent,
|
|
cygterm_accepting
|
|
};
|
|
Local local;
|
|
const char *command;
|
|
char cmdline[2 * MAX_PATH];
|
|
int cport;
|
|
const char *err;
|
|
int cmdlinelen;
|
|
|
|
cygterm_debug("top");
|
|
|
|
local = snew(struct cygterm_backend_data);
|
|
local->fn = &fn_table;
|
|
local->a = NULL;
|
|
local->s = NULL;
|
|
local->cfg = *cfg;
|
|
local->editing = 0;
|
|
local->echoing = 0;
|
|
local->exitcode = 0;
|
|
*backend_handle = local;
|
|
|
|
local->frontend = frontend_handle;
|
|
|
|
/* set up listen socket for communication with child */
|
|
cygterm_debug("setupCygTerm");
|
|
|
|
/* let sk use INADDR_LOOPBACK and let WinSock choose a port */
|
|
local->a = sk_newlistener(0, 0, (Plug)local, 1, ADDRTYPE_IPV4);
|
|
if ((err = sk_socket_error(local->a)) != NULL)
|
|
goto fail_free;
|
|
|
|
/* now, get the port that WinSock chose */
|
|
/* XXX: Is there another function in PuTTY to do this? */
|
|
cygterm_debug("getting port");
|
|
cport = sk_getport(local->a);
|
|
if (cport == -1) {
|
|
err = "Failed to get port number for cthelper";
|
|
goto fail_close;
|
|
}
|
|
|
|
if (strchr(local->cfg.termtype, ' ')) {
|
|
err = "term type contains spaces";
|
|
goto fail_close;
|
|
}
|
|
|
|
/* Build cthelper command line */
|
|
cmdlinelen = sprintf(cmdline, CTHELPER" %u %s ", cport, local->cfg.termtype);
|
|
cmdlinelen += makeAttributes(cmdline + cmdlinelen, &local->cfg);
|
|
|
|
command = cfg->cygcmd;
|
|
cygterm_debug("command is :%s:", command);
|
|
/* A command of "." or "-" tells us to pass no command arguments to
|
|
* cthelper which will then run the user's shell under Cygwin. */
|
|
if ((command[0]=='-'||command[0]=='.') && command[1]=='\0')
|
|
;
|
|
else if (cmdlinelen + strlen(command) + 2 > sizeof cmdline) {
|
|
err = "command is too long";
|
|
goto fail_close;
|
|
}
|
|
else {
|
|
cmdlinelen += sprintf(cmdline + cmdlinelen, " %s", command);
|
|
}
|
|
|
|
/* Add the Cygwin /bin path to the PATH. */
|
|
if (cfg->cygautopath) {
|
|
char *cygwinBinPath = getCygwinBin();
|
|
if (!cygwinBinPath) {
|
|
/* we'll try anyway */
|
|
cygterm_debug("cygwin bin directory not found");
|
|
}
|
|
else {
|
|
cygterm_debug("found cygwin directory: %s", cygwinBinPath);
|
|
appendPath(cygwinBinPath);
|
|
sfree(cygwinBinPath);
|
|
}
|
|
}
|
|
|
|
cygterm_debug("starting cthelper: %s", cmdline);
|
|
if ((err = spawnChild(cmdline, &local->pi, &local->ctl)))
|
|
goto fail_close;
|
|
|
|
/* This should be set to the local hostname, Apparently, realhost is used
|
|
* only to set the window title.
|
|
*/
|
|
strcpy(*realhost = smalloc(sizeof CYGTERM_NAME), CYGTERM_NAME);
|
|
cygterm_debug("OK");
|
|
return 0;
|
|
|
|
fail_close:
|
|
sk_close(local->a);
|
|
fail_free:
|
|
sfree(local);
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
cygterm_free(void *handle)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
sfree(local);
|
|
}
|
|
|
|
static void
|
|
cygterm_reconfig(void *handle, Config *cfg)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
local->cfg = *cfg;
|
|
}
|
|
|
|
static int
|
|
cygterm_send(void *handle, char *buf, int len)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("frontend -> pty %u", len);
|
|
// dmemdump(buf, len);
|
|
#if 0
|
|
/* HACK */
|
|
{
|
|
int i;
|
|
for (i = 0; i < len - 1; i++)
|
|
if (buf[i] == 033 && !(buf[i+1]&0x80)) {
|
|
memmove(buf + i, buf + i + 1, --len - i);
|
|
buf[i] |= 0x80;
|
|
}
|
|
}
|
|
#endif
|
|
if (local->s != 0)
|
|
local->bufsize = sk_write(local->s, buf, len);
|
|
cygterm_debug("OK");
|
|
return local->bufsize;
|
|
}
|
|
|
|
static int
|
|
cygterm_sendbuffer(void *handle)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
return local->bufsize;
|
|
}
|
|
|
|
static void
|
|
cygterm_size(void *handle, int width, int height)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
cygterm_debug("size=%d,%d (last=%d,%d)",
|
|
width, height, local->cfg.width, local->cfg.height);
|
|
local->cfg.width = width;
|
|
local->cfg.height = height;
|
|
if (local->s) {
|
|
DWORD n;
|
|
Message m;
|
|
m.size = MESSAGE_MIN + sizeof(m.msg);
|
|
m.type = MSG_RESIZE;
|
|
m.msg.resize.width = width;
|
|
m.msg.resize.height = height;
|
|
cygterm_debug("WriteFile %p %p:%u", local->ctl, &m, m.size);
|
|
WriteFile(local->ctl, (const char *)&m, m.size, &n, 0);
|
|
cygterm_debug("WriteFile returns %d");
|
|
}
|
|
}
|
|
|
|
static void
|
|
cygterm_special(void *handle, Telnet_Special code)
|
|
{
|
|
cygterm_debug("top");
|
|
}
|
|
|
|
static const struct telnet_special *
|
|
cygterm_get_specials(void *handle)
|
|
{
|
|
cygterm_debug("top");
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
cygterm_connected(void *handle)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
return local->s != NULL;
|
|
}
|
|
|
|
static int
|
|
cygterm_exitcode(void *handle)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
return local->exitcode;
|
|
}
|
|
|
|
static int
|
|
cygterm_sendok(void *handle)
|
|
{
|
|
cygterm_debug("top");
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
cygterm_unthrottle(void *handle, int backlog)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("top");
|
|
sk_set_frozen(local->s, backlog > CYGTERM_MAX_BACKLOG);
|
|
}
|
|
|
|
static int
|
|
cygterm_ldisc(void *handle, int option)
|
|
{
|
|
Local local = handle;
|
|
cygterm_debug("cygterm_ldisc: %d", option);
|
|
switch (option) {
|
|
case LD_EDIT:
|
|
return local->editing;
|
|
case LD_ECHO:
|
|
return local->echoing;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
cygterm_provide_ldisc(void *handle, void *ldisc)
|
|
{
|
|
cygterm_debug("top");
|
|
}
|
|
|
|
static void
|
|
cygterm_provide_logctx(void *handle, void *logctx)
|
|
{
|
|
cygterm_debug("top");
|
|
}
|
|
|
|
static int
|
|
cygterm_cfg_info(void *handle)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Backend cygterm_backend = {
|
|
cygterm_init,
|
|
cygterm_free,
|
|
cygterm_reconfig,
|
|
cygterm_send,
|
|
cygterm_sendbuffer,
|
|
cygterm_size,
|
|
cygterm_special,
|
|
cygterm_get_specials,
|
|
cygterm_connected,
|
|
cygterm_exitcode,
|
|
cygterm_sendok,
|
|
cygterm_ldisc,
|
|
cygterm_provide_ldisc,
|
|
cygterm_provide_logctx,
|
|
cygterm_unthrottle,
|
|
cygterm_cfg_info,
|
|
1
|
|
};
|
|
|
|
|
|
/* like strcpy(), but return pointer to terminating null character */
|
|
static char *
|
|
strecpy(char *d,const char *s)
|
|
{
|
|
while ((*d = *s++))
|
|
d++;
|
|
return d;
|
|
}
|
|
|
|
/* Make cthelper attribute string from PuTTY Config */
|
|
static size_t
|
|
makeAttributes(char *buf, Config *cfg)
|
|
{
|
|
char *e = buf;
|
|
|
|
if (cfg->bksp_is_delete)
|
|
e = strecpy(e, ":erase=^?");
|
|
else
|
|
e = strecpy(e, ":erase=^H");
|
|
|
|
e += sprintf(e, ":size=%d,%d", cfg->height, cfg->width);
|
|
|
|
/* TODO: other options? localedit? localecho? */
|
|
|
|
return e - buf;
|
|
}
|
|
|
|
|
|
/* Utility functions for spawning cthelper process */
|
|
static BOOL
|
|
getRegistry(char *valueData, LPDWORD psize, HKEY key, const char *subKey, const char *valueName)
|
|
{
|
|
HKEY k;
|
|
LONG ret;
|
|
|
|
if (ERROR_SUCCESS != (ret = RegOpenKey(key, subKey, &k)))
|
|
return ret;
|
|
|
|
ERROR_SUCCESS == (ret = RegQueryInfoKey(k, 0, 0, 0, 0, 0, 0, 0, 0, psize, 0, 0))
|
|
&& ERROR_SUCCESS == (ret = RegQueryValueEx(k, valueName, 0, 0, valueData, psize));
|
|
|
|
RegCloseKey(k);
|
|
return ret;
|
|
}
|
|
|
|
/* As of Cygwin 1.7, one of these keys contains the Cygwin install root. */
|
|
#define CYGWIN_U_SETUP_ROOTDIR \
|
|
HKEY_CURRENT_USER,\
|
|
"Software\\Cygwin\\setup",\
|
|
"rootdir"
|
|
#define CYGWIN_S_SETUP_ROOTDIR \
|
|
HKEY_LOCAL_MACHINE,\
|
|
"Software\\Cygwin\\setup",\
|
|
"rootdir"
|
|
|
|
static char *
|
|
getCygwinBin(void)
|
|
{
|
|
char *dir;
|
|
DWORD size = MAX_PATH;
|
|
|
|
dir = smalloc(size);
|
|
dir[0] = '\0';
|
|
|
|
if (ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_U_SETUP_ROOTDIR)
|
|
|| ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_S_SETUP_ROOTDIR))
|
|
{
|
|
strcat(dir, "\\bin");
|
|
}
|
|
else
|
|
{
|
|
sfree(dir);
|
|
dir = 0;
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
static void
|
|
appendPath(const char *append)
|
|
{
|
|
char *path;
|
|
char *newPath;
|
|
|
|
cygterm_debug("getting PATH");
|
|
if (!(path = getenv("PATH"))) {
|
|
cygterm_debug("hmm.. PATH not set");
|
|
path = "";
|
|
}
|
|
cygterm_debug("alloc newPath");
|
|
newPath = smalloc(5 + strlen(append) + 1 + strlen(path) + 1);
|
|
cygterm_debug("init newPath");
|
|
sprintf(newPath, "PATH=%s;%s", path, append);
|
|
cygterm_debug("set newPath");
|
|
putenv(newPath);
|
|
cygterm_debug("free newPath");
|
|
sfree(newPath);
|
|
}
|
|
|
|
static const char *
|
|
spawnChild(char *cmd, LPPROCESS_INFORMATION ppi, PHANDLE pin)
|
|
{
|
|
STARTUPINFO si = {sizeof(si)};
|
|
SECURITY_ATTRIBUTES sa = {sizeof(sa)};
|
|
HANDLE in;
|
|
|
|
/* Create an anonymous pipe over which to send events such as resize */
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
if (!CreatePipe(&in, pin, &sa, 1))
|
|
return "failed to create event pipe";
|
|
|
|
/* cthelper will use stdin to get event messages */
|
|
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
|
|
si.hStdInput = in;
|
|
si.wShowWindow = SW_HIDE;
|
|
|
|
/* We allow cthelper to inherit handles. I have no idea if there are
|
|
* other inheritable handles in PuTTY that this will effect. cthelper
|
|
* will attempt to close all open descriptors.
|
|
*/
|
|
if (!CreateProcess(
|
|
NULL, cmd, /* command line */
|
|
NULL, NULL, /* no process or thread security attributes */
|
|
TRUE, /* inherit handles */
|
|
CREATE_NEW_CONSOLE, /* create a new console window */
|
|
NULL, /* use parent environment */
|
|
0, /* use parent working directory */
|
|
&si, /* STARTUPINFO sets stdin to read end of pipe */
|
|
ppi))
|
|
{
|
|
CloseHandle(in);
|
|
CloseHandle(*pin);
|
|
*pin = INVALID_HANDLE_VALUE;
|
|
return "failed to run cthelper";
|
|
}
|
|
|
|
/* close the read end of the pipe */
|
|
CloseHandle(in);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ex:set ts=4 sw=4: */
|