From 58ca4f0aa8c4306ac0a629c9a85fb1efaf4ff092 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Thu, 10 Aug 2023 11:30:24 +1000
Subject: [PATCH 01/62] Only include unistd.h once.

---
 openbsd-compat/bsd-closefrom.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/openbsd-compat/bsd-closefrom.c b/openbsd-compat/bsd-closefrom.c
index 704da531f..49a4f35ff 100644
--- a/openbsd-compat/bsd-closefrom.c
+++ b/openbsd-compat/bsd-closefrom.c
@@ -28,7 +28,6 @@
 #include <stdlib.h>
 #include <stddef.h>
 #include <string.h>
-#include <unistd.h>
 #ifdef HAVE_DIRENT_H
 # include <dirent.h>
 # define NAMLEN(dirent) strlen((dirent)->d_name)

From 78b4dc6684f4d35943b46b24ee645edfdb9974f5 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 10 Aug 2023 01:01:07 +0000
Subject: [PATCH 02/62] upstream: openssh-9.4

OpenBSD-Commit-ID: 71fc1e01a4c4ea061b252bd399cda7be757e6e35
---
 version.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/version.h b/version.h
index 69e76e634..e5b1e719d 100644
--- a/version.h
+++ b/version.h
@@ -1,6 +1,6 @@
-/* $OpenBSD: version.h,v 1.97 2023/03/15 21:19:57 djm Exp $ */
+/* $OpenBSD: version.h,v 1.98 2023/08/10 01:01:07 djm Exp $ */
 
-#define SSH_VERSION	"OpenSSH_9.3"
+#define SSH_VERSION	"OpenSSH_9.4"
 
 #define SSH_PORTABLE	"p1"
 #define SSH_RELEASE	SSH_VERSION SSH_PORTABLE

From d0cee4298491314f09afa1c4383a66d913150b26 Mon Sep 17 00:00:00 2001
From: Damien Miller <djm@mindrot.org>
Date: Thu, 10 Aug 2023 11:05:14 +1000
Subject: [PATCH 03/62] update version in README

---
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README b/README
index 796101c7a..e44e44ced 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-See https://www.openssh.com/releasenotes.html#9.3p1 for the release
+See https://www.openssh.com/releasenotes.html#9.4p1 for the release
 notes.
 
 Please read https://www.openssh.com/report.html for bug reporting

From 0fcb60bf83130dfa428bc4422b3a3ac20fb528af Mon Sep 17 00:00:00 2001
From: Damien Miller <djm@mindrot.org>
Date: Thu, 10 Aug 2023 11:05:42 +1000
Subject: [PATCH 04/62] update versions in RPM specs

---
 contrib/redhat/openssh.spec | 2 +-
 contrib/suse/openssh.spec   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/contrib/redhat/openssh.spec b/contrib/redhat/openssh.spec
index a665aa20b..7d6fe3cfd 100644
--- a/contrib/redhat/openssh.spec
+++ b/contrib/redhat/openssh.spec
@@ -1,4 +1,4 @@
-%global ver 9.3p1
+%global ver 9.4p1
 %global rel 1%{?dist}
 
 # OpenSSH privilege separation requires a user & group ID
diff --git a/contrib/suse/openssh.spec b/contrib/suse/openssh.spec
index 406b7c0b8..777362766 100644
--- a/contrib/suse/openssh.spec
+++ b/contrib/suse/openssh.spec
@@ -13,7 +13,7 @@
 
 Summary:	OpenSSH, a free Secure Shell (SSH) protocol implementation
 Name:		openssh
-Version:	9.3p1
+Version:	9.4p1
 URL:		https://www.openssh.com/
 Release:	1
 Source0:	openssh-%{version}.tar.gz

From e962f9b318a238db1becc53c2bf79dd3a49095b4 Mon Sep 17 00:00:00 2001
From: Damien Miller <djm@mindrot.org>
Date: Thu, 10 Aug 2023 11:10:22 +1000
Subject: [PATCH 05/62] depend

---
 .depend | 19 -------------------
 1 file changed, 19 deletions(-)

diff --git a/.depend b/.depend
index 24b2bb0b3..259bf3b2f 100644
--- a/.depend
+++ b/.depend
@@ -8,11 +8,8 @@ atomicio.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-comp
 audit-bsm.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
 audit-linux.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
 audit.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
-auth-bsdauth-monitor.o: xmalloc.h sshkey.h sshbuf.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h log.h ssherr.h
 auth-bsdauth.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
 auth-krb5.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h sshkey.h misc.h servconf.h uidswap.h hostfile.h auth.h auth-pam.h audit.h loginrec.h
-auth-log.o: authfile.h monitor_wrap.h channels.h
-auth-log.o: xmalloc.h match.h groupaccess.h log.h ssherr.h sshbuf.h misc.h servconf.h openbsd-compat/sys-queue.h sshkey.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h auth-options.h canohost.h packet.h dispatch.h
 auth-options.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssherr.h log.h sshbuf.h misc.h sshkey.h match.h ssh2.h auth-options.h
 auth-pam.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
 auth-passwd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h packet.h openbsd-compat/sys-queue.h dispatch.h sshbuf.h ssherr.h log.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h auth-options.h
@@ -21,25 +18,16 @@ auth-shadow.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-c
 auth-sia.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
 auth.o: authfile.h monitor_wrap.h channels.h
 auth.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h match.h groupaccess.h log.h ssherr.h sshbuf.h misc.h servconf.h openbsd-compat/sys-queue.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h auth-options.h canohost.h uidswap.h packet.h dispatch.h
-auth2-banner.o: atomicio.h xmalloc.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h pathnames.h
 auth2-chall.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh2.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h sshbuf.h packet.h openbsd-compat/sys-queue.h dispatch.h ssherr.h log.h misc.h servconf.h
 auth2-gss.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
-auth2-hostbased-monitor.o: canohost.h pathnames.h match.h
-auth2-hostbased-monitor.o: xmalloc.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h log.h ssherr.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h uidswap.h
 auth2-hostbased.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h kex.h mac.h crypto_api.h sshbuf.h log.h ssherr.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h canohost.h
 auth2-hostbased.o: monitor_wrap.h pathnames.h match.h
-auth2-kbdint-monitor.o: xmalloc.h packet.h openbsd-compat/sys-queue.h dispatch.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h log.h ssherr.h misc.h servconf.h
 auth2-kbdint.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h packet.h openbsd-compat/sys-queue.h dispatch.h hostfile.h auth.h auth-pam.h audit.h loginrec.h log.h ssherr.h misc.h servconf.h
-auth2-methods.o: log.h ssherr.h misc.h servconf.h openbsd-compat/sys-queue.h xmalloc.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h
 auth2-none.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h atomicio.h xmalloc.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h misc.h servconf.h ssh2.h monitor_wrap.h
 auth2-passwd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h packet.h openbsd-compat/sys-queue.h dispatch.h ssherr.h log.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h monitor_wrap.h misc.h servconf.h
-auth2-pubkey-monitor.o: loginrec.h pathnames.h uidswap.h auth-options.h canohost.h monitor_wrap.h authfile.h match.h channels.h session.h sk-api.h
-auth2-pubkey-monitor.o: xmalloc.h ssh.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h log.h ssherr.h misc.h servconf.h compat.h sshkey.h hostfile.h auth.h auth-pam.h audit.h
 auth2-pubkey.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h kex.h mac.h crypto_api.h sshbuf.h log.h ssherr.h misc.h servconf.h compat.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h
 auth2-pubkey.o: pathnames.h uidswap.h auth-options.h canohost.h monitor_wrap.h authfile.h match.h channels.h session.h sk-api.h
 auth2-pubkeyfile.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh.h log.h ssherr.h misc.h sshkey.h digest.h hostfile.h auth.h auth-pam.h audit.h loginrec.h auth-options.h authfile.h match.h
-auth2-userauth.o: atomicio.h xmalloc.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h audit.h loginrec.h pathnames.h monitor_wrap.h
-auth2-userauth.o: digest.h
 auth2.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h atomicio.h xmalloc.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h servconf.h sshkey.h hostfile.h auth.h auth-pam.h audit.h loginrec.h pathnames.h monitor_wrap.h digest.h
 authfd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h sshbuf.h sshkey.h authfd.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h log.h ssherr.h atomicio.h misc.h
 authfile.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h ssh.h log.h ssherr.h authfile.h misc.h atomicio.h sshkey.h sshbuf.h krl.h
@@ -71,7 +59,6 @@ gss-serv.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-comp
 hash.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h crypto_api.h
 hmac.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h digest.h hmac.h
 hostfile.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h match.h sshkey.h hostfile.h log.h ssherr.h misc.h pathnames.h digest.h hmac.h sshbuf.h
-kex-names.o: kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h log.h ssherr.h match.h digest.h misc.h xmalloc.h
 kex.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh.h ssh2.h atomicio.h version.h packet.h openbsd-compat/sys-queue.h dispatch.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h crypto_api.h log.h ssherr.h
 kex.o: match.h misc.h monitor.h myproposal.h sshbuf.h digest.h xmalloc.h
 kexc25519.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshkey.h kex.h mac.h crypto_api.h sshbuf.h digest.h ssherr.h ssh2.h
@@ -169,12 +156,6 @@ sshconnect.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-co
 sshconnect.o: kex.h mac.h crypto_api.h
 sshconnect2.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshbuf.h packet.h dispatch.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h crypto_api.h
 sshconnect2.o: sshconnect.h authfile.h dh.h authfd.h log.h ssherr.h misc.h readconf.h match.h canohost.h msg.h pathnames.h uidswap.h hostfile.h utf8.h ssh-sk.h sk-api.h
-sshd-monitor.o: openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h authfile.h pathnames.h atomicio.h canohost.h hostfile.h auth.h auth-pam.h audit.h loginrec.h authfd.h msg.h channels.h session.h monitor.h monitor_wrap.h auth-options.h version.h sk-api.h dh.h
-sshd-monitor.o: xmalloc.h ssh.h ssh2.h sshpty.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h
-sshd-unpriv-postauth.o: openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h authfile.h pathnames.h atomicio.h canohost.h hostfile.h auth.h auth-pam.h audit.h loginrec.h authfd.h msg.h channels.h session.h monitor.h monitor_wrap.h auth-options.h version.h sk-api.h dh.h
-sshd-unpriv-postauth.o: xmalloc.h ssh.h ssh2.h sshpty.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h
-sshd-unpriv-preauth.o: openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h authfile.h pathnames.h atomicio.h canohost.h hostfile.h auth.h auth-pam.h audit.h loginrec.h authfd.h msg.h channels.h session.h monitor.h monitor_wrap.h auth-options.h version.h sk-api.h dh.h
-sshd-unpriv-preauth.o: xmalloc.h ssh.h ssh2.h sshpty.h packet.h openbsd-compat/sys-queue.h dispatch.h log.h ssherr.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h kex.h mac.h crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h
 sshd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshpty.h packet.h dispatch.h log.h ssherr.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h chacha.h
 sshd.o: poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h kex.h mac.h crypto_api.h authfile.h pathnames.h atomicio.h canohost.h hostfile.h auth.h auth-pam.h audit.h loginrec.h authfd.h msg.h channels.h session.h monitor.h monitor_wrap.h ssh-sandbox.h auth-options.h version.h sk-api.h srclimit.h dh.h
 ssherr.o: ssherr.h

From a8c57bcb077f0cfdffcf9f23866bf73bb93e185c Mon Sep 17 00:00:00 2001
From: "naddy@openbsd.org" <naddy@openbsd.org>
Date: Thu, 10 Aug 2023 14:37:32 +0000
Subject: [PATCH 06/62] upstream: drop a wayward comma, ok jmc@

OpenBSD-Commit-ID: 5c11fbb9592a29b37bbf36f66df50db9d38182c6
---
 ssh-agent.1 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/ssh-agent.1 b/ssh-agent.1
index 327f0e196..6815eb834 100644
--- a/ssh-agent.1
+++ b/ssh-agent.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: ssh-agent.1,v 1.78 2023/07/23 20:04:45 naddy Exp $
+.\" $OpenBSD: ssh-agent.1,v 1.79 2023/08/10 14:37:32 naddy Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: July 23 2023 $
+.Dd $Mdocdate: August 10 2023 $
 .Dt SSH-AGENT 1
 .Os
 .Sh NAME
@@ -122,7 +122,7 @@ Note that signalling that an
 .Nm
 client is remote is performed by
 .Xr ssh 1 ,
-and use of other tools to forward access to the agent socket, may circumvent
+and use of other tools to forward access to the agent socket may circumvent
 this restriction.
 .Pp
 The

From f9f18006678d2eac8b0c5a5dddf17ab7c50d1e9f Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 10 Aug 2023 23:05:48 +0000
Subject: [PATCH 07/62] upstream: better debug logging of sessions' exit status

OpenBSD-Commit-ID: 82237567fcd4098797cbdd17efa6ade08e1a36b0
---
 session.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/session.c b/session.c
index 89dcfdab6..aa342e84d 100644
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.c,v 1.335 2023/03/07 06:09:14 dtucker Exp $ */
+/* $OpenBSD: session.c,v 1.336 2023/08/10 23:05:48 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -2380,17 +2380,17 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
 {
 	Channel *c;
 	int r;
+	char *note = NULL;
 
 	if ((c = channel_lookup(ssh, s->chanid)) == NULL)
 		fatal_f("session %d: no channel %d", s->self, s->chanid);
-	debug_f("session %d channel %d pid %ld",
-	    s->self, s->chanid, (long)s->pid);
 
 	if (WIFEXITED(status)) {
 		channel_request_start(ssh, s->chanid, "exit-status", 0);
 		if ((r = sshpkt_put_u32(ssh, WEXITSTATUS(status))) != 0 ||
 		    (r = sshpkt_send(ssh)) != 0)
 			sshpkt_fatal(ssh, r, "%s: exit reply", __func__);
+		xasprintf(&note, "exit %d", WEXITSTATUS(status));
 	} else if (WIFSIGNALED(status)) {
 		channel_request_start(ssh, s->chanid, "exit-signal", 0);
 #ifndef WCOREDUMP
@@ -2402,11 +2402,18 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
 		    (r = sshpkt_put_cstring(ssh, "")) != 0 ||
 		    (r = sshpkt_send(ssh)) != 0)
 			sshpkt_fatal(ssh, r, "%s: exit reply", __func__);
+		xasprintf(&note, "signal %d%s", WTERMSIG(status),
+		    WCOREDUMP(status) ? " core dumped" : "");
 	} else {
 		/* Some weird exit cause.  Just exit. */
-		ssh_packet_disconnect(ssh, "wait returned status %04x.", status);
+		ssh_packet_disconnect(ssh, "wait returned status %04x.",
+		    status);
 	}
 
+	debug_f("session %d channel %d pid %ld %s", s->self, s->chanid,
+	    (long)s->pid, note == NULL ? "UNKNOWN" : note);
+	free(note);
+
 	/* disconnect channel */
 	debug_f("release channel %d", s->chanid);
 

From fa8da52934cb7dff6f660a143276bdb28bb9bbe1 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Sun, 13 Aug 2023 15:01:27 +1000
Subject: [PATCH 08/62] Add obsd72 and obsd73 test targets.

---
 .github/workflows/selfhosted.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/selfhosted.yml b/.github/workflows/selfhosted.yml
index e84db699e..7ba428b29 100644
--- a/.github/workflows/selfhosted.yml
+++ b/.github/workflows/selfhosted.yml
@@ -40,6 +40,8 @@ jobs:
           - obsd67
           - obsd69
           - obsd70
+          - obsd72
+          - obsd73
           - obsdsnap
           - obsdsnap-i386
           - openindiana

From d1ab7eb90474df656d5e9935bae6df0bd000d343 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 14 Aug 2023 03:37:00 +0000
Subject: [PATCH 09/62] upstream: add message number of SSH2_MSG_NEWCOMPRESS
 defined in RFC8308

OpenBSD-Commit-ID: 6c984171c96ed67effd7b5092f3d3975d55d6028
---
 ssh2.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ssh2.h b/ssh2.h
index 32ddae89b..288c39f69 100644
--- a/ssh2.h
+++ b/ssh2.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh2.h,v 1.19 2020/11/19 23:05:05 dtucker Exp $ */
+/* $OpenBSD: ssh2.h,v 1.20 2023/08/14 03:37:00 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -85,6 +85,7 @@
 #define SSH2_MSG_SERVICE_REQUEST			5
 #define SSH2_MSG_SERVICE_ACCEPT				6
 #define SSH2_MSG_EXT_INFO				7
+#define SSH2_MSG_NEWCOMPRESS				8
 
 /* transport layer: alg negotiation */
 

From e706bca324a70f68dadfd0ec69edfdd486eed23a Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 16 Aug 2023 16:14:11 +0000
Subject: [PATCH 10/62] upstream: defence-in-depth MaxAuthTries check in
 monitor; ok markus

OpenBSD-Commit-ID: 65a4225dc708e2dae71315adf93677edace46c21
---
 monitor.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/monitor.c b/monitor.c
index 1489c78d8..b3ed515ed 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.236 2023/05/10 10:04:20 dtucker Exp $ */
+/* $OpenBSD: monitor.c,v 1.237 2023/08/16 16:14:11 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -342,6 +342,11 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
 				    auth_method, auth_submethod);
 			}
 		}
+		if (authctxt->failures > options.max_authtries) {
+			/* Shouldn't happen */
+			fatal_f("privsep child made too many authentication "
+			    "attempts");
+		}
 	}
 
 	if (!authctxt->valid)

From 803e22eabd3ba75485eedd8b7b44d6ace79f2052 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 18 Aug 2023 01:37:41 +0000
Subject: [PATCH 11/62] upstream: fix regression in OpenSSH 9.4 (mux.c r1.99)
 that caused

multiplexed sessions to ignore SIGINT under some circumstances. Reported by /
feedback naddy@, ok dtucker@

OpenBSD-Commit-ID: 4d5c6c894664f50149153fd4764f21f43e7d7e5a
---
 kex.c  |  4 ++--
 misc.c | 42 ++++++++++++++++++++++++++++++------------
 misc.h |  5 +++--
 mux.c  |  6 ++++--
 4 files changed, 39 insertions(+), 18 deletions(-)

diff --git a/kex.c b/kex.c
index b4e2ab75f..fd04bb0b5 100644
--- a/kex.c
+++ b/kex.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.178 2023/03/12 10:40:39 dtucker Exp $ */
+/* $OpenBSD: kex.c,v 1.179 2023/08/18 01:37:41 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -1334,7 +1334,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
 		for (;;) {
 			if (timeout_ms > 0) {
 				r = waitrfd(ssh_packet_get_connection_in(ssh),
-				    &timeout_ms);
+				    &timeout_ms, NULL);
 				if (r == -1 && errno == ETIMEDOUT) {
 					send_error(ssh, "Timed out waiting "
 					    "for SSH identification string.");
diff --git a/misc.c b/misc.c
index 4b87c4090..956587035 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.185 2023/08/04 06:32:40 dtucker Exp $ */
+/* $OpenBSD: misc.c,v 1.186 2023/08/18 01:37:41 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005-2020 Damien Miller.  All rights reserved.
@@ -313,20 +313,38 @@ set_sock_tos(int fd, int tos)
  * Returns 0 if fd ready or -1 on timeout or error (see errno).
  */
 static int
