Introduce a sane interface function, from_backend(), for backends to
[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 <stdarg.h>
11
12 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
13 #include "putty.h"
14 #include "winstuff.h"
15 #include "storage.h"
16
17 void 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 }
27 void 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 }
37
38 static char *password = NULL;
39
40 void logevent(char *string) { }
41
42 void verify_ssh_host_key(char *host, int port, char *keytype,
43 char *keystr, char *fingerprint) {
44 int ret;
45 HANDLE hin;
46 DWORD savemode, i;
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;
89
90 if (ret == 2) /* key was different */
91 fprintf(stderr, wrongmsg, fingerprint);
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') {
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 */
112 if (line[0] == 'y' || line[0] == 'Y')
113 store_host_key(host, port, keytype, keystr);
114 else {
115 fprintf(stderr, abandoned);
116 exit(0);
117 }
118 }
119 }
120
121 HANDLE outhandle, errhandle;
122 DWORD orig_console_mode;
123
124 void 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 }
130
131 void from_backend(int is_stderr, char *data, int len) {
132 int pos;
133 DWORD ret;
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))
139 return; /* give up in panic */
140 pos += ret;
141 }
142 }
143
144 struct input_data {
145 DWORD len;
146 char buffer[4096];
147 HANDLE event;
148 };
149
150 static int get_password(const char *prompt, char *str, int maxlen)
151 {
152 HANDLE hin, hout;
153 DWORD savemode, i;
154
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 }
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
192 static DWORD WINAPI stdin_read_thread(void *param) {
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
209 /*
210 * Short description of parameters.
211 */
212 static 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
225 int 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;
234 int portnumber = -1;
235
236 ssh_get_password = get_password;
237
238 flags = FLAG_STDERR;
239 /*
240 * Process the command line.
241 */
242 do_defaults(NULL, &cfg);
243 default_protocol = cfg.protocol;
244 default_port = cfg.port;
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 }
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;
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;
272 } else if (!strcmp(p, "-v")) {
273 flags |= FLAG_VERBOSE;
274 } else if (!strcmp(p, "-log")) {
275 logfile = "putty.log";
276 } else if (!strcmp(p, "-pw") && argc > 1) {
277 --argc, password = *++argv;
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';
283 } else if (!strcmp(p, "-P") && argc > 1) {
284 --argc, portnumber = atoi(*++argv);
285 }
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 {
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
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 */
344 r = strrchr(p, '@');
345 if (r == p) p++, r = NULL; /* discount initial @ */
346 if (r == NULL) {
347 /*
348 * One string.
349 */
350 do_defaults (p, &cfg);
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
386 if (!*cfg.host) {
387 usage();
388 }
389
390 if (!*cfg.remote_cmd)
391 flags |= FLAG_INTERACTIVE;
392
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 /*
412 * Select port.
413 */
414 if (portnumber != -1)
415 cfg.port = portnumber;
416
417 /*
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
450 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
451 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
452 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
453 errhandle = GetStdHandle(STD_ERROR_HANDLE);
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;
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
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 }
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 }
515 if (back->socket() == INVALID_SOCKET)
516 break; /* we closed the connection */
517 }
518 WSACleanup();
519 return 0;
520 }