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