]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Actually _commit_ plink. It now works as a CVS transport!
[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
24 HANDLE outhandle;
25
26 void term_out(void)
27 {
28     int reap;
29     DWORD ret;
30
31     reap = 0;
32     while (reap < inbuf_head) {
33         if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
34             return;                    /* give up in panic */
35         reap += ret;
36     }
37     inbuf_head = 0;
38 }
39
40 struct input_data {
41     DWORD len;
42     char buffer[4096];
43     HANDLE event;
44 };
45
46 int WINAPI stdin_read_thread(void *param) {
47     struct input_data *idata = (struct input_data *)param;
48     HANDLE inhandle;
49
50     inhandle = GetStdHandle(STD_INPUT_HANDLE);
51
52     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
53                     &idata->len, NULL)) {
54         SetEvent(idata->event);
55     }
56
57     idata->len = 0;
58     SetEvent(idata->event);
59
60     return 0;
61 }
62
63 int main(int argc, char **argv) {
64     WSADATA wsadata;
65     WORD winsock_ver;
66     WSAEVENT netevent, stdinevent;
67     HANDLE handles[2];
68     SOCKET socket;
69     DWORD threadid;
70     struct input_data idata;
71     int sending;
72
73     flags = FLAG_CONNECTION;
74     /*
75      * Process the command line.
76      */
77     default_protocol = DEFAULT_PROTOCOL;
78     default_port = DEFAULT_PORT;
79     do_defaults(NULL);
80     while (--argc) {
81         char *p = *++argv;
82         if (*p == '-') {
83             if (!strcmp(p, "-ssh")) {
84                 default_protocol = cfg.protocol = PROT_SSH;
85                 default_port = cfg.port = 22;
86             } else if (!strcmp(p, "-log")) {
87                 logfile = "putty.log";
88             }
89         } else if (*p) {
90             if (!*cfg.host) {
91                 char *q = p;
92                 /*
93                  * If the hostname starts with "telnet:", set the
94                  * protocol to Telnet and process the string as a
95                  * Telnet URL.
96                  */
97                 if (!strncmp(q, "telnet:", 7)) {
98                     char c;
99
100                     q += 7;
101                     if (q[0] == '/' && q[1] == '/')
102                         q += 2;
103                     cfg.protocol = PROT_TELNET;
104                     p = q;
105                     while (*p && *p != ':' && *p != '/') p++;
106                     c = *p;
107                     if (*p)
108                         *p++ = '\0';
109                     if (c == ':')
110                         cfg.port = atoi(p);
111                     else
112                         cfg.port = -1;
113                     strncpy (cfg.host, q, sizeof(cfg.host)-1);
114                     cfg.host[sizeof(cfg.host)-1] = '\0';
115                 } else {
116                     /*
117                      * Three cases. Either (a) there's a nonzero
118                      * length string followed by an @, in which
119                      * case that's user and the remainder is host.
120                      * Or (b) there's only one string, not counting
121                      * a potential initial @, and it exists in the
122                      * saved-sessions database. Or (c) only one
123                      * string and it _doesn't_ exist in the
124                      * database.
125                      */
126                     char *r = strrchr(p, '@');
127                     if (r == p) p++, r = NULL;   /* discount initial @ */
128                     if (r == NULL) {
129                         /*
130                          * One string.
131                          */
132                         do_defaults (p);
133                         if (cfg.host[0] == '\0') {
134                             /* No settings for this host; use defaults */
135                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
136                             cfg.host[sizeof(cfg.host)-1] = '\0';
137                             cfg.port = 22;
138                         }
139                     } else {
140                         *r++ = '\0';
141                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
142                         cfg.username[sizeof(cfg.username)-1] = '\0';
143                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
144                         cfg.host[sizeof(cfg.host)-1] = '\0';
145                         cfg.port = 22;
146                     }
147                 }
148             } else {
149                 int len = sizeof(cfg.remote_cmd) - 1;
150                 char *cp = cfg.remote_cmd;
151                 int len2;
152
153                 strncpy(cp, p, len); cp[len] = '\0';
154                 len2 = strlen(cp); len -= len2; cp += len2;
155                 while (--argc) {
156                     if (len > 0)
157                         len--, *cp++ = ' ';
158                     strncpy(cp, *++argv, len); cp[len] = '\0';
159                     len2 = strlen(cp); len -= len2; cp += len2;
160                 }
161                 cfg.nopty = TRUE;      /* command => no terminal */
162                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
163                 break;                 /* done with cmdline */
164             }
165         }
166     }
167
168     /*
169      * Select protocol. This is farmed out into a table in a
170      * separate file to enable an ssh-free variant.
171      */
172     {
173         int i;
174         back = NULL;
175         for (i = 0; backends[i].backend != NULL; i++)
176             if (backends[i].protocol == cfg.protocol) {
177                 back = backends[i].backend;
178                 break;
179             }
180         if (back == NULL) {
181             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
182             return 1;
183         }
184     }
185
186     /*
187      * Initialise WinSock.
188      */
189     winsock_ver = MAKEWORD(2, 0);
190     if (WSAStartup(winsock_ver, &wsadata)) {
191         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
192                    MB_OK | MB_ICONEXCLAMATION);
193         return 1;
194     }
195     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
196         MessageBox(NULL, "WinSock version is incompatible with 2.0",
197                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
198         WSACleanup();
199         return 1;
200     }
201
202     /*
203      * Start up the connection.
204      */
205     {
206         char *error;
207         char *realhost;
208
209         error = back->init (NULL, cfg.host, cfg.port, &realhost);
210         if (error) {
211             fprintf(stderr, "Unable to open connection:\n%s", error);
212             return 1;
213         }
214     }
215
216     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
217     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
218
219     if (!cfg.ldisc_term)
220         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
221     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
222
223     /*
224      * Now we must send the back end oodles of stuff.
225      */
226     socket = back->socket();
227     /*
228      * Turn off ECHO and LINE input modes. We don't care if this
229      * call fails, because we know we aren't necessarily running in
230      * a console.
231      */
232     WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
233     handles[0] = netevent;
234     handles[1] = stdinevent;
235     sending = FALSE;
236     while (1) {
237         int n;
238         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
239         if (n == 0) {
240             WSANETWORKEVENTS things;
241             if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
242                 if (things.lNetworkEvents & FD_READ)
243                     back->msg(0, FD_READ);
244                 if (things.lNetworkEvents & FD_CLOSE) {
245                     back->msg(0, FD_CLOSE);
246                     break;
247                 }
248             }
249             term_out();
250             if (!sending && back->sendok()) {
251                 /*
252                  * Create a separate thread to read from stdin.
253                  * This is a total pain, but I can't find another
254                  * way to do it:
255                  *
256                  *  - an overlapped ReadFile or ReadFileEx just
257                  *    doesn't happen; we get failure from
258                  *    ReadFileEx, and ReadFile blocks despite being
259                  *    given an OVERLAPPED structure. Perhaps we
260                  *    can't do overlapped reads on consoles. WHY
261                  *    THE HELL NOT?
262                  * 
263                  *  - WaitForMultipleObjects(netevent, console)
264                  *    doesn't work, because it signals the console
265                  *    when _anything_ happens, including mouse
266                  *    motions and other things that don't cause
267                  *    data to be readable - so we're back to
268                  *    ReadFile blocking.
269                  */
270                 idata.event = stdinevent;
271                 if (!CreateThread(NULL, 0, stdin_read_thread,
272                                   &idata, 0, &threadid)) {
273                     fprintf(stderr, "Unable to create second thread\n");
274                     exit(1);
275                 }
276                 sending = TRUE;
277             }
278         } else if (n == 1) {
279             if (idata.len > 0) {
280                 back->send(idata.buffer, idata.len);
281             } else {
282                 back->special(TS_EOF);
283             }
284         }
285     }
286     WSACleanup();
287     return 0;
288 }