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
);
121 HANDLE outhandle
, errhandle
;
122 DWORD orig_console_mode
;
126 void begin_session(void) {
128 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_PROCESSED_INPUT
);
130 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), orig_console_mode
);
133 void from_backend(int is_stderr
, char *data
, int len
) {
136 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
140 if (!WriteFile(h
, data
+pos
, len
-pos
, &ret
, NULL
))
141 return; /* give up in panic */
149 HANDLE event
, eventback
;
152 static int get_password(const char *prompt
, char *str
, int maxlen
)
158 static int tried_once
= 0;
163 strncpy(str
, password
, maxlen
);
164 str
[maxlen
-1] = '\0';
170 hin
= GetStdHandle(STD_INPUT_HANDLE
);
171 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
172 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
173 fprintf(stderr
, "Cannot get standard input/output handles");
177 GetConsoleMode(hin
, &savemode
);
178 SetConsoleMode(hin
, (savemode
& (~ENABLE_ECHO_INPUT
)) |
179 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
);
181 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
182 ReadFile(hin
, str
, maxlen
-1, &i
, NULL
);
184 SetConsoleMode(hin
, savemode
);
186 if ((int)i
> maxlen
) i
= maxlen
-1; else i
= i
- 2;
189 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
194 static DWORD WINAPI
stdin_read_thread(void *param
) {
195 struct input_data
*idata
= (struct input_data
*)param
;
198 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
200 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
201 &idata
->len
, NULL
) && idata
->len
> 0) {
202 SetEvent(idata
->event
);
203 WaitForSingleObject(idata
->eventback
, INFINITE
);
207 SetEvent(idata
->event
);
213 * Short description of parameters.
215 static void usage(void)
217 printf("PuTTY Link: command-line connection utility\n");
219 printf("Usage: plink [options] [user@]host [command]\n");
220 printf("Options:\n");
221 printf(" -v show verbose messages\n");
222 printf(" -ssh force use of ssh protocol\n");
223 printf(" -P port connect to specified port\n");
224 printf(" -pw passw login with specified password\n");
228 char *do_select(SOCKET skt
, int startup
) {
231 events
= FD_READ
| FD_WRITE
| FD_OOB
| FD_CLOSE
;
235 if (WSAEventSelect (skt
, netevent
, events
) == SOCKET_ERROR
) {
236 switch (WSAGetLastError()) {
237 case WSAENETDOWN
: return "Network is down";
238 default: return "WSAAsyncSelect(): unknown error";
244 int main(int argc
, char **argv
) {
250 struct input_data idata
;
257 ssh_get_password
= get_password
;
259 sklist
= NULL
; skcount
= sksize
= 0;
263 * Process the command line.
265 do_defaults(NULL
, &cfg
);
266 default_protocol
= cfg
.protocol
;
267 default_port
= cfg
.port
;
270 * Override the default protocol if PLINK_PROTOCOL is set.
272 char *p
= getenv("PLINK_PROTOCOL");
275 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
276 if (!strcmp(backends
[i
].name
, p
)) {
277 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
278 default_port
= cfg
.port
= backends
[i
].backend
->default_port
;
287 if (!strcmp(p
, "-ssh")) {
288 default_protocol
= cfg
.protocol
= PROT_SSH
;
289 default_port
= cfg
.port
= 22;
290 } else if (!strcmp(p
, "-telnet")) {
291 default_protocol
= cfg
.protocol
= PROT_TELNET
;
292 default_port
= cfg
.port
= 23;
293 } else if (!strcmp(p
, "-raw")) {
294 default_protocol
= cfg
.protocol
= PROT_RAW
;
295 } else if (!strcmp(p
, "-v")) {
296 flags
|= FLAG_VERBOSE
;
297 } else if (!strcmp(p
, "-log")) {
298 logfile
= "putty.log";
299 } else if (!strcmp(p
, "-pw") && argc
> 1) {
300 --argc
, password
= *++argv
;
301 } else if (!strcmp(p
, "-l") && argc
> 1) {
303 --argc
, username
= *++argv
;
304 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
305 cfg
.username
[sizeof(cfg
.username
)-1] = '\0';
306 } else if (!strcmp(p
, "-P") && argc
> 1) {
307 --argc
, portnumber
= atoi(*++argv
);
313 * If the hostname starts with "telnet:", set the
314 * protocol to Telnet and process the string as a
317 if (!strncmp(q
, "telnet:", 7)) {
321 if (q
[0] == '/' && q
[1] == '/')
323 cfg
.protocol
= PROT_TELNET
;
325 while (*p
&& *p
!= ':' && *p
!= '/') p
++;
333 strncpy (cfg
.host
, q
, sizeof(cfg
.host
)-1);
334 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
338 * Before we process the [user@]host string, we
339 * first check for the presence of a protocol
340 * prefix (a protocol name followed by ",").
345 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
346 j
= strlen(backends
[i
].name
);
348 !memcmp(backends
[i
].name
, p
, j
)) {
349 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
350 portnumber
= backends
[i
].backend
->default_port
;
358 * Three cases. Either (a) there's a nonzero
359 * length string followed by an @, in which
360 * case that's user and the remainder is host.
361 * Or (b) there's only one string, not counting
362 * a potential initial @, and it exists in the
363 * saved-sessions database. Or (c) only one
364 * string and it _doesn't_ exist in the
368 if (r
== p
) p
++, r
= NULL
; /* discount initial @ */
374 do_defaults (p
, &cfg2
);
375 if (cfg2
.host
[0] == '\0') {
376 /* No settings for this host; use defaults */
377 strncpy(cfg
.host
, p
, sizeof(cfg
.host
)-1);
378 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
384 strncpy(cfg
.username
, p
, sizeof(cfg
.username
)-1);
385 cfg
.username
[sizeof(cfg
.username
)-1] = '\0';
386 strncpy(cfg
.host
, r
, sizeof(cfg
.host
)-1);
387 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
392 int len
= sizeof(cfg
.remote_cmd
) - 1;
393 char *cp
= cfg
.remote_cmd
;
396 strncpy(cp
, p
, len
); cp
[len
] = '\0';
397 len2
= strlen(cp
); len
-= len2
; cp
+= len2
;
401 strncpy(cp
, *++argv
, len
); cp
[len
] = '\0';
402 len2
= strlen(cp
); len
-= len2
; cp
+= len2
;
404 cfg
.nopty
= TRUE
; /* command => no terminal */
405 cfg
.ldisc_term
= TRUE
; /* use stdin like a line buffer */
406 break; /* done with cmdline */
415 if (!*cfg
.remote_cmd
)
416 flags
|= FLAG_INTERACTIVE
;
419 * Select protocol. This is farmed out into a table in a
420 * separate file to enable an ssh-free variant.
425 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
426 if (backends
[i
].protocol
== cfg
.protocol
) {
427 back
= backends
[i
].backend
;
431 fprintf(stderr
, "Internal fault: Unsupported protocol found\n");
439 if (portnumber
!= -1)
440 cfg
.port
= portnumber
;
443 * Initialise WinSock.
445 winsock_ver
= MAKEWORD(2, 0);
446 if (WSAStartup(winsock_ver
, &wsadata
)) {
447 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
448 MB_OK
| MB_ICONEXCLAMATION
);
451 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
452 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
453 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
460 * Start up the connection.
462 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
467 error
= back
->init (cfg
.host
, cfg
.port
, &realhost
);
469 fprintf(stderr
, "Unable to open connection:\n%s", error
);
475 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
477 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &orig_console_mode
);
478 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_PROCESSED_INPUT
);
479 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
480 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
483 * Turn off ECHO and LINE input modes. We don't care if this
484 * call fails, because we know we aren't necessarily running in
487 handles
[0] = netevent
;
488 handles
[1] = stdinevent
;
493 if (!sending
&& back
->sendok()) {
495 * Create a separate thread to read from stdin. This is
496 * a total pain, but I can't find another way to do it:
498 * - an overlapped ReadFile or ReadFileEx just doesn't
499 * happen; we get failure from ReadFileEx, and
500 * ReadFile blocks despite being given an OVERLAPPED
501 * structure. Perhaps we can't do overlapped reads
502 * on consoles. WHY THE HELL NOT?
504 * - WaitForMultipleObjects(netevent, console) doesn't
505 * work, because it signals the console when
506 * _anything_ happens, including mouse motions and
507 * other things that don't cause data to be readable
508 * - so we're back to ReadFile blocking.
510 idata
.event
= stdinevent
;
511 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
512 if (!CreateThread(NULL
, 0, stdin_read_thread
,
513 &idata
, 0, &threadid
)) {
514 fprintf(stderr
, "Unable to create second thread\n");
520 n
= WaitForMultipleObjects(2, handles
, FALSE
, INFINITE
);
522 WSANETWORKEVENTS things
;
525 extern SOCKET
first_socket(enum234
*), next_socket(enum234
*);
526 extern int select_result(WPARAM
, LPARAM
);
530 * We must not call select_result() for any socket
531 * until we have finished enumerating within the tree.
532 * This is because select_result() may close the socket
533 * and modify the tree.
535 /* Count the active sockets. */
537 for (socket
= first_socket(&e
); socket
!= INVALID_SOCKET
;
538 socket
= next_socket(&e
))
541 /* Expand the buffer if necessary. */
544 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
547 /* Retrieve the sockets into sklist. */
549 for (socket
= first_socket(&e
); socket
!= INVALID_SOCKET
;
550 socket
= next_socket(&e
)) {
551 sklist
[skcount
++] = socket
;
554 /* Now we're done enumerating; go through the list. */
555 for (i
= 0; i
< skcount
; i
++) {
559 if (!WSAEnumNetworkEvents(socket
, netevent
, &things
)) {
560 noise_ultralight(socket
);
561 noise_ultralight(things
.lNetworkEvents
);
562 if (things
.lNetworkEvents
& FD_READ
)
563 connopen
&= select_result(wp
, (LPARAM
)FD_READ
);
564 if (things
.lNetworkEvents
& FD_CLOSE
)
565 connopen
&= select_result(wp
, (LPARAM
)FD_CLOSE
);
566 if (things
.lNetworkEvents
& FD_OOB
)
567 connopen
&= select_result(wp
, (LPARAM
)FD_OOB
);
568 if (things
.lNetworkEvents
& FD_WRITE
)
569 connopen
&= select_result(wp
, (LPARAM
)FD_WRITE
);
573 noise_ultralight(idata
.len
);
575 back
->send(idata
.buffer
, idata
.len
);
577 back
->special(TS_EOF
);
579 SetEvent(idata
.eventback
);
581 if (!connopen
|| back
->socket() == NULL
)
582 break; /* we closed the connection */