]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Create settings.c and move the load/save session code out of
[PuTTY.git] / 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
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 }
108
109 HANDLE outhandle;
110 DWORD orig_console_mode;
111
112 void 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 }
118
119 void term_out(void)
120 {
121     int reap;
122     DWORD ret;
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
132 struct input_data {
133     DWORD len;
134     char buffer[4096];
135     HANDLE event;
136 };
137
138 static int get_password(const char *prompt, char *str, int maxlen)
139 {
140     HANDLE hin, hout;
141     DWORD savemode, i;
142
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     }
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
180 static int WINAPI stdin_read_thread(void *param) {
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
197 /*
198  *  Short description of parameters.
199  */
200 static 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
213 int 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;
222     int portnumber = -1;
223
224     ssh_get_password = get_password;
225
226     flags = FLAG_STDERR;
227     /*
228      * Process the command line.
229      */
230     do_defaults(NULL, &cfg);
231     default_protocol = cfg.protocol;
232     default_port = cfg.port;
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     }
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;
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;
260             } else if (!strcmp(p, "-v")) {
261                 flags |= FLAG_VERBOSE;
262             } else if (!strcmp(p, "-log")) {
263                 logfile = "putty.log";
264             } else if (!strcmp(p, "-pw") && argc > 1) {
265                 --argc, password = *++argv;
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';
271             } else if (!strcmp(p, "-P") && argc > 1) {
272                 --argc, portnumber = atoi(*++argv);
273             }
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 {
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
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                      */
332                     r = strrchr(p, '@');
333                     if (r == p) p++, r = NULL;   /* discount initial @ */
334                     if (r == NULL) {
335                         /*
336                          * One string.
337                          */
338                         do_defaults (p, &cfg);
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
374     if (!*cfg.host) {
375         usage();
376     }
377
378     if (!*cfg.remote_cmd)
379         flags |= FLAG_INTERACTIVE;
380
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     /*
400      * Select port.
401      */
402     if (portnumber != -1)
403         cfg.port = portnumber;
404
405     /*
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
438     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
439     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
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;
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
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();
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 }