d493c686790ac611b6f0373cb7a984e54544d220
[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 va_list ap;
20 fprintf(stderr, "FATAL ERROR: ");
21 va_start(ap, p);
22 vfprintf(stderr, p, ap);
23 va_end(ap);
24 fputc('\n', stderr);
25 WSACleanup();
26 exit(1);
27 }
28 void connection_fatal (char *p, ...) {
29 va_list ap;
30 fprintf(stderr, "FATAL ERROR: ");
31 va_start(ap, p);
32 vfprintf(stderr, p, ap);
33 va_end(ap);
34 fputc('\n', stderr);
35 WSACleanup();
36 exit(1);
37 }
38
39 static char *password = NULL;
40
41 void logevent(char *string) { }
42
43 void verify_ssh_host_key(char *host, int port, char *keytype,
44 char *keystr, char *fingerprint) {
45 int ret;
46 HANDLE hin;
47 DWORD savemode, i;
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;
90
91 if (ret == 2) { /* key was different */
92 fprintf(stderr, wrongmsg, fingerprint);
93 fflush(stderr);
94 }
95 if (ret == 1) { /* key was absent */
96 fprintf(stderr, absentmsg, fingerprint);
97 fflush(stderr);
98 }
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') {
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 */
117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
119 else {
120 fprintf(stderr, abandoned);
121 exit(0);
122 }
123 }
124 }
125
126 HANDLE inhandle, outhandle, errhandle;
127 DWORD orig_console_mode;
128
129 WSAEVENT netevent;
130
131 void from_backend(int is_stderr, char *data, int len) {
132 int pos;
133 DWORD ret;
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))
139 return; /* give up in panic */
140 pos += ret;
141 }
142 }
143
144 int term_ldisc(int mode) { return FALSE; }
145 void 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
161 struct input_data {
162 DWORD len;
163 char buffer[4096];
164 HANDLE event, eventback;
165 };
166
167 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
168 {
169 HANDLE hin, hout;
170 DWORD savemode, newmode, i;
171
172 if (is_pw && password) {
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 }
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);
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);
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
208 if (is_pw)
209 WriteFile(hout, "\r\n", 2, &i, NULL);
210
211 return 1;
212 }
213
214 static DWORD WINAPI stdin_read_thread(void *param) {
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),
221 &idata->len, NULL) && idata->len > 0) {
222 SetEvent(idata->event);
223 WaitForSingleObject(idata->eventback, INFINITE);
224 }
225
226 idata->len = 0;
227 SetEvent(idata->event);
228
229 return 0;
230 }
231
232 /*
233 * Short description of parameters.
234 */
235 static 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");
245 printf(" -m file read remote command(s) from file\n");
246 exit(1);
247 }
248
249 char *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
265 int main(int argc, char **argv) {
266 WSADATA wsadata;
267 WORD winsock_ver;
268 WSAEVENT stdinevent;
269 HANDLE handles[2];
270 DWORD threadid;
271 struct input_data idata;
272 int sending;
273 int portnumber = -1;
274 SOCKET *sklist;
275 int skcount, sksize;
276 int connopen;
277
278 ssh_get_line = get_line;
279
280 sklist = NULL; skcount = sksize = 0;
281
282 flags = FLAG_STDERR;
283 /*
284 * Process the command line.
285 */
286 do_defaults(NULL, &cfg);
287 default_protocol = cfg.protocol;
288 default_port = cfg.port;
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 }
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;
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;
316 } else if (!strcmp(p, "-v")) {
317 flags |= FLAG_VERBOSE;
318 } else if (!strcmp(p, "-log")) {
319 logfile = "putty.log";
320 } else if (!strcmp(p, "-pw") && argc > 1) {
321 --argc, password = *++argv;
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';
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 */
356 } else if (!strcmp(p, "-P") && argc > 1) {
357 --argc, portnumber = atoi(*++argv);
358 }
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 {
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
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 */
417 r = strrchr(p, '@');
418 if (r == p) p++, r = NULL; /* discount initial @ */
419 if (r == NULL) {
420 /*
421 * One string.
422 */
423 Config cfg2;
424 do_defaults (p, &cfg2);
425 if (cfg2.host[0] == '\0') {
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';
429 cfg.port = default_port;
430 } else {
431 cfg = cfg2;
432 cfg.remote_cmd_ptr = cfg.remote_cmd;
433 }
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';
440 cfg.port = default_port;
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 */
457 break; /* done with cmdline */
458 }
459 }
460 }
461
462 if (!*cfg.host) {
463 usage();
464 }
465
466 if (!*cfg.remote_cmd_ptr)
467 flags |= FLAG_INTERACTIVE;
468
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 /*
488 * Select port.
489 */
490 if (portnumber != -1)
491 cfg.port = portnumber;
492
493 /*
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 }
508 sk_init();
509
510 /*
511 * Start up the connection.
512 */
513 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
514 {
515 char *error;
516 char *realhost;
517
518 error = back->init (cfg.host, cfg.port, &realhost);
519 if (error) {
520 fprintf(stderr, "Unable to open connection:\n%s", error);
521 return 1;
522 }
523 }
524 connopen = 1;
525
526 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
527
528 inhandle = GetStdHandle(STD_INPUT_HANDLE);
529 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
530 errhandle = GetStdHandle(STD_ERROR_HANDLE);
531 GetConsoleMode(inhandle, &orig_console_mode);
532 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
533
534 /*
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 */
539 handles[0] = netevent;
540 handles[1] = stdinevent;
541 sending = FALSE;
542 while (1) {
543 int n;
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;
563 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
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
572 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
573 if (n == 0) {
574 WSANETWORKEVENTS things;
575 SOCKET socket;
576 extern SOCKET first_socket(int *), next_socket(int *);
577 extern int select_result(WPARAM, LPARAM);
578 int i, socketstate;
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;
588 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
589 socket = next_socket(&socketstate))
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;
600 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
601 socket = next_socket(&socketstate)) {
602 sklist[skcount++] = socket;
603 }
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;
610 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
611 noise_ultralight(socket);
612 noise_ultralight(things.lNetworkEvents);
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 }
623 } else if (n == 1) {
624 noise_ultralight(idata.len);
625 if (idata.len > 0) {
626 back->send(idata.buffer, idata.len);
627 } else {
628 back->special(TS_EOF);
629 }
630 SetEvent(idata.eventback);
631 }
632 if (!connopen || back->socket() == NULL)
633 break; /* we closed the connection */
634 }
635 WSACleanup();
636 return 0;
637 }