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);
93 if (ret == 1) /* key was absent */
94 fprintf(stderr, absentmsg, fingerprint);
96 hin = GetStdHandle(STD_INPUT_HANDLE);
97 GetConsoleMode(hin, &savemode);
98 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
99 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
100 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
101 SetConsoleMode(hin, savemode);
103 if (ret == 2) { /* key was different */
104 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
105 if (line[0] == 'y' || line[0] == 'Y')
106 store_host_key(host, port, keytype, keystr);
108 fprintf(stderr, abandoned);
112 if (ret == 1) { /* key was absent */
113 if (line[0] == 'y' || line[0] == 'Y')
114 store_host_key(host, port, keytype, keystr);
116 fprintf(stderr, abandoned);
122 HANDLE outhandle, errhandle;
123 DWORD orig_console_mode;
127 void begin_session(void) {
129 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
131 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
134 void from_backend(int is_stderr, char *data, int len) {
137 HANDLE h = (is_stderr ? errhandle : outhandle);
141 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
142 return; /* give up in panic */
150 HANDLE event, eventback;
153 static int get_password(const char *prompt, char *str, int maxlen)
159 static int tried_once = 0;
164 strncpy(str, password, maxlen);
165 str[maxlen-1] = '\0';
171 hin = GetStdHandle(STD_INPUT_HANDLE);
172 hout = GetStdHandle(STD_OUTPUT_HANDLE);
173 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
174 fprintf(stderr, "Cannot get standard input/output handles");
178 GetConsoleMode(hin, &savemode);
179 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
180 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
182 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
183 ReadFile(hin, str, maxlen-1, &i, NULL);
185 SetConsoleMode(hin, savemode);
187 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
190 WriteFile(hout, "\r\n", 2, &i, NULL);
195 static DWORD WINAPI stdin_read_thread(void *param) {
196 struct input_data *idata = (struct input_data *)param;
199 inhandle = GetStdHandle(STD_INPUT_HANDLE);
201 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
202 &idata->len, NULL) && idata->len > 0) {
203 SetEvent(idata->event);
204 WaitForSingleObject(idata->eventback, INFINITE);
208 SetEvent(idata->event);
214 * Short description of parameters.
216 static void usage(void)
218 printf("PuTTY Link: command-line connection utility\n");
220 printf("Usage: plink [options] [user@]host [command]\n");
221 printf("Options:\n");
222 printf(" -v show verbose messages\n");
223 printf(" -ssh force use of ssh protocol\n");
224 printf(" -P port connect to specified port\n");
225 printf(" -pw passw login with specified password\n");
229 char *do_select(SOCKET skt, int startup) {
232 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
236 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
237 switch (WSAGetLastError()) {
238 case WSAENETDOWN: return "Network is down";
239 default: return "WSAAsyncSelect(): unknown error";
245 int main(int argc, char **argv) {
251 struct input_data idata;
258 ssh_get_password = get_password;
260 sklist = NULL; skcount = sksize = 0;
264 * Process the command line.
266 do_defaults(NULL, &cfg);
267 default_protocol = cfg.protocol;
268 default_port = cfg.port;
271 * Override the default protocol if PLINK_PROTOCOL is set.
273 char *p = getenv("PLINK_PROTOCOL");
276 for (i = 0; backends[i].backend != NULL; i++) {
277 if (!strcmp(backends[i].name, p)) {
278 default_protocol = cfg.protocol = backends[i].protocol;
279 default_port = cfg.port = backends[i].backend->default_port;
288 if (!strcmp(p, "-ssh")) {
289 default_protocol = cfg.protocol = PROT_SSH;
290 default_port = cfg.port = 22;
291 } else if (!strcmp(p, "-telnet")) {
292 default_protocol = cfg.protocol = PROT_TELNET;
293 default_port = cfg.port = 23;
294 } else if (!strcmp(p, "-raw")) {
295 default_protocol = cfg.protocol = PROT_RAW;
296 } else if (!strcmp(p, "-v")) {
297 flags |= FLAG_VERBOSE;
298 } else if (!strcmp(p, "-log")) {
299 logfile = "putty.log";
300 } else if (!strcmp(p, "-pw") && argc > 1) {
301 --argc, password = *++argv;
302 } else if (!strcmp(p, "-l") && argc > 1) {
304 --argc, username = *++argv;
305 strncpy(cfg.username, username, sizeof(cfg.username));
306 cfg.username[sizeof(cfg.username)-1] = '\0';
307 } else if (!strcmp(p, "-P") && argc > 1) {
308 --argc, portnumber = atoi(*++argv);
314 * If the hostname starts with "telnet:", set the
315 * protocol to Telnet and process the string as a
318 if (!strncmp(q, "telnet:", 7)) {
322 if (q[0] == '/' && q[1] == '/')
324 cfg.protocol = PROT_TELNET;
326 while (*p && *p != ':' && *p != '/') p++;
334 strncpy (cfg.host, q, sizeof(cfg.host)-1);
335 cfg.host[sizeof(cfg.host)-1] = '\0';
339 * Before we process the [user@]host string, we
340 * first check for the presence of a protocol
341 * prefix (a protocol name followed by ",").
346 for (i = 0; backends[i].backend != NULL; i++) {
347 j = strlen(backends[i].name);
349 !memcmp(backends[i].name, p, j)) {
350 default_protocol = cfg.protocol = backends[i].protocol;
351 portnumber = backends[i].backend->default_port;
359 * Three cases. Either (a) there's a nonzero
360 * length string followed by an @, in which
361 * case that's user and the remainder is host.
362 * Or (b) there's only one string, not counting
363 * a potential initial @, and it exists in the
364 * saved-sessions database. Or (c) only one
365 * string and it _doesn't_ exist in the
369 if (r == p) p++, r = NULL; /* discount initial @ */
375 do_defaults (p, &cfg2);
376 if (cfg2.host[0] == '\0') {
377 /* No settings for this host; use defaults */
378 strncpy(cfg.host, p, sizeof(cfg.host)-1);
379 cfg.host[sizeof(cfg.host)-1] = '\0';
385 strncpy(cfg.username, p, sizeof(cfg.username)-1);
386 cfg.username[sizeof(cfg.username)-1] = '\0';
387 strncpy(cfg.host, r, sizeof(cfg.host)-1);
388 cfg.host[sizeof(cfg.host)-1] = '\0';
393 int len = sizeof(cfg.remote_cmd) - 1;
394 char *cp = cfg.remote_cmd;
397 strncpy(cp, p, len); cp[len] = '\0';
398 len2 = strlen(cp); len -= len2; cp += len2;
402 strncpy(cp, *++argv, len); cp[len] = '\0';
403 len2 = strlen(cp); len -= len2; cp += len2;
405 cfg.nopty = TRUE; /* command => no terminal */
406 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
407 break; /* done with cmdline */
416 if (!*cfg.remote_cmd)
417 flags |= FLAG_INTERACTIVE;
420 * Select protocol. This is farmed out into a table in a
421 * separate file to enable an ssh-free variant.
426 for (i = 0; backends[i].backend != NULL; i++)
427 if (backends[i].protocol == cfg.protocol) {
428 back = backends[i].backend;
432 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
440 if (portnumber != -1)
441 cfg.port = portnumber;
444 * Initialise WinSock.
446 winsock_ver = MAKEWORD(2, 0);
447 if (WSAStartup(winsock_ver, &wsadata)) {
448 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
449 MB_OK | MB_ICONEXCLAMATION);
452 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
453 MessageBox(NULL, "WinSock version is incompatible with 2.0",
454 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
461 * Start up the connection.
463 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
468 error = back->init (cfg.host, cfg.port, &realhost);
470 fprintf(stderr, "Unable to open connection:\n%s", error);
476 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
478 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
479 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
480 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
481 errhandle = GetStdHandle(STD_ERROR_HANDLE);
484 * Turn off ECHO and LINE input modes. We don't care if this
485 * call fails, because we know we aren't necessarily running in
488 handles[0] = netevent;
489 handles[1] = stdinevent;
494 if (!sending && back->sendok()) {
496 * Create a separate thread to read from stdin. This is
497 * a total pain, but I can't find another way to do it:
499 * - an overlapped ReadFile or ReadFileEx just doesn't
500 * happen; we get failure from ReadFileEx, and
501 * ReadFile blocks despite being given an OVERLAPPED
502 * structure. Perhaps we can't do overlapped reads
503 * on consoles. WHY THE HELL NOT?
505 * - WaitForMultipleObjects(netevent, console) doesn't
506 * work, because it signals the console when
507 * _anything_ happens, including mouse motions and
508 * other things that don't cause data to be readable
509 * - so we're back to ReadFile blocking.
511 idata.event = stdinevent;
512 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
513 if (!CreateThread(NULL, 0, stdin_read_thread,
514 &idata, 0, &threadid)) {
515 fprintf(stderr, "Unable to create second thread\n");
521 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
523 WSANETWORKEVENTS things;
526 extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
527 extern int select_result(WPARAM, LPARAM);
531 * We must not call select_result() for any socket
532 * until we have finished enumerating within the tree.
533 * This is because select_result() may close the socket
534 * and modify the tree.
536 /* Count the active sockets. */
538 for (socket = first_socket(&e); socket != INVALID_SOCKET;
539 socket = next_socket(&e))
542 /* Expand the buffer if necessary. */
545 sklist = srealloc(sklist, sksize * sizeof(*sklist));
548 /* Retrieve the sockets into sklist. */
550 for (socket = first_socket(&e); socket != INVALID_SOCKET;
551 socket = next_socket(&e)) {
552 sklist[skcount++] = socket;
555 /* Now we're done enumerating; go through the list. */
556 for (i = 0; i < skcount; i++) {
560 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
561 noise_ultralight(socket);
562 noise_ultralight(things.lNetworkEvents);
563 if (things.lNetworkEvents & FD_READ)
564 connopen &= select_result(wp, (LPARAM)FD_READ);
565 if (things.lNetworkEvents & FD_CLOSE)
566 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
567 if (things.lNetworkEvents & FD_OOB)
568 connopen &= select_result(wp, (LPARAM)FD_OOB);
569 if (things.lNetworkEvents & FD_WRITE)
570 connopen &= select_result(wp, (LPARAM)FD_WRITE);
574 noise_ultralight(idata.len);
576 back->send(idata.buffer, idata.len);
578 back->special(TS_EOF);
580 SetEvent(idata.eventback);
582 if (!connopen || back->socket() == NULL)
583 break; /* we closed the connection */