/*
 * PLink - a command-line (stdin/stdout) variant of PuTTY.
 *
 * Modified for using as CVSNT Protocol
 *
 */

#ifndef AUTO_WINSOCK
#include <winsock2.h>
#endif
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <process.h>

#define PUTTY_DO_GLOBALS	       /* actually _define_ globals */
#define PLINK_EXPORT __declspec(dllexport)
#include "putty/putty.h"
#include "putty/storage.h"
#include "putty/tree234.h"

#include "plink_cvsnt.h"

extern int fatal_exit;
extern int fatal_exit_code;

#define MAX_STDIN_BACKLOG 4096

static Backend *back;
static void *backhandle;
static Config cfg;
extern putty_callbacks *callbacks;

void fatalbox(char *p, ...)
{
    va_list ap;
    fprintf(stderr, "FATAL ERROR: ");
    va_start(ap, p);
    vfprintf(stderr, p, ap);
    va_end(ap);
    fputc('\n', stderr);
    cleanup_exit(1);
}

void modalfatalbox(char *p, ...)
{
    va_list ap;
    fprintf(stderr, "FATAL ERROR: ");
    va_start(ap, p);
    vfprintf(stderr, p, ap);
    va_end(ap);
    fputc('\n', stderr);
    cleanup_exit(1);
}

void connection_fatal(void *frontend, char *p, ...)
{
    va_list ap;
    fprintf(stderr, "FATAL ERROR: ");
    va_start(ap, p);
    vfprintf(stderr, p, ap);
    va_end(ap);
    fputc('\n', stderr);
    cleanup_exit(1);
}
void cmdline_error(char *p, ...)
{
    va_list ap;
    fprintf(stderr, "plink: ");
    va_start(ap, p);
    vfprintf(stderr, p, ap);
    va_end(ap);
    fputc('\n', stderr);
    exit(1);
}

static char *password = NULL;

HANDLE inhandle, outhandle, errhandle;
DWORD orig_console_mode;
HANDLE handles[4];

WSAEVENT netevent;

char *cmdline_password = NULL;

static int cmdline_get_line(const char *prompt, char *str,
                            int maxlen, int is_pw)
{
    static int tried_once = 0;

    assert(is_pw && cmdline_password);

    if (tried_once) {
	return 0;
    } else {
	strncpy(str, cmdline_password, maxlen);
	str[maxlen - 1] = '\0';
	tried_once = 1;
	return 1;
    }
}

int term_ldisc(Terminal *term, int mode)
{
    return FALSE;
}

void ldisc_update(void *frontend, int echo, int edit)
{
    /* Update stdin read mode to reflect changes in line discipline. */
    DWORD mode;

    mode = ENABLE_PROCESSED_INPUT;
    if (echo)
	mode = mode | ENABLE_ECHO_INPUT;
    else
	mode = mode & ~ENABLE_ECHO_INPUT;
    if (edit)
	mode = mode | ENABLE_LINE_INPUT;
    else
	mode = mode & ~ENABLE_LINE_INPUT;
    SetConsoleMode(inhandle, mode);
}

struct input_data {
    DWORD len;
    char buffer[4096];
    HANDLE event, eventback;
};

static struct input_data *global_idata;

int plink_write_data(const void *buffer, int length)
{
    struct input_data *idata = global_idata;
	const unsigned char *p;
	int l;

	if(fatal_exit)
		return -1;

	for(l=length, p=(const unsigned char *)buffer; l>0; l-=sizeof(idata->buffer), p+=sizeof(idata->buffer))
	{
		idata->len = (l>sizeof(idata->buffer))?sizeof(idata->buffer):l;
		memcpy(idata->buffer,p,idata->len);
		SetEvent(idata->event);
		WaitForSingleObject(idata->eventback, INFINITE);
	}
	return length;
}

struct output_data {
    DWORD len, lenwritten;
    int writeret;
    char *buffer;
    int is_stderr, done;
    HANDLE event, eventback;
    int busy,wrap;
};

