mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
53f7da8ce7
This commit replaces all those fiddly little linking modules (be_all.c, be_none.c, be_ssh.c etc) with a single source file controlled by ifdefs, and introduces a function be_list() in setup.cmake that makes it easy to compile a version of it appropriate to each application. This is a net reduction in code according to 'git diff --stat', even though I've introduced more comments. It also gets rid of another pile of annoying little source files in the top-level directory that didn't deserve to take up so much room in 'ls'. More concretely, doing this has some maintenance advantages. Centralisation means less to maintain (e.g. n_ui_backends is worked out once in a way that makes sense everywhere), and also, 'appname' can now be reliably set per program. Previously, some programs got the wrong appname due to sharing the same linking module (e.g. Plink had appname="PuTTY"), which was a latent bug that would have manifested if I'd wanted to reuse the same string in another context. One thing I've changed in this rework is that Windows pterm no longer has the ConPTY backend in its backends[]: it now has an empty one. The special be_conpty.c module shouldn't really have been there in the first place: it was used in the very earliest uncommitted drafts of the ConPTY work, where I was using another method of selecting that backend, but now that Windows pterm has a dedicated backend_vt_from_conf() that refers to conpty_backend by name, it has no need to live in backends[] at all, just as it doesn't have to in Unix pterm.
422 lines
12 KiB
C
422 lines
12 KiB
C
/*
|
|
* 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
|
|
*
|
|
* This is a standalone application that speaks on its standard I/O
|
|
* (or a listening Unix-domain socket) the server end of the bare
|
|
* ssh-connection protocol used by PuTTY's connection sharing.
|
|
*
|
|
* The idea of this tool is that you can use it to communicate across
|
|
* any 8-bit-clean data channel between two inconveniently separated
|
|
* domains, provided the channel is already (as the name suggests)
|
|
* adequately secured against eavesdropping and modification and
|
|
* already authenticated as the right user.
|
|
*
|
|
* If you're sitting at one end of such a channel and want to type
|
|
* commands into the other end, the most obvious thing to do is to run
|
|
* a terminal session directly over it. But if you run psusan at one
|
|
* end, and a PuTTY (or compatible) client at the other end, then you
|
|
* not only get a single terminal session: you get all the other SSH
|
|
* amenities, like the ability to spawn extra terminal sessions,
|
|
* forward ports or X11 connections, even forward an SSH agent.
|
|
*
|
|
* There are a surprising number of channels of that kind; see the man
|
|
* page for some examples.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <pwd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "putty.h"
|
|
#include "mpint.h"
|
|
#include "ssh.h"
|
|
#include "ssh/server.h"
|
|
|
|
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);
|
|
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);
|
|
}
|
|
|
|
char *platform_default_s(const char *name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool platform_default_b(const char *name, bool def)
|
|
{
|
|
return def;
|
|
}
|
|
|
|
int platform_default_i(const char *name, int def)
|
|
{
|
|
return def;
|
|
}
|
|
|
|
FontSpec *platform_default_fontspec(const char *name)
|
|
{
|
|
return fontspec_new("");
|
|
}
|
|
|
|
Filename *platform_default_filename(const char *name)
|
|
{
|
|
return filename_from_str("");
|
|
}
|
|
|
|
char *x_get_default(const char *key)
|
|
{
|
|
return NULL; /* this is a stub */
|
|
}
|
|
|
|
void old_keyfile_warning(void) { }
|
|
|
|
void timer_change_notify(unsigned long next)
|
|
{
|
|
}
|
|
|
|
char *platform_get_x_display(void) { return NULL; }
|
|
|
|
void make_unix_sftp_filehandle_key(void *vdata, size_t size)
|
|
{
|
|
/* psusan runs without a random number generator, so we can't make
|
|
* this up by random_read. Fortunately, psusan is also
|
|
* non-adversarial, so it's safe to generate this trivially. */
|
|
unsigned char *data = (unsigned char *)vdata;
|
|
for (size_t i = 0; i < size; i++)
|
|
data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
|
|
}
|
|
|
|
static bool verbose;
|
|
|
|
struct server_instance {
|
|
unsigned id;
|
|
LogPolicy logpolicy;
|
|
};
|
|
|
|
static void log_to_stderr(unsigned id, const char *msg)
|
|
{
|
|
if (!verbose)
|
|
return;
|
|
if (id != (unsigned)-1)
|
|
fprintf(stderr, "#%u: ", id);
|
|
fputs(msg, stderr);
|
|
fputc('\n', stderr);
|
|
fflush(stderr);
|
|
}
|
|
|
|
static void server_eventlog(LogPolicy *lp, const char *event)
|
|
{
|
|
struct server_instance *inst = container_of(
|
|
lp, struct server_instance, logpolicy);
|
|
if (verbose)
|
|
log_to_stderr(inst->id, event);
|
|
}
|
|
|
|
static void server_logging_error(LogPolicy *lp, const char *event)
|
|
{
|
|
struct server_instance *inst = container_of(
|
|
lp, struct server_instance, logpolicy);
|
|
log_to_stderr(inst->id, event); /* unconditional */
|
|
}
|
|
|
|
static int server_askappend(
|
|
LogPolicy *lp, Filename *filename,
|
|
void (*callback)(void *ctx, int result), void *ctx)
|
|
{
|
|
return 2; /* always overwrite (FIXME: could make this a cmdline option) */
|
|
}
|
|
|
|
static const LogPolicyVtable server_logpolicy_vt = {
|
|
.eventlog = server_eventlog,
|
|
.askappend = server_askappend,
|
|
.logging_error = server_logging_error,
|
|
.verbose = null_lp_verbose_no,
|
|
};
|
|
|
|
static void show_help(FILE *fp)
|
|
{
|
|
fputs("usage: psusan [options]\n"
|
|
"options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
|
|
" --listen-once (with --listen) stop after one connection\n"
|
|
" --verbose print log messages to standard error\n"
|
|
" --sessiondir DIR cwd for session subprocess (default $HOME)\n"
|
|
" --sshlog FILE write ssh-connection packet log to FILE\n"
|
|
" --sshrawlog FILE write packets and raw data log to FILE\n"
|
|
"also: psusan --help show this text\n"
|
|
" psusan --version show version information\n", fp);
|
|
}
|
|
|
|
static void show_version_and_exit(void)
|
|
{
|
|
char *buildinfo_text = buildinfo("\n");
|
|
printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
|
|
sfree(buildinfo_text);
|
|
exit(0);
|
|
}
|
|
|
|
const bool buildinfo_gtk_relevant = false;
|
|
|
|
static bool listening = false, listen_once = false;
|
|
static bool finished = false;
|
|
void server_instance_terminated(LogPolicy *lp)
|
|
{
|
|
struct server_instance *inst = container_of(
|
|
lp, struct server_instance, logpolicy);
|
|
|
|
if (listening && !listen_once) {
|
|
log_to_stderr(inst->id, "connection terminated");
|
|
} else {
|
|
finished = true;
|
|
}
|
|
|
|
sfree(inst);
|
|
}
|
|
|
|
bool psusan_continue(void *ctx, bool fd, bool cb)
|
|
{
|
|
return !finished;
|
|
}
|
|
|
|
static bool longoptarg(const char *arg, const char *expected,
|
|
const char **val, int *argcp, char ***argvp)
|
|
{
|
|
int len = strlen(expected);
|
|
if (memcmp(arg, expected, len))
|
|
return false;
|
|
if (arg[len] == '=') {
|
|
*val = arg + len + 1;
|
|
return true;
|
|
} else if (arg[len] == '\0') {
|
|
if (--*argcp > 0) {
|
|
*val = *++*argvp;
|
|
return true;
|
|
} else {
|
|
fprintf(stderr, "%s: option %s expects an argument\n",
|
|
appname, expected);
|
|
exit(1);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool longoptnoarg(const char *arg, const char *expected)
|
|
{
|
|
int len = strlen(expected);
|
|
if (memcmp(arg, expected, len))
|
|
return false;
|
|
if (arg[len] == '=') {
|
|
fprintf(stderr, "%s: option %s expects no argument\n",
|
|
appname, expected);
|
|
exit(1);
|
|
} else if (arg[len] == '\0') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct server_config {
|
|
Conf *conf;
|
|
const SshServerConfig *ssc;
|
|
|
|
unsigned next_id;
|
|
|
|
Socket *listening_socket;
|
|
Plug listening_plug;
|
|
};
|
|
|
|
static Plug *server_conn_plug(
|
|
struct server_config *cfg, struct server_instance **inst_out)
|
|
{
|
|
struct server_instance *inst = snew(struct server_instance);
|
|
|
|
memset(inst, 0, sizeof(*inst));
|
|
|
|
inst->id = cfg->next_id++;
|
|
inst->logpolicy.vt = &server_logpolicy_vt;
|
|
|
|
if (inst_out)
|
|
*inst_out = inst;
|
|
|
|
return ssh_server_plug(
|
|
cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
|
|
&inst->logpolicy, &unix_live_sftpserver_vt);
|
|
}
|
|
|
|
static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
|
|
const char *error_msg, int error_code)
|
|
{
|
|
log_to_stderr(-1, error_msg);
|
|
}
|
|
|
|
static void server_closing(Plug *plug, PlugCloseType type,
|
|
const char *error_msg)
|
|
{
|
|
if (type != PLUGCLOSE_NORMAL)
|
|
log_to_stderr(-1, error_msg);
|
|
}
|
|
|
|
static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
|
|
{
|
|
struct server_config *cfg = container_of(
|
|
p, struct server_config, listening_plug);
|
|
Socket *s;
|
|
const char *err;
|
|
|
|
struct server_instance *inst;
|
|
|
|
if (listen_once) {
|
|
if (!cfg->listening_socket) /* in case of rapid double-accept */
|
|
return 1;
|
|
sk_close(cfg->listening_socket);
|
|
cfg->listening_socket = NULL;
|
|
}
|
|
|
|
Plug *plug = server_conn_plug(cfg, &inst);
|
|
s = constructor(ctx, plug);
|
|
if ((err = sk_socket_error(s)) != NULL)
|
|
return 1;
|
|
|
|
SocketPeerInfo *pi = sk_peer_info(s);
|
|
|
|
char *msg = dupprintf("new connection from %s", pi->log_text);
|
|
log_to_stderr(inst->id, msg);
|
|
sfree(msg);
|
|
sk_free_peer_info(pi);
|
|
|
|
sk_set_frozen(s, false);
|
|
ssh_server_start(plug, s);
|
|
return 0;
|
|
}
|
|
|
|
static const PlugVtable server_plugvt = {
|
|
.log = server_log,
|
|
.closing = server_closing,
|
|
.accepting = server_accepting,
|
|
};
|
|
|
|
unsigned auth_methods(AuthPolicy *ap)
|
|
{ return 0; }
|
|
bool auth_none(AuthPolicy *ap, ptrlen username)
|
|
{ return false; }
|
|
int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
|
|
ptrlen *new_password_opt)
|
|
{ return 0; }
|
|
bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
|
|
{ return false; }
|
|
RSAKey *auth_publickey_ssh1(
|
|
AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
|
|
{ return NULL; }
|
|
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
|
|
{ return NULL; }
|
|
int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
|
|
{ return -1; }
|
|
char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
|
|
{ return NULL; }
|
|
bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
|
|
{ return false; }
|
|
bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
|
|
{ return false; }
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *listen_socket = NULL;
|
|
|
|
SshServerConfig ssc;
|
|
|
|
Conf *conf = make_ssh_server_conf();
|
|
|
|
memset(&ssc, 0, sizeof(ssc));
|
|
|
|
ssc.application_name = "PSUSAN";
|
|
ssc.session_starting_dir = getenv("HOME");
|
|
ssc.bare_connection = true;
|
|
|
|
while (--argc > 0) {
|
|
const char *arg = *++argv;
|
|
const char *val;
|
|
|
|
if (longoptnoarg(arg, "--help")) {
|
|
show_help(stdout);
|
|
exit(0);
|
|
} else if (longoptnoarg(arg, "--version")) {
|
|
show_version_and_exit();
|
|
} else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
|
|
verbose = true;
|
|
} else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
|
|
ssc.session_starting_dir = val;
|
|
} else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
|
|
longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
|
|
Filename *logfile = filename_from_str(val);
|
|
conf_set_filename(conf, CONF_logfilename, logfile);
|
|
filename_free(logfile);
|
|
conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
|
|
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
|
|
} else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
|
|
longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
|
|
Filename *logfile = filename_from_str(val);
|
|
conf_set_filename(conf, CONF_logfilename, logfile);
|
|
filename_free(logfile);
|
|
conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
|
|
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
|
|
} else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
|
|
listen_socket = val;
|
|
} else if (!strcmp(arg, "--listen-once")) {
|
|
listen_once = true;
|
|
} else {
|
|
fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
sk_init();
|
|
uxsel_init();
|
|
|
|
struct server_config scfg;
|
|
scfg.conf = conf;
|
|
scfg.ssc = &ssc;
|
|
scfg.next_id = 0;
|
|
|
|
if (listen_socket) {
|
|
listening = true;
|
|
scfg.listening_plug.vt = &server_plugvt;
|
|
SockAddr *addr = unix_sock_addr(listen_socket);
|
|
scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
|
|
char *msg = dupprintf("listening on Unix socket %s", listen_socket);
|
|
log_to_stderr(-1, msg);
|
|
sfree(msg);
|
|
} else {
|
|
struct server_instance *inst;
|
|
Plug *plug = server_conn_plug(&scfg, &inst);
|
|
ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug));
|
|
log_to_stderr(inst->id, "running directly on stdio");
|
|
}
|
|
|
|
cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
|
|
psusan_continue, NULL);
|
|
|
|
return 0;
|
|
}
|