/*
-- putty-0.59/windows/cygterm.c	1969-12-31 17:00:00.000000000 -0700
++ putty-0.59-cygterm/windows/cygterm.c	2007-02-06 16:35:57.000000000 -0700
@ -0,0 +1,543 @@
*/

#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"
#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 */
void RunCommand( HWND hwnd, char * cmd ) ;
	
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 */
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, local->cfg.termtype);
else
	cmdlinelen = sprintf(cmdline, CTHELPER" %u %s ", cport, local->cfg.termtype);
	cmdlinelen += makeAttributes(cmdline + cmdlinelen, &local->cfg);

	command = cfg->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 (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);
		}
	}
//MessageBox( NULL, cmdline, "Command", MB_OK);
	
	cygterm_debug("starting cthelper: %s", cmdline);
	{ char buffer[1024] ; sprintf( buffer,"starting cthelper: %s", cmdline ) ; logevent( NULL,buffer ) ; }
	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,
	"cygterm",			// Ajout version 20100914
	PROT_CYGTERM,			// Ajout version 20100914
	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;
}

/* 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(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 if (ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_SYS_ROOT_MOUNT)
	    || ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_USER_ROOT_MOUNT))
	{
		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;

	/* Add the Cygwin /bin path to the PATH env var. */
    if (!getenv("NOCYGWIN")) {
        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);
        }
    }
	{ 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
