1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-26 09:42:25 +00:00
putty-source/windows/winplink.c
Simon Tatham ad0c502cef Refactor the LogContext type.
LogContext is now the owner of the logevent() function that back ends
and so forth are constantly calling. Previously, logevent was owned by
the Frontend, which would store the message into its list for the GUI
Event Log dialog (or print it to standard error, or whatever) and then
pass it _back_ to LogContext to write to the currently open log file.
Now it's the other way round: LogContext gets the message from the
back end first, writes it to its log file if it feels so inclined, and
communicates it back to the front end.

This means that lots of parts of the back end system no longer need to
have a pointer to a full-on Frontend; the only thing they needed it
for was logging, so now they just have a LogContext (which many of
them had to have anyway, e.g. for logging SSH packets or session
traffic).

LogContext itself also doesn't get a full Frontend pointer any more:
it now talks back to the front end via a little vtable of its own
called LogPolicy, which contains the method that passes Event Log
entries through, the old askappend() function that decides whether to
truncate a pre-existing log file, and an emergency function for
printing an especially prominent message if the log file can't be
created. One minor nice effect of this is that console and GUI apps
can implement that last function subtly differently, so that Unix
console apps can write it with a plain \n instead of the \r\n
(harmless but inelegant) that the old centralised implementation
generated.

One other consequence of this is that the LogContext has to be
provided to backend_init() so that it's available to backends from the
instant of creation, rather than being provided via a separate API
call a couple of function calls later, because backends have typically
started doing things that need logging (like making network
connections) before the call to backend_provide_logctx. Fortunately,
there's no case in the whole code base where we don't already have
logctx by the time we make a backend (so I don't actually remember why
I ever delayed providing one). So that shortens the backend API by one
function, which is always nice.

While I'm tidying up, I've also moved the printf-style logeventf() and
the handy logevent_and_free() into logging.c, instead of having copies
of them scattered around other places. This has also let me remove
some stub functions from a couple of outlying applications like
Pageant. Finally, I've removed the pointless "_tag" at the end of
LogContext's official struct name.
2018-10-10 21:50:50 +01:00

648 lines
19 KiB
C

