2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 #define MAX_STDIN_BACKLOG 4096
20 void fatalbox(char *p
, ...)
23 fprintf(stderr
, "FATAL ERROR: ");
25 vfprintf(stderr
, p
, ap
);
31 void connection_fatal(char *p
, ...)
34 fprintf(stderr
, "FATAL ERROR: ");
36 vfprintf(stderr
, p
, ap
);
43 static char *password
= NULL
;
45 void logevent(char *string
)
49 void verify_ssh_host_key(char *host
, int port
, char *keytype
,
50 char *keystr
, char *fingerprint
)
56 static const char absentmsg
[] =
57 "The server's host key is not cached in the registry. You\n"
58 "have no guarantee that the server is the computer you\n"
60 "The server's key fingerprint is:\n"
62 "If you trust this host, enter \"y\" to add the key to\n"
63 "PuTTY's cache and carry on connecting.\n"
64 "If you want to carry on connecting just once, without\n"
65 "adding the key to the cache, enter \"n\".\n"
66 "If you do not trust this host, press Return to abandon the\n"
68 "Store key in cache? (y/n) ";
70 static const char wrongmsg
[] =
71 "WARNING - POTENTIAL SECURITY BREACH!\n"
72 "The server's host key does not match the one PuTTY has\n"
73 "cached in the registry. This means that either the\n"
74 "server administrator has changed the host key, or you\n"
75 "have actually connected to another computer pretending\n"
77 "The new key fingerprint is:\n"
79 "If you were expecting this change and trust the new key,\n"
80 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
81 "If you want to carry on connecting but without updating\n"
82 "the cache, enter \"n\".\n"
83 "If you want to abandon the connection completely, press\n"
84 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
86 "Update cached key? (y/n, Return cancels connection) ";
88 static const char abandoned
[] = "Connection abandoned.\n";
93 * Verify the key against the registry.
95 ret
= verify_host_key(host
, port
, keytype
, keystr
);
97 if (ret
== 0) /* success - key matched OK */
100 if (ret
== 2) { /* key was different */
101 fprintf(stderr
, wrongmsg
, fingerprint
);
104 if (ret
== 1) { /* key was absent */
105 fprintf(stderr
, absentmsg
, fingerprint
);
109 hin
= GetStdHandle(STD_INPUT_HANDLE
);
110 GetConsoleMode(hin
, &savemode
);
111 SetConsoleMode(hin
, (savemode
| ENABLE_ECHO_INPUT
|
112 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
));
113 ReadFile(hin
, line
, sizeof(line
) - 1, &i
, NULL
);
114 SetConsoleMode(hin
, savemode
);
116 if (line
[0] != '\0' && line
[0] != '\r' && line
[0] != '\n') {
117 if (line
[0] == 'y' || line
[0] == 'Y')
118 store_host_key(host
, port
, keytype
, keystr
);
120 fprintf(stderr
, abandoned
);
126 * Ask whether the selected cipher is acceptable (since it was
127 * below the configured 'warn' threshold).
128 * cs: 0 = both ways, 1 = client->server, 2 = server->client
130 void askcipher(char *ciphername
, int cs
)
135 static const char msg
[] =
136 "The first %scipher supported by the server is\n"
137 "%s, which is below the configured warning threshold.\n"
138 "Continue with connection? (y/n) ";
139 static const char abandoned
[] = "Connection abandoned.\n";
145 (cs
== 1) ?
"client-to-server " :
150 hin
= GetStdHandle(STD_INPUT_HANDLE
);
151 GetConsoleMode(hin
, &savemode
);
152 SetConsoleMode(hin
, (savemode
| ENABLE_ECHO_INPUT
|
153 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
));
154 ReadFile(hin
, line
, sizeof(line
) - 1, &i
, NULL
);
155 SetConsoleMode(hin
, savemode
);
157 if (line
[0] == 'y' || line
[0] == 'Y') {
160 fprintf(stderr
, abandoned
);
165 HANDLE inhandle
, outhandle
, errhandle
;
166 DWORD orig_console_mode
;
170 int term_ldisc(int mode
)
174 void ldisc_update(int echo
, int edit
)
176 /* Update stdin read mode to reflect changes in line discipline. */
179 mode
= ENABLE_PROCESSED_INPUT
;
181 mode
= mode
| ENABLE_ECHO_INPUT
;
183 mode
= mode
& ~ENABLE_ECHO_INPUT
;
185 mode
= mode
| ENABLE_LINE_INPUT
;
187 mode
= mode
& ~ENABLE_LINE_INPUT
;
188 SetConsoleMode(inhandle
, mode
);
191 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
194 DWORD savemode
, newmode
, i
;
196 if (is_pw
&& password
) {
197 static int tried_once
= 0;
202 strncpy(str
, password
, maxlen
);
203 str
[maxlen
- 1] = '\0';
209 hin
= GetStdHandle(STD_INPUT_HANDLE
);
210 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
211 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
212 fprintf(stderr
, "Cannot get standard input/output handles");
216 GetConsoleMode(hin
, &savemode
);
217 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
219 newmode
&= ~ENABLE_ECHO_INPUT
;
221 newmode
|= ENABLE_ECHO_INPUT
;
222 SetConsoleMode(hin
, newmode
);
224 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
225 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
227 SetConsoleMode(hin
, savemode
);
229 if ((int) i
> maxlen
)
236 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
244 HANDLE event
, eventback
;
247 static DWORD WINAPI
stdin_read_thread(void *param
)
249 struct input_data
*idata
= (struct input_data
*) param
;
252 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
254 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
255 &idata
->len
, NULL
) && idata
->len
> 0) {
256 SetEvent(idata
->event
);
257 WaitForSingleObject(idata
->eventback
, INFINITE
);
261 SetEvent(idata
->event
);
267 DWORD len
, lenwritten
;
271 HANDLE event
, eventback
;
275 static DWORD WINAPI
stdout_write_thread(void *param
)
277 struct output_data
*odata
= (struct output_data
*) param
;
278 HANDLE outhandle
, errhandle
;
280 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
281 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
284 WaitForSingleObject(odata
->eventback
, INFINITE
);
288 WriteFile(odata
->is_stderr ? errhandle
: outhandle
,
289 odata
->buffer
, odata
->len
, &odata
->lenwritten
, NULL
);
290 SetEvent(odata
->event
);
296 bufchain stdout_data
, stderr_data
;
297 struct output_data odata
, edata
;
299 void try_output(int is_stderr
)
301 struct output_data
*data
= (is_stderr ?
&edata
: &odata
);
306 bufchain_prefix(is_stderr ?
&stderr_data
: &stdout_data
,
307 &senddata
, &sendlen
);
308 data
->buffer
= senddata
;
310 SetEvent(data
->eventback
);
315 int from_backend(int is_stderr
, char *data
, int len
)
319 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
325 bufchain_add(&stderr_data
, data
, len
);
328 bufchain_add(&stdout_data
, data
, len
);
332 osize
= bufchain_size(&stdout_data
);
333 esize
= bufchain_size(&stderr_data
);
335 return osize
+ esize
;
339 * Short description of parameters.
341 static void usage(void)
343 printf("PuTTY Link: command-line connection utility\n");
345 printf("Usage: plink [options] [user@]host [command]\n");
346 printf("Options:\n");
347 printf(" -v show verbose messages\n");
348 printf(" -ssh force use of ssh protocol\n");
349 printf(" -P port connect to specified port\n");
350 printf(" -pw passw login with specified password\n");
351 printf(" -m file read remote command(s) from file\n");
355 char *do_select(SOCKET skt
, int startup
)
359 events
= FD_READ
| FD_WRITE
| FD_OOB
| FD_CLOSE
| FD_ACCEPT
;
363 if (WSAEventSelect(skt
, netevent
, events
) == SOCKET_ERROR
) {
364 switch (WSAGetLastError()) {
366 return "Network is down";
368 return "WSAAsyncSelect(): unknown error";
374 int main(int argc
, char **argv
)
378 WSAEVENT stdinevent
, stdoutevent
, stderrevent
;
380 DWORD in_threadid
, out_threadid
, err_threadid
;
381 struct input_data idata
;
389 ssh_get_line
= get_line
;
392 skcount
= sksize
= 0;
394 * Initialise port and protocol to sensible defaults. (These
395 * will be overridden by more or less anything.)
397 default_protocol
= PROT_SSH
;
402 * Process the command line.
404 do_defaults(NULL
, &cfg
);
405 default_protocol
= cfg
.protocol
;
406 default_port
= cfg
.port
;
409 * Override the default protocol if PLINK_PROTOCOL is set.
411 char *p
= getenv("PLINK_PROTOCOL");
414 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
415 if (!strcmp(backends
[i
].name
, p
)) {
416 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
417 default_port
= cfg
.port
=
418 backends
[i
].backend
->default_port
;
427 if (!strcmp(p
, "-ssh")) {
428 default_protocol
= cfg
.protocol
= PROT_SSH
;
429 default_port
= cfg
.port
= 22;
430 } else if (!strcmp(p
, "-telnet")) {
431 default_protocol
= cfg
.protocol
= PROT_TELNET
;
432 default_port
= cfg
.port
= 23;
433 } else if (!strcmp(p
, "-raw")) {
434 default_protocol
= cfg
.protocol
= PROT_RAW
;
435 } else if (!strcmp(p
, "-v")) {
436 flags
|= FLAG_VERBOSE
;
437 } else if (!strcmp(p
, "-log")) {
438 logfile
= "putty.log";
439 } else if (!strcmp(p
, "-pw") && argc
> 1) {
440 --argc
, password
= *++argv
;
441 } else if (!strcmp(p
, "-l") && argc
> 1) {
443 --argc
, username
= *++argv
;
444 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
445 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
446 } else if (!strcmp(p
, "-m") && argc
> 1) {
447 char *filename
, *command
;
452 --argc
, filename
= *++argv
;
454 cmdlen
= cmdsize
= 0;
456 fp
= fopen(filename
, "r");
458 fprintf(stderr
, "plink: unable to open command "
459 "file \"%s\"\n", filename
);
467 if (cmdlen
>= cmdsize
) {
468 cmdsize
= cmdlen
+ 512;
469 command
= srealloc(command
, cmdsize
);
471 command
[cmdlen
++] = d
;
473 cfg
.remote_cmd_ptr
= command
;
474 cfg
.nopty
= TRUE
; /* command => no terminal */
475 } else if (!strcmp(p
, "-P") && argc
> 1) {
476 --argc
, portnumber
= atoi(*++argv
);
482 * If the hostname starts with "telnet:", set the
483 * protocol to Telnet and process the string as a
486 if (!strncmp(q
, "telnet:", 7)) {
490 if (q
[0] == '/' && q
[1] == '/')
492 cfg
.protocol
= PROT_TELNET
;
494 while (*p
&& *p
!= ':' && *p
!= '/')
503 strncpy(cfg
.host
, q
, sizeof(cfg
.host
) - 1);
504 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
508 * Before we process the [user@]host string, we
509 * first check for the presence of a protocol
510 * prefix (a protocol name followed by ",").
515 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
516 j
= strlen(backends
[i
].name
);
518 !memcmp(backends
[i
].name
, p
, j
)) {
519 default_protocol
= cfg
.protocol
=
520 backends
[i
].protocol
;
522 backends
[i
].backend
->default_port
;
530 * Three cases. Either (a) there's a nonzero
531 * length string followed by an @, in which
532 * case that's user and the remainder is host.
533 * Or (b) there's only one string, not counting
534 * a potential initial @, and it exists in the
535 * saved-sessions database. Or (c) only one
536 * string and it _doesn't_ exist in the
541 p
++, r
= NULL
; /* discount initial @ */
547 do_defaults(p
, &cfg2
);
548 if (cfg2
.host
[0] == '\0') {
549 /* No settings for this host; use defaults */
550 strncpy(cfg
.host
, p
, sizeof(cfg
.host
) - 1);
551 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
552 cfg
.port
= default_port
;
555 cfg
.remote_cmd_ptr
= cfg
.remote_cmd
;
559 strncpy(cfg
.username
, p
, sizeof(cfg
.username
) - 1);
560 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
561 strncpy(cfg
.host
, r
, sizeof(cfg
.host
) - 1);
562 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
563 cfg
.port
= default_port
;
567 int len
= sizeof(cfg
.remote_cmd
) - 1;
568 char *cp
= cfg
.remote_cmd
;
579 strncpy(cp
, *++argv
, len
);
585 cfg
.nopty
= TRUE
; /* command => no terminal */
586 break; /* done with cmdline */
595 if (!*cfg
.remote_cmd_ptr
)
596 flags
|= FLAG_INTERACTIVE
;
599 * Select protocol. This is farmed out into a table in a
600 * separate file to enable an ssh-free variant.
605 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
606 if (backends
[i
].protocol
== cfg
.protocol
) {
607 back
= backends
[i
].backend
;
612 "Internal fault: Unsupported protocol found\n");
620 if (portnumber
!= -1)
621 cfg
.port
= portnumber
;
624 * Initialise WinSock.
626 winsock_ver
= MAKEWORD(2, 0);
627 if (WSAStartup(winsock_ver
, &wsadata
)) {
628 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
629 MB_OK
| MB_ICONEXCLAMATION
);
632 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
633 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
634 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
641 * Start up the connection.
643 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
648 error
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
650 fprintf(stderr
, "Unable to open connection:\n%s", error
);
657 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
658 stdoutevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
659 stderrevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
661 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
662 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
663 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
664 GetConsoleMode(inhandle
, &orig_console_mode
);
665 SetConsoleMode(inhandle
, ENABLE_PROCESSED_INPUT
);
668 * Turn off ECHO and LINE input modes. We don't care if this
669 * call fails, because we know we aren't necessarily running in
672 handles
[0] = netevent
;
673 handles
[1] = stdinevent
;
674 handles
[2] = stdoutevent
;
675 handles
[3] = stderrevent
;
679 * Create spare threads to write to stdout and stderr, so we
680 * can arrange asynchronous writes.
682 odata
.event
= stdoutevent
;
683 odata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
685 odata
.busy
= odata
.done
= 0;
686 if (!CreateThread(NULL
, 0, stdout_write_thread
,
687 &odata
, 0, &out_threadid
)) {
688 fprintf(stderr
, "Unable to create output thread\n");
691 edata
.event
= stderrevent
;
692 edata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
694 edata
.busy
= edata
.done
= 0;
695 if (!CreateThread(NULL
, 0, stdout_write_thread
,
696 &edata
, 0, &err_threadid
)) {
697 fprintf(stderr
, "Unable to create error output thread\n");
704 if (!sending
&& back
->sendok()) {
706 * Create a separate thread to read from stdin. This is
707 * a total pain, but I can't find another way to do it:
709 * - an overlapped ReadFile or ReadFileEx just doesn't
710 * happen; we get failure from ReadFileEx, and
711 * ReadFile blocks despite being given an OVERLAPPED
712 * structure. Perhaps we can't do overlapped reads
713 * on consoles. WHY THE HELL NOT?
715 * - WaitForMultipleObjects(netevent, console) doesn't
716 * work, because it signals the console when
717 * _anything_ happens, including mouse motions and
718 * other things that don't cause data to be readable
719 * - so we're back to ReadFile blocking.
721 idata
.event
= stdinevent
;
722 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
723 if (!CreateThread(NULL
, 0, stdin_read_thread
,
724 &idata
, 0, &in_threadid
)) {
725 fprintf(stderr
, "Unable to create input thread\n");
731 n
= WaitForMultipleObjects(4, handles
, FALSE
, INFINITE
);
733 WSANETWORKEVENTS things
;
735 extern SOCKET
first_socket(int *), next_socket(int *);
736 extern int select_result(WPARAM
, LPARAM
);
740 * We must not call select_result() for any socket
741 * until we have finished enumerating within the tree.
742 * This is because select_result() may close the socket
743 * and modify the tree.
745 /* Count the active sockets. */
747 for (socket
= first_socket(&socketstate
);
748 socket
!= INVALID_SOCKET
;
749 socket
= next_socket(&socketstate
)) i
++;
751 /* Expand the buffer if necessary. */
754 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
757 /* Retrieve the sockets into sklist. */
759 for (socket
= first_socket(&socketstate
);
760 socket
!= INVALID_SOCKET
;
761 socket
= next_socket(&socketstate
)) {
762 sklist
[skcount
++] = socket
;
765 /* Now we're done enumerating; go through the list. */
766 for (i
= 0; i
< skcount
; i
++) {
769 wp
= (WPARAM
) socket
;
770 if (!WSAEnumNetworkEvents(socket
, NULL
, &things
)) {
771 noise_ultralight(socket
);
772 noise_ultralight(things
.lNetworkEvents
);
773 if (things
.lNetworkEvents
& FD_READ
)
774 connopen
&= select_result(wp
, (LPARAM
) FD_READ
);
775 if (things
.lNetworkEvents
& FD_CLOSE
)
776 connopen
&= select_result(wp
, (LPARAM
) FD_CLOSE
);
777 if (things
.lNetworkEvents
& FD_OOB
)
778 connopen
&= select_result(wp
, (LPARAM
) FD_OOB
);
779 if (things
.lNetworkEvents
& FD_WRITE
)
780 connopen
&= select_result(wp
, (LPARAM
) FD_WRITE
);
781 if (things
.lNetworkEvents
& FD_ACCEPT
)
782 connopen
&= select_result(wp
, (LPARAM
) FD_ACCEPT
);
788 noise_ultralight(idata
.len
);
790 back
->send(idata
.buffer
, idata
.len
);
792 back
->special(TS_EOF
);
796 if (!odata
.writeret
) {
797 fprintf(stderr
, "Unable to write to standard output\n");
800 bufchain_consume(&stdout_data
, odata
.lenwritten
);
801 if (bufchain_size(&stdout_data
) > 0)
803 back
->unthrottle(bufchain_size(&stdout_data
) +
804 bufchain_size(&stderr_data
));
807 if (!edata
.writeret
) {
808 fprintf(stderr
, "Unable to write to standard output\n");
811 bufchain_consume(&stderr_data
, edata
.lenwritten
);
812 if (bufchain_size(&stderr_data
) > 0)
814 back
->unthrottle(bufchain_size(&stdout_data
) +
815 bufchain_size(&stderr_data
));
817 if (!reading
&& back
->sendbuffer() < MAX_STDIN_BACKLOG
) {
818 SetEvent(idata
.eventback
);
821 if (!connopen
|| back
->socket() == NULL
)
822 break; /* we closed the connection */