X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/d74d141c2daed084c8a62c5dc5b88e801b81ee5a..86916870d36cb70e7ef0ea760e75a6ae8b2d83a5:/plink.c diff --git a/plink.c b/plink.c index 4113a812..f9271519 100644 --- a/plink.c +++ b/plink.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #define PUTTY_DO_GLOBALS /* actually _define_ globals */ @@ -15,6 +16,8 @@ #include "storage.h" #include "tree234.h" +#define MAX_STDIN_BACKLOG 4096 + void fatalbox(char *p, ...) { va_list ap; @@ -24,9 +27,9 @@ void fatalbox(char *p, ...) va_end(ap); fputc('\n', stderr); WSACleanup(); - exit(1); + cleanup_exit(1); } -void connection_fatal(char *p, ...) +void modalfatalbox(char *p, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); @@ -35,89 +38,28 @@ void connection_fatal(char *p, ...) va_end(ap); fputc('\n', stderr); WSACleanup(); - exit(1); + cleanup_exit(1); } - -static char *password = NULL; - -void logevent(char *string) +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); + WSACleanup(); + cleanup_exit(1); } - -void verify_ssh_host_key(char *host, int port, char *keytype, - char *keystr, char *fingerprint) +void cmdline_error(char *p, ...) { - int ret; - HANDLE hin; - DWORD savemode, i; - - static const char absentmsg[] = - "The server's host key is not cached in the registry. You\n" - "have no guarantee that the server is the computer you\n" - "think it is.\n" - "The server's key fingerprint is:\n" - "%s\n" - "If you trust this host, enter \"y\" to add the key to\n" - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without\n" - "adding the key to the cache, enter \"n\".\n" - "If you do not trust this host, press Return to abandon the\n" - "connection.\n" - "Store key in cache? (y/n) "; - - static const char wrongmsg[] = - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The server's host key does not match the one PuTTY has\n" - "cached in the registry. This means that either the\n" - "server administrator has changed the host key, or you\n" - "have actually connected to another computer pretending\n" - "to be the server.\n" - "The new key fingerprint is:\n" - "%s\n" - "If you were expecting this change and trust the new key,\n" - "enter \"y\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating\n" - "the cache, enter \"n\".\n" - "If you want to abandon the connection completely, press\n" - "Return to cancel. Pressing Return is the ONLY guaranteed\n" - "safe choice.\n" - "Update cached key? (y/n, Return cancels connection) "; - - static const char abandoned[] = "Connection abandoned.\n"; - - char line[32]; - - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return; - - if (ret == 2) { /* key was different */ - fprintf(stderr, wrongmsg, fingerprint); - fflush(stderr); - } - if (ret == 1) { /* key was absent */ - fprintf(stderr, absentmsg, fingerprint); - fflush(stderr); - } - - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); - - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); - } else { - fprintf(stderr, abandoned); - exit(0); - } + va_list ap; + fprintf(stderr, "plink: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); } HANDLE inhandle, outhandle, errhandle; @@ -125,25 +67,14 @@ DWORD orig_console_mode; WSAEVENT netevent; -void from_backend(int is_stderr, char *data, int len) -{ - int pos; - DWORD ret; - HANDLE h = (is_stderr ? errhandle : outhandle); - - pos = 0; - while (pos < len) { - if (!WriteFile(h, data + pos, len - pos, &ret, NULL)) - return; /* give up in panic */ - pos += ret; - } -} +static Backend *back; +static void *backhandle; -int term_ldisc(int mode) +int term_ldisc(Terminal *term, int mode) { return FALSE; } -void ldisc_update(int echo, int edit) +void ldisc_update(void *frontend, int echo, int edit) { /* Update stdin read mode to reflect changes in line discipline. */ DWORD mode; @@ -166,56 +97,6 @@ struct input_data { HANDLE event, eventback; }; -static int get_line(const char *prompt, char *str, int maxlen, int is_pw) -{ - HANDLE hin, hout; - DWORD savemode, newmode, i; - - if (is_pw && password) { - static int tried_once = 0; - - if (tried_once) { - return 0; - } else { - strncpy(str, password, maxlen); - str[maxlen - 1] = '\0'; - tried_once = 1; - return 1; - } - } - - hin = GetStdHandle(STD_INPUT_HANDLE); - hout = GetStdHandle(STD_OUTPUT_HANDLE); - if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Cannot get standard input/output handles"); - return 0; - } - - GetConsoleMode(hin, &savemode); - newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; - if (is_pw) - newmode &= ~ENABLE_ECHO_INPUT; - else - newmode |= ENABLE_ECHO_INPUT; - SetConsoleMode(hin, newmode); - - WriteFile(hout, prompt, strlen(prompt), &i, NULL); - ReadFile(hin, str, maxlen - 1, &i, NULL); - - SetConsoleMode(hin, savemode); - - if ((int) i > maxlen) - i = maxlen - 1; - else - i = i - 2; - str[i] = '\0'; - - if (is_pw) - WriteFile(hout, "\r\n", 2, &i, NULL); - - return 1; -} - static DWORD WINAPI stdin_read_thread(void *param) { struct input_data *idata = (struct input_data *) param; @@ -235,6 +116,75 @@ static DWORD WINAPI stdin_read_thread(void *param) return 0; } +struct output_data { + DWORD len, lenwritten; + int writeret; + char *buffer; + int is_stderr, done; + HANDLE event, eventback; + int busy; +}; + +static DWORD WINAPI stdout_write_thread(void *param) +{ + struct output_data *odata = (struct output_data *) param; + HANDLE outhandle, errhandle; + + outhandle = GetStdHandle(STD_OUTPUT_HANDLE); + errhandle = GetStdHandle(STD_ERROR_HANDLE); + + while (1) { + WaitForSingleObject(odata->eventback, INFINITE); + if (odata->done) + break; + odata->writeret = + WriteFile(odata->is_stderr ? errhandle : outhandle, + 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; + SetEvent(data->eventback); + data->busy = 1; + } +} + +int from_backend(void *frontend_handle, int is_stderr, char *data, int len) +{ + 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; +} + /* * Short description of parameters. */ @@ -243,12 +193,28 @@ static void usage(void) printf("PuTTY Link: command-line connection utility\n"); printf("%s\n", ver); printf("Usage: plink [options] [user@]host [command]\n"); + printf(" (\"host\" can also be a PuTTY saved session name)\n"); printf("Options:\n"); printf(" -v show verbose messages\n"); - printf(" -ssh force use of ssh protocol\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -ssh -telnet -rlogin -raw\n"); + printf(" force use of a particular protocol (default SSH)\n"); printf(" -P port connect to specified port\n"); - printf(" -pw passw login with specified password\n"); + printf(" -l user connect with specified username\n"); printf(" -m file read remote command(s) from file\n"); + printf(" -batch disable all interactive prompts\n"); + printf("The following options only apply to SSH connections:\n"); + printf(" -pw passw login with specified password\n"); + printf(" -L listen-port:host:port Forward local port to " + "remote address\n"); + printf(" -R listen-port:host:port Forward remote port to" + " local address\n"); + printf(" -X -x enable / disable X11 forwarding\n"); + printf(" -A -a enable / disable agent forwarding\n"); + printf(" -t -T enable / disable pty allocation\n"); + printf(" -1 -2 force use of particular protocol version\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for authentication\n"); exit(1); } @@ -256,7 +222,8 @@ char *do_select(SOCKET skt, int startup) { int events; if (startup) { - events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT; + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); } else { events = 0; } @@ -275,17 +242,20 @@ int main(int argc, char **argv) { WSADATA wsadata; WORD winsock_ver; - WSAEVENT stdinevent; - HANDLE handles[2]; - DWORD threadid; + WSAEVENT stdinevent, stdoutevent, stderrevent; + HANDLE handles[4]; + DWORD in_threadid, out_threadid, err_threadid; struct input_data idata; + int reading; int sending; int portnumber = -1; SOCKET *sklist; int skcount, sksize; int connopen; + int exitcode; + int errors; - ssh_get_line = get_line; + ssh_get_line = console_get_line; sklist = NULL; skcount = sksize = 0; @@ -303,6 +273,7 @@ int main(int argc, char **argv) do_defaults(NULL, &cfg); default_protocol = cfg.protocol; default_port = cfg.port; + errors = 0; { /* * Override the default protocol if PLINK_PROTOCOL is set. @@ -323,56 +294,21 @@ int main(int argc, char **argv) while (--argc) { char *p = *++argv; if (*p == '-') { - if (!strcmp(p, "-ssh")) { - default_protocol = cfg.protocol = PROT_SSH; - default_port = cfg.port = 22; - } else if (!strcmp(p, "-telnet")) { - default_protocol = cfg.protocol = PROT_TELNET; - default_port = cfg.port = 23; - } else if (!strcmp(p, "-raw")) { - default_protocol = cfg.protocol = PROT_RAW; - } else if (!strcmp(p, "-v")) { - flags |= FLAG_VERBOSE; - } else if (!strcmp(p, "-log")) { - logfile = "putty.log"; - } else if (!strcmp(p, "-pw") && argc > 1) { - --argc, password = *++argv; - } else if (!strcmp(p, "-l") && argc > 1) { - char *username; - --argc, username = *++argv; - strncpy(cfg.username, username, sizeof(cfg.username)); - cfg.username[sizeof(cfg.username) - 1] = '\0'; - } else if (!strcmp(p, "-m") && argc > 1) { - char *filename, *command; - int cmdlen, cmdsize; - FILE *fp; - int c, d; - - --argc, filename = *++argv; - - cmdlen = cmdsize = 0; - command = NULL; - fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "plink: unable to open command " - "file \"%s\"\n", filename); - return 1; - } - do { - c = fgetc(fp); - d = c; - if (c == EOF) - d = 0; - if (cmdlen >= cmdsize) { - cmdsize = cmdlen + 512; - command = srealloc(command, cmdsize); - } - command[cmdlen++] = d; - } while (c != EOF); - cfg.remote_cmd_ptr = command; - cfg.nopty = TRUE; /* command => no terminal */ - } else if (!strcmp(p, "-P") && argc > 1) { - --argc, portnumber = atoi(*++argv); + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, &cfg); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = 1; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = 1; + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = 1; } } else if (*p) { if (!*cfg.host) { @@ -463,34 +399,90 @@ int main(int argc, char **argv) } } } else { - int len = sizeof(cfg.remote_cmd) - 1; - char *cp = cfg.remote_cmd; - int len2; - - strncpy(cp, p, len); - cp[len] = '\0'; - len2 = strlen(cp); - len -= len2; - cp += len2; - while (--argc) { - if (len > 0) - len--, *cp++ = ' '; - strncpy(cp, *++argv, len); - cp[len] = '\0'; - len2 = strlen(cp); - len -= len2; - cp += len2; + char *command; + int cmdlen, cmdsize; + cmdlen = cmdsize = 0; + command = NULL; + + while (argc) { + while (*p) { + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = srealloc(command, cmdsize); + } + command[cmdlen++]=*p++; + } + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = srealloc(command, cmdsize); + } + command[cmdlen++]=' '; /* always add trailing space */ + if (--argc) p = *++argv; } + if (cmdlen) command[--cmdlen]='\0'; + /* change trailing blank to NUL */ + cfg.remote_cmd_ptr = command; + cfg.remote_cmd_ptr2 = NULL; cfg.nopty = TRUE; /* command => no terminal */ + break; /* done with cmdline */ } } } + if (errors) + return 1; + if (!*cfg.host) { usage(); } + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg.host[0] != '\0') { + char *atsign = strchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(&cfg); + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + + /* + * Remove any remaining whitespace from the hostname. + */ + { + int p1 = 0, p2 = 0; + while (cfg.host[p2] != '\0') { + if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { + cfg.host[p1] = cfg.host[p2]; + p1++; + } + p2++; + } + cfg.host[p1] = '\0'; + } + if (!*cfg.remote_cmd_ptr) flags |= FLAG_INTERACTIVE; @@ -543,17 +535,25 @@ int main(int argc, char **argv) { 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(cfg.host, cfg.port, &realhost); + error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, + &realhost, nodelay); if (error) { fprintf(stderr, "Unable to open connection:\n%s", error); return 1; } + logctx = log_init(NULL); + back->provide_logctx(backhandle, logctx); sfree(realhost); } connopen = 1; stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL); + stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL); + stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL); inhandle = GetStdHandle(STD_INPUT_HANDLE); outhandle = GetStdHandle(STD_OUTPUT_HANDLE); @@ -568,11 +568,37 @@ int main(int argc, char **argv) */ handles[0] = netevent; handles[1] = stdinevent; + 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.is_stderr = 0; + odata.busy = odata.done = 0; + if (!CreateThread(NULL, 0, stdout_write_thread, + &odata, 0, &out_threadid)) { + fprintf(stderr, "Unable to create output thread\n"); + cleanup_exit(1); + } + edata.event = stderrevent; + edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL); + edata.is_stderr = 1; + edata.busy = edata.done = 0; + if (!CreateThread(NULL, 0, stdout_write_thread, + &edata, 0, &err_threadid)) { + fprintf(stderr, "Unable to create error output thread\n"); + cleanup_exit(1); + } + while (1) { int n; - if (!sending && back->sendok()) { + 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: @@ -592,14 +618,14 @@ int main(int argc, char **argv) idata.event = stdinevent; idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL); if (!CreateThread(NULL, 0, stdin_read_thread, - &idata, 0, &threadid)) { - fprintf(stderr, "Unable to create second thread\n"); - exit(1); + &idata, 0, &in_threadid)) { + fprintf(stderr, "Unable to create input thread\n"); + cleanup_exit(1); } sending = TRUE; } - n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + n = WaitForMultipleObjects(4, handles, FALSE, INFINITE); if (n == 0) { WSANETWORKEVENTS things; SOCKET socket; @@ -639,33 +665,79 @@ int main(int argc, char **argv) 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); - if (things.lNetworkEvents & FD_READ) - connopen &= select_result(wp, (LPARAM) FD_READ); - if (things.lNetworkEvents & FD_CLOSE) - connopen &= select_result(wp, (LPARAM) FD_CLOSE); - if (things.lNetworkEvents & FD_OOB) - connopen &= select_result(wp, (LPARAM) FD_OOB); - if (things.lNetworkEvents & FD_WRITE) - connopen &= select_result(wp, (LPARAM) FD_WRITE); - if (things.lNetworkEvents & FD_ACCEPT) - connopen &= select_result(wp, (LPARAM) FD_ACCEPT); + 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 (idata.len > 0) { - back->send(idata.buffer, idata.len); - } else { - back->special(TS_EOF); + 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 output\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() == NULL) + if ((!connopen || back->socket(backhandle) == NULL) && + bufchain_size(&stdout_data) == 0 && + bufchain_size(&stderr_data) == 0) break; /* we closed the connection */ } WSACleanup(); - return 0; + exitcode = back->exitcode(backhandle); + if (exitcode < 0) { + fprintf(stderr, "Remote process exit code unavailable\n"); + exitcode = 1; /* this is an error condition */ + } + return exitcode; }