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