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