static struct output_data *global_odata;

/*
 * Read up to max_length worth of data.  This routine will
 *  block only if there are no data to read.  If anything is
 *  available, up to max_length of it will be returned immediately.
 *
 * Rewritten March 6, 2005, Bjoren Davis.
 *
 * I was having problems with cvsnt hanging at the end of
 *  some ssh transfers, and I had no idea why.  I looked at the
 *  old version of this routine, and I really couldn't understand
 *  what it was trying to do.  It looked as though it was doing
 *  a read-and-block-only-if-there's-nothing-but-otherwise-gimme
 *  -what-you-got.  However, it would block and loop if at the
 *  outset of the routine you didn't have any data.  Strange.
 *  So I wrote the read-and-block-only-if-there's-nothing-but-otherwise-
 *  gimme-what-you-got routine you see here.
 */
int plink_read_data(void *buffer, int max_length)
{
    struct output_data		*odata;
    int				 todo, l;
    char			*ptr;

    odata = global_odata;
    todo = max_length;
    ptr = (char *) buffer;

    /*
     * Loop until we can get something into the return buffer.
     */
    while (TRUE) {
	/*
	 * I put the test for fatal_exit here so that it gets checked
	 *  everytime after we wait.
	 */
	if (fatal_exit) {
	    return -1;
	}

	if (odata->done) {  /* !!!this flag doesn't seem to be set anywhere. */
	    odata->writeret = 0;
	    return 0;
	}

	/*
	 * Do we have data, and have we not sent all of it up yet?
	 */

	if (odata->len > 0 && (l = odata->len - odata->lenwritten) > 0) {
	    /* Yes, we have some data to return. */

	    /*
	     * If we have fewer data than what's being asked for, then
	     *  just settle for what we have.
	     */
	    if (todo > l) {
		/* we can return at most l bytes right now. */
		todo = l;
	    }

	    memcpy(ptr, odata->buffer + odata->lenwritten, todo);
	    odata->lenwritten += todo;
	    l -= todo;
	    odata->writeret = todo;

	    /*
	     * Have we completely drained our supply?  If so, then
	     *  we want to trigger a reload.  If not, then we'll
	     *  fetch more the next time around.
	     */

	    if (l == 0) {
		/* There's nothing left.  Trigger the reload. */

		SetEvent(odata->event);
	    }

	    return todo;
	}

	/*
	 * We're here because we have no data.  So wait for some.
	 */
	WaitForSingleObject(odata->eventback, INFINITE);
    }

    /* NOTREACHED */
}

static DWORD WINAPI stderr_write_thread(void *param)
{
    struct output_data *odata = (struct output_data *) param;
    HANDLE errhandle;

    errhandle = GetStdHandle(STD_ERROR_HANDLE);

    while (1)
	{
		WaitForSingleObject(odata->eventback, INFINITE);
		if (odata->done)
			break;
		odata->writeret =
			WriteFile(errhandle,
				odata->buffer, odata->len, &odata->lenwritten, NULL);
		SetEvent(odata->event);
    }
	return 0;
}

bufchain stdout_data, stderr_data;
struct output_data odata, edata;

void try_output(int is_stderr)
{
    struct output_data *data = (is_stderr ? &edata : &odata);
    void *senddata;
    int sendlen;

    if (!data->busy) {
	bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
			&senddata, &sendlen);
	data->buffer = senddata;
	data->len = sendlen;
	data->lenwritten = 0;
	SetEvent(data->eventback);
	data->busy = 1;
    }
}

int from_backend(void *frontend, int is_stderr, const char *data, int len)
{
    HANDLE h = (is_stderr ? errhandle : outhandle);
    int osize, esize;

    assert(len > 0);

    if (is_stderr) {
	bufchain_add(&stderr_data, data, len);
	try_output(1);
    } else {
	bufchain_add(&stdout_data, data, len);
	try_output(0);
    }

    osize = bufchain_size(&stdout_data);
    esize = bufchain_size(&stderr_data);

    return osize + esize;
}

