diff --git a/gsi-openssh.spec b/gsi-openssh.spec index 4c3bc2f..9304f81 100644 --- a/gsi-openssh.spec +++ b/gsi-openssh.spec @@ -24,7 +24,7 @@ %global libedit 1 %global openssh_ver 9.3p1 -%global openssh_rel 4 +%global openssh_rel 6 Summary: An implementation of the SSH protocol with GSI authentication Name: gsi-openssh @@ -46,6 +46,7 @@ Source13: gsisshd-keygen Source15: gsisshd-keygen.target Source19: %{name}-server-systemd-sysusers.conf Source20: gsissh-host-keys-migration.sh +Source21: gsissh-host-keys-migration.service Source99: README.sshd-and-gsisshd #https://bugzilla.mindrot.org/show_bug.cgi?id=2581 @@ -179,6 +180,12 @@ Patch1014: openssh-8.7p1-nohostsha1proof.patch Patch1015: openssh-9.3p1-upstream-cve-2023-38408.patch # upstream b7afd8a4ecaca8afd3179b55e9db79c0ff210237 Patch1016: openssh-9.3p1-openssl-compat.patch +Patch1017: openssh-9.6p1-CVE-2023-51385.patch +Patch1018: openssh-9.6p1-CVE-2023-48795.patch +Patch1019: openssh-9.6p1-CVE-2023-51384.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2294879 +# Merged in OpenSSH 9.8 +Patch1020: openssh-9.6p1-cve-2024-6387.patch # Fix issue with read-only ssh buffer during gssapi key exchange (#1938224) # https://github.com/openssh-gsskex/openssh-gsskex/pull/19 @@ -188,8 +195,8 @@ Patch97: openssh-8.0p1-sshbuf-readonly.patch Patch98: openssh-9.3p1-gsissh.patch # This is the HPN patch -# Based on https://sourceforge.net/projects/hpnssh/files/Patches/HPN-SSH%2015v2%208.5p1/ -Patch99: openssh-9.3p1-hpn-15.2-modified.patch +# Based on https://github.com/rapier1/hpn-ssh/ tag: hpn-9_3_P1 (hpn17v13) +Patch99: openssh-9.3p1-hpn-17.13.patch License: BSD Requires: /sbin/nologin @@ -247,6 +254,8 @@ Requires(pre): /usr/sbin/useradd Requires: pam >= 1.0.1-3 Requires: crypto-policies >= 20220824-1 %{?systemd_requires} +Requires(pre): policycoreutils-python-utils +Requires(postun): policycoreutils-python-utils %description SSH (Secure SHell) is a program for logging into and executing @@ -342,6 +351,10 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1014 -p1 -b .nosha1hostproof %patch -P 1015 -p1 -b .cve-2023-38408 %patch -P 1016 -p1 -b .ossl-version +%patch -P 1017 -p1 -b .cve-2023-51385 +%patch -P 1018 -p1 -b .cve-2023-48795 +%patch -P 1019 -p1 -b .cve-2023-51384 +%patch -P 1020 -p1 -b .cve-2024-6387 %patch -P 100 -p1 -b .coverity @@ -393,8 +406,8 @@ fi --sysconfdir=%{_sysconfdir}/gsissh \ --libexecdir=%{_libexecdir}/gsissh \ --datadir=%{_datadir}/gsissh \ - --with-default-path=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin \ - --with-superuser-path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \ + --with-default-path=%{_libexecdir}/gsissh/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin \ + --with-superuser-path=%{_libexecdir}/gsissh/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \ --with-privsep-path=%{_datadir}/empty.sshd \ --disable-strip \ --without-zlib-version-check \ @@ -454,21 +467,23 @@ install -m644 %{SOURCE15} $RPM_BUILD_ROOT/%{_unitdir}/gsisshd-keygen.target install -m755 %{SOURCE13} $RPM_BUILD_ROOT/%{_libexecdir}/gsissh/sshd-keygen install -d -m711 ${RPM_BUILD_ROOT}/%{_datadir}/empty.sshd install -p -D -m 0644 %{SOURCE19} $RPM_BUILD_ROOT%{_sysusersdir}/%{name}-server.conf -# Migration script for Fedora 38 change to remove group ownership for standard host keys +# Migration service/script for Fedora 38 change to remove group ownership for standard host keys # See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit install -m744 %{SOURCE20} $RPM_BUILD_ROOT/%{_libexecdir}/gsissh/ssh-host-keys-migration.sh +# Pulled-in via a `Wants=` in `gsisshd.service` & `gsisshd@.service` +install -m644 %{SOURCE21} $RPM_BUILD_ROOT/%{_unitdir}/gsissh-host-keys-migration.service +install -d $RPM_BUILD_ROOT/%{_localstatedir}/lib +touch $RPM_BUILD_ROOT/%{_localstatedir}/lib/.gsissh-host-keys-migration rm $RPM_BUILD_ROOT%{_bindir}/ssh-add rm $RPM_BUILD_ROOT%{_bindir}/ssh-agent rm $RPM_BUILD_ROOT%{_bindir}/ssh-keyscan rm $RPM_BUILD_ROOT%{_libexecdir}/gsissh/ssh-keycat rm $RPM_BUILD_ROOT%{_libexecdir}/gsissh/ssh-pkcs11-helper -rm $RPM_BUILD_ROOT%{_libexecdir}/gsissh/ssh-sk-helper rm $RPM_BUILD_ROOT%{_mandir}/man1/ssh-add.1* rm $RPM_BUILD_ROOT%{_mandir}/man1/ssh-agent.1* rm $RPM_BUILD_ROOT%{_mandir}/man1/ssh-keyscan.1* rm $RPM_BUILD_ROOT%{_mandir}/man8/ssh-pkcs11-helper.8* -rm $RPM_BUILD_ROOT%{_mandir}/man8/ssh-sk-helper.8* for f in $RPM_BUILD_ROOT%{_bindir}/* \ $RPM_BUILD_ROOT%{_sbindir}/* \ @@ -476,16 +491,27 @@ for f in $RPM_BUILD_ROOT%{_bindir}/* \ mv $f `dirname $f`/gsi`basename $f` done +# Add scp and hpnscp symlinks in gsisshd's path +mkdir $RPM_BUILD_ROOT%{_libexecdir}/gsissh/bin +ln -nrs $RPM_BUILD_ROOT%{_bindir}/gsiscp \ + $RPM_BUILD_ROOT%{_libexecdir}/gsissh/bin/scp +ln -nrs $RPM_BUILD_ROOT%{_bindir}/gsiscp \ + $RPM_BUILD_ROOT%{_libexecdir}/gsissh/bin/hpnscp + perl -pi -e "s|$RPM_BUILD_ROOT||g" $RPM_BUILD_ROOT%{_mandir}/man*/* %pre server %sysusers_create_compat %{SOURCE19} +semanage port -a -t ssh_port_t -p tcp 2222 2>/dev/null || : %post server if [ $1 -gt 1 ]; then - # In the case of an upgrade run the migration + # In the case of an upgrade (never true on OSTree systems) run the migration # script for Fedora 38 to remove group ownership for host keys. %{_libexecdir}/gsissh/ssh-host-keys-migration.sh + # Prevent the systemd unit that performs the same service (useful for + # OSTree systems) from running. + touch /var/lib/.gsissh-host-keys-migration fi %systemd_post gsisshd.service gsisshd.socket @@ -494,6 +520,9 @@ fi %postun server %systemd_postun_with_restart gsisshd.service +if [ $1 -eq 0 ]; then + semanage port -d -t ssh_port_t -p tcp 2222 2>/dev/null || : +fi %files %license LICENCE @@ -503,7 +532,7 @@ fi %attr(0755,root,root) %{_bindir}/gsissh-keygen %attr(0644,root,root) %{_mandir}/man1/gsissh-keygen.1* %attr(0755,root,root) %dir %{_libexecdir}/gsissh -%attr(4755,root,root) %{_libexecdir}/gsissh/ssh-keysign +%attr(4555,root,root) %{_libexecdir}/gsissh/ssh-keysign %attr(0644,root,root) %{_mandir}/man8/gsissh-keysign.8* %files clients @@ -516,7 +545,12 @@ fi %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/gsissh/ssh_config.d/50-redhat.conf %attr(0644,root,root) %{_mandir}/man5/gsissh_config.5* %attr(0755,root,root) %{_bindir}/gsisftp +%attr(0755,root,root) %{_libexecdir}/gsissh/ssh-sk-helper %attr(0644,root,root) %{_mandir}/man1/gsisftp.1* +%attr(0644,root,root) %{_mandir}/man8/gsissh-sk-helper.8* +%attr(0755,root,root) %dir %{_libexecdir}/gsissh/bin +%{_libexecdir}/gsissh/bin/scp +%{_libexecdir}/gsissh/bin/hpnscp %files server %dir %attr(0711,root,root) %{_datadir}/empty.sshd @@ -539,9 +573,17 @@ fi %attr(0644,root,root) %{_unitdir}/gsisshd-keygen@.service %attr(0644,root,root) %{_unitdir}/gsisshd-keygen.target %attr(0644,root,root) %{_sysusersdir}/%{name}-server.conf +%attr(0644,root,root) %{_unitdir}/gsissh-host-keys-migration.service %attr(0744,root,root) %{_libexecdir}/gsissh/ssh-host-keys-migration.sh +%ghost %attr(0644,root,root) %{_localstatedir}/lib/.gsissh-host-keys-migration %changelog +* Fri Jul 12 2024 Mattias Ellert - 9.3p1-6 +- Add scp and hpnscp symlinks in gsisshd's path + +* Mon Jul 08 2024 Mattias Ellert - 9.3p1-5 +- Based on openssh-9.3p1-11.fc39 + * Tue Oct 17 2023 Mattias Ellert - 9.3p1-4 - Based on openssh-9.3p1-9.fc39 diff --git a/gsissh-host-keys-migration.service b/gsissh-host-keys-migration.service new file mode 100644 index 0000000..db91516 --- /dev/null +++ b/gsissh-host-keys-migration.service @@ -0,0 +1,15 @@ +[Unit] +Description=Update gsissh host key permissions +Documentation=https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit +Before=gsisshd.service +After=gsissh-keygen.target +ConditionPathExists=!/var/lib/.gsissh-host-keys-migration + +[Service] +Type=oneshot +ExecStart=-/usr/libexec/gsissh/ssh-host-keys-migration.sh +ExecStart=touch /var/lib/.gsissh-host-keys-migration +RemainAfterExit=yes + +[Install] +WantedBy=gsisshd.service diff --git a/gsisshd.service b/gsisshd.service index afe165d..6272ced 100644 --- a/gsisshd.service +++ b/gsisshd.service @@ -3,6 +3,9 @@ Description=gsissh server daemon Documentation=man:gsisshd(8) man:gsisshd_config(5) After=network.target gsisshd-keygen.target Wants=gsisshd-keygen.target +# Migration for Fedora 38 change to remove group ownership for standard host keys +# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit +Wants=gsissh-host-keys-migration.service [Service] Type=notify diff --git a/gsisshd@.service b/gsisshd@.service index 3f5ecbd..8a3fff2 100644 --- a/gsisshd@.service +++ b/gsisshd@.service @@ -3,6 +3,9 @@ Description=gsissh per-connection server daemon Documentation=man:gsisshd(8) man:gsisshd_config(5) Wants=gsisshd-keygen.target After=gsisshd-keygen.target +# Migration for Fedora 38 change to remove group ownership for standard host keys +# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit +Wants=gsissh-host-keys-migration.service [Service] EnvironmentFile=-/etc/sysconfig/gsisshd diff --git a/openssh-9.3p1-hpn-15.2-modified.patch b/openssh-9.3p1-hpn-15.2-modified.patch deleted file mode 100644 index d348911..0000000 --- a/openssh-9.3p1-hpn-15.2-modified.patch +++ /dev/null @@ -1,2595 +0,0 @@ -diff -Nur openssh-9.3p1.orig/auth2.c openssh-9.3p1/auth2.c ---- openssh-9.3p1.orig/auth2.c 2023-06-26 17:56:58.794634874 +0200 -+++ openssh-9.3p1/auth2.c 2023-06-26 17:59:24.993036944 +0200 -@@ -52,6 +52,8 @@ - #include "dispatch.h" - #include "pathnames.h" - #include "ssherr.h" -+#include "canohost.h" -+ - #ifdef GSSAPI - #include "ssh-gss.h" - #endif -@@ -74,6 +76,8 @@ - extern Authmethod method_gssapi; - #endif - -+static int log_flag = 0; -+ - Authmethod *authmethods[] = { - &method_none, - &method_pubkey, -@@ -297,6 +301,11 @@ - - debug("userauth-request for user %s service %s method %s", - user[0] ? user : "", service, method); -+ if (!log_flag) { -+ logit("SSH: Server;Ltype: Authname;Remote: %s-%d;Name: %s", -+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), user); -+ log_flag = 1; -+ } - debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); - - #ifdef WITH_SELINUX -diff -Nur openssh-9.3p1.orig/channels.c openssh-9.3p1/channels.c ---- openssh-9.3p1.orig/channels.c 2023-06-26 17:56:58.689634585 +0200 -+++ openssh-9.3p1/channels.c 2023-06-26 18:26:41.719517151 +0200 -@@ -229,6 +229,9 @@ - /* Setup helper */ - static void channel_handler_init(struct ssh_channels *sc); - -+static int hpn_disabled = 0; -+static int hpn_buffer_size = 2 * 1024 * 1024; -+ - /* -- channel core */ - - void -@@ -495,6 +498,7 @@ - c->local_window = window; - c->local_window_max = window; - c->local_maxpacket = maxpack; -+ c->dynamic_window = 0; - c->remote_name = xstrdup(remote_name); - c->ctl_chan = -1; - c->delayed = 1; /* prevent call to channel_post handler */ -@@ -1203,6 +1207,28 @@ - c->io_want = SSH_CHAN_IO_SOCK_W; - } - -+static int -+channel_tcpwinsz(struct ssh *ssh) -+{ -+ u_int32_t tcpwinsz = 0; -+ socklen_t optsz = sizeof(tcpwinsz); -+ int ret = -1; -+ -+ /* if we aren't on a socket return 128KB */ -+ if (!ssh_packet_connection_is_on_socket(ssh)) -+ return 128 * 1024; -+ -+ ret = getsockopt(ssh_packet_get_connection_in(ssh), -+ SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz); -+ /* return no more than SSHBUF_SIZE_MAX (currently 256MB) */ -+ if ((ret == 0) && tcpwinsz > SSHBUF_SIZE_MAX) -+ tcpwinsz = SSHBUF_SIZE_MAX; -+ -+ debug2("tcpwinsz: tcp connection %d, Receive window: %d", -+ ssh_packet_get_connection_in(ssh), tcpwinsz); -+ return tcpwinsz; -+} -+ - static void - channel_pre_open(struct ssh *ssh, Channel *c) - { -@@ -2305,22 +2331,31 @@ - - if (c->type == SSH_CHANNEL_OPEN && - !(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && -- ((c->local_window_max - c->local_window > -- c->local_maxpacket*3) || -+ ((ssh_packet_is_interactive(ssh) && -+ c->local_window_max - c->local_window > c->local_maxpacket*3) || - c->local_window < c->local_window_max/2) && - c->local_consumed > 0) { -+ u_int addition = 0; -+ u_int32_t tcpwinsz = channel_tcpwinsz(ssh); -+ /* adjust max window size if we are in a dynamic environment */ -+ if (c->dynamic_window && (tcpwinsz > c->local_window_max)) { -+ /* grow the window somewhat aggressively to maintain pressure */ -+ addition = 1.5 * (tcpwinsz - c->local_window_max); -+ c->local_window_max += addition; -+ debug("Channel: Window growth to %d by %d bytes", c->local_window_max, addition); -+ } - if (!c->have_remote_id) - fatal_f("channel %d: no remote id", c->self); - if ((r = sshpkt_start(ssh, - SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || - (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || -- (r = sshpkt_put_u32(ssh, c->local_consumed)) != 0 || -+ (r = sshpkt_put_u32(ssh, c->local_consumed + addition)) != 0 || - (r = sshpkt_send(ssh)) != 0) { - fatal_fr(r, "channel %i", c->self); - } - debug2("channel %d: window %d sent adjust %d", c->self, -- c->local_window, c->local_consumed); -- c->local_window += c->local_consumed; -+ c->local_window, c->local_consumed + addition); -+ c->local_window += c->local_consumed + addition; - c->local_consumed = 0; - } - return 1; -@@ -3717,6 +3752,14 @@ - return addr; - } - -+void -+channel_set_hpn(int external_hpn_disabled, int external_hpn_buffer_size) -+{ -+ hpn_disabled = external_hpn_disabled; -+ hpn_buffer_size = external_hpn_buffer_size; -+ debug("HPN Disabled: %d, HPN Buffer Size: %d", hpn_disabled, hpn_buffer_size); -+} -+ - static int - channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, - struct Forward *fwd, int *allocated_listen_port, -@@ -3856,8 +3899,10 @@ - } - - /* Allocate a channel number for the socket. */ -+ /* explicitly test for hpn disabled option. if true use smaller window size */ - c = channel_new(ssh, "port-listener", type, sock, sock, -1, -- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, -+ hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : hpn_buffer_size, -+ CHAN_TCP_PACKET_DEFAULT, - 0, "port listener", 1); - c->path = xstrdup(host); - c->host_port = fwd->connect_port; -@@ -5041,7 +5086,8 @@ - sock = socks[n]; - nc = channel_new(ssh, "x11-listener", - SSH_CHANNEL_X11_LISTENER, sock, sock, -1, -- CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, -+ hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : hpn_buffer_size, -+ CHAN_X11_PACKET_DEFAULT, - 0, "X11 inet listener", 1); - nc->single_connection = single_connection; - (*chanids)[n] = nc->self; -diff -Nur openssh-9.3p1.orig/channels.h openssh-9.3p1/channels.h ---- openssh-9.3p1.orig/channels.h 2023-06-26 17:56:58.638634445 +0200 -+++ openssh-9.3p1/channels.h 2023-06-26 17:59:24.996036952 +0200 -@@ -173,8 +173,10 @@ - u_int local_window_max; - u_int local_consumed; - u_int local_maxpacket; -+ int dynamic_window; - int extended_usage; - int single_connection; -+ u_int tcpwinsz; - - char *ctype; /* const type - NB. not freed on channel_free */ - char *xctype; /* extended type */ -@@ -254,7 +256,7 @@ - #define SSH_CHAN_IO_SOCK (SSH_CHAN_IO_SOCK_R|SSH_CHAN_IO_SOCK_W) - - /* Read buffer size */ --#define CHAN_RBUF (16*1024) -+#define CHAN_RBUF CHAN_SES_PACKET_DEFAULT - - /* Maximum size for direct reads to buffers */ - #define CHANNEL_MAX_READ CHAN_SES_PACKET_DEFAULT -@@ -396,4 +398,7 @@ - void chan_write_failed(struct ssh *, Channel *); - void chan_obuf_empty(struct ssh *, Channel *); - -+/* hpn handler */ -+void channel_set_hpn(int, int); -+ - #endif -diff -Nur openssh-9.3p1.orig/cipher.c openssh-9.3p1/cipher.c ---- openssh-9.3p1.orig/cipher.c 2023-06-26 17:56:58.717634662 +0200 -+++ openssh-9.3p1/cipher.c 2023-06-26 17:59:24.997036955 +0200 -@@ -48,6 +48,7 @@ - #include "sshbuf.h" - #include "ssherr.h" - #include "digest.h" -+#include "log.h" - - #include "openbsd-compat/openssl-compat.h" - -@@ -55,6 +56,9 @@ - #define EVP_CIPHER_CTX void - #endif - -+/* for multi-threaded aes-ctr cipher */ -+extern const EVP_CIPHER *evp_aes_ctr_mt(void); -+ - struct sshcipher_ctx { - int plaintext; - int encrypt; -@@ -64,7 +68,7 @@ - const struct sshcipher *cipher; - }; - --static const struct sshcipher ciphers[] = { -+static struct sshcipher ciphers[] = { - #ifdef WITH_OPENSSL - #ifndef OPENSSL_NO_DES - { "3des-cbc", 8, 24, 0, 0, CFLAG_CBC, EVP_des_ede3_cbc }, -@@ -131,6 +135,29 @@ - #endif - } - -+/* used to get the cipher name so when force rekeying to handle the -+ * single to multithreaded ctr cipher swap we only rekey when appropriate -+ */ -+const char * -+cipher_ctx_name(const struct sshcipher_ctx *cc) -+{ -+ return cc->cipher->name; -+} -+ -+/* in order to get around sandbox and forking issues with a threaded cipher -+ * we set the initial pre-auth aes-ctr cipher to the default OpenSSH cipher -+ * post auth we set them to the new evp as defined by cipher-ctr-mt -+ */ -+#ifdef WITH_OPENSSL -+void -+cipher_reset_multithreaded(void) -+{ -+ cipher_by_name("aes128-ctr")->evptype = evp_aes_ctr_mt; -+ cipher_by_name("aes192-ctr")->evptype = evp_aes_ctr_mt; -+ cipher_by_name("aes256-ctr")->evptype = evp_aes_ctr_mt; -+} -+#endif -+ - u_int - cipher_blocksize(const struct sshcipher *c) - { -@@ -180,10 +207,10 @@ - return cc->plaintext; - } - --const struct sshcipher * -+struct sshcipher * - cipher_by_name(const char *name) - { -- const struct sshcipher *c; -+ struct sshcipher *c; - for (c = ciphers; c->name != NULL; c++) - if (strcmp(c->name, name) == 0) - return c; -@@ -205,7 +232,8 @@ - for ((p = strsep(&cp, CIPHER_SEP)); p && *p != '\0'; - (p = strsep(&cp, CIPHER_SEP))) { - c = cipher_by_name(p); -- if (c == NULL || (c->flags & CFLAG_INTERNAL) != 0) { -+ if (c == NULL || ((c->flags & CFLAG_INTERNAL) != 0 && -+ (c->flags & CFLAG_NONE) != 0)) { - free(cipher_list); - return 0; - } -diff -Nur openssh-9.3p1.orig/cipher-ctr-mt.c openssh-9.3p1/cipher-ctr-mt.c ---- openssh-9.3p1.orig/cipher-ctr-mt.c 1970-01-01 01:00:00.000000000 +0100 -+++ openssh-9.3p1/cipher-ctr-mt.c 2023-06-26 17:59:24.998036958 +0200 -@@ -0,0 +1,678 @@ -+/* -+ * OpenSSH Multi-threaded AES-CTR Cipher -+ * -+ * Author: Benjamin Bennett -+ * Author: Mike Tasota -+ * Author: Chris Rapier -+ * Copyright (c) 2008-2013 Pittsburgh Supercomputing Center. All rights reserved. -+ * -+ * Based on original OpenSSH AES-CTR cipher. Small portions remain unchanged, -+ * Copyright (c) 2003 Markus Friedl -+ * -+ * Permission to use, copy, modify, and distribute this software for any -+ * purpose with or without fee is hereby granted, provided that the above -+ * copyright notice and this permission notice appear in all copies. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -+ */ -+#include "includes.h" -+ -+#if defined(WITH_OPENSSL) -+#include -+ -+#include -+#include -+ -+#include -+ -+#include "xmalloc.h" -+#include "log.h" -+#include -+ -+/* compatibility with old or broken OpenSSL versions */ -+#include "openbsd-compat/openssl-compat.h" -+ -+#ifndef USE_BUILTIN_RIJNDAEL -+#include -+#endif -+ -+#include -+ -+/*-------------------- TUNABLES --------------------*/ -+/* maximum number of threads and queues */ -+#define MAX_THREADS 32 -+#define MAX_NUMKQ (MAX_THREADS * 2) -+ -+/* Number of pregen threads to use */ -+int cipher_threads = 2; -+ -+/* Number of keystream queues */ -+int numkq = 4; -+ -+/* Length of a keystream queue */ -+#define KQLEN 4096 -+ -+/* Processor cacheline length */ -+#define CACHELINE_LEN 64 -+ -+/* Collect thread stats and print at cancellation when in debug mode */ -+#define CIPHER_THREAD_STATS -+ -+/* Can the system do unaligned loads natively? */ -+#if defined(__aarch64__) || \ -+ defined(__i386__) || \ -+ defined(__powerpc__) || \ -+ defined(__x86_64__) -+# define CIPHER_UNALIGNED_OK -+#endif -+#if defined(__SIZEOF_INT128__) -+# define CIPHER_INT128_OK -+#endif -+/*-------------------- END TUNABLES --------------------*/ -+ -+ -+const EVP_CIPHER *evp_aes_ctr_mt(void); -+ -+#ifdef CIPHER_THREAD_STATS -+/* -+ * Struct to collect thread stats -+ */ -+struct thread_stats { -+ u_int fills; -+ u_int skips; -+ u_int waits; -+ u_int drains; -+}; -+ -+/* -+ * Debug print the thread stats -+ * Use with pthread_cleanup_push for displaying at thread cancellation -+ */ -+static void -+thread_loop_stats(void *x) -+{ -+ struct thread_stats *s = x; -+ debug("AES-CTR MT tid %lu - %u fills, %u skips, %u waits", pthread_self(), -+ s->fills, s->skips, s->waits); -+} -+ -+# define STATS_STRUCT(s) struct thread_stats s -+# define STATS_INIT(s) { memset(&s, 0, sizeof(s)); } -+# define STATS_FILL(s) { s.fills++; } -+# define STATS_SKIP(s) { s.skips++; } -+# define STATS_WAIT(s) { s.waits++; } -+# define STATS_DRAIN(s) { s.drains++; } -+#else -+# define STATS_STRUCT(s) -+# define STATS_INIT(s) -+# define STATS_FILL(s) -+# define STATS_SKIP(s) -+# define STATS_WAIT(s) -+# define STATS_DRAIN(s) -+#endif -+ -+/* Keystream Queue state */ -+enum { -+ KQINIT, -+ KQEMPTY, -+ KQFILLING, -+ KQFULL, -+ KQDRAINING -+}; -+ -+/* Keystream Queue struct */ -+struct kq { -+ u_char keys[KQLEN][AES_BLOCK_SIZE]; -+ u_char ctr[AES_BLOCK_SIZE]; -+ u_char pad0[CACHELINE_LEN]; -+ int qstate; -+ pthread_mutex_t lock; -+ pthread_cond_t cond; -+ u_char pad1[CACHELINE_LEN]; -+}; -+ -+/* Context struct */ -+struct ssh_aes_ctr_ctx_mt -+{ -+ int struct_id; -+ struct kq q[MAX_NUMKQ]; -+ AES_KEY aes_ctx; -+ STATS_STRUCT(stats); -+ u_char aes_counter[AES_BLOCK_SIZE]; -+ pthread_t tid[MAX_THREADS]; -+ int id[MAX_THREADS]; -+ pthread_rwlock_t tid_lock; -+#ifdef __APPLE__ -+ pthread_rwlock_t stop_lock; -+ int exit_flag; -+#endif /* __APPLE__ */ -+ int state; -+ int qidx; -+ int ridx; -+}; -+ -+/* -+ * increment counter 'ctr', -+ * the counter is of size 'len' bytes and stored in network-byte-order. -+ * (LSB at ctr[len-1], MSB at ctr[0]) -+ */ -+static void -+ssh_ctr_inc(u_char *ctr, size_t len) -+{ -+ int i; -+ -+ for (i = len - 1; i >= 0; i--) -+ if (++ctr[i]) /* continue on overflow */ -+ return; -+} -+ -+/* -+ * Add num to counter 'ctr' -+ */ -+static void -+ssh_ctr_add(u_char *ctr, uint32_t num, u_int len) -+{ -+ int i; -+ uint16_t n; -+ -+ for (n = 0, i = len - 1; i >= 0 && (num || n); i--) { -+ n = ctr[i] + (num & 0xff) + n; -+ num >>= 8; -+ ctr[i] = n & 0xff; -+ n >>= 8; -+ } -+} -+ -+/* -+ * Threads may be cancelled in a pthread_cond_wait, we must free the mutex -+ */ -+static void -+thread_loop_cleanup(void *x) -+{ -+ pthread_mutex_unlock((pthread_mutex_t *)x); -+} -+ -+#ifdef __APPLE__ -+/* Check if we should exit, we are doing both cancel and exit condition -+ * since on OSX threads seem to occasionally fail to notice when they have -+ * been cancelled. We want to have a backup to make sure that we won't hang -+ * when the main process join()-s the cancelled thread. -+ */ -+static void -+thread_loop_check_exit(struct ssh_aes_ctr_ctx_mt *c) -+{ -+ int exit_flag; -+ -+ pthread_rwlock_rdlock(&c->stop_lock); -+ exit_flag = c->exit_flag; -+ pthread_rwlock_unlock(&c->stop_lock); -+ -+ if (exit_flag) -+ pthread_exit(NULL); -+} -+#else -+# define thread_loop_check_exit(s) -+#endif /* __APPLE__ */ -+ -+/* -+ * Helper function to terminate the helper threads -+ */ -+static void -+stop_and_join_pregen_threads(struct ssh_aes_ctr_ctx_mt *c) -+{ -+ int i; -+ -+#ifdef __APPLE__ -+ /* notify threads that they should exit */ -+ pthread_rwlock_wrlock(&c->stop_lock); -+ c->exit_flag = TRUE; -+ pthread_rwlock_unlock(&c->stop_lock); -+#endif /* __APPLE__ */ -+ -+ /* Cancel pregen threads */ -+ for (i = 0; i < cipher_threads; i++) { -+ debug ("Canceled %lu (%d,%d)", c->tid[i], c->struct_id, c->id[i]); -+ pthread_cancel(c->tid[i]); -+ } -+ /* shouldn't need this - see commit logs for hpn-7_7_P1 -cjr 11/7/19*/ -+ /* for (i = 0; i < numkq; i++) { */ -+ /* pthread_mutex_lock(&c->q[i].lock); */ -+ /* pthread_cond_broadcast(&c->q[i].cond); */ -+ /* pthread_mutex_unlock(&c->q[i].lock); */ -+ /* } */ -+ for (i = 0; i < cipher_threads; i++) { -+ if (pthread_kill(c->tid[i], 0) != 0) -+ debug3("AES-CTR MT pthread_join failure: Invalid thread id %lu in %s", c->tid[i], __FUNCTION__); -+ else { -+ debug ("Joining %lu (%d, %d)", c->tid[i], c->struct_id, c->id[i]); -+ pthread_join(c->tid[i], NULL); -+ } -+ } -+} -+ -+/* -+ * The life of a pregen thread: -+ * Find empty keystream queues and fill them using their counter. -+ * When done, update counter for the next fill. -+ */ -+static void * -+thread_loop(void *x) -+{ -+ AES_KEY key; -+ STATS_STRUCT(stats); -+ struct ssh_aes_ctr_ctx_mt *c = x; -+ struct kq *q; -+ int i; -+ int qidx; -+ pthread_t first_tid; -+ -+ /* Threads stats on cancellation */ -+ STATS_INIT(stats); -+#ifdef CIPHER_THREAD_STATS -+ pthread_cleanup_push(thread_loop_stats, &stats); -+#endif -+ -+ /* Thread local copy of AES key */ -+ memcpy(&key, &c->aes_ctx, sizeof(key)); -+ -+ pthread_rwlock_rdlock(&c->tid_lock); -+ first_tid = c->tid[0]; -+ pthread_rwlock_unlock(&c->tid_lock); -+ -+ /* -+ * Handle the special case of startup, one thread must fill -+ * the first KQ then mark it as draining. Lock held throughout. -+ */ -+ if (pthread_equal(pthread_self(), first_tid)) { -+ q = &c->q[0]; -+ pthread_mutex_lock(&q->lock); -+ if (q->qstate == KQINIT) { -+ for (i = 0; i < KQLEN; i++) { -+ AES_encrypt(q->ctr, q->keys[i], &key); -+ ssh_ctr_inc(q->ctr, AES_BLOCK_SIZE); -+ } -+ ssh_ctr_add(q->ctr, KQLEN * (numkq - 1), AES_BLOCK_SIZE); -+ q->qstate = KQDRAINING; -+ STATS_FILL(stats); -+ pthread_cond_broadcast(&q->cond); -+ } -+ pthread_mutex_unlock(&q->lock); -+ } else -+ STATS_SKIP(stats); -+ -+ /* -+ * Normal case is to find empty queues and fill them, skipping over -+ * queues already filled by other threads and stopping to wait for -+ * a draining queue to become empty. -+ * -+ * Multiple threads may be waiting on a draining queue and awoken -+ * when empty. The first thread to wake will mark it as filling, -+ * others will move on to fill, skip, or wait on the next queue. -+ */ -+ for (qidx = 1;; qidx = (qidx + 1) % numkq) { -+ /* Check if I was cancelled, also checked in cond_wait */ -+ pthread_testcancel(); -+ -+ /* Check if we should exit as well */ -+ thread_loop_check_exit(c); -+ -+ /* Lock queue and block if its draining */ -+ q = &c->q[qidx]; -+ pthread_mutex_lock(&q->lock); -+ pthread_cleanup_push(thread_loop_cleanup, &q->lock); -+ while (q->qstate == KQDRAINING || q->qstate == KQINIT) { -+ STATS_WAIT(stats); -+ thread_loop_check_exit(c); -+ pthread_cond_wait(&q->cond, &q->lock); -+ } -+ pthread_cleanup_pop(0); -+ -+ /* If filling or full, somebody else got it, skip */ -+ if (q->qstate != KQEMPTY) { -+ pthread_mutex_unlock(&q->lock); -+ STATS_SKIP(stats); -+ continue; -+ } -+ -+ /* -+ * Empty, let's fill it. -+ * Queue lock is relinquished while we do this so others -+ * can see that it's being filled. -+ */ -+ q->qstate = KQFILLING; -+ pthread_cond_broadcast(&q->cond); -+ pthread_mutex_unlock(&q->lock); -+ for (i = 0; i < KQLEN; i++) { -+ AES_encrypt(q->ctr, q->keys[i], &key); -+ ssh_ctr_inc(q->ctr, AES_BLOCK_SIZE); -+ } -+ -+ /* Re-lock, mark full and signal consumer */ -+ pthread_mutex_lock(&q->lock); -+ ssh_ctr_add(q->ctr, KQLEN * (numkq - 1), AES_BLOCK_SIZE); -+ q->qstate = KQFULL; -+ STATS_FILL(stats); -+ pthread_cond_broadcast(&q->cond); -+ pthread_mutex_unlock(&q->lock); -+ } -+ -+#ifdef CIPHER_THREAD_STATS -+ /* Stats */ -+ pthread_cleanup_pop(1); -+#endif -+ -+ return NULL; -+} -+ -+static int -+ssh_aes_ctr(EVP_CIPHER_CTX *ctx, u_char *dest, const u_char *src, -+ LIBCRYPTO_EVP_INL_TYPE len) -+{ -+ typedef union { -+#ifdef CIPHER_INT128_OK -+ __uint128_t *u128; -+#endif -+ uint64_t *u64; -+ uint32_t *u32; -+ uint8_t *u8; -+ const uint8_t *cu8; -+ uintptr_t u; -+ } ptrs_t; -+ ptrs_t destp, srcp, bufp; -+ uintptr_t align; -+ struct ssh_aes_ctr_ctx_mt *c; -+ struct kq *q, *oldq; -+ int ridx; -+ u_char *buf; -+ -+ if (len == 0) -+ return 1; -+ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) -+ return 0; -+ -+ q = &c->q[c->qidx]; -+ ridx = c->ridx; -+ -+ /* src already padded to block multiple */ -+ srcp.cu8 = src; -+ destp.u8 = dest; -+ while (len > 0) { -+ buf = q->keys[ridx]; -+ bufp.u8 = buf; -+ -+ /* figure out the alignment on the fly */ -+#ifdef CIPHER_UNALIGNED_OK -+ align = 0; -+#else -+ align = destp.u | srcp.u | bufp.u; -+#endif -+ -+#ifdef CIPHER_INT128_OK -+ if ((align & 0xf) == 0) { -+ destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; -+ } else -+#endif -+ if ((align & 0x7) == 0) { -+ destp.u64[0] = srcp.u64[0] ^ bufp.u64[0]; -+ destp.u64[1] = srcp.u64[1] ^ bufp.u64[1]; -+ } else if ((align & 0x3) == 0) { -+ destp.u32[0] = srcp.u32[0] ^ bufp.u32[0]; -+ destp.u32[1] = srcp.u32[1] ^ bufp.u32[1]; -+ destp.u32[2] = srcp.u32[2] ^ bufp.u32[2]; -+ destp.u32[3] = srcp.u32[3] ^ bufp.u32[3]; -+ } else { -+ size_t i; -+ for (i = 0; i < AES_BLOCK_SIZE; ++i) -+ dest[i] = src[i] ^ buf[i]; -+ } -+ -+ destp.u += AES_BLOCK_SIZE; -+ srcp.u += AES_BLOCK_SIZE; -+ len -= AES_BLOCK_SIZE; -+ ssh_ctr_inc(c->aes_counter, AES_BLOCK_SIZE); -+ -+ /* Increment read index, switch queues on rollover */ -+ if ((ridx = (ridx + 1) % KQLEN) == 0) { -+ oldq = q; -+ -+ /* Mark next queue draining, may need to wait */ -+ c->qidx = (c->qidx + 1) % numkq; -+ q = &c->q[c->qidx]; -+ pthread_mutex_lock(&q->lock); -+ while (q->qstate != KQFULL) { -+ STATS_WAIT(c->stats); -+ pthread_cond_wait(&q->cond, &q->lock); -+ } -+ q->qstate = KQDRAINING; -+ pthread_cond_broadcast(&q->cond); -+ pthread_mutex_unlock(&q->lock); -+ -+ /* Mark consumed queue empty and signal producers */ -+ pthread_mutex_lock(&oldq->lock); -+ oldq->qstate = KQEMPTY; -+ STATS_DRAIN(c->stats); -+ pthread_cond_broadcast(&oldq->cond); -+ pthread_mutex_unlock(&oldq->lock); -+ } -+ } -+ c->ridx = ridx; -+ return 1; -+} -+ -+#define HAVE_NONE 0 -+#define HAVE_KEY 1 -+#define HAVE_IV 2 -+ -+int X = 0; -+ -+static int -+ssh_aes_ctr_init(EVP_CIPHER_CTX *ctx, const u_char *key, const u_char *iv, -+ int enc) -+{ -+ struct ssh_aes_ctr_ctx_mt *c; -+ int i; -+ -+ /* get the number of cores in the system */ -+ /* if it's not linux it currently defaults to 2 */ -+ /* divide by 2 to get threads for each direction (MODE_IN||MODE_OUT) */ -+#ifdef __linux__ -+ cipher_threads = sysconf(_SC_NPROCESSORS_ONLN) / 2; -+#endif /*__linux__*/ -+#ifdef __APPLE__ -+ cipher_threads = sysconf(_SC_NPROCESSORS_ONLN) / 2; -+#endif /*__APPLE__*/ -+#ifdef __FREEBSD__ -+ int req[2]; -+ size_t len; -+ -+ req[0] = CTL_HW; -+ req[1] = HW_NCPU; -+ -+ len = sizeof(ncpu); -+ sysctl(req, 2, &cipher_threads, &len, NULL, 0); -+ cipher_threads = cipher_threads / 2; -+#endif /*__FREEBSD__*/ -+ -+ /* if they have less than 4 cores spin up 4 threads anyway */ -+ if (cipher_threads < 2) -+ cipher_threads = 2; -+ -+ /* assure that we aren't trying to create more threads */ -+ /* than we have in the struct. cipher_threads is half the */ -+ /* total of allowable threads hence the odd looking math here */ -+ if (cipher_threads * 2 > MAX_THREADS) -+ cipher_threads = MAX_THREADS / 2; -+ -+ /* set the number of keystream queues */ -+ numkq = cipher_threads * 2; -+ -+ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) { -+ c = xmalloc(sizeof(*c)); -+ pthread_rwlock_init(&c->tid_lock, NULL); -+#ifdef __APPLE__ -+ pthread_rwlock_init(&c->stop_lock, NULL); -+ c->exit_flag = FALSE; -+#endif /* __APPLE__ */ -+ -+ c->state = HAVE_NONE; -+ for (i = 0; i < numkq; i++) { -+ pthread_mutex_init(&c->q[i].lock, NULL); -+ pthread_cond_init(&c->q[i].cond, NULL); -+ } -+ -+ STATS_INIT(c->stats); -+ EVP_CIPHER_CTX_set_app_data(ctx, c); -+ } -+ -+ if (c->state == (HAVE_KEY | HAVE_IV)) { -+ /* tell the pregen threads to exit */ -+ stop_and_join_pregen_threads(c); -+ -+#ifdef __APPLE__ -+ /* reset the exit flag */ -+ c->exit_flag = FALSE; -+#endif /* __APPLE__ */ -+ -+ /* Start over getting key & iv */ -+ c->state = HAVE_NONE; -+ } -+ -+ if (key != NULL) { -+ AES_set_encrypt_key(key, EVP_CIPHER_CTX_key_length(ctx) * 8, -+ &c->aes_ctx); -+ c->state |= HAVE_KEY; -+ } -+ -+ if (iv != NULL) { -+ memcpy(c->aes_counter, iv, AES_BLOCK_SIZE); -+ c->state |= HAVE_IV; -+ } -+ -+ if (c->state == (HAVE_KEY | HAVE_IV)) { -+ /* Clear queues */ -+ memcpy(c->q[0].ctr, c->aes_counter, AES_BLOCK_SIZE); -+ c->q[0].qstate = KQINIT; -+ for (i = 1; i < numkq; i++) { -+ memcpy(c->q[i].ctr, c->aes_counter, AES_BLOCK_SIZE); -+ ssh_ctr_add(c->q[i].ctr, i * KQLEN, AES_BLOCK_SIZE); -+ c->q[i].qstate = KQEMPTY; -+ } -+ c->qidx = 0; -+ c->ridx = 0; -+ -+ /* Start threads */ -+ for (i = 0; i < cipher_threads; i++) { -+ pthread_rwlock_wrlock(&c->tid_lock); -+ if (pthread_create(&c->tid[i], NULL, thread_loop, c) != 0) -+ debug ("AES-CTR MT Could not create thread in %s", __FUNCTION__); /*should die here */ -+ else { -+ if (!c->struct_id) -+ c->struct_id = X++; -+ c->id[i] = i; -+ debug ("AES-CTR MT spawned a thread with id %lu in %s (%d, %d)", c->tid[i], __FUNCTION__, c->struct_id, c->id[i]); -+ } -+ pthread_rwlock_unlock(&c->tid_lock); -+ } -+ pthread_mutex_lock(&c->q[0].lock); -+ while (c->q[0].qstate == KQINIT) -+ pthread_cond_wait(&c->q[0].cond, &c->q[0].lock); -+ pthread_mutex_unlock(&c->q[0].lock); -+ } -+ return 1; -+} -+ -+/* this function is no longer used but might prove handy in the future -+ * this comment also applies to ssh_aes_ctr_thread_reconstruction -+ */ -+void -+ssh_aes_ctr_thread_destroy(EVP_CIPHER_CTX *ctx) -+{ -+ struct ssh_aes_ctr_ctx_mt *c; -+ -+ c = EVP_CIPHER_CTX_get_app_data(ctx); -+ stop_and_join_pregen_threads(c); -+} -+ -+void -+ssh_aes_ctr_thread_reconstruction(EVP_CIPHER_CTX *ctx) -+{ -+ struct ssh_aes_ctr_ctx_mt *c; -+ int i; -+ c = EVP_CIPHER_CTX_get_app_data(ctx); -+ /* reconstruct threads */ -+ for (i = 0; i < cipher_threads; i++) { -+ pthread_rwlock_wrlock(&c->tid_lock); -+ if (pthread_create(&c->tid[i], NULL, thread_loop, c) !=0 ) -+ debug("AES-CTR MT could not create thread in %s", __FUNCTION__); -+ else { -+ c->struct_id = X++; -+ c->id[i] = i; -+ debug ("AES-CTR MT spawned a thread with id %lu in %s (%d, %d)", c->tid[i], __FUNCTION__, c->struct_id, c->id[i]); -+ debug("AES-CTR MT spawned a thread with id %lu in %s", c->tid[i], __FUNCTION__); -+ } -+ pthread_rwlock_unlock(&c->tid_lock); -+ } -+} -+ -+static int -+ssh_aes_ctr_cleanup(EVP_CIPHER_CTX *ctx) -+{ -+ struct ssh_aes_ctr_ctx_mt *c; -+ -+ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) != NULL) { -+#ifdef CIPHER_THREAD_STATS -+ debug("AES-CTR MT main thread: %u drains, %u waits", c->stats.drains, -+ c->stats.waits); -+#endif -+ stop_and_join_pregen_threads(c); -+ -+ memset(c, 0, sizeof(*c)); -+ free(c); -+ EVP_CIPHER_CTX_set_app_data(ctx, NULL); -+ } -+ return 1; -+} -+ -+/* */ -+const EVP_CIPHER * -+evp_aes_ctr_mt(void) -+{ -+# if OPENSSL_VERSION_NUMBER >= 0x10100000UL -+ static EVP_CIPHER *aes_ctr; -+ aes_ctr = EVP_CIPHER_meth_new(NID_undef, 16/*block*/, 16/*key*/); -+ EVP_CIPHER_meth_set_iv_length(aes_ctr, AES_BLOCK_SIZE); -+ EVP_CIPHER_meth_set_init(aes_ctr, ssh_aes_ctr_init); -+ EVP_CIPHER_meth_set_cleanup(aes_ctr, ssh_aes_ctr_cleanup); -+ EVP_CIPHER_meth_set_do_cipher(aes_ctr, ssh_aes_ctr); -+# ifndef SSH_OLD_EVP -+ EVP_CIPHER_meth_set_flags(aes_ctr, EVP_CIPH_CBC_MODE -+ | EVP_CIPH_VARIABLE_LENGTH -+ | EVP_CIPH_ALWAYS_CALL_INIT -+ | EVP_CIPH_CUSTOM_IV); -+# endif /*SSH_OLD_EVP*/ -+ return (aes_ctr); -+# else /*earlier versions of openssl*/ -+ static EVP_CIPHER aes_ctr; -+ memset(&aes_ctr, 0, sizeof(EVP_CIPHER)); -+ aes_ctr.nid = NID_undef; -+ aes_ctr.block_size = AES_BLOCK_SIZE; -+ aes_ctr.iv_len = AES_BLOCK_SIZE; -+ aes_ctr.key_len = 16; -+ aes_ctr.init = ssh_aes_ctr_init; -+ aes_ctr.cleanup = ssh_aes_ctr_cleanup; -+ aes_ctr.do_cipher = ssh_aes_ctr; -+# ifndef SSH_OLD_EVP -+ aes_ctr.flags = EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH | -+ EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CUSTOM_IV; -+# endif /*SSH_OLD_EVP*/ -+ return &aes_ctr; -+# endif /*OPENSSH_VERSION_NUMBER*/ -+} -+ -+#endif /* defined(WITH_OPENSSL) */ -diff -Nur openssh-9.3p1.orig/cipher.h openssh-9.3p1/cipher.h ---- openssh-9.3p1.orig/cipher.h 2023-06-26 17:56:58.717634662 +0200 -+++ openssh-9.3p1/cipher.h 2023-06-26 17:59:24.999036961 +0200 -@@ -68,7 +68,9 @@ - - struct sshcipher_ctx; - --const struct sshcipher *cipher_by_name(const char *); -+void ssh_aes_ctr_thread_destroy(EVP_CIPHER_CTX *ctx); // defined in cipher-ctr-mt.c -+void ssh_aes_ctr_thread_reconstruction(EVP_CIPHER_CTX *ctx); -+struct sshcipher *cipher_by_name(const char *); - const char *cipher_warning_message(const struct sshcipher_ctx *); - int ciphers_valid(const char *); - char *cipher_alg_list(char, int); -@@ -86,6 +88,8 @@ - u_int cipher_authlen(const struct sshcipher *); - u_int cipher_ivlen(const struct sshcipher *); - u_int cipher_is_cbc(const struct sshcipher *); -+void cipher_reset_multithreaded(void); -+const char *cipher_ctx_name(const struct sshcipher_ctx *); - - u_int cipher_ctx_is_plaintext(struct sshcipher_ctx *); - -diff -Nur openssh-9.3p1.orig/clientloop.c openssh-9.3p1/clientloop.c ---- openssh-9.3p1.orig/clientloop.c 2023-06-26 17:56:58.584634296 +0200 -+++ openssh-9.3p1/clientloop.c 2023-06-26 17:59:25.000036964 +0200 -@@ -1646,7 +1646,9 @@ - return NULL; - c = channel_new(ssh, "x11", - SSH_CHANNEL_X11_OPEN, sock, sock, -1, -- CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); -+ /* again is this really necessary for X11? */ -+ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, -+ CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); - c->force_drain = 1; - return c; - } -@@ -1681,7 +1683,8 @@ - - c = channel_new(ssh, "authentication agent connection", - SSH_CHANNEL_OPEN, sock, sock, -1, -- CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, -+ options.hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : options.hpn_buffer_size, -+ CHAN_TCP_PACKET_DEFAULT, 0, - "authentication agent connection", 1); - c->force_drain = 1; - return c; -@@ -1708,7 +1711,8 @@ - debug("Tunnel forwarding using interface %s", ifname); - - c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, -- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); -+ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, -+ CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); - c->datagram = 1; - - #if defined(SSH_TUN_FILTER) -diff -Nur openssh-9.3p1.orig/compat.c openssh-9.3p1/compat.c ---- openssh-9.3p1.orig/compat.c 2023-06-26 17:56:58.764634791 +0200 -+++ openssh-9.3p1/compat.c 2023-06-26 17:59:25.001036966 +0200 -@@ -135,6 +135,17 @@ - ssh->compat = check[i].bugs; - if (forbid_ssh_rsa) - ssh->compat |= SSH_RH_RSASIGSHA; -+ /* Check to see if the remote side is OpenSSH and not HPN */ -+ /* TODO: See if we can work this into the new method for bug checks */ -+ if (strstr(version, "OpenSSH") != NULL) { -+ if (strstr(version, "hpn") == NULL) { -+ ssh->compat |= SSH_BUG_LARGEWINDOW; -+ debug("Remote is NOT HPN enabled"); -+ } else { -+ debug("Remote is HPN Enabled"); -+ } -+ } -+ debug("ssh->compat is %u", ssh->compat); - return; - } - } -diff -Nur openssh-9.3p1.orig/compat.h openssh-9.3p1/compat.h ---- openssh-9.3p1.orig/compat.h 2023-06-26 17:56:58.765634794 +0200 -+++ openssh-9.3p1/compat.h 2023-06-26 17:59:25.001036966 +0200 -@@ -57,6 +57,7 @@ - #define SSH_BUG_CURVE25519PAD 0x10000000 - #define SSH_BUG_HOSTKEYS 0x20000000 - #define SSH_BUG_DHGEX_LARGE 0x40000000 -+#define SSH_BUG_LARGEWINDOW 0x80000000 - - struct ssh; - -diff -Nur openssh-9.3p1.orig/defines.h openssh-9.3p1/defines.h ---- openssh-9.3p1.orig/defines.h 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/defines.h 2023-06-26 17:59:25.002036969 +0200 -@@ -889,7 +889,7 @@ - #endif - - #ifndef SSH_IOBUFSZ --# define SSH_IOBUFSZ 8192 -+# define SSH_IOBUFSZ 32*1024 - #endif - - /* -diff -Nur openssh-9.3p1.orig/digest.h openssh-9.3p1/digest.h ---- openssh-9.3p1.orig/digest.h 2023-06-26 17:56:58.672634538 +0200 -+++ openssh-9.3p1/digest.h 2023-06-26 17:59:25.002036969 +0200 -@@ -27,7 +27,8 @@ - #define SSH_DIGEST_SHA256 2 - #define SSH_DIGEST_SHA384 3 - #define SSH_DIGEST_SHA512 4 --#define SSH_DIGEST_MAX 5 -+#define SSH_DIGEST_NULL 5 -+#define SSH_DIGEST_MAX 6 - - struct sshbuf; - struct ssh_digest_ctx; -diff -Nur openssh-9.3p1.orig/digest-openssl.c openssh-9.3p1/digest-openssl.c ---- openssh-9.3p1.orig/digest-openssl.c 2023-06-26 17:56:58.672634538 +0200 -+++ openssh-9.3p1/digest-openssl.c 2023-06-26 17:59:25.002036969 +0200 -@@ -61,6 +61,7 @@ - { SSH_DIGEST_SHA256, "SHA256", 32, EVP_sha256 }, - { SSH_DIGEST_SHA384, "SHA384", 48, EVP_sha384 }, - { SSH_DIGEST_SHA512, "SHA512", 64, EVP_sha512 }, -+ { SSH_DIGEST_NULL, "NONEMAC", 0, EVP_md_null}, - { -1, NULL, 0, NULL }, - }; - -diff -Nur openssh-9.3p1.orig/HPN-README openssh-9.3p1/HPN-README ---- openssh-9.3p1.orig/HPN-README 1970-01-01 01:00:00.000000000 +0100 -+++ openssh-9.3p1/HPN-README 2023-06-26 17:59:25.003036972 +0200 -@@ -0,0 +1,153 @@ -+Notes: -+ -+MULTI-THREADED CIPHER: -+The AES cipher in CTR mode has been multithreaded (MTR-AES-CTR). This will allow ssh installations -+on hosts with multiple cores to use more than one processing core during encryption. -+Tests have show significant throughput performance increases when using MTR-AES-CTR up -+to and including a full gigabit per second on quad core systems. It should be possible to -+achieve full line rate on dual core systems but OS and data management overhead makes this -+more difficult to achieve. The cipher stream from MTR-AES-CTR is entirely compatible with single -+thread AES-CTR (ST-AES-CTR) implementations and should be 100% backward compatible. Optimal -+performance requires the MTR-AES-CTR mode be enabled on both ends of the connection. -+The MTR-AES-CTR replaces ST-AES-CTR and is used in exactly the same way with the same -+nomenclature. -+Use examples: -+ ssh -caes128-ctr you@host.com -+ scp -oCipher=aes256-ctr file you@host.com:~/file -+ -+NONE CIPHER: -+To use the NONE option you must have the NoneEnabled switch set on the server and -+you *must* have *both* NoneEnabled and NoneSwitch set to yes on the client. The NONE -+feature works with ALL ssh subsystems (as far as we can tell) *AS LONG AS* a tty is not -+spawned. If a user uses the -T switch to prevent a tty being created the NONE cipher will -+be disabled. -+ -+The performance increase will only be as good as the network and TCP stack tuning -+on the reciever side of the connection allows. As a rule of thumb a user will need -+at least 10Mb/s connection with a 100ms RTT to see a doubling of performance. The -+HPN-SSH home page describes this in greater detail. -+ -+http://www.psc.edu/networking/projects/hpn-ssh -+ -+NONE MAC: -+Starting with HPN 15v1 users will have the option to disable HMAC (message -+authentication ciphers) when using the NONE cipher. You must enable the following: -+NoneEnabled, NoneSwitch, and NoneMacEnabled. If all three are not enabled the None MAC -+will be automatically disabled. In tests the use of the None MAC improved throuput by -+more than 30%. -+ -+ex: scp -oNoneSwitch=yes -oNoneEnabled=yes -oNoneMacEnabled=yes file host:~ -+ -+BUFFER SIZES: -+ -+If HPN is disabled the receive buffer size will be set to the -+OpenSSH default of 2MB (for OpenSSH versions before 4.7: 64KB). -+ -+If an HPN system connects to a nonHPN system the receive buffer will -+be set to the HPNBufferSize value. The default is 2MB but user adjustable. -+ -+If an HPN to HPN connection is established a number of different things might -+happen based on the user options and conditions. -+ -+Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set -+HPN Buffer Size = up to 64MB -+This is the default state. The HPN buffer size will grow to a maximum of 64MB -+as the TCP receive buffer grows. The maximum HPN Buffer size of 64MB is -+geared towards 10GigE transcontinental connections. -+ -+Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set -+HPN Buffer Size = TCP receive buffer value. -+Users on non-autotuning systems should disable TCPRcvBufPoll in the -+ssh_config and sshd_config -+ -+Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set -+HPN Buffer Size = minimum of TCP receive buffer and HPNBufferSize. -+This would be the system defined TCP receive buffer (RWIN). -+ -+Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf SET -+HPN Buffer Size = minimum of TCPRcvBuf and HPNBufferSize. -+Generally there is no need to set both. -+ -+Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set -+HPN Buffer Size = grows to HPNBufferSize -+The buffer will grow up to the maximum size specified here. -+ -+Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf SET -+HPN Buffer Size = minimum of TCPRcvBuf and HPNBufferSize. -+Generally there is no need to set both of these, especially on autotuning -+systems. However, if the users wishes to override the autotuning this would be -+one way to do it. -+ -+Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf SET -+HPN Buffer Size = TCPRcvBuf. -+This will override autotuning and set the TCP recieve buffer to the user defined -+value. -+ -+ -+HPN Specific Configuration options -+ -+TcpRcvBuf=[int]KB client -+ Set the TCP socket receive buffer to n Kilobytes. It can be set up to the -+maximum socket size allowed by the system. This is useful in situations where -+the tcp receive window is set low but the maximum buffer size is set -+higher (as is typical). This works on a per TCP connection basis. You can also -+use this to artifically limit the transfer rate of the connection. In these -+cases the throughput will be no more than n/RTT. The minimum buffer size is 1KB. -+Default is the current system wide tcp receive buffer size. -+ -+TcpRcvBufPoll=[yes/no] client/server -+ Enable of disable the polling of the tcp receive buffer through the life -+of the connection. You would want to make sure that this option is enabled -+for systems making use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista) -+default is yes. -+ -+NoneEnabled=[yes/no] client/server -+ Enable or disable the use of the None cipher. Care must always be used -+when enabling this as it will allow users to send data in the clear. However, -+it is important to note that authentication information remains encrypted -+even if this option is enabled. Set to no by default. -+ -+NoneMacEnabled=[yes/no] client/server -+ Enable or disable the use of the None MAC. When this is enabled ssh -+will *not* provide data integrity of any data being transmitted between hosts. Use -+with caution as it, unlike just using NoneEnabled, doesn't provide data integrity and -+protection against man-in-the-middle attacks. As with NoneEnabled all authentication -+remains encrypted and integrity is ensured. Default is no. -+ -+NoneSwitch=[yes/no] client -+ Switch the encryption cipher being used to the None cipher after -+authentication takes place. NoneEnabled must be enabled on both the client -+and server side of the connection. When the connection switches to the NONE -+cipher a warning is sent to STDERR. The connection attempt will fail with an -+error if a client requests a NoneSwitch from the server that does not explicitly -+have NoneEnabled set to yes. Note: The NONE cipher cannot be used in -+interactive (shell) sessions and it will fail silently. Set to no by default. -+ -+HPNDisabled=[yes/no] client/server -+ In some situations, such as transfers on a local area network, the impact -+of the HPN code produces a net decrease in performance. In these cases it is -+helpful to disable the HPN functionality. By default HPNDisabled is set to no. -+ -+HPNBufferSize=[int]KB client/server -+ This is the default buffer size the HPN functionality uses when interacting -+with nonHPN SSH installations. Conceptually this is similar to the TcpRcvBuf -+option as applied to the internal SSH flow control. This value can range from -+1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause performance -+problems depending on the length of the network path. The default size of this buffer -+is 2MB. -+ -+DisableMTAES=[yes/no] client/server -+ Switch the encryption cipher being used from the multithreaded MT-AES-CTR cipher -+back to the stock single-threaded AES-CTR cipher. Useful on modern processors with -+AES-NI instructions which make the stock single-threaded AES-CTR cipher faster than -+the multithreaded MT-AES-CTR cipher. Set to no by default. -+ -+ -+Credits: This patch was conceived, designed, and led by Chris Rapier (rapier@psc.edu) -+ The majority of the actual coding for versions up to HPN12v1 was performed -+ by Michael Stevens (mstevens@andrew.cmu.edu). The MT-AES-CTR cipher was -+ implemented by Ben Bennet (ben@psc.edu) and improved by Mike Tasota -+ (tasota@gmail.com) an NSF REU grant recipient for 2013. -+ Allan Jude provided the code for the NoneMac and buffer normalization. -+ This work was financed, in part, by Cisco System, Inc., the National -+ Library of Medicine, and the National Science Foundation. -diff -Nur openssh-9.3p1.orig/kex.c openssh-9.3p1/kex.c ---- openssh-9.3p1.orig/kex.c 2023-06-26 17:56:58.759634778 +0200 -+++ openssh-9.3p1/kex.c 2023-06-26 17:59:25.004036975 +0200 -@@ -65,6 +65,7 @@ - - #include "ssherr.h" - #include "sshbuf.h" -+#include "canohost.h" - #include "digest.h" - #include "xmalloc.h" - #include "audit.h" -@@ -1061,6 +1062,11 @@ - int nenc, nmac, ncomp; - u_int mode, ctos, need, dh_need, authlen; - int r, first_kex_follows; -+ int auth_flag = 0; -+ int log_flag = 0; -+ -+ auth_flag = packet_authentication_state(ssh); -+ debug("AUTH STATE IS %d", auth_flag); - - debug2("local %s KEXINIT proposal", kex->server ? "server" : "client"); - if ((r = kex_buf2prop(kex->my, NULL, &my)) != 0) -@@ -1141,11 +1147,40 @@ - peer[ncomp] = NULL; - goto out; - } -+ debug("REQUESTED ENC.NAME is '%s'", newkeys->enc.name); -+ debug("REQUESTED MAC.NAME is '%s'", newkeys->mac.name); -+ if (strcmp(newkeys->enc.name, "none") == 0) { -+ if (auth_flag == 1) { -+ debug("None requested post authentication."); -+ ssh->none = 1; -+ } -+ else -+ fatal("Pre-authentication none cipher requests are not allowed."); -+ if (newkeys->mac.name != NULL && strcmp(newkeys->mac.name, "none") == 0) -+ debug("Requesting: NONEMAC. Authflag is %d", auth_flag); -+ } -+ - debug("kex: %s cipher: %s MAC: %s compression: %s", - ctos ? "client->server" : "server->client", - newkeys->enc.name, - authlen == 0 ? newkeys->mac.name : "", - newkeys->comp.name); -+ /* -+ * client starts with ctos = 0 && log flag = 0 and no log. -+ * 2nd client pass ctos = 1 and flag = 1 so no log. -+ * server starts with ctos = 1 && log_flag = 0 so log. -+ * 2nd sever pass ctos = 1 && log flag = 1 so no log. -+ * -cjr -+ */ -+ if (ctos && !log_flag) { -+ logit("SSH: Server;Ltype: Kex;Remote: %s-%d;Enc: %s;MAC: %s;Comp: %s", -+ ssh_remote_ipaddr(ssh), -+ ssh_remote_port(ssh), -+ newkeys->enc.name, -+ authlen == 0 ? newkeys->mac.name : "", -+ newkeys->comp.name); -+ } -+ log_flag = 1; - } - need = dh_need = 0; - for (mode = 0; mode < MODE_MAX; mode++) { -@@ -1493,7 +1528,7 @@ - if (version_addendum != NULL && *version_addendum == '\0') - version_addendum = NULL; - if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", -- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, -+ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, - version_addendum == NULL ? "" : " ", - version_addendum == NULL ? "" : version_addendum)) != 0) { - oerrno = errno; -@@ -1629,6 +1664,14 @@ - r = SSH_ERR_INVALID_FORMAT; - goto out; - } -+ -+ /* report the version information to syslog if this is the server */ -+ if (timeout_ms == -1) { /* only the server uses this value */ -+ logit("SSH: Server;Ltype: Version;Remote: %s-%d;Protocol: %d.%d;Client: %.100s", -+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), -+ remote_major, remote_minor, remote_version); -+ } -+ - debug("Remote protocol version %d.%d, remote software version %.100s", - remote_major, remote_minor, remote_version); - compat_banner(ssh, remote_version); -diff -Nur openssh-9.3p1.orig/log.c openssh-9.3p1/log.c ---- openssh-9.3p1.orig/log.c 2023-06-26 17:56:58.620634395 +0200 -+++ openssh-9.3p1/log.c 2023-06-26 17:59:25.005036978 +0200 -@@ -46,6 +46,11 @@ - #include - #include - #include -+#include "packet.h" /* needed for host and port look ups */ -+#ifdef HAVE_SYS_TIME_H -+# include /* to get current time */ -+#endif -+ - #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) - # include - #endif -@@ -65,6 +70,8 @@ - - extern char *__progname; - -+extern struct ssh *active_state; -+ - #define LOG_SYSLOG_VIS (VIS_CSTYLE|VIS_NL|VIS_TAB|VIS_OCTAL) - #define LOG_STDERR_VIS (VIS_SAFE|VIS_OCTAL) - -diff -Nur openssh-9.3p1.orig/mac.c openssh-9.3p1/mac.c ---- openssh-9.3p1.orig/mac.c 2023-06-26 17:56:58.719634668 +0200 -+++ openssh-9.3p1/mac.c 2023-06-26 17:59:25.005036978 +0200 -@@ -63,6 +63,7 @@ - { "hmac-sha2-512", SSH_DIGEST, SSH_DIGEST_SHA512, 0, 0, 0, 0 }, - { "hmac-md5", SSH_DIGEST, SSH_DIGEST_MD5, 0, 0, 0, 0 }, - { "hmac-md5-96", SSH_DIGEST, SSH_DIGEST_MD5, 96, 0, 0, 0 }, -+ { "none", SSH_DIGEST, SSH_DIGEST_NULL, 0, 0, 0, 0 }, - { "umac-64@openssh.com", SSH_UMAC, 0, 0, 128, 64, 0 }, - { "umac-128@openssh.com", SSH_UMAC128, 0, 0, 128, 128, 0 }, - -diff -Nur openssh-9.3p1.orig/Makefile.in openssh-9.3p1/Makefile.in ---- openssh-9.3p1.orig/Makefile.in 2023-06-26 17:56:58.801634893 +0200 -+++ openssh-9.3p1/Makefile.in 2023-06-26 18:05:19.227009623 +0200 -@@ -49,7 +49,7 @@ - CFLAGS_NOPIE=@CFLAGS_NOPIE@ - CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ - PICFLAG=@PICFLAG@ --LIBS=@LIBS@ -+LIBS=@LIBS@ -lpthread - CHANNELLIBS=@CHANNELLIBS@ - K5LIBS=@K5LIBS@ - GSSLIBS=@GSSLIBS@ -@@ -97,7 +97,7 @@ - LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ - authfd.o authfile.o \ - canohost.o channels.o cipher.o cipher-aes.o cipher-aesctr.o \ -- cleanup.o \ -+ cipher-ctr-mt.o cleanup.o \ - compat.o fatal.o hostfile.o \ - log.o match.o moduli.o nchan.o packet.o \ - readpass.o ttymodes.o xmalloc.o addr.o addrmatch.o \ -diff -Nur openssh-9.3p1.orig/packet.c openssh-9.3p1/packet.c ---- openssh-9.3p1.orig/packet.c 2023-06-26 17:56:58.723634679 +0200 -+++ openssh-9.3p1/packet.c 2023-06-26 17:59:25.007036983 +0200 -@@ -246,7 +246,7 @@ - TAILQ_INIT(&ssh->public_keys); - state->connection_in = -1; - state->connection_out = -1; -- state->max_packet_size = 32768; -+ state->max_packet_size = CHAN_SES_PACKET_DEFAULT; - state->packet_timeout_ms = -1; - state->p_send.packets = state->p_read.packets = 0; - state->initialized = 1; -@@ -294,7 +294,7 @@ - ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) - { - struct session_state *state; -- const struct sshcipher *none = cipher_by_name("none"); -+ struct sshcipher *none = cipher_by_name("none"); - int r; - - if (none == NULL) { -@@ -958,10 +958,19 @@ - * so enforce a 1GB limit for small blocksizes. - * See RFC4344 section 3.2. - */ -- if (enc->block_size >= 16) -- *max_blocks = (u_int64_t)1 << (enc->block_size*2); -- else -- *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; -+ -+ /* we really don't need to rekey if we are using the none cipher -+ * but there isn't a good way to disable it entirely that I can find -+ * and using a blocksize larger that 16 doesn't work (dunno why) -+ * so this seems to be a good limit for now - CJR 10/16/2020*/ -+ if (ssh->none == 1) { -+ *max_blocks = (u_int64_t)1 << (16*2); -+ } else { -+ if (enc->block_size >= 16) -+ *max_blocks = (u_int64_t)1 << (enc->block_size*2); -+ else -+ *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; -+ } - if (state->rekey_limit) - *max_blocks = MINIMUM(*max_blocks, - state->rekey_limit / enc->block_size); -@@ -970,6 +979,24 @@ - return 0; - } - -+/* this supports the forced rekeying required for the NONE cipher */ -+int rekey_requested = 0; -+void -+packet_request_rekeying(void) -+{ -+ rekey_requested = 1; -+} -+ -+/* used to determine if pre or post auth when rekeying for aes-ctr -+ * and none cipher switch */ -+int -+packet_authentication_state(const struct ssh *ssh) -+{ -+ struct session_state *state = ssh->state; -+ -+ return state->after_authentication; -+} -+ - #define MAX_PACKETS (1U<<31) - static int - ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) -@@ -996,6 +1023,13 @@ - if (state->p_send.packets == 0 && state->p_read.packets == 0) - return 0; - -+ /* used to force rekeying when called for by the none -+ * cipher switch and aes-mt-ctr methods -cjr */ -+ if (rekey_requested == 1) { -+ rekey_requested = 0; -+ return 1; -+ } -+ - /* Time-based rekeying */ - if (state->rekey_interval != 0 && - (int64_t)state->rekey_time + state->rekey_interval <= monotime()) -@@ -1342,7 +1376,7 @@ - struct session_state *state = ssh->state; - int len, r, ms_remain = 0; - struct pollfd pfd; -- char buf[8192]; -+ char buf[SSH_IOBUFSZ]; - struct timeval start; - struct timespec timespec, *timespecp = NULL; - -@@ -1822,6 +1856,8 @@ - state->packet_discard -= rlen; - return 0; - } -+ -+ ssh->fdout_bytes += rlen; - return 0; - } - -@@ -1887,17 +1923,21 @@ - switch (r) { - case SSH_ERR_CONN_CLOSED: - ssh_packet_clear_keys(ssh); -+ sshpkt_final_log_entry(ssh); - logdie("Connection closed by %s", remote_id); - case SSH_ERR_CONN_TIMEOUT: - ssh_packet_clear_keys(ssh); -+ sshpkt_final_log_entry(ssh); - logdie("Connection %s %s timed out", - ssh->state->server_side ? "from" : "to", remote_id); - case SSH_ERR_DISCONNECTED: - ssh_packet_clear_keys(ssh); -+ sshpkt_final_log_entry(ssh); - logdie("Disconnected from %s", remote_id); - case SSH_ERR_SYSTEM_ERROR: - if (errno == ECONNRESET) { - ssh_packet_clear_keys(ssh); -+ sshpkt_final_log_entry(ssh); - logdie("Connection reset by %s", remote_id); - } - /* FALLTHROUGH */ -@@ -1939,6 +1979,24 @@ - logdie_f("should have exited"); - } - -+/* this prints out the final log entry */ -+void -+sshpkt_final_log_entry (struct ssh *ssh) { -+ double total_time; -+ -+ if (ssh->start_time < 1) -+ /* this will produce a NaN in the output. -cjr */ -+ total_time = 0; -+ else -+ total_time = monotime_double() - ssh->start_time; -+ -+ logit("SSH: Server;LType: Throughput;Remote: %s-%d;IN: %lu;OUT: %lu;Duration: %.1f;tPut_in: %.1f;tPut_out: %.1f", -+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), -+ ssh->stdin_bytes, ssh->fdout_bytes, total_time, -+ ssh->stdin_bytes / total_time, -+ ssh->fdout_bytes / total_time); -+} -+ - /* - * Logs the error plus constructs and sends a disconnect packet, closes the - * connection, and exits. This function never returns. The error message -@@ -2008,6 +2066,7 @@ - return SSH_ERR_CONN_CLOSED; - if ((r = sshbuf_consume(state->output, len)) != 0) - return r; -+ ssh->stdin_bytes += len; - } - return 0; - } -@@ -2799,3 +2858,10 @@ - ssh->state->extra_pad = pad; - return 0; - } -+ -+/* need this for the moment for the aes-ctr cipher */ -+void * -+ssh_packet_get_send_context(struct ssh *ssh) -+{ -+ return ssh->state->send_context; -+} -diff -Nur openssh-9.3p1.orig/packet.h openssh-9.3p1/packet.h ---- openssh-9.3p1.orig/packet.h 2023-06-26 17:56:58.724634681 +0200 -+++ openssh-9.3p1/packet.h 2023-06-26 17:59:25.008036986 +0200 -@@ -86,6 +86,14 @@ - - /* APP data */ - void *app_data; -+ -+ /* logging data for ServerLogging patch*/ -+ double start_time; -+ u_long fdout_bytes; -+ u_long stdin_bytes; -+ -+ /* track that we are in a none cipher/mac state */ -+ int none; - }; - - typedef int (ssh_packet_hook_fn)(struct ssh *, struct sshbuf *, -@@ -157,6 +165,8 @@ - int ssh_packet_set_maxsize(struct ssh *, u_int); - u_int ssh_packet_get_maxsize(struct ssh *); - -+int packet_authentication_state(const struct ssh *); -+ - int ssh_packet_get_state(struct ssh *, struct sshbuf *); - int ssh_packet_set_state(struct ssh *, struct sshbuf *); - -@@ -171,6 +181,13 @@ - - void *ssh_packet_get_input(struct ssh *); - void *ssh_packet_get_output(struct ssh *); -+void *ssh_packet_get_receive_context(struct ssh *); -+void *ssh_packet_get_send_context(struct ssh *); -+ -+/* for forced packet rekeying post auth */ -+void packet_request_rekeying(void); -+/* final log entry support */ -+void sshpkt_final_log_entry (struct ssh *); - - /* new API */ - int sshpkt_start(struct ssh *ssh, u_char type); -diff -Nur openssh-9.3p1.orig/progressmeter.c openssh-9.3p1/progressmeter.c ---- openssh-9.3p1.orig/progressmeter.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/progressmeter.c 2023-07-05 10:45:57.895329810 +0200 -@@ -67,6 +67,8 @@ - static off_t start_pos; /* initial position of transfer */ - static off_t end_pos; /* ending position of transfer */ - static off_t cur_pos; /* transfer position as of last refresh */ -+static off_t last_pos; -+static off_t max_delta_pos = 0; - static volatile off_t *counter; /* progress counter */ - static long stalled; /* how long we have been stalled */ - static int bytes_per_second; /* current speed in bytes per second */ -@@ -133,6 +135,7 @@ - int cur_speed; - int hours, minutes, seconds; - int file_len, cols; -+ off_t delta_pos; - - if ((!force_update && !alarm_fired && !win_resized) || !can_output()) - return; -@@ -148,6 +151,10 @@ - now = monotime_double(); - bytes_left = end_pos - cur_pos; - -+ delta_pos = cur_pos - last_pos; -+ if (delta_pos > max_delta_pos) -+ max_delta_pos = delta_pos; -+ - if (bytes_left > 0) - elapsed = now - last_update; - else { -@@ -171,13 +178,14 @@ - bytes_per_second = cur_speed; - - last_update = now; -+ last_pos = cur_pos; - - /* Don't bother if we can't even display the completion percentage */ - if (win_size < 4) - return; - - /* filename */ -- file_len = cols = win_size - 36; -+ file_len = cols = win_size - 45; - if (file_len > 0) { - asmprintf(&buf, INT_MAX, &cols, "%-*s", file_len, file); - /* If we used fewer columns than expected then pad */ -@@ -194,6 +202,12 @@ - xextendf(&buf, NULL, " %3d%% %s %s/s ", percent, format_size(cur_pos), - format_rate((off_t)bytes_per_second)); - -+ /* instantaneous rate */ -+ if (bytes_left > 0) -+ xextendf(&buf, NULL, "%s/s ", format_rate(delta_pos)); -+ else -+ xextendf(&buf, NULL, "%s/s ", format_rate(max_delta_pos)); -+ - /* ETA */ - if (!transferred) - stalled += elapsed; -diff -Nur openssh-9.3p1.orig/readconf.c openssh-9.3p1/readconf.c ---- openssh-9.3p1.orig/readconf.c 2023-06-26 17:56:58.806634907 +0200 -+++ openssh-9.3p1/readconf.c 2023-06-26 17:59:25.011036994 +0200 -@@ -67,6 +67,7 @@ - #include "uidswap.h" - #include "myproposal.h" - #include "digest.h" -+#include "sshbuf.h" - #include "ssh-gss.h" - - /* Format of the configuration file: -@@ -169,6 +170,9 @@ - oHashKnownHosts, - oTunnel, oTunnelDevice, - oLocalCommand, oPermitLocalCommand, oRemoteCommand, -+ oTcpRcvBufPoll, oTcpRcvBuf, oHPNDisabled, oHPNBufferSize, -+ oNoneEnabled, oNoneMacEnabled, oNoneSwitch, -+ oDisableMTAES, - oVisualHostKey, - oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull, - oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass, -@@ -314,6 +318,10 @@ - { "kexalgorithms", oKexAlgorithms }, - { "ipqos", oIPQoS }, - { "requesttty", oRequestTTY }, -+ { "noneenabled", oNoneEnabled }, -+ { "nonemacenabled", oNoneMacEnabled }, -+ { "noneswitch", oNoneSwitch }, -+ { "disablemtaes", oDisableMTAES }, - { "sessiontype", oSessionType }, - { "stdinnull", oStdinNull }, - { "forkafterauthentication", oForkAfterAuthentication }, -@@ -340,6 +348,11 @@ - { "rsaminsize", oRequiredRSASize }, /* alias */ - { "enableescapecommandline", oEnableEscapeCommandline }, - -+ { "tcprcvbufpoll", oTcpRcvBufPoll }, -+ { "tcprcvbuf", oTcpRcvBuf }, -+ { "hpndisabled", oHPNDisabled }, -+ { "hpnbuffersize", oHPNBufferSize }, -+ - { NULL, oBadOption } - }; - -@@ -1182,6 +1195,46 @@ - intptr = &options->check_host_ip; - goto parse_flag; - -+ case oHPNDisabled: -+ intptr = &options->hpn_disabled; -+ goto parse_flag; -+ -+ case oHPNBufferSize: -+ intptr = &options->hpn_buffer_size; -+ goto parse_int; -+ -+ case oTcpRcvBufPoll: -+ intptr = &options->tcp_rcv_buf_poll; -+ goto parse_flag; -+ -+ case oNoneEnabled: -+ intptr = &options->none_enabled; -+ goto parse_flag; -+ -+ case oNoneMacEnabled: -+ intptr = &options->nonemac_enabled; -+ goto parse_flag; -+ -+ case oDisableMTAES: -+ intptr = &options->disable_multithreaded; -+ goto parse_flag; -+ -+ /* -+ * We check to see if the command comes from the command -+ * line or not. If it does then enable it otherwise fail. -+ * NONE should never be a default configuration. -+ */ -+ case oNoneSwitch: -+ if (strcmp(filename, "command-line") == 0) { -+ intptr = &options->none_switch; -+ goto parse_flag; -+ } else { -+ error("NoneSwitch is found in %.200s.\nYou may only use this configuration option from the command line", filename); -+ error("Continuing..."); -+ debug("NoneSwitch directive found in %.200s.", filename); -+ return 0; -+ } -+ - case oVerifyHostKeyDNS: - intptr = &options->verify_host_key_dns; - multistate_ptr = multistate_yesnoask; -@@ -1436,6 +1489,10 @@ - *intptr = value; - break; - -+ case oTcpRcvBuf: -+ intptr = &options->tcp_rcv_buf; -+ goto parse_int; -+ - case oCiphers: - arg = argv_next(&ac, &av); - if (!arg || *arg == '\0') { -@@ -2476,6 +2533,14 @@ - options->ip_qos_interactive = -1; - options->ip_qos_bulk = -1; - options->request_tty = -1; -+ options->none_switch = -1; -+ options->none_enabled = -1; -+ options->nonemac_enabled = -1; -+ options->disable_multithreaded = -1; -+ options->hpn_disabled = -1; -+ options->hpn_buffer_size = -1; -+ options->tcp_rcv_buf_poll = -1; -+ options->tcp_rcv_buf = -1; - options->session_type = -1; - options->stdin_null = -1; - options->fork_after_authentication = -1; -@@ -2653,6 +2718,43 @@ - options->server_alive_interval = 0; - if (options->server_alive_count_max == -1) - options->server_alive_count_max = 3; -+ if (options->hpn_disabled == -1) -+ options->hpn_disabled = 0; -+ if (options->hpn_buffer_size > -1) { -+ /* if a user tries to set the size to 0 set it to 1KB */ -+ if (options->hpn_buffer_size == 0) -+ options->hpn_buffer_size = 1; -+ /* limit the buffer to SSHBUF_SIZE_MAX (currently 256MB) */ -+ if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { -+ options->hpn_buffer_size = SSHBUF_SIZE_MAX; -+ debug("User requested buffer larger than 256MB. Request reverted to 256MB"); -+ } else -+ options->hpn_buffer_size *= 1024; -+ debug("hpn_buffer_size set to %d", options->hpn_buffer_size); -+ } -+ if (options->tcp_rcv_buf == 0) -+ options->tcp_rcv_buf = 1; -+ if (options->tcp_rcv_buf > -1) -+ options->tcp_rcv_buf *=1024; -+ if (options->tcp_rcv_buf_poll == -1) -+ options->tcp_rcv_buf_poll = 1; -+ if (options->none_switch == -1) -+ options->none_switch = 0; -+ if (options->none_enabled == -1) -+ options->none_enabled = 0; -+ if (options->none_enabled == 0 && options->none_switch > 0) { -+ fprintf(stderr, "NoneEnabled must be enabled to use the None Switch option. None cipher disabled.\n"); -+ options->none_enabled = 0; -+ } -+ if (options->nonemac_enabled == -1) -+ options->nonemac_enabled = 0; -+ if (options->nonemac_enabled > 0 && (options->none_enabled == 0 || -+ options->none_switch == 0)) { -+ fprintf(stderr, "None MAC can only be used with the None cipher. None MAC disabled.\n"); -+ options->nonemac_enabled = 0; -+ } -+ if (options->disable_multithreaded == -1) -+ options->disable_multithreaded = 0; - if (options->control_master == -1) - options->control_master = 0; - if (options->control_persist == -1) { -diff -Nur openssh-9.3p1.orig/readconf.h openssh-9.3p1/readconf.h ---- openssh-9.3p1.orig/readconf.h 2023-06-26 17:56:58.806634907 +0200 -+++ openssh-9.3p1/readconf.h 2023-06-26 17:59:25.012036996 +0200 -@@ -56,6 +56,10 @@ - int strict_host_key_checking; /* Strict host key checking. */ - int compression; /* Compress packets in both directions. */ - int tcp_keep_alive; /* Set SO_KEEPALIVE. */ -+ int tcp_rcv_buf; /* user switch to set tcp recv buffer */ -+ int tcp_rcv_buf_poll; /* Option to poll recv buf every window transfer */ -+ int hpn_disabled; /* Switch to disable HPN buffer management */ -+ int hpn_buffer_size; /* User definable size for HPN buffer window */ - int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ - int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ - SyslogFacility log_facility; /* Facility for system logging. */ -@@ -127,7 +131,12 @@ - - int enable_ssh_keysign; - int64_t rekey_limit; -+ int none_switch; /* Use none cipher */ -+ int none_enabled; /* Allow none to be used */ -+ int nonemac_enabled; /* Allow none to be used */ -+ int disable_multithreaded; /*disable multithreaded aes-ctr*/ - int rekey_interval; -+ - int no_host_authentication_for_localhost; - int identities_only; - int server_alive_interval; -diff -Nur openssh-9.3p1.orig/regress/integrity.sh openssh-9.3p1/regress/integrity.sh ---- openssh-9.3p1.orig/regress/integrity.sh 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/regress/integrity.sh 2023-06-26 17:59:25.013036999 +0200 -@@ -21,6 +21,12 @@ - cmd="$SUDO env SSH_SK_HELPER="$SSH_SK_HELPER" sh ${OBJ}/sshd-log-wrapper.sh -i -f $OBJ/sshd_proxy" - - for m in $macs; do -+ # the none mac is now valid but tests against it will succeed when we expect it to -+ # fail. so we need to explicity remove it from the list of macs returned. -+ if [ "$m" = "none" ]; then -+ continue -+ fi -+ - trace "test $tid: mac $m" - elen=0 - epad=0 -diff -Nur openssh-9.3p1.orig/sandbox-seccomp-filter.c openssh-9.3p1/sandbox-seccomp-filter.c ---- openssh-9.3p1.orig/sandbox-seccomp-filter.c 2023-06-26 17:56:58.742634731 +0200 -+++ openssh-9.3p1/sandbox-seccomp-filter.c 2023-06-26 17:59:25.013036999 +0200 -@@ -295,6 +295,9 @@ - #ifdef __NR_geteuid32 - SC_ALLOW(__NR_geteuid32), - #endif -+#ifdef __NR_getpeername /* not defined on archs that go via socketcall(2) */ -+ SC_ALLOW(__NR_getpeername), -+#endif - #ifdef __NR_getpgid - SC_ALLOW(__NR_getpgid), - #endif -@@ -413,6 +416,9 @@ - #ifdef __NR_sigprocmask - SC_ALLOW(__NR_sigprocmask), - #endif -+#ifdef __NR_socketcall -+ SC_ALLOW(__NR_socketcall), -+#endif - #ifdef __NR_time - SC_ALLOW(__NR_time), - #endif -diff -Nur openssh-9.3p1.orig/scp.c openssh-9.3p1/scp.c ---- openssh-9.3p1.orig/scp.c 2023-06-26 17:56:58.782634841 +0200 -+++ openssh-9.3p1/scp.c 2023-06-26 17:59:25.015037005 +0200 -@@ -1670,7 +1670,7 @@ - off_t size, statbytes; - unsigned long long ull; - int setimes, targisdir, wrerr; -- char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; -+ char ch, *cp, *np, *targ, *why, *vect[1], buf[16384], visbuf[16384]; - char **patterns = NULL; - size_t n, npatterns = 0; - struct timeval tv[2]; -diff -Nur openssh-9.3p1.orig/servconf.c openssh-9.3p1/servconf.c ---- openssh-9.3p1.orig/servconf.c 2023-06-26 17:56:58.807634910 +0200 -+++ openssh-9.3p1/servconf.c 2023-06-26 17:59:25.016037008 +0200 -@@ -70,6 +70,7 @@ - #include "auth.h" - #include "myproposal.h" - #include "digest.h" -+#include "sshbuf.h" - #include "ssh-gss.h" - - static void add_listen_addr(ServerOptions *, const char *, -@@ -200,6 +201,12 @@ - options->authorized_principals_file = NULL; - options->authorized_principals_command = NULL; - options->authorized_principals_command_user = NULL; -+ options->tcp_rcv_buf_poll = -1; -+ options->hpn_disabled = -1; -+ options->hpn_buffer_size = -1; -+ options->none_enabled = -1; -+ options->nonemac_enabled = -1; -+ options->disable_multithreaded = -1; - options->ip_qos_interactive = -1; - options->ip_qos_bulk = -1; - options->version_addendum = NULL; -@@ -294,6 +301,10 @@ - fill_default_server_options(ServerOptions *options) - { - u_int i; -+ /* needed for hpn socket tests */ -+ int sock; -+ int socksize; -+ int socksizelen = sizeof(int); - - /* Portable-specific options */ - if (options->use_pam == -1) -@@ -465,6 +476,51 @@ - } - if (options->permit_tun == -1) - options->permit_tun = SSH_TUNMODE_NO; -+ if (options->none_enabled == -1) -+ options->none_enabled = 0; -+ if (options->nonemac_enabled == -1) -+ options->nonemac_enabled = 0; -+ if (options->nonemac_enabled > 0 && options->none_enabled == 0) { -+ debug ("Attempted to enabled None MAC without setting None Enabled to true. None MAC disabled."); -+ options->nonemac_enabled = 0; -+ } -+ if (options->disable_multithreaded == -1) -+ options->disable_multithreaded = 0; -+ if (options->hpn_disabled == -1) -+ options->hpn_disabled = 0; -+ -+ if (options->hpn_buffer_size == -1) { -+ /* option not explicitly set. Now we have to figure out */ -+ /* what value to use */ -+ if (options->hpn_disabled == 1) { -+ options->hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; -+ } else { -+ /* get the current RCV size and set it to that */ -+ /*create a socket but don't connect it */ -+ /* we use that the get the rcv socket size */ -+ sock = socket(AF_INET, SOCK_STREAM, 0); -+ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, -+ &socksize, &socksizelen); -+ close(sock); -+ options->hpn_buffer_size = socksize; -+ debug("HPN Buffer Size: %d", options->hpn_buffer_size); -+ } -+ } else { -+ /* we have to do this in case the user sets both values in a contradictory */ -+ /* manner. hpn_disabled overrrides hpn_buffer_size*/ -+ if (options->hpn_disabled <= 0) { -+ if (options->hpn_buffer_size == 0) -+ options->hpn_buffer_size = 1; -+ /* limit the maximum buffer to SSHBUF_SIZE_MAX (currently 256MB) */ -+ if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { -+ options->hpn_buffer_size = SSHBUF_SIZE_MAX; -+ } else { -+ options->hpn_buffer_size *= 1024; -+ } -+ } else -+ options->hpn_buffer_size = CHAN_TCP_WINDOW_DEFAULT; -+ } -+ - if (options->ip_qos_interactive == -1) - options->ip_qos_interactive = IPTOS_DSCP_AF21; - if (options->ip_qos_bulk == -1) -@@ -546,6 +602,9 @@ - sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication, - sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, - sPrintMotd, sPrintLastLog, sIgnoreRhosts, -+ sNoneEnabled, sNoneMacEnabled, -+ sDisableMTAES, -+ sTcpRcvBufPoll, sHPNDisabled, sHPNBufferSize, - sX11Forwarding, sX11DisplayOffset, sX11MaxDisplays, sX11UseLocalhost, - sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive, - sPermitUserEnvironment, sAllowTcpForwarding, sCompression, -@@ -747,6 +806,12 @@ - { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, - { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, - { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, -+ { "hpndisabled", sHPNDisabled, SSHCFG_ALL }, -+ { "hpnbuffersize", sHPNBufferSize, SSHCFG_ALL }, -+ { "tcprcvbufpoll", sTcpRcvBufPoll, SSHCFG_ALL }, -+ { "noneenabled", sNoneEnabled, SSHCFG_ALL }, -+ { "disableMTAES", sDisableMTAES, SSHCFG_ALL }, -+ { "nonemacenabled", sNoneMacEnabled, SSHCFG_ALL }, - { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, - { "include", sInclude, SSHCFG_ALL }, - { "ipqos", sIPQoS, SSHCFG_ALL }, -@@ -809,6 +874,7 @@ - - for (i = 0; keywords[i].name; i++) - if (strcasecmp(cp, keywords[i].name) == 0) { -+ debug("Config token is %s", keywords[i].name); - *flags = keywords[i].flags; - return keywords[i].opcode; - } -@@ -1636,12 +1702,36 @@ - multistate_ptr = multistate_ignore_rhosts; - goto parse_multistate; - -+ case sTcpRcvBufPoll: -+ intptr = &options->tcp_rcv_buf_poll; -+ goto parse_flag; -+ -+ case sHPNDisabled: -+ intptr = &options->hpn_disabled; -+ goto parse_flag; -+ -+ case sHPNBufferSize: -+ intptr = &options->hpn_buffer_size; -+ goto parse_int; -+ - case sIgnoreUserKnownHosts: - intptr = &options->ignore_user_known_hosts; - parse_flag: - multistate_ptr = multistate_flag; - goto parse_multistate; - -+ case sNoneEnabled: -+ intptr = &options->none_enabled; -+ goto parse_flag; -+ -+ case sNoneMacEnabled: -+ intptr = &options->nonemac_enabled; -+ goto parse_flag; -+ -+ case sDisableMTAES: -+ intptr = &options->disable_multithreaded; -+ goto parse_flag; -+ - case sHostbasedAuthentication: - intptr = &options->hostbased_authentication; - goto parse_flag; -diff -Nur openssh-9.3p1.orig/servconf.h openssh-9.3p1/servconf.h ---- openssh-9.3p1.orig/servconf.h 2023-06-26 17:56:58.808634912 +0200 -+++ openssh-9.3p1/servconf.h 2023-06-26 17:59:25.017037010 +0200 -@@ -214,6 +214,13 @@ - int use_pam; /* Enable auth via PAM */ - int permit_pam_user_change; /* Allow PAM to change user name */ - -+ int tcp_rcv_buf_poll; /* poll tcp rcv window in autotuning kernels*/ -+ int hpn_disabled; /* disable hpn functionality. false by default */ -+ int hpn_buffer_size; /* set the hpn buffer size - default 3MB */ -+ int none_enabled; /* Enable NONE cipher switch */ -+ int disable_multithreaded; /*disable multithreaded aes-ctr cipher */ -+ int nonemac_enabled; /* Enable NONE MAC switch */ -+ - int permit_tun; - - char **permitted_opens; /* May also be one of PERMITOPEN_* */ -diff -Nur openssh-9.3p1.orig/serverloop.c openssh-9.3p1/serverloop.c ---- openssh-9.3p1.orig/serverloop.c 2023-06-26 17:56:58.784634846 +0200 -+++ openssh-9.3p1/serverloop.c 2023-06-26 17:59:25.018037013 +0200 -@@ -347,6 +347,7 @@ - sigset_t bsigset, osigset; - - debug("Entering interactive session for SSH2."); -+ ssh->start_time = monotime_double(); - - if (sigemptyset(&bsigset) == -1 || sigaddset(&bsigset, SIGCHLD) == -1) - error_f("bsigset setup: %s", strerror(errno)); -@@ -386,6 +387,7 @@ - - if (received_sigterm) { - logit("Exiting on signal %d", (int)received_sigterm); -+ sshpkt_final_log_entry(ssh); - /* Clean up sessions, utmp, etc. */ - cleanup_exit(255); - } -@@ -406,6 +408,9 @@ - /* free all channels, no more reads and writes */ - channel_free_all(ssh); - -+ /* final entry must come after channels close -cjr */ -+ sshpkt_final_log_entry(ssh); -+ - /* free remaining sessions, e.g. remove wtmp entries */ - session_destroy_all(ssh, NULL); - } -@@ -556,7 +561,8 @@ - debug("Tunnel forwarding using interface %s", ifname); - - c = channel_new(ssh, "tun", SSH_CHANNEL_OPEN, sock, sock, -1, -- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); -+ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, -+ CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); - c->datagram = 1; - #if defined(SSH_TUN_FILTER) - if (mode == SSH_TUNMODE_POINTOPOINT) -@@ -607,6 +613,8 @@ - c = channel_new(ssh, "session", SSH_CHANNEL_LARVAL, - -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, - 0, "server-session", 1); -+ if ((options.tcp_rcv_buf_poll) && (!options.hpn_disabled)) -+ c->dynamic_window = 1; - if (session_open(the_authctxt, c->self) != 1) { - debug("session open failed, free channel %d", c->self); - channel_free(ssh, c); -diff -Nur openssh-9.3p1.orig/session.c openssh-9.3p1/session.c ---- openssh-9.3p1.orig/session.c 2023-06-26 17:56:58.732634703 +0200 -+++ openssh-9.3p1/session.c 2023-07-05 10:50:50.184150123 +0200 -@@ -226,6 +226,7 @@ - goto authsock_err; - - /* Allocate a channel for the authentication agent socket. */ -+ /* this shouldn't matter if its hpn or not - cjr */ - nc = channel_new(ssh, "auth-listener", - SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, -@@ -2369,7 +2370,8 @@ - channel_set_fds(ssh, s->chanid, - fdout, fdin, fderr, - ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, -- 1, is_tty, CHAN_SES_WINDOW_DEFAULT); -+ 1, is_tty, -+ options.hpn_disabled ? CHAN_SES_WINDOW_DEFAULT : options.hpn_buffer_size); - } - - /* -diff -Nur openssh-9.3p1.orig/sftp.1 openssh-9.3p1/sftp.1 ---- openssh-9.3p1.orig/sftp.1 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/sftp.1 2023-06-26 17:59:25.021037021 +0200 -@@ -299,7 +299,8 @@ - Specify how many requests may be outstanding at any one time. - Increasing this may slightly improve file transfer speed - but will increase memory usage. --The default is 64 outstanding requests. -+The default is 256 outstanding requests providing for 8MB -+of outstanding data with a 32KB buffer. - .It Fl r - Recursively copy entire directories when uploading and downloading. - Note that -diff -Nur openssh-9.3p1.orig/sftp-client.c openssh-9.3p1/sftp-client.c ---- openssh-9.3p1.orig/sftp-client.c 2023-06-26 17:56:58.702634621 +0200 -+++ openssh-9.3p1/sftp-client.c 2023-06-26 18:01:56.954454674 +0200 -@@ -72,7 +72,7 @@ - #define DEFAULT_COPY_BUFLEN 32768 - - /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */ --#define DEFAULT_NUM_REQUESTS 64 -+#define DEFAULT_NUM_REQUESTS 256 - - /* Minimum amount of data to read at a time */ - #define MIN_READ_SIZE 512 -diff -Nur openssh-9.3p1.orig/ssh_api.c openssh-9.3p1/ssh_api.c ---- openssh-9.3p1.orig/ssh_api.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/ssh_api.c 2023-06-26 17:59:25.024037030 +0200 -@@ -410,7 +410,7 @@ - char *cp; - int r; - -- if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0) -+ if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_RELEASE)) != 0) - return r; - if ((r = sshbuf_putb(ssh_packet_get_output(ssh), banner)) != 0) - return r; -diff -Nur openssh-9.3p1.orig/sshbuf.h openssh-9.3p1/sshbuf.h ---- openssh-9.3p1.orig/sshbuf.h 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1/sshbuf.h 2023-06-26 17:59:25.024037030 +0200 -@@ -28,7 +28,7 @@ - # endif /* OPENSSL_HAS_ECC */ - #endif /* WITH_OPENSSL */ - --#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size */ -+#define SSHBUF_SIZE_MAX 0xF000000 /* Hard maximum size 256MB */ - #define SSHBUF_REFS_MAX 0x100000 /* Max child buffers */ - #define SSHBUF_MAX_BIGNUM (16384 / 8) /* Max bignum *bytes* */ - #define SSHBUF_MAX_ECPOINT ((528 * 2 / 8) + 1) /* Max EC point *bytes* */ -diff -Nur openssh-9.3p1.orig/ssh.c openssh-9.3p1/ssh.c ---- openssh-9.3p1.orig/ssh.c 2023-06-26 17:56:58.809634915 +0200 -+++ openssh-9.3p1/ssh.c 2023-06-26 17:59:25.025037033 +0200 -@@ -1069,6 +1069,10 @@ - break; - case 'T': - options.request_tty = REQUEST_TTY_NO; -+ /* ensure that the user doesn't try to backdoor a */ -+ /* null cipher switch on an interactive session */ -+ /* so explicitly disable it no matter what */ -+ options.none_switch=0; - break; - case 'o': - line = xstrdup(optarg); -@@ -1815,6 +1819,8 @@ - setproctitle("%s [mux]", options.control_path); - } - -+extern const EVP_CIPHER *evp_aes_ctr_mt(void); -+ - /* Do fork() after authentication. Used by "ssh -f" */ - static void - fork_postauth(void) -@@ -2132,6 +2138,79 @@ - NULL, fileno(stdin), command, environ); - } - -+static void -+hpn_options_init(struct ssh *ssh) -+{ -+ /* -+ * We need to check to see if what they want to do about buffer -+ * sizes here. In a hpn to nonhpn connection we want to limit -+ * the window size to something reasonable in case the far side -+ * has the large window bug. In hpn to hpn connection we want to -+ * use the max window size but allow the user to override it -+ * lastly if they disabled hpn then use the ssh std window size. -+ * -+ * So why don't we just do a getsockopt() here and set the -+ * ssh window to that? In the case of a autotuning receive -+ * window the window would get stuck at the initial buffer -+ * size generally less than 96k. Therefore we need to set the -+ * maximum ssh window size to the maximum hpn buffer size -+ * unless the user has specifically set the tcprcvbufpoll -+ * to no. In which case we *can* just set the window to the -+ * minimum of the hpn buffer size and tcp receive buffer size. -+ */ -+ -+ if (tty_flag) -+ options.hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; -+ else -+ options.hpn_buffer_size = 2 * 1024 * 1024; -+ -+ if (ssh->compat & SSH_BUG_LARGEWINDOW) { -+ debug("HPN to Non-HPN connection"); -+ } else { -+ debug("HPN to HPN connection"); -+ int sock, socksize; -+ socklen_t socksizelen; -+ if (options.tcp_rcv_buf_poll <= 0) { -+ sock = socket(AF_INET, SOCK_STREAM, 0); -+ socksizelen = sizeof(socksize); -+ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, -+ &socksize, &socksizelen); -+ close(sock); -+ debug("socksize %d", socksize); -+ options.hpn_buffer_size = socksize; -+ debug("HPNBufferSize set to TCP RWIN: %d", options.hpn_buffer_size); -+ } else { -+ if (options.tcp_rcv_buf > 0) { -+ /* -+ * Create a socket but don't connect it: -+ * we use that the get the rcv socket size -+ */ -+ sock = socket(AF_INET, SOCK_STREAM, 0); -+ /* -+ * If they are using the tcp_rcv_buf option, -+ * attempt to set the buffer size to that. -+ */ -+ if (options.tcp_rcv_buf) { -+ socksizelen = sizeof(options.tcp_rcv_buf); -+ setsockopt(sock, SOL_SOCKET, SO_RCVBUF, -+ &options.tcp_rcv_buf, socksizelen); -+ } -+ socksizelen = sizeof(socksize); -+ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, -+ &socksize, &socksizelen); -+ close(sock); -+ debug("socksize %d", socksize); -+ options.hpn_buffer_size = socksize; -+ debug("HPNBufferSize set to user TCPRcvBuf: %d", options.hpn_buffer_size); -+ } -+ } -+ } -+ -+ debug("Final hpn_buffer_size = %d", options.hpn_buffer_size); -+ -+ channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); -+} -+ - /* open new channel for a session */ - static int - ssh_session2_open(struct ssh *ssh) -@@ -2150,9 +2229,11 @@ - if (in == -1 || out == -1 || err == -1) - fatal("dup() in/out/err failed"); - -- window = CHAN_SES_WINDOW_DEFAULT; -+ window = options.hpn_buffer_size; -+ - packetmax = CHAN_SES_PACKET_DEFAULT; - if (tty_flag) { -+ window = CHAN_SES_WINDOW_DEFAULT; - window >>= 1; - packetmax >>= 1; - } -@@ -2163,6 +2244,11 @@ - - debug3_f("channel_new: %d", c->self); - -+ if (options.tcp_rcv_buf_poll > 0 && !options.hpn_disabled) { -+ c->dynamic_window = 1; -+ debug("Enabled Dynamic Window Scaling"); -+ } -+ - channel_send_open(ssh, c->self); - if (options.session_type != SESSION_TYPE_NONE) - channel_register_open_confirm(ssh, c->self, -@@ -2177,6 +2263,13 @@ - int r, id = -1; - char *cp, *tun_fwd_ifname = NULL; - -+ /* -+ * We need to initialize this early because the forwarding logic below -+ * might open channels that use the hpn buffer sizes. We can't send a -+ * window of -1 (the default) to the server as it breaks things. -+ */ -+ hpn_options_init(ssh); -+ - /* XXX should be pre-session */ - if (!options.control_persist) - ssh_init_stdio_forwarding(ssh); -diff -Nur openssh-9.3p1.orig/sshconnect2.c openssh-9.3p1/sshconnect2.c ---- openssh-9.3p1.orig/sshconnect2.c 2023-06-26 17:56:58.811634921 +0200 -+++ openssh-9.3p1/sshconnect2.c 2023-07-05 04:58:43.822783207 +0200 -@@ -76,6 +76,7 @@ - #include "utf8.h" - #include "ssh-sk.h" - #include "sk-api.h" -+#include "myproposal.h" - - #ifdef GSSAPI - #include "ssh-gss.h" -@@ -85,6 +86,13 @@ - extern Options options; - - /* -+ * tty_flag is set in ssh.c. Use this in ssh_userauth2: -+ * if it is set, then prevent the switch to the null cipher. -+ */ -+ -+extern int tty_flag; -+ -+/* - * SSH2 key exchange - */ - -@@ -216,6 +224,8 @@ - return ret; - } - -+static char *myproposal[PROPOSAL_MAX]; -+static const char *myproposal_default[PROPOSAL_MAX] = { KEX_CLIENT }; - void - ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, - const struct ssh_conn_info *cinfo) -@@ -229,6 +239,8 @@ - char *gss_host = NULL; - #endif - -+ memcpy(&myproposal, &myproposal_default, sizeof(myproposal)); -+ - xxx_host = host; - xxx_hostaddr = hostaddr; - xxx_conn_info = cinfo; -@@ -577,6 +589,53 @@ - - if (!authctxt.success) - fatal("Authentication failed."); -+ -+ /* -+ * If the user wants to use the none cipher and/or none mac, do it post authentication -+ * and only if the right conditions are met -- both of the NONE commands -+ * must be true and there must be no tty allocated. -+ */ -+ if (options.none_switch == 1 && options.none_enabled == 1) { -+ if (!tty_flag) { /* no null on tty sessions */ -+ debug("Requesting none rekeying..."); -+ memcpy(&myproposal, &myproposal_default, sizeof(myproposal)); -+ myproposal[PROPOSAL_ENC_ALGS_STOC] = "none"; -+ myproposal[PROPOSAL_ENC_ALGS_CTOS] = "none"; -+ fprintf(stderr, "WARNING: ENABLED NONE CIPHER!!!\n"); -+ /* NONEMAC can only be used in context of the NONE CIPHER */ -+ if (options.nonemac_enabled == 1) { -+ myproposal[PROPOSAL_MAC_ALGS_STOC] = "none"; -+ myproposal[PROPOSAL_MAC_ALGS_CTOS] = "none"; -+ fprintf(stderr, "WARNING: ENABLED NONE MAC\n"); -+ } -+ kex_prop2buf(ssh->kex->my, myproposal); -+ packet_request_rekeying(); -+ } else { -+ /* requested NONE cipher when in a tty */ -+ debug("Cannot switch to NONE cipher with tty allocated"); -+ fprintf(stderr, "NONE cipher switch disabled when a TTY is allocated\n"); -+ } -+ } -+ -+#ifdef WITH_OPENSSL -+ if (options.disable_multithreaded == 0) { -+ /* if we are using aes-ctr there can be issues in either a fork or sandbox -+ * so the initial aes-ctr is defined to point to the original single process -+ * evp. After authentication we'll be past the fork and the sandboxed privsep -+ * so we repoint the define to the multithreaded evp. To start the threads we -+ * then force a rekey -+ */ -+ const void *cc = ssh_packet_get_send_context(ssh); -+ -+ /* only do this for the ctr cipher. otherwise gcm mode breaks. Don't know why though */ -+ if (strstr(cipher_ctx_name(cc), "ctr")) { -+ debug("Single to Multithread CTR cipher swap - client request"); -+ cipher_reset_multithreaded(); -+ packet_request_rekeying(); -+ } -+ } -+#endif -+ - if (ssh_packet_connection_is_on_socket(ssh)) { - verbose("Authenticated to %s ([%s]:%d) using \"%s\".", host, - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), -diff -Nur openssh-9.3p1.orig/sshconnect.c openssh-9.3p1/sshconnect.c ---- openssh-9.3p1.orig/sshconnect.c 2023-06-26 17:56:58.650634478 +0200 -+++ openssh-9.3p1/sshconnect.c 2023-06-26 17:59:25.028037041 +0200 -@@ -344,6 +344,30 @@ - #endif - - /* -+ * Set TCP receive buffer if requested. -+ * Note: tuning needs to happen after the socket is -+ * created but before the connection happens -+ * so winscale is negotiated properly -cjr -+ */ -+static void -+ssh_set_socket_recvbuf(int sock) -+{ -+ void *buf = (void *)&options.tcp_rcv_buf; -+ int sz = sizeof(options.tcp_rcv_buf); -+ int socksize; -+ int socksizelen = sizeof(int); -+ -+ debug("setsockopt Attempting to set SO_RCVBUF to %d", options.tcp_rcv_buf); -+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, buf, sz) >= 0) { -+ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &socksize, &socksizelen); -+ debug("setsockopt SO_RCVBUF: %.100s %d", strerror(errno), socksize); -+ } -+ else -+ error("Couldn't set socket receive buffer to %d: %.100s", -+ options.tcp_rcv_buf, strerror(errno)); -+} -+ -+/* - * Creates a socket for use as the ssh connection. - */ - static int -@@ -365,6 +389,9 @@ - } - (void)fcntl(sock, F_SETFD, FD_CLOEXEC); - -+ if (options.tcp_rcv_buf > 0) -+ ssh_set_socket_recvbuf(sock); -+ - /* Use interactive QOS (if specified) until authentication completed */ - if (options.ip_qos_interactive != INT_MAX) - set_sock_tos(sock, options.ip_qos_interactive); -diff -Nur openssh-9.3p1.orig/sshd.c openssh-9.3p1/sshd.c ---- openssh-9.3p1.orig/sshd.c 2023-06-26 17:56:58.812634923 +0200 -+++ openssh-9.3p1/sshd.c 2023-07-05 11:08:30.843125617 +0200 -@@ -1093,6 +1093,8 @@ - int ret, listen_sock; - struct addrinfo *ai; - char ntop[NI_MAXHOST], strport[NI_MAXSERV]; -+ int socksize; -+ int socksizelen = sizeof(int); - - for (ai = la->addrs; ai; ai = ai->ai_next) { - if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) -@@ -1138,6 +1140,11 @@ - - debug("Bind to port %s on %s.", strport, ntop); - -+ getsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, -+ &socksize, &socksizelen); -+ debug("Server TCP RWIN socket size: %d", socksize); -+ debug("HPN Buffer Size: %d", options.hpn_buffer_size); -+ - /* Bind the socket to the desired port. */ - if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) == -1) { - error("Bind to port %s on %s failed: %.200s.", -@@ -1876,6 +1883,19 @@ - /* Fill in default values for those options not explicitly set. */ - fill_default_server_options(&options); - -+ if (options.none_enabled == 1) { -+ char *old_ciphers = options.ciphers; -+ xasprintf(&options.ciphers, "%s,none", old_ciphers); -+ free(old_ciphers); -+ -+ /* only enable the none MAC in context of the none cipher -cjr */ -+ if (options.nonemac_enabled == 1) { -+ char *old_macs = options.macs; -+ xasprintf(&options.macs, "%s,none", old_macs); -+ free(old_macs); -+ } -+ } -+ - /* Check that options are sensible */ - if (options.authorized_keys_command_user == NULL && - (options.authorized_keys_command != NULL && -@@ -2359,6 +2379,9 @@ - rdomain == NULL ? "" : "\""); - free(laddr); - -+ /* set the HPN options for the child */ -+ channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); -+ - /* - * We don't want to listen forever unless the other side - * successfully authenticates itself. So we set up an alarm which is -@@ -2471,6 +2494,25 @@ - /* Try to send all our hostkeys to the client */ - notify_hostkeys(ssh); - -+#ifdef WITH_OPENSSL -+ if (options.disable_multithreaded == 0) { -+ /* if we are using aes-ctr there can be issues in either a fork or sandbox -+ * so the initial aes-ctr is defined to point ot the original single process -+ * evp. After authentication we'll be past the fork and the sandboxed privsep -+ * so we repoint the define to the multithreaded evp. To start the threads we -+ * then force a rekey -+ */ -+ const void *cc = ssh_packet_get_send_context(the_active_state); -+ -+ /* only rekey if necessary. If we don't do this gcm mode cipher breaks */ -+ if (strstr(cipher_ctx_name(cc), "ctr")) { -+ debug("Single to Multithreaded CTR cipher swap - server request"); -+ cipher_reset_multithreaded(); -+ packet_request_rekeying(); -+ } -+ } -+#endif -+ - /* Start session. */ - do_authenticated(ssh, authctxt); - -@@ -2545,6 +2587,11 @@ - struct kex *kex; - int r; - -+ if (options.none_enabled == 1) -+ debug("WARNING: None cipher enabled"); -+ if (options.nonemac_enabled == 1) -+ debug("WARNING: None MAC enabled"); -+ - if (options.rekey_limit || options.rekey_interval) - ssh_packet_set_rekey_limits(ssh, options.rekey_limit, - options.rekey_interval); -diff -Nur openssh-9.3p1.orig/sshd_config openssh-9.3p1/sshd_config ---- openssh-9.3p1.orig/sshd_config 2023-06-26 17:56:58.813634926 +0200 -+++ openssh-9.3p1/sshd_config 2023-06-26 17:59:25.030037046 +0200 -@@ -127,6 +127,28 @@ - # override default of no subsystems - Subsystem sftp /usr/libexec/sftp-server - -+# the following are HPN related configuration options -+# tcp receive buffer polling. disable in non autotuning kernels -+#TcpRcvBufPoll yes -+ -+# disable hpn performance boosts -+#HPNDisabled no -+ -+# buffer size for hpn to non-hpn connections -+#HPNBufferSize 2048 -+ -+# allow the use of the none cipher -+#NoneEnabled no -+ -+# allow the use of the none MAC -+#NoneMacEnabled no -+ -+# Disable MT-AES-CTR cipher on server -+# * needed for GSI-OpenSSH 7.4p1 because it's broken on the server side there -+# * useful for modern processors with AES-NI instructions making the stock -+# AES-CTR cipher faster than the MT-AES-CTR cipher -+DisableMTAES yes -+ - # Example of overriding settings on a per-user basis - #Match User anoncvs - # X11Forwarding no -diff -Nur openssh-9.3p1.orig/version.h openssh-9.3p1/version.h ---- openssh-9.3p1.orig/version.h 2023-06-26 17:56:58.814634929 +0200 -+++ openssh-9.3p1/version.h 2023-06-26 17:59:25.031037049 +0200 -@@ -16,5 +16,6 @@ - - #define SSH_PORTABLE "p1" - #define GSI_PORTABLE "c-GSI" -+#define SSH_HPN "-hpn15v2" - #define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \ -- GSI_VERSION KRB5_VERSION -+ GSI_VERSION SSH_HPN KRB5_VERSION diff --git a/openssh-9.3p1-hpn-17.13.patch b/openssh-9.3p1-hpn-17.13.patch new file mode 100644 index 0000000..10db813 --- /dev/null +++ b/openssh-9.3p1-hpn-17.13.patch @@ -0,0 +1,13723 @@ +diff -Nur openssh-9.3p1.orig/auth2.c openssh-9.3p1/auth2.c +--- openssh-9.3p1.orig/auth2.c 2024-07-10 09:07:09.801082054 +0200 ++++ openssh-9.3p1/auth2.c 2024-07-10 09:18:45.456118963 +0200 +@@ -52,6 +52,8 @@ + #include "dispatch.h" + #include "pathnames.h" + #include "ssherr.h" ++#include "canohost.h" ++ + #ifdef GSSAPI + #include "ssh-gss.h" + #endif +@@ -74,6 +76,8 @@ + extern Authmethod method_gssapi; + #endif + ++static int log_flag = 0; ++ + Authmethod *authmethods[] = { + &method_none, + &method_pubkey, +@@ -103,6 +107,9 @@ + #define MATCH_PARTIAL 3 /* method matches, submethod can't be checked */ + static int list_starts_with(const char *, const char *, const char *); + ++/* read the user banner from the path in sshd_config ++ * this isn't to read it on the client side but to read ++ * it into what we are going to send on the server side */ + char * + auth2_read_banner(void) + { +@@ -297,6 +304,11 @@ + + debug("userauth-request for user %s service %s method %s", + user[0] ? user : "", service, method); ++ if (!log_flag) { ++ logit("SSH: Server;Ltype: Authname;Remote: %s-%d;Name: %s", ++ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), user); ++ log_flag = 1; ++ } + debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); + + #ifdef WITH_SELINUX +diff -Nur openssh-9.3p1.orig/binn.c openssh-9.3p1/binn.c +--- openssh-9.3p1.orig/binn.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/binn.c 2024-07-10 09:09:55.896568391 +0200 +@@ -0,0 +1,3498 @@ ++#include ++#include ++#include ++#include ++#include ++#include "binn.h" ++ ++#define UNUSED(x) (void)(x) ++#define roundval(dbl) dbl >= 0.0 ? (int)(dbl + 0.5) : ((dbl - (double)(int)dbl) <= -0.5 ? (int)dbl : (int)(dbl - 0.5)) ++ ++// magic number: 0x1F 0xb1 0x22 0x1F => 0x1FB1221F or 0x1F22B11F ++// because the BINN_STORAGE_NOBYTES (binary 000) may not have so many sub-types (BINN_STORAGE_HAS_MORE = 0x10) ++#define BINN_MAGIC 0x1F22B11F ++ ++#define MAX_BINN_HEADER 9 // [1:type][4:size][4:count] ++#define MIN_BINN_SIZE 3 // [1:type][1:size][1:count] ++#define CHUNK_SIZE 256 // 1024 ++ ++#define BINN_STRUCT 1 ++#define BINN_BUFFER 2 ++ ++void* (*malloc_fn)(size_t len) = 0; ++void* (*realloc_fn)(void *ptr, size_t len) = 0; ++void (*free_fn)(void *ptr) = 0; ++ ++/***************************************************************************/ ++ ++#if defined(__alpha__) || defined(__hppa__) || defined(__mips__) || defined(__powerpc__) || defined(__sparc__) ++#define BINN_ONLY_ALIGNED_ACCESS ++#elif ( defined(__arm__) || defined(__aarch64__) ) && !defined(__ARM_FEATURE_UNALIGNED) ++#define BINN_ONLY_ALIGNED_ACCESS ++#endif ++ ++#if defined(_WIN32) ++#define BIG_ENDIAN 0x1000 ++#define LITTLE_ENDIAN 0x0001 ++#define BYTE_ORDER LITTLE_ENDIAN ++#elif defined(__APPLE__) ++/* macros already defined */ ++#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) ++#include ++#elif defined(_AIX) ++#include ++#else ++#include ++#endif ++ ++#ifndef BYTE_ORDER ++#error "BYTE_ORDER not defined" ++#endif ++#ifndef BIG_ENDIAN ++#error "BIG_ENDIAN not defined" ++#endif ++#ifndef LITTLE_ENDIAN ++#error "LITTLE_ENDIAN not defined" ++#endif ++#if BIG_ENDIAN == LITTLE_ENDIAN ++#error "BIG_ENDIAN == LITTLE_ENDIAN" ++#endif ++#if BYTE_ORDER!=BIG_ENDIAN && BYTE_ORDER!=LITTLE_ENDIAN ++#error "BYTE_ORDER not supported" ++#endif ++ ++typedef unsigned short int u16; ++typedef unsigned int u32; ++typedef unsigned long long int u64; ++ ++BINN_PRIVATE void copy_be16(u16 *pdest, u16 *psource) { ++#if BYTE_ORDER == LITTLE_ENDIAN ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ dest[0] = source[1]; ++ dest[1] = source[0]; ++#else // if BYTE_ORDER == BIG_ENDIAN ++#ifdef BINN_ONLY_ALIGNED_ACCESS ++ if ((uintptr_t)psource % 2 == 0){ // address aligned to 16 bit ++ *pdest = *psource; ++ } else { ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ dest[0] = source[0]; // indexes are the same ++ dest[1] = source[1]; ++ } ++#else ++ *pdest = *psource; ++#endif ++#endif ++} ++ ++BINN_PRIVATE void copy_be32(u32 *pdest, u32 *psource) { ++#if BYTE_ORDER == LITTLE_ENDIAN ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ dest[0] = source[3]; ++ dest[1] = source[2]; ++ dest[2] = source[1]; ++ dest[3] = source[0]; ++#else // if BYTE_ORDER == BIG_ENDIAN ++#ifdef BINN_ONLY_ALIGNED_ACCESS ++ if ((uintptr_t)psource % 4 == 0){ // address aligned to 32 bit ++ *pdest = *psource; ++ } else { ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ dest[0] = source[0]; // indexes are the same ++ dest[1] = source[1]; ++ dest[2] = source[2]; ++ dest[3] = source[3]; ++ } ++#else ++ *pdest = *psource; ++#endif ++#endif ++} ++ ++BINN_PRIVATE void copy_be64(u64 *pdest, u64 *psource) { ++#if BYTE_ORDER == LITTLE_ENDIAN ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ int i; ++ for (i=0; i < 8; i++) { ++ dest[i] = source[7-i]; ++ } ++#else // if BYTE_ORDER == BIG_ENDIAN ++#ifdef BINN_ONLY_ALIGNED_ACCESS ++ if ((uintptr_t)psource % 8 == 0){ // address aligned to 64 bit ++ *pdest = *psource; ++ } else { ++ unsigned char *source = (unsigned char *) psource; ++ unsigned char *dest = (unsigned char *) pdest; ++ int i; ++ for (i=0; i < 8; i++) { ++ dest[i] = source[i]; // indexes are the same ++ } ++ } ++#else ++ *pdest = *psource; ++#endif ++#endif ++} ++ ++/***************************************************************************/ ++ ++#ifndef _MSC_VER ++#define stricmp strcasecmp ++#define strnicmp strncasecmp ++#endif ++ ++BINN_PRIVATE BOOL IsValidBinnHeader(void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize); ++ ++/***************************************************************************/ ++ ++char * APIENTRY binn_version() { ++ return BINN_VERSION; ++} ++ ++/***************************************************************************/ ++ ++void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*)) { ++ ++ malloc_fn = new_malloc; ++ realloc_fn = new_realloc; ++ free_fn = new_free; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE void check_alloc_functions() { ++ ++ if (malloc_fn == 0) malloc_fn = &malloc; ++ if (realloc_fn == 0) realloc_fn = &realloc; ++ if (free_fn == 0) free_fn = &free; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE void * binn_malloc(int size) { ++ check_alloc_functions(); ++ return malloc_fn(size); ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE void * binn_memdup(void *src, int size) { ++ void *dest; ++ ++ if (src == NULL || size <= 0) return NULL; ++ dest = binn_malloc(size); ++ if (dest == NULL) return NULL; ++ memcpy(dest, src, size); ++ return dest; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE size_t strlen2(char *str) { ++ ++ if (str == NULL) return 0; ++ return strlen(str); ++ ++} ++ ++/***************************************************************************/ ++ ++int APIENTRY binn_create_type(int storage_type, int data_type_index) { ++ if (data_type_index < 0) return -1; ++ if ((storage_type < BINN_STORAGE_MIN) || (storage_type > BINN_STORAGE_MAX)) return -1; ++ if (data_type_index < 16) ++ return storage_type | data_type_index; ++ else if (data_type_index < 4096) { ++ storage_type |= BINN_STORAGE_HAS_MORE; ++ storage_type <<= 8; ++ data_type_index >>= 4; ++ return storage_type | data_type_index; ++ } else ++ return -1; ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type) { ++ int storage_type, extra_type; ++ BOOL retval=TRUE; ++ ++again: ++ ++ if (long_type < 0) { ++ goto loc_invalid; ++ } else if (long_type <= 0xff) { ++ storage_type = long_type & BINN_STORAGE_MASK; ++ extra_type = long_type & BINN_TYPE_MASK; ++ } else if (long_type <= 0xffff) { ++ storage_type = long_type & BINN_STORAGE_MASK16; ++ storage_type >>= 8; ++ extra_type = long_type & BINN_TYPE_MASK16; ++ extra_type >>= 4; ++ } else if (long_type & BINN_STORAGE_VIRTUAL) { ++ //storage_type = BINN_STORAGE_VIRTUAL; ++ //extra_type = xxx; ++ long_type &= 0xffff; ++ goto again; ++ } else { ++loc_invalid: ++ storage_type = -1; ++ extra_type = -1; ++ retval = FALSE; ++ } ++ ++ if (pstorage_type) *pstorage_type = storage_type; ++ if (pextra_type) *pextra_type = extra_type; ++ ++ return retval; ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_create(binn *item, int type, int size, void *pointer) { ++ BOOL retval=FALSE; ++ ++ switch (type) { ++ case BINN_LIST: ++ case BINN_MAP: ++ case BINN_OBJECT: ++ break; ++ default: ++ goto loc_exit; ++ } ++ ++ if ((item == NULL) || (size < 0)) goto loc_exit; ++ if (size < MIN_BINN_SIZE) { ++ if (pointer) goto loc_exit; ++ else size = 0; ++ } ++ ++ memset(item, 0, sizeof(binn)); ++ ++ if (pointer) { ++ item->pre_allocated = TRUE; ++ item->pbuf = pointer; ++ item->alloc_size = size; ++ } else { ++ item->pre_allocated = FALSE; ++ if (size == 0) size = CHUNK_SIZE; ++ pointer = binn_malloc(size); ++ if (pointer == 0) return INVALID_BINN; ++ item->pbuf = pointer; ++ item->alloc_size = size; ++ } ++ ++ item->header = BINN_MAGIC; ++ //item->allocated = FALSE; -- already zeroed ++ item->writable = TRUE; ++ item->used_size = MAX_BINN_HEADER; // save space for the header ++ item->type = type; ++ //item->count = 0; -- already zeroed ++ item->dirty = TRUE; // the header is not written to the buffer ++ ++ retval = TRUE; ++ ++loc_exit: ++ return retval; ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_new(int type, int size, void *pointer) { ++ binn *item; ++ ++ item = (binn*) binn_malloc(sizeof(binn)); ++ ++ if (binn_create(item, type, size, pointer) == FALSE) { ++ free_fn(item); ++ return NULL; ++ } ++ ++ item->allocated = TRUE; ++ return item; ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_create_list(binn *list) { ++ ++ return binn_create(list, BINN_LIST, 0, NULL); ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_create_map(binn *map) { ++ ++ return binn_create(map, BINN_MAP, 0, NULL); ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_create_object(binn *object) { ++ ++ return binn_create(object, BINN_OBJECT, 0, NULL); ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_list() { ++ return binn_new(BINN_LIST, 0, 0); ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_map() { ++ return binn_new(BINN_MAP, 0, 0); ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_object() { ++ return binn_new(BINN_OBJECT, 0, 0); ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_copy(void *old) { ++ int type, count, size, header_size; ++ unsigned char *old_ptr = binn_ptr(old); ++ binn *item; ++ ++ size = 0; ++ if (!IsValidBinnHeader(old_ptr, &type, &count, &size, &header_size)) return NULL; ++ ++ item = binn_new(type, size - header_size + MAX_BINN_HEADER, NULL); ++ if( item ){ ++ unsigned char *dest; ++ dest = ((unsigned char *) item->pbuf) + MAX_BINN_HEADER; ++ memcpy(dest, old_ptr + header_size, size - header_size); ++ item->used_size = MAX_BINN_HEADER + size - header_size; ++ item->count = count; ++ } ++ return item; ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_load(void *data, binn *value) { ++ ++ if ((data == NULL) || (value == NULL)) return FALSE; ++ memset(value, 0, sizeof(binn)); ++ value->header = BINN_MAGIC; ++ //value->allocated = FALSE; -- already zeroed ++ //value->writable = FALSE; ++ ++ if (binn_is_valid(data, &value->type, &value->count, &value->size) == FALSE) return FALSE; ++ value->ptr = data; ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++binn * APIENTRY binn_open(void *data) { ++ binn *item; ++ ++ item = (binn*) binn_malloc(sizeof(binn)); ++ ++ if (binn_load(data, item) == FALSE) { ++ free_fn(item); ++ return NULL; ++ } ++ ++ item->allocated = TRUE; ++ return item; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int binn_get_ptr_type(void *ptr) { ++ ++ if (ptr == NULL) return 0; ++ ++ switch (*(unsigned int *)ptr) { ++ case BINN_MAGIC: ++ return BINN_STRUCT; ++ default: ++ return BINN_BUFFER; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_is_struct(void *ptr) { ++ ++ if (ptr == NULL) return FALSE; ++ ++ if ((*(unsigned int *)ptr) == BINN_MAGIC) { ++ return TRUE; ++ } else { ++ return FALSE; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int CalcAllocation(int needed_size, int alloc_size) { ++ int calc_size; ++ ++ calc_size = alloc_size; ++ while (calc_size < needed_size) { ++ calc_size <<= 1; // same as *= 2 ++ //calc_size += CHUNK_SIZE; -- this is slower than the above line, because there are more reallocations ++ } ++ return calc_size; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL CheckAllocation(binn *item, int add_size) { ++ int alloc_size; ++ void *ptr; ++ ++ if (item->used_size + add_size > item->alloc_size) { ++ if (item->pre_allocated) return FALSE; ++ alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size); ++ ptr = realloc_fn(item->pbuf, alloc_size); ++ if (ptr == NULL) return FALSE; ++ item->pbuf = ptr; ++ item->alloc_size = alloc_size; ++ } ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++#if BYTE_ORDER == BIG_ENDIAN ++ ++BINN_PRIVATE int get_storage_size(int storage_type) { ++ ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ return 0; ++ case BINN_STORAGE_BYTE: ++ return 1; ++ case BINN_STORAGE_WORD: ++ return 2; ++ case BINN_STORAGE_DWORD: ++ return 4; ++ case BINN_STORAGE_QWORD: ++ return 8; ++ default: ++ return 0; ++ } ++ ++} ++ ++#endif ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE unsigned char * AdvanceDataPos(unsigned char *p, unsigned char *plimit) { ++ unsigned char byte; ++ int storage_type, DataSize; ++ ++ if (p > plimit) return 0; ++ ++ byte = *p; p++; ++ storage_type = byte & BINN_STORAGE_MASK; ++ if (byte & BINN_STORAGE_HAS_MORE) p++; ++ ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ //p += 0; ++ break; ++ case BINN_STORAGE_BYTE: ++ p ++; ++ break; ++ case BINN_STORAGE_WORD: ++ p += 2; ++ break; ++ case BINN_STORAGE_DWORD: ++ p += 4; ++ break; ++ case BINN_STORAGE_QWORD: ++ p += 8; ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ if (p > plimit) return 0; ++ DataSize = *((unsigned char*)p); ++ if (DataSize & 0x80) { ++ if (p + sizeof(int) - 1 > plimit) return 0; ++ copy_be32((u32*)&DataSize, (u32*)p); ++ DataSize &= 0x7FFFFFFF; ++ p+=4; ++ } else { ++ p++; ++ } ++ p += DataSize; ++ if (storage_type == BINN_STORAGE_STRING) { ++ p++; // null terminator. ++ } ++ break; ++ case BINN_STORAGE_CONTAINER: ++ if (p > plimit) return 0; ++ DataSize = *((unsigned char*)p); ++ if (DataSize & 0x80) { ++ if (p + sizeof(int) - 1 > plimit) return 0; ++ copy_be32((u32*)&DataSize, (u32*)p); ++ DataSize &= 0x7FFFFFFF; ++ } ++ DataSize--; // remove the type byte already added before ++ p += DataSize; ++ break; ++ default: ++ return 0; ++ } ++ ++ if (p > plimit) return 0; ++ ++ return p; ++ ++} ++ ++/***************************************************************************/ ++ ++/* ++ ++The id can be stored with 1 to 5 bytes ++ ++S = signal bit ++X = bit part of id ++ ++ 0SXX XXXX ++ 100S XXXX + 1 byte ++ 101S XXXX + 2 bytes ++ 110S XXXX + 3 bytes ++ 1110 0000 + 4 bytes ++ ++*/ ++BINN_PRIVATE int read_map_id(unsigned char **pp, unsigned char *plimit) { ++ unsigned char *p, c, sign, type; ++ int id, extra_bytes; ++ ++ p = *pp; ++ ++ c = *p++; ++ ++ if (c & 0x80) { ++ extra_bytes = ((c & 0x60) >> 5) + 1; ++ if (p + extra_bytes > plimit ) { ++ *pp = p + extra_bytes; ++ return 0; ++ } ++ } ++ ++ type = c & 0xE0; ++ sign = c & 0x10; ++ ++ if ((c & 0x80) == 0) { ++ sign = c & 0x40; ++ id = c & 0x3F; ++ } else if (type == 0x80) { ++ id = c & 0x0F; ++ id = (id << 8) | *p++; ++ } else if (type == 0xA0) { ++ id = c & 0x0F; ++ id = (id << 8) | *p++; ++ id = (id << 8) | *p++; ++ } else if (type == 0xC0) { ++ id = c & 0x0F; ++ id = (id << 8) | *p++; ++ id = (id << 8) | *p++; ++ id = (id << 8) | *p++; ++ } else if (type == 0xE0) { ++ copy_be32((u32*)&id, (u32*)p); ++ p += 4; ++ } else { ++ *pp = plimit + 2; ++ return 0; ++ } ++ ++ if (sign) id = -id; ++ ++ *pp = p; ++ ++ return id; ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE unsigned char * SearchForID(unsigned char *p, int header_size, int size, int numitems, int id) { ++ unsigned char *plimit, *base; ++ int i, int32; ++ ++ base = p; ++ plimit = p + size - 1; ++ p += header_size; ++ ++ // search for the ID in all the arguments. ++ for (i = 0; i < numitems; i++) { ++ int32 = read_map_id(&p, plimit); ++ if (p > plimit) break; ++ // Compare if the IDs are equal. ++ if (int32 == id) return p; ++ // xxx ++ p = AdvanceDataPos(p, plimit); ++ if ((p == 0) || (p < base)) break; ++ } ++ ++ return NULL; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE unsigned char * SearchForKey(unsigned char *p, int header_size, int size, int numitems, const char *key) { ++ unsigned char len, *plimit, *base; ++ int i, keylen; ++ ++ base = p; ++ plimit = p + size - 1; ++ p += header_size; ++ ++ keylen = strlen(key); ++ ++ // search for the key in all the arguments. ++ for (i = 0; i < numitems; i++) { ++ len = *((unsigned char *)p); ++ p++; ++ if (p > plimit) break; ++ // Compare if the strings are equal. ++ if (len > 0) { ++ if (strnicmp((char*)p, key, len) == 0) { // note that there is no null terminator here ++ if (keylen == len) { ++ p += len; ++ return p; ++ } ++ } ++ p += len; ++ if (p > plimit) break; ++ } else if (len == keylen) { // in the case of empty string: "" ++ return p; ++ } ++ // xxx ++ p = AdvanceDataPos(p, plimit); ++ if ((p == 0) || (p < base)) break; ++ } ++ ++ return NULL; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size); ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_list_add_raw(binn *item, int type, void *pvalue, int size) { ++ ++ if ((item == NULL) || (item->type != BINN_LIST) || (item->writable == FALSE)) return FALSE; ++ ++ //if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for data_store and data_format. ++ ++ if (AddValue(item, type, pvalue, size) == FALSE) return FALSE; ++ ++ item->count++; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_object_set_raw(binn *item, const char *key, int type, void *pvalue, int size) { ++ unsigned char *p, len; ++ int int32; ++ ++ if ((item == NULL) || (item->type != BINN_OBJECT) || (item->writable == FALSE)) return FALSE; ++ ++ if (key == NULL) return FALSE; ++ int32 = strlen(key); ++ if (int32 > 255) return FALSE; ++ ++ // is the key already in it? ++ p = SearchForKey(item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, key); ++ if (p) return FALSE; ++ ++ // start adding it ++ ++ if (CheckAllocation(item, 1 + int32) == FALSE) return FALSE; // bytes used for the key size and the key itself. ++ ++ p = ((unsigned char *) item->pbuf) + item->used_size; ++ len = int32; ++ *p = len; ++ p++; ++ memcpy(p, key, int32); ++ int32++; // now contains the strlen + 1 byte for the len ++ item->used_size += int32; ++ ++ if (AddValue(item, type, pvalue, size) == FALSE) { ++ item->used_size -= int32; ++ return FALSE; ++ } ++ ++ item->count++; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_map_set_raw(binn *item, int id, int type, void *pvalue, int size) { ++ unsigned char *base, *p, sign; ++ int id_size; ++ ++ if ((item == NULL) || (item->type != BINN_MAP) || (item->writable == FALSE)) return FALSE; ++ ++ // is the ID already in it? ++ p = SearchForID(item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, id); ++ if (p) return FALSE; ++ ++ // start adding it ++ ++ if (CheckAllocation(item, 5) == FALSE) return FALSE; // max 5 bytes used for the id. ++ ++ p = base = ((unsigned char *) item->pbuf) + item->used_size; ++ ++ sign = (id < 0); ++ if (sign) id = -id; ++ ++ if (id <= 0x3F) { ++ *p++ = (sign << 6) | id; ++ } else if (id <= 0xFFF) { ++ *p++ = 0x80 | (sign << 4) | ((id & 0xF00) >> 8); ++ *p++ = id & 0xFF; ++ } else if (id <= 0xFFFFF) { ++ *p++ = 0xA0 | (sign << 4) | ((id & 0xF0000) >> 16); ++ *p++ = (id & 0xFF00) >> 8; ++ *p++ = id & 0xFF; ++ } else if (id <= 0xFFFFFFF) { ++ *p++ = 0xC0 | (sign << 4) | ((id & 0xF000000) >> 24); ++ *p++ = (id & 0xFF0000) >> 16; ++ *p++ = (id & 0xFF00) >> 8; ++ *p++ = id & 0xFF; ++ } else { ++ *p++ = 0xE0; ++ if (sign) id = -id; ++ copy_be32((u32*)p, (u32*)&id); ++ p += 4; ++ } ++ ++ id_size = (p - base); ++ item->used_size += id_size; ++ ++ if (AddValue(item, type, pvalue, size) == FALSE) { ++ item->used_size -= id_size; ++ return FALSE; ++ } ++ ++ item->count++; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE void * compress_int(int *pstorage_type, int *ptype, void *psource) { ++ int storage_type, storage_type2, type, type2=0; ++ int64 vint = 0; ++ uint64 vuint; ++ char *pvalue; ++#if BYTE_ORDER == BIG_ENDIAN ++ int size1, size2; ++#endif ++ ++ storage_type = *pstorage_type; ++ if (storage_type == BINN_STORAGE_BYTE) return psource; ++ ++ type = *ptype; ++ ++ switch (type) { ++ case BINN_INT64: ++ vint = *(int64*)psource; ++ goto loc_signed; ++ case BINN_INT32: ++ vint = *(int*)psource; ++ goto loc_signed; ++ case BINN_INT16: ++ vint = *(short*)psource; ++ goto loc_signed; ++ case BINN_UINT64: ++ vuint = *(uint64*)psource; ++ goto loc_positive; ++ case BINN_UINT32: ++ vuint = *(unsigned int*)psource; ++ goto loc_positive; ++ case BINN_UINT16: ++ vuint = *(unsigned short*)psource; ++ goto loc_positive; ++ } ++ ++loc_signed: ++ ++ if (vint >= 0) { ++ vuint = vint; ++ goto loc_positive; ++ } ++ ++//loc_negative: ++ ++ if (vint >= INT8_MIN) { ++ type2 = BINN_INT8; ++ } else ++ if (vint >= INT16_MIN) { ++ type2 = BINN_INT16; ++ } else ++ if (vint >= INT32_MIN) { ++ type2 = BINN_INT32; ++ } ++ goto loc_exit; ++ ++loc_positive: ++ ++ if (vuint <= UINT8_MAX) { ++ type2 = BINN_UINT8; ++ } else ++ if (vuint <= UINT16_MAX) { ++ type2 = BINN_UINT16; ++ } else ++ if (vuint <= UINT32_MAX) { ++ type2 = BINN_UINT32; ++ } ++ ++loc_exit: ++ ++ pvalue = (char *) psource; ++ ++ if ((type2) && (type2 != type)) { ++ *ptype = type2; ++ storage_type2 = binn_get_write_storage(type2); ++ *pstorage_type = storage_type2; ++#if BYTE_ORDER == BIG_ENDIAN ++ size1 = get_storage_size(storage_type); ++ size2 = get_storage_size(storage_type2); ++ pvalue += (size1 - size2); ++#endif ++ } ++ ++ return pvalue; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int type_family(int type); ++ ++BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size) { ++ int int32, ArgSize, storage_type, extra_type; ++ unsigned char *p; ++ ++ binn_get_type_info(type, &storage_type, &extra_type); ++ ++ if (pvalue == NULL) { ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ if (size == 0) break; // the 2 above are allowed to have 0 length ++ /* fall through */ ++ default: ++ return FALSE; ++ } ++ } ++ ++ if ((type_family(type) == BINN_FAMILY_INT) && (item->disable_int_compression == FALSE)) ++ pvalue = compress_int(&storage_type, &type, pvalue); ++ ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ size = 0; ++ ArgSize = size; ++ break; ++ case BINN_STORAGE_BYTE: ++ size = 1; ++ ArgSize = size; ++ break; ++ case BINN_STORAGE_WORD: ++ size = 2; ++ ArgSize = size; ++ break; ++ case BINN_STORAGE_DWORD: ++ size = 4; ++ ArgSize = size; ++ break; ++ case BINN_STORAGE_QWORD: ++ size = 8; ++ ArgSize = size; ++ break; ++ case BINN_STORAGE_BLOB: ++ if (size < 0) return FALSE; ++ //if (size == 0) ... ++ ArgSize = size + 4; // at least this size ++ break; ++ case BINN_STORAGE_STRING: ++ if (size < 0) return FALSE; ++ if (size == 0) size = strlen2( (char *) pvalue); ++ ArgSize = size + 5; // at least this size ++ break; ++ case BINN_STORAGE_CONTAINER: ++ if (size <= 0) return FALSE; ++ ArgSize = size; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ ArgSize += 2; // at least 2 bytes used for data_type. ++ if (CheckAllocation(item, ArgSize) == FALSE) return FALSE; ++ ++ // Gets the pointer to the next place in buffer ++ p = ((unsigned char *) item->pbuf) + item->used_size; ++ ++ // If the data is not a container, store the data type ++ if (storage_type != BINN_STORAGE_CONTAINER) { ++ if (type > 255) { ++ u16 type16 = type; ++ copy_be16((u16*)p, (u16*)&type16); ++ p += 2; ++ item->used_size += 2; ++ } else { ++ *p = type; ++ p++; ++ item->used_size++; ++ } ++ } ++ ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ // Nothing to do. ++ break; ++ case BINN_STORAGE_BYTE: ++ *((char *) p) = *((char *) pvalue); ++ item->used_size += 1; ++ break; ++ case BINN_STORAGE_WORD: ++ copy_be16((u16*)p, (u16*)pvalue); ++ item->used_size += 2; ++ break; ++ case BINN_STORAGE_DWORD: ++ copy_be32((u32*)p, (u32*)pvalue); ++ item->used_size += 4; ++ break; ++ case BINN_STORAGE_QWORD: ++ copy_be64((u64*)p, (u64*)pvalue); ++ item->used_size += 8; ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ if (size > 127) { ++ int32 = size | 0x80000000; ++ copy_be32((u32*)p, (u32*)&int32); ++ p += 4; ++ item->used_size += 4; ++ } else { ++ *((unsigned char *) p) = size; ++ p++; ++ item->used_size++; ++ } ++ memcpy(p, pvalue, size); ++ if (storage_type == BINN_STORAGE_STRING) { ++ p += size; ++ *((char *) p) = (char) 0; ++ size++; // null terminator ++ } ++ item->used_size += size; ++ break; ++ case BINN_STORAGE_CONTAINER: ++ memcpy(p, pvalue, size); ++ item->used_size += size; ++ break; ++ } ++ ++ item->dirty = TRUE; ++ ++ return TRUE; ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_save_header(binn *item) { ++ unsigned char byte, *p; ++ int int32, size; ++ ++ if (item == NULL) return FALSE; ++ ++#ifndef BINN_DISABLE_SMALL_HEADER ++ ++ p = ((unsigned char *) item->pbuf) + MAX_BINN_HEADER; ++ size = item->used_size - MAX_BINN_HEADER + 3; // at least 3 bytes for the header ++ ++ // write the count ++ if (item->count > 127) { ++ p -= 4; ++ size += 3; ++ int32 = item->count | 0x80000000; ++ copy_be32((u32*)p, (u32*)&int32); ++ } else { ++ p--; ++ *p = (unsigned char) item->count; ++ } ++ ++ // write the size ++ if (size > 127) { ++ p -= 4; ++ size += 3; ++ int32 = size | 0x80000000; ++ copy_be32((u32*)p, (u32*)&int32); ++ } else { ++ p--; ++ *p = (unsigned char) size; ++ } ++ ++ // write the type. ++ p--; ++ *p = (unsigned char) item->type; ++ ++ // set the values ++ item->ptr = p; ++ item->size = size; ++ ++ UNUSED(byte); ++ ++#else ++ ++ p = (unsigned char *) item->pbuf; ++ ++ // write the type. ++ byte = item->type; ++ *p = byte; p++; ++ // write the size ++ int32 = item->used_size | 0x80000000; ++ copy_be32((u32*)p, (u32*)&int32); ++ p+=4; ++ // write the count ++ int32 = item->count | 0x80000000; ++ copy_be32((u32*)p, (u32*)&int32); ++ ++ item->ptr = item->pbuf; ++ item->size = item->used_size; ++ ++#endif ++ ++ item->dirty = FALSE; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++void APIENTRY binn_free(binn *item) { ++ ++ if (item == NULL) return; ++ ++ if ((item->writable) && (item->pre_allocated == FALSE)) { ++ free_fn(item->pbuf); ++ } ++ ++ if (item->freefn) item->freefn(item->ptr); ++ ++ if (item->allocated) { ++ free_fn(item); ++ } else { ++ memset(item, 0, sizeof(binn)); ++ item->header = BINN_MAGIC; ++ } ++ ++} ++ ++/***************************************************************************/ ++// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later ++void * APIENTRY binn_release(binn *item) { ++ void *data; ++ ++ if (item == NULL) return NULL; ++ ++ data = binn_ptr(item); ++ ++ if (data > item->pbuf) { ++ memmove(item->pbuf, data, item->size); ++ data = item->pbuf; ++ } ++ ++ if (item->allocated) { ++ free_fn(item); ++ } else { ++ memset(item, 0, sizeof(binn)); ++ item->header = BINN_MAGIC; ++ } ++ ++ return data; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL IsValidBinnHeader(void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) { ++ unsigned char byte, *p, *plimit=0; ++ int int32, type, size, count; ++ ++ if (pbuf == NULL) return FALSE; ++ ++ p = (unsigned char *) pbuf; ++ ++ if (psize && *psize > 0) { ++ plimit = p + *psize - 1; ++ } ++ ++ // get the type ++ byte = *p; p++; ++ if ((byte & BINN_STORAGE_MASK) != BINN_STORAGE_CONTAINER) return FALSE; ++ if (byte & BINN_STORAGE_HAS_MORE) return FALSE; ++ type = byte; ++ ++ switch (type) { ++ case BINN_LIST: ++ case BINN_MAP: ++ case BINN_OBJECT: ++ break; ++ default: ++ return FALSE; ++ } ++ ++ // get the size ++ if (plimit && p > plimit) return FALSE; ++ int32 = *((unsigned char*)p); ++ if (int32 & 0x80) { ++ if (plimit && p + sizeof(int) - 1 > plimit) return FALSE; ++ copy_be32((u32*)&int32, (u32*)p); ++ int32 &= 0x7FFFFFFF; ++ p+=4; ++ } else { ++ p++; ++ } ++ size = int32; ++ ++ // get the count ++ if (plimit && p > plimit) return FALSE; ++ int32 = *((unsigned char*)p); ++ if (int32 & 0x80) { ++ if (plimit && p + sizeof(int) - 1 > plimit) return FALSE; ++ copy_be32((u32*)&int32, (u32*)p); ++ int32 &= 0x7FFFFFFF; ++ p+=4; ++ } else { ++ p++; ++ } ++ count = int32; ++ ++#if 0 ++ // get the size ++ copy_be32((u32*)&size, (u32*)p); ++ size &= 0x7FFFFFFF; ++ p+=4; ++ ++ // get the count ++ copy_be32((u32*)&count, (u32*)p); ++ count &= 0x7FFFFFFF; ++ p+=4; ++#endif ++ ++ if ((size < MIN_BINN_SIZE) || (count < 0)) return FALSE; ++ ++ // return the values ++ if (ptype) *ptype = type; ++ if (pcount) *pcount = count; ++ if (psize && *psize==0) *psize = size; ++ if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf); ++ return TRUE; ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int binn_buf_type(void *pbuf) { ++ int type; ++ ++ if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN; ++ ++ return type; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int binn_buf_count(void *pbuf) { ++ int nitems; ++ ++ if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0; ++ ++ return nitems; ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE int binn_buf_size(void *pbuf) { ++ int size=0; ++ ++ if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0; ++ ++ return size; ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_ptr(void *ptr) { ++ binn *item; ++ ++ switch (binn_get_ptr_type(ptr)) { ++ case BINN_STRUCT: ++ item = (binn*) ptr; ++ if (item->writable && item->dirty) { ++ binn_save_header(item); ++ } ++ return item->ptr; ++ case BINN_BUFFER: ++ return ptr; ++ default: ++ return NULL; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++int APIENTRY binn_size(void *ptr) { ++ binn *item; ++ ++ switch (binn_get_ptr_type(ptr)) { ++ case BINN_STRUCT: ++ item = (binn*) ptr; ++ if (item->writable && item->dirty) { ++ binn_save_header(item); ++ } ++ return item->size; ++ case BINN_BUFFER: ++ return binn_buf_size(ptr); ++ default: ++ return 0; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++int APIENTRY binn_type(void *ptr) { ++ binn *item; ++ ++ switch (binn_get_ptr_type(ptr)) { ++ case BINN_STRUCT: ++ item = (binn*) ptr; ++ return item->type; ++ case BINN_BUFFER: ++ return binn_buf_type(ptr); ++ default: ++ return -1; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++int APIENTRY binn_count(void *ptr) { ++ binn *item; ++ ++ switch (binn_get_ptr_type(ptr)) { ++ case BINN_STRUCT: ++ item = (binn*) ptr; ++ return item->count; ++ case BINN_BUFFER: ++ return binn_buf_count(ptr); ++ default: ++ return -1; ++ } ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize) { ++ int i, type, count, size, header_size; ++ unsigned char *p, *plimit, *base, len; ++ void *pbuf; ++ ++ pbuf = binn_ptr(ptr); ++ if (pbuf == NULL) return FALSE; ++ ++ // is there an informed size? ++ if (psize && *psize > 0) { ++ size = *psize; ++ } else { ++ size = 0; ++ } ++ ++ if (!IsValidBinnHeader(pbuf, &type, &count, &size, &header_size)) return FALSE; ++ ++ // is there an informed size? ++ if (psize && *psize > 0) { ++ // is it the same as the one in the buffer? ++ if (size != *psize) return FALSE; ++ } ++ // is there an informed count? ++ if (pcount && *pcount > 0) { ++ // is it the same as the one in the buffer? ++ if (count != *pcount) return FALSE; ++ } ++ // is there an informed type? ++ if (ptype && *ptype != 0) { ++ // is it the same as the one in the buffer? ++ if (type != *ptype) return FALSE; ++ } ++ ++ // it could compare the content size with the size informed on the header ++ ++ p = (unsigned char *)pbuf; ++ base = p; ++ plimit = p + size; ++ ++ p += header_size; ++ ++ // process all the arguments. ++ for (i = 0; i < count; i++) { ++ switch (type) { ++ case BINN_OBJECT: ++ // gets the string size (argument name) ++ len = *p; ++ p++; ++ //if (len == 0) goto Invalid; ++ // increment the used space ++ p += len; ++ break; ++ case BINN_MAP: ++ // increment the used space ++ read_map_id(&p, plimit); ++ break; ++ //case BINN_LIST: ++ // break; ++ } ++ // xxx ++ p = AdvanceDataPos(p, plimit); ++ if ((p == 0) || (p < base)) goto Invalid; ++ } ++ ++ if (ptype && *ptype==0) *ptype = type; ++ if (pcount && *pcount==0) *pcount = count; ++ if (psize && *psize==0) *psize = size; ++ return TRUE; ++ ++Invalid: ++ return FALSE; ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize) { ++ ++ if (ptype) *ptype = 0; ++ if (pcount) *pcount = 0; ++ if (psize) *psize = 0; ++ ++ return binn_is_valid_ex(ptr, ptype, pcount, psize); ++ ++} ++ ++/***************************************************************************/ ++/*** INTERNAL FUNCTIONS ****************************************************/ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL GetValue(unsigned char *p, binn *value) { ++ unsigned char byte; ++ int data_type, storage_type; //, extra_type; ++ int DataSize; ++ void *p2; ++ ++ if (value == NULL) return FALSE; ++ memset(value, 0, sizeof(binn)); ++ value->header = BINN_MAGIC; ++ //value->allocated = FALSE; -- already zeroed ++ //value->writable = FALSE; ++ ++ // saves for use with BINN_STORAGE_CONTAINER ++ p2 = p; ++ ++ // read the data type ++ byte = *p; p++; ++ storage_type = byte & BINN_STORAGE_MASK; ++ if (byte & BINN_STORAGE_HAS_MORE) { ++ data_type = byte << 8; ++ byte = *p; p++; ++ data_type |= byte; ++ //extra_type = data_type & BINN_TYPE_MASK16; ++ } else { ++ data_type = byte; ++ //extra_type = byte & BINN_TYPE_MASK; ++ } ++ ++ //value->storage_type = storage_type; ++ value->type = data_type; ++ ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ break; ++ case BINN_STORAGE_BYTE: ++ value->vuint8 = *((unsigned char *) p); ++ value->ptr = p; //value->ptr = &value->vuint8; ++ break; ++ case BINN_STORAGE_WORD: ++ copy_be16((u16*)&value->vint16, (u16*)p); ++ value->ptr = &value->vint16; ++ break; ++ case BINN_STORAGE_DWORD: ++ copy_be32((u32*)&value->vint32, (u32*)p); ++ value->ptr = &value->vint32; ++ break; ++ case BINN_STORAGE_QWORD: ++ copy_be64((u64*)&value->vint64, (u64*)p); ++ value->ptr = &value->vint64; ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ DataSize = *((unsigned char*)p); ++ if (DataSize & 0x80) { ++ copy_be32((u32*)&DataSize, (u32*)p); ++ DataSize &= 0x7FFFFFFF; ++ p+=4; ++ } else { ++ p++; ++ } ++ value->size = DataSize; ++ value->ptr = p; ++ break; ++ case BINN_STORAGE_CONTAINER: ++ value->ptr = p2; // <-- it returns the pointer to the container, not the data ++ if (IsValidBinnHeader(p2, NULL, &value->count, &value->size, NULL) == FALSE) return FALSE; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ // convert the returned value, if needed ++ ++ switch (value->type) { ++ case BINN_TRUE: ++ value->type = BINN_BOOL; ++ value->vbool = TRUE; ++ value->ptr = &value->vbool; ++ break; ++ case BINN_FALSE: ++ value->type = BINN_BOOL; ++ value->vbool = FALSE; ++ value->ptr = &value->vbool; ++ break; ++#ifdef BINN_EXTENDED ++ case BINN_SINGLE_STR: ++ value->type = BINN_SINGLE; ++ value->vfloat = (float) atof((const char*)value->ptr); // converts from string to double, and then to float ++ value->ptr = &value->vfloat; ++ break; ++ case BINN_DOUBLE_STR: ++ value->type = BINN_DOUBLE; ++ value->vdouble = atof((const char*)value->ptr); // converts from string to double ++ value->ptr = &value->vdouble; ++ break; ++#endif ++ /* ++ case BINN_DECIMAL: ++ case BINN_CURRENCYSTR: ++ case BINN_DATE: ++ case BINN_DATETIME: ++ case BINN_TIME: ++ */ ++ } ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++#if BYTE_ORDER == LITTLE_ENDIAN ++ ++// on little-endian devices we store the value so we can return a pointer to integers. ++// it's valid only for single-threaded apps. multi-threaded apps must use the _get_ functions instead. ++ ++binn local_value; ++ ++BINN_PRIVATE void * store_value(binn *value) { ++ ++ memcpy(&local_value, value, sizeof(binn)); ++ ++ switch (binn_get_read_storage(value->type)) { ++ case BINN_STORAGE_NOBYTES: ++ // return a valid pointer ++ case BINN_STORAGE_WORD: ++ case BINN_STORAGE_DWORD: ++ case BINN_STORAGE_QWORD: ++ return &local_value.vint32; // returns the pointer to the converted value, from big-endian to little-endian ++ } ++ ++ return value->ptr; // returns from the on stack value to be thread-safe (for list, map, object, string and blob) ++ ++} ++ ++#endif ++ ++/***************************************************************************/ ++/*** READ FUNCTIONS ********************************************************/ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_object_get_value(void *ptr, const char *key, binn *value) { ++ int type, count, size=0, header_size; ++ unsigned char *p; ++ ++ ptr = binn_ptr(ptr); ++ if ((ptr == 0) || (key == 0) || (value == 0)) return FALSE; ++ ++ // check the header ++ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; ++ ++ if (type != BINN_OBJECT) return FALSE; ++ if (count == 0) return FALSE; ++ ++ p = (unsigned char *) ptr; ++ p = SearchForKey(p, header_size, size, count, key); ++ if (p == FALSE) return FALSE; ++ ++ return GetValue(p, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_map_get_value(void* ptr, int id, binn *value) { ++ int type, count, size=0, header_size; ++ unsigned char *p; ++ ++ ptr = binn_ptr(ptr); ++ if ((ptr == 0) || (value == 0)) return FALSE; ++ ++ // check the header ++ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; ++ ++ if (type != BINN_MAP) return FALSE; ++ if (count == 0) return FALSE; ++ ++ p = (unsigned char *) ptr; ++ p = SearchForID(p, header_size, size, count, id); ++ if (p == FALSE) return FALSE; ++ ++ return GetValue(p, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_list_get_value(void* ptr, int pos, binn *value) { ++ int i, type, count, size=0, header_size; ++ unsigned char *p, *plimit, *base; ++ ++ ptr = binn_ptr(ptr); ++ if ((ptr == 0) || (value == 0)) return FALSE; ++ ++ // check the header ++ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; ++ ++ if (type != BINN_LIST) return FALSE; ++ if (count == 0) return FALSE; ++ if ((pos <= 0) | (pos > count)) return FALSE; ++ pos--; // convert from base 1 to base 0 ++ ++ p = (unsigned char *) ptr; ++ base = p; ++ plimit = p + size; ++ p += header_size; ++ ++ for (i = 0; i < pos; i++) { ++ p = AdvanceDataPos(p, plimit); ++ if ((p == 0) || (p < base)) return FALSE; ++ } ++ ++ return GetValue(p, value); ++ ++} ++ ++/***************************************************************************/ ++/*** READ PAIR BY POSITION *************************************************/ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_read_pair(int expected_type, void *ptr, int pos, int *pid, char *pkey, binn *value) { ++ int type, count, size=0, header_size; ++ int i, int32, id = 0, counter=0; ++ unsigned char *p, *plimit, *base, *key = NULL, len = 0; ++ ++ ptr = binn_ptr(ptr); ++ ++ // check the header ++ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; ++ ++ if ((type != expected_type) || (count == 0) || (pos < 1) || (pos > count)) return FALSE; ++ ++ p = (unsigned char *) ptr; ++ base = p; ++ plimit = p + size - 1; ++ p += header_size; ++ ++ for (i = 0; i < count; i++) { ++ switch (type) { ++ case BINN_MAP: ++ int32 = read_map_id(&p, plimit); ++ if (p > plimit) return FALSE; ++ id = int32; ++ break; ++ case BINN_OBJECT: ++ len = *((unsigned char *)p); p++; ++ if (p > plimit) return FALSE; ++ key = p; ++ p += len; ++ if (p > plimit) return FALSE; ++ break; ++ } ++ counter++; ++ if (counter == pos) goto found; ++ // ++ p = AdvanceDataPos(p, plimit); ++ if ((p == 0) || (p < base)) return FALSE; ++ } ++ ++ return FALSE; ++ ++found: ++ ++ switch (type) { ++ case BINN_MAP: ++ if (pid) *pid = id; ++ break; ++ case BINN_OBJECT: ++ if (pkey) { ++ memcpy(pkey, key, len); ++ pkey[len] = 0; ++ } ++ break; ++ } ++ ++ return GetValue(p, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_map_get_pair(void *ptr, int pos, int *pid, binn *value) { ++ ++ return binn_read_pair(BINN_MAP, ptr, pos, pid, NULL, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_object_get_pair(void *ptr, int pos, char *pkey, binn *value) { ++ ++ return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value); ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_map_pair(void *map, int pos, int *pid) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_read_pair(BINN_MAP, map, pos, pid, NULL, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_read_pair(BINN_OBJECT, obj, pos, NULL, pkey, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_map_get_pair(ptr, pos, pid, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_object_get_pair(ptr, pos, pkey, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++/*** SEQUENTIAL READ FUNCTIONS *********************************************/ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_iter_init(binn_iter *iter, void *ptr, int expected_type) { ++ int type, count, size=0, header_size; ++ ++ ptr = binn_ptr(ptr); ++ if ((ptr == 0) || (iter == 0)) return FALSE; ++ memset(iter, 0, sizeof(binn_iter)); ++ ++ // check the header ++ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; ++ ++ if (type != expected_type) return FALSE; ++ //if (count == 0) return FALSE; -- should not be used ++ ++ iter->plimit = (unsigned char *)ptr + size - 1; ++ iter->pnext = (unsigned char *)ptr + header_size; ++ iter->count = count; ++ iter->current = 0; ++ iter->type = type; ++ ++ return TRUE; ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value) { ++ unsigned char *pnow; ++ ++ if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != BINN_LIST)) return FALSE; ++ ++ iter->current++; ++ if (iter->current > iter->count) return FALSE; ++ ++ pnow = iter->pnext; ++ iter->pnext = AdvanceDataPos(pnow, iter->plimit); ++ if (iter->pnext != 0 && iter->pnext < pnow) return FALSE; ++ ++ return GetValue(pnow, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BINN_PRIVATE BOOL binn_read_next_pair(int expected_type, binn_iter *iter, int *pid, char *pkey, binn *value) { ++ int int32, id; ++ unsigned char *p, *key; ++ unsigned short len; ++ ++ if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != expected_type)) return FALSE; ++ ++ iter->current++; ++ if (iter->current > iter->count) return FALSE; ++ ++ p = iter->pnext; ++ ++ switch (expected_type) { ++ case BINN_MAP: ++ int32 = read_map_id(&p, iter->plimit); ++ if (p > iter->plimit) return FALSE; ++ id = int32; ++ if (pid) *pid = id; ++ break; ++ case BINN_OBJECT: ++ len = *((unsigned char *)p); p++; ++ key = p; ++ p += len; ++ if (p > iter->plimit) return FALSE; ++ if (pkey) { ++ memcpy(pkey, key, len); ++ pkey[len] = 0; ++ } ++ break; ++ } ++ ++ iter->pnext = AdvanceDataPos(p, iter->plimit); ++ if (iter->pnext != 0 && iter->pnext < p) return FALSE; ++ ++ return GetValue(p, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value) { ++ ++ return binn_read_next_pair(BINN_MAP, iter, pid, NULL, value); ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value) { ++ ++ return binn_read_next_pair(BINN_OBJECT, iter, NULL, pkey, value); ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_list_next_value(binn_iter *iter) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_list_next(iter, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_map_next(iter, pid, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++ ++binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_object_next(iter, pkey, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_list_next(iter, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_map_next(iter, pid, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_object_next(iter, pkey, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/*************************************************************************************/ ++/****** EXTENDED INTERFACE ***********************************************************/ ++/****** none of the functions above call the functions below *************************/ ++/*************************************************************************************/ ++ ++int APIENTRY binn_get_write_storage(int type) { ++ int storage_type; ++ ++ switch (type) { ++ case BINN_SINGLE_STR: ++ case BINN_DOUBLE_STR: ++ return BINN_STORAGE_STRING; ++ ++ case BINN_BOOL: ++ return BINN_STORAGE_NOBYTES; ++ ++ default: ++ binn_get_type_info(type, &storage_type, NULL); ++ return storage_type; ++ } ++ ++} ++ ++/*************************************************************************************/ ++ ++int APIENTRY binn_get_read_storage(int type) { ++ int storage_type; ++ ++ switch (type) { ++#ifdef BINN_EXTENDED ++ case BINN_SINGLE_STR: ++ return BINN_STORAGE_DWORD; ++ case BINN_DOUBLE_STR: ++ return BINN_STORAGE_QWORD; ++#endif ++ case BINN_BOOL: ++ case BINN_TRUE: ++ case BINN_FALSE: ++ return BINN_STORAGE_DWORD; ++ default: ++ binn_get_type_info(type, &storage_type, NULL); ++ return storage_type; ++ } ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL GetWriteConvertedData(int *ptype, void **ppvalue, int *psize) { ++ int type; ++ float f1; ++ double d1; ++ char pstr[128]; ++ ++ UNUSED(pstr); ++ UNUSED(d1); ++ UNUSED(f1); ++ ++ type = *ptype; ++ ++ if (*ppvalue == NULL) { ++ switch (type) { ++ case BINN_NULL: ++ case BINN_TRUE: ++ case BINN_FALSE: ++ break; ++ case BINN_STRING: ++ case BINN_BLOB: ++ if (*psize == 0) break; ++ /* fall through */ ++ default: ++ return FALSE; ++ } ++ } ++ ++ switch (type) { ++#ifdef BINN_EXTENDED ++ case BINN_SINGLE: ++ f1 = **(float**)ppvalue; ++ d1 = f1; // convert from float (32bits) to double (64bits) ++ type = BINN_SINGLE_STR; ++ goto conv_double; ++ case BINN_DOUBLE: ++ d1 = **(double**)ppvalue; ++ type = BINN_DOUBLE_STR; ++conv_double: ++ // the '%.17e' is more precise than the '%g' ++ snprintf(pstr, 127, "%.17e", d1); ++ *ppvalue = pstr; ++ *ptype = type; ++ break; ++#endif ++ case BINN_DECIMAL: ++ case BINN_CURRENCYSTR: ++ /* ++ if (binn_malloc_extptr(128) == NULL) return FALSE; ++ snprintf(sptr, 127, "%E", **ppvalue); ++ *ppvalue = sptr; ++ */ ++ return TRUE; //! temporary ++ break; ++ ++ case BINN_DATE: ++ case BINN_DATETIME: ++ case BINN_TIME: ++ return TRUE; //! temporary ++ break; ++ ++ case BINN_BOOL: ++ if (**((BOOL**)ppvalue) == FALSE) { ++ type = BINN_FALSE; ++ } else { ++ type = BINN_TRUE; ++ } ++ *ptype = type; ++ break; ++ ++ } ++ ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE int type_family(int type) { ++ ++ switch (type) { ++ case BINN_LIST: ++ case BINN_MAP: ++ case BINN_OBJECT: ++ return BINN_FAMILY_BINN; ++ ++ case BINN_INT8: ++ case BINN_INT16: ++ case BINN_INT32: ++ case BINN_INT64: ++ case BINN_UINT8: ++ case BINN_UINT16: ++ case BINN_UINT32: ++ case BINN_UINT64: ++ return BINN_FAMILY_INT; ++ ++ case BINN_FLOAT32: ++ case BINN_FLOAT64: ++ //case BINN_SINGLE: ++ case BINN_SINGLE_STR: ++ //case BINN_DOUBLE: ++ case BINN_DOUBLE_STR: ++ return BINN_FAMILY_FLOAT; ++ ++ case BINN_STRING: ++ case BINN_HTML: ++ case BINN_CSS: ++ case BINN_XML: ++ case BINN_JSON: ++ case BINN_JAVASCRIPT: ++ return BINN_FAMILY_STRING; ++ ++ case BINN_BLOB: ++ case BINN_JPEG: ++ case BINN_GIF: ++ case BINN_PNG: ++ case BINN_BMP: ++ return BINN_FAMILY_BLOB; ++ ++ case BINN_DECIMAL: ++ case BINN_CURRENCY: ++ case BINN_DATE: ++ case BINN_TIME: ++ case BINN_DATETIME: ++ return BINN_FAMILY_STRING; ++ ++ case BINN_BOOL: ++ return BINN_FAMILY_BOOL; ++ ++ case BINN_NULL: ++ return BINN_FAMILY_NULL; ++ ++ default: ++ // if it wasn't found ++ return BINN_FAMILY_NONE; ++ } ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE int int_type(int type) { ++ ++ switch (type) { ++ case BINN_INT8: ++ case BINN_INT16: ++ case BINN_INT32: ++ case BINN_INT64: ++ return BINN_SIGNED_INT; ++ ++ case BINN_UINT8: ++ case BINN_UINT16: ++ case BINN_UINT32: ++ case BINN_UINT64: ++ return BINN_UNSIGNED_INT; ++ ++ default: ++ return 0; ++ } ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL copy_raw_value(void *psource, void *pdest, int data_store) { ++ ++ switch (data_store) { ++ case BINN_STORAGE_NOBYTES: ++ break; ++ case BINN_STORAGE_BYTE: ++ *((char *) pdest) = *(char *)psource; ++ break; ++ case BINN_STORAGE_WORD: ++ *((short *) pdest) = *(short *)psource; ++ break; ++ case BINN_STORAGE_DWORD: ++ *((int *) pdest) = *(int *)psource; ++ break; ++ case BINN_STORAGE_QWORD: ++ *((uint64 *) pdest) = *(uint64 *)psource; ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ case BINN_STORAGE_CONTAINER: ++ *((char **) pdest) = (char *)psource; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL copy_int_value(void *psource, void *pdest, int source_type, int dest_type) { ++ uint64 vuint64 = 0; int64 vint64 = 0; ++ ++ switch (source_type) { ++ case BINN_INT8: ++ vint64 = *(signed char *)psource; ++ break; ++ case BINN_INT16: ++ vint64 = *(short *)psource; ++ break; ++ case BINN_INT32: ++ vint64 = *(int *)psource; ++ break; ++ case BINN_INT64: ++ vint64 = *(int64 *)psource; ++ break; ++ ++ case BINN_UINT8: ++ vuint64 = *(unsigned char *)psource; ++ break; ++ case BINN_UINT16: ++ vuint64 = *(unsigned short *)psource; ++ break; ++ case BINN_UINT32: ++ vuint64 = *(unsigned int *)psource; ++ break; ++ case BINN_UINT64: ++ vuint64 = *(uint64 *)psource; ++ break; ++ ++ default: ++ return FALSE; ++ } ++ ++ ++ // copy from int64 to uint64, if possible ++ ++ if ((int_type(source_type) == BINN_UNSIGNED_INT) && (int_type(dest_type) == BINN_SIGNED_INT)) { ++ if (vuint64 > INT64_MAX) return FALSE; ++ vint64 = vuint64; ++ } else if ((int_type(source_type) == BINN_SIGNED_INT) && (int_type(dest_type) == BINN_UNSIGNED_INT)) { ++ if (vint64 < 0) return FALSE; ++ vuint64 = vint64; ++ } ++ ++ ++ switch (dest_type) { ++ case BINN_INT8: ++ if ((vint64 < INT8_MIN) || (vint64 > INT8_MAX)) return FALSE; ++ *(signed char *)pdest = (signed char) vint64; ++ break; ++ case BINN_INT16: ++ if ((vint64 < INT16_MIN) || (vint64 > INT16_MAX)) return FALSE; ++ *(short *)pdest = (short) vint64; ++ break; ++ case BINN_INT32: ++ if ((vint64 < INT32_MIN) || (vint64 > INT32_MAX)) return FALSE; ++ *(int *)pdest = (int) vint64; ++ break; ++ case BINN_INT64: ++ *(int64 *)pdest = vint64; ++ break; ++ ++ case BINN_UINT8: ++ if (vuint64 > UINT8_MAX) return FALSE; ++ *(unsigned char *)pdest = (unsigned char) vuint64; ++ break; ++ case BINN_UINT16: ++ if (vuint64 > UINT16_MAX) return FALSE; ++ *(unsigned short *)pdest = (unsigned short) vuint64; ++ break; ++ case BINN_UINT32: ++ if (vuint64 > UINT32_MAX) return FALSE; ++ *(unsigned int *)pdest = (unsigned int) vuint64; ++ break; ++ case BINN_UINT64: ++ *(uint64 *)pdest = vuint64; ++ break; ++ ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL copy_float_value(void *psource, void *pdest, int source_type) { ++ ++ switch (source_type) { ++ case BINN_FLOAT32: ++ *(double *)pdest = *(float *)psource; ++ break; ++ case BINN_FLOAT64: ++ *(float *)pdest = (float) *(double *)psource; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE void zero_value(void *pvalue, int type) { ++ //int size=0; ++ ++ switch (binn_get_read_storage(type)) { ++ case BINN_STORAGE_NOBYTES: ++ break; ++ case BINN_STORAGE_BYTE: ++ *((char *) pvalue) = 0; ++ //size=1; ++ break; ++ case BINN_STORAGE_WORD: ++ *((short *) pvalue) = 0; ++ //size=2; ++ break; ++ case BINN_STORAGE_DWORD: ++ *((int *) pvalue) = 0; ++ //size=4; ++ break; ++ case BINN_STORAGE_QWORD: ++ *((uint64 *) pvalue) = 0; ++ //size=8; ++ break; ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_STRING: ++ case BINN_STORAGE_CONTAINER: ++ *(char **)pvalue = NULL; ++ break; ++ } ++ ++ //if (size>0) memset(pvalue, 0, size); ++ ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL copy_value(void *psource, void *pdest, int source_type, int dest_type, int data_store) { ++ ++ if (type_family(source_type) != type_family(dest_type)) return FALSE; ++ ++ if ((type_family(source_type) == BINN_FAMILY_INT) && (source_type != dest_type)) { ++ return copy_int_value(psource, pdest, source_type, dest_type); ++ } else if ((type_family(source_type) == BINN_FAMILY_FLOAT) && (source_type != dest_type)) { ++ return copy_float_value(psource, pdest, source_type); ++ } else { ++ return copy_raw_value(psource, pdest, data_store); ++ } ++ ++} ++ ++/*************************************************************************************/ ++/*** WRITE FUNCTIONS *****************************************************************/ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size) { ++ ++ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; ++ ++ return binn_list_add_raw(list, type, pvalue, size); ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size) { ++ ++ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; ++ ++ return binn_map_set_raw(map, id, type, pvalue, size); ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_object_set(binn *obj, const char *key, int type, void *pvalue, int size) { ++ ++ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; ++ ++ return binn_object_set_raw(obj, key, type, pvalue, size); ++ ++} ++ ++/*************************************************************************************/ ++ ++// this function is used by the wrappers ++BOOL APIENTRY binn_add_value(binn *item, int binn_type, int id, char *name, int type, void *pvalue, int size) { ++ ++ switch (binn_type) { ++ case BINN_LIST: ++ return binn_list_add(item, type, pvalue, size); ++ case BINN_MAP: ++ return binn_map_set(item, id, type, pvalue, size); ++ case BINN_OBJECT: ++ return binn_object_set(item, name, type, pvalue, size); ++ default: ++ return FALSE; ++ } ++ ++} ++ ++/*************************************************************************************/ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_list_add_new(binn *list, binn *value) { ++ BOOL retval; ++ ++ retval = binn_list_add_value(list, value); ++ if (value) free_fn(value); ++ return retval; ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value) { ++ BOOL retval; ++ ++ retval = binn_map_set_value(map, id, value); ++ if (value) free_fn(value); ++ return retval; ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_object_set_new(binn *obj, const char *key, binn *value) { ++ BOOL retval; ++ ++ retval = binn_object_set_value(obj, key, value); ++ if (value) free_fn(value); ++ return retval; ++ ++} ++ ++/*************************************************************************************/ ++/*** READ FUNCTIONS ******************************************************************/ ++/*************************************************************************************/ ++ ++binn * APIENTRY binn_list_value(void *ptr, int pos) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_list_get_value(ptr, pos, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/*************************************************************************************/ ++ ++binn * APIENTRY binn_map_value(void *ptr, int id) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_map_get_value(ptr, id, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/*************************************************************************************/ ++ ++binn * APIENTRY binn_object_value(void *ptr, const char *key) { ++ binn *value; ++ ++ value = (binn *) binn_malloc(sizeof(binn)); ++ ++ if (binn_object_get_value(ptr, key, value) == FALSE) { ++ free_fn(value); ++ return NULL; ++ } ++ ++ value->allocated = TRUE; ++ return value; ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_list_get_value(list, pos, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_map_get_value(map, id, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++ ++void * APIENTRY binn_object_read(void *obj, const char *key, int *ptype, int *psize) { ++ binn value; ++ ++ if (binn_object_get_value(obj, key, &value) == FALSE) return NULL; ++ if (ptype) *ptype = value.type; ++ if (psize) *psize = value.size; ++#if BYTE_ORDER == LITTLE_ENDIAN ++ return store_value(&value); ++#else ++ return value.ptr; ++#endif ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_list_get(void *ptr, int pos, int type, void *pvalue, int *psize) { ++ binn value; ++ int storage_type; ++ ++ storage_type = binn_get_read_storage(type); ++ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; ++ ++ zero_value(pvalue, type); ++ ++ if (binn_list_get_value(ptr, pos, &value) == FALSE) return FALSE; ++ ++ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; ++ ++ if (psize) *psize = value.size; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_map_get(void *ptr, int id, int type, void *pvalue, int *psize) { ++ binn value; ++ int storage_type; ++ ++ storage_type = binn_get_read_storage(type); ++ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; ++ ++ zero_value(pvalue, type); ++ ++ if (binn_map_get_value(ptr, id, &value) == FALSE) return FALSE; ++ ++ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; ++ ++ if (psize) *psize = value.size; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++ ++// if (binn_object_get(obj, "multiplier", BINN_INT32, &multiplier, NULL) == FALSE) xxx; ++ ++BOOL APIENTRY binn_object_get(void *ptr, const char *key, int type, void *pvalue, int *psize) { ++ binn value; ++ int storage_type; ++ ++ storage_type = binn_get_read_storage(type); ++ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; ++ ++ zero_value(pvalue, type); ++ ++ if (binn_object_get_value(ptr, key, &value) == FALSE) return FALSE; ++ ++ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; ++ ++ if (psize) *psize = value.size; ++ ++ return TRUE; ++ ++} ++ ++/***************************************************************************/ ++/***************************************************************************/ ++ ++// these functions below may not be implemented as inline functions, because ++// they use a lot of space, even for the variable. so they will be exported. ++ ++// but what about using as static? ++// is there any problem with wrappers? can these wrappers implement these functions using the header? ++// if as static, will they be present even on modules that don't use the functions? ++ ++signed char APIENTRY binn_list_int8(void *list, int pos) { ++ signed char value; ++ ++ binn_list_get(list, pos, BINN_INT8, &value, NULL); ++ ++ return value; ++} ++ ++short APIENTRY binn_list_int16(void *list, int pos) { ++ short value; ++ ++ binn_list_get(list, pos, BINN_INT16, &value, NULL); ++ ++ return value; ++} ++ ++int APIENTRY binn_list_int32(void *list, int pos) { ++ int value; ++ ++ binn_list_get(list, pos, BINN_INT32, &value, NULL); ++ ++ return value; ++} ++ ++int64 APIENTRY binn_list_int64(void *list, int pos) { ++ int64 value; ++ ++ binn_list_get(list, pos, BINN_INT64, &value, NULL); ++ ++ return value; ++} ++ ++unsigned char APIENTRY binn_list_uint8(void *list, int pos) { ++ unsigned char value; ++ ++ binn_list_get(list, pos, BINN_UINT8, &value, NULL); ++ ++ return value; ++} ++ ++unsigned short APIENTRY binn_list_uint16(void *list, int pos) { ++ unsigned short value; ++ ++ binn_list_get(list, pos, BINN_UINT16, &value, NULL); ++ ++ return value; ++} ++ ++unsigned int APIENTRY binn_list_uint32(void *list, int pos) { ++ unsigned int value; ++ ++ binn_list_get(list, pos, BINN_UINT32, &value, NULL); ++ ++ return value; ++} ++ ++uint64 APIENTRY binn_list_uint64(void *list, int pos) { ++ uint64 value; ++ ++ binn_list_get(list, pos, BINN_UINT64, &value, NULL); ++ ++ return value; ++} ++ ++float APIENTRY binn_list_float(void *list, int pos) { ++ float value; ++ ++ binn_list_get(list, pos, BINN_FLOAT32, &value, NULL); ++ ++ return value; ++} ++ ++double APIENTRY binn_list_double(void *list, int pos) { ++ double value; ++ ++ binn_list_get(list, pos, BINN_FLOAT64, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_list_bool(void *list, int pos) { ++ BOOL value; ++ ++ binn_list_get(list, pos, BINN_BOOL, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_list_null(void *list, int pos) { ++ ++ return binn_list_get(list, pos, BINN_NULL, NULL, NULL); ++ ++} ++ ++char * APIENTRY binn_list_str(void *list, int pos) { ++ char *value; ++ ++ binn_list_get(list, pos, BINN_STRING, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_list_blob(void *list, int pos, int *psize) { ++ void *value; ++ ++ binn_list_get(list, pos, BINN_BLOB, &value, psize); ++ ++ return value; ++} ++ ++void * APIENTRY binn_list_list(void *list, int pos) { ++ void *value; ++ ++ binn_list_get(list, pos, BINN_LIST, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_list_map(void *list, int pos) { ++ void *value; ++ ++ binn_list_get(list, pos, BINN_MAP, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_list_object(void *list, int pos) { ++ void *value; ++ ++ binn_list_get(list, pos, BINN_OBJECT, &value, NULL); ++ ++ return value; ++} ++ ++/***************************************************************************/ ++ ++signed char APIENTRY binn_map_int8(void *map, int id) { ++ signed char value; ++ ++ binn_map_get(map, id, BINN_INT8, &value, NULL); ++ ++ return value; ++} ++ ++short APIENTRY binn_map_int16(void *map, int id) { ++ short value; ++ ++ binn_map_get(map, id, BINN_INT16, &value, NULL); ++ ++ return value; ++} ++ ++int APIENTRY binn_map_int32(void *map, int id) { ++ int value; ++ ++ binn_map_get(map, id, BINN_INT32, &value, NULL); ++ ++ return value; ++} ++ ++int64 APIENTRY binn_map_int64(void *map, int id) { ++ int64 value; ++ ++ binn_map_get(map, id, BINN_INT64, &value, NULL); ++ ++ return value; ++} ++ ++unsigned char APIENTRY binn_map_uint8(void *map, int id) { ++ unsigned char value; ++ ++ binn_map_get(map, id, BINN_UINT8, &value, NULL); ++ ++ return value; ++} ++ ++unsigned short APIENTRY binn_map_uint16(void *map, int id) { ++ unsigned short value; ++ ++ binn_map_get(map, id, BINN_UINT16, &value, NULL); ++ ++ return value; ++} ++ ++unsigned int APIENTRY binn_map_uint32(void *map, int id) { ++ unsigned int value; ++ ++ binn_map_get(map, id, BINN_UINT32, &value, NULL); ++ ++ return value; ++} ++ ++uint64 APIENTRY binn_map_uint64(void *map, int id) { ++ uint64 value; ++ ++ binn_map_get(map, id, BINN_UINT64, &value, NULL); ++ ++ return value; ++} ++ ++float APIENTRY binn_map_float(void *map, int id) { ++ float value; ++ ++ binn_map_get(map, id, BINN_FLOAT32, &value, NULL); ++ ++ return value; ++} ++ ++double APIENTRY binn_map_double(void *map, int id) { ++ double value; ++ ++ binn_map_get(map, id, BINN_FLOAT64, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_map_bool(void *map, int id) { ++ BOOL value; ++ ++ binn_map_get(map, id, BINN_BOOL, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_map_null(void *map, int id) { ++ ++ return binn_map_get(map, id, BINN_NULL, NULL, NULL); ++ ++} ++ ++char * APIENTRY binn_map_str(void *map, int id) { ++ char *value; ++ ++ binn_map_get(map, id, BINN_STRING, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_map_blob(void *map, int id, int *psize) { ++ void *value; ++ ++ binn_map_get(map, id, BINN_BLOB, &value, psize); ++ ++ return value; ++} ++ ++void * APIENTRY binn_map_list(void *map, int id) { ++ void *value; ++ ++ binn_map_get(map, id, BINN_LIST, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_map_map(void *map, int id) { ++ void *value; ++ ++ binn_map_get(map, id, BINN_MAP, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_map_object(void *map, int id) { ++ void *value; ++ ++ binn_map_get(map, id, BINN_OBJECT, &value, NULL); ++ ++ return value; ++} ++ ++/***************************************************************************/ ++ ++signed char APIENTRY binn_object_int8(void *obj, const char *key) { ++ signed char value; ++ ++ binn_object_get(obj, key, BINN_INT8, &value, NULL); ++ ++ return value; ++} ++ ++short APIENTRY binn_object_int16(void *obj, const char *key) { ++ short value; ++ ++ binn_object_get(obj, key, BINN_INT16, &value, NULL); ++ ++ return value; ++} ++ ++int APIENTRY binn_object_int32(void *obj, const char *key) { ++ int value; ++ ++ binn_object_get(obj, key, BINN_INT32, &value, NULL); ++ ++ return value; ++} ++ ++int64 APIENTRY binn_object_int64(void *obj, const char *key) { ++ int64 value; ++ ++ binn_object_get(obj, key, BINN_INT64, &value, NULL); ++ ++ return value; ++} ++ ++unsigned char APIENTRY binn_object_uint8(void *obj, const char *key) { ++ unsigned char value; ++ ++ binn_object_get(obj, key, BINN_UINT8, &value, NULL); ++ ++ return value; ++} ++ ++unsigned short APIENTRY binn_object_uint16(void *obj, const char *key) { ++ unsigned short value; ++ ++ binn_object_get(obj, key, BINN_UINT16, &value, NULL); ++ ++ return value; ++} ++ ++unsigned int APIENTRY binn_object_uint32(void *obj, const char *key) { ++ unsigned int value; ++ ++ binn_object_get(obj, key, BINN_UINT32, &value, NULL); ++ ++ return value; ++} ++ ++uint64 APIENTRY binn_object_uint64(void *obj, const char *key) { ++ uint64 value; ++ ++ binn_object_get(obj, key, BINN_UINT64, &value, NULL); ++ ++ return value; ++} ++ ++float APIENTRY binn_object_float(void *obj, const char *key) { ++ float value; ++ ++ binn_object_get(obj, key, BINN_FLOAT32, &value, NULL); ++ ++ return value; ++} ++ ++double APIENTRY binn_object_double(void *obj, const char *key) { ++ double value; ++ ++ binn_object_get(obj, key, BINN_FLOAT64, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_object_bool(void *obj, const char *key) { ++ BOOL value; ++ ++ binn_object_get(obj, key, BINN_BOOL, &value, NULL); ++ ++ return value; ++} ++ ++BOOL APIENTRY binn_object_null(void *obj, const char *key) { ++ ++ return binn_object_get(obj, key, BINN_NULL, NULL, NULL); ++ ++} ++ ++char * APIENTRY binn_object_str(void *obj, const char *key) { ++ char *value; ++ ++ binn_object_get(obj, key, BINN_STRING, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_object_blob(void *obj, const char *key, int *psize) { ++ void *value; ++ ++ binn_object_get(obj, key, BINN_BLOB, &value, psize); ++ ++ return value; ++} ++ ++void * APIENTRY binn_object_list(void *obj, const char *key) { ++ void *value; ++ ++ binn_object_get(obj, key, BINN_LIST, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_object_map(void *obj, const char *key) { ++ void *value; ++ ++ binn_object_get(obj, key, BINN_MAP, &value, NULL); ++ ++ return value; ++} ++ ++void * APIENTRY binn_object_object(void *obj, const char *key) { ++ void *value; ++ ++ binn_object_get(obj, key, BINN_OBJECT, &value, NULL); ++ ++ return value; ++} ++ ++/*************************************************************************************/ ++/*************************************************************************************/ ++ ++BINN_PRIVATE binn * binn_alloc_item() { ++ binn *item; ++ item = (binn *) binn_malloc(sizeof(binn)); ++ if (item) { ++ memset(item, 0, sizeof(binn)); ++ item->header = BINN_MAGIC; ++ item->allocated = TRUE; ++ //item->writable = FALSE; -- already zeroed ++ } ++ return item; ++} ++ ++/*************************************************************************************/ ++ ++binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn) { ++ int storage_type; ++ binn *item = binn_alloc_item(); ++ if (item) { ++ item->type = type; ++ binn_get_type_info(type, &storage_type, NULL); ++ switch (storage_type) { ++ case BINN_STORAGE_NOBYTES: ++ break; ++ case BINN_STORAGE_STRING: ++ if (size == 0) size = strlen((char*)pvalue) + 1; ++ /* fall through */ ++ case BINN_STORAGE_BLOB: ++ case BINN_STORAGE_CONTAINER: ++ if (freefn == BINN_TRANSIENT) { ++ item->ptr = binn_memdup(pvalue, size); ++ if (item->ptr == NULL) { ++ free_fn(item); ++ return NULL; ++ } ++ item->freefn = free_fn; ++ if (storage_type == BINN_STORAGE_STRING) size--; ++ } else { ++ item->ptr = pvalue; ++ item->freefn = freefn; ++ } ++ item->size = size; ++ break; ++ default: ++ item->ptr = &item->vint32; ++ copy_raw_value(pvalue, item->ptr, storage_type); ++ } ++ } ++ return item; ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree) { ++ ++ if (item == NULL || str == NULL) return FALSE; ++ ++ if (pfree == BINN_TRANSIENT) { ++ item->ptr = binn_memdup(str, strlen(str) + 1); ++ if (item->ptr == NULL) return FALSE; ++ item->freefn = free_fn; ++ } else { ++ item->ptr = str; ++ item->freefn = pfree; ++ } ++ ++ item->type = BINN_STRING; ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree) { ++ ++ if (item == NULL || ptr == NULL) return FALSE; ++ ++ if (pfree == BINN_TRANSIENT) { ++ item->ptr = binn_memdup(ptr, size); ++ if (item->ptr == NULL) return FALSE; ++ item->freefn = free_fn; ++ } else { ++ item->ptr = ptr; ++ item->freefn = pfree; ++ } ++ ++ item->type = BINN_BLOB; ++ item->size = size; ++ return TRUE; ++ ++} ++ ++/*************************************************************************************/ ++/*** READ CONVERTED VALUE ************************************************************/ ++/*************************************************************************************/ ++ ++#ifdef _MSC_VER ++#define atoi64 _atoi64 ++#else ++int64 atoi64(char *str) { ++ int64 retval; ++ int is_negative=0; ++ ++ if (*str == '-') { ++ is_negative = 1; ++ str++; ++ } ++ retval = 0; ++ for (; *str; str++) { ++ retval = 10 * retval + (*str - '0'); ++ } ++ if (is_negative) retval *= -1; ++ return retval; ++} ++#endif ++ ++/*****************************************************************************/ ++ ++BINN_PRIVATE BOOL is_integer(char *p) { ++ BOOL retval; ++ ++ if (p == NULL) return FALSE; ++ if (*p == '-') p++; ++ if (*p == 0) return FALSE; ++ ++ retval = TRUE; ++ ++ for (; *p; p++) { ++ if ( (*p < '0') || (*p > '9') ) { ++ retval = FALSE; ++ } ++ } ++ ++ return retval; ++} ++ ++/*****************************************************************************/ ++ ++BINN_PRIVATE BOOL is_float(char *p) { ++ BOOL retval, number_found=FALSE; ++ ++ if (p == NULL) return FALSE; ++ if (*p == '-') p++; ++ if (*p == 0) return FALSE; ++ ++ retval = TRUE; ++ ++ for (; *p; p++) { ++ if ((*p == '.') || (*p == ',')) { ++ if (!number_found) retval = FALSE; ++ } else if ( (*p >= '0') && (*p <= '9') ) { ++ number_found = TRUE; ++ } else { ++ return FALSE; ++ } ++ } ++ ++ return retval; ++} ++ ++/*************************************************************************************/ ++ ++BINN_PRIVATE BOOL is_bool_str(char *str, BOOL *pbool) { ++ int64 vint; ++ double vdouble; ++ ++ if (str == NULL || pbool == NULL) return FALSE; ++ ++ if (stricmp(str, "true") == 0) goto loc_true; ++ if (stricmp(str, "yes") == 0) goto loc_true; ++ if (stricmp(str, "on") == 0) goto loc_true; ++ //if (stricmp(str, "1") == 0) goto loc_true; ++ ++ if (stricmp(str, "false") == 0) goto loc_false; ++ if (stricmp(str, "no") == 0) goto loc_false; ++ if (stricmp(str, "off") == 0) goto loc_false; ++ //if (stricmp(str, "0") == 0) goto loc_false; ++ ++ if (is_integer(str)) { ++ vint = atoi64(str); ++ *pbool = (vint != 0) ? TRUE : FALSE; ++ return TRUE; ++ } else if (is_float(str)) { ++ vdouble = atof(str); ++ *pbool = (vdouble != 0) ? TRUE : FALSE; ++ return TRUE; ++ } ++ ++ return FALSE; ++ ++loc_true: ++ *pbool = TRUE; ++ return TRUE; ++ ++loc_false: ++ *pbool = FALSE; ++ return TRUE; ++ ++} ++ ++/* these three functions are used to ++ * suppress warnings about implicit conversions ++ * in binn_get_int32 and binn_get_int64 ++ * CJR 2/9/2023 ++ */ ++ ++float ++binn_cvt_int2float (int value) { ++ return (float)value; ++} ++ ++float ++binn_cvt_long2float (long int value) { ++ return (float)value; ++} ++ ++float ++binn_cvt_long2dbl (long int value) { ++ return (long long int)value; ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_get_int32(binn *value, int *pint) { ++ ++ if (value == NULL || pint == NULL) return FALSE; ++ ++ if (type_family(value->type) == BINN_FAMILY_INT) { ++ return copy_int_value(value->ptr, pint, value->type, BINN_INT32); ++ } ++ ++ switch (value->type) { ++ case BINN_FLOAT: ++ if ((value->vfloat < binn_cvt_int2float(INT32_MIN)) || (value->vfloat > binn_cvt_int2float(INT32_MAX))) return FALSE; ++ *pint = roundval(value->vfloat); ++ break; ++ case BINN_DOUBLE: ++ if ((value->vdouble < INT32_MIN) || (value->vdouble > INT32_MAX)) return FALSE; ++ *pint = roundval(value->vdouble); ++ break; ++ case BINN_STRING: ++ if (is_integer((char*)value->ptr)) ++ *pint = atoi((char*)value->ptr); ++ else if (is_float((char*)value->ptr)) ++ *pint = roundval(atof((char*)value->ptr)); ++ else ++ return FALSE; ++ break; ++ case BINN_BOOL: ++ *pint = value->vbool; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_get_int64(binn *value, int64 *pint) { ++ ++ if (value == NULL || pint == NULL) return FALSE; ++ ++ if (type_family(value->type) == BINN_FAMILY_INT) { ++ return copy_int_value(value->ptr, pint, value->type, BINN_INT64); ++ } ++ ++ switch (value->type) { ++ case BINN_FLOAT: ++ if ((value->vfloat < binn_cvt_long2float(INT64_MIN)) || (value->vfloat > binn_cvt_long2float(INT64_MAX))) return FALSE; ++ *pint = roundval(value->vfloat); ++ break; ++ case BINN_DOUBLE: ++ if ((value->vdouble < binn_cvt_long2dbl(INT64_MIN)) || (value->vdouble > binn_cvt_long2dbl(INT64_MAX))) return FALSE; ++ *pint = roundval(value->vdouble); ++ break; ++ case BINN_STRING: ++ if (is_integer((char*)value->ptr)) ++ *pint = atoi64((char*)value->ptr); ++ else if (is_float((char*)value->ptr)) ++ *pint = roundval(atof((char*)value->ptr)); ++ else ++ return FALSE; ++ break; ++ case BINN_BOOL: ++ *pint = value->vbool; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_get_double(binn *value, double *pfloat) { ++ int64 vint; ++ ++ if (value == NULL || pfloat == NULL) return FALSE; ++ ++ if (type_family(value->type) == BINN_FAMILY_INT) { ++ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE; ++ *pfloat = (double) vint; ++ return TRUE; ++ } ++ ++ switch (value->type) { ++ case BINN_FLOAT: ++ *pfloat = value->vfloat; ++ break; ++ case BINN_DOUBLE: ++ *pfloat = value->vdouble; ++ break; ++ case BINN_STRING: ++ if (is_integer((char*)value->ptr)) ++ *pfloat = (double) atoi64((char*)value->ptr); ++ else if (is_float((char*)value->ptr)) ++ *pfloat = atof((char*)value->ptr); ++ else ++ return FALSE; ++ break; ++ case BINN_BOOL: ++ *pfloat = value->vbool; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool) { ++ int64 vint; ++ ++ if (value == NULL || pbool == NULL) return FALSE; ++ ++ if (type_family(value->type) == BINN_FAMILY_INT) { ++ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE; ++ *pbool = (vint != 0) ? TRUE : FALSE; ++ return TRUE; ++ } ++ ++ switch (value->type) { ++ case BINN_BOOL: ++ *pbool = value->vbool; ++ break; ++ case BINN_FLOAT: ++ *pbool = (value->vfloat != 0) ? TRUE : FALSE; ++ break; ++ case BINN_DOUBLE: ++ *pbool = (value->vdouble != 0) ? TRUE : FALSE; ++ break; ++ case BINN_STRING: ++ return is_bool_str((char*)value->ptr, pbool); ++ default: ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/*************************************************************************************/ ++ ++char * APIENTRY binn_get_str(binn *value) { ++ int64 vint; ++ char buf[128]; ++ ++ if (value == NULL) return NULL; ++ ++ if (type_family(value->type) == BINN_FAMILY_INT) { ++ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return NULL; ++ snprintf(buf, sizeof buf, "%" INT64_FORMAT, vint); ++ goto loc_convert_value; ++ } ++ ++ switch (value->type) { ++ case BINN_FLOAT: ++ value->vdouble = value->vfloat; ++ /* fall through */ ++ case BINN_DOUBLE: ++ snprintf(buf, sizeof buf, "%g", value->vdouble); ++ goto loc_convert_value; ++ case BINN_STRING: ++ return (char*) value->ptr; ++ case BINN_BOOL: ++ if (value->vbool) ++ strcpy(buf, "true"); ++ else ++ strcpy(buf, "false"); ++ goto loc_convert_value; ++ } ++ ++ return NULL; ++ ++loc_convert_value: ++ ++ //value->vint64 = 0; ++ value->ptr = strdup(buf); ++ if (value->ptr == NULL) return NULL; ++ value->freefn = free; ++ value->type = BINN_STRING; ++ return (char*) value->ptr; ++ ++} ++ ++/*************************************************************************************/ ++/*** GENERAL FUNCTIONS ***************************************************************/ ++/*************************************************************************************/ ++ ++BOOL APIENTRY binn_is_container(binn *item) { ++ ++ if (item == NULL) return FALSE; ++ ++ switch (item->type) { ++ case BINN_LIST: ++ case BINN_MAP: ++ case BINN_OBJECT: ++ return TRUE; ++ default: ++ return FALSE; ++ } ++ ++} ++ ++/*************************************************************************************/ +diff -Nur openssh-9.3p1.orig/binn.h openssh-9.3p1/binn.h +--- openssh-9.3p1.orig/binn.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/binn.h 2024-07-10 09:09:55.897568393 +0200 +@@ -0,0 +1,943 @@ ++ ++// TO ENABLE INLINE FUNCTIONS: ++// ON MSVC: enable the 'Inline Function Expansion' (/Ob2) compiler option, and maybe the ++// 'Whole Program Optimitazion' (/GL), that requires the ++// 'Link Time Code Generation' (/LTCG) linker option to be enabled too ++ ++#ifndef BINN_H ++#define BINN_H ++#include ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define BINN_VERSION "3.0.0" /* using semantic versioning */ ++ ++#ifndef NULL ++#ifdef __cplusplus ++#define NULL 0 ++#else ++#define NULL ((void *)0) ++#endif ++#endif ++ ++#ifndef TRUE ++#define TRUE 1 ++#endif ++ ++#ifndef FALSE ++#define FALSE 0 ++#endif ++ ++#ifndef BOOL ++typedef int BOOL; ++#endif ++ ++#ifndef APIENTRY ++ #ifdef _WIN32 ++ #define APIENTRY __stdcall ++ #else ++ //#define APIENTRY __attribute__((stdcall)) ++ #define APIENTRY ++ #endif ++#endif ++ ++#ifndef BINN_PRIVATE ++ #ifdef DEBUG ++ #define BINN_PRIVATE ++ #else ++ #define BINN_PRIVATE static ++ #endif ++#endif ++ ++#ifdef _MSC_VER ++ #define INLINE __inline ++ #define ALWAYS_INLINE __forceinline ++#else ++ // you can change to 'extern inline' if using the gcc option -flto ++ #define INLINE static inline ++ #define ALWAYS_INLINE static inline __attribute__((always_inline)) ++#endif ++ ++#ifndef int64 ++#if defined(_MSC_VER) || defined(__BORLANDC__) ++ typedef __int64 int64; ++ typedef unsigned __int64 uint64; ++#else ++ typedef long long int int64; ++ typedef unsigned long long int uint64; ++#endif ++#endif ++ ++#ifdef _WIN32 ++#define INT64_FORMAT "I64i" ++#define UINT64_FORMAT "I64u" ++#define INT64_HEX_FORMAT "I64x" ++#else ++#define INT64_FORMAT "lli" ++#define UINT64_FORMAT "llu" ++#define INT64_HEX_FORMAT "llx" ++#endif ++ ++ ++// BINN CONSTANTS ---------------------------------------- ++ ++#define INVALID_BINN 0 ++ ++// Storage Data Types ------------------------------------ ++ ++#define BINN_STORAGE_NOBYTES 0x00 ++#define BINN_STORAGE_BYTE 0x20 // 8 bits ++#define BINN_STORAGE_WORD 0x40 // 16 bits -- the endianess (byte order) is automatically corrected ++#define BINN_STORAGE_DWORD 0x60 // 32 bits -- the endianess (byte order) is automatically corrected ++#define BINN_STORAGE_QWORD 0x80 // 64 bits -- the endianess (byte order) is automatically corrected ++#define BINN_STORAGE_STRING 0xA0 // Are stored with null termination ++#define BINN_STORAGE_BLOB 0xC0 ++#define BINN_STORAGE_CONTAINER 0xE0 ++#define BINN_STORAGE_VIRTUAL 0x80000 ++ ++#define BINN_STORAGE_MIN BINN_STORAGE_NOBYTES ++#define BINN_STORAGE_MAX BINN_STORAGE_CONTAINER ++ ++#define BINN_STORAGE_MASK 0xE0 ++#define BINN_STORAGE_MASK16 0xE000 ++#define BINN_STORAGE_HAS_MORE 0x10 ++#define BINN_TYPE_MASK 0x0F ++#define BINN_TYPE_MASK16 0x0FFF ++ ++#define BINN_MAX_VALUE_MASK 0xFFFFF ++ ++ ++// Data Formats ------------------------------------------ ++ ++#define BINN_LIST 0xE0 ++#define BINN_MAP 0xE1 ++#define BINN_OBJECT 0xE2 ++ ++#define BINN_NULL 0x00 ++#define BINN_TRUE 0x01 ++#define BINN_FALSE 0x02 ++ ++#define BINN_UINT8 0x20 // (BYTE) (unsigned byte) Is the default format for the BYTE type ++#define BINN_INT8 0x21 // (BYTE) (signed byte, from -128 to +127. The 0x80 is the sign bit, so the range in hex is from 0x80 [-128] to 0x7F [127], being 0x00 = 0 and 0xFF = -1) ++#define BINN_UINT16 0x40 // (WORD) (unsigned integer) Is the default format for the WORD type ++#define BINN_INT16 0x41 // (WORD) (signed integer) ++#define BINN_UINT32 0x60 // (DWORD) (unsigned integer) Is the default format for the DWORD type ++#define BINN_INT32 0x61 // (DWORD) (signed integer) ++#define BINN_UINT64 0x80 // (QWORD) (unsigned integer) Is the default format for the QWORD type ++#define BINN_INT64 0x81 // (QWORD) (signed integer) ++ ++#define BINN_SCHAR BINN_INT8 ++#define BINN_UCHAR BINN_UINT8 ++ ++#define BINN_STRING 0xA0 // (STRING) Raw String ++#define BINN_DATETIME 0xA1 // (STRING) iso8601 format -- YYYY-MM-DD HH:MM:SS ++#define BINN_DATE 0xA2 // (STRING) iso8601 format -- YYYY-MM-DD ++#define BINN_TIME 0xA3 // (STRING) iso8601 format -- HH:MM:SS ++#define BINN_DECIMAL 0xA4 // (STRING) High precision number - used for generic decimal values and for those ones that cannot be represented in the float64 format. ++#define BINN_CURRENCYSTR 0xA5 // (STRING) With currency unit/symbol - check for some iso standard format ++#define BINN_SINGLE_STR 0xA6 // (STRING) Can be restored to float32 ++#define BINN_DOUBLE_STR 0xA7 // (STRING) May be restored to float64 ++ ++#define BINN_FLOAT32 0x62 // (DWORD) ++#define BINN_FLOAT64 0x82 // (QWORD) ++#define BINN_FLOAT BINN_FLOAT32 ++#define BINN_SINGLE BINN_FLOAT32 ++#define BINN_DOUBLE BINN_FLOAT64 ++ ++#define BINN_CURRENCY 0x83 // (QWORD) ++ ++#define BINN_BLOB 0xC0 // (BLOB) Raw Blob ++ ++ ++// virtual types: ++ ++#define BINN_BOOL 0x80061 // (DWORD) The value may be 0 or 1 ++ ++#ifdef BINN_EXTENDED ++//#define BINN_SINGLE 0x800A1 // (STRING) Can be restored to float32 ++//#define BINN_DOUBLE 0x800A2 // (STRING) May be restored to float64 ++#endif ++ ++//#define BINN_BINN 0x800E1 // (CONTAINER) ++//#define BINN_BINN_BUFFER 0x800C1 // (BLOB) user binn. it's not open by the parser ++ ++ ++// extended content types: ++ ++// strings: ++ ++#define BINN_HTML 0xB001 ++#define BINN_XML 0xB002 ++#define BINN_JSON 0xB003 ++#define BINN_JAVASCRIPT 0xB004 ++#define BINN_CSS 0xB005 ++ ++// blobs: ++ ++#define BINN_JPEG 0xD001 ++#define BINN_GIF 0xD002 ++#define BINN_PNG 0xD003 ++#define BINN_BMP 0xD004 ++ ++ ++// type families ++#define BINN_FAMILY_NONE 0x00 ++#define BINN_FAMILY_NULL 0xf1 ++#define BINN_FAMILY_INT 0xf2 ++#define BINN_FAMILY_FLOAT 0xf3 ++#define BINN_FAMILY_STRING 0xf4 ++#define BINN_FAMILY_BLOB 0xf5 ++#define BINN_FAMILY_BOOL 0xf6 ++#define BINN_FAMILY_BINN 0xf7 ++ ++// integer types related to signal ++#define BINN_SIGNED_INT 11 ++#define BINN_UNSIGNED_INT 22 ++ ++ ++typedef void (*binn_mem_free)(void*); ++#define BINN_STATIC ((binn_mem_free)0) ++#define BINN_TRANSIENT ((binn_mem_free)-1) ++ ++ ++// --- BINN STRUCTURE -------------------------------------------------------------- ++ ++ ++struct binn_struct { ++ int header; // this struct header holds the magic number (BINN_MAGIC) that identifies this memory block as a binn structure ++ BOOL allocated; // the struct can be allocated using malloc_fn() or can be on the stack ++ BOOL writable; // did it was create for writing? it can use the pbuf if not unified with ptr ++ BOOL dirty; // the container header is not written to the buffer ++ // ++ void *pbuf; // use *ptr below? ++ BOOL pre_allocated; ++ int alloc_size; ++ int used_size; ++ // ++ int type; ++ void *ptr; ++ int size; ++ int count; ++ // ++ binn_mem_free freefn; // used only when type == BINN_STRING or BINN_BLOB ++ // ++ union { ++ signed char vint8; ++ signed short vint16; ++ signed int vint32; ++ int64 vint64; ++ unsigned char vuint8; ++ unsigned short vuint16; ++ unsigned int vuint32; ++ uint64 vuint64; ++ // ++ signed char vchar; ++ unsigned char vuchar; ++ signed short vshort; ++ unsigned short vushort; ++ signed int vint; ++ unsigned int vuint; ++ // ++ float vfloat; ++ double vdouble; ++ // ++ BOOL vbool; ++ }; ++ // ++ BOOL disable_int_compression; ++}; ++ ++typedef struct binn_struct binn; ++ ++ ++ ++// --- GENERAL FUNCTIONS ---------------------------------------------------------- ++ ++char * APIENTRY binn_version(); ++ ++void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*)); ++ ++int APIENTRY binn_create_type(int storage_type, int data_type_index); ++BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type); ++ ++int APIENTRY binn_get_write_storage(int type); ++int APIENTRY binn_get_read_storage(int type); ++ ++BOOL APIENTRY binn_is_container(binn *item); ++ ++ ++// --- WRITE FUNCTIONS ------------------------------------------------------------ ++ ++// create a new binn allocating memory for the structure ++binn * APIENTRY binn_new(int type, int size, void *buffer); ++binn * APIENTRY binn_list(); ++binn * APIENTRY binn_map(); ++binn * APIENTRY binn_object(); ++ ++// create a new binn storing the structure on the stack ++BOOL APIENTRY binn_create(binn *item, int type, int size, void *buffer); ++BOOL APIENTRY binn_create_list(binn *list); ++BOOL APIENTRY binn_create_map(binn *map); ++BOOL APIENTRY binn_create_object(binn *object); ++ ++// create a new binn as a copy from another ++binn * APIENTRY binn_copy(void *old); ++ ++ ++BOOL APIENTRY binn_list_add_new(binn *list, binn *value); ++BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value); ++BOOL APIENTRY binn_object_set_new(binn *obj, const char *key, binn *value); ++ ++ ++// extended interface ++ ++BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size); ++BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size); ++BOOL APIENTRY binn_object_set(binn *obj, const char *key, int type, void *pvalue, int size); ++ ++ ++// release memory ++ ++void APIENTRY binn_free(binn *item); ++void * APIENTRY binn_release(binn *item); // free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later ++ ++ ++// --- CREATING VALUES --------------------------------------------------- ++ ++binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn); ++ ++ALWAYS_INLINE binn * binn_int8(signed char value) { ++ return binn_value(BINN_INT8, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_int16(short value) { ++ return binn_value(BINN_INT16, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_int32(int value) { ++ return binn_value(BINN_INT32, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_int64(int64 value) { ++ return binn_value(BINN_INT64, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_uint8(unsigned char value) { ++ return binn_value(BINN_UINT8, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_uint16(unsigned short value) { ++ return binn_value(BINN_UINT16, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_uint32(unsigned int value) { ++ return binn_value(BINN_UINT32, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_uint64(uint64 value) { ++ return binn_value(BINN_UINT64, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_float(float value) { ++ return binn_value(BINN_FLOAT, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_double(double value) { ++ return binn_value(BINN_DOUBLE, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_bool(BOOL value) { ++ return binn_value(BINN_BOOL, &value, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_null() { ++ return binn_value(BINN_NULL, NULL, 0, NULL); ++} ++ALWAYS_INLINE binn * binn_string(char *str, binn_mem_free freefn) { ++ return binn_value(BINN_STRING, str, 0, freefn); ++} ++ALWAYS_INLINE binn * binn_blob(void *ptr, int size, binn_mem_free freefn) { ++ return binn_value(BINN_BLOB, ptr, size, freefn); ++} ++ ++ ++// --- READ FUNCTIONS ------------------------------------------------------------- ++ ++// these functions accept pointer to the binn structure and pointer to the binn buffer ++void * APIENTRY binn_ptr(void *ptr); ++int APIENTRY binn_size(void *ptr); ++int APIENTRY binn_type(void *ptr); ++int APIENTRY binn_count(void *ptr); ++ ++BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize); ++/* the function returns the values (type, count and size) and they don't need to be ++ initialized. these values are read from the buffer. example: ++ ++ int type, count, size; ++ result = binn_is_valid(ptr, &type, &count, &size); ++*/ ++BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize); ++/* if some value is informed (type, count or size) then the function will check if ++ the value returned from the serialized data matches the informed value. otherwise ++ the values must be initialized to zero. example: ++ ++ int type=0, count=0, size = known_size; ++ result = binn_is_valid_ex(ptr, &type, &count, &size); ++*/ ++ ++BOOL APIENTRY binn_is_struct(void *ptr); ++ ++ ++// Loading a binn buffer into a binn value - this is optional ++ ++BOOL APIENTRY binn_load(void *data, binn *item); // on stack ++binn * APIENTRY binn_open(void *data); // allocated ++ ++ ++// easiest interface to use, but don't check if the value is there ++ ++signed char APIENTRY binn_list_int8(void *list, int pos); ++short APIENTRY binn_list_int16(void *list, int pos); ++int APIENTRY binn_list_int32(void *list, int pos); ++int64 APIENTRY binn_list_int64(void *list, int pos); ++unsigned char APIENTRY binn_list_uint8(void *list, int pos); ++unsigned short APIENTRY binn_list_uint16(void *list, int pos); ++unsigned int APIENTRY binn_list_uint32(void *list, int pos); ++uint64 APIENTRY binn_list_uint64(void *list, int pos); ++float APIENTRY binn_list_float(void *list, int pos); ++double APIENTRY binn_list_double(void *list, int pos); ++BOOL APIENTRY binn_list_bool(void *list, int pos); ++BOOL APIENTRY binn_list_null(void *list, int pos); ++char * APIENTRY binn_list_str(void *list, int pos); ++void * APIENTRY binn_list_blob(void *list, int pos, int *psize); ++void * APIENTRY binn_list_list(void *list, int pos); ++void * APIENTRY binn_list_map(void *list, int pos); ++void * APIENTRY binn_list_object(void *list, int pos); ++ ++signed char APIENTRY binn_map_int8(void *map, int id); ++short APIENTRY binn_map_int16(void *map, int id); ++int APIENTRY binn_map_int32(void *map, int id); ++int64 APIENTRY binn_map_int64(void *map, int id); ++unsigned char APIENTRY binn_map_uint8(void *map, int id); ++unsigned short APIENTRY binn_map_uint16(void *map, int id); ++unsigned int APIENTRY binn_map_uint32(void *map, int id); ++uint64 APIENTRY binn_map_uint64(void *map, int id); ++float APIENTRY binn_map_float(void *map, int id); ++double APIENTRY binn_map_double(void *map, int id); ++BOOL APIENTRY binn_map_bool(void *map, int id); ++BOOL APIENTRY binn_map_null(void *map, int id); ++char * APIENTRY binn_map_str(void *map, int id); ++void * APIENTRY binn_map_blob(void *map, int id, int *psize); ++void * APIENTRY binn_map_list(void *map, int id); ++void * APIENTRY binn_map_map(void *map, int id); ++void * APIENTRY binn_map_object(void *map, int id); ++ ++signed char APIENTRY binn_object_int8(void *obj, const char *key); ++short APIENTRY binn_object_int16(void *obj, const char *key); ++int APIENTRY binn_object_int32(void *obj, const char *key); ++int64 APIENTRY binn_object_int64(void *obj, const char *key); ++unsigned char APIENTRY binn_object_uint8(void *obj, const char *key); ++unsigned short APIENTRY binn_object_uint16(void *obj, const char *key); ++unsigned int APIENTRY binn_object_uint32(void *obj, const char *key); ++uint64 APIENTRY binn_object_uint64(void *obj, const char *key); ++float APIENTRY binn_object_float(void *obj, const char *key); ++double APIENTRY binn_object_double(void *obj, const char *key); ++BOOL APIENTRY binn_object_bool(void *obj, const char *key); ++BOOL APIENTRY binn_object_null(void *obj, const char *key); ++char * APIENTRY binn_object_str(void *obj, const char *key); ++void * APIENTRY binn_object_blob(void *obj, const char *key, int *psize); ++void * APIENTRY binn_object_list(void *obj, const char *key); ++void * APIENTRY binn_object_map(void *obj, const char *key); ++void * APIENTRY binn_object_object(void *obj, const char *key); ++ ++ ++// return a pointer to an allocated binn structure - must be released with the free() function or equivalent set in binn_set_alloc_functions() ++binn * APIENTRY binn_list_value(void *list, int pos); ++binn * APIENTRY binn_map_value(void *map, int id); ++binn * APIENTRY binn_object_value(void *obj, const char *key); ++ ++// read the value to a binn structure on the stack ++BOOL APIENTRY binn_list_get_value(void* list, int pos, binn *value); ++BOOL APIENTRY binn_map_get_value(void* map, int id, binn *value); ++BOOL APIENTRY binn_object_get_value(void *obj, const char *key, binn *value); ++ ++// single interface - these functions check the data type ++BOOL APIENTRY binn_list_get(void *list, int pos, int type, void *pvalue, int *psize); ++BOOL APIENTRY binn_map_get(void *map, int id, int type, void *pvalue, int *psize); ++BOOL APIENTRY binn_object_get(void *obj, const char *key, int type, void *pvalue, int *psize); ++ ++// these 3 functions return a pointer to the value and the data type ++// they are thread-safe on big-endian devices ++// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings ++// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications ++void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize); ++void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize); ++void * APIENTRY binn_object_read(void *obj, const char *key, int *ptype, int *psize); ++ ++ ++// READ PAIR FUNCTIONS ++ ++// these functions use base 1 in the 'pos' argument ++ ++// on stack ++BOOL APIENTRY binn_map_get_pair(void *map, int pos, int *pid, binn *value); ++BOOL APIENTRY binn_object_get_pair(void *obj, int pos, char *pkey, binn *value); // must free the memory returned in the pkey ++ ++// allocated ++binn * APIENTRY binn_map_pair(void *map, int pos, int *pid); ++binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey); // must free the memory returned in the pkey ++ ++// these 2 functions return a pointer to the value and the data type ++// they are thread-safe on big-endian devices ++// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings ++// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications ++void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize); ++void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize); ++ ++ ++// SEQUENTIAL READ FUNCTIONS ++ ++typedef struct binn_iter_struct { ++ unsigned char *pnext; ++ unsigned char *plimit; ++ int type; ++ int count; ++ int current; ++} binn_iter; ++ ++BOOL APIENTRY binn_iter_init(binn_iter *iter, void *pbuf, int type); ++ ++// allocated ++binn * APIENTRY binn_list_next_value(binn_iter *iter); ++binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid); ++binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey); // the key must be declared as: char key[256]; ++ ++// on stack ++BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value); ++BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value); ++BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value); // the key must be declared as: char key[256]; ++ ++// these 3 functions return a pointer to the value and the data type ++// they are thread-safe on big-endian devices ++// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings ++// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications ++void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize); ++void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize); ++void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize); // the key must be declared as: char key[256]; ++ ++ ++// --- MACROS ------------------------------------------------------------ ++ ++ ++#define binn_is_writable(item) (item)->writable; ++ ++ ++// set values on stack allocated binn structures ++ ++#define binn_set_null(item) do { (item)->type = BINN_NULL; } while (0) ++ ++#define binn_set_bool(item,value) do { (item)->type = BINN_BOOL; (item)->vbool = value; (item)->ptr = &((item)->vbool); } while (0) ++ ++#define binn_set_int(item,value) do { (item)->type = BINN_INT32; (item)->vint32 = value; (item)->ptr = &((item)->vint32); } while (0) ++#define binn_set_int64(item,value) do { (item)->type = BINN_INT64; (item)->vint64 = value; (item)->ptr = &((item)->vint64); } while (0) ++ ++#define binn_set_uint(item,value) do { (item)->type = BINN_UINT32; (item)->vuint32 = value; (item)->ptr = &((item)->vuint32); } while (0) ++#define binn_set_uint64(item,value) do { (item)->type = BINN_UINT64; (item)->vuint64 = value; (item)->ptr = &((item)->vuint64); } while (0) ++ ++#define binn_set_float(item,value) do { (item)->type = BINN_FLOAT; (item)->vfloat = value; (item)->ptr = &((item)->vfloat); } while (0) ++#define binn_set_double(item,value) do { (item)->type = BINN_DOUBLE; (item)->vdouble = value; (item)->ptr = &((item)->vdouble); } while (0) ++ ++//#define binn_set_string(item,str,pfree) do { (item)->type = BINN_STRING; (item)->ptr = str; (item)->freefn = pfree; } while (0) ++//#define binn_set_blob(item,ptr,size,pfree) do { (item)->type = BINN_BLOB; (item)->ptr = ptr; (item)->freefn = pfree; (item)->size = size; } while (0) ++BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree); ++BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree); ++ ++ ++//#define binn_double(value) { (item)->type = BINN_DOUBLE; (item)->vdouble = value; (item)->ptr = &((item)->vdouble) } ++ ++ ++ ++// FOREACH MACROS ++// -------------- ++// ++// We must use these declarations inside the functions that will use the macros: ++// ++// binn_iter iter; ++// binn value; ++// char key[256]; // only for objects ++// int id; // only for maps ++ ++#define binn_object_foreach(object, key, value) \ ++ binn_iter_init(&iter, object, BINN_OBJECT); \ ++ while (binn_object_next(&iter, key, &value)) ++ ++#define binn_map_foreach(map, id, value) \ ++ binn_iter_init(&iter, map, BINN_MAP); \ ++ while (binn_map_next(&iter, &id, &value)) ++ ++#define binn_list_foreach(list, value) \ ++ binn_iter_init(&iter, list, BINN_LIST); \ ++ while (binn_list_next(&iter, &value)) ++ ++// If you need nested foreach loops, use the macros below for the nested loop ++// Also we need to add an additional declaration on the function to hold the iterator ++// We can add in the same line as the first iterator: ++// ++// binn_iter iter, iter2; ++ ++#define binn_object_foreach2(object, key, value) \ ++ binn_iter_init(&iter2, object, BINN_OBJECT); \ ++ while (binn_object_next(&iter2, key, &value)) ++ ++#define binn_map_foreach2(map, id, value) \ ++ binn_iter_init(&iter2, map, BINN_MAP); \ ++ while (binn_map_next(&iter2, &id, &value)) ++ ++#define binn_list_foreach2(list, value) \ ++ binn_iter_init(&iter2, list, BINN_LIST); \ ++ while (binn_list_next(&iter2, &value)) ++ ++ ++/*************************************************************************************/ ++/*** SET FUNCTIONS *******************************************************************/ ++/*************************************************************************************/ ++ ++ALWAYS_INLINE BOOL binn_list_add_int8(binn *list, signed char value) { ++ return binn_list_add(list, BINN_INT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_int16(binn *list, short value) { ++ return binn_list_add(list, BINN_INT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_int32(binn *list, int value) { ++ return binn_list_add(list, BINN_INT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_int64(binn *list, int64 value) { ++ return binn_list_add(list, BINN_INT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_uint8(binn *list, unsigned char value) { ++ return binn_list_add(list, BINN_UINT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_uint16(binn *list, unsigned short value) { ++ return binn_list_add(list, BINN_UINT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_uint32(binn *list, unsigned int value) { ++ return binn_list_add(list, BINN_UINT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_uint64(binn *list, uint64 value) { ++ return binn_list_add(list, BINN_UINT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_float(binn *list, float value) { ++ return binn_list_add(list, BINN_FLOAT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_double(binn *list, double value) { ++ return binn_list_add(list, BINN_FLOAT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_bool(binn *list, BOOL value) { ++ return binn_list_add(list, BINN_BOOL, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_null(binn *list) { ++ return binn_list_add(list, BINN_NULL, NULL, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_str(binn *list, char *str) { ++ return binn_list_add(list, BINN_STRING, str, 0); ++} ++ALWAYS_INLINE BOOL binn_list_add_blob(binn *list, void *ptr, int size) { ++ return binn_list_add(list, BINN_BLOB, ptr, size); ++} ++ALWAYS_INLINE BOOL binn_list_add_list(binn *list, void *list2) { ++ return binn_list_add(list, BINN_LIST, binn_ptr(list2), binn_size(list2)); ++} ++ALWAYS_INLINE BOOL binn_list_add_map(binn *list, void *map) { ++ return binn_list_add(list, BINN_MAP, binn_ptr(map), binn_size(map)); ++} ++ALWAYS_INLINE BOOL binn_list_add_object(binn *list, void *obj) { ++ return binn_list_add(list, BINN_OBJECT, binn_ptr(obj), binn_size(obj)); ++} ++ALWAYS_INLINE BOOL binn_list_add_value(binn *list, binn *value) { ++ return binn_list_add(list, value->type, binn_ptr(value), binn_size(value)); ++} ++ ++/*************************************************************************************/ ++ ++ALWAYS_INLINE BOOL binn_map_set_int8(binn *map, int id, signed char value) { ++ return binn_map_set(map, id, BINN_INT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_int16(binn *map, int id, short value) { ++ return binn_map_set(map, id, BINN_INT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_int32(binn *map, int id, int value) { ++ return binn_map_set(map, id, BINN_INT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_int64(binn *map, int id, int64 value) { ++ return binn_map_set(map, id, BINN_INT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_uint8(binn *map, int id, unsigned char value) { ++ return binn_map_set(map, id, BINN_UINT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_uint16(binn *map, int id, unsigned short value) { ++ return binn_map_set(map, id, BINN_UINT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_uint32(binn *map, int id, unsigned int value) { ++ return binn_map_set(map, id, BINN_UINT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_uint64(binn *map, int id, uint64 value) { ++ return binn_map_set(map, id, BINN_UINT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_float(binn *map, int id, float value) { ++ return binn_map_set(map, id, BINN_FLOAT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_double(binn *map, int id, double value) { ++ return binn_map_set(map, id, BINN_FLOAT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_bool(binn *map, int id, BOOL value) { ++ return binn_map_set(map, id, BINN_BOOL, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_null(binn *map, int id) { ++ return binn_map_set(map, id, BINN_NULL, NULL, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_str(binn *map, int id, char *str) { ++ return binn_map_set(map, id, BINN_STRING, str, 0); ++} ++ALWAYS_INLINE BOOL binn_map_set_blob(binn *map, int id, void *ptr, int size) { ++ return binn_map_set(map, id, BINN_BLOB, ptr, size); ++} ++ALWAYS_INLINE BOOL binn_map_set_list(binn *map, int id, void *list) { ++ return binn_map_set(map, id, BINN_LIST, binn_ptr(list), binn_size(list)); ++} ++ALWAYS_INLINE BOOL binn_map_set_map(binn *map, int id, void *map2) { ++ return binn_map_set(map, id, BINN_MAP, binn_ptr(map2), binn_size(map2)); ++} ++ALWAYS_INLINE BOOL binn_map_set_object(binn *map, int id, void *obj) { ++ return binn_map_set(map, id, BINN_OBJECT, binn_ptr(obj), binn_size(obj)); ++} ++ALWAYS_INLINE BOOL binn_map_set_value(binn *map, int id, binn *value) { ++ return binn_map_set(map, id, value->type, binn_ptr(value), binn_size(value)); ++} ++ ++/*************************************************************************************/ ++ ++ALWAYS_INLINE BOOL binn_object_set_int8(binn *obj, const char *key, signed char value) { ++ return binn_object_set(obj, key, BINN_INT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_int16(binn *obj, const char *key, short value) { ++ return binn_object_set(obj, key, BINN_INT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_int32(binn *obj, const char *key, int value) { ++ return binn_object_set(obj, key, BINN_INT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_int64(binn *obj, const char *key, int64 value) { ++ return binn_object_set(obj, key, BINN_INT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_uint8(binn *obj, const char *key, unsigned char value) { ++ return binn_object_set(obj, key, BINN_UINT8, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_uint16(binn *obj, const char *key, unsigned short value) { ++ return binn_object_set(obj, key, BINN_UINT16, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_uint32(binn *obj, const char *key, unsigned int value) { ++ return binn_object_set(obj, key, BINN_UINT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_uint64(binn *obj, const char *key, uint64 value) { ++ return binn_object_set(obj, key, BINN_UINT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_float(binn *obj, const char *key, float value) { ++ return binn_object_set(obj, key, BINN_FLOAT32, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_double(binn *obj, const char *key, double value) { ++ return binn_object_set(obj, key, BINN_FLOAT64, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_bool(binn *obj, const char *key, BOOL value) { ++ return binn_object_set(obj, key, BINN_BOOL, &value, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_null(binn *obj, const char *key) { ++ return binn_object_set(obj, key, BINN_NULL, NULL, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_str(binn *obj, const char *key, char *str) { ++ return binn_object_set(obj, key, BINN_STRING, str, 0); ++} ++ALWAYS_INLINE BOOL binn_object_set_blob(binn *obj, const char *key, void *ptr, int size) { ++ return binn_object_set(obj, key, BINN_BLOB, ptr, size); ++} ++ALWAYS_INLINE BOOL binn_object_set_list(binn *obj, const char *key, void *list) { ++ return binn_object_set(obj, key, BINN_LIST, binn_ptr(list), binn_size(list)); ++} ++ALWAYS_INLINE BOOL binn_object_set_map(binn *obj, const char *key, void *map) { ++ return binn_object_set(obj, key, BINN_MAP, binn_ptr(map), binn_size(map)); ++} ++ALWAYS_INLINE BOOL binn_object_set_object(binn *obj, const char *key, void *obj2) { ++ return binn_object_set(obj, key, BINN_OBJECT, binn_ptr(obj2), binn_size(obj2)); ++} ++ALWAYS_INLINE BOOL binn_object_set_value(binn *obj, const char *key, binn *value) { ++ return binn_object_set(obj, key, value->type, binn_ptr(value), binn_size(value)); ++} ++ ++/*************************************************************************************/ ++/*** GET FUNCTIONS *******************************************************************/ ++/*************************************************************************************/ ++ ++ALWAYS_INLINE BOOL binn_list_get_int8(void *list, int pos, signed char *pvalue) { ++ return binn_list_get(list, pos, BINN_INT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_int16(void *list, int pos, short *pvalue) { ++ return binn_list_get(list, pos, BINN_INT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_int32(void *list, int pos, int *pvalue) { ++ return binn_list_get(list, pos, BINN_INT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_int64(void *list, int pos, int64 *pvalue) { ++ return binn_list_get(list, pos, BINN_INT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_uint8(void *list, int pos, unsigned char *pvalue) { ++ return binn_list_get(list, pos, BINN_UINT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_uint16(void *list, int pos, unsigned short *pvalue) { ++ return binn_list_get(list, pos, BINN_UINT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_uint32(void *list, int pos, unsigned int *pvalue) { ++ return binn_list_get(list, pos, BINN_UINT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_uint64(void *list, int pos, uint64 *pvalue) { ++ return binn_list_get(list, pos, BINN_UINT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_float(void *list, int pos, float *pvalue) { ++ return binn_list_get(list, pos, BINN_FLOAT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_double(void *list, int pos, double *pvalue) { ++ return binn_list_get(list, pos, BINN_FLOAT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_bool(void *list, int pos, BOOL *pvalue) { ++ return binn_list_get(list, pos, BINN_BOOL, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_str(void *list, int pos, char **pvalue) { ++ return binn_list_get(list, pos, BINN_STRING, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_blob(void *list, int pos, void **pvalue, int *psize) { ++ return binn_list_get(list, pos, BINN_BLOB, pvalue, psize); ++} ++ALWAYS_INLINE BOOL binn_list_get_list(void *list, int pos, void **pvalue) { ++ return binn_list_get(list, pos, BINN_LIST, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_map(void *list, int pos, void **pvalue) { ++ return binn_list_get(list, pos, BINN_MAP, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_list_get_object(void *list, int pos, void **pvalue) { ++ return binn_list_get(list, pos, BINN_OBJECT, pvalue, NULL); ++} ++ ++/***************************************************************************/ ++ ++ALWAYS_INLINE BOOL binn_map_get_int8(void *map, int id, signed char *pvalue) { ++ return binn_map_get(map, id, BINN_INT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_int16(void *map, int id, short *pvalue) { ++ return binn_map_get(map, id, BINN_INT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_int32(void *map, int id, int *pvalue) { ++ return binn_map_get(map, id, BINN_INT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_int64(void *map, int id, int64 *pvalue) { ++ return binn_map_get(map, id, BINN_INT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_uint8(void *map, int id, unsigned char *pvalue) { ++ return binn_map_get(map, id, BINN_UINT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_uint16(void *map, int id, unsigned short *pvalue) { ++ return binn_map_get(map, id, BINN_UINT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_uint32(void *map, int id, unsigned int *pvalue) { ++ return binn_map_get(map, id, BINN_UINT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_uint64(void *map, int id, uint64 *pvalue) { ++ return binn_map_get(map, id, BINN_UINT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_float(void *map, int id, float *pvalue) { ++ return binn_map_get(map, id, BINN_FLOAT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_double(void *map, int id, double *pvalue) { ++ return binn_map_get(map, id, BINN_FLOAT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_bool(void *map, int id, BOOL *pvalue) { ++ return binn_map_get(map, id, BINN_BOOL, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_str(void *map, int id, char **pvalue) { ++ return binn_map_get(map, id, BINN_STRING, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_blob(void *map, int id, void **pvalue, int *psize) { ++ return binn_map_get(map, id, BINN_BLOB, pvalue, psize); ++} ++ALWAYS_INLINE BOOL binn_map_get_list(void *map, int id, void **pvalue) { ++ return binn_map_get(map, id, BINN_LIST, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_map(void *map, int id, void **pvalue) { ++ return binn_map_get(map, id, BINN_MAP, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_map_get_object(void *map, int id, void **pvalue) { ++ return binn_map_get(map, id, BINN_OBJECT, pvalue, NULL); ++} ++ ++/***************************************************************************/ ++ ++// usage: ++// if (binn_object_get_int32(obj, "key", &value) == FALSE) xxx; ++ ++ALWAYS_INLINE BOOL binn_object_get_int8(void *obj, const char *key, signed char *pvalue) { ++ return binn_object_get(obj, key, BINN_INT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_int16(void *obj, const char *key, short *pvalue) { ++ return binn_object_get(obj, key, BINN_INT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_int32(void *obj, const char *key, int *pvalue) { ++ return binn_object_get(obj, key, BINN_INT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_int64(void *obj, const char *key, int64 *pvalue) { ++ return binn_object_get(obj, key, BINN_INT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_uint8(void *obj, const char *key, unsigned char *pvalue) { ++ return binn_object_get(obj, key, BINN_UINT8, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_uint16(void *obj, const char *key, unsigned short *pvalue) { ++ return binn_object_get(obj, key, BINN_UINT16, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_uint32(void *obj, const char *key, unsigned int *pvalue) { ++ return binn_object_get(obj, key, BINN_UINT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_uint64(void *obj, const char *key, uint64 *pvalue) { ++ return binn_object_get(obj, key, BINN_UINT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_float(void *obj, const char *key, float *pvalue) { ++ return binn_object_get(obj, key, BINN_FLOAT32, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_double(void *obj, const char *key, double *pvalue) { ++ return binn_object_get(obj, key, BINN_FLOAT64, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_bool(void *obj, const char *key, BOOL *pvalue) { ++ return binn_object_get(obj, key, BINN_BOOL, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_str(void *obj, const char *key, char **pvalue) { ++ return binn_object_get(obj, key, BINN_STRING, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_blob(void *obj, const char *key, void **pvalue, int *psize) { ++ return binn_object_get(obj, key, BINN_BLOB, pvalue, psize); ++} ++ALWAYS_INLINE BOOL binn_object_get_list(void *obj, const char *key, void **pvalue) { ++ return binn_object_get(obj, key, BINN_LIST, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_map(void *obj, const char *key, void **pvalue) { ++ return binn_object_get(obj, key, BINN_MAP, pvalue, NULL); ++} ++ALWAYS_INLINE BOOL binn_object_get_object(void *obj, const char *key, void **pvalue) { ++ return binn_object_get(obj, key, BINN_OBJECT, pvalue, NULL); ++} ++ ++/***************************************************************************/ ++ ++BOOL APIENTRY binn_get_int32(binn *value, int *pint); ++BOOL APIENTRY binn_get_int64(binn *value, int64 *pint); ++BOOL APIENTRY binn_get_double(binn *value, double *pfloat); ++BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool); ++char * APIENTRY binn_get_str(binn *value); ++ ++// boolean string values: ++// 1, true, yes, on ++// 0, false, no, off ++ ++// boolean number values: ++// !=0 [true] ++// ==0 [false] ++ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif //BINN_H +diff -Nur openssh-9.3p1.orig/channels.c openssh-9.3p1/channels.c +--- openssh-9.3p1.orig/channels.c 2024-07-10 09:07:09.714081799 +0200 ++++ openssh-9.3p1/channels.c 2024-07-10 11:29:27.911096803 +0200 +@@ -229,6 +229,9 @@ + /* Setup helper */ + static void channel_handler_init(struct ssh_channels *sc); + ++static int hpn_disabled = 0; ++static int hpn_buffer_size = 2 * 1024 * 1024; ++ + /* -- channel core */ + + void +@@ -484,6 +487,9 @@ + (c->output = sshbuf_new()) == NULL || + (c->extended = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); ++ sshbuf_relabel(c->input, "channel input"); ++ sshbuf_relabel(c->output, "channel output"); ++ sshbuf_relabel(c->extended, "channel extended"); + if ((r = sshbuf_set_max_size(c->input, CHAN_INPUT_MAX)) != 0) + fatal_fr(r, "sshbuf_set_max_size"); + c->ostate = CHAN_OUTPUT_OPEN; +@@ -495,6 +501,7 @@ + c->local_window = window; + c->local_window_max = window; + c->local_maxpacket = maxpack; ++ c->dynamic_window = 0; + c->remote_name = xstrdup(remote_name); + c->ctl_chan = -1; + c->delayed = 1; /* prevent call to channel_post handler */ +@@ -1203,6 +1210,28 @@ + c->io_want = SSH_CHAN_IO_SOCK_W; + } + ++static int ++channel_tcpwinsz(struct ssh *ssh) ++{ ++ u_int32_t tcpwinsz = 0; ++ socklen_t optsz = sizeof(tcpwinsz); ++ int ret = -1; ++ ++ /* if we aren't on a socket return 128KB */ ++ if (!ssh_packet_connection_is_on_socket(ssh)) ++ return 128 * 1024; ++ ++ ret = getsockopt(ssh_packet_get_connection_in(ssh), ++ SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz); ++ /* return no more than SSHBUF_SIZE_MAX (currently 256MB) */ ++ if ((ret == 0) && tcpwinsz > SSHBUF_SIZE_MAX) ++ tcpwinsz = SSHBUF_SIZE_MAX; ++ ++ debug3_f("tcp connection %d, Receive window: %d", ++ ssh_packet_get_connection_in(ssh), tcpwinsz); ++ return tcpwinsz; ++} ++ + static void + channel_pre_open(struct ssh *ssh, Channel *c) + { +@@ -2303,24 +2332,55 @@ + { + int r; + ++ /* going back to a set denominator of 2. Prior versions had a ++ * dynamic denominator based on the size of the buffer. This may ++ * have been helpful in some situations but it isn't helping in ++ * the general case -cjr 6/30/23 */ + if (c->type == SSH_CHANNEL_OPEN && + !(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && + ((c->local_window_max - c->local_window > + c->local_maxpacket*3) || + c->local_window < c->local_window_max/2) && + c->local_consumed > 0) { ++ u_int addition = 0; ++ u_int32_t tcpwinsz = channel_tcpwinsz(ssh); ++ /* adjust max window size if we are in a dynamic environment ++ * and the tcp receive buffer is larger than the ssh window */ ++ if (c->dynamic_window && (tcpwinsz > c->local_window_max)) { ++ if (c->hpn_buffer_limit) { ++ /* limit window growth to prevent buffer issues ++ * still not sure what is causing the buffer issues ++ * but it may be an issue with c->local_consumed not being ++ * handled properly in the cases of bottenecked IO to the ++ * wfd endpoint. This does have an impact on throughput ++ * as we're essentially maxing out local_window_max to ++ * half of the window size */ ++ addition = (tcpwinsz/2 - c->local_window_max); ++ } ++ else { ++ /* aggressively grow the window */ ++ addition = tcpwinsz - c->local_window_max; ++ } ++ c->local_window_max += addition; ++ sshbuf_set_window_max(c->output, c->local_window_max); ++ sshbuf_set_window_max(c->input, c->local_window_max); ++ //c->output->window_max = c->local_window_max; ++ //c->input->window_max = c->local_window_max; ++ debug("Channel %d: Window growth to %d by %d bytes",c->self, ++ c->local_window_max, addition); ++ } + if (!c->have_remote_id) + fatal_f("channel %d: no remote id", c->self); + if ((r = sshpkt_start(ssh, + SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || + (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || +- (r = sshpkt_put_u32(ssh, c->local_consumed)) != 0 || ++ (r = sshpkt_put_u32(ssh, c->local_consumed + addition)) != 0 || + (r = sshpkt_send(ssh)) != 0) { + fatal_fr(r, "channel %i", c->self); + } +- debug2("channel %d: window %d sent adjust %d", c->self, +- c->local_window, c->local_consumed); +- c->local_window += c->local_consumed; ++ debug3_f("channel %d: window %d sent adjust %d", c->self, ++ c->local_window, c->local_consumed + addition); ++ c->local_window += c->local_consumed + addition; + c->local_consumed = 0; + } + return 1; +@@ -2939,7 +2999,6 @@ + (r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "channel %i: send datagram", c->self); + c->remote_window -= plen; +- return; + } + + /* Enqueue packet for buffered data. */ +@@ -3602,7 +3661,7 @@ + error_fr(r, "parse adjust"); + ssh_packet_disconnect(ssh, "Invalid window adjust message"); + } +- debug2("channel %d: rcvd adjust %u", c->self, adjust); ++ debug3_f("channel %d: rcvd adjust %u", c->self, adjust); + if ((new_rwin = c->remote_window + adjust) < c->remote_window) { + fatal("channel %d: adjust %u overflows remote window %u", + c->self, adjust, c->remote_window); +@@ -3717,6 +3776,14 @@ + return addr; + } + ++void ++channel_set_hpn(int external_hpn_disabled, int external_hpn_buffer_size) ++{ ++ hpn_disabled = external_hpn_disabled; ++ hpn_buffer_size = external_hpn_buffer_size; ++ debug("HPN Disabled: %d, HPN Buffer Size: %d", hpn_disabled, hpn_buffer_size); ++} ++ + static int + channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, + struct Forward *fwd, int *allocated_listen_port, +@@ -3856,8 +3923,10 @@ + } + + /* Allocate a channel number for the socket. */ ++ /* explicitly test for hpn disabled option. if true use smaller window size */ + c = channel_new(ssh, "port-listener", type, sock, sock, -1, +- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, ++ hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : hpn_buffer_size, ++ CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); + c->path = xstrdup(host); + c->host_port = fwd->connect_port; +@@ -5041,7 +5110,8 @@ + sock = socks[n]; + nc = channel_new(ssh, "x11-listener", + SSH_CHANNEL_X11_LISTENER, sock, sock, -1, +- CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, ++ hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : hpn_buffer_size, ++ CHAN_X11_PACKET_DEFAULT, + 0, "X11 inet listener", 1); + nc->single_connection = single_connection; + (*chanids)[n] = nc->self; +diff -Nur openssh-9.3p1.orig/channels.h openssh-9.3p1/channels.h +--- openssh-9.3p1.orig/channels.h 2024-07-10 09:07:09.675081685 +0200 ++++ openssh-9.3p1/channels.h 2024-07-10 11:14:08.804403496 +0200 +@@ -173,6 +173,8 @@ + u_int local_window_max; + u_int local_consumed; + u_int local_maxpacket; ++ int dynamic_window; ++ int hpn_buffer_limit; + int extended_usage; + int single_connection; + +@@ -254,7 +256,7 @@ + #define SSH_CHAN_IO_SOCK (SSH_CHAN_IO_SOCK_R|SSH_CHAN_IO_SOCK_W) + + /* Read buffer size */ +-#define CHAN_RBUF (16*1024) ++#define CHAN_RBUF CHAN_SES_PACKET_DEFAULT + + /* Maximum size for direct reads to buffers */ + #define CHANNEL_MAX_READ CHAN_SES_PACKET_DEFAULT +@@ -396,4 +398,6 @@ + void chan_write_failed(struct ssh *, Channel *); + void chan_obuf_empty(struct ssh *, Channel *); + ++/* hpn handler */ ++void channel_set_hpn(int, int); + #endif +diff -Nur openssh-9.3p1.orig/cipher.c openssh-9.3p1/cipher.c +--- openssh-9.3p1.orig/cipher.c 2024-07-10 09:07:09.734081857 +0200 ++++ openssh-9.3p1/cipher.c 2024-07-10 09:09:55.899568399 +0200 +@@ -48,23 +48,36 @@ + #include "sshbuf.h" + #include "ssherr.h" + #include "digest.h" ++#include "log.h" + + #include "openbsd-compat/openssl-compat.h" + ++/* for provider functions */ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++#include ++#include ++#include ++#endif ++ + #ifndef WITH_OPENSSL + #define EVP_CIPHER_CTX void ++#define EVP_CIPHER void ++#else ++/* for multi-threaded aes-ctr cipher */ ++extern const EVP_CIPHER *evp_aes_ctr_mt(void); + #endif + + struct sshcipher_ctx { + int plaintext; + int encrypt; + EVP_CIPHER_CTX *evp; ++ const EVP_CIPHER *meth_ptr; /*used to free memory in aes_ctr_mt */ + struct chachapoly_ctx *cp_ctx; + struct aesctr_ctx ac_ctx; /* XXX union with evp? */ + const struct sshcipher *cipher; + }; + +-static const struct sshcipher ciphers[] = { ++static struct sshcipher ciphers[] = { + #ifdef WITH_OPENSSL + #ifndef OPENSSL_NO_DES + { "3des-cbc", 8, 24, 0, 0, CFLAG_CBC, EVP_des_ede3_cbc }, +@@ -131,12 +144,62 @@ + #endif + } + ++/* used to get the cipher name so when force rekeying to handle the ++ * single to multithreaded ctr cipher swap we only rekey when appropriate ++ */ ++const char * ++cipher_ctx_name(const struct sshcipher_ctx *cc) ++{ ++ return cc->cipher->name; ++} ++ + u_int + cipher_blocksize(const struct sshcipher *c) + { + return (c->block_size); + } + ++uint64_t ++cipher_rekey_blocks(const struct sshcipher *c) ++{ ++ /* ++ * Chacha20-Poly1305 does not benefit from data-based rekeying, ++ * per "The Security of ChaCha20-Poly1305 in the Multi-user Setting", ++ * Degabriele, J. P., Govinden, J, Gunther, F. and Paterson K. ++ * ACM CCS 2021; https://eprint.iacr.org/2023/085.pdf ++ * ++ * Cryptanalysis aside, we do still want do need to prevent the SSH ++ * sequence number wrapping and also to rekey to provide some ++ * protection for long lived sessions against key disclosure at the ++ * endpoints, so arrange for rekeying every 2**32 blocks as the ++ * 128-bit block ciphers do (i.e. every 32GB data). ++ */ ++ if ((c->flags & CFLAG_CHACHAPOLY) != 0) ++ return (uint64_t)1 << 32; ++ ++ /* there is no actual need to rekey the NULL cipher but ++ * rekeying is a necessary step. In part, as mentioned above, ++ * to keep the seqnr from wrapping. So we set it to the ++ * maximum possible -cjr 4/10/23 */ ++ if ((c->flags & CFLAG_NONE) != 0) ++ return (uint64_t)1 << 32; ++ ++ /* ++ * The 2^(blocksize*2) limit is too expensive for 3DES, ++ * so enforce a 1GB data limit for small blocksizes. ++ * See discussion in RFC4344 section 3.2. ++ */ ++ if (c->block_size < 16) ++ return ((uint64_t)1 << 30) / c->block_size; ++ /* ++ * Otherwise, use the RFC4344 s3.2 recommendation of 2**(L/4) blocks ++ * before rekeying where L is the blocksize in bits. ++ * Most other ciphers have a 128 bit blocksize, so this equates to ++ * 2**32 blocks / 64GB data. ++ */ ++ return (uint64_t)1 << (c->block_size * 2); ++} ++ + u_int + cipher_keylen(const struct sshcipher *c) + { +@@ -180,10 +243,10 @@ + return cc->plaintext; + } + +-const struct sshcipher * ++struct sshcipher * + cipher_by_name(const char *name) + { +- const struct sshcipher *c; ++ struct sshcipher *c; + for (c = ciphers; c->name != NULL; c++) + if (strcmp(c->name, name) == 0) + return c; +@@ -205,7 +268,8 @@ + for ((p = strsep(&cp, CIPHER_SEP)); p && *p != '\0'; + (p = strsep(&cp, CIPHER_SEP))) { + c = cipher_by_name(p); +- if (c == NULL || (c->flags & CFLAG_INTERNAL) != 0) { ++ if (c == NULL || ((c->flags & CFLAG_INTERNAL) != 0 && ++ (c->flags & CFLAG_NONE) != 0)) { + free(cipher_list); + return 0; + } +@@ -226,7 +290,7 @@ + int + cipher_init(struct sshcipher_ctx **ccp, const struct sshcipher *cipher, + const u_char *key, u_int keylen, const u_char *iv, u_int ivlen, +- int do_encrypt) ++ int do_encrypt, int post_auth) + { + struct sshcipher_ctx *cc = NULL; + int ret = SSH_ERR_INTERNAL_ERROR; +@@ -241,6 +305,7 @@ + + cc->plaintext = (cipher->flags & CFLAG_NONE) != 0; + cc->encrypt = do_encrypt; ++ cc->meth_ptr = NULL; + + if (keylen < cipher->key_len || + (iv != NULL && ivlen < cipher_ivlen(cipher))) { +@@ -273,6 +338,53 @@ + ret = SSH_ERR_ALLOC_FAIL; + goto out; + } ++ /* the following block is for AES-CTR-MT cipher switching ++ * if we are using the ctr cipher and we are post-auth then ++ * start the threaded cipher. If OSSL supports providers (OSSL 3.0+) then ++ * we load our hpnssh provider. If it doesn't (OSSL < 1.1) then we use the ++ * _meth_new process found in cipher-ctr-mt.c */ ++ if (strstr(cc->cipher->name, "ctr") && post_auth) { ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ /* this version of openssl uses providers */ ++ OSSL_LIB_CTX *aes_lib = NULL; /* probably not needed */ ++ OSSL_PROVIDER *aes_mt_provider = NULL; ++ type = NULL; ++ ++ if (OSSL_PROVIDER_add_builtin(aes_lib, "hpnssh", ++ OSSL_provider_init) != 1) { ++ fatal("Failed to add HPNSSH provider for AES-CTR"); ++ } ++ aes_mt_provider = OSSL_PROVIDER_load(aes_lib, "hpnssh"); ++ ++ if (aes_mt_provider != NULL) { ++ /* use the previous key length to determine which cipher to load */ ++ if (cipher->key_len == 32) ++ type = EVP_CIPHER_fetch(aes_lib, "aes_ctr_mt_256", NULL); ++ if (cipher->key_len == 24) ++ type = EVP_CIPHER_fetch(aes_lib, "aes_ctr_mt_192", NULL); ++ if (cipher->key_len == 16) ++ type = EVP_CIPHER_fetch(aes_lib, "aes_ctr_mt_128", NULL); ++ if (type == NULL) { ++ ERR_print_errors_fp(stderr); ++ fatal("FAILED TO LOAD aes_ctr_mt"); ++ } else { ++ debug("LOADED aes_ctr_mt"); ++ } ++ } ++ else { ++ ERR_print_errors_fp(stderr); ++ fatal("Failed to load HPN-SSH AES-CTR-MT provider."); ++ } ++#else ++ type = (*evp_aes_ctr_mt)(); /* see cipher-ctr-mt.c */ ++ /* we need to free this later if using aes_ctr_mt ++ * under OSSL 1.1. Honestly, we could avoid this by making ++ * it a global in cipher-ctr_mt.c and exporting it here ++ * then we'd only have to call EVP_CIPHER_meth once but this ++ * works for now. TODO: This. cjr 02.22.2023 */ ++ cc->meth_ptr = type; ++#endif /* OPENSSL_VERSION_NUMBER */ ++ } /* if (strstr()) */ + if (EVP_CipherInit(cc->evp, type, NULL, (u_char *)iv, + (do_encrypt == CIPHER_ENCRYPT)) == 0) { + ret = SSH_ERR_LIBCRYPTO_ERROR; +@@ -411,6 +523,16 @@ + #ifdef WITH_OPENSSL + EVP_CIPHER_CTX_free(cc->evp); + cc->evp = NULL; ++ /* if meth_ptr isn't null then we are using the aes_ctr_mt ++ * evp_cipher_meth_new() in cipher-ctr-mt.c under OSSL 1.1 ++ * if we don't explicitly free it then, even though we free ++ * the ctx it is a part of it doesn't get freed. So... ++ * cjr 2/7/2023 ++ */ ++ if (cc->meth_ptr != NULL) { ++ EVP_CIPHER_meth_free((void *)(EVP_CIPHER *)cc->meth_ptr); ++ cc->meth_ptr = NULL; ++ } + #endif + freezero(cc, sizeof(*cc)); + } +diff -Nur openssh-9.3p1.orig/cipher-chachapoly.c openssh-9.3p1/cipher-chachapoly.c +--- openssh-9.3p1.orig/cipher-chachapoly.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/cipher-chachapoly.c 2024-07-10 09:09:55.900568402 +0200 +@@ -89,7 +89,7 @@ + if (!do_encrypt) { + const u_char *tag = src + aadlen + len; + +- poly1305_auth(expected_tag, src, aadlen + len, poly_key); ++ poly1305_auth(NULL, expected_tag, src, aadlen + len, poly_key); + if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { + r = SSH_ERR_MAC_INVALID; + goto out; +@@ -109,7 +109,7 @@ + + /* If encrypting, calculate and append tag */ + if (do_encrypt) { +- poly1305_auth(dest + aadlen + len, dest, aadlen + len, ++ poly1305_auth(NULL, dest + aadlen + len, dest, aadlen + len, + poly_key); + } + r = 0; +diff -Nur openssh-9.3p1.orig/cipher-chachapoly-libcrypto.c openssh-9.3p1/cipher-chachapoly-libcrypto.c +--- openssh-9.3p1.orig/cipher-chachapoly-libcrypto.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/cipher-chachapoly-libcrypto.c 2024-07-10 09:09:55.900568402 +0200 +@@ -35,8 +35,18 @@ + #include "ssherr.h" + #include "cipher-chachapoly.h" + ++ ++/* using the EVP_MAC interface for poly1305 is significantly ++ * faster than the version bundled with OpenSSH. However, ++ * this interface is only available in OpenSSL 3.0+ ++ * -cjr 10/21/2022 */ + struct chachapoly_ctx { + EVP_CIPHER_CTX *main_evp, *header_evp; ++#ifdef OPENSSL_HAVE_POLY_EVP ++ EVP_MAC_CTX *poly_ctx; ++#else ++ char *poly_ctx; ++#endif + }; + + struct chachapoly_ctx * +@@ -57,6 +67,15 @@ + goto fail; + if (EVP_CIPHER_CTX_iv_length(ctx->header_evp) != 16) + goto fail; ++#ifdef OPENSSL_HAVE_POLY_EVP ++ EVP_MAC *mac = NULL; ++ if ((mac = EVP_MAC_fetch(NULL, "POLY1305", NULL)) == NULL) ++ goto fail; ++ if ((ctx->poly_ctx = EVP_MAC_CTX_new(mac)) == NULL) ++ goto fail; ++#else ++ ctx->poly_ctx = NULL; ++#endif + return ctx; + fail: + chachapoly_free(ctx); +@@ -70,6 +89,9 @@ + return; + EVP_CIPHER_CTX_free(cpctx->main_evp); + EVP_CIPHER_CTX_free(cpctx->header_evp); ++#ifdef OPENSSL_HAVE_POLY_EVP ++ EVP_MAC_CTX_free(cpctx->poly_ctx); ++#endif + freezero(cpctx, sizeof(*cpctx)); + } + +@@ -108,7 +130,7 @@ + if (!do_encrypt) { + const u_char *tag = src + aadlen + len; + +- poly1305_auth(expected_tag, src, aadlen + len, poly_key); ++ poly1305_auth(ctx->poly_ctx, expected_tag, src, aadlen + len, poly_key); + if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { + r = SSH_ERR_MAC_INVALID; + goto out; +@@ -134,7 +156,7 @@ + + /* If encrypting, calculate and append tag */ + if (do_encrypt) { +- poly1305_auth(dest + aadlen + len, dest, aadlen + len, ++ poly1305_auth(ctx->poly_ctx, dest + aadlen + len, dest, aadlen + len, + poly_key); + } + r = 0; +diff -Nur openssh-9.3p1.orig/cipher-ctr-mt.c openssh-9.3p1/cipher-ctr-mt.c +--- openssh-9.3p1.orig/cipher-ctr-mt.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/cipher-ctr-mt.c 2024-07-10 09:37:30.560413838 +0200 +@@ -0,0 +1,679 @@ ++/* ++ * OpenSSH Multi-threaded AES-CTR Cipher ++ * ++ * Author: Benjamin Bennett ++ * Author: Mike Tasota ++ * Author: Chris Rapier ++ * Copyright (c) 2008-2021 Pittsburgh Supercomputing Center. All rights reserved. ++ * ++ * Based on original OpenSSH AES-CTR cipher. Small portions remain unchanged, ++ * Copyright (c) 2003 Markus Friedl ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++#include "includes.h" ++ ++#if defined(WITH_OPENSSL) ++#if OPENSSL_VERSION_NUMBER < 0x30000000UL ++#include ++ ++#include ++#include ++ ++#include ++ ++#include "xmalloc.h" ++#include "log.h" ++#include ++#include "uthash.h" ++ ++/* compatibility with old or broken OpenSSL versions */ ++#include "openbsd-compat/openssl-compat.h" ++ ++#ifndef USE_BUILTIN_RIJNDAEL ++#include ++#endif ++ ++#include ++ ++#ifdef __APPLE__ ++#include ++#include ++#endif ++ ++/* note regarding threads and queues */ ++/* initially this cipher was written in a way that ++ * the key stream was generated in a per cipher block ++ * loop. For example, if the key stream queue length was ++ * 16k and the cipher block size was 16 bytes it would ++ * fill the queue 16 bytes at a time. Mitch Dorrell pointed ++ * out that we could fill the queue in once call eliminating ++ * loop and multiple calls to EVP_EncryptUpdate. Doing so ++ * dramatically reduced CPU load in the threads and indicated ++ * that we could also eliminate most of the threads and queues ++ * as it would take far less time for a queue to ebter KQ_FULL ++ * state. As such, we've reduced the default number of threads ++ * and queues from 2 and 8 (respectively) to 1 and 2. We've also ++ * elimnated the need to determine the physical number of cores on ++ * the system and, if the user desires, can spin up more threads ++ * using an environment variable. Additionally, queues is now fixed ++ * at thread_count + 1. ++ * cjr 10/19/2022 */ ++ ++/*-------------------- TUNABLES --------------------*/ ++/* maximum number of threads and queues */ ++#define MAX_THREADS 4 ++#define MAX_NUMKQ (MAX_THREADS + 1) ++ ++/* Number of pregen threads to use */ ++/* this is a default value. The actual number is ++ * determined during init as a function of the number ++ * of available cores */ ++int cipher_threads = 1; ++ ++/* Number of keystream queues */ ++/* ideally this should be large enough so that there is ++ * always a key queue for a thread to work on ++ * so maybe double of the number of threads. Again this ++ * is a default and the actual value is determined in init*/ ++int numkq = 2; ++ ++/* Length of a keystream queue */ ++/* one queue holds 512KB (1024 * 32 * 16) of key data ++ * being that the queues are destroyed after a rekey ++ * and at leats one has to be fully filled prior to ++ * enciphering data we don't want this to be too large */ ++#define KQLEN (1024 * 32) ++ ++/* Processor cacheline length */ ++#define CACHELINE_LEN 64 ++ ++/* Can the system do unaligned loads natively? */ ++#if defined(__aarch64__) || \ ++ defined(__i386__) || \ ++ defined(__powerpc__) || \ ++ defined(__x86_64__) ++# define CIPHER_UNALIGNED_OK ++#endif ++#if defined(__SIZEOF_INT128__) ++# define CIPHER_INT128_OK ++#endif ++/*-------------------- END TUNABLES --------------------*/ ++ ++#define HAVE_NONE 0 ++#define HAVE_KEY 1 ++#define HAVE_IV 2 ++int X = 0; ++ ++const EVP_CIPHER *evp_aes_ctr_mt(void); ++ ++/* Keystream Queue state */ ++enum { ++ KQINIT, ++ KQEMPTY, ++ KQFILLING, ++ KQFULL, ++ KQDRAINING ++}; ++ ++/* Keystream Queue struct */ ++struct kq { ++ u_char keys[KQLEN][AES_BLOCK_SIZE]; /* [32768][16B] */ ++ u_char ctr[AES_BLOCK_SIZE]; /* 16B */ ++ u_char pad0[CACHELINE_LEN]; ++ pthread_mutex_t lock; ++ pthread_cond_t cond; ++ int qstate; ++ u_char pad1[CACHELINE_LEN]; ++}; ++ ++/* Context struct */ ++struct ssh_aes_ctr_ctx_mt ++{ ++ long unsigned int struct_id; ++ int keylen; ++ int state; ++ int qidx; ++ int ridx; ++ int id[MAX_THREADS]; /* 32 */ ++ AES_KEY aes_key; ++ const u_char *orig_key; ++ u_char aes_counter[AES_BLOCK_SIZE]; /* 16B */ ++ pthread_t tid[MAX_THREADS]; /* 32 */ ++ pthread_rwlock_t tid_lock; ++ struct kq q[MAX_NUMKQ]; /* 33 */ ++#ifdef __APPLE__ ++ pthread_rwlock_t stop_lock; ++ int exit_flag; ++#endif /* __APPLE__ */ ++}; ++ ++/* this defines the hash and elements of evp context pointers ++ * that are created in thread_loop. We use this to clear and ++ * free the contexts in stop_and_prejoin ++ */ ++struct aes_mt_ctx_ptrs { ++ pthread_t tid; ++ EVP_CIPHER_CTX *pointer; /* 32 */ ++ UT_hash_handle hh; ++}; ++ ++/* globals */ ++/* how we increment the id the structs we create */ ++long unsigned int global_struct_id = 0; ++ ++/* keep a copy of the pointers created in thread_loop to free later */ ++struct aes_mt_ctx_ptrs *evp_ptrs = NULL; ++ ++/* ++ * Add num to counter 'ctr' ++ */ ++static void ++ssh_ctr_add(u_char *ctr, uint32_t num, u_int len) ++{ ++ int i; ++ uint16_t n; ++ ++ for (n = 0, i = len - 1; i >= 0 && (num || n); i--) { ++ n = ctr[i] + (num & 0xff) + n; ++ num >>= 8; ++ ctr[i] = n & 0xff; ++ n >>= 8; ++ } ++} ++ ++/* ++ * Threads may be cancelled in a pthread_cond_wait, we must free the mutex ++ */ ++static void ++thread_loop_cleanup(void *x) ++{ ++ pthread_mutex_unlock((pthread_mutex_t *)x); ++} ++ ++#ifdef __APPLE__ ++/* Check if we should exit, we are doing both cancel and exit condition ++ * since on OSX threads seem to occasionally fail to notice when they have ++ * been cancelled. We want to have a backup to make sure that we won't hang ++ * when the main process join()-s the cancelled thread. ++ */ ++static void ++thread_loop_check_exit(struct ssh_aes_ctr_ctx_mt *c) ++{ ++ int exit_flag; ++ ++ pthread_rwlock_rdlock(&c->stop_lock); ++ exit_flag = c->exit_flag; ++ pthread_rwlock_unlock(&c->stop_lock); ++ ++ if (exit_flag) ++ pthread_exit(NULL); ++} ++#else ++# define thread_loop_check_exit(s) ++#endif /* __APPLE__ */ ++ ++/* ++ * Helper function to terminate the helper threads ++ */ ++static void ++stop_and_join_pregen_threads(struct ssh_aes_ctr_ctx_mt *c) ++{ ++ int i; ++ ++#ifdef __APPLE__ ++ /* notify threads that they should exit */ ++ pthread_rwlock_wrlock(&c->stop_lock); ++ c->exit_flag = TRUE; ++ pthread_rwlock_unlock(&c->stop_lock); ++#endif /* __APPLE__ */ ++ ++ /* Cancel pregen threads */ ++ for (i = 0; i < cipher_threads; i++) { ++ debug ("Canceled %lu (%lu,%d)", c->tid[i], c->struct_id, c->id[i]); ++ pthread_cancel(c->tid[i]); ++ } ++ for (i = 0; i < numkq; i++) { ++ pthread_mutex_lock(&c->q[i].lock); ++ pthread_cond_broadcast(&c->q[i].cond); ++ pthread_mutex_unlock(&c->q[i].lock); ++ } ++ for (i = 0; i < cipher_threads; i++) { ++ if (pthread_kill(c->tid[i], 0) != 0) ++ debug3("AES-CTR MT pthread_join failure: Invalid thread id %lu in %s", ++ c->tid[i], __FUNCTION__); ++ else { ++ debug ("Joining %lu (%lu, %d)", c->tid[i], c->struct_id, c->id[i]); ++ pthread_mutex_destroy(&c->q[i].lock); ++ pthread_cond_destroy(&c->q[i].cond); ++ pthread_join(c->tid[i], NULL); ++ /* this finds the entry in the hash that corresponding to the ++ * thread id. That's used to find the pointer to the cipher struct ++ * created in thread_loop. */ ++ struct aes_mt_ctx_ptrs *ptr; ++ HASH_FIND_INT(evp_ptrs, &c->tid[i], ptr); ++ EVP_CIPHER_CTX_free(ptr->pointer); ++ HASH_DEL(evp_ptrs, ptr); ++ free(ptr); } ++ } ++ pthread_rwlock_destroy(&c->tid_lock); ++} ++ ++/* ++ * The life of a pregen thread: ++ * Find empty keystream queues and fill them using their counter. ++ * When done, update counter for the next fill. ++ */ ++/* previously this used the low level interface which is, sadly, ++ * slower than the EVP interface by a long shot. The original ctx (from the ++ * body of the code) isn't passed in here but we have the key and the counter ++ * which means we should be able to create the exact same ctx and use that to ++ * fill the keystream queues. I'm concerned about additional overhead but the ++ * additional speed from AESNI should make up for it. */ ++/* The above comment was made when I thought I needed to do a new EVP init for ++ * each counter increment. Turns out not to be the case -cjr 10/15/21*/ ++ ++static void * ++thread_loop(void *x) ++{ ++ EVP_CIPHER_CTX *aesni_ctx; ++ struct ssh_aes_ctr_ctx_mt *c = x; ++ struct kq *q; ++ struct aes_mt_ctx_ptrs *ptr; ++ int qidx; ++ pthread_t first_tid; ++ int outlen; ++ u_char mynull[KQLEN * AES_BLOCK_SIZE]; ++ memset(&mynull, 0, KQLEN * AES_BLOCK_SIZE); ++ ++ /* get the thread id to see if this is the first one */ ++ pthread_rwlock_rdlock(&c->tid_lock); ++ first_tid = c->tid[0]; ++ pthread_rwlock_unlock(&c->tid_lock); ++ ++ /* create the context for this thread */ ++ aesni_ctx = EVP_CIPHER_CTX_new(); ++ ++ /* keep track of the pointer for the evp in this struct ++ * so we can free it later. So we place it in a hash indexed on the ++ * thread id, which is available to us in the free function. ++ * Note, the thread id isn't necessary unique across rekeys but ++ * that's okay as they are unique during a key. */ ++ ptr = malloc(sizeof *ptr); /*freed in stop & prejoin */ ++ ptr->tid = pthread_self(); /* index for hash */ ++ ptr->pointer = aesni_ctx; ++ HASH_ADD_INT(evp_ptrs, tid, ptr); ++ ++ /* initialize the cipher ctx with the key provided ++ * determine which cipher to use based on the key size */ ++ if (c->keylen == 256) ++ EVP_EncryptInit_ex(aesni_ctx, EVP_aes_256_ctr(), NULL, c->orig_key, NULL); ++ else if (c->keylen == 128) ++ EVP_EncryptInit_ex(aesni_ctx, EVP_aes_128_ctr(), NULL, c->orig_key, NULL); ++ else if (c->keylen == 192) ++ EVP_EncryptInit_ex(aesni_ctx, EVP_aes_192_ctr(), NULL, c->orig_key, NULL); ++ else { ++ logit("Invalid key length of %d in AES CTR MT. Exiting", c->keylen); ++ exit(1); ++ } ++ ++ /* ++ * Handle the special case of startup, one thread must fill ++ * the first KQ then mark it as draining. Lock held throughout. ++ */ ++ ++ if (pthread_equal(pthread_self(), first_tid)) { ++ /* get the first element of the keyque struct */ ++ q = &c->q[0]; ++ pthread_mutex_lock(&q->lock); ++ /* if we are in the INIT state then fill the queue */ ++ if (q->qstate == KQINIT) { ++ /* set the initial counter */ ++ EVP_EncryptInit_ex(aesni_ctx, NULL, NULL, NULL, q->ctr); ++ ++ /* encypher a block sized null string (mynull) with the key. This ++ * returns the keystream because xoring the keystream ++ * against null returns the keystream. Store that in the appropriate queue */ ++ EVP_EncryptUpdate(aesni_ctx, q->keys[0], &outlen, mynull, KQLEN * AES_BLOCK_SIZE); ++ ++ /* add the number of blocks creates to the aes counter */ ++ ssh_ctr_add(q->ctr, KQLEN * numkq, AES_BLOCK_SIZE); ++ q->qstate = KQDRAINING; ++ pthread_cond_broadcast(&q->cond); ++ } ++ pthread_mutex_unlock(&q->lock); ++ } ++ ++ /* ++ * Normal case is to find empty queues and fill them, skipping over ++ * queues already filled by other threads and stopping to wait for ++ * a draining queue to become empty. ++ * ++ * Multiple threads may be waiting on a draining queue and awoken ++ * when empty. The first thread to wake will mark it as filling, ++ * others will move on to fill, skip, or wait on the next queue. ++ */ ++ for (qidx = 1;; qidx = (qidx + 1) % numkq) { ++ /* Check if I was cancelled, also checked in cond_wait */ ++ pthread_testcancel(); ++ ++ /* Check if we should exit as well */ ++ thread_loop_check_exit(c); ++ ++ /* Lock queue and block if its draining */ ++ q = &c->q[qidx]; ++ pthread_mutex_lock(&q->lock); ++ pthread_cleanup_push(thread_loop_cleanup, &q->lock); ++ while (q->qstate == KQDRAINING || q->qstate == KQINIT) { ++ thread_loop_check_exit(c); ++ pthread_cond_wait(&q->cond, &q->lock); ++ } ++ pthread_cleanup_pop(0); ++ ++ /* If filling or full, somebody else got it, skip */ ++ if (q->qstate != KQEMPTY) { ++ pthread_mutex_unlock(&q->lock); ++ continue; ++ } ++ ++ /* ++ * Empty, let's fill it. ++ * Queue lock is relinquished while we do this so others ++ * can see that it's being filled. ++ */ ++ q->qstate = KQFILLING; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ ++ /* set the initial counter */ ++ EVP_EncryptInit_ex(aesni_ctx, NULL, NULL, NULL, q->ctr); ++ ++ /* see coresponding block above for useful comments */ ++ EVP_EncryptUpdate(aesni_ctx, q->keys[0], &outlen, mynull, KQLEN * AES_BLOCK_SIZE); ++ ++ /* Re-lock, mark full and signal consumer */ ++ pthread_mutex_lock(&q->lock); ++ ssh_ctr_add(q->ctr, KQLEN * numkq, AES_BLOCK_SIZE); ++ q->qstate = KQFULL; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ } ++ ++ return NULL; ++} ++ ++/* this is where the data is actually enciphered and deciphered */ ++/* this may also benefit from upgrading to the EVP API */ ++static int ++ssh_aes_ctr(EVP_CIPHER_CTX *ctx, u_char *dest, const u_char *src, ++ LIBCRYPTO_EVP_INL_TYPE len) ++{ ++ typedef union { ++#ifdef CIPHER_INT128_OK ++ __uint128_t *u128; ++#endif ++ uint64_t *u64; ++ uint32_t *u32; ++ uint8_t *u8; ++ const uint8_t *cu8; ++ uintptr_t u; ++ } ptrs_t; ++ ptrs_t destp, srcp, bufp; ++ uintptr_t align; ++ struct ssh_aes_ctr_ctx_mt *c; ++ struct kq *q, *oldq; ++ int ridx; ++ u_char *buf; ++ ++ if (len == 0) ++ return 1; ++ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) ++ return 0; ++ ++ q = &c->q[c->qidx]; ++ ridx = c->ridx; ++ ++ /* src already padded to block multiple */ ++ srcp.cu8 = src; ++ destp.u8 = dest; ++ do { /* do until len is 0 */ ++ buf = q->keys[ridx]; ++ bufp.u8 = buf; ++ ++ /* figure out the alignment on the fly */ ++#ifdef CIPHER_UNALIGNED_OK ++ align = 0; ++#else ++ align = destp.u | srcp.u | bufp.u; ++#endif ++ ++ /* xor the src against the key (buf) ++ * different systems can do all 16 bytes at once or ++ * may need to do it in 8 or 4 bytes chunks ++ * worst case is doing it as a loop */ ++#ifdef CIPHER_INT128_OK ++ if ((align & 0xf) == 0) { ++ destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; ++ } else ++#endif ++ /* 64 bits */ ++ if ((align & 0x7) == 0) { ++ destp.u64[0] = srcp.u64[0] ^ bufp.u64[0]; ++ destp.u64[1] = srcp.u64[1] ^ bufp.u64[1]; ++ /* 32 bits */ ++ } else if ((align & 0x3) == 0) { ++ destp.u32[0] = srcp.u32[0] ^ bufp.u32[0]; ++ destp.u32[1] = srcp.u32[1] ^ bufp.u32[1]; ++ destp.u32[2] = srcp.u32[2] ^ bufp.u32[2]; ++ destp.u32[3] = srcp.u32[3] ^ bufp.u32[3]; ++ } else { ++ /*1 byte at a time*/ ++ size_t i; ++ for (i = 0; i < AES_BLOCK_SIZE; ++i) ++ dest[i] = src[i] ^ buf[i]; ++ } ++ ++ /* inc/decrement the pointers by the block size (16)*/ ++ destp.u += AES_BLOCK_SIZE; ++ srcp.u += AES_BLOCK_SIZE; ++ ++ /* Increment read index, switch queues on rollover */ ++ if ((ridx = (ridx + 1) % KQLEN) == 0) { ++ oldq = q; ++ ++ /* Mark next queue draining, may need to wait */ ++ c->qidx = (c->qidx + 1) % numkq; ++ q = &c->q[c->qidx]; ++ pthread_mutex_lock(&q->lock); ++ while (q->qstate != KQFULL) { ++ pthread_cond_wait(&q->cond, &q->lock); ++ } ++ q->qstate = KQDRAINING; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ ++ /* Mark consumed queue empty and signal producers */ ++ pthread_mutex_lock(&oldq->lock); ++ oldq->qstate = KQEMPTY; ++ pthread_cond_broadcast(&oldq->cond); ++ pthread_mutex_unlock(&oldq->lock); ++ } ++ } while (len -= AES_BLOCK_SIZE); ++ c->ridx = ridx; ++ return 1; ++} ++ ++static int ++ssh_aes_ctr_init(EVP_CIPHER_CTX *ctx, const u_char *key, const u_char *iv, ++ int enc) ++{ ++ struct ssh_aes_ctr_ctx_mt *c; ++ int i; ++ ++ char *aes_threads = getenv("SSH_CIPHER_THREADS"); ++ if (aes_threads != NULL && strlen(aes_threads) != 0) ++ cipher_threads = atoi(aes_threads); ++ else ++ cipher_threads = 1; ++ ++ if (cipher_threads < 1) ++ cipher_threads = 1; ++ ++ if (cipher_threads > MAX_THREADS) ++ cipher_threads = MAX_THREADS; ++ ++ numkq = cipher_threads + 1; ++ ++ if (numkq > MAX_NUMKQ) ++ numkq = MAX_NUMKQ; ++ ++ debug("Starting %d threads and %d queues\n", cipher_threads, numkq); ++ ++ /* set up the initial state of c (our cipher stream struct) */ ++ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) { ++ c = xmalloc(sizeof(*c)); ++ pthread_rwlock_init(&c->tid_lock, NULL); ++#ifdef __APPLE__ ++ pthread_rwlock_init(&c->stop_lock, NULL); ++ c->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ c->state = HAVE_NONE; ++ ++ /* initialize the mutexs and conditions for each lock in our struct */ ++ for (i = 0; i < numkq; i++) { ++ pthread_mutex_init(&c->q[i].lock, NULL); ++ pthread_cond_init(&c->q[i].cond, NULL); ++ } ++ ++ /* attach our struct to the context */ ++ EVP_CIPHER_CTX_set_app_data(ctx, c); ++ } ++ ++ /* we are initializing but the current structure already ++ has an IV and key so we want to kill the existing key data ++ and start over. This is important when we need to rekey the data stream */ ++ if (c->state == (HAVE_KEY | HAVE_IV)) { ++ /* tell the pregen threads to exit */ ++ stop_and_join_pregen_threads(c); ++ ++#ifdef __APPLE__ ++ /* reset the exit flag */ ++ c->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ /* Start over getting key & iv */ ++ c->state = HAVE_NONE; ++ } ++ ++ /* set the initial key for this key stream queue */ ++ if (key != NULL) { ++ AES_set_encrypt_key(key, EVP_CIPHER_CTX_key_length(ctx) * 8, ++ &c->aes_key); ++ c->orig_key = key; ++ c->keylen = EVP_CIPHER_CTX_key_length(ctx) * 8; ++ c->state |= HAVE_KEY; ++ } ++ ++ /* set the IV */ ++ if (iv != NULL) { ++ /* init the counter this is just a 16byte uchar */ ++ memcpy(c->aes_counter, iv, AES_BLOCK_SIZE); ++ c->state |= HAVE_IV; ++ } ++ ++ if (c->state == (HAVE_KEY | HAVE_IV)) { ++ /* Clear queues */ ++ /* set the first key in the key queue to the current counter */ ++ memcpy(c->q[0].ctr, c->aes_counter, AES_BLOCK_SIZE); ++ /* indicate that it needs to be initialized */ ++ c->q[0].qstate = KQINIT; ++ /* for each of the remaining queues set the first counter to the ++ * counter and then add the size of the queue to the counter */ ++ for (i = 1; i < numkq; i++) { ++ memcpy(c->q[i].ctr, c->aes_counter, AES_BLOCK_SIZE); ++ ssh_ctr_add(c->q[i].ctr, i * KQLEN, AES_BLOCK_SIZE); ++ c->q[i].qstate = KQEMPTY; ++ } ++ c->qidx = 0; ++ c->ridx = 0; ++ c->struct_id = global_struct_id++; ++ ++ ++ /* Start threads */ ++#define STACK_SIZE (1024 * 1024) ++ pthread_attr_t attr; ++ pthread_attr_init(&attr); ++ pthread_attr_setstacksize(&attr, STACK_SIZE); ++ for (i = 0; i < cipher_threads; i++) { ++ pthread_rwlock_wrlock(&c->tid_lock); ++ if (pthread_create(&c->tid[i], &attr, thread_loop, c) != 0) ++ fatal ("AES-CTR MT Could not create thread in %s", __FUNCTION__); ++ /*should die here */ ++ else { ++ c->id[i] = i; ++ debug ("AES-CTR MT spawned a thread with id %lu in %s (%lu, %d)", ++ c->tid[i], __FUNCTION__, c->struct_id, c->id[i]); ++ } ++ pthread_rwlock_unlock(&c->tid_lock); ++ } ++ pthread_mutex_lock(&c->q[0].lock); ++ // wait for all of the threads to be initialized ++ while (c->q[0].qstate == KQINIT) ++ pthread_cond_wait(&c->q[0].cond, &c->q[0].lock); ++ pthread_mutex_unlock(&c->q[0].lock); ++ } ++ return 1; ++} ++ ++static int ++ssh_aes_ctr_cleanup(EVP_CIPHER_CTX *ctx) ++{ ++ struct ssh_aes_ctr_ctx_mt *c; ++ ++ if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) != NULL) { ++ stop_and_join_pregen_threads(c); ++ ++ memset(c, 0, sizeof(*c)); ++ free(c); ++ EVP_CIPHER_CTX_set_app_data(ctx, NULL); ++ } ++ return 1; ++} ++ ++/* */ ++/* we've stipped out support for LibreSSL and OpenSSL < 1.1 ++ * it was getting to be too much to maintain. If LibreSSL ++ * ever incorporates the meth_new() functionality we'll ++ * reinstate support in configure.ac ++ * cjr 2/8/2023 ++ */ ++const EVP_CIPHER * ++evp_aes_ctr_mt(void) ++{ ++ static EVP_CIPHER *aes_ctr; ++ aes_ctr = EVP_CIPHER_meth_new(NID_undef, 16/*block*/, 16/*key*/); ++ EVP_CIPHER_meth_set_iv_length(aes_ctr, AES_BLOCK_SIZE); ++ EVP_CIPHER_meth_set_init(aes_ctr, ssh_aes_ctr_init); ++ EVP_CIPHER_meth_set_cleanup(aes_ctr, ssh_aes_ctr_cleanup); ++ EVP_CIPHER_meth_set_do_cipher(aes_ctr, ssh_aes_ctr); ++# ifndef SSH_OLD_EVP ++ EVP_CIPHER_meth_set_flags(aes_ctr, EVP_CIPH_CBC_MODE ++ | EVP_CIPH_VARIABLE_LENGTH ++ | EVP_CIPH_ALWAYS_CALL_INIT ++ | EVP_CIPH_CUSTOM_IV); ++# endif /*SSH_OLD_EVP*/ ++ return aes_ctr; ++} ++#endif /* OSSL VERSION NUMBER */ ++#endif /* OSSL */ +diff -Nur openssh-9.3p1.orig/cipher-ctr-mt-functions.c openssh-9.3p1/cipher-ctr-mt-functions.c +--- openssh-9.3p1.orig/cipher-ctr-mt-functions.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/cipher-ctr-mt-functions.c 2024-07-10 09:09:55.901568405 +0200 +@@ -0,0 +1,652 @@ ++/* ++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3 ++ * ++ * Author: Benjamin Bennett ++ * Author: Mike Tasota ++ * Author: Chris Rapier ++ * Copyright (c) 2008-2022 Pittsburgh Supercomputing Center. All rights reserved. ++ * ++ * Based on original OpenSSH AES-CTR cipher. Small portions remain unchanged, ++ * Copyright (c) 2003 Markus Friedl ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include "includes.h" ++ ++#ifdef WITH_OPENSSL ++/* only for systems with OSSL 3 */ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++#include ++#include ++#include ++#include "xmalloc.h" ++#include ++#include "cipher-ctr-mt-functions.h" ++#include "log.h" ++ ++/* for provider error struct */ ++#include "ossl3-provider-err.h" ++#include "num.h" ++ ++/* note regarding threads and queues */ ++/* initially this cipher was written in a way that ++ * the key stream was generated in a per cipher block ++ * loop. For example, if the key stream queue length was ++ * 16k and the cipher block size was 16 bytes it would ++ * fill the queue 16 bytes at a time. Mitch Dorrell pointed ++ * out that we could fill the queue in once call eliminating ++ * loop and multiple calls to EVP_EncryptUpdate. Doing so ++ * dramatically reduced CPU load in the threads and indicated ++ * that we could also eliminate most of the threads and queues ++ * as it would take far less time for a queue to ebter KQ_FULL ++ * state. As such, we've reduced the default number of threads ++ * and queues from 2 and 8 (respectively) to 1 and 2. We've also ++ * elimnated the need to determine the physical number of cores on ++ * the system and, if the user desires, can spin up more threads ++ * using an environment variable. Additionally, queues is now fixed ++ * at thread_count + 1. ++ * cjr 10/19/2022 */ ++ ++/*-------------------- TUNABLES --------------------*/ ++/* Number of pregen threads to use */ ++/* this is a default value. The actual number is ++ * determined during init as a function of the number ++ * of available cores */ ++int cipher_threads = 1; ++ ++/* Number of keystream queues */ ++/* ideally this should be large enough so that there is ++ * always a key queue for a thread to work on ++ * so maybe double of the number of threads. Again this ++ * is a default and the actual value is determined in init*/ ++int numkq = 2; ++/*-------------------- END TUNABLES --------------------*/ ++ ++/* globals */ ++/* how we increment the id the structs we create */ ++long unsigned int global_struct_id = 0; ++ ++/* keep a copy of the pointers created in thread_loop to free later */ ++struct aes_mt_ctx_ptrs *evp_ptrs = NULL; ++ ++/* private functions */ ++ ++/* ++ * Add num to counter 'ctr' ++ */ ++static void ++ssh_ctr_add(u_char *ctr, uint32_t num, u_int len) ++{ ++ int i; ++ uint16_t n; ++ ++ for (n = 0, i = len - 1; i >= 0 && (num || n); i--) { ++ n = ctr[i] + (num & 0xff) + n; ++ num >>= 8; ++ ctr[i] = n & 0xff; ++ n >>= 8; ++ } ++} ++ ++/* ++ * Threads may be cancelled in a pthread_cond_wait, we must free the mutex ++ */ ++static void ++thread_loop_cleanup(void *x) ++{ ++ pthread_mutex_unlock((pthread_mutex_t *)x); ++} ++ ++#ifdef __APPLE__ ++/* Check if we should exit, we are doing both cancel and exit condition ++ * since on OSX threads seem to occasionally fail to notice when they have ++ * been cancelled. We want to have a backup to make sure that we won't hang ++ * when the main process join()-s the cancelled thread. ++ */ ++static void ++thread_loop_check_exit(struct aes_mt_ctx_st *aes_mt_ctx) ++{ ++ int exit_flag; ++ ++ pthread_rwlock_rdlock(&aes_mt_ctx->stop_lock); ++ exit_flag = aes_mt_ctx->exit_flag; ++ pthread_rwlock_unlock(&aes_mt_ctx->stop_lock); ++ ++ if (exit_flag) ++ pthread_exit(NULL); ++} ++#else ++# define thread_loop_check_exit(s) ++#endif /* __APPLE__ */ ++ ++/* ++ * Helper function to terminate the helper threads ++ */ ++static void ++stop_and_join_pregen_threads(struct aes_mt_ctx_st *aes_mt_ctx) ++{ ++ int i; ++ ++#ifdef __APPLE__ ++ /* notify threads that they should exit */ ++ pthread_rwlock_wrlock(&aes_mt_ctx->stop_lock); ++ aes_mt_ctx->exit_flag = TRUE; ++ pthread_rwlock_unlock(&aes_mt_ctx->stop_lock); ++#endif /* __APPLE__ */ ++ ++ /* Cancel pregen threads */ ++ for (i = 0; i < cipher_threads; i++) { ++ debug_f ("Canceled %lu (%lu,%d)", aes_mt_ctx->tid[i], aes_mt_ctx->struct_id, ++ aes_mt_ctx->id[i]); ++ pthread_cancel(aes_mt_ctx->tid[i]); ++ } ++ for (i = 0; i < cipher_threads; i++) { ++ if (pthread_kill(aes_mt_ctx->tid[i], 0) != 0) ++ debug3("AES-CTR MT pthread_join failure: Invalid thread id %lu in %s", ++ aes_mt_ctx->tid[i], __func__); ++ else { ++ debug_f ("Joining %lu (%lu, %d)", aes_mt_ctx->tid[i], aes_mt_ctx->struct_id, ++ aes_mt_ctx->id[i]); ++ pthread_join(aes_mt_ctx->tid[i], NULL); ++ /* this finds the entry in the hash that corresponding to the ++ * thread id. That's used to find the pointer to the cipher struct ++ * created in thread_loop. */ ++ struct aes_mt_ctx_ptrs *ptr; ++ HASH_FIND_INT(evp_ptrs, &aes_mt_ctx->tid[i], ptr); ++ EVP_CIPHER_CTX_free(ptr->pointer); ++ HASH_DEL(evp_ptrs, ptr); ++ free(ptr); ++ } ++ } ++ pthread_rwlock_destroy(&aes_mt_ctx->tid_lock); ++} ++ ++/* determine the number of threads to use ++ * Testing indicates that in most all situations the optimal number of ++ * threads is 1 meaning 1 for inbound and 1 for outbound. The optimal ++ * queue count has also been determined to be thread_count + 1. ++ * note this function updates two globals - numkq and cipher_threads ++ * it returns the value of cipher_threads but it doesn't need to */ ++static int get_thread_count() { ++ ++ char * aes_threads = getenv("SSH_CIPHER_THREADS"); ++ debug_f ("SSH thread count is %s", aes_threads); ++ if (aes_threads != NULL && strlen(aes_threads) != 0) ++ cipher_threads = atoi(aes_threads); ++ else ++ cipher_threads = 1; ++ ++ if (cipher_threads < 1) ++ cipher_threads = 1; ++ ++ if (cipher_threads > MAX_THREADS) ++ cipher_threads = MAX_THREADS; ++ ++ numkq = cipher_threads + 1; ++ ++ if (numkq > MAX_NUMKQ) ++ numkq = MAX_NUMKQ; ++ ++ debug_f ("Starting %d threads and %d queues\n", cipher_threads, numkq); ++ ++ return (cipher_threads); ++} ++ ++ ++/* ++ * The life of a pregen thread: ++ * Find empty keystream queues and fill them using their counter. ++ * When done, update counter for the next fill. ++ */ ++static void * ++thread_loop(void *job) ++{ ++ EVP_CIPHER_CTX *evp_ctx; ++ struct aes_mt_ctx_st *aes_mt_ctx = job; ++ struct kq *q; ++ struct aes_mt_ctx_ptrs *ptr; ++ int qidx; ++ pthread_t first_tid; ++ int outlen; ++ u_char mynull[KQLEN * AES_BLOCK_SIZE]; ++ memset(&mynull, 0, KQLEN * AES_BLOCK_SIZE); ++ ++ /* get the thread id to see if this is the first one */ ++ pthread_rwlock_rdlock(&aes_mt_ctx->tid_lock); ++ first_tid = aes_mt_ctx->tid[0]; ++ pthread_rwlock_unlock(&aes_mt_ctx->tid_lock); ++ ++ /* create the context for this thread */ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ ++ /* keep track of the pointer for the evp in this struct ++ * so we can free it later. So we place it in a hash indexed on the ++ * thread id, which is available to us in the free function. ++ * Note, the thread id isn't necessary unique across rekeys but ++ * that's okay as they are unique during a key. */ ++ ptr = malloc(sizeof *ptr); /*freed in stop & prejoin */ ++ ptr->tid = pthread_self(); /* index for hash */ ++ ptr->pointer = evp_ctx; ++ HASH_ADD_INT(evp_ptrs, tid, ptr); ++ ++ /* initialize the cipher ctx with the key provided ++ * determinbe which cipher to use based on the key size */ ++ if (aes_mt_ctx->keylen == 256) ++ EVP_EncryptInit_ex(evp_ctx, EVP_aes_256_ctr(), NULL, aes_mt_ctx->orig_key, NULL); ++ else if (aes_mt_ctx->keylen == 128) ++ EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ctr(), NULL, aes_mt_ctx->orig_key, NULL); ++ else if (aes_mt_ctx->keylen == 192) ++ EVP_EncryptInit_ex(evp_ctx, EVP_aes_192_ctr(), NULL, aes_mt_ctx->orig_key, NULL); ++ else ++ fatal("Invalid key length of %d in AES CTR MT. Exiting", aes_mt_ctx->keylen); ++ ++ /* ++ * Handle the special case of startup, one thread must fill ++ * the first KQ then mark it as draining. Lock held throughout. ++ */ ++ if (pthread_equal(pthread_self(), first_tid)) { ++ /* get the first element of the key queue struct */ ++ q = &aes_mt_ctx->q[0]; ++ pthread_mutex_lock(&q->lock); ++ /* if we are in the INIT state then fill the queue */ ++ if (q->qstate == KQINIT) { ++ /* set the initial counter */ ++ EVP_EncryptInit_ex(evp_ctx, NULL, NULL, NULL, q->ctr); ++ /* encypher a block sized null string (mynull) with the key. This ++ * returns the keystream because xoring the keystream ++ * against null returns the keystream. Store that in the appropriate queue */ ++ EVP_EncryptUpdate(evp_ctx, q->keys[0], &outlen, mynull, KQLEN * AES_BLOCK_SIZE); ++ /* Update the aes counter */ ++ ssh_ctr_add(q->ctr, KQLEN * numkq, AES_BLOCK_SIZE); ++ /* since this is the first thread set it to draining */ ++ q->qstate = KQDRAINING; ++ pthread_cond_broadcast(&q->cond); ++ } ++ pthread_mutex_unlock(&q->lock); ++ } ++ ++ /* ++ * Normal case is to find empty queues and fill them, skipping over ++ * queues already filled by other threads and stopping to wait for ++ * a draining queue to become empty. ++ * ++ * Multiple threads may be waiting on a draining queue and awoken ++ * when empty. The first thread to wake will mark it as filling, ++ * others will move on to fill, skip, or wait on the next queue. ++ */ ++ for (qidx = 1;; qidx = (qidx + 1) % numkq) { ++ /* Check if I was cancelled, also checked in cond_wait */ ++ pthread_testcancel(); ++ ++ /* Check if we should exit as well */ ++ thread_loop_check_exit(aes_mt_ctx); ++ ++ /* Lock queue and block if its draining */ ++ q = &aes_mt_ctx->q[qidx]; ++ pthread_mutex_lock(&q->lock); ++ pthread_cleanup_push(thread_loop_cleanup, &q->lock); ++ while (q->qstate == KQDRAINING || q->qstate == KQINIT) { ++ thread_loop_check_exit(aes_mt_ctx); ++ pthread_cond_wait(&q->cond, &q->lock); ++ } ++ pthread_cleanup_pop(0); ++ ++ /* If filling or full, somebody else got it, skip */ ++ if (q->qstate != KQEMPTY) { ++ pthread_mutex_unlock(&q->lock); ++ continue; ++ } ++ ++ /* ++ * Empty, let's fill it. ++ * Queue lock is relinquished while we do this so others ++ * can see that it's being filled. ++ */ ++ q->qstate = KQFILLING; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ ++ /* set the initial counter */ ++ EVP_EncryptInit_ex(evp_ctx, NULL, NULL, NULL, q->ctr); ++ ++ /* see coresponding block above for useful comments */ ++ EVP_EncryptUpdate(evp_ctx, q->keys[0], &outlen, mynull, KQLEN * AES_BLOCK_SIZE); ++ ++ /* Re-lock, mark full and signal consumer */ ++ pthread_mutex_lock(&q->lock); ++ ssh_ctr_add(q->ctr, KQLEN * numkq, AES_BLOCK_SIZE); ++ q->qstate = KQFULL; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ } ++ ++ return NULL; ++} ++ ++ ++/* Our version of the EVP functions ++ * these are public as they are used by the provider */ ++ ++/* instantiate the cipher context. ++ * in this we create the EVP ctx and the AES ctx, setup the AES ctx ++ * initialize the EVP and then attach the AES ctx to the EVP ctx. ++ * The *only* difference between aes_mt_newctx_256|192|128 is the ++ * keylength of the cipher used in EVP_CipherInit ++ * parameters: provider context ++ * returns: EVP context ++ */ ++/* honestly the way this works makes me think that there has to be ++ * a better way of doing this however, I've yet to find one that doesn't ++ * involve more madness. I think that's mostly becase I don't understand ++ * how params work properly. I feel like I shoudl be able to use them ++ * to specify the key length but... also, I'd think I'd be able to ++ * set aes_mt_ctx_st->keylen to the keylength but that doesn't seem to ++ * work either. That said, this does work even if it's a bit clunky. ++ * -cjr 09/08/2022 */ ++void *aes_mt_newctx_256(void *provctx) ++{ ++ struct aes_mt_ctx_st *aes_mt_ctx = malloc(sizeof(*aes_mt_ctx)); ++ EVP_CIPHER_CTX *evp_ctx = EVP_CIPHER_CTX_new(); ++ ++ if ((aes_mt_ctx != NULL) && (evp_ctx != NULL)) { ++ get_thread_count(); /* update cipher_threads and numkq */ ++ pthread_rwlock_init(&aes_mt_ctx->tid_lock, NULL); ++#ifdef __APPLE__ ++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL); ++ aes_mt_ctx->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ aes_mt_ctx->state = HAVE_NONE; ++ ++ /* initialize the mutexs and conditions for each lock in our struct */ ++ for (int i = 0; i < numkq; i++) { ++ pthread_mutex_init(&aes_mt_ctx->q[i].lock, NULL); ++ pthread_cond_init(&aes_mt_ctx->q[i].cond, NULL); ++ } ++ aes_mt_ctx->provctx = provctx; ++ EVP_CipherInit(evp_ctx, EVP_aes_256_ctr(), NULL, NULL, 0); ++ EVP_CIPHER_CTX_set_app_data(evp_ctx, aes_mt_ctx); ++ return evp_ctx; ++ } ++ return NULL; ++} ++ ++void *aes_mt_newctx_192(void *provctx) ++{ ++ struct aes_mt_ctx_st *aes_mt_ctx = malloc(sizeof(*aes_mt_ctx)); ++ EVP_CIPHER_CTX *evp_ctx = EVP_CIPHER_CTX_new(); ++ ++ if ((aes_mt_ctx != NULL) && (evp_ctx != NULL)) { ++ get_thread_count(); /* update cipher_threads and numkq */ ++ pthread_rwlock_init(&aes_mt_ctx->tid_lock, NULL); ++#ifdef __APPLE__ ++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL); ++ aes_mt_ctx->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ aes_mt_ctx->state = HAVE_NONE; ++ ++ /* initialize the mutexs and conditions for each lock in our struct */ ++ for (int i = 0; i < numkq; i++) { ++ pthread_mutex_init(&aes_mt_ctx->q[i].lock, NULL); ++ pthread_cond_init(&aes_mt_ctx->q[i].cond, NULL); ++ } ++ aes_mt_ctx->provctx = provctx; ++ EVP_CipherInit(evp_ctx, EVP_aes_192_ctr(), NULL, NULL, 0); ++ EVP_CIPHER_CTX_set_app_data(evp_ctx, aes_mt_ctx); ++ return evp_ctx; ++ } ++ return NULL; ++} ++ ++void *aes_mt_newctx_128(void *provctx) ++{ ++ struct aes_mt_ctx_st *aes_mt_ctx = malloc(sizeof(*aes_mt_ctx)); ++ EVP_CIPHER_CTX *evp_ctx = EVP_CIPHER_CTX_new(); ++ ++ if ((aes_mt_ctx != NULL) && (evp_ctx != NULL)) { ++ get_thread_count(); /* update cipher_threads and numkq */ ++ pthread_rwlock_init(&aes_mt_ctx->tid_lock, NULL); ++#ifdef __APPLE__ ++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL); ++ aes_mt_ctx->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ aes_mt_ctx->state = HAVE_NONE; ++ ++ /* initialize the mutexs and conditions for each lock in our struct */ ++ for (int i = 0; i < numkq; i++) { ++ pthread_mutex_init(&aes_mt_ctx->q[i].lock, NULL); ++ pthread_cond_init(&aes_mt_ctx->q[i].cond, NULL); ++ } ++ aes_mt_ctx->provctx = provctx; ++ EVP_CipherInit(evp_ctx, EVP_aes_128_ctr(), NULL, NULL, 0); ++ EVP_CIPHER_CTX_set_app_data(evp_ctx, aes_mt_ctx); ++ return evp_ctx; ++ } ++ return NULL; ++} ++ ++/* this function expects a void but we need the actual context ++ * to get the app_data. ++ */ ++void aes_mt_freectx(void *vevp_ctx) ++{ ++ EVP_CIPHER_CTX *evp_ctx = vevp_ctx; ++ struct aes_mt_ctx_st *aes_mt_ctx; ++ ++ if ((aes_mt_ctx = EVP_CIPHER_CTX_get_app_data(evp_ctx)) != NULL) { ++ stop_and_join_pregen_threads(aes_mt_ctx); ++ ++ memset(aes_mt_ctx, 0, sizeof(*aes_mt_ctx)); ++ free(aes_mt_ctx); ++ EVP_CIPHER_CTX_set_app_data(evp_ctx, NULL); ++ } ++ EVP_CIPHER_CTX_free(evp_ctx); ++} ++ ++/* this function takes the EVP context, gets the AES context ++ * and starts the various threads we need */ ++int aes_mt_start_threads(void *vevp_ctx, const u_char *key, ++ size_t keylen, const u_char *iv, ++ size_t ivlen, const OSSL_PARAM *ossl_params) ++{ ++ EVP_CIPHER_CTX *evp_ctx = vevp_ctx; ++ struct aes_mt_ctx_st *aes_mt_ctx; ++ ++ ++ /* get the initial state of aes_mt_ctx (our cipher stream struct) */ ++ if ((aes_mt_ctx = EVP_CIPHER_CTX_get_app_data(evp_ctx)) == NULL) { ++ fatal("Missing AES MT context data!"); ++ } ++ ++ /* we are initializing but the current structure already ++ * has an IV and key so we want to kill the existing key data ++ * and start over. This is important when we need to rekey the data stream */ ++ if (aes_mt_ctx->state == (HAVE_KEY | HAVE_IV)) { ++ /* tell the pregen threads to exit */ ++ stop_and_join_pregen_threads(aes_mt_ctx); ++ ++#ifdef __APPLE__ ++ /* reset the exit flag */ ++ aes_mt_ctx->exit_flag = FALSE; ++#endif /* __APPLE__ */ ++ ++ /* Start over getting key & iv */ ++ aes_mt_ctx->state = HAVE_NONE; ++ } ++ ++ /* set the initial key for this key stream queue */ ++ if (key != NULL) { ++ aes_mt_ctx->keylen = EVP_CIPHER_CTX_key_length(evp_ctx) * 8; ++ aes_mt_ctx->orig_key = key; ++ aes_mt_ctx->state |= HAVE_KEY; ++ } ++ ++ /* set the IV */ ++ if (iv != NULL) { ++ /* init the counter this is just a 16byte uchar */ ++ memcpy(aes_mt_ctx->aes_counter, iv, AES_BLOCK_SIZE); ++ aes_mt_ctx->state |= HAVE_IV; ++ } ++ ++ if (aes_mt_ctx->state == (HAVE_KEY | HAVE_IV)) { ++ /* Clear queues */ ++ /* set the first key in the key queue to the current counter */ ++ memcpy(aes_mt_ctx->q[0].ctr, aes_mt_ctx->aes_counter, AES_BLOCK_SIZE); ++ /* indicate that it needs to be initialized */ ++ aes_mt_ctx->q[0].qstate = KQINIT; ++ /* for each of the remaining queues set the first counter to the ++ * counter and then add the size of the queue to the counter */ ++ for (int i = 1; i < numkq; i++) { ++ memcpy(aes_mt_ctx->q[i].ctr, aes_mt_ctx->aes_counter, AES_BLOCK_SIZE); ++ ssh_ctr_add(aes_mt_ctx->q[i].ctr, i * KQLEN, AES_BLOCK_SIZE); ++ aes_mt_ctx->q[i].qstate = KQEMPTY; ++ } ++ aes_mt_ctx->qidx = 0; ++ aes_mt_ctx->ridx = 0; ++ aes_mt_ctx->struct_id = global_struct_id++; ++ ++ /* Start threads */ ++ for (int i = 0; i < cipher_threads; i++) { ++ pthread_rwlock_wrlock(&aes_mt_ctx->tid_lock); ++ if (pthread_create(&aes_mt_ctx->tid[i], NULL, thread_loop, aes_mt_ctx) != 0) ++ fatal ("AES-CTR MT Could not create thread in %s", __func__); ++ else { ++ aes_mt_ctx->id[i] = i; ++ debug_f ("AES-CTR MT spawned a thread with id %lu (%lu, %d)", ++ aes_mt_ctx->tid[i], aes_mt_ctx->struct_id, ++ aes_mt_ctx->id[i]); ++ } ++ pthread_rwlock_unlock(&aes_mt_ctx->tid_lock); ++ } ++ pthread_mutex_lock(&aes_mt_ctx->q[0].lock); ++ // wait for all of the threads to be initialized ++ while (aes_mt_ctx->q[0].qstate == KQINIT) ++ pthread_cond_wait(&aes_mt_ctx->q[0].cond, &aes_mt_ctx->q[0].lock); ++ pthread_mutex_unlock(&aes_mt_ctx->q[0].lock); ++ } ++ return 1; ++} ++ ++/* this should correspond to ssh_aes_ctr ++ * OSSL_CORE_MAKE_FUNC(int, cipher_cipher, ++ * (void *cctx, ++ * unsigned char *out, size_t *outl, size_t outsize, ++ * const unsigned char *in, size_t inl)) ++ */ ++ ++int aes_mt_do_cipher(void *vevp_ctx, ++ u_char *dest, size_t *destlen, size_t destsize, ++ const u_char *src, size_t len) ++{ ++ typedef union { ++#ifdef CIPHER_INT128_OK ++ __uint128_t *u128; ++#endif ++ uint64_t *u64; ++ uint32_t *u32; ++ uint8_t *u8; ++ const uint8_t *cu8; ++ uintptr_t u; ++ } ptrs_t; ++ ptrs_t destp, srcp, bufp; ++ uintptr_t align; ++ struct aes_mt_ctx_st *aes_mt_ctx; ++ struct kq *q, *oldq; ++ int ridx; ++ u_char *buf; ++ EVP_CIPHER_CTX *evp_ctx = vevp_ctx; ++ ++ if (len == 0) ++ return 1; ++ ++ if ((aes_mt_ctx = EVP_CIPHER_CTX_get_app_data(evp_ctx)) == NULL) ++ return 0; ++ ++ q = &aes_mt_ctx->q[aes_mt_ctx->qidx]; ++ ridx = aes_mt_ctx->ridx; ++ ++ /* src already padded to block multiple */ ++ srcp.cu8 = src; ++ destp.u8 = dest; ++ do { /* do until len is 0 */ ++ buf = q->keys[ridx]; ++ bufp.u8 = buf; ++ ++ /* figure out the alignment on the fly */ ++#ifdef CIPHER_UNALIGNED_OK ++ align = 0; ++#else ++ align = destp.u | srcp.u | bufp.u; ++#endif ++ ++ /* xor the src against the key (buf) ++ * different systems can do all 16 bytes at once or ++ * may need to do it in 8 or 4 bytes chunks ++ * worst case is doing it as a loop */ ++#ifdef CIPHER_INT128_OK ++ if ((align & 0xf) == 0) { ++ destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; ++ } else ++#endif ++ /* 64 bits */ ++ if ((align & 0x7) == 0) { ++ destp.u64[0] = srcp.u64[0] ^ bufp.u64[0]; ++ destp.u64[1] = srcp.u64[1] ^ bufp.u64[1]; ++ /* 32 bits */ ++ } else if ((align & 0x3) == 0) { ++ destp.u32[0] = srcp.u32[0] ^ bufp.u32[0]; ++ destp.u32[1] = srcp.u32[1] ^ bufp.u32[1]; ++ destp.u32[2] = srcp.u32[2] ^ bufp.u32[2]; ++ destp.u32[3] = srcp.u32[3] ^ bufp.u32[3]; ++ } else { ++ /*1 byte at a time*/ ++ size_t i; ++ for (i = 0; i < AES_BLOCK_SIZE; ++i) ++ dest[i] = src[i] ^ buf[i]; ++ } ++ ++ /* inc/decrement the pointers by the block size (16)*/ ++ destp.u += AES_BLOCK_SIZE; ++ srcp.u += AES_BLOCK_SIZE; ++ ++ /* Increment read index, switch queues on rollover */ ++ if ((ridx = (ridx + 1) % KQLEN) == 0) { ++ oldq = q; ++ ++ /* Mark next queue draining, may need to wait */ ++ aes_mt_ctx->qidx = (aes_mt_ctx->qidx + 1) % numkq; ++ q = &aes_mt_ctx->q[aes_mt_ctx->qidx]; ++ pthread_mutex_lock(&q->lock); ++ while (q->qstate != KQFULL) { ++ pthread_cond_wait(&q->cond, &q->lock); ++ } ++ q->qstate = KQDRAINING; ++ pthread_cond_broadcast(&q->cond); ++ pthread_mutex_unlock(&q->lock); ++ ++ /* Mark consumed queue empty and signal producers */ ++ pthread_mutex_lock(&oldq->lock); ++ oldq->qstate = KQEMPTY; ++ pthread_cond_broadcast(&oldq->cond); ++ pthread_mutex_unlock(&oldq->lock); ++ } ++ } while (len -= AES_BLOCK_SIZE); ++ aes_mt_ctx->ridx = ridx; ++ return 1; ++} ++ ++#endif /*OPENSSL_VERSION_NUMBER */ ++#endif /*WITH_OPENSSL*/ +diff -Nur openssh-9.3p1.orig/cipher-ctr-mt-functions.h openssh-9.3p1/cipher-ctr-mt-functions.h +--- openssh-9.3p1.orig/cipher-ctr-mt-functions.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/cipher-ctr-mt-functions.h 2024-07-10 09:09:55.901568405 +0200 +@@ -0,0 +1,144 @@ ++/* ++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3 ++ * ++ * Author: Benjamin Bennett ++ * Author: Mike Tasota ++ * Author: Chris Rapier ++ * Copyright (c) 2008-2022 Pittsburgh Supercomputing Center. All rights reserved. ++ * ++ * Based on original OpenSSH AES-CTR cipher. Small portions remain unchanged, ++ * Copyright (c) 2003 Markus Friedl ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#ifndef CTR_MT_FUNCS ++#define CTR_MT_FUNCS ++ ++/* includes */ ++#include "includes.h" /* needed to get version number */ ++#include ++#include ++#include "cipher-aesctr.h" ++#include "uthash.h" ++ ++#ifndef USE_BUILTIN_RIJNDAEL ++#include ++#endif ++ ++#ifdef WITH_OPENSSL ++/* only for systems with OSSL 3 */ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ ++/*-------------------- TUNABLES --------------------*/ ++/* maximum number of threads and queues */ ++#define MAX_THREADS 32 ++#define MAX_NUMKQ (MAX_THREADS + 1) ++ ++/* one queue holds 8192 * 4 * 16B (512KB) of key data ++ * being that the queues are destroyed after a rekey ++ * and at leats one has to be fully filled prior to ++ * enciphering data we don't want this to be too large */ ++#define KQLEN (8192 * 4) ++ ++/* Processor cacheline length */ ++#define CACHELINE_LEN 64 ++ ++/* Can the system do unaligned loads natively? */ ++#if defined(__aarch64__) || \ ++ defined(__i386__) || \ ++ defined(__powerpc__) || \ ++ defined(__x86_64__) ++# define CIPHER_UNALIGNED_OK ++#endif ++#if defined(__SIZEOF_INT128__) ++# define CIPHER_INT128_OK ++#endif ++ ++/* context states */ ++#define HAVE_NONE 0 ++#define HAVE_KEY 1 ++#define HAVE_IV 2 ++ ++/* Keystream Queue state */ ++enum { ++ KQINIT, ++ KQEMPTY, ++ KQFILLING, ++ KQFULL, ++ KQDRAINING ++}; ++ ++/* structs */ ++ ++/* provider struct */ ++struct provider_ctx_st { ++ const OSSL_CORE_HANDLE *core_handle; ++ struct proverr_functions_st *proverr_handle; ++}; ++ ++/* Keystream Queue struct */ ++struct kq { ++ u_char keys[KQLEN][AES_BLOCK_SIZE]; /* [32768][16B] */ ++ u_char ctr[AES_BLOCK_SIZE]; /* 16B */ ++ u_char pad0[CACHELINE_LEN]; ++ pthread_mutex_t lock; ++ pthread_cond_t cond; ++ int qstate; ++ u_char pad1[CACHELINE_LEN]; ++}; ++ ++/* AES MT context struct */ ++struct aes_mt_ctx_st { ++ struct provider_ctx_st *provctx; ++ long unsigned int struct_id; ++ int keylen; ++ int state; ++ int qidx; ++ int ridx; ++ int id[MAX_THREADS]; /* 32 */ ++ AES_KEY aes_key; ++ const u_char *orig_key; ++ u_char aes_counter[AES_BLOCK_SIZE]; /* 16B */ ++ pthread_t tid[MAX_THREADS]; /* 32 */ ++ pthread_rwlock_t tid_lock; ++ struct kq q[MAX_NUMKQ]; /* 33 */ ++#ifdef __APPLE__ ++ pthread_rwlock_t stop_lock; ++ int exit_flag; ++#endif /* __APPLE__ */ ++ int ongoing; /* possibly not needed */ ++}; ++ ++/* this holds an array of evp context pointers that are ++ * created in thread_loop. Since we can't effectively free those ++ * contexts in thread_loop we keep a copy of those pointers here ++ * and then free them in stop_and_join_pregenthreads ++ * cjr 2/2/2023 ++ */ ++struct aes_mt_ctx_ptrs { ++ EVP_CIPHER_CTX *pointer; ++ pthread_t tid; ++ UT_hash_handle hh; ++}; ++ ++int aes_mt_do_cipher(void *, u_char *, size_t *, size_t, const u_char *, size_t); ++int aes_mt_start_threads(void *, const u_char *, size_t, const u_char *, size_t, const OSSL_PARAM *); ++void aes_mt_freectx(void *); ++void *aes_mt_newctx_256(void *); ++void *aes_mt_newctx_192(void *); ++void *aes_mt_newctx_128(void *); ++ ++#endif /* VERSION NUMBER */ ++#endif /* WITH OPENSSL */ ++#endif /* CTR_MT_FUNCS */ +diff -Nur openssh-9.3p1.orig/cipher-ctr-mt-provider.c openssh-9.3p1/cipher-ctr-mt-provider.c +--- openssh-9.3p1.orig/cipher-ctr-mt-provider.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/cipher-ctr-mt-provider.c 2024-07-10 09:09:55.902568408 +0200 +@@ -0,0 +1,392 @@ ++/* ++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3 ++ * ++ * Author: Chris Rapier ++ * Copyright (c) 2022 Pittsburgh Supercomputing Center. All rights reserved. ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++/* based on vienere.c from https://github.com/provider-corner/vigenere by ++ * Richard Levitte provided under a CC0 Public License. */ ++ ++#include "includes.h" ++ ++#ifdef WITH_OPENSSL ++/* only for systems with OSSL 3.0+ */ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "xmalloc.h" ++#include "ossl3-provider-err.h" ++#include "num.h" ++#include "cipher-ctr-mt-functions.h" ++ ++#define ERR_HANDLE(ctx) ((ctx)->provctx->proverr_handle) ++ ++/* forward declartion of cipher functions */ ++/* cipher context functions */ ++OSSL_FUNC_cipher_newctx_fn aes_mt_newctx_256; ++OSSL_FUNC_cipher_newctx_fn aes_mt_newctx_192; ++OSSL_FUNC_cipher_newctx_fn aes_mt_newctx_128; ++OSSL_FUNC_cipher_freectx_fn aes_mt_freectx; ++ ++/* param related function*/ ++static OSSL_FUNC_cipher_get_params_fn aes_mt_get_params_256; ++static OSSL_FUNC_cipher_get_params_fn aes_mt_get_params_192; ++static OSSL_FUNC_cipher_get_params_fn aes_mt_get_params_128; ++static OSSL_FUNC_cipher_gettable_params_fn aes_mt_gettable_params; ++static OSSL_FUNC_cipher_set_ctx_params_fn aes_mt_set_ctx_params; ++static OSSL_FUNC_cipher_get_ctx_params_fn aes_mt_get_ctx_params; ++static OSSL_FUNC_cipher_settable_ctx_params_fn aes_mt_settable_ctx_params; ++static OSSL_FUNC_cipher_gettable_ctx_params_fn aes_mt_gettable_ctx_params; ++ ++/* en/decipher functions */ ++OSSL_FUNC_cipher_encrypt_init_fn aes_mt_start_threads; ++OSSL_FUNC_cipher_decrypt_init_fn aes_mt_start_threads; ++OSSL_FUNC_cipher_update_fn aes_mt_do_cipher; ++ ++/* provider context */ ++static OSSL_FUNC_provider_query_operation_fn aes_mt_prov_query; ++static OSSL_FUNC_provider_get_reason_strings_fn aes_mt_prov_reasons; ++static OSSL_FUNC_provider_teardown_fn aes_mt_prov_teardown; ++static OSSL_FUNC_provider_get_params_fn aes_mt_prov_get_params; ++OSSL_provider_init_fn OSSL_provider_init; /* need this? */ ++ ++/* error functions */ ++OSSL_FUNC_core_new_error_fn *c_new_error; ++OSSL_FUNC_core_set_error_debug_fn *c_set_error_debug; ++OSSL_FUNC_core_vset_error_fn *c_vset_error; ++OSSL_FUNC_core_set_error_mark_fn *c_set_error_mark; ++OSSL_FUNC_core_clear_last_error_mark_fn *c_clear_last_error_mark; ++OSSL_FUNC_core_pop_error_to_mark_fn *c_pop_error_to_mark; ++ ++/* Errors used in this provider */ ++#define AES_MT_E_MALLOC 1 ++#define AES_MT_ONGOING_OPERATION 2 ++#define AES_MT_BAD_KEYLEN 3 ++ ++/* typedef for function pointers */ ++typedef void(*fptr_t)(void); ++ ++/* all of the various arrays we need */ ++ ++/* BAD_KEYLEN isn't being used at the moment */ ++const OSSL_ITEM reasons[] = { ++ { AES_MT_E_MALLOC, "Memory allocation failure" }, ++ { AES_MT_ONGOING_OPERATION, "Operation underway" }, ++ { AES_MT_BAD_KEYLEN, "Only 256, 192, and 128 Key lengths are supported" }, ++ { 0, NULL } /* Termination */ ++}; ++ ++/* function mapping for 256|192|128 key lengths */ ++const OSSL_DISPATCH aes_mt_funcs_256[] = { ++ { OSSL_FUNC_CIPHER_NEWCTX, (fptr_t)aes_mt_newctx_256 } , ++ { OSSL_FUNC_CIPHER_FREECTX, (fptr_t)aes_mt_freectx }, ++ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_UPDATE, (fptr_t)aes_mt_do_cipher }, ++ { OSSL_FUNC_CIPHER_GET_PARAMS, (fptr_t)aes_mt_get_params_256 }, ++ { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (fptr_t)aes_mt_gettable_params }, ++ { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (fptr_t)aes_mt_get_ctx_params }, ++ { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_gettable_ctx_params }, ++ { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (fptr_t)aes_mt_set_ctx_params }, ++ { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_settable_ctx_params }, ++ { 0, NULL } ++}; ++ ++const OSSL_DISPATCH aes_mt_funcs_192[] = { ++ { OSSL_FUNC_CIPHER_NEWCTX, (fptr_t)aes_mt_newctx_192 } , ++ { OSSL_FUNC_CIPHER_FREECTX, (fptr_t)aes_mt_freectx }, ++ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_UPDATE, (fptr_t)aes_mt_do_cipher }, ++ { OSSL_FUNC_CIPHER_GET_PARAMS, (fptr_t)aes_mt_get_params_192 }, ++ { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (fptr_t)aes_mt_gettable_params }, ++ { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (fptr_t)aes_mt_get_ctx_params }, ++ { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_gettable_ctx_params }, ++ { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (fptr_t)aes_mt_set_ctx_params }, ++ { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_settable_ctx_params }, ++ { 0, NULL } ++}; ++ ++const OSSL_DISPATCH aes_mt_funcs_128[] = { ++ { OSSL_FUNC_CIPHER_NEWCTX, (fptr_t)aes_mt_newctx_128 } , ++ { OSSL_FUNC_CIPHER_FREECTX, (fptr_t)aes_mt_freectx }, ++ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (fptr_t)aes_mt_start_threads }, ++ { OSSL_FUNC_CIPHER_UPDATE, (fptr_t)aes_mt_do_cipher }, ++ { OSSL_FUNC_CIPHER_GET_PARAMS, (fptr_t)aes_mt_get_params_128 }, ++ { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (fptr_t)aes_mt_gettable_params }, ++ { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (fptr_t)aes_mt_get_ctx_params }, ++ { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_gettable_ctx_params }, ++ { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (fptr_t)aes_mt_set_ctx_params }, ++ { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, ++ (fptr_t)aes_mt_settable_ctx_params }, ++ { 0, NULL } ++}; ++ ++/* the ciphers found in this provider */ ++const OSSL_ALGORITHM aes_mt_ciphers[] = { ++ { "aes_ctr_mt_256", "provider=hpnssh", aes_mt_funcs_256 }, ++ { "aes_ctr_mt_192", "provider=hpnssh", aes_mt_funcs_192 }, ++ { "aes_ctr_mt_128", "provider=hpnssh", aes_mt_funcs_128 }, ++ { NULL, NULL, NULL } ++}; ++ ++/* function mapping for provider methods */ ++const OSSL_DISPATCH provider_functions[] = { ++ { OSSL_FUNC_PROVIDER_TEARDOWN, (fptr_t)aes_mt_prov_teardown }, ++ { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (fptr_t)aes_mt_prov_query }, ++ { OSSL_FUNC_PROVIDER_GET_PARAMS, (fptr_t)aes_mt_prov_get_params }, ++ { OSSL_FUNC_PROVIDER_GET_REASON_STRINGS, (fptr_t)aes_mt_prov_reasons }, ++ { 0, NULL } ++}; ++ ++static const OSSL_PARAM ctx_get_param_table[] = { ++ { "keylen", OSSL_PARAM_UNSIGNED_INTEGER, NULL, sizeof(size_t), 0 }, ++ { NULL, 0, NULL, 0, 0 }, ++}; ++ ++static const OSSL_PARAM cipher_get_param_table[] = { ++ { "blocksize", OSSL_PARAM_UNSIGNED_INTEGER, NULL, sizeof(size_t), 0 }, ++ { "keylen", OSSL_PARAM_UNSIGNED_INTEGER, NULL, sizeof(size_t), 0 }, ++ { NULL, 0, NULL, 0, 0 }, ++}; ++ ++static const OSSL_PARAM cipher_set_param_table[] = { ++ { "keylen", OSSL_PARAM_UNSIGNED_INTEGER, NULL, sizeof(size_t), 0 }, ++ { NULL, 0, NULL, 0, 0 }, ++}; ++ ++ ++/* provider functions start here */ ++ ++static void provider_ctx_free(struct provider_ctx_st *ctx) ++{ ++ if (ctx != NULL) ++ proverr_free_handle(ctx->proverr_handle); ++ free(ctx); ++} ++ ++static struct provider_ctx_st *provider_ctx_new(const OSSL_CORE_HANDLE *core, ++ const OSSL_DISPATCH *in) ++{ ++ struct provider_ctx_st *ctx; ++ ++ if ((ctx = malloc(sizeof(*ctx))) != NULL ++ && (ctx->proverr_handle = proverr_new_handle(core, in)) != NULL) { ++ ctx->core_handle = core; ++ } else { ++ provider_ctx_free(ctx); ++ ctx = NULL; ++ } ++ return ctx; ++} ++ ++ ++/* returns the appropriate algo table for the requested function ++ * in this case we should only be working with OP_CIPHER */ ++const OSSL_ALGORITHM *aes_mt_prov_query(void *provctx, int operation_id, ++ int *no_store) ++{ ++ switch (operation_id) { ++ case OSSL_OP_CIPHER: ++ return aes_mt_ciphers; ++ } ++ return NULL; ++} ++ ++const OSSL_ITEM *aes_mt_prov_reasons(void *provctx) ++{ ++ return reasons; ++} ++ ++static int aes_mt_prov_get_params(void *provctx, OSSL_PARAM *params) ++{ ++ OSSL_PARAM *p; ++ int ok = 1; ++ ++ char *VERSION="1.0"; ++ char *BUILDTYPE="aes_ctr_mt@hpnssh.org"; ++ ++ for(p = params; p->key != NULL; p++) ++ if (strcasecmp(p->key, "version") == 0) { ++ *(const void **)p->data = VERSION; ++ p->return_size = strlen(VERSION); ++ } else if (strcasecmp(p->key, "buildinfo") == 0 ++ && BUILDTYPE[0] != '\0') { ++ *(const void **)p->data = BUILDTYPE; ++ p->return_size = strlen(BUILDTYPE); ++ } ++ return ok; ++} ++ ++/* The function that tears down this provider */ ++static void aes_mt_prov_teardown(void *vprovctx) ++{ ++ provider_ctx_free(vprovctx); ++} ++ ++int OSSL_provider_init(const OSSL_CORE_HANDLE *core, ++ const OSSL_DISPATCH *in, ++ const OSSL_DISPATCH **out, ++ void **vprovctx) ++{ ++ if ((*vprovctx = provider_ctx_new(core, in)) == NULL) ++ return 0; ++ *out = provider_functions; ++ return 1; ++} ++ ++/* parameter functions for 256|192|128 bit key lengths */ ++static int aes_mt_get_params_256(OSSL_PARAM params[]) ++{ ++ OSSL_PARAM *p; ++ int ok = 1; ++ ++ for (p = params; p->key != NULL; p++) { ++ if (strcasecmp(p->key, "blocksize") == 0) ++ if (provnum_set_size_t(p, AES_BLOCK_SIZE) < 0) { ++ ok = 0; ++ continue; ++ } ++ if (strcasecmp(p->key, "keylen") == 0) { ++ size_t keyl = 32; ++ ++ if (provnum_set_size_t(p, keyl) < 0) { ++ ok = 0; ++ continue; ++ } ++ } ++ } ++ return ok; ++} ++ ++static int aes_mt_get_params_192(OSSL_PARAM params[]) ++{ ++ OSSL_PARAM *p; ++ int ok = 1; ++ ++ for (p = params; p->key != NULL; p++) { ++ if (strcasecmp(p->key, "blocksize") == 0) ++ if (provnum_set_size_t(p, AES_BLOCK_SIZE) < 0) { ++ ok = 0; ++ continue; ++ } ++ if (strcasecmp(p->key, "keylen") == 0) { ++ size_t keyl = 24; ++ ++ if (provnum_set_size_t(p, keyl) < 0) { ++ ok = 0; ++ continue; ++ } ++ } ++ } ++ return ok; ++} ++ ++static int aes_mt_get_params_128(OSSL_PARAM params[]) ++{ ++ OSSL_PARAM *p; ++ int ok = 1; ++ ++ for (p = params; p->key != NULL; p++) { ++ if (strcasecmp(p->key, "blocksize") == 0) ++ if (provnum_set_size_t(p, AES_BLOCK_SIZE) < 0) { ++ ok = 0; ++ continue; ++ } ++ if (strcasecmp(p->key, "keylen") == 0) { ++ size_t keyl = 16; ++ ++ if (provnum_set_size_t(p, keyl) < 0) { ++ ok = 0; ++ continue; ++ } ++ } ++ } ++ return ok; ++} ++ ++/* Parameters that libcrypto can get from this implementation */ ++static const OSSL_PARAM *aes_mt_gettable_params(void *provctx) ++{ ++ return cipher_get_param_table; ++} ++ ++static const OSSL_PARAM *aes_mt_gettable_ctx_params(void *cctx, void *provctx) ++{ ++ return ctx_get_param_table; ++} ++ ++static int aes_mt_get_ctx_params(void *vctx, OSSL_PARAM params[]) ++{ ++ struct aes_mt_ctx_st *ctx = vctx; ++ int ok = 1; ++ ++ if (ctx->keylen > 0) { ++ OSSL_PARAM *p; ++ ++ for (p = params; p->key != NULL; p++) ++ if (strcasecmp(p->key, "keylen") == 0 ++ && provnum_set_size_t(p, ctx->keylen) < 0) { ++ ok = 0; ++ continue; ++ } ++ } ++ return ok; ++} ++ ++/* Parameters that libcrypto can send to this implementation */ ++static const OSSL_PARAM *aes_mt_settable_ctx_params(void *cctx, void *provctx) ++{ ++ return cipher_set_param_table; ++} ++ ++static int aes_mt_set_ctx_params(void *vctx, const OSSL_PARAM params[]) ++{ ++ struct aes_mt_ctx_st *ctx = vctx; ++ const OSSL_PARAM *p; ++ int ok = 1; ++ ++ if (ctx->ongoing) { ++ ERR_raise(ERR_HANDLE(ctx), AES_MT_ONGOING_OPERATION); ++ return 0; ++ } ++ ++ for (p = params; p->key != NULL; p++) ++ if (strcasecmp(p->key, "keylen") == 0) { ++ size_t keyl = 0; ++ ++ if (provnum_get_size_t(&keyl, p) < 0) { ++ ok = 0; ++ continue; ++ } ++ ctx->keylen = keyl; ++ } ++ return ok; ++} ++ ++#endif /*OPENSSL_VERSION_NUMBER */ ++#endif /*WITH_OPENSSL*/ +diff -Nur openssh-9.3p1.orig/cipher.h openssh-9.3p1/cipher.h +--- openssh-9.3p1.orig/cipher.h 2024-07-10 09:07:09.734081857 +0200 ++++ openssh-9.3p1/cipher.h 2024-07-10 09:09:55.902568408 +0200 +@@ -68,25 +68,29 @@ + + struct sshcipher_ctx; + +-const struct sshcipher *cipher_by_name(const char *); ++struct sshcipher *cipher_by_name(const char *); + const char *cipher_warning_message(const struct sshcipher_ctx *); + int ciphers_valid(const char *); + char *cipher_alg_list(char, int); + const char *compression_alg_list(int); + int cipher_init(struct sshcipher_ctx **, const struct sshcipher *, +- const u_char *, u_int, const u_char *, u_int, int); ++ const u_char *, u_int, const u_char *, u_int, int, int); + int cipher_crypt(struct sshcipher_ctx *, u_int, u_char *, const u_char *, + u_int, u_int, u_int); + int cipher_get_length(struct sshcipher_ctx *, u_int *, u_int, + const u_char *, u_int); + void cipher_free(struct sshcipher_ctx *); + u_int cipher_blocksize(const struct sshcipher *); ++uint64_t cipher_rekey_blocks(const struct sshcipher *); + u_int cipher_keylen(const struct sshcipher *); + u_int cipher_seclen(const struct sshcipher *); + u_int cipher_authlen(const struct sshcipher *); + u_int cipher_ivlen(const struct sshcipher *); + u_int cipher_is_cbc(const struct sshcipher *); ++void cipher_reset_multithreaded(void); ++const char *cipher_ctx_name(const struct sshcipher_ctx *); + ++const char *cipher_ctx_name(const struct sshcipher_ctx *); + u_int cipher_ctx_is_plaintext(struct sshcipher_ctx *); + + int cipher_get_keyiv(struct sshcipher_ctx *, u_char *, size_t); +diff -Nur openssh-9.3p1.orig/clientloop.c openssh-9.3p1/clientloop.c +--- openssh-9.3p1.orig/clientloop.c 2024-07-10 09:07:09.623081532 +0200 ++++ openssh-9.3p1/clientloop.c 2024-07-10 09:09:55.903568411 +0200 +@@ -114,6 +114,7 @@ + #include "msg.h" + #include "ssherr.h" + #include "hostfile.h" ++#include "metrics.h" + + #ifdef GSSAPI + #include "ssh-gss.h" +@@ -169,6 +170,10 @@ + + static void client_init_dispatch(struct ssh *ssh); + int session_ident = -1; ++int metrics_hdr_remote_flag = 0; ++int metrics_hdr_local_flag = 0; ++int remote_no_poll_flag = 0; ++int local_no_poll_flag = 0; + + /* Track escape per proto2 channel */ + struct escape_filter_ctx { +@@ -196,6 +201,9 @@ + TAILQ_HEAD_INITIALIZER(global_confirms); + + void ssh_process_session2_setup(int, int, int, struct sshbuf *); ++ ++void client_request_metrics(struct ssh *); ++ + static void quit_message(const char *fmt, ...) + __attribute__((__format__ (printf, 1, 2))); + +@@ -1283,6 +1291,7 @@ + double start_time, total_time; + int r, len; + u_int64_t ibytes, obytes; ++ time_t previous_time; + int conn_in_ready, conn_out_ready; + + debug("Entering interactive session."); +@@ -1323,6 +1332,7 @@ + client_repledge(); + + start_time = monotime_double(); ++ previous_time = time(NULL); /* for metrics polling */ + + /* Initialize variables. */ + last_was_cr = 1; +@@ -1368,9 +1378,17 @@ + } + + schedule_server_alive_check(); ++ if (options.metrics) ++ client_request_metrics(ssh); /* initial metrics polling */ + + /* Main loop of the client for the interactive session mode. */ + while (!quit_pending) { ++ if (options.metrics) { ++ if ((time(NULL) - previous_time) >= options.metrics_interval) { ++ client_request_metrics(ssh); ++ previous_time = time(NULL); ++ } ++ } + + /* Process buffered packets sent by the server. */ + client_process_buffered_input_packets(ssh); +@@ -1459,6 +1477,10 @@ + } + } + } ++ ++ if (options.metrics) ++ client_request_metrics(ssh); /* final metrics polling */ ++ + free(pfd); + + /* Terminate the session. */ +@@ -1646,7 +1668,9 @@ + return NULL; + c = channel_new(ssh, "x11", + SSH_CHANNEL_X11_OPEN, sock, sock, -1, +- CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); ++ /* again is this really necessary for X11? */ ++ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, ++ CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); + c->force_drain = 1; + return c; + } +@@ -1681,7 +1705,8 @@ + + c = channel_new(ssh, "authentication agent connection", + SSH_CHANNEL_OPEN, sock, sock, -1, +- CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, ++ options.hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : options.hpn_buffer_size, ++ CHAN_TCP_PACKET_DEFAULT, 0, + "authentication agent connection", 1); + c->force_drain = 1; + return c; +@@ -1708,7 +1733,8 @@ + debug("Tunnel forwarding using interface %s", ifname); + + c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, +- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); ++ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, ++ CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + c->datagram = 1; + + #if defined(SSH_TUN_FILTER) +@@ -2502,6 +2528,167 @@ + return 1; + } + ++/* take the response from the server and parse out the data. ++ * the _ctx should be null. It's just here because the format ++ * of the callback handler expects it. Likewise, seq is ++ * not used. */ ++static void ++client_process_request_metrics (struct ssh *ssh, int type, u_int32_t seq, void *_ctx) { ++ struct tcp_info local_tcp_info; ++ const u_char *blob; ++ FILE *remfptr; ++ FILE *localfptr; ++ char remfilename[1024]; ++ char localfilename[1024]; ++ time_t now; ++ struct tm *info; ++ char timestamp[40]; ++ char *metricsstring = NULL; ++ size_t tcpi_len, len = 0; ++ binn *metricsobj = NULL; ++ int r, kernel_version = 0; ++ ++ time(&now); ++ info = localtime(&now); ++ strftime(timestamp, 40, "%d-%m-%Y %H:%M:%S", info); ++ ++ /* malloc the string 1KB should be large enough */ ++ metricsstring = malloc(1024); ++ ++ /* get the local socket information */ ++ int sock_in = ssh_packet_get_connection_in(ssh); ++ ++ /* the user can specify a name/path with options.metrics_path ++ * but if it's not defined we'll use a default name. In either case ++ * the name will have a suffix of local for the local data and remote for ++ * the remote data */ ++ if (options.metrics_path == NULL) { ++ snprintf(remfilename, 1024, "%s", "./ssh_stack_metrics.remote"); ++ snprintf(localfilename, 1024, "%s", "./ssh_stack_metrics.local"); ++ } else { ++ snprintf(remfilename, 1024, "%s.%s", options.metrics_path, "remote"); ++ snprintf(localfilename, 1024, "%s.%s", options.metrics_path, "local"); ++ } ++ ++ /* should be type 81 and if it's not then its likley that ++ * the remote does not support polling. We can still get local data though ++ */ ++ if (type != SSH2_MSG_REQUEST_SUCCESS) { ++ if (remote_no_poll_flag == 0) { ++ error("Remote does not support stack metric polling. Local data only."); ++ remote_no_poll_flag = 1; ++ } ++ goto localonly; ++ } ++ ++ /* open the file handle to write the remote data*/ ++ remfptr = fopen(remfilename, "a"); ++ if (remfptr == NULL) ++ fatal("Error opening %s: %s", remfilename, strerror(errno)); ++ ++ /* read the entire packet string into blob ++ * blob has to be a const uchar as that's what string_direct expects ++ * we cast it as a void for the binn functions */ ++ sshpkt_get_string_direct(ssh, &blob, &len); ++ if (len == 0) { ++ /* received no data. which is weird */ ++ error("Received no remote metrics data. Continuing."); ++ } ++ ++ /* get the kernel version printing the header */ ++ kernel_version = binn_object_int32((void *)blob, "kernel_version"); ++ ++ /* create a string of the data from the binn object blob */ ++ metrics_read_binn_object((void *)blob, &metricsstring); ++ ++ /* have we printed the header? */ ++ if (metrics_hdr_remote_flag == 0) { ++ metrics_print_header(remfptr, "REMOTE CONNECTION", kernel_version); ++ metrics_hdr_remote_flag = 1; ++ } ++ fprintf(remfptr, "%s, ", timestamp); ++ fprintf(remfptr, "%s\n", metricsstring); ++ ++ /* close remote file pointer*/ ++ fclose(remfptr); ++ ++ /* got the remote data, now get the local */ ++localonly: ++/* TCP_INFO is defined in metrics.h*/ ++#if !defined TCP_INFO ++ if (local_no_poll_flag == 0) { ++ error("Local host does not support metric polling. Remote data only."); ++ local_no_poll_flag = 1; ++ } ++#else ++ /* open file handle for local data */ ++ localfptr = fopen(localfilename, "a"); ++ if(localfptr == NULL) ++ fatal("Error opening %s: %s", localfilename, strerror(errno)); ++ ++ /* create the binn object*/ ++ metricsobj = binn_object(); ++ if (metricsobj == NULL) { ++ fatal("Could not create metrics object"); ++ } ++ ++ tcpi_len = (size_t)sizeof(local_tcp_info); ++ if ((r = getsockopt(sock_in, IPPROTO_TCP, TCP_INFO, (void *)&local_tcp_info, ++ (socklen_t *)&tcpi_len)) != 0){ ++ error("Could not read tcp_info from socket"); ++ goto out; ++ } ++ ++ /* we write and read to a binn object because it lets us ++ * format the data consistently */ ++ metrics_write_binn_object(&local_tcp_info, metricsobj); ++ ++ /* create a string of the data from the binn object metricsobj */ ++ metrics_read_binn_object((void *)metricsobj, &metricsstring); ++ ++ /* get the kernel version printing the header */ ++ kernel_version = binn_object_int32(metricsobj, "kernel_version"); ++ ++ if (metrics_hdr_local_flag == 0) { ++ metrics_print_header(localfptr, "LOCAL CONNECTION", kernel_version); ++ metrics_hdr_local_flag = 1; ++ } ++ ++ fprintf(localfptr, "%s, ", timestamp); ++ fprintf(localfptr, "%s\n", metricsstring); ++ fclose (localfptr); ++#endif /* TCP_INFO */ ++out: ++ free(metricsstring); ++} ++ ++/* Use the SSH2_MSG_GLOBAL_REQUEST protocol to ++ * ask the server to send metrics back to the client. ++ * we use the non-canonical string stack-metrics@hpnssh.org ++ * to indicate the type of request we want. If the receiver doesn't ++ * understand it then the response indiactes a failure. ++ * I can probably do this by using clint_input_global_request but ++ * I need to understand that better. ++ */ ++void client_request_metrics(struct ssh *ssh) { ++ int r; ++ ++ debug("Asking server for TCP stack metrics"); ++ /* create a pakcet of GLOBAL_REQUEST type */ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || ++ /* define the type of GLOBAL_REQUEST message */ ++ (r = sshpkt_put_cstring(ssh, ++ "stack-metrics@hpnssh.org")) != 0 || ++ /* indicate if we want a response. 1 for yes 0 for no */ ++ (r = sshpkt_put_u8(ssh, 1)) != 0) ++ fatal_fr(r, "prepare stack request failure"); ++ /* send the packet */ ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal_fr(r, "send stack request"); ++ /* i believe this indicates what we are to use for a callback */ ++ client_register_global_confirm(client_process_request_metrics, NULL); ++} ++ + static int + client_input_global_request(int type, u_int32_t seq, struct ssh *ssh) + { +@@ -2557,6 +2744,11 @@ + if ((c = channel_lookup(ssh, id)) == NULL) + fatal_f("channel %d: unknown channel", id); + ++ if (options.hpn_buffer_limit) { ++ debug("Limiting receive buffer size"); ++ c->hpn_buffer_limit = 1; ++ } ++ + ssh_packet_set_interactive(ssh, want_tty, + options.ip_qos_interactive, options.ip_qos_bulk); + +@@ -2627,6 +2819,43 @@ + + len = sshbuf_len(cmd); + if (len > 0) { ++ /* we may be connecting to a server that has hpn prefixed ++ * binaries installed. In that case we need to rewrite any ++ * scp commands to look for hpnscp instead. ++ */ ++ if (ssh->compat & SSH_HPNSSH) { ++ char *new_cmd; ++ new_cmd = malloc(len+4); ++ /* read the existing command into a temp buffer */ ++ sprintf(new_cmd, "%s", (const u_char*)sshbuf_ptr(cmd)); ++ const char *pos; ++ /* see if the command starts with scp */ ++ pos = strstr(new_cmd, "scp"); ++ /* by substracting the pointer new_cmd from the pointer ++ * pos we end up with the position of the needle in the ++ * haystack. If it's 0 then we can mess with it ++ */ ++ if (pos - new_cmd == 0) { ++ debug("Rewriting scp command for hpnscp."); ++ sprintf(new_cmd, "hpn%s", (const u_char*)sshbuf_ptr(cmd)); ++ debug("Command was: %s and is now %s", ++ (const u_char*)sshbuf_ptr(cmd), new_cmd); ++ /* free the existing sshbuf 'cmd' ++ * recreate it and then write our new_cmd into ++ * the sshbuf struct ++ */ ++ sshbuf_free(cmd); ++ if ((cmd = sshbuf_new()) == NULL) ++ fatal("sshbuf_new failed in scp rewrite"); ++ sshbuf_putf(cmd, "%s", new_cmd); ++ /* we use len later on so don't forget to ++ * increment it by the number of new chars in the ++ * command ++ */ ++ len += 3; ++ free(new_cmd); ++ } ++ } + if (len > 900) + len = 900; + if (want_subsystem) { +diff -Nur openssh-9.3p1.orig/compat.c openssh-9.3p1/compat.c +--- openssh-9.3p1.orig/compat.c 2024-07-10 09:07:09.766081951 +0200 ++++ openssh-9.3p1/compat.c 2024-07-10 09:09:55.904568414 +0200 +@@ -135,6 +135,29 @@ + ssh->compat = check[i].bugs; + if (forbid_ssh_rsa) + ssh->compat |= SSH_RH_RSASIGSHA; ++ /* Check to see if the remote side is OpenSSH and not HPN */ ++ /* TODO: See if we can work this into the new method for bug checks */ ++ if (strstr(version, "OpenSSH") != NULL) { ++ if (strstr(version, "hpn") == NULL) { ++ ssh->compat |= SSH_BUG_LARGEWINDOW; ++ debug("Remote is NOT HPN enabled"); ++ } else { ++ /* this checks to see if the remote ++ * version string indicates that we ++ * have access to hpn prefixed binaries ++ * You'll need to change this to include ++ * new major version numbers. Which is ++ * why we should figure out how to make ++ * the match pattern list work ++ */ ++ if ((strstr(version, "hpn16") != NULL) || ++ (strstr(version, "hpn17") != NULL) || ++ (strstr(version, "hpn18") != NULL)) ++ ssh->compat |= SSH_HPNSSH; ++ debug("Remote is HPN Enabled"); ++ } ++ } ++ debug("ssh->compat is %u", ssh->compat); + return; + } + } +diff -Nur openssh-9.3p1.orig/compat.h openssh-9.3p1/compat.h +--- openssh-9.3p1.orig/compat.h 2024-07-10 09:07:09.766081951 +0200 ++++ openssh-9.3p1/compat.h 2024-07-10 09:09:55.904568414 +0200 +@@ -51,12 +51,13 @@ + #define SSH_BUG_PROBE 0x00400000 + /* #define unused 0x00800000 */ + #define SSH_OLD_FORWARD_ADDR 0x01000000 +-/* #define unused 0x02000000 */ ++#define SSH_HPNSSH 0x02000000 /* indicates that we have hpn prefixes binaries */ + #define SSH_NEW_OPENSSH 0x04000000 + #define SSH_BUG_DYNAMIC_RPORT 0x08000000 + #define SSH_BUG_CURVE25519PAD 0x10000000 + #define SSH_BUG_HOSTKEYS 0x20000000 + #define SSH_BUG_DHGEX_LARGE 0x40000000 ++#define SSH_BUG_LARGEWINDOW 0x80000000 /* basically a notice that this is HPN aware */ + + struct ssh; + +diff -Nur openssh-9.3p1.orig/configure.ac openssh-9.3p1/configure.ac +--- openssh-9.3p1.orig/configure.ac 2024-07-10 09:07:09.804082062 +0200 ++++ openssh-9.3p1/configure.ac 2024-07-10 09:09:55.905568417 +0200 +@@ -3056,6 +3056,30 @@ + EVP_chacha20 \ + ]) + ++ # OpenSSL 3.0 API ++ # Does OpenSSL support the EVP_MAC functions for Poly1305? ++ AC_MSG_CHECKING([whether OpenSSL supports Poly1305 MAC EVP]) ++ AC_RUN_IFELSE( ++ [AC_LANG_PROGRAM([[ ++ #include ++ #include ++ #include ++ ]], [[ ++ EVP_MAC *mac = EVP_MAC_fetch(NULL, "poly1305", NULL); ++ if (mac == NULL) ++ exit(1); ++ ]])], ++ [ ++ AC_MSG_RESULT([yes]) ++ AC_DEFINE([OPENSSL_HAVE_POLY_EVP], [1], ++ [Libcrypto supports Poly1305 MAC EVP]) ++ ], ++ [ ++ AC_MSG_RESULT([no]) ++ ] ++ ) ++ ++ + if test "x$openssl_engine" = "xyes" ; then + AC_MSG_CHECKING([for OpenSSL ENGINE support]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +diff -Nur openssh-9.3p1.orig/defines.h openssh-9.3p1/defines.h +--- openssh-9.3p1.orig/defines.h 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/defines.h 2024-07-10 09:09:55.906568420 +0200 +@@ -889,7 +889,7 @@ + #endif + + #ifndef SSH_IOBUFSZ +-# define SSH_IOBUFSZ 8192 ++# define SSH_IOBUFSZ 32*1024 + #endif + + /* +diff -Nur openssh-9.3p1.orig/digest.h openssh-9.3p1/digest.h +--- openssh-9.3p1.orig/digest.h 2024-07-10 09:07:09.704081770 +0200 ++++ openssh-9.3p1/digest.h 2024-07-10 09:09:55.906568420 +0200 +@@ -27,7 +27,8 @@ + #define SSH_DIGEST_SHA256 2 + #define SSH_DIGEST_SHA384 3 + #define SSH_DIGEST_SHA512 4 +-#define SSH_DIGEST_MAX 5 ++#define SSH_DIGEST_NULL 5 ++#define SSH_DIGEST_MAX 6 + + struct sshbuf; + struct ssh_digest_ctx; +diff -Nur openssh-9.3p1.orig/digest-openssl.c openssh-9.3p1/digest-openssl.c +--- openssh-9.3p1.orig/digest-openssl.c 2024-07-10 09:07:09.704081770 +0200 ++++ openssh-9.3p1/digest-openssl.c 2024-07-10 09:09:55.907568423 +0200 +@@ -61,6 +61,7 @@ + { SSH_DIGEST_SHA256, "SHA256", 32, EVP_sha256 }, + { SSH_DIGEST_SHA384, "SHA384", 48, EVP_sha384 }, + { SSH_DIGEST_SHA512, "SHA512", 64, EVP_sha512 }, ++ { SSH_DIGEST_NULL, "NONEMAC", 0, EVP_md_null}, + { -1, NULL, 0, NULL }, + }; + +diff -Nur openssh-9.3p1.orig/FUNDING.yml openssh-9.3p1/FUNDING.yml +--- openssh-9.3p1.orig/FUNDING.yml 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/FUNDING.yml 2024-07-10 09:09:55.907568423 +0200 +@@ -0,0 +1,12 @@ ++# These are supported funding model platforms ++ ++github:rapier1 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] ++#patreon: # Replace with a single Patreon username ++#open_collective: # Replace with a single Open Collective username ++#ko_fi: # Replace with a single Ko-fi username ++#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel ++#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry ++#liberapay: # Replace with a single Liberapay username ++#issuehunt: # Replace with a single IssueHunt username ++#otechie: # Replace with a single Otechie username ++#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +diff -Nur openssh-9.3p1.orig/HPN-README openssh-9.3p1/HPN-README +--- openssh-9.3p1.orig/HPN-README 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/HPN-README 2024-07-10 09:09:55.907568423 +0200 +@@ -0,0 +1,230 @@ ++Notes: ++ ++LibreSSL Support: ++Changes in LibreSSL version 3.5 and 3.6 prevent the use of the threaded AES CTR cipher. ++In those cases HPNSSH will fallback to the serial versionof the AES CTR cipher. A warning ++is printed to stderr. ++ ++Automatic Port Fallback (in version 17v3) ++The hpnssh client now uses TCP port 2222 to connect automatically as this is the ++default hpnsshd port. However, we understand that many users will be end up connecting ++standard SSH servers on port 22. To make the easier for users the client will fall back to ++port 22 in the event that there is no hpnssh server running on port 2222. The behaviour can ++be modifed as follows: ++-oFallback=[yes|no] will enable or disable port fallback. Default is yes. ++-oFallbackPort=[N] where N is the port number that should be used for fall back. ++ Default is 22. ++ ++TCP_INFO Metrics ++This features allows the client to request tcp networking information from the ++TCP_INFO struct. This includes data on retransmits, round trip time, lost packets, ++data transferred, and the like. The metrics are polled periodically through the ++life of the connection. By default this is every 5 seconds but users can pick different ++polling periods. The resulting data is stored in two distinct files; one for local ++metrics and one for remote metrics. Remote metrics are only available if the remote ++supports this feature. This feature will *not* diagnose a poorly performing connection ++but may provide insight into what is happening during the connection. ++ ++Usage: ++-oMetrics=[yes|no] will enable metrics polling. Default: No. ++-oMetricsInterval=[N] where N is the polling period in seconds. Default: 5 seconds. ++-oMetricsPath=[/filepath/filename] is the name of the file where the remote and ++ local data will be stored. Default: ./ssh_stack_metrics.[local|remote]. ++ Any other option chosen by the user will have a .local or .remote suffix appended to it. ++ ++The number of instruments polled by this features is dependent on the kernel of the host. ++This means that a remote host with an older kernel may report fewer instruments than a client ++host running a current kernel or vice versa. If there is a discrepency in the number of instruments ++in the ssh_stack_metrics.local and .remote file this is the most likely reason. ++ ++Additionally, determining which file represents sender side versus receiver side is dependent ++on the nature of the connection. Therfore, it's up to the user to make that determination. ++ ++Linux Note: Currently this is only supported on Linux kernel versions 3.7 and greater. Newer kernels ++may have more instruments available to poll than older kernels. ++ ++FreeBSD Note: This is supported on Release 6 and higher. However, FreeBSD has fewer available ++instruments than new Linux kernels. ++ ++Multiplexing Note: The metrics are reported from the TCP socket which means that gathering ++metrics from multiplexed sessions will report on the activity of all sessions on that socket. ++This will likely result in less clear results and, as such, we suggest only gathering metrics ++from non-multiplexed session. ++ ++SCP with Resume functionality ++This feature allows SCP to resume failed transfers. In the event of a failed transfer ++issues the same scp command with the '-R' option. For example - if you issued: ++'scp myhugefile me@host:~' ++and it dies halfway through the transfer issuing ++'scp -Z myhugefile me@host:~' ++will resume the transfer at the point where it left off. ++ ++This is implemented by having the source host send a hash (blake2b512) of the file to the ++target host. Teh target host then computes it's own hash of the target file. If the hashes match ++then the file is skipped as this indicates a successful transfer. However, if the hashes do not ++match then the target sends the source its hash along with the size of the file. The source then ++computes the hash of the file *up to* the size of the target file. If those hashes match then ++the source only send the necessary bytes to complete the transfer. If the hashes do not match then ++the entire file is resent. If the target file is larger then the source file then the entire ++source file is sent and any existing target file is overwritten. ++ ++SCP however, will use the first scp in the user's path. This might not support the resume ++function and the attempt will fail. In those cases the user can explicitly define the path to the ++resume enabled scp with the '-z' option. For example: ++ ++'scp -Z -z /opt/hpnssh/usr/bin/scp myhugefile me@host:~' ++ ++MULTI-THREADED CIPHER: ++The AES cipher in CTR mode has been multithreaded (MTR-AES-CTR). This will allow ssh installations ++on hosts with multiple cores to use more than one processing core during encryption. ++Tests have show significant throughput performance increases when using MTR-AES-CTR up ++to and including a full gigabit per second on quad core systems. It should be possible to ++achieve full line rate on dual core systems but OS and data management overhead makes this ++more difficult to achieve. The cipher stream from MTR-AES-CTR is entirely compatible with single ++thread AES-CTR (ST-AES-CTR) implementations and should be 100% backward compatible. Optimal ++performance requires the MTR-AES-CTR mode be enabled on both ends of the connection. ++The MTR-AES-CTR replaces ST-AES-CTR and is used in exactly the same way with the same ++nomenclature. ++Use examples: ++ ssh -caes128-ctr you@host.com ++ scp -oCipher=aes256-ctr file you@host.com:~/file ++ ++NONE CIPHER: ++To use the NONE option you must have the NoneEnabled switch set on the server and ++you *must* have *both* NoneEnabled and NoneSwitch set to yes on the client. The NONE ++feature works with ALL ssh subsystems (as far as we can tell) *AS LONG AS* a tty is not ++spawned. If a user uses the -T switch to prevent a tty being created the NONE cipher will ++be disabled. ++ ++The performance increase will only be as good as the network and TCP stack tuning ++on the reciever side of the connection allows. As a rule of thumb a user will need ++at least 10Mb/s connection with a 100ms RTT to see a doubling of performance. The ++HPN-SSH home page describes this in greater detail. ++ ++http://www.psc.edu/networking/projects/hpn-ssh ++ ++NONE MAC: ++Starting with HPN 15v1 users will have the option to disable HMAC (message ++authentication ciphers) when using the NONE cipher. You must enable the following: ++NoneEnabled, NoneSwitch, and NoneMacEnabled. If all three are not enabled the None MAC ++will be automatically disabled. In tests the use of the None MAC improved throuput by ++more than 30%. ++ ++ex: scp -oNoneSwitch=yes -oNoneEnabled=yes -oNoneMacEnabled=yes file host:~ ++ ++BUFFER SIZES: ++ ++If HPN is disabled the receive buffer size will be set to the ++OpenSSH default of 2MB (for OpenSSH versions before 4.7: 64KB). ++ ++If an HPN system connects to a nonHPN system the receive buffer will ++be set to the HPNBufferSize value. The default is 2MB but user adjustable. ++ ++If an HPN to HPN connection is established a number of different things might ++happen based on the user options and conditions. ++ ++Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set ++HPN Buffer Size = up to 64MB ++This is the default state. The HPN buffer size will grow to a maximum of 64MB ++as the TCP receive buffer grows. The maximum HPN Buffer size of 64MB is ++geared towards 10GigE transcontinental connections. ++ ++Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set ++HPN Buffer Size = TCP receive buffer value. ++Users on non-autotuning systems should disable TCPRcvBufPoll in the ++ssh_config and sshd_config ++ ++Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set ++HPN Buffer Size = minimum of TCP receive buffer and HPNBufferSize. ++This would be the system defined TCP receive buffer (RWIN). ++ ++Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf SET ++HPN Buffer Size = minimum of TCPRcvBuf and HPNBufferSize. ++Generally there is no need to set both. ++ ++Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set ++HPN Buffer Size = grows to HPNBufferSize ++The buffer will grow up to the maximum size specified here. ++ ++Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf SET ++HPN Buffer Size = minimum of TCPRcvBuf and HPNBufferSize. ++Generally there is no need to set both of these, especially on autotuning ++systems. However, if the users wishes to override the autotuning this would be ++one way to do it. ++ ++Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf SET ++HPN Buffer Size = TCPRcvBuf. ++This will override autotuning and set the TCP recieve buffer to the user defined ++value. ++ ++ ++HPN Specific Configuration options ++ ++TcpRcvBuf=[int]KB client ++ Set the TCP socket receive buffer to n Kilobytes. It can be set up to the ++maximum socket size allowed by the system. This is useful in situations where ++the tcp receive window is set low but the maximum buffer size is set ++higher (as is typical). This works on a per TCP connection basis. You can also ++use this to artifically limit the transfer rate of the connection. In these ++cases the throughput will be no more than n/RTT. The minimum buffer size is 1KB. ++Default is the current system wide tcp receive buffer size. ++ ++TcpRcvBufPoll=[yes/no] client/server ++ Enable of disable the polling of the tcp receive buffer through the life ++of the connection. You would want to make sure that this option is enabled ++for systems making use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista) ++default is yes. ++ ++NoneEnabled=[yes/no] client/server ++ Enable or disable the use of the None cipher. Care must always be used ++when enabling this as it will allow users to send data in the clear. However, ++it is important to note that authentication information remains encrypted ++even if this option is enabled. Set to no by default. ++ ++NoneMacEnabled=[yes/no] client/server ++ Enable or disable the use of the None MAC. When this is enabled ssh ++will *not* provide data integrity of any data being transmitted between hosts. Use ++with caution as it, unlike just using NoneEnabled, doesn't provide data integrity and ++protection against man-in-the-middle attacks. As with NoneEnabled all authentication ++remains encrypted and integrity is ensured. Default is no. ++ ++NoneSwitch=[yes/no] client ++ Switch the encryption cipher being used to the None cipher after ++authentication takes place. NoneEnabled must be enabled on both the client ++and server side of the connection. When the connection switches to the NONE ++cipher a warning is sent to STDERR. The connection attempt will fail with an ++error if a client requests a NoneSwitch from the server that does not explicitly ++have NoneEnabled set to yes. Note: The NONE cipher cannot be used in ++interactive (shell) sessions and it will fail silently. Set to no by default. ++ ++HPNDisabled=[yes/no] client/server ++ In some situations, such as transfers on a local area network, the impact ++of the HPN code produces a net decrease in performance. In these cases it is ++helpful to disable the HPN functionality. By default HPNDisabled is set to no. ++ ++HPNBufferSize=[int]KB client/server ++ This is the default buffer size the HPN functionality uses when interacting ++with nonHPN SSH installations. Conceptually this is similar to the TcpRcvBuf ++option as applied to the internal SSH flow control. This value can range from ++1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause performance ++problems depending on the length of the network path. The default size of this buffer ++is 2MB. ++ ++DisableMTAES=[yes/no] client/server ++ Switch the encryption cipher being used from the multithreaded MT-AES-CTR cipher ++back to the stock single-threaded AES-CTR cipher. Useful on modern processors with ++AES-NI instructions which make the stock single-threaded AES-CTR cipher faster than ++the multithreaded MT-AES-CTR cipher. Set to no by default. ++ ++ ++Credits: This patch was conceived, designed, and led by Chris Rapier (rapier@psc.edu) ++ The majority of the actual coding for versions up to HPN12v1 was performed ++ by Michael Stevens (mstevens@andrew.cmu.edu). The MT-AES-CTR cipher was ++ implemented by Ben Bennet (ben@psc.edu) and improved by Mike Tasota ++ (tasota@gmail.com) an NSF REU grant recipient for 2013. ++ Allan Jude provided the code for the NoneMac and buffer normalization. ++ This work was financed, in part, by Cisco System, Inc., the National ++ Library of Medicine, and the National Science Foundation. ++ ++Sponsors: Thanks to Niklas Hambuchen for being the first sponsor of HPN-SSH ++ via github's sponsor program! +diff -Nur openssh-9.3p1.orig/HPNSSHInstallation.txt openssh-9.3p1/HPNSSHInstallation.txt +--- openssh-9.3p1.orig/HPNSSHInstallation.txt 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/HPNSSHInstallation.txt 2024-07-10 09:09:55.908568426 +0200 +@@ -0,0 +1,350 @@ ++HPN-SSH Installation ++ ++The process of installing HPN-SSH from source is a relatively painless process ++but does have some nuances. This document will go through the process step by ++step to help you get the most from your installation. If you find any errors ++please contact us at hpnssh@psc.edu. ++ ++ ++Step 1: Get the source code. ++ ++The official repository for HPN-SSH is found at ++https://github.com/rapier1/openssh-portable. Get a copy with ++"git clone https://github.com/rapier1/openssh-portable”. ++ ++ ++Step 2: Install dependencies. ++ ++What you need to install is dependent on your distribution but will include: ++* OpenSSL development package ++ * Debian: libssl-dev ++ * Fedora: openssl-devel ++* Alternatively you can use LIbreSSL ++ * However, in this case we suggest compiling and installing libressl manually as ++ there are few maintained linux packages for LibreSSL. ++ * Also, LibreSSL v3.5 and v3.6 do not support the threaded AES-CTR cipher. ++ If that’s important to you then you should use OpenSSL. ++* Z compression library ++ * Debian: zlib1g-dev ++ * Fedora: zlib-devel ++* Autoconf ++* Automake ++ ++ ++Step 3: Install optional dependencies. ++ ++This optional libraries will extend the functionality of HPN-SSH to allow the use of PAM ++authentication, Kerberos, graphical password tools, etc. ++* PAM ++* Kerberos ++* GTK ++ ++ ++Step 4: Build the configure file ++ ++generate ./configure with “autoreconf -f -i” ++ ++ ++Step 5: Configuration ++ ++Configure the installation. You can get detailed information on how to do this by ++issuing “./configure --help”. However, commonly you will want to change the default installation ++location of the binaries. This can be done with “--prefix=/[desired_path]”. For example, ++if you want the binaries installed into /usr/bin as opposed to the default of ++/usr/local/bin you’d use “./configure --prefix=/usr”. Other common options would be to ++incorporate pam, kerberos, alternative SSL libraries, and so forth. However, for most users ++either no additional configuration options or modifying the prefix will suffice. ++ ++ ++Step 6: Make ++ ++Make the application with “make -j[num cores]”. So if you have an 8 core system ++you’d use “make -j8” ++ ++ ++Step 7: Installation ++ ++After HPN-SSH successfully builds, install it with “sudo make install”. This will install the ++binaries, configuration files, and generate the unique host keys used. At this point you can ++make changes to the ssh client and server default configuration. These files are ++found, generally, in /etc/hpnssh/ssh_config and sshd_config respectively. You may want to ++change the default port from 2222 to some other value. You may also want to enable the ++NoneCipher and NoneMac options. For more information use “man hpnsshd_config” and ++“man hpnssh_config”. Note: The hpnssh client expects the server to be on port 2222 but will ++fallback to 22 if it’s not found there. So if you do change the default port you’ll need to ++make sure the clients point at the correct port. ++ ++ ++Step 8: Set up the hpnsshd user. ++ ++This user is part of the privilege separation routines used in the ++pre-authentication sandbox. I suggest using the following command: ++ ++sudo useradd --system --shell /usr/sbin/nologin --comment="Privilege separated HPNSSH User" \ ++--home=/run/hpnsshd hpnsshd ++ ++Alternatively, you can use vipw to add the user manually. ++ ++ ++Step 9: Finishing up. ++ ++At this point you can start hpnsshd manually by running “sudo /usr/sbin/hpnsshd” ++or whatever the full path to the hpnsshd binary might be. However, this won’t ++restart automatically on reboot. To do this you’ll need to install an appropriate ++systemd configuration file. If that seems like a good idea to you then following steps may be ++of help. Otherwise, you are done. Enjoy! ++ ++ ++Step 10: Installing a systemd startup file. ++ ++The correct systemd startup file depends on the distribution you are using. For system ++using systemd (you start a service with systemctl) create a file at /lib/systemd/system/hpnssh.service ++with the following contents NB: you may need to update the paths to match your installation: ++ ++[Unit] ++Description=HPN/OpenBSD Secure Shell server ++Documentation=man:hpnsshd(8) man:hpnsshd_config(5) ++After=network.target auditd.service ++ConditionPathExists=!/etc/hpnssh/sshd_not_to_be_run ++ ++[Service] ++EnvironmentFile=-/etc/default/hpnssh ++ExecStartPre=/usr/sbin/hpnsshd -t ++ExecStart=/usr/sbin/hpnsshd -D $SSHD_OPTS ++ExecReload=/usr/sbin/hpnsshd -t ++ExecReload=/bin/kill -HUP $MAINPID ++KillMode=process ++Restart=on-failure ++RestartPreventExitStatus=255 ++Type=notify ++RuntimeDirectory=hpnsshd ++RuntimeDirectoryMode=0755 ++ ++[Install] ++WantedBy=multi-user.target ++Alias=hpnsshd.service ++ ++ ++Then create the defaults file at /etc/defaults/hpnssh with the following content: ++# Default settings for openssh-server. ++# Options to pass to sshd ++SSHD_OPTS= ++ ++Enter any runtime options you want on the SSHD_OPTS line. If you can’t think of any, simply ++leave it blank. ++ ++You must then reload the systemd service to make it aware of this new service with ++sudo systemctl daemon-reload ++ ++If you are using an init.d (you start a service with ‘system’) then you need to install an ++init.d. Create the file /etc/init.d/hpnssh and copy the following into it. NB: The following is ++for where hpnsshd is found at /usr/sbin/hpnsshd. If it is not in that location you’ll need to ++update the paths. ++ ++#! /bin/sh ++ ++### BEGIN INIT INFO ++# Provides: hpnsshd ++# Required-Start: $remote_fs $syslog ++# Required-Stop: $remote_fs $syslog ++# Default-Start: 2 3 4 5 ++# Default-Stop: ++# Short-Description: OpenBSD Secure Shell server with HPN ++### END INIT INFO ++ ++set -e ++ ++# /etc/init.d/hpnssh: start and stop the OpenBSD "secure shell(tm)" daemon ++ ++test -x /usr/sbin/hpnsshd || exit 0 ++( /usr/sbin/hpnsshd -\? 2>&1 | grep -q OpenSSH ) 2>/dev/null || exit 0 ++ ++umask 022 ++ ++if test -f /etc/default/hpnssh; then ++ . /etc/default/hpnssh ++fi ++ ++. /lib/lsb/init-functions ++ ++ ++if [ -n "$2" ]; then ++ SSHD_OPTS="$SSHD_OPTS $2" ++fi ++ ++# Are we running from init? ++run_by_init() { ++ ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ] ++} ++ ++check_for_no_start() { ++ # forget it if we're trying to start, and /etc/hpnssh/sshd_not_to_be_run exists ++ if [ -e /etc/hpnssh/sshd_not_to_be_run ]; then ++ if [ "$1" = log_end_msg ]; then ++ log_end_msg 0 || true ++ fi ++ if ! run_by_init; then ++ log_action_msg "HPN/OpenBSD Secure Shell server not in use (/etc/hpnssh/sshd_not_to_be_run)" || true ++ fi ++ exit 0 ++ fi ++} ++ ++check_dev_null() { ++ if [ ! -c /dev/null ]; then ++ if [ "$1" = log_end_msg ]; then ++ log_end_msg 1 || true ++ fi ++ if ! run_by_init; then ++ log_action_msg "/dev/null is not a character device!" || true ++ fi ++ exit 1 ++ fi ++} ++ ++check_privsep_dir() { ++ # Create the PrivSep empty dir if necessary ++ if [ ! -d /run/hpnsshd ]; then ++ mkdir /run/hpnsshd ++ chmod 0755 /run/hpnsshd ++ fi ++} ++ ++check_config() { ++ if [ ! -e /etc/hpnssh/sshd_not_to_be_run ]; then ++ # shellcheck disable=SC2086 ++ /usr/sbin/hpnsshd $SSHD_OPTS -t || exit 1 ++ fi ++} ++ ++export PATH="${PATH:+$PATH:}/usr/sbin:/sbin" ++ ++case "$1" in ++ start) ++ check_privsep_dir ++ check_for_no_start ++ check_dev_null ++ log_daemon_msg "Starting HPN/OpenBSD Secure Shell server" "hpnsshd" || true ++ # shellcheck disable=SC2086 ++ if start-stop-daemon --start --quiet --oknodo --chuid 0:0 --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd -- $SSHD_OPTS; then ++ log_end_msg 0 || true ++ else ++ log_end_msg 1 || true ++ fi ++ ;; ++ stop) ++ log_daemon_msg "Stopping HPN/OpenBSD Secure Shell server" "hpnsshd" || true ++ if start-stop-daemon --stop --quiet --oknodo --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd; then ++ log_end_msg 0 || true ++ else ++ log_end_msg 1 || true ++ fi ++ ;; ++ ++ ++ reload|force-reload) ++ check_for_no_start ++ check_config ++ log_daemon_msg "Reloading HPN/OpenBSD Secure Shell server's configuration" "hpnsshd" || true ++ if start-stop-daemon --stop --signal 1 --quiet --oknodo --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd; then ++ log_end_msg 0 || true ++ else ++ log_end_msg 1 || true ++ fi ++ ;; ++ ++ ++ restart) ++ check_privsep_dir ++ check_config ++ log_daemon_msg "Restarting HPN/OpenBSD Secure Shell server" "hpnsshd" || true ++ start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd ++ check_for_no_start log_end_msg ++ check_dev_null log_end_msg ++ # shellcheck disable=SC2086 ++ if start-stop-daemon --start --quiet --oknodo --chuid 0:0 --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd -- $SSHD_OPTS; then ++ log_end_msg 0 || true ++ else ++ log_end_msg 1 || true ++ fi ++ ;; ++ ++ ++ try-restart) ++ check_privsep_dir ++ check_config ++ log_daemon_msg "Restarting HPN/OpenBSD Secure Shell server" "hpnsshd" || true ++ RET=0 ++ start-stop-daemon --stop --quiet --retry 30 --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd || RET="$?" ++ case $RET in ++ 0) ++ # old daemon stopped ++ check_for_no_start log_end_msg ++ check_dev_null log_end_msg ++ # shellcheck disable=SC2086 ++ if start-stop-daemon --start --quiet --oknodo --chuid 0:0 --pidfile /run/hpnsshd.pid --exec /usr/sbin/hpnsshd -- $SSHD_OPTS; then ++ log_end_msg 0 || true ++ else ++ log_end_msg 1 || true ++ fi ++ ;; ++ 1) ++ # daemon not running ++ log_progress_msg "(not running)" || true ++ log_end_msg 0 || true ++ ;; ++ *) ++ # failed to stop ++ log_progress_msg "(failed to stop)" || true ++ log_end_msg 1 || true ++ ;; ++ esac ++ ;; ++ ++ ++ status) ++ status_of_proc -p /run/hpnsshd.pid /usr/sbin/hpnsshd hpnsshd && exit 0 || exit $? ++ ;; ++ ++ ++ *) ++ log_action_msg "Usage: /etc/init.d/hpnssh {start|stop|reload|force-reload|restart|try-restart|status}" || true ++ exit 1 ++esac ++ ++exit 0 ++ ++ ++Step 10: Working with SELinux. ++ ++If you are using SELinux you’ll need to run a few more commands in order to grant hpnssh the ++necessary exceptions to open sockets, files, read keys, and so forth. Run the following commands ++to allow this. Note, I’m not sure every single one of these is needed so if someone knows better ++please let me know. Again, double check the paths of the files being updated. ++ ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_dsa_key ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_rsa_key ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_ecdsa_key ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_ed25519_key ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_dsa_key.pub ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_rsa_key.pub ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_ecdsa_key.pub ++semanage fcontext -a -f f -t sshd_key_t /etc/hpnssh/ssh_host_ed25519_key.pub ++semanage fcontext -a -f f -t sshd_exec_t /usr/sbin/hpnsshd ++semanage fcontext -a -f f -t sshd_keygen_exec_t /usr/libexec/hpnssh/hpnsshd-keygen ++semanage fcontext -a -f f -t bin_t /usr/libexec/hpnssh/hpnsftp-server ++semanage fcontext -a -f f -t ssh_exec_t /usr/bin/hpnssh ++semanage fcontext -a -f f -t ssh_agent_exec_t /usr/bin/hpnssh-agent ++semanage fcontext -a -f f -t ssh_keygen_exec_t /usr/bin/hpnssh-keygen ++semanage fcontext -a -f f -t etc_t /etc/pam.d/hpnsshd ++semanage port -a -t ssh_port_t -p tcp 2222 ++restorecon /usr/sbin/hpnsshd ++restorecon /etc/hpnssh/ssh*_key ++restorecon /etc/hpnssh/ssh*_key\.pub ++restorecon /usr/libexec/hpnssh/hpnsshd-keygen ++restorecon /usr/libexec/hpnssh/hpnsftp-server ++restorecon /usr/bin/hpnssh ++restorecon /usr/bin/hpnssh-agent ++restorecon /usr/bin/hpnssh-keygen ++restorecon /etc/pam.d/hpnsshd ++ +diff -Nur openssh-9.3p1.orig/kex.c openssh-9.3p1/kex.c +--- openssh-9.3p1.orig/kex.c 2024-07-10 09:07:09.780081992 +0200 ++++ openssh-9.3p1/kex.c 2024-07-10 09:44:57.239722141 +0200 +@@ -65,6 +65,7 @@ + + #include "ssherr.h" + #include "sshbuf.h" ++#include "canohost.h" + #include "digest.h" + #include "xmalloc.h" + #include "audit.h" +@@ -1079,6 +1080,11 @@ + int nenc, nmac, ncomp; + u_int mode, ctos, need, dh_need, authlen; + int r, first_kex_follows; ++ int auth_flag = 0; ++ int log_flag = 0; ++ ++ auth_flag = packet_authentication_state(ssh); ++ debug("AUTH STATE IS %d", auth_flag); + + debug2("local %s KEXINIT proposal", kex->server ? "server" : "client"); + if ((r = kex_buf2prop(kex->my, NULL, &my)) != 0) +@@ -1169,11 +1175,40 @@ + peer[ncomp] = NULL; + goto out; + } ++ debug("REQUESTED ENC.NAME is '%s'", newkeys->enc.name); ++ debug("REQUESTED MAC.NAME is '%s'", newkeys->mac.name); ++ if (strcmp(newkeys->enc.name, "none") == 0) { ++ if (auth_flag == 1) { ++ debug("None requested post authentication."); ++ ssh->none = 1; ++ } ++ else ++ fatal("Pre-authentication none cipher requests are not allowed."); ++ if (newkeys->mac.name != NULL && strcmp(newkeys->mac.name, "none") == 0) ++ debug("Requesting: NONEMAC. Authflag is %d", auth_flag); ++ } ++ + debug("kex: %s cipher: %s MAC: %s compression: %s", + ctos ? "client->server" : "server->client", + newkeys->enc.name, + authlen == 0 ? newkeys->mac.name : "", + newkeys->comp.name); ++ /* ++ * client starts with ctos = 0 && log flag = 0 and no log. ++ * 2nd client pass ctos = 1 and flag = 1 so no log. ++ * server starts with ctos = 1 && log_flag = 0 so log. ++ * 2nd sever pass ctos = 1 && log flag = 1 so no log. ++ * -cjr ++ */ ++ if (ctos && !log_flag) { ++ logit("SSH: Server;Ltype: Kex;Remote: %s-%d;Enc: %s;MAC: %s;Comp: %s", ++ ssh_remote_ipaddr(ssh), ++ ssh_remote_port(ssh), ++ newkeys->enc.name, ++ authlen == 0 ? newkeys->mac.name : "", ++ newkeys->comp.name); ++ } ++ log_flag = 1; + } + need = dh_need = 0; + for (mode = 0; mode < MODE_MAX; mode++) { +@@ -1521,7 +1556,7 @@ + if (version_addendum != NULL && *version_addendum == '\0') + version_addendum = NULL; + if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", +- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, ++ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, + version_addendum == NULL ? "" : " ", + version_addendum == NULL ? "" : version_addendum)) != 0) { + oerrno = errno; +@@ -1657,6 +1692,14 @@ + r = SSH_ERR_INVALID_FORMAT; + goto out; + } ++ ++ /* report the version information to syslog if this is the server */ ++ if (timeout_ms == -1) { /* only the server uses this value */ ++ logit("SSH: Server;Ltype: Version;Remote: %s-%d;Protocol: %d.%d;Client: %.100s", ++ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), ++ remote_major, remote_minor, remote_version); ++ } ++ + debug("Remote protocol version %d.%d, remote software version %.100s", + remote_major, remote_minor, remote_version); + compat_banner(ssh, remote_version); +diff -Nur openssh-9.3p1.orig/mac.c openssh-9.3p1/mac.c +--- openssh-9.3p1.orig/mac.c 2024-07-10 09:07:09.735081860 +0200 ++++ openssh-9.3p1/mac.c 2024-07-10 09:09:55.908568426 +0200 +@@ -63,6 +63,7 @@ + { "hmac-sha2-512", SSH_DIGEST, SSH_DIGEST_SHA512, 0, 0, 0, 0 }, + { "hmac-md5", SSH_DIGEST, SSH_DIGEST_MD5, 0, 0, 0, 0 }, + { "hmac-md5-96", SSH_DIGEST, SSH_DIGEST_MD5, 96, 0, 0, 0 }, ++ { "none", SSH_DIGEST, SSH_DIGEST_NULL, 0, 0, 0, 0 }, + { "umac-64@openssh.com", SSH_UMAC, 0, 0, 128, 64, 0 }, + { "umac-128@openssh.com", SSH_UMAC128, 0, 0, 128, 128, 0 }, + +diff -Nur openssh-9.3p1.orig/Makefile.in openssh-9.3p1/Makefile.in +--- openssh-9.3p1.orig/Makefile.in 2024-07-10 09:07:09.806082068 +0200 ++++ openssh-9.3p1/Makefile.in 2024-07-10 09:09:55.909568429 +0200 +@@ -49,7 +49,7 @@ + CFLAGS_NOPIE=@CFLAGS_NOPIE@ + CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ + PICFLAG=@PICFLAG@ +-LIBS=@LIBS@ ++LIBS=@LIBS@ -lpthread + CHANNELLIBS=@CHANNELLIBS@ + K5LIBS=@K5LIBS@ + GSSLIBS=@GSSLIBS@ +@@ -97,7 +97,7 @@ + LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ + authfd.o authfile.o \ + canohost.o channels.o cipher.o cipher-aes.o cipher-aesctr.o \ +- cleanup.o \ ++ cleanup.o cipher-ctr-mt.o \ + compat.o fatal.o hostfile.o \ + log.o match.o moduli.o nchan.o packet.o \ + readpass.o ttymodes.o xmalloc.o addr.o addrmatch.o \ +@@ -114,7 +114,8 @@ + kexsntrup761x25519.o sntrup761.o kexgen.o \ + kexgssc.o \ + sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ +- sshbuf-io.o auditstub.o ++ sshbuf-io.o auditstub.o metrics.o binn.o cipher-ctr-mt-provider.o \ ++ cipher-ctr-mt-functions.o ossl3-provider-err.o num.o + + SKOBJS= ssh-sk-client.o + +@@ -214,7 +215,7 @@ + $(LD) -o $@ $(SSHDOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) $(GSSLIBS) $(K5LIBS) $(CHANNELLIBS) + + scp$(EXEEXT): $(LIBCOMPAT) libssh.a $(SCP_OBJS) +- $(LD) -o $@ $(SCP_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) ++ $(LD) -o $@ $(SCP_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(CHANNELLIBS) + + ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHADD_OBJS) + $(LD) -o $@ $(SSHADD_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(CHANNELLIBS) +diff -Nur openssh-9.3p1.orig/metrics.c openssh-9.3p1/metrics.c +--- openssh-9.3p1.orig/metrics.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/metrics.c 2024-07-10 09:09:55.909568429 +0200 +@@ -0,0 +1,426 @@ ++#include "includes.h" ++#include "metrics.h" ++#include "ssherr.h" ++#include ++#ifdef __linux__ ++#include ++#endif ++ ++#ifndef KERNEL_VERSION /* shouldn't be necessary to define this */ ++#define KERNEL_VERSION(a,b,c) (((a) <<16) + ((b) << 8) +(c)) ++#endif ++ ++/* add the information from the tcp_info struct to the ++ * serialized binary object ++ */ ++void ++metrics_write_binn_object(struct tcp_info *data, struct binn_struct *binnobj) { ++#if !defined TCP_INFO ++ /*tcp info isn't supported on this system */ ++ return; ++#else ++ ++/* the base set of tcpi_ measurements starting from kernel 3.7.0 ++ * these measurements existed in previous kernels but the oldest this ++ * is going to support is 3.7.0. We do need to store the kernel version as well */ ++ ++/* obvioulsy the version code macro only exists under linux so ++ * on non linux systems we set the kernel version to 0 ++ * which will get us the base set of metrics from netinet/tcp.h ++ */ ++#ifdef __linux__ ++ binn_object_set_uint32(binnobj, "kernel_version", LINUX_VERSION_CODE); ++#else ++ binn_object_set_uint32(binnobj, "kernel_version", 0); ++#endif ++ ++ /* the following are common under both linux and BSD */ ++ binn_object_set_uint8(binnobj, "tcpi_snd_wscale", ++ data->tcpi_snd_wscale); ++ binn_object_set_uint8(binnobj, "tcpi_rcv_wscale", ++ data->tcpi_rcv_wscale); ++ binn_object_set_uint8(binnobj, "tcpi_state", ++ data->tcpi_state); ++ binn_object_set_uint8(binnobj, "tcpi_options", ++ data->tcpi_options); ++ binn_object_set_uint32(binnobj, "tcpi_snd_ssthresh", ++ data->tcpi_snd_ssthresh); ++ binn_object_set_uint32(binnobj, "tcpi_rtt", ++ data->tcpi_rtt); ++ binn_object_set_uint32(binnobj, "tcpi_last_data_recv", ++ data->tcpi_last_data_recv); ++ binn_object_set_uint32(binnobj, "tcpi_rttvar", ++ data->tcpi_rttvar); ++ binn_object_set_uint32(binnobj, "tcpi_snd_cwnd", ++ data->tcpi_snd_cwnd); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_mss", ++ data->tcpi_rcv_mss); ++ binn_object_set_uint32(binnobj, "tcpi_rto", ++ data->tcpi_rto); ++ binn_object_set_uint32(binnobj, "tcpi_snd_mss", ++ data->tcpi_snd_mss); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_space", ++ data->tcpi_rcv_space); ++ ++/* the following exist under both but with different names */ ++#ifdef __linux__ ++ binn_object_set_uint8(binnobj, "tcpi_ca_state", ++ data->tcpi_ca_state); ++ binn_object_set_uint8(binnobj, "tcpi_probes", ++ data->tcpi_probes); ++ binn_object_set_uint8(binnobj, "tcpi_backoff", ++ data->tcpi_backoff); ++ binn_object_set_uint8(binnobj, "tcpi_retransmits", ++ data->tcpi_retransmits); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_ssthresh", ++ data->tcpi_rcv_ssthresh); ++ binn_object_set_uint32(binnobj, "tcpi_sacked", ++ data->tcpi_sacked); ++ binn_object_set_uint32(binnobj, "tcpi_pmtu", ++ data->tcpi_pmtu); ++ binn_object_set_uint32(binnobj, "tcpi_fackets", ++ data->tcpi_fackets); ++ binn_object_set_uint32(binnobj, "tcpi_lost", ++ data->tcpi_lost); ++ binn_object_set_uint32(binnobj, "tcpi_last_ack_sent", ++ data->tcpi_last_ack_sent); ++ binn_object_set_uint32(binnobj, "tcpi_unacked", ++ data->tcpi_unacked); ++ binn_object_set_uint32(binnobj, "tcpi_last_data_sent", ++ data->tcpi_last_data_sent); ++ binn_object_set_uint32(binnobj, "tcpi_last_ack_recv", ++ data->tcpi_last_ack_recv); ++ binn_object_set_uint32(binnobj, "tcpi_reordering", ++ data->tcpi_reordering); ++ binn_object_set_uint32(binnobj, "tcpi_advmss", ++ data->tcpi_advmss); ++ binn_object_set_uint32(binnobj, "tcpi_retrans", ++ data->tcpi_retrans); ++ binn_object_set_uint32(binnobj, "tcpi_ato", ++ data->tcpi_ato); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_rtt", ++ data->tcpi_rcv_rtt); ++#else ++ binn_object_set_uint8(binnobj, "tcpi_backoff", ++ data->__tcpi_backoff); ++ binn_object_set_uint8(binnobj, "tcpi_ca_state", ++ data->__tcpi_ca_state); ++ binn_object_set_uint8(binnobj, "tcpi_probes", ++ data->__tcpi_probes); ++ binn_object_set_uint8(binnobj, "tcpi_retransmits", ++ data->__tcpi_retransmits); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_ssthresh", ++ data->__tcpi_rcv_ssthresh); ++ binn_object_set_uint32(binnobj, "tcpi_sacked", ++ data->__tcpi_sacked); ++ binn_object_set_uint32(binnobj, "tcpi_pmtu", ++ data->__tcpi_pmtu); ++ binn_object_set_uint32(binnobj, "tcpi_fackets", ++ data->__tcpi_fackets); ++ binn_object_set_uint32(binnobj, "tcpi_lost", ++ data->__tcpi_lost); ++ binn_object_set_uint32(binnobj, "tcpi_last_ack_sent", ++ data->__tcpi_last_ack_sent); ++ binn_object_set_uint32(binnobj, "tcpi_unacked", ++ data->__tcpi_unacked); ++ binn_object_set_uint32(binnobj, "tcpi_last_data_sent", ++ data->__tcpi_last_data_sent); ++ binn_object_set_uint32(binnobj, "tcpi_last_ack_recv", ++ data->__tcpi_last_ack_recv); ++ binn_object_set_uint32(binnobj, "tcpi_reordering", ++ data->__tcpi_reordering); ++ binn_object_set_uint32(binnobj, "tcpi_advmss", ++ data->__tcpi_advmss); ++ binn_object_set_uint32(binnobj, "tcpi_retrans", ++ data->__tcpi_retrans); ++ binn_object_set_uint32(binnobj, "tcpi_ato", ++ data->__tcpi_ato); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_rtt", ++ data->__tcpi_rcv_rtt); ++#endif ++ ++/* Under BSD snd_rexmitpack is the same as linux total_retrans*/ ++#ifdef __linux__ ++ binn_object_set_uint32(binnobj, "tcpi_total_retrans", ++ data->tcpi_total_retrans); ++#else ++ binn_object_set_uint32(binnobj, "tcpi_total_retrans", ++ data->tcpi_snd_rexmitpack); ++#endif ++ ++/* The last section are for kernel specific metrics in linux */ ++#ifdef __linux__ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0) ++ binn_object_set_uint64(binnobj, "tcpi_max_pacing_rate", ++ data->tcpi_max_pacing_rate); ++ binn_object_set_uint64(binnobj, "tcpi_pacing_rate", ++ data->tcpi_pacing_rate); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0) ++ binn_object_set_uint64(binnobj, "tcpi_bytes_acked", ++ data->tcpi_bytes_acked); ++ binn_object_set_uint64(binnobj, "tcpi_bytes_received", ++ data->tcpi_bytes_received); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0) ++ binn_object_set_uint32(binnobj, "tcpi_segs_in", ++ data->tcpi_segs_in); ++ binn_object_set_uint32(binnobj, "tcpi_segs_out", ++ data->tcpi_segs_out); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0) ++ binn_object_set_uint32(binnobj, "tcpi_notsent_bytes", ++ data->tcpi_notsent_bytes); ++ binn_object_set_uint32(binnobj, "tcpi_min_rtt", ++ data->tcpi_min_rtt); ++ binn_object_set_uint32(binnobj, "tcpi_data_segs_in", ++ data->tcpi_data_segs_in); ++ binn_object_set_uint32(binnobj, "tcpi_data_segs_out", ++ data->tcpi_data_segs_out); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0) ++ binn_object_set_uint8(binnobj, "tcpi_delivery_rate_app_limited", ++ data->tcpi_delivery_rate_app_limited); ++ binn_object_set_uint64(binnobj, "tcpi_delivery_rate", ++ data->tcpi_delivery_rate); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) ++ binn_object_set_uint64(binnobj, "tcpi_busy_time", ++ data->tcpi_busy_time); ++ binn_object_set_uint64(binnobj, "tcpi_sndbuf_limited", ++ data->tcpi_sndbuf_limited); ++ binn_object_set_uint64(binnobj, "tcpi_rwnd_limited", ++ data->tcpi_rwnd_limited); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) ++ binn_object_set_uint32(binnobj, "tcpi_delivered", ++ data->tcpi_delivered); ++ binn_object_set_uint32(binnobj, "tcpi_delivered_ce", ++ data->tcpi_delivered_ce); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,19,0) ++ binn_object_set_uint64(binnobj, "tcpi_bytes_sent", ++ data->tcpi_bytes_sent); ++ binn_object_set_uint64(binnobj, "tcpi_bytes_retrans", ++ data->tcpi_bytes_retrans); ++ binn_object_set_uint32(binnobj, "tcpi_dsack_dups", ++ data->tcpi_dsack_dups); ++ binn_object_set_uint32(binnobj, "tcpi_reord_seen", ++ data->tcpi_reord_seen); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) ++ binn_object_set_uint32(binnobj, "tcpi_snd_wnd", ++ data->tcpi_snd_wnd); ++ binn_object_set_uint32(binnobj, "tcpi_rcv_ooopack", ++ data->tcpi_rcv_ooopack); ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,5,0) ++ binn_object_set_uint8(binnobj, "tcpi_fastopen_client_fail", ++ data->tcpi_fastopen_client_fail); ++#endif ++#endif /*endif for #ifdef __linux__ */ ++#endif /*endif for TCP_INFO */ ++} ++ ++/* this reads out the tcp_info binn object and formats it into a single line ++ * the object will not necessarily have all of the elements. If it's empty it ++ * current just spits out 0. This isn't optimal as 0 can also be a valid value */ ++void ++metrics_read_binn_object (void *binnobj, char **output) { ++ int len = 0; ++ int buflen = 1023; ++ int kernel_version = 0; ++ if (binnobj == NULL) { ++ fprintf(stderr, "Metric polling returned bad data.\n"); ++ return; ++ } ++ kernel_version = binn_object_uint32(binnobj, "kernel_version"); ++ ++ /* base set of metrics */ ++ len = snprintf(*output, buflen, "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", ++ binn_object_uint8(binnobj, "tcpi_state"), ++ binn_object_uint8(binnobj, "tcpi_ca_state"), ++ binn_object_uint8(binnobj, "tcpi_retransmits"), ++ binn_object_uint8(binnobj, "tcpi_probes"), ++ binn_object_uint8(binnobj, "tcpi_backoff"), ++ binn_object_uint8(binnobj, "tcpi_options"), ++ binn_object_uint8(binnobj, "tcpi_snd_wscale"), ++ binn_object_uint8(binnobj, "tcpi_rcv_wscale"), ++ binn_object_uint32(binnobj, "tcpi_rto"), ++ binn_object_uint32(binnobj, "tcpi_ato"), ++ binn_object_uint32(binnobj, "tcpi_snd_mss"), ++ binn_object_uint32(binnobj, "tcpi_rcv_mss"), ++ binn_object_uint32(binnobj, "tcpi_unacked"), ++ binn_object_uint32(binnobj, "tcpi_sacked"), ++ binn_object_uint32(binnobj, "tcpi_lost"), ++ binn_object_uint32(binnobj, "tcpi_retrans"), ++ binn_object_uint32(binnobj, "tcpi_fackets"), ++ binn_object_uint32(binnobj, "tcpi_last_data_sent"), ++ binn_object_uint32(binnobj, "tcpi_last_ack_sent"), ++ binn_object_uint32(binnobj, "tcpi_last_data_recv"), ++ binn_object_uint32(binnobj, "tcpi_last_ack_recv"), ++ binn_object_uint32(binnobj, "tcpi_pmtu"), ++ binn_object_uint32(binnobj, "tcpi_rcv_ssthresh"), ++ binn_object_uint32(binnobj, "tcpi_rtt"), ++ binn_object_uint32(binnobj, "tcpi_rttvar"), ++ binn_object_uint32(binnobj, "tcpi_snd_ssthresh"), ++ binn_object_uint32(binnobj, "tcpi_snd_cwnd"), ++ binn_object_uint32(binnobj, "tcpi_advmss"), ++ binn_object_uint32(binnobj, "tcpi_reordering"), ++ binn_object_uint32(binnobj, "tcpi_rcv_rtt"), ++ binn_object_uint32(binnobj, "tcpi_rcv_space"), ++ binn_object_uint32(binnobj, "tcpi_total_retrans") ++ ); ++ ++ /* compare the received kernel version to the version that supports ++ * any given metric. This means that a remote host that has a different ++ * kernel that the local host will be able to process the data in terms of ++ * the remote kernel version. Only necessary under linux*/ ++#ifdef __linux__ ++ if (kernel_version >= KERNEL_VERSION(3,15,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %llu, %llu", ++ binn_object_uint64(binnobj, "tcpi_max_pacing_rate"), ++ binn_object_uint64(binnobj, "tcpi_pacing_rate") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,1,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %llu, %llu", ++ binn_object_uint64(binnobj, "tcpi_bytes_acked"), ++ binn_object_uint64(binnobj, "tcpi_bytes_received") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,2,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d, %d", ++ binn_object_uint32(binnobj, "tcpi_segs_in"), ++ binn_object_uint32(binnobj, "tcpi_segs_out") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,6,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d, %d, %d, %d", ++ binn_object_uint32(binnobj, "tcpi_notsent_bytes"), ++ binn_object_uint32(binnobj, "tcpi_min_rtt"), ++ binn_object_uint32(binnobj, "tcpi_data_segs_in"), ++ binn_object_uint32(binnobj, "tcpi_data_segs_out") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,9,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d, %llu", ++ binn_object_uint8(binnobj, "tcpi_delivery_rate_app_limited"), ++ binn_object_uint64(binnobj, "tcpi_delivery_rate") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,10,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %llu, %llu, %llu", ++ binn_object_uint64(binnobj, "tcpi_busy_time"), ++ binn_object_uint64(binnobj, "tcpi_sndbuf_limited"), ++ binn_object_uint64(binnobj, "tcpi_rwnd_limited") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,18,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d, %d", ++ binn_object_uint32(binnobj, "tcpi_delivered"), ++ binn_object_uint32(binnobj, "tcpi_delivered_ce") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,19,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %llu, %llu, %d, %d", ++ binn_object_uint64(binnobj, "tcpi_bytes_sent"), ++ binn_object_uint64(binnobj, "tcpi_bytes_retrans"), ++ binn_object_uint32(binnobj, "tcpi_dsack_dups"), ++ binn_object_uint32(binnobj, "tcpi_reord_seen") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(5,4,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d, %d", ++ binn_object_uint32(binnobj, "tcpi_snd_wnd"), ++ binn_object_uint32(binnobj, "tcpi_rcv_ooopack") ++ ); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(5,5,0)) { ++ len += snprintf(*output+len, (buflen-len), ", %d", ++ binn_object_uint8(binnobj, "tcpi_fastopen_client_fail") ++ ); ++ } ++#endif /* ifdef __linux__ */ ++} ++ ++/* Print out the header to the file so that the column header matches the ++ * data in the metrics object. That varies by kernel version so we have to ++ * do tests for each possible version where they added new values to tcp_info ++ * NOTE: This doesn't put the values/headers in the most useful order but the ++ * resulting file is probably going to get processed by something */ ++void ++metrics_print_header(FILE *fptr, char *extra_text, int kernel_version) { ++ if (extra_text != NULL) { ++ fprintf(fptr, "%s\n", extra_text); ++ } ++ fprintf(fptr, "timestamp, state, ca_state, retransmits, probes, backoff, options, "); ++ fprintf(fptr, "snd_wscale, rcv_wscale, rto, ato, snd_mss, rcv_mss, unacked, sacked, lost, retrans, "); ++ fprintf(fptr, "fackets, last_data_sent, last_ack_sent, last_data_recv, "); ++ fprintf(fptr, "last_ack_recv, pmtu, rcv_ssthresh, rtt, rttvar, snd_ssthresh, "); ++ fprintf(fptr, "snd_cwnd, advmss, reordering, rcv_rtt, rcv_space, total_retrans"); ++ ++ /* compare the received kernel version to the version that supports ++ * any given metric. This way we can print consistent headers. ++ * Only necessary under linux*/ ++#ifdef __linux__ ++ if (kernel_version >= KERNEL_VERSION(3,15,0)) { ++ fprintf(fptr, ", max_pacing_rate, pacing_rate"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,1,0)) { ++ fprintf(fptr, ", bytes_acked, bytes_received"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,2,0)) { ++ fprintf(fptr, ", segs_in, segs_out"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,6,0)) { ++ fprintf(fptr, ", notsent_bytes, min_rtt, data_segs_in, data_seg_out"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,9,0)) { ++ fprintf(fptr, ", delivery_rate_app_limited, delivery_rate"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,10,0)) { ++ fprintf(fptr, ", busy_time, sndbuf_limited, rwnd_limited"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,18,0)) { ++ fprintf(fptr, ", delivered, delivered_ce"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(4,19,0)) { ++ fprintf(fptr, ", bytes_sent, bytes_retrans, dsack_dups, reord_seen"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(5,4,0)) { ++ fprintf(fptr, ", snd_wnd, rcv_ooopack"); ++ } ++ ++ if (kernel_version >= KERNEL_VERSION(5,5,0)) { ++ fprintf(fptr, ", fastopen_client_fail"); ++ } ++#endif /* ifdef __linux__ */ ++ fprintf(fptr, "\n\n"); ++} +diff -Nur openssh-9.3p1.orig/metrics.h openssh-9.3p1/metrics.h +--- openssh-9.3p1.orig/metrics.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/metrics.h 2024-07-10 09:09:55.909568429 +0200 +@@ -0,0 +1,28 @@ ++#ifndef METRICS_H ++#define METRICS_H ++ ++#include "binn.h" ++ ++/* linux, freebsd, and netbsd have tcp_info structs. ++ * I don't know about other systems so we disable this ++ * functionality for them */ ++#if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ ++#define TCP_INFO 1 ++#if defined __linux__ ++#include ++#else ++#include ++#endif ++#else ++/* make a null struct for tcp_info on systems that don't support it*/ ++typedef struct tcp_info { ++ void *dummy; ++} tcp_info; ++#endif ++ ++void metrics_write_binn_object(struct tcp_info *, struct binn_struct *); ++void metrics_read_binn_object(void *, char **); ++void metrics_print_header(FILE *, char *, int); ++ ++ ++#endif /* define metrics_h */ +diff -Nur openssh-9.3p1.orig/misc.c openssh-9.3p1/misc.c +--- openssh-9.3p1.orig/misc.c 2024-07-10 09:07:09.806082068 +0200 ++++ openssh-9.3p1/misc.c 2024-07-10 09:09:55.910568431 +0200 +@@ -70,6 +70,29 @@ + #include "ssherr.h" + #include "platform.h" + ++void ++read_mem_stats(statm_t *result, int post_auth) ++{ ++ if (!post_auth) ++ return; ++ ++ const char* statm_path = "/proc/self/statm"; ++ ++ FILE *f = fopen(statm_path,"r"); ++ if(!f){ ++ perror(statm_path); ++ abort(); ++ } ++ if(7 != fscanf(f,"%lu %lu %lu %lu %lu %lu %lu", ++ &result->size, &result->resident, &result->share, &result->text, &result->lib, ++ &result->data, &result->dt)) ++ { ++ perror(statm_path); ++ abort(); ++ } ++ fclose(f); ++} ++ + /* remove newline at end of string */ + char * + chop(char *s) +@@ -1606,20 +1629,6 @@ + return (v); + } + +-u_int32_t +-get_u32_le(const void *vp) +-{ +- const u_char *p = (const u_char *)vp; +- u_int32_t v; +- +- v = (u_int32_t)p[0]; +- v |= (u_int32_t)p[1] << 8; +- v |= (u_int32_t)p[2] << 16; +- v |= (u_int32_t)p[3] << 24; +- +- return (v); +-} +- + u_int16_t + get_u16(const void *vp) + { +@@ -1659,17 +1668,6 @@ + } + + void +-put_u32_le(void *vp, u_int32_t v) +-{ +- u_char *p = (u_char *)vp; +- +- p[0] = (u_char)v & 0xff; +- p[1] = (u_char)(v >> 8) & 0xff; +- p[2] = (u_char)(v >> 16) & 0xff; +- p[3] = (u_char)(v >> 24) & 0xff; +-} +- +-void + put_u16(void *vp, u_int16_t v) + { + u_char *p = (u_char *)vp; +diff -Nur openssh-9.3p1.orig/misc.h openssh-9.3p1/misc.h +--- openssh-9.3p1.orig/misc.h 2024-07-10 09:07:09.806082068 +0200 ++++ openssh-9.3p1/misc.h 2024-07-10 09:09:55.910568431 +0200 +@@ -155,12 +155,6 @@ + void put_u16(void *, u_int16_t) + __attribute__((__bounded__( __minbytes__, 1, 2))); + +-/* Little-endian store/load, used by umac.c */ +-u_int32_t get_u32_le(const void *) +- __attribute__((__bounded__(__minbytes__, 1, 4))); +-void put_u32_le(void *, u_int32_t) +- __attribute__((__bounded__(__minbytes__, 1, 4))); +- + struct bwlimit { + size_t buflen; + u_int64_t rate; /* desired rate in kbit/s */ +@@ -244,4 +238,16 @@ + /* On OpenBSD time_t is int64_t which is long long. */ + /* #define SSH_TIME_T_MAX LLONG_MAX */ + ++typedef struct statm_t { ++ unsigned long size; ++ unsigned long resident; ++ unsigned long share; ++ unsigned long text; ++ unsigned long lib; ++ unsigned long data; ++ unsigned long dt; ++} statm_t; ++ ++void read_mem_stats(struct statm_t *, int); ++ + #endif /* _MISC_H */ +diff -Nur openssh-9.3p1.orig/num.c openssh-9.3p1/num.c +--- openssh-9.3p1.orig/num.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/num.c 2024-07-10 09:09:55.910568431 +0200 +@@ -0,0 +1,158 @@ ++/* CC0 license applied, see LICENCE.md */ ++ ++#include ++#include "num.h" ++ ++#ifdef WITH_OPENSSL ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ ++typedef enum { BIG = 1, LITTLE = -1 } endian_t; ++typedef enum { NEGATIVE = 0xff, POSITIVE = 0x00 } sign_t; ++ ++static endian_t nativeendian(void) ++{ ++ const int endiantest = 1; ++ ++ return *((char *)&endiantest) == 1 ? LITTLE : BIG; ++} ++ ++static sign_t paramsign(const OSSL_PARAM *param) ++{ ++ size_t srcmsb = nativeendian() == BIG ? 0 : param->data_size - 1; ++ ++ return ++ param->data_type == OSSL_PARAM_UNSIGNED_INTEGER ++ ? POSITIVE ++ : (((unsigned char *)param->data)[srcmsb] & 0x80 ++ ? NEGATIVE ++ : POSITIVE); ++} ++ ++struct numdesc { ++ void *data; ++ unsigned int data_type; /* The OSSL_PARAM data type */ ++ ++ /* These fields concern the whole number */ ++ size_t size; ++ endian_t endian; ++ sign_t sign; ++ ++ /* These fields concern the limbs of the number */ ++ size_t limbsize; ++ endian_t limbendian; ++ /* This is for odd archs. */ ++ /* see the manual for mpz_import() for an in depth explanation. */ ++ size_t limbnailbits; ++}; ++ ++struct resultdesc { ++ size_t size; ++ int result; /* 1 or (negative) error */ ++}; ++ ++static struct resultdesc provnum_copy(struct numdesc dest, struct numdesc src) ++{ ++ struct resultdesc result = { dest.size, 1, }; ++ ++ if (src.data_type != OSSL_PARAM_INTEGER ++ && src.data_type != OSSL_PARAM_UNSIGNED_INTEGER) { ++ result.result = PROVNUM_E_WRONG_TYPE; ++ return result; ++ } ++ ++ if (src.size == 0) { ++ memset(dest.data, 0, dest.size); ++ return result; ++ } ++ ++ /* Extra data */ ++ size_t srcmsb = src.endian == BIG ? 0 : src.size - 1; ++ int srcmsb2lsb = src.endian == BIG ? 1 : -1; ++ ++ /* ++ * If the source is bigger than the destination, analyse to see if the ++ * most significant byte is just padding that can be ignored. ++ * The rules to determine if the most significant byte is just padding ++ * are: ++ * ++ * 1. the most significant byte equals srcsigned, which just so happens ++ * to have the 2's complement padding value. ++ * 2. The most significant bit of the next to most significant byte ++ * equals the most significant bit of srcsigned. ++ */ ++ size_t end = dest.data == NULL ? 1 : dest.size; ++ for (; src.size > end; srcmsb += srcmsb2lsb, src.size--) ++ if (((unsigned char *)src.data)[srcmsb] != src.sign ++ || ((((unsigned char *)src.data)[srcmsb + srcmsb2lsb] & 0x80) ++ != (src.sign & 0x80))) ++ break; ++ ++ if (src.size > dest.size) { ++ result.result = PROVNUM_E_TOOBIG; ++ return result; ++ } ++ ++ size_t srclsb = srcmsb + srcmsb2lsb * (src.size - 1); ++ ++ /* Simple case, all significant details match */ ++ if (dest.endian == src.endian ++ && dest.limbsize == 1 ++ && dest.limbnailbits == 0 ++ && (dest.data_type == OSSL_PARAM_INTEGER || src.sign == POSITIVE)) { ++ ++ if (src.size < dest.size) { ++ size_t padstart = dest.endian == BIG ? 0 : dest.size - src.size; ++ ++ memset((unsigned char *)dest.data + padstart, src.sign, ++ dest.size - src.size); ++ } ++ ++ size_t deststart = dest.endian == BIG ? dest.size - src.size : 0; ++ size_t srcstart = src.endian == BIG ? srcmsb : srclsb; ++ ++ memcpy((unsigned char *)dest.data + deststart, ++ (unsigned char *)src.data + srcstart, ++ src.size); ++ return result; ++ } ++ ++ /* Complex case, for sign or limb conversion. Currently unsupported */ ++ result.result = PROVNUM_E_UNSUPPORTED; ++ return result; ++} ++ ++#define implement_provnum(T, DT) \ ++ int provnum_get_##T(T *dest, const OSSL_PARAM *param) \ ++ { \ ++ endian_t endian = nativeendian(); \ ++ struct numdesc destnd = { \ ++ dest, DT, sizeof(T), endian, POSITIVE, 1, endian, 0 \ ++ }; \ ++ struct numdesc srcnd = { \ ++ param->data, param->data_type, param->data_size, \ ++ endian, paramsign(param), 1, endian, 0 \ ++ }; \ ++ \ ++ struct resultdesc result = provnum_copy(destnd, srcnd); \ ++ return result.result; \ ++ } \ ++ int provnum_set_##T(OSSL_PARAM *param, T src) \ ++ { \ ++ endian_t endian = nativeendian(); \ ++ struct numdesc destnd = { \ ++ param->data, param->data_type, param->data_size, \ ++ endian, POSITIVE, 1, endian, 0 \ ++ }; \ ++ struct numdesc srcnd = { \ ++ &src, DT, sizeof(T), endian, POSITIVE, 1, endian, 0 \ ++ }; \ ++ \ ++ struct resultdesc result = provnum_copy(destnd, srcnd); \ ++ param->return_size = result.size; \ ++ return result.result; \ ++ } ++ ++implement_provnum(size_t, OSSL_PARAM_UNSIGNED_INTEGER) ++ ++#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000UL */ ++#endif /* WITH_OPENSSL */ +diff -Nur openssh-9.3p1.orig/num.h openssh-9.3p1/num.h +--- openssh-9.3p1.orig/num.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/num.h 2024-07-10 09:09:55.910568431 +0200 +@@ -0,0 +1,17 @@ ++/* CC0 license applied, see LICENCE.md */ ++ ++#include "includes.h" ++#ifdef WITH_OPENSSL ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ ++#include ++ ++/* Convert between OSSL_PARAM and size_t */ ++int provnum_get_size_t(size_t *dest, const OSSL_PARAM *param); ++int provnum_set_size_t(OSSL_PARAM *param, size_t src); ++ ++#define PROVNUM_E_WRONG_TYPE -1 ++#define PROVNUM_E_TOOBIG -2 ++#define PROVNUM_E_UNSUPPORTED -3 ++#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000UL */ ++#endif /* WITH_OPENSSL */ +diff -Nur openssh-9.3p1.orig/ossl3-provider-err.c openssh-9.3p1/ossl3-provider-err.c +--- openssh-9.3p1.orig/ossl3-provider-err.c 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/ossl3-provider-err.c 2024-07-10 09:09:55.910568431 +0200 +@@ -0,0 +1,110 @@ ++/* CC0 license applied, see LICENCE.md */ ++ ++#include ++#include ++#include "ossl3-provider-err.h" ++ ++#ifdef WITH_OPENSSL ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++ ++struct proverr_functions_st { ++ const OSSL_CORE_HANDLE *core; ++ OSSL_FUNC_core_new_error_fn *core_new_error; ++ OSSL_FUNC_core_set_error_debug_fn *core_set_error_debug; ++ OSSL_FUNC_core_vset_error_fn *core_vset_error; ++}; ++ ++struct proverr_functions_st * ++proverr_new_handle(const OSSL_CORE_HANDLE *core, const OSSL_DISPATCH *dispatch) ++{ ++ /* ++ * libcrypto gives providers the tools to create error routines similar ++ * to the ones defined in ++ */ ++ OSSL_FUNC_core_new_error_fn *c_new_error = NULL; ++ OSSL_FUNC_core_set_error_debug_fn *c_set_error_debug = NULL; ++ OSSL_FUNC_core_vset_error_fn *c_vset_error = NULL; ++ struct proverr_functions_st *handle = NULL; ++ ++ assert(core != NULL); ++ assert(dispatch != NULL); ++ ++#ifndef DEBUG ++ if (core == NULL || dispatch == NULL) ++ return NULL; ++#endif ++ ++ for (; dispatch->function_id != 0; dispatch++) ++ switch (dispatch->function_id) { ++ case OSSL_FUNC_CORE_NEW_ERROR: ++ c_new_error = OSSL_FUNC_core_new_error(dispatch); ++ break; ++ case OSSL_FUNC_CORE_SET_ERROR_DEBUG: ++ c_set_error_debug = OSSL_FUNC_core_set_error_debug(dispatch); ++ break; ++ case OSSL_FUNC_CORE_VSET_ERROR: ++ c_vset_error = OSSL_FUNC_core_vset_error(dispatch); ++ break; ++ } ++ ++ assert(c_new_error != NULL); ++ assert(c_set_error_debug != NULL); ++ assert(c_vset_error != NULL); ++ ++#ifdef NDEBUG ++ if (c_new_error == NULL || c_set_error_debug == NULL || c_vset_error == NULL) ++ return NULL; ++#endif ++ ++ if ((handle = malloc(sizeof(*handle))) != NULL) { ++ handle->core = core; ++ handle->core_new_error = c_new_error; ++ handle->core_set_error_debug = c_set_error_debug; ++ handle->core_vset_error = c_vset_error; ++ } ++ return handle; ++} ++ ++struct proverr_functions_st * ++proverr_dup_handle(struct proverr_functions_st *src) ++{ ++ struct proverr_functions_st *dst = NULL; ++ ++ if (src != NULL ++ && (dst = malloc(sizeof(*dst))) != NULL) { ++ dst->core = src->core; ++ dst->core_new_error = src->core_new_error; ++ dst->core_set_error_debug = src->core_set_error_debug; ++ dst->core_vset_error = src->core_vset_error; ++ } ++ return dst; ++} ++ ++void proverr_free_handle(struct proverr_functions_st *handle) ++{ ++ free(handle); ++} ++ ++void proverr_new_error(const struct proverr_functions_st *handle) ++{ ++ handle->core_new_error(handle->core); ++} ++ ++void proverr_set_error_debug(const struct proverr_functions_st *handle, ++ const char *file, int line, const char *func) ++{ ++ handle->core_set_error_debug(handle->core, file, line, func); ++} ++ ++void proverr_set_error(const struct proverr_functions_st *handle, ++ uint32_t reason, const char *fmt, ...) ++{ ++ va_list ap; ++ ++ va_start(ap, fmt); ++ handle->core_vset_error(handle->core, reason, fmt, ap); ++ va_end(ap); ++} ++ ++#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000UL */ ++#endif /* WITH_OPENSSL */ +diff -Nur openssh-9.3p1.orig/ossl3-provider-err.h openssh-9.3p1/ossl3-provider-err.h +--- openssh-9.3p1.orig/ossl3-provider-err.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/ossl3-provider-err.h 2024-07-10 09:09:55.911568435 +0200 +@@ -0,0 +1,75 @@ ++/* CC0 license applied, see LICENCE.md */ ++ ++#include "includes.h" ++#ifdef WITH_OPENSSL ++#if OPENSSL_VERSION_NUMBER >= 0x30000000UL ++#include ++#include ++#include ++ ++/* ++ * The idea with this library is to replace OpenSSL's ERR_raise() and ++ * ERR_raise_data() with variants that are more suitable for providers that ++ * have their own error reason table. ++ * ++ * This assumes variadic function-like macros, i.e. C99 or newer. ++ * ++ * A minimal amount of preparation is needed on provider initialization and ++ * takedown: ++ * ++ * 1. The provider's outgoing OSSL_DISPATCH table must include an entry ++ * got OSSL_FUNC_PROVIDER_GET_REASON_STRINGS, with a function that returns ++ * the provider's table of reasons. ++ * That table of reasons is a simple OSSL_ITEM array, where each element ++ * contains a numeric reason identity for the reason, and the description ++ * text string for that reason. ++ * Each numeric reason identity MUST be unique within this array. ++ * ++ * 2. On provider initialization, an error handle must be created using ++ * proverr_new_handle(). The returned pointer is passed as first argument ++ * to ERR_raise() and ERR_raise_data(). ++ * ++ * 3. On provider takedown, the error handle must be freed, using ++ * proverr_free_handle(). ++ * ++ * With this preparation, the provider code can use ERR_raise() and ++ * ERR_raise_data() "as usual", with the exception that the first argument is ++ * the error handle instead of one of the OpenSSL ERR_LIB_ macros. ++ */ ++ ++/* ++ * In case was included, we throw away its error recording ++ * macros. ++ * Note that ERR_put_error() is NOT recreated. It's deprecated and should not ++ * be used any more. ++ */ ++#undef ERR_put_error ++#undef ERR_raise ++#undef ERR_raise_data ++ ++#define ERR_raise(handle, reason) ERR_raise_data((handle),(reason),NULL) ++ ++#define ERR_raise_data(handle, reason, ...) \ ++ (proverr_new_error(handle), \ ++ proverr_set_error_debug(handle, OPENSSL_FILE,OPENSSL_LINE,OPENSSL_FUNC), \ ++ proverr_set_error(handle, reason, __VA_ARGS__)) ++ ++/* ++ * The structure where the libcrypto core handle and core functions are ++ * captured. ++ */ ++struct proverr_functions_st; ++ ++struct proverr_functions_st * ++proverr_new_handle(const OSSL_CORE_HANDLE *core, const OSSL_DISPATCH *in); ++struct proverr_functions_st * ++proverr_dup_handle(struct proverr_functions_st *src); ++void proverr_free_handle(struct proverr_functions_st *handle); ++ ++void proverr_new_error(const struct proverr_functions_st *handle); ++void proverr_set_error_debug(const struct proverr_functions_st *handle, ++ const char *file, int line, const char *func); ++void proverr_set_error(const struct proverr_functions_st *handle, ++ uint32_t reason, const char *fmt, ...); ++#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000UL */ ++#endif /* WITH_OPENSSL */ +diff -Nur openssh-9.3p1.orig/packet.c openssh-9.3p1/packet.c +--- openssh-9.3p1.orig/packet.c 2024-07-10 09:07:09.781081995 +0200 ++++ openssh-9.3p1/packet.c 2024-07-10 15:18:33.232847035 +0200 +@@ -63,6 +63,9 @@ + #endif + #include + #include ++#ifdef HAVE_UTIL_H ++# include ++#endif + + /* + * Explicitly include OpenSSL before zlib as some versions of OpenSSL have +@@ -104,7 +107,20 @@ + #define DBG(x) + #endif + +-#define PACKET_MAX_SIZE (256 * 1024) ++/* OpenSSH usings 256KB packet size max but that consumes a ++ * lot of memory with the buffers we are using. However, we need ++ * a large packet size if the banner that's being sent is large. ++ * So we need a 256KB packet pre authentication and a smaller one ++ * in this case SSH_IOBUFSZ + 1KB, afterwards. So we change ++ * PACKET_MAX_SIZE from a #define to a global. Then, in the function ++ * ssh_packet_set_authentcated we reduce the size to something ++ * more memory efficient. -cjr 04/07/23 ++ */ ++u_int packet_max_size = 256 * 1024; ++ ++/* global to support forced rekeying */ ++int rekey_requested = 0; ++ + + struct packet_state { + u_int32_t seqnr; +@@ -241,12 +257,19 @@ + (state->outgoing_packet = sshbuf_new()) == NULL || + (state->incoming_packet = sshbuf_new()) == NULL) + goto fail; ++ /* these buffers are important in terms of tracking buffer usage ++ * so we explicitly label them with descriptive names */ ++ sshbuf_relabel(state->input, "input"); ++ sshbuf_relabel(state->incoming_packet, "inpacket"); ++ sshbuf_relabel(state->output, "output"); ++ sshbuf_relabel(state->outgoing_packet, "outpacket"); ++ + TAILQ_INIT(&state->outgoing); + TAILQ_INIT(&ssh->private_keys); + TAILQ_INIT(&ssh->public_keys); + state->connection_in = -1; + state->connection_out = -1; +- state->max_packet_size = 32768; ++ state->max_packet_size = CHAN_SES_PACKET_DEFAULT; + state->packet_timeout_ms = -1; + state->p_send.packets = state->p_read.packets = 0; + state->initialized = 1; +@@ -294,7 +317,7 @@ + ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) + { + struct session_state *state; +- const struct sshcipher *none = cipher_by_name("none"); ++ struct sshcipher *none = cipher_by_name("none"); + int r; + + if (none == NULL) { +@@ -311,9 +334,11 @@ + state->connection_in = fd_in; + state->connection_out = fd_out; + if ((r = cipher_init(&state->send_context, none, +- (const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT)) != 0 || ++ (const u_char *)"", 0, NULL, 0, ++ CIPHER_ENCRYPT, state->after_authentication)) != 0 || + (r = cipher_init(&state->receive_context, none, +- (const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT)) != 0) { ++ (const u_char *)"", 0, NULL, 0, ++ CIPHER_DECRYPT, state->after_authentication)) != 0) { + error_fr(r, "cipher_init failed"); + free(ssh); /* XXX need ssh_free_session_state? */ + return NULL; +@@ -384,7 +409,7 @@ + + if (state->packet_discard_mac) { + char buf[1024]; +- size_t dlen = PACKET_MAX_SIZE; ++ size_t dlen = packet_max_size; + + if (dlen > state->packet_discard_mac_already) + dlen -= state->packet_discard_mac_already; +@@ -885,6 +910,7 @@ + const char *wmsg; + int r, crypt_type; + const char *dir = mode == MODE_OUT ? "out" : "in"; ++ char blocks_s[FMT_SCALED_STRSIZE], bytes_s[FMT_SCALED_STRSIZE]; + + debug2_f("mode %d", mode); + +@@ -928,7 +954,8 @@ + cipher_free(*ccp); + *ccp = NULL; + if ((r = cipher_init(ccp, enc->cipher, enc->key, enc->key_len, +- enc->iv, enc->iv_len, crypt_type)) != 0) ++ enc->iv, enc->iv_len, crypt_type, ++ state->after_authentication)) != 0) + return r; + if (!state->cipher_warning_done && + (wmsg = cipher_warning_message(*ccp)) != NULL) { +@@ -953,23 +980,45 @@ + } + comp->enabled = 1; + } +- /* +- * The 2^(blocksize*2) limit is too expensive for 3DES, +- * so enforce a 1GB limit for small blocksizes. +- * See RFC4344 section 3.2. +- */ +- if (enc->block_size >= 16) +- *max_blocks = (u_int64_t)1 << (enc->block_size*2); +- else +- *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; +- if (state->rekey_limit) +- *max_blocks = MINIMUM(*max_blocks, +- state->rekey_limit / enc->block_size); +- debug("rekey %s after %llu blocks", dir, +- (unsigned long long)*max_blocks); ++ ++ /* get the maximum number of blocks the cipher can ++ * handle safely */ ++ *max_blocks = cipher_rekey_blocks(enc->cipher); ++ ++ /* if we have a custom oRekeyLimit use that. */ ++ if (state->rekey_limit) ++ *max_blocks = MINIMUM(*max_blocks, ++ state->rekey_limit / enc->block_size); ++ ++ /* these lines support the debug */ ++ strlcpy(blocks_s, "?", sizeof(blocks_s)); ++ strlcpy(bytes_s, "?", sizeof(bytes_s)); ++ if (*max_blocks * enc->block_size < LLONG_MAX) { ++ fmt_scaled((long long)*max_blocks, blocks_s); ++ fmt_scaled((long long)*max_blocks * enc->block_size, bytes_s); ++ } ++ debug("rekey %s after %s blocks / %sB data", dir, blocks_s, bytes_s); ++ + return 0; + } + ++/* this supports the forced rekeying required for the NONE cipher */ ++void ++packet_request_rekeying(void) ++{ ++ rekey_requested = 1; ++} ++ ++/* used to determine if pre or post auth when rekeying for aes-ctr ++ * and none cipher switch */ ++int ++packet_authentication_state(const struct ssh *ssh) ++{ ++ struct session_state *state = ssh->state; ++ ++ return state->after_authentication; ++} ++ + #define MAX_PACKETS (1U<<31) + static int + ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) +@@ -996,6 +1045,14 @@ + if (state->p_send.packets == 0 && state->p_read.packets == 0) + return 0; + ++ /* used to force rekeying when called for by the none ++ * cipher switch and aes-mt-ctr methods -cjr */ ++ if (rekey_requested == 1) { ++ debug_f("Got the rekey request"); ++ rekey_requested = 0; ++ return 1; ++ } ++ + /* Time-based rekeying */ + if (state->rekey_interval != 0 && + (int64_t)state->rekey_time + state->rekey_interval <= monotime()) +@@ -1352,7 +1409,7 @@ + struct session_state *state = ssh->state; + int len, r, ms_remain = 0; + struct pollfd pfd; +- char buf[8192]; ++ char buf[SSH_IOBUFSZ]; + struct timeval start; + struct timespec timespec, *timespecp = NULL; + +@@ -1479,7 +1536,7 @@ + return 0; /* packet is incomplete */ + state->packlen = PEEK_U32(cp); + if (state->packlen < 4 + 1 || +- state->packlen > PACKET_MAX_SIZE) ++ state->packlen > packet_max_size) + return SSH_ERR_MESSAGE_INCOMPLETE; + } + need = state->packlen + 4; +@@ -1538,7 +1595,7 @@ + sshbuf_ptr(state->input), sshbuf_len(state->input)) != 0) + return 0; + if (state->packlen < 1 + 4 || +- state->packlen > PACKET_MAX_SIZE) { ++ state->packlen > packet_max_size) { + #ifdef PACKET_DEBUG + sshbuf_dump(state->input, stderr); + #endif +@@ -1565,7 +1622,7 @@ + goto out; + state->packlen = PEEK_U32(sshbuf_ptr(state->incoming_packet)); + if (state->packlen < 1 + 4 || +- state->packlen > PACKET_MAX_SIZE) { ++ state->packlen > packet_max_size) { + #ifdef PACKET_DEBUG + fprintf(stderr, "input: \n"); + sshbuf_dump(state->input, stderr); +@@ -1574,7 +1631,7 @@ + #endif + logit("Bad packet length %u.", state->packlen); + return ssh_packet_start_discard(ssh, enc, mac, 0, +- PACKET_MAX_SIZE); ++ packet_max_size); + } + if ((r = sshbuf_consume(state->input, block_size)) != 0) + goto out; +@@ -1597,7 +1654,7 @@ + logit("padding error: need %d block %d mod %d", + need, block_size, need % block_size); + return ssh_packet_start_discard(ssh, enc, mac, 0, +- PACKET_MAX_SIZE - block_size); ++ packet_max_size - block_size); + } + /* + * check if the entire packet has been received and +@@ -1641,11 +1698,11 @@ + if (r != SSH_ERR_MAC_INVALID) + goto out; + logit("Corrupted MAC on input."); +- if (need + block_size > PACKET_MAX_SIZE) ++ if (need + block_size > packet_max_size) + return SSH_ERR_INTERNAL_ERROR; + return ssh_packet_start_discard(ssh, enc, mac, + sshbuf_len(state->incoming_packet), +- PACKET_MAX_SIZE - need - block_size); ++ packet_max_size - need - block_size); + } + /* Remove MAC from input buffer */ + DBG(debug("MAC #%d ok", state->p_read.seqnr)); +@@ -1843,7 +1900,7 @@ + int r; + size_t rlen; + +- if ((r = sshbuf_read(fd, state->input, PACKET_MAX_SIZE, &rlen)) != 0) ++ if ((r = sshbuf_read(fd, state->input, packet_max_size, &rlen)) != 0) + return r; + + if (state->packet_discard) { +@@ -1922,17 +1979,21 @@ + switch (r) { + case SSH_ERR_CONN_CLOSED: + ssh_packet_clear_keys(ssh); ++ sshpkt_final_log_entry(ssh); + logdie("Connection closed by %s", remote_id); + case SSH_ERR_CONN_TIMEOUT: + ssh_packet_clear_keys(ssh); ++ sshpkt_final_log_entry(ssh); + logdie("Connection %s %s timed out", + ssh->state->server_side ? "from" : "to", remote_id); + case SSH_ERR_DISCONNECTED: + ssh_packet_clear_keys(ssh); ++ sshpkt_final_log_entry(ssh); + logdie("Disconnected from %s", remote_id); + case SSH_ERR_SYSTEM_ERROR: + if (errno == ECONNRESET) { + ssh_packet_clear_keys(ssh); ++ sshpkt_final_log_entry(ssh); + logdie("Connection reset by %s", remote_id); + } + /* FALLTHROUGH */ +@@ -1974,6 +2035,24 @@ + logdie_f("should have exited"); + } + ++/* this prints out the final log entry */ ++void ++sshpkt_final_log_entry (struct ssh *ssh) { ++ double total_time; ++ ++ if (ssh->start_time < 1) ++ /* this will produce a NaN in the output. -cjr */ ++ total_time = 0; ++ else ++ total_time = monotime_double() - ssh->start_time; ++ ++ logit("SSH: Server;LType: Throughput;Remote: %s-%d;IN: %lu;OUT: %lu;Duration: %.1f;tPut_in: %.1f;tPut_out: %.1f", ++ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), ++ ssh->stdin_bytes, ssh->fdout_bytes, total_time, ++ ssh->stdin_bytes / total_time, ++ ssh->fdout_bytes / total_time); ++} ++ + /* + * Logs the error plus constructs and sends a disconnect packet, closes the + * connection, and exits. This function never returns. The error message +@@ -2216,10 +2295,19 @@ + ssh->kex->server = 1; /* XXX unify? */ + } + ++/* Set the state of the connection to post auth ++ * While we are here also decrease the size of ++ * packet_max_size to something more reasonable. ++ * In this case thats 33k. Which is the size of ++ * the largest packet we expect to see and some space ++ * for overhead. This reduces memory usage in high ++ * BDP environments without impacting performance ++ * -cjr 4/11/23 */ + void + ssh_packet_set_authenticated(struct ssh *ssh) + { + ssh->state->after_authentication = 1; ++ packet_max_size = SSH_IOBUFSZ + 1024; + } + + void * +@@ -2837,3 +2925,10 @@ + ssh->state->extra_pad = pad; + return 0; + } ++ ++/* need this for the moment for the aes-ctr cipher */ ++void * ++ssh_packet_get_send_context(struct ssh *ssh) ++{ ++ return ssh->state->send_context; ++} +diff -Nur openssh-9.3p1.orig/packet.h openssh-9.3p1/packet.h +--- openssh-9.3p1.orig/packet.h 2024-07-10 09:07:09.738081869 +0200 ++++ openssh-9.3p1/packet.h 2024-07-10 09:09:55.912568437 +0200 +@@ -86,6 +86,17 @@ + + /* APP data */ + void *app_data; ++ ++ /* logging data for ServerLogging patch*/ ++ double start_time; ++ u_long fdout_bytes; ++ u_long stdin_bytes; ++ ++ /* track that we are in a none cipher/mac state */ ++ int none; ++ ++ /* use the less agressive window growth option */ ++ int hpn_buffer_limit; + }; + + typedef int (ssh_packet_hook_fn)(struct ssh *, struct sshbuf *, +@@ -157,6 +168,8 @@ + int ssh_packet_set_maxsize(struct ssh *, u_int); + u_int ssh_packet_get_maxsize(struct ssh *); + ++int packet_authentication_state(const struct ssh *); ++ + int ssh_packet_get_state(struct ssh *, struct sshbuf *); + int ssh_packet_set_state(struct ssh *, struct sshbuf *); + +@@ -171,6 +184,13 @@ + + void *ssh_packet_get_input(struct ssh *); + void *ssh_packet_get_output(struct ssh *); ++void *ssh_packet_get_receive_context(struct ssh *); ++void *ssh_packet_get_send_context(struct ssh *); ++ ++/* for forced packet rekeying post auth */ ++void packet_request_rekeying(void); ++/* final log entry support */ ++void sshpkt_final_log_entry (struct ssh *); + + /* new API */ + int sshpkt_start(struct ssh *ssh, u_char type); +diff -Nur openssh-9.3p1.orig/poly1305.c openssh-9.3p1/poly1305.c +--- openssh-9.3p1.orig/poly1305.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/poly1305.c 2024-07-10 09:09:55.912568437 +0200 +@@ -14,6 +14,16 @@ + + #include "poly1305.h" + ++#ifdef OPENSSL_HAVE_POLY_EVP ++void ++poly1305_auth(EVP_MAC_CTX *poly_ctx, unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { ++ size_t poly_out_len; ++ EVP_MAC_init(poly_ctx, (const u_char *)key, POLY1305_KEYLEN, NULL); ++ EVP_MAC_update(poly_ctx, m, inlen); ++ EVP_MAC_final(poly_ctx, out, &poly_out_len, (size_t)POLY1305_TAGLEN); ++} ++#else ++ + #define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + + #define U8TO32_LE(p) \ +@@ -31,7 +41,7 @@ + } while (0) + + void +-poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { ++poly1305_auth(char *unused, unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { + uint32_t t0,t1,t2,t3; + uint32_t h0,h1,h2,h3,h4; + uint32_t r0,r1,r2,r3,r4; +@@ -158,3 +168,4 @@ + U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); + } ++#endif /* OPENSSL_HAVE_POLY_EVP */ +diff -Nur openssh-9.3p1.orig/poly1305.h openssh-9.3p1/poly1305.h +--- openssh-9.3p1.orig/poly1305.h 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/poly1305.h 2024-07-10 09:09:55.912568437 +0200 +@@ -13,8 +13,15 @@ + #define POLY1305_KEYLEN 32 + #define POLY1305_TAGLEN 16 + +-void poly1305_auth(u_char out[POLY1305_TAGLEN], const u_char *m, size_t inlen, ++#ifdef OPENSSL_HAVE_POLY_EVP ++#include ++ ++void poly1305_auth(EVP_MAC_CTX *poly_key, u_char out[POLY1305_TAGLEN], const u_char *m, size_t inlen, ++ const u_char key[POLY1305_KEYLEN]) ++#else ++void poly1305_auth(char *unused, u_char out[POLY1305_TAGLEN], const u_char *m, size_t inlen, + const u_char key[POLY1305_KEYLEN]) ++#endif + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))); +diff -Nur openssh-9.3p1.orig/progressmeter.c openssh-9.3p1/progressmeter.c +--- openssh-9.3p1.orig/progressmeter.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/progressmeter.c 2024-07-10 09:09:55.912568437 +0200 +@@ -67,6 +67,8 @@ + static off_t start_pos; /* initial position of transfer */ + static off_t end_pos; /* ending position of transfer */ + static off_t cur_pos; /* transfer position as of last refresh */ ++static off_t last_pos; ++static off_t max_delta_pos = 0; + static volatile off_t *counter; /* progress counter */ + static long stalled; /* how long we have been stalled */ + static int bytes_per_second; /* current speed in bytes per second */ +@@ -133,6 +135,7 @@ + int cur_speed; + int hours, minutes, seconds; + int file_len, cols; ++ off_t delta_pos; + + if ((!force_update && !alarm_fired && !win_resized) || !can_output()) + return; +@@ -148,6 +151,10 @@ + now = monotime_double(); + bytes_left = end_pos - cur_pos; + ++ delta_pos = cur_pos - last_pos; ++ if (delta_pos > max_delta_pos) ++ max_delta_pos = delta_pos; ++ + if (bytes_left > 0) + elapsed = now - last_update; + else { +@@ -177,7 +184,7 @@ + return; + + /* filename */ +- file_len = cols = win_size - 36; ++ file_len = cols = win_size - 45; + if (file_len > 0) { + asmprintf(&buf, INT_MAX, &cols, "%-*s", file_len, file); + /* If we used fewer columns than expected then pad */ +@@ -194,6 +201,12 @@ + xextendf(&buf, NULL, " %3d%% %s %s/s ", percent, format_size(cur_pos), + format_rate((off_t)bytes_per_second)); + ++ /* instantaneous rate */ ++ if (bytes_left > 0) ++ xextendf(&buf, NULL, "%s/s", format_rate((off_t)delta_pos)); ++ else ++ xextendf(&buf, NULL, "%s/s", format_rate((off_t)max_delta_pos)); ++ + /* ETA */ + if (!transferred) + stalled += elapsed; +@@ -236,6 +249,7 @@ + } + free(buf); + free(obuf); ++ last_pos = cur_pos; + } + + static void +diff -Nur openssh-9.3p1.orig/readconf.c openssh-9.3p1/readconf.c +--- openssh-9.3p1.orig/readconf.c 2024-07-10 09:07:09.808082074 +0200 ++++ openssh-9.3p1/readconf.c 2024-07-10 09:09:55.913568440 +0200 +@@ -68,6 +68,7 @@ + #include "myproposal.h" + #include "digest.h" + #include "ssh-gss.h" ++#include "sshbuf.h" + + /* Format of the configuration file: + +@@ -169,6 +170,9 @@ + oHashKnownHosts, + oTunnel, oTunnelDevice, + oLocalCommand, oPermitLocalCommand, oRemoteCommand, ++ oTcpRcvBufPoll, oTcpRcvBuf, oHPNDisabled, oHPNBufferSize, ++ oNoneEnabled, oNoneMacEnabled, oNoneSwitch, oHPNBufferLimit, ++ oMetrics, oMetricsPath, oMetricsInterval, oFallback, oFallbackPort, + oVisualHostKey, + oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull, + oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass, +@@ -314,6 +318,15 @@ + { "kexalgorithms", oKexAlgorithms }, + { "ipqos", oIPQoS }, + { "requesttty", oRequestTTY }, ++ { "noneenabled", oNoneEnabled }, ++ { "nonemacenabled", oNoneMacEnabled }, ++ { "noneswitch", oNoneSwitch }, ++ { "hpnbufferlimit", oHPNBufferLimit }, ++ { "metrics", oMetrics }, ++ { "metricspath", oMetricsPath }, ++ { "metricsinterval", oMetricsInterval }, ++ { "fallback", oFallback }, ++ { "fallbackport", oFallbackPort }, + { "sessiontype", oSessionType }, + { "stdinnull", oStdinNull }, + { "forkafterauthentication", oForkAfterAuthentication }, +@@ -336,6 +349,10 @@ + { "proxyjump", oProxyJump }, + { "securitykeyprovider", oSecurityKeyProvider }, + { "knownhostscommand", oKnownHostsCommand }, ++ { "tcprcvbufpoll", oTcpRcvBufPoll }, ++ { "tcprcvbuf", oTcpRcvBuf }, ++ { "hpndisabled", oHPNDisabled }, ++ { "hpnbuffersize", oHPNBufferSize }, + { "requiredrsasize", oRequiredRSASize }, + { "rsaminsize", oRequiredRSASize }, /* alias */ + { "enableescapecommandline", oEnableEscapeCommandline }, +@@ -531,7 +548,7 @@ + + if (port == 0) { + sp = getservbyname(SSH_SERVICE_NAME, "tcp"); +- port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; ++ port = sp ? ntohs(sp->s_port) : HPNSSH_DEFAULT_PORT; + } + return port; + } +@@ -1182,6 +1199,67 @@ + intptr = &options->check_host_ip; + goto parse_flag; + ++ case oHPNDisabled: ++ intptr = &options->hpn_disabled; ++ goto parse_flag; ++ ++ case oHPNBufferSize: ++ intptr = &options->hpn_buffer_size; ++ goto parse_int; ++ ++ case oTcpRcvBufPoll: ++ intptr = &options->tcp_rcv_buf_poll; ++ goto parse_flag; ++ ++ case oNoneEnabled: ++ intptr = &options->none_enabled; ++ goto parse_flag; ++ ++ case oNoneMacEnabled: ++ intptr = &options->nonemac_enabled; ++ goto parse_flag; ++ ++ case oHPNBufferLimit: ++ intptr = &options->hpn_buffer_limit; ++ goto parse_flag; ++ ++ case oMetrics: ++ intptr = &options->metrics; ++ goto parse_flag; ++ ++ case oMetricsInterval: ++ intptr = &options->metrics_interval; ++ goto parse_int; ++ ++ case oMetricsPath: ++ charptr = &options->metrics_path; ++ options->metrics = 1; ++ goto parse_string; ++ ++ case oFallback: ++ intptr = &options->fallback; ++ goto parse_flag; ++ ++ case oFallbackPort: ++ intptr = &options->fallback_port; ++ goto parse_int; ++ ++ /* ++ * We check to see if the command comes from the command ++ * line or not. If it does then enable it otherwise fail. ++ * NONE should never be a default configuration. ++ */ ++ case oNoneSwitch: ++ if (strcmp(filename, "command-line") == 0) { ++ intptr = &options->none_switch; ++ goto parse_flag; ++ } else { ++ error("NoneSwitch is found in %.200s.\nYou may only use this configuration option from the command line", filename); ++ error("Continuing..."); ++ debug("NoneSwitch directive found in %.200s.", filename); ++ return 0; ++ } ++ + case oVerifyHostKeyDNS: + intptr = &options->verify_host_key_dns; + multistate_ptr = multistate_yesnoask; +@@ -1436,6 +1514,10 @@ + *intptr = value; + break; + ++ case oTcpRcvBuf: ++ intptr = &options->tcp_rcv_buf; ++ goto parse_int; ++ + case oCiphers: + arg = argv_next(&ac, &av); + if (!arg || *arg == '\0') { +@@ -2476,6 +2558,19 @@ + options->ip_qos_interactive = -1; + options->ip_qos_bulk = -1; + options->request_tty = -1; ++ options->none_switch = -1; ++ options->none_enabled = -1; ++ options->nonemac_enabled = -1; ++ options->metrics = -1; ++ options->metrics_path = NULL; ++ options->metrics_interval = -1; ++ options->hpn_disabled = -1; ++ options->hpn_buffer_size = -1; ++ options->hpn_buffer_limit = -1; ++ options->fallback = -1; ++ options->fallback_port = -1; ++ options->tcp_rcv_buf_poll = -1; ++ options->tcp_rcv_buf = -1; + options->session_type = -1; + options->stdin_null = -1; + options->fork_after_authentication = -1; +@@ -2653,8 +2748,53 @@ + options->server_alive_interval = 0; + if (options->server_alive_count_max == -1) + options->server_alive_count_max = 3; ++ if (options->hpn_disabled == -1) ++ options->hpn_disabled = 0; ++ if (options->hpn_buffer_size > -1) { ++ /* if a user tries to set the size to 0 set it to 1KB */ ++ if (options->hpn_buffer_size == 0) ++ options->hpn_buffer_size = 1; ++ /* limit the buffer to SSHBUF_SIZE_MAX (currently 256MB) */ ++ if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { ++ options->hpn_buffer_size = SSHBUF_SIZE_MAX; ++ debug("User requested buffer larger than 256MB. Request reverted to 256MB"); ++ } else ++ options->hpn_buffer_size *= 1024; ++ debug("hpn_buffer_size set to %d", options->hpn_buffer_size); ++ } ++ if (options->hpn_buffer_limit == -1) ++ options->hpn_buffer_limit = 0; ++ if (options->tcp_rcv_buf == 0) ++ options->tcp_rcv_buf = 1; ++ if (options->tcp_rcv_buf > -1) ++ options->tcp_rcv_buf *=1024; ++ if (options->tcp_rcv_buf_poll == -1) ++ options->tcp_rcv_buf_poll = 1; ++ if (options->none_switch == -1) ++ options->none_switch = 0; ++ if (options->none_enabled == -1) ++ options->none_enabled = 0; ++ if (options->none_enabled == 0 && options->none_switch > 0) { ++ fprintf(stderr, "NoneEnabled must be enabled to use the None Switch option. None cipher disabled.\n"); ++ options->none_enabled = 0; ++ } ++ if (options->nonemac_enabled == -1) ++ options->nonemac_enabled = 0; ++ if (options->nonemac_enabled > 0 && (options->none_enabled == 0 || ++ options->none_switch == 0)) { ++ fprintf(stderr, "None MAC can only be used with the None cipher. None MAC disabled.\n"); ++ options->nonemac_enabled = 0; ++ } ++ if (options->metrics == -1) ++ options->metrics = 0; ++ if (options->metrics_interval == -1) ++ options->metrics_interval = 5; + if (options->control_master == -1) + options->control_master = 0; ++ if (options->fallback == -1) ++ options->fallback = 1; ++ if (options->fallback_port == -1) ++ options->fallback_port = SSH_DEFAULT_PORT; + if (options->control_persist == -1) { + options->control_persist = 0; + options->control_persist_timeout = 0; +diff -Nur openssh-9.3p1.orig/readconf.h openssh-9.3p1/readconf.h +--- openssh-9.3p1.orig/readconf.h 2024-07-10 09:07:09.809082077 +0200 ++++ openssh-9.3p1/readconf.h 2024-07-10 09:09:55.913568440 +0200 +@@ -56,6 +56,11 @@ + int strict_host_key_checking; /* Strict host key checking. */ + int compression; /* Compress packets in both directions. */ + int tcp_keep_alive; /* Set SO_KEEPALIVE. */ ++ int tcp_rcv_buf; /* user switch to set tcp recv buffer */ ++ int tcp_rcv_buf_poll; /* Option to poll recv buf every window transfer */ ++ int hpn_disabled; /* Switch to disable HPN buffer management */ ++ int hpn_buffer_size; /* User definable size for HPN buffer window */ ++ int hpn_buffer_limit; /* limit local_window_max to 1/2 receive buffer */ + int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ + int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ + SyslogFacility log_facility; /* Facility for system logging. */ +@@ -128,6 +133,16 @@ + int enable_ssh_keysign; + int64_t rekey_limit; + int rekey_interval; ++ ++ int none_switch; /* Use none cipher */ ++ int none_enabled; /* Allow none to be used */ ++ int nonemac_enabled; /* Allow none to be used */ ++ int metrics; /* enable metrics */ ++ int metrics_interval; /* time in seconds between polls */ ++ char *metrics_path; /* path for the metrics files */ ++ int fallback; /* en|disable fallback port (def: true) */ ++ int fallback_port; /* port to fallback to (def: 22) */ ++ + int no_host_authentication_for_localhost; + int identities_only; + int server_alive_interval; +diff -Nur openssh-9.3p1.orig/README.md openssh-9.3p1/README.md +--- openssh-9.3p1.orig/README.md 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/README.md 2024-07-10 09:09:55.914568443 +0200 +@@ -1,16 +1,20 @@ +-# Portable OpenSSH ++# HPNSSH: Based on Portable OpenSSH + +-[![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml) +-[![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) ++Starting with version HPN17v0 there will be significant changes to the naming convention used for executables and installation locations. The last version that does not include these changes is HPN16v1 corresponding to the HPN-8_8_P1 tag on the master branch. + +-OpenSSH is a complete implementation of the SSH protocol (version 2) for secure remote login, command execution and file transfer. It includes a client ``ssh`` and server ``sshd``, file transfer utilities ``scp`` and ``sftp`` as well as tools for key generation (``ssh-keygen``), run-time key storage (``ssh-agent``) and a number of supporting programs. ++HPNSSH is a variant of OpenSSH. It a complete implementation of the SSH protocol (version 2) for secure remote login, command execution and file transfer. It includes a client ``hpnssh`` and server ``hpnsshd``, file transfer utilities ``hpnscp`` and ``hpnsftp`` as well as tools for key generation (``hpnssh-keygen``), run-time key storage (``hpnssh-agent``) and a number of supporting programs. It includes numerous performance and functionality enhancements focused on high performance networks and computing envrironments. Complete information can be found in the HPN-README file. + +-This is a port of OpenBSD's [OpenSSH](https://openssh.com) to most Unix-like operating systems, including Linux, OS X and Cygwin. Portable OpenSSH polyfills OpenBSD APIs that are not available elsewhere, adds sshd sandboxing for more operating systems and includes support for OS-native authentication and auditing (e.g. using PAM). ++It is fully compatible with all compliant implementations of the SSH protocol and OpenSSH in particular. ++ ++This version of HPNSSH is significant departure in terms of naming executables and installation locations. Specifically, all executables are now prefixed with ``hpn``. So ``ssh`` becomes ``hpnssh`` and ``scp`` is now ``hpnscp``. Configuation files and host keys can now be found in ``/etc/hpnssh``. By default ``hpnsshd`` now runs on port 2222 but this is configurable. This change was made in order to prevent installations of hpnssh, particularly from package distributions, from interfering with default installations of OpenSSH. HPNSSH is backwards compatible with all versions of OpenSSH including configuration files, keys, and run time options. Additionally, the client will, by default attempt to connect to port 2222 but will automatically fall back to port 22. This is also user configurable. ++ ++HPNSSH is based on OpenSSH portable. This is a port of OpenBSD's [OpenSSH](https://openssh.com) to most Unix-like operating systems, including Linux, OS X and Cygwin. Portable OpenSSH polyfills OpenBSD APIs that are not available elsewhere, adds sshd sandboxing for more operating systems and includes support for OS-native authentication and auditing (e.g. using PAM). ++ ++This document will be changing over time to reflect new changes and features. This document is built off of the OpenSSH README.md + + ## Documentation + +-The official documentation for OpenSSH are the man pages for each tool: ++The official documentation for OpenSSH are the man pages for each tool. + + * [ssh(1)](https://man.openbsd.org/ssh.1) + * [sshd(8)](https://man.openbsd.org/sshd.8) +@@ -21,15 +25,15 @@ + * [ssh-keyscan(8)](https://man.openbsd.org/ssh-keyscan.8) + * [sftp-server(8)](https://man.openbsd.org/sftp-server.8) + +-## Stable Releases ++All options in OpenSSH are respected by HPN-SSH. The man pages for HPN-SSH tools are the same as the name of the tool. + +-Stable release tarballs are available from a number of [download mirrors](https://www.openssh.com/portable.html#downloads). We recommend the use of a stable release for most users. Please read the [release notes](https://www.openssh.com/releasenotes.html) for details of recent changes and potential incompatibilities. ++## Building HPNSSH + +-## Building Portable OpenSSH ++Detailed step by step instructions can be found at https://psc.edu/hpn-ssh-home/ + + ### Dependencies + +-Portable OpenSSH is built using autoconf and make. It requires a working C compiler, standard library and headers. ++HPNSSH is built using autoconf and make. It requires a working C compiler, standard library and headers. + + ``libcrypto`` from either [LibreSSL](https://www.libressl.org/) or [OpenSSL](https://www.openssl.org) may also be used. OpenSSH may be built without either of these, but the resulting binaries will have only a subset of the cryptographic algorithms normally available. + +@@ -44,8 +48,8 @@ + Releases include a pre-built copy of the ``configure`` script and may be built using: + + ``` +-tar zxvf openssh-X.YpZ.tar.gz +-cd openssh ++tar zxvf hpnssh-X.YpZ.tar.gz ++cd hpnssh + ./configure # [options] + make && make tests + ``` +@@ -57,7 +61,7 @@ + If building from git, you'll need [autoconf](https://www.gnu.org/software/autoconf/) installed to build the ``configure`` script. The following commands will check out and build portable OpenSSH from git: + + ``` +-git clone https://github.com/openssh/openssh-portable # or https://anongit.mindrot.org/openssh.git ++git clone https://github.com/rapier1/openssh-portable + cd openssh-portable + autoreconf + ./configure +@@ -76,6 +80,7 @@ + ``--with-libedit`` | Enable [libedit](https://www.thrysoee.dk/editline/) support for sftp. + ``--with-kerberos5`` | Enable Kerberos/GSSAPI support. Both [Heimdal](https://www.h5l.org/) and [MIT](https://web.mit.edu/kerberos/) Kerberos implementations are supported. + ``--with-selinux`` | Enable [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux) support. ++``--with-security-key-builtin`` | Include built-in support for U2F/FIDO2 security keys. This requires [libfido2](https://github.com/Yubico/libfido2) be installed. + + ## Development + +diff -Nur openssh-9.3p1.orig/sandbox-seccomp-filter.c openssh-9.3p1/sandbox-seccomp-filter.c +--- openssh-9.3p1.orig/sandbox-seccomp-filter.c 2024-07-10 09:07:09.751081907 +0200 ++++ openssh-9.3p1/sandbox-seccomp-filter.c 2024-07-10 09:09:55.914568443 +0200 +@@ -295,6 +295,9 @@ + #ifdef __NR_geteuid32 + SC_ALLOW(__NR_geteuid32), + #endif ++#ifdef __NR_getpeername /* not defined on archs that go via socketcall(2) */ ++ SC_ALLOW(__NR_getpeername), ++#endif + #ifdef __NR_getpgid + SC_ALLOW(__NR_getpgid), + #endif +@@ -413,6 +416,9 @@ + #ifdef __NR_sigprocmask + SC_ALLOW(__NR_sigprocmask), + #endif ++#ifdef __NR_socketcall ++ SC_ALLOW(__NR_socketcall), ++#endif + #ifdef __NR_time + SC_ALLOW(__NR_time), + #endif +diff -Nur openssh-9.3p1.orig/scp.1 openssh-9.3p1/scp.1 +--- openssh-9.3p1.orig/scp.1 2024-07-10 09:07:09.720081816 +0200 ++++ openssh-9.3p1/scp.1 2024-07-10 10:06:25.921495807 +0200 +@@ -18,7 +18,7 @@ + .Nd OpenSSH secure file copy + .Sh SYNOPSIS + .Nm scp +-.Op Fl 346ABCOpqRrsTv ++.Op Fl 346ABCOpqRrsTvZ + .Op Fl c Ar cipher + .Op Fl D Ar sftp_server_path + .Op Fl F Ar ssh_config +@@ -252,6 +252,10 @@ + Note that + .Nm + follows symbolic links encountered in the tree traversal. ++.It Fl Z ++Resume failed or interrupted transfer. Identical files will be skipped. Remote must have resume option. ++.Nm ++only option. + .It Fl S Ar program + Name of + .Ar program +@@ -285,10 +289,13 @@ + .It Cm nrequests Ns = Ns Ar value + Controls how many concurrent SFTP read or write requests may be in progress + at any point in time during a download or upload. +-By default 64 requests may be active concurrently. ++This value must be between 1 and 8192. ++By default 1024 requests may be active concurrently. + .It Cm buffer Ns = Ns Ar value + Controls the maximum buffer size for a single SFTP read/write operation used + during download or upload. ++This value must be between 1B and 255KB. You may use ++the K unit for the size. E.g. 32768 or 32K. + By default a 32KB buffer is used. + .El + .El +@@ -322,6 +329,7 @@ + .Sh AUTHORS + .An Timo Rinne Aq Mt tri@iki.fi + .An Tatu Ylonen Aq Mt ylo@cs.hut.fi ++.An Chris Rapier Aq Mt rapier@psc.edu + .Sh CAVEATS + The legacy SCP protocol (selected by the + .Fl O +diff -Nur openssh-9.3p1.orig/scp.c openssh-9.3p1/scp.c +--- openssh-9.3p1.orig/scp.c 2024-07-10 09:07:09.793082030 +0200 ++++ openssh-9.3p1/scp.c 2024-07-10 09:09:55.915568446 +0200 +@@ -17,6 +17,7 @@ + /* + * Copyright (c) 1999 Theo de Raadt. All rights reserved. + * Copyright (c) 1999 Aaron Campbell. All rights reserved. ++ * Copyright (c) 2021 Chris Rapier. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions +@@ -133,6 +134,9 @@ + #include "misc.h" + #include "progressmeter.h" + #include "utf8.h" ++#ifdef WITH_OPENSSL ++#include ++#endif + #include "sftp.h" + + #include "sftp-common.h" +@@ -175,6 +179,10 @@ + /* This is the program to execute for the secured connection. ("ssh" or -S) */ + char *ssh_program = _PATH_SSH_PROGRAM; + ++/* this is path to the remote scp program allowing the user to specify ++ * a non-default scp */ ++char *remote_path; ++ + /* This is used to store the pid of ssh_program */ + pid_t do_cmd_pid = -1; + pid_t do_cmd_pid2 = -1; +@@ -189,6 +197,17 @@ + int remote_glob(struct sftp_conn *, const char *, int, + int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ + ++/* Flag to indicate that this is a file resume */ ++int resume_flag = 0; /* 0 is off, 1 is on */ ++ ++/* we want the host name for debugging purposes */ ++char hostname[HOST_NAME_MAX + 1]; ++ ++/* defines for the resume function. Need them even if not supported */ ++#define HASH_LEN 128 /*40 sha1, 64 blake2s256 128 blake2b512*/ ++#define BUF_AND_HASH HASH_LEN + 64 /* length of the hash and other data to get size of buffer */ ++#define HASH_BUFLEN 8192 /* 8192 seems to be a good balance between freads ++ * and the digest func*/ + static void + killchild(int signo) + { +@@ -234,6 +253,9 @@ + int status; + pid_t pid; + ++#ifdef DEBUG ++ fprintf(stderr, "In do_local_cmd\n"); ++#endif + if (a->num == 0) + fatal("do_local_cmd: no arguments"); + +@@ -460,6 +482,8 @@ + void tolocal(int, char *[], enum scp_mode_e, char *sftp_direct); + void toremote(int, char *[], enum scp_mode_e, char *sftp_direct); + void usage(void); ++void calculate_hash(char *, char *, off_t); /*get the hash of file to length*/ ++void rand_str(char *, size_t); /*gen randome char string */ + + void source_sftp(int, char *, char *, struct sftp_conn *); + void sink_sftp(int, char *, const char *, struct sftp_conn *); +@@ -478,11 +502,18 @@ + char *sftp_direct = NULL; + long long llv; + ++ /* we use this to prepend the debugging statements ++ * so we know which side is saying what */ ++ gethostname(hostname, HOST_NAME_MAX + 1); ++ + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + msetlocale(); + ++ /* for use with rand function when resume option is used*/ ++ srand(time(NULL)); ++ + /* Copy argv, because we modify it */ + argv0 = argv[0]; + newargv = xcalloc(MAXIMUM(argc + 1, 1), sizeof(*newargv)); +@@ -506,7 +537,7 @@ + + fflag = Tflag = tflag = 0; + while ((ch = getopt(argc, argv, +- "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { ++ "12346ABCTdfOpqRrstvZD:F:J:M:P:S:c:i:l:o:X:")) != -1) { + switch (ch) { + /* User-visible flags. */ + case '1': +@@ -587,24 +618,36 @@ + addargs(&remote_remote_args, "-q"); + showprogress = 0; + break; ++#ifdef WITH_OPENSSL ++ case 'Z': ++ /* currently resume only works in SCP mode */ ++ resume_flag = 1; ++ mode = MODE_SCP; ++ break; ++#endif + case 'X': + /* Please keep in sync with sftp.c -X */ + if (strncmp(optarg, "buffer=", 7) == 0) { + r = scan_scaled(optarg + 7, &llv); +- if (r == 0 && (llv <= 0 || llv > 256 * 1024)) { ++ /* don't ask for a buffer larger than the maximum ++ * size that SFTP can handle */ ++ if (r == 0 && (llv <= 0 || llv > (SFTP_MAX_MSG_LENGTH - 1024))) { + r = -1; + errno = EINVAL; + } + if (r == -1) { +- fatal("Invalid buffer size \"%s\": %s", +- optarg + 7, strerror(errno)); ++ fatal("Invalid buffer size. Must be between 1B and 255KB." ++ "\"%s\": %s", optarg + 7, strerror(errno)); + } + sftp_copy_buflen = (size_t)llv; + } else if (strncmp(optarg, "nrequests=", 10) == 0) { +- llv = strtonum(optarg + 10, 1, 256 * 1024, ++ /* more than 10k to 15k requests starts stalling the connection ++ * 8192 * default buffer size is 256MB of outstanding data. ++ * if users need more then they need to up the buffer size */ ++ llv = strtonum(optarg + 10, 1, 8 * 1024, + &errstr); + if (errstr != NULL) { +- fatal("Invalid number of requests " ++ fatal("Invalid number of requests. Must be between 1 and 8192. " + "\"%s\": %s", optarg + 10, errstr); + } + sftp_nrequests = (size_t)llv; +@@ -692,11 +735,20 @@ + remin = remout = -1; + do_cmd_pid = -1; + /* Command to be executed on remote system using "ssh". */ +- (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", +- verbose_mode ? " -v" : "", +- iamrecursive ? " -r" : "", pflag ? " -p" : "", +- targetshouldbedirectory ? " -d" : ""); +- ++ /* In the event of an hpn to hpn connection the scp ++ * command is rewritten to hpnscp. This happens in ++ * clientloop.c -cjr 12/12/2022 */ ++ ++ (void) snprintf(cmd, sizeof cmd, "%s%s%s%s%s%s", ++ remote_path ? remote_path : "scp", ++ verbose_mode ? " -v" : "", ++ iamrecursive ? " -r" : "", ++ pflag ? " -p" : "", ++ targetshouldbedirectory ? " -d" : "", ++ resume_flag ? " -Z" : ""); ++#ifdef DEBUG ++ fprintf(stderr, "%s: Sending cmd %s\n", hostname, cmd); ++#endif + (void) ssh_signal(SIGPIPE, lostconn); + + if (colon(argv[argc - 1])) /* Dest is remote host. */ +@@ -1310,6 +1362,74 @@ + free(src); + } + ++/* calculate the hash of a file up to length bytes ++ * this is used to determine if remote and local file ++ * fragments match. There may be a more efficient process for the hashing ++ * TODO: I'd like to XXHash for the hashing but that requires that both ++ * ends have xxhash installed and then dealing with fallbacks */ ++#ifdef WITH_OPENSSL ++void calculate_hash(char *filename, char *output, off_t length) ++{ ++ int n, md_len; ++ EVP_MD_CTX *c; ++ const EVP_MD *md; ++ char buf[HASH_BUFLEN]; ++ ssize_t bytes; ++ unsigned char out[EVP_MAX_MD_SIZE]; ++ char tmp[3]; ++ FILE *file_ptr; ++ *output = '\0'; ++ ++ /* open file for calculating hash */ ++ file_ptr = fopen(filename, "r"); ++ if (file_ptr==NULL) ++ { ++ if (verbose_mode) { ++ fprintf(stderr, "%s: error opening file %s\n", hostname, filename); ++ /* file the expected output with spaces */ ++ snprintf(output, HASH_LEN, "%s", " "); ++ } ++ return; ++ } ++ ++ md = EVP_get_digestbyname("blake2b512"); ++ c = EVP_MD_CTX_new(); ++ EVP_DigestInit_ex(c, md, NULL); ++ ++ while (length > 0) { ++ if (length > HASH_BUFLEN) ++ /* fread returns the number of elements read. ++ * in this case 1. Multiply by the length to get the bytes */ ++ bytes=fread(buf, HASH_BUFLEN, 1, file_ptr) * HASH_BUFLEN; ++ else ++ bytes=fread(buf, length, 1, file_ptr) * length; ++ EVP_DigestUpdate(c, buf, bytes); ++ length -= HASH_BUFLEN; ++ } ++ EVP_DigestFinal(c, out, &md_len); ++ EVP_MD_CTX_free(c); ++ /* convert the hash into a string */ ++ for(n=0; n < md_len; n++) { ++ snprintf(tmp, 3, "%02x", out[n]); ++ strncat(output, tmp, 3); ++ } ++#ifdef DEBUG ++ fprintf(stderr, "%s: HASH IS '%s' of length %ld\n", hostname, output, strlen(output)); ++#endif ++ fclose(file_ptr); ++} ++#else ++void calculate_hash(char *filename, char *output, off_t length) ++{ ++ /* empty function for builds without openssl */ ++} ++#endif /* WITH_OPENSSL */ ++ ++#define TYPE_OVERFLOW(type, val) \ ++ ((sizeof(type) == 4 && (val) > INT32_MAX) || \ ++ (sizeof(type) == 8 && (val) > INT64_MAX) || \ ++ (sizeof(type) != 4 && sizeof(type) != 8)) ++ + /* Prepare remote path, handling ~ by assuming cwd is the homedir */ + static char * + prepare_remote_path(struct sftp_conn *conn, const char *path) +@@ -1393,14 +1513,23 @@ + struct stat stb; + static BUF buffer; + BUF *bp; +- off_t i, statbytes; ++ off_t i, statbytes, xfer_size; + size_t amt, nr; + int fd = -1, haderr, indx; +- char *last, *name, buf[PATH_MAX + 128], encname[PATH_MAX]; ++ char *cp, *last, *name, buf[PATH_MAX + BUF_AND_HASH], encname[PATH_MAX]; + int len; ++ char hashsum[HASH_LEN+1], test_hashsum[HASH_LEN+1]; ++ char inbuf[PATH_MAX + BUF_AND_HASH]; ++ size_t insize; ++ unsigned long long ull; ++ char *match; /* used to communicate fragment match */ ++ match = "\0"; /*default is to fail the match. NULL and F both indicate fail*/ + + for (indx = 0; indx < argc; ++indx) { + name = argv[indx]; ++#ifdef DEBUG ++ fprintf(stderr, "%s index is %d, name is %s\n", hostname, indx, name); ++#endif + statbytes = 0; + len = strlen(name); + while (len > 1 && name[len-1] == '/') +@@ -1422,6 +1551,14 @@ + unset_nonblock(fd); + switch (stb.st_mode & S_IFMT) { + case S_IFREG: ++ /* only calculate hash if we are in resume mode and a file*/ ++ if (resume_flag) { ++ calculate_hash(name, hashsum, stb.st_size); ++#ifdef DEBUG ++ fprintf(stderr, "%s: Name is '%s' and hash '%s'\n", hostname, name, hashsum); ++ fprintf (stderr,"%s: size of %s is %ld\n", hostname, name, stb.st_size); ++#endif ++ } + break; + case S_IFDIR: + if (iamrecursive) { +@@ -1443,14 +1580,133 @@ + goto next; + } + #define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) +- snprintf(buf, sizeof buf, "C%04o %lld %s\n", +- (u_int) (stb.st_mode & FILEMODEMASK), +- (long long)stb.st_size, last); +- if (verbose_mode) +- fmprintf(stderr, "Sending file modes: %s", buf); ++ /* Add a hash of the file along with the filemode if in resume */ ++ if (resume_flag) ++ snprintf(buf, sizeof buf, "C%04o %lld %s %s\n", ++ (u_int) (stb.st_mode & FILEMODEMASK), ++ (long long)stb.st_size, hashsum, last); ++ else ++ snprintf(buf, sizeof buf, "C%04o %lld %s\n", ++ (u_int) (stb.st_mode & FILEMODEMASK), ++ (long long)stb.st_size, last); ++ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Sending file modes: %s", hostname, buf); ++#endif + (void) atomicio(vwrite, remout, buf, strlen(buf)); +- if (response() < 0) ++ ++#ifdef DEBUG ++ fprintf(stderr, "%s: inbuf length %ld buf length %ld\n", hostname, strlen(inbuf), strlen(buf)); ++#endif ++ if (resume_flag) { /* get the hash response from the remote */ ++ (void) atomicio(read, remin, inbuf, BUF_AND_HASH - 1); ++#ifdef DEBUG ++ fprintf(stderr, "%s: we got '%s' in inbuf length %ld buf was %ld\n", ++ hostname, inbuf, strlen(inbuf), strlen(buf)); ++#endif ++ } ++ if (response() < 0) { ++#ifdef DEBUG ++ fprintf(stderr, "%s: response is less than 0\n", hostname); ++#endif + goto next; ++ } ++ xfer_size = stb.st_size; ++ ++ /* we only do the following in resume mode because we have a ++ * new buf from the remote to parse */ ++ if (resume_flag) { ++ cp = inbuf; ++ if (*cp == 'R') { /* resume file transfer*/ ++ char *in_hashsum; /* where to hold the incoming hash */ ++ in_hashsum = calloc(HASH_LEN+1, sizeof(char)); ++ for (++cp; cp < inbuf + 5; cp++) { ++ /* skip over the mode */ ++ } ++ if (*cp++ != ' ') { ++ fprintf(stderr, "%s: mode not delineated!\n", hostname); ++ } ++ ++ if (!isdigit((unsigned char)*cp)) ++ fprintf(stderr, "%s: size not present\n", hostname); ++ ull = strtoull(cp, &cp, 10); ++ if (!cp || *cp++ != ' ') ++ fprintf(stderr, "%s: size not delimited\n", hostname); ++ if (TYPE_OVERFLOW(off_t, ull)) ++ fprintf(stderr, "%s: size out of range\n", hostname); ++ insize = (off_t)ull; ++ ++#ifdef DEBUG ++ fprintf (stderr, "%s: received size of %ld\n", hostname, insize); ++#endif ++ ++ /* copy the cp pointer byte by byte */ ++ int i; ++ for (i = 0; i < HASH_LEN; i++) { ++ strncat(in_hashsum, cp++, 1); ++ } ++#ifdef DEBUG ++ fprintf (stderr, "%s: in_hashsum '%s'\n", hostname, in_hashsum); ++#endif ++ ++ /*get the hash of the source file to the byte length we just got*/ ++ calculate_hash(name, test_hashsum, insize); ++#ifdef DEBUG ++ fprintf(stderr, "%s: calculated hashsum of local %s to %ld is %s\n", ++ hostname, last, insize, test_hashsum); ++#endif ++ /* compare the incoming hash to the hash of the local file*/ ++ if (strcmp(in_hashsum, test_hashsum) == 0) { ++ /* the fragments match so we should seek to the appropriate place in the ++ * local file and set the remote file to append */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: File fragments match\n", hostname); ++ fprintf(stderr, "%s: seeking to %ld\n", hostname, insize); ++#endif ++ xfer_size = stb.st_size - insize; ++#ifdef DEBUG ++ fprintf(stderr, "%s: xfer_size: %ld, stb.st_size: %ld insize: %ld\n", ++ hostname, xfer_size, stb.st_size, insize); ++#endif ++ if (lseek(fd, insize, SEEK_CUR) != (off_t)insize) { ++#ifdef DEBUG ++ fprintf(stderr, "%s: lseek did not return %ld\n", hostname, insize) ; ++#endif ++ goto next; ++ } ++ match = "M"; ++ } else { ++ /* the fragments don't match so we should start over from the begining */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: File fragments do not match '%s'(in) '%s'(local)\n", ++ hostname, in_hashsum, test_hashsum); ++#endif ++ match = "F"; ++ xfer_size = stb.st_size; ++ } ++ free(in_hashsum); ++ } ++ if (*cp == 'S') { /* skip file */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Should be skipping this file\n", hostname); ++#endif ++ goto next; ++ } ++ if (*cp == 'C') { /*transfer entire file*/ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Resending entire file\n", hostname); ++#endif ++ xfer_size = stb.st_size; ++ } ++ /* need to send the match status ++ * We always send the match status or we get out of sync ++ */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: sending match %s\n", hostname, match); ++#endif ++ (void) atomicio(vwrite, remout, match, 1); ++ } ++ + if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) { + next: if (fd != -1) { + (void) close(fd); +@@ -1458,13 +1714,17 @@ + } + continue; + } ++ ++#ifdef DEBUG ++ fprintf(stderr, "%s: going to xfer %ld\n", hostname, xfer_size); ++#endif + if (showprogress) +- start_progress_meter(curfile, stb.st_size, &statbytes); ++ start_progress_meter(curfile, xfer_size, &statbytes); + set_nonblock(remout); +- for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { ++ for (haderr = i = 0; i < xfer_size; i += bp->cnt) { + amt = bp->cnt; +- if (i + (off_t)amt > stb.st_size) +- amt = stb.st_size - i; ++ if (i + (off_t)amt > xfer_size) ++ amt = xfer_size - i; + if (!haderr) { + if ((nr = atomicio(read, fd, + bp->buf, amt)) != amt) { +@@ -1657,28 +1917,40 @@ + (sizeof(type) == 8 && (val) > INT64_MAX) || \ + (sizeof(type) != 4 && sizeof(type) != 8)) + ++ + void + sink(int argc, char **argv, const char *src) + { + static BUF buffer; +- struct stat stb; ++ struct stat stb, cpstat, npstat; + BUF *bp; + off_t i; + size_t j, count; + int amt, exists, first, ofd; + mode_t mode, omode, mask; +- off_t size, statbytes; ++ off_t size, statbytes, xfer_size; + unsigned long long ull; + int setimes, targisdir, wrerr; +- char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; ++ char ch, *cp, *np, *np_tmp, *targ, *why, *vect[1], buf[16384], visbuf[16384]; + char **patterns = NULL; + size_t n, npatterns = 0; + struct timeval tv[2]; ++ char remote_hashsum[HASH_LEN+1]; ++ char local_hashsum[HASH_LEN+1]; ++ char tmpbuf[BUF_AND_HASH]; ++ char outbuf[BUF_AND_HASH]; ++ char match; ++ int bad_match_flag = 0; ++ np = NULL; /* this was originally '/0' but that's wrong */ ++ np_tmp = NULL; + + #define atime tv[0] + #define mtime tv[1] + #define SCREWUP(str) { why = str; goto screwup; } + ++#ifdef DEBUG ++ fprintf (stderr, "%s: LOCAL In sink with %s\n", hostname, src); ++#endif + if (TYPE_OVERFLOW(time_t, 0) || TYPE_OVERFLOW(off_t, 0)) + SCREWUP("Unexpected off_t/time_t size"); + +@@ -1694,9 +1966,16 @@ + if (targetshouldbedirectory) + verifydir(targ); + ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n",hostname); ++#endif + (void) atomicio(vwrite, remout, "", 1); + if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) + targisdir = 1; ++ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Target is %s with a size of %ld\n", hostname, targ, stb.st_size); ++#endif + if (src != NULL && !iamrecursive && !Tflag) { + /* + * Prepare to try to restrict incoming filenames to match +@@ -1705,7 +1984,12 @@ + if (brace_expand(src, &patterns, &npatterns) != 0) + fatal_f("could not expand pattern"); + } ++ + for (first = 1;; first = 0) { ++ bad_match_flag = 0; /* used in resume mode. */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: At start of loop buf is %s\n", hostname, buf); ++#endif + cp = buf; + if (atomicio(read, remin, cp, 1) != 1) + goto done; +@@ -1733,6 +2017,9 @@ + continue; + } + if (buf[0] == 'E') { ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n", hostname); ++#endif + (void) atomicio(vwrite, remout, "", 1); + goto done; + } +@@ -1740,6 +2027,9 @@ + *--cp = 0; + + cp = buf; ++#ifdef DEBUG ++ fprintf(stderr, "%s: buf is %s\n", hostname, buf); ++#endif + if (*cp == 'T') { + setimes++; + cp++; +@@ -1767,9 +2057,19 @@ + if (!cp || *cp++ != '\0' || atime.tv_usec < 0 || + atime.tv_usec > 999999) + SCREWUP("atime.usec not delimited"); ++ ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n", hostname); ++#endif + (void) atomicio(vwrite, remout, "", 1); + continue; + } ++ if (*cp == 'R') { /*resume file transfer (dont' think I need this here)*/ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Received a RESUME request with %s\n", hostname, cp); ++#endif ++ resume_flag = 1; ++ } + if (*cp != 'C' && *cp != 'D') { + /* + * Check for the case "rcp remote:foo\* local:bar". +@@ -1785,6 +2085,18 @@ + SCREWUP("expected control record"); + } + mode = 0; ++#ifdef DEBUG ++ fprintf(stderr, "%s: buf is %s\n", hostname, buf); ++ fprintf(stderr, "%s: cp is %s\n", hostname, cp); ++#endif ++ /* we need to track if this object is a directory ++ * before we move the pointer. If we are in resume mode ++ * we might end up trying to get an mdsum on a directory ++ * and that doesn't work */ ++ int dir_flag = 0; ++ if (*cp == 'D') ++ dir_flag = 1; ++ + for (++cp; cp < buf + 5; cp++) { + if (*cp < '0' || *cp > '7') + SCREWUP("bad mode"); +@@ -1795,6 +2107,10 @@ + if (*cp++ != ' ') + SCREWUP("mode not delimited"); + ++#ifdef DEBUG ++ fprintf(stderr, "%s: cp is %s\n", hostname, cp); ++#endif ++ + if (!isdigit((unsigned char)*cp)) + SCREWUP("size not present"); + ull = strtoull(cp, &cp, 10); +@@ -1804,11 +2120,32 @@ + SCREWUP("size out of range"); + size = (off_t)ull; + ++#ifdef DEBUG ++ fprintf(stderr, "%s: cp is %s\n", hostname, cp); ++#endif ++ if (resume_flag && !dir_flag) { ++ *remote_hashsum = '\0'; ++ int i; ++ for (i = 0; i < HASH_LEN; i++) { ++ strncat (remote_hashsum, cp++, 1); ++ } ++#ifdef DEBUG ++ fprintf (stderr, "%s: '%s'\n", hostname, remote_hashsum); ++#endif ++ if (!cp || *cp++ != ' ') ++ SCREWUP("hash not delimited"); ++ } ++#ifdef DEBUG ++ fprintf(stderr, "%s: cp is %s\n", hostname, cp); ++#endif + if (*cp == '\0' || strchr(cp, '/') != NULL || + strcmp(cp, ".") == 0 || strcmp(cp, "..") == 0) { + run_err("error: unexpected filename: %s", cp); + exit(1); + } ++#ifdef DEBUG ++ fprintf(stderr, "%s cp is %s\n", hostname, cp); ++#endif + if (npatterns > 0) { + for (n = 0; n < npatterns; n++) { + if (strcmp(patterns[n], cp) == 0 || +@@ -1870,11 +2207,195 @@ + } + omode = mode; + mode |= S_IWUSR; ++ stat(cp, &cpstat); ++ xfer_size = size; ++ if (resume_flag) { ++#ifdef DEBUG ++ fprintf(stderr, "%s: np is %s\n", hostname, np); ++#endif ++ /* does the file exist and if it does it writable? */ ++ if (stat(np, &npstat) == -1) { ++#ifdef DEBUG ++ fprintf(stderr, "%s Local file does not exist size is %ld!\n", ++ hostname, npstat.st_size); ++#endif ++ npstat.st_size = 0; ++ } else { ++ /* check to see if the file is writeable ++ * if it isn't then we need to skip it but ++ * before we skip it we need to send the remote ++ * what they are expecting so BUF_AND_HASH bytes and then ++ * a null. ++ * NOTE!!! The format in the snprintf needs the actual numeric ++ * because using a define isn't working */ ++ if (access (np, W_OK) != 0) { ++ fprintf(stderr, "scp: %s: Permission denied on %s\n", np, hostname); ++ snprintf(outbuf, BUF_AND_HASH, "S%-*s", BUF_AND_HASH-2, " "); ++ (void)atomicio(vwrite, remout, outbuf, strlen(outbuf)); ++ (void)atomicio(vwrite, remout, "", 1); ++ continue; ++ } ++ } ++ /* this file is already here do we need to move it? ++ * Check to make sure npstat.st_size > 0. If it is 0 then we ++ * may trying to be moving a zero byte file in which case this ++ * following block fails. See on 0 byte files the hashes will ++ * always match and the file won't be created even though it should ++ */ ++ if (xfer_size == npstat.st_size && (npstat.st_size > 0)) { ++ calculate_hash(np, local_hashsum, npstat.st_size); ++ if (strcmp(local_hashsum,remote_hashsum) == 0) { ++ /* we can skip this file if we want to. */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Files are the same\n", hostname); ++#endif ++ /* the remote is expecting something so we need to send them something*/ ++ snprintf(outbuf, BUF_AND_HASH, "S%-*s", BUF_AND_HASH-2, " "); ++ (void)atomicio(vwrite, remout, outbuf, strlen(outbuf)); ++#ifdef DEBUG ++ fprintf(stderr,"%s: sent '%s' to remote\n", hostname, outbuf); ++#endif ++ /* the remote is waiting on an ack so send a null */ ++ (void)atomicio(vwrite, remout, "", 1); ++ if (showprogress) ++ fprintf (stderr, "Skipping identical file: %s\n", np); ++ continue; ++ } else { ++ /* file sizes are the same but they don't match */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: target(%ld) is different than source(%ld)!\n", ++ hostname, npstat.st_size, size); ++#endif ++ snprintf(tmpbuf, sizeof outbuf, "C%04o %lld %s", ++ (u_int) (npstat.st_mode & FILEMODEMASK), ++ (long long)npstat.st_size, local_hashsum); ++ snprintf(outbuf, BUF_AND_HASH, "%-*s", BUF_AND_HASH-1, tmpbuf); ++ (void) atomicio(vwrite, remout, outbuf, strlen(outbuf)); ++ bad_match_flag = 1; ++ } ++ } ++ /* if npstat.st_size is 0 then the local file doesn't exist and ++ * we have to move it. Since we are in resume mode treat it as a resume */ ++ if (npstat.st_size < xfer_size || (npstat.st_size == 0)) { ++ char rand_string[9]; ++#ifdef DEBUG ++ fprintf (stderr, "%s: %s is smaller than %s\n", hostname, np, cp); ++#endif ++ calculate_hash(np, local_hashsum, npstat.st_size); ++#define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) ++ snprintf(tmpbuf, sizeof outbuf, "R%04o %lld %s", ++ (u_int) (npstat.st_mode & FILEMODEMASK), ++ (long long)npstat.st_size, local_hashsum); ++ snprintf(outbuf, BUF_AND_HASH, "%-*s", BUF_AND_HASH-1, tmpbuf); ++#ifdef DEBUG ++ fprintf (stderr, "%s: new buf is %s of length %ld\n", ++ hostname, outbuf, strlen(outbuf)); ++ fprintf(stderr, "%s: Sending new file (%s) modes: %s\n", ++ hostname, np, outbuf); ++#endif ++ /*now we have to send np's length and hash to the other end ++ * if the computed hashes match then we seek to np's length in ++ * file and append to np starting from there */ ++ (void) atomicio(vwrite, remout, outbuf, strlen(outbuf)); ++#ifdef DEBUG ++ fprintf(stderr, "%s: New size: %ld, size: %ld, st_size: %ld\n", ++ hostname, size - npstat.st_size, size, npstat.st_size); ++#endif ++ xfer_size = size - npstat.st_size; ++ resume_flag = 1; ++ np_tmp = xstrdup(np); ++ /* We should have a random component to avoid clobbering a ++ * local file */ ++ rand_str(rand_string, 8); ++ strcat(np, rand_string); ++#ifdef DEBUG ++ fprintf(stderr, "%s: Will concat %s to %s after xfer\n", ++ hostname, np, np_tmp); ++#endif ++ } else if (npstat.st_size > size) { ++ /* the target file is larger than the source. ++ * so we need to overwrite it. We don't need to send the hash though. */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: target(%ld) is larger than source(%ld)!\n", ++ hostname, npstat.st_size, size); ++#endif ++ snprintf(tmpbuf, sizeof outbuf, "C%04o %lld", ++ (u_int) (npstat.st_mode & FILEMODEMASK), ++ (long long)npstat.st_size); ++ snprintf(outbuf, BUF_AND_HASH, "%-*s", BUF_AND_HASH-1, tmpbuf); ++ (void) atomicio(vwrite, remout, outbuf, strlen(outbuf)); ++ bad_match_flag = 1; ++ } ++ ++#ifdef DEBUG ++ fprintf (stderr, "%s: CP is %s(%ld) NP is %s(%ld)\n", ++ hostname, cp, size, np, npstat.st_size); ++#endif ++ /* we are in resume mode so we need this *here* and not later ++ * because we need to get the file match information from the remote ++ * outside of resume mode we don't get that so we get out of sync ++ * so we have a test for the resume_flag after this block */ ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n", hostname); ++#endif ++ (void) atomicio(vwrite, remout, "", 1); ++ ++ /* the remote is always going to send a match status ++ * so we need to read it so we don't get out of sync */ ++ (void) atomicio(read, remin, &match, 1); ++ if (match != 'M') {/*fragments do not match*/ ++ /* expected response of F, M and NULL *but* ++ * anything other than M is a failure ++ * if it's a NULL then we reset xfer_size but ++ * we retain the file pointers */ ++ xfer_size = size; ++ bad_match_flag = 1; ++ if (match == 'F') { ++ /* got an F for failure and not NULL ++ * so we want to swap over the filename from ++ * the temp back to the original */ ++#ifdef DEBUG ++ fprintf(stderr, "%s: match status is F\n", hostname); ++#endif ++ if (np_tmp != NULL) ++ np = np_tmp; ++ else { ++ continue; ++ } ++ } else { ++#ifdef DEBUG ++ fprintf(stderr, "%s: match received is NULL\n", hostname); ++#endif ++ } ++ } else { ++#ifdef DEBUG ++ fprintf(stderr, "%s match status is M\n", hostname); ++#endif ++ bad_match_flag = 0; /* while this is set at the beginning of the ++ * loop I'm setting it here explicitly as well */ ++ } ++ } ++ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Creating file. mode is %d for %s\n", ++ hostname, mode, np); ++#endif + if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) { + bad: run_err("%s: %s", np, strerror(errno)); + continue; + } +- (void) atomicio(vwrite, remout, "", 1); ++ ++ /* in the case of not using the resume function we need this vwrite here ++ * in the case of using the resume flag it comes in the above if (resume_flag) block ++ * why? because scp is weird and depends on an intricate and silly dance of ++ * call and response at just the right time. That's why */ ++ if (!resume_flag) { ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n", hostname); ++#endif ++ (void) atomicio(vwrite, remout, "", 1); ++ } ++ + if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) { + (void) close(ofd); + continue; +@@ -1889,13 +2410,17 @@ + */ + statbytes = 0; + if (showprogress) +- start_progress_meter(curfile, size, &statbytes); ++ start_progress_meter(curfile, xfer_size, &statbytes); + set_nonblock(remin); +- for (count = i = 0; i < size; i += bp->cnt) { ++#ifdef DEBUG ++ fprintf(stderr, "%s: xfer_size is %ld\n", hostname, xfer_size); ++#endif ++ for (count = i = 0; i < xfer_size; i += bp->cnt) { + amt = bp->cnt; +- if (i + amt > size) +- amt = size - i; ++ if (i + amt > xfer_size) ++ amt = xfer_size - i; + count += amt; ++ /* read the data from the socket*/ + do { + j = atomicio6(read, remin, cp, amt, + scpio, &statbytes); +@@ -1930,8 +2455,78 @@ + wrerr = 1; + } + if (!wrerr && (!exists || S_ISREG(stb.st_mode)) && +- ftruncate(ofd, size) != 0) ++ ftruncate(ofd, xfer_size) != 0) + note_err("%s: truncate: %s", np, strerror(errno)); ++ ++ /* if np_tmp isn't set then we don't have a resume file to cat */ ++ /* likewise, bad match flag means no resume flag */ ++#ifdef DEBUG ++ fprintf (stderr, "%s: resume_flag: %d, np_tmp: %s, bad_match_flag: %d\n", ++ hostname, resume_flag, np_tmp, bad_match_flag); ++#endif ++ if (resume_flag && np_tmp && !bad_match_flag) { ++ FILE *orig, *resume; ++ char res_buf[512]; /* set at 512 just because, might want to increase*/ ++ ssize_t res_bytes = 0; ++ off_t sum = 0; ++ struct stat res_stat; ++ *res_buf = '\0'; ++ orig = NULL; /*supress warnings*/ ++ resume = NULL; /*supress warnings*/ ++#ifdef DEBUG ++ fprintf(stderr, "%s: Resume flag is set. Going to concat %s to %s now\n", ++ hostname, np, np_tmp); ++#endif ++ /* np/ofd is the resume file so open np_tmp for appending ++ * close ofd because we are going to be shifting it ++ * and I don't wnat the same file open in multiple descriptors */ ++ if (close(ofd) == -1) ++ note_err("%s: close: %s", np, strerror(errno)); ++ /* orig is the target file, resume is the temp file */ ++ orig = fopen(np_tmp, "a"); /*open for appending*/ ++ if (orig == NULL) { ++ fprintf(stderr, "%s: Could not open %s for appending.", hostname, np_tmp); ++ goto stopcat; ++ } ++ resume = fopen(np, "r"); /*open for reading only*/ ++ if (resume == NULL) { ++ fprintf(stderr, "%s: Could not open %s for reading.", hostname, np); ++ goto stopcat; ++ } ++ /* get the number of bytes in the temp file*/ ++ if (fstat(fileno(resume), &res_stat) == -1) { ++ fprintf(stderr, "%s: Could not stat %s", hostname, np); ++ goto stopcat; ++ } ++ /* while the number of bytes read from the temp file ++ * is less than the size of the file read in a chunk and ++ * write it to the target file */ ++ do { ++ res_bytes = fread(res_buf, 1, 512, resume); ++ fwrite(res_buf, 1, res_bytes, orig); ++ sum += res_bytes; ++ } while (sum < res_stat.st_size); ++ ++stopcat: if (orig) ++ fclose(orig); ++ if (resume) ++ fclose(resume); ++ /* delete the resume file */ ++ remove(np); ++#ifdef DEBUG ++ fprintf (stderr, "%s: np(%s) and np_tmp(%s)\n", hostname, np, np_tmp); ++#endif ++ np = np_tmp; ++#ifdef DEBUG ++ fprintf (stderr, "%s np(%s) and np_tmp(%s)\n", hostname, np, np_tmp); ++#endif ++ /* reset ofd to the original np */ ++ if ((ofd = open(np_tmp, O_WRONLY)) == -1) { ++ fprintf(stderr, "%s: couldn't open %s in append function\n", hostname, np_tmp); ++ atomicio(vwrite, remout, "", 1); ++ goto bad; ++ } ++ } + if (pflag) { + if (exists || omode != mode) + #ifdef HAVE_FCHMOD +@@ -1966,8 +2561,17 @@ + } + } + /* If no error was noted then signal success for this file */ +- if (note_err(NULL) == 0) ++ if (note_err(NULL) == 0) { ++#ifdef DEBUG ++ fprintf (stderr, "%s: Sending null to remout.\n", hostname); ++#endif + (void) atomicio(vwrite, remout, "", 1); ++ } ++ /* we are in resume mode and we have allocated memory for np_tmp */ ++ if (resume_flag && np_tmp != NULL) { ++ free(np_tmp); ++ np_tmp = NULL; ++ } + } + done: + for (n = 0; n < npatterns; n++) +@@ -2111,11 +2715,19 @@ + void + usage(void) + { ++#ifdef WITH_OPENSSL ++ (void) fprintf(stderr, ++ "usage: scp [-346ABCOpqRrsTvZ] [-c cipher] [-D sftp_server_path] [-F ssh_config]\n" ++ " [-i identity_file] [-J destination] [-l limit] [-o ssh_option]\n" ++ " [-P port] [-S program] [-X sftp_option] source ... target\n"); ++ exit(1); ++#else + (void) fprintf(stderr, + "usage: scp [-346ABCOpqRrsTv] [-c cipher] [-D sftp_server_path] [-F ssh_config]\n" + " [-i identity_file] [-J destination] [-l limit] [-o ssh_option]\n" + " [-P port] [-S program] [-X sftp_option] source ... target\n"); + exit(1); ++#endif + } + + void +@@ -2254,6 +2866,18 @@ + exit(1); + } + ++void rand_str(char *dest, size_t length) { ++ char charset[] = "0123456789" ++ "abcdefghijklmnopqrstuvwxyz" ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; ++ ++ while (length-- > 0) { ++ size_t index = (double) rand() / RAND_MAX * (sizeof charset - 1); ++ *dest++ = charset[index]; ++ } ++ *dest = '\0'; ++} ++ + void + cleanup_exit(int i) + { +diff -Nur openssh-9.3p1.orig/servconf.c openssh-9.3p1/servconf.c +--- openssh-9.3p1.orig/servconf.c 2024-07-10 09:07:09.809082077 +0200 ++++ openssh-9.3p1/servconf.c 2024-07-10 10:16:09.316203959 +0200 +@@ -200,6 +200,12 @@ + options->authorized_principals_file = NULL; + options->authorized_principals_command = NULL; + options->authorized_principals_command_user = NULL; ++ options->tcp_rcv_buf_poll = -1; ++ options->hpn_disabled = -1; ++ options->hpn_buffer_size = -1; ++ options->none_enabled = -1; ++ options->nonemac_enabled = -1; ++ options->hpn_buffer_limit = -1; + options->ip_qos_interactive = -1; + options->ip_qos_bulk = -1; + options->version_addendum = NULL; +@@ -294,6 +300,10 @@ + fill_default_server_options(ServerOptions *options) + { + u_int i; ++ /* needed for hpn socket tests */ ++ int sock; ++ int socksize; ++ int socksizelen = sizeof(int); + + /* Portable-specific options */ + if (options->use_pam == -1) +@@ -465,6 +475,51 @@ + } + if (options->permit_tun == -1) + options->permit_tun = SSH_TUNMODE_NO; ++ if (options->none_enabled == -1) ++ options->none_enabled = 0; ++ if (options->nonemac_enabled == -1) ++ options->nonemac_enabled = 0; ++ if (options->nonemac_enabled > 0 && options->none_enabled == 0) { ++ debug ("Attempted to enabled None MAC without setting None Enabled to true. None MAC disabled."); ++ options->nonemac_enabled = 0; ++ } ++ if (options->hpn_disabled == -1) ++ options->hpn_disabled = 0; ++ if (options->hpn_buffer_limit == -1) ++ options->hpn_buffer_limit = 0; ++ ++ if (options->hpn_buffer_size == -1) { ++ /* option not explicitly set. Now we have to figure out */ ++ /* what value to use */ ++ if (options->hpn_disabled == 1) { ++ options->hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; ++ } else { ++ /* get the current RCV size and set it to that */ ++ /*create a socket but don't connect it */ ++ /* we use that the get the rcv socket size */ ++ sock = socket(AF_INET, SOCK_STREAM, 0); ++ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, ++ &socksize, &socksizelen); ++ close(sock); ++ options->hpn_buffer_size = socksize; ++ debug("HPN Buffer Size: %d", options->hpn_buffer_size); ++ } ++ } else { ++ /* we have to do this in case the user sets both values in a contradictory */ ++ /* manner. hpn_disabled overrrides hpn_buffer_size*/ ++ if (options->hpn_disabled <= 0) { ++ if (options->hpn_buffer_size == 0) ++ options->hpn_buffer_size = 1; ++ /* limit the maximum buffer to SSHBUF_SIZE_MAX (currently 256MB) */ ++ if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { ++ options->hpn_buffer_size = SSHBUF_SIZE_MAX; ++ } else { ++ options->hpn_buffer_size *= 1024; ++ } ++ } else ++ options->hpn_buffer_size = CHAN_TCP_WINDOW_DEFAULT; ++ } ++ + if (options->ip_qos_interactive == -1) + options->ip_qos_interactive = IPTOS_DSCP_AF21; + if (options->ip_qos_bulk == -1) +@@ -546,6 +601,8 @@ + sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication, + sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, + sPrintMotd, sPrintLastLog, sIgnoreRhosts, ++ sNoneEnabled, sNoneMacEnabled, sHPNBufferLimit, ++ sTcpRcvBufPoll, sHPNDisabled, sHPNBufferSize, + sX11Forwarding, sX11DisplayOffset, sX11MaxDisplays, sX11UseLocalhost, + sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive, + sPermitUserEnvironment, sAllowTcpForwarding, sCompression, +@@ -747,6 +804,12 @@ + { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, + { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, + { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, ++ { "hpndisabled", sHPNDisabled, SSHCFG_ALL }, ++ { "hpnbuffersize", sHPNBufferSize, SSHCFG_ALL }, ++ { "tcprcvbufpoll", sTcpRcvBufPoll, SSHCFG_ALL }, ++ { "noneenabled", sNoneEnabled, SSHCFG_ALL }, ++ { "nonemacenabled", sNoneMacEnabled, SSHCFG_ALL }, ++ { "hpnbufferlimit", sHPNBufferLimit, SSHCFG_ALL }, + { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, + { "include", sInclude, SSHCFG_ALL }, + { "ipqos", sIPQoS, SSHCFG_ALL }, +@@ -809,6 +872,7 @@ + + for (i = 0; keywords[i].name; i++) + if (strcasecmp(cp, keywords[i].name) == 0) { ++ debug("Config token is %s", keywords[i].name); + *flags = keywords[i].flags; + return keywords[i].opcode; + } +@@ -1636,12 +1700,36 @@ + multistate_ptr = multistate_ignore_rhosts; + goto parse_multistate; + ++ case sTcpRcvBufPoll: ++ intptr = &options->tcp_rcv_buf_poll; ++ goto parse_flag; ++ ++ case sHPNDisabled: ++ intptr = &options->hpn_disabled; ++ goto parse_flag; ++ ++ case sHPNBufferSize: ++ intptr = &options->hpn_buffer_size; ++ goto parse_int; ++ + case sIgnoreUserKnownHosts: + intptr = &options->ignore_user_known_hosts; + parse_flag: + multistate_ptr = multistate_flag; + goto parse_multistate; + ++ case sNoneEnabled: ++ intptr = &options->none_enabled; ++ goto parse_flag; ++ ++ case sNoneMacEnabled: ++ intptr = &options->nonemac_enabled; ++ goto parse_flag; ++ ++ case sHPNBufferLimit: ++ intptr = &options->hpn_buffer_limit; ++ goto parse_flag; ++ + case sHostbasedAuthentication: + intptr = &options->hostbased_authentication; + goto parse_flag; +diff -Nur openssh-9.3p1.orig/servconf.h openssh-9.3p1/servconf.h +--- openssh-9.3p1.orig/servconf.h 2024-07-10 09:07:09.810082080 +0200 ++++ openssh-9.3p1/servconf.h 2024-07-10 10:19:26.757782065 +0200 +@@ -213,6 +213,12 @@ + + int use_pam; /* Enable auth via PAM */ + int permit_pam_user_change; /* Allow PAM to change user name */ ++ int tcp_rcv_buf_poll; /* poll tcp rcv window in autotuning kernels*/ ++ int hpn_disabled; /* disable hpn functionality. false by default */ ++ int hpn_buffer_size; /* set the hpn buffer size - default 3MB */ ++ int none_enabled; /* Enable NONE cipher switch */ ++ int nonemac_enabled; /* Enable NONE MAC switch */ ++ int hpn_buffer_limit; /* limit local_window_max to 1/2 receive buffer */ + + int permit_tun; + +diff -Nur openssh-9.3p1.orig/serverloop.c openssh-9.3p1/serverloop.c +--- openssh-9.3p1.orig/serverloop.c 2024-07-10 09:07:09.794082033 +0200 ++++ openssh-9.3p1/serverloop.c 2024-07-10 12:49:21.563392024 +0200 +@@ -81,6 +81,7 @@ + #include "serverloop.h" + #include "ssherr.h" + #include "compat.h" ++#include "metrics.h" + + extern ServerOptions options; + +@@ -347,6 +348,7 @@ + sigset_t bsigset, osigset; + + debug("Entering interactive session for SSH2."); ++ ssh->start_time = monotime_double(); + + if (sigemptyset(&bsigset) == -1 || sigaddset(&bsigset, SIGCHLD) == -1) + error_f("bsigset setup: %s", strerror(errno)); +@@ -385,7 +387,9 @@ + error_f("osigset sigprocmask: %s", strerror(errno)); + + if (received_sigterm) { ++ sshpkt_final_log_entry(ssh); + logit("Exiting on signal %d", (int)received_sigterm); ++ sshpkt_final_log_entry(ssh); + /* Clean up sessions, utmp, etc. */ + cleanup_exit(255); + } +@@ -403,9 +407,15 @@ + collect_children(ssh); + free(pfd); + ++ /* write final log entry */ ++ sshpkt_final_log_entry(ssh); ++ + /* free all channels, no more reads and writes */ + channel_free_all(ssh); + ++ /* final entry must come after channels close -cjr */ ++ sshpkt_final_log_entry(ssh); ++ + /* free remaining sessions, e.g. remove wtmp entries */ + session_destroy_all(ssh, NULL); + } +@@ -556,7 +566,8 @@ + debug("Tunnel forwarding using interface %s", ifname); + + c = channel_new(ssh, "tun", SSH_CHANNEL_OPEN, sock, sock, -1, +- CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); ++ options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, ++ CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + c->datagram = 1; + #if defined(SSH_TUN_FILTER) + if (mode == SSH_TUNMODE_POINTOPOINT) +@@ -607,6 +618,10 @@ + c = channel_new(ssh, "session", SSH_CHANNEL_LARVAL, + -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, + 0, "server-session", 1); ++ if ((options.tcp_rcv_buf_poll) && (!options.hpn_disabled)) ++ c->dynamic_window = 1; ++ if (options.hpn_buffer_limit) ++ c->hpn_buffer_limit = 1; + if (session_open(the_authctxt, c->self) != 1) { + debug("session open failed, free channel %d", c->self); + channel_free(ssh, c); +@@ -675,6 +690,67 @@ + return 0; + } + ++/* we need to get the actual socket in use and from there ++ * read the values in the TCP_INFO struct. ++ * shout out to Rene Pfiffer for the article https://linuxgazette.net/136/pfeiffer.html ++ * for getting me started. This only works on systems that support the tcp_info ++ * struct; linux, freebsd, netbsd as of now. Apple has one but it's completely different ++ * than any of the others. ++ */ ++static int ++server_input_metrics_request(struct ssh *ssh, struct sshbuf **respp) ++{ ++ int success = 0; ++ /* TCP_INFO is defined in metrics.h ++ * if it's not defined then we don't support tcp_info ++ * so just return a failure code */ ++#if !defined TCP_INFO ++ return success; ++#else ++ struct tcp_info tcp_info; ++ struct sshbuf *resp = NULL; ++ int tcp_info_len; ++ /* this is the socket of the current connection */ ++ int sock_in = ssh_packet_get_connection_in(ssh); ++ int r; ++ binn *metricsobj; ++ ++ tcp_info_len = sizeof(tcp_info); /*expect around 330 bytes */ ++ if ((resp = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new"); ++ ++ /* create the binn object to hold the serialized metrics */ ++ metricsobj = binn_object(); ++ if (metricsobj == NULL) { ++ error_f("Could not create metrics object"); ++ goto out; ++ } ++ ++ debug("Stack metrics request for connection %d", sock_in); ++ if ((r = getsockopt(sock_in, IPPROTO_TCP, TCP_INFO, (void *)&tcp_info, ++ (socklen_t *)&tcp_info_len)) != 0) { ++ error_f("Could not read tcp_info from socket"); ++ goto out; ++ } ++ /* write the tcp_info data to the binn object */ ++ metrics_write_binn_object(&tcp_info, metricsobj); ++ if ((r = sshbuf_put_string(resp, binn_ptr(metricsobj), ++ binn_size(metricsobj))) != 0) { ++ error_fr(r, "Failed to build tcp_info object"); ++ goto out; ++ } ++ /* copy the pointer of the response to the passed in response pointer */ ++ *respp = resp; ++ resp = NULL; /* don't free it */ ++ /* everything worked so set success to true */ ++ success = 1; ++out: ++ sshbuf_free(resp); ++ binn_free(metricsobj); ++ return success; ++#endif /* TCP_INFO */ ++} ++ + static int + server_input_hostkeys_prove(struct ssh *ssh, struct sshbuf **respp) + { +@@ -854,6 +930,10 @@ + success = 1; + } else if (strcmp(rtype, "hostkeys-prove-00@openssh.com") == 0) { + success = server_input_hostkeys_prove(ssh, &resp); ++ } else if (strcmp(rtype, "stack-metrics@hpnssh.org") == 0) { ++ /* resp is the response (sshbuf struct) from the function which is ++ * handled below in the want_reply stanza */ ++ success = server_input_metrics_request(ssh, &resp); + } + /* XXX sshpkt_get_end() */ + if (want_reply) { +diff -Nur openssh-9.3p1.orig/session.c openssh-9.3p1/session.c +--- openssh-9.3p1.orig/session.c 2024-07-10 09:07:09.744081887 +0200 ++++ openssh-9.3p1/session.c 2024-07-10 09:09:55.917568452 +0200 +@@ -226,6 +226,7 @@ + goto authsock_err; + + /* Allocate a channel for the authentication agent socket. */ ++ /* this shouldn't matter if its hpn or not - cjr */ + nc = channel_new(ssh, "auth-listener", + SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, + CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, +@@ -2369,7 +2370,8 @@ + channel_set_fds(ssh, s->chanid, + fdout, fdin, fderr, + ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, +- 1, is_tty, CHAN_SES_WINDOW_DEFAULT); ++ 1, is_tty, ++ options.hpn_disabled ? CHAN_SES_WINDOW_DEFAULT : options.hpn_buffer_size); + } + + /* +diff -Nur openssh-9.3p1.orig/sftp.1 openssh-9.3p1/sftp.1 +--- openssh-9.3p1.orig/sftp.1 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/sftp.1 2024-07-10 09:09:55.917568452 +0200 +@@ -299,7 +299,8 @@ + Specify how many requests may be outstanding at any one time. + Increasing this may slightly improve file transfer speed + but will increase memory usage. +-The default is 64 outstanding requests. ++The default is 1024 outstanding requests providing for 32MB ++of outstanding data with a 32KB buffer. + .It Fl r + Recursively copy entire directories when uploading and downloading. + Note that +@@ -328,10 +329,13 @@ + .It Cm nrequests Ns = Ns Ar value + Controls how many concurrent SFTP read or write requests may be in progress + at any point in time during a download or upload. +-By default 64 requests may be active concurrently. ++This value must be between 1 and 8192. ++By default 1024 requests may be active concurrently. + .It Cm buffer Ns = Ns Ar value + Controls the maximum buffer size for a single SFTP read/write operation used + during download or upload. ++This value must be between 1B and 255KB. You may use ++the K unit for the size. E.g. 32768 or 32K. + By default a 32KB buffer is used. + .El + .El +diff -Nur openssh-9.3p1.orig/sftp.c openssh-9.3p1/sftp.c +--- openssh-9.3p1.orig/sftp.c 2024-07-10 09:07:09.794082033 +0200 ++++ openssh-9.3p1/sftp.c 2024-07-10 09:09:55.918568455 +0200 +@@ -2546,23 +2546,28 @@ + replacearg(&args, 0, "%s", ssh_program); + break; + case 'X': +- /* Please keep in sync with ssh.c -X */ ++ /* Please keep in sync with scp.c -X */ + if (strncmp(optarg, "buffer=", 7) == 0) { + r = scan_scaled(optarg + 7, &llv); +- if (r == 0 && (llv <= 0 || llv > 256 * 1024)) { ++ /* don't ask for a buffer larger than the maximum ++ * size that SFTP can handle */ ++ if (r == 0 && (llv <= 0 || llv > (SFTP_MAX_MSG_LENGTH - 1024))) { + r = -1; + errno = EINVAL; + } + if (r == -1) { +- fatal("Invalid buffer size \"%s\": %s", +- optarg + 7, strerror(errno)); ++ fatal("Invalid buffer size. Must be between 1B and 255KB." ++ "\"%s\": %s", optarg + 7, strerror(errno)); + } + copy_buffer_len = (size_t)llv; + } else if (strncmp(optarg, "nrequests=", 10) == 0) { +- llv = strtonum(optarg + 10, 1, 256 * 1024, ++ /* more than 10k to 15k requests starts stalling the connection ++ * 8192 * default buffer size is 256MB of outstanding data. ++ * if users need more then they need to up the buffer size */ ++ llv = strtonum(optarg + 10, 1, 8 * 1024, + &errstr); + if (errstr != NULL) { +- fatal("Invalid number of requests " ++ fatal("Invalid number of requests. Must be between 1 and 8192. " + "\"%s\": %s", optarg + 10, errstr); + } + num_requests = (size_t)llv; +diff -Nur openssh-9.3p1.orig/sftp-client.c openssh-9.3p1/sftp-client.c +--- openssh-9.3p1.orig/sftp-client.c 2024-07-10 09:07:09.724081828 +0200 ++++ openssh-9.3p1/sftp-client.c 2024-07-10 09:09:55.919568458 +0200 +@@ -72,7 +72,8 @@ + #define DEFAULT_COPY_BUFLEN 32768 + + /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */ +-#define DEFAULT_NUM_REQUESTS 64 ++/* 1024 xfer requests gives us 32MB of receive buffer space */ ++#define DEFAULT_NUM_REQUESTS 1024 + + /* Minimum amount of data to read at a time */ + #define MIN_READ_SIZE 512 +diff -Nur openssh-9.3p1.orig/ssh.1 openssh-9.3p1/ssh.1 +--- openssh-9.3p1.orig/ssh.1 2024-07-10 09:07:09.810082080 +0200 ++++ openssh-9.3p1/ssh.1 2024-07-10 09:09:55.919568458 +0200 +@@ -522,6 +522,7 @@ + .It ControlMaster + .It ControlPath + .It ControlPersist ++.It DisableMTAES* + .It DynamicForward + .It EnableSSHKeysign + .It EnableEscapeCommandline +@@ -550,6 +551,9 @@ + .It HostKeyAlgorithms + .It HostKeyAlias + .It Hostname ++.It HPNDisabled* ++.It HPNBufferLimit* ++.It HPNBufferSize* + .It IdentitiesOnly + .It IdentityAgent + .It IdentityFile +@@ -566,7 +570,13 @@ + .It LogVerbose + .It MACs + .It Match ++.It Metrics ++.It MetricsInterval ++.It MetricsPath + .It NoHostAuthenticationForLocalhost ++.It NoneCipherEnabled* ++.It NoneEnabled* ++.It NoneMacEnabled* + .It NumberOfPasswordPrompts + .It PasswordAuthentication + .It PermitLocalCommand +@@ -597,6 +607,8 @@ + .It StrictHostKeyChecking + .It SyslogFacility + .It TCPKeepAlive ++.It TcpRcvBuf* ++.It TcpRcvBufPoll* + .It Tunnel + .It TunnelDevice + .It UpdateHostKeys +@@ -605,6 +617,8 @@ + .It VerifyHostKeyDNS + .It VisualHostKey + .It XAuthLocation ++.Pp ++.It * Hpnssh specific configuration option. + .El + .Pp + .It Fl p Ar port +@@ -1811,3 +1825,7 @@ + created OpenSSH. + Markus Friedl contributed the support for SSH + protocol versions 1.5 and 2.0. ++Chris Rapier, Michael Stevens, Ben Bennet, and Mike Tasota developed ++the HPN extensions at the Pittsburgh Supercomuting Center with grants ++from Cisco, the National Library of Medicine, and the National Science ++Foundation. +diff -Nur openssh-9.3p1.orig/ssh_api.c openssh-9.3p1/ssh_api.c +--- openssh-9.3p1.orig/ssh_api.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/ssh_api.c 2024-07-10 09:09:55.919568458 +0200 +@@ -410,7 +410,7 @@ + char *cp; + int r; + +- if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0) ++ if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_RELEASE)) != 0) + return r; + if ((r = sshbuf_putb(ssh_packet_get_output(ssh), banner)) != 0) + return r; +diff -Nur openssh-9.3p1.orig/sshbuf.c openssh-9.3p1/sshbuf.c +--- openssh-9.3p1.orig/sshbuf.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/sshbuf.c 2024-07-10 09:09:55.920568461 +0200 +@@ -27,6 +27,20 @@ + #define SSHBUF_INTERNAL + #include "sshbuf.h" + #include "misc.h" ++/* #include "log.h" */ ++ ++#define BUF_WATERSHED 256*1024 ++ ++#ifdef SSHBUF_DEBUG ++# define SSHBUF_TELL(what) do { \ ++ printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \ ++ __FILE__, __LINE__, __func__, what, \ ++ buf->size, buf->alloc, buf->off, buf->max_size); \ ++ fflush(stdout); \ ++ } while (0) ++#else ++# define SSHBUF_TELL(what) ++#endif + + #ifdef SSHBUF_DEBUG + # define SSHBUF_TELL(what) do { \ +@@ -45,12 +59,27 @@ + size_t off; /* First available byte is buf->d + buf->off */ + size_t size; /* Last byte is buf->d + buf->size - 1 */ + size_t max_size; /* Maximum size of buffer */ ++ size_t window_max; /* channel window max */ + size_t alloc; /* Total bytes allocated to buf->d */ + int readonly; /* Refers to external, const data */ + u_int refcount; /* Tracks self and number of child buffers */ + struct sshbuf *parent; /* If child, pointer to parent */ ++ char label[MAX_LABEL_LEN]; /* String for buffer label - debugging use */ ++ struct timeval buf_ts; /* creation time of buffer */ + }; + ++void ++sshbuf_relabel(struct sshbuf *buf, const char *label) ++{ ++ if (label != NULL) ++ strncpy(buf->label, label, MAX_LABEL_LEN-1); ++} ++ ++float time_diff(struct timeval *start, struct timeval *end) ++{ ++ return (end->tv_sec - start->tv_sec) + 1e-6*(end->tv_usec - start->tv_usec); ++} ++ + static inline int + sshbuf_check_sanity(const struct sshbuf *buf) + { +@@ -89,7 +118,7 @@ + } + + struct sshbuf * +-sshbuf_new(void) ++sshbuf_new_label (const char *label) + { + struct sshbuf *ret; + +@@ -100,6 +129,8 @@ + ret->readonly = 0; + ret->refcount = 1; + ret->parent = NULL; ++ if (label != NULL) ++ strncpy(ret->label, label, MAX_LABEL_LEN-1); + if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { + free(ret); + return NULL; +@@ -218,7 +249,7 @@ + size_t + sshbuf_max_size(const struct sshbuf *buf) + { +- return buf->max_size; ++ return buf->max_size / 2; + } + + size_t +@@ -240,13 +271,18 @@ + } + + int +-sshbuf_set_max_size(struct sshbuf *buf, size_t max_size) ++sshbuf_set_max_size(struct sshbuf *buf, size_t requested_size) + { +- size_t rlen; ++ size_t rlen, max_size = requested_size * 2; + u_char *dp; + int r; + +- SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); ++ /* ++ * Note that we set the actual allocation limit to be 2x the requested ++ * size. This is to avoid some pathological compaction behaviour later ++ * when a buffer is at capacity and has small drains/fills on it. ++ */ ++ SSHBUF_DBG(("set max buf = %p requested = %zu", buf, requested_size)); + if ((r = sshbuf_check_sanity(buf)) != 0) + return r; + if (max_size == buf->max_size) +@@ -255,9 +291,17 @@ + return SSH_ERR_BUFFER_READ_ONLY; + if (max_size > SSHBUF_SIZE_MAX) + return SSH_ERR_NO_BUFFER_SPACE; +- /* pack and realloc if necessary */ +- sshbuf_maybe_pack(buf, max_size < buf->size); +- if (max_size < buf->alloc && max_size > buf->size) { ++ /* ++ * Always pack as it makes everything that follows easier. ++ * Potentially expensive, but this should seldom be called on buffers ++ * that already contain data. ++ */ ++ sshbuf_maybe_pack(buf, 1); ++ /* Refuse setting a maximum below current amount of data in buffer */ ++ if (requested_size < buf->size) ++ return SSH_ERR_NO_BUFFER_SPACE; ++ /* Shrink alloc if the existing allocation is larger than requested */ ++ if (requested_size < buf->alloc) { + if (buf->size < SSHBUF_SIZE_INIT) + rlen = SSHBUF_SIZE_INIT; + else +@@ -271,8 +315,6 @@ + buf->alloc = rlen; + } + SSHBUF_TELL("new-max"); +- if (max_size < buf->alloc) +- return SSH_ERR_NO_BUFFER_SPACE; + buf->max_size = max_size; + return 0; + } +@@ -290,7 +332,7 @@ + { + if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) + return 0; +- return buf->max_size - (buf->size - buf->off); ++ return (buf->max_size / 2) - (buf->size - buf->off); + } + + const u_char * +@@ -319,12 +361,19 @@ + if (buf->readonly || buf->refcount > 1) + return SSH_ERR_BUFFER_READ_ONLY; + SSHBUF_TELL("check"); +- /* Check that len is reasonable and that max_size + available < len */ +- if (len > buf->max_size || buf->max_size - len < buf->size - buf->off) ++ /* Check that len is reasonable and that max size + available < len */ ++ if (len > (buf->max_size / 2) || ++ (buf->max_size / 2) - len < buf->size - buf->off) + return SSH_ERR_NO_BUFFER_SPACE; + return 0; + } + ++void ++sshbuf_set_window_max(struct sshbuf *buf, size_t len) ++{ ++ buf->window_max = len; ++} ++ + int + sshbuf_allocate(struct sshbuf *buf, size_t len) + { +@@ -350,9 +399,42 @@ + */ + need = len + buf->size - buf->alloc; + rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC); ++ /* With the changes in 8.9 the output buffer end up growing pretty ++ * slowly. It's knows that it needs to grow but it only does so 32K ++ * at a time. This means a lot of calls to realloc and memcpy which ++ * kills performance until the buffer reaches some maximum size. ++ * so we explicitly test for a buffer that's trying to grow and ++ * if it is then we push the growth to whatever the adjusted value of ++ * local_window_max happens to be. This significantly reduces overhead ++ * and improves performance. In this case we look for a buffer that is trying ++ * to grow larger than BUF_WATERSHED (256*1024 taken from PACKET_MAX_SIZE) ++ * and where the local_window_max isn't zero (which is usally in the Channels ++ * struct but we copied it into the shhbuf as window_max). If it is zero or ++ * the buffer is smaller than BUF_WATERSHED we just use the ++ * normal value for need. We also don't want to grow the buffer past ++ * what we need (the size of window_max) so if the current allocation (in ++ * buf->alloc) is greater than window_max we skip it. ++ * ++ * Turns out the extra functions on the following conditional aren't needed ++ * -cjr 04/06/23 ++ */ ++ if (rlen > BUF_WATERSHED) { ++ /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu win_max is %zu max_size is %zu", ++ buf->label, buf, rlen, need, buf->window_max, buf->max_size); */ ++ /* easiest thing to do is grow the nuffer by 4MB each time. It might end ++ * up being somewhat overallocated but works quickly */ ++ need = (4*1024*1024); ++ rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC); ++ /* debug_f ("Post: label: %s, %p, rlen is %zu need is %zu win_max is %zu max_size is %zu", */ ++ /* buf->label, buf, rlen, need, buf->window_max, buf->max_size); */ ++ } + SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); +- if (rlen > buf->max_size) +- rlen = buf->alloc + need; ++ ++ /* rlen might be above the max allocation */ ++ if (rlen > buf->max_size) { ++ rlen = buf->max_size; ++ /* debug_f("set rlen to %zu", buf->max_size);*/ ++ } + SSHBUF_DBG(("adjusted rlen %zu", rlen)); + if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) { + SSHBUF_DBG(("realloc fail")); +diff -Nur openssh-9.3p1.orig/sshbuf.h openssh-9.3p1/sshbuf.h +--- openssh-9.3p1.orig/sshbuf.h 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/sshbuf.h 2024-07-10 09:09:55.920568461 +0200 +@@ -28,10 +28,12 @@ + # endif /* OPENSSL_HAS_ECC */ + #endif /* WITH_OPENSSL */ + +-#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size */ ++#define SSHBUF_SIZE_MAX 0xFFFFFFF /* Hard maximum size 256MB */ + #define SSHBUF_REFS_MAX 0x100000 /* Max child buffers */ + #define SSHBUF_MAX_BIGNUM (16384 / 8) /* Max bignum *bytes* */ + #define SSHBUF_MAX_ECPOINT ((528 * 2 / 8) + 1) /* Max EC point *bytes* */ ++#define MAX_LABEL_LEN 64 /*maximum size of sshbuf label */ ++#define sshbuf_new() sshbuf_new_label(__func__) + + struct sshbuf; + +@@ -39,7 +41,18 @@ + * Create a new sshbuf buffer. + * Returns pointer to buffer on success, or NULL on allocation failure. + */ +-struct sshbuf *sshbuf_new(void); ++/* struct sshbuf *sshbuf_new(void); */ ++ ++/* ++ * Create a new labeled sshbuf buffer. ++ * Returns pointer to buffer on success, or NULL on allocation failure. ++ */ ++struct sshbuf *sshbuf_new_label(const char *); ++ ++/* ++ * relabel the sshbuf struct ++ */ ++void sshbuf_relabel(struct sshbuf *, const char *); + + /* + * Create a new, read-only sshbuf buffer from existing data. +@@ -75,12 +88,13 @@ + void sshbuf_reset(struct sshbuf *buf); + + /* +- * Return the maximum size of buf ++ * Return the maximum usable size of buf + */ + size_t sshbuf_max_size(const struct sshbuf *buf); + + /* +- * Set the maximum size of buf ++ * Set the maximum usable size of buf. Note that the buffer may consume up ++ * to 2x this memory plus bookkeeping overhead. + * Returns 0 on success, or a negative SSH_ERR_* error code on failure. + */ + int sshbuf_set_max_size(struct sshbuf *buf, size_t max_size); +@@ -344,6 +358,9 @@ + ((u_char *)(p))[1] = __v & 0xff; \ + } while (0) + ++ ++void sshbuf_set_window_max(struct sshbuf *buf , size_t len); ++ + /* Internal definitions follow. Exposed for regress tests */ + #ifdef SSHBUF_INTERNAL + +diff -Nur openssh-9.3p1.orig/ssh.c openssh-9.3p1/ssh.c +--- openssh-9.3p1.orig/ssh.c 2024-07-10 09:07:09.811082083 +0200 ++++ openssh-9.3p1/ssh.c 2024-07-10 09:09:55.921568464 +0200 +@@ -1104,6 +1104,10 @@ + break; + case 'T': + options.request_tty = REQUEST_TTY_NO; ++ /* ensure that the user doesn't try to backdoor a */ ++ /* null cipher switch on an interactive session */ ++ /* so explicitly disable it no matter what */ ++ options.none_switch=0; + break; + case 'o': + line = xstrdup(optarg); +@@ -1649,10 +1653,36 @@ + timeout_ms = options.connection_timeout * 1000; + + /* Open a connection to the remote host. */ ++ /* we try initially on the default hpnssh port returned by ++ * default_ssh_port() which now returns HPNSSH_DEFAULT_PORT ++ * if that fails we reset the port to SSH_DEFAULT_PORT ++ * -cjr 8/17/2022 ++ */ ++tryagain: + if (ssh_connect(ssh, host, options.host_arg, addrs, &hostaddr, + options.port, options.connection_attempts, +- &timeout_ms, options.tcp_keep_alive) != 0) ++ &timeout_ms, options.tcp_keep_alive) != 0) { ++ /* could not connect. If the port requested is the same as ++ * hpnssh default port then fallback. Otherwise, exit */ ++ if ((options.port == default_ssh_port()) && options.fallback) { ++ int port = options.fallback_port; ++ options.port = port; ++ fprintf(stderr, "HPNSSH server not available on default port %d\n", ++ default_ssh_port()); ++ if (port == 22) ++ fprintf(stderr, "Falling back to OpenSSH default port %d\n", ++ port); ++ else ++ fprintf(stderr, "Falling back to user defined port %d\n", ++ port); ++ addrs = resolve_host(host, port, 1, ++ cname, sizeof(cname)); ++ goto tryagain; ++ } else { ++ exit(255); ++ } + exit(255); ++ } + + if (addrs != NULL) + freeaddrinfo(addrs); +@@ -2171,6 +2201,82 @@ + NULL, fileno(stdin), command, environ); + } + ++static void ++hpn_options_init(struct ssh *ssh) ++{ ++ /* ++ * We need to check to see if what they want to do about buffer ++ * sizes here. In a hpn to nonhpn connection we want to limit ++ * the window size to something reasonable in case the far side ++ * has the large window bug. In hpn to hpn connection we want to ++ * use the max window size but allow the user to override it ++ * lastly if they disabled hpn then use the ssh std window size. ++ * ++ * So why don't we just do a getsockopt() here and set the ++ * ssh window to that? In the case of a autotuning receive ++ * window the window would get stuck at the initial buffer ++ * size generally less than 96k. Therefore we need to set the ++ * maximum ssh window size to the maximum hpn buffer size ++ * unless the user has specifically set the tcprcvbufpoll ++ * to no. In which case we *can* just set the window to the ++ * minimum of the hpn buffer size and tcp receive buffer size. ++ */ ++ ++ if (tty_flag) ++ options.hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; ++ else ++ options.hpn_buffer_size = 2 * 1024 * 1024; ++ ++ if (ssh->compat & SSH_BUG_LARGEWINDOW) { ++ debug("HPN to Non-HPN connection"); ++ } else { ++ debug("HPN to HPN connection"); ++ if (ssh->compat & SSH_HPNSSH) { ++ debug("Using 'hpn' prefixed binaries"); ++ } ++ int sock, socksize; ++ socklen_t socksizelen; ++ if (options.tcp_rcv_buf_poll <= 0) { ++ sock = socket(AF_INET, SOCK_STREAM, 0); ++ socksizelen = sizeof(socksize); ++ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, ++ &socksize, &socksizelen); ++ close(sock); ++ debug("socksize %d", socksize); ++ options.hpn_buffer_size = socksize; ++ debug("HPNBufferSize set to TCP RWIN: %d", options.hpn_buffer_size); ++ } else { ++ if (options.tcp_rcv_buf > 0) { ++ /* ++ * Create a socket but don't connect it: ++ * we use that the get the rcv socket size ++ */ ++ sock = socket(AF_INET, SOCK_STREAM, 0); ++ /* ++ * If they are using the tcp_rcv_buf option, ++ * attempt to set the buffer size to that. ++ */ ++ if (options.tcp_rcv_buf) { ++ socksizelen = sizeof(options.tcp_rcv_buf); ++ setsockopt(sock, SOL_SOCKET, SO_RCVBUF, ++ &options.tcp_rcv_buf, socksizelen); ++ } ++ socksizelen = sizeof(socksize); ++ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, ++ &socksize, &socksizelen); ++ close(sock); ++ debug("socksize %d", socksize); ++ options.hpn_buffer_size = socksize; ++ debug("HPNBufferSize set to user TCPRcvBuf: %d", options.hpn_buffer_size); ++ } ++ } ++ } ++ ++ debug("Final hpn_buffer_size = %d", options.hpn_buffer_size); ++ ++ channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); ++} ++ + /* open new channel for a session */ + static int + ssh_session2_open(struct ssh *ssh) +@@ -2189,9 +2295,10 @@ + if (in == -1 || out == -1 || err == -1) + fatal("dup() in/out/err failed"); + +- window = CHAN_SES_WINDOW_DEFAULT; ++ window = options.hpn_buffer_size; + packetmax = CHAN_SES_PACKET_DEFAULT; + if (tty_flag) { ++ window = CHAN_SES_WINDOW_DEFAULT; + window >>= 1; + packetmax >>= 1; + } +@@ -2200,6 +2307,16 @@ + window, packetmax, CHAN_EXTENDED_WRITE, + "client-session", CHANNEL_NONBLOCK_STDIO); + ++ /* TODO: Is this the right place for these options? */ ++ if (options.tcp_rcv_buf_poll > 0 && !options.hpn_disabled) { ++ c->dynamic_window = 1; ++ debug("Enabled Dynamic Window Scaling"); ++ } ++ ++ if (options.hpn_buffer_limit) ++ c->hpn_buffer_limit = 1; ++ ++ + debug3_f("channel_new: %d", c->self); + + channel_send_open(ssh, c->self); +@@ -2216,6 +2333,13 @@ + int r, id = -1; + char *cp, *tun_fwd_ifname = NULL; + ++ /* ++ * We need to initialize this early because the forwarding logic below ++ * might open channels that use the hpn buffer sizes. We can't send a ++ * window of -1 (the default) to the server as it breaks things. ++ */ ++ hpn_options_init(ssh); ++ + /* XXX should be pre-session */ + if (!options.control_persist) + ssh_init_stdio_forwarding(ssh); +diff -Nur openssh-9.3p1.orig/ssh_config.5 openssh-9.3p1/ssh_config.5 +--- openssh-9.3p1.orig/ssh_config.5 2024-07-10 09:07:09.811082083 +0200 ++++ openssh-9.3p1/ssh_config.5 2024-07-10 09:09:55.921568464 +0200 +@@ -62,6 +62,19 @@ + .Pq Pa /etc/ssh/ssh_config + .El + .Pp ++.Xr hpnssh 1 ++is fully compatible with OpenSSH's ssh and uses the same configuration format, directives, and options. ++.Xr hpnssh 1 ++can use the same configuration file as ++.Xr ssh 1 ++though the inverse is not necessarily true. This is because ++.Xr hpnssh 1 ++has some additional options which are discussed below. As such users should either not ++use ++.Xr hpnssh 1 ++specific directives in their default ssh_config files or ensure that they always use a ++custom config file. ++.Pp + Unless noted otherwise, for each parameter, the first obtained value + will be used. + The configuration files contain sections separated by +@@ -611,6 +624,11 @@ + then the backgrounded master connection will automatically terminate + after it has remained idle (with no client connections) for the + specified time. ++.It Cm DisableMTAES ++Switch the encryption cipher being used from the multithreaded MT-AES-CTR cipher ++back to the stock single-threaded AES-CTR cipher. This may prove to be more ++effcient in some circumstances. Default is ++.Cm no. HPNSSH only. + .It Cm DynamicForward + Specifies that a TCP port on the local machine be forwarded + over the secure channel, and the application +@@ -1022,6 +1040,28 @@ + .Cm Hostname + specifications). + The default is the name given on the command line. ++.It Cm HPNDisabled ++In some situations, such as transfers on a local area network, the impact ++of the HPN code produces a net decrease in performance. In these cases it is ++helpful to disable the HPN functionality. By default HPNDisabled is set to ++.Cm no. HPNSSH only. ++.It Cm HPNBufferLimit ++This option will force the hpnssh receive buffer to grow more slowly and limits ++the growth to one half of the TCP receive buffer. This option can prove useful ++in situation where a high speed path with larger RTTs are writing to a slower ++device or file system. Enabling this option will reduce performance but may provide ++a more stable connection. The option only impacts the receiving side of the connection. ++For example, a client receiving data from a server but not a client sending data. ++By default this option is set to ++.Cm no. HPNSSH only. ++.It Cm HPNBufferSize ++This is the default buffer size, in kilobytes, the HPN functionality uses when interacting ++with nonHPN SSH installations. Conceptually this is similar to the TcpRcvBuf ++option as applied to the internal SSH flow control. This value can range from ++1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause performance ++problems depending on the length of the network path. The default size of this buffer ++is 2MB. ++.Cm HPNSSH only. + .It Cm IdentitiesOnly + Specifies that + .Xr ssh 1 +@@ -1392,6 +1432,63 @@ + .Pp + The list of available MAC algorithms may also be obtained using + .Qq ssh -Q mac . ++.It Cm Metrics ++Indicate that the ssh client should gather and write TCP stack metrics from the ++TCP_INFO socket structure on compliant operating systems to a text file. ++These metrics will be collected from both the local and remote hosts if metrics ++are supported by the version of SSH and TCP_INFO is supported by the ++operating system. Currently operating system support is limited to Linux ++version 3.7 and greater, NetBSD, and FreeBSD (version 6 or greater). ++ ++If one of the hosts does not support the Metrics option then only data ++from the the host that does will be reported. See the HPN-README file ++for more detail. ++ ++The argument to the keyword must be ++.Cm yes ++or ++.Cm no ++(the default). ++ ++See ++.Cm MetricsInterval ++and ++.Cm MetricsPath ++for more metrics options. ++.Cm HPNSSH only. ++.It Cm MetricsInterval ++The frequency, in seconds, with which the TCP_INFO struct will be polled. The ++default value is 5 seconds. ++ ++.Cm Metrics ++must be set to ++.Cm yes ++fot this option to have any effect. ++.Cm HPNSSH only. ++.It Cm MetricsPath ++The file path where ++.Cm Metrics ++should write the TCP stack metrics. ++ ++The default is ++.Sm off ++.Ar ./ssh_stack_metrics.local ++ and ++.Ar ./ssh_stack_metrics.remote ++.Sm on ++ ++The user may specify any other filepath and filename that they have write ++access to. E.g. MetricsPath=/tmp/my_metrics. The suffix of .local ++and .remote will be appended to this path. Files are ++.Cm not ++overwritten by subsequent metrics sessions. Instead, data from new sessions ++will be appended if the file already exists. ++ ++.Cm Metrics ++must be set to ++.Cm yes ++fot this option to have any effect. ++.Cm HPNSSH only. + .It Cm NoHostAuthenticationForLocalhost + Disable host authentication for localhost (loopback addresses). + The argument to this keyword must be +@@ -1399,6 +1496,36 @@ + or + .Cm no + (the default). ++.It Cm NoneEnabled ++Enable or disable the use of the None cipher. Care must always be used ++when enabling this as it will allow users to send data in the clear. However, ++it is important to note that authentication information remains encrypted ++even if this option is enabled. Default is ++.Cm no. HPNSSH only. ++.It Cm NoneMacEnabled ++Enable or disable the use of the None MAC. When this is enabled ssh ++will *not* provide data integrity of any data being transmitted between hosts. Use ++with caution as it, unlike just using NoneEnabled, doesn't provide data integrity and ++protection against man-in-the-middle attacks. As with NoneEnabled all authentication ++remains encrypted and integrity is ensured. Default is ++.Cm no. HPNSSH only. ++.It Cm NoneSwitch ++Switch the encryption cipher being used to the None cipher after ++authentication takes place. NoneEnabled must be enabled on both the client ++and server side of the connection. When the connection switches to the NONE ++cipher a warning is sent to STDERR. The connection attempt will fail with an ++error if a client requests a NoneSwitch from the server that does not explicitly ++have NoneEnabled set to yes. Note: The NONE cipher cannot be used in ++interactive (shell) sessions and it will fail silently. Set to ++.Cm no ++by default. ++.Cm HPNSSH only. ++.Pp ++.Cm Note: ++this options cannot actualy be set in the ssh_config file. It must be ++set on the command line use -oNoneEnabled. This is to prevent it from being ++used accidentally. It is included here for completeness as neither NoneMacEnabled ++or NoneCipherEnabled have any effect without this option. + .It Cm NumberOfPasswordPrompts + Specifies the number of password prompts before giving up. + The argument to this keyword must be an integer. +@@ -1925,6 +2052,21 @@ + See also + .Cm ServerAliveInterval + for protocol-level keepalives. ++.It Cm TcpRcvBuf ++Set the TCP socket receive buffer to n Kilobytes. It can be set up to the ++maximum socket size allowed by the system. This is useful in situations where ++the tcp receive window is set low but the maximum buffer size is set ++higher (as is typical). This works on a per TCP connection basis. You can also ++use this to artifically limit the transfer rate of the connection. In these ++cases the throughput will be no more than n/RTT. The minimum buffer size is 1KB. ++Default is the current system wide tcp receive buffer size. ++.Cm HPNSSH only. ++.It Cm TcpRcvBufPoll ++Enable of disable the polling of the tcp receive buffer through the life ++of the connection. You would want to make sure that this option is enabled ++for systems making use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista). ++Default is ++.Cm yes. HPNSSH only. + .It Cm Tunnel + Request + .Xr tun 4 +@@ -2267,3 +2409,11 @@ + created OpenSSH. + .An Markus Friedl + contributed the support for SSH protocol versions 1.5 and 2.0. ++.An Chris Rapier, ++.An Michael Stevens, ++.An Ben Bennet, ++and ++.An Mike Tasota ++developed the HPN extensions at the Pittsburgh Supercomuting Center ++with grants from Cisco, the National Library of Medicine, and ++the National Science Foundation. +diff -Nur openssh-9.3p1.orig/sshconnect2.c openssh-9.3p1/sshconnect2.c +--- openssh-9.3p1.orig/sshconnect2.c 2024-07-10 09:07:09.812082086 +0200 ++++ openssh-9.3p1/sshconnect2.c 2024-07-10 09:09:55.922568467 +0200 +@@ -85,6 +85,13 @@ + extern Options options; + + /* ++ * tty_flag is set in ssh.c. Use this in ssh_userauth2: ++ * if it is set, then prevent the switch to the null cipher. ++ */ ++ ++extern int tty_flag; ++ ++/* + * SSH2 key exchange + */ + +@@ -576,6 +583,60 @@ + + if (!authctxt.success) + fatal("Authentication failed."); ++ ++ /* ++ * If the user wants to use the none cipher and/or none mac, do it post authentication ++ * and only if the right conditions are met -- both of the NONE commands ++ * must be true and there must be no tty allocated. ++ */ ++ if (options.none_switch == 1 && options.none_enabled == 1) { ++ char *myproposal[PROPOSAL_MAX]; ++ char *s = NULL; ++ const char *none_cipher = "none"; ++ if (!tty_flag) { /* no null on tty sessions */ ++ debug("Requesting none rekeying..."); ++ kex_proposal_populate_entries(ssh, myproposal, s, none_cipher, ++ options.macs, compression_alg_list(options.compression), ++ options.hostkeyalgorithms); ++ fprintf(stderr, "WARNING: ENABLED NONE CIPHER!!!\n"); ++ ++ /* NONEMAC can only be used in context of the NONE CIPHER */ ++ if (options.nonemac_enabled == 1) { ++ const char *none_mac = "none"; ++ kex_proposal_populate_entries(ssh, myproposal, s, none_cipher, ++ none_mac, compression_alg_list(options.compression), ++ options.hostkeyalgorithms); ++ fprintf(stderr, "WARNING: ENABLED NONE MAC\n"); ++ } ++ kex_prop2buf(ssh->kex->my, myproposal); ++ packet_request_rekeying(); ++ } else { ++ /* requested NONE cipher when in a tty */ ++ debug("Cannot switch to NONE cipher with tty allocated"); ++ fprintf(stderr, "NONE cipher switch disabled when a TTY is allocated\n"); ++ } ++ } ++ ++#ifdef WITH_OPENSSL ++ /* if we are using aes-ctr there can be issues in either a fork or sandbox ++ * so the initial aes-ctr is defined to point to the original single process ++ * evp. After authentication we'll be past the fork and the sandboxed privsep ++ * so we repoint the define to the multithreaded evp. To start the threads we ++ * then force a rekey ++ */ ++ /* We now explicitly call the mt cipher in cipher.c so we don't need ++ * the cipher_reset_multithreaded() anymore. We just need to ++ * force a rekey -cjr 09/08/2022 */ ++ const void *cc = ssh_packet_get_send_context(ssh); ++ /* only do this for the ctr cipher. otherwise gcm mode breaks. */ ++ if (strstr(cipher_ctx_name(cc), "ctr")) { ++ debug("Single to Multithread CTR cipher swap - client request"); ++ /* cipher_reset_multithreaded(); */ ++ ssh_packet_set_authenticated(ssh); ++ packet_request_rekeying(); ++ } ++#endif ++ + if (ssh_packet_connection_is_on_socket(ssh)) { + verbose("Authenticated to %s ([%s]:%d) using \"%s\".", host, + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), +diff -Nur openssh-9.3p1.orig/sshconnect.c openssh-9.3p1/sshconnect.c +--- openssh-9.3p1.orig/sshconnect.c 2024-07-10 09:07:09.685081714 +0200 ++++ openssh-9.3p1/sshconnect.c 2024-07-10 09:09:55.922568467 +0200 +@@ -344,6 +344,30 @@ + #endif + + /* ++ * Set TCP receive buffer if requested. ++ * Note: tuning needs to happen after the socket is ++ * created but before the connection happens ++ * so winscale is negotiated properly -cjr ++ */ ++static void ++ssh_set_socket_recvbuf(int sock) ++{ ++ void *buf = (void *)&options.tcp_rcv_buf; ++ int sz = sizeof(options.tcp_rcv_buf); ++ int socksize; ++ int socksizelen = sizeof(int); ++ ++ debug("setsockopt Attempting to set SO_RCVBUF to %d", options.tcp_rcv_buf); ++ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, buf, sz) >= 0) { ++ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &socksize, &socksizelen); ++ debug("setsockopt SO_RCVBUF: %.100s %d", strerror(errno), socksize); ++ } ++ else ++ error("Couldn't set socket receive buffer to %d: %.100s", ++ options.tcp_rcv_buf, strerror(errno)); ++} ++ ++/* + * Creates a socket for use as the ssh connection. + */ + static int +@@ -365,6 +389,9 @@ + } + (void)fcntl(sock, F_SETFD, FD_CLOEXEC); + ++ if (options.tcp_rcv_buf > 0) ++ ssh_set_socket_recvbuf(sock); ++ + /* Use interactive QOS (if specified) until authentication completed */ + if (options.ip_qos_interactive != INT_MAX) + set_sock_tos(sock, options.ip_qos_interactive); +diff -Nur openssh-9.3p1.orig/sshd.8 openssh-9.3p1/sshd.8 +--- openssh-9.3p1.orig/sshd.8 2024-07-10 09:07:09.812082086 +0200 ++++ openssh-9.3p1/sshd.8 2024-07-10 09:09:55.923568470 +0200 +@@ -1078,3 +1078,7 @@ + protocol versions 1.5 and 2.0. + Niels Provos and Markus Friedl contributed support + for privilege separation. ++Chris Rapier, Michael Stevens, Ben Bennet, and Mike Tasota developed ++the HPN extensions at the Pittsburgh Supercomuting Center with grants ++from Cisco, the National Library of Medicine, and the National Science ++Foundation. +diff -Nur openssh-9.3p1.orig/sshd.c openssh-9.3p1/sshd.c +--- openssh-9.3p1.orig/sshd.c 2024-07-10 09:07:09.813082089 +0200 ++++ openssh-9.3p1/sshd.c 2024-07-10 10:29:13.426499996 +0200 +@@ -1093,6 +1093,8 @@ + int ret, listen_sock; + struct addrinfo *ai; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; ++ int socksize; ++ int socksizelen = sizeof(int); + + for (ai = la->addrs; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) +@@ -1138,6 +1140,11 @@ + + debug("Bind to port %s on %s.", strport, ntop); + ++ getsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, ++ &socksize, &socksizelen); ++ debug("Server TCP RWIN socket size: %d", socksize); ++ debug("HPN Buffer Size: %d", options.hpn_buffer_size); ++ + /* Bind the socket to the desired port. */ + if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) == -1) { + error("Bind to port %s on %s failed: %.200s.", +@@ -1793,8 +1800,8 @@ + free(line); + break; + case 'V': +- fprintf(stderr, "%s, %s\n", +- SSH_VERSION, SSH_OPENSSL_VERSION); ++ fprintf(stderr, "%s%s, %s\n", ++ SSH_VERSION, SSH_HPN, SSH_OPENSSL_VERSION); + exit(0); + default: + usage(); +@@ -1876,6 +1883,19 @@ + /* Fill in default values for those options not explicitly set. */ + fill_default_server_options(&options); + ++ if (options.none_enabled == 1) { ++ char *old_ciphers = options.ciphers; ++ xasprintf(&options.ciphers, "%s,none", old_ciphers); ++ free(old_ciphers); ++ ++ /* only enable the none MAC in context of the none cipher -cjr */ ++ if (options.nonemac_enabled == 1) { ++ char *old_macs = options.macs; ++ xasprintf(&options.macs, "%s,none", old_macs); ++ free(old_macs); ++ } ++ } ++ + /* Check that options are sensible */ + if (options.authorized_keys_command_user == NULL && + (options.authorized_keys_command != NULL && +@@ -2359,6 +2379,9 @@ + rdomain == NULL ? "" : "\""); + free(laddr); + ++ /* set the HPN options for the child */ ++ channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); ++ + /* + * We don't want to listen forever unless the other side + * successfully authenticates itself. So we set up an alarm which is +@@ -2471,6 +2494,27 @@ + /* Try to send all our hostkeys to the client */ + notify_hostkeys(ssh); + ++#ifdef WITH_OPENSSL ++ /* if we are using aes-ctr there can be issues in either a fork or sandbox ++ * so the initial aes-ctr is defined to point to the original single process ++ * evp. After authentication we'll be past the fork and the sandboxed privsep ++ * so we repoint the define to the multithreaded evp. To start the threads we ++ * then force a rekey ++ */ ++ /* We now explicitly call the mt cipher in cipher.c so we don't need ++ * the cipher_reset_multithreaded() anymore. We just need to ++ * force a rekey -cjr 09/08/2022 */ ++ const void *cc = ssh_packet_get_send_context(the_active_state); ++ ++ /* only rekey if necessary. If we don't do this gcm mode cipher breaks */ ++ if (strstr(cipher_ctx_name(cc), "ctr")) { ++ debug("Single to Multithreaded CTR cipher swap - server request"); ++ /* cipher_reset_multithreaded(); */ ++ ssh_packet_set_authenticated(ssh); ++ packet_request_rekeying(); ++ } ++#endif ++ + /* Start session. */ + do_authenticated(ssh, authctxt); + +@@ -2545,6 +2589,11 @@ + struct kex *kex; + int r; + ++ if (options.none_enabled == 1) ++ debug("WARNING: None cipher enabled"); ++ if (options.nonemac_enabled == 1) ++ debug("WARNING: None MAC enabled"); ++ + if (options.rekey_limit || options.rekey_interval) + ssh_packet_set_rekey_limits(ssh, options.rekey_limit, + options.rekey_interval); +diff -Nur openssh-9.3p1.orig/sshd_config openssh-9.3p1/sshd_config +--- openssh-9.3p1.orig/sshd_config 2024-07-10 09:07:09.813082089 +0200 ++++ openssh-9.3p1/sshd_config 2024-07-10 09:09:55.923568470 +0200 +@@ -19,6 +19,7 @@ + # semanage port -a -t ssh_port_t -p tcp #PORTNUMBER + # + #Port 22 ++Port 2222 + #AddressFamily any + #ListenAddress 0.0.0.0 + #ListenAddress :: +@@ -127,6 +128,27 @@ + # override default of no subsystems + Subsystem sftp /usr/libexec/sftp-server + ++# the following are HPN related configuration options ++# tcp receive buffer polling. disable in non autotuning kernels ++#TcpRcvBufPoll yes ++ ++# disable hpn performance boosts ++#HPNDisabled no ++ ++# buffer size for hpn to non-hpn connections ++#HPNBufferSize 2048 ++ ++# allow the use of the none cipher ++#NoneEnabled no ++ ++# allow the use of the none MAC ++#NoneMacEnabled no ++ ++# clamp the server's receive buffer to ++# 1/2 the tcp receive window. This may be useful ++# in some environments. ++#HPNBufferLimit no ++ + # Example of overriding settings on a per-user basis + #Match User anoncvs + # X11Forwarding no +diff -Nur openssh-9.3p1.orig/sshd_config.5 openssh-9.3p1/sshd_config.5 +--- openssh-9.3p1.orig/sshd_config.5 2024-07-10 09:07:09.814082092 +0200 ++++ openssh-9.3p1/sshd_config.5 2024-07-10 09:09:55.924568473 +0200 +@@ -56,6 +56,16 @@ + .Pq \&" + in order to represent arguments containing spaces. + .Pp ++.Xr hpnsshd 8 ++is fully compatible with OpenSSH's sshd and uses the same configuration format, directives, and options. ++.Xr hpnsshd 8 ++can use the same configuration file as ++.Xr sshd 8 ++though the inverse is not necessarily true. This is because ++.Xr hpnssh 8 ++has some additional options which are discussed below. As such administrators should maintain ++seperate configuration files for each. ++.Pp + The possible + keywords and their meanings are as follows (note that + keywords are case-insensitive and arguments are case-sensitive): +@@ -659,6 +669,10 @@ + TCP and StreamLocal. + This option overrides all other forwarding-related options and may + simplify restricted configurations. ++.It Cm DisableMTAES ++Switch the encryption cipher being used from the multithreaded MT-AES-CTR cipher ++back to the stock single-threaded AES-CTR cipher. Default is ++.Cm no. HPNSSH only. + .It Cm ExposeAuthInfo + Writes a temporary file containing a list of authentication methods and + public credentials (e.g. keys) used to authenticate the user. +@@ -892,6 +906,28 @@ + The default for this option is: + The list of available signature algorithms may also be obtained using + .Qq ssh -Q HostKeyAlgorithms . ++.It Cm HPNDisabled ++In some situations, such as transfers on a local area network, the impact ++of the HPN code produces a net decrease in performance. In these cases it is ++helpful to disable the HPN functionality. By default HPNDisabled is set to ++.CM no. ++.It Cm HPNBufferLimit ++This option will force the hpnssh receive buffer to grow more slowly and limits ++the growth to one half of the TCP receive buffer. This option can prove useful ++in situation where a high speed path with larger RTTs are writing to a slower ++device or file system. Enabling this option will reduce performance but may provide ++a more stable connection. The option only impacts the receiving side of the connection. ++For example, a client receiving data from a server but not a client sending data. If ++enabled on a server this will impact all incoming connections. ++By default this option is set to ++.Cm no. HPNSSH only. ++.It Cm HPNBufferSize ++This is the default buffer size, in kilobytes, that HPN functionality uses when interacting ++with nonHPN SSH installations. Conceptually this is similar to the TcpRcvBuf ++option as applied to the internal SSH flow control. This value can range from ++1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause performance ++problems depending on the length of the network path. The default size of this buffer ++is 2MB. + .It Cm IgnoreRhosts + Specifies whether to ignore per-user + .Pa .rhosts +@@ -1393,6 +1429,19 @@ + key exchange methods. + The default is + .Pa /etc/moduli . ++.It Cm NoneEnabled ++Enable or disable the use of the None cipher. Care must always be used ++when enabling this as it will allow users to send data in the clear. However, ++it is important to note that authentication information remains encrypted ++even if this option is enabled. Default is ++.Cm no ++.It Cm NoneMacEnabled ++Enable or disable the use of the None MAC. When this is enabled ssh ++will *not* provide data integrity of any data being transmitted between hosts. Use ++with caution as it, unlike just using NoneEnabled, doesn't provide data integrity and ++protection against man-in-the-middle attacks. As with NoneEnabled all authentication ++remains encrypted and integrity is ensured. Default is ++.Cm no. + .It Cm PasswordAuthentication + Specifies whether password authentication is allowed. + The default is +@@ -1835,6 +1884,21 @@ + .Pp + To disable TCP keepalive messages, the value should be set to + .Cm no . ++.It Cm TcpRcvBuf ++Set the TCP socket receive buffer to n Kilobytes. It can be set up to the ++maximum socket size allowed by the system. This is useful in situations where ++the tcp receive window is set low but the maximum buffer size is set ++higher (as is typical). This works on a per TCP connection basis. You can also ++use this to artifically limit the transfer rate of the connection. In these ++cases the throughput will be no more than n/RTT KB/s. The minimum buffer size is 1KB. ++Default is the current system wide tcp receive buffer size. ++.IT Cm TcpRcvBufPoll ++Enable of disable the polling of the tcp receive buffer throughout the life ++of the connection. Make sure that this option is enabled for systems making ++use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista) in order to ++maximize throughput on high performance networks. ++Default is ++.Cm yes. + .It Cm TrustedUserCAKeys + Specifies a file containing public keys of certificate authorities that are + trusted to sign user certificates for authentication, or +@@ -2129,3 +2193,11 @@ + and + .An Markus Friedl + contributed support for privilege separation. ++.An Chris Rapier, ++.An Michael Stevens, ++.An Ben Bennet, ++and ++.An Mike Tasota ++developed the HPN extensions at the Pittsburgh Supercomuting Center ++with grants from Cisco, the National Library of Medicine, and ++the National Science Foundation. +diff -Nur openssh-9.3p1.orig/ssh.h openssh-9.3p1/ssh.h +--- openssh-9.3p1.orig/ssh.h 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/ssh.h 2024-07-10 09:09:55.924568473 +0200 +@@ -17,6 +17,7 @@ + + /* Default port number. */ + #define SSH_DEFAULT_PORT 22 ++#define HPNSSH_DEFAULT_PORT 2222 + + /* + * Maximum number of certificate files that can be specified +@@ -48,7 +49,7 @@ + * Name for the service. The port named by this service overrides the + * default port if present. + */ +-#define SSH_SERVICE_NAME "ssh" ++#define SSH_SERVICE_NAME "hpnssh" + + /* + * Name of the environment variable containing the process ID of the +diff -Nur openssh-9.3p1.orig/sshkey.c openssh-9.3p1/sshkey.c +--- openssh-9.3p1.orig/sshkey.c 2024-07-10 09:07:09.754081916 +0200 ++++ openssh-9.3p1/sshkey.c 2024-07-10 09:09:55.925568475 +0200 +@@ -1776,7 +1776,7 @@ + stderr); + #endif + if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher), +- keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 1)) != 0) ++ keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 1, 0)) != 0) + goto out; + + /* Serialise and encrypt the private key using the ephemeral key */ +@@ -1911,7 +1911,7 @@ + keyiv, SSH_DIGEST_MAX_LENGTH)) != 0) + goto out; + if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher), +- keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 0)) != 0) ++ keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 0, 0)) != 0) + goto out; + #ifdef DEBUG_PK + fprintf(stderr, "%s: key+iv\n", __func__); +@@ -3015,7 +3015,7 @@ + goto out; + } + if ((r = cipher_init(&ciphercontext, cipher, key, keylen, +- key + keylen, ivlen, 1)) != 0) ++ key + keylen, ivlen, 1, 0)) != 0) + goto out; + + if ((r = sshbuf_put(encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC))) != 0 || +@@ -3259,7 +3259,7 @@ + /* decrypt private portion of key */ + if ((r = sshbuf_reserve(decrypted, encrypted_len, &dp)) != 0 || + (r = cipher_init(&ciphercontext, cipher, key, keylen, +- key + keylen, ivlen, 0)) != 0) ++ key + keylen, ivlen, 0, 0)) != 0) + goto out; + if ((r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(decoded), + encrypted_len, 0, authlen)) != 0) { +diff -Nur openssh-9.3p1.orig/sshkey-xmss.c openssh-9.3p1/sshkey-xmss.c +--- openssh-9.3p1.orig/sshkey-xmss.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/sshkey-xmss.c 2024-07-10 09:09:55.925568475 +0200 +@@ -956,7 +956,7 @@ + if ((r = sshbuf_reserve(encrypted, + encrypted_len + aadlen + authlen, &cp)) != 0 || + (r = cipher_init(&ciphercontext, cipher, key, keylen, +- iv, ivlen, 1)) != 0 || ++ iv, ivlen, 1, 0)) != 0 || + (r = cipher_crypt(ciphercontext, 0, cp, sshbuf_ptr(encoded), + encrypted_len, aadlen, authlen)) != 0) + goto out; +@@ -1049,7 +1049,7 @@ + /* decrypt private state of key */ + if ((r = sshbuf_reserve(decrypted, aadlen + encrypted_len, &dp)) != 0 || + (r = cipher_init(&ciphercontext, cipher, key, keylen, +- iv, ivlen, 0)) != 0 || ++ iv, ivlen, 0, 0)) != 0 || + (r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(copy), + encrypted_len, aadlen, authlen)) != 0) + goto out; +diff -Nur openssh-9.3p1.orig/umac.c openssh-9.3p1/umac.c +--- openssh-9.3p1.orig/umac.c 2023-03-15 22:28:19.000000000 +0100 ++++ openssh-9.3p1/umac.c 2024-07-10 09:09:55.926568478 +0200 +@@ -134,15 +134,48 @@ + /* --- Endian Conversion --- Forcing assembly on some platforms */ + /* ---------------------------------------------------------------------- */ + ++ ++/* Using local statically defined versions of the get/put functions ++ * found in misc.c allows them to be inlined. This improves throughput ++ * performance by 10% to 15% on well connected (10Gb/s+) systems. ++ * Chris Rapier 2022-03-09 */ ++ ++static __attribute__((__bounded__(__minbytes__, 1, 4))) ++u_int32_t umac_get_u32_le(const void *vp) ++{ ++ const u_char *p = (const u_char *)vp; ++ u_int32_t v; ++ ++ v = (u_int32_t)p[0]; ++ v |= (u_int32_t)p[1] << 8; ++ v |= (u_int32_t)p[2] << 16; ++ v |= (u_int32_t)p[3] << 24; ++ ++ return (v); ++} ++ ++#if 0 /* compile time warning thrown otherwise */ ++static __attribute__((__bounded__(__minbytes__, 1, 4))); ++void umac_put_u32_le(void *vp, u_int32_t v) ++{ ++ u_char *p = (u_char *)vp; ++ ++ p[0] = (u_char)v & 0xff; ++ p[1] = (u_char)(v >> 8) & 0xff; ++ p[2] = (u_char)(v >> 16) & 0xff; ++ p[3] = (u_char)(v >> 24) & 0xff; ++} ++#endif ++ + #if (__LITTLE_ENDIAN__) + #define LOAD_UINT32_REVERSED(p) get_u32(p) + #define STORE_UINT32_REVERSED(p,v) put_u32(p,v) + #else +-#define LOAD_UINT32_REVERSED(p) get_u32_le(p) +-#define STORE_UINT32_REVERSED(p,v) put_u32_le(p,v) ++#define LOAD_UINT32_REVERSED(p) umac_get_u32_le(p) ++#define STORE_UINT32_REVERSED(p,v) umac_put_u32_le(p,v) + #endif + +-#define LOAD_UINT32_LITTLE(p) (get_u32_le(p)) ++#define LOAD_UINT32_LITTLE(p) (umac_get_u32_le(p)) + #define STORE_UINT32_BIG(p,v) put_u32(p, v) + + /* ---------------------------------------------------------------------- */ +diff -Nur openssh-9.3p1.orig/uthash.h openssh-9.3p1/uthash.h +--- openssh-9.3p1.orig/uthash.h 1970-01-01 01:00:00.000000000 +0100 ++++ openssh-9.3p1/uthash.h 2024-07-10 09:09:55.926568478 +0200 +@@ -0,0 +1,1140 @@ ++/* ++Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ ++All rights reserved. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions are met: ++ ++ * Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER ++OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++*/ ++ ++#ifndef UTHASH_H ++#define UTHASH_H ++ ++#define UTHASH_VERSION 2.3.0 ++ ++#include /* memcmp, memset, strlen */ ++#include /* ptrdiff_t */ ++#include /* exit */ ++ ++#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT ++/* This codepath is provided for backward compatibility, but I plan to remove it. */ ++#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" ++typedef unsigned int uint32_t; ++typedef unsigned char uint8_t; ++#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT ++#else ++#include /* uint8_t, uint32_t */ ++#endif ++ ++/* These macros use decltype or the earlier __typeof GNU extension. ++ As decltype is only available in newer compilers (VS2010 or gcc 4.3+ ++ when compiling c++ source) this code uses whatever method is needed ++ or, for VS2008 where neither is available, uses casting workarounds. */ ++#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) ++#if defined(_MSC_VER) /* MS compiler */ ++#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ ++#define DECLTYPE(x) (decltype(x)) ++#else /* VS2008 or older (or VS2010 in C mode) */ ++#define NO_DECLTYPE ++#endif ++#elif defined(__MCST__) /* Elbrus C Compiler */ ++#define DECLTYPE(x) (__typeof(x)) ++#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) ++#define NO_DECLTYPE ++#else /* GNU, Sun and other compilers */ ++#define DECLTYPE(x) (__typeof(x)) ++#endif ++#endif ++ ++#ifdef NO_DECLTYPE ++#define DECLTYPE(x) ++#define DECLTYPE_ASSIGN(dst,src) \ ++do { \ ++ char **_da_dst = (char**)(&(dst)); \ ++ *_da_dst = (char*)(src); \ ++} while (0) ++#else ++#define DECLTYPE_ASSIGN(dst,src) \ ++do { \ ++ (dst) = DECLTYPE(dst)(src); \ ++} while (0) ++#endif ++ ++#ifndef uthash_malloc ++#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ ++#endif ++#ifndef uthash_free ++#define uthash_free(ptr,sz) free(ptr) /* free fcn */ ++#endif ++#ifndef uthash_bzero ++#define uthash_bzero(a,n) memset(a,'\0',n) ++#endif ++#ifndef uthash_strlen ++#define uthash_strlen(s) strlen(s) ++#endif ++ ++#ifndef HASH_FUNCTION ++#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) ++#endif ++ ++#ifndef HASH_KEYCMP ++#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) ++#endif ++ ++#ifndef uthash_noexpand_fyi ++#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ ++#endif ++#ifndef uthash_expand_fyi ++#define uthash_expand_fyi(tbl) /* can be defined to log expands */ ++#endif ++ ++#ifndef HASH_NONFATAL_OOM ++#define HASH_NONFATAL_OOM 0 ++#endif ++ ++#if HASH_NONFATAL_OOM ++/* malloc failures can be recovered from */ ++ ++#ifndef uthash_nonfatal_oom ++#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ ++#endif ++ ++#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) ++#define IF_HASH_NONFATAL_OOM(x) x ++ ++#else ++/* malloc failures result in lost memory, hash tables are unusable */ ++ ++#ifndef uthash_fatal ++#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ ++#endif ++ ++#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") ++#define IF_HASH_NONFATAL_OOM(x) ++ ++#endif ++ ++/* initial number of buckets */ ++#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ ++#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ ++#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ ++ ++/* calculate the element whose hash handle address is hhp */ ++#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) ++/* calculate the hash handle from element address elp */ ++#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) ++ ++#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ ++do { \ ++ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ ++ unsigned _hd_bkt; \ ++ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ ++ (head)->hh.tbl->buckets[_hd_bkt].count++; \ ++ _hd_hh_item->hh_next = NULL; \ ++ _hd_hh_item->hh_prev = NULL; \ ++} while (0) ++ ++#define HASH_VALUE(keyptr,keylen,hashv) \ ++do { \ ++ HASH_FUNCTION(keyptr, keylen, hashv); \ ++} while (0) ++ ++#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ ++do { \ ++ (out) = NULL; \ ++ if (head) { \ ++ unsigned _hf_bkt; \ ++ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ ++ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ ++ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ ++ } \ ++ } \ ++} while (0) ++ ++#define HASH_FIND(hh,head,keyptr,keylen,out) \ ++do { \ ++ (out) = NULL; \ ++ if (head) { \ ++ unsigned _hf_hashv; \ ++ HASH_VALUE(keyptr, keylen, _hf_hashv); \ ++ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ ++ } \ ++} while (0) ++ ++#ifdef HASH_BLOOM ++#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) ++#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) ++#define HASH_BLOOM_MAKE(tbl,oomed) \ ++do { \ ++ (tbl)->bloom_nbits = HASH_BLOOM; \ ++ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ ++ if (!(tbl)->bloom_bv) { \ ++ HASH_RECORD_OOM(oomed); \ ++ } else { \ ++ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ ++ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ ++ } \ ++} while (0) ++ ++#define HASH_BLOOM_FREE(tbl) \ ++do { \ ++ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ ++} while (0) ++ ++#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) ++#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) ++ ++#define HASH_BLOOM_ADD(tbl,hashv) \ ++ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) ++ ++#define HASH_BLOOM_TEST(tbl,hashv) \ ++ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) ++ ++#else ++#define HASH_BLOOM_MAKE(tbl,oomed) ++#define HASH_BLOOM_FREE(tbl) ++#define HASH_BLOOM_ADD(tbl,hashv) ++#define HASH_BLOOM_TEST(tbl,hashv) (1) ++#define HASH_BLOOM_BYTELEN 0U ++#endif ++ ++#define HASH_MAKE_TABLE(hh,head,oomed) \ ++do { \ ++ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ ++ if (!(head)->hh.tbl) { \ ++ HASH_RECORD_OOM(oomed); \ ++ } else { \ ++ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ ++ (head)->hh.tbl->tail = &((head)->hh); \ ++ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ ++ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ ++ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ ++ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ ++ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ ++ (head)->hh.tbl->signature = HASH_SIGNATURE; \ ++ if (!(head)->hh.tbl->buckets) { \ ++ HASH_RECORD_OOM(oomed); \ ++ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ ++ } else { \ ++ uthash_bzero((head)->hh.tbl->buckets, \ ++ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ ++ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ ++ IF_HASH_NONFATAL_OOM( \ ++ if (oomed) { \ ++ uthash_free((head)->hh.tbl->buckets, \ ++ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ ++ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ ++ } \ ++ ) \ ++ } \ ++ } \ ++} while (0) ++ ++#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ ++do { \ ++ (replaced) = NULL; \ ++ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ ++ if (replaced) { \ ++ HASH_DELETE(hh, head, replaced); \ ++ } \ ++ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ ++} while (0) ++ ++#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ ++do { \ ++ (replaced) = NULL; \ ++ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ ++ if (replaced) { \ ++ HASH_DELETE(hh, head, replaced); \ ++ } \ ++ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ ++} while (0) ++ ++#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ ++do { \ ++ unsigned _hr_hashv; \ ++ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ ++ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ ++} while (0) ++ ++#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ ++do { \ ++ unsigned _hr_hashv; \ ++ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ ++ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ ++} while (0) ++ ++#define HASH_APPEND_LIST(hh, head, add) \ ++do { \ ++ (add)->hh.next = NULL; \ ++ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ ++ (head)->hh.tbl->tail->next = (add); \ ++ (head)->hh.tbl->tail = &((add)->hh); \ ++} while (0) ++ ++#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ ++do { \ ++ do { \ ++ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ ++ break; \ ++ } \ ++ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ ++} while (0) ++ ++#ifdef NO_DECLTYPE ++#undef HASH_AKBI_INNER_LOOP ++#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ ++do { \ ++ char *_hs_saved_head = (char*)(head); \ ++ do { \ ++ DECLTYPE_ASSIGN(head, _hs_iter); \ ++ if (cmpfcn(head, add) > 0) { \ ++ DECLTYPE_ASSIGN(head, _hs_saved_head); \ ++ break; \ ++ } \ ++ DECLTYPE_ASSIGN(head, _hs_saved_head); \ ++ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ ++} while (0) ++#endif ++ ++#if HASH_NONFATAL_OOM ++ ++#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ ++do { \ ++ if (!(oomed)) { \ ++ unsigned _ha_bkt; \ ++ (head)->hh.tbl->num_items++; \ ++ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ ++ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ ++ if (oomed) { \ ++ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ ++ HASH_DELETE_HH(hh, head, &(add)->hh); \ ++ (add)->hh.tbl = NULL; \ ++ uthash_nonfatal_oom(add); \ ++ } else { \ ++ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ ++ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ ++ } \ ++ } else { \ ++ (add)->hh.tbl = NULL; \ ++ uthash_nonfatal_oom(add); \ ++ } \ ++} while (0) ++ ++#else ++ ++#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ ++do { \ ++ unsigned _ha_bkt; \ ++ (head)->hh.tbl->num_items++; \ ++ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ ++ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ ++ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ ++ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ ++} while (0) ++ ++#endif ++ ++ ++#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ ++do { \ ++ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ ++ (add)->hh.hashv = (hashval); \ ++ (add)->hh.key = (char*) (keyptr); \ ++ (add)->hh.keylen = (unsigned) (keylen_in); \ ++ if (!(head)) { \ ++ (add)->hh.next = NULL; \ ++ (add)->hh.prev = NULL; \ ++ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ ++ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ ++ (head) = (add); \ ++ IF_HASH_NONFATAL_OOM( } ) \ ++ } else { \ ++ void *_hs_iter = (head); \ ++ (add)->hh.tbl = (head)->hh.tbl; \ ++ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ ++ if (_hs_iter) { \ ++ (add)->hh.next = _hs_iter; \ ++ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ ++ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ ++ } else { \ ++ (head) = (add); \ ++ } \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ ++ } else { \ ++ HASH_APPEND_LIST(hh, head, add); \ ++ } \ ++ } \ ++ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ ++ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ ++} while (0) ++ ++#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ ++do { \ ++ unsigned _hs_hashv; \ ++ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ ++ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ ++} while (0) ++ ++#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ ++ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) ++ ++#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ ++ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) ++ ++#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ ++do { \ ++ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ ++ (add)->hh.hashv = (hashval); \ ++ (add)->hh.key = (const void*) (keyptr); \ ++ (add)->hh.keylen = (unsigned) (keylen_in); \ ++ if (!(head)) { \ ++ (add)->hh.next = NULL; \ ++ (add)->hh.prev = NULL; \ ++ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ ++ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ ++ (head) = (add); \ ++ IF_HASH_NONFATAL_OOM( } ) \ ++ } else { \ ++ (add)->hh.tbl = (head)->hh.tbl; \ ++ HASH_APPEND_LIST(hh, head, add); \ ++ } \ ++ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ ++ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ ++} while (0) ++ ++#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ ++do { \ ++ unsigned _ha_hashv; \ ++ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ ++ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ ++} while (0) ++ ++#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ ++ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) ++ ++#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ ++ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) ++ ++#define HASH_TO_BKT(hashv,num_bkts,bkt) \ ++do { \ ++ bkt = ((hashv) & ((num_bkts) - 1U)); \ ++} while (0) ++ ++/* delete "delptr" from the hash table. ++ * "the usual" patch-up process for the app-order doubly-linked-list. ++ * The use of _hd_hh_del below deserves special explanation. ++ * These used to be expressed using (delptr) but that led to a bug ++ * if someone used the same symbol for the head and deletee, like ++ * HASH_DELETE(hh,users,users); ++ * We want that to work, but by changing the head (users) below ++ * we were forfeiting our ability to further refer to the deletee (users) ++ * in the patch-up process. Solution: use scratch space to ++ * copy the deletee pointer, then the latter references are via that ++ * scratch pointer rather than through the repointed (users) symbol. ++ */ ++#define HASH_DELETE(hh,head,delptr) \ ++ HASH_DELETE_HH(hh, head, &(delptr)->hh) ++ ++#define HASH_DELETE_HH(hh,head,delptrhh) \ ++do { \ ++ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ ++ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ ++ HASH_BLOOM_FREE((head)->hh.tbl); \ ++ uthash_free((head)->hh.tbl->buckets, \ ++ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ ++ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ ++ (head) = NULL; \ ++ } else { \ ++ unsigned _hd_bkt; \ ++ if (_hd_hh_del == (head)->hh.tbl->tail) { \ ++ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ ++ } \ ++ if (_hd_hh_del->prev != NULL) { \ ++ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ ++ } else { \ ++ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ ++ } \ ++ if (_hd_hh_del->next != NULL) { \ ++ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ ++ } \ ++ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ ++ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ ++ (head)->hh.tbl->num_items--; \ ++ } \ ++ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ ++} while (0) ++ ++/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ ++#define HASH_FIND_STR(head,findstr,out) \ ++do { \ ++ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ ++ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ ++} while (0) ++#define HASH_ADD_STR(head,strfield,add) \ ++do { \ ++ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ ++ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ ++} while (0) ++#define HASH_REPLACE_STR(head,strfield,add,replaced) \ ++do { \ ++ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ ++ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ ++} while (0) ++#define HASH_FIND_INT(head,findint,out) \ ++ HASH_FIND(hh,head,findint,sizeof(int),out) ++#define HASH_ADD_INT(head,intfield,add) \ ++ HASH_ADD(hh,head,intfield,sizeof(int),add) ++#define HASH_REPLACE_INT(head,intfield,add,replaced) \ ++ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) ++#define HASH_FIND_PTR(head,findptr,out) \ ++ HASH_FIND(hh,head,findptr,sizeof(void *),out) ++#define HASH_ADD_PTR(head,ptrfield,add) \ ++ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) ++#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ ++ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) ++#define HASH_DEL(head,delptr) \ ++ HASH_DELETE(hh,head,delptr) ++ ++/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. ++ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. ++ */ ++#ifdef HASH_DEBUG ++#include /* fprintf, stderr */ ++#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) ++#define HASH_FSCK(hh,head,where) \ ++do { \ ++ struct UT_hash_handle *_thh; \ ++ if (head) { \ ++ unsigned _bkt_i; \ ++ unsigned _count = 0; \ ++ char *_prev; \ ++ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ ++ unsigned _bkt_count = 0; \ ++ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ ++ _prev = NULL; \ ++ while (_thh) { \ ++ if (_prev != (char*)(_thh->hh_prev)) { \ ++ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ ++ (where), (void*)_thh->hh_prev, (void*)_prev); \ ++ } \ ++ _bkt_count++; \ ++ _prev = (char*)(_thh); \ ++ _thh = _thh->hh_next; \ ++ } \ ++ _count += _bkt_count; \ ++ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ ++ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ ++ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ ++ } \ ++ } \ ++ if (_count != (head)->hh.tbl->num_items) { \ ++ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ ++ (where), (head)->hh.tbl->num_items, _count); \ ++ } \ ++ _count = 0; \ ++ _prev = NULL; \ ++ _thh = &(head)->hh; \ ++ while (_thh) { \ ++ _count++; \ ++ if (_prev != (char*)_thh->prev) { \ ++ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ ++ (where), (void*)_thh->prev, (void*)_prev); \ ++ } \ ++ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ ++ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ ++ } \ ++ if (_count != (head)->hh.tbl->num_items) { \ ++ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ ++ (where), (head)->hh.tbl->num_items, _count); \ ++ } \ ++ } \ ++} while (0) ++#else ++#define HASH_FSCK(hh,head,where) ++#endif ++ ++/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to ++ * the descriptor to which this macro is defined for tuning the hash function. ++ * The app can #include to get the prototype for write(2). */ ++#ifdef HASH_EMIT_KEYS ++#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ ++do { \ ++ unsigned _klen = fieldlen; \ ++ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ ++ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ ++} while (0) ++#else ++#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) ++#endif ++ ++/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ ++#define HASH_BER(key,keylen,hashv) \ ++do { \ ++ unsigned _hb_keylen = (unsigned)keylen; \ ++ const unsigned char *_hb_key = (const unsigned char*)(key); \ ++ (hashv) = 0; \ ++ while (_hb_keylen-- != 0U) { \ ++ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ ++ } \ ++} while (0) ++ ++ ++/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at ++ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx ++ * (archive link: https://archive.is/Ivcan ) ++ */ ++#define HASH_SAX(key,keylen,hashv) \ ++do { \ ++ unsigned _sx_i; \ ++ const unsigned char *_hs_key = (const unsigned char*)(key); \ ++ hashv = 0; \ ++ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ ++ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ ++ } \ ++} while (0) ++/* FNV-1a variation */ ++#define HASH_FNV(key,keylen,hashv) \ ++do { \ ++ unsigned _fn_i; \ ++ const unsigned char *_hf_key = (const unsigned char*)(key); \ ++ (hashv) = 2166136261U; \ ++ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ ++ hashv = hashv ^ _hf_key[_fn_i]; \ ++ hashv = hashv * 16777619U; \ ++ } \ ++} while (0) ++ ++#define HASH_OAT(key,keylen,hashv) \ ++do { \ ++ unsigned _ho_i; \ ++ const unsigned char *_ho_key=(const unsigned char*)(key); \ ++ hashv = 0; \ ++ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ ++ hashv += _ho_key[_ho_i]; \ ++ hashv += (hashv << 10); \ ++ hashv ^= (hashv >> 6); \ ++ } \ ++ hashv += (hashv << 3); \ ++ hashv ^= (hashv >> 11); \ ++ hashv += (hashv << 15); \ ++} while (0) ++ ++#define HASH_JEN_MIX(a,b,c) \ ++do { \ ++ a -= b; a -= c; a ^= ( c >> 13 ); \ ++ b -= c; b -= a; b ^= ( a << 8 ); \ ++ c -= a; c -= b; c ^= ( b >> 13 ); \ ++ a -= b; a -= c; a ^= ( c >> 12 ); \ ++ b -= c; b -= a; b ^= ( a << 16 ); \ ++ c -= a; c -= b; c ^= ( b >> 5 ); \ ++ a -= b; a -= c; a ^= ( c >> 3 ); \ ++ b -= c; b -= a; b ^= ( a << 10 ); \ ++ c -= a; c -= b; c ^= ( b >> 15 ); \ ++} while (0) ++ ++#define HASH_JEN(key,keylen,hashv) \ ++do { \ ++ unsigned _hj_i,_hj_j,_hj_k; \ ++ unsigned const char *_hj_key=(unsigned const char*)(key); \ ++ hashv = 0xfeedbeefu; \ ++ _hj_i = _hj_j = 0x9e3779b9u; \ ++ _hj_k = (unsigned)(keylen); \ ++ while (_hj_k >= 12U) { \ ++ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ ++ + ( (unsigned)_hj_key[2] << 16 ) \ ++ + ( (unsigned)_hj_key[3] << 24 ) ); \ ++ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ ++ + ( (unsigned)_hj_key[6] << 16 ) \ ++ + ( (unsigned)_hj_key[7] << 24 ) ); \ ++ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ ++ + ( (unsigned)_hj_key[10] << 16 ) \ ++ + ( (unsigned)_hj_key[11] << 24 ) ); \ ++ \ ++ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ ++ \ ++ _hj_key += 12; \ ++ _hj_k -= 12U; \ ++ } \ ++ hashv += (unsigned)(keylen); \ ++ switch ( _hj_k ) { \ ++ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ ++ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ ++ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ ++ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ ++ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ ++ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ ++ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ ++ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ ++ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ ++ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ ++ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ ++ default: ; \ ++ } \ ++ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ ++} while (0) ++ ++/* The Paul Hsieh hash function */ ++#undef get16bits ++#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ ++ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) ++#define get16bits(d) (*((const uint16_t *) (d))) ++#endif ++ ++#if !defined (get16bits) ++#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ ++ +(uint32_t)(((const uint8_t *)(d))[0]) ) ++#endif ++#define HASH_SFH(key,keylen,hashv) \ ++do { \ ++ unsigned const char *_sfh_key=(unsigned const char*)(key); \ ++ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ ++ \ ++ unsigned _sfh_rem = _sfh_len & 3U; \ ++ _sfh_len >>= 2; \ ++ hashv = 0xcafebabeu; \ ++ \ ++ /* Main loop */ \ ++ for (;_sfh_len > 0U; _sfh_len--) { \ ++ hashv += get16bits (_sfh_key); \ ++ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ ++ hashv = (hashv << 16) ^ _sfh_tmp; \ ++ _sfh_key += 2U*sizeof (uint16_t); \ ++ hashv += hashv >> 11; \ ++ } \ ++ \ ++ /* Handle end cases */ \ ++ switch (_sfh_rem) { \ ++ case 3: hashv += get16bits (_sfh_key); \ ++ hashv ^= hashv << 16; \ ++ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ ++ hashv += hashv >> 11; \ ++ break; \ ++ case 2: hashv += get16bits (_sfh_key); \ ++ hashv ^= hashv << 11; \ ++ hashv += hashv >> 17; \ ++ break; \ ++ case 1: hashv += *_sfh_key; \ ++ hashv ^= hashv << 10; \ ++ hashv += hashv >> 1; \ ++ break; \ ++ default: ; \ ++ } \ ++ \ ++ /* Force "avalanching" of final 127 bits */ \ ++ hashv ^= hashv << 3; \ ++ hashv += hashv >> 5; \ ++ hashv ^= hashv << 4; \ ++ hashv += hashv >> 17; \ ++ hashv ^= hashv << 25; \ ++ hashv += hashv >> 6; \ ++} while (0) ++ ++/* iterate over items in a known bucket to find desired item */ ++#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ ++do { \ ++ if ((head).hh_head != NULL) { \ ++ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ ++ } else { \ ++ (out) = NULL; \ ++ } \ ++ while ((out) != NULL) { \ ++ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ ++ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ ++ break; \ ++ } \ ++ } \ ++ if ((out)->hh.hh_next != NULL) { \ ++ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ ++ } else { \ ++ (out) = NULL; \ ++ } \ ++ } \ ++} while (0) ++ ++/* add an item to a bucket */ ++#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ ++do { \ ++ UT_hash_bucket *_ha_head = &(head); \ ++ _ha_head->count++; \ ++ (addhh)->hh_next = _ha_head->hh_head; \ ++ (addhh)->hh_prev = NULL; \ ++ if (_ha_head->hh_head != NULL) { \ ++ _ha_head->hh_head->hh_prev = (addhh); \ ++ } \ ++ _ha_head->hh_head = (addhh); \ ++ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ ++ && !(addhh)->tbl->noexpand) { \ ++ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ ++ IF_HASH_NONFATAL_OOM( \ ++ if (oomed) { \ ++ HASH_DEL_IN_BKT(head,addhh); \ ++ } \ ++ ) \ ++ } \ ++} while (0) ++ ++/* remove an item from a given bucket */ ++#define HASH_DEL_IN_BKT(head,delhh) \ ++do { \ ++ UT_hash_bucket *_hd_head = &(head); \ ++ _hd_head->count--; \ ++ if (_hd_head->hh_head == (delhh)) { \ ++ _hd_head->hh_head = (delhh)->hh_next; \ ++ } \ ++ if ((delhh)->hh_prev) { \ ++ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ ++ } \ ++ if ((delhh)->hh_next) { \ ++ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ ++ } \ ++} while (0) ++ ++/* Bucket expansion has the effect of doubling the number of buckets ++ * and redistributing the items into the new buckets. Ideally the ++ * items will distribute more or less evenly into the new buckets ++ * (the extent to which this is true is a measure of the quality of ++ * the hash function as it applies to the key domain). ++ * ++ * With the items distributed into more buckets, the chain length ++ * (item count) in each bucket is reduced. Thus by expanding buckets ++ * the hash keeps a bound on the chain length. This bounded chain ++ * length is the essence of how a hash provides constant time lookup. ++ * ++ * The calculation of tbl->ideal_chain_maxlen below deserves some ++ * explanation. First, keep in mind that we're calculating the ideal ++ * maximum chain length based on the *new* (doubled) bucket count. ++ * In fractions this is just n/b (n=number of items,b=new num buckets). ++ * Since the ideal chain length is an integer, we want to calculate ++ * ceil(n/b). We don't depend on floating point arithmetic in this ++ * hash, so to calculate ceil(n/b) with integers we could write ++ * ++ * ceil(n/b) = (n/b) + ((n%b)?1:0) ++ * ++ * and in fact a previous version of this hash did just that. ++ * But now we have improved things a bit by recognizing that b is ++ * always a power of two. We keep its base 2 log handy (call it lb), ++ * so now we can write this with a bit shift and logical AND: ++ * ++ * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) ++ * ++ */ ++#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ ++do { \ ++ unsigned _he_bkt; \ ++ unsigned _he_bkt_i; \ ++ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ ++ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ ++ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ ++ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ ++ if (!_he_new_buckets) { \ ++ HASH_RECORD_OOM(oomed); \ ++ } else { \ ++ uthash_bzero(_he_new_buckets, \ ++ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ ++ (tbl)->ideal_chain_maxlen = \ ++ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ++ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ ++ (tbl)->nonideal_items = 0; \ ++ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ ++ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ ++ while (_he_thh != NULL) { \ ++ _he_hh_nxt = _he_thh->hh_next; \ ++ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ ++ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ ++ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ ++ (tbl)->nonideal_items++; \ ++ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ ++ _he_newbkt->expand_mult++; \ ++ } \ ++ } \ ++ _he_thh->hh_prev = NULL; \ ++ _he_thh->hh_next = _he_newbkt->hh_head; \ ++ if (_he_newbkt->hh_head != NULL) { \ ++ _he_newbkt->hh_head->hh_prev = _he_thh; \ ++ } \ ++ _he_newbkt->hh_head = _he_thh; \ ++ _he_thh = _he_hh_nxt; \ ++ } \ ++ } \ ++ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ ++ (tbl)->num_buckets *= 2U; \ ++ (tbl)->log2_num_buckets++; \ ++ (tbl)->buckets = _he_new_buckets; \ ++ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ++ ((tbl)->ineff_expands+1U) : 0U; \ ++ if ((tbl)->ineff_expands > 1U) { \ ++ (tbl)->noexpand = 1; \ ++ uthash_noexpand_fyi(tbl); \ ++ } \ ++ uthash_expand_fyi(tbl); \ ++ } \ ++} while (0) ++ ++ ++/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ ++/* Note that HASH_SORT assumes the hash handle name to be hh. ++ * HASH_SRT was added to allow the hash handle name to be passed in. */ ++#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) ++#define HASH_SRT(hh,head,cmpfcn) \ ++do { \ ++ unsigned _hs_i; \ ++ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ ++ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ ++ if (head != NULL) { \ ++ _hs_insize = 1; \ ++ _hs_looping = 1; \ ++ _hs_list = &((head)->hh); \ ++ while (_hs_looping != 0U) { \ ++ _hs_p = _hs_list; \ ++ _hs_list = NULL; \ ++ _hs_tail = NULL; \ ++ _hs_nmerges = 0; \ ++ while (_hs_p != NULL) { \ ++ _hs_nmerges++; \ ++ _hs_q = _hs_p; \ ++ _hs_psize = 0; \ ++ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ ++ _hs_psize++; \ ++ _hs_q = ((_hs_q->next != NULL) ? \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ ++ if (_hs_q == NULL) { \ ++ break; \ ++ } \ ++ } \ ++ _hs_qsize = _hs_insize; \ ++ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ ++ if (_hs_psize == 0U) { \ ++ _hs_e = _hs_q; \ ++ _hs_q = ((_hs_q->next != NULL) ? \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ ++ _hs_qsize--; \ ++ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ ++ _hs_e = _hs_p; \ ++ if (_hs_p != NULL) { \ ++ _hs_p = ((_hs_p->next != NULL) ? \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ ++ } \ ++ _hs_psize--; \ ++ } else if ((cmpfcn( \ ++ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ ++ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ ++ )) <= 0) { \ ++ _hs_e = _hs_p; \ ++ if (_hs_p != NULL) { \ ++ _hs_p = ((_hs_p->next != NULL) ? \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ ++ } \ ++ _hs_psize--; \ ++ } else { \ ++ _hs_e = _hs_q; \ ++ _hs_q = ((_hs_q->next != NULL) ? \ ++ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ ++ _hs_qsize--; \ ++ } \ ++ if ( _hs_tail != NULL ) { \ ++ _hs_tail->next = ((_hs_e != NULL) ? \ ++ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ ++ } else { \ ++ _hs_list = _hs_e; \ ++ } \ ++ if (_hs_e != NULL) { \ ++ _hs_e->prev = ((_hs_tail != NULL) ? \ ++ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ ++ } \ ++ _hs_tail = _hs_e; \ ++ } \ ++ _hs_p = _hs_q; \ ++ } \ ++ if (_hs_tail != NULL) { \ ++ _hs_tail->next = NULL; \ ++ } \ ++ if (_hs_nmerges <= 1U) { \ ++ _hs_looping = 0; \ ++ (head)->hh.tbl->tail = _hs_tail; \ ++ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ ++ } \ ++ _hs_insize *= 2U; \ ++ } \ ++ HASH_FSCK(hh, head, "HASH_SRT"); \ ++ } \ ++} while (0) ++ ++/* This function selects items from one hash into another hash. ++ * The end result is that the selected items have dual presence ++ * in both hashes. There is no copy of the items made; rather ++ * they are added into the new hash through a secondary hash ++ * hash handle that must be present in the structure. */ ++#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ ++do { \ ++ unsigned _src_bkt, _dst_bkt; \ ++ void *_last_elt = NULL, *_elt; \ ++ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ++ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ ++ if ((src) != NULL) { \ ++ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ ++ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ ++ _src_hh != NULL; \ ++ _src_hh = _src_hh->hh_next) { \ ++ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ ++ if (cond(_elt)) { \ ++ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ ++ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ ++ _dst_hh->key = _src_hh->key; \ ++ _dst_hh->keylen = _src_hh->keylen; \ ++ _dst_hh->hashv = _src_hh->hashv; \ ++ _dst_hh->prev = _last_elt; \ ++ _dst_hh->next = NULL; \ ++ if (_last_elt_hh != NULL) { \ ++ _last_elt_hh->next = _elt; \ ++ } \ ++ if ((dst) == NULL) { \ ++ DECLTYPE_ASSIGN(dst, _elt); \ ++ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ ++ IF_HASH_NONFATAL_OOM( \ ++ if (_hs_oomed) { \ ++ uthash_nonfatal_oom(_elt); \ ++ (dst) = NULL; \ ++ continue; \ ++ } \ ++ ) \ ++ } else { \ ++ _dst_hh->tbl = (dst)->hh_dst.tbl; \ ++ } \ ++ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ ++ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ ++ (dst)->hh_dst.tbl->num_items++; \ ++ IF_HASH_NONFATAL_OOM( \ ++ if (_hs_oomed) { \ ++ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ ++ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ ++ _dst_hh->tbl = NULL; \ ++ uthash_nonfatal_oom(_elt); \ ++ continue; \ ++ } \ ++ ) \ ++ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ ++ _last_elt = _elt; \ ++ _last_elt_hh = _dst_hh; \ ++ } \ ++ } \ ++ } \ ++ } \ ++ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ ++} while (0) ++ ++#define HASH_CLEAR(hh,head) \ ++do { \ ++ if ((head) != NULL) { \ ++ HASH_BLOOM_FREE((head)->hh.tbl); \ ++ uthash_free((head)->hh.tbl->buckets, \ ++ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ ++ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ ++ (head) = NULL; \ ++ } \ ++} while (0) ++ ++#define HASH_OVERHEAD(hh,head) \ ++ (((head) != NULL) ? ( \ ++ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ++ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ ++ sizeof(UT_hash_table) + \ ++ (HASH_BLOOM_BYTELEN))) : 0U) ++ ++#ifdef NO_DECLTYPE ++#define HASH_ITER(hh,head,el,tmp) \ ++for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ ++ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) ++#else ++#define HASH_ITER(hh,head,el,tmp) \ ++for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ ++ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) ++#endif ++ ++/* obtain a count of items in the hash */ ++#define HASH_COUNT(head) HASH_CNT(hh,head) ++#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) ++ ++typedef struct UT_hash_bucket { ++ struct UT_hash_handle *hh_head; ++ unsigned count; ++ ++ /* expand_mult is normally set to 0. In this situation, the max chain length ++ * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If ++ * the bucket's chain exceeds this length, bucket expansion is triggered). ++ * However, setting expand_mult to a non-zero value delays bucket expansion ++ * (that would be triggered by additions to this particular bucket) ++ * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. ++ * (The multiplier is simply expand_mult+1). The whole idea of this ++ * multiplier is to reduce bucket expansions, since they are expensive, in ++ * situations where we know that a particular bucket tends to be overused. ++ * It is better to let its chain length grow to a longer yet-still-bounded ++ * value, than to do an O(n) bucket expansion too often. ++ */ ++ unsigned expand_mult; ++ ++} UT_hash_bucket; ++ ++/* random signature used only to find hash tables in external analysis */ ++#define HASH_SIGNATURE 0xa0111fe1u ++#define HASH_BLOOM_SIGNATURE 0xb12220f2u ++ ++typedef struct UT_hash_table { ++ UT_hash_bucket *buckets; ++ unsigned num_buckets, log2_num_buckets; ++ unsigned num_items; ++ struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ++ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ ++ ++ /* in an ideal situation (all buckets used equally), no bucket would have ++ * more than ceil(#items/#buckets) items. that's the ideal chain length. */ ++ unsigned ideal_chain_maxlen; ++ ++ /* nonideal_items is the number of items in the hash whose chain position ++ * exceeds the ideal chain maxlen. these items pay the penalty for an uneven ++ * hash distribution; reaching them in a chain traversal takes >ideal steps */ ++ unsigned nonideal_items; ++ ++ /* ineffective expands occur when a bucket doubling was performed, but ++ * afterward, more than half the items in the hash had nonideal chain ++ * positions. If this happens on two consecutive expansions we inhibit any ++ * further expansion, as it's not helping; this happens when the hash ++ * function isn't a good fit for the key domain. When expansion is inhibited ++ * the hash will still work, albeit no longer in constant time. */ ++ unsigned ineff_expands, noexpand; ++ ++ uint32_t signature; /* used only to find hash tables in external analysis */ ++#ifdef HASH_BLOOM ++ uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ ++ uint8_t *bloom_bv; ++ uint8_t bloom_nbits; ++#endif ++ ++} UT_hash_table; ++ ++typedef struct UT_hash_handle { ++ struct UT_hash_table *tbl; ++ void *prev; /* prev element in app order */ ++ void *next; /* next element in app order */ ++ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ ++ struct UT_hash_handle *hh_next; /* next hh in bucket order */ ++ const void *key; /* ptr to enclosing struct's key */ ++ unsigned keylen; /* enclosing struct's key len */ ++ unsigned hashv; /* result of hash-fcn(key) */ ++} UT_hash_handle; ++ ++#endif /* UTHASH_H */ +diff -Nur openssh-9.3p1.orig/version.h openssh-9.3p1/version.h +--- openssh-9.3p1.orig/version.h 2024-07-10 09:07:09.814082092 +0200 ++++ openssh-9.3p1/version.h 2024-07-10 10:31:57.907981932 +0200 +@@ -16,5 +16,6 @@ + + #define SSH_PORTABLE "p1" + #define GSI_PORTABLE "c-GSI" ++#define SSH_HPN "-hpn17v13" + #define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \ +- GSI_VERSION KRB5_VERSION ++ GSI_VERSION KRB5_VERSION SSH_HPN diff --git a/openssh-9.6p1-CVE-2023-48795.patch b/openssh-9.6p1-CVE-2023-48795.patch new file mode 100644 index 0000000..7ada8b0 --- /dev/null +++ b/openssh-9.6p1-CVE-2023-48795.patch @@ -0,0 +1,443 @@ +diff --git a/PROTOCOL b/PROTOCOL +index d453c779..ded935eb 100644 +--- a/PROTOCOL ++++ b/PROTOCOL +@@ -137,6 +137,32 @@ than as a named global or channel request to allow pings with very + + This is identical to curve25519-sha256 as later published in RFC8731. + ++1.9 transport: strict key exchange extension ++ ++OpenSSH supports a number of transport-layer hardening measures under ++a "strict KEX" feature. This feature is signalled similarly to the ++RFC8308 ext-info feature: by including a additional algorithm in the ++initiial SSH2_MSG_KEXINIT kex_algorithms field. The client may append ++"kex-strict-c-v00@openssh.com" to its kex_algorithms and the server ++may append "kex-strict-s-v00@openssh.com". These pseudo-algorithms ++are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored ++if they are present in subsequent SSH2_MSG_KEXINIT packets. ++ ++When an endpoint that supports this extension observes this algorithm ++name in a peer's KEXINIT packet, it MUST make the following changes to ++the the protocol: ++ ++a) During initial KEX, terminate the connection if any unexpected or ++ out-of-sequence packet is received. This includes terminating the ++ connection if the first packet received is not SSH2_MSG_KEXINIT. ++ Unexpected packets for the purpose of strict KEX include messages ++ that are otherwise valid at any time during the connection such as ++ SSH2_MSG_DEBUG and SSH2_MSG_IGNORE. ++b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the ++ packet sequence number to zero. This behaviour persists for the ++ duration of the connection (i.e. not just the first ++ SSH2_MSG_NEWKEYS). ++ + 2. Connection protocol changes + + 2.1. connection: Channel write close extension "eow@openssh.com" +diff --git a/kex.c b/kex.c +index aa5e792d..d478ff6e 100644 +--- a/kex.c ++++ b/kex.c +@@ -65,7 +65,7 @@ + #endif + + /* prototype */ +-static int kex_choose_conf(struct ssh *); ++static int kex_choose_conf(struct ssh *, uint32_t seq); + static int kex_input_newkeys(int, u_int32_t, struct ssh *); + + static const char * const proposal_names[PROPOSAL_MAX] = { +@@ -177,6 +177,18 @@ kex_names_valid(const char *names) + return 1; + } + ++/* returns non-zero if proposal contains any algorithm from algs */ ++static int ++has_any_alg(const char *proposal, const char *algs) ++{ ++ char *cp; ++ ++ if ((cp = match_list(proposal, algs, NULL)) == NULL) ++ return 0; ++ free(cp); ++ return 1; ++} ++ + /* + * Concatenate algorithm names, avoiding duplicates in the process. + * Caller must free returned string. +@@ -184,7 +196,7 @@ kex_names_valid(const char *names) + char * + kex_names_cat(const char *a, const char *b) + { +- char *ret = NULL, *tmp = NULL, *cp, *p, *m; ++ char *ret = NULL, *tmp = NULL, *cp, *p; + size_t len; + + if (a == NULL || *a == '\0') +@@ -201,10 +213,8 @@ kex_names_cat(const char *a, const char *b) + } + strlcpy(ret, a, len); + for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { +- if ((m = match_list(ret, p, NULL)) != NULL) { +- free(m); ++ if (has_any_alg(ret, p)) + continue; /* Algorithm already present */ +- } + if (strlcat(ret, ",", len) >= len || + strlcat(ret, p, len) >= len) { + free(tmp); +@@ -334,15 +344,23 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX], + const char *defpropclient[PROPOSAL_MAX] = { KEX_CLIENT }; + const char **defprop = ssh->kex->server ? defpropserver : defpropclient; + u_int i; ++ char *cp; + + if (prop == NULL) + fatal_f("proposal missing"); + ++ /* Append EXT_INFO signalling to KexAlgorithms */ ++ if (kexalgos == NULL) ++ kexalgos = defprop[PROPOSAL_KEX_ALGS]; ++ if ((cp = kex_names_cat(kexalgos, ssh->kex->server ? ++ "kex-strict-s-v00@openssh.com" : ++ "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL) ++ fatal_f("kex_names_cat"); ++ + for (i = 0; i < PROPOSAL_MAX; i++) { + switch(i) { + case PROPOSAL_KEX_ALGS: +- prop[i] = compat_kex_proposal(ssh, +- kexalgos ? kexalgos : defprop[i]); ++ prop[i] = compat_kex_proposal(ssh, cp); + break; + case PROPOSAL_ENC_ALGS_CTOS: + case PROPOSAL_ENC_ALGS_STOC: +@@ -363,6 +381,7 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX], + prop[i] = xstrdup(defprop[i]); + } + } ++ free(cp); + } + + void +@@ -466,7 +485,12 @@ kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh) + { + int r; + +- error("kex protocol error: type %d seq %u", type, seq); ++ /* If in strict mode, any unexpected message is an error */ ++ if ((ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) { ++ ssh_packet_disconnect(ssh, "strict KEX violation: " ++ "unexpected packet type %u (seqnr %u)", type, seq); ++ } ++ error_f("type %u seq %u", type, seq); + if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 || + (r = sshpkt_put_u32(ssh, seq)) != 0 || + (r = sshpkt_send(ssh)) != 0) +@@ -563,7 +587,7 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) + if (ninfo >= 1024) { + error("SSH2_MSG_EXT_INFO with too many entries, expected " + "<=1024, received %u", ninfo); +- return SSH_ERR_INVALID_FORMAT; ++ return dispatch_protocol_error(type, seq, ssh); + } + for (i = 0; i < ninfo; i++) { + if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0) +@@ -681,7 +705,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) + error_f("no kex"); + return SSH_ERR_INTERNAL_ERROR; + } +- ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_protocol_error); + ptr = sshpkt_ptr(ssh, &dlen); + if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0) + return r; +@@ -717,7 +741,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) + if (!(kex->flags & KEX_INIT_SENT)) + if ((r = kex_send_kexinit(ssh)) != 0) + return r; +- if ((r = kex_choose_conf(ssh)) != 0) ++ if ((r = kex_choose_conf(ssh, seq)) != 0) + return r; + + if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) +@@ -981,20 +1005,14 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) + return (1); + } + +-/* returns non-zero if proposal contains any algorithm from algs */ + static int +-has_any_alg(const char *proposal, const char *algs) ++kexalgs_contains(char **peer, const char *ext) + { +- char *cp; +- +- if ((cp = match_list(proposal, algs, NULL)) == NULL) +- return 0; +- free(cp); +- return 1; ++ return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); + } + + static int +-kex_choose_conf(struct ssh *ssh) ++kex_choose_conf(struct ssh *ssh, uint32_t seq) + { + struct kex *kex = ssh->kex; + struct newkeys *newkeys; +@@ -1019,13 +1037,23 @@ kex_choose_conf(struct ssh *ssh) + sprop=peer; + } + +- /* Check whether client supports ext_info_c */ +- if (kex->server && (kex->flags & KEX_INITIAL)) { +- char *ext; +- +- ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL); +- kex->ext_info_c = (ext != NULL); +- free(ext); ++ /* Check whether peer supports ext_info/kex_strict */ ++ if ((kex->flags & KEX_INITIAL) != 0) { ++ if (kex->server) { ++ kex->ext_info_c = kexalgs_contains(peer, "ext-info-c"); ++ kex->kex_strict = kexalgs_contains(peer, ++ "kex-strict-c-v00@openssh.com"); ++ } else { ++ kex->kex_strict = kexalgs_contains(peer, ++ "kex-strict-s-v00@openssh.com"); ++ } ++ if (kex->kex_strict) { ++ debug3_f("will use strict KEX ordering"); ++ if (seq != 0) ++ ssh_packet_disconnect(ssh, ++ "strict KEX violation: " ++ "KEXINIT was not the first packet"); ++ } + } + + /* Check whether client supports rsa-sha2 algorithms */ +diff --git a/kex.h b/kex.h +index 5f7ef784..272ebb43 100644 +--- a/kex.h ++++ b/kex.h +@@ -149,6 +149,7 @@ struct kex { + u_int kex_type; + char *server_sig_algs; + int ext_info_c; ++ int kex_strict; + struct sshbuf *my; + struct sshbuf *peer; + struct sshbuf *client_version; +diff --git a/packet.c b/packet.c +index 52017def..beb214f9 100644 +--- a/packet.c ++++ b/packet.c +@@ -1207,8 +1207,13 @@ ssh_packet_send2_wrapped(struct ssh *ssh) + sshbuf_dump(state->output, stderr); + #endif + /* increment sequence number for outgoing packets */ +- if (++state->p_send.seqnr == 0) ++ if (++state->p_send.seqnr == 0) { ++ if ((ssh->kex->flags & KEX_INITIAL) != 0) { ++ ssh_packet_disconnect(ssh, "outgoing sequence number " ++ "wrapped during initial key exchange"); ++ } + logit("outgoing seqnr wraps around"); ++ } + if (++state->p_send.packets == 0) + if (!(ssh->compat & SSH_BUG_NOREKEY)) + return SSH_ERR_NEED_REKEY; +@@ -1216,6 +1221,11 @@ ssh_packet_send2_wrapped(struct ssh *ssh) + state->p_send.bytes += len; + sshbuf_reset(state->outgoing_packet); + ++ if (type == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { ++ debug_f("resetting send seqnr %u", state->p_send.seqnr); ++ state->p_send.seqnr = 0; ++ } ++ + if (type == SSH2_MSG_NEWKEYS) + r = ssh_set_newkeys(ssh, MODE_OUT); + else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side) +@@ -1344,8 +1354,7 @@ ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + /* Stay in the loop until we have received a complete packet. */ + for (;;) { + /* Try to read a packet from the buffer. */ +- r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p); +- if (r != 0) ++ if ((r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p)) != 0) + break; + /* If we got a packet, return it. */ + if (*typep != SSH_MSG_NONE) +@@ -1629,10 +1615,16 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0) + goto out; + } ++ + if (seqnr_p != NULL) + *seqnr_p = state->p_read.seqnr; +- if (++state->p_read.seqnr == 0) ++ if (++state->p_read.seqnr == 0) { ++ if ((ssh->kex->flags & KEX_INITIAL) != 0) { ++ ssh_packet_disconnect(ssh, "incoming sequence number " ++ "wrapped during initial key exchange"); ++ } + logit("incoming seqnr wraps around"); ++ } + if (++state->p_read.packets == 0) + if (!(ssh->compat & SSH_BUG_NOREKEY)) + return SSH_ERR_NEED_REKEY; +@@ -1698,6 +1690,10 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + #endif + /* reset for next packet */ + state->packlen = 0; ++ if (*typep == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { ++ debug_f("resetting read seqnr %u", state->p_read.seqnr); ++ state->p_read.seqnr = 0; ++ } + + if ((r = ssh_packet_check_rekey(ssh)) != 0) + return r; +@@ -1720,10 +1716,39 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + r = ssh_packet_read_poll2(ssh, typep, seqnr_p); + if (r != 0) + return r; +- if (*typep) { +- state->keep_alive_timeouts = 0; +- DBG(debug("received packet type %d", *typep)); ++ if (*typep == 0) { ++ /* no message ready */ ++ return 0; + } ++ state->keep_alive_timeouts = 0; ++ DBG(debug("received packet type %d", *typep)); ++ ++ /* Always process disconnect messages */ ++ if (*typep == SSH2_MSG_DISCONNECT) { ++ if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || ++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) ++ return r; ++ /* Ignore normal client exit notifications */ ++ do_log2(ssh->state->server_side && ++ reason == SSH2_DISCONNECT_BY_APPLICATION ? ++ SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, ++ "Received disconnect from %s port %d:" ++ "%u: %.400s", ssh_remote_ipaddr(ssh), ++ ssh_remote_port(ssh), reason, msg); ++ free(msg); ++ return SSH_ERR_DISCONNECTED; ++ } ++ ++ /* ++ * Do not implicitly handle any messages here during initial ++ * KEX when in strict mode. They will be need to be allowed ++ * explicitly by the KEX dispatch table or they will generate ++ * protocol errors. ++ */ ++ if (ssh->kex != NULL && ++ (ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) ++ return 0; ++ /* Implicitly handle transport-level messages */ + switch (*typep) { + case SSH2_MSG_IGNORE: + debug3("Received SSH2_MSG_IGNORE"); +@@ -1738,19 +1763,6 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + debug("Remote: %.900s", msg); + free(msg); + break; +- case SSH2_MSG_DISCONNECT: +- if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || +- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) +- return r; +- /* Ignore normal client exit notifications */ +- do_log2(ssh->state->server_side && +- reason == SSH2_DISCONNECT_BY_APPLICATION ? +- SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, +- "Received disconnect from %s port %d:" +- "%u: %.400s", ssh_remote_ipaddr(ssh), +- ssh_remote_port(ssh), reason, msg); +- free(msg); +- return SSH_ERR_DISCONNECTED; + case SSH2_MSG_UNIMPLEMENTED: + if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0) + return r; +@@ -2242,6 +2254,7 @@ kex_to_blob(struct sshbuf *m, struct kex *kex) + (r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 || + (r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 || + (r = sshbuf_put_u32(m, kex->kex_type)) != 0 || ++ (r = sshbuf_put_u32(m, kex->kex_strict)) != 0 || + (r = sshbuf_put_stringb(m, kex->my)) != 0 || + (r = sshbuf_put_stringb(m, kex->peer)) != 0 || + (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || +@@ -2404,6 +2417,7 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp) + (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 || + (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 || + (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || ++ (r = sshbuf_get_u32(m, &kex->kex_strict)) != 0 || + (r = sshbuf_get_stringb(m, kex->my)) != 0 || + (r = sshbuf_get_stringb(m, kex->peer)) != 0 || + (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || +@@ -2732,6 +2746,7 @@ sshpkt_disconnect(struct ssh *ssh, const char *fmt,...) + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + ++ debug2_f("sending SSH2_MSG_DISCONNECT: %s", buf); + if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || + (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || + (r = sshpkt_put_cstring(ssh, buf)) != 0 || +diff --git a/sshconnect2.c b/sshconnect2.c +index df6caf81..0cccbcc4 100644 +--- a/sshconnect2.c ++++ b/sshconnect2.c +@@ -358,7 +358,6 @@ struct cauthmethod { + }; + + static int input_userauth_service_accept(int, u_int32_t, struct ssh *); +-static int input_userauth_ext_info(int, u_int32_t, struct ssh *); + static int input_userauth_success(int, u_int32_t, struct ssh *); + static int input_userauth_failure(int, u_int32_t, struct ssh *); + static int input_userauth_banner(int, u_int32_t, struct ssh *); +@@ -472,7 +471,7 @@ ssh_userauth2(struct ssh *ssh, const char *local_user, + + ssh->authctxt = &authctxt; + ssh_dispatch_init(ssh, &input_userauth_error); +- ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &input_userauth_ext_info); ++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, kex_input_ext_info); + ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_ACCEPT, &input_userauth_service_accept); + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt.success); /* loop until success */ + pubkey_cleanup(ssh); +@@ -531,12 +530,6 @@ input_userauth_service_accept(int type, u_int32_t seq, struct ssh *ssh) + return r; + } + +-static int +-input_userauth_ext_info(int type, u_int32_t seqnr, struct ssh *ssh) +-{ +- return kex_input_ext_info(type, seqnr, ssh); +-} +- + void + userauth(struct ssh *ssh, char *authlist) + { +@@ -615,6 +608,7 @@ input_userauth_success(int type, u_int32_t seq, struct ssh *ssh) + free(authctxt->methoddata); + authctxt->methoddata = NULL; + authctxt->success = 1; /* break out */ ++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, dispatch_protocol_error); + return 0; + } + +diff -up openssh-8.7p1/sshd.c.kexstrict openssh-8.7p1/sshd.c +--- openssh-8.7p1/sshd.c.kexstrict 2023-11-27 13:19:18.855433602 +0100 ++++ openssh-8.7p1/sshd.c 2023-11-27 13:28:10.441325314 +0100 +@@ -2586,7 +2586,7 @@ do_ssh2_kex(struct ssh *ssh) + if (gss && orig) + xasprintf(&newstr, "%s,%s", gss, orig); + else if (gss) +- newstr = gss; ++ xasprintf(&newstr, "%s,%s", gss, "kex-strict-s-v00@openssh.com"); + else if (orig) + newstr = orig; + diff --git a/openssh-9.6p1-CVE-2023-51384.patch b/openssh-9.6p1-CVE-2023-51384.patch new file mode 100644 index 0000000..673ff1d --- /dev/null +++ b/openssh-9.6p1-CVE-2023-51384.patch @@ -0,0 +1,149 @@ +diff --git a/ssh-agent.c b/ssh-agent.c +index f528611635e..1d4c321eb0b 100644 +--- a/ssh-agent.c ++++ b/ssh-agent.c +@@ -247,6 +247,91 @@ free_dest_constraints(struct dest_constraint *dcs, size_t ndcs) + free(dcs); + } + ++static void ++dup_dest_constraint_hop(const struct dest_constraint_hop *dch, ++ struct dest_constraint_hop *out) ++{ ++ u_int i; ++ int r; ++ ++ out->user = dch->user == NULL ? NULL : xstrdup(dch->user); ++ out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname); ++ out->is_ca = dch->is_ca; ++ out->nkeys = dch->nkeys; ++ out->keys = out->nkeys == 0 ? NULL : ++ xcalloc(out->nkeys, sizeof(*out->keys)); ++ out->key_is_ca = out->nkeys == 0 ? NULL : ++ xcalloc(out->nkeys, sizeof(*out->key_is_ca)); ++ for (i = 0; i < dch->nkeys; i++) { ++ if (dch->keys[i] != NULL && ++ (r = sshkey_from_private(dch->keys[i], ++ &(out->keys[i]))) != 0) ++ fatal_fr(r, "copy key"); ++ out->key_is_ca[i] = dch->key_is_ca[i]; ++ } ++} ++ ++static struct dest_constraint * ++dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs) ++{ ++ size_t i; ++ struct dest_constraint *ret; ++ ++ if (ndcs == 0) ++ return NULL; ++ ret = xcalloc(ndcs, sizeof(*ret)); ++ for (i = 0; i < ndcs; i++) { ++ dup_dest_constraint_hop(&dcs[i].from, &ret[i].from); ++ dup_dest_constraint_hop(&dcs[i].to, &ret[i].to); ++ } ++ return ret; ++} ++ ++#ifdef DEBUG_CONSTRAINTS ++static void ++dump_dest_constraint_hop(const struct dest_constraint_hop *dch) ++{ ++ u_int i; ++ char *fp; ++ ++ debug_f("user %s hostname %s is_ca %d nkeys %u", ++ dch->user == NULL ? "(null)" : dch->user, ++ dch->hostname == NULL ? "(null)" : dch->hostname, ++ dch->is_ca, dch->nkeys); ++ for (i = 0; i < dch->nkeys; i++) { ++ fp = NULL; ++ if (dch->keys[i] != NULL && ++ (fp = sshkey_fingerprint(dch->keys[i], ++ SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) ++ fatal_f("fingerprint failed"); ++ debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys, ++ dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]), ++ dch->keys[i] == NULL ? "" : " ", ++ dch->keys[i] == NULL ? "none" : fp, ++ dch->key_is_ca[i]); ++ free(fp); ++ } ++} ++#endif /* DEBUG_CONSTRAINTS */ ++ ++static void ++dump_dest_constraints(const char *context, ++ const struct dest_constraint *dcs, size_t ndcs) ++{ ++#ifdef DEBUG_CONSTRAINTS ++ size_t i; ++ ++ debug_f("%s: %zu constraints", context, ndcs); ++ for (i = 0; i < ndcs; i++) { ++ debug_f("constraint %zu / %zu: from: ", i, ndcs); ++ dump_dest_constraint_hop(&dcs[i].from); ++ debug_f("constraint %zu / %zu: to: ", i, ndcs); ++ dump_dest_constraint_hop(&dcs[i].to); ++ } ++ debug_f("done for %s", context); ++#endif /* DEBUG_CONSTRAINTS */ ++} ++ + static void + free_identity(Identity *id) + { +@@ -518,13 +603,22 @@ process_request_identities(SocketEntry *e) + Identity *id; + struct sshbuf *msg, *keys; + int r; +- u_int nentries = 0; ++ u_int i = 0, nentries = 0; ++ char *fp; + + debug2_f("entering"); + + if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + TAILQ_FOREACH(id, &idtab->idlist, next) { ++ if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT, ++ SSH_FP_DEFAULT)) == NULL) ++ fatal_f("fingerprint failed"); ++ debug_f("key %u / %u: %s %s", i++, idtab->nentries, ++ sshkey_ssh_name(id->key), fp); ++ dump_dest_constraints(__func__, ++ id->dest_constraints, id->ndest_constraints); ++ free(fp); + /* identity not visible, don't include in response */ + if (identity_permitted(id, e, NULL, NULL, NULL) != 0) + continue; +@@ -1224,6 +1318,7 @@ process_add_identity(SocketEntry *e) + sshbuf_reset(e->request); + goto out; + } ++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints); + + if (sk_provider != NULL) { + if (!sshkey_is_sk(k)) { +@@ -1403,6 +1498,7 @@ process_add_smartcard_key(SocketEntry *e) + error_f("failed to parse constraints"); + goto send; + } ++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints); + + sane_uri = sanitize_pkcs11_provider(e, provider); + if (sane_uri == NULL) +@@ -1438,10 +1534,9 @@ process_add_smartcard_key(SocketEntry *e) + } + id->death = death; + id->confirm = confirm; +- id->dest_constraints = dest_constraints; ++ id->dest_constraints = dup_dest_constraints( ++ dest_constraints, ndest_constraints); + id->ndest_constraints = ndest_constraints; +- dest_constraints = NULL; /* transferred */ +- ndest_constraints = 0; + TAILQ_INSERT_TAIL(&idtab->idlist, id, next); + idtab->nentries++; + success = 1; diff --git a/openssh-9.6p1-CVE-2023-51385.patch b/openssh-9.6p1-CVE-2023-51385.patch new file mode 100644 index 0000000..7596d20 --- /dev/null +++ b/openssh-9.6p1-CVE-2023-51385.patch @@ -0,0 +1,57 @@ +diff --git a/ssh.c b/ssh.c +index 35c48e62..48d93ddf 100644 +--- a/ssh.c ++++ b/ssh.c +@@ -626,6 +626,41 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo) + free(cinfo); + } + ++static int ++valid_hostname(const char *s) ++{ ++ size_t i; ++ ++ if (*s == '-') ++ return 0; ++ for (i = 0; s[i] != 0; i++) { ++ if (strchr("'`\"$\\;&<>|(){}", s[i]) != NULL || ++ isspace((u_char)s[i]) || iscntrl((u_char)s[i])) ++ return 0; ++ } ++ return 1; ++} ++ ++static int ++valid_ruser(const char *s) ++{ ++ size_t i; ++ ++ if (*s == '-') ++ return 0; ++ for (i = 0; s[i] != 0; i++) { ++ if (strchr("'`\";&<>|(){}", s[i]) != NULL) ++ return 0; ++ /* Disallow '-' after whitespace */ ++ if (isspace((u_char)s[i]) && s[i + 1] == '-') ++ return 0; ++ /* Disallow \ in last position */ ++ if (s[i] == '\\' && s[i + 1] == '\0') ++ return 0; ++ } ++ return 1; ++} ++ + /* + * Main program for the ssh client. + */ +@@ -1118,6 +1153,10 @@ main(int ac, char **av) + if (!host) + usage(); + ++ if (!valid_hostname(host)) ++ fatal("hostname contains invalid characters"); ++ if (options.user != NULL && !valid_ruser(options.user)) ++ fatal("remote username contains invalid characters"); + options.host_arg = xstrdup(host); + + /* Initialize the command to execute on remote host. */ diff --git a/openssh-9.6p1-cve-2024-6387.patch b/openssh-9.6p1-cve-2024-6387.patch new file mode 100644 index 0000000..0b976c0 --- /dev/null +++ b/openssh-9.6p1-cve-2024-6387.patch @@ -0,0 +1,18 @@ +diff --git a/log.c b/log.c +index 9fc1a2e2e..191ff4a5a 100644 +--- a/log.c ++++ b/log.c +@@ -451,12 +451,14 @@ void + sshsigdie(const char *file, const char *func, int line, int showfunc, + LogLevel level, const char *suffix, const char *fmt, ...) + { ++#ifdef SYSLOG_R_SAFE_IN_SIGHAND + va_list args; + + va_start(args, fmt); + sshlogv(file, func, line, showfunc, SYSLOG_LEVEL_FATAL, + suffix, fmt, args); + va_end(args); ++#endif + _exit(1); + }