=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/ssh-agent.c,v retrieving revision 1.280 retrieving revision 1.281 diff -u -r1.280 -r1.281 --- src/usr.bin/ssh/ssh-agent.c 2021/12/19 22:09:23 1.280 +++ src/usr.bin/ssh/ssh-agent.c 2021/12/19 22:11:39 1.281 @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-agent.c,v 1.280 2021/12/19 22:09:23 djm Exp $ */ +/* $OpenBSD: ssh-agent.c,v 1.281 2021/12/19 22:11:39 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -78,6 +78,7 @@ #include "pathnames.h" #include "ssh-pkcs11.h" #include "sk-api.h" +#include "myproposal.h" #ifndef DEFAULT_ALLOWED_PROVIDERS # define DEFAULT_ALLOWED_PROVIDERS "/usr/lib*/*,/usr/local/lib*/*" @@ -91,6 +92,8 @@ #define AGENT_MAX_SESSION_IDS 16 /* Maximum size of session ID */ #define AGENT_MAX_SID_LEN 128 +/* Maximum number of destination constraints to accept on a key */ +#define AGENT_MAX_DEST_CONSTRAINTS 1024 /* XXX store hostkey_sid in a refcounted tree */ @@ -127,6 +130,8 @@ time_t death; u_int confirm; char *sk_provider; + struct dest_constraint *dest_constraints; + size_t ndest_constraints; } Identity; struct idtable { @@ -199,15 +204,253 @@ } static void +free_dest_constraint_hop(struct dest_constraint_hop *dch) +{ + u_int i; + + if (dch == NULL) + return; + free(dch->user); + free(dch->hostname); + for (i = 0; i < dch->nkeys; i++) + sshkey_free(dch->keys[i]); + free(dch->keys); + free(dch->key_is_ca); +} + +static void +free_dest_constraints(struct dest_constraint *dcs, size_t ndcs) +{ + size_t i; + + for (i = 0; i < ndcs; i++) { + free_dest_constraint_hop(&dcs[i].from); + free_dest_constraint_hop(&dcs[i].to); + } + free(dcs); +} + +static void free_identity(Identity *id) { sshkey_free(id->key); free(id->provider); free(id->comment); free(id->sk_provider); + free_dest_constraints(id->dest_constraints, id->ndest_constraints); free(id); } +/* + * Match 'key' against the key/CA list in a destination constraint hop + * Returns 0 on success or -1 otherwise. + */ +static int +match_key_hop(const char *tag, const struct sshkey *key, + const struct dest_constraint_hop *dch) +{ + const char *reason = NULL; + u_int i; + char *fp; + + if (key == NULL) + return -1; + /* XXX logspam */ + if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + debug3_f("%s: entering hostname %s, requested key %s %s, %u keys avail", + tag, dch->hostname, sshkey_type(key), fp, dch->nkeys); + free(fp); + for (i = 0; i < dch->nkeys; i++) { + if (dch->keys[i] == NULL) + return -1; + /* XXX logspam */ + if ((fp = sshkey_fingerprint(dch->keys[i], SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + debug3_f("%s: key %u: %s%s %s", tag, i, + dch->key_is_ca[i] ? "CA " : "", + sshkey_type(dch->keys[i]), fp); + free(fp); + if (!sshkey_is_cert(key)) { + /* plain key */ + if (dch->key_is_ca[i] || + !sshkey_equal(key, dch->keys[i])) + continue; + return 0; + } + /* certificate */ + if (!dch->key_is_ca[i]) + continue; + if (key->cert == NULL || key->cert->signature_key == NULL) + return -1; /* shouldn't happen */ + if (!sshkey_equal(key->cert->signature_key, dch->keys[i])) + continue; + if (sshkey_cert_check_host(key, dch->hostname, 1, + SSH_ALLOWED_CA_SIGALGS, &reason) != 0) { + debug_f("cert %s / hostname %s rejected: %s", + key->cert->key_id, dch->hostname, reason); + continue; + } + return 0; + } + return -1; +} + +/* Check destination constraints on an identity against the hostkey/user */ +static int +permitted_by_dest_constraints(const struct sshkey *fromkey, + const struct sshkey *tokey, Identity *id, const char *user, + const char **hostnamep) +{ + size_t i; + struct dest_constraint *d; + + if (hostnamep != NULL) + *hostnamep = NULL; + for (i = 0; i < id->ndest_constraints; i++) { + d = id->dest_constraints + i; + /* XXX remove logspam */ + debug2_f("constraint %zu %s%s%s (%u keys) > %s%s%s (%u keys)", + i, d->from.user ? d->from.user : "", + d->from.user ? "@" : "", + d->from.hostname ? d->from.hostname : "(ORIGIN)", + d->from.nkeys, + d->to.user ? d->to.user : "", d->to.user ? "@" : "", + d->to.hostname ? d->to.hostname : "(ANY)", d->to.nkeys); + + /* Match 'from' key */ + if (fromkey == NULL) { + /* We are matching the first hop */ + if (d->from.hostname != NULL || d->from.nkeys != 0) + continue; + } else if (match_key_hop("from", fromkey, &d->from) != 0) + continue; + + /* Match 'to' key */ + if (tokey != NULL && match_key_hop("to", tokey, &d->to) != 0) + continue; + + /* Match user if specified */ + if (d->to.user != NULL && user != NULL && + !match_pattern(user, d->to.user)) + continue; + + /* successfully matched this constraint */ + if (hostnamep != NULL) + *hostnamep = d->to.hostname; + debug2_f("allowed for hostname %s", + d->to.hostname == NULL ? "*" : d->to.hostname); + return 0; + } + /* no match */ + debug2_f("%s identity \"%s\" not permitted for this destination", + sshkey_type(id->key), id->comment); + return -1; +} + +/* + * Check whether hostkeys on a SocketEntry and the optionally specified user + * are permitted by the destination constraints on the Identity. + * Returns 0 on success or -1 otherwise. + */ +static int +identity_permitted(Identity *id, SocketEntry *e, char *user, + const char **forward_hostnamep, const char **last_hostnamep) +{ + size_t i; + const char **hp; + struct hostkey_sid *hks; + const struct sshkey *fromkey = NULL; + const char *test_user; + char *fp1, *fp2; + + /* XXX remove logspam */ + debug3_f("entering: key %s comment \"%s\", %zu socket bindings, " + "%zu constraints", sshkey_type(id->key), id->comment, + e->nsession_ids, id->ndest_constraints); + if (id->ndest_constraints == 0) + return 0; /* unconstrained */ + if (e->nsession_ids == 0) + return 0; /* local use */ + /* + * Walk through the hops recorded by session_id and try to find a + * constraint that satisfies each. + */ + for (i = 0; i < e->nsession_ids; i++) { + hks = e->session_ids + i; + if (hks->key == NULL) + fatal_f("internal error: no bound key"); + /* XXX remove logspam */ + fp1 = fp2 = NULL; + if (fromkey != NULL && + (fp1 = sshkey_fingerprint(fromkey, SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + if ((fp2 = sshkey_fingerprint(hks->key, SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + debug3_f("socketentry fd=%d, entry %zu %s, " + "from hostkey %s %s to user %s hostkey %s %s", + e->fd, i, hks->forwarded ? "FORWARD" : "AUTH", + fromkey ? sshkey_type(fromkey) : "(ORIGIN)", + fromkey ? fp1 : "", user ? user : "(ANY)", + sshkey_type(hks->key), fp2); + free(fp1); + free(fp2); + /* + * Record the hostnames for the initial forwarding and + * the final destination. + */ + hp = NULL; + if (i == e->nsession_ids - 1) + hp = last_hostnamep; + else if (i == 0) + hp = forward_hostnamep; + /* Special handling for final recorded binding */ + test_user = NULL; + if (i == e->nsession_ids - 1) { + /* Can only check user at final hop */ + test_user = user; + /* + * user is only presented for signature requests. + * If this is the case, make sure last binding is not + * for a forwarding. + */ + if (hks->forwarded && user != NULL) { + error_f("tried to sign on forwarding hop"); + return -1; + } + } else if (!hks->forwarded) { + error_f("tried to forward though signing bind"); + return -1; + } + if (permitted_by_dest_constraints(fromkey, hks->key, id, + test_user, hp) != 0) + return -1; + fromkey = hks->key; + } + /* + * Another special case: if the last bound session ID was for a + * forwarding, and this function is not being called to check a sign + * request (i.e. no 'user' supplied), then only permit the key if + * there is a permission that would allow it to be used at another + * destination. This hides keys that are allowed to be used to + * authenicate *to* a host but not permitted for *use* beyond it. + */ + hks = &e->session_ids[e->nsession_ids - 1]; + if (hks->forwarded && user == NULL && + permitted_by_dest_constraints(hks->key, NULL, id, + NULL, NULL) != 0) { + debug3_f("key permitted at host but not after"); + return -1; + } + + /* success */ + return 0; +} + /* return matching private key for given public key */ static Identity * lookup_identity(struct sshkey *key) @@ -255,27 +498,36 @@ process_request_identities(SocketEntry *e) { Identity *id; - struct sshbuf *msg; + struct sshbuf *msg, *keys; int r; + u_int nentries = 0; debug2_f("entering"); - if ((msg = sshbuf_new()) == NULL) + if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); - if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || - (r = sshbuf_put_u32(msg, idtab->nentries)) != 0) - fatal_fr(r, "compose"); TAILQ_FOREACH(id, &idtab->idlist, next) { - if ((r = sshkey_puts_opts(id->key, msg, + /* identity not visible, don't include in response */ + if (identity_permitted(id, e, NULL, NULL, NULL) != 0) + continue; + if ((r = sshkey_puts_opts(id->key, keys, SSHKEY_SERIALIZE_INFO)) != 0 || - (r = sshbuf_put_cstring(msg, id->comment)) != 0) { + (r = sshbuf_put_cstring(keys, id->comment)) != 0) { error_fr(r, "compose key/comment"); continue; } + nentries++; } + debug2_f("replying with %u allowed of %u available keys", + nentries, idtab->nentries); + if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || + (r = sshbuf_put_u32(msg, nentries)) != 0 || + (r = sshbuf_putb(msg, keys)) != 0) + fatal_fr(r, "compose"); if ((r = sshbuf_put_stringb(e->output, msg)) != 0) fatal_fr(r, "enqueue"); sshbuf_free(msg); + sshbuf_free(keys); } @@ -446,10 +698,11 @@ process_sign_request2(SocketEntry *e) { u_char *signature = NULL; - size_t i, slen = 0; + size_t slen = 0; u_int compat = 0, flags; int r, ok = -1; char *fp = NULL, *user = NULL, *sig_dest = NULL; + const char *fwd_host = NULL, *dest_host = NULL; struct sshbuf *msg = NULL, *data = NULL, *sid = NULL; struct sshkey *key = NULL; struct identity *id; @@ -470,31 +723,41 @@ verbose_f("%s key not found", sshkey_type(key)); goto send; } - /* - * If session IDs were recorded for this socket, then use them to - * annotate the confirmation messages with the host keys. - */ - if (e->nsession_ids > 0 && - parse_userauth_request(data, key, &user, &sid) == 0) { + if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + + if (id->ndest_constraints != 0) { + if (e->nsession_ids == 0) { + logit_f("refusing use of destination-constrained key " + "to sign on unbound connection"); + goto send; + } + if (parse_userauth_request(data, key, &user, &sid) != 0) { + logit_f("refusing use of destination-constrained key " + "to sign an unidentified signature"); + goto send; + } + /* XXX logspam */ + debug_f("user=%s", user); + if (identity_permitted(id, e, user, &fwd_host, &dest_host) != 0) + goto send; + /* XXX display fwd_host/dest_host in askpass UI */ /* - * session ID from userauth request should match the final - * ID in the list recorded in the socket, unless the ssh - * client at that point lacks the binding extension (or if - * an attacker is trying to steal use of the agent). + * Ensure that the session ID is the most recent one + * registered on the socket - it should have been bound by + * ssh immediately before userauth. */ - i = e->nsession_ids - 1; - if (buf_equal(sid, e->session_ids[i].sid) == 0) { - if ((fp = sshkey_fingerprint(e->session_ids[i].key, - SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) - fatal_f("fingerprint failed"); - debug3_f("destination %s %s (slot %zu)", - sshkey_type(e->session_ids[i].key), fp, i); - xasprintf(&sig_dest, "public key request for " - "target user \"%s\" to %s %s", user, - sshkey_type(e->session_ids[i].key), fp); - free(fp); - fp = NULL; + if (buf_equal(sid, + e->session_ids[e->nsession_ids - 1].sid) != 0) { + error_f("unexpected session ID (%zu listed) on " + "signature request for target user %s with " + "key %s %s", e->nsession_ids, user, + sshkey_type(id->key), fp); + goto send; } + xasprintf(&sig_dest, "public key authentication request for " + "user \"%s\" to listed host", user); } if (id->confirm && confirm_key(id, sig_dest) != 0) { verbose_f("user refused key"); @@ -507,9 +770,6 @@ goto send; } if ((id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD)) { - if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, - SSH_FP_DEFAULT)) == NULL) - fatal_f("fingerprint failed"); notifier = notify_start(0, "Confirm user presence for key %s %s%s%s", sshkey_type(id->key), fp, @@ -566,6 +826,9 @@ debug_f("key not found"); goto done; } + /* identity not visible, cannot be removed */ + if (identity_permitted(id, e, NULL, NULL, NULL) != 0) + goto done; /* error already logged */ /* We have this key, free it. */ if (idtab->nentries < 1) fatal_f("internal error: nentries %d", idtab->nentries); @@ -625,10 +888,119 @@ } static int -parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp) +parse_dest_constraint_hop(struct sshbuf *b, struct dest_constraint_hop *dch) { + u_char key_is_ca; + size_t elen = 0; + int r; + struct sshkey *k = NULL; + char *fp; + + memset(dch, '\0', sizeof(*dch)); + if ((r = sshbuf_get_cstring(b, &dch->user, NULL)) != 0 || + (r = sshbuf_get_cstring(b, &dch->hostname, NULL)) != 0 || + (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { + error_fr(r, "parse"); + goto out; + } + if (elen != 0) { + error_f("unsupported extensions (len %zu)", elen); + r = SSH_ERR_FEATURE_UNSUPPORTED; + goto out; + } + if (*dch->hostname == '\0') { + free(dch->hostname); + dch->hostname = NULL; + } + if (*dch->user == '\0') { + free(dch->user); + dch->user = NULL; + } + while (sshbuf_len(b) != 0) { + dch->keys = xrecallocarray(dch->keys, dch->nkeys, + dch->nkeys + 1, sizeof(*dch->keys)); + dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys, + dch->nkeys + 1, sizeof(*dch->key_is_ca)); + if ((r = sshkey_froms(b, &k)) != 0 || + (r = sshbuf_get_u8(b, &key_is_ca)) != 0) + goto out; + if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, + SSH_FP_DEFAULT)) == NULL) + fatal_f("fingerprint failed"); + debug3_f("%s%s%s: adding %skey %s %s", + dch->user == NULL ? "" : dch->user, + dch->user == NULL ? "" : "@", + dch->hostname, key_is_ca ? "CA " : "", sshkey_type(k), fp); + free(fp); + dch->keys[dch->nkeys] = k; + dch->key_is_ca[dch->nkeys] = key_is_ca != 0; + dch->nkeys++; + k = NULL; /* transferred */ + } + /* success */ + r = 0; + out: + sshkey_free(k); + return r; +} + +static int +parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc) +{ + struct sshbuf *b = NULL, *frombuf = NULL, *tobuf = NULL; + int r; + size_t elen = 0; + + debug3_f("entering"); + + memset(dc, '\0', sizeof(*dc)); + if ((r = sshbuf_froms(m, &b)) != 0 || + (r = sshbuf_froms(b, &frombuf)) != 0 || + (r = sshbuf_froms(b, &tobuf)) != 0 || + (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { + error_fr(r, "parse"); + goto out; + } + if ((r = parse_dest_constraint_hop(frombuf, &dc->from) != 0) || + (r = parse_dest_constraint_hop(tobuf, &dc->to) != 0)) + goto out; /* already logged */ + if (elen != 0) { + error_f("unsupported extensions (len %zu)", elen); + r = SSH_ERR_FEATURE_UNSUPPORTED; + goto out; + } + debug2_f("parsed %s (%u keys) > %s%s%s (%u keys)", + dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys, + dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "", + dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys); + /* check consistency */ + if ((dc->from.hostname == NULL) != (dc->from.nkeys == 0) || + dc->from.user != NULL) { + error_f("inconsistent \"from\" specification"); + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + if (dc->to.hostname == NULL || dc->to.nkeys == 0) { + error_f("incomplete \"to\" specification"); + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + /* success */ + r = 0; + out: + sshbuf_free(b); + sshbuf_free(frombuf); + sshbuf_free(tobuf); + return r; +} + +static int +parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, + struct dest_constraint **dcsp, size_t *ndcsp) +{ char *ext_name = NULL; int r; + struct sshbuf *b = NULL; if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) { error_fr(r, "parse constraint extension"); @@ -650,6 +1022,27 @@ error_fr(r, "parse %s", ext_name); goto out; } + } else if (strcmp(ext_name, + "restrict-destination-v00@openssh.com") == 0) { + if (*dcsp != NULL) { + error_f("%s already set", ext_name); + goto out; + } + if ((r = sshbuf_froms(m, &b)) != 0) { + error_fr(r, "parse %s outer", ext_name); + goto out; + } + while (sshbuf_len(b) != 0) { + if (*ndcsp >= AGENT_MAX_DEST_CONSTRAINTS) { + error_f("too many %s constraints", ext_name); + goto out; + } + *dcsp = xrecallocarray(*dcsp, *ndcsp, *ndcsp + 1, + sizeof(**dcsp)); + if ((r = parse_dest_constraint(b, + *dcsp + (*ndcsp)++)) != 0) + goto out; /* error already logged */ + } } else { error_f("unsupported constraint \"%s\"", ext_name); r = SSH_ERR_FEATURE_UNSUPPORTED; @@ -659,12 +1052,14 @@ r = 0; out: free(ext_name); + sshbuf_free(b); return r; } static int parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, - u_int *secondsp, int *confirmp, char **sk_providerp) + u_int *secondsp, int *confirmp, char **sk_providerp, + struct dest_constraint **dcsp, size_t *ndcsp) { u_char ctype; int r; @@ -719,7 +1114,7 @@ break; case SSH_AGENT_CONSTRAIN_EXTENSION: if ((r = parse_key_constraint_extension(m, - sk_providerp)) != 0) + sk_providerp, dcsp, ndcsp)) != 0) goto out; /* error already logged */ break; default: @@ -743,6 +1138,8 @@ char canonical_provider[PATH_MAX]; time_t death = 0; u_int seconds = 0; + struct dest_constraint *dest_constraints = NULL; + size_t ndest_constraints = 0; struct sshkey *k = NULL; int r = SSH_ERR_INTERNAL_ERROR; @@ -754,7 +1151,7 @@ goto out; } if (parse_key_constraints(e->request, k, &death, &seconds, &confirm, - &sk_provider) != 0) { + &sk_provider, &dest_constraints, &ndest_constraints) != 0) { error_f("failed to parse constraints"); sshbuf_reset(e->request); goto out; @@ -797,10 +1194,15 @@ /* Increment the number of identities. */ idtab->nentries++; } else { + /* identity not visible, do not update */ + if (identity_permitted(id, e, NULL, NULL, NULL) != 0) + goto out; /* error already logged */ /* key state might have been updated */ sshkey_free(id->key); free(id->comment); free(id->sk_provider); + free_dest_constraints(id->dest_constraints, + id->ndest_constraints); } /* success */ id->key = k; @@ -808,23 +1210,29 @@ id->death = death; id->confirm = confirm; id->sk_provider = sk_provider; + id->dest_constraints = dest_constraints; + id->ndest_constraints = ndest_constraints; if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); debug_f("add %s %s \"%.100s\" (life: %u) (confirm: %u) " - "(provider: %s)", sshkey_ssh_name(k), fp, comment, seconds, - confirm, sk_provider == NULL ? "none" : sk_provider); + "(provider: %s) (destination constraints: %zu)", + sshkey_ssh_name(k), fp, comment, seconds, confirm, + sk_provider == NULL ? "none" : sk_provider, ndest_constraints); free(fp); /* transferred */ k = NULL; comment = NULL; sk_provider = NULL; + dest_constraints = NULL; + ndest_constraints = 0; success = 1; out: free(sk_provider); free(comment); sshkey_free(k); + free_dest_constraints(dest_constraints, ndest_constraints); send_status(e, success); } @@ -907,6 +1315,8 @@ time_t death = 0; struct sshkey **keys = NULL, *k; Identity *id; + struct dest_constraint *dest_constraints = NULL; + size_t ndest_constraints = 0; debug2_f("entering"); if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || @@ -915,7 +1325,7 @@ goto send; } if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm, - NULL) != 0) { + NULL, &dest_constraints, &ndest_constraints) != 0) { error_f("failed to parse constraints"); goto send; } @@ -949,6 +1359,10 @@ } id->death = death; id->confirm = confirm; + id->dest_constraints = dest_constraints; + id->ndest_constraints = ndest_constraints; + dest_constraints = NULL; /* transferred */ + ndest_constraints = 0; TAILQ_INSERT_TAIL(&idtab->idlist, id, next); idtab->nentries++; success = 1; @@ -962,6 +1376,7 @@ free(provider); free(keys); free(comments); + free_dest_constraints(dest_constraints, ndest_constraints); send_status(e, success); } @@ -1015,8 +1430,8 @@ struct sshkey *key = NULL; struct sshbuf *sid = NULL, *sig = NULL; char *fp = NULL; - u_char fwd; size_t i; + u_char fwd = 0; debug2_f("entering"); if ((r = sshkey_froms(e->request, &key)) != 0 || @@ -1037,6 +1452,12 @@ } /* check whether sid/key already recorded */ for (i = 0; i < e->nsession_ids; i++) { + if (!e->session_ids[i].forwarded) { + error_f("attempt to bind session ID to socket " + "previously bound for authentication attempt"); + r = -1; + goto out; + } sid_match = buf_equal(sid, e->session_ids[i].sid) == 0; key_match = sshkey_equal(key, e->session_ids[i].key); if (sid_match && key_match) {