10a4ec10fca04e3dd1d4e13568148f3d454db7b6
[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 "storage.h"
15
16 void 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 }
26 void 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 }
36
37 static char *password = NULL;
38
39 void logevent(char *string) { }
40
41 void verify_ssh_host_key(char *host, int port, char *keytype,
42 char *keystr, char *fingerprint) {
43 int ret;
44 HANDLE hin;
45 DWORD savemode, i;
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;
88
89 if (ret == 2) /* key was different */
90 fprintf(stderr, wrongmsg, fingerprint);
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') {
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 */
111 if (line[0] == 'y' || line[0] == 'Y')
112 store_host_key(host, port, keytype, keystr);
113 else {
114 fprintf(stderr, abandoned);
115 exit(0);
116 }
117 }
118 }
119
120 HANDLE outhandle;
121 DWORD orig_console_mode;
122
123 void 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 }
129
130 void term_out(void)
131 {
132 int reap;
133 DWORD ret;
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
143 struct input_data {
144 DWORD len;
145 char buffer[4096];
146 HANDLE event;
147 };
148
149 static int get_password(const char *prompt, char *str, int maxlen)
150 {
151 HANDLE hin, hout;
152 DWORD savemode, i;
153
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 }
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
191 static DWORD WINAPI stdin_read_thread(void *param) {
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
208 /*
209 * Short description of parameters.
210 */
211 static 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
224 int 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;
233 int portnumber = -1;
234
235 ssh_get_password = get_password;
236
237 flags = FLAG_STDERR;
238 /*
239 * Process the command line.
240 */
241 do_defaults(NULL, &cfg);
242 default_protocol = cfg.protocol;
243 default_port = cfg.port;
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 }
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;
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;
271 } else if (!strcmp(p, "-v")) {
272 flags |= FLAG_VERBOSE;
273 } else if (!strcmp(p, "-log")) {
274 logfile = "putty.log";
275 } else if (!strcmp(p, "-pw") && argc > 1) {
276 --argc, password = *++argv;
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';
282 } else if (!strcmp(p, "-P") && argc > 1) {
283 --argc, portnumber = atoi(*++argv);
284 }
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 {
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
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 */
343 r = strrchr(p, '@');
344 if (r == p) p++, r = NULL; /* discount initial @ */
345 if (r == NULL) {
346 /*
347 * One string.
348 */
349 do_defaults (p, &cfg);
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
385 if (!*cfg.host) {
386 usage();
387 }
388
389 if (!*cfg.remote_cmd)
390 flags |= FLAG_INTERACTIVE;
391
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 /*
411 * Select port.
412 */
413 if (portnumber != -1)
414 cfg.port = portnumber;
415
416 /*
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
449 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
450 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
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;
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
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();
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 }
514 if (back->socket() == INVALID_SOCKET)
515 break; /* we closed the connection */
516 }
517 WSACleanup();
518 return 0;
519 }