42171169970cc5257b1c20e84792017269967ae4
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
);
125 HANDLE inhandle
, outhandle
, errhandle
;
126 DWORD orig_console_mode
;
130 int term_ldisc(int mode
)
134 void ldisc_update(int echo
, int edit
)
136 /* Update stdin read mode to reflect changes in line discipline. */
139 mode
= ENABLE_PROCESSED_INPUT
;
141 mode
= mode
| ENABLE_ECHO_INPUT
;
143 mode
= mode
& ~ENABLE_ECHO_INPUT
;
145 mode
= mode
| ENABLE_LINE_INPUT
;
147 mode
= mode
& ~ENABLE_LINE_INPUT
;
148 SetConsoleMode(inhandle
, mode
);
151 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
154 DWORD savemode
, newmode
, i
;
156 if (is_pw
&& password
) {
157 static int tried_once
= 0;
162 strncpy(str
, password
, maxlen
);
163 str
[maxlen
- 1] = '\0';
169 hin
= GetStdHandle(STD_INPUT_HANDLE
);
170 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
171 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
172 fprintf(stderr
, "Cannot get standard input/output handles");
176 GetConsoleMode(hin
, &savemode
);
177 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
179 newmode
&= ~ENABLE_ECHO_INPUT
;
181 newmode
|= ENABLE_ECHO_INPUT
;
182 SetConsoleMode(hin
, newmode
);
184 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
185 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
187 SetConsoleMode(hin
, savemode
);
189 if ((int) i
> maxlen
)
196 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
204 HANDLE event
, eventback
;
207 static DWORD WINAPI
stdin_read_thread(void *param
)
209 struct input_data
*idata
= (struct input_data
*) param
;
212 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
214 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
215 &idata
->len
, NULL
) && idata
->len
> 0) {
216 SetEvent(idata
->event
);
217 WaitForSingleObject(idata
->eventback
, INFINITE
);
221 SetEvent(idata
->event
);
227 DWORD len
, lenwritten
;
231 HANDLE event
, eventback
;
235 static DWORD WINAPI
stdout_write_thread(void *param
)
237 struct output_data
*odata
= (struct output_data
*) param
;
238 HANDLE outhandle
, errhandle
;
240 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
241 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
244 WaitForSingleObject(odata
->eventback
, INFINITE
);
248 WriteFile(odata
->is_stderr ? errhandle
: outhandle
,
249 odata
->buffer
, odata
->len
, &odata
->lenwritten
, NULL
);
250 SetEvent(odata
->event
);
256 bufchain stdout_data
, stderr_data
;
257 struct output_data odata
, edata
;
259 void try_output(int is_stderr
)
261 struct output_data
*data
= (is_stderr ?
&edata
: &odata
);
266 bufchain_prefix(is_stderr ?
&stderr_data
: &stdout_data
,
267 &senddata
, &sendlen
);
268 data
->buffer
= senddata
;
270 SetEvent(data
->eventback
);
275 int from_backend(int is_stderr
, char *data
, int len
)
279 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
285 bufchain_add(&stderr_data
, data
, len
);
288 bufchain_add(&stdout_data
, data
, len
);
292 osize
= bufchain_size(&stdout_data
);
293 esize
= bufchain_size(&stderr_data
);
295 return osize
+ esize
;
299 * Short description of parameters.
301 static void usage(void)
303 printf("PuTTY Link: command-line connection utility\n");
305 printf("Usage: plink [options] [user@]host [command]\n");
306 printf("Options:\n");
307 printf(" -v show verbose messages\n");
308 printf(" -ssh force use of ssh protocol\n");
309 printf(" -P port connect to specified port\n");
310 printf(" -pw passw login with specified password\n");
311 printf(" -m file read remote command(s) from file\n");
315 char *do_select(SOCKET skt
, int startup
)
319 events
= FD_READ
| FD_WRITE
| FD_OOB
| FD_CLOSE
| FD_ACCEPT
;
323 if (WSAEventSelect(skt
, netevent
, events
) == SOCKET_ERROR
) {
324 switch (WSAGetLastError()) {
326 return "Network is down";
328 return "WSAAsyncSelect(): unknown error";
334 int main(int argc
, char **argv
)
338 WSAEVENT stdinevent
, stdoutevent
, stderrevent
;
340 DWORD in_threadid
, out_threadid
, err_threadid
;
341 struct input_data idata
;
349 ssh_get_line
= get_line
;
352 skcount
= sksize
= 0;
354 * Initialise port and protocol to sensible defaults. (These
355 * will be overridden by more or less anything.)
357 default_protocol
= PROT_SSH
;
362 * Process the command line.
364 do_defaults(NULL
, &cfg
);
365 default_protocol
= cfg
.protocol
;
366 default_port
= cfg
.port
;
369 * Override the default protocol if PLINK_PROTOCOL is set.
371 char *p
= getenv("PLINK_PROTOCOL");
374 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
375 if (!strcmp(backends
[i
].name
, p
)) {
376 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
377 default_port
= cfg
.port
=
378 backends
[i
].backend
->default_port
;
387 if (!strcmp(p
, "-ssh")) {
388 default_protocol
= cfg
.protocol
= PROT_SSH
;
389 default_port
= cfg
.port
= 22;
390 } else if (!strcmp(p
, "-telnet")) {
391 default_protocol
= cfg
.protocol
= PROT_TELNET
;
392 default_port
= cfg
.port
= 23;
393 } else if (!strcmp(p
, "-raw")) {
394 default_protocol
= cfg
.protocol
= PROT_RAW
;
395 } else if (!strcmp(p
, "-v")) {
396 flags
|= FLAG_VERBOSE
;
397 } else if (!strcmp(p
, "-log")) {
398 logfile
= "putty.log";
399 } else if (!strcmp(p
, "-pw") && argc
> 1) {
400 --argc
, password
= *++argv
;
401 } else if (!strcmp(p
, "-l") && argc
> 1) {
403 --argc
, username
= *++argv
;
404 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
405 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
406 } else if (!strcmp(p
, "-m") && argc
> 1) {
407 char *filename
, *command
;
412 --argc
, filename
= *++argv
;
414 cmdlen
= cmdsize
= 0;
416 fp
= fopen(filename
, "r");
418 fprintf(stderr
, "plink: unable to open command "
419 "file \"%s\"\n", filename
);
427 if (cmdlen
>= cmdsize
) {
428 cmdsize
= cmdlen
+ 512;
429 command
= srealloc(command
, cmdsize
);
431 command
[cmdlen
++] = d
;
433 cfg
.remote_cmd_ptr
= command
;
434 cfg
.nopty
= TRUE
; /* command => no terminal */
435 } else if (!strcmp(p
, "-P") && argc
> 1) {
436 --argc
, portnumber
= atoi(*++argv
);
442 * If the hostname starts with "telnet:", set the
443 * protocol to Telnet and process the string as a
446 if (!strncmp(q
, "telnet:", 7)) {
450 if (q
[0] == '/' && q
[1] == '/')
452 cfg
.protocol
= PROT_TELNET
;
454 while (*p
&& *p
!= ':' && *p
!= '/')
463 strncpy(cfg
.host
, q
, sizeof(cfg
.host
) - 1);
464 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
468 * Before we process the [user@]host string, we
469 * first check for the presence of a protocol
470 * prefix (a protocol name followed by ",").
475 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
476 j
= strlen(backends
[i
].name
);
478 !memcmp(backends
[i
].name
, p
, j
)) {
479 default_protocol
= cfg
.protocol
=
480 backends
[i
].protocol
;
482 backends
[i
].backend
->default_port
;
490 * Three cases. Either (a) there's a nonzero
491 * length string followed by an @, in which
492 * case that's user and the remainder is host.
493 * Or (b) there's only one string, not counting
494 * a potential initial @, and it exists in the
495 * saved-sessions database. Or (c) only one
496 * string and it _doesn't_ exist in the
501 p
++, r
= NULL
; /* discount initial @ */
507 do_defaults(p
, &cfg2
);
508 if (cfg2
.host
[0] == '\0') {
509 /* No settings for this host; use defaults */
510 strncpy(cfg
.host
, p
, sizeof(cfg
.host
) - 1);
511 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
512 cfg
.port
= default_port
;
515 cfg
.remote_cmd_ptr
= cfg
.remote_cmd
;
519 strncpy(cfg
.username
, p
, sizeof(cfg
.username
) - 1);
520 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
521 strncpy(cfg
.host
, r
, sizeof(cfg
.host
) - 1);
522 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
523 cfg
.port
= default_port
;
527 int len
= sizeof(cfg
.remote_cmd
) - 1;
528 char *cp
= cfg
.remote_cmd
;
539 strncpy(cp
, *++argv
, len
);
545 cfg
.nopty
= TRUE
; /* command => no terminal */
546 break; /* done with cmdline */
555 if (!*cfg
.remote_cmd_ptr
)
556 flags
|= FLAG_INTERACTIVE
;
559 * Select protocol. This is farmed out into a table in a
560 * separate file to enable an ssh-free variant.
565 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
566 if (backends
[i
].protocol
== cfg
.protocol
) {
567 back
= backends
[i
].backend
;
572 "Internal fault: Unsupported protocol found\n");
580 if (portnumber
!= -1)
581 cfg
.port
= portnumber
;
584 * Initialise WinSock.
586 winsock_ver
= MAKEWORD(2, 0);
587 if (WSAStartup(winsock_ver
, &wsadata
)) {
588 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
589 MB_OK
| MB_ICONEXCLAMATION
);
592 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
593 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
594 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
601 * Start up the connection.
603 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
608 error
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
610 fprintf(stderr
, "Unable to open connection:\n%s", error
);
617 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
618 stdoutevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
619 stderrevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
621 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
622 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
623 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
624 GetConsoleMode(inhandle
, &orig_console_mode
);
625 SetConsoleMode(inhandle
, ENABLE_PROCESSED_INPUT
);
628 * Turn off ECHO and LINE input modes. We don't care if this
629 * call fails, because we know we aren't necessarily running in
632 handles
[0] = netevent
;
633 handles
[1] = stdinevent
;
634 handles
[2] = stdoutevent
;
635 handles
[3] = stderrevent
;
639 * Create spare threads to write to stdout and stderr, so we
640 * can arrange asynchronous writes.
642 odata
.event
= stdoutevent
;
643 odata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
645 odata
.busy
= odata
.done
= 0;
646 if (!CreateThread(NULL
, 0, stdout_write_thread
,
647 &odata
, 0, &out_threadid
)) {
648 fprintf(stderr
, "Unable to create output thread\n");
651 edata
.event
= stderrevent
;
652 edata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
654 edata
.busy
= edata
.done
= 0;
655 if (!CreateThread(NULL
, 0, stdout_write_thread
,
656 &edata
, 0, &err_threadid
)) {
657 fprintf(stderr
, "Unable to create error output thread\n");
664 if (!sending
&& back
->sendok()) {
666 * Create a separate thread to read from stdin. This is
667 * a total pain, but I can't find another way to do it:
669 * - an overlapped ReadFile or ReadFileEx just doesn't
670 * happen; we get failure from ReadFileEx, and
671 * ReadFile blocks despite being given an OVERLAPPED
672 * structure. Perhaps we can't do overlapped reads
673 * on consoles. WHY THE HELL NOT?
675 * - WaitForMultipleObjects(netevent, console) doesn't
676 * work, because it signals the console when
677 * _anything_ happens, including mouse motions and
678 * other things that don't cause data to be readable
679 * - so we're back to ReadFile blocking.
681 idata
.event
= stdinevent
;
682 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
683 if (!CreateThread(NULL
, 0, stdin_read_thread
,
684 &idata
, 0, &in_threadid
)) {
685 fprintf(stderr
, "Unable to create input thread\n");
691 n
= WaitForMultipleObjects(4, handles
, FALSE
, INFINITE
);
693 WSANETWORKEVENTS things
;
695 extern SOCKET
first_socket(int *), next_socket(int *);
696 extern int select_result(WPARAM
, LPARAM
);
700 * We must not call select_result() for any socket
701 * until we have finished enumerating within the tree.
702 * This is because select_result() may close the socket
703 * and modify the tree.
705 /* Count the active sockets. */
707 for (socket
= first_socket(&socketstate
);
708 socket
!= INVALID_SOCKET
;
709 socket
= next_socket(&socketstate
)) i
++;
711 /* Expand the buffer if necessary. */
714 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
717 /* Retrieve the sockets into sklist. */
719 for (socket
= first_socket(&socketstate
);
720 socket
!= INVALID_SOCKET
;
721 socket
= next_socket(&socketstate
)) {
722 sklist
[skcount
++] = socket
;
725 /* Now we're done enumerating; go through the list. */
726 for (i
= 0; i
< skcount
; i
++) {
729 wp
= (WPARAM
) socket
;
730 if (!WSAEnumNetworkEvents(socket
, NULL
, &things
)) {
731 noise_ultralight(socket
);
732 noise_ultralight(things
.lNetworkEvents
);
733 if (things
.lNetworkEvents
& FD_READ
)
734 connopen
&= select_result(wp
, (LPARAM
) FD_READ
);
735 if (things
.lNetworkEvents
& FD_CLOSE
)
736 connopen
&= select_result(wp
, (LPARAM
) FD_CLOSE
);
737 if (things
.lNetworkEvents
& FD_OOB
)
738 connopen
&= select_result(wp
, (LPARAM
) FD_OOB
);
739 if (things
.lNetworkEvents
& FD_WRITE
)
740 connopen
&= select_result(wp
, (LPARAM
) FD_WRITE
);
741 if (things
.lNetworkEvents
& FD_ACCEPT
)
742 connopen
&= select_result(wp
, (LPARAM
) FD_ACCEPT
);
748 noise_ultralight(idata
.len
);
750 back
->send(idata
.buffer
, idata
.len
);
752 back
->special(TS_EOF
);
756 if (!odata
.writeret
) {
757 fprintf(stderr
, "Unable to write to standard output\n");
760 bufchain_consume(&stdout_data
, odata
.lenwritten
);
761 if (bufchain_size(&stdout_data
) > 0)
763 back
->unthrottle(bufchain_size(&stdout_data
) +
764 bufchain_size(&stderr_data
));
767 if (!edata
.writeret
) {
768 fprintf(stderr
, "Unable to write to standard output\n");
771 bufchain_consume(&stderr_data
, edata
.lenwritten
);
772 if (bufchain_size(&stderr_data
) > 0)
774 back
->unthrottle(bufchain_size(&stdout_data
) +
775 bufchain_size(&stderr_data
));
777 if (!reading
&& back
->sendbuffer() < MAX_STDIN_BACKLOG
) {
778 SetEvent(idata
.eventback
);
781 if (!connopen
|| back
->socket() == NULL
)
782 break; /* we closed the connection */