Add a new back-end function to return the exit code of the remote
[u/mdw/putty] / plink.c
CommitLineData
12dc4ec0 1/*
2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
3 */
4
4d331a77 5#ifndef AUTO_WINSOCK
12dc4ec0 6#include <winsock2.h>
4d331a77 7#endif
12dc4ec0 8#include <windows.h>
9#include <stdio.h>
49bad831 10#include <stdlib.h>
12dc4ec0 11#include <stdarg.h>
12
32874aea 13#define PUTTY_DO_GLOBALS /* actually _define_ globals */
12dc4ec0 14#include "putty.h"
a9422f39 15#include "storage.h"
8df7a775 16#include "tree234.h"
12dc4ec0 17
5471d09a 18#define MAX_STDIN_BACKLOG 4096
19
32874aea 20void fatalbox(char *p, ...)
21{
12dc4ec0 22 va_list ap;
49bad831 23 fprintf(stderr, "FATAL ERROR: ");
12dc4ec0 24 va_start(ap, p);
25 vfprintf(stderr, p, ap);
26 va_end(ap);
27 fputc('\n', stderr);
28 WSACleanup();
29 exit(1);
30}
32874aea 31void connection_fatal(char *p, ...)
32{
8d5de777 33 va_list ap;
49bad831 34 fprintf(stderr, "FATAL ERROR: ");
8d5de777 35 va_start(ap, p);
36 vfprintf(stderr, p, ap);
37 va_end(ap);
38 fputc('\n', stderr);
39 WSACleanup();
40 exit(1);
41}
12dc4ec0 42
d8426c54 43static char *password = NULL;
44
32874aea 45void logevent(char *string)
46{
47}
a9422f39 48
49void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 50 char *keystr, char *fingerprint)
51{
a9422f39 52 int ret;
fcbb94d3 53 HANDLE hin;
54 DWORD savemode, i;
a9422f39 55
56 static const char absentmsg[] =
32874aea 57 "The server's host key is not cached in the registry. You\n"
58 "have no guarantee that the server is the computer you\n"
59 "think it is.\n"
60 "The server's key fingerprint is:\n"
61 "%s\n"
62 "If you trust this host, enter \"y\" to add the key to\n"
63 "PuTTY's cache and carry on connecting.\n"
d0718310 64 "If you want to carry on connecting just once, without\n"
65 "adding the key to the cache, enter \"n\".\n"
66 "If you do not trust this host, press Return to abandon the\n"
67 "connection.\n"
68 "Store key in cache? (y/n) ";
a9422f39 69
70 static const char wrongmsg[] =
32874aea 71 "WARNING - POTENTIAL SECURITY BREACH!\n"
72 "The server's host key does not match the one PuTTY has\n"
73 "cached in the registry. This means that either the\n"
74 "server administrator has changed the host key, or you\n"
75 "have actually connected to another computer pretending\n"
76 "to be the server.\n"
77 "The new key fingerprint is:\n"
78 "%s\n"
79 "If you were expecting this change and trust the new key,\n"
80 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
81 "If you want to carry on connecting but without updating\n"
82 "the cache, enter \"n\".\n"
83 "If you want to abandon the connection completely, press\n"
84 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
85 "safe choice.\n"
86 "Update cached key? (y/n, Return cancels connection) ";
a9422f39 87
88 static const char abandoned[] = "Connection abandoned.\n";
89
90 char line[32];
91
92 /*
93 * Verify the key against the registry.
94 */
95 ret = verify_host_key(host, port, keytype, keystr);
96
32874aea 97 if (ret == 0) /* success - key matched OK */
98 return;
fcbb94d3 99
b4453f49 100 if (ret == 2) { /* key was different */
32874aea 101 fprintf(stderr, wrongmsg, fingerprint);
b4453f49 102 fflush(stderr);
103 }
104 if (ret == 1) { /* key was absent */
32874aea 105 fprintf(stderr, absentmsg, fingerprint);
b4453f49 106 fflush(stderr);
107 }
fcbb94d3 108
109 hin = GetStdHandle(STD_INPUT_HANDLE);
110 GetConsoleMode(hin, &savemode);
111 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
32874aea 112 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
113 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
fcbb94d3 114 SetConsoleMode(hin, savemode);
115
d0718310 116 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
32874aea 117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
d0718310 119 } else {
120 fprintf(stderr, abandoned);
121 exit(0);
a9422f39 122 }
123}
989b10e9 124
ca20bfcf 125/*
126 * Ask whether the selected cipher is acceptable (since it was
127 * below the configured 'warn' threshold).
128 * cs: 0 = both ways, 1 = client->server, 2 = server->client
129 */
130void askcipher(char *ciphername, int cs)
131{
132 HANDLE hin;
133 DWORD savemode, i;
134
135 static const char msg[] =
136 "The first %scipher supported by the server is\n"
137 "%s, which is below the configured warning threshold.\n"
138 "Continue with connection? (y/n) ";
139 static const char abandoned[] = "Connection abandoned.\n";
140
141 char line[32];
142
143 fprintf(stderr, msg,
144 (cs == 0) ? "" :
145 (cs == 1) ? "client-to-server " :
146 "server-to-client ",
147 ciphername);
148 fflush(stderr);
149
150 hin = GetStdHandle(STD_INPUT_HANDLE);
151 GetConsoleMode(hin, &savemode);
152 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
153 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
154 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
155 SetConsoleMode(hin, savemode);
156
157 if (line[0] == 'y' || line[0] == 'Y') {
158 return;
159 } else {
160 fprintf(stderr, abandoned);
161 exit(0);
162 }
163}
164
7bedb13c 165/*
00db133f 166 * Ask whether to wipe a session log file before writing to it.
167 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
168 */
169int askappend(char *filename)
170{
171 HANDLE hin;
172 DWORD savemode, i;
173
174 static const char msgtemplate[] =
175 "The session log file \"%.*s\" already exists.\n"
176 "You can overwrite it with a new session log,\n"
177 "append your session log to the end of it,\n"
178 "or disable session logging for this session.\n"
179 "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
180 "or just press Return to disable logging.\n"
181 "Wipe the log file? (y/n, Return cancels logging) ";
182
183 char line[32];
184
185 fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
186 fflush(stderr);
187
188 hin = GetStdHandle(STD_INPUT_HANDLE);
189 GetConsoleMode(hin, &savemode);
190 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
191 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
192 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
193 SetConsoleMode(hin, savemode);
194
195 if (line[0] == 'y' || line[0] == 'Y')
196 return 2;
197 else if (line[0] == 'n' || line[0] == 'N')
198 return 1;
199 else
200 return 0;
201}
202
203/*
7bedb13c 204 * Warn about the obsolescent key file format.
205 */
206void old_keyfile_warning(void)
207{
208 static const char message[] =
209 "You are loading an SSH 2 private key which has an\n"
210 "old version of the file format. This means your key\n"
211 "file is not fully tamperproof. Future versions of\n"
212 "PuTTY may stop supporting this private key format,\n"
213 "so we recommend you convert your key to the new\n"
214 "format.\n"
215 "\n"
216 "Once the key is loaded into PuTTYgen, you can perform\n"
217 "this conversion simply by saving it again.\n";
218
219 fputs(message, stderr);
220}
221
0965bee0 222HANDLE inhandle, outhandle, errhandle;
6f34e365 223DWORD orig_console_mode;
224
8df7a775 225WSAEVENT netevent;
226
32874aea 227int term_ldisc(int mode)
228{
229 return FALSE;
230}
231void ldisc_update(int echo, int edit)
232{
0965bee0 233 /* Update stdin read mode to reflect changes in line discipline. */
234 DWORD mode;
235
236 mode = ENABLE_PROCESSED_INPUT;
237 if (echo)
32874aea 238 mode = mode | ENABLE_ECHO_INPUT;
0965bee0 239 else
32874aea 240 mode = mode & ~ENABLE_ECHO_INPUT;
0965bee0 241 if (edit)
32874aea 242 mode = mode | ENABLE_LINE_INPUT;
0965bee0 243 else
32874aea 244 mode = mode & ~ENABLE_LINE_INPUT;
0965bee0 245 SetConsoleMode(inhandle, mode);
246}
247
fa17a66e 248static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
67779be7 249{
250 HANDLE hin, hout;
fa17a66e 251 DWORD savemode, newmode, i;
67779be7 252
fa17a66e 253 if (is_pw && password) {
32874aea 254 static int tried_once = 0;
255
256 if (tried_once) {
257 return 0;
258 } else {
259 strncpy(str, password, maxlen);
260 str[maxlen - 1] = '\0';
261 tried_once = 1;
262 return 1;
263 }
67779be7 264 }
67779be7 265
266 hin = GetStdHandle(STD_INPUT_HANDLE);
267 hout = GetStdHandle(STD_OUTPUT_HANDLE);
268 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
32874aea 269 fprintf(stderr, "Cannot get standard input/output handles");
270 return 0;
67779be7 271 }
272
273 GetConsoleMode(hin, &savemode);
fa17a66e 274 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
275 if (is_pw)
32874aea 276 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 277 else
32874aea 278 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 279 SetConsoleMode(hin, newmode);
67779be7 280
281 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 282 ReadFile(hin, str, maxlen - 1, &i, NULL);
67779be7 283
284 SetConsoleMode(hin, savemode);
285
32874aea 286 if ((int) i > maxlen)
287 i = maxlen - 1;
288 else
289 i = i - 2;
67779be7 290 str[i] = '\0';
291
fa17a66e 292 if (is_pw)
32874aea 293 WriteFile(hout, "\r\n", 2, &i, NULL);
67779be7 294
295 return 1;
296}
297
5471d09a 298struct input_data {
299 DWORD len;
300 char buffer[4096];
301 HANDLE event, eventback;
302};
303
32874aea 304static DWORD WINAPI stdin_read_thread(void *param)
305{
306 struct input_data *idata = (struct input_data *) param;
12dc4ec0 307 HANDLE inhandle;
308
309 inhandle = GetStdHandle(STD_INPUT_HANDLE);
310
311 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
32874aea 312 &idata->len, NULL) && idata->len > 0) {
313 SetEvent(idata->event);
314 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 315 }
316
317 idata->len = 0;
318 SetEvent(idata->event);
319
320 return 0;
321}
322
5471d09a 323struct output_data {
324 DWORD len, lenwritten;
325 int writeret;
326 char *buffer;
327 int is_stderr, done;
328 HANDLE event, eventback;
329 int busy;
330};
331
332static DWORD WINAPI stdout_write_thread(void *param)
333{
334 struct output_data *odata = (struct output_data *) param;
335 HANDLE outhandle, errhandle;
336
337 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
338 errhandle = GetStdHandle(STD_ERROR_HANDLE);
339
340 while (1) {
341 WaitForSingleObject(odata->eventback, INFINITE);
342 if (odata->done)
343 break;
344 odata->writeret =
345 WriteFile(odata->is_stderr ? errhandle : outhandle,
346 odata->buffer, odata->len, &odata->lenwritten, NULL);
347 SetEvent(odata->event);
348 }
349
350 return 0;
351}
352
353bufchain stdout_data, stderr_data;
354struct output_data odata, edata;
355
356void try_output(int is_stderr)
357{
358 struct output_data *data = (is_stderr ? &edata : &odata);
359 void *senddata;
360 int sendlen;
361
362 if (!data->busy) {
363 bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
364 &senddata, &sendlen);
365 data->buffer = senddata;
366 data->len = sendlen;
367 SetEvent(data->eventback);
368 data->busy = 1;
369 }
370}
371
372int from_backend(int is_stderr, char *data, int len)
373{
5471d09a 374 HANDLE h = (is_stderr ? errhandle : outhandle);
5471d09a 375 int osize, esize;
376
377 if (is_stderr) {
378 bufchain_add(&stderr_data, data, len);
379 try_output(1);
380 } else {
381 bufchain_add(&stdout_data, data, len);
382 try_output(0);
383 }
384
385 osize = bufchain_size(&stdout_data);
386 esize = bufchain_size(&stderr_data);
387
388 return osize + esize;
389}
390
d8426c54 391/*
392 * Short description of parameters.
393 */
394static void usage(void)
395{
396 printf("PuTTY Link: command-line connection utility\n");
397 printf("%s\n", ver);
398 printf("Usage: plink [options] [user@]host [command]\n");
e672967c 399 printf(" (\"host\" can also be a PuTTY saved session name)\n");
d8426c54 400 printf("Options:\n");
401 printf(" -v show verbose messages\n");
402 printf(" -ssh force use of ssh protocol\n");
403 printf(" -P port connect to specified port\n");
404 printf(" -pw passw login with specified password\n");
96621a84 405 printf(" -m file read remote command(s) from file\n");
e7aabca4 406 printf(" -L listen-port:host:port Forward local port to "
407 "remote address\n");
408 printf(" -R listen-port:host:port Forward remote port to"
409 " local address\n");
d8426c54 410 exit(1);
411}
412
32874aea 413char *do_select(SOCKET skt, int startup)
414{
8df7a775 415 int events;
416 if (startup) {
3ad9d396 417 events = (FD_CONNECT | FD_READ | FD_WRITE |
418 FD_OOB | FD_CLOSE | FD_ACCEPT);
8df7a775 419 } else {
420 events = 0;
421 }
32874aea 422 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
423 switch (WSAGetLastError()) {
424 case WSAENETDOWN:
425 return "Network is down";
426 default:
427 return "WSAAsyncSelect(): unknown error";
428 }
8df7a775 429 }
430 return NULL;
431}
432
32874aea 433int main(int argc, char **argv)
434{
12dc4ec0 435 WSADATA wsadata;
436 WORD winsock_ver;
5471d09a 437 WSAEVENT stdinevent, stdoutevent, stderrevent;
438 HANDLE handles[4];
439 DWORD in_threadid, out_threadid, err_threadid;
12dc4ec0 440 struct input_data idata;
5471d09a 441 int reading;
12dc4ec0 442 int sending;
d8426c54 443 int portnumber = -1;
8df7a775 444 SOCKET *sklist;
445 int skcount, sksize;
446 int connopen;
d8d6c7e5 447 int exitcode;
e7aabca4 448 char extra_portfwd[sizeof(cfg.portfwd)];
12dc4ec0 449
fa17a66e 450 ssh_get_line = get_line;
67779be7 451
32874aea 452 sklist = NULL;
453 skcount = sksize = 0;
c9bdcd96 454 /*
455 * Initialise port and protocol to sensible defaults. (These
456 * will be overridden by more or less anything.)
457 */
458 default_protocol = PROT_SSH;
459 default_port = 22;
8df7a775 460
67779be7 461 flags = FLAG_STDERR;
12dc4ec0 462 /*
463 * Process the command line.
464 */
a9422f39 465 do_defaults(NULL, &cfg);
e7a7383f 466 default_protocol = cfg.protocol;
467 default_port = cfg.port;
8cb9c947 468 {
32874aea 469 /*
470 * Override the default protocol if PLINK_PROTOCOL is set.
471 */
472 char *p = getenv("PLINK_PROTOCOL");
473 int i;
474 if (p) {
475 for (i = 0; backends[i].backend != NULL; i++) {
476 if (!strcmp(backends[i].name, p)) {
477 default_protocol = cfg.protocol = backends[i].protocol;
478 default_port = cfg.port =
479 backends[i].backend->default_port;
480 break;
481 }
482 }
483 }
8cb9c947 484 }
12dc4ec0 485 while (--argc) {
32874aea 486 char *p = *++argv;
487 if (*p == '-') {
488 if (!strcmp(p, "-ssh")) {
12dc4ec0 489 default_protocol = cfg.protocol = PROT_SSH;
490 default_port = cfg.port = 22;
32874aea 491 } else if (!strcmp(p, "-telnet")) {
9d33ebdd 492 default_protocol = cfg.protocol = PROT_TELNET;
493 default_port = cfg.port = 23;
66a3d938 494 } else if (!strcmp(p, "-rlogin")) {
495 default_protocol = cfg.protocol = PROT_RLOGIN;
496 default_port = cfg.port = 513;
32874aea 497 } else if (!strcmp(p, "-raw")) {
9d33ebdd 498 default_protocol = cfg.protocol = PROT_RAW;
67779be7 499 } else if (!strcmp(p, "-v")) {
32874aea 500 flags |= FLAG_VERBOSE;
12dc4ec0 501 } else if (!strcmp(p, "-log")) {
32874aea 502 logfile = "putty.log";
503 } else if (!strcmp(p, "-pw") && argc > 1) {
504 --argc, password = *++argv;
505 } else if (!strcmp(p, "-l") && argc > 1) {
506 char *username;
507 --argc, username = *++argv;
508 strncpy(cfg.username, username, sizeof(cfg.username));
509 cfg.username[sizeof(cfg.username) - 1] = '\0';
e7aabca4 510 } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) {
41ff4658 511 char *fwd, *ptr, *q;
e7aabca4 512 int i=0;
513 --argc, fwd = *++argv;
514 ptr = extra_portfwd;
515 /* if multiple forwards, find end of list */
516 if (ptr[0]=='R' || ptr[0]=='L') {
517 for (i = 0; i < sizeof(extra_portfwd) - 2; i++)
518 if (ptr[i]=='\000' && ptr[i+1]=='\000')
519 break;
520 ptr = ptr + i + 1; /* point to next forward slot */
521 }
522 ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */
523 strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i);
41ff4658 524 q = strchr(ptr, ':');
525 if (q) *q = '\t'; /* replace first : with \t */
e7aabca4 526 ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */
527 extra_portfwd[sizeof(extra_portfwd) - 1] = '\0';
32874aea 528 } else if (!strcmp(p, "-m") && argc > 1) {
529 char *filename, *command;
530 int cmdlen, cmdsize;
531 FILE *fp;
532 int c, d;
533
534 --argc, filename = *++argv;
535
536 cmdlen = cmdsize = 0;
537 command = NULL;
538 fp = fopen(filename, "r");
539 if (!fp) {
540 fprintf(stderr, "plink: unable to open command "
541 "file \"%s\"\n", filename);
542 return 1;
543 }
544 do {
545 c = fgetc(fp);
546 d = c;
547 if (c == EOF)
548 d = 0;
549 if (cmdlen >= cmdsize) {
550 cmdsize = cmdlen + 512;
551 command = srealloc(command, cmdsize);
552 }
553 command[cmdlen++] = d;
554 } while (c != EOF);
555 cfg.remote_cmd_ptr = command;
fd5e5847 556 cfg.remote_cmd_ptr2 = NULL;
32874aea 557 cfg.nopty = TRUE; /* command => no terminal */
558 } else if (!strcmp(p, "-P") && argc > 1) {
559 --argc, portnumber = atoi(*++argv);
560 }
12dc4ec0 561 } else if (*p) {
32874aea 562 if (!*cfg.host) {
563 char *q = p;
564 /*
565 * If the hostname starts with "telnet:", set the
566 * protocol to Telnet and process the string as a
567 * Telnet URL.
568 */
569 if (!strncmp(q, "telnet:", 7)) {
570 char c;
571
572 q += 7;
573 if (q[0] == '/' && q[1] == '/')
574 q += 2;
575 cfg.protocol = PROT_TELNET;
576 p = q;
577 while (*p && *p != ':' && *p != '/')
578 p++;
579 c = *p;
580 if (*p)
581 *p++ = '\0';
582 if (c == ':')
583 cfg.port = atoi(p);
584 else
585 cfg.port = -1;
586 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
587 cfg.host[sizeof(cfg.host) - 1] = '\0';
588 } else {
589 char *r;
590 /*
591 * Before we process the [user@]host string, we
592 * first check for the presence of a protocol
593 * prefix (a protocol name followed by ",").
594 */
595 r = strchr(p, ',');
596 if (r) {
597 int i, j;
598 for (i = 0; backends[i].backend != NULL; i++) {
599 j = strlen(backends[i].name);
600 if (j == r - p &&
601 !memcmp(backends[i].name, p, j)) {
602 default_protocol = cfg.protocol =
603 backends[i].protocol;
604 portnumber =
605 backends[i].backend->default_port;
606 p = r + 1;
607 break;
608 }
609 }
610 }
611
612 /*
613 * Three cases. Either (a) there's a nonzero
614 * length string followed by an @, in which
615 * case that's user and the remainder is host.
616 * Or (b) there's only one string, not counting
617 * a potential initial @, and it exists in the
618 * saved-sessions database. Or (c) only one
619 * string and it _doesn't_ exist in the
620 * database.
621 */
622 r = strrchr(p, '@');
623 if (r == p)
624 p++, r = NULL; /* discount initial @ */
625 if (r == NULL) {
626 /*
627 * One string.
628 */
629 Config cfg2;
630 do_defaults(p, &cfg2);
631 if (cfg2.host[0] == '\0') {
632 /* No settings for this host; use defaults */
633 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
634 cfg.host[sizeof(cfg.host) - 1] = '\0';
635 cfg.port = default_port;
636 } else {
637 cfg = cfg2;
638 cfg.remote_cmd_ptr = cfg.remote_cmd;
639 }
640 } else {
641 *r++ = '\0';
642 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
643 cfg.username[sizeof(cfg.username) - 1] = '\0';
644 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
645 cfg.host[sizeof(cfg.host) - 1] = '\0';
646 cfg.port = default_port;
647 }
648 }
649 } else {
650 int len = sizeof(cfg.remote_cmd) - 1;
651 char *cp = cfg.remote_cmd;
652 int len2;
653
654 strncpy(cp, p, len);
655 cp[len] = '\0';
656 len2 = strlen(cp);
657 len -= len2;
658 cp += len2;
659 while (--argc) {
660 if (len > 0)
661 len--, *cp++ = ' ';
662 strncpy(cp, *++argv, len);
663 cp[len] = '\0';
664 len2 = strlen(cp);
665 len -= len2;
666 cp += len2;
667 }
668 cfg.nopty = TRUE; /* command => no terminal */
669 break; /* done with cmdline */
670 }
12dc4ec0 671 }
672 }
673
d8426c54 674 if (!*cfg.host) {
32874aea 675 usage();
d8426c54 676 }
d8426c54 677
449925a6 678 /*
679 * Trim leading whitespace off the hostname if it's there.
680 */
681 {
682 int space = strspn(cfg.host, " \t");
683 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
684 }
685
686 /* See if host is of the form user@host */
687 if (cfg.host[0] != '\0') {
688 char *atsign = strchr(cfg.host, '@');
689 /* Make sure we're not overflowing the user field */
690 if (atsign) {
691 if (atsign - cfg.host < sizeof cfg.username) {
692 strncpy(cfg.username, cfg.host, atsign - cfg.host);
693 cfg.username[atsign - cfg.host] = '\0';
694 }
695 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
696 }
697 }
698
699 /*
700 * Trim a colon suffix off the hostname if it's there.
701 */
702 cfg.host[strcspn(cfg.host, ":")] = '\0';
703
96621a84 704 if (!*cfg.remote_cmd_ptr)
32874aea 705 flags |= FLAG_INTERACTIVE;
67779be7 706
12dc4ec0 707 /*
708 * Select protocol. This is farmed out into a table in a
709 * separate file to enable an ssh-free variant.
710 */
711 {
32874aea 712 int i;
713 back = NULL;
714 for (i = 0; backends[i].backend != NULL; i++)
715 if (backends[i].protocol == cfg.protocol) {
716 back = backends[i].backend;
717 break;
718 }
719 if (back == NULL) {
720 fprintf(stderr,
721 "Internal fault: Unsupported protocol found\n");
722 return 1;
723 }
12dc4ec0 724 }
725
726 /*
e7aabca4 727 * Add extra port forwardings (accumulated on command line) to
728 * cfg.
729 */
730 {
731 int i;
732 char *p;
733 p = extra_portfwd;
734 i = 0;
735 while (cfg.portfwd[i])
736 i += strlen(cfg.portfwd+i) + 1;
737 while (*p) {
738 if (strlen(p)+2 > sizeof(cfg.portfwd)-i) {
739 fprintf(stderr, "Internal fault: not enough space for all"
740 " port forwardings\n");
741 break;
742 }
743 strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1);
744 i += strlen(cfg.portfwd+i) + 1;
745 cfg.portfwd[i] = '\0';
746 p += strlen(p)+1;
747 }
748 }
749
750 /*
8cb9c947 751 * Select port.
752 */
753 if (portnumber != -1)
32874aea 754 cfg.port = portnumber;
8cb9c947 755
756 /*
12dc4ec0 757 * Initialise WinSock.
758 */
759 winsock_ver = MAKEWORD(2, 0);
760 if (WSAStartup(winsock_ver, &wsadata)) {
761 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
762 MB_OK | MB_ICONEXCLAMATION);
763 return 1;
764 }
765 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
766 MessageBox(NULL, "WinSock version is incompatible with 2.0",
767 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
768 WSACleanup();
769 return 1;
770 }
8df7a775 771 sk_init();
12dc4ec0 772
773 /*
774 * Start up the connection.
775 */
8df7a775 776 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 777 {
778 char *error;
779 char *realhost;
2184a5d9 780 /* nodelay is only useful if stdin is a character device (console) */
781 int nodelay = cfg.tcp_nodelay &&
782 (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
12dc4ec0 783
2184a5d9 784 error = back->init(cfg.host, cfg.port, &realhost, nodelay);
12dc4ec0 785 if (error) {
786 fprintf(stderr, "Unable to open connection:\n%s", error);
787 return 1;
788 }
6e1ebb76 789 sfree(realhost);
12dc4ec0 790 }
8df7a775 791 connopen = 1;
12dc4ec0 792
12dc4ec0 793 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
5471d09a 794 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
795 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 796
0965bee0 797 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 798 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 799 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 800 GetConsoleMode(inhandle, &orig_console_mode);
801 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 802
803 /*
12dc4ec0 804 * Turn off ECHO and LINE input modes. We don't care if this
805 * call fails, because we know we aren't necessarily running in
806 * a console.
807 */
12dc4ec0 808 handles[0] = netevent;
809 handles[1] = stdinevent;
5471d09a 810 handles[2] = stdoutevent;
811 handles[3] = stderrevent;
12dc4ec0 812 sending = FALSE;
5471d09a 813
814 /*
815 * Create spare threads to write to stdout and stderr, so we
816 * can arrange asynchronous writes.
817 */
818 odata.event = stdoutevent;
819 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
820 odata.is_stderr = 0;
821 odata.busy = odata.done = 0;
822 if (!CreateThread(NULL, 0, stdout_write_thread,
823 &odata, 0, &out_threadid)) {
824 fprintf(stderr, "Unable to create output thread\n");
825 exit(1);
826 }
827 edata.event = stderrevent;
828 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
829 edata.is_stderr = 1;
830 edata.busy = edata.done = 0;
831 if (!CreateThread(NULL, 0, stdout_write_thread,
832 &edata, 0, &err_threadid)) {
833 fprintf(stderr, "Unable to create error output thread\n");
834 exit(1);
835 }
836
12dc4ec0 837 while (1) {
32874aea 838 int n;
839
840 if (!sending && back->sendok()) {
841 /*
842 * Create a separate thread to read from stdin. This is
843 * a total pain, but I can't find another way to do it:
844 *
845 * - an overlapped ReadFile or ReadFileEx just doesn't
846 * happen; we get failure from ReadFileEx, and
847 * ReadFile blocks despite being given an OVERLAPPED
848 * structure. Perhaps we can't do overlapped reads
849 * on consoles. WHY THE HELL NOT?
850 *
851 * - WaitForMultipleObjects(netevent, console) doesn't
852 * work, because it signals the console when
853 * _anything_ happens, including mouse motions and
854 * other things that don't cause data to be readable
855 * - so we're back to ReadFile blocking.
856 */
857 idata.event = stdinevent;
858 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
859 if (!CreateThread(NULL, 0, stdin_read_thread,
5471d09a 860 &idata, 0, &in_threadid)) {
861 fprintf(stderr, "Unable to create input thread\n");
32874aea 862 exit(1);
863 }
864 sending = TRUE;
865 }
866
5471d09a 867 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
32874aea 868 if (n == 0) {
869 WSANETWORKEVENTS things;
8df7a775 870 SOCKET socket;
d2371c81 871 extern SOCKET first_socket(int *), next_socket(int *);
8df7a775 872 extern int select_result(WPARAM, LPARAM);
32874aea 873 int i, socketstate;
874
875 /*
876 * We must not call select_result() for any socket
877 * until we have finished enumerating within the tree.
878 * This is because select_result() may close the socket
879 * and modify the tree.
880 */
881 /* Count the active sockets. */
882 i = 0;
883 for (socket = first_socket(&socketstate);
884 socket != INVALID_SOCKET;
885 socket = next_socket(&socketstate)) i++;
886
887 /* Expand the buffer if necessary. */
888 if (i > sksize) {
889 sksize = i + 16;
890 sklist = srealloc(sklist, sksize * sizeof(*sklist));
891 }
892
893 /* Retrieve the sockets into sklist. */
894 skcount = 0;
895 for (socket = first_socket(&socketstate);
896 socket != INVALID_SOCKET;
d2371c81 897 socket = next_socket(&socketstate)) {
32874aea 898 sklist[skcount++] = socket;
899 }
900
901 /* Now we're done enumerating; go through the list. */
902 for (i = 0; i < skcount; i++) {
903 WPARAM wp;
904 socket = sklist[i];
905 wp = (WPARAM) socket;
ffb959c7 906 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
32874aea 907 noise_ultralight(socket);
908 noise_ultralight(things.lNetworkEvents);
3ad9d396 909 if (things.lNetworkEvents & FD_CONNECT)
910 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
8df7a775 911 if (things.lNetworkEvents & FD_READ)
32874aea 912 connopen &= select_result(wp, (LPARAM) FD_READ);
8df7a775 913 if (things.lNetworkEvents & FD_CLOSE)
32874aea 914 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
8df7a775 915 if (things.lNetworkEvents & FD_OOB)
32874aea 916 connopen &= select_result(wp, (LPARAM) FD_OOB);
8df7a775 917 if (things.lNetworkEvents & FD_WRITE)
32874aea 918 connopen &= select_result(wp, (LPARAM) FD_WRITE);
d74d141c 919 if (things.lNetworkEvents & FD_ACCEPT)
920 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
921
8df7a775 922 }
923 }
32874aea 924 } else if (n == 1) {
5471d09a 925 reading = 0;
32874aea 926 noise_ultralight(idata.len);
42856df4 927 if (connopen && back->socket() != NULL) {
928 if (idata.len > 0) {
929 back->send(idata.buffer, idata.len);
930 } else {
931 back->special(TS_EOF);
932 }
32874aea 933 }
5471d09a 934 } else if (n == 2) {
935 odata.busy = 0;
936 if (!odata.writeret) {
937 fprintf(stderr, "Unable to write to standard output\n");
938 exit(0);
939 }
940 bufchain_consume(&stdout_data, odata.lenwritten);
941 if (bufchain_size(&stdout_data) > 0)
942 try_output(0);
42856df4 943 if (connopen && back->socket() != NULL) {
944 back->unthrottle(bufchain_size(&stdout_data) +
945 bufchain_size(&stderr_data));
946 }
5471d09a 947 } else if (n == 3) {
948 edata.busy = 0;
949 if (!edata.writeret) {
950 fprintf(stderr, "Unable to write to standard output\n");
951 exit(0);
952 }
953 bufchain_consume(&stderr_data, edata.lenwritten);
954 if (bufchain_size(&stderr_data) > 0)
955 try_output(1);
42856df4 956 if (connopen && back->socket() != NULL) {
957 back->unthrottle(bufchain_size(&stdout_data) +
958 bufchain_size(&stderr_data));
959 }
5471d09a 960 }
961 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
32874aea 962 SetEvent(idata.eventback);
5471d09a 963 reading = 1;
32874aea 964 }
42856df4 965 if ((!connopen || back->socket() == NULL) &&
966 bufchain_size(&stdout_data) == 0 &&
967 bufchain_size(&stderr_data) == 0)
32874aea 968 break; /* we closed the connection */
12dc4ec0 969 }
970 WSACleanup();
d8d6c7e5 971 exitcode = back->exitcode();
972 if (exitcode < 0) {
973 fprintf(stderr, "Remote process exit code unavailable\n");
974 exitcode = 1; /* this is an error condition */
975 }
976 return exitcode;
12dc4ec0 977}