diff options
Diffstat (limited to 'unix/uppity.c')
-rw-r--r-- | unix/uppity.c | 969 |
1 files changed, 969 insertions, 0 deletions
diff --git a/unix/uppity.c b/unix/uppity.c new file mode 100644 index 00000000..11fead95 --- /dev/null +++ b/unix/uppity.c @@ -0,0 +1,969 @@ +/* + * 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 "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 *data, size_t size) +{ + random_read(data, size); +} + +static bool verbose; + +struct server_config; + +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; + struct server_config *cfg; +}; + +struct server_config { + unsigned config_id; + + Conf *conf; + const SshServerConfig *ssc; + + ssh_key **hostkeys; + int nhostkeys; + + RSAKey *hostkey1; + + unsigned auth_methods; + + struct AuthPolicyShared *ap_shared; + + Socket *listening_socket; + Plug listening_plug; +}; + +static unsigned next_id = 0; + +static void log_to_stderr(unsigned id, const char *msg) +{ + if (id != (unsigned)-1) + fprintf(stderr, "conn#%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, +}; + +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) +{ + struct server_instance *inst = container_of( + ap, struct server_instance, ap); + return inst->cfg->auth_methods; +} +bool auth_none(AuthPolicy *ap, ptrlen username) +{ + struct server_instance *inst = container_of( + ap, struct server_instance, ap); + if (inst->cfg->auth_methods & AUTHMETHOD_NONE) + return true; + 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: { + struct server_instance *inst = container_of( + ap, struct server_instance, ap); + aki = snew(AuthKbdInt); + if (inst->cfg->ssc->stunt_allow_trivial_ki_auth) { + aki->title = dupstr(""); + aki->instruction = dupstr(""); + } else { + 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] [--and <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" + " --deny-auth METHOD forbid a userauth method\n" + " --allow-auth METHOD allow a userauth method\n" + " (METHOD = none/password/publickey/kbdint/tis/" + "cryptocard)\n" + " --exitsignum send buggy numeric \"exit-signal\" " + "message\n" + " --verbose print event log messages to standard " + "error\n" + " --sshlog FILE write SSH packet log to FILE\n" + " --sshrawlog FILE write SSH packets + raw data log" + " to FILE\n" + " --and run a separate server on another port\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; +} + +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 = next_id++; + inst->ap.shared = cfg->ap_shared; + if (cfg->ssc->stunt_allow_trivial_ki_auth) + inst->ap.kbdint_state = 1; + inst->logpolicy.vt = &server_logpolicy_vt; + inst->cfg = cfg; + + 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, PlugCloseType type, + const char *error_msg) +{ + if (type != PLUGCLOSE_NORMAL) + 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 = 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 to serv#%u " + "from %s (untrustworthy peer)\n", + cfg->config_id, pi->log_text); + sk_free_peer_info(pi); + sk_close(s); + next_id = old_next_id; + return 1; + } + + char *msg = dupprintf("new connection to serv#%u from %s", + cfg->config_id, 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, +}; + +static unsigned auth_method_from_name(const char *name) +{ + if (!strcmp(name, "none")) + return AUTHMETHOD_NONE; + if (!strcmp(name, "tis")) + return AUTHMETHOD_TIS; + if (!strcmp(name, "cryptocard") || !strcmp(name, "ccard")) + return AUTHMETHOD_CRYPTOCARD; + if (!strcmp(name, "keyboard-interactive") || !strcmp(name, "k-i") || + !strcmp(name, "kbdint") || !strcmp(name, "ki")) + return AUTHMETHOD_KBDINT; + if (!strcmp(name, "publickey") || !strcmp(name, "pubkey") || + !strcmp(name, "pk")) + return AUTHMETHOD_PUBLICKEY; + if (!strcmp(name, "password") || !strcmp(name, "pw")) + return AUTHMETHOD_PASSWORD; + return 0; +} + +struct cmdline_instance { + int listen_port; + const char *listen_socket; + ssh_key **hostkeys; + size_t nhostkeys, hostkeysize; + RSAKey *hostkey1; + unsigned auth_methods; + struct AuthPolicyShared aps; + SshServerConfig ssc; + Conf *conf; +}; + +static void init_cmdline_instance(struct cmdline_instance *ci) +{ + ci->listen_port = -1; + ci->listen_socket = NULL; + + ci->hostkeys = NULL; + ci->nhostkeys = ci->hostkeysize = 0; + ci->hostkey1 = NULL; + + ci->conf = make_ssh_server_conf(); + + ci->aps.ssh1keys = NULL; + ci->aps.ssh2keys = NULL; + + memset(&ci->ssc, 0, sizeof(ci->ssc)); + + ci->ssc.application_name = "Uppity"; + ci->ssc.session_starting_dir = getenv("HOME"); + ci->ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; + ci->ssc.ssh1_allow_compression = true; + + ci->auth_methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | + AUTHMETHOD_KBDINT | AUTHMETHOD_TIS | + AUTHMETHOD_CRYPTOCARD); +} + +static void cmdline_instance_start(struct cmdline_instance *ci) +{ + static unsigned next_server_config_id = 0; + + struct server_config *scfg = snew(struct server_config); + scfg->config_id = next_server_config_id++; + scfg->conf = ci->conf; + scfg->ssc = &ci->ssc; + scfg->hostkeys = ci->hostkeys; + scfg->nhostkeys = ci->nhostkeys; + scfg->hostkey1 = ci->hostkey1; + scfg->ap_shared = &ci->aps; + scfg->auth_methods = ci->auth_methods; + + if (ci->listen_port >= 0 || ci->listen_socket) { + listening = true; + scfg->listening_plug.vt = &server_plugvt; + char *msg; + if (ci->listen_port >= 0) { + scfg->listening_socket = sk_newlistener( + NULL, ci->listen_port, &scfg->listening_plug, true, + ADDRTYPE_UNSPEC); + msg = dupprintf("serv#%u: listening on port %d", + scfg->config_id, ci->listen_port); + } else { + SockAddr *addr = unix_sock_addr(ci->listen_socket); + scfg->listening_socket = new_unix_listener( + addr, &scfg->listening_plug); + msg = dupprintf("serv#%u: listening on Unix socket %s", + scfg->config_id, ci->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, "speaking SSH on stdio"); + } +} + +int main(int argc, char **argv) +{ + size_t ninstances = 0, instancessize = 8; + struct cmdline_instance *instances = snewn( + instancessize, struct cmdline_instance); + struct cmdline_instance *ci = &instances[ninstances++]; + init_cmdline_instance(ci); + + 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 (longoptnoarg(arg, "--and")) { + sgrowarray(instances, instancessize, ninstances); + ci = &instances[ninstances++]; + init_cmdline_instance(ci); + } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { + if (val[0] == '/') { + ci->listen_port = -1; + ci->listen_socket = val; + } else { + ci->listen_port = atoi(val); + ci->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 < ci->nhostkeys; i++) + if (ssh_key_alg(ci->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(ci->hostkeys, ci->hostkeysize, ci->nhostkeys); + ci->hostkeys[ci->nhostkeys++] = key; + } else if (keytype == SSH_KEYTYPE_SSH1) { + if (ci->hostkey1) { + fprintf(stderr, "%s: host key '%s' is a redundant " + "SSH-1 host key\n", appname, val); + exit(1); + } + ci->hostkey1 = snew(RSAKey); + if (!rsa1_load_f(keyfile, ci->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 (ci->ssc.rsa_kex_key) { + freersakey(ci->ssc.rsa_kex_key); + } else { + ci->ssc.rsa_kex_key = snew(RSAKey); + } + + if (!rsa1_load_f(keyfile, ci->ssc.rsa_kex_key, NULL, &error)) { + fprintf(stderr, "%s: unable to load RSA kex key '%s': " + "%s\n", appname, val, error); + exit(1); + } + + ci->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 = ci->aps.ssh2keys; + ci->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 = ci->aps.ssh1keys; + ci->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); + ci->ssc.banner = ptrlen_from_strbuf(sb); + } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) { + ci->ssc.banner = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { + ci->ssc.session_starting_dir = val; + } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { + ci->ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--allow-auth", &val, &argc, &argv)) { + unsigned method = auth_method_from_name(val); + if (!method) { + fprintf(stderr, "%s: unrecognised auth method '%s'\n", + appname, val); + exit(1); + } + ci->auth_methods |= method; + } else if (longoptarg(arg, "--deny-auth", &val, &argc, &argv)) { + unsigned method = auth_method_from_name(val); + if (!method) { + fprintf(stderr, "%s: unrecognised auth method '%s'\n", + appname, val); + exit(1); + } + ci->auth_methods &= ~method; + } 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); + } + ci->ssc.ssh1_cipher_mask = mask; + } else if (longoptnoarg(arg, "--ssh1-no-compression")) { + ci->ssc.ssh1_allow_compression = false; + } else if (longoptnoarg(arg, "--exitsignum")) { + ci->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(ci->conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(ci->conf, CONF_logtype, LGTYP_PACKETS); + conf_set_int(ci->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(ci->conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(ci->conf, CONF_logtype, LGTYP_SSHRAW); + conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR); + } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { + ci->ssc.stunt_pretend_to_accept_any_pubkey = true; + } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { + ci->ssc.stunt_open_unconditional_agent_socket = true; + } else if (!strcmp(arg, "--allow-none-auth")) { + /* backwards-compatibility synonym for --allow-auth=none */ + ci->auth_methods |= AUTHMETHOD_NONE; + } else if (!strcmp(arg, "--allow-trivial-ki-auth")) { + ci->ssc.stunt_allow_trivial_ki_auth = true; + } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { + ci->ssc.stunt_return_success_to_pubkey_offer = true; + } else { + fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); + exit(1); + } + } + + if (ninstances > 1 && listen_once) { + fprintf(stderr, "%s: cannot listen once only with multiple server " + "instances\n", appname); + exit(1); + } + for (size_t i = 0; i < ninstances; i++) { + ci = &instances[i]; + if (ci->nhostkeys == 0 && !ci->hostkey1) { + fprintf(stderr, "%s: specify at least one host key\n", appname); + exit(1); + } + if (ninstances > 1 && !(ci->listen_port >= 0 || ci->listen_socket)) { + fprintf(stderr, "%s: cannot talk to stdio with multiple server " + "instances\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(); + + for (size_t i = 0; i < ninstances; i++) + cmdline_instance_start(&instances[i]); + + cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, + cliloop_always_continue, NULL); + + return 0; +} |