Run entire source base through GNU indent to tidy up the varying
[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"
62 "If you do not trust this host, enter \"n\" to abandon the\n"
63 "connection.\n" "Continue connecting? (y/n) ";
a9422f39 64
65 static const char wrongmsg[] =
32874aea 66 "WARNING - POTENTIAL SECURITY BREACH!\n"
67 "The server's host key does not match the one PuTTY has\n"
68 "cached in the registry. This means that either the\n"
69 "server administrator has changed the host key, or you\n"
70 "have actually connected to another computer pretending\n"
71 "to be the server.\n"
72 "The new key fingerprint is:\n"
73 "%s\n"
74 "If you were expecting this change and trust the new key,\n"
75 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
76 "If you want to carry on connecting but without updating\n"
77 "the cache, enter \"n\".\n"
78 "If you want to abandon the connection completely, press\n"
79 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
80 "safe choice.\n"
81 "Update cached key? (y/n, Return cancels connection) ";
a9422f39 82
83 static const char abandoned[] = "Connection abandoned.\n";
84
85 char line[32];
86
87 /*
88 * Verify the key against the registry.
89 */
90 ret = verify_host_key(host, port, keytype, keystr);
91
32874aea 92 if (ret == 0) /* success - key matched OK */
93 return;
fcbb94d3 94
b4453f49 95 if (ret == 2) { /* key was different */
32874aea 96 fprintf(stderr, wrongmsg, fingerprint);
b4453f49 97 fflush(stderr);
98 }
99 if (ret == 1) { /* key was absent */
32874aea 100 fprintf(stderr, absentmsg, fingerprint);
b4453f49 101 fflush(stderr);
102 }
fcbb94d3 103
104 hin = GetStdHandle(STD_INPUT_HANDLE);
105 GetConsoleMode(hin, &savemode);
106 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
32874aea 107 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
108 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
fcbb94d3 109 SetConsoleMode(hin, savemode);
110
32874aea 111 if (ret == 2) { /* key was different */
112 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
113 if (line[0] == 'y' || line[0] == 'Y')
114 store_host_key(host, port, keytype, keystr);
115 } else {
116 fprintf(stderr, abandoned);
117 exit(0);
118 }
a9422f39 119 }
32874aea 120 if (ret == 1) { /* key was absent */
121 if (line[0] == 'y' || line[0] == 'Y')
122 store_host_key(host, port, keytype, keystr);
123 else {
124 fprintf(stderr, abandoned);
125 exit(0);
126 }
a9422f39 127 }
128}
989b10e9 129
0965bee0 130HANDLE inhandle, outhandle, errhandle;
6f34e365 131DWORD orig_console_mode;
132
8df7a775 133WSAEVENT netevent;
134
32874aea 135void from_backend(int is_stderr, char *data, int len)
136{
fe50e814 137 int pos;
12dc4ec0 138 DWORD ret;
fe50e814 139 HANDLE h = (is_stderr ? errhandle : outhandle);
140
141 pos = 0;
142 while (pos < len) {
32874aea 143 if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
144 return; /* give up in panic */
145 pos += ret;
12dc4ec0 146 }
12dc4ec0 147}
148
32874aea 149int term_ldisc(int mode)
150{
151 return FALSE;
152}
153void ldisc_update(int echo, int edit)
154{
0965bee0 155 /* Update stdin read mode to reflect changes in line discipline. */
156 DWORD mode;
157
158 mode = ENABLE_PROCESSED_INPUT;
159 if (echo)
32874aea 160 mode = mode | ENABLE_ECHO_INPUT;
0965bee0 161 else
32874aea 162 mode = mode & ~ENABLE_ECHO_INPUT;
0965bee0 163 if (edit)
32874aea 164 mode = mode | ENABLE_LINE_INPUT;
0965bee0 165 else
32874aea 166 mode = mode & ~ENABLE_LINE_INPUT;
0965bee0 167 SetConsoleMode(inhandle, mode);
168}
169
12dc4ec0 170struct input_data {
171 DWORD len;
172 char buffer[4096];
8df7a775 173 HANDLE event, eventback;
12dc4ec0 174};
175
fa17a66e 176static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
67779be7 177{
178 HANDLE hin, hout;
fa17a66e 179 DWORD savemode, newmode, i;
67779be7 180
fa17a66e 181 if (is_pw && password) {
32874aea 182 static int tried_once = 0;
183
184 if (tried_once) {
185 return 0;
186 } else {
187 strncpy(str, password, maxlen);
188 str[maxlen - 1] = '\0';
189 tried_once = 1;
190 return 1;
191 }
67779be7 192 }
67779be7 193
194 hin = GetStdHandle(STD_INPUT_HANDLE);
195 hout = GetStdHandle(STD_OUTPUT_HANDLE);
196 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
32874aea 197 fprintf(stderr, "Cannot get standard input/output handles");
198 return 0;
67779be7 199 }
200
201 GetConsoleMode(hin, &savemode);
fa17a66e 202 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
203 if (is_pw)
32874aea 204 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 205 else
32874aea 206 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 207 SetConsoleMode(hin, newmode);
67779be7 208
209 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 210 ReadFile(hin, str, maxlen - 1, &i, NULL);
67779be7 211
212 SetConsoleMode(hin, savemode);
213
32874aea 214 if ((int) i > maxlen)
215 i = maxlen - 1;
216 else
217 i = i - 2;
67779be7 218 str[i] = '\0';
219
fa17a66e 220 if (is_pw)
32874aea 221 WriteFile(hout, "\r\n", 2, &i, NULL);
67779be7 222
223 return 1;
224}
225
32874aea 226static DWORD WINAPI stdin_read_thread(void *param)
227{
228 struct input_data *idata = (struct input_data *) param;
12dc4ec0 229 HANDLE inhandle;
230
231 inhandle = GetStdHandle(STD_INPUT_HANDLE);
232
233 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
32874aea 234 &idata->len, NULL) && idata->len > 0) {
235 SetEvent(idata->event);
236 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 237 }
238
239 idata->len = 0;
240 SetEvent(idata->event);
241
242 return 0;
243}
244
d8426c54 245/*
246 * Short description of parameters.
247 */
248static void usage(void)
249{
250 printf("PuTTY Link: command-line connection utility\n");
251 printf("%s\n", ver);
252 printf("Usage: plink [options] [user@]host [command]\n");
253 printf("Options:\n");
254 printf(" -v show verbose messages\n");
255 printf(" -ssh force use of ssh protocol\n");
256 printf(" -P port connect to specified port\n");
257 printf(" -pw passw login with specified password\n");
96621a84 258 printf(" -m file read remote command(s) from file\n");
d8426c54 259 exit(1);
260}
261
32874aea 262char *do_select(SOCKET skt, int startup)
263{
8df7a775 264 int events;
265 if (startup) {
266 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
267 } else {
268 events = 0;
269 }
32874aea 270 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
271 switch (WSAGetLastError()) {
272 case WSAENETDOWN:
273 return "Network is down";
274 default:
275 return "WSAAsyncSelect(): unknown error";
276 }
8df7a775 277 }
278 return NULL;
279}
280
32874aea 281int main(int argc, char **argv)
282{
12dc4ec0 283 WSADATA wsadata;
284 WORD winsock_ver;
8df7a775 285 WSAEVENT stdinevent;
12dc4ec0 286 HANDLE handles[2];
12dc4ec0 287 DWORD threadid;
288 struct input_data idata;
289 int sending;
d8426c54 290 int portnumber = -1;
8df7a775 291 SOCKET *sklist;
292 int skcount, sksize;
293 int connopen;
12dc4ec0 294
fa17a66e 295 ssh_get_line = get_line;
67779be7 296
32874aea 297 sklist = NULL;
298 skcount = sksize = 0;
8df7a775 299
67779be7 300 flags = FLAG_STDERR;
12dc4ec0 301 /*
302 * Process the command line.
303 */
a9422f39 304 do_defaults(NULL, &cfg);
e7a7383f 305 default_protocol = cfg.protocol;
306 default_port = cfg.port;
8cb9c947 307 {
32874aea 308 /*
309 * Override the default protocol if PLINK_PROTOCOL is set.
310 */
311 char *p = getenv("PLINK_PROTOCOL");
312 int i;
313 if (p) {
314 for (i = 0; backends[i].backend != NULL; i++) {
315 if (!strcmp(backends[i].name, p)) {
316 default_protocol = cfg.protocol = backends[i].protocol;
317 default_port = cfg.port =
318 backends[i].backend->default_port;
319 break;
320 }
321 }
322 }
8cb9c947 323 }
12dc4ec0 324 while (--argc) {
32874aea 325 char *p = *++argv;
326 if (*p == '-') {
327 if (!strcmp(p, "-ssh")) {
12dc4ec0 328 default_protocol = cfg.protocol = PROT_SSH;
329 default_port = cfg.port = 22;
32874aea 330 } else if (!strcmp(p, "-telnet")) {
9d33ebdd 331 default_protocol = cfg.protocol = PROT_TELNET;
332 default_port = cfg.port = 23;
32874aea 333 } else if (!strcmp(p, "-raw")) {
9d33ebdd 334 default_protocol = cfg.protocol = PROT_RAW;
67779be7 335 } else if (!strcmp(p, "-v")) {
32874aea 336 flags |= FLAG_VERBOSE;
12dc4ec0 337 } else if (!strcmp(p, "-log")) {
32874aea 338 logfile = "putty.log";
339 } else if (!strcmp(p, "-pw") && argc > 1) {
340 --argc, password = *++argv;
341 } else if (!strcmp(p, "-l") && argc > 1) {
342 char *username;
343 --argc, username = *++argv;
344 strncpy(cfg.username, username, sizeof(cfg.username));
345 cfg.username[sizeof(cfg.username) - 1] = '\0';
346 } else if (!strcmp(p, "-m") && argc > 1) {
347 char *filename, *command;
348 int cmdlen, cmdsize;
349 FILE *fp;
350 int c, d;
351
352 --argc, filename = *++argv;
353
354 cmdlen = cmdsize = 0;
355 command = NULL;
356 fp = fopen(filename, "r");
357 if (!fp) {
358 fprintf(stderr, "plink: unable to open command "
359 "file \"%s\"\n", filename);
360 return 1;
361 }
362 do {
363 c = fgetc(fp);
364 d = c;
365 if (c == EOF)
366 d = 0;
367 if (cmdlen >= cmdsize) {
368 cmdsize = cmdlen + 512;
369 command = srealloc(command, cmdsize);
370 }
371 command[cmdlen++] = d;
372 } while (c != EOF);
373 cfg.remote_cmd_ptr = command;
374 cfg.nopty = TRUE; /* command => no terminal */
375 } else if (!strcmp(p, "-P") && argc > 1) {
376 --argc, portnumber = atoi(*++argv);
377 }
12dc4ec0 378 } else if (*p) {
32874aea 379 if (!*cfg.host) {
380 char *q = p;
381 /*
382 * If the hostname starts with "telnet:", set the
383 * protocol to Telnet and process the string as a
384 * Telnet URL.
385 */
386 if (!strncmp(q, "telnet:", 7)) {
387 char c;
388
389 q += 7;
390 if (q[0] == '/' && q[1] == '/')
391 q += 2;
392 cfg.protocol = PROT_TELNET;
393 p = q;
394 while (*p && *p != ':' && *p != '/')
395 p++;
396 c = *p;
397 if (*p)
398 *p++ = '\0';
399 if (c == ':')
400 cfg.port = atoi(p);
401 else
402 cfg.port = -1;
403 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
404 cfg.host[sizeof(cfg.host) - 1] = '\0';
405 } else {
406 char *r;
407 /*
408 * Before we process the [user@]host string, we
409 * first check for the presence of a protocol
410 * prefix (a protocol name followed by ",").
411 */
412 r = strchr(p, ',');
413 if (r) {
414 int i, j;
415 for (i = 0; backends[i].backend != NULL; i++) {
416 j = strlen(backends[i].name);
417 if (j == r - p &&
418 !memcmp(backends[i].name, p, j)) {
419 default_protocol = cfg.protocol =
420 backends[i].protocol;
421 portnumber =
422 backends[i].backend->default_port;
423 p = r + 1;
424 break;
425 }
426 }
427 }
428
429 /*
430 * Three cases. Either (a) there's a nonzero
431 * length string followed by an @, in which
432 * case that's user and the remainder is host.
433 * Or (b) there's only one string, not counting
434 * a potential initial @, and it exists in the
435 * saved-sessions database. Or (c) only one
436 * string and it _doesn't_ exist in the
437 * database.
438 */
439 r = strrchr(p, '@');
440 if (r == p)
441 p++, r = NULL; /* discount initial @ */
442 if (r == NULL) {
443 /*
444 * One string.
445 */
446 Config cfg2;
447 do_defaults(p, &cfg2);
448 if (cfg2.host[0] == '\0') {
449 /* No settings for this host; use defaults */
450 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
451 cfg.host[sizeof(cfg.host) - 1] = '\0';
452 cfg.port = default_port;
453 } else {
454 cfg = cfg2;
455 cfg.remote_cmd_ptr = cfg.remote_cmd;
456 }
457 } else {
458 *r++ = '\0';
459 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
460 cfg.username[sizeof(cfg.username) - 1] = '\0';
461 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
462 cfg.host[sizeof(cfg.host) - 1] = '\0';
463 cfg.port = default_port;
464 }
465 }
466 } else {
467 int len = sizeof(cfg.remote_cmd) - 1;
468 char *cp = cfg.remote_cmd;
469 int len2;
470
471 strncpy(cp, p, len);
472 cp[len] = '\0';
473 len2 = strlen(cp);
474 len -= len2;
475 cp += len2;
476 while (--argc) {
477 if (len > 0)
478 len--, *cp++ = ' ';
479 strncpy(cp, *++argv, len);
480 cp[len] = '\0';
481 len2 = strlen(cp);
482 len -= len2;
483 cp += len2;
484 }
485 cfg.nopty = TRUE; /* command => no terminal */
486 break; /* done with cmdline */
487 }
12dc4ec0 488 }
489 }
490
d8426c54 491 if (!*cfg.host) {
32874aea 492 usage();
d8426c54 493 }
d8426c54 494
96621a84 495 if (!*cfg.remote_cmd_ptr)
32874aea 496 flags |= FLAG_INTERACTIVE;
67779be7 497
12dc4ec0 498 /*
499 * Select protocol. This is farmed out into a table in a
500 * separate file to enable an ssh-free variant.
501 */
502 {
32874aea 503 int i;
504 back = NULL;
505 for (i = 0; backends[i].backend != NULL; i++)
506 if (backends[i].protocol == cfg.protocol) {
507 back = backends[i].backend;
508 break;
509 }
510 if (back == NULL) {
511 fprintf(stderr,
512 "Internal fault: Unsupported protocol found\n");
513 return 1;
514 }
12dc4ec0 515 }
516
517 /*
8cb9c947 518 * Select port.
519 */
520 if (portnumber != -1)
32874aea 521 cfg.port = portnumber;
8cb9c947 522
523 /*
12dc4ec0 524 * Initialise WinSock.
525 */
526 winsock_ver = MAKEWORD(2, 0);
527 if (WSAStartup(winsock_ver, &wsadata)) {
528 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
529 MB_OK | MB_ICONEXCLAMATION);
530 return 1;
531 }
532 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
533 MessageBox(NULL, "WinSock version is incompatible with 2.0",
534 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
535 WSACleanup();
536 return 1;
537 }
8df7a775 538 sk_init();
12dc4ec0 539
540 /*
541 * Start up the connection.
542 */
8df7a775 543 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 544 {
545 char *error;
546 char *realhost;
547
32874aea 548 error = back->init(cfg.host, cfg.port, &realhost);
12dc4ec0 549 if (error) {
550 fprintf(stderr, "Unable to open connection:\n%s", error);
551 return 1;
552 }
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);
8df7a775 652 }
653 }
32874aea 654 } else if (n == 1) {
655 noise_ultralight(idata.len);
656 if (idata.len > 0) {
657 back->send(idata.buffer, idata.len);
658 } else {
659 back->special(TS_EOF);
660 }
661 SetEvent(idata.eventback);
662 }
663 if (!connopen || back->socket() == NULL)
664 break; /* we closed the connection */
12dc4ec0 665 }
666 WSACleanup();
667 return 0;
668}