diff options
| author | Ed Maste <emaste@FreeBSD.org> | 2021-04-23 19:10:38 +0000 |
|---|---|---|
| committer | Ed Maste <emaste@FreeBSD.org> | 2021-04-23 19:10:38 +0000 |
| commit | 206be79acbdeb88ea254ac622a60a4ee8015c5f6 (patch) | |
| tree | a2b2db1cfcb757172a91d03ca0c8805185a76e1b /clientloop.c | |
| parent | 3bbd8dc96b4466d8e4f850fc0adf7d02e1df2dc7 (diff) | |
Diffstat (limited to 'clientloop.c')
| -rw-r--r-- | clientloop.c | 563 |
1 files changed, 356 insertions, 207 deletions
diff --git a/clientloop.c b/clientloop.c index 60b46d1616c8..70f492f89371 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.346 2020/09/16 03:07:31 dtucker Exp $ */ +/* $OpenBSD: clientloop.c,v 1.358 2021/01/27 10:05:28 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -233,13 +233,13 @@ set_control_persist_exit_time(struct ssh *ssh) } else if (channel_still_open(ssh)) { /* some client connections are still open */ if (control_persist_exit_time > 0) - debug2("%s: cancel scheduled exit", __func__); + debug2_f("cancel scheduled exit"); control_persist_exit_time = 0; } else if (control_persist_exit_time <= 0) { /* a client connection has recently closed */ control_persist_exit_time = monotime() + (time_t)options.control_persist_timeout; - debug2("%s: schedule exit in %d seconds", __func__, + debug2_f("schedule exit in %d seconds", options.control_persist_timeout); } /* else we are already counting down to the timeout */ @@ -307,7 +307,7 @@ client_x11_get_proto(struct ssh *ssh, const char *display, if ((r = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10)) < 0 || (size_t)r >= sizeof(xdisplay)) { - error("%s: display name too long", __func__); + error_f("display name too long"); return -1; } display = xdisplay; @@ -322,15 +322,14 @@ client_x11_get_proto(struct ssh *ssh, const char *display, */ mktemp_proto(xauthdir, sizeof(xauthdir)); if (mkdtemp(xauthdir) == NULL) { - error("%s: mkdtemp: %s", - __func__, strerror(errno)); + error_f("mkdtemp: %s", strerror(errno)); return -1; } do_unlink = 1; if ((r = snprintf(xauthfile, sizeof(xauthfile), "%s/xauthfile", xauthdir)) < 0 || (size_t)r >= sizeof(xauthfile)) { - error("%s: xauthfile path too long", __func__); + error_f("xauthfile path too long"); rmdir(xauthdir); return -1; } @@ -356,7 +355,7 @@ client_x11_get_proto(struct ssh *ssh, const char *display, SSH_X11_PROTO, x11_timeout_real, _PATH_DEVNULL); } - debug2("%s: xauth command: %s", __func__, cmd); + debug2_f("xauth command: %s", cmd); if (timeout != 0 && x11_refuse_time == 0) { now = monotime() + 1; @@ -445,7 +444,7 @@ client_check_window_change(struct ssh *ssh) if (!received_window_change_signal) return; received_window_change_signal = 0; - debug2("%s: changed", __func__); + debug2_f("changed"); channel_send_window_changes(ssh); } @@ -487,7 +486,7 @@ server_alive_check(struct ssh *ssh) (r = sshpkt_put_cstring(ssh, "keepalive@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0 || /* boolean: want reply */ (r = sshpkt_send(ssh)) != 0) - fatal("%s: send packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "send packet"); /* Insert an empty placeholder to maintain ordering */ client_register_global_confirm(NULL, NULL); schedule_server_alive_check(); @@ -569,7 +568,7 @@ client_wait_until_can_do_something(struct ssh *ssh, /* Note: we might still have data in the buffers. */ if ((r = sshbuf_putf(stderr_buffer, "select: %s\r\n", strerror(errno))) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); quit_pending = 1; } else if (options.server_alive_interval > 0 && !FD_ISSET(connection_in, *readsetp) && monotime() >= server_alive_time) @@ -629,8 +628,7 @@ client_process_net_input(struct ssh *ssh, fd_set *readset) if ((r = sshbuf_putf(stderr_buffer, "Connection to %.300s closed by remote host.\r\n", host)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); quit_pending = 1; return; } @@ -650,8 +648,7 @@ client_process_net_input(struct ssh *ssh, fd_set *readset) if ((r = sshbuf_putf(stderr_buffer, "Read from remote host %.300s: %.100s\r\n", host, strerror(errno))) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); quit_pending = 1; return; } @@ -701,8 +698,7 @@ client_status_confirm(struct ssh *ssh, int type, Channel *c, void *ctx) if (tochan) { if ((r = sshbuf_put(c->extended, errmsg, strlen(errmsg))) != 0) - fatal("%s: buffer error %s", __func__, - ssh_err(r)); + fatal_fr(r, "sshbuf_put"); } else error("%s", errmsg); if (cr->action == CONFIRM_TTY) { @@ -750,8 +746,8 @@ client_register_global_confirm(global_confirm_cb *cb, void *ctx) last_gc = TAILQ_LAST(&global_confirms, global_confirms); if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) { if (++last_gc->ref_count >= INT_MAX) - fatal("%s: last_gc->ref_count = %d", - __func__, last_gc->ref_count); + fatal_f("last_gc->ref_count = %d", + last_gc->ref_count); return; } @@ -914,7 +910,7 @@ print_escape_help(struct sshbuf *b, int escape_char, int mux_client, if ((r = sshbuf_putf(b, "%c?\r\nSupported escape sequences:\r\n", escape_char)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); suppress_flags = (mux_client ? SUPPRESS_MUXCLIENT : 0) | @@ -926,14 +922,14 @@ print_escape_help(struct sshbuf *b, int escape_char, int mux_client, continue; if ((r = sshbuf_putf(b, " %c%-3s - %s\r\n", escape_char, esc_txt[i].cmd, esc_txt[i].text)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); } if ((r = sshbuf_putf(b, " %c%c - send the escape character by typing it twice\r\n" "(Note that escapes are only recognized immediately after " "newline.)\r\n", escape_char, escape_char)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); } /* @@ -973,8 +969,7 @@ process_escapes(struct ssh *ssh, Channel *c, /* Terminate the connection. */ if ((r = sshbuf_putf(berr, "%c.\r\n", efc->escape_char)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); if (c && c->ctl_chan != -1) { chan_read_failed(ssh, c); chan_write_failed(ssh, c); @@ -1003,16 +998,14 @@ process_escapes(struct ssh *ssh, Channel *c, "%c%s escape not available to " "multiplexed sessions\r\n", efc->escape_char, b)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); continue; } /* Suspend the program. Inform the user */ if ((r = sshbuf_putf(berr, "%c^Z [suspend ssh]\r\n", efc->escape_char)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); /* Restore terminal modes and suspend. */ client_suspend_self(bin, bout, berr); @@ -1023,17 +1016,15 @@ process_escapes(struct ssh *ssh, Channel *c, case 'B': if ((r = sshbuf_putf(berr, "%cB\r\n", efc->escape_char)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); channel_request_start(ssh, c->self, "break", 0); if ((r = sshpkt_put_u32(ssh, 1000)) != 0 || (r = sshpkt_send(ssh)) != 0) - fatal("%s: send packet: %s", __func__, - ssh_err(r)); + fatal_fr(r, "send packet"); continue; case 'R': - if (datafellows & SSH_BUG_NOREKEY) + if (ssh->compat & SSH_BUG_NOREKEY) logit("Server does not " "support re-keying"); else @@ -1049,8 +1040,7 @@ process_escapes(struct ssh *ssh, Channel *c, if ((r = sshbuf_putf(berr, "%c%c [Logging to syslog]\r\n", efc->escape_char, ch)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); continue; } if (ch == 'V' && options.log_level > @@ -1063,8 +1053,7 @@ process_escapes(struct ssh *ssh, Channel *c, "%c%c [LogLevel %s]\r\n", efc->escape_char, ch, log_level_name(options.log_level))) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); continue; case '&': @@ -1082,11 +1071,9 @@ process_escapes(struct ssh *ssh, Channel *c, /* Stop listening for new connections. */ channel_stop_listening(ssh); - if ((r = sshbuf_putf(berr, - "%c& [backgrounded]\n", efc->escape_char)) - != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + if ((r = sshbuf_putf(berr, "%c& " + "[backgrounded]\n", efc->escape_char)) != 0) + fatal_fr(r, "sshbuf_putf"); /* Fork into background. */ pid = fork(); @@ -1101,8 +1088,7 @@ process_escapes(struct ssh *ssh, Channel *c, /* The child continues serving connections. */ /* fake EOF on stdin */ if ((r = sshbuf_put_u8(bin, 4)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_put_u8"); return -1; case '?': print_escape_help(berr, efc->escape_char, @@ -1113,12 +1099,10 @@ process_escapes(struct ssh *ssh, Channel *c, case '#': if ((r = sshbuf_putf(berr, "%c#\r\n", efc->escape_char)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); s = channel_open_message(ssh); if ((r = sshbuf_put(berr, s, strlen(s))) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_put"); free(s); continue; @@ -1132,8 +1116,7 @@ process_escapes(struct ssh *ssh, Channel *c, if (ch != efc->escape_char) { if ((r = sshbuf_put_u8(bin, efc->escape_char)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_put_u8"); bytes++; } /* Escaped characters fall through here */ @@ -1160,7 +1143,7 @@ process_escapes(struct ssh *ssh, Channel *c, */ last_was_cr = (ch == '\r' || ch == '\n'); if ((r = sshbuf_put_u8(bin, ch)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_put_u8"); bytes++; } return bytes; @@ -1246,30 +1229,30 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, debug("pledge: id"); if (pledge("stdio rpath wpath cpath unix inet dns recvfd sendfd proc exec id tty", NULL) == -1) - fatal("%s pledge(): %s", __func__, strerror(errno)); + fatal_f("pledge(): %s", strerror(errno)); } else if (options.forward_x11 || options.permit_local_command) { debug("pledge: exec"); if (pledge("stdio rpath wpath cpath unix inet dns proc exec tty", NULL) == -1) - fatal("%s pledge(): %s", __func__, strerror(errno)); + fatal_f("pledge(): %s", strerror(errno)); } else if (options.update_hostkeys) { debug("pledge: filesystem full"); if (pledge("stdio rpath wpath cpath unix inet dns proc tty", NULL) == -1) - fatal("%s pledge(): %s", __func__, strerror(errno)); + fatal_f("pledge(): %s", strerror(errno)); } else if (!option_clear_or_none(options.proxy_command) || fork_after_authentication_flag) { debug("pledge: proc"); if (pledge("stdio cpath unix inet dns proc tty", NULL) == -1) - fatal("%s pledge(): %s", __func__, strerror(errno)); + fatal_f("pledge(): %s", strerror(errno)); } else { debug("pledge: network"); if (pledge("stdio unix inet dns proc tty", NULL) == -1) - fatal("%s pledge(): %s", __func__, strerror(errno)); + fatal_f("pledge(): %s", strerror(errno)); } start_time = monotime_double(); @@ -1285,7 +1268,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, /* Initialize buffer. */ if ((stderr_buffer = sshbuf_new()) == NULL) - fatal("%s: sshbuf_new failed", __func__); + fatal_f("sshbuf_new failed"); client_init_dispatch(ssh); @@ -1336,8 +1319,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, /* manual rekey request */ debug("need rekeying"); if ((r = kex_start_rekex(ssh)) != 0) - fatal("%s: kex_start_rekex: %s", __func__, - ssh_err(r)); + fatal_fr(r, "kex_start_rekex"); need_rekeying = 0; } else { /* @@ -1414,7 +1396,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language tag */ (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) - fatal("%s: send disconnect: %s", __func__, ssh_err(r)); + fatal_fr(r, "send disconnect"); channel_free_all(ssh); @@ -1451,7 +1433,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, if (have_pty && options.log_level != SYSLOG_LEVEL_QUIET) { if ((r = sshbuf_putf(stderr_buffer, "Connection to %.64s closed.\r\n", host)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_putf"); } /* Output any buffered data for stderr. */ @@ -1462,7 +1444,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, if (len < 0 || (u_int)len != sshbuf_len(stderr_buffer)) error("Write failed flushing stderr buffer."); else if ((r = sshbuf_consume(stderr_buffer, len)) != 0) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); + fatal_fr(r, "sshbuf_consume"); } /* Clear and free any buffers. */ @@ -1499,15 +1481,15 @@ client_request_forwarded_tcpip(struct ssh *ssh, const char *request_type, (r = sshpkt_get_cstring(ssh, &originator_address, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) - fatal("%s: parse packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "parse packet"); - debug("%s: listen %s port %d, originator %s port %d", __func__, + debug_f("listen %s port %d, originator %s port %d", listen_address, listen_port, originator_address, originator_port); if (listen_port > 0xffff) - error("%s: invalid listen port", __func__); + error_f("invalid listen port"); else if (originator_port > 0xffff) - error("%s: invalid originator port", __func__); + error_f("invalid originator port"); else { c = channel_connect_by_listen_address(ssh, listen_address, listen_port, "forwarded-tcpip", @@ -1516,7 +1498,7 @@ client_request_forwarded_tcpip(struct ssh *ssh, const char *request_type, if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) { if ((b = sshbuf_new()) == NULL) { - error("%s: alloc reply", __func__); + error_f("alloc reply"); goto out; } /* reconstruct and send to muxclient */ @@ -1531,8 +1513,7 @@ client_request_forwarded_tcpip(struct ssh *ssh, const char *request_type, (r = sshbuf_put_cstring(b, originator_address)) != 0 || (r = sshbuf_put_u32(b, originator_port)) != 0 || (r = sshbuf_put_stringb(c->output, b)) != 0) { - error("%s: compose for muxclient %s", __func__, - ssh_err(r)); + error_fr(r, "compose for muxclient"); goto out; } } @@ -1556,9 +1537,9 @@ client_request_forwarded_streamlocal(struct ssh *ssh, if ((r = sshpkt_get_cstring(ssh, &listen_path, NULL)) != 0 || (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* reserved */ (r = sshpkt_get_end(ssh)) != 0) - fatal("%s: parse packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "parse packet"); - debug("%s: request: %s", __func__, listen_path); + debug_f("request: %s", listen_path); c = channel_connect_by_listen_path(ssh, listen_path, "forwarded-streamlocal@openssh.com", "forwarded-streamlocal"); @@ -1588,7 +1569,7 @@ client_request_x11(struct ssh *ssh, const char *request_type, int rchan) if ((r = sshpkt_get_cstring(ssh, &originator, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) - fatal("%s: parse packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "parse packet"); /* XXX check permission */ /* XXX range check originator port? */ debug("client_request_x11: request from %s %u", originator, @@ -1623,8 +1604,7 @@ client_request_agent(struct ssh *ssh, const char *request_type, int rchan) } if (r != 0) { if (r != SSH_ERR_AGENT_NOT_PRESENT) - debug("%s: ssh_get_authentication_socket: %s", - __func__, ssh_err(r)); + debug_fr(r, "ssh_get_authentication_socket"); return NULL; } c = channel_new(ssh, "authentication agent connection", @@ -1785,16 +1765,15 @@ client_input_channel_req(int type, u_int32_t seq, struct ssh *ssh) exit_status = exitval; } else { /* Probably for a mux channel that has already closed */ - debug("%s: no sink for exit-status on channel %d", - __func__, id); + debug_f("no sink for exit-status on channel %d", + id); } if ((r = sshpkt_get_end(ssh)) != 0) goto out; } if (reply && c != NULL && !(c->flags & CHAN_CLOSE_SENT)) { if (!c->have_remote_id) - fatal("%s: channel %d: no remote_id", - __func__, c->self); + fatal_f("channel %d: no remote_id", c->self); if ((r = sshpkt_start(ssh, success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || @@ -1814,12 +1793,13 @@ struct hostkeys_update_ctx { /* * Keys received from the server and a flag for each indicating * whether they already exist in known_hosts. - * keys_seen is filled in by hostkeys_find() and later (for new + * keys_match is filled in by hostkeys_find() and later (for new * keys) by client_global_hostkeys_private_confirm(). */ struct sshkey **keys; - int *keys_seen; - size_t nkeys, nnew; + u_int *keys_match; /* mask of HKF_MATCH_* from hostfile.h */ + int *keys_verified; /* flag for new keys verified by server */ + size_t nkeys, nnew, nincomplete; /* total, new keys, incomplete match */ /* * Keys that are in known_hosts, but were not present in the update @@ -1828,6 +1808,12 @@ struct hostkeys_update_ctx { */ struct sshkey **old_keys; size_t nold; + + /* Various special cases. */ + int complex_hostspec; /* wildcard or manual pattern-list host name */ + int ca_available; /* saw CA key for this host */ + int old_key_seen; /* saw old key with other name/addr */ + int other_name_seen; /* saw key with other name/addr */ }; static void @@ -1840,7 +1826,8 @@ hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx) for (i = 0; i < ctx->nkeys; i++) sshkey_free(ctx->keys[i]); free(ctx->keys); - free(ctx->keys_seen); + free(ctx->keys_match); + free(ctx->keys_verified); for (i = 0; i < ctx->nold; i++) sshkey_free(ctx->old_keys[i]); free(ctx->old_keys); @@ -1849,6 +1836,30 @@ hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx) free(ctx); } +/* + * Returns non-zero if a known_hosts hostname list is not of a form that + * can be handled by UpdateHostkeys. These include wildcard hostnames and + * hostnames lists that do not follow the form host[,ip]. + */ +static int +hostspec_is_complex(const char *hosts) +{ + char *cp; + + /* wildcard */ + if (strchr(hosts, '*') != NULL || strchr(hosts, '?') != NULL) + return 1; + /* single host/ip = ok */ + if ((cp = strchr(hosts, ',')) == NULL) + return 0; + /* more than two entries on the line */ + if (strchr(cp + 1, ',') != NULL) + return 1; + /* XXX maybe parse cp+1 and ensure it is an IP? */ + return 0; +} + +/* callback to search for ctx->keys in known_hosts */ static int hostkeys_find(struct hostkey_foreach_line *l, void *_ctx) { @@ -1856,25 +1867,73 @@ hostkeys_find(struct hostkey_foreach_line *l, void *_ctx) size_t i; struct sshkey **tmp; - if (l->status != HKF_STATUS_MATCHED || l->key == NULL) + if (l->key == NULL) return 0; + if (l->status != HKF_STATUS_MATCHED) { + /* Record if one of the keys appears on a non-matching line */ + for (i = 0; i < ctx->nkeys; i++) { + if (sshkey_equal(l->key, ctx->keys[i])) { + ctx->other_name_seen = 1; + debug3_f("found %s key under different " + "name/addr at %s:%ld", + sshkey_ssh_name(ctx->keys[i]), + l->path, l->linenum); + return 0; + } + } + return 0; + } + /* Don't proceed if revocation or CA markers are present */ + /* XXX relax this */ + if (l->marker != MRK_NONE) { + debug3_f("hostkeys file %s:%ld has CA/revocation marker", + l->path, l->linenum); + ctx->complex_hostspec = 1; + return 0; + } - /* Mark off keys we've already seen for this host */ - for (i = 0; i < ctx->nkeys; i++) { - if (sshkey_equal(l->key, ctx->keys[i])) { - debug3("%s: found %s key at %s:%ld", __func__, - sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum); - ctx->keys_seen[i] = 1; + /* If CheckHostIP is enabled, then check for mismatched hostname/addr */ + if (ctx->ip_str != NULL && strchr(l->hosts, ',') != NULL) { + if ((l->match & HKF_MATCH_HOST) == 0) { + /* Record if address matched a different hostname. */ + ctx->other_name_seen = 1; + debug3_f("found address %s against different hostname " + "at %s:%ld", ctx->ip_str, l->path, l->linenum); return 0; + } else if ((l->match & HKF_MATCH_IP) == 0) { + /* Record if hostname matched a different address. */ + ctx->other_name_seen = 1; + debug3_f("found hostname %s against different address " + "at %s:%ld", ctx->host_str, l->path, l->linenum); } } + + /* + * UpdateHostkeys is skipped for wildcard host names and hostnames + * that contain more than two entries (ssh never writes these). + */ + if (hostspec_is_complex(l->hosts)) { + debug3_f("hostkeys file %s:%ld complex host specification", + l->path, l->linenum); + ctx->complex_hostspec = 1; + return 0; + } + + /* Mark off keys we've already seen for this host */ + for (i = 0; i < ctx->nkeys; i++) { + if (!sshkey_equal(l->key, ctx->keys[i])) + continue; + debug3_f("found %s key at %s:%ld", + sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum); + ctx->keys_match[i] |= l->match; + return 0; + } /* This line contained a key that not offered by the server */ - debug3("%s: deprecated %s key at %s:%ld", __func__, - sshkey_ssh_name(l->key), l->path, l->linenum); + debug3_f("deprecated %s key at %s:%ld", sshkey_ssh_name(l->key), + l->path, l->linenum); if ((tmp = recallocarray(ctx->old_keys, ctx->nold, ctx->nold + 1, sizeof(*ctx->old_keys))) == NULL) - fatal("%s: recallocarray failed nold = %zu", - __func__, ctx->nold); + fatal_f("recallocarray failed nold = %zu", ctx->nold); ctx->old_keys = tmp; ctx->old_keys[ctx->nold++] = l->key; l->key = NULL; @@ -1882,6 +1941,63 @@ hostkeys_find(struct hostkey_foreach_line *l, void *_ctx) return 0; } +/* callback to search for ctx->old_keys in known_hosts under other names */ +static int +hostkeys_check_old(struct hostkey_foreach_line *l, void *_ctx) +{ + struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; + size_t i; + int hashed; + + /* only care about lines that *don't* match the active host spec */ + if (l->status == HKF_STATUS_MATCHED || l->key == NULL) + return 0; + + hashed = l->match & (HKF_MATCH_HOST_HASHED|HKF_MATCH_IP_HASHED); + for (i = 0; i < ctx->nold; i++) { + if (!sshkey_equal(l->key, ctx->old_keys[i])) + continue; + debug3_f("found deprecated %s key at %s:%ld as %s", + sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum, + hashed ? "[HASHED]" : l->hosts); + ctx->old_key_seen = 1; + break; + } + return 0; +} + +/* + * Check known_hosts files for deprecated keys under other names. Returns 0 + * on success or -1 on failure. Updates ctx->old_key_seen if deprecated keys + * exist under names other than the active hostname/IP. + */ +static int +check_old_keys_othernames(struct hostkeys_update_ctx *ctx) +{ + size_t i; + int r; + + debug2_f("checking for %zu deprecated keys", ctx->nold); + for (i = 0; i < options.num_user_hostfiles; i++) { + debug3_f("searching %s for %s / %s", + options.user_hostfiles[i], ctx->host_str, + ctx->ip_str ? ctx->ip_str : "(none)"); + if ((r = hostkeys_foreach(options.user_hostfiles[i], + hostkeys_check_old, ctx, ctx->host_str, ctx->ip_str, + HKF_WANT_PARSE_KEY, 0)) != 0) { + if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { + debug_f("hostkeys file %s does not exist", + options.user_hostfiles[i]); + continue; + } + error_fr(r, "hostkeys_foreach failed for %s", + options.user_hostfiles[i]); + return -1; + } + } + return 0; +} + static void hostkey_change_preamble(LogLevel loglevel) { @@ -1901,11 +2017,11 @@ update_known_hosts(struct hostkeys_update_ctx *ctx) struct stat sb; for (i = 0; i < ctx->nkeys; i++) { - if (ctx->keys_seen[i] != 2) + if (!ctx->keys_verified[i]) continue; if ((fp = sshkey_fingerprint(ctx->keys[i], options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) - fatal("%s: sshkey_fingerprint failed", __func__); + fatal_f("sshkey_fingerprint failed"); if (first && asking) hostkey_change_preamble(loglevel); do_log2(loglevel, "Learned new hostkey: %s %s", @@ -1916,7 +2032,7 @@ update_known_hosts(struct hostkeys_update_ctx *ctx) for (i = 0; i < ctx->nold; i++) { if ((fp = sshkey_fingerprint(ctx->old_keys[i], options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) - fatal("%s: sshkey_fingerprint failed", __func__); + fatal_f("sshkey_fingerprint failed"); if (first && asking) hostkey_change_preamble(loglevel); do_log2(loglevel, "Deprecating obsolete hostkey: %s %s", @@ -1965,11 +2081,12 @@ update_known_hosts(struct hostkeys_update_ctx *ctx) */ if (stat(options.user_hostfiles[i], &sb) != 0) { if (errno == ENOENT) { - debug("%s: known hosts file %s does not exist", - __func__, strerror(errno)); + debug_f("known hosts file %s does not " + "exist", options.user_hostfiles[i]); } else { - error("%s: known hosts file %s inaccessible", - __func__, strerror(errno)); + error_f("known hosts file %s " + "inaccessible: %s", + options.user_hostfiles[i], strerror(errno)); } continue; } @@ -1978,8 +2095,8 @@ update_known_hosts(struct hostkeys_update_ctx *ctx) i == 0 ? ctx->keys : NULL, i == 0 ? ctx->nkeys : 0, options.hash_known_hosts, 0, options.fingerprint_hash)) != 0) { - error("%s: hostfile_replace_entries failed for %s: %s", - __func__, options.user_hostfiles[i], ssh_err(r)); + error_fr(r, "hostfile_replace_entries failed for %s", + options.user_hostfiles[i]); } } } @@ -1996,7 +2113,7 @@ client_global_hostkeys_private_confirm(struct ssh *ssh, int type, size_t siglen; if (ctx->nnew == 0) - fatal("%s: ctx->nnew == 0", __func__); /* sanity */ + fatal_f("ctx->nnew == 0"); /* sanity */ if (type != SSH2_MSG_REQUEST_SUCCESS) { error("Server failed to confirm ownership of " "private host keys"); @@ -2007,31 +2124,26 @@ client_global_hostkeys_private_confirm(struct ssh *ssh, int type, sshkey_type_from_name(ssh->kex->hostkey_alg)); if ((signdata = sshbuf_new()) == NULL) - fatal("%s: sshbuf_new failed", __func__); - /* Don't want to accidentally accept an unbound signature */ - if (ssh->kex->session_id_len == 0) - fatal("%s: ssh->kex->session_id_len == 0", __func__); + fatal_f("sshbuf_new failed"); /* * Expect a signature for each of the ctx->nnew private keys we * haven't seen before. They will be in the same order as the - * ctx->keys where the corresponding ctx->keys_seen[i] == 0. + * ctx->keys where the corresponding ctx->keys_match[i] == 0. */ for (ndone = i = 0; i < ctx->nkeys; i++) { - if (ctx->keys_seen[i]) + if (ctx->keys_match[i]) continue; /* Prepare data to be signed: session ID, unique string, key */ sshbuf_reset(signdata); if ( (r = sshbuf_put_cstring(signdata, "hostkeys-prove-00@openssh.com")) != 0 || - (r = sshbuf_put_string(signdata, ssh->kex->session_id, - ssh->kex->session_id_len)) != 0 || + (r = sshbuf_put_stringb(signdata, + ssh->kex->session_id)) != 0 || (r = sshkey_puts(ctx->keys[i], signdata)) != 0) - fatal("%s: failed to prepare signature: %s", - __func__, ssh_err(r)); + fatal_fr(r, "compose signdata"); /* Extract and verify signature */ if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) { - error("%s: couldn't parse message: %s", - __func__, ssh_err(r)); + error_fr(r, "parse sig"); goto out; } /* @@ -2044,19 +2156,19 @@ client_global_hostkeys_private_confirm(struct ssh *ssh, int type, sshbuf_ptr(signdata), sshbuf_len(signdata), use_kexsigtype ? ssh->kex->hostkey_alg : NULL, 0, NULL)) != 0) { - error("%s: server gave bad signature for %s key %zu", - __func__, sshkey_type(ctx->keys[i]), i); + error_f("server gave bad signature for %s key %zu", + sshkey_type(ctx->keys[i]), i); goto out; } /* Key is good. Mark it as 'seen' */ - ctx->keys_seen[i] = 2; + ctx->keys_verified[i] = 1; ndone++; } + /* Shouldn't happen */ if (ndone != ctx->nnew) - fatal("%s: ndone != ctx->nnew (%zu / %zu)", __func__, - ndone, ctx->nnew); /* Shouldn't happen */ + fatal_f("ndone != ctx->nnew (%zu / %zu)", ndone, ctx->nnew); if ((r = sshpkt_get_end(ssh)) != 0) { - error("%s: protocol error", __func__); + error_f("protocol error"); goto out; } @@ -2102,9 +2214,10 @@ client_input_hostkeys(struct ssh *ssh) static int hostkeys_seen = 0; /* XXX use struct ssh */ extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */ struct hostkeys_update_ctx *ctx = NULL; + u_int want; if (hostkeys_seen) - fatal("%s: server already sent hostkeys", __func__); + fatal_f("server already sent hostkeys"); if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK && options.batch_mode) return 1; /* won't ask in batchmode, so don't even try */ @@ -2116,59 +2229,59 @@ client_input_hostkeys(struct ssh *ssh) sshkey_free(key); key = NULL; if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) { - error("%s: couldn't parse message: %s", - __func__, ssh_err(r)); + error_fr(r, "parse key"); goto out; } if ((r = sshkey_from_blob(blob, len, &key)) != 0) { - do_log2(r == SSH_ERR_KEY_TYPE_UNKNOWN ? + do_log2_fr(r, r == SSH_ERR_KEY_TYPE_UNKNOWN ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_ERROR, - "%s: parse key: %s", __func__, ssh_err(r)); + "convert key"); continue; } fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); - debug3("%s: received %s key %s", __func__, - sshkey_type(key), fp); + debug3_f("received %s key %s", sshkey_type(key), fp); free(fp); if (!key_accepted_by_hostkeyalgs(key)) { - debug3("%s: %s key not permitted by HostkeyAlgorithms", - __func__, sshkey_ssh_name(key)); + debug3_f("%s key not permitted by " + "HostkeyAlgorithms", sshkey_ssh_name(key)); continue; } /* Skip certs */ if (sshkey_is_cert(key)) { - debug3("%s: %s key is a certificate; skipping", - __func__, sshkey_ssh_name(key)); + debug3_f("%s key is a certificate; skipping", + sshkey_ssh_name(key)); continue; } /* Ensure keys are unique */ for (i = 0; i < ctx->nkeys; i++) { if (sshkey_equal(key, ctx->keys[i])) { - error("%s: received duplicated %s host key", - __func__, sshkey_ssh_name(key)); + error_f("received duplicated %s host key", + sshkey_ssh_name(key)); goto out; } } /* Key is good, record it */ if ((tmp = recallocarray(ctx->keys, ctx->nkeys, ctx->nkeys + 1, sizeof(*ctx->keys))) == NULL) - fatal("%s: recallocarray failed nkeys = %zu", - __func__, ctx->nkeys); + fatal_f("recallocarray failed nkeys = %zu", + ctx->nkeys); ctx->keys = tmp; ctx->keys[ctx->nkeys++] = key; key = NULL; } if (ctx->nkeys == 0) { - debug("%s: server sent no hostkeys", __func__); + debug_f("server sent no hostkeys"); goto out; } - if ((ctx->keys_seen = calloc(ctx->nkeys, - sizeof(*ctx->keys_seen))) == NULL) - fatal("%s: calloc failed", __func__); + if ((ctx->keys_match = calloc(ctx->nkeys, + sizeof(*ctx->keys_match))) == NULL || + (ctx->keys_verified = calloc(ctx->nkeys, + sizeof(*ctx->keys_verified))) == NULL) + fatal_f("calloc failed"); get_hostfile_hostname_ipaddr(host, options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL, @@ -2177,68 +2290,107 @@ client_input_hostkeys(struct ssh *ssh) /* Find which keys we already know about. */ for (i = 0; i < options.num_user_hostfiles; i++) { - debug("%s: searching %s for %s / %s", __func__, + debug_f("searching %s for %s / %s", options.user_hostfiles[i], ctx->host_str, ctx->ip_str ? ctx->ip_str : "(none)"); if ((r = hostkeys_foreach(options.user_hostfiles[i], hostkeys_find, ctx, ctx->host_str, ctx->ip_str, - HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) { + HKF_WANT_PARSE_KEY, 0)) != 0) { if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { - debug("%s: hostkeys file %s does not exist", - __func__, options.user_hostfiles[i]); + debug_f("hostkeys file %s does not exist", + options.user_hostfiles[i]); continue; } - error("%s: hostkeys_foreach failed for %s: %s", - __func__, options.user_hostfiles[i], ssh_err(r)); + error_fr(r, "hostkeys_foreach failed for %s", + options.user_hostfiles[i]); goto out; } } /* Figure out if we have any new keys to add */ - ctx->nnew = 0; + ctx->nnew = ctx->nincomplete = 0; + want = HKF_MATCH_HOST | ( options.check_host_ip ? HKF_MATCH_IP : 0); for (i = 0; i < ctx->nkeys; i++) { - if (!ctx->keys_seen[i]) + if (ctx->keys_match[i] == 0) ctx->nnew++; + if ((ctx->keys_match[i] & want) != want) + ctx->nincomplete++; } - debug3("%s: %zu keys from server: %zu new, %zu retained. %zu to remove", - __func__, ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew, ctx->nold); + debug3_f("%zu server keys: %zu new, %zu retained, " + "%zu incomplete match. %zu to remove", ctx->nkeys, ctx->nnew, + ctx->nkeys - ctx->nnew - ctx->nincomplete, + ctx->nincomplete, ctx->nold); - if (ctx->nnew == 0 && ctx->nold != 0) { - /* We have some keys to remove. Just do it. */ - update_known_hosts(ctx); - } else if (ctx->nnew != 0) { + if (ctx->nnew == 0 && ctx->nold == 0) { + debug_f("no new or deprecated keys from server"); + goto out; + } + + /* Various reasons why we cannot proceed with the update */ + if (ctx->complex_hostspec) { + debug_f("CA/revocation marker, manual host list or wildcard " + "host pattern found, skipping UserKnownHostsFile update"); + goto out; + } + if (ctx->other_name_seen) { + debug_f("host key found matching a different name/address, " + "skipping UserKnownHostsFile update"); + goto out; + } + /* + * If removing keys, check whether they appear under different + * names/addresses and refuse to proceed if they do. This avoids + * cases such as hosts with multiple names becoming inconsistent + * with regards to CheckHostIP entries. + * XXX UpdateHostkeys=force to override this (and other) checks? + */ + if (ctx->nold != 0) { + if (check_old_keys_othernames(ctx) != 0) + goto out; /* error already logged */ + if (ctx->old_key_seen) { + debug_f("key(s) for %s%s%s exist under other names; " + "skipping UserKnownHostsFile update", + ctx->host_str, ctx->ip_str == NULL ? "" : ",", + ctx->ip_str == NULL ? "" : ctx->ip_str); + goto out; + } + } + + if (ctx->nnew == 0) { /* - * We have received hitherto-unseen keys from the server. - * Ask the server to confirm ownership of the private halves. + * We have some keys to remove or fix matching for. + * We can proceed to do this without requiring a fresh proof + * from the server. */ - debug3("%s: asking server to prove ownership for %zu keys", - __func__, ctx->nnew); - if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || - (r = sshpkt_put_cstring(ssh, - "hostkeys-prove-00@openssh.com")) != 0 || - (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */ - fatal("%s: cannot prepare packet: %s", - __func__, ssh_err(r)); - if ((buf = sshbuf_new()) == NULL) - fatal("%s: sshbuf_new", __func__); - for (i = 0; i < ctx->nkeys; i++) { - if (ctx->keys_seen[i]) - continue; - sshbuf_reset(buf); - if ((r = sshkey_putb(ctx->keys[i], buf)) != 0) - fatal("%s: sshkey_putb: %s", - __func__, ssh_err(r)); - if ((r = sshpkt_put_stringb(ssh, buf)) != 0) - fatal("%s: sshpkt_put_string: %s", - __func__, ssh_err(r)); - } - if ((r = sshpkt_send(ssh)) != 0) - fatal("%s: sshpkt_send: %s", __func__, ssh_err(r)); - client_register_global_confirm( - client_global_hostkeys_private_confirm, ctx); - ctx = NULL; /* will be freed in callback */ + update_known_hosts(ctx); + goto out; + } + /* + * We have received previously-unseen keys from the server. + * Ask the server to confirm ownership of the private halves. + */ + debug3_f("asking server to prove ownership for %zu keys", ctx->nnew); + if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || + (r = sshpkt_put_cstring(ssh, + "hostkeys-prove-00@openssh.com")) != 0 || + (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */ + fatal_fr(r, "prepare hostkeys-prove"); + if ((buf = sshbuf_new()) == NULL) + fatal_f("sshbuf_new"); + for (i = 0; i < ctx->nkeys; i++) { + if (ctx->keys_match[i]) + continue; + sshbuf_reset(buf); + if ((r = sshkey_putb(ctx->keys[i], buf)) != 0 || + (r = sshpkt_put_stringb(ssh, buf)) != 0) + fatal_fr(r, "assemble hostkeys-prove"); } + if ((r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "send hostkeys-prove"); + client_register_global_confirm( + client_global_hostkeys_private_confirm, ctx); + ctx = NULL; /* will be freed in callback */ /* Success */ out: @@ -2279,6 +2431,19 @@ client_input_global_request(int type, u_int32_t seq, struct ssh *ssh) return r; } +static void +client_send_env(struct ssh *ssh, int id, const char *name, const char *val) +{ + int r; + + debug("channel %d: setting env %s = \"%s\"", id, name, val); + channel_request_start(ssh, id, "env", 0); + if ((r = sshpkt_put_cstring(ssh, name)) != 0 || + (r = sshpkt_put_cstring(ssh, val)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "send setenv"); +} + void client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, const char *term, struct termios *tiop, int in_fd, struct sshbuf *cmd, @@ -2288,10 +2453,10 @@ client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, char *name, *val; Channel *c = NULL; - debug2("%s: id %d", __func__, id); + debug2_f("id %d", id); if ((c = channel_lookup(ssh, id)) == NULL) - fatal("%s: channel %d: unknown channel", __func__, id); + fatal_f("channel %d: unknown channel", id); ssh_packet_set_interactive(ssh, want_tty, options.ip_qos_interactive, options.ip_qos_bulk); @@ -2311,12 +2476,12 @@ client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, (r = sshpkt_put_u32(ssh, (u_int)ws.ws_row)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_xpixel)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_ypixel)) != 0) - fatal("%s: build packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "build pty-req"); if (tiop == NULL) tiop = get_saved_tio(); ssh_tty_make_modes(ssh, -1, tiop); if ((r = sshpkt_send(ssh)) != 0) - fatal("%s: send packet: %s", __func__, ssh_err(r)); + fatal_fr(r, "send pty-req"); /* XXX wait for reply */ c->client_tty = 1; } @@ -2345,15 +2510,7 @@ client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, free(name); continue; } - - debug("Sending env %s = %s", name, val); - channel_request_start(ssh, id, "env", 0); - if ((r = sshpkt_put_cstring(ssh, name)) != 0 || - (r = sshpkt_put_cstring(ssh, val)) != 0 || - (r = sshpkt_send(ssh)) != 0) { - fatal("%s: send packet: %s", - __func__, ssh_err(r)); - } + client_send_env(ssh, id, name, val); free(name); } } @@ -2365,13 +2522,7 @@ client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, continue; } *val++ = '\0'; - - debug("Setting env %s = %s", name, val); - channel_request_start(ssh, id, "env", 0); - if ((r = sshpkt_put_cstring(ssh, name)) != 0 || - (r = sshpkt_put_cstring(ssh, val)) != 0 || - (r = sshpkt_send(ssh)) != 0) - fatal("%s: send packet: %s", __func__, ssh_err(r)); + client_send_env(ssh, id, name, val); free(name); } @@ -2393,14 +2544,12 @@ client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, } if ((r = sshpkt_put_stringb(ssh, cmd)) != 0 || (r = sshpkt_send(ssh)) != 0) - fatal("%s: send command: %s", __func__, ssh_err(r)); + fatal_fr(r, "send command"); } else { channel_request_start(ssh, id, "shell", 1); client_expect_confirm(ssh, id, "shell", CONFIRM_CLOSE); - if ((r = sshpkt_send(ssh)) != 0) { - fatal("%s: send shell request: %s", - __func__, ssh_err(r)); - } + if ((r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "send shell"); } } |
