2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 #define MAX_STDIN_BACKLOG 4096
20 void fatalbox(char *p, ...)
23 fprintf(stderr, "FATAL ERROR: ");
25 vfprintf(stderr, p, ap);
31 void connection_fatal(char *p, ...)
34 fprintf(stderr, "FATAL ERROR: ");
36 vfprintf(stderr, p, ap);
43 static char *password = NULL;
45 void logevent(char *string)
49 void verify_ssh_host_key(char *host, int port, char *keytype,
50 char *keystr, char *fingerprint)
56 static const char absentmsg[] =
57 "The server's host key is not cached in the registry. You\n"
58 "have no guarantee that the server is the computer you\n"
60 "The server's key fingerprint is:\n"
62 "If you trust this host, enter \"y\" to add the key to\n"
63 "PuTTY's cache and carry on connecting.\n"
64 "If you want to carry on connecting just once, without\n"
65 "adding the key to the cache, enter \"n\".\n"
66 "If you do not trust this host, press Return to abandon the\n"
68 "Store key in cache? (y/n) ";
70 static const char wrongmsg[] =
71 "WARNING - POTENTIAL SECURITY BREACH!\n"
72 "The server's host key does not match the one PuTTY has\n"
73 "cached in the registry. This means that either the\n"
74 "server administrator has changed the host key, or you\n"
75 "have actually connected to another computer pretending\n"
77 "The new key fingerprint is:\n"
79 "If you were expecting this change and trust the new key,\n"
80 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
81 "If you want to carry on connecting but without updating\n"
82 "the cache, enter \"n\".\n"
83 "If you want to abandon the connection completely, press\n"
84 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
86 "Update cached key? (y/n, Return cancels connection) ";
88 static const char abandoned[] = "Connection abandoned.\n";
93 * Verify the key against the registry.
95 ret = verify_host_key(host, port, keytype, keystr);
97 if (ret == 0) /* success - key matched OK */
100 if (ret == 2) { /* key was different */
101 fprintf(stderr, wrongmsg, fingerprint);
104 if (ret == 1) { /* key was absent */
105 fprintf(stderr, absentmsg, fingerprint);
109 hin = GetStdHandle(STD_INPUT_HANDLE);
110 GetConsoleMode(hin, &savemode);
111 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
112 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
113 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
114 SetConsoleMode(hin, savemode);
116 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
120 fprintf(stderr, abandoned);
126 * Ask whether the selected cipher is acceptable (since it was
127 * below the configured 'warn' threshold).
128 * cs: 0 = both ways, 1 = client->server, 2 = server->client
130 void askcipher(char *ciphername, int cs)
135 static const char msg[] =
136 "The first %scipher supported by the server is\n"
137 "%s, which is below the configured warning threshold.\n"
138 "Continue with connection? (y/n) ";
139 static const char abandoned[] = "Connection abandoned.\n";
145 (cs == 1) ? "client-to-server " :
150 hin = GetStdHandle(STD_INPUT_HANDLE);
151 GetConsoleMode(hin, &savemode);
152 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
153 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
154 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
155 SetConsoleMode(hin, savemode);
157 if (line[0] == 'y' || line[0] == 'Y') {
160 fprintf(stderr, abandoned);
165 HANDLE inhandle, outhandle, errhandle;
166 DWORD orig_console_mode;
170 int term_ldisc(int mode)
174 void ldisc_update(int echo, int edit)
176 /* Update stdin read mode to reflect changes in line discipline. */
179 mode = ENABLE_PROCESSED_INPUT;
181 mode = mode | ENABLE_ECHO_INPUT;
183 mode = mode & ~ENABLE_ECHO_INPUT;
185 mode = mode | ENABLE_LINE_INPUT;
187 mode = mode & ~ENABLE_LINE_INPUT;
188 SetConsoleMode(inhandle, mode);
191 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
194 DWORD savemode, newmode, i;
196 if (is_pw && password) {
197 static int tried_once = 0;
202 strncpy(str, password, maxlen);
203 str[maxlen - 1] = '\0';
209 hin = GetStdHandle(STD_INPUT_HANDLE);
210 hout = GetStdHandle(STD_OUTPUT_HANDLE);
211 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
212 fprintf(stderr, "Cannot get standard input/output handles");
216 GetConsoleMode(hin, &savemode);
217 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
219 newmode &= ~ENABLE_ECHO_INPUT;
221 newmode |= ENABLE_ECHO_INPUT;
222 SetConsoleMode(hin, newmode);
224 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
225 ReadFile(hin, str, maxlen - 1, &i, NULL);
227 SetConsoleMode(hin, savemode);
229 if ((int) i > maxlen)
236 WriteFile(hout, "\r\n", 2, &i, NULL);
244 HANDLE event, eventback;
247 static DWORD WINAPI stdin_read_thread(void *param)
249 struct input_data *idata = (struct input_data *) param;
252 inhandle = GetStdHandle(STD_INPUT_HANDLE);
254 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
255 &idata->len, NULL) && idata->len > 0) {
256 SetEvent(idata->event);
257 WaitForSingleObject(idata->eventback, INFINITE);
261 SetEvent(idata->event);
267 DWORD len, lenwritten;
271 HANDLE event, eventback;
275 static DWORD WINAPI stdout_write_thread(void *param)
277 struct output_data *odata = (struct output_data *) param;
278 HANDLE outhandle, errhandle;
280 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
281 errhandle = GetStdHandle(STD_ERROR_HANDLE);
284 WaitForSingleObject(odata->eventback, INFINITE);
288 WriteFile(odata->is_stderr ? errhandle : outhandle,
289 odata->buffer, odata->len, &odata->lenwritten, NULL);
290 SetEvent(odata->event);
296 bufchain stdout_data, stderr_data;
297 struct output_data odata, edata;
299 void try_output(int is_stderr)
301 struct output_data *data = (is_stderr ? &edata : &odata);
306 bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
307 &senddata, &sendlen);
308 data->buffer = senddata;
310 SetEvent(data->eventback);
315 int from_backend(int is_stderr, char *data, int len)
317 HANDLE h = (is_stderr ? errhandle : outhandle);
321 bufchain_add(&stderr_data, data, len);
324 bufchain_add(&stdout_data, data, len);
328 osize = bufchain_size(&stdout_data);
329 esize = bufchain_size(&stderr_data);
331 return osize + esize;
335 * Short description of parameters.
337 static void usage(void)
339 printf("PuTTY Link: command-line connection utility\n");
341 printf("Usage: plink [options] [user@]host [command]\n");
342 printf(" (\"host\" can also be a PuTTY saved session name)\n");
343 printf("Options:\n");
344 printf(" -v show verbose messages\n");
345 printf(" -ssh force use of ssh protocol\n");
346 printf(" -P port connect to specified port\n");
347 printf(" -pw passw login with specified password\n");
348 printf(" -m file read remote command(s) from file\n");
352 char *do_select(SOCKET skt, int startup)
356 events = (FD_CONNECT | FD_READ | FD_WRITE |
357 FD_OOB | FD_CLOSE | FD_ACCEPT);
361 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
362 switch (WSAGetLastError()) {
364 return "Network is down";
366 return "WSAAsyncSelect(): unknown error";
372 int main(int argc, char **argv)
376 WSAEVENT stdinevent, stdoutevent, stderrevent;
378 DWORD in_threadid, out_threadid, err_threadid;
379 struct input_data idata;
387 ssh_get_line = get_line;
390 skcount = sksize = 0;
392 * Initialise port and protocol to sensible defaults. (These
393 * will be overridden by more or less anything.)
395 default_protocol = PROT_SSH;
400 * Process the command line.
402 do_defaults(NULL, &cfg);
403 default_protocol = cfg.protocol;
404 default_port = cfg.port;
407 * Override the default protocol if PLINK_PROTOCOL is set.
409 char *p = getenv("PLINK_PROTOCOL");
412 for (i = 0; backends[i].backend != NULL; i++) {
413 if (!strcmp(backends[i].name, p)) {
414 default_protocol = cfg.protocol = backends[i].protocol;
415 default_port = cfg.port =
416 backends[i].backend->default_port;
425 if (!strcmp(p, "-ssh")) {
426 default_protocol = cfg.protocol = PROT_SSH;
427 default_port = cfg.port = 22;
428 } else if (!strcmp(p, "-telnet")) {
429 default_protocol = cfg.protocol = PROT_TELNET;
430 default_port = cfg.port = 23;
431 } else if (!strcmp(p, "-raw")) {
432 default_protocol = cfg.protocol = PROT_RAW;
433 } else if (!strcmp(p, "-v")) {
434 flags |= FLAG_VERBOSE;
435 } else if (!strcmp(p, "-log")) {
436 logfile = "putty.log";
437 } else if (!strcmp(p, "-pw") && argc > 1) {
438 --argc, password = *++argv;
439 } else if (!strcmp(p, "-l") && argc > 1) {
441 --argc, username = *++argv;
442 strncpy(cfg.username, username, sizeof(cfg.username));
443 cfg.username[sizeof(cfg.username) - 1] = '\0';
444 } else if (!strcmp(p, "-m") && argc > 1) {
445 char *filename, *command;
450 --argc, filename = *++argv;
452 cmdlen = cmdsize = 0;
454 fp = fopen(filename, "r");
456 fprintf(stderr, "plink: unable to open command "
457 "file \"%s\"\n", filename);
465 if (cmdlen >= cmdsize) {
466 cmdsize = cmdlen + 512;
467 command = srealloc(command, cmdsize);
469 command[cmdlen++] = d;
471 cfg.remote_cmd_ptr = command;
472 cfg.remote_cmd_ptr2 = NULL;
473 cfg.nopty = TRUE; /* command => no terminal */
474 } else if (!strcmp(p, "-P") && argc > 1) {
475 --argc, portnumber = atoi(*++argv);
481 * If the hostname starts with "telnet:", set the
482 * protocol to Telnet and process the string as a
485 if (!strncmp(q, "telnet:", 7)) {
489 if (q[0] == '/' && q[1] == '/')
491 cfg.protocol = PROT_TELNET;
493 while (*p && *p != ':' && *p != '/')
502 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
503 cfg.host[sizeof(cfg.host) - 1] = '\0';
507 * Before we process the [user@]host string, we
508 * first check for the presence of a protocol
509 * prefix (a protocol name followed by ",").
514 for (i = 0; backends[i].backend != NULL; i++) {
515 j = strlen(backends[i].name);
517 !memcmp(backends[i].name, p, j)) {
518 default_protocol = cfg.protocol =
519 backends[i].protocol;
521 backends[i].backend->default_port;
529 * Three cases. Either (a) there's a nonzero
530 * length string followed by an @, in which
531 * case that's user and the remainder is host.
532 * Or (b) there's only one string, not counting
533 * a potential initial @, and it exists in the
534 * saved-sessions database. Or (c) only one
535 * string and it _doesn't_ exist in the
540 p++, r = NULL; /* discount initial @ */
546 do_defaults(p, &cfg2);
547 if (cfg2.host[0] == '\0') {
548 /* No settings for this host; use defaults */
549 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
550 cfg.host[sizeof(cfg.host) - 1] = '\0';
551 cfg.port = default_port;
554 cfg.remote_cmd_ptr = cfg.remote_cmd;
558 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
559 cfg.username[sizeof(cfg.username) - 1] = '\0';
560 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
561 cfg.host[sizeof(cfg.host) - 1] = '\0';
562 cfg.port = default_port;
566 int len = sizeof(cfg.remote_cmd) - 1;
567 char *cp = cfg.remote_cmd;
578 strncpy(cp, *++argv, len);
584 cfg.nopty = TRUE; /* command => no terminal */
585 break; /* done with cmdline */
595 * Trim leading whitespace off the hostname if it's there.
598 int space = strspn(cfg.host, " \t");
599 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
602 /* See if host is of the form user@host */
603 if (cfg.host[0] != '\0') {
604 char *atsign = strchr(cfg.host, '@');
605 /* Make sure we're not overflowing the user field */
607 if (atsign - cfg.host < sizeof cfg.username) {
608 strncpy(cfg.username, cfg.host, atsign - cfg.host);
609 cfg.username[atsign - cfg.host] = '\0';
611 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
616 * Trim a colon suffix off the hostname if it's there.
618 cfg.host[strcspn(cfg.host, ":")] = '\0';
620 if (!*cfg.remote_cmd_ptr)
621 flags |= FLAG_INTERACTIVE;
624 * Select protocol. This is farmed out into a table in a
625 * separate file to enable an ssh-free variant.
630 for (i = 0; backends[i].backend != NULL; i++)
631 if (backends[i].protocol == cfg.protocol) {
632 back = backends[i].backend;
637 "Internal fault: Unsupported protocol found\n");
645 if (portnumber != -1)
646 cfg.port = portnumber;
649 * Initialise WinSock.
651 winsock_ver = MAKEWORD(2, 0);
652 if (WSAStartup(winsock_ver, &wsadata)) {
653 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
654 MB_OK | MB_ICONEXCLAMATION);
657 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
658 MessageBox(NULL, "WinSock version is incompatible with 2.0",
659 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
666 * Start up the connection.
668 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
673 error = back->init(cfg.host, cfg.port, &realhost);
675 fprintf(stderr, "Unable to open connection:\n%s", error);
682 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
683 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
684 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
686 inhandle = GetStdHandle(STD_INPUT_HANDLE);
687 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
688 errhandle = GetStdHandle(STD_ERROR_HANDLE);
689 GetConsoleMode(inhandle, &orig_console_mode);
690 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
693 * Turn off ECHO and LINE input modes. We don't care if this
694 * call fails, because we know we aren't necessarily running in
697 handles[0] = netevent;
698 handles[1] = stdinevent;
699 handles[2] = stdoutevent;
700 handles[3] = stderrevent;
704 * Create spare threads to write to stdout and stderr, so we
705 * can arrange asynchronous writes.
707 odata.event = stdoutevent;
708 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
710 odata.busy = odata.done = 0;
711 if (!CreateThread(NULL, 0, stdout_write_thread,
712 &odata, 0, &out_threadid)) {
713 fprintf(stderr, "Unable to create output thread\n");
716 edata.event = stderrevent;
717 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
719 edata.busy = edata.done = 0;
720 if (!CreateThread(NULL, 0, stdout_write_thread,
721 &edata, 0, &err_threadid)) {
722 fprintf(stderr, "Unable to create error output thread\n");
729 if (!sending && back->sendok()) {
731 * Create a separate thread to read from stdin. This is
732 * a total pain, but I can't find another way to do it:
734 * - an overlapped ReadFile or ReadFileEx just doesn't
735 * happen; we get failure from ReadFileEx, and
736 * ReadFile blocks despite being given an OVERLAPPED
737 * structure. Perhaps we can't do overlapped reads
738 * on consoles. WHY THE HELL NOT?
740 * - WaitForMultipleObjects(netevent, console) doesn't
741 * work, because it signals the console when
742 * _anything_ happens, including mouse motions and
743 * other things that don't cause data to be readable
744 * - so we're back to ReadFile blocking.
746 idata.event = stdinevent;
747 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
748 if (!CreateThread(NULL, 0, stdin_read_thread,
749 &idata, 0, &in_threadid)) {
750 fprintf(stderr, "Unable to create input thread\n");
756 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
758 WSANETWORKEVENTS things;
760 extern SOCKET first_socket(int *), next_socket(int *);
761 extern int select_result(WPARAM, LPARAM);
765 * We must not call select_result() for any socket
766 * until we have finished enumerating within the tree.
767 * This is because select_result() may close the socket
768 * and modify the tree.
770 /* Count the active sockets. */
772 for (socket = first_socket(&socketstate);
773 socket != INVALID_SOCKET;
774 socket = next_socket(&socketstate)) i++;
776 /* Expand the buffer if necessary. */
779 sklist = srealloc(sklist, sksize * sizeof(*sklist));
782 /* Retrieve the sockets into sklist. */
784 for (socket = first_socket(&socketstate);
785 socket != INVALID_SOCKET;
786 socket = next_socket(&socketstate)) {
787 sklist[skcount++] = socket;
790 /* Now we're done enumerating; go through the list. */
791 for (i = 0; i < skcount; i++) {
794 wp = (WPARAM) socket;
795 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
796 noise_ultralight(socket);
797 noise_ultralight(things.lNetworkEvents);
798 if (things.lNetworkEvents & FD_CONNECT)
799 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
800 if (things.lNetworkEvents & FD_READ)
801 connopen &= select_result(wp, (LPARAM) FD_READ);
802 if (things.lNetworkEvents & FD_CLOSE)
803 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
804 if (things.lNetworkEvents & FD_OOB)
805 connopen &= select_result(wp, (LPARAM) FD_OOB);
806 if (things.lNetworkEvents & FD_WRITE)
807 connopen &= select_result(wp, (LPARAM) FD_WRITE);
808 if (things.lNetworkEvents & FD_ACCEPT)
809 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
815 noise_ultralight(idata.len);
816 if (connopen && back->socket() != NULL) {
818 back->send(idata.buffer, idata.len);
820 back->special(TS_EOF);
825 if (!odata.writeret) {
826 fprintf(stderr, "Unable to write to standard output\n");
829 bufchain_consume(&stdout_data, odata.lenwritten);
830 if (bufchain_size(&stdout_data) > 0)
832 if (connopen && back->socket() != NULL) {
833 back->unthrottle(bufchain_size(&stdout_data) +
834 bufchain_size(&stderr_data));
838 if (!edata.writeret) {
839 fprintf(stderr, "Unable to write to standard output\n");
842 bufchain_consume(&stderr_data, edata.lenwritten);
843 if (bufchain_size(&stderr_data) > 0)
845 if (connopen && back->socket() != NULL) {
846 back->unthrottle(bufchain_size(&stdout_data) +
847 bufchain_size(&stderr_data));
850 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
851 SetEvent(idata.eventback);
854 if ((!connopen || back->socket() == NULL) &&
855 bufchain_size(&stdout_data) == 0 &&
856 bufchain_size(&stderr_data) == 0)
857 break; /* we closed the connection */