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