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