]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Prevent network errors from summarily closing the window when CoE is off
[PuTTY.git] / plink.c
1 /*
2  * PLink - a command-line (stdin/stdout) variant of PuTTY.
3  */
4
5 #include <winsock2.h>
6 #include <windows.h>
7 #include <stdio.h>
8 #include <stdarg.h>
9
10 #define PUTTY_DO_GLOBALS                       /* actually _define_ globals */
11 #include "putty.h"
12
13 void fatalbox (char *p, ...) {
14     va_list ap;
15     fprintf(stderr, "FATAL ERROR: ", p);
16     va_start(ap, p);
17     vfprintf(stderr, p, ap);
18     va_end(ap);
19     fputc('\n', stderr);
20     WSACleanup();
21     exit(1);
22 }
23 void connection_fatal (char *p, ...) {
24     va_list ap;
25     fprintf(stderr, "FATAL ERROR: ", p);
26     va_start(ap, p);
27     vfprintf(stderr, p, ap);
28     va_end(ap);
29     fputc('\n', stderr);
30     WSACleanup();
31     exit(1);
32 }
33
34 HANDLE outhandle;
35
36 void term_out(void)
37 {
38     int reap;
39     DWORD ret;
40
41     reap = 0;
42     while (reap < inbuf_head) {
43         if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
44             return;                    /* give up in panic */
45         reap += ret;
46     }
47     inbuf_head = 0;
48 }
49
50 struct input_data {
51     DWORD len;
52     char buffer[4096];
53     HANDLE event;
54 };
55
56 static int get_password(const char *prompt, char *str, int maxlen)
57 {
58     HANDLE hin, hout;
59     DWORD savemode, i;
60
61 #if 0 /* this allows specifying a password some other way */
62     if (password) {
63         static int tried_once = 0;
64
65         if (tried_once) {
66             return 0;
67         } else {
68             strncpy(str, password, maxlen);
69             str[maxlen-1] = '\0';
70             tried_once = 1;
71             return 1;
72         }
73     }
74 #endif
75
76     hin = GetStdHandle(STD_INPUT_HANDLE);
77     hout = GetStdHandle(STD_OUTPUT_HANDLE);
78     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
79         fprintf(stderr, "Cannot get standard input/output handles");
80         return 0;
81     }
82
83     GetConsoleMode(hin, &savemode);
84     SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
85                    ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
86
87     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
88     ReadFile(hin, str, maxlen-1, &i, NULL);
89
90     SetConsoleMode(hin, savemode);
91
92     if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
93     str[i] = '\0';
94
95     WriteFile(hout, "\r\n", 2, &i, NULL);
96
97     return 1;
98 }
99
100 int WINAPI stdin_read_thread(void *param) {
101     struct input_data *idata = (struct input_data *)param;
102     HANDLE inhandle;
103
104     inhandle = GetStdHandle(STD_INPUT_HANDLE);
105
106     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
107                     &idata->len, NULL)) {
108         SetEvent(idata->event);
109     }
110
111     idata->len = 0;
112     SetEvent(idata->event);
113
114     return 0;
115 }
116
117 int main(int argc, char **argv) {
118     WSADATA wsadata;
119     WORD winsock_ver;
120     WSAEVENT netevent, stdinevent;
121     HANDLE handles[2];
122     SOCKET socket;
123     DWORD threadid;
124     struct input_data idata;
125     int sending;
126
127     ssh_get_password = get_password;
128
129     flags = FLAG_STDERR;
130     /*
131      * Process the command line.
132      */
133     default_protocol = DEFAULT_PROTOCOL;
134     default_port = DEFAULT_PORT;
135     do_defaults(NULL);
136     while (--argc) {
137         char *p = *++argv;
138         if (*p == '-') {
139             if (!strcmp(p, "-ssh")) {
140                 default_protocol = cfg.protocol = PROT_SSH;
141                 default_port = cfg.port = 22;
142             } else if (!strcmp(p, "-v")) {
143                 flags |= FLAG_VERBOSE;
144             } else if (!strcmp(p, "-log")) {
145                 logfile = "putty.log";
146             }
147         } else if (*p) {
148             if (!*cfg.host) {
149                 char *q = p;
150                 /*
151                  * If the hostname starts with "telnet:", set the
152                  * protocol to Telnet and process the string as a
153                  * Telnet URL.
154                  */
155                 if (!strncmp(q, "telnet:", 7)) {
156                     char c;
157
158                     q += 7;
159                     if (q[0] == '/' && q[1] == '/')
160                         q += 2;
161                     cfg.protocol = PROT_TELNET;
162                     p = q;
163                     while (*p && *p != ':' && *p != '/') p++;
164                     c = *p;
165                     if (*p)
166                         *p++ = '\0';
167                     if (c == ':')
168                         cfg.port = atoi(p);
169                     else
170                         cfg.port = -1;
171                     strncpy (cfg.host, q, sizeof(cfg.host)-1);
172                     cfg.host[sizeof(cfg.host)-1] = '\0';
173                 } else {
174                     /*
175                      * Three cases. Either (a) there's a nonzero
176                      * length string followed by an @, in which
177                      * case that's user and the remainder is host.
178                      * Or (b) there's only one string, not counting
179                      * a potential initial @, and it exists in the
180                      * saved-sessions database. Or (c) only one
181                      * string and it _doesn't_ exist in the
182                      * database.
183                      */
184                     char *r = strrchr(p, '@');
185                     if (r == p) p++, r = NULL;   /* discount initial @ */
186                     if (r == NULL) {
187                         /*
188                          * One string.
189                          */
190                         do_defaults (p);
191                         if (cfg.host[0] == '\0') {
192                             /* No settings for this host; use defaults */
193                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
194                             cfg.host[sizeof(cfg.host)-1] = '\0';
195                             cfg.port = 22;
196                         }
197                     } else {
198                         *r++ = '\0';
199                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
200                         cfg.username[sizeof(cfg.username)-1] = '\0';
201                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
202                         cfg.host[sizeof(cfg.host)-1] = '\0';
203                         cfg.port = 22;
204                     }
205                 }
206             } else {
207                 int len = sizeof(cfg.remote_cmd) - 1;
208                 char *cp = cfg.remote_cmd;
209                 int len2;
210
211                 strncpy(cp, p, len); cp[len] = '\0';
212                 len2 = strlen(cp); len -= len2; cp += len2;
213                 while (--argc) {
214                     if (len > 0)
215                         len--, *cp++ = ' ';
216                     strncpy(cp, *++argv, len); cp[len] = '\0';
217                     len2 = strlen(cp); len -= len2; cp += len2;
218                 }
219                 cfg.nopty = TRUE;      /* command => no terminal */
220                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
221                 break;                 /* done with cmdline */
222             }
223         }
224     }
225
226     if (!*cfg.remote_cmd)
227         flags |= FLAG_INTERACTIVE;
228
229     /*
230      * Select protocol. This is farmed out into a table in a
231      * separate file to enable an ssh-free variant.
232      */
233     {
234         int i;
235         back = NULL;
236         for (i = 0; backends[i].backend != NULL; i++)
237             if (backends[i].protocol == cfg.protocol) {
238                 back = backends[i].backend;
239                 break;
240             }
241         if (back == NULL) {
242             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
243             return 1;
244         }
245     }
246
247     /*
248      * Initialise WinSock.
249      */
250     winsock_ver = MAKEWORD(2, 0);
251     if (WSAStartup(winsock_ver, &wsadata)) {
252         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
253                    MB_OK | MB_ICONEXCLAMATION);
254         return 1;
255     }
256     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
257         MessageBox(NULL, "WinSock version is incompatible with 2.0",
258                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
259         WSACleanup();
260         return 1;
261     }
262
263     /*
264      * Start up the connection.
265      */
266     {
267         char *error;
268         char *realhost;
269
270         error = back->init (NULL, cfg.host, cfg.port, &realhost);
271         if (error) {
272             fprintf(stderr, "Unable to open connection:\n%s", error);
273             return 1;
274         }
275     }
276
277     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
278     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
279
280     if (!cfg.ldisc_term)
281         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
282     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
283
284     /*
285      * Now we must send the back end oodles of stuff.
286      */
287     socket = back->socket();
288     /*
289      * Turn off ECHO and LINE input modes. We don't care if this
290      * call fails, because we know we aren't necessarily running in
291      * a console.
292      */
293     WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
294     handles[0] = netevent;
295     handles[1] = stdinevent;
296     sending = FALSE;
297     while (1) {
298         int n;
299         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
300         if (n == 0) {
301             WSANETWORKEVENTS things;
302             if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
303                 if (things.lNetworkEvents & FD_READ)
304                     back->msg(0, FD_READ);
305                 if (things.lNetworkEvents & FD_CLOSE) {
306                     back->msg(0, FD_CLOSE);
307                     break;
308                 }
309             }
310             term_out();
311             if (!sending && back->sendok()) {
312                 /*
313                  * Create a separate thread to read from stdin.
314                  * This is a total pain, but I can't find another
315                  * way to do it:
316                  *
317                  *  - an overlapped ReadFile or ReadFileEx just
318                  *    doesn't happen; we get failure from
319                  *    ReadFileEx, and ReadFile blocks despite being
320                  *    given an OVERLAPPED structure. Perhaps we
321                  *    can't do overlapped reads on consoles. WHY
322                  *    THE HELL NOT?
323                  * 
324                  *  - WaitForMultipleObjects(netevent, console)
325                  *    doesn't work, because it signals the console
326                  *    when _anything_ happens, including mouse
327                  *    motions and other things that don't cause
328                  *    data to be readable - so we're back to
329                  *    ReadFile blocking.
330                  */
331                 idata.event = stdinevent;
332                 if (!CreateThread(NULL, 0, stdin_read_thread,
333                                   &idata, 0, &threadid)) {
334                     fprintf(stderr, "Unable to create second thread\n");
335                     exit(1);
336                 }
337                 sending = TRUE;
338             }
339         } else if (n == 1) {
340             if (idata.len > 0) {
341                 back->send(idata.buffer, idata.len);
342             } else {
343                 back->special(TS_EOF);
344             }
345         }
346     }
347     WSACleanup();
348     return 0;
349 }