SSH port forwarding! How cool is that?
[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
32874aea 18void fatalbox(char *p, ...)
19{
12dc4ec0 20 va_list ap;
49bad831 21 fprintf(stderr, "FATAL ERROR: ");
12dc4ec0 22 va_start(ap, p);
23 vfprintf(stderr, p, ap);
24 va_end(ap);
25 fputc('\n', stderr);
26 WSACleanup();
27 exit(1);
28}
32874aea 29void connection_fatal(char *p, ...)
30{
8d5de777 31 va_list ap;
49bad831 32 fprintf(stderr, "FATAL ERROR: ");
8d5de777 33 va_start(ap, p);
34 vfprintf(stderr, p, ap);
35 va_end(ap);
36 fputc('\n', stderr);
37 WSACleanup();
38 exit(1);
39}
12dc4ec0 40
d8426c54 41static char *password = NULL;
42
32874aea 43void logevent(char *string)
44{
45}
a9422f39 46
47void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 48 char *keystr, char *fingerprint)
49{
a9422f39 50 int ret;
fcbb94d3 51 HANDLE hin;
52 DWORD savemode, i;
a9422f39 53
54 static const char absentmsg[] =
32874aea 55 "The server's host key is not cached in the registry. You\n"
56 "have no guarantee that the server is the computer you\n"
57 "think it is.\n"
58 "The server's key fingerprint is:\n"
59 "%s\n"
60 "If you trust this host, enter \"y\" to add the key to\n"
61 "PuTTY's cache and carry on connecting.\n"
d0718310 62 "If you want to carry on connecting just once, without\n"
63 "adding the key to the cache, enter \"n\".\n"
64 "If you do not trust this host, press Return to abandon the\n"
65 "connection.\n"
66 "Store key in cache? (y/n) ";
a9422f39 67
68 static const char wrongmsg[] =
32874aea 69 "WARNING - POTENTIAL SECURITY BREACH!\n"
70 "The server's host key does not match the one PuTTY has\n"
71 "cached in the registry. This means that either the\n"
72 "server administrator has changed the host key, or you\n"
73 "have actually connected to another computer pretending\n"
74 "to be the server.\n"
75 "The new key fingerprint is:\n"
76 "%s\n"
77 "If you were expecting this change and trust the new key,\n"
78 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
79 "If you want to carry on connecting but without updating\n"
80 "the cache, enter \"n\".\n"
81 "If you want to abandon the connection completely, press\n"
82 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
83 "safe choice.\n"
84 "Update cached key? (y/n, Return cancels connection) ";
a9422f39 85
86 static const char abandoned[] = "Connection abandoned.\n";
87
88 char line[32];
89
90 /*
91 * Verify the key against the registry.
92 */
93 ret = verify_host_key(host, port, keytype, keystr);
94
32874aea 95 if (ret == 0) /* success - key matched OK */
96 return;
fcbb94d3 97
b4453f49 98 if (ret == 2) { /* key was different */
32874aea 99 fprintf(stderr, wrongmsg, fingerprint);
b4453f49 100 fflush(stderr);
101 }
102 if (ret == 1) { /* key was absent */
32874aea 103 fprintf(stderr, absentmsg, fingerprint);
b4453f49 104 fflush(stderr);
105 }
fcbb94d3 106
107 hin = GetStdHandle(STD_INPUT_HANDLE);
108 GetConsoleMode(hin, &savemode);
109 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
32874aea 110 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
111 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
fcbb94d3 112 SetConsoleMode(hin, savemode);
113
d0718310 114 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
32874aea 115 if (line[0] == 'y' || line[0] == 'Y')
116 store_host_key(host, port, keytype, keystr);
d0718310 117 } else {
118 fprintf(stderr, abandoned);
119 exit(0);
a9422f39 120 }
121}
989b10e9 122
0965bee0 123HANDLE inhandle, outhandle, errhandle;
6f34e365 124DWORD orig_console_mode;
125
8df7a775 126WSAEVENT netevent;
127
32874aea 128void from_backend(int is_stderr, char *data, int len)
129{
fe50e814 130 int pos;
12dc4ec0 131 DWORD ret;
fe50e814 132 HANDLE h = (is_stderr ? errhandle : outhandle);
133
134 pos = 0;
135 while (pos < len) {
32874aea 136 if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
137 return; /* give up in panic */
138 pos += ret;
12dc4ec0 139 }
12dc4ec0 140}
141
32874aea 142int term_ldisc(int mode)
143{
144 return FALSE;
145}
146void ldisc_update(int echo, int edit)
147{
0965bee0 148 /* Update stdin read mode to reflect changes in line discipline. */
149 DWORD mode;
150
151 mode = ENABLE_PROCESSED_INPUT;
152 if (echo)
32874aea 153 mode = mode | ENABLE_ECHO_INPUT;
0965bee0 154 else
32874aea 155 mode = mode & ~ENABLE_ECHO_INPUT;
0965bee0 156 if (edit)
32874aea 157 mode = mode | ENABLE_LINE_INPUT;
0965bee0 158 else
32874aea 159 mode = mode & ~ENABLE_LINE_INPUT;
0965bee0 160 SetConsoleMode(inhandle, mode);
161}
162
12dc4ec0 163struct input_data {
164 DWORD len;
165 char buffer[4096];
8df7a775 166 HANDLE event, eventback;
12dc4ec0 167};
168
fa17a66e 169static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
67779be7 170{
171 HANDLE hin, hout;
fa17a66e 172 DWORD savemode, newmode, i;
67779be7 173
fa17a66e 174 if (is_pw && password) {
32874aea 175 static int tried_once = 0;
176
177 if (tried_once) {
178 return 0;
179 } else {
180 strncpy(str, password, maxlen);
181 str[maxlen - 1] = '\0';
182 tried_once = 1;
183 return 1;
184 }
67779be7 185 }
67779be7 186
187 hin = GetStdHandle(STD_INPUT_HANDLE);
188 hout = GetStdHandle(STD_OUTPUT_HANDLE);
189 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
32874aea 190 fprintf(stderr, "Cannot get standard input/output handles");
191 return 0;
67779be7 192 }
193
194 GetConsoleMode(hin, &savemode);
fa17a66e 195 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
196 if (is_pw)
32874aea 197 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 198 else
32874aea 199 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 200 SetConsoleMode(hin, newmode);
67779be7 201
202 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 203 ReadFile(hin, str, maxlen - 1, &i, NULL);
67779be7 204
205 SetConsoleMode(hin, savemode);
206
32874aea 207 if ((int) i > maxlen)
208 i = maxlen - 1;
209 else
210 i = i - 2;
67779be7 211 str[i] = '\0';
212
fa17a66e 213 if (is_pw)
32874aea 214 WriteFile(hout, "\r\n", 2, &i, NULL);
67779be7 215
216 return 1;
217}
218
32874aea 219static DWORD WINAPI stdin_read_thread(void *param)
220{
221 struct input_data *idata = (struct input_data *) param;
12dc4ec0 222 HANDLE inhandle;
223
224 inhandle = GetStdHandle(STD_INPUT_HANDLE);
225
226 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
32874aea 227 &idata->len, NULL) && idata->len > 0) {
228 SetEvent(idata->event);
229 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 230 }
231
232 idata->len = 0;
233 SetEvent(idata->event);
234
235 return 0;
236}
237
d8426c54 238/*
239 * Short description of parameters.
240 */
241static void usage(void)
242{
243 printf("PuTTY Link: command-line connection utility\n");
244 printf("%s\n", ver);
245 printf("Usage: plink [options] [user@]host [command]\n");
246 printf("Options:\n");
247 printf(" -v show verbose messages\n");
248 printf(" -ssh force use of ssh protocol\n");
249 printf(" -P port connect to specified port\n");
250 printf(" -pw passw login with specified password\n");
96621a84 251 printf(" -m file read remote command(s) from file\n");
d8426c54 252 exit(1);
253}
254
32874aea 255char *do_select(SOCKET skt, int startup)
256{
8df7a775 257 int events;
258 if (startup) {
d74d141c 259 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT;
8df7a775 260 } else {
261 events = 0;
262 }
32874aea 263 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
264 switch (WSAGetLastError()) {
265 case WSAENETDOWN:
266 return "Network is down";
267 default:
268 return "WSAAsyncSelect(): unknown error";
269 }
8df7a775 270 }
271 return NULL;
272}
273
32874aea 274int main(int argc, char **argv)
275{
12dc4ec0 276 WSADATA wsadata;
277 WORD winsock_ver;
8df7a775 278 WSAEVENT stdinevent;
12dc4ec0 279 HANDLE handles[2];
12dc4ec0 280 DWORD threadid;
281 struct input_data idata;
282 int sending;
d8426c54 283 int portnumber = -1;
8df7a775 284 SOCKET *sklist;
285 int skcount, sksize;
286 int connopen;
12dc4ec0 287
fa17a66e 288 ssh_get_line = get_line;
67779be7 289
32874aea 290 sklist = NULL;
291 skcount = sksize = 0;
c9bdcd96 292 /*
293 * Initialise port and protocol to sensible defaults. (These
294 * will be overridden by more or less anything.)
295 */
296 default_protocol = PROT_SSH;
297 default_port = 22;
8df7a775 298
67779be7 299 flags = FLAG_STDERR;
12dc4ec0 300 /*
301 * Process the command line.
302 */
a9422f39 303 do_defaults(NULL, &cfg);
e7a7383f 304 default_protocol = cfg.protocol;
305 default_port = cfg.port;
8cb9c947 306 {
32874aea 307 /*
308 * Override the default protocol if PLINK_PROTOCOL is set.
309 */
310 char *p = getenv("PLINK_PROTOCOL");
311 int i;
312 if (p) {
313 for (i = 0; backends[i].backend != NULL; i++) {
314 if (!strcmp(backends[i].name, p)) {
315 default_protocol = cfg.protocol = backends[i].protocol;
316 default_port = cfg.port =
317 backends[i].backend->default_port;
318 break;
319 }
320 }
321 }
8cb9c947 322 }
12dc4ec0 323 while (--argc) {
32874aea 324 char *p = *++argv;
325 if (*p == '-') {
326 if (!strcmp(p, "-ssh")) {
12dc4ec0 327 default_protocol = cfg.protocol = PROT_SSH;
328 default_port = cfg.port = 22;
32874aea 329 } else if (!strcmp(p, "-telnet")) {
9d33ebdd 330 default_protocol = cfg.protocol = PROT_TELNET;
331 default_port = cfg.port = 23;
32874aea 332 } else if (!strcmp(p, "-raw")) {
9d33ebdd 333 default_protocol = cfg.protocol = PROT_RAW;
67779be7 334 } else if (!strcmp(p, "-v")) {
32874aea 335 flags |= FLAG_VERBOSE;
12dc4ec0 336 } else if (!strcmp(p, "-log")) {
32874aea 337 logfile = "putty.log";
338 } else if (!strcmp(p, "-pw") && argc > 1) {
339 --argc, password = *++argv;
340 } else if (!strcmp(p, "-l") && argc > 1) {
341 char *username;
342 --argc, username = *++argv;
343 strncpy(cfg.username, username, sizeof(cfg.username));
344 cfg.username[sizeof(cfg.username) - 1] = '\0';
345 } else if (!strcmp(p, "-m") && argc > 1) {
346 char *filename, *command;
347 int cmdlen, cmdsize;
348 FILE *fp;
349 int c, d;
350
351 --argc, filename = *++argv;
352
353 cmdlen = cmdsize = 0;
354 command = NULL;
355 fp = fopen(filename, "r");
356 if (!fp) {
357 fprintf(stderr, "plink: unable to open command "
358 "file \"%s\"\n", filename);
359 return 1;
360 }
361 do {
362 c = fgetc(fp);
363 d = c;
364 if (c == EOF)
365 d = 0;
366 if (cmdlen >= cmdsize) {
367 cmdsize = cmdlen + 512;
368 command = srealloc(command, cmdsize);
369 }
370 command[cmdlen++] = d;
371 } while (c != EOF);
372 cfg.remote_cmd_ptr = command;
373 cfg.nopty = TRUE; /* command => no terminal */
374 } else if (!strcmp(p, "-P") && argc > 1) {
375 --argc, portnumber = atoi(*++argv);
376 }
12dc4ec0 377 } else if (*p) {
32874aea 378 if (!*cfg.host) {
379 char *q = p;
380 /*
381 * If the hostname starts with "telnet:", set the
382 * protocol to Telnet and process the string as a
383 * Telnet URL.
384 */
385 if (!strncmp(q, "telnet:", 7)) {
386 char c;
387
388 q += 7;
389 if (q[0] == '/' && q[1] == '/')
390 q += 2;
391 cfg.protocol = PROT_TELNET;
392 p = q;
393 while (*p && *p != ':' && *p != '/')
394 p++;
395 c = *p;
396 if (*p)
397 *p++ = '\0';
398 if (c == ':')
399 cfg.port = atoi(p);
400 else
401 cfg.port = -1;
402 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
403 cfg.host[sizeof(cfg.host) - 1] = '\0';
404 } else {
405 char *r;
406 /*
407 * Before we process the [user@]host string, we
408 * first check for the presence of a protocol
409 * prefix (a protocol name followed by ",").
410 */
411 r = strchr(p, ',');
412 if (r) {
413 int i, j;
414 for (i = 0; backends[i].backend != NULL; i++) {
415 j = strlen(backends[i].name);
416 if (j == r - p &&
417 !memcmp(backends[i].name, p, j)) {
418 default_protocol = cfg.protocol =
419 backends[i].protocol;
420 portnumber =
421 backends[i].backend->default_port;
422 p = r + 1;
423 break;
424 }
425 }
426 }
427
428 /*
429 * Three cases. Either (a) there's a nonzero
430 * length string followed by an @, in which
431 * case that's user and the remainder is host.
432 * Or (b) there's only one string, not counting
433 * a potential initial @, and it exists in the
434 * saved-sessions database. Or (c) only one
435 * string and it _doesn't_ exist in the
436 * database.
437 */
438 r = strrchr(p, '@');
439 if (r == p)
440 p++, r = NULL; /* discount initial @ */
441 if (r == NULL) {
442 /*
443 * One string.
444 */
445 Config cfg2;
446 do_defaults(p, &cfg2);
447 if (cfg2.host[0] == '\0') {
448 /* No settings for this host; use defaults */
449 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
450 cfg.host[sizeof(cfg.host) - 1] = '\0';
451 cfg.port = default_port;
452 } else {
453 cfg = cfg2;
454 cfg.remote_cmd_ptr = cfg.remote_cmd;
455 }
456 } else {
457 *r++ = '\0';
458 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
459 cfg.username[sizeof(cfg.username) - 1] = '\0';
460 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
461 cfg.host[sizeof(cfg.host) - 1] = '\0';
462 cfg.port = default_port;
463 }
464 }
465 } else {
466 int len = sizeof(cfg.remote_cmd) - 1;
467 char *cp = cfg.remote_cmd;
468 int len2;
469
470 strncpy(cp, p, len);
471 cp[len] = '\0';
472 len2 = strlen(cp);
473 len -= len2;
474 cp += len2;
475 while (--argc) {
476 if (len > 0)
477 len--, *cp++ = ' ';
478 strncpy(cp, *++argv, len);
479 cp[len] = '\0';
480 len2 = strlen(cp);
481 len -= len2;
482 cp += len2;
483 }
484 cfg.nopty = TRUE; /* command => no terminal */
485 break; /* done with cmdline */
486 }
12dc4ec0 487 }
488 }
489
d8426c54 490 if (!*cfg.host) {
32874aea 491 usage();
d8426c54 492 }
d8426c54 493
96621a84 494 if (!*cfg.remote_cmd_ptr)
32874aea 495 flags |= FLAG_INTERACTIVE;
67779be7 496
12dc4ec0 497 /*
498 * Select protocol. This is farmed out into a table in a
499 * separate file to enable an ssh-free variant.
500 */
501 {
32874aea 502 int i;
503 back = NULL;
504 for (i = 0; backends[i].backend != NULL; i++)
505 if (backends[i].protocol == cfg.protocol) {
506 back = backends[i].backend;
507 break;
508 }
509 if (back == NULL) {
510 fprintf(stderr,
511 "Internal fault: Unsupported protocol found\n");
512 return 1;
513 }
12dc4ec0 514 }
515
516 /*
8cb9c947 517 * Select port.
518 */
519 if (portnumber != -1)
32874aea 520 cfg.port = portnumber;
8cb9c947 521
522 /*
12dc4ec0 523 * Initialise WinSock.
524 */
525 winsock_ver = MAKEWORD(2, 0);
526 if (WSAStartup(winsock_ver, &wsadata)) {
527 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
528 MB_OK | MB_ICONEXCLAMATION);
529 return 1;
530 }
531 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
532 MessageBox(NULL, "WinSock version is incompatible with 2.0",
533 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
534 WSACleanup();
535 return 1;
536 }
8df7a775 537 sk_init();
12dc4ec0 538
539 /*
540 * Start up the connection.
541 */
8df7a775 542 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 543 {
544 char *error;
545 char *realhost;
546
32874aea 547 error = back->init(cfg.host, cfg.port, &realhost);
12dc4ec0 548 if (error) {
549 fprintf(stderr, "Unable to open connection:\n%s", error);
550 return 1;
551 }
6e1ebb76 552 sfree(realhost);
12dc4ec0 553 }
8df7a775 554 connopen = 1;
12dc4ec0 555
12dc4ec0 556 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
557
0965bee0 558 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 559 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 560 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 561 GetConsoleMode(inhandle, &orig_console_mode);
562 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 563
564 /*
12dc4ec0 565 * Turn off ECHO and LINE input modes. We don't care if this
566 * call fails, because we know we aren't necessarily running in
567 * a console.
568 */
12dc4ec0 569 handles[0] = netevent;
570 handles[1] = stdinevent;
571 sending = FALSE;
572 while (1) {
32874aea 573 int n;
574
575 if (!sending && back->sendok()) {
576 /*
577 * Create a separate thread to read from stdin. This is
578 * a total pain, but I can't find another way to do it:
579 *
580 * - an overlapped ReadFile or ReadFileEx just doesn't
581 * happen; we get failure from ReadFileEx, and
582 * ReadFile blocks despite being given an OVERLAPPED
583 * structure. Perhaps we can't do overlapped reads
584 * on consoles. WHY THE HELL NOT?
585 *
586 * - WaitForMultipleObjects(netevent, console) doesn't
587 * work, because it signals the console when
588 * _anything_ happens, including mouse motions and
589 * other things that don't cause data to be readable
590 * - so we're back to ReadFile blocking.
591 */
592 idata.event = stdinevent;
593 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
594 if (!CreateThread(NULL, 0, stdin_read_thread,
595 &idata, 0, &threadid)) {
596 fprintf(stderr, "Unable to create second thread\n");
597 exit(1);
598 }
599 sending = TRUE;
600 }
601
602 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
603 if (n == 0) {
604 WSANETWORKEVENTS things;
8df7a775 605 SOCKET socket;
d2371c81 606 extern SOCKET first_socket(int *), next_socket(int *);
8df7a775 607 extern int select_result(WPARAM, LPARAM);
32874aea 608 int i, socketstate;
609
610 /*
611 * We must not call select_result() for any socket
612 * until we have finished enumerating within the tree.
613 * This is because select_result() may close the socket
614 * and modify the tree.
615 */
616 /* Count the active sockets. */
617 i = 0;
618 for (socket = first_socket(&socketstate);
619 socket != INVALID_SOCKET;
620 socket = next_socket(&socketstate)) i++;
621
622 /* Expand the buffer if necessary. */
623 if (i > sksize) {
624 sksize = i + 16;
625 sklist = srealloc(sklist, sksize * sizeof(*sklist));
626 }
627
628 /* Retrieve the sockets into sklist. */
629 skcount = 0;
630 for (socket = first_socket(&socketstate);
631 socket != INVALID_SOCKET;
d2371c81 632 socket = next_socket(&socketstate)) {
32874aea 633 sklist[skcount++] = socket;
634 }
635
636 /* Now we're done enumerating; go through the list. */
637 for (i = 0; i < skcount; i++) {
638 WPARAM wp;
639 socket = sklist[i];
640 wp = (WPARAM) socket;
ffb959c7 641 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
32874aea 642 noise_ultralight(socket);
643 noise_ultralight(things.lNetworkEvents);
8df7a775 644 if (things.lNetworkEvents & FD_READ)
32874aea 645 connopen &= select_result(wp, (LPARAM) FD_READ);
8df7a775 646 if (things.lNetworkEvents & FD_CLOSE)
32874aea 647 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
8df7a775 648 if (things.lNetworkEvents & FD_OOB)
32874aea 649 connopen &= select_result(wp, (LPARAM) FD_OOB);
8df7a775 650 if (things.lNetworkEvents & FD_WRITE)
32874aea 651 connopen &= select_result(wp, (LPARAM) FD_WRITE);
d74d141c 652 if (things.lNetworkEvents & FD_ACCEPT)
653 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
654
8df7a775 655 }
656 }
32874aea 657 } else if (n == 1) {
658 noise_ultralight(idata.len);
659 if (idata.len > 0) {
660 back->send(idata.buffer, idata.len);
661 } else {
662 back->special(TS_EOF);
663 }
664 SetEvent(idata.eventback);
665 }
666 if (!connopen || back->socket() == NULL)
667 break; /* we closed the connection */
12dc4ec0 668 }
669 WSACleanup();
670 return 0;
671}