char *do_select(SOCKET skt, int startup)
{
    int events;
    if (startup) {
	events = (FD_CONNECT | FD_READ | FD_WRITE |
		  FD_OOB | FD_CLOSE | FD_ACCEPT);
    } else {
	events = 0;
    }
    if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
	switch (WSAGetLastError()) {
	  case WSAENETDOWN:
	    return "Network is down";
	  default:
	    return "WSAAsyncSelect(): unknown error";
	}
    }
    return NULL;
}

void listen_thread(void* param)
{
    WSAEVENT stdinevent;
    SOCKET *sklist;
    int reading = 0;
    int sending = 0;
	struct input_data idata = {0};
    int skcount, sksize;
    int connopen = 1;

    stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
    handles[1] = stdinevent;
    sklist = NULL;
    skcount = sksize = 0;

	while (1) {
	int n;

	if (!sending && back->sendok(backhandle)) {
	    /*
	     * Create a separate thread to read from stdin. This is
	     * a total pain, but I can't find another way to do it:
	     *
	     *  - an overlapped ReadFile or ReadFileEx just doesn't
	     *    happen; we get failure from ReadFileEx, and
	     *    ReadFile blocks despite being given an OVERLAPPED
	     *    structure. Perhaps we can't do overlapped reads
	     *    on consoles. WHY THE HELL NOT?
	     * 
	     *  - WaitForMultipleObjects(netevent, console) doesn't
	     *    work, because it signals the console when
	     *    _anything_ happens, including mouse motions and
	     *    other things that don't cause data to be readable
	     *    - so we're back to ReadFile blocking.
	     */
	    idata.event = stdinevent;
	    idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
	    sending = TRUE;
		global_idata = &idata;
	}

	n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
	if (n == 0) {
	    WSANETWORKEVENTS things;
	    SOCKET socket;
	    extern SOCKET first_socket(int *), next_socket(int *);
	    extern int select_result(WPARAM, LPARAM);
	    int i, socketstate;

	    /*
	     * We must not call select_result() for any socket
	     * until we have finished enumerating within the tree.
	     * This is because select_result() may close the socket
	     * and modify the tree.
	     */
	    /* Count the active sockets. */
	    i = 0;
	    for (socket = first_socket(&socketstate);
		 socket != INVALID_SOCKET;
		 socket = next_socket(&socketstate)) i++;

	    /* Expand the buffer if necessary. */
	    if (i > sksize) {
		sksize = i + 16;
		sklist = srealloc(sklist, sksize * sizeof(*sklist));
	    }

	    /* Retrieve the sockets into sklist. */
	    skcount = 0;
	    for (socket = first_socket(&socketstate);
		 socket != INVALID_SOCKET;
		 socket = next_socket(&socketstate)) {
		sklist[skcount++] = socket;
	    }

	    /* Now we're done enumerating; go through the list. */
	    for (i = 0; i < skcount; i++) {
		WPARAM wp;
		socket = sklist[i];
		wp = (WPARAM) socket;
		if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
                    static const struct { int bit, mask; } eventtypes[] = {
                        {FD_CONNECT_BIT, FD_CONNECT},
                        {FD_READ_BIT, FD_READ},
                        {FD_CLOSE_BIT, FD_CLOSE},
                        {FD_OOB_BIT, FD_OOB},
                        {FD_WRITE_BIT, FD_WRITE},
                        {FD_ACCEPT_BIT, FD_ACCEPT},
                    };
                    int e;

		    noise_ultralight(socket);
		    noise_ultralight(things.lNetworkEvents);

                    for (e = 0; e < lenof(eventtypes); e++)
                        if (things.lNetworkEvents & eventtypes[e].mask) {
                            LPARAM lp;
                            int err = things.iErrorCode[eventtypes[e].bit];
                            lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
                            connopen &= select_result(wp, lp);
                        }
		}
	    }
	} else if (n == 1) {
	    reading = 0;
	    noise_ultralight(idata.len);
	    if (connopen && back->socket(backhandle) != NULL) {
		if (idata.len > 0) {
		    back->send(backhandle, idata.buffer, idata.len);
		} else {
		    back->special(backhandle, TS_EOF);
		}
	    }
	} else if (n == 2) {
	    odata.busy = 0;
	    if (!odata.writeret) {
		fprintf(stderr, "Unable to write to standard output\n");
		cleanup_exit(0);
	    }
	    bufchain_consume(&stdout_data, odata.lenwritten);
	    if (bufchain_size(&stdout_data) > 0)
		try_output(0);
	    if (connopen && back->socket(backhandle) != NULL) {
		back->unthrottle(backhandle, bufchain_size(&stdout_data) +
				 bufchain_size(&stderr_data));
	    }
	} else if (n == 3) {
	    edata.busy = 0;
	    if (!edata.writeret) {
		fprintf(stderr, "Unable to write to standard error\n");
		cleanup_exit(0);
	    }
	    bufchain_consume(&stderr_data, edata.lenwritten);
	    if (bufchain_size(&stderr_data) > 0)
		try_output(1);
	    if (connopen && back->socket(backhandle) != NULL) {
		back->unthrottle(backhandle, bufchain_size(&stdout_data) +
				 bufchain_size(&stderr_data));
	    }
	}
	if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
	    SetEvent(idata.eventback);
	    reading = 1;
	}
	if ((!connopen || back->socket(backhandle) == NULL) &&
	    bufchain_size(&stdout_data) == 0 &&
	    bufchain_size(&stderr_data) == 0)
	    break;		       /* we closed the connection */
    }
	fatal_exit = TRUE;
}

