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 want to carry on connecting just once, without\n"
63 "adding the key to the cache, enter \"n\".\n"
64 "If you do not trust this host, press Return to abandon the\n"
66 "Store key in cache? (y/n) ";
68 static const char wrongmsg[] =
69 "WARNING - POTENTIAL SECURITY BREACH!\n"
70 "The server's host key does not match the one PuTTY has\n"
71 "cached in the registry. This means that either the\n"
72 "server administrator has changed the host key, or you\n"
73 "have actually connected to another computer pretending\n"
75 "The new key fingerprint is:\n"
77 "If you were expecting this change and trust the new key,\n"
78 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
79 "If you want to carry on connecting but without updating\n"
80 "the cache, enter \"n\".\n"
81 "If you want to abandon the connection completely, press\n"
82 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
84 "Update cached key? (y/n, Return cancels connection) ";
86 static const char abandoned[] = "Connection abandoned.\n";
91 * Verify the key against the registry.
93 ret = verify_host_key(host, port, keytype, keystr);
95 if (ret == 0) /* success - key matched OK */
98 if (ret == 2) { /* key was different */
99 fprintf(stderr, wrongmsg, fingerprint);
102 if (ret == 1) { /* key was absent */
103 fprintf(stderr, absentmsg, fingerprint);
107 hin = GetStdHandle(STD_INPUT_HANDLE);
108 GetConsoleMode(hin, &savemode);
109 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
110 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
111 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
112 SetConsoleMode(hin, savemode);
114 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
115 if (line[0] == 'y' || line[0] == 'Y')
116 store_host_key(host, port, keytype, keystr);
118 fprintf(stderr, abandoned);
123 HANDLE inhandle, outhandle, errhandle;
124 DWORD orig_console_mode;
128 void from_backend(int is_stderr, char *data, int len)
132 HANDLE h = (is_stderr ? errhandle : outhandle);
136 if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
137 return; /* give up in panic */
142 int term_ldisc(int mode)
146 void ldisc_update(int echo, int edit)
148 /* Update stdin read mode to reflect changes in line discipline. */
151 mode = ENABLE_PROCESSED_INPUT;
153 mode = mode | ENABLE_ECHO_INPUT;
155 mode = mode & ~ENABLE_ECHO_INPUT;
157 mode = mode | ENABLE_LINE_INPUT;
159 mode = mode & ~ENABLE_LINE_INPUT;
160 SetConsoleMode(inhandle, mode);
166 HANDLE event, eventback;
169 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
172 DWORD savemode, newmode, i;
174 if (is_pw && password) {
175 static int tried_once = 0;
180 strncpy(str, password, maxlen);
181 str[maxlen - 1] = '\0';
187 hin = GetStdHandle(STD_INPUT_HANDLE);
188 hout = GetStdHandle(STD_OUTPUT_HANDLE);
189 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
190 fprintf(stderr, "Cannot get standard input/output handles");
194 GetConsoleMode(hin, &savemode);
195 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
197 newmode &= ~ENABLE_ECHO_INPUT;
199 newmode |= ENABLE_ECHO_INPUT;
200 SetConsoleMode(hin, newmode);
202 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
203 ReadFile(hin, str, maxlen - 1, &i, NULL);
205 SetConsoleMode(hin, savemode);
207 if ((int) i > maxlen)
214 WriteFile(hout, "\r\n", 2, &i, NULL);
219 static DWORD WINAPI stdin_read_thread(void *param)
221 struct input_data *idata = (struct input_data *) param;
224 inhandle = GetStdHandle(STD_INPUT_HANDLE);
226 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
227 &idata->len, NULL) && idata->len > 0) {
228 SetEvent(idata->event);
229 WaitForSingleObject(idata->eventback, INFINITE);
233 SetEvent(idata->event);
239 * Short description of parameters.
241 static void usage(void)
243 printf("PuTTY Link: command-line connection utility\n");
245 printf("Usage: plink [options] [user@]host [command]\n");
246 printf("Options:\n");
247 printf(" -v show verbose messages\n");
248 printf(" -ssh force use of ssh protocol\n");
249 printf(" -P port connect to specified port\n");
250 printf(" -pw passw login with specified password\n");
251 printf(" -m file read remote command(s) from file\n");
255 char *do_select(SOCKET skt, int startup)
259 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
263 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
264 switch (WSAGetLastError()) {
266 return "Network is down";
268 return "WSAAsyncSelect(): unknown error";
274 int main(int argc, char **argv)
281 struct input_data idata;
288 ssh_get_line = get_line;
291 skcount = sksize = 0;
293 * Initialise port and protocol to sensible defaults. (These
294 * will be overridden by more or less anything.)
296 default_protocol = PROT_SSH;
301 * Process the command line.
303 do_defaults(NULL, &cfg);
304 default_protocol = cfg.protocol;
305 default_port = cfg.port;
308 * Override the default protocol if PLINK_PROTOCOL is set.
310 char *p = getenv("PLINK_PROTOCOL");
313 for (i = 0; backends[i].backend != NULL; i++) {
314 if (!strcmp(backends[i].name, p)) {
315 default_protocol = cfg.protocol = backends[i].protocol;
316 default_port = cfg.port =
317 backends[i].backend->default_port;
326 if (!strcmp(p, "-ssh")) {
327 default_protocol = cfg.protocol = PROT_SSH;
328 default_port = cfg.port = 22;
329 } else if (!strcmp(p, "-telnet")) {
330 default_protocol = cfg.protocol = PROT_TELNET;
331 default_port = cfg.port = 23;
332 } else if (!strcmp(p, "-raw")) {
333 default_protocol = cfg.protocol = PROT_RAW;
334 } else if (!strcmp(p, "-v")) {
335 flags |= FLAG_VERBOSE;
336 } else if (!strcmp(p, "-log")) {
337 logfile = "putty.log";
338 } else if (!strcmp(p, "-pw") && argc > 1) {
339 --argc, password = *++argv;
340 } else if (!strcmp(p, "-l") && argc > 1) {
342 --argc, username = *++argv;
343 strncpy(cfg.username, username, sizeof(cfg.username));
344 cfg.username[sizeof(cfg.username) - 1] = '\0';
345 } else if (!strcmp(p, "-m") && argc > 1) {
346 char *filename, *command;
351 --argc, filename = *++argv;
353 cmdlen = cmdsize = 0;
355 fp = fopen(filename, "r");
357 fprintf(stderr, "plink: unable to open command "
358 "file \"%s\"\n", filename);
366 if (cmdlen >= cmdsize) {
367 cmdsize = cmdlen + 512;
368 command = srealloc(command, cmdsize);
370 command[cmdlen++] = d;
372 cfg.remote_cmd_ptr = command;
373 cfg.nopty = TRUE; /* command => no terminal */
374 } else if (!strcmp(p, "-P") && argc > 1) {
375 --argc, portnumber = atoi(*++argv);
381 * If the hostname starts with "telnet:", set the
382 * protocol to Telnet and process the string as a
385 if (!strncmp(q, "telnet:", 7)) {
389 if (q[0] == '/' && q[1] == '/')
391 cfg.protocol = PROT_TELNET;
393 while (*p && *p != ':' && *p != '/')
402 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
403 cfg.host[sizeof(cfg.host) - 1] = '\0';
407 * Before we process the [user@]host string, we
408 * first check for the presence of a protocol
409 * prefix (a protocol name followed by ",").
414 for (i = 0; backends[i].backend != NULL; i++) {
415 j = strlen(backends[i].name);
417 !memcmp(backends[i].name, p, j)) {
418 default_protocol = cfg.protocol =
419 backends[i].protocol;
421 backends[i].backend->default_port;
429 * Three cases. Either (a) there's a nonzero
430 * length string followed by an @, in which
431 * case that's user and the remainder is host.
432 * Or (b) there's only one string, not counting
433 * a potential initial @, and it exists in the
434 * saved-sessions database. Or (c) only one
435 * string and it _doesn't_ exist in the
440 p++, r = NULL; /* discount initial @ */
446 do_defaults(p, &cfg2);
447 if (cfg2.host[0] == '\0') {
448 /* No settings for this host; use defaults */
449 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
450 cfg.host[sizeof(cfg.host) - 1] = '\0';
451 cfg.port = default_port;
454 cfg.remote_cmd_ptr = cfg.remote_cmd;
458 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
459 cfg.username[sizeof(cfg.username) - 1] = '\0';
460 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
461 cfg.host[sizeof(cfg.host) - 1] = '\0';
462 cfg.port = default_port;
466 int len = sizeof(cfg.remote_cmd) - 1;
467 char *cp = cfg.remote_cmd;
478 strncpy(cp, *++argv, len);
484 cfg.nopty = TRUE; /* command => no terminal */
485 break; /* done with cmdline */
494 if (!*cfg.remote_cmd_ptr)
495 flags |= FLAG_INTERACTIVE;
498 * Select protocol. This is farmed out into a table in a
499 * separate file to enable an ssh-free variant.
504 for (i = 0; backends[i].backend != NULL; i++)
505 if (backends[i].protocol == cfg.protocol) {
506 back = backends[i].backend;
511 "Internal fault: Unsupported protocol found\n");
519 if (portnumber != -1)
520 cfg.port = portnumber;
523 * Initialise WinSock.
525 winsock_ver = MAKEWORD(2, 0);
526 if (WSAStartup(winsock_ver, &wsadata)) {
527 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
528 MB_OK | MB_ICONEXCLAMATION);
531 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
532 MessageBox(NULL, "WinSock version is incompatible with 2.0",
533 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
540 * Start up the connection.
542 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
547 error = back->init(cfg.host, cfg.port, &realhost);
549 fprintf(stderr, "Unable to open connection:\n%s", error);
556 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
558 inhandle = GetStdHandle(STD_INPUT_HANDLE);
559 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
560 errhandle = GetStdHandle(STD_ERROR_HANDLE);
561 GetConsoleMode(inhandle, &orig_console_mode);
562 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
565 * Turn off ECHO and LINE input modes. We don't care if this
566 * call fails, because we know we aren't necessarily running in
569 handles[0] = netevent;
570 handles[1] = stdinevent;
575 if (!sending && back->sendok()) {
577 * Create a separate thread to read from stdin. This is
578 * a total pain, but I can't find another way to do it:
580 * - an overlapped ReadFile or ReadFileEx just doesn't
581 * happen; we get failure from ReadFileEx, and
582 * ReadFile blocks despite being given an OVERLAPPED
583 * structure. Perhaps we can't do overlapped reads
584 * on consoles. WHY THE HELL NOT?
586 * - WaitForMultipleObjects(netevent, console) doesn't
587 * work, because it signals the console when
588 * _anything_ happens, including mouse motions and
589 * other things that don't cause data to be readable
590 * - so we're back to ReadFile blocking.
592 idata.event = stdinevent;
593 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
594 if (!CreateThread(NULL, 0, stdin_read_thread,
595 &idata, 0, &threadid)) {
596 fprintf(stderr, "Unable to create second thread\n");
602 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
604 WSANETWORKEVENTS things;
606 extern SOCKET first_socket(int *), next_socket(int *);
607 extern int select_result(WPARAM, LPARAM);
611 * We must not call select_result() for any socket
612 * until we have finished enumerating within the tree.
613 * This is because select_result() may close the socket
614 * and modify the tree.
616 /* Count the active sockets. */
618 for (socket = first_socket(&socketstate);
619 socket != INVALID_SOCKET;
620 socket = next_socket(&socketstate)) i++;
622 /* Expand the buffer if necessary. */
625 sklist = srealloc(sklist, sksize * sizeof(*sklist));
628 /* Retrieve the sockets into sklist. */
630 for (socket = first_socket(&socketstate);
631 socket != INVALID_SOCKET;
632 socket = next_socket(&socketstate)) {
633 sklist[skcount++] = socket;
636 /* Now we're done enumerating; go through the list. */
637 for (i = 0; i < skcount; i++) {
640 wp = (WPARAM) socket;
641 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
642 noise_ultralight(socket);
643 noise_ultralight(things.lNetworkEvents);
644 if (things.lNetworkEvents & FD_READ)
645 connopen &= select_result(wp, (LPARAM) FD_READ);
646 if (things.lNetworkEvents & FD_CLOSE)
647 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
648 if (things.lNetworkEvents & FD_OOB)
649 connopen &= select_result(wp, (LPARAM) FD_OOB);
650 if (things.lNetworkEvents & FD_WRITE)
651 connopen &= select_result(wp, (LPARAM) FD_WRITE);
655 noise_ultralight(idata.len);
657 back->send(idata.buffer, idata.len);
659 back->special(TS_EOF);
661 SetEvent(idata.eventback);
663 if (!connopen || back->socket() == NULL)
664 break; /* we closed the connection */