2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 void fatalbox (char *p, ...) {
20 fprintf(stderr, "FATAL ERROR: ");
22 vfprintf(stderr, p, ap);
28 void connection_fatal (char *p, ...) {
30 fprintf(stderr, "FATAL ERROR: ");
32 vfprintf(stderr, p, ap);
39 static char *password = NULL;
41 void logevent(char *string) { }
43 void verify_ssh_host_key(char *host, int port, char *keytype,
44 char *keystr, char *fingerprint) {
49 static const char absentmsg[] =
50 "The server's host key is not cached in the registry. You\n"
51 "have no guarantee that the server is the computer you\n"
53 "The server's key fingerprint is:\n"
55 "If you trust this host, enter \"y\" to add the key to\n"
56 "PuTTY's cache and carry on connecting.\n"
57 "If you do not trust this host, enter \"n\" to abandon the\n"
59 "Continue connecting? (y/n) ";
61 static const char wrongmsg[] =
62 "WARNING - POTENTIAL SECURITY BREACH!\n"
63 "The server's host key does not match the one PuTTY has\n"
64 "cached in the registry. This means that either the\n"
65 "server administrator has changed the host key, or you\n"
66 "have actually connected to another computer pretending\n"
68 "The new key fingerprint is:\n"
70 "If you were expecting this change and trust the new key,\n"
71 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
72 "If you want to carry on connecting but without updating\n"
73 "the cache, enter \"n\".\n"
74 "If you want to abandon the connection completely, press\n"
75 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
77 "Update cached key? (y/n, Return cancels connection) ";
79 static const char abandoned[] = "Connection abandoned.\n";
84 * Verify the key against the registry.
86 ret = verify_host_key(host, port, keytype, keystr);
88 if (ret == 0) /* success - key matched OK */
91 if (ret == 2) { /* key was different */
92 fprintf(stderr, wrongmsg, fingerprint);
95 if (ret == 1) { /* key was absent */
96 fprintf(stderr, absentmsg, fingerprint);
100 hin = GetStdHandle(STD_INPUT_HANDLE);
101 GetConsoleMode(hin, &savemode);
102 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
103 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
104 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
105 SetConsoleMode(hin, savemode);
107 if (ret == 2) { /* key was different */
108 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
109 if (line[0] == 'y' || line[0] == 'Y')
110 store_host_key(host, port, keytype, keystr);
112 fprintf(stderr, abandoned);
116 if (ret == 1) { /* key was absent */
117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
120 fprintf(stderr, abandoned);
126 HANDLE inhandle, outhandle, errhandle;
127 DWORD orig_console_mode;
131 void from_backend(int is_stderr, char *data, int len) {
134 HANDLE h = (is_stderr ? errhandle : outhandle);
138 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
139 return; /* give up in panic */
144 int term_ldisc(int mode) { return FALSE; }
145 void ldisc_update(int echo, int edit) {
146 /* Update stdin read mode to reflect changes in line discipline. */
149 mode = ENABLE_PROCESSED_INPUT;
151 mode = mode | ENABLE_ECHO_INPUT;
153 mode = mode &~ ENABLE_ECHO_INPUT;
155 mode = mode | ENABLE_LINE_INPUT;
157 mode = mode &~ ENABLE_LINE_INPUT;
158 SetConsoleMode(inhandle, mode);
164 HANDLE event, eventback;
167 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
170 DWORD savemode, newmode, i;
172 if (is_pw && password) {
173 static int tried_once = 0;
178 strncpy(str, password, maxlen);
179 str[maxlen-1] = '\0';
185 hin = GetStdHandle(STD_INPUT_HANDLE);
186 hout = GetStdHandle(STD_OUTPUT_HANDLE);
187 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
188 fprintf(stderr, "Cannot get standard input/output handles");
192 GetConsoleMode(hin, &savemode);
193 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
195 newmode &= ~ENABLE_ECHO_INPUT;
197 newmode |= ENABLE_ECHO_INPUT;
198 SetConsoleMode(hin, newmode);
200 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
201 ReadFile(hin, str, maxlen-1, &i, NULL);
203 SetConsoleMode(hin, savemode);
205 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
209 WriteFile(hout, "\r\n", 2, &i, NULL);
214 static DWORD WINAPI stdin_read_thread(void *param) {
215 struct input_data *idata = (struct input_data *)param;
218 inhandle = GetStdHandle(STD_INPUT_HANDLE);
220 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
221 &idata->len, NULL) && idata->len > 0) {
222 SetEvent(idata->event);
223 WaitForSingleObject(idata->eventback, INFINITE);
227 SetEvent(idata->event);
233 * Short description of parameters.
235 static void usage(void)
237 printf("PuTTY Link: command-line connection utility\n");
239 printf("Usage: plink [options] [user@]host [command]\n");
240 printf("Options:\n");
241 printf(" -v show verbose messages\n");
242 printf(" -ssh force use of ssh protocol\n");
243 printf(" -P port connect to specified port\n");
244 printf(" -pw passw login with specified password\n");
245 printf(" -m file read remote command(s) from file\n");
249 char *do_select(SOCKET skt, int startup) {
252 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
256 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
257 switch (WSAGetLastError()) {
258 case WSAENETDOWN: return "Network is down";
259 default: return "WSAAsyncSelect(): unknown error";
265 int main(int argc, char **argv) {
271 struct input_data idata;
278 ssh_get_line = get_line;
280 sklist = NULL; skcount = sksize = 0;
284 * Process the command line.
286 do_defaults(NULL, &cfg);
287 default_protocol = cfg.protocol;
288 default_port = cfg.port;
291 * Override the default protocol if PLINK_PROTOCOL is set.
293 char *p = getenv("PLINK_PROTOCOL");
296 for (i = 0; backends[i].backend != NULL; i++) {
297 if (!strcmp(backends[i].name, p)) {
298 default_protocol = cfg.protocol = backends[i].protocol;
299 default_port = cfg.port = backends[i].backend->default_port;
308 if (!strcmp(p, "-ssh")) {
309 default_protocol = cfg.protocol = PROT_SSH;
310 default_port = cfg.port = 22;
311 } else if (!strcmp(p, "-telnet")) {
312 default_protocol = cfg.protocol = PROT_TELNET;
313 default_port = cfg.port = 23;
314 } else if (!strcmp(p, "-raw")) {
315 default_protocol = cfg.protocol = PROT_RAW;
316 } else if (!strcmp(p, "-v")) {
317 flags |= FLAG_VERBOSE;
318 } else if (!strcmp(p, "-log")) {
319 logfile = "putty.log";
320 } else if (!strcmp(p, "-pw") && argc > 1) {
321 --argc, password = *++argv;
322 } else if (!strcmp(p, "-l") && argc > 1) {
324 --argc, username = *++argv;
325 strncpy(cfg.username, username, sizeof(cfg.username));
326 cfg.username[sizeof(cfg.username)-1] = '\0';
327 } else if (!strcmp(p, "-m") && argc > 1) {
328 char *filename, *command;
333 --argc, filename = *++argv;
335 cmdlen = cmdsize = 0;
337 fp = fopen(filename, "r");
339 fprintf(stderr, "plink: unable to open command "
340 "file \"%s\"\n", filename);
348 if (cmdlen >= cmdsize) {
349 cmdsize = cmdlen + 512;
350 command = srealloc(command, cmdsize);
352 command[cmdlen++] = d;
354 cfg.remote_cmd_ptr = command;
355 cfg.nopty = TRUE; /* command => no terminal */
356 } else if (!strcmp(p, "-P") && argc > 1) {
357 --argc, portnumber = atoi(*++argv);
363 * If the hostname starts with "telnet:", set the
364 * protocol to Telnet and process the string as a
367 if (!strncmp(q, "telnet:", 7)) {
371 if (q[0] == '/' && q[1] == '/')
373 cfg.protocol = PROT_TELNET;
375 while (*p && *p != ':' && *p != '/') p++;
383 strncpy (cfg.host, q, sizeof(cfg.host)-1);
384 cfg.host[sizeof(cfg.host)-1] = '\0';
388 * Before we process the [user@]host string, we
389 * first check for the presence of a protocol
390 * prefix (a protocol name followed by ",").
395 for (i = 0; backends[i].backend != NULL; i++) {
396 j = strlen(backends[i].name);
398 !memcmp(backends[i].name, p, j)) {
399 default_protocol = cfg.protocol = backends[i].protocol;
400 portnumber = backends[i].backend->default_port;
408 * Three cases. Either (a) there's a nonzero
409 * length string followed by an @, in which
410 * case that's user and the remainder is host.
411 * Or (b) there's only one string, not counting
412 * a potential initial @, and it exists in the
413 * saved-sessions database. Or (c) only one
414 * string and it _doesn't_ exist in the
418 if (r == p) p++, r = NULL; /* discount initial @ */
424 do_defaults (p, &cfg2);
425 if (cfg2.host[0] == '\0') {
426 /* No settings for this host; use defaults */
427 strncpy(cfg.host, p, sizeof(cfg.host)-1);
428 cfg.host[sizeof(cfg.host)-1] = '\0';
429 cfg.port = default_port;
432 cfg.remote_cmd_ptr = cfg.remote_cmd;
436 strncpy(cfg.username, p, sizeof(cfg.username)-1);
437 cfg.username[sizeof(cfg.username)-1] = '\0';
438 strncpy(cfg.host, r, sizeof(cfg.host)-1);
439 cfg.host[sizeof(cfg.host)-1] = '\0';
440 cfg.port = default_port;
444 int len = sizeof(cfg.remote_cmd) - 1;
445 char *cp = cfg.remote_cmd;
448 strncpy(cp, p, len); cp[len] = '\0';
449 len2 = strlen(cp); len -= len2; cp += len2;
453 strncpy(cp, *++argv, len); cp[len] = '\0';
454 len2 = strlen(cp); len -= len2; cp += len2;
456 cfg.nopty = TRUE; /* command => no terminal */
457 break; /* done with cmdline */
466 if (!*cfg.remote_cmd_ptr)
467 flags |= FLAG_INTERACTIVE;
470 * Select protocol. This is farmed out into a table in a
471 * separate file to enable an ssh-free variant.
476 for (i = 0; backends[i].backend != NULL; i++)
477 if (backends[i].protocol == cfg.protocol) {
478 back = backends[i].backend;
482 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
490 if (portnumber != -1)
491 cfg.port = portnumber;
494 * Initialise WinSock.
496 winsock_ver = MAKEWORD(2, 0);
497 if (WSAStartup(winsock_ver, &wsadata)) {
498 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
499 MB_OK | MB_ICONEXCLAMATION);
502 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
503 MessageBox(NULL, "WinSock version is incompatible with 2.0",
504 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
511 * Start up the connection.
513 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
518 error = back->init (cfg.host, cfg.port, &realhost);
520 fprintf(stderr, "Unable to open connection:\n%s", error);
526 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
528 inhandle = GetStdHandle(STD_INPUT_HANDLE);
529 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
530 errhandle = GetStdHandle(STD_ERROR_HANDLE);
531 GetConsoleMode(inhandle, &orig_console_mode);
532 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
535 * Turn off ECHO and LINE input modes. We don't care if this
536 * call fails, because we know we aren't necessarily running in
539 handles[0] = netevent;
540 handles[1] = stdinevent;
545 if (!sending && back->sendok()) {
547 * Create a separate thread to read from stdin. This is
548 * a total pain, but I can't find another way to do it:
550 * - an overlapped ReadFile or ReadFileEx just doesn't
551 * happen; we get failure from ReadFileEx, and
552 * ReadFile blocks despite being given an OVERLAPPED
553 * structure. Perhaps we can't do overlapped reads
554 * on consoles. WHY THE HELL NOT?
556 * - WaitForMultipleObjects(netevent, console) doesn't
557 * work, because it signals the console when
558 * _anything_ happens, including mouse motions and
559 * other things that don't cause data to be readable
560 * - so we're back to ReadFile blocking.
562 idata.event = stdinevent;
563 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
564 if (!CreateThread(NULL, 0, stdin_read_thread,
565 &idata, 0, &threadid)) {
566 fprintf(stderr, "Unable to create second thread\n");
572 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
574 WSANETWORKEVENTS things;
576 extern SOCKET first_socket(int *), next_socket(int *);
577 extern int select_result(WPARAM, LPARAM);
581 * We must not call select_result() for any socket
582 * until we have finished enumerating within the tree.
583 * This is because select_result() may close the socket
584 * and modify the tree.
586 /* Count the active sockets. */
588 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
589 socket = next_socket(&socketstate))
592 /* Expand the buffer if necessary. */
595 sklist = srealloc(sklist, sksize * sizeof(*sklist));
598 /* Retrieve the sockets into sklist. */
600 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
601 socket = next_socket(&socketstate)) {
602 sklist[skcount++] = socket;
605 /* Now we're done enumerating; go through the list. */
606 for (i = 0; i < skcount; i++) {
610 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
611 noise_ultralight(socket);
612 noise_ultralight(things.lNetworkEvents);
613 if (things.lNetworkEvents & FD_READ)
614 connopen &= select_result(wp, (LPARAM)FD_READ);
615 if (things.lNetworkEvents & FD_CLOSE)
616 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
617 if (things.lNetworkEvents & FD_OOB)
618 connopen &= select_result(wp, (LPARAM)FD_OOB);
619 if (things.lNetworkEvents & FD_WRITE)
620 connopen &= select_result(wp, (LPARAM)FD_WRITE);
624 noise_ultralight(idata.len);
626 back->send(idata.buffer, idata.len);
628 back->special(TS_EOF);
630 SetEvent(idata.eventback);
632 if (!connopen || back->socket() == NULL)
633 break; /* we closed the connection */