123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- /*
- * Top-level code for SSH server implementation.
- */
- #include <assert.h>
- #include <stddef.h>
- #include "putty.h"
- #include "ssh.h"
- #include "bpp.h"
- #include "ppl.h"
- #include "channel.h"
- #include "server.h"
- #ifndef NO_GSSAPI
- #include "gssc.h"
- #include "gss.h"
- #endif
- struct Ssh { int dummy; };
- typedef struct server server;
- struct server {
- bufchain in_raw, out_raw;
- IdempotentCallback ic_out_raw;
- bool pending_close;
- bufchain dummy_user_input; /* we never put anything on this */
- PacketLogSettings pls;
- LogContext *logctx;
- struct DataTransferStats stats;
- int remote_bugs;
- Socket *socket;
- Plug plug;
- int conn_throttle_count;
- bool frozen;
- Conf *conf;
- const SshServerConfig *ssc;
- ssh_key *const *hostkeys;
- int nhostkeys;
- RSAKey *hostkey1;
- AuthPolicy *authpolicy;
- LogPolicy *logpolicy;
- const SftpServerVtable *sftpserver_vt;
- agentfwd *stunt_agentfwd;
- Seat seat;
- Ssh ssh;
- struct ssh_version_receiver version_receiver;
- BinaryPacketProtocol *bpp;
- PacketProtocolLayer *base_layer;
- ConnectionLayer *cl;
- #ifndef NO_GSSAPI
- struct ssh_connection_shared_gss_state gss_state;
- #endif
- };
- static void ssh_server_free_callback(void *vsrv);
- static void server_got_ssh_version(struct ssh_version_receiver *rcv,
- int major_version);
- static void server_connect_bpp(server *srv);
- static void server_bpp_output_raw_data_callback(void *vctx);
- void share_activate(ssh_sharing_state *sharestate,
- const char *server_verstring) {}
- void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
- ConnectionLayer *cl) {}
- int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; }
- void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
- const void *vpkt, int pktlen) {}
- void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
- unsigned upstream_id, unsigned server_id,
- unsigned server_currwin, unsigned server_maxpkt,
- unsigned client_adjusted_window,
- const char *peer_addr, int peer_port, int endian,
- int protomajor, int protominor,
- const void *initial_data, int initial_len) {}
- Channel *agentf_new(SshChannel *c) { return NULL; }
- bool agent_exists(void) { return false; }
- void ssh_got_exitcode(Ssh *ssh, int exitcode) {}
- void ssh_check_frozen(Ssh *ssh) {}
- mainchan *mainchan_new(
- PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
- int term_width, int term_height, bool is_simple, SshChannel **sc_out)
- { return NULL; }
- void mainchan_get_specials(
- mainchan *mc, add_special_fn_t add_special, void *ctx) {}
- void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {}
- void mainchan_terminal_size(mainchan *mc, int width, int height) {}
- /* Seat functions to ensure we don't get choosy about crypto - as the
- * server, it's not up to us to give user warnings */
- static SeatPromptResult server_confirm_weak_crypto_primitive(
- Seat *seat, SeatDialogText *text,
- void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
- { return SPR_OK; }
- static SeatPromptResult server_confirm_weak_cached_hostkey(
- Seat *seat, SeatDialogText *text,
- void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
- { return SPR_OK; }
- static const SeatVtable server_seat_vt = {
- .output = nullseat_output,
- .eof = nullseat_eof,
- .sent = nullseat_sent,
- .banner = nullseat_banner,
- .get_userpass_input = nullseat_get_userpass_input,
- .notify_session_started = nullseat_notify_session_started,
- .notify_remote_exit = nullseat_notify_remote_exit,
- .notify_remote_disconnect = nullseat_notify_remote_disconnect,
- .connection_fatal = nullseat_connection_fatal,
- .nonfatal = nullseat_nonfatal,
- .update_specials_menu = nullseat_update_specials_menu,
- .get_ttymode = nullseat_get_ttymode,
- .set_busy_status = nullseat_set_busy_status,
- .confirm_ssh_host_key = nullseat_confirm_ssh_host_key,
- .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive,
- .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey,
- .prompt_descriptions = nullseat_prompt_descriptions,
- .is_utf8 = nullseat_is_never_utf8,
- .echoedit_update = nullseat_echoedit_update,
- .get_x_display = nullseat_get_x_display,
- .get_windowid = nullseat_get_windowid,
- .get_window_pixel_size = nullseat_get_window_pixel_size,
- .stripctrl_new = nullseat_stripctrl_new,
- .set_trust_status = nullseat_set_trust_status,
- .can_set_trust_status = nullseat_can_set_trust_status_no,
- .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
- .verbose = nullseat_verbose_no,
- .interactive = nullseat_interactive_no,
- .get_cursor_position = nullseat_get_cursor_position,
- };
- static void server_closing(Plug *plug, PlugCloseType type,
- const char *error_msg)
- {
- server *srv = container_of(plug, server, plug);
- if (type != PLUGCLOSE_NORMAL) {
- ssh_remote_error(&srv->ssh, "%s", error_msg);
- } else if (srv->bpp) {
- srv->bpp->input_eof = true;
- queue_idempotent_callback(&srv->bpp->ic_in_raw);
- }
- }
- static void server_receive(
- Plug *plug, int urgent, const char *data, size_t len)
- {
- server *srv = container_of(plug, server, plug);
- /* Log raw data, if we're in that mode. */
- if (srv->logctx)
- log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len,
- 0, NULL, NULL, 0, NULL);
- bufchain_add(&srv->in_raw, data, len);
- if (!srv->frozen && srv->bpp)
- queue_idempotent_callback(&srv->bpp->ic_in_raw);
- }
- static void server_sent(Plug *plug, size_t bufsize)
- {
- #ifdef FIXME
- server *srv = container_of(plug, server, plug);
- /*
- * If the send backlog on the SSH socket itself clears, we should
- * unthrottle the whole world if it was throttled. Also trigger an
- * extra call to the consumer of the BPP's output, to try to send
- * some more data off its bufchain.
- */
- if (bufsize < SSH_MAX_BACKLOG) {
- srv_throttle_all(srv, 0, bufsize);
- queue_idempotent_callback(&srv->ic_out_raw);
- }
- #endif
- }
- LogContext *ssh_get_logctx(Ssh *ssh)
- {
- server *srv = container_of(ssh, server, ssh);
- return srv->logctx;
- }
- void ssh_sendbuffer_changed(Ssh *ssh)
- {
- }
- void ssh_throttle_conn(Ssh *ssh, int adjust)
- {
- server *srv = container_of(ssh, server, ssh);
- int old_count = srv->conn_throttle_count;
- bool frozen;
- srv->conn_throttle_count += adjust;
- assert(srv->conn_throttle_count >= 0);
- if (srv->conn_throttle_count && !old_count) {
- frozen = true;
- } else if (!srv->conn_throttle_count && old_count) {
- frozen = false;
- } else {
- return; /* don't change current frozen state */
- }
- srv->frozen = frozen;
- if (srv->socket) {
- sk_set_frozen(srv->socket, frozen);
- /*
- * Now process any SSH connection data that was stashed in our
- * queue while we were frozen.
- */
- queue_idempotent_callback(&srv->bpp->ic_in_raw);
- }
- }
- void ssh_conn_processed_data(Ssh *ssh)
- {
- /* FIXME: we could add the same check_frozen_state system as we
- * have in ssh.c, but because that was originally added to work
- * around a peculiarity of the GUI event loop, I haven't yet. */
- }
- Conf *make_ssh_server_conf(void)
- {
- Conf *conf = conf_new();
- load_open_settings(NULL, conf);
- /* In Uppity, we support even the legacy des-cbc cipher by
- * default, so that it will be available if the user forces it by
- * overriding the KEXINIT strings. If the user wants it _not_
- * supported, of course, they can override KEXINIT in the other
- * direction. */
- conf_set_bool(conf, CONF_ssh2_des_cbc, true);
- return conf;
- }
- void ssh_check_sendok(Ssh *ssh) {}
- static const PlugVtable ssh_server_plugvt = {
- .log = nullplug_log,
- .closing = server_closing,
- .receive = server_receive,
- .sent = server_sent,
- };
- Plug *ssh_server_plug(
- Conf *conf, const SshServerConfig *ssc,
- ssh_key *const *hostkeys, int nhostkeys,
- RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
- const SftpServerVtable *sftpserver_vt)
- {
- server *srv = snew(server);
- memset(srv, 0, sizeof(server));
- srv->plug.vt = &ssh_server_plugvt;
- srv->conf = conf_copy(conf);
- srv->ssc = ssc;
- srv->logctx = log_init(logpolicy, conf);
- conf_set_bool(srv->conf, CONF_ssh_no_shell, true);
- srv->nhostkeys = nhostkeys;
- srv->hostkeys = hostkeys;
- srv->hostkey1 = hostkey1;
- srv->authpolicy = authpolicy;
- srv->logpolicy = logpolicy;
- srv->sftpserver_vt = sftpserver_vt;
- srv->seat.vt = &server_seat_vt;
- bufchain_init(&srv->in_raw);
- bufchain_init(&srv->out_raw);
- bufchain_init(&srv->dummy_user_input);
- #ifndef NO_GSSAPI
- /* FIXME: replace with sensible */
- srv->gss_state.libs = snew(struct ssh_gss_liblist);
- srv->gss_state.libs->nlibraries = 0;
- #endif
- return &srv->plug;
- }
- void ssh_server_start(Plug *plug, Socket *socket)
- {
- server *srv = container_of(plug, server, plug);
- const char *our_protoversion;
- if (srv->ssc->bare_connection) {
- our_protoversion = "2.0"; /* SSH-2 only */
- } else if (srv->hostkey1 && srv->nhostkeys) {
- our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */
- } else if (srv->hostkey1) {
- our_protoversion = "1.5"; /* SSH-1 only */
- } else {
- assert(srv->nhostkeys);
- our_protoversion = "2.0"; /* SSH-2 only */
- }
- srv->socket = socket;
- srv->ic_out_raw.fn = server_bpp_output_raw_data_callback;
- srv->ic_out_raw.ctx = srv;
- srv->version_receiver.got_ssh_version = server_got_ssh_version;
- srv->bpp = ssh_verstring_new(
- srv->conf, srv->logctx, srv->ssc->bare_connection,
- our_protoversion, &srv->version_receiver,
- true, srv->ssc->application_name);
- server_connect_bpp(srv);
- queue_idempotent_callback(&srv->bpp->ic_in_raw);
- }
- static void ssh_server_free_callback(void *vsrv)
- {
- server *srv = (server *)vsrv;
- logeventf(srv->logctx, "freeing server instance");
- bufchain_clear(&srv->in_raw);
- bufchain_clear(&srv->out_raw);
- bufchain_clear(&srv->dummy_user_input);
- if (srv->socket)
- sk_close(srv->socket);
- if (srv->stunt_agentfwd)
- agentfwd_free(srv->stunt_agentfwd);
- if (srv->base_layer)
- ssh_ppl_free(srv->base_layer);
- if (srv->bpp)
- ssh_bpp_free(srv->bpp);
- delete_callbacks_for_context(srv);
- conf_free(srv->conf);
- log_free(srv->logctx);
- #ifndef NO_GSSAPI
- sfree(srv->gss_state.libs); /* FIXME: replace with sensible */
- #endif
- LogPolicy *lp = srv->logpolicy;
- sfree(srv);
- server_instance_terminated(lp);
- }
- static void server_connect_bpp(server *srv)
- {
- srv->bpp->ssh = &srv->ssh;
- srv->bpp->in_raw = &srv->in_raw;
- srv->bpp->out_raw = &srv->out_raw;
- bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw);
- srv->bpp->pls = &srv->pls;
- srv->bpp->logctx = srv->logctx;
- srv->bpp->remote_bugs = srv->remote_bugs;
- /* Servers don't really have a notion of 'unexpected' connection
- * closure. The client is free to close if it likes. */
- srv->bpp->expect_close = true;
- }
- static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl)
- {
- ppl->bpp = srv->bpp;
- ppl->logctx = srv->logctx;
- ppl->ssh = &srv->ssh;
- ppl->seat = &srv->seat;
- ppl->interactor = NULL;
- ppl->remote_bugs = srv->remote_bugs;
- }
- static void server_bpp_output_raw_data_callback(void *vctx)
- {
- server *srv = (server *)vctx;
- if (!srv->socket)
- return;
- while (bufchain_size(&srv->out_raw) > 0) {
- size_t backlog;
- ptrlen data = bufchain_prefix(&srv->out_raw);
- if (srv->logctx)
- log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len,
- 0, NULL, NULL, 0, NULL);
- backlog = sk_write(srv->socket, data.ptr, data.len);
- bufchain_consume(&srv->out_raw, data.len);
- if (backlog > SSH_MAX_BACKLOG) {
- #ifdef FIXME
- ssh_throttle_all(ssh, 1, backlog);
- #endif
- return;
- }
- }
- if (srv->pending_close) {
- sk_close(srv->socket);
- srv->socket = NULL;
- queue_toplevel_callback(ssh_server_free_callback, srv);
- }
- }
- static void server_shutdown_internal(server *srv)
- {
- /*
- * We only need to free the base PPL, which will free the others
- * (if any) transitively.
- */
- if (srv->base_layer) {
- ssh_ppl_free(srv->base_layer);
- srv->base_layer = NULL;
- }
- srv->cl = NULL;
- }
- static void server_initiate_connection_close(server *srv)
- {
- /* Wind up everything above the BPP. */
- server_shutdown_internal(srv);
- /* Force any remaining queued SSH packets through the BPP, and
- * schedule closing the network socket after they go out. */
- ssh_bpp_handle_output(srv->bpp);
- srv->pending_close = true;
- queue_idempotent_callback(&srv->ic_out_raw);
- /* Now we expect the other end to close the connection too in
- * response, so arrange that we'll receive notification of that
- * via ssh_remote_eof. */
- srv->bpp->expect_close = true;
- }
- #define GET_FORMATTED_MSG(fmt) \
- char *msg; \
- va_list ap; \
- va_start(ap, fmt); \
- msg = dupvprintf(fmt, ap); \
- va_end(ap);
- #define LOG_FORMATTED_MSG(logctx, fmt) do \
- { \
- va_list ap; \
- va_start(ap, fmt); \
- logeventvf(logctx, fmt, ap); \
- va_end(ap); \
- } while (0)
- void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
- {
- server *srv = container_of(ssh, server, ssh);
- LOG_FORMATTED_MSG(srv->logctx, fmt);
- queue_toplevel_callback(ssh_server_free_callback, srv);
- }
- void ssh_remote_eof(Ssh *ssh, const char *fmt, ...)
- {
- server *srv = container_of(ssh, server, ssh);
- LOG_FORMATTED_MSG(srv->logctx, fmt);
- queue_toplevel_callback(ssh_server_free_callback, srv);
- }
- void ssh_proto_error(Ssh *ssh, const char *fmt, ...)
- {
- server *srv = container_of(ssh, server, ssh);
- if (srv->base_layer) {
- GET_FORMATTED_MSG(fmt);
- ssh_bpp_queue_disconnect(srv->bpp, msg,
- SSH2_DISCONNECT_PROTOCOL_ERROR);
- server_initiate_connection_close(srv);
- logeventf(srv->logctx, "Protocol error: %s", msg);
- sfree(msg);
- }
- }
- void ssh_sw_abort(Ssh *ssh, const char *fmt, ...)
- {
- server *srv = container_of(ssh, server, ssh);
- LOG_FORMATTED_MSG(srv->logctx, fmt);
- queue_toplevel_callback(ssh_server_free_callback, srv);
- }
- void ssh_user_close(Ssh *ssh, const char *fmt, ...)
- {
- server *srv = container_of(ssh, server, ssh);
- ssh_bpp_handle_output(srv->bpp);
- LOG_FORMATTED_MSG(srv->logctx, fmt);
- queue_toplevel_callback(ssh_server_free_callback, srv);
- }
- static void server_got_ssh_version(struct ssh_version_receiver *rcv,
- int major_version)
- {
- server *srv = container_of(rcv, server, version_receiver);
- BinaryPacketProtocol *old_bpp;
- PacketProtocolLayer *connection_layer;
- old_bpp = srv->bpp;
- srv->remote_bugs = ssh_verstring_get_bugs(old_bpp);
- if (srv->ssc->bare_connection) {
- srv->bpp = ssh2_bare_bpp_new(srv->logctx);
- server_connect_bpp(srv);
- connection_layer = ssh2_connection_new(
- &srv->ssh, NULL, false, srv->conf,
- ssh_verstring_get_local(old_bpp), &srv->dummy_user_input,
- &srv->cl);
- ssh2connection_server_configure(connection_layer,
- srv->sftpserver_vt, srv->ssc);
- server_connect_ppl(srv, connection_layer);
- srv->base_layer = connection_layer;
- } else if (major_version == 2) {
- PacketProtocolLayer *userauth_layer, *transport_child_layer;
- srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true);
- server_connect_bpp(srv);
- connection_layer = ssh2_connection_new(
- &srv->ssh, NULL, false, srv->conf,
- ssh_verstring_get_local(old_bpp), &srv->dummy_user_input,
- &srv->cl);
- ssh2connection_server_configure(connection_layer,
- srv->sftpserver_vt, srv->ssc);
- server_connect_ppl(srv, connection_layer);
- if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) {
- userauth_layer = NULL;
- transport_child_layer = connection_layer;
- } else {
- userauth_layer = ssh2_userauth_server_new(
- connection_layer, srv->authpolicy, srv->ssc);
- server_connect_ppl(srv, userauth_layer);
- transport_child_layer = userauth_layer;
- }
- srv->base_layer = ssh2_transport_new(
- srv->conf, NULL, 0, NULL,
- ssh_verstring_get_remote(old_bpp),
- ssh_verstring_get_local(old_bpp),
- #ifndef NO_GSSAPI
- &srv->gss_state,
- #else
- NULL,
- #endif
- &srv->stats, transport_child_layer, srv->ssc);
- ssh2_transport_provide_hostkeys(
- srv->base_layer, srv->hostkeys, srv->nhostkeys);
- if (userauth_layer)
- ssh2_userauth_server_set_transport_layer(
- userauth_layer, srv->base_layer);
- server_connect_ppl(srv, srv->base_layer);
- } else {
- srv->bpp = ssh1_bpp_new(srv->logctx);
- server_connect_bpp(srv);
- connection_layer = ssh1_connection_new(
- &srv->ssh, srv->conf, &srv->dummy_user_input, &srv->cl);
- ssh1connection_server_configure(connection_layer, srv->ssc);
- server_connect_ppl(srv, connection_layer);
- srv->base_layer = ssh1_login_server_new(
- connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc);
- server_connect_ppl(srv, srv->base_layer);
- }
- /* Connect the base layer - whichever it is - to the BPP, and set
- * up its selfptr. */
- srv->base_layer->selfptr = &srv->base_layer;
- ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq);
- #ifdef FIXME // we probably will want one of these, in the end
- srv->pinger = pinger_new(srv->conf, &srv->backend);
- #endif
- if (srv->ssc->stunt_open_unconditional_agent_socket) {
- char *socketname;
- srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname);
- if (srv->stunt_agentfwd) {
- logeventf(srv->logctx, "opened unconditional agent socket at %s\n",
- socketname);
- sfree(socketname);
- }
- }
- queue_idempotent_callback(&srv->bpp->ic_in_raw);
- ssh_ppl_process_queue(srv->base_layer);
- ssh_bpp_free(old_bpp);
- }
|