Rethink the whole line discipline architecture. Instead of having
[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 if (ret == 1) /* key was absent */
94 fprintf(stderr, absentmsg, fingerprint);
95
96 hin = GetStdHandle(STD_INPUT_HANDLE);
97 GetConsoleMode(hin, &savemode);
98 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
99 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
100 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
101 SetConsoleMode(hin, savemode);
102
103 if (ret == 2) { /* key was different */
104 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
105 if (line[0] == 'y' || line[0] == 'Y')
106 store_host_key(host, port, keytype, keystr);
107 } else {
108 fprintf(stderr, abandoned);
109 exit(0);
110 }
111 }
112 if (ret == 1) { /* key was absent */
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 }
121
122 HANDLE inhandle, outhandle, errhandle;
123 DWORD orig_console_mode;
124
125 WSAEVENT netevent;
126
127 void from_backend(int is_stderr, char *data, int len) {
128 int pos;
129 DWORD ret;
130 HANDLE h = (is_stderr ? errhandle : outhandle);
131
132 pos = 0;
133 while (pos < len) {
134 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
135 return; /* give up in panic */
136 pos += ret;
137 }
138 }
139
140 int term_ldisc(int mode) { return FALSE; }
141 void ldisc_update(int echo, int edit) {
142 /* Update stdin read mode to reflect changes in line discipline. */
143 DWORD mode;
144
145 mode = ENABLE_PROCESSED_INPUT;
146 if (echo)
147 mode = mode | ENABLE_ECHO_INPUT;
148 else
149 mode = mode &~ ENABLE_ECHO_INPUT;
150 if (edit)
151 mode = mode | ENABLE_LINE_INPUT;
152 else
153 mode = mode &~ ENABLE_LINE_INPUT;
154 SetConsoleMode(inhandle, mode);
155 }
156
157 struct input_data {
158 DWORD len;
159 char buffer[4096];
160 HANDLE event, eventback;
161 };
162
163 static int get_password(const char *prompt, char *str, int maxlen)
164 {
165 HANDLE hin, hout;
166 DWORD savemode, i;
167
168 if (password) {
169 static int tried_once = 0;
170
171 if (tried_once) {
172 return 0;
173 } else {
174 strncpy(str, password, maxlen);
175 str[maxlen-1] = '\0';
176 tried_once = 1;
177 return 1;
178 }
179 }
180
181 hin = GetStdHandle(STD_INPUT_HANDLE);
182 hout = GetStdHandle(STD_OUTPUT_HANDLE);
183 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
184 fprintf(stderr, "Cannot get standard input/output handles");
185 return 0;
186 }
187
188 GetConsoleMode(hin, &savemode);
189 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
190 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
191
192 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
193 ReadFile(hin, str, maxlen-1, &i, NULL);
194
195 SetConsoleMode(hin, savemode);
196
197 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
198 str[i] = '\0';
199
200 WriteFile(hout, "\r\n", 2, &i, NULL);
201
202 return 1;
203 }
204
205 static DWORD WINAPI stdin_read_thread(void *param) {
206 struct input_data *idata = (struct input_data *)param;
207 HANDLE inhandle;
208
209 inhandle = GetStdHandle(STD_INPUT_HANDLE);
210
211 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
212 &idata->len, NULL) && idata->len > 0) {
213 SetEvent(idata->event);
214 WaitForSingleObject(idata->eventback, INFINITE);
215 }
216
217 idata->len = 0;
218 SetEvent(idata->event);
219
220 return 0;
221 }
222
223 /*
224 * Short description of parameters.
225 */
226 static void usage(void)
227 {
228 printf("PuTTY Link: command-line connection utility\n");
229 printf("%s\n", ver);
230 printf("Usage: plink [options] [user@]host [command]\n");
231 printf("Options:\n");
232 printf(" -v show verbose messages\n");
233 printf(" -ssh force use of ssh protocol\n");
234 printf(" -P port connect to specified port\n");
235 printf(" -pw passw login with specified password\n");
236 exit(1);
237 }
238
239 char *do_select(SOCKET skt, int startup) {
240 int events;
241 if (startup) {
242 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
243 } else {
244 events = 0;
245 }
246 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
247 switch (WSAGetLastError()) {
248 case WSAENETDOWN: return "Network is down";
249 default: return "WSAAsyncSelect(): unknown error";
250 }
251 }
252 return NULL;
253 }
254
255 int main(int argc, char **argv) {
256 WSADATA wsadata;
257 WORD winsock_ver;
258 WSAEVENT stdinevent;
259 HANDLE handles[2];
260 DWORD threadid;
261 struct input_data idata;
262 int sending;
263 int portnumber = -1;
264 SOCKET *sklist;
265 int skcount, sksize;
266 int connopen;
267
268 ssh_get_password = get_password;
269
270 sklist = NULL; skcount = sksize = 0;
271
272 flags = FLAG_STDERR;
273 /*
274 * Process the command line.
275 */
276 do_defaults(NULL, &cfg);
277 default_protocol = cfg.protocol;
278 default_port = cfg.port;
279 {
280 /*
281 * Override the default protocol if PLINK_PROTOCOL is set.
282 */
283 char *p = getenv("PLINK_PROTOCOL");
284 int i;
285 if (p) {
286 for (i = 0; backends[i].backend != NULL; i++) {
287 if (!strcmp(backends[i].name, p)) {
288 default_protocol = cfg.protocol = backends[i].protocol;
289 default_port = cfg.port = backends[i].backend->default_port;
290 break;
291 }
292 }
293 }
294 }
295 while (--argc) {
296 char *p = *++argv;
297 if (*p == '-') {
298 if (!strcmp(p, "-ssh")) {
299 default_protocol = cfg.protocol = PROT_SSH;
300 default_port = cfg.port = 22;
301 } else if (!strcmp(p, "-telnet")) {
302 default_protocol = cfg.protocol = PROT_TELNET;
303 default_port = cfg.port = 23;
304 } else if (!strcmp(p, "-raw")) {
305 default_protocol = cfg.protocol = PROT_RAW;
306 } else if (!strcmp(p, "-v")) {
307 flags |= FLAG_VERBOSE;
308 } else if (!strcmp(p, "-log")) {
309 logfile = "putty.log";
310 } else if (!strcmp(p, "-pw") && argc > 1) {
311 --argc, password = *++argv;
312 } else if (!strcmp(p, "-l") && argc > 1) {
313 char *username;
314 --argc, username = *++argv;
315 strncpy(cfg.username, username, sizeof(cfg.username));
316 cfg.username[sizeof(cfg.username)-1] = '\0';
317 } else if (!strcmp(p, "-P") && argc > 1) {
318 --argc, portnumber = atoi(*++argv);
319 }
320 } else if (*p) {
321 if (!*cfg.host) {
322 char *q = p;
323 /*
324 * If the hostname starts with "telnet:", set the
325 * protocol to Telnet and process the string as a
326 * Telnet URL.
327 */
328 if (!strncmp(q, "telnet:", 7)) {
329 char c;
330
331 q += 7;
332 if (q[0] == '/' && q[1] == '/')
333 q += 2;
334 cfg.protocol = PROT_TELNET;
335 p = q;
336 while (*p && *p != ':' && *p != '/') p++;
337 c = *p;
338 if (*p)
339 *p++ = '\0';
340 if (c == ':')
341 cfg.port = atoi(p);
342 else
343 cfg.port = -1;
344 strncpy (cfg.host, q, sizeof(cfg.host)-1);
345 cfg.host[sizeof(cfg.host)-1] = '\0';
346 } else {
347 char *r;
348 /*
349 * Before we process the [user@]host string, we
350 * first check for the presence of a protocol
351 * prefix (a protocol name followed by ",").
352 */
353 r = strchr(p, ',');
354 if (r) {
355 int i, j;
356 for (i = 0; backends[i].backend != NULL; i++) {
357 j = strlen(backends[i].name);
358 if (j == r-p &&
359 !memcmp(backends[i].name, p, j)) {
360 default_protocol = cfg.protocol = backends[i].protocol;
361 portnumber = backends[i].backend->default_port;
362 p = r+1;
363 break;
364 }
365 }
366 }
367
368 /*
369 * Three cases. Either (a) there's a nonzero
370 * length string followed by an @, in which
371 * case that's user and the remainder is host.
372 * Or (b) there's only one string, not counting
373 * a potential initial @, and it exists in the
374 * saved-sessions database. Or (c) only one
375 * string and it _doesn't_ exist in the
376 * database.
377 */
378 r = strrchr(p, '@');
379 if (r == p) p++, r = NULL; /* discount initial @ */
380 if (r == NULL) {
381 /*
382 * One string.
383 */
384 Config cfg2;
385 do_defaults (p, &cfg2);
386 if (cfg2.host[0] == '\0') {
387 /* No settings for this host; use defaults */
388 strncpy(cfg.host, p, sizeof(cfg.host)-1);
389 cfg.host[sizeof(cfg.host)-1] = '\0';
390 cfg.port = 22;
391 } else
392 cfg = cfg2;
393 } else {
394 *r++ = '\0';
395 strncpy(cfg.username, p, sizeof(cfg.username)-1);
396 cfg.username[sizeof(cfg.username)-1] = '\0';
397 strncpy(cfg.host, r, sizeof(cfg.host)-1);
398 cfg.host[sizeof(cfg.host)-1] = '\0';
399 cfg.port = 22;
400 }
401 }
402 } else {
403 int len = sizeof(cfg.remote_cmd) - 1;
404 char *cp = cfg.remote_cmd;
405 int len2;
406
407 strncpy(cp, p, len); cp[len] = '\0';
408 len2 = strlen(cp); len -= len2; cp += len2;
409 while (--argc) {
410 if (len > 0)
411 len--, *cp++ = ' ';
412 strncpy(cp, *++argv, len); cp[len] = '\0';
413 len2 = strlen(cp); len -= len2; cp += len2;
414 }
415 cfg.nopty = TRUE; /* command => no terminal */
416 break; /* done with cmdline */
417 }
418 }
419 }
420
421 if (!*cfg.host) {
422 usage();
423 }
424
425 if (!*cfg.remote_cmd)
426 flags |= FLAG_INTERACTIVE;
427
428 /*
429 * Select protocol. This is farmed out into a table in a
430 * separate file to enable an ssh-free variant.
431 */
432 {
433 int i;
434 back = NULL;
435 for (i = 0; backends[i].backend != NULL; i++)
436 if (backends[i].protocol == cfg.protocol) {
437 back = backends[i].backend;
438 break;
439 }
440 if (back == NULL) {
441 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
442 return 1;
443 }
444 }
445
446 /*
447 * Select port.
448 */
449 if (portnumber != -1)
450 cfg.port = portnumber;
451
452 /*
453 * Initialise WinSock.
454 */
455 winsock_ver = MAKEWORD(2, 0);
456 if (WSAStartup(winsock_ver, &wsadata)) {
457 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
458 MB_OK | MB_ICONEXCLAMATION);
459 return 1;
460 }
461 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
462 MessageBox(NULL, "WinSock version is incompatible with 2.0",
463 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
464 WSACleanup();
465 return 1;
466 }
467 sk_init();
468
469 /*
470 * Start up the connection.
471 */
472 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
473 {
474 char *error;
475 char *realhost;
476
477 error = back->init (cfg.host, cfg.port, &realhost);
478 if (error) {
479 fprintf(stderr, "Unable to open connection:\n%s", error);
480 return 1;
481 }
482 }
483 connopen = 1;
484
485 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
486
487 inhandle = GetStdHandle(STD_INPUT_HANDLE);
488 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
489 errhandle = GetStdHandle(STD_ERROR_HANDLE);
490 GetConsoleMode(inhandle, &orig_console_mode);
491 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
492
493 /*
494 * Turn off ECHO and LINE input modes. We don't care if this
495 * call fails, because we know we aren't necessarily running in
496 * a console.
497 */
498 handles[0] = netevent;
499 handles[1] = stdinevent;
500 sending = FALSE;
501 while (1) {
502 int n;
503
504 if (!sending && back->sendok()) {
505 /*
506 * Create a separate thread to read from stdin. This is
507 * a total pain, but I can't find another way to do it:
508 *
509 * - an overlapped ReadFile or ReadFileEx just doesn't
510 * happen; we get failure from ReadFileEx, and
511 * ReadFile blocks despite being given an OVERLAPPED
512 * structure. Perhaps we can't do overlapped reads
513 * on consoles. WHY THE HELL NOT?
514 *
515 * - WaitForMultipleObjects(netevent, console) doesn't
516 * work, because it signals the console when
517 * _anything_ happens, including mouse motions and
518 * other things that don't cause data to be readable
519 * - so we're back to ReadFile blocking.
520 */
521 idata.event = stdinevent;
522 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
523 if (!CreateThread(NULL, 0, stdin_read_thread,
524 &idata, 0, &threadid)) {
525 fprintf(stderr, "Unable to create second thread\n");
526 exit(1);
527 }
528 sending = TRUE;
529 }
530
531 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
532 if (n == 0) {
533 WSANETWORKEVENTS things;
534 enum234 e;
535 SOCKET socket;
536 extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
537 extern int select_result(WPARAM, LPARAM);
538 int i;
539
540 /*
541 * We must not call select_result() for any socket
542 * until we have finished enumerating within the tree.
543 * This is because select_result() may close the socket
544 * and modify the tree.
545 */
546 /* Count the active sockets. */
547 i = 0;
548 for (socket = first_socket(&e); socket != INVALID_SOCKET;
549 socket = next_socket(&e))
550 i++;
551
552 /* Expand the buffer if necessary. */
553 if (i > sksize) {
554 sksize = i+16;
555 sklist = srealloc(sklist, sksize * sizeof(*sklist));
556 }
557
558 /* Retrieve the sockets into sklist. */
559 skcount = 0;
560 for (socket = first_socket(&e); socket != INVALID_SOCKET;
561 socket = next_socket(&e)) {
562 sklist[skcount++] = socket;
563 }
564
565 /* Now we're done enumerating; go through the list. */
566 for (i = 0; i < skcount; i++) {
567 WPARAM wp;
568 socket = sklist[i];
569 wp = (WPARAM)socket;
570 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
571 noise_ultralight(socket);
572 noise_ultralight(things.lNetworkEvents);
573 if (things.lNetworkEvents & FD_READ)
574 connopen &= select_result(wp, (LPARAM)FD_READ);
575 if (things.lNetworkEvents & FD_CLOSE)
576 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
577 if (things.lNetworkEvents & FD_OOB)
578 connopen &= select_result(wp, (LPARAM)FD_OOB);
579 if (things.lNetworkEvents & FD_WRITE)
580 connopen &= select_result(wp, (LPARAM)FD_WRITE);
581 }
582 }
583 } else if (n == 1) {
584 noise_ultralight(idata.len);
585 if (idata.len > 0) {
586 back->send(idata.buffer, idata.len);
587 } else {
588 back->special(TS_EOF);
589 }
590 SetEvent(idata.eventback);
591 }
592 if (!connopen || back->socket() == NULL)
593 break; /* we closed the connection */
594 }
595 WSACleanup();
596 return 0;
597 }