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) {
45 static const char absentmsg[] =
46 "The server's host key is not cached in the registry. You\n"
47 "have no guarantee that the server is the computer you\n"
49 "The server's key fingerprint is:\n"
51 "If you trust this host, enter \"y\" to add the key to\n"
52 "PuTTY's cache and carry on connecting.\n"
53 "If you do not trust this host, enter \"n\" to abandon the\n"
55 "Continue connecting? (y/n) ";
57 static const char wrongmsg[] =
58 "WARNING - POTENTIAL SECURITY BREACH!\n"
59 "The server's host key does not match the one PuTTY has\n"
60 "cached in the registry. This means that either the\n"
61 "server administrator has changed the host key, or you\n"
62 "have actually connected to another computer pretending\n"
64 "The new key fingerprint is:\n"
66 "If you were expecting this change and trust the new key,\n"
67 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
68 "If you want to carry on connecting but without updating\n"
69 "the cache, enter \"n\".\n"
70 "If you want to abandon the connection completely, press\n"
71 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
73 "Update cached key? (y/n, Return cancels connection) ";
75 static const char abandoned[] = "Connection abandoned.\n";
80 * Verify the key against the registry.
82 ret = verify_host_key(host, port, keytype, keystr);
84 if (ret == 0) /* success - key matched OK */
86 if (ret == 2) { /* key was different */
87 fprintf(stderr, wrongmsg, fingerprint);
88 if (fgets(line, sizeof(line), stdin) &&
89 line[0] != '\0' && line[0] != '\n') {
90 if (line[0] == 'y' || line[0] == 'Y')
91 store_host_key(host, port, keytype, keystr);
93 fprintf(stderr, abandoned);
97 if (ret == 1) { /* key was absent */
98 fprintf(stderr, absentmsg, fingerprint);
99 if (fgets(line, sizeof(line), stdin) &&
100 (line[0] == 'y' || line[0] == 'Y'))
101 store_host_key(host, port, keytype, keystr);
103 fprintf(stderr, abandoned);
110 DWORD orig_console_mode;
112 void begin_session(void) {
114 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
116 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
124 while (reap < inbuf_head) {
125 if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
126 return; /* give up in panic */
138 static int get_password(const char *prompt, char *str, int maxlen)
144 static int tried_once = 0;
149 strncpy(str, password, maxlen);
150 str[maxlen-1] = '\0';
156 hin = GetStdHandle(STD_INPUT_HANDLE);
157 hout = GetStdHandle(STD_OUTPUT_HANDLE);
158 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
159 fprintf(stderr, "Cannot get standard input/output handles");
163 GetConsoleMode(hin, &savemode);
164 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
165 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
167 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
168 ReadFile(hin, str, maxlen-1, &i, NULL);
170 SetConsoleMode(hin, savemode);
172 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
175 WriteFile(hout, "\r\n", 2, &i, NULL);
180 static DWORD WINAPI stdin_read_thread(void *param) {
181 struct input_data *idata = (struct input_data *)param;
184 inhandle = GetStdHandle(STD_INPUT_HANDLE);
186 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
187 &idata->len, NULL)) {
188 SetEvent(idata->event);
192 SetEvent(idata->event);
198 * Short description of parameters.
200 static void usage(void)
202 printf("PuTTY Link: command-line connection utility\n");
204 printf("Usage: plink [options] [user@]host [command]\n");
205 printf("Options:\n");
206 printf(" -v show verbose messages\n");
207 printf(" -ssh force use of ssh protocol\n");
208 printf(" -P port connect to specified port\n");
209 printf(" -pw passw login with specified password\n");
213 int main(int argc, char **argv) {
216 WSAEVENT netevent, stdinevent;
220 struct input_data idata;
224 ssh_get_password = get_password;
228 * Process the command line.
230 do_defaults(NULL, &cfg);
231 default_protocol = cfg.protocol;
232 default_port = cfg.port;
235 * Override the default protocol if PLINK_PROTOCOL is set.
237 char *p = getenv("PLINK_PROTOCOL");
240 for (i = 0; backends[i].backend != NULL; i++) {
241 if (!strcmp(backends[i].name, p)) {
242 default_protocol = cfg.protocol = backends[i].protocol;
243 default_port = cfg.port = backends[i].backend->default_port;
252 if (!strcmp(p, "-ssh")) {
253 default_protocol = cfg.protocol = PROT_SSH;
254 default_port = cfg.port = 22;
255 } else if (!strcmp(p, "-telnet")) {
256 default_protocol = cfg.protocol = PROT_TELNET;
257 default_port = cfg.port = 23;
258 } else if (!strcmp(p, "-raw")) {
259 default_protocol = cfg.protocol = PROT_RAW;
260 } else if (!strcmp(p, "-v")) {
261 flags |= FLAG_VERBOSE;
262 } else if (!strcmp(p, "-log")) {
263 logfile = "putty.log";
264 } else if (!strcmp(p, "-pw") && argc > 1) {
265 --argc, password = *++argv;
266 } else if (!strcmp(p, "-l") && argc > 1) {
268 --argc, username = *++argv;
269 strncpy(cfg.username, username, sizeof(cfg.username));
270 cfg.username[sizeof(cfg.username)-1] = '\0';
271 } else if (!strcmp(p, "-P") && argc > 1) {
272 --argc, portnumber = atoi(*++argv);
278 * If the hostname starts with "telnet:", set the
279 * protocol to Telnet and process the string as a
282 if (!strncmp(q, "telnet:", 7)) {
286 if (q[0] == '/' && q[1] == '/')
288 cfg.protocol = PROT_TELNET;
290 while (*p && *p != ':' && *p != '/') p++;
298 strncpy (cfg.host, q, sizeof(cfg.host)-1);
299 cfg.host[sizeof(cfg.host)-1] = '\0';
303 * Before we process the [user@]host string, we
304 * first check for the presence of a protocol
305 * prefix (a protocol name followed by ",").
310 for (i = 0; backends[i].backend != NULL; i++) {
311 j = strlen(backends[i].name);
313 !memcmp(backends[i].name, p, j)) {
314 default_protocol = cfg.protocol = backends[i].protocol;
315 portnumber = backends[i].backend->default_port;
323 * Three cases. Either (a) there's a nonzero
324 * length string followed by an @, in which
325 * case that's user and the remainder is host.
326 * Or (b) there's only one string, not counting
327 * a potential initial @, and it exists in the
328 * saved-sessions database. Or (c) only one
329 * string and it _doesn't_ exist in the
333 if (r == p) p++, r = NULL; /* discount initial @ */
338 do_defaults (p, &cfg);
339 if (cfg.host[0] == '\0') {
340 /* No settings for this host; use defaults */
341 strncpy(cfg.host, p, sizeof(cfg.host)-1);
342 cfg.host[sizeof(cfg.host)-1] = '\0';
347 strncpy(cfg.username, p, sizeof(cfg.username)-1);
348 cfg.username[sizeof(cfg.username)-1] = '\0';
349 strncpy(cfg.host, r, sizeof(cfg.host)-1);
350 cfg.host[sizeof(cfg.host)-1] = '\0';
355 int len = sizeof(cfg.remote_cmd) - 1;
356 char *cp = cfg.remote_cmd;
359 strncpy(cp, p, len); cp[len] = '\0';
360 len2 = strlen(cp); len -= len2; cp += len2;
364 strncpy(cp, *++argv, len); cp[len] = '\0';
365 len2 = strlen(cp); len -= len2; cp += len2;
367 cfg.nopty = TRUE; /* command => no terminal */
368 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
369 break; /* done with cmdline */
378 if (!*cfg.remote_cmd)
379 flags |= FLAG_INTERACTIVE;
382 * Select protocol. This is farmed out into a table in a
383 * separate file to enable an ssh-free variant.
388 for (i = 0; backends[i].backend != NULL; i++)
389 if (backends[i].protocol == cfg.protocol) {
390 back = backends[i].backend;
394 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
402 if (portnumber != -1)
403 cfg.port = portnumber;
406 * Initialise WinSock.
408 winsock_ver = MAKEWORD(2, 0);
409 if (WSAStartup(winsock_ver, &wsadata)) {
410 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
411 MB_OK | MB_ICONEXCLAMATION);
414 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
415 MessageBox(NULL, "WinSock version is incompatible with 2.0",
416 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
422 * Start up the connection.
428 error = back->init (NULL, cfg.host, cfg.port, &realhost);
430 fprintf(stderr, "Unable to open connection:\n%s", error);
435 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
436 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
438 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
439 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
440 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
443 * Now we must send the back end oodles of stuff.
445 socket = back->socket();
447 * Turn off ECHO and LINE input modes. We don't care if this
448 * call fails, because we know we aren't necessarily running in
451 WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
452 handles[0] = netevent;
453 handles[1] = stdinevent;
458 if (!sending && back->sendok()) {
460 * Create a separate thread to read from stdin. This is
461 * a total pain, but I can't find another way to do it:
463 * - an overlapped ReadFile or ReadFileEx just doesn't
464 * happen; we get failure from ReadFileEx, and
465 * ReadFile blocks despite being given an OVERLAPPED
466 * structure. Perhaps we can't do overlapped reads
467 * on consoles. WHY THE HELL NOT?
469 * - WaitForMultipleObjects(netevent, console) doesn't
470 * work, because it signals the console when
471 * _anything_ happens, including mouse motions and
472 * other things that don't cause data to be readable
473 * - so we're back to ReadFile blocking.
475 idata.event = stdinevent;
476 if (!CreateThread(NULL, 0, stdin_read_thread,
477 &idata, 0, &threadid)) {
478 fprintf(stderr, "Unable to create second thread\n");
484 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
486 WSANETWORKEVENTS things;
487 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
488 if (things.lNetworkEvents & FD_READ)
489 back->msg(0, FD_READ);
490 if (things.lNetworkEvents & FD_CLOSE) {
491 back->msg(0, FD_CLOSE);
498 back->send(idata.buffer, idata.len);
500 back->special(TS_EOF);