diff --git a/openssh-9.6p1-gssapi-keyex.patch b/openssh-9.6p1-gssapi-keyex.patch index ef1f97e..5875943 100644 --- a/openssh-9.6p1-gssapi-keyex.patch +++ b/openssh-9.6p1-gssapi-keyex.patch @@ -1,6 +1,6 @@ diff --color -ruNp a/auth2.c b/auth2.c ---- a/auth2.c 2024-09-16 11:45:56.858133241 +0200 -+++ b/auth2.c 2024-09-16 11:46:34.688939755 +0200 +--- a/auth2.c 2026-03-13 12:32:48.463830672 +0100 ++++ b/auth2.c 2026-03-13 12:16:40.013559000 +0100 @@ -71,6 +71,7 @@ extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; @@ -18,8 +18,8 @@ diff --color -ruNp a/auth2.c b/auth2.c #endif &method_passwd, diff --color -ruNp a/auth2-gss.c b/auth2-gss.c ---- a/auth2-gss.c 2024-09-16 11:45:56.858133241 +0200 -+++ b/auth2-gss.c 2024-09-16 11:46:34.689939776 +0200 +--- a/auth2-gss.c 2026-03-13 12:32:48.464158978 +0100 ++++ b/auth2-gss.c 2026-03-13 12:16:40.011579476 +0100 @@ -51,6 +51,7 @@ #define SSH_GSSAPI_MAX_MECHS 2048 @@ -108,8 +108,8 @@ diff --color -ruNp a/auth2-gss.c b/auth2-gss.c &methodcfg_gssapi, userauth_gssapi, diff --color -ruNp a/auth2-methods.c b/auth2-methods.c ---- a/auth2-methods.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/auth2-methods.c 2024-09-16 11:46:34.689939776 +0200 +--- a/auth2-methods.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/auth2-methods.c 2026-03-13 12:16:40.011621502 +0100 @@ -50,6 +50,11 @@ struct authmethod_cfg methodcfg_pubkey = &options.pubkey_authentication }; @@ -131,8 +131,8 @@ diff --color -ruNp a/auth2-methods.c b/auth2-methods.c #endif &methodcfg_passwd, diff --color -ruNp a/auth.c b/auth.c ---- a/auth.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/auth.c 2024-09-16 11:46:34.690939798 +0200 +--- a/auth.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/auth.c 2026-03-13 12:16:39.971971452 +0100 @@ -356,7 +356,8 @@ auth_root_allowed(struct ssh *ssh, const case PERMIT_NO_PASSWD: if (strcmp(method, "publickey") == 0 || @@ -144,8 +144,8 @@ diff --color -ruNp a/auth.c b/auth.c break; case PERMIT_FORCED_ONLY: diff --color -ruNp a/canohost.c b/canohost.c ---- a/canohost.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/canohost.c 2024-09-16 11:46:34.690939798 +0200 +--- a/canohost.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/canohost.c 2026-03-13 12:16:39.971614349 +0100 @@ -35,6 +35,99 @@ #include "canohost.h" #include "misc.h" @@ -247,8 +247,8 @@ diff --color -ruNp a/canohost.c b/canohost.c ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) { diff --color -ruNp a/canohost.h b/canohost.h ---- a/canohost.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/canohost.h 2024-09-16 11:46:34.690939798 +0200 +--- a/canohost.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/canohost.h 2026-03-13 12:16:39.973010227 +0100 @@ -15,6 +15,9 @@ #ifndef _CANOHOST_H #define _CANOHOST_H @@ -260,8 +260,8 @@ diff --color -ruNp a/canohost.h b/canohost.h int get_peer_port(int); char *get_local_ipaddr(int); diff --color -ruNp a/clientloop.c b/clientloop.c ---- a/clientloop.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/clientloop.c 2024-09-16 11:46:34.690939798 +0200 +--- a/clientloop.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/clientloop.c 2026-03-13 12:16:39.973067475 +0100 @@ -115,6 +115,10 @@ #include "ssherr.h" #include "hostfile.h" @@ -289,8 +289,8 @@ diff --color -ruNp a/clientloop.c b/clientloop.c if (conn_in_ready) client_process_net_input(ssh); diff --color -ruNp a/configure.ac b/configure.ac ---- a/configure.ac 2024-09-16 11:45:56.870133497 +0200 -+++ b/configure.ac 2024-09-16 11:46:34.691939819 +0200 +--- a/configure.ac 2026-03-13 12:32:48.476497111 +0100 ++++ b/configure.ac 2026-03-13 12:16:39.974067978 +0100 @@ -774,6 +774,30 @@ int main(void) { if (NSVersionOfRunTimeL [Use tunnel device compatibility to OpenBSD]) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], @@ -323,8 +323,8 @@ diff --color -ruNp a/configure.ac b/configure.ac AC_CHECK_DECL([AU_IPv4], [], AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) diff --color -ruNp a/gss-genr.c b/gss-genr.c ---- a/gss-genr.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-genr.c 2024-09-16 11:46:34.708940181 +0200 +--- a/gss-genr.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/gss-genr.c 2026-03-13 12:16:40.012672759 +0100 @@ -42,9 +42,33 @@ #include "sshbuf.h" #include "log.h" @@ -710,8 +710,8 @@ diff --color -ruNp a/gss-genr.c b/gss-genr.c + #endif /* GSSAPI */ diff --color -ruNp a/gss-serv.c b/gss-serv.c ---- a/gss-serv.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-serv.c 2024-09-16 11:46:34.692939840 +0200 +--- a/gss-serv.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/gss-serv.c 2026-03-13 12:16:39.978787818 +0100 @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */ @@ -1004,8 +1004,8 @@ diff --color -ruNp a/gss-serv.c b/gss-serv.c /* Privileged */ diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c ---- a/gss-serv-krb5.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-serv-krb5.c 2024-09-16 11:46:34.692939840 +0200 +--- a/gss-serv-krb5.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/gss-serv-krb5.c 2026-03-13 12:16:39.978934056 +0100 @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */ @@ -1143,8 +1143,8 @@ diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c #endif /* KRB5 */ diff --color -ruNp a/kex.c b/kex.c ---- a/kex.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex.c 2024-09-16 11:46:34.692939840 +0200 +--- a/kex.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/kex.c 2026-03-13 12:16:39.978857653 +0100 @@ -297,17 +297,37 @@ static int kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) { @@ -1200,8 +1200,8 @@ diff --color -ruNp a/kex.c b/kex.c sshkey_free(kex->initial_hostkey); free(kex->failed_choice); diff --color -ruNp a/kexdh.c b/kexdh.c ---- a/kexdh.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kexdh.c 2024-09-16 11:46:34.693939862 +0200 +--- a/kexdh.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/kexdh.c 2026-03-13 12:16:39.979537228 +0100 @@ -49,13 +49,23 @@ kex_dh_keygen(struct kex *kex) { switch (kex->kex_type) { @@ -1227,8 +1227,8 @@ diff --color -ruNp a/kexdh.c b/kexdh.c break; case KEX_DH_GRP18_SHA512: diff --color -ruNp a/kexgen.c b/kexgen.c ---- a/kexgen.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kexgen.c 2024-09-16 11:46:34.693939862 +0200 +--- a/kexgen.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/kexgen.c 2026-03-13 12:16:39.979737335 +0100 @@ -44,7 +44,7 @@ static int input_kex_gen_init(int, u_int32_t, struct ssh *); static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh); @@ -1240,7 +1240,7 @@ diff --color -ruNp a/kexgen.c b/kexgen.c const struct sshbuf *client_version, diff --color -ruNp a/kexgssc.c b/kexgssc.c --- a/kexgssc.c 1970-01-01 01:00:00.000000000 +0100 -+++ b/kexgssc.c 2024-10-14 15:18:02.491798105 +0200 ++++ b/kexgssc.c 2026-03-13 12:25:23.115812190 +0100 @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. @@ -1369,7 +1369,7 @@ diff --color -ruNp a/kexgssc.c b/kexgssc.c + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) -+ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ssh_packet_disconnect(ssh, "Hash's MIC didn't verify"); + + gss_release_buffer(&gss->minor, &gss->msg_tok); + @@ -1592,10 +1592,10 @@ diff --color -ruNp a/kexgssc.c b/kexgssc.c + fatal("Failed to read token: %s", ssh_err(r)); + /* If we're already complete - protocol error */ + if (gss->major == GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ ssh_packet_disconnect(ssh, "Protocol error: received token when complete"); + } else { + if (gss->major != GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ ssh_packet_disconnect(ssh, "Protocol error: did not receive final token"); + } + if ((r = sshpkt_get_end(ssh)) != 0) + fatal("Expecting end of packet."); @@ -1731,7 +1731,7 @@ diff --color -ruNp a/kexgssc.c b/kexgssc.c + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) -+ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ssh_packet_disconnect(ssh, "Hash's MIC didn't verify"); + + gss_release_buffer(&gss->minor, &gss->msg_tok); + @@ -1932,10 +1932,10 @@ diff --color -ruNp a/kexgssc.c b/kexgssc.c + fatal("Failed to read token: %s", ssh_err(r)); + /* If we're already complete - protocol error */ + if (gss->major == GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ ssh_packet_disconnect(ssh, "Protocol error: received token when complete"); + } else { + if (gss->major != GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ ssh_packet_disconnect(ssh, "Protocol error: did not receive final token"); + } + if ((r = sshpkt_get_end(ssh)) != 0) + fatal("Expecting end of packet."); @@ -1950,8 +1950,8 @@ diff --color -ruNp a/kexgssc.c b/kexgssc.c +#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ diff --color -ruNp a/kexgsss.c b/kexgsss.c --- a/kexgsss.c 1970-01-01 01:00:00.000000000 +0100 -+++ b/kexgsss.c 2024-10-14 15:18:02.491798105 +0200 -@@ -0,0 +1,601 @@ ++++ b/kexgsss.c 2026-03-13 12:32:17.556172591 +0100 +@@ -0,0 +1,603 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * @@ -2083,7 +2083,7 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c +{ + struct kex *kex = ssh->kex; + Gssctxt *gss = kex->gss; -+ gss_buffer_desc msg_tok; ++ gss_buffer_desc msg_tok = GSS_C_EMPTY_BUFFER; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + struct sshbuf *shared_secret = NULL; @@ -2167,7 +2167,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + Gssctxt *gss = kex->gss; + struct sshbuf *empty; + struct sshbuf *client_pubkey = NULL; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + OM_uint32 ret_flags = 0; + int r; + @@ -2243,7 +2244,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + struct ssh *ssh) +{ + Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + OM_uint32 ret_flags = 0; + int r; + @@ -2334,7 +2336,7 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c +{ + struct kex *kex = ssh->kex; + Gssctxt *gss = kex->gss; -+ gss_buffer_desc msg_tok; ++ gss_buffer_desc msg_tok = GSS_C_EMPTY_BUFFER; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + const BIGNUM *pub_key, *dh_p, *dh_g; @@ -2475,10 +2477,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + fatal("GSS_GEX, bad parameters: %d !< %d !< %d", min, nbits, max); + + kex->dh = mm_choose_dh(min, nbits, max); -+ if (kex->dh == NULL) { -+ sshpkt_disconnect(ssh, "Protocol error: no matching group found"); -+ fatal("Protocol error: no matching group found"); -+ } ++ if (kex->dh == NULL) ++ ssh_packet_disconnect(ssh, "Protocol error: no matching group found"); + + DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || @@ -2510,7 +2510,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + struct ssh *ssh) +{ + Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + OM_uint32 ret_flags = 0; + int r; + @@ -2537,7 +2538,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + struct ssh *ssh) +{ + Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + OM_uint32 ret_flags = 0; + int r; + @@ -2554,8 +2556,8 @@ diff --color -ruNp a/kexgsss.c b/kexgsss.c + +#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ diff --color -ruNp a/kex.h b/kex.h ---- a/kex.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex.h 2024-09-16 11:46:34.710940224 +0200 +--- a/kex.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/kex.h 2026-03-13 12:16:40.013688997 +0100 @@ -29,6 +29,10 @@ #include "mac.h" #include "crypto_api.h" @@ -2567,7 +2569,7 @@ diff --color -ruNp a/kex.h b/kex.h #ifdef WITH_OPENSSL # include # include -@@ -102,6 +106,15 @@ enum kex_exchange { +@@ -103,6 +107,15 @@ enum kex_exchange { KEX_C25519_SHA256, KEX_KEM_SNTRUP761X25519_SHA512, KEX_KEM_MLKEM768X25519_SHA256, @@ -2583,7 +2585,7 @@ diff --color -ruNp a/kex.h b/kex.h KEX_MAX }; -@@ -164,6 +177,13 @@ struct kex { +@@ -165,6 +178,13 @@ struct kex { u_int flags; int hash_alg; int ec_nid; @@ -2597,7 +2599,7 @@ diff --color -ruNp a/kex.h b/kex.h char *failed_choice; int (*verify_host_key)(struct sshkey *, struct ssh *); struct sshkey *(*load_host_public_key)(int, int, struct ssh *); -@@ -189,8 +209,10 @@ int kex_hash_from_name(const char *); +@@ -191,8 +211,10 @@ int kex_hash_from_name(const char *); int kex_nid_from_name(const char *); int kex_names_valid(const char *); char *kex_alg_list(char); @@ -2608,7 +2610,7 @@ diff --color -ruNp a/kex.h b/kex.h int kex_assemble_names(char **, const char *, const char *); void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], const char *, const char *, const char *, const char *, const char *); -@@ -224,6 +246,12 @@ int kexgex_client(struct ssh *); +@@ -226,6 +248,12 @@ int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); int kex_gen_client(struct ssh *); int kex_gen_server(struct ssh *); @@ -2621,7 +2623,7 @@ diff --color -ruNp a/kex.h b/kex.h int kex_dh_keypair(struct kex *); int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, -@@ -256,6 +284,12 @@ int kexgex_hash(int, const struct sshbu +@@ -264,6 +292,12 @@ int kexgex_hash(int, const struct sshbu const BIGNUM *, const u_char *, size_t, u_char *, size_t *); @@ -2635,8 +2637,8 @@ diff --color -ruNp a/kex.h b/kex.h __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); diff --color -ruNp a/kex-names.c b/kex-names.c ---- a/kex-names.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex-names.c 2024-09-16 11:46:34.694939883 +0200 +--- a/kex-names.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/kex-names.c 2026-03-13 12:16:39.979603048 +0100 @@ -45,6 +45,10 @@ #include "ssherr.h" #include "xmalloc.h" @@ -2648,7 +2650,7 @@ diff --color -ruNp a/kex-names.c b/kex-names.c struct kexalg { char *name; u_int type; -@@ -83,15 +87,28 @@ static const struct kexalg kexalgs[] = { +@@ -89,15 +93,28 @@ static const struct kexalg kexalgs[] = { #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ { NULL, 0, -1, -1}, }; @@ -2680,7 +2682,7 @@ diff --color -ruNp a/kex-names.c b/kex-names.c if (ret != NULL) ret[rlen++] = sep; nlen = strlen(k->name); -@@ -106,6 +123,18 @@ kex_alg_list(char sep) +@@ -112,6 +129,18 @@ kex_alg_list(char sep) return ret; } @@ -2699,7 +2701,7 @@ diff --color -ruNp a/kex-names.c b/kex-names.c static const struct kexalg * kex_alg_by_name(const char *name) { -@@ -115,6 +144,10 @@ kex_alg_by_name(const char *name) +@@ -121,6 +150,10 @@ kex_alg_by_name(const char *name) if (strcmp(k->name, name) == 0) return k; } @@ -2710,7 +2712,7 @@ diff --color -ruNp a/kex-names.c b/kex-names.c return NULL; } -@@ -328,3 +361,26 @@ kex_assemble_names(char **listp, const c +@@ -334,3 +367,26 @@ kex_assemble_names(char **listp, const c free(ret); return r; } @@ -2738,8 +2740,8 @@ diff --color -ruNp a/kex-names.c b/kex-names.c + return 1; +} diff --color -ruNp a/Makefile.in b/Makefile.in ---- a/Makefile.in 2024-09-16 11:45:56.868133454 +0200 -+++ b/Makefile.in 2024-09-16 11:46:34.695939904 +0200 +--- a/Makefile.in 2026-03-13 12:32:48.475081074 +0100 ++++ b/Makefile.in 2026-03-13 12:16:39.979453307 +0100 @@ -114,6 +114,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexgexc.o kexgexs.o \ @@ -2767,9 +2769,9 @@ diff --color -ruNp a/Makefile.in b/Makefile.in regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \ diff --color -ruNp a/monitor.c b/monitor.c ---- a/monitor.c 2024-09-16 11:45:56.861133305 +0200 -+++ b/monitor.c 2024-09-16 11:46:34.696939926 +0200 -@@ -143,6 +143,8 @@ int mm_answer_gss_setup_ctx(struct ssh * +--- a/monitor.c 2026-03-13 12:32:48.467311058 +0100 ++++ b/monitor.c 2026-03-13 12:16:40.012477799 +0100 +@@ -144,6 +144,8 @@ int mm_answer_gss_setup_ctx(struct ssh * int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); @@ -2778,7 +2780,7 @@ diff --color -ruNp a/monitor.c b/monitor.c #endif #ifdef SSH_AUDIT_EVENTS -@@ -219,11 +221,18 @@ struct mon_table mon_dispatch_proto20[] +@@ -220,11 +222,18 @@ struct mon_table mon_dispatch_proto20[] {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, @@ -2797,7 +2799,7 @@ diff --color -ruNp a/monitor.c b/monitor.c #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, #endif -@@ -292,6 +301,10 @@ monitor_child_preauth(struct ssh *ssh, s +@@ -293,6 +302,10 @@ monitor_child_preauth(struct ssh *ssh, s /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); @@ -2808,7 +2810,7 @@ diff --color -ruNp a/monitor.c b/monitor.c /* The first few requests do not require asynchronous access */ while (!authenticated) { -@@ -344,8 +357,15 @@ monitor_child_preauth(struct ssh *ssh, s +@@ -345,8 +358,15 @@ monitor_child_preauth(struct ssh *ssh, s if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { auth_log(ssh, authenticated, partial, auth_method, auth_submethod); @@ -2825,7 +2827,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if (authenticated || partial) { auth2_update_session_info(authctxt, auth_method, auth_submethod); -@@ -413,6 +433,10 @@ monitor_child_postauth(struct ssh *ssh, +@@ -414,6 +434,10 @@ monitor_child_postauth(struct ssh *ssh, monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); @@ -2836,7 +2838,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if (auth_opts->permit_pty_flag) { monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); -@@ -1793,6 +1817,17 @@ monitor_apply_keystate(struct ssh *ssh, +@@ -1803,6 +1827,17 @@ monitor_apply_keystate(struct ssh *ssh, # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif @@ -2854,7 +2856,7 @@ diff --color -ruNp a/monitor.c b/monitor.c #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; -@@ -1885,8 +1920,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, +@@ -1896,8 +1931,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, u_char *p; int r; @@ -2865,7 +2867,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if ((r = sshbuf_get_string(m, &p, &len)) != 0) fatal_fr(r, "parse"); -@@ -1918,8 +1953,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh +@@ -1929,8 +1964,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh OM_uint32 flags = 0; /* GSI needs this */ int r; @@ -2876,7 +2878,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) fatal_fr(r, "ssh_gssapi_get_buffer_desc"); -@@ -1939,6 +1974,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh +@@ -1950,6 +1985,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); @@ -2884,7 +2886,7 @@ diff --color -ruNp a/monitor.c b/monitor.c } return (0); } -@@ -1950,8 +1986,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, +@@ -1961,8 +1997,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, OM_uint32 ret; int r; @@ -2895,7 +2897,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) -@@ -1977,13 +2013,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, +@@ -1988,13 +2024,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) { @@ -2917,7 +2919,7 @@ diff --color -ruNp a/monitor.c b/monitor.c sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) -@@ -1992,7 +2032,11 @@ mm_answer_gss_userok(struct ssh *ssh, in +@@ -2003,7 +2043,11 @@ mm_answer_gss_userok(struct ssh *ssh, in debug3_f("sending result %d", authenticated); mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); @@ -2930,7 +2932,7 @@ diff --color -ruNp a/monitor.c b/monitor.c if ((displayname = ssh_gssapi_displayname()) != NULL) auth2_record_info(authctxt, "%s", displayname); -@@ -2000,5 +2044,84 @@ mm_answer_gss_userok(struct ssh *ssh, in +@@ -2011,5 +2055,84 @@ mm_answer_gss_userok(struct ssh *ssh, in /* Monitor loop will terminate if authenticated */ return (authenticated); } @@ -3016,8 +3018,8 @@ diff --color -ruNp a/monitor.c b/monitor.c #endif /* GSSAPI */ diff --color -ruNp a/monitor.h b/monitor.h ---- a/monitor.h 2024-09-16 11:45:56.861133305 +0200 -+++ b/monitor.h 2024-09-16 11:46:34.696939926 +0200 +--- a/monitor.h 2026-03-13 12:32:48.467853845 +0100 ++++ b/monitor.h 2026-03-13 12:16:40.011929029 +0100 @@ -67,6 +67,8 @@ enum monitor_reqtype { MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, @@ -3028,8 +3030,8 @@ diff --color -ruNp a/monitor.h b/monitor.h struct ssh; diff --color -ruNp a/monitor_wrap.c b/monitor_wrap.c ---- a/monitor_wrap.c 2024-09-16 11:45:56.862133326 +0200 -+++ b/monitor_wrap.c 2024-09-16 11:46:34.697939947 +0200 +--- a/monitor_wrap.c 2026-03-13 12:32:48.468148305 +0100 ++++ b/monitor_wrap.c 2026-03-13 12:16:40.011969272 +0100 @@ -1075,13 +1075,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss } @@ -3108,8 +3110,8 @@ diff --color -ruNp a/monitor_wrap.c b/monitor_wrap.c /* diff --color -ruNp a/monitor_wrap.h b/monitor_wrap.h ---- a/monitor_wrap.h 2024-09-16 11:45:56.862133326 +0200 -+++ b/monitor_wrap.h 2024-09-16 11:46:34.697939947 +0200 +--- a/monitor_wrap.h 2026-03-13 12:32:48.468446940 +0100 ++++ b/monitor_wrap.h 2026-03-13 12:16:40.012015851 +0100 @@ -67,8 +67,10 @@ void mm_decode_activate_server_options(s OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, @@ -3123,8 +3125,8 @@ diff --color -ruNp a/monitor_wrap.h b/monitor_wrap.h #ifdef USE_PAM diff --color -ruNp a/readconf.c b/readconf.c ---- a/readconf.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/readconf.c 2024-09-16 11:46:34.699939990 +0200 +--- a/readconf.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/readconf.c 2026-03-13 12:16:40.012058137 +0100 @@ -70,6 +70,7 @@ #include "uidswap.h" #include "myproposal.h" @@ -3165,7 +3167,7 @@ diff --color -ruNp a/readconf.c b/readconf.c #endif #ifdef ENABLE_PKCS11 { "pkcs11provider", oPKCS11Provider }, -@@ -1227,10 +1242,42 @@ parse_time: +@@ -1256,10 +1271,42 @@ parse_time: intptr = &options->gss_authentication; goto parse_flag; @@ -3208,7 +3210,7 @@ diff --color -ruNp a/readconf.c b/readconf.c case oBatchMode: intptr = &options->batch_mode; goto parse_flag; -@@ -2542,7 +2589,13 @@ initialize_options(Options * options) +@@ -2576,7 +2623,13 @@ initialize_options(Options * options) options->fwd_opts.streamlocal_bind_unlink = -1; options->pubkey_authentication = -1; options->gss_authentication = -1; @@ -3222,7 +3224,7 @@ diff --color -ruNp a/readconf.c b/readconf.c options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; -@@ -2705,8 +2758,18 @@ fill_default_options(Options * options) +@@ -2739,8 +2792,18 @@ fill_default_options(Options * options) options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; if (options->gss_authentication == -1) options->gss_authentication = 0; @@ -3241,7 +3243,7 @@ diff --color -ruNp a/readconf.c b/readconf.c if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) -@@ -3533,7 +3596,14 @@ dump_client_config(Options *o, const cha +@@ -3567,7 +3630,14 @@ dump_client_config(Options *o, const cha dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); #ifdef GSSAPI dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); @@ -3257,8 +3259,8 @@ diff --color -ruNp a/readconf.c b/readconf.c dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); diff --color -ruNp a/readconf.h b/readconf.h ---- a/readconf.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/readconf.h 2024-09-16 11:46:34.699939990 +0200 +--- a/readconf.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/readconf.h 2026-03-13 12:16:39.991140430 +0100 @@ -40,7 +40,13 @@ typedef struct { int pubkey_authentication; /* Try ssh2 pubkey authentication. */ int hostbased_authentication; /* ssh2's rhosts_rsa */ @@ -3274,8 +3276,8 @@ diff --color -ruNp a/readconf.h b/readconf.h * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ diff --color -ruNp a/servconf.c b/servconf.c ---- a/servconf.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/servconf.c 2024-09-16 11:46:34.700940011 +0200 +--- a/servconf.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/servconf.c 2026-03-13 12:16:39.991185528 +0100 @@ -68,6 +68,7 @@ #include "auth.h" #include "myproposal.h" @@ -3296,7 +3298,7 @@ diff --color -ruNp a/servconf.c b/servconf.c options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->permit_empty_passwd = -1; -@@ -376,10 +380,18 @@ fill_default_server_options(ServerOption +@@ -378,10 +382,18 @@ fill_default_server_options(ServerOption options->kerberos_get_afs_token = 0; if (options->gss_authentication == -1) options->gss_authentication = 0; @@ -3315,7 +3317,7 @@ diff --color -ruNp a/servconf.c b/servconf.c if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) -@@ -558,6 +570,7 @@ typedef enum { +@@ -564,6 +576,7 @@ typedef enum { sPerSourcePenalties, sPerSourcePenaltyExemptList, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, @@ -3323,7 +3325,7 @@ diff --color -ruNp a/servconf.c b/servconf.c sAcceptEnv, sSetEnv, sPermitTunnel, sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, -@@ -643,12 +656,22 @@ static struct { +@@ -649,12 +662,22 @@ static struct { #ifdef GSSAPI { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, @@ -3346,7 +3348,7 @@ diff --color -ruNp a/servconf.c b/servconf.c { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ -@@ -1585,6 +1608,10 @@ process_server_config_line_depth(ServerO +@@ -1605,6 +1628,10 @@ process_server_config_line_depth(ServerO intptr = &options->gss_authentication; goto parse_flag; @@ -3357,7 +3359,7 @@ diff --color -ruNp a/servconf.c b/servconf.c case sGssCleanupCreds: intptr = &options->gss_cleanup_creds; goto parse_flag; -@@ -1593,6 +1620,22 @@ process_server_config_line_depth(ServerO +@@ -1613,6 +1640,22 @@ process_server_config_line_depth(ServerO intptr = &options->gss_strict_acceptor; goto parse_flag; @@ -3380,7 +3382,7 @@ diff --color -ruNp a/servconf.c b/servconf.c case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; -@@ -3178,6 +3221,10 @@ dump_config(ServerOptions *o) +@@ -3204,6 +3247,10 @@ dump_config(ServerOptions *o) #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); @@ -3392,9 +3394,9 @@ diff --color -ruNp a/servconf.c b/servconf.c dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, diff --color -ruNp a/servconf.h b/servconf.h ---- a/servconf.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/servconf.h 2024-09-16 11:46:34.700940011 +0200 -@@ -149,8 +149,11 @@ typedef struct { +--- a/servconf.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/servconf.h 2026-03-13 12:16:40.004993534 +0100 +@@ -150,8 +150,11 @@ typedef struct { int kerberos_get_afs_token; /* If true, try to get AFS token if * authenticated with Kerberos. */ int gss_authentication; /* If true, permit GSSAPI authentication */ @@ -3407,8 +3409,8 @@ diff --color -ruNp a/servconf.h b/servconf.h * authentication. */ int kbd_interactive_authentication; /* If true, permit */ diff --color -ruNp a/session.c b/session.c ---- a/session.c 2024-09-16 11:45:56.866133411 +0200 -+++ b/session.c 2024-09-16 11:46:34.701940032 +0200 +--- a/session.c 2026-03-13 12:32:48.472280104 +0100 ++++ b/session.c 2026-03-13 12:16:40.005034524 +0100 @@ -2674,13 +2674,19 @@ do_cleanup(struct ssh *ssh, Authctxt *au #ifdef KRB5 @@ -3432,9 +3434,9 @@ diff --color -ruNp a/session.c b/session.c /* remove agent socket */ diff --color -ruNp a/ssh.1 b/ssh.1 ---- a/ssh.1 2024-09-16 11:45:56.875133603 +0200 -+++ b/ssh.1 2024-09-16 11:46:34.701940032 +0200 -@@ -536,7 +536,13 @@ For full details of the options listed b +--- a/ssh.1 2026-03-13 12:32:48.479681434 +0100 ++++ b/ssh.1 2026-03-13 12:16:40.012217780 +0100 +@@ -538,7 +538,13 @@ For full details of the options listed b .It GatewayPorts .It GlobalKnownHostsFile .It GSSAPIAuthentication @@ -3448,7 +3450,7 @@ diff --color -ruNp a/ssh.1 b/ssh.1 .It HashKnownHosts .It Host .It HostbasedAcceptedAlgorithms -@@ -624,6 +630,8 @@ flag), +@@ -626,6 +632,8 @@ flag), (supported message integrity codes), .Ar kex (key exchange algorithms), @@ -3458,8 +3460,8 @@ diff --color -ruNp a/ssh.1 b/ssh.1 (key types), .Ar key-ca-sign diff --color -ruNp a/ssh.c b/ssh.c ---- a/ssh.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh.c 2024-09-16 11:46:34.702940054 +0200 +--- a/ssh.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/ssh.c 2026-03-13 12:16:40.012768046 +0100 @@ -827,6 +827,8 @@ main(int ac, char **av) else if (strcmp(optarg, "kex") == 0 || strcasecmp(optarg, "KexAlgorithms") == 0) @@ -3481,8 +3483,8 @@ diff --color -ruNp a/ssh.c b/ssh.c if (cp == NULL) fatal("Unsupported query \"%s\"", optarg); diff --color -ruNp a/ssh_config b/ssh_config ---- a/ssh_config 2024-09-16 11:45:56.884133795 +0200 -+++ b/ssh_config 2024-09-16 11:46:34.702940054 +0200 +--- a/ssh_config 2026-03-13 12:32:48.487976307 +0100 ++++ b/ssh_config 2026-03-13 12:16:40.007769377 +0100 @@ -24,6 +24,8 @@ # HostbasedAuthentication no # GSSAPIAuthentication no @@ -3493,8 +3495,8 @@ diff --color -ruNp a/ssh_config b/ssh_config # CheckHostIP no # AddressFamily any diff --color -ruNp a/ssh_config.5 b/ssh_config.5 ---- a/ssh_config.5 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh_config.5 2024-09-16 11:46:34.703940075 +0200 +--- a/ssh_config.5 2024-09-20 00:20:48.000000000 +0200 ++++ b/ssh_config.5 2026-03-13 12:16:40.013000257 +0100 @@ -938,10 +938,68 @@ The default is Specifies whether user authentication based on GSSAPI is allowed. The default is @@ -3565,8 +3567,8 @@ diff --color -ruNp a/ssh_config.5 b/ssh_config.5 Indicates that .Xr ssh 1 diff --color -ruNp a/sshconnect2.c b/sshconnect2.c ---- a/sshconnect2.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshconnect2.c 2024-09-16 11:46:34.703940075 +0200 +--- a/sshconnect2.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/sshconnect2.c 2026-03-13 12:16:40.008053898 +0100 @@ -222,6 +222,11 @@ ssh_kex2(struct ssh *ssh, char *host, st char *all_key, *hkalgs = NULL; int r, use_known_hosts_order = 0; @@ -3669,7 +3671,7 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || -@@ -368,6 +439,7 @@ static int input_gssapi_response(int typ +@@ -369,6 +440,7 @@ static int input_gssapi_response(int typ static int input_gssapi_token(int type, u_int32_t, struct ssh *); static int input_gssapi_error(int, u_int32_t, struct ssh *); static int input_gssapi_errtok(int, u_int32_t, struct ssh *); @@ -3677,7 +3679,7 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c #endif void userauth(struct ssh *, char *); -@@ -384,6 +456,11 @@ static char *authmethods_get(void); +@@ -385,6 +457,11 @@ static char *authmethods_get(void); Authmethod authmethods[] = { #ifdef GSSAPI @@ -3689,7 +3691,7 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c {"gssapi-with-mic", userauth_gssapi, userauth_gssapi_cleanup, -@@ -755,12 +832,32 @@ userauth_gssapi(struct ssh *ssh) +@@ -756,12 +833,32 @@ userauth_gssapi(struct ssh *ssh) OM_uint32 min; int r, ok = 0; gss_OID mech = NULL; @@ -3723,7 +3725,7 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c /* Check to see whether the mechanism is usable before we offer it */ while (authctxt->mech_tried < authctxt->gss_supported_mechs->count && -@@ -769,13 +866,15 @@ userauth_gssapi(struct ssh *ssh) +@@ -770,13 +867,15 @@ userauth_gssapi(struct ssh *ssh) elements[authctxt->mech_tried]; /* My DER encoding requires length<128 */ if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt, @@ -3740,7 +3742,7 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c if (!ok || mech == NULL) return 0; -@@ -1009,6 +1108,55 @@ input_gssapi_error(int type, u_int32_t p +@@ -1010,6 +1109,55 @@ input_gssapi_error(int type, u_int32_t p free(lang); return r; } @@ -3797,9 +3799,9 @@ diff --color -ruNp a/sshconnect2.c b/sshconnect2.c static int diff --color -ruNp a/sshd.c b/sshd.c ---- a/sshd.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshd.c 2024-09-16 11:46:34.704940096 +0200 -@@ -1551,7 +1551,8 @@ main(int ac, char **av) +--- a/sshd.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/sshd.c 2026-03-13 12:16:40.008426802 +0100 +@@ -1558,7 +1558,8 @@ main(int ac, char **av) free(fp); } accumulate_host_timing_secret(cfg, NULL); @@ -3810,8 +3812,8 @@ diff --color -ruNp a/sshd.c b/sshd.c exit(1); } diff --color -ruNp a/sshd_config b/sshd_config ---- a/sshd_config 2024-09-16 11:45:56.888133880 +0200 -+++ b/sshd_config 2024-09-16 11:46:34.704940096 +0200 +--- a/sshd_config 2026-03-13 12:32:48.491841092 +0100 ++++ b/sshd_config 2026-03-13 12:16:40.008629209 +0100 @@ -77,6 +77,8 @@ AuthorizedKeysFile .ssh/authorized_keys # GSSAPI options #GSSAPIAuthentication no @@ -3822,8 +3824,8 @@ diff --color -ruNp a/sshd_config b/sshd_config # Set this to 'yes' to enable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will diff --color -ruNp a/sshd_config.5 b/sshd_config.5 ---- a/sshd_config.5 2024-09-16 11:45:56.885133816 +0200 -+++ b/sshd_config.5 2024-09-16 11:46:34.704940096 +0200 +--- a/sshd_config.5 2026-03-13 12:32:48.489069461 +0100 ++++ b/sshd_config.5 2026-03-13 12:16:40.013495921 +0100 @@ -739,6 +739,11 @@ Specifies whether to automatically destr on logout. The default is @@ -3870,9 +3872,9 @@ diff --color -ruNp a/sshd_config.5 b/sshd_config.5 Specifies the signature algorithms that will be accepted for hostbased authentication as a list of comma-separated patterns. diff --color -ruNp a/sshd-session.c b/sshd-session.c ---- a/sshd-session.c 2024-09-16 11:45:56.888133880 +0200 -+++ b/sshd-session.c 2024-09-16 11:46:34.705940118 +0200 -@@ -660,8 +660,8 @@ notify_hostkeys(struct ssh *ssh) +--- a/sshd-session.c 2026-03-13 12:32:48.491392577 +0100 ++++ b/sshd-session.c 2026-03-13 12:16:40.013202390 +0100 +@@ -662,8 +662,8 @@ notify_hostkeys(struct ssh *ssh) } debug3_f("sent %u hostkeys", nkeys); if (nkeys == 0) @@ -3883,7 +3885,7 @@ diff --color -ruNp a/sshd-session.c b/sshd-session.c sshpkt_fatal(ssh, r, "%s: send", __func__); sshbuf_free(buf); } -@@ -1180,8 +1180,9 @@ main(int ac, char **av) +@@ -1182,8 +1182,9 @@ main(int ac, char **av) break; } } @@ -3895,7 +3897,7 @@ diff --color -ruNp a/sshd-session.c b/sshd-session.c /* Ensure that umask disallows at least group and world write */ new_umask = umask(0077) | 0022; -@@ -1462,6 +1463,48 @@ do_ssh2_kex(struct ssh *ssh) +@@ -1476,6 +1477,48 @@ do_ssh2_kex(struct ssh *ssh) free(hkalgs); @@ -3944,7 +3946,7 @@ diff --color -ruNp a/sshd-session.c b/sshd-session.c /* start key exchange */ if ((r = kex_setup(ssh, myproposal)) != 0) fatal_r(r, "kex_setup"); -@@ -1479,7 +1522,18 @@ do_ssh2_kex(struct ssh *ssh) +@@ -1493,7 +1536,18 @@ do_ssh2_kex(struct ssh *ssh) #ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; #endif @@ -3965,8 +3967,8 @@ diff --color -ruNp a/sshd-session.c b/sshd-session.c kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; diff --color -ruNp a/ssh-gss.h b/ssh-gss.h ---- a/ssh-gss.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh-gss.h 2024-09-16 11:46:34.710940224 +0200 +--- a/ssh-gss.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/ssh-gss.h 2026-03-13 12:16:40.013453154 +0100 @@ -61,10 +61,36 @@ #define SSH_GSS_OIDTYPE 0x06 @@ -4092,9 +4094,9 @@ diff --color -ruNp a/ssh-gss.h b/ssh-gss.h #endif /* _SSH_GSS_H */ diff --color -ruNp a/sshkey.c b/sshkey.c ---- a/sshkey.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshkey.c 2024-09-16 11:46:34.706940139 +0200 -@@ -131,6 +131,75 @@ extern const struct sshkey_impl sshkey_x +--- a/sshkey.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/sshkey.c 2026-03-13 12:16:40.006634461 +0100 +@@ -132,6 +132,75 @@ extern const struct sshkey_impl sshkey_x extern const struct sshkey_impl sshkey_xmss_cert_impl; #endif @@ -4170,7 +4172,7 @@ diff --color -ruNp a/sshkey.c b/sshkey.c const struct sshkey_impl * const keyimpls[] = { &sshkey_ed25519_impl, &sshkey_ed25519_cert_impl, -@@ -169,6 +238,7 @@ const struct sshkey_impl * const keyimpl +@@ -170,6 +239,7 @@ const struct sshkey_impl * const keyimpl &sshkey_xmss_impl, &sshkey_xmss_cert_impl, #endif @@ -4178,7 +4180,7 @@ diff --color -ruNp a/sshkey.c b/sshkey.c NULL }; -@@ -324,7 +394,7 @@ sshkey_alg_list(int certs_only, int plai +@@ -339,7 +409,7 @@ sshkey_alg_list(int certs_only, int plai for (i = 0; keyimpls[i] != NULL; i++) { impl = keyimpls[i]; @@ -4188,9 +4190,9 @@ diff --color -ruNp a/sshkey.c b/sshkey.c if (!include_sigonly && impl->sigonly) continue; diff --color -ruNp a/sshkey.h b/sshkey.h ---- a/sshkey.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshkey.h 2024-09-16 11:46:34.706940139 +0200 -@@ -71,6 +71,7 @@ enum sshkey_types { +--- a/sshkey.h 2024-09-20 00:20:48.000000000 +0200 ++++ b/sshkey.h 2026-03-13 12:16:40.008972328 +0100 +@@ -73,6 +73,7 @@ enum sshkey_types { KEY_ECDSA_SK_CERT, KEY_ED25519_SK, KEY_ED25519_SK_CERT, diff --git a/openssh-9.9p1-first-match-wins.patch b/openssh-9.9p1-first-match-wins.patch new file mode 100644 index 0000000..7d76437 --- /dev/null +++ b/openssh-9.9p1-first-match-wins.patch @@ -0,0 +1,106 @@ +diff --color -ruNp a/regress/cfgparse.sh b/regress/cfgparse.sh +--- a/regress/cfgparse.sh 2024-09-20 00:20:48.000000000 +0200 ++++ b/regress/cfgparse.sh 2026-03-05 17:30:54.959690744 +0100 +@@ -51,7 +51,7 @@ listenaddress ::1 + EOD + + ($SUDO ${SSHD} -T -f $OBJ/sshd_config.1 | \ +- grep 'listenaddress ' >$OBJ/sshd_config.2 && ++ grep '^listenaddress ' >$OBJ/sshd_config.2 && + diff $OBJ/sshd_config.0 $OBJ/sshd_config.2) || \ + fail "listenaddress order 1" + # test 2: listenaddress first +@@ -67,9 +67,22 @@ listenaddress ::1 + EOD + + ($SUDO ${SSHD} -T -f $OBJ/sshd_config.1 | \ +- grep 'listenaddress ' >$OBJ/sshd_config.2 && ++ grep '^listenaddress ' >$OBJ/sshd_config.2 && + diff $OBJ/sshd_config.0 $OBJ/sshd_config.2) || \ + fail "listenaddress order 2" + ++# Check idempotence of MaxStartups ++verbose "maxstartups idempotent" ++echo "maxstartups 1:2:3" > $OBJ/sshd_config.0 ++cat > $OBJ/sshd_config.1 <$OBJ/sshd_config.2 && ++ diff $OBJ/sshd_config.0 $OBJ/sshd_config.2) || \ ++ fail "maxstartups idempotence" ++ + # cleanup + rm -f $OBJ/sshd_config.[012] +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2026-03-05 16:15:49.035275297 +0100 ++++ b/servconf.c 2026-03-05 17:13:29.915897329 +0100 +@@ -1366,7 +1366,7 @@ process_server_config_line_depth(ServerO + struct include_list *includes) + { + char *str, ***chararrayptr, **charptr, *arg, *arg2, *p, *keyword; +- int cmdline = 0, *intptr, value, value2, n, port, oactive, r; ++ int cmdline = 0, *intptr, value, value2, value3, n, port, oactive, r; + int ca_only = 0, found = 0; + SyslogFacility *log_facility_ptr; + LogLevel *log_level_ptr; +@@ -2095,25 +2095,27 @@ process_server_config_line_depth(ServerO + if (!arg || *arg == '\0') + fatal("%s line %d: %s missing argument.", + filename, linenum, keyword); ++ /* begin:rate:max */ + if ((n = sscanf(arg, "%d:%d:%d", +- &options->max_startups_begin, +- &options->max_startups_rate, +- &options->max_startups)) == 3) { +- if (options->max_startups_begin > +- options->max_startups || +- options->max_startups_rate > 100 || +- options->max_startups_rate < 1) ++ &value, &value2, &value3)) == 3) { ++ if (value > value3 || value2 > 100 || value2 < 1) + fatal("%s line %d: Invalid %s spec.", + filename, linenum, keyword); +- } else if (n != 1) ++ } else if (n == 1) { ++ value3 = value; ++ value = value2 = -1; ++ } else { + fatal("%s line %d: Invalid %s spec.", + filename, linenum, keyword); +- else +- options->max_startups = options->max_startups_begin; +- if (options->max_startups <= 0 || +- options->max_startups_begin <= 0) ++ } ++ if (value3 <= 0 || (value2 != -1 && value <= 0)) + fatal("%s line %d: Invalid %s spec.", + filename, linenum, keyword); ++ if (*activep && options->max_startups == -1) { ++ options->max_startups_begin = value; ++ options->max_startups_rate = value2; ++ options->max_startups = value3; ++ } + break; + + case sPerSourceNetBlockSize: +@@ -2133,7 +2135,7 @@ process_server_config_line_depth(ServerO + if (n != 1 && n != 2) + fatal("%s line %d: Invalid %s spec.", + filename, linenum, keyword); +- if (*activep) { ++ if (*activep && options->per_source_masklen_ipv4 == -1) { + options->per_source_masklen_ipv4 = value; + options->per_source_masklen_ipv6 = value2; + } +@@ -2621,7 +2623,7 @@ process_server_config_line_depth(ServerO + else if ((value2 = parse_ipqos(arg)) == -1) + fatal("%s line %d: Bad %s value: %s", + filename, linenum, keyword, arg); +- if (*activep) { ++ if (*activep && options->ip_qos_interactive == -1) { + options->ip_qos_interactive = value; + options->ip_qos_bulk = value2; + } diff --git a/openssh-9.9p1-gssapi-s4u.patch b/openssh-9.9p1-gssapi-s4u.patch index 7e94f6b..31bf5d5 100644 --- a/openssh-9.9p1-gssapi-s4u.patch +++ b/openssh-9.9p1-gssapi-s4u.patch @@ -1,8 +1,7 @@ -diff --git a/auth-krb5.c b/auth-krb5.c -index a37be93c3..34f058967 100644 ---- a/auth-krb5.c -+++ b/auth-krb5.c -@@ -465,6 +465,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm +diff --color -ruNp a/auth-krb5.c b/auth-krb5.c +--- a/auth-krb5.c 2026-03-12 11:51:16.406723629 +0100 ++++ b/auth-krb5.c 2026-03-12 12:00:49.464289202 +0100 +@@ -462,6 +462,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, * a primary cache for this collection, if it supports that (non-FILE) */ if (krb5_cc_support_switch(ctx, type)) { @@ -22,21 +21,19 @@ index a37be93c3..34f058967 100644 debug3_f("calling cc_new_unique(%s)", ccname); ret = krb5_cc_new_unique(ctx, type, NULL, ccache); free(type); -diff --git a/configure.ac b/configure.ac -index e33462027..22f806787 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -5102,11 +5102,17 @@ AC_ARG_WITH([kerberos5], - # include +diff --color -ruNp a/configure.ac b/configure.ac +--- a/configure.ac 2026-03-12 11:51:16.580313749 +0100 ++++ b/configure.ac 2026-03-12 12:00:59.319587937 +0100 +@@ -4949,10 +4949,16 @@ AC_ARG_WITH([kerberos5], #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H) # include -+#endif + #endif +#ifdef HAVE_GSSAPI_EXT_H +# include +#endif +#ifdef HAVE_GSSAPI_KRB5_H +# include - #endif ++#endif ]]) saved_LIBS="$LIBS" - LIBS="$LIBS $K5LIBS" @@ -46,11 +43,388 @@ index e33462027..22f806787 100644 LIBS="$saved_LIBS" fi -diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c -index 2c786ef14..64e327917 100644 ---- a/gss-serv-krb5.c -+++ b/gss-serv-krb5.c -@@ -590,6 +590,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, +diff --color -ruNp a/gss-serv.c b/gss-serv.c +--- a/gss-serv.c 2026-03-12 11:51:16.603196888 +0100 ++++ b/gss-serv.c 2026-03-12 12:01:44.171014784 +0100 +@@ -53,7 +53,7 @@ extern ServerOptions options; + + static ssh_gssapi_client gssapi_client = + { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, +- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL}; ++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0}; + + ssh_gssapi_mech gssapi_null_mech = + { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; +@@ -486,26 +486,351 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + return (ctx->major); + } + +-/* As user - called on fatal/exit */ ++/* Returns non-zero if Kerberos credentials have already been stored. */ ++int ++ssh_gssapi_credentials_stored(void) ++{ ++ return gssapi_client.store.envval != NULL; ++} ++ ++/* Returns a pointer to the credential-cache descriptor for this session. */ ++ssh_gssapi_ccache * ++ssh_gssapi_get_ccache(void) ++{ ++ return &gssapi_client.store; ++} ++ ++/* Log human-readable GSSAPI major and minor status strings. */ ++static void ++log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label) ++{ ++ OM_uint32 lmin, mctx; ++ gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER; ++ ++ mctx = 0; ++ do { ++ gss_display_status(&lmin, major, GSS_C_GSS_CODE, ++ GSS_C_NO_OID, &mctx, &emsg); ++ logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value); ++ gss_release_buffer(&lmin, &emsg); ++ } while (mctx != 0); ++ ++ mctx = 0; ++ do { ++ gss_display_status(&lmin, minor, GSS_C_MECH_CODE, ++ &gssapi_kerberos_mech.oid, &mctx, &emsg); ++ if (emsg.length > 0) ++ logit("%s: %.*s", label, ++ (int)emsg.length, (char *)emsg.value); ++ gss_release_buffer(&lmin, &emsg); ++ } while (mctx != 0); ++} ++ ++/* Log the canonical string form of a GSSAPI name as a debug message. */ ++static void ++debug_gss_name(const char *label, gss_name_t name) ++{ ++ OM_uint32 lmin; ++ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; ++ ++ if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) { ++ debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value); ++ gss_release_buffer(&lmin, &buf); ++ } ++} ++ ++/* ++ * Check whether the user already has valid GSSAPI initiator credentials ++ * (e.g. a Kerberos TGT) in their default credential store with at least ++ * min_lifetime seconds remaining. Pass GSS_C_INDEFINITE to accept any ++ * positive remaining lifetime. Runs as the user. ++ * Returns 1 if sufficient credentials exist, 0 otherwise. ++ */ ++int ++ssh_gssapi_user_has_valid_tgt(u_int min_lifetime) ++{ ++ OM_uint32 major, minor, lifetime = 0; ++ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; ++ int found = 0; ++ ++ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, ++ GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime); ++ if (!GSS_ERROR(major) && lifetime > 0 && ++ (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime)) ++ found = 1; ++ if (cred != GSS_C_NO_CREDENTIAL) ++ gss_release_cred(&minor, &cred); ++ return found; ++} ++ ++ ++/* ++ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket ++ * for the SSH user on behalf of the host principal. Runs privileged. ++ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success. ++ * Returns 0 on success, -1 on failure. ++ */ ++/* Privileged */ ++int ++ssh_gssapi_s4u2self(const char *user, u_int lifetime) ++{ ++ OM_uint32 major, minor, status; ++ gss_OID_set oidset = GSS_C_NO_OID_SET; ++ gss_name_t host_name = GSS_C_NO_NAME; ++ gss_name_t user_name = GSS_C_NO_NAME; ++ gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL; ++ gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL; ++ gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER; ++ char lname[NI_MAXHOST]; ++ char *val; ++ ++ if (gethostname(lname, sizeof(lname)) != 0) { ++ logit_f("gethostname: %s", strerror(errno)); ++ return -1; ++ } ++ ++ /* Acquire acceptor credential for host/ from the keytab */ ++ gss_create_empty_oid_set(&status, &oidset); ++ gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset); ++ ++ xasprintf(&val, "host@%s", lname); ++ gssbuf.value = val; ++ gssbuf.length = strlen(val); ++ major = gss_import_name(&minor, &gssbuf, ++ GSS_C_NT_HOSTBASED_SERVICE, &host_name); ++ free(val); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_import_name (host) failed"); ++ gss_release_oid_set(&status, &oidset); ++ return -1; ++ } ++ debug_gss_name("host name parsed as", host_name); ++ ++ debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s", ++ (unsigned)getuid(), (unsigned)geteuid(), lname); ++#ifdef HAVE_GSS_ACQUIRE_CRED_FROM ++ { ++ gss_key_value_element_desc store_elements[] = { ++ { "client_keytab", "/etc/krb5.keytab" }, ++ { "keytab", "/etc/krb5.keytab" }, ++ { "ccache", "MEMORY:" }, ++ }; ++ const gss_key_value_set_desc cred_store = { 3, store_elements }; ++ ++ major = gss_acquire_cred_from(&minor, host_name, lifetime, ++ oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL); ++ } ++#else ++ major = gss_acquire_cred(&minor, host_name, lifetime, ++ oidset, GSS_C_BOTH, &host_creds, NULL, NULL); ++#endif ++ gss_release_name(&minor, &host_name); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u", ++ lname, (unsigned)getuid(), (unsigned)geteuid()); ++ log_gss_error(major, minor, "S4U2Self: gss_acquire_cred"); ++ gss_release_oid_set(&status, &oidset); ++ return -1; ++ } ++ ++ /* Import the SSH username as a GSSAPI/Kerberos name */ ++ gssbuf.value = (void *)user; ++ gssbuf.length = strlen(user); ++ major = gss_import_name(&minor, &gssbuf, ++ GSS_C_NT_USER_NAME, &user_name); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_import_name (user) failed"); ++ gss_release_cred(&minor, &host_creds); ++ gss_release_oid_set(&status, &oidset); ++ return -1; ++ } ++ debug_gss_name("user name parsed as", user_name); ++ ++ /* S4U2Self: obtain a service ticket for the user without their creds */ ++ debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user); ++ major = gss_acquire_cred_impersonate_name(&minor, ++ host_creds, user_name, lifetime, ++ oidset, GSS_C_INITIATE, ++ &impersonated_creds, NULL, NULL); ++ ++ gss_release_cred(&minor, &host_creds); ++ gss_release_oid_set(&status, &oidset); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_acquire_cred_impersonate_name failed for %.100s", ++ user); ++ log_gss_error(major, minor, ++ "S4U2Self: gss_acquire_cred_impersonate_name"); ++ gss_release_name(&minor, &user_name); ++ return -1; ++ } ++ ++ /* Get the display name (Kerberos principal string) for storecreds */ ++ major = gss_display_name(&minor, user_name, &displayname, NULL); ++ gss_release_name(&minor, &user_name); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_display_name failed"); ++ gss_release_cred(&minor, &impersonated_creds); ++ return -1; ++ } ++ ++ /* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */ ++ gssapi_client.mech = &gssapi_kerberos_mech; ++ gssapi_client.creds = impersonated_creds; ++ gssapi_client.displayname.value = xmalloc(displayname.length + 1); ++ memcpy(gssapi_client.displayname.value, ++ displayname.value, displayname.length); ++ ((char *)gssapi_client.displayname.value)[displayname.length] = '\0'; ++ gssapi_client.displayname.length = displayname.length; ++ /* ++ * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name. ++ * gss_display_name for a user-name returns the canonical principal ++ * string (e.g. user@REALM) which krb5_parse_name can consume directly. ++ */ ++ gssapi_client.exportedname.value = xmalloc(displayname.length + 1); ++ memcpy(gssapi_client.exportedname.value, ++ displayname.value, displayname.length); ++ ((char *)gssapi_client.exportedname.value)[displayname.length] = '\0'; ++ gssapi_client.exportedname.length = displayname.length; ++ ++ gss_release_buffer(&minor, &displayname); ++ debug_f("S4U2Self succeeded for %.100s", user); ++ return 0; ++} ++ ++/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */ + void +-ssh_gssapi_cleanup_creds(void) ++ssh_gssapi_storecreds_s4u2self(void) + { +- krb5_ccache ccache = NULL; +- krb5_error_code problem; ++ if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) { ++ debug_f("no GSSAPI mechanism for storing S4U2Self credentials"); ++ return; ++ } ++ (*gssapi_client.mech->storecreds)(&gssapi_client); ++} ++ ++/* ++ * Perform S4U2Proxy for each configured service principal, then flush all ++ * resulting tickets into the user's ccache. Runs as user, after ++ * ssh_gssapi_storecreds_s4u2self() has created the ccache. ++ * ++ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the ++ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT ++ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ. The output token ++ * (AP-REQ) is discarded — we do not connect to the target service. ++ * ++ * After iterating all services, gss_store_cred() flushes the accumulated ++ * proxy service tickets from the credential's internal ccache into the ++ * KRB5CCNAME ccache that storecreds_s4u2self() already created. ++ */ ++/* As user */ ++void ++ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime) ++{ ++ OM_uint32 major, minor; ++ gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER; ++ gss_name_t target_name; ++ gss_ctx_id_t ctx; ++ u_int i; ++ ++ if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) { ++ debug_f("no proxy credential available"); ++ return; ++ } ++ if (gssapi_client.store.envval == NULL) { ++ debug_f("no ccache path set; cannot store proxy tickets"); ++ return; ++ } + +- if (gssapi_client.store.data != NULL) { +- if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) { +- debug_f("krb5_cc_resolve(): %.100s", +- krb5_get_err_text(gssapi_client.store.data, problem)); +- } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) { +- debug_f("krb5_cc_destroy(): %.100s", +- krb5_get_err_text(gssapi_client.store.data, problem)); +- } else { +- krb5_free_context(gssapi_client.store.data); +- gssapi_client.store.data = NULL; ++ debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s", ++ (unsigned)getuid(), (unsigned)geteuid(), nservices, ++ gssapi_client.store.envval); ++ ++ /* Point the GSSAPI library at the user's ccache for ticket storage */ ++ setenv("KRB5CCNAME", gssapi_client.store.envval, 1); ++ ++ for (i = 0; i < nservices; i++) { ++ ctx = GSS_C_NO_CONTEXT; ++ target_name = GSS_C_NO_NAME; ++ ++ service_buf.value = services[i]; ++ service_buf.length = strlen(services[i]); ++ ++ /* ++ * GSS_C_NO_OID: let the library determine the name type. ++ * With Kerberos as the active mechanism, a fully-qualified ++ * principal like "svc/host@REALM" is parsed correctly. ++ */ ++ major = gss_import_name(&minor, &service_buf, ++ GSS_C_NO_OID, &target_name); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_import_name failed for %.200s", ++ services[i]); ++ log_gss_error(major, minor, "S4U2Proxy: gss_import_name"); ++ continue; + } ++ debug_gss_name("target service name parsed as", target_name); ++ ++ debug_f("calling gss_init_sec_context for %.200s", services[i]); ++ major = gss_init_sec_context(&minor, ++ gssapi_client.creds, /* proxy credential */ ++ &ctx, target_name, ++ GSS_C_NO_OID, /* default mech (Kerberos) */ ++ 0, /* no flags, no mutual auth */ ++ lifetime, ++ GSS_C_NO_CHANNEL_BINDINGS, ++ GSS_C_NO_BUFFER, /* no input token */ ++ NULL, /* actual_mech_type */ ++ &output_token, ++ NULL, /* ret_flags */ ++ NULL); /* time_rec */ ++ ++ gss_release_buffer(&minor, &output_token); ++ gss_release_name(&minor, &target_name); ++ if (ctx != GSS_C_NO_CONTEXT) ++ gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER); ++ ++ if (GSS_ERROR(major)) { ++ logit_f("S4U2Proxy for %.200s on behalf of %.200s failed", ++ services[i], ++ (char *)gssapi_client.displayname.value); ++ log_gss_error(major, minor, ++ "S4U2Proxy: gss_init_sec_context"); ++ } else ++ debug_f("S4U2Proxy ticket obtained for %.200s", ++ services[i]); + } ++ ++ /* ++ * Flush all proxy service tickets from the credential's internal ++ * ccache into the KRB5CCNAME ccache via gss_store_cred(). ++ */ ++ major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE, ++ GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */, ++ NULL, NULL); ++ if (GSS_ERROR(major)) { ++ logit_f("gss_store_cred failed; proxy tickets may be missing"); ++ log_gss_error(major, minor, "S4U2Proxy: gss_store_cred"); ++ } ++ ++ unsetenv("KRB5CCNAME"); ++} ++ ++#ifndef KRB5 ++/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */ ++void ++ssh_gssapi_cleanup_creds(void) ++{ ++} ++ ++/* ++ * Filter the user's ccache; full implementation in gss-serv-krb5.c. ++ */ ++void ++ssh_gssapi_krb5_filter_ccache(u_int drop_flags, ++ char **proxy_services, u_int nproxy_services) ++{ + } ++#endif /* !KRB5 */ + + /* As user */ + int +diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c +--- a/gss-serv-krb5.c 2026-03-12 11:51:16.581364233 +0100 ++++ b/gss-serv-krb5.c 2026-03-12 12:01:20.402569731 +0100 +@@ -609,6 +609,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_c return 1; } @@ -282,401 +656,20 @@ index 2c786ef14..64e327917 100644 ssh_gssapi_mech gssapi_kerberos_mech = { "toWM5Slw5Ew8Mqkay+al2g==", "Kerberos", -diff --git a/gss-serv.c b/gss-serv.c -index 165484db0..90e2d03ce 100644 ---- a/gss-serv.c -+++ b/gss-serv.c -@@ -54,7 +54,7 @@ extern ServerOptions options; - - static ssh_gssapi_client gssapi_client = - { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, -- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL}; -+ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0}; - - ssh_gssapi_mech gssapi_null_mech = - { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; -@@ -484,26 +484,351 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) - return (ctx->major); - } - --/* As user - called on fatal/exit */ -+/* Returns non-zero if Kerberos credentials have already been stored. */ -+int -+ssh_gssapi_credentials_stored(void) -+{ -+ return gssapi_client.store.envval != NULL; -+} -+ -+/* Returns a pointer to the credential-cache descriptor for this session. */ -+ssh_gssapi_ccache * -+ssh_gssapi_get_ccache(void) -+{ -+ return &gssapi_client.store; -+} -+ -+/* Log human-readable GSSAPI major and minor status strings. */ -+static void -+log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label) -+{ -+ OM_uint32 lmin, mctx; -+ gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER; -+ -+ mctx = 0; -+ do { -+ gss_display_status(&lmin, major, GSS_C_GSS_CODE, -+ GSS_C_NO_OID, &mctx, &emsg); -+ logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value); -+ gss_release_buffer(&lmin, &emsg); -+ } while (mctx != 0); -+ -+ mctx = 0; -+ do { -+ gss_display_status(&lmin, minor, GSS_C_MECH_CODE, -+ &gssapi_kerberos_mech.oid, &mctx, &emsg); -+ if (emsg.length > 0) -+ logit("%s: %.*s", label, -+ (int)emsg.length, (char *)emsg.value); -+ gss_release_buffer(&lmin, &emsg); -+ } while (mctx != 0); -+} -+ -+/* Log the canonical string form of a GSSAPI name as a debug message. */ -+static void -+debug_gss_name(const char *label, gss_name_t name) -+{ -+ OM_uint32 lmin; -+ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; -+ -+ if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) { -+ debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value); -+ gss_release_buffer(&lmin, &buf); -+ } -+} -+ -+/* -+ * Check whether the user already has valid GSSAPI initiator credentials -+ * (e.g. a Kerberos TGT) in their default credential store with at least -+ * min_lifetime seconds remaining. Pass GSS_C_INDEFINITE to accept any -+ * positive remaining lifetime. Runs as the user. -+ * Returns 1 if sufficient credentials exist, 0 otherwise. -+ */ -+int -+ssh_gssapi_user_has_valid_tgt(u_int min_lifetime) -+{ -+ OM_uint32 major, minor, lifetime = 0; -+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; -+ int found = 0; -+ -+ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, -+ GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime); -+ if (!GSS_ERROR(major) && lifetime > 0 && -+ (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime)) -+ found = 1; -+ if (cred != GSS_C_NO_CREDENTIAL) -+ gss_release_cred(&minor, &cred); -+ return found; -+} -+ -+ -+/* -+ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket -+ * for the SSH user on behalf of the host principal. Runs privileged. -+ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success. -+ * Returns 0 on success, -1 on failure. -+ */ -+/* Privileged */ -+int -+ssh_gssapi_s4u2self(const char *user, u_int lifetime) -+{ -+ OM_uint32 major, minor, status; -+ gss_OID_set oidset = GSS_C_NO_OID_SET; -+ gss_name_t host_name = GSS_C_NO_NAME; -+ gss_name_t user_name = GSS_C_NO_NAME; -+ gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL; -+ gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL; -+ gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER; -+ char lname[NI_MAXHOST]; -+ char *val; -+ -+ if (gethostname(lname, sizeof(lname)) != 0) { -+ logit_f("gethostname: %s", strerror(errno)); -+ return -1; -+ } -+ -+ /* Acquire acceptor credential for host/ from the keytab */ -+ gss_create_empty_oid_set(&status, &oidset); -+ gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset); -+ -+ xasprintf(&val, "host@%s", lname); -+ gssbuf.value = val; -+ gssbuf.length = strlen(val); -+ major = gss_import_name(&minor, &gssbuf, -+ GSS_C_NT_HOSTBASED_SERVICE, &host_name); -+ free(val); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_import_name (host) failed"); -+ gss_release_oid_set(&status, &oidset); -+ return -1; -+ } -+ debug_gss_name("host name parsed as", host_name); -+ -+ debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s", -+ (unsigned)getuid(), (unsigned)geteuid(), lname); -+#ifdef HAVE_GSS_ACQUIRE_CRED_FROM -+ { -+ gss_key_value_element_desc store_elements[] = { -+ { "client_keytab", "/etc/krb5.keytab" }, -+ { "keytab", "/etc/krb5.keytab" }, -+ { "ccache", "MEMORY:" }, -+ }; -+ const gss_key_value_set_desc cred_store = { 3, store_elements }; -+ -+ major = gss_acquire_cred_from(&minor, host_name, lifetime, -+ oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL); -+ } -+#else -+ major = gss_acquire_cred(&minor, host_name, lifetime, -+ oidset, GSS_C_BOTH, &host_creds, NULL, NULL); -+#endif -+ gss_release_name(&minor, &host_name); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u", -+ lname, (unsigned)getuid(), (unsigned)geteuid()); -+ log_gss_error(major, minor, "S4U2Self: gss_acquire_cred"); -+ gss_release_oid_set(&status, &oidset); -+ return -1; -+ } -+ -+ /* Import the SSH username as a GSSAPI/Kerberos name */ -+ gssbuf.value = (void *)user; -+ gssbuf.length = strlen(user); -+ major = gss_import_name(&minor, &gssbuf, -+ GSS_C_NT_USER_NAME, &user_name); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_import_name (user) failed"); -+ gss_release_cred(&minor, &host_creds); -+ gss_release_oid_set(&status, &oidset); -+ return -1; -+ } -+ debug_gss_name("user name parsed as", user_name); -+ -+ /* S4U2Self: obtain a service ticket for the user without their creds */ -+ debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user); -+ major = gss_acquire_cred_impersonate_name(&minor, -+ host_creds, user_name, lifetime, -+ oidset, GSS_C_INITIATE, -+ &impersonated_creds, NULL, NULL); -+ -+ gss_release_cred(&minor, &host_creds); -+ gss_release_oid_set(&status, &oidset); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_acquire_cred_impersonate_name failed for %.100s", -+ user); -+ log_gss_error(major, minor, -+ "S4U2Self: gss_acquire_cred_impersonate_name"); -+ gss_release_name(&minor, &user_name); -+ return -1; -+ } -+ -+ /* Get the display name (Kerberos principal string) for storecreds */ -+ major = gss_display_name(&minor, user_name, &displayname, NULL); -+ gss_release_name(&minor, &user_name); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_display_name failed"); -+ gss_release_cred(&minor, &impersonated_creds); -+ return -1; -+ } -+ -+ /* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */ -+ gssapi_client.mech = &gssapi_kerberos_mech; -+ gssapi_client.creds = impersonated_creds; -+ gssapi_client.displayname.value = xmalloc(displayname.length + 1); -+ memcpy(gssapi_client.displayname.value, -+ displayname.value, displayname.length); -+ ((char *)gssapi_client.displayname.value)[displayname.length] = '\0'; -+ gssapi_client.displayname.length = displayname.length; -+ /* -+ * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name. -+ * gss_display_name for a user-name returns the canonical principal -+ * string (e.g. user@REALM) which krb5_parse_name can consume directly. -+ */ -+ gssapi_client.exportedname.value = xmalloc(displayname.length + 1); -+ memcpy(gssapi_client.exportedname.value, -+ displayname.value, displayname.length); -+ ((char *)gssapi_client.exportedname.value)[displayname.length] = '\0'; -+ gssapi_client.exportedname.length = displayname.length; -+ -+ gss_release_buffer(&minor, &displayname); -+ debug_f("S4U2Self succeeded for %.100s", user); -+ return 0; -+} -+ -+/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */ - void --ssh_gssapi_cleanup_creds(void) -+ssh_gssapi_storecreds_s4u2self(void) - { -- krb5_ccache ccache = NULL; -- krb5_error_code problem; -- -- if (gssapi_client.store.data != NULL) { -- if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) { -- debug_f("krb5_cc_resolve(): %.100s", -- krb5_get_err_text(gssapi_client.store.data, problem)); -- } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) { -- debug_f("krb5_cc_destroy(): %.100s", -- krb5_get_err_text(gssapi_client.store.data, problem)); -- } else { -- krb5_free_context(gssapi_client.store.data); -- gssapi_client.store.data = NULL; -+ if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) { -+ debug_f("no GSSAPI mechanism for storing S4U2Self credentials"); -+ return; -+ } -+ (*gssapi_client.mech->storecreds)(&gssapi_client); -+} -+ -+/* -+ * Perform S4U2Proxy for each configured service principal, then flush all -+ * resulting tickets into the user's ccache. Runs as user, after -+ * ssh_gssapi_storecreds_s4u2self() has created the ccache. -+ * -+ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the -+ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT -+ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ. The output token -+ * (AP-REQ) is discarded — we do not connect to the target service. -+ * -+ * After iterating all services, gss_store_cred() flushes the accumulated -+ * proxy service tickets from the credential's internal ccache into the -+ * KRB5CCNAME ccache that storecreds_s4u2self() already created. -+ */ -+/* As user */ -+void -+ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime) -+{ -+ OM_uint32 major, minor; -+ gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER; -+ gss_name_t target_name; -+ gss_ctx_id_t ctx; -+ u_int i; -+ -+ if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) { -+ debug_f("no proxy credential available"); -+ return; -+ } -+ if (gssapi_client.store.envval == NULL) { -+ debug_f("no ccache path set; cannot store proxy tickets"); -+ return; -+ } -+ -+ debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s", -+ (unsigned)getuid(), (unsigned)geteuid(), nservices, -+ gssapi_client.store.envval); -+ -+ /* Point the GSSAPI library at the user's ccache for ticket storage */ -+ setenv("KRB5CCNAME", gssapi_client.store.envval, 1); -+ -+ for (i = 0; i < nservices; i++) { -+ ctx = GSS_C_NO_CONTEXT; -+ target_name = GSS_C_NO_NAME; -+ -+ service_buf.value = services[i]; -+ service_buf.length = strlen(services[i]); -+ -+ /* -+ * GSS_C_NO_OID: let the library determine the name type. -+ * With Kerberos as the active mechanism, a fully-qualified -+ * principal like "svc/host@REALM" is parsed correctly. -+ */ -+ major = gss_import_name(&minor, &service_buf, -+ GSS_C_NO_OID, &target_name); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_import_name failed for %.200s", -+ services[i]); -+ log_gss_error(major, minor, "S4U2Proxy: gss_import_name"); -+ continue; - } -+ debug_gss_name("target service name parsed as", target_name); -+ -+ debug_f("calling gss_init_sec_context for %.200s", services[i]); -+ major = gss_init_sec_context(&minor, -+ gssapi_client.creds, /* proxy credential */ -+ &ctx, target_name, -+ GSS_C_NO_OID, /* default mech (Kerberos) */ -+ 0, /* no flags, no mutual auth */ -+ lifetime, -+ GSS_C_NO_CHANNEL_BINDINGS, -+ GSS_C_NO_BUFFER, /* no input token */ -+ NULL, /* actual_mech_type */ -+ &output_token, -+ NULL, /* ret_flags */ -+ NULL); /* time_rec */ -+ -+ gss_release_buffer(&minor, &output_token); -+ gss_release_name(&minor, &target_name); -+ if (ctx != GSS_C_NO_CONTEXT) -+ gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER); -+ -+ if (GSS_ERROR(major)) { -+ logit_f("S4U2Proxy for %.200s on behalf of %.200s failed", -+ services[i], -+ (char *)gssapi_client.displayname.value); -+ log_gss_error(major, minor, -+ "S4U2Proxy: gss_init_sec_context"); -+ } else -+ debug_f("S4U2Proxy ticket obtained for %.200s", -+ services[i]); - } -+ -+ /* -+ * Flush all proxy service tickets from the credential's internal -+ * ccache into the KRB5CCNAME ccache via gss_store_cred(). -+ */ -+ major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE, -+ GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */, -+ NULL, NULL); -+ if (GSS_ERROR(major)) { -+ logit_f("gss_store_cred failed; proxy tickets may be missing"); -+ log_gss_error(major, minor, "S4U2Proxy: gss_store_cred"); -+ } -+ -+ unsetenv("KRB5CCNAME"); -+} -+ -+#ifndef KRB5 -+/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */ -+void -+ssh_gssapi_cleanup_creds(void) -+{ -+} -+ -+/* -+ * Filter the user's ccache; full implementation in gss-serv-krb5.c. -+ */ -+void -+ssh_gssapi_krb5_filter_ccache(u_int drop_flags, -+ char **proxy_services, u_int nproxy_services) -+{ - } -+#endif /* !KRB5 */ - - /* As user */ - int -diff --git a/servconf.c b/servconf.c -index 1093dcbac..019070505 100644 ---- a/servconf.c -+++ b/servconf.c -@@ -149,6 +149,9 @@ initialize_server_options(ServerOptions *options) +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2026-03-12 11:51:16.603765849 +0100 ++++ b/servconf.c 2026-03-12 12:07:51.450992249 +0100 +@@ -148,6 +148,9 @@ initialize_server_options(ServerOptions + options->gss_indicators = NULL; options->gss_store_rekey = -1; options->gss_kex_algorithms = NULL; - options->gss_indicators = NULL; + options->gss_allow_s4u2self = -1; + options->gss_proxy_services = NULL; + options->num_gss_proxy_services = 0; options->use_kuserok = -1; options->enable_k5users = -1; options->password_authentication = -1; -@@ -404,6 +407,8 @@ fill_default_server_options(ServerOptions *options) +@@ -407,6 +410,8 @@ fill_default_server_options(ServerOption options->gss_deleg_creds = 1; if (options->gss_strict_acceptor == -1) options->gss_strict_acceptor = 1; @@ -685,7 +678,7 @@ index 1093dcbac..019070505 100644 if (options->gss_store_rekey == -1) options->gss_store_rekey = 0; #ifdef GSSAPI -@@ -599,7 +604,7 @@ typedef enum { +@@ -603,7 +608,7 @@ typedef enum { sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor, @@ -694,7 +687,7 @@ index 1093dcbac..019070505 100644 sAcceptEnv, sSetEnv, sPermitTunnel, sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, -@@ -697,6 +702,8 @@ static struct { +@@ -701,6 +706,8 @@ static struct { { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, { "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL }, { "gssapiindicators", sGssIndicators, SSHCFG_ALL }, @@ -703,7 +696,7 @@ index 1093dcbac..019070505 100644 #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, -@@ -708,6 +715,8 @@ static struct { +@@ -712,6 +719,8 @@ static struct { { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, { "gssapienablek5users", sUnsupported, SSHCFG_ALL }, { "gssapiindicators", sUnsupported, SSHCFG_ALL }, @@ -712,7 +705,7 @@ index 1093dcbac..019070505 100644 #endif { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, -@@ -1748,6 +1757,44 @@ process_server_config_line_depth(ServerOptions *options, char *line, +@@ -1742,6 +1751,44 @@ process_server_config_line_depth(ServerO options->gss_indicators = xstrdup(arg); break; @@ -757,7 +750,7 @@ index 1093dcbac..019070505 100644 case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; -@@ -3026,6 +3073,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) +@@ -3010,6 +3057,7 @@ copy_set_server_options(ServerOptions *d M_CP_INTOPT(password_authentication); M_CP_INTOPT(gss_authentication); @@ -765,7 +758,7 @@ index 1093dcbac..019070505 100644 M_CP_INTOPT(pubkey_authentication); M_CP_INTOPT(pubkey_auth_options); M_CP_INTOPT(kerberos_authentication); -@@ -3379,6 +3427,15 @@ dump_config(ServerOptions *o) +@@ -3364,6 +3412,15 @@ dump_config(ServerOptions *o) dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); dump_cfg_string(sGssIndicators, o->gss_indicators); @@ -781,10 +774,9 @@ index 1093dcbac..019070505 100644 #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, -diff --git a/servconf.h b/servconf.h -index 6bfdf6305..40dcd29d8 100644 ---- a/servconf.h -+++ b/servconf.h +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2026-03-12 11:51:16.604292877 +0100 ++++ b/servconf.h 2026-03-12 12:02:44.689745248 +0100 @@ -160,6 +160,9 @@ typedef struct { int gss_cleanup_creds; /* If true, destroy cred cache on logout */ int gss_deleg_creds; /* If true, accept delegated GSS credentials */ @@ -795,7 +787,7 @@ index 6bfdf6305..40dcd29d8 100644 int gss_store_rekey; char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ int password_authentication; /* If true, permit password -@@ -313,6 +316,7 @@ TAILQ_HEAD(include_list, include_item); +@@ -314,6 +317,7 @@ TAILQ_HEAD(include_list, include_item); M_CP_STROPT(permit_user_env_allowlist); \ M_CP_STROPT(pam_service_name); \ M_CP_STROPT(gss_indicators); \ @@ -803,42 +795,90 @@ index 6bfdf6305..40dcd29d8 100644 M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \ M_CP_STRARRAYOPT(allow_users, num_allow_users); \ M_CP_STRARRAYOPT(deny_users, num_deny_users); \ -diff --git a/ssh-gss.h b/ssh-gss.h -index 1506719a9..a0d51c9be 100644 ---- a/ssh-gss.h -+++ b/ssh-gss.h -@@ -119,6 +119,7 @@ typedef struct { - int used; - int updated; - char **indicators; /* auth indicators */ -+ int allow_self; /* allow protocol transition */ - } ssh_gssapi_client; - - typedef struct ssh_gssapi_mech_struct { -@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); - void ssh_gssapi_do_child(char ***, u_int *); - void ssh_gssapi_cleanup_creds(void); - int ssh_gssapi_storecreds(void); -+int ssh_gssapi_credentials_stored(void); -+ssh_gssapi_ccache *ssh_gssapi_get_ccache(void); -+int ssh_gssapi_user_has_valid_tgt(u_int); -+int ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int); -+int ssh_gssapi_s4u2self(const char *, u_int); -+void ssh_gssapi_storecreds_s4u2self(void); -+void ssh_gssapi_s4u2proxy(char **, u_int, u_int); -+/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */ -+#define SSH_GSSAPI_CCFILTER_TGT (1u << 0) /* krbtgt/... entries */ -+#define SSH_GSSAPI_CCFILTER_SELF (1u << 1) /* S4U2Self evidence ticket */ -+#define SSH_GSSAPI_CCFILTER_PROXY (1u << 2) /* S4U2Proxy service tickets */ -+void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int); - const char *ssh_gssapi_displayname(void); - - char *ssh_gssapi_server_mechanisms(void); -diff --git a/sshd-session.c b/sshd-session.c -index a558bbc33..d8b6af13f 100644 ---- a/sshd-session.c -+++ b/sshd-session.c -@@ -1425,6 +1425,104 @@ main(int ac, char **av) +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2026-03-12 11:51:16.604904588 +0100 ++++ b/sshd_config.5 2026-03-12 12:10:03.542653814 +0100 +@@ -834,6 +834,76 @@ FIDO2-based pre-authentication in FreeIP + The default + .Dq none + is to not use GSSAPI authentication indicators for access decisions. ++.It Cm GSSAPIAllowS4U2Self ++Controls whether the SSH server performs a Kerberos protocol transition ++(S4U2Self) after a successful authentication using any other method. ++Accepted values are ++.Cm no , ++.Cm yes , ++or a time interval (see ++.Sx TIME FORMATS ++below). ++.Cm no ++disables S4U2Self entirely. ++.Cm yes ++enables S4U2Self and requests a ticket with the maximum lifetime ++permitted by the KDC. ++A time interval (e.g.\& ++.Cm 8h , ++.Cm 1d ) ++enables S4U2Self and requests a ticket valid for at most that duration. ++The option is a no-op when delegated GSSAPI credentials are already available. ++The obtained service ticket is stored in the default credentials cache and is ++accessible to any application that has access to the Kerberos host principal ++.Pq host/machine.fqdn@REALM ++credentials on the same host. ++.Pp ++The default is ++.Cm no . ++.It Cm GSSAPIS42UProxyServices ++Specifies a list of Kerberos service principals for which constrained ++delegation (S4U2Proxy) tickets should be obtained after a successful ++S4U2Self protocol transition. ++Each entry must be a fully-qualified Kerberos principal name of the form ++.Ar service/host@REALM . ++Multiple principals may be listed, separated by whitespace. ++The keyword ++.Cm none ++clears any previously set list. ++.Pp ++This option may be used independently of ++.Cm GSSAPIAllowS4U2Self . ++When S4U2Self succeeds, the server iterates the list and calls ++.Xr gss_init_sec_context 3 ++for each principal with the proxy credential obtained by S4U2Self as ++the initiator. ++The GSSAPI library presents the evidence ticket to the KDC via an ++S4U2Proxy TGS-REQ; if the host service holds the necessary constrained- ++delegation permission in the KDC, a service ticket from the user to ++the target service is issued. ++These tickets are accumulated and then flushed into the user's ccache ++via ++.Xr gss_store_cred 3 , ++so that any application running in the user's session can use them ++without further interaction. ++The AP-REQ output token of each ++.Xr gss_init_sec_context 3 ++call is discarded; no network connection to the target service is made. ++.Pp ++When used together with ++.Cm GSSAPIAllowS4U2Self , ++the TGT and S4U2Self ticket are also stored in the user's ccache in ++addition to the S4U2Proxy service tickets. ++When used alone (without ++.Cm GSSAPIAllowS4U2Self ) , ++only the S4U2Proxy service tickets are stored; the intermediate S4U2Self ++credential is not placed in the user's ccache. ++.Pp ++This option supports ++.Cm Match ++blocks, allowing per-user or per-host lists of delegation targets. ++.Pp ++The default is empty (no S4U2Proxy delegation is performed). + .It Cm HostbasedAcceptedAlgorithms + The default is handled system-wide by + .Xr crypto-policies 7 . +diff --color -ruNp a/sshd-session.c b/sshd-session.c +--- a/sshd-session.c 2026-03-12 11:51:16.597852696 +0100 ++++ b/sshd-session.c 2026-03-12 12:03:24.375297257 +0100 +@@ -1489,6 +1489,104 @@ main(int ac, char **av) authctxt->krb5_set_env = ssh_gssapi_storecreds(); restore_uid(); } @@ -943,84 +983,33 @@ index a558bbc33..d8b6af13f 100644 #endif #ifdef WITH_SELINUX sshd_selinux_setup_exec_context(authctxt->pw->pw_name, -diff --git a/sshd_config.5 b/sshd_config.5 -index 3dbce55fc..d25b406fb 100644 ---- a/sshd_config.5 -+++ b/sshd_config.5 -@@ -832,6 +832,76 @@ FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens - .El - .Pp - The default is to not use GSSAPI authentication indicators for access decisions. -+.It Cm GSSAPIAllowS4U2Self -+Controls whether the SSH server performs a Kerberos protocol transition -+(S4U2Self) after a successful authentication using any other method. -+Accepted values are -+.Cm no , -+.Cm yes , -+or a time interval (see -+.Sx TIME FORMATS -+below). -+.Cm no -+disables S4U2Self entirely. -+.Cm yes -+enables S4U2Self and requests a ticket with the maximum lifetime -+permitted by the KDC. -+A time interval (e.g.\& -+.Cm 8h , -+.Cm 1d ) -+enables S4U2Self and requests a ticket valid for at most that duration. -+The option is a no-op when delegated GSSAPI credentials are already available. -+The obtained service ticket is stored in the default credentials cache and is -+accessible to any application that has access to the Kerberos host principal -+.Pq host/machine.fqdn@REALM -+credentials on the same host. -+.Pp -+The default is -+.Cm no . -+.It Cm GSSAPIS42UProxyServices -+Specifies a list of Kerberos service principals for which constrained -+delegation (S4U2Proxy) tickets should be obtained after a successful -+S4U2Self protocol transition. -+Each entry must be a fully-qualified Kerberos principal name of the form -+.Ar service/host@REALM . -+Multiple principals may be listed, separated by whitespace. -+The keyword -+.Cm none -+clears any previously set list. -+.Pp -+This option may be used independently of -+.Cm GSSAPIAllowS4U2Self . -+When S4U2Self succeeds, the server iterates the list and calls -+.Xr gss_init_sec_context 3 -+for each principal with the proxy credential obtained by S4U2Self as -+the initiator. -+The GSSAPI library presents the evidence ticket to the KDC via an -+S4U2Proxy TGS-REQ; if the host service holds the necessary constrained- -+delegation permission in the KDC, a service ticket from the user to -+the target service is issued. -+These tickets are accumulated and then flushed into the user's ccache -+via -+.Xr gss_store_cred 3 , -+so that any application running in the user's session can use them -+without further interaction. -+The AP-REQ output token of each -+.Xr gss_init_sec_context 3 -+call is discarded; no network connection to the target service is made. -+.Pp -+When used together with -+.Cm GSSAPIAllowS4U2Self , -+the TGT and S4U2Self ticket are also stored in the user's ccache in -+addition to the S4U2Proxy service tickets. -+When used alone (without -+.Cm GSSAPIAllowS4U2Self ) , -+only the S4U2Proxy service tickets are stored; the intermediate S4U2Self -+credential is not placed in the user's ccache. -+.Pp -+This option supports -+.Cm Match -+blocks, allowing per-user or per-host lists of delegation targets. -+.Pp -+The default is empty (no S4U2Proxy delegation is performed). - .It Cm HostbasedAcceptedAlgorithms - The default is handled system-wide by - .Xr crypto-policies 7 . +diff --color -ruNp a/ssh-gss.h b/ssh-gss.h +--- a/ssh-gss.h 2026-03-12 11:51:16.583291666 +0100 ++++ b/ssh-gss.h 2026-03-12 12:03:11.541329687 +0100 +@@ -119,6 +119,7 @@ typedef struct { + int used; + int updated; + char **indicators; /* auth indicators */ ++ int allow_self; /* allow protocol transition */ + } ssh_gssapi_client; + + typedef struct ssh_gssapi_mech_struct { +@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, + void ssh_gssapi_do_child(char ***, u_int *); + void ssh_gssapi_cleanup_creds(void); + int ssh_gssapi_storecreds(void); ++int ssh_gssapi_credentials_stored(void); ++ssh_gssapi_ccache *ssh_gssapi_get_ccache(void); ++int ssh_gssapi_user_has_valid_tgt(u_int); ++int ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int); ++int ssh_gssapi_s4u2self(const char *, u_int); ++void ssh_gssapi_storecreds_s4u2self(void); ++void ssh_gssapi_s4u2proxy(char **, u_int, u_int); ++/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */ ++#define SSH_GSSAPI_CCFILTER_TGT (1u << 0) /* krbtgt/... entries */ ++#define SSH_GSSAPI_CCFILTER_SELF (1u << 1) /* S4U2Self evidence ticket */ ++#define SSH_GSSAPI_CCFILTER_PROXY (1u << 2) /* S4U2Proxy service tickets */ ++void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int); + const char *ssh_gssapi_displayname(void); + + char *ssh_gssapi_server_mechanisms(void); diff --git a/openssh-9.9p1-maxstartups-mistracking.patch b/openssh-9.9p1-maxstartups-mistracking.patch new file mode 100644 index 0000000..0e92903 --- /dev/null +++ b/openssh-9.9p1-maxstartups-mistracking.patch @@ -0,0 +1,73 @@ +diff --color -ruNp a/srclimit.c b/srclimit.c +--- a/srclimit.c 2024-09-20 00:20:48.000000000 +0200 ++++ b/srclimit.c 2026-03-06 13:30:48.408309619 +0100 +@@ -427,7 +427,9 @@ srclimit_penalise(struct xaddr *addr, in + penalty->active = 1; + if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL) + fatal_f("internal error: %s penalty tables corrupt", t); +- verbose_f("%s: new %s %s penalty of %d seconds for %s", t, ++ do_log2_f(penalty->active ? ++ SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE, ++ "%s: new %s %s penalty of %d seconds for %s", t, + addrnetmask, penalty->active ? "active" : "deferred", + penalty_secs, reason); + if (++(*npenaltiesp) > (size_t)max_sources) +@@ -446,7 +448,7 @@ srclimit_penalise(struct xaddr *addr, in + existing->expiry = now + penalty_cfg.penalty_max; + if (existing->expiry - now > penalty_cfg.penalty_min && + !existing->active) { +- verbose_f("%s: activating %s penalty of %lld seconds for %s", ++ logit_f("%s: activating %s penalty of %lld seconds for %s", + addrnetmask, t, (long long)(existing->expiry - now), + reason); + existing->active = 1; +diff --color -ruNp a/sshd.c b/sshd.c +--- a/sshd.c 2026-03-06 13:10:52.653617548 +0100 ++++ b/sshd.c 2026-03-06 13:24:50.865079998 +0100 +@@ -291,8 +291,10 @@ child_finish(struct early_child *child) + { + if (children_active == 0) + fatal_f("internal error: children_active underflow"); +- if (child->pipefd != -1) ++ if (child->pipefd != -1) { ++ srclimit_done(child->pipefd); + close(child->pipefd); ++ } + free(child->id); + memset(child, '\0', sizeof(*child)); + child->pipefd = -1; +@@ -311,6 +313,7 @@ child_close(struct early_child *child, i + if (!quiet) + debug_f("enter%s", force_final ? " (forcing)" : ""); + if (child->pipefd != -1) { ++ srclimit_done(child->pipefd); + close(child->pipefd); + child->pipefd = -1; + } +@@ -978,10 +981,11 @@ server_accept_loop(int *sock_in, int *so + } + /* FALLTHROUGH */ + case 0: +- /* child exited preauth */ ++ /* child closed pipe */ + if (children[i].early) + listening--; +- srclimit_done(children[i].pipefd); ++ debug3_f("child %lu for %s closed pipe", ++ (long)children[i].pid, children[i].id); + child_close(&(children[i]), 0, 0); + break; + case 1: +@@ -1003,6 +1007,12 @@ server_accept_loop(int *sock_in, int *so + "child %ld for %s in state %d", + (int)c, (long)children[i].pid, + children[i].id, children[i].early); ++ ++ if (children[i].early) ++ listening--; ++ if (children[i].pid > 0) ++ kill(children[i].pid, SIGTERM); ++ child_close(&(children[i]), 0, 0); + } + break; + } diff --git a/openssh-9.9p1-sshd-no-delegate-credentials.patch b/openssh-9.9p1-sshd-no-delegate-credentials.patch index 2b70103..59d0120 100644 --- a/openssh-9.9p1-sshd-no-delegate-credentials.patch +++ b/openssh-9.9p1-sshd-no-delegate-credentials.patch @@ -1,8 +1,7 @@ -diff --git a/gss-serv.c b/gss-serv.c -index 5c0491cf1..e2c501d0c 100644 ---- a/gss-serv.c -+++ b/gss-serv.c -@@ -509,6 +509,11 @@ ssh_gssapi_cleanup_creds(void) +diff --color -ruNp a/gss-serv.c b/gss-serv.c +--- a/gss-serv.c 2026-03-11 13:54:53.076924823 +0100 ++++ b/gss-serv.c 2026-03-11 14:10:41.232855086 +0100 +@@ -493,6 +493,11 @@ ssh_gssapi_cleanup_creds(void) int ssh_gssapi_storecreds(void) { @@ -14,19 +13,18 @@ index 5c0491cf1..e2c501d0c 100644 if (gssapi_client.mech && gssapi_client.mech->storecreds) { return (*gssapi_client.mech->storecreds)(&gssapi_client); } else -diff --git a/servconf.c b/servconf.c -index aab653244..02a9888c9 100644 ---- a/servconf.c -+++ b/servconf.c -@@ -144,6 +144,7 @@ initialize_server_options(ServerOptions *options) +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2026-03-11 13:54:53.086263187 +0100 ++++ b/servconf.c 2026-03-11 14:03:10.708713524 +0100 +@@ -143,6 +143,7 @@ initialize_server_options(ServerOptions options->gss_authentication=-1; options->gss_keyex = -1; options->gss_cleanup_creds = -1; + options->gss_deleg_creds = -1; options->gss_strict_acceptor = -1; + options->gss_indicators = NULL; options->gss_store_rekey = -1; - options->gss_kex_algorithms = NULL; -@@ -403,6 +404,8 @@ fill_default_server_options(ServerOptions *options) +@@ -402,6 +403,8 @@ fill_default_server_options(ServerOption options->gss_keyex = 0; if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; @@ -61,7 +59,7 @@ index aab653244..02a9888c9 100644 { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, -@@ -1713,6 +1719,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, +@@ -1703,6 +1709,10 @@ process_server_config_line_depth(ServerO intptr = &options->gss_cleanup_creds; goto parse_flag; @@ -72,7 +70,7 @@ index aab653244..02a9888c9 100644 case sGssStrictAcceptor: intptr = &options->gss_strict_acceptor; goto parse_flag; -@@ -3359,6 +3369,7 @@ dump_config(ServerOptions *o) +@@ -3348,6 +3358,7 @@ dump_config(ServerOptions *o) #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); @@ -80,10 +78,9 @@ index aab653244..02a9888c9 100644 dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); -diff --git a/servconf.h b/servconf.h -index 7c41df417..6bfdf6305 100644 ---- a/servconf.h -+++ b/servconf.h +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2026-03-11 13:54:53.086763709 +0100 ++++ b/servconf.h 2026-03-11 14:13:51.130708769 +0100 @@ -158,6 +158,7 @@ typedef struct { int gss_authentication; /* If true, permit GSSAPI authentication */ int gss_keyex; /* If true, permit GSSAPI key exchange */ @@ -92,11 +89,10 @@ index 7c41df417..6bfdf6305 100644 int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ int gss_store_rekey; char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ -diff --git a/sshd_config.0 b/sshd_config.0 -index 49349bb30..e798f4df5 100644 ---- a/sshd_config.0 -+++ b/sshd_config.0 -@@ -453,6 +453,9 @@ DESCRIPTION +diff --color -ruNp a/sshd_config.0 b/sshd_config.0 +--- a/sshd_config.0 2026-03-11 13:54:52.904233471 +0100 ++++ b/sshd_config.0 2026-03-11 14:12:35.341170737 +0100 +@@ -451,6 +451,9 @@ DESCRIPTION Specifies whether to automatically destroy the user's credentials cache on logout. The default is yes. @@ -106,11 +102,10 @@ index 49349bb30..e798f4df5 100644 GSSAPIStrictAcceptorCheck Determines whether to be strict about the identity of the GSSAPI acceptor a client authenticates against. If set to yes then the -diff --git a/sshd_config.5 b/sshd_config.5 -index 90ab87edd..8c677bfd0 100644 ---- a/sshd_config.5 -+++ b/sshd_config.5 -@@ -733,6 +733,9 @@ Specifies whether to automatically destroy the user's credentials cache +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2026-03-11 13:54:53.087046352 +0100 ++++ b/sshd_config.5 2026-03-11 14:05:15.657248031 +0100 +@@ -733,6 +733,9 @@ Specifies whether to automatically destr on logout. The default is .Cm yes . diff --git a/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch b/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch index 237e45d..6c55a66 100644 --- a/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch +++ b/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch @@ -1,38 +1,7 @@ -From 5d5a66e96ad03132f65371070f4fa475f10207d9 Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Mon, 10 Jun 2024 23:00:03 +0300 -Subject: [PATCH] support authentication indicators in GSSAPI - -RFC 6680 defines a set of GSSAPI extensions to handle attributes -associated with the GSSAPI names. MIT Kerberos and FreeIPA use -name attributes to add information about pre-authentication methods used -to acquire the initial Kerberos ticket. The attribute 'auth-indicators' -may contain list of strings that KDC has associated with the ticket -issuance process. - -Use authentication indicators to authorise or deny access to SSH server. -GSSAPIIndicators setting allows to specify a list of possible indicators -that a Kerberos ticket presented must or must not contain. More details -on the syntax are provided in sshd_config(5) man page. - -Fixes: https://bugzilla.mindrot.org/show_bug.cgi?id=2696 - -Signed-off-by: Alexander Bokovoy ---- - configure.ac | 1 + - gss-serv-krb5.c | 64 +++++++++++++++++++++++++++--- - gss-serv.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++- - servconf.c | 15 ++++++- - servconf.h | 2 + - ssh-gss.h | 7 ++++ - sshd_config.5 | 44 +++++++++++++++++++++ - 7 files changed, 228 insertions(+), 8 deletions(-) - -diff --git a/configure.ac b/configure.ac -index d92a85809..2cbe20bf3 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -5004,6 +5004,7 @@ AC_ARG_WITH([kerberos5], +diff --color -ruNp a/configure.ac b/configure.ac +--- a/configure.ac 2026-03-10 12:43:36.860784813 +0100 ++++ b/configure.ac 2026-03-10 12:46:27.022297835 +0100 +@@ -4932,6 +4932,7 @@ AC_ARG_WITH([kerberos5], AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h]) AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h]) AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h]) @@ -40,113 +9,10 @@ index d92a85809..2cbe20bf3 100644 AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1], [Define this if you want to use libkafs' AFS support])]) -diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c -index 03188d9b3..2c786ef14 100644 ---- a/gss-serv-krb5.c -+++ b/gss-serv-krb5.c -@@ -43,6 +43,7 @@ - #include "log.h" - #include "misc.h" - #include "servconf.h" -+#include "match.h" - - #include "ssh-gss.h" - -@@ -87,6 +88,32 @@ ssh_gssapi_krb5_init(void) - return 1; - } - -+/* Check if any of the indicators in the Kerberos ticket match -+ * one of indicators in the list of allowed/denied rules. -+ * In case of the match, apply the decision from the rule. -+ * In case of no indicator from the ticket matching the rule, deny -+ */ -+ -+static int -+ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched) -+{ -+ int ret; -+ u_int i; -+ -+ /* Check indicators */ -+ for (i = 0; client->indicators[i] != NULL; i++) { -+ ret = match_pattern_list(client->indicators[i], -+ options.gss_indicators, 1); -+ /* negative or positive match */ -+ if (ret != 0) { -+ *matched = i; -+ return ret; -+ } -+ } -+ /* No rule matched */ -+ return 0; -+} -+ - /* Check if this user is OK to login. This only works with krb5 - other - * GSSAPI mechanisms will need their own. - * Returns true if the user is OK to log in, otherwise returns 0 -@@ -193,7 +220,7 @@ static int - ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) - { - krb5_principal princ; -- int retval; -+ int retval, matched; - const char *errmsg; - int k5login_exists; - -@@ -216,17 +243,42 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) - if (k5login_exists && - ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) { - retval = 1; -- logit("Authorized to %s, krb5 principal %s (krb5_kuserok)", -- name, (char *)client->displayname.value); -+ errmsg = "krb5_kuserok"; - } else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value, - name, k5login_exists)) { - retval = 1; -- logit("Authorized to %s, krb5 principal %s " -- "(ssh_gssapi_krb5_cmdok)", -- name, (char *)client->displayname.value); -+ errmsg = "ssh_gssapi_krb5_cmdok"; - } else - retval = 0; - -+ if ((retval == 1) && (options.gss_indicators != NULL)) { -+ /* At this point the configuration enforces presence of indicators -+ * so we drop the authorization result again */ -+ retval = 0; -+ if (client->indicators) { -+ matched = -1; -+ retval = ssh_gssapi_check_indicators(client, &matched); -+ if (retval != 0) { -+ retval = (retval == 1); -+ logit("Ticket contains indicator %s, " -+ "krb5 principal %s is %s", -+ client->indicators[matched], -+ (char *)client->displayname.value, -+ retval ? "allowed" : "denied"); -+ goto cont; -+ } -+ } -+ if (retval == 0) { -+ logit("GSSAPI authentication indicators enforced " -+ "but not matched. krb5 principal %s denied", -+ (char *)client->displayname.value); -+ } -+ } -+cont: -+ if (retval == 1) { -+ logit("Authorized to %s, krb5 principal %s (%s)", -+ name, (char *)client->displayname.value, errmsg); -+ } - krb5_free_principal(krb_context, princ); - return retval; - } -diff --git a/gss-serv.c b/gss-serv.c -index 9d5435eda..5c0491cf1 100644 ---- a/gss-serv.c -+++ b/gss-serv.c -@@ -54,7 +54,7 @@ extern ServerOptions options; +diff --color -ruNp a/gss-serv.c b/gss-serv.c +--- a/gss-serv.c 2026-03-10 12:43:36.802443034 +0100 ++++ b/gss-serv.c 2026-03-12 10:04:37.520993330 +0100 +@@ -53,7 +53,7 @@ extern ServerOptions options; static ssh_gssapi_client gssapi_client = { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, @@ -155,7 +21,7 @@ index 9d5435eda..5c0491cf1 100644 ssh_gssapi_mech gssapi_null_mech = { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; -@@ -296,6 +296,92 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name) +@@ -295,6 +295,95 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss return GSS_S_COMPLETE; } @@ -163,9 +29,10 @@ index 9d5435eda..5c0491cf1 100644 +/* Extract authentication indicators from the Kerberos ticket. Authentication + * indicators are GSSAPI name attributes for the name "auth-indicators". + * Multiple indicators might be present in the ticket. -+ * Each indicator is a utf8 string. */ ++ * Each indicator is an utf8 string. */ + +#define AUTH_INDICATORS_TAG "auth-indicators" ++#define SSH_GSSAPI_MAX_INDICATORS 64 + +/* Privileged (called from accept_secure_ctx) */ +static OM_uint32 @@ -188,24 +55,7 @@ index 9d5435eda..5c0491cf1 100644 + return (0); + } + -+ count = 0; -+ for (i = 0; i < attrs->count; i++) { -+ /* skip anything but auth-indicators */ -+ if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) || -+ strncmp(AUTH_INDICATORS_TAG, -+ attrs->elements[i].value, -+ sizeof(AUTH_INDICATORS_TAG) - 1) != 0) -+ continue; -+ count++; -+ } -+ -+ if (count == 0) { -+ /* No auth-indicators in the ticket */ -+ (void) gss_release_buffer_set(&ctx->minor, &attrs); -+ return (0); -+ } -+ -+ client->indicators = recallocarray(NULL, 0, count + 1, sizeof(char*)); ++ client->indicators = NULL; + count = 0; + for (i = 0; i < attrs->count; i++) { + authenticated = 0; @@ -213,22 +63,33 @@ index 9d5435eda..5c0491cf1 100644 + more = -1; + /* skip anything but auth-indicators */ + if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) || -+ strncmp(AUTH_INDICATORS_TAG, -+ attrs->elements[i].value, -+ sizeof(AUTH_INDICATORS_TAG) - 1) != 0) ++ memcmp(AUTH_INDICATORS_TAG, ++ attrs->elements[i].value, ++ sizeof(AUTH_INDICATORS_TAG) - 1) != 0) + continue; + /* retrieve all indicators */ + while (more != 0) { + value.value = NULL; + display_value.value = NULL; + ctx->major = gss_get_name_attribute(&ctx->minor, gss_name, -+ &attrs->elements[i], &authenticated, -+ &complete, &value, &display_value, &more); -+ if (ctx->major != GSS_S_COMPLETE) { ++ &attrs->elements[i], &authenticated, ++ &complete, &value, &display_value, &more); ++ if (ctx->major != GSS_S_COMPLETE) + goto out; -+ } + + if ((value.value != NULL) && authenticated) { ++ if (count >= SSH_GSSAPI_MAX_INDICATORS) { ++ logit("ssh_gssapi_getindicators: too many " ++ "indicators, truncating at %d", ++ SSH_GSSAPI_MAX_INDICATORS); ++ /* value/display_value released at out: */ ++ goto done; ++ } ++ ++ client->indicators = xrecallocarray(client->indicators, count, count + 1, sizeof(char*)); ++ if (client->indicators == NULL) { ++ fatal("ssh_gssapi_getindicators failed to allocate memory"); ++ } + client->indicators[count] = xmalloc(value.length + 1); + memcpy(client->indicators[count], value.value, value.length); + client->indicators[count][value.length] = '\0'; @@ -237,18 +98,26 @@ index 9d5435eda..5c0491cf1 100644 + } + } + ++done: ++ /* slot [count] is zeroed by recallocarray, serves as NULL sentinel */ ++ +out: ++ if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) { ++ for (i = 0; i < count; i++) ++ free(client->indicators[i]); ++ free(client->indicators); ++ client->indicators = NULL; ++ } + (void) gss_release_buffer(&ctx->minor, &value); + (void) gss_release_buffer(&ctx->minor, &display_value); + (void) gss_release_buffer_set(&ctx->minor, &attrs); + return (ctx->major); +} -+ + /* Extract the client details from a given context. This can only reliably * be called once for a context */ -@@ -385,6 +471,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) +@@ -384,6 +473,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g } gss_release_buffer(&ctx->minor, &ename); @@ -261,7 +130,7 @@ index 9d5435eda..5c0491cf1 100644 /* We can't copy this structure, so we just move the pointer to it */ client->creds = ctx->client_creds; -@@ -447,6 +539,7 @@ int +@@ -446,6 +541,7 @@ int ssh_gssapi_userok(char *user, struct passwd *pw, int kex) { OM_uint32 lmin; @@ -269,34 +138,174 @@ index 9d5435eda..5c0491cf1 100644 (void) kex; /* used in privilege separation */ -@@ -465,6 +558,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex) +@@ -464,8 +560,14 @@ ssh_gssapi_userok(char *user, struct pas gss_release_buffer(&lmin, &gssapi_client.displayname); gss_release_buffer(&lmin, &gssapi_client.exportedname); gss_release_cred(&lmin, &gssapi_client.creds); +- explicit_bzero(&gssapi_client, +- sizeof(ssh_gssapi_client)); + + if (gssapi_client.indicators != NULL) { -+ for(i = 0; gssapi_client.indicators[i] != NULL; i++) { ++ for (i = 0; gssapi_client.indicators[i] != NULL; i++) + free(gssapi_client.indicators[i]); -+ } + free(gssapi_client.indicators); + } + - explicit_bzero(&gssapi_client, - sizeof(ssh_gssapi_client)); ++ explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client)); return 0; -diff --git a/servconf.c b/servconf.c -index e7e4ad046..aab653244 100644 ---- a/servconf.c -+++ b/servconf.c -@@ -147,6 +147,7 @@ initialize_server_options(ServerOptions *options) + } + else +diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c +--- a/gss-serv-krb5.c 2026-03-10 12:43:36.823015336 +0100 ++++ b/gss-serv-krb5.c 2026-03-11 12:58:56.024455238 +0100 +@@ -43,6 +43,7 @@ + #include "log.h" + #include "misc.h" + #include "servconf.h" ++#include "match.h" + + #include "ssh-gss.h" + +@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void) + return 1; + } + ++/* Check if any of the indicators in the Kerberos ticket match ++ * one of indicators in the list of allowed/denied rules. ++ * In case of the match, apply the decision from the rule. ++ * In case of no indicator from the ticket matching the rule, deny ++ */ ++ ++static int ++ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched) ++{ ++ int ret; ++ u_int i; ++ *matched = -1; ++ ++ /* Check indicators */ ++ for (i = 0; client->indicators[i] != NULL; i++) { ++ ret = match_pattern_list(client->indicators[i], ++ options.gss_indicators, 1); ++ /* negative or positive match */ ++ if (ret != 0) { ++ *matched = i; ++ return ret; ++ } ++ } ++ /* No rule matched */ ++ return 0; ++} ++ + /* Check if this user is OK to login. This only works with krb5 - other + * GSSAPI mechanisms will need their own. + * Returns true if the user is OK to log in, otherwise returns 0 +@@ -193,15 +221,15 @@ static int + ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) + { + krb5_principal princ; +- int retval; ++ int retval, matched, success; + const char *errmsg; + int k5login_exists; + + if (ssh_gssapi_krb5_init() == 0) + return 0; + +- if ((retval = krb5_parse_name(krb_context, client->exportedname.value, +- &princ))) { ++ retval = krb5_parse_name(krb_context, client->exportedname.value, &princ); ++ if (retval) { + errmsg = krb5_get_error_message(krb_context, retval); + logit("krb5_parse_name(): %.100s", errmsg); + krb5_free_error_message(krb_context, errmsg); +@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client + if (k5login_exists && + ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) { + retval = 1; +- logit("Authorized to %s, krb5 principal %s (krb5_kuserok)", +- name, (char *)client->displayname.value); ++ errmsg = "krb5_kuserok"; + } else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value, + name, k5login_exists)) { + retval = 1; +- logit("Authorized to %s, krb5 principal %s " +- "(ssh_gssapi_krb5_cmdok)", +- name, (char *)client->displayname.value); +- } else ++ errmsg = "ssh_gssapi_krb5_cmdok"; ++ } else { ++ retval = 0; ++ goto out; ++ } ++ ++ /* At this point we are good if no indicators were defined */ ++ if (options.gss_indicators == NULL) { ++ retval = 1; ++ goto out; ++ } ++ ++ /* At this point we have indicators defined in the configuration, ++ * if clientt did not provide any indicators, we reject */ ++ if (!client->indicators) { ++ retval = 0; ++ logit("GSSAPI authentication indicators enforced " ++ "but indicators not provided by the client. " ++ "krb5 principal %s denied", ++ (char *)client->displayname.value); ++ goto out; ++ } ++ ++ /* At this point the configuration enforces presence of indicators ++ * check the match */ ++ matched = -1; ++ success = ssh_gssapi_check_indicators(client, &matched); ++ ++ switch (success) { ++ case 1: ++ logit("Provided indicator %s allowed by the configuration", ++ client->indicators[matched]); ++ retval = 1; ++ break; ++ case -1: ++ logit("Provided indicator %s rejected by the configuration", ++ client->indicators[matched]); ++ retval = 0; ++ break; ++ default: ++ logit("Provided indicators do not match the configuration"); + retval = 0; ++ break; ++ } + ++out: ++ if (retval == 1) { ++ logit("Authorized to %s, krb5 principal %s (%s)", ++ name, (char *)client->displayname.value, errmsg); ++ } + krb5_free_principal(krb_context, princ); + return retval; + } +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2026-03-10 12:43:36.928060353 +0100 ++++ b/servconf.c 2026-03-11 13:20:09.725354925 +0100 +@@ -144,6 +144,7 @@ initialize_server_options(ServerOptions + options->gss_keyex = -1; + options->gss_cleanup_creds = -1; options->gss_strict_acceptor = -1; ++ options->gss_indicators = NULL; options->gss_store_rekey = -1; options->gss_kex_algorithms = NULL; -+ options->gss_indicators = NULL; options->use_kuserok = -1; - options->enable_k5users = -1; - options->password_authentication = -1; -@@ -598,7 +599,7 @@ typedef enum { +@@ -557,6 +558,7 @@ fill_default_server_options(ServerOption + CLEAR_ON_NONE(options->routing_domain); + CLEAR_ON_NONE(options->host_key_agent); + CLEAR_ON_NONE(options->per_source_penalty_exempt); ++ CLEAR_ON_NONE(options->gss_indicators); + + for (i = 0; i < options->num_host_key_files; i++) + CLEAR_ON_NONE(options->host_key_files[i]); +@@ -594,7 +596,7 @@ typedef enum { sPerSourcePenalties, sPerSourcePenaltyExemptList, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssEnablek5users, sGssStrictAcceptor, @@ -305,7 +314,7 @@ index e7e4ad046..aab653244 100644 sAcceptEnv, sSetEnv, sPermitTunnel, sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, -@@ -694,6 +695,7 @@ static struct { +@@ -690,6 +692,7 @@ static struct { { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, { "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL }, @@ -313,7 +322,7 @@ index e7e4ad046..aab653244 100644 #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, -@@ -703,6 +705,7 @@ static struct { +@@ -699,6 +702,7 @@ static struct { { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, { "gssapienablek5users", sUnsupported, SSHCFG_ALL }, @@ -321,7 +330,7 @@ index e7e4ad046..aab653244 100644 #endif { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, -@@ -1730,6 +1733,15 @@ process_server_config_line_depth(ServerOptions *options, char *line, +@@ -1715,6 +1719,15 @@ process_server_config_line_depth(ServerO options->gss_kex_algorithms = xstrdup(arg); break; @@ -337,7 +346,7 @@ index e7e4ad046..aab653244 100644 case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; -@@ -3351,6 +3363,7 @@ dump_config(ServerOptions *o) +@@ -3329,6 +3342,7 @@ dump_config(ServerOptions *o) dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); @@ -345,10 +354,9 @@ index e7e4ad046..aab653244 100644 #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, -diff --git a/servconf.h b/servconf.h -index 7c7e5d434..7c41df417 100644 ---- a/servconf.h -+++ b/servconf.h +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2026-03-10 12:43:36.833119920 +0100 ++++ b/servconf.h 2026-03-11 13:21:36.742117033 +0100 @@ -181,6 +181,7 @@ typedef struct { char **allow_groups; u_int num_deny_groups; @@ -357,7 +365,7 @@ index 7c7e5d434..7c41df417 100644 u_int num_subsystems; char **subsystem_name; -@@ -310,6 +311,7 @@ TAILQ_HEAD(include_list, include_item); +@@ -309,6 +310,7 @@ TAILQ_HEAD(include_list, include_item); M_CP_STROPT(routing_domain); \ M_CP_STROPT(permit_user_env_allowlist); \ M_CP_STROPT(pam_service_name); \ @@ -365,36 +373,10 @@ index 7c7e5d434..7c41df417 100644 M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \ M_CP_STRARRAYOPT(allow_users, num_allow_users); \ M_CP_STRARRAYOPT(deny_users, num_deny_users); \ -diff --git a/ssh-gss.h b/ssh-gss.h -index a894e23c9..59cf46d47 100644 ---- a/ssh-gss.h -+++ b/ssh-gss.h -@@ -34,6 +34,12 @@ - #include - #endif - -+#ifdef HAVE_GSSAPI_EXT_H -+#include -+#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H) -+#include -+#endif -+ - #ifdef KRB5 - # ifndef HEIMDAL - # ifdef HAVE_GSSAPI_GENERIC_H -@@ -107,6 +113,7 @@ typedef struct { - ssh_gssapi_ccache store; - int used; - int updated; -+ char **indicators; /* auth indicators */ - } ssh_gssapi_client; - - typedef struct ssh_gssapi_mech_struct { -diff --git a/sshd_config.5 b/sshd_config.5 -index 583a01cdb..90ab87edd 100644 ---- a/sshd_config.5 -+++ b/sshd_config.5 -@@ -785,6 +785,50 @@ gss-nistp256-sha256- +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2026-03-10 12:43:36.859313302 +0100 ++++ b/sshd_config.5 2026-03-11 13:28:04.541970063 +0100 +@@ -785,6 +785,52 @@ gss-nistp256-sha256- gss-curve25519-sha256- .Ed This option only applies to connections using GSSAPI. @@ -441,10 +423,33 @@ index 583a01cdb..90ab87edd 100644 +FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens +.El +.Pp -+The default is to not use GSSAPI authentication indicators for access decisions. ++The default ++.Dq none ++is to not use GSSAPI authentication indicators for access decisions. .It Cm HostbasedAcceptedAlgorithms The default is handled system-wide by .Xr crypto-policies 7 . --- -2.49.0 - +diff --color -ruNp a/ssh-gss.h b/ssh-gss.h +--- a/ssh-gss.h 2026-03-10 12:43:36.898148309 +0100 ++++ b/ssh-gss.h 2026-03-11 13:23:07.601956965 +0100 +@@ -34,6 +34,12 @@ + #include + #endif + ++#ifdef HAVE_GSSAPI_EXT_H ++#include ++#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H) ++#include ++#endif ++ + #ifdef KRB5 + # ifndef HEIMDAL + # ifdef HAVE_GSSAPI_GENERIC_H +@@ -112,6 +118,7 @@ typedef struct { + ssh_gssapi_ccache store; + int used; + int updated; ++ char **indicators; /* auth indicators */ + } ssh_gssapi_client; + + typedef struct ssh_gssapi_mech_struct { diff --git a/openssh.spec b/openssh.spec index f354a01..f19714e 100644 --- a/openssh.spec +++ b/openssh.spec @@ -43,7 +43,7 @@ Summary: An open source implementation of SSH protocol version 2 Name: openssh Version: %{openssh_ver} -Release: 21%{?dist}.alma.1 +Release: 22%{?dist}.alma.1 URL: http://www.openssh.com/portable.html Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc @@ -228,6 +228,15 @@ Patch1033: openssh-9.9p1-reject-null-char-in-url-string.patch Patch1034: openssh-9.9p1-sshd-no-delegate-credentials.patch Patch1035: openssh-10.0-mlkem-nist-fips.patch Patch1036: openssh-9.9p1-gssapi-s4u.patch +# upstream 683d0abe596b069a896f1688f86256f1beeb0cdc +# upstream 9313233a735733821dfd170b70782fb7da492962 +# upstream 2b0f4a72bd87bef7cc9f0a1889cfc98545cbb158 +# upstream 19f7cb39eecb4b8f768f37e8294dc3a9142e022b +# upstream 97b32fa2af25c16aec4de85c5cbb63fd038b4dfa +Patch1037: openssh-9.9p1-first-match-wins.patch +# upstream eddd1d2daa64a6ab1a915ca88436fa41aede44d4 +# upstream bc328144f149af07139a0f2c1329018cd85b86b7 +Patch1038: openssh-9.9p1-maxstartups-mistracking.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -252,7 +261,6 @@ BuildRequires: systemd-rpm-macros BuildRequires: gcc make BuildRequires: p11-kit-devel BuildRequires: libfido2-devel -Recommends: p11-kit Obsoletes: openssh-ldap < 8.3p1-4 Obsoletes: openssh-cavs < 8.4p1-5 @@ -429,6 +437,8 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1034 -p1 -b .sshd-nogsscreds %patch -P 1035 -p1 -b .mlkem-nist-fips %patch -P 1036 -p1 -b .gssapi-s4u +%patch -P 1037 -p1 -b .first-match-wins +%patch -P 1038 -p1 -b .maxstartups-mistracking %patch -P 100 -p1 -b .coverity @@ -709,9 +719,23 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog -* Thu Mar 12 2026 Koichiro Iwao - 9.9p1-21.alma.1 +* Wed Mar 18 2026 Koichiro Iwao - 9.9p1-22.alma.1 - Unpatch Red Hat help message +* Thu Mar 12 2026 Zoltan Fridrich - 9.9p1-22 +- Remove recommendation of p11-kit + Resolves: RHEL-139070 +- Only the first value of MaxStartups, PerSourceNetBlockSize and IPQoS + in sshd_config should count when defined multiple times + Resolves: RHEL-150365 +- Fix mistracking of MaxStartups process exits in some situations + Resolves: RHEL-121768 +- Fix GSSAPI authentication indicator issues found by AI + Resolves: RHEL-154309 +- CVE-2026-3497: Fix information disclosure or denial of service due + to uninitialized variables in gssapi-keyex + Resolves: RHEL-155813 + * Wed Mar 11 2026 Dmitry Belyavskiy - 9.9p1-21 - Implement obtaining Kerberos tickets on behalf of user on SSH authentication Resolves: RHEL-92932