Run entire source base through GNU indent to tidy up the varying
[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 do not trust this host, enter \"n\" to abandon the\n"
63 "connection.\n" "Continue connecting? (y/n) ";
64
65 static const char wrongmsg[] =
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) ";
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
92 if (ret == 0) /* success - key matched OK */
93 return;
94
95 if (ret == 2) { /* key was different */
96 fprintf(stderr, wrongmsg, fingerprint);
97 fflush(stderr);
98 }
99 if (ret == 1) { /* key was absent */
100 fprintf(stderr, absentmsg, fingerprint);
101 fflush(stderr);
102 }
103
104 hin = GetStdHandle(STD_INPUT_HANDLE);
105 GetConsoleMode(hin, &savemode);
106 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
107 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
108 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
109 SetConsoleMode(hin, savemode);
110
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 }
119 }
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 }
127 }
128 }
129
130 HANDLE inhandle, outhandle, errhandle;
131 DWORD orig_console_mode;
132
133 WSAEVENT netevent;
134
135 void from_backend(int is_stderr, char *data, int len)
136 {
137 int pos;
138 DWORD ret;
139 HANDLE h = (is_stderr ? errhandle : outhandle);
140
141 pos = 0;
142 while (pos < len) {
143 if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
144 return; /* give up in panic */
145 pos += ret;
146 }
147 }
148
149 int term_ldisc(int mode)
150 {
151 return FALSE;
152 }
153 void ldisc_update(int echo, int edit)
154 {
155 /* Update stdin read mode to reflect changes in line discipline. */
156 DWORD mode;
157
158 mode = ENABLE_PROCESSED_INPUT;
159 if (echo)
160 mode = mode | ENABLE_ECHO_INPUT;
161 else
162 mode = mode & ~ENABLE_ECHO_INPUT;
163 if (edit)
164 mode = mode | ENABLE_LINE_INPUT;
165 else
166 mode = mode & ~ENABLE_LINE_INPUT;
167 SetConsoleMode(inhandle, mode);
168 }
169
170 struct input_data {
171 DWORD len;
172 char buffer[4096];
173 HANDLE event, eventback;
174 };
175
176 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
177 {
178 HANDLE hin, hout;
179 DWORD savemode, newmode, i;
180
181 if (is_pw && password) {
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 }
192 }
193
194 hin = GetStdHandle(STD_INPUT_HANDLE);
195 hout = GetStdHandle(STD_OUTPUT_HANDLE);
196 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
197 fprintf(stderr, "Cannot get standard input/output handles");
198 return 0;
199 }
200
201 GetConsoleMode(hin, &savemode);
202 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
203 if (is_pw)
204 newmode &= ~ENABLE_ECHO_INPUT;
205 else
206 newmode |= ENABLE_ECHO_INPUT;
207 SetConsoleMode(hin, newmode);
208
209 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
210 ReadFile(hin, str, maxlen - 1, &i, NULL);
211
212 SetConsoleMode(hin, savemode);
213
214 if ((int) i > maxlen)
215 i = maxlen - 1;
216 else
217 i = i - 2;
218 str[i] = '\0';
219
220 if (is_pw)
221 WriteFile(hout, "\r\n", 2, &i, NULL);
222
223 return 1;
224 }
225
226 static DWORD WINAPI stdin_read_thread(void *param)
227 {
228 struct input_data *idata = (struct input_data *) param;
229 HANDLE inhandle;
230
231 inhandle = GetStdHandle(STD_INPUT_HANDLE);
232
233 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
234 &idata->len, NULL) && idata->len > 0) {
235 SetEvent(idata->event);
236 WaitForSingleObject(idata->eventback, INFINITE);
237 }
238
239 idata->len = 0;
240 SetEvent(idata->event);
241
242 return 0;
243 }
244
245 /*
246 * Short description of parameters.
247 */
248 static 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");
258 printf(" -m file read remote command(s) from file\n");
259 exit(1);
260 }
261
262 char *do_select(SOCKET skt, int startup)
263 {
264 int events;
265 if (startup) {
266 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
267 } else {
268 events = 0;
269 }
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 }
277 }
278 return NULL;
279 }
280
281 int main(int argc, char **argv)
282 {
283 WSADATA wsadata;
284 WORD winsock_ver;
285 WSAEVENT stdinevent;
286 HANDLE handles[2];
287 DWORD threadid;
288 struct input_data idata;
289 int sending;
290 int portnumber = -1;
291 SOCKET *sklist;
292 int skcount, sksize;
293 int connopen;
294
295 ssh_get_line = get_line;
296
297 sklist = NULL;
298 skcount = sksize = 0;
299
300 flags = FLAG_STDERR;
301 /*
302 * Process the command line.
303 */
304 do_defaults(NULL, &cfg);
305 default_protocol = cfg.protocol;
306 default_port = cfg.port;
307 {
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 }
323 }
324 while (--argc) {
325 char *p = *++argv;
326 if (*p == '-') {
327 if (!strcmp(p, "-ssh")) {
328 default_protocol = cfg.protocol = PROT_SSH;
329 default_port = cfg.port = 22;
330 } else if (!strcmp(p, "-telnet")) {
331 default_protocol = cfg.protocol = PROT_TELNET;
332 default_port = cfg.port = 23;
333 } else if (!strcmp(p, "-raw")) {
334 default_protocol = cfg.protocol = PROT_RAW;
335 } else if (!strcmp(p, "-v")) {
336 flags |= FLAG_VERBOSE;
337 } else if (!strcmp(p, "-log")) {
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 }
378 } else if (*p) {
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 }
488 }
489 }
490
491 if (!*cfg.host) {
492 usage();
493 }
494
495 if (!*cfg.remote_cmd_ptr)
496 flags |= FLAG_INTERACTIVE;
497
498 /*
499 * Select protocol. This is farmed out into a table in a
500 * separate file to enable an ssh-free variant.
501 */
502 {
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 }
515 }
516
517 /*
518 * Select port.
519 */
520 if (portnumber != -1)
521 cfg.port = portnumber;
522
523 /*
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 }
538 sk_init();
539
540 /*
541 * Start up the connection.
542 */
543 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
544 {
545 char *error;
546 char *realhost;
547
548 error = back->init(cfg.host, cfg.port, &realhost);
549 if (error) {
550 fprintf(stderr, "Unable to open connection:\n%s", error);
551 return 1;
552 }
553 }
554 connopen = 1;
555
556 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
557
558 inhandle = GetStdHandle(STD_INPUT_HANDLE);
559 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
560 errhandle = GetStdHandle(STD_ERROR_HANDLE);
561 GetConsoleMode(inhandle, &orig_console_mode);
562 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
563
564 /*
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 */
569 handles[0] = netevent;
570 handles[1] = stdinevent;
571 sending = FALSE;
572 while (1) {
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;
605 SOCKET socket;
606 extern SOCKET first_socket(int *), next_socket(int *);
607 extern int select_result(WPARAM, LPARAM);
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;
632 socket = next_socket(&socketstate)) {
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;
641 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
642 noise_ultralight(socket);
643 noise_ultralight(things.lNetworkEvents);
644 if (things.lNetworkEvents & FD_READ)
645 connopen &= select_result(wp, (LPARAM) FD_READ);
646 if (things.lNetworkEvents & FD_CLOSE)
647 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
648 if (things.lNetworkEvents & FD_OOB)
649 connopen &= select_result(wp, (LPARAM) FD_OOB);
650 if (things.lNetworkEvents & FD_WRITE)
651 connopen &= select_result(wp, (LPARAM) FD_WRITE);
652 }
653 }
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 */
665 }
666 WSACleanup();
667 return 0;
668 }