-waitfd(int fd, int *timeoutp, short events)
+waitfd(int fd, int *timeoutp, short events, volatile sig_atomic_t *stop)
 {
 	struct pollfd pfd;
-	struct timeval t_start;
-	int oerrno, r, have_timeout = (*timeoutp >= 0);
+	struct timespec timeout;
+	int oerrno, r;
+	sigset_t nsigset, osigset;
 
+	if (timeoutp && *timeoutp == -1)
+		timeoutp = NULL;
 	pfd.fd = fd;
 	pfd.events = events;
-	for (; !have_timeout || *timeoutp >= 0;) {
-		monotime_tv(&t_start);
-		r = poll(&pfd, 1, *timeoutp);
+	ptimeout_init(&timeout);
+	if (timeoutp != NULL)
+		ptimeout_deadline_ms(&timeout, *timeoutp);
+	if (stop != NULL)
+		sigfillset(&nsigset);
+	for (; timeoutp == NULL || *timeoutp >= 0;) {
+		if (stop != NULL) {
+			sigprocmask(SIG_BLOCK, &nsigset, &osigset);
+			if (*stop) {
+				sigprocmask(SIG_SETMASK, &osigset, NULL);
+				errno = EINTR;
+				return -1;
+			}
+		}
+		r = ppoll(&pfd, 1, ptimeout_get_tsp(&timeout),
+		    stop != NULL ? &osigset : NULL);
 		oerrno = errno;
-		if (have_timeout)
-			ms_subtract_diff(&t_start, timeoutp);
+		if (stop != NULL)
+			sigprocmask(SIG_SETMASK, &osigset, NULL);
+		if (timeoutp)
+			*timeoutp = ptimeout_get_ms(&timeout);
 		errno = oerrno;
 		if (r > 0)
 			return 0;
@@ -346,8 +364,8 @@ waitfd(int fd, int *timeoutp, short events)
  * Returns 0 if fd ready or -1 on timeout or error (see errno).
  */
 int
-waitrfd(int fd, int *timeoutp) {
-	return waitfd(fd, timeoutp, POLLIN);
+waitrfd(int fd, int *timeoutp, volatile sig_atomic_t *stop) {
+	return waitfd(fd, timeoutp, POLLIN, stop);
 }
 
 /*
@@ -381,7 +399,7 @@ timeout_connect(int sockfd, const struct sockaddr *serv_addr,
 		break;
 	}
 
-	if (waitfd(sockfd, timeoutp, POLLIN | POLLOUT) == -1)
+	if (waitfd(sockfd, timeoutp, POLLIN | POLLOUT, NULL) == -1)
 		return -1;
 
 	/* Completed or failed */
diff --git a/misc.h b/misc.h
index fd77a7fd7..f9bdc6eb5 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.103 2023/07/19 14:02:27 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.104 2023/08/18 01:37:41 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -19,6 +19,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <stdio.h>
+#include <signal.h>
 
 /* Data structure for representing a forwarding request. */
 struct Forward {
@@ -57,7 +58,7 @@ char	*get_rdomain(int);
 int	 set_rdomain(int, const char *);
 int	 get_sock_af(int);
 void	 set_sock_tos(int, int);
-int	 waitrfd(int, int *);
+int	 waitrfd(int, int *, volatile sig_atomic_t *);
 int	 timeout_connect(int, const struct sockaddr *, socklen_t, int *);
 int	 a2port(const char *);
 int	 a2tun(const char *, int *);
diff --git a/mux.c b/mux.c
index 3a0f87674..d9d5e7d99 100644
--- a/mux.c
+++ b/mux.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mux.c,v 1.99 2023/08/04 06:32:40 dtucker Exp $ */
+/* $OpenBSD: mux.c,v 1.100 2023/08/18 01:37:41 djm Exp $ */
 /*
  * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
  *
@@ -1480,7 +1480,9 @@ mux_client_read(int fd, struct sshbuf *b, size_t need, int timeout_ms)
 			case EWOULDBLOCK:
 #endif
 			case EAGAIN:
-				if (waitrfd(fd, &timeout_ms) == -1)
+				if (waitrfd(fd, &timeout_ms,
+				    &muxclient_terminate) == -1 &&
+				    errno != EINTR)
 					return -1;	/* timeout */
 				/* FALLTHROUGH */
 			case EINTR:

From 25b75e21f16bccdaa472ea1889b293c9bd51a87b Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Mon, 14 Aug 2023 11:10:08 +1000
Subject: [PATCH 12/62] Add 9.4 branch to CI status page.

---
 .github/ci-status.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/ci-status.md b/.github/ci-status.md
index f3e088fd6..8d4cea10d 100644
--- a/.github/ci-status.md
+++ b/.github/ci-status.md
@@ -6,6 +6,10 @@ master :
 [![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)
 [![Coverity Status](https://scan.coverity.com/projects/21341/badge.svg)](https://scan.coverity.com/projects/openssh-portable)
 
+9.4 :
+[![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_9_4)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_4)
+[![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_9_4)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_4)
+
 9.3 :
 [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_9_3)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_3)
 [![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_9_3)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_3)

From cb4ed12ffc332d1f72d054ed92655b5f1c38f621 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Sat, 19 Aug 2023 07:39:08 +1000
Subject: [PATCH 13/62] Fix zlib version check for 1.3 and future version.

bz#3604.
---
 configure.ac | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 07893e870..e3128dfcb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1464,7 +1464,7 @@ else
 	[[
 	int a=0, b=0, c=0, d=0, n, v;
 	n = sscanf(ZLIB_VERSION, "%d.%d.%d.%d", &a, &b, &c, &d);
-	if (n != 3 && n != 4)
+	if (n < 1)
 		exit(1);
 	v = a*1000000 + b*10000 + c*100 + d;
 	fprintf(stderr, "found zlib version %s (%d)\n", ZLIB_VERSION, v);

From 84efebf352fc700e9040c8065707c63caedd36a3 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 21 Aug 2023 04:36:46 +0000
Subject: [PATCH 14/62] upstream: want stdlib.h for free(3)

OpenBSD-Commit-ID: 743af3c6e3ce5e6cecd051668f0327a01f44af29
---
 sshkey.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/sshkey.c b/sshkey.c
index 727728536..2d3906ad8 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.137 2023/07/27 22:23:05 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.138 2023/08/21 04:36:46 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Alexander von Gernler.  All rights reserved.
@@ -41,6 +41,7 @@
 #include <errno.h>
 #include <limits.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <resolv.h>
 #include <time.h>

From 1acac79bfbe207e8db639e8043524962037c8feb Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Mon, 21 Aug 2023 18:05:26 +1000
Subject: [PATCH 15/62] Add test for zlib development branch.

---
 .github/configs             | 4 ++++
 .github/setup_ci.sh         | 8 ++++++++
 .github/workflows/c-cpp.yml | 1 +
 3 files changed, 13 insertions(+)

diff --git a/.github/configs b/.github/configs
index e054eb319..66cb2d0e8 100755
--- a/.github/configs
+++ b/.github/configs
@@ -205,6 +205,10 @@ case "$config" in
 		;;
 	esac
 	;;
+    zlib-develop)
+	INSTALL_ZLIB=develop
+	CONFIGFLAGS="--with-zlib=/opt/zlib --with-rpath=-Wl,-rpath,"
+	;;
     *)
 	echo "Unknown configuration $config"
 	exit 1
diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh
index 154f51bdc..010a333a6 100755
--- a/.github/setup_ci.sh
+++ b/.github/setup_ci.sh
@@ -133,6 +133,8 @@ for TARGET in $TARGETS; do
     valgrind*)
        PACKAGES="$PACKAGES valgrind"
        ;;
+    zlib-*)
+       ;;
     *) echo "Invalid option '${TARGET}'"
         exit 1
         ;;
@@ -214,3 +216,9 @@ if [ ! -z "${INSTALL_BORINGSSL}" ]; then
      cp ${HOME}/boringssl/build/crypto/libcrypto.a /opt/boringssl/lib &&
      cp -r ${HOME}/boringssl/include /opt/boringssl)
 fi
+
+if [ ! -z "${INSTALL_ZLIB}" ]; then
+    (cd ${HOME} && git clone https://github.com/madler/zlib.git &&
+     cd ${HOME}/zlib && ./configure && make &&
+     sudo make install prefix=/opt/zlib)
+fi
diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml
index e4e2a64e0..be0c97f84 100644
--- a/.github/workflows/c-cpp.yml
+++ b/.github/workflows/c-cpp.yml
@@ -73,6 +73,7 @@ jobs:
           - { target: ubuntu-latest, config: openssl-3.1.0 }
           - { target: ubuntu-latest, config: openssl-1.1.1_stable }
           - { target: ubuntu-latest, config: openssl-3.0 }  # stable branch
+          - { target: ubuntu-latest, config: zlib-develop }
           - { target: ubuntu-22.04, config: pam }
           - { target: ubuntu-22.04, config: krb5 }
           - { target: ubuntu-22.04, config: heimdal }

From 598ca75c85acaaacee5ef954251e489cc20d7be9 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Mon, 21 Aug 2023 18:38:36 +1000
Subject: [PATCH 16/62] Add OpenBSD ARM64 test host.

---
 .github/workflows/selfhosted.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/selfhosted.yml b/.github/workflows/selfhosted.yml
index 7ba428b29..195170320 100644
--- a/.github/workflows/selfhosted.yml
+++ b/.github/workflows/selfhosted.yml
@@ -42,6 +42,7 @@ jobs:
           - obsd70
           - obsd72
           - obsd73
+          - obsdarm64
           - obsdsnap
           - obsdsnap-i386
           - openindiana

From 17fa6cd10a26e193bb6f65d21264d2fe553bcd87 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Mon, 21 Aug 2023 19:47:58 +1000
Subject: [PATCH 17/62] obsd-arm64 host is real hardware...

so put in the correct config location.
---
 .github/workflows/selfhosted.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/selfhosted.yml b/.github/workflows/selfhosted.yml
index 195170320..de0a4125b 100644
--- a/.github/workflows/selfhosted.yml
+++ b/.github/workflows/selfhosted.yml
@@ -42,7 +42,6 @@ jobs:
           - obsd70
           - obsd72
           - obsd73
-          - obsdarm64
           - obsdsnap
           - obsdsnap-i386
           - openindiana
@@ -79,6 +78,7 @@ jobs:
           - { target: ARM64, config: default, host: ARM64 }
           - { target: ARM64, config: pam, host: ARM64 }
           - { target: debian-riscv64, config: default, host: debian-riscv64 }
+          - { target: obsd-arm64, config: default, host: obsd-arm64 }
           - { target: openwrt-mips, config: default, host: openwrt-mips }
           - { target: openwrt-mipsel, config: default, host: openwrt-mipsel }
     steps:

From 3c6ab63b383b0b7630da175941e01de9db32a256 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Fri, 25 Aug 2023 14:48:02 +1000
Subject: [PATCH 18/62] Include Portable version in sshd version string.

bz#3608, ok djm@
---
 sshd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sshd.c b/sshd.c
index 264e81ac7..8524808f9 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1697,7 +1697,7 @@ main(int ac, char **av)
 			break;
 		case 'V':
 			fprintf(stderr, "%s, %s\n",
-			    SSH_VERSION, SSH_OPENSSL_VERSION);
+			    SSH_RELEASE, SSH_OPENSSL_VERSION);
 			exit(0);
 		default:
 			usage();

From 9d7193a8359639801193ad661a59d1ae4dc3d302 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 21 Aug 2023 04:59:54 +0000
Subject: [PATCH 19/62] upstream: correct math for ClientAliveInterval that
 caused the

probes to be sent less frequently than configured; from Dawid Majchrzak

OpenBSD-Commit-ID: 641153e7c05117436ddfc58267aa267ca8b80038
---
 serverloop.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/serverloop.c b/serverloop.c
index de5fa2e3c..f3683c2e4 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: serverloop.c,v 1.236 2023/03/08 04:43:12 guenther Exp $ */
+/* $OpenBSD: serverloop.c,v 1.237 2023/08/21 04:59:54 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -253,7 +253,7 @@ wait_until_can_do_something(struct ssh *ssh,
 	/* ClientAliveInterval probing */
 	if (client_alive_scheduled) {
 		if (ret == 0 &&
-		    now > last_client_time + options.client_alive_interval) {
+		    now >= last_client_time + options.client_alive_interval) {
 			/* ppoll timed out and we're due to probe */
 			client_alive_check(ssh);
 			last_client_time = now;

From d2d247938b38b928f8a6e1a47a330c5584d3a358 Mon Sep 17 00:00:00 2001
From: "tobhe@openbsd.org" <tobhe@openbsd.org>
Date: Mon, 21 Aug 2023 21:16:18 +0000
Subject: [PATCH 20/62] upstream: Log errors in kex_exchange_identification()
 with level

verbose instead of error to reduce preauth log spam. All of those get logged
with a more generic error message by sshpkt_fatal().

feedback from sthen@
ok djm@

OpenBSD-Commit-ID: bd47dab4695b134a44c379f0e9a39eed33047809
---
 kex.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/kex.c b/kex.c
index fd04bb0b5..502da12ed 100644
--- a/kex.c
+++ b/kex.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.179 2023/08/18 01:37:41 djm Exp $ */
+/* $OpenBSD: kex.c,v 1.180 2023/08/21 21:16:18 tobhe Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -1353,7 +1353,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
 			len = atomicio(read, ssh_packet_get_connection_in(ssh),
 			    &c, 1);
 			if (len != 1 && errno == EPIPE) {
-				error_f("Connection closed by remote host");
+				verbose_f("Connection closed by remote host");
 				r = SSH_ERR_CONN_CLOSED;
 				goto out;
 			} else if (len != 1) {
@@ -1369,7 +1369,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
 			if (c == '\n')
 				break;
 			if (c == '\0' || expect_nl) {
-				error_f("banner line contains invalid "
+				verbose_f("banner line contains invalid "
 				    "characters");
 				goto invalid;
 			}
@@ -1379,7 +1379,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
 				goto out;
 			}
 			if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) {
-				error_f("banner line too long");
+				verbose_f("banner line too long");
 				goto invalid;
 			}
 		}
@@ -1395,7 +1395,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
 		}
 		/* Do not accept lines before the SSH ident from a client */
 		if (ssh->kex->server) {
-			error_f("client sent invalid protocol identifier "
+			verbose_f("client sent invalid protocol identifier "
 			    "\"%.256s\"", cp);
 			free(cp);
 			goto invalid;

From dce6d80d2ed3cad2c516082682d5f6ca877ef714 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 28 Aug 2023 03:28:43 +0000
Subject: [PATCH 21/62] upstream: Introduce a transport-level ping facility

This adds a pair of SSH transport protocol messages SSH2_MSG_PING/PONG
to implement a ping capability. These messages use numbers in the "local
extensions" number space and are advertised using a "ping@openssh.com"
ext-info message with a string version number of "0".

ok markus@

OpenBSD-Commit-ID: b6b3c4cb2084c62f85a8dc67cf74954015eb547f
---
 PROTOCOL | 35 ++++++++++++++++++++++++++++++++++-
 kex.c    | 47 ++++++++++++++++++++++++++++++++++-------------
 kex.h    |  3 ++-
 packet.c | 23 +++++++++++++++++++++--
 ssh2.h   |  6 +++++-
 5 files changed, 96 insertions(+), 18 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index 27804d0ca..d453c779b 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -104,6 +104,39 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o
 
 This is identical to curve25519-sha256 as later published in RFC8731.
 
+1.9 transport: ping facility
+
+OpenSSH implements a transport level ping message SSH2_MSG_PING
+and a corresponding SSH2_MSG_PONG reply.
+
+#define SSH2_MSG_PING	192
+#define SSH2_MSG_PONG	193
+
+The ping message is simply:
+
+	byte		SSH_MSG_PING
+	string		data
+
+The reply copies the data (which may be the empty string) from the
+ping:
+
+	byte		SSH_MSG_PONG
+	string		data
+
+Replies are sent in order. They are sent immediately except when rekeying
+is in progress, in which case they are queued until rekeying completes.
+
+The server advertises support for these messages using the
+SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message:
+
+	string		"ping@openssh.com"
+	string		"0" (version)
+
+The ping/reply message is implemented at the transport layer rather
+than as a named global or channel request to allow pings with very
+short packet lengths, which would not be possible with other
+approaches.
+
 2. Connection protocol changes
 
 2.1. connection: Channel write close extension "eow@openssh.com"
@@ -712,4 +745,4 @@ master instance and later clients.
 OpenSSH extends the usual agent protocol. These changes are documented
 in the PROTOCOL.agent file.
 
-$OpenBSD: PROTOCOL,v 1.48 2022/11/07 01:53:01 dtucker Exp $
+$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $
diff --git a/kex.c b/kex.c
index 502da12ed..8ff92f2a2 100644
--- a/kex.c
+++ b/kex.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.180 2023/08/21 21:16:18 tobhe Exp $ */
+/* $OpenBSD: kex.c,v 1.181 2023/08/28 03:28:43 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -492,12 +492,14 @@ kex_send_ext_info(struct ssh *ssh)
 		return SSH_ERR_ALLOC_FAIL;
 	/* XXX filter algs list by allowed pubkey/hostbased types */
 	if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 ||
-	    (r = sshpkt_put_u32(ssh, 2)) != 0 ||
+	    (r = sshpkt_put_u32(ssh, 3)) != 0 ||
 	    (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 ||
 	    (r = sshpkt_put_cstring(ssh, algs)) != 0 ||
 	    (r = sshpkt_put_cstring(ssh,
 	    "publickey-hostbound@openssh.com")) != 0 ||
 	    (r = sshpkt_put_cstring(ssh, "0")) != 0 ||
+	    (r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 ||
+	    (r = sshpkt_put_cstring(ssh, "0")) != 0 ||
 	    (r = sshpkt_send(ssh)) != 0) {
 		error_fr(r, "compose");
 		goto out;
@@ -527,6 +529,23 @@ kex_send_newkeys(struct ssh *ssh)
 	return 0;
 }
 
+/* Check whether an ext_info value contains the expected version string */
+static int
+kex_ext_info_check_ver(struct kex *kex, const char *name,
+    const u_char *val, size_t len, const char *want_ver, u_int flag)
+{
+	if (memchr(val, '\0', len) != NULL) {
+		error("SSH2_MSG_EXT_INFO: %s value contains nul byte", name);
+		return SSH_ERR_INVALID_FORMAT;
+	}
+	debug_f("%s=<%s>", name, val);
+	if (strcmp(val, want_ver) == 0)
+		kex->flags |= flag;
+	else
+		debug_f("unsupported version of %s extension", name);
+	return 0;
+}
+
 int
 kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
 {
@@ -557,6 +576,8 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
 			/* Ensure no \0 lurking in value */
 			if (memchr(val, '\0', vlen) != NULL) {
 				error_f("nul byte in %s", name);
+				free(name);
+				free(val);
 				return SSH_ERR_INVALID_FORMAT;
 			}
 			debug_f("%s=<%s>", name, val);
@@ -564,18 +585,18 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
 			val = NULL;
 		} else if (strcmp(name,
 		    "publickey-hostbound@openssh.com") == 0) {
-			/* XXX refactor */
-			/* Ensure no \0 lurking in value */
-			if (memchr(val, '\0', vlen) != NULL) {
-				error_f("nul byte in %s", name);
-				return SSH_ERR_INVALID_FORMAT;
+			if ((r = kex_ext_info_check_ver(kex, name, val, vlen,
+			    "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) {
+				free(name);
+				free(val);
+				return r;
 			}
-			debug_f("%s=<%s>", name, val);
-			if (strcmp(val, "0") == 0)
-				kex->flags |= KEX_HAS_PUBKEY_HOSTBOUND;
-			else {
-				debug_f("unsupported version of %s extension",
-				    name);
+		} else if (strcmp(name, "ping@openssh.com") == 0) {
+			if ((r = kex_ext_info_check_ver(kex, name, val, vlen,
+			    "0", KEX_HAS_PING)) != 0) {
+				free(name);
+				free(val);
+				return r;
 			}
 		} else
 			debug_f("%s (unrecognised)", name);
diff --git a/kex.h b/kex.h
index 8b54e3f4b..5f7ef784e 100644
--- a/kex.h
+++ b/kex.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.h,v 1.118 2023/03/06 12:14:48 dtucker Exp $ */
+/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -111,6 +111,7 @@ enum kex_exchange {
 #define KEX_HAS_PUBKEY_HOSTBOUND	0x0004
 #define KEX_RSA_SHA2_256_SUPPORTED 	0x0008 /* only set in server for now */
 #define KEX_RSA_SHA2_512_SUPPORTED 	0x0010 /* only set in server for now */
+#define KEX_HAS_PING		 	0x0020
 
 struct sshenc {
 	char	*name;
diff --git a/packet.c b/packet.c
index fdb8783bc..77e5c57ba 100644
--- a/packet.c
+++ b/packet.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.c,v 1.310 2023/04/06 03:21:31 djm Exp $ */
+/* $OpenBSD: packet.c,v 1.311 2023/08/28 03:28:43 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1054,6 +1054,8 @@ int
 ssh_packet_log_type(u_char type)
 {
 	switch (type) {
+	case SSH2_MSG_PING:
+	case SSH2_MSG_PONG:
 	case SSH2_MSG_CHANNEL_DATA:
 	case SSH2_MSG_CHANNEL_EXTENDED_DATA:
 	case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
@@ -1675,7 +1677,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
 		goto out;
 	if (ssh_packet_log_type(*typep))
 		debug3("receive packet: type %u", *typep);
-	if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) {
+	if (*typep < SSH2_MSG_MIN) {
 		if ((r = sshpkt_disconnect(ssh,
 		    "Invalid ssh2 packet type: %d", *typep)) != 0 ||
 		    (r = ssh_packet_write_wait(ssh)) != 0)
@@ -1710,6 +1712,8 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
 	u_int reason, seqnr;
 	int r;
 	u_char *msg;
+	const u_char *d;
+	size_t len;
 
 	for (;;) {
 		msg = NULL;
@@ -1753,6 +1757,21 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
 			debug("Received SSH2_MSG_UNIMPLEMENTED for %u",
 			    seqnr);
 			break;
+		case SSH2_MSG_PING:
+			if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0)
+				return r;
+			DBG(debug("Received SSH2_MSG_PING len %zu", len));
+			if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 ||
+			    (r = sshpkt_put_string(ssh, d, len)) != 0 ||
+			    (r = sshpkt_send(ssh)) != 0)
+				return r;
+			break;
+		case SSH2_MSG_PONG:
+			if ((r = sshpkt_get_string_direct(ssh,
+			    NULL, &len)) != 0)
+				return r;
+			DBG(debug("Received SSH2_MSG_PONG len %zu", len));
+			break;
 		default:
 			return 0;
 		}
diff --git a/ssh2.h b/ssh2.h
index 288c39f69..0d48d0527 100644
--- a/ssh2.h
+++ b/ssh2.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh2.h,v 1.20 2023/08/14 03:37:00 djm Exp $ */
+/* $OpenBSD: ssh2.h,v 1.21 2023/08/28 03:28:43 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -108,6 +108,10 @@
 #define SSH2_MSG_KEX_ECDH_INIT				30
 #define SSH2_MSG_KEX_ECDH_REPLY				31
 
+/* transport layer: OpenSSH extensions */
+#define SSH2_MSG_PING					192
+#define SSH2_MSG_PONG					193
+
 /* user authentication: generic */
 
 #define SSH2_MSG_USERAUTH_REQUEST			50

From 7603ba71264e7fa938325c37eca993e2fa61272f Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 28 Aug 2023 03:31:16 +0000
Subject: [PATCH 22/62] upstream: Add keystroke timing obfuscation to the
 client.

This attempts to hide inter-keystroke timings by sending interactive
traffic at fixed intervals (default: every 20ms) when there is only a
small amount of data being sent. It also sends fake "chaff" keystrokes
for a random interval after the last real keystroke. These are
controlled by a new ssh_config ObscureKeystrokeTiming keyword/

feedback/ok markus@

OpenBSD-Commit-ID: 02231ddd4f442212820976068c34a36e3c1b15be
---
 clientloop.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++++--
 misc.c       |  31 ++++++++----
 misc.h       |   3 +-
 packet.c     |  14 +++++-
 packet.h     |   3 +-
 readconf.c   |  64 ++++++++++++++++++++++++-
 readconf.h   |   8 +++-
 ssh_config.5 |  22 ++++++++-
 8 files changed, 256 insertions(+), 22 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index 99846a978..94ff2cb9b 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.392 2023/04/03 08:10:54 dtucker Exp $ */
+/* $OpenBSD: clientloop.c,v 1.393 2023/08/28 03:31:16 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -507,6 +507,128 @@ server_alive_check(struct ssh *ssh)
 	schedule_server_alive_check();
 }
 
+/* Try to send a dummy keystroke */
+static int
+send_chaff(struct ssh *ssh)
+{
+	int r;
+
+	if ((ssh->kex->flags & KEX_HAS_PING) == 0)
+		return 0;
+	/* XXX probabilistically send chaff? */
+	/*
+	 * a SSH2_MSG_CHANNEL_DATA payload is 9 bytes:
+	 *    4 bytes channel ID + 4 bytes string length + 1 byte string data
+	 * simulate that here.
+	 */
+	if ((r = sshpkt_start(ssh, SSH2_MSG_PING)) != 0 ||
+	    (r = sshpkt_put_cstring(ssh, "PING!")) != 0 ||
+	    (r = sshpkt_send(ssh)) != 0)
+		fatal_fr(r, "send packet");
+	return 1;
+}
+
+/*
+ * Performs keystroke timing obfuscation. Returns non-zero if the
+ * output fd should be polled.
+ */
+static int
+obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
+{
+	static int active;
+	static struct timespec next_interval, chaff_until;
+	struct timespec now, tmp;
+	int just_started = 0, had_keystroke = 0;
+	static unsigned long long nchaff;
+	char *stop_reason = NULL;
+	long long n;
+
+	monotime_ts(&now);
+
+	if (options.obscure_keystroke_timing_interval <= 0)
+		return 1;	/* disabled in config */
+
+	if (!channel_still_open(ssh) || quit_pending) {
+		/* Stop if no channels left of we're waiting for one to close */
+		stop_reason = "no active channels";
+	} else if (ssh_packet_is_rekeying(ssh)) {
+		/* Stop if we're rekeying */
+		stop_reason = "rekeying started";
+	} else if (!ssh_packet_interactive_data_to_write(ssh) &&
+	    ssh_packet_have_data_to_write(ssh)) {
+		/* Stop if the output buffer has more than a few keystrokes */
+		stop_reason = "output buffer filling";
+	} else if (active && ssh_packet_have_data_to_write(ssh)) {
+		/* Still in active mode and have a keystroke queued. */
+		had_keystroke = 1;
+	} else if (active) {
+		if (timespeccmp(&now, &chaff_until, >=)) {
+			/* Stop if there have been no keystrokes for a while */
+			stop_reason = "chaff time expired";
+		} else if (timespeccmp(&now, &next_interval, >=)) {
+			/* Otherwise if we were due to send, then send chaff */
+			if (send_chaff(ssh))
+				nchaff++;
+		}
+	}
+
+	if (stop_reason != NULL) {
+		active = 0;
+		debug3_f("stopping: %s (%llu chaff packets sent)",
+		    stop_reason, nchaff);
+		return 1;
+	}
+
+	/*
+	 * If we're in interactive mode, and only have a small amount
+	 * of outbound data, then we assume that the user is typing
+	 * interactively. In this case, start quantising outbound packets to
+	 * fixed time intervals to hide inter-keystroke timing.
+	 */
+	if (!active && ssh_packet_interactive_data_to_write(ssh)) {
+		debug3_f("starting: interval %d",
+		    options.obscure_keystroke_timing_interval);
+		just_started = had_keystroke = active = 1;
+		nchaff = 0;
+		ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval);
+		timespecadd(&now, &tmp, &next_interval);
+	}
+
+	/* Don't hold off if obfuscation inactive */
+	if (!active)
+		return 1;
+
+	if (had_keystroke) {
+		/*
+		 * Arrange to send chaff packets for a random interval after
+		 * the last keystroke was sent.
+		 */
+		ms_to_timespec(&tmp, SSH_KEYSTROKE_CHAFF_MIN_MS +
+		    arc4random_uniform(SSH_KEYSTROKE_CHAFF_RNG_MS));
+		timespecadd(&now, &tmp, &chaff_until);
+	}
+
+	ptimeout_deadline_monotime_tsp(timeout, &next_interval);
+
+	if (just_started)
+		return 1;
+
+	/* Don't arm output fd for poll until the timing interval has elapsed */
+	if (timespeccmp(&now, &next_interval, <))
+		return 0;
+
+	/* Calculate number of intervals missed since the last check */
+	n = (now.tv_sec - next_interval.tv_sec) * 1000 * 1000 * 1000;
+	n += now.tv_nsec - next_interval.tv_nsec;
+	n /= options.obscure_keystroke_timing_interval * 1000 * 1000;
+	n = (n < 0) ? 1 : n + 1;
+
+	/* Advance to the next interval */
+	ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval * n);
+	timespecadd(&now, &tmp, &next_interval);
+	return 1;
+}
+
 /*
  * Waits until the client can do something (some data becomes available on
  * one of the file descriptors).
@@ -517,7 +639,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
     int *conn_in_readyp, int *conn_out_readyp)
 {
 	struct timespec timeout;
-	int ret;
+	int ret, oready;
 	u_int p;
 
 	*conn_in_readyp = *conn_out_readyp = 0;
@@ -537,11 +659,14 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
 		return;
 	}
 
+	oready = obfuscate_keystroke_timing(ssh, &timeout);
+
 	/* Monitor server connection on reserved pollfd entries */
 	(*pfdp)[0].fd = connection_in;
 	(*pfdp)[0].events = POLLIN;
 	(*pfdp)[1].fd = connection_out;
-	(*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0;
+	(*pfdp)[1].events = (oready && ssh_packet_have_data_to_write(ssh)) ?
+	    POLLOUT : 0;
 
 	/*
 	 * Wait for something to happen.  This will suspend the process until
@@ -558,7 +683,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
 		    ssh_packet_get_rekey_timeout(ssh));
 	}
 
-	ret = poll(*pfdp, *npfd_activep, ptimeout_get_ms(&timeout));
+	ret = ppoll(*pfdp, *npfd_activep, ptimeout_get_tsp(&timeout), NULL);
 
 	if (ret == -1) {
 		/*
diff --git a/misc.c b/misc.c
index 956587035..42582c618 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.186 2023/08/18 01:37:41 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.187 2023/08/28 03:31:16 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005-2020 Damien Miller.  All rights reserved.
@@ -2901,22 +2901,33 @@ ptimeout_deadline_ms(struct timespec *pt, long ms)
 	ptimeout_deadline_tsp(pt, &p);
 }
 
+/* Specify a poll/ppoll deadline at wall clock monotime 'when' (timespec) */
+void
+ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when)
+{
+	struct timespec now, t;
+
+	monotime_ts(&now);
+
+	if (timespeccmp(&now, when, >=)) {
+		/* 'when' is now or in the past. Timeout ASAP */
+		pt->tv_sec = 0;
+		pt->tv_nsec = 0;
+	} else {
+		timespecsub(when, &now, &t);
+		ptimeout_deadline_tsp(pt, &t);
+	}
+}
+
 /* Specify a poll/ppoll deadline at wall clock monotime 'when' */
 void
 ptimeout_deadline_monotime(struct timespec *pt, time_t when)
 {
-	struct timespec now, t;
+	struct timespec t;
 
 	t.tv_sec = when;
 	t.tv_nsec = 0;
-	monotime_ts(&now);
-
-	if (timespeccmp(&now, &t, >=))
-		ptimeout_deadline_sec(pt, 0);
-	else {
-		timespecsub(&t, &now, &t);
-		ptimeout_deadline_tsp(pt, &t);
-	}
+	ptimeout_deadline_monotime_tsp(pt, &t);
 }
 
 /* Get a poll(2) timeout value in milliseconds */
diff --git a/misc.h b/misc.h
index f9bdc6eb5..4f941597e 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.104 2023/08/18 01:37:41 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.105 2023/08/28 03:31:16 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -214,6 +214,7 @@ struct timespec;
 void ptimeout_init(struct timespec *pt);
 void ptimeout_deadline_sec(struct timespec *pt, long sec);
 void ptimeout_deadline_ms(struct timespec *pt, long ms);
+void ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when);
 void ptimeout_deadline_monotime(struct timespec *pt, time_t when);
 int ptimeout_get_ms(struct timespec *pt);
 struct timespec *ptimeout_get_tsp(struct timespec *pt);
diff --git a/packet.c b/packet.c
index 77e5c57ba..52017defb 100644
--- a/packet.c
+++ b/packet.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.c,v 1.311 2023/08/28 03:28:43 djm Exp $ */
+/* $OpenBSD: packet.c,v 1.312 2023/08/28 03:31:16 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2083,6 +2083,18 @@ ssh_packet_not_very_much_data_to_write(struct ssh *ssh)
 		return sshbuf_len(ssh->state->output) < 128 * 1024;
 }
 
+/*
+ * returns true when there are at most a few keystrokes of data to write
+ * and the connection is in interactive mode.
+ */
+
+int
+ssh_packet_interactive_data_to_write(struct ssh *ssh)
+{
+	return ssh->state->interactive_mode &&
+	    sshbuf_len(ssh->state->output) < 256;
+}
+
 void
 ssh_packet_set_tos(struct ssh *ssh, int tos)
 {
diff --git a/packet.h b/packet.h
index 176488b1e..11925a27d 100644
--- a/packet.h
+++ b/packet.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.h,v 1.94 2022/01/22 00:49:34 djm Exp $ */
+/* $OpenBSD: packet.h,v 1.95 2023/08/28 03:31:16 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -145,6 +145,7 @@ int	 ssh_packet_write_poll(struct ssh *);
 int	 ssh_packet_write_wait(struct ssh *);
 int      ssh_packet_have_data_to_write(struct ssh *);
 int      ssh_packet_not_very_much_data_to_write(struct ssh *);
+int	 ssh_packet_interactive_data_to_write(struct ssh *);
 
 int	 ssh_packet_connection_is_on_socket(struct ssh *);
 int	 ssh_packet_remaining(struct ssh *);
diff --git a/readconf.c b/readconf.c
index 0d50e89b1..131c24f52 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.380 2023/07/17 06:16:33 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.381 2023/08/28 03:31:16 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -178,7 +178,7 @@ typedef enum {
 	oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms,
 	oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
 	oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
-	oEnableEscapeCommandline,
+	oEnableEscapeCommandline, oObscureKeystrokeTiming,
 	oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -327,6 +327,7 @@ static struct {
 	{ "knownhostscommand", oKnownHostsCommand },
 	{ "requiredrsasize", oRequiredRSASize },
 	{ "enableescapecommandline", oEnableEscapeCommandline },
+	{ "obscurekeystroketiming", oObscureKeystrokeTiming },
 
 	{ NULL, oBadOption }
 };
@@ -2280,6 +2281,48 @@ parse_pubkey_algos:
 		intptr = &options->required_rsa_size;
 		goto parse_int;
 
+	case oObscureKeystrokeTiming:
+		value = -1;
+		while ((arg = argv_next(&ac, &av)) != NULL) {
+			if (value != -1) {
+				error("%s line %d: invalid arguments",
+				    filename, linenum);
+				goto out;
+			}
+			if (strcmp(arg, "yes") == 0 ||
+			    strcmp(arg, "true") == 0)
+				value = SSH_KEYSTROKE_DEFAULT_INTERVAL_MS;
+			else if (strcmp(arg, "no") == 0 ||
+			    strcmp(arg, "false") == 0)
+				value = 0;
+			else if (strncmp(arg, "interval:", 9) == 0) {
+				if ((errstr = atoi_err(arg + 9,
+				    &value)) != NULL) {
+					error("%s line %d: integer value %s.",
+					    filename, linenum, errstr);
+					goto out;
+				}
+				if (value <= 0 || value > 1000) {
+					error("%s line %d: value out of range.",
+					    filename, linenum);
+					goto out;
+				}
+			} else {
+				error("%s line %d: unsupported argument \"%s\"",
+				    filename, linenum, arg);
+				goto out;
+			}
+		}
+		if (value == -1) {
+			error("%s line %d: missing argument",
+			    filename, linenum);
+			goto out;
+		}
+		intptr = &options->obscure_keystroke_timing_interval;
+		if (*activep && *intptr == -1)
+			*intptr = value;
+		break;
+
 	case oDeprecated:
 		debug("%s line %d: Deprecated option \"%s\"",
 		    filename, linenum, keyword);
@@ -2530,6 +2573,7 @@ initialize_options(Options * options)
 	options->known_hosts_command = NULL;
 	options->required_rsa_size = -1;
 	options->enable_escape_commandline = -1;
+	options->obscure_keystroke_timing_interval = -1;
 	options->tag = NULL;
 }
 
@@ -2731,6 +2775,10 @@ fill_default_options(Options * options)
 		options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
 	if (options->enable_escape_commandline == -1)
 		options->enable_escape_commandline = 0;
+	if (options->obscure_keystroke_timing_interval == -1) {
+		options->obscure_keystroke_timing_interval =
+		    SSH_KEYSTROKE_DEFAULT_INTERVAL_MS;
+	}
 
 	/* Expand KEX name lists */
 	all_cipher = cipher_alg_list(',', 0);
@@ -3273,6 +3321,16 @@ lookup_opcode_name(OpCodes code)
 static void
 dump_cfg_int(OpCodes code, int val)
 {
+	if (code == oObscureKeystrokeTiming) {
+		if (val == 0) {
+			printf("%s no\n", lookup_opcode_name(code));
+			return;
+		} else if (val == SSH_KEYSTROKE_DEFAULT_INTERVAL_MS) {
+			printf("%s yes\n", lookup_opcode_name(code));
+			return;
+		}
+		/* FALLTHROUGH */
+	}
 	printf("%s %d\n", lookup_opcode_name(code), val);
 }
 
@@ -3423,6 +3481,8 @@ dump_client_config(Options *o, const char *host)
 	dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max);
 	dump_cfg_int(oServerAliveInterval, o->server_alive_interval);
 	dump_cfg_int(oRequiredRSASize, o->required_rsa_size);
+	dump_cfg_int(oObscureKeystrokeTiming,
+	    o->obscure_keystroke_timing_interval);
 
 	/* String options */
 	dump_cfg_string(oBindAddress, o->bind_address);
diff --git a/readconf.h b/readconf.h
index dfe5bab0a..ce261bd63 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.151 2023/07/17 04:08:31 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.152 2023/08/28 03:31:16 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -180,6 +180,7 @@ typedef struct {
 
 	int	required_rsa_size;	/* minimum size of RSA keys */
 	int	enable_escape_commandline;	/* ~C commandline */
+	int	obscure_keystroke_timing_interval;
 
 	char	*ignored_unknown; /* Pattern list of unknown tokens to ignore */
 }       Options;
@@ -222,6 +223,11 @@ typedef struct {
 #define SSH_STRICT_HOSTKEY_YES	2
 #define SSH_STRICT_HOSTKEY_ASK	3
 
+/* ObscureKeystrokes parameters */
+#define SSH_KEYSTROKE_DEFAULT_INTERVAL_MS	20
+#define SSH_KEYSTROKE_CHAFF_MIN_MS		1024
+#define SSH_KEYSTROKE_CHAFF_RNG_MS		2048
+
 const char *kex_default_pk_alg(void);
 char	*ssh_connection_hash(const char *thishost, const char *host,
     const char *portstr, const char *user);
diff --git a/ssh_config.5 b/ssh_config.5
index ab8d1021d..5364ae85a 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.383 2023/07/17 05:36:14 jsg Exp $
-.Dd $Mdocdate: July 17 2023 $
+.\" $OpenBSD: ssh_config.5,v 1.384 2023/08/28 03:31:16 djm Exp $
+.Dd $Mdocdate: August 28 2023 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1358,6 +1358,24 @@ or
 Specifies the number of password prompts before giving up.
 The argument to this keyword must be an integer.
 The default is 3.
+.It Cm ObscureKeystrokeTiming
+Specifies whether
+.Xr ssh 1
+should try to obscure inter-keystroke timings from passive observers of
+network traffic.
+If enabled, then for interactive sessions,
+.Xr ssh 1
+will send keystrokes at fixed intervals of a few tens of milliseconds
+and will send fake keystroke packets for some time after typing ceases.
+The argument to this keyword must be
+.Cm yes ,
+.Cm no
+or an interval specifier of the form
+.Cm interval:milliseconds
+(e.g.\&
+.Cm interval:80 for 80 milliseconds).
+The default is to obscure keystrokes using a 20ms packet interval.
+Note that smaller intervals will result in higher fake keystroke packet rates.
 .It Cm PasswordAuthentication
 Specifies whether to use password authentication.
 The argument to this keyword must be

From 3867361ca691d0956ef7d5fb8181cf554a91d84a Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 28 Aug 2023 04:06:52 +0000
Subject: [PATCH 23/62] upstream: explicit long long type in timing
 calculations (doesn't

matter, since the range is pre-clamped)

OpenBSD-Commit-ID: f786ed902d04a5b8ecc581d068fea1a79aa772de
---
 clientloop.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index 94ff2cb9b..a0c3ed8d0 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.393 2023/08/28 03:31:16 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.394 2023/08/28 04:06:52 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -618,9 +618,9 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
 		return 0;
 
 	/* Calculate number of intervals missed since the last check */
-	n = (now.tv_sec - next_interval.tv_sec) * 1000 * 1000 * 1000;
+	n = (now.tv_sec - next_interval.tv_sec) * 1000LL * 1000 * 1000;
 	n += now.tv_nsec - next_interval.tv_nsec;
-	n /= options.obscure_keystroke_timing_interval * 1000 * 1000;
+	n /= options.obscure_keystroke_timing_interval * 1000LL * 1000;
 	n = (n < 0) ? 1 : n + 1;
 
 	/* Advance to the next interval */

From 528da5b9d7c5da01ed7a73ff21c722e1b5326006 Mon Sep 17 00:00:00 2001
From: "jmc@openbsd.org" <jmc@openbsd.org>
Date: Mon, 28 Aug 2023 05:32:28 +0000
Subject: [PATCH 24/62] upstream: add spacing for punctuation when macro args;

OpenBSD-Commit-ID: e80343c16ce0420b2aec98701527cf90371bd0db
---
 ssh_config.5 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ssh_config.5 b/ssh_config.5
index 5364ae85a..267ebabe4 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,7 +33,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.384 2023/08/28 03:31:16 djm Exp $
+.\" $OpenBSD: ssh_config.5,v 1.385 2023/08/28 05:32:28 jmc Exp $
 .Dd $Mdocdate: August 28 2023 $
 .Dt SSH_CONFIG 5
 .Os
@@ -1373,7 +1373,7 @@ The argument to this keyword must be
 or an interval specifier of the form
 .Cm interval:milliseconds
 (e.g.\&
-.Cm interval:80 for 80 milliseconds).
+.Cm interval:80 for 80 milliseconds ) .
 The default is to obscure keystrokes using a 20ms packet interval.
 Note that smaller intervals will result in higher fake keystroke packet rates.
 .It Cm PasswordAuthentication

From 01dbf3d46651b7d6ddf5e45d233839bbfffaeaec Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 28 Aug 2023 09:48:11 +0000
Subject: [PATCH 25/62] upstream: limit artificial login delay to a reasonable
 maximum (5s)

and don't delay at all for the "none" authentication mechanism. Patch by
Dmitry Belyavskiy in bz3602 with polish/ok dtucker@

OpenBSD-Commit-ID: 85b364676dd84cf1de0e98fc2fbdcb1a844ce515
---
 auth2.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/auth2.c b/auth2.c
index 34346e573..c628999e0 100644
--- a/auth2.c
+++ b/auth2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2.c,v 1.166 2023/03/08 04:43:12 guenther Exp $ */
+/* $OpenBSD: auth2.c,v 1.167 2023/08/28 09:48:11 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -218,6 +218,7 @@ input_service_request(int type, u_int32_t seq, struct ssh *ssh)
 }
 
 #define MIN_FAIL_DELAY_SECONDS 0.005
+#define MAX_FAIL_DELAY_SECONDS 5.0
 static double
 user_specific_delay(const char *user)
 {
@@ -243,6 +244,12 @@ ensure_minimum_time_since(double start, double seconds)
 	struct timespec ts;
 	double elapsed = monotime_double() - start, req = seconds, remain;
 
+	if (elapsed > MAX_FAIL_DELAY_SECONDS) {
+		debug3_f("elapsed %0.3lfms exceeded the max delay "
+		    "requested %0.3lfms)", elapsed*1000, req*1000);
+		return;
+	}
+
 	/* if we've already passed the requested time, scale up */
 	while ((remain = seconds - elapsed) < 0.0)
 		seconds *= 2;
@@ -334,7 +341,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
 		debug2("input_userauth_request: try method %s", method);
 		authenticated =	m->userauth(ssh, method);
 	}
-	if (!authctxt->authenticated)
+	if (!authctxt->authenticated && strcmp(method, "none") != 0)
 		ensure_minimum_time_since(tstart,
 		    user_specific_delay(authctxt->user));
 	userauth_finish(ssh, authenticated, method, NULL);

From cfa66857db90cd908de131e0041a50ffc17c7df8 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 28 Aug 2023 09:52:09 +0000
Subject: [PATCH 26/62] upstream: descriptive text shouldn't be under .Cm

OpenBSD-Commit-ID: b1afaeb456a52bc8a58f4f9f8b2f9fa8f6bf651b
---
 ssh_config.5 | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/ssh_config.5 b/ssh_config.5
index 267ebabe4..7f64c2cf9 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,7 +33,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.385 2023/08/28 05:32:28 jmc Exp $
+.\" $OpenBSD: ssh_config.5,v 1.386 2023/08/28 09:52:09 djm Exp $
 .Dd $Mdocdate: August 28 2023 $
 .Dt SSH_CONFIG 5
 .Os
@@ -1373,7 +1373,8 @@ The argument to this keyword must be
 or an interval specifier of the form
 .Cm interval:milliseconds
 (e.g.\&
-.Cm interval:80 for 80 milliseconds ) .
+.Cm interval:80
+for 80 milliseconds).
 The default is to obscure keystrokes using a 20ms packet interval.
 Note that smaller intervals will result in higher fake keystroke packet rates.
 .It Cm PasswordAuthentication

From f98031773db361424d59e3301aa92aacf423d920 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Tue, 29 Aug 2023 02:50:10 +0000
Subject: [PATCH 27/62] upstream: make PerSourceMaxStartups first-match-wins;
 ok dtucker@

OpenBSD-Commit-ID: dac0c24cb709e3c595b8b4f422a0355dc5a3b4e7
---
 servconf.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/servconf.c b/servconf.c
index 45a2f2c27..e16f9e90f 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.396 2023/07/17 05:26:38 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.397 2023/08/29 02:50:10 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -2030,7 +2030,7 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 				fatal("%s line %d: %s integer value %s.",
 				    filename, linenum, keyword, errstr);
 		}
-		if (*activep)
+		if (*activep && options->per_source_max_startups == -1)
 			options->per_source_max_startups = value;
 		break;
 

From ff3eda68ceb2e2bb8f48e3faceb96076c3e85c20 Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Thu, 31 Aug 2023 23:02:35 +1000
Subject: [PATCH 28/62] Set LLONG_MAX for C89 test.

If we don't have LLONG_MAX, configure will figure out that it can get it
by setting -std=gnu99, at which point we won't be testing C89 any more.
To avoid this, feed it in via CFLAGS.
---
 .github/configs | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/.github/configs b/.github/configs
index 66cb2d0e8..c7d6a55ab 100755
--- a/.github/configs
+++ b/.github/configs
@@ -30,6 +30,13 @@ case "$config" in
     default|sol64)
 	;;
     c89)
+	# If we don't have LLONG_MAX, configure will figure out that it can
+	# get it by setting -std=gnu99, at which point we won't be testing
+	# C89 any more.  To avoid this, feed it in via CFLAGS.
+	llong_max=`gcc -E -dM - </dev/null | \
+	    awk '$2=="__LONG_LONG_MAX__"{print $3}'`
+	CPPFLAGS="-DLLONG_MAX=${llong_max}"
+
 	CC="gcc"
 	CFLAGS="-Wall -std=c89 -pedantic -Werror=vla"
 	CONFIGFLAGS="--without-zlib"

From 43254b326ac6e2131dbd750f9464dc62c14bd5a7 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Sun, 3 Sep 2023 23:59:32 +0000
Subject: [PATCH 29/62] upstream: set interactive mode for ControlPersist
 sessions if they

originally requested a tty; enables keystroke timing obfuscation for most
ControlPersist sessions. Spotted by naddy@

OpenBSD-Commit-ID: 72783a26254202e2f3f41a2818a19956fe49a772
---
 ssh.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/ssh.c b/ssh.c
index caf3c692c..1dbbda7d6 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.593 2023/07/26 23:06:00 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.594 2023/09/03 23:59:32 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2140,7 +2140,7 @@ ssh_session2_open(struct ssh *ssh)
 static int
 ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
 {
-	int r, id = -1;
+	int r, interactive, id = -1;
 	char *cp, *tun_fwd_ifname = NULL;
 
 	/* XXX should be pre-session */
@@ -2197,8 +2197,11 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
 	if (options.session_type != SESSION_TYPE_NONE)
 		id = ssh_session2_open(ssh);
 	else {
-		ssh_packet_set_interactive(ssh,
-		    options.control_master == SSHCTL_MASTER_NO,
+		interactive = options.control_master == SSHCTL_MASTER_NO;
+		/* ControlPersist may have clobbered ControlMaster, so check */
+		if (need_controlpersist_detach)
+			interactive = otty_flag != 0;
+		ssh_packet_set_interactive(ssh, interactive,
 		    options.ip_qos_interactive, options.ip_qos_bulk);
 	}
 

From ccf7d913db34e49b7a6db1b8331bd402004c840d Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 4 Sep 2023 00:01:46 +0000
Subject: [PATCH 30/62] upstream: make channel_output_poll() return a flag
 indicating

whether channel data was enqueued. Will be used to improve keystroke timing
obfuscation. Problem spotted by / tested by naddy@

OpenBSD-Commit-ID: f9776c7b0065ba7c3bbe50431fd3b629f44314d0
---
 channels.c | 35 ++++++++++++++++++++++-------------
 channels.h |  4 ++--
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/channels.c b/channels.c
index da66b7b34..598ff322a 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.432 2023/07/04 03:59:21 dlg Exp $ */
+/* $OpenBSD: channels.c,v 1.433 2023/09/04 00:01:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2890,8 +2890,9 @@ channel_after_poll(struct ssh *ssh, struct pollfd *pfd, u_int npfd)
 
 /*
  * Enqueue data for channels with open or draining c->input.
+ * Returns non-zero if a packet was enqueued.
  */
-static void
+static int
 channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 {
 	size_t len, plen;
@@ -2914,7 +2915,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 			else
 				chan_ibuf_empty(ssh, c);
 		}
-		return;
+		return 0;
 	}
 
 	if (!c->have_remote_id)
@@ -2931,7 +2932,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 		 */
 		if (plen > c->remote_window || plen > c->remote_maxpacket) {
 			debug("channel %d: datagram too big", c->self);
-			return;
+			return 0;
 		}
 		/* Enqueue it */
 		if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 ||
@@ -2940,7 +2941,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 		    (r = sshpkt_send(ssh)) != 0)
 			fatal_fr(r, "channel %i: send datagram", c->self);
 		c->remote_window -= plen;
-		return;
+		return 1;
 	}
 
 	/* Enqueue packet for buffered data. */
@@ -2949,7 +2950,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 	if (len > c->remote_maxpacket)
 		len = c->remote_maxpacket;
 	if (len == 0)
-		return;
+		return 0;
 	if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 ||
 	    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
 	    (r = sshpkt_put_string(ssh, sshbuf_ptr(c->input), len)) != 0 ||
@@ -2958,19 +2959,21 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
 	if ((r = sshbuf_consume(c->input, len)) != 0)
 		fatal_fr(r, "channel %i: consume", c->self);
 	c->remote_window -= len;
+	return 1;
 }
 
 /*
  * Enqueue data for channels with open c->extended in read mode.
+ * Returns non-zero if a packet was enqueued.
  */
-static void
+static int
 channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
 {
 	size_t len;
 	int r;
 
 	if ((len = sshbuf_len(c->extended)) == 0)
-		return;
+		return 0;
 
 	debug2("channel %d: rwin %u elen %zu euse %d", c->self,
 	    c->remote_window, sshbuf_len(c->extended), c->extended_usage);
@@ -2979,7 +2982,7 @@ channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
 	if (len > c->remote_maxpacket)
 		len = c->remote_maxpacket;
 	if (len == 0)
-		return;
+		return 0;
 	if (!c->have_remote_id)
 		fatal_f("channel %d: no remote id", c->self);
 	if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA)) != 0 ||
@@ -2992,15 +2995,20 @@ channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
 		fatal_fr(r, "channel %i: consume", c->self);
 	c->remote_window -= len;
 	debug2("channel %d: sent ext data %zu", c->self, len);
+	return 1;
 }
 
-/* If there is data to send to the connection, enqueue some of it now. */
-void
+/*
+ * If there is data to send to the connection, enqueue some of it now.
+ * Returns non-zero if data was enqueued.
+ */
+int
 channel_output_poll(struct ssh *ssh)
 {
 	struct ssh_channels *sc = ssh->chanctxt;
 	Channel *c;
 	u_int i;
+	int ret = 0;
 
 	for (i = 0; i < sc->channels_alloc; i++) {
 		c = sc->channels[i];
@@ -3023,12 +3031,13 @@ channel_output_poll(struct ssh *ssh)
 		/* Get the amount of buffered data for this channel. */
 		if (c->istate == CHAN_INPUT_OPEN ||
 		    c->istate == CHAN_INPUT_WAIT_DRAIN)
-			channel_output_poll_input_open(ssh, c);
+			ret |= channel_output_poll_input_open(ssh, c);
 		/* Send extended data, i.e. stderr */
 		if (!(c->flags & CHAN_EOF_SENT) &&
 		    c->extended_usage == CHAN_EXTENDED_READ)
-			channel_output_poll_extended_read(ssh, c);
+			ret |= channel_output_poll_extended_read(ssh, c);
 	}
+	return ret;
 }
 
 /* -- mux proxy support  */
diff --git a/channels.h b/channels.h
index 7afba7837..58019a84e 100644
--- a/channels.h
+++ b/channels.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.h,v 1.151 2023/07/04 03:59:21 dlg Exp $ */
+/* $OpenBSD: channels.h,v 1.152 2023/09/04 00:01:46 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -335,7 +335,7 @@ struct timespec;
 void	 channel_prepare_poll(struct ssh *, struct pollfd **,
 	    u_int *, u_int *, u_int, struct timespec *);
 void	 channel_after_poll(struct ssh *, struct pollfd *, u_int);
-void     channel_output_poll(struct ssh *);
+int	 channel_output_poll(struct ssh *);
 
 int      channel_not_very_much_buffered_data(struct ssh *);
 void     channel_close_all(struct ssh *);

From b5fd97896b59a3a46245cf438cc8b16c795d9f74 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 4 Sep 2023 00:04:02 +0000
Subject: [PATCH 31/62] upstream: avoid bogus "obfuscate_keystroke_timing:
 stopping ..."

debug messages when keystroke timing obfuscation was never started; spotted
by naddy@

OpenBSD-Commit-ID: 5c270d35f7d2974db5c1646e9c64188f9393be31
---
 clientloop.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index a0c3ed8d0..f04a5aef8 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.394 2023/08/28 04:06:52 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.395 2023/09/04 00:04:02 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -573,9 +573,11 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
 	}
 
 	if (stop_reason != NULL) {
-		active = 0;
-		debug3_f("stopping: %s (%llu chaff packets sent)",
-		    stop_reason, nchaff);
+		if (active) {
+			debug3_f("stopping: %s (%llu chaff packets sent)",
+			    stop_reason, nchaff);
+			active = 0;
+		}
 		return 1;
 	}
 

From 694150ad92765574ff82a18f4e86322bd3231e68 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 4 Sep 2023 00:08:14 +0000
Subject: [PATCH 32/62] upstream: trigger keystroke timing obfucation only if
 the channels

layer enqueud some data in the last poll() cycle; this avoids triggering the
obfuscatior for non-channels data like ClientAlive probes and also fixes a
related problem were the obfucations would be triggered on fully quiescent
connections.

Based on / tested by naddy@

OpenBSD-Commit-ID: d98f32dc62d7663ff4660e4556e184032a0db123
---
 clientloop.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index f04a5aef8..93bd9343c 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.395 2023/09/04 00:04:02 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.396 2023/09/04 00:08:14 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -533,7 +533,8 @@ send_chaff(struct ssh *ssh)
  * output fd should be polled.
  */
 static int
-obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
+obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
+    int channel_did_enqueue)
 {
 	static int active;
 	static struct timespec next_interval, chaff_until;
@@ -558,7 +559,8 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
 	    ssh_packet_have_data_to_write(ssh)) {
 		/* Stop if the output buffer has more than a few keystrokes */
 		stop_reason = "output buffer filling";
-	} else if (active && ssh_packet_have_data_to_write(ssh)) {
+	} else if (active && channel_did_enqueue &&
+	    ssh_packet_have_data_to_write(ssh)) {
 		/* Still in active mode and have a keystroke queued. */
 		had_keystroke = 1;
 	} else if (active) {
@@ -587,7 +589,8 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
 	 * interactively. In this case, start quantising outbound packets to
 	 * fixed time intervals to hide inter-keystroke timing.
 	 */
-	if (!active && ssh_packet_interactive_data_to_write(ssh)) {
+	if (!active && ssh_packet_interactive_data_to_write(ssh) &&
+	    channel_did_enqueue && ssh_packet_have_data_to_write(ssh)) {
 		debug3_f("starting: interval %d",
 		    options.obscure_keystroke_timing_interval);
 		just_started = had_keystroke = active = 1;
@@ -637,7 +640,7 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout)
  */
 static void
 client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
-    u_int *npfd_allocp, u_int *npfd_activep, int rekeying,
+    u_int *npfd_allocp, u_int *npfd_activep, int channel_did_enqueue,
     int *conn_in_readyp, int *conn_out_readyp)
 {
 	struct timespec timeout;
@@ -661,7 +664,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
 		return;
 	}
 
-	oready = obfuscate_keystroke_timing(ssh, &timeout);
+	oready = obfuscate_keystroke_timing(ssh, &timeout, channel_did_enqueue);
 
 	/* Monitor server connection on reserved pollfd entries */
 	(*pfdp)[0].fd = connection_in;
@@ -680,7 +683,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
 		ptimeout_deadline_monotime(&timeout, control_persist_exit_time);
 	if (options.server_alive_interval > 0)
 		ptimeout_deadline_monotime(&timeout, server_alive_time);
-	if (options.rekey_interval > 0 && !rekeying) {
+	if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) {
 		ptimeout_deadline_sec(&timeout,
 		    ssh_packet_get_rekey_timeout(ssh));
 	}
@@ -1402,7 +1405,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
 	struct pollfd *pfd = NULL;
 	u_int npfd_alloc = 0, npfd_active = 0;
 	double start_time, total_time;
-	int r, len;
+	int channel_did_enqueue = 0, r, len;
 	u_int64_t ibytes, obytes;
 	int conn_in_ready, conn_out_ready;
 
@@ -1492,6 +1495,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
 
 	/* Main loop of the client for the interactive session mode. */
 	while (!quit_pending) {
+		channel_did_enqueue = 0;
 
 		/* Process buffered packets sent by the server. */
 		client_process_buffered_input_packets(ssh);
@@ -1513,7 +1517,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
 			 * enqueue them for sending to the server.
 			 */
 			if (ssh_packet_not_very_much_data_to_write(ssh))
-				channel_output_poll(ssh);
+				channel_did_enqueue = channel_output_poll(ssh);
 
 			/*
 			 * Check if the window size has changed, and buffer a
@@ -1529,7 +1533,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
 		 * available on one of the descriptors).
 		 */
 		client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc,
-		    &npfd_active, ssh_packet_is_rekeying(ssh),
+		    &npfd_active, channel_did_enqueue,
 		    &conn_in_ready, &conn_out_ready);
 
 		if (quit_pending)

From e1c284d60a928bcdd60bc575c6f9604663502770 Mon Sep 17 00:00:00 2001
From: "job@openbsd.org" <job@openbsd.org>
Date: Mon, 4 Sep 2023 10:29:58 +0000
Subject: [PATCH 33/62] upstream: Generate Ed25519 keys when invoked without
 arguments

Ed25519 public keys are very convenient due to their small size.
OpenSSH has supported Ed25519 since version 6.5 (January 2014).

OK djm@ markus@ sthen@ deraadt@

OpenBSD-Commit-ID: f498beaad19c8cdcc357381a60df4a9c69858b3f
---
 ssh-keygen.1 |  6 +++---
 ssh-keygen.c | 10 +++-------
 2 files changed, 6 insertions(+), 10 deletions(-)

diff --git a/ssh-keygen.1 b/ssh-keygen.1
index c760f91be..c392141ea 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: ssh-keygen.1,v 1.229 2023/07/23 20:04:45 naddy Exp $
+.\"	$OpenBSD: ssh-keygen.1,v 1.230 2023/09/04 10:29:58 job Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: July 23 2023 $
+.Dd $Mdocdate: September 4 2023 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -185,7 +185,7 @@ The type of key to be generated is specified with the
 option.
 If invoked without any arguments,
 .Nm
-will generate an RSA key.
+will generate an Ed25519 key.
 .Pp
 .Nm
 is also used to generate groups for use in Diffie-Hellman group
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 9ccea624c..5b945a849 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.470 2023/07/17 04:01:10 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.471 2023/09/04 10:29:58 job Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -67,11 +67,7 @@
 #include "sk-api.h" /* XXX for SSH_SK_USER_PRESENCE_REQD; remove */
 #include "cipher.h"
 
-#ifdef WITH_OPENSSL
-# define DEFAULT_KEY_TYPE_NAME "rsa"
-#else
-# define DEFAULT_KEY_TYPE_NAME "ed25519"
-#endif
+#define DEFAULT_KEY_TYPE_NAME "ed25519"
 
 /*
  * Default number of bits in the RSA, DSA and ECDSA keys.  These value can be
@@ -263,7 +259,7 @@ ask_filename(struct passwd *pw, const char *prompt)
 	char *name = NULL;
 
 	if (key_type_name == NULL)
-		name = _PATH_SSH_CLIENT_ID_RSA;
+		name = _PATH_SSH_CLIENT_ID_ED25519;
 	else {
 		switch (sshkey_type_from_name(key_type_name)) {
 		case KEY_DSA_CERT:

From 1ee0a16e07b6f0847ff463d7b5221c4bf1876e25 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:18:15 +0000
Subject: [PATCH 34/62] upstream: handle cr+lf (instead of just cr) in sshsig
 signature

files

OpenBSD-Commit-ID: 647460a212b916540016d066568816507375fd7f
---
 sshsig.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/sshsig.c b/sshsig.c
index 854d67322..d219db90e 100644
--- a/sshsig.c
+++ b/sshsig.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshsig.c,v 1.32 2023/04/06 03:56:02 djm Exp $ */
+/* $OpenBSD: sshsig.c,v 1.33 2023/09/06 23:18:15 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -38,7 +38,7 @@
 #define SIG_VERSION		0x01
 #define MAGIC_PREAMBLE		"SSHSIG"
 #define MAGIC_PREAMBLE_LEN	(sizeof(MAGIC_PREAMBLE) - 1)
-#define BEGIN_SIGNATURE		"-----BEGIN SSH SIGNATURE-----\n"
+#define BEGIN_SIGNATURE		"-----BEGIN SSH SIGNATURE-----"
 #define END_SIGNATURE		"-----END SSH SIGNATURE-----"
 #define RSA_SIGN_ALG		"rsa-sha2-512" /* XXX maybe make configurable */
 #define RSA_SIGN_ALLOWED	"rsa-sha2-512,rsa-sha2-256"
@@ -59,8 +59,7 @@ sshsig_armor(const struct sshbuf *blob, struct sshbuf **out)
 		goto out;
 	}
 