int plink_connect(const char *username, const char *password, const char *keyfile, const char *host, unsigned port, char version, const char *cmd, const char *proxyname, const char *proxyport, const char *proxyuser, const char *proxypassword)
{
    WSAEVENT stdoutevent, stderrevent;
    DWORD err_threadid;
    int reading = 0;
    int sending = 0;

    ssh_get_line = console_get_line;

    /*
     * Initialise port and protocol to sensible defaults. (These
     * will be overridden by more or less anything.)
     */
    default_protocol = PROT_SSH;
    default_port = 22;

    flags = FLAG_STDERR;
    /*
     * Process the command line.
     */
    do_defaults(NULL, &cfg);
    loaded_session = FALSE;

	if(port>0)
		cfg.port = port;
	strncpy(cfg.username,username,sizeof(cfg.username));

	if(cmd)
	{
		strncpy(cfg.remote_cmd,cmd,sizeof(cfg.remote_cmd));
		cfg.remote_cmd_ptr = cfg.remote_cmd;
	}

	if(proxyname)
	{
		strncpy(cfg.proxy_host,proxyname,sizeof(cfg.proxy_host));
		if(proxyport)
			cfg.proxy_port=atoi(proxyport);
		else
			cfg.proxy_port=3128;
		if(proxyuser)
			strncpy(cfg.proxy_username,proxyuser,sizeof(cfg.proxy_username));
		if(proxypassword)
			strncpy(cfg.proxy_password,proxypassword,sizeof(cfg.proxy_password));
		/* CVSNT only does HTTP at the moment..  it's rare that anyone wants anything
		   else anyway */
		cfg.proxy_type=PROXY_HTTP;
	}

	switch(version)
	{
	case 1:
		cfg.sshprot=0;
		break;
	case 2:
		cfg.sshprot=3;
		break;
	}

	if(keyfile)
	{
		cfg.keyfile=filename_from_str(keyfile);
	}
	else if(password)
	{
		cmdline_password = (char*)password;
		ssh_get_line = cmdline_get_line;
		ssh_getline_pw_only = TRUE;
	}

	{
		Config cfg2;
		do_defaults((char*)host, &cfg2);
		if (loaded_session || cfg2.host[0] == '\0')
		{
			/* No settings for this host; use defaults */
			/* (or session was already loaded with -load) */
			strncpy(cfg.host, host, sizeof(cfg.host) - 1);
			cfg.host[sizeof(cfg.host) - 1] = '\0';
			cfg.port = default_port;
		}
		else
		{
			if(!cfg2.remote_cmd[0])
			{
				strncpy(cfg2.remote_cmd,cfg.remote_cmd,sizeof(cfg2.remote_cmd));
			}
			cfg = cfg2;
			/* Ick: patch up internal pointer after copy */
			cfg.remote_cmd_ptr = cfg.remote_cmd;

			if(strchr(cfg.host,'@'))
			{
				size_t n = strchr(cfg.host,'@')-cfg.host;
				strncpy(cfg.username,cfg.host,(n>sizeof(cfg.username))?sizeof(cfg.username)-1:n);
				cfg.username[(n>sizeof(cfg.username))?sizeof(cfg.username)-1:n]='\0';
				memmove(cfg.host,cfg.host+n+1,strlen(cfg.host+n+1)+1);
			}

		}
	}

	cfg.nopty = 1;
    if (!*cfg.remote_cmd_ptr)
		flags |= FLAG_INTERACTIVE;

    /*
     * Select protocol. This is farmed out into a table in a
     * separate file to enable an ssh-free variant.
     */
    {
	int i;
	back = NULL;
	for (i = 0; backends[i].backend != NULL; i++)
	    if (backends[i].protocol == cfg.protocol) {
		back = backends[i].backend;
		break;
	    }
	if (back == NULL) {
	    fprintf(stderr,
		    "Internal fault: Unsupported protocol found\n");
	    return 1;
	}
    }

    sk_init();

    /*
     * Start up the connection.
     */
    netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
    {
	const char *error;
	char *realhost;
	/* nodelay is only useful if stdin is a character device (console) */
	int nodelay = cfg.tcp_nodelay &&
	    (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);

	error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, nodelay, cfg.tcp_keepalives);
	if (error) {
	    fprintf(stderr, "Unable to open connection:\n%s", error);
	    return 1;
	}
	sfree(realhost);
    }

    stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
    stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);

    inhandle = GetStdHandle(STD_INPUT_HANDLE);
    outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
    errhandle = GetStdHandle(STD_ERROR_HANDLE);
    GetConsoleMode(inhandle, &orig_console_mode);
    SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);

    /*
     * Turn off ECHO and LINE input modes. We don't care if this
     * call fails, because we know we aren't necessarily running in
     * a console.
     */
    handles[0] = netevent;
    handles[2] = stdoutevent;
    handles[3] = stderrevent;
    sending = FALSE;

    /*
     * Create spare threads to write to stdout and stderr, so we
     * can arrange asynchronous writes.
     */
    odata.event = stdoutevent;
    odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
    odata.busy = odata.done = 0;
    odata.len = odata.lenwritten = 0;

	global_odata = &odata;

    edata.event = stderrevent;
    edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
    edata.is_stderr = 1;
    edata.busy = edata.done = 0;
    if (!CreateThread(NULL, 0, stderr_write_thread,
		&edata, 0, &err_threadid))
	{
 		fprintf(stderr, "Unable to create error output thread\n");
 		cleanup_exit(1);
    }

	if(!_beginthread(listen_thread,0,NULL))
	{
		fprintf(stderr, "Unable to create background thread\n");
		cleanup_exit(1);
	}

	while(!global_idata)
	{
		Sleep(100);
		if(fatal_exit)
		{
			int exitcode = back->exitcode(backhandle);
			if (exitcode < 0) {
				exitcode = fatal_exit_code;		       /* this is an error condition */
			}
			return exitcode; 
		}
	}
	return 0;
}

putty_callbacks PLINK_EXPORT *plink_set_callbacks(putty_callbacks *new_callbacks)
{
	putty_callbacks *old = callbacks;
	callbacks = new_callbacks;
	return old;
}


syntax highlighted by Code2HTML, v. 0.9.1