summaryrefslogtreecommitdiffstats
path: root/debian/patches
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:40:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:40:05 +0000
commite21fe8c3a4007c4a10f67e9e8107c3f083002f06 (patch)
tree926b3c2a4d3fd0decde08f6a4d22862ef03a7046 /debian/patches
parentAdding upstream version 1:9.2p1. (diff)
downloadopenssh-e21fe8c3a4007c4a10f67e9e8107c3f083002f06.tar.xz
openssh-e21fe8c3a4007c4a10f67e9e8107c3f083002f06.zip
Adding debian version 1:9.2p1-2+deb12u2.debian/1%9.2p1-2+deb12u2debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/patches')
-rw-r--r--debian/patches/CVE-2023-28531.patch31
-rw-r--r--debian/patches/CVE-2023-38408-1.patch30
-rw-r--r--debian/patches/CVE-2023-38408-2.patch104
-rw-r--r--debian/patches/CVE-2023-38408-3.patch174
-rw-r--r--debian/patches/CVE-2023-48795.patch468
-rw-r--r--debian/patches/CVE-2023-51384.patch168
-rw-r--r--debian/patches/CVE-2023-51385.patch94
-rw-r--r--debian/patches/authorized-keys-man-symlink.patch26
-rw-r--r--debian/patches/conch-ssh-rsa.patch42
-rw-r--r--debian/patches/debian-banner.patch162
-rw-r--r--debian/patches/debian-config.patch285
-rw-r--r--debian/patches/dnssec-sshfp.patch94
-rw-r--r--debian/patches/doc-hash-tab-completion.patch28
-rw-r--r--debian/patches/gnome-ssh-askpass2-icon.patch26
-rw-r--r--debian/patches/gssapi.patch4156
-rw-r--r--debian/patches/keepalive-extensions.patch135
-rw-r--r--debian/patches/maxhostnamelen.patch30
-rw-r--r--debian/patches/mention-ssh-keygen-on-keychange.patch44
-rw-r--r--debian/patches/no-openssl-version-status.patch62
-rw-r--r--debian/patches/openbsd-docs.patch255
-rw-r--r--debian/patches/package-versioning.patch47
-rw-r--r--debian/patches/remove-spurious-ssh-agent-options.patch29
-rw-r--r--debian/patches/restore-authorized_keys2.patch35
-rw-r--r--debian/patches/restore-tcp-wrappers.patch172
-rw-r--r--debian/patches/revert-ipqos-defaults.patch93
-rw-r--r--debian/patches/scp-quoting.patch41
-rw-r--r--debian/patches/selinux-role.patch471
-rw-r--r--debian/patches/series35
-rw-r--r--debian/patches/shell-path.patch39
-rw-r--r--debian/patches/ssh-agent-setgid.patch40
-rw-r--r--debian/patches/ssh-argv0.patch31
-rw-r--r--debian/patches/ssh-vulnkey-compat.patch42
-rw-r--r--debian/patches/syslog-level-silent.patch47
-rw-r--r--debian/patches/systemd-readiness.patch84
-rw-r--r--debian/patches/systemd-socket-activation.patch138
-rw-r--r--debian/patches/user-group-modes.patch210
36 files changed, 7968 insertions, 0 deletions
diff --git a/debian/patches/CVE-2023-28531.patch b/debian/patches/CVE-2023-28531.patch
new file mode 100644
index 0000000..448632a
--- /dev/null
+++ b/debian/patches/CVE-2023-28531.patch
@@ -0,0 +1,31 @@
+From d1461c936223751e723662115b12bb0e9ba96f65 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Thu, 9 Mar 2023 06:58:26 +0000
+Subject: upstream: include destination constraints for smartcard keys too.
+
+Spotted by Luci Stanescu; ok deraadt@ markus@
+
+OpenBSD-Commit-ID: add879fac6903a1cb1d1e42c4309e5359c3d870f
+
+Bug-Debian: https://bugs.debian.org/1033166
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=54ac4ab2b53ce9fcb66b8250dee91c070e4167ed
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2023-28531.patch
+---
+ authfd.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/authfd.c b/authfd.c
+index b633e35ea..b9ae505ad 100644
+--- a/authfd.c
++++ b/authfd.c
+@@ -666,7 +666,7 @@ ssh_update_card(int sock, int add, const char *reader_id, const char *pin,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
+ {
+ struct sshbuf *msg;
+- int r, constrained = (life || confirm);
++ int r, constrained = (life || confirm || dest_constraints);
+ u_char type;
+
+ if (add) {
diff --git a/debian/patches/CVE-2023-38408-1.patch b/debian/patches/CVE-2023-38408-1.patch
new file mode 100644
index 0000000..9f6d1ec
--- /dev/null
+++ b/debian/patches/CVE-2023-38408-1.patch
@@ -0,0 +1,30 @@
+From dee3878689aef5365955442869be02d420b65ea6 Mon Sep 17 00:00:00 2001
+From: Damien Miller <djm@mindrot.org>
+Date: Thu, 13 Jul 2023 12:09:34 +1000
+Subject: terminate pkcs11 process for bad libraries
+
+Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=b23fe83f06ee7e721033769cfa03ae840476d280
+Last-Update: 2023-09-17
+
+Patch-Name: CVE-2023-38408-1.patch
+---
+ ssh-pkcs11.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index b2e2b32a5..9e48c134e 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -1537,10 +1537,8 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ error("dlopen %s failed: %s", provider_id, dlerror());
+ goto fail;
+ }
+- if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) {
+- error("dlsym(C_GetFunctionList) failed: %s", dlerror());
+- goto fail;
+- }
++ if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
++ fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
+ p = xcalloc(1, sizeof(*p));
+ p->name = xstrdup(provider_id);
+ p->handle = handle;
diff --git a/debian/patches/CVE-2023-38408-2.patch b/debian/patches/CVE-2023-38408-2.patch
new file mode 100644
index 0000000..c8f5980
--- /dev/null
+++ b/debian/patches/CVE-2023-38408-2.patch
@@ -0,0 +1,104 @@
+From 5c06b89189eb27f692b900526d60bf744918511e Mon Sep 17 00:00:00 2001
+From: Damien Miller <djm@mindrot.org>
+Date: Fri, 7 Jul 2023 13:30:15 +1000
+Subject: disallow remote addition of FIDO/PKCS11 keys
+
+Depends on the local client performing the session-bind@openssh.com
+operation, so non-OpenSSH local client may circumvent this.
+
+Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=d7790cdce72a1b6982795baa2b4d6f0bdbb0100d
+Last-Update: 2023-09-17
+
+Patch-Name: CVE-2023-38408-2.patch
+---
+ ssh-agent.1 | 22 ++++++++++++++++++++--
+ ssh-agent.c | 21 ++++++++++++++++++++-
+ 2 files changed, 40 insertions(+), 3 deletions(-)
+
+diff --git a/ssh-agent.1 b/ssh-agent.1
+index ba418bb41..0e5b6c15c 100644
+--- a/ssh-agent.1
++++ b/ssh-agent.1
+@@ -107,9 +107,27 @@ environment variable).
+ .It Fl O Ar option
+ Specify an option when starting
+ .Nm .
+-Currently only one option is supported:
++Currently two options are supported:
++.Cm allow-remote-pkcs11
++and
+ .Cm no-restrict-websafe .
+-This instructs
++.Pp
++The
++.Cm allow-remote-pkcs11
++option allows clients of a forwarded
++.Nm
++to load PKCS#11 or FIDO provider libraries.
++By default only local clients may perform this operation.
++Note that signalling that a
++.Nm
++client remote is performed by
++.Xr ssh 1 ,
++and use of other tools to forward access to the agent socket may circumvent
++this restriction.
++.Pp
++The
++.Cm no-restrict-websafe ,
++instructs
+ .Nm
+ to permit signatures using FIDO keys that might be web authentication
+ requests.
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 63e1137bc..dce2849d8 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -170,6 +170,12 @@ char socket_dir[PATH_MAX];
+ /* Pattern-list of allowed PKCS#11/Security key paths */
+ static char *allowed_providers;
+
++/*
++ * Allows PKCS11 providers or SK keys that use non-internal providers to
++ * be added over a remote connection (identified by session-bind@openssh.com).
++ */
++static int remote_add_provider;
++
+ /* locking */
+ #define LOCK_SIZE 32
+ #define LOCK_SALT_SIZE 16
+@@ -1229,6 +1235,12 @@ process_add_identity(SocketEntry *e)
+ if (strcasecmp(sk_provider, "internal") == 0) {
+ debug_f("internal provider");
+ } else {
++ if (e->nsession_ids != 0 && !remote_add_provider) {
++ verbose("failed add of SK provider \"%.100s\": "
++ "remote addition of providers is disabled",
++ sk_provider);
++ goto out;
++ }
+ if (realpath(sk_provider, canonical_provider) == NULL) {
+ verbose("failed provider \"%.100s\": "
+ "realpath: %s", sk_provider,
+@@ -1392,6 +1404,11 @@ process_add_smartcard_key(SocketEntry *e)
+ error_f("failed to parse constraints");
+ goto send;
+ }
++ if (e->nsession_ids != 0 && !remote_add_provider) {
++ verbose("failed PKCS#11 add of \"%.100s\": remote addition of "
++ "providers is disabled", provider);
++ goto send;
++ }
+ if (realpath(provider, canonical_provider) == NULL) {
+ verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+ provider, strerror(errno));
+@@ -2052,7 +2069,9 @@ main(int ac, char **av)
+ break;
+ case 'O':
+ if (strcmp(optarg, "no-restrict-websafe") == 0)
+- restrict_websafe = 0;
++ restrict_websafe = 0;
++ else if (strcmp(optarg, "allow-remote-pkcs11") == 0)
++ remote_add_provider = 1;
+ else
+ fatal("Unknown -O option");
+ break;
diff --git a/debian/patches/CVE-2023-38408-3.patch b/debian/patches/CVE-2023-38408-3.patch
new file mode 100644
index 0000000..6089222
--- /dev/null
+++ b/debian/patches/CVE-2023-38408-3.patch
@@ -0,0 +1,174 @@
+From 29c7785a3673101b3af8f6f712795fa128e52ddd Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Wed, 19 Jul 2023 14:02:27 +0000
+Subject: upstream: Ensure FIDO/PKCS11 libraries contain expected symbols
+
+This checks via nlist(3) that candidate provider libraries contain one
+of the symbols that we will require prior to dlopen(), which can cause
+a number of side effects, including execution of constructors.
+
+Feedback deraadt; ok markus
+
+OpenBSD-Commit-ID: 1508a5fbd74e329e69a55b56c453c292029aefbe
+
+Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=29ef8a04866ca14688d5b7fed7b8b9deab851f77
+Last-Update: 2023-09-17
+
+Patch-Name: CVE-2023-38408-3.patch
+---
+ misc.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ misc.h | 1 +
+ ssh-pkcs11.c | 4 +++
+ ssh-sk.c | 8 +++---
+ 4 files changed, 86 insertions(+), 3 deletions(-)
+
+diff --git a/misc.c b/misc.c
+index 2268d8875..36e72f5b5 100644
+--- a/misc.c
++++ b/misc.c
+@@ -22,6 +22,7 @@
+
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
++#include <sys/mman.h>
+ #include <sys/socket.h>
+ #include <sys/stat.h>
+ #include <sys/time.h>
+@@ -35,6 +36,9 @@
+ #ifdef HAVE_POLL_H
+ #include <poll.h>
+ #endif
++#ifdef HAVE_NLIST_H
++#include <nlist.h>
++#endif
+ #include <signal.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+@@ -2962,3 +2966,75 @@ ptimeout_isset(struct timespec *pt)
+ {
+ return pt->tv_sec != -1;
+ }
++
++/*
++ * Returns zero if the library at 'path' contains symbol 's', nonzero
++ * otherwise.
++ */
++int
++lib_contains_symbol(const char *path, const char *s)
++{
++#ifdef HAVE_NLIST_H
++ struct nlist nl[2];
++ int ret = -1, r;
++
++ memset(nl, 0, sizeof(nl));
++ nl[0].n_name = xstrdup(s);
++ nl[1].n_name = NULL;
++ if ((r = nlist(path, nl)) == -1) {
++ error_f("nlist failed for %s", path);
++ goto out;
++ }
++ if (r != 0 || nl[0].n_value == 0 || nl[0].n_type == 0) {
++ error_f("library %s does not contain symbol %s", path, s);
++ goto out;
++ }
++ /* success */
++ ret = 0;
++ out:
++ free(nl[0].n_name);
++ return ret;
++#else /* HAVE_NLIST_H */
++ int fd, ret = -1;
++ struct stat st;
++ void *m = NULL;
++ size_t sz = 0;
++
++ memset(&st, 0, sizeof(st));
++ if ((fd = open(path, O_RDONLY)) < 0) {
++ error_f("open %s: %s", path, strerror(errno));
++ return -1;
++ }
++ if (fstat(fd, &st) != 0) {
++ error_f("fstat %s: %s", path, strerror(errno));
++ goto out;
++ }
++ if (!S_ISREG(st.st_mode)) {
++ error_f("%s is not a regular file", path);
++ goto out;
++ }
++ if (st.st_size < 0 ||
++ (size_t)st.st_size < strlen(s) ||
++ st.st_size >= INT_MAX/2) {
++ error_f("%s bad size %lld", path, (long long)st.st_size);
++ goto out;
++ }
++ sz = (size_t)st.st_size;
++ if ((m = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED ||
++ m == NULL) {
++ error_f("mmap %s: %s", path, strerror(errno));
++ goto out;
++ }
++ if (memmem(m, sz, s, strlen(s)) == NULL) {
++ error_f("%s does not contain expected string %s", path, s);
++ goto out;
++ }
++ /* success */
++ ret = 0;
++ out:
++ if (m != NULL && m != MAP_FAILED)
++ munmap(m, sz);
++ close(fd);
++ return ret;
++#endif /* HAVE_NLIST_H */
++}
+diff --git a/misc.h b/misc.h
+index 31b6557c6..1a49e7cec 100644
+--- a/misc.h
++++ b/misc.h
+@@ -96,6 +96,7 @@ int parse_absolute_time(const char *, uint64_t *);
+ void format_absolute_time(uint64_t, char *, size_t);
+ int path_absolute(const char *);
+ int stdfd_devnull(int, int, int);
++int lib_contains_symbol(const char *, const char *);
+
+ void sock_set_v6only(int);
+
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index 9e48c134e..0aef37934 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -1532,6 +1532,10 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ debug_f("provider already registered: %s", provider_id);
+ goto fail;
+ }
++ if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) {
++ error("provider %s is not a PKCS11 library", provider_id);
++ goto fail;
++ }
+ /* open shared pkcs11-library */
+ if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
+ error("dlopen %s failed: %s", provider_id, dlerror());
+diff --git a/ssh-sk.c b/ssh-sk.c
+index fbeb39320..d1c18803f 100644
+--- a/ssh-sk.c
++++ b/ssh-sk.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: ssh-sk.c,v 1.39 2022/07/20 03:29:14 djm Exp $ */
++/* $OpenBSD: ssh-sk.c,v 1.40 2023/07/19 14:02:27 djm Exp $ */
+ /*
+ * Copyright (c) 2019 Google LLC
+ *
+@@ -133,10 +133,12 @@ sshsk_open(const char *path)
+ goto fail;
+ #endif
+ }
+- if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
+- error("Provider \"%s\" dlopen failed: %s", path, dlerror());
++ if (lib_contains_symbol(path, "sk_api_version") != 0) {
++ error("provider %s is not an OpenSSH FIDO library", path);
+ goto fail;
+ }
++ if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL)
++ fatal("Provider \"%s\" dlopen failed: %s", path, dlerror());
+ if ((ret->sk_api_version = dlsym(ret->dlhandle,
+ "sk_api_version")) == NULL) {
+ error("Provider \"%s\" dlsym(sk_api_version) failed: %s",
diff --git a/debian/patches/CVE-2023-48795.patch b/debian/patches/CVE-2023-48795.patch
new file mode 100644
index 0000000..ef4eca1
--- /dev/null
+++ b/debian/patches/CVE-2023-48795.patch
@@ -0,0 +1,468 @@
+From 9148d0a8031d89f53f045b63ac3a709611d94778 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Mon, 18 Dec 2023 14:45:17 +0000
+Subject: upstream: implement "strict key exchange" in ssh and sshd
+
+This adds a protocol extension to improve the integrity of the SSH
+transport protocol, particular in and around the initial key exchange
+(KEX) phase.
+
+Full details of the extension are in the PROTOCOL file.
+
+with markus@
+
+OpenBSD-Commit-ID: 2a66ac962f0a630d7945fee54004ed9e9c439f14
+
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=1edb00c58f8a6875fad6a497aa2bacf37f9e6cd5
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2023-48795.patch
+---
+ PROTOCOL | 26 +++++++++++++++++
+ kex.c | 72 +++++++++++++++++++++++++++++++----------------
+ kex.h | 1 +
+ packet.c | 78 ++++++++++++++++++++++++++++++++++++++-------------
+ sshconnect2.c | 14 +++------
+ sshd.c | 9 ++++--
+ 6 files changed, 143 insertions(+), 57 deletions(-)
+
+diff --git a/PROTOCOL b/PROTOCOL
+index 27804d0ca..ad611a01a 100644
+--- a/PROTOCOL
++++ b/PROTOCOL
+@@ -104,6 +104,32 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o
+
+ This is identical to curve25519-sha256 as later published in RFC8731.
+
++1.9 transport: strict key exchange extension
++
++OpenSSH supports a number of transport-layer hardening measures under
++a "strict KEX" feature. This feature is signalled similarly to the
++RFC8308 ext-info feature: by including a additional algorithm in the
++initiial SSH2_MSG_KEXINIT kex_algorithms field. The client may append
++"kex-strict-c-v00@openssh.com" to its kex_algorithms and the server
++may append "kex-strict-s-v00@openssh.com". These pseudo-algorithms
++are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
++if they are present in subsequent SSH2_MSG_KEXINIT packets.
++
++When an endpoint that supports this extension observes this algorithm
++name in a peer's KEXINIT packet, it MUST make the following changes to
++the the protocol:
++
++a) During initial KEX, terminate the connection if any unexpected or
++ out-of-sequence packet is received. This includes terminating the
++ connection if the first packet received is not SSH2_MSG_KEXINIT.
++ Unexpected packets for the purpose of strict KEX include messages
++ that are otherwise valid at any time during the connection such as
++ SSH2_MSG_DEBUG and SSH2_MSG_IGNORE.
++b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the
++ packet sequence number to zero. This behaviour persists for the
++ duration of the connection (i.e. not just the first
++ SSH2_MSG_NEWKEYS).
++
+ 2. Connection protocol changes
+
+ 2.1. connection: Channel write close extension "eow@openssh.com"
+diff --git a/kex.c b/kex.c
+index e7b7316c7..0b4fc4767 100644
+--- a/kex.c
++++ b/kex.c
+@@ -68,7 +68,7 @@
+ #endif
+
+ /* prototype */
+-static int kex_choose_conf(struct ssh *);
++static int kex_choose_conf(struct ssh *, uint32_t seq);
+ static int kex_input_newkeys(int, u_int32_t, struct ssh *);
+
+ static const char * const proposal_names[PROPOSAL_MAX] = {
+@@ -209,6 +209,18 @@ kex_names_valid(const char *names)
+ return 1;
+ }
+
++/* returns non-zero if proposal contains any algorithm from algs */
++static int
++has_any_alg(const char *proposal, const char *algs)
++{
++ char *cp;
++
++ if ((cp = match_list(proposal, algs, NULL)) == NULL)
++ return 0;
++ free(cp);
++ return 1;
++}
++
+ /*
+ * Concatenate algorithm names, avoiding duplicates in the process.
+ * Caller must free returned string.
+@@ -216,7 +228,7 @@ kex_names_valid(const char *names)
+ char *
+ kex_names_cat(const char *a, const char *b)
+ {
+- char *ret = NULL, *tmp = NULL, *cp, *p, *m;
++ char *ret = NULL, *tmp = NULL, *cp, *p;
+ size_t len;
+
+ if (a == NULL || *a == '\0')
+@@ -233,10 +245,8 @@ kex_names_cat(const char *a, const char *b)
+ }
+ strlcpy(ret, a, len);
+ for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
+- if ((m = match_list(ret, p, NULL)) != NULL) {
+- free(m);
++ if (has_any_alg(ret, p))
+ continue; /* Algorithm already present */
+- }
+ if (strlcat(ret, ",", len) >= len ||
+ strlcat(ret, p, len) >= len) {
+ free(tmp);
+@@ -467,7 +477,12 @@ kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh)
+ {
+ int r;
+
+- error("kex protocol error: type %d seq %u", type, seq);
++ /* If in strict mode, any unexpected message is an error */
++ if ((ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) {
++ ssh_packet_disconnect(ssh, "strict KEX violation: "
++ "unexpected packet type %u (seqnr %u)", type, seq);
++ }
++ error_f("type %u seq %u", type, seq);
+ if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 ||
+ (r = sshpkt_put_u32(ssh, seq)) != 0 ||
+ (r = sshpkt_send(ssh)) != 0)
+@@ -542,6 +557,11 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
+ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_protocol_error);
+ if ((r = sshpkt_get_u32(ssh, &ninfo)) != 0)
+ return r;
++ if (ninfo >= 1024) {
++ error("SSH2_MSG_EXT_INFO with too many entries, expected "
++ "<=1024, received %u", ninfo);
++ return dispatch_protocol_error(type, seq, ssh);
++ }
+ for (i = 0; i < ninfo; i++) {
+ if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0)
+ return r;
+@@ -657,7 +677,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
+ error_f("no kex");
+ return SSH_ERR_INTERNAL_ERROR;
+ }
+- ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL);
++ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_protocol_error);
+ ptr = sshpkt_ptr(ssh, &dlen);
+ if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0)
+ return r;
+@@ -693,7 +713,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
+ if (!(kex->flags & KEX_INIT_SENT))
+ if ((r = kex_send_kexinit(ssh)) != 0)
+ return r;
+- if ((r = kex_choose_conf(ssh)) != 0)
++ if ((r = kex_choose_conf(ssh, seq)) != 0)
+ return r;
+
+ if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL)
+@@ -960,20 +980,14 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX])
+ return (1);
+ }
+
+-/* returns non-zero if proposal contains any algorithm from algs */
+ static int
+-has_any_alg(const char *proposal, const char *algs)
++kexalgs_contains(char **peer, const char *ext)
+ {
+- char *cp;
+-
+- if ((cp = match_list(proposal, algs, NULL)) == NULL)
+- return 0;
+- free(cp);
+- return 1;
++ return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext);
+ }
+
+ static int
+-kex_choose_conf(struct ssh *ssh)
++kex_choose_conf(struct ssh *ssh, uint32_t seq)
+ {
+ struct kex *kex = ssh->kex;
+ struct newkeys *newkeys;
+@@ -998,13 +1012,23 @@ kex_choose_conf(struct ssh *ssh)
+ sprop=peer;
+ }
+
+- /* Check whether client supports ext_info_c */
+- if (kex->server && (kex->flags & KEX_INITIAL)) {
+- char *ext;
+-
+- ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL);
+- kex->ext_info_c = (ext != NULL);
+- free(ext);
++ /* Check whether peer supports ext_info/kex_strict */
++ if ((kex->flags & KEX_INITIAL) != 0) {
++ if (kex->server) {
++ kex->ext_info_c = kexalgs_contains(peer, "ext-info-c");
++ kex->kex_strict = kexalgs_contains(peer,
++ "kex-strict-c-v00@openssh.com");
++ } else {
++ kex->kex_strict = kexalgs_contains(peer,
++ "kex-strict-s-v00@openssh.com");
++ }
++ if (kex->kex_strict) {
++ debug3_f("will use strict KEX ordering");
++ if (seq != 0)
++ ssh_packet_disconnect(ssh,
++ "strict KEX violation: "
++ "KEXINIT was not the first packet");
++ }
+ }
+
+ /* Check whether client supports rsa-sha2 algorithms */
+diff --git a/kex.h b/kex.h
+index e356f5ed0..99b47435f 100644
+--- a/kex.h
++++ b/kex.h
+@@ -157,6 +157,7 @@ struct kex {
+ u_int kex_type;
+ char *server_sig_algs;
+ int ext_info_c;
++ int kex_strict;
+ struct sshbuf *my;
+ struct sshbuf *peer;
+ struct sshbuf *client_version;
+diff --git a/packet.c b/packet.c
+index 3f64d2d32..25553fbb5 100644
+--- a/packet.c
++++ b/packet.c
+@@ -1205,8 +1205,13 @@ ssh_packet_send2_wrapped(struct ssh *ssh)
+ sshbuf_dump(state->output, stderr);
+ #endif
+ /* increment sequence number for outgoing packets */
+- if (++state->p_send.seqnr == 0)
++ if (++state->p_send.seqnr == 0) {
++ if ((ssh->kex->flags & KEX_INITIAL) != 0) {
++ ssh_packet_disconnect(ssh, "outgoing sequence number "
++ "wrapped during initial key exchange");
++ }
+ logit("outgoing seqnr wraps around");
++ }
+ if (++state->p_send.packets == 0)
+ if (!(ssh->compat & SSH_BUG_NOREKEY))
+ return SSH_ERR_NEED_REKEY;
+@@ -1214,6 +1219,11 @@ ssh_packet_send2_wrapped(struct ssh *ssh)
+ state->p_send.bytes += len;
+ sshbuf_reset(state->outgoing_packet);
+
++ if (type == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) {
++ debug_f("resetting send seqnr %u", state->p_send.seqnr);
++ state->p_send.seqnr = 0;
++ }
++
+ if (type == SSH2_MSG_NEWKEYS)
+ r = ssh_set_newkeys(ssh, MODE_OUT);
+ else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side)
+@@ -1342,8 +1352,7 @@ ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ /* Stay in the loop until we have received a complete packet. */
+ for (;;) {
+ /* Try to read a packet from the buffer. */
+- r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p);
+- if (r != 0)
++ if ((r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p)) != 0)
+ break;
+ /* If we got a packet, return it. */
+ if (*typep != SSH_MSG_NONE)
+@@ -1627,10 +1636,16 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0)
+ goto out;
+ }
++
+ if (seqnr_p != NULL)
+ *seqnr_p = state->p_read.seqnr;
+- if (++state->p_read.seqnr == 0)
++ if (++state->p_read.seqnr == 0) {
++ if ((ssh->kex->flags & KEX_INITIAL) != 0) {
++ ssh_packet_disconnect(ssh, "incoming sequence number "
++ "wrapped during initial key exchange");
++ }
+ logit("incoming seqnr wraps around");
++ }
+ if (++state->p_read.packets == 0)
+ if (!(ssh->compat & SSH_BUG_NOREKEY))
+ return SSH_ERR_NEED_REKEY;
+@@ -1696,6 +1711,10 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ #endif
+ /* reset for next packet */
+ state->packlen = 0;
++ if (*typep == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) {
++ debug_f("resetting read seqnr %u", state->p_read.seqnr);
++ state->p_read.seqnr = 0;
++ }
+
+ if ((r = ssh_packet_check_rekey(ssh)) != 0)
+ return r;
+@@ -1716,10 +1735,39 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ r = ssh_packet_read_poll2(ssh, typep, seqnr_p);
+ if (r != 0)
+ return r;
+- if (*typep) {
+- state->keep_alive_timeouts = 0;
+- DBG(debug("received packet type %d", *typep));
++ if (*typep == 0) {
++ /* no message ready */
++ return 0;
++ }
++ state->keep_alive_timeouts = 0;
++ DBG(debug("received packet type %d", *typep));
++
++ /* Always process disconnect messages */
++ if (*typep == SSH2_MSG_DISCONNECT) {
++ if ((r = sshpkt_get_u32(ssh, &reason)) != 0 ||
++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0)
++ return r;
++ /* Ignore normal client exit notifications */
++ do_log2(ssh->state->server_side &&
++ reason == SSH2_DISCONNECT_BY_APPLICATION ?
++ SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
++ "Received disconnect from %s port %d:"
++ "%u: %.400s", ssh_remote_ipaddr(ssh),
++ ssh_remote_port(ssh), reason, msg);
++ free(msg);
++ return SSH_ERR_DISCONNECTED;
+ }
++
++ /*
++ * Do not implicitly handle any messages here during initial
++ * KEX when in strict mode. They will be need to be allowed
++ * explicitly by the KEX dispatch table or they will generate
++ * protocol errors.
++ */
++ if (ssh->kex != NULL &&
++ (ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict)
++ return 0;
++ /* Implicitly handle transport-level messages */
+ switch (*typep) {
+ case SSH2_MSG_IGNORE:
+ debug3("Received SSH2_MSG_IGNORE");
+@@ -1734,19 +1782,6 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ debug("Remote: %.900s", msg);
+ free(msg);
+ break;
+- case SSH2_MSG_DISCONNECT:
+- if ((r = sshpkt_get_u32(ssh, &reason)) != 0 ||
+- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0)
+- return r;
+- /* Ignore normal client exit notifications */
+- do_log2(ssh->state->server_side &&
+- reason == SSH2_DISCONNECT_BY_APPLICATION ?
+- SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
+- "Received disconnect from %s port %d:"
+- "%u: %.400s", ssh_remote_ipaddr(ssh),
+- ssh_remote_port(ssh), reason, msg);
+- free(msg);
+- return SSH_ERR_DISCONNECTED;
+ case SSH2_MSG_UNIMPLEMENTED:
+ if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0)
+ return r;
+@@ -2211,6 +2246,7 @@ kex_to_blob(struct sshbuf *m, struct kex *kex)
+ (r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 ||
+ (r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 ||
+ (r = sshbuf_put_u32(m, kex->kex_type)) != 0 ||
++ (r = sshbuf_put_u32(m, kex->kex_strict)) != 0 ||
+ (r = sshbuf_put_stringb(m, kex->my)) != 0 ||
+ (r = sshbuf_put_stringb(m, kex->peer)) != 0 ||
+ (r = sshbuf_put_stringb(m, kex->client_version)) != 0 ||
+@@ -2373,6 +2409,7 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp)
+ (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 ||
+ (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 ||
+ (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 ||
++ (r = sshbuf_get_u32(m, &kex->kex_strict)) != 0 ||
+ (r = sshbuf_get_stringb(m, kex->my)) != 0 ||
+ (r = sshbuf_get_stringb(m, kex->peer)) != 0 ||
+ (r = sshbuf_get_stringb(m, kex->client_version)) != 0 ||
+@@ -2701,6 +2738,7 @@ sshpkt_disconnect(struct ssh *ssh, const char *fmt,...)
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
++ debug2_f("sending SSH2_MSG_DISCONNECT: %s", buf);
+ if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 ||
+ (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 ||
+ (r = sshpkt_put_cstring(ssh, buf)) != 0 ||
+diff --git a/sshconnect2.c b/sshconnect2.c
+index cb6a94e76..3e5f69470 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -250,7 +250,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ fatal_fr(r, "kex_assemble_namelist");
+ free(all_key);
+
+- if ((s = kex_names_cat(options.kex_algorithms, "ext-info-c")) == NULL)
++ if ((s = kex_names_cat(options.kex_algorithms,
++ "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL)
+ fatal_f("kex_names_cat");
+ myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, s);
+ myproposal[PROPOSAL_ENC_ALGS_CTOS] =
+@@ -441,7 +442,6 @@ struct cauthmethod {
+ };
+
+ static int input_userauth_service_accept(int, u_int32_t, struct ssh *);
+-static int input_userauth_ext_info(int, u_int32_t, struct ssh *);
+ static int input_userauth_success(int, u_int32_t, struct ssh *);
+ static int input_userauth_failure(int, u_int32_t, struct ssh *);
+ static int input_userauth_banner(int, u_int32_t, struct ssh *);
+@@ -561,7 +561,7 @@ ssh_userauth2(struct ssh *ssh, const char *local_user,
+
+ ssh->authctxt = &authctxt;
+ ssh_dispatch_init(ssh, &input_userauth_error);
+- ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &input_userauth_ext_info);
++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, kex_input_ext_info);
+ ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_ACCEPT, &input_userauth_service_accept);
+ ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt.success); /* loop until success */
+ pubkey_cleanup(ssh);
+@@ -613,13 +613,6 @@ input_userauth_service_accept(int type, u_int32_t seq, struct ssh *ssh)
+ return r;
+ }
+
+-/* ARGSUSED */
+-static int
+-input_userauth_ext_info(int type, u_int32_t seqnr, struct ssh *ssh)
+-{
+- return kex_input_ext_info(type, seqnr, ssh);
+-}
+-
+ void
+ userauth(struct ssh *ssh, char *authlist)
+ {
+@@ -701,6 +694,7 @@ input_userauth_success(int type, u_int32_t seq, struct ssh *ssh)
+ free(authctxt->methoddata);
+ authctxt->methoddata = NULL;
+ authctxt->success = 1; /* break out */
++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, dispatch_protocol_error);
+ return 0;
+ }
+
+diff --git a/sshd.c b/sshd.c
+index fc22fcb62..dd8fd94a3 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2471,11 +2471,13 @@ do_ssh2_kex(struct ssh *ssh)
+ {
+ char *myproposal[PROPOSAL_MAX] = { KEX_SERVER };
+ struct kex *kex;
+- char *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL;
++ char *s, *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL;
+ int r;
+
+- myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh,
+- options.kex_algorithms);
++ if ((s = kex_names_cat(options.kex_algorithms,
++ "kex-strict-s-v00@openssh.com")) == NULL)
++ fatal_f("kex_names_cat");
++ myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, s);
+ myproposal[PROPOSAL_ENC_ALGS_CTOS] =
+ myproposal[PROPOSAL_ENC_ALGS_STOC] = prop_enc =
+ compat_cipher_proposal(ssh, options.ciphers);
+@@ -2583,6 +2585,7 @@ do_ssh2_kex(struct ssh *ssh)
+ free(prop_kex);
+ free(prop_enc);
+ free(prop_hostkey);
++ free(s);
+ debug("KEX done");
+ }
+
diff --git a/debian/patches/CVE-2023-51384.patch b/debian/patches/CVE-2023-51384.patch
new file mode 100644
index 0000000..1953197
--- /dev/null
+++ b/debian/patches/CVE-2023-51384.patch
@@ -0,0 +1,168 @@
+From d5be669c872a313a71d60babee64f3a80340dc51 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Mon, 18 Dec 2023 14:46:12 +0000
+Subject: upstream: apply destination constraints to all p11 keys
+
+Previously applied only to the first key returned from each token.
+
+ok markus@
+
+OpenBSD-Commit-ID: 36df3afb8eb94eec6b2541f063d0d164ef8b488d
+
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=881d9c6af9da4257c69c327c4e2f1508b2fa754b
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2023-51384.patch
+---
+ ssh-agent.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 99 insertions(+), 4 deletions(-)
+
+diff --git a/ssh-agent.c b/ssh-agent.c
+index dce2849d8..27bf9b5ad 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -248,6 +248,91 @@ free_dest_constraints(struct dest_constraint *dcs, size_t ndcs)
+ free(dcs);
+ }
+
++static void
++dup_dest_constraint_hop(const struct dest_constraint_hop *dch,
++ struct dest_constraint_hop *out)
++{
++ u_int i;
++ int r;
++
++ out->user = dch->user == NULL ? NULL : xstrdup(dch->user);
++ out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname);
++ out->is_ca = dch->is_ca;
++ out->nkeys = dch->nkeys;
++ out->keys = out->nkeys == 0 ? NULL :
++ xcalloc(out->nkeys, sizeof(*out->keys));
++ out->key_is_ca = out->nkeys == 0 ? NULL :
++ xcalloc(out->nkeys, sizeof(*out->key_is_ca));
++ for (i = 0; i < dch->nkeys; i++) {
++ if (dch->keys[i] != NULL &&
++ (r = sshkey_from_private(dch->keys[i],
++ &(out->keys[i]))) != 0)
++ fatal_fr(r, "copy key");
++ out->key_is_ca[i] = dch->key_is_ca[i];
++ }
++}
++
++static struct dest_constraint *
++dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs)
++{
++ size_t i;
++ struct dest_constraint *ret;
++
++ if (ndcs == 0)
++ return NULL;
++ ret = xcalloc(ndcs, sizeof(*ret));
++ for (i = 0; i < ndcs; i++) {
++ dup_dest_constraint_hop(&dcs[i].from, &ret[i].from);
++ dup_dest_constraint_hop(&dcs[i].to, &ret[i].to);
++ }
++ return ret;
++}
++
++#ifdef DEBUG_CONSTRAINTS
++static void
++dump_dest_constraint_hop(const struct dest_constraint_hop *dch)
++{
++ u_int i;
++ char *fp;
++
++ debug_f("user %s hostname %s is_ca %d nkeys %u",
++ dch->user == NULL ? "(null)" : dch->user,
++ dch->hostname == NULL ? "(null)" : dch->hostname,
++ dch->is_ca, dch->nkeys);
++ for (i = 0; i < dch->nkeys; i++) {
++ fp = NULL;
++ if (dch->keys[i] != NULL &&
++ (fp = sshkey_fingerprint(dch->keys[i],
++ SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
++ fatal_f("fingerprint failed");
++ debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys,
++ dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]),
++ dch->keys[i] == NULL ? "" : " ",
++ dch->keys[i] == NULL ? "none" : fp,
++ dch->key_is_ca[i]);
++ free(fp);
++ }
++}
++#endif /* DEBUG_CONSTRAINTS */
++
++static void
++dump_dest_constraints(const char *context,
++ const struct dest_constraint *dcs, size_t ndcs)
++{
++#ifdef DEBUG_CONSTRAINTS
++ size_t i;
++
++ debug_f("%s: %zu constraints", context, ndcs);
++ for (i = 0; i < ndcs; i++) {
++ debug_f("constraint %zu / %zu: from: ", i, ndcs);
++ dump_dest_constraint_hop(&dcs[i].from);
++ debug_f("constraint %zu / %zu: to: ", i, ndcs);
++ dump_dest_constraint_hop(&dcs[i].to);
++ }
++ debug_f("done for %s", context);
++#endif /* DEBUG_CONSTRAINTS */
++}
++
+ static void
+ free_identity(Identity *id)
+ {
+@@ -519,13 +604,22 @@ process_request_identities(SocketEntry *e)
+ Identity *id;
+ struct sshbuf *msg, *keys;
+ int r;
+- u_int nentries = 0;
++ u_int i = 0, nentries = 0;
++ char *fp;
+
+ debug2_f("entering");
+
+ if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
+ TAILQ_FOREACH(id, &idtab->idlist, next) {
++ if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT,
++ SSH_FP_DEFAULT)) == NULL)
++ fatal_f("fingerprint failed");
++ debug_f("key %u / %u: %s %s", i++, idtab->nentries,
++ sshkey_ssh_name(id->key), fp);
++ dump_dest_constraints(__func__,
++ id->dest_constraints, id->ndest_constraints);
++ free(fp);
+ /* identity not visible, don't include in response */
+ if (identity_permitted(id, e, NULL, NULL, NULL) != 0)
+ continue;
+@@ -1225,6 +1319,7 @@ process_add_identity(SocketEntry *e)
+ sshbuf_reset(e->request);
+ goto out;
+ }
++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
+
+ if (sk_provider != NULL) {
+ if (!sshkey_is_sk(k)) {
+@@ -1404,6 +1499,7 @@ process_add_smartcard_key(SocketEntry *e)
+ error_f("failed to parse constraints");
+ goto send;
+ }
++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
+ if (e->nsession_ids != 0 && !remote_add_provider) {
+ verbose("failed PKCS#11 add of \"%.100s\": remote addition of "
+ "providers is disabled", provider);
+@@ -1439,10 +1535,9 @@ process_add_smartcard_key(SocketEntry *e)
+ }
+ id->death = death;
+ id->confirm = confirm;
+- id->dest_constraints = dest_constraints;
++ id->dest_constraints = dup_dest_constraints(
++ dest_constraints, ndest_constraints);
+ id->ndest_constraints = ndest_constraints;
+- dest_constraints = NULL; /* transferred */
+- ndest_constraints = 0;
+ TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
+ idtab->nentries++;
+ success = 1;
diff --git a/debian/patches/CVE-2023-51385.patch b/debian/patches/CVE-2023-51385.patch
new file mode 100644
index 0000000..714c7a0
--- /dev/null
+++ b/debian/patches/CVE-2023-51385.patch
@@ -0,0 +1,94 @@
+From 14c4d6f0fa446414d1c38ad083107576d0ae3032 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Mon, 18 Dec 2023 14:47:44 +0000
+Subject: upstream: ban user/hostnames with most shell metacharacters
+
+This makes ssh(1) refuse user or host names provided on the
+commandline that contain most shell metacharacters.
+
+Some programs that invoke ssh(1) using untrusted data do not filter
+metacharacters in arguments they supply. This could create
+interactions with user-specified ProxyCommand and other directives
+that allow shell injection attacks to occur.
+
+It's a mistake to invoke ssh(1) with arbitrary untrusted arguments,
+but getting this stuff right can be tricky, so this should prevent
+most obvious ways of creating risky situations. It however is not
+and cannot be perfect: ssh(1) has no practical way of interpreting
+what shell quoting rules are in use and how they interact with the
+user's specified ProxyCommand.
+
+To allow configurations that use strange user or hostnames to
+continue to work, this strictness is applied only to names coming
+from the commandline. Names specified using User or Hostname
+directives in ssh_config(5) are not affected.
+
+feedback/ok millert@ markus@ dtucker@ deraadt@
+
+OpenBSD-Commit-ID: 3b487348b5964f3e77b6b4d3da4c3b439e94b2d9
+
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=7ef3787c84b6b524501211b11a26c742f829af1a
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2023-51385.patch
+---
+ ssh.c | 39 +++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+diff --git a/ssh.c b/ssh.c
+index 8b6b54558..422405035 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -623,6 +623,41 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo)
+ free(cinfo);
+ }
+
++static int
++valid_hostname(const char *s)
++{
++ size_t i;
++
++ if (*s == '-')
++ return 0;
++ for (i = 0; s[i] != 0; i++) {
++ if (strchr("'`\"$\\;&<>|(){}", s[i]) != NULL ||
++ isspace((u_char)s[i]) || iscntrl((u_char)s[i]))
++ return 0;
++ }
++ return 1;
++}
++
++static int
++valid_ruser(const char *s)
++{
++ size_t i;
++
++ if (*s == '-')
++ return 0;
++ for (i = 0; s[i] != 0; i++) {
++ if (strchr("'`\";&<>|(){}", s[i]) != NULL)
++ return 0;
++ /* Disallow '-' after whitespace */
++ if (isspace((u_char)s[i]) && s[i + 1] == '-')
++ return 0;
++ /* Disallow \ in last position */
++ if (s[i] == '\\' && s[i + 1] == '\0')
++ return 0;
++ }
++ return 1;
++}
++
+ /*
+ * Main program for the ssh client.
+ */
+@@ -1110,6 +1145,10 @@ main(int ac, char **av)
+ if (!host)
+ usage();
+
++ if (!valid_hostname(host))
++ fatal("hostname contains invalid characters");
++ if (options.user != NULL && !valid_ruser(options.user))
++ fatal("remote username contains invalid characters");
+ options.host_arg = xstrdup(host);
+
+ /* Initialize the command to execute on remote host. */
diff --git a/debian/patches/authorized-keys-man-symlink.patch b/debian/patches/authorized-keys-man-symlink.patch
new file mode 100644
index 0000000..46657c9
--- /dev/null
+++ b/debian/patches/authorized-keys-man-symlink.patch
@@ -0,0 +1,26 @@
+From 374a21e4acc5b06719640c0d6b82afdf4182b900 Mon Sep 17 00:00:00 2001
+From: Tomas Pospisek <tpo_deb@sourcepole.ch>
+Date: Sun, 9 Feb 2014 16:10:07 +0000
+Subject: Install authorized_keys(5) as a symlink to sshd(8)
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1720
+Bug-Debian: http://bugs.debian.org/441817
+Last-Update: 2013-09-14
+
+Patch-Name: authorized-keys-man-symlink.patch
+---
+ Makefile.in | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/Makefile.in b/Makefile.in
+index 32b607719..85c6ebeb5 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -415,6 +415,7 @@ install-files:
+ $(INSTALL) -m 644 sshd_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/sshd_config.5
+ $(INSTALL) -m 644 ssh_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/ssh_config.5
+ $(INSTALL) -m 644 sshd.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
++ ln -s ../$(mansubdir)8/sshd.8 $(DESTDIR)$(mandir)/$(mansubdir)5/authorized_keys.5
+ $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
+ $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
+ $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
diff --git a/debian/patches/conch-ssh-rsa.patch b/debian/patches/conch-ssh-rsa.patch
new file mode 100644
index 0000000..09981a5
--- /dev/null
+++ b/debian/patches/conch-ssh-rsa.patch
@@ -0,0 +1,42 @@
+From 2df31e50f4cd159978c99055ed2d54b98a5ec7e4 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Tue, 15 Feb 2022 18:25:35 +0000
+Subject: Work around RSA SHA-2 signature issues in conch
+
+This was supposed to be fixed in Twisted upstream
+(https://twistedmatrix.com/trac/ticket/9765), and that fix is in Debian
+now. However, regression tests still seem to fail in GitLab CI but not
+locally (see e.g.
+https://salsa.debian.org/ssh-team/openssh/-/jobs/3513178). Leave this
+in place for now until we figure out what's wrong.
+
+Forwarded: not-needed
+Last-Update: 2022-11-14
+
+Patch-Name: conch-ssh-rsa.patch
+---
+ regress/test-exec.sh | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/regress/test-exec.sh b/regress/test-exec.sh
+index a56108179..45ef82314 100644
+--- a/regress/test-exec.sh
++++ b/regress/test-exec.sh
+@@ -682,6 +682,17 @@ REGRESS_INTEROP_CONCH=no
+ if test -x "$CONCH" ; then
+ REGRESS_INTEROP_CONCH=yes
+ fi
++case "$SCRIPT" in
++*conch*) ;;
++*) REGRESS_INTEROP_CONCH=no
++esac
++
++if test "$REGRESS_INTEROP_CONCH" = "yes" ; then
++ # Work around missing support for RSA SHA-2 signatures:
++ # https://twistedmatrix.com/trac/ticket/9765
++ echo HostKeyAlgorithms +ssh-rsa >> $OBJ/sshd_config
++ echo PubkeyAcceptedAlgorithms +ssh-rsa >> $OBJ/sshd_config
++fi
+
+ # If PuTTY is present, new enough and we are running a PuTTY test, prepare
+ # keys and configuration.
diff --git a/debian/patches/debian-banner.patch b/debian/patches/debian-banner.patch
new file mode 100644
index 0000000..2c32879
--- /dev/null
+++ b/debian/patches/debian-banner.patch
@@ -0,0 +1,162 @@
+From 2d3ac49df11f0aed81f35ce9588eb2c578ec98f2 Mon Sep 17 00:00:00 2001
+From: Kees Cook <kees@debian.org>
+Date: Sun, 9 Feb 2014 16:10:06 +0000
+Subject: Add DebianBanner server configuration option
+
+Setting this to "no" causes sshd to omit the Debian revision from its
+initial protocol handshake, for those scared by package-versioning.patch.
+
+Bug-Debian: http://bugs.debian.org/562048
+Forwarded: not-needed
+Last-Update: 2023-01-02
+
+Patch-Name: debian-banner.patch
+---
+ kex.c | 5 +++--
+ kex.h | 2 +-
+ servconf.c | 9 +++++++++
+ servconf.h | 2 ++
+ sshconnect.c | 2 +-
+ sshd.c | 2 +-
+ sshd_config.5 | 5 +++++
+ 7 files changed, 22 insertions(+), 5 deletions(-)
+
+diff --git a/kex.c b/kex.c
+index 32858e65c..e7b7316c7 100644
+--- a/kex.c
++++ b/kex.c
+@@ -1272,7 +1272,7 @@ send_error(struct ssh *ssh, char *msg)
+ */
+ int
+ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
+- const char *version_addendum)
++ int debian_banner, const char *version_addendum)
+ {
+ int remote_major, remote_minor, mismatch, oerrno = 0;
+ size_t len, n;
+@@ -1290,7 +1290,8 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
+ if (version_addendum != NULL && *version_addendum == '\0')
+ version_addendum = NULL;
+ if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n",
+- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE,
++ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2,
++ debian_banner ? SSH_RELEASE : SSH_RELEASE_MINIMUM,
+ version_addendum == NULL ? "" : " ",
+ version_addendum == NULL ? "" : version_addendum)) != 0) {
+ oerrno = errno;
+diff --git a/kex.h b/kex.h
+index 240dca89d..e356f5ed0 100644
+--- a/kex.h
++++ b/kex.h
+@@ -200,7 +200,7 @@ char *kex_names_cat(const char *, const char *);
+ int kex_assemble_names(char **, const char *, const char *);
+ int kex_gss_names_valid(const char *);
+
+-int kex_exchange_identification(struct ssh *, int, const char *);
++int kex_exchange_identification(struct ssh *, int, int, const char *);
+
+ struct kex *kex_new(void);
+ int kex_ready(struct ssh *, char *[PROPOSAL_MAX]);
+diff --git a/servconf.c b/servconf.c
+index 6529f2736..4ecbca8ed 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -203,6 +203,7 @@ initialize_server_options(ServerOptions *options)
+ options->channel_timeouts = NULL;
+ options->num_channel_timeouts = 0;
+ options->unused_connection_timeout = -1;
++ options->debian_banner = -1;
+ }
+
+ /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
+@@ -461,6 +462,8 @@ fill_default_server_options(ServerOptions *options)
+ options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
+ if (options->unused_connection_timeout == -1)
+ options->unused_connection_timeout = 0;
++ if (options->debian_banner == -1)
++ options->debian_banner = 1;
+
+ assemble_algorithms(options);
+
+@@ -546,6 +549,7 @@ typedef enum {
+ sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
+ sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
+ sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout,
++ sDebianBanner,
+ sDeprecated, sIgnore, sUnsupported
+ } ServerOpCodes;
+
+@@ -719,6 +723,7 @@ static struct {
+ { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL },
+ { "channeltimeout", sChannelTimeout, SSHCFG_ALL },
+ { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL },
++ { "debianbanner", sDebianBanner, SSHCFG_GLOBAL },
+ { NULL, sBadOption, 0 }
+ };
+
+@@ -2596,6 +2601,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ }
+ goto parse_time;
+
++ case sDebianBanner:
++ intptr = &options->debian_banner;
++ goto parse_flag;
++
+ case sDeprecated:
+ case sIgnore:
+ case sUnsupported:
+diff --git a/servconf.h b/servconf.h
+index 38a68d576..e142cf77a 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -238,6 +238,8 @@ typedef struct {
+ u_int num_channel_timeouts;
+
+ int unused_connection_timeout;
++
++ int debian_banner;
+ } ServerOptions;
+
+ /* Information about the incoming connection as used by Match */
+diff --git a/sshconnect.c b/sshconnect.c
+index 77a3080ba..dbb3c98a7 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -1574,7 +1574,7 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost,
+ lowercase(host);
+
+ /* Exchange protocol version identification strings with the server. */
+- if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0)
++ if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0)
+ sshpkt_fatal(ssh, r, "banner exchange");
+
+ /* Put the connection into non-blocking mode. */
+diff --git a/sshd.c b/sshd.c
+index dd4a19131..b4ee435e5 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2238,7 +2238,7 @@ main(int ac, char **av)
+ if (!debug_flag)
+ alarm(options.login_grace_time);
+
+- if ((r = kex_exchange_identification(ssh, -1,
++ if ((r = kex_exchange_identification(ssh, -1, options.debian_banner,
+ options.version_addendum)) != 0)
+ sshpkt_fatal(ssh, r, "banner exchange");
+
+diff --git a/sshd_config.5 b/sshd_config.5
+index eb58750a5..2c6ec76cb 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -615,6 +615,11 @@ or
+ .Cm no .
+ The default is
+ .Cm yes .
++.It Cm DebianBanner
++Specifies whether the distribution-specified extra version suffix is
++included during initial protocol handshake.
++The default is
++.Cm yes .
+ .It Cm DenyGroups
+ This keyword can be followed by a list of group name patterns, separated
+ by spaces.
diff --git a/debian/patches/debian-config.patch b/debian/patches/debian-config.patch
new file mode 100644
index 0000000..f32c3fd
--- /dev/null
+++ b/debian/patches/debian-config.patch
@@ -0,0 +1,285 @@
+From aedb5d2ee2799e3a95b6913721533d2c42c496b3 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:18 +0000
+Subject: Various Debian-specific configuration changes
+
+ssh: Enable ForwardX11Trusted, returning to earlier semantics which cause
+fewer problems with existing setups (http://bugs.debian.org/237021).
+
+ssh: Set 'SendEnv LANG LC_*' by default (http://bugs.debian.org/264024).
+
+ssh: Enable HashKnownHosts by default to try to limit the spread of ssh
+worms.
+
+ssh: Enable GSSAPIAuthentication by default.
+
+ssh: Include /etc/ssh/ssh_config.d/*.conf.
+
+sshd: Enable PAM, disable KbdInteractiveAuthentication, and disable
+PrintMotd.
+
+sshd: Enable X11Forwarding.
+
+sshd: Set 'AcceptEnv LANG LC_*' by default.
+
+sshd: Change sftp subsystem path to /usr/lib/openssh/sftp-server.
+
+sshd: Include /etc/ssh/sshd_config.d/*.conf.
+
+regress: Run tests with 'UsePAM yes', to match sshd_config.
+
+Document all of this.
+
+Author: Russ Allbery <rra@debian.org>
+Forwarded: not-needed
+Last-Update: 2023-01-03
+
+Patch-Name: debian-config.patch
+---
+ readconf.c | 2 +-
+ regress/test-exec.sh | 1 +
+ ssh.1 | 24 ++++++++++++++++++++++++
+ ssh_config | 8 +++++++-
+ ssh_config.5 | 26 +++++++++++++++++++++++++-
+ sshd_config | 18 ++++++++++++------
+ sshd_config.5 | 29 +++++++++++++++++++++++++++++
+ 7 files changed, 99 insertions(+), 9 deletions(-)
+
+diff --git a/readconf.c b/readconf.c
+index 18e814039..6c8ad919f 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2536,7 +2536,7 @@ fill_default_options(Options * options)
+ if (options->forward_x11 == -1)
+ options->forward_x11 = 0;
+ if (options->forward_x11_trusted == -1)
+- options->forward_x11_trusted = 0;
++ options->forward_x11_trusted = 1;
+ if (options->forward_x11_timeout == -1)
+ options->forward_x11_timeout = 1200;
+ /*
+diff --git a/regress/test-exec.sh b/regress/test-exec.sh
+index df43f0214..a56108179 100644
+--- a/regress/test-exec.sh
++++ b/regress/test-exec.sh
+@@ -539,6 +539,7 @@ cat << EOF > $OBJ/sshd_config
+ AcceptEnv _XXX_TEST_*
+ AcceptEnv _XXX_TEST
+ Subsystem sftp $SFTPSERVER
++ UsePAM yes
+ EOF
+
+ # This may be necessary if /usr/src and/or /usr/obj are group-writable,
+diff --git a/ssh.1 b/ssh.1
+index d07cd7840..395abd40b 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -847,6 +847,16 @@ directive in
+ .Xr ssh_config 5
+ for more information.
+ .Pp
++(Debian-specific: X11 forwarding is not subjected to X11 SECURITY extension
++restrictions by default, because too many programs currently crash in this
++mode.
++Set the
++.Cm ForwardX11Trusted
++option to
++.Dq no
++to restore the upstream behaviour.
++This may change in future depending on client-side improvements.)
++.Pp
+ .It Fl x
+ Disables X11 forwarding.
+ .Pp
+@@ -855,6 +865,20 @@ Enables trusted X11 forwarding.
+ Trusted X11 forwardings are not subjected to the X11 SECURITY extension
+ controls.
+ .Pp
++(Debian-specific: In the default configuration, this option is equivalent to
++.Fl X ,
++since
++.Cm ForwardX11Trusted
++defaults to
++.Dq yes
++as described above.
++Set the
++.Cm ForwardX11Trusted
++option to
++.Dq no
++to restore the upstream behaviour.
++This may change in future depending on client-side improvements.)
++.Pp
+ .It Fl y
+ Send log information using the
+ .Xr syslog 3
+diff --git a/ssh_config b/ssh_config
+index 52aae8692..09a17cf18 100644
+--- a/ssh_config
++++ b/ssh_config
+@@ -17,9 +17,12 @@
+ # list of available options, their meanings and defaults, please see the
+ # ssh_config(5) man page.
+
+-# Host *
++Include /etc/ssh/ssh_config.d/*.conf
++
++Host *
+ # ForwardAgent no
+ # ForwardX11 no
++# ForwardX11Trusted yes
+ # PasswordAuthentication yes
+ # HostbasedAuthentication no
+ # GSSAPIAuthentication no
+@@ -46,3 +49,6 @@
+ # ProxyCommand ssh -q -W %h:%p gateway.example.com
+ # RekeyLimit 1G 1h
+ # UserKnownHostsFile ~/.ssh/known_hosts.d/%k
++ SendEnv LANG LC_*
++ HashKnownHosts yes
++ GSSAPIAuthentication yes
+diff --git a/ssh_config.5 b/ssh_config.5
+index 82d68650e..32851c984 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -71,6 +71,29 @@ Since the first obtained value for each parameter is used, more
+ host-specific declarations should be given near the beginning of the
+ file, and general defaults at the end.
+ .Pp
++Note that the Debian
++.Ic openssh-client
++package sets several options as standard in
++.Pa /etc/ssh/ssh_config
++which are not the default in
++.Xr ssh 1 :
++.Pp
++.Bl -bullet -offset indent -compact
++.It
++.Cm Include /etc/ssh/ssh_config.d/*.conf
++.It
++.Cm SendEnv No LANG LC_*
++.It
++.Cm HashKnownHosts No yes
++.It
++.Cm GSSAPIAuthentication No yes
++.El
++.Pp
++.Pa /etc/ssh/ssh_config.d/*.conf
++files are included at the start of the system-wide configuration file, so
++options set there will override those in
++.Pa /etc/ssh/ssh_config.
++.Pp
+ The file contains keyword-argument pairs, one per line.
+ Lines starting with
+ .Ql #
+@@ -802,11 +825,12 @@ elapsed.
+ .It Cm ForwardX11Trusted
+ If this option is set to
+ .Cm yes ,
++(the Debian-specific default),
+ remote X11 clients will have full access to the original X11 display.
+ .Pp
+ If this option is set to
+ .Cm no
+-(the default),
++(the upstream default),
+ remote X11 clients will be considered untrusted and prevented
+ from stealing or tampering with data belonging to trusted X11
+ clients.
+diff --git a/sshd_config b/sshd_config
+index ecfe8d026..677f97d5d 100644
+--- a/sshd_config
++++ b/sshd_config
+@@ -10,6 +10,8 @@
+ # possible, but leave them commented. Uncommented options override the
+ # default value.
+
++Include /etc/ssh/sshd_config.d/*.conf
++
+ #Port 22
+ #AddressFamily any
+ #ListenAddress 0.0.0.0
+@@ -57,8 +59,9 @@ AuthorizedKeysFile .ssh/authorized_keys
+ #PasswordAuthentication yes
+ #PermitEmptyPasswords no
+
+-# Change to no to disable s/key passwords
+-#KbdInteractiveAuthentication yes
++# Change to yes to enable challenge-response passwords (beware issues with
++# some PAM modules and threads)
++KbdInteractiveAuthentication no
+
+ # Kerberos options
+ #KerberosAuthentication no
+@@ -81,16 +84,16 @@ AuthorizedKeysFile .ssh/authorized_keys
+ # If you just want the PAM account and session checks to run without
+ # PAM authentication, then enable this but set PasswordAuthentication
+ # and KbdInteractiveAuthentication to 'no'.
+-#UsePAM no
++UsePAM yes
+
+ #AllowAgentForwarding yes
+ #AllowTcpForwarding yes
+ #GatewayPorts no
+-#X11Forwarding no
++X11Forwarding yes
+ #X11DisplayOffset 10
+ #X11UseLocalhost yes
+ #PermitTTY yes
+-#PrintMotd yes
++PrintMotd no
+ #PrintLastLog yes
+ #TCPKeepAlive yes
+ #PermitUserEnvironment no
+@@ -107,8 +110,11 @@ AuthorizedKeysFile .ssh/authorized_keys
+ # no default banner path
+ #Banner none
+
++# Allow client to pass locale environment variables
++AcceptEnv LANG LC_*
++
+ # override default of no subsystems
+-Subsystem sftp /usr/libexec/sftp-server
++Subsystem sftp /usr/lib/openssh/sftp-server
+
+ # Example of overriding settings on a per-user basis
+ #Match User anoncvs
+diff --git a/sshd_config.5 b/sshd_config.5
+index b9a19c7bd..b13f7665b 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -56,6 +56,35 @@ Arguments may optionally be enclosed in double quotes
+ .Pq \&"
+ in order to represent arguments containing spaces.
+ .Pp
++Note that the Debian
++.Ic openssh-server
++package sets several options as standard in
++.Pa /etc/ssh/sshd_config
++which are not the default in
++.Xr sshd 8 :
++.Pp
++.Bl -bullet -offset indent -compact
++.It
++.Cm Include /etc/ssh/sshd_config.d/*.conf
++.It
++.Cm KbdInteractiveAuthentication No no
++.It
++.Cm X11Forwarding No yes
++.It
++.Cm PrintMotd No no
++.It
++.Cm AcceptEnv No LANG LC_*
++.It
++.Cm Subsystem No sftp /usr/lib/openssh/sftp-server
++.It
++.Cm UsePAM No yes
++.El
++.Pp
++.Pa /etc/ssh/sshd_config.d/*.conf
++files are included at the start of the configuration file, so options set
++there will override those in
++.Pa /etc/ssh/sshd_config.
++.Pp
+ The possible
+ keywords and their meanings are as follows (note that
+ keywords are case-insensitive and arguments are case-sensitive):
diff --git a/debian/patches/dnssec-sshfp.patch b/debian/patches/dnssec-sshfp.patch
new file mode 100644
index 0000000..ec30655
--- /dev/null
+++ b/debian/patches/dnssec-sshfp.patch
@@ -0,0 +1,94 @@
+From 25f54fd79c7dc62d5ffaa7ebdc2e3de86a031084 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:01 +0000
+Subject: Force use of DNSSEC even if "options edns0" isn't in resolv.conf
+
+This allows SSHFP DNS records to be verified if glibc 2.11 is installed.
+
+Origin: vendor, https://cvs.fedoraproject.org/viewvc/F-12/openssh/openssh-5.2p1-edns.patch?revision=1.1&view=markup
+Bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=572049
+Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=572049
+Last-Update: 2010-04-06
+
+Patch-Name: dnssec-sshfp.patch
+---
+ dns.c | 14 +++++++++++++-
+ openbsd-compat/getrrsetbyname.c | 10 +++++-----
+ openbsd-compat/getrrsetbyname.h | 3 +++
+ 3 files changed, 21 insertions(+), 6 deletions(-)
+
+diff --git a/dns.c b/dns.c
+index f2310bec2..9021d1785 100644
+--- a/dns.c
++++ b/dns.c
+@@ -198,6 +198,7 @@ verify_host_key_dns(const char *hostname, struct sockaddr *address,
+ {
+ u_int counter;
+ int result;
++ unsigned int rrset_flags = 0;
+ struct rrsetinfo *fingerprints = NULL;
+
+ u_int8_t hostkey_algorithm;
+@@ -220,8 +221,19 @@ verify_host_key_dns(const char *hostname, struct sockaddr *address,
+ return -1;
+ }
+
++ /*
++ * Original getrrsetbyname function, found on OpenBSD for example,
++ * doesn't accept any flag and prerequisite for obtaining AD bit in
++ * DNS response is set by "options edns0" in resolv.conf.
++ *
++ * Our version is more clever and use RRSET_FORCE_EDNS0 flag.
++ */
++#ifndef HAVE_GETRRSETBYNAME
++ rrset_flags |= RRSET_FORCE_EDNS0;
++#endif
+ result = getrrsetbyname(hostname, DNS_RDATACLASS_IN,
+- DNS_RDATATYPE_SSHFP, 0, &fingerprints);
++ DNS_RDATATYPE_SSHFP, rrset_flags, &fingerprints);
++
+ if (result) {
+ verbose("DNS lookup error: %s", dns_result_totext(result));
+ return -1;
+diff --git a/openbsd-compat/getrrsetbyname.c b/openbsd-compat/getrrsetbyname.c
+index 73de5e948..4dfa351c6 100644
+--- a/openbsd-compat/getrrsetbyname.c
++++ b/openbsd-compat/getrrsetbyname.c
+@@ -214,8 +214,8 @@ getrrsetbyname(const char *hostname, unsigned int rdclass,
+ goto fail;
+ }
+
+- /* don't allow flags yet, unimplemented */
+- if (flags) {
++ /* Allow RRSET_FORCE_EDNS0 flag only. */
++ if ((flags & !RRSET_FORCE_EDNS0) != 0) {
+ result = ERRSET_INVAL;
+ goto fail;
+ }
+@@ -231,9 +231,9 @@ getrrsetbyname(const char *hostname, unsigned int rdclass,
+ #endif /* DEBUG */
+
+ #ifdef RES_USE_DNSSEC
+- /* turn on DNSSEC if EDNS0 is configured */
+- if (_resp->options & RES_USE_EDNS0)
+- _resp->options |= RES_USE_DNSSEC;
++ /* turn on DNSSEC if required */
++ if (flags & RRSET_FORCE_EDNS0)
++ _resp->options |= (RES_USE_EDNS0|RES_USE_DNSSEC);
+ #endif /* RES_USE_DNSEC */
+
+ /* make query */
+diff --git a/openbsd-compat/getrrsetbyname.h b/openbsd-compat/getrrsetbyname.h
+index 1283f5506..dbbc85a2a 100644
+--- a/openbsd-compat/getrrsetbyname.h
++++ b/openbsd-compat/getrrsetbyname.h
+@@ -72,6 +72,9 @@
+ #ifndef RRSET_VALIDATED
+ # define RRSET_VALIDATED 1
+ #endif
++#ifndef RRSET_FORCE_EDNS0
++# define RRSET_FORCE_EDNS0 0x0001
++#endif
+
+ /*
+ * Return codes for getrrsetbyname()
diff --git a/debian/patches/doc-hash-tab-completion.patch b/debian/patches/doc-hash-tab-completion.patch
new file mode 100644
index 0000000..48d44e5
--- /dev/null
+++ b/debian/patches/doc-hash-tab-completion.patch
@@ -0,0 +1,28 @@
+From 4202164dacce1c368f7e6e5c02b3080486deddbf Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:11 +0000
+Subject: Document that HashKnownHosts may break tab-completion
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1727
+Bug-Debian: http://bugs.debian.org/430154
+Last-Update: 2021-11-05
+
+Patch-Name: doc-hash-tab-completion.patch
+---
+ ssh_config.5 | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/ssh_config.5 b/ssh_config.5
+index 92600ed6e..82d68650e 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -921,6 +921,9 @@ Note that existing names and addresses in known hosts files
+ will not be converted automatically,
+ but may be manually hashed using
+ .Xr ssh-keygen 1 .
++Use of this option may break facilities such as tab-completion that rely
++on being able to read unhashed host names from
++.Pa ~/.ssh/known_hosts .
+ .It Cm HostbasedAcceptedAlgorithms
+ Specifies the signature algorithms that will be used for hostbased
+ authentication as a comma-separated list of patterns.
diff --git a/debian/patches/gnome-ssh-askpass2-icon.patch b/debian/patches/gnome-ssh-askpass2-icon.patch
new file mode 100644
index 0000000..50f27b4
--- /dev/null
+++ b/debian/patches/gnome-ssh-askpass2-icon.patch
@@ -0,0 +1,26 @@
+From 0b0ba78b1a3a0a7fd2d0d72f508d225c04df5aa7 Mon Sep 17 00:00:00 2001
+From: Vincent Untz <vuntz@ubuntu.com>
+Date: Sun, 9 Feb 2014 16:10:16 +0000
+Subject: Give the ssh-askpass-gnome window a default icon
+
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/27152
+Last-Update: 2010-02-28
+
+Patch-Name: gnome-ssh-askpass2-icon.patch
+---
+ contrib/gnome-ssh-askpass2.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/contrib/gnome-ssh-askpass2.c b/contrib/gnome-ssh-askpass2.c
+index a62f98152..304569802 100644
+--- a/contrib/gnome-ssh-askpass2.c
++++ b/contrib/gnome-ssh-askpass2.c
+@@ -320,6 +320,8 @@ main(int argc, char **argv)
+
+ gtk_init(&argc, &argv);
+
++ gtk_window_set_default_icon_from_file ("/usr/share/pixmaps/ssh-askpass-gnome.png", NULL);
++
+ if (argc > 1) {
+ message = g_strjoinv(" ", argv + 1);
+ } else {
diff --git a/debian/patches/gssapi.patch b/debian/patches/gssapi.patch
new file mode 100644
index 0000000..072c079
--- /dev/null
+++ b/debian/patches/gssapi.patch
@@ -0,0 +1,4156 @@
+From 61798b25a23b55d72a86a35062106cc3fc0ab834 Mon Sep 17 00:00:00 2001
+From: Simon Wilkinson <simon@sxw.org.uk>
+Date: Sun, 9 Feb 2014 16:09:48 +0000
+Subject: GSSAPI key exchange support
+
+This patch has been rejected upstream: "None of the OpenSSH developers are
+in favour of adding this, and this situation has not changed for several
+years. This is not a slight on Simon's patch, which is of fine quality, but
+just that a) we don't trust GSSAPI implementations that much and b) we don't
+like adding new KEX since they are pre-auth attack surface. This one is
+particularly scary, since it requires hooks out to typically root-owned
+system resources."
+
+However, quite a lot of people rely on this in Debian, and it's better to
+have it merged into the main openssh package rather than having separate
+-krb5 packages (as we used to have). It seems to have a generally good
+security history.
+
+Author: Simon Wilkinson <simon@sxw.org.uk>
+Author: Colin Watson <cjwatson@debian.org>
+Author: Jakub Jelen <jjelen@redhat.com>
+Origin: other, https://github.com/openssh-gsskex/openssh-gsskex/pull/23
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242
+Last-Updated: 2023-01-02
+
+Patch-Name: gssapi.patch
+---
+ Makefile.in | 5 +-
+ README.md | 36 +++
+ auth.c | 94 +-------
+ auth2-gss.c | 56 ++++-
+ auth2.c | 2 +
+ canohost.c | 91 ++++++++
+ canohost.h | 3 +
+ clientloop.c | 13 ++
+ configure.ac | 24 ++
+ gss-genr.c | 297 +++++++++++++++++++++++-
+ gss-serv-krb5.c | 87 ++++++-
+ gss-serv.c | 205 +++++++++++++++--
+ kex.c | 66 +++++-
+ kex.h | 29 +++
+ kexdh.c | 10 +
+ kexgen.c | 2 +-
+ kexgssc.c | 599 ++++++++++++++++++++++++++++++++++++++++++++++++
+ kexgsss.c | 474 ++++++++++++++++++++++++++++++++++++++
+ monitor.c | 139 ++++++++++-
+ monitor.h | 2 +
+ monitor_wrap.c | 57 ++++-
+ monitor_wrap.h | 4 +-
+ readconf.c | 74 ++++++
+ readconf.h | 6 +
+ servconf.c | 47 ++++
+ servconf.h | 3 +
+ session.c | 10 +-
+ ssh-gss.h | 54 ++++-
+ ssh-null.c | 108 +++++++++
+ ssh.1 | 8 +
+ ssh.c | 6 +-
+ ssh_config | 2 +
+ ssh_config.5 | 57 +++++
+ sshconnect2.c | 156 ++++++++++++-
+ sshd.c | 62 ++++-
+ sshd_config | 2 +
+ sshd_config.5 | 30 +++
+ sshkey.c | 8 +-
+ sshkey.h | 1 +
+ 39 files changed, 2765 insertions(+), 164 deletions(-)
+ create mode 100644 kexgssc.c
+ create mode 100644 kexgsss.c
+ create mode 100644 ssh-null.c
+
+diff --git a/Makefile.in b/Makefile.in
+index c0ebfa041..32b607719 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -101,7 +101,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ readpass.o ttymodes.o xmalloc.o addr.o addrmatch.o \
+ atomicio.o dispatch.o mac.o misc.o utf8.o \
+ monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-ecdsa-sk.o \
+- ssh-ed25519-sk.o ssh-rsa.o dh.o \
++ ssh-ed25519-sk.o ssh-rsa.o ssh-null.o dh.o \
+ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \
+ ssh-pkcs11.o smult_curve25519_ref.o \
+ poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \
+@@ -110,6 +110,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
+ kexgexc.o kexgexs.o \
+ kexsntrup761x25519.o sntrup761.o kexgen.o \
++ kexgssc.o \
+ sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+ sshbuf-io.o
+
+@@ -126,7 +127,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \
+ auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
+ auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
+ monitor.o monitor_wrap.o auth-krb5.o \
+- auth2-gss.o gss-serv.o gss-serv-krb5.o \
++ auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \
+ loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
+ srclimit.o sftp-server.o sftp-common.o \
+ sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
+diff --git a/README.md b/README.md
+index 3da933817..a338802ff 100644
+--- a/README.md
++++ b/README.md
+@@ -1,3 +1,39 @@
++Portable OpenSSH with GSSAPI Key Exchange patches
++=================================================
++
++[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/openssh-gsskex/openssh-gsskex.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/openssh-gsskex/openssh-gsskex/context:cpp)
++
++Currently, there are two branches with gssapi key exchange related
++patches:
++
++ * fedora/master: Changes that are shipped in Fedora [![Build Status](https://travis-ci.org/openssh-gsskex/openssh-gsskex.svg?branch=fedora%2Fmaster)](https://travis-ci.org/openssh-gsskex/openssh-gsskex)
++ * debian/master: Changes that are shipped in Debian [![Build Status](https://travis-ci.org/openssh-gsskex/openssh-gsskex.svg?branch=debian%2Fmaster)](https://travis-ci.org/openssh-gsskex/openssh-gsskex)
++
++The target is to converge to a shared repository with single master
++branch from where we could build releases for both OSes.
++
++
++What is in:
++
++ * The original patch implementing missing parts of RFC4462 by Simon Wilkinson
++ adapted to the current OpenSSH versions and with several fixes
++ * New methods for GSSAPI Kex from IETF draft [1] from Jakub Jelen
++
++
++Missing kerberos-related parts:
++
++ * .k5login and .kusers support available in Fedora [2] [3].
++ * Improved handling of kerberos ccache location [4]
++
++
++
++[1] https://tools.ietf.org/html/draft-ietf-curdle-gss-keyex-sha2-08
++[2] https://src.fedoraproject.org/rpms/openssh/blob/master/f/openssh-6.6p1-kuserok.patch
++[3] https://src.fedoraproject.org/rpms/openssh/blob/master/f/openssh-6.6p1-GSSAPIEnablek5users.patch
++[4] https://bugzilla.mindrot.org/show_bug.cgi?id=2775
++
++-------------------------------------------------------------------------------
++
+ # Portable OpenSSH
+
+ [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml)
+diff --git a/auth.c b/auth.c
+index 03a777cce..946dc6074 100644
+--- a/auth.c
++++ b/auth.c
+@@ -358,7 +358,8 @@ auth_root_allowed(struct ssh *ssh, const char *method)
+ case PERMIT_NO_PASSWD:
+ if (strcmp(method, "publickey") == 0 ||
+ strcmp(method, "hostbased") == 0 ||
+- strcmp(method, "gssapi-with-mic") == 0)
++ strcmp(method, "gssapi-with-mic") == 0 ||
++ strcmp(method, "gssapi-keyex") == 0)
+ return 1;
+ break;
+ case PERMIT_FORCED_ONLY:
+@@ -638,97 +639,6 @@ fakepw(void)
+ return (&fake);
+ }
+
+-/*
+- * Returns the remote DNS hostname as a string. The returned string must not
+- * be freed. NB. this will usually trigger a DNS query the first time it is
+- * called.
+- * This function does additional checks on the hostname to mitigate some
+- * attacks on based on conflation of hostnames and IP addresses.
+- */
+-
+-static char *
+-remote_hostname(struct ssh *ssh)
+-{
+- struct sockaddr_storage from;
+- socklen_t fromlen;
+- struct addrinfo hints, *ai, *aitop;
+- char name[NI_MAXHOST], ntop2[NI_MAXHOST];
+- const char *ntop = ssh_remote_ipaddr(ssh);
+-
+- /* Get IP address of client. */
+- fromlen = sizeof(from);
+- memset(&from, 0, sizeof(from));
+- if (getpeername(ssh_packet_get_connection_in(ssh),
+- (struct sockaddr *)&from, &fromlen) == -1) {
+- debug("getpeername failed: %.100s", strerror(errno));
+- return xstrdup(ntop);
+- }
+-
+- ipv64_normalise_mapped(&from, &fromlen);
+- if (from.ss_family == AF_INET6)
+- fromlen = sizeof(struct sockaddr_in6);
+-
+- debug3("Trying to reverse map address %.100s.", ntop);
+- /* Map the IP address to a host name. */
+- if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
+- NULL, 0, NI_NAMEREQD) != 0) {
+- /* Host name not found. Use ip address. */
+- return xstrdup(ntop);
+- }
+-
+- /*
+- * if reverse lookup result looks like a numeric hostname,
+- * someone is trying to trick us by PTR record like following:
+- * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5
+- */
+- memset(&hints, 0, sizeof(hints));
+- hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+- hints.ai_flags = AI_NUMERICHOST;
+- if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
+- logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
+- name, ntop);
+- freeaddrinfo(ai);
+- return xstrdup(ntop);
+- }
+-
+- /* Names are stored in lowercase. */
+- lowercase(name);
+-
+- /*
+- * Map it back to an IP address and check that the given
+- * address actually is an address of this host. This is
+- * necessary because anyone with access to a name server can
+- * define arbitrary names for an IP address. Mapping from
+- * name to IP address can be trusted better (but can still be
+- * fooled if the intruder has access to the name server of
+- * the domain).
+- */
+- memset(&hints, 0, sizeof(hints));
+- hints.ai_family = from.ss_family;
+- hints.ai_socktype = SOCK_STREAM;
+- if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
+- logit("reverse mapping checking getaddrinfo for %.700s "
+- "[%s] failed.", name, ntop);
+- return xstrdup(ntop);
+- }
+- /* Look for the address from the list of addresses. */
+- for (ai = aitop; ai; ai = ai->ai_next) {
+- if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
+- sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
+- (strcmp(ntop, ntop2) == 0))
+- break;
+- }
+- freeaddrinfo(aitop);
+- /* If we reached the end of the list, the address was not there. */
+- if (ai == NULL) {
+- /* Address not found for the host name. */
+- logit("Address %.100s maps to %.600s, but this does not "
+- "map back to the address.", ntop, name);
+- return xstrdup(ntop);
+- }
+- return xstrdup(name);
+-}
+-
+ /*
+ * Return the canonical name of the host in the other side of the current
+ * connection. The host name is cached, so it is efficient to call this
+diff --git a/auth2-gss.c b/auth2-gss.c
+index 2062609d9..4566d425c 100644
+--- a/auth2-gss.c
++++ b/auth2-gss.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: auth2-gss.c,v 1.33 2021/12/19 22:12:07 djm Exp $ */
+
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+@@ -55,6 +55,48 @@ static int input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh);
+ static int input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh);
+ static int input_gssapi_errtok(int, u_int32_t, struct ssh *);
+
++/*
++ * The 'gssapi_keyex' userauth mechanism.
++ */
++static int
++userauth_gsskeyex(struct ssh *ssh)
++{
++ Authctxt *authctxt = ssh->authctxt;
++ int r, authenticated = 0;
++ struct sshbuf *b = NULL;
++ gss_buffer_desc mic, gssbuf;
++ u_char *p;
++ size_t len;
++
++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal_fr(r, "parsing");
++
++ if ((b = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ mic.value = p;
++ mic.length = len;
++
++ ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
++ "gssapi-keyex", ssh->kex->session_id);
++
++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++ fatal_f("sshbuf_mutable_ptr failed");
++ gssbuf.length = sshbuf_len(b);
++
++ /* gss_kex_context is NULL with privsep, so we can't check it here */
++ if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context,
++ &gssbuf, &mic))))
++ authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 1));
++
++ sshbuf_free(b);
++ free(mic.value);
++
++ return (authenticated);
++}
++
+ /*
+ * We only support those mechanisms that we know about (ie ones that we know
+ * how to check local user kuserok and the like)
+@@ -261,7 +303,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh)
+ if ((r = sshpkt_get_end(ssh)) != 0)
+ fatal_fr(r, "parse packet");
+
+- authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user));
++ authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 1));
+
+ if ((!use_privsep || mm_is_monitor()) &&
+ (displayname = ssh_gssapi_displayname()) != NULL)
+@@ -307,7 +350,8 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
+ gssbuf.length = sshbuf_len(b);
+
+ if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))))
+- authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user));
++ authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 0));
+ else
+ logit("GSSAPI MIC check failed");
+
+@@ -327,6 +371,12 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
+ return 0;
+ }
+
++Authmethod method_gsskeyex = {
++ "gssapi-keyex",
++ userauth_gsskeyex,
++ &options.gss_authentication
++};
++
+ Authmethod method_gssapi = {
+ "gssapi-with-mic",
+ NULL,
+diff --git a/auth2.c b/auth2.c
+index 6c061934b..44bd49177 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -71,6 +71,7 @@ extern Authmethod method_passwd;
+ extern Authmethod method_kbdint;
+ extern Authmethod method_hostbased;
+ #ifdef GSSAPI
++extern Authmethod method_gsskeyex;
+ extern Authmethod method_gssapi;
+ #endif
+
+@@ -78,6 +79,7 @@ Authmethod *authmethods[] = {
+ &method_none,
+ &method_pubkey,
+ #ifdef GSSAPI
++ &method_gsskeyex,
+ &method_gssapi,
+ #endif
+ &method_passwd,
+diff --git a/canohost.c b/canohost.c
+index a810da0ee..99f1cd406 100644
+--- a/canohost.c
++++ b/canohost.c
+@@ -35,6 +35,97 @@
+ #include "canohost.h"
+ #include "misc.h"
+
++/*
++ * Returns the remote DNS hostname as a string. The returned string must not
++ * be freed. NB. this will usually trigger a DNS query the first time it is
++ * called.
++ * This function does additional checks on the hostname to mitigate some
++ * attacks on legacy rhosts-style authentication.
++ */
++
++char *
++remote_hostname(struct ssh *ssh)
++{
++ struct sockaddr_storage from;
++ socklen_t fromlen;
++ struct addrinfo hints, *ai, *aitop;
++ char name[NI_MAXHOST], ntop2[NI_MAXHOST];
++ const char *ntop = ssh_remote_ipaddr(ssh);
++
++ /* Get IP address of client. */
++ fromlen = sizeof(from);
++ memset(&from, 0, sizeof(from));
++ if (getpeername(ssh_packet_get_connection_in(ssh),
++ (struct sockaddr *)&from, &fromlen) == -1) {
++ debug("getpeername failed: %.100s", strerror(errno));
++ return xstrdup(ntop);
++ }
++
++ ipv64_normalise_mapped(&from, &fromlen);
++ if (from.ss_family == AF_INET6)
++ fromlen = sizeof(struct sockaddr_in6);
++
++ debug3("Trying to reverse map address %.100s.", ntop);
++ /* Map the IP address to a host name. */
++ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
++ NULL, 0, NI_NAMEREQD) != 0) {
++ /* Host name not found. Use ip address. */
++ return xstrdup(ntop);
++ }
++
++ /*
++ * if reverse lookup result looks like a numeric hostname,
++ * someone is trying to trick us by PTR record like following:
++ * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5
++ */
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
++ hints.ai_flags = AI_NUMERICHOST;
++ if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
++ logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
++ name, ntop);
++ freeaddrinfo(ai);
++ return xstrdup(ntop);
++ }
++
++ /* Names are stored in lowercase. */
++ lowercase(name);
++
++ /*
++ * Map it back to an IP address and check that the given
++ * address actually is an address of this host. This is
++ * necessary because anyone with access to a name server can
++ * define arbitrary names for an IP address. Mapping from
++ * name to IP address can be trusted better (but can still be
++ * fooled if the intruder has access to the name server of
++ * the domain).
++ */
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_family = from.ss_family;
++ hints.ai_socktype = SOCK_STREAM;
++ if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
++ logit("reverse mapping checking getaddrinfo for %.700s "
++ "[%s] failed.", name, ntop);
++ return xstrdup(ntop);
++ }
++ /* Look for the address from the list of addresses. */
++ for (ai = aitop; ai; ai = ai->ai_next) {
++ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
++ sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
++ (strcmp(ntop, ntop2) == 0))
++ break;
++ }
++ freeaddrinfo(aitop);
++ /* If we reached the end of the list, the address was not there. */
++ if (ai == NULL) {
++ /* Address not found for the host name. */
++ logit("Address %.100s maps to %.600s, but this does not "
++ "map back to the address.", ntop, name);
++ return xstrdup(ntop);
++ }
++ return xstrdup(name);
++}
++
+ void
+ ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len)
+ {
+diff --git a/canohost.h b/canohost.h
+index 26d62855a..0cadc9f18 100644
+--- a/canohost.h
++++ b/canohost.h
+@@ -15,6 +15,9 @@
+ #ifndef _CANOHOST_H
+ #define _CANOHOST_H
+
++struct ssh;
++
++char *remote_hostname(struct ssh *);
+ char *get_peer_ipaddr(int);
+ int get_peer_port(int);
+ char *get_local_ipaddr(int);
+diff --git a/clientloop.c b/clientloop.c
+index fef9efc6c..8f1fac58a 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -115,6 +115,10 @@
+ #include "ssherr.h"
+ #include "hostfile.h"
+
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++
+ /* Permitted RSA signature algorithms for UpdateHostkeys proofs */
+ #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256"
+
+@@ -1415,6 +1419,15 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
+ /* Do channel operations. */
+ channel_after_poll(ssh, pfd, npfd_active);
+
++#ifdef GSSAPI
++ if (!ssh_packet_is_rekeying(ssh) &&
++ options.gss_renewal_rekey &&
++ ssh_gssapi_credentials_updated(NULL)) {
++ debug("credentials updated - forcing rekey");
++ need_rekeying = 1;
++ }
++#endif
++
+ /* Buffer input from the connection. */
+ if (conn_in_ready)
+ client_process_net_input(ssh);
+diff --git a/configure.ac b/configure.ac
+index 22fee70f6..75a9e5670 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -756,6 +756,30 @@ int main(void) { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16))
+ [Use tunnel device compatibility to OpenBSD])
+ AC_DEFINE([SSH_TUN_PREPEND_AF], [1],
+ [Prepend the address family to IP tunnel traffic])
++ AC_MSG_CHECKING([if we have the Security Authorization Session API])
++ AC_TRY_COMPILE([#include <Security/AuthSession.h>],
++ [SessionCreate(0, 0);],
++ [ac_cv_use_security_session_api="yes"
++ AC_DEFINE([USE_SECURITY_SESSION_API], [1],
++ [platform has the Security Authorization Session API])
++ LIBS="$LIBS -framework Security"
++ AC_MSG_RESULT([yes])],
++ [ac_cv_use_security_session_api="no"
++ AC_MSG_RESULT([no])])
++ AC_MSG_CHECKING([if we have an in-memory credentials cache])
++ AC_TRY_COMPILE(
++ [#include <Kerberos/Kerberos.h>],
++ [cc_context_t c;
++ (void) cc_initialize (&c, 0, NULL, NULL);],
++ [AC_DEFINE([USE_CCAPI], [1],
++ [platform uses an in-memory credentials cache])
++ LIBS="$LIBS -framework Security"
++ AC_MSG_RESULT([yes])
++ if test "x$ac_cv_use_security_session_api" = "xno"; then
++ AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***])
++ fi],
++ [AC_MSG_RESULT([no])]
++ )
+ m4_pattern_allow([AU_IPv])
+ AC_CHECK_DECL([AU_IPv4], [],
+ AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records])
+diff --git a/gss-genr.c b/gss-genr.c
+index 2cd695e54..9f9745b7f 100644
+--- a/gss-genr.c
++++ b/gss-genr.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-genr.c,v 1.28 2021/01/27 10:05:28 djm Exp $ */
+
+ /*
+- * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+@@ -42,9 +42,33 @@
+ #include "sshbuf.h"
+ #include "log.h"
+ #include "ssh2.h"
++#include "cipher.h"
++#include "sshkey.h"
++#include "kex.h"
++#include "digest.h"
++#include "packet.h"
+
+ #include "ssh-gss.h"
+
++typedef struct {
++ char *encoded;
++ gss_OID oid;
++} ssh_gss_kex_mapping;
++
++/*
++ * XXX - It would be nice to find a more elegant way of handling the
++ * XXX passing of the key exchange context to the userauth routines
++ */
++
++Gssctxt *gss_kex_context = NULL;
++
++static ssh_gss_kex_mapping *gss_enc2oid = NULL;
++
++int
++ssh_gssapi_oid_table_ok(void) {
++ return (gss_enc2oid != NULL);
++}
++
+ /* sshbuf_get for gss_buffer_desc */
+ int
+ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
+@@ -60,6 +84,159 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
+ return 0;
+ }
+
++/* sshpkt_get of gss_buffer_desc */
++int
++ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g)
++{
++ int r;
++ u_char *p;
++ size_t len;
++
++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0)
++ return r;
++ g->value = p;
++ g->length = len;
++ return 0;
++}
++
++/*
++ * Return a list of the gss-group1-sha1 mechanisms supported by this program
++ *
++ * We test mechanisms to ensure that we can use them, to avoid starting
++ * a key exchange with a bad mechanism
++ */
++
++char *
++ssh_gssapi_client_mechanisms(const char *host, const char *client,
++ const char *kex) {
++ gss_OID_set gss_supported = NULL;
++ OM_uint32 min_status;
++
++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported)))
++ return NULL;
++
++ return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism,
++ host, client, kex);
++}
++
++char *
++ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check,
++ const char *host, const char *client, const char *kex) {
++ struct sshbuf *buf = NULL;
++ size_t i;
++ int r = SSH_ERR_ALLOC_FAIL;
++ int oidpos, enclen;
++ char *mechs, *encoded;
++ u_char digest[SSH_DIGEST_MAX_LENGTH];
++ char deroid[2];
++ struct ssh_digest_ctx *md = NULL;
++ char *s, *cp, *p;
++
++ if (gss_enc2oid != NULL) {
++ for (i = 0; gss_enc2oid[i].encoded != NULL; i++)
++ free(gss_enc2oid[i].encoded);
++ free(gss_enc2oid);
++ }
++
++ gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) *
++ (gss_supported->count + 1));
++
++ if ((buf = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ oidpos = 0;
++ s = cp = xstrdup(kex);
++ for (i = 0; i < gss_supported->count; i++) {
++ if (gss_supported->elements[i].length < 128 &&
++ (*check)(NULL, &(gss_supported->elements[i]), host, client)) {
++
++ deroid[0] = SSH_GSS_OIDTYPE;
++ deroid[1] = gss_supported->elements[i].length;
++
++ if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL ||
++ (r = ssh_digest_update(md, deroid, 2)) != 0 ||
++ (r = ssh_digest_update(md,
++ gss_supported->elements[i].elements,
++ gss_supported->elements[i].length)) != 0 ||
++ (r = ssh_digest_final(md, digest, sizeof(digest))) != 0)
++ fatal_fr(r, "digest failed");
++ ssh_digest_free(md);
++ md = NULL;
++
++ encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5)
++ * 2);
++ enclen = __b64_ntop(digest,
++ ssh_digest_bytes(SSH_DIGEST_MD5), encoded,
++ ssh_digest_bytes(SSH_DIGEST_MD5) * 2);
++
++ cp = strncpy(s, kex, strlen(kex));
++ for ((p = strsep(&cp, ",")); p && *p != '\0';
++ (p = strsep(&cp, ","))) {
++ if (sshbuf_len(buf) != 0 &&
++ (r = sshbuf_put_u8(buf, ',')) != 0)
++ fatal_fr(r, "sshbuf_put_u8 error");
++ if ((r = sshbuf_put(buf, p, strlen(p))) != 0 ||
++ (r = sshbuf_put(buf, encoded, enclen)) != 0)
++ fatal_fr(r, "sshbuf_put error");
++ }
++
++ gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]);
++ gss_enc2oid[oidpos].encoded = encoded;
++ oidpos++;
++ }
++ }
++ free(s);
++ gss_enc2oid[oidpos].oid = NULL;
++ gss_enc2oid[oidpos].encoded = NULL;
++
++ if ((mechs = sshbuf_dup_string(buf)) == NULL)
++ fatal_f("sshbuf_dup_string failed");
++
++ sshbuf_free(buf);
++
++ if (strlen(mechs) == 0) {
++ free(mechs);
++ mechs = NULL;
++ }
++
++ return (mechs);
++}
++
++gss_OID
++ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) {
++ int i = 0;
++
++#define SKIP_KEX_NAME(type) \
++ case type: \
++ if (strlen(name) < sizeof(type##_ID)) \
++ return GSS_C_NO_OID; \
++ name += sizeof(type##_ID) - 1; \
++ break;
++
++ switch (kex_type) {
++ SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1)
++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1)
++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256)
++ SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512)
++ SKIP_KEX_NAME(KEX_GSS_GEX_SHA1)
++ SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256)
++ SKIP_KEX_NAME(KEX_GSS_C25519_SHA256)
++ default:
++ return GSS_C_NO_OID;
++ }
++
++#undef SKIP_KEX_NAME
++
++ while (gss_enc2oid[i].encoded != NULL &&
++ strcmp(name, gss_enc2oid[i].encoded) != 0)
++ i++;
++
++ if (gss_enc2oid[i].oid != NULL && ctx != NULL)
++ ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid);
++
++ return gss_enc2oid[i].oid;
++}
++
+ /* Check that the OID in a data stream matches that in the context */
+ int
+ ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len)
+@@ -216,7 +393,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
+ }
+
+ ctx->major = gss_init_sec_context(&ctx->minor,
+- GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid,
++ ctx->client_creds, &ctx->context, ctx->name, ctx->oid,
+ GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag,
+ 0, NULL, recv_tok, NULL, send_tok, flags, NULL);
+
+@@ -245,9 +422,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
+ return (ctx->major);
+ }
+
++OM_uint32
++ssh_gssapi_client_identity(Gssctxt *ctx, const char *name)
++{
++ gss_buffer_desc gssbuf;
++ gss_name_t gssname;
++ OM_uint32 status;
++ gss_OID_set oidset;
++
++ gssbuf.value = (void *) name;
++ gssbuf.length = strlen(gssbuf.value);
++
++ gss_create_empty_oid_set(&status, &oidset);
++ gss_add_oid_set_member(&status, ctx->oid, &oidset);
++
++ ctx->major = gss_import_name(&ctx->minor, &gssbuf,
++ GSS_C_NT_USER_NAME, &gssname);
++
++ if (!ctx->major)
++ ctx->major = gss_acquire_cred(&ctx->minor,
++ gssname, 0, oidset, GSS_C_INITIATE,
++ &ctx->client_creds, NULL, NULL);
++
++ gss_release_name(&status, &gssname);
++ gss_release_oid_set(&status, &oidset);
++
++ if (ctx->major)
++ ssh_gssapi_error(ctx);
++
++ return(ctx->major);
++}
++
+ OM_uint32
+ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
+ {
++ if (ctx == NULL)
++ return -1;
++
+ if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context,
+ GSS_C_QOP_DEFAULT, buffer, hash)))
+ ssh_gssapi_error(ctx);
+@@ -255,6 +466,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
+ return (ctx->major);
+ }
+
++/* Priviledged when used by server */
++OM_uint32
++ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
++{
++ if (ctx == NULL)
++ return -1;
++
++ ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
++ gssbuf, gssmic, NULL);
++
++ return (ctx->major);
++}
++
+ void
+ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
+ const char *context, const struct sshbuf *session_id)
+@@ -271,11 +495,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
+ }
+
+ int
+-ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
++ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host,
++ const char *client)
+ {
+ gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
+ OM_uint32 major, minor;
+ gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"};
++ Gssctxt *intctx = NULL;
++
++ if (ctx == NULL)
++ ctx = &intctx;
+
+ /* RFC 4462 says we MUST NOT do SPNEGO */
+ if (oid->length == spnego_oid.length &&
+@@ -285,6 +514,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+ ssh_gssapi_build_ctx(ctx);
+ ssh_gssapi_set_oid(*ctx, oid);
+ major = ssh_gssapi_import_name(*ctx, host);
++
++ if (!GSS_ERROR(major) && client)
++ major = ssh_gssapi_client_identity(*ctx, client);
++
+ if (!GSS_ERROR(major)) {
+ major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token,
+ NULL);
+@@ -294,10 +527,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+ GSS_C_NO_BUFFER);
+ }
+
+- if (GSS_ERROR(major))
++ if (GSS_ERROR(major) || intctx != NULL)
+ ssh_gssapi_delete_ctx(ctx);
+
+ return (!GSS_ERROR(major));
+ }
+
++int
++ssh_gssapi_credentials_updated(Gssctxt *ctxt) {
++ static gss_name_t saved_name = GSS_C_NO_NAME;
++ static OM_uint32 saved_lifetime = 0;
++ static gss_OID saved_mech = GSS_C_NO_OID;
++ static gss_name_t name;
++ static OM_uint32 last_call = 0;
++ OM_uint32 lifetime, now, major, minor;
++ int equal;
++
++ now = time(NULL);
++
++ if (ctxt) {
++ debug("Rekey has happened - updating saved versions");
++
++ if (saved_name != GSS_C_NO_NAME)
++ gss_release_name(&minor, &saved_name);
++
++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
++ &saved_name, &saved_lifetime, NULL, NULL);
++
++ if (!GSS_ERROR(major)) {
++ saved_mech = ctxt->oid;
++ saved_lifetime+= now;
++ } else {
++ /* Handle the error */
++ }
++ return 0;
++ }
++
++ if (now - last_call < 10)
++ return 0;
++
++ last_call = now;
++
++ if (saved_mech == GSS_C_NO_OID)
++ return 0;
++
++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
++ &name, &lifetime, NULL, NULL);
++ if (major == GSS_S_CREDENTIALS_EXPIRED)
++ return 0;
++ else if (GSS_ERROR(major))
++ return 0;
++
++ major = gss_compare_name(&minor, saved_name, name, &equal);
++ gss_release_name(&minor, &name);
++ if (GSS_ERROR(major))
++ return 0;
++
++ if (equal && (saved_lifetime < lifetime + now - 10))
++ return 1;
++
++ return 0;
++}
++
+ #endif /* GSSAPI */
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index a151bc1e4..ef20401ec 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */
+
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+@@ -120,7 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ krb5_error_code problem;
+ krb5_principal princ;
+ OM_uint32 maj_status, min_status;
+- int len;
++ const char *new_ccname;
+ const char *errmsg;
+
+ if (client->creds == NULL) {
+@@ -180,11 +180,16 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ return;
+ }
+
+- client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache));
++ new_ccname = krb5_cc_get_name(krb_context, ccache);
++
+ client->store.envvar = "KRB5CCNAME";
+- len = strlen(client->store.filename) + 6;
+- client->store.envval = xmalloc(len);
+- snprintf(client->store.envval, len, "FILE:%s", client->store.filename);
++#ifdef USE_CCAPI
++ xasprintf(&client->store.envval, "API:%s", new_ccname);
++ client->store.filename = NULL;
++#else
++ xasprintf(&client->store.envval, "FILE:%s", new_ccname);
++ client->store.filename = xstrdup(new_ccname);
++#endif
+
+ #ifdef USE_PAM
+ if (options.use_pam)
+@@ -193,9 +198,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+
+ krb5_cc_close(krb_context, ccache);
+
++ client->store.data = krb_context;
++
+ return;
+ }
+
++int
++ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
++ ssh_gssapi_client *client)
++{
++ krb5_ccache ccache = NULL;
++ krb5_principal principal = NULL;
++ char *name = NULL;
++ krb5_error_code problem;
++ OM_uint32 maj_status, min_status;
++
++ if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) {
++ logit("krb5_cc_resolve(): %.100s",
++ krb5_get_err_text(krb_context, problem));
++ return 0;
++ }
++
++ /* Find out who the principal in this cache is */
++ if ((problem = krb5_cc_get_principal(krb_context, ccache,
++ &principal))) {
++ logit("krb5_cc_get_principal(): %.100s",
++ krb5_get_err_text(krb_context, problem));
++ krb5_cc_close(krb_context, ccache);
++ return 0;
++ }
++
++ if ((problem = krb5_unparse_name(krb_context, principal, &name))) {
++ logit("krb5_unparse_name(): %.100s",
++ krb5_get_err_text(krb_context, problem));
++ krb5_free_principal(krb_context, principal);
++ krb5_cc_close(krb_context, ccache);
++ return 0;
++ }
++
++
++ if (strcmp(name,client->exportedname.value)!=0) {
++ debug("Name in local credentials cache differs. Not storing");
++ krb5_free_principal(krb_context, principal);
++ krb5_cc_close(krb_context, ccache);
++ krb5_free_unparsed_name(krb_context, name);
++ return 0;
++ }
++ krb5_free_unparsed_name(krb_context, name);
++
++ /* Name matches, so lets get on with it! */
++
++ if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) {
++ logit("krb5_cc_initialize(): %.100s",
++ krb5_get_err_text(krb_context, problem));
++ krb5_free_principal(krb_context, principal);
++ krb5_cc_close(krb_context, ccache);
++ return 0;
++ }
++
++ krb5_free_principal(krb_context, principal);
++
++ if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds,
++ ccache))) {
++ logit("gss_krb5_copy_ccache() failed. Sorry!");
++ krb5_cc_close(krb_context, ccache);
++ return 0;
++ }
++
++ return 1;
++}
++
+ ssh_gssapi_mech gssapi_kerberos_mech = {
+ "toWM5Slw5Ew8Mqkay+al2g==",
+ "Kerberos",
+@@ -203,7 +275,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = {
+ NULL,
+ &ssh_gssapi_krb5_userok,
+ NULL,
+- &ssh_gssapi_krb5_storecreds
++ &ssh_gssapi_krb5_storecreds,
++ &ssh_gssapi_krb5_updatecreds
+ };
+
+ #endif /* KRB5 */
+diff --git a/gss-serv.c b/gss-serv.c
+index b5d4bb2d1..4287579ae 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */
+
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+@@ -44,17 +44,19 @@
+ #include "session.h"
+ #include "misc.h"
+ #include "servconf.h"
++#include "uidswap.h"
+
+ #include "ssh-gss.h"
++#include "monitor_wrap.h"
+
+ extern ServerOptions options;
+
+ static ssh_gssapi_client gssapi_client =
+- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER,
+- GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}};
++ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
+
+ ssh_gssapi_mech gssapi_null_mech =
+- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL};
++ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+
+ #ifdef KRB5
+ extern ssh_gssapi_mech gssapi_kerberos_mech;
+@@ -140,6 +142,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid)
+ return (ssh_gssapi_acquire_cred(*ctx));
+ }
+
++/* Unprivileged */
++char *
++ssh_gssapi_server_mechanisms(void) {
++ if (supported_oids == NULL)
++ ssh_gssapi_prepare_supported_oids();
++ return (ssh_gssapi_kex_mechs(supported_oids,
++ &ssh_gssapi_server_check_mech, NULL, NULL,
++ options.gss_kex_algorithms));
++}
++
++/* Unprivileged */
++int
++ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data,
++ const char *dummy) {
++ Gssctxt *ctx = NULL;
++ int res;
++
++ res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid)));
++ ssh_gssapi_delete_ctx(&ctx);
++
++ return (res);
++}
++
+ /* Unprivileged */
+ void
+ ssh_gssapi_supported_oids(gss_OID_set *oidset)
+@@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *oidset)
+ gss_OID_set supported;
+
+ gss_create_empty_oid_set(&min_status, oidset);
+- gss_indicate_mechs(&min_status, &supported);
++
++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported)))
++ return;
+
+ while (supported_mechs[i]->name != NULL) {
+ if (GSS_ERROR(gss_test_oid_set_member(&min_status,
+@@ -276,8 +303,48 @@ OM_uint32
+ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ {
+ int i = 0;
++ int equal = 0;
++ gss_name_t new_name = GSS_C_NO_NAME;
++ gss_buffer_desc ename = GSS_C_EMPTY_BUFFER;
++
++ if (options.gss_store_rekey && client->used && ctx->client_creds) {
++ if (client->mech->oid.length != ctx->oid->length ||
++ (memcmp(client->mech->oid.elements,
++ ctx->oid->elements, ctx->oid->length) !=0)) {
++ debug("Rekeyed credentials have different mechanism");
++ return GSS_S_COMPLETE;
++ }
++
++ if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
++ ctx->client_creds, ctx->oid, &new_name,
++ NULL, NULL, NULL))) {
++ ssh_gssapi_error(ctx);
++ return (ctx->major);
++ }
+
+- gss_buffer_desc ename;
++ ctx->major = gss_compare_name(&ctx->minor, client->name,
++ new_name, &equal);
++
++ if (GSS_ERROR(ctx->major)) {
++ ssh_gssapi_error(ctx);
++ return (ctx->major);
++ }
++
++ if (!equal) {
++ debug("Rekeyed credentials have different name");
++ return GSS_S_COMPLETE;
++ }
++
++ debug("Marking rekeyed credentials for export");
++
++ gss_release_name(&ctx->minor, &client->name);
++ gss_release_cred(&ctx->minor, &client->creds);
++ client->name = new_name;
++ client->creds = ctx->client_creds;
++ ctx->client_creds = GSS_C_NO_CREDENTIAL;
++ client->updated = 1;
++ return GSS_S_COMPLETE;
++ }
+
+ client->mech = NULL;
+
+@@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ if (client->mech == NULL)
+ return GSS_S_FAILURE;
+
++ if (ctx->client_creds &&
++ (ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
++ ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) {
++ ssh_gssapi_error(ctx);
++ return (ctx->major);
++ }
++
+ if ((ctx->major = gss_display_name(&ctx->minor, ctx->client,
+ &client->displayname, NULL))) {
+ ssh_gssapi_error(ctx);
+@@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ return (ctx->major);
+ }
+
++ gss_release_buffer(&ctx->minor, &ename);
++
+ /* We can't copy this structure, so we just move the pointer to it */
+ client->creds = ctx->client_creds;
+ ctx->client_creds = GSS_C_NO_CREDENTIAL;
+@@ -319,11 +395,20 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ void
+ ssh_gssapi_cleanup_creds(void)
+ {
+- if (gssapi_client.store.filename != NULL) {
+- /* Unlink probably isn't sufficient */
+- debug("removing gssapi cred file\"%s\"",
+- gssapi_client.store.filename);
+- unlink(gssapi_client.store.filename);
++ 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;
++ }
+ }
+ }
+
+@@ -356,19 +441,23 @@ ssh_gssapi_do_child(char ***envp, u_int *envsizep)
+
+ /* Privileged */
+ int
+-ssh_gssapi_userok(char *user)
++ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ OM_uint32 lmin;
+
++ (void) kex; /* used in privilege separation */
++
+ if (gssapi_client.exportedname.length == 0 ||
+ gssapi_client.exportedname.value == NULL) {
+ debug("No suitable client data");
+ return 0;
+ }
+ if (gssapi_client.mech && gssapi_client.mech->userok)
+- if ((*gssapi_client.mech->userok)(&gssapi_client, user))
++ if ((*gssapi_client.mech->userok)(&gssapi_client, user)) {
++ gssapi_client.used = 1;
++ gssapi_client.store.owner = pw;
+ return 1;
+- else {
++ } else {
+ /* Destroy delegated credentials if userok fails */
+ gss_release_buffer(&lmin, &gssapi_client.displayname);
+ gss_release_buffer(&lmin, &gssapi_client.exportedname);
+@@ -382,14 +471,90 @@ ssh_gssapi_userok(char *user)
+ return (0);
+ }
+
+-/* Privileged */
+-OM_uint32
+-ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
++/* These bits are only used for rekeying. The unpriviledged child is running
++ * as the user, the monitor is root.
++ *
++ * In the child, we want to :
++ * *) Ask the monitor to store our credentials into the store we specify
++ * *) If it succeeds, maybe do a PAM update
++ */
++
++/* Stuff for PAM */
++
++#ifdef USE_PAM
++static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg,
++ struct pam_response **resp, void *data)
+ {
+- ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
+- gssbuf, gssmic, NULL);
++ return (PAM_CONV_ERR);
++}
++#endif
+
+- return (ctx->major);
++void
++ssh_gssapi_rekey_creds(void) {
++ int ok;
++#ifdef USE_PAM
++ int ret;
++ pam_handle_t *pamh = NULL;
++ struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL};
++ char *envstr;
++#endif
++
++ if (gssapi_client.store.filename == NULL &&
++ gssapi_client.store.envval == NULL &&
++ gssapi_client.store.envvar == NULL)
++ return;
++
++ ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store));
++
++ if (!ok)
++ return;
++
++ debug("Rekeyed credentials stored successfully");
++
++ /* Actually managing to play with the ssh pam stack from here will
++ * be next to impossible. In any case, we may want different options
++ * for rekeying. So, use our own :)
++ */
++#ifdef USE_PAM
++ if (!use_privsep) {
++ debug("Not even going to try and do PAM with privsep disabled");
++ return;
++ }
++
++ ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name,
++ &pamconv, &pamh);
++ if (ret)
++ return;
++
++ xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar,
++ gssapi_client.store.envval);
++
++ ret = pam_putenv(pamh, envstr);
++ if (!ret)
++ pam_setcred(pamh, PAM_REINITIALIZE_CRED);
++ pam_end(pamh, PAM_SUCCESS);
++#endif
++}
++
++int
++ssh_gssapi_update_creds(ssh_gssapi_ccache *store) {
++ int ok = 0;
++
++ /* Check we've got credentials to store */
++ if (!gssapi_client.updated)
++ return 0;
++
++ gssapi_client.updated = 0;
++
++ temporarily_use_uid(gssapi_client.store.owner);
++ if (gssapi_client.mech && gssapi_client.mech->updatecreds)
++ ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client);
++ else
++ debug("No update function for this mechanism");
++
++ restore_uid();
++
++ return ok;
+ }
+
+ /* Privileged */
+diff --git a/kex.c b/kex.c
+index 8cdefcf7c..c259e00fc 100644
+--- a/kex.c
++++ b/kex.c
+@@ -57,11 +57,16 @@
+ #include "misc.h"
+ #include "dispatch.h"
+ #include "monitor.h"
++#include "xmalloc.h"
+
+ #include "ssherr.h"
+ #include "sshbuf.h"
+ #include "digest.h"
+
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++
+ /* prototype */
+ static int kex_choose_conf(struct ssh *);
+ static int kex_input_newkeys(int, u_int32_t, struct ssh *);
+@@ -117,15 +122,28 @@ static const struct kexalg kexalgs[] = {
+ #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
+ { NULL, 0, -1, -1},
+ };
++static const struct kexalg gss_kexalgs[] = {
++#ifdef GSSAPI
++ { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 },
++ { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 },
++ { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 },
++ { KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 },
++ { KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 },
++ { KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256,
++ NID_X9_62_prime256v1, SSH_DIGEST_SHA256 },
++ { KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
++#endif
++ { NULL, 0, -1, -1},
++};
+
+-char *
+-kex_alg_list(char sep)
++static char *
++kex_alg_list_internal(char sep, const struct kexalg *algs)
+ {
+ char *ret = NULL, *tmp;
+ size_t nlen, rlen = 0;
+ const struct kexalg *k;
+
+- for (k = kexalgs; k->name != NULL; k++) {
++ for (k = algs; k->name != NULL; k++) {
+ if (ret != NULL)
+ ret[rlen++] = sep;
+ nlen = strlen(k->name);
+@@ -140,6 +158,18 @@ kex_alg_list(char sep)
+ return ret;
+ }
+
++char *
++kex_alg_list(char sep)
++{
++ return kex_alg_list_internal(sep, kexalgs);
++}
++
++char *
++kex_gss_alg_list(char sep)
++{
++ return kex_alg_list_internal(sep, gss_kexalgs);
++}
++
+ static const struct kexalg *
+ kex_alg_by_name(const char *name)
+ {
+@@ -149,6 +179,10 @@ kex_alg_by_name(const char *name)
+ if (strcmp(k->name, name) == 0)
+ return k;
+ }
++ for (k = gss_kexalgs; k->name != NULL; k++) {
++ if (strncmp(k->name, name, strlen(k->name)) == 0)
++ return k;
++ }
+ return NULL;
+ }
+
+@@ -317,6 +351,29 @@ kex_assemble_names(char **listp, const char *def, const char *all)
+ return r;
+ }
+
++/* Validate GSS KEX method name list */
++int
++kex_gss_names_valid(const char *names)
++{
++ char *s, *cp, *p;
++
++ if (names == NULL || *names == '\0')
++ return 0;
++ s = cp = xstrdup(names);
++ for ((p = strsep(&cp, ",")); p && *p != '\0';
++ (p = strsep(&cp, ","))) {
++ if (strncmp(p, "gss-", 4) != 0
++ || kex_alg_by_name(p) == NULL) {
++ error("Unsupported KEX algorithm \"%.100s\"", p);
++ free(s);
++ return 0;
++ }
++ }
++ debug3("gss kex names ok: [%s]", names);
++ free(s);
++ return 1;
++}
++
+ /* put algorithm proposal into buffer */
+ int
+ kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX])
+@@ -719,6 +776,9 @@ kex_free(struct kex *kex)
+ sshbuf_free(kex->session_id);
+ sshbuf_free(kex->initial_sig);
+ sshkey_free(kex->initial_hostkey);
++#ifdef GSSAPI
++ free(kex->gss_host);
++#endif /* GSSAPI */
+ free(kex->failed_choice);
+ free(kex->hostkey_alg);
+ free(kex->name);
+diff --git a/kex.h b/kex.h
+index c35329501..240dca89d 100644
+--- a/kex.h
++++ b/kex.h
+@@ -102,6 +102,15 @@ enum kex_exchange {
+ KEX_ECDH_SHA2,
+ KEX_C25519_SHA256,
+ KEX_KEM_SNTRUP761X25519_SHA512,
++#ifdef GSSAPI
++ KEX_GSS_GRP1_SHA1,
++ KEX_GSS_GRP14_SHA1,
++ KEX_GSS_GRP14_SHA256,
++ KEX_GSS_GRP16_SHA512,
++ KEX_GSS_GEX_SHA1,
++ KEX_GSS_NISTP256_SHA256,
++ KEX_GSS_C25519_SHA256,
++#endif
+ KEX_MAX
+ };
+
+@@ -159,6 +168,12 @@ struct kex {
+ u_int flags;
+ int hash_alg;
+ int ec_nid;
++#ifdef GSSAPI
++ int gss_deleg_creds;
++ int gss_trust_dns;
++ char *gss_host;
++ char *gss_client;
++#endif
+ char *failed_choice;
+ int (*verify_host_key)(struct sshkey *, struct ssh *);
+ struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
+@@ -180,8 +195,10 @@ struct kex {
+
+ int kex_names_valid(const char *);
+ char *kex_alg_list(char);
++char *kex_gss_alg_list(char);
+ char *kex_names_cat(const char *, const char *);
+ int kex_assemble_names(char **, const char *, const char *);
++int kex_gss_names_valid(const char *);
+
+ int kex_exchange_identification(struct ssh *, int, const char *);
+
+@@ -209,6 +226,12 @@ int kexgex_client(struct ssh *);
+ int kexgex_server(struct ssh *);
+ int kex_gen_client(struct ssh *);
+ int kex_gen_server(struct ssh *);
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++int kexgssgex_client(struct ssh *);
++int kexgssgex_server(struct ssh *);
++int kexgss_client(struct ssh *);
++int kexgss_server(struct ssh *);
++#endif
+
+ int kex_dh_keypair(struct kex *);
+ int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
+@@ -241,6 +264,12 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
+ const BIGNUM *, const u_char *, size_t,
+ u_char *, size_t *);
+
++int kex_gen_hash(int hash_alg, const struct sshbuf *client_version,
++ const struct sshbuf *server_version, const struct sshbuf *client_kexinit,
++ const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob,
++ const struct sshbuf *client_pub, const struct sshbuf *server_pub,
++ const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen);
++
+ void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE])
+ __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE)))
+ __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE)));
+diff --git a/kexdh.c b/kexdh.c
+index c1084f214..0faab21b0 100644
+--- a/kexdh.c
++++ b/kexdh.c
+@@ -49,13 +49,23 @@ kex_dh_keygen(struct kex *kex)
+ {
+ switch (kex->kex_type) {
+ case KEX_DH_GRP1_SHA1:
++#ifdef GSSAPI
++ case KEX_GSS_GRP1_SHA1:
++#endif
+ kex->dh = dh_new_group1();
+ break;
+ case KEX_DH_GRP14_SHA1:
+ case KEX_DH_GRP14_SHA256:
++#ifdef GSSAPI
++ case KEX_GSS_GRP14_SHA1:
++ case KEX_GSS_GRP14_SHA256:
++#endif
+ kex->dh = dh_new_group14();
+ break;
+ case KEX_DH_GRP16_SHA512:
++#ifdef GSSAPI
++ case KEX_GSS_GRP16_SHA512:
++#endif
+ kex->dh = dh_new_group16();
+ break;
+ case KEX_DH_GRP18_SHA512:
+diff --git a/kexgen.c b/kexgen.c
+index 20f3c5711..ca704844e 100644
+--- a/kexgen.c
++++ b/kexgen.c
+@@ -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);
+
+-static int
++int
+ kex_gen_hash(
+ int hash_alg,
+ const struct sshbuf *client_version,
+diff --git a/kexgssc.c b/kexgssc.c
+new file mode 100644
+index 000000000..1c62740e7
+--- /dev/null
++++ b/kexgssc.c
+@@ -0,0 +1,599 @@
++/*
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++
++#include "includes.h"
++
++#include <openssl/crypto.h>
++#include <openssl/bn.h>
++
++#include <string.h>
++
++#include "xmalloc.h"
++#include "sshbuf.h"
++#include "ssh2.h"
++#include "sshkey.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "digest.h"
++#include "ssherr.h"
++
++#include "ssh-gss.h"
++
++int
++kexgss_client(struct ssh *ssh)
++{
++ struct kex *kex = ssh->kex;
++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
++ recv_tok = GSS_C_EMPTY_BUFFER,
++ gssbuf, msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
++ Gssctxt *ctxt;
++ OM_uint32 maj_status, min_status, ret_flags;
++ struct sshbuf *server_blob = NULL;
++ struct sshbuf *shared_secret = NULL;
++ struct sshbuf *server_host_key_blob = NULL;
++ struct sshbuf *empty = NULL;
++ u_char *msg;
++ int type = 0;
++ int first = 1;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ size_t hashlen;
++ u_char c;
++ int r;
++
++ /* Initialise our GSSAPI world */
++ ssh_gssapi_build_ctx(&ctxt);
++ if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
++ == GSS_C_NO_OID)
++ fatal("Couldn't identify host exchange");
++
++ if (ssh_gssapi_import_name(ctxt, kex->gss_host))
++ fatal("Couldn't import hostname");
++
++ if (kex->gss_client &&
++ ssh_gssapi_client_identity(ctxt, kex->gss_client))
++ fatal("Couldn't acquire client credentials");
++
++ /* Step 1 */
++ switch (kex->kex_type) {
++ case KEX_GSS_GRP1_SHA1:
++ case KEX_GSS_GRP14_SHA1:
++ case KEX_GSS_GRP14_SHA256:
++ case KEX_GSS_GRP16_SHA512:
++ r = kex_dh_keypair(kex);
++ break;
++ case KEX_GSS_NISTP256_SHA256:
++ r = kex_ecdh_keypair(kex);
++ break;
++ case KEX_GSS_C25519_SHA256:
++ r = kex_c25519_keypair(kex);
++ break;
++ default:
++ fatal_f("Unexpected KEX type %d", kex->kex_type);
++ }
++ if (r != 0)
++ return r;
++
++ token_ptr = GSS_C_NO_BUFFER;
++
++ do {
++ debug("Calling gss_init_sec_context");
++
++ maj_status = ssh_gssapi_init_ctx(ctxt,
++ kex->gss_deleg_creds, token_ptr, &send_tok,
++ &ret_flags);
++
++ if (GSS_ERROR(maj_status)) {
++ /* XXX Useles code: Missing send? */
++ if (send_tok.length != 0) {
++ if ((r = sshpkt_start(ssh,
++ SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value,
++ send_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ fatal("gss_init_context failed");
++ }
++
++ /* If we've got an old receive buffer get rid of it */
++ if (token_ptr != GSS_C_NO_BUFFER)
++ gss_release_buffer(&min_status, &recv_tok);
++
++ if (maj_status == GSS_S_COMPLETE) {
++ /* If mutual state flag is not true, kex fails */
++ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++ fatal("Mutual authentication failed");
++
++ /* If integ avail flag is not true kex fails */
++ if (!(ret_flags & GSS_C_INTEG_FLAG))
++ fatal("Integrity check failed");
++ }
++
++ /*
++ * If we have data to send, then the last message that we
++ * received cannot have been a 'complete'.
++ */
++ if (send_tok.length != 0) {
++ if (first) {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value,
++ send_tok.length)) != 0 ||
++ (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0)
++ fatal("failed to construct packet: %s", ssh_err(r));
++ first = 0;
++ } else {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value,
++ send_tok.length)) != 0)
++ fatal("failed to construct packet: %s", ssh_err(r));
++ }
++ if ((r = sshpkt_send(ssh)) != 0)
++ fatal("failed to send packet: %s", ssh_err(r));
++ gss_release_buffer(&min_status, &send_tok);
++
++ /* If we've sent them data, they should reply */
++ do {
++ type = ssh_packet_read(ssh);
++ if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
++ debug("Received KEXGSS_HOSTKEY");
++ if (server_host_key_blob)
++ fatal("Server host key received more than once");
++ if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0)
++ fatal("Failed to read server host key: %s", ssh_err(r));
++ }
++ } while (type == SSH2_MSG_KEXGSS_HOSTKEY);
++
++ switch (type) {
++ case SSH2_MSG_KEXGSS_CONTINUE:
++ debug("Received GSSAPI_CONTINUE");
++ if (maj_status == GSS_S_COMPLETE)
++ fatal("GSSAPI Continue received from server when complete");
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("Failed to read token: %s", ssh_err(r));
++ break;
++ case SSH2_MSG_KEXGSS_COMPLETE:
++ debug("Received GSSAPI_COMPLETE");
++ if (msg_tok.value != NULL)
++ fatal("Received GSSAPI_COMPLETE twice?");
++ if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &msg_tok)) != 0)
++ fatal("Failed to read message: %s", ssh_err(r));
++
++ /* Is there a token included? */
++ if ((r = sshpkt_get_u8(ssh, &c)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ if (c) {
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
++ ssh, &recv_tok)) != 0)
++ fatal("Failed to read token: %s", ssh_err(r));
++ /* If we're already complete - protocol error */
++ if (maj_status == GSS_S_COMPLETE)
++ sshpkt_disconnect(ssh, "Protocol error: received token when complete");
++ } else {
++ /* No token included */
++ if (maj_status != GSS_S_COMPLETE)
++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
++ }
++ if ((r = sshpkt_get_end(ssh)) != 0) {
++ fatal("Expecting end of packet.");
++ }
++ break;
++ case SSH2_MSG_KEXGSS_ERROR:
++ debug("Received Error");
++ if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
++ (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
++ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt_get failed: %s", ssh_err(r));
++ fatal("GSSAPI Error: \n%.400s", msg);
++ default:
++ sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
++ type);
++ }
++ token_ptr = &recv_tok;
++ } else {
++ /* No data, and not complete */
++ if (maj_status != GSS_S_COMPLETE)
++ fatal("Not complete, and no token output");
++ }
++ } while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++ /*
++ * We _must_ have received a COMPLETE message in reply from the
++ * server, which will have set server_blob and msg_tok
++ */
++
++ if (type != SSH2_MSG_KEXGSS_COMPLETE)
++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
++
++ /* compute shared secret */
++ switch (kex->kex_type) {
++ case KEX_GSS_GRP1_SHA1:
++ case KEX_GSS_GRP14_SHA1:
++ case KEX_GSS_GRP14_SHA256:
++ case KEX_GSS_GRP16_SHA512:
++ r = kex_dh_dec(kex, server_blob, &shared_secret);
++ break;
++ case KEX_GSS_C25519_SHA256:
++ if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80)
++ fatal("The received key has MSB of last octet set!");
++ r = kex_c25519_dec(kex, server_blob, &shared_secret);
++ break;
++ case KEX_GSS_NISTP256_SHA256:
++ if (sshbuf_len(server_blob) != 65)
++ fatal("The received NIST-P256 key did not match"
++ "expected length (expected 65, got %zu)", sshbuf_len(server_blob));
++
++ if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED)
++ fatal("The received NIST-P256 key does not have first octet 0x04");
++
++ r = kex_ecdh_dec(kex, server_blob, &shared_secret);
++ break;
++ default:
++ r = SSH_ERR_INVALID_ARGUMENT;
++ break;
++ }
++ if (r != 0)
++ goto out;
++
++ if ((empty = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++
++ hashlen = sizeof(hash);
++ if ((r = kex_gen_hash(
++ kex->hash_alg,
++ kex->client_version,
++ kex->server_version,
++ kex->my,
++ kex->peer,
++ (server_host_key_blob ? server_host_key_blob : empty),
++ kex->client_pub,
++ server_blob,
++ shared_secret,
++ hash, &hashlen)) != 0)
++ fatal_f("Unexpected KEX type %d", kex->kex_type);
++
++ gssbuf.value = hash;
++ gssbuf.length = hashlen;
++
++ /* Verify that the hash matches the MIC we just got. */
++ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
++
++ gss_release_buffer(&min_status, &msg_tok);
++
++ if (kex->gss_deleg_creds)
++ ssh_gssapi_credentials_updated(ctxt);
++
++ if (gss_kex_context == NULL)
++ gss_kex_context = ctxt;
++ else
++ ssh_gssapi_delete_ctx(&ctxt);
++
++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++ r = kex_send_newkeys(ssh);
++
++out:
++ explicit_bzero(hash, sizeof(hash));
++ explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key));
++ sshbuf_free(empty);
++ sshbuf_free(server_host_key_blob);
++ sshbuf_free(server_blob);
++ sshbuf_free(shared_secret);
++ sshbuf_free(kex->client_pub);
++ kex->client_pub = NULL;
++ return r;
++}
++
++int
++kexgssgex_client(struct ssh *ssh)
++{
++ struct kex *kex = ssh->kex;
++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
++ recv_tok = GSS_C_EMPTY_BUFFER, gssbuf,
++ msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
++ Gssctxt *ctxt;
++ OM_uint32 maj_status, min_status, ret_flags;
++ struct sshbuf *shared_secret = NULL;
++ BIGNUM *p = NULL;
++ BIGNUM *g = NULL;
++ struct sshbuf *buf = NULL;
++ struct sshbuf *server_host_key_blob = NULL;
++ struct sshbuf *server_blob = NULL;
++ BIGNUM *dh_server_pub = NULL;
++ u_char *msg;
++ int type = 0;
++ int first = 1;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ size_t hashlen;
++ const BIGNUM *pub_key, *dh_p, *dh_g;
++ int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX;
++ struct sshbuf *empty = NULL;
++ u_char c;
++ int r;
++
++ /* Initialise our GSSAPI world */
++ ssh_gssapi_build_ctx(&ctxt);
++ if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
++ == GSS_C_NO_OID)
++ fatal("Couldn't identify host exchange");
++
++ if (ssh_gssapi_import_name(ctxt, kex->gss_host))
++ fatal("Couldn't import hostname");
++
++ if (kex->gss_client &&
++ ssh_gssapi_client_identity(ctxt, kex->gss_client))
++ fatal("Couldn't acquire client credentials");
++
++ debug("Doing group exchange");
++ nbits = dh_estimate(kex->dh_need * 8);
++
++ kex->min = DH_GRP_MIN;
++ kex->max = DH_GRP_MAX;
++ kex->nbits = nbits;
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 ||
++ (r = sshpkt_put_u32(ssh, min)) != 0 ||
++ (r = sshpkt_put_u32(ssh, nbits)) != 0 ||
++ (r = sshpkt_put_u32(ssh, max)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("Failed to construct a packet: %s", ssh_err(r));
++
++ if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0)
++ fatal("Error: %s", ssh_err(r));
++
++ if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 ||
++ (r = sshpkt_get_bignum2(ssh, &g)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("shpkt_get_bignum2 failed: %s", ssh_err(r));
++
++ if (BN_num_bits(p) < min || BN_num_bits(p) > max)
++ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d",
++ min, BN_num_bits(p), max);
++
++ if ((kex->dh = dh_new_group(g, p)) == NULL)
++ fatal("dn_new_group() failed");
++ p = g = NULL; /* belong to kex->dh now */
++
++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
++ goto out;
++ DH_get0_key(kex->dh, &pub_key, NULL);
++
++ token_ptr = GSS_C_NO_BUFFER;
++
++ do {
++ /* Step 2 - call GSS_Init_sec_context() */
++ debug("Calling gss_init_sec_context");
++
++ maj_status = ssh_gssapi_init_ctx(ctxt,
++ kex->gss_deleg_creds, token_ptr, &send_tok,
++ &ret_flags);
++
++ if (GSS_ERROR(maj_status)) {
++ /* XXX Useles code: Missing send? */
++ if (send_tok.length != 0) {
++ if ((r = sshpkt_start(ssh,
++ SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value,
++ send_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ fatal("gss_init_context failed");
++ }
++
++ /* If we've got an old receive buffer get rid of it */
++ if (token_ptr != GSS_C_NO_BUFFER)
++ gss_release_buffer(&min_status, &recv_tok);
++
++ if (maj_status == GSS_S_COMPLETE) {
++ /* If mutual state flag is not true, kex fails */
++ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++ fatal("Mutual authentication failed");
++
++ /* If integ avail flag is not true kex fails */
++ if (!(ret_flags & GSS_C_INTEG_FLAG))
++ fatal("Integrity check failed");
++ }
++
++ /*
++ * If we have data to send, then the last message that we
++ * received cannot have been a 'complete'.
++ */
++ if (send_tok.length != 0) {
++ if (first) {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value,
++ send_tok.length)) != 0 ||
++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ first = 0;
++ } else {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh,send_tok.value,
++ send_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ if ((r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt_send failed: %s", ssh_err(r));
++ gss_release_buffer(&min_status, &send_tok);
++
++ /* If we've sent them data, they should reply */
++ do {
++ type = ssh_packet_read(ssh);
++ if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
++ debug("Received KEXGSS_HOSTKEY");
++ if (server_host_key_blob)
++ fatal("Server host key received more than once");
++ if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ } while (type == SSH2_MSG_KEXGSS_HOSTKEY);
++
++ switch (type) {
++ case SSH2_MSG_KEXGSS_CONTINUE:
++ debug("Received GSSAPI_CONTINUE");
++ if (maj_status == GSS_S_COMPLETE)
++ fatal("GSSAPI Continue received from server when complete");
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ break;
++ case SSH2_MSG_KEXGSS_COMPLETE:
++ debug("Received GSSAPI_COMPLETE");
++ if (msg_tok.value != NULL)
++ fatal("Received GSSAPI_COMPLETE twice?");
++ if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &msg_tok)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ /* Is there a token included? */
++ if ((r = sshpkt_get_u8(ssh, &c)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ if (c) {
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
++ ssh, &recv_tok)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ /* If we're already complete - protocol error */
++ if (maj_status == GSS_S_COMPLETE)
++ sshpkt_disconnect(ssh, "Protocol error: received token when complete");
++ } else {
++ /* No token included */
++ if (maj_status != GSS_S_COMPLETE)
++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
++ }
++ break;
++ case SSH2_MSG_KEXGSS_ERROR:
++ debug("Received Error");
++ if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
++ (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
++ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ fatal("GSSAPI Error: \n%.400s", msg);
++ default:
++ sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
++ type);
++ }
++ token_ptr = &recv_tok;
++ } else {
++ /* No data, and not complete */
++ if (maj_status != GSS_S_COMPLETE)
++ fatal("Not complete, and no token output");
++ }
++ } while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++ /*
++ * We _must_ have received a COMPLETE message in reply from the
++ * server, which will have set dh_server_pub and msg_tok
++ */
++
++ if (type != SSH2_MSG_KEXGSS_COMPLETE)
++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
++
++ /* 7. C verifies that the key Q_S is valid */
++ /* 8. C computes shared secret */
++ if ((buf = sshbuf_new()) == NULL ||
++ (r = sshbuf_put_stringb(buf, server_blob)) != 0 ||
++ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0)
++ goto out;
++ sshbuf_free(buf);
++ buf = NULL;
++
++ if ((shared_secret = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++
++ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0)
++ goto out;
++ if ((empty = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++
++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++ hashlen = sizeof(hash);
++ if ((r = kexgex_hash(
++ kex->hash_alg,
++ kex->client_version,
++ kex->server_version,
++ kex->my,
++ kex->peer,
++ (server_host_key_blob ? server_host_key_blob : empty),
++ kex->min, kex->nbits, kex->max,
++ dh_p, dh_g,
++ pub_key,
++ dh_server_pub,
++ sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
++ hash, &hashlen)) != 0)
++ fatal("Failed to calculate hash: %s", ssh_err(r));
++
++ gssbuf.value = hash;
++ gssbuf.length = hashlen;
++
++ /* Verify that the hash matches the MIC we just got. */
++ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
++
++ gss_release_buffer(&min_status, &msg_tok);
++
++ if (kex->gss_deleg_creds)
++ ssh_gssapi_credentials_updated(ctxt);
++
++ if (gss_kex_context == NULL)
++ gss_kex_context = ctxt;
++ else
++ ssh_gssapi_delete_ctx(&ctxt);
++
++ /* Finally derive the keys and send them */
++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++ r = kex_send_newkeys(ssh);
++out:
++ sshbuf_free(buf);
++ sshbuf_free(server_blob);
++ sshbuf_free(empty);
++ explicit_bzero(hash, sizeof(hash));
++ DH_free(kex->dh);
++ kex->dh = NULL;
++ BN_clear_free(dh_server_pub);
++ sshbuf_free(shared_secret);
++ sshbuf_free(server_host_key_blob);
++ return r;
++}
++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
+diff --git a/kexgsss.c b/kexgsss.c
+new file mode 100644
+index 000000000..a2c02148b
+--- /dev/null
++++ b/kexgsss.c
+@@ -0,0 +1,474 @@
++/*
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++
++#include <string.h>
++
++#include <openssl/crypto.h>
++#include <openssl/bn.h>
++
++#include "xmalloc.h"
++#include "sshbuf.h"
++#include "ssh2.h"
++#include "sshkey.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "ssh-gss.h"
++#include "monitor_wrap.h"
++#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */
++#include "servconf.h"
++#include "ssh-gss.h"
++#include "digest.h"
++#include "ssherr.h"
++
++extern ServerOptions options;
++
++int
++kexgss_server(struct ssh *ssh)
++{
++ struct kex *kex = ssh->kex;
++ OM_uint32 maj_status, min_status;
++
++ /*
++ * Some GSSAPI implementations use the input value of ret_flags (an
++ * output variable) as a means of triggering mechanism specific
++ * features. Initializing it to zero avoids inadvertently
++ * activating this non-standard behaviour.
++ */
++
++ OM_uint32 ret_flags = 0;
++ gss_buffer_desc gssbuf, recv_tok, msg_tok;
++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
++ Gssctxt *ctxt = NULL;
++ struct sshbuf *shared_secret = NULL;
++ struct sshbuf *client_pubkey = NULL;
++ struct sshbuf *server_pubkey = NULL;
++ struct sshbuf *empty = sshbuf_new();
++ int type = 0;
++ gss_OID oid;
++ char *mechs;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ size_t hashlen;
++ int r;
++
++ /* Initialise GSSAPI */
++
++ /* If we're rekeying, privsep means that some of the private structures
++ * in the GSSAPI code are no longer available. This kludges them back
++ * into life
++ */
++ if (!ssh_gssapi_oid_table_ok()) {
++ mechs = ssh_gssapi_server_mechanisms();
++ free(mechs);
++ }
++
++ debug2_f("Identifying %s", kex->name);
++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++ if (oid == GSS_C_NO_OID)
++ fatal("Unknown gssapi mechanism");
++
++ debug2_f("Acquiring credentials");
++
++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
++ fatal("Unable to acquire credentials for the server");
++
++ do {
++ debug("Wait SSH2_MSG_KEXGSS_INIT");
++ type = ssh_packet_read(ssh);
++ switch(type) {
++ case SSH2_MSG_KEXGSS_INIT:
++ if (client_pubkey != NULL)
++ fatal("Received KEXGSS_INIT after initialising");
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ switch (kex->kex_type) {
++ case KEX_GSS_GRP1_SHA1:
++ case KEX_GSS_GRP14_SHA1:
++ case KEX_GSS_GRP14_SHA256:
++ case KEX_GSS_GRP16_SHA512:
++ r = kex_dh_enc(kex, client_pubkey, &server_pubkey,
++ &shared_secret);
++ break;
++ case KEX_GSS_NISTP256_SHA256:
++ r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey,
++ &shared_secret);
++ break;
++ case KEX_GSS_C25519_SHA256:
++ r = kex_c25519_enc(kex, client_pubkey, &server_pubkey,
++ &shared_secret);
++ break;
++ default:
++ fatal_f("Unexpected KEX type %d", kex->kex_type);
++ }
++ if (r != 0)
++ goto out;
++
++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
++ break;
++ case SSH2_MSG_KEXGSS_CONTINUE:
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ break;
++ default:
++ sshpkt_disconnect(ssh,
++ "Protocol error: didn't expect packet type %d",
++ type);
++ }
++
++ maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
++ &send_tok, &ret_flags));
++
++ gss_release_buffer(&min_status, &recv_tok);
++
++ if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
++ fatal("Zero length token output when incomplete");
++
++ if (client_pubkey == NULL)
++ fatal("No client public key");
++
++ if (maj_status & GSS_S_CONTINUE_NEEDED) {
++ debug("Sending GSSAPI_CONTINUE");
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ gss_release_buffer(&min_status, &send_tok);
++ }
++ } while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++ if (GSS_ERROR(maj_status)) {
++ if (send_tok.length > 0) {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ fatal("accept_ctx died");
++ }
++
++ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++ fatal("Mutual Authentication flag wasn't set");
++
++ if (!(ret_flags & GSS_C_INTEG_FLAG))
++ fatal("Integrity flag wasn't set");
++
++ hashlen = sizeof(hash);
++ if ((r = kex_gen_hash(
++ kex->hash_alg,
++ kex->client_version,
++ kex->server_version,
++ kex->peer,
++ kex->my,
++ empty,
++ client_pubkey,
++ server_pubkey,
++ shared_secret,
++ hash, &hashlen)) != 0)
++ goto out;
++
++ gssbuf.value = hash;
++ gssbuf.length = hashlen;
++
++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
++ fatal("Couldn't get MIC");
++
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
++ (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 ||
++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ if (send_tok.length != 0) {
++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ } else {
++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ if ((r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt_send failed: %s", ssh_err(r));
++
++ gss_release_buffer(&min_status, &send_tok);
++ gss_release_buffer(&min_status, &msg_tok);
++
++ if (gss_kex_context == NULL)
++ gss_kex_context = ctxt;
++ else
++ ssh_gssapi_delete_ctx(&ctxt);
++
++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++ r = kex_send_newkeys(ssh);
++
++ /* If this was a rekey, then save out any delegated credentials we
++ * just exchanged. */
++ if (options.gss_store_rekey)
++ ssh_gssapi_rekey_creds();
++out:
++ sshbuf_free(empty);
++ explicit_bzero(hash, sizeof(hash));
++ sshbuf_free(shared_secret);
++ sshbuf_free(client_pubkey);
++ sshbuf_free(server_pubkey);
++ return r;
++}
++
++int
++kexgssgex_server(struct ssh *ssh)
++{
++ struct kex *kex = ssh->kex;
++ OM_uint32 maj_status, min_status;
++
++ /*
++ * Some GSSAPI implementations use the input value of ret_flags (an
++ * output variable) as a means of triggering mechanism specific
++ * features. Initializing it to zero avoids inadvertently
++ * activating this non-standard behaviour.
++ */
++
++ OM_uint32 ret_flags = 0;
++ gss_buffer_desc gssbuf, recv_tok, msg_tok;
++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
++ Gssctxt *ctxt = NULL;
++ struct sshbuf *shared_secret = NULL;
++ int type = 0;
++ gss_OID oid;
++ char *mechs;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ size_t hashlen;
++ BIGNUM *dh_client_pub = NULL;
++ const BIGNUM *pub_key, *dh_p, *dh_g;
++ int min = -1, max = -1, nbits = -1;
++ int cmin = -1, cmax = -1; /* client proposal */
++ struct sshbuf *empty = sshbuf_new();
++ int r;
++
++ /* Initialise GSSAPI */
++
++ /* If we're rekeying, privsep means that some of the private structures
++ * in the GSSAPI code are no longer available. This kludges them back
++ * into life
++ */
++ if (!ssh_gssapi_oid_table_ok())
++ if ((mechs = ssh_gssapi_server_mechanisms()))
++ free(mechs);
++
++ debug2_f("Identifying %s", kex->name);
++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++ if (oid == GSS_C_NO_OID)
++ fatal("Unknown gssapi mechanism");
++
++ debug2_f("Acquiring credentials");
++
++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
++ fatal("Unable to acquire credentials for the server");
++
++ /* 5. S generates an ephemeral key pair (do the allocations early) */
++ debug("Doing group exchange");
++ ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ);
++ /* store client proposal to provide valid signature */
++ if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 ||
++ (r = sshpkt_get_u32(ssh, &nbits)) != 0 ||
++ (r = sshpkt_get_u32(ssh, &cmax)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ kex->nbits = nbits;
++ kex->min = cmin;
++ kex->max = cmax;
++ min = MAX(DH_GRP_MIN, cmin);
++ max = MIN(DH_GRP_MAX, cmax);
++ nbits = MAXIMUM(DH_GRP_MIN, nbits);
++ nbits = MINIMUM(DH_GRP_MAX, nbits);
++ if (max < min || nbits < min || max < nbits)
++ fatal("GSS_GEX, bad parameters: %d !< %d !< %d",
++ min, nbits, max);
++ kex->dh = PRIVSEP(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");
++ }
++
++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 ||
++ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
++ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ if ((r = ssh_packet_write_wait(ssh)) != 0)
++ fatal("ssh_packet_write_wait: %s", ssh_err(r));
++
++ /* Compute our exchange value in parallel with the client */
++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
++ goto out;
++
++ do {
++ debug("Wait SSH2_MSG_GSSAPI_INIT");
++ type = ssh_packet_read(ssh);
++ switch(type) {
++ case SSH2_MSG_KEXGSS_INIT:
++ if (dh_client_pub != NULL)
++ fatal("Received KEXGSS_INIT after initialising");
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
++ break;
++ case SSH2_MSG_KEXGSS_CONTINUE:
++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++ &recv_tok)) != 0 ||
++ (r = sshpkt_get_end(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ break;
++ default:
++ sshpkt_disconnect(ssh,
++ "Protocol error: didn't expect packet type %d",
++ type);
++ }
++
++ maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
++ &send_tok, &ret_flags));
++
++ gss_release_buffer(&min_status, &recv_tok);
++
++ if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
++ fatal("Zero length token output when incomplete");
++
++ if (dh_client_pub == NULL)
++ fatal("No client public key");
++
++ if (maj_status & GSS_S_CONTINUE_NEEDED) {
++ debug("Sending GSSAPI_CONTINUE");
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ gss_release_buffer(&min_status, &send_tok);
++ }
++ } while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++ if (GSS_ERROR(maj_status)) {
++ if (send_tok.length > 0) {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ fatal("accept_ctx died");
++ }
++
++ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++ fatal("Mutual Authentication flag wasn't set");
++
++ if (!(ret_flags & GSS_C_INTEG_FLAG))
++ fatal("Integrity flag wasn't set");
++
++ /* calculate shared secret */
++ if ((shared_secret = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++ if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0)
++ goto out;
++
++ DH_get0_key(kex->dh, &pub_key, NULL);
++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++ hashlen = sizeof(hash);
++ if ((r = kexgex_hash(
++ kex->hash_alg,
++ kex->client_version,
++ kex->server_version,
++ kex->peer,
++ kex->my,
++ empty,
++ cmin, nbits, cmax,
++ dh_p, dh_g,
++ dh_client_pub,
++ pub_key,
++ sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
++ hash, &hashlen)) != 0)
++ fatal("kexgex_hash failed: %s", ssh_err(r));
++
++ gssbuf.value = hash;
++ gssbuf.length = hashlen;
++
++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
++ fatal("Couldn't get MIC");
++
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 ||
++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ if (send_tok.length != 0) {
++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ } else {
++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
++ fatal("sshpkt failed: %s", ssh_err(r));
++ }
++ if ((r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++
++ gss_release_buffer(&min_status, &send_tok);
++ gss_release_buffer(&min_status, &msg_tok);
++
++ if (gss_kex_context == NULL)
++ gss_kex_context = ctxt;
++ else
++ ssh_gssapi_delete_ctx(&ctxt);
++
++ /* Finally derive the keys and send them */
++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++ r = kex_send_newkeys(ssh);
++
++ /* If this was a rekey, then save out any delegated credentials we
++ * just exchanged. */
++ if (options.gss_store_rekey)
++ ssh_gssapi_rekey_creds();
++out:
++ sshbuf_free(empty);
++ explicit_bzero(hash, sizeof(hash));
++ DH_free(kex->dh);
++ kex->dh = NULL;
++ BN_clear_free(dh_client_pub);
++ sshbuf_free(shared_secret);
++ return r;
++}
++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
+diff --git a/monitor.c b/monitor.c
+index 91e0e6245..5f7df9539 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -145,6 +145,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *);
+ 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 *);
++int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *);
+ #endif
+
+ #ifdef SSH_AUDIT_EVENTS
+@@ -217,11 +219,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},
++ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign},
+ #endif
+ {0, 0, NULL}
+ };
+
+ struct mon_table mon_dispatch_postauth20[] = {
++#ifdef GSSAPI
++ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx},
++ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
++ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign},
++ {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds},
++#endif
+ #ifdef WITH_OPENSSL
+ {MONITOR_REQ_MODULI, 0, mm_answer_moduli},
+ #endif
+@@ -290,6 +299,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
+ /* Permit requests for moduli and signatures */
+ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
++#ifdef GSSAPI
++ /* and for the GSSAPI key exchange */
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++#endif
+
+ /* The first few requests do not require asynchronous access */
+ while (!authenticated) {
+@@ -401,6 +414,10 @@ monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor)
+ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1);
++#ifdef GSSAPI
++ /* and for the GSSAPI key exchange */
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++#endif
+
+ if (auth_opts->permit_pty_flag) {
+ monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
+@@ -1744,6 +1761,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ # ifdef OPENSSL_HAS_ECC
+ kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
+ # endif
++# ifdef GSSAPI
++ if (options.gss_keyex) {
++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server;
++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server;
++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server;
++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server;
++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server;
++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
++ }
++# endif
+ #endif /* WITH_OPENSSL */
+ kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+@@ -1836,8 +1864,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ u_char *p;
+ int r;
+
+- if (!options.gss_authentication)
+- fatal_f("GSSAPI authentication not enabled");
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
+
+ if ((r = sshbuf_get_string(m, &p, &len)) != 0)
+ fatal_fr(r, "parse");
+@@ -1869,8 +1897,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ OM_uint32 flags = 0; /* GSI needs this */
+ int r;
+
+- if (!options.gss_authentication)
+- fatal_f("GSSAPI authentication not enabled");
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
+
+ if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0)
+ fatal_fr(r, "ssh_gssapi_get_buffer_desc");
+@@ -1890,6 +1918,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0);
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1);
+ }
+ return (0);
+ }
+@@ -1901,8 +1930,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+ OM_uint32 ret;
+ int r;
+
+- if (!options.gss_authentication)
+- fatal_f("GSSAPI authentication not enabled");
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
+
+ if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 ||
+ (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0)
+@@ -1928,13 +1957,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+ int
+ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ {
+- int r, authenticated;
++ int r, authenticated, kex;
+ const char *displayname;
+
+- if (!options.gss_authentication)
+- fatal_f("GSSAPI authentication not enabled");
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
+
+- authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user);
++ if ((r = sshbuf_get_u32(m, &kex)) != 0)
++ fatal_fr(r, "buffer error");
++
++ authenticated = authctxt->valid &&
++ ssh_gssapi_userok(authctxt->user, authctxt->pw, kex);
+
+ sshbuf_reset(m);
+ if ((r = sshbuf_put_u32(m, authenticated)) != 0)
+@@ -1943,7 +1976,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ debug3_f("sending result %d", authenticated);
+ mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m);
+
+- auth_method = "gssapi-with-mic";
++ if (kex) {
++ auth_method = "gssapi-keyex";
++ } else {
++ auth_method = "gssapi-with-mic";
++ }
+
+ if ((displayname = ssh_gssapi_displayname()) != NULL)
+ auth2_record_info(authctxt, "%s", displayname);
+@@ -1951,5 +1988,83 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ /* Monitor loop will terminate if authenticated */
+ return (authenticated);
+ }
+-#endif /* GSSAPI */
+
++int
++mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++ gss_buffer_desc data;
++ gss_buffer_desc hash = GSS_C_EMPTY_BUFFER;
++ OM_uint32 major, minor;
++ size_t len;
++ u_char *p = NULL;
++ int r;
++
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
++
++ if ((r = sshbuf_get_string(m, &p, &len)) != 0)
++ fatal_fr(r, "buffer error");
++ data.value = p;
++ data.length = len;
++ /* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */
++ if (data.length != 20 && data.length != 32 && data.length != 64)
++ fatal_f("data length incorrect: %d", (int) data.length);
++
++ /* Save the session ID on the first time around */
++ if (session_id2_len == 0) {
++ session_id2_len = data.length;
++ session_id2 = xmalloc(session_id2_len);
++ memcpy(session_id2, data.value, session_id2_len);
++ }
++ major = ssh_gssapi_sign(gsscontext, &data, &hash);
++
++ free(data.value);
++
++ sshbuf_reset(m);
++
++ if ((r = sshbuf_put_u32(m, major)) != 0 ||
++ (r = sshbuf_put_string(m, hash.value, hash.length)) != 0)
++ fatal_fr(r, "buffer error");
++
++ mm_request_send(socket, MONITOR_ANS_GSSSIGN, m);
++
++ gss_release_buffer(&minor, &hash);
++
++ /* Turn on getpwnam permissions */
++ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1);
++
++ /* And credential updating, for when rekeying */
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1);
++
++ return (0);
++}
++
++int
++mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
++ ssh_gssapi_ccache store;
++ int r, ok;
++
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal_f("GSSAPI not enabled");
++
++ if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 ||
++ (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 ||
++ (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0)
++ fatal_fr(r, "buffer error");
++
++ ok = ssh_gssapi_update_creds(&store);
++
++ free(store.filename);
++ free(store.envvar);
++ free(store.envval);
++
++ sshbuf_reset(m);
++ if ((r = sshbuf_put_u32(m, ok)) != 0)
++ fatal_fr(r, "buffer error");
++
++ mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m);
++
++ return(0);
++}
++
++#endif /* GSSAPI */
+diff --git a/monitor.h b/monitor.h
+index 683e5e071..2b1a2d590 100644
+--- a/monitor.h
++++ b/monitor.h
+@@ -63,6 +63,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,
+
++ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
++ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
+ };
+
+ struct ssh;
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index 8e379a15c..a4b66c56f 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -1000,13 +1000,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
+ }
+
+ int
+-mm_ssh_gssapi_userok(char *user)
++mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ struct sshbuf *m;
+ int r, authenticated = 0;
+
+ if ((m = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
++ if ((r = sshbuf_put_u32(m, kex)) != 0)
++ fatal_fr(r, "buffer error");
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+@@ -1019,4 +1021,57 @@ mm_ssh_gssapi_userok(char *user)
+ debug3_f("user %sauthenticated", authenticated ? "" : "not ");
+ return (authenticated);
+ }
++
++OM_uint32
++mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash)
++{
++ struct sshbuf *m;
++ OM_uint32 major;
++ int r;
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++ if ((r = sshbuf_put_string(m, data->value, data->length)) != 0)
++ fatal_fr(r, "buffer error");
++
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m);
++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m);
++
++ if ((r = sshbuf_get_u32(m, &major)) != 0 ||
++ (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0)
++ fatal_fr(r, "buffer error");
++
++ sshbuf_free(m);
++
++ return (major);
++}
++
++int
++mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store)
++{
++ struct sshbuf *m;
++ int r, ok;
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ if ((r = sshbuf_put_cstring(m,
++ store->filename ? store->filename : "")) != 0 ||
++ (r = sshbuf_put_cstring(m,
++ store->envvar ? store->envvar : "")) != 0 ||
++ (r = sshbuf_put_cstring(m,
++ store->envval ? store->envval : "")) != 0)
++ fatal_fr(r, "buffer error");
++
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m);
++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m);
++
++ if ((r = sshbuf_get_u32(m, &ok)) != 0)
++ fatal_fr(r, "buffer error");
++
++ sshbuf_free(m);
++
++ return (ok);
++}
++
+ #endif /* GSSAPI */
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index 0df49c25b..830fdb308 100644
+--- a/monitor_wrap.h
++++ b/monitor_wrap.h
+@@ -65,8 +65,10 @@ int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t,
+ OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID);
+ OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *,
+ gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *);
+-int mm_ssh_gssapi_userok(char *user);
++int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex);
+ OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
++OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
++int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *);
+ #endif
+
+ #ifdef USE_PAM
+diff --git a/readconf.c b/readconf.c
+index cf7949884..55af0115d 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -67,6 +67,7 @@
+ #include "uidswap.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "ssh-gss.h"
+
+ /* Format of the configuration file:
+
+@@ -161,6 +162,8 @@ typedef enum {
+ oClearAllForwardings, oNoHostAuthenticationForLocalhost,
+ oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
+ oAddressFamily, oGssAuthentication, oGssDelegateCreds,
++ oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey,
++ oGssServerIdentity, oGssKexAlgorithms,
+ oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
+ oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist,
+ oHashKnownHosts,
+@@ -207,10 +210,22 @@ static struct {
+ /* Sometimes-unsupported options */
+ #if defined(GSSAPI)
+ { "gssapiauthentication", oGssAuthentication },
++ { "gssapikeyexchange", oGssKeyEx },
+ { "gssapidelegatecredentials", oGssDelegateCreds },
++ { "gssapitrustdns", oGssTrustDns },
++ { "gssapiclientidentity", oGssClientIdentity },
++ { "gssapiserveridentity", oGssServerIdentity },
++ { "gssapirenewalforcesrekey", oGssRenewalRekey },
++ { "gssapikexalgorithms", oGssKexAlgorithms },
+ # else
+ { "gssapiauthentication", oUnsupported },
++ { "gssapikeyexchange", oUnsupported },
+ { "gssapidelegatecredentials", oUnsupported },
++ { "gssapitrustdns", oUnsupported },
++ { "gssapiclientidentity", oUnsupported },
++ { "gssapiserveridentity", oUnsupported },
++ { "gssapirenewalforcesrekey", oUnsupported },
++ { "gssapikexalgorithms", oUnsupported },
+ #endif
+ #ifdef ENABLE_PKCS11
+ { "pkcs11provider", oPKCS11Provider },
+@@ -1122,10 +1137,46 @@ parse_time:
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+
++ case oGssKeyEx:
++ intptr = &options->gss_keyex;
++ goto parse_flag;
++
+ case oGssDelegateCreds:
+ intptr = &options->gss_deleg_creds;
+ goto parse_flag;
+
++ case oGssTrustDns:
++ intptr = &options->gss_trust_dns;
++ goto parse_flag;
++
++ case oGssClientIdentity:
++ charptr = &options->gss_client_identity;
++ goto parse_string;
++
++ case oGssServerIdentity:
++ charptr = &options->gss_server_identity;
++ goto parse_string;
++
++ case oGssRenewalRekey:
++ intptr = &options->gss_renewal_rekey;
++ goto parse_flag;
++
++ case oGssKexAlgorithms:
++ arg = argv_next(&ac, &av);
++ if (!arg || *arg == '\0') {
++ error("%.200s line %d: Missing argument.",
++ filename, linenum);
++ goto out;
++ }
++ if (!kex_gss_names_valid(arg)) {
++ error("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.",
++ filename, linenum, arg ? arg : "<NONE>");
++ goto out;
++ }
++ if (*activep && options->gss_kex_algorithms == NULL)
++ options->gss_kex_algorithms = xstrdup(arg);
++ break;
++
+ case oBatchMode:
+ intptr = &options->batch_mode;
+ goto parse_flag;
+@@ -2343,7 +2394,13 @@ initialize_options(Options * options)
+ options->fwd_opts.streamlocal_bind_unlink = -1;
+ options->pubkey_authentication = -1;
+ options->gss_authentication = -1;
++ options->gss_keyex = -1;
+ options->gss_deleg_creds = -1;
++ options->gss_trust_dns = -1;
++ options->gss_renewal_rekey = -1;
++ options->gss_client_identity = NULL;
++ options->gss_server_identity = NULL;
++ options->gss_kex_algorithms = NULL;
+ options->password_authentication = -1;
+ options->kbd_interactive_authentication = -1;
+ options->kbd_interactive_devices = NULL;
+@@ -2502,8 +2559,18 @@ fill_default_options(Options * options)
+ options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL;
+ if (options->gss_authentication == -1)
+ options->gss_authentication = 0;
++ if (options->gss_keyex == -1)
++ options->gss_keyex = 0;
+ if (options->gss_deleg_creds == -1)
+ options->gss_deleg_creds = 0;
++ if (options->gss_trust_dns == -1)
++ options->gss_trust_dns = 0;
++ if (options->gss_renewal_rekey == -1)
++ options->gss_renewal_rekey = 0;
++#ifdef GSSAPI
++ if (options->gss_kex_algorithms == NULL)
++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
++#endif
+ if (options->password_authentication == -1)
+ options->password_authentication = 1;
+ if (options->kbd_interactive_authentication == -1)
+@@ -3301,7 +3368,14 @@ dump_client_config(Options *o, const char *host)
+ dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports);
+ #ifdef GSSAPI
+ dump_cfg_fmtint(oGssAuthentication, o->gss_authentication);
++ dump_cfg_fmtint(oGssKeyEx, o->gss_keyex);
+ dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds);
++ dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns);
++ dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey);
++ dump_cfg_string(oGssClientIdentity, o->gss_client_identity);
++ dump_cfg_string(oGssServerIdentity, o->gss_server_identity);
++ dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ?
++ o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX);
+ #endif /* GSSAPI */
+ dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts);
+ dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication);
+diff --git a/readconf.h b/readconf.h
+index 2ce1b4c33..d0eb92362 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -40,7 +40,13 @@ typedef struct {
+ int pubkey_authentication; /* Try ssh2 pubkey authentication. */
+ int hostbased_authentication; /* ssh2's rhosts_rsa */
+ int gss_authentication; /* Try GSS authentication */
++ int gss_keyex; /* Try GSS key exchange */
+ int gss_deleg_creds; /* Delegate GSS credentials */
++ int gss_trust_dns; /* Trust DNS for GSS canonicalization */
++ int gss_renewal_rekey; /* Credential renewal forces rekey */
++ char *gss_client_identity; /* Principal to initiate GSSAPI with */
++ char *gss_server_identity; /* GSSAPI target principal */
++ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */
+ int password_authentication; /* Try password
+ * authentication. */
+ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
+diff --git a/servconf.c b/servconf.c
+index 2e039da8b..940de9e15 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -70,6 +70,7 @@
+ #include "auth.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "ssh-gss.h"
+
+ static void add_listen_addr(ServerOptions *, const char *,
+ const char *, int);
+@@ -136,8 +137,11 @@ initialize_server_options(ServerOptions *options)
+ options->kerberos_ticket_cleanup = -1;
+ options->kerberos_get_afs_token = -1;
+ options->gss_authentication=-1;
++ options->gss_keyex = -1;
+ options->gss_cleanup_creds = -1;
+ options->gss_strict_acceptor = -1;
++ options->gss_store_rekey = -1;
++ options->gss_kex_algorithms = NULL;
+ options->password_authentication = -1;
+ options->kbd_interactive_authentication = -1;
+ options->permit_empty_passwd = -1;
+@@ -360,10 +364,18 @@ fill_default_server_options(ServerOptions *options)
+ options->kerberos_get_afs_token = 0;
+ if (options->gss_authentication == -1)
+ options->gss_authentication = 0;
++ if (options->gss_keyex == -1)
++ options->gss_keyex = 0;
+ if (options->gss_cleanup_creds == -1)
+ options->gss_cleanup_creds = 1;
+ if (options->gss_strict_acceptor == -1)
+ options->gss_strict_acceptor = 1;
++ if (options->gss_store_rekey == -1)
++ options->gss_store_rekey = 0;
++#ifdef GSSAPI
++ if (options->gss_kex_algorithms == NULL)
++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
++#endif
+ if (options->password_authentication == -1)
+ options->password_authentication = 1;
+ if (options->kbd_interactive_authentication == -1)
+@@ -520,6 +532,7 @@ typedef enum {
+ sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
+ sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
+ sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
++ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
+ sAcceptEnv, sSetEnv, sPermitTunnel,
+ sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
+ sUsePrivilegeSeparation, sAllowAgentForwarding,
+@@ -602,12 +615,22 @@ static struct {
+ #ifdef GSSAPI
+ { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL },
+ { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
++ { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL },
+ { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
++ { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
++ { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
++ { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
+ #else
+ { "gssapiauthentication", sUnsupported, SSHCFG_ALL },
+ { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
++ { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
+ { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
++ { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
++ { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
++ { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
+ #endif
++ { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
++ { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
+ { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL },
+ { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
+ { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */
+@@ -1644,6 +1667,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+
++ case sGssKeyEx:
++ intptr = &options->gss_keyex;
++ goto parse_flag;
++
+ case sGssCleanupCreds:
+ intptr = &options->gss_cleanup_creds;
+ goto parse_flag;
+@@ -1652,6 +1679,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ intptr = &options->gss_strict_acceptor;
+ goto parse_flag;
+
++ case sGssStoreRekey:
++ intptr = &options->gss_store_rekey;
++ goto parse_flag;
++
++ case sGssKexAlgorithms:
++ arg = argv_next(&ac, &av);
++ if (!arg || *arg == '\0')
++ fatal("%.200s line %d: Missing argument.",
++ filename, linenum);
++ if (!kex_gss_names_valid(arg))
++ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.",
++ filename, linenum, arg ? arg : "<NONE>");
++ if (*activep && options->gss_kex_algorithms == NULL)
++ options->gss_kex_algorithms = xstrdup(arg);
++ break;
++
+ case sPasswordAuthentication:
+ intptr = &options->password_authentication;
+ goto parse_flag;
+@@ -3018,6 +3061,10 @@ dump_config(ServerOptions *o)
+ #ifdef GSSAPI
+ dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
++ dump_cfg_fmtint(sGssKeyEx, o->gss_keyex);
++ dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
++ dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
++ dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
+ #endif
+ dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index 7ad43de87..38a68d576 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -141,8 +141,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 */
++ int gss_keyex; /* If true, permit GSSAPI key exchange */
+ int gss_cleanup_creds; /* If true, destroy cred cache on logout */
+ 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. */
+ int password_authentication; /* If true, permit password
+ * authentication. */
+ int kbd_interactive_authentication; /* If true, permit */
+diff --git a/session.c b/session.c
+index f30d7ac51..96960f2da 100644
+--- a/session.c
++++ b/session.c
+@@ -2680,13 +2680,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+
+ #ifdef KRB5
+ if (options.kerberos_ticket_cleanup &&
+- authctxt->krb5_ctx)
++ authctxt->krb5_ctx) {
++ temporarily_use_uid(authctxt->pw);
+ krb5_cleanup_proc(authctxt);
++ restore_uid();
++ }
+ #endif
+
+ #ifdef GSSAPI
+- if (options.gss_cleanup_creds)
++ if (options.gss_cleanup_creds) {
++ temporarily_use_uid(authctxt->pw);
+ ssh_gssapi_cleanup_creds();
++ restore_uid();
++ }
+ #endif
+
+ /* remove agent socket */
+diff --git a/ssh-gss.h b/ssh-gss.h
+index a8af117d2..6303ce185 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -1,6 +1,6 @@
+ /* $OpenBSD: ssh-gss.h,v 1.15 2021/01/27 10:05:28 djm Exp $ */
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+@@ -61,10 +61,34 @@
+
+ #define SSH_GSS_OIDTYPE 0x06
+
++#define SSH2_MSG_KEXGSS_INIT 30
++#define SSH2_MSG_KEXGSS_CONTINUE 31
++#define SSH2_MSG_KEXGSS_COMPLETE 32
++#define SSH2_MSG_KEXGSS_HOSTKEY 33
++#define SSH2_MSG_KEXGSS_ERROR 34
++#define SSH2_MSG_KEXGSS_GROUPREQ 40
++#define SSH2_MSG_KEXGSS_GROUP 41
++#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-"
++#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-"
++#define KEX_GSS_GRP14_SHA256_ID "gss-group14-sha256-"
++#define KEX_GSS_GRP16_SHA512_ID "gss-group16-sha512-"
++#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-"
++#define KEX_GSS_NISTP256_SHA256_ID "gss-nistp256-sha256-"
++#define KEX_GSS_C25519_SHA256_ID "gss-curve25519-sha256-"
++
++#define GSS_KEX_DEFAULT_KEX \
++ KEX_GSS_GRP14_SHA256_ID "," \
++ KEX_GSS_GRP16_SHA512_ID "," \
++ KEX_GSS_NISTP256_SHA256_ID "," \
++ KEX_GSS_C25519_SHA256_ID "," \
++ KEX_GSS_GRP14_SHA1_ID "," \
++ KEX_GSS_GEX_SHA1_ID
++
+ typedef struct {
+ char *filename;
+ char *envvar;
+ char *envval;
++ struct passwd *owner;
+ void *data;
+ } ssh_gssapi_ccache;
+
+@@ -72,8 +96,11 @@ typedef struct {
+ gss_buffer_desc displayname;
+ gss_buffer_desc exportedname;
+ gss_cred_id_t creds;
++ gss_name_t name;
+ struct ssh_gssapi_mech_struct *mech;
+ ssh_gssapi_ccache store;
++ int used;
++ int updated;
+ } ssh_gssapi_client;
+
+ typedef struct ssh_gssapi_mech_struct {
+@@ -84,6 +111,7 @@ typedef struct ssh_gssapi_mech_struct {
+ int (*userok) (ssh_gssapi_client *, char *);
+ int (*localname) (ssh_gssapi_client *, char **);
+ void (*storecreds) (ssh_gssapi_client *);
++ int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *);
+ } ssh_gssapi_mech;
+
+ typedef struct {
+@@ -94,10 +122,11 @@ typedef struct {
+ gss_OID oid; /* client */
+ gss_cred_id_t creds; /* server */
+ gss_name_t client; /* server */
+- gss_cred_id_t client_creds; /* server */
++ gss_cred_id_t client_creds; /* both */
+ } Gssctxt;
+
+ extern ssh_gssapi_mech *supported_mechs[];
++extern Gssctxt *gss_kex_context;
+
+ int ssh_gssapi_check_oid(Gssctxt *, void *, size_t);
+ void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t);
+@@ -109,6 +138,7 @@ OM_uint32 ssh_gssapi_test_oid_supported(OM_uint32 *, gss_OID, int *);
+
+ struct sshbuf;
+ int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *);
++int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *);
+
+ OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *);
+ OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int,
+@@ -123,17 +153,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **);
+ OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_buildmic(struct sshbuf *, const char *,
+ const char *, const char *, const struct sshbuf *);
+-int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *);
++int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *);
++OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *);
++int ssh_gssapi_credentials_updated(Gssctxt *);
+
+ /* In the server */
++typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *,
++ const char *);
++char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *);
++char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *,
++ const char *, const char *);
++gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int);
++int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *,
++ const char *);
+ OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID);
+-int ssh_gssapi_userok(char *name);
++int ssh_gssapi_userok(char *name, struct passwd *, int kex);
+ 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);
+ void ssh_gssapi_storecreds(void);
+ const char *ssh_gssapi_displayname(void);
+
++char *ssh_gssapi_server_mechanisms(void);
++int ssh_gssapi_oid_table_ok(void);
++
++int ssh_gssapi_update_creds(ssh_gssapi_ccache *store);
++void ssh_gssapi_rekey_creds(void);
++
+ #endif /* GSSAPI */
+
+ #endif /* _SSH_GSS_H */
+diff --git a/ssh-null.c b/ssh-null.c
+new file mode 100644
+index 000000000..a934bda77
+--- /dev/null
++++ b/ssh-null.c
+@@ -0,0 +1,108 @@
++/*
++ * Copyright (c) 2023 Colin Watson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#ifdef GSSAPI
++
++#include "sshbuf.h"
++#include "ssherr.h"
++#include "sshkey.h"
++
++static int
++ssh_null_equal(const struct sshkey *a, const struct sshkey *b)
++{
++ return 1;
++}
++
++static int
++ssh_null_serialize_public(const struct sshkey *key, struct sshbuf *b,
++ enum sshkey_serialize_rep opts)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static int
++ssh_null_deserialize_public(const char *ktype, struct sshbuf *b,
++ struct sshkey *key)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static int
++ssh_null_serialize_private(const struct sshkey *key, struct sshbuf *b,
++ enum sshkey_serialize_rep opts)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static int
++ssh_null_deserialize_private(const char *ktype, struct sshbuf *b,
++ struct sshkey *key)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static int
++ssh_null_copy_public(const struct sshkey *from, struct sshkey *to)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static int
++ssh_null_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
++ const u_char *data, size_t dlen, const char *alg, u_int compat,
++ struct sshkey_sig_details **detailsp)
++{
++ return SSH_ERR_KEY_TYPE_UNKNOWN;
++}
++
++static const struct sshkey_impl_funcs sshkey_null_funcs = {
++ /* .size = */ NULL,
++ /* .alloc = */ NULL,
++ /* .cleanup = */ NULL,
++ /* .equal = */ ssh_null_equal,
++ /* .ssh_serialize_public = */ ssh_null_serialize_public,
++ /* .ssh_deserialize_public = */ ssh_null_deserialize_public,
++ /* .ssh_serialize_private = */ ssh_null_serialize_private,
++ /* .ssh_deserialize_private = */ ssh_null_deserialize_private,
++ /* .generate = */ NULL,
++ /* .copy_public = */ ssh_null_copy_public,
++ /* .sign = */ NULL,
++ /* .verify = */ ssh_null_verify,
++};
++
++const struct sshkey_impl sshkey_null_impl = {
++ /* .name = */ "null",
++ /* .shortname = */ "null",
++ /* .sigalg = */ NULL,
++ /* .type = */ KEY_NULL,
++ /* .nid = */ 0,
++ /* .cert = */ 0,
++ /* .sigonly = */ 0,
++ /* .keybits = */ 0,
++ /* .funcs = */ &sshkey_null_funcs,
++};
++
++#endif /* GSSAPI */
+diff --git a/ssh.1 b/ssh.1
+index a3d1ba163..dfd80b8bc 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -534,7 +534,13 @@ For full details of the options listed below, and their possible values, see
+ .It GatewayPorts
+ .It GlobalKnownHostsFile
+ .It GSSAPIAuthentication
++.It GSSAPIKeyExchange
++.It GSSAPIClientIdentity
+ .It GSSAPIDelegateCredentials
++.It GSSAPIKexAlgorithms
++.It GSSAPIRenewalForcesRekey
++.It GSSAPIServerIdentity
++.It GSSAPITrustDns
+ .It HashKnownHosts
+ .It Host
+ .It HostbasedAcceptedAlgorithms
+@@ -612,6 +618,8 @@ flag),
+ (supported message integrity codes),
+ .Ar kex
+ (key exchange algorithms),
++.Ar kex-gss
++(GSSAPI key exchange algorithms),
+ .Ar key
+ (key types),
+ .Ar key-cert
+diff --git a/ssh.c b/ssh.c
+index 87454b84a..855777d7f 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -787,6 +787,8 @@ main(int ac, char **av)
+ else if (strcmp(optarg, "kex") == 0 ||
+ strcasecmp(optarg, "KexAlgorithms") == 0)
+ cp = kex_alg_list('\n');
++ else if (strcmp(optarg, "kex-gss") == 0)
++ cp = kex_gss_alg_list('\n');
+ else if (strcmp(optarg, "key") == 0)
+ cp = sshkey_alg_list(0, 0, 0, '\n');
+ else if (strcmp(optarg, "key-cert") == 0)
+@@ -814,8 +816,8 @@ main(int ac, char **av)
+ } else if (strcmp(optarg, "help") == 0) {
+ cp = xstrdup(
+ "cipher\ncipher-auth\ncompression\nkex\n"
+- "key\nkey-cert\nkey-plain\nkey-sig\nmac\n"
+- "protocol-version\nsig");
++ "kex-gss\nkey\nkey-cert\nkey-plain\n"
++ "key-sig\nmac\nprotocol-version\nsig");
+ }
+ if (cp == NULL)
+ fatal("Unsupported query \"%s\"", optarg);
+diff --git a/ssh_config b/ssh_config
+index 842ea866c..52aae8692 100644
+--- a/ssh_config
++++ b/ssh_config
+@@ -24,6 +24,8 @@
+ # HostbasedAuthentication no
+ # GSSAPIAuthentication no
+ # GSSAPIDelegateCredentials no
++# GSSAPIKeyExchange no
++# GSSAPITrustDNS no
+ # BatchMode no
+ # CheckHostIP yes
+ # AddressFamily any
+diff --git a/ssh_config.5 b/ssh_config.5
+index 9eb6b9779..43f3b642c 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -839,10 +839,67 @@ The default is
+ Specifies whether user authentication based on GSSAPI is allowed.
+ The default is
+ .Cm no .
++.It Cm GSSAPIClientIdentity
++If set, specifies the GSSAPI client identity that ssh should use when
++connecting to the server. The default is unset, which means that the default
++identity will be used.
+ .It Cm GSSAPIDelegateCredentials
+ Forward (delegate) credentials to the server.
+ The default is
+ .Cm no .
++.It Cm GSSAPIKeyExchange
++Specifies whether key exchange based on GSSAPI may be used. When using
++GSSAPI key exchange the server need not have a host key.
++The default is
++.Dq no .
++.It Cm GSSAPIRenewalForcesRekey
++If set to
++.Dq yes
++then renewal of the client's GSSAPI credentials will force the rekeying of the
++ssh connection. With a compatible server, this will delegate the renewed
++credentials to a session on the server.
++.Pp
++Checks are made to ensure that credentials are only propagated when the new
++credentials match the old ones on the originating client and where the
++receiving server still has the old set in its cache.
++.Pp
++The default is
++.Dq no .
++.Pp
++For this to work
++.Cm GSSAPIKeyExchange
++needs to be enabled in the server and also used by the client.
++.It Cm GSSAPIServerIdentity
++If set, specifies the GSSAPI server identity that ssh should expect when
++connecting to the server. The default is unset, which means that the
++expected GSSAPI server identity will be determined from the target
++hostname.
++.It Cm GSSAPITrustDns
++Set to
++.Dq yes
++to indicate that the DNS is trusted to securely canonicalize
++the name of the host being connected to. If
++.Dq no ,
++the hostname entered on the
++command line will be passed untouched to the GSSAPI library.
++The default is
++.Dq no .
++.It Cm GSSAPIKexAlgorithms
++The list of key exchange algorithms that are offered for GSSAPI
++key exchange. Possible values are
++.Bd -literal -offset 3n
++gss-gex-sha1-,
++gss-group1-sha1-,
++gss-group14-sha1-,
++gss-group14-sha256-,
++gss-group16-sha512-,
++gss-nistp256-sha256-,
++gss-curve25519-sha256-
++.Ed
++.Pp
++The default is
++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,gss-curve25519-sha256-,gss-gex-sha1-,gss-group14-sha1- .
++This option only applies to connections using GSSAPI.
+ .It Cm HashKnownHosts
+ Indicates that
+ .Xr ssh 1
+diff --git a/sshconnect2.c b/sshconnect2.c
+index 58fe98db2..cb6a94e76 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -81,8 +81,6 @@
+ #endif
+
+ /* import */
+-extern char *client_version_string;
+-extern char *server_version_string;
+ extern Options options;
+
+ /*
+@@ -226,6 +224,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ char *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL;
+ int r, use_known_hosts_order = 0;
+
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++ char *orig = NULL, *gss = NULL;
++ char *gss_host = NULL;
++#endif
++
+ xxx_host = host;
+ xxx_hostaddr = hostaddr;
+ xxx_conn_info = cinfo;
+@@ -269,6 +272,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ compat_pkalg_proposal(ssh, options.hostkeyalgorithms);
+ }
+
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++ if (options.gss_keyex) {
++ /* Add the GSSAPI mechanisms currently supported on this
++ * client to the key exchange algorithm proposal */
++ orig = myproposal[PROPOSAL_KEX_ALGS];
++
++ if (options.gss_server_identity) {
++ gss_host = xstrdup(options.gss_server_identity);
++ } else if (options.gss_trust_dns) {
++ gss_host = remote_hostname(ssh);
++ /* Fall back to specified host if we are using proxy command
++ * and can not use DNS on that socket */
++ if (strcmp(gss_host, "UNKNOWN") == 0) {
++ free(gss_host);
++ gss_host = xstrdup(host);
++ }
++ } else {
++ gss_host = xstrdup(host);
++ }
++
++ gss = ssh_gssapi_client_mechanisms(gss_host,
++ options.gss_client_identity, options.gss_kex_algorithms);
++ if (gss) {
++ debug("Offering GSSAPI proposal: %s", gss);
++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++ "%s,%s", gss, orig);
++
++ /* If we've got GSSAPI algorithms, then we also support the
++ * 'null' hostkey, as a last resort */
++ orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
++ xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],
++ "%s,null", orig);
++ }
++ }
++#endif
++
+ if (options.rekey_limit || options.rekey_interval)
+ ssh_packet_set_rekey_limits(ssh, options.rekey_limit,
+ options.rekey_interval);
+@@ -287,16 +326,46 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ # ifdef OPENSSL_HAS_ECC
+ ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client;
+ # endif
+-#endif
++# ifdef GSSAPI
++ if (options.gss_keyex) {
++ ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client;
++ ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client;
++ ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client;
++ ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client;
++ ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client;
++ ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client;
++ ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client;
++ }
++# endif
++#endif /* WITH_OPENSSL */
+ ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ ssh->kex->verify_host_key=&verify_host_key_callback;
+
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++ if (options.gss_keyex) {
++ ssh->kex->gss_deleg_creds = options.gss_deleg_creds;
++ ssh->kex->gss_trust_dns = options.gss_trust_dns;
++ ssh->kex->gss_client = options.gss_client_identity;
++ ssh->kex->gss_host = gss_host;
++ }
++#endif
++
+ ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done);
+
+ /* remove ext-info from the KEX proposals for rekeying */
+ myproposal[PROPOSAL_KEX_ALGS] =
+ compat_kex_proposal(ssh, options.kex_algorithms);
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++ /* repair myproposal after it was crumpled by the */
++ /* ext-info removal above */
++ if (gss) {
++ orig = myproposal[PROPOSAL_KEX_ALGS];
++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++ "%s,%s", gss, orig);
++ free(gss);
++ }
++#endif
+ if ((r = kex_prop2buf(ssh->kex->my, myproposal)) != 0)
+ fatal_r(r, "kex_prop2buf");
+
+@@ -394,6 +463,7 @@ static int input_gssapi_response(int type, u_int32_t, struct ssh *);
+ 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 *);
++static int userauth_gsskeyex(struct ssh *);
+ #endif
+
+ void userauth(struct ssh *, char *);
+@@ -410,6 +480,11 @@ static char *authmethods_get(void);
+
+ Authmethod authmethods[] = {
+ #ifdef GSSAPI
++ {"gssapi-keyex",
++ userauth_gsskeyex,
++ NULL,
++ &options.gss_keyex,
++ NULL},
+ {"gssapi-with-mic",
+ userauth_gssapi,
+ userauth_gssapi_cleanup,
+@@ -785,12 +860,32 @@ userauth_gssapi(struct ssh *ssh)
+ OM_uint32 min;
+ int r, ok = 0;
+ gss_OID mech = NULL;
++ char *gss_host = NULL;
++
++ if (options.gss_server_identity) {
++ gss_host = xstrdup(options.gss_server_identity);
++ } else if (options.gss_trust_dns) {
++ gss_host = remote_hostname(ssh);
++ /* Fall back to specified host if we are using proxy command
++ * and can not use DNS on that socket */
++ if (strcmp(gss_host, "UNKNOWN") == 0) {
++ free(gss_host);
++ gss_host = xstrdup(authctxt->host);
++ }
++ } else {
++ gss_host = xstrdup(authctxt->host);
++ }
+
+ /* Try one GSSAPI method at a time, rather than sending them all at
+ * once. */
+
+ if (authctxt->gss_supported_mechs == NULL)
+- gss_indicate_mechs(&min, &authctxt->gss_supported_mechs);
++ if (GSS_ERROR(gss_indicate_mechs(&min,
++ &authctxt->gss_supported_mechs))) {
++ authctxt->gss_supported_mechs = NULL;
++ free(gss_host);
++ return 0;
++ }
+
+ /* Check to see whether the mechanism is usable before we offer it */
+ while (authctxt->mech_tried < authctxt->gss_supported_mechs->count &&
+@@ -799,13 +894,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,
+- mech, authctxt->host)) {
++ mech, gss_host, options.gss_client_identity)) {
+ ok = 1; /* Mechanism works */
+ } else {
+ authctxt->mech_tried++;
+ }
+ }
+
++ free(gss_host);
++
+ if (!ok || mech == NULL)
+ return 0;
+
+@@ -1046,6 +1143,55 @@ input_gssapi_error(int type, u_int32_t plen, struct ssh *ssh)
+ free(lang);
+ return r;
+ }
++
++int
++userauth_gsskeyex(struct ssh *ssh)
++{
++ struct sshbuf *b = NULL;
++ Authctxt *authctxt = ssh->authctxt;
++ gss_buffer_desc gssbuf;
++ gss_buffer_desc mic = GSS_C_EMPTY_BUFFER;
++ OM_uint32 ms;
++ int r;
++
++ static int attempt = 0;
++ if (attempt++ >= 1)
++ return (0);
++
++ if (gss_kex_context == NULL) {
++ debug("No valid Key exchange context");
++ return (0);
++ }
++
++ if ((b = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service,
++ "gssapi-keyex", ssh->kex->session_id);
++
++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++ fatal_f("sshbuf_mutable_ptr failed");
++ gssbuf.length = sshbuf_len(b);
++
++ if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) {
++ sshbuf_free(b);
++ return (0);
++ }
++
++ if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 ||
++ (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal_fr(r, "parsing");
++
++ sshbuf_free(b);
++ gss_release_buffer(&ms, &mic);
++
++ return (1);
++}
++
+ #endif /* GSSAPI */
+
+ static int
+diff --git a/sshd.c b/sshd.c
+index 6321936c0..6ad9a845a 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -803,8 +803,8 @@ notify_hostkeys(struct ssh *ssh)
+ }
+ debug3_f("sent %u hostkeys", nkeys);
+ if (nkeys == 0)
+- fatal_f("no hostkeys");
+- if ((r = sshpkt_send(ssh)) != 0)
++ debug3_f("no hostkeys");
++ else if ((r = sshpkt_send(ssh)) != 0)
+ sshpkt_fatal(ssh, r, "%s: send", __func__);
+ sshbuf_free(buf);
+ }
+@@ -1914,7 +1914,8 @@ main(int ac, char **av)
+ free(fp);
+ }
+ accumulate_host_timing_secret(cfg, NULL);
+- if (!sensitive_data.have_ssh2_key) {
++ /* The GSSAPI key exchange can run without a host key */
++ if (!sensitive_data.have_ssh2_key && !options.gss_keyex) {
+ logit("sshd: no hostkeys available -- exiting.");
+ exit(1);
+ }
+@@ -2398,6 +2399,48 @@ do_ssh2_kex(struct ssh *ssh)
+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = prop_hostkey =
+ compat_pkalg_proposal(ssh, list_hostkey_types());
+
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++ {
++ char *orig;
++ char *gss = NULL;
++ char *newstr = NULL;
++ orig = myproposal[PROPOSAL_KEX_ALGS];
++
++ /*
++ * If we don't have a host key, then there's no point advertising
++ * the other key exchange algorithms
++ */
++
++ if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0)
++ orig = NULL;
++
++ if (options.gss_keyex)
++ gss = ssh_gssapi_server_mechanisms();
++ else
++ gss = NULL;
++
++ if (gss && orig)
++ xasprintf(&newstr, "%s,%s", gss, orig);
++ else if (gss)
++ newstr = gss;
++ else if (orig)
++ newstr = orig;
++
++ /*
++ * If we've got GSSAPI mechanisms, then we've got the 'null' host
++ * key alg, but we can't tell people about it unless its the only
++ * host key algorithm we support
++ */
++ if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0)
++ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null";
++
++ if (newstr)
++ myproposal[PROPOSAL_KEX_ALGS] = newstr;
++ else
++ fatal("No supported key exchange algorithms");
++ }
++#endif
++
+ /* start key exchange */
+ if ((r = kex_setup(ssh, myproposal)) != 0)
+ fatal_r(r, "kex_setup");
+@@ -2413,7 +2456,18 @@ do_ssh2_kex(struct ssh *ssh)
+ # ifdef OPENSSL_HAS_ECC
+ kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
+ # endif
+-#endif
++# ifdef GSSAPI
++ if (options.gss_keyex) {
++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server;
++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server;
++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server;
++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server;
++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server;
++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
++ }
++# endif
++#endif /* WITH_OPENSSL */
+ kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ kex->load_host_public_key=&get_hostkey_public_by_type;
+diff --git a/sshd_config b/sshd_config
+index 36894ace5..ecfe8d026 100644
+--- a/sshd_config
++++ b/sshd_config
+@@ -69,6 +69,8 @@ AuthorizedKeysFile .ssh/authorized_keys
+ # GSSAPI options
+ #GSSAPIAuthentication no
+ #GSSAPICleanupCredentials yes
++#GSSAPIStrictAcceptorCheck yes
++#GSSAPIKeyExchange no
+
+ # Set this to 'yes' to enable PAM authentication, account processing,
+ # and session processing. If this is enabled, PAM authentication will
+diff --git a/sshd_config.5 b/sshd_config.5
+index 7313a7f79..83f840511 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -719,6 +719,11 @@ Specifies whether to automatically destroy the user's credentials cache
+ on logout.
+ The default is
+ .Cm yes .
++.It Cm GSSAPIKeyExchange
++Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange
++doesn't rely on ssh keys to verify host identity.
++The default is
++.Cm no .
+ .It Cm GSSAPIStrictAcceptorCheck
+ Determines whether to be strict about the identity of the GSSAPI acceptor
+ a client authenticates against.
+@@ -733,6 +738,31 @@ machine's default store.
+ This facility is provided to assist with operation on multi homed machines.
+ The default is
+ .Cm yes .
++.It Cm GSSAPIStoreCredentialsOnRekey
++Controls whether the user's GSSAPI credentials should be updated following a
++successful connection rekeying. This option can be used to accepted renewed
++or updated credentials from a compatible client. The default is
++.Dq no .
++.Pp
++For this to work
++.Cm GSSAPIKeyExchange
++needs to be enabled in the server and also used by the client.
++.It Cm GSSAPIKexAlgorithms
++The list of key exchange algorithms that are accepted by GSSAPI
++key exchange. Possible values are
++.Bd -literal -offset 3n
++gss-gex-sha1-,
++gss-group1-sha1-,
++gss-group14-sha1-,
++gss-group14-sha256-,
++gss-group16-sha512-,
++gss-nistp256-sha256-,
++gss-curve25519-sha256-
++.Ed
++.Pp
++The default is
++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,gss-curve25519-sha256-,gss-gex-sha1-,gss-group14-sha1- .
++This option only applies to connections using GSSAPI.
+ .It Cm HostbasedAcceptedAlgorithms
+ Specifies the signature algorithms that will be accepted for hostbased
+ authentication as a list of comma-separated patterns.
+diff --git a/sshkey.c b/sshkey.c
+index 43712253d..35a423e92 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -127,6 +127,9 @@ extern const struct sshkey_impl sshkey_dsa_cert_impl;
+ extern const struct sshkey_impl sshkey_xmss_impl;
+ extern const struct sshkey_impl sshkey_xmss_cert_impl;
+ #endif
++#ifdef GSSAPI
++extern const struct sshkey_impl sshkey_null_impl;
++#endif /* GSSAPI */
+
+ const struct sshkey_impl * const keyimpls[] = {
+ &sshkey_ed25519_impl,
+@@ -164,6 +167,9 @@ const struct sshkey_impl * const keyimpls[] = {
+ &sshkey_xmss_impl,
+ &sshkey_xmss_cert_impl,
+ #endif
++#ifdef GSSAPI
++ &sshkey_null_impl,
++#endif /* GSSAPI */
+ NULL
+ };
+
+@@ -319,7 +325,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
+
+ for (i = 0; keyimpls[i] != NULL; i++) {
+ impl = keyimpls[i];
+- if (impl->name == NULL)
++ if (impl->name == NULL || impl->type == KEY_NULL)
+ continue;
+ if (!include_sigonly && impl->sigonly)
+ continue;
+diff --git a/sshkey.h b/sshkey.h
+index 771c4bcee..a0742e8c2 100644
+--- a/sshkey.h
++++ b/sshkey.h
+@@ -71,6 +71,7 @@ enum sshkey_types {
+ KEY_ECDSA_SK_CERT,
+ KEY_ED25519_SK,
+ KEY_ED25519_SK_CERT,
++ KEY_NULL,
+ KEY_UNSPEC
+ };
+
diff --git a/debian/patches/keepalive-extensions.patch b/debian/patches/keepalive-extensions.patch
new file mode 100644
index 0000000..6df9b9e
--- /dev/null
+++ b/debian/patches/keepalive-extensions.patch
@@ -0,0 +1,135 @@
+From dbc7024bb9fe29a5d2bd398219ae2fc5668826b8 Mon Sep 17 00:00:00 2001
+From: Richard Kettlewell <rjk@greenend.org.uk>
+Date: Sun, 9 Feb 2014 16:09:52 +0000
+Subject: Various keepalive extensions
+
+Add compatibility aliases for ProtocolKeepAlives and SetupTimeOut, supported
+in previous versions of Debian's OpenSSH package but since superseded by
+ServerAliveInterval. (We're probably stuck with this bit for
+compatibility.)
+
+In batch mode, default ServerAliveInterval to five minutes.
+
+Adjust documentation to match and to give some more advice on use of
+keepalives.
+
+Author: Ian Jackson <ian@chiark.greenend.org.uk>
+Author: Matthew Vernon <matthew@debian.org>
+Author: Colin Watson <cjwatson@debian.org>
+Last-Update: 2023-01-02
+
+Patch-Name: keepalive-extensions.patch
+---
+ readconf.c | 14 ++++++++++++--
+ ssh_config.5 | 21 +++++++++++++++++++--
+ sshd_config.5 | 3 +++
+ 3 files changed, 34 insertions(+), 4 deletions(-)
+
+diff --git a/readconf.c b/readconf.c
+index 4a39f5b66..9ac71133a 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -179,6 +179,7 @@ typedef enum {
+ oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
+ oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
+ oEnableEscapeCommandline,
++ oProtocolKeepAlives, oSetupTimeOut,
+ oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
+ } OpCodes;
+
+@@ -339,6 +340,8 @@ static struct {
+ { "knownhostscommand", oKnownHostsCommand },
+ { "requiredrsasize", oRequiredRSASize },
+ { "enableescapecommandline", oEnableEscapeCommandline },
++ { "protocolkeepalives", oProtocolKeepAlives },
++ { "setuptimeout", oSetupTimeOut },
+
+ { NULL, oBadOption }
+ };
+@@ -1772,6 +1775,8 @@ parse_pubkey_algos:
+ goto parse_flag;
+
+ case oServerAliveInterval:
++ case oProtocolKeepAlives: /* Debian-specific compatibility alias */
++ case oSetupTimeOut: /* Debian-specific compatibility alias */
+ intptr = &options->server_alive_interval;
+ goto parse_time;
+
+@@ -2654,8 +2659,13 @@ fill_default_options(Options * options)
+ options->rekey_interval = 0;
+ if (options->verify_host_key_dns == -1)
+ options->verify_host_key_dns = 0;
+- if (options->server_alive_interval == -1)
+- options->server_alive_interval = 0;
++ if (options->server_alive_interval == -1) {
++ /* in batch mode, default is 5mins */
++ if (options->batch_mode == 1)
++ options->server_alive_interval = 300;
++ else
++ options->server_alive_interval = 0;
++ }
+ if (options->server_alive_count_max == -1)
+ options->server_alive_count_max = 3;
+ if (options->control_master == -1)
+diff --git a/ssh_config.5 b/ssh_config.5
+index 43f3b642c..09905bff3 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -275,9 +275,13 @@ If set to
+ .Cm yes ,
+ user interaction such as password prompts and host key confirmation requests
+ will be disabled.
++In addition, the
++.Cm ServerAliveInterval
++option will be set to 300 seconds by default (Debian-specific).
+ This option is useful in scripts and other batch jobs where no user
+ is present to interact with
+-.Xr ssh 1 .
++.Xr ssh 1 ,
++and where it is desirable to detect a broken network swiftly.
+ The argument must be
+ .Cm yes
+ or
+@@ -1794,7 +1798,14 @@ from the server,
+ will send a message through the encrypted
+ channel to request a response from the server.
+ The default
+-is 0, indicating that these messages will not be sent to the server.
++is 0, indicating that these messages will not be sent to the server,
++or 300 if the
++.Cm BatchMode
++option is set (Debian-specific).
++.Cm ProtocolKeepAlives
++and
++.Cm SetupTimeOut
++are Debian-specific compatibility aliases for this option.
+ .It Cm SessionType
+ May be used to either request invocation of a subsystem on the remote system,
+ or to prevent the execution of a remote command at all.
+@@ -1908,6 +1919,12 @@ Specifies whether the system should send TCP keepalive messages to the
+ other side.
+ If they are sent, death of the connection or crash of one
+ of the machines will be properly noticed.
++This option only uses TCP keepalives (as opposed to using ssh level
++keepalives), so takes a long time to notice when the connection dies.
++As such, you probably want
++the
++.Cm ServerAliveInterval
++option as well.
+ However, this means that
+ connections will die if the route is down temporarily, and some people
+ find it annoying.
+diff --git a/sshd_config.5 b/sshd_config.5
+index 83f840511..eb58750a5 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1830,6 +1830,9 @@ This avoids infinitely hanging sessions.
+ .Pp
+ To disable TCP keepalive messages, the value should be set to
+ .Cm no .
++.Pp
++This option was formerly called
++.Cm KeepAlive .
+ .It Cm TrustedUserCAKeys
+ Specifies a file containing public keys of certificate authorities that are
+ trusted to sign user certificates for authentication, or
diff --git a/debian/patches/maxhostnamelen.patch b/debian/patches/maxhostnamelen.patch
new file mode 100644
index 0000000..fd53e7f
--- /dev/null
+++ b/debian/patches/maxhostnamelen.patch
@@ -0,0 +1,30 @@
+From 36b00b5f4d96d6d9db3fd9e418bd2d1f66e8e7fe Mon Sep 17 00:00:00 2001
+From: Svante Signell <svante.signell@gmail.com>
+Date: Fri, 5 Nov 2021 23:22:53 +0000
+Subject: Define MAXHOSTNAMELEN on GNU/Hurd
+
+Bug-Debian: https://bugs.debian.org/997030
+Last-Update: 2021-11-05
+
+Patch-Name: maxhostnamelen.patch
+---
+ defines.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/defines.h b/defines.h
+index 279e509aa..7225cbfab 100644
+--- a/defines.h
++++ b/defines.h
+@@ -136,6 +136,12 @@ enum
+ # endif
+ #endif /* HOST_NAME_MAX */
+
++#ifndef MAXHOSTNAMELEN
++# if defined(_POSIX_HOST_NAME_MAX)
++# define MAXHOSTNAMELEN _POSIX_HOST_NAME_MAX
++# endif
++#endif /* MAXHOSTNAMELEN */
++
+ #if defined(HAVE_DECL_MAXSYMLINKS) && HAVE_DECL_MAXSYMLINKS == 0
+ # define MAXSYMLINKS 5
+ #endif
diff --git a/debian/patches/mention-ssh-keygen-on-keychange.patch b/debian/patches/mention-ssh-keygen-on-keychange.patch
new file mode 100644
index 0000000..71151ff
--- /dev/null
+++ b/debian/patches/mention-ssh-keygen-on-keychange.patch
@@ -0,0 +1,44 @@
+From e797fa7ecced95a0b7f27b000e467ffb31934d28 Mon Sep 17 00:00:00 2001
+From: Scott Moser <smoser@ubuntu.com>
+Date: Sun, 9 Feb 2014 16:10:03 +0000
+Subject: Mention ssh-keygen in ssh fingerprint changed warning
+
+Author: Chris Lamb <lamby@debian.org>
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1843
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/686607
+Last-Update: 2017-08-22
+
+Patch-Name: mention-ssh-keygen-on-keychange.patch
+---
+ sshconnect.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/sshconnect.c b/sshconnect.c
+index 891e968dc..77a3080ba 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -1270,9 +1270,13 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
+ error("%s. This could either mean that", key_msg);
+ error("DNS SPOOFING is happening or the IP address for the host");
+ error("and its host key have changed at the same time.");
+- if (ip_status != HOST_NEW)
++ if (ip_status != HOST_NEW) {
+ error("Offending key for IP in %s:%lu",
+ ip_found->file, ip_found->line);
++ error(" remove with:");
++ error(" ssh-keygen -f \"%s\" -R \"%s\"",
++ ip_found->file, ip);
++ }
+ }
+ /* The host key has changed. */
+ warn_changed_key(host_key);
+@@ -1284,6 +1288,9 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
+ error("Offending %s key in %s:%lu",
+ sshkey_type(host_found->key),
+ host_found->file, host_found->line);
++ error(" remove with:");
++ error(" ssh-keygen -f \"%s\" -R \"%s\"",
++ host_found->file, host);
+
+ /*
+ * If strict host key checking is in use, the user will have
diff --git a/debian/patches/no-openssl-version-status.patch b/debian/patches/no-openssl-version-status.patch
new file mode 100644
index 0000000..4babac9
--- /dev/null
+++ b/debian/patches/no-openssl-version-status.patch
@@ -0,0 +1,62 @@
+From c7c2ce00f07135457dbd924cfe962e03a2b0ab62 Mon Sep 17 00:00:00 2001
+From: Kurt Roeckx <kurt@roeckx.be>
+Date: Sun, 9 Feb 2014 16:10:14 +0000
+Subject: Don't check the status field of the OpenSSL version
+
+There is no reason to check the version of OpenSSL (in Debian). If it's
+not compatible the soname will change. OpenSSH seems to want to do a
+check for the soname based on the version number, but wants to keep the
+status of the release the same. Remove that check on the status since
+it doesn't tell you anything about how compatible that version is.
+
+Author: Colin Watson <cjwatson@debian.org>
+Bug-Debian: https://bugs.debian.org/93581
+Bug-Debian: https://bugs.debian.org/664383
+Bug-Debian: https://bugs.debian.org/732940
+Forwarded: not-needed
+Last-Update: 2014-10-07
+
+Patch-Name: no-openssl-version-status.patch
+---
+ openbsd-compat/openssl-compat.c | 6 +++---
+ openbsd-compat/regress/opensslvertest.c | 1 +
+ 2 files changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/openbsd-compat/openssl-compat.c b/openbsd-compat/openssl-compat.c
+index a37ca61bf..c1749210d 100644
+--- a/openbsd-compat/openssl-compat.c
++++ b/openbsd-compat/openssl-compat.c
+@@ -34,7 +34,7 @@
+ /*
+ * OpenSSL version numbers: MNNFFPPS: major minor fix patch status
+ * We match major, minor, fix and status (not patch) for <1.0.0.
+- * After that, we acceptable compatible fix versions (so we
++ * After that, we accept compatible fix and status versions (so we
+ * allow 1.0.1 to work with 1.0.0). Going backwards is only allowed
+ * within a patch series.
+ */
+@@ -55,10 +55,10 @@ ssh_compatible_openssl(long headerver, long libver)
+ }
+
+ /*
+- * For versions >= 1.0.0, major,minor,status must match and library
++ * For versions >= 1.0.0, major,minor must match and library
+ * fix version must be equal to or newer than the header.
+ */
+- mask = 0xfff0000fL; /* major,minor,status */
++ mask = 0xfff00000L; /* major,minor */
+ hfix = (headerver & 0x000ff000) >> 12;
+ lfix = (libver & 0x000ff000) >> 12;
+ if ( (headerver & mask) == (libver & mask) && lfix >= hfix)
+diff --git a/openbsd-compat/regress/opensslvertest.c b/openbsd-compat/regress/opensslvertest.c
+index d50066609..aeccc107b 100644
+--- a/openbsd-compat/regress/opensslvertest.c
++++ b/openbsd-compat/regress/opensslvertest.c
+@@ -37,6 +37,7 @@ struct version_test {
+
+ /* built with 1.0.1b release headers */
+ { 0x1000101fL, 0x1000101fL, 1},/* exact match */
++ { 0x1000101fL, 0x10001010L, 1}, /* different status: ok */
+ { 0x1000101fL, 0x1000102fL, 1}, /* newer library patch version: ok */
+ { 0x1000101fL, 0x1000100fL, 1}, /* older library patch version: ok */
+ { 0x1000101fL, 0x1000201fL, 1}, /* newer library fix version: ok */
diff --git a/debian/patches/openbsd-docs.patch b/debian/patches/openbsd-docs.patch
new file mode 100644
index 0000000..6312b84
--- /dev/null
+++ b/debian/patches/openbsd-docs.patch
@@ -0,0 +1,255 @@
+From f8033f154f0fe23f974f67ba2f8a29754a5044af Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:09 +0000
+Subject: Adjust various OpenBSD-specific references in manual pages
+
+No single bug reference for this patch, but history includes:
+ https://bugs.debian.org/154434 (login.conf(5))
+ https://bugs.debian.org/513417 (/etc/rc)
+ https://bugs.debian.org/530692 (ssl(8))
+ https://bugs.launchpad.net/bugs/456660 (ssl(8))
+ https://bugs.debian.org/998069 (rdomain(4))
+
+Forwarded: not-needed
+Last-Update: 2021-11-05
+
+Patch-Name: openbsd-docs.patch
+---
+ moduli.5 | 4 ++--
+ ssh-keygen.1 | 12 ++++--------
+ ssh.1 | 4 ++++
+ sshd.8 | 5 ++---
+ sshd_config.5 | 40 ++--------------------------------------
+ 5 files changed, 14 insertions(+), 51 deletions(-)
+
+diff --git a/moduli.5 b/moduli.5
+index 5086a6d42..6dffdc7e6 100644
+--- a/moduli.5
++++ b/moduli.5
+@@ -21,7 +21,7 @@
+ .Nd Diffie-Hellman moduli
+ .Sh DESCRIPTION
+ The
+-.Pa /etc/moduli
++.Pa /etc/ssh/moduli
+ file contains prime numbers and generators for use by
+ .Xr sshd 8
+ in the Diffie-Hellman Group Exchange key exchange method.
+@@ -110,7 +110,7 @@ first estimates the size of the modulus required to produce enough
+ Diffie-Hellman output to sufficiently key the selected symmetric cipher.
+ .Xr sshd 8
+ then randomly selects a modulus from
+-.Fa /etc/moduli
++.Fa /etc/ssh/moduli
+ that best meets the size requirement.
+ .Sh SEE ALSO
+ .Xr ssh-keygen 1 ,
+diff --git a/ssh-keygen.1 b/ssh-keygen.1
+index 8b1f617d2..f2a021878 100644
+--- a/ssh-keygen.1
++++ b/ssh-keygen.1
+@@ -212,9 +212,7 @@ key in
+ .Pa ~/.ssh/id_ed25519_sk
+ or
+ .Pa ~/.ssh/id_rsa .
+-Additionally, the system administrator may use this to generate host keys,
+-as seen in
+-.Pa /etc/rc .
++Additionally, the system administrator may use this to generate host keys.
+ .Pp
+ Normally this program generates the key and asks for a file in which
+ to store the private key.
+@@ -279,9 +277,7 @@ If
+ .Fl f
+ has also been specified, its argument is used as a prefix to the
+ default path for the resulting host key files.
+-This is used by
+-.Pa /etc/rc
+-to generate new host keys.
++This is used by system administration scripts to generate new host keys.
+ .It Fl a Ar rounds
+ When saving a private key, this option specifies the number of KDF
+ (key derivation function, currently
+@@ -849,7 +845,7 @@ option.
+ Valid generator values are 2, 3, and 5.
+ .Pp
+ Screened DH groups may be installed in
+-.Pa /etc/moduli .
++.Pa /etc/ssh/moduli .
+ It is important that this file contains moduli of a range of bit lengths.
+ .Pp
+ A number of options are available for moduli generation and screening via the
+@@ -1307,7 +1303,7 @@ on all machines
+ where the user wishes to log in using public key authentication.
+ There is no need to keep the contents of this file secret.
+ .Pp
+-.It Pa /etc/moduli
++.It Pa /etc/ssh/moduli
+ Contains Diffie-Hellman groups used for DH-GEX.
+ The file format is described in
+ .Xr moduli 5 .
+diff --git a/ssh.1 b/ssh.1
+index 4028f971a..6b56f5638 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -925,6 +925,10 @@ implements public key authentication protocol automatically,
+ using one of the DSA, ECDSA, Ed25519 or RSA algorithms.
+ The HISTORY section of
+ .Xr ssl 8
++(on non-OpenBSD systems, see
++.nh
++http://www.openbsd.org/cgi\-bin/man.cgi?query=ssl&sektion=8#HISTORY)
++.hy
+ contains a brief discussion of the DSA and RSA algorithms.
+ .Pp
+ The file
+diff --git a/sshd.8 b/sshd.8
+index 71da859dc..ec1687585 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -64,7 +64,7 @@ over an insecure network.
+ .Nm
+ listens for connections from clients.
+ It is normally started at boot from
+-.Pa /etc/rc .
++.Pa /etc/init.d/ssh .
+ It forks a new
+ daemon for each incoming connection.
+ The forked daemons handle
+@@ -921,7 +921,7 @@ This file is for host-based authentication (see
+ .Xr ssh 1 ) .
+ It should only be writable by root.
+ .Pp
+-.It Pa /etc/moduli
++.It Pa /etc/ssh/moduli
+ Contains Diffie-Hellman groups used for the "Diffie-Hellman Group Exchange"
+ key exchange method.
+ The file format is described in
+@@ -1019,7 +1019,6 @@ The content of this file is not sensitive; it can be world-readable.
+ .Xr ssh-keyscan 1 ,
+ .Xr chroot 2 ,
+ .Xr hosts_access 5 ,
+-.Xr login.conf 5 ,
+ .Xr moduli 5 ,
+ .Xr sshd_config 5 ,
+ .Xr inetd 8 ,
+diff --git a/sshd_config.5 b/sshd_config.5
+index 2c6ec76cb..b9a19c7bd 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -981,9 +981,6 @@ for interactive sessions and
+ for non-interactive sessions.
+ .It Cm KbdInteractiveAuthentication
+ Specifies whether to allow keyboard-interactive authentication.
+-All authentication styles from
+-.Xr login.conf 5
+-are supported.
+ The default is
+ .Cm yes .
+ The argument to this keyword must be
+@@ -1087,45 +1084,33 @@ The following forms may be used:
+ .Sm off
+ .Ar hostname | address
+ .Sm on
+-.Op Cm rdomain Ar domain
+ .It
+ .Cm ListenAddress
+ .Sm off
+ .Ar hostname : port
+ .Sm on
+-.Op Cm rdomain Ar domain
+ .It
+ .Cm ListenAddress
+ .Sm off
+ .Ar IPv4_address : port
+ .Sm on
+-.Op Cm rdomain Ar domain
+ .It
+ .Cm ListenAddress
+ .Sm off
+ .Oo Ar hostname | address Oc : Ar port
+ .Sm on
+-.Op Cm rdomain Ar domain
+ .El
+ .Pp
+-The optional
+-.Cm rdomain
+-qualifier requests
+-.Xr sshd 8
+-listen in an explicit routing domain.
+ If
+ .Ar port
+ is not specified,
+ sshd will listen on the address and all
+ .Cm Port
+ options specified.
+-The default is to listen on all local addresses on the current default
+-routing domain.
++The default is to listen on all local addresses.
+ Multiple
+ .Cm ListenAddress
+ options are permitted.
+-For more information on routing domains, see
+-.Xr rdomain 4 .
+ .It Cm LoginGraceTime
+ The server disconnects after this time if the user has not
+ successfully logged in.
+@@ -1250,14 +1235,8 @@ The available criteria are
+ .Cm Host ,
+ .Cm LocalAddress ,
+ .Cm LocalPort ,
+-.Cm RDomain ,
+ and
+-.Cm Address
+-(with
+-.Cm RDomain
+-representing the
+-.Xr rdomain 4
+-on which the connection was received).
++.Cm Address .
+ .Pp
+ The match patterns may consist of single entries or comma-separated
+ lists and may use the wildcard and negation operators described in the
+@@ -1329,7 +1308,6 @@ Available keywords are
+ .Cm PubkeyAuthOptions ,
+ .Cm RekeyLimit ,
+ .Cm RevokedKeys ,
+-.Cm RDomain ,
+ .Cm SetEnv ,
+ .Cm StreamLocalBindMask ,
+ .Cm StreamLocalBindUnlink ,
+@@ -1724,15 +1702,6 @@ an OpenSSH Key Revocation List (KRL) as generated by
+ .Xr ssh-keygen 1 .
+ For more information on KRLs, see the KEY REVOCATION LISTS section in
+ .Xr ssh-keygen 1 .
+-.It Cm RDomain
+-Specifies an explicit routing domain that is applied after authentication
+-has completed.
+-The user session, as well as any forwarded or listening IP sockets,
+-will be bound to this
+-.Xr rdomain 4 .
+-If the routing domain is set to
+-.Cm \&%D ,
+-then the domain in which the incoming connection was received will be applied.
+ .It Cm SecurityKeyProvider
+ Specifies a path to a library that will be used when loading
+ FIDO authenticator-hosted keys, overriding the default of using
+@@ -2047,8 +2016,6 @@ which are expanded at runtime:
+ .It %%
+ A literal
+ .Sq % .
+-.It \&%D
+-The routing domain in which the incoming connection was received.
+ .It %F
+ The fingerprint of the CA key.
+ .It %f
+@@ -2087,9 +2054,6 @@ accepts the tokens %%, %h, %U, and %u.
+ .Pp
+ .Cm ChrootDirectory
+ accepts the tokens %%, %h, %U, and %u.
+-.Pp
+-.Cm RoutingDomain
+-accepts the token %D.
+ .Sh FILES
+ .Bl -tag -width Ds
+ .It Pa /etc/ssh/sshd_config
diff --git a/debian/patches/package-versioning.patch b/debian/patches/package-versioning.patch
new file mode 100644
index 0000000..8529c23
--- /dev/null
+++ b/debian/patches/package-versioning.patch
@@ -0,0 +1,47 @@
+From 720ad1a8e62ff52438766b49f8413ac55b17f570 Mon Sep 17 00:00:00 2001
+From: Matthew Vernon <matthew@debian.org>
+Date: Sun, 9 Feb 2014 16:10:05 +0000
+Subject: Include the Debian version in our identification
+
+This makes it easier to audit networks for versions patched against security
+vulnerabilities. It has little detrimental effect, as attackers will
+generally just try attacks rather than bothering to scan for
+vulnerable-looking version strings. (However, see debian-banner.patch.)
+
+Forwarded: not-needed
+Last-Update: 2021-11-05
+
+Patch-Name: package-versioning.patch
+---
+ kex.c | 2 +-
+ version.h | 7 ++++++-
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/kex.c b/kex.c
+index c259e00fc..32858e65c 100644
+--- a/kex.c
++++ b/kex.c
+@@ -1290,7 +1290,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
+ if (version_addendum != NULL && *version_addendum == '\0')
+ version_addendum = NULL;
+ if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n",
+- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION,
++ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE,
+ version_addendum == NULL ? "" : " ",
+ version_addendum == NULL ? "" : version_addendum)) != 0) {
+ oerrno = errno;
+diff --git a/version.h b/version.h
+index d83ae5b94..a7d5ecef6 100644
+--- a/version.h
++++ b/version.h
+@@ -3,4 +3,9 @@
+ #define SSH_VERSION "OpenSSH_9.2"
+
+ #define SSH_PORTABLE "p1"
+-#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
++#define SSH_RELEASE_MINIMUM SSH_VERSION SSH_PORTABLE
++#ifdef SSH_EXTRAVERSION
++#define SSH_RELEASE SSH_RELEASE_MINIMUM " " SSH_EXTRAVERSION
++#else
++#define SSH_RELEASE SSH_RELEASE_MINIMUM
++#endif
diff --git a/debian/patches/remove-spurious-ssh-agent-options.patch b/debian/patches/remove-spurious-ssh-agent-options.patch
new file mode 100644
index 0000000..ee02fb3
--- /dev/null
+++ b/debian/patches/remove-spurious-ssh-agent-options.patch
@@ -0,0 +1,29 @@
+From 74edce484429249265baaee1e8a5d1785ee7afa7 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Tue, 7 Feb 2023 23:55:19 +0000
+Subject: Remove spurious ssh-agent options
+
+These cause regress/agent-getpeereid.sh to leave a stray ssh-agent
+process lying around.
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=3536
+Last-Update: 2023-02-07
+
+Patch-Name: remove-spurious-ssh-agent-options.patch
+---
+ regress/agent-getpeereid.sh | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/regress/agent-getpeereid.sh b/regress/agent-getpeereid.sh
+index 79e9c7d9e..e0379445d 100644
+--- a/regress/agent-getpeereid.sh
++++ b/regress/agent-getpeereid.sh
+@@ -53,7 +53,7 @@ else
+ fi
+
+ trace "kill agent"
+- ${SSHAGENT} -vvv -k >>$OBJ/ssh-agent.log 2>&1
++ ${SSHAGENT} -k >>$OBJ/ssh-agent.log 2>&1
+ fi
+
+ rm -f ${OBJ}/agent
diff --git a/debian/patches/restore-authorized_keys2.patch b/debian/patches/restore-authorized_keys2.patch
new file mode 100644
index 0000000..918da0f
--- /dev/null
+++ b/debian/patches/restore-authorized_keys2.patch
@@ -0,0 +1,35 @@
+From b2cc972d55fcc3c3df709a340ce3019fec9880c4 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 5 Mar 2017 02:02:11 +0000
+Subject: Restore reading authorized_keys2 by default
+
+Upstream seems to intend to gradually phase this out, so don't assume
+that this will remain the default forever. However, we were late in
+adopting the upstream sshd_config changes, so it makes sense to extend
+the grace period.
+
+Bug-Debian: https://bugs.debian.org/852320
+Forwarded: not-needed
+Last-Update: 2017-03-05
+
+Patch-Name: restore-authorized_keys2.patch
+---
+ sshd_config | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/sshd_config b/sshd_config
+index 677f97d5d..d500d18cd 100644
+--- a/sshd_config
++++ b/sshd_config
+@@ -38,9 +38,8 @@ Include /etc/ssh/sshd_config.d/*.conf
+
+ #PubkeyAuthentication yes
+
+-# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
+-# but this is overridden so installations will only check .ssh/authorized_keys
+-AuthorizedKeysFile .ssh/authorized_keys
++# Expect .ssh/authorized_keys2 to be disregarded by default in future.
++#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
+
+ #AuthorizedPrincipalsFile none
+
diff --git a/debian/patches/restore-tcp-wrappers.patch b/debian/patches/restore-tcp-wrappers.patch
new file mode 100644
index 0000000..404c696
--- /dev/null
+++ b/debian/patches/restore-tcp-wrappers.patch
@@ -0,0 +1,172 @@
+From 8cee1ce3e07ac7904468ab8076ad5595048fb4c9 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Tue, 7 Oct 2014 13:22:41 +0100
+Subject: Restore TCP wrappers support
+
+Support for TCP wrappers was dropped in OpenSSH 6.7. See this message
+and thread:
+
+ https://lists.mindrot.org/pipermail/openssh-unix-dev/2014-April/032497.html
+
+It is true that this reduces preauth attack surface in sshd. On the
+other hand, this support seems to be quite widely used, and abruptly
+dropping it (from the perspective of users who don't read
+openssh-unix-dev) could easily cause more serious problems in practice.
+
+It's not entirely clear what the right long-term answer for Debian is,
+but it at least probably doesn't involve dropping this feature shortly
+before a freeze.
+
+Forwarded: not-needed
+Last-Update: 2022-02-23
+
+Patch-Name: restore-tcp-wrappers.patch
+---
+ configure.ac | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ sshd.8 | 7 +++++++
+ sshd.c | 25 +++++++++++++++++++++++
+ 3 files changed, 89 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 75a9e5670..0c59c8b50 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1667,6 +1667,62 @@ else
+ AC_MSG_RESULT([no])
+ fi
+
++# Check whether user wants TCP wrappers support
++TCPW_MSG="no"
++AC_ARG_WITH([tcp-wrappers],
++ [ --with-tcp-wrappers[[=PATH]] Enable tcpwrappers support (optionally in PATH)],
++ [
++ if test "x$withval" != "xno" ; then
++ saved_LIBS="$LIBS"
++ saved_LDFLAGS="$LDFLAGS"
++ saved_CPPFLAGS="$CPPFLAGS"
++ if test -n "${withval}" && \
++ test "x${withval}" != "xyes"; then
++ if test -d "${withval}/lib"; then
++ if test -n "${need_dash_r}"; then
++ LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}"
++ else
++ LDFLAGS="-L${withval}/lib ${LDFLAGS}"
++ fi
++ else
++ if test -n "${need_dash_r}"; then
++ LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}"
++ else
++ LDFLAGS="-L${withval} ${LDFLAGS}"
++ fi
++ fi
++ if test -d "${withval}/include"; then
++ CPPFLAGS="-I${withval}/include ${CPPFLAGS}"
++ else
++ CPPFLAGS="-I${withval} ${CPPFLAGS}"
++ fi
++ fi
++ LIBS="-lwrap $LIBS"
++ AC_MSG_CHECKING([for libwrap])
++ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
++#include <sys/types.h>
++#include <sys/socket.h>
++#include <netinet/in.h>
++#include <tcpd.h>
++int deny_severity = 0, allow_severity = 0;
++ ]], [[
++ hosts_access(0);
++ ]])], [
++ AC_MSG_RESULT([yes])
++ AC_DEFINE([LIBWRAP], [1],
++ [Define if you want
++ TCP Wrappers support])
++ SSHDLIBS="$SSHDLIBS -lwrap"
++ TCPW_MSG="yes"
++ ], [
++ AC_MSG_ERROR([*** libwrap missing])
++
++ ])
++ LIBS="$saved_LIBS"
++ fi
++ ]
++)
++
+ # Check whether user wants to use ldns
+ LDNS_MSG="no"
+ AC_ARG_WITH(ldns,
+@@ -5653,6 +5709,7 @@ echo " PAM support: $PAM_MSG"
+ echo " OSF SIA support: $SIA_MSG"
+ echo " KerberosV support: $KRB5_MSG"
+ echo " SELinux support: $SELINUX_MSG"
++echo " TCP Wrappers support: $TCPW_MSG"
+ echo " libedit support: $LIBEDIT_MSG"
+ echo " libldns support: $LDNS_MSG"
+ echo " Solaris process contract support: $SPC_MSG"
+diff --git a/sshd.8 b/sshd.8
+index bace97839..71da859dc 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -910,6 +910,12 @@ the user's home directory becomes accessible.
+ This file should be writable only by the user, and need not be
+ readable by anyone else.
+ .Pp
++.It Pa /etc/hosts.allow
++.It Pa /etc/hosts.deny
++Access controls that should be enforced by tcp-wrappers are defined here.
++Further details are described in
++.Xr hosts_access 5 .
++.Pp
+ .It Pa /etc/hosts.equiv
+ This file is for host-based authentication (see
+ .Xr ssh 1 ) .
+@@ -1012,6 +1018,7 @@ The content of this file is not sensitive; it can be world-readable.
+ .Xr ssh-keygen 1 ,
+ .Xr ssh-keyscan 1 ,
+ .Xr chroot 2 ,
++.Xr hosts_access 5 ,
+ .Xr login.conf 5 ,
+ .Xr moduli 5 ,
+ .Xr sshd_config 5 ,
+diff --git a/sshd.c b/sshd.c
+index 6ad9a845a..7944cf633 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -129,6 +129,13 @@
+ #include "srclimit.h"
+ #include "dh.h"
+
++#ifdef LIBWRAP
++#include <tcpd.h>
++#include <syslog.h>
++int allow_severity;
++int deny_severity;
++#endif /* LIBWRAP */
++
+ /* Re-exec fds */
+ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1)
+ #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2)
+@@ -2189,6 +2196,24 @@ main(int ac, char **av)
+ #ifdef SSH_AUDIT_EVENTS
+ audit_connection_from(remote_ip, remote_port);
+ #endif
++#ifdef LIBWRAP
++ allow_severity = options.log_facility|LOG_INFO;
++ deny_severity = options.log_facility|LOG_WARNING;
++ /* Check whether logins are denied from this host. */
++ if (ssh_packet_connection_is_on_socket(ssh)) {
++ struct request_info req;
++
++ request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0);
++ fromhost(&req);
++
++ if (!hosts_access(&req)) {
++ debug("Connection refused by tcp wrapper");
++ refuse(&req);
++ /* NOTREACHED */
++ fatal("libwrap refuse returns");
++ }
++ }
++#endif /* LIBWRAP */
+
+ rdomain = ssh_packet_rdomain_in(ssh);
+
diff --git a/debian/patches/revert-ipqos-defaults.patch b/debian/patches/revert-ipqos-defaults.patch
new file mode 100644
index 0000000..9ea3ad6
--- /dev/null
+++ b/debian/patches/revert-ipqos-defaults.patch
@@ -0,0 +1,93 @@
+From 8aea1d66b4ba0afd6cb4b25991bfb683d951c6e2 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Mon, 8 Apr 2019 10:46:29 +0100
+Subject: Revert "upstream: Update default IPQoS in ssh(1), sshd(8) to DSCP
+ AF21 for"
+
+This reverts commit 5ee8448ad7c306f05a9f56769f95336a8269f379.
+
+The IPQoS default changes have some unfortunate interactions with
+iptables (see https://bugs.debian.org/923880) and VMware, so I'm
+temporarily reverting them until those have been fixed.
+
+Bug-Debian: https://bugs.debian.org/923879
+Bug-Debian: https://bugs.debian.org/926229
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/1822370
+Last-Update: 2019-04-08
+
+Patch-Name: revert-ipqos-defaults.patch
+---
+ readconf.c | 4 ++--
+ servconf.c | 4 ++--
+ ssh_config.5 | 6 ++----
+ sshd_config.5 | 6 ++----
+ 4 files changed, 8 insertions(+), 12 deletions(-)
+
+diff --git a/readconf.c b/readconf.c
+index 6c8ad919f..2db9cb6ae 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2686,9 +2686,9 @@ fill_default_options(Options * options)
+ if (options->visual_host_key == -1)
+ options->visual_host_key = 0;
+ if (options->ip_qos_interactive == -1)
+- options->ip_qos_interactive = IPTOS_DSCP_AF21;
++ options->ip_qos_interactive = IPTOS_LOWDELAY;
+ if (options->ip_qos_bulk == -1)
+- options->ip_qos_bulk = IPTOS_DSCP_CS1;
++ options->ip_qos_bulk = IPTOS_THROUGHPUT;
+ if (options->request_tty == -1)
+ options->request_tty = REQUEST_TTY_AUTO;
+ if (options->session_type == -1)
+diff --git a/servconf.c b/servconf.c
+index 4ecbca8ed..8b824e35a 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -441,9 +441,9 @@ fill_default_server_options(ServerOptions *options)
+ if (options->permit_tun == -1)
+ options->permit_tun = SSH_TUNMODE_NO;
+ if (options->ip_qos_interactive == -1)
+- options->ip_qos_interactive = IPTOS_DSCP_AF21;
++ options->ip_qos_interactive = IPTOS_LOWDELAY;
+ if (options->ip_qos_bulk == -1)
+- options->ip_qos_bulk = IPTOS_DSCP_CS1;
++ options->ip_qos_bulk = IPTOS_THROUGHPUT;
+ if (options->version_addendum == NULL)
+ options->version_addendum = xstrdup("");
+ if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1)
+diff --git a/ssh_config.5 b/ssh_config.5
+index 32851c984..f8616c18b 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1221,11 +1221,9 @@ If one argument is specified, it is used as the packet class unconditionally.
+ If two values are specified, the first is automatically selected for
+ interactive sessions and the second for non-interactive sessions.
+ The default is
+-.Cm af21
+-(Low-Latency Data)
++.Cm lowdelay
+ for interactive sessions and
+-.Cm cs1
+-(Lower Effort)
++.Cm throughput
+ for non-interactive sessions.
+ .It Cm KbdInteractiveAuthentication
+ Specifies whether to use keyboard-interactive authentication.
+diff --git a/sshd_config.5 b/sshd_config.5
+index b13f7665b..7fd8abf48 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1002,11 +1002,9 @@ If one argument is specified, it is used as the packet class unconditionally.
+ If two values are specified, the first is automatically selected for
+ interactive sessions and the second for non-interactive sessions.
+ The default is
+-.Cm af21
+-(Low-Latency Data)
++.Cm lowdelay
+ for interactive sessions and
+-.Cm cs1
+-(Lower Effort)
++.Cm throughput
+ for non-interactive sessions.
+ .It Cm KbdInteractiveAuthentication
+ Specifies whether to allow keyboard-interactive authentication.
diff --git a/debian/patches/scp-quoting.patch b/debian/patches/scp-quoting.patch
new file mode 100644
index 0000000..2419730
--- /dev/null
+++ b/debian/patches/scp-quoting.patch
@@ -0,0 +1,41 @@
+From 501d8554b6792531778d6b3b9344f8e55d84df29 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Nicolas=20Valc=C3=A1rcel?= <nvalcarcel@ubuntu.com>
+Date: Sun, 9 Feb 2014 16:09:59 +0000
+Subject: Adjust scp quoting in verbose mode
+
+Tweak scp's reporting of filenames in verbose mode to be a bit less
+confusing with spaces.
+
+This should be revised to mimic real shell quoting.
+
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/89945
+Last-Update: 2010-02-27
+
+Patch-Name: scp-quoting.patch
+---
+ scp.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/scp.c b/scp.c
+index 1adff5cec..df590c4e3 100644
+--- a/scp.c
++++ b/scp.c
+@@ -239,8 +239,16 @@ do_local_cmd(arglist *a)
+
+ if (verbose_mode) {
+ fprintf(stderr, "Executing:");
+- for (i = 0; i < a->num; i++)
+- fmprintf(stderr, " %s", a->list[i]);
++ for (i = 0; i < a->num; i++) {
++ if (i == 0)
++ fmprintf(stderr, " %s", a->list[i]);
++ else
++ /*
++ * TODO: misbehaves if a->list[i] contains a
++ * single quote
++ */
++ fmprintf(stderr, " '%s'", a->list[i]);
++ }
+ fprintf(stderr, "\n");
+ }
+ if ((pid = fork()) == -1)
diff --git a/debian/patches/selinux-role.patch b/debian/patches/selinux-role.patch
new file mode 100644
index 0000000..7fb35f2
--- /dev/null
+++ b/debian/patches/selinux-role.patch
@@ -0,0 +1,471 @@
+From a1b3f6592e7ef61f5d9544fc652ae44f8c47bd2e Mon Sep 17 00:00:00 2001
+From: Manoj Srivastava <srivasta@debian.org>
+Date: Sun, 9 Feb 2014 16:09:49 +0000
+Subject: Handle SELinux authorisation roles
+
+Rejected upstream due to discomfort with magic usernames; a better approach
+will need an SSH protocol change. In the meantime, this came from Debian's
+SELinux maintainer, so we'll keep it until we have something better.
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1641
+Bug-Debian: http://bugs.debian.org/394795
+Last-Update: 2021-11-05
+
+Patch-Name: selinux-role.patch
+---
+ auth.h | 1 +
+ auth2.c | 10 ++++++++--
+ monitor.c | 36 +++++++++++++++++++++++++++++++++---
+ monitor.h | 2 ++
+ monitor_wrap.c | 27 ++++++++++++++++++++++++---
+ monitor_wrap.h | 3 ++-
+ openbsd-compat/port-linux.c | 21 ++++++++++++++-------
+ openbsd-compat/port-linux.h | 4 ++--
+ platform.c | 4 ++--
+ platform.h | 2 +-
+ session.c | 10 +++++-----
+ session.h | 2 +-
+ sshd.c | 2 +-
+ sshpty.c | 4 ++--
+ sshpty.h | 2 +-
+ 15 files changed, 99 insertions(+), 31 deletions(-)
+
+diff --git a/auth.h b/auth.h
+index 6d2d39762..d16dc66b8 100644
+--- a/auth.h
++++ b/auth.h
+@@ -65,6 +65,7 @@ struct Authctxt {
+ char *service;
+ struct passwd *pw; /* set if 'valid' */
+ char *style;
++ char *role;
+
+ /* Method lists for multiple authentication */
+ char **auth_methods; /* modified from server config */
+diff --git a/auth2.c b/auth2.c
+index 44bd49177..d66d73876 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -264,7 +264,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
+ {
+ Authctxt *authctxt = ssh->authctxt;
+ Authmethod *m = NULL;
+- char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
++ char *user = NULL, *service = NULL, *method = NULL, *style = NULL, *role = NULL;
+ int r, authenticated = 0;
+ double tstart = monotime_double();
+
+@@ -278,8 +278,13 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
+ debug("userauth-request for user %s service %s method %s", user, service, method);
+ debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
+
++ if ((role = strchr(user, '/')) != NULL)
++ *role++ = 0;
++
+ if ((style = strchr(user, ':')) != NULL)
+ *style++ = 0;
++ else if (role && (style = strchr(role, ':')) != NULL)
++ *style++ = '\0';
+
+ if (authctxt->attempt >= 1024)
+ auth_maxtries_exceeded(ssh);
+@@ -308,8 +313,9 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
+ use_privsep ? " [net]" : "");
+ authctxt->service = xstrdup(service);
+ authctxt->style = style ? xstrdup(style) : NULL;
++ authctxt->role = role ? xstrdup(role) : NULL;
+ if (use_privsep)
+- mm_inform_authserv(service, style);
++ mm_inform_authserv(service, style, role);
+ userauth_banner(ssh);
+ if (auth2_setup_methods_lists(authctxt) != 0)
+ ssh_packet_disconnect(ssh,
+diff --git a/monitor.c b/monitor.c
+index 5f7df9539..769eda640 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -117,6 +117,7 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
+ int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
+ int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
+ int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
++int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
+ int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
+@@ -195,6 +196,7 @@ struct mon_table mon_dispatch_proto20[] = {
+ {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
+ {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
+ {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
++ {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
+ {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
+ {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
+ #ifdef USE_PAM
+@@ -815,6 +817,7 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
+
+ /* Allow service/style information on the auth context */
+ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
+
+ #ifdef USE_PAM
+@@ -848,15 +851,42 @@ mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m)
+ monitor_permit_authentications(1);
+
+ if ((r = sshbuf_get_cstring(m, &authctxt->service, NULL)) != 0 ||
+- (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0)
++ (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0 ||
++ (r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
+ fatal_fr(r, "parse");
+- debug3_f("service=%s, style=%s", authctxt->service, authctxt->style);
++ debug3_f("service=%s, style=%s, role=%s",
++ authctxt->service, authctxt->style, authctxt->role);
+
+ if (strlen(authctxt->style) == 0) {
+ free(authctxt->style);
+ authctxt->style = NULL;
+ }
+
++ if (strlen(authctxt->role) == 0) {
++ free(authctxt->role);
++ authctxt->role = NULL;
++ }
++
++ return (0);
++}
++
++int
++mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++ int r;
++
++ monitor_permit_authentications(1);
++
++ if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++ debug3("%s: role=%s",
++ __func__, authctxt->role);
++
++ if (strlen(authctxt->role) == 0) {
++ free(authctxt->role);
++ authctxt->role = NULL;
++ }
++
+ return (0);
+ }
+
+@@ -1578,7 +1608,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
+ res = pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty));
+ if (res == 0)
+ goto error;
+- pty_setowner(authctxt->pw, s->tty);
++ pty_setowner(authctxt->pw, s->tty, authctxt->role);
+
+ if ((r = sshbuf_put_u32(m, 1)) != 0 ||
+ (r = sshbuf_put_cstring(m, s->tty)) != 0)
+diff --git a/monitor.h b/monitor.h
+index 2b1a2d590..4d87284aa 100644
+--- a/monitor.h
++++ b/monitor.h
+@@ -65,6 +65,8 @@ enum monitor_reqtype {
+
+ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
+ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
++
++ MONITOR_REQ_AUTHROLE = 154,
+ };
+
+ struct ssh;
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index a4b66c56f..6fb47becb 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -375,10 +375,10 @@ mm_auth2_read_banner(void)
+ return (banner);
+ }
+
+-/* Inform the privileged process about service and style */
++/* Inform the privileged process about service, style, and role */
+
+ void
+-mm_inform_authserv(char *service, char *style)
++mm_inform_authserv(char *service, char *style, char *role)
+ {
+ struct sshbuf *m;
+ int r;
+@@ -388,7 +388,8 @@ mm_inform_authserv(char *service, char *style)
+ if ((m = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
+ if ((r = sshbuf_put_cstring(m, service)) != 0 ||
+- (r = sshbuf_put_cstring(m, style ? style : "")) != 0)
++ (r = sshbuf_put_cstring(m, style ? style : "")) != 0 ||
++ (r = sshbuf_put_cstring(m, role ? role : "")) != 0)
+ fatal_fr(r, "assemble");
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHSERV, m);
+@@ -396,6 +397,26 @@ mm_inform_authserv(char *service, char *style)
+ sshbuf_free(m);
+ }
+
++/* Inform the privileged process about role */
++
++void
++mm_inform_authrole(char *role)
++{
++ struct sshbuf *m;
++ int r;
++
++ debug3("%s entering", __func__);
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal("%s: sshbuf_new failed", __func__);
++ if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
++
++ sshbuf_free(m);
++}
++
+ /* Do the password authentication */
+ int
+ mm_auth_password(struct ssh *ssh, char *password)
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index 830fdb308..c84f96d0c 100644
+--- a/monitor_wrap.h
++++ b/monitor_wrap.h
+@@ -48,7 +48,8 @@ DH *mm_choose_dh(int, int, int);
+ int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
+ const u_char *, size_t, const char *, const char *,
+ const char *, u_int compat);
+-void mm_inform_authserv(char *, char *);
++void mm_inform_authserv(char *, char *, char *);
++void mm_inform_authrole(char *);
+ struct passwd *mm_getpwnamallow(struct ssh *, const char *);
+ char *mm_auth2_read_banner(void);
+ int mm_auth_password(struct ssh *, char *);
+diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
+index 77cb8213a..3a59ba1b7 100644
+--- a/openbsd-compat/port-linux.c
++++ b/openbsd-compat/port-linux.c
+@@ -56,7 +56,7 @@ ssh_selinux_enabled(void)
+
+ /* Return the default security context for the given username */
+ static char *
+-ssh_selinux_getctxbyname(char *pwname)
++ssh_selinux_getctxbyname(char *pwname, const char *role)
+ {
+ char *sc = NULL, *sename = NULL, *lvl = NULL;
+ int r;
+@@ -70,9 +70,16 @@ ssh_selinux_getctxbyname(char *pwname)
+ #endif
+
+ #ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
+- r = get_default_context_with_level(sename, lvl, NULL, &sc);
++ if (role != NULL && role[0])
++ r = get_default_context_with_rolelevel(sename, role, lvl, NULL,
++ &sc);
++ else
++ r = get_default_context_with_level(sename, lvl, NULL, &sc);
+ #else
+- r = get_default_context(sename, NULL, &sc);
++ if (role != NULL && role[0])
++ r = get_default_context_with_role(sename, role, NULL, &sc);
++ else
++ r = get_default_context(sename, NULL, &sc);
+ #endif
+
+ if (r != 0) {
+@@ -102,7 +109,7 @@ ssh_selinux_getctxbyname(char *pwname)
+
+ /* Set the execution context to the default for the specified user */
+ void
+-ssh_selinux_setup_exec_context(char *pwname)
++ssh_selinux_setup_exec_context(char *pwname, const char *role)
+ {
+ char *user_ctx = NULL;
+
+@@ -111,7 +118,7 @@ ssh_selinux_setup_exec_context(char *pwname)
+
+ debug3("%s: setting execution context", __func__);
+
+- user_ctx = ssh_selinux_getctxbyname(pwname);
++ user_ctx = ssh_selinux_getctxbyname(pwname, role);
+ if (setexeccon(user_ctx) != 0) {
+ switch (security_getenforce()) {
+ case -1:
+@@ -133,7 +140,7 @@ ssh_selinux_setup_exec_context(char *pwname)
+
+ /* Set the TTY context for the specified user */
+ void
+-ssh_selinux_setup_pty(char *pwname, const char *tty)
++ssh_selinux_setup_pty(char *pwname, const char *tty, const char *role)
+ {
+ char *new_tty_ctx = NULL, *user_ctx = NULL, *old_tty_ctx = NULL;
+ security_class_t chrclass;
+@@ -143,7 +150,7 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
+
+ debug3("%s: setting TTY context on %s", __func__, tty);
+
+- user_ctx = ssh_selinux_getctxbyname(pwname);
++ user_ctx = ssh_selinux_getctxbyname(pwname, role);
+
+ /* XXX: should these calls fatal() upon failure in enforcing mode? */
+
+diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
+index 3c22a854d..c88129428 100644
+--- a/openbsd-compat/port-linux.h
++++ b/openbsd-compat/port-linux.h
+@@ -19,8 +19,8 @@
+
+ #ifdef WITH_SELINUX
+ int ssh_selinux_enabled(void);
+-void ssh_selinux_setup_pty(char *, const char *);
+-void ssh_selinux_setup_exec_context(char *);
++void ssh_selinux_setup_pty(char *, const char *, const char *);
++void ssh_selinux_setup_exec_context(char *, const char *);
+ void ssh_selinux_change_context(const char *);
+ void ssh_selinux_setfscreatecon(const char *);
+ #endif
+diff --git a/platform.c b/platform.c
+index 4fe8744ee..70c3a9b58 100644
+--- a/platform.c
++++ b/platform.c
+@@ -144,7 +144,7 @@ platform_setusercontext(struct passwd *pw)
+ * called if sshd is running as root.
+ */
+ void
+-platform_setusercontext_post_groups(struct passwd *pw)
++platform_setusercontext_post_groups(struct passwd *pw, const char *role)
+ {
+ #if !defined(HAVE_LOGIN_CAP) && defined(USE_PAM)
+ /*
+@@ -185,7 +185,7 @@ platform_setusercontext_post_groups(struct passwd *pw)
+ }
+ #endif /* HAVE_SETPCRED */
+ #ifdef WITH_SELINUX
+- ssh_selinux_setup_exec_context(pw->pw_name);
++ ssh_selinux_setup_exec_context(pw->pw_name, role);
+ #endif
+ }
+
+diff --git a/platform.h b/platform.h
+index 7fef8c983..027fdfb51 100644
+--- a/platform.h
++++ b/platform.h
+@@ -25,7 +25,7 @@ void platform_post_fork_parent(pid_t child_pid);
+ void platform_post_fork_child(void);
+ int platform_privileged_uidswap(void);
+ void platform_setusercontext(struct passwd *);
+-void platform_setusercontext_post_groups(struct passwd *);
++void platform_setusercontext_post_groups(struct passwd *, const char *);
+ char *platform_get_krb5_client(const char *);
+ char *platform_krb5_get_principal_name(const char *);
+ int platform_locked_account(struct passwd *);
+diff --git a/session.c b/session.c
+index 96960f2da..492463529 100644
+--- a/session.c
++++ b/session.c
+@@ -1355,7 +1355,7 @@ safely_chroot(const char *path, uid_t uid)
+
+ /* Set login name, uid, gid, and groups. */
+ void
+-do_setusercontext(struct passwd *pw)
++do_setusercontext(struct passwd *pw, const char *role)
+ {
+ char uidstr[32], *chroot_path, *tmp;
+
+@@ -1383,7 +1383,7 @@ do_setusercontext(struct passwd *pw)
+ endgrent();
+ #endif
+
+- platform_setusercontext_post_groups(pw);
++ platform_setusercontext_post_groups(pw, role);
+
+ if (!in_chroot && options.chroot_directory != NULL &&
+ strcasecmp(options.chroot_directory, "none") != 0) {
+@@ -1527,7 +1527,7 @@ do_child(struct ssh *ssh, Session *s, const char *command)
+
+ /* Force a password change */
+ if (s->authctxt->force_pwchange) {
+- do_setusercontext(pw);
++ do_setusercontext(pw, s->authctxt->role);
+ child_close_fds(ssh);
+ do_pwchange(s);
+ exit(1);
+@@ -1545,7 +1545,7 @@ do_child(struct ssh *ssh, Session *s, const char *command)
+ /* When PAM is enabled we rely on it to do the nologin check */
+ if (!options.use_pam)
+ do_nologin(pw);
+- do_setusercontext(pw);
++ do_setusercontext(pw, s->authctxt->role);
+ /*
+ * PAM session modules in do_setusercontext may have
+ * generated messages, so if this in an interactive
+@@ -1941,7 +1941,7 @@ session_pty_req(struct ssh *ssh, Session *s)
+ sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
+
+ if (!use_privsep)
+- pty_setowner(s->pw, s->tty);
++ pty_setowner(s->pw, s->tty, s->authctxt->role);
+
+ /* Set window size from the packet. */
+ pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel);
+diff --git a/session.h b/session.h
+index 344a1ddf9..20ea822a7 100644
+--- a/session.h
++++ b/session.h
+@@ -77,7 +77,7 @@ void session_pty_cleanup2(Session *);
+ Session *session_new(void);
+ Session *session_by_tty(char *);
+ void session_close(struct ssh *, Session *);
+-void do_setusercontext(struct passwd *);
++void do_setusercontext(struct passwd *, const char *);
+
+ const char *session_get_remote_name_or_ip(struct ssh *, u_int, int);
+
+diff --git a/sshd.c b/sshd.c
+index 7944cf633..dd4a19131 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -584,7 +584,7 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt)
+ reseed_prngs();
+
+ /* Drop privileges */
+- do_setusercontext(authctxt->pw);
++ do_setusercontext(authctxt->pw, authctxt->role);
+
+ skip:
+ /* It is safe now to apply the key state */
+diff --git a/sshpty.c b/sshpty.c
+index cae0b977a..7870c6482 100644
+--- a/sshpty.c
++++ b/sshpty.c
+@@ -163,7 +163,7 @@ pty_change_window_size(int ptyfd, u_int row, u_int col,
+ }
+
+ void
+-pty_setowner(struct passwd *pw, const char *tty)
++pty_setowner(struct passwd *pw, const char *tty, const char *role)
+ {
+ struct group *grp;
+ gid_t gid;
+@@ -187,7 +187,7 @@ pty_setowner(struct passwd *pw, const char *tty)
+ strerror(errno));
+
+ #ifdef WITH_SELINUX
+- ssh_selinux_setup_pty(pw->pw_name, tty);
++ ssh_selinux_setup_pty(pw->pw_name, tty, role);
+ #endif
+
+ if (st.st_uid != pw->pw_uid || st.st_gid != gid) {
+diff --git a/sshpty.h b/sshpty.h
+index 9ec7e9a15..de7e000ae 100644
+--- a/sshpty.h
++++ b/sshpty.h
+@@ -24,5 +24,5 @@ int pty_allocate(int *, int *, char *, size_t);
+ void pty_release(const char *);
+ void pty_make_controlling_tty(int *, const char *);
+ void pty_change_window_size(int, u_int, u_int, u_int, u_int);
+-void pty_setowner(struct passwd *, const char *);
++void pty_setowner(struct passwd *, const char *, const char *);
+ void disconnect_controlling_tty(void);
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..f9d3791
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,35 @@
+gssapi.patch
+restore-tcp-wrappers.patch
+selinux-role.patch
+ssh-vulnkey-compat.patch
+keepalive-extensions.patch
+syslog-level-silent.patch
+user-group-modes.patch
+scp-quoting.patch
+shell-path.patch
+dnssec-sshfp.patch
+mention-ssh-keygen-on-keychange.patch
+package-versioning.patch
+debian-banner.patch
+authorized-keys-man-symlink.patch
+openbsd-docs.patch
+ssh-argv0.patch
+doc-hash-tab-completion.patch
+ssh-agent-setgid.patch
+no-openssl-version-status.patch
+gnome-ssh-askpass2-icon.patch
+systemd-readiness.patch
+debian-config.patch
+restore-authorized_keys2.patch
+revert-ipqos-defaults.patch
+maxhostnamelen.patch
+conch-ssh-rsa.patch
+systemd-socket-activation.patch
+remove-spurious-ssh-agent-options.patch
+CVE-2023-38408-1.patch
+CVE-2023-38408-2.patch
+CVE-2023-38408-3.patch
+CVE-2023-28531.patch
+CVE-2023-48795.patch
+CVE-2023-51384.patch
+CVE-2023-51385.patch
diff --git a/debian/patches/shell-path.patch b/debian/patches/shell-path.patch
new file mode 100644
index 0000000..5f839df
--- /dev/null
+++ b/debian/patches/shell-path.patch
@@ -0,0 +1,39 @@
+From b364a18c85a959fdfd0a5a2c497482809cadf29f Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:00 +0000
+Subject: Look for $SHELL on the path for ProxyCommand/LocalCommand
+
+There's some debate on the upstream bug about whether POSIX requires this.
+I (Colin Watson) agree with Vincent and think it does.
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1494
+Bug-Debian: http://bugs.debian.org/492728
+Last-Update: 2020-02-21
+
+Patch-Name: shell-path.patch
+---
+ sshconnect.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/sshconnect.c b/sshconnect.c
+index 792bc34bc..891e968dc 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -248,7 +248,7 @@ ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg,
+ * extra privileges above.
+ */
+ ssh_signal(SIGPIPE, SIG_DFL);
+- execv(argv[0], argv);
++ execvp(argv[0], argv);
+ perror(argv[0]);
+ exit(1);
+ }
+@@ -1671,7 +1671,7 @@ ssh_local_cmd(const char *args)
+ if (pid == 0) {
+ ssh_signal(SIGPIPE, SIG_DFL);
+ debug3("Executing %s -c \"%s\"", shell, args);
+- execl(shell, shell, "-c", args, (char *)NULL);
++ execlp(shell, shell, "-c", args, (char *)NULL);
+ error("Couldn't execute %s -c \"%s\": %s",
+ shell, args, strerror(errno));
+ _exit(1);
diff --git a/debian/patches/ssh-agent-setgid.patch b/debian/patches/ssh-agent-setgid.patch
new file mode 100644
index 0000000..14478d3
--- /dev/null
+++ b/debian/patches/ssh-agent-setgid.patch
@@ -0,0 +1,40 @@
+From bf54d67a00bf4d408f0e52236c4248cecfb5177f Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:13 +0000
+Subject: Document consequences of ssh-agent being setgid in ssh-agent(1)
+
+Bug-Debian: http://bugs.debian.org/711623
+Forwarded: no
+Last-Update: 2020-02-21
+
+Patch-Name: ssh-agent-setgid.patch
+---
+ ssh-agent.1 | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/ssh-agent.1 b/ssh-agent.1
+index b0bf65da8..ba418bb41 100644
+--- a/ssh-agent.1
++++ b/ssh-agent.1
+@@ -230,6 +230,21 @@ socket and stores its pathname in this variable.
+ It is accessible only to the current user,
+ but is easily abused by root or another instance of the same user.
+ .El
++.Pp
++In Debian,
++.Nm
++is installed with the set-group-id bit set, to prevent
++.Xr ptrace 2
++attacks retrieving private key material.
++This has the side-effect of causing the run-time linker to remove certain
++environment variables which might have security implications for set-id
++programs, including
++.Ev LD_PRELOAD ,
++.Ev LD_LIBRARY_PATH ,
++and
++.Ev TMPDIR .
++If you need to set any of these environment variables, you will need to do
++so in the program executed by ssh-agent.
+ .Sh FILES
+ .Bl -tag -width Ds
+ .It Pa $TMPDIR/ssh-XXXXXXXXXX/agent.<ppid>
diff --git a/debian/patches/ssh-argv0.patch b/debian/patches/ssh-argv0.patch
new file mode 100644
index 0000000..756d80e
--- /dev/null
+++ b/debian/patches/ssh-argv0.patch
@@ -0,0 +1,31 @@
+From b252064f6d116feca5d07dfe6dfd62ba005927bd Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:10:10 +0000
+Subject: ssh(1): Refer to ssh-argv0(1)
+
+Old versions of OpenSSH (up to 2.5 or thereabouts) allowed creating symlinks
+to ssh with the name of the host you want to connect to. Debian ships an
+ssh-argv0 script restoring this feature; this patch refers to its manual
+page from ssh(1).
+
+Bug-Debian: http://bugs.debian.org/111341
+Forwarded: not-needed
+Last-Update: 2013-09-14
+
+Patch-Name: ssh-argv0.patch
+---
+ ssh.1 | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/ssh.1 b/ssh.1
+index 6b56f5638..d07cd7840 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1667,6 +1667,7 @@ if an error occurred.
+ .Xr sftp 1 ,
+ .Xr ssh-add 1 ,
+ .Xr ssh-agent 1 ,
++.Xr ssh-argv0 1 ,
+ .Xr ssh-keygen 1 ,
+ .Xr ssh-keyscan 1 ,
+ .Xr tun 4 ,
diff --git a/debian/patches/ssh-vulnkey-compat.patch b/debian/patches/ssh-vulnkey-compat.patch
new file mode 100644
index 0000000..f37f827
--- /dev/null
+++ b/debian/patches/ssh-vulnkey-compat.patch
@@ -0,0 +1,42 @@
+From 3878210a9526dc6c78c48d959bab0afb0052b64f Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@ubuntu.com>
+Date: Sun, 9 Feb 2014 16:09:50 +0000
+Subject: Accept obsolete ssh-vulnkey configuration options
+
+These options were used as part of Debian's response to CVE-2008-0166.
+Nearly six years later, we no longer need to continue carrying the bulk
+of that patch, but we do need to avoid failing when the associated
+configuration options are still present.
+
+Last-Update: 2014-02-09
+
+Patch-Name: ssh-vulnkey-compat.patch
+---
+ readconf.c | 1 +
+ servconf.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/readconf.c b/readconf.c
+index 55af0115d..4a39f5b66 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -194,6 +194,7 @@ static struct {
+ { "fallbacktorsh", oDeprecated },
+ { "globalknownhostsfile2", oDeprecated },
+ { "rhostsauthentication", oDeprecated },
++ { "useblacklistedkeys", oDeprecated },
+ { "userknownhostsfile2", oDeprecated },
+ { "useroaming", oDeprecated },
+ { "usersh", oDeprecated },
+diff --git a/servconf.c b/servconf.c
+index 940de9e15..6529f2736 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -651,6 +651,7 @@ static struct {
+ { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
+ { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
+ { "strictmodes", sStrictModes, SSHCFG_GLOBAL },
++ { "permitblacklistedkeys", sDeprecated, SSHCFG_GLOBAL },
+ { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
+ { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
+ { "uselogin", sDeprecated, SSHCFG_GLOBAL },
diff --git a/debian/patches/syslog-level-silent.patch b/debian/patches/syslog-level-silent.patch
new file mode 100644
index 0000000..e1a8aee
--- /dev/null
+++ b/debian/patches/syslog-level-silent.patch
@@ -0,0 +1,47 @@
+From ac80435d753ff39d9c6ded2f7535d770f257fc59 Mon Sep 17 00:00:00 2001
+From: Natalie Amery <nmamery@chiark.greenend.org.uk>
+Date: Sun, 9 Feb 2014 16:09:54 +0000
+Subject: "LogLevel SILENT" compatibility
+
+"LogLevel SILENT" (-qq) was introduced in Debian openssh 1:3.0.1p1-1 to
+match the behaviour of non-free SSH, in which -q does not suppress fatal
+errors. However, this was unintentionally broken in 1:4.6p1-2 and nobody
+complained, so we've dropped most of it. The parts that remain are basic
+configuration file compatibility, and an adjustment to "Pseudo-terminal will
+not be allocated ..." which should be split out into a separate patch.
+
+Author: Matthew Vernon <matthew@debian.org>
+Author: Colin Watson <cjwatson@debian.org>
+Last-Update: 2013-09-14
+
+Patch-Name: syslog-level-silent.patch
+---
+ log.c | 1 +
+ ssh.c | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/log.c b/log.c
+index 99bf046a7..bdc4b6515 100644
+--- a/log.c
++++ b/log.c
+@@ -96,6 +96,7 @@ static struct {
+ LogLevel val;
+ } log_levels[] =
+ {
++ { "SILENT", SYSLOG_LEVEL_QUIET }, /* compatibility */
+ { "QUIET", SYSLOG_LEVEL_QUIET },
+ { "FATAL", SYSLOG_LEVEL_FATAL },
+ { "ERROR", SYSLOG_LEVEL_ERROR },
+diff --git a/ssh.c b/ssh.c
+index 855777d7f..8b6b54558 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -1363,7 +1363,7 @@ main(int ac, char **av)
+ /* Do not allocate a tty if stdin is not a tty. */
+ if ((!isatty(fileno(stdin)) || options.stdin_null) &&
+ options.request_tty != REQUEST_TTY_FORCE) {
+- if (tty_flag)
++ if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)
+ logit("Pseudo-terminal will not be allocated because "
+ "stdin is not a terminal.");
+ tty_flag = 0;
diff --git a/debian/patches/systemd-readiness.patch b/debian/patches/systemd-readiness.patch
new file mode 100644
index 0000000..8f9f543
--- /dev/null
+++ b/debian/patches/systemd-readiness.patch
@@ -0,0 +1,84 @@
+From 5d04f3ebd2825c03fa7c39e27c28bf3384345806 Mon Sep 17 00:00:00 2001
+From: Michael Biebl <biebl@debian.org>
+Date: Mon, 21 Dec 2015 16:08:47 +0000
+Subject: Add systemd readiness notification support
+
+Bug-Debian: https://bugs.debian.org/778913
+Forwarded: no
+Last-Update: 2017-08-22
+
+Patch-Name: systemd-readiness.patch
+---
+ configure.ac | 24 ++++++++++++++++++++++++
+ sshd.c | 9 +++++++++
+ 2 files changed, 33 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 0c59c8b50..23423e48d 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -4915,6 +4915,29 @@ AC_SUBST([GSSLIBS])
+ AC_SUBST([K5LIBS])
+ AC_SUBST([CHANNELLIBS])
+
++# Check whether user wants systemd support
++SYSTEMD_MSG="no"
++AC_ARG_WITH(systemd,
++ [ --with-systemd Enable systemd support],
++ [ if test "x$withval" != "xno" ; then
++ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
++ if test "$PKGCONFIG" != "no"; then
++ AC_MSG_CHECKING([for libsystemd])
++ if $PKGCONFIG --exists libsystemd; then
++ SYSTEMD_CFLAGS=`$PKGCONFIG --cflags libsystemd`
++ SYSTEMD_LIBS=`$PKGCONFIG --libs libsystemd`
++ CPPFLAGS="$CPPFLAGS $SYSTEMD_CFLAGS"
++ SSHDLIBS="$SSHDLIBS $SYSTEMD_LIBS"
++ AC_MSG_RESULT([yes])
++ AC_DEFINE(HAVE_SYSTEMD, 1, [Define if you want systemd support.])
++ SYSTEMD_MSG="yes"
++ else
++ AC_MSG_RESULT([no])
++ fi
++ fi
++ fi ]
++)
++
+ # Looking for programs, paths and files
+
+ PRIVSEP_PATH=/var/empty
+@@ -5715,6 +5738,7 @@ echo " libldns support: $LDNS_MSG"
+ echo " Solaris process contract support: $SPC_MSG"
+ echo " Solaris project support: $SP_MSG"
+ echo " Solaris privilege support: $SPP_MSG"
++echo " systemd support: $SYSTEMD_MSG"
+ echo " IP address in \$DISPLAY hack: $DISPLAY_HACK_MSG"
+ echo " Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG"
+ echo " BSD Auth support: $BSD_AUTH_MSG"
+diff --git a/sshd.c b/sshd.c
+index b4ee435e5..0a4eefe01 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -88,6 +88,10 @@
+ #include <prot.h>
+ #endif
+
++#ifdef HAVE_SYSTEMD
++#include <systemd/sd-daemon.h>
++#endif
++
+ #include "xmalloc.h"
+ #include "ssh.h"
+ #include "ssh2.h"
+@@ -2094,6 +2098,11 @@ main(int ac, char **av)
+ }
+ }
+
++#ifdef HAVE_SYSTEMD
++ /* Signal systemd that we are ready to accept connections */
++ sd_notify(0, "READY=1");
++#endif
++
+ /* Accept a connection and return in a forked child */
+ server_accept_loop(&sock_in, &sock_out,
+ &newsock, config_s);
diff --git a/debian/patches/systemd-socket-activation.patch b/debian/patches/systemd-socket-activation.patch
new file mode 100644
index 0000000..5441622
--- /dev/null
+++ b/debian/patches/systemd-socket-activation.patch
@@ -0,0 +1,138 @@
+From 4cedd1c9acac0fba598db2eaf43278dfe8e53ef0 Mon Sep 17 00:00:00 2001
+From: Steve Langasek <steve.langasek@ubuntu.com>
+Date: Thu, 1 Sep 2022 16:03:37 +0100
+Subject: Support systemd socket activation
+
+Unlike inetd socket activation, with systemd socket activation the
+supervisor passes the listened-on socket to the child process and lets
+the child process handle the accept(). This lets us do delayed start
+of the sshd daemon without becoming incompatible with config options
+like ClientAliveCountMax.
+
+Last-Update: 2022-09-01
+
+Patch-Name: systemd-socket-activation.patch
+---
+ sshd.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++---------
+ 1 file changed, 75 insertions(+), 14 deletions(-)
+
+diff --git a/sshd.c b/sshd.c
+index 0a4eefe01..fc22fcb62 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -141,10 +141,16 @@ int deny_severity;
+ #endif /* LIBWRAP */
+
+ /* Re-exec fds */
+-#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1)
+-#define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2)
+-#define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3)
+-#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4)
++#ifdef HAVE_SYSTEMD
++#define SYSTEMD_OFFSET sd_listen_fds(0)
++#else
++#define SYSTEMD_OFFSET 0
++#endif
++
++#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1 + SYSTEMD_OFFSET)
++#define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2 + SYSTEMD_OFFSET)
++#define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3 + SYSTEMD_OFFSET)
++#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4 + SYSTEMD_OFFSET)
+
+ extern char *__progname;
+
+@@ -1025,6 +1031,48 @@ server_accept_inetd(int *sock_in, int *sock_out)
+ debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out);
+ }
+
++#ifdef HAVE_SYSTEMD
++/*
++ * Configure our socket fds that were passed from systemd
++ */
++static void
++setup_systemd_socket(int listen_sock)
++{
++ int ret;
++ struct sockaddr_storage addr;
++ socklen_t len = sizeof(addr);
++ char ntop[NI_MAXHOST], strport[NI_MAXSERV];
++
++ if (getsockname(listen_sock, (struct sockaddr *)&addr, &len) != 0)
++ return;
++
++ if (((struct sockaddr *)&addr)->sa_family != AF_INET
++ && ((struct sockaddr *)&addr)->sa_family != AF_INET6)
++ return;
++ if (num_listen_socks >= MAX_LISTEN_SOCKS)
++ fatal("Too many listen sockets. "
++ "Enlarge MAX_LISTEN_SOCKS");
++ if ((ret = getnameinfo((struct sockaddr *)&addr, len, ntop,
++ sizeof(ntop), strport, sizeof(strport),
++ NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
++ error("getnameinfo failed: %.100s",
++ ssh_gai_strerror(ret));
++ return;
++ }
++ if (set_nonblock(listen_sock) == -1) {
++ close(listen_sock);
++ return;
++ }
++ /* Socket options */
++ set_reuseaddr(listen_sock);
++
++ listen_socks[num_listen_socks] = listen_sock;
++ num_listen_socks++;
++
++ logit("Server listening on %s port %s.", ntop, strport);
++}
++#endif
++
+ /*
+ * Listen for TCP connections
+ */
+@@ -1104,22 +1152,35 @@ static void
+ server_listen(void)
+ {
+ u_int i;
++#ifdef HAVE_SYSTEMD
++ int systemd_socket_count;
++#endif
+
+ /* Initialise per-source limit tracking. */
+ srclimit_init(options.max_startups, options.per_source_max_startups,
+ options.per_source_masklen_ipv4, options.per_source_masklen_ipv6);
+
+- for (i = 0; i < options.num_listen_addrs; i++) {
+- listen_on_addrs(&options.listen_addrs[i]);
+- freeaddrinfo(options.listen_addrs[i].addrs);
+- free(options.listen_addrs[i].rdomain);
+- memset(&options.listen_addrs[i], 0,
+- sizeof(options.listen_addrs[i]));
++#ifdef HAVE_SYSTEMD
++ systemd_socket_count = sd_listen_fds(0);
++ if (systemd_socket_count > 0)
++ {
++ int i;
++ for (i = 0; i < systemd_socket_count; i++)
++ setup_systemd_socket(SD_LISTEN_FDS_START + i);
++ } else
++#endif
++ {
++ for (i = 0; i < options.num_listen_addrs; i++) {
++ listen_on_addrs(&options.listen_addrs[i]);
++ freeaddrinfo(options.listen_addrs[i].addrs);
++ free(options.listen_addrs[i].rdomain);
++ memset(&options.listen_addrs[i], 0,
++ sizeof(options.listen_addrs[i]));
++ }
++ free(options.listen_addrs);
++ options.listen_addrs = NULL;
++ options.num_listen_addrs = 0;
+ }
+- free(options.listen_addrs);
+- options.listen_addrs = NULL;
+- options.num_listen_addrs = 0;
+-
+ if (!num_listen_socks)
+ fatal("Cannot bind any address.");
+ }
diff --git a/debian/patches/user-group-modes.patch b/debian/patches/user-group-modes.patch
new file mode 100644
index 0000000..51a233b
--- /dev/null
+++ b/debian/patches/user-group-modes.patch
@@ -0,0 +1,210 @@
+From ad9efda53c54f37dbd429c16db4be2946f27063e Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sun, 9 Feb 2014 16:09:58 +0000
+Subject: Allow harmless group-writability
+
+Allow secure files (~/.ssh/config, ~/.ssh/authorized_keys, etc.) to be
+group-writable, provided that the group in question contains only the file's
+owner. Rejected upstream for IMO incorrect reasons (e.g. a misunderstanding
+about the contents of gr->gr_mem). Given that per-user groups and umask 002
+are the default setup in Debian (for good reasons - this makes operating in
+setgid directories with other groups much easier), we need to permit this by
+default.
+
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1060
+Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=314347
+Last-Update: 2022-02-23
+
+Patch-Name: user-group-modes.patch
+---
+ auth-rhosts.c | 6 ++----
+ auth.c | 3 +--
+ misc.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++-----
+ misc.h | 2 ++
+ readconf.c | 3 +--
+ ssh.1 | 2 ++
+ ssh_config.5 | 2 ++
+ 7 files changed, 62 insertions(+), 13 deletions(-)
+
+diff --git a/auth-rhosts.c b/auth-rhosts.c
+index 56724677a..e15f5bc5a 100644
+--- a/auth-rhosts.c
++++ b/auth-rhosts.c
+@@ -266,8 +266,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
+ return 0;
+ }
+ if (options.strict_modes &&
+- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
+- (st.st_mode & 022) != 0)) {
++ !secure_permissions(&st, pw->pw_uid)) {
+ logit("Rhosts authentication refused for %.100s: "
+ "bad ownership or modes for home directory.", pw->pw_name);
+ auth_debug_add("Rhosts authentication refused for %.100s: "
+@@ -296,8 +295,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
+ * allowing access to their account by anyone.
+ */
+ if (options.strict_modes &&
+- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
+- (st.st_mode & 022) != 0)) {
++ !secure_permissions(&st, pw->pw_uid)) {
+ logit("Rhosts authentication refused for %.100s: "
+ "bad modes for %.200s", pw->pw_name, path);
+ auth_debug_add("Bad file modes for %.200s", path);
+diff --git a/auth.c b/auth.c
+index 946dc6074..6653b9e15 100644
+--- a/auth.c
++++ b/auth.c
+@@ -432,8 +432,7 @@ check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host,
+ user_hostfile = tilde_expand_filename(userfile, pw->pw_uid);
+ if (options.strict_modes &&
+ (stat(user_hostfile, &st) == 0) &&
+- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
+- (st.st_mode & 022) != 0)) {
++ !secure_permissions(&st, pw->pw_uid)) {
+ logit("Authentication refused for %.100s: "
+ "bad owner or modes for %.200s",
+ pw->pw_name, user_hostfile);
+diff --git a/misc.c b/misc.c
+index c098dc610..2268d8875 100644
+--- a/misc.c
++++ b/misc.c
+@@ -55,9 +55,9 @@
+ #include <netdb.h>
+ #ifdef HAVE_PATHS_H
+ # include <paths.h>
++#endif
+ #include <pwd.h>
+ #include <grp.h>
+-#endif
+ #ifdef SSH_TUN_OPENBSD
+ #include <net/if.h>
+ #endif
+@@ -1385,6 +1385,55 @@ percent_dollar_expand(const char *string, ...)
+ return ret;
+ }
+
++int
++secure_permissions(struct stat *st, uid_t uid)
++{
++ if (!platform_sys_dir_uid(st->st_uid) && st->st_uid != uid)
++ return 0;
++ if ((st->st_mode & 002) != 0)
++ return 0;
++ if ((st->st_mode & 020) != 0) {
++ /* If the file is group-writable, the group in question must
++ * have exactly one member, namely the file's owner.
++ * (Zero-member groups are typically used by setgid
++ * binaries, and are unlikely to be suitable.)
++ */
++ struct passwd *pw;
++ struct group *gr;
++ int members = 0;
++
++ gr = getgrgid(st->st_gid);
++ if (!gr)
++ return 0;
++
++ /* Check primary group memberships. */
++ while ((pw = getpwent()) != NULL) {
++ if (pw->pw_gid == gr->gr_gid) {
++ ++members;
++ if (pw->pw_uid != uid)
++ return 0;
++ }
++ }
++ endpwent();
++
++ pw = getpwuid(st->st_uid);
++ if (!pw)
++ return 0;
++
++ /* Check supplementary group memberships. */
++ if (gr->gr_mem[0]) {
++ ++members;
++ if (strcmp(pw->pw_name, gr->gr_mem[0]) ||
++ gr->gr_mem[1])
++ return 0;
++ }
++
++ if (!members)
++ return 0;
++ }
++ return 1;
++}
++
+ int
+ tun_open(int tun, int mode, char **ifname)
+ {
+@@ -2194,8 +2243,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir,
+ snprintf(err, errlen, "%s is not a regular file", buf);
+ return -1;
+ }
+- if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
+- (stp->st_mode & 022) != 0) {
++ if (!secure_permissions(stp, uid)) {
+ snprintf(err, errlen, "bad ownership or modes for file %s",
+ buf);
+ return -1;
+@@ -2210,8 +2258,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir,
+ strlcpy(buf, cp, sizeof(buf));
+
+ if (stat(buf, &st) == -1 ||
+- (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
+- (st.st_mode & 022) != 0) {
++ !secure_permissions(&st, uid)) {
+ snprintf(err, errlen,
+ "bad ownership or modes for directory %s", buf);
+ return -1;
+diff --git a/misc.h b/misc.h
+index 84d93e059..31b6557c6 100644
+--- a/misc.h
++++ b/misc.h
+@@ -233,6 +233,8 @@ struct notifier_ctx *notify_start(int, const char *, ...)
+ void notify_complete(struct notifier_ctx *, const char *, ...)
+ __attribute__((format(printf, 2, 3)));
+
++int secure_permissions(struct stat *st, uid_t uid);
++
+ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+ #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+ #define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y))
+diff --git a/readconf.c b/readconf.c
+index 9ac71133a..18e814039 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2319,8 +2319,7 @@ read_config_file_depth(const char *filename, struct passwd *pw,
+
+ if (fstat(fileno(f), &sb) == -1)
+ fatal("fstat %s: %s", filename, strerror(errno));
+- if (((sb.st_uid != 0 && sb.st_uid != getuid()) ||
+- (sb.st_mode & 022) != 0))
++ if (!secure_permissions(&sb, getuid()))
+ fatal("Bad owner or permissions on %s", filename);
+ }
+
+diff --git a/ssh.1 b/ssh.1
+index dfd80b8bc..4028f971a 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1563,6 +1563,8 @@ The file format and configuration options are described in
+ .Xr ssh_config 5 .
+ Because of the potential for abuse, this file must have strict permissions:
+ read/write for the user, and not writable by others.
++It may be group-writable provided that the group in question contains only
++the user.
+ .Pp
+ .It Pa ~/.ssh/environment
+ Contains additional definitions for environment variables; see
+diff --git a/ssh_config.5 b/ssh_config.5
+index 09905bff3..92600ed6e 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -2258,6 +2258,8 @@ The format of this file is described above.
+ This file is used by the SSH client.
+ Because of the potential for abuse, this file must have strict permissions:
+ read/write for the user, and not writable by others.
++It may be group-writable provided that the group in question contains only
++the user.
+ .It Pa /etc/ssh/ssh_config
+ Systemwide configuration file.
+ This file provides defaults for those