X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/8cb9c947887dc7c26e7d02ccdb59e092c88e46a2..2184a5d91ffbcf2de2f730c83dda2d9443035f50:/plink.c diff --git a/plink.c b/plink.c index 6ac5e0ac..5ef9da2e 100644 --- a/plink.c +++ b/plink.c @@ -7,14 +7,20 @@ #endif #include #include +#include #include -#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ #include "putty.h" +#include "storage.h" +#include "tree234.h" -void fatalbox (char *p, ...) { +#define MAX_STDIN_BACKLOG 4096 + +void fatalbox(char *p, ...) +{ va_list ap; - fprintf(stderr, "FATAL ERROR: ", p); + fprintf(stderr, "FATAL ERROR: "); va_start(ap, p); vfprintf(stderr, p, ap); va_end(ap); @@ -22,9 +28,10 @@ void fatalbox (char *p, ...) { WSACleanup(); exit(1); } -void connection_fatal (char *p, ...) { +void connection_fatal(char *p, ...) +{ va_list ap; - fprintf(stderr, "FATAL ERROR: ", p); + fprintf(stderr, "FATAL ERROR: "); va_start(ap, p); vfprintf(stderr, p, ap); va_end(ap); @@ -35,92 +42,238 @@ void connection_fatal (char *p, ...) { static char *password = NULL; +void logevent(char *string) +{ +} + +void verify_ssh_host_key(char *host, int port, char *keytype, + char *keystr, char *fingerprint) +{ + 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); + } +} + /* - * Stubs for linking with other modules. + * Ask whether the selected cipher is acceptable (since it was + * below the configured 'warn' threshold). + * cs: 0 = both ways, 1 = client->server, 2 = server->client */ -void write_clip (void *data, int len) { } -void term_deselect(void) { } +void askcipher(char *ciphername, int cs) +{ + HANDLE hin; + DWORD savemode, i; -HANDLE outhandle; -DWORD orig_console_mode; + static const char msg[] = + "The first %scipher supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Continue with connection? (y/n) "; + static const char abandoned[] = "Connection abandoned.\n"; -void begin_session(void) { - if (!cfg.ldisc_term) - SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT); - else - SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode); + char line[32]; + + fprintf(stderr, msg, + (cs == 0) ? "" : + (cs == 1) ? "client-to-server " : + "server-to-client ", + ciphername); + 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] == 'y' || line[0] == 'Y') { + return; + } else { + fprintf(stderr, abandoned); + exit(0); + } } -void term_out(void) +/* + * Warn about the obsolescent key file format. + */ +void old_keyfile_warning(void) { - int reap; - DWORD ret; - reap = 0; - while (reap < inbuf_head) { - if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL)) - return; /* give up in panic */ - reap += ret; - } - inbuf_head = 0; + static const char message[] = + "You are loading an SSH 2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again.\n"; + + fputs(message, stderr); } -struct input_data { - DWORD len; - char buffer[4096]; - HANDLE event; -}; +HANDLE inhandle, outhandle, errhandle; +DWORD orig_console_mode; + +WSAEVENT netevent; -static int get_password(const char *prompt, char *str, int maxlen) +int term_ldisc(int mode) +{ + return FALSE; +} +void ldisc_update(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); +} + +static int get_line(const char *prompt, char *str, int maxlen, int is_pw) { HANDLE hin, hout; - DWORD savemode, i; + DWORD savemode, newmode, i; + + if (is_pw && password) { + static int tried_once = 0; - if (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; - } + 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; + fprintf(stderr, "Cannot get standard input/output handles"); + return 0; } GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT); + 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); + ReadFile(hin, str, maxlen - 1, &i, NULL); SetConsoleMode(hin, savemode); - if ((int)i > maxlen) i = maxlen-1; else i = i - 2; + if ((int) i > maxlen) + i = maxlen - 1; + else + i = i - 2; str[i] = '\0'; - WriteFile(hout, "\r\n", 2, &i, NULL); + if (is_pw) + WriteFile(hout, "\r\n", 2, &i, NULL); return 1; } -int WINAPI stdin_read_thread(void *param) { - struct input_data *idata = (struct input_data *)param; +struct input_data { + DWORD len; + char buffer[4096]; + HANDLE event, eventback; +}; + +static DWORD WINAPI stdin_read_thread(void *param) +{ + struct input_data *idata = (struct input_data *) param; HANDLE inhandle; inhandle = GetStdHandle(STD_INPUT_HANDLE); while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer), - &idata->len, NULL)) { - SetEvent(idata->event); + &idata->len, NULL) && idata->len > 0) { + SetEvent(idata->event); + WaitForSingleObject(idata->eventback, INFINITE); } idata->len = 0; @@ -129,6 +282,74 @@ int 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(int is_stderr, char *data, int len) +{ + HANDLE h = (is_stderr ? errhandle : outhandle); + int osize, esize; + + 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. */ @@ -137,205 +358,358 @@ 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(" -P port connect to specified port\n"); printf(" -pw passw login with specified password\n"); + printf(" -m file read remote command(s) from file\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"); exit(1); } -int main(int argc, char **argv) { +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; +} + +int main(int argc, char **argv) +{ WSADATA wsadata; WORD winsock_ver; - WSAEVENT netevent, stdinevent; - HANDLE handles[2]; - SOCKET socket; - 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; + char extra_portfwd[sizeof(cfg.portfwd)]; + + ssh_get_line = get_line; - ssh_get_password = get_password; + sklist = NULL; + skcount = sksize = 0; + /* + * 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); + do_defaults(NULL, &cfg); default_protocol = cfg.protocol; default_port = cfg.port; { - /* - * Override the default protocol if PLINK_PROTOCOL is set. - */ - char *p = getenv("PLINK_PROTOCOL"); - int i; - if (p) { - for (i = 0; backends[i].backend != NULL; i++) { - if (!strcmp(backends[i].name, p)) { - default_protocol = cfg.protocol = backends[i].protocol; - default_port = cfg.port = backends[i].backend->default_port; - break; - } - } - } + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + int i; + if (p) { + for (i = 0; backends[i].backend != NULL; i++) { + if (!strcmp(backends[i].name, p)) { + default_protocol = cfg.protocol = backends[i].protocol; + default_port = cfg.port = + backends[i].backend->default_port; + break; + } + } + } } while (--argc) { - char *p = *++argv; - if (*p == '-') { - if (!strcmp(p, "-ssh")) { + 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")) { + } else if (!strcmp(p, "-telnet")) { default_protocol = cfg.protocol = PROT_TELNET; default_port = cfg.port = 23; - } else if (!strcmp(p, "-raw")) { + } else if (!strcmp(p, "-raw")) { default_protocol = cfg.protocol = PROT_RAW; } else if (!strcmp(p, "-v")) { - flags |= FLAG_VERBOSE; + 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, "-P") && argc > 1) { - --argc, portnumber = atoi(*++argv); - } + 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, "-L") || !strcmp(p, "-R")) && argc > 1) { + char *fwd, *ptr, *q; + int i=0; + --argc, fwd = *++argv; + ptr = extra_portfwd; + /* if multiple forwards, find end of list */ + if (ptr[0]=='R' || ptr[0]=='L') { + for (i = 0; i < sizeof(extra_portfwd) - 2; i++) + if (ptr[i]=='\000' && ptr[i+1]=='\000') + break; + ptr = ptr + i + 1; /* point to next forward slot */ + } + ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */ + strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i); + q = strchr(ptr, ':'); + if (q) *q = '\t'; /* replace first : with \t */ + ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */ + extra_portfwd[sizeof(extra_portfwd) - 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.remote_cmd_ptr2 = NULL; + cfg.nopty = TRUE; /* command => no terminal */ + } else if (!strcmp(p, "-P") && argc > 1) { + --argc, portnumber = atoi(*++argv); + } } else if (*p) { - if (!*cfg.host) { - char *q = p; - /* - * If the hostname starts with "telnet:", set the - * protocol to Telnet and process the string as a - * Telnet URL. - */ - if (!strncmp(q, "telnet:", 7)) { - char c; - - q += 7; - if (q[0] == '/' && q[1] == '/') - q += 2; - cfg.protocol = PROT_TELNET; - p = q; - while (*p && *p != ':' && *p != '/') p++; - c = *p; - if (*p) - *p++ = '\0'; - if (c == ':') - cfg.port = atoi(p); - else - cfg.port = -1; - strncpy (cfg.host, q, sizeof(cfg.host)-1); - cfg.host[sizeof(cfg.host)-1] = '\0'; - } else { - char *r; - /* - * Before we process the [user@]host string, we - * first check for the presence of a protocol - * prefix (a protocol name followed by ","). - */ - r = strchr(p, ','); - if (r) { - int i, j; - for (i = 0; backends[i].backend != NULL; i++) { - j = strlen(backends[i].name); - if (j == r-p && - !memcmp(backends[i].name, p, j)) { - default_protocol = cfg.protocol = backends[i].protocol; - portnumber = backends[i].backend->default_port; - p = r+1; - break; - } - } - } - - /* - * Three cases. Either (a) there's a nonzero - * length string followed by an @, in which - * case that's user and the remainder is host. - * Or (b) there's only one string, not counting - * a potential initial @, and it exists in the - * saved-sessions database. Or (c) only one - * string and it _doesn't_ exist in the - * database. - */ - r = strrchr(p, '@'); - if (r == p) p++, r = NULL; /* discount initial @ */ - if (r == NULL) { - /* - * One string. - */ - do_defaults (p); - if (cfg.host[0] == '\0') { - /* No settings for this host; use defaults */ - strncpy(cfg.host, p, sizeof(cfg.host)-1); - cfg.host[sizeof(cfg.host)-1] = '\0'; - cfg.port = 22; - } - } else { - *r++ = '\0'; - strncpy(cfg.username, p, sizeof(cfg.username)-1); - cfg.username[sizeof(cfg.username)-1] = '\0'; - strncpy(cfg.host, r, sizeof(cfg.host)-1); - cfg.host[sizeof(cfg.host)-1] = '\0'; - cfg.port = 22; - } - } - } 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; - } - cfg.nopty = TRUE; /* command => no terminal */ - cfg.ldisc_term = TRUE; /* use stdin like a line buffer */ - break; /* done with cmdline */ - } + if (!*cfg.host) { + char *q = p; + /* + * If the hostname starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + if (!strncmp(q, "telnet:", 7)) { + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + cfg.protocol = PROT_TELNET; + p = q; + while (*p && *p != ':' && *p != '/') + p++; + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + cfg.port = atoi(p); + else + cfg.port = -1; + strncpy(cfg.host, q, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } else { + char *r; + /* + * Before we process the [user@]host string, we + * first check for the presence of a protocol + * prefix (a protocol name followed by ","). + */ + r = strchr(p, ','); + if (r) { + int i, j; + for (i = 0; backends[i].backend != NULL; i++) { + j = strlen(backends[i].name); + if (j == r - p && + !memcmp(backends[i].name, p, j)) { + default_protocol = cfg.protocol = + backends[i].protocol; + portnumber = + backends[i].backend->default_port; + p = r + 1; + break; + } + } + } + + /* + * Three cases. Either (a) there's a nonzero + * length string followed by an @, in which + * case that's user and the remainder is host. + * Or (b) there's only one string, not counting + * a potential initial @, and it exists in the + * saved-sessions database. Or (c) only one + * string and it _doesn't_ exist in the + * database. + */ + r = strrchr(p, '@'); + if (r == p) + p++, r = NULL; /* discount initial @ */ + if (r == NULL) { + /* + * One string. + */ + Config cfg2; + do_defaults(p, &cfg2); + if (cfg2.host[0] == '\0') { + /* No settings for this host; use defaults */ + strncpy(cfg.host, p, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + cfg.port = default_port; + } else { + cfg = cfg2; + cfg.remote_cmd_ptr = cfg.remote_cmd; + } + } else { + *r++ = '\0'; + strncpy(cfg.username, p, sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + strncpy(cfg.host, r, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + cfg.port = default_port; + } + } + } 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; + } + cfg.nopty = TRUE; /* command => no terminal */ + break; /* done with cmdline */ + } } } if (!*cfg.host) { - usage(); + 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); } - if (!*cfg.remote_cmd) - flags |= FLAG_INTERACTIVE; + /* 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)); + } + } + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + + 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; - } + 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; + } + } + + /* + * Add extra port forwardings (accumulated on command line) to + * cfg. + */ + { + int i; + char *p; + p = extra_portfwd; + i = 0; + while (cfg.portfwd[i]) + i += strlen(cfg.portfwd+i) + 1; + while (*p) { + if (strlen(p)+2 > sizeof(cfg.portfwd)-i) { + fprintf(stderr, "Internal fault: not enough space for all" + " port forwardings\n"); + break; + } + strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1); + i += strlen(cfg.portfwd+i) + 1; + cfg.portfwd[i] = '\0'; + p += strlen(p)+1; + } } /* * Select port. */ if (portnumber != -1) - cfg.port = portnumber; + cfg.port = portnumber; /* * Initialise WinSock. @@ -352,89 +726,204 @@ int main(int argc, char **argv) { WSACleanup(); return 1; } + sk_init(); /* * Start up the connection. */ + netevent = CreateEvent(NULL, FALSE, FALSE, NULL); { 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, cfg.host, cfg.port, &realhost); + error = back->init(cfg.host, cfg.port, &realhost, nodelay); if (error) { fprintf(stderr, "Unable to open connection:\n%s", error); return 1; } + sfree(realhost); } + connopen = 1; - netevent = CreateEvent(NULL, FALSE, FALSE, NULL); stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL); + stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL); + stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL); - GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode); - SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT); + 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); /* - * Now we must send the back end oodles of stuff. - */ - socket = back->socket(); - /* * 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. */ - WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE); 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"); + 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"); + exit(1); + } + while (1) { - int n; - - if (!sending && back->sendok()) { - /* - * 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; - if (!CreateThread(NULL, 0, stdin_read_thread, - &idata, 0, &threadid)) { - fprintf(stderr, "Unable to create second thread\n"); - exit(1); - } - sending = TRUE; - } - - n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - if (n == 0) { - WSANETWORKEVENTS things; - if (!WSAEnumNetworkEvents(socket, netevent, &things)) { - if (things.lNetworkEvents & FD_READ) - back->msg(0, FD_READ); - if (things.lNetworkEvents & FD_CLOSE) { - back->msg(0, FD_CLOSE); - break; - } - } - term_out(); - } else if (n == 1) { - if (idata.len > 0) { - back->send(idata.buffer, idata.len); - } else { - back->special(TS_EOF); - } - } + int n; + + if (!sending && back->sendok()) { + /* + * 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); + if (!CreateThread(NULL, 0, stdin_read_thread, + &idata, 0, &in_threadid)) { + fprintf(stderr, "Unable to create input thread\n"); + exit(1); + } + sending = TRUE; + } + + 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)) { + noise_ultralight(socket); + noise_ultralight(things.lNetworkEvents); + if (things.lNetworkEvents & FD_CONNECT) + connopen &= select_result(wp, (LPARAM) FD_CONNECT); + 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); + + } + } + } else if (n == 1) { + reading = 0; + noise_ultralight(idata.len); + if (connopen && back->socket() != NULL) { + if (idata.len > 0) { + back->send(idata.buffer, idata.len); + } else { + back->special(TS_EOF); + } + } + } else if (n == 2) { + odata.busy = 0; + if (!odata.writeret) { + fprintf(stderr, "Unable to write to standard output\n"); + exit(0); + } + bufchain_consume(&stdout_data, odata.lenwritten); + if (bufchain_size(&stdout_data) > 0) + try_output(0); + if (connopen && back->socket() != NULL) { + back->unthrottle(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"); + exit(0); + } + bufchain_consume(&stderr_data, edata.lenwritten); + if (bufchain_size(&stderr_data) > 0) + try_output(1); + if (connopen && back->socket() != NULL) { + back->unthrottle(bufchain_size(&stdout_data) + + bufchain_size(&stderr_data)); + } + } + if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) { + SetEvent(idata.eventback); + reading = 1; + } + if ((!connopen || back->socket() == NULL) && + bufchain_size(&stdout_data) == 0 && + bufchain_size(&stderr_data) == 0) + break; /* we closed the connection */ } WSACleanup(); return 0;