/*
* PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#include "putty.h"
#include "storage.h"
#include "tree234.h"
#include "winsecur.h"
#define WM_AGENT_CALLBACK (WM_APP + 4)
struct agent_callback {
void (*callback)(void *, void *, int);
void *callback_ctx;
void *data;
int len;
};
void modalfatalbox(const char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
if (logctx) {
log_free(logctx);
logctx = NULL;
}
cleanup_exit(1);
}
void nonfatal(const char *p, ...)
{
va_list ap;
fprintf(stderr, "ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
}
void connection_fatal(Frontend *frontend, const char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
if (logctx) {
log_free(logctx);
logctx = NULL;
}
cleanup_exit(1);
}
void cmdline_error(const char *p, ...)
{
va_list ap;
fprintf(stderr, "plink: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
HANDLE inhandle, outhandle, errhandle;
struct handle *stdin_handle, *stdout_handle, *stderr_handle;
DWORD orig_console_mode;
WSAEVENT netevent;
static Backend *backend;
static Conf *conf;
int term_ldisc(Terminal *term, int mode)
{
return FALSE;
}
void frontend_echoedit_update(Frontend *frontend, int echo, int edit)
{
/* Update stdin read mode to reflect changes in line discipline. */
DWORD mode;
mode = ENABLE_PROCESSED_INPUT;
if (echo)
mode = mode | ENABLE_ECHO_INPUT;
else
mode = mode & ~ENABLE_ECHO_INPUT;
if (edit)
mode = mode | ENABLE_LINE_INPUT;
else
mode = mode & ~ENABLE_LINE_INPUT;
SetConsoleMode(inhandle, mode);
}
char *get_ttymode(Frontend *frontend, const char *mode) { return NULL; }
int from_backend(Frontend *frontend, int is_stderr,
const void *data, int len)
{
if (is_stderr) {
handle_write(stderr_handle, data, len);
} else {
handle_write(stdout_handle, data, len);
}
return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
}
int from_backend_eof(Frontend *frontend)
{
handle_write_eof(stdout_handle);
return FALSE; /* do not respond to incoming EOF with outgoing */
}
int get_userpass_input(prompts_t *p, bufchain *input)
{
int ret;
ret = cmdline_get_passwd_input(p);
if (ret == -1)
ret = console_get_userpass_input(p);
return ret;
}
static DWORD main_thread_id;
void agent_schedule_callback(void (*callback)(void *, void *, int),
void *callback_ctx, void *data, int len)
{
struct agent_callback *c = snew(struct agent_callback);
c->callback = callback;
c->callback_ctx = callback_ctx;
c->data = data;
c->len = len;
PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);
}
/*
* Short description of parameters.
*/
static void usage(void)
{
printf("Plink: command-line connection utility\n");
printf("%s\n", ver);
printf("Usage: plink [options] [user@]host [command]\n");
printf(" (\"host\" can also be a PuTTY saved session name)\n");
printf("Options:\n");
printf(" -V print version information and exit\n");
printf(" -pgpfp print PGP key fingerprints and exit\n");
printf(" -v show verbose messages\n");
printf(" -load sessname Load settings from saved session\n");
printf(" -ssh -telnet -rlogin -raw -serial\n");
printf(" force use of a particular protocol\n");
printf(" -P port connect to specified port\n");
printf(" -l user connect with specified username\n");
printf(" -batch disable all interactive prompts\n");
printf(" -proxycmd command\n");
printf(" use 'command' as local proxy\n");
printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
printf(" Specify the serial configuration (serial only)\n");
printf("The following options only apply to SSH connections:\n");
printf(" -pw passw login with specified password\n");
printf(" -D [listen-IP:]listen-port\n");
printf(" Dynamic SOCKS-based port forwarding\n");
printf(" -L [listen-IP:]listen-port:host:port\n");
printf(" Forward local port to remote address\n");
printf(" -R [listen-IP:]listen-port:host:port\n");
printf(" Forward remote port to local address\n");
printf(" -X -x enable / disable X11 forwarding\n");
printf(" -A -a enable / disable agent forwarding\n");
printf(" -t -T enable / disable pty allocation\n");
printf(" -1 -2 force use of particular protocol version\n");
printf(" -4 -6 force use of IPv4 or IPv6\n");
printf(" -C enable compression\n");
printf(" -i key private key file for user authentication\n");
printf(" -noagent disable use of Pageant\n");
printf(" -agent enable use of Pageant\n");
printf(" -noshare disable use of connection sharing\n");
printf(" -share enable use of connection sharing\n");
printf(" -hostkey aa:bb:cc:...\n");
printf(" manually specify a host key (may be repeated)\n");
printf(" -m file read remote command(s) from file\n");
printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
printf(" -N don't start a shell/command (SSH-2 only)\n");
printf(" -nc host:port\n");
printf(" open tunnel in place of session (SSH-2 only)\n");
printf(" -sshlog file\n");
printf(" -sshrawlog file\n");
printf(" log protocol details to a file\n");
printf(" -shareexists\n");
printf(" test whether a connection-sharing upstream exists\n");
exit(1);
}
static void version(void)
{
char *buildinfo_text = buildinfo("\n");
printf("plink: %s\n%s\n", ver, buildinfo_text);
sfree(buildinfo_text);
exit(0);
}
char *do_select(SOCKET skt, int startup)
{
int events;
if (startup) {
events = (FD_CONNECT | FD_READ | FD_WRITE |
FD_OOB | FD_CLOSE | FD_ACCEPT);
} else {
events = 0;
}
if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
switch (p_WSAGetLastError()) {
case WSAENETDOWN:
return "Network is down";
default:
return "WSAEventSelect(): unknown error";
}
}
return NULL;
}
int stdin_gotdata(struct handle *h, void *data, int len)
{
if (len < 0) {
/*
* Special case: report read error.
*/
char buf[4096];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,
buf, lenof(buf), NULL);
buf[lenof(buf)-1] = '\0';
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
fprintf(stderr, "Unable to read from standard input: %s\n", buf);
cleanup_exit(0);
}
noise_ultralight(len);
if (backend_connected(backend)) {
if (len > 0) {
return backend_send(backend, data, len);
} else {
backend_special(backend, SS_EOF, 0);
return 0;
}
} else
return 0;
}
void stdouterr_sent(struct handle *h, int new_backlog)
{
if (new_backlog < 0) {
/*
* Special case: report write error.
*/
char buf[4096];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,
buf, lenof(buf), NULL);
buf[lenof(buf)-1] = '\0';
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
fprintf(stderr, "Unable to write to standard %s: %s\n",
(h == stdout_handle ? "output" : "error"), buf);
cleanup_exit(0);
}
if (backend_connected(backend)) {
backend_unthrottle(backend, (handle_backlog(stdout_handle) +
handle_backlog(stderr_handle)));
}
}
const int share_can_be_downstream = TRUE;
const int share_can_be_upstream = TRUE;
int main(int argc, char **argv)
{
int sending;
SOCKET *sklist;
int skcount, sksize;
int exitcode;
int errors;
int use_subsystem = 0;
int just_test_share_exists = FALSE;
unsigned long now, next, then;
const struct BackendVtable *vt;
dll_hijacking_protection();
sklist = NULL;
skcount = sksize = 0;
/*
* Initialise port and protocol to sensible defaults. (These
* will be overridden by more or less anything.)
*/
default_protocol = PROT_SSH;
default_port = 22;
flags = 0;
cmdline_tooltype |=
(TOOLTYPE_HOST_ARG |
TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD);
/*
* Process the command line.
*/
conf = conf_new();
do_defaults(NULL, conf);
loaded_session = FALSE;
default_protocol = conf_get_int(conf, CONF_protocol);
default_port = conf_get_int(conf, CONF_port);
errors = 0;
{
/*
* Override the default protocol if PLINK_PROTOCOL is set.
*/
char *p = getenv("PLINK_PROTOCOL");
if (p) {
const struct BackendVtable *vt = backend_vt_from_name(p);
if (vt) {
default_protocol = vt->protocol;
default_port = vt->default_port;
conf_set_int(conf, CONF_protocol, default_protocol);
conf_set_int(conf, CONF_port, default_port);
}
}
}
while (--argc) {
char *p = *++argv;
int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
1, conf);
if (ret == -2) {
fprintf(stderr,
"plink: option \"%s\" requires an argument\n", p);
errors = 1;
} else if (ret == 2) {
--argc, ++argv;
} else if (ret == 1) {
continue;
} else if (!strcmp(p, "-batch")) {
console_batch_mode = 1;
} else if (!strcmp(p, "-s")) {
/* Save status to write to conf later. */
use_subsystem = 1;
} else if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
version();
} else if (!strcmp(p, "--help")) {
usage();
} else if (!strcmp(p, "-pgpfp")) {
pgp_fingerprints();
exit(1);
} else if (!strcmp(p, "-shareexists")) {
just_test_share_exists = TRUE;
} else if (*p != '-') {
char *command;
int cmdlen, cmdsize;
cmdlen = cmdsize = 0;
command = NULL;
while (argc) {
while (*p) {
if (cmdlen >= cmdsize) {
cmdsize = cmdlen + 512;
command = sresize(command, cmdsize, char);
}
command[cmdlen++]=*p++;
}
if (cmdlen >= cmdsize) {
cmdsize = cmdlen + 512;
command = sresize(command, cmdsize, char);
}
command[cmdlen++]=' '; /* always add trailing space */
if (--argc) p = *++argv;
}
if (cmdlen) command[--cmdlen]='\0';
/* change trailing blank to NUL */
conf_set_str(conf, CONF_remote_cmd, command);
conf_set_str(conf, CONF_remote_cmd2, "");
conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */
break; /* done with cmdline */
} else {
fprintf(stderr, "plink: unknown option \"%s\"\n", p);
errors = 1;
}
}
if (errors)
return 1;
if (!cmdline_host_ok(conf)) {
usage();
}
prepare_session(conf);
/*
* Perform command-line overrides on session configuration.
*/
cmdline_run_saved(conf);
/*
* Apply subsystem status.
*/
if (use_subsystem)
conf_set_int(conf, CONF_ssh_subsys, TRUE);
if (!*conf_get_str(conf, CONF_remote_cmd) &&
!*conf_get_str(conf, CONF_remote_cmd2) &&
!*conf_get_str(conf, CONF_ssh_nc_host))
flags |= FLAG_INTERACTIVE;
/*
* Select protocol. This is farmed out into a table in a
* separate file to enable an ssh-free variant.
*/
vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
if (vt == NULL) {
fprintf(stderr,
"Internal fault: Unsupported protocol found\n");
return 1;
}
sk_init();
if (p_WSAEventSelect == NULL) {
fprintf(stderr, "Plink requires WinSock 2\n");
return 1;
}
/*
* Plink doesn't provide any way to add forwardings after the
* connection is set up, so if there are none now, we can safely set
* the "simple" flag.
*/
if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
!conf_get_int(conf, CONF_x11_forward) &&
!conf_get_int(conf, CONF_agentfwd) &&
!conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
conf_set_int(conf, CONF_ssh_simple, TRUE);
logctx = log_init(default_logpolicy, conf);
if (just_test_share_exists) {
if (!vt->test_for_upstream) {
fprintf(stderr, "Connection sharing not supported for connection "
"type '%s'\n", vt->name);
return 1;
}
if (vt->test_for_upstream(conf_get_str(conf, CONF_host),
conf_get_int(conf, CONF_port), conf))
return 0;
else
return 1;
}
if (restricted_acl) {
lp_eventlog(default_logpolicy, "Running with restricted process ACL");
}
/*
* Start up the connection.
*/
netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
{
const char *error;
char *realhost;
/* nodelay is only useful if stdin is a character device (console) */
int nodelay = conf_get_int(conf, CONF_tcp_nodelay) &&
(GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
error = backend_init(vt, NULL, &backend, logctx, conf,
conf_get_str(conf, CONF_host),
conf_get_int(conf, CONF_port),
&realhost, nodelay,
conf_get_int(conf, CONF_tcp_keepalives));
if (error) {
fprintf(stderr, "Unable to open connection:\n%s", error);
return 1;
}
sfree(realhost);
}
inhandle = GetStdHandle(STD_INPUT_HANDLE);
outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
errhandle = GetStdHandle(STD_ERROR_HANDLE);
/*
* Turn off ECHO and LINE input modes. We don't care if this
* call fails, because we know we aren't necessarily running in
* a console.
*/
GetConsoleMode(inhandle, &orig_console_mode);
SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
/*
* Pass the output handles to the handle-handling subsystem.
* (The input one we leave until we're through the
* authentication process.)
*/
stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
main_thread_id = GetCurrentThreadId();
sending = FALSE;
now = GETTICKCOUNT();
while (1) {
int nhandles;
HANDLE *handles;
int n;
DWORD ticks;
if (!sending && backend_sendok(backend)) {
stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
0);
sending = TRUE;
}
if (toplevel_callback_pending()) {
ticks = 0;
next = now;
} else if (run_timers(now, &next)) {
then = now;
now = GETTICKCOUNT();
if (now - then > next - then)
ticks = 0;
else
ticks = next - now;
} else {
ticks = INFINITE;
/* no need to initialise next here because we can never
* get WAIT_TIMEOUT */
}
handles = handle_get_events(&nhandles);
handles = sresize(handles, nhandles+1, HANDLE);
handles[nhandles] = netevent;
n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
QS_POSTMESSAGE);
if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
handle_got_event(handles[n - WAIT_OBJECT_0]);
} else if (n == WAIT_OBJECT_0 + nhandles) {
WSANETWORKEVENTS things;
SOCKET socket;
extern SOCKET first_socket(int *), next_socket(int *);
int i, socketstate;
/*
* We must not call select_result() for any socket
* until we have finished enumerating within the tree.
* This is because select_result() may close the socket
* and modify the tree.
*/
/* Count the active sockets. */
i = 0;
for (socket = first_socket(&socketstate);
socket != INVALID_SOCKET;
socket = next_socket(&socketstate)) i++;
/* Expand the buffer if necessary. */
if (i > sksize) {
sksize = i + 16;
sklist = sresize(sklist, sksize, SOCKET);
}
/* Retrieve the sockets into sklist. */
skcount = 0;
for (socket = first_socket(&socketstate);
socket != INVALID_SOCKET;
socket = next_socket(&socketstate)) {
sklist[skcount++] = socket;
}
/* Now we're done enumerating; go through the list. */
for (i = 0; i < skcount; i++) {
WPARAM wp;
socket = sklist[i];
wp = (WPARAM) socket;
if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
static const struct { int bit, mask; } eventtypes[] = {
{FD_CONNECT_BIT, FD_CONNECT},
{FD_READ_BIT, FD_READ},
{FD_CLOSE_BIT, FD_CLOSE},
{FD_OOB_BIT, FD_OOB},
{FD_WRITE_BIT, FD_WRITE},
{FD_ACCEPT_BIT, FD_ACCEPT},
};
int e;
noise_ultralight(socket);
noise_ultralight(things.lNetworkEvents);
for (e = 0; e < lenof(eventtypes); e++)
if (things.lNetworkEvents & eventtypes[e].mask) {
LPARAM lp;
int err = things.iErrorCode[eventtypes[e].bit];
lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
select_result(wp, lp);
}
}
}
} else if (n == WAIT_OBJECT_0 + nhandles + 1) {
MSG msg;
while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
PM_REMOVE)) {
struct agent_callback *c = (struct agent_callback *)msg.lParam;
c->callback(c->callback_ctx, c->data, c->len);
sfree(c);
}
}
run_toplevel_callbacks();
if (n == WAIT_TIMEOUT) {
now = next;
} else {
now = GETTICKCOUNT();
}
sfree(handles);
if (sending)
handle_unthrottle(stdin_handle, backend_sendbuffer(backend));
if (!backend_connected(backend) &&
handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
break; /* we closed the connection */
}
exitcode = backend_exitcode(backend);
if (exitcode < 0) {
fprintf(stderr, "Remote process exit code unavailable\n");
exitcode = 1; /* this is an error condition */
}
cleanup_exit(exitcode);
return 0; /* placate compiler warning */
}