2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 void fatalbox(char *p, ...)
21 fprintf(stderr, "FATAL ERROR: ");
23 vfprintf(stderr, p, ap);
29 void connection_fatal(char *p, ...)
32 fprintf(stderr, "FATAL ERROR: ");
34 vfprintf(stderr, p, ap);
41 static char *password = NULL;
43 void logevent(char *string)
47 void verify_ssh_host_key(char *host, int port, char *keytype,
48 char *keystr, char *fingerprint)
54 static const char absentmsg[] =
55 "The server's host key is not cached in the registry. You\n"
56 "have no guarantee that the server is the computer you\n"
58 "The server's key fingerprint is:\n"
60 "If you trust this host, enter \"y\" to add the key to\n"
61 "PuTTY's cache and carry on connecting.\n"
62 "If you do not trust this host, enter \"n\" to abandon the\n"
63 "connection.\n" "Continue connecting? (y/n) ";
65 static const char wrongmsg[] =
66 "WARNING - POTENTIAL SECURITY BREACH!\n"
67 "The server's host key does not match the one PuTTY has\n"
68 "cached in the registry. This means that either the\n"
69 "server administrator has changed the host key, or you\n"
70 "have actually connected to another computer pretending\n"
72 "The new key fingerprint is:\n"
74 "If you were expecting this change and trust the new key,\n"
75 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
76 "If you want to carry on connecting but without updating\n"
77 "the cache, enter \"n\".\n"
78 "If you want to abandon the connection completely, press\n"
79 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
81 "Update cached key? (y/n, Return cancels connection) ";
83 static const char abandoned[] = "Connection abandoned.\n";
88 * Verify the key against the registry.
90 ret = verify_host_key(host, port, keytype, keystr);
92 if (ret == 0) /* success - key matched OK */
95 if (ret == 2) { /* key was different */
96 fprintf(stderr, wrongmsg, fingerprint);
99 if (ret == 1) { /* key was absent */
100 fprintf(stderr, absentmsg, fingerprint);
104 hin = GetStdHandle(STD_INPUT_HANDLE);
105 GetConsoleMode(hin, &savemode);
106 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
107 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
108 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
109 SetConsoleMode(hin, savemode);
111 if (ret == 2) { /* key was different */
112 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
113 if (line[0] == 'y' || line[0] == 'Y')
114 store_host_key(host, port, keytype, keystr);
116 fprintf(stderr, abandoned);
120 if (ret == 1) { /* key was absent */
121 if (line[0] == 'y' || line[0] == 'Y')
122 store_host_key(host, port, keytype, keystr);
124 fprintf(stderr, abandoned);
130 HANDLE inhandle, outhandle, errhandle;
131 DWORD orig_console_mode;
135 void from_backend(int is_stderr, char *data, int len)
139 HANDLE h = (is_stderr ? errhandle : outhandle);
143 if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
144 return; /* give up in panic */
149 int term_ldisc(int mode)
153 void ldisc_update(int echo, int edit)
155 /* Update stdin read mode to reflect changes in line discipline. */
158 mode = ENABLE_PROCESSED_INPUT;
160 mode = mode | ENABLE_ECHO_INPUT;
162 mode = mode & ~ENABLE_ECHO_INPUT;
164 mode = mode | ENABLE_LINE_INPUT;
166 mode = mode & ~ENABLE_LINE_INPUT;
167 SetConsoleMode(inhandle, mode);
173 HANDLE event, eventback;
176 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
179 DWORD savemode, newmode, i;
181 if (is_pw && password) {
182 static int tried_once = 0;
187 strncpy(str, password, maxlen);
188 str[maxlen - 1] = '\0';
194 hin = GetStdHandle(STD_INPUT_HANDLE);
195 hout = GetStdHandle(STD_OUTPUT_HANDLE);
196 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
197 fprintf(stderr, "Cannot get standard input/output handles");
201 GetConsoleMode(hin, &savemode);
202 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
204 newmode &= ~ENABLE_ECHO_INPUT;
206 newmode |= ENABLE_ECHO_INPUT;
207 SetConsoleMode(hin, newmode);
209 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
210 ReadFile(hin, str, maxlen - 1, &i, NULL);
212 SetConsoleMode(hin, savemode);
214 if ((int) i > maxlen)
221 WriteFile(hout, "\r\n", 2, &i, NULL);
226 static DWORD WINAPI stdin_read_thread(void *param)
228 struct input_data *idata = (struct input_data *) param;
231 inhandle = GetStdHandle(STD_INPUT_HANDLE);
233 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
234 &idata->len, NULL) && idata->len > 0) {
235 SetEvent(idata->event);
236 WaitForSingleObject(idata->eventback, INFINITE);
240 SetEvent(idata->event);
246 * Short description of parameters.
248 static void usage(void)
250 printf("PuTTY Link: command-line connection utility\n");
252 printf("Usage: plink [options] [user@]host [command]\n");
253 printf("Options:\n");
254 printf(" -v show verbose messages\n");
255 printf(" -ssh force use of ssh protocol\n");
256 printf(" -P port connect to specified port\n");
257 printf(" -pw passw login with specified password\n");
258 printf(" -m file read remote command(s) from file\n");
262 char *do_select(SOCKET skt, int startup)
266 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
270 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
271 switch (WSAGetLastError()) {
273 return "Network is down";
275 return "WSAAsyncSelect(): unknown error";
281 int main(int argc, char **argv)
288 struct input_data idata;
295 ssh_get_line = get_line;
298 skcount = sksize = 0;
302 * Process the command line.
304 do_defaults(NULL, &cfg);
305 default_protocol = cfg.protocol;
306 default_port = cfg.port;
309 * Override the default protocol if PLINK_PROTOCOL is set.
311 char *p = getenv("PLINK_PROTOCOL");
314 for (i = 0; backends[i].backend != NULL; i++) {
315 if (!strcmp(backends[i].name, p)) {
316 default_protocol = cfg.protocol = backends[i].protocol;
317 default_port = cfg.port =
318 backends[i].backend->default_port;
327 if (!strcmp(p, "-ssh")) {
328 default_protocol = cfg.protocol = PROT_SSH;
329 default_port = cfg.port = 22;
330 } else if (!strcmp(p, "-telnet")) {
331 default_protocol = cfg.protocol = PROT_TELNET;
332 default_port = cfg.port = 23;
333 } else if (!strcmp(p, "-raw")) {
334 default_protocol = cfg.protocol = PROT_RAW;
335 } else if (!strcmp(p, "-v")) {
336 flags |= FLAG_VERBOSE;
337 } else if (!strcmp(p, "-log")) {
338 logfile = "putty.log";
339 } else if (!strcmp(p, "-pw") && argc > 1) {
340 --argc, password = *++argv;
341 } else if (!strcmp(p, "-l") && argc > 1) {
343 --argc, username = *++argv;
344 strncpy(cfg.username, username, sizeof(cfg.username));
345 cfg.username[sizeof(cfg.username) - 1] = '\0';
346 } else if (!strcmp(p, "-m") && argc > 1) {
347 char *filename, *command;
352 --argc, filename = *++argv;
354 cmdlen = cmdsize = 0;
356 fp = fopen(filename, "r");
358 fprintf(stderr, "plink: unable to open command "
359 "file \"%s\"\n", filename);
367 if (cmdlen >= cmdsize) {
368 cmdsize = cmdlen + 512;
369 command = srealloc(command, cmdsize);
371 command[cmdlen++] = d;
373 cfg.remote_cmd_ptr = command;
374 cfg.nopty = TRUE; /* command => no terminal */
375 } else if (!strcmp(p, "-P") && argc > 1) {
376 --argc, portnumber = atoi(*++argv);
382 * If the hostname starts with "telnet:", set the
383 * protocol to Telnet and process the string as a
386 if (!strncmp(q, "telnet:", 7)) {
390 if (q[0] == '/' && q[1] == '/')
392 cfg.protocol = PROT_TELNET;
394 while (*p && *p != ':' && *p != '/')
403 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
404 cfg.host[sizeof(cfg.host) - 1] = '\0';
408 * Before we process the [user@]host string, we
409 * first check for the presence of a protocol
410 * prefix (a protocol name followed by ",").
415 for (i = 0; backends[i].backend != NULL; i++) {
416 j = strlen(backends[i].name);
418 !memcmp(backends[i].name, p, j)) {
419 default_protocol = cfg.protocol =
420 backends[i].protocol;
422 backends[i].backend->default_port;
430 * Three cases. Either (a) there's a nonzero
431 * length string followed by an @, in which
432 * case that's user and the remainder is host.
433 * Or (b) there's only one string, not counting
434 * a potential initial @, and it exists in the
435 * saved-sessions database. Or (c) only one
436 * string and it _doesn't_ exist in the
441 p++, r = NULL; /* discount initial @ */
447 do_defaults(p, &cfg2);
448 if (cfg2.host[0] == '\0') {
449 /* No settings for this host; use defaults */
450 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
451 cfg.host[sizeof(cfg.host) - 1] = '\0';
452 cfg.port = default_port;
455 cfg.remote_cmd_ptr = cfg.remote_cmd;
459 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
460 cfg.username[sizeof(cfg.username) - 1] = '\0';
461 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
462 cfg.host[sizeof(cfg.host) - 1] = '\0';
463 cfg.port = default_port;
467 int len = sizeof(cfg.remote_cmd) - 1;
468 char *cp = cfg.remote_cmd;
479 strncpy(cp, *++argv, len);
485 cfg.nopty = TRUE; /* command => no terminal */
486 break; /* done with cmdline */
495 if (!*cfg.remote_cmd_ptr)
496 flags |= FLAG_INTERACTIVE;
499 * Select protocol. This is farmed out into a table in a
500 * separate file to enable an ssh-free variant.
505 for (i = 0; backends[i].backend != NULL; i++)
506 if (backends[i].protocol == cfg.protocol) {
507 back = backends[i].backend;
512 "Internal fault: Unsupported protocol found\n");
520 if (portnumber != -1)
521 cfg.port = portnumber;
524 * Initialise WinSock.
526 winsock_ver = MAKEWORD(2, 0);
527 if (WSAStartup(winsock_ver, &wsadata)) {
528 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
529 MB_OK | MB_ICONEXCLAMATION);
532 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
533 MessageBox(NULL, "WinSock version is incompatible with 2.0",
534 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
541 * Start up the connection.
543 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
548 error = back->init(cfg.host, cfg.port, &realhost);
550 fprintf(stderr, "Unable to open connection:\n%s", error);
557 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
559 inhandle = GetStdHandle(STD_INPUT_HANDLE);
560 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
561 errhandle = GetStdHandle(STD_ERROR_HANDLE);
562 GetConsoleMode(inhandle, &orig_console_mode);
563 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
566 * Turn off ECHO and LINE input modes. We don't care if this
567 * call fails, because we know we aren't necessarily running in
570 handles[0] = netevent;
571 handles[1] = stdinevent;
576 if (!sending && back->sendok()) {
578 * Create a separate thread to read from stdin. This is
579 * a total pain, but I can't find another way to do it:
581 * - an overlapped ReadFile or ReadFileEx just doesn't
582 * happen; we get failure from ReadFileEx, and
583 * ReadFile blocks despite being given an OVERLAPPED
584 * structure. Perhaps we can't do overlapped reads
585 * on consoles. WHY THE HELL NOT?
587 * - WaitForMultipleObjects(netevent, console) doesn't
588 * work, because it signals the console when
589 * _anything_ happens, including mouse motions and
590 * other things that don't cause data to be readable
591 * - so we're back to ReadFile blocking.
593 idata.event = stdinevent;
594 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
595 if (!CreateThread(NULL, 0, stdin_read_thread,
596 &idata, 0, &threadid)) {
597 fprintf(stderr, "Unable to create second thread\n");
603 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
605 WSANETWORKEVENTS things;
607 extern SOCKET first_socket(int *), next_socket(int *);
608 extern int select_result(WPARAM, LPARAM);
612 * We must not call select_result() for any socket
613 * until we have finished enumerating within the tree.
614 * This is because select_result() may close the socket
615 * and modify the tree.
617 /* Count the active sockets. */
619 for (socket = first_socket(&socketstate);
620 socket != INVALID_SOCKET;
621 socket = next_socket(&socketstate)) i++;
623 /* Expand the buffer if necessary. */
626 sklist = srealloc(sklist, sksize * sizeof(*sklist));
629 /* Retrieve the sockets into sklist. */
631 for (socket = first_socket(&socketstate);
632 socket != INVALID_SOCKET;
633 socket = next_socket(&socketstate)) {
634 sklist[skcount++] = socket;
637 /* Now we're done enumerating; go through the list. */
638 for (i = 0; i < skcount; i++) {
641 wp = (WPARAM) socket;
642 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
643 noise_ultralight(socket);
644 noise_ultralight(things.lNetworkEvents);
645 if (things.lNetworkEvents & FD_READ)
646 connopen &= select_result(wp, (LPARAM) FD_READ);
647 if (things.lNetworkEvents & FD_CLOSE)
648 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
649 if (things.lNetworkEvents & FD_OOB)
650 connopen &= select_result(wp, (LPARAM) FD_OOB);
651 if (things.lNetworkEvents & FD_WRITE)
652 connopen &= select_result(wp, (LPARAM) FD_WRITE);
656 noise_ultralight(idata.len);
658 back->send(idata.buffer, idata.len);
660 back->special(TS_EOF);
662 SetEvent(idata.eventback);
664 if (!connopen || back->socket() == NULL)
665 break; /* we closed the connection */