-	if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
-	    sizeof(BEGIN_SIGNATURE)-1)) != 0) {
+	if ((r = sshbuf_putf(buf, "%s\n", BEGIN_SIGNATURE)) != 0) {
 		error_fr(r, "sshbuf_putf");
 		goto out;
 	}
@@ -99,23 +98,35 @@ sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out)
 		return SSH_ERR_ALLOC_FAIL;
 	}
 
+	/* Expect and consume preamble + lf/crlf */
 	if ((r = sshbuf_cmp(sbuf, 0,
 	    BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
 		error("Couldn't parse signature: missing header");
 		goto done;
 	}
-
 	if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
 		error_fr(r, "consume");
 		goto done;
 	}
-
+	if ((r = sshbuf_cmp(sbuf, 0, "\r\n", 2)) == 0)
+		eoffset = 2;
+	else if ((r = sshbuf_cmp(sbuf, 0, "\n", 1)) == 0)
+		eoffset = 1;
+	else {
+		r = SSH_ERR_INVALID_FORMAT;
+		error_f("no header eol");
+		goto done;
+	}
+	if ((r = sshbuf_consume(sbuf, eoffset)) != 0) {
+		error_fr(r, "consume eol");
+		goto done;
+	}
+	/* Find and consume lf + suffix (any prior cr would be ignored) */
 	if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE,
-	    sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) {
+	    sizeof(END_SIGNATURE), &eoffset)) != 0) {
 		error("Couldn't parse signature: missing footer");
 		goto done;
 	}
