2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
12 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
17 void fatalbox (char *p, ...) {
19 fprintf(stderr, "FATAL ERROR: ", p);
21 vfprintf(stderr, p, ap);
27 void connection_fatal (char *p, ...) {
29 fprintf(stderr, "FATAL ERROR: ", p);
31 vfprintf(stderr, p, ap);
38 static char *password = NULL;
40 void logevent(char *string) { }
42 void verify_ssh_host_key(char *host, int port, char *keytype,
43 char *keystr, char *fingerprint) {
48 static const char absentmsg[] =
49 "The server's host key is not cached in the registry. You\n"
50 "have no guarantee that the server is the computer you\n"
52 "The server's key fingerprint is:\n"
54 "If you trust this host, enter \"y\" to add the key to\n"
55 "PuTTY's cache and carry on connecting.\n"
56 "If you do not trust this host, enter \"n\" to abandon the\n"
58 "Continue connecting? (y/n) ";
60 static const char wrongmsg[] =
61 "WARNING - POTENTIAL SECURITY BREACH!\n"
62 "The server's host key does not match the one PuTTY has\n"
63 "cached in the registry. This means that either the\n"
64 "server administrator has changed the host key, or you\n"
65 "have actually connected to another computer pretending\n"
67 "The new key fingerprint is:\n"
69 "If you were expecting this change and trust the new key,\n"
70 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
71 "If you want to carry on connecting but without updating\n"
72 "the cache, enter \"n\".\n"
73 "If you want to abandon the connection completely, press\n"
74 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
76 "Update cached key? (y/n, Return cancels connection) ";
78 static const char abandoned[] = "Connection abandoned.\n";
83 * Verify the key against the registry.
85 ret = verify_host_key(host, port, keytype, keystr);
87 if (ret == 0) /* success - key matched OK */
90 if (ret == 2) /* key was different */
91 fprintf(stderr, wrongmsg, fingerprint);
92 if (ret == 1) /* key was absent */
93 fprintf(stderr, absentmsg, fingerprint);
95 hin = GetStdHandle(STD_INPUT_HANDLE);
96 GetConsoleMode(hin, &savemode);
97 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
98 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
99 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
100 SetConsoleMode(hin, savemode);
102 if (ret == 2) { /* key was different */
103 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
104 if (line[0] == 'y' || line[0] == 'Y')
105 store_host_key(host, port, keytype, keystr);
107 fprintf(stderr, abandoned);
111 if (ret == 1) { /* key was absent */
112 if (line[0] == 'y' || line[0] == 'Y')
113 store_host_key(host, port, keytype, keystr);
115 fprintf(stderr, abandoned);
122 DWORD orig_console_mode;
124 void begin_session(void) {
126 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
128 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
136 while (reap < inbuf_head) {
137 if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
138 return; /* give up in panic */
150 static int get_password(const char *prompt, char *str, int maxlen)
156 static int tried_once = 0;
161 strncpy(str, password, maxlen);
162 str[maxlen-1] = '\0';
168 hin = GetStdHandle(STD_INPUT_HANDLE);
169 hout = GetStdHandle(STD_OUTPUT_HANDLE);
170 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
171 fprintf(stderr, "Cannot get standard input/output handles");
175 GetConsoleMode(hin, &savemode);
176 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
177 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
179 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
180 ReadFile(hin, str, maxlen-1, &i, NULL);
182 SetConsoleMode(hin, savemode);
184 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
187 WriteFile(hout, "\r\n", 2, &i, NULL);
192 static DWORD WINAPI stdin_read_thread(void *param) {
193 struct input_data *idata = (struct input_data *)param;
196 inhandle = GetStdHandle(STD_INPUT_HANDLE);
198 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
199 &idata->len, NULL)) {
200 SetEvent(idata->event);
204 SetEvent(idata->event);
210 * Short description of parameters.
212 static void usage(void)
214 printf("PuTTY Link: command-line connection utility\n");
216 printf("Usage: plink [options] [user@]host [command]\n");
217 printf("Options:\n");
218 printf(" -v show verbose messages\n");
219 printf(" -ssh force use of ssh protocol\n");
220 printf(" -P port connect to specified port\n");
221 printf(" -pw passw login with specified password\n");
225 int main(int argc, char **argv) {
228 WSAEVENT netevent, stdinevent;
232 struct input_data idata;
236 ssh_get_password = get_password;
240 * Process the command line.
242 do_defaults(NULL, &cfg);
243 default_protocol = cfg.protocol;
244 default_port = cfg.port;
247 * Override the default protocol if PLINK_PROTOCOL is set.
249 char *p = getenv("PLINK_PROTOCOL");
252 for (i = 0; backends[i].backend != NULL; i++) {
253 if (!strcmp(backends[i].name, p)) {
254 default_protocol = cfg.protocol = backends[i].protocol;
255 default_port = cfg.port = backends[i].backend->default_port;
264 if (!strcmp(p, "-ssh")) {
265 default_protocol = cfg.protocol = PROT_SSH;
266 default_port = cfg.port = 22;
267 } else if (!strcmp(p, "-telnet")) {
268 default_protocol = cfg.protocol = PROT_TELNET;
269 default_port = cfg.port = 23;
270 } else if (!strcmp(p, "-raw")) {
271 default_protocol = cfg.protocol = PROT_RAW;
272 } else if (!strcmp(p, "-v")) {
273 flags |= FLAG_VERBOSE;
274 } else if (!strcmp(p, "-log")) {
275 logfile = "putty.log";
276 } else if (!strcmp(p, "-pw") && argc > 1) {
277 --argc, password = *++argv;
278 } else if (!strcmp(p, "-l") && argc > 1) {
280 --argc, username = *++argv;
281 strncpy(cfg.username, username, sizeof(cfg.username));
282 cfg.username[sizeof(cfg.username)-1] = '\0';
283 } else if (!strcmp(p, "-P") && argc > 1) {
284 --argc, portnumber = atoi(*++argv);
290 * If the hostname starts with "telnet:", set the
291 * protocol to Telnet and process the string as a
294 if (!strncmp(q, "telnet:", 7)) {
298 if (q[0] == '/' && q[1] == '/')
300 cfg.protocol = PROT_TELNET;
302 while (*p && *p != ':' && *p != '/') p++;
310 strncpy (cfg.host, q, sizeof(cfg.host)-1);
311 cfg.host[sizeof(cfg.host)-1] = '\0';
315 * Before we process the [user@]host string, we
316 * first check for the presence of a protocol
317 * prefix (a protocol name followed by ",").
322 for (i = 0; backends[i].backend != NULL; i++) {
323 j = strlen(backends[i].name);
325 !memcmp(backends[i].name, p, j)) {
326 default_protocol = cfg.protocol = backends[i].protocol;
327 portnumber = backends[i].backend->default_port;
335 * Three cases. Either (a) there's a nonzero
336 * length string followed by an @, in which
337 * case that's user and the remainder is host.
338 * Or (b) there's only one string, not counting
339 * a potential initial @, and it exists in the
340 * saved-sessions database. Or (c) only one
341 * string and it _doesn't_ exist in the
345 if (r == p) p++, r = NULL; /* discount initial @ */
350 do_defaults (p, &cfg);
351 if (cfg.host[0] == '\0') {
352 /* No settings for this host; use defaults */
353 strncpy(cfg.host, p, sizeof(cfg.host)-1);
354 cfg.host[sizeof(cfg.host)-1] = '\0';
359 strncpy(cfg.username, p, sizeof(cfg.username)-1);
360 cfg.username[sizeof(cfg.username)-1] = '\0';
361 strncpy(cfg.host, r, sizeof(cfg.host)-1);
362 cfg.host[sizeof(cfg.host)-1] = '\0';
367 int len = sizeof(cfg.remote_cmd) - 1;
368 char *cp = cfg.remote_cmd;
371 strncpy(cp, p, len); cp[len] = '\0';
372 len2 = strlen(cp); len -= len2; cp += len2;
376 strncpy(cp, *++argv, len); cp[len] = '\0';
377 len2 = strlen(cp); len -= len2; cp += len2;
379 cfg.nopty = TRUE; /* command => no terminal */
380 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
381 break; /* done with cmdline */
390 if (!*cfg.remote_cmd)
391 flags |= FLAG_INTERACTIVE;
394 * Select protocol. This is farmed out into a table in a
395 * separate file to enable an ssh-free variant.
400 for (i = 0; backends[i].backend != NULL; i++)
401 if (backends[i].protocol == cfg.protocol) {
402 back = backends[i].backend;
406 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
414 if (portnumber != -1)
415 cfg.port = portnumber;
418 * Initialise WinSock.
420 winsock_ver = MAKEWORD(2, 0);
421 if (WSAStartup(winsock_ver, &wsadata)) {
422 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
423 MB_OK | MB_ICONEXCLAMATION);
426 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
427 MessageBox(NULL, "WinSock version is incompatible with 2.0",
428 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
434 * Start up the connection.
440 error = back->init (NULL, cfg.host, cfg.port, &realhost);
442 fprintf(stderr, "Unable to open connection:\n%s", error);
447 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
448 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
450 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
451 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
452 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
455 * Now we must send the back end oodles of stuff.
457 socket = back->socket();
459 * Turn off ECHO and LINE input modes. We don't care if this
460 * call fails, because we know we aren't necessarily running in
463 WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
464 handles[0] = netevent;
465 handles[1] = stdinevent;
470 if (!sending && back->sendok()) {
472 * Create a separate thread to read from stdin. This is
473 * a total pain, but I can't find another way to do it:
475 * - an overlapped ReadFile or ReadFileEx just doesn't
476 * happen; we get failure from ReadFileEx, and
477 * ReadFile blocks despite being given an OVERLAPPED
478 * structure. Perhaps we can't do overlapped reads
479 * on consoles. WHY THE HELL NOT?
481 * - WaitForMultipleObjects(netevent, console) doesn't
482 * work, because it signals the console when
483 * _anything_ happens, including mouse motions and
484 * other things that don't cause data to be readable
485 * - so we're back to ReadFile blocking.
487 idata.event = stdinevent;
488 if (!CreateThread(NULL, 0, stdin_read_thread,
489 &idata, 0, &threadid)) {
490 fprintf(stderr, "Unable to create second thread\n");
496 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
498 WSANETWORKEVENTS things;
499 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
500 if (things.lNetworkEvents & FD_READ)
501 back->msg(0, FD_READ);
502 if (things.lNetworkEvents & FD_CLOSE) {
503 back->msg(0, FD_CLOSE);
510 back->send(idata.buffer, idata.len);
512 back->special(TS_EOF);
515 if (back->socket() == INVALID_SOCKET)
516 break; /* we closed the connection */