summaryrefslogtreecommitdiffstats
path: root/debian/patches
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 11:13:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 11:13:19 +0000
commit94597021fbf5b12b369b7bb3c13af715d8be8c4e (patch)
tree5195a1ab18ed95e7ae2e5bf5e74396b00861a6b7 /debian/patches
parentAdding upstream version 1:8.4p1. (diff)
downloadopenssh-debian/1%8.4p1-5+deb11u3.tar.xz
openssh-debian/1%8.4p1-5+deb11u3.zip
Adding debian version 1:8.4p1-5+deb11u3.debian/1%8.4p1-5+deb11u3debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--debian/patches/CVE-2021-41617-1.patch35
-rw-r--r--debian/patches/CVE-2021-41617-2.patch26
-rw-r--r--debian/patches/CVE-2023-38408-1.patch30
-rw-r--r--debian/patches/CVE-2023-38408-2.patch171
-rw-r--r--debian/patches/CVE-2023-48795.patch460
-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-old-privkey-format.patch68
-rw-r--r--debian/patches/debian-banner.patch162
-rw-r--r--debian/patches/debian-config.patch270
-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.patch4002
-rw-r--r--debian/patches/keepalive-extensions.patch135
-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.patch148
-rw-r--r--debian/patches/package-versioning.patch47
-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/revert-x32-sandbox-breakage.patch39
-rw-r--r--debian/patches/sandbox-pselect6_time64.patch32
-rw-r--r--debian/patches/scp-quoting.patch41
-rw-r--r--debian/patches/selinux-role.patch472
-rw-r--r--debian/patches/series35
-rw-r--r--debian/patches/shell-path.patch39
-rw-r--r--debian/patches/ssh-agent-double-free.patch26
-rw-r--r--debian/patches/ssh-agent-setgid.patch40
-rw-r--r--debian/patches/ssh-argv0.patch31
-rw-r--r--debian/patches/ssh-copy-id-heredoc-syntax.patch37
-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/user-group-modes.patch210
36 files changed, 7403 insertions, 0 deletions
diff --git a/debian/patches/CVE-2021-41617-1.patch b/debian/patches/CVE-2021-41617-1.patch
new file mode 100644
index 0000000..7d89b7e
--- /dev/null
+++ b/debian/patches/CVE-2021-41617-1.patch
@@ -0,0 +1,35 @@
+From ad2748dee50e4c0040f6efda5eff4a34e4eb5b85 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Sun, 26 Sep 2021 14:01:03 +0000
+Subject: upstream: need initgroups() before setresgid(); reported by anton@,
+
+ok deraadt@
+
+OpenBSD-Commit-ID: 6aa003ee658b316960d94078f2a16edbc25087ce
+
+Bug-Debian: https://bugs.debian.org/995130
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=f3cbe43e28fe71427d41cfe3a17125b972710455
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2021-41617-1.patch
+---
+ auth.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/auth.c b/auth.c
+index 4152d9c44..32870851b 100644
+--- a/auth.c
++++ b/auth.c
+@@ -853,6 +853,12 @@ subprocess(const char *tag, struct passwd *pw, const char *command,
+ }
+ closefrom(STDERR_FILENO + 1);
+
++ if (geteuid() == 0 &&
++ initgroups(pw->pw_name, pw->pw_gid) == -1) {
++ error("%s: initgroups(%s, %u): %s", tag,
++ pw->pw_name, (u_int)pw->pw_gid, strerror(errno));
++ _exit(1);
++ }
+ /* Don't use permanently_set_uid() here to avoid fatal() */
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) {
+ error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
diff --git a/debian/patches/CVE-2021-41617-2.patch b/debian/patches/CVE-2021-41617-2.patch
new file mode 100644
index 0000000..62a066b
--- /dev/null
+++ b/debian/patches/CVE-2021-41617-2.patch
@@ -0,0 +1,26 @@
+From 0afd6ea47554dee40d1eaf33fa73d693fb661e64 Mon Sep 17 00:00:00 2001
+From: Damien Miller <djm@mindrot.org>
+Date: Mon, 27 Sep 2021 00:03:19 +1000
+Subject: initgroups needs grp.h
+
+Bug-Debian: https://bugs.debian.org/995130
+Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=bf944e3794eff5413f2df1ef37cddf96918c6bde
+Last-Update: 2023-12-19
+
+Patch-Name: CVE-2021-41617-2.patch
+---
+ auth.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/auth.c b/auth.c
+index 32870851b..88578ec45 100644
+--- a/auth.c
++++ b/auth.c
+@@ -39,6 +39,7 @@
+ # include <paths.h>
+ #endif
+ #include <pwd.h>
++#include <grp.h>
+ #ifdef HAVE_LOGIN_H
+ #include <login.h>
+ #endif
diff --git a/debian/patches/CVE-2023-38408-1.patch b/debian/patches/CVE-2023-38408-1.patch
new file mode 100644
index 0000000..b70fd9f
--- /dev/null
+++ b/debian/patches/CVE-2023-38408-1.patch
@@ -0,0 +1,30 @@
+From 8175e38eaf5636f45c3f27f4eadee1d583b70d35 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 f495883d1..d864051c4 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -1519,10 +1519,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..7ee3c13
--- /dev/null
+++ b/debian/patches/CVE-2023-38408-2.patch
@@ -0,0 +1,171 @@
+From fb685ebb9f8391ab2836715c9c347ee50a0c9f48 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: backport, https://anongit.mindrot.org/openssh.git/commit/?id=29ef8a04866ca14688d5b7fed7b8b9deab851f77
+Last-Update: 2023-09-17
+
+Patch-Name: CVE-2023-38408-2.patch
+---
+ misc.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ misc.h | 1 +
+ ssh-pkcs11.c | 4 +++
+ ssh-sk.c | 6 ++--
+ 4 files changed, 88 insertions(+), 2 deletions(-)
+
+diff --git a/misc.c b/misc.c
+index c75a795c2..c6e9e71a3 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>
+@@ -2463,3 +2467,78 @@ ssh_signal(int signum, sshsig_t handler)
+ }
+ return osa.sa_handler;
+ }
++
++/*
++ * 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("%s: nlist failed for %s", __func__, path);
++ goto out;
++ }
++ if (r != 0 || nl[0].n_value == 0 || nl[0].n_type == 0) {
++ error("%s: library %s does not contain symbol %s",
++ __func__, 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("%s: open %s: %s", __func__, path, strerror(errno));
++ return -1;
++ }
++ if (fstat(fd, &st) != 0) {
++ error("%s: fstat %s: %s", __func__, path, strerror(errno));
++ goto out;
++ }
++ if (!S_ISREG(st.st_mode)) {
++ error("%s: %s is not a regular file", __func__, path);
++ goto out;
++ }
++ if (st.st_size < 0 ||
++ (size_t)st.st_size < strlen(s) ||
++ st.st_size >= INT_MAX/2) {
++ error("%s: %s bad size %lld",
++ __func__, 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("%s: mmap %s: %s", __func__, path, strerror(errno));
++ goto out;
++ }
++ if (memmem(m, sz, s, strlen(s)) == NULL) {
++ error("%s: %s does not contain expected string %s",
++ __func__, 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 b34c798e7..eb30bb454 100644
+--- a/misc.h
++++ b/misc.h
+@@ -90,6 +90,7 @@ const char *atoi_err(const char *, int *);
+ int parse_absolute_time(const char *, uint64_t *);
+ void format_absolute_time(uint64_t, char *, size_t);
+ int path_absolute(const char *);
++int lib_contains_symbol(const char *, const char *);
+
+ void sock_set_v6only(int);
+
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index d864051c4..df4d53626 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -1514,6 +1514,10 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ __func__, 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 1455df635..76fee8af2 100644
+--- a/ssh-sk.c
++++ b/ssh-sk.c
+@@ -123,10 +123,12 @@ sshsk_open(const char *path)
+ #endif
+ return ret;
+ }
+- 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..8f14f86
--- /dev/null
+++ b/debian/patches/CVE-2023-48795.patch
@@ -0,0 +1,460 @@
+From 802a7af111c9ddb438ca4fd8c5cc35534e199fda 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-21
+
+Patch-Name: CVE-2023-48795.patch
+---
+ PROTOCOL | 26 +++++++++++++++++
+ kex.c | 68 ++++++++++++++++++++++++++++++++-----------
+ kex.h | 1 +
+ packet.c | 80 ++++++++++++++++++++++++++++++++++++++-------------
+ sshconnect2.c | 14 +++------
+ sshd.c | 8 ++++--
+ 6 files changed, 149 insertions(+), 48 deletions(-)
+
+diff --git a/PROTOCOL b/PROTOCOL
+index ecdacb9dc..bbe7ea560 100644
+--- a/PROTOCOL
++++ b/PROTOCOL
+@@ -102,6 +102,32 @@ OpenSSH supports the use of ECDH in Curve25519 for key exchange as
+ described at:
+ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.org.txt?h=curve25519
+
++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 763c45536..bda0473f7 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 *proposal_names[PROPOSAL_MAX] = {
+@@ -207,6 +207,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.
+@@ -214,7 +226,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')
+@@ -231,10 +243,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);
+@@ -466,7 +476,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("%s: type %u seq %u", __func__, type, seq);
+ if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 ||
+ (r = sshpkt_put_u32(ssh, seq)) != 0 ||
+ (r = sshpkt_send(ssh)) != 0)
+@@ -538,6 +553,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;
+@@ -638,7 +658,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
+ error("%s: no hex", __func__);
+ 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;
+@@ -674,7 +694,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)
+@@ -939,7 +959,13 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX])
+ }
+
+ static int
+-kex_choose_conf(struct ssh *ssh)
++kexalgs_contains(char **peer, const char *ext)
++{
++ return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext);
++}
++
++static int
++kex_choose_conf(struct ssh *ssh, uint32_t seq)
+ {
+ struct kex *kex = ssh->kex;
+ struct newkeys *newkeys;
+@@ -964,13 +990,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("%s: will use strict KEX ordering", __func__);
++ if (seq != 0)
++ ssh_packet_disconnect(ssh,
++ "strict KEX violation: "
++ "KEXINIT was not the first packet");
++ }
+ }
+
+ /* Algorithm Negotiation */
+diff --git a/kex.h b/kex.h
+index 938dca03b..c60e592ec 100644
+--- a/kex.h
++++ b/kex.h
+@@ -154,6 +154,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 00e3180cb..78c357b14 100644
+--- a/packet.c
++++ b/packet.c
+@@ -1208,8 +1208,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;
+@@ -1217,6 +1222,12 @@ 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("%s: resetting send seqnr %u", __func__,
++ 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)
+@@ -1350,8 +1361,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)
+@@ -1638,10 +1648,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;
+@@ -1707,6 +1723,11 @@ 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("%s: resetting read seqnr %u", __func__,
++ state->p_read.seqnr);
++ state->p_read.seqnr = 0;
++ }
+
+ /* do we need to rekey? */
+ if (ssh_packet_need_rekeying(ssh, 0)) {
+@@ -1731,10 +1752,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");
+@@ -1749,19 +1799,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;
+@@ -2240,6 +2277,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 ||
+@@ -2402,6 +2440,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 ||
+@@ -2729,6 +2768,7 @@ sshpkt_disconnect(struct ssh *ssh, const char *fmt,...)
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
++ debug2("%s: sending SSH2_MSG_DISCONNECT: %s", __func__, 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 c47fc31a6..61392c934 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -233,7 +233,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ fatal("%s: kex_assemble_namelist", __func__);
+ 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("%s: kex_names_cat", __func__);
+ myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s);
+ myproposal[PROPOSAL_ENC_ALGS_CTOS] =
+@@ -423,7 +424,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 *);
+@@ -546,7 +546,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);
+@@ -591,13 +591,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)
+ {
+@@ -679,6 +672,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 fb9b7b7fb..e894ad6f7 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2405,10 +2405,13 @@ do_ssh2_kex(struct ssh *ssh)
+ {
+ char *myproposal[PROPOSAL_MAX] = { KEX_SERVER };
+ struct kex *kex;
++ char *s;
+ int r;
+
+- myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(
+- options.kex_algorithms);
++ if ((s = kex_names_cat(options.kex_algorithms,
++ "kex-strict-s-v00@openssh.com")) == NULL)
++ fatal("%s: kex_names_cat", __func__);
++ myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s);
+ myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(
+ options.ciphers);
+ myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(
+@@ -2517,6 +2520,7 @@ do_ssh2_kex(struct ssh *ssh)
+ (r = ssh_packet_write_wait(ssh)) != 0)
+ fatal("%s: send test: %s", __func__, ssh_err(r));
+ #endif
++ free(s);
+ debug("KEX done");
+ }
+
diff --git a/debian/patches/CVE-2023-51385.patch b/debian/patches/CVE-2023-51385.patch
new file mode 100644
index 0000000..7c7c179
--- /dev/null
+++ b/debian/patches/CVE-2023-51385.patch
@@ -0,0 +1,94 @@
+From 2bea7434c9fad19f017846adad0e995d8da00642 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 aa15b8a1f..dda3405c9 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -638,6 +638,41 @@ set_addrinfo_port(struct addrinfo *addrs, int port)
+ }
+ }
+
++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.
+ */
+@@ -1123,6 +1158,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");
+ 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..2680fc7
--- /dev/null
+++ b/debian/patches/authorized-keys-man-symlink.patch
@@ -0,0 +1,26 @@
+From 27ced5f6a3c5dec6e0a78ae138d3db56d49953bd 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 56759c388..73e56aaac 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -408,6 +408,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-old-privkey-format.patch b/debian/patches/conch-old-privkey-format.patch
new file mode 100644
index 0000000..c7063ce
--- /dev/null
+++ b/debian/patches/conch-old-privkey-format.patch
@@ -0,0 +1,68 @@
+From a73fcc8bab768900ca16d3121303941511b28d45 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Thu, 30 Aug 2018 00:58:56 +0100
+Subject: Work around conch interoperability failure
+
+Twisted Conch fails to read private keys in the new format
+(https://twistedmatrix.com/trac/ticket/9515). Work around this until it
+can be fixed in Twisted.
+
+Forwarded: not-needed
+Last-Update: 2019-10-09
+
+Patch-Name: conch-old-privkey-format.patch
+---
+ regress/Makefile | 2 +-
+ regress/conch-ciphers.sh | 2 +-
+ regress/test-exec.sh | 12 ++++++++++++
+ 3 files changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/regress/Makefile b/regress/Makefile
+index 8b4ed9de3..f50d189bb 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -122,7 +122,7 @@ CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \
+ rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
+ scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
+ sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
+- ssh-rsa_oldfmt \
++ ssh-rsa_oldfmt ssh-rsa_oldfmt.pub \
+ ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \
+ ssh_proxy_envpass sshd.log sshd_config sshd_config_minimal \
+ sshd_config.* sshd_proxy sshd_proxy.* sshd_proxy_bak \
+diff --git a/regress/conch-ciphers.sh b/regress/conch-ciphers.sh
+index 6678813a2..6ff5da20b 100644
+--- a/regress/conch-ciphers.sh
++++ b/regress/conch-ciphers.sh
+@@ -16,7 +16,7 @@ for c in aes256-ctr aes256-cbc aes192-ctr aes192-cbc aes128-ctr aes128-cbc \
+ rm -f ${COPY}
+ # XXX the 2nd "cat" seems to be needed because of buggy FD handling
+ # in conch
+- ${CONCH} --identity $OBJ/ssh-rsa --port $PORT --user $USER -e none \
++ ${CONCH} --identity $OBJ/ssh-rsa_oldfmt --port $PORT --user $USER -e none \
+ --known-hosts $OBJ/known_hosts --notty --noagent --nox11 -n \
+ 127.0.0.1 "cat ${DATA}" 2>/dev/null | cat > ${COPY}
+ if [ $? -ne 0 ]; then
+diff --git a/regress/test-exec.sh b/regress/test-exec.sh
+index 5dc975d07..d8491b2be 100644
+--- a/regress/test-exec.sh
++++ b/regress/test-exec.sh
+@@ -587,6 +587,18 @@ 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
++ # Convert rsa key to old format to work around
++ # https://twistedmatrix.com/trac/ticket/9515
++ cp $OBJ/ssh-rsa $OBJ/ssh-rsa_oldfmt
++ cp $OBJ/ssh-rsa.pub $OBJ/ssh-rsa_oldfmt.pub
++ ${SSHKEYGEN} -p -N '' -m PEM -f $OBJ/ssh-rsa_oldfmt >/dev/null
++fi
+
+ # If PuTTY is present 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..82cc37c
--- /dev/null
+++ b/debian/patches/debian-banner.patch
@@ -0,0 +1,162 @@
+From 6353ee79cc71ef33a0a34d2d769a5fe327f6260d 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: 2020-06-07
+
+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 ce7bb5b3b..763c45536 100644
+--- a/kex.c
++++ b/kex.c
+@@ -1225,7 +1225,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, i, n;
+@@ -1243,7 +1243,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 fe7141414..938dca03b 100644
+--- a/kex.h
++++ b/kex.h
+@@ -194,7 +194,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 21abe41ac..f9eb778d6 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -195,6 +195,7 @@ initialize_server_options(ServerOptions *options)
+ options->fingerprint_hash = -1;
+ options->disable_forwarding = -1;
+ options->expose_userauth_info = -1;
++ options->debian_banner = -1;
+ }
+
+ /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
+@@ -469,6 +470,8 @@ fill_default_server_options(ServerOptions *options)
+ options->expose_userauth_info = 0;
+ if (options->sk_provider == NULL)
+ options->sk_provider = xstrdup("internal");
++ if (options->debian_banner == -1)
++ options->debian_banner = 1;
+
+ assemble_algorithms(options);
+
+@@ -548,6 +551,7 @@ typedef enum {
+ sStreamLocalBindMask, sStreamLocalBindUnlink,
+ sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
+ sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
++ sDebianBanner,
+ sDeprecated, sIgnore, sUnsupported
+ } ServerOpCodes;
+
+@@ -712,6 +716,7 @@ static struct {
+ { "rdomain", sRDomain, SSHCFG_ALL },
+ { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL },
+ { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL },
++ { "debianbanner", sDebianBanner, SSHCFG_GLOBAL },
+ { NULL, sBadOption, 0 }
+ };
+
+@@ -2402,6 +2407,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ *charptr = xstrdup(arg);
+ break;
+
++ case sDebianBanner:
++ intptr = &options->debian_banner;
++ goto parse_flag;
++
+ case sDeprecated:
+ case sIgnore:
+ case sUnsupported:
+diff --git a/servconf.h b/servconf.h
+index f10908e5b..4afdf24d0 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -227,6 +227,8 @@ typedef struct {
+ int expose_userauth_info;
+ u_int64_t timing_secret;
+ char *sk_provider;
++
++ int debian_banner;
+ } ServerOptions;
+
+ /* Information about the incoming connection as used by Match */
+diff --git a/sshconnect.c b/sshconnect.c
+index 3ae20b74e..bab3916d8 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -1296,7 +1296,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 38d281ab4..50f2726bf 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2232,7 +2232,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 6457620bb..33dc0c675 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -540,6 +540,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..aa370e5
--- /dev/null
+++ b/debian/patches/debian-config.patch
@@ -0,0 +1,270 @@
+From a0c9f82b05d33f3e2cf8e5442cee47c09d1a1dd8 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 ChallengeResponseAuthentication, 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.
+
+Document all of this.
+
+Author: Russ Allbery <rra@debian.org>
+Forwarded: not-needed
+Last-Update: 2020-10-18
+
+Patch-Name: debian-config.patch
+---
+ readconf.c | 2 +-
+ ssh.1 | 24 ++++++++++++++++++++++++
+ ssh_config | 8 +++++++-
+ ssh_config.5 | 26 +++++++++++++++++++++++++-
+ sshd_config | 18 ++++++++++++------
+ sshd_config.5 | 29 +++++++++++++++++++++++++++++
+ 6 files changed, 98 insertions(+), 9 deletions(-)
+
+diff --git a/readconf.c b/readconf.c
+index f4f273c96..e676b6be6 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2153,7 +2153,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/ssh.1 b/ssh.1
+index 76ddd89b5..ad48fc8c8 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -812,6 +812,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
+@@ -820,6 +830,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 96ca7a5df..6d6c59521 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 #
+@@ -742,11 +765,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 2c48105f8..459c1b230 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
+-#ChallengeResponseAuthentication yes
++# Change to yes to enable challenge-response passwords (beware issues with
++# some PAM modules and threads)
++ChallengeResponseAuthentication 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 ChallengeResponseAuthentication 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 32ae46476..472001dd1 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 ChallengeResponseAuthentication 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..23ecc0d
--- /dev/null
+++ b/debian/patches/dnssec-sshfp.patch
@@ -0,0 +1,94 @@
+From 78a7702d88713e854550a05fa9b8670f219d9bf9 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 e4f9bf830..9c9fe6413 100644
+--- a/dns.c
++++ b/dns.c
+@@ -210,6 +210,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;
+@@ -233,8 +234,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 dc6fe0533..e061a290a 100644
+--- a/openbsd-compat/getrrsetbyname.c
++++ b/openbsd-compat/getrrsetbyname.c
+@@ -209,8 +209,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;
+ }
+@@ -226,9 +226,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..3e96f3b
--- /dev/null
+++ b/debian/patches/doc-hash-tab-completion.patch
@@ -0,0 +1,28 @@
+From 5fca8a730171f96a72007118c0d35cf4a09359f8 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: 2013-09-14
+
+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 190e1d927..96ca7a5df 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -861,6 +861,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 HostbasedAuthentication
+ Specifies whether to try rhosts based authentication with public key
+ authentication.
diff --git a/debian/patches/gnome-ssh-askpass2-icon.patch b/debian/patches/gnome-ssh-askpass2-icon.patch
new file mode 100644
index 0000000..d7d0bed
--- /dev/null
+++ b/debian/patches/gnome-ssh-askpass2-icon.patch
@@ -0,0 +1,26 @@
+From c26f6f9c7051b9ab2ac13d1d227e6d39527839cc 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 f7912727c..bf8c92c8f 100644
+--- a/contrib/gnome-ssh-askpass2.c
++++ b/contrib/gnome-ssh-askpass2.c
+@@ -322,6 +322,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..d779eac
--- /dev/null
+++ b/debian/patches/gssapi.patch
@@ -0,0 +1,4002 @@
+From d1b7918f9bce6e997c7952ac795e18d09192b2a6 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/commits/debian/master
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242
+Last-Updated: 2020-06-07
+
+Patch-Name: gssapi.patch
+---
+ Makefile.in | 3 +-
+ README.md | 33 +++
+ auth.c | 96 +-------
+ auth2-gss.c | 56 ++++-
+ auth2.c | 2 +
+ canohost.c | 93 ++++++++
+ canohost.h | 3 +
+ clientloop.c | 15 +-
+ configure.ac | 24 ++
+ gss-genr.c | 300 +++++++++++++++++++++++-
+ gss-serv-krb5.c | 85 ++++++-
+ gss-serv.c | 186 +++++++++++++--
+ kex.c | 66 +++++-
+ kex.h | 29 +++
+ kexdh.c | 10 +
+ kexgen.c | 2 +-
+ kexgssc.c | 606 ++++++++++++++++++++++++++++++++++++++++++++++++
+ kexgsss.c | 474 +++++++++++++++++++++++++++++++++++++
+ monitor.c | 139 ++++++++++-
+ monitor.h | 2 +
+ monitor_wrap.c | 57 ++++-
+ monitor_wrap.h | 4 +-
+ readconf.c | 70 ++++++
+ readconf.h | 6 +
+ servconf.c | 47 ++++
+ servconf.h | 3 +
+ session.c | 10 +-
+ ssh-gss.h | 54 ++++-
+ ssh.1 | 8 +
+ ssh.c | 6 +-
+ ssh_config | 2 +
+ ssh_config.5 | 57 +++++
+ sshconnect2.c | 154 +++++++++++-
+ sshd.c | 62 ++++-
+ sshd_config | 2 +
+ sshd_config.5 | 30 +++
+ sshkey.c | 3 +-
+ sshkey.h | 1 +
+ 38 files changed, 2640 insertions(+), 160 deletions(-)
+ create mode 100644 kexgssc.c
+ create mode 100644 kexgsss.c
+
+diff --git a/Makefile.in b/Makefile.in
+index acfb919da..56759c388 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -107,6 +107,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
+ kexgexc.o kexgexs.o \
+ sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \
++ kexgssc.o \
+ sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+ sshbuf-io.o
+
+@@ -123,7 +124,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 \
+ 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 md5crypt.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 28fb43d2a..5b73d24c0 100644
+--- a/README.md
++++ b/README.md
+@@ -1,3 +1,36 @@
++Portable OpenSSH with GSSAPI Key Exchange patches
++=================================================
++
++Currently, there are two branches with gssapi key exchange related
++patches:
++
++ * fedora/master: Changes that are shipped in Fedora
++ * debian/master: Changes that are shipped in Debian
++
++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
+
+ [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/openssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:openssh)
+diff --git a/auth.c b/auth.c
+index 9a5498b66..3d31ec860 100644
+--- a/auth.c
++++ b/auth.c
+@@ -400,7 +400,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:
+@@ -724,99 +725,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 legacy rhosts-style authentication.
+- * XXX is RhostsRSAAuthentication vulnerable to these?
+- * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?)
+- */
+-
+-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 9351e0428..d6446c0cf 100644
+--- a/auth2-gss.c
++++ b/auth2-gss.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: auth2-gss.c,v 1.29 2018/07/31 03:10:27 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
+@@ -54,6 +54,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("%s: %s", __func__, ssh_err(r));
++
++ if ((b = sshbuf_new()) == NULL)
++ fatal("%s: sshbuf_new failed", __func__);
++
++ mic.value = p;
++ mic.length = len;
++
++ ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
++ "gssapi-keyex");
++
++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++ fatal("%s: sshbuf_mutable_ptr failed", __func__);
++ 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)
+@@ -260,7 +302,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh)
+ if ((r = sshpkt_get_end(ssh)) != 0)
+ fatal("%s: %s", __func__, ssh_err(r));
+
+- 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)
+@@ -306,7 +349,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");
+
+@@ -326,6 +370,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",
+ userauth_gssapi,
+diff --git a/auth2.c b/auth2.c
+index 242a7adbe..9fa1404b3 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -73,6 +73,7 @@ extern Authmethod method_passwd;
+ extern Authmethod method_kbdint;
+ extern Authmethod method_hostbased;
+ #ifdef GSSAPI
++extern Authmethod method_gsskeyex;
+ extern Authmethod method_gssapi;
+ #endif
+
+@@ -80,6 +81,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 abea9c6e6..8e81b5193 100644
+--- a/canohost.c
++++ b/canohost.c
+@@ -35,6 +35,99 @@
+ #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.
++ * XXX is RhostsRSAAuthentication vulnerable to these?
++ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?)
++ */
++
++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 60b46d161..2cebea29f 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -112,6 +112,10 @@
+ #include "ssherr.h"
+ #include "hostfile.h"
+
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++
+ /* import options */
+ extern Options options;
+
+@@ -1368,9 +1372,18 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
+ break;
+
+ /* Do channel operations unless rekeying in progress. */
+- if (!ssh_packet_is_rekeying(ssh))
++ if (!ssh_packet_is_rekeying(ssh)) {
+ channel_after_select(ssh, readset, writeset);
+
++#ifdef GSSAPI
++ if (options.gss_renewal_rekey &&
++ ssh_gssapi_credentials_updated(NULL)) {
++ debug("credentials updated - forcing rekey");
++ need_rekeying = 1;
++ }
++#endif
++ }
++
+ /* Buffer input from the connection. */
+ client_process_net_input(ssh, readset);
+
+diff --git a/configure.ac b/configure.ac
+index 7005a503e..c8a96deb4 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -679,6 +679,30 @@ main() { 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 d56257b4a..763a63ffa 100644
+--- a/gss-genr.c
++++ b/gss-genr.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-genr.c,v 1.26 2018/07/10 09:13:30 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
+@@ -41,12 +41,36 @@
+ #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"
+
+ extern u_char *session_id2;
+ extern u_int session_id2_len;
+
++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)
+@@ -62,6 +86,162 @@ 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("%s: sshbuf_new failed", __func__);
++
++ 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("%s: digest failed: %s", __func__,
++ ssh_err(r));
++ 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("%s: sshbuf_put_u8 error: %s",
++ __func__, ssh_err(r));
++ if ((r = sshbuf_put(buf, p, strlen(p))) != 0 ||
++ (r = sshbuf_put(buf, encoded, enclen)) != 0)
++ fatal("%s: sshbuf_put error: %s",
++ __func__, ssh_err(r));
++ }
++
++ 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("%s: sshbuf_dup_string failed", __func__);
++
++ 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)
+@@ -218,7 +398,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);
+
+@@ -247,9 +427,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);
+@@ -257,6 +471,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)
+@@ -273,11 +500,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 &&
+@@ -287,6 +519,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);
+@@ -296,10 +532,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..ef9beb67c 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,8 +120,8 @@ 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 *errmsg;
++ const char *new_ccname;
+
+ if (client->creds == NULL) {
+ debug("No credentials stored");
+@@ -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)
+@@ -196,6 +201,71 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ 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 +273,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..55f4d4bda 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);
++ }
++
++ 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;
++ }
+
+- gss_buffer_desc ename;
++ 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;
+@@ -356,19 +432,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 +462,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 aecb9394d..751cfc710 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 *);
+@@ -115,15 +120,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);
+@@ -138,6 +156,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)
+ {
+@@ -147,6 +177,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;
+ }
+
+@@ -315,6 +349,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])
+@@ -697,6 +754,9 @@ kex_free(struct kex *kex)
+ sshbuf_free(kex->server_version);
+ sshbuf_free(kex->client_pub);
+ free(kex->session_id);
++#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 a5ae6ac05..fe7141414 100644
+--- a/kex.h
++++ b/kex.h
+@@ -102,6 +102,15 @@ enum kex_exchange {
+ KEX_ECDH_SHA2,
+ KEX_C25519_SHA256,
+ KEX_KEM_SNTRUP4591761X25519_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
+ };
+
+@@ -153,6 +162,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 *);
+@@ -174,8 +189,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 *);
+
+@@ -202,6 +219,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 **,
+@@ -234,6 +257,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 6e0159f9f..d024a8b9a 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 69348b964..c0e8c2f44 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..f6e1405eb
+--- /dev/null
++++ b/kexgssc.c
+@@ -0,0 +1,606 @@
++/*
++ * 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("%s: Unexpected KEX type %d", __func__, 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("%s: Unexpected KEX type %d", __func__, 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);
++
++ /* save session id */
++ if (kex->session_id == NULL) {
++ kex->session_id_len = hashlen;
++ kex->session_id = xmalloc(kex->session_id_len);
++ memcpy(kex->session_id, hash, kex->session_id_len);
++ }
++
++ 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..60bc02deb
+--- /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("%s: Identifying %s", __func__, kex->name);
++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++ if (oid == GSS_C_NO_OID)
++ fatal("Unknown gssapi mechanism");
++
++ debug2("%s: Acquiring credentials", __func__);
++
++ 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("%s: Unexpected KEX type %d", __func__, 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("%s: Identifying %s", __func__, kex->name);
++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++ if (oid == GSS_C_NO_OID)
++ fatal("Unknown gssapi mechanism");
++
++ debug2("%s: Acquiring credentials", __func__);
++
++ 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 4cf79dfc9..11868952b 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -148,6 +148,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
+@@ -220,11 +222,18 @@ struct mon_table mon_dispatch_proto20[] = {
+ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
+ {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok},
+ {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic},
++ {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
+@@ -293,6 +302,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) {
+@@ -406,6 +419,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);
+@@ -1725,6 +1742,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_SNTRUP4591761X25519_SHA512] = kex_gen_server;
+@@ -1818,8 +1846,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ u_char *p;
+ int r;
+
+- if (!options.gss_authentication)
+- fatal("%s: GSSAPI authentication not enabled", __func__);
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal("%s: GSSAPI not enabled", __func__);
+
+ if ((r = sshbuf_get_string(m, &p, &len)) != 0)
+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
+@@ -1851,8 +1879,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("%s: GSSAPI authentication not enabled", __func__);
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal("%s: GSSAPI not enabled", __func__);
+
+ if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0)
+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
+@@ -1872,6 +1900,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);
+ }
+@@ -1883,8 +1912,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+ OM_uint32 ret;
+ int r;
+
+- if (!options.gss_authentication)
+- fatal("%s: GSSAPI authentication not enabled", __func__);
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal("%s: GSSAPI not enabled", __func__);
+
+ if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 ||
+ (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0)
+@@ -1910,13 +1939,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("%s: GSSAPI authentication not enabled", __func__);
++ if (!options.gss_authentication && !options.gss_keyex)
++ fatal("%s: GSSAPI not enabled", __func__);
+
+- authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user);
++ if ((r = sshbuf_get_u32(m, &kex)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++ authenticated = authctxt->valid &&
++ ssh_gssapi_userok(authctxt->user, authctxt->pw, kex);
+
+ sshbuf_reset(m);
+ if ((r = sshbuf_put_u32(m, authenticated)) != 0)
+@@ -1925,7 +1958,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ debug3("%s: sending result %d", __func__, 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);
+@@ -1933,5 +1970,85 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ /* Monitor loop will terminate if authenticated */
+ return (authenticated);
+ }
++
++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("%s: GSSAPI not enabled", __func__);
++
++ if ((r = sshbuf_get_string(m, &p, &len)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++ 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("%s: data length incorrect: %d", __func__,
++ (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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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("%s: GSSAPI not enabled", __func__);
++
++ 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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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 5e38d83eb..0e78cd006 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -993,13 +993,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("%s: sshbuf_new failed", __func__);
++ if ((r = sshbuf_put_u32(m, kex)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+@@ -1012,4 +1014,57 @@ mm_ssh_gssapi_userok(char *user)
+ debug3("%s: user %sauthenticated",__func__, 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("%s: sshbuf_new failed", __func__);
++ if ((r = sshbuf_put_string(m, data->value, data->length)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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("%s: sshbuf_new failed", __func__);
++
++ 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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ 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("%s: buffer error: %s", __func__, ssh_err(r));
++
++ sshbuf_free(m);
++
++ return (ok);
++}
++
+ #endif /* GSSAPI */
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index 0db38c206..75aef1c74 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 554efd7c9..57dae55d1 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:
+
+@@ -160,6 +161,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,
+@@ -204,10 +207,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 },
+@@ -1068,10 +1083,42 @@ 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 = strdelim(&s);
++ 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 oBatchMode:
+ intptr = &options->batch_mode;
+ goto parse_flag;
+@@ -1976,7 +2023,13 @@ initialize_options(Options * options)
+ options->pubkey_authentication = -1;
+ options->challenge_response_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;
+@@ -2125,8 +2178,18 @@ fill_default_options(Options * options)
+ options->challenge_response_authentication = 1;
+ 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)
+@@ -2776,7 +2839,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 d6a15550d..3803eeddf 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -41,7 +41,13 @@ typedef struct {
+ int challenge_response_authentication;
+ /* Try S/Key or TIS, authentication. */
+ 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 f08e37477..ded8f4a87 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);
+@@ -134,8 +135,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->challenge_response_authentication = -1;
+@@ -376,10 +380,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)
+@@ -523,6 +535,7 @@ typedef enum {
+ sHostKeyAlgorithms,
+ sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
+ sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
++ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
+ sAcceptEnv, sSetEnv, sPermitTunnel,
+ sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
+ sUsePrivilegeSeparation, sAllowAgentForwarding,
+@@ -600,12 +613,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", sChallengeResponseAuthentication, SSHCFG_GLOBAL },
+@@ -1557,6 +1580,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;
+@@ -1565,6 +1592,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 = strdelim(&cp);
++ 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;
+@@ -2808,6 +2851,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 1df8f3db8..f10908e5b 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -138,8 +138,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 27ca8a104..857f17b3c 100644
+--- a/session.c
++++ b/session.c
+@@ -2685,13 +2685,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 36180d07a..50d80bbca 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -1,6 +1,6 @@
+ /* $OpenBSD: ssh-gss.h,v 1.14 2018/07/10 09:13:30 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 *);
+-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.1 b/ssh.1
+index 555317887..be8e964f0 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -506,7 +506,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 HostbasedAuthentication
+@@ -582,6 +588,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 f34ca0d71..bb98a7e2d 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -801,6 +801,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)
+@@ -826,8 +828,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 6be1f1aa2..bd86d000c 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -779,10 +779,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 f64aae66a..c47fc31a6 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -80,8 +80,6 @@
+ #endif
+
+ /* import */
+-extern char *client_version_string;
+-extern char *server_version_string;
+ extern Options options;
+
+ /*
+@@ -210,6 +208,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ char *s, *all_key;
+ 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;
+
+@@ -253,6 +256,41 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ compat_pkalg_proposal(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) {
++ 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);
+@@ -271,16 +309,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_SNTRUP4591761X25519_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(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("kex_prop2buf: %s", ssh_err(r));
+
+@@ -377,6 +445,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 *);
+@@ -393,6 +462,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,
+@@ -763,12 +837,31 @@ userauth_gssapi(struct ssh *ssh)
+ OM_uint32 min;
+ int r, ok = 0;
+ gss_OID mech = NULL;
++ char *gss_host;
++
++ 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) {
++ gss_host = 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 &&
+@@ -777,13 +870,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;
+
+@@ -1023,6 +1118,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("%s: sshbuf_new failed", __func__);
++
++ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service,
++ "gssapi-keyex");
++
++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++ fatal("%s: sshbuf_mutable_ptr failed", __func__);
++ 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("%s: %s", __func__, ssh_err(r));
++
++ sshbuf_free(b);
++ gss_release_buffer(&ms, &mic);
++
++ return (1);
++}
++
+ #endif /* GSSAPI */
+
+ static int
+diff --git a/sshd.c b/sshd.c
+index 8aa7f3df6..8c5d5822e 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -816,8 +816,8 @@ notify_hostkeys(struct ssh *ssh)
+ }
+ debug3("%s: sent %u hostkeys", __func__, nkeys);
+ if (nkeys == 0)
+- fatal("%s: no hostkeys", __func__);
+- if ((r = sshpkt_send(ssh)) != 0)
++ debug3("%s: no hostkeys", __func__);
++ else if ((r = sshpkt_send(ssh)) != 0)
+ sshpkt_fatal(ssh, r, "%s: send", __func__);
+ sshbuf_free(buf);
+ }
+@@ -1901,7 +1901,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);
+ }
+@@ -2393,6 +2394,48 @@ do_ssh2_kex(struct ssh *ssh)
+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal(
+ 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("kex_setup: %s", ssh_err(r));
+@@ -2408,7 +2451,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_SNTRUP4591761X25519_SHA512] = kex_gen_server;
+ kex->load_host_public_key=&get_hostkey_public_by_type;
+diff --git a/sshd_config b/sshd_config
+index 19b7c91a1..2c48105f8 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 6fa421cae..eabbe9e73 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -644,6 +644,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.
+@@ -658,6 +663,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 HostbasedAcceptedKeyTypes
+ Specifies the key types that will be accepted for hostbased authentication
+ as a list of comma-separated patterns.
+diff --git a/sshkey.c b/sshkey.c
+index ac451f1a8..b88282e19 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -156,6 +156,7 @@ static const struct keytype keytypes[] = {
+ KEY_ECDSA_SK_CERT, NID_X9_62_prime256v1, 1, 0 },
+ # endif /* OPENSSL_HAS_ECC */
+ #endif /* WITH_OPENSSL */
++ { "null", "null", NULL, KEY_NULL, 0, 0, 0 },
+ { NULL, NULL, NULL, -1, -1, 0, 0 }
+ };
+
+@@ -257,7 +258,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
+ const struct keytype *kt;
+
+ for (kt = keytypes; kt->type != -1; kt++) {
+- if (kt->name == NULL)
++ if (kt->name == NULL || kt->type == KEY_NULL)
+ continue;
+ if (!include_sigonly && kt->sigonly)
+ continue;
+diff --git a/sshkey.h b/sshkey.h
+index 2d8b62497..dc1c10597 100644
+--- a/sshkey.h
++++ b/sshkey.h
+@@ -69,6 +69,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..c9bc832
--- /dev/null
+++ b/debian/patches/keepalive-extensions.patch
@@ -0,0 +1,135 @@
+From 164d1c9f11309d38273ac64e30eda2baa3733f78 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: 2020-02-21
+
+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 b069333fa..3d0a812b3 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -176,6 +176,7 @@ typedef enum {
+ oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
+ oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump,
+ oSecurityKeyProvider,
++ oProtocolKeepAlives, oSetupTimeOut,
+ oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
+ } OpCodes;
+
+@@ -326,6 +327,8 @@ static struct {
+ { "ignoreunknown", oIgnoreUnknown },
+ { "proxyjump", oProxyJump },
+ { "securitykeyprovider", oSecurityKeyProvider },
++ { "protocolkeepalives", oProtocolKeepAlives },
++ { "setuptimeout", oSetupTimeOut },
+
+ { NULL, oBadOption }
+ };
+@@ -1534,6 +1537,8 @@ parse_keytypes:
+ 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;
+
+@@ -2266,8 +2271,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 bd86d000c..3ceb800ba 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
+@@ -1624,7 +1628,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 SetEnv
+ Directly specify one or more environment variables and their contents to
+ be sent to the server.
+@@ -1704,6 +1715,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 eabbe9e73..6457620bb 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1691,6 +1691,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/mention-ssh-keygen-on-keychange.patch b/debian/patches/mention-ssh-keygen-on-keychange.patch
new file mode 100644
index 0000000..cb227f8
--- /dev/null
+++ b/debian/patches/mention-ssh-keygen-on-keychange.patch
@@ -0,0 +1,44 @@
+From c8da63c601b5d44fd233548385809c9c3a2fa0b8 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 5f8c81b84..3ae20b74e 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -994,9 +994,13 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
+ 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);
+@@ -1005,6 +1009,9 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
+ 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..e383375
--- /dev/null
+++ b/debian/patches/no-openssl-version-status.patch
@@ -0,0 +1,62 @@
+From cf3ffd6a25d425bed33dd698f92e64953d9769eb 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 5d019b598..58474873d 100644
+--- a/openbsd-compat/regress/opensslvertest.c
++++ b/openbsd-compat/regress/opensslvertest.c
+@@ -35,6 +35,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..64405d5
--- /dev/null
+++ b/debian/patches/openbsd-docs.patch
@@ -0,0 +1,148 @@
+From 6bcbfca92b58917dba48b696dd63529fa5dcbb82 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:
+ http://bugs.debian.org/154434 (login.conf(5))
+ http://bugs.debian.org/513417 (/etc/rc)
+ http://bugs.debian.org/530692 (ssl(8))
+ https://bugs.launchpad.net/bugs/456660 (ssl(8))
+
+Forwarded: not-needed
+Last-Update: 2017-10-04
+
+Patch-Name: openbsd-docs.patch
+---
+ moduli.5 | 4 ++--
+ ssh-keygen.1 | 12 ++++--------
+ ssh.1 | 4 ++++
+ sshd.8 | 5 ++---
+ sshd_config.5 | 3 +--
+ 5 files changed, 13 insertions(+), 15 deletions(-)
+
+diff --git a/moduli.5 b/moduli.5
+index ef0de0850..149846c8c 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 3ae596caa..836174fb6 100644
+--- a/ssh-keygen.1
++++ b/ssh-keygen.1
+@@ -202,9 +202,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.
+@@ -269,9 +267,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) rounds used.
+@@ -804,7 +800,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 and
+ that both ends of a connection share common moduli.
+ .Pp
+@@ -1185,7 +1181,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 5d613076c..1880c032d 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -890,6 +890,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 97d547ffa..7895a6a94 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -65,7 +65,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
+@@ -911,7 +911,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
+@@ -1009,7 +1009,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 33dc0c675..32ae46476 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -385,8 +385,7 @@ Certificates signed using other algorithms will not be accepted for
+ public key or host-based authentication.
+ .It Cm ChallengeResponseAuthentication
+ Specifies whether challenge-response authentication is allowed (e.g. via
+-PAM or through authentication styles supported in
+-.Xr login.conf 5 )
++PAM).
+ The default is
+ .Cm yes .
+ .It Cm ChrootDirectory
diff --git a/debian/patches/package-versioning.patch b/debian/patches/package-versioning.patch
new file mode 100644
index 0000000..daa1473
--- /dev/null
+++ b/debian/patches/package-versioning.patch
@@ -0,0 +1,47 @@
+From 707144d399b9fc959a4f6be3fd8e239c208c88ff 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: 2019-06-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 751cfc710..ce7bb5b3b 100644
+--- a/kex.c
++++ b/kex.c
+@@ -1243,7 +1243,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 c2f9c55bb..480cd59e1 100644
+--- a/version.h
++++ b/version.h
+@@ -3,4 +3,9 @@
+ #define SSH_VERSION "OpenSSH_8.4"
+
+ #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/restore-authorized_keys2.patch b/debian/patches/restore-authorized_keys2.patch
new file mode 100644
index 0000000..a1f5205
--- /dev/null
+++ b/debian/patches/restore-authorized_keys2.patch
@@ -0,0 +1,35 @@
+From 8dc9bb0d9cf53a35d6003623f1e7c91326d79875 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 459c1b230..dc0db5706 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..7388fad
--- /dev/null
+++ b/debian/patches/restore-tcp-wrappers.patch
@@ -0,0 +1,172 @@
+From 6806b85f30244d186206004386a9faddc16b8738 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: 2019-06-05
+
+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 c8a96deb4..bb435ec1f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1571,6 +1571,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,
+@@ -5536,6 +5592,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 " MD5 password support: $MD5_MSG"
+ echo " libedit support: $LIBEDIT_MSG"
+ echo " libldns support: $LDNS_MSG"
+diff --git a/sshd.8 b/sshd.8
+index b2fad56d3..97d547ffa 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -900,6 +900,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 ) .
+@@ -1002,6 +1008,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 8c5d5822e..a50ec3584 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -124,6 +124,13 @@
+ #include "ssherr.h"
+ #include "sk-api.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)
+@@ -2183,6 +2190,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..b84cef1
--- /dev/null
+++ b/debian/patches/revert-ipqos-defaults.patch
@@ -0,0 +1,93 @@
+From 3728919292c05983372954d27426f7d966813139 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 e676b6be6..c60df5602 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2298,9 +2298,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->proxy_use_fdpass == -1)
+diff --git a/servconf.c b/servconf.c
+index f9eb778d6..98afcfcec 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -453,9 +453,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 6d6c59521..080d289a7 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1156,11 +1156,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 472001dd1..a555e7ec3 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -925,11 +925,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/revert-x32-sandbox-breakage.patch b/debian/patches/revert-x32-sandbox-breakage.patch
new file mode 100644
index 0000000..32cff92
--- /dev/null
+++ b/debian/patches/revert-x32-sandbox-breakage.patch
@@ -0,0 +1,39 @@
+From 67434174b3d64b352a794275f77489ebf1575849 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Mon, 26 Oct 2020 17:36:22 +0000
+Subject: Revert "detect Linux/X32 systems"
+
+This reverts commit 5b56bd0affea7b02b540bdbc4d1d271b0e4fc885. The bug
+reporter wasn't actually using x32, but rather an ordinary 32-bit
+userspace on a 64-bit kernel; this patch broke the seccomp sandbox on
+the actual x32 architecture.
+
+Patch-Name: revert-x32-sandbox-breakage.patch
+---
+ configure.ac | 6 ------
+ 1 file changed, 6 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 5944299fa..15fc0d653 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -521,8 +521,6 @@ SPP_MSG="no"
+ # the --with-solaris-privs option and --with-sandbox=solaris).
+ SOLARIS_PRIVS="no"
+
+-AC_CHECK_SIZEOF([size_t])
+-
+ # Check for some target-specific stuff
+ case "$host" in
+ *-*-aix*)
+@@ -891,10 +889,6 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16))
+ case "$host" in
+ x86_64-*)
+ seccomp_audit_arch=AUDIT_ARCH_X86_64
+- # X32: AMD64 instructions in 32bit address space.
+- if test "x$ac_cv_sizeof_size_t" = "x4" ; then
+- seccomp_audit_arch=AUDIT_ARCH_I386
+- fi
+ ;;
+ i*86-*)
+ seccomp_audit_arch=AUDIT_ARCH_I386
diff --git a/debian/patches/sandbox-pselect6_time64.patch b/debian/patches/sandbox-pselect6_time64.patch
new file mode 100644
index 0000000..7a14bc1
--- /dev/null
+++ b/debian/patches/sandbox-pselect6_time64.patch
@@ -0,0 +1,32 @@
+From ed99ef256258d8556dbe39d976c2528ede050f14 Mon Sep 17 00:00:00 2001
+From: Darren Tucker <dtucker@dtucker.net>
+Date: Fri, 20 Nov 2020 13:37:54 +1100
+Subject: Add new pselect6_time64 syscall on ARM.
+
+This is apparently needed on armhfp/armv7hl. bz#3232, patch from
+jjelen at redhat.com.
+
+Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=0f90440ca70abab947acbd77795e9f130967956c
+Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=3232
+Bug-Debian: https://bugs.debian.org/1004427
+Last-Update: 2022-02-25
+
+Patch-Name: sandbox-pselect6_time64.patch
+---
+ sandbox-seccomp-filter.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
+index e0768c063..5065ae7ef 100644
+--- a/sandbox-seccomp-filter.c
++++ b/sandbox-seccomp-filter.c
+@@ -267,6 +267,9 @@ static const struct sock_filter preauth_insns[] = {
+ #ifdef __NR_pselect6
+ SC_ALLOW(__NR_pselect6),
+ #endif
++#ifdef __NR_pselect6_time64
++ SC_ALLOW(__NR_pselect6_time64),
++#endif
+ #ifdef __NR_read
+ SC_ALLOW(__NR_read),
+ #endif
diff --git a/debian/patches/scp-quoting.patch b/debian/patches/scp-quoting.patch
new file mode 100644
index 0000000..604e831
--- /dev/null
+++ b/debian/patches/scp-quoting.patch
@@ -0,0 +1,41 @@
+From 94f06f8888f2e11267120eeebdb931d95bbfb7fd 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 6ae17061d..2d1b8e9b9 100644
+--- a/scp.c
++++ b/scp.c
+@@ -201,8 +201,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..3161999
--- /dev/null
+++ b/debian/patches/selinux-role.patch
@@ -0,0 +1,472 @@
+From c574865182e2c5dfa183b577f49ac602d16df5c0 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: 2020-10-18
+
+Patch-Name: selinux-role.patch
+---
+ auth.h | 1 +
+ auth2.c | 10 ++++++++--
+ monitor.c | 37 +++++++++++++++++++++++++++++++++----
+ 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(+), 32 deletions(-)
+
+diff --git a/auth.h b/auth.h
+index becc672b5..5da9fe75f 100644
+--- a/auth.h
++++ b/auth.h
+@@ -63,6 +63,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 9fa1404b3..d8363bdba 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -265,7 +265,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();
+
+@@ -279,8 +279,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++ == 0) {
+ /* setup auth context */
+@@ -307,8 +312,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 11868952b..98362948f 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -118,6 +118,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 *);
+@@ -198,6 +199,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
+@@ -820,6 +822,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
+@@ -853,16 +856,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("%s: buffer error: %s", __func__, ssh_err(r));
+- debug3("%s: service=%s, style=%s",
+- __func__, authctxt->service, authctxt->style);
++ debug3("%s: service=%s, style=%s, role=%s",
++ __func__, 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);
+ }
+
+@@ -1566,7 +1595,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 0e78cd006..d41d3949d 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -364,10 +364,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;
+@@ -377,7 +377,8 @@ mm_inform_authserv(char *service, char *style)
+ if ((m = sshbuf_new()) == NULL)
+ fatal("%s: sshbuf_new failed", __func__);
+ 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("%s: buffer error: %s", __func__, ssh_err(r));
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHSERV, m);
+@@ -385,6 +386,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 75aef1c74..c39e5dd8b 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 f46094faf..56f1d2c1e 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 security_context_t
+-ssh_selinux_getctxbyname(char *pwname)
++ssh_selinux_getctxbyname(char *pwname, const char *role)
+ {
+ security_context_t sc = NULL;
+ char *sename = NULL, *lvl = NULL;
+@@ -71,9 +71,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) {
+@@ -103,7 +110,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)
+ {
+ security_context_t user_ctx = NULL;
+
+@@ -112,7 +119,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:
+@@ -134,7 +141,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)
+ {
+ security_context_t new_tty_ctx = NULL;
+ security_context_t user_ctx = NULL;
+@@ -146,7 +153,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 44ba71dc5..2defe9425 100644
+--- a/platform.c
++++ b/platform.c
+@@ -143,7 +143,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)
+ /*
+@@ -184,7 +184,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 ea4f9c584..60d72ffe7 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_sys_dir_uid(uid_t);
+diff --git a/session.c b/session.c
+index 857f17b3c..b1796a803 100644
+--- a/session.c
++++ b/session.c
+@@ -1364,7 +1364,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;
+
+@@ -1392,7 +1392,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) {
+@@ -1536,7 +1536,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);
+@@ -1554,7 +1554,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
+@@ -1953,7 +1953,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 ce59dabd9..675c91146 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 a50ec3584..38d281ab4 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -594,7 +594,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 bce09e255..308449b37 100644
+--- a/sshpty.c
++++ b/sshpty.c
+@@ -162,7 +162,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;
+@@ -186,7 +186,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..d62e524
--- /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
+conch-old-privkey-format.patch
+revert-ipqos-defaults.patch
+revert-x32-sandbox-breakage.patch
+ssh-copy-id-heredoc-syntax.patch
+ssh-agent-double-free.patch
+sandbox-pselect6_time64.patch
+CVE-2023-38408-1.patch
+CVE-2023-38408-2.patch
+CVE-2021-41617-1.patch
+CVE-2021-41617-2.patch
+CVE-2023-48795.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..503b08d
--- /dev/null
+++ b/debian/patches/shell-path.patch
@@ -0,0 +1,39 @@
+From a7d2f23b7b86f97749856482233cdc9dd970d1d3 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 9ec0618a9..5f8c81b84 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -263,7 +263,7 @@ ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg,
+ /* Execute the proxy command. Note that we gave up any
+ extra privileges above. */
+ ssh_signal(SIGPIPE, SIG_DFL);
+- execv(argv[0], argv);
++ execvp(argv[0], argv);
+ perror(argv[0]);
+ exit(1);
+ }
+@@ -1392,7 +1392,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-double-free.patch b/debian/patches/ssh-agent-double-free.patch
new file mode 100644
index 0000000..20ae613
--- /dev/null
+++ b/debian/patches/ssh-agent-double-free.patch
@@ -0,0 +1,26 @@
+From 421db3656dcafbe810226463bf27a18a0b1c3186 Mon Sep 17 00:00:00 2001
+From: Colin Watson <cjwatson@debian.org>
+Date: Sat, 13 Mar 2021 09:35:05 +0000
+Subject: Double free in ssh-agent(1)
+
+Origin: upstream, https://ftp.openbsd.org/pub/OpenBSD/patches/6.8/common/015_sshagent.patch.sig
+Bug-Debian: https://bugs.debian.org/984940
+Last-Update: 2021-03-13
+
+Patch-Name: ssh-agent-double-free.patch
+---
+ ssh-agent.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/ssh-agent.c b/ssh-agent.c
+index e1fd1f3f6..48155c96e 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -581,6 +581,7 @@ process_add_identity(SocketEntry *e)
+ goto err;
+ }
+ free(ext_name);
++ ext_name = NULL;
+ break;
+ default:
+ error("%s: Unknown constraint %d", __func__, ctype);
diff --git a/debian/patches/ssh-agent-setgid.patch b/debian/patches/ssh-agent-setgid.patch
new file mode 100644
index 0000000..5d7a6c0
--- /dev/null
+++ b/debian/patches/ssh-agent-setgid.patch
@@ -0,0 +1,40 @@
+From 7a305ed4a0cba43d0d1bc6ebf5737521a0854a9d 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 2cf46160b..272da79b3 100644
+--- a/ssh-agent.1
++++ b/ssh-agent.1
+@@ -206,6 +206,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..12f8c1b
--- /dev/null
+++ b/debian/patches/ssh-argv0.patch
@@ -0,0 +1,31 @@
+From 0e71b467fd84b0972c6aa2762d93af1c3defc0dc 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 1880c032d..76ddd89b5 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1632,6 +1632,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-copy-id-heredoc-syntax.patch b/debian/patches/ssh-copy-id-heredoc-syntax.patch
new file mode 100644
index 0000000..e025967
--- /dev/null
+++ b/debian/patches/ssh-copy-id-heredoc-syntax.patch
@@ -0,0 +1,37 @@
+From 27cf2f667b46a99f4469f41bcb8e004834a3d34f Mon Sep 17 00:00:00 2001
+From: Oleg <Fallmay@users.noreply.github.com>
+Date: Thu, 1 Oct 2020 12:09:08 +0300
+Subject: Fix `EOF: command not found` error in ssh-copy-id
+
+Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=d9e727dcc04a52caaac87543ea1d230e9e6b5604
+Bug: https://github.com/openssh/openssh-portable/pull/206
+Bug-Debian: https://bugs.debian.org/975540
+Bug-Debian: https://bugs.debian.org/976242
+Last-Update: 2020-12-02
+
+Patch-Name: ssh-copy-id-heredoc-syntax.patch
+---
+ contrib/ssh-copy-id | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
+index 392f64f94..a76907717 100644
+--- a/contrib/ssh-copy-id
++++ b/contrib/ssh-copy-id
+@@ -247,7 +247,7 @@ installkeys_sh() {
+ # the -z `tail ...` checks for a trailing newline. The echo adds one if was missing
+ # the cat adds the keys we're getting via STDIN
+ # and if available restorecon is used to restore the SELinux context
+- INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF)
++ INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF
+ cd;
+ umask 077;
+ mkdir -p $(dirname "${AUTH_KEY_FILE}") &&
+@@ -258,6 +258,7 @@ installkeys_sh() {
+ restorecon -F .ssh ${AUTH_KEY_FILE};
+ fi
+ EOF
++ )
+
+ # to defend against quirky remote shells: use 'exec sh -c' to get POSIX;
+ printf "exec sh -c '%s'" "${INSTALLKEYS_SH}"
diff --git a/debian/patches/ssh-vulnkey-compat.patch b/debian/patches/ssh-vulnkey-compat.patch
new file mode 100644
index 0000000..f4bedfd
--- /dev/null
+++ b/debian/patches/ssh-vulnkey-compat.patch
@@ -0,0 +1,42 @@
+From 61b4d4c07d19cd0816ab5d48da81a75f7adbdf24 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 57dae55d1..b069333fa 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -191,6 +191,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 ded8f4a87..21abe41ac 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -649,6 +649,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..d6215de
--- /dev/null
+++ b/debian/patches/syslog-level-silent.patch
@@ -0,0 +1,47 @@
+From 33a5f7aadea15899586710c615408045eaaecebd 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 6b1a7a314..5ebae1480 100644
+--- a/log.c
++++ b/log.c
+@@ -93,6 +93,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 bb98a7e2d..aa15b8a1f 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -1373,7 +1373,7 @@ main(int ac, char **av)
+ /* Do not allocate a tty if stdin is not a tty. */
+ if ((!isatty(fileno(stdin)) || stdin_null_flag) &&
+ 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..37e98c1
--- /dev/null
+++ b/debian/patches/systemd-readiness.patch
@@ -0,0 +1,84 @@
+From e8453621b2a26f8d6afec405ff60201749b01e5e 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 bb435ec1f..5944299fa 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -4785,6 +4785,29 @@ AC_ARG_WITH([kerberos5],
+ AC_SUBST([GSSLIBS])
+ AC_SUBST([K5LIBS])
+
++# 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
+@@ -5599,6 +5622,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 50f2726bf..fb9b7b7fb 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -85,6 +85,10 @@
+ #include <prot.h>
+ #endif
+
++#ifdef HAVE_SYSTEMD
++#include <systemd/sd-daemon.h>
++#endif
++
+ #include "xmalloc.h"
+ #include "ssh.h"
+ #include "ssh2.h"
+@@ -2076,6 +2080,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/user-group-modes.patch b/debian/patches/user-group-modes.patch
new file mode 100644
index 0000000..8f5a8a3
--- /dev/null
+++ b/debian/patches/user-group-modes.patch
@@ -0,0 +1,210 @@
+From d08cd2b0cfbedf3ccd2ec3adaef850b8d9a87e85 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: 2019-10-09
+
+Patch-Name: user-group-modes.patch
+---
+ auth-rhosts.c | 6 ++----
+ auth.c | 3 +--
+ misc.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-----
+ misc.h | 2 ++
+ readconf.c | 3 +--
+ ssh.1 | 2 ++
+ ssh_config.5 | 2 ++
+ 7 files changed, 63 insertions(+), 13 deletions(-)
+
+diff --git a/auth-rhosts.c b/auth-rhosts.c
+index e81321b49..3bcc73766 100644
+--- a/auth-rhosts.c
++++ b/auth-rhosts.c
+@@ -260,8 +260,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: "
+@@ -287,8 +286,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, buf);
+ auth_debug_add("Bad file modes for %.200s", buf);
+diff --git a/auth.c b/auth.c
+index 3d31ec860..4152d9c44 100644
+--- a/auth.c
++++ b/auth.c
+@@ -474,8 +474,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 4623b5755..c75a795c2 100644
+--- a/misc.c
++++ b/misc.c
+@@ -55,8 +55,9 @@
+ #include <netdb.h>
+ #ifdef HAVE_PATHS_H
+ # include <paths.h>
+-#include <pwd.h>
+ #endif
++#include <pwd.h>
++#include <grp.h>
+ #ifdef SSH_TUN_OPENBSD
+ #include <net/if.h>
+ #endif
+@@ -1271,6 +1272,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)
+ {
+@@ -2056,8 +2106,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;
+@@ -2072,8 +2121,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 ab94a79c0..b34c798e7 100644
+--- a/misc.h
++++ b/misc.h
+@@ -192,6 +192,8 @@ struct notifier_ctx *notify_start(int, const char *, ...)
+ __attribute__((format(printf, 2, 3)));
+ void notify_complete(struct notifier_ctx *);
+
++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 3d0a812b3..f4f273c96 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -1967,8 +1967,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 be8e964f0..5d613076c 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1528,6 +1528,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 3ceb800ba..190e1d927 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -2010,6 +2010,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