2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
12 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
17 void fatalbox (char *p, ...) {
19 fprintf(stderr, "FATAL ERROR: ", p);
21 vfprintf(stderr, p, ap);
27 void connection_fatal (char *p, ...) {
29 fprintf(stderr, "FATAL ERROR: ", p);
31 vfprintf(stderr, p, ap);
38 static char *password = NULL;
40 void logevent(char *string) { }
42 void verify_ssh_host_key(char *host, int port, char *keytype,
43 char *keystr, char *fingerprint) {
48 static const char absentmsg[] =
49 "The server's host key is not cached in the registry. You\n"
50 "have no guarantee that the server is the computer you\n"
52 "The server's key fingerprint is:\n"
54 "If you trust this host, enter \"y\" to add the key to\n"
55 "PuTTY's cache and carry on connecting.\n"
56 "If you do not trust this host, enter \"n\" to abandon the\n"
58 "Continue connecting? (y/n) ";
60 static const char wrongmsg[] =
61 "WARNING - POTENTIAL SECURITY BREACH!\n"
62 "The server's host key does not match the one PuTTY has\n"
63 "cached in the registry. This means that either the\n"
64 "server administrator has changed the host key, or you\n"
65 "have actually connected to another computer pretending\n"
67 "The new key fingerprint is:\n"
69 "If you were expecting this change and trust the new key,\n"
70 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
71 "If you want to carry on connecting but without updating\n"
72 "the cache, enter \"n\".\n"
73 "If you want to abandon the connection completely, press\n"
74 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
76 "Update cached key? (y/n, Return cancels connection) ";
78 static const char abandoned[] = "Connection abandoned.\n";
83 * Verify the key against the registry.
85 ret = verify_host_key(host, port, keytype, keystr);
87 if (ret == 0) /* success - key matched OK */
90 if (ret == 2) /* key was different */
91 fprintf(stderr, wrongmsg, fingerprint);
92 if (ret == 1) /* key was absent */
93 fprintf(stderr, absentmsg, fingerprint);
95 hin = GetStdHandle(STD_INPUT_HANDLE);
96 GetConsoleMode(hin, &savemode);
97 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
98 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
99 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
100 SetConsoleMode(hin, savemode);
102 if (ret == 2) { /* key was different */
103 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
104 if (line[0] == 'y' || line[0] == 'Y')
105 store_host_key(host, port, keytype, keystr);
107 fprintf(stderr, abandoned);
111 if (ret == 1) { /* key was absent */
112 if (line[0] == 'y' || line[0] == 'Y')
113 store_host_key(host, port, keytype, keystr);
115 fprintf(stderr, abandoned);
121 HANDLE outhandle, errhandle;
122 DWORD orig_console_mode;
126 void begin_session(void) {
128 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
130 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
133 void from_backend(int is_stderr, char *data, int len) {
136 HANDLE h = (is_stderr ? errhandle : outhandle);
140 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
141 return; /* give up in panic */
149 HANDLE event, eventback;
152 static int get_password(const char *prompt, char *str, int maxlen)
158 static int tried_once = 0;
163 strncpy(str, password, maxlen);
164 str[maxlen-1] = '\0';
170 hin = GetStdHandle(STD_INPUT_HANDLE);
171 hout = GetStdHandle(STD_OUTPUT_HANDLE);
172 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
173 fprintf(stderr, "Cannot get standard input/output handles");
177 GetConsoleMode(hin, &savemode);
178 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
179 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
181 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
182 ReadFile(hin, str, maxlen-1, &i, NULL);
184 SetConsoleMode(hin, savemode);
186 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
189 WriteFile(hout, "\r\n", 2, &i, NULL);
194 static DWORD WINAPI stdin_read_thread(void *param) {
195 struct input_data *idata = (struct input_data *)param;
198 inhandle = GetStdHandle(STD_INPUT_HANDLE);
200 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
201 &idata->len, NULL) && idata->len > 0) {
202 SetEvent(idata->event);
203 WaitForSingleObject(idata->eventback, INFINITE);
207 SetEvent(idata->event);
213 * Short description of parameters.
215 static void usage(void)
217 printf("PuTTY Link: command-line connection utility\n");
219 printf("Usage: plink [options] [user@]host [command]\n");
220 printf("Options:\n");
221 printf(" -v show verbose messages\n");
222 printf(" -ssh force use of ssh protocol\n");
223 printf(" -P port connect to specified port\n");
224 printf(" -pw passw login with specified password\n");
228 char *do_select(SOCKET skt, int startup) {
231 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
235 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
236 switch (WSAGetLastError()) {
237 case WSAENETDOWN: return "Network is down";
238 default: return "WSAAsyncSelect(): unknown error";
244 int main(int argc, char **argv) {
250 struct input_data idata;
257 ssh_get_password = get_password;
259 sklist = NULL; skcount = sksize = 0;
263 * Process the command line.
265 do_defaults(NULL, &cfg);
266 default_protocol = cfg.protocol;
267 default_port = cfg.port;
270 * Override the default protocol if PLINK_PROTOCOL is set.
272 char *p = getenv("PLINK_PROTOCOL");
275 for (i = 0; backends[i].backend != NULL; i++) {
276 if (!strcmp(backends[i].name, p)) {
277 default_protocol = cfg.protocol = backends[i].protocol;
278 default_port = cfg.port = backends[i].backend->default_port;
287 if (!strcmp(p, "-ssh")) {
288 default_protocol = cfg.protocol = PROT_SSH;
289 default_port = cfg.port = 22;
290 } else if (!strcmp(p, "-telnet")) {
291 default_protocol = cfg.protocol = PROT_TELNET;
292 default_port = cfg.port = 23;
293 } else if (!strcmp(p, "-raw")) {
294 default_protocol = cfg.protocol = PROT_RAW;
295 } else if (!strcmp(p, "-v")) {
296 flags |= FLAG_VERBOSE;
297 } else if (!strcmp(p, "-log")) {
298 logfile = "putty.log";
299 } else if (!strcmp(p, "-pw") && argc > 1) {
300 --argc, password = *++argv;
301 } else if (!strcmp(p, "-l") && argc > 1) {
303 --argc, username = *++argv;
304 strncpy(cfg.username, username, sizeof(cfg.username));
305 cfg.username[sizeof(cfg.username)-1] = '\0';
306 } else if (!strcmp(p, "-P") && argc > 1) {
307 --argc, portnumber = atoi(*++argv);
313 * If the hostname starts with "telnet:", set the
314 * protocol to Telnet and process the string as a
317 if (!strncmp(q, "telnet:", 7)) {
321 if (q[0] == '/' && q[1] == '/')
323 cfg.protocol = PROT_TELNET;
325 while (*p && *p != ':' && *p != '/') p++;
333 strncpy (cfg.host, q, sizeof(cfg.host)-1);
334 cfg.host[sizeof(cfg.host)-1] = '\0';
338 * Before we process the [user@]host string, we
339 * first check for the presence of a protocol
340 * prefix (a protocol name followed by ",").
345 for (i = 0; backends[i].backend != NULL; i++) {
346 j = strlen(backends[i].name);
348 !memcmp(backends[i].name, p, j)) {
349 default_protocol = cfg.protocol = backends[i].protocol;
350 portnumber = backends[i].backend->default_port;
358 * Three cases. Either (a) there's a nonzero
359 * length string followed by an @, in which
360 * case that's user and the remainder is host.
361 * Or (b) there's only one string, not counting
362 * a potential initial @, and it exists in the
363 * saved-sessions database. Or (c) only one
364 * string and it _doesn't_ exist in the
368 if (r == p) p++, r = NULL; /* discount initial @ */
373 do_defaults (p, &cfg);
374 if (cfg.host[0] == '\0') {
375 /* No settings for this host; use defaults */
376 strncpy(cfg.host, p, sizeof(cfg.host)-1);
377 cfg.host[sizeof(cfg.host)-1] = '\0';
382 strncpy(cfg.username, p, sizeof(cfg.username)-1);
383 cfg.username[sizeof(cfg.username)-1] = '\0';
384 strncpy(cfg.host, r, sizeof(cfg.host)-1);
385 cfg.host[sizeof(cfg.host)-1] = '\0';
390 int len = sizeof(cfg.remote_cmd) - 1;
391 char *cp = cfg.remote_cmd;
394 strncpy(cp, p, len); cp[len] = '\0';
395 len2 = strlen(cp); len -= len2; cp += len2;
399 strncpy(cp, *++argv, len); cp[len] = '\0';
400 len2 = strlen(cp); len -= len2; cp += len2;
402 cfg.nopty = TRUE; /* command => no terminal */
403 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
404 break; /* done with cmdline */
413 if (!*cfg.remote_cmd)
414 flags |= FLAG_INTERACTIVE;
417 * Select protocol. This is farmed out into a table in a
418 * separate file to enable an ssh-free variant.
423 for (i = 0; backends[i].backend != NULL; i++)
424 if (backends[i].protocol == cfg.protocol) {
425 back = backends[i].backend;
429 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
437 if (portnumber != -1)
438 cfg.port = portnumber;
441 * Initialise WinSock.
443 winsock_ver = MAKEWORD(2, 0);
444 if (WSAStartup(winsock_ver, &wsadata)) {
445 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
446 MB_OK | MB_ICONEXCLAMATION);
449 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
450 MessageBox(NULL, "WinSock version is incompatible with 2.0",
451 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
458 * Start up the connection.
460 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
465 error = back->init (cfg.host, cfg.port, &realhost);
467 fprintf(stderr, "Unable to open connection:\n%s", error);
473 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
475 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
476 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
477 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
478 errhandle = GetStdHandle(STD_ERROR_HANDLE);
481 * Turn off ECHO and LINE input modes. We don't care if this
482 * call fails, because we know we aren't necessarily running in
485 handles[0] = netevent;
486 handles[1] = stdinevent;
491 if (!sending && back->sendok()) {
493 * Create a separate thread to read from stdin. This is
494 * a total pain, but I can't find another way to do it:
496 * - an overlapped ReadFile or ReadFileEx just doesn't
497 * happen; we get failure from ReadFileEx, and
498 * ReadFile blocks despite being given an OVERLAPPED
499 * structure. Perhaps we can't do overlapped reads
500 * on consoles. WHY THE HELL NOT?
502 * - WaitForMultipleObjects(netevent, console) doesn't
503 * work, because it signals the console when
504 * _anything_ happens, including mouse motions and
505 * other things that don't cause data to be readable
506 * - so we're back to ReadFile blocking.
508 idata.event = stdinevent;
509 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
510 if (!CreateThread(NULL, 0, stdin_read_thread,
511 &idata, 0, &threadid)) {
512 fprintf(stderr, "Unable to create second thread\n");
518 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
520 WSANETWORKEVENTS things;
523 extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
524 extern int select_result(WPARAM, LPARAM);
528 * We must not call select_result() for any socket
529 * until we have finished enumerating within the tree.
530 * This is because select_result() may close the socket
531 * and modify the tree.
533 /* Count the active sockets. */
535 for (socket = first_socket(&e); socket != INVALID_SOCKET;
536 socket = next_socket(&e))
539 /* Expand the buffer if necessary. */
542 sklist = srealloc(sklist, sksize * sizeof(*sklist));
545 /* Retrieve the sockets into sklist. */
547 for (socket = first_socket(&e); socket != INVALID_SOCKET;
548 socket = next_socket(&e)) {
549 sklist[skcount++] = socket;
552 /* Now we're done enumerating; go through the list. */
553 for (i = 0; i < skcount; i++) {
557 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
558 if (things.lNetworkEvents & FD_READ)
559 connopen &= select_result(wp, (LPARAM)FD_READ);
560 if (things.lNetworkEvents & FD_CLOSE)
561 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
562 if (things.lNetworkEvents & FD_OOB)
563 connopen &= select_result(wp, (LPARAM)FD_OOB);
564 if (things.lNetworkEvents & FD_WRITE)
565 connopen &= select_result(wp, (LPARAM)FD_WRITE);
570 back->send(idata.buffer, idata.len);
572 back->special(TS_EOF);
574 SetEvent(idata.eventback);
576 if (!connopen || back->socket() == NULL)
577 break; /* we closed the connection */