-
 	if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) {
 		error_fr(r, "consume");
 		goto done;

From 52dfe3c72d98503d8b7c6f64fc7e19d685636c0b Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:21:36 +0000
Subject: [PATCH 35/62] upstream: downgrade duplicate Subsystem directives from
 being a

fatal error to being a debug message to match behaviour with just about all
other directives.

OpenBSD-Commit-ID: fc90ed2cc0c18d4eb8e33d2c5e98d25f282588ce
---
 servconf.c | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/servconf.c b/servconf.c
index e16f9e90f..a3779a9d8 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.397 2023/08/29 02:50:10 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.398 2023/09/06 23:21:36 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -1942,13 +1942,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 			fatal("%s line %d: %s missing argument.",
 			    filename, linenum, keyword);
 		if (!*activep) {
-			arg = argv_next(&ac, &av);
+			argv_consume(&ac);
+			break;
+		}
+		found = 0;
+		for (i = 0; i < options->num_subsystems; i++) {
+			if (strcmp(arg, options->subsystem_name[i]) == 0) {
+				found = 1;
+				break;
+			}
+		}
+		if (found) {
+			debug("%s line %d: Subsystem '%s' already defined.",
+			    filename, linenum, arg);
+			argv_consume(&ac);
 			break;
 		}
-		for (i = 0; i < options->num_subsystems; i++)
-			if (strcmp(arg, options->subsystem_name[i]) == 0)
-				fatal("%s line %d: Subsystem '%s' "
-				    "already defined.", filename, linenum, arg);
 		options->subsystem_name[options->num_subsystems] = xstrdup(arg);
 		arg = argv_next(&ac, &av);
 		if (!arg || *arg == '\0')

From e19069c9fac4c111d6496b19c7f7db43b4f07b4f Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:23:53 +0000
Subject: [PATCH 36/62] upstream: preserve quoting of Subsystem commands and
 arguments.

This may change behaviour of exotic configurations, but the most common
subsystem configuration (sftp-server) is unlikely to be affected.

OpenBSD-Commit-ID: 8ffa296aeca981de5b0945242ce75aa6dee479bf
---
 servconf.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/servconf.c b/servconf.c
index a3779a9d8..91449d543 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.398 2023/09/06 23:21:36 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.399 2023/09/06 23:23:53 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -1960,21 +1960,19 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 		}
 		options->subsystem_name[options->num_subsystems] = xstrdup(arg);
 		arg = argv_next(&ac, &av);
-		if (!arg || *arg == '\0')
+		if (!arg || *arg == '\0') {
 			fatal("%s line %d: Missing subsystem command.",
 			    filename, linenum);
-		options->subsystem_command[options->num_subsystems] = xstrdup(arg);
-
-		/* Collect arguments (separate to executable) */
-		p = xstrdup(arg);
-		len = strlen(p) + 1;
-		while ((arg = argv_next(&ac, &av)) != NULL) {
-			len += 1 + strlen(arg);
-			p = xreallocarray(p, 1, len);
-			strlcat(p, " ", len);
-			strlcat(p, arg, len);
 		}
-		options->subsystem_args[options->num_subsystems] = p;
+		options->subsystem_command[options->num_subsystems] =
+		    xstrdup(arg);
+		/* Collect arguments (separate to executable) */
+		arg = argv_assemble(1, &arg); /* quote command correctly */
+		arg2 = argv_assemble(ac, av); /* rest of command */
+		xasprintf(&options->subsystem_args[options->num_subsystems],
+		    "%s %s", arg, arg2);
+		free(arg2);
+		argv_consume(&ac);
 		options->num_subsystems++;
 		break;
 

From 6e52826e2a74d077147a82ead8d4fbd5b54f4e3b Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:26:37 +0000
Subject: [PATCH 37/62] upstream: allocate the subsystems array as necessary
 and remove the

fixed limit of subsystems. Saves a few kb of memory in the server and makes
it more like the other options.

OpenBSD-Commit-ID: e683dfca6bdcbc3cc339bb6c6517c0c4736a547f
---
 servconf.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/servconf.c b/servconf.c
index 91449d543..6650162be 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.399 2023/09/06 23:23:53 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.400 2023/09/06 23:26:37 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -1933,10 +1933,6 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 		break;
 
 	case sSubsystem:
-		if (options->num_subsystems >= MAX_SUBSYSTEMS) {
-			fatal("%s line %d: too many subsystems defined.",
-			    filename, linenum);
-		}
 		arg = argv_next(&ac, &av);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: %s missing argument.",
@@ -1958,6 +1954,18 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 			argv_consume(&ac);
 			break;
 		}
+		options->subsystem_name = xrecallocarray(
+		    options->subsystem_name, options->num_subsystems,
+		    options->num_subsystems + 1,
+		    sizeof(options->subsystem_name));
+		options->subsystem_command = xrecallocarray(
+		    options->subsystem_command, options->num_subsystems,
+		    options->num_subsystems + 1,
+		    sizeof(options->subsystem_command));
+		options->subsystem_args = xrecallocarray(
+		    options->subsystem_args, options->num_subsystems,
+		    options->num_subsystems + 1,
+		    sizeof(options->subsystem_args));
 		options->subsystem_name[options->num_subsystems] = xstrdup(arg);
 		arg = argv_next(&ac, &av);
 		if (!arg || *arg == '\0') {

From 8a1450c62035e834d8a79a5d0d1c904236f9dcfe Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:35:35 +0000
Subject: [PATCH 38/62] upstream: allow override of Sybsystem directives in
 sshd Match

blocks

OpenBSD-Commit-ID: 3911d18a826a2d2fe7e4519075cf3e57af439722
---
 servconf.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
 servconf.h | 14 ++++++++------
 2 files changed, 54 insertions(+), 8 deletions(-)

diff --git a/servconf.c b/servconf.c
index 6650162be..3c2bf4827 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.400 2023/09/06 23:26:37 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.401 2023/09/06 23:35:35 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -643,7 +643,7 @@ static struct {
 	{ "macs", sMacs, SSHCFG_GLOBAL },
 	{ "protocol", sIgnore, SSHCFG_GLOBAL },
 	{ "gatewayports", sGatewayPorts, SSHCFG_ALL },
-	{ "subsystem", sSubsystem, SSHCFG_GLOBAL },
+	{ "subsystem", sSubsystem, SSHCFG_ALL },
 	{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
 	{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL },
 	{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL },
@@ -2694,6 +2694,47 @@ int parse_server_match_testspec(struct connection_info *ci, char *spec)
 	return 0;
 }
 
+void
+servconf_merge_subsystems(ServerOptions *dst, ServerOptions *src)
+{
+	u_int i, j, found;
+
+	for (i = 0; i < src->num_subsystems; i++) {
+		found = 0;
+		for (j = 0; j < dst->num_subsystems; j++) {
+			if (strcmp(src->subsystem_name[i],
+			    dst->subsystem_name[j]) == 0) {
+				found = 1;
+				break;
+			}
+		}
+		if (found) {
+			debug_f("override \"%s\"", dst->subsystem_name[j]);
+			free(dst->subsystem_command[j]);
+			free(dst->subsystem_args[j]);
+			dst->subsystem_command[j] =
+			    xstrdup(src->subsystem_command[i]);
+			dst->subsystem_args[j] =
+			    xstrdup(src->subsystem_args[i]);
+			continue;
+		}
+		debug_f("add \"%s\"", src->subsystem_name[i]);
+		dst->subsystem_name = xrecallocarray(
+		    dst->subsystem_name, dst->num_subsystems,
+		    dst->num_subsystems + 1, sizeof(dst->subsystem_name));
+		dst->subsystem_command = xrecallocarray(
+		    dst->subsystem_command, dst->num_subsystems,
+		    dst->num_subsystems + 1, sizeof(dst->subsystem_command));
+		dst->subsystem_args = xrecallocarray(
+		    dst->subsystem_args, dst->num_subsystems,
+		    dst->num_subsystems + 1, sizeof(dst->subsystem_args));
+		j = dst->num_subsystems++;
+		dst->subsystem_name[j] = xstrdup(src->subsystem_name[i]);
+		dst->subsystem_command[j] = xstrdup(src->subsystem_command[i]);
+		dst->subsystem_args[j] = xstrdup(src->subsystem_args[i]);
+	}
+}
+
 /*
  * Copy any supported values that are set.
  *
@@ -2800,6 +2841,9 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
 		free(dst->chroot_directory);
 		dst->chroot_directory = NULL;
 	}
+
+	/* Subsystems require merging. */
+	servconf_merge_subsystems(dst, src);
 }
 
 #undef M_CP_INTOPT
diff --git a/servconf.h b/servconf.h
index 7ad43de87..ed7b72e8e 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.159 2023/01/17 09:44:48 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.160 2023/09/06 23:35:35 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -20,8 +20,6 @@
 
 #define MAX_PORTS		256	/* Max # ports. */
 
-#define MAX_SUBSYSTEMS		256	/* Max # subsystems. */
-
 /* permit_root_login */
 #define	PERMIT_NOT_SET		-1
 #define	PERMIT_NO		0
@@ -165,9 +163,9 @@ typedef struct {
 	char   **deny_groups;
 
 	u_int num_subsystems;
-	char   *subsystem_name[MAX_SUBSYSTEMS];
-	char   *subsystem_command[MAX_SUBSYSTEMS];
-	char   *subsystem_args[MAX_SUBSYSTEMS];
+	char   **subsystem_name;
+	char   **subsystem_command;
+	char   **subsystem_args;
 
 	u_int num_accept_env;
 	char   **accept_env;
@@ -294,6 +292,9 @@ TAILQ_HEAD(include_list, include_item);
 		M_CP_STRARRAYOPT(permitted_listens, num_permitted_listens); \
 		M_CP_STRARRAYOPT(channel_timeouts, num_channel_timeouts); \
 		M_CP_STRARRAYOPT(log_verbose, num_log_verbose); \
+		M_CP_STRARRAYOPT(subsystem_name, num_subsystems); \
+		M_CP_STRARRAYOPT(subsystem_command, num_subsystems); \
+		M_CP_STRARRAYOPT(subsystem_args, num_subsystems); \
 	} while (0)
 
 struct connection_info *get_connection_info(struct ssh *, int, int);
@@ -310,6 +311,7 @@ void	 parse_server_match_config(ServerOptions *,
 	    struct include_list *includes, struct connection_info *);
 int	 parse_server_match_testspec(struct connection_info *, char *);
 int	 server_match_spec_complete(struct connection_info *);
+void	 servconf_merge_subsystems(ServerOptions *, ServerOptions *);
 void	 copy_set_server_options(ServerOptions *, ServerOptions *, int);
 void	 dump_config(ServerOptions *);
 char	*derelativise_path(const char *);

From 0e1f4401c466fa4fdaea81b6dadc8dd1fc4cf0af Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 6 Sep 2023 23:36:09 +0000
Subject: [PATCH 39/62] upstream: regression test for override of subsystem in
 match blocks

OpenBSD-Regress-ID: 5f8135da3bfda71067084c048d717b0e8793e87c
---
 regress/Makefile           |  5 ++-
 regress/match-subsystem.sh | 90 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+), 2 deletions(-)
 create mode 100644 regress/match-subsystem.sh

diff --git a/regress/Makefile b/regress/Makefile
index 5caf9b8e4..c21b0215a 100644
--- a/regress/Makefile
+++ b/regress/Makefile
@@ -1,4 +1,4 @@
-#	$OpenBSD: Makefile,v 1.125 2023/05/17 05:52:01 djm Exp $
+#	$OpenBSD: Makefile,v 1.126 2023/09/06 23:36:09 djm Exp $
 
 tests:		prep file-tests t-exec unit
 
@@ -103,7 +103,8 @@ LTESTS= 	connect \
 		agent-restrict \
 		hostbased \
 		channel-timeout \
-		connection-timeout
+		connection-timeout \
+		match-subsystem
 
 INTEROP_TESTS=	putty-transfer putty-ciphers putty-kex conch-ciphers
 #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
diff --git a/regress/match-subsystem.sh b/regress/match-subsystem.sh
new file mode 100644
index 000000000..0b691d8e8
--- /dev/null
+++ b/regress/match-subsystem.sh
@@ -0,0 +1,90 @@
+#	$OpenBSD: match-subsystem.sh,v 1.1 2023/09/06 23:36:09 djm Exp $
+#	Placed in the Public Domain.
+
+tid="sshd_config match subsystem"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+try_subsystem() {
+	_id=$1
+	_subsystem=$2
+	_expect=$3
+	${SSHD} -tf $OBJ/sshd_proxy || fatal "$_id: bad config"
+	${SSH} -sF $OBJ/ssh_proxy somehost $_subsystem
+	_exit=$?
+	trace "$_id subsystem $_subsystem"
+	if [ $_exit -ne $_expect ] ; then
+		fail "$_id: subsystem $_subsystem exit $_exit expected $_expect"
+	fi
+	return $?
+}
+
+# Simple case: subsystem in main config.
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Subsystem xxx /bin/sh -c "exit 23"
+_EOF
+try_subsystem "main config" xxx 23
+
+# No clobber in main config.
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Subsystem xxx /bin/sh -c "exit 23"
+Subsystem xxx /bin/sh -c "exit 24"
+_EOF
+try_subsystem "main config no clobber" xxx 23
+
+# Subsystem in match all block
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Match all
+Subsystem xxx /bin/sh -c "exit 21"
+_EOF
+try_subsystem "match all" xxx 21
+
+# No clobber in match all block
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Match all
+Subsystem xxx /bin/sh -c "exit 21"
+Subsystem xxx /bin/sh -c "exit 24"
+_EOF
+try_subsystem "match all no clobber" xxx 21
+
+# Subsystem in match user block
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Match user *
+Subsystem xxx /bin/sh -c "exit 20"
+_EOF
+try_subsystem "match user" xxx 20
+
+# No clobber in match user block
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Match user *
+Subsystem xxx /bin/sh -c "exit 20"
+Subsystem xxx /bin/sh -c "exit 24"
+Match all
+Subsystem xxx /bin/sh -c "exit 24"
+_EOF
+try_subsystem "match user no clobber" xxx 20
+
+# Override main with match all
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Subsystem xxx /bin/sh -c "exit 23"
+Match all
+Subsystem xxx /bin/sh -c "exit 19"
+_EOF
+try_subsystem "match all override" xxx 19
+
+# Override main with match user
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >> $OBJ/sshd_proxy << _EOF
+Subsystem xxx /bin/sh -c "exit 23"
+Match user *
+Subsystem xxx /bin/sh -c "exit 18"
+_EOF
+try_subsystem "match user override" xxx 18
+

From 249d8bd0472b53e3a2a0e138b4c030a31e83346a Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 05:50:12 +0000
Subject: [PATCH 40/62] upstream: fix scp in SFTP mode recursive upload and
 download of

directories that contain symlinks to other directories. In scp mode, the
links would be followed, but in SFTP mode they were not. bz3611, ok dtucker@

OpenBSD-Commit-ID: 9760fda668eaa94a992250d7670dfbc62a45197c
---
 sftp-client.c | 73 +++++++++++++++++++++++++++++++++++----------------
 1 file changed, 50 insertions(+), 23 deletions(-)

diff --git a/sftp-client.c b/sftp-client.c
index 098b9121a..d6566a834 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.171 2023/04/30 22:54:22 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.172 2023/09/08 05:50:12 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -1890,6 +1890,7 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 	SFTP_DIRENT **dir_entries;
 	char *filename, *new_src = NULL, *new_dst = NULL;
 	mode_t mode = 0777, tmpmode = mode;
