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