1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00
putty-source/unix/psusan.c
Simon Tatham f39c51f9a7 Rename most of the platform source files.
This gets rid of all those annoying 'win', 'ux' and 'gtk' prefixes
which made filenames annoying to type and to tab-complete. Also, as
with my other recent renaming sprees, I've taken the opportunity to
expand and clarify some of the names so that they're not such cryptic
abbreviations.
2021-04-26 18:00:01 +01:00

423 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"
const char *const appname = "psusan";
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, const char *error_msg, int error_code,
bool calling_back)
{
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, 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;
}