+	Attrib *a, ldirattrib, lsym;
 
 	if (depth >= MAX_DIR_DEPTH) {
 		error("Maximum directory depth exceeded: %d levels", depth);
@@ -1898,10 +1899,14 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 
 	debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
 
-	if (dirattrib == NULL &&
-	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
-		error("stat remote \"%s\" directory failed", src);
-		return -1;
+	if (dirattrib == NULL) {
+		if ((a = do_stat(conn, src, 1)) == NULL) {
+			error("stat remote \"%s\" directory failed", src);
+			return -1;
+		}
+		/* Don't let this be clobbered by later do_stat calls */
+		ldirattrib = *a;
+		dirattrib = &ldirattrib;
 	}
 	if (!S_ISDIR(dirattrib->perm)) {
 		error("\"%s\" is not a directory", src);
@@ -1936,25 +1941,35 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		new_dst = path_append(dst, filename);
 		new_src = path_append(src, filename);
 
-		if (S_ISDIR(dir_entries[i]->a.perm)) {
+		a = &dir_entries[i]->a;
+		if (S_ISLNK(a->perm)) {
+			if (!follow_link_flag) {
+				logit("download \"%s\": not a regular file",
+				    new_src);
+				continue;
+			}
+			/* Replace the stat contents with the symlink target */
+			if ((a = do_stat(conn, new_src, 1)) == NULL) {
+				logit("remote stat \"%s\" failed", new_src);
+				ret = -1;
+				continue;
+			}
+			/* Don't let this be clobbered by later do_stat calls */
+			lsym = *a;
+			a = &lsym;
+		}
+
+		if (S_ISDIR(a->perm)) {
 			if (strcmp(filename, ".") == 0 ||
 			    strcmp(filename, "..") == 0)
 				continue;
 			if (download_dir_internal(conn, new_src, new_dst,
-			    depth + 1, &(dir_entries[i]->a), preserve_flag,
+			    depth + 1, a, preserve_flag,
 			    print_flag, resume_flag,
 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
 				ret = -1;
-		} else if (S_ISREG(dir_entries[i]->a.perm) ||
-		    (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
-			/*
-			 * If this is a symlink then don't send the link's
-			 * Attrib. do_download() will do a FXP_STAT operation
-			 * and get the link target's attributes.
-			 */
-			if (do_download(conn, new_src, new_dst,
-			    S_ISLNK(dir_entries[i]->a.perm) ? NULL :
-			    &(dir_entries[i]->a),
+		} else if (S_ISREG(a->perm)) {
+			if (do_download(conn, new_src, new_dst, a,
 			    preserve_flag, resume_flag, fsync_flag,
 			    inplace_flag) == -1) {
 				error("Download of file %s to %s failed",
@@ -2299,21 +2314,33 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		new_dst = path_append(dst, filename);
 		new_src = path_append(src, filename);
 
+		if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
+			continue;
 		if (lstat(new_src, &sb) == -1) {
 			logit("local lstat \"%s\": %s", filename,
 			    strerror(errno));
 			ret = -1;
-		} else if (S_ISDIR(sb.st_mode)) {
-			if (strcmp(filename, ".") == 0 ||
-			    strcmp(filename, "..") == 0)
+			continue;
+		}
+		if (S_ISLNK(sb.st_mode)) {
+			if (!follow_link_flag) {
+				logit("%s: not a regular file", filename);
 				continue;
-
+			}
+			/* Replace the stat contents with the symlink target */
+			if (stat(new_src, &sb) == -1) {
+				logit("local stat \"%s\": %s", filename,
+				    strerror(errno));
+				ret = -1;
+				continue;
+			}
+		}
+		if (S_ISDIR(sb.st_mode)) {
 			if (upload_dir_internal(conn, new_src, new_dst,
 			    depth + 1, preserve_flag, print_flag, resume,
 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
 				ret = -1;
-		} else if (S_ISREG(sb.st_mode) ||
-		    (follow_link_flag && S_ISLNK(sb.st_mode))) {
+		} else if (S_ISREG(sb.st_mode)) {
 			if (do_upload(conn, new_src, new_dst,
 			    preserve_flag, resume, fsync_flag,
 			    inplace_flag) == -1) {

From 2de990142a83bf60ef694378b8598706bc654b08 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 05:56:13 +0000
Subject: [PATCH 41/62] upstream: the sftp code was one of my first
 contributions to

OpenSSH and it shows - the function names are terrible.

Rename do_blah() to sftp_blah() to make them less so.

Completely mechanical except for sftp_stat() and sftp_lstat() which
change from returning a pointer to a static variable (error-prone) to
taking a pointer to a caller-provided receiver.

OpenBSD-Commit-ID: eb54d6a72d0bbba4d623e2175cf5cc4c75dc2ba4
---
 scp.c            |  44 +++----
 sftp-client.c    | 324 +++++++++++++++++++++++------------------------
 sftp-client.h    |  76 ++++++-----
 sftp-glob.c      |  24 ++--
 sftp-usergroup.c |   8 +-
 sftp.c           | 124 ++++++++++--------
 6 files changed, 302 insertions(+), 298 deletions(-)

diff --git a/scp.c b/scp.c
index 5edb4f07d..55c018770 100644
--- a/scp.c
+++ b/scp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: scp.c,v 1.257 2023/07/14 05:31:44 djm Exp $ */
+/* $OpenBSD: scp.c,v 1.258 2023/09/08 05:56:13 djm Exp $ */
 /*
  * scp - secure remote copy.  This is basically patched BSD rcp which
  * uses ssh to do the data transfer (instead of using rcmd).
@@ -1028,7 +1028,7 @@ do_sftp_connect(char *host, char *user, int port, char *sftp_direct,
 		    reminp, remoutp, pidp) < 0)
 			return NULL;
 	}
-	return do_init(*reminp, *remoutp,
+	return sftp_init(*reminp, *remoutp,
 	    sftp_copy_buflen, sftp_nrequests, limit_kbps);
 }
 
@@ -1324,8 +1324,8 @@ prepare_remote_path(struct sftp_conn *conn, const char *path)
 			return xstrdup(".");
 		return xstrdup(path + 2 + nslash);
 	}
-	if (can_expand_path(conn))
-		return do_expand_path(conn, path);
+	if (sftp_can_expand_path(conn))
+		return sftp_expand_path(conn, path);
 	/* No protocol extension */
 	error("server expand-path extension is required "
 	    "for ~user paths in SFTP mode");
@@ -1353,17 +1353,17 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
 	 */
 	if ((target = prepare_remote_path(conn, targ)) == NULL)
 		cleanup_exit(255);
-	target_is_dir = remote_is_dir(conn, target);
+	target_is_dir = sftp_remote_is_dir(conn, target);
 	if (targetshouldbedirectory && !target_is_dir) {
 		debug("target directory \"%s\" does not exist", target);
 		a.flags = SSH2_FILEXFER_ATTR_PERMISSIONS;
 		a.perm = st.st_mode | 0700; /* ensure writable */
-		if (do_mkdir(conn, target, &a, 1) != 0)
+		if (sftp_mkdir(conn, target, &a, 1) != 0)
 			cleanup_exit(255); /* error already logged */
 		target_is_dir = 1;
 	}
 	if (target_is_dir)
-		abs_dst = path_append(target, filename);
+		abs_dst = sftp_path_append(target, filename);
 	else {
 		abs_dst = target;
 		target = NULL;
@@ -1371,12 +1371,12 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
 	debug3_f("copying local %s to remote %s", src, abs_dst);
 
 	if (src_is_dir && iamrecursive) {
-		if (upload_dir(conn, src, abs_dst, pflag,
+		if (sftp_upload_dir(conn, src, abs_dst, pflag,
 		    SFTP_PROGRESS_ONLY, 0, 0, 1, 1) != 0) {
 			error("failed to upload directory %s to %s", src, targ);
 			errs = 1;
 		}
-	} else if (do_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) {
+	} else if (sftp_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) {
 		error("failed to upload file %s to %s", src, targ);
 		errs = 1;
 	}
@@ -1585,7 +1585,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
 		 * a GLOB_NOCHECK result. Check whether the unglobbed path
 		 * exists so we can give a nice error message early.
 		 */
-		if (do_stat(conn, g.gl_pathv[0], 1) == NULL) {
+		if (sftp_stat(conn, g.gl_pathv[0], 1, NULL) != 0) {
 			error("%s: %s", src, strerror(ENOENT));
 			err = -1;
 			goto out;
@@ -1621,17 +1621,17 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
 		}
 
 		if (dst_is_dir)
-			abs_dst = path_append(dst, filename);
+			abs_dst = sftp_path_append(dst, filename);
 		else
 			abs_dst = xstrdup(dst);
 
 		debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
-			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
-			    pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1)
+		if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
+			if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst,
+			    NULL, pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1)
 				err = -1;
 		} else {
-			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
+			if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL,
 			    pflag, 0, 0, 1) == -1)
 				err = -1;
 		}
@@ -1993,7 +1993,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
 		cleanup_exit(255);
 	memset(&g, 0, sizeof(g));
 
-	targetisdir = remote_is_dir(to, target);
+	targetisdir = sftp_remote_is_dir(to, target);
 	if (!targetisdir && targetshouldbedirectory) {
 		error("%s: destination is not a directory", targ);
 		err = -1;
@@ -2018,7 +2018,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
 		 * a GLOB_NOCHECK result. Check whether the unglobbed path
 		 * exists so we can give a nice error message early.
 		 */
-		if (do_stat(from, g.gl_pathv[0], 1) == NULL) {
+		if (sftp_stat(from, g.gl_pathv[0], 1, NULL) != 0) {
 			error("%s: %s", src, strerror(ENOENT));
 			err = -1;
 			goto out;
@@ -2034,18 +2034,18 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
 		}
 
 		if (targetisdir)
-			abs_dst = path_append(target, filename);
+			abs_dst = sftp_path_append(target, filename);
 		else
 			abs_dst = xstrdup(target);
 
 		debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
-		if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
-			if (crossload_dir(from, to, g.gl_pathv[i], abs_dst,
+		if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
+			if (sftp_crossload_dir(from, to, g.gl_pathv[i], abs_dst,
 			    NULL, pflag, SFTP_PROGRESS_ONLY, 1) == -1)
 				err = -1;
 		} else {
-			if (do_crossload(from, to, g.gl_pathv[i], abs_dst, NULL,
-			    pflag) == -1)
+			if (sftp_crossload(from, to, g.gl_pathv[i], abs_dst,
+			    NULL, pflag) == -1)
 				err = -1;
 		}
 		free(abs_dst);
diff --git a/sftp-client.c b/sftp-client.c
index d6566a834..8153c3d2d 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.172 2023/09/08 05:50:12 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.173 2023/09/08 05:56:13 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -342,16 +342,17 @@ get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
 	return handle;
 }
 
-/* XXX returning &static is error-prone. Refactor to fill *Attrib argument */
-static Attrib *
-get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
+static int
+get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a)
 {
 	struct sshbuf *msg;
 	u_int id;
 	u_char type;
 	int r;
-	static Attrib a;
+	Attrib attr;
 
+	if (a != NULL)
+		memset(a, '\0', sizeof(*a));
 	if ((msg = sshbuf_new()) == NULL)
 		fatal_f("sshbuf_new failed");
 	get_msg(conn, msg);
@@ -372,21 +373,24 @@ get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
 		else
 			error("stat remote: %s", fx2txt(status));
 		sshbuf_free(msg);
-		return(NULL);
+		return -1;
 	} else if (type != SSH2_FXP_ATTRS) {
 		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
 		    SSH2_FXP_ATTRS, type);
 	}
-	if ((r = decode_attrib(msg, &a)) != 0) {
+	if ((r = decode_attrib(msg, &attr)) != 0) {
 		error_fr(r, "decode_attrib");
 		sshbuf_free(msg);
-		return NULL;
+		return -1;
 	}
+	/* success */
+	if (a != NULL)
+		*a = attr;
 	debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o",
-	    type, id, a.flags, a.perm);
+	    type, id, attr.flags, attr.perm);
 	sshbuf_free(msg);
 
-	return &a;
+	return 0;
 }
 
 static int
@@ -449,7 +453,7 @@ get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
 }
 
 struct sftp_conn *
-do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
+sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
     u_int64_t limit_kbps)
 {
 	u_char type;
@@ -560,7 +564,7 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
 	/* Query the server for its limits */
 	if (ret->exts & SFTP_EXT_LIMITS) {
 		struct sftp_limits limits;
-		if (do_limits(ret, &limits) != 0)
+		if (sftp_get_limits(ret, &limits) != 0)
 			fatal_f("limits failed");
 
 		/* If the caller did not specify, find a good value */
@@ -614,7 +618,7 @@ sftp_proto_version(struct sftp_conn *conn)
 }
 
 int
-do_limits(struct sftp_conn *conn, struct sftp_limits *limits)
+sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits)
 {
 	u_int id, msg_id;
 	u_char type;
@@ -668,7 +672,7 @@ do_limits(struct sftp_conn *conn, struct sftp_limits *limits)
 }
 
 int
-do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
+sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
 {
 	u_int id, status;
 	struct sshbuf *msg;
@@ -696,7 +700,7 @@ do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
 
 
 static int
-do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
+sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
     SFTP_DIRENT ***dir)
 {
 	struct sshbuf *msg;
@@ -821,16 +825,16 @@ do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
 
  out:
 	sshbuf_free(msg);
-	do_close(conn, handle, handle_len);
+	sftp_close(conn, handle, handle_len);
 	free(handle);
 
 	if (status != 0 && dir != NULL) {
 		/* Don't return results on error */
-		free_sftp_dirents(*dir);
+		sftp_free_dirents(*dir);
 		*dir = NULL;
 	} else if (interrupted && dir != NULL && *dir != NULL) {
 		/* Don't return partial matches on interrupt */
-		free_sftp_dirents(*dir);
+		sftp_free_dirents(*dir);
 		*dir = xcalloc(1, sizeof(**dir));
 		**dir = NULL;
 	}
@@ -839,12 +843,12 @@ do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
 }
 
 int
-do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
+sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
 {
-	return(do_lsreaddir(conn, path, 0, dir));
+	return sftp_lsreaddir(conn, path, 0, dir);
 }
 
-void free_sftp_dirents(SFTP_DIRENT **s)
+void sftp_free_dirents(SFTP_DIRENT **s)
 {
 	int i;
 
@@ -859,7 +863,7 @@ void free_sftp_dirents(SFTP_DIRENT **s)
 }
 
 int
-do_rm(struct sftp_conn *conn, const char *path)
+sftp_rm(struct sftp_conn *conn, const char *path)
 {
 	u_int status, id;
 
@@ -874,7 +878,7 @@ do_rm(struct sftp_conn *conn, const char *path)
 }
 
 int
-do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
+sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
 {
 	u_int status, id;
 
@@ -892,7 +896,7 @@ do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
 }
 
 int
-do_rmdir(struct sftp_conn *conn, const char *path)
+sftp_rmdir(struct sftp_conn *conn, const char *path)
 {
 	u_int status, id;
 
@@ -909,8 +913,8 @@ do_rmdir(struct sftp_conn *conn, const char *path)
 	return status == SSH2_FX_OK ? 0 : -1;
 }
 
-Attrib *
-do_stat(struct sftp_conn *conn, const char *path, int quiet)
+int
+sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
 {
 	u_int id;
 
@@ -922,33 +926,31 @@ do_stat(struct sftp_conn *conn, const char *path, int quiet)
 	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
 	    path, strlen(path));
 
-	return(get_decode_stat(conn, id, quiet));
+	return get_decode_stat(conn, id, quiet, a);
 }
 
-Attrib *
-do_lstat(struct sftp_conn *conn, const char *path, int quiet)
+int
+sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
 {
 	u_int id;
 
 	if (conn->version == 0) {
-		if (quiet)
-			debug("Server version does not support lstat operation");
-		else
-			logit("Server version does not support lstat operation");
-		return(do_stat(conn, path, quiet));
+		do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO,
+		    "Server version does not support lstat operation");
+		return sftp_stat(conn, path, quiet, a);
 	}
 
 	id = conn->msg_id++;
 	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
 	    strlen(path));
 
-	return(get_decode_stat(conn, id, quiet));
+	return get_decode_stat(conn, id, quiet, a);
 }
 
 #ifdef notyet
-Attrib *
-do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
-    int quiet)
+int
+sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
+    int quiet, Attrib *a)
 {
 	u_int id;
 
@@ -958,12 +960,12 @@ do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
 	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
 	    handle_len);
 
-	return(get_decode_stat(conn, id, quiet));
+	return get_decode_stat(conn, id, quiet, a);
 }
 #endif
 
 int
-do_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
+sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
 {
 	u_int status, id;
 
@@ -981,7 +983,7 @@ do_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
 }
 
 int
-do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
+sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
     Attrib *a)
 {
 	u_int status, id;
@@ -1001,7 +1003,7 @@ do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
 
 /* Implements both the realpath and expand-path operations */
 static char *
-do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
+sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
 {
 	struct sshbuf *msg;
 	u_int expected_id, count, id;
@@ -1076,31 +1078,31 @@ do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
 }
 
 char *
-do_realpath(struct sftp_conn *conn, const char *path)
+sftp_realpath(struct sftp_conn *conn, const char *path)
 {
-	return do_realpath_expand(conn, path, 0);
+	return sftp_realpath_expand(conn, path, 0);
 }
 
 int
-can_expand_path(struct sftp_conn *conn)
+sftp_can_expand_path(struct sftp_conn *conn)
 {
 	return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
 }
 
 char *
-do_expand_path(struct sftp_conn *conn, const char *path)
+sftp_expand_path(struct sftp_conn *conn, const char *path)
 {
-	if (!can_expand_path(conn)) {
+	if (!sftp_can_expand_path(conn)) {
 		debug3_f("no server support, fallback to realpath");
-		return do_realpath_expand(conn, path, 0);
+		return sftp_realpath_expand(conn, path, 0);
 	}
-	return do_realpath_expand(conn, path, 1);
+	return sftp_realpath_expand(conn, path, 1);
 }
 
 int
-do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
+sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 {
-	Attrib junk, *a;
+	Attrib junk, attr;
 	struct sshbuf *msg;
 	u_char *old_handle, *new_handle;
 	u_int mode, status, id;
@@ -1114,14 +1116,14 @@ do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 	}
 
 	/* Make sure the file exists, and we can copy its perms */
-	if ((a = do_stat(conn, oldpath, 0)) == NULL)
+	if (sftp_stat(conn, oldpath, 0, &attr) != 0)
 		return -1;
 
 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
-	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
-		mode = a->perm & 0777;
+	if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
+		mode = attr.perm & 0777;
 
-		if (!S_ISREG(a->perm)) {
+		if (!S_ISREG(attr.perm)) {
 			error("Cannot copy non-regular file: %s", oldpath);
 			return -1;
 		}
@@ -1131,9 +1133,9 @@ do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 	}
 
 	/* Set up the new perms for the new file */
-	attrib_clear(a);
-	a->perm = mode;
-	a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
+	attrib_clear(&attr);
+	attr.perm = mode;
+	attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
 
 	if ((msg = sshbuf_new()) == NULL)
 		fatal("%s: sshbuf_new failed", __func__);
@@ -1167,7 +1169,7 @@ do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 	    (r = sshbuf_put_cstring(msg, newpath)) != 0 ||
 	    (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
 	    SSH2_FXF_TRUNC)) != 0 ||
-	    (r = encode_attrib(msg, a)) != 0)
+	    (r = encode_attrib(msg, &attr)) != 0)
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
 	send_msg(conn, msg);
 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath);
@@ -1204,8 +1206,8 @@ do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 
 	/* Clean up everything */
 	sshbuf_free(msg);
-	do_close(conn, old_handle, old_handle_len);
-	do_close(conn, new_handle, new_handle_len);
+	sftp_close(conn, old_handle, old_handle_len);
+	sftp_close(conn, new_handle, new_handle_len);
 	free(old_handle);
 	free(new_handle);
 
@@ -1213,7 +1215,7 @@ do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 }
 
 int
-do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
+sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
     int force_legacy)
 {
 	struct sshbuf *msg;
@@ -1258,7 +1260,7 @@ do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
 }
 
 int
-do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
+sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 {
 	struct sshbuf *msg;
 	u_int status, id;
@@ -1296,7 +1298,7 @@ do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 }
 
 int
-do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
+sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 {
 	struct sshbuf *msg;
 	u_int status, id;
@@ -1332,7 +1334,7 @@ do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
 }
 
 int
