Files
KiTTY/cygterm/cygterm.c
T
2019-04-27 23:49:32 +02:00

655 lines
16 KiB
C

#ifdef CYGTERMPORT
#include <stdio.h> /* sprintf */
#include <string.h>
#include <limits.h> /* INT_MAX */
#include "putty.h"
#include "cthelper/cthelper.h"
#include "cthelper/message.h"
void SearchCtHelper( void ) ;
#define CYGTERM_MAX_BACKLOG 16384
#define CYGTERM_NAME "Cygterm"
#ifdef __INTERIX
#define CTHELPER "posix.exe /u /c cthelper.exe"
#define CTHELPER64 "posix.exe /u /c cthelper64.exe"
#else
#define CTHELPER "cthelper"
#define CTHELPER64 "cthelper64"
#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
size_t win_seat_output_local(Seat *seat, bool is_stderr, const void *data, size_t len) ;
typedef struct cygterm_backend_data {
//const struct plug_function_table *fn;
//void *frontend;
Socket *a;
Socket *s;
Seat *seat;
LogContext *logctx;
PROCESS_INFORMATION pi;
HANDLE ctl;
Conf * conf;
Plug plug;
Backend backend;
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, bool calling_back)
{
//Local local = (Local)plug;
Local *local = container_of(plug,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);
seat_notify_remote_exit(local->seat);
if (error_msg) {
cygterm_debug("error_msg: %s", error_msg);
//connection_fatal(local->frontend, "%s", error_msg);
seat_connection_fatal(local->seat, "%s", error_msg);
}
return 0;
}
static int cygterm_receive(Plug * plug, int urgent, const char *data, size_t len)
{
//Local local = (Local)plug;
Local *local = container_of(plug,Local,plug);
int backlog;
cygterm_debug("backend -> display %u", len);
// dmemdump(data, len);
//backlog = from_backend(local->frontend, 0, data, len);
backlog = win_seat_output_local(local->seat,false,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, size_t bufsize)
{
//Local local = (Local)plug;
Local *local = container_of(plug,Local,plug);
local->bufsize = bufsize;
}
static void
cygterm_size(void *handle, int width, int height);
Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug);
static int
cygterm_accepting(Plug * plug, accept_fn_t constructor, accept_ctx_t ctx)
{
//Local local = (Local)plug;
Local *local = container_of(plug,Local,plug);
cygterm_debug("top");
local->s = constructor(ctx, plug);
sk_set_frozen(local->s, 0);
// Reset terminal size
cygterm_size(local, conf_get_int(local->conf,CONF_width), conf_get_int(local->conf,CONF_height));
cygterm_debug("OK");
return 0;
}
static char *getCygwinBin(int use64);
void appendPath(const char *append);
static size_t makeAttributes(char *buf, Conf *conf);
static const char *spawnChild(char *cmd, Conf *conf, LPPROCESS_INFORMATION ppi, PHANDLE pin);
/* Backend functions for the cygterm backend */
void RunCommand( HWND hwnd, char * cmd ) ;
static const struct PlugVtable Cygterm_plugvt = {
cygterm_log,
cygterm_closing,
cygterm_receive,
cygterm_sent,
};
/*
static const char * cygterm_init(void *frontend_handle, void **backend_handle,
Conf *conf,
const char *unused_host, int unused_port,
char **realhost, int nodelay, int keepalive)
*/
static const char * cygterm_init(Seat *seat, Backend **backend_handle,
LogContext *logctx,
Conf *conf,
const 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).
*/
Local *local;
const char *command;
char cmdline[2 * MAX_PATH];
int cport;
const char *err;
int cmdlinelen;
cygterm_debug("top");
local = snew(Local);
//local->fn = &fn_table;
local->plug.vt = &Cygterm_plugvt;
local->backend.vt = &cygterm_backend;
local->a = NULL;
local->s = NULL;
local->conf = conf;
local->editing = 0;
local->echoing = 0;
local->exitcode = 0;
*backend_handle = &local->backend;
local->seat = seat;
local->logctx = logctx;
//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);
local->a = sk_newlistener(0, 0, (Plug*)local, 1, ADDRTYPE_IPV4); // local-> ou seat ou local tout court
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(conf_get_str(local->conf,CONF_termtype), ' ')) {
err = "term type contains spaces";
goto fail_close;
}
/* Build cthelper command line */
char * CTHELPER_PATH = getenv( "CTHELPER_PATH" ) ;
if( CTHELPER_PATH==NULL ) { SearchCtHelper() ; CTHELPER_PATH = getenv( "CTHELPER_PATH" ) ; }
if( CTHELPER_PATH!=NULL ) cmdlinelen = sprintf(cmdline, "\"%s\" %u %s ", CTHELPER_PATH, cport, conf_get_str(local->conf,CONF_termtype));
else {
//cmdlinelen = sprintf(cmdline, CTHELPER" %u %s ", cport,conf_get_str(local->conf,CONF_termtype));
if(conf_get_int(conf, CONF_cygterm64)) {
cmdlinelen = sprintf(cmdline, CTHELPER64" %u %s ", cport, conf_get_str(local->conf, CONF_termtype));
}
else {
cmdlinelen = sprintf(cmdline, CTHELPER" %u %s ", cport, conf_get_str(local->conf, CONF_termtype));
}
}
cmdlinelen += makeAttributes(cmdline + cmdlinelen, local->conf);
command = conf_get_str(conf,CONF_cygcmd);
if( command[0]=='?' ) {
RunCommand(NULL,(char*)(command+1));
exit(0);
}
else {
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 (conf_get_int(conf,CONF_cygautopath)) {
char *cygwinBinPath = getCygwinBin(conf_get_int(conf, CONF_cygterm64));
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);
{ char buffer[1024] ; sprintf( buffer,"starting cthelper: %s", cmdline ) ; logevent( NULL,buffer ) ; }
if ((err = spawnChild(cmdline, conf, &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;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
sfree(local);
}
static void
cygterm_reconfig(void *handle, Conf *conf)
{
//Local local = handle;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
local->conf = conf;
}
static int
cygterm_send(void *handle, const char *buf, int len)
{
//Local local = handle;
Local *local = container_of(handle, Local, backend);
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;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
return local->bufsize;
}
static void
cygterm_size(void *handle, int width, int height)
{
//Local local = handle;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
cygterm_debug("size=%d,%d (last=%d,%d)",
width, height, conf_get_int(local->conf,CONF_width), conf_get_int(local->conf,CONF_height));
conf_set_int(local->conf,CONF_width,width);
conf_set_int(local->conf,CONF_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)
static void cygterm_special(void *handle, SessionSpecialCode 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;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
return local->s != NULL;
}
static int
cygterm_exitcode(void *handle)
{
//Local local = handle;
Local *local = container_of(handle, Local, backend);
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;
Local *local = container_of(handle, Local, backend);
cygterm_debug("top");
sk_set_frozen(local->s, backlog > CYGTERM_MAX_BACKLOG);
}
static int
cygterm_ldisc(void *handle, int option)
{
//Local local = handle;
Local *local = container_of(handle, Local, backend);
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;
}
const struct BackendVtable 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_logctx,
cygterm_unthrottle,
cygterm_cfg_info,
NULL /* test_for_upstream */,
"cygterm",
PROT_CYGTERM,
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, Conf *conf)
{
char *e = buf;
if (conf_get_int(conf,CONF_bksp_is_delete))
e = strecpy(e, ":erase=^?");
else
e = strecpy(e, ":erase=^H");
e += sprintf(e, ":size=%d,%d", conf_get_int(conf,CONF_height), conf_get_int(conf,CONF_width));
/* TODO: other options? localedit? localecho? */
return e - buf;
}
#ifndef KEY_WOW64_64KEY
#define KEY_WOW64_64KEY 0x0100
#endif
#ifndef KEY_WOW64_32KEY
#define KEY_WOW64_32KEY 0x0200
#endif
/* Utility functions for spawning cthelper process */
static BOOL
getRegistry(char *valueData, LPDWORD psize, HKEY key, const char *subKey, const char *valueName, int use64)
{
HKEY k;
LONG ret;
if (ERROR_SUCCESS != (ret = RegOpenKeyEx(key, subKey, 0, KEY_READ | (use64 ? KEY_WOW64_64KEY : KEY_WOW64_32KEY), &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, (LPBYTE)valueData, psize)) ;
RegCloseKey(k);
return ret;
}
/* Cygwin 1.5 definition */
#define CYGWIN_SYS_ROOT_MOUNT \
HKEY_LOCAL_MACHINE,\
"Software\\Cygnus Solutions\\Cygwin\\mounts v2\\/",\
"native"
#define CYGWIN_USER_ROOT_MOUNT \
HKEY_CURRENT_USER,\
"Software\\Cygnus Solutions\\Cygwin\\mounts v2\\/",\
"native"
/* 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(int use64)
{
char *dir;
DWORD size = MAX_PATH;
dir = smalloc(size);
dir[0] = '\0';
if (ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_U_SETUP_ROOTDIR, use64) || ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_S_SETUP_ROOTDIR, use64))
{
strcat(dir, "\\bin");
}
else if (ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_SYS_ROOT_MOUNT, use64) || ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_USER_ROOT_MOUNT, use64))
{
strcat(dir, "\\bin");
}
else
{
sfree(dir);
dir = 0;
}
return dir;
}
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, Conf *conf, 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;
/* Add the Cygwin /bin path to the PATH env var. */
if (!getenv("NOCYGWIN")) {
char *cygwinBinPath = getCygwinBin(conf_get_int(conf, CONF_cygterm64));
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);
}
}
{ char buffer[1024] ; sprintf(buffer, "Create process %s", cmd ) ; logevent(NULL,buffer); }
/* 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;
}
#endif