Add some more commented-out diagnostics for ssh1
[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>
10#include <stdarg.h>
11
12#define PUTTY_DO_GLOBALS /* actually _define_ globals */
13#include "putty.h"
a9422f39 14#include "storage.h"
12dc4ec0 15
16void fatalbox (char *p, ...) {
17 va_list ap;
18 fprintf(stderr, "FATAL ERROR: ", p);
19 va_start(ap, p);
20 vfprintf(stderr, p, ap);
21 va_end(ap);
22 fputc('\n', stderr);
23 WSACleanup();
24 exit(1);
25}
8d5de777 26void connection_fatal (char *p, ...) {
27 va_list ap;
28 fprintf(stderr, "FATAL ERROR: ", p);
29 va_start(ap, p);
30 vfprintf(stderr, p, ap);
31 va_end(ap);
32 fputc('\n', stderr);
33 WSACleanup();
34 exit(1);
35}
12dc4ec0 36
d8426c54 37static char *password = NULL;
38
a9422f39 39void logevent(char *string) { }
40
41void verify_ssh_host_key(char *host, int port, char *keytype,
42 char *keystr, char *fingerprint) {
43 int ret;
fcbb94d3 44 HANDLE hin;
45 DWORD savemode, i;
a9422f39 46
47 static const char absentmsg[] =
48 "The server's host key is not cached in the registry. You\n"
49 "have no guarantee that the server is the computer you\n"
50 "think it is.\n"
51 "The server's key fingerprint is:\n"
52 "%s\n"
53 "If you trust this host, enter \"y\" to add the key to\n"
54 "PuTTY's cache and carry on connecting.\n"
55 "If you do not trust this host, enter \"n\" to abandon the\n"
56 "connection.\n"
57 "Continue connecting? (y/n) ";
58
59 static const char wrongmsg[] =
60 "WARNING - POTENTIAL SECURITY BREACH!\n"
61 "The server's host key does not match the one PuTTY has\n"
62 "cached in the registry. This means that either the\n"
63 "server administrator has changed the host key, or you\n"
64 "have actually connected to another computer pretending\n"
65 "to be the server.\n"
66 "The new key fingerprint is:\n"
67 "%s\n"
68 "If you were expecting this change and trust the new key,\n"
69 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
70 "If you want to carry on connecting but without updating\n"
71 "the cache, enter \"n\".\n"
72 "If you want to abandon the connection completely, press\n"
73 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
74 "safe choice.\n"
75 "Update cached key? (y/n, Return cancels connection) ";
76
77 static const char abandoned[] = "Connection abandoned.\n";
78
79 char line[32];
80
81 /*
82 * Verify the key against the registry.
83 */
84 ret = verify_host_key(host, port, keytype, keystr);
85
86 if (ret == 0) /* success - key matched OK */
87 return;
fcbb94d3 88
89 if (ret == 2) /* key was different */
a9422f39 90 fprintf(stderr, wrongmsg, fingerprint);
fcbb94d3 91 if (ret == 1) /* key was absent */
92 fprintf(stderr, absentmsg, fingerprint);
93
94 hin = GetStdHandle(STD_INPUT_HANDLE);
95 GetConsoleMode(hin, &savemode);
96 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
97 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
98 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
99 SetConsoleMode(hin, savemode);
100
101 if (ret == 2) { /* key was different */
102 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
a9422f39 103 if (line[0] == 'y' || line[0] == 'Y')
104 store_host_key(host, port, keytype, keystr);
105 } else {
106 fprintf(stderr, abandoned);
107 exit(0);
108 }
109 }
110 if (ret == 1) { /* key was absent */
fcbb94d3 111 if (line[0] == 'y' || line[0] == 'Y')
a9422f39 112 store_host_key(host, port, keytype, keystr);
113 else {
114 fprintf(stderr, abandoned);
115 exit(0);
116 }
117 }
118}
989b10e9 119
12dc4ec0 120HANDLE outhandle;
6f34e365 121DWORD orig_console_mode;
122
123void begin_session(void) {
124 if (!cfg.ldisc_term)
125 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
126 else
127 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
128}
12dc4ec0 129
130void term_out(void)
131{
132 int reap;
133 DWORD ret;
12dc4ec0 134 reap = 0;
135 while (reap < inbuf_head) {
136 if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
137 return; /* give up in panic */
138 reap += ret;
139 }
140 inbuf_head = 0;
141}
142
143struct input_data {
144 DWORD len;
145 char buffer[4096];
146 HANDLE event;
147};
148
67779be7 149static int get_password(const char *prompt, char *str, int maxlen)
150{
151 HANDLE hin, hout;
152 DWORD savemode, i;
153
67779be7 154 if (password) {
155 static int tried_once = 0;
156
157 if (tried_once) {
158 return 0;
159 } else {
160 strncpy(str, password, maxlen);
161 str[maxlen-1] = '\0';
162 tried_once = 1;
163 return 1;
164 }
165 }
67779be7 166
167 hin = GetStdHandle(STD_INPUT_HANDLE);
168 hout = GetStdHandle(STD_OUTPUT_HANDLE);
169 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
170 fprintf(stderr, "Cannot get standard input/output handles");
171 return 0;
172 }
173
174 GetConsoleMode(hin, &savemode);
175 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
176 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
177
178 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
179 ReadFile(hin, str, maxlen-1, &i, NULL);
180
181 SetConsoleMode(hin, savemode);
182
183 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
184 str[i] = '\0';
185
186 WriteFile(hout, "\r\n", 2, &i, NULL);
187
188 return 1;
189}
190
6d2d5e8d 191static DWORD WINAPI stdin_read_thread(void *param) {
12dc4ec0 192 struct input_data *idata = (struct input_data *)param;
193 HANDLE inhandle;
194
195 inhandle = GetStdHandle(STD_INPUT_HANDLE);
196
197 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
198 &idata->len, NULL)) {
199 SetEvent(idata->event);
200 }
201
202 idata->len = 0;
203 SetEvent(idata->event);
204
205 return 0;
206}
207
d8426c54 208/*
209 * Short description of parameters.
210 */
211static void usage(void)
212{
213 printf("PuTTY Link: command-line connection utility\n");
214 printf("%s\n", ver);
215 printf("Usage: plink [options] [user@]host [command]\n");
216 printf("Options:\n");
217 printf(" -v show verbose messages\n");
218 printf(" -ssh force use of ssh protocol\n");
219 printf(" -P port connect to specified port\n");
220 printf(" -pw passw login with specified password\n");
221 exit(1);
222}
223
12dc4ec0 224int main(int argc, char **argv) {
225 WSADATA wsadata;
226 WORD winsock_ver;
227 WSAEVENT netevent, stdinevent;
228 HANDLE handles[2];
229 SOCKET socket;
230 DWORD threadid;
231 struct input_data idata;
232 int sending;
d8426c54 233 int portnumber = -1;
12dc4ec0 234
67779be7 235 ssh_get_password = get_password;
236
237 flags = FLAG_STDERR;
12dc4ec0 238 /*
239 * Process the command line.
240 */
a9422f39 241 do_defaults(NULL, &cfg);
e7a7383f 242 default_protocol = cfg.protocol;
243 default_port = cfg.port;
8cb9c947 244 {
245 /*
246 * Override the default protocol if PLINK_PROTOCOL is set.
247 */
248 char *p = getenv("PLINK_PROTOCOL");
249 int i;
250 if (p) {
251 for (i = 0; backends[i].backend != NULL; i++) {
252 if (!strcmp(backends[i].name, p)) {
253 default_protocol = cfg.protocol = backends[i].protocol;
254 default_port = cfg.port = backends[i].backend->default_port;
255 break;
256 }
257 }
258 }
259 }
12dc4ec0 260 while (--argc) {
261 char *p = *++argv;
262 if (*p == '-') {
263 if (!strcmp(p, "-ssh")) {
264 default_protocol = cfg.protocol = PROT_SSH;
265 default_port = cfg.port = 22;
9d33ebdd 266 } else if (!strcmp(p, "-telnet")) {
267 default_protocol = cfg.protocol = PROT_TELNET;
268 default_port = cfg.port = 23;
269 } else if (!strcmp(p, "-raw")) {
270 default_protocol = cfg.protocol = PROT_RAW;
67779be7 271 } else if (!strcmp(p, "-v")) {
272 flags |= FLAG_VERBOSE;
12dc4ec0 273 } else if (!strcmp(p, "-log")) {
274 logfile = "putty.log";
d8426c54 275 } else if (!strcmp(p, "-pw") && argc > 1) {
276 --argc, password = *++argv;
e7a7383f 277 } else if (!strcmp(p, "-l") && argc > 1) {
278 char *username;
279 --argc, username = *++argv;
280 strncpy(cfg.username, username, sizeof(cfg.username));
281 cfg.username[sizeof(cfg.username)-1] = '\0';
d8426c54 282 } else if (!strcmp(p, "-P") && argc > 1) {
283 --argc, portnumber = atoi(*++argv);
284 }
12dc4ec0 285 } else if (*p) {
286 if (!*cfg.host) {
287 char *q = p;
288 /*
289 * If the hostname starts with "telnet:", set the
290 * protocol to Telnet and process the string as a
291 * Telnet URL.
292 */
293 if (!strncmp(q, "telnet:", 7)) {
294 char c;
295
296 q += 7;
297 if (q[0] == '/' && q[1] == '/')
298 q += 2;
299 cfg.protocol = PROT_TELNET;
300 p = q;
301 while (*p && *p != ':' && *p != '/') p++;
302 c = *p;
303 if (*p)
304 *p++ = '\0';
305 if (c == ':')
306 cfg.port = atoi(p);
307 else
308 cfg.port = -1;
309 strncpy (cfg.host, q, sizeof(cfg.host)-1);
310 cfg.host[sizeof(cfg.host)-1] = '\0';
311 } else {
9d33ebdd 312 char *r;
313 /*
314 * Before we process the [user@]host string, we
315 * first check for the presence of a protocol
316 * prefix (a protocol name followed by ",").
317 */
318 r = strchr(p, ',');
319 if (r) {
320 int i, j;
321 for (i = 0; backends[i].backend != NULL; i++) {
322 j = strlen(backends[i].name);
323 if (j == r-p &&
324 !memcmp(backends[i].name, p, j)) {
325 default_protocol = cfg.protocol = backends[i].protocol;
326 portnumber = backends[i].backend->default_port;
327 p = r+1;
328 break;
329 }
330 }
331 }
332
12dc4ec0 333 /*
334 * Three cases. Either (a) there's a nonzero
335 * length string followed by an @, in which
336 * case that's user and the remainder is host.
337 * Or (b) there's only one string, not counting
338 * a potential initial @, and it exists in the
339 * saved-sessions database. Or (c) only one
340 * string and it _doesn't_ exist in the
341 * database.
342 */
9d33ebdd 343 r = strrchr(p, '@');
12dc4ec0 344 if (r == p) p++, r = NULL; /* discount initial @ */
345 if (r == NULL) {
346 /*
347 * One string.
348 */
a9422f39 349 do_defaults (p, &cfg);
12dc4ec0 350 if (cfg.host[0] == '\0') {
351 /* No settings for this host; use defaults */
352 strncpy(cfg.host, p, sizeof(cfg.host)-1);
353 cfg.host[sizeof(cfg.host)-1] = '\0';
354 cfg.port = 22;
355 }
356 } else {
357 *r++ = '\0';
358 strncpy(cfg.username, p, sizeof(cfg.username)-1);
359 cfg.username[sizeof(cfg.username)-1] = '\0';
360 strncpy(cfg.host, r, sizeof(cfg.host)-1);
361 cfg.host[sizeof(cfg.host)-1] = '\0';
362 cfg.port = 22;
363 }
364 }
365 } else {
366 int len = sizeof(cfg.remote_cmd) - 1;
367 char *cp = cfg.remote_cmd;
368 int len2;
369
370 strncpy(cp, p, len); cp[len] = '\0';
371 len2 = strlen(cp); len -= len2; cp += len2;
372 while (--argc) {
373 if (len > 0)
374 len--, *cp++ = ' ';
375 strncpy(cp, *++argv, len); cp[len] = '\0';
376 len2 = strlen(cp); len -= len2; cp += len2;
377 }
378 cfg.nopty = TRUE; /* command => no terminal */
379 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
380 break; /* done with cmdline */
381 }
382 }
383 }
384
d8426c54 385 if (!*cfg.host) {
386 usage();
387 }
d8426c54 388
67779be7 389 if (!*cfg.remote_cmd)
390 flags |= FLAG_INTERACTIVE;
391
12dc4ec0 392 /*
393 * Select protocol. This is farmed out into a table in a
394 * separate file to enable an ssh-free variant.
395 */
396 {
397 int i;
398 back = NULL;
399 for (i = 0; backends[i].backend != NULL; i++)
400 if (backends[i].protocol == cfg.protocol) {
401 back = backends[i].backend;
402 break;
403 }
404 if (back == NULL) {
405 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
406 return 1;
407 }
408 }
409
410 /*
8cb9c947 411 * Select port.
412 */
413 if (portnumber != -1)
414 cfg.port = portnumber;
415
416 /*
12dc4ec0 417 * Initialise WinSock.
418 */
419 winsock_ver = MAKEWORD(2, 0);
420 if (WSAStartup(winsock_ver, &wsadata)) {
421 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
422 MB_OK | MB_ICONEXCLAMATION);
423 return 1;
424 }
425 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
426 MessageBox(NULL, "WinSock version is incompatible with 2.0",
427 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
428 WSACleanup();
429 return 1;
430 }
431
432 /*
433 * Start up the connection.
434 */
435 {
436 char *error;
437 char *realhost;
438
439 error = back->init (NULL, cfg.host, cfg.port, &realhost);
440 if (error) {
441 fprintf(stderr, "Unable to open connection:\n%s", error);
442 return 1;
443 }
444 }
445
446 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
447 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
448
6f34e365 449 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
450 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
12dc4ec0 451 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
452
453 /*
454 * Now we must send the back end oodles of stuff.
455 */
456 socket = back->socket();
457 /*
458 * Turn off ECHO and LINE input modes. We don't care if this
459 * call fails, because we know we aren't necessarily running in
460 * a console.
461 */
462 WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
463 handles[0] = netevent;
464 handles[1] = stdinevent;
465 sending = FALSE;
466 while (1) {
467 int n;
8cb9c947 468
469 if (!sending && back->sendok()) {
470 /*
471 * Create a separate thread to read from stdin. This is
472 * a total pain, but I can't find another way to do it:
473 *
474 * - an overlapped ReadFile or ReadFileEx just doesn't
475 * happen; we get failure from ReadFileEx, and
476 * ReadFile blocks despite being given an OVERLAPPED
477 * structure. Perhaps we can't do overlapped reads
478 * on consoles. WHY THE HELL NOT?
479 *
480 * - WaitForMultipleObjects(netevent, console) doesn't
481 * work, because it signals the console when
482 * _anything_ happens, including mouse motions and
483 * other things that don't cause data to be readable
484 * - so we're back to ReadFile blocking.
485 */
486 idata.event = stdinevent;
487 if (!CreateThread(NULL, 0, stdin_read_thread,
488 &idata, 0, &threadid)) {
489 fprintf(stderr, "Unable to create second thread\n");
490 exit(1);
491 }
492 sending = TRUE;
493 }
494
12dc4ec0 495 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
496 if (n == 0) {
497 WSANETWORKEVENTS things;
498 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
499 if (things.lNetworkEvents & FD_READ)
500 back->msg(0, FD_READ);
501 if (things.lNetworkEvents & FD_CLOSE) {
502 back->msg(0, FD_CLOSE);
503 break;
504 }
505 }
506 term_out();
12dc4ec0 507 } else if (n == 1) {
508 if (idata.len > 0) {
509 back->send(idata.buffer, idata.len);
510 } else {
511 back->special(TS_EOF);
512 }
513 }
cf6e59d6 514 if (back->socket() == INVALID_SOCKET)
515 break; /* we closed the connection */
12dc4ec0 516 }
517 WSACleanup();
518 return 0;
519}