-do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
+sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
 {
 	struct sshbuf *msg;
 	u_int status, id;
@@ -1365,7 +1367,7 @@ do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
 
 #ifdef notyet
 char *
-do_readlink(struct sftp_conn *conn, const char *path)
+sftp_readlink(struct sftp_conn *conn, const char *path)
 {
 	struct sshbuf *msg;
 	u_int expected_id, count, id;
@@ -1423,7 +1425,7 @@ do_readlink(struct sftp_conn *conn, const char *path)
 #endif
 
 int
-do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
+sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
     int quiet)
 {
 	struct sshbuf *msg;
@@ -1454,7 +1456,7 @@ do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
 
 #ifdef notyet
 int
-do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
+sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
     struct sftp_statvfs *st, int quiet)
 {
 	struct sshbuf *msg;
@@ -1484,7 +1486,7 @@ do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
 #endif
 
 int
-do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
+sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
 {
 	struct sshbuf *msg;
 	u_int status, id;
@@ -1592,7 +1594,7 @@ progress_meter_path(const char *path)
 }
 
 int
-do_download(struct sftp_conn *conn, const char *remote_path,
+sftp_download(struct sftp_conn *conn, const char *remote_path,
     const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
     int fsync_flag, int inplace_flag)
 {
@@ -1608,14 +1610,18 @@ do_download(struct sftp_conn *conn, const char *remote_path,
 	struct requests requests;
 	struct request *req;
 	u_char type;
+	Attrib attr;
 
 	debug2_f("download remote \"%s\" to local \"%s\"",
 	    remote_path, local_path);
 
 	TAILQ_INIT(&requests);
 
-	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
-		return -1;
+	if (a == NULL) {
+		if (sftp_stat(conn, remote_path, 0, &attr) != 0)
+			return -1;
+		a = &attr;
+	}
 
 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
@@ -1661,7 +1667,7 @@ do_download(struct sftp_conn *conn, const char *remote_path,
 			error("Unable to resume download of \"%s\": "
 			    "local file is larger than remote", local_path);
  fail:
-			do_close(conn, handle, handle_len);
+			sftp_close(conn, handle, handle_len);
 			free(handle);
 			if (local_fd != -1)
 				close(local_fd);
@@ -1836,14 +1842,14 @@ do_download(struct sftp_conn *conn, const char *remote_path,
 	if (read_error) {
 		error("read remote \"%s\" : %s", remote_path, fx2txt(status));
 		status = -1;
-		do_close(conn, handle, handle_len);
+		sftp_close(conn, handle, handle_len);
 	} else if (write_error) {
 		error("write local \"%s\": %s", local_path,
 		    strerror(write_errno));
 		status = SSH2_FX_FAILURE;
-		do_close(conn, handle, handle_len);
+		sftp_close(conn, handle, handle_len);
 	} else {
-		if (do_close(conn, handle, handle_len) != 0 || interrupted)
+		if (sftp_close(conn, handle, handle_len) != 0 || interrupted)
 			status = SSH2_FX_FAILURE;
 		else
 			status = SSH2_FX_OK;
@@ -1900,12 +1906,10 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 	debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
 
 	if (dirattrib == NULL) {
-		if ((a = do_stat(conn, src, 1)) == NULL) {
+		if (sftp_stat(conn, src, 1, &ldirattrib) != 0) {
 			error("stat remote \"%s\" directory failed", src);
 			return -1;
 		}
-		/* Don't let this be clobbered by later do_stat calls */
-		ldirattrib = *a;
 		dirattrib = &ldirattrib;
 	}
 	if (!S_ISDIR(dirattrib->perm)) {
@@ -1928,7 +1932,7 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		return -1;
 	}
 
-	if (do_readdir(conn, src, &dir_entries) == -1) {
+	if (sftp_readdir(conn, src, &dir_entries) == -1) {
 		error("remote readdir \"%s\" failed", src);
 		return -1;
 	}
@@ -1938,8 +1942,8 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		free(new_src);
 
 		filename = dir_entries[i]->filename;
-		new_dst = path_append(dst, filename);
-		new_src = path_append(src, filename);
+		new_dst = sftp_path_append(dst, filename);
+		new_src = sftp_path_append(src, filename);
 
 		a = &dir_entries[i]->a;
 		if (S_ISLNK(a->perm)) {
@@ -1949,13 +1953,11 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 				continue;
 			}
 			/* Replace the stat contents with the symlink target */
-			if ((a = do_stat(conn, new_src, 1)) == NULL) {
+			if (sftp_stat(conn, new_src, 1, &lsym) != 0) {
 				logit("remote stat \"%s\" failed", new_src);
 				ret = -1;
 				continue;
 			}
-			/* Don't let this be clobbered by later do_stat calls */
-			lsym = *a;
 			a = &lsym;
 		}
 
@@ -1969,7 +1971,7 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
 				ret = -1;
 		} else if (S_ISREG(a->perm)) {
-			if (do_download(conn, new_src, new_dst, a,
+			if (sftp_download(conn, new_src, new_dst, a,
 			    preserve_flag, resume_flag, fsync_flag,
 			    inplace_flag) == -1) {
 				error("Download of file %s to %s failed",
@@ -2001,20 +2003,20 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		error("local chmod directory \"%s\": %s", dst,
 		    strerror(errno));
 
-	free_sftp_dirents(dir_entries);
+	sftp_free_dirents(dir_entries);
 
 	return ret;
 }
 
 int
-download_dir(struct sftp_conn *conn, const char *src, const char *dst,
+sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
     Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
     int fsync_flag, int follow_link_flag, int inplace_flag)
 {
 	char *src_canon;
 	int ret;
 
-	if ((src_canon = do_realpath(conn, src)) == NULL) {
+	if ((src_canon = sftp_realpath(conn, src)) == NULL) {
 		error("download \"%s\": path canonicalization failed", src);
 		return -1;
 	}
@@ -2027,7 +2029,7 @@ download_dir(struct sftp_conn *conn, const char *src, const char *dst,
 }
 
 int
-do_upload(struct sftp_conn *conn, const char *local_path,
+sftp_upload(struct sftp_conn *conn, const char *local_path,
     const char *remote_path, int preserve_flag, int resume,
     int fsync_flag, int inplace_flag)
 {
@@ -2037,7 +2039,7 @@ do_upload(struct sftp_conn *conn, const char *local_path,
 	u_char type, *handle, *data;
 	struct sshbuf *msg;
 	struct stat sb;
-	Attrib a, t, *c = NULL;
+	Attrib a, t, c;
 	u_int32_t startid, ackid;
 	u_int64_t highwater = 0, maxack = 0;
 	struct request *ack = NULL;
@@ -2073,19 +2075,19 @@ do_upload(struct sftp_conn *conn, const char *local_path,
 
 	if (resume) {
 		/* Get remote file size if it exists */
-		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
+		if (sftp_stat(conn, remote_path, 0, &c) != 0) {
 			close(local_fd);
 			return -1;
 		}
 
-		if ((off_t)c->size >= sb.st_size) {
+		if ((off_t)c.size >= sb.st_size) {
 			error("resume \"%s\": destination file "
 			    "same size or larger", local_path);
 			close(local_fd);
 			return -1;
 		}
 
-		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
+		if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) {
 			close(local_fd);
 			return -1;
 		}
@@ -2109,7 +2111,7 @@ do_upload(struct sftp_conn *conn, const char *local_path,
 	data = xmalloc(conn->upload_buflen);
 
 	/* Read from local and write to remote */
-	offset = progress_counter = (resume ? c->size : 0);
+	offset = progress_counter = (resume ? c.size : 0);
 	if (showprogress) {
 		start_progress_meter(progress_meter_path(local_path),
 		    sb.st_size, &progress_counter);
@@ -2221,7 +2223,7 @@ do_upload(struct sftp_conn *conn, const char *local_path,
 		attrib_clear(&t);
 		t.flags = SSH2_FILEXFER_ATTR_SIZE;
 		t.size = highwater;
-		do_fsetstat(conn, handle, handle_len, &t);
+		sftp_fsetstat(conn, handle, handle_len, &t);
 	}
 
 	if (close(local_fd) == -1) {
@@ -2231,12 +2233,12 @@ do_upload(struct sftp_conn *conn, const char *local_path,
 
 	/* Override umask and utimes if asked */
 	if (preserve_flag)
-		do_fsetstat(conn, handle, handle_len, &a);
+		sftp_fsetstat(conn, handle, handle_len, &a);
 
 	if (fsync_flag)
-		(void)do_fsync(conn, handle, handle_len);
+		(void)sftp_fsync(conn, handle, handle_len);
 
-	if (do_close(conn, handle, handle_len) != 0)
+	if (sftp_close(conn, handle, handle_len) != 0)
 		status = SSH2_FX_FAILURE;
 
 	free(handle);
@@ -2254,7 +2256,7 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 	struct dirent *dp;
 	char *filename, *new_src = NULL, *new_dst = NULL;
 	struct stat sb;
-	Attrib a, *dirattrib;
+	Attrib a, dirattrib;
 	u_int32_t saved_perm;
 
 	debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst);
@@ -2290,10 +2292,10 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 	 */
 	saved_perm = a.perm;
 	a.perm |= (S_IWUSR|S_IXUSR);
-	if (do_mkdir(conn, dst, &a, 0) != 0) {
-		if ((dirattrib = do_stat(conn, dst, 0)) == NULL)
+	if (sftp_mkdir(conn, dst, &a, 0) != 0) {
+		if (sftp_stat(conn, dst, 0, &dirattrib) != 0)
 			return -1;
-		if (!S_ISDIR(dirattrib->perm)) {
+		if (!S_ISDIR(dirattrib.perm)) {
 			error("\"%s\" exists but is not a directory", dst);
 			return -1;
 		}
@@ -2311,8 +2313,8 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		free(new_dst);
 		free(new_src);
 		filename = dp->d_name;
-		new_dst = path_append(dst, filename);
-		new_src = path_append(src, filename);
+		new_dst = sftp_path_append(dst, filename);
+		new_src = sftp_path_append(src, filename);
 
 		if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
 			continue;
@@ -2341,7 +2343,7 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
 				ret = -1;
 		} else if (S_ISREG(sb.st_mode)) {
-			if (do_upload(conn, new_src, new_dst,
+			if (sftp_upload(conn, new_src, new_dst,
 			    preserve_flag, resume, fsync_flag,
 			    inplace_flag) == -1) {
 				error("upload \"%s\" to \"%s\" failed",
@@ -2354,21 +2356,21 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 	free(new_dst);
 	free(new_src);
 
-	do_setstat(conn, dst, &a);
+	sftp_setstat(conn, dst, &a);
 
 	(void) closedir(dirp);
 	return ret;
 }
 
 int
-upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
+sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
     int preserve_flag, int print_flag, int resume, int fsync_flag,
     int follow_link_flag, int inplace_flag)
 {
 	char *dst_canon;
 	int ret;
 
-	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
+	if ((dst_canon = sftp_realpath(conn, dst)) == NULL) {
 		error("upload \"%s\": path canonicalization failed", dst);
 		return -1;
 	}
@@ -2427,13 +2429,13 @@ handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
 				*write_errorp = status;
 		}
 		/*
-		 * XXX this doesn't do full reply matching like do_upload and
+		 * XXX this doesn't do full reply matching like sftp_upload and
 		 * so cannot gracefully truncate terminated uploads at a
 		 * high-water mark. ATM the only caller of this function (scp)
 		 * doesn't support transfer resumption, so this doesn't matter
 		 * a whole lot.
 		 *
-		 * To be safe, do_crossload truncates the destination file to
+		 * To be safe, sftp_crossload truncates the destination file to
 		 * zero length on upload failure, since we can't trust the
 		 * server not to have reordered replies that could have
 		 * inserted holes where none existed in the source file.
@@ -2448,7 +2450,7 @@ handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
 }
 
 int
-do_crossload(struct sftp_conn *from, struct sftp_conn *to,
+sftp_crossload(struct sftp_conn *from, struct sftp_conn *to,
     const char *from_path, const char *to_path,
     Attrib *a, int preserve_flag)
 {
@@ -2463,13 +2465,17 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
 	struct requests requests;
 	struct request *req;
 	u_char type;
+	Attrib attr;
 
 	debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path);
 
 	TAILQ_INIT(&requests);
 
-	if (a == NULL && (a = do_stat(from, from_path, 0)) == NULL)
-		return -1;
+	if (a == NULL) {
+		if (sftp_stat(from, from_path, 0, &attr) != 0)
+			return -1;
+		a = &attr;
+	}
 
 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
 	    (!S_ISREG(a->perm))) {
@@ -2499,7 +2505,7 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
 	if (send_open(to, to_path, "dest",
 	    SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
 	    &to_handle, &to_handle_len) != 0) {
-		do_close(from, from_handle, from_handle_len);
+		sftp_close(from, from_handle, from_handle_len);
 		return -1;
 	}
 
@@ -2649,7 +2655,7 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
 	/* Truncate at 0 length on interrupt or error to avoid holes at dest */
 	if (read_error || write_error || interrupted) {
 		debug("truncating \"%s\" at 0", to_path);
-		do_close(to, to_handle, to_handle_len);
+		sftp_close(to, to_handle, to_handle_len);
 		free(to_handle);
 		if (send_open(to, to_path, "dest",
 		    SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
@@ -2661,17 +2667,17 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
 	if (read_error) {
 		error("read origin \"%s\": %s", from_path, fx2txt(status));
 		status = -1;
-		do_close(from, from_handle, from_handle_len);
+		sftp_close(from, from_handle, from_handle_len);
 		if (to_handle != NULL)
-			do_close(to, to_handle, to_handle_len);
+			sftp_close(to, to_handle, to_handle_len);
 	} else if (write_error) {
 		error("write dest \"%s\": %s", to_path, fx2txt(write_error));
 		status = SSH2_FX_FAILURE;
-		do_close(from, from_handle, from_handle_len);
+		sftp_close(from, from_handle, from_handle_len);
 		if (to_handle != NULL)
-			do_close(to, to_handle, to_handle_len);
+			sftp_close(to, to_handle, to_handle_len);
 	} else {
-		if (do_close(from, from_handle, from_handle_len) != 0 ||
+		if (sftp_close(from, from_handle, from_handle_len) != 0 ||
 		    interrupted)
 			status = -1;
 		else
@@ -2679,8 +2685,8 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
 		if (to_handle != NULL) {
 			/* Need to resend utimes after write */
 			if (preserve_flag)
-				do_fsetstat(to, to_handle, to_handle_len, a);
-			do_close(to, to_handle, to_handle_len);
+				sftp_fsetstat(to, to_handle, to_handle_len, a);
+			sftp_close(to, to_handle, to_handle_len);
 		}
 	}
 	sshbuf_free(msg);
@@ -2700,7 +2706,7 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 	SFTP_DIRENT **dir_entries;
 	char *filename, *new_from_path = NULL, *new_to_path = NULL;
 	mode_t mode = 0777;
-	Attrib curdir;
+	Attrib curdir, ldirattrib, newdir;
 
 	debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
 
@@ -2709,10 +2715,12 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 		return -1;
 	}
 
-	if (dirattrib == NULL &&
-	    (dirattrib = do_stat(from, from_path, 1)) == NULL) {
-		error("stat remote \"%s\" failed", from_path);
-		return -1;
+	if (dirattrib == NULL) {
+		if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) {
+			error("stat remote \"%s\" failed", from_path);
+			return -1;
+		}
+		dirattrib = &ldirattrib;
 	}
 	if (!S_ISDIR(dirattrib->perm)) {
 		error("\"%s\" is not a directory", from_path);
@@ -2740,17 +2748,17 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 	 * the path already existed and is a directory.  Ensure we can
 	 * write to the directory we create for the duration of the transfer.
 	 */
-	if (do_mkdir(to, to_path, &curdir, 0) != 0) {
-		if ((dirattrib = do_stat(to, to_path, 0)) == NULL)
+	if (sftp_mkdir(to, to_path, &curdir, 0) != 0) {
+		if (sftp_stat(to, to_path, 0, &newdir) != 0)
 			return -1;
-		if (!S_ISDIR(dirattrib->perm)) {
+		if (!S_ISDIR(newdir.perm)) {
 			error("\"%s\" exists but is not a directory", to_path);
 			return -1;
 		}
 	}
 	curdir.perm = mode;
 
-	if (do_readdir(from, from_path, &dir_entries) == -1) {
+	if (sftp_readdir(from, from_path, &dir_entries) == -1) {
 		error("origin readdir \"%s\" failed", from_path);
 		return -1;
 	}
@@ -2760,8 +2768,8 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 		free(new_to_path);
 
 		filename = dir_entries[i]->filename;
-		new_from_path = path_append(from_path, filename);
-		new_to_path = path_append(to_path, filename);
+		new_from_path = sftp_path_append(from_path, filename);
+		new_to_path = sftp_path_append(to_path, filename);
 
 		if (S_ISDIR(dir_entries[i]->a.perm)) {
 			if (strcmp(filename, ".") == 0 ||
@@ -2776,10 +2784,10 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 		    (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
 			/*
 			 * If this is a symlink then don't send the link's
-			 * Attrib. do_download() will do a FXP_STAT operation
+			 * Attrib. sftp_download() will do a FXP_STAT operation
 			 * and get the link target's attributes.
 			 */
-			if (do_crossload(from, to, new_from_path, new_to_path,
+			if (sftp_crossload(from, to, new_from_path, new_to_path,
 			    S_ISLNK(dir_entries[i]->a.perm) ? NULL :
 			    &(dir_entries[i]->a), preserve_flag) == -1) {
 				error("crossload \"%s\" to \"%s\" failed",
@@ -2794,22 +2802,22 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 	free(new_to_path);
 	free(new_from_path);
 
-	do_setstat(to, to_path, &curdir);
+	sftp_setstat(to, to_path, &curdir);
 
-	free_sftp_dirents(dir_entries);
+	sftp_free_dirents(dir_entries);
 
 	return ret;
 }
 
 int
-crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
+sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
     const char *from_path, const char *to_path,
     Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag)
 {
 	char *from_path_canon;
 	int ret;
 
-	if ((from_path_canon = do_realpath(from, from_path)) == NULL) {
+	if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) {
 		error("crossload \"%s\": path canonicalization failed",
 		    from_path);
 		return -1;
@@ -2822,13 +2830,13 @@ crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
 }
 
 int
-can_get_users_groups_by_id(struct sftp_conn *conn)
+sftp_can_get_users_groups_by_id(struct sftp_conn *conn)
 {
 	return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0;
 }
 
 int
-do_get_users_groups_by_id(struct sftp_conn *conn,
+sftp_get_users_groups_by_id(struct sftp_conn *conn,
     const u_int *uids, u_int nuids,
     const u_int *gids, u_int ngids,
     char ***usernamesp, char ***groupnamesp)
@@ -2840,7 +2848,7 @@ do_get_users_groups_by_id(struct sftp_conn *conn,
 	int r;
 
 	*usernamesp = *groupnamesp = NULL;
-	if (!can_get_users_groups_by_id(conn))
+	if (!sftp_can_get_users_groups_by_id(conn))
 		return SSH_ERR_FEATURE_UNSUPPORTED;
 
 	if ((msg = sshbuf_new()) == NULL ||
@@ -2936,7 +2944,7 @@ do_get_users_groups_by_id(struct sftp_conn *conn,
 }
 
 char *
-path_append(const char *p1, const char *p2)
+sftp_path_append(const char *p1, const char *p2)
 {
 	char *ret;
 	size_t len = strlen(p1) + strlen(p2) + 2;
@@ -2955,13 +2963,13 @@ path_append(const char *p1, const char *p2)
  * freed and a replacement allocated.  Caller must free returned string.
  */
 char *
-make_absolute(char *p, const char *pwd)
+sftp_make_absolute(char *p, const char *pwd)
 {
 	char *abs_str;
 
 	/* Derelativise */
 	if (p && !path_absolute(p)) {
-		abs_str = path_append(pwd, p);
+		abs_str = sftp_path_append(pwd, p);
 		free(p);
 		return(abs_str);
 	} else
@@ -2969,34 +2977,22 @@ make_absolute(char *p, const char *pwd)
 }
 
 int
-remote_is_dir(struct sftp_conn *conn, const char *path)
+sftp_remote_is_dir(struct sftp_conn *conn, const char *path)
 {
-	Attrib *a;
+	Attrib a;
 
 	/* XXX: report errors? */
-	if ((a = do_stat(conn, path, 1)) == NULL)
+	if (sftp_stat(conn, path, 1, &a) != 0)
 		return(0);
-	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
+	if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
 		return(0);
-	return(S_ISDIR(a->perm));
+	return S_ISDIR(a.perm);
 }
 
 
-int
-local_is_dir(const char *path)
-{
-	struct stat sb;
-
-	/* XXX: report errors? */
-	if (stat(path, &sb) == -1)
-		return(0);
-
-	return(S_ISDIR(sb.st_mode));
-}
-
 /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
 int
-globpath_is_dir(const char *pathname)
+sftp_globpath_is_dir(const char *pathname)
 {
 	size_t l = strlen(pathname);
 
diff --git a/sftp-client.h b/sftp-client.h
index d7deab17e..74cdae7dc 100644
--- a/sftp-client.h
+++ b/sftp-client.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.h,v 1.38 2022/09/19 10:43:12 djm Exp $ */
+/* $OpenBSD: sftp-client.h,v 1.39 2023/09/08 05:56:13 djm Exp $ */
 
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@@ -70,107 +70,106 @@ struct sftp_limits {
  * Initialise a SSH filexfer connection. Returns NULL on error or
  * a pointer to a initialized sftp_conn struct on success.
  */
-struct sftp_conn *do_init(int, int, u_int, u_int, u_int64_t);
+struct sftp_conn *sftp_init(int, int, u_int, u_int, u_int64_t);
 
 u_int sftp_proto_version(struct sftp_conn *);
 
 /* Query server limits */
-int do_limits(struct sftp_conn *, struct sftp_limits *);
+int sftp_get_limits(struct sftp_conn *, struct sftp_limits *);
 
 /* Close file referred to by 'handle' */
-int do_close(struct sftp_conn *, const u_char *, u_int);
+int sftp_close(struct sftp_conn *, const u_char *, u_int);
 
 /* Read contents of 'path' to NULL-terminated array 'dir' */
-int do_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***);
+int sftp_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***);
 
-/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from do_readdir) */
-void free_sftp_dirents(SFTP_DIRENT **);
+/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from sftp_readdir) */
+void sftp_free_dirents(SFTP_DIRENT **);
 
 /* Delete file 'path' */
-int do_rm(struct sftp_conn *, const char *);
+int sftp_rm(struct sftp_conn *, const char *);
 
 /* Create directory 'path' */
-int do_mkdir(struct sftp_conn *, const char *, Attrib *, int);
+int sftp_mkdir(struct sftp_conn *, const char *, Attrib *, int);
 
 /* Remove directory 'path' */
-int do_rmdir(struct sftp_conn *, const char *);
+int sftp_rmdir(struct sftp_conn *, const char *);
 
 /* Get file attributes of 'path' (follows symlinks) */
-Attrib *do_stat(struct sftp_conn *, const char *, int);
+int sftp_stat(struct sftp_conn *, const char *, int, Attrib *);
 
 /* Get file attributes of 'path' (does not follow symlinks) */
-Attrib *do_lstat(struct sftp_conn *, const char *, int);
+int sftp_lstat(struct sftp_conn *, const char *, int, Attrib *);
 
 /* Set file attributes of 'path' */
-int do_setstat(struct sftp_conn *, const char *, Attrib *);
+int sftp_setstat(struct sftp_conn *, const char *, Attrib *);
 
 /* Set file attributes of open file 'handle' */
-int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *);
+int sftp_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *);
 
 /* Set file attributes of 'path', not following symlinks */
-int do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a);
+int sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a);
 
 /* Canonicalise 'path' - caller must free result */
-char *do_realpath(struct sftp_conn *, const char *);
+char *sftp_realpath(struct sftp_conn *, const char *);
 
 /* Canonicalisation with tilde expansion (requires server extension) */
-char *do_expand_path(struct sftp_conn *, const char *);
+char *sftp_expand_path(struct sftp_conn *, const char *);
 
 /* Returns non-zero if server can tilde-expand paths */
-int can_expand_path(struct sftp_conn *);
+int sftp_can_expand_path(struct sftp_conn *);
 
 /* Get statistics for filesystem hosting file at "path" */
-int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
+int sftp_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
 
 /* Rename 'oldpath' to 'newpath' */
-int do_rename(struct sftp_conn *, const char *, const char *, int);
+int sftp_rename(struct sftp_conn *, const char *, const char *, int);
 
 /* Copy 'oldpath' to 'newpath' */
-int do_copy(struct sftp_conn *, const char *, const char *);
+int sftp_copy(struct sftp_conn *, const char *, const char *);
 
 /* Link 'oldpath' to 'newpath' */
-int do_hardlink(struct sftp_conn *, const char *, const char *);
+int sftp_hardlink(struct sftp_conn *, const char *, const char *);
 
 /* Rename 'oldpath' to 'newpath' */
-int do_symlink(struct sftp_conn *, const char *, const char *);
+int sftp_symlink(struct sftp_conn *, const char *, const char *);
 
 /* Call fsync() on open file 'handle' */
-int do_fsync(struct sftp_conn *conn, u_char *, u_int);
+int sftp_fsync(struct sftp_conn *conn, u_char *, u_int);
 
 /*
  * Download 'remote_path' to 'local_path'. Preserve permissions and times
  * if 'pflag' is set
  */
-int do_download(struct sftp_conn *, const char *, const char *, Attrib *,
+int sftp_download(struct sftp_conn *, const char *, const char *, Attrib *,
     int, int, int, int);
 
 /*
  * Recursively download 'remote_directory' to 'local_directory'. Preserve
  * times if 'pflag' is set
  */
-int download_dir(struct sftp_conn *, const char *, const char *, Attrib *,
+int sftp_download_dir(struct sftp_conn *, const char *, const char *, Attrib *,
     int, int, int, int, int, int);
 
 /*
  * Upload 'local_path' to 'remote_path'. Preserve permissions and times
  * if 'pflag' is set
  */
-int do_upload(struct sftp_conn *, const char *, const char *,
+int sftp_upload(struct sftp_conn *, const char *, const char *,
     int, int, int, int);
 
 /*
  * Recursively upload 'local_directory' to 'remote_directory'. Preserve
  * times if 'pflag' is set
  */
-int upload_dir(struct sftp_conn *, const char *, const char *,
+int sftp_upload_dir(struct sftp_conn *, const char *, const char *,
     int, int, int, int, int, int);
 
 /*
  * Download a 'from_path' from the 'from' connection and upload it to
  * to 'to' connection at 'to_path'.
  */
-int
-do_crossload(struct sftp_conn *from, struct sftp_conn *to,
+int sftp_crossload(struct sftp_conn *from, struct sftp_conn *to,
     const char *from_path, const char *to_path,
     Attrib *a, int preserve_flag);
 
@@ -178,7 +177,7 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
  * Recursively download a directory from 'from_path' from the 'from'
  * connection and upload it to 'to' connection at 'to_path'.
  */
-int crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
+int sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
     const char *from_path, const char *to_path,
     Attrib *dirattrib, int preserve_flag, int print_flag,
     int follow_link_flag);
@@ -186,26 +185,23 @@ int crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
 /*
  * User/group ID to name translation.
  */
-int can_get_users_groups_by_id(struct sftp_conn *conn);
-int do_get_users_groups_by_id(struct sftp_conn *conn,
+int sftp_can_get_users_groups_by_id(struct sftp_conn *conn);
+int sftp_get_users_groups_by_id(struct sftp_conn *conn,
     const u_int *uids, u_int nuids,
     const u_int *gids, u_int ngids,
     char ***usernamesp, char ***groupnamesp);
 
 /* Concatenate paths, taking care of slashes. Caller must free result. */
-char *path_append(const char *, const char *);
+char *sftp_path_append(const char *, const char *);
 
 /* Make absolute path if relative path and CWD is given. Does not modify
  * original if the path is already absolute. */
-char *make_absolute(char *, const char *);
+char *sftp_make_absolute(char *, const char *);
 
 /* Check if remote path is directory */
-int remote_is_dir(struct sftp_conn *conn, const char *path);
-
-/* Check if local path is directory */
-int local_is_dir(const char *path);
+int sftp_remote_is_dir(struct sftp_conn *conn, const char *path);
 
 /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
-int globpath_is_dir(const char *pathname);
+int sftp_globpath_is_dir(const char *pathname);
 
 #endif
diff --git a/sftp-glob.c b/sftp-glob.c
index afeb15f9e..616168581 100644
--- a/sftp-glob.c
+++ b/sftp-glob.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-glob.c,v 1.31 2022/10/24 21:51:55 djm Exp $ */
+/* $OpenBSD: sftp-glob.c,v 1.32 2023/09/08 05:56:13 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -51,7 +51,7 @@ fudge_opendir(const char *path)
 
 	r = xcalloc(1, sizeof(*r));
 
-	if (do_readdir(cur.conn, path, &r->dir)) {
+	if (sftp_readdir(cur.conn, path, &r->dir)) {
 		free(r);
 		return(NULL);
 	}
@@ -103,32 +103,32 @@ fudge_readdir(struct SFTP_OPENDIR *od)
 static void
 fudge_closedir(struct SFTP_OPENDIR *od)
 {
-	free_sftp_dirents(od->dir);
+	sftp_free_dirents(od->dir);
 	free(od);
 }
 
 static int
 fudge_lstat(const char *path, struct stat *st)
 {
-	Attrib *a;
+	Attrib a;
 
-	if (!(a = do_lstat(cur.conn, path, 1)))
-		return(-1);
+	if (sftp_lstat(cur.conn, path, 1, &a) != 0)
+		return -1;
 
-	attrib_to_stat(a, st);
+	attrib_to_stat(&a, st);
 
-	return(0);
+	return 0;
 }
 
 static int
 fudge_stat(const char *path, struct stat *st)
 {
-	Attrib *a;
+	Attrib a;
 
-	if (!(a = do_stat(cur.conn, path, 1)))
-		return(-1);
+	if (sftp_stat(cur.conn, path, 1, &a) != 0)
+		return -1;
 
-	attrib_to_stat(a, st);
+	attrib_to_stat(&a, st);
 
 	return(0);
 }
diff --git a/sftp-usergroup.c b/sftp-usergroup.c
index 083930a4a..93396ffc6 100644
--- a/sftp-usergroup.c
+++ b/sftp-usergroup.c
@@ -106,9 +106,9 @@ lookup_and_record(struct sftp_conn *conn,
 	u_int i;
 	char **usernames = NULL, **groupnames = NULL;
 
-	if ((r = do_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
+	if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
 	    &usernames, &groupnames)) != 0) {
-		debug_fr(r, "do_get_users_groups_by_id");
+		debug_fr(r, "sftp_get_users_groups_by_id");
 		return;
 	}
 	for (i = 0; i < nuids; i++) {
@@ -176,7 +176,7 @@ get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g)
 {
 	u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
 
-	if (!can_get_users_groups_by_id(conn))
+	if (!sftp_can_get_users_groups_by_id(conn))
 		return;
 
 	collect_ids_from_glob(g, 1, &uids, &nuids);
@@ -215,7 +215,7 @@ get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d)
 {
 	u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
 
-	if (!can_get_users_groups_by_id(conn))
+	if (!sftp_can_get_users_groups_by_id(conn))
 		return;
 
 	collect_ids_from_dirents(d, 1, &uids, &nuids);
diff --git a/sftp.c b/sftp.c
index b113f9309..843801c59 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.234 2023/04/12 08:53:54 jsg Exp $ */
+/* $OpenBSD: sftp.c,v 1.235 2023/09/08 05:56:13 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -628,11 +628,21 @@ make_absolute_pwd_glob(char *p, const char *pwd)
 	escpwd = escape_glob(pwd);
 	if (p == NULL)
 		return escpwd;
-	ret = make_absolute(p, escpwd);
+	ret = sftp_make_absolute(p, escpwd);
 	free(escpwd);
 	return ret;
 }
 
+static int
+local_is_dir(const char *path)
+{
+	struct stat sb;
+
+	if (stat(path, &sb) == -1)
+		return 0;
+	return S_ISDIR(sb.st_mode);
+}
+
 static int
 process_get(struct sftp_conn *conn, const char *src, const char *dst,
     const char *pwd, int pflag, int rflag, int resume, int fflag)
@@ -677,12 +687,12 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
 
 		if (g.gl_matchc == 1 && dst) {
 			if (local_is_dir(dst)) {
-				abs_dst = path_append(dst, filename);
+				abs_dst = sftp_path_append(dst, filename);
 			} else {
 				abs_dst = xstrdup(dst);
 			}
 		} else if (dst) {
-			abs_dst = path_append(dst, filename);
+			abs_dst = sftp_path_append(dst, filename);
 		} else {
 			abs_dst = xstrdup(filename);
 		}
@@ -696,13 +706,14 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
 			mprintf("Fetching %s to %s\n",
 			    g.gl_pathv[i], abs_dst);
 		/* XXX follow link flag */
-		if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
-			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
-			    pflag || global_pflag, 1, resume,
+		if (sftp_globpath_is_dir(g.gl_pathv[i]) &&
+		    (rflag || global_rflag)) {
+			if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst,
+			    NULL, pflag || global_pflag, 1, resume,
 			    fflag || global_fflag, 0, 0) == -1)
 				err = -1;
 		} else {
-			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
+			if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL,
 			    pflag || global_pflag, resume,
 			    fflag || global_fflag, 0) == -1)
 				err = -1;
@@ -731,7 +742,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
 
 	if (dst) {
 		tmp_dst = xstrdup(dst);
-		tmp_dst = make_absolute(tmp_dst, pwd);
+		tmp_dst = sftp_make_absolute(tmp_dst, pwd);
 	}
 
 	memset(&g, 0, sizeof(g));
@@ -744,7 +755,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
 
 	/* If we aren't fetching to pwd then stash this status for later */
 	if (tmp_dst != NULL)
-		dst_is_dir = remote_is_dir(conn, tmp_dst);
+		dst_is_dir = sftp_remote_is_dir(conn, tmp_dst);
 
 	/* If multiple matches, dst may be directory or unspecified */
 	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
@@ -774,13 +785,13 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
 		if (g.gl_matchc == 1 && tmp_dst) {
 			/* If directory specified, append filename */
 			if (dst_is_dir)
-				abs_dst = path_append(tmp_dst, filename);
+				abs_dst = sftp_path_append(tmp_dst, filename);
 			else
 				abs_dst = xstrdup(tmp_dst);
 		} else if (tmp_dst) {
-			abs_dst = path_append(tmp_dst, filename);
+			abs_dst = sftp_path_append(tmp_dst, filename);
 		} else {
-			abs_dst = make_absolute(xstrdup(filename), pwd);
+			abs_dst = sftp_make_absolute(xstrdup(filename), pwd);
 		}
 		free(tmp);
 
@@ -792,13 +803,14 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
 			mprintf("Uploading %s to %s\n",
 			    g.gl_pathv[i], abs_dst);
 		/* XXX follow_link_flag */
-		if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
-			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
+		if (sftp_globpath_is_dir(g.gl_pathv[i]) &&
+		    (rflag || global_rflag)) {
+			if (sftp_upload_dir(conn, g.gl_pathv[i], abs_dst,
 			    pflag || global_pflag, 1, resume,
 			    fflag || global_fflag, 0, 0) == -1)
 				err = -1;
 		} else {
-			if (do_upload(conn, g.gl_pathv[i], abs_dst,
+			if (sftp_upload(conn, g.gl_pathv[i], abs_dst,
 			    pflag || global_pflag, resume,
 			    fflag || global_fflag, 0) == -1)
 				err = -1;
@@ -839,7 +851,7 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
 	u_int c = 1, colspace = 0, columns = 1;
 	SFTP_DIRENT **d;
 
-	if ((n = do_readdir(conn, path, &d)) != 0)
+	if ((n = sftp_readdir(conn, path, &d)) != 0)
 		return (n);
 
 	if (!(lflag & LS_SHORT_VIEW)) {
@@ -881,13 +893,13 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
 		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
 			continue;
 
-		tmp = path_append(path, d[n]->filename);
+		tmp = sftp_path_append(path, d[n]->filename);
 		fname = path_strip(tmp, strip_path);
 		free(tmp);
 
 		if (lflag & LS_LONG_VIEW) {
 			if ((lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) != 0 ||
-			    can_get_users_groups_by_id(conn)) {
+			    sftp_can_get_users_groups_by_id(conn)) {
 				char *lname;
 				struct stat sb;
 
@@ -916,7 +928,7 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
 		printf("\n");
 
-	free_sftp_dirents(d);
+	sftp_free_dirents(d);
 	return (0);
 }
 
@@ -1070,7 +1082,7 @@ do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
 	char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
 	char s_icapacity[16], s_dcapacity[16];
 
-	if (do_statvfs(conn, path, &st, 1) == -1)
+	if (sftp_statvfs(conn, path, &st, 1) == -1)
 		return -1;
 	if (st.f_files == 0)
 		strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
@@ -1544,7 +1556,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 	int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
 	int cmdnum, i;
 	unsigned long n_arg = 0;
-	Attrib a, *aa;
+	Attrib a, aa;
 	char path_buf[PATH_MAX];
 	int err = 0;
 	glob_t g;
@@ -1585,23 +1597,24 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		    rflag, aflag, fflag);
 		break;
 	case I_COPY:
-		path1 = make_absolute(path1, *pwd);
-		path2 = make_absolute(path2, *pwd);
-		err = do_copy(conn, path1, path2);
+		path1 = sftp_make_absolute(path1, *pwd);
+		path2 = sftp_make_absolute(path2, *pwd);
+		err = sftp_copy(conn, path1, path2);
 		break;
 	case I_RENAME:
-		path1 = make_absolute(path1, *pwd);
-		path2 = make_absolute(path2, *pwd);
-		err = do_rename(conn, path1, path2, lflag);
+		path1 = sftp_make_absolute(path1, *pwd);
+		path2 = sftp_make_absolute(path2, *pwd);
+		err = sftp_rename(conn, path1, path2, lflag);
 		break;
 	case I_SYMLINK:
 		sflag = 1;
 		/* FALLTHROUGH */
 	case I_LINK:
 		if (!sflag)
-			path1 = make_absolute(path1, *pwd);
-		path2 = make_absolute(path2, *pwd);
-		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
+			path1 = sftp_make_absolute(path1, *pwd);
+		path2 = sftp_make_absolute(path2, *pwd);
+		err = (sflag ? sftp_symlink : sftp_hardlink)(conn,
+		    path1, path2);
 		break;
 	case I_RM:
 		path1 = make_absolute_pwd_glob(path1, *pwd);
@@ -1609,42 +1622,42 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			if (!quiet)
 				mprintf("Removing %s\n", g.gl_pathv[i]);
-			err = do_rm(conn, g.gl_pathv[i]);
+			err = sftp_rm(conn, g.gl_pathv[i]);
 			if (err != 0 && err_abort)
 				break;
 		}
 		break;
 	case I_MKDIR:
-		path1 = make_absolute(path1, *pwd);
+		path1 = sftp_make_absolute(path1, *pwd);
 		attrib_clear(&a);
 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
 		a.perm = 0777;
-		err = do_mkdir(conn, path1, &a, 1);
+		err = sftp_mkdir(conn, path1, &a, 1);
 		break;
 	case I_RMDIR:
-		path1 = make_absolute(path1, *pwd);
-		err = do_rmdir(conn, path1);
+		path1 = sftp_make_absolute(path1, *pwd);
+		err = sftp_rmdir(conn, path1);
 		break;
 	case I_CHDIR:
 		if (path1 == NULL || *path1 == '\0')
 			path1 = xstrdup(startdir);
-		path1 = make_absolute(path1, *pwd);
-		if ((tmp = do_realpath(conn, path1)) == NULL) {
+		path1 = sftp_make_absolute(path1, *pwd);
+		if ((tmp = sftp_realpath(conn, path1)) == NULL) {
 			err = 1;
 			break;
 		}
-		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
+		if (sftp_stat(conn, tmp, 0, &aa) != 0) {
 			free(tmp);
 			err = 1;
 			break;
 		}
-		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
+		if (!(aa.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
 			error("Can't change directory: Can't check target");
 			free(tmp);
 			err = 1;
 			break;
 		}
-		if (!S_ISDIR(aa->perm)) {
+		if (!S_ISDIR(aa.perm)) {
 			error("Can't change directory: \"%s\" is not "
 			    "a directory", tmp);
 			free(tmp);
@@ -1672,7 +1685,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		/* Default to current directory if no path specified */
 		if (path1 == NULL)
 			path1 = xstrdup(*pwd);
-		path1 = make_absolute(path1, *pwd);
+		path1 = sftp_make_absolute(path1, *pwd);
 		err = do_df(conn, path1, hflag, iflag);
 		break;
 	case I_LCHDIR:
@@ -1714,7 +1727,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 			if (!quiet)
 				mprintf("Changing mode on %s\n",
 				    g.gl_pathv[i]);
-			err = (hflag ? do_lsetstat : do_setstat)(conn,
+			err = (hflag ? sftp_lsetstat : sftp_setstat)(conn,
 			    g.gl_pathv[i], &a);
 			if (err != 0 && err_abort)
 				break;
@@ -1725,15 +1738,15 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		path1 = make_absolute_pwd_glob(path1, *pwd);
 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-			if (!(aa = (hflag ? do_lstat : do_stat)(conn,
-			    g.gl_pathv[i], 0))) {
+			if ((hflag ? sftp_lstat : sftp_stat)(conn,
+			    g.gl_pathv[i], 0, &aa) != 0) {
 				if (err_abort) {
 					err = -1;
 					break;
 				} else
 					continue;
 			}
-			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
+			if (!(aa.flags & SSH2_FILEXFER_ATTR_UIDGID)) {
 				error("Can't get current ownership of "
 				    "remote file \"%s\"", g.gl_pathv[i]);
 				if (err_abort) {
@@ -1742,20 +1755,20 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 				} else
 					continue;
 			}
-			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
+			aa.flags &= SSH2_FILEXFER_ATTR_UIDGID;
 			if (cmdnum == I_CHOWN) {
 				if (!quiet)
 					mprintf("Changing owner on %s\n",
 					    g.gl_pathv[i]);
-				aa->uid = n_arg;
+				aa.uid = n_arg;
 			} else {
 				if (!quiet)
 					mprintf("Changing group on %s\n",
 					    g.gl_pathv[i]);
-				aa->gid = n_arg;
+				aa.gid = n_arg;
 			}
-			err = (hflag ? do_lsetstat : do_setstat)(conn,
-			    g.gl_pathv[i], aa);
+			err = (hflag ? sftp_lsetstat : sftp_setstat)(conn,
+			    g.gl_pathv[i], &aa);
 			if (err != 0 && err_abort)
 				break;
 		}
@@ -2234,16 +2247,15 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
 	}
 #endif /* USE_LIBEDIT */
 
-	remote_path = do_realpath(conn, ".");
-	if (remote_path == NULL)
+	if ((remote_path = sftp_realpath(conn, ".")) == NULL)
 		fatal("Need cwd");
 	startdir = xstrdup(remote_path);
 
 	if (file1 != NULL) {
 		dir = xstrdup(file1);
-		dir = make_absolute(dir, remote_path);
+		dir = sftp_make_absolute(dir, remote_path);
 
-		if (remote_is_dir(conn, dir) && file2 == NULL) {
+		if (sftp_remote_is_dir(conn, dir) && file2 == NULL) {
 			if (!quiet)
 				mprintf("Changing to: %s\n", dir);
 			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
@@ -2652,7 +2664,7 @@ main(int argc, char **argv)
 	}
 	freeargs(&args);
 
-	conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
+	conn = sftp_init(in, out, copy_buffer_len, num_requests, limit_kbps);
 	if (conn == NULL)
 		fatal("Couldn't initialise connection to server");
 

From 7c0ce2bf98b303b6ad91493ee3247d96c18ba1f6 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 05:50:57 +0000
Subject: [PATCH 42/62] upstream: regress test for recursive copies of
 directories containing

symlinks to other directories. bz3611, ok dtucker@

OpenBSD-Regress-ID: eaa4c29cc5cddff4e72a16bcce14aeb1ecfc94b9
---
 regress/scp.sh | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/regress/scp.sh b/regress/scp.sh
index 76c2b2a6b..640cf434f 100644
--- a/regress/scp.sh
+++ b/regress/scp.sh
@@ -1,4 +1,4 @@
-#	$OpenBSD: scp.sh,v 1.18 2023/01/13 04:47:34 dtucker Exp $
+#	$OpenBSD: scp.sh,v 1.19 2023/09/08 05:50:57 djm Exp $
 #	Placed in the Public Domain.
 
 tid="scp"
@@ -30,13 +30,25 @@ scpclean() {
 	chmod 755 ${DIR} ${DIR2} ${DIR3}
 }
 
+# Create directory structure for recursive copy tests.
+forest() {
+	scpclean
+	rm -rf ${DIR2}
+	cp ${DATA} ${DIR}/copy
+	ln -s ${DIR}/copy ${DIR}/copy-sym
+	mkdir ${DIR}/subdir
+	cp ${DATA} ${DIR}/subdir/copy
+	ln -s ${DIR}/subdir ${DIR}/subdir-sym
+}
+
 for mode in scp sftp ; do
 	tag="$tid: $mode mode"
 	if test $mode = scp ; then
 		scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp"
 	else
-		scpopts="-s -D ${SFTPSERVER}"
+		scpopts="-qs -D ${SFTPSERVER}"
 	fi
+
 	verbose "$tag: simple copy local file to local file"
 	scpclean
 	$SCP $scpopts ${DATA} ${COPY} || fail "copy failed"
@@ -96,21 +108,19 @@ for mode in scp sftp ; do
 	cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
 
 	verbose "$tag: recursive local dir to remote dir"
-	scpclean
-	rm -rf ${DIR2}
-	cp ${DATA} ${DIR}/copy
+	forest
 	$SCP $scpopts -r ${DIR} somehost:${DIR2} || fail "copy failed"
 	diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
 
 	verbose "$tag: recursive local dir to local dir"
-	scpclean
+	forest
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
 	$SCP $scpopts -r ${DIR} ${DIR2} || fail "copy failed"
 	diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
 
 	verbose "$tag: recursive remote dir to local dir"
-	scpclean
+	forest
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
 	$SCP $scpopts -r somehost:${DIR} ${DIR2} || fail "copy failed"

From 5e1dfe5014ebc194641678303e22ab3bba15f4e5 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 06:10:02 +0000
Subject: [PATCH 43/62] upstream: fix recursive remote-remote copies of
 directories that

contain symlinks to other directories (similar to bz3611)

OpenBSD-Commit-ID: 7e19d2ae09b4f941bf8eecc3955c9120171da37f
---
 sftp-client.c | 36 ++++++++++++++++++++++--------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/sftp-client.c b/sftp-client.c
index 8153c3d2d..2598029f7 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.173 2023/09/08 05:56:13 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.174 2023/09/08 06:10:02 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -2706,7 +2706,7 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 	SFTP_DIRENT **dir_entries;
 	char *filename, *new_from_path = NULL, *new_to_path = NULL;
 	mode_t mode = 0777;
-	Attrib curdir, ldirattrib, newdir;
+	Attrib *a, curdir, ldirattrib, newdir, lsym;
 
 	debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
 
@@ -2771,25 +2771,33 @@ crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
 		new_from_path = sftp_path_append(from_path, filename);
 		new_to_path = sftp_path_append(to_path, filename);
 
-		if (S_ISDIR(dir_entries[i]->a.perm)) {
+		a = &dir_entries[i]->a;
+		if (S_ISLNK(a->perm)) {
+			if (!follow_link_flag) {
+				logit("%s: not a regular file", filename);
+				continue;
+			}
+			/* Replace the stat contents with the symlink target */
+			if (sftp_stat(from, new_from_path, 1, &lsym) != 0) {
+				logit("remote stat \"%s\" failed",
+				    new_from_path);
+				ret = -1;
+				continue;
+			}
+			a = &lsym;
+		}
+		if (S_ISDIR(a->perm)) {
 			if (strcmp(filename, ".") == 0 ||
 			    strcmp(filename, "..") == 0)
 				continue;
 			if (crossload_dir_internal(from, to,
 			    new_from_path, new_to_path,
-			    depth + 1, &(dir_entries[i]->a), preserve_flag,
+			    depth + 1, a, preserve_flag,
 			    print_flag, follow_link_flag) == -1)
 				ret = -1;
-		} else if (S_ISREG(dir_entries[i]->a.perm) ||
-		    (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
-			/*
-			 * If this is a symlink then don't send the link's
-			 * Attrib. sftp_download() will do a FXP_STAT operation
-			 * and get the link target's attributes.
-			 */
-			if (sftp_crossload(from, to, new_from_path, new_to_path,
-			    S_ISLNK(dir_entries[i]->a.perm) ? NULL :
-			    &(dir_entries[i]->a), preserve_flag) == -1) {
+		} else if (S_ISREG(a->perm)) {
+			if (sftp_crossload(from, to, new_from_path,
+			    new_to_path, a, preserve_flag) == -1) {
 				error("crossload \"%s\" to \"%s\" failed",
 				    new_from_path, new_to_path);
 				ret = -1;

From c4f966482983e18601eec70a1563115de836616f Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 06:10:57 +0000
Subject: [PATCH 44/62] upstream: regress test recursive remote-remote
 directories copies where

the directory contains a symlink to another directory.

also remove errant `set -x` that snuck in at some point

OpenBSD-Regress-ID: 1c94a48bdbd633ef2285954ee257725cd7bc456f
---
 regress/scp3.sh | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/regress/scp3.sh b/regress/scp3.sh
index 383121f45..eeb7a9dde 100644
--- a/regress/scp3.sh
+++ b/regress/scp3.sh
@@ -1,10 +1,8 @@
-#	$OpenBSD: scp3.sh,v 1.4 2023/01/13 04:47:34 dtucker Exp $
+#	$OpenBSD: scp3.sh,v 1.5 2023/09/08 06:10:57 djm Exp $
 #	Placed in the Public Domain.
 
 tid="scp3"
 
-set -x
-
 COPY2=${OBJ}/copy2
 DIR=${COPY}.dd
 DIR2=${COPY}.dd2
@@ -22,6 +20,17 @@ scpclean() {
 	chmod 755 ${DIR} ${DIR2}
 }
 
+# Create directory structure for recursive copy tests.
+forest() {
+	scpclean
+	rm -rf ${DIR2}
+	cp ${DATA} ${DIR}/copy
+	ln -s ${DIR}/copy ${DIR}/copy-sym
+	mkdir ${DIR}/subdir
+	cp ${DATA} ${DIR}/subdir/copy
+	ln -s ${DIR}/subdir ${DIR}/subdir-sym
+}
+
 for mode in scp sftp ; do
 	scpopts="-F${OBJ}/ssh_proxy -S ${SSH} -q"
 	tag="$tid: $mode mode"
@@ -43,9 +52,7 @@ for mode in scp sftp ; do
 	cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
 
 	verbose "$tag: recursive remote dir to remote dir"
-	scpclean
-	rm -rf ${DIR2}
-	cp ${DATA} ${DIR}/copy
+	forest
 	$SCP $scpopts -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
 	diff -r ${DIR} ${DIR2} || fail "corrupted copy"
 	diff -r ${DIR2} ${DIR} || fail "corrupted copy"

From bd1b9e52f5fa94d87223c90905c5fdc1a7c32aa6 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Fri, 8 Sep 2023 06:34:24 +0000
Subject: [PATCH 45/62] upstream: fix sizeof(*ptr) instead sizeof(ptr) in
 realloc (pointer here

is char**, so harmless); spotted in CID 416964

OpenBSD-Commit-ID: c61caa4a5a667ee20bb1042098861e6c72c69002
---
 servconf.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/servconf.c b/servconf.c
index 3c2bf4827..49f7f7322 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.401 2023/09/06 23:35:35 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.402 2023/09/08 06:34:24 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -1957,15 +1957,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 		options->subsystem_name = xrecallocarray(
 		    options->subsystem_name, options->num_subsystems,
 		    options->num_subsystems + 1,
-		    sizeof(options->subsystem_name));
+		    sizeof(*options->subsystem_name));
 		options->subsystem_command = xrecallocarray(
 		    options->subsystem_command, options->num_subsystems,
 		    options->num_subsystems + 1,
-		    sizeof(options->subsystem_command));
+		    sizeof(*options->subsystem_command));
 		options->subsystem_args = xrecallocarray(
 		    options->subsystem_args, options->num_subsystems,
 		    options->num_subsystems + 1,
-		    sizeof(options->subsystem_args));
+		    sizeof(*options->subsystem_args));
 		options->subsystem_name[options->num_subsystems] = xstrdup(arg);
 		arg = argv_next(&ac, &av);
 		if (!arg || *arg == '\0') {
@@ -2721,13 +2721,13 @@ servconf_merge_subsystems(ServerOptions *dst, ServerOptions *src)
 		debug_f("add \"%s\"", src->subsystem_name[i]);
 		dst->subsystem_name = xrecallocarray(
 		    dst->subsystem_name, dst->num_subsystems,
-		    dst->num_subsystems + 1, sizeof(dst->subsystem_name));
+		    dst->num_subsystems + 1, sizeof(*dst->subsystem_name));
 		dst->subsystem_command = xrecallocarray(
 		    dst->subsystem_command, dst->num_subsystems,
-		    dst->num_subsystems + 1, sizeof(dst->subsystem_command));
+		    dst->num_subsystems + 1, sizeof(*dst->subsystem_command));
 		dst->subsystem_args = xrecallocarray(
 		    dst->subsystem_args, dst->num_subsystems,
-		    dst->num_subsystems + 1, sizeof(dst->subsystem_args));
+		    dst->num_subsystems + 1, sizeof(*dst->subsystem_args));
 		j = dst->num_subsystems++;
 		dst->subsystem_name[j] = xstrdup(src->subsystem_name[i]);
 		dst->subsystem_command[j] = xstrdup(src->subsystem_command[i]);

From 90ccc5918ea505bf156c31148b6b59a1bf5d6dc6 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Sun, 10 Sep 2023 03:25:53 +0000
Subject: [PATCH 46/62] upstream: randomise keystroke obfuscation intervals and
 average

interval rate. ok dtucker@

OpenBSD-Commit-ID: 05f61d051ab418fcfc4857ff306e420037502382
---
 clientloop.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 46 insertions(+), 6 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index 93bd9343c..b3a3b36e9 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.396 2023/09/04 00:08:14 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.397 2023/09/10 03:25:53 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -118,6 +118,9 @@
 /* Permitted RSA signature algorithms for UpdateHostkeys proofs */
 #define HOSTKEY_PROOF_RSA_ALGS	"rsa-sha2-512,rsa-sha2-256"
 
+/* Uncertainty (in percent) of keystroke timing intervals */
+#define SSH_KEYSTROKE_TIMING_FUZZ 10
+
 /* import options */
 extern Options options;
 
@@ -528,6 +531,43 @@ send_chaff(struct ssh *ssh)
 	return 1;
 }
 
+/* Sets the next interval to send a keystroke or chaff packet */
+static void
+set_next_interval(const struct timespec *now, struct timespec *next_interval,
+    u_int interval_ms, int starting)
+{
+	struct timespec tmp;
+	long long interval_ns, fuzz_ns;
+	static long long rate_fuzz;
+
+	interval_ns = interval_ms * (1000LL * 1000);
+	fuzz_ns = (interval_ns * SSH_KEYSTROKE_TIMING_FUZZ) / 100;
+	/* Center fuzz around requested interval */
+	if (fuzz_ns > INT_MAX)
+		fuzz_ns = INT_MAX;
+	if (fuzz_ns > interval_ns) {
+		/* Shouldn't happen */
+		fatal_f("internal error: fuzz %u%% %lldns > interval %lldns",
+		    SSH_KEYSTROKE_TIMING_FUZZ, fuzz_ns, interval_ns);
+	}
+	/*
+	 * Randomise the keystroke/chaff intervals in two ways:
+	 * 1. Each interval has some random jitter applied to make the
+	 *    interval-to-interval time unpredictable.
+	 * 2. The overall interval rate is also randomly perturbed for each
+	 *    each chaffing session to make the average rate unpredictable.
+	 */
+	if (starting)
+		rate_fuzz = arc4random_uniform(fuzz_ns);
+	interval_ns -= fuzz_ns;
+	interval_ns += arc4random_uniform(fuzz_ns) + rate_fuzz;
+
+	tmp.tv_sec = interval_ns / (1000 * 1000 * 1000);
+	tmp.tv_nsec = interval_ns % (1000 * 1000 * 1000);
+
+	timespecadd(now, &tmp, next_interval);
+}
+
 /*
  * Performs keystroke timing obfuscation. Returns non-zero if the
  * output fd should be polled.
@@ -591,12 +631,12 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
 	 */
 	if (!active && ssh_packet_interactive_data_to_write(ssh) &&
 	    channel_did_enqueue && ssh_packet_have_data_to_write(ssh)) {
-		debug3_f("starting: interval %d",
+		debug3_f("starting: interval ~%dms",
 		    options.obscure_keystroke_timing_interval);
 		just_started = had_keystroke = active = 1;
 		nchaff = 0;
-		ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval);
-		timespecadd(&now, &tmp, &next_interval);
+		set_next_interval(&now, &next_interval,
+		    options.obscure_keystroke_timing_interval, 1);
 	}
 
 	/* Don't hold off if obfuscation inactive */
@@ -629,8 +669,8 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
 	n = (n < 0) ? 1 : n + 1;
 
 	/* Advance to the next interval */
-	ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval * n);
-	timespecadd(&now, &tmp, &next_interval);
+	set_next_interval(&now, &next_interval,
+	    options.obscure_keystroke_timing_interval * n, 0);
 	return 1;
 }
 

From 41232d25532b4d2ef6c5db62efc0cf50a79d26ca Mon Sep 17 00:00:00 2001
From: Darren Tucker <dtucker@dtucker.net>
Date: Sun, 10 Sep 2023 15:45:38 +1000
Subject: [PATCH 47/62] Use zero-call-used-regs=used with Apple compilers.

Apple's versions of clang have version numbers that do not match the
corresponding upstream clang versions.  Unfortunately, they do still
have the clang-15 zero-call-used-regs=all bug, so for now use the value
that doesn't result in segfaults.  We could allowlist future versions
that are known to work.  bz#3584 (and probably also our github CI
failures).
---
 configure.ac | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index e3128dfcb..d8816e3f6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -187,7 +187,13 @@ if test "$GCC" = "yes" || test "$GCC" = "egcs"; then
 	AC_MSG_RESULT([$GCC_VER])
 
 	AC_MSG_CHECKING([clang version])
-	CLANG_VER=`$CC -v 2>&1 | $AWK '/clang version /{print $3}'`
+	ver="`$CC -v 2>&1`"
+	if echo "$ver" | grep "Apple" >/dev/null; then
+		CLANG_VER="apple-`echo "$ver" | \
+		    awk '/Apple LLVM/ {print $4"-"$5}'`"
+	else
+		CLANG_VER=`echo "$ver" | $AWK '/clang version /{print $3}'`
+	fi
 	AC_MSG_RESULT([$CLANG_VER])
 
 	OSSH_CHECK_CFLAG_COMPILE([-pipe])
@@ -225,7 +231,7 @@ if test "$GCC" = "yes" || test "$GCC" = "egcs"; then
 	# https://bugzilla.mindrot.org/show_bug.cgi?id=3475 and
 	# https://github.com/llvm/llvm-project/issues/59242
 	case "$CLANG_VER" in
-	15.*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;;
+	15.*|apple*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;;
 	*)    OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=all]) ;;
 	esac
 	OSSH_CHECK_CFLAG_COMPILE([-ftrivial-auto-var-init=zero])

From 21b79af6c8d2357c822c84cef3fbdb8001ed263b Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Sun, 10 Sep 2023 03:51:55 +0000
Subject: [PATCH 48/62] upstream: typo in comment

OpenBSD-Commit-ID: 69285e0ce962a7c6b0ab5f17a293c60a0a360a18
---
 clientloop.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clientloop.c b/clientloop.c
index b3a3b36e9..3e9fa3220 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.397 2023/09/10 03:25:53 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.398 2023/09/10 03:51:55 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -555,7 +555,7 @@ set_next_interval(const struct timespec *now, struct timespec *next_interval,
 	 * 1. Each interval has some random jitter applied to make the
 	 *    interval-to-interval time unpredictable.
 	 * 2. The overall interval rate is also randomly perturbed for each
-	 *    each chaffing session to make the average rate unpredictable.
+	 *    chaffing session to make the average rate unpredictable.
 	 */
 	if (starting)
 		rate_fuzz = arc4random_uniform(fuzz_ns);

From b6b49130a0089b297245ee39e769231d7c763014 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Sun, 10 Sep 2023 23:12:32 +0000
Subject: [PATCH 49/62] upstream: rename remote_glob() -> sftp_glob() to match
 other API

OpenBSD-Commit-ID: d9dfb3708d824ec02970a84d96cf5937e0887229
---
 scp.c       |  8 ++++----
 sftp-glob.c |  6 +++---
 sftp.c      | 16 ++++++++--------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/scp.c b/scp.c
index 55c018770..eaa407cb1 100644
--- a/scp.c
+++ b/scp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: scp.c,v 1.258 2023/09/08 05:56:13 djm Exp $ */
+/* $OpenBSD: scp.c,v 1.259 2023/09/10 23:12:32 djm Exp $ */
 /*
  * scp - secure remote copy.  This is basically patched BSD rcp which
  * uses ssh to do the data transfer (instead of using rcmd).
@@ -186,7 +186,7 @@ size_t sftp_nrequests;
 /* Needed for sftp */
 volatile sig_atomic_t interrupted = 0;
 
-int remote_glob(struct sftp_conn *, const char *, int,
+int sftp_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
 
 static void
@@ -1568,7 +1568,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
 	}
 
 	debug3_f("copying remote %s to local %s", abs_src, dst);
-	if ((r = remote_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK,
+	if ((r = sftp_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK,
 	    NULL, &g)) != 0) {
 		if (r == GLOB_NOSPACE)
 			error("%s: too many glob matches", src);
@@ -2001,7 +2001,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
 	}
 
 	debug3_f("copying remote %s to remote %s", abs_src, target);
-	if ((r = remote_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK,
+	if ((r = sftp_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK,
 	    NULL, &g)) != 0) {
 		if (r == GLOB_NOSPACE)
 			error("%s: too many glob matches", src);
diff --git a/sftp-glob.c b/sftp-glob.c
index 616168581..1b82759b0 100644
--- a/sftp-glob.c
+++ b/sftp-glob.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-glob.c,v 1.32 2023/09/08 05:56:13 djm Exp $ */
+/* $OpenBSD: sftp-glob.c,v 1.33 2023/09/10 23:12:32 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -32,7 +32,7 @@
 #include "sftp-common.h"
 #include "sftp-client.h"
 
-int remote_glob(struct sftp_conn *, const char *, int,
+int sftp_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *);
 
 struct SFTP_OPENDIR {
@@ -134,7 +134,7 @@ fudge_stat(const char *path, struct stat *st)
 }
 
 int
-remote_glob(struct sftp_conn *conn, const char *pattern, int flags,
+sftp_glob(struct sftp_conn *conn, const char *pattern, int flags,
     int (*errfunc)(const char *, int), glob_t *pglob)
 {
 	int r;
diff --git a/sftp.c b/sftp.c
index 843801c59..c609b4153 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.235 2023/09/08 05:56:13 djm Exp $ */
+/* $OpenBSD: sftp.c,v 1.236 2023/09/10 23:12:32 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -110,7 +110,7 @@ struct complete_ctx {
 	char **remote_pathp;
 };
 
-int remote_glob(struct sftp_conn *, const char *, int,
+int sftp_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
 
 extern char *__progname;
@@ -655,7 +655,7 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
 	memset(&g, 0, sizeof(g));
 
 	debug3("Looking up %s", abs_src);
-	if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
+	if ((r = sftp_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
 		if (r == GLOB_NOSPACE) {
 			error("Too many matches for \"%s\".", abs_src);
 		} else {
@@ -977,7 +977,7 @@ do_globbed_ls(struct sftp_conn *conn, const char *path,
 
 	memset(&g, 0, sizeof(g));
 
-	if ((r = remote_glob(conn, path,
+	if ((r = sftp_glob(conn, path,
 	    GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
 	    NULL, &g)) != 0 ||
 	    (g.gl_pathc && !g.gl_matchc)) {
@@ -1618,7 +1618,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		break;
 	case I_RM:
 		path1 = make_absolute_pwd_glob(path1, *pwd);
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
+		sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			if (!quiet)
 				mprintf("Removing %s\n", g.gl_pathv[i]);
@@ -1722,7 +1722,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		attrib_clear(&a);
 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
 		a.perm = n_arg;
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
+		sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			if (!quiet)
 				mprintf("Changing mode on %s\n",
@@ -1736,7 +1736,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 	case I_CHOWN:
 	case I_CHGRP:
 		path1 = make_absolute_pwd_glob(path1, *pwd);
-		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
+		sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
 			if ((hflag ? sftp_lstat : sftp_stat)(conn,
 			    g.gl_pathv[i], 0, &aa) != 0) {
@@ -2017,7 +2017,7 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
 	memset(&g, 0, sizeof(g));
 	if (remote != LOCAL) {
 		tmp = make_absolute_pwd_glob(tmp, remote_path);
-		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
+		sftp_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
 	} else
 		(void)glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
 

From 6c2c6ffde75df95fd838039850d3dd3d84956d87 Mon Sep 17 00:00:00 2001
From: "deraadt@openbsd.org" <deraadt@openbsd.org>
Date: Tue, 19 Sep 2023 20:37:07 +0000
Subject: [PATCH 50/62] upstream: typo; from Jim Spath

OpenBSD-Commit-ID: 2f5fba917b5d4fcf93d9e0b0756c7f63189e228e
---
 sshd.8 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/sshd.8 b/sshd.8
index 9c8f2fcaa..73d5e9232 100644
--- a/sshd.8
+++ b/sshd.8
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd.8,v 1.324 2023/02/10 06:39:27 jmc Exp $
-.Dd $Mdocdate: February 10 2023 $
+.\" $OpenBSD: sshd.8,v 1.325 2023/09/19 20:37:07 deraadt Exp $
+.Dd $Mdocdate: September 19 2023 $
 .Dt SSHD 8
 .Os
 .Sh NAME
@@ -320,7 +320,7 @@ forwarding TCP connections, or forwarding the authentication agent
 connection over the secure channel.
 .Pp
 After this, the client either requests an interactive shell or execution
-or a non-interactive command, which
+of a non-interactive command, which
 .Nm
 will execute via the user's shell using its
 .Fl c

From 12e2d4b13f6f63ce2de13cbfcc9e4d0d4b4ab231 Mon Sep 17 00:00:00 2001
From: Damien Miller <djm@mindrot.org>
Date: Wed, 4 Oct 2023 10:54:04 +1100
Subject: [PATCH 51/62] use portable provider allowlist path in manpage

spotted by Jann Horn
---
 ssh-agent.1 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ssh-agent.1 b/ssh-agent.1
index 6815eb834..0b93d03a3 100644
--- a/ssh-agent.1
+++ b/ssh-agent.1
@@ -156,7 +156,7 @@ See PATTERNS in
 .Xr ssh_config 5
 for a description of pattern-list syntax.
 The default list is
-.Dq /usr/lib/*,/usr/local/lib/* .
+.Dq usr/lib*/*,/usr/local/lib*/* .
 .It Fl s
 Generate Bourne shell commands on
 .Dv stdout .

From 60ec3d54fd1ebfe2dda75893fa1e870b8dffbb0d Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Tue, 3 Oct 2023 23:56:10 +0000
Subject: [PATCH 52/62] upstream: fix link to agent draft; spotted by Jann Horn

OpenBSD-Commit-ID: ff5bda21a83ec013db683e282256a85201d2dc4b
---
 PROTOCOL.agent | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/PROTOCOL.agent b/PROTOCOL.agent
index 44e463674..1c4841147 100644
--- a/PROTOCOL.agent
+++ b/PROTOCOL.agent
@@ -1,5 +1,5 @@
 The SSH agent protocol is described in
-https://tools.ietf.org/html/draft-miller-ssh-agent-04
+https://tools.ietf.org/html/draft-miller-ssh-agent
 
 This file documents OpenSSH's extensions to the agent protocol.
 
@@ -81,4 +81,4 @@ the constraint is:
 
 This option is only valid for XMSS keys.
 
-$OpenBSD: PROTOCOL.agent,v 1.19 2023/04/12 08:53:54 jsg Exp $
+$OpenBSD: PROTOCOL.agent,v 1.20 2023/10/03 23:56:10 djm Exp $

From ffe27e54a4bb18d5d3bbd3f4cc93a41b8d94dfd2 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 4 Oct 2023 04:03:50 +0000
Subject: [PATCH 53/62] upstream: add some cautionary text about % token
 expansion and

shell metacharacters; based on report from vinci AT protonmail.ch

OpenBSD-Commit-ID: aa1450a54fcee2f153ef70368d90edb1e7019113
---
 ssh_config.5 | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/ssh_config.5 b/ssh_config.5
index 7f64c2cf9..367305d2c 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.386 2023/08/28 09:52:09 djm Exp $
-.Dd $Mdocdate: August 28 2023 $
+.\" $OpenBSD: ssh_config.5,v 1.387 2023/10/04 04:03:50 djm Exp $
+.Dd $Mdocdate: October 4 2023 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -2206,6 +2206,16 @@ accepts all tokens.
 and
 .Cm ProxyJump
 accept the tokens %%, %h, %n, %p, and %r.
+.Pp
+Note that some of these directives build commands for execution via the shell.
+Because
+.Xr ssh 1
+performs no filtering or escaping of characters that have special meaning in
+shell commands (e.g. quotes), it is the user's reposibility to ensure that
+the arguments passed to
+.Xr ssh 1
+do not contain such characters and that tokens are appropriately quoted
+when used.
 .Sh ENVIRONMENT VARIABLES
 Arguments to some keywords can be expanded at runtime from environment
 variables on the client by enclosing them in

From f65f187b105d9b5c12fd750a211397d08c17c6d4 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Wed, 4 Oct 2023 04:04:09 +0000
Subject: [PATCH 54/62] upstream: openssh-9.5

OpenBSD-Commit-ID: 5e0af680480bd3b6f5560cf840ad032d48fd6b16
---
 version.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/version.h b/version.h
index e5b1e719d..1363d4706 100644
--- a/version.h
+++ b/version.h
@@ -1,6 +1,6 @@
-/* $OpenBSD: version.h,v 1.98 2023/08/10 01:01:07 djm Exp $ */
+/* $OpenBSD: version.h,v 1.99 2023/10/04 04:04:09 djm Exp $ */
 
-#define SSH_VERSION	"OpenSSH_9.4"
+#define SSH_VERSION	"OpenSSH_9.5"
 
 #define SSH_PORTABLE	"p1"
 #define SSH_RELEASE	SSH_VERSION SSH_PORTABLE

From 80a2f64b8c1d27383cc83d182b73920d1e6a91f1 Mon Sep 17 00:00:00 2001
From: Damien Miller <djm@mindrot.org>
Date: Wed, 4 Oct 2023 15:34:10 +1100
Subject: [PATCH 55/62] crank version numbers

---
 README                      | 2 +-
 contrib/redhat/openssh.spec | 2 +-
 contrib/suse/openssh.spec   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README b/README
index e44e44ced..6e41c8657 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-See https://www.openssh.com/releasenotes.html#9.4p1 for the release
+See https://www.openssh.com/releasenotes.html#9.5p1 for the release
 notes.
 
 Please read https://www.openssh.com/report.html for bug reporting
diff --git a/contrib/redhat/openssh.spec b/contrib/redhat/openssh.spec
index 7d6fe3cfd..7a167d445 100644
--- a/contrib/redhat/openssh.spec
+++ b/contrib/redhat/openssh.spec
@@ -1,4 +1,4 @@
-%global ver 9.4p1
+%global ver 9.5p1
 %global rel 1%{?dist}
 
 # OpenSSH privilege separation requires a user & group ID
diff --git a/contrib/suse/openssh.spec b/contrib/suse/openssh.spec
index 777362766..921d0f5a4 100644
--- a/contrib/suse/openssh.spec
+++ b/contrib/suse/openssh.spec
@@ -13,7 +13,7 @@
 
 Summary:	OpenSSH, a free Secure Shell (SSH) protocol implementation
 Name:		openssh
-Version:	9.4p1
+Version:	9.5p1
 URL:		https://www.openssh.com/
 Release:	1
 Source0:	openssh-%{version}.tar.gz

From 0c1b7a7d267504d3888db3e2abc5884c111479e9 Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Wed, 1 Nov 2023 10:03:52 -0400
Subject: [PATCH 56/62] rename path_append to sftp_path_append within WINDOWS
 ifdef

---
 sftp-client.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sftp-client.c b/sftp-client.c
index 4c57f0324..2b1037af1 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -2991,14 +2991,14 @@ sftp_make_absolute(char *p, const char *pwd)
 	*/
 	char *s1, *s2;
 	if (!is_absolute_path(p)) {
-		abs_str = path_append(pwd, p);
+		abs_str = sftp_path_append(pwd, p);
 		free(p);
 		p = abs_str;
 	}
 
 	/* Append "/" if needed to the absolute windows path */	
 	if (p && p[0] != '\0' && p[1] == ':') {
-		s1 = path_append("/", p);
+		s1 = sftp_path_append("/", p);
 		free(p);
 		p = s1;
 	}

From 00ef5b4d9c2fa77975ce67a09993e0cdcf319e47 Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Wed, 29 Nov 2023 11:41:35 -0500
Subject: [PATCH 57/62] remove sym link part of scp bash tests on Windows

---
 regress/scp.sh  | 4 ++++
 regress/scp3.sh | 6 +++++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/regress/scp.sh b/regress/scp.sh
index ec7e2dccf..d95e94773 100644
--- a/regress/scp.sh
+++ b/regress/scp.sh
@@ -35,10 +35,14 @@ forest() {
 	scpclean
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
+if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/copy ${DIR}/copy-sym
+fi
 	mkdir ${DIR}/subdir
 	cp ${DATA} ${DIR}/subdir/copy
+if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/subdir ${DIR}/subdir-sym
+fi
 }
 
 for mode in scp sftp ; do
diff --git a/regress/scp3.sh b/regress/scp3.sh
index 09dac55e5..c87701344 100644
--- a/regress/scp3.sh
+++ b/regress/scp3.sh
@@ -25,10 +25,14 @@ forest() {
 	scpclean
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
+if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/copy ${DIR}/copy-sym
+fi
 	mkdir ${DIR}/subdir
 	cp ${DATA} ${DIR}/subdir/copy
+if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/subdir ${DIR}/subdir-sym
+fi
 }
 
 for mode in scp sftp ; do
@@ -63,7 +67,7 @@ for mode in scp sftp ; do
 
 	verbose "$tag: recursive remote dir to remote dir"
 	forest
-	$SCP $scpopts -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
+	$SCP "${scpopts[@]}" -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
 	diff -r ${DIR} ${DIR2} || fail "corrupted copy"
 	diff -r ${DIR2} ${DIR} || fail "corrupted copy"
 

From 702f528bb7920a78f1fe52acb0ba2a5b31bbdd06 Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Wed, 29 Nov 2023 14:15:23 -0500
Subject: [PATCH 58/62] add SCP sym link tests to Windows pester tests

---
 regress/pesterTests/SCP.Tests.ps1 | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/regress/pesterTests/SCP.Tests.ps1 b/regress/pesterTests/SCP.Tests.ps1
index 3648ac73d..d247fbc09 100644
--- a/regress/pesterTests/SCP.Tests.ps1
+++ b/regress/pesterTests/SCP.Tests.ps1
@@ -27,13 +27,20 @@ Describe "Tests for scp command" -Tags "CI" {
         $DestinationFilePath = Join-Path $DestinationDir $fileName1        
         $NestedSourceDir= Join-Path $SourceDir "nested"
         $NestedSourceFilePath = Join-Path $NestedSourceDir $fileName2
+        $tmpDir = Join-Path $testDir "tmpDir"
+        $tmpDirFilePath = Join-Path $tmpDir $fileName1
+        $tmpDirSymLinkPath = Join-Path $SourceDir "SymLinkDir"
         $null = New-Item $SourceDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $null = New-Item $NestedSourceDir -ItemType directory -Force -ErrorAction SilentlyContinue
+        $null = New-Item $tmpDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $null = New-item -path $SourceFilePath -ItemType file -force -ErrorAction SilentlyContinue
         $null = New-item -path $NestedSourceFilePath -ItemType file -force -ErrorAction SilentlyContinue
+        $null = New-item -path $tmpDirFilePath -ItemType file -force -ErrorAction SilentlyContinue
         "Test content111" | Set-content -Path $SourceFilePath
         "Test content333" | Set-content -Path $SourceFilePath3
         "Test content in nested dir" | Set-content -Path $NestedSourceFilePath
+        "Test content in tmp dir for sym link" | Set-content -Path $tmpDirFilePath
+        $null = New-Item -Path $tmpDirSymLinkPath -ItemType SymbolicLink -Value $tmpDir
         $null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $sshcmd = (get-command ssh).Path        
 

From 476d7f5a263ed338cc6dbde7395d28b65ce9b515 Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Wed, 29 Nov 2023 16:54:45 -0500
Subject: [PATCH 59/62] add comments to bash test changes

---
 regress/scp.sh  | 1 +
 regress/scp3.sh | 1 +
 2 files changed, 2 insertions(+)

diff --git a/regress/scp.sh b/regress/scp.sh
index d95e94773..2ad4e49f5 100644
--- a/regress/scp.sh
+++ b/regress/scp.sh
@@ -35,6 +35,7 @@ forest() {
 	scpclean
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
+# symbolic link test coverage on Windows is in SCP.Tests.ps1
 if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/copy ${DIR}/copy-sym
 fi
diff --git a/regress/scp3.sh b/regress/scp3.sh
index c87701344..53164db6c 100644
--- a/regress/scp3.sh
+++ b/regress/scp3.sh
@@ -25,6 +25,7 @@ forest() {
 	scpclean
 	rm -rf ${DIR2}
 	cp ${DATA} ${DIR}/copy
+# symbolic link test coverage on Windows is in SCP.Tests.ps1
 if [ "$os" != "windows" ]; then
 	ln -s ${DIR}/copy ${DIR}/copy-sym
 fi

From 42e182fd4a9d51551d425b644c7aaf8d728ab719 Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Thu, 30 Nov 2023 15:27:59 -0500
Subject: [PATCH 60/62] fix scp.sh test failures

---
 regress/scp.sh | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/regress/scp.sh b/regress/scp.sh
index 2ad4e49f5..5b893c2ea 100644
--- a/regress/scp.sh
+++ b/regress/scp.sh
@@ -50,9 +50,17 @@ for mode in scp sftp ; do
 	tag="$tid: $mode mode"
 	# scpopts should be an array to preverse the double quotes
 	if [ "$os" == "windows" ]; then
-		scpopts=(-qs -D ${SFTPSERVER})
+		if test $mode = scp ; then
+			scpopts=(-O -q -S "$TEST_SHELL_PATH ${OBJ}/scp-ssh-wrapper.scp")
+		else
+			scpopts=(-qs -D ${SFTPSERVER})
+		fi
 	else
-		scpopts="-qs -D ${SFTPSERVER}"
+		if test $mode = scp ; then
+			scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp"
+		else
+			scpopts="-qs -D ${SFTPSERVER}"
+		fi
 	fi
 
 	verbose "$tag: simple copy local file to local file"
@@ -117,7 +125,7 @@ for mode in scp sftp ; do
 
 	verbose "$tag: recursive local dir to remote dir"
 	forest
-	$SCP $scpopts -r ${DIR} somehost:${DIR2} || fail "copy failed"
+	$SCP "${scpopts[@]}" -r ${DIR} somehost:${DIR2} || fail "copy failed"
 	diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
 
 	verbose "$tag: recursive local dir to local dir"
@@ -219,5 +227,5 @@ for mode in scp sftp ; do
 	cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target"
 done
 
-#scpclean
+scpclean
 rm -f ${OBJ}/scp-ssh-wrapper.scp

From a6248c1e5bff9f5bd07ed2c6a61aa0cc708eb86b Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Thu, 30 Nov 2023 16:50:01 -0500
Subject: [PATCH 61/62] add SCP symlink coverage to Pester Tests in place of
 Bash tests

---
 regress/pesterTests/SCP.Tests.ps1 | 63 ++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 10 deletions(-)

diff --git a/regress/pesterTests/SCP.Tests.ps1 b/regress/pesterTests/SCP.Tests.ps1
index d247fbc09..75c5195f2 100644
--- a/regress/pesterTests/SCP.Tests.ps1
+++ b/regress/pesterTests/SCP.Tests.ps1
@@ -27,23 +27,29 @@ Describe "Tests for scp command" -Tags "CI" {
         $DestinationFilePath = Join-Path $DestinationDir $fileName1        
         $NestedSourceDir= Join-Path $SourceDir "nested"
         $NestedSourceFilePath = Join-Path $NestedSourceDir $fileName2
-        $tmpDir = Join-Path $testDir "tmpDir"
-        $tmpDirFilePath = Join-Path $tmpDir $fileName1
-        $tmpDirSymLinkPath = Join-Path $SourceDir "SymLinkDir"
         $null = New-Item $SourceDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $null = New-Item $NestedSourceDir -ItemType directory -Force -ErrorAction SilentlyContinue
-        $null = New-Item $tmpDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $null = New-item -path $SourceFilePath -ItemType file -force -ErrorAction SilentlyContinue
         $null = New-item -path $NestedSourceFilePath -ItemType file -force -ErrorAction SilentlyContinue
-        $null = New-item -path $tmpDirFilePath -ItemType file -force -ErrorAction SilentlyContinue
         "Test content111" | Set-content -Path $SourceFilePath
         "Test content333" | Set-content -Path $SourceFilePath3
         "Test content in nested dir" | Set-content -Path $NestedSourceFilePath
-        "Test content in tmp dir for sym link" | Set-content -Path $tmpDirFilePath
-        $null = New-Item -Path $tmpDirSymLinkPath -ItemType SymbolicLink -Value $tmpDir
         $null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
         $sshcmd = (get-command ssh).Path        
 
+        # for symlink tests
+        $SourceDirSymLinkName = "SourceDirSymLink"
+        $SourceDirSymLink = Join-Path $testDir $SourceDirSymLinkName
+        $tmpDir = Join-Path $testDir "tmpDir"
+        $tmpDirFilePath = Join-Path $tmpDir $fileName1
+        $null = New-Item $SourceDirSymLink -ItemType directory -Force -ErrorAction SilentlyContinue
+        $null = New-Item $tmpDir -ItemType directory -Force -ErrorAction SilentlyContinue
+        $null = New-item -path $tmpDirFilePath -ItemType file -force -ErrorAction SilentlyContinue
+        "Test content in tmp dir for sym link" | Set-content -Path $tmpDirFilePath
+        $SymLinkName = "SymLinkDir"
+        $SymLinkDir = Join-Path $SourceDirSymLink $SymLinkName
+        $null = New-Item -Path $SymLinkDir -ItemType SymbolicLink -Value $tmpDir
+
         $server = $OpenSSHTestInfo["Target"]
         $port = $OpenSSHTestInfo["Port"]
         $ssouser = $OpenSSHTestInfo["SSOUser"]
@@ -121,6 +127,27 @@ Describe "Tests for scp command" -Tags "CI" {
             }
         )
 
+        $testData2 = @(
+            @{
+                Title = 'symlink copy from local dir to remote dir'
+                Source = $SourceDirSymLink
+                Destination = "test_target:$DestinationDir"
+                Options = "-r -p -c aes128-ctr"
+            },
+            @{
+                Title = 'symlink copy from local dir to local dir'
+                Source = $SourceDirSymLink
+                Destination = $DestinationDir
+                Options = "-r "
+            },
+            @{
+                Title = 'symlink copy from remote dir to local dir'            
+                Source = "test_target:$SourceDirSymLink"
+                Destination = $DestinationDir
+                Options = "-C -r -q"
+            }
+        )
+
         # for the first time, delete the existing log files.
         if ($OpenSSHTestInfo['DebugMode'])
         {
@@ -159,6 +186,10 @@ Describe "Tests for scp command" -Tags "CI" {
             {
                 Get-Item $SourceDir | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
             }
+            if(-not [string]::IsNullOrEmpty($SourceDirSymLink))
+            {
+                Get-Item $SourceDirSymLink | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
+            }
             if(-not [string]::IsNullOrEmpty($DestinationDir))
             {
                 Get-Item $DestinationDir | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
@@ -167,7 +198,7 @@ Describe "Tests for scp command" -Tags "CI" {
     }
 
     BeforeAll {
-        $null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
+        #$null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
     }
 
     AfterEach {
@@ -176,14 +207,12 @@ Describe "Tests for scp command" -Tags "CI" {
         $tI++
     }       
     
-
     It 'File copy: <Title> ' -TestCases:$testData {
         param([string]$Title, $Source, $Destination, [string]$Options)
         iex  "scp $Options $Source $Destination"
         $LASTEXITCODE | Should Be 0
         #validate file content. DestPath is the path to the file.
         CheckTarget -target $DestinationFilePath | Should Be $true
-
         $equal = @(Compare-Object (Get-ChildItem -path $SourceFilePath) (Get-ChildItem -path $DestinationFilePath) -Property Name, Length ).Length -eq 0
         $equal | Should Be $true
 
@@ -210,6 +239,10 @@ Describe "Tests for scp command" -Tags "CI" {
             $equal | Should Be $true
         }
 
+        write-host "source dir:"
+        Get-ChildItem -Recurse -path $SourceDir | write-host
+        write-host "destination dir:"
+        Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) | write-host
         $equal = @(Compare-Object (Get-ChildItem -Recurse -path $SourceDir) (Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) ) -Property Name, Length).Length -eq 0
         $equal | Should Be $true
 
@@ -220,6 +253,16 @@ Describe "Tests for scp command" -Tags "CI" {
         }
     }
 
+    It 'Directory with symlink recursive copy: <Title> ' -TestCases:$testData2 {
+        param([string]$Title, $Source, $Destination, [string]$Options)                        
+            
+        iex  "scp $Options $Source $Destination"
+        $LASTEXITCODE | Should Be 0
+        $expectedFilepath = join-path $DestinationDir $SourceDirSymLinkName $SymLinkName $fileName1
+        CheckTarget -target $expectedFilepath | Should Be $true
+        Get-Content $expectedFilepath | Should Be "Test content in tmp dir for sym link"
+    }
+
     It 'File copy: path contains wildcards ' {
         $Source = Join-Path $SourceDir $wildcardFileName2
         scp -p $Source $DestinationDir

From e10443e22981a45f96c1566d5872fc950a03d52a Mon Sep 17 00:00:00 2001
From: "tgauth@bu.edu" <tessgauthier@microsoft.com>
Date: Fri, 1 Dec 2023 15:49:33 -0500
Subject: [PATCH 62/62] remove debug statements from pester tests

---
 regress/pesterTests/SCP.Tests.ps1 | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/regress/pesterTests/SCP.Tests.ps1 b/regress/pesterTests/SCP.Tests.ps1
index 75c5195f2..1c3580c83 100644
--- a/regress/pesterTests/SCP.Tests.ps1
+++ b/regress/pesterTests/SCP.Tests.ps1
@@ -198,7 +198,7 @@ Describe "Tests for scp command" -Tags "CI" {
     }
 
     BeforeAll {
-        #$null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
+        $null = New-Item $DestinationDir -ItemType directory -Force -ErrorAction SilentlyContinue
     }
 
     AfterEach {
@@ -207,12 +207,14 @@ Describe "Tests for scp command" -Tags "CI" {
         $tI++
     }       
     
+
     It 'File copy: <Title> ' -TestCases:$testData {
         param([string]$Title, $Source, $Destination, [string]$Options)
         iex  "scp $Options $Source $Destination"
         $LASTEXITCODE | Should Be 0
         #validate file content. DestPath is the path to the file.
         CheckTarget -target $DestinationFilePath | Should Be $true
+        
         $equal = @(Compare-Object (Get-ChildItem -path $SourceFilePath) (Get-ChildItem -path $DestinationFilePath) -Property Name, Length ).Length -eq 0
         $equal | Should Be $true
 
@@ -239,10 +241,6 @@ Describe "Tests for scp command" -Tags "CI" {
             $equal | Should Be $true
         }
 
-        write-host "source dir:"
-        Get-ChildItem -Recurse -path $SourceDir | write-host
-        write-host "destination dir:"
-        Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) | write-host
         $equal = @(Compare-Object (Get-ChildItem -Recurse -path $SourceDir) (Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) ) -Property Name, Length).Length -eq 0
         $equal | Should Be $true