putty/unix/uxserver.c

843 строки
27 KiB
C

/*
* SSH server for Unix: main program.
*
* ======================================================================
*
* This server is NOT SECURE!
*
* DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!
*
* Its purpose is to speak the server end of everything PuTTY speaks
* on the client side, so that I can test that I haven't broken PuTTY
* when I reorganise its code, even things like RSA key exchange or
* chained auth methods which it's hard to find a server that speaks
* at all.
*
* It has no interaction with the OS's authentication system: the
* authentications it will accept are configurable by command-line
* option, and once you authenticate, it will run the connection
* protocol - including all subprocesses and shells - under the same
* Unix user id you started it under.
*
* It really is only suitable for testing the actual SSH protocol.
* Don't use it for anything more serious!
*
* ======================================================================
*/
#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 "sshserver.h"
const char *const appname = "uppity";
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; }
static bool verbose;
struct AuthPolicyShared {
struct AuthPolicy_ssh1_pubkey *ssh1keys;
struct AuthPolicy_ssh2_pubkey *ssh2keys;
};
struct AuthPolicy {
struct AuthPolicyShared *shared;
int kbdint_state;
};
struct server_instance {
unsigned id;
AuthPolicy ap;
LogPolicy logpolicy;
};
static void log_to_stderr(unsigned id, const char *msg)
{
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 = {
server_eventlog,
server_askappend,
server_logging_error,
null_lp_verbose_no,
};
struct AuthPolicy_ssh1_pubkey {
RSAKey key;
struct AuthPolicy_ssh1_pubkey *next;
};
struct AuthPolicy_ssh2_pubkey {
ptrlen public_blob;
struct AuthPolicy_ssh2_pubkey *next;
};
unsigned auth_methods(AuthPolicy *ap)
{
return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT |
AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD);
}
bool auth_none(AuthPolicy *ap, ptrlen username)
{
return false;
}
int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
ptrlen *new_password_opt)
{
const char *PHONY_GOOD_PASSWORD = "weasel";
const char *PHONY_BAD_PASSWORD = "ferret";
if (!new_password_opt) {
/* Accept login with our preconfigured good password */
if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD))
return 1;
/* Don't outright reject the bad password, but insist on a change */
if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
return 2;
/* Reject anything else */
return 0;
} else {
/* In a password-change request, expect the bad password as input */
if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
return 0;
/* Accept a request to change it to the good password */
if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD))
return 1;
/* Outright reject a request to change it to the same password
* as it already 'was' */
if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD))
return 0;
/* Anything else, pretend the new pw wasn't good enough, and
* re-request a change */
return 2;
}
}
bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
{
struct AuthPolicy_ssh2_pubkey *iter;
for (iter = ap->shared->ssh2keys; iter; iter = iter->next) {
if (ptrlen_eq_ptrlen(public_blob, iter->public_blob))
return true;
}
return false;
}
RSAKey *auth_publickey_ssh1(
AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
{
struct AuthPolicy_ssh1_pubkey *iter;
for (iter = ap->shared->ssh1keys; iter; iter = iter->next) {
if (mp_cmp_eq(rsa_modulus, iter->key.modulus))
return &iter->key;
}
return NULL;
}
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
{
AuthKbdInt *aki;
switch (ap->kbdint_state) {
case 0:
aki = snew(AuthKbdInt);
aki->title = dupstr("Initial double prompt");
aki->instruction =
dupstr("First prompt should echo, second should not");
aki->nprompts = 2;
aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt);
aki->prompts[0].prompt = dupstr("Echoey prompt: ");
aki->prompts[0].echo = true;
aki->prompts[1].prompt = dupstr("Silent prompt: ");
aki->prompts[1].echo = false;
return aki;
case 1:
aki = snew(AuthKbdInt);
aki->title = dupstr("Zero-prompt step");
aki->instruction = dupstr("Shouldn't see any prompts this time");
aki->nprompts = 0;
aki->prompts = NULL;
return aki;
default:
ap->kbdint_state = 0;
return NULL;
}
}
int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
{
switch (ap->kbdint_state) {
case 0:
if (ptrlen_eq_string(responses[0], "stoat") &&
ptrlen_eq_string(responses[1], "weasel")) {
ap->kbdint_state++;
return 0; /* those are the expected responses */
} else {
ap->kbdint_state = 0;
return -1;
}
break;
case 1:
return +1; /* succeed after the zero-prompt step */
default:
ap->kbdint_state = 0;
return -1;
}
}
char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
{
/* FIXME: test returning a challenge string without \n, and ensure
* it gets printed as a prompt in its own right, without PuTTY
* making up a "Response: " prompt to follow it */
return dupprintf("This is a dummy %s challenge!\n",
(method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard"));
}
bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
{
return ptrlen_eq_string(response, "otter");
}
bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
{
return true;
}
static void safety_warning(FILE *fp)
{
fputs(" =================================================\n"
" THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n"
" DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n"
" =================================================\n", fp);
}
static void show_help(FILE *fp)
{
safety_warning(fp);
fputs("\n"
"usage: uppity [options]\n"
"options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n"
" --listen-once (with --listen) stop after one "
"connection\n"
" --hostkey KEY SSH host key (need at least one)\n"
" --rsakexkey KEY key for SSH-2 RSA key exchange "
"(in SSH-1 format)\n"
" --userkey KEY public key"
" acceptable for user authentication\n"
" --sessiondir DIR cwd for session subprocess (default $HOME)\n"
" --bannertext TEXT send TEXT as SSH-2 auth banner\n"
" --bannerfile FILE send contents of FILE as SSH-2 auth "
"banner\n"
" --kexinit-kex STR override list of SSH-2 KEX methods\n"
" --kexinit-hostkey STR override list of SSH-2 host key "
"types\n"
" --kexinit-cscipher STR override list of SSH-2 "
"client->server ciphers\n"
" --kexinit-sccipher STR override list of SSH-2 "
"server->client ciphers\n"
" --kexinit-csmac STR override list of SSH-2 "
"client->server MACs\n"
" --kexinit-scmac STR override list of SSH-2 "
"server->client MACs\n"
" --kexinit-cscomp STR override list of SSH-2 "
"c->s compression types\n"
" --kexinit-sccomp STR override list of SSH-2 "
"s->c compression types\n"
" --ssh1-ciphers STR override list of SSH-1 ciphers\n"
" --ssh1-no-compression forbid compression in SSH-1\n"
" --exitsignum send buggy numeric \"exit-signal\" "
"message\n"
" --verbose print event log messages to standard "
"output\n"
" --sshlog FILE write SSH packet log to FILE\n"
" --sshrawlog FILE write SSH packets + raw data log"
" to FILE\n"
"also: uppity --help show this text\n"
" uppity --version show version information\n"
"\n", fp);
safety_warning(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);
}
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;
ssh_key **hostkeys;
int nhostkeys;
RSAKey *hostkey1;
struct AuthPolicyShared *ap_shared;
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->ap.shared = cfg->ap_shared;
inst->logpolicy.vt = &server_logpolicy_vt;
if (inst_out)
*inst_out = inst;
return ssh_server_plug(
cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1,
&inst->ap, &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((unsigned)-1, error_msg);
}
static void server_closing(Plug *plug, const char *error_msg, int error_code,
bool calling_back)
{
log_to_stderr((unsigned)-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;
}
unsigned old_next_id = cfg->next_id;
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);
if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) {
fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n",
pi->log_text);
sk_free_peer_info(pi);
sk_close(s);
cfg->next_id = old_next_id;
return 1;
}
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 = {
server_log,
server_closing,
NULL, /* recv */
NULL, /* send */
server_accepting
};
int main(int argc, char **argv)
{
int listen_port = -1;
const char *listen_socket = NULL;
ssh_key **hostkeys = NULL;
size_t nhostkeys = 0, hostkeysize = 0;
RSAKey *hostkey1 = NULL;
struct AuthPolicyShared aps;
SshServerConfig ssc;
Conf *conf = make_ssh_server_conf();
aps.ssh1keys = NULL;
aps.ssh2keys = NULL;
memset(&ssc, 0, sizeof(ssc));
ssc.session_starting_dir = getenv("HOME");
ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK;
ssc.ssh1_allow_compression = true;
if (argc <= 1) {
/*
* We're going to terminate with an error message below,
* because there are no host keys. But we'll display the help
* as additional standard-error output, if nothing else so
* that people see the giant safety warning.
*/
show_help(stderr);
fputc('\n', stderr);
}
while (--argc > 0) {
const char *arg = *++argv;
const char *val;
if (!strcmp(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, "--listen", &val, &argc, &argv)) {
if (val[0] == '/') {
listen_port = -1;
listen_socket = val;
} else {
listen_port = atoi(val);
listen_socket = NULL;
}
} else if (!strcmp(arg, "--listen-once")) {
listen_once = true;
} else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) {
Filename *keyfile;
int keytype;
const char *error;
keyfile = filename_from_str(val);
keytype = key_type(keyfile);
if (keytype == SSH_KEYTYPE_SSH2) {
ssh2_userkey *uk;
ssh_key *key;
uk = ppk_load_f(keyfile, NULL, &error);
filename_free(keyfile);
if (!uk || !uk->key) {
fprintf(stderr, "%s: unable to load host key '%s': "
"%s\n", appname, val, error);
exit(1);
}
char *invalid = ssh_key_invalid(uk->key, 0);
if (invalid) {
fprintf(stderr, "%s: host key '%s' is unusable: "
"%s\n", appname, val, invalid);
exit(1);
}
key = uk->key;
sfree(uk->comment);
sfree(uk);
for (int i = 0; i < nhostkeys; i++)
if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) {
fprintf(stderr, "%s: host key '%s' duplicates key "
"type %s\n", appname, val,
ssh_key_alg(key)->ssh_id);
exit(1);
}
sgrowarray(hostkeys, hostkeysize, nhostkeys);
hostkeys[nhostkeys++] = key;
} else if (keytype == SSH_KEYTYPE_SSH1) {
if (hostkey1) {
fprintf(stderr, "%s: host key '%s' is a redundant "
"SSH-1 host key\n", appname, val);
exit(1);
}
hostkey1 = snew(RSAKey);
if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) {
fprintf(stderr, "%s: unable to load host key '%s': "
"%s\n", appname, val, error);
exit(1);
}
} else {
fprintf(stderr, "%s: '%s' is not loadable as a "
"private key (%s)", appname, val,
key_type_to_str(keytype));
exit(1);
}
} else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) {
Filename *keyfile;
int keytype;
const char *error;
keyfile = filename_from_str(val);
keytype = key_type(keyfile);
if (keytype != SSH_KEYTYPE_SSH1) {
fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format "
"private key (%s)", appname, val,
key_type_to_str(keytype));
exit(1);
}
if (ssc.rsa_kex_key) {
freersakey(ssc.rsa_kex_key);
} else {
ssc.rsa_kex_key = snew(RSAKey);
}
if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) {
fprintf(stderr, "%s: unable to load RSA kex key '%s': "
"%s\n", appname, val, error);
exit(1);
}
ssc.rsa_kex_key->sshk.vt = &ssh_rsa;
} else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) {
Filename *keyfile;
int keytype;
const char *error;
keyfile = filename_from_str(val);
keytype = key_type(keyfile);
if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
strbuf *sb = strbuf_new();
struct AuthPolicy_ssh2_pubkey *node;
void *blob;
if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb),
NULL, &error)) {
fprintf(stderr, "%s: unable to load user key '%s': "
"%s\n", appname, val, error);
exit(1);
}
node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len);
blob = snew_plus_get_aux(node);
memcpy(blob, sb->u, sb->len);
node->public_blob = make_ptrlen(blob, sb->len);
node->next = aps.ssh2keys;
aps.ssh2keys = node;
strbuf_free(sb);
} else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
strbuf *sb = strbuf_new();
BinarySource src[1];
struct AuthPolicy_ssh1_pubkey *node;
if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb),
NULL, &error)) {
fprintf(stderr, "%s: unable to load user key '%s': "
"%s\n", appname, val, error);
exit(1);
}
node = snew(struct AuthPolicy_ssh1_pubkey);
BinarySource_BARE_INIT(src, sb->u, sb->len);
get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST);
node->next = aps.ssh1keys;
aps.ssh1keys = node;
strbuf_free(sb);
} else {
fprintf(stderr, "%s: '%s' is not loadable as a public key "
"(%s)\n", appname, val, key_type_to_str(keytype));
exit(1);
}
} else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) {
FILE *fp = fopen(val, "r");
if (!fp) {
fprintf(stderr, "%s: %s: open: %s\n", appname,
val, strerror(errno));
exit(1);
}
strbuf *sb = strbuf_new();
if (!read_file_into(BinarySink_UPCAST(sb), fp)) {
fprintf(stderr, "%s: %s: read: %s\n", appname,
val, strerror(errno));
exit(1);
}
fclose(fp);
ssc.banner = ptrlen_from_strbuf(sb);
} else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) {
ssc.banner = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
ssc.session_starting_dir = val;
} else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) {
ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val);
} else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) {
ptrlen list = ptrlen_from_asciz(val);
ptrlen word;
unsigned long mask = 0;
while (word = ptrlen_get_word(&list, ","), word.len != 0) {
#define SSH1_CIPHER_CASE(bitpos, name) \
if (ptrlen_eq_string(word, name)) { \
mask |= 1U << bitpos; \
continue; \
}
SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE);
#undef SSH1_CIPHER_CASE
fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n",
appname, PTRLEN_PRINTF(word));
exit(1);
}
ssc.ssh1_cipher_mask = mask;
} else if (longoptnoarg(arg, "--ssh1-no-compression")) {
ssc.ssh1_allow_compression = false;
} else if (longoptnoarg(arg, "--exitsignum")) {
ssc.exit_signal_numeric = true;
} 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 {
fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
exit(1);
}
}
if (nhostkeys == 0 && !hostkey1) {
fprintf(stderr, "%s: specify at least one host key\n", appname);
exit(1);
}
random_ref();
/*
* Block SIGPIPE, so that we'll get EPIPE individually on
* particular network connections that go wrong.
*/
putty_signal(SIGPIPE, SIG_IGN);
sk_init();
uxsel_init();
struct server_config scfg;
scfg.conf = conf;
scfg.ssc = &ssc;
scfg.hostkeys = hostkeys;
scfg.nhostkeys = nhostkeys;
scfg.hostkey1 = hostkey1;
scfg.ap_shared = &aps;
scfg.next_id = 0;
if (listen_port >= 0 || listen_socket) {
listening = true;
scfg.listening_plug.vt = &server_plugvt;
char *msg;
if (listen_port >= 0) {
scfg.listening_socket = sk_newlistener(
NULL, listen_port, &scfg.listening_plug, true,
ADDRTYPE_UNSPEC);
msg = dupprintf("%s: listening on port %d",
appname, listen_port);
} else {
SockAddr *addr = unix_sock_addr(listen_socket);
scfg.listening_socket = new_unix_listener(
addr, &scfg.listening_plug);
msg = dupprintf("%s: listening on Unix socket %s",
appname, 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, "speaking SSH on stdio");
}
cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
cliloop_always_continue, NULL);
return 0;
}