2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
12 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
16 void fatalbox (char *p, ...) {
18 fprintf(stderr, "FATAL ERROR: ", p);
20 vfprintf(stderr, p, ap);
26 void connection_fatal (char *p, ...) {
28 fprintf(stderr, "FATAL ERROR: ", p);
30 vfprintf(stderr, p, ap);
37 static char *password = NULL;
39 void logevent(char *string) { }
41 void verify_ssh_host_key(char *host, int port, char *keytype,
42 char *keystr, char *fingerprint) {
47 static const char absentmsg[] =
48 "The server's host key is not cached in the registry. You\n"
49 "have no guarantee that the server is the computer you\n"
51 "The server's key fingerprint is:\n"
53 "If you trust this host, enter \"y\" to add the key to\n"
54 "PuTTY's cache and carry on connecting.\n"
55 "If you do not trust this host, enter \"n\" to abandon the\n"
57 "Continue connecting? (y/n) ";
59 static const char wrongmsg[] =
60 "WARNING - POTENTIAL SECURITY BREACH!\n"
61 "The server's host key does not match the one PuTTY has\n"
62 "cached in the registry. This means that either the\n"
63 "server administrator has changed the host key, or you\n"
64 "have actually connected to another computer pretending\n"
66 "The new key fingerprint is:\n"
68 "If you were expecting this change and trust the new key,\n"
69 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
70 "If you want to carry on connecting but without updating\n"
71 "the cache, enter \"n\".\n"
72 "If you want to abandon the connection completely, press\n"
73 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
75 "Update cached key? (y/n, Return cancels connection) ";
77 static const char abandoned[] = "Connection abandoned.\n";
82 * Verify the key against the registry.
84 ret = verify_host_key(host, port, keytype, keystr);
86 if (ret == 0) /* success - key matched OK */
89 if (ret == 2) /* key was different */
90 fprintf(stderr, wrongmsg, fingerprint);
91 if (ret == 1) /* key was absent */
92 fprintf(stderr, absentmsg, fingerprint);
94 hin = GetStdHandle(STD_INPUT_HANDLE);
95 GetConsoleMode(hin, &savemode);
96 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
97 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
98 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
99 SetConsoleMode(hin, savemode);
101 if (ret == 2) { /* key was different */
102 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
103 if (line[0] == 'y' || line[0] == 'Y')
104 store_host_key(host, port, keytype, keystr);
106 fprintf(stderr, abandoned);
110 if (ret == 1) { /* key was absent */
111 if (line[0] == 'y' || line[0] == 'Y')
112 store_host_key(host, port, keytype, keystr);
114 fprintf(stderr, abandoned);
121 DWORD orig_console_mode;
123 void begin_session(void) {
125 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
127 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
135 while (reap < inbuf_head) {
136 if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
137 return; /* give up in panic */
149 static int get_password(const char *prompt, char *str, int maxlen)
155 static int tried_once = 0;
160 strncpy(str, password, maxlen);
161 str[maxlen-1] = '\0';
167 hin = GetStdHandle(STD_INPUT_HANDLE);
168 hout = GetStdHandle(STD_OUTPUT_HANDLE);
169 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
170 fprintf(stderr, "Cannot get standard input/output handles");
174 GetConsoleMode(hin, &savemode);
175 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
176 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
178 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
179 ReadFile(hin, str, maxlen-1, &i, NULL);
181 SetConsoleMode(hin, savemode);
183 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
186 WriteFile(hout, "\r\n", 2, &i, NULL);
191 static DWORD WINAPI stdin_read_thread(void *param) {
192 struct input_data *idata = (struct input_data *)param;
195 inhandle = GetStdHandle(STD_INPUT_HANDLE);
197 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
198 &idata->len, NULL)) {
199 SetEvent(idata->event);
203 SetEvent(idata->event);
209 * Short description of parameters.
211 static void usage(void)
213 printf("PuTTY Link: command-line connection utility\n");
215 printf("Usage: plink [options] [user@]host [command]\n");
216 printf("Options:\n");
217 printf(" -v show verbose messages\n");
218 printf(" -ssh force use of ssh protocol\n");
219 printf(" -P port connect to specified port\n");
220 printf(" -pw passw login with specified password\n");
224 int main(int argc, char **argv) {
227 WSAEVENT netevent, stdinevent;
231 struct input_data idata;
235 ssh_get_password = get_password;
239 * Process the command line.
241 do_defaults(NULL, &cfg);
242 default_protocol = cfg.protocol;
243 default_port = cfg.port;
246 * Override the default protocol if PLINK_PROTOCOL is set.
248 char *p = getenv("PLINK_PROTOCOL");
251 for (i = 0; backends[i].backend != NULL; i++) {
252 if (!strcmp(backends[i].name, p)) {
253 default_protocol = cfg.protocol = backends[i].protocol;
254 default_port = cfg.port = backends[i].backend->default_port;
263 if (!strcmp(p, "-ssh")) {
264 default_protocol = cfg.protocol = PROT_SSH;
265 default_port = cfg.port = 22;
266 } else if (!strcmp(p, "-telnet")) {
267 default_protocol = cfg.protocol = PROT_TELNET;
268 default_port = cfg.port = 23;
269 } else if (!strcmp(p, "-raw")) {
270 default_protocol = cfg.protocol = PROT_RAW;
271 } else if (!strcmp(p, "-v")) {
272 flags |= FLAG_VERBOSE;
273 } else if (!strcmp(p, "-log")) {
274 logfile = "putty.log";
275 } else if (!strcmp(p, "-pw") && argc > 1) {
276 --argc, password = *++argv;
277 } else if (!strcmp(p, "-l") && argc > 1) {
279 --argc, username = *++argv;
280 strncpy(cfg.username, username, sizeof(cfg.username));
281 cfg.username[sizeof(cfg.username)-1] = '\0';
282 } else if (!strcmp(p, "-P") && argc > 1) {
283 --argc, portnumber = atoi(*++argv);
289 * If the hostname starts with "telnet:", set the
290 * protocol to Telnet and process the string as a
293 if (!strncmp(q, "telnet:", 7)) {
297 if (q[0] == '/' && q[1] == '/')
299 cfg.protocol = PROT_TELNET;
301 while (*p && *p != ':' && *p != '/') p++;
309 strncpy (cfg.host, q, sizeof(cfg.host)-1);
310 cfg.host[sizeof(cfg.host)-1] = '\0';
314 * Before we process the [user@]host string, we
315 * first check for the presence of a protocol
316 * prefix (a protocol name followed by ",").
321 for (i = 0; backends[i].backend != NULL; i++) {
322 j = strlen(backends[i].name);
324 !memcmp(backends[i].name, p, j)) {
325 default_protocol = cfg.protocol = backends[i].protocol;
326 portnumber = backends[i].backend->default_port;
334 * Three cases. Either (a) there's a nonzero
335 * length string followed by an @, in which
336 * case that's user and the remainder is host.
337 * Or (b) there's only one string, not counting
338 * a potential initial @, and it exists in the
339 * saved-sessions database. Or (c) only one
340 * string and it _doesn't_ exist in the
344 if (r == p) p++, r = NULL; /* discount initial @ */
349 do_defaults (p, &cfg);
350 if (cfg.host[0] == '\0') {
351 /* No settings for this host; use defaults */
352 strncpy(cfg.host, p, sizeof(cfg.host)-1);
353 cfg.host[sizeof(cfg.host)-1] = '\0';
358 strncpy(cfg.username, p, sizeof(cfg.username)-1);
359 cfg.username[sizeof(cfg.username)-1] = '\0';
360 strncpy(cfg.host, r, sizeof(cfg.host)-1);
361 cfg.host[sizeof(cfg.host)-1] = '\0';
366 int len = sizeof(cfg.remote_cmd) - 1;
367 char *cp = cfg.remote_cmd;
370 strncpy(cp, p, len); cp[len] = '\0';
371 len2 = strlen(cp); len -= len2; cp += len2;
375 strncpy(cp, *++argv, len); cp[len] = '\0';
376 len2 = strlen(cp); len -= len2; cp += len2;
378 cfg.nopty = TRUE; /* command => no terminal */
379 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
380 break; /* done with cmdline */
389 if (!*cfg.remote_cmd)
390 flags |= FLAG_INTERACTIVE;
393 * Select protocol. This is farmed out into a table in a
394 * separate file to enable an ssh-free variant.
399 for (i = 0; backends[i].backend != NULL; i++)
400 if (backends[i].protocol == cfg.protocol) {
401 back = backends[i].backend;
405 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
413 if (portnumber != -1)
414 cfg.port = portnumber;
417 * Initialise WinSock.
419 winsock_ver = MAKEWORD(2, 0);
420 if (WSAStartup(winsock_ver, &wsadata)) {
421 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
422 MB_OK | MB_ICONEXCLAMATION);
425 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
426 MessageBox(NULL, "WinSock version is incompatible with 2.0",
427 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
433 * Start up the connection.
439 error = back->init (NULL, cfg.host, cfg.port, &realhost);
441 fprintf(stderr, "Unable to open connection:\n%s", error);
446 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
447 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
449 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
450 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
451 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
454 * Now we must send the back end oodles of stuff.
456 socket = back->socket();
458 * Turn off ECHO and LINE input modes. We don't care if this
459 * call fails, because we know we aren't necessarily running in
462 WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
463 handles[0] = netevent;
464 handles[1] = stdinevent;
469 if (!sending && back->sendok()) {
471 * Create a separate thread to read from stdin. This is
472 * a total pain, but I can't find another way to do it:
474 * - an overlapped ReadFile or ReadFileEx just doesn't
475 * happen; we get failure from ReadFileEx, and
476 * ReadFile blocks despite being given an OVERLAPPED
477 * structure. Perhaps we can't do overlapped reads
478 * on consoles. WHY THE HELL NOT?
480 * - WaitForMultipleObjects(netevent, console) doesn't
481 * work, because it signals the console when
482 * _anything_ happens, including mouse motions and
483 * other things that don't cause data to be readable
484 * - so we're back to ReadFile blocking.
486 idata.event = stdinevent;
487 if (!CreateThread(NULL, 0, stdin_read_thread,
488 &idata, 0, &threadid)) {
489 fprintf(stderr, "Unable to create second thread\n");
495 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
497 WSANETWORKEVENTS things;
498 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
499 if (things.lNetworkEvents & FD_READ)
500 back->msg(0, FD_READ);
501 if (things.lNetworkEvents & FD_CLOSE) {
502 back->msg(0, FD_CLOSE);
509 back->send(idata.buffer, idata.len);
511 back->special(TS_EOF);
514 if (back->socket() == INVALID_SOCKET)
515 break; /* we closed the connection */