public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/gsi-openssh] rawhide: Based on openssh-10.5p1-1.fc45
@ 2026-09-22 19:58 Mattias Ellert
0 siblings, 0 replies; only message in thread
From: Mattias Ellert @ 2026-09-22 19:58 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/gsi-openssh
Branch : rawhide
Commit : fd9f3fdaabca4133c00c2a2dfa3fa713f425f1f1
Author : Mattias Ellert <mattias.ellert@physics.uu.se>
Date : 2026-09-22T01:04:33+02:00
Stats : +27533/-27831 in 77 file(s)
URL : https://src.fedoraproject.org/rpms/gsi-openssh/c/fd9f3fdaabca4133c00c2a2dfa3fa713f425f1f1?branch=rawhide
Log:
Based on openssh-10.5p1-1.fc45
---
diff --git a/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch b/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
deleted file mode 100644
index bb64edb..0000000
--- a/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
+++ /dev/null
@@ -1,416 +0,0 @@
-From cdb21e545ffde9b237fa9ab7be8e32b78ffc00bb Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:06:32 +0200
-Subject: [PATCH 01/54] Add SELinux role and MLS (Multi-Level Security) support
-
-openssh-7.8p1-role-mls
-
-Add support for SELinux role-based access control and Multi-Level
-Security (MLS) by allowing users to specify their role as part of the
-username in the format user/role.
-
-Changes:
-- auth.h: Add role field to Authctxt structure
-- auth2.c: Parse role from username and inform monitor process
-- auth2-gss.c: Include role in GSSAPI MIC authentication
-- auth2-hostbased.c: Include role in hostbased authentication signature
-- auth2-pubkey.c: Include role in pubkey authentication userstyle
-- monitor.c: Add mm_answer_authrole() and handle role in authentication
- blob validation
-- monitor_wrap.c: Add mm_inform_authrole() for privilege separation
-- misc.c: Update colon() to handle /role syntax in file paths
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth.h | 3 +++
- auth2-gss.c | 11 ++++++++++-
- auth2-hostbased.c | 9 +++++++++
- auth2-pubkey.c | 19 +++++++++++++++----
- auth2.c | 14 ++++++++++++++
- misc.c | 18 +++++++++++++++++-
- monitor.c | 37 +++++++++++++++++++++++++++++++++++--
- monitor.h | 4 ++++
- monitor_wrap.c | 21 +++++++++++++++++++++
- monitor_wrap.h | 3 +++
- 10 files changed, 131 insertions(+), 8 deletions(-)
-
-diff --git a/auth.h b/auth.h
-index 634a84aa8..32ee3cbf4 100644
---- a/auth.h
-+++ b/auth.h
-@@ -65,6 +65,9 @@ struct Authctxt {
- char *service;
- struct passwd *pw; /* set if 'valid' */
- char *style;
-+#ifdef WITH_SELINUX
-+ char *role;
-+#endif
-
- /* Method lists for multiple authentication */
- char **auth_methods; /* modified from server config */
-diff --git a/auth2-gss.c b/auth2-gss.c
-index 053548527..b380dc117 100644
---- a/auth2-gss.c
-+++ b/auth2-gss.c
-@@ -284,6 +284,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- Authctxt *authctxt = ssh->authctxt;
- Gssctxt *gssctxt;
- int r, authenticated = 0;
-+ char *micuser;
- struct sshbuf *b;
- gss_buffer_desc mic, gssbuf;
- u_char *p;
-@@ -300,7 +301,13 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- fatal_f("sshbuf_new failed");
- mic.value = p;
- mic.length = len;
-- ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
-+#ifdef WITH_SELINUX
-+ if (authctxt->role && authctxt->role[0] != 0)
-+ xasprintf(&micuser, "%s/%s", authctxt->user, authctxt->role);
-+ else
-+#endif
-+ micuser = authctxt->user;
-+ ssh_gssapi_buildmic(b, micuser, authctxt->service,
- "gssapi-with-mic", ssh->kex->session_id);
-
- if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
-@@ -313,6 +320,8 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- logit("GSSAPI MIC check failed");
-
- sshbuf_free(b);
-+ if (micuser != authctxt->user)
-+ free(micuser);
- free(mic.value);
-
- authctxt->postponed = 0;
-diff --git a/auth2-hostbased.c b/auth2-hostbased.c
-index 8a1acdec3..a287091ec 100644
---- a/auth2-hostbased.c
-+++ b/auth2-hostbased.c
-@@ -130,7 +130,16 @@ userauth_hostbased(struct ssh *ssh, const char *method)
- /* reconstruct packet */
- if ((r = sshbuf_put_stringb(b, ssh->kex->session_id)) != 0 ||
- (r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
-+#ifdef WITH_SELINUX
-+ (authctxt->role
-+ ? ( (r = sshbuf_put_u32(b, strlen(authctxt->user)+strlen(authctxt->role)+1)) != 0 ||
-+ (r = sshbuf_put(b, authctxt->user, strlen(authctxt->user))) != 0 ||
-+ (r = sshbuf_put_u8(b, '/') != 0) ||
-+ (r = sshbuf_put(b, authctxt->role, strlen(authctxt->role))) != 0)
-+ : (r = sshbuf_put_cstring(b, authctxt->user)) != 0) ||
-+#else
- (r = sshbuf_put_cstring(b, authctxt->user)) != 0 ||
-+#endif
- (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
- (r = sshbuf_put_cstring(b, method)) != 0 ||
- (r = sshbuf_put_string(b, pkalg, alen)) != 0 ||
-diff --git a/auth2-pubkey.c b/auth2-pubkey.c
-index e446ef412..319e42b2b 100644
---- a/auth2-pubkey.c
-+++ b/auth2-pubkey.c
-@@ -71,6 +71,8 @@
-
- /* import */
- extern ServerOptions options;
-+extern int inetd_flag;
-+extern Authctxt *the_authctxt;
- extern struct authmethod_cfg methodcfg_pubkey;
-
- static char *
-@@ -201,9 +203,16 @@ userauth_pubkey(struct ssh *ssh, const char *method)
- goto done;
- }
- /* reconstruct packet */
-- xasprintf(&userstyle, "%s%s%s", authctxt->user,
-+ xasprintf(&userstyle, "%s%s%s%s%s", authctxt->user,
- authctxt->style ? ":" : "",
-- authctxt->style ? authctxt->style : "");
-+ authctxt->style ? authctxt->style : "",
-+#ifdef WITH_SELINUX
-+ authctxt->role ? "/" : "",
-+ authctxt->role ? authctxt->role : ""
-+#else
-+ "", ""
-+#endif
-+ );
- if ((r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
- (r = sshbuf_put_cstring(b, userstyle)) != 0 ||
- (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
-@@ -473,7 +482,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key,
- if ((pid = subprocess("AuthorizedPrincipalsCommand", command,
- ac, av, &f,
- SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
-- runas_pw, temporarily_use_uid, restore_uid)) == 0)
-+ runas_pw, temporarily_use_uid, restore_uid,
-+ inetd_flag, the_authctxt)) == 0)
- goto out;
-
- uid_swapped = 1;
-@@ -749,7 +759,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key,
- if ((pid = subprocess("AuthorizedKeysCommand", command,
- ac, av, &f,
- SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
-- runas_pw, temporarily_use_uid, restore_uid)) == 0)
-+ runas_pw, temporarily_use_uid, restore_uid,
-+ inetd_flag, the_authctxt)) == 0)
- goto out;
-
- uid_swapped = 1;
-diff --git a/auth2.c b/auth2.c
-index 3a1682746..bfd425eab 100644
---- a/auth2.c
-+++ b/auth2.c
-@@ -271,6 +271,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- Authctxt *authctxt = ssh->authctxt;
- Authmethod *m = NULL;
- char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
-+#ifdef WITH_SELINUX
-+ char *role = NULL;
-+#endif
- int r, authenticated = 0;
- double tstart = monotime_double();
-
-@@ -284,6 +287,11 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- debug("userauth-request for user %s service %s method %s", user, service, method);
- debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
-
-+#ifdef WITH_SELINUX
-+ if ((role = strchr(user, '/')) != NULL)
-+ *role++ = 0;
-+#endif
-+
- if ((style = strchr(user, ':')) != NULL)
- *style++ = 0;
-
-@@ -295,6 +303,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- authctxt->user = xstrdup(user);
- authctxt->service = xstrdup(service);
- authctxt->style = style ? xstrdup(style) : NULL;
-+#ifdef WITH_SELINUX
-+ authctxt->role = role ? xstrdup(role) : NULL;
-+#endif
- if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
- authctxt->valid = 1;
- debug2_f("setting up authctxt for %s", user);
-@@ -314,6 +325,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- authctxt->valid ? "authenticating " : "invalid ", user);
- setproctitle("%s [net]", authctxt->valid ? user : "unknown");
- mm_inform_authserv(service, style);
-+#ifdef WITH_SELINUX
-+ mm_inform_authrole(role);
-+#endif
- userauth_banner(ssh);
- if ((r = kex_server_update_ext_info(ssh)) != 0)
- fatal_fr(r, "kex_server_update_ext_info failed");
-diff --git a/misc.c b/misc.c
-index ed3e9d314..e96fa8d61 100644
---- a/misc.c
-+++ b/misc.c
-@@ -869,6 +869,7 @@ char *
- colon(char *cp)
- {
- int flag = 0;
-+ int start = 1;
-
- if (*cp == ':') /* Leading colon is part of file name. */
- return NULL;
-@@ -884,6 +885,13 @@ colon(char *cp)
- return (cp);
- if (*cp == '/')
- return NULL;
-+ if (start) {
-+ /* Slash on beginning or after dots only denotes file name. */
-+ if (*cp == '/')
-+ return (0);
-+ if (*cp != '.')
-+ start = 0;
-+ }
- }
- return NULL;
- }
-@@ -2828,7 +2836,8 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
- pid_t
- subprocess(const char *tag, const char *command,
- int ac, char **av, FILE **child, u_int flags,
-- struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs)
-+ struct passwd *pw, privdrop_fn *drop_privs,
-+ privrestore_fn *restore_privs, int inetd, void *the_authctxt)
- {
- FILE *f = NULL;
- struct stat st;
-@@ -2961,6 +2970,13 @@ subprocess(const char *tag, const char *command,
- error("%s: dup2: %s", tag, strerror(errno));
- _exit(1);
- }
-+#ifdef WITH_SELINUX
-+ if (sshd_selinux_setup_env_variables(inetd, the_authctxt) < 0) {
-+ error ("failed to copy environment: %s",
-+ strerror(errno));
-+ _exit(127);
-+ }
-+#endif
- if (env != NULL)
- execve(av[0], av, env);
- else
-diff --git a/monitor.c b/monitor.c
-index 9d6672bb6..1e41dfa53 100644
---- a/monitor.c
-+++ b/monitor.c
-@@ -111,6 +111,9 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
- int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
- int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
- int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
-+#ifdef WITH_SELINUX
-+int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
-+#endif
- int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
- int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
- int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
-@@ -188,6 +191,9 @@ struct mon_table mon_dispatch_proto20[] = {
- {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
- {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
- {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
-+#ifdef WITH_SELINUX
-+ {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
-+#endif
- {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
- {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
- #ifdef USE_PAM
-@@ -946,6 +952,9 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
-
- /* Allow service/style information on the auth context */
- monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
-+#ifdef WITH_SELINUX
-+ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
-+#endif
- monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
-
- #ifdef USE_PAM
-@@ -1020,6 +1029,26 @@ key_base_type_match(const char *method, const struct sshkey *key,
- return found;
- }
-
-+#ifdef WITH_SELINUX
-+int
-+mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
-+{
-+ int r;
-+ monitor_permit_authentications(1);
-+
-+ if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
-+ fatal_f("buffer error: %s", ssh_err(r));
-+ debug3_f("role=%s", authctxt->role);
-+
-+ if (strlen(authctxt->role) == 0) {
-+ free(authctxt->role);
-+ authctxt->role = NULL;
-+ }
-+
-+ return (0);
-+}
-+#endif
-+
- int
- mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m)
- {
-@@ -1392,7 +1421,7 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
- struct sshbuf *b;
- struct sshkey *hostkey = NULL;
- const u_char *p;
-- char *userstyle, *cp;
-+ char *userstyle, *s, *cp;
- size_t len;
- u_char type;
- int hostbound = 0, r, fail = 0;
-@@ -1423,6 +1452,8 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
- fail++;
- if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
- fatal_fr(r, "parse userstyle");
-+ if ((s = strchr(cp, '/')) != NULL)
-+ *s = '\0';
- xasprintf(&userstyle, "%s%s%s", authctxt->user,
- authctxt->style ? ":" : "",
- authctxt->style ? authctxt->style : "");
-@@ -1473,7 +1504,7 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
- {
- struct sshbuf *b;
- const u_char *p;
-- char *cp, *userstyle;
-+ char *cp, *s, *userstyle;
- size_t len;
- int r, fail = 0;
- u_char type;
-@@ -1494,6 +1525,8 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
- fail++;
- if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
- fatal_fr(r, "parse userstyle");
-+ if ((s = strchr(cp, '/')) != NULL)
-+ *s = '\0';
- xasprintf(&userstyle, "%s%s%s", authctxt->user,
- authctxt->style ? ":" : "",
- authctxt->style ? authctxt->style : "");
-diff --git a/monitor.h b/monitor.h
-index fe0b00b2e..1b46e794e 100644
---- a/monitor.h
-+++ b/monitor.h
-@@ -57,6 +57,10 @@ enum monitor_reqtype {
- MONITOR_REQ_TERM = 50,
- MONITOR_REQ_STATE = 51, MONITOR_ANS_STATE = 52,
-
-+#ifdef WITH_SELINUX
-+ MONITOR_REQ_AUTHROLE = 80,
-+#endif
-+
- MONITOR_REQ_PAM_START = 100,
- MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103,
- MONITOR_REQ_PAM_INIT_CTX = 104, MONITOR_ANS_PAM_INIT_CTX = 105,
-diff --git a/monitor_wrap.c b/monitor_wrap.c
-index 81596a4cc..f3c341bc7 100644
---- a/monitor_wrap.c
-+++ b/monitor_wrap.c
-@@ -473,6 +473,27 @@ mm_inform_authserv(char *service, char *style)
- sshbuf_free(m);
- }
-
-+/* Inform the privileged process about role */
-+
-+#ifdef WITH_SELINUX
-+void
-+mm_inform_authrole(char *role)
-+{
-+ int r;
-+ struct sshbuf *m;
-+
-+ debug3_f("entering");
-+
-+ if ((m = sshbuf_new()) == NULL)
-+ fatal_f("sshbuf_new failed");
-+ if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
-+ fatal_f("buffer error: %s", ssh_err(r));
-+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
-+
-+ sshbuf_free(m);
-+}
-+#endif
-+
- /* Do the password authentication */
- int
- mm_auth_password(struct ssh *ssh, char *password)
-diff --git a/monitor_wrap.h b/monitor_wrap.h
-index c2f7f97d9..3e08ddd78 100644
---- a/monitor_wrap.h
-+++ b/monitor_wrap.h
-@@ -51,6 +51,9 @@ int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
- const u_char *, size_t, const char *, const char *,
- const char *, u_int compat);
- void mm_inform_authserv(char *, char *);
-+#ifdef WITH_SELINUX
-+void mm_inform_authrole(char *);
-+#endif
- struct passwd *mm_getpwnamallow(struct ssh *, const char *);
- char *mm_auth2_read_banner(void);
- int mm_auth_password(struct ssh *, char *);
---
-2.53.0
-
diff --git a/0001-openssh-6.7p1-coverity.patch b/0001-openssh-6.7p1-coverity.patch
new file mode 100644
index 0000000..b2b01f5
--- /dev/null
+++ b/0001-openssh-6.7p1-coverity.patch
@@ -0,0 +1,192 @@
+From fa6f77d16574bf45434431c2fd60fcd34e9a0c8f Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Fri, 7 Aug 2026 17:25:07 +0200
+Subject: [PATCH 01/53] openssh-6.7p1-coverity
+
+---
+ krl.c | 3 +++
+ loginrec.c | 2 ++
+ misc.c | 3 +++
+ monitor.c | 4 ++--
+ openbsd-compat/bindresvport.c | 2 +-
+ openbsd-compat/bsd-pselect.c | 8 ++++----
+ readconf.c | 1 +
+ servconf.c | 5 +++--
+ serverloop.c | 2 +-
+ 9 files changed, 20 insertions(+), 10 deletions(-)
+
+diff --git a/krl.c b/krl.c
+index 0e2b5f155..f2d105f23 100644
+--- a/krl.c
++++ b/krl.c
+@@ -1202,6 +1202,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
+ return r;
+ erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha1s, &rb);
+ free(rb.blob);
++ rb.blob = NULL; /* make coverity happy */
+ if (erb != NULL) {
+ KRL_DBG(("revoked by key SHA1"));
+ return SSH_ERR_KEY_REVOKED;
+@@ -1212,6 +1213,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
+ return r;
+ erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha256s, &rb);
+ free(rb.blob);
++ rb.blob = NULL; /* make coverity happy */
+ if (erb != NULL) {
+ KRL_DBG(("revoked by key SHA256"));
+ return SSH_ERR_KEY_REVOKED;
+@@ -1223,6 +1225,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
+ return r;
+ erb = RB_FIND(revoked_blob_tree, &krl->revoked_keys, &rb);
+ free(rb.blob);
++ rb.blob = NULL; /* make coverity happy */
+ if (erb != NULL) {
+ KRL_DBG(("revoked by explicit key"));
+ return SSH_ERR_KEY_REVOKED;
+diff --git a/loginrec.c b/loginrec.c
+index 7499aa975..2807cad4a 100644
+--- a/loginrec.c
++++ b/loginrec.c
+@@ -680,9 +680,11 @@ construct_utmp(struct logininfo *li,
+ */
+
+ /* Use strncpy because we don't necessarily want null termination */
++ /* coverity[buffer_size_warning : FALSE] */
+ strncpy(ut->ut_name, li->username,
+ MIN_SIZEOF(ut->ut_name, li->username));
+ # ifdef HAVE_HOST_IN_UTMP
++ /* coverity[buffer_size_warning : FALSE] */
+ strncpy(ut->ut_host, li->hostname,
+ MIN_SIZEOF(ut->ut_host, li->hostname));
+ # endif
+diff --git a/misc.c b/misc.c
+index 517fa7a97..88dc78821 100644
+--- a/misc.c
++++ b/misc.c
+@@ -1612,6 +1612,8 @@ sanitise_stdfd(void)
+ }
+ if (nullfd > STDERR_FILENO)
+ close(nullfd);
++ /* coverity[leaked_handle : FALSE]*/
++ /* coverity[leaked_handle : FALSE]*/
+ }
+
+ char *
+@@ -2815,6 +2817,7 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
+ }
+ if (devnull > STDERR_FILENO)
+ close(devnull);
++ /* coverity[leaked_handle : FALSE]*/
+ return ret;
+ }
+
+diff --git a/monitor.c b/monitor.c
+index 149671145..0e5871994 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -367,7 +367,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
+ mm_get_keystate(ssh, pmonitor);
+
+ /* Drain any buffered messages from the child */
+- while (pmonitor->m_log_recvfd != -1 && monitor_read_log(pmonitor) == 0)
++ while (pmonitor->m_log_recvfd >= 0 && monitor_read_log(pmonitor) == 0)
+ ;
+
+ /* Wait for the child's exit status */
+@@ -1733,7 +1733,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
+ s->ptymaster = s->ptyfd;
+
+ debug3_f("tty %s ptyfd %d", s->tty, s->ttyfd);
+-
++ /* coverity[leaked_handle : FALSE] */
+ return (0);
+
+ error:
+diff --git a/openbsd-compat/bindresvport.c b/openbsd-compat/bindresvport.c
+index 346c7fe56..f42792fde 100644
+--- a/openbsd-compat/bindresvport.c
++++ b/openbsd-compat/bindresvport.c
+@@ -59,7 +59,7 @@ bindresvport_sa(int sd, struct sockaddr *sa)
+ struct sockaddr_in6 *in6;
+ u_int16_t *portp;
+ u_int16_t port;
+- socklen_t salen;
++ socklen_t salen = sizeof(struct sockaddr_storage);
+ int i;
+
+ if (sa == NULL) {
+diff --git a/openbsd-compat/bsd-pselect.c b/openbsd-compat/bsd-pselect.c
+index 26bdc3e08..8e2939b95 100644
+--- a/openbsd-compat/bsd-pselect.c
++++ b/openbsd-compat/bsd-pselect.c
+@@ -85,13 +85,13 @@ pselect_notify_setup(void)
+ static void
+ pselect_notify_parent(void)
+ {
+- if (notify_pipe[1] != -1)
++ if (notify_pipe[1] >= 0)
+ (void)write(notify_pipe[1], "", 1);
+ }
+ static void
+ pselect_notify_prepare(fd_set *readset)
+ {
+- if (notify_pipe[0] != -1)
++ if (notify_pipe[0] >= 0)
+ FD_SET(notify_pipe[0], readset);
+ }
+ static void
+@@ -99,8 +99,8 @@ pselect_notify_done(fd_set *readset)
+ {
+ char c;
+
+- if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) {
+- while (read(notify_pipe[0], &c, 1) != -1)
++ if (notify_pipe[0] >= 0 && FD_ISSET(notify_pipe[0], readset)) {
++ while (read(notify_pipe[0], &c, 1) >= 0)
+ debug2_f("reading");
+ FD_CLR(notify_pipe[0], readset);
+ }
+diff --git a/readconf.c b/readconf.c
+index bc48b4f55..7465216f3 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2111,6 +2111,7 @@ parse_pubkey_algos:
+ } else if (r != 0) {
+ error("%.200s line %d: glob failed for %s.",
+ filename, linenum, arg2);
++ free(arg2);
+ goto out;
+ }
+ free(arg2);
+diff --git a/servconf.c b/servconf.c
+index ac0c31d95..e7819d59a 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -2026,8 +2026,9 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ if (*activep && *charptr == NULL) {
+ *charptr = tilde_expand_filename(arg, getuid());
+ /* increase optional counter */
+- if (intptr != NULL)
+- *intptr = *intptr + 1;
++ /* DEAD CODE intptr is still NULL ;)
++ if (intptr != NULL)
++ *intptr = *intptr + 1; */
+ }
+ break;
+
+diff --git a/serverloop.c b/serverloop.c
+index 9d8a3429e..c22681483 100644
+--- a/serverloop.c
++++ b/serverloop.c
+@@ -536,7 +536,7 @@ server_request_tun(struct ssh *ssh)
+ debug_f("invalid tun");
+ goto done;
+ }
+- if (auth_opts->force_tun_device != -1) {
++ if (auth_opts->force_tun_device >= 0) {
+ if (tun != SSH_TUNID_ANY &&
+ auth_opts->force_tun_device != (int)tun)
+ goto done;
+--
+2.55.0
+
diff --git a/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch b/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
new file mode 100644
index 0000000..a7f7030
--- /dev/null
+++ b/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
@@ -0,0 +1,416 @@
+From 3c47f2bb223e4e400fac08c12bf36a2a88fa55ba Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:06:32 +0200
+Subject: [PATCH 02/53] Add SELinux role and MLS (Multi-Level Security) support
+
+openssh-7.8p1-role-mls
+
+Add support for SELinux role-based access control and Multi-Level
+Security (MLS) by allowing users to specify their role as part of the
+username in the format user/role.
+
+Changes:
+- auth.h: Add role field to Authctxt structure
+- auth2.c: Parse role from username and inform monitor process
+- auth2-gss.c: Include role in GSSAPI MIC authentication
+- auth2-hostbased.c: Include role in hostbased authentication signature
+- auth2-pubkey.c: Include role in pubkey authentication userstyle
+- monitor.c: Add mm_answer_authrole() and handle role in authentication
+ blob validation
+- monitor_wrap.c: Add mm_inform_authrole() for privilege separation
+- misc.c: Update colon() to handle /role syntax in file paths
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth.h | 3 +++
+ auth2-gss.c | 11 ++++++++++-
+ auth2-hostbased.c | 9 +++++++++
+ auth2-pubkey.c | 19 +++++++++++++++----
+ auth2.c | 14 ++++++++++++++
+ misc.c | 18 +++++++++++++++++-
+ monitor.c | 37 +++++++++++++++++++++++++++++++++++--
+ monitor.h | 4 ++++
+ monitor_wrap.c | 21 +++++++++++++++++++++
+ monitor_wrap.h | 3 +++
+ 10 files changed, 131 insertions(+), 8 deletions(-)
+
+diff --git a/auth.h b/auth.h
+index 0f11458ca..efe819323 100644
+--- a/auth.h
++++ b/auth.h
+@@ -65,6 +65,9 @@ struct Authctxt {
+ char *service;
+ struct passwd *pw; /* set if 'valid' */
+ char *style;
++#ifdef WITH_SELINUX
++ char *role;
++#endif
+
+ /* Method lists for multiple authentication */
+ char **auth_methods; /* modified from server config */
+diff --git a/auth2-gss.c b/auth2-gss.c
+index 9e9496e53..553fba328 100644
+--- a/auth2-gss.c
++++ b/auth2-gss.c
+@@ -280,6 +280,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ Authctxt *authctxt = ssh->authctxt;
+ Gssctxt *gssctxt;
+ int r, authenticated = 0;
++ char *micuser;
+ struct sshbuf *b;
+ gss_buffer_desc mic, gssbuf;
+ u_char *p;
+@@ -297,7 +298,13 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ fatal_f("sshbuf_new failed");
+ mic.value = p;
+ mic.length = len;
+- ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
++#ifdef WITH_SELINUX
++ if (authctxt->role && authctxt->role[0] != 0)
++ xasprintf(&micuser, "%s/%s", authctxt->user, authctxt->role);
++ else
++#endif
++ micuser = authctxt->user;
++ ssh_gssapi_buildmic(b, micuser, authctxt->service,
+ "gssapi-with-mic", ssh->kex->session_id);
+
+ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
+@@ -310,6 +317,8 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ logit("GSSAPI MIC check failed");
+
+ sshbuf_free(b);
++ if (micuser != authctxt->user)
++ free(micuser);
+ free(mic.value);
+
+ if (!authenticated)
+diff --git a/auth2-hostbased.c b/auth2-hostbased.c
+index 564a8d3cc..6a6704d93 100644
+--- a/auth2-hostbased.c
++++ b/auth2-hostbased.c
+@@ -131,7 +131,16 @@ userauth_hostbased(struct ssh *ssh, const char *method)
+ /* reconstruct packet */
+ if ((r = sshbuf_put_stringb(b, ssh->kex->session_id)) != 0 ||
+ (r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
++#ifdef WITH_SELINUX
++ (authctxt->role
++ ? ( (r = sshbuf_put_u32(b, strlen(authctxt->user)+strlen(authctxt->role)+1)) != 0 ||
++ (r = sshbuf_put(b, authctxt->user, strlen(authctxt->user))) != 0 ||
++ (r = sshbuf_put_u8(b, '/') != 0) ||
++ (r = sshbuf_put(b, authctxt->role, strlen(authctxt->role))) != 0)
++ : (r = sshbuf_put_cstring(b, authctxt->user)) != 0) ||
++#else
+ (r = sshbuf_put_cstring(b, authctxt->user)) != 0 ||
++#endif
+ (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
+ (r = sshbuf_put_cstring(b, method)) != 0 ||
+ (r = sshbuf_put_string(b, pkalg, alen)) != 0 ||
+diff --git a/auth2-pubkey.c b/auth2-pubkey.c
+index c15b1ba71..dc07faa92 100644
+--- a/auth2-pubkey.c
++++ b/auth2-pubkey.c
+@@ -71,6 +71,8 @@
+
+ /* import */
+ extern ServerOptions options;
++extern int inetd_flag;
++extern Authctxt *the_authctxt;
+ extern struct authmethod_cfg methodcfg_pubkey;
+
+ static char *
+@@ -201,9 +203,16 @@ userauth_pubkey(struct ssh *ssh, const char *method)
+ goto done;
+ }
+ /* reconstruct packet */
+- xasprintf(&userstyle, "%s%s%s", authctxt->user,
++ xasprintf(&userstyle, "%s%s%s%s%s", authctxt->user,
+ authctxt->style ? ":" : "",
+- authctxt->style ? authctxt->style : "");
++ authctxt->style ? authctxt->style : "",
++#ifdef WITH_SELINUX
++ authctxt->role ? "/" : "",
++ authctxt->role ? authctxt->role : ""
++#else
++ "", ""
++#endif
++ );
+ if ((r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
+ (r = sshbuf_put_cstring(b, userstyle)) != 0 ||
+ (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
+@@ -473,7 +482,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key,
+ if ((pid = subprocess("AuthorizedPrincipalsCommand", command,
+ ac, av, &f,
+ SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
+- runas_pw, temporarily_use_uid, restore_uid)) == 0)
++ runas_pw, temporarily_use_uid, restore_uid,
++ inetd_flag, the_authctxt)) == 0)
+ goto out;
+
+ uid_swapped = 1;
+@@ -749,7 +759,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key,
+ if ((pid = subprocess("AuthorizedKeysCommand", command,
+ ac, av, &f,
+ SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
+- runas_pw, temporarily_use_uid, restore_uid)) == 0)
++ runas_pw, temporarily_use_uid, restore_uid,
++ inetd_flag, the_authctxt)) == 0)
+ goto out;
+
+ uid_swapped = 1;
+diff --git a/auth2.c b/auth2.c
+index 3f353a719..497fa8ed2 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -277,6 +277,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ Authctxt *authctxt = ssh->authctxt;
+ Authmethod *m = NULL;
+ char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
++#ifdef WITH_SELINUX
++ char *role = NULL;
++#endif
+ int r, authenticated = 0;
+ double tstart = monotime_double();
+
+@@ -290,6 +293,11 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ debug("userauth-request for user %s service %s method %s", user, service, method);
+ debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
+
++#ifdef WITH_SELINUX
++ if ((role = strchr(user, '/')) != NULL)
++ *role++ = 0;
++#endif
++
+ if ((style = strchr(user, ':')) != NULL)
+ *style++ = 0;
+
+@@ -301,6 +309,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ authctxt->user = xstrdup(user);
+ authctxt->service = xstrdup(service);
+ authctxt->style = style ? xstrdup(style) : NULL;
++#ifdef WITH_SELINUX
++ authctxt->role = role ? xstrdup(role) : NULL;
++#endif
+ if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
+ authctxt->valid = 1;
+ debug2_f("setting up authctxt for %s", user);
+@@ -320,6 +331,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ authctxt->valid ? "authenticating " : "invalid ", user);
+ setproctitle("%s [net]", authctxt->valid ? user : "unknown");
+ mm_inform_authserv(service, style);
++#ifdef WITH_SELINUX
++ mm_inform_authrole(role);
++#endif
+ userauth_banner(ssh);
+ if ((r = kex_server_update_ext_info(ssh)) != 0)
+ fatal_fr(r, "kex_server_update_ext_info failed");
+diff --git a/misc.c b/misc.c
+index 88dc78821..f5275d108 100644
+--- a/misc.c
++++ b/misc.c
+@@ -869,6 +869,7 @@ char *
+ colon(char *cp)
+ {
+ int flag = 0;
++ int start = 1;
+
+ if (*cp == ':') /* Leading colon is part of file name. */
+ return NULL;
+@@ -884,6 +885,13 @@ colon(char *cp)
+ return (cp);
+ if (*cp == '/')
+ return NULL;
++ if (start) {
++ /* Slash on beginning or after dots only denotes file name. */
++ if (*cp == '/')
++ return (0);
++ if (*cp != '.')
++ start = 0;
++ }
+ }
+ return NULL;
+ }
+@@ -2833,7 +2841,8 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
+ pid_t
+ subprocess(const char *tag, const char *command,
+ int ac, char **av, FILE **child, u_int flags,
+- struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs)
++ struct passwd *pw, privdrop_fn *drop_privs,
++ privrestore_fn *restore_privs, int inetd, void *the_authctxt)
+ {
+ FILE *f = NULL;
+ struct stat st;
+@@ -2966,6 +2975,13 @@ subprocess(const char *tag, const char *command,
+ error("%s: dup2: %s", tag, strerror(errno));
+ _exit(1);
+ }
++#ifdef WITH_SELINUX
++ if (sshd_selinux_setup_env_variables(inetd, the_authctxt) < 0) {
++ error ("failed to copy environment: %s",
++ strerror(errno));
++ _exit(127);
++ }
++#endif
+ if (env != NULL)
+ execve(av[0], av, env);
+ else
+diff --git a/monitor.c b/monitor.c
+index 0e5871994..c659e99e0 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -111,6 +111,9 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
+ int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
+ int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
+ int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
++#ifdef WITH_SELINUX
++int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
++#endif
+ int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
+@@ -188,6 +191,9 @@ struct mon_table mon_dispatch_proto20[] = {
+ {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
+ {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
+ {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
++#ifdef WITH_SELINUX
++ {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
++#endif
+ {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
+ {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
+ #ifdef USE_PAM
+@@ -932,6 +938,9 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
+
+ /* Allow service/style information on the auth context */
+ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
++#ifdef WITH_SELINUX
++ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
++#endif
+ monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
+
+ #ifdef USE_PAM
+@@ -1006,6 +1015,26 @@ key_base_type_match(const char *method, const struct sshkey *key,
+ return found;
+ }
+
++#ifdef WITH_SELINUX
++int
++mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++ int r;
++ monitor_permit_authentications(1);
++
++ if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
++ fatal_f("buffer error: %s", ssh_err(r));
++ debug3_f("role=%s", authctxt->role);
++
++ if (strlen(authctxt->role) == 0) {
++ free(authctxt->role);
++ authctxt->role = NULL;
++ }
++
++ return (0);
++}
++#endif
++
+ int
+ mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m)
+ {
+@@ -1378,7 +1407,7 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
+ struct sshbuf *b;
+ struct sshkey *hostkey = NULL;
+ const u_char *p;
+- char *userstyle, *cp;
++ char *userstyle, *s, *cp;
+ size_t len;
+ u_char type;
+ int hostbound = 0, r, fail = 0;
+@@ -1409,6 +1438,8 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
+ fail++;
+ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ fatal_fr(r, "parse userstyle");
++ if ((s = strchr(cp, '/')) != NULL)
++ *s = '\0';
+ xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ authctxt->style ? ":" : "",
+ authctxt->style ? authctxt->style : "");
+@@ -1459,7 +1490,7 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
+ {
+ struct sshbuf *b;
+ const u_char *p;
+- char *cp, *userstyle;
++ char *cp, *s, *userstyle;
+ size_t len;
+ int r, fail = 0;
+ u_char type;
+@@ -1480,6 +1511,8 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
+ fail++;
+ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ fatal_fr(r, "parse userstyle");
++ if ((s = strchr(cp, '/')) != NULL)
++ *s = '\0';
+ xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ authctxt->style ? ":" : "",
+ authctxt->style ? authctxt->style : "");
+diff --git a/monitor.h b/monitor.h
+index fe0b00b2e..1b46e794e 100644
+--- a/monitor.h
++++ b/monitor.h
+@@ -57,6 +57,10 @@ enum monitor_reqtype {
+ MONITOR_REQ_TERM = 50,
+ MONITOR_REQ_STATE = 51, MONITOR_ANS_STATE = 52,
+
++#ifdef WITH_SELINUX
++ MONITOR_REQ_AUTHROLE = 80,
++#endif
++
+ MONITOR_REQ_PAM_START = 100,
+ MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103,
+ MONITOR_REQ_PAM_INIT_CTX = 104, MONITOR_ANS_PAM_INIT_CTX = 105,
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index e9a771b34..af0252964 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -440,6 +440,27 @@ mm_inform_authserv(char *service, char *style)
+ sshbuf_free(m);
+ }
+
++/* Inform the privileged process about role */
++
++#ifdef WITH_SELINUX
++void
++mm_inform_authrole(char *role)
++{
++ int r;
++ struct sshbuf *m;
++
++ debug3_f("entering");
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++ if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
++ fatal_f("buffer error: %s", ssh_err(r));
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
++
++ sshbuf_free(m);
++}
++#endif
++
+ /* Do the password authentication */
+ int
+ mm_auth_password(struct ssh *ssh, char *password)
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index f43759b21..30585187c 100644
+--- a/monitor_wrap.h
++++ b/monitor_wrap.h
+@@ -51,6 +51,9 @@ int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
+ const u_char *, size_t, const char *, const char *,
+ const char *, u_int compat);
+ void mm_inform_authserv(char *, char *);
++#ifdef WITH_SELINUX
++void mm_inform_authrole(char *);
++#endif
+ struct passwd *mm_getpwnamallow(struct ssh *, const char *);
+ char *mm_auth2_read_banner(void);
+ int mm_auth_password(struct ssh *, char *);
+--
+2.55.0
+
diff --git a/0002-Implement-SELinux-environment-variable-setup-for-sub.patch b/0002-Implement-SELinux-environment-variable-setup-for-sub.patch
deleted file mode 100644
index e54ff03..0000000
--- a/0002-Implement-SELinux-environment-variable-setup-for-sub.patch
+++ /dev/null
@@ -1,677 +0,0 @@
-From 8b25614132c78116498a2b94634b2b8bacf82f5f Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:06:47 +0200
-Subject: [PATCH 02/54] Implement SELinux environment variable setup for
- subprocess execution
-
-openssh-6.6p1-selinux-env-setup
-
-Implement comprehensive SELinux context management for sshd, including
-role selection, MLS level validation, and environment variable setup for
-pam_selinux integration.
-
-Changes:
-- openbsd-compat/port-linux-sshd.c: New file with SELinux context
- management functions including sshd_selinux_getctxbyname(),
- sshd_selinux_setup_exec_context(), and environment variable setup
-- openbsd-compat/Makefile.in: Add port-linux-sshd.o to build
-- openbsd-compat/port-linux.c: Remove old ssh_selinux_setup_exec_context(),
- update ssh_selinux_setup_pty() to use getexeccon()
-- openbsd-compat/port-linux.h: Update function declarations for
- sshd-specific SELinux functions
-- platform.c: Replace ssh_selinux_setup_exec_context() with
- sshd_selinux_setup_exec_context() and pass additional parameters
- (inetd_flag, do_pam_putenv, the_authctxt, options.use_pam)
-- sshd-session.c: Add SELinux execution context setup call in main()
-- auth-pam.c: Change do_pam_putenv() value parameter to const char *
-- auth-pam.h: Update do_pam_putenv() signature and add do_pam_chauthtok()
- declaration
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-pam.c | 2 +-
- auth-pam.h | 3 +-
- openbsd-compat/Makefile.in | 3 +-
- openbsd-compat/port-linux-sshd.c | 454 +++++++++++++++++++++++++++++++
- openbsd-compat/port-linux.c | 37 +--
- openbsd-compat/port-linux.h | 5 +-
- platform.c | 8 +-
- sshd-session.c | 7 +-
- 8 files changed, 480 insertions(+), 39 deletions(-)
- create mode 100644 openbsd-compat/port-linux-sshd.c
-
-diff --git a/auth-pam.c b/auth-pam.c
-index 29607e041..3f342f9e1 100644
---- a/auth-pam.c
-+++ b/auth-pam.c
-@@ -1145,7 +1145,7 @@ is_pam_session_open(void)
- * during the ssh authentication process.
- */
- int
--do_pam_putenv(char *name, char *value)
-+do_pam_putenv(char *name, const char *value)
- {
- int ret = 1;
- char *compound;
-diff --git a/auth-pam.h b/auth-pam.h
-index 491336701..f9167cfc8 100644
---- a/auth-pam.h
-+++ b/auth-pam.h
-@@ -32,7 +32,8 @@ void finish_pam(void);
- u_int do_pam_account(void);
- void do_pam_session(struct ssh *);
- void do_pam_setcred(void);
--int do_pam_putenv(char *, char *);
-+void do_pam_chauthtok(void);
-+int do_pam_putenv(char *, const char *);
- char ** fetch_pam_environment(void);
- char ** fetch_pam_child_environment(void);
- void free_pam_environment(char **);
-diff --git a/openbsd-compat/Makefile.in b/openbsd-compat/Makefile.in
-index 53c87db6d..39531ae77 100644
---- a/openbsd-compat/Makefile.in
-+++ b/openbsd-compat/Makefile.in
-@@ -102,7 +102,8 @@ PORTS= port-aix.o \
- port-prngd.o \
- port-solaris.o \
- port-net.o \
-- port-uw.o
-+ port-uw.o \
-+ port-linux-sshd.o
-
- .c.o:
- $(CC) $(CFLAGS_NOPIE) $(PICFLAG) $(CPPFLAGS) -c $<
-diff --git a/openbsd-compat/port-linux-sshd.c b/openbsd-compat/port-linux-sshd.c
-new file mode 100644
-index 000000000..ab083a637
---- /dev/null
-+++ b/openbsd-compat/port-linux-sshd.c
-@@ -0,0 +1,454 @@
-+/*
-+ * Copyright (c) 2005 Daniel Walsh <dwalsh@redhat.com>
-+ * Copyright (c) 2014 Petr Lautrbach <plautrba@redhat.com>
-+ *
-+ * 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.
-+ */
-+
-+/*
-+ * Linux-specific portability code - just SELinux support for sshd at present
-+ */
-+
-+#include "includes.h"
-+
-+#if defined(WITH_SELINUX) || defined(LINUX_OOM_ADJUST)
-+#include <errno.h>
-+#include <stdarg.h>
-+#include <string.h>
-+#include <stdio.h>
-+#include <stdlib.h>
-+
-+#include "log.h"
-+#include "xmalloc.h"
-+#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */
-+#include "servconf.h"
-+#include "port-linux.h"
-+#include "sshkey.h"
-+#include "hostfile.h"
-+#include "auth.h"
-+
-+#ifdef WITH_SELINUX
-+#include <selinux/selinux.h>
-+#include <selinux/context.h>
-+#include <selinux/get_context_list.h>
-+#include <selinux/get_default_type.h>
-+
-+#ifdef HAVE_LINUX_AUDIT
-+#include <libaudit.h>
-+#include <unistd.h>
-+#endif
-+
-+/* Wrapper around is_selinux_enabled() to log its return value once only */
-+int
-+sshd_selinux_enabled(void)
-+{
-+ static int enabled = -1;
-+
-+ if (enabled == -1) {
-+ enabled = (is_selinux_enabled() == 1);
-+ debug("SELinux support %s", enabled ? "enabled" : "disabled");
-+ }
-+
-+ return (enabled);
-+}
-+
-+/* Send audit message */
-+static int
-+sshd_selinux_send_audit_message(int success, security_context_t default_context,
-+ security_context_t selected_context)
-+{
-+ int rc=0;
-+#ifdef HAVE_LINUX_AUDIT
-+ char *msg = NULL;
-+ int audit_fd = audit_open();
-+ security_context_t default_raw=NULL;
-+ security_context_t selected_raw=NULL;
-+ rc = -1;
-+ if (audit_fd < 0) {
-+ if (errno == EINVAL || errno == EPROTONOSUPPORT ||
-+ errno == EAFNOSUPPORT)
-+ return 0; /* No audit support in kernel */
-+ error("Error connecting to audit system.");
-+ return rc;
-+ }
-+ if (selinux_trans_to_raw_context(default_context, &default_raw) < 0) {
-+ error("Error translating default context.");
-+ default_raw = NULL;
-+ }
-+ if (selinux_trans_to_raw_context(selected_context, &selected_raw) < 0) {
-+ error("Error translating selected context.");
-+ selected_raw = NULL;
-+ }
-+ if (asprintf(&msg, "sshd: default-context=%s selected-context=%s",
-+ default_raw ? default_raw : (default_context ? default_context: "?"),
-+ selected_context ? selected_raw : (selected_context ? selected_context :"?")) < 0) {
-+ error("Error allocating memory.");
-+ goto out;
-+ }
-+ if (audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
-+ msg, NULL, NULL, NULL, success) <= 0) {
-+ error("Error sending audit message.");
-+ goto out;
-+ }
-+ rc = 0;
-+ out:
-+ free(msg);
-+ freecon(default_raw);
-+ freecon(selected_raw);
-+ close(audit_fd);
-+#endif
-+ return rc;
-+}
-+
-+static int
-+mls_range_allowed(security_context_t src, security_context_t dst)
-+{
-+ struct av_decision avd;
-+ int retval;
-+ access_vector_t bit;
-+ security_class_t class;
-+
-+ debug_f("src:%s dst:%s", src, dst);
-+ class = string_to_security_class("context");
-+ if (!class) {
-+ error("string_to_security_class failed to translate security class context");
-+ return 1;
-+ }
-+ bit = string_to_av_perm(class, "contains");
-+ if (!bit) {
-+ error("string_to_av_perm failed to translate av perm contains");
-+ return 1;
-+ }
-+ retval = security_compute_av(src, dst, class, bit, &avd);
-+ if (retval || ((bit & avd.allowed) != bit))
-+ return 0;
-+
-+ return 1;
-+}
-+
-+static int
-+get_user_context(const char *sename, const char *role, const char *lvl,
-+ security_context_t *sc) {
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+ if (lvl == NULL || lvl[0] == '\0' || get_default_context_with_level(sename, lvl, NULL, sc) != 0) {
-+ /* User may have requested a level completely outside of his
-+ allowed range. We get a context just for auditing as the
-+ range check below will certainly fail for default context. */
-+#endif
-+ if (get_default_context(sename, NULL, sc) != 0) {
-+ *sc = NULL;
-+ return -1;
-+ }
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+ }
-+#endif
-+ if (role != NULL && role[0]) {
-+ context_t con;
-+ char *type=NULL;
-+ if (get_default_type(role, &type) != 0) {
-+ error("get_default_type: failed to get default type for '%s'",
-+ role);
-+ goto out;
-+ }
-+ con = context_new(*sc);
-+ if (!con) {
-+ goto out;
-+ }
-+ context_role_set(con, role);
-+ context_type_set(con, type);
-+ freecon(*sc);
-+ *sc = strdup(context_str(con));
-+ context_free(con);
-+ if (!*sc)
-+ return -1;
-+ }
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+ if (lvl != NULL && lvl[0]) {
-+ /* verify that the requested range is obtained */
-+ context_t con;
-+ security_context_t obtained_raw;
-+ security_context_t requested_raw;
-+ con = context_new(*sc);
-+ if (!con) {
-+ goto out;
-+ }
-+ context_range_set(con, lvl);
-+ if (selinux_trans_to_raw_context(*sc, &obtained_raw) < 0) {
-+ context_free(con);
-+ goto out;
-+ }
-+ if (selinux_trans_to_raw_context(context_str(con), &requested_raw) < 0) {
-+ freecon(obtained_raw);
-+ context_free(con);
-+ goto out;
-+ }
-+
-+ debug("get_user_context: obtained context '%s' requested context '%s'",
-+ obtained_raw, requested_raw);
-+ if (strcmp(obtained_raw, requested_raw)) {
-+ /* set the context to the real requested one but fail */
-+ freecon(requested_raw);
-+ freecon(obtained_raw);
-+ freecon(*sc);
-+ *sc = strdup(context_str(con));
-+ context_free(con);
-+ return -1;
-+ }
-+ freecon(requested_raw);
-+ freecon(obtained_raw);
-+ context_free(con);
-+ }
-+#endif
-+ return 0;
-+ out:
-+ freecon(*sc);
-+ *sc = NULL;
-+ return -1;
-+}
-+
-+static void
-+ssh_selinux_get_role_level(char **role, const char **level,
-+ Authctxt *the_authctxt)
-+{
-+ *role = NULL;
-+ *level = NULL;
-+ if (the_authctxt) {
-+ if (the_authctxt->role != NULL) {
-+ char *slash;
-+ *role = xstrdup(the_authctxt->role);
-+ if ((slash = strchr(*role, '/')) != NULL) {
-+ *slash = '\0';
-+ *level = slash + 1;
-+ }
-+ }
-+ }
-+}
-+
-+/* Return the default security context for the given username */
-+static int
-+sshd_selinux_getctxbyname(char *pwname, security_context_t *default_sc,
-+ security_context_t *user_sc, int inetd, Authctxt *the_authctxt)
-+{
-+ char *sename, *lvl;
-+ char *role;
-+ const char *reqlvl;
-+ int r = 0;
-+ context_t con = NULL;
-+
-+ ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
-+
-+#ifdef HAVE_GETSEUSERBYNAME
-+ if ((r=getseuserbyname(pwname, &sename, &lvl)) != 0) {
-+ sename = NULL;
-+ lvl = NULL;
-+ }
-+#else
-+ sename = pwname;
-+ lvl = "";
-+#endif
-+
-+ if (r == 0) {
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+ r = get_default_context_with_level(sename, lvl, NULL, default_sc);
-+#else
-+ r = get_default_context(sename, NULL, default_sc);
-+#endif
-+ }
-+
-+ if (r == 0) {
-+ /* If launched from xinetd, we must use current level */
-+ if (inetd) {
-+ security_context_t sshdsc=NULL;
-+
-+ if (getcon_raw(&sshdsc) < 0)
-+ fatal("failed to allocate security context");
-+
-+ if ((con=context_new(sshdsc)) == NULL)
-+ fatal("failed to allocate selinux context");
-+ reqlvl = context_range_get(con);
-+ freecon(sshdsc);
-+ if (reqlvl !=NULL && lvl != NULL && strcmp(reqlvl, lvl) == 0)
-+ /* we actually don't change level */
-+ reqlvl = "";
-+
-+ debug_f("current connection level '%s'", reqlvl);
-+
-+ }
-+
-+ if ((reqlvl != NULL && reqlvl[0]) || (role != NULL && role[0])) {
-+ r = get_user_context(sename, role, reqlvl, user_sc);
-+
-+ if (r == 0 && reqlvl != NULL && reqlvl[0]) {
-+ security_context_t default_level_sc = *default_sc;
-+ if (role != NULL && role[0]) {
-+ if (get_user_context(sename, role, lvl, &default_level_sc) < 0)
-+ default_level_sc = *default_sc;
-+ }
-+ /* verify that the requested range is contained in the user range */
-+ if (mls_range_allowed(default_level_sc, *user_sc)) {
-+ logit("permit MLS level %s (user range %s)", reqlvl, lvl);
-+ } else {
-+ r = -1;
-+ error("deny MLS level %s (user range %s)", reqlvl, lvl);
-+ }
-+ if (default_level_sc != *default_sc)
-+ freecon(default_level_sc);
-+ }
-+ } else {
-+ *user_sc = *default_sc;
-+ }
-+ }
-+ if (r != 0) {
-+ error_f("Failed to get default SELinux security "
-+ "context for %s", pwname);
-+ }
-+
-+#ifdef HAVE_GETSEUSERBYNAME
-+ free(sename);
-+ free(lvl);
-+#endif
-+
-+ if (role != NULL)
-+ free(role);
-+ if (con)
-+ context_free(con);
-+
-+ return (r);
-+}
-+
-+/* Setup environment variables for pam_selinux */
-+static int
-+sshd_selinux_setup_variables(int(*set_it)(char *, const char *), int inetd,
-+ Authctxt *the_authctxt)
-+{
-+ const char *reqlvl;
-+ char *role;
-+ char *use_current;
-+ int rv;
-+
-+ debug3_f("setting execution context");
-+
-+ ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
-+
-+ rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
-+
-+ if (inetd) {
-+ use_current = "1";
-+ } else {
-+ use_current = "";
-+ rv = rv || set_it("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
-+ }
-+
-+ rv = rv || set_it("SELINUX_USE_CURRENT_RANGE", use_current);
-+
-+ if (role != NULL)
-+ free(role);
-+
-+ return rv;
-+}
-+
-+static int
-+sshd_selinux_setup_pam_variables(int inetd,
-+ int(pam_setenv)(char *, const char *), Authctxt *the_authctxt)
-+{
-+ return sshd_selinux_setup_variables(pam_setenv, inetd, the_authctxt);
-+}
-+
-+static int
-+do_setenv(char *name, const char *value)
-+{
-+ return setenv(name, value, 1);
-+}
-+
-+int
-+sshd_selinux_setup_env_variables(int inetd, void *the_authctxt)
-+{
-+ Authctxt *authctxt = (Authctxt *) the_authctxt;
-+ return sshd_selinux_setup_variables(do_setenv, inetd, authctxt);
-+}
-+
-+/* Set the execution context to the default for the specified user */
-+void
-+sshd_selinux_setup_exec_context(char *pwname, int inetd,
-+ int(pam_setenv)(char *, const char *), void *the_authctxt, int use_pam)
-+{
-+ security_context_t user_ctx = NULL;
-+ int r = 0;
-+ security_context_t default_ctx = NULL;
-+ Authctxt *authctxt = (Authctxt *) the_authctxt;
-+
-+ if (!sshd_selinux_enabled())
-+ return;
-+
-+ if (use_pam) {
-+ /* do not compute context, just setup environment for pam_selinux */
-+ if (sshd_selinux_setup_pam_variables(inetd, pam_setenv, authctxt)) {
-+ switch (security_getenforce()) {
-+ case -1:
-+ fatal_f("security_getenforce() failed");
-+ case 0:
-+ error_f("SELinux PAM variable setup failure. Continuing in permissive mode.");
-+ break;
-+ default:
-+ fatal_f("SELinux PAM variable setup failure. Aborting connection.");
-+ }
-+ }
-+ return;
-+ }
-+
-+ debug3_f("setting execution context");
-+
-+ r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx, inetd, authctxt);
-+ if (r >= 0) {
-+ r = setexeccon(user_ctx);
-+ if (r < 0) {
-+ error_f("Failed to set SELinux execution context %s for %s",
-+ user_ctx, pwname);
-+ }
-+#ifdef HAVE_SETKEYCREATECON
-+ else if (setkeycreatecon(user_ctx) < 0) {
-+ error_f("Failed to set SELinux keyring creation context %s for %s",
-+ user_ctx, pwname);
-+ }
-+#endif
-+ }
-+ if (user_ctx == NULL) {
-+ user_ctx = default_ctx;
-+ }
-+ if (r < 0 || user_ctx != default_ctx) {
-+ /* audit just the case when user changed a role or there was
-+ a failure */
-+ sshd_selinux_send_audit_message(r >= 0, default_ctx, user_ctx);
-+ }
-+ if (r < 0) {
-+ switch (security_getenforce()) {
-+ case -1:
-+ fatal_f("security_getenforce() failed");
-+ case 0:
-+ error_f("ELinux failure. Continuing in permissive mode.");
-+ break;
-+ default:
-+ fatal_f("SELinux failure. Aborting connection.");
-+ }
-+ }
-+ if (user_ctx != NULL && user_ctx != default_ctx)
-+ freecon(user_ctx);
-+ if (default_ctx != NULL)
-+ freecon(default_ctx);
-+
-+ debug3_f("done");
-+}
-+
-+#endif
-+#endif
-+
-diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
-index c1d54f38d..7426f6f79 100644
---- a/openbsd-compat/port-linux.c
-+++ b/openbsd-compat/port-linux.c
-@@ -109,37 +109,6 @@ ssh_selinux_getctxbyname(char *pwname)
- return sc;
- }
-
--/* Set the execution context to the default for the specified user */
--void
--ssh_selinux_setup_exec_context(char *pwname)
--{
-- char *user_ctx = NULL;
--
-- if (!ssh_selinux_enabled())
-- return;
--
-- debug3("%s: setting execution context", __func__);
--
-- user_ctx = ssh_selinux_getctxbyname(pwname);
-- if (setexeccon(user_ctx) != 0) {
-- switch (security_getenforce()) {
-- case -1:
-- fatal("%s: security_getenforce() failed", __func__);
-- case 0:
-- error("%s: Failed to set SELinux execution "
-- "context for %s", __func__, pwname);
-- break;
-- default:
-- fatal("%s: Failed to set SELinux execution context "
-- "for %s (in enforcing mode)", __func__, pwname);
-- }
-- }
-- if (user_ctx != NULL)
-- freecon(user_ctx);
--
-- debug3("%s: done", __func__);
--}
--
- /* Set the TTY context for the specified user */
- void
- ssh_selinux_setup_pty(char *pwname, const char *tty)
-@@ -152,7 +121,11 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
-
- debug3("%s: setting TTY context on %s", __func__, tty);
-
-- user_ctx = ssh_selinux_getctxbyname(pwname);
-+ if (getexeccon(&user_ctx) != 0) {
-+ error_f("getexeccon: %s", strerror(errno));
-+ goto out;
-+ }
-+
-
- /* XXX: should these calls fatal() upon failure in enforcing mode? */
-
-diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
-index 959430de1..26c5f773b 100644
---- a/openbsd-compat/port-linux.h
-+++ b/openbsd-compat/port-linux.h
-@@ -20,9 +20,12 @@
- #ifdef WITH_SELINUX
- int ssh_selinux_enabled(void);
- void ssh_selinux_setup_pty(char *, const char *);
--void ssh_selinux_setup_exec_context(char *);
- void ssh_selinux_change_context(const char *);
- void ssh_selinux_setfscreatecon(const char *);
-+
-+int sshd_selinux_enabled(void);
-+void sshd_selinux_setup_exec_context(char *, int, int(char *, const char *), void *, int);
-+int sshd_selinux_setup_env_variables(int inetd, void *);
- #endif
-
- #ifdef LINUX_OOM_ADJUST
-diff --git a/platform.c b/platform.c
-index fd1a7a7c2..66d0c2a6b 100644
---- a/platform.c
-+++ b/platform.c
-@@ -33,6 +33,8 @@
- #include "openbsd-compat/openbsd-compat.h"
-
- extern ServerOptions options;
-+extern int inetd_flag;
-+extern Authctxt *the_authctxt;
-
- /* return 1 if we are running with privilege to swap UIDs, 0 otherwise */
- int
-@@ -55,7 +57,7 @@ platform_setusercontext(struct passwd *pw)
- {
- #ifdef WITH_SELINUX
- /* Cache selinux status for later use */
-- (void)ssh_selinux_enabled();
-+ (void)sshd_selinux_enabled();
- #endif
-
- #ifdef USE_SOLARIS_PROJECTS
-@@ -140,7 +142,9 @@ platform_setusercontext_post_groups(struct passwd *pw)
- }
- #endif /* HAVE_SETPCRED */
- #ifdef WITH_SELINUX
-- ssh_selinux_setup_exec_context(pw->pw_name);
-+ sshd_selinux_setup_exec_context(pw->pw_name,
-+ inetd_flag, do_pam_putenv, the_authctxt,
-+ options.use_pam);
- #endif
- }
-
-diff --git a/sshd-session.c b/sshd-session.c
-index e9a488d08..967737c5b 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -123,7 +123,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
- int debug_flag = 0;
-
- /* Flag indicating that the daemon is being started from inetd. */
--static int inetd_flag = 0;
-+int inetd_flag = 0;
-
- /* debug goes to stderr unless inetd_flag is set */
- static int log_stderr = 0;
-@@ -1274,6 +1274,11 @@ main(int ac, char **av)
- restore_uid();
- }
- #endif
-+#ifdef WITH_SELINUX
-+ sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
-+ inetd_flag, do_pam_putenv, the_authctxt,
-+ options.use_pam);
-+#endif
- #ifdef USE_PAM
- if (options.use_pam) {
- do_pam_setcred();
---
-2.53.0
-
diff --git a/0003-Implement-SELinux-environment-variable-setup-for-sub.patch b/0003-Implement-SELinux-environment-variable-setup-for-sub.patch
new file mode 100644
index 0000000..62edf2c
--- /dev/null
+++ b/0003-Implement-SELinux-environment-variable-setup-for-sub.patch
@@ -0,0 +1,677 @@
+From c5df0bfbb8de0b1536476f200a9af3ecf0c10485 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:06:47 +0200
+Subject: [PATCH 03/53] Implement SELinux environment variable setup for
+ subprocess execution
+
+openssh-6.6p1-selinux-env-setup
+
+Implement comprehensive SELinux context management for sshd, including
+role selection, MLS level validation, and environment variable setup for
+pam_selinux integration.
+
+Changes:
+- openbsd-compat/port-linux-sshd.c: New file with SELinux context
+ management functions including sshd_selinux_getctxbyname(),
+ sshd_selinux_setup_exec_context(), and environment variable setup
+- openbsd-compat/Makefile.in: Add port-linux-sshd.o to build
+- openbsd-compat/port-linux.c: Remove old ssh_selinux_setup_exec_context(),
+ update ssh_selinux_setup_pty() to use getexeccon()
+- openbsd-compat/port-linux.h: Update function declarations for
+ sshd-specific SELinux functions
+- platform.c: Replace ssh_selinux_setup_exec_context() with
+ sshd_selinux_setup_exec_context() and pass additional parameters
+ (inetd_flag, do_pam_putenv, the_authctxt, options.use_pam)
+- sshd-session.c: Add SELinux execution context setup call in main()
+- auth-pam.c: Change do_pam_putenv() value parameter to const char *
+- auth-pam.h: Update do_pam_putenv() signature and add do_pam_chauthtok()
+ declaration
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-pam.c | 2 +-
+ auth-pam.h | 3 +-
+ openbsd-compat/Makefile.in | 3 +-
+ openbsd-compat/port-linux-sshd.c | 454 +++++++++++++++++++++++++++++++
+ openbsd-compat/port-linux.c | 37 +--
+ openbsd-compat/port-linux.h | 5 +-
+ platform.c | 8 +-
+ sshd-session.c | 7 +-
+ 8 files changed, 480 insertions(+), 39 deletions(-)
+ create mode 100644 openbsd-compat/port-linux-sshd.c
+
+diff --git a/auth-pam.c b/auth-pam.c
+index 29607e041..3f342f9e1 100644
+--- a/auth-pam.c
++++ b/auth-pam.c
+@@ -1145,7 +1145,7 @@ is_pam_session_open(void)
+ * during the ssh authentication process.
+ */
+ int
+-do_pam_putenv(char *name, char *value)
++do_pam_putenv(char *name, const char *value)
+ {
+ int ret = 1;
+ char *compound;
+diff --git a/auth-pam.h b/auth-pam.h
+index 491336701..f9167cfc8 100644
+--- a/auth-pam.h
++++ b/auth-pam.h
+@@ -32,7 +32,8 @@ void finish_pam(void);
+ u_int do_pam_account(void);
+ void do_pam_session(struct ssh *);
+ void do_pam_setcred(void);
+-int do_pam_putenv(char *, char *);
++void do_pam_chauthtok(void);
++int do_pam_putenv(char *, const char *);
+ char ** fetch_pam_environment(void);
+ char ** fetch_pam_child_environment(void);
+ void free_pam_environment(char **);
+diff --git a/openbsd-compat/Makefile.in b/openbsd-compat/Makefile.in
+index 53c87db6d..39531ae77 100644
+--- a/openbsd-compat/Makefile.in
++++ b/openbsd-compat/Makefile.in
+@@ -102,7 +102,8 @@ PORTS= port-aix.o \
+ port-prngd.o \
+ port-solaris.o \
+ port-net.o \
+- port-uw.o
++ port-uw.o \
++ port-linux-sshd.o
+
+ .c.o:
+ $(CC) $(CFLAGS_NOPIE) $(PICFLAG) $(CPPFLAGS) -c $<
+diff --git a/openbsd-compat/port-linux-sshd.c b/openbsd-compat/port-linux-sshd.c
+new file mode 100644
+index 000000000..ab083a637
+--- /dev/null
++++ b/openbsd-compat/port-linux-sshd.c
+@@ -0,0 +1,454 @@
++/*
++ * Copyright (c) 2005 Daniel Walsh <dwalsh@redhat.com>
++ * Copyright (c) 2014 Petr Lautrbach <plautrba@redhat.com>
++ *
++ * 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.
++ */
++
++/*
++ * Linux-specific portability code - just SELinux support for sshd at present
++ */
++
++#include "includes.h"
++
++#if defined(WITH_SELINUX) || defined(LINUX_OOM_ADJUST)
++#include <errno.h>
++#include <stdarg.h>
++#include <string.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include "log.h"
++#include "xmalloc.h"
++#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */
++#include "servconf.h"
++#include "port-linux.h"
++#include "sshkey.h"
++#include "hostfile.h"
++#include "auth.h"
++
++#ifdef WITH_SELINUX
++#include <selinux/selinux.h>
++#include <selinux/context.h>
++#include <selinux/get_context_list.h>
++#include <selinux/get_default_type.h>
++
++#ifdef HAVE_LINUX_AUDIT
++#include <libaudit.h>
++#include <unistd.h>
++#endif
++
++/* Wrapper around is_selinux_enabled() to log its return value once only */
++int
++sshd_selinux_enabled(void)
++{
++ static int enabled = -1;
++
++ if (enabled == -1) {
++ enabled = (is_selinux_enabled() == 1);
++ debug("SELinux support %s", enabled ? "enabled" : "disabled");
++ }
++
++ return (enabled);
++}
++
++/* Send audit message */
++static int
++sshd_selinux_send_audit_message(int success, security_context_t default_context,
++ security_context_t selected_context)
++{
++ int rc=0;
++#ifdef HAVE_LINUX_AUDIT
++ char *msg = NULL;
++ int audit_fd = audit_open();
++ security_context_t default_raw=NULL;
++ security_context_t selected_raw=NULL;
++ rc = -1;
++ if (audit_fd < 0) {
++ if (errno == EINVAL || errno == EPROTONOSUPPORT ||
++ errno == EAFNOSUPPORT)
++ return 0; /* No audit support in kernel */
++ error("Error connecting to audit system.");
++ return rc;
++ }
++ if (selinux_trans_to_raw_context(default_context, &default_raw) < 0) {
++ error("Error translating default context.");
++ default_raw = NULL;
++ }
++ if (selinux_trans_to_raw_context(selected_context, &selected_raw) < 0) {
++ error("Error translating selected context.");
++ selected_raw = NULL;
++ }
++ if (asprintf(&msg, "sshd: default-context=%s selected-context=%s",
++ default_raw ? default_raw : (default_context ? default_context: "?"),
++ selected_context ? selected_raw : (selected_context ? selected_context :"?")) < 0) {
++ error("Error allocating memory.");
++ goto out;
++ }
++ if (audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
++ msg, NULL, NULL, NULL, success) <= 0) {
++ error("Error sending audit message.");
++ goto out;
++ }
++ rc = 0;
++ out:
++ free(msg);
++ freecon(default_raw);
++ freecon(selected_raw);
++ close(audit_fd);
++#endif
++ return rc;
++}
++
++static int
++mls_range_allowed(security_context_t src, security_context_t dst)
++{
++ struct av_decision avd;
++ int retval;
++ access_vector_t bit;
++ security_class_t class;
++
++ debug_f("src:%s dst:%s", src, dst);
++ class = string_to_security_class("context");
++ if (!class) {
++ error("string_to_security_class failed to translate security class context");
++ return 1;
++ }
++ bit = string_to_av_perm(class, "contains");
++ if (!bit) {
++ error("string_to_av_perm failed to translate av perm contains");
++ return 1;
++ }
++ retval = security_compute_av(src, dst, class, bit, &avd);
++ if (retval || ((bit & avd.allowed) != bit))
++ return 0;
++
++ return 1;
++}
++
++static int
++get_user_context(const char *sename, const char *role, const char *lvl,
++ security_context_t *sc) {
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++ if (lvl == NULL || lvl[0] == '\0' || get_default_context_with_level(sename, lvl, NULL, sc) != 0) {
++ /* User may have requested a level completely outside of his
++ allowed range. We get a context just for auditing as the
++ range check below will certainly fail for default context. */
++#endif
++ if (get_default_context(sename, NULL, sc) != 0) {
++ *sc = NULL;
++ return -1;
++ }
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++ }
++#endif
++ if (role != NULL && role[0]) {
++ context_t con;
++ char *type=NULL;
++ if (get_default_type(role, &type) != 0) {
++ error("get_default_type: failed to get default type for '%s'",
++ role);
++ goto out;
++ }
++ con = context_new(*sc);
++ if (!con) {
++ goto out;
++ }
++ context_role_set(con, role);
++ context_type_set(con, type);
++ freecon(*sc);
++ *sc = strdup(context_str(con));
++ context_free(con);
++ if (!*sc)
++ return -1;
++ }
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++ if (lvl != NULL && lvl[0]) {
++ /* verify that the requested range is obtained */
++ context_t con;
++ security_context_t obtained_raw;
++ security_context_t requested_raw;
++ con = context_new(*sc);
++ if (!con) {
++ goto out;
++ }
++ context_range_set(con, lvl);
++ if (selinux_trans_to_raw_context(*sc, &obtained_raw) < 0) {
++ context_free(con);
++ goto out;
++ }
++ if (selinux_trans_to_raw_context(context_str(con), &requested_raw) < 0) {
++ freecon(obtained_raw);
++ context_free(con);
++ goto out;
++ }
++
++ debug("get_user_context: obtained context '%s' requested context '%s'",
++ obtained_raw, requested_raw);
++ if (strcmp(obtained_raw, requested_raw)) {
++ /* set the context to the real requested one but fail */
++ freecon(requested_raw);
++ freecon(obtained_raw);
++ freecon(*sc);
++ *sc = strdup(context_str(con));
++ context_free(con);
++ return -1;
++ }
++ freecon(requested_raw);
++ freecon(obtained_raw);
++ context_free(con);
++ }
++#endif
++ return 0;
++ out:
++ freecon(*sc);
++ *sc = NULL;
++ return -1;
++}
++
++static void
++ssh_selinux_get_role_level(char **role, const char **level,
++ Authctxt *the_authctxt)
++{
++ *role = NULL;
++ *level = NULL;
++ if (the_authctxt) {
++ if (the_authctxt->role != NULL) {
++ char *slash;
++ *role = xstrdup(the_authctxt->role);
++ if ((slash = strchr(*role, '/')) != NULL) {
++ *slash = '\0';
++ *level = slash + 1;
++ }
++ }
++ }
++}
++
++/* Return the default security context for the given username */
++static int
++sshd_selinux_getctxbyname(char *pwname, security_context_t *default_sc,
++ security_context_t *user_sc, int inetd, Authctxt *the_authctxt)
++{
++ char *sename, *lvl;
++ char *role;
++ const char *reqlvl;
++ int r = 0;
++ context_t con = NULL;
++
++ ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
++
++#ifdef HAVE_GETSEUSERBYNAME
++ if ((r=getseuserbyname(pwname, &sename, &lvl)) != 0) {
++ sename = NULL;
++ lvl = NULL;
++ }
++#else
++ sename = pwname;
++ lvl = "";
++#endif
++
++ if (r == 0) {
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++ r = get_default_context_with_level(sename, lvl, NULL, default_sc);
++#else
++ r = get_default_context(sename, NULL, default_sc);
++#endif
++ }
++
++ if (r == 0) {
++ /* If launched from xinetd, we must use current level */
++ if (inetd) {
++ security_context_t sshdsc=NULL;
++
++ if (getcon_raw(&sshdsc) < 0)
++ fatal("failed to allocate security context");
++
++ if ((con=context_new(sshdsc)) == NULL)
++ fatal("failed to allocate selinux context");
++ reqlvl = context_range_get(con);
++ freecon(sshdsc);
++ if (reqlvl !=NULL && lvl != NULL && strcmp(reqlvl, lvl) == 0)
++ /* we actually don't change level */
++ reqlvl = "";
++
++ debug_f("current connection level '%s'", reqlvl);
++
++ }
++
++ if ((reqlvl != NULL && reqlvl[0]) || (role != NULL && role[0])) {
++ r = get_user_context(sename, role, reqlvl, user_sc);
++
++ if (r == 0 && reqlvl != NULL && reqlvl[0]) {
++ security_context_t default_level_sc = *default_sc;
++ if (role != NULL && role[0]) {
++ if (get_user_context(sename, role, lvl, &default_level_sc) < 0)
++ default_level_sc = *default_sc;
++ }
++ /* verify that the requested range is contained in the user range */
++ if (mls_range_allowed(default_level_sc, *user_sc)) {
++ logit("permit MLS level %s (user range %s)", reqlvl, lvl);
++ } else {
++ r = -1;
++ error("deny MLS level %s (user range %s)", reqlvl, lvl);
++ }
++ if (default_level_sc != *default_sc)
++ freecon(default_level_sc);
++ }
++ } else {
++ *user_sc = *default_sc;
++ }
++ }
++ if (r != 0) {
++ error_f("Failed to get default SELinux security "
++ "context for %s", pwname);
++ }
++
++#ifdef HAVE_GETSEUSERBYNAME
++ free(sename);
++ free(lvl);
++#endif
++
++ if (role != NULL)
++ free(role);
++ if (con)
++ context_free(con);
++
++ return (r);
++}
++
++/* Setup environment variables for pam_selinux */
++static int
++sshd_selinux_setup_variables(int(*set_it)(char *, const char *), int inetd,
++ Authctxt *the_authctxt)
++{
++ const char *reqlvl;
++ char *role;
++ char *use_current;
++ int rv;
++
++ debug3_f("setting execution context");
++
++ ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
++
++ rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
++
++ if (inetd) {
++ use_current = "1";
++ } else {
++ use_current = "";
++ rv = rv || set_it("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
++ }
++
++ rv = rv || set_it("SELINUX_USE_CURRENT_RANGE", use_current);
++
++ if (role != NULL)
++ free(role);
++
++ return rv;
++}
++
++static int
++sshd_selinux_setup_pam_variables(int inetd,
++ int(pam_setenv)(char *, const char *), Authctxt *the_authctxt)
++{
++ return sshd_selinux_setup_variables(pam_setenv, inetd, the_authctxt);
++}
++
++static int
++do_setenv(char *name, const char *value)
++{
++ return setenv(name, value, 1);
++}
++
++int
++sshd_selinux_setup_env_variables(int inetd, void *the_authctxt)
++{
++ Authctxt *authctxt = (Authctxt *) the_authctxt;
++ return sshd_selinux_setup_variables(do_setenv, inetd, authctxt);
++}
++
++/* Set the execution context to the default for the specified user */
++void
++sshd_selinux_setup_exec_context(char *pwname, int inetd,
++ int(pam_setenv)(char *, const char *), void *the_authctxt, int use_pam)
++{
++ security_context_t user_ctx = NULL;
++ int r = 0;
++ security_context_t default_ctx = NULL;
++ Authctxt *authctxt = (Authctxt *) the_authctxt;
++
++ if (!sshd_selinux_enabled())
++ return;
++
++ if (use_pam) {
++ /* do not compute context, just setup environment for pam_selinux */
++ if (sshd_selinux_setup_pam_variables(inetd, pam_setenv, authctxt)) {
++ switch (security_getenforce()) {
++ case -1:
++ fatal_f("security_getenforce() failed");
++ case 0:
++ error_f("SELinux PAM variable setup failure. Continuing in permissive mode.");
++ break;
++ default:
++ fatal_f("SELinux PAM variable setup failure. Aborting connection.");
++ }
++ }
++ return;
++ }
++
++ debug3_f("setting execution context");
++
++ r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx, inetd, authctxt);
++ if (r >= 0) {
++ r = setexeccon(user_ctx);
++ if (r < 0) {
++ error_f("Failed to set SELinux execution context %s for %s",
++ user_ctx, pwname);
++ }
++#ifdef HAVE_SETKEYCREATECON
++ else if (setkeycreatecon(user_ctx) < 0) {
++ error_f("Failed to set SELinux keyring creation context %s for %s",
++ user_ctx, pwname);
++ }
++#endif
++ }
++ if (user_ctx == NULL) {
++ user_ctx = default_ctx;
++ }
++ if (r < 0 || user_ctx != default_ctx) {
++ /* audit just the case when user changed a role or there was
++ a failure */
++ sshd_selinux_send_audit_message(r >= 0, default_ctx, user_ctx);
++ }
++ if (r < 0) {
++ switch (security_getenforce()) {
++ case -1:
++ fatal_f("security_getenforce() failed");
++ case 0:
++ error_f("ELinux failure. Continuing in permissive mode.");
++ break;
++ default:
++ fatal_f("SELinux failure. Aborting connection.");
++ }
++ }
++ if (user_ctx != NULL && user_ctx != default_ctx)
++ freecon(user_ctx);
++ if (default_ctx != NULL)
++ freecon(default_ctx);
++
++ debug3_f("done");
++}
++
++#endif
++#endif
++
+diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
+index c1d54f38d..7426f6f79 100644
+--- a/openbsd-compat/port-linux.c
++++ b/openbsd-compat/port-linux.c
+@@ -109,37 +109,6 @@ ssh_selinux_getctxbyname(char *pwname)
+ return sc;
+ }
+
+-/* Set the execution context to the default for the specified user */
+-void
+-ssh_selinux_setup_exec_context(char *pwname)
+-{
+- char *user_ctx = NULL;
+-
+- if (!ssh_selinux_enabled())
+- return;
+-
+- debug3("%s: setting execution context", __func__);
+-
+- user_ctx = ssh_selinux_getctxbyname(pwname);
+- if (setexeccon(user_ctx) != 0) {
+- switch (security_getenforce()) {
+- case -1:
+- fatal("%s: security_getenforce() failed", __func__);
+- case 0:
+- error("%s: Failed to set SELinux execution "
+- "context for %s", __func__, pwname);
+- break;
+- default:
+- fatal("%s: Failed to set SELinux execution context "
+- "for %s (in enforcing mode)", __func__, pwname);
+- }
+- }
+- if (user_ctx != NULL)
+- freecon(user_ctx);
+-
+- debug3("%s: done", __func__);
+-}
+-
+ /* Set the TTY context for the specified user */
+ void
+ ssh_selinux_setup_pty(char *pwname, const char *tty)
+@@ -152,7 +121,11 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
+
+ debug3("%s: setting TTY context on %s", __func__, tty);
+
+- user_ctx = ssh_selinux_getctxbyname(pwname);
++ if (getexeccon(&user_ctx) != 0) {
++ error_f("getexeccon: %s", strerror(errno));
++ goto out;
++ }
++
+
+ /* XXX: should these calls fatal() upon failure in enforcing mode? */
+
+diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
+index 959430de1..26c5f773b 100644
+--- a/openbsd-compat/port-linux.h
++++ b/openbsd-compat/port-linux.h
+@@ -20,9 +20,12 @@
+ #ifdef WITH_SELINUX
+ int ssh_selinux_enabled(void);
+ void ssh_selinux_setup_pty(char *, const char *);
+-void ssh_selinux_setup_exec_context(char *);
+ void ssh_selinux_change_context(const char *);
+ void ssh_selinux_setfscreatecon(const char *);
++
++int sshd_selinux_enabled(void);
++void sshd_selinux_setup_exec_context(char *, int, int(char *, const char *), void *, int);
++int sshd_selinux_setup_env_variables(int inetd, void *);
+ #endif
+
+ #ifdef LINUX_OOM_ADJUST
+diff --git a/platform.c b/platform.c
+index fd1a7a7c2..66d0c2a6b 100644
+--- a/platform.c
++++ b/platform.c
+@@ -33,6 +33,8 @@
+ #include "openbsd-compat/openbsd-compat.h"
+
+ extern ServerOptions options;
++extern int inetd_flag;
++extern Authctxt *the_authctxt;
+
+ /* return 1 if we are running with privilege to swap UIDs, 0 otherwise */
+ int
+@@ -55,7 +57,7 @@ platform_setusercontext(struct passwd *pw)
+ {
+ #ifdef WITH_SELINUX
+ /* Cache selinux status for later use */
+- (void)ssh_selinux_enabled();
++ (void)sshd_selinux_enabled();
+ #endif
+
+ #ifdef USE_SOLARIS_PROJECTS
+@@ -140,7 +142,9 @@ platform_setusercontext_post_groups(struct passwd *pw)
+ }
+ #endif /* HAVE_SETPCRED */
+ #ifdef WITH_SELINUX
+- ssh_selinux_setup_exec_context(pw->pw_name);
++ sshd_selinux_setup_exec_context(pw->pw_name,
++ inetd_flag, do_pam_putenv, the_authctxt,
++ options.use_pam);
+ #endif
+ }
+
+diff --git a/sshd-session.c b/sshd-session.c
+index 06a72daaf..1a40a43f1 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -123,7 +123,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
+ int debug_flag = 0;
+
+ /* Flag indicating that the daemon is being started from inetd. */
+-static int inetd_flag = 0;
++int inetd_flag = 0;
+
+ /* debug goes to stderr unless inetd_flag is set */
+ static int log_stderr = 0;
+@@ -1277,6 +1277,11 @@ main(int ac, char **av)
+ restore_uid();
+ }
+ #endif
++#ifdef WITH_SELINUX
++ sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
++ inetd_flag, do_pam_putenv, the_authctxt,
++ options.use_pam);
++#endif
+ #ifdef USE_PAM
+ if (options.use_pam) {
+ do_pam_setcred();
+--
+2.55.0
+
diff --git a/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch b/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
deleted file mode 100644
index 779a6ef..0000000
--- a/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 8423e892d7532589c2df0598470e01eaa383fb6b Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:07:02 +0200
-Subject: [PATCH 03/54] Pass inetd flags and auth context to subprocess calls
- for SELinux
-
-openssh-7.6p1-subprocess-selinux
-
-Pass inetd flags and authentication context down to subprocess calls
-for SELinux environment setup. This ensures that AuthorizedKeysCommand
-and AuthorizedPrincipalsCommand subprocesses run with the correct
-SELinux context based on the authenticated user's role and level.
-
-Changes:
-- misc.c: Update subprocess() function signature to accept inetd and
- the_authctxt parameters; add sshd_selinux_setup_env_variables() call
- in child process before execve
-- misc.h: Update subprocess() function declaration
-- auth2-pubkey.c: Add extern declarations for inetd_flag and
- the_authctxt; pass them to subprocess() calls for
- AuthorizedKeysCommand and AuthorizedPrincipalsCommand
-- sshconnect.c: Update subprocess() call with new parameters (0, NULL)
-- sshd-auth.c: Make inetd_flag non-static (extern)
-- sshd-session.c: Make inetd_flag non-static (extern)
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- misc.h | 2 +-
- sshconnect.c | 2 +-
- sshd-auth.c | 2 +-
- 3 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/misc.h b/misc.h
-index 791876c1e..fe11f9d8a 100644
---- a/misc.h
-+++ b/misc.h
-@@ -129,7 +129,7 @@ typedef void privrestore_fn(void);
- #define SSH_SUBPROCESS_UNSAFE_PATH (1<<3) /* Don't check for safe cmd */
- #define SSH_SUBPROCESS_PRESERVE_ENV (1<<4) /* Keep parent environment */
- pid_t subprocess(const char *, const char *, int, char **, FILE **, u_int,
-- struct passwd *, privdrop_fn *, privrestore_fn *);
-+ struct passwd *, privdrop_fn *, privrestore_fn *, int, void *);
-
- typedef struct arglist arglist;
- struct arglist {
-diff --git a/sshconnect.c b/sshconnect.c
-index 4384277a6..5e761561d 100644
---- a/sshconnect.c
-+++ b/sshconnect.c
-@@ -911,7 +911,7 @@ load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
-
- if ((pid = subprocess(tag, command, ac, av, &f,
- SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
-- SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
-+ SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL, 0, NULL)) == 0)
- goto out;
-
- load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
-diff --git a/sshd-auth.c b/sshd-auth.c
-index 76350a2a3..73acb0dbe 100644
---- a/sshd-auth.c
-+++ b/sshd-auth.c
-@@ -119,7 +119,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
- int debug_flag = 0;
-
- /* Flag indicating that the daemon is being started from inetd. */
--static int inetd_flag = 0;
-+int inetd_flag = 0;
-
- /* Saved arguments to main(). */
- static char **saved_argv;
---
-2.53.0
-
diff --git a/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch b/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
new file mode 100644
index 0000000..e77ac29
--- /dev/null
+++ b/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
@@ -0,0 +1,74 @@
+From e63a4ad30d07f0912f63360263985d7aef948565 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:07:02 +0200
+Subject: [PATCH 04/53] Pass inetd flags and auth context to subprocess calls
+ for SELinux
+
+openssh-7.6p1-subprocess-selinux
+
+Pass inetd flags and authentication context down to subprocess calls
+for SELinux environment setup. This ensures that AuthorizedKeysCommand
+and AuthorizedPrincipalsCommand subprocesses run with the correct
+SELinux context based on the authenticated user's role and level.
+
+Changes:
+- misc.c: Update subprocess() function signature to accept inetd and
+ the_authctxt parameters; add sshd_selinux_setup_env_variables() call
+ in child process before execve
+- misc.h: Update subprocess() function declaration
+- auth2-pubkey.c: Add extern declarations for inetd_flag and
+ the_authctxt; pass them to subprocess() calls for
+ AuthorizedKeysCommand and AuthorizedPrincipalsCommand
+- sshconnect.c: Update subprocess() call with new parameters (0, NULL)
+- sshd-auth.c: Make inetd_flag non-static (extern)
+- sshd-session.c: Make inetd_flag non-static (extern)
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ misc.h | 2 +-
+ sshconnect.c | 2 +-
+ sshd-auth.c | 2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/misc.h b/misc.h
+index 791876c1e..fe11f9d8a 100644
+--- a/misc.h
++++ b/misc.h
+@@ -129,7 +129,7 @@ typedef void privrestore_fn(void);
+ #define SSH_SUBPROCESS_UNSAFE_PATH (1<<3) /* Don't check for safe cmd */
+ #define SSH_SUBPROCESS_PRESERVE_ENV (1<<4) /* Keep parent environment */
+ pid_t subprocess(const char *, const char *, int, char **, FILE **, u_int,
+- struct passwd *, privdrop_fn *, privrestore_fn *);
++ struct passwd *, privdrop_fn *, privrestore_fn *, int, void *);
+
+ typedef struct arglist arglist;
+ struct arglist {
+diff --git a/sshconnect.c b/sshconnect.c
+index 0ddfc76b3..bdaade682 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -954,7 +954,7 @@ load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
+
+ if ((pid = subprocess(tag, command, ac, av, &f,
+ SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
+- SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
++ SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL, 0, NULL)) == 0)
+ goto out;
+
+ load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
+diff --git a/sshd-auth.c b/sshd-auth.c
+index 4888c8e62..14109151c 100644
+--- a/sshd-auth.c
++++ b/sshd-auth.c
+@@ -119,7 +119,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
+ int debug_flag = 0;
+
+ /* Flag indicating that the daemon is being started from inetd. */
+-static int inetd_flag = 0;
++int inetd_flag = 0;
+
+ /* Saved arguments to main(). */
+ static char **saved_argv;
+--
+2.55.0
+
diff --git a/0004-openssh-6.6p1-keycat.patch b/0004-openssh-6.6p1-keycat.patch
deleted file mode 100644
index 329085c..0000000
--- a/0004-openssh-6.6p1-keycat.patch
+++ /dev/null
@@ -1,380 +0,0 @@
-From 5cc1b10c8cd5de91ec819a9397abd24aef9f099d Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 04/54] openssh-6.6p1-keycat
-
----
- .gitignore | 1 +
- HOWTO.ssh-keycat | 12 +++
- Makefile.in | 8 +-
- configure.ac | 6 ++
- ssh-keycat.c | 241 +++++++++++++++++++++++++++++++++++++++++++++++
- 5 files changed, 267 insertions(+), 1 deletion(-)
- create mode 100644 HOWTO.ssh-keycat
- create mode 100644 ssh-keycat.c
-
-diff --git a/.gitignore b/.gitignore
-index 907533067..7b4668d71 100644
---- a/.gitignore
-+++ b/.gitignore
-@@ -28,6 +28,7 @@ ssh-agent
- ssh-keygen
- ssh-keyscan
- ssh-keysign
-+ssh-keycat
- ssh-pkcs11-helper
- ssh-sk-helper
- sshd
-diff --git a/HOWTO.ssh-keycat b/HOWTO.ssh-keycat
-new file mode 100644
-index 000000000..630ec628c
---- /dev/null
-+++ b/HOWTO.ssh-keycat
-@@ -0,0 +1,12 @@
-+The ssh-keycat retrieves the content of the ~/.ssh/authorized_keys
-+of an user in any environment. This includes environments with
-+polyinstantiation of home directories and SELinux MLS policy enabled.
-+
-+To use ssh-keycat, set these options in /etc/ssh/sshd_config file:
-+ AuthorizedKeysCommand /usr/libexec/openssh/ssh-keycat
-+ AuthorizedKeysCommandUser root
-+
-+Do not forget to enable public key authentication:
-+ PubkeyAuthentication yes
-+
-+
-diff --git a/Makefile.in b/Makefile.in
-index 2aac879c1..42b45d33d 100644
---- a/Makefile.in
-+++ b/Makefile.in
-@@ -23,6 +23,7 @@ SSH_PROGRAM=@bindir@/ssh
- ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
- SFTP_SERVER=$(libexecdir)/sftp-server
- SSH_KEYSIGN=$(libexecdir)/ssh-keysign
-+SSH_KEYCAT=$(libexecdir)/ssh-keycat
- SSHD_SESSION=$(libexecdir)/sshd-session
- SSHD_AUTH=$(libexecdir)/sshd-auth
- SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
-@@ -58,6 +59,7 @@ CHANNELLIBS=@CHANNELLIBS@
- K5LIBS=@K5LIBS@
- GSSLIBS=@GSSLIBS@
- SSHDLIBS=@SSHDLIBS@
-+KEYCATLIBS=@KEYCATLIBS@
- LIBEDIT=@LIBEDIT@
- LIBFIDO2=@LIBFIDO2@
- LIBWTMPDB=@LIBWTMPDB@
-@@ -75,7 +77,7 @@ MKDIR_P=@MKDIR_P@
-
- .SUFFIXES: .lo
-
--TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) $(SK_STANDALONE)
-+TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) ssh-keycat$(EXEEXT) $(SK_STANDALONE)
-
- LIBOPENSSH_OBJS=\
- ssh_api.o \
-@@ -252,6 +254,9 @@ ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(P11HELPER_OBJS)
- ssh-sk-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(SKHELPER_OBJS)
- $(LD) -o $@ $(SKHELPER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) $(LIBFIDO2) $(CHANNELLIBS)
-
-+ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
-+ $(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(CHANNELLIBS) $(LIBS)
-+
- ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
- $(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) $(CHANNELLIBS)
-
-@@ -440,6 +445,7 @@ install-files:
- $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
- $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
- $(INSTALL) -m 0755 $(STRIP_OPT) ssh-sk-helper$(EXEEXT) $(DESTDIR)$(SSH_SK_HELPER)$(EXEEXT)
-+ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keycat$(EXEEXT) $(DESTDIR)$(libexecdir)/ssh-keycat$(EXEEXT)
- $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
- $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
- $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
-diff --git a/configure.ac b/configure.ac
-index a8e9df66b..8f8276c78 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3709,6 +3709,7 @@ AC_ARG_WITH([pam],
- PAM_MSG="yes"
-
- SSHDLIBS="$SSHDLIBS -lpam"
-+ KEYCATLIBS="$KEYCATLIBS -lpam"
- AC_DEFINE([USE_PAM], [1],
- [Define if you want to enable PAM support])
-
-@@ -3719,6 +3720,7 @@ AC_ARG_WITH([pam],
- ;;
- *)
- SSHDLIBS="$SSHDLIBS -ldl"
-+ KEYCATLIBS="$KEYCATLIBS -ldl"
- ;;
- esac
- fi
-@@ -4962,6 +4964,7 @@ AC_ARG_WITH([selinux],
- fi ]
- )
- AC_SUBST([SSHDLIBS])
-+AC_SUBST([KEYCATLIBS])
-
- # Check whether user wants Kerberos 5 support
- KRB5_MSG="no"
-@@ -5975,6 +5978,9 @@ fi
- if test ! -z "${SSHDLIBS}"; then
- echo " +for sshd: ${SSHDLIBS}"
- fi
-+if test ! -z "${KEYCATLIBS}"; then
-+echo " +for ssh-keycat: ${KEYCATLIBS}"
-+fi
-
- echo ""
-
-diff --git a/ssh-keycat.c b/ssh-keycat.c
-new file mode 100644
-index 000000000..8bb17c73e
---- /dev/null
-+++ b/ssh-keycat.c
-@@ -0,0 +1,241 @@
-+/*
-+ * Redistribution and use in source and binary forms, with or without
-+ * modification, are permitted provided that the following conditions
-+ * are met:
-+ * 1. Redistributions of source code must retain the above copyright
-+ * notice, and the entire permission notice in its entirety,
-+ * including the disclaimer of warranties.
-+ * 2. Redistributions in binary form must reproduce the above copyright
-+ * notice, this list of conditions and the following disclaimer in the
-+ * documentation and/or other materials provided with the distribution.
-+ * 3. The name of the author may not be used to endorse or promote
-+ * products derived from this software without specific prior
-+ * written permission.
-+ *
-+ * ALTERNATIVELY, this product may be distributed under the terms of
-+ * the GNU Public License, in which case the provisions of the GPL are
-+ * required INSTEAD OF the above restrictions. (This clause is
-+ * necessary due to a potential bad interaction between the GPL and
-+ * the restrictions contained in a BSD-style copyright.)
-+ *
-+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
-+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
-+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-+ * OF THE POSSIBILITY OF SUCH DAMAGE.
-+ */
-+
-+/*
-+ * Copyright (c) 2011 Red Hat, Inc.
-+ * Written by Tomas Mraz <tmraz@redhat.com>
-+*/
-+
-+#define _GNU_SOURCE
-+
-+#include "config.h"
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <string.h>
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <pwd.h>
-+#include <fcntl.h>
-+#include <unistd.h>
-+#ifdef HAVE_STDINT_H
-+#include <stdint.h>
-+#endif
-+
-+#include <security/pam_appl.h>
-+
-+#include "uidswap.h"
-+#include "misc.h"
-+
-+#define ERR_USAGE 1
-+#define ERR_PAM_START 2
-+#define ERR_OPEN_SESSION 3
-+#define ERR_CLOSE_SESSION 4
-+#define ERR_PAM_END 5
-+#define ERR_GETPWNAM 6
-+#define ERR_MEMORY 7
-+#define ERR_OPEN 8
-+#define ERR_FILE_MODE 9
-+#define ERR_FDOPEN 10
-+#define ERR_STAT 11
-+#define ERR_WRITE 12
-+#define ERR_PAM_PUTENV 13
-+#define BUFLEN 4096
-+
-+/* Just ignore the messages in the conversation function */
-+static int
-+dummy_conv(int num_msg, const struct pam_message **msgm,
-+ struct pam_response **response, void *appdata_ptr)
-+{
-+ struct pam_response *rsp;
-+
-+ (void)msgm;
-+ (void)appdata_ptr;
-+
-+ if (num_msg <= 0)
-+ return PAM_CONV_ERR;
-+
-+ /* Just allocate the array as empty responses */
-+ rsp = calloc (num_msg, sizeof (struct pam_response));
-+ if (rsp == NULL)
-+ return PAM_CONV_ERR;
-+
-+ *response = rsp;
-+ return PAM_SUCCESS;
-+}
-+
-+static struct pam_conv conv = {
-+ dummy_conv,
-+ NULL
-+};
-+
-+char *
-+make_auth_keys_name(const struct passwd *pwd)
-+{
-+ char *fname;
-+
-+ if (asprintf(&fname, "%s/.ssh/authorized_keys", pwd->pw_dir) < 0)
-+ return NULL;
-+
-+ return fname;
-+}
-+
-+int
-+dump_keys(const char *user)
-+{
-+ struct passwd *pwd;
-+ int fd = -1;
-+ FILE *f = NULL;
-+ char *fname = NULL;
-+ int rv = 0;
-+ char buf[BUFLEN];
-+ size_t len;
-+ struct stat st;
-+
-+ if ((pwd = getpwnam(user)) == NULL) {
-+ return ERR_GETPWNAM;
-+ }
-+
-+ if ((fname = make_auth_keys_name(pwd)) == NULL) {
-+ return ERR_MEMORY;
-+ }
-+
-+ temporarily_use_uid(pwd);
-+
-+ if ((fd = open(fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < 0) {
-+ rv = ERR_OPEN;
-+ goto fail;
-+ }
-+
-+ if (fstat(fd, &st) < 0) {
-+ rv = ERR_STAT;
-+ goto fail;
-+ }
-+
-+ if (!S_ISREG(st.st_mode) ||
-+ (st.st_uid != pwd->pw_uid && st.st_uid != 0)) {
-+ rv = ERR_FILE_MODE;
-+ goto fail;
-+ }
-+
-+ unset_nonblock(fd);
-+
-+ if ((f = fdopen(fd, "r")) == NULL) {
-+ rv = ERR_FDOPEN;
-+ goto fail;
-+ }
-+
-+ fd = -1;
-+
-+ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
-+ rv = fwrite(buf, 1, len, stdout) != len ? ERR_WRITE : 0;
-+ }
-+
-+fail:
-+ if (fd != -1)
-+ close(fd);
-+ if (f != NULL)
-+ fclose(f);
-+ free(fname);
-+ restore_uid();
-+ return rv;
-+}
-+
-+static const char *env_names[] = { "SELINUX_ROLE_REQUESTED",
-+ "SELINUX_LEVEL_REQUESTED",
-+ "SELINUX_USE_CURRENT_RANGE"
-+};
-+
-+extern char **environ;
-+
-+int
-+set_pam_environment(pam_handle_t *pamh)
-+{
-+ int i;
-+ size_t j;
-+
-+ for (j = 0; j < sizeof(env_names)/sizeof(env_names[0]); ++j) {
-+ int len = strlen(env_names[j]);
-+
-+ for (i = 0; environ[i] != NULL; ++i) {
-+ if (strncmp(env_names[j], environ[i], len) == 0 &&
-+ environ[i][len] == '=') {
-+ if (pam_putenv(pamh, environ[i]) != PAM_SUCCESS)
-+ return ERR_PAM_PUTENV;
-+ }
-+ }
-+ }
-+
-+ return 0;
-+}
-+
-+int
-+main(int argc, char *argv[])
-+{
-+ pam_handle_t *pamh = NULL;
-+ int retval;
-+ int ev = 0;
-+
-+ if (argc != 2) {
-+ fprintf(stderr, "Usage: %s <user-name>\n", argv[0]);
-+ return ERR_USAGE;
-+ }
-+
-+ retval = pam_start("ssh-keycat", argv[1], &conv, &pamh);
-+ if (retval != PAM_SUCCESS) {
-+ return ERR_PAM_START;
-+ }
-+
-+ ev = set_pam_environment(pamh);
-+ if (ev != 0)
-+ goto finish;
-+
-+ retval = pam_open_session(pamh, PAM_SILENT);
-+ if (retval != PAM_SUCCESS) {
-+ ev = ERR_OPEN_SESSION;
-+ goto finish;
-+ }
-+
-+ ev = dump_keys(argv[1]);
-+
-+ retval = pam_close_session(pamh, PAM_SILENT);
-+ if (retval != PAM_SUCCESS) {
-+ ev = ERR_CLOSE_SESSION;
-+ }
-+
-+finish:
-+ retval = pam_end (pamh,retval);
-+ if (retval != PAM_SUCCESS) {
-+ ev = ERR_PAM_END;
-+ }
-+ return ev;
-+}
---
-2.53.0
-
diff --git a/0005-openssh-6.6p1-allow-ip-opts.patch b/0005-openssh-6.6p1-allow-ip-opts.patch
deleted file mode 100644
index 0243e81..0000000
--- a/0005-openssh-6.6p1-allow-ip-opts.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 135bb6903b715012a8ae302022bb7a31d1879c6a Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 05/54] openssh-6.6p1-allow-ip-opts
-
-#https://bugzilla.mindrot.org/show_bug.cgi?id=1644
----
- sshd-session.c | 32 ++++++++++++++++++++++++++------
- 1 file changed, 26 insertions(+), 6 deletions(-)
-
-diff --git a/sshd-session.c b/sshd-session.c
-index 967737c5b..00d00e293 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -726,12 +726,32 @@ check_ip_options(struct ssh *ssh)
-
- if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts,
- &option_size) >= 0 && option_size != 0) {
-- text[0] = '\0';
-- for (i = 0; i < option_size; i++)
-- snprintf(text + i*3, sizeof(text) - i*3,
-- " %2.2x", opts[i]);
-- fatal("Connection from %.100s port %d with IP opts: %.800s",
-- ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
-+ i = 0;
-+ do {
-+ switch (opts[i]) {
-+ case 0:
-+ case 1:
-+ ++i;
-+ break;
-+ case 130:
-+ case 133:
-+ case 134:
-+ if (i + 1 < option_size && opts[i + 1] >= 2) {
-+ i += opts[i + 1];
-+ break;
-+ }
-+ /* FALLTHROUGH */
-+ default:
-+ /* Fail, fatally, if we detect either loose or strict
-+ * or incorrect source routing options. */
-+ text[0] = '\0';
-+ for (i = 0; i < option_size; i++)
-+ snprintf(text + i*3, sizeof(text) - i*3,
-+ " %2.2x", opts[i]);
-+ fatal("Connection from %.100s port %d with IP options:%.800s",
-+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
-+ }
-+ } while (i < option_size);
- }
- #endif /* IP_OPTIONS */
- }
---
-2.53.0
-
diff --git a/0005-openssh-6.6p1-keycat.patch b/0005-openssh-6.6p1-keycat.patch
new file mode 100644
index 0000000..bd9a5ef
--- /dev/null
+++ b/0005-openssh-6.6p1-keycat.patch
@@ -0,0 +1,393 @@
+From 6164d9589be5125b95da999ed6615aa431a42e70 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 05/53] openssh-6.6p1-keycat
+
+---
+ .depend | 1 +
+ .gitignore | 1 +
+ HOWTO.ssh-keycat | 12 +++
+ Makefile.in | 8 +-
+ configure.ac | 6 ++
+ ssh-keycat.c | 241 +++++++++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 268 insertions(+), 1 deletion(-)
+ create mode 100644 HOWTO.ssh-keycat
+ create mode 100644 ssh-keycat.c
+
+diff --git a/.depend b/.depend
+index 845fadceb..7d2d00bbf 100644
+--- a/.depend
++++ b/.depend
+@@ -135,6 +135,7 @@ ssh-ed25519-sk.o: includes.h config.h defines.h platform.h openbsd-compat/openbs
+ ssh-ed25519.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h crypto_api.h log.h ssherr.h sshbuf.h sshkey.h
+ ssh-keygen.o: cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h
+ ssh-keygen.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h sshkey.h authfile.h sshbuf.h pathnames.h log.h ssherr.h misc.h match.h hostfile.h dns.h ssh.h ssh2.h atomicio.h krl.h digest.h utf8.h authfd.h sshsig.h ssh-sk.h sk-api.h cipher.h
++ssh-keycat.o: config.h uidswap.h misc.h
+ ssh-keyscan.o: atomicio.h misc.h hostfile.h ssh_api.h ssh2.h dns.h addr.h
+ ssh-keyscan.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h sshbuf.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h kex.h mac.h crypto_api.h compat.h myproposal.h packet.h dispatch.h log.h ssherr.h
+ ssh-keysign.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h log.h ssherr.h sshkey.h ssh.h ssh2.h misc.h sshbuf.h authfile.h msg.h canohost.h pathnames.h readconf.h uidswap.h
+diff --git a/.gitignore b/.gitignore
+index 907533067..7b4668d71 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -28,6 +28,7 @@ ssh-agent
+ ssh-keygen
+ ssh-keyscan
+ ssh-keysign
++ssh-keycat
+ ssh-pkcs11-helper
+ ssh-sk-helper
+ sshd
+diff --git a/HOWTO.ssh-keycat b/HOWTO.ssh-keycat
+new file mode 100644
+index 000000000..630ec628c
+--- /dev/null
++++ b/HOWTO.ssh-keycat
+@@ -0,0 +1,12 @@
++The ssh-keycat retrieves the content of the ~/.ssh/authorized_keys
++of an user in any environment. This includes environments with
++polyinstantiation of home directories and SELinux MLS policy enabled.
++
++To use ssh-keycat, set these options in /etc/ssh/sshd_config file:
++ AuthorizedKeysCommand /usr/libexec/openssh/ssh-keycat
++ AuthorizedKeysCommandUser root
++
++Do not forget to enable public key authentication:
++ PubkeyAuthentication yes
++
++
+diff --git a/Makefile.in b/Makefile.in
+index e9503e7b7..2a449680a 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -23,6 +23,7 @@ SSH_PROGRAM=@bindir@/ssh
+ ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
+ SFTP_SERVER=$(libexecdir)/sftp-server
+ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
++SSH_KEYCAT=$(libexecdir)/ssh-keycat
+ SSHD_SESSION=$(libexecdir)/sshd-session
+ SSHD_AUTH=$(libexecdir)/sshd-auth
+ SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+@@ -58,6 +59,7 @@ CHANNELLIBS=@CHANNELLIBS@
+ K5LIBS=@K5LIBS@
+ GSSLIBS=@GSSLIBS@
+ SSHDLIBS=@SSHDLIBS@
++KEYCATLIBS=@KEYCATLIBS@
+ LIBEDIT=@LIBEDIT@
+ LIBFIDO2=@LIBFIDO2@
+ LIBWTMPDB=@LIBWTMPDB@
+@@ -75,7 +77,7 @@ MKDIR_P=@MKDIR_P@
+
+ .SUFFIXES: .lo
+
+-TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) $(SK_STANDALONE)
++TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) ssh-keycat$(EXEEXT) $(SK_STANDALONE)
+
+ LIBOPENSSH_OBJS=\
+ ssh_api.o \
+@@ -253,6 +255,9 @@ ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(P11HELPER_OBJS)
+ ssh-sk-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(SKHELPER_OBJS)
+ $(LD) -o $@ $(SKHELPER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) $(LIBFIDO2) $(CHANNELLIBS)
+
++ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
++ $(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(CHANNELLIBS) $(LIBS)
++
+ ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
+ $(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) $(CHANNELLIBS)
+
+@@ -449,6 +454,7 @@ install-files:
+ $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
+ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
+ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-sk-helper$(EXEEXT) $(DESTDIR)$(SSH_SK_HELPER)$(EXEEXT)
++ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keycat$(EXEEXT) $(DESTDIR)$(libexecdir)/ssh-keycat$(EXEEXT)
+ $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
+ $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
+ $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
+diff --git a/configure.ac b/configure.ac
+index cb1711f6b..4b3cff38c 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -3639,6 +3639,7 @@ AC_ARG_WITH([pam],
+ PAM_MSG="yes"
+
+ SSHDLIBS="$SSHDLIBS -lpam"
++ KEYCATLIBS="$KEYCATLIBS -lpam"
+ AC_DEFINE([USE_PAM], [1],
+ [Define if you want to enable PAM support])
+
+@@ -3649,6 +3650,7 @@ AC_ARG_WITH([pam],
+ ;;
+ *)
+ SSHDLIBS="$SSHDLIBS -ldl"
++ KEYCATLIBS="$KEYCATLIBS -ldl"
+ ;;
+ esac
+ fi
+@@ -4892,6 +4894,7 @@ AC_ARG_WITH([selinux],
+ fi ]
+ )
+ AC_SUBST([SSHDLIBS])
++AC_SUBST([KEYCATLIBS])
+
+ # Check whether user wants Kerberos 5 support
+ KRB5_MSG="no"
+@@ -5905,6 +5908,9 @@ fi
+ if test ! -z "${SSHDLIBS}"; then
+ echo " +for sshd: ${SSHDLIBS}"
+ fi
++if test ! -z "${KEYCATLIBS}"; then
++echo " +for ssh-keycat: ${KEYCATLIBS}"
++fi
+
+ echo ""
+
+diff --git a/ssh-keycat.c b/ssh-keycat.c
+new file mode 100644
+index 000000000..8bb17c73e
+--- /dev/null
++++ b/ssh-keycat.c
+@@ -0,0 +1,241 @@
++/*
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, and the entire permission notice in its entirety,
++ * including the disclaimer of warranties.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ * 3. The name of the author may not be used to endorse or promote
++ * products derived from this software without specific prior
++ * written permission.
++ *
++ * ALTERNATIVELY, this product may be distributed under the terms of
++ * the GNU Public License, in which case the provisions of the GPL are
++ * required INSTEAD OF the above restrictions. (This clause is
++ * necessary due to a potential bad interaction between the GPL and
++ * the restrictions contained in a BSD-style copyright.)
++ *
++ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
++ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/*
++ * Copyright (c) 2011 Red Hat, Inc.
++ * Written by Tomas Mraz <tmraz@redhat.com>
++*/
++
++#define _GNU_SOURCE
++
++#include "config.h"
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <pwd.h>
++#include <fcntl.h>
++#include <unistd.h>
++#ifdef HAVE_STDINT_H
++#include <stdint.h>
++#endif
++
++#include <security/pam_appl.h>
++
++#include "uidswap.h"
++#include "misc.h"
++
++#define ERR_USAGE 1
++#define ERR_PAM_START 2
++#define ERR_OPEN_SESSION 3
++#define ERR_CLOSE_SESSION 4
++#define ERR_PAM_END 5
++#define ERR_GETPWNAM 6
++#define ERR_MEMORY 7
++#define ERR_OPEN 8
++#define ERR_FILE_MODE 9
++#define ERR_FDOPEN 10
++#define ERR_STAT 11
++#define ERR_WRITE 12
++#define ERR_PAM_PUTENV 13
++#define BUFLEN 4096
++
++/* Just ignore the messages in the conversation function */
++static int
++dummy_conv(int num_msg, const struct pam_message **msgm,
++ struct pam_response **response, void *appdata_ptr)
++{
++ struct pam_response *rsp;
++
++ (void)msgm;
++ (void)appdata_ptr;
++
++ if (num_msg <= 0)
++ return PAM_CONV_ERR;
++
++ /* Just allocate the array as empty responses */
++ rsp = calloc (num_msg, sizeof (struct pam_response));
++ if (rsp == NULL)
++ return PAM_CONV_ERR;
++
++ *response = rsp;
++ return PAM_SUCCESS;
++}
++
++static struct pam_conv conv = {
++ dummy_conv,
++ NULL
++};
++
++char *
++make_auth_keys_name(const struct passwd *pwd)
++{
++ char *fname;
++
++ if (asprintf(&fname, "%s/.ssh/authorized_keys", pwd->pw_dir) < 0)
++ return NULL;
++
++ return fname;
++}
++
++int
++dump_keys(const char *user)
++{
++ struct passwd *pwd;
++ int fd = -1;
++ FILE *f = NULL;
++ char *fname = NULL;
++ int rv = 0;
++ char buf[BUFLEN];
++ size_t len;
++ struct stat st;
++
++ if ((pwd = getpwnam(user)) == NULL) {
++ return ERR_GETPWNAM;
++ }
++
++ if ((fname = make_auth_keys_name(pwd)) == NULL) {
++ return ERR_MEMORY;
++ }
++
++ temporarily_use_uid(pwd);
++
++ if ((fd = open(fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < 0) {
++ rv = ERR_OPEN;
++ goto fail;
++ }
++
++ if (fstat(fd, &st) < 0) {
++ rv = ERR_STAT;
++ goto fail;
++ }
++
++ if (!S_ISREG(st.st_mode) ||
++ (st.st_uid != pwd->pw_uid && st.st_uid != 0)) {
++ rv = ERR_FILE_MODE;
++ goto fail;
++ }
++
++ unset_nonblock(fd);
++
++ if ((f = fdopen(fd, "r")) == NULL) {
++ rv = ERR_FDOPEN;
++ goto fail;
++ }
++
++ fd = -1;
++
++ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
++ rv = fwrite(buf, 1, len, stdout) != len ? ERR_WRITE : 0;
++ }
++
++fail:
++ if (fd != -1)
++ close(fd);
++ if (f != NULL)
++ fclose(f);
++ free(fname);
++ restore_uid();
++ return rv;
++}
++
++static const char *env_names[] = { "SELINUX_ROLE_REQUESTED",
++ "SELINUX_LEVEL_REQUESTED",
++ "SELINUX_USE_CURRENT_RANGE"
++};
++
++extern char **environ;
++
++int
++set_pam_environment(pam_handle_t *pamh)
++{
++ int i;
++ size_t j;
++
++ for (j = 0; j < sizeof(env_names)/sizeof(env_names[0]); ++j) {
++ int len = strlen(env_names[j]);
++
++ for (i = 0; environ[i] != NULL; ++i) {
++ if (strncmp(env_names[j], environ[i], len) == 0 &&
++ environ[i][len] == '=') {
++ if (pam_putenv(pamh, environ[i]) != PAM_SUCCESS)
++ return ERR_PAM_PUTENV;
++ }
++ }
++ }
++
++ return 0;
++}
++
++int
++main(int argc, char *argv[])
++{
++ pam_handle_t *pamh = NULL;
++ int retval;
++ int ev = 0;
++
++ if (argc != 2) {
++ fprintf(stderr, "Usage: %s <user-name>\n", argv[0]);
++ return ERR_USAGE;
++ }
++
++ retval = pam_start("ssh-keycat", argv[1], &conv, &pamh);
++ if (retval != PAM_SUCCESS) {
++ return ERR_PAM_START;
++ }
++
++ ev = set_pam_environment(pamh);
++ if (ev != 0)
++ goto finish;
++
++ retval = pam_open_session(pamh, PAM_SILENT);
++ if (retval != PAM_SUCCESS) {
++ ev = ERR_OPEN_SESSION;
++ goto finish;
++ }
++
++ ev = dump_keys(argv[1]);
++
++ retval = pam_close_session(pamh, PAM_SILENT);
++ if (retval != PAM_SUCCESS) {
++ ev = ERR_CLOSE_SESSION;
++ }
++
++finish:
++ retval = pam_end (pamh,retval);
++ if (retval != PAM_SUCCESS) {
++ ev = ERR_PAM_END;
++ }
++ return ev;
++}
+--
+2.55.0
+
diff --git a/0006-openssh-5.9p1-ipv6man.patch b/0006-openssh-5.9p1-ipv6man.patch
deleted file mode 100644
index 2bb7ae8..0000000
--- a/0006-openssh-5.9p1-ipv6man.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From ba20c2380b3108b1f8e2c00689a8efc6623cd481 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 06/54] openssh-5.9p1-ipv6man
-
-#(drop?) https://bugzilla.mindrot.org/show_bug.cgi?id=1925
----
- ssh.1 | 2 ++
- sshd.8 | 2 ++
- 2 files changed, 4 insertions(+)
-
-diff --git a/ssh.1 b/ssh.1
-index 82ae5480c..c2c8f50f1 100644
---- a/ssh.1
-+++ b/ssh.1
-@@ -1669,6 +1669,8 @@ manual page for more information.
- .Nm
- exits with the exit status of the remote command or with 255
- if an error occurred.
-+.Sh IPV6
-+IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
- .Sh SEE ALSO
- .Xr scp 1 ,
- .Xr sftp 1 ,
-diff --git a/sshd.8 b/sshd.8
-index 7fbca776a..0226a8303 100644
---- a/sshd.8
-+++ b/sshd.8
-@@ -1018,6 +1018,8 @@ concurrently for different ports, this contains the process ID of the one
- started last).
- The content of this file is not sensitive; it can be world-readable.
- .El
-+.Sh IPV6
-+IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
- .Sh SEE ALSO
- .Xr scp 1 ,
- .Xr sftp 1 ,
---
-2.53.0
-
diff --git a/0006-openssh-6.6p1-allow-ip-opts.patch b/0006-openssh-6.6p1-allow-ip-opts.patch
new file mode 100644
index 0000000..b9d390e
--- /dev/null
+++ b/0006-openssh-6.6p1-allow-ip-opts.patch
@@ -0,0 +1,56 @@
+From b7b287396f0792287ee4be08702ff70876cd401d Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 06/53] openssh-6.6p1-allow-ip-opts
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1644
+---
+ sshd-session.c | 32 ++++++++++++++++++++++++++------
+ 1 file changed, 26 insertions(+), 6 deletions(-)
+
+diff --git a/sshd-session.c b/sshd-session.c
+index 1a40a43f1..0f41756ff 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -727,12 +727,32 @@ check_ip_options(struct ssh *ssh)
+
+ if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts,
+ &option_size) >= 0 && option_size != 0) {
+- text[0] = '\0';
+- for (i = 0; i < option_size; i++)
+- snprintf(text + i*3, sizeof(text) - i*3,
+- " %2.2x", opts[i]);
+- fatal("Connection from %.100s port %d with IP opts: %.800s",
+- ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
++ i = 0;
++ do {
++ switch (opts[i]) {
++ case 0:
++ case 1:
++ ++i;
++ break;
++ case 130:
++ case 133:
++ case 134:
++ if (i + 1 < option_size && opts[i + 1] >= 2) {
++ i += opts[i + 1];
++ break;
++ }
++ /* FALLTHROUGH */
++ default:
++ /* Fail, fatally, if we detect either loose or strict
++ * or incorrect source routing options. */
++ text[0] = '\0';
++ for (i = 0; i < option_size; i++)
++ snprintf(text + i*3, sizeof(text) - i*3,
++ " %2.2x", opts[i]);
++ fatal("Connection from %.100s port %d with IP options:%.800s",
++ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
++ }
++ } while (i < option_size);
+ }
+ #endif /* IP_OPTIONS */
+ }
+--
+2.55.0
+
diff --git a/0007-openssh-5.8p2-sigpipe.patch b/0007-openssh-5.8p2-sigpipe.patch
deleted file mode 100644
index 1db2d86..0000000
--- a/0007-openssh-5.8p2-sigpipe.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From e15cc73c1a26dcfab3fd0077ee0561724ad4f8b8 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 07/54] openssh-5.8p2-sigpipe
-
----
- ssh-keyscan.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index 9bd3e78eb..bb3ee462d 100644
---- a/ssh-keyscan.c
-+++ b/ssh-keyscan.c
-@@ -777,6 +777,9 @@ main(int argc, char **argv)
- if (maxfd > fdlim_get(0))
- fdlim_set(maxfd);
- fdcon = xcalloc(maxfd, sizeof(con));
-+
-+ signal(SIGPIPE, SIG_IGN);
-+
- read_wait = xcalloc(maxfd, sizeof(struct pollfd));
- for (j = 0; j < maxfd; j++)
- read_wait[j].fd = -1;
---
-2.53.0
-
diff --git a/0007-openssh-5.9p1-ipv6man.patch b/0007-openssh-5.9p1-ipv6man.patch
new file mode 100644
index 0000000..93f77c5
--- /dev/null
+++ b/0007-openssh-5.9p1-ipv6man.patch
@@ -0,0 +1,40 @@
+From bf4e65b7335e516bae78a0bd0c085ab55f377973 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 07/53] openssh-5.9p1-ipv6man
+
+#(drop?) https://bugzilla.mindrot.org/show_bug.cgi?id=1925
+---
+ ssh.1 | 2 ++
+ sshd.8 | 2 ++
+ 2 files changed, 4 insertions(+)
+
+diff --git a/ssh.1 b/ssh.1
+index 60ee3ab34..089bd348a 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1579,6 +1579,8 @@ manual page for more information.
+ .Nm
+ exits with the exit status of the remote command or with 255
+ if an error occurred.
++.Sh IPV6
++IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
+ .Sh SEE ALSO
+ .Xr scp 1 ,
+ .Xr sftp 1 ,
+diff --git a/sshd.8 b/sshd.8
+index a65aae75d..24e5639a3 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -1024,6 +1024,8 @@ concurrently for different ports, this contains the process ID of the one
+ started last).
+ The content of this file is not sensitive; it can be world-readable.
+ .El
++.Sh IPV6
++IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
+ .Sh SEE ALSO
+ .Xr scp 1 ,
+ .Xr sftp 1 ,
+--
+2.55.0
+
diff --git a/0008-openssh-5.8p2-sigpipe.patch b/0008-openssh-5.8p2-sigpipe.patch
new file mode 100644
index 0000000..312da67
--- /dev/null
+++ b/0008-openssh-5.8p2-sigpipe.patch
@@ -0,0 +1,26 @@
+From dccd2a9aba5fa758cfd27c6de86fa73830aa7f6f Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 08/53] openssh-5.8p2-sigpipe
+
+---
+ ssh-keyscan.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index f093b0108..77b4341c9 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -797,6 +797,9 @@ main(int argc, char **argv)
+ if (maxfd > fdlim_get(0))
+ fdlim_set(maxfd);
+ fdcon = xcalloc(maxfd, sizeof(con));
++
++ signal(SIGPIPE, SIG_IGN);
++
+ read_wait = xcalloc(maxfd, sizeof(struct pollfd));
+ for (j = 0; j < maxfd; j++)
+ read_wait[j].fd = -1;
+--
+2.55.0
+
diff --git a/0009-openssh-5.1p1-askpass-progress.patch b/0009-openssh-5.1p1-askpass-progress.patch
index 7b3674b..9c14332 100644
--- a/0009-openssh-5.1p1-askpass-progress.patch
+++ b/0009-openssh-5.1p1-askpass-progress.patch
@@ -1,7 +1,7 @@
-From 4314d288db2a814ad58e91da21d2a69e4983d3c1 Mon Sep 17 00:00:00 2001
+From 993767d6c9c2b2e9cfa6ff2cd3d7bc8c4869c2f6 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 09/54] openssh-5.1p1-askpass-progress
+Subject: [PATCH 09/53] openssh-5.1p1-askpass-progress
---
contrib/gnome-ssh-askpass2.c | 39 +++++++++++++++++++++++++++++++++---
@@ -92,5 +92,5 @@ index a62f98152..cb7152dc8 100644
/* Grab focus */
--
-2.53.0
+2.55.0
diff --git a/0010-openssh-4.3p2-askpass-grab-info.patch b/0010-openssh-4.3p2-askpass-grab-info.patch
index 61223ae..af4d057 100644
--- a/0010-openssh-4.3p2-askpass-grab-info.patch
+++ b/0010-openssh-4.3p2-askpass-grab-info.patch
@@ -1,7 +1,7 @@
-From db4ca336e4bddfc4c5ce651fbdf1f40348d93b8f Mon Sep 17 00:00:00 2001
+From 68609eb845515333fe86756dd0d0f1fe866943ef Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 10/54] openssh-4.3p2-askpass-grab-info
+Subject: [PATCH 10/53] openssh-4.3p2-askpass-grab-info
#https://bugzilla.redhat.com/show_bug.cgi?id=198332
---
@@ -28,5 +28,5 @@ index cb7152dc8..bbe93d838 100644
gtk_dialog_run(GTK_DIALOG(err));
--
-2.53.0
+2.55.0
diff --git a/0011-openssh-8.7p1-redhat.patch b/0011-openssh-8.7p1-redhat.patch
index 5ab2485..23c071d 100644
--- a/0011-openssh-8.7p1-redhat.patch
+++ b/0011-openssh-8.7p1-redhat.patch
@@ -1,7 +1,7 @@
-From ec63856f2c4276da3aa22c78feacfebfbfb594dc Mon Sep 17 00:00:00 2001
+From 313333f429aedd5f255643400669204a32e61023 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 11/54] openssh-8.7p1-redhat
+Subject: [PATCH 11/53] openssh-8.7p1-redhat
#https://bugzilla.mindrot.org/show_bug.cgi?id=1635 (WONTFIX)
---
@@ -57,7 +57,7 @@ index 000000000..8b1b59021
+# Uncomment this if you want to use .local domain
+# Host *.local
diff --git a/sshd_config b/sshd_config
-index 0f4a3a724..608203e4b 100644
+index 57ecabb86..f43aa8093 100644
--- a/sshd_config
+++ b/sshd_config
@@ -10,6 +10,14 @@
@@ -76,10 +76,10 @@ index 0f4a3a724..608203e4b 100644
#AddressFamily any
#ListenAddress 0.0.0.0
diff --git a/sshd_config.0 b/sshd_config.0
-index 770ec742e..1f71c37c3 100644
+index 2248006ea..91eda77f4 100644
--- a/sshd_config.0
+++ b/sshd_config.0
-@@ -1247,9 +1247,9 @@ DESCRIPTION
+@@ -1265,9 +1265,9 @@ DESCRIPTION
SyslogFacility
Gives the facility code that is used when logging messages from
@@ -93,10 +93,10 @@ index 770ec742e..1f71c37c3 100644
TCPKeepAlive
Specifies whether the system should send TCP keepalive messages
diff --git a/sshd_config.5 b/sshd_config.5
-index 3f5e29812..ca425a8da 100644
+index a7d300139..e85cca6eb 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -1971,7 +1971,7 @@ By default no subsystems are defined.
+@@ -1989,7 +1989,7 @@ By default no subsystems are defined.
.It Cm SyslogFacility
Gives the facility code that is used when logging messages from
.Xr sshd 8 .
@@ -143,5 +143,5 @@ index 000000000..1d592d13f
+Include /etc/crypto-policies/back-ends/opensshserver.config
+
--
-2.53.0
+2.55.0
diff --git a/0012-openssh-7.8p1-UsePAM-warning.patch b/0012-openssh-7.8p1-UsePAM-warning.patch
index 831535a..0d001bd 100644
--- a/0012-openssh-7.8p1-UsePAM-warning.patch
+++ b/0012-openssh-7.8p1-UsePAM-warning.patch
@@ -1,7 +1,7 @@
-From 98092c1fa5c54b32edcb7cef18a4fab6078efbc0 Mon Sep 17 00:00:00 2001
+From 10feee43540cbebad70f9903a6cbd59409f9f0c4 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 12/54] openssh-7.8p1-UsePAM-warning
+Subject: [PATCH 12/53] openssh-7.8p1-UsePAM-warning
# warn users for unsupported UsePAM=no (#757545)
---
@@ -10,10 +10,10 @@ Subject: [PATCH 12/54] openssh-7.8p1-UsePAM-warning
2 files changed, 6 insertions(+)
diff --git a/sshd-session.c b/sshd-session.c
-index 00d00e293..6976d6872 100644
+index 0f41756ff..6e9b28dec 100644
--- a/sshd-session.c
+++ b/sshd-session.c
-@@ -1075,6 +1075,10 @@ main(int ac, char **av)
+@@ -1076,6 +1076,10 @@ main(int ac, char **av)
"enabled authentication methods");
}
@@ -25,10 +25,10 @@ index 00d00e293..6976d6872 100644
if (options.moduli_file != NULL)
dh_set_moduli_file(options.moduli_file);
diff --git a/sshd_config b/sshd_config
-index 608203e4b..48af6321b 100644
+index f43aa8093..f626d5801 100644
--- a/sshd_config
+++ b/sshd_config
-@@ -89,6 +89,8 @@ AuthorizedKeysFile .ssh/authorized_keys
+@@ -90,6 +90,8 @@ AuthorizedKeysFile .ssh/authorized_keys
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and KbdInteractiveAuthentication to 'no'.
@@ -38,5 +38,5 @@ index 608203e4b..48af6321b 100644
#AllowAgentForwarding yes
--
-2.53.0
+2.55.0
diff --git a/0013-openssh-9.6p1-gssapi-keyex.patch b/0013-openssh-9.6p1-gssapi-keyex.patch
index f2106a4..de2cabf 100644
--- a/0013-openssh-9.6p1-gssapi-keyex.patch
+++ b/0013-openssh-9.6p1-gssapi-keyex.patch
@@ -1,9 +1,10 @@
-From 208080ec2ceddb9e4c917d4e57b9bc4452db3049 Mon Sep 17 00:00:00 2001
+From 988b543e5a92109f08a6ef213c834eb82924178e Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 13/54] openssh-9.6p1-gssapi-keyex
+Subject: [PATCH 13/53] openssh-9.6p1-gssapi-keyex
---
+ .depend | 2 +
Makefile.in | 7 +-
auth.c | 3 +-
auth2-gss.c | 52 +++-
@@ -13,55 +14,68 @@ Subject: [PATCH 13/54] openssh-9.6p1-gssapi-keyex
canohost.h | 3 +
clientloop.c | 12 +
configure.ac | 24 ++
- gss-genr.c | 312 ++++++++++++++++++++-
+ gss-genr.c | 313 ++++++++++++++++++++-
gss-serv-krb5.c | 97 ++++++-
gss-serv.c | 200 ++++++++++++--
- kex-names.c | 61 ++++-
+ kex-names.c | 67 ++++-
kex.c | 35 ++-
kex.h | 34 +++
kexdh.c | 10 +
kexgen.c | 2 +-
kexgssc.c | 706 ++++++++++++++++++++++++++++++++++++++++++++++++
kexgsss.c | 603 +++++++++++++++++++++++++++++++++++++++++
- monitor.c | 147 +++++++++-
+ monitor.c | 150 +++++++++-
monitor.h | 2 +
monitor_wrap.c | 57 +++-
monitor_wrap.h | 4 +-
readconf.c | 70 +++++
readconf.h | 6 +
- servconf.c | 46 ++++
- servconf.h | 3 +
+ servconf.c | 28 ++
+ servconf.h | 11 +-
session.c | 10 +-
ssh-gss.h | 64 ++++-
- ssh.1 | 8 +
- ssh.c | 6 +-
+ ssh.1 | 2 +
+ ssh.c | 7 +-
ssh_config | 2 +
- ssh_config.5 | 58 ++++
+ ssh_config.5 | 61 +++++
sshconnect2.c | 154 ++++++++++-
- sshd-auth.c | 55 +++-
+ sshd-auth.c | 58 +++-
sshd-session.c | 9 +-
sshd.c | 3 +-
sshd_config | 2 +
- sshd_config.5 | 40 ++-
+ sshd_config.5 | 43 ++-
sshkey.c | 72 ++++-
sshkey.h | 1 +
- 41 files changed, 3004 insertions(+), 78 deletions(-)
+ 42 files changed, 3004 insertions(+), 84 deletions(-)
create mode 100644 kexgssc.c
create mode 100644 kexgsss.c
+diff --git a/.depend b/.depend
+index 7d2d00bbf..45ad2bf4e 100644
+--- a/.depend
++++ b/.depend
+@@ -67,6 +67,8 @@ kexc25519.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-com
+ kexdh.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ kexecdh.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ kexgen.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshkey.h kex.h mac.h crypto_api.h log.h ssherr.h packet.h dispatch.h ssh2.h sshbuf.h digest.h
++kexgssc.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
++kexgsss.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ kexgex.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ kexgexc.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ kexgexs.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
diff --git a/Makefile.in b/Makefile.in
-index 42b45d33d..a12f6ad1d 100644
+index 2a449680a..1eeb34cad 100644
--- a/Makefile.in
+++ b/Makefile.in
-@@ -108,6 +108,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+@@ -109,6 +109,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
- kexgexc.o kexgexs.o \
+ kexgexc.o kexgexs.o kexmlkem768ecdh.o \
kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
+ kexgssc.o \
sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
sshbuf-io.o misc-agent.o ssherr-libcrypto.o
-@@ -131,7 +132,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \
+@@ -132,7 +133,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \
auth2-chall.o groupaccess.o \
auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
@@ -70,7 +84,7 @@ index 42b45d33d..a12f6ad1d 100644
auth2-gss.o gss-serv.o gss-serv-krb5.o \
loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
sftp-server.o sftp-common.o \
-@@ -143,7 +144,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \
+@@ -144,7 +145,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \
serverloop.o auth.o auth2.o auth-options.o session.o auth2-chall.o \
groupaccess.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
@@ -79,7 +93,7 @@ index 42b45d33d..a12f6ad1d 100644
monitor_wrap.o auth-krb5.o \
audit.o audit-bsm.o audit-linux.o platform.o \
loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
-@@ -555,7 +556,7 @@ regress-prep:
+@@ -566,7 +567,7 @@ regress-prep:
ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile
REGRESSLIBS=libssh.a $(LIBCOMPAT)
@@ -89,7 +103,7 @@ index 42b45d33d..a12f6ad1d 100644
regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS)
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \
diff --git a/auth.c b/auth.c
-index a0217a811..142ac5afd 100644
+index a4a37c11a..9d1307b76 100644
--- a/auth.c
+++ b/auth.c
@@ -354,7 +354,8 @@ auth_root_allowed(struct ssh *ssh, const char *method)
@@ -103,7 +117,7 @@ index a0217a811..142ac5afd 100644
break;
case PERMIT_FORCED_ONLY:
diff --git a/auth2-gss.c b/auth2-gss.c
-index b380dc117..90f099e85 100644
+index 553fba328..d110a4eac 100644
--- a/auth2-gss.c
+++ b/auth2-gss.c
@@ -51,6 +51,7 @@
@@ -163,16 +177,16 @@ index b380dc117..90f099e85 100644
/*
* We only support those mechanisms that we know about (ie ones that we know
* how to check local user kuserok and the like)
-@@ -267,7 +310,7 @@ input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
+@@ -261,7 +304,7 @@ input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
if ((r = sshpkt_get_end(ssh)) != 0)
fatal_fr(r, "parse packet");
- authenticated = mm_ssh_gssapi_userok(authctxt->user);
+ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1);
+ if (!authenticated)
+ auth_failure_delay(authctxt, tstart);
- authctxt->postponed = 0;
- ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
-@@ -315,7 +358,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+@@ -312,7 +355,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
gssbuf.length = sshbuf_len(b);
if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))
@@ -218,7 +232,7 @@ index 99637a89b..a05908cf3 100644
#endif
&methodcfg_passwd,
diff --git a/auth2.c b/auth2.c
-index bfd425eab..3c3bdb776 100644
+index 497fa8ed2..f0f39707f 100644
--- a/auth2.c
+++ b/auth2.c
@@ -71,6 +71,7 @@ extern Authmethod method_passwd;
@@ -357,7 +371,7 @@ index 26d62855a..0cadc9f18 100644
int get_peer_port(int);
char *get_local_ipaddr(int);
diff --git a/clientloop.c b/clientloop.c
-index 6a0e7b6b8..ab5c4d4ad 100644
+index 49950cecc..f076d1d56 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -101,6 +101,10 @@
@@ -371,7 +385,7 @@ index 6a0e7b6b8..ab5c4d4ad 100644
/* Permitted RSA signature algorithms for UpdateHostkeys proofs */
#define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256"
-@@ -1611,6 +1615,14 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
+@@ -1617,6 +1621,14 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
/* Do channel operations. */
channel_after_poll(ssh, pfd, npfd_active);
@@ -387,7 +401,7 @@ index 6a0e7b6b8..ab5c4d4ad 100644
if (conn_in_ready)
client_process_net_input(ssh);
diff --git a/configure.ac b/configure.ac
-index 8f8276c78..75576b45d 100644
+index 4b3cff38c..cb46903f4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -844,6 +844,30 @@ int main(void) { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16))
@@ -422,7 +436,7 @@ index 8f8276c78..75576b45d 100644
AC_CHECK_DECL([AU_IPv4], [],
AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records])
diff --git a/gss-genr.c b/gss-genr.c
-index 7088d93b4..a07dbf512 100644
+index 7088d93b4..ec2c9c6dd 100644
--- a/gss-genr.c
+++ b/gss-genr.c
@@ -42,9 +42,33 @@
@@ -459,7 +473,7 @@ index 7088d93b4..a07dbf512 100644
/* sshbuf_get for gss_buffer_desc */
int
ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
-@@ -60,6 +84,169 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
+@@ -60,6 +84,170 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
return 0;
}
@@ -557,8 +571,9 @@ index 7088d93b4..a07dbf512 100644
+ enclen = __b64_ntop(digest,
+ ssh_digest_bytes(SSH_DIGEST_MD5), encoded,
+ ssh_digest_bytes(SSH_DIGEST_MD5) * 2);
-+
++#pragma GCC diagnostic ignored "-Wstringop-overflow"
+ cp = strncpy(s, kex, strlen(kex));
++#pragma GCC diagnostic pop
+ for ((p = strsep(&cp, ",")); p && *p != '\0';
+ (p = strsep(&cp, ","))) {
+ if (sshbuf_len(buf) != 0 &&
@@ -629,7 +644,7 @@ index 7088d93b4..a07dbf512 100644
/* Check that the OID in a data stream matches that in the context */
int
ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len)
-@@ -168,6 +355,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx)
+@@ -168,6 +356,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx)
(*ctx)->creds = GSS_C_NO_CREDENTIAL;
(*ctx)->client = GSS_C_NO_NAME;
(*ctx)->client_creds = GSS_C_NO_CREDENTIAL;
@@ -637,7 +652,7 @@ index 7088d93b4..a07dbf512 100644
}
/* Delete our context, providing it has been built correctly */
-@@ -193,6 +381,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx)
+@@ -193,6 +382,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx)
gss_release_name(&ms, &(*ctx)->client);
if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL)
gss_release_cred(&ms, &(*ctx)->client_creds);
@@ -650,7 +665,7 @@ index 7088d93b4..a07dbf512 100644
free(*ctx);
*ctx = NULL;
-@@ -216,7 +410,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
+@@ -216,7 +411,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
}
ctx->major = gss_init_sec_context(&ctx->minor,
@@ -659,7 +674,7 @@ index 7088d93b4..a07dbf512 100644
GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag,
0, NULL, recv_tok, NULL, send_tok, flags, NULL);
-@@ -245,9 +439,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
+@@ -245,9 +440,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
return (ctx->major);
}
@@ -703,7 +718,7 @@ index 7088d93b4..a07dbf512 100644
if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context,
GSS_C_QOP_DEFAULT, buffer, hash)))
ssh_gssapi_error(ctx);
-@@ -255,6 +483,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
+@@ -255,6 +484,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
return (ctx->major);
}
@@ -723,7 +738,7 @@ index 7088d93b4..a07dbf512 100644
void
ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
const char *context, const struct sshbuf *session_id)
-@@ -271,11 +512,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
+@@ -271,11 +513,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
}
int
@@ -741,7 +756,7 @@ index 7088d93b4..a07dbf512 100644
/* RFC 4462 says we MUST NOT do SPNEGO */
if (oid->length == spnego_oid.length &&
-@@ -285,6 +531,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+@@ -285,6 +532,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
ssh_gssapi_build_ctx(ctx);
ssh_gssapi_set_oid(*ctx, oid);
major = ssh_gssapi_import_name(*ctx, host);
@@ -752,7 +767,7 @@ index 7088d93b4..a07dbf512 100644
if (!GSS_ERROR(major)) {
major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token,
NULL);
-@@ -294,10 +544,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+@@ -294,10 +545,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
GSS_C_NO_BUFFER);
}
@@ -1256,7 +1271,7 @@ index f9ae303b5..8b4fa9cfe 100644
/* Privileged */
diff --git a/kex-names.c b/kex-names.c
-index 751f06cea..769a36810 100644
+index 116150316..2bc431223 100644
--- a/kex-names.c
+++ b/kex-names.c
@@ -36,6 +36,7 @@
@@ -1278,7 +1293,7 @@ index 751f06cea..769a36810 100644
struct kexalg {
char *name;
u_int type;
-@@ -88,9 +93,22 @@ static const struct kexalg kexalgs[] = {
+@@ -86,9 +91,22 @@ static const struct kexalg kexalgs[] = {
#endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
{ NULL, 0, -1, -1, 0 },
};
@@ -1303,7 +1318,7 @@ index 751f06cea..769a36810 100644
{
char *ret = NULL;
const struct kexalg *k;
-@@ -102,6 +120,18 @@ kex_alg_list(char sep)
+@@ -100,6 +118,18 @@ kex_alg_list(char sep)
return ret;
}
@@ -1322,7 +1337,7 @@ index 751f06cea..769a36810 100644
static const struct kexalg *
kex_alg_by_name(const char *name)
{
-@@ -111,6 +141,10 @@ kex_alg_by_name(const char *name)
+@@ -109,6 +139,10 @@ kex_alg_by_name(const char *name)
if (strcmp(k->name, name) == 0)
return k;
}
@@ -1333,7 +1348,7 @@ index 751f06cea..769a36810 100644
return NULL;
}
-@@ -172,6 +206,12 @@ kex_names_valid(const char *names)
+@@ -170,6 +204,12 @@ kex_names_valid(const char *names)
return 0;
for ((p = strsep(&cp, ",")); p && *p != '\0';
(p = strsep(&cp, ","))) {
@@ -1346,7 +1361,7 @@ index 751f06cea..769a36810 100644
if (kex_alg_by_name(p) == NULL) {
error("Unsupported KEX algorithm \"%.100s\"", p);
free(s);
-@@ -334,3 +368,26 @@ kex_assemble_names(char **listp, const char *def, const char *all)
+@@ -332,3 +372,26 @@ kex_assemble_names(char **listp, const char *def, const char *all)
free(ret);
return r;
}
@@ -1374,7 +1389,7 @@ index 751f06cea..769a36810 100644
+ return 1;
+}
diff --git a/kex.c b/kex.c
-index 85b112c75..3608a5504 100644
+index b2c3d9720..1b8f1c3c5 100644
--- a/kex.c
+++ b/kex.c
@@ -293,17 +293,37 @@ static int
@@ -1421,7 +1436,7 @@ index 85b112c75..3608a5504 100644
(r = sshbuf_put_cstring(m, "0")) != 0 ||
(r = sshbuf_put_cstring(m, "agent-forward")) != 0 ||
(r = sshbuf_put_cstring(m, "0")) != 0) {
-@@ -741,6 +761,9 @@ kex_free(struct kex *kex)
+@@ -744,6 +764,9 @@ kex_free(struct kex *kex)
sshbuf_free(kex->server_version);
sshbuf_free(kex->client_pub);
sshbuf_free(kex->session_id);
@@ -1432,7 +1447,7 @@ index 85b112c75..3608a5504 100644
sshkey_free(kex->initial_hostkey);
free(kex->failed_choice);
diff --git a/kex.h b/kex.h
-index 4f6d92164..2c86b25f9 100644
+index 9816b1d83..21919a9aa 100644
--- a/kex.h
+++ b/kex.h
@@ -29,6 +29,10 @@
@@ -1444,12 +1459,12 @@ index 4f6d92164..2c86b25f9 100644
+#endif
+
#ifdef WITH_OPENSSL
- # include <openssl/bn.h>
- # include <openssl/dh.h>
-@@ -103,6 +107,15 @@ enum kex_exchange {
- KEX_C25519_SHA256,
+ #include <openssl/bn.h>
+ #include <openssl/dh.h>
+@@ -98,6 +102,15 @@ enum kex_exchange {
KEX_KEM_SNTRUP761X25519_SHA512,
KEX_KEM_MLKEM768X25519_SHA256,
+ KEX_KEM_MLKEM768ECDH_SHA256,
+#ifdef GSSAPI
+ KEX_GSS_GRP1_SHA1,
+ KEX_GSS_GRP14_SHA1,
@@ -1462,7 +1477,7 @@ index 4f6d92164..2c86b25f9 100644
KEX_MAX
};
-@@ -170,6 +183,13 @@ struct kex {
+@@ -166,6 +179,13 @@ struct kex {
u_int flags;
int hash_alg;
int ec_nid;
@@ -1476,7 +1491,7 @@ index 4f6d92164..2c86b25f9 100644
char *failed_choice;
int (*verify_host_key)(struct sshkey *, struct ssh *);
struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
-@@ -197,8 +217,10 @@ int kex_nid_from_name(const char *);
+@@ -193,8 +213,10 @@ int kex_nid_from_name(const char *);
int kex_is_pq_from_name(const char *);
int kex_names_valid(const char *);
char *kex_alg_list(char);
@@ -1487,7 +1502,7 @@ index 4f6d92164..2c86b25f9 100644
int kex_assemble_names(char **, const char *, const char *);
void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX],
const char *, const char *, const char *, const char *, const char *);
-@@ -232,6 +254,12 @@ int kexgex_client(struct ssh *);
+@@ -228,6 +250,12 @@ int kexgex_client(struct ssh *);
int kexgex_server(struct ssh *);
int kex_gen_client(struct ssh *);
int kex_gen_server(struct ssh *);
@@ -1500,7 +1515,7 @@ index 4f6d92164..2c86b25f9 100644
int kex_dh_keypair(struct kex *);
int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
-@@ -270,6 +298,12 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
+@@ -275,6 +303,12 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
const BIGNUM *, const u_char *, size_t,
u_char *, size_t *);
@@ -1542,7 +1557,7 @@ index cbcb2d836..b63374b0e 100644
break;
case KEX_DH_GRP18_SHA512:
diff --git a/kexgen.c b/kexgen.c
-index 5643bc831..a2beb3f10 100644
+index d1f9d840a..ff504580f 100644
--- a/kexgen.c
+++ b/kexgen.c
@@ -44,7 +44,7 @@
@@ -2876,7 +2891,7 @@ index 000000000..98e9404df
+
+#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
diff --git a/monitor.c b/monitor.c
-index 1e41dfa53..025c6abbe 100644
+index c659e99e0..fa477899e 100644
--- a/monitor.c
+++ b/monitor.c
@@ -138,6 +138,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *);
@@ -2946,10 +2961,12 @@ index 1e41dfa53..025c6abbe 100644
if (auth_opts->permit_pty_flag) {
monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
-@@ -1924,6 +1948,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
- # ifdef OPENSSL_HAS_ECC
+@@ -1908,8 +1932,18 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+ kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
- # endif
+- kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_server;
+-#endif
+# ifdef GSSAPI
+ if (options.gss_keyex) {
+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
@@ -2961,10 +2978,11 @@ index 1e41dfa53..025c6abbe 100644
+ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
+ }
+# endif
- #endif /* WITH_OPENSSL */
++#endif /* WITH_OPENSSL */
kex->kex[KEX_C25519_SHA256] = kex_gen_server;
kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
-@@ -2022,8 +2057,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
+@@ -2007,8 +2041,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
u_char *p;
int r;
@@ -2975,7 +2993,7 @@ index 1e41dfa53..025c6abbe 100644
if ((r = sshbuf_get_string(m, &p, &len)) != 0)
fatal_fr(r, "parse");
-@@ -2055,8 +2090,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2040,8 +2074,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
OM_uint32 flags = 0; /* GSI needs this */
int r;
@@ -2986,7 +3004,7 @@ index 1e41dfa53..025c6abbe 100644
if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0)
fatal_fr(r, "ssh_gssapi_get_buffer_desc");
-@@ -2076,6 +2111,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2061,6 +2095,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0);
monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1);
monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1);
@@ -2994,7 +3012,7 @@ index 1e41dfa53..025c6abbe 100644
}
return (0);
}
-@@ -2087,8 +2123,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2072,8 +2107,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
OM_uint32 ret;
int r;
@@ -3005,7 +3023,7 @@ index 1e41dfa53..025c6abbe 100644
if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 ||
(r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0)
-@@ -2114,13 +2150,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2099,13 +2134,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
int
mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
{
@@ -3027,7 +3045,7 @@ index 1e41dfa53..025c6abbe 100644
sshbuf_reset(m);
if ((r = sshbuf_put_u32(m, authenticated)) != 0)
-@@ -2129,7 +2169,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2114,7 +2153,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
debug3_f("sending result %d", authenticated);
mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m);
@@ -3040,7 +3058,7 @@ index 1e41dfa53..025c6abbe 100644
if ((displayname = ssh_gssapi_displayname()) != NULL)
auth2_record_info(authctxt, "%s", displayname);
-@@ -2137,5 +2181,84 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2122,5 +2165,84 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
/* Monitor loop will terminate if authenticated */
return (authenticated);
}
@@ -3139,10 +3157,10 @@ index 1b46e794e..75a0d6181 100644
struct ssh;
diff --git a/monitor_wrap.c b/monitor_wrap.c
-index f3c341bc7..496de436e 100644
+index af0252964..2b044f383 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
-@@ -1162,13 +1162,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
+@@ -1112,13 +1112,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
}
int
@@ -3159,7 +3177,7 @@ index f3c341bc7..496de436e 100644
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m);
mm_request_receive_expect(pmonitor->m_recvfd,
-@@ -1181,6 +1183,59 @@ mm_ssh_gssapi_userok(char *user)
+@@ -1131,6 +1133,59 @@ mm_ssh_gssapi_userok(char *user)
debug3_f("user %sauthenticated", authenticated ? "" : "not ");
return (authenticated);
}
@@ -3220,7 +3238,7 @@ index f3c341bc7..496de436e 100644
/*
diff --git a/monitor_wrap.h b/monitor_wrap.h
-index 3e08ddd78..a708bf4d8 100644
+index 30585187c..07b207d1d 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -72,8 +72,10 @@ void mm_decode_activate_server_options(struct ssh *ssh, struct sshbuf *m);
@@ -3236,7 +3254,7 @@ index 3e08ddd78..a708bf4d8 100644
#ifdef USE_PAM
diff --git a/readconf.c b/readconf.c
-index 10cbd04ba..9004076e4 100644
+index 7465216f3..992c78866 100644
--- a/readconf.c
+++ b/readconf.c
@@ -57,6 +57,7 @@
@@ -3322,7 +3340,7 @@ index 10cbd04ba..9004076e4 100644
case oBatchMode:
intptr = &options->batch_mode;
goto parse_flag;
-@@ -2718,7 +2765,13 @@ initialize_options(Options * options)
+@@ -2719,7 +2766,13 @@ initialize_options(Options * options)
options->fwd_opts.streamlocal_bind_unlink = -1;
options->pubkey_authentication = -1;
options->gss_authentication = -1;
@@ -3336,7 +3354,7 @@ index 10cbd04ba..9004076e4 100644
options->password_authentication = -1;
options->kbd_interactive_authentication = -1;
options->kbd_interactive_devices = NULL;
-@@ -2884,8 +2937,18 @@ fill_default_options(Options * options)
+@@ -2885,8 +2938,18 @@ fill_default_options(Options * options)
options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL;
if (options->gss_authentication == -1)
options->gss_authentication = 0;
@@ -3355,7 +3373,7 @@ index 10cbd04ba..9004076e4 100644
if (options->password_authentication == -1)
options->password_authentication = 1;
if (options->kbd_interactive_authentication == -1)
-@@ -3765,7 +3828,14 @@ dump_client_config(Options *o, const char *host)
+@@ -3766,7 +3829,14 @@ dump_client_config(Options *o, const char *host)
dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports);
#ifdef GSSAPI
dump_cfg_fmtint(oGssAuthentication, o->gss_authentication);
@@ -3389,7 +3407,7 @@ index dbcb41725..8b0c98acf 100644
* authentication. */
int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
diff --git a/servconf.c b/servconf.c
-index 668259a20..de3fa9481 100644
+index e7819d59a..f28d19194 100644
--- a/servconf.c
+++ b/servconf.c
@@ -62,6 +62,7 @@
@@ -3398,95 +3416,40 @@ index 668259a20..de3fa9481 100644
#include "version.h"
+#include "ssh-gss.h"
- #if !defined(SSHD_PAM_SERVICE)
- # define SSHD_PAM_SERVICE "sshd"
-@@ -131,9 +132,12 @@ initialize_server_options(ServerOptions *options)
- options->kerberos_ticket_cleanup = -1;
- options->kerberos_get_afs_token = -1;
- options->gss_authentication=-1;
-+ options->gss_keyex = -1;
- options->gss_cleanup_creds = -1;
- options->gss_deleg_creds = -1;
- options->gss_strict_acceptor = -1;
-+ options->gss_store_rekey = -1;
-+ options->gss_kex_algorithms = NULL;
- options->password_authentication = -1;
- options->kbd_interactive_authentication = -1;
- options->permit_empty_passwd = -1;
-@@ -372,12 +376,20 @@ fill_default_server_options(ServerOptions *options)
- options->kerberos_get_afs_token = 0;
- if (options->gss_authentication == -1)
- options->gss_authentication = 0;
-+ if (options->gss_keyex == -1)
-+ options->gss_keyex = 0;
- if (options->gss_cleanup_creds == -1)
- options->gss_cleanup_creds = 1;
- if (options->gss_deleg_creds == -1)
- options->gss_deleg_creds = 1;
- if (options->gss_strict_acceptor == -1)
- options->gss_strict_acceptor = 1;
-+ if (options->gss_store_rekey == -1)
-+ options->gss_store_rekey = 0;
+ #define SSHD_CONFIG_BLOB_VERSION 1
+
+@@ -335,6 +336,10 @@ fill_default_server_options(ServerOptions *options)
+ options->log_facility = SYSLOG_FACILITY_AUTH;
+ if (options->log_level == SYSLOG_LEVEL_NOT_SET)
+ options->log_level = SYSLOG_LEVEL_INFO;
+#ifdef GSSAPI
+ if (options->gss_kex_algorithms == NULL)
+ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
+#endif
- if (options->password_authentication == -1)
- options->password_authentication = 1;
- if (options->kbd_interactive_authentication == -1)
-@@ -565,6 +577,7 @@ typedef enum {
- sPerSourcePenalties, sPerSourcePenaltyExemptList,
- sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
- sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssStrictAcceptor,
-+ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- sAcceptEnv, sSetEnv, sPermitTunnel,
- sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
- sUsePrivilegeSeparation, sAllowAgentForwarding,
-@@ -650,14 +663,24 @@ static struct {
- #ifdef GSSAPI
- { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL },
- { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
-+ { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL },
- { "gssapidelegatecredentials", sGssDelegateCreds, SSHCFG_GLOBAL },
- { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
-+ { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
-+ { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
-+ { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- #else
- { "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
- { "gssapidelegatecredentials", sUnsupported, SSHCFG_GLOBAL },
- { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- #endif
-+ { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
- { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL },
- { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
- { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */
-@@ -1650,6 +1673,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ if (options->permit_user_env == -1) {
+ options->permit_user_env = 0;
+ options->permit_user_env_allowlist = NULL;
+@@ -1435,6 +1440,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->gss_authentication;
goto parse_flag;
-+ case sGssKeyEx:
++ case sGSSAPIKeyExchange:
+ intptr = &options->gss_keyex;
+ goto parse_flag;
+
- case sGssCleanupCreds:
+ case sGSSAPICleanupCredentials:
intptr = &options->gss_cleanup_creds;
goto parse_flag;
-@@ -1662,6 +1689,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -1446,6 +1455,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ case sGSSAPIStrictAcceptorCheck:
intptr = &options->gss_strict_acceptor;
goto parse_flag;
-
-+ case sGssStoreRekey:
++
++ case sGSSAPIStoreCredentialsOnRekey:
+ intptr = &options->gss_store_rekey;
+ goto parse_flag;
+
-+ case sGssKexAlgorithms:
++ case sGSSAPIKexAlgorithms:
+ arg = argv_next(&ac, &av);
+ if (!arg || *arg == '\0')
+ fatal("%.200s line %d: Missing argument.",
@@ -3497,43 +3460,53 @@ index 668259a20..de3fa9481 100644
+ if (*activep && options->gss_kex_algorithms == NULL)
+ options->gss_kex_algorithms = xstrdup(arg);
+ break;
-+
+ #endif /* GSSAPI */
+
case sPasswordAuthentication:
- intptr = &options->password_authentication;
- goto parse_flag;
-@@ -3291,7 +3334,10 @@ dump_config(ServerOptions *o)
- dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
- dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
- dump_cfg_fmtint(sGssDelegateCreds, o->gss_deleg_creds);
-+ dump_cfg_fmtint(sGssKeyEx, o->gss_keyex);
- dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
-+ dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
-+ dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
+@@ -4217,7 +4242,10 @@ dump_config(ServerOptions *o)
+ dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGSSAPICleanupCredentials, o->gss_cleanup_creds);
+ dump_cfg_fmtint(sGSSAPIDelegateCredentials, o->gss_deleg_creds);
++ dump_cfg_fmtint(sGSSAPIKeyExchange, o->gss_keyex);
+ dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
++ dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
++ dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
#endif
dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
dump_cfg_fmtint(sKbdInteractiveAuthentication,
diff --git a/servconf.h b/servconf.h
-index b06db09e1..76641d580 100644
+index 3648e2be1..c6da79f53 100644
--- a/servconf.h
+++ b/servconf.h
-@@ -151,9 +151,12 @@ typedef struct {
- int kerberos_get_afs_token; /* If true, try to get AFS token if
- * authenticated with Kerberos. */
- int gss_authentication; /* If true, permit GSSAPI authentication */
-+ int gss_keyex; /* If true, permit GSSAPI key exchange */
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
- int gss_deleg_creds; /* If true, accept delegated GSS credentials */
- int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */
-+ int gss_store_rekey;
-+ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */
- int password_authentication; /* If true, permit password
- * authentication. */
- int kbd_interactive_authentication; /* If true, permit */
+@@ -316,14 +316,21 @@ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOB
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_INTFLAG(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_ALIAS(GSSAPICleanupCreds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+ SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE)
++SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL)
++SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL)
+ #endif /* GSSAPI */
+
+ #define SSHD_CONFIG_ENTRIES \
diff --git a/session.c b/session.c
-index 93de35d7c..f14d5fab8 100644
+index fc9c9d6f8..7b76f1f71 100644
--- a/session.c
+++ b/session.c
-@@ -2638,13 +2638,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+@@ -2642,13 +2642,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
#ifdef KRB5
if (options.kerberos_ticket_cleanup &&
@@ -3684,26 +3657,10 @@ index 7b14e74a8..8ec451926 100644
#endif /* _SSH_GSS_H */
diff --git a/ssh.1 b/ssh.1
-index c2c8f50f1..b5d0a1d09 100644
+index 089bd348a..a61b0abfa 100644
--- a/ssh.1
+++ b/ssh.1
-@@ -543,9 +543,15 @@ For full details of the options listed below, and their possible values, see
- .It ForwardX11Timeout
- .It ForwardX11Trusted
- .It GSSAPIAuthentication
-+.It GSSAPIKeyExchange
-+.It GSSAPIClientIdentity
- .It GSSAPIDelegateCredentials
- .It GatewayPorts
- .It GlobalKnownHostsFile
-+.It GSSAPIKexAlgorithms
-+.It GSSAPIRenewalForcesRekey
-+.It GSSAPIServerIdentity
-+.It GSSAPITrustDns
- .It HashKnownHosts
- .It Host
- .It HostKeyAlgorithms
-@@ -640,6 +646,8 @@ flag),
+@@ -539,6 +539,8 @@ flag),
(supported message integrity codes),
.Ar kex
(key exchange algorithms),
@@ -3713,10 +3670,10 @@ index c2c8f50f1..b5d0a1d09 100644
(key types),
.Ar key-ca-sign
diff --git a/ssh.c b/ssh.c
-index 531f28eb2..bc3be8f8e 100644
+index e9f99c433..b9a59ca43 100644
--- a/ssh.c
+++ b/ssh.c
-@@ -805,6 +805,9 @@ main(int ac, char **av)
+@@ -790,6 +790,9 @@ main(int ac, char **av)
else if (strcmp(optarg, "kex") == 0 ||
strcasecmp(optarg, "KexAlgorithms") == 0)
cp = kex_alg_list('\n');
@@ -3726,7 +3683,7 @@ index 531f28eb2..bc3be8f8e 100644
else if (strcmp(optarg, "key") == 0)
cp = sshkey_alg_list(0, 0, 0, '\n');
else if (strcmp(optarg, "key-cert") == 0)
-@@ -835,8 +837,8 @@ main(int ac, char **av)
+@@ -820,8 +823,8 @@ main(int ac, char **av)
} else if (strcmp(optarg, "help") == 0) {
cp = xstrdup(
"cipher\ncipher-auth\ncompression\nkex\n"
@@ -3751,10 +3708,10 @@ index d9324c957..ca7c5853b 100644
# CheckHostIP no
# AddressFamily any
diff --git a/ssh_config.5 b/ssh_config.5
-index b459b0449..9e5163774 100644
+index bdaa49c81..04797a1e9 100644
--- a/ssh_config.5
+++ b/ssh_config.5
-@@ -976,10 +976,71 @@ The default is
+@@ -1014,10 +1014,71 @@ The default is
Specifies whether user authentication based on GSSAPI is allowed.
The default is
.Cm no .
@@ -3827,10 +3784,10 @@ index b459b0449..9e5163774 100644
Indicates that
.Xr ssh 1
diff --git a/sshconnect2.c b/sshconnect2.c
-index 478a9a52f..31bbc0ed8 100644
+index 0d4888402..5459caa0c 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
-@@ -223,6 +223,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -223,6 +223,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
char *all_key, *hkalgs = NULL;
int r, use_known_hosts_order = 0;
@@ -3839,10 +3796,10 @@ index 478a9a52f..31bbc0ed8 100644
+ char *gss_host = NULL;
+#endif
+
- xxx_host = host;
- xxx_hostaddr = hostaddr;
- xxx_conn_info = cinfo;
-@@ -256,6 +261,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ xxx_host = xstrdup(host);
+ xxx_hostaddr = *hostaddr;
+ xxx_conn_info = ssh_conn_info_dup(cinfo);
+@@ -257,6 +262,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
compression_alg_list(options.compression),
hkalgs ? hkalgs : options.hostkeyalgorithms);
@@ -3885,10 +3842,10 @@ index 478a9a52f..31bbc0ed8 100644
free(hkalgs);
/* start key exchange */
-@@ -272,15 +313,45 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- # ifdef OPENSSL_HAS_ECC
+@@ -271,16 +312,46 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
+ ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client;
ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client;
- # endif
-#endif
+# ifdef GSSAPI
+ if (options.gss_keyex) {
@@ -3905,6 +3862,7 @@ index 478a9a52f..31bbc0ed8 100644
ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_client;
ssh->kex->verify_host_key=&verify_host_key_callback;
+#if defined(GSSAPI) && defined(WITH_OPENSSL)
@@ -4060,10 +4018,10 @@ index 478a9a52f..31bbc0ed8 100644
static int
diff --git a/sshd-auth.c b/sshd-auth.c
-index 73acb0dbe..885cd8281 100644
+index 14109151c..079063f47 100644
--- a/sshd-auth.c
+++ b/sshd-auth.c
-@@ -691,7 +691,7 @@ main(int ac, char **av)
+@@ -665,7 +665,7 @@ main(int ac, char **av)
break;
}
}
@@ -4072,7 +4030,7 @@ index 73acb0dbe..885cd8281 100644
fatal("internal error: received no hostkeys");
/* Ensure that umask disallows at least group and world write */
-@@ -811,6 +811,48 @@ do_ssh2_kex(struct ssh *ssh)
+@@ -784,6 +784,48 @@ do_ssh2_kex(struct ssh *ssh)
free(hkalgs);
@@ -4121,10 +4079,12 @@ index 73acb0dbe..885cd8281 100644
if ((r = kex_exchange_identification(ssh, -1,
options.version_addendum)) != 0)
sshpkt_fatal(ssh, r, "banner exchange");
-@@ -836,6 +878,17 @@ do_ssh2_kex(struct ssh *ssh)
- # ifdef OPENSSL_HAS_ECC
+@@ -807,8 +849,18 @@ do_ssh2_kex(struct ssh *ssh)
+ kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+ kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
- # endif /* OPENSSL_HAS_ECC */
+- kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_server;
+-#endif
+# ifdef GSSAPI
+ if (options.gss_keyex) {
+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
@@ -4136,14 +4096,15 @@ index 73acb0dbe..885cd8281 100644
+ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
+ }
+# endif
- #endif /* WITH_OPENSSL */
++#endif /* WITH_OPENSSL */
kex->kex[KEX_C25519_SHA256] = kex_gen_server;
kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
diff --git a/sshd-session.c b/sshd-session.c
-index 6976d6872..f847fc0a9 100644
+index 6e9b28dec..cf6709f46 100644
--- a/sshd-session.c
+++ b/sshd-session.c
-@@ -564,8 +564,8 @@ notify_hostkeys(struct ssh *ssh)
+@@ -565,8 +565,8 @@ notify_hostkeys(struct ssh *ssh)
}
debug3_f("sent %u hostkeys", nkeys);
if (nkeys == 0)
@@ -4154,7 +4115,7 @@ index 6976d6872..f847fc0a9 100644
sshpkt_fatal(ssh, r, "%s: send", __func__);
sshbuf_free(buf);
}
-@@ -1107,8 +1107,9 @@ main(int ac, char **av)
+@@ -1108,8 +1108,9 @@ main(int ac, char **av)
break;
}
}
@@ -4167,10 +4128,10 @@ index 6976d6872..f847fc0a9 100644
/* Ensure that umask disallows at least group and world write */
new_umask = umask(0077) | 0022;
diff --git a/sshd.c b/sshd.c
-index 74d25fc73..552435817 100644
+index 7d1466d97..f66e1f86f 100644
--- a/sshd.c
+++ b/sshd.c
-@@ -1667,7 +1667,8 @@ main(int ac, char **av)
+@@ -1669,7 +1669,8 @@ main(int ac, char **av)
free(fp);
}
accumulate_host_timing_secret(cfg, NULL);
@@ -4181,10 +4142,10 @@ index 74d25fc73..552435817 100644
exit(1);
}
diff --git a/sshd_config b/sshd_config
-index 48af6321b..8db9f0fb1 100644
+index f626d5801..404aa1bbb 100644
--- a/sshd_config
+++ b/sshd_config
-@@ -79,6 +79,8 @@ AuthorizedKeysFile .ssh/authorized_keys
+@@ -80,6 +80,8 @@ AuthorizedKeysFile .ssh/authorized_keys
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
@@ -4194,10 +4155,10 @@ index 48af6321b..8db9f0fb1 100644
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
diff --git a/sshd_config.5 b/sshd_config.5
-index ca425a8da..5928b6cd2 100644
+index e85cca6eb..625e6fdec 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -752,9 +752,19 @@ on logout.
+@@ -760,9 +760,19 @@ on logout.
The default is
.Cm yes .
.It Cm GSSAPIDelegateCredentials
@@ -4219,10 +4180,10 @@ index ca425a8da..5928b6cd2 100644
.It Cm GSSAPIStrictAcceptorCheck
Determines whether to be strict about the identity of the GSSAPI acceptor
a client authenticates against.
-@@ -769,6 +779,35 @@ machine's default store.
- This facility is provided to assist with operation on multi homed machines.
+@@ -778,6 +788,35 @@ This facility is provided to assist with operation on multi homed machines.
The default is
.Cm yes .
+ This option may not be effective in Windows Active Directory environments.
+.It Cm GSSAPIStoreCredentialsOnRekey
+Controls whether the user's GSSAPI credentials should be updated following a
+successful connection rekeying. This option can be used to accepted renewed
@@ -4256,7 +4217,7 @@ index ca425a8da..5928b6cd2 100644
Specifies the signature algorithms that will be accepted for hostbased
authentication as a list of comma-separated patterns.
diff --git a/sshkey.c b/sshkey.c
-index 59d14531c..ccc35dbf5 100644
+index a590529a0..7d1401a8f 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -115,6 +115,75 @@ extern const struct sshkey_impl sshkey_rsa_sha512_impl;
@@ -4353,17 +4314,17 @@ index 59d14531c..ccc35dbf5 100644
if (!include_sigonly && impl->sigonly)
continue;
diff --git a/sshkey.h b/sshkey.h
-index a9cdfcd19..9eced5d8a 100644
+index e4f84f16f..3e738f42c 100644
--- a/sshkey.h
+++ b/sshkey.h
-@@ -67,6 +67,7 @@ enum sshkey_types {
- KEY_ECDSA_SK_CERT,
- KEY_ED25519_SK,
+@@ -63,6 +63,7 @@ enum sshkey_types {
KEY_ED25519_SK_CERT,
+ KEY_MLDSA44_ED25519,
+ KEY_MLDSA44_ED25519_CERT,
+ KEY_NULL,
KEY_UNSPEC
};
--
-2.53.0
+2.55.0
diff --git a/0014-openssh-6.6p1-force_krb.patch b/0014-openssh-6.6p1-force_krb.patch
index 36b9f6c..fdd0c85 100644
--- a/0014-openssh-6.6p1-force_krb.patch
+++ b/0014-openssh-6.6p1-force_krb.patch
@@ -1,7 +1,7 @@
-From a0339453b9b0ac515752d0c60ffd9bcb46563e71 Mon Sep 17 00:00:00 2001
+From 5d1debdaee7b6dc51c5f10530b4698a7a2f1bb80 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 14/54] openssh-6.6p1-force_krb
+Subject: [PATCH 14/53] openssh-6.6p1-force_krb
#http://www.mail-archive.com/kerberos@mit.edu/msg17591.html
---
@@ -218,7 +218,7 @@ index be401fe0e..c26174928 100644
/* This writes out any forwarded credentials from the structure populated
* during userauth. Called after we have setuid to the user */
diff --git a/session.c b/session.c
-index f14d5fab8..610a2cc6f 100644
+index 7b76f1f71..b4db64ea4 100644
--- a/session.c
+++ b/session.c
@@ -643,6 +643,29 @@ do_exec(struct ssh *ssh, Session *s, const char *command)
@@ -267,10 +267,10 @@ index 8ec451926..db34d77f4 100644
/* draft-ietf-secsh-gsskeyex-06 */
diff --git a/sshd.8 b/sshd.8
-index 0226a8303..e2d8ff003 100644
+index 24e5639a3..8ef358cd5 100644
--- a/sshd.8
+++ b/sshd.8
-@@ -286,6 +286,7 @@ Finally, the server and the client enter an authentication dialog.
+@@ -287,6 +287,7 @@ Finally, the server and the client enter an authentication dialog.
The client tries to authenticate itself using
host-based authentication,
public key authentication,
@@ -278,7 +278,7 @@ index 0226a8303..e2d8ff003 100644
challenge-response authentication,
or password authentication.
.Pp
-@@ -874,6 +875,12 @@ This file is used in exactly the same way as
+@@ -878,6 +879,12 @@ This file is used in exactly the same way as
but allows host-based authentication without permitting login with
rlogin/rsh.
.Pp
@@ -292,5 +292,5 @@ index 0226a8303..e2d8ff003 100644
This directory is the default location for all user-specific configuration
and authentication information.
--
-2.53.0
+2.55.0
diff --git a/0015-openssh-7.7p1-gssapi-new-unique.patch b/0015-openssh-7.7p1-gssapi-new-unique.patch
index f697b7b..55ebe93 100644
--- a/0015-openssh-7.7p1-gssapi-new-unique.patch
+++ b/0015-openssh-7.7p1-gssapi-new-unique.patch
@@ -1,25 +1,23 @@
-From ea30e158dcece39525beb0bc16eb4a26d8eb801f Mon Sep 17 00:00:00 2001
+From 9452875760b99fb538463f291fcedee75dee4a38 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 15/54] openssh-7.7p1-gssapi-new-unique
+Subject: [PATCH 15/53] openssh-7.7p1-gssapi-new-unique
-# Improve ccache handling in openssh (#991186, #1199363, #1566494)
-# https://bugzilla.mindrot.org/show_bug.cgi?id=2775
---
- auth-krb5.c | 262 ++++++++++++++++++++++++++++++++++++++++++------
+ auth-krb5.c | 264 ++++++++++++++++++++++++++++++++++++++++++------
auth.h | 3 +-
gss-serv-krb5.c | 42 +++-----
gss-serv.c | 12 +--
- servconf.c | 12 ++-
+ servconf.c | 5 +
servconf.h | 2 +
session.c | 5 +-
ssh-gss.h | 4 +-
sshd-session.c | 2 +-
sshd_config.5 | 8 ++
- 10 files changed, 280 insertions(+), 72 deletions(-)
+ 10 files changed, 276 insertions(+), 71 deletions(-)
diff --git a/auth-krb5.c b/auth-krb5.c
-index 3c6dc0622..0de1aaf8d 100644
+index 3c6dc0622..863110910 100644
--- a/auth-krb5.c
+++ b/auth-krb5.c
@@ -51,6 +51,7 @@
@@ -127,7 +125,7 @@ index 3c6dc0622..0de1aaf8d 100644
if (authctxt->krb5_user) {
krb5_free_principal(authctxt->krb5_ctx, authctxt->krb5_user);
authctxt->krb5_user = NULL;
-@@ -238,36 +281,189 @@ krb5_cleanup_proc(Authctxt *authctxt)
+@@ -238,36 +281,191 @@ krb5_cleanup_proc(Authctxt *authctxt)
}
}
@@ -281,6 +279,7 @@ index 3c6dc0622..0de1aaf8d 100644
+ umask(old_umask);
+ if (tmpfd == -1) {
+ logit("mkstemp(): %.100s", strerror(oerrno));
++ free(ccname);
+ return oerrno;
+ }
+
@@ -288,6 +287,7 @@ index 3c6dc0622..0de1aaf8d 100644
+ oerrno = errno;
+ logit("fchmod(): %.100s", strerror(oerrno));
+ close(tmpfd);
++ free(ccname);
+ return oerrno;
+ }
+ /* make sure the KRB5CCNAME is set for non-standard location */
@@ -341,7 +341,7 @@ index 3c6dc0622..0de1aaf8d 100644
#endif /* !HEIMDAL */
#endif /* KRB5 */
diff --git a/auth.h b/auth.h
-index 32ee3cbf4..9e55ff8ad 100644
+index efe819323..2a19e2b34 100644
--- a/auth.h
+++ b/auth.h
@@ -85,6 +85,7 @@ struct Authctxt {
@@ -352,7 +352,7 @@ index 32ee3cbf4..9e55ff8ad 100644
#endif
struct sshbuf *loginmsg;
-@@ -243,7 +244,7 @@ FILE *auth_openprincipals(const char *, struct passwd *, int);
+@@ -244,7 +245,7 @@ FILE *auth_openprincipals(const char *, struct passwd *, int);
int sys_auth_passwd(struct ssh *, const char *);
#if defined(KRB5) && !defined(HEIMDAL)
@@ -516,83 +516,49 @@ index 8b4fa9cfe..6eb7d2163 100644
ok = mm_ssh_gssapi_update_creds(&gssapi_client.store);
diff --git a/servconf.c b/servconf.c
-index de3fa9481..10b43d56a 100644
+index f28d19194..ae18d56fd 100644
--- a/servconf.c
+++ b/servconf.c
-@@ -131,6 +131,7 @@ initialize_server_options(ServerOptions *options)
- options->kerberos_or_local_passwd = -1;
- options->kerberos_ticket_cleanup = -1;
- options->kerberos_get_afs_token = -1;
-+ options->kerberos_unique_ccache = -1;
- options->gss_authentication=-1;
- options->gss_keyex = -1;
- options->gss_cleanup_creds = -1;
-@@ -374,6 +375,8 @@ fill_default_server_options(ServerOptions *options)
- options->kerberos_ticket_cleanup = 1;
- if (options->kerberos_get_afs_token == -1)
- options->kerberos_get_afs_token = 0;
-+ if (options->kerberos_unique_ccache == -1)
-+ options->kerberos_unique_ccache = 0;
- if (options->gss_authentication == -1)
- options->gss_authentication = 0;
- if (options->gss_keyex == -1)
-@@ -561,7 +564,7 @@ typedef enum {
- sPort, sHostKeyFile, sLoginGraceTime,
- sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose,
- sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
-- sKerberosGetAFSToken, sPasswordAuthentication,
-+ sKerberosGetAFSToken, sKerberosUniqueCCache, sPasswordAuthentication,
- sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- sPrintMotd, sPrintLastLog, sIgnoreRhosts,
- sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-@@ -652,11 +655,13 @@ static struct {
- #else
- { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- #endif
-+ { "kerberosuniqueccache", sKerberosUniqueCCache, SSHCFG_GLOBAL },
- #else
- { "kerberosauthentication", sUnsupported, SSHCFG_ALL },
- { "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL },
- { "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL },
- { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
-+ { "kerberosuniqueccache", sUnsupported, SSHCFG_GLOBAL },
- #endif
- { "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL },
- { "afstokenpassing", sUnsupported, SSHCFG_GLOBAL },
-@@ -1669,6 +1674,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- intptr = &options->kerberos_get_afs_token;
- goto parse_flag;
+@@ -1435,6 +1435,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ #endif /* USE_AFS */
+ #endif /* KRB5 */
+ case sKerberosUniqueCCache:
+ intptr = &options->kerberos_unique_ccache;
+ goto parse_flag;
+
- case sGssAuthentication:
+ #ifdef GSSAPI
+ case sGSSAPIAuthentication:
intptr = &options->gss_authentication;
- goto parse_flag;
-@@ -3329,6 +3338,7 @@ dump_config(ServerOptions *o)
+@@ -4237,6 +4241,7 @@ dump_config(ServerOptions *o)
# ifdef USE_AFS
dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token);
# endif
+ dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
#endif
#ifdef GSSAPI
- dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
diff --git a/servconf.h b/servconf.h
-index 76641d580..bf589911a 100644
+index c6da79f53..5f5672082 100644
--- a/servconf.h
+++ b/servconf.h
-@@ -150,6 +150,8 @@ typedef struct {
- * file on logout. */
- int kerberos_get_afs_token; /* If true, try to get AFS token if
- * authenticated with Kerberos. */
-+ int kerberos_unique_ccache; /* If true, the acquired ticket will
-+ * be stored in per-session ccache */
- int gss_authentication; /* If true, permit GSSAPI authentication */
- int gss_keyex; /* If true, permit GSSAPI key exchange */
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
+@@ -303,12 +303,14 @@ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOB
+ SSHCONF_INTFLAG(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHD_CONFIG_KRB5_AFS
+ #else /* KRB5 */
+ #define SSHD_CONFIG_ENTRIES_KRB5 \
+ SSHCONF_UNSUPPORTED_INT(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOBAL)
+ #endif /* KRB5 */
+
diff --git a/session.c b/session.c
-index 610a2cc6f..517cab6b6 100644
+index b4db64ea4..c2cdd6c4f 100644
--- a/session.c
+++ b/session.c
@@ -988,7 +988,8 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
@@ -637,10 +603,10 @@ index db34d77f4..a894e23c9 100644
char *ssh_gssapi_server_mechanisms(void);
diff --git a/sshd-session.c b/sshd-session.c
-index f847fc0a9..9d75a4555 100644
+index cf6709f46..8a3121d01 100644
--- a/sshd-session.c
+++ b/sshd-session.c
-@@ -1295,7 +1295,7 @@ main(int ac, char **av)
+@@ -1298,7 +1298,7 @@ main(int ac, char **av)
#ifdef GSSAPI
if (options.gss_authentication) {
temporarily_use_uid(authctxt->pw);
@@ -650,10 +616,10 @@ index f847fc0a9..9d75a4555 100644
}
#endif
diff --git a/sshd_config.5 b/sshd_config.5
-index 5928b6cd2..d0b0dfe36 100644
+index 625e6fdec..08a304cdc 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -1057,6 +1057,14 @@ Specifies whether to automatically destroy the user's ticket cache
+@@ -1074,6 +1074,14 @@ Specifies whether to automatically destroy the user's ticket cache
file on logout.
The default is
.Cm yes .
@@ -669,5 +635,5 @@ index 5928b6cd2..d0b0dfe36 100644
Specifies the permitted KEX (Key Exchange) algorithms that the server will
offer to clients.
--
-2.53.0
+2.55.0
diff --git a/0016-openssh-7.2p2-k5login_directory.patch b/0016-openssh-7.2p2-k5login_directory.patch
index caa5b9d..2cd743e 100644
--- a/0016-openssh-7.2p2-k5login_directory.patch
+++ b/0016-openssh-7.2p2-k5login_directory.patch
@@ -1,7 +1,7 @@
-From f05d0adc8f15e01a3479d49645df26170be9d776 Mon Sep 17 00:00:00 2001
+From 76a46dc287ab78071242bbf8f31dd1dd2f805970 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 16/54] openssh-7.2p2-k5login_directory
+Subject: [PATCH 16/53] openssh-7.2p2-k5login_directory
# Respect k5login_directory option in krk5.conf (#1328243)
---
@@ -12,10 +12,10 @@ Subject: [PATCH 16/54] openssh-7.2p2-k5login_directory
4 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/auth-krb5.c b/auth-krb5.c
-index 0de1aaf8d..148242cbf 100644
+index 863110910..996b19738 100644
--- a/auth-krb5.c
+++ b/auth-krb5.c
-@@ -465,5 +465,21 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
+@@ -467,5 +467,21 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
return (krb5_cc_resolve(ctx, ccname, ccache));
}
}
@@ -38,10 +38,10 @@ index 0de1aaf8d..148242cbf 100644
#endif /* !HEIMDAL */
#endif /* KRB5 */
diff --git a/auth.h b/auth.h
-index 9e55ff8ad..815f821f9 100644
+index 2a19e2b34..9dd25f494 100644
--- a/auth.h
+++ b/auth.h
-@@ -245,6 +245,8 @@ int sys_auth_passwd(struct ssh *, const char *);
+@@ -246,6 +246,8 @@ int sys_auth_passwd(struct ssh *, const char *);
#if defined(KRB5) && !defined(HEIMDAL)
krb5_error_code ssh_krb5_cc_new_unique(krb5_context, krb5_ccache *, int *);
@@ -84,10 +84,10 @@ index 415fb1893..7df31c6af 100644
}
diff --git a/sshd.8 b/sshd.8
-index e2d8ff003..fa33f5232 100644
+index 8ef358cd5..ae22a9ea4 100644
--- a/sshd.8
+++ b/sshd.8
-@@ -880,6 +880,10 @@ rlogin/rsh.
+@@ -884,6 +884,10 @@ rlogin/rsh.
These files enforce GSSAPI/Kerberos authentication access control.
Further details are described in
.Xr ksu 1 .
@@ -99,5 +99,5 @@ index e2d8ff003..fa33f5232 100644
.It Pa ~/.ssh/
This directory is the default location for all user-specific configuration
--
-2.53.0
+2.55.0
diff --git a/0017-openssh-6.6p1-kuserok.patch b/0017-openssh-6.6p1-kuserok.patch
index 7e0ae52..f84381d 100644
--- a/0017-openssh-6.6p1-kuserok.patch
+++ b/0017-openssh-6.6p1-kuserok.patch
@@ -1,20 +1,19 @@
-From bdb2b58e1a7f4d4a42a0aca1f8f3015b5edc86a1 Mon Sep 17 00:00:00 2001
+From 4672febd5ff5082fe25b18069485f05491238dc2 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 17/54] openssh-6.6p1-kuserok
+Subject: [PATCH 17/53] openssh-6.6p1-kuserok
-#https://bugzilla.mindrot.org/show_bug.cgi?id=1780
---
auth-krb5.c | 20 ++++++++-
gss-serv-krb5.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c | 13 +++++-
- servconf.h | 1 +
+ servconf.c | 5 +++
+ servconf.h | 2 +
sshd_config | 1 +
sshd_config.5 | 5 +++
- 6 files changed, 139 insertions(+), 7 deletions(-)
+ 6 files changed, 133 insertions(+), 6 deletions(-)
diff --git a/auth-krb5.c b/auth-krb5.c
-index 148242cbf..287bdd35a 100644
+index 996b19738..2e3c1c80f 100644
--- a/auth-krb5.c
+++ b/auth-krb5.c
@@ -55,6 +55,21 @@
@@ -190,51 +189,10 @@ index 7df31c6af..6f5826ce8 100644
if ((fp = fopen(file, "r")) == NULL) {
int saved_errno = errno;
diff --git a/servconf.c b/servconf.c
-index 10b43d56a..751388bc0 100644
+index ae18d56fd..5de03f3bf 100644
--- a/servconf.c
+++ b/servconf.c
-@@ -139,6 +139,7 @@ initialize_server_options(ServerOptions *options)
- options->gss_strict_acceptor = -1;
- options->gss_store_rekey = -1;
- options->gss_kex_algorithms = NULL;
-+ options->use_kuserok = -1;
- options->password_authentication = -1;
- options->kbd_interactive_authentication = -1;
- options->permit_empty_passwd = -1;
-@@ -393,6 +394,8 @@ fill_default_server_options(ServerOptions *options)
- if (options->gss_kex_algorithms == NULL)
- options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
- #endif
-+ if (options->use_kuserok == -1)
-+ options->use_kuserok = 1;
- if (options->password_authentication == -1)
- options->password_authentication = 1;
- if (options->kbd_interactive_authentication == -1)
-@@ -564,7 +567,7 @@ typedef enum {
- sPort, sHostKeyFile, sLoginGraceTime,
- sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose,
- sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
-- sKerberosGetAFSToken, sKerberosUniqueCCache, sPasswordAuthentication,
-+ sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication,
- sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- sPrintMotd, sPrintLastLog, sIgnoreRhosts,
- sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-@@ -656,12 +659,14 @@ static struct {
- { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- #endif
- { "kerberosuniqueccache", sKerberosUniqueCCache, SSHCFG_GLOBAL },
-+ { "kerberosusekuserok", sKerberosUseKuserok, SSHCFG_ALL },
- #else
- { "kerberosauthentication", sUnsupported, SSHCFG_ALL },
- { "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL },
- { "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL },
- { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- { "kerberosuniqueccache", sUnsupported, SSHCFG_GLOBAL },
-+ { "kerberosusekuserok", sUnsupported, SSHCFG_ALL },
- #endif
- { "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL },
- { "afstokenpassing", sUnsupported, SSHCFG_GLOBAL },
-@@ -2463,6 +2468,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -2229,6 +2229,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
}
break;
@@ -245,39 +203,39 @@ index 10b43d56a..751388bc0 100644
case sMatch:
if (cmdline)
fatal("Match directive not supported as a command-line "
-@@ -3030,6 +3039,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- M_CP_INTOPT(client_alive_interval);
- M_CP_INTOPT(ip_qos_interactive);
- M_CP_INTOPT(ip_qos_bulk);
-+ M_CP_INTOPT(use_kuserok);
- M_CP_INTOPT(rekey_limit);
- M_CP_INTOPT(rekey_interval);
- M_CP_INTOPT(log_level);
-@@ -3339,6 +3349,7 @@ dump_config(ServerOptions *o)
+@@ -4242,6 +4246,7 @@ dump_config(ServerOptions *o)
dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token);
# endif
dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
+ dump_cfg_fmtint(sKerberosUseKuserok, o->use_kuserok);
#endif
#ifdef GSSAPI
- dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
diff --git a/servconf.h b/servconf.h
-index bf589911a..9843f96c5 100644
+index 5f5672082..7c003b8c5 100644
--- a/servconf.h
+++ b/servconf.h
-@@ -152,6 +152,7 @@ typedef struct {
- * authenticated with Kerberos. */
- int kerberos_unique_ccache; /* If true, the acquired ticket will
- * be stored in per-session ccache */
-+ int use_kuserok;
- int gss_authentication; /* If true, permit GSSAPI authentication */
- int gss_keyex; /* If true, permit GSSAPI key exchange */
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
+@@ -304,6 +304,7 @@ SSHCONF_INTFLAG(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL, 0,
+ SSHCONF_INTFLAG(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(use_kuserok, KerberosUseKuserok, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+ SSHD_CONFIG_KRB5_AFS
+ #else /* KRB5 */
+ #define SSHD_CONFIG_ENTRIES_KRB5 \
+@@ -311,6 +312,7 @@ SSHCONF_UNSUPPORTED_INT(kerberos_authentication, KerberosAuthentication, SSHCFG_
+ SSHCONF_UNSUPPORTED_INT(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(use_kuserok, KerberosUseKuserok, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOBAL)
+ #endif /* KRB5 */
+
diff --git a/sshd_config b/sshd_config
-index 8db9f0fb1..ea5a878e6 100644
+index 404aa1bbb..76279d795 100644
--- a/sshd_config
+++ b/sshd_config
-@@ -75,6 +75,7 @@ AuthorizedKeysFile .ssh/authorized_keys
+@@ -76,6 +76,7 @@ AuthorizedKeysFile .ssh/authorized_keys
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
@@ -286,10 +244,10 @@ index 8db9f0fb1..ea5a878e6 100644
# GSSAPI options
#GSSAPIAuthentication no
diff --git a/sshd_config.5 b/sshd_config.5
-index d0b0dfe36..1762874c4 100644
+index 08a304cdc..0077ee096 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -1065,6 +1065,10 @@ The default value
+@@ -1082,6 +1082,10 @@ The default value
.Cm no
can lead to overwriting previous tickets by subseqent connections to the same
user account.
@@ -300,7 +258,7 @@ index d0b0dfe36..1762874c4 100644
.It Cm KexAlgorithms
Specifies the permitted KEX (Key Exchange) algorithms that the server will
offer to clients.
-@@ -1379,6 +1383,7 @@ Available keywords are
+@@ -1398,6 +1402,7 @@ Available keywords are
.Cm IPQoS ,
.Cm KbdInteractiveAuthentication ,
.Cm KerberosAuthentication ,
@@ -309,5 +267,5 @@ index d0b0dfe36..1762874c4 100644
.Cm MaxAuthTries ,
.Cm MaxSessions ,
--
-2.53.0
+2.55.0
diff --git a/0018-openssh-6.4p1-fromto-remote.patch b/0018-openssh-6.4p1-fromto-remote.patch
index 3bee6a3..204c575 100644
--- a/0018-openssh-6.4p1-fromto-remote.patch
+++ b/0018-openssh-6.4p1-fromto-remote.patch
@@ -1,7 +1,7 @@
-From c095e2f48e279af65a9419f580ac9cae14453a93 Mon Sep 17 00:00:00 2001
+From e6388dfdb9cc60c505b7d2765ec0688de0820abb Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 18/54] openssh-6.4p1-fromto-remote
+Subject: [PATCH 18/53] openssh-6.4p1-fromto-remote
# Use tty allocation for a remote scp (#985650)
---
@@ -9,7 +9,7 @@ Subject: [PATCH 18/54] openssh-6.4p1-fromto-remote
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/scp.c b/scp.c
-index 1faa9a555..d87d939a1 100644
+index 28ac7a29d..4821514ba 100644
--- a/scp.c
+++ b/scp.c
@@ -1143,7 +1143,10 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
@@ -25,5 +25,5 @@ index 1faa9a555..d87d939a1 100644
addargs(&alist, "%s",
remote_remote_args.list[j]);
--
-2.53.0
+2.55.0
diff --git a/0019-openssh-6.6.1p1-log-in-chroot.patch b/0019-openssh-6.6.1p1-log-in-chroot.patch
index b4a46ab..2538ec2 100644
--- a/0019-openssh-6.6.1p1-log-in-chroot.patch
+++ b/0019-openssh-6.6.1p1-log-in-chroot.patch
@@ -1,9 +1,8 @@
-From f187210c18f933a3b239c49bd09efeabba4e4712 Mon Sep 17 00:00:00 2001
+From 2316543bf85035762d9aadf200067fcaf2d0e338 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 19/54] openssh-6.6.1p1-log-in-chroot
+Subject: [PATCH 19/53] openssh-6.6.1p1-log-in-chroot
-# log via monitor in chroots without /dev/log (#2681)
---
log.c | 11 +++++++++--
log.h | 1 +
@@ -58,10 +57,10 @@ index 8e8dfc23f..70048a8af 100644
int log_change_level(LogLevel);
int log_is_on_stderr(void);
diff --git a/monitor.c b/monitor.c
-index 025c6abbe..5255083ab 100644
+index fa477899e..14cdccf2f 100644
--- a/monitor.c
+++ b/monitor.c
-@@ -2030,9 +2030,22 @@ monitor_init(void)
+@@ -2014,9 +2014,22 @@ monitor_init(void)
}
void
@@ -100,7 +99,7 @@ index 75a0d6181..f4721c472 100644
struct Authctxt;
void monitor_child_preauth(struct ssh *, struct monitor *);
diff --git a/session.c b/session.c
-index 517cab6b6..0c26448d9 100644
+index c2cdd6c4f..572487b4a 100644
--- a/session.c
+++ b/session.c
@@ -162,6 +162,7 @@ login_cap_t *lc;
@@ -154,12 +153,12 @@ index 517cab6b6..0c26448d9 100644
do_rc_files(ssh, s, shell);
/* restore SIGPIPE for child */
-@@ -1645,9 +1641,17 @@ do_child(struct ssh *ssh, Session *s, const char *command)
+@@ -1646,9 +1642,17 @@ do_child(struct ssh *ssh, Session *s, const char *command)
#ifdef WITH_SELINUX
ssh_selinux_change_context("sftpd_t");
#endif
-- exit(sftp_server_main(i, argv, s->pw));
-+ exit(sftp_server_main(i, argv, s->pw, have_dev_log));
+- exit(sftp_server_main(sftp_argc, sftp_argv, s->pw));
++ exit(sftp_server_main(sftp_argc, sftp_argv, s->pw, have_dev_log));
}
+ /*
@@ -185,10 +184,10 @@ index 2c70f89bc..bbb79f278 100644
+ return (sftp_server_main(argc, argv, user_pw, 0));
}
diff --git a/sftp-server.c b/sftp-server.c
-index ebdb31d32..7a6e4fa88 100644
+index f87fc5825..eef9a96f8 100644
--- a/sftp-server.c
+++ b/sftp-server.c
-@@ -1893,7 +1893,7 @@ sftp_server_usage(void)
+@@ -1918,7 +1918,7 @@ sftp_server_usage(void)
}
int
@@ -197,7 +196,7 @@ index ebdb31d32..7a6e4fa88 100644
{
int i, r, in, out, ch, skipargs = 0, log_stderr = 0;
ssize_t len, olen;
-@@ -1905,7 +1905,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
+@@ -1930,7 +1930,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
extern char *__progname;
__progname = ssh_get_progname(argv[0]);
@@ -206,7 +205,7 @@ index ebdb31d32..7a6e4fa88 100644
pw = pwcopy(user_pw);
-@@ -1978,7 +1978,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
+@@ -2003,7 +2003,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
}
}
@@ -227,7 +226,7 @@ index 2bde8bb7f..ddf1a3968 100644
+int sftp_server_main(int, char **, struct passwd *, int);
void sftp_server_cleanup_exit(int) __attribute__((noreturn));
diff --git a/sshd-session.c b/sshd-session.c
-index 9d75a4555..23b80eb01 100644
+index 8a3121d01..8a06616fd 100644
--- a/sshd-session.c
+++ b/sshd-session.c
@@ -387,7 +387,7 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt)
@@ -252,5 +251,5 @@ index 9d75a4555..23b80eb01 100644
/* Demote the private keys to public keys. */
demote_sensitive_data();
--
-2.53.0
+2.55.0
diff --git a/0020-openssh-6.6.1p1-scp-non-existing-directory.patch b/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
index 6e266f2..32d4b60 100644
--- a/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
+++ b/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
@@ -1,7 +1,7 @@
-From 276729dc37d204e3da80455788deeac854c72b46 Mon Sep 17 00:00:00 2001
+From cf8d9d0f602ac23fa35e4f8b638a0494564d7dc4 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 20/54] openssh-6.6.1p1-scp-non-existing-directory
+Subject: [PATCH 20/53] openssh-6.6.1p1-scp-non-existing-directory
# scp file into non-existing directory (#1142223)
---
@@ -9,7 +9,7 @@ Subject: [PATCH 20/54] openssh-6.6.1p1-scp-non-existing-directory
1 file changed, 4 insertions(+)
diff --git a/scp.c b/scp.c
-index d87d939a1..b5ad45c12 100644
+index 4821514ba..fca4393f3 100644
--- a/scp.c
+++ b/scp.c
@@ -1871,6 +1871,10 @@ sink(int argc, char **argv, const char *src)
@@ -24,5 +24,5 @@ index d87d939a1..b5ad45c12 100644
mode |= S_IWUSR;
if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) {
--
-2.53.0
+2.55.0
diff --git a/0021-openssh-6.6p1-GSSAPIEnablek5users.patch b/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
index d62778b..96eeff4 100644
--- a/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
+++ b/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
@@ -1,14 +1,14 @@
-From cf6f839370b1f8a207de08b4b00c28ce6684d182 Mon Sep 17 00:00:00 2001
+From dd1871972173c9f4836c4d8096411981d291ff85 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 21/54] openssh-6.6p1-GSSAPIEnablek5users
+Subject: [PATCH 21/53] openssh-6.6p1-GSSAPIEnablek5users
---
- gss-serv-krb5.c | 3 +--
- servconf.c | 13 ++++++++++++-
- servconf.h | 1 +
- sshd_config | 1 +
- 4 files changed, 15 insertions(+), 3 deletions(-)
+ gss-serv-krb5.c | 3 +--
+ servconf.c | 5 +++++
+ servconf.h | 6 ++++--
+ sshd_config | 1 +
+ 4 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
index 6f5826ce8..723509409 100644
@@ -32,95 +32,57 @@ index 6f5826ce8..723509409 100644
k5login_exists);
}
diff --git a/servconf.c b/servconf.c
-index 751388bc0..8de81fdc8 100644
+index 5de03f3bf..3dbfccea8 100644
--- a/servconf.c
+++ b/servconf.c
-@@ -140,6 +140,7 @@ initialize_server_options(ServerOptions *options)
- options->gss_store_rekey = -1;
- options->gss_kex_algorithms = NULL;
- options->use_kuserok = -1;
-+ options->enable_k5users = -1;
- options->password_authentication = -1;
- options->kbd_interactive_authentication = -1;
- options->permit_empty_passwd = -1;
-@@ -396,6 +397,8 @@ fill_default_server_options(ServerOptions *options)
- #endif
- if (options->use_kuserok == -1)
- options->use_kuserok = 1;
-+ if (options->enable_k5users == -1)
-+ options->enable_k5users = 0;
- if (options->password_authentication == -1)
- options->password_authentication = 1;
- if (options->kbd_interactive_authentication == -1)
-@@ -582,7 +585,7 @@ typedef enum {
- sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
- sPerSourcePenalties, sPerSourcePenaltyExemptList,
- sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
-- sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssStrictAcceptor,
-+ sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
- sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- sAcceptEnv, sSetEnv, sPermitTunnel,
- sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
-@@ -679,6 +682,7 @@ static struct {
- { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
- { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
- { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
-+ { "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
- #else
- { "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -688,6 +692,7 @@ static struct {
- { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
- { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
- { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapienablek5users", sUnsupported, SSHCFG_ALL },
- #endif
- { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -2472,6 +2477,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -2233,6 +2233,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->use_kuserok;
goto parse_flag;
-+ case sGssEnablek5users:
++ case sGSSAPIEnablek5users:
+ intptr = &options->enable_k5users;
+ goto parse_flag;
+
case sMatch:
if (cmdline)
fatal("Match directive not supported as a command-line "
-@@ -3040,6 +3049,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- M_CP_INTOPT(ip_qos_interactive);
- M_CP_INTOPT(ip_qos_bulk);
- M_CP_INTOPT(use_kuserok);
-+ M_CP_INTOPT(enable_k5users);
- M_CP_INTOPT(rekey_limit);
- M_CP_INTOPT(rekey_interval);
- M_CP_INTOPT(log_level);
-@@ -3350,6 +3360,7 @@ dump_config(ServerOptions *o)
+@@ -4247,6 +4251,7 @@ dump_config(ServerOptions *o)
# endif
dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
dump_cfg_fmtint(sKerberosUseKuserok, o->use_kuserok);
-+ dump_cfg_fmtint(sGssEnablek5users, o->enable_k5users);
++ dump_cfg_fmtint(sGSSAPIEnablek5users, o->enable_k5users);
#endif
#ifdef GSSAPI
- dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
diff --git a/servconf.h b/servconf.h
-index 9843f96c5..569100e47 100644
+index 7c003b8c5..c55b64c73 100644
--- a/servconf.h
+++ b/servconf.h
-@@ -153,6 +153,7 @@ typedef struct {
- int kerberos_unique_ccache; /* If true, the acquired ticket will
- * be stored in per-session ccache */
- int use_kuserok;
-+ int enable_k5users;
- int gss_authentication; /* If true, permit GSSAPI authentication */
- int gss_keyex; /* If true, permit GSSAPI key exchange */
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
+@@ -325,7 +325,8 @@ SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SS
+ SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+-SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE)
++SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -334,7 +335,8 @@ SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBA
+ SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL)
++SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL)
+ #endif /* GSSAPI */
+
+ #define SSHD_CONFIG_ENTRIES \
diff --git a/sshd_config b/sshd_config
-index ea5a878e6..33713c886 100644
+index 76279d795..d38d899a6 100644
--- a/sshd_config
+++ b/sshd_config
-@@ -82,6 +82,7 @@ AuthorizedKeysFile .ssh/authorized_keys
+@@ -83,6 +83,7 @@ AuthorizedKeysFile .ssh/authorized_keys
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
@@ -129,5 +91,5 @@ index ea5a878e6..33713c886 100644
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
--
-2.53.0
+2.55.0
diff --git a/0022-openssh-6.8p1-sshdT-output.patch b/0022-openssh-6.8p1-sshdT-output.patch
index bebadf3..acfe3f8 100644
--- a/0022-openssh-6.8p1-sshdT-output.patch
+++ b/0022-openssh-6.8p1-sshdT-output.patch
@@ -1,7 +1,7 @@
-From 49bd8aa161e505e9c59e97246e8981be3fd92f63 Mon Sep 17 00:00:00 2001
+From c5cc166ff9eeba6b346022aa1ee0e4d78a2e1fdd Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 22/54] openssh-6.8p1-sshdT-output
+Subject: [PATCH 22/53] openssh-6.8p1-sshdT-output
# apply upstream patch and make sshd -T more consistent (#1187521)
---
@@ -9,10 +9,10 @@ Subject: [PATCH 22/54] openssh-6.8p1-sshdT-output
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/servconf.c b/servconf.c
-index 8de81fdc8..8045756fb 100644
+index 3dbfccea8..27147a1e7 100644
--- a/servconf.c
+++ b/servconf.c
-@@ -3403,7 +3403,7 @@ dump_config(ServerOptions *o)
+@@ -4294,7 +4294,7 @@ dump_config(ServerOptions *o)
dump_cfg_string(sXAuthLocation, o->xauth_location);
dump_cfg_string(sCiphers, o->ciphers);
dump_cfg_string(sMacs, o->macs);
@@ -22,5 +22,5 @@ index 8de81fdc8..8045756fb 100644
dump_cfg_string(sChrootDirectory, o->chroot_directory);
dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys);
--
-2.53.0
+2.55.0
diff --git a/0023-openssh-6.7p1-sftp-force-permission.patch b/0023-openssh-6.7p1-sftp-force-permission.patch
index 3d7f5d0..049b61e 100644
--- a/0023-openssh-6.7p1-sftp-force-permission.patch
+++ b/0023-openssh-6.7p1-sftp-force-permission.patch
@@ -1,7 +1,7 @@
-From 3ea41ef9bf2da8814a1f579898a839b5150d7b6b Mon Sep 17 00:00:00 2001
+From 71b2ca7bbf39514e96da573758d157a7d52625bc Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 23/54] openssh-6.7p1-sftp-force-permission
+Subject: [PATCH 23/53] openssh-6.7p1-sftp-force-permission
# Add sftp option to force mode of created files (#1191055)
---
@@ -10,7 +10,7 @@ Subject: [PATCH 23/54] openssh-6.7p1-sftp-force-permission
2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/sftp-server.8 b/sftp-server.8
-index 5311bf929..5e6e3aa44 100644
+index d9060ab9a..cd5b035d6 100644
--- a/sftp-server.8
+++ b/sftp-server.8
@@ -38,6 +38,7 @@
@@ -21,7 +21,7 @@ index 5311bf929..5e6e3aa44 100644
.Ek
.Nm
.Fl Q Ar protocol_feature
-@@ -138,6 +139,12 @@ Sets an explicit
+@@ -149,6 +150,12 @@ Sets an explicit
.Xr umask 2
to be applied to newly-created files and directories, instead of the
user's default mask.
@@ -35,7 +35,7 @@ index 5311bf929..5e6e3aa44 100644
.Pp
On some systems,
diff --git a/sftp-server.c b/sftp-server.c
-index 7a6e4fa88..d18c4acb9 100644
+index eef9a96f8..567cf6e9a 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -68,6 +68,10 @@ struct sshbuf *oqueue;
@@ -77,7 +77,7 @@ index 7a6e4fa88..d18c4acb9 100644
if (status != SSH2_FX_OK)
send_status(id, status);
free(name);
-@@ -1886,7 +1897,7 @@ sftp_server_usage(void)
+@@ -1911,7 +1922,7 @@ sftp_server_usage(void)
fprintf(stderr,
"usage: %s [-ehR] [-d start_directory] [-f log_facility] "
"[-l log_level]\n\t[-P denied_requests] "
@@ -86,7 +86,7 @@ index 7a6e4fa88..d18c4acb9 100644
" %s -Q protocol_feature\n",
__progname, __progname);
exit(1);
-@@ -1910,7 +1921,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
+@@ -1935,7 +1946,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
pw = pwcopy(user_pw);
while (!skipargs && (ch = getopt(argc, argv,
@@ -95,7 +95,7 @@ index 7a6e4fa88..d18c4acb9 100644
switch (ch) {
case 'Q':
if (strcasecmp(optarg, "requests") != 0) {
-@@ -1972,6 +1983,15 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
+@@ -1997,6 +2008,15 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
fatal("Invalid umask \"%s\"", optarg);
(void)umask((mode_t)mask);
break;
@@ -112,5 +112,5 @@ index 7a6e4fa88..d18c4acb9 100644
default:
sftp_server_usage();
--
-2.53.0
+2.55.0
diff --git a/0024-openssh-7.2p2-s390-closefrom.patch b/0024-openssh-7.2p2-s390-closefrom.patch
index bd3f4f3..2c95518 100644
--- a/0024-openssh-7.2p2-s390-closefrom.patch
+++ b/0024-openssh-7.2p2-s390-closefrom.patch
@@ -1,7 +1,7 @@
-From 6f9059876d4323de33ca706da0e883d142757f46 Mon Sep 17 00:00:00 2001
+From dd953f0572ef6b53825afb50cc44d65b2047831e Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 24/54] openssh-7.2p2-s390-closefrom
+Subject: [PATCH 24/53] openssh-7.2p2-s390-closefrom
# make s390 use /dev/ crypto devices -- ignore closefrom
---
@@ -47,5 +47,5 @@ index 49a4f35ff..f61124585 100644
(void) closedir(dirp);
return;
--
-2.53.0
+2.55.0
diff --git a/0025-openssh-7.5p1-sandbox.patch b/0025-openssh-7.5p1-sandbox.patch
index b5a24a4..d24685a 100644
--- a/0025-openssh-7.5p1-sandbox.patch
+++ b/0025-openssh-7.5p1-sandbox.patch
@@ -1,7 +1,7 @@
-From 164a1599ea7f147919f6c925ee17c21820b9e7ee Mon Sep 17 00:00:00 2001
+From 4688280417e4ea28be7683b554f91e31f234136b Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 25/54] openssh-7.5p1-sandbox
+Subject: [PATCH 25/53] openssh-7.5p1-sandbox
# Sandbox adjustments for s390 and audit
---
@@ -9,7 +9,7 @@ Subject: [PATCH 25/54] openssh-7.5p1-sandbox
1 file changed, 21 insertions(+)
diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
-index 7b2444930..e21048f3a 100644
+index bf753eef2..b65413c95 100644
--- a/sandbox-seccomp-filter.c
+++ b/sandbox-seccomp-filter.c
@@ -305,6 +305,9 @@ static const struct sock_filter preauth_insns[] = {
@@ -55,5 +55,5 @@ index 7b2444930..e21048f3a 100644
SC_ALLOW(__NR_getuid),
#endif
--
-2.53.0
+2.55.0
diff --git a/0026-openssh-7.8p1-scp-ipv6.patch b/0026-openssh-7.8p1-scp-ipv6.patch
index 71c7386..7faacb0 100644
--- a/0026-openssh-7.8p1-scp-ipv6.patch
+++ b/0026-openssh-7.8p1-scp-ipv6.patch
@@ -1,7 +1,7 @@
-From 3e86feb824f2f1a7efe4b6b7b35c145bef1d0dd4 Mon Sep 17 00:00:00 2001
+From 46c63b52afa8136a46a9094a69b4c011f22b44ac Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 26/54] openssh-7.8p1-scp-ipv6
+Subject: [PATCH 26/53] openssh-7.8p1-scp-ipv6
# Unbreak scp between two IPv6 hosts (#1620333)
---
@@ -9,7 +9,7 @@ Subject: [PATCH 26/54] openssh-7.8p1-scp-ipv6
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scp.c b/scp.c
-index b5ad45c12..5618e28e7 100644
+index fca4393f3..ac43ec5f4 100644
--- a/scp.c
+++ b/scp.c
@@ -1164,7 +1164,9 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
@@ -24,5 +24,5 @@ index b5ad45c12..5618e28e7 100644
thost, targ);
if (do_local_cmd(&alist) != 0)
--
-2.53.0
+2.55.0
diff --git a/0027-openssh-8.0p1-crypto-policies.patch b/0027-openssh-8.0p1-crypto-policies.patch
index 8a1b7d4..0145163 100644
--- a/0027-openssh-8.0p1-crypto-policies.patch
+++ b/0027-openssh-8.0p1-crypto-policies.patch
@@ -1,18 +1,18 @@
-From 0914766aed901780fb744b4ba06e500ad03cf428 Mon Sep 17 00:00:00 2001
+From 7a5ff8f728cfb98c90dcbe648723fb4d4ed02300 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 27/54] openssh-8.0p1-crypto-policies
+Subject: [PATCH 27/53] openssh-8.0p1-crypto-policies
---
- ssh_config.5 | 168 +++++++++++++++++++--------------------------
- sshd_config.5 | 186 ++++++++++++++++++--------------------------------
- 2 files changed, 140 insertions(+), 214 deletions(-)
+ ssh_config.5 | 172 +++++++++++++++++++-------------------------
+ sshd_config.5 | 193 ++++++++++++++++++--------------------------------
+ 2 files changed, 139 insertions(+), 226 deletions(-)
diff --git a/ssh_config.5 b/ssh_config.5
-index 9e5163774..306fa1d8a 100644
+index 04797a1e9..c4c72f587 100644
--- a/ssh_config.5
+++ b/ssh_config.5
-@@ -438,17 +438,13 @@ A single argument of
+@@ -468,18 +468,13 @@ A single argument of
causes no CNAMEs to be considered for canonicalization.
This is the default behaviour.
.It Cm CASignatureAlgorithms
@@ -29,13 +29,14 @@ index 9e5163774..306fa1d8a 100644
-ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
-.Pp
If the specified list begins with a
.Sq +
character, then the specified algorithms will be appended to the default set
-@@ -587,20 +583,25 @@ If the option is set to
+@@ -618,20 +613,25 @@ If the option is set to
(the default),
the check will not be executed.
.It Cm Ciphers
@@ -65,7 +66,7 @@ index 9e5163774..306fa1d8a 100644
.Pp
The supported ciphers are:
.Bd -literal -offset indent
-@@ -616,13 +617,6 @@ aes256-gcm@openssh.com
+@@ -647,13 +647,6 @@ aes256-gcm@openssh.com
chacha20-poly1305@openssh.com
.Ed
.Pp
@@ -79,7 +80,7 @@ index 9e5163774..306fa1d8a 100644
The list of available ciphers may also be obtained using
.Qq ssh -Q cipher .
.It Cm ClearAllForwardings
-@@ -1022,6 +1016,11 @@ command line will be passed untouched to the GSSAPI library.
+@@ -1060,6 +1053,11 @@ command line will be passed untouched to the GSSAPI library.
The default is
.Dq no .
.It Cm GSSAPIKexAlgorithms
@@ -91,7 +92,7 @@ index 9e5163774..306fa1d8a 100644
The list of key exchange algorithms that are offered for GSSAPI
key exchange. Possible values are
.Bd -literal -offset 3n
-@@ -1034,9 +1033,6 @@ gss-nistp256-sha256-,
+@@ -1072,9 +1070,6 @@ gss-nistp256-sha256-,
gss-curve25519-sha256-
.Ed
.Pp
@@ -101,7 +102,7 @@ index 9e5163774..306fa1d8a 100644
This option only applies to connections using GSSAPI.
.Pp
The list of supported key exchange algorithms may also be obtained using
-@@ -1056,38 +1053,25 @@ will not be converted automatically,
+@@ -1097,40 +1092,25 @@ will not be converted automatically,
but may be manually hashed using
.Xr ssh-keygen 1 .
.It Cm HostbasedAcceptedAlgorithms
@@ -137,18 +138,20 @@ index 9e5163774..306fa1d8a 100644
-webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
-rsa-sha2-512-cert-v01@openssh.com,
-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-mldsa44-ed25519-cert-v01@openssh.com,
-ssh-ed25519,
-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
-webauthn-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
+at the head of the built-in openssh default set.
.Pp
The
.Fl Q
-@@ -1142,6 +1126,17 @@ to prefer their algorithms.
+@@ -1187,6 +1167,17 @@ to prefer their algorithms.
.Pp
The list of available signature algorithms may also be obtained using
.Qq ssh -Q HostKeyAlgorithms .
@@ -166,7 +169,7 @@ index 9e5163774..306fa1d8a 100644
.It Cm HostKeyAlias
Specifies an alias that should be used instead of the
real host name when looking up or saving the host key
-@@ -1364,6 +1359,11 @@ it may be zero or more of:
+@@ -1385,6 +1376,11 @@ it may be zero or more of:
and
.Cm pam .
.It Cm KexAlgorithms
@@ -178,7 +181,7 @@ index 9e5163774..306fa1d8a 100644
Specifies the permitted KEX (Key Exchange) algorithms that will be used and
their preference order.
The selected algorithm will be the first algorithm in this list that
-@@ -1375,28 +1374,16 @@ Multiple algorithms must be comma-separated.
+@@ -1393,28 +1389,16 @@ Multiple algorithms must be comma-separated.
.Pp
If the specified list begins with a
.Sq +
@@ -211,7 +214,7 @@ index 9e5163774..306fa1d8a 100644
.Pp
The list of supported key exchange algorithms may also be obtained using
.Qq ssh -Q kex .
-@@ -1510,37 +1498,33 @@ function, and all code in the
+@@ -1531,37 +1515,33 @@ function, and all code in the
file.
This option is intended for debugging and no overrides are enabled by default.
.It Cm MACs
@@ -258,7 +261,7 @@ index 9e5163774..306fa1d8a 100644
The list of available MAC algorithms may also be obtained using
.Qq ssh -Q mac .
.It Cm NoHostAuthenticationForLocalhost
-@@ -1729,41 +1713,31 @@ instead of continuing to execute and pass data.
+@@ -1750,43 +1730,31 @@ instead of continuing to execute and pass data.
The default is
.Cm no .
.It Cm PubkeyAcceptedAlgorithms
@@ -295,12 +298,14 @@ index 9e5163774..306fa1d8a 100644
-webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
-rsa-sha2-512-cert-v01@openssh.com,
-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-mldsa44-ed25519-cert-v01@openssh.com,
-ssh-ed25519,
-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
-webauthn-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
+built-in openssh default set.
.Pp
@@ -312,7 +317,7 @@ index 9e5163774..306fa1d8a 100644
.It Cm PubkeyAuthentication
Specifies whether to try public key authentication.
The argument to this keyword must be
-@@ -2517,7 +2491,9 @@ for those users who do not have a configuration file.
+@@ -2540,7 +2508,9 @@ for those users who do not have a configuration file.
This file must be world-readable.
.El
.Sh SEE ALSO
@@ -324,10 +329,10 @@ index 9e5163774..306fa1d8a 100644
.An -nosplit
OpenSSH is a derivative of the original and free
diff --git a/sshd_config.5 b/sshd_config.5
-index 1762874c4..1d16dabaf 100644
+index 0077ee096..b72757895 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -383,17 +383,13 @@ If the argument is
+@@ -383,18 +383,13 @@ If the argument is
then no banner is displayed.
By default, no banner is displayed.
.It Cm CASignatureAlgorithms
@@ -344,13 +349,14 @@ index 1762874c4..1d16dabaf 100644
-ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
-.Pp
If the specified list begins with a
.Sq +
character, then the specified algorithms will be appended to the default set
-@@ -537,20 +533,25 @@ The default is
+@@ -538,20 +533,25 @@ The default is
indicating not to
.Xr chroot 2 .
.It Cm Ciphers
@@ -380,7 +386,7 @@ index 1762874c4..1d16dabaf 100644
.Pp
The supported ciphers are:
.Pp
-@@ -577,13 +578,6 @@ aes256-gcm@openssh.com
+@@ -578,13 +578,6 @@ aes256-gcm@openssh.com
chacha20-poly1305@openssh.com
.El
.Pp
@@ -394,7 +400,7 @@ index 1762874c4..1d16dabaf 100644
The list of available ciphers may also be obtained using
.Qq ssh -Q cipher .
.It Cm ClientAliveCountMax
-@@ -789,58 +783,46 @@ For this to work
+@@ -798,60 +791,46 @@ For this to work
.Cm GSSAPIKeyExchange
needs to be enabled in the server and also used by the client.
.It Cm GSSAPIKexAlgorithms
@@ -461,18 +467,20 @@ index 1762874c4..1d16dabaf 100644
-webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
-rsa-sha2-512-cert-v01@openssh.com,
-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-mldsa44-ed25519-cert-v01@openssh.com,
-ssh-ed25519,
-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
-webauthn-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
+the head of the built-in openssh default set.
.Pp
The list of available signature algorithms may also be obtained using
.Qq ssh -Q HostbasedAcceptedAlgorithms .
-@@ -904,27 +886,13 @@ is specified, the location of the socket will be read from the
+@@ -919,29 +898,13 @@ is specified, the location of the socket will be read from the
.Ev SSH_AUTH_SOCK
environment variable.
.It Cm HostKeyAlgorithms
@@ -494,18 +502,20 @@ index 1762874c4..1d16dabaf 100644
-webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
-rsa-sha2-512-cert-v01@openssh.com,
-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-mldsa44-ed25519-cert-v01@openssh.com,
-ssh-ed25519,
-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
-webauthn-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
-.Pp
The list of available signature algorithms may also be obtained using
.Qq ssh -Q HostKeyAlgorithms .
.It Cm IgnoreRhosts
-@@ -1070,6 +1038,11 @@ Specifies whether to look at .k5login file for user's aliases.
+@@ -1087,6 +1050,11 @@ Specifies whether to look at .k5login file for user's aliases.
The default is
.Cm yes .
.It Cm KexAlgorithms
@@ -517,7 +527,7 @@ index 1762874c4..1d16dabaf 100644
Specifies the permitted KEX (Key Exchange) algorithms that the server will
offer to clients.
The ordering of this list is not important, as the client specifies the
-@@ -1078,16 +1051,16 @@ Multiple algorithms must be comma-separated.
+@@ -1095,16 +1063,16 @@ Multiple algorithms must be comma-separated.
.Pp
If the specified list begins with a
.Sq +
@@ -538,7 +548,7 @@ index 1762874c4..1d16dabaf 100644
.Pp
The supported algorithms are:
.Pp
-@@ -1124,14 +1097,6 @@ sntrup761x25519-sha512
+@@ -1143,14 +1111,6 @@ sntrup761x25519-sha512
sntrup761x25519-sha512@openssh.com
.El
.Pp
@@ -553,7 +563,7 @@ index 1762874c4..1d16dabaf 100644
The list of supported key exchange algorithms may also be obtained using
.Qq ssh -Q KexAlgorithms .
.It Cm ListenAddress
-@@ -1218,21 +1183,26 @@ function, and all code in the
+@@ -1237,21 +1197,26 @@ function, and all code in the
file.
This option is intended for debugging and no overrides are enabled by default.
.It Cm MACs
@@ -584,7 +594,7 @@ index 1762874c4..1d16dabaf 100644
.Pp
The algorithms that contain
.Qq -etm
-@@ -1275,15 +1245,6 @@ umac-64-etm@openssh.com
+@@ -1294,15 +1259,6 @@ umac-64-etm@openssh.com
umac-128-etm@openssh.com
.El
.Pp
@@ -600,7 +610,7 @@ index 1762874c4..1d16dabaf 100644
The list of available MAC algorithms may also be obtained using
.Qq ssh -Q mac .
.It Cm Match
-@@ -1773,38 +1734,25 @@ or equivalent.)
+@@ -1792,40 +1748,25 @@ or equivalent.)
The default is
.Cm yes .
.It Cm PubkeyAcceptedAlgorithms
@@ -637,18 +647,20 @@ index 1762874c4..1d16dabaf 100644
-webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
-rsa-sha2-512-cert-v01@openssh.com,
-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-mldsa44-ed25519-cert-v01@openssh.com,
-ssh-ed25519,
-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
-sk-ssh-ed25519@openssh.com,
-sk-ecdsa-sha2-nistp256@openssh.com,
-webauthn-sk-ecdsa-sha2-nistp256@openssh.com,
--rsa-sha2-512,rsa-sha2-256
+-rsa-sha2-512,rsa-sha2-256,
+-ssh-mldsa44-ed25519@openssh.com
-.Ed
+built-in openssh default set.
.Pp
The list of available signature algorithms may also be obtained using
.Qq ssh -Q PubkeyAcceptedAlgorithms .
-@@ -2317,7 +2265,9 @@ This file should be writable by root only, but it is recommended
+@@ -2338,7 +2279,9 @@ This file should be writable by root only, but it is recommended
.El
.Sh SEE ALSO
.Xr sftp-server 8 ,
@@ -660,5 +672,5 @@ index 1762874c4..1d16dabaf 100644
.An -nosplit
OpenSSH is a derivative of the original and free
--
-2.53.0
+2.55.0
diff --git a/0028-openssh-8.0p1-openssl-kdf.patch b/0028-openssh-8.0p1-openssl-kdf.patch
index 053daad..9c3d58c 100644
--- a/0028-openssh-8.0p1-openssl-kdf.patch
+++ b/0028-openssh-8.0p1-openssl-kdf.patch
@@ -1,7 +1,7 @@
-From 1a2e4068f997c1a9ed4179671162201f766f20b8 Mon Sep 17 00:00:00 2001
+From 1fdd8f9ddb1b2e1e0d4f7d97e5e7e17b74a85a6a Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 28/54] openssh-8.0p1-openssl-kdf
+Subject: [PATCH 28/53] openssh-8.0p1-openssl-kdf
# Use OpenSSL KDF (#1631761)
---
@@ -10,10 +10,10 @@ Subject: [PATCH 28/54] openssh-8.0p1-openssl-kdf
2 files changed, 108 insertions(+)
diff --git a/configure.ac b/configure.ac
-index 75576b45d..86f0e9e09 100644
+index cb46903f4..394acc3d8 100644
--- a/configure.ac
+++ b/configure.ac
-@@ -3248,6 +3248,7 @@ if test "x$openssl" = "xyes" ; then
+@@ -3261,6 +3261,7 @@ if test "x$openssl" = "xyes" ; then
HMAC_CTX_init \
RSA_generate_key_ex \
RSA_get_default_method \
@@ -22,7 +22,7 @@ index 75576b45d..86f0e9e09 100644
# LibreSSL/OpenSSL API differences
diff --git a/kex.c b/kex.c
-index 3608a5504..54e711810 100644
+index 1b8f1c3c5..6981b59f5 100644
--- a/kex.c
+++ b/kex.c
@@ -37,6 +37,11 @@
@@ -37,7 +37,7 @@ index 3608a5504..54e711810 100644
#endif
#include "ssh.h"
-@@ -1083,6 +1088,107 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1086,6 +1091,107 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
return r;
}
@@ -145,7 +145,7 @@ index 3608a5504..54e711810 100644
static int
derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
const struct sshbuf *shared_secret, u_char **keyp)
-@@ -1146,6 +1252,7 @@ derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
+@@ -1149,6 +1255,7 @@ derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
ssh_digest_free(hashctx);
return r;
}
@@ -154,5 +154,5 @@ index 3608a5504..54e711810 100644
#define NKEYS 6
int
--
-2.53.0
+2.55.0
diff --git a/0029-openssh-8.2p1-visibility.patch b/0029-openssh-8.2p1-visibility.patch
index 3461efa..4008c0c 100644
--- a/0029-openssh-8.2p1-visibility.patch
+++ b/0029-openssh-8.2p1-visibility.patch
@@ -1,7 +1,7 @@
-From 03f96498c3b1c5a01014a47a800f94430c7c92ce Mon Sep 17 00:00:00 2001
+From 0c8380243aa47419186f12070b4115846bf9b425 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 29/54] openssh-8.2p1-visibility
+Subject: [PATCH 29/53] openssh-8.2p1-visibility
# sk-dummy.so built with -fvisibility=hidden does not work
---
@@ -9,7 +9,7 @@ Subject: [PATCH 29/54] openssh-8.2p1-visibility
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/regress/misc/sk-dummy/sk-dummy.c b/regress/misc/sk-dummy/sk-dummy.c
-index 4c96e8827..4af5209db 100644
+index cbbba7459..f1b3f060b 100644
--- a/regress/misc/sk-dummy/sk-dummy.c
+++ b/regress/misc/sk-dummy/sk-dummy.c
@@ -80,7 +80,7 @@ skdebug(const char *func, const char *fmt, ...)
@@ -49,5 +49,5 @@ index 4c96e8827..4af5209db 100644
struct sk_resident_key ***rks, size_t *nrks)
{
--
-2.53.0
+2.55.0
diff --git a/0030-openssh-8.2p1-x11-without-ipv6.patch b/0030-openssh-8.2p1-x11-without-ipv6.patch
index 8760132..373e889 100644
--- a/0030-openssh-8.2p1-x11-without-ipv6.patch
+++ b/0030-openssh-8.2p1-x11-without-ipv6.patch
@@ -1,7 +1,7 @@
-From efca1f54d8afc867f3989b5ed790054de942cb0c Mon Sep 17 00:00:00 2001
+From a29df25b1ed0d2945d0990bc7ca8918c16c7d264 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 30/54] openssh-8.2p1-x11-without-ipv6
+Subject: [PATCH 30/53] openssh-8.2p1-x11-without-ipv6
# Do not break X11 without IPv6
---
@@ -9,10 +9,10 @@ Subject: [PATCH 30/54] openssh-8.2p1-x11-without-ipv6
1 file changed, 10 insertions(+)
diff --git a/channels.c b/channels.c
-index 71cc67d17..14d536d64 100644
+index fcd80288f..bae84e5f9 100644
--- a/channels.c
+++ b/channels.c
-@@ -5132,6 +5132,16 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+@@ -5141,6 +5141,16 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
debug2_f("bind port %d: %.100s", port,
strerror(errno));
close(sock);
@@ -30,5 +30,5 @@ index 71cc67d17..14d536d64 100644
close(socks[n]);
num_socks = 0;
--
-2.53.0
+2.55.0
diff --git a/0031-openssh-8.0p1-preserve-pam-errors.patch b/0031-openssh-8.0p1-preserve-pam-errors.patch
index 2894ebc..ad8382c 100644
--- a/0031-openssh-8.0p1-preserve-pam-errors.patch
+++ b/0031-openssh-8.0p1-preserve-pam-errors.patch
@@ -1,7 +1,7 @@
-From 8203803a93ad6b807a94324156ef866b45cc8eae Mon Sep 17 00:00:00 2001
+From 74d160a951c390e2c690bf3462b643b7328e3b89 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 31/54] openssh-8.0p1-preserve-pam-errors
+Subject: [PATCH 31/53] openssh-8.0p1-preserve-pam-errors
# sshd provides PAM an incorrect error code (#1879503)
---
@@ -53,5 +53,5 @@ index 3f342f9e1..d3dcd037b 100644
sshpam_set_maxtries_reached(1);
/* FALLTHROUGH */
--
-2.53.0
+2.55.0
diff --git a/0032-openssh-8.7p1-scp-kill-switch.patch b/0032-openssh-8.7p1-scp-kill-switch.patch
index 947a6b9..6ba3100 100644
--- a/0032-openssh-8.7p1-scp-kill-switch.patch
+++ b/0032-openssh-8.7p1-scp-kill-switch.patch
@@ -1,7 +1,7 @@
-From 61acc5e94ea087db20504777e74bdb5591762056 Mon Sep 17 00:00:00 2001
+From ee385dfe54142b1f8c03f20b8a63b9f8f1b52b3f Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 32/54] openssh-8.7p1-scp-kill-switch
+Subject: [PATCH 32/53] openssh-8.7p1-scp-kill-switch
# Implement kill switch for SCP protocol
---
@@ -11,12 +11,12 @@ Subject: [PATCH 32/54] openssh-8.7p1-scp-kill-switch
3 files changed, 16 insertions(+)
diff --git a/pathnames.h b/pathnames.h
-index ae01c69e2..80db76e00 100644
+index 2f0b9f4f6..ce5ddc14f 100644
--- a/pathnames.h
+++ b/pathnames.h
-@@ -40,6 +40,7 @@
- #define _PATH_HOST_RSA_KEY_FILE SSHDIR "/ssh_host_rsa_key"
+@@ -41,6 +41,7 @@
#define _PATH_HOST_ED25519_KEY_FILE SSHDIR "/ssh_host_ed25519_key"
+ #define _PATH_HOST_MLDSA44_ED25519_KEY_FILE SSHDIR "/ssh_host_mldsa44_ed25519_key"
#define _PATH_DH_MODULI SSHDIR "/moduli"
+#define _PATH_SCP_KILL_SWITCH SSHDIR "/disable_scp"
@@ -41,7 +41,7 @@ index 7bce0fe6f..2bb838c7d 100644
.Ex -std scp
.Sh SEE ALSO
diff --git a/scp.c b/scp.c
-index 5618e28e7..76dcb6040 100644
+index ac43ec5f4..5775be457 100644
--- a/scp.c
+++ b/scp.c
@@ -628,6 +628,14 @@ main(int argc, char **argv)
@@ -60,5 +60,5 @@ index 5618e28e7..76dcb6040 100644
fatal("unknown user %u", (u_int) userid);
--
-2.53.0
+2.55.0
diff --git a/0033-openssh-8.7p1-recursive-scp.patch b/0033-openssh-8.7p1-recursive-scp.patch
index 33f1067..83ac8d3 100644
--- a/0033-openssh-8.7p1-recursive-scp.patch
+++ b/0033-openssh-8.7p1-recursive-scp.patch
@@ -1,7 +1,7 @@
-From 72223f519294c234e7313dc0efb86c1164a67b47 Mon Sep 17 00:00:00 2001
+From f21a7b168a25eca8ef2928eb546642974d2d2a31 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 33/54] openssh-8.7p1-recursive-scp
+Subject: [PATCH 33/53] openssh-8.7p1-recursive-scp
# Workaround for lack of sftp_realpath in older versions of RHEL
# https://bugzilla.redhat.com/show_bug.cgi?id=2038854
@@ -15,7 +15,7 @@ Subject: [PATCH 33/54] openssh-8.7p1-recursive-scp
4 files changed, 52 insertions(+), 20 deletions(-)
diff --git a/scp.c b/scp.c
-index 76dcb6040..973848e04 100644
+index 5775be457..646cf2478 100644
--- a/scp.c
+++ b/scp.c
@@ -1377,7 +1377,7 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
@@ -28,7 +28,7 @@ index 76dcb6040..973848e04 100644
errs = 1;
}
diff --git a/sftp-client.c b/sftp-client.c
-index 69ef28cdc..3012eac12 100644
+index 1f031128e..aad864357 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -988,7 +988,7 @@ sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
@@ -113,7 +113,7 @@ index 69ef28cdc..3012eac12 100644
}
int
-@@ -2001,7 +2033,7 @@ sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -2006,7 +2038,7 @@ sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
char *src_canon;
int ret;
@@ -122,7 +122,7 @@ index 69ef28cdc..3012eac12 100644
error("download \"%s\": path canonicalization failed", src);
return -1;
}
-@@ -2356,12 +2388,12 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -2361,12 +2393,12 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
int
sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
int preserve_flag, int print_flag, int resume, int fsync_flag,
@@ -137,7 +137,7 @@ index 69ef28cdc..3012eac12 100644
error("upload \"%s\": path canonicalization failed", dst);
return -1;
}
-@@ -2819,7 +2851,7 @@ sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
+@@ -2829,7 +2861,7 @@ sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
char *from_path_canon;
int ret;
@@ -169,10 +169,10 @@ index cc8e20298..36b68d4cc 100644
/*
* Download a 'from_path' from the 'from' connection and upload it to
diff --git a/sftp.c b/sftp.c
-index eebb166e8..4a71dd05d 100644
+index 31d8fa494..d3be72ffe 100644
--- a/sftp.c
+++ b/sftp.c
-@@ -805,7 +805,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -804,7 +804,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
(rflag || global_rflag)) {
if (sftp_upload_dir(conn, g.gl_pathv[i], abs_dst,
pflag || global_pflag, 1, resume,
@@ -181,7 +181,7 @@ index eebb166e8..4a71dd05d 100644
err = -1;
} else {
if (sftp_upload(conn, g.gl_pathv[i], abs_dst,
-@@ -1640,7 +1640,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
+@@ -1649,7 +1649,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
if (path1 == NULL || *path1 == '\0')
path1 = xstrdup(startdir);
path1 = sftp_make_absolute(path1, *pwd);
@@ -190,7 +190,7 @@ index eebb166e8..4a71dd05d 100644
err = 1;
break;
}
-@@ -2266,7 +2266,7 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
+@@ -2275,7 +2275,7 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
}
#endif /* USE_LIBEDIT */
@@ -200,5 +200,5 @@ index eebb166e8..4a71dd05d 100644
startdir = xstrdup(remote_path);
--
-2.53.0
+2.55.0
diff --git a/0034-openssh-8.7p1-ibmca.patch b/0034-openssh-8.7p1-ibmca.patch
index 553297a..8ca4ccb 100644
--- a/0034-openssh-8.7p1-ibmca.patch
+++ b/0034-openssh-8.7p1-ibmca.patch
@@ -1,7 +1,7 @@
-From 27e73c9d6c4a44bdfb98852ddeb30e902ff8658e Mon Sep 17 00:00:00 2001
+From 5330624d76a24557fb71d8852c6e5e1c8edce762 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 34/54] openssh-8.7p1-ibmca
+Subject: [PATCH 34/53] openssh-8.7p1-ibmca
# downstream only, IBMCA tentative fix
# From https://bugzilla.redhat.com/show_bug.cgi?id=1976202#c14
@@ -23,5 +23,5 @@ index f61124585..417c20484 100644
#include <sys/types.h>
#include <unistd.h>
--
-2.53.0
+2.55.0
diff --git a/0035-openssh-7.6p1-audit.patch b/0035-openssh-7.6p1-audit.patch
index 40df06c..51d7285 100644
--- a/0035-openssh-7.6p1-audit.patch
+++ b/0035-openssh-7.6p1-audit.patch
@@ -1,7 +1,7 @@
-From 00ef565336c933f9464b09c55134fa3b3c76ad40 Mon Sep 17 00:00:00 2001
+From 6fefa768f29953c62f81f6e4e8e70e32f6c48dd9 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 35/54] openssh-7.6p1-audit
+Subject: [PATCH 35/53] openssh-7.6p1-audit
---
Makefile.in | 2 +-
@@ -35,10 +35,10 @@ Subject: [PATCH 35/54] openssh-7.6p1-audit
create mode 100644 auditstub.c
diff --git a/Makefile.in b/Makefile.in
-index a12f6ad1d..f80c95846 100644
+index 1eeb34cad..f61f89e4d 100644
--- a/Makefile.in
+++ b/Makefile.in
-@@ -110,7 +110,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+@@ -111,7 +111,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
kexgssc.o \
sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
@@ -782,10 +782,10 @@ index 000000000..639a798df
+{
+}
diff --git a/auth.c b/auth.c
-index 142ac5afd..1858bddab 100644
+index 9d1307b76..9851625cf 100644
--- a/auth.c
+++ b/auth.c
-@@ -499,9 +499,6 @@ getpwnamallow(struct ssh *ssh, const char *user)
+@@ -502,9 +502,6 @@ getpwnamallow(struct ssh *ssh, const char *user)
record_failed_login(ssh, user,
auth_get_canonical_hostname(ssh, options.use_dns), "ssh");
#endif
@@ -796,10 +796,10 @@ index 142ac5afd..1858bddab 100644
}
if (!allowed_user(ssh, pw))
diff --git a/auth.h b/auth.h
-index 815f821f9..b72b76163 100644
+index 9dd25f494..066f5effa 100644
--- a/auth.h
+++ b/auth.h
-@@ -213,6 +213,8 @@ struct sshkey *get_hostkey_private_by_type(int, int, struct ssh *);
+@@ -214,6 +214,8 @@ struct sshkey *get_hostkey_private_by_type(int, int, struct ssh *);
int get_hostkey_index(struct sshkey *, int, struct ssh *);
int sshd_hostkey_sign(struct ssh *, struct sshkey *, struct sshkey *,
u_char **, size_t *, const u_char *, size_t, const char *);
@@ -808,7 +808,7 @@ index 815f821f9..b72b76163 100644
/* Key / cert options linkage to auth layer */
int auth_activate_options(struct ssh *, struct sshauthopt *);
-@@ -238,6 +240,8 @@ int auth_check_authkey_line(struct passwd *, struct sshkey *,
+@@ -239,6 +241,8 @@ int auth_check_authkey_line(struct passwd *, struct sshkey *,
char *, const char *, const char *, const char *, struct sshauthopt **);
int auth_check_authkeys_file(struct passwd *, FILE *, char *,
struct sshkey *, const char *, const char *, struct sshauthopt **);
@@ -818,10 +818,10 @@ index 815f821f9..b72b76163 100644
FILE *auth_openprincipals(const char *, struct passwd *, int);
diff --git a/auth2-hostbased.c b/auth2-hostbased.c
-index a287091ec..bc4775042 100644
+index 6a6704d93..d877471da 100644
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
-@@ -158,7 +158,7 @@ userauth_hostbased(struct ssh *ssh, const char *method)
+@@ -159,7 +159,7 @@ userauth_hostbased(struct ssh *ssh, const char *method)
authenticated = 0;
if (mm_hostbased_key_allowed(ssh, authctxt->pw, cuser,
chost, key) &&
@@ -830,7 +830,7 @@ index a287091ec..bc4775042 100644
sshbuf_ptr(b), sshbuf_len(b), pkalg, ssh->compat, NULL) == 0)
authenticated = 1;
-@@ -175,6 +175,20 @@ done:
+@@ -176,6 +176,20 @@ done:
return authenticated;
}
@@ -852,7 +852,7 @@ index a287091ec..bc4775042 100644
int
hostbased_key_allowed(struct ssh *ssh, struct passwd *pw,
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
-index 319e42b2b..973810dc9 100644
+index dc07faa92..f2a4329e5 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -230,7 +230,7 @@ userauth_pubkey(struct ssh *ssh, const char *method)
@@ -886,10 +886,10 @@ index 319e42b2b..973810dc9 100644
match_principals_file(struct passwd *pw, char *file,
struct sshkey_cert *cert, struct sshauthopt **authoptsp)
diff --git a/auth2.c b/auth2.c
-index 3c3bdb776..790bb3454 100644
+index f0f39707f..71460d78f 100644
--- a/auth2.c
+++ b/auth2.c
-@@ -315,9 +315,6 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+@@ -321,9 +321,6 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
authctxt->valid = 0;
/* Invalid user, fake password information */
authctxt->pw = fakepw();
@@ -900,7 +900,7 @@ index 3c3bdb776..790bb3454 100644
#ifdef USE_PAM
if (options.use_pam)
diff --git a/cipher.c b/cipher.c
-index f770e666c..b05755d73 100644
+index 0697e559b..82076d4bd 100644
--- a/cipher.c
+++ b/cipher.c
@@ -63,25 +63,6 @@ struct sshcipher_ctx {
@@ -929,7 +929,7 @@ index f770e666c..b05755d73 100644
static const struct sshcipher ciphers[] = {
#ifdef WITH_OPENSSL
#ifndef OPENSSL_NO_DES
-@@ -410,7 +391,7 @@ cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
+@@ -418,7 +399,7 @@ cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
void
cipher_free(struct sshcipher_ctx *cc)
{
@@ -939,7 +939,7 @@ index f770e666c..b05755d73 100644
if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
chachapoly_free(cc->cp_ctx);
diff --git a/cipher.h b/cipher.h
-index 6533ff2bb..2e05a0210 100644
+index 32abdde22..e953dfc3c 100644
--- a/cipher.h
+++ b/cipher.h
@@ -47,7 +47,25 @@
@@ -970,7 +970,7 @@ index 6533ff2bb..2e05a0210 100644
const struct sshcipher *cipher_by_name(const char *);
diff --git a/kex.c b/kex.c
-index 54e711810..70bebce6a 100644
+index 6981b59f5..9ce2b7e12 100644
--- a/kex.c
+++ b/kex.c
@@ -64,6 +64,7 @@
@@ -981,7 +981,7 @@ index 54e711810..70bebce6a 100644
/* prototype */
static int kex_choose_conf(struct ssh *, uint32_t seq);
-@@ -826,12 +827,16 @@ kex_start_rekex(struct ssh *ssh)
+@@ -829,12 +830,16 @@ kex_start_rekex(struct ssh *ssh)
}
static int
@@ -1000,7 +1000,7 @@ index 54e711810..70bebce6a 100644
if ((enc->cipher = cipher_by_name(name)) == NULL) {
error_f("unsupported cipher %s", name);
free(name);
-@@ -852,8 +857,12 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
+@@ -855,8 +860,12 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
{
char *name = match_list(client, server, NULL);
@@ -1014,7 +1014,7 @@ index 54e711810..70bebce6a 100644
if (mac_setup(mac, name) < 0) {
error_f("unsupported MAC %s", name);
free(name);
-@@ -866,12 +875,16 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
+@@ -869,12 +878,16 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
}
static int
@@ -1033,7 +1033,7 @@ index 54e711810..70bebce6a 100644
#ifdef WITH_ZLIB
if (strcmp(name, "zlib@openssh.com") == 0) {
comp->type = COMP_DELAYED;
-@@ -1035,7 +1048,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1038,7 +1051,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC;
nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC;
ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC;
@@ -1042,7 +1042,7 @@ index 54e711810..70bebce6a 100644
sprop[nenc])) != 0) {
kex->failed_choice = peer[nenc];
peer[nenc] = NULL;
-@@ -1050,7 +1063,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1053,7 +1066,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
peer[nmac] = NULL;
goto out;
}
@@ -1051,7 +1051,7 @@ index 54e711810..70bebce6a 100644
sprop[ncomp])) != 0) {
kex->failed_choice = peer[ncomp];
peer[ncomp] = NULL;
-@@ -1073,6 +1086,10 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1076,6 +1089,10 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
dh_need = MAXIMUM(dh_need, newkeys->enc.block_size);
dh_need = MAXIMUM(dh_need, newkeys->enc.iv_len);
dh_need = MAXIMUM(dh_need, newkeys->mac.key_len);
@@ -1062,7 +1062,7 @@ index 54e711810..70bebce6a 100644
}
/* XXX need runden? */
kex->we_need = need;
-@@ -1342,6 +1359,36 @@ dump_digest(const char *msg, const u_char *digest, int len)
+@@ -1345,6 +1362,36 @@ dump_digest(const char *msg, const u_char *digest, int len)
}
#endif
@@ -1100,10 +1100,10 @@ index 54e711810..70bebce6a 100644
* Send a plaintext error message to the peer, suffixed by \r\n.
* Only used during banner exchange, and there only for the server.
diff --git a/kex.h b/kex.h
-index 2c86b25f9..0a0cb1562 100644
+index 21919a9aa..8f9934899 100644
--- a/kex.h
+++ b/kex.h
-@@ -261,6 +261,8 @@ int kexgss_client(struct ssh *);
+@@ -257,6 +257,8 @@ int kexgss_client(struct ssh *);
int kexgss_server(struct ssh *);
#endif
@@ -1113,7 +1113,7 @@ index 2c86b25f9..0a0cb1562 100644
int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
struct sshbuf **);
diff --git a/mac.c b/mac.c
-index 17607830c..2913b8a8c 100644
+index 30496b402..d47df0cdf 100644
--- a/mac.c
+++ b/mac.c
@@ -230,6 +230,20 @@ mac_clear(struct sshmac *mac)
@@ -1149,7 +1149,7 @@ index 04089f41b..6b5ebc44b 100644
#endif /* SSHMAC_H */
diff --git a/monitor.c b/monitor.c
-index 5255083ab..305883278 100644
+index 14cdccf2f..733a3460f 100644
--- a/monitor.c
+++ b/monitor.c
@@ -83,6 +83,7 @@
@@ -1204,7 +1204,7 @@ index 5255083ab..305883278 100644
#endif
{0, 0, NULL}
};
-@@ -1603,8 +1620,10 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1589,8 +1606,10 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
int r, ret, req_presence = 0, req_verify = 0, valid_data = 0;
int encoded_ret;
struct sshkey_sig_details *sig_details = NULL;
@@ -1216,7 +1216,7 @@ index 5255083ab..305883278 100644
(r = sshbuf_get_string_direct(m, &signature, &signaturelen)) != 0 ||
(r = sshbuf_get_string_direct(m, &data, &datalen)) != 0 ||
(r = sshbuf_get_cstring(m, &sigalg, NULL)) != 0)
-@@ -1613,6 +1632,8 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1599,6 +1618,8 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
if (hostbased_cuser == NULL || hostbased_chost == NULL ||
!monitor_allowed_key(blob, bloblen))
fatal_f("bad key, not previously allowed");
@@ -1225,7 +1225,7 @@ index 5255083ab..305883278 100644
/* Empty signature algorithm means NULL. */
if (*sigalg == '\0') {
-@@ -1628,14 +1649,19 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1614,14 +1635,19 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
case MM_USERKEY:
valid_data = monitor_valid_userblob(ssh, data, datalen);
auth_method = "publickey";
@@ -1245,7 +1245,7 @@ index 5255083ab..305883278 100644
break;
}
if (!valid_data)
-@@ -1647,8 +1673,6 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1633,8 +1659,6 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
SSH_FP_DEFAULT)) == NULL)
fatal_f("sshkey_fingerprint failed");
@@ -1254,7 +1254,7 @@ index 5255083ab..305883278 100644
debug3_f("%s %s signature using %s %s%s%s", auth_method,
sshkey_type(key), sigalg == NULL ? "default" : sigalg,
(ret == 0) ? "verified" : "unverified",
-@@ -1736,13 +1760,19 @@ mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw)
+@@ -1722,13 +1746,19 @@ mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw)
}
static void
@@ -1275,7 +1275,7 @@ index 5255083ab..305883278 100644
session_unused(s->self);
}
-@@ -1809,7 +1839,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1795,7 +1825,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
error:
if (s != NULL)
@@ -1284,7 +1284,7 @@ index 5255083ab..305883278 100644
if ((r = sshbuf_put_u32(m, 0)) != 0)
fatal_fr(r, "assemble 0");
mm_request_send(sock, MONITOR_ANS_PTY, m);
-@@ -1828,7 +1858,7 @@ mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1814,7 +1844,7 @@ mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m)
if ((r = sshbuf_get_cstring(m, &tty, NULL)) != 0)
fatal_fr(r, "parse tty");
if ((s = session_by_tty(tty)) != NULL)
@@ -1293,7 +1293,7 @@ index 5255083ab..305883278 100644
sshbuf_reset(m);
free(tty);
return (0);
-@@ -1850,6 +1880,8 @@ mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req)
+@@ -1836,6 +1866,8 @@ mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req)
sshpam_cleanup();
#endif
@@ -1302,7 +1302,7 @@ index 5255083ab..305883278 100644
while (waitpid(pmonitor->m_pid, &status, 0) == -1)
if (errno != EINTR)
exit(1);
-@@ -1896,12 +1928,46 @@ mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m)
+@@ -1882,12 +1914,46 @@ mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m)
{
char *cmd;
int r;
@@ -1350,7 +1350,7 @@ index 5255083ab..305883278 100644
free(cmd);
return (0);
}
-@@ -1974,6 +2040,7 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+@@ -1958,6 +2024,7 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
void
mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
{
@@ -1358,7 +1358,7 @@ index 5255083ab..305883278 100644
debug3_f("Waiting for new keys");
if ((child_state = sshbuf_new()) == NULL)
-@@ -1981,6 +2048,19 @@ mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
+@@ -1965,6 +2032,19 @@ mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT,
child_state);
debug3_f("GOT new keys");
@@ -1378,7 +1378,7 @@ index 5255083ab..305883278 100644
}
-@@ -2275,3 +2355,102 @@ mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
+@@ -2259,3 +2339,102 @@ mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
#endif /* GSSAPI */
@@ -1501,10 +1501,10 @@ index f4721c472..087addff5 100644
MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
diff --git a/monitor_wrap.c b/monitor_wrap.c
-index 496de436e..ef419b743 100644
+index 2b044f383..bdac7d3f9 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
-@@ -598,7 +598,7 @@ mm_key_allowed(enum mm_keytype type, const char *user, const char *host,
+@@ -565,7 +565,7 @@ mm_key_allowed(enum mm_keytype type, const char *user, const char *host,
*/
int
@@ -1513,7 +1513,7 @@ index 496de436e..ef419b743 100644
const u_char *data, size_t datalen, const char *sigalg, u_int compat,
struct sshkey_sig_details **sig_detailsp)
{
-@@ -614,7 +614,8 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
+@@ -581,7 +581,8 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
*sig_detailsp = NULL;
if ((m = sshbuf_new()) == NULL)
fatal_f("sshbuf_new failed");
@@ -1523,7 +1523,7 @@ index 496de436e..ef419b743 100644
(r = sshbuf_put_string(m, sig, siglen)) != 0 ||
(r = sshbuf_put_string(m, data, datalen)) != 0 ||
(r = sshbuf_put_cstring(m, sigalg == NULL ? "" : sigalg)) != 0)
-@@ -647,6 +648,22 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
+@@ -614,6 +615,22 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
return 0;
}
@@ -1546,7 +1546,7 @@ index 496de436e..ef419b743 100644
void
mm_send_keystate(struct ssh *ssh, struct monitor *monitor)
{
-@@ -1063,11 +1080,12 @@ mm_audit_event(struct ssh *ssh, ssh_audit_event_t event)
+@@ -1013,11 +1030,12 @@ mm_audit_event(struct ssh *ssh, ssh_audit_event_t event)
sshbuf_free(m);
}
@@ -1561,7 +1561,7 @@ index 496de436e..ef419b743 100644
debug3_f("entering command %s", command);
-@@ -1077,6 +1095,30 @@ mm_audit_run_command(const char *command)
+@@ -1027,6 +1045,30 @@ mm_audit_run_command(const char *command)
fatal_fr(r, "buffer error");
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_COMMAND, m);
@@ -1592,7 +1592,7 @@ index 496de436e..ef419b743 100644
sshbuf_free(m);
}
#endif /* SSH_AUDIT_EVENTS */
-@@ -1325,3 +1367,83 @@ server_get_connection_info(struct ssh *ssh, int populate, int use_dns)
+@@ -1275,3 +1317,83 @@ server_get_connection_info(struct ssh *ssh, int populate, int use_dns)
return &ci;
}
@@ -1677,7 +1677,7 @@ index 496de436e..ef419b743 100644
+}
+#endif /* SSH_AUDIT_EVENTS */
diff --git a/monitor_wrap.h b/monitor_wrap.h
-index a708bf4d8..9856968f9 100644
+index 07b207d1d..7f846f085 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -63,7 +63,9 @@ int mm_user_key_allowed(struct ssh *ssh, struct passwd *, struct sshkey *, int,
@@ -1706,10 +1706,10 @@ index a708bf4d8..9856968f9 100644
struct Session;
diff --git a/packet.c b/packet.c
-index 190a579d1..fa6f4c843 100644
+index 63dcb5351..4c3c541ed 100644
--- a/packet.c
+++ b/packet.c
-@@ -77,6 +77,7 @@
+@@ -75,6 +75,7 @@
#endif
#include "xmalloc.h"
@@ -1717,7 +1717,7 @@ index 190a579d1..fa6f4c843 100644
#include "compat.h"
#include "ssh2.h"
#include "cipher.h"
-@@ -511,6 +512,13 @@ ssh_packet_get_connection_out(struct ssh *ssh)
+@@ -514,6 +515,13 @@ ssh_packet_get_connection_out(struct ssh *ssh)
return ssh->state->connection_out;
}
@@ -1731,7 +1731,7 @@ index 190a579d1..fa6f4c843 100644
/*
* Returns the IP-address of the remote host as a string. The returned
* string must not be freed.
-@@ -681,22 +689,19 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
+@@ -684,22 +692,19 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
struct session_state *state = ssh->state;
u_int mode;
struct packet *p;
@@ -1759,7 +1759,7 @@ index 190a579d1..fa6f4c843 100644
while ((p = TAILQ_FIRST(&state->outgoing))) {
sshbuf_free(p->payload);
TAILQ_REMOVE(&state->outgoing, p, next);
-@@ -737,8 +742,18 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
+@@ -740,8 +745,18 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
#endif /* WITH_ZLIB */
cipher_free(state->send_context);
cipher_free(state->receive_context);
@@ -1778,7 +1778,7 @@ index 190a579d1..fa6f4c843 100644
free(ssh->local_ipaddr);
ssh->local_ipaddr = NULL;
free(ssh->remote_ipaddr);
-@@ -1004,6 +1019,7 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
+@@ -1007,6 +1022,7 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
(unsigned long long)state->p_send.bytes,
(unsigned long long)state->p_send.blocks);
kex_free_newkeys(state->newkeys[mode]);
@@ -1786,7 +1786,7 @@ index 190a579d1..fa6f4c843 100644
state->newkeys[mode] = NULL;
}
/* note that both bytes and the seqnr are not reset */
-@@ -2385,6 +2401,72 @@ ssh_packet_get_output(struct ssh *ssh)
+@@ -2398,6 +2414,72 @@ ssh_packet_get_output(struct ssh *ssh)
return (void *)ssh->state->output;
}
@@ -1860,17 +1860,17 @@ index 190a579d1..fa6f4c843 100644
static int
ssh_packet_set_postauth(struct ssh *ssh)
diff --git a/packet.h b/packet.h
-index 3e8acb2cd..e9aefdaad 100644
+index 442e38bb7..bfe481220 100644
--- a/packet.h
+++ b/packet.h
-@@ -225,4 +225,5 @@ char *connection_info_message(struct ssh *ssh);
- # undef EC_POINT
- #endif
+@@ -207,4 +207,5 @@ void sshpkt_fmt_connection_id(struct ssh *ssh, char *s, size_t l);
+ const u_char *sshpkt_ptr(struct ssh *, size_t *lenp);
+ char *connection_info_message(struct ssh *ssh);
+void packet_destroy_all(struct ssh *, int, int);
#endif /* PACKET_H */
diff --git a/session.c b/session.c
-index 0c26448d9..75ff1080e 100644
+index 572487b4a..d36c1b702 100644
--- a/session.c
+++ b/session.c
@@ -139,6 +139,7 @@ static int session_pty_req(struct ssh *, Session *);
@@ -1930,7 +1930,7 @@ index 0c26448d9..75ff1080e 100644
/* Force a password change */
if (s->authctxt->force_pwchange) {
-@@ -1717,6 +1734,9 @@ session_unused(int id)
+@@ -1718,6 +1735,9 @@ session_unused(int id)
sessions[id].ttyfd = -1;
sessions[id].ptymaster = -1;
sessions[id].x11_chanids = NULL;
@@ -1940,7 +1940,7 @@ index 0c26448d9..75ff1080e 100644
sessions[id].next_unused = sessions_first_unused;
sessions_first_unused = id;
}
-@@ -1795,6 +1815,19 @@ session_open(Authctxt *authctxt, int chanid)
+@@ -1796,6 +1816,19 @@ session_open(Authctxt *authctxt, int chanid)
return 1;
}
@@ -1960,7 +1960,7 @@ index 0c26448d9..75ff1080e 100644
Session *
session_by_tty(char *tty)
{
-@@ -2413,6 +2446,32 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
+@@ -2417,6 +2450,32 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
chan_write_failed(ssh, c);
}
@@ -1993,7 +1993,7 @@ index 0c26448d9..75ff1080e 100644
void
session_close(struct ssh *ssh, Session *s)
{
-@@ -2426,6 +2485,10 @@ session_close(struct ssh *ssh, Session *s)
+@@ -2430,6 +2489,10 @@ session_close(struct ssh *ssh, Session *s)
if (s->ttyfd != -1)
session_pty_cleanup(s);
@@ -2004,7 +2004,7 @@ index 0c26448d9..75ff1080e 100644
free(s->term);
free(s->display);
free(s->x11_chanids);
-@@ -2502,14 +2565,14 @@ session_close_by_channel(struct ssh *ssh, int id, int force, void *arg)
+@@ -2506,14 +2569,14 @@ session_close_by_channel(struct ssh *ssh, int id, int force, void *arg)
}
void
@@ -2021,7 +2021,7 @@ index 0c26448d9..75ff1080e 100644
else
session_close(ssh, s);
}
-@@ -2635,6 +2698,15 @@ do_authenticated2(struct ssh *ssh, Authctxt *authctxt)
+@@ -2639,6 +2702,15 @@ do_authenticated2(struct ssh *ssh, Authctxt *authctxt)
server_loop2(ssh, authctxt);
}
@@ -2037,7 +2037,7 @@ index 0c26448d9..75ff1080e 100644
void
do_cleanup(struct ssh *ssh, Authctxt *authctxt)
{
-@@ -2698,7 +2770,7 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+@@ -2702,7 +2774,7 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
* or if running in monitor.
*/
if (mm_is_monitor())
@@ -2078,7 +2078,7 @@ index 344a1ddf9..a41c6efcd 100644
void session_close(struct ssh *, Session *);
void do_setusercontext(struct passwd *);
diff --git a/sshd-session.c b/sshd-session.c
-index 23b80eb01..d8972fbd5 100644
+index 8a06616fd..0a9708d3a 100644
--- a/sshd-session.c
+++ b/sshd-session.c
@@ -181,8 +181,8 @@ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes);
@@ -2221,7 +2221,7 @@ index 23b80eb01..d8972fbd5 100644
reseed_prngs();
-@@ -1333,6 +1402,9 @@ main(int ac, char **av)
+@@ -1336,6 +1405,9 @@ main(int ac, char **av)
do_authenticated(ssh, authctxt);
/* The connection has been terminated. */
@@ -2231,7 +2231,7 @@ index 23b80eb01..d8972fbd5 100644
ssh_packet_get_bytes(ssh, &ibytes, &obytes);
verbose("Transferred: sent %llu, received %llu bytes",
(unsigned long long)obytes, (unsigned long long)ibytes);
-@@ -1378,6 +1450,16 @@ sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey,
+@@ -1381,6 +1453,16 @@ sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey,
void
cleanup_exit(int i)
{
@@ -2248,7 +2248,7 @@ index 23b80eb01..d8972fbd5 100644
if (the_active_state != NULL && the_authctxt != NULL) {
do_cleanup(the_active_state, the_authctxt);
if (privsep_is_preauth &&
-@@ -1392,7 +1474,9 @@ cleanup_exit(int i)
+@@ -1395,7 +1477,9 @@ cleanup_exit(int i)
}
#ifdef SSH_AUDIT_EVENTS
/* done after do_cleanup so it can cancel the PAM auth 'thread' */
@@ -2260,7 +2260,7 @@ index 23b80eb01..d8972fbd5 100644
#endif
/* Override default fatal exit value when auth was attempted */
diff --git a/sshd.c b/sshd.c
-index 552435817..22ad4dff0 100644
+index f66e1f86f..6b9847f67 100644
--- a/sshd.c
+++ b/sshd.c
@@ -206,6 +206,15 @@ close_listen_socks(void)
@@ -2288,5 +2288,5 @@ index 552435817..22ad4dff0 100644
if (options.pid_file != NULL)
unlink(options.pid_file);
--
-2.53.0
+2.55.0
diff --git a/0036-openssh-7.1p2-audit-race-condition.patch b/0036-openssh-7.1p2-audit-race-condition.patch
index 3bd6594..3fcbcce 100644
--- a/0036-openssh-7.1p2-audit-race-condition.patch
+++ b/0036-openssh-7.1p2-audit-race-condition.patch
@@ -1,7 +1,7 @@
-From d18c0e8c12110d7a46bcbfb67cb6e28e7ebcecf1 Mon Sep 17 00:00:00 2001
+From 6991682335fb1dded7b50d903b7f9cc600535ba2 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 36/54] openssh-7.1p2-audit-race-condition
+Subject: [PATCH 36/53] openssh-7.1p2-audit-race-condition
# Audit race condition in forked child (#1310684)
---
@@ -11,10 +11,10 @@ Subject: [PATCH 36/54] openssh-7.1p2-audit-race-condition
3 files changed, 102 insertions(+), 7 deletions(-)
diff --git a/monitor_wrap.c b/monitor_wrap.c
-index ef419b743..fa3718924 100644
+index bdac7d3f9..4dc64857b 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
-@@ -1446,4 +1446,50 @@ mm_audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_
+@@ -1396,4 +1396,50 @@ mm_audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SERVER_KEY_FREE, m);
sshbuf_free(m);
}
@@ -66,7 +66,7 @@ index ef419b743..fa3718924 100644
+}
#endif /* SSH_AUDIT_EVENTS */
diff --git a/monitor_wrap.h b/monitor_wrap.h
-index 9856968f9..0865f59cb 100644
+index 7f846f085..41e4b79fe 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -98,6 +98,8 @@ void mm_audit_unsupported_body(struct ssh *, int);
@@ -79,7 +79,7 @@ index 9856968f9..0865f59cb 100644
struct Session;
diff --git a/session.c b/session.c
-index 75ff1080e..1a7f5ca7a 100644
+index d36c1b702..bd8dc808c 100644
--- a/session.c
+++ b/session.c
@@ -161,6 +161,10 @@ static Session *sessions = NULL;
@@ -200,5 +200,5 @@ index 75ff1080e..1a7f5ca7a 100644
if (s->authctxt->force_pwchange) {
do_setusercontext(pw);
--
-2.53.0
+2.55.0
diff --git a/0037-openssh-9.0p1-audit-log.patch b/0037-openssh-9.0p1-audit-log.patch
index 4e8f27e..60cb439 100644
--- a/0037-openssh-9.0p1-audit-log.patch
+++ b/0037-openssh-9.0p1-audit-log.patch
@@ -1,7 +1,7 @@
-From 8a4843f8726028ca6b10285698dccea852632592 Mon Sep 17 00:00:00 2001
+From bb3f0efad18c45bc7c131c6898c9bc87bcb50f55 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 37/54] openssh-9.0p1-audit-log
+Subject: [PATCH 37/53] openssh-9.0p1-audit-log
# https://bugzilla.redhat.com/show_bug.cgi?id=2049947
---
@@ -262,5 +262,5 @@ index 45d66ccff..05ac132cf 100644
void audit_unsupported(struct ssh *, int);
void audit_kex(struct ssh *, int, char *, char *, char *, char *);
--
-2.53.0
+2.55.0
diff --git a/0038-openssh-7.7p1-fips.patch b/0038-openssh-7.7p1-fips.patch
index 19c1b21..04b6851 100644
--- a/0038-openssh-7.7p1-fips.patch
+++ b/0038-openssh-7.7p1-fips.patch
@@ -1,7 +1,7 @@
-From 55c0d54b91cc39ed6f10542f883dbaad6a3213ca Mon Sep 17 00:00:00 2001
+From d57fef8fc7b2ad6426d58ab77fa5aa67172bdac0 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 28 Aug 2025 14:01:38 +0200
-Subject: [PATCH 38/54] openssh-7.7p1-fips
+Subject: [PATCH 38/53] openssh-7.7p1-fips
---
dh.c | 41 ++++++++++++++++++++++
@@ -9,7 +9,7 @@ Subject: [PATCH 38/54] openssh-7.7p1-fips
kex-names.c | 6 +++-
kex.c | 3 +-
kexgen.c | 74 ++++++++++++++++++++++++++++++++--------
- kexgexc.c | 5 +++
+ kexgexc.c | 6 ++++
myproposal.h | 33 ++++++++++++++++++
readconf.c | 21 +++++++++---
sandbox-seccomp-filter.c | 3 ++
@@ -23,7 +23,7 @@ Subject: [PATCH 38/54] openssh-7.7p1-fips
sshconnect2.c | 11 +++++-
sshd.c | 19 +++++++++++
sshkey.c | 37 ++++++++++++++++++++
- 19 files changed, 300 insertions(+), 30 deletions(-)
+ 19 files changed, 301 insertions(+), 30 deletions(-)
diff --git a/dh.c b/dh.c
index b291750d8..3efe0a69f 100644
@@ -102,7 +102,7 @@ index c6326a39d..e51e292b8 100644
u_int dh_estimate(int);
void dh_set_moduli_file(const char *);
diff --git a/kex-names.c b/kex-names.c
-index 769a36810..c0761ae8f 100644
+index 2bc431223..8e9c621bd 100644
--- a/kex-names.c
+++ b/kex-names.c
@@ -32,6 +32,7 @@
@@ -113,7 +113,7 @@ index 769a36810..c0761ae8f 100644
#include <openssl/evp.h>
#endif
-@@ -207,7 +208,10 @@ kex_names_valid(const char *names)
+@@ -211,7 +212,10 @@ kex_names_valid(const char *names)
return 0;
}
if (kex_alg_by_name(p) == NULL) {
@@ -126,7 +126,7 @@ index 769a36810..c0761ae8f 100644
return 0;
}
diff --git a/kex.c b/kex.c
-index 70bebce6a..e1059dec4 100644
+index 9ce2b7e12..7e50f277f 100644
--- a/kex.c
+++ b/kex.c
@@ -37,6 +37,7 @@
@@ -147,7 +147,7 @@ index 70bebce6a..e1059dec4 100644
"ext-info-s,kex-strict-s-v00@openssh.com" :
"ext-info-c,kex-strict-c-v00@openssh.com")) == NULL)
diff --git a/kexgen.c b/kexgen.c
-index a2beb3f10..2f8252488 100644
+index ff504580f..834073ef1 100644
--- a/kexgen.c
+++ b/kexgen.c
@@ -31,6 +31,7 @@
@@ -158,7 +158,7 @@ index a2beb3f10..2f8252488 100644
#include "sshkey.h"
#include "kex.h"
-@@ -115,13 +116,28 @@ kex_gen_client(struct ssh *ssh)
+@@ -118,13 +119,28 @@ kex_gen_client(struct ssh *ssh)
break;
#endif /* WITH_OPENSSL */
case KEX_C25519_SHA256:
@@ -190,7 +190,7 @@ index a2beb3f10..2f8252488 100644
break;
default:
r = SSH_ERR_INVALID_ARGUMENT;
-@@ -189,15 +205,30 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
+@@ -196,15 +212,30 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
break;
#endif /* WITH_OPENSSL */
case KEX_C25519_SHA256:
@@ -226,7 +226,7 @@ index a2beb3f10..2f8252488 100644
break;
default:
r = SSH_ERR_INVALID_ARGUMENT;
-@@ -312,16 +343,31 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
+@@ -323,16 +354,31 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
break;
#endif /* WITH_OPENSSL */
case KEX_C25519_SHA256:
@@ -265,7 +265,7 @@ index a2beb3f10..2f8252488 100644
default:
r = SSH_ERR_INVALID_ARGUMENT;
diff --git a/kexgexc.c b/kexgexc.c
-index 1c2194a8f..f9a58b955 100644
+index 1c2194a8f..2763e862a 100644
--- a/kexgexc.c
+++ b/kexgexc.c
@@ -29,6 +29,7 @@
@@ -276,7 +276,7 @@ index 1c2194a8f..f9a58b955 100644
#include <sys/types.h>
#include <openssl/bn.h>
-@@ -117,6 +118,11 @@ input_kex_dh_gex_group(int type, u_int32
+@@ -116,6 +117,11 @@ input_kex_dh_gex_group(int type, uint32_t seq, struct ssh *ssh)
}
p = g = NULL; /* belong to kex->dh now */
@@ -289,12 +289,12 @@ index 1c2194a8f..f9a58b955 100644
if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
goto out;
diff --git a/myproposal.h b/myproposal.h
-index d992d8b12..6433e0820 100644
+index a2a1f991e..f03293f35 100644
--- a/myproposal.h
+++ b/myproposal.h
@@ -60,6 +60,18 @@
"rsa-sha2-512," \
- "rsa-sha2-256"
+ "rsa-sha2-256" \
+#define KEX_FIPS_PK_ALG \
+ "ecdsa-sha2-nistp256-cert-v01@openssh.com," \
@@ -340,7 +340,7 @@ index d992d8b12..6433e0820 100644
#define SSH_ALLOWED_CA_SIGALGS \
"ssh-ed25519," \
diff --git a/readconf.c b/readconf.c
-index 9004076e4..058e53cf1 100644
+index 992c78866..2cbf1fcbd 100644
--- a/readconf.c
+++ b/readconf.c
@@ -38,6 +38,12 @@
@@ -356,7 +356,7 @@ index 9004076e4..058e53cf1 100644
#include <util.h>
#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
# include <vis.h>
-@@ -3098,11 +3104,16 @@ fill_default_options(Options * options)
+@@ -3099,11 +3105,16 @@ fill_default_options(Options * options)
all_key = sshkey_alg_list(0, 0, 1, ',');
all_sig = sshkey_alg_list(0, 1, 1, ',');
/* remove unsupported algos from default lists */
@@ -379,7 +379,7 @@ index 9004076e4..058e53cf1 100644
do { \
if ((r = kex_assemble_names(&options->what, \
diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
-index e21048f3a..25c9df42a 100644
+index b65413c95..17e1cea72 100644
--- a/sandbox-seccomp-filter.c
+++ b/sandbox-seccomp-filter.c
@@ -258,6 +258,9 @@ static const struct sock_filter preauth_insns[] = {
@@ -393,7 +393,7 @@ index e21048f3a..25c9df42a 100644
SC_DENY(__NR_openat, EACCES),
#endif
diff --git a/servconf.c b/servconf.c
-index 8045756fb..6c1ba77f6 100644
+index 27147a1e7..846f5d212 100644
--- a/servconf.c
+++ b/servconf.c
@@ -38,7 +38,15 @@
@@ -412,7 +412,7 @@ index 8045756fb..6c1ba77f6 100644
#include "xmalloc.h"
#include "ssh.h"
-@@ -242,11 +250,16 @@ assemble_algorithms(ServerOptions *o)
+@@ -213,11 +221,16 @@ assemble_algorithms(ServerOptions *o)
all_key = sshkey_alg_list(0, 0, 1, ',');
all_sig = sshkey_alg_list(0, 1, 1, ',');
/* remove unsupported algos from default lists */
@@ -435,7 +435,7 @@ index 8045756fb..6c1ba77f6 100644
do { \
if ((r = kex_assemble_names(&o->what, defaults, all)) != 0) \
diff --git a/ssh-ed25519.c b/ssh-ed25519.c
-index 2369c3af0..af059d7f1 100644
+index 8228f2830..668823ed2 100644
--- a/ssh-ed25519.c
+++ b/ssh-ed25519.c
@@ -24,6 +24,7 @@
@@ -446,7 +446,7 @@ index 2369c3af0..af059d7f1 100644
#include "log.h"
#include "sshbuf.h"
-@@ -162,6 +163,10 @@ ssh_ed25519_sign(struct sshkey *key,
+@@ -163,6 +164,10 @@ ssh_ed25519_sign(struct sshkey *key,
key->ed25519_sk == NULL ||
datalen >= INT_MAX - crypto_sign_ed25519_BYTES)
return SSH_ERR_INVALID_ARGUMENT;
@@ -457,7 +457,7 @@ index 2369c3af0..af059d7f1 100644
smlen = slen = datalen + crypto_sign_ed25519_BYTES;
if ((sig = malloc(slen)) == NULL)
return SSH_ERR_ALLOC_FAIL;
-@@ -243,6 +248,10 @@ ssh_ed25519_verify(const struct sshkey *key,
+@@ -244,6 +249,10 @@ ssh_ed25519_verify(const struct sshkey *key,
dlen >= INT_MAX - crypto_sign_ed25519_BYTES ||
sig == NULL || siglen == 0)
return SSH_ERR_INVALID_ARGUMENT;
@@ -485,7 +485,7 @@ index a894e23c9..329dc9da0 100644
typedef struct {
diff --git a/ssh-keygen.c b/ssh-keygen.c
-index 584d5a899..12430e6a4 100644
+index 6667a5c1b..ed1932ff0 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -22,6 +22,7 @@
@@ -515,7 +515,7 @@ index 584d5a899..12430e6a4 100644
switch (type) {
case KEY_RSA:
if (*bitsp < SSH_RSA_MINIMUM_MODULUS_SIZE)
-@@ -248,7 +254,7 @@ ask_filename(struct passwd *pw, const char *prompt)
+@@ -243,7 +249,7 @@ ask_filename(struct passwd *pw, const char *prompt)
char *name = NULL;
if (key_type_name == NULL)
@@ -523,8 +523,8 @@ index 584d5a899..12430e6a4 100644
+ name = FIPS_mode() ? _PATH_SSH_CLIENT_ID_RSA : _PATH_SSH_CLIENT_ID_ED25519;
else {
switch (sshkey_type_from_shortname(key_type_name)) {
- #ifdef OPENSSL_HAS_ECC
-@@ -1057,9 +1063,17 @@ do_gen_all_hostkeys(struct passwd *pw)
+ case KEY_ECDSA_CERT:
+@@ -1048,9 +1054,17 @@ do_gen_all_hostkeys(struct passwd *pw)
first = 1;
printf("%s: generating new host keys: ", __progname);
}
@@ -543,7 +543,7 @@ index 584d5a899..12430e6a4 100644
if ((fd = mkstemp(prv_tmp)) == -1) {
error("Could not save your private key in %s: %s",
prv_tmp, strerror(errno));
-@@ -3768,7 +3782,7 @@ main(int argc, char **argv)
+@@ -3792,7 +3806,7 @@ main(int argc, char **argv)
}
if (key_type_name == NULL)
@@ -553,7 +553,7 @@ index 584d5a899..12430e6a4 100644
type = sshkey_type_from_shortname(key_type_name);
type_bits_valid(type, key_type_name, &bits);
diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index bb3ee462d..3cea1daac 100644
+index 77b4341c9..284d4deee 100644
--- a/ssh-keyscan.c
+++ b/ssh-keyscan.c
@@ -21,6 +21,7 @@
@@ -564,7 +564,7 @@ index bb3ee462d..3cea1daac 100644
#include <errno.h>
#include <limits.h>
-@@ -234,6 +235,14 @@ keygrab_ssh2(con *c)
+@@ -238,6 +239,14 @@ keygrab_ssh2(con *c)
char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
int r;
@@ -609,7 +609,7 @@ index ccadb14ca..5af39bb39 100644
goto out;
}
diff --git a/ssh.c b/ssh.c
-index bc3be8f8e..e7bdba327 100644
+index b9a59ca43..00c0edefc 100644
--- a/ssh.c
+++ b/ssh.c
@@ -71,6 +71,7 @@
@@ -620,7 +620,7 @@ index bc3be8f8e..e7bdba327 100644
#include "openbsd-compat/openssl-compat.h"
#include "xmalloc.h"
-@@ -1631,6 +1632,10 @@ main(int ac, char **av)
+@@ -1617,6 +1618,10 @@ main(int ac, char **av)
exit(0);
}
@@ -632,7 +632,7 @@ index bc3be8f8e..e7bdba327 100644
if (options.sk_provider != NULL && *options.sk_provider == '$' &&
strlen(options.sk_provider) > 1) {
diff --git a/sshconnect2.c b/sshconnect2.c
-index 31bbc0ed8..2ddd89d5c 100644
+index 5459caa0c..244c725f7 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -45,6 +45,10 @@
@@ -646,7 +646,7 @@ index 31bbc0ed8..2ddd89d5c 100644
#include "xmalloc.h"
#include "ssh.h"
#include "ssh2.h"
-@@ -263,6 +267,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -264,6 +268,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
#if defined(GSSAPI) && defined(WITH_OPENSSL)
if (options.gss_keyex) {
@@ -656,7 +656,7 @@ index 31bbc0ed8..2ddd89d5c 100644
/* Add the GSSAPI mechanisms currently supported on this
* client to the key exchange algorithm proposal */
orig = myproposal[PROPOSAL_KEX_ALGS];
-@@ -282,7 +289,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -283,7 +290,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
}
gss = ssh_gssapi_client_mechanisms(gss_host,
@@ -668,7 +668,7 @@ index 31bbc0ed8..2ddd89d5c 100644
debug("Offering GSSAPI proposal: %s", gss);
xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
diff --git a/sshd.c b/sshd.c
-index 22ad4dff0..2865619c7 100644
+index 6b9847f67..ae26b10b3 100644
--- a/sshd.c
+++ b/sshd.c
@@ -43,6 +43,7 @@
@@ -693,8 +693,8 @@ index 22ad4dff0..2865619c7 100644
#ifdef HAVE_SECUREWARE
#include <sys/security.h>
#include <prot.h>
-@@ -1604,6 +1612,13 @@ main(int ac, char **av)
- &key, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR)
+@@ -1605,6 +1613,13 @@ main(int ac, char **av)
+ r != SSH_ERR_SYSTEM_ERROR && !have_agent)
do_log2_r(r, ll, "Unable to load host key \"%s\"",
options.host_key_files[i]);
+ if (FIPS_mode() && key != NULL && (sshkey_type_plain(key->type) == KEY_ED25519_SK
@@ -707,7 +707,7 @@ index 22ad4dff0..2865619c7 100644
if (sshkey_is_sk(key) &&
key->sk_flags & SSH_SK_USER_PRESENCE_REQD) {
debug("host key %s requires user presence, ignoring",
-@@ -1827,6 +1842,10 @@ main(int ac, char **av)
+@@ -1829,6 +1844,10 @@ main(int ac, char **av)
/* Reinitialize the log (because of the fork above). */
log_init(__progname, options.log_level, options.log_facility, log_stderr);
@@ -719,7 +719,7 @@ index 22ad4dff0..2865619c7 100644
* Chdir to the root directory so that the current disk can be
* unmounted if desired.
diff --git a/sshkey.c b/sshkey.c
-index ccc35dbf5..82e55509d 100644
+index 7d1401a8f..2380c8081 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -36,6 +36,7 @@
@@ -757,7 +757,7 @@ index ccc35dbf5..82e55509d 100644
if (!include_sigonly && impl->sigonly)
continue;
if ((certs_only && !impl->cert) || (plain_only && impl->cert))
-@@ -1431,6 +1445,20 @@ sshkey_read(struct sshkey *ret, char **cpp)
+@@ -1427,6 +1441,20 @@ sshkey_read(struct sshkey *ret, char **cpp)
return SSH_ERR_EC_CURVE_MISMATCH;
}
@@ -778,7 +778,7 @@ index ccc35dbf5..82e55509d 100644
/* Fill in ret from parsed key */
sshkey_free_contents(ret);
*ret = *k;
-@@ -2264,6 +2292,11 @@ sshkey_sign(struct sshkey *key,
+@@ -2260,6 +2288,11 @@ sshkey_sign(struct sshkey *key,
*lenp = 0;
if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE)
return SSH_ERR_INVALID_ARGUMENT;
@@ -790,7 +790,7 @@ index ccc35dbf5..82e55509d 100644
if ((impl = sshkey_impl_from_key(key)) == NULL)
return SSH_ERR_KEY_TYPE_UNKNOWN;
if ((r = sshkey_unshield_private(key)) != 0)
-@@ -2303,6 +2336,10 @@ sshkey_verify(const struct sshkey *key,
+@@ -2299,6 +2332,10 @@ sshkey_verify(const struct sshkey *key,
*detailsp = NULL;
if (siglen == 0 || dlen > SSH_KEY_MAX_SIGN_DATA_SIZE)
return SSH_ERR_INVALID_ARGUMENT;
@@ -802,5 +802,5 @@ index ccc35dbf5..82e55509d 100644
return SSH_ERR_KEY_TYPE_UNKNOWN;
return impl->funcs->verify(key, sig, siglen, data, dlen,
--
-2.53.0
+2.55.0
diff --git a/0039-openssh-8.7p1-negotiate-supported-algs.patch b/0039-openssh-8.7p1-negotiate-supported-algs.patch
index d00bed4..fbdd62d 100644
--- a/0039-openssh-8.7p1-negotiate-supported-algs.patch
+++ b/0039-openssh-8.7p1-negotiate-supported-algs.patch
@@ -1,18 +1,19 @@
-From bfbdaf0b1355e06be5c40dae3986d4ddde50ac08 Mon Sep 17 00:00:00 2001
+From 5e5ea62ca23bdce9f3cad2ea11c8f053faeb42ee Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 39/54] openssh-8.7p1-negotiate-supported-algs
+Subject: [PATCH 39/53] openssh-8.7p1-negotiate-supported-algs
# Don't propose disallowed algorithms during hostkey negotiation
# upstream MR:
# https://github.com/openssh/openssh-portable/pull/323
---
- regress/hostkey-agent.sh | 32 +++++++++++++++++++++++++-------
- sshconnect2.c | 17 +++++++++++++++--
- 2 files changed, 40 insertions(+), 9 deletions(-)
+ regress/hostkey-agent.sh | 32 +++++++++++++++++++++++++-------
+ regress/knownhosts-command.sh | 8 ++++++--
+ sshconnect2.c | 17 +++++++++++++++--
+ 3 files changed, 46 insertions(+), 11 deletions(-)
diff --git a/regress/hostkey-agent.sh b/regress/hostkey-agent.sh
-index 28dcfe170..b9e716dcd 100644
+index 0e7962e3c..251d5258e 100644
--- a/regress/hostkey-agent.sh
+++ b/regress/hostkey-agent.sh
@@ -17,8 +17,21 @@ trace "make CA key"
@@ -86,12 +87,37 @@ index 28dcfe170..b9e716dcd 100644
verbose "cert type $k"
opts="-oHostKeyAlgorithms=$k -F $OBJ/ssh_proxy"
SSH_CONNECTION=`${SSH} $opts host 'echo $SSH_CONNECTION'`
+diff --git a/regress/knownhosts-command.sh b/regress/knownhosts-command.sh
+index 58004b80e..036a7c2b9 100644
+--- a/regress/knownhosts-command.sh
++++ b/regress/knownhosts-command.sh
+@@ -38,8 +38,12 @@ _EOF
+ chmod a+x $OBJ/knownhosts_command
+ ${SSH} -F $OBJ/ssh_proxy x true && fail "ssh connect succeeded with bad exit"
+
++PUBKEY_ACCEPTED_ALGOS=`$SSH -G "example.com" | \
++ grep -i "PubkeyAcceptedAlgorithms" | cut -d ' ' -f2- | tr "," "|"`
++SSH_ACCEPTED_HOSTKEY_TYPES=`echo "$SSH_HOSTKEY_TYPES" | tr ' ' '\n' | egrep "$PUBKEY_ACCEPTED_ALGOS"`
++
+ cp $OBJ/sshd_proxy $OBJ/sshd_proxy.bak
+-for keytype in ${SSH_HOSTKEY_TYPES} ; do
++for keytype in ${SSH_ACCEPTED_HOSTKEY_TYPES} ; do
+ grep -vi HostKeyAlgorithms $OBJ/sshd_proxy.bak > $OBJ/sshd_proxy
+ echo "HostKeyAlgorithms=+$keytype" >> $OBJ/sshd_proxy
+ algs=$keytype
+@@ -53,5 +57,5 @@ test "x\$3" = "x$LOGNAME" || die "wrong username \$3 (expected $LOGNAME)"
+ grep -- "\$1.*\$2" $OBJ/known_hosts
+ _EOF
+ ${SSH} -F $OBJ/ssh_proxy -oHostKeyAlgorithms=$algs x true ||
+- fail "ssh connect failed for keytype $x"
++ fail "ssh connect failed for keytype $keytype"
+ done
diff --git a/sshconnect2.c b/sshconnect2.c
-index 2ddd89d5c..31f51be62 100644
+index 244c725f7..c21b3a11a 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
-@@ -224,7 +224,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- const struct ssh_conn_info *cinfo)
+@@ -224,7 +224,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ u_short port, const struct ssh_conn_info *cinfo)
{
char *myproposal[PROPOSAL_MAX];
- char *all_key, *hkalgs = NULL;
@@ -99,9 +125,9 @@ index 2ddd89d5c..31f51be62 100644
int r, use_known_hosts_order = 0;
#if defined(GSSAPI) && defined(WITH_OPENSSL)
-@@ -260,10 +260,22 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- if (use_known_hosts_order)
- hkalgs = order_hostkeyalgs(host, hostaddr, port, cinfo);
+@@ -261,10 +261,22 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ port, cinfo);
+ }
+ filtered_algs = hkalgs ? match_filter_allowlist(hkalgs, options.pubkey_accepted_algos)
+ : match_filter_allowlist(options.hostkeyalgorithms,
@@ -123,7 +149,7 @@ index 2ddd89d5c..31f51be62 100644
#if defined(GSSAPI) && defined(WITH_OPENSSL)
if (options.gss_keyex) {
-@@ -307,6 +319,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -308,6 +320,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
#endif
free(hkalgs);
@@ -132,5 +158,5 @@ index 2ddd89d5c..31f51be62 100644
/* start key exchange */
if ((r = kex_setup(ssh, myproposal)) != 0)
--
-2.53.0
+2.55.0
diff --git a/0040-openssh-9.0p1-evp-fips-kex.patch b/0040-openssh-9.0p1-evp-fips-kex.patch
index 66fd4e6..8ad50ff 100644
--- a/0040-openssh-9.0p1-evp-fips-kex.patch
+++ b/0040-openssh-9.0p1-evp-fips-kex.patch
@@ -1,15 +1,15 @@
-From fc166604790409eefd5f550c71c232be3c18d94d Mon Sep 17 00:00:00 2001
+From 162e99b9189796fad6327148d8c6817d5ee74c18 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 40/54] openssh-9.0p1-evp-fips-kex
+Subject: [PATCH 40/53] openssh-9.0p1-evp-fips-kex
---
dh.c | 98 +++++++++++++++++++++++++++++++++-----
kex.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
kex.h | 6 +++
kexdh.c | 52 ++++++++++++++++++--
- kexecdh.c | 129 ++++++++++++++++++++++++++++++++++++++++----------
- 5 files changed, 382 insertions(+), 42 deletions(-)
+ kexecdh.c | 127 ++++++++++++++++++++++++++++++++++++++-----------
+ 5 files changed, 378 insertions(+), 44 deletions(-)
diff --git a/dh.c b/dh.c
index 3efe0a69f..bf324309c 100644
@@ -140,10 +140,10 @@ index 3efe0a69f..bf324309c 100644
DH *
diff --git a/kex.c b/kex.c
-index e1059dec4..1eb3ca9d3 100644
+index 7e50f277f..62cfa86cd 100644
--- a/kex.c
+++ b/kex.c
-@@ -1619,3 +1619,142 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
+@@ -1622,3 +1622,142 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
return r;
}
@@ -287,20 +287,20 @@ index e1059dec4..1eb3ca9d3 100644
+}
+#endif /* WITH_OPENSSL */
diff --git a/kex.h b/kex.h
-index 0a0cb1562..ff41324e9 100644
+index 8f9934899..1fe4bb29b 100644
--- a/kex.h
+++ b/kex.h
-@@ -37,6 +37,9 @@
- # include <openssl/bn.h>
- # include <openssl/dh.h>
- # include <openssl/ecdsa.h>
-+# include <openssl/evp.h>
-+# include <openssl/core_names.h>
-+# include <openssl/param_build.h>
- # ifdef OPENSSL_HAS_ECC
- # include <openssl/ec.h>
- # else /* OPENSSL_HAS_ECC */
-@@ -317,6 +320,9 @@ int kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE],
+@@ -38,6 +38,9 @@
+ #include <openssl/dh.h>
+ #include <openssl/ec.h>
+ #include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++#include <openssl/core_names.h>
++#include <openssl/param_build.h>
+ #else /* OPENSSL */
+ #define BIGNUM void
+ #define DH void
+@@ -322,6 +325,9 @@ int kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE],
const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int)
__attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE)))
__attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE)));
@@ -404,10 +404,10 @@ index b63374b0e..74afa1184 100644
}
diff --git a/kexecdh.c b/kexecdh.c
-index 6a9058cdc..b661e0705 100644
+index a9dbf5c4b..38d827b4c 100644
--- a/kexecdh.c
+++ b/kexecdh.c
-@@ -35,16 +35,56 @@
+@@ -35,11 +35,51 @@
#include <openssl/bn.h>
#include <openssl/ecdh.h>
@@ -421,11 +421,7 @@ index 6a9058cdc..b661e0705 100644
#include "sshbuf.h"
#include "ssherr.h"
+#include "log.h"
-
- static int
- kex_ecdh_dec_key_group(struct kex *, const struct sshbuf *, EC_KEY *key,
- const EC_GROUP *, struct sshbuf **);
-
++
+static EC_KEY *
+generate_ec_keys(int ec_nid)
+{
@@ -460,11 +456,10 @@ index 6a9058cdc..b661e0705 100644
+ OSSL_PARAM_free(params);
+ return client_key;
+}
-+
+
int
kex_ecdh_keypair(struct kex *kex)
- {
-@@ -54,11 +94,7 @@ kex_ecdh_keypair(struct kex *kex)
+@@ -50,11 +90,7 @@ kex_ecdh_keypair(struct kex *kex)
struct sshbuf *buf = NULL;
int r;
@@ -477,7 +472,7 @@ index 6a9058cdc..b661e0705 100644
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
-@@ -100,11 +136,7 @@ kex_ecdh_enc(struct kex *kex, const struct sshbuf *client_blob,
+@@ -96,11 +132,7 @@ kex_ecdh_enc(struct kex *kex, const struct sshbuf *client_blob,
*server_blobp = NULL;
*shared_secretp = NULL;
@@ -490,7 +485,7 @@ index 6a9058cdc..b661e0705 100644
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
-@@ -139,11 +171,21 @@ kex_ecdh_dec_key_group(struct kex *kex, const struct sshbuf *ec_blob,
+@@ -135,11 +167,21 @@ kex_ecdh_dec_key_group(struct kex *kex, const struct sshbuf *ec_blob,
{
struct sshbuf *buf = NULL;
BIGNUM *shared_secret = NULL;
@@ -515,7 +510,7 @@ index 6a9058cdc..b661e0705 100644
*shared_secretp = NULL;
if ((buf = sshbuf_new()) == NULL) {
-@@ -152,45 +194,82 @@ kex_ecdh_dec_key_group(struct kex *kex, const struct sshbuf *ec_blob,
+@@ -148,29 +190,58 @@ kex_ecdh_dec_key_group(struct kex *kex, const struct sshbuf *ec_blob,
}
if ((r = sshbuf_put_stringb(buf, ec_blob)) != 0)
goto out;
@@ -573,28 +568,32 @@ index 6a9058cdc..b661e0705 100644
goto out;
}
- klen = (EC_GROUP_get_degree(group) + 7) / 8;
-- if ((kbuf = malloc(klen)) == NULL ||
-- (shared_secret = BN_new()) == NULL) {
-+ if ((kbuf = malloc(klen)) == NULL) {
+ if ((kbuf = malloc(klen)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
-- if (ECDH_compute_key(kbuf, klen, dh_pub, key, NULL) != (int)klen ||
-- BN_bin2bn(kbuf, klen, shared_secret) == NULL) {
+- if (ECDH_compute_key(kbuf, klen, dh_pub, key, NULL) != (int)klen) {
+ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
- #ifdef DEBUG_KEXECDH
- dump_digest("shared secret", kbuf, klen);
- #endif
-+ if ((shared_secret = BN_new()) == NULL ||
-+ (BN_bin2bn(kbuf, klen, shared_secret) == NULL)) {
-+ r = SSH_ERR_ALLOC_FAIL;
-+ goto out;
-+ }
- if ((r = sshbuf_put_bignum2(buf, shared_secret)) != 0)
- goto out;
+@@ -181,23 +252,25 @@ kex_ecdh_dec_key_group(struct kex *kex, const struct sshbuf *ec_blob,
+ if ((r = sshbuf_put(buf, kbuf, klen)) != 0)
+ goto out;
+ } else {
+- if ((shared_secret = BN_new()) == NULL) {
++ if ((shared_secret = BN_new()) == NULL ||
++ (BN_bin2bn(kbuf, klen, shared_secret) == NULL)) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+- if (BN_bin2bn(kbuf, klen, shared_secret) == NULL) {
+- r = SSH_ERR_LIBCRYPTO_ERROR;
+- goto out;
+- }
+ if ((r = sshbuf_put_bignum2(buf, shared_secret)) != 0)
+ goto out;
+ }
*shared_secretp = buf;
buf = NULL;
out:
@@ -611,5 +610,5 @@ index 6a9058cdc..b661e0705 100644
return r;
}
--
-2.53.0
+2.55.0
diff --git a/0041-openssh-8.7p1-nohostsha1proof.patch b/0041-openssh-8.7p1-nohostsha1proof.patch
index 0b62cc4..dc39983 100644
--- a/0041-openssh-8.7p1-nohostsha1proof.patch
+++ b/0041-openssh-8.7p1-nohostsha1proof.patch
@@ -1,7 +1,7 @@
-From 816d931356b3b72f61929c8d66746b55d96882bc Mon Sep 17 00:00:00 2001
+From 8555dd3916d311cbef5bff785523cd27a193c720 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 41/54] openssh-8.7p1-nohostsha1proof
+Subject: [PATCH 41/53] openssh-8.7p1-nohostsha1proof
---
compat.c | 6 +++++
@@ -65,10 +65,10 @@ index 1a19060fc..2e6db5bf9 100644
/* #define unused 0x00000020 */
#define SSH_BUG_DEBUG 0x00000040
diff --git a/monitor.c b/monitor.c
-index 305883278..bf5a5105b 100644
+index 733a3460f..8269e78c3 100644
--- a/monitor.c
+++ b/monitor.c
-@@ -755,11 +755,18 @@ int
+@@ -741,11 +741,18 @@ int
mm_answer_setcompat(struct ssh *ssh, int sock, struct sshbuf *m)
{
int r;
@@ -88,7 +88,7 @@ index 305883278..bf5a5105b 100644
compat_set = 1;
return (0);
-@@ -773,10 +780,12 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -759,11 +766,13 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
struct sshbuf *sigbuf = NULL;
u_char *p = NULL, *signature = NULL;
char *alg = NULL;
@@ -99,11 +99,12 @@ index 305883278..bf5a5105b 100644
+ int r, is_proof = 0;
+ int keyid, compat;
const char proof_req[] = "hostkeys-prove-00@openssh.com";
+ static int nhostkey_proofs_done, *hostkey_proofs_done;
+ const char safe_rsa[] = "rsa-sha2-256";
debug3_f("entering");
-@@ -838,18 +847,30 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -836,18 +845,30 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
}
if ((key = get_hostkey_by_index(keyid)) != NULL) {
@@ -138,7 +139,7 @@ index 305883278..bf5a5105b 100644
sshbuf_reset(m);
diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
-index 16c2f2dff..f4700deeb 100644
+index 89376f115..db7cb418b 100644
--- a/regress/unittests/kex/test_kex.c
+++ b/regress/unittests/kex/test_kex.c
@@ -110,7 +110,8 @@ do_kex_with_key(char *kex, char *cipher, char *mac,
@@ -152,10 +153,10 @@ index 16c2f2dff..f4700deeb 100644
kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname;
ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0);
diff --git a/regress/unittests/sshkey/test_file.c b/regress/unittests/sshkey/test_file.c
-index e412b75d8..5b06bc905 100644
+index f8fcc3abd..d4ab64cf0 100644
--- a/regress/unittests/sshkey/test_file.c
+++ b/regress/unittests/sshkey/test_file.c
-@@ -106,6 +106,7 @@ sshkey_file_tests(void)
+@@ -104,6 +104,7 @@ sshkey_file_tests(void)
sshkey_free(k2);
TEST_DONE();
@@ -163,7 +164,7 @@ index e412b75d8..5b06bc905 100644
TEST_START("load RSA cert with SHA1 signature");
ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha1"), &k2), 0);
ASSERT_PTR_NE(k2, NULL);
-@@ -113,7 +114,7 @@ sshkey_file_tests(void)
+@@ -111,7 +112,7 @@ sshkey_file_tests(void)
ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
ASSERT_STRING_EQ(k2->cert->signature_type, "ssh-rsa");
sshkey_free(k2);
@@ -173,10 +174,10 @@ index e412b75d8..5b06bc905 100644
TEST_START("load RSA cert with SHA512 signature");
ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha512"), &k2), 0);
diff --git a/regress/unittests/sshkey/test_fuzz.c b/regress/unittests/sshkey/test_fuzz.c
-index d0f47d7cf..ba4d506d5 100644
+index 0e5bdf748..3bdfde167 100644
--- a/regress/unittests/sshkey/test_fuzz.c
+++ b/regress/unittests/sshkey/test_fuzz.c
-@@ -273,13 +273,14 @@ sshkey_fuzz_tests(void)
+@@ -313,13 +313,14 @@ sshkey_fuzz_tests(void)
TEST_DONE();
#ifdef WITH_OPENSSL
@@ -193,10 +194,10 @@ index d0f47d7cf..ba4d506d5 100644
TEST_START("fuzz RSA SHA256 sig");
buf = load_file("rsa_1");
diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c
-index 1701c87c5..36f6fd335 100644
+index 59efbb28e..42198775f 100644
--- a/regress/unittests/sshkey/test_sshkey.c
+++ b/regress/unittests/sshkey/test_sshkey.c
-@@ -59,6 +59,9 @@ build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
+@@ -57,6 +57,9 @@ build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
u_char *sigblob;
size_t siglen;
@@ -206,7 +207,7 @@ index 1701c87c5..36f6fd335 100644
ca_buf = sshbuf_new();
ASSERT_PTR_NE(ca_buf, NULL);
ASSERT_INT_EQ(sshkey_putb(ca_key, ca_buf), 0);
-@@ -100,8 +103,9 @@ build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
+@@ -98,8 +101,9 @@ build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
ASSERT_INT_EQ(sshbuf_put_string(b, NULL, 0), 0); /* reserved */
ASSERT_INT_EQ(sshbuf_put_stringb(b, ca_buf), 0); /* signature key */
ASSERT_INT_EQ(sshkey_sign(sign_key, &sigblob, &siglen,
@@ -218,7 +219,7 @@ index 1701c87c5..36f6fd335 100644
free(sigblob);
sshbuf_free(ca_buf);
-@@ -118,16 +122,20 @@ signature_test(struct sshkey *k, struct sshkey *bad, const char *sig_alg,
+@@ -116,16 +120,20 @@ signature_test(struct sshkey *k, struct sshkey *bad, const char *sig_alg,
{
size_t len;
u_char *sig;
@@ -247,7 +248,7 @@ index 1701c87c5..36f6fd335 100644
free(sig);
}
-@@ -552,7 +560,7 @@ sshkey_tests(void)
+@@ -625,7 +633,7 @@ sshkey_tests(void)
ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_1.pub"), &k2,
NULL), 0);
k3 = get_private("rsa_1");
@@ -257,7 +258,7 @@ index 1701c87c5..36f6fd335 100644
SSH_ERR_KEY_CERT_INVALID_SIGN_KEY);
ASSERT_PTR_EQ(k4, NULL);
diff --git a/serverloop.c b/serverloop.c
-index 8e63480ec..1087a6c7d 100644
+index c22681483..29445426b 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -76,6 +76,7 @@
@@ -268,7 +269,7 @@ index 8e63480ec..1087a6c7d 100644
extern ServerOptions options;
-@@ -724,7 +725,10 @@ server_input_hostkeys_prove(struct ssh *ssh, struct sshbuf **respp)
+@@ -725,7 +726,10 @@ server_input_hostkeys_prove(struct ssh *ssh, struct sshbuf **respp)
else if (ssh->kex->flags & KEX_RSA_SHA2_256_SUPPORTED)
sigalg = "rsa-sha2-256";
}
@@ -295,7 +296,7 @@ index 5af39bb39..88232c6ed 100644
goto out;
}
diff --git a/sshconnect2.c b/sshconnect2.c
-index 31f51be62..eee1f9794 100644
+index c21b3a11a..3660873a6 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1440,6 +1440,14 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp,
@@ -314,10 +315,10 @@ index 31f51be62..eee1f9794 100644
}
diff --git a/sshd-session.c b/sshd-session.c
-index d8972fbd5..7064afd7c 100644
+index 0a9708d3a..adf5b1b49 100644
--- a/sshd-session.c
+++ b/sshd-session.c
-@@ -1260,6 +1260,27 @@ main(int ac, char **av)
+@@ -1261,6 +1261,27 @@ main(int ac, char **av)
check_ip_options(ssh);
@@ -346,5 +347,5 @@ index d8972fbd5..7064afd7c 100644
channel_init_channels(ssh);
channel_set_af(ssh, options.address_family);
--
-2.53.0
+2.55.0
diff --git a/0042-openssh-9.9p1-separate-keysign.patch b/0042-openssh-9.9p1-separate-keysign.patch
index 1535e23..c267e9e 100644
--- a/0042-openssh-9.9p1-separate-keysign.patch
+++ b/0042-openssh-9.9p1-separate-keysign.patch
@@ -1,17 +1,17 @@
-From f3de328093334a903ee5459340765b7f5441216a Mon Sep 17 00:00:00 2001
+From e9eba7f4adb59d308ff9dec4f8ec1a426cfe3e39 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 42/54] openssh-9.9p1-separate-keysign
+Subject: [PATCH 42/53] openssh-9.9p1-separate-keysign
---
ssh_config.5 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ssh_config.5 b/ssh_config.5
-index 306fa1d8a..ab98c172f 100644
+index c4c72f587..f2ff8585c 100644
--- a/ssh_config.5
+++ b/ssh_config.5
-@@ -797,7 +797,7 @@ or
+@@ -834,7 +834,7 @@ or
This option should be placed in the non-hostspecific section.
See
.Xr ssh-keysign 8
@@ -21,5 +21,5 @@ index 306fa1d8a..ab98c172f 100644
Sets the escape character (default:
.Ql ~ ) .
--
-2.53.0
+2.55.0
diff --git a/0043-openssh-9.9p1-openssl-mlkem.patch b/0043-openssh-9.9p1-openssl-mlkem.patch
index 602d7be..8ffc1c7 100644
--- a/0043-openssh-9.9p1-openssl-mlkem.patch
+++ b/0043-openssh-9.9p1-openssl-mlkem.patch
@@ -1,18 +1,18 @@
-From 1d53d34290defbbb3b6daa967c74b8f00282dc7e Mon Sep 17 00:00:00 2001
+From 648348f34e3f4279e030184a318a9c0326fd82a1 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Thu, 15 May 2025 13:43:29 +0200
-Subject: [PATCH 43/54] openssh-9.9p1-openssl-mlkem
+Subject: [PATCH 43/53] openssh-9.9p1-openssl-mlkem
---
kex-names.c | 23 +++-
- kexmlkem768x25519.c | 291 ++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 313 insertions(+), 1 deletion(-)
+ kexmlkem768x25519.c | 292 ++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 314 insertions(+), 1 deletion(-)
diff --git a/kex-names.c b/kex-names.c
-index c0761ae8f..882f2905b 100644
+index 8e9c621bd..cdd5db90e 100644
--- a/kex-names.c
+++ b/kex-names.c
-@@ -108,6 +108,19 @@ static const struct kexalg gss_kexalgs[] = {
+@@ -106,6 +106,19 @@ static const struct kexalg gss_kexalgs[] = {
{ NULL, 0, -1, -1, 0},
};
@@ -32,7 +32,7 @@ index c0761ae8f..882f2905b 100644
static char *
kex_alg_list_internal(char sep, const struct kexalg *algs)
{
-@@ -115,8 +128,12 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
+@@ -113,8 +126,12 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
const struct kexalg *k;
char sep_str[2] = {sep, '\0'};
@@ -46,7 +46,7 @@ index c0761ae8f..882f2905b 100644
return ret;
}
-@@ -138,6 +155,10 @@ kex_alg_by_name(const char *name)
+@@ -136,6 +153,10 @@ kex_alg_by_name(const char *name)
{
const struct kexalg *k;
@@ -58,13 +58,13 @@ index c0761ae8f..882f2905b 100644
if (strcmp(k->name, name) == 0)
return k;
diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
-index 2585d1db3..ab6167221 100644
+index e2c2c0a86..36658120f 100644
--- a/kexmlkem768x25519.c
+++ b/kexmlkem768x25519.c
-@@ -44,10 +44,127 @@
+@@ -45,9 +45,127 @@
+
#ifdef USE_MLKEM768X25519
- #include "libcrux_mlkem768_sha3.h"
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <stdio.h>
@@ -181,16 +181,16 @@ index 2585d1db3..ab6167221 100644
+
+ return r;
+}
-
++
int
kex_kem_mlkem768x25519_keypair(struct kex *kex)
{
+#if 0
struct sshbuf *buf = NULL;
- u_char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN], *cp = NULL;
+ u_char *cp = NULL;
size_t need;
-@@ -82,6 +199,36 @@ kex_kem_mlkem768x25519_keypair(struct kex *kex)
- explicit_bzero(rnd, sizeof(rnd));
+@@ -78,6 +196,36 @@ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+ out:
sshbuf_free(buf);
return r;
+#else
@@ -226,7 +226,7 @@ index 2585d1db3..ab6167221 100644
}
int
-@@ -89,6 +236,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+@@ -85,6 +233,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
const struct sshbuf *client_blob, struct sshbuf **server_blobp,
struct sshbuf **shared_secretp)
{
@@ -234,7 +234,7 @@ index 2585d1db3..ab6167221 100644
struct sshbuf *server_blob = NULL;
struct sshbuf *buf = NULL;
const u_char *client_pub;
-@@ -181,12 +329,97 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+@@ -170,12 +319,97 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
sshbuf_free(server_blob);
sshbuf_free(buf);
return r;
@@ -277,13 +277,13 @@ index 2585d1db3..ab6167221 100644
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
-+ if (mlkem768_encap_secret(client_pub, enc.snd, enc.fst.value) != 0)
++ if (mlkem768_encap_secret(client_pub, enc.snd.data, enc.fst.data) != 0)
+ goto out;
+
+ /* generate ECDH key pair, store server pubkey after ciphertext */
+ kexc25519_keygen(server_key, server_pub);
-+ if ((r = sshbuf_put(buf, enc.snd, sizeof(enc.snd))) != 0 ||
-+ (r = sshbuf_put(server_blob, enc.fst.value, sizeof(enc.fst.value))) != 0 ||
++ if ((r = sshbuf_put(buf, enc.snd.data, sizeof(enc.snd.data))) != 0 ||
++ (r = sshbuf_put(server_blob, enc.fst.data, sizeof(enc.fst.data))) != 0 ||
+ (r = sshbuf_put(server_blob, server_pub, sizeof(server_pub))) != 0)
+ goto out;
+ /* append ECDH shared key */
@@ -295,8 +295,8 @@ index 2585d1db3..ab6167221 100644
+#ifdef DEBUG_KEXECDH
+ dump_digest("server public key 25519:", server_pub, CURVE25519_SIZE);
+ dump_digest("server cipher text:",
-+ enc.fst.value, sizeof(enc.fst.value));
-+ dump_digest("server kem key:", enc.snd, sizeof(enc.snd));
++ enc.fst.data, sizeof(enc.fst.data));
++ dump_digest("server kem key:", enc.snd.data, sizeof(enc.snd.data));
+ dump_digest("concatenation of KEM key and ECDH shared key:",
+ sshbuf_ptr(buf), sshbuf_len(buf));
+#endif
@@ -330,10 +330,10 @@ index 2585d1db3..ab6167221 100644
{
+#if 0
struct sshbuf *buf = NULL;
- u_char mlkem_key[crypto_kem_mlkem768_BYTES];
+ u_char shared_secret[MLKEM768_BYTES];
const u_char *ciphertext, *server_pub;
-@@ -254,6 +487,64 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- explicit_bzero(mlkem_key, sizeof(mlkem_key));
+@@ -236,6 +470,64 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ explicit_bzero(shared_secret, sizeof(shared_secret));
sshbuf_free(buf);
return r;
+#else
@@ -398,5 +398,5 @@ index 2585d1db3..ab6167221 100644
#else /* USE_MLKEM768X25519 */
int
--
-2.53.0
+2.55.0
diff --git a/0044-openssh-9.9p2-error_processing.patch b/0044-openssh-9.9p2-error_processing.patch
index 35df34b..dc7f1eb 100644
--- a/0044-openssh-9.9p2-error_processing.patch
+++ b/0044-openssh-9.9p2-error_processing.patch
@@ -1,7 +1,7 @@
-From 04ec0e34a4d4f867cad32ed8543b32e4de458f8f Mon Sep 17 00:00:00 2001
+From 876a71d2fa9c9eea5b4263216413e9f3864956d2 Mon Sep 17 00:00:00 2001
From: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Fri, 16 May 2025 14:53:54 +0200
-Subject: [PATCH 44/54] openssh-9.9p2-error_processing
+Subject: [PATCH 44/53] openssh-9.9p2-error_processing
# https://www.openwall.com/lists/oss-security/2025/02/22/1
---
@@ -9,10 +9,10 @@ Subject: [PATCH 44/54] openssh-9.9p2-error_processing
1 file changed, 2 insertions(+)
diff --git a/ssh-agent.c b/ssh-agent.c
-index c73abd1d0..7773a4b07 100644
+index 1604f540a..4c8e1a4da 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
-@@ -1352,6 +1352,8 @@ process_add_identity(SocketEntry *e)
+@@ -1359,6 +1359,8 @@ process_add_identity(SocketEntry *e)
if ((r = sshkey_private_deserialize(e->request, &k)) != 0 ||
k == NULL ||
(r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) {
@@ -22,5 +22,5 @@ index c73abd1d0..7773a4b07 100644
goto out;
}
--
-2.53.0
+2.55.0
diff --git a/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch b/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
new file mode 100644
index 0000000..e73e923
--- /dev/null
+++ b/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
@@ -0,0 +1,88 @@
+From e864b54e074914b1bdc2a239d02b9cabc97e9ca4 Mon Sep 17 00:00:00 2001
+From: Zoltan Fridrich <zfridric@redhat.com>
+Date: Mon, 5 May 2025 11:52:25 +0200
+Subject: [PATCH 45/53] Ignore bad hostkeys in known_hosts file
+
+Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
+
+# https://github.com/openssh/openssh-portable/pull/567
+---
+ hostfile.c | 15 +++++++++++++++
+ hostfile.h | 1 +
+ ssh.c | 2 ++
+ 3 files changed, 18 insertions(+)
+
+diff --git a/hostfile.c b/hostfile.c
+index 033b29104..4517aa080 100644
+--- a/hostfile.c
++++ b/hostfile.c
+@@ -63,6 +63,14 @@
+ #include "hmac.h"
+ #include "sshbuf.h"
+
++static int required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
++
++void
++hostfile_set_minimum_rsa_size(int size)
++{
++ required_rsa_size = size;
++}
++
+ /* XXX hmac is too easy to dictionary attack; use bcrypt? */
+
+ static int
+@@ -233,6 +241,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
+ struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
+ struct hostkeys *hostkeys = ctx->hostkeys;
+ struct hostkey_entry *tmp;
++ int r = 0;
+
+ if (l->status == HKF_STATUS_INVALID) {
+ /* XXX make this verbose() in the future */
+@@ -241,6 +250,12 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
+ return 0;
+ }
+
++ if ((r = sshkey_check_rsa_length(l->key, required_rsa_size)) != 0) {
++ debug2_f("%s:%ld: ignoring hostkey: %s",
++ l->path, l->linenum, ssh_err(r));
++ return 0;
++ }
++
+ debug3_f("found %skey type %s in file %s:%lu",
+ l->marker == MRK_NONE ? "" :
+ (l->marker == MRK_CA ? "ca " : "revoked "),
+diff --git a/hostfile.h b/hostfile.h
+index a24a4e329..0e9b1a19a 100644
+--- a/hostfile.h
++++ b/hostfile.h
+@@ -119,5 +119,6 @@ int hostkeys_foreach_file(const char *path, FILE *f,
+ const char *host, const char *ip, u_int options, u_int note);
+
+ void hostfile_create_user_ssh_dir(const char *, int);
++void hostfile_set_minimum_rsa_size(int);
+
+ #endif
+diff --git a/ssh.c b/ssh.c
+index 00c0edefc..0182cc567 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -97,6 +97,7 @@
+ #include "version.h"
+ #include "ssherr.h"
+ #include "utf8.h"
++#include "hostfile.h"
+
+ #ifdef ENABLE_PKCS11
+ #include "ssh-pkcs11.h"
+@@ -1361,6 +1362,7 @@ main(int ac, char **av)
+ options.update_hostkeys = 0;
+ }
+ }
++ hostfile_set_minimum_rsa_size(options.required_rsa_size);
+ if (options.connection_attempts <= 0)
+ fatal("Invalid number of ConnectionAttempts");
+
+--
+2.55.0
+
diff --git a/0045-Provide-better-error-for-non-supported-private-keys.patch b/0045-Provide-better-error-for-non-supported-private-keys.patch
deleted file mode 100644
index 320c71e..0000000
--- a/0045-Provide-better-error-for-non-supported-private-keys.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 1d59bb1f658bab8de931d115959fcb0fe93d27fa Mon Sep 17 00:00:00 2001
-From: Zoltan Fridrich <zfridric@redhat.com>
-Date: Wed, 16 Apr 2025 15:11:59 +0200
-Subject: [PATCH 45/54] Provide better error for non-supported private keys
-
-Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
-
-# https://github.com/openssh/openssh-portable/pull/564
----
- sshkey.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/sshkey.c b/sshkey.c
-index 82e55509d..5225eabf2 100644
---- a/sshkey.c
-+++ b/sshkey.c
-@@ -3554,6 +3554,9 @@ translate_libcrypto_error(unsigned long pem_err)
- return SSH_ERR_LIBCRYPTO_ERROR;
- }
- case ERR_LIB_ASN1:
-+#ifdef ERR_LIB_OSSL_DECODER
-+ case ERR_LIB_OSSL_DECODER:
-+#endif
- return SSH_ERR_INVALID_FORMAT;
- }
- return SSH_ERR_LIBCRYPTO_ERROR;
---
-2.53.0
-
diff --git a/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch b/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
deleted file mode 100644
index 2485e95..0000000
--- a/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-From def442146bbfe1116ca51c0b81a2eebc13a86eeb Mon Sep 17 00:00:00 2001
-From: Zoltan Fridrich <zfridric@redhat.com>
-Date: Mon, 5 May 2025 11:52:25 +0200
-Subject: [PATCH 46/54] Ignore bad hostkeys in known_hosts file
-
-Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
-
-# https://github.com/openssh/openssh-portable/pull/567
----
- hostfile.c | 15 +++++++++++++++
- hostfile.h | 1 +
- ssh.c | 2 ++
- 3 files changed, 18 insertions(+)
-
-diff --git a/hostfile.c b/hostfile.c
-index 033b29104..4517aa080 100644
---- a/hostfile.c
-+++ b/hostfile.c
-@@ -63,6 +63,14 @@
- #include "hmac.h"
- #include "sshbuf.h"
-
-+static int required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
-+
-+void
-+hostfile_set_minimum_rsa_size(int size)
-+{
-+ required_rsa_size = size;
-+}
-+
- /* XXX hmac is too easy to dictionary attack; use bcrypt? */
-
- static int
-@@ -233,6 +241,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
- struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
- struct hostkeys *hostkeys = ctx->hostkeys;
- struct hostkey_entry *tmp;
-+ int r = 0;
-
- if (l->status == HKF_STATUS_INVALID) {
- /* XXX make this verbose() in the future */
-@@ -241,6 +250,12 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
- return 0;
- }
-
-+ if ((r = sshkey_check_rsa_length(l->key, required_rsa_size)) != 0) {
-+ debug2_f("%s:%ld: ignoring hostkey: %s",
-+ l->path, l->linenum, ssh_err(r));
-+ return 0;
-+ }
-+
- debug3_f("found %skey type %s in file %s:%lu",
- l->marker == MRK_NONE ? "" :
- (l->marker == MRK_CA ? "ca " : "revoked "),
-diff --git a/hostfile.h b/hostfile.h
-index a24a4e329..0e9b1a19a 100644
---- a/hostfile.h
-+++ b/hostfile.h
-@@ -119,5 +119,6 @@ int hostkeys_foreach_file(const char *path, FILE *f,
- const char *host, const char *ip, u_int options, u_int note);
-
- void hostfile_create_user_ssh_dir(const char *, int);
-+void hostfile_set_minimum_rsa_size(int);
-
- #endif
-diff --git a/ssh.c b/ssh.c
-index e7bdba327..4a17c0a7d 100644
---- a/ssh.c
-+++ b/ssh.c
-@@ -97,6 +97,7 @@
- #include "version.h"
- #include "ssherr.h"
- #include "utf8.h"
-+#include "hostfile.h"
-
- #ifdef ENABLE_PKCS11
- #include "ssh-pkcs11.h"
-@@ -1375,6 +1376,7 @@ main(int ac, char **av)
- options.update_hostkeys = 0;
- }
- }
-+ hostfile_set_minimum_rsa_size(options.required_rsa_size);
- if (options.connection_attempts <= 0)
- fatal("Invalid number of ConnectionAttempts");
-
---
-2.53.0
-
diff --git a/0046-support-authentication-indicators-in-GSSAPI.patch b/0046-support-authentication-indicators-in-GSSAPI.patch
new file mode 100644
index 0000000..01d460b
--- /dev/null
+++ b/0046-support-authentication-indicators-in-GSSAPI.patch
@@ -0,0 +1,458 @@
+From 5348ff4f4c59dac677b8348b1213c6ef1e530e31 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:23:51 +0200
+Subject: [PATCH 46/53] support authentication indicators in GSSAPI
+
+https://github.com/openssh/openssh-portable/pull/500
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ configure.ac | 1 +
+ gss-serv-krb5.c | 89 ++++++++++++++++++++++++++++++++++----
+ gss-serv.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++--
+ servconf.c | 11 +++++
+ servconf.h | 6 ++-
+ ssh-gss.h | 7 +++
+ sshd_config.5 | 46 ++++++++++++++++++++
+ 7 files changed, 258 insertions(+), 14 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 394acc3d8..d6274328b 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -5059,6 +5059,7 @@ AC_ARG_WITH([kerberos5],
+ AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h])
+ AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h])
+ AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h])
++ AC_CHECK_HEADERS([gssapi_ext.h gssapi/gssapi_ext.h])
+
+ AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1],
+ [Define this if you want to use libkafs' AFS support])])
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index 723509409..fc7cabc09 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -43,6 +43,7 @@
+ #include "log.h"
+ #include "misc.h"
+ #include "servconf.h"
++#include "match.h"
+
+ #include "ssh-gss.h"
+
+@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void)
+ return 1;
+ }
+
++/* Check if any of the indicators in the Kerberos ticket match
++ * one of indicators in the list of allowed/denied rules.
++ * In case of the match, apply the decision from the rule.
++ * In case of no indicator from the ticket matching the rule, deny
++ */
++
++static int
++ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
++{
++ int ret;
++ u_int i;
++ *matched = -1;
++
++ /* Check indicators */
++ for (i = 0; client->indicators[i] != NULL; i++) {
++ ret = match_pattern_list(client->indicators[i],
++ options.gss_indicators, 1);
++ /* negative or positive match */
++ if (ret != 0) {
++ *matched = i;
++ return ret;
++ }
++ }
++ /* No rule matched */
++ return 0;
++}
++
+ /* Check if this user is OK to login. This only works with krb5 - other
+ * GSSAPI mechanisms will need their own.
+ * Returns true if the user is OK to log in, otherwise returns 0
+@@ -193,15 +221,15 @@ static int
+ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ {
+ krb5_principal princ;
+- int retval;
++ int retval, matched, success;
+ const char *errmsg;
+ int k5login_exists;
+
+ if (ssh_gssapi_krb5_init() == 0)
+ return 0;
+
+- if ((retval = krb5_parse_name(krb_context, client->exportedname.value,
+- &princ))) {
++ retval = krb5_parse_name(krb_context, client->exportedname.value, &princ);
++ if (retval) {
+ errmsg = krb5_get_error_message(krb_context, retval);
+ logit("krb5_parse_name(): %.100s", errmsg);
+ krb5_free_error_message(krb_context, errmsg);
+@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ if (k5login_exists &&
+ ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) {
+ retval = 1;
+- logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
+- name, (char *)client->displayname.value);
++ errmsg = "krb5_kuserok";
+ } else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
+ name, k5login_exists)) {
+ retval = 1;
+- logit("Authorized to %s, krb5 principal %s "
+- "(ssh_gssapi_krb5_cmdok)",
+- name, (char *)client->displayname.value);
+- } else
++ errmsg = "ssh_gssapi_krb5_cmdok";
++ } else {
+ retval = 0;
++ goto out;
++ }
+
++ /* At this point we are good if no indicators were defined */
++ if (options.gss_indicators == NULL) {
++ retval = 1;
++ goto out;
++ }
++
++ /* At this point we have indicators defined in the configuration,
++ * if clientt did not provide any indicators, we reject */
++ if (!client->indicators) {
++ retval = 0;
++ logit("GSSAPI authentication indicators enforced "
++ "but indicators not provided by the client. "
++ "krb5 principal %s denied",
++ (char *)client->displayname.value);
++ goto out;
++ }
++
++ /* At this point the configuration enforces presence of indicators
++ * check the match */
++ matched = -1;
++ success = ssh_gssapi_check_indicators(client, &matched);
++
++ switch (success) {
++ case 1:
++ logit("Provided indicator %s allowed by the configuration",
++ client->indicators[matched]);
++ retval = 1;
++ break;
++ case -1:
++ logit("Provided indicator %s rejected by the configuration",
++ client->indicators[matched]);
++ retval = 0;
++ break;
++ default:
++ logit("Provided indicators do not match the configuration");
++ retval = 0;
++ break;
++ }
++
++out:
++ if (retval == 1) {
++ logit("Authorized to %s, krb5 principal %s (%s)",
++ name, (char *)client->displayname.value, errmsg);
++ }
+ krb5_free_principal(krb_context, princ);
+ return retval;
+ }
+diff --git a/gss-serv.c b/gss-serv.c
+index 6eb7d2163..237a3c40a 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -55,7 +55,7 @@ extern ServerOptions options;
+
+ static ssh_gssapi_client gssapi_client =
+ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
+- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
+
+ ssh_gssapi_mech gssapi_null_mech =
+ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+@@ -297,6 +297,99 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name)
+ return GSS_S_COMPLETE;
+ }
+
++
++/* Extract authentication indicators from the Kerberos ticket. Authentication
++ * indicators are GSSAPI name attributes for the name "auth-indicators".
++ * Multiple indicators might be present in the ticket.
++ * Each indicator is an utf8 string. */
++
++#define AUTH_INDICATORS_TAG "auth-indicators"
++#define SSH_GSSAPI_MAX_INDICATORS 64
++
++/* Privileged (called from accept_secure_ctx) */
++static OM_uint32
++ssh_gssapi_getindicators(Gssctxt *ctx, gss_name_t gss_name, ssh_gssapi_client *client)
++{
++ gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
++ gss_buffer_desc value = GSS_C_EMPTY_BUFFER;
++ gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER;
++ int is_mechname, authenticated, complete, more;
++ size_t count, i;
++
++ /* always initialize client->indicators */
++ client->indicators = NULL;
++
++ ctx->major = gss_inquire_name(&ctx->minor, gss_name,
++ &is_mechname, NULL, &attrs);
++ if (ctx->major != GSS_S_COMPLETE)
++ return ctx->major;
++
++ if (attrs == GSS_C_NO_BUFFER_SET) {
++ /* no indicators in the ticket */
++ return GSS_S_COMPLETE;
++ }
++
++ /* client->indicators is NULL terminated */
++ count = 0;
++ client->indicators = xcalloc(count + 1, sizeof(char *));
++
++ for (i = 0; i < attrs->count; i++) {
++ authenticated = 0;
++ complete = 0;
++ more = -1;
++
++ /* skip anything but auth-indicators */
++ if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) ||
++ memcmp(AUTH_INDICATORS_TAG, attrs->elements[i].value,
++ sizeof(AUTH_INDICATORS_TAG) - 1) != 0)
++ continue;
++
++ /* retrieve all indicators */
++ while (more != 0) {
++ value.value = NULL;
++ display_value.value = NULL;
++
++ ctx->major = gss_get_name_attribute(&ctx->minor, gss_name,
++ &attrs->elements[i],
++ &authenticated, &complete,
++ &value, &display_value, &more);
++ if (ctx->major != GSS_S_COMPLETE)
++ goto out;
++
++ if (value.value == NULL || !authenticated)
++ continue;
++
++ if (count >= SSH_GSSAPI_MAX_INDICATORS) {
++ logit("ssh_gssapi_getindicators:"
++ " too many indicators, truncating at %d",
++ SSH_GSSAPI_MAX_INDICATORS);
++ goto out;
++ }
++
++ client->indicators[count] = xmalloc(value.length + 1);
++ memcpy(client->indicators[count], value.value, value.length);
++ client->indicators[count][value.length] = '\0';
++ count++;
++
++ /* add NULL terminator */
++ client->indicators = xrecallocarray(client->indicators, count,
++ count + 1, sizeof(char *));
++ }
++ }
++
++out:
++ if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) {
++ for (i = 0; i < count; i++)
++ free(client->indicators[i]);
++ free(client->indicators);
++ client->indicators = NULL;
++ }
++ gss_release_buffer(&ctx->minor, &value);
++ gss_release_buffer(&ctx->minor, &display_value);
++ gss_release_buffer_set(&ctx->minor, &attrs);
++ return ctx->major;
++}
++
+ /* Extract the client details from a given context. This can only reliably
+ * be called once for a context */
+
+@@ -386,6 +479,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ }
+
+ gss_release_buffer(&ctx->minor, &ename);
++ /* Retrieve authentication indicators, if they exist */
++ if ((ctx->major = ssh_gssapi_getindicators(ctx,
++ ctx->client, client))) {
++ ssh_gssapi_error(ctx);
++ return (ctx->major);
++ }
+
+ /* We can't copy this structure, so we just move the pointer to it */
+ client->creds = ctx->client_creds;
+@@ -453,6 +552,7 @@ int
+ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ OM_uint32 lmin;
++ size_t i;
+
+ (void) kex; /* used in privilege separation */
+
+@@ -471,8 +571,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ gss_release_buffer(&lmin, &gssapi_client.displayname);
+ gss_release_buffer(&lmin, &gssapi_client.exportedname);
+ gss_release_cred(&lmin, &gssapi_client.creds);
+- explicit_bzero(&gssapi_client,
+- sizeof(ssh_gssapi_client));
++
++ if (gssapi_client.indicators != NULL) {
++ for (i = 0; gssapi_client.indicators[i] != NULL; i++)
++ free(gssapi_client.indicators[i]);
++ free(gssapi_client.indicators);
++ }
++
++ explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client));
+ return 0;
+ }
+ else
+diff --git a/servconf.c b/servconf.c
+index 846f5d212..d9cccc17c 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -456,6 +456,7 @@ fill_default_server_options(ServerOptions *options)
+ CLEAR_ON_NONE(options->routing_domain);
+ CLEAR_ON_NONE(options->host_key_agent);
+ CLEAR_ON_NONE(options->per_source_penalty_exempt);
++ CLEAR_ON_NONE(options->gss_indicators);
+
+ for (i = 0; i < options->num_host_key_files; i++)
+ CLEAR_ON_NONE(options->host_key_files[i]);
+@@ -1488,6 +1489,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ if (*activep && options->gss_kex_algorithms == NULL)
+ options->gss_kex_algorithms = xstrdup(arg);
+ break;
++
++ case sGSSAPIIndicators:
++ arg = argv_next(&ac, &av);
++ if (!arg || *arg == '\0')
++ fatal("%s line %d: %s missing argument.",
++ filename, linenum, keyword);
++ if (options->gss_indicators == NULL)
++ options->gss_indicators = xstrdup(arg);
++ break;
+ #endif /* GSSAPI */
+
+ case sPasswordAuthentication:
+@@ -4274,6 +4284,7 @@ dump_config(ServerOptions *o)
+ dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
+ dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
+ dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
++ dump_cfg_string(sGSSAPIIndicators, o->gss_indicators);
+ #endif
+ dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index c55b64c73..81e699655 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -326,7 +326,8 @@ SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1
+ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH)
++SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -336,7 +337,8 @@ SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_G
+ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL)
++SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL)
+ #endif /* GSSAPI */
+
+ #define SSHD_CONFIG_ENTRIES \
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 329dc9da0..1506719a9 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -34,6 +34,12 @@
+ #include <gssapi/gssapi.h>
+ #endif
+
++#ifdef HAVE_GSSAPI_EXT_H
++#include <gssapi_ext.h>
++#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H)
++#include <gssapi/gssapi_ext.h>
++#endif
++
+ #ifdef KRB5
+ # ifndef HEIMDAL
+ # ifdef HAVE_GSSAPI_GENERIC_H
+@@ -112,6 +118,7 @@ typedef struct {
+ ssh_gssapi_ccache store;
+ int used;
+ int updated;
++ char **indicators; /* auth indicators */
+ } ssh_gssapi_client;
+
+ typedef struct ssh_gssapi_mech_struct {
+diff --git a/sshd_config.5 b/sshd_config.5
+index b72757895..2a8f3abea 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -811,6 +811,52 @@ This option only applies to connections using GSSAPI.
+ .Pp
+ The list of supported key exchange algorithms may also be obtained using
+ .Qq ssh -Q GSSAPIKexAlgorithms .
++.It Cm GSSAPIIndicators
++Specifies whether to accept or deny GSSAPI authenticated access if Kerberos
++mechanism is used and Kerberos ticket contains a particular set of
++authentication indicators. The values can be specified as a comma-separated list
++.Cm [!]name1,[!]name2,... .
++When indicator's name is prefixed with !, the authentication indicator 'name'
++will deny access to the system. Otherwise, one of non-negated authentication
++indicators must be present in the Kerberos ticket to allow access. If
++.Cm GSSAPIIndicators
++is defined, a Kerberos ticket that has indicators but does not match the
++policy will get denial. If at least one indicator is configured, whether for
++access or denial, tickets without authentication indicators will be explicitly
++rejected.
++.Pp
++By default systems using MIT Kerberos 1.17 or later will not assign any
++indicators. SPAKE and PKINIT methods add authentication indicators
++to all successful authentications. The SPAKE pre-authentication method is
++preferred over an encrypted timestamp pre-authentication when passwords used to
++authenticate user principals. Kerberos KDCs built with Heimdal Kerberos
++(including Samba AD DC built with Heimdal) do not add authentication
++indicators. However, OpenSSH built against Heimdal Kerberos library is able to
++inquire authentication indicators and thus can be used to check for their presence.
++.Pp
++Indicator name is case-sensitive and depends on the configuration of a
++particular Kerberos deployment. Indicators available in MIT Kerberos and
++FreeIPA environments:
++.Pp
++.Bl -tag -width XXXX -offset indent -compact
++.It Cm hardened
++SPAKE or encrypted timestamp pre-authentication mechanisms in MIT Kerberos and FreeIPA
++.It Cm pkinit
++smartcard or PKCS11 token-based pre-authentication in MIT Kerberos and FreeIPA
++.It Cm radius
++pre-authentication based on a RADIUS server in MIT Kerberos and FreeIPA
++.It Cm otp
++TOTP/HOTP-based two-factor pre-authentication in FreeIPA
++.It Cm idp
++OAuth2-based pre-authentication in FreeIPA using an external identity provider
++and device authorization grant flow
++.It Cm passkey
++FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
++.El
++.Pp
++The default
++.Dq none
++is to not use GSSAPI authentication indicators for access decisions.
+ .It Cm HostbasedAcceptedAlgorithms
+ The default is handled system-wide by
+ .Xr crypto-policies 7 .
+--
+2.55.0
+
diff --git a/0047-NIST-curves-hybrid-KEX-implementation.patch b/0047-NIST-curves-hybrid-KEX-implementation.patch
new file mode 100644
index 0000000..6885241
--- /dev/null
+++ b/0047-NIST-curves-hybrid-KEX-implementation.patch
@@ -0,0 +1,1353 @@
+From 111be4b2ba5e0e2549d21d1ed4c53fe9af90827a Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 20 Oct 2025 16:07:31 +0200
+Subject: [PATCH 47/53] NIST curves hybrid KEX implementation
+
+---
+ compat.c | 23 +-
+ compat.h | 1 +
+ crypto_api.h | 5 +
+ kex-names.c | 91 ++++-
+ kex.c | 1 +
+ kex.h | 20 +-
+ kexgen.c | 67 ++-
+ kexmlkem768x25519.c | 680 +++++++++++++++++++++++++++++--
+ monitor.c | 2 +
+ myproposal.h | 4 +
+ regress/unittests/kex/test_kex.c | 4 +
+ ssh-keyscan.c | 3 +-
+ ssh_api.c | 6 +-
+ sshconnect2.c | 3 +-
+ sshd-auth.c | 2 +
+ 15 files changed, 852 insertions(+), 60 deletions(-)
+
+diff --git a/compat.c b/compat.c
+index 4312510e5..e6c7935d4 100644
+--- a/compat.c
++++ b/compat.c
+@@ -36,6 +36,7 @@
+ #include "compat.h"
+ #include "log.h"
+ #include "match.h"
++#include <openssl/fips.h>
+
+ /* determine bug flags from SSH protocol banner */
+ void
+@@ -148,8 +149,9 @@ char *
+ compat_kex_proposal(struct ssh *ssh, const char *p)
+ {
+ char *cp = NULL, *cp2 = NULL;
++ int mlkem_available = is_mlkem768_available();
+
+- if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0)
++ if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0 && mlkem_available == 2)
+ return xstrdup(p);
+ debug2_f("original KEX proposal: %s", p);
+ if ((ssh->compat & SSH_BUG_CURVE25519PAD) != 0)
+@@ -164,6 +166,25 @@ compat_kex_proposal(struct ssh *ssh, const char *p)
+ free(cp);
+ cp = cp2;
+ }
++ if (mlkem_available == 2)
++ return cp ? cp : xstrdup(p);
++ if (mlkem_available == 1 && FIPS_mode()) {
++ if ((cp2 = match_filter_denylist(cp ? cp : p,
++ "mlkem768x25519-sha256")) == NULL)
++ fatal("match_filter_denylist failed");
++ free(cp);
++ cp = cp2;
++ }
++ if (mlkem_available == 0) {
++ if ((cp2 = match_filter_denylist(cp ? cp : p,
++ "mlkem768x25519-sha256,"
++ "mlkem768nistp256-sha256,"
++ "mlkem1024nistp384-sha384")) == NULL)
++ fatal("match_filter_denylist failed");
++ free(cp);
++ cp = cp2;
++ }
++
+ if (cp == NULL || *cp == '\0')
+ fatal("No supported key exchange algorithms found");
+ debug2_f("compat KEX proposal: %s", cp);
+diff --git a/compat.h b/compat.h
+index 2e6db5bf9..b78e55b69 100644
+--- a/compat.h
++++ b/compat.h
+@@ -62,4 +62,5 @@ struct ssh;
+
+ void compat_banner(struct ssh *, const char *);
+ char *compat_kex_proposal(struct ssh *, const char *);
++int is_mlkem768_available(void);
+ #endif
+diff --git a/crypto_api.h b/crypto_api.h
+index f8441ed6f..60ea24a3e 100644
+--- a/crypto_api.h
++++ b/crypto_api.h
+@@ -203,6 +203,11 @@ int crypto_sign_mldsa44_ed25519_verify(const uint8_t sig[MLDSA44_ED25519_SIG_SZ]
+ const uint8_t *ctx, size_t ctxlen,
+ const uint8_t pk[MLDSA44_ED25519_PK_SZ]);
+
++/* ML-KEM-1024 */
++#define crypto_kem_mlkem1024_PUBLICKEYBYTES 1568
++#define crypto_kem_mlkem1024_SECRETKEYBYTES 3168
++#define crypto_kem_mlkem1024_CIPHERTEXTBYTES 1568
++
+ /* Utility */
+ void sha3_256(uint8_t digest[32], const uint8_t *data, size_t len);
+ void sha3_512(uint8_t digest[64], const uint8_t *data, size_t len);
+diff --git a/kex-names.c b/kex-names.c
+index cdd5db90e..f9c51429a 100644
+--- a/kex-names.c
++++ b/kex-names.c
+@@ -34,6 +34,7 @@
+ #include <openssl/crypto.h>
+ #include <openssl/fips.h>
+ #include <openssl/evp.h>
++#include <openssl/err.h>
+ #endif
+
+ #include "kex.h"
+@@ -73,8 +74,6 @@ static const struct kexalg kexalgs[] = {
+ SSH_DIGEST_SHA384, KEX_NOT_PQ },
+ { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
+ SSH_DIGEST_SHA512, KEX_NOT_PQ },
+- { KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768ECDH_SHA256,
+- NID_X9_62_prime256v1, SSH_DIGEST_SHA256, KEX_IS_PQ },
+ #endif /* WITH_OPENSSL */
+ #if defined(HAVE_EVP_SHA256) || !defined(WITH_OPENSSL)
+ { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
+@@ -88,6 +87,10 @@ static const struct kexalg kexalgs[] = {
+ #ifdef USE_MLKEM768X25519
+ { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
+ SSH_DIGEST_SHA256, KEX_IS_PQ },
++ { KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768NISTP256_SHA256, 0,
++ SSH_DIGEST_SHA256, KEX_IS_PQ },
++ { KEX_MLKEM1024NISTP384_SHA384, KEX_KEM_MLKEM1024NISTP384_SHA384, 0,
++ SSH_DIGEST_SHA384, KEX_IS_PQ },
+ #endif
+ #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
+ { NULL, 0, -1, -1, 0 },
+@@ -106,14 +109,27 @@ static const struct kexalg gss_kexalgs[] = {
+ { NULL, 0, -1, -1, 0},
+ };
+
+-static int is_mlkem768_available()
++/*
++ * 0 - unavailable
++ * 1 - available in non-FIPS mode
++ * 2 - available always
++ */
++int is_mlkem768_available()
+ {
+ static int is_fetched = -1;
+
+ if (is_fetched == -1) {
+- EVP_KEM *mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
+- is_fetched = mlkem768 != NULL ? 1 : 0;
++ EVP_KEM *mlkem768 = NULL;
++
++ ERR_set_mark();
++ mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++ is_fetched = (mlkem768 == NULL) ? 0 : 2;
++ if (is_fetched == 0 && FIPS_mode() == 1) {
++ mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", "provider=default,-fips");
++ is_fetched = (mlkem768 == NULL) ? 0 : 1;
++ }
+ EVP_KEM_free(mlkem768);
++ ERR_pop_to_mark();
+ }
+
+ return is_fetched;
+@@ -125,11 +141,32 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
+ char *ret = NULL;
+ const struct kexalg *k;
+ char sep_str[2] = {sep, '\0'};
++ int x25519mlkem_available = 0, nistmlkem_available = 0;
+
+- for (k = kexalgs; k->name != NULL; k++) {
+- if (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0
+- && !is_mlkem768_available())
++ /*
++ * FIPS provider can provide ML-KEMs and then all hybrids are available
++ * Otherwise only NIST hybrids are available
++ * */
++ if (FIPS_mode()) {
++ if (is_mlkem768_available() == 2) {
++ x25519mlkem_available = 1;
++ nistmlkem_available = 1;
++ } else if (is_mlkem768_available() == 1) {
++ nistmlkem_available = 1;
++ }
++ } else {
++ if (is_mlkem768_available() > 0) {
++ x25519mlkem_available = 1;
++ nistmlkem_available = 1;
++ }
++ }
++
++ for (k = algs; k->name != NULL; k++) {
++ if ( (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0 && x25519mlkem_available == 0)
++ || (strcmp(k->name, KEX_MLKEM768NISTP256_SHA256) == 0 && nistmlkem_available == 0)
++ || (strcmp(k->name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
+ continue;
++
+ xextendf(&ret, sep_str, "%s", k->name);
+ }
+
+@@ -152,10 +189,30 @@ static const struct kexalg *
+ kex_alg_by_name(const char *name)
+ {
+ const struct kexalg *k;
++ int x25519mlkem_available = 0, nistmlkem_available = 0;
+
+- if (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0
+- && !is_mlkem768_available())
+- return NULL;
++ /*
++ * FIPS provider can provide ML-KEMs and then all hybrids are available
++ * Otherwise only NIST hybrids are available
++ * */
++ if (FIPS_mode()) {
++ if (is_mlkem768_available() == 2) {
++ x25519mlkem_available = 1;
++ nistmlkem_available = 1;
++ } else if (is_mlkem768_available() == 1) {
++ nistmlkem_available = 1;
++ }
++ } else {
++ if (is_mlkem768_available() > 0) {
++ x25519mlkem_available = 1;
++ nistmlkem_available = 1;
++ }
++ }
++
++ if ( (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0 && x25519mlkem_available == 0)
++ || (strcmp(name, KEX_MLKEM768NISTP256_SHA256) == 0 && nistmlkem_available == 0)
++ || (strcmp(name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
++ return NULL;
+
+ for (k = kexalgs; k->name != NULL; k++) {
+ if (strcmp(k->name, name) == 0)
+@@ -233,8 +290,16 @@ kex_names_valid(const char *names)
+ return 0;
+ }
+ if (kex_alg_by_name(p) == NULL) {
+- if (FIPS_mode())
+- error("\"%.100s\" is not allowed in FIPS mode", p);
++ if (FIPS_mode()) {
++ if ((strcmp(p, KEX_MLKEM768X25519_SHA256) == 0)
++ || (strcmp(p, KEX_MLKEM768NISTP256_SHA256) == 0)
++ || (strcmp(p, KEX_MLKEM1024NISTP384_SHA384) == 0)) {
++ debug("\"%.100s\" is not allowed in FIPS mode", p);
++ continue;
++ }
++ else
++ error("\"%.100s\" is not allowed in FIPS mode", p);
++ }
+ else
+ error("Unsupported KEX algorithm \"%.100s\"", p);
+ free(s);
+diff --git a/kex.c b/kex.c
+index 62cfa86cd..1e0c29b91 100644
+--- a/kex.c
++++ b/kex.c
+@@ -760,6 +760,7 @@ kex_free(struct kex *kex)
+ #ifdef WITH_OPENSSL
+ DH_free(kex->dh);
+ EC_KEY_free(kex->ec_client_key);
++ EVP_PKEY_free(kex->ec_hybrid_client_key);
+ #endif /* WITH_OPENSSL */
+ for (mode = 0; mode < MODE_MAX; mode++) {
+ kex_free_newkeys(kex->newkeys[mode]);
+diff --git a/kex.h b/kex.h
+index 1fe4bb29b..4e1b2875e 100644
+--- a/kex.h
++++ b/kex.h
+@@ -65,7 +65,8 @@
+ #define KEX_SNTRUP761X25519_SHA512 "sntrup761x25519-sha512"
+ #define KEX_SNTRUP761X25519_SHA512_OLD "sntrup761x25519-sha512@openssh.com"
+ #define KEX_MLKEM768X25519_SHA256 "mlkem768x25519-sha256"
+-#define KEX_MLKEM768NISTP256_SHA256 "mlkem768nistp256-sha256"
++#define KEX_MLKEM768NISTP256_SHA256 "mlkem768nistp256-sha256"
++#define KEX_MLKEM1024NISTP384_SHA384 "mlkem1024nistp384-sha384"
+
+ #define COMP_NONE 0
+ #define COMP_DELAYED 2
+@@ -104,7 +105,8 @@ enum kex_exchange {
+ KEX_C25519_SHA256,
+ KEX_KEM_SNTRUP761X25519_SHA512,
+ KEX_KEM_MLKEM768X25519_SHA256,
+- KEX_KEM_MLKEM768ECDH_SHA256,
++ KEX_KEM_MLKEM768NISTP256_SHA256,
++ KEX_KEM_MLKEM1024NISTP384_SHA384,
+ #ifdef GSSAPI
+ KEX_GSS_GRP1_SHA1,
+ KEX_GSS_GRP14_SHA1,
+@@ -207,6 +209,9 @@ struct kex {
+ u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */
+ u_char mlkem768_client_key[crypto_kem_mlkem768_SECRETKEYBYTES]; /* KEM */
+ struct sshbuf *client_pub;
++ /* FIXME */
++ EVP_PKEY *ec_hybrid_client_key; /* NIST hybrids */
++ u_char mlkem1024_client_key[crypto_kem_mlkem1024_SECRETKEYBYTES]; /* ML-KEM 1024 + NIST */
+ };
+
+ int kex_name_valid(const char *);
+@@ -297,6 +302,17 @@ int kex_kem_mlkem768ecdh_enc(struct kex *, const struct sshbuf *,
+ struct sshbuf **, struct sshbuf **);
+ int kex_kem_mlkem768ecdh_dec(struct kex *, const struct sshbuf *,
+ struct sshbuf **);
++int kex_kem_mlkem768nistp256_keypair(struct kex *);
++int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
++ struct sshbuf **, struct sshbuf **);
++int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
++ struct sshbuf **);
++
++int kex_kem_mlkem1024nistp384_keypair(struct kex *);
++int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
++ struct sshbuf **, struct sshbuf **);
++int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
++ struct sshbuf **);
+
+ int kex_dh_keygen(struct kex *);
+ int kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *);
+diff --git a/kexgen.c b/kexgen.c
+index 834073ef1..79ddb5925 100644
+--- a/kexgen.c
++++ b/kexgen.c
+@@ -114,9 +114,6 @@ kex_gen_client(struct ssh *ssh)
+ case KEX_ECDH_SHA2:
+ r = kex_ecdh_keypair(kex);
+ break;
+- case KEX_KEM_MLKEM768ECDH_SHA256:
+- r = kex_kem_mlkem768ecdh_keypair(kex);
+- break;
+ #endif /* WITH_OPENSSL */
+ case KEX_C25519_SHA256:
+ if (FIPS_mode()) {
+@@ -136,12 +133,24 @@ kex_gen_client(struct ssh *ssh)
+ break;
+ case KEX_KEM_MLKEM768X25519_SHA256:
+ if (FIPS_mode()) {
+- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+- r = SSH_ERR_INVALID_ARGUMENT;
++ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++ if (mlkem == NULL) {
++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++ r = SSH_ERR_INVALID_ARGUMENT;
++ } else {
++ EVP_KEM_free(mlkem);
++ r = kex_kem_mlkem768x25519_keypair(kex);
++ }
+ } else {
+ r = kex_kem_mlkem768x25519_keypair(kex);
+ }
+ break;
++ case KEX_KEM_MLKEM768NISTP256_SHA256:
++ r = kex_kem_mlkem768nistp256_keypair(kex);
++ break;
++ case KEX_KEM_MLKEM1024NISTP384_SHA384:
++ r = kex_kem_mlkem1024nistp384_keypair(kex);
++ break;
+ default:
+ r = SSH_ERR_INVALID_ARGUMENT;
+ break;
+@@ -206,10 +215,6 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
+ case KEX_ECDH_SHA2:
+ r = kex_ecdh_dec(kex, server_blob, &shared_secret);
+ break;
+- case KEX_KEM_MLKEM768ECDH_SHA256:
+- r = kex_kem_mlkem768ecdh_dec(kex, server_blob,
+- &shared_secret);
+- break;
+ #endif /* WITH_OPENSSL */
+ case KEX_C25519_SHA256:
+ if (FIPS_mode()) {
+@@ -230,13 +235,28 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
+ break;
+ case KEX_KEM_MLKEM768X25519_SHA256:
+ if (FIPS_mode()) {
+- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+- r = SSH_ERR_INVALID_ARGUMENT;
++ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++ if (mlkem == NULL) {
++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++ r = SSH_ERR_INVALID_ARGUMENT;
++ } else {
++ EVP_KEM_free(mlkem);
++ r = kex_kem_mlkem768x25519_dec(kex, server_blob,
++ &shared_secret);
++ }
+ } else {
+ r = kex_kem_mlkem768x25519_dec(kex, server_blob,
+ &shared_secret);
+ }
+ break;
++ case KEX_KEM_MLKEM768NISTP256_SHA256:
++ r = kex_kem_mlkem768nistp256_dec(kex, server_blob,
++ &shared_secret);
++ break;
++ case KEX_KEM_MLKEM1024NISTP384_SHA384:
++ r = kex_kem_mlkem1024nistp384_dec(kex, server_blob,
++ &shared_secret);
++ break;
+ default:
+ r = SSH_ERR_INVALID_ARGUMENT;
+ break;
+@@ -290,6 +310,8 @@ out:
+ sizeof(kex->sntrup761_client_key));
+ explicit_bzero(kex->mlkem768_client_key,
+ sizeof(kex->mlkem768_client_key));
++ explicit_bzero(kex->mlkem1024_client_key,
++ sizeof(kex->mlkem1024_client_key));
+ sshbuf_free(server_host_key_blob);
+ free(signature);
+ sshbuf_free(tmp);
+@@ -348,10 +370,6 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
+ r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey,
+ &shared_secret);
+ break;
+- case KEX_KEM_MLKEM768ECDH_SHA256:
+- r = kex_kem_mlkem768ecdh_enc(kex, client_pubkey,
+- &server_pubkey, &shared_secret);
+- break;
+ #endif /* WITH_OPENSSL */
+ case KEX_C25519_SHA256:
+ if (FIPS_mode()) {
+@@ -373,13 +391,28 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
+ break;
+ case KEX_KEM_MLKEM768X25519_SHA256:
+ if (FIPS_mode()) {
+- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+- r = SSH_ERR_INVALID_ARGUMENT;
++ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++ if (mlkem == NULL) {
++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++ r = SSH_ERR_INVALID_ARGUMENT;
++ } else {
++ EVP_KEM_free(mlkem);
++ r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
++ &server_pubkey, &shared_secret);
++ }
+ } else {
+ r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
+ &server_pubkey, &shared_secret);
+ }
+ break;
++ case KEX_KEM_MLKEM768NISTP256_SHA256:
++ r = kex_kem_mlkem768nistp256_enc(kex, client_pubkey,
++ &server_pubkey, &shared_secret);
++ break;
++ case KEX_KEM_MLKEM1024NISTP384_SHA384:
++ r = kex_kem_mlkem1024nistp384_enc(kex, client_pubkey,
++ &server_pubkey, &shared_secret);
++ break;
+ default:
+ r = SSH_ERR_INVALID_ARGUMENT;
+ break;
+diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
+index 36658120f..03efb788f 100644
+--- a/kexmlkem768x25519.c
++++ b/kexmlkem768x25519.c
+@@ -45,19 +45,34 @@
+
+ #ifdef USE_MLKEM768X25519
+
++#include "libcrux_internal.h"
++#include <openssl/bn.h>
++#include <openssl/ec.h>
+ #include <openssl/err.h>
+ #include <openssl/evp.h>
++#include <openssl/fips.h>
+ #include <stdio.h>
+
++#define FIPS_FALLBACK_PROPQ "provider=default,-fips"
++
+ static int
+-mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++mlkem_keypair_gen(const char *algname, unsigned char *pubkeybuf, size_t pubkey_size,
++ unsigned char *privkeybuf, size_t privkey_size)
+ {
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+ int ret = SSH_ERR_INTERNAL_ERROR;
+- size_t pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES;
++ size_t got_pub_size = pubkey_size, got_priv_size = privkey_size;
++
++ ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, NULL);
++
++ if (ctx == NULL && FIPS_mode()) {
++ /* We have filtered x25519 + ML-KEM in FIPS mode earlier
++ * so if we are in FIPS mode and ML-KEM is not available with default propq,
++ * we can fetch it from the default provider */
++ ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, FIPS_FALLBACK_PROPQ);
++ }
+
+- ctx = EVP_PKEY_CTX_new_from_name(NULL, "mlkem768", NULL);
+ if (ctx == NULL) {
+ ret = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+@@ -69,17 +84,23 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
+ goto err;
+ }
+
+- if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &pubkey_size) <= 0
+- || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &privkey_size) <= 0) {
++ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &got_pub_size) <= 0
++ || EVP_PKEY_get_raw_private_key(pkey, NULL, &got_priv_size) <= 0) {
+ ret = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+ }
+
+- if (privkey_size != crypto_kem_mlkem768_SECRETKEYBYTES
+- || pubkey_size != crypto_kem_mlkem768_PUBLICKEYBYTES) {
++ if (privkey_size != got_priv_size || pubkey_size != got_pub_size) {
+ ret = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+ }
++
++ if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &got_pub_size) <= 0
++ || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &got_priv_size) <= 0) {
++ ret = SSH_ERR_LIBCRYPTO_ERROR;
++ goto err;
++ }
++
+ ret = 0;
+
+ err:
+@@ -91,27 +112,57 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
+ }
+
+ static int
+-mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++{
++ return mlkem_keypair_gen("mlkem768", pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES,
++ privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
++}
++
++static int
++mlkem1024_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++{
++ return mlkem_keypair_gen("mlkem1024", pubkeybuf, crypto_kem_mlkem1024_PUBLICKEYBYTES,
++ privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES);
++}
++
++static int
++mlkem_encap_secret(const char *mlkem_alg, const u_char *pubkeybuf, u_char *secret, u_char *out)
+ {
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ int r = SSH_ERR_INTERNAL_ERROR;
+- size_t outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
+- secretlen = crypto_kem_mlkem768_BYTES;
++ size_t outlen, expected_outlen, publen, secretlen = crypto_kem_mlkem768_BYTES;
++ int fips_fallback = 0;
++
++ if (strcmp(mlkem_alg, "mlkem768") == 0) {
++ outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES;
++ publen = crypto_kem_mlkem768_PUBLICKEYBYTES;
++ } else if (strcmp(mlkem_alg, "mlkem1024") == 0) {
++ outlen = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
++ publen = crypto_kem_mlkem1024_PUBLICKEYBYTES;
++ } else
++ return r;
+
+- pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "mlkem768", NULL,
+- pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES);
++ expected_outlen = outlen;
++
++ pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, NULL,
++ pubkeybuf, publen);
++ if (pkey == NULL && FIPS_mode()) {
++ pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, FIPS_FALLBACK_PROPQ,
++ pubkeybuf, publen);
++ fips_fallback = 1;
++ }
+ if (pkey == NULL) {
+ r = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+ }
+
+- ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
++ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
+ if (ctx == NULL
+ || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
+- || EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0
++ || EVP_PKEY_encapsulate(ctx, out, &expected_outlen, secret, &secretlen) <= 0
+ || secretlen != crypto_kem_mlkem768_BYTES
+- || outlen != crypto_kem_mlkem768_CIPHERTEXTBYTES) {
++ || outlen != expected_outlen) {
+ r = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+ }
+@@ -127,25 +178,45 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
+ }
+
+ static int
+-mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++{
++ return mlkem_encap_secret("mlkem768", pubkeybuf, secret, out);
++}
++
++static int
++mlkem1024_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++{
++ return mlkem_encap_secret("mlkem1024", pubkeybuf, secret, out);
++}
++
++static int
++mlkem_decap_secret(const char *algname,
++ const u_char *privkeybuf, size_t privkey_len,
++ const u_char *wrapped, size_t wrapped_len,
++ u_char *secret)
+ {
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ int r = SSH_ERR_INTERNAL_ERROR;
+- size_t wrappedlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
+- secretlen = crypto_kem_mlkem768_BYTES;
++ size_t secretlen = crypto_kem_mlkem768_BYTES;
++ int fips_fallback = 0;
+
+- pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "mlkem768", NULL,
+- privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
++ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
++ NULL, privkeybuf, privkey_len);
++ if (pkey == NULL && FIPS_mode()) {
++ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
++ FIPS_FALLBACK_PROPQ, privkeybuf, privkey_len);
++ fips_fallback = 1;
++ }
+ if (pkey == NULL) {
+ r = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+ }
+
+- ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
++ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
+ if (ctx == NULL
+ || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
+- || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrappedlen) <= 0
++ || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrapped_len) <= 0
+ || secretlen != crypto_kem_mlkem768_BYTES) {
+ r = SSH_ERR_LIBCRYPTO_ERROR;
+ goto err;
+@@ -162,6 +233,20 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s
+ return r;
+ }
+
++static int
++mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++{
++ return mlkem_decap_secret("mlkem768", privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES,
++ wrapped, crypto_kem_mlkem768_CIPHERTEXTBYTES, secret);
++}
++
++static int
++mlkem1024_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++{
++ return mlkem_decap_secret("mlkem1024", privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES,
++ wrapped, crypto_kem_mlkem1024_CIPHERTEXTBYTES, secret);
++}
++
+ int
+ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+ {
+@@ -327,7 +412,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+ u_char hash[SSH_DIGEST_MAX_LENGTH];
+ size_t need;
+ int r = SSH_ERR_INTERNAL_ERROR;
+- struct libcrux_mlkem768_enc_result enc; /* FIXME */
++ libcrux_mlkem768_enc_result enc;
+
+ *server_blobp = NULL;
+ *shared_secretp = NULL;
+@@ -529,6 +614,520 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ return r;
+ #endif
+ }
++
++#define NIST_P256_COMPRESSED_LEN 33
++#define NIST_P256_UNCOMPRESSED_LEN 65
++#define NIST_P384_COMPRESSED_LEN 49
++#define NIST_P384_UNCOMPRESSED_LEN 97
++#define NIST_BUF_MAX_SIZE NIST_P384_UNCOMPRESSED_LEN
++
++static const char ec256[] = "P-256";
++static const char ec384[] = "P-384";
++static const char *len2curve_name(size_t len)
++{
++ switch (len) {
++ case NIST_P256_COMPRESSED_LEN:
++ case NIST_P256_UNCOMPRESSED_LEN:
++ return ec256;
++ break;
++ case NIST_P384_COMPRESSED_LEN:
++ case NIST_P384_UNCOMPRESSED_LEN:
++ return ec384;
++ break;
++ }
++ return NULL;
++}
++
++static EVP_PKEY *
++buf2nist_key(const unsigned char *pub_key_buf, size_t pub_key_len)
++{
++ EVP_PKEY *pkey = NULL;
++ EVP_PKEY_CTX *ctx = NULL;
++ OSSL_PARAM params[3];
++ const char *curve_name = len2curve_name(pub_key_len);
++
++ if (curve_name == NULL)
++ return NULL;
++
++ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
++ if (!ctx)
++ goto err;
++
++ if (EVP_PKEY_fromdata_init(ctx) <= 0)
++ goto err;
++
++ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
++ params[1] = OSSL_PARAM_construct_octet_string(
++ OSSL_PKEY_PARAM_PUB_KEY, (void *)pub_key_buf, pub_key_len);
++ params[2] = OSSL_PARAM_construct_end();
++
++ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
++ goto err;
++
++ EVP_PKEY_CTX_free(ctx);
++ return pkey;
++
++err:
++ EVP_PKEY_CTX_free(ctx);
++ EVP_PKEY_free(pkey);
++ return NULL;
++}
++
++static int
++kex_nist_shared_key_ext(EVP_PKEY *priv_key,
++ const u_char *pub_key_buf, size_t pub_key_len, struct sshbuf *out)
++{
++ EVP_PKEY_CTX *ctx = NULL;
++ unsigned char *shared_secret = NULL;
++ size_t shared_secret_len = 0;
++ EVP_PKEY *peer_key = buf2nist_key(pub_key_buf, pub_key_len);
++ int r = SSH_ERR_INTERNAL_ERROR;
++
++ if (peer_key == NULL)
++ return SSH_ERR_KEY_LENGTH;
++
++ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, priv_key, NULL);
++ if (!ctx)
++ goto end;
++
++ if ((EVP_PKEY_derive_init(ctx) <= 0)
++ || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0
++ || EVP_PKEY_derive(ctx, NULL, &shared_secret_len) <= 0)
++ goto end;
++
++ shared_secret = OPENSSL_malloc(shared_secret_len);
++ if (shared_secret == NULL)
++ goto end;
++
++ if (EVP_PKEY_derive(ctx, shared_secret, &shared_secret_len) <= 0)
++ goto end;
++
++ if ((r = sshbuf_put(out, shared_secret, shared_secret_len)) != 0)
++ goto end;
++
++ r = 0;
++
++end:
++ EVP_PKEY_free(peer_key);
++ if (shared_secret)
++ OPENSSL_clear_free(shared_secret, shared_secret_len);
++ EVP_PKEY_CTX_free(ctx);
++
++ return r;
++}
++
++static EVP_PKEY *
++nist_pkey_keygen(size_t pub_key_len)
++{
++ const char *curve_name = len2curve_name(pub_key_len);
++ EVP_PKEY_CTX *pctx = NULL;
++ EVP_PKEY *pkey = NULL;
++ OSSL_PARAM params[2];
++
++ if (curve_name == NULL)
++ return NULL;
++
++ pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
++ if (!pctx)
++ return NULL;
++
++ if (EVP_PKEY_keygen_init(pctx) <= 0) {
++ EVP_PKEY_CTX_free(pctx);
++ return NULL;
++ }
++
++ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
++ params[1] = OSSL_PARAM_construct_end();
++
++ if (EVP_PKEY_CTX_set_params(pctx, params) <= 0
++ || EVP_PKEY_keygen(pctx, &pkey) <= 0) {
++ EVP_PKEY_CTX_free(pctx);
++ return NULL;
++ }
++
++ EVP_PKEY_CTX_free(pctx);
++ return pkey;
++}
++
++static size_t decompress_pub_key(void *pub, size_t compressed_len, size_t decompressed_len)
++{
++ EC_GROUP *group = NULL;
++ EC_POINT *point = NULL;
++ BN_CTX *ctx = NULL;
++ size_t len = 0;
++ int group_nid = NID_undef;
++
++ switch (compressed_len) {
++ case NIST_P256_COMPRESSED_LEN:
++ group_nid = NID_X9_62_prime256v1;
++ break;
++ case NIST_P384_COMPRESSED_LEN:
++ group_nid = NID_secp384r1;
++ break;
++ default:
++ return 0;
++ break;
++ }
++
++ ctx = BN_CTX_new();
++ group = EC_GROUP_new_by_curve_name(group_nid);
++ if (ctx == NULL || group == NULL)
++ goto err;
++
++ point = EC_POINT_new(group);
++ if (point == NULL)
++ goto err;
++
++ if (!EC_POINT_oct2point(group, point, pub, compressed_len, ctx))
++ goto err;
++
++ len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pub, decompressed_len, ctx);
++
++err:
++ EC_POINT_free(point);
++ EC_GROUP_free(group);
++ BN_CTX_free(ctx);
++
++ return len;
++}
++
++static int
++get_uncompressed_ec_pubkey(EVP_PKEY *pkey, unsigned char *buf, size_t buf_len)
++{
++ OSSL_PARAM params[2];
++ size_t required_len = 0, out_len = 0;
++
++ params[0] = OSSL_PARAM_construct_utf8_string(
++ OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT,
++ "uncompressed", 0);
++ params[1] = OSSL_PARAM_construct_end();
++
++ if (EVP_PKEY_set_params(pkey, params) <= 0
++ || EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
++ buf, buf_len, &required_len) <= 0) {
++ return SSH_ERR_LIBCRYPTO_ERROR;
++ }
++
++ if (required_len != buf_len) {
++ /* Red Hat certified FIPS provider ignores OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT
++ * We may have to perform the conversion manually */
++ if (len2curve_name(required_len) == len2curve_name(buf_len)) {
++ out_len = decompress_pub_key(buf, required_len, buf_len);
++ if (out_len != buf_len) {
++ debug_f("Error decompressing the compressed public key");
++ return SSH_ERR_LIBCRYPTO_ERROR;
++ } else {
++ return 0;
++ }
++ } else {
++ debug_f("Unexpected length of uncompressed public key: expected %d, got %d", buf_len, required_len);
++ return SSH_ERR_LIBCRYPTO_ERROR;
++ }
++ }
++
++ return 0;
++}
++/* nist_bytes_len should always be uncompressed */
++static int
++kex_kem_mlkem_nist_keypair(struct kex *kex, size_t mlkem_bytes_len, size_t nist_bytes_len)
++{
++ struct sshbuf *buf = NULL;
++ u_char *cp = NULL;
++ size_t need;
++ int r = SSH_ERR_INTERNAL_ERROR;
++ u_char *client_key = NULL;
++
++ if ((buf = sshbuf_new()) == NULL)
++ return SSH_ERR_ALLOC_FAIL;
++ need = mlkem_bytes_len + nist_bytes_len;
++ if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
++ goto out;
++
++ if (mlkem_bytes_len == crypto_kem_mlkem768_PUBLICKEYBYTES) {
++ client_key = kex->mlkem768_client_key;
++ r = mlkem768_keypair_gen(cp, client_key);
++ }
++
++ if (mlkem_bytes_len == crypto_kem_mlkem1024_PUBLICKEYBYTES) {
++ client_key = kex->mlkem1024_client_key;
++ r = mlkem1024_keypair_gen(cp, client_key);
++ }
++
++ if (client_key == NULL)
++ goto out;
++
++ if (r != 0)
++ goto out;
++#ifdef DEBUG_KEXECDH
++ dump_digest("client public key mlkemXXX:", cp, mlkem_bytes_len);
++#endif
++ cp += mlkem_bytes_len;
++ if ((kex->ec_hybrid_client_key = nist_pkey_keygen(nist_bytes_len)) == NULL)
++ goto out;
++
++ if ((r = get_uncompressed_ec_pubkey(kex->ec_hybrid_client_key, cp, nist_bytes_len)) != 0)
++ goto out;
++
++#ifdef DEBUG_KEXECDH
++ dump_digest("client public key NIST:", cp, nist_bytes_len);
++#endif
++ /* success */
++ r = 0;
++ kex->client_pub = buf;
++ buf = NULL;
++ out:
++ sshbuf_free(buf);
++ if (r == SSH_ERR_LIBCRYPTO_ERROR)
++ ERR_print_errors_fp(stderr);
++
++ return r;
++}
++
++static int
++kex_kem_mlkem_nist_enc(struct kex *kex, const char *nist_curve,
++ const struct sshbuf *client_blob, struct sshbuf **server_blobp,
++ struct sshbuf **shared_secretp)
++{
++ struct sshbuf *server_blob = NULL;
++ struct sshbuf *buf = NULL;
++ const u_char *client_pub;
++ u_char server_pub[NIST_BUF_MAX_SIZE];
++ u_char enc_out[crypto_kem_mlkem1024_CIPHERTEXTBYTES];
++ u_char secret[crypto_kem_mlkem768_BYTES];
++ EVP_PKEY *server_key = NULL;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ size_t client_buf_len, mlkem_buf_len, ecdh_buf_len, server_key_len, enc_out_len;
++ int r = SSH_ERR_INTERNAL_ERROR;
++
++ *server_blobp = NULL;
++ *shared_secretp = NULL;
++
++ client_buf_len = sshbuf_len(client_blob);
++ /* client_blob contains both KEM and ECDH client pubkeys */
++ if (strcmp(nist_curve, "P-256") == 0) {
++ if (crypto_kem_mlkem768_PUBLICKEYBYTES > client_buf_len)
++ return r;
++
++ ecdh_buf_len = client_buf_len - crypto_kem_mlkem768_PUBLICKEYBYTES;
++ if (ecdh_buf_len != NIST_P256_COMPRESSED_LEN &&
++ ecdh_buf_len != NIST_P256_UNCOMPRESSED_LEN)
++ return r;
++ mlkem_buf_len = crypto_kem_mlkem768_PUBLICKEYBYTES;
++ enc_out_len = crypto_kem_mlkem768_CIPHERTEXTBYTES;
++ server_key_len = NIST_P256_UNCOMPRESSED_LEN;
++ } else if (strcmp(nist_curve, "P-384") == 0) {
++ if (crypto_kem_mlkem1024_PUBLICKEYBYTES > client_buf_len)
++ return r;
++
++ ecdh_buf_len = client_buf_len - crypto_kem_mlkem1024_PUBLICKEYBYTES;
++ if (ecdh_buf_len != NIST_P384_COMPRESSED_LEN &&
++ ecdh_buf_len != NIST_P384_UNCOMPRESSED_LEN)
++ return r;
++ mlkem_buf_len = crypto_kem_mlkem1024_PUBLICKEYBYTES;
++ enc_out_len = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
++ server_key_len = NIST_P384_UNCOMPRESSED_LEN;
++ } else
++ return r;
++
++ client_pub = sshbuf_ptr(client_blob);
++#ifdef DEBUG_KEXECDH
++ dump_digest("client public key mlkem:", client_pub, mlkem_buf_len);
++ dump_digest("client public key NIST:", client_pub + mlkem_buf_len, ecdh_buf_len);
++#endif
++
++ /* allocate buffer for concatenation of KEM key and ECDH shared key */
++ /* the buffer will be hashed and the result is the shared secret */
++ if ((buf = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++ /* allocate space for encrypted KEM key and ECDH pub key */
++ if ((server_blob = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++ r = (mlkem_buf_len == crypto_kem_mlkem768_PUBLICKEYBYTES) ?
++ mlkem768_encap_secret(client_pub, secret, enc_out) :
++ mlkem1024_encap_secret(client_pub, secret, enc_out);
++
++ if (r != 0)
++ goto out;
++
++ /* generate ECDH key pair, store server pubkey after ciphertext */
++ server_key = nist_pkey_keygen(server_key_len);
++
++ if ((server_key == NULL) ||
++ (r = get_uncompressed_ec_pubkey(server_key, server_pub, server_key_len) != 0) ||
++ (r = sshbuf_put(buf, secret, sizeof(secret))) != 0 ||
++ (r = sshbuf_put(server_blob, enc_out, enc_out_len) != 0)||
++ (r = sshbuf_put(server_blob, server_pub, server_key_len)) != 0)
++ goto out;
++
++ /* append ECDH shared key */
++ client_pub += mlkem_buf_len;
++ if ((r = kex_nist_shared_key_ext(server_key, client_pub, ecdh_buf_len, buf)) < 0)
++ goto out;
++ if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
++ goto out;
++#ifdef DEBUG_KEXECDH
++ dump_digest("server public NIST:", server_pub, server_key_len);
++ dump_digest("server cipher text:", enc_out, enc_out_len);
++ dump_digest("server kem key:", secret, sizeof(secret));
++ dump_digest("concatenation of KEM key and ECDH shared key:",
++ sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++ /* string-encoded hash is resulting shared secret */
++ sshbuf_reset(buf);
++ if ((r = sshbuf_put_string(buf, hash,
++ ssh_digest_bytes(kex->hash_alg))) != 0)
++ goto out;
++#ifdef DEBUG_KEXECDH
++ dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++ /* success */
++ r = 0;
++ *server_blobp = server_blob;
++ *shared_secretp = buf;
++ server_blob = NULL;
++ buf = NULL;
++ out:
++ explicit_bzero(hash, sizeof(hash));
++ EVP_PKEY_free(server_key);
++ explicit_bzero(enc_out, sizeof(enc_out));
++ explicit_bzero(secret, sizeof(secret));
++ sshbuf_free(server_blob);
++ sshbuf_free(buf);
++ return r;
++}
++
++static int
++kex_kem_mlkem_nist_dec(struct kex *kex,
++ const struct sshbuf *server_blob, struct sshbuf **shared_secretp,
++ size_t mlkem_len)
++{
++ struct sshbuf *buf = NULL;
++ const u_char *ciphertext, *server_pub;
++ u_char hash[SSH_DIGEST_MAX_LENGTH];
++ u_char decap[crypto_kem_mlkem768_BYTES];
++ int r;
++ size_t nist_len;
++
++ *shared_secretp = NULL;
++
++ if (sshbuf_len(server_blob) < mlkem_len) {
++ r = SSH_ERR_SIGNATURE_INVALID;
++ goto out;
++ }
++
++ nist_len = sshbuf_len(server_blob) - mlkem_len;
++
++ switch (mlkem_len) {
++ case crypto_kem_mlkem768_CIPHERTEXTBYTES:
++ if (nist_len != NIST_P256_COMPRESSED_LEN
++ && nist_len != NIST_P256_UNCOMPRESSED_LEN) {
++ r = SSH_ERR_SIGNATURE_INVALID;
++ goto out;
++ }
++ break;
++ case crypto_kem_mlkem1024_CIPHERTEXTBYTES:
++ if (nist_len != NIST_P384_COMPRESSED_LEN
++ && nist_len != NIST_P384_UNCOMPRESSED_LEN) {
++ r = SSH_ERR_SIGNATURE_INVALID;
++ goto out;
++ }
++ break;
++ }
++
++ ciphertext = sshbuf_ptr(server_blob);
++ server_pub = ciphertext + mlkem_len;
++ /* hash concatenation of KEM key and ECDH shared key */
++ if ((buf = sshbuf_new()) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++#ifdef DEBUG_KEXECDH
++ dump_digest("server cipher text:", ciphertext, mlkem_len);
++ dump_digest("server public key NIST:", server_pub, nist_len);
++#endif
++ r = (mlkem_len == crypto_kem_mlkem768_CIPHERTEXTBYTES) ?
++ mlkem768_decap_secret(kex->mlkem768_client_key, ciphertext, decap) :
++ mlkem1024_decap_secret(kex->mlkem1024_client_key, ciphertext, decap);
++
++ if (r != 0)
++ goto out;
++ if ((r = sshbuf_put(buf, decap, sizeof(decap))) != 0)
++ goto out;
++ if ((r = kex_nist_shared_key_ext(kex->ec_hybrid_client_key, server_pub,
++ nist_len, buf)) < 0)
++ goto out;
++ if ((r = ssh_digest_buffer(kex->hash_alg, buf,
++ hash, sizeof(hash))) != 0)
++ goto out;
++#ifdef DEBUG_KEXECDH
++ dump_digest("client kem key:", decap, sizeof(decap));
++ dump_digest("concatenation of KEM key and ECDH shared key:",
++ sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++ sshbuf_reset(buf);
++ if ((r = sshbuf_put_string(buf, hash,
++ ssh_digest_bytes(kex->hash_alg))) != 0)
++ goto out;
++#ifdef DEBUG_KEXECDH
++ dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++ /* success */
++ r = 0;
++ *shared_secretp = buf;
++ buf = NULL;
++ out:
++ explicit_bzero(hash, sizeof(hash));
++ explicit_bzero(decap, sizeof(decap));
++ sshbuf_free(buf);
++ return r;
++}
++
++int
++kex_kem_mlkem768nistp256_keypair(struct kex *kex)
++{
++ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem768_PUBLICKEYBYTES, NIST_P256_UNCOMPRESSED_LEN);
++}
++
++int
++kex_kem_mlkem768nistp256_enc(struct kex *kex, const struct sshbuf *client_blob,
++ struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
++{
++ return kex_kem_mlkem_nist_enc(kex, "P-256", client_blob, server_blobp, shared_secretp);
++}
++
++int
++kex_kem_mlkem768nistp256_dec(struct kex *kex, const struct sshbuf *server_blob,
++ struct sshbuf **shared_secretp)
++{
++ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
++ crypto_kem_mlkem768_CIPHERTEXTBYTES);
++}
++
++int
++kex_kem_mlkem1024nistp384_keypair(struct kex *kex)
++{
++ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem1024_PUBLICKEYBYTES, NIST_P384_UNCOMPRESSED_LEN);
++}
++
++int
++kex_kem_mlkem1024nistp384_enc(struct kex *kex, const struct sshbuf *client_blob,
++ struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
++{
++ return kex_kem_mlkem_nist_enc(kex, "P-384", client_blob, server_blobp, shared_secretp);
++}
++
++int
++kex_kem_mlkem1024nistp384_dec(struct kex *kex, const struct sshbuf *server_blob,
++ struct sshbuf **shared_secretp)
++{
++ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
++ crypto_kem_mlkem1024_CIPHERTEXTBYTES);
++}
++
+ #else /* USE_MLKEM768X25519 */
+ int
+ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+@@ -550,4 +1149,39 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ {
+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
+ }
++
++int kex_kem_mlkem768nistp256_keypair(struct kex *)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
++ struct sshbuf **, struct sshbuf **)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
++ struct sshbuf **)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int kex_kem_mlkem1024nistp384_keypair(struct kex *)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
++ struct sshbuf **, struct sshbuf **)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
++ struct sshbuf **)
++{
++ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
+ #endif /* USE_MLKEM768X25519 */
+diff --git a/monitor.c b/monitor.c
+index 8269e78c3..87df3e9d7 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -2034,6 +2034,8 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ kex->load_host_public_key=&get_hostkey_public_by_type;
+ kex->load_host_private_key=&get_hostkey_private_by_type;
+ kex->host_key_index=&get_hostkey_index;
+diff --git a/myproposal.h b/myproposal.h
+index f03293f35..db50c5b1c 100644
+--- a/myproposal.h
++++ b/myproposal.h
+@@ -26,6 +26,8 @@
+
+ #define KEX_SERVER_KEX \
+ "mlkem768x25519-sha256," \
++ "mlkem768nistp256-sha256," \
++ "mlkem1024nistp384-sha384," \
+ "sntrup761x25519-sha512," \
+ "sntrup761x25519-sha512@openssh.com," \
+ "curve25519-sha256," \
+@@ -99,6 +101,8 @@
+ "aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se," \
+ "aes128-gcm@openssh.com,aes256-gcm@openssh.com"
+ #define KEX_DEFAULT_KEX_FIPS \
++ "mlkem768nistp256-sha256," \
++ "mlkem1024nistp384-sha384," \
+ "ecdh-sha2-nistp256," \
+ "ecdh-sha2-nistp384," \
+ "ecdh-sha2-nistp521," \
+diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
+index db7cb418b..fad9a634a 100644
+--- a/regress/unittests/kex/test_kex.c
++++ b/regress/unittests/kex/test_kex.c
+@@ -169,6 +169,8 @@ do_kex_with_key(char *kex, char *cipher, char *mac,
+ server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ server2->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++ server2->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++ server2->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ server2->kex->load_host_public_key = server->kex->load_host_public_key;
+ server2->kex->load_host_private_key = server->kex->load_host_private_key;
+ server2->kex->sign = server->kex->sign;
+@@ -242,6 +244,8 @@ kex_tests(void)
+ }
+ # ifdef USE_MLKEM768X25519
+ do_kex("mlkem768x25519-sha256");
++ do_kex("mlkem768nistp256-sha256");
++ do_kex("mlkem1024nistp384-sha384");
+ # endif /* USE_MLKEM768X25519 */
+ # ifdef USE_SNTRUP761X25519
+ do_kex("sntrup761x25519-sha512");
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index 284d4deee..405faf167 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -307,7 +307,8 @@ keygrab_ssh2(con *c)
+ c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ c->c_ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
+- c->c_ssh->kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_client;
++ c->c_ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++ c->c_ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper);
+ /*
+ * do the key-exchange until an error occurs or until
+diff --git a/ssh_api.c b/ssh_api.c
+index d01ac5483..f554b12cd 100644
+--- a/ssh_api.c
++++ b/ssh_api.c
+@@ -133,7 +133,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
+ ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
+- ssh->kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_server;
++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ ssh->kex->load_host_public_key=&_ssh_host_public_key;
+ ssh->kex->load_host_private_key=&_ssh_host_private_key;
+ ssh->kex->sign=&_ssh_host_key_sign;
+@@ -151,7 +152,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
+ ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
+- ssh->kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_client;
++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ ssh->kex->verify_host_key =&_ssh_verify_host_key;
+ }
+ *sshp = ssh;
+diff --git a/sshconnect2.c b/sshconnect2.c
+index 3660873a6..b5d63e599 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -349,7 +349,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
+- ssh->kex->kex[KEX_KEM_MLKEM768ECDH_SHA256] = kex_gen_client;
++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ ssh->kex->verify_host_key=&verify_host_key_callback;
+
+ #if defined(GSSAPI) && defined(WITH_OPENSSL)
+diff --git a/sshd-auth.c b/sshd-auth.c
+index 079063f47..6038758ce 100644
+--- a/sshd-auth.c
++++ b/sshd-auth.c
+@@ -864,6 +864,8 @@ do_ssh2_kex(struct ssh *ssh)
+ kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ kex->load_host_public_key=&get_hostkey_public_by_type;
+ kex->load_host_private_key=&get_hostkey_private_by_type;
+ kex->host_key_index=&get_hostkey_index;
+--
+2.55.0
+
diff --git a/0047-support-authentication-indicators-in-GSSAPI.patch b/0047-support-authentication-indicators-in-GSSAPI.patch
deleted file mode 100644
index fd06bdf..0000000
--- a/0047-support-authentication-indicators-in-GSSAPI.patch
+++ /dev/null
@@ -1,487 +0,0 @@
-From 477e0dfe44d5e309a9357375582884220fde4acf Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:23:51 +0200
-Subject: [PATCH 47/54] support authentication indicators in GSSAPI
-
-https://github.com/openssh/openssh-portable/pull/500
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- configure.ac | 1 +
- gss-serv-krb5.c | 89 +++++++++++++++++++++++++++++++++++----
- gss-serv.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c | 16 ++++++-
- servconf.h | 2 +
- ssh-gss.h | 7 ++++
- sshd_config.5 | 46 +++++++++++++++++++++
- 7 files changed, 256 insertions(+), 13 deletions(-)
-
-diff --git a/configure.ac b/configure.ac
-index 86f0e9e09..1388e5e72 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -5129,6 +5129,7 @@ AC_ARG_WITH([kerberos5],
- AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h])
- AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h])
- AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h])
-+ AC_CHECK_HEADERS([gssapi_ext.h gssapi/gssapi_ext.h])
-
- AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1],
- [Define this if you want to use libkafs' AFS support])])
-diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
-index 723509409..fc7cabc09 100644
---- a/gss-serv-krb5.c
-+++ b/gss-serv-krb5.c
-@@ -43,6 +43,7 @@
- #include "log.h"
- #include "misc.h"
- #include "servconf.h"
-+#include "match.h"
-
- #include "ssh-gss.h"
-
-@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void)
- return 1;
- }
-
-+/* Check if any of the indicators in the Kerberos ticket match
-+ * one of indicators in the list of allowed/denied rules.
-+ * In case of the match, apply the decision from the rule.
-+ * In case of no indicator from the ticket matching the rule, deny
-+ */
-+
-+static int
-+ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
-+{
-+ int ret;
-+ u_int i;
-+ *matched = -1;
-+
-+ /* Check indicators */
-+ for (i = 0; client->indicators[i] != NULL; i++) {
-+ ret = match_pattern_list(client->indicators[i],
-+ options.gss_indicators, 1);
-+ /* negative or positive match */
-+ if (ret != 0) {
-+ *matched = i;
-+ return ret;
-+ }
-+ }
-+ /* No rule matched */
-+ return 0;
-+}
-+
- /* Check if this user is OK to login. This only works with krb5 - other
- * GSSAPI mechanisms will need their own.
- * Returns true if the user is OK to log in, otherwise returns 0
-@@ -193,15 +221,15 @@ static int
- ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
- {
- krb5_principal princ;
-- int retval;
-+ int retval, matched, success;
- const char *errmsg;
- int k5login_exists;
-
- if (ssh_gssapi_krb5_init() == 0)
- return 0;
-
-- if ((retval = krb5_parse_name(krb_context, client->exportedname.value,
-- &princ))) {
-+ retval = krb5_parse_name(krb_context, client->exportedname.value, &princ);
-+ if (retval) {
- errmsg = krb5_get_error_message(krb_context, retval);
- logit("krb5_parse_name(): %.100s", errmsg);
- krb5_free_error_message(krb_context, errmsg);
-@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
- if (k5login_exists &&
- ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) {
- retval = 1;
-- logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
-- name, (char *)client->displayname.value);
-+ errmsg = "krb5_kuserok";
- } else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
- name, k5login_exists)) {
- retval = 1;
-- logit("Authorized to %s, krb5 principal %s "
-- "(ssh_gssapi_krb5_cmdok)",
-- name, (char *)client->displayname.value);
-- } else
-+ errmsg = "ssh_gssapi_krb5_cmdok";
-+ } else {
- retval = 0;
-+ goto out;
-+ }
-
-+ /* At this point we are good if no indicators were defined */
-+ if (options.gss_indicators == NULL) {
-+ retval = 1;
-+ goto out;
-+ }
-+
-+ /* At this point we have indicators defined in the configuration,
-+ * if clientt did not provide any indicators, we reject */
-+ if (!client->indicators) {
-+ retval = 0;
-+ logit("GSSAPI authentication indicators enforced "
-+ "but indicators not provided by the client. "
-+ "krb5 principal %s denied",
-+ (char *)client->displayname.value);
-+ goto out;
-+ }
-+
-+ /* At this point the configuration enforces presence of indicators
-+ * check the match */
-+ matched = -1;
-+ success = ssh_gssapi_check_indicators(client, &matched);
-+
-+ switch (success) {
-+ case 1:
-+ logit("Provided indicator %s allowed by the configuration",
-+ client->indicators[matched]);
-+ retval = 1;
-+ break;
-+ case -1:
-+ logit("Provided indicator %s rejected by the configuration",
-+ client->indicators[matched]);
-+ retval = 0;
-+ break;
-+ default:
-+ logit("Provided indicators do not match the configuration");
-+ retval = 0;
-+ break;
-+ }
-+
-+out:
-+ if (retval == 1) {
-+ logit("Authorized to %s, krb5 principal %s (%s)",
-+ name, (char *)client->displayname.value, errmsg);
-+ }
- krb5_free_principal(krb_context, princ);
- return retval;
- }
-diff --git a/gss-serv.c b/gss-serv.c
-index 6eb7d2163..686ac4eb3 100644
---- a/gss-serv.c
-+++ b/gss-serv.c
-@@ -55,7 +55,7 @@ extern ServerOptions options;
-
- static ssh_gssapi_client gssapi_client =
- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
-- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
-+ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
-
- ssh_gssapi_mech gssapi_null_mech =
- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
-@@ -295,6 +295,99 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss
- return GSS_S_COMPLETE;
- }
-
-+
-+/* Extract authentication indicators from the Kerberos ticket. Authentication
-+ * indicators are GSSAPI name attributes for the name "auth-indicators".
-+ * Multiple indicators might be present in the ticket.
-+ * Each indicator is an utf8 string. */
-+
-+#define AUTH_INDICATORS_TAG "auth-indicators"
-+#define SSH_GSSAPI_MAX_INDICATORS 64
-+
-+/* Privileged (called from accept_secure_ctx) */
-+static OM_uint32
-+ssh_gssapi_getindicators(Gssctxt *ctx, gss_name_t gss_name, ssh_gssapi_client *client)
-+{
-+ gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
-+ gss_buffer_desc value = GSS_C_EMPTY_BUFFER;
-+ gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER;
-+ int is_mechname, authenticated, complete, more;
-+ size_t count, i;
-+
-+ /* always initialize client->indicators */
-+ client->indicators = NULL;
-+
-+ ctx->major = gss_inquire_name(&ctx->minor, gss_name,
-+ &is_mechname, NULL, &attrs);
-+ if (ctx->major != GSS_S_COMPLETE)
-+ return ctx->major;
-+
-+ if (attrs == GSS_C_NO_BUFFER_SET) {
-+ /* no indicators in the ticket */
-+ return GSS_S_COMPLETE;
-+ }
-+
-+ /* client->indicators is NULL terminated */
-+ count = 0;
-+ client->indicators = xcalloc(count + 1, sizeof(char *));
-+
-+ for (i = 0; i < attrs->count; i++) {
-+ authenticated = 0;
-+ complete = 0;
-+ more = -1;
-+
-+ /* skip anything but auth-indicators */
-+ if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) ||
-+ memcmp(AUTH_INDICATORS_TAG, attrs->elements[i].value,
-+ sizeof(AUTH_INDICATORS_TAG) - 1) != 0)
-+ continue;
-+
-+ /* retrieve all indicators */
-+ while (more != 0) {
-+ value.value = NULL;
-+ display_value.value = NULL;
-+
-+ ctx->major = gss_get_name_attribute(&ctx->minor, gss_name,
-+ &attrs->elements[i],
-+ &authenticated, &complete,
-+ &value, &display_value, &more);
-+ if (ctx->major != GSS_S_COMPLETE)
-+ goto out;
-+
-+ if (value.value == NULL || !authenticated)
-+ continue;
-+
-+ if (count >= SSH_GSSAPI_MAX_INDICATORS) {
-+ logit("ssh_gssapi_getindicators:"
-+ " too many indicators, truncating at %d",
-+ SSH_GSSAPI_MAX_INDICATORS);
-+ goto out;
-+ }
-+
-+ client->indicators[count] = xmalloc(value.length + 1);
-+ memcpy(client->indicators[count], value.value, value.length);
-+ client->indicators[count][value.length] = '\0';
-+ count++;
-+
-+ /* add NULL terminator */
-+ client->indicators = xrecallocarray(client->indicators, count,
-+ count + 1, sizeof(char *));
-+ }
-+ }
-+
-+out:
-+ if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) {
-+ for (i = 0; i < count; i++)
-+ free(client->indicators[i]);
-+ free(client->indicators);
-+ client->indicators = NULL;
-+ }
-+ gss_release_buffer(&ctx->minor, &value);
-+ gss_release_buffer(&ctx->minor, &display_value);
-+ gss_release_buffer_set(&ctx->minor, &attrs);
-+ return ctx->major;
-+}
-+
- /* Extract the client details from a given context. This can only reliably
- * be called once for a context */
-
-@@ -386,6 +475,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
- }
-
- gss_release_buffer(&ctx->minor, &ename);
-+ /* Retrieve authentication indicators, if they exist */
-+ if ((ctx->major = ssh_gssapi_getindicators(ctx,
-+ ctx->client, client))) {
-+ ssh_gssapi_error(ctx);
-+ return (ctx->major);
-+ }
-
- /* We can't copy this structure, so we just move the pointer to it */
- client->creds = ctx->client_creds;
-@@ -453,6 +548,7 @@ int
- ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
- {
- OM_uint32 lmin;
-+ size_t i;
-
- (void) kex; /* used in privilege separation */
-
-@@ -471,8 +567,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
- gss_release_buffer(&lmin, &gssapi_client.displayname);
- gss_release_buffer(&lmin, &gssapi_client.exportedname);
- gss_release_cred(&lmin, &gssapi_client.creds);
-- explicit_bzero(&gssapi_client,
-- sizeof(ssh_gssapi_client));
-+
-+ if (gssapi_client.indicators != NULL) {
-+ for (i = 0; gssapi_client.indicators[i] != NULL; i++)
-+ free(gssapi_client.indicators[i]);
-+ free(gssapi_client.indicators);
-+ }
-+
-+ explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client));
- return 0;
- }
- else
-diff --git a/servconf.c b/servconf.c
-index 6c1ba77f6..268c09d1c 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -145,6 +145,7 @@ initialize_server_options(ServerOptions *options)
- options->gss_cleanup_creds = -1;
- options->gss_deleg_creds = -1;
- options->gss_strict_acceptor = -1;
-+ options->gss_indicators = NULL;
- options->gss_store_rekey = -1;
- options->gss_kex_algorithms = NULL;
- options->use_kuserok = -1;
-@@ -560,6 +561,7 @@ fill_default_server_options(ServerOptions *options)
- CLEAR_ON_NONE(options->routing_domain);
- CLEAR_ON_NONE(options->host_key_agent);
- CLEAR_ON_NONE(options->per_source_penalty_exempt);
-+ CLEAR_ON_NONE(options->gss_indicators);
-
- for (i = 0; i < options->num_host_key_files; i++)
- CLEAR_ON_NONE(options->host_key_files[i]);
-@@ -599,7 +601,7 @@ typedef enum {
- sPerSourcePenalties, sPerSourcePenaltyExemptList,
- sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
- sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
-- sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
-+ sGssIndicators, sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- sAcceptEnv, sSetEnv, sPermitTunnel,
- sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
- sUsePrivilegeSeparation, sAllowAgentForwarding,
-@@ -696,6 +698,7 @@ static struct {
- { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
- { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- { "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
-+ { "gssapiindicators", sGssIndicators, SSHCFG_ALL },
- #else
- { "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -706,6 +709,7 @@ static struct {
- { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
- { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- { "gssapienablek5users", sUnsupported, SSHCFG_ALL },
-+ { "gssapiindicators", sUnsupported, SSHCFG_ALL },
- #endif
- { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -1737,6 +1741,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- options->gss_kex_algorithms = xstrdup(arg);
- break;
-
-+ case sGssIndicators:
-+ arg = argv_next(&ac, &av);
-+ if (!arg || *arg == '\0')
-+ fatal("%s line %d: %s missing argument.",
-+ filename, linenum, keyword);
-+ if (options->gss_indicators == NULL)
-+ options->gss_indicators = xstrdup(arg);
-+ break;
-+
- case sPasswordAuthentication:
- intptr = &options->password_authentication;
- goto parse_flag;
-@@ -3383,6 +3396,7 @@ dump_config(ServerOptions *o)
- dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
- dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
- dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
-+ dump_cfg_string(sGssIndicators, o->gss_indicators);
- #endif
- dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
- dump_cfg_fmtint(sKbdInteractiveAuthentication,
-diff --git a/servconf.h b/servconf.h
-index 569100e47..781f20c61 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -181,6 +181,7 @@ typedef struct {
- char **allow_groups;
- u_int num_deny_groups;
- char **deny_groups;
-+ char *gss_indicators;
-
- u_int num_subsystems;
- char **subsystem_name;
-@@ -310,6 +311,7 @@ TAILQ_HEAD(include_list, include_item);
- M_CP_STROPT(routing_domain); \
- M_CP_STROPT(permit_user_env_allowlist); \
- M_CP_STROPT(pam_service_name); \
-+ M_CP_STROPT(gss_indicators); \
- M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files, 1);\
- M_CP_STRARRAYOPT(revoked_keys_files, \
- num_revoked_keys_files, 1); \
-diff --git a/ssh-gss.h b/ssh-gss.h
-index 329dc9da0..1506719a9 100644
---- a/ssh-gss.h
-+++ b/ssh-gss.h
-@@ -34,6 +34,12 @@
- #include <gssapi/gssapi.h>
- #endif
-
-+#ifdef HAVE_GSSAPI_EXT_H
-+#include <gssapi_ext.h>
-+#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H)
-+#include <gssapi/gssapi_ext.h>
-+#endif
-+
- #ifdef KRB5
- # ifndef HEIMDAL
- # ifdef HAVE_GSSAPI_GENERIC_H
-@@ -112,6 +118,7 @@ typedef struct {
- ssh_gssapi_ccache store;
- int used;
- int updated;
-+ char **indicators; /* auth indicators */
- } ssh_gssapi_client;
-
- typedef struct ssh_gssapi_mech_struct {
-diff --git a/sshd_config.5 b/sshd_config.5
-index 1d16dabaf..4ca80016c 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -803,6 +803,52 @@ This option only applies to connections using GSSAPI.
- .Pp
- The list of supported key exchange algorithms may also be obtained using
- .Qq ssh -Q GSSAPIKexAlgorithms .
-+.It Cm GSSAPIIndicators
-+Specifies whether to accept or deny GSSAPI authenticated access if Kerberos
-+mechanism is used and Kerberos ticket contains a particular set of
-+authentication indicators. The values can be specified as a comma-separated list
-+.Cm [!]name1,[!]name2,... .
-+When indicator's name is prefixed with !, the authentication indicator 'name'
-+will deny access to the system. Otherwise, one of non-negated authentication
-+indicators must be present in the Kerberos ticket to allow access. If
-+.Cm GSSAPIIndicators
-+is defined, a Kerberos ticket that has indicators but does not match the
-+policy will get denial. If at least one indicator is configured, whether for
-+access or denial, tickets without authentication indicators will be explicitly
-+rejected.
-+.Pp
-+By default systems using MIT Kerberos 1.17 or later will not assign any
-+indicators. SPAKE and PKINIT methods add authentication indicators
-+to all successful authentications. The SPAKE pre-authentication method is
-+preferred over an encrypted timestamp pre-authentication when passwords used to
-+authenticate user principals. Kerberos KDCs built with Heimdal Kerberos
-+(including Samba AD DC built with Heimdal) do not add authentication
-+indicators. However, OpenSSH built against Heimdal Kerberos library is able to
-+inquire authentication indicators and thus can be used to check for their presence.
-+.Pp
-+Indicator name is case-sensitive and depends on the configuration of a
-+particular Kerberos deployment. Indicators available in MIT Kerberos and
-+FreeIPA environments:
-+.Pp
-+.Bl -tag -width XXXX -offset indent -compact
-+.It Cm hardened
-+SPAKE or encrypted timestamp pre-authentication mechanisms in MIT Kerberos and FreeIPA
-+.It Cm pkinit
-+smartcard or PKCS11 token-based pre-authentication in MIT Kerberos and FreeIPA
-+.It Cm radius
-+pre-authentication based on a RADIUS server in MIT Kerberos and FreeIPA
-+.It Cm otp
-+TOTP/HOTP-based two-factor pre-authentication in FreeIPA
-+.It Cm idp
-+OAuth2-based pre-authentication in FreeIPA using an external identity provider
-+and device authorization grant flow
-+.It Cm passkey
-+FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
-+.El
-+.Pp
-+The default
-+.Dq none
-+is to not use GSSAPI authentication indicators for access decisions.
- .It Cm HostbasedAcceptedAlgorithms
- The default is handled system-wide by
- .Xr crypto-policies 7 .
---
-2.53.0
-
diff --git a/0048-NIST-curves-hybrid-KEX-implementation.patch b/0048-NIST-curves-hybrid-KEX-implementation.patch
deleted file mode 100644
index c7ab711..0000000
--- a/0048-NIST-curves-hybrid-KEX-implementation.patch
+++ /dev/null
@@ -1,1303 +0,0 @@
-From 3802a51b69044f419bb53deaff7061d62d9596fe Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 20 Oct 2025 16:07:31 +0200
-Subject: [PATCH 48/54] NIST curves hybrid KEX implementation
-
----
- compat.c | 23 +-
- compat.h | 1 +
- crypto_api.h | 4 +
- kex-names.c | 89 +++-
- kex.c | 1 +
- kex.h | 19 +
- kexgen.c | 56 ++-
- kexmlkem768x25519.c | 679 +++++++++++++++++++++++++++++--
- monitor.c | 2 +
- myproposal.h | 4 +
- regress/unittests/kex/test_kex.c | 4 +
- ssh-keyscan.c | 2 +
- ssh_api.c | 4 +
- sshconnect2.c | 2 +
- sshd-auth.c | 2 +
- 15 files changed, 851 insertions(+), 41 deletions(-)
-
-diff --git a/compat.c b/compat.c
-index 4312510e5..e6c7935d4 100644
---- a/compat.c
-+++ b/compat.c
-@@ -36,6 +36,7 @@
- #include "compat.h"
- #include "log.h"
- #include "match.h"
-+#include <openssl/fips.h>
-
- /* determine bug flags from SSH protocol banner */
- void
-@@ -148,8 +149,9 @@ char *
- compat_kex_proposal(struct ssh *ssh, const char *p)
- {
- char *cp = NULL, *cp2 = NULL;
-+ int mlkem_available = is_mlkem768_available();
-
-- if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0)
-+ if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0 && mlkem_available == 2)
- return xstrdup(p);
- debug2_f("original KEX proposal: %s", p);
- if ((ssh->compat & SSH_BUG_CURVE25519PAD) != 0)
-@@ -164,6 +166,25 @@ compat_kex_proposal(struct ssh *ssh, const char *p)
- free(cp);
- cp = cp2;
- }
-+ if (mlkem_available == 2)
-+ return cp ? cp : xstrdup(p);
-+ if (mlkem_available == 1 && FIPS_mode()) {
-+ if ((cp2 = match_filter_denylist(cp ? cp : p,
-+ "mlkem768x25519-sha256")) == NULL)
-+ fatal("match_filter_denylist failed");
-+ free(cp);
-+ cp = cp2;
-+ }
-+ if (mlkem_available == 0) {
-+ if ((cp2 = match_filter_denylist(cp ? cp : p,
-+ "mlkem768x25519-sha256,"
-+ "mlkem768nistp256-sha256,"
-+ "mlkem1024nistp384-sha384")) == NULL)
-+ fatal("match_filter_denylist failed");
-+ free(cp);
-+ cp = cp2;
-+ }
-+
- if (cp == NULL || *cp == '\0')
- fatal("No supported key exchange algorithms found");
- debug2_f("compat KEX proposal: %s", cp);
-diff --git a/compat.h b/compat.h
-index 2e6db5bf9..b78e55b69 100644
---- a/compat.h
-+++ b/compat.h
-@@ -62,4 +62,5 @@ struct ssh;
-
- void compat_banner(struct ssh *, const char *);
- char *compat_kex_proposal(struct ssh *, const char *);
-+int is_mlkem768_available(void);
- #endif
-diff --git a/crypto_api.h b/crypto_api.h
-index f5e38b547..6a8ede067 100644
---- a/crypto_api.h
-+++ b/crypto_api.h
-@@ -82,4 +82,8 @@ int crypto_kem_sntrup761_keypair(unsigned char *pk, unsigned char *sk);
- #define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088
- #define crypto_kem_mlkem768_BYTES 32
-
-+#define crypto_kem_mlkem1024_PUBLICKEYBYTES 1568
-+#define crypto_kem_mlkem1024_SECRETKEYBYTES 3168
-+#define crypto_kem_mlkem1024_CIPHERTEXTBYTES 1568
-+
- #endif /* crypto_api_h */
-diff --git a/kex-names.c b/kex-names.c
-index 882f2905b..60fc487b0 100644
---- a/kex-names.c
-+++ b/kex-names.c
-@@ -34,6 +34,7 @@
- #include <openssl/crypto.h>
- #include <openssl/fips.h>
- #include <openssl/evp.h>
-+#include <openssl/err.h>
- #endif
-
- #include "kex.h"
-@@ -90,6 +91,10 @@ static const struct kexalg kexalgs[] = {
- #ifdef USE_MLKEM768X25519
- { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
- SSH_DIGEST_SHA256, KEX_IS_PQ },
-+ { KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768NISTP256_SHA256, 0,
-+ SSH_DIGEST_SHA256, KEX_IS_PQ },
-+ { KEX_MLKEM1024NISTP384_SHA384, KEX_KEM_MLKEM1024NISTP384_SHA384, 0,
-+ SSH_DIGEST_SHA384, KEX_IS_PQ },
- #endif
- #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
- { NULL, 0, -1, -1, 0 },
-@@ -108,14 +113,27 @@ static const struct kexalg gss_kexalgs[] = {
- { NULL, 0, -1, -1, 0},
- };
-
--static int is_mlkem768_available()
-+/*
-+ * 0 - unavailable
-+ * 1 - available in non-FIPS mode
-+ * 2 - available always
-+ */
-+int is_mlkem768_available()
- {
- static int is_fetched = -1;
-
- if (is_fetched == -1) {
-- EVP_KEM *mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-- is_fetched = mlkem768 != NULL ? 1 : 0;
-+ EVP_KEM *mlkem768 = NULL;
-+
-+ ERR_set_mark();
-+ mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+ is_fetched = (mlkem768 == NULL) ? 0 : 2;
-+ if (is_fetched == 0 && FIPS_mode() == 1) {
-+ mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", "provider=default,-fips");
-+ is_fetched = (mlkem768 == NULL) ? 0 : 1;
-+ }
- EVP_KEM_free(mlkem768);
-+ ERR_pop_to_mark();
- }
-
- return is_fetched;
-@@ -127,11 +145,32 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
- char *ret = NULL;
- const struct kexalg *k;
- char sep_str[2] = {sep, '\0'};
-+ int x25519mlkem_available = 0, nistmlkem_available = 0;
-
-- for (k = kexalgs; k->name != NULL; k++) {
-- if (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0
-- && !is_mlkem768_available())
-+ /*
-+ * FIPS provider can provide ML-KEMs and then all hybrids are available
-+ * Otherwise only NIST hybrids are available
-+ * */
-+ if (FIPS_mode()) {
-+ if (is_mlkem768_available() == 2) {
-+ x25519mlkem_available = 1;
-+ nistmlkem_available = 1;
-+ } else if (is_mlkem768_available() == 1) {
-+ nistmlkem_available = 1;
-+ }
-+ } else {
-+ if (is_mlkem768_available() > 0) {
-+ x25519mlkem_available = 1;
-+ nistmlkem_available = 1;
-+ }
-+ }
-+
-+ for (k = algs; k->name != NULL; k++) {
-+ if ( (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0 && x25519mlkem_available == 0)
-+ || (strcmp(k->name, KEX_MLKEM768NISTP256_SHA256) == 0 && nistmlkem_available == 0)
-+ || (strcmp(k->name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
- continue;
-+
- xextendf(&ret, sep_str, "%s", k->name);
- }
-
-@@ -154,10 +193,30 @@ static const struct kexalg *
- kex_alg_by_name(const char *name)
- {
- const struct kexalg *k;
-+ int x25519mlkem_available = 0, nistmlkem_available = 0;
-
-- if (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0
-- && !is_mlkem768_available())
-- return NULL;
-+ /*
-+ * FIPS provider can provide ML-KEMs and then all hybrids are available
-+ * Otherwise only NIST hybrids are available
-+ * */
-+ if (FIPS_mode()) {
-+ if (is_mlkem768_available() == 2) {
-+ x25519mlkem_available = 1;
-+ nistmlkem_available = 1;
-+ } else if (is_mlkem768_available() == 1) {
-+ nistmlkem_available = 1;
-+ }
-+ } else {
-+ if (is_mlkem768_available() > 0) {
-+ x25519mlkem_available = 1;
-+ nistmlkem_available = 1;
-+ }
-+ }
-+
-+ if ( (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0 && x25519mlkem_available == 0)
-+ || (strcmp(name, KEX_MLKEM768NISTP256_SHA256) == 0 && nistmlkem_available == 0)
-+ || (strcmp(name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
-+ return NULL;
-
- for (k = kexalgs; k->name != NULL; k++) {
- if (strcmp(k->name, name) == 0)
-@@ -229,8 +288,16 @@ kex_names_valid(const char *names)
- return 0;
- }
- if (kex_alg_by_name(p) == NULL) {
-- if (FIPS_mode())
-- error("\"%.100s\" is not allowed in FIPS mode", p);
-+ if (FIPS_mode()) {
-+ if ((strcmp(p, KEX_MLKEM768X25519_SHA256) == 0)
-+ || (strcmp(p, KEX_MLKEM768NISTP256_SHA256) == 0)
-+ || (strcmp(p, KEX_MLKEM1024NISTP384_SHA384) == 0)) {
-+ debug("\"%.100s\" is not allowed in FIPS mode", p);
-+ continue;
-+ }
-+ else
-+ error("\"%.100s\" is not allowed in FIPS mode", p);
-+ }
- else
- error("Unsupported KEX algorithm \"%.100s\"", p);
- free(s);
-diff --git a/kex.c b/kex.c
-index 1eb3ca9d3..8cfd446c0 100644
---- a/kex.c
-+++ b/kex.c
-@@ -757,6 +757,7 @@ kex_free(struct kex *kex)
- #ifdef OPENSSL_HAS_ECC
- EC_KEY_free(kex->ec_client_key);
- #endif /* OPENSSL_HAS_ECC */
-+ EVP_PKEY_free(kex->ec_hybrid_client_key);
- #endif /* WITH_OPENSSL */
- for (mode = 0; mode < MODE_MAX; mode++) {
- kex_free_newkeys(kex->newkeys[mode]);
-diff --git a/kex.h b/kex.h
-index ff41324e9..d110ad29a 100644
---- a/kex.h
-+++ b/kex.h
-@@ -72,6 +72,8 @@
- #define KEX_SNTRUP761X25519_SHA512 "sntrup761x25519-sha512"
- #define KEX_SNTRUP761X25519_SHA512_OLD "sntrup761x25519-sha512@openssh.com"
- #define KEX_MLKEM768X25519_SHA256 "mlkem768x25519-sha256"
-+#define KEX_MLKEM768NISTP256_SHA256 "mlkem768nistp256-sha256"
-+#define KEX_MLKEM1024NISTP384_SHA384 "mlkem1024nistp384-sha384"
-
- #define COMP_NONE 0
- #define COMP_DELAYED 2
-@@ -110,6 +112,8 @@ enum kex_exchange {
- KEX_C25519_SHA256,
- KEX_KEM_SNTRUP761X25519_SHA512,
- KEX_KEM_MLKEM768X25519_SHA256,
-+ KEX_KEM_MLKEM768NISTP256_SHA256,
-+ KEX_KEM_MLKEM1024NISTP384_SHA384,
- #ifdef GSSAPI
- KEX_GSS_GRP1_SHA1,
- KEX_GSS_GRP14_SHA1,
-@@ -211,6 +215,9 @@ struct kex {
- u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */
- u_char mlkem768_client_key[crypto_kem_mlkem768_SECRETKEYBYTES]; /* KEM */
- struct sshbuf *client_pub;
-+ /* FIXME */
-+ EVP_PKEY *ec_hybrid_client_key; /* NIST hybrids */
-+ u_char mlkem1024_client_key[crypto_kem_mlkem1024_SECRETKEYBYTES]; /* ML-KEM 1024 + NIST */
- };
-
- int kex_name_valid(const char *);
-@@ -293,6 +300,18 @@ int kex_kem_mlkem768x25519_enc(struct kex *, const struct sshbuf *,
- int kex_kem_mlkem768x25519_dec(struct kex *, const struct sshbuf *,
- struct sshbuf **);
-
-+int kex_kem_mlkem768nistp256_keypair(struct kex *);
-+int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
-+ struct sshbuf **, struct sshbuf **);
-+int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
-+ struct sshbuf **);
-+
-+int kex_kem_mlkem1024nistp384_keypair(struct kex *);
-+int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
-+ struct sshbuf **, struct sshbuf **);
-+int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
-+ struct sshbuf **);
-+
- int kex_dh_keygen(struct kex *);
- int kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *);
-
-diff --git a/kexgen.c b/kexgen.c
-index 2f8252488..9a356e298 100644
---- a/kexgen.c
-+++ b/kexgen.c
-@@ -133,12 +133,24 @@ kex_gen_client(struct ssh *ssh)
- break;
- case KEX_KEM_MLKEM768X25519_SHA256:
- if (FIPS_mode()) {
-- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-- r = SSH_ERR_INVALID_ARGUMENT;
-+ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+ if (mlkem == NULL) {
-+ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+ r = SSH_ERR_INVALID_ARGUMENT;
-+ } else {
-+ EVP_KEM_free(mlkem);
-+ r = kex_kem_mlkem768x25519_keypair(kex);
-+ }
- } else {
- r = kex_kem_mlkem768x25519_keypair(kex);
- }
- break;
-+ case KEX_KEM_MLKEM768NISTP256_SHA256:
-+ r = kex_kem_mlkem768nistp256_keypair(kex);
-+ break;
-+ case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+ r = kex_kem_mlkem1024nistp384_keypair(kex);
-+ break;
- default:
- r = SSH_ERR_INVALID_ARGUMENT;
- break;
-@@ -223,13 +235,28 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
- break;
- case KEX_KEM_MLKEM768X25519_SHA256:
- if (FIPS_mode()) {
-- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-- r = SSH_ERR_INVALID_ARGUMENT;
-+ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+ if (mlkem == NULL) {
-+ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+ r = SSH_ERR_INVALID_ARGUMENT;
-+ } else {
-+ EVP_KEM_free(mlkem);
-+ r = kex_kem_mlkem768x25519_dec(kex, server_blob,
-+ &shared_secret);
-+ }
- } else {
- r = kex_kem_mlkem768x25519_dec(kex, server_blob,
- &shared_secret);
- }
- break;
-+ case KEX_KEM_MLKEM768NISTP256_SHA256:
-+ r = kex_kem_mlkem768nistp256_dec(kex, server_blob,
-+ &shared_secret);
-+ break;
-+ case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+ r = kex_kem_mlkem1024nistp384_dec(kex, server_blob,
-+ &shared_secret);
-+ break;
- default:
- r = SSH_ERR_INVALID_ARGUMENT;
- break;
-@@ -283,6 +310,8 @@ out:
- sizeof(kex->sntrup761_client_key));
- explicit_bzero(kex->mlkem768_client_key,
- sizeof(kex->mlkem768_client_key));
-+ explicit_bzero(kex->mlkem1024_client_key,
-+ sizeof(kex->mlkem1024_client_key));
- sshbuf_free(server_host_key_blob);
- free(signature);
- sshbuf_free(tmp);
-@@ -362,13 +391,28 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
- break;
- case KEX_KEM_MLKEM768X25519_SHA256:
- if (FIPS_mode()) {
-- logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-- r = SSH_ERR_INVALID_ARGUMENT;
-+ EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+ if (mlkem == NULL) {
-+ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+ r = SSH_ERR_INVALID_ARGUMENT;
-+ } else {
-+ EVP_KEM_free(mlkem);
-+ r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
-+ &server_pubkey, &shared_secret);
-+ }
- } else {
- r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
- &server_pubkey, &shared_secret);
- }
- break;
-+ case KEX_KEM_MLKEM768NISTP256_SHA256:
-+ r = kex_kem_mlkem768nistp256_enc(kex, client_pubkey,
-+ &server_pubkey, &shared_secret);
-+ break;
-+ case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+ r = kex_kem_mlkem1024nistp384_enc(kex, client_pubkey,
-+ &server_pubkey, &shared_secret);
-+ break;
- default:
- r = SSH_ERR_INVALID_ARGUMENT;
- break;
-diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
-index ab6167221..7c063f21c 100644
---- a/kexmlkem768x25519.c
-+++ b/kexmlkem768x25519.c
-@@ -44,19 +44,33 @@
- #ifdef USE_MLKEM768X25519
-
- #include "libcrux_mlkem768_sha3.h"
-+#include <openssl/bn.h>
-+#include <openssl/ec.h>
- #include <openssl/err.h>
- #include <openssl/evp.h>
-+#include <openssl/fips.h>
- #include <stdio.h>
-
-+#define FIPS_FALLBACK_PROPQ "provider=default,-fips"
-+
- static int
--mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+mlkem_keypair_gen(const char *algname, unsigned char *pubkeybuf, size_t pubkey_size,
-+ unsigned char *privkeybuf, size_t privkey_size)
- {
- EVP_PKEY_CTX *ctx = NULL;
- EVP_PKEY *pkey = NULL;
- int ret = SSH_ERR_INTERNAL_ERROR;
-- size_t pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES;
-+ size_t got_pub_size = pubkey_size, got_priv_size = privkey_size;
-+
-+ ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, NULL);
-+
-+ if (ctx == NULL && FIPS_mode()) {
-+ /* We have filtered x25519 + ML-KEM in FIPS mode earlier
-+ * so if we are in FIPS mode and ML-KEM is not available with default propq,
-+ * we can fetch it from the default provider */
-+ ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, FIPS_FALLBACK_PROPQ);
-+ }
-
-- ctx = EVP_PKEY_CTX_new_from_name(NULL, "mlkem768", NULL);
- if (ctx == NULL) {
- ret = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
-@@ -68,17 +82,23 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
- goto err;
- }
-
-- if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &pubkey_size) <= 0
-- || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &privkey_size) <= 0) {
-+ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &got_pub_size) <= 0
-+ || EVP_PKEY_get_raw_private_key(pkey, NULL, &got_priv_size) <= 0) {
- ret = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
- }
-
-- if (privkey_size != crypto_kem_mlkem768_SECRETKEYBYTES
-- || pubkey_size != crypto_kem_mlkem768_PUBLICKEYBYTES) {
-+ if (privkey_size != got_priv_size || pubkey_size != got_pub_size) {
- ret = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
- }
-+
-+ if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &got_pub_size) <= 0
-+ || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &got_priv_size) <= 0) {
-+ ret = SSH_ERR_LIBCRYPTO_ERROR;
-+ goto err;
-+ }
-+
- ret = 0;
-
- err:
-@@ -90,27 +110,57 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
- }
-
- static int
--mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+{
-+ return mlkem_keypair_gen("mlkem768", pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES,
-+ privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
-+}
-+
-+static int
-+mlkem1024_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+{
-+ return mlkem_keypair_gen("mlkem1024", pubkeybuf, crypto_kem_mlkem1024_PUBLICKEYBYTES,
-+ privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES);
-+}
-+
-+static int
-+mlkem_encap_secret(const char *mlkem_alg, const u_char *pubkeybuf, u_char *secret, u_char *out)
- {
- EVP_PKEY *pkey = NULL;
- EVP_PKEY_CTX *ctx = NULL;
- int r = SSH_ERR_INTERNAL_ERROR;
-- size_t outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
-- secretlen = crypto_kem_mlkem768_BYTES;
-+ size_t outlen, expected_outlen, publen, secretlen = crypto_kem_mlkem768_BYTES;
-+ int fips_fallback = 0;
-+
-+ if (strcmp(mlkem_alg, "mlkem768") == 0) {
-+ outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES;
-+ publen = crypto_kem_mlkem768_PUBLICKEYBYTES;
-+ } else if (strcmp(mlkem_alg, "mlkem1024") == 0) {
-+ outlen = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
-+ publen = crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+ } else
-+ return r;
-
-- pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "mlkem768", NULL,
-- pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES);
-+ expected_outlen = outlen;
-+
-+ pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, NULL,
-+ pubkeybuf, publen);
-+ if (pkey == NULL && FIPS_mode()) {
-+ pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, FIPS_FALLBACK_PROPQ,
-+ pubkeybuf, publen);
-+ fips_fallback = 1;
-+ }
- if (pkey == NULL) {
- r = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
- }
-
-- ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
-+ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
- if (ctx == NULL
- || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
-- || EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0
-+ || EVP_PKEY_encapsulate(ctx, out, &expected_outlen, secret, &secretlen) <= 0
- || secretlen != crypto_kem_mlkem768_BYTES
-- || outlen != crypto_kem_mlkem768_CIPHERTEXTBYTES) {
-+ || outlen != expected_outlen) {
- r = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
- }
-@@ -126,25 +176,45 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
- }
-
- static int
--mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+{
-+ return mlkem_encap_secret("mlkem768", pubkeybuf, secret, out);
-+}
-+
-+static int
-+mlkem1024_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+{
-+ return mlkem_encap_secret("mlkem1024", pubkeybuf, secret, out);
-+}
-+
-+static int
-+mlkem_decap_secret(const char *algname,
-+ const u_char *privkeybuf, size_t privkey_len,
-+ const u_char *wrapped, size_t wrapped_len,
-+ u_char *secret)
- {
- EVP_PKEY *pkey = NULL;
- EVP_PKEY_CTX *ctx = NULL;
- int r = SSH_ERR_INTERNAL_ERROR;
-- size_t wrappedlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
-- secretlen = crypto_kem_mlkem768_BYTES;
-+ size_t secretlen = crypto_kem_mlkem768_BYTES;
-+ int fips_fallback = 0;
-
-- pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "mlkem768", NULL,
-- privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
-+ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
-+ NULL, privkeybuf, privkey_len);
-+ if (pkey == NULL && FIPS_mode()) {
-+ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
-+ FIPS_FALLBACK_PROPQ, privkeybuf, privkey_len);
-+ fips_fallback = 1;
-+ }
- if (pkey == NULL) {
- r = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
- }
-
-- ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
-+ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
- if (ctx == NULL
- || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
-- || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrappedlen) <= 0
-+ || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrapped_len) <= 0
- || secretlen != crypto_kem_mlkem768_BYTES) {
- r = SSH_ERR_LIBCRYPTO_ERROR;
- goto err;
-@@ -161,6 +231,20 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s
- return r;
- }
-
-+static int
-+mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+{
-+ return mlkem_decap_secret("mlkem768", privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES,
-+ wrapped, crypto_kem_mlkem768_CIPHERTEXTBYTES, secret);
-+}
-+
-+static int
-+mlkem1024_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+{
-+ return mlkem_decap_secret("mlkem1024", privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES,
-+ wrapped, crypto_kem_mlkem1024_CIPHERTEXTBYTES, secret);
-+}
-+
- int
- kex_kem_mlkem768x25519_keypair(struct kex *kex)
- {
-@@ -337,7 +421,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
- u_char hash[SSH_DIGEST_MAX_LENGTH];
- size_t need;
- int r = SSH_ERR_INTERNAL_ERROR;
-- struct libcrux_mlkem768_enc_result enc; /* FIXME */
-+ struct libcrux_mlkem768_enc_result enc;
-
- *server_blobp = NULL;
- *shared_secretp = NULL;
-@@ -546,6 +630,520 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- return r;
- #endif
- }
-+
-+#define NIST_P256_COMPRESSED_LEN 33
-+#define NIST_P256_UNCOMPRESSED_LEN 65
-+#define NIST_P384_COMPRESSED_LEN 49
-+#define NIST_P384_UNCOMPRESSED_LEN 97
-+#define NIST_BUF_MAX_SIZE NIST_P384_UNCOMPRESSED_LEN
-+
-+static const char ec256[] = "P-256";
-+static const char ec384[] = "P-384";
-+static const char *len2curve_name(size_t len)
-+{
-+ switch (len) {
-+ case NIST_P256_COMPRESSED_LEN:
-+ case NIST_P256_UNCOMPRESSED_LEN:
-+ return ec256;
-+ break;
-+ case NIST_P384_COMPRESSED_LEN:
-+ case NIST_P384_UNCOMPRESSED_LEN:
-+ return ec384;
-+ break;
-+ }
-+ return NULL;
-+}
-+
-+static EVP_PKEY *
-+buf2nist_key(const unsigned char *pub_key_buf, size_t pub_key_len)
-+{
-+ EVP_PKEY *pkey = NULL;
-+ EVP_PKEY_CTX *ctx = NULL;
-+ OSSL_PARAM params[3];
-+ const char *curve_name = len2curve_name(pub_key_len);
-+
-+ if (curve_name == NULL)
-+ return NULL;
-+
-+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
-+ if (!ctx)
-+ goto err;
-+
-+ if (EVP_PKEY_fromdata_init(ctx) <= 0)
-+ goto err;
-+
-+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
-+ params[1] = OSSL_PARAM_construct_octet_string(
-+ OSSL_PKEY_PARAM_PUB_KEY, (void *)pub_key_buf, pub_key_len);
-+ params[2] = OSSL_PARAM_construct_end();
-+
-+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
-+ goto err;
-+
-+ EVP_PKEY_CTX_free(ctx);
-+ return pkey;
-+
-+err:
-+ EVP_PKEY_CTX_free(ctx);
-+ EVP_PKEY_free(pkey);
-+ return NULL;
-+}
-+
-+static int
-+kex_nist_shared_key_ext(EVP_PKEY *priv_key,
-+ const u_char *pub_key_buf, size_t pub_key_len, struct sshbuf *out)
-+{
-+ EVP_PKEY_CTX *ctx = NULL;
-+ unsigned char *shared_secret = NULL;
-+ size_t shared_secret_len = 0;
-+ EVP_PKEY *peer_key = buf2nist_key(pub_key_buf, pub_key_len);
-+ int r = SSH_ERR_INTERNAL_ERROR;
-+
-+ if (peer_key == NULL)
-+ return SSH_ERR_KEY_LENGTH;
-+
-+ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, priv_key, NULL);
-+ if (!ctx)
-+ goto end;
-+
-+ if ((EVP_PKEY_derive_init(ctx) <= 0)
-+ || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0
-+ || EVP_PKEY_derive(ctx, NULL, &shared_secret_len) <= 0)
-+ goto end;
-+
-+ shared_secret = OPENSSL_malloc(shared_secret_len);
-+ if (shared_secret == NULL)
-+ goto end;
-+
-+ if (EVP_PKEY_derive(ctx, shared_secret, &shared_secret_len) <= 0)
-+ goto end;
-+
-+ if ((r = sshbuf_put(out, shared_secret, shared_secret_len)) != 0)
-+ goto end;
-+
-+ r = 0;
-+
-+end:
-+ EVP_PKEY_free(peer_key);
-+ if (shared_secret)
-+ OPENSSL_clear_free(shared_secret, shared_secret_len);
-+ EVP_PKEY_CTX_free(ctx);
-+
-+ return r;
-+}
-+
-+static EVP_PKEY *
-+nist_pkey_keygen(size_t pub_key_len)
-+{
-+ const char *curve_name = len2curve_name(pub_key_len);
-+ EVP_PKEY_CTX *pctx = NULL;
-+ EVP_PKEY *pkey = NULL;
-+ OSSL_PARAM params[2];
-+
-+ if (curve_name == NULL)
-+ return NULL;
-+
-+ pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
-+ if (!pctx)
-+ return NULL;
-+
-+ if (EVP_PKEY_keygen_init(pctx) <= 0) {
-+ EVP_PKEY_CTX_free(pctx);
-+ return NULL;
-+ }
-+
-+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
-+ params[1] = OSSL_PARAM_construct_end();
-+
-+ if (EVP_PKEY_CTX_set_params(pctx, params) <= 0
-+ || EVP_PKEY_keygen(pctx, &pkey) <= 0) {
-+ EVP_PKEY_CTX_free(pctx);
-+ return NULL;
-+ }
-+
-+ EVP_PKEY_CTX_free(pctx);
-+ return pkey;
-+}
-+
-+static size_t decompress_pub_key(void *pub, size_t compressed_len, size_t decompressed_len)
-+{
-+ EC_GROUP *group = NULL;
-+ EC_POINT *point = NULL;
-+ BN_CTX *ctx = NULL;
-+ size_t len = 0;
-+ int group_nid = NID_undef;
-+
-+ switch (compressed_len) {
-+ case NIST_P256_COMPRESSED_LEN:
-+ group_nid = NID_X9_62_prime256v1;
-+ break;
-+ case NIST_P384_COMPRESSED_LEN:
-+ group_nid = NID_secp384r1;
-+ break;
-+ default:
-+ return 0;
-+ break;
-+ }
-+
-+ ctx = BN_CTX_new();
-+ group = EC_GROUP_new_by_curve_name(group_nid);
-+ if (ctx == NULL || group == NULL)
-+ goto err;
-+
-+ point = EC_POINT_new(group);
-+ if (point == NULL)
-+ goto err;
-+
-+ if (!EC_POINT_oct2point(group, point, pub, compressed_len, ctx))
-+ goto err;
-+
-+ len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pub, decompressed_len, ctx);
-+
-+err:
-+ EC_POINT_free(point);
-+ EC_GROUP_free(group);
-+ BN_CTX_free(ctx);
-+
-+ return len;
-+}
-+
-+static int
-+get_uncompressed_ec_pubkey(EVP_PKEY *pkey, unsigned char *buf, size_t buf_len)
-+{
-+ OSSL_PARAM params[2];
-+ size_t required_len = 0, out_len = 0;
-+
-+ params[0] = OSSL_PARAM_construct_utf8_string(
-+ OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT,
-+ "uncompressed", 0);
-+ params[1] = OSSL_PARAM_construct_end();
-+
-+ if (EVP_PKEY_set_params(pkey, params) <= 0
-+ || EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
-+ buf, buf_len, &required_len) <= 0) {
-+ return SSH_ERR_LIBCRYPTO_ERROR;
-+ }
-+
-+ if (required_len != buf_len) {
-+ /* Red Hat certified FIPS provider ignores OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT
-+ * We may have to perform the conversion manually */
-+ if (len2curve_name(required_len) == len2curve_name(buf_len)) {
-+ out_len = decompress_pub_key(buf, required_len, buf_len);
-+ if (out_len != buf_len) {
-+ debug_f("Error decompressing the compressed public key");
-+ return SSH_ERR_LIBCRYPTO_ERROR;
-+ } else {
-+ return 0;
-+ }
-+ } else {
-+ debug_f("Unexpected length of uncompressed public key: expected %d, got %d", buf_len, required_len);
-+ return SSH_ERR_LIBCRYPTO_ERROR;
-+ }
-+ }
-+
-+ return 0;
-+}
-+/* nist_bytes_len should always be uncompressed */
-+static int
-+kex_kem_mlkem_nist_keypair(struct kex *kex, size_t mlkem_bytes_len, size_t nist_bytes_len)
-+{
-+ struct sshbuf *buf = NULL;
-+ u_char *cp = NULL;
-+ size_t need;
-+ int r = SSH_ERR_INTERNAL_ERROR;
-+ u_char *client_key = NULL;
-+
-+ if ((buf = sshbuf_new()) == NULL)
-+ return SSH_ERR_ALLOC_FAIL;
-+ need = mlkem_bytes_len + nist_bytes_len;
-+ if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
-+ goto out;
-+
-+ if (mlkem_bytes_len == crypto_kem_mlkem768_PUBLICKEYBYTES) {
-+ client_key = kex->mlkem768_client_key;
-+ r = mlkem768_keypair_gen(cp, client_key);
-+ }
-+
-+ if (mlkem_bytes_len == crypto_kem_mlkem1024_PUBLICKEYBYTES) {
-+ client_key = kex->mlkem1024_client_key;
-+ r = mlkem1024_keypair_gen(cp, client_key);
-+ }
-+
-+ if (client_key == NULL)
-+ goto out;
-+
-+ if (r != 0)
-+ goto out;
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("client public key mlkemXXX:", cp, mlkem_bytes_len);
-+#endif
-+ cp += mlkem_bytes_len;
-+ if ((kex->ec_hybrid_client_key = nist_pkey_keygen(nist_bytes_len)) == NULL)
-+ goto out;
-+
-+ if ((r = get_uncompressed_ec_pubkey(kex->ec_hybrid_client_key, cp, nist_bytes_len)) != 0)
-+ goto out;
-+
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("client public key NIST:", cp, nist_bytes_len);
-+#endif
-+ /* success */
-+ r = 0;
-+ kex->client_pub = buf;
-+ buf = NULL;
-+ out:
-+ sshbuf_free(buf);
-+ if (r == SSH_ERR_LIBCRYPTO_ERROR)
-+ ERR_print_errors_fp(stderr);
-+
-+ return r;
-+}
-+
-+static int
-+kex_kem_mlkem_nist_enc(struct kex *kex, const char *nist_curve,
-+ const struct sshbuf *client_blob, struct sshbuf **server_blobp,
-+ struct sshbuf **shared_secretp)
-+{
-+ struct sshbuf *server_blob = NULL;
-+ struct sshbuf *buf = NULL;
-+ const u_char *client_pub;
-+ u_char server_pub[NIST_BUF_MAX_SIZE];
-+ u_char enc_out[crypto_kem_mlkem1024_CIPHERTEXTBYTES];
-+ u_char secret[crypto_kem_mlkem768_BYTES];
-+ EVP_PKEY *server_key = NULL;
-+ u_char hash[SSH_DIGEST_MAX_LENGTH];
-+ size_t client_buf_len, mlkem_buf_len, ecdh_buf_len, server_key_len, enc_out_len;
-+ int r = SSH_ERR_INTERNAL_ERROR;
-+
-+ *server_blobp = NULL;
-+ *shared_secretp = NULL;
-+
-+ client_buf_len = sshbuf_len(client_blob);
-+ /* client_blob contains both KEM and ECDH client pubkeys */
-+ if (strcmp(nist_curve, "P-256") == 0) {
-+ if (crypto_kem_mlkem768_PUBLICKEYBYTES > client_buf_len)
-+ return r;
-+
-+ ecdh_buf_len = client_buf_len - crypto_kem_mlkem768_PUBLICKEYBYTES;
-+ if (ecdh_buf_len != NIST_P256_COMPRESSED_LEN &&
-+ ecdh_buf_len != NIST_P256_UNCOMPRESSED_LEN)
-+ return r;
-+ mlkem_buf_len = crypto_kem_mlkem768_PUBLICKEYBYTES;
-+ enc_out_len = crypto_kem_mlkem768_CIPHERTEXTBYTES;
-+ server_key_len = NIST_P256_UNCOMPRESSED_LEN;
-+ } else if (strcmp(nist_curve, "P-384") == 0) {
-+ if (crypto_kem_mlkem1024_PUBLICKEYBYTES > client_buf_len)
-+ return r;
-+
-+ ecdh_buf_len = client_buf_len - crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+ if (ecdh_buf_len != NIST_P384_COMPRESSED_LEN &&
-+ ecdh_buf_len != NIST_P384_UNCOMPRESSED_LEN)
-+ return r;
-+ mlkem_buf_len = crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+ enc_out_len = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
-+ server_key_len = NIST_P384_UNCOMPRESSED_LEN;
-+ } else
-+ return r;
-+
-+ client_pub = sshbuf_ptr(client_blob);
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("client public key mlkem:", client_pub, mlkem_buf_len);
-+ dump_digest("client public key NIST:", client_pub + mlkem_buf_len, ecdh_buf_len);
-+#endif
-+
-+ /* allocate buffer for concatenation of KEM key and ECDH shared key */
-+ /* the buffer will be hashed and the result is the shared secret */
-+ if ((buf = sshbuf_new()) == NULL) {
-+ r = SSH_ERR_ALLOC_FAIL;
-+ goto out;
-+ }
-+ /* allocate space for encrypted KEM key and ECDH pub key */
-+ if ((server_blob = sshbuf_new()) == NULL) {
-+ r = SSH_ERR_ALLOC_FAIL;
-+ goto out;
-+ }
-+ r = (mlkem_buf_len == crypto_kem_mlkem768_PUBLICKEYBYTES) ?
-+ mlkem768_encap_secret(client_pub, secret, enc_out) :
-+ mlkem1024_encap_secret(client_pub, secret, enc_out);
-+
-+ if (r != 0)
-+ goto out;
-+
-+ /* generate ECDH key pair, store server pubkey after ciphertext */
-+ server_key = nist_pkey_keygen(server_key_len);
-+
-+ if ((server_key == NULL) ||
-+ (r = get_uncompressed_ec_pubkey(server_key, server_pub, server_key_len) != 0) ||
-+ (r = sshbuf_put(buf, secret, sizeof(secret))) != 0 ||
-+ (r = sshbuf_put(server_blob, enc_out, enc_out_len) != 0)||
-+ (r = sshbuf_put(server_blob, server_pub, server_key_len)) != 0)
-+ goto out;
-+
-+ /* append ECDH shared key */
-+ client_pub += mlkem_buf_len;
-+ if ((r = kex_nist_shared_key_ext(server_key, client_pub, ecdh_buf_len, buf)) < 0)
-+ goto out;
-+ if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
-+ goto out;
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("server public NIST:", server_pub, server_key_len);
-+ dump_digest("server cipher text:", enc_out, enc_out_len);
-+ dump_digest("server kem key:", secret, sizeof(secret));
-+ dump_digest("concatenation of KEM key and ECDH shared key:",
-+ sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+ /* string-encoded hash is resulting shared secret */
-+ sshbuf_reset(buf);
-+ if ((r = sshbuf_put_string(buf, hash,
-+ ssh_digest_bytes(kex->hash_alg))) != 0)
-+ goto out;
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+ /* success */
-+ r = 0;
-+ *server_blobp = server_blob;
-+ *shared_secretp = buf;
-+ server_blob = NULL;
-+ buf = NULL;
-+ out:
-+ explicit_bzero(hash, sizeof(hash));
-+ EVP_PKEY_free(server_key);
-+ explicit_bzero(enc_out, sizeof(enc_out));
-+ explicit_bzero(secret, sizeof(secret));
-+ sshbuf_free(server_blob);
-+ sshbuf_free(buf);
-+ return r;
-+}
-+
-+static int
-+kex_kem_mlkem_nist_dec(struct kex *kex,
-+ const struct sshbuf *server_blob, struct sshbuf **shared_secretp,
-+ size_t mlkem_len)
-+{
-+ struct sshbuf *buf = NULL;
-+ const u_char *ciphertext, *server_pub;
-+ u_char hash[SSH_DIGEST_MAX_LENGTH];
-+ u_char decap[crypto_kem_mlkem768_BYTES];
-+ int r;
-+ size_t nist_len;
-+
-+ *shared_secretp = NULL;
-+
-+ if (sshbuf_len(server_blob) < mlkem_len) {
-+ r = SSH_ERR_SIGNATURE_INVALID;
-+ goto out;
-+ }
-+
-+ nist_len = sshbuf_len(server_blob) - mlkem_len;
-+
-+ switch (mlkem_len) {
-+ case crypto_kem_mlkem768_CIPHERTEXTBYTES:
-+ if (nist_len != NIST_P256_COMPRESSED_LEN
-+ && nist_len != NIST_P256_UNCOMPRESSED_LEN) {
-+ r = SSH_ERR_SIGNATURE_INVALID;
-+ goto out;
-+ }
-+ break;
-+ case crypto_kem_mlkem1024_CIPHERTEXTBYTES:
-+ if (nist_len != NIST_P384_COMPRESSED_LEN
-+ && nist_len != NIST_P384_UNCOMPRESSED_LEN) {
-+ r = SSH_ERR_SIGNATURE_INVALID;
-+ goto out;
-+ }
-+ break;
-+ }
-+
-+ ciphertext = sshbuf_ptr(server_blob);
-+ server_pub = ciphertext + mlkem_len;
-+ /* hash concatenation of KEM key and ECDH shared key */
-+ if ((buf = sshbuf_new()) == NULL) {
-+ r = SSH_ERR_ALLOC_FAIL;
-+ goto out;
-+ }
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("server cipher text:", ciphertext, mlkem_len);
-+ dump_digest("server public key NIST:", server_pub, nist_len);
-+#endif
-+ r = (mlkem_len == crypto_kem_mlkem768_CIPHERTEXTBYTES) ?
-+ mlkem768_decap_secret(kex->mlkem768_client_key, ciphertext, decap) :
-+ mlkem1024_decap_secret(kex->mlkem1024_client_key, ciphertext, decap);
-+
-+ if (r != 0)
-+ goto out;
-+ if ((r = sshbuf_put(buf, decap, sizeof(decap))) != 0)
-+ goto out;
-+ if ((r = kex_nist_shared_key_ext(kex->ec_hybrid_client_key, server_pub,
-+ nist_len, buf)) < 0)
-+ goto out;
-+ if ((r = ssh_digest_buffer(kex->hash_alg, buf,
-+ hash, sizeof(hash))) != 0)
-+ goto out;
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("client kem key:", decap, sizeof(decap));
-+ dump_digest("concatenation of KEM key and ECDH shared key:",
-+ sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+ sshbuf_reset(buf);
-+ if ((r = sshbuf_put_string(buf, hash,
-+ ssh_digest_bytes(kex->hash_alg))) != 0)
-+ goto out;
-+#ifdef DEBUG_KEXECDH
-+ dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+ /* success */
-+ r = 0;
-+ *shared_secretp = buf;
-+ buf = NULL;
-+ out:
-+ explicit_bzero(hash, sizeof(hash));
-+ explicit_bzero(decap, sizeof(decap));
-+ sshbuf_free(buf);
-+ return r;
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_keypair(struct kex *kex)
-+{
-+ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem768_PUBLICKEYBYTES, NIST_P256_UNCOMPRESSED_LEN);
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_enc(struct kex *kex, const struct sshbuf *client_blob,
-+ struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
-+{
-+ return kex_kem_mlkem_nist_enc(kex, "P-256", client_blob, server_blobp, shared_secretp);
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_dec(struct kex *kex, const struct sshbuf *server_blob,
-+ struct sshbuf **shared_secretp)
-+{
-+ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
-+ crypto_kem_mlkem768_CIPHERTEXTBYTES);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_keypair(struct kex *kex)
-+{
-+ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem1024_PUBLICKEYBYTES, NIST_P384_UNCOMPRESSED_LEN);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_enc(struct kex *kex, const struct sshbuf *client_blob,
-+ struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
-+{
-+ return kex_kem_mlkem_nist_enc(kex, "P-384", client_blob, server_blobp, shared_secretp);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_dec(struct kex *kex, const struct sshbuf *server_blob,
-+ struct sshbuf **shared_secretp)
-+{
-+ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
-+ crypto_kem_mlkem1024_CIPHERTEXTBYTES);
-+}
-+
- #else /* USE_MLKEM768X25519 */
- int
- kex_kem_mlkem768x25519_keypair(struct kex *kex)
-@@ -567,4 +1165,39 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- {
- return SSH_ERR_SIGN_ALG_UNSUPPORTED;
- }
-+
-+int kex_kem_mlkem768nistp256_keypair(struct kex *)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
-+ struct sshbuf **, struct sshbuf **)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
-+ struct sshbuf **)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int kex_kem_mlkem1024nistp384_keypair(struct kex *)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
-+ struct sshbuf **, struct sshbuf **)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
-+ struct sshbuf **)
-+{
-+ return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
- #endif /* USE_MLKEM768X25519 */
-diff --git a/monitor.c b/monitor.c
-index bf5a5105b..36db1afee 100644
---- a/monitor.c
-+++ b/monitor.c
-@@ -2050,6 +2050,8 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
- kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- kex->load_host_public_key=&get_hostkey_public_by_type;
- kex->load_host_private_key=&get_hostkey_private_by_type;
- kex->host_key_index=&get_hostkey_index;
-diff --git a/myproposal.h b/myproposal.h
-index 6433e0820..51e3ef5c8 100644
---- a/myproposal.h
-+++ b/myproposal.h
-@@ -26,6 +26,8 @@
-
- #define KEX_SERVER_KEX \
- "mlkem768x25519-sha256," \
-+ "mlkem768nistp256-sha256," \
-+ "mlkem1024nistp384-sha384," \
- "sntrup761x25519-sha512," \
- "sntrup761x25519-sha512@openssh.com," \
- "curve25519-sha256," \
-@@ -99,6 +101,8 @@
- "aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se," \
- "aes128-gcm@openssh.com,aes256-gcm@openssh.com"
- #define KEX_DEFAULT_KEX_FIPS \
-+ "mlkem768nistp256-sha256," \
-+ "mlkem1024nistp384-sha384," \
- "ecdh-sha2-nistp256," \
- "ecdh-sha2-nistp384," \
- "ecdh-sha2-nistp521," \
-diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
-index f4700deeb..99a8e3b46 100644
---- a/regress/unittests/kex/test_kex.c
-+++ b/regress/unittests/kex/test_kex.c
-@@ -171,6 +171,8 @@ do_kex_with_key(char *kex, char *cipher, char *mac,
- server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- server2->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+ server2->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+ server2->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- server2->kex->load_host_public_key = server->kex->load_host_public_key;
- server2->kex->load_host_private_key = server->kex->load_host_private_key;
- server2->kex->sign = server->kex->sign;
-@@ -248,6 +250,8 @@ kex_tests(void)
- }
- # ifdef USE_MLKEM768X25519
- do_kex("mlkem768x25519-sha256");
-+ do_kex("mlkem768nistp256-sha256");
-+ do_kex("mlkem1024nistp384-sha384");
- # endif /* USE_MLKEM768X25519 */
- # ifdef USE_SNTRUP761X25519
- do_kex("sntrup761x25519-sha512");
-diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index 3cea1daac..f5ce90434 100644
---- a/ssh-keyscan.c
-+++ b/ssh-keyscan.c
-@@ -300,6 +300,8 @@ keygrab_ssh2(con *c)
- c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- c->c_ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+ c->c_ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+ c->c_ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper);
- /*
- * do the key-exchange until an error occurs or until
-diff --git a/ssh_api.c b/ssh_api.c
-index 38ac17da1..b178582b3 100644
---- a/ssh_api.c
-+++ b/ssh_api.c
-@@ -135,6 +135,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
- ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- ssh->kex->load_host_public_key=&_ssh_host_public_key;
- ssh->kex->load_host_private_key=&_ssh_host_private_key;
- ssh->kex->sign=&_ssh_host_key_sign;
-@@ -154,6 +156,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
- ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- ssh->kex->verify_host_key =&_ssh_verify_host_key;
- }
- *sshp = ssh;
-diff --git a/sshconnect2.c b/sshconnect2.c
-index eee1f9794..50c228802 100644
---- a/sshconnect2.c
-+++ b/sshconnect2.c
-@@ -350,6 +350,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- ssh->kex->verify_host_key=&verify_host_key_callback;
-
- #if defined(GSSAPI) && defined(WITH_OPENSSL)
-diff --git a/sshd-auth.c b/sshd-auth.c
-index 885cd8281..a5d2a1a6e 100644
---- a/sshd-auth.c
-+++ b/sshd-auth.c
-@@ -893,6 +893,8 @@ do_ssh2_kex(struct ssh *ssh)
- kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- kex->load_host_public_key=&get_hostkey_public_by_type;
- kex->load_host_private_key=&get_hostkey_private_by_type;
- kex->host_key_index=&get_hostkey_index;
---
-2.53.0
-
diff --git a/0048-openssh-7.3p1-x11-max-displays.patch b/0048-openssh-7.3p1-x11-max-displays.patch
new file mode 100644
index 0000000..c385586
--- /dev/null
+++ b/0048-openssh-7.3p1-x11-max-displays.patch
@@ -0,0 +1,150 @@
+From 9e952349b6b64ff2916199323b1db909c3f1d6f3 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Fri, 12 Dec 2025 15:35:14 +0100
+Subject: [PATCH 48/53] openssh-7.3p1-x11-max-displays
+
+Move MAX_DISPLAYS to a configuration option (#1341302)
+---
+ channels.c | 9 ++++++---
+ channels.h | 2 +-
+ servconf.c | 5 +++++
+ servconf.h | 2 ++
+ session.c | 5 +++--
+ sshd_config.5 | 7 +++++++
+ 6 files changed, 24 insertions(+), 6 deletions(-)
+
+diff --git a/channels.c b/channels.c
+index bae84e5f9..48ec0ab83 100644
+--- a/channels.c
++++ b/channels.c
+@@ -5085,7 +5085,7 @@ rdynamic_connect_finish(struct ssh *ssh, Channel *c)
+ */
+ int
+ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+- int x11_use_localhost, int single_connection,
++ int x11_use_localhost, int x11_max_displays, int single_connection,
+ u_int *display_numberp, int **chanids)
+ {
+ Channel *nc = NULL;
+@@ -5098,8 +5098,11 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+ x11_display_offset > UINT16_MAX - X11_BASE_PORT - MAX_DISPLAYS)
+ return -1;
+
++ /* Try to bind ports starting at 6000+X11DisplayOffset */
++ x11_max_displays = x11_max_displays + x11_display_offset;
++
+ for (display_number = x11_display_offset;
+- display_number < x11_display_offset + MAX_DISPLAYS;
++ display_number < x11_max_displays;
+ display_number++) {
+ port = X11_BASE_PORT + display_number;
+ memset(&hints, 0, sizeof(hints));
+@@ -5164,7 +5167,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+ if (num_socks > 0)
+ break;
+ }
+- if (display_number >= x11_display_offset + MAX_DISPLAYS) {
++ if (display_number >= x11_max_displays || port < X11_BASE_PORT ) {
+ error("Failed to allocate internet-domain X11 display socket.");
+ return -1;
+ }
+diff --git a/channels.h b/channels.h
+index f69096498..bc3b52aa2 100644
+--- a/channels.h
++++ b/channels.h
+@@ -391,7 +391,7 @@ int permitopen_port(const char *);
+
+ void channel_set_x11_refuse_time(struct ssh *, time_t);
+ int x11_connect_display(struct ssh *);
+-int x11_create_display_inet(struct ssh *, int, int, int, u_int *, int **);
++int x11_create_display_inet(struct ssh *, int, int, int, int, u_int *, int **);
+ void x11_request_forwarding_with_spoofing(struct ssh *, int,
+ const char *, const char *, const char *, int);
+ int x11_channel_used_recently(struct ssh *ssh);
+diff --git a/servconf.c b/servconf.c
+index d9cccc17c..8746a2417 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -1533,6 +1533,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ *intptr = value;
+ break;
+
++ case sX11MaxDisplays:
++ intptr = &options->x11_max_displays;
++ goto parse_int;
++
+ case sX11UseLocalhost:
+ intptr = &options->x11_use_localhost;
+ goto parse_flag;
+@@ -4249,6 +4253,7 @@ dump_config(ServerOptions *o)
+ #endif
+ dump_cfg_int(sLoginGraceTime, o->login_grace_time);
+ dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
++ dump_cfg_int(sX11MaxDisplays, o->x11_max_displays);
+ dump_cfg_int(sMaxAuthTries, o->max_authtries);
+ dump_cfg_int(sMaxSessions, o->max_sessions);
+ dump_cfg_int(sClientAliveInterval, o->client_alive_interval);
+diff --git a/servconf.h b/servconf.h
+index 81e699655..4e5f7edc6 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -40,6 +40,7 @@ struct sshbuf;
+
+ #define DEFAULT_AUTH_FAIL_MAX 6 /* Default for MaxAuthTries */
+ #define DEFAULT_SESSIONS_MAX 10 /* Default for MaxSessions */
++#define DEFAULT_MAX_DISPLAYS 1000 /* Maximum number of fake X11 displays to try. */
+
+ /* Magic name for internal sftp-server */
+ #define INTERNAL_SFTP_NAME "internal-sftp"
+@@ -172,6 +173,7 @@ SSHCONF_INTFLAG(ignore_user_known_hosts, IgnoreUserKnownHosts, SSHCFG_GLOBAL, 0,
+ SSHCONF_INTFLAG(print_motd, PrintMotd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(x11_forwarding, X11Forwarding, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INT(x11_display_offset, X11DisplayOffset, SSHCFG_ALL, NULL, SSHD_DEFAULT_X11_DISPLAY_OFFSET, SSHCFG_COPY_MATCH) \
++SSHCONF_INT(x11_max_displays, X11MaxDisplays, SSHCFG_ALL, NULL, DEFAULT_MAX_DISPLAYS, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(x11_use_localhost, X11UseLocalhost, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+ SSHCONF_STRING(xauth_location, XAuthLocation, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(permit_tty, PermitTTY, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+diff --git a/session.c b/session.c
+index bd8dc808c..e2cf13bdc 100644
+--- a/session.c
++++ b/session.c
+@@ -2693,8 +2693,9 @@ session_setup_x11fwd(struct ssh *ssh, Session *s)
+ return 0;
+ }
+ if (x11_create_display_inet(ssh, options.x11_display_offset,
+- options.x11_use_localhost, s->single_connection,
+- &s->display_number, &s->x11_chanids) == -1) {
++ options.x11_use_localhost, options.x11_max_displays,
++ s->single_connection, &s->display_number,
++ &s->x11_chanids) == -1) {
+ debug("x11_create_display_inet failed.");
+ return 0;
+ }
+diff --git a/sshd_config.5 b/sshd_config.5
+index 2a8f3abea..bf9efc897 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1430,6 +1430,7 @@ Available keywords are
+ .Cm TrustedUserCAKeys ,
+ .Cm UnusedConnectionTimeout ,
+ .Cm X11DisplayOffset ,
++.Cm X11MaxDisplays ,
+ .Cm X11Forwarding
+ and
+ .Cm X11UseLocalhost .
+@@ -2147,6 +2148,12 @@ Specifies the first display number available for
+ X11 forwarding.
+ This prevents sshd from interfering with real X11 servers.
+ The default is 10.
++.It Cm X11MaxDisplays
++Specifies the maximum number of displays available for
++.Xr sshd 8 Ns 's
++X11 forwarding.
++This prevents sshd from exhausting local ports.
++The default is 1000.
+ .It Cm X11Forwarding
+ Specifies whether X11 forwarding is permitted.
+ The argument must be
+--
+2.55.0
+
diff --git a/0049-Fix-ssh-pkcs11-client-helper-termination.patch b/0049-Fix-ssh-pkcs11-client-helper-termination.patch
new file mode 100644
index 0000000..ba1d2d7
--- /dev/null
+++ b/0049-Fix-ssh-pkcs11-client-helper-termination.patch
@@ -0,0 +1,48 @@
+From 3bb305e252bed53087559ccedfe631a09b870a9c Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:59:26 +0200
+Subject: [PATCH 49/53] Fix ssh-pkcs11-client helper termination
+
+Don't terminate the PKCS#11 helper when SSH2_AGENT_FAILURE is returned.
+This is a legitimate response when a token requires PIN authentication.
+The helper must remain active so it can prompt for PIN during actual key use.
+
+Only terminate the helper on unexpected responses or communication failures.
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ ssh-pkcs11-client.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
+index 6d74d7280..30a4cf5dc 100644
+--- a/ssh-pkcs11-client.c
++++ b/ssh-pkcs11-client.c
+@@ -429,12 +429,18 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ }
+ /* success */
+ ret = 0;
+- } else if (type == SSH2_AGENT_FAILURE) {
+- if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
+- error_fr(r, "failed to parse failure response");
+- }
+- if (ret != 0) {
+- debug_f("no keys; terminate helper");
++ } else if (type == SSH_AGENT_FAILURE || type == SSH2_AGENT_FAILURE) {
++ if (type == SSH2_AGENT_FAILURE) {
++ if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
++ error_fr(r, "failed to parse failure response");
++ }
++ /* Provider registered but returned no keys (may need PIN) */
++ /* Don't terminate helper - keep it for later use */
++ ret = 0;
++ nkeys = 0;
++ } else {
++ /* Unexpected response - terminate helper */
++ debug_f("unexpected response %d; terminate helper", type);
+ helper_terminate(helper);
+ }
+ sshbuf_free(msg);
+--
+2.55.0
+
diff --git a/0049-openssh-7.3p1-x11-max-displays.patch b/0049-openssh-7.3p1-x11-max-displays.patch
deleted file mode 100644
index 3e9592c..0000000
--- a/0049-openssh-7.3p1-x11-max-displays.patch
+++ /dev/null
@@ -1,192 +0,0 @@
-From f8a244536a7925d63047245a34e8f062227ac56f Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Fri, 12 Dec 2025 15:35:14 +0100
-Subject: [PATCH 49/54] openssh-7.3p1-x11-max-displays
-
-Move MAX_DISPLAYS to a configuration option (#1341302)
----
- channels.c | 9 ++++++---
- channels.h | 2 +-
- servconf.c | 12 +++++++++++-
- servconf.h | 2 ++
- session.c | 5 +++--
- sshd_config.5 | 7 +++++++
- 6 files changed, 30 insertions(+), 7 deletions(-)
-
-diff --git a/channels.c b/channels.c
-index 14d536d64..73eacc5f6 100644
---- a/channels.c
-+++ b/channels.c
-@@ -5076,7 +5076,7 @@ rdynamic_connect_finish(struct ssh *ssh, Channel *c)
- */
- int
- x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
-- int x11_use_localhost, int single_connection,
-+ int x11_use_localhost, int x11_max_displays, int single_connection,
- u_int *display_numberp, int **chanids)
- {
- Channel *nc = NULL;
-@@ -5089,8 +5089,11 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
- x11_display_offset > UINT16_MAX - X11_BASE_PORT - MAX_DISPLAYS)
- return -1;
-
-+ /* Try to bind ports starting at 6000+X11DisplayOffset */
-+ x11_max_displays = x11_max_displays + x11_display_offset;
-+
- for (display_number = x11_display_offset;
-- display_number < x11_display_offset + MAX_DISPLAYS;
-+ display_number < x11_max_displays;
- display_number++) {
- port = X11_BASE_PORT + display_number;
- memset(&hints, 0, sizeof(hints));
-@@ -5155,7 +5158,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
- if (num_socks > 0)
- break;
- }
-- if (display_number >= x11_display_offset + MAX_DISPLAYS) {
-+ if (display_number >= x11_max_displays || port < X11_BASE_PORT ) {
- error("Failed to allocate internet-domain X11 display socket.");
- return -1;
- }
-diff --git a/channels.h b/channels.h
-index 2fcf9f8cb..7bf37b053 100644
---- a/channels.h
-+++ b/channels.h
-@@ -390,7 +390,7 @@ int permitopen_port(const char *);
-
- void channel_set_x11_refuse_time(struct ssh *, time_t);
- int x11_connect_display(struct ssh *);
--int x11_create_display_inet(struct ssh *, int, int, int, u_int *, int **);
-+int x11_create_display_inet(struct ssh *, int, int, int, int, u_int *, int **);
- void x11_request_forwarding_with_spoofing(struct ssh *, int,
- const char *, const char *, const char *, int);
- int x11_channel_used_recently(struct ssh *ssh);
-diff --git a/servconf.c b/servconf.c
-index 268c09d1c..f4d6b19dd 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -118,6 +118,7 @@ initialize_server_options(ServerOptions *options)
- options->print_lastlog = -1;
- options->x11_forwarding = -1;
- options->x11_display_offset = -1;
-+ options->x11_max_displays = -1;
- options->x11_use_localhost = -1;
- options->permit_tty = -1;
- options->permit_user_rc = -1;
-@@ -359,6 +360,8 @@ fill_default_server_options(ServerOptions *options)
- options->x11_forwarding = 0;
- if (options->x11_display_offset == -1)
- options->x11_display_offset = 10;
-+ if (options->x11_max_displays == -1)
-+ options->x11_max_displays = DEFAULT_MAX_DISPLAYS;
- if (options->x11_use_localhost == -1)
- options->x11_use_localhost = 1;
- if (options->xauth_location == NULL)
-@@ -588,7 +591,7 @@ typedef enum {
- sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication,
- sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- sPrintMotd, sPrintLastLog, sIgnoreRhosts,
-- sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-+ sX11Forwarding, sX11DisplayOffset, sX11MaxDisplays, sX11UseLocalhost,
- sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive,
- sPermitUserEnvironment, sAllowTcpForwarding, sCompression,
- sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
-@@ -730,6 +733,7 @@ static struct {
- { "ignoreuserknownhosts", sIgnoreUserKnownHosts, SSHCFG_GLOBAL },
- { "x11forwarding", sX11Forwarding, SSHCFG_ALL },
- { "x11displayoffset", sX11DisplayOffset, SSHCFG_ALL },
-+ { "x11maxdisplays", sX11MaxDisplays, SSHCFG_ALL },
- { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
- { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
- { "strictmodes", sStrictModes, SSHCFG_GLOBAL },
-@@ -1781,6 +1785,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- *intptr = value;
- break;
-
-+ case sX11MaxDisplays:
-+ intptr = &options->x11_max_displays;
-+ goto parse_int;
-+
- case sX11UseLocalhost:
- intptr = &options->x11_use_localhost;
- goto parse_flag;
-@@ -3065,6 +3073,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- M_CP_INTOPT(fwd_opts.streamlocal_bind_unlink);
- M_CP_INTOPT(x11_display_offset);
- M_CP_INTOPT(x11_forwarding);
-+ M_CP_INTOPT(x11_max_displays);
- M_CP_INTOPT(x11_use_localhost);
- M_CP_INTOPT(permit_tty);
- M_CP_INTOPT(permit_user_rc);
-@@ -3361,6 +3370,7 @@ dump_config(ServerOptions *o)
- #endif
- dump_cfg_int(sLoginGraceTime, o->login_grace_time);
- dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
-+ dump_cfg_int(sX11MaxDisplays, o->x11_max_displays);
- dump_cfg_int(sMaxAuthTries, o->max_authtries);
- dump_cfg_int(sMaxSessions, o->max_sessions);
- dump_cfg_int(sClientAliveInterval, o->client_alive_interval);
-diff --git a/servconf.h b/servconf.h
-index 781f20c61..fea25947e 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -38,6 +38,7 @@
-
- #define DEFAULT_AUTH_FAIL_MAX 6 /* Default for MaxAuthTries */
- #define DEFAULT_SESSIONS_MAX 10 /* Default for MaxSessions */
-+#define DEFAULT_MAX_DISPLAYS 1000 /* Maximum number of fake X11 displays to try. */
-
- /* Magic name for internal sftp-server */
- #define INTERNAL_SFTP_NAME "internal-sftp"
-@@ -115,6 +116,7 @@ typedef struct {
- int x11_forwarding; /* If true, permit inet (spoofing) X11 fwd. */
- int x11_display_offset; /* What DISPLAY number to start
- * searching at */
-+ int x11_max_displays; /* Number of displays to search */
- int x11_use_localhost; /* If true, use localhost for fake X11 server. */
- char *xauth_location; /* Location of xauth program */
- int permit_tty; /* If false, deny pty allocation */
-diff --git a/session.c b/session.c
-index 1a7f5ca7a..52f86fd46 100644
---- a/session.c
-+++ b/session.c
-@@ -2689,8 +2689,9 @@ session_setup_x11fwd(struct ssh *ssh, Session *s)
- return 0;
- }
- if (x11_create_display_inet(ssh, options.x11_display_offset,
-- options.x11_use_localhost, s->single_connection,
-- &s->display_number, &s->x11_chanids) == -1) {
-+ options.x11_use_localhost, options.x11_max_displays,
-+ s->single_connection, &s->display_number,
-+ &s->x11_chanids) == -1) {
- debug("x11_create_display_inet failed.");
- return 0;
- }
-diff --git a/sshd_config.5 b/sshd_config.5
-index 4ca80016c..c28220077 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -1416,6 +1416,7 @@ Available keywords are
- .Cm TrustedUserCAKeys ,
- .Cm UnusedConnectionTimeout ,
- .Cm X11DisplayOffset ,
-+.Cm X11MaxDisplays ,
- .Cm X11Forwarding
- and
- .Cm X11UseLocalhost .
-@@ -2133,6 +2134,12 @@ Specifies the first display number available for
- X11 forwarding.
- This prevents sshd from interfering with real X11 servers.
- The default is 10.
-+.It Cm X11MaxDisplays
-+Specifies the maximum number of displays available for
-+.Xr sshd 8 Ns 's
-+X11 forwarding.
-+This prevents sshd from exhausting local ports.
-+The default is 1000.
- .It Cm X11Forwarding
- Specifies whether X11 forwarding is permitted.
- The argument must be
---
-2.53.0
-
diff --git a/0050-Fix-ssh-pkcs11-client-helper-termination.patch b/0050-Fix-ssh-pkcs11-client-helper-termination.patch
deleted file mode 100644
index 1b85620..0000000
--- a/0050-Fix-ssh-pkcs11-client-helper-termination.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-From f9f68e3d5e1d4bf2e945a189c6688b27abf9810b Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:59:26 +0200
-Subject: [PATCH 50/54] Fix ssh-pkcs11-client helper termination
-
-Don't terminate the PKCS#11 helper when SSH2_AGENT_FAILURE is returned.
-This is a legitimate response when a token requires PIN authentication.
-The helper must remain active so it can prompt for PIN during actual key use.
-
-Only terminate the helper on unexpected responses or communication failures.
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- ssh-pkcs11-client.c | 18 ++++++++++++------
- 1 file changed, 12 insertions(+), 6 deletions(-)
-
-diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
-index 6d74d7280..30a4cf5dc 100644
---- a/ssh-pkcs11-client.c
-+++ b/ssh-pkcs11-client.c
-@@ -429,12 +429,18 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- }
- /* success */
- ret = 0;
-- } else if (type == SSH2_AGENT_FAILURE) {
-- if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
-- error_fr(r, "failed to parse failure response");
-- }
-- if (ret != 0) {
-- debug_f("no keys; terminate helper");
-+ } else if (type == SSH_AGENT_FAILURE || type == SSH2_AGENT_FAILURE) {
-+ if (type == SSH2_AGENT_FAILURE) {
-+ if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
-+ error_fr(r, "failed to parse failure response");
-+ }
-+ /* Provider registered but returned no keys (may need PIN) */
-+ /* Don't terminate helper - keep it for later use */
-+ ret = 0;
-+ nkeys = 0;
-+ } else {
-+ /* Unexpected response - terminate helper */
-+ debug_f("unexpected response %d; terminate helper", type);
- helper_terminate(helper);
- }
- sshbuf_free(msg);
---
-2.53.0
-
diff --git a/0050-openssh-10.2p1-pam-auth.patch b/0050-openssh-10.2p1-pam-auth.patch
new file mode 100644
index 0000000..85eb0e6
--- /dev/null
+++ b/0050-openssh-10.2p1-pam-auth.patch
@@ -0,0 +1,43 @@
+From 4a9ecd9744225d274ec05a4aab66acef25498288 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:25:04 +0200
+Subject: [PATCH 50/53] openssh-10.2p1-pam-auth
+
+https://bugzilla.redhat.com/show_bug.cgi?id=2423900
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-pam.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/auth-pam.c b/auth-pam.c
+index d3dcd037b..aaad16765 100644
+--- a/auth-pam.c
++++ b/auth-pam.c
+@@ -455,6 +455,7 @@ static int
+ check_pam_user(Authctxt *authctxt)
+ {
+ const char *pam_user;
++ const struct passwd *pam_pw;
+
+ if (authctxt == NULL || authctxt->pw == NULL ||
+ authctxt->pw->pw_name == NULL)
+@@ -469,11 +470,10 @@ check_pam_user(Authctxt *authctxt)
+ return PAM_USER_UNKNOWN;
+ }
+
+- if (sshpam_initial_user == NULL)
+- fatal_f("internal error: sshpam_initial_user NULL");
+- if (strcmp(sshpam_initial_user, pam_user) != 0) {
+- error_f("PAM user \"%s\" does not match previous \"%s\"",
+- pam_user, sshpam_initial_user);
++ pam_pw = getpwnam(pam_user);
++ if (pam_pw == NULL || pam_pw->pw_uid != authctxt->pw->pw_uid) {
++ debug("PAM user \"%s\" does not match expected \"%s\"",
++ pam_user, authctxt->pw->pw_name);
+ return PAM_USER_UNKNOWN;
+ }
+ return PAM_SUCCESS;
+--
+2.55.0
+
diff --git a/0051-gssapi-s4u.patch b/0051-gssapi-s4u.patch
new file mode 100644
index 0000000..9061918
--- /dev/null
+++ b/0051-gssapi-s4u.patch
@@ -0,0 +1,1027 @@
+From 13df14017a530856a20dcc0208a67e126178d392 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:25:21 +0200
+Subject: [PATCH 51/53] gssapi-s4u
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-krb5.c | 13 ++
+ configure.ac | 10 +-
+ gss-serv-krb5.c | 225 ++++++++++++++++++++++++++++
+ gss-serv.c | 388 ++++++++++++++++++++++++++++++++++++++++++++++--
+ servconf.c | 46 ++++++
+ servconf.h | 8 +-
+ ssh-gss.h | 13 ++
+ sshd-session.c | 98 ++++++++++++
+ sshd_config.5 | 70 +++++++++
+ 9 files changed, 851 insertions(+), 20 deletions(-)
+
+diff --git a/auth-krb5.c b/auth-krb5.c
+index 2e3c1c80f..9cd3618b2 100644
+--- a/auth-krb5.c
++++ b/auth-krb5.c
+@@ -465,6 +465,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
+ * a primary cache for this collection, if it supports that (non-FILE)
+ */
+ if (krb5_cc_support_switch(ctx, type)) {
++ /*
++ * For collection-type caches (KCM, KEYRING, …) reuse the
++ * existing primary ccache when one is already present. The
++ * caller will reinitialise it with krb5_cc_initialize(), so
++ * its old contents are replaced rather than orphaned. Only
++ * create a fresh unique ccache when no primary exists yet.
++ */
++ if (krb5_cc_default(ctx, ccache) == 0) {
++ debug3_f("reusing existing default ccache of type %s",
++ type);
++ free(type);
++ return 0;
++ }
+ debug3_f("calling cc_new_unique(%s)", ccname);
+ ret = krb5_cc_new_unique(ctx, type, NULL, ccache);
+ free(type);
+diff --git a/configure.ac b/configure.ac
+index d6274328b..b2c959ff3 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -5075,11 +5075,17 @@ AC_ARG_WITH([kerberos5],
+ # include <gssapi_generic.h>
+ #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H)
+ # include <gssapi/gssapi_generic.h>
++#endif
++#ifdef HAVE_GSSAPI_EXT_H
++# include <gssapi/gssapi_ext.h>
++#endif
++#ifdef HAVE_GSSAPI_KRB5_H
++# include <gssapi/gssapi_krb5.h>
+ #endif
+ ]])
+ saved_LIBS="$LIBS"
+- LIBS="$LIBS $K5LIBS"
+- AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message])
++ LIBS="$LIBS $GSSLIBS $K5LIBS "
++ AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message gss_acquire_cred_from])
+ LIBS="$saved_LIBS"
+
+ fi
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index fc7cabc09..06d450d4d 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -609,6 +609,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
+ return 1;
+ }
+
++/*
++ * Check whether the user's default ccache already contains valid service
++ * tickets for all principals listed in services[]. Returns 1 if every
++ * listed service has a ticket with at least min_lifetime seconds remaining
++ * (pass GSS_C_INDEFINITE to accept any positive remaining lifetime), 0 if
++ * any ticket is missing or too close to expiry. Runs as the user.
++ *
++ * Unlike ssh_gssapi_user_has_valid_tgt(), this cannot use gss_acquire_cred()
++ * because service tickets are not initiator credentials — GSSAPI only
++ * surfaces TGTs via that API. We iterate the ccache with the krb5 API
++ * directly instead.
++ */
++int
++ssh_gssapi_user_has_valid_proxy_tickets(char **services, u_int nservices,
++ u_int min_lifetime)
++{
++ krb5_context ctx = NULL;
++ krb5_ccache cc = NULL;
++ krb5_cc_cursor cursor;
++ krb5_creds cred;
++ krb5_principal svc_princ;
++ int *found = NULL;
++ u_int i;
++ int all_found = 0;
++ time_t now;
++
++ if (nservices == 0)
++ return 0;
++
++ if (krb5_init_context(&ctx) != 0)
++ return 0;
++
++ found = xcalloc(nservices, sizeof(*found));
++ now = time(NULL);
++
++ if (krb5_cc_default(ctx, &cc) != 0)
++ goto out;
++
++ if (krb5_cc_start_seq_get(ctx, cc, &cursor) != 0) {
++ krb5_cc_close(ctx, cc);
++ cc = NULL;
++ goto out;
++ }
++
++ while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
++ /*
++ * Use krb5_principal_compare() so that service names
++ * configured without an explicit realm (krb5_parse_name
++ * appends the default realm) still match the fully-
++ * qualified principal stored in the ccache.
++ */
++ for (i = 0; i < nservices; i++) {
++ if (found[i])
++ continue;
++ if (krb5_parse_name(ctx, services[i],
++ &svc_princ) != 0)
++ continue;
++ if (krb5_principal_compare(ctx,
++ cred.server, svc_princ)) {
++ krb5_deltat remaining =
++ cred.times.endtime - now;
++ if (remaining > 0 &&
++ (min_lifetime == GSS_C_INDEFINITE ||
++ (krb5_deltat)min_lifetime <= remaining))
++ found[i] = 1;
++ }
++ krb5_free_principal(ctx, svc_princ);
++ }
++ krb5_free_cred_contents(ctx, &cred);
++ }
++ krb5_cc_end_seq_get(ctx, cc, &cursor);
++ krb5_cc_close(ctx, cc);
++ cc = NULL;
++
++ all_found = 1;
++ for (i = 0; i < nservices; i++) {
++ if (!found[i]) {
++ all_found = 0;
++ break;
++ }
++ }
++out:
++ free(found);
++ if (ctx != NULL)
++ krb5_free_context(ctx);
++ return all_found;
++}
++
++/* As user - called on fatal/exit */
++void
++ssh_gssapi_cleanup_creds(void)
++{
++ ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
++ krb5_ccache ccache = NULL;
++ krb5_error_code problem;
++
++ if (store->data != NULL) {
++ if ((problem = krb5_cc_resolve(store->data,
++ store->envval, &ccache))) {
++ debug_f("krb5_cc_resolve(): %.100s",
++ krb5_get_err_text(store->data, problem));
++ } else if ((problem = krb5_cc_destroy(store->data, ccache))) {
++ debug_f("krb5_cc_destroy(): %.100s",
++ krb5_get_err_text(store->data, problem));
++ } else {
++ krb5_free_context(store->data);
++ store->data = NULL;
++ }
++ }
++}
++
++/*
++ * Filter the user's ccache by removing the ticket classes indicated by
++ * drop_flags (SSH_GSSAPI_CCFILTER_* bitmask). Each credential is
++ * categorised as one of:
++ * TGT - server principal matches "krbtgt/" prefix
++ * PROXY - server principal matches one of proxy_services[]
++ * SELF - everything else (the S4U2Self evidence ticket)
++ * Credentials in a flagged category are discarded; the rest are written
++ * back after reinitialising the ccache. Runs as user.
++ */
++void
++ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
++ char **proxy_services, u_int nproxy_services)
++{
++ ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
++ krb5_context ctx = (krb5_context)store->data;
++ krb5_ccache cc = NULL;
++ krb5_cc_cursor cursor;
++ krb5_creds *keep = NULL;
++ krb5_principal princ = NULL;
++ krb5_error_code problem;
++ char *srvname;
++ u_int i, nkeep = 0, cap = 0;
++ int is_tgt, is_proxy, drop;
++
++ if (ctx == NULL || store->envval == NULL)
++ return;
++
++ if ((problem = krb5_cc_resolve(ctx, store->envval, &cc)) != 0) {
++ debug_f("krb5_cc_resolve: %.100s",
++ krb5_get_err_text(ctx, problem));
++ return;
++ }
++ if ((problem = krb5_cc_get_principal(ctx, cc, &princ)) != 0) {
++ debug_f("krb5_cc_get_principal: %.100s",
++ krb5_get_err_text(ctx, problem));
++ krb5_cc_close(ctx, cc);
++ return;
++ }
++ if ((problem = krb5_cc_start_seq_get(ctx, cc, &cursor)) != 0) {
++ debug_f("krb5_cc_start_seq_get: %.100s",
++ krb5_get_err_text(ctx, problem));
++ krb5_free_principal(ctx, princ);
++ krb5_cc_close(ctx, cc);
++ return;
++ }
++
++ {
++ krb5_creds cred;
++ krb5_principal svc_princ;
++ while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
++ is_tgt = is_proxy = 0;
++ if (krb5_unparse_name(ctx, cred.server,
++ &srvname) == 0) {
++ is_tgt = strncmp(srvname, "krbtgt/", 7) == 0;
++ krb5_free_unparsed_name(ctx, srvname);
++ }
++ if (!is_tgt) {
++ /*
++ * Use krb5_principal_compare() rather than
++ * strcmp() so that a service name configured
++ * without an explicit realm (krb5_parse_name
++ * appends the default realm) still matches
++ * the fully-qualified name in the ccache.
++ */
++ for (i = 0; i < nproxy_services; i++) {
++ if (krb5_parse_name(ctx,
++ proxy_services[i], &svc_princ) != 0)
++ continue;
++ if (krb5_principal_compare(ctx,
++ cred.server, svc_princ))
++ is_proxy = 1;
++ krb5_free_principal(ctx, svc_princ);
++ if (is_proxy)
++ break;
++ }
++ }
++ if (is_tgt)
++ drop = drop_flags & SSH_GSSAPI_CCFILTER_TGT;
++ else if (is_proxy)
++ drop = drop_flags & SSH_GSSAPI_CCFILTER_PROXY;
++ else
++ drop = drop_flags & SSH_GSSAPI_CCFILTER_SELF;
++
++ if (!drop) {
++ if (nkeep >= cap) {
++ cap = cap ? cap * 2 : 4;
++ keep = xreallocarray(keep, cap,
++ sizeof(*keep));
++ }
++ keep[nkeep++] = cred;
++ } else
++ krb5_free_cred_contents(ctx, &cred);
++ }
++ }
++ krb5_cc_end_seq_get(ctx, cc, &cursor);
++
++ if ((problem = krb5_cc_initialize(ctx, cc, princ)) != 0) {
++ logit_f("krb5_cc_initialize: %.100s",
++ krb5_get_err_text(ctx, problem));
++ } else {
++ for (i = 0; i < nkeep; i++)
++ krb5_cc_store_cred(ctx, cc, &keep[i]);
++ debug_f("ccache filter 0x%x: retained %u ticket(s)",
++ drop_flags, nkeep);
++ }
++
++ for (i = 0; i < nkeep; i++)
++ krb5_free_cred_contents(ctx, &keep[i]);
++ free(keep);
++ krb5_free_principal(ctx, princ);
++ krb5_cc_close(ctx, cc);
++}
++
+ ssh_gssapi_mech gssapi_kerberos_mech = {
+ "toWM5Slw5Ew8Mqkay+al2g==",
+ "Kerberos",
+diff --git a/gss-serv.c b/gss-serv.c
+index 237a3c40a..d7206499d 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -55,7 +55,7 @@ extern ServerOptions options;
+
+ static ssh_gssapi_client gssapi_client =
+ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
+- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
+
+ ssh_gssapi_mech gssapi_null_mech =
+ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+@@ -492,26 +492,382 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ return (ctx->major);
+ }
+
+-/* As user - called on fatal/exit */
++/* Returns non-zero if Kerberos credentials have already been stored. */
++int
++ssh_gssapi_credentials_stored(void)
++{
++ return gssapi_client.store.envval != NULL;
++}
++
++/* Returns a pointer to the credential-cache descriptor for this session. */
++ssh_gssapi_ccache *
++ssh_gssapi_get_ccache(void)
++{
++ return &gssapi_client.store;
++}
++
++/* Log human-readable GSSAPI major and minor status strings. */
++static void
++log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label)
++{
++ OM_uint32 lmin, mctx;
++ gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER;
++
++ mctx = 0;
++ do {
++ gss_display_status(&lmin, major, GSS_C_GSS_CODE,
++ GSS_C_NO_OID, &mctx, &emsg);
++ logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value);
++ gss_release_buffer(&lmin, &emsg);
++ } while (mctx != 0);
++
++ mctx = 0;
++ do {
++ gss_display_status(&lmin, minor, GSS_C_MECH_CODE,
++ &gssapi_kerberos_mech.oid, &mctx, &emsg);
++ if (emsg.length > 0)
++ logit("%s: %.*s", label,
++ (int)emsg.length, (char *)emsg.value);
++ gss_release_buffer(&lmin, &emsg);
++ } while (mctx != 0);
++}
++
++/* Log the canonical string form of a GSSAPI name as a debug message. */
++static void
++debug_gss_name(const char *label, gss_name_t name)
++{
++ OM_uint32 lmin;
++ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
++
++ if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) {
++ debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value);
++ gss_release_buffer(&lmin, &buf);
++ }
++}
++
++/*
++ * Check whether the user already has valid GSSAPI initiator credentials
++ * (e.g. a Kerberos TGT) in their default credential store with at least
++ * min_lifetime seconds remaining. Pass GSS_C_INDEFINITE to accept any
++ * positive remaining lifetime. Runs as the user.
++ * Returns 1 if sufficient credentials exist, 0 otherwise.
++ */
++int
++ssh_gssapi_user_has_valid_tgt(u_int min_lifetime)
++{
++ OM_uint32 major, minor, lifetime = 0;
++ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
++ int found = 0;
++
++ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
++ GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime);
++ if (!GSS_ERROR(major) && lifetime > 0 &&
++ (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime))
++ found = 1;
++ if (cred != GSS_C_NO_CREDENTIAL)
++ gss_release_cred(&minor, &cred);
++ return found;
++}
++
++
++/*
++ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket
++ * for the SSH user on behalf of the host principal. Runs privileged.
++ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success.
++ * Returns 0 on success, -1 on failure.
++ */
++/* Privileged */
++int
++ssh_gssapi_s4u2self(const char *user, u_int lifetime)
++{
++ OM_uint32 major, minor, status;
++ gss_OID_set oidset = GSS_C_NO_OID_SET;
++ gss_name_t host_name = GSS_C_NO_NAME;
++ gss_name_t user_name = GSS_C_NO_NAME;
++ gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL;
++ gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL;
++ gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER;
++ char lname[NI_MAXHOST];
++ char *val;
++
++ if (gethostname(lname, sizeof(lname)) != 0) {
++ logit_f("gethostname: %s", strerror(errno));
++ return -1;
++ }
++
++ /* Acquire acceptor credential for host/ from the keytab */
++ gss_create_empty_oid_set(&status, &oidset);
++ gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset);
++
++ xasprintf(&val, "host@%s", lname);
++ gssbuf.value = val;
++ gssbuf.length = strlen(val);
++ major = gss_import_name(&minor, &gssbuf,
++ GSS_C_NT_HOSTBASED_SERVICE, &host_name);
++ free(val);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_import_name (host) failed");
++ gss_release_oid_set(&status, &oidset);
++ return -1;
++ }
++ debug_gss_name("host name parsed as", host_name);
++
++ debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s",
++ (unsigned)getuid(), (unsigned)geteuid(), lname);
++ #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
++ {
++# if defined(KRB5)
++ /*
++ * Resolve the keytab path: krb5_kt_default_name respects
++ * KRB5_KTNAME and krb5.conf default_keytab_name.
++ */
++ char keytab_name[MAXPATHLEN];
++ krb5_context tmp_ctx;
++
++
++ keytab_name[0] = '\0';
++ if (krb5_init_context(&tmp_ctx) == 0) {
++ (void)krb5_kt_default_name(tmp_ctx, keytab_name,
++ sizeof(keytab_name));
++ krb5_free_context(tmp_ctx);
++ }
++ if (keytab_name[0] == '\0')
++ strlcpy(keytab_name, "FILE:/etc/krb5.keytab",
++ sizeof(keytab_name));
++ /*
++ * client_keytab lets GSSAPI do AS-REQ to obtain a TGT for the
++ * host principal (initiator role needed for S4U2Self).
++ * keytab covers the acceptor role.
++ * ccache: MEMORY: keeps the resulting TGT volatile.
++ */
++ gss_key_value_element_desc store_elements[] = {
++ { "client_keytab", keytab_name },
++ { "keytab", keytab_name },
++ { "ccache", "MEMORY:" },
++ };
++ const gss_key_value_set_desc cred_store = { 3, store_elements };
++# else
++ gss_key_value_element_desc store_elements[] = {
++ { "ccache", "MEMORY:" },
++ };
++ const gss_key_value_set_desc cred_store = { 1, store_elements };
++# endif
++
++
++ major = gss_acquire_cred_from(&minor, host_name, lifetime,
++ oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL);
++ }
++#else
++ major = gss_acquire_cred(&minor, host_name, lifetime,
++ oidset, GSS_C_BOTH, &host_creds, NULL, NULL);
++#endif
++ gss_release_name(&minor, &host_name);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u",
++ lname, (unsigned)getuid(), (unsigned)geteuid());
++ log_gss_error(major, minor, "S4U2Self: gss_acquire_cred");
++ gss_release_oid_set(&status, &oidset);
++ return -1;
++ }
++
++ /* Import the SSH username as a GSSAPI/Kerberos name */
++ gssbuf.value = (void *)user;
++ gssbuf.length = strlen(user);
++ major = gss_import_name(&minor, &gssbuf,
++ GSS_C_NT_USER_NAME, &user_name);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_import_name (user) failed");
++ gss_release_cred(&minor, &host_creds);
++ gss_release_oid_set(&status, &oidset);
++ return -1;
++ }
++ debug_gss_name("user name parsed as", user_name);
++
++ /* S4U2Self: obtain a service ticket for the user without their creds */
++ debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user);
++ major = gss_acquire_cred_impersonate_name(&minor,
++ host_creds, user_name, lifetime,
++ oidset, GSS_C_INITIATE,
++ &impersonated_creds, NULL, NULL);
++
++ gss_release_cred(&minor, &host_creds);
++ gss_release_oid_set(&status, &oidset);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_acquire_cred_impersonate_name failed for %.100s",
++ user);
++ log_gss_error(major, minor,
++ "S4U2Self: gss_acquire_cred_impersonate_name");
++ gss_release_name(&minor, &user_name);
++ return -1;
++ }
++
++ /* Get the display name (Kerberos principal string) for storecreds */
++ major = gss_display_name(&minor, user_name, &displayname, NULL);
++ gss_release_name(&minor, &user_name);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_display_name failed");
++ gss_release_cred(&minor, &impersonated_creds);
++ return -1;
++ }
++
++ /* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */
++ gssapi_client.mech = &gssapi_kerberos_mech;
++ gssapi_client.creds = impersonated_creds;
++ gssapi_client.displayname.value = xmalloc(displayname.length + 1);
++ memcpy(gssapi_client.displayname.value,
++ displayname.value, displayname.length);
++ ((char *)gssapi_client.displayname.value)[displayname.length] = '\0';
++ gssapi_client.displayname.length = displayname.length;
++ /*
++ * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name.
++ * gss_display_name for a user-name returns the canonical principal
++ * string (e.g. user@REALM) which krb5_parse_name can consume directly.
++ */
++ gssapi_client.exportedname.value = xmalloc(displayname.length + 1);
++ memcpy(gssapi_client.exportedname.value,
++ displayname.value, displayname.length);
++ ((char *)gssapi_client.exportedname.value)[displayname.length] = '\0';
++ gssapi_client.exportedname.length = displayname.length;
++
++ gss_release_buffer(&minor, &displayname);
++ debug_f("S4U2Self succeeded for %.100s", user);
++ return 0;
++}
++
++/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */
+ void
+-ssh_gssapi_cleanup_creds(void)
++ssh_gssapi_storecreds_s4u2self(void)
+ {
+- krb5_ccache ccache = NULL;
+- krb5_error_code problem;
+-
+- if (gssapi_client.store.data != NULL) {
+- if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) {
+- debug_f("krb5_cc_resolve(): %.100s",
+- krb5_get_err_text(gssapi_client.store.data, problem));
+- } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) {
+- debug_f("krb5_cc_destroy(): %.100s",
+- krb5_get_err_text(gssapi_client.store.data, problem));
+- } else {
+- krb5_free_context(gssapi_client.store.data);
+- gssapi_client.store.data = NULL;
++ if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) {
++ debug_f("no GSSAPI mechanism for storing S4U2Self credentials");
++ return;
++ }
++ (*gssapi_client.mech->storecreds)(&gssapi_client);
++}
++
++/*
++ * Perform S4U2Proxy for each configured service principal, then flush all
++ * resulting tickets into the user's ccache. Runs as user, after
++ * ssh_gssapi_storecreds_s4u2self() has created the ccache.
++ *
++ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the
++ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT
++ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ. The output token
++ * (AP-REQ) is discarded — we do not connect to the target service.
++ *
++ * After iterating all services, gss_store_cred() flushes the accumulated
++ * proxy service tickets from the credential's internal ccache into the
++ * KRB5CCNAME ccache that storecreds_s4u2self() already created.
++ */
++/* As user */
++void
++ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime)
++{
++ OM_uint32 major, minor;
++ gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER;
++ gss_name_t target_name;
++ gss_ctx_id_t ctx;
++ u_int i;
++
++ if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) {
++ debug_f("no proxy credential available");
++ return;
++ }
++ if (gssapi_client.store.envval == NULL) {
++ debug_f("no ccache path set; cannot store proxy tickets");
++ return;
++ }
++
++ debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s",
++ (unsigned)getuid(), (unsigned)geteuid(), nservices,
++ gssapi_client.store.envval);
++
++ /* Point the GSSAPI library at the user's ccache for ticket storage */
++ setenv("KRB5CCNAME", gssapi_client.store.envval, 1);
++
++ for (i = 0; i < nservices; i++) {
++ ctx = GSS_C_NO_CONTEXT;
++ target_name = GSS_C_NO_NAME;
++
++ service_buf.value = services[i];
++ service_buf.length = strlen(services[i]);
++
++ /*
++ * GSS_C_NO_OID: let the library determine the name type.
++ * With Kerberos as the active mechanism, a fully-qualified
++ * principal like "svc/host@REALM" is parsed correctly.
++ */
++ major = gss_import_name(&minor, &service_buf,
++ GSS_C_NO_OID, &target_name);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_import_name failed for %.200s",
++ services[i]);
++ log_gss_error(major, minor, "S4U2Proxy: gss_import_name");
++ continue;
+ }
++ debug_gss_name("target service name parsed as", target_name);
++
++ debug_f("calling gss_init_sec_context for %.200s", services[i]);
++ major = gss_init_sec_context(&minor,
++ gssapi_client.creds, /* proxy credential */
++ &ctx, target_name,
++ GSS_C_NO_OID, /* default mech (Kerberos) */
++ 0, /* no flags, no mutual auth */
++ lifetime,
++ GSS_C_NO_CHANNEL_BINDINGS,
++ GSS_C_NO_BUFFER, /* no input token */
++ NULL, /* actual_mech_type */
++ &output_token,
++ NULL, /* ret_flags */
++ NULL); /* time_rec */
++
++ gss_release_buffer(&minor, &output_token);
++ gss_release_name(&minor, &target_name);
++ if (ctx != GSS_C_NO_CONTEXT)
++ gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER);
++
++ if (GSS_ERROR(major)) {
++ logit_f("S4U2Proxy for %.200s on behalf of %.200s failed",
++ services[i],
++ (char *)gssapi_client.displayname.value);
++ log_gss_error(major, minor,
++ "S4U2Proxy: gss_init_sec_context");
++ } else
++ debug_f("S4U2Proxy ticket obtained for %.200s",
++ services[i]);
+ }
++
++ /*
++ * Flush all proxy service tickets from the credential's internal
++ * ccache into the KRB5CCNAME ccache via gss_store_cred().
++ */
++ major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE,
++ GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */,
++ NULL, NULL);
++ if (GSS_ERROR(major)) {
++ logit_f("gss_store_cred failed; proxy tickets may be missing");
++ log_gss_error(major, minor, "S4U2Proxy: gss_store_cred");
++ }
++
++ unsetenv("KRB5CCNAME");
++}
++
++#ifndef KRB5
++/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
++void
++ssh_gssapi_cleanup_creds(void)
++{
++}
++
++/*
++ * Filter the user's ccache; full implementation in gss-serv-krb5.c.
++ */
++void
++ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
++ char **proxy_services, u_int nproxy_services)
++{
+ }
++#endif /* !KRB5 */
+
+ /* As user */
+ int
+diff --git a/servconf.c b/servconf.c
+index 8746a2417..667468590 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -1498,6 +1498,43 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ if (options->gss_indicators == NULL)
+ options->gss_indicators = xstrdup(arg);
+ break;
++
++ case sGSSAPIAllowS4U2Self:
++ arg = argv_next(&ac, &av);
++ if (!arg || *arg == '\0')
++ fatal("%s line %d: %s missing argument.",
++ filename, linenum, keyword);
++ if (strcasecmp(arg, "no") == 0)
++ value = 0;
++ else if (strcasecmp(arg, "yes") == 0)
++ value = INT_MAX;
++ else if ((value = convtime(arg)) <= 0)
++ fatal("%s line %d: invalid %s value \"%s\".",
++ filename, linenum, keyword, arg);
++ if (*activep && options->gss_allow_s4u2self == -1)
++ options->gss_allow_s4u2self = value;
++ break;
++
++ case sGSSAPIProxyS4U2Services:
++ while ((arg = argv_next(&ac, &av)) != NULL) {
++ if (*arg == '\0')
++ fatal("%s line %d: %s missing argument.",
++ filename, linenum, keyword);
++ if (strcasecmp(arg, "none") == 0) {
++ for (i = 0; i < options->num_gss_proxy_services; i++)
++ free(options->gss_proxy_services[i]);
++ free(options->gss_proxy_services);
++ options->gss_proxy_services = NULL;
++ options->num_gss_proxy_services = 0;
++ break;
++ }
++ if (!*activep)
++ continue;
++ opt_array_append(filename, linenum, keyword,
++ &options->gss_proxy_services,
++ &options->num_gss_proxy_services, arg);
++ }
++ break;
+ #endif /* GSSAPI */
+
+ case sPasswordAuthentication:
+@@ -4290,6 +4327,15 @@ dump_config(ServerOptions *o)
+ dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
+ dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
+ dump_cfg_string(sGSSAPIIndicators, o->gss_indicators);
++ if (o->gss_allow_s4u2self == 0)
++ printf("%s no\n", lookup_opcode_name(sGSSAPIAllowS4U2Self));
++ else if (o->gss_allow_s4u2self == INT_MAX)
++ printf("%s yes\n", lookup_opcode_name(sGSSAPIAllowS4U2Self));
++ else
++ printf("%s %d\n", lookup_opcode_name(sGSSAPIAllowS4U2Self),
++ o->gss_allow_s4u2self);
++ dump_cfg_strarray_oneline(sGSSAPIProxyS4U2Services,
++ o->num_gss_proxy_services, o->gss_proxy_services);
+ #endif
+ dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index 4e5f7edc6..9ee4a089d 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -329,7 +329,9 @@ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+-SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH)
++SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(gss_allow_s4u2self, GSSAPIAllowS4U2Self, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_STRARRAY(gss_proxy_services, num_gss_proxy_services, GSSAPIProxyS4U2Services, SSHCFG_ALL, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -340,7 +342,9 @@ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL) \
+-SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL)
++SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_INT(gss_allow_s4u2self, GSSAPIAllowS4U2Self, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_STRING(gss_proxy_services, GSSAPIProxyS4U2Services, SSHCFG_ALL)
+ #endif /* GSSAPI */
+
+ #define SSHD_CONFIG_ENTRIES \
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 1506719a9..a0d51c9be 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -119,6 +119,7 @@ typedef struct {
+ int used;
+ int updated;
+ char **indicators; /* auth indicators */
++ int allow_self; /* allow protocol transition */
+ } ssh_gssapi_client;
+
+ typedef struct ssh_gssapi_mech_struct {
+@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_do_child(char ***, u_int *);
+ void ssh_gssapi_cleanup_creds(void);
+ int ssh_gssapi_storecreds(void);
++int ssh_gssapi_credentials_stored(void);
++ssh_gssapi_ccache *ssh_gssapi_get_ccache(void);
++int ssh_gssapi_user_has_valid_tgt(u_int);
++int ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int);
++int ssh_gssapi_s4u2self(const char *, u_int);
++void ssh_gssapi_storecreds_s4u2self(void);
++void ssh_gssapi_s4u2proxy(char **, u_int, u_int);
++/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */
++#define SSH_GSSAPI_CCFILTER_TGT (1u << 0) /* krbtgt/... entries */
++#define SSH_GSSAPI_CCFILTER_SELF (1u << 1) /* S4U2Self evidence ticket */
++#define SSH_GSSAPI_CCFILTER_PROXY (1u << 2) /* S4U2Proxy service tickets */
++void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int);
+ const char *ssh_gssapi_displayname(void);
+
+ char *ssh_gssapi_server_mechanisms(void);
+diff --git a/sshd-session.c b/sshd-session.c
+index adf5b1b49..7d0e25ca1 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -1396,6 +1396,104 @@ main(int ac, char **av)
+ authctxt->krb5_set_env = ssh_gssapi_storecreds();
+ restore_uid();
+ }
++ /*
++ * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
++ * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
++ * to obtain an impersonated credential for the user, then optionally
++ * follow with S4U2Proxy for configured target services.
++ *
++ * GSSAPIAllowS4U2Self alone: store S4U2Self evidence ticket only;
++ * the host TGT is removed.
++ * GSSAPIProxyS4U2Services alone: store host TGT and S4U2Proxy service
++ * tickets; the S4U2Self evidence ticket
++ * is removed.
++ * Both: store host TGT, S4U2Self evidence ticket,
++ * and all S4U2Proxy service tickets.
++ *
++ * When S4U2Proxy tickets are present the host TGT must remain in the
++ * ccache; applications check for TGT presence to determine whether
++ * Kerberos credentials are available. Only in GSSAPIAllowS4U2Self-alone
++ * mode (no proxy tickets) is the host TGT removed.
++ *
++ * Skip S4U2Self when the user already has credentials covering the
++ * requested lifetime: check for a valid TGT in the GSSAPIAllowS4U2Self-
++ * alone case, or for valid proxy tickets for every configured service
++ * otherwise.
++ */
++ if ((options.gss_allow_s4u2self || options.num_gss_proxy_services > 0) &&
++ !ssh_gssapi_credentials_stored()) {
++ u_int lifetime = (!options.gss_allow_s4u2self ||
++ options.gss_allow_s4u2self == INT_MAX) ?
++ GSS_C_INDEFINITE : (u_int)options.gss_allow_s4u2self;
++ int skip = 0;
++
++ temporarily_use_uid(authctxt->pw);
++ if (options.gss_allow_s4u2self &&
++ options.num_gss_proxy_services == 0) {
++ /* S4U2Self-alone: skip if user already has a valid TGT */
++ skip = ssh_gssapi_user_has_valid_tgt(lifetime);
++ } else if (options.num_gss_proxy_services > 0) {
++ /*
++ * Proxy-only or both: skip if every configured service
++ * already has a valid ticket in the user's ccache.
++ * Service tickets are not GSSAPI initiator credentials,
++ * so gss_acquire_cred() cannot be used; iterate the
++ * ccache with the krb5 API instead.
++ */
++ skip = ssh_gssapi_user_has_valid_proxy_tickets(
++ options.gss_proxy_services,
++ options.num_gss_proxy_services,
++ lifetime);
++ }
++ restore_uid();
++
++ if (skip) {
++ debug_f("user %.100s already has valid Kerberos "
++ "credentials, skipping S4U2Self",
++ authctxt->user);
++ } else if (ssh_gssapi_s4u2self(authctxt->user, lifetime) == 0) {
++ u_int filter;
++
++ temporarily_use_uid(authctxt->pw);
++ /*
++ * Always create the ccache via storecreds_s4u2self so
++ * that s4u2proxy has a ccache to store tickets into.
++ * gss_krb5_copy_ccache() copies the host service's own
++ * TGT along with the evidence ticket; filter_ccache
++ * removes the ticket classes that should not be kept.
++ */
++ ssh_gssapi_storecreds_s4u2self();
++ if (options.num_gss_proxy_services > 0)
++ ssh_gssapi_s4u2proxy(
++ options.gss_proxy_services,
++ options.num_gss_proxy_services,
++ lifetime);
++
++ /*
++ * Remove the host TGT only in GSSAPIAllowS4U2Self-alone
++ * mode; when proxy tickets are present the TGT must
++ * stay so that applications recognise the ccache as
++ * holding live Kerberos credentials.
++ * Remove the S4U2Self evidence ticket in proxy-only
++ * mode (GSSAPIProxyS4U2Services without GSSAPIAllowS4U2Self).
++ */
++ filter = 0;
++ if (options.gss_allow_s4u2self &&
++ options.num_gss_proxy_services == 0)
++ filter = SSH_GSSAPI_CCFILTER_TGT |
++ SSH_GSSAPI_CCFILTER_PROXY;
++ else if (!options.gss_allow_s4u2self)
++ filter = SSH_GSSAPI_CCFILTER_SELF;
++ if (filter != 0)
++ ssh_gssapi_krb5_filter_ccache(filter,
++ options.gss_proxy_services,
++ options.num_gss_proxy_services);
++ restore_uid();
++ } else {
++ logit("S4U2Self failed for user %.100s, continuing",
++ authctxt->user);
++ }
++ }
+ #endif
+ #ifdef WITH_SELINUX
+ sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
+diff --git a/sshd_config.5 b/sshd_config.5
+index bf9efc897..ca8a8131e 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -857,6 +857,76 @@ FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
+ The default
+ .Dq none
+ is to not use GSSAPI authentication indicators for access decisions.
++.It Cm GSSAPIAllowS4U2Self
++Controls whether the SSH server performs a Kerberos protocol transition
++(S4U2Self) after a successful authentication using any other method.
++Accepted values are
++.Cm no ,
++.Cm yes ,
++or a time interval (see
++.Sx TIME FORMATS
++below).
++.Cm no
++disables S4U2Self entirely.
++.Cm yes
++enables S4U2Self and requests a ticket with the maximum lifetime
++permitted by the KDC.
++A time interval (e.g.\&
++.Cm 8h ,
++.Cm 1d )
++enables S4U2Self and requests a ticket valid for at most that duration.
++The option is a no-op when delegated GSSAPI credentials are already available.
++The obtained service ticket is stored in the default credentials cache and is
++accessible to any application that has access to the Kerberos host principal
++.Pq host/machine.fqdn@REALM
++credentials on the same host.
++.Pp
++The default is
++.Cm no .
++.It Cm GSSAPIProxyS4U2Services
++Specifies a list of Kerberos service principals for which constrained
++delegation (S4U2Proxy) tickets should be obtained after a successful
++S4U2Self protocol transition.
++Each entry must be a fully-qualified Kerberos principal name of the form
++.Ar service/host@REALM .
++Multiple principals may be listed, separated by whitespace.
++The keyword
++.Cm none
++clears any previously set list.
++.Pp
++This option may be used independently of
++.Cm GSSAPIAllowS4U2Self .
++When S4U2Self succeeds, the server iterates the list and calls
++.Xr gss_init_sec_context 3
++for each principal with the proxy credential obtained by S4U2Self as
++the initiator.
++The GSSAPI library presents the evidence ticket to the KDC via an
++S4U2Proxy TGS-REQ; if the host service holds the necessary constrained-
++delegation permission in the KDC, a service ticket from the user to
++the target service is issued.
++These tickets are accumulated and then flushed into the user's ccache
++via
++.Xr gss_store_cred 3 ,
++so that any application running in the user's session can use them
++without further interaction.
++The AP-REQ output token of each
++.Xr gss_init_sec_context 3
++call is discarded; no network connection to the target service is made.
++.Pp
++When used together with
++.Cm GSSAPIAllowS4U2Self ,
++the TGT and S4U2Self ticket are also stored in the user's ccache in
++addition to the S4U2Proxy service tickets.
++When used alone (without
++.Cm GSSAPIAllowS4U2Self ) ,
++only the S4U2Proxy service tickets are stored; the intermediate S4U2Self
++credential is not placed in the user's ccache.
++.Pp
++This option supports
++.Cm Match
++blocks, allowing per-user or per-host lists of delegation targets.
++.Pp
++The default is empty (no S4U2Proxy delegation is performed).
+ .It Cm HostbasedAcceptedAlgorithms
+ The default is handled system-wide by
+ .Xr crypto-policies 7 .
+--
+2.55.0
+
diff --git a/0051-openssh-10.2p1-pam-auth.patch b/0051-openssh-10.2p1-pam-auth.patch
deleted file mode 100644
index 5128af3..0000000
--- a/0051-openssh-10.2p1-pam-auth.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 04c13392280d1839b19cfe6e4dba48db3a8a8e77 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:25:04 +0200
-Subject: [PATCH 51/54] openssh-10.2p1-pam-auth
-
-https://bugzilla.redhat.com/show_bug.cgi?id=2423900
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-pam.c | 10 +++++-----
- 1 file changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/auth-pam.c b/auth-pam.c
-index d3dcd037b..aaad16765 100644
---- a/auth-pam.c
-+++ b/auth-pam.c
-@@ -455,6 +455,7 @@ static int
- check_pam_user(Authctxt *authctxt)
- {
- const char *pam_user;
-+ const struct passwd *pam_pw;
-
- if (authctxt == NULL || authctxt->pw == NULL ||
- authctxt->pw->pw_name == NULL)
-@@ -469,11 +470,10 @@ check_pam_user(Authctxt *authctxt)
- return PAM_USER_UNKNOWN;
- }
-
-- if (sshpam_initial_user == NULL)
-- fatal_f("internal error: sshpam_initial_user NULL");
-- if (strcmp(sshpam_initial_user, pam_user) != 0) {
-- error_f("PAM user \"%s\" does not match previous \"%s\"",
-- pam_user, sshpam_initial_user);
-+ pam_pw = getpwnam(pam_user);
-+ if (pam_pw == NULL || pam_pw->pw_uid != authctxt->pw->pw_uid) {
-+ debug("PAM user \"%s\" does not match expected \"%s\"",
-+ pam_user, authctxt->pw->pw_name);
- return PAM_USER_UNKNOWN;
- }
- return PAM_SUCCESS;
---
-2.53.0
-
diff --git a/0052-gssapi-s4u.patch b/0052-gssapi-s4u.patch
deleted file mode 100644
index 8b3b2c2..0000000
--- a/0052-gssapi-s4u.patch
+++ /dev/null
@@ -1,1077 +0,0 @@
-From f629beb0fb094ffea8327699a942ba3c180f5e04 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:25:21 +0200
-Subject: [PATCH 52/54] gssapi-s4u
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-krb5.c | 13 ++
- configure.ac | 10 +-
- gss-serv-krb5.c | 225 ++++++++++++++++++++++++++++
- gss-serv.c | 388 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c | 58 ++++++++
- servconf.h | 4 +
- ssh-gss.h | 13 ++
- sshd-session.c | 98 ++++++++++++
- sshd_config.5 | 70 +++++++++
- 9 files changed, 861 insertions(+), 18 deletions(-)
-
-diff --git a/auth-krb5.c b/auth-krb5.c
-index 287bdd35a..7d4fbcc3d 100644
---- a/auth-krb5.c
-+++ b/auth-krb5.c
-@@ -463,6 +463,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
- * a primary cache for this collection, if it supports that (non-FILE)
- */
- if (krb5_cc_support_switch(ctx, type)) {
-+ /*
-+ * For collection-type caches (KCM, KEYRING, …) reuse the
-+ * existing primary ccache when one is already present. The
-+ * caller will reinitialise it with krb5_cc_initialize(), so
-+ * its old contents are replaced rather than orphaned. Only
-+ * create a fresh unique ccache when no primary exists yet.
-+ */
-+ if (krb5_cc_default(ctx, ccache) == 0) {
-+ debug3_f("reusing existing default ccache of type %s",
-+ type);
-+ free(type);
-+ return 0;
-+ }
- debug3_f("calling cc_new_unique(%s)", ccname);
- ret = krb5_cc_new_unique(ctx, type, NULL, ccache);
- free(type);
-diff --git a/configure.ac b/configure.ac
-index 1388e5e72..62b2bf9e7 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -5145,11 +5145,17 @@ AC_ARG_WITH([kerberos5],
- # include <gssapi_generic.h>
- #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H)
- # include <gssapi/gssapi_generic.h>
-+#endif
-+#ifdef HAVE_GSSAPI_EXT_H
-+# include <gssapi/gssapi_ext.h>
-+#endif
-+#ifdef HAVE_GSSAPI_KRB5_H
-+# include <gssapi/gssapi_krb5.h>
- #endif
- ]])
- saved_LIBS="$LIBS"
-- LIBS="$LIBS $K5LIBS"
-- AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message])
-+ LIBS="$LIBS $GSSLIBS $K5LIBS "
-+ AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message gss_acquire_cred_from])
- LIBS="$saved_LIBS"
-
- fi
-diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
-index fc7cabc09..06d450d4d 100644
---- a/gss-serv-krb5.c
-+++ b/gss-serv-krb5.c
-@@ -609,6 +609,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
- return 1;
- }
-
-+/*
-+ * Check whether the user's default ccache already contains valid service
-+ * tickets for all principals listed in services[]. Returns 1 if every
-+ * listed service has a ticket with at least min_lifetime seconds remaining
-+ * (pass GSS_C_INDEFINITE to accept any positive remaining lifetime), 0 if
-+ * any ticket is missing or too close to expiry. Runs as the user.
-+ *
-+ * Unlike ssh_gssapi_user_has_valid_tgt(), this cannot use gss_acquire_cred()
-+ * because service tickets are not initiator credentials — GSSAPI only
-+ * surfaces TGTs via that API. We iterate the ccache with the krb5 API
-+ * directly instead.
-+ */
-+int
-+ssh_gssapi_user_has_valid_proxy_tickets(char **services, u_int nservices,
-+ u_int min_lifetime)
-+{
-+ krb5_context ctx = NULL;
-+ krb5_ccache cc = NULL;
-+ krb5_cc_cursor cursor;
-+ krb5_creds cred;
-+ krb5_principal svc_princ;
-+ int *found = NULL;
-+ u_int i;
-+ int all_found = 0;
-+ time_t now;
-+
-+ if (nservices == 0)
-+ return 0;
-+
-+ if (krb5_init_context(&ctx) != 0)
-+ return 0;
-+
-+ found = xcalloc(nservices, sizeof(*found));
-+ now = time(NULL);
-+
-+ if (krb5_cc_default(ctx, &cc) != 0)
-+ goto out;
-+
-+ if (krb5_cc_start_seq_get(ctx, cc, &cursor) != 0) {
-+ krb5_cc_close(ctx, cc);
-+ cc = NULL;
-+ goto out;
-+ }
-+
-+ while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
-+ /*
-+ * Use krb5_principal_compare() so that service names
-+ * configured without an explicit realm (krb5_parse_name
-+ * appends the default realm) still match the fully-
-+ * qualified principal stored in the ccache.
-+ */
-+ for (i = 0; i < nservices; i++) {
-+ if (found[i])
-+ continue;
-+ if (krb5_parse_name(ctx, services[i],
-+ &svc_princ) != 0)
-+ continue;
-+ if (krb5_principal_compare(ctx,
-+ cred.server, svc_princ)) {
-+ krb5_deltat remaining =
-+ cred.times.endtime - now;
-+ if (remaining > 0 &&
-+ (min_lifetime == GSS_C_INDEFINITE ||
-+ (krb5_deltat)min_lifetime <= remaining))
-+ found[i] = 1;
-+ }
-+ krb5_free_principal(ctx, svc_princ);
-+ }
-+ krb5_free_cred_contents(ctx, &cred);
-+ }
-+ krb5_cc_end_seq_get(ctx, cc, &cursor);
-+ krb5_cc_close(ctx, cc);
-+ cc = NULL;
-+
-+ all_found = 1;
-+ for (i = 0; i < nservices; i++) {
-+ if (!found[i]) {
-+ all_found = 0;
-+ break;
-+ }
-+ }
-+out:
-+ free(found);
-+ if (ctx != NULL)
-+ krb5_free_context(ctx);
-+ return all_found;
-+}
-+
-+/* As user - called on fatal/exit */
-+void
-+ssh_gssapi_cleanup_creds(void)
-+{
-+ ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
-+ krb5_ccache ccache = NULL;
-+ krb5_error_code problem;
-+
-+ if (store->data != NULL) {
-+ if ((problem = krb5_cc_resolve(store->data,
-+ store->envval, &ccache))) {
-+ debug_f("krb5_cc_resolve(): %.100s",
-+ krb5_get_err_text(store->data, problem));
-+ } else if ((problem = krb5_cc_destroy(store->data, ccache))) {
-+ debug_f("krb5_cc_destroy(): %.100s",
-+ krb5_get_err_text(store->data, problem));
-+ } else {
-+ krb5_free_context(store->data);
-+ store->data = NULL;
-+ }
-+ }
-+}
-+
-+/*
-+ * Filter the user's ccache by removing the ticket classes indicated by
-+ * drop_flags (SSH_GSSAPI_CCFILTER_* bitmask). Each credential is
-+ * categorised as one of:
-+ * TGT - server principal matches "krbtgt/" prefix
-+ * PROXY - server principal matches one of proxy_services[]
-+ * SELF - everything else (the S4U2Self evidence ticket)
-+ * Credentials in a flagged category are discarded; the rest are written
-+ * back after reinitialising the ccache. Runs as user.
-+ */
-+void
-+ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
-+ char **proxy_services, u_int nproxy_services)
-+{
-+ ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
-+ krb5_context ctx = (krb5_context)store->data;
-+ krb5_ccache cc = NULL;
-+ krb5_cc_cursor cursor;
-+ krb5_creds *keep = NULL;
-+ krb5_principal princ = NULL;
-+ krb5_error_code problem;
-+ char *srvname;
-+ u_int i, nkeep = 0, cap = 0;
-+ int is_tgt, is_proxy, drop;
-+
-+ if (ctx == NULL || store->envval == NULL)
-+ return;
-+
-+ if ((problem = krb5_cc_resolve(ctx, store->envval, &cc)) != 0) {
-+ debug_f("krb5_cc_resolve: %.100s",
-+ krb5_get_err_text(ctx, problem));
-+ return;
-+ }
-+ if ((problem = krb5_cc_get_principal(ctx, cc, &princ)) != 0) {
-+ debug_f("krb5_cc_get_principal: %.100s",
-+ krb5_get_err_text(ctx, problem));
-+ krb5_cc_close(ctx, cc);
-+ return;
-+ }
-+ if ((problem = krb5_cc_start_seq_get(ctx, cc, &cursor)) != 0) {
-+ debug_f("krb5_cc_start_seq_get: %.100s",
-+ krb5_get_err_text(ctx, problem));
-+ krb5_free_principal(ctx, princ);
-+ krb5_cc_close(ctx, cc);
-+ return;
-+ }
-+
-+ {
-+ krb5_creds cred;
-+ krb5_principal svc_princ;
-+ while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
-+ is_tgt = is_proxy = 0;
-+ if (krb5_unparse_name(ctx, cred.server,
-+ &srvname) == 0) {
-+ is_tgt = strncmp(srvname, "krbtgt/", 7) == 0;
-+ krb5_free_unparsed_name(ctx, srvname);
-+ }
-+ if (!is_tgt) {
-+ /*
-+ * Use krb5_principal_compare() rather than
-+ * strcmp() so that a service name configured
-+ * without an explicit realm (krb5_parse_name
-+ * appends the default realm) still matches
-+ * the fully-qualified name in the ccache.
-+ */
-+ for (i = 0; i < nproxy_services; i++) {
-+ if (krb5_parse_name(ctx,
-+ proxy_services[i], &svc_princ) != 0)
-+ continue;
-+ if (krb5_principal_compare(ctx,
-+ cred.server, svc_princ))
-+ is_proxy = 1;
-+ krb5_free_principal(ctx, svc_princ);
-+ if (is_proxy)
-+ break;
-+ }
-+ }
-+ if (is_tgt)
-+ drop = drop_flags & SSH_GSSAPI_CCFILTER_TGT;
-+ else if (is_proxy)
-+ drop = drop_flags & SSH_GSSAPI_CCFILTER_PROXY;
-+ else
-+ drop = drop_flags & SSH_GSSAPI_CCFILTER_SELF;
-+
-+ if (!drop) {
-+ if (nkeep >= cap) {
-+ cap = cap ? cap * 2 : 4;
-+ keep = xreallocarray(keep, cap,
-+ sizeof(*keep));
-+ }
-+ keep[nkeep++] = cred;
-+ } else
-+ krb5_free_cred_contents(ctx, &cred);
-+ }
-+ }
-+ krb5_cc_end_seq_get(ctx, cc, &cursor);
-+
-+ if ((problem = krb5_cc_initialize(ctx, cc, princ)) != 0) {
-+ logit_f("krb5_cc_initialize: %.100s",
-+ krb5_get_err_text(ctx, problem));
-+ } else {
-+ for (i = 0; i < nkeep; i++)
-+ krb5_cc_store_cred(ctx, cc, &keep[i]);
-+ debug_f("ccache filter 0x%x: retained %u ticket(s)",
-+ drop_flags, nkeep);
-+ }
-+
-+ for (i = 0; i < nkeep; i++)
-+ krb5_free_cred_contents(ctx, &keep[i]);
-+ free(keep);
-+ krb5_free_principal(ctx, princ);
-+ krb5_cc_close(ctx, cc);
-+}
-+
- ssh_gssapi_mech gssapi_kerberos_mech = {
- "toWM5Slw5Ew8Mqkay+al2g==",
- "Kerberos",
-diff --git a/gss-serv.c b/gss-serv.c
-index 686ac4eb3..26d0a13a6 100644
---- a/gss-serv.c
-+++ b/gss-serv.c
-@@ -55,7 +55,7 @@ extern ServerOptions options;
-
- static ssh_gssapi_client gssapi_client =
- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
-- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
-+ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
-
- ssh_gssapi_mech gssapi_null_mech =
- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
-@@ -488,26 +488,382 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
- return (ctx->major);
- }
-
--/* As user - called on fatal/exit */
-+/* Returns non-zero if Kerberos credentials have already been stored. */
-+int
-+ssh_gssapi_credentials_stored(void)
-+{
-+ return gssapi_client.store.envval != NULL;
-+}
-+
-+/* Returns a pointer to the credential-cache descriptor for this session. */
-+ssh_gssapi_ccache *
-+ssh_gssapi_get_ccache(void)
-+{
-+ return &gssapi_client.store;
-+}
-+
-+/* Log human-readable GSSAPI major and minor status strings. */
-+static void
-+log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label)
-+{
-+ OM_uint32 lmin, mctx;
-+ gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER;
-+
-+ mctx = 0;
-+ do {
-+ gss_display_status(&lmin, major, GSS_C_GSS_CODE,
-+ GSS_C_NO_OID, &mctx, &emsg);
-+ logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value);
-+ gss_release_buffer(&lmin, &emsg);
-+ } while (mctx != 0);
-+
-+ mctx = 0;
-+ do {
-+ gss_display_status(&lmin, minor, GSS_C_MECH_CODE,
-+ &gssapi_kerberos_mech.oid, &mctx, &emsg);
-+ if (emsg.length > 0)
-+ logit("%s: %.*s", label,
-+ (int)emsg.length, (char *)emsg.value);
-+ gss_release_buffer(&lmin, &emsg);
-+ } while (mctx != 0);
-+}
-+
-+/* Log the canonical string form of a GSSAPI name as a debug message. */
-+static void
-+debug_gss_name(const char *label, gss_name_t name)
-+{
-+ OM_uint32 lmin;
-+ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
-+
-+ if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) {
-+ debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value);
-+ gss_release_buffer(&lmin, &buf);
-+ }
-+}
-+
-+/*
-+ * Check whether the user already has valid GSSAPI initiator credentials
-+ * (e.g. a Kerberos TGT) in their default credential store with at least
-+ * min_lifetime seconds remaining. Pass GSS_C_INDEFINITE to accept any
-+ * positive remaining lifetime. Runs as the user.
-+ * Returns 1 if sufficient credentials exist, 0 otherwise.
-+ */
-+int
-+ssh_gssapi_user_has_valid_tgt(u_int min_lifetime)
-+{
-+ OM_uint32 major, minor, lifetime = 0;
-+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
-+ int found = 0;
-+
-+ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
-+ GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime);
-+ if (!GSS_ERROR(major) && lifetime > 0 &&
-+ (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime))
-+ found = 1;
-+ if (cred != GSS_C_NO_CREDENTIAL)
-+ gss_release_cred(&minor, &cred);
-+ return found;
-+}
-+
-+
-+/*
-+ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket
-+ * for the SSH user on behalf of the host principal. Runs privileged.
-+ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success.
-+ * Returns 0 on success, -1 on failure.
-+ */
-+/* Privileged */
-+int
-+ssh_gssapi_s4u2self(const char *user, u_int lifetime)
-+{
-+ OM_uint32 major, minor, status;
-+ gss_OID_set oidset = GSS_C_NO_OID_SET;
-+ gss_name_t host_name = GSS_C_NO_NAME;
-+ gss_name_t user_name = GSS_C_NO_NAME;
-+ gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL;
-+ gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL;
-+ gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER;
-+ char lname[NI_MAXHOST];
-+ char *val;
-+
-+ if (gethostname(lname, sizeof(lname)) != 0) {
-+ logit_f("gethostname: %s", strerror(errno));
-+ return -1;
-+ }
-+
-+ /* Acquire acceptor credential for host/ from the keytab */
-+ gss_create_empty_oid_set(&status, &oidset);
-+ gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset);
-+
-+ xasprintf(&val, "host@%s", lname);
-+ gssbuf.value = val;
-+ gssbuf.length = strlen(val);
-+ major = gss_import_name(&minor, &gssbuf,
-+ GSS_C_NT_HOSTBASED_SERVICE, &host_name);
-+ free(val);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_import_name (host) failed");
-+ gss_release_oid_set(&status, &oidset);
-+ return -1;
-+ }
-+ debug_gss_name("host name parsed as", host_name);
-+
-+ debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s",
-+ (unsigned)getuid(), (unsigned)geteuid(), lname);
-+ #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
-+ {
-+# if defined(KRB5)
-+ /*
-+ * Resolve the keytab path: krb5_kt_default_name respects
-+ * KRB5_KTNAME and krb5.conf default_keytab_name.
-+ */
-+ char keytab_name[MAXPATHLEN];
-+ krb5_context tmp_ctx;
-+
-+
-+ keytab_name[0] = '\0';
-+ if (krb5_init_context(&tmp_ctx) == 0) {
-+ (void)krb5_kt_default_name(tmp_ctx, keytab_name,
-+ sizeof(keytab_name));
-+ krb5_free_context(tmp_ctx);
-+ }
-+ if (keytab_name[0] == '\0')
-+ strlcpy(keytab_name, "FILE:/etc/krb5.keytab",
-+ sizeof(keytab_name));
-+ /*
-+ * client_keytab lets GSSAPI do AS-REQ to obtain a TGT for the
-+ * host principal (initiator role needed for S4U2Self).
-+ * keytab covers the acceptor role.
-+ * ccache: MEMORY: keeps the resulting TGT volatile.
-+ */
-+ gss_key_value_element_desc store_elements[] = {
-+ { "client_keytab", keytab_name },
-+ { "keytab", keytab_name },
-+ { "ccache", "MEMORY:" },
-+ };
-+ const gss_key_value_set_desc cred_store = { 3, store_elements };
-+# else
-+ gss_key_value_element_desc store_elements[] = {
-+ { "ccache", "MEMORY:" },
-+ };
-+ const gss_key_value_set_desc cred_store = { 1, store_elements };
-+# endif
-+
-+
-+ major = gss_acquire_cred_from(&minor, host_name, lifetime,
-+ oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL);
-+ }
-+#else
-+ major = gss_acquire_cred(&minor, host_name, lifetime,
-+ oidset, GSS_C_BOTH, &host_creds, NULL, NULL);
-+#endif
-+ gss_release_name(&minor, &host_name);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u",
-+ lname, (unsigned)getuid(), (unsigned)geteuid());
-+ log_gss_error(major, minor, "S4U2Self: gss_acquire_cred");
-+ gss_release_oid_set(&status, &oidset);
-+ return -1;
-+ }
-+
-+ /* Import the SSH username as a GSSAPI/Kerberos name */
-+ gssbuf.value = (void *)user;
-+ gssbuf.length = strlen(user);
-+ major = gss_import_name(&minor, &gssbuf,
-+ GSS_C_NT_USER_NAME, &user_name);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_import_name (user) failed");
-+ gss_release_cred(&minor, &host_creds);
-+ gss_release_oid_set(&status, &oidset);
-+ return -1;
-+ }
-+ debug_gss_name("user name parsed as", user_name);
-+
-+ /* S4U2Self: obtain a service ticket for the user without their creds */
-+ debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user);
-+ major = gss_acquire_cred_impersonate_name(&minor,
-+ host_creds, user_name, lifetime,
-+ oidset, GSS_C_INITIATE,
-+ &impersonated_creds, NULL, NULL);
-+
-+ gss_release_cred(&minor, &host_creds);
-+ gss_release_oid_set(&status, &oidset);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_acquire_cred_impersonate_name failed for %.100s",
-+ user);
-+ log_gss_error(major, minor,
-+ "S4U2Self: gss_acquire_cred_impersonate_name");
-+ gss_release_name(&minor, &user_name);
-+ return -1;
-+ }
-+
-+ /* Get the display name (Kerberos principal string) for storecreds */
-+ major = gss_display_name(&minor, user_name, &displayname, NULL);
-+ gss_release_name(&minor, &user_name);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_display_name failed");
-+ gss_release_cred(&minor, &impersonated_creds);
-+ return -1;
-+ }
-+
-+ /* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */
-+ gssapi_client.mech = &gssapi_kerberos_mech;
-+ gssapi_client.creds = impersonated_creds;
-+ gssapi_client.displayname.value = xmalloc(displayname.length + 1);
-+ memcpy(gssapi_client.displayname.value,
-+ displayname.value, displayname.length);
-+ ((char *)gssapi_client.displayname.value)[displayname.length] = '\0';
-+ gssapi_client.displayname.length = displayname.length;
-+ /*
-+ * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name.
-+ * gss_display_name for a user-name returns the canonical principal
-+ * string (e.g. user@REALM) which krb5_parse_name can consume directly.
-+ */
-+ gssapi_client.exportedname.value = xmalloc(displayname.length + 1);
-+ memcpy(gssapi_client.exportedname.value,
-+ displayname.value, displayname.length);
-+ ((char *)gssapi_client.exportedname.value)[displayname.length] = '\0';
-+ gssapi_client.exportedname.length = displayname.length;
-+
-+ gss_release_buffer(&minor, &displayname);
-+ debug_f("S4U2Self succeeded for %.100s", user);
-+ return 0;
-+}
-+
-+/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */
- void
--ssh_gssapi_cleanup_creds(void)
-+ssh_gssapi_storecreds_s4u2self(void)
- {
-- krb5_ccache ccache = NULL;
-- krb5_error_code problem;
--
-- if (gssapi_client.store.data != NULL) {
-- if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) {
-- debug_f("krb5_cc_resolve(): %.100s",
-- krb5_get_err_text(gssapi_client.store.data, problem));
-- } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) {
-- debug_f("krb5_cc_destroy(): %.100s",
-- krb5_get_err_text(gssapi_client.store.data, problem));
-- } else {
-- krb5_free_context(gssapi_client.store.data);
-- gssapi_client.store.data = NULL;
-+ if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) {
-+ debug_f("no GSSAPI mechanism for storing S4U2Self credentials");
-+ return;
-+ }
-+ (*gssapi_client.mech->storecreds)(&gssapi_client);
-+}
-+
-+/*
-+ * Perform S4U2Proxy for each configured service principal, then flush all
-+ * resulting tickets into the user's ccache. Runs as user, after
-+ * ssh_gssapi_storecreds_s4u2self() has created the ccache.
-+ *
-+ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the
-+ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT
-+ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ. The output token
-+ * (AP-REQ) is discarded — we do not connect to the target service.
-+ *
-+ * After iterating all services, gss_store_cred() flushes the accumulated
-+ * proxy service tickets from the credential's internal ccache into the
-+ * KRB5CCNAME ccache that storecreds_s4u2self() already created.
-+ */
-+/* As user */
-+void
-+ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime)
-+{
-+ OM_uint32 major, minor;
-+ gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER;
-+ gss_name_t target_name;
-+ gss_ctx_id_t ctx;
-+ u_int i;
-+
-+ if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) {
-+ debug_f("no proxy credential available");
-+ return;
-+ }
-+ if (gssapi_client.store.envval == NULL) {
-+ debug_f("no ccache path set; cannot store proxy tickets");
-+ return;
-+ }
-+
-+ debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s",
-+ (unsigned)getuid(), (unsigned)geteuid(), nservices,
-+ gssapi_client.store.envval);
-+
-+ /* Point the GSSAPI library at the user's ccache for ticket storage */
-+ setenv("KRB5CCNAME", gssapi_client.store.envval, 1);
-+
-+ for (i = 0; i < nservices; i++) {
-+ ctx = GSS_C_NO_CONTEXT;
-+ target_name = GSS_C_NO_NAME;
-+
-+ service_buf.value = services[i];
-+ service_buf.length = strlen(services[i]);
-+
-+ /*
-+ * GSS_C_NO_OID: let the library determine the name type.
-+ * With Kerberos as the active mechanism, a fully-qualified
-+ * principal like "svc/host@REALM" is parsed correctly.
-+ */
-+ major = gss_import_name(&minor, &service_buf,
-+ GSS_C_NO_OID, &target_name);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_import_name failed for %.200s",
-+ services[i]);
-+ log_gss_error(major, minor, "S4U2Proxy: gss_import_name");
-+ continue;
- }
-+ debug_gss_name("target service name parsed as", target_name);
-+
-+ debug_f("calling gss_init_sec_context for %.200s", services[i]);
-+ major = gss_init_sec_context(&minor,
-+ gssapi_client.creds, /* proxy credential */
-+ &ctx, target_name,
-+ GSS_C_NO_OID, /* default mech (Kerberos) */
-+ 0, /* no flags, no mutual auth */
-+ lifetime,
-+ GSS_C_NO_CHANNEL_BINDINGS,
-+ GSS_C_NO_BUFFER, /* no input token */
-+ NULL, /* actual_mech_type */
-+ &output_token,
-+ NULL, /* ret_flags */
-+ NULL); /* time_rec */
-+
-+ gss_release_buffer(&minor, &output_token);
-+ gss_release_name(&minor, &target_name);
-+ if (ctx != GSS_C_NO_CONTEXT)
-+ gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER);
-+
-+ if (GSS_ERROR(major)) {
-+ logit_f("S4U2Proxy for %.200s on behalf of %.200s failed",
-+ services[i],
-+ (char *)gssapi_client.displayname.value);
-+ log_gss_error(major, minor,
-+ "S4U2Proxy: gss_init_sec_context");
-+ } else
-+ debug_f("S4U2Proxy ticket obtained for %.200s",
-+ services[i]);
- }
-+
-+ /*
-+ * Flush all proxy service tickets from the credential's internal
-+ * ccache into the KRB5CCNAME ccache via gss_store_cred().
-+ */
-+ major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE,
-+ GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */,
-+ NULL, NULL);
-+ if (GSS_ERROR(major)) {
-+ logit_f("gss_store_cred failed; proxy tickets may be missing");
-+ log_gss_error(major, minor, "S4U2Proxy: gss_store_cred");
-+ }
-+
-+ unsetenv("KRB5CCNAME");
-+}
-+
-+#ifndef KRB5
-+/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
-+void
-+ssh_gssapi_cleanup_creds(void)
-+{
-+}
-+
-+/*
-+ * Filter the user's ccache; full implementation in gss-serv-krb5.c.
-+ */
-+void
-+ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
-+ char **proxy_services, u_int nproxy_services)
-+{
- }
-+#endif /* !KRB5 */
-
- /* As user */
- int
-diff --git a/servconf.c b/servconf.c
-index f4d6b19dd..e05a31584 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -149,6 +149,9 @@ initialize_server_options(ServerOptions *options)
- options->gss_indicators = NULL;
- options->gss_store_rekey = -1;
- options->gss_kex_algorithms = NULL;
-+ options->gss_allow_s4u2self = -1;
-+ options->gss_proxy_services = NULL;
-+ options->num_gss_proxy_services = 0;
- options->use_kuserok = -1;
- options->enable_k5users = -1;
- options->password_authentication = -1;
-@@ -406,6 +409,8 @@ fill_default_server_options(ServerOptions *options)
- options->gss_deleg_creds = 1;
- if (options->gss_strict_acceptor == -1)
- options->gss_strict_acceptor = 1;
-+ if (options->gss_allow_s4u2self == -1)
-+ options->gss_allow_s4u2self = 0;
- if (options->gss_store_rekey == -1)
- options->gss_store_rekey = 0;
- #ifdef GSSAPI
-@@ -603,6 +608,7 @@ typedef enum {
- sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
- sPerSourcePenalties, sPerSourcePenaltyExemptList,
- sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
-+ sGssAllowS4U2Self, sGssProxyS4U2Services,
- sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
- sGssIndicators, sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- sAcceptEnv, sSetEnv, sPermitTunnel,
-@@ -702,6 +708,8 @@ static struct {
- { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- { "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
- { "gssapiindicators", sGssIndicators, SSHCFG_ALL },
-+ { "gssapiallows4u2self", sGssAllowS4U2Self, SSHCFG_ALL },
-+ { "gssapiproxys4u2services", sGssProxyS4U2Services, SSHCFG_ALL },
- #else
- { "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -713,6 +721,8 @@ static struct {
- { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- { "gssapienablek5users", sUnsupported, SSHCFG_ALL },
- { "gssapiindicators", sUnsupported, SSHCFG_ALL },
-+ { "gssapiallows4u2self", sUnsupported, SSHCFG_ALL },
-+ { "gssapiproxys4u2services", sUnsupported, SSHCFG_ALL },
- #endif
- { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -1754,6 +1764,44 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- options->gss_indicators = xstrdup(arg);
- break;
-
-+ case sGssAllowS4U2Self:
-+ arg = argv_next(&ac, &av);
-+ if (!arg || *arg == '\0')
-+ fatal("%s line %d: %s missing argument.",
-+ filename, linenum, keyword);
-+ if (strcasecmp(arg, "no") == 0)
-+ value = 0;
-+ else if (strcasecmp(arg, "yes") == 0)
-+ value = INT_MAX;
-+ else if ((value = convtime(arg)) <= 0)
-+ fatal("%s line %d: invalid %s value \"%s\".",
-+ filename, linenum, keyword, arg);
-+ if (*activep && options->gss_allow_s4u2self == -1)
-+ options->gss_allow_s4u2self = value;
-+ break;
-+
-+ case sGssProxyS4U2Services:
-+ while ((arg = argv_next(&ac, &av)) != NULL) {
-+ if (*arg == '\0')
-+ fatal("%s line %d: %s missing argument.",
-+ filename, linenum, keyword);
-+ if (strcasecmp(arg, "none") == 0) {
-+ /* "none" clears any previous list */
-+ for (i = 0; i < options->num_gss_proxy_services; i++)
-+ free(options->gss_proxy_services[i]);
-+ free(options->gss_proxy_services);
-+ options->gss_proxy_services = NULL;
-+ options->num_gss_proxy_services = 0;
-+ break;
-+ }
-+ if (!*activep)
-+ continue;
-+ opt_array_append(filename, linenum, keyword,
-+ &options->gss_proxy_services,
-+ &options->num_gss_proxy_services, arg);
-+ }
-+ break;
-+
- case sPasswordAuthentication:
- intptr = &options->password_authentication;
- goto parse_flag;
-@@ -3053,6 +3101,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
-
- M_CP_INTOPT(password_authentication);
- M_CP_INTOPT(gss_authentication);
-+ M_CP_INTOPT(gss_allow_s4u2self);
- M_CP_INTOPT(pubkey_authentication);
- M_CP_INTOPT(pubkey_auth_options);
- M_CP_INTOPT(kerberos_authentication);
-@@ -3407,6 +3456,15 @@ dump_config(ServerOptions *o)
- dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
- dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
- dump_cfg_string(sGssIndicators, o->gss_indicators);
-+ if (o->gss_allow_s4u2self == 0)
-+ printf("%s no\n", lookup_opcode_name(sGssAllowS4U2Self));
-+ else if (o->gss_allow_s4u2self == INT_MAX)
-+ printf("%s yes\n", lookup_opcode_name(sGssAllowS4U2Self));
-+ else
-+ printf("%s %d\n", lookup_opcode_name(sGssAllowS4U2Self),
-+ o->gss_allow_s4u2self);
-+ dump_cfg_strarray_oneline(sGssProxyS4U2Services, o->num_gss_proxy_services,
-+ o->gss_proxy_services);
- #endif
- dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
- dump_cfg_fmtint(sKbdInteractiveAuthentication,
-diff --git a/servconf.h b/servconf.h
-index fea25947e..9acea32cf 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -161,6 +161,9 @@ typedef struct {
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
- int gss_deleg_creds; /* If true, accept delegated GSS credentials */
- int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */
-+ int gss_allow_s4u2self; /* 0=no, INT_MAX=yes (GSS_C_INDEFINITE), >0=ticket lifetime s */
-+ char **gss_proxy_services; /* S4U2Proxy target service principals */
-+ u_int num_gss_proxy_services;
- int gss_store_rekey;
- char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */
- int password_authentication; /* If true, permit password
-@@ -314,6 +317,7 @@ TAILQ_HEAD(include_list, include_item);
- M_CP_STROPT(permit_user_env_allowlist); \
- M_CP_STROPT(pam_service_name); \
- M_CP_STROPT(gss_indicators); \
-+ M_CP_STRARRAYOPT(gss_proxy_services, num_gss_proxy_services, 1); \
- M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files, 1);\
- M_CP_STRARRAYOPT(revoked_keys_files, \
- num_revoked_keys_files, 1); \
-diff --git a/ssh-gss.h b/ssh-gss.h
-index 1506719a9..a0d51c9be 100644
---- a/ssh-gss.h
-+++ b/ssh-gss.h
-@@ -119,6 +119,7 @@ typedef struct {
- int used;
- int updated;
- char **indicators; /* auth indicators */
-+ int allow_self; /* allow protocol transition */
- } ssh_gssapi_client;
-
- typedef struct ssh_gssapi_mech_struct {
-@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
- void ssh_gssapi_do_child(char ***, u_int *);
- void ssh_gssapi_cleanup_creds(void);
- int ssh_gssapi_storecreds(void);
-+int ssh_gssapi_credentials_stored(void);
-+ssh_gssapi_ccache *ssh_gssapi_get_ccache(void);
-+int ssh_gssapi_user_has_valid_tgt(u_int);
-+int ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int);
-+int ssh_gssapi_s4u2self(const char *, u_int);
-+void ssh_gssapi_storecreds_s4u2self(void);
-+void ssh_gssapi_s4u2proxy(char **, u_int, u_int);
-+/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */
-+#define SSH_GSSAPI_CCFILTER_TGT (1u << 0) /* krbtgt/... entries */
-+#define SSH_GSSAPI_CCFILTER_SELF (1u << 1) /* S4U2Self evidence ticket */
-+#define SSH_GSSAPI_CCFILTER_PROXY (1u << 2) /* S4U2Proxy service tickets */
-+void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int);
- const char *ssh_gssapi_displayname(void);
-
- char *ssh_gssapi_server_mechanisms(void);
-diff --git a/sshd-session.c b/sshd-session.c
-index 7064afd7c..d1d13d863 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -1393,6 +1393,104 @@ main(int ac, char **av)
- authctxt->krb5_set_env = ssh_gssapi_storecreds();
- restore_uid();
- }
-+ /*
-+ * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
-+ * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
-+ * to obtain an impersonated credential for the user, then optionally
-+ * follow with S4U2Proxy for configured target services.
-+ *
-+ * GSSAPIAllowS4U2Self alone: store S4U2Self evidence ticket only;
-+ * the host TGT is removed.
-+ * GSSAPIProxyS4U2Services alone: store host TGT and S4U2Proxy service
-+ * tickets; the S4U2Self evidence ticket
-+ * is removed.
-+ * Both: store host TGT, S4U2Self evidence ticket,
-+ * and all S4U2Proxy service tickets.
-+ *
-+ * When S4U2Proxy tickets are present the host TGT must remain in the
-+ * ccache; applications check for TGT presence to determine whether
-+ * Kerberos credentials are available. Only in GSSAPIAllowS4U2Self-alone
-+ * mode (no proxy tickets) is the host TGT removed.
-+ *
-+ * Skip S4U2Self when the user already has credentials covering the
-+ * requested lifetime: check for a valid TGT in the GSSAPIAllowS4U2Self-
-+ * alone case, or for valid proxy tickets for every configured service
-+ * otherwise.
-+ */
-+ if ((options.gss_allow_s4u2self || options.num_gss_proxy_services > 0) &&
-+ !ssh_gssapi_credentials_stored()) {
-+ u_int lifetime = (!options.gss_allow_s4u2self ||
-+ options.gss_allow_s4u2self == INT_MAX) ?
-+ GSS_C_INDEFINITE : (u_int)options.gss_allow_s4u2self;
-+ int skip = 0;
-+
-+ temporarily_use_uid(authctxt->pw);
-+ if (options.gss_allow_s4u2self &&
-+ options.num_gss_proxy_services == 0) {
-+ /* S4U2Self-alone: skip if user already has a valid TGT */
-+ skip = ssh_gssapi_user_has_valid_tgt(lifetime);
-+ } else if (options.num_gss_proxy_services > 0) {
-+ /*
-+ * Proxy-only or both: skip if every configured service
-+ * already has a valid ticket in the user's ccache.
-+ * Service tickets are not GSSAPI initiator credentials,
-+ * so gss_acquire_cred() cannot be used; iterate the
-+ * ccache with the krb5 API instead.
-+ */
-+ skip = ssh_gssapi_user_has_valid_proxy_tickets(
-+ options.gss_proxy_services,
-+ options.num_gss_proxy_services,
-+ lifetime);
-+ }
-+ restore_uid();
-+
-+ if (skip) {
-+ debug_f("user %.100s already has valid Kerberos "
-+ "credentials, skipping S4U2Self",
-+ authctxt->user);
-+ } else if (ssh_gssapi_s4u2self(authctxt->user, lifetime) == 0) {
-+ u_int filter;
-+
-+ temporarily_use_uid(authctxt->pw);
-+ /*
-+ * Always create the ccache via storecreds_s4u2self so
-+ * that s4u2proxy has a ccache to store tickets into.
-+ * gss_krb5_copy_ccache() copies the host service's own
-+ * TGT along with the evidence ticket; filter_ccache
-+ * removes the ticket classes that should not be kept.
-+ */
-+ ssh_gssapi_storecreds_s4u2self();
-+ if (options.num_gss_proxy_services > 0)
-+ ssh_gssapi_s4u2proxy(
-+ options.gss_proxy_services,
-+ options.num_gss_proxy_services,
-+ lifetime);
-+
-+ /*
-+ * Remove the host TGT only in GSSAPIAllowS4U2Self-alone
-+ * mode; when proxy tickets are present the TGT must
-+ * stay so that applications recognise the ccache as
-+ * holding live Kerberos credentials.
-+ * Remove the S4U2Self evidence ticket in proxy-only
-+ * mode (GSSAPIProxyS4U2Services without GSSAPIAllowS4U2Self).
-+ */
-+ filter = 0;
-+ if (options.gss_allow_s4u2self &&
-+ options.num_gss_proxy_services == 0)
-+ filter = SSH_GSSAPI_CCFILTER_TGT |
-+ SSH_GSSAPI_CCFILTER_PROXY;
-+ else if (!options.gss_allow_s4u2self)
-+ filter = SSH_GSSAPI_CCFILTER_SELF;
-+ if (filter != 0)
-+ ssh_gssapi_krb5_filter_ccache(filter,
-+ options.gss_proxy_services,
-+ options.num_gss_proxy_services);
-+ restore_uid();
-+ } else {
-+ logit("S4U2Self failed for user %.100s, continuing",
-+ authctxt->user);
-+ }
-+ }
- #endif
- #ifdef WITH_SELINUX
- sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
-diff --git a/sshd_config.5 b/sshd_config.5
-index c28220077..30d1d59da 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -846,6 +846,76 @@ FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
- The default
- .Dq none
- is to not use GSSAPI authentication indicators for access decisions.
-+.It Cm GSSAPIAllowS4U2Self
-+Controls whether the SSH server performs a Kerberos protocol transition
-+(S4U2Self) after a successful authentication using any other method.
-+Accepted values are
-+.Cm no ,
-+.Cm yes ,
-+or a time interval (see
-+.Sx TIME FORMATS
-+below).
-+.Cm no
-+disables S4U2Self entirely.
-+.Cm yes
-+enables S4U2Self and requests a ticket with the maximum lifetime
-+permitted by the KDC.
-+A time interval (e.g.\&
-+.Cm 8h ,
-+.Cm 1d )
-+enables S4U2Self and requests a ticket valid for at most that duration.
-+The option is a no-op when delegated GSSAPI credentials are already available.
-+The obtained service ticket is stored in the default credentials cache and is
-+accessible to any application that has access to the Kerberos host principal
-+.Pq host/machine.fqdn@REALM
-+credentials on the same host.
-+.Pp
-+The default is
-+.Cm no .
-+.It Cm GSSAPIProxyS4U2Services
-+Specifies a list of Kerberos service principals for which constrained
-+delegation (S4U2Proxy) tickets should be obtained after a successful
-+S4U2Self protocol transition.
-+Each entry must be a fully-qualified Kerberos principal name of the form
-+.Ar service/host@REALM .
-+Multiple principals may be listed, separated by whitespace.
-+The keyword
-+.Cm none
-+clears any previously set list.
-+.Pp
-+This option may be used independently of
-+.Cm GSSAPIAllowS4U2Self .
-+When S4U2Self succeeds, the server iterates the list and calls
-+.Xr gss_init_sec_context 3
-+for each principal with the proxy credential obtained by S4U2Self as
-+the initiator.
-+The GSSAPI library presents the evidence ticket to the KDC via an
-+S4U2Proxy TGS-REQ; if the host service holds the necessary constrained-
-+delegation permission in the KDC, a service ticket from the user to
-+the target service is issued.
-+These tickets are accumulated and then flushed into the user's ccache
-+via
-+.Xr gss_store_cred 3 ,
-+so that any application running in the user's session can use them
-+without further interaction.
-+The AP-REQ output token of each
-+.Xr gss_init_sec_context 3
-+call is discarded; no network connection to the target service is made.
-+.Pp
-+When used together with
-+.Cm GSSAPIAllowS4U2Self ,
-+the TGT and S4U2Self ticket are also stored in the user's ccache in
-+addition to the S4U2Proxy service tickets.
-+When used alone (without
-+.Cm GSSAPIAllowS4U2Self ) ,
-+only the S4U2Proxy service tickets are stored; the intermediate S4U2Self
-+credential is not placed in the user's ccache.
-+.Pp
-+This option supports
-+.Cm Match
-+blocks, allowing per-user or per-host lists of delegation targets.
-+.Pp
-+The default is empty (no S4U2Proxy delegation is performed).
- .It Cm HostbasedAcceptedAlgorithms
- The default is handled system-wide by
- .Xr crypto-policies 7 .
---
-2.53.0
-
diff --git a/0052-openssh-10.2p1-pkcs11-uri.patch b/0052-openssh-10.2p1-pkcs11-uri.patch
new file mode 100644
index 0000000..7e68251
--- /dev/null
+++ b/0052-openssh-10.2p1-pkcs11-uri.patch
@@ -0,0 +1,3423 @@
+From dc4d137cc79b3ec45ed7624c437e86409808438e Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 15 Dec 2025 14:24:07 +0100
+Subject: [PATCH 52/53] openssh-10.2p1-pkcs11-uri
+
+---
+ .depend | 1 +
+ Makefile.in | 26 +-
+ configure.ac | 37 ++
+ regress/Makefile | 5 +-
+ regress/pkcs11-uri.sh | 410 ++++++++++++++++
+ regress/unittests/Makefile | 2 +-
+ regress/unittests/pkcs11/tests.c | 353 ++++++++++++++
+ ssh-add.c | 48 +-
+ ssh-agent.c | 117 ++++-
+ ssh-keygen.c | 7 +-
+ ssh-pkcs11-client.c | 19 +
+ ssh-pkcs11-uri.c | 438 ++++++++++++++++++
+ ssh-pkcs11-uri.h | 46 ++
+ ssh-pkcs11.c | 771 ++++++++++++++++++++++---------
+ ssh-pkcs11.h | 4 +
+ ssh.c | 104 ++++-
+ ssh_config.5 | 15 +
+ sshkey.c | 4 +
+ 18 files changed, 2134 insertions(+), 273 deletions(-)
+ create mode 100644 regress/pkcs11-uri.sh
+ create mode 100644 regress/unittests/pkcs11/tests.c
+ create mode 100644 ssh-pkcs11-uri.c
+ create mode 100644 ssh-pkcs11-uri.h
+
+diff --git a/.depend b/.depend
+index 45ad2bf4e..b430c7e77 100644
+--- a/.depend
++++ b/.depend
+@@ -145,6 +145,7 @@ ssh-mldsa-eddsa.o: includes.h config.h defines.h platform.h openbsd-compat/openb
+ ssh-pkcs11-client.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h pathnames.h xmalloc.h sshbuf.h log.h ssherr.h misc.h sshkey.h authfd.h atomicio.h ssh-pkcs11.h
+ ssh-pkcs11-helper.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h sshbuf.h log.h ssherr.h misc.h sshkey.h authfd.h ssh-pkcs11.h
+ ssh-pkcs11.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h log.h ssherr.h sshkey.h ssh-pkcs11.h
++ssh-pkcs11-uri.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ ssh-rsa.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
+ ssh-sk-client.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h log.h ssherr.h sshbuf.h sshkey.h msg.h pathnames.h ssh-sk.h misc.h
+ ssh-sk-helper.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/bsd-sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h log.h ssherr.h sshkey.h authfd.h misc.h sshbuf.h msg.h uidswap.h ssh-sk.h ssh-pkcs11.h
+diff --git a/Makefile.in b/Makefile.in
+index f61f89e4d..23d5f908a 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -113,12 +113,12 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+ sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
+
+-P11OBJS= ssh-pkcs11-client.o
++P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
+
+ SKOBJS= ssh-sk-client.o
+
+ SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
+- sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o $(SKOBJS)
++ sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+
+ SSHDOBJS=sshd.o \
+ platform-listen.o \
+@@ -162,11 +162,11 @@ SSHADD_OBJS= ssh-add.o $(P11OBJS) $(SKOBJS)
+
+ SSHAGENT_OBJS= ssh-agent.o $(P11OBJS) $(SKOBJS)
+
+-SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o ssh-pkcs11.o $(SKOBJS)
++SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+
+ SSHKEYSIGN_OBJS=ssh-keysign.o readconf.o uidswap.o $(P11OBJS) $(SKOBJS)
+
+-P11HELPER_OBJS= ssh-pkcs11-helper.o ssh-pkcs11.o $(SKOBJS)
++P11HELPER_OBJS= ssh-pkcs11-helper.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+
+ SKHELPER_OBJS= ssh-sk-helper.o ssh-sk.o sk-usbhid.o ssherr-nolibcrypto.o
+
+@@ -336,6 +336,8 @@ clean: regressclean
+ rm -f regress/unittests/sshsig/test_sshsig$(EXEEXT)
+ rm -f regress/unittests/utf8/*.o
+ rm -f regress/unittests/utf8/test_utf8$(EXEEXT)
++ rm -f regress/unittests/pkcs11/*.o
++ rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT)
+ rm -f regress/misc/sk-dummy/*.o
+ rm -f regress/misc/sk-dummy/*.lo
+ rm -f regress/misc/ssh-verify-attestation/ssh-verify-attestation$(EXEEXT)
+@@ -379,6 +381,8 @@ distclean: regressclean
+ rm -f regress/unittests/sshsig/test_sshsig
+ rm -f regress/unittests/utf8/*.o
+ rm -f regress/unittests/utf8/test_utf8
++ rm -f regress/unittests/pkcs11/*.o
++ rm -f regress/unittests/pkcs11/test_pkcs11
+ rm -f regress/misc/sk-dummy/*.o
+ rm -f regress/misc/sk-dummy/*.lo
+ rm -f regress/misc/sk-dummy/sk-dummy.so
+@@ -561,6 +565,7 @@ regress-prep:
+ $(MKDIR_P) `pwd`/regress/unittests/sshkey
+ $(MKDIR_P) `pwd`/regress/unittests/sshsig
+ $(MKDIR_P) `pwd`/regress/unittests/utf8
++ $(MKDIR_P) `pwd`/regress/unittests/pkcs11
+ $(MKDIR_P) `pwd`/regress/misc/sk-dummy
+ $(MKDIR_P) `pwd`/regress/misc/ssh-verify-attestation
+ [ -f `pwd`/regress/Makefile ] || \
+@@ -764,6 +769,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
+ regress/unittests/test_helper/libtest_helper.a \
+ -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(TESTLIBS)
+
++UNITTESTS_TEST_PKCS11_OBJS=\
++ regress/unittests/pkcs11/tests.o
++
++regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
++ ${UNITTESTS_TEST_PKCS11_OBJS} ssh-pkcs11-uri.o \
++ regress/unittests/test_helper/libtest_helper.a libssh.a
++ $(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
++ regress/unittests/test_helper/libtest_helper.a \
++ ssh-pkcs11-uri.o -lssh -lopenbsd-compat -lcrypto $(LIBS) -lm
++
+ # These all need to be compiled -fPIC, so they are treated differently.
+ SK_DUMMY_OBJS=\
+ regress/misc/sk-dummy/sk-dummy.lo \
+@@ -811,7 +826,8 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
+ regress/unittests/sshbuf/test_sshbuf$(EXEEXT) \
+ regress/unittests/sshkey/test_sshkey$(EXEEXT) \
+ regress/unittests/sshsig/test_sshsig$(EXEEXT) \
+- regress/unittests/utf8/test_utf8$(EXEEXT)
++ regress/unittests/utf8/test_utf8$(EXEEXT) \
++ regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
+
+ tests: file-tests t-exec interop-tests extra-tests unit
+ echo all tests passed
+diff --git a/configure.ac b/configure.ac
+index b2c959ff3..c37b02efb 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2305,12 +2305,14 @@ AC_LINK_IFELSE(
+ [AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).])
+ ])
+
++SCARD_MSG="yes"
+ disable_pkcs11=
+ AC_ARG_ENABLE([pkcs11],
+ [ --disable-pkcs11 disable PKCS#11 support code [no]],
+ [
+ if test "x$enableval" = "xno" ; then
+ disable_pkcs11=1
++ SCARD_MSG="no"
+ fi
+ ]
+ )
+@@ -2340,6 +2342,40 @@ AC_SEARCH_LIBS([dlopen], [dl])
+ AC_CHECK_FUNCS([dlopen])
+ AC_CHECK_DECL([RTLD_NOW], [], [], [#include <dlfcn.h>])
+
++# Check whether we have a p11-kit, we got default provider on command line
++DEFAULT_PKCS11_PROVIDER_MSG="no"
++AC_ARG_WITH([default-pkcs11-provider],
++ [ --with-default-pkcs11-provider[[=PATH]] Use default pkcs11 provider (p11-kit detected by default)],
++ [ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then
++ if test "x$withval" = "xyes" ; then
++ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
++ if test "x$PKGCONFIG" != "xno"; then
++ AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit])
++ if "$PKGCONFIG" "p11-kit-1"; then
++ AC_MSG_RESULT([yes])
++ use_pkgconfig_for_p11kit=yes
++ else
++ AC_MSG_RESULT([no])
++ fi
++ fi
++ else
++ PKCS11_PATH="${withval}"
++ fi
++ if test "x$use_pkgconfig_for_p11kit" = "xyes"; then
++ PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1`
++ fi
++ AC_CHECK_FILE("$PKCS11_PATH",
++ [ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)])
++ DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH"
++ ],
++ [ AC_MSG_ERROR([Requested PKCS11 provided not found]) ]
++ )
++ else
++ AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider])
++ fi ]
++)
++
++
+ # IRIX has a const char return value for gai_strerror()
+ AC_CHECK_FUNCS([gai_strerror], [
+ AC_DEFINE([HAVE_GAI_STRERROR])
+@@ -5921,6 +5957,7 @@ echo " BSD Auth support: $BSD_AUTH_MSG"
+ echo " Random number source: $RAND_MSG"
+ echo " Privsep sandbox style: $SANDBOX_STYLE"
+ echo " PKCS#11 support: $enable_pkcs11"
++echo " Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG"
+ echo " U2F/FIDO support: $enable_sk"
+
+ echo ""
+diff --git a/regress/Makefile b/regress/Makefile
+index fa19ada28..492112e80 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -118,6 +118,7 @@ LTESTS= connect \
+ penalty-expire \
+ connect-bigconf \
+ ssh-pkcs11 \
++ pkcs11-uri \
+ ssh-tty \
+ proxyjump
+
+@@ -144,7 +145,8 @@ CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \
+ known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
+ modpipe netcat no_identity_config \
+ pidfile putty.rsa2 ready regress.log remote_pid \
+- revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \
++ revoked-* rsa rsa-agent rsa-agent.pub rsa-agent-cert.pub \
++ rsa.pub rsa_ssh2_cr.prv pkcs11*.crt pkcs11*.key pkcs11.info \
+ rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
+ scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
+ sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
+@@ -294,6 +296,7 @@ unit unit-bench:
+ test "x${UNITTEST_VERBOSE}" = "x" || ARGS="$$ARGS -v"; \
+ test "x${UNITTEST_BENCH_DETAIL}" = "x" || ARGS="$$ARGS -B"; \
+ test "x${UNITTEST_BENCH_ONLY}" = "x" || ARGS="$$ARGS -O ${UNITTEST_BENCH_ONLY}"; \
++ $$V ${.OBJDIR}/unittests/pkcs11/test_pkcs11 ; \
+ $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf $${ARGS}; \
+ $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
+ -d ${.CURDIR}/unittests/sshkey/testdata $${ARGS}; \
+diff --git a/regress/pkcs11-uri.sh b/regress/pkcs11-uri.sh
+new file mode 100644
+index 000000000..72d69bda0
+--- /dev/null
++++ b/regress/pkcs11-uri.sh
+@@ -0,0 +1,410 @@
++#
++# Copyright (c) 2017 Red Hat
++#
++# Authors: Jakub Jelen <jjelen@redhat.com>
++#
++# 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.
++
++tid="pkcs11 tests with soft token"
++
++try_token_libs() {
++ for _lib in "$@" ; do
++ if test -f "$_lib" ; then
++ verbose "Using token library $_lib"
++ TEST_SSH_PKCS11="$_lib"
++ return
++ fi
++ done
++ echo "skipped: Unable to find PKCS#11 token library"
++ exit 0
++}
++
++try_token_libs \
++ /usr/local/lib/softhsm/libsofthsm2.so \
++ /usr/lib64/pkcs11/libsofthsm2.so \
++ /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
++
++TEST_SSH_PIN=1234
++TEST_SSH_SOPIN=12345678
++if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
++ SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
++ export SSH_PKCS11_HELPER
++fi
++
++test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
++
++# setup environment for softhsm token
++DIR=$OBJ/SOFTHSM
++rm -rf $DIR
++TOKEN=$DIR/tokendir
++mkdir -p $TOKEN
++SOFTHSM2_CONF=$DIR/softhsm2.conf
++export SOFTHSM2_CONF
++cat > $SOFTHSM2_CONF << EOF
++# SoftHSM v2 configuration file
++directories.tokendir = ${TOKEN}
++objectstore.backend = file
++# ERROR, WARNING, INFO, DEBUG
++log.level = DEBUG
++# If CKF_REMOVABLE_DEVICE flag should be set
++slots.removable = false
++EOF
++out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
++slot=$(echo -- $out | sed 's/.* //')
++
++# prevent ssh-agent from calling ssh-askpass
++SSH_ASKPASS=/usr/bin/true
++export SSH_ASKPASS
++unset DISPLAY
++# We need interactive access to test PKCS# since it prompts for PIN
++# Backup ssh_proxy before modifications
++cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
++sed -i 's/.*BatchMode.*//g' $OBJ/ssh_proxy
++# Remove IdentityFile entries to prevent exceeding MaxAuthTries when using
++# PKCS11Provider (-I) or PKCS11 URIs. The default ssh_config includes multiple
++# identity files that would be tried before PKCS11 keys, causing authentication
++# to fail with "Too many authentication failures" before PKCS11 keys are reached.
++grep -iv IdentityFile $OBJ/ssh_proxy > $OBJ/ssh_proxy.tmp
++mv $OBJ/ssh_proxy.tmp $OBJ/ssh_proxy
++
++# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh)
++notty() {
++ perl -e 'use POSIX; POSIX::setsid();
++ if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
++}
++
++trace "generating keys"
++ID1="02"
++ID2="04"
++ID3="06"
++RSA=${DIR}/RSA
++EC=${DIR}/EC
++ED25519=${DIR}/ED25519
++openssl genpkey -algorithm rsa > $RSA
++openssl pkcs8 -nocrypt -in $RSA |\
++ softhsm2-util --slot "$slot" --label "SSH RSA Key $ID1" --id $ID1 \
++ --pin "$TEST_SSH_PIN" --import /dev/stdin
++openssl genpkey \
++ -genparam \
++ -algorithm ec \
++ -pkeyopt ec_paramgen_curve:prime256v1 |\
++ openssl genpkey \
++ -paramfile /dev/stdin > $EC
++openssl pkcs8 -nocrypt -in $EC |\
++ softhsm2-util --slot "$slot" --label "SSH ECDSA Key $ID2" --id $ID2 \
++ --pin "$TEST_SSH_PIN" --import /dev/stdin
++openssl genpkey -algorithm ed25519 > $ED25519
++openssl pkcs8 -nocrypt -in $ED25519 |\
++ softhsm2-util --slot "$slot" --label "SSH ED25519 Key $ID3" --id $ID3 \
++ --pin "$TEST_SSH_PIN" --import /dev/stdin
++
++trace "List the keys in the ssh-keygen with PKCS#11 URIs"
++${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys
++if [ $? -ne 0 ]; then
++ fail "FAIL: keygen fails to enumerate keys on PKCS#11 token"
++fi
++grep "pkcs11:" $OBJ/token_keys > /dev/null
++if [ $? -ne 0 ]; then
++ fail "FAIL: The keys from ssh-keygen do not contain PKCS#11 URI as a comment"
++fi
++
++# Set the ECDSA key to authorized keys
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Simple connect with ssh (without PKCS#11 URI)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \
++ -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with pkcs11 failed (exit code $r)"
++fi
++
++trace "Connect with PKCS#11 URI"
++trace " (ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace " (RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Connect with PKCS#11 URI including PIN should not prompt"
++trace " (ECDSA key should succeed)"
++${SSH} -F $OBJ/ssh_proxy -i \
++ "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace " (RSA key should fail)"
++${SSH} -F $OBJ/ssh_proxy -i \
++ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (ED25519 key should succeed)"
++${SSH} -F $OBJ/ssh_proxy -i \
++ "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Connect with various filtering options in PKCS#11 URI"
++trace " (by object label, ECDSA should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:object=SSH%20ECDSA%20Key%2004?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace " (by object label, RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:object=SSH%20RSA%20Key%2002?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (by object label, ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:object=SSH%20ED25519%20Key%2006?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (by token label, ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID2};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (by token label, ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID3};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (by wrong token label, should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:token=token-slot-99?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++
++
++
++trace "Test PKCS#11 URI specification in configuration files"
++echo "IdentityFile \"pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}\"" \
++ >> $OBJ/ssh_proxy
++trace " (ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI in config failed (exit code $r)"
++fi
++
++# Set the RSA key as authorized
++grep "RSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace " (RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI in config succeeded (should fail)"
++fi
++sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
++
++trace "Combination of PKCS11Provider and PKCS11URI on commandline"
++trace " (RSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++ -i "pkcs11:id=%${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect with PKCS#11 URI and provider combination" \
++ "failed (exit code $r)"
++fi
++
++trace "Regress: Missing provider in PKCS11URI option"
++${SSH} -F $OBJ/ssh_proxy \
++ -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5
++r=$?
++if [ $r -eq 139 ]; then
++ fail "FAIL: ssh connect with missing provider_id from configuration option" \
++ "crashed (exit code $r)"
++fi
++
++
++trace "SSH Agent can work with PKCS#11 URI"
++trace "start the agent"
++eval `${SSHAGENT} -s` > /dev/null
++
++r=$?
++if [ $r -ne 0 ]; then
++ fail "could not start ssh-agent: exit code $r"
++else
++ trace "add whole provider to agent"
++ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++ "pkcs11:?module-path=${TEST_SSH_PKCS11}" #> /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add failed with whole provider: exit code $r"
++ fi
++
++ trace " pkcs11 list via agent (all keys)"
++ ${SSHADD} -l > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add -l failed with whole provider: exit code $r"
++ fi
++
++ trace " pkcs11 connect via agent (all keys)"
++ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++ r=$?
++ if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect failed with whole provider (exit code $r)"
++ fi
++
++ trace " remove pkcs11 keys (all keys)"
++ ${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add -d failed with whole provider: exit code $r"
++ fi
++
++ trace "add only RSA key to the agent"
++ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL ssh-add failed with RSA key: exit code $r"
++ fi
++
++ trace " pkcs11 connect via agent (RSA key)"
++ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++ r=$?
++ if [ $r -ne 5 ]; then
++ fail "FAIL: ssh connect failed with RSA key (exit code $r)"
++ fi
++
++ trace " remove RSA pkcs11 key"
++ ${SSHADD} -d "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" \
++ > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add -d failed with RSA key: exit code $r"
++ fi
++
++ trace "add only ECDSA key to the agent"
++ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++ "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add failed with second key: exit code $r"
++ fi
++
++ trace " pkcs11 connect via agent (ECDSA key should fail)"
++ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++ r=$?
++ if [ $r -eq 5 ]; then
++ fail "FAIL: ssh connect passed with ECDSA key (should fail)"
++ fi
++
++ trace "add also the RSA key to the agent"
++ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "FAIL: ssh-add failed with first key: exit code $r"
++ fi
++
++ trace " remove ECDSA pkcs11 key"
++ ${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
++ > /dev/null 2>&1
++ r=$?
++ if [ $r -ne 0 ]; then
++ fail "ssh-add -d failed with ECDSA key: exit code $r"
++ fi
++
++ trace " remove already-removed pkcs11 key should fail"
++ ${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
++ > /dev/null 2>&1
++ r=$?
++ if [ $r -eq 0 ]; then
++ fail "FAIL: ssh-add -d passed with non-existing key (should fail)"
++ fi
++
++ trace " pkcs11 connect via agent (the RSA key should be still usable)"
++ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++ r=$?
++ if [ $r -ne 5 ]; then
++ fail "ssh connect failed with RSA key (after removing ECDSA): exit code $r"
++ fi
++
++ trace "kill agent"
++ ${SSHAGENT} -k > /dev/null
++fi
++
++# Restore original ssh_proxy
++mv $OBJ/ssh_proxy_bak $OBJ/ssh_proxy
+diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
+index 5d482eab4..51bed5baf 100644
+--- a/regress/unittests/Makefile
++++ b/regress/unittests/Makefile
+@@ -1,6 +1,6 @@
+ # $OpenBSD: Makefile,v 1.15 2026/06/14 04:08:05 djm Exp $
+
+ SUBDIR= test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
+-SUBDIR+=authopt misc sshsig servconf crypto
++SUBDIR+=authopt misc sshsig servconf crypto pkcs11
+
+ .include <bsd.subdir.mk>
+diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c
+new file mode 100644
+index 000000000..89ba45c4e
+--- /dev/null
++++ b/regress/unittests/pkcs11/tests.c
+@@ -0,0 +1,353 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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"
++
++#include <locale.h>
++#include <string.h>
++
++#include "../test_helper/test_helper.h"
++
++#include "sshbuf.h"
++#include "ssh-pkcs11-uri.h"
++
++#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
++
++/* prototypes are not public -- specify them here internally for tests */
++struct sshbuf *percent_encode(const char *, size_t, char *);
++int percent_decode(char *, char **);
++
++void
++compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b)
++{
++ ASSERT_PTR_NE(a, NULL);
++ ASSERT_PTR_NE(b, NULL);
++ ASSERT_SIZE_T_EQ(a->id_len, b->id_len);
++ ASSERT_MEM_EQ(a->id, b->id, a->id_len);
++ if (b->object != NULL)
++ ASSERT_STRING_EQ(a->object, b->object);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->object, b->object);
++ if (b->module_path != NULL)
++ ASSERT_STRING_EQ(a->module_path, b->module_path);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->module_path, b->module_path);
++ if (b->token != NULL)
++ ASSERT_STRING_EQ(a->token, b->token);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->token, b->token);
++ if (b->manuf != NULL)
++ ASSERT_STRING_EQ(a->manuf, b->manuf);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->manuf, b->manuf);
++ if (b->lib_manuf != NULL)
++ ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf);
++ if (b->serial != NULL)
++ ASSERT_STRING_EQ(a->serial, b->serial);
++ else /* both should be null */
++ ASSERT_PTR_EQ(a->serial, b->serial);
++}
++
++void
++check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv)
++{
++ char *buf = NULL, *str;
++ struct pkcs11_uri *pkcs11uri = NULL;
++ int rv;
++
++ if (expect_rv == 0)
++ str = "Valid";
++ else
++ str = "Invalid";
++ asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri);
++ TEST_START(buf);
++ free(buf);
++ pkcs11uri = pkcs11_uri_init();
++ rv = pkcs11_uri_parse(uri, pkcs11uri);
++ ASSERT_INT_EQ(rv, expect_rv);
++ if (rv == 0) /* in case of failure result is undefined */
++ compare_uri(pkcs11uri, expect);
++ pkcs11_uri_cleanup(pkcs11uri);
++ free(expect);
++ TEST_DONE();
++}
++
++void
++check_parse(char *uri, struct pkcs11_uri *expect)
++{
++ check_parse_rv(uri, expect, 0);
++}
++
++struct pkcs11_uri *
++compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf,
++ char *manuf, char *serial, char *module_path, char *object, char *pin)
++{
++ struct pkcs11_uri *uri = pkcs11_uri_init();
++ if (id_len > 0) {
++ uri->id_len = id_len;
++ uri->id = id;
++ }
++ uri->module_path = module_path;
++ uri->token = token;
++ uri->lib_manuf = lib_manuf;
++ uri->manuf = manuf;
++ uri->serial = serial;
++ uri->object = object;
++ uri->pin = pin;
++ return uri;
++}
++
++static void
++test_parse_valid(void)
++{
++ /* path arguments */
++ check_parse("pkcs11:id=%01",
++ compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++ check_parse("pkcs11:id=%00%01",
++ compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++ check_parse("pkcs11:token=SSH%20Keys",
++ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++ check_parse("pkcs11:library-manufacturer=OpenSC",
++ compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));
++ check_parse("pkcs11:manufacturer=piv_II",
++ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
++ check_parse("pkcs11:serial=IamSerial",
++ compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
++ check_parse("pkcs11:object=SIGN%20Key",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "SIGN Key", NULL));
++ /* query arguments */
++ check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++ check_parse("pkcs11:?pin-value=123456",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, "123456"));
++
++ /* combinations */
++ /* ID SHOULD be percent encoded */
++ check_parse("pkcs11:token=SSH%20Key;id=0",
++ compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL, NULL, NULL));
++ check_parse(
++ "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
++ compose_uri(NULL, 0, NULL, NULL, "CAC", NULL,
++ "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++ check_parse(
++ "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL,
++ "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key", NULL));
++ check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so&pin-value=123456",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, "123456"));
++
++ /* empty path component matches everything */
++ check_parse("pkcs11:", EMPTY_URI);
++
++ /* empty string is a valid to match against (and different from NULL) */
++ check_parse("pkcs11:token=",
++ compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL, NULL, NULL));
++ /* Percent character needs to be percent-encoded */
++ check_parse("pkcs11:token=%25",
++ compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL, NULL, NULL));
++}
++
++static void
++test_parse_invalid(void)
++{
++ /* Invalid percent encoding */
++ check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1);
++ /* Invalid percent encoding */
++ check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1);
++ /* Space MUST be percent encoded -- XXX not enforced yet */
++ check_parse("pkcs11:token=SSH Keys",
++ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++ /* MUST NOT contain duplicate attributes of the same name */
++ check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1);
++ /* MUST NOT contain duplicate attributes of the same name */
++ check_parse_rv("pkcs11:?pin-value=111111&pin-value=123456", EMPTY_URI, -1);
++ /* Unrecognized attribute in path are ignored with log message */
++ check_parse("pkcs11:key_name=SSH", EMPTY_URI);
++ /* Unrecognized attribute in query SHOULD be ignored */
++ check_parse("pkcs11:?key_name=SSH", EMPTY_URI);
++}
++
++void
++check_gen(char *expect, struct pkcs11_uri *uri)
++{
++ char *buf = NULL, *uri_str;
++
++ asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect);
++ TEST_START(buf);
++ free(buf);
++ uri_str = pkcs11_uri_get(uri);
++ ASSERT_PTR_NE(uri_str, NULL);
++ ASSERT_STRING_EQ(uri_str, expect);
++ free(uri_str);
++ TEST_DONE();
++}
++
++static void
++test_generate_valid(void)
++{
++ /* path arguments */
++ check_gen("pkcs11:id=%01",
++ compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++ check_gen("pkcs11:id=%00%01",
++ compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++ check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */
++ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++ /* library-manufacturer is not implmented now */
++ /*check_gen("pkcs11:library-manufacturer=OpenSC",
++ compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));*/
++ check_gen("pkcs11:manufacturer=piv_II",
++ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
++ check_gen("pkcs11:serial=IamSerial",
++ compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
++ check_gen("pkcs11:object=RSA%20Key",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "RSA Key", NULL));
++ /* query arguments */
++ check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
++ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++
++ /* combinations */
++ check_gen("pkcs11:id=%02;token=SSH%20Keys",
++ compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++ check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
++ compose_uri("\xEE\x02", 2, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++ check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II",
++ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, "Encryption Key", NULL));
++
++ /* empty path component matches everything */
++ check_gen("pkcs11:", EMPTY_URI);
++
++}
++
++void
++check_encode(char *source, size_t len, char *allow_list, char *expect)
++{
++ char *buf = NULL;
++ struct sshbuf *b;
++
++ asprintf(&buf, "percent_encode: expected %s", expect);
++ TEST_START(buf);
++ free(buf);
++
++ b = percent_encode(source, len, allow_list);
++ ASSERT_STRING_EQ(sshbuf_ptr(b), expect);
++ sshbuf_free(b);
++ TEST_DONE();
++}
++
++static void
++test_percent_encode_multibyte(void)
++{
++ /* SHOULD be encoded as octets according to the UTF-8 character encoding */
++
++ /* multi-byte characters are "for free" */
++ check_encode("$", 1, "", "%24");
++ check_encode("¢", 2, "", "%C2%A2");
++ check_encode("€", 3, "", "%E2%82%AC");
++ check_encode("𐍈", 4, "", "%F0%90%8D%88");
++
++ /* CK_UTF8CHAR is unsigned char (1 byte) */
++ /* labels SHOULD be normalized to NFC [UAX15] */
++
++}
++
++static void
++test_percent_encode(void)
++{
++ /* Without allow list encodes everything (for CKA_ID) */
++ check_encode("A*", 2, "", "%41%2A");
++ check_encode("\x00", 1, "", "%00");
++ check_encode("\x7F", 1, "", "%7F");
++ check_encode("\x80", 1, "", "%80");
++ check_encode("\xff", 1, "", "%FF");
++
++ /* Default allow list encodes anything but safe letters */
++ check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST,
++ "test%000alpha");
++ check_encode(" ", 1, PKCS11_URI_WHITELIST,
++ "%20"); /* Space MUST be percent encoded */
++ check_encode("/", 1, PKCS11_URI_WHITELIST,
++ "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */
++ check_encode("?", 1, PKCS11_URI_WHITELIST,
++ "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */
++ check_encode("#", 1, PKCS11_URI_WHITELIST,
++ "%23"); /* '#' MUST be always percent encoded */
++ check_encode("key=value;separator?query&#anch", 35, PKCS11_URI_WHITELIST,
++ "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch");
++
++ /* Components in query can have '/' unencoded (useful for paths) */
++ check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/",
++ "/path/to.file");
++}
++
++void
++check_decode(char *source, char *expect, int expect_len)
++{
++ char *buf = NULL, *out = NULL;
++ int rv;
++
++ asprintf(&buf, "percent_decode: %s", source);
++ TEST_START(buf);
++ free(buf);
++
++ rv = percent_decode(source, &out);
++ ASSERT_INT_EQ(rv, expect_len);
++ if (rv >= 0)
++ ASSERT_MEM_EQ(out, expect, expect_len);
++ free(out);
++ TEST_DONE();
++}
++
++static void
++test_percent_decode(void)
++{
++ /* simple valid cases */
++ check_decode("%00", "\x00", 1);
++ check_decode("%FF", "\xFF", 1);
++
++ /* normal strings shold be kept intact */
++ check_decode("strings are left", "strings are left", 16);
++ check_decode("10%25 of trees", "10% of trees", 12);
++
++ /* make sure no more than 2 bytes are parsed */
++ check_decode("%222", "\x22" "2", 2);
++
++ /* invalid expects failure */
++ check_decode("%0", "", -1);
++ check_decode("%Z", "", -1);
++ check_decode("%FG", "", -1);
++}
++
++void
++tests(void)
++{
++ test_percent_encode();
++ test_percent_encode_multibyte();
++ test_percent_decode();
++ test_parse_valid();
++ test_parse_invalid();
++ test_generate_valid();
++}
++
++void
++benchmarks(void)
++{
++ printf("no benchmarks\n");
++}
++
+diff --git a/ssh-add.c b/ssh-add.c
+index 9231c5277..1b34baffa 100644
+--- a/ssh-add.c
++++ b/ssh-add.c
+@@ -70,6 +70,7 @@
+ #include "ssh-sk.h"
+ #include "sk-api.h"
+ #include "hostfile.h"
++#include "ssh-pkcs11-uri.h"
+
+ #define CERT_EXPIRY_GRACE (5*60)
+
+@@ -271,6 +272,38 @@ check_cert_lifetime(const struct sshkey *cert, int cert_lifetime)
+ return MINIMUM(cert_lifetime, (int)n);
+ }
+
++#ifdef ENABLE_PKCS11
++static int
++update_card(int agent_fd, int add, const char *id, int qflag,
++ int key_only, int cert_only,
++ struct dest_constraint **dest_constraints, size_t ndest_constraints,
++ struct sshkey **certs, size_t ncerts, char *pin);
++
++int
++update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag,
++ struct dest_constraint **dest_constraints, size_t ndest_constraints)
++{
++ char *pin = NULL;
++ struct pkcs11_uri *uri;
++
++ /* dry-run parse to make sure the URI is valid and to report errors */
++ uri = pkcs11_uri_init();
++ if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
++ fatal("Failed to parse PKCS#11 URI");
++ if (uri->pin != NULL) {
++ pin = strdup(uri->pin);
++ if (pin == NULL) {
++ fatal("Failed to dupplicate string");
++ }
++ /* pin is freed in the update_card() */
++ }
++ pkcs11_uri_cleanup(uri);
++
++ return update_card(agent_fd, adding, pkcs11_uri, qflag, 1, 0,
++ dest_constraints, ndest_constraints, NULL, 0, pin);
++}
++#endif
++
+ static int
+ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
+ int qflag, int Nflag, const char *skprovider,
+@@ -461,15 +494,14 @@ static int
+ update_card(int agent_fd, int add, const char *id, int qflag,
+ int key_only, int cert_only,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints,
+- struct sshkey **certs, size_t ncerts)
++ struct sshkey **certs, size_t ncerts, char *pin)
+ {
+- char *pin = NULL;
+ int r, ret = -1;
+
+ if (key_only)
+ ncerts = 0;
+
+- if (add) {
++ if (add && pin == NULL) {
+ if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
+ RP_ALLOW_STDIN)) == NULL)
+ return -1;
+@@ -651,6 +683,14 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
+ char *file, int qflag, int Nflag, const char *skprovider,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
+ {
++#ifdef ENABLE_PKCS11
++ if (strlen(file) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(file, PKCS11_URI_SCHEME,
++ strlen(PKCS11_URI_SCHEME)) == 0) {
++ return update_pkcs11_uri(agent_fd, !deleting, file, qflag,
++ dest_constraints, ndest_constraints);
++ }
++#endif
+ if (deleting) {
+ if (delete_file(agent_fd, file, key_only,
+ cert_only, qflag) == -1)
+@@ -1003,7 +1043,7 @@ main(int argc, char **argv)
+ if (update_card(agent_fd, !deleting, pkcs11provider,
+ qflag, key_only, cert_only,
+ dest_constraints, ndest_constraints,
+- certs, ncerts) == -1)
++ certs, ncerts, NULL) == -1)
+ ret = 1;
+ for (n = 0; n < ncerts; n++)
+ sshkey_free(certs[n]);
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 4c8e1a4da..28f5c95f9 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -1553,10 +1553,75 @@ add_p11_identity(struct sshkey *key, char *comment, const char *provider,
+ idtab->nentries++;
+ }
+
++static char *
++sanitize_pkcs11_provider(const char *provider)
++{
++ struct pkcs11_uri *uri = NULL;
++ char *sane_uri, *module_path = NULL; /* default path */
++ char canonical_provider[PATH_MAX];
++
++ if (provider == NULL)
++ return NULL;
++
++ memset(canonical_provider, 0, sizeof(canonical_provider));
++
++ if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(provider, PKCS11_URI_SCHEME,
++ strlen(PKCS11_URI_SCHEME)) == 0) {
++ /* PKCS#11 URI */
++ uri = pkcs11_uri_init();
++ if (uri == NULL) {
++ error("Failed to init PKCS#11 URI");
++ return NULL;
++ }
++
++ if (pkcs11_uri_parse(provider, uri) != 0) {
++ error("Failed to parse PKCS#11 URI");
++ pkcs11_uri_cleanup(uri);
++ return NULL;
++ }
++ /* validate also provider from URI */
++ if (uri->module_path)
++ module_path = strdup(uri->module_path);
++ } else
++ module_path = strdup(provider); /* simple path */
++
++ if (module_path != NULL) { /* do not validate default NULL path in URI */
++ if (realpath(module_path, canonical_provider) == NULL) {
++ verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
++ module_path, strerror(errno));
++ free(module_path);
++ pkcs11_uri_cleanup(uri);
++ return NULL;
++ }
++ free(module_path);
++ if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
++ verbose("refusing PKCS#11 provider \"%.100s\": "
++ "not allowed", canonical_provider);
++ pkcs11_uri_cleanup(uri);
++ return NULL;
++ }
++
++ /* copy verified and sanitized provider path back to the uri */
++ if (uri) {
++ free(uri->module_path);
++ uri->module_path = xstrdup(canonical_provider);
++ }
++ }
++
++ if (uri) {
++ sane_uri = pkcs11_uri_get(uri);
++ pkcs11_uri_cleanup(uri);
++ return sane_uri;
++ } else {
++ return xstrdup(canonical_provider); /* simple path */
++ }
++}
++
+ static void
+ process_add_smartcard_key(SocketEntry *e)
+ {
+- char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
++ char *provider = NULL, *pin = NULL, *sane_uri = NULL;
+ char **comments = NULL;
+ int r, i, count = 0, success = 0, confirm = 0;
+ u_int seconds = 0;
+@@ -1585,25 +1650,18 @@ process_add_smartcard_key(SocketEntry *e)
+ "providers is disabled", provider);
+ goto send;
+ }
+- if (realpath(provider, canonical_provider) == NULL) {
+- verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+- provider, strerror(errno));
+- goto send;
+- }
+- if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
+- verbose("refusing PKCS#11 add of \"%.100s\": "
+- "provider not allowed", canonical_provider);
++ sane_uri = sanitize_pkcs11_provider(provider);
++ if (sane_uri == NULL)
+ goto send;
+- }
+- debug_f("add %.100s", canonical_provider);
+ if (lifetime && !death)
+ death = monotime() + lifetime;
+
+- count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
++ debug_f("add %.100s", sane_uri);
++ count = pkcs11_add_provider(sane_uri, pin, &keys, &comments);
+ for (i = 0; i < count; i++) {
+ if (comments[i] == NULL || comments[i][0] == '\0') {
+ free(comments[i]);
+- comments[i] = xstrdup(canonical_provider);
++ comments[i] = xstrdup(sane_uri);
+ }
+ for (j = 0; j < ncerts; j++) {
+ if (!sshkey_is_cert(certs[j]))
+@@ -1613,13 +1671,13 @@ process_add_smartcard_key(SocketEntry *e)
+ if (pkcs11_make_cert(keys[i], certs[j], &k) != 0)
+ continue;
+ add_p11_identity(k, xstrdup(comments[i]),
+- canonical_provider, death, confirm,
++ sane_uri, death, confirm,
+ dest_constraints, ndest_constraints);
+ success = 1;
+ }
+ if (!cert_only && lookup_identity(keys[i]) == NULL) {
+ add_p11_identity(keys[i], comments[i],
+- canonical_provider, death, confirm,
++ sane_uri, death, confirm,
+ dest_constraints, ndest_constraints);
+ keys[i] = NULL; /* transferred */
+ comments[i] = NULL; /* transferred */
+@@ -1632,6 +1690,7 @@ process_add_smartcard_key(SocketEntry *e)
+ send:
+ free(pin);
+ free(provider);
++ free(sane_uri);
+ free(keys);
+ free(comments);
+ free_dest_constraints(dest_constraints, ndest_constraints);
+@@ -1644,8 +1703,8 @@ send:
+ static void
+ process_remove_smartcard_key(SocketEntry *e)
+ {
+- char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
+- int r, success = 0;
++ char *provider = NULL, *pin = NULL, *sane_uri = NULL;
++ int r, success = 0, removed_count = 0;
+ Identity *id, *nxt;
+
+ debug2_f("entering");
+@@ -1656,30 +1715,38 @@ process_remove_smartcard_key(SocketEntry *e)
+ }
+ free(pin);
+
+- if (realpath(provider, canonical_provider) == NULL) {
+- verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+- provider, strerror(errno));
++ sane_uri = sanitize_pkcs11_provider(provider);
++ if (sane_uri == NULL)
+ goto send;
+- }
+
+- debug_f("remove %.100s", canonical_provider);
++ debug_f("remove %.100s", sane_uri);
+ for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) {
+ nxt = TAILQ_NEXT(id, next);
+ /* Skip file--based keys */
+ if (id->provider == NULL)
+ continue;
+- if (!strcmp(canonical_provider, id->provider)) {
++ if (!strcmp(sane_uri, id->provider)) {
+ TAILQ_REMOVE(&idtab->idlist, id, next);
+ free_identity(id);
+ idtab->nentries--;
++ removed_count++;
+ }
+ }
+- if (pkcs11_del_provider(canonical_provider) == 0)
++ /* Only succeed if we actually removed keys and unloaded provider */
++ if (removed_count > 0 && pkcs11_del_provider(sane_uri) == 0) {
+ success = 1;
+- else
++ debug_f("removed %d key(s) from provider %s",
++ removed_count, sane_uri);
++ } else if (removed_count == 0) {
++ error_f("no matching keys found for provider %s", sane_uri);
++ /* Still try to clean up provider if it exists */
++ pkcs11_del_provider(sane_uri);
++ } else {
+ error_f("pkcs11_del_provider failed");
++ }
+ send:
+ free(provider);
++ free(sane_uri);
+ send_status(e, success);
+ }
+ #endif /* ENABLE_PKCS11 */
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index ed1932ff0..342b0c57d 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -820,8 +820,11 @@ do_download(struct passwd *pw)
+ free(fp);
+ } else {
+ (void) sshkey_write(keys[i], stdout); /* XXX check */
+- fprintf(stdout, "%s%s\n",
+- *(comments[i]) == '\0' ? "" : " ", comments[i]);
++ if (*(comments[i]) != '\0') {
++ fprintf(stdout, " %s", comments[i]);
++ }
++ (void) pkcs11_uri_write(keys[i], stdout);
++ fprintf(stdout, "\n");
+ }
+ free(comments[i]);
+ sshkey_free(keys[i]);
+diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
+index 30a4cf5dc..3fd058a0c 100644
+--- a/ssh-pkcs11-client.c
++++ b/ssh-pkcs11-client.c
+@@ -18,6 +18,7 @@
+
+ #include "includes.h"
+
++#ifdef ENABLE_PKCS11
+ #include <sys/types.h>
+ #include <sys/time.h>
+ #include <sys/socket.h>
+@@ -38,6 +39,7 @@
+ #include "authfd.h"
+ #include "atomicio.h"
+ #include "ssh-pkcs11.h"
++#include "ssh-pkcs11-uri.h"
+ #include "ssherr.h"
+
+ /* borrows code from sftp-server and ssh-agent */
+@@ -379,6 +381,19 @@ pkcs11_start_helper(const char *path)
+ return helper;
+ }
+
++int
++pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin, struct sshkey ***keyp, char ***labelsp)
++{
++ int nkeys = 0;
++ char *provider_uri = pkcs11_uri_get(uri);
++
++ debug_f("called, provider_uri = %s", provider_uri);
++
++ nkeys = pkcs11_add_provider(provider_uri, pin, keyp, labelsp);
++
++ return nkeys;
++}
++
+ int
+ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ char ***labelsp)
+@@ -390,6 +405,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ struct sshbuf *msg;
+ struct helper *helper;
+
++ debug_f("called, name = %s", name);
++
+ if ((helper = helper_by_provider(name)) == NULL &&
+ (helper = pkcs11_start_helper(name)) == NULL)
+ return -1;
+@@ -414,6 +431,7 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ *keysp = xcalloc(nkeys, sizeof(struct sshkey *));
+ if (labelsp)
+ *labelsp = xcalloc(nkeys, sizeof(char *));
++ debug_f("nkeys = %u", nkeys);
+ for (i = 0; i < nkeys; i++) {
+ /* XXX clean up properly instead of fatal() */
+ if ((r = sshkey_froms(msg, &k)) != 0 ||
+@@ -497,3 +515,4 @@ pkcs11_key_free(struct sshkey *key)
+ if (helper->nkeyblobs == 0)
+ helper_terminate(helper);
+ }
++#endif
+diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
+new file mode 100644
+index 000000000..d5b074592
+--- /dev/null
++++ b/ssh-pkcs11-uri.c
+@@ -0,0 +1,438 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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 ENABLE_PKCS11
++
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++
++#include "sshkey.h"
++#include "sshbuf.h"
++#include "log.h"
++
++#define CRYPTOKI_COMPAT
++#include "pkcs11.h"
++
++#include "ssh-pkcs11-uri.h"
++
++#define PKCS11_URI_PATH_SEPARATOR ";"
++#define PKCS11_URI_QUERY_SEPARATOR "&"
++#define PKCS11_URI_VALUE_SEPARATOR "="
++#define PKCS11_URI_ID "id"
++#define PKCS11_URI_TOKEN "token"
++#define PKCS11_URI_OBJECT "object"
++#define PKCS11_URI_LIB_MANUF "library-manufacturer"
++#define PKCS11_URI_MANUF "manufacturer"
++#define PKCS11_URI_SERIAL "serial"
++#define PKCS11_URI_MODULE_PATH "module-path"
++#define PKCS11_URI_PIN_VALUE "pin-value"
++
++/* Keyword tokens. */
++typedef enum {
++ pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pSerial,
++ pModulePath, pPinValue, pBadOption
++} pkcs11uriOpCodes;
++
++/* Textual representation of the tokens. */
++static struct {
++ const char *name;
++ pkcs11uriOpCodes opcode;
++} keywords[] = {
++ { PKCS11_URI_ID, pId },
++ { PKCS11_URI_TOKEN, pToken },
++ { PKCS11_URI_OBJECT, pObject },
++ { PKCS11_URI_LIB_MANUF, pLibraryManufacturer },
++ { PKCS11_URI_MANUF, pManufacturer },
++ { PKCS11_URI_SERIAL, pSerial },
++ { PKCS11_URI_MODULE_PATH, pModulePath },
++ { PKCS11_URI_PIN_VALUE, pPinValue },
++ { NULL, pBadOption }
++};
++
++static pkcs11uriOpCodes
++parse_token(const char *cp)
++{
++ u_int i;
++
++ for (i = 0; keywords[i].name; i++)
++ if (strncasecmp(cp, keywords[i].name,
++ strlen(keywords[i].name)) == 0)
++ return keywords[i].opcode;
++
++ return pBadOption;
++}
++
++int
++percent_decode(char *data, char **outp)
++{
++ char tmp[3];
++ char *out, *tmp_end;
++ char *p = data;
++ long value;
++ size_t outlen = 0;
++
++ out = malloc(strlen(data)+1); /* upper bound */
++ if (out == NULL)
++ return -1;
++ while (*p != '\0') {
++ switch (*p) {
++ case '%':
++ p++;
++ if (*p == '\0')
++ goto fail;
++ tmp[0] = *p++;
++ if (*p == '\0')
++ goto fail;
++ tmp[1] = *p++;
++ tmp[2] = '\0';
++ tmp_end = NULL;
++ value = strtol(tmp, &tmp_end, 16);
++ if (tmp_end != tmp+2)
++ goto fail;
++ else
++ out[outlen++] = (char) value;
++ break;
++ default:
++ out[outlen++] = *p++;
++ break;
++ }
++ }
++
++ /* zero terminate */
++ out[outlen] = '\0';
++ *outp = out;
++ return outlen;
++fail:
++ free(out);
++ return -1;
++}
++
++struct sshbuf *
++percent_encode(const char *data, size_t length, const char *allow_list)
++{
++ struct sshbuf *b = NULL;
++ char tmp[4], *cp;
++ size_t i;
++
++ if ((b = sshbuf_new()) == NULL)
++ return NULL;
++ for (i = 0; i < length; i++) {
++ cp = strchr(allow_list, data[i]);
++ /* if c is specified as '\0' pointer to terminator is returned !! */
++ if (cp != NULL && *cp != '\0') {
++ if (sshbuf_put(b, &data[i], 1) != 0)
++ goto err;
++ } else
++ if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3
++ || sshbuf_put(b, tmp, 3) != 0)
++ goto err;
++ }
++ if (sshbuf_put(b, "\0", 1) == 0)
++ return b;
++err:
++ sshbuf_free(b);
++ return NULL;
++}
++
++char *
++pkcs11_uri_append(char *part, const char *separator, const char *key,
++ struct sshbuf *value)
++{
++ char *new_part;
++ size_t size = 0;
++
++ if (value == NULL)
++ return NULL;
++
++ size = asprintf(&new_part,
++ "%s%s%s" PKCS11_URI_VALUE_SEPARATOR "%s",
++ (part != NULL ? part : ""),
++ (part != NULL ? separator : ""),
++ key, sshbuf_ptr(value));
++ sshbuf_free(value);
++ free(part);
++
++ if (size <= 0)
++ return NULL;
++ return new_part;
++}
++
++char *
++pkcs11_uri_get(struct pkcs11_uri *uri)
++{
++ size_t size = 0;
++ char *p = NULL, *path = NULL, *query = NULL;
++
++ /* compose a percent-encoded ID */
++ if (uri->id_len > 0) {
++ struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, "");
++ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++ PKCS11_URI_ID, key_id);
++ if (path == NULL)
++ goto err;
++ }
++
++ /* Write object label */
++ if (uri->object) {
++ struct sshbuf *label = percent_encode(uri->object, strlen(uri->object),
++ PKCS11_URI_WHITELIST);
++ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++ PKCS11_URI_OBJECT, label);
++ if (path == NULL)
++ goto err;
++ }
++
++ /* Write token label */
++ if (uri->token) {
++ struct sshbuf *label = percent_encode(uri->token, strlen(uri->token),
++ PKCS11_URI_WHITELIST);
++ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++ PKCS11_URI_TOKEN, label);
++ if (path == NULL)
++ goto err;
++ }
++
++ /* Write manufacturer */
++ if (uri->manuf) {
++ struct sshbuf *manuf = percent_encode(uri->manuf,
++ strlen(uri->manuf), PKCS11_URI_WHITELIST);
++ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++ PKCS11_URI_MANUF, manuf);
++ if (path == NULL)
++ goto err;
++ }
++
++ /* Write serial */
++ if (uri->serial) {
++ struct sshbuf *serial = percent_encode(uri->serial,
++ strlen(uri->serial), PKCS11_URI_WHITELIST);
++ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++ PKCS11_URI_SERIAL, serial);
++ if (path == NULL)
++ goto err;
++ }
++
++ /* Write module_path */
++ if (uri->module_path) {
++ struct sshbuf *module = percent_encode(uri->module_path,
++ strlen(uri->module_path), PKCS11_URI_WHITELIST "/");
++ query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR,
++ PKCS11_URI_MODULE_PATH, module);
++ if (query == NULL)
++ goto err;
++ }
++
++ size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s",
++ path != NULL ? path : "",
++ query != NULL ? "?" : "",
++ query != NULL ? query : "");
++err:
++ free(query);
++ free(path);
++ if (size <= 0)
++ return NULL;
++ return p;
++}
++
++struct pkcs11_uri *
++pkcs11_uri_init()
++{
++ struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri));
++ return d;
++}
++
++void
++pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11)
++{
++ if (pkcs11 == NULL) {
++ return;
++ }
++
++ free(pkcs11->id);
++ free(pkcs11->module_path);
++ free(pkcs11->token);
++ free(pkcs11->object);
++ free(pkcs11->lib_manuf);
++ free(pkcs11->manuf);
++ free(pkcs11->serial);
++ if (pkcs11->pin)
++ freezero(pkcs11->pin, strlen(pkcs11->pin));
++ free(pkcs11);
++}
++
++int
++pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11)
++{
++ char *saveptr1, *saveptr2, *str1, *str2, *tok;
++ int rv = 0, len;
++ char *p = NULL;
++
++ size_t scheme_len = strlen(PKCS11_URI_SCHEME);
++ if (strlen(uri) < scheme_len || /* empty URI matches everything */
++ strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) {
++ error_f("The '%s' does not look like PKCS#11 URI", uri);
++ return -1;
++ }
++
++ if (pkcs11 == NULL) {
++ error_f("Bad arguments. The pkcs11 can't be null");
++ return -1;
++ }
++
++ /* skip URI schema name */
++ p = strdup(uri);
++ str1 = p;
++
++ /* everything before ? */
++ tok = strtok_r(str1, "?", &saveptr1);
++ if (tok == NULL) {
++ error_f("pk11-path expected, got EOF");
++ rv = -1;
++ goto out;
++ }
++
++ /* skip URI schema name:
++ * the scheme ensures that there is at least something before "?"
++ * allowing empty pk11-path. Resulting token at worst pointing to
++ * \0 byte */
++ tok = tok + scheme_len;
++
++ /* parse pk11-path */
++ for (str2 = tok; ; str2 = NULL) {
++ char **charptr, *arg = NULL;
++ pkcs11uriOpCodes opcode;
++ tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2);
++ if (tok == NULL)
++ break;
++ opcode = parse_token(tok);
++ if (opcode != pBadOption)
++ arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
++
++ switch (opcode) {
++ case pId:
++ /* CKA_ID */
++ if (pkcs11->id != NULL) {
++ verbose_f("The id already set in the PKCS#11 URI");
++ rv = -1;
++ goto out;
++ }
++ len = percent_decode(arg, &pkcs11->id);
++ if (len <= 0) {
++ verbose_f("Failed to percent-decode CKA_ID: %s", arg);
++ rv = -1;
++ goto out;
++ } else
++ pkcs11->id_len = len;
++ debug3_f("Setting CKA_ID = %s from PKCS#11 URI", arg);
++ break;
++ case pToken:
++ /* CK_TOKEN_INFO -> label */
++ charptr = &pkcs11->token;
++ parse_string:
++ if (*charptr != NULL) {
++ verbose_f("The %s already set in the PKCS#11 URI",
++ keywords[opcode].name);
++ rv = -1;
++ goto out;
++ }
++ percent_decode(arg, charptr);
++ debug3_f("Setting %s = %s from PKCS#11 URI",
++ keywords[opcode].name, *charptr);
++ break;
++
++ case pObject:
++ /* CK_TOKEN_INFO -> manufacturerID */
++ charptr = &pkcs11->object;
++ goto parse_string;
++
++ case pManufacturer:
++ /* CK_TOKEN_INFO -> manufacturerID */
++ charptr = &pkcs11->manuf;
++ goto parse_string;
++
++ case pSerial:
++ /* CK_TOKEN_INFO -> serialNumber */
++ charptr = &pkcs11->serial;
++ goto parse_string;
++
++ case pLibraryManufacturer:
++ /* CK_INFO -> manufacturerID */
++ charptr = &pkcs11->lib_manuf;
++ goto parse_string;
++
++ default:
++ /* Unrecognized attribute in the URI path SHOULD be error */
++ verbose_f("Unknown part of path in PKCS#11 URI: %s", tok);
++ }
++ }
++
++ tok = strtok_r(NULL, "?", &saveptr1);
++ if (tok == NULL) {
++ goto out;
++ }
++ /* parse pk11-query (optional) */
++ for (str2 = tok; ; str2 = NULL) {
++ char *arg;
++ pkcs11uriOpCodes opcode;
++ tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2);
++ if (tok == NULL)
++ break;
++ opcode = parse_token(tok);
++ if (opcode != pBadOption)
++ arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
++
++ switch (opcode) {
++ case pModulePath:
++ /* module-path is PKCS11Provider */
++ if (pkcs11->module_path != NULL) {
++ verbose_f("Multiple module-path attributes are"
++ "not supported the PKCS#11 URI");
++ rv = -1;
++ goto out;
++ }
++ percent_decode(arg, &pkcs11->module_path);
++ debug3_f("Setting PKCS11Provider = %s from PKCS#11 URI",
++ pkcs11->module_path);
++ break;
++
++ case pPinValue:
++ /* pin-value */
++ if (pkcs11->pin != NULL) {
++ verbose_f("Multiple pin-value attributes are"
++ "not supported the PKCS#11 URI");
++ rv = -1;
++ goto out;
++ }
++ percent_decode(arg, &pkcs11->pin);
++ debug3_f("Setting PIN from PKCS#11 URI");
++ break;
++
++ default:
++ /* Unrecognized attribute in the URI query SHOULD be ignored */
++ verbose_f("Unknown part of query in PKCS#11 URI: %s", tok);
++ }
++ }
++out:
++ free(p);
++ return rv;
++}
++
++#endif /* ENABLE_PKCS11 */
+diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h
+new file mode 100644
+index 000000000..bc758e760
+--- /dev/null
++++ b/ssh-pkcs11-uri.h
+@@ -0,0 +1,46 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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 _SSH_PKCS11_URI_H
++#define _SSH_PKCS11_URI_H
++
++#define PKCS11_URI_SCHEME "pkcs11:"
++#define PKCS11_URI_WHITELIST "abcdefghijklmnopqrstuvwxyz" \
++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
++ "0123456789_-.()"
++
++struct pkcs11_uri {
++ /* path */
++ char *id;
++ size_t id_len;
++ char *token;
++ char *object;
++ char *lib_manuf;
++ char *manuf;
++ char *serial;
++ /* query */
++ char *module_path;
++ char *pin; /* Only parsed, but not printed */
++};
++
++struct pkcs11_uri *pkcs11_uri_init();
++void pkcs11_uri_cleanup(struct pkcs11_uri *);
++int pkcs11_uri_parse(const char *, struct pkcs11_uri *);
++struct pkcs11_uri *pkcs11_uri_init();
++char *pkcs11_uri_get(struct pkcs11_uri *uri);
++#endif /* _SSH_PKCS11_URI_H */
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index bd45ef090..2d308ed60 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -36,6 +36,7 @@
+ #include <openssl/ecdsa.h>
+ #include <openssl/x509.h>
+ #include <openssl/err.h>
++#include <openssl/evp.h>
+ #endif
+
+ #define CRYPTOKI_COMPAT
+@@ -48,6 +49,7 @@
+ #include "misc.h"
+ #include "sshbuf.h"
+ #include "ssh-pkcs11.h"
++#include "ssh-pkcs11-uri.h"
+ #include "digest.h"
+ #include "xmalloc.h"
+ #include "crypto_api.h"
+@@ -58,8 +60,8 @@ struct pkcs11_slotinfo {
+ int logged_in;
+ };
+
+-struct pkcs11_provider {
+- char *name;
++struct pkcs11_module {
++ char *module_path;
+ void *handle;
+ CK_FUNCTION_LIST *function_list;
+ CK_INFO info;
+@@ -68,6 +70,13 @@ struct pkcs11_provider {
+ struct pkcs11_slotinfo *slotinfo;
+ int valid;
+ int refcount;
++};
++
++struct pkcs11_provider {
++ char *name;
++ struct pkcs11_module *module; /* can be shared between various providers */
++ int refcount;
++ int valid;
+ TAILQ_ENTRY(pkcs11_provider) next;
+ };
+
+@@ -79,6 +88,7 @@ struct pkcs11_key {
+ CK_ULONG slotidx;
+ char *keyid;
+ int keyid_len;
++ char *label;
+ TAILQ_ENTRY(pkcs11_key) next;
+ };
+
+@@ -105,26 +115,61 @@ ossl_error(const char *msg)
+ * this is called when a provider gets unregistered.
+ */
+ static void
+-pkcs11_provider_finalize(struct pkcs11_provider *p)
++pkcs11_module_finalize(struct pkcs11_module *m)
+ {
+ CK_RV rv;
+ CK_ULONG i;
+
+- debug_f("provider \"%s\" refcount %d valid %d",
+- p->name, p->refcount, p->valid);
+- if (!p->valid)
++ debug_f("%p refcount %d valid %d", m, m->refcount, m->valid);
++ if (!m->valid)
+ return;
+- for (i = 0; i < p->nslots; i++) {
+- if (p->slotinfo[i].session &&
+- (rv = p->function_list->C_CloseSession(
+- p->slotinfo[i].session)) != CKR_OK)
++ for (i = 0; i < m->nslots; i++) {
++ if (m->slotinfo[i].session &&
++ (rv = m->function_list->C_CloseSession(
++ m->slotinfo[i].session)) != CKR_OK)
+ error("C_CloseSession failed: %lu", rv);
+ }
+- if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK)
++ if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK)
+ error("C_Finalize failed: %lu", rv);
++ m->valid = 0;
++ m->function_list = NULL;
++ dlclose(m->handle);
++}
++
++/*
++ * remove a reference to the pkcs11 module.
++ * called when a provider is unregistered.
++ */
++static void
++pkcs11_module_unref(struct pkcs11_module *m)
++{
++ debug_f("%p refcount %d", m, m->refcount);
++ if (--m->refcount <= 0) {
++ pkcs11_module_finalize(m);
++ if (m->valid)
++ error_f("%p still valid", m);
++ free(m->slotlist);
++ free(m->slotinfo);
++ free(m->module_path);
++ free(m);
++ }
++}
++
++/*
++ * finalize a provider shared library, it's no longer usable.
++ * however, there might still be keys referencing this provider,
++ * so the actual freeing of memory is handled by pkcs11_provider_unref().
++ * this is called when a provider gets unregistered.
++ */
++static void
++pkcs11_provider_finalize(struct pkcs11_provider *p)
++{
++ debug_f("%p refcount %d valid %d", p, p->refcount, p->valid);
++ if (!p->valid)
++ return;
++ pkcs11_module_unref(p->module);
++ p->module = NULL;
+ p->valid = 0;
+- p->function_list = NULL;
+- dlclose(p->handle);
+ }
+
+ /*
+@@ -136,15 +181,27 @@ pkcs11_provider_unref(struct pkcs11_provider *p)
+ {
+ debug_f("provider \"%s\" refcount %d", p->name, p->refcount);
+ if (--p->refcount <= 0) {
+- if (p->valid)
+- error_f("provider \"%s\" still valid", p->name);
+ free(p->name);
+- free(p->slotlist);
+- free(p->slotinfo);
++ if (p->module)
++ pkcs11_module_unref(p->module);
+ free(p);
+ }
+ }
+
++/* lookup provider by module path */
++static struct pkcs11_module *
++pkcs11_provider_lookup_module(char *module_path)
++{
++ struct pkcs11_provider *p;
++
++ TAILQ_FOREACH(p, &pkcs11_providers, next) {
++ debug("check %p %s (%s)", p, p->name, p->module->module_path);
++ if (!strcmp(module_path, p->module->module_path))
++ return (p->module);
++ }
++ return (NULL);
++}
++
+ /* lookup provider by name */
+ static struct pkcs11_provider *
+ pkcs11_provider_lookup(char *provider_id)
+@@ -159,19 +216,55 @@ pkcs11_provider_lookup(char *provider_id)
+ return (NULL);
+ }
+
++int pkcs11_del_provider_by_uri(struct pkcs11_uri *);
++
+ /* unregister provider by name */
+ int
+ pkcs11_del_provider(char *provider_id)
++{
++ int rv;
++ struct pkcs11_uri *uri;
++
++ debug_f("called, provider_id = %s", provider_id);
++
++ if (provider_id == NULL)
++ return 0;
++
++ uri = pkcs11_uri_init();
++ if (uri == NULL)
++ fatal("Failed to init PKCS#11 URI");
++
++ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++ if (pkcs11_uri_parse(provider_id, uri) != 0)
++ fatal("Failed to parse PKCS#11 URI");
++ } else {
++ uri->module_path = strdup(provider_id);
++ }
++
++ rv = pkcs11_del_provider_by_uri(uri);
++ pkcs11_uri_cleanup(uri);
++ return rv;
++}
++
++/* unregister provider by PKCS#11 URI */
++int
++pkcs11_del_provider_by_uri(struct pkcs11_uri *uri)
+ {
+ struct pkcs11_provider *p;
++ int rv = -1;
++ char *provider_uri = pkcs11_uri_get(uri);
+
+- if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
++ debug3_f("called with provider %s", provider_uri);
++
++ if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) {
+ TAILQ_REMOVE(&pkcs11_providers, p, next);
+ pkcs11_provider_finalize(p);
+ pkcs11_provider_unref(p);
+- return (0);
++ rv = 0;
+ }
+- return (-1);
++ free(provider_uri);
++ return rv;
+ }
+
+ /* release a wrapped object */
+@@ -183,6 +276,7 @@ pkcs11_k11_free(struct pkcs11_key *k11)
+ if (k11->provider)
+ pkcs11_provider_unref(k11->provider);
+ free(k11->keyid);
++ free(k11->label);
+ sshbuf_free(k11->keyblob);
+ free(k11);
+ }
+@@ -198,8 +292,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr,
+ CK_RV rv;
+ int ret = -1;
+
+- f = p->function_list;
+- session = p->slotinfo[slotidx].session;
++ f = p->module->function_list;
++ session = p->module->slotinfo[slotidx].session;
+ if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) {
+ error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv);
+ return (-1);
+@@ -236,14 +330,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
+ if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH)
+ verbose("Deferring PIN entry to reader keypad.");
+ else {
+- snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ",
++ snprintf(prompt, sizeof(prompt), "Enter PIN for '%.32s': ",
+ si->token.label);
+- if ((pin = read_passphrase(prompt, RP_ALLOW_EOF)) == NULL) {
++ if ((pin = read_passphrase(prompt, RP_ALLOW_EOF|RP_ALLOW_STDIN)) == NULL) {
+ debug_f("no pin specified");
+ return (-1); /* bail out */
+ }
+ }
+- rv = provider->function_list->C_Login(si->session, type, (u_char *)pin,
++ rv = provider->module->function_list->C_Login(si->session, type, (u_char *)pin,
+ (pin != NULL) ? strlen(pin) : 0);
+ if (pin != NULL)
+ freezero(pin, strlen(pin));
+@@ -273,13 +367,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
+ static int
+ pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type)
+ {
+- if (k11 == NULL || k11->provider == NULL || !k11->provider->valid) {
++ if (k11 == NULL || k11->provider == NULL || !k11->provider->valid ||
++ k11->provider->module == NULL || !k11->provider->module->valid) {
+ error("no pkcs11 (valid) provider found");
+ return (-1);
+ }
+
+ return pkcs11_login_slot(k11->provider,
+- &k11->provider->slotinfo[k11->slotidx], type);
++ &k11->provider->module->slotinfo[k11->slotidx], type);
+ }
+
+
+@@ -295,13 +390,14 @@ pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj,
+
+ *val = 0;
+
+- if (!k11->provider || !k11->provider->valid) {
++ if (!k11->provider || !k11->provider->valid ||
++ !k11->provider->module || !k11->provider->module->valid) {
+ error("no pkcs11 (valid) provider found");
+ return (-1);
+ }
+
+- f = k11->provider->function_list;
+- si = &k11->provider->slotinfo[k11->slotidx];
++ f = k11->provider->module->function_list;
++ si = &k11->provider->module->slotinfo[k11->slotidx];
+
+ attr.type = type;
+ attr.pValue = &flag;
+@@ -332,13 +428,14 @@ pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type)
+ int always_auth = 0;
+ int did_login = 0;
+
+- if (!k11->provider || !k11->provider->valid) {
++ if (!k11->provider || !k11->provider->valid ||
++ !k11->provider->module || !k11->provider->module->valid) {
+ error("no pkcs11 (valid) provider found");
+ return (-1);
+ }
+
+- f = k11->provider->function_list;
+- si = &k11->provider->slotinfo[k11->slotidx];
++ f = k11->provider->module->function_list;
++ si = &k11->provider->module->slotinfo[k11->slotidx];
+
+ if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) {
+ if (pkcs11_login(k11, CKU_USER) < 0) {
+@@ -437,6 +534,12 @@ pkcs11_record_key(struct pkcs11_provider *provider, CK_ULONG slotidx,
+ k11->keyid = xmalloc(k11->keyid_len);
+ memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
+ }
++ if (keyid_attrib->ulValueLen > 0 ) {
++ k11->label = xmalloc(keyid_attrib->ulValueLen+1);
++ memcpy(k11->label, keyid_attrib->pValue, keyid_attrib->ulValueLen);
++ k11->label[keyid_attrib->ulValueLen] = 0;
++ }
++
+ TAILQ_INSERT_TAIL(&pkcs11_keys, k11, next);
+
+ return 0;
+@@ -464,6 +567,42 @@ pkcs11_lookup_key(struct sshkey *key)
+ return found;
+ }
+
++/*
++ * This can't be in the ssh-pkcs11-uri, becase we can not depend on
++ * PKCS#11 structures in ssh-agent (using client-helper communication)
++ */
++int
++pkcs11_uri_write(const struct sshkey *key, FILE *f)
++{
++ char *p = NULL;
++ struct pkcs11_uri uri;
++ struct pkcs11_key *k11 = pkcs11_lookup_key(key);
++
++ if (k11 == NULL) {
++ error("Failed to get ex_data for key type %d", key->type);
++ return (-1);
++ }
++
++ /* omit type -- we are looking for private-public or private-certificate pairs */
++ uri.id = k11->keyid;
++ uri.id_len = k11->keyid_len;
++ uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label;
++ uri.object = k11->label;
++ uri.module_path = k11->provider->module->module_path;
++ uri.lib_manuf = k11->provider->module->info.manufacturerID;
++ uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID;
++ uri.serial = k11->provider->module->slotinfo[k11->slotidx].token.serialNumber;
++
++ p = pkcs11_uri_get(&uri);
++ /* do not cleanup -- we do not allocate here, only reference */
++ if (p == NULL)
++ return -1;
++
++ fprintf(f, " %s", p);
++ free(p);
++ return 0;
++}
++
+ #ifdef WITH_OPENSSL
+ /*
+ * See:
+@@ -568,8 +707,8 @@ pkcs11_sign_rsa(struct sshkey *key,
+ return SSH_ERR_AGENT_FAILURE;
+ }
+
+- f = k11->provider->function_list;
+- si = &k11->provider->slotinfo[k11->slotidx];
++ f = k11->provider->module->function_list;
++ si = &k11->provider->module->slotinfo[k11->slotidx];
+
+ if ((siglen = EVP_PKEY_size(key->pkey)) <= 0)
+ return SSH_ERR_INVALID_ARGUMENT;
+@@ -657,8 +796,8 @@ pkcs11_sign_ecdsa(struct sshkey *key,
+ debug3_f("sign using provider %s slotidx %lu",
+ k11->provider->name, (u_long)k11->slotidx);
+
+- f = k11->provider->function_list;
+- si = &k11->provider->slotinfo[k11->slotidx];
++ f = k11->provider->module->function_list;
++ si = &k11->provider->module->slotinfo[k11->slotidx];
+
+ /* Prepare digest to be signed */
+ if ((hashalg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
+@@ -741,8 +880,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
+ debug3_f("sign using provider %s slotidx %lu",
+ k11->provider->name, (u_long)k11->slotidx);
+
+- f = k11->provider->function_list;
+- si = &k11->provider->slotinfo[k11->slotidx];
++ f = k11->provider->module->function_list;
++ si = &k11->provider->module->slotinfo[k11->slotidx];
+
+ xdata = xmalloc(datalen);
+ memcpy(xdata, data, datalen);
+@@ -770,7 +909,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
+ return ret;
+ }
+
+-/* remove trailing spaces */
++/* remove trailing spaces. Note, that this does NOT guarantee the buffer
++ * will be null terminated if there are no trailing spaces! */
+ static char *
+ rmspace(u_char *buf, size_t len)
+ {
+@@ -802,8 +942,8 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
+ CK_SESSION_HANDLE session;
+ int login_required, ret;
+
+- f = p->function_list;
+- si = &p->slotinfo[slotidx];
++ f = p->module->function_list;
++ si = &p->module->slotinfo[slotidx];
+
+ login_required = si->token.flags & CKF_LOGIN_REQUIRED;
+
+@@ -813,9 +953,9 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
+ error("pin required");
+ return (-SSH_PKCS11_ERR_PIN_REQUIRED);
+ }
+- if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION|
++ if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION|
+ CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) {
+- error("C_OpenSession failed: %lu", rv);
++ error("C_OpenSession failed for slot %lu: %lu", slotidx, rv);
+ return (-1);
+ }
+ if (login_required && pin != NULL && strlen(pin) != 0) {
+@@ -851,7 +991,8 @@ static struct sshkey *
+ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ CK_OBJECT_HANDLE *obj)
+ {
+- CK_ATTRIBUTE key_attr[3];
++ CK_ATTRIBUTE key_attr[4];
++ int nattr = 4;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -864,14 +1005,15 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+
+ memset(&key_attr, 0, sizeof(key_attr));
+ key_attr[0].type = CKA_ID;
+- key_attr[1].type = CKA_EC_POINT;
+- key_attr[2].type = CKA_EC_PARAMS;
++ key_attr[1].type = CKA_LABEL;
++ key_attr[2].type = CKA_EC_POINT;
++ key_attr[3].type = CKA_EC_PARAMS;
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+ /* figure out size of the attributes */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ return (NULL);
+@@ -882,19 +1024,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ * ensure that none of the others are zero length.
+ * XXX assumes CKA_ID is always first.
+ */
+- if (key_attr[1].ulValueLen == 0 ||
+- key_attr[2].ulValueLen == 0) {
++ if (key_attr[2].ulValueLen == 0 ||
++ key_attr[3].ulValueLen == 0) {
+ error("invalid attribute length");
+ return (NULL);
+ }
+
+ /* allocate buffers for attributes */
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ if (key_attr[i].ulValueLen > 0)
+ key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+
+ /* retrieve ID, public point and curve parameters of EC key */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ goto fail;
+@@ -906,8 +1048,8 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ goto fail;
+ }
+
+- attrp = key_attr[2].pValue;
+- group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen);
++ attrp = key_attr[3].pValue;
++ group = d2i_ECPKParameters(NULL, &attrp, key_attr[3].ulValueLen);
+ if (group == NULL) {
+ ossl_error("d2i_ECPKParameters failed");
+ goto fail;
+@@ -918,13 +1060,13 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ goto fail;
+ }
+
+- if (key_attr[1].ulValueLen <= 2) {
++ if (key_attr[2].ulValueLen <= 2) {
+ error("CKA_EC_POINT too small");
+ goto fail;
+ }
+
+- attrp = key_attr[1].pValue;
+- octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen);
++ attrp = key_attr[2].pValue;
++ octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[2].ulValueLen);
+ if (octet == NULL) {
+ ossl_error("d2i_ASN1_OCTET_STRING failed");
+ goto fail;
+@@ -985,7 +1127,8 @@ static struct sshkey *
+ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ CK_OBJECT_HANDLE *obj)
+ {
+- CK_ATTRIBUTE key_attr[3];
++ CK_ATTRIBUTE key_attr[4];
++ int nattr = 4;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -996,14 +1139,15 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+
+ memset(&key_attr, 0, sizeof(key_attr));
+ key_attr[0].type = CKA_ID;
+- key_attr[1].type = CKA_MODULUS;
+- key_attr[2].type = CKA_PUBLIC_EXPONENT;
++ key_attr[1].type = CKA_LABEL;
++ key_attr[2].type = CKA_MODULUS;
++ key_attr[3].type = CKA_PUBLIC_EXPONENT;
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+ /* figure out size of the attributes */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ return (NULL);
+@@ -1014,19 +1158,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ * ensure that none of the others are zero length.
+ * XXX assumes CKA_ID is always first.
+ */
+- if (key_attr[1].ulValueLen == 0 ||
+- key_attr[2].ulValueLen == 0) {
++ if (key_attr[2].ulValueLen == 0 ||
++ key_attr[3].ulValueLen == 0) {
+ error("invalid attribute length");
+ return (NULL);
+ }
+
+ /* allocate buffers for attributes */
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ if (key_attr[i].ulValueLen > 0)
+ key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+
+ /* retrieve ID, modulus and public exponent of RSA key */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ goto fail;
+@@ -1038,8 +1182,8 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ goto fail;
+ }
+
+- rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL);
+- rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
++ rsa_n = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
++ rsa_e = BN_bin2bn(key_attr[3].pValue, key_attr[3].ulValueLen, NULL);
+ if (rsa_n == NULL || rsa_e == NULL) {
+ error("BN_bin2bn failed");
+ goto fail;
+@@ -1071,7 +1215,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ /* success */
+ success = 0;
+ fail:
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ free(key_attr[i].pValue);
+ RSA_free(rsa);
+ if (success != 0) {
+@@ -1086,7 +1230,8 @@ static struct sshkey *
+ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ CK_OBJECT_HANDLE *obj)
+ {
+- CK_ATTRIBUTE key_attr[3];
++ CK_ATTRIBUTE key_attr[4];
++ int nattr = 4;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -1106,14 +1251,15 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+
+ memset(&key_attr, 0, sizeof(key_attr));
+ key_attr[0].type = CKA_ID;
+- key_attr[1].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
+- key_attr[2].type = CKA_EC_PARAMS;
++ key_attr[1].type = CKA_LABEL;
++ key_attr[2].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
++ key_attr[3].type = CKA_EC_PARAMS;
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+ /* figure out size of the attributes */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ return (NULL);
+@@ -1124,28 +1270,28 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ * ensure that none of the others are zero length.
+ * XXX assumes CKA_ID is always first.
+ */
+- if (key_attr[1].ulValueLen == 0 ||
+- key_attr[2].ulValueLen == 0) {
++ if (key_attr[2].ulValueLen == 0 ||
++ key_attr[3].ulValueLen == 0) {
+ error("invalid attribute length");
+ return (NULL);
+ }
+
+ /* allocate buffers for attributes */
+- for (i = 0; i < 3; i++) {
++ for (i = 0; i < nattr; i++) {
+ if (key_attr[i].ulValueLen > 0)
+ key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+ }
+
+ /* retrieve ID, public point and curve parameters of EC key */
+- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ goto fail;
+ }
+
+ /* Expect one of the supported identifiers in CKA_EC_PARAMS */
+- d = (u_char *)key_attr[2].pValue;
+- len = key_attr[2].ulValueLen;
++ d = (u_char *)key_attr[3].pValue;
++ len = key_attr[3].ulValueLen;
+ if ((len != sizeof(id1) || memcmp(d, id1, sizeof(id1)) != 0) &&
+ (len != sizeof(id2) || memcmp(d, id2, sizeof(id2)) != 0)) {
+ hex = tohex(d, len);
+@@ -1157,16 +1303,16 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ * Expect either a raw 32 byte pubkey or an OCTET STRING with
+ * a 32 byte pubkey in CKA_VALUE
+ */
+- d = (u_char *)key_attr[1].pValue;
+- len = key_attr[1].ulValueLen;
++ d = (u_char *)key_attr[2].pValue;
++ len = key_attr[2].ulValueLen;
+ if (len == ED25519_PK_SZ + 2 && d[0] == 0x04 && d[1] == ED25519_PK_SZ) {
+ d += 2;
+ len -= 2;
+ }
+ if (len != ED25519_PK_SZ) {
+- hex = tohex(key_attr[1].pValue, key_attr[1].ulValueLen);
++ hex = tohex(key_attr[2].pValue, key_attr[2].ulValueLen);
+ logit_f("CKA_EC_POINT invalid octet str: %s (len %lu)",
+- hex, (u_long)key_attr[1].ulValueLen);
++ hex, (u_long)key_attr[2].ulValueLen);
+ goto fail;
+ }
+
+@@ -1186,7 +1332,7 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ key = NULL;
+ }
+ free(hex);
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ free(key_attr[i].pValue);
+ return key;
+ }
+@@ -1196,7 +1342,8 @@ static int
+ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ CK_OBJECT_HANDLE *obj, struct sshkey **keyp, char **labelp)
+ {
+- CK_ATTRIBUTE cert_attr[3];
++ CK_ATTRIBUTE cert_attr[4];
++ int nattr = 4;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -1218,14 +1365,15 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+
+ memset(&cert_attr, 0, sizeof(cert_attr));
+ cert_attr[0].type = CKA_ID;
+- cert_attr[1].type = CKA_SUBJECT;
+- cert_attr[2].type = CKA_VALUE;
++ cert_attr[1].type = CKA_LABEL;
++ cert_attr[2].type = CKA_SUBJECT;
++ cert_attr[3].type = CKA_VALUE;
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+ /* figure out size of the attributes */
+- rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ return -1;
+@@ -1237,18 +1385,19 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ * XXX assumes CKA_ID is always first.
+ */
+ if (cert_attr[1].ulValueLen == 0 ||
+- cert_attr[2].ulValueLen == 0) {
++ cert_attr[2].ulValueLen == 0 ||
++ cert_attr[3].ulValueLen == 0) {
+ error("invalid attribute length");
+ return -1;
+ }
+
+ /* allocate buffers for attributes */
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ if (cert_attr[i].ulValueLen > 0)
+ cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen);
+
+ /* retrieve ID, subject and value of certificate */
+- rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
++ rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_GetAttributeValue failed: %lu", rv);
+ goto out;
+@@ -1262,8 +1411,8 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ subject = xstrdup("invalid subject");
+ X509_NAME_free(x509_name);
+
+- cp = cert_attr[2].pValue;
+- if ((x509 = d2i_X509(NULL, &cp, cert_attr[2].ulValueLen)) == NULL) {
++ cp = cert_attr[3].pValue;
++ if ((x509 = d2i_X509(NULL, &cp, cert_attr[3].ulValueLen)) == NULL) {
+ error("d2i_x509 failed");
+ goto out;
+ }
+@@ -1371,7 +1520,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ goto out;
+ }
+ out:
+- for (i = 0; i < 3; i++)
++ for (i = 0; i < nattr; i++)
+ free(cert_attr[i].pValue);
+ X509_free(x509);
+ RSA_free(rsa);
+@@ -1412,11 +1561,12 @@ note_key(struct pkcs11_provider *p, CK_ULONG slotidx, const char *context,
+ */
+ static int
+ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
+- struct sshkey ***keysp, char ***labelsp, int *nkeys)
++ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
+ {
+ struct sshkey *key = NULL;
+ CK_OBJECT_CLASS key_class;
+- CK_ATTRIBUTE key_attr[1];
++ CK_ATTRIBUTE key_attr[3];
++ int nattr = 1;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -1433,10 +1583,23 @@ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
+ key_attr[0].pValue = &key_class;
+ key_attr[0].ulValueLen = sizeof(key_class);
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ if (uri->id != NULL) {
++ key_attr[nattr].type = CKA_ID;
++ key_attr[nattr].pValue = uri->id;
++ key_attr[nattr].ulValueLen = uri->id_len;
++ nattr++;
++ }
++ if (uri->object != NULL) {
++ key_attr[nattr].type = CKA_LABEL;
++ key_attr[nattr].pValue = uri->object;
++ key_attr[nattr].ulValueLen = strlen(uri->object);
++ nattr++;
++ }
++
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+- rv = f->C_FindObjectsInit(session, key_attr, 1);
++ rv = f->C_FindObjectsInit(session, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_FindObjectsInit failed: %lu", rv);
+ goto fail;
+@@ -1509,6 +1672,13 @@ fail:
+
+ return (ret);
+ }
++#else
++static int
++pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
++ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
++{
++ return 0;
++}
+ #endif /* WITH_OPENSSL */
+
+ /*
+@@ -1518,11 +1688,12 @@ fail:
+ */
+ static int
+ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
+- struct sshkey ***keysp, char ***labelsp, int *nkeys)
++ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
+ {
+ struct sshkey *key = NULL;
+ CK_OBJECT_CLASS key_class;
+- CK_ATTRIBUTE key_attr[2];
++ CK_ATTRIBUTE key_attr[3];
++ int nattr = 1;
+ CK_SESSION_HANDLE session;
+ CK_FUNCTION_LIST *f = NULL;
+ CK_RV rv;
+@@ -1538,10 +1709,23 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
+ key_attr[0].pValue = &key_class;
+ key_attr[0].ulValueLen = sizeof(key_class);
+
+- session = p->slotinfo[slotidx].session;
+- f = p->function_list;
++ if (uri->id != NULL) {
++ key_attr[nattr].type = CKA_ID;
++ key_attr[nattr].pValue = uri->id;
++ key_attr[nattr].ulValueLen = uri->id_len;
++ nattr++;
++ }
++ if (uri->object != NULL) {
++ key_attr[nattr].type = CKA_LABEL;
++ key_attr[nattr].pValue = uri->object;
++ key_attr[nattr].ulValueLen = strlen(uri->object);
++ nattr++;
++ }
++
++ session = p->module->slotinfo[slotidx].session;
++ f = p->module->function_list;
+
+- rv = f->C_FindObjectsInit(session, key_attr, 1);
++ rv = f->C_FindObjectsInit(session, key_attr, nattr);
+ if (rv != CKR_OK) {
+ error("C_FindObjectsInit failed: %lu", rv);
+ goto fail;
+@@ -1827,16 +2011,10 @@ pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx,
+ }
+ #endif /* WITH_PKCS11_KEYGEN */
+
+-/*
+- * register a new provider, fails if provider already exists. if
+- * keyp is provided, fetch keys.
+- */
+ static int
+-pkcs11_register_provider(char *provider_id, char *pin,
+- struct sshkey ***keyp, char ***labelsp,
+- struct pkcs11_provider **providerp, CK_ULONG user)
++pkcs11_initialize_provider(struct pkcs11_uri *uri, struct pkcs11_provider **providerp)
+ {
+- int nkeys, need_finalize = 0;
++ int need_finalize = 0;
+ int ret = -1;
+ struct pkcs11_provider *p = NULL;
+ void *handle = NULL;
+@@ -1845,128 +2023,126 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ CK_FUNCTION_LIST *f = NULL;
+ CK_TOKEN_INFO *token;
+ CK_ULONG i;
++ char *provider_module = NULL;
++ struct pkcs11_module *m = NULL;
++
++ /* if no provider specified, fallback to p11-kit */
++ if (uri->module_path == NULL) {
++#ifdef PKCS11_DEFAULT_PROVIDER
++ provider_module = strdup(PKCS11_DEFAULT_PROVIDER);
++#else
++ error_f("No module path provided");
++ goto fail;
++#endif
++ } else {
++ provider_module = strdup(uri->module_path);
++ }
++ p = xcalloc(1, sizeof(*p));
++ p->name = pkcs11_uri_get(uri);
+
+- if (providerp == NULL)
+- goto fail;
+- *providerp = NULL;
+-
+- if (keyp != NULL)
+- *keyp = NULL;
+- if (labelsp != NULL)
+- *labelsp = NULL;
+-
+- if (pkcs11_provider_lookup(provider_id) != NULL) {
+- debug_f("provider already registered: %s", provider_id);
++ if (lib_contains_symbol(provider_module, "C_GetFunctionList") != 0) {
++ error("provider %s is not a PKCS11 library", provider_module);
+ goto fail;
+ }
+- if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) {
+- error("provider %s is not a PKCS11 library", provider_id);
+- goto fail;
++ if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
++ && m->valid) {
++ debug_f("provider module already initialized: %s", provider_module);
++ free(provider_module);
++ /* Skip the initialization of PKCS#11 module */
++ m->refcount++;
++ p->module = m;
++ p->valid = 1;
++ TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
++ p->refcount++; /* add to provider list */
++ *providerp = p;
++ return 0;
++ } else {
++ m = xcalloc(1, sizeof(*m));
++ p->module = m;
++ m->refcount++;
+ }
++
+ /* open shared pkcs11-library */
+- if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
+- error("dlopen %s failed: %s", provider_id, dlerror());
++ if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) {
++ error("dlopen %s failed: %s", provider_module, dlerror());
+ goto fail;
+ }
+ if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
+ fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
+- p = xcalloc(1, sizeof(*p));
+- p->name = xstrdup(provider_id);
+- p->handle = handle;
+- /* set up the pkcs11 callbacks */
++ p->module->handle = handle;
++ /* setup the pkcs11 callbacks */
+ if ((rv = (*getfunctionlist)(&f)) != CKR_OK) {
+ error("C_GetFunctionList for provider %s failed: %lu",
+- provider_id, rv);
++ provider_module, rv);
+ goto fail;
+ }
+- p->function_list = f;
++ m->function_list = f;
+ if ((rv = f->C_Initialize(NULL)) != CKR_OK) {
+ error("C_Initialize for provider %s failed: %lu",
+- provider_id, rv);
++ provider_module, rv);
+ goto fail;
+ }
+ need_finalize = 1;
+- if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) {
++ if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) {
+ error("C_GetInfo for provider %s failed: %lu",
+- provider_id, rv);
++ provider_module, rv);
+ goto fail;
+ }
+- debug("provider %s: manufacturerID <%.*s> cryptokiVersion %d.%d"
+- " libraryDescription <%.*s> libraryVersion %d.%d",
+- provider_id,
+- RMSPACE(p->info.manufacturerID),
+- p->info.cryptokiVersion.major,
+- p->info.cryptokiVersion.minor,
+- RMSPACE(p->info.libraryDescription),
+- p->info.libraryVersion.major,
+- p->info.libraryVersion.minor);
+- if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
++ rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
++ if (uri->lib_manuf != NULL &&
++ strncmp(uri->lib_manuf, m->info.manufacturerID, 32)) {
++ debug_f("Skipping provider %s not matching library_manufacturer",
++ m->info.manufacturerID);
++ goto fail;
++ }
++ rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
++ debug("provider %s: manufacturerID <%.32s> cryptokiVersion %d.%d"
++ " libraryDescription <%.32s> libraryVersion %d.%d",
++ provider_module,
++ m->info.manufacturerID,
++ m->info.cryptokiVersion.major,
++ m->info.cryptokiVersion.minor,
++ m->info.libraryDescription,
++ m->info.libraryVersion.major,
++ m->info.libraryVersion.minor);
++
++ if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) {
+ error("C_GetSlotList failed: %lu", rv);
+ goto fail;
+ }
+- if (p->nslots == 0) {
+- debug_f("provider %s returned no slots", provider_id);
++ if (m->nslots == 0) {
++ debug_f("provider %s returned no slots", provider_module);
+ ret = -SSH_PKCS11_ERR_NO_SLOTS;
+ goto fail;
+ }
+- p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID));
+- if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots))
++ m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID));
++ if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots))
+ != CKR_OK) {
+ error("C_GetSlotList for provider %s failed: %lu",
+- provider_id, rv);
++ provider_module, rv);
+ goto fail;
+ }
+- p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo));
++ m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
+ p->valid = 1;
+- nkeys = 0;
+- for (i = 0; i < p->nslots; i++) {
+- token = &p->slotinfo[i].token;
+- if ((rv = f->C_GetTokenInfo(p->slotlist[i], token))
++ m->valid = 1;
++ for (i = 0; i < m->nslots; i++) {
++ token = &m->slotinfo[i].token;
++ if ((rv = f->C_GetTokenInfo(m->slotlist[i], token))
+ != CKR_OK) {
+ error("C_GetTokenInfo for provider %s slot %lu "
+- "failed: %lu", provider_id, (u_long)i, rv);
+- continue;
+- }
+- if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
+- debug2_f("ignoring uninitialised token in "
+- "provider %s slot %lu", provider_id, (u_long)i);
++ "failed: %lu", provider_module, (u_long)i, rv);
+ continue;
+ }
+ debug("provider %s slot %lu: label <%.*s> "
+ "manufacturerID <%.*s> model <%.*s> serial <%.*s> "
+ "flags 0x%lx",
+- provider_id, (unsigned long)i,
++ provider_module, (unsigned long)i,
+ RMSPACE(token->label), RMSPACE(token->manufacturerID),
+ RMSPACE(token->model), RMSPACE(token->serialNumber),
+ token->flags);
+- /*
+- * open session, login with pin and retrieve public
+- * keys (if keyp is provided)
+- */
+- if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 ||
+- keyp == NULL)
+- continue;
+- pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
+-#ifdef WITH_OPENSSL
+- pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
+-#endif
+- if (nkeys == 0 && !p->slotinfo[i].logged_in &&
+- pkcs11_interactive) {
+- /*
+- * Some tokens require login before they will
+- * expose keys.
+- */
+- if (pkcs11_login_slot(p, &p->slotinfo[i],
+- CKU_USER) < 0) {
+- error("login failed");
+- continue;
+- }
+- pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
+-#ifdef WITH_OPENSSL
+- pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
+-#endif
+- }
+ }
++ m->module_path = provider_module;
++ provider_module = NULL;
+
+ /* now owned by caller */
+ *providerp = p;
+@@ -1974,21 +2150,22 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
+ p->refcount++; /* add to provider list */
+
+- return (nkeys);
++ return 0;
+ fail:
+ if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
+ error("C_Finalize for provider %s failed: %lu",
+- provider_id, rv);
++ provider_module, rv);
++ free(provider_module);
++ if (m) {
++ free(m->slotlist);
++ free(m);
++ }
+ if (p) {
+ free(p->name);
+- free(p->slotlist);
+- free(p->slotinfo);
+ free(p);
+ }
+ if (handle)
+ dlclose(handle);
+- if (ret > 0)
+- ret = -1;
+ return (ret);
+ }
+
+@@ -2024,18 +2201,163 @@ pkcs11_terminate(void)
+ }
+
+ /*
+- * register a new provider and get number of keys hold by the token,
+- * fails if provider already exists
++ * register a new provider, fails if provider already exists. if
++ * keyp is provided, fetch keys.
+ */
++static int
++pkcs11_register_provider_by_uri(struct pkcs11_uri *uri, char *pin,
++ struct sshkey ***keyp, char ***labelsp, struct pkcs11_provider **providerp,
++ CK_ULONG user)
++{
++ int nkeys;
++ int ret = -1;
++ struct pkcs11_provider *p = NULL;
++ CK_ULONG i;
++ CK_TOKEN_INFO *token;
++ char *provider_uri = NULL;
++
++ if (providerp == NULL)
++ goto fail;
++ *providerp = NULL;
++
++ if (keyp != NULL)
++ *keyp = NULL;
++
++ if ((ret = pkcs11_initialize_provider(uri, &p)) != 0) {
++ goto fail;
++ }
++
++ provider_uri = pkcs11_uri_get(uri);
++ if (pin == NULL && uri->pin != NULL) {
++ pin = uri->pin;
++ }
++ nkeys = 0;
++ for (i = 0; i < p->module->nslots; i++) {
++ token = &p->module->slotinfo[i].token;
++ if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
++ debug2_f("ignoring uninitialised token in "
++ "provider %s slot %lu", provider_uri, (u_long)i);
++ continue;
++ }
++ if (uri->token != NULL &&
++ strncmp(token->label, uri->token, 32) != 0) {
++ debug2_f("ignoring token not matching label (%.32s) "
++ "specified by PKCS#11 URI in slot %lu",
++ token->label, (unsigned long)i);
++ continue;
++ }
++ if (uri->manuf != NULL &&
++ strncmp(token->manufacturerID, uri->manuf, 32) != 0) {
++ debug2_f("ignoring token not matching requrested "
++ "manufacturerID (%.32s) specified by PKCS#11 URI in "
++ "slot %lu", token->manufacturerID, (unsigned long)i);
++ continue;
++ }
++ if (uri->serial != NULL &&
++ strncmp(token->serialNumber, uri->serial, 16) != 0) {
++ debug2_f("ignoring token not matching requrested "
++ "serialNumber (%s) specified by PKCS#11 URI in "
++ "slot %lu", token->serialNumber, (unsigned long)i);
++ continue;
++ }
++ debug("provider %s slot %lu: label <%.32s> manufacturerID <%.32s> "
++ "model <%.16s> serial <%.16s> flags 0x%lx",
++ provider_uri, (unsigned long)i,
++ token->label, token->manufacturerID, token->model,
++ token->serialNumber, token->flags);
++ /*
++ * open session if not yet opened, login with pin and
++ * retrieve public keys (if keyp is provided)
++ */
++ if ((p->module->slotinfo[i].session != 0 ||
++ (ret = pkcs11_open_session(p, i, pin, user)) != 0) && /* ??? */
++ keyp == NULL)
++ continue;
++ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++ if (nkeys == 0 && !p->module->slotinfo[i].logged_in &&
++ pkcs11_interactive) {
++ /*
++ * Some tokens require login before they will
++ * expose keys.
++ */
++ debug3_f("Trying to login as there were no keys found");
++ if (pkcs11_login_slot(p, &p->module->slotinfo[i],
++ CKU_USER) < 0) {
++ error("login failed");
++ continue;
++ }
++ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++ }
++ if (nkeys == 0 && uri->object != NULL) {
++ debug3_f("No keys found. Retrying without label (%.32s) ",
++ uri->object);
++ /* Try once more without the label filter */
++ char *label = uri->object;
++ uri->object = NULL; /* XXX clone uri? */
++ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++ uri->object = label;
++ }
++ }
++ pin = NULL; /* Will be cleaned up with URI */
++
++ /* now owned by caller */
++ *providerp = p;
++
++ free(provider_uri);
++ return (nkeys);
++ fail:
++ if (p) {
++ TAILQ_REMOVE(&pkcs11_providers, p, next);
++ pkcs11_provider_unref(p);
++ }
++ if (ret > 0)
++ ret = -1;
++ return (ret);
++}
++
++#ifdef WITH_PKCS11_KEYGEN
++static int
++pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
++ char ***labelsp, struct pkcs11_provider **providerp, CK_ULONG user)
++{
++ struct pkcs11_uri *uri = NULL;
++ int r;
++
++ debug_f("called, provider_id = %s", provider_id);
++
++ uri = pkcs11_uri_init();
++ if (uri == NULL)
++ fatal("failed to init PKCS#11 URI");
++
++ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++ if (pkcs11_uri_parse(provider_id, uri) != 0)
++ fatal("Failed to parse PKCS#11 URI");
++ } else {
++ uri->module_path = strdup(provider_id);
++ }
++
++ r = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, providerp, user);
++ pkcs11_uri_cleanup(uri);
++
++ return r;
++}
++#endif
++
+ int
+-pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
+- char ***labelsp)
++pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin,
++ struct sshkey ***keyp, char ***labelsp)
+ {
+ struct pkcs11_provider *p = NULL;
+ int nkeys;
++ char *provider_uri = pkcs11_uri_get(uri);
+
+- nkeys = pkcs11_register_provider(provider_id, pin, keyp, labelsp,
+- &p, CKU_USER);
++ debug_f("called, provider_uri = %s", provider_uri);
++
++ nkeys = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, &p, CKU_USER);
+
+ /* no keys found or some other error, de-register provider */
+ if (nkeys <= 0 && p != NULL) {
+@@ -2044,11 +2366,38 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
+ pkcs11_provider_unref(p);
+ }
+ if (nkeys == 0)
+- debug_f("provider %s returned no keys", provider_id);
++ debug_f("provider %s returned no keys", provider_uri);
+
++ free(provider_uri);
+ return (nkeys);
+ }
+
++int
++pkcs11_add_provider(char *provider_id, char *pin,
++ struct sshkey ***keyp, char ***labelsp)
++{
++ struct pkcs11_uri *uri;
++ int nkeys;
++
++ uri = pkcs11_uri_init();
++ if (uri == NULL)
++ fatal("Failed to init PKCS#11 URI");
++
++ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++ if (pkcs11_uri_parse(provider_id, uri) != 0)
++ fatal("Failed to parse PKCS#11 URI");
++ } else {
++ uri->module_path = strdup(provider_id);
++ }
++
++ nkeys = pkcs11_add_provider_by_uri(uri, pin, keyp, labelsp);
++ pkcs11_uri_cleanup(uri);
++
++ return (nkeys);
++}
++
++
+ int
+ pkcs11_sign(struct sshkey *key,
+ u_char **sigp, size_t *lenp,
+diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
+index 1d0277a6d..13459644c 100644
+--- a/ssh-pkcs11.h
++++ b/ssh-pkcs11.h
+@@ -24,12 +24,16 @@
+ #define SSH_PKCS11_ERR_PIN_REQUIRED 4
+ #define SSH_PKCS11_ERR_PIN_LOCKED 5
+
++#include "ssh-pkcs11-uri.h"
++
+ struct sshkey;
+
+ int pkcs11_init(int);
+ void pkcs11_terminate(void);
+ int pkcs11_add_provider(char *, char *, struct sshkey ***, char ***);
++int pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***, char ***);
+ int pkcs11_del_provider(char *);
++int pkcs11_uri_write(const struct sshkey *, FILE *);
+ int pkcs11_sign(struct sshkey *, u_char **, size_t *,
+ const u_char *, size_t, const char *, const char *,
+ const char *, u_int);
+diff --git a/ssh.c b/ssh.c
+index 0182cc567..c8f3545af 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -848,6 +848,14 @@ main(int ac, char **av)
+ options.gss_deleg_creds = 1;
+ break;
+ case 'i':
++#ifdef ENABLE_PKCS11
++ if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(optarg, PKCS11_URI_SCHEME,
++ strlen(PKCS11_URI_SCHEME)) == 0) {
++ add_identity_file(&options, NULL, optarg, 1);
++ break;
++ }
++#endif
+ p = tilde_expand_filename(optarg, getuid());
+ if (stat(p, &st) == -1)
+ fprintf(stderr, "Warning: Identity file %s "
+@@ -1832,6 +1840,7 @@ main(int ac, char **av)
+
+ #ifdef ENABLE_PKCS11
+ (void)pkcs11_del_provider(options.pkcs11_provider);
++ pkcs11_terminate();
+ #endif
+
+ skip_connect:
+@@ -2356,6 +2365,45 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
+ options.escape_char : SSH_ESCAPECHAR_NONE, id);
+ }
+
++#ifdef ENABLE_PKCS11
++static void
++load_pkcs11_identity(char *pkcs11_uri, char *identity_files[],
++ struct sshkey *identity_keys[], int *n_ids)
++{
++ int nkeys, i;
++ struct sshkey **keys;
++ struct pkcs11_uri *uri;
++
++ debug("identity file '%s' from pkcs#11", pkcs11_uri);
++ uri = pkcs11_uri_init();
++ if (uri == NULL)
++ fatal("Failed to init PKCS#11 URI");
++
++ if (pkcs11_uri_parse(pkcs11_uri, uri) != 0)
++ fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri);
++
++ /* we need to merge URI and provider together */
++ if (options.pkcs11_provider != NULL && uri->module_path == NULL)
++ uri->module_path = strdup(options.pkcs11_provider);
++
++ if (options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
++ (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys, NULL)) > 0) {
++ for (i = 0; i < nkeys; i++) {
++ if (*n_ids >= SSH_MAX_IDENTITY_FILES) {
++ sshkey_free(keys[i]);
++ continue;
++ }
++ identity_keys[*n_ids] = keys[i];
++ identity_files[*n_ids] = pkcs11_uri_get(uri);
++ (*n_ids)++;
++ }
++ free(keys);
++ }
++
++ pkcs11_uri_cleanup(uri);
++}
++#endif /* ENABLE_PKCS11 */
++
+ /* Loads all IdentityFile and CertificateFile keys */
+ static void
+ load_public_identity_files(const struct ssh_conn_info *cinfo)
+@@ -2370,11 +2418,6 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
+ char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
+ struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
+ int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
+-#ifdef ENABLE_PKCS11
+- struct sshkey **keys = NULL;
+- char **comments = NULL;
+- int nkeys;
+-#endif /* PKCS11 */
+
+ n_ids = n_certs = 0;
+ memset(identity_files, 0, sizeof(identity_files));
+@@ -2387,33 +2430,46 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
+ sizeof(certificate_file_userprovided));
+
+ #ifdef ENABLE_PKCS11
+- if (options.pkcs11_provider != NULL &&
+- options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
+- (pkcs11_init(!options.batch_mode) == 0) &&
+- (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL,
+- &keys, &comments)) > 0) {
+- for (i = 0; i < nkeys; i++) {
+- if (n_ids >= SSH_MAX_IDENTITY_FILES) {
+- sshkey_free(keys[i]);
+- free(comments[i]);
+- continue;
+- }
+- identity_keys[n_ids] = keys[i];
+- identity_files[n_ids] = comments[i]; /* transferred */
+- n_ids++;
+- }
+- free(keys);
+- free(comments);
++ /* handle fallback from PKCS11Provider option */
++ pkcs11_init(!options.batch_mode);
++
++ if (options.pkcs11_provider != NULL) {
++ struct pkcs11_uri *uri;
++
++ uri = pkcs11_uri_init();
++ if (uri == NULL)
++ fatal("Failed to init PKCS#11 URI");
++
++ /* Construct simple PKCS#11 URI to simplify access */
++ uri->module_path = strdup(options.pkcs11_provider);
++
++ /* Add it as any other IdentityFile */
++ cp = pkcs11_uri_get(uri);
++ add_identity_file(&options, NULL, cp, 1);
++ free(cp);
++
++ pkcs11_uri_cleanup(uri);
+ }
+ #endif /* ENABLE_PKCS11 */
+ for (i = 0; i < options.num_identity_files; i++) {
++ char *name = options.identity_files[i];
+ if (n_ids >= SSH_MAX_IDENTITY_FILES ||
+- strcasecmp(options.identity_files[i], "none") == 0) {
++ strcasecmp(name, "none") == 0) {
+ free(options.identity_files[i]);
+ options.identity_files[i] = NULL;
+ continue;
+ }
+- cp = tilde_expand_filename(options.identity_files[i], getuid());
++#ifdef ENABLE_PKCS11
++ if (strlen(name) >= strlen(PKCS11_URI_SCHEME) &&
++ strncmp(name, PKCS11_URI_SCHEME,
++ strlen(PKCS11_URI_SCHEME)) == 0) {
++ load_pkcs11_identity(name, identity_files,
++ identity_keys, &n_ids);
++ free(options.identity_files[i]);
++ continue;
++ }
++#endif /* ENABLE_PKCS11 */
++ cp = tilde_expand_filename(name, getuid());
+ filename = default_client_percent_dollar_expand(cp, cinfo);
+ free(cp);
+ check_load(sshkey_load_public(filename, &public, NULL),
+diff --git a/ssh_config.5 b/ssh_config.5
+index f2ff8585c..f16eb61ea 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1304,6 +1304,21 @@ may also be used in conjunction with
+ .Cm CertificateFile
+ in order to provide any certificate also needed for authentication with
+ the identity.
++.Pp
++The authentication identity can be also specified in a form of PKCS#11 URI
++starting with a string
++.Cm pkcs11: .
++There is supported a subset of the PKCS#11 URI as defined
++in RFC 7512 (implemented path arguments
++.Cm id ,
++.Cm manufacturer ,
++.Cm object ,
++.Cm token
++and query arguments
++.Cm module-path
++and
++.Cm pin-value
++). The URI can not be in quotes.
+ .It Cm IgnoreUnknown
+ Specifies a pattern-list of unknown options to be ignored if they are
+ encountered in configuration parsing.
+diff --git a/sshkey.c b/sshkey.c
+index 2380c8081..084ff3b55 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -852,8 +852,10 @@ sshkey_free_contents(struct sshkey *k)
+
+ if (k == NULL)
+ return;
++#ifdef ENABLE_PKCS11
+ if ((k->flags & SSHKEY_FLAG_EXT) != 0)
+ pkcs11_key_free(k);
++#endif
+ if ((impl = sshkey_impl_from_type(k->type)) != NULL &&
+ impl->funcs->cleanup != NULL)
+ impl->funcs->cleanup(k);
+@@ -2300,9 +2302,11 @@ sshkey_sign(struct sshkey *key,
+ if (sshkey_is_sk(key)) {
+ r = sshsk_sign(sk_provider, key, sigp, lenp, data,
+ datalen, compat, sk_pin);
++#ifdef ENABLE_PKCS11
+ } else if ((key->flags & SSHKEY_FLAG_EXT) != 0) {
+ r = pkcs11_sign(key, sigp, lenp, data, datalen,
+ alg, sk_provider, sk_pin, compat);
++#endif
+ } else {
+ if (impl->funcs->sign == NULL)
+ r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
+--
+2.55.0
+
diff --git a/0053-gssapi-tests.patch b/0053-gssapi-tests.patch
new file mode 100644
index 0000000..26fa784
--- /dev/null
+++ b/0053-gssapi-tests.patch
@@ -0,0 +1,230 @@
+From 90f47f37aa1b16d2039f3ed1a37ab418be09a2e1 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 53/53] gssapi-tests
+
+---
+ regress/Makefile | 1 +
+ regress/gss-kex.sh | 198 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 199 insertions(+)
+ create mode 100644 regress/gss-kex.sh
+
+diff --git a/regress/Makefile b/regress/Makefile
+index 492112e80..2011f87a7 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -127,6 +127,7 @@ INTEROP_TESTS+= dropbear-ciphers dropbear-kex dropbear-server
+ #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
+
+ EXTRA_TESTS= agent-pkcs11
++EXTRA_TESTS+= gss-auth gss-kex
+ #EXTRA_TESTS+= cipher-speed
+
+ USERNAME= ${LOGNAME}
+diff --git a/regress/gss-kex.sh b/regress/gss-kex.sh
+new file mode 100644
+index 000000000..63b2e178b
+--- /dev/null
++++ b/regress/gss-kex.sh
+@@ -0,0 +1,198 @@
++tid="GSS-API Key Exchange"
++
++# Skip the test if GSSAPI support is not configured
++if ! grep -E '^#define GSSAPI' "$BUILDDIR/config.h" >/dev/null 2>&1; then
++ skip "GSSAPI not enabled"
++fi
++
++# We test with MIT Kerberos KDC, skip if not installed
++if ! which krb5kdc >/dev/null 2>&1; then
++ skip "MIT Kerberos KDC not installed"
++fi
++
++# The test needs nss_wrapper to emulate gethostname() and /etc/hosts,
++# we skip if the shared library is not installed
++nss_wrapper="libnss_wrapper.so"
++if ! ldconfig -p | grep "$nss_wrapper" >/dev/null 2>&1; then
++ skip "$nss_wrapper not installed"
++fi
++
++# Set up the username of the SSH client
++client="$LOGNAME"
++if [ "x$client" = "x" ]; then
++ client="$(whoami)"
++fi
++
++# Set up SSHD and KDC hostnames and resolve both to localhost
++sshd_hostname="sshd.example.org"
++kdc_hostname="kdc.example.org"
++kdc_port=2088
++hosts="$OBJ/hosts"
++echo "127.0.0.1 $sshd_hostname $kdc_hostname" > "$hosts"
++
++# Set up a directory to store Kerberos data
++gssdir="$OBJ/gss"
++mkdir -p "$gssdir"
++export KRB5CCNAME="$gssdir/cc"
++export KRB5_CONFIG="$gssdir/krb5.conf"
++export KRB5_KDC_PROFILE="$gssdir/kdc.conf"
++export KRB5_KTNAME="$gssdir/ssh.keytab"
++export KRB5RCACHETYPE="none"
++kdc_pidfile="$gssdir/pid"
++
++# Configure Kerberos
++cat<<EOF > "$KRB5_KDC_PROFILE"
++[realms]
++ EXAMPLE.ORG = {
++ database_name = $gssdir/principal
++ key_stash_file = $gssdir/stash
++ kdc_listen = $kdc_hostname:$kdc_port
++ kdc_tcp_listen = $kdc_hostname:$kdc_port
++ }
++[logging]
++ kdc = FILE:$gssdir/kdc.log
++ debug = true
++EOF
++
++cat<<EOF > "$KRB5_CONFIG"
++[libdefaults]
++ default_realm = EXAMPLE.ORG
++[realms]
++ EXAMPLE.ORG = {
++ kdc = $kdc_hostname:$kdc_port
++ }
++EOF
++
++# Back up the default sshd_config
++cp "$OBJ/sshd_config" "$OBJ/sshd_config.orig"
++
++setup_sshd() {
++ kex_alg="$1"
++
++ cp "$OBJ/sshd_config.orig" "$OBJ/sshd_config"
++
++ cat<<EOF >> "$OBJ/sshd_config"
++PubkeyAuthentication no
++PasswordAuthentication no
++GSSAPIAuthentication yes
++GSSAPIKeyExchange yes
++GSSAPIKexAlgorithms $kex_alg
++GSSAPIStrictAcceptorCheck no
++EOF
++
++ test_ssh_sshd_env_backup="$TEST_SSH_SSHD_ENV"
++ TEST_SSH_SSHD_ENV="$TEST_SSH_SSHD_ENV \
++ LD_PRELOAD=$nss_wrapper \
++ NSS_WRAPPER_HOSTS=$hosts \
++ NSS_WRAPPER_HOSTNAME=$sshd_hostname \
++ KRB5_CONFIG=$KRB5_CONFIG \
++ KRB5_KDC_PROFILE=$KRB5_KDC_PROFILE \
++ KRB5CCNAME=$KRB5CCNAME \
++ KRB5_KTNAME=$KRB5_KTNAME \
++ KRB5RCACHETYPE=$KRB5RCACHETYPE"
++ start_sshd
++}
++
++teardown_sshd() {
++ TEST_SSH_SSHD_ENV="$test_ssh_sshd_env_backup"
++ stop_sshd
++}
++
++setup_kdc() {
++ kdb5_util create -P "foo" -s
++ krb5kdc -w 1 -P "$kdc_pidfile"
++ i=0;
++ while [ ! -f "$kdc_pidfile" -a $i -lt 10 ]; do
++ i=$((i + 1))
++ sleep 1
++ done
++ test -f "$kdc_pidfile" || fatal "KDC failed to start"
++}
++
++teardown_kdc() {
++ kill "$(cat "$kdc_pidfile")"
++ kdestroy
++ rm -f "$KRB5_KTNAME" "$kdc_pidfile"
++ kdb5_util destroy -f
++}
++
++setup_nss_emulation() {
++ export LD_PRELOAD="$nss_wrapper"
++ export NSS_WRAPPER_HOSTS="$hosts"
++}
++
++teardown_nss_emulation() {
++ unset LD_PRELOAD
++ unset NSS_WRAPPER_HOSTS
++}
++
++test_gss_kex() {
++ kex_family="$1"
++ auth_sshd="$2"
++ expect="$3"
++
++ # Check if the algorithm family is recognized by ssh
++ if ! ${REAL_SSH} -G -F "$OBJ/ssh_config" \
++ -o "GSSAPIKeyExchange yes" \
++ -o "GSSAPIKexAlgorithms $kex_family" \
++ "$client@$sshd_hostname" >/dev/null 2>&1; then
++ verbose "gss kex $kex_family not supported, skipping"
++ return 0
++ fi
++
++ setup_sshd "$kex_family"
++ setup_nss_emulation
++ setup_kdc
++
++ kadmin.local add_principal -randkey "host/$sshd_hostname"
++ if $auth_sshd; then
++ kadmin.local ktadd "host/$sshd_hostname"
++ fi
++ kadmin.local add_principal -pw "foo" "$client"
++ echo "foo" | kinit "$client"
++
++ ${SSH} -F "$OBJ/ssh_config" \
++ -o "GSSAPIAuthentication yes" \
++ -o "GSSAPIKeyExchange yes" \
++ -o "GSSAPIKexAlgorithms $kex_family" \
++ -o "GSSAPIDelegateCredentials no" \
++ "$client@$sshd_hostname" true
++ status=$?
++
++ teardown_kdc
++ teardown_nss_emulation
++ teardown_sshd
++
++ [ $status -eq $expect ]
++}
++
++# GSS kex family prefixes to test.
++# GSSAPIKexAlgorithms accepts these prefixes; the full algorithm names
++# (prefix + Base64(MD5(OID))) are constructed during negotiation.
++gss_kex_families="
++ gss-group14-sha256-
++ gss-group16-sha512-
++ gss-nistp256-sha256-
++ gss-curve25519-sha256-
++ gss-group14-sha1-
++ gss-gex-sha1-
++"
++
++# Positive tests: each GSS kex algorithm connects successfully
++for kex_family in $gss_kex_families; do
++ verbose "gss kex $kex_family"
++ test_gss_kex "$kex_family" true 0 \
++ || fail "gss kex $kex_family failed"
++done
++
++# Negative test: connection must fail when keytab is missing
++verbose "gss kex negative test: no keytab"
++test_gss_kex "gss-group14-sha256-" false 255 \
++ || fail "gss kex succeeded without keytab"
++
++unset KRB5CCNAME
++unset KRB5_CONFIG
++unset KRB5_KDC_PROFILE
++unset KRB5_KTNAME
++unset KRB5RCACHETYPE
++rm -rf "$gssdir"
+--
+2.55.0
+
diff --git a/0053-openssh-10.2p1-pkcs11-uri.patch b/0053-openssh-10.2p1-pkcs11-uri.patch
deleted file mode 100644
index cbaeaf3..0000000
--- a/0053-openssh-10.2p1-pkcs11-uri.patch
+++ /dev/null
@@ -1,3410 +0,0 @@
-From 50a4a4adde0ff965afe34dbc059996d86d4fcfc6 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 15 Dec 2025 14:24:07 +0100
-Subject: [PATCH 53/54] openssh-10.2p1-pkcs11-uri
-
----
- Makefile.in | 26 +-
- configure.ac | 37 ++
- regress/Makefile | 5 +-
- regress/pkcs11-uri.sh | 410 ++++++++++++++++
- regress/unittests/Makefile | 2 +-
- regress/unittests/pkcs11/tests.c | 353 ++++++++++++++
- ssh-add.c | 48 +-
- ssh-agent.c | 117 ++++-
- ssh-keygen.c | 7 +-
- ssh-pkcs11-client.c | 19 +
- ssh-pkcs11-uri.c | 438 ++++++++++++++++++
- ssh-pkcs11-uri.h | 46 ++
- ssh-pkcs11.c | 771 ++++++++++++++++++++++---------
- ssh-pkcs11.h | 4 +
- ssh.c | 104 ++++-
- ssh_config.5 | 15 +
- sshkey.c | 4 +
- 17 files changed, 2133 insertions(+), 273 deletions(-)
- create mode 100644 regress/pkcs11-uri.sh
- create mode 100644 regress/unittests/pkcs11/tests.c
- create mode 100644 ssh-pkcs11-uri.c
- create mode 100644 ssh-pkcs11-uri.h
-
-diff --git a/Makefile.in b/Makefile.in
-index f80c95846..c3c8da863 100644
---- a/Makefile.in
-+++ b/Makefile.in
-@@ -112,12 +112,12 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
- sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
- sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
-
--P11OBJS= ssh-pkcs11-client.o
-+P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
-
- SKOBJS= ssh-sk-client.o
-
- SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
-- sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o $(SKOBJS)
-+ sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
-
- SSHDOBJS=sshd.o \
- platform-listen.o \
-@@ -161,11 +161,11 @@ SSHADD_OBJS= ssh-add.o $(P11OBJS) $(SKOBJS)
-
- SSHAGENT_OBJS= ssh-agent.o $(P11OBJS) $(SKOBJS)
-
--SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o ssh-pkcs11.o $(SKOBJS)
-+SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
-
- SSHKEYSIGN_OBJS=ssh-keysign.o readconf.o uidswap.o $(P11OBJS) $(SKOBJS)
-
--P11HELPER_OBJS= ssh-pkcs11-helper.o ssh-pkcs11.o $(SKOBJS)
-+P11HELPER_OBJS= ssh-pkcs11-helper.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
-
- SKHELPER_OBJS= ssh-sk-helper.o ssh-sk.o sk-usbhid.o ssherr-nolibcrypto.o
-
-@@ -331,6 +331,8 @@ clean: regressclean
- rm -f regress/unittests/sshsig/test_sshsig$(EXEEXT)
- rm -f regress/unittests/utf8/*.o
- rm -f regress/unittests/utf8/test_utf8$(EXEEXT)
-+ rm -f regress/unittests/pkcs11/*.o
-+ rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT)
- rm -f regress/misc/sk-dummy/*.o
- rm -f regress/misc/sk-dummy/*.lo
- rm -f regress/misc/ssh-verify-attestation/ssh-verify-attestation$(EXEEXT)
-@@ -370,6 +372,8 @@ distclean: regressclean
- rm -f regress/unittests/sshsig/test_sshsig
- rm -f regress/unittests/utf8/*.o
- rm -f regress/unittests/utf8/test_utf8
-+ rm -f regress/unittests/pkcs11/*.o
-+ rm -f regress/unittests/pkcs11/test_pkcs11
- rm -f regress/misc/sk-dummy/*.o
- rm -f regress/misc/sk-dummy/*.lo
- rm -f regress/misc/sk-dummy/sk-dummy.so
-@@ -550,6 +554,7 @@ regress-prep:
- $(MKDIR_P) `pwd`/regress/unittests/sshkey
- $(MKDIR_P) `pwd`/regress/unittests/sshsig
- $(MKDIR_P) `pwd`/regress/unittests/utf8
-+ $(MKDIR_P) `pwd`/regress/unittests/pkcs11
- $(MKDIR_P) `pwd`/regress/misc/sk-dummy
- $(MKDIR_P) `pwd`/regress/misc/ssh-verify-attestation
- [ -f `pwd`/regress/Makefile ] || \
-@@ -725,6 +730,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
- regress/unittests/test_helper/libtest_helper.a \
- -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(TESTLIBS)
-
-+UNITTESTS_TEST_PKCS11_OBJS=\
-+ regress/unittests/pkcs11/tests.o
-+
-+regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
-+ ${UNITTESTS_TEST_PKCS11_OBJS} ssh-pkcs11-uri.o \
-+ regress/unittests/test_helper/libtest_helper.a libssh.a
-+ $(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
-+ regress/unittests/test_helper/libtest_helper.a \
-+ ssh-pkcs11-uri.o -lssh -lopenbsd-compat -lcrypto $(LIBS) -lm
-+
- # These all need to be compiled -fPIC, so they are treated differently.
- SK_DUMMY_OBJS=\
- regress/misc/sk-dummy/sk-dummy.lo \
-@@ -770,7 +785,8 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
- regress/unittests/sshbuf/test_sshbuf$(EXEEXT) \
- regress/unittests/sshkey/test_sshkey$(EXEEXT) \
- regress/unittests/sshsig/test_sshsig$(EXEEXT) \
-- regress/unittests/utf8/test_utf8$(EXEEXT)
-+ regress/unittests/utf8/test_utf8$(EXEEXT) \
-+ regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
-
- tests: file-tests t-exec interop-tests extra-tests unit
- echo all tests passed
-diff --git a/configure.ac b/configure.ac
-index 62b2bf9e7..f581a9801 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -2305,12 +2305,14 @@ AC_LINK_IFELSE(
- [AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).])
- ])
-
-+SCARD_MSG="yes"
- disable_pkcs11=
- AC_ARG_ENABLE([pkcs11],
- [ --disable-pkcs11 disable PKCS#11 support code [no]],
- [
- if test "x$enableval" = "xno" ; then
- disable_pkcs11=1
-+ SCARD_MSG="no"
- fi
- ]
- )
-@@ -2340,6 +2342,40 @@ AC_SEARCH_LIBS([dlopen], [dl])
- AC_CHECK_FUNCS([dlopen])
- AC_CHECK_DECL([RTLD_NOW], [], [], [#include <dlfcn.h>])
-
-+# Check whether we have a p11-kit, we got default provider on command line
-+DEFAULT_PKCS11_PROVIDER_MSG="no"
-+AC_ARG_WITH([default-pkcs11-provider],
-+ [ --with-default-pkcs11-provider[[=PATH]] Use default pkcs11 provider (p11-kit detected by default)],
-+ [ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then
-+ if test "x$withval" = "xyes" ; then
-+ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
-+ if test "x$PKGCONFIG" != "xno"; then
-+ AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit])
-+ if "$PKGCONFIG" "p11-kit-1"; then
-+ AC_MSG_RESULT([yes])
-+ use_pkgconfig_for_p11kit=yes
-+ else
-+ AC_MSG_RESULT([no])
-+ fi
-+ fi
-+ else
-+ PKCS11_PATH="${withval}"
-+ fi
-+ if test "x$use_pkgconfig_for_p11kit" = "xyes"; then
-+ PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1`
-+ fi
-+ AC_CHECK_FILE("$PKCS11_PATH",
-+ [ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)])
-+ DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH"
-+ ],
-+ [ AC_MSG_ERROR([Requested PKCS11 provided not found]) ]
-+ )
-+ else
-+ AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider])
-+ fi ]
-+)
-+
-+
- # IRIX has a const char return value for gai_strerror()
- AC_CHECK_FUNCS([gai_strerror], [
- AC_DEFINE([HAVE_GAI_STRERROR])
-@@ -5991,6 +6027,7 @@ echo " BSD Auth support: $BSD_AUTH_MSG"
- echo " Random number source: $RAND_MSG"
- echo " Privsep sandbox style: $SANDBOX_STYLE"
- echo " PKCS#11 support: $enable_pkcs11"
-+echo " Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG"
- echo " U2F/FIDO support: $enable_sk"
-
- echo ""
-diff --git a/regress/Makefile b/regress/Makefile
-index ae45bd463..cbe7a9610 100644
---- a/regress/Makefile
-+++ b/regress/Makefile
-@@ -118,6 +118,7 @@ LTESTS= connect \
- penalty-expire \
- connect-bigconf \
- ssh-pkcs11 \
-+ pkcs11-uri \
- ssh-tty \
- proxyjump
-
-@@ -144,7 +145,8 @@ CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \
- known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
- modpipe netcat no_identity_config \
- pidfile putty.rsa2 ready regress.log remote_pid \
-- revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \
-+ revoked-* rsa rsa-agent rsa-agent.pub rsa-agent-cert.pub \
-+ rsa.pub rsa_ssh2_cr.prv pkcs11*.crt pkcs11*.key pkcs11.info \
- rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
- scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
- sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
-@@ -294,6 +296,7 @@ unit unit-bench:
- test "x${UNITTEST_VERBOSE}" = "x" || ARGS="$$ARGS -v"; \
- test "x${UNITTEST_BENCH_DETAIL}" = "x" || ARGS="$$ARGS -B"; \
- test "x${UNITTEST_BENCH_ONLY}" = "x" || ARGS="$$ARGS -O ${UNITTEST_BENCH_ONLY}"; \
-+ $$V ${.OBJDIR}/unittests/pkcs11/test_pkcs11 ; \
- $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf $${ARGS}; \
- $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
- -d ${.CURDIR}/unittests/sshkey/testdata $${ARGS}; \
-diff --git a/regress/pkcs11-uri.sh b/regress/pkcs11-uri.sh
-new file mode 100644
-index 000000000..72d69bda0
---- /dev/null
-+++ b/regress/pkcs11-uri.sh
-@@ -0,0 +1,410 @@
-+#
-+# Copyright (c) 2017 Red Hat
-+#
-+# Authors: Jakub Jelen <jjelen@redhat.com>
-+#
-+# 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.
-+
-+tid="pkcs11 tests with soft token"
-+
-+try_token_libs() {
-+ for _lib in "$@" ; do
-+ if test -f "$_lib" ; then
-+ verbose "Using token library $_lib"
-+ TEST_SSH_PKCS11="$_lib"
-+ return
-+ fi
-+ done
-+ echo "skipped: Unable to find PKCS#11 token library"
-+ exit 0
-+}
-+
-+try_token_libs \
-+ /usr/local/lib/softhsm/libsofthsm2.so \
-+ /usr/lib64/pkcs11/libsofthsm2.so \
-+ /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
-+
-+TEST_SSH_PIN=1234
-+TEST_SSH_SOPIN=12345678
-+if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
-+ SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
-+ export SSH_PKCS11_HELPER
-+fi
-+
-+test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
-+
-+# setup environment for softhsm token
-+DIR=$OBJ/SOFTHSM
-+rm -rf $DIR
-+TOKEN=$DIR/tokendir
-+mkdir -p $TOKEN
-+SOFTHSM2_CONF=$DIR/softhsm2.conf
-+export SOFTHSM2_CONF
-+cat > $SOFTHSM2_CONF << EOF
-+# SoftHSM v2 configuration file
-+directories.tokendir = ${TOKEN}
-+objectstore.backend = file
-+# ERROR, WARNING, INFO, DEBUG
-+log.level = DEBUG
-+# If CKF_REMOVABLE_DEVICE flag should be set
-+slots.removable = false
-+EOF
-+out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
-+slot=$(echo -- $out | sed 's/.* //')
-+
-+# prevent ssh-agent from calling ssh-askpass
-+SSH_ASKPASS=/usr/bin/true
-+export SSH_ASKPASS
-+unset DISPLAY
-+# We need interactive access to test PKCS# since it prompts for PIN
-+# Backup ssh_proxy before modifications
-+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
-+sed -i 's/.*BatchMode.*//g' $OBJ/ssh_proxy
-+# Remove IdentityFile entries to prevent exceeding MaxAuthTries when using
-+# PKCS11Provider (-I) or PKCS11 URIs. The default ssh_config includes multiple
-+# identity files that would be tried before PKCS11 keys, causing authentication
-+# to fail with "Too many authentication failures" before PKCS11 keys are reached.
-+grep -iv IdentityFile $OBJ/ssh_proxy > $OBJ/ssh_proxy.tmp
-+mv $OBJ/ssh_proxy.tmp $OBJ/ssh_proxy
-+
-+# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh)
-+notty() {
-+ perl -e 'use POSIX; POSIX::setsid();
-+ if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
-+}
-+
-+trace "generating keys"
-+ID1="02"
-+ID2="04"
-+ID3="06"
-+RSA=${DIR}/RSA
-+EC=${DIR}/EC
-+ED25519=${DIR}/ED25519
-+openssl genpkey -algorithm rsa > $RSA
-+openssl pkcs8 -nocrypt -in $RSA |\
-+ softhsm2-util --slot "$slot" --label "SSH RSA Key $ID1" --id $ID1 \
-+ --pin "$TEST_SSH_PIN" --import /dev/stdin
-+openssl genpkey \
-+ -genparam \
-+ -algorithm ec \
-+ -pkeyopt ec_paramgen_curve:prime256v1 |\
-+ openssl genpkey \
-+ -paramfile /dev/stdin > $EC
-+openssl pkcs8 -nocrypt -in $EC |\
-+ softhsm2-util --slot "$slot" --label "SSH ECDSA Key $ID2" --id $ID2 \
-+ --pin "$TEST_SSH_PIN" --import /dev/stdin
-+openssl genpkey -algorithm ed25519 > $ED25519
-+openssl pkcs8 -nocrypt -in $ED25519 |\
-+ softhsm2-util --slot "$slot" --label "SSH ED25519 Key $ID3" --id $ID3 \
-+ --pin "$TEST_SSH_PIN" --import /dev/stdin
-+
-+trace "List the keys in the ssh-keygen with PKCS#11 URIs"
-+${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys
-+if [ $? -ne 0 ]; then
-+ fail "FAIL: keygen fails to enumerate keys on PKCS#11 token"
-+fi
-+grep "pkcs11:" $OBJ/token_keys > /dev/null
-+if [ $? -ne 0 ]; then
-+ fail "FAIL: The keys from ssh-keygen do not contain PKCS#11 URI as a comment"
-+fi
-+
-+# Set the ECDSA key to authorized keys
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Simple connect with ssh (without PKCS#11 URI)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \
-+ -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with pkcs11 failed (exit code $r)"
-+fi
-+
-+trace "Connect with PKCS#11 URI"
-+trace " (ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace " (RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Connect with PKCS#11 URI including PIN should not prompt"
-+trace " (ECDSA key should succeed)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+ "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace " (RSA key should fail)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (ED25519 key should succeed)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+ "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Connect with various filtering options in PKCS#11 URI"
-+trace " (by object label, ECDSA should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:object=SSH%20ECDSA%20Key%2004?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace " (by object label, RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:object=SSH%20RSA%20Key%2002?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (by object label, ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:object=SSH%20ED25519%20Key%2006?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (by token label, ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID2};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (by token label, ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID3};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (by wrong token label, should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:token=token-slot-99?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+
-+
-+
-+trace "Test PKCS#11 URI specification in configuration files"
-+echo "IdentityFile \"pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}\"" \
-+ >> $OBJ/ssh_proxy
-+trace " (ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI in config failed (exit code $r)"
-+fi
-+
-+# Set the RSA key as authorized
-+grep "RSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace " (RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI in config succeeded (should fail)"
-+fi
-+sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
-+
-+trace "Combination of PKCS11Provider and PKCS11URI on commandline"
-+trace " (RSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+ -i "pkcs11:id=%${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect with PKCS#11 URI and provider combination" \
-+ "failed (exit code $r)"
-+fi
-+
-+trace "Regress: Missing provider in PKCS11URI option"
-+${SSH} -F $OBJ/ssh_proxy \
-+ -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5
-+r=$?
-+if [ $r -eq 139 ]; then
-+ fail "FAIL: ssh connect with missing provider_id from configuration option" \
-+ "crashed (exit code $r)"
-+fi
-+
-+
-+trace "SSH Agent can work with PKCS#11 URI"
-+trace "start the agent"
-+eval `${SSHAGENT} -s` > /dev/null
-+
-+r=$?
-+if [ $r -ne 0 ]; then
-+ fail "could not start ssh-agent: exit code $r"
-+else
-+ trace "add whole provider to agent"
-+ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+ "pkcs11:?module-path=${TEST_SSH_PKCS11}" #> /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add failed with whole provider: exit code $r"
-+ fi
-+
-+ trace " pkcs11 list via agent (all keys)"
-+ ${SSHADD} -l > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add -l failed with whole provider: exit code $r"
-+ fi
-+
-+ trace " pkcs11 connect via agent (all keys)"
-+ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+ r=$?
-+ if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect failed with whole provider (exit code $r)"
-+ fi
-+
-+ trace " remove pkcs11 keys (all keys)"
-+ ${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add -d failed with whole provider: exit code $r"
-+ fi
-+
-+ trace "add only RSA key to the agent"
-+ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL ssh-add failed with RSA key: exit code $r"
-+ fi
-+
-+ trace " pkcs11 connect via agent (RSA key)"
-+ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+ r=$?
-+ if [ $r -ne 5 ]; then
-+ fail "FAIL: ssh connect failed with RSA key (exit code $r)"
-+ fi
-+
-+ trace " remove RSA pkcs11 key"
-+ ${SSHADD} -d "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" \
-+ > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add -d failed with RSA key: exit code $r"
-+ fi
-+
-+ trace "add only ECDSA key to the agent"
-+ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+ "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add failed with second key: exit code $r"
-+ fi
-+
-+ trace " pkcs11 connect via agent (ECDSA key should fail)"
-+ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+ r=$?
-+ if [ $r -eq 5 ]; then
-+ fail "FAIL: ssh connect passed with ECDSA key (should fail)"
-+ fi
-+
-+ trace "add also the RSA key to the agent"
-+ echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+ "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "FAIL: ssh-add failed with first key: exit code $r"
-+ fi
-+
-+ trace " remove ECDSA pkcs11 key"
-+ ${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
-+ > /dev/null 2>&1
-+ r=$?
-+ if [ $r -ne 0 ]; then
-+ fail "ssh-add -d failed with ECDSA key: exit code $r"
-+ fi
-+
-+ trace " remove already-removed pkcs11 key should fail"
-+ ${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
-+ > /dev/null 2>&1
-+ r=$?
-+ if [ $r -eq 0 ]; then
-+ fail "FAIL: ssh-add -d passed with non-existing key (should fail)"
-+ fi
-+
-+ trace " pkcs11 connect via agent (the RSA key should be still usable)"
-+ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+ r=$?
-+ if [ $r -ne 5 ]; then
-+ fail "ssh connect failed with RSA key (after removing ECDSA): exit code $r"
-+ fi
-+
-+ trace "kill agent"
-+ ${SSHAGENT} -k > /dev/null
-+fi
-+
-+# Restore original ssh_proxy
-+mv $OBJ/ssh_proxy_bak $OBJ/ssh_proxy
-diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
-index e370900e4..d6c89c129 100644
---- a/regress/unittests/Makefile
-+++ b/regress/unittests/Makefile
-@@ -1,6 +1,6 @@
- # $OpenBSD: Makefile,v 1.13 2023/09/24 08:14:13 claudio Exp $
-
- SUBDIR= test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
--SUBDIR+=authopt misc sshsig
-+SUBDIR+=authopt misc sshsig pkcs11
-
- .include <bsd.subdir.mk>
-diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c
-new file mode 100644
-index 000000000..89ba45c4e
---- /dev/null
-+++ b/regress/unittests/pkcs11/tests.c
-@@ -0,0 +1,353 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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"
-+
-+#include <locale.h>
-+#include <string.h>
-+
-+#include "../test_helper/test_helper.h"
-+
-+#include "sshbuf.h"
-+#include "ssh-pkcs11-uri.h"
-+
-+#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
-+
-+/* prototypes are not public -- specify them here internally for tests */
-+struct sshbuf *percent_encode(const char *, size_t, char *);
-+int percent_decode(char *, char **);
-+
-+void
-+compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b)
-+{
-+ ASSERT_PTR_NE(a, NULL);
-+ ASSERT_PTR_NE(b, NULL);
-+ ASSERT_SIZE_T_EQ(a->id_len, b->id_len);
-+ ASSERT_MEM_EQ(a->id, b->id, a->id_len);
-+ if (b->object != NULL)
-+ ASSERT_STRING_EQ(a->object, b->object);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->object, b->object);
-+ if (b->module_path != NULL)
-+ ASSERT_STRING_EQ(a->module_path, b->module_path);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->module_path, b->module_path);
-+ if (b->token != NULL)
-+ ASSERT_STRING_EQ(a->token, b->token);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->token, b->token);
-+ if (b->manuf != NULL)
-+ ASSERT_STRING_EQ(a->manuf, b->manuf);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->manuf, b->manuf);
-+ if (b->lib_manuf != NULL)
-+ ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf);
-+ if (b->serial != NULL)
-+ ASSERT_STRING_EQ(a->serial, b->serial);
-+ else /* both should be null */
-+ ASSERT_PTR_EQ(a->serial, b->serial);
-+}
-+
-+void
-+check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv)
-+{
-+ char *buf = NULL, *str;
-+ struct pkcs11_uri *pkcs11uri = NULL;
-+ int rv;
-+
-+ if (expect_rv == 0)
-+ str = "Valid";
-+ else
-+ str = "Invalid";
-+ asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri);
-+ TEST_START(buf);
-+ free(buf);
-+ pkcs11uri = pkcs11_uri_init();
-+ rv = pkcs11_uri_parse(uri, pkcs11uri);
-+ ASSERT_INT_EQ(rv, expect_rv);
-+ if (rv == 0) /* in case of failure result is undefined */
-+ compare_uri(pkcs11uri, expect);
-+ pkcs11_uri_cleanup(pkcs11uri);
-+ free(expect);
-+ TEST_DONE();
-+}
-+
-+void
-+check_parse(char *uri, struct pkcs11_uri *expect)
-+{
-+ check_parse_rv(uri, expect, 0);
-+}
-+
-+struct pkcs11_uri *
-+compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf,
-+ char *manuf, char *serial, char *module_path, char *object, char *pin)
-+{
-+ struct pkcs11_uri *uri = pkcs11_uri_init();
-+ if (id_len > 0) {
-+ uri->id_len = id_len;
-+ uri->id = id;
-+ }
-+ uri->module_path = module_path;
-+ uri->token = token;
-+ uri->lib_manuf = lib_manuf;
-+ uri->manuf = manuf;
-+ uri->serial = serial;
-+ uri->object = object;
-+ uri->pin = pin;
-+ return uri;
-+}
-+
-+static void
-+test_parse_valid(void)
-+{
-+ /* path arguments */
-+ check_parse("pkcs11:id=%01",
-+ compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_parse("pkcs11:id=%00%01",
-+ compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_parse("pkcs11:token=SSH%20Keys",
-+ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_parse("pkcs11:library-manufacturer=OpenSC",
-+ compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));
-+ check_parse("pkcs11:manufacturer=piv_II",
-+ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
-+ check_parse("pkcs11:serial=IamSerial",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
-+ check_parse("pkcs11:object=SIGN%20Key",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "SIGN Key", NULL));
-+ /* query arguments */
-+ check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+ check_parse("pkcs11:?pin-value=123456",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, "123456"));
-+
-+ /* combinations */
-+ /* ID SHOULD be percent encoded */
-+ check_parse("pkcs11:token=SSH%20Key;id=0",
-+ compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_parse(
-+ "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
-+ compose_uri(NULL, 0, NULL, NULL, "CAC", NULL,
-+ "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+ check_parse(
-+ "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL,
-+ "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key", NULL));
-+ check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so&pin-value=123456",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, "123456"));
-+
-+ /* empty path component matches everything */
-+ check_parse("pkcs11:", EMPTY_URI);
-+
-+ /* empty string is a valid to match against (and different from NULL) */
-+ check_parse("pkcs11:token=",
-+ compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL, NULL, NULL));
-+ /* Percent character needs to be percent-encoded */
-+ check_parse("pkcs11:token=%25",
-+ compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL, NULL, NULL));
-+}
-+
-+static void
-+test_parse_invalid(void)
-+{
-+ /* Invalid percent encoding */
-+ check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1);
-+ /* Invalid percent encoding */
-+ check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1);
-+ /* Space MUST be percent encoded -- XXX not enforced yet */
-+ check_parse("pkcs11:token=SSH Keys",
-+ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+ /* MUST NOT contain duplicate attributes of the same name */
-+ check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1);
-+ /* MUST NOT contain duplicate attributes of the same name */
-+ check_parse_rv("pkcs11:?pin-value=111111&pin-value=123456", EMPTY_URI, -1);
-+ /* Unrecognized attribute in path are ignored with log message */
-+ check_parse("pkcs11:key_name=SSH", EMPTY_URI);
-+ /* Unrecognized attribute in query SHOULD be ignored */
-+ check_parse("pkcs11:?key_name=SSH", EMPTY_URI);
-+}
-+
-+void
-+check_gen(char *expect, struct pkcs11_uri *uri)
-+{
-+ char *buf = NULL, *uri_str;
-+
-+ asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect);
-+ TEST_START(buf);
-+ free(buf);
-+ uri_str = pkcs11_uri_get(uri);
-+ ASSERT_PTR_NE(uri_str, NULL);
-+ ASSERT_STRING_EQ(uri_str, expect);
-+ free(uri_str);
-+ TEST_DONE();
-+}
-+
-+static void
-+test_generate_valid(void)
-+{
-+ /* path arguments */
-+ check_gen("pkcs11:id=%01",
-+ compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_gen("pkcs11:id=%00%01",
-+ compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */
-+ compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+ /* library-manufacturer is not implmented now */
-+ /*check_gen("pkcs11:library-manufacturer=OpenSC",
-+ compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));*/
-+ check_gen("pkcs11:manufacturer=piv_II",
-+ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
-+ check_gen("pkcs11:serial=IamSerial",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
-+ check_gen("pkcs11:object=RSA%20Key",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "RSA Key", NULL));
-+ /* query arguments */
-+ check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
-+ compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+
-+ /* combinations */
-+ check_gen("pkcs11:id=%02;token=SSH%20Keys",
-+ compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+ check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
-+ compose_uri("\xEE\x02", 2, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+ check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II",
-+ compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, "Encryption Key", NULL));
-+
-+ /* empty path component matches everything */
-+ check_gen("pkcs11:", EMPTY_URI);
-+
-+}
-+
-+void
-+check_encode(char *source, size_t len, char *allow_list, char *expect)
-+{
-+ char *buf = NULL;
-+ struct sshbuf *b;
-+
-+ asprintf(&buf, "percent_encode: expected %s", expect);
-+ TEST_START(buf);
-+ free(buf);
-+
-+ b = percent_encode(source, len, allow_list);
-+ ASSERT_STRING_EQ(sshbuf_ptr(b), expect);
-+ sshbuf_free(b);
-+ TEST_DONE();
-+}
-+
-+static void
-+test_percent_encode_multibyte(void)
-+{
-+ /* SHOULD be encoded as octets according to the UTF-8 character encoding */
-+
-+ /* multi-byte characters are "for free" */
-+ check_encode("$", 1, "", "%24");
-+ check_encode("¢", 2, "", "%C2%A2");
-+ check_encode("€", 3, "", "%E2%82%AC");
-+ check_encode("𐍈", 4, "", "%F0%90%8D%88");
-+
-+ /* CK_UTF8CHAR is unsigned char (1 byte) */
-+ /* labels SHOULD be normalized to NFC [UAX15] */
-+
-+}
-+
-+static void
-+test_percent_encode(void)
-+{
-+ /* Without allow list encodes everything (for CKA_ID) */
-+ check_encode("A*", 2, "", "%41%2A");
-+ check_encode("\x00", 1, "", "%00");
-+ check_encode("\x7F", 1, "", "%7F");
-+ check_encode("\x80", 1, "", "%80");
-+ check_encode("\xff", 1, "", "%FF");
-+
-+ /* Default allow list encodes anything but safe letters */
-+ check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST,
-+ "test%000alpha");
-+ check_encode(" ", 1, PKCS11_URI_WHITELIST,
-+ "%20"); /* Space MUST be percent encoded */
-+ check_encode("/", 1, PKCS11_URI_WHITELIST,
-+ "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */
-+ check_encode("?", 1, PKCS11_URI_WHITELIST,
-+ "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */
-+ check_encode("#", 1, PKCS11_URI_WHITELIST,
-+ "%23"); /* '#' MUST be always percent encoded */
-+ check_encode("key=value;separator?query&#anch", 35, PKCS11_URI_WHITELIST,
-+ "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch");
-+
-+ /* Components in query can have '/' unencoded (useful for paths) */
-+ check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/",
-+ "/path/to.file");
-+}
-+
-+void
-+check_decode(char *source, char *expect, int expect_len)
-+{
-+ char *buf = NULL, *out = NULL;
-+ int rv;
-+
-+ asprintf(&buf, "percent_decode: %s", source);
-+ TEST_START(buf);
-+ free(buf);
-+
-+ rv = percent_decode(source, &out);
-+ ASSERT_INT_EQ(rv, expect_len);
-+ if (rv >= 0)
-+ ASSERT_MEM_EQ(out, expect, expect_len);
-+ free(out);
-+ TEST_DONE();
-+}
-+
-+static void
-+test_percent_decode(void)
-+{
-+ /* simple valid cases */
-+ check_decode("%00", "\x00", 1);
-+ check_decode("%FF", "\xFF", 1);
-+
-+ /* normal strings shold be kept intact */
-+ check_decode("strings are left", "strings are left", 16);
-+ check_decode("10%25 of trees", "10% of trees", 12);
-+
-+ /* make sure no more than 2 bytes are parsed */
-+ check_decode("%222", "\x22" "2", 2);
-+
-+ /* invalid expects failure */
-+ check_decode("%0", "", -1);
-+ check_decode("%Z", "", -1);
-+ check_decode("%FG", "", -1);
-+}
-+
-+void
-+tests(void)
-+{
-+ test_percent_encode();
-+ test_percent_encode_multibyte();
-+ test_percent_decode();
-+ test_parse_valid();
-+ test_parse_invalid();
-+ test_generate_valid();
-+}
-+
-+void
-+benchmarks(void)
-+{
-+ printf("no benchmarks\n");
-+}
-+
-diff --git a/ssh-add.c b/ssh-add.c
-index 1e9eddf90..e476fa662 100644
---- a/ssh-add.c
-+++ b/ssh-add.c
-@@ -70,6 +70,7 @@
- #include "ssh-sk.h"
- #include "sk-api.h"
- #include "hostfile.h"
-+#include "ssh-pkcs11-uri.h"
-
- #define CERT_EXPIRY_GRACE (5*60)
-
-@@ -272,6 +273,38 @@ check_cert_lifetime(const struct sshkey *cert, int cert_lifetime)
- return MINIMUM(cert_lifetime, (int)n);
- }
-
-+#ifdef ENABLE_PKCS11
-+static int
-+update_card(int agent_fd, int add, const char *id, int qflag,
-+ int key_only, int cert_only,
-+ struct dest_constraint **dest_constraints, size_t ndest_constraints,
-+ struct sshkey **certs, size_t ncerts, char *pin);
-+
-+int
-+update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag,
-+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
-+{
-+ char *pin = NULL;
-+ struct pkcs11_uri *uri;
-+
-+ /* dry-run parse to make sure the URI is valid and to report errors */
-+ uri = pkcs11_uri_init();
-+ if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
-+ fatal("Failed to parse PKCS#11 URI");
-+ if (uri->pin != NULL) {
-+ pin = strdup(uri->pin);
-+ if (pin == NULL) {
-+ fatal("Failed to dupplicate string");
-+ }
-+ /* pin is freed in the update_card() */
-+ }
-+ pkcs11_uri_cleanup(uri);
-+
-+ return update_card(agent_fd, adding, pkcs11_uri, qflag, 1, 0,
-+ dest_constraints, ndest_constraints, NULL, 0, pin);
-+}
-+#endif
-+
- static int
- add_file(int agent_fd, const char *filename, int key_only, int cert_only,
- int qflag, int Nflag, const char *skprovider,
-@@ -462,15 +495,14 @@ static int
- update_card(int agent_fd, int add, const char *id, int qflag,
- int key_only, int cert_only,
- struct dest_constraint **dest_constraints, size_t ndest_constraints,
-- struct sshkey **certs, size_t ncerts)
-+ struct sshkey **certs, size_t ncerts, char *pin)
- {
-- char *pin = NULL;
- int r, ret = -1;
-
- if (key_only)
- ncerts = 0;
-
-- if (add) {
-+ if (add && pin == NULL) {
- if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
- RP_ALLOW_STDIN)) == NULL)
- return -1;
-@@ -652,6 +684,14 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
- char *file, int qflag, int Nflag, const char *skprovider,
- struct dest_constraint **dest_constraints, size_t ndest_constraints)
- {
-+#ifdef ENABLE_PKCS11
-+ if (strlen(file) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(file, PKCS11_URI_SCHEME,
-+ strlen(PKCS11_URI_SCHEME)) == 0) {
-+ return update_pkcs11_uri(agent_fd, !deleting, file, qflag,
-+ dest_constraints, ndest_constraints);
-+ }
-+#endif
- if (deleting) {
- if (delete_file(agent_fd, file, key_only,
- cert_only, qflag) == -1)
-@@ -1003,7 +1043,7 @@ main(int argc, char **argv)
- if (update_card(agent_fd, !deleting, pkcs11provider,
- qflag, key_only, cert_only,
- dest_constraints, ndest_constraints,
-- certs, ncerts) == -1)
-+ certs, ncerts, NULL) == -1)
- ret = 1;
- for (n = 0; n < ncerts; n++)
- sshkey_free(certs[n]);
-diff --git a/ssh-agent.c b/ssh-agent.c
-index 7773a4b07..3451d7a14 100644
---- a/ssh-agent.c
-+++ b/ssh-agent.c
-@@ -1546,10 +1546,75 @@ add_p11_identity(struct sshkey *key, char *comment, const char *provider,
- idtab->nentries++;
- }
-
-+static char *
-+sanitize_pkcs11_provider(const char *provider)
-+{
-+ struct pkcs11_uri *uri = NULL;
-+ char *sane_uri, *module_path = NULL; /* default path */
-+ char canonical_provider[PATH_MAX];
-+
-+ if (provider == NULL)
-+ return NULL;
-+
-+ memset(canonical_provider, 0, sizeof(canonical_provider));
-+
-+ if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(provider, PKCS11_URI_SCHEME,
-+ strlen(PKCS11_URI_SCHEME)) == 0) {
-+ /* PKCS#11 URI */
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL) {
-+ error("Failed to init PKCS#11 URI");
-+ return NULL;
-+ }
-+
-+ if (pkcs11_uri_parse(provider, uri) != 0) {
-+ error("Failed to parse PKCS#11 URI");
-+ pkcs11_uri_cleanup(uri);
-+ return NULL;
-+ }
-+ /* validate also provider from URI */
-+ if (uri->module_path)
-+ module_path = strdup(uri->module_path);
-+ } else
-+ module_path = strdup(provider); /* simple path */
-+
-+ if (module_path != NULL) { /* do not validate default NULL path in URI */
-+ if (realpath(module_path, canonical_provider) == NULL) {
-+ verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
-+ module_path, strerror(errno));
-+ free(module_path);
-+ pkcs11_uri_cleanup(uri);
-+ return NULL;
-+ }
-+ free(module_path);
-+ if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
-+ verbose("refusing PKCS#11 provider \"%.100s\": "
-+ "not allowed", canonical_provider);
-+ pkcs11_uri_cleanup(uri);
-+ return NULL;
-+ }
-+
-+ /* copy verified and sanitized provider path back to the uri */
-+ if (uri) {
-+ free(uri->module_path);
-+ uri->module_path = xstrdup(canonical_provider);
-+ }
-+ }
-+
-+ if (uri) {
-+ sane_uri = pkcs11_uri_get(uri);
-+ pkcs11_uri_cleanup(uri);
-+ return sane_uri;
-+ } else {
-+ return xstrdup(canonical_provider); /* simple path */
-+ }
-+}
-+
- static void
- process_add_smartcard_key(SocketEntry *e)
- {
-- char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
-+ char *provider = NULL, *pin = NULL, *sane_uri = NULL;
- char **comments = NULL;
- int r, i, count = 0, success = 0, confirm = 0;
- u_int seconds = 0;
-@@ -1578,25 +1643,18 @@ process_add_smartcard_key(SocketEntry *e)
- "providers is disabled", provider);
- goto send;
- }
-- if (realpath(provider, canonical_provider) == NULL) {
-- verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
-- provider, strerror(errno));
-- goto send;
-- }
-- if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
-- verbose("refusing PKCS#11 add of \"%.100s\": "
-- "provider not allowed", canonical_provider);
-+ sane_uri = sanitize_pkcs11_provider(provider);
-+ if (sane_uri == NULL)
- goto send;
-- }
-- debug_f("add %.100s", canonical_provider);
- if (lifetime && !death)
- death = monotime() + lifetime;
-
-- count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
-+ debug_f("add %.100s", sane_uri);
-+ count = pkcs11_add_provider(sane_uri, pin, &keys, &comments);
- for (i = 0; i < count; i++) {
- if (comments[i] == NULL || comments[i][0] == '\0') {
- free(comments[i]);
-- comments[i] = xstrdup(canonical_provider);
-+ comments[i] = xstrdup(sane_uri);
- }
- for (j = 0; j < ncerts; j++) {
- if (!sshkey_is_cert(certs[j]))
-@@ -1606,13 +1664,13 @@ process_add_smartcard_key(SocketEntry *e)
- if (pkcs11_make_cert(keys[i], certs[j], &k) != 0)
- continue;
- add_p11_identity(k, xstrdup(comments[i]),
-- canonical_provider, death, confirm,
-+ sane_uri, death, confirm,
- dest_constraints, ndest_constraints);
- success = 1;
- }
- if (!cert_only && lookup_identity(keys[i]) == NULL) {
- add_p11_identity(keys[i], comments[i],
-- canonical_provider, death, confirm,
-+ sane_uri, death, confirm,
- dest_constraints, ndest_constraints);
- keys[i] = NULL; /* transferred */
- comments[i] = NULL; /* transferred */
-@@ -1625,6 +1683,7 @@ process_add_smartcard_key(SocketEntry *e)
- send:
- free(pin);
- free(provider);
-+ free(sane_uri);
- free(keys);
- free(comments);
- free_dest_constraints(dest_constraints, ndest_constraints);
-@@ -1637,8 +1696,8 @@ send:
- static void
- process_remove_smartcard_key(SocketEntry *e)
- {
-- char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
-- int r, success = 0;
-+ char *provider = NULL, *pin = NULL, *sane_uri = NULL;
-+ int r, success = 0, removed_count = 0;
- Identity *id, *nxt;
-
- debug2_f("entering");
-@@ -1649,30 +1708,38 @@ process_remove_smartcard_key(SocketEntry *e)
- }
- free(pin);
-
-- if (realpath(provider, canonical_provider) == NULL) {
-- verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
-- provider, strerror(errno));
-+ sane_uri = sanitize_pkcs11_provider(provider);
-+ if (sane_uri == NULL)
- goto send;
-- }
-
-- debug_f("remove %.100s", canonical_provider);
-+ debug_f("remove %.100s", sane_uri);
- for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) {
- nxt = TAILQ_NEXT(id, next);
- /* Skip file--based keys */
- if (id->provider == NULL)
- continue;
-- if (!strcmp(canonical_provider, id->provider)) {
-+ if (!strcmp(sane_uri, id->provider)) {
- TAILQ_REMOVE(&idtab->idlist, id, next);
- free_identity(id);
- idtab->nentries--;
-+ removed_count++;
- }
- }
-- if (pkcs11_del_provider(canonical_provider) == 0)
-+ /* Only succeed if we actually removed keys and unloaded provider */
-+ if (removed_count > 0 && pkcs11_del_provider(sane_uri) == 0) {
- success = 1;
-- else
-+ debug_f("removed %d key(s) from provider %s",
-+ removed_count, sane_uri);
-+ } else if (removed_count == 0) {
-+ error_f("no matching keys found for provider %s", sane_uri);
-+ /* Still try to clean up provider if it exists */
-+ pkcs11_del_provider(sane_uri);
-+ } else {
- error_f("pkcs11_del_provider failed");
-+ }
- send:
- free(provider);
-+ free(sane_uri);
- send_status(e, success);
- }
- #endif /* ENABLE_PKCS11 */
-diff --git a/ssh-keygen.c b/ssh-keygen.c
-index 12430e6a4..266462f07 100644
---- a/ssh-keygen.c
-+++ b/ssh-keygen.c
-@@ -831,8 +831,11 @@ do_download(struct passwd *pw)
- free(fp);
- } else {
- (void) sshkey_write(keys[i], stdout); /* XXX check */
-- fprintf(stdout, "%s%s\n",
-- *(comments[i]) == '\0' ? "" : " ", comments[i]);
-+ if (*(comments[i]) != '\0') {
-+ fprintf(stdout, " %s", comments[i]);
-+ }
-+ (void) pkcs11_uri_write(keys[i], stdout);
-+ fprintf(stdout, "\n");
- }
- free(comments[i]);
- sshkey_free(keys[i]);
-diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
-index 30a4cf5dc..3fd058a0c 100644
---- a/ssh-pkcs11-client.c
-+++ b/ssh-pkcs11-client.c
-@@ -18,6 +18,7 @@
-
- #include "includes.h"
-
-+#ifdef ENABLE_PKCS11
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/socket.h>
-@@ -38,6 +39,7 @@
- #include "authfd.h"
- #include "atomicio.h"
- #include "ssh-pkcs11.h"
-+#include "ssh-pkcs11-uri.h"
- #include "ssherr.h"
-
- /* borrows code from sftp-server and ssh-agent */
-@@ -379,6 +381,19 @@ pkcs11_start_helper(const char *path)
- return helper;
- }
-
-+int
-+pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin, struct sshkey ***keyp, char ***labelsp)
-+{
-+ int nkeys = 0;
-+ char *provider_uri = pkcs11_uri_get(uri);
-+
-+ debug_f("called, provider_uri = %s", provider_uri);
-+
-+ nkeys = pkcs11_add_provider(provider_uri, pin, keyp, labelsp);
-+
-+ return nkeys;
-+}
-+
- int
- pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- char ***labelsp)
-@@ -390,6 +405,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- struct sshbuf *msg;
- struct helper *helper;
-
-+ debug_f("called, name = %s", name);
-+
- if ((helper = helper_by_provider(name)) == NULL &&
- (helper = pkcs11_start_helper(name)) == NULL)
- return -1;
-@@ -414,6 +431,7 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- *keysp = xcalloc(nkeys, sizeof(struct sshkey *));
- if (labelsp)
- *labelsp = xcalloc(nkeys, sizeof(char *));
-+ debug_f("nkeys = %u", nkeys);
- for (i = 0; i < nkeys; i++) {
- /* XXX clean up properly instead of fatal() */
- if ((r = sshkey_froms(msg, &k)) != 0 ||
-@@ -497,3 +515,4 @@ pkcs11_key_free(struct sshkey *key)
- if (helper->nkeyblobs == 0)
- helper_terminate(helper);
- }
-+#endif
-diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
-new file mode 100644
-index 000000000..d5b074592
---- /dev/null
-+++ b/ssh-pkcs11-uri.c
-@@ -0,0 +1,438 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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 ENABLE_PKCS11
-+
-+#include <stdio.h>
-+#include <string.h>
-+#include <stdlib.h>
-+
-+#include "sshkey.h"
-+#include "sshbuf.h"
-+#include "log.h"
-+
-+#define CRYPTOKI_COMPAT
-+#include "pkcs11.h"
-+
-+#include "ssh-pkcs11-uri.h"
-+
-+#define PKCS11_URI_PATH_SEPARATOR ";"
-+#define PKCS11_URI_QUERY_SEPARATOR "&"
-+#define PKCS11_URI_VALUE_SEPARATOR "="
-+#define PKCS11_URI_ID "id"
-+#define PKCS11_URI_TOKEN "token"
-+#define PKCS11_URI_OBJECT "object"
-+#define PKCS11_URI_LIB_MANUF "library-manufacturer"
-+#define PKCS11_URI_MANUF "manufacturer"
-+#define PKCS11_URI_SERIAL "serial"
-+#define PKCS11_URI_MODULE_PATH "module-path"
-+#define PKCS11_URI_PIN_VALUE "pin-value"
-+
-+/* Keyword tokens. */
-+typedef enum {
-+ pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pSerial,
-+ pModulePath, pPinValue, pBadOption
-+} pkcs11uriOpCodes;
-+
-+/* Textual representation of the tokens. */
-+static struct {
-+ const char *name;
-+ pkcs11uriOpCodes opcode;
-+} keywords[] = {
-+ { PKCS11_URI_ID, pId },
-+ { PKCS11_URI_TOKEN, pToken },
-+ { PKCS11_URI_OBJECT, pObject },
-+ { PKCS11_URI_LIB_MANUF, pLibraryManufacturer },
-+ { PKCS11_URI_MANUF, pManufacturer },
-+ { PKCS11_URI_SERIAL, pSerial },
-+ { PKCS11_URI_MODULE_PATH, pModulePath },
-+ { PKCS11_URI_PIN_VALUE, pPinValue },
-+ { NULL, pBadOption }
-+};
-+
-+static pkcs11uriOpCodes
-+parse_token(const char *cp)
-+{
-+ u_int i;
-+
-+ for (i = 0; keywords[i].name; i++)
-+ if (strncasecmp(cp, keywords[i].name,
-+ strlen(keywords[i].name)) == 0)
-+ return keywords[i].opcode;
-+
-+ return pBadOption;
-+}
-+
-+int
-+percent_decode(char *data, char **outp)
-+{
-+ char tmp[3];
-+ char *out, *tmp_end;
-+ char *p = data;
-+ long value;
-+ size_t outlen = 0;
-+
-+ out = malloc(strlen(data)+1); /* upper bound */
-+ if (out == NULL)
-+ return -1;
-+ while (*p != '\0') {
-+ switch (*p) {
-+ case '%':
-+ p++;
-+ if (*p == '\0')
-+ goto fail;
-+ tmp[0] = *p++;
-+ if (*p == '\0')
-+ goto fail;
-+ tmp[1] = *p++;
-+ tmp[2] = '\0';
-+ tmp_end = NULL;
-+ value = strtol(tmp, &tmp_end, 16);
-+ if (tmp_end != tmp+2)
-+ goto fail;
-+ else
-+ out[outlen++] = (char) value;
-+ break;
-+ default:
-+ out[outlen++] = *p++;
-+ break;
-+ }
-+ }
-+
-+ /* zero terminate */
-+ out[outlen] = '\0';
-+ *outp = out;
-+ return outlen;
-+fail:
-+ free(out);
-+ return -1;
-+}
-+
-+struct sshbuf *
-+percent_encode(const char *data, size_t length, const char *allow_list)
-+{
-+ struct sshbuf *b = NULL;
-+ char tmp[4], *cp;
-+ size_t i;
-+
-+ if ((b = sshbuf_new()) == NULL)
-+ return NULL;
-+ for (i = 0; i < length; i++) {
-+ cp = strchr(allow_list, data[i]);
-+ /* if c is specified as '\0' pointer to terminator is returned !! */
-+ if (cp != NULL && *cp != '\0') {
-+ if (sshbuf_put(b, &data[i], 1) != 0)
-+ goto err;
-+ } else
-+ if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3
-+ || sshbuf_put(b, tmp, 3) != 0)
-+ goto err;
-+ }
-+ if (sshbuf_put(b, "\0", 1) == 0)
-+ return b;
-+err:
-+ sshbuf_free(b);
-+ return NULL;
-+}
-+
-+char *
-+pkcs11_uri_append(char *part, const char *separator, const char *key,
-+ struct sshbuf *value)
-+{
-+ char *new_part;
-+ size_t size = 0;
-+
-+ if (value == NULL)
-+ return NULL;
-+
-+ size = asprintf(&new_part,
-+ "%s%s%s" PKCS11_URI_VALUE_SEPARATOR "%s",
-+ (part != NULL ? part : ""),
-+ (part != NULL ? separator : ""),
-+ key, sshbuf_ptr(value));
-+ sshbuf_free(value);
-+ free(part);
-+
-+ if (size <= 0)
-+ return NULL;
-+ return new_part;
-+}
-+
-+char *
-+pkcs11_uri_get(struct pkcs11_uri *uri)
-+{
-+ size_t size = 0;
-+ char *p = NULL, *path = NULL, *query = NULL;
-+
-+ /* compose a percent-encoded ID */
-+ if (uri->id_len > 0) {
-+ struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, "");
-+ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+ PKCS11_URI_ID, key_id);
-+ if (path == NULL)
-+ goto err;
-+ }
-+
-+ /* Write object label */
-+ if (uri->object) {
-+ struct sshbuf *label = percent_encode(uri->object, strlen(uri->object),
-+ PKCS11_URI_WHITELIST);
-+ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+ PKCS11_URI_OBJECT, label);
-+ if (path == NULL)
-+ goto err;
-+ }
-+
-+ /* Write token label */
-+ if (uri->token) {
-+ struct sshbuf *label = percent_encode(uri->token, strlen(uri->token),
-+ PKCS11_URI_WHITELIST);
-+ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+ PKCS11_URI_TOKEN, label);
-+ if (path == NULL)
-+ goto err;
-+ }
-+
-+ /* Write manufacturer */
-+ if (uri->manuf) {
-+ struct sshbuf *manuf = percent_encode(uri->manuf,
-+ strlen(uri->manuf), PKCS11_URI_WHITELIST);
-+ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+ PKCS11_URI_MANUF, manuf);
-+ if (path == NULL)
-+ goto err;
-+ }
-+
-+ /* Write serial */
-+ if (uri->serial) {
-+ struct sshbuf *serial = percent_encode(uri->serial,
-+ strlen(uri->serial), PKCS11_URI_WHITELIST);
-+ path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+ PKCS11_URI_SERIAL, serial);
-+ if (path == NULL)
-+ goto err;
-+ }
-+
-+ /* Write module_path */
-+ if (uri->module_path) {
-+ struct sshbuf *module = percent_encode(uri->module_path,
-+ strlen(uri->module_path), PKCS11_URI_WHITELIST "/");
-+ query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR,
-+ PKCS11_URI_MODULE_PATH, module);
-+ if (query == NULL)
-+ goto err;
-+ }
-+
-+ size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s",
-+ path != NULL ? path : "",
-+ query != NULL ? "?" : "",
-+ query != NULL ? query : "");
-+err:
-+ free(query);
-+ free(path);
-+ if (size <= 0)
-+ return NULL;
-+ return p;
-+}
-+
-+struct pkcs11_uri *
-+pkcs11_uri_init()
-+{
-+ struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri));
-+ return d;
-+}
-+
-+void
-+pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11)
-+{
-+ if (pkcs11 == NULL) {
-+ return;
-+ }
-+
-+ free(pkcs11->id);
-+ free(pkcs11->module_path);
-+ free(pkcs11->token);
-+ free(pkcs11->object);
-+ free(pkcs11->lib_manuf);
-+ free(pkcs11->manuf);
-+ free(pkcs11->serial);
-+ if (pkcs11->pin)
-+ freezero(pkcs11->pin, strlen(pkcs11->pin));
-+ free(pkcs11);
-+}
-+
-+int
-+pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11)
-+{
-+ char *saveptr1, *saveptr2, *str1, *str2, *tok;
-+ int rv = 0, len;
-+ char *p = NULL;
-+
-+ size_t scheme_len = strlen(PKCS11_URI_SCHEME);
-+ if (strlen(uri) < scheme_len || /* empty URI matches everything */
-+ strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) {
-+ error_f("The '%s' does not look like PKCS#11 URI", uri);
-+ return -1;
-+ }
-+
-+ if (pkcs11 == NULL) {
-+ error_f("Bad arguments. The pkcs11 can't be null");
-+ return -1;
-+ }
-+
-+ /* skip URI schema name */
-+ p = strdup(uri);
-+ str1 = p;
-+
-+ /* everything before ? */
-+ tok = strtok_r(str1, "?", &saveptr1);
-+ if (tok == NULL) {
-+ error_f("pk11-path expected, got EOF");
-+ rv = -1;
-+ goto out;
-+ }
-+
-+ /* skip URI schema name:
-+ * the scheme ensures that there is at least something before "?"
-+ * allowing empty pk11-path. Resulting token at worst pointing to
-+ * \0 byte */
-+ tok = tok + scheme_len;
-+
-+ /* parse pk11-path */
-+ for (str2 = tok; ; str2 = NULL) {
-+ char **charptr, *arg = NULL;
-+ pkcs11uriOpCodes opcode;
-+ tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2);
-+ if (tok == NULL)
-+ break;
-+ opcode = parse_token(tok);
-+ if (opcode != pBadOption)
-+ arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
-+
-+ switch (opcode) {
-+ case pId:
-+ /* CKA_ID */
-+ if (pkcs11->id != NULL) {
-+ verbose_f("The id already set in the PKCS#11 URI");
-+ rv = -1;
-+ goto out;
-+ }
-+ len = percent_decode(arg, &pkcs11->id);
-+ if (len <= 0) {
-+ verbose_f("Failed to percent-decode CKA_ID: %s", arg);
-+ rv = -1;
-+ goto out;
-+ } else
-+ pkcs11->id_len = len;
-+ debug3_f("Setting CKA_ID = %s from PKCS#11 URI", arg);
-+ break;
-+ case pToken:
-+ /* CK_TOKEN_INFO -> label */
-+ charptr = &pkcs11->token;
-+ parse_string:
-+ if (*charptr != NULL) {
-+ verbose_f("The %s already set in the PKCS#11 URI",
-+ keywords[opcode].name);
-+ rv = -1;
-+ goto out;
-+ }
-+ percent_decode(arg, charptr);
-+ debug3_f("Setting %s = %s from PKCS#11 URI",
-+ keywords[opcode].name, *charptr);
-+ break;
-+
-+ case pObject:
-+ /* CK_TOKEN_INFO -> manufacturerID */
-+ charptr = &pkcs11->object;
-+ goto parse_string;
-+
-+ case pManufacturer:
-+ /* CK_TOKEN_INFO -> manufacturerID */
-+ charptr = &pkcs11->manuf;
-+ goto parse_string;
-+
-+ case pSerial:
-+ /* CK_TOKEN_INFO -> serialNumber */
-+ charptr = &pkcs11->serial;
-+ goto parse_string;
-+
-+ case pLibraryManufacturer:
-+ /* CK_INFO -> manufacturerID */
-+ charptr = &pkcs11->lib_manuf;
-+ goto parse_string;
-+
-+ default:
-+ /* Unrecognized attribute in the URI path SHOULD be error */
-+ verbose_f("Unknown part of path in PKCS#11 URI: %s", tok);
-+ }
-+ }
-+
-+ tok = strtok_r(NULL, "?", &saveptr1);
-+ if (tok == NULL) {
-+ goto out;
-+ }
-+ /* parse pk11-query (optional) */
-+ for (str2 = tok; ; str2 = NULL) {
-+ char *arg;
-+ pkcs11uriOpCodes opcode;
-+ tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2);
-+ if (tok == NULL)
-+ break;
-+ opcode = parse_token(tok);
-+ if (opcode != pBadOption)
-+ arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
-+
-+ switch (opcode) {
-+ case pModulePath:
-+ /* module-path is PKCS11Provider */
-+ if (pkcs11->module_path != NULL) {
-+ verbose_f("Multiple module-path attributes are"
-+ "not supported the PKCS#11 URI");
-+ rv = -1;
-+ goto out;
-+ }
-+ percent_decode(arg, &pkcs11->module_path);
-+ debug3_f("Setting PKCS11Provider = %s from PKCS#11 URI",
-+ pkcs11->module_path);
-+ break;
-+
-+ case pPinValue:
-+ /* pin-value */
-+ if (pkcs11->pin != NULL) {
-+ verbose_f("Multiple pin-value attributes are"
-+ "not supported the PKCS#11 URI");
-+ rv = -1;
-+ goto out;
-+ }
-+ percent_decode(arg, &pkcs11->pin);
-+ debug3_f("Setting PIN from PKCS#11 URI");
-+ break;
-+
-+ default:
-+ /* Unrecognized attribute in the URI query SHOULD be ignored */
-+ verbose_f("Unknown part of query in PKCS#11 URI: %s", tok);
-+ }
-+ }
-+out:
-+ free(p);
-+ return rv;
-+}
-+
-+#endif /* ENABLE_PKCS11 */
-diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h
-new file mode 100644
-index 000000000..bc758e760
---- /dev/null
-+++ b/ssh-pkcs11-uri.h
-@@ -0,0 +1,46 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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 _SSH_PKCS11_URI_H
-+#define _SSH_PKCS11_URI_H
-+
-+#define PKCS11_URI_SCHEME "pkcs11:"
-+#define PKCS11_URI_WHITELIST "abcdefghijklmnopqrstuvwxyz" \
-+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
-+ "0123456789_-.()"
-+
-+struct pkcs11_uri {
-+ /* path */
-+ char *id;
-+ size_t id_len;
-+ char *token;
-+ char *object;
-+ char *lib_manuf;
-+ char *manuf;
-+ char *serial;
-+ /* query */
-+ char *module_path;
-+ char *pin; /* Only parsed, but not printed */
-+};
-+
-+struct pkcs11_uri *pkcs11_uri_init();
-+void pkcs11_uri_cleanup(struct pkcs11_uri *);
-+int pkcs11_uri_parse(const char *, struct pkcs11_uri *);
-+struct pkcs11_uri *pkcs11_uri_init();
-+char *pkcs11_uri_get(struct pkcs11_uri *uri);
-+#endif /* _SSH_PKCS11_URI_H */
-diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
-index 7a7d3b8ea..6f23fc38b 100644
---- a/ssh-pkcs11.c
-+++ b/ssh-pkcs11.c
-@@ -36,6 +36,7 @@
- #include <openssl/ecdsa.h>
- #include <openssl/x509.h>
- #include <openssl/err.h>
-+#include <openssl/evp.h>
- #endif
-
- #define CRYPTOKI_COMPAT
-@@ -48,6 +49,7 @@
- #include "misc.h"
- #include "sshbuf.h"
- #include "ssh-pkcs11.h"
-+#include "ssh-pkcs11-uri.h"
- #include "digest.h"
- #include "xmalloc.h"
- #include "crypto_api.h"
-@@ -58,8 +60,8 @@ struct pkcs11_slotinfo {
- int logged_in;
- };
-
--struct pkcs11_provider {
-- char *name;
-+struct pkcs11_module {
-+ char *module_path;
- void *handle;
- CK_FUNCTION_LIST *function_list;
- CK_INFO info;
-@@ -68,6 +70,13 @@ struct pkcs11_provider {
- struct pkcs11_slotinfo *slotinfo;
- int valid;
- int refcount;
-+};
-+
-+struct pkcs11_provider {
-+ char *name;
-+ struct pkcs11_module *module; /* can be shared between various providers */
-+ int refcount;
-+ int valid;
- TAILQ_ENTRY(pkcs11_provider) next;
- };
-
-@@ -79,6 +88,7 @@ struct pkcs11_key {
- CK_ULONG slotidx;
- char *keyid;
- int keyid_len;
-+ char *label;
- TAILQ_ENTRY(pkcs11_key) next;
- };
-
-@@ -105,26 +115,61 @@ ossl_error(const char *msg)
- * this is called when a provider gets unregistered.
- */
- static void
--pkcs11_provider_finalize(struct pkcs11_provider *p)
-+pkcs11_module_finalize(struct pkcs11_module *m)
- {
- CK_RV rv;
- CK_ULONG i;
-
-- debug_f("provider \"%s\" refcount %d valid %d",
-- p->name, p->refcount, p->valid);
-- if (!p->valid)
-+ debug_f("%p refcount %d valid %d", m, m->refcount, m->valid);
-+ if (!m->valid)
- return;
-- for (i = 0; i < p->nslots; i++) {
-- if (p->slotinfo[i].session &&
-- (rv = p->function_list->C_CloseSession(
-- p->slotinfo[i].session)) != CKR_OK)
-+ for (i = 0; i < m->nslots; i++) {
-+ if (m->slotinfo[i].session &&
-+ (rv = m->function_list->C_CloseSession(
-+ m->slotinfo[i].session)) != CKR_OK)
- error("C_CloseSession failed: %lu", rv);
- }
-- if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK)
-+ if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK)
- error("C_Finalize failed: %lu", rv);
-+ m->valid = 0;
-+ m->function_list = NULL;
-+ dlclose(m->handle);
-+}
-+
-+/*
-+ * remove a reference to the pkcs11 module.
-+ * called when a provider is unregistered.
-+ */
-+static void
-+pkcs11_module_unref(struct pkcs11_module *m)
-+{
-+ debug_f("%p refcount %d", m, m->refcount);
-+ if (--m->refcount <= 0) {
-+ pkcs11_module_finalize(m);
-+ if (m->valid)
-+ error_f("%p still valid", m);
-+ free(m->slotlist);
-+ free(m->slotinfo);
-+ free(m->module_path);
-+ free(m);
-+ }
-+}
-+
-+/*
-+ * finalize a provider shared library, it's no longer usable.
-+ * however, there might still be keys referencing this provider,
-+ * so the actual freeing of memory is handled by pkcs11_provider_unref().
-+ * this is called when a provider gets unregistered.
-+ */
-+static void
-+pkcs11_provider_finalize(struct pkcs11_provider *p)
-+{
-+ debug_f("%p refcount %d valid %d", p, p->refcount, p->valid);
-+ if (!p->valid)
-+ return;
-+ pkcs11_module_unref(p->module);
-+ p->module = NULL;
- p->valid = 0;
-- p->function_list = NULL;
-- dlclose(p->handle);
- }
-
- /*
-@@ -136,15 +181,27 @@ pkcs11_provider_unref(struct pkcs11_provider *p)
- {
- debug_f("provider \"%s\" refcount %d", p->name, p->refcount);
- if (--p->refcount <= 0) {
-- if (p->valid)
-- error_f("provider \"%s\" still valid", p->name);
- free(p->name);
-- free(p->slotlist);
-- free(p->slotinfo);
-+ if (p->module)
-+ pkcs11_module_unref(p->module);
- free(p);
- }
- }
-
-+/* lookup provider by module path */
-+static struct pkcs11_module *
-+pkcs11_provider_lookup_module(char *module_path)
-+{
-+ struct pkcs11_provider *p;
-+
-+ TAILQ_FOREACH(p, &pkcs11_providers, next) {
-+ debug("check %p %s (%s)", p, p->name, p->module->module_path);
-+ if (!strcmp(module_path, p->module->module_path))
-+ return (p->module);
-+ }
-+ return (NULL);
-+}
-+
- /* lookup provider by name */
- static struct pkcs11_provider *
- pkcs11_provider_lookup(char *provider_id)
-@@ -159,19 +216,55 @@ pkcs11_provider_lookup(char *provider_id)
- return (NULL);
- }
-
-+int pkcs11_del_provider_by_uri(struct pkcs11_uri *);
-+
- /* unregister provider by name */
- int
- pkcs11_del_provider(char *provider_id)
-+{
-+ int rv;
-+ struct pkcs11_uri *uri;
-+
-+ debug_f("called, provider_id = %s", provider_id);
-+
-+ if (provider_id == NULL)
-+ return 0;
-+
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL)
-+ fatal("Failed to init PKCS#11 URI");
-+
-+ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+ if (pkcs11_uri_parse(provider_id, uri) != 0)
-+ fatal("Failed to parse PKCS#11 URI");
-+ } else {
-+ uri->module_path = strdup(provider_id);
-+ }
-+
-+ rv = pkcs11_del_provider_by_uri(uri);
-+ pkcs11_uri_cleanup(uri);
-+ return rv;
-+}
-+
-+/* unregister provider by PKCS#11 URI */
-+int
-+pkcs11_del_provider_by_uri(struct pkcs11_uri *uri)
- {
- struct pkcs11_provider *p;
-+ int rv = -1;
-+ char *provider_uri = pkcs11_uri_get(uri);
-
-- if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
-+ debug3_f("called with provider %s", provider_uri);
-+
-+ if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) {
- TAILQ_REMOVE(&pkcs11_providers, p, next);
- pkcs11_provider_finalize(p);
- pkcs11_provider_unref(p);
-- return (0);
-+ rv = 0;
- }
-- return (-1);
-+ free(provider_uri);
-+ return rv;
- }
-
- /* release a wrapped object */
-@@ -183,6 +276,7 @@ pkcs11_k11_free(struct pkcs11_key *k11)
- if (k11->provider)
- pkcs11_provider_unref(k11->provider);
- free(k11->keyid);
-+ free(k11->label);
- sshbuf_free(k11->keyblob);
- free(k11);
- }
-@@ -198,8 +292,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr,
- CK_RV rv;
- int ret = -1;
-
-- f = p->function_list;
-- session = p->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-+ session = p->module->slotinfo[slotidx].session;
- if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) {
- error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv);
- return (-1);
-@@ -236,14 +330,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
- if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH)
- verbose("Deferring PIN entry to reader keypad.");
- else {
-- snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ",
-+ snprintf(prompt, sizeof(prompt), "Enter PIN for '%.32s': ",
- si->token.label);
-- if ((pin = read_passphrase(prompt, RP_ALLOW_EOF)) == NULL) {
-+ if ((pin = read_passphrase(prompt, RP_ALLOW_EOF|RP_ALLOW_STDIN)) == NULL) {
- debug_f("no pin specified");
- return (-1); /* bail out */
- }
- }
-- rv = provider->function_list->C_Login(si->session, type, (u_char *)pin,
-+ rv = provider->module->function_list->C_Login(si->session, type, (u_char *)pin,
- (pin != NULL) ? strlen(pin) : 0);
- if (pin != NULL)
- freezero(pin, strlen(pin));
-@@ -273,13 +367,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
- static int
- pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type)
- {
-- if (k11 == NULL || k11->provider == NULL || !k11->provider->valid) {
-+ if (k11 == NULL || k11->provider == NULL || !k11->provider->valid ||
-+ k11->provider->module == NULL || !k11->provider->module->valid) {
- error("no pkcs11 (valid) provider found");
- return (-1);
- }
-
- return pkcs11_login_slot(k11->provider,
-- &k11->provider->slotinfo[k11->slotidx], type);
-+ &k11->provider->module->slotinfo[k11->slotidx], type);
- }
-
-
-@@ -295,13 +390,14 @@ pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj,
-
- *val = 0;
-
-- if (!k11->provider || !k11->provider->valid) {
-+ if (!k11->provider || !k11->provider->valid ||
-+ !k11->provider->module || !k11->provider->module->valid) {
- error("no pkcs11 (valid) provider found");
- return (-1);
- }
-
-- f = k11->provider->function_list;
-- si = &k11->provider->slotinfo[k11->slotidx];
-+ f = k11->provider->module->function_list;
-+ si = &k11->provider->module->slotinfo[k11->slotidx];
-
- attr.type = type;
- attr.pValue = &flag;
-@@ -332,13 +428,14 @@ pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type)
- int always_auth = 0;
- int did_login = 0;
-
-- if (!k11->provider || !k11->provider->valid) {
-+ if (!k11->provider || !k11->provider->valid ||
-+ !k11->provider->module || !k11->provider->module->valid) {
- error("no pkcs11 (valid) provider found");
- return (-1);
- }
-
-- f = k11->provider->function_list;
-- si = &k11->provider->slotinfo[k11->slotidx];
-+ f = k11->provider->module->function_list;
-+ si = &k11->provider->module->slotinfo[k11->slotidx];
-
- if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) {
- if (pkcs11_login(k11, CKU_USER) < 0) {
-@@ -437,6 +534,12 @@ pkcs11_record_key(struct pkcs11_provider *provider, CK_ULONG slotidx,
- k11->keyid = xmalloc(k11->keyid_len);
- memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
- }
-+ if (keyid_attrib->ulValueLen > 0 ) {
-+ k11->label = xmalloc(keyid_attrib->ulValueLen+1);
-+ memcpy(k11->label, keyid_attrib->pValue, keyid_attrib->ulValueLen);
-+ k11->label[keyid_attrib->ulValueLen] = 0;
-+ }
-+
- TAILQ_INSERT_TAIL(&pkcs11_keys, k11, next);
-
- return 0;
-@@ -464,6 +567,42 @@ pkcs11_lookup_key(struct sshkey *key)
- return found;
- }
-
-+/*
-+ * This can't be in the ssh-pkcs11-uri, becase we can not depend on
-+ * PKCS#11 structures in ssh-agent (using client-helper communication)
-+ */
-+int
-+pkcs11_uri_write(const struct sshkey *key, FILE *f)
-+{
-+ char *p = NULL;
-+ struct pkcs11_uri uri;
-+ struct pkcs11_key *k11 = pkcs11_lookup_key(key);
-+
-+ if (k11 == NULL) {
-+ error("Failed to get ex_data for key type %d", key->type);
-+ return (-1);
-+ }
-+
-+ /* omit type -- we are looking for private-public or private-certificate pairs */
-+ uri.id = k11->keyid;
-+ uri.id_len = k11->keyid_len;
-+ uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label;
-+ uri.object = k11->label;
-+ uri.module_path = k11->provider->module->module_path;
-+ uri.lib_manuf = k11->provider->module->info.manufacturerID;
-+ uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID;
-+ uri.serial = k11->provider->module->slotinfo[k11->slotidx].token.serialNumber;
-+
-+ p = pkcs11_uri_get(&uri);
-+ /* do not cleanup -- we do not allocate here, only reference */
-+ if (p == NULL)
-+ return -1;
-+
-+ fprintf(f, " %s", p);
-+ free(p);
-+ return 0;
-+}
-+
- #ifdef WITH_OPENSSL
- /*
- * See:
-@@ -568,8 +707,8 @@ pkcs11_sign_rsa(struct sshkey *key,
- return SSH_ERR_AGENT_FAILURE;
- }
-
-- f = k11->provider->function_list;
-- si = &k11->provider->slotinfo[k11->slotidx];
-+ f = k11->provider->module->function_list;
-+ si = &k11->provider->module->slotinfo[k11->slotidx];
-
- if ((siglen = EVP_PKEY_size(key->pkey)) <= 0)
- return SSH_ERR_INVALID_ARGUMENT;
-@@ -658,8 +797,8 @@ pkcs11_sign_ecdsa(struct sshkey *key,
- debug3_f("sign using provider %s slotidx %lu",
- k11->provider->name, (u_long)k11->slotidx);
-
-- f = k11->provider->function_list;
-- si = &k11->provider->slotinfo[k11->slotidx];
-+ f = k11->provider->module->function_list;
-+ si = &k11->provider->module->slotinfo[k11->slotidx];
-
- /* Prepare digest to be signed */
- if ((hashalg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
-@@ -743,8 +882,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
- debug3_f("sign using provider %s slotidx %lu",
- k11->provider->name, (u_long)k11->slotidx);
-
-- f = k11->provider->function_list;
-- si = &k11->provider->slotinfo[k11->slotidx];
-+ f = k11->provider->module->function_list;
-+ si = &k11->provider->module->slotinfo[k11->slotidx];
-
- xdata = xmalloc(datalen);
- memcpy(xdata, data, datalen);
-@@ -772,7 +911,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
- return ret;
- }
-
--/* remove trailing spaces */
-+/* remove trailing spaces. Note, that this does NOT guarantee the buffer
-+ * will be null terminated if there are no trailing spaces! */
- static char *
- rmspace(u_char *buf, size_t len)
- {
-@@ -804,8 +944,8 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
- CK_SESSION_HANDLE session;
- int login_required, ret;
-
-- f = p->function_list;
-- si = &p->slotinfo[slotidx];
-+ f = p->module->function_list;
-+ si = &p->module->slotinfo[slotidx];
-
- login_required = si->token.flags & CKF_LOGIN_REQUIRED;
-
-@@ -815,9 +955,9 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
- error("pin required");
- return (-SSH_PKCS11_ERR_PIN_REQUIRED);
- }
-- if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION|
-+ if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION|
- CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) {
-- error("C_OpenSession failed: %lu", rv);
-+ error("C_OpenSession failed for slot %lu: %lu", slotidx, rv);
- return (-1);
- }
- if (login_required && pin != NULL && strlen(pin) != 0) {
-@@ -854,7 +994,8 @@ static struct sshkey *
- pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- CK_OBJECT_HANDLE *obj)
- {
-- CK_ATTRIBUTE key_attr[3];
-+ CK_ATTRIBUTE key_attr[4];
-+ int nattr = 4;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -867,14 +1008,15 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-
- memset(&key_attr, 0, sizeof(key_attr));
- key_attr[0].type = CKA_ID;
-- key_attr[1].type = CKA_EC_POINT;
-- key_attr[2].type = CKA_EC_PARAMS;
-+ key_attr[1].type = CKA_LABEL;
-+ key_attr[2].type = CKA_EC_POINT;
-+ key_attr[3].type = CKA_EC_PARAMS;
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
- /* figure out size of the attributes */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- return (NULL);
-@@ -885,19 +1027,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- * ensure that none of the others are zero length.
- * XXX assumes CKA_ID is always first.
- */
-- if (key_attr[1].ulValueLen == 0 ||
-- key_attr[2].ulValueLen == 0) {
-+ if (key_attr[2].ulValueLen == 0 ||
-+ key_attr[3].ulValueLen == 0) {
- error("invalid attribute length");
- return (NULL);
- }
-
- /* allocate buffers for attributes */
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- if (key_attr[i].ulValueLen > 0)
- key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
-
- /* retrieve ID, public point and curve parameters of EC key */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- goto fail;
-@@ -909,8 +1051,8 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- goto fail;
- }
-
-- attrp = key_attr[2].pValue;
-- group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen);
-+ attrp = key_attr[3].pValue;
-+ group = d2i_ECPKParameters(NULL, &attrp, key_attr[3].ulValueLen);
- if (group == NULL) {
- ossl_error("d2i_ECPKParameters failed");
- goto fail;
-@@ -921,13 +1063,13 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- goto fail;
- }
-
-- if (key_attr[1].ulValueLen <= 2) {
-+ if (key_attr[2].ulValueLen <= 2) {
- error("CKA_EC_POINT too small");
- goto fail;
- }
-
-- attrp = key_attr[1].pValue;
-- octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen);
-+ attrp = key_attr[2].pValue;
-+ octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[2].ulValueLen);
- if (octet == NULL) {
- ossl_error("d2i_ASN1_OCTET_STRING failed");
- goto fail;
-@@ -989,7 +1131,8 @@ static struct sshkey *
- pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- CK_OBJECT_HANDLE *obj)
- {
-- CK_ATTRIBUTE key_attr[3];
-+ CK_ATTRIBUTE key_attr[4];
-+ int nattr = 4;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -1000,14 +1143,15 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-
- memset(&key_attr, 0, sizeof(key_attr));
- key_attr[0].type = CKA_ID;
-- key_attr[1].type = CKA_MODULUS;
-- key_attr[2].type = CKA_PUBLIC_EXPONENT;
-+ key_attr[1].type = CKA_LABEL;
-+ key_attr[2].type = CKA_MODULUS;
-+ key_attr[3].type = CKA_PUBLIC_EXPONENT;
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
- /* figure out size of the attributes */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- return (NULL);
-@@ -1018,19 +1162,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- * ensure that none of the others are zero length.
- * XXX assumes CKA_ID is always first.
- */
-- if (key_attr[1].ulValueLen == 0 ||
-- key_attr[2].ulValueLen == 0) {
-+ if (key_attr[2].ulValueLen == 0 ||
-+ key_attr[3].ulValueLen == 0) {
- error("invalid attribute length");
- return (NULL);
- }
-
- /* allocate buffers for attributes */
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- if (key_attr[i].ulValueLen > 0)
- key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
-
- /* retrieve ID, modulus and public exponent of RSA key */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- goto fail;
-@@ -1042,8 +1186,8 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- goto fail;
- }
-
-- rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL);
-- rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
-+ rsa_n = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
-+ rsa_e = BN_bin2bn(key_attr[3].pValue, key_attr[3].ulValueLen, NULL);
- if (rsa_n == NULL || rsa_e == NULL) {
- error("BN_bin2bn failed");
- goto fail;
-@@ -1075,7 +1219,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- /* success */
- success = 0;
- fail:
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- free(key_attr[i].pValue);
- RSA_free(rsa);
- if (success != 0) {
-@@ -1090,7 +1234,8 @@ static struct sshkey *
- pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- CK_OBJECT_HANDLE *obj)
- {
-- CK_ATTRIBUTE key_attr[3];
-+ CK_ATTRIBUTE key_attr[4];
-+ int nattr = 4;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -1110,14 +1255,15 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-
- memset(&key_attr, 0, sizeof(key_attr));
- key_attr[0].type = CKA_ID;
-- key_attr[1].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
-- key_attr[2].type = CKA_EC_PARAMS;
-+ key_attr[1].type = CKA_LABEL;
-+ key_attr[2].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
-+ key_attr[3].type = CKA_EC_PARAMS;
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
- /* figure out size of the attributes */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- return (NULL);
-@@ -1128,28 +1274,28 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- * ensure that none of the others are zero length.
- * XXX assumes CKA_ID is always first.
- */
-- if (key_attr[1].ulValueLen == 0 ||
-- key_attr[2].ulValueLen == 0) {
-+ if (key_attr[2].ulValueLen == 0 ||
-+ key_attr[3].ulValueLen == 0) {
- error("invalid attribute length");
- return (NULL);
- }
-
- /* allocate buffers for attributes */
-- for (i = 0; i < 3; i++) {
-+ for (i = 0; i < nattr; i++) {
- if (key_attr[i].ulValueLen > 0)
- key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
- }
-
- /* retrieve ID, public point and curve parameters of EC key */
-- rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- goto fail;
- }
-
- /* Expect one of the supported identifiers in CKA_EC_PARAMS */
-- d = (u_char *)key_attr[2].pValue;
-- len = key_attr[2].ulValueLen;
-+ d = (u_char *)key_attr[3].pValue;
-+ len = key_attr[3].ulValueLen;
- if ((len != sizeof(id1) || memcmp(d, id1, sizeof(id1)) != 0) &&
- (len != sizeof(id2) || memcmp(d, id2, sizeof(id2)) != 0)) {
- hex = tohex(d, len);
-@@ -1161,16 +1307,16 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- * Expect either a raw 32 byte pubkey or an OCTET STRING with
- * a 32 byte pubkey in CKA_VALUE
- */
-- d = (u_char *)key_attr[1].pValue;
-- len = key_attr[1].ulValueLen;
-+ d = (u_char *)key_attr[2].pValue;
-+ len = key_attr[2].ulValueLen;
- if (len == ED25519_PK_SZ + 2 && d[0] == 0x04 && d[1] == ED25519_PK_SZ) {
- d += 2;
- len -= 2;
- }
- if (len != ED25519_PK_SZ) {
-- hex = tohex(key_attr[1].pValue, key_attr[1].ulValueLen);
-+ hex = tohex(key_attr[2].pValue, key_attr[2].ulValueLen);
- logit_f("CKA_EC_POINT invalid octet str: %s (len %lu)",
-- hex, (u_long)key_attr[1].ulValueLen);
-+ hex, (u_long)key_attr[2].ulValueLen);
- goto fail;
- }
-
-@@ -1190,7 +1336,7 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- key = NULL;
- }
- free(hex);
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- free(key_attr[i].pValue);
- return key;
- }
-@@ -1200,7 +1346,8 @@ static int
- pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- CK_OBJECT_HANDLE *obj, struct sshkey **keyp, char **labelp)
- {
-- CK_ATTRIBUTE cert_attr[3];
-+ CK_ATTRIBUTE cert_attr[4];
-+ int nattr = 4;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -1226,14 +1373,15 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-
- memset(&cert_attr, 0, sizeof(cert_attr));
- cert_attr[0].type = CKA_ID;
-- cert_attr[1].type = CKA_SUBJECT;
-- cert_attr[2].type = CKA_VALUE;
-+ cert_attr[1].type = CKA_LABEL;
-+ cert_attr[2].type = CKA_SUBJECT;
-+ cert_attr[3].type = CKA_VALUE;
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
- /* figure out size of the attributes */
-- rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- return -1;
-@@ -1245,18 +1393,19 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- * XXX assumes CKA_ID is always first.
- */
- if (cert_attr[1].ulValueLen == 0 ||
-- cert_attr[2].ulValueLen == 0) {
-+ cert_attr[2].ulValueLen == 0 ||
-+ cert_attr[3].ulValueLen == 0) {
- error("invalid attribute length");
- return -1;
- }
-
- /* allocate buffers for attributes */
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- if (cert_attr[i].ulValueLen > 0)
- cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen);
-
- /* retrieve ID, subject and value of certificate */
-- rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
-+ rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
- if (rv != CKR_OK) {
- error("C_GetAttributeValue failed: %lu", rv);
- goto out;
-@@ -1270,8 +1419,8 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- subject = xstrdup("invalid subject");
- X509_NAME_free(x509_name);
-
-- cp = cert_attr[2].pValue;
-- if ((x509 = d2i_X509(NULL, &cp, cert_attr[2].ulValueLen)) == NULL) {
-+ cp = cert_attr[3].pValue;
-+ if ((x509 = d2i_X509(NULL, &cp, cert_attr[3].ulValueLen)) == NULL) {
- error("d2i_x509 failed");
- goto out;
- }
-@@ -1381,7 +1530,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- goto out;
- }
- out:
-- for (i = 0; i < 3; i++)
-+ for (i = 0; i < nattr; i++)
- free(cert_attr[i].pValue);
- X509_free(x509);
- RSA_free(rsa);
-@@ -1424,11 +1573,12 @@ note_key(struct pkcs11_provider *p, CK_ULONG slotidx, const char *context,
- */
- static int
- pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
-- struct sshkey ***keysp, char ***labelsp, int *nkeys)
-+ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
- {
- struct sshkey *key = NULL;
- CK_OBJECT_CLASS key_class;
-- CK_ATTRIBUTE key_attr[1];
-+ CK_ATTRIBUTE key_attr[3];
-+ int nattr = 1;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -1445,10 +1595,23 @@ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
- key_attr[0].pValue = &key_class;
- key_attr[0].ulValueLen = sizeof(key_class);
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ if (uri->id != NULL) {
-+ key_attr[nattr].type = CKA_ID;
-+ key_attr[nattr].pValue = uri->id;
-+ key_attr[nattr].ulValueLen = uri->id_len;
-+ nattr++;
-+ }
-+ if (uri->object != NULL) {
-+ key_attr[nattr].type = CKA_LABEL;
-+ key_attr[nattr].pValue = uri->object;
-+ key_attr[nattr].ulValueLen = strlen(uri->object);
-+ nattr++;
-+ }
-+
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
-- rv = f->C_FindObjectsInit(session, key_attr, 1);
-+ rv = f->C_FindObjectsInit(session, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_FindObjectsInit failed: %lu", rv);
- goto fail;
-@@ -1521,6 +1684,13 @@ fail:
-
- return (ret);
- }
-+#else
-+static int
-+pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
-+ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
-+{
-+ return 0;
-+}
- #endif /* WITH_OPENSSL */
-
- /*
-@@ -1530,11 +1700,12 @@ fail:
- */
- static int
- pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
-- struct sshkey ***keysp, char ***labelsp, int *nkeys)
-+ struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
- {
- struct sshkey *key = NULL;
- CK_OBJECT_CLASS key_class;
-- CK_ATTRIBUTE key_attr[2];
-+ CK_ATTRIBUTE key_attr[3];
-+ int nattr = 1;
- CK_SESSION_HANDLE session;
- CK_FUNCTION_LIST *f = NULL;
- CK_RV rv;
-@@ -1550,10 +1721,23 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
- key_attr[0].pValue = &key_class;
- key_attr[0].ulValueLen = sizeof(key_class);
-
-- session = p->slotinfo[slotidx].session;
-- f = p->function_list;
-+ if (uri->id != NULL) {
-+ key_attr[nattr].type = CKA_ID;
-+ key_attr[nattr].pValue = uri->id;
-+ key_attr[nattr].ulValueLen = uri->id_len;
-+ nattr++;
-+ }
-+ if (uri->object != NULL) {
-+ key_attr[nattr].type = CKA_LABEL;
-+ key_attr[nattr].pValue = uri->object;
-+ key_attr[nattr].ulValueLen = strlen(uri->object);
-+ nattr++;
-+ }
-+
-+ session = p->module->slotinfo[slotidx].session;
-+ f = p->module->function_list;
-
-- rv = f->C_FindObjectsInit(session, key_attr, 1);
-+ rv = f->C_FindObjectsInit(session, key_attr, nattr);
- if (rv != CKR_OK) {
- error("C_FindObjectsInit failed: %lu", rv);
- goto fail;
-@@ -1841,16 +2025,10 @@ pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx,
- }
- #endif /* WITH_PKCS11_KEYGEN */
-
--/*
-- * register a new provider, fails if provider already exists. if
-- * keyp is provided, fetch keys.
-- */
- static int
--pkcs11_register_provider(char *provider_id, char *pin,
-- struct sshkey ***keyp, char ***labelsp,
-- struct pkcs11_provider **providerp, CK_ULONG user)
-+pkcs11_initialize_provider(struct pkcs11_uri *uri, struct pkcs11_provider **providerp)
- {
-- int nkeys, need_finalize = 0;
-+ int need_finalize = 0;
- int ret = -1;
- struct pkcs11_provider *p = NULL;
- void *handle = NULL;
-@@ -1859,128 +2037,126 @@ pkcs11_register_provider(char *provider_id, char *pin,
- CK_FUNCTION_LIST *f = NULL;
- CK_TOKEN_INFO *token;
- CK_ULONG i;
-+ char *provider_module = NULL;
-+ struct pkcs11_module *m = NULL;
-+
-+ /* if no provider specified, fallback to p11-kit */
-+ if (uri->module_path == NULL) {
-+#ifdef PKCS11_DEFAULT_PROVIDER
-+ provider_module = strdup(PKCS11_DEFAULT_PROVIDER);
-+#else
-+ error_f("No module path provided");
-+ goto fail;
-+#endif
-+ } else {
-+ provider_module = strdup(uri->module_path);
-+ }
-+ p = xcalloc(1, sizeof(*p));
-+ p->name = pkcs11_uri_get(uri);
-
-- if (providerp == NULL)
-- goto fail;
-- *providerp = NULL;
--
-- if (keyp != NULL)
-- *keyp = NULL;
-- if (labelsp != NULL)
-- *labelsp = NULL;
--
-- if (pkcs11_provider_lookup(provider_id) != NULL) {
-- debug_f("provider already registered: %s", provider_id);
-+ if (lib_contains_symbol(provider_module, "C_GetFunctionList") != 0) {
-+ error("provider %s is not a PKCS11 library", provider_module);
- goto fail;
- }
-- if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) {
-- error("provider %s is not a PKCS11 library", provider_id);
-- goto fail;
-+ if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
-+ && m->valid) {
-+ debug_f("provider module already initialized: %s", provider_module);
-+ free(provider_module);
-+ /* Skip the initialization of PKCS#11 module */
-+ m->refcount++;
-+ p->module = m;
-+ p->valid = 1;
-+ TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
-+ p->refcount++; /* add to provider list */
-+ *providerp = p;
-+ return 0;
-+ } else {
-+ m = xcalloc(1, sizeof(*m));
-+ p->module = m;
-+ m->refcount++;
- }
-+
- /* open shared pkcs11-library */
-- if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
-- error("dlopen %s failed: %s", provider_id, dlerror());
-+ if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) {
-+ error("dlopen %s failed: %s", provider_module, dlerror());
- goto fail;
- }
- if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
- fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
-- p = xcalloc(1, sizeof(*p));
-- p->name = xstrdup(provider_id);
-- p->handle = handle;
-- /* set up the pkcs11 callbacks */
-+ p->module->handle = handle;
-+ /* setup the pkcs11 callbacks */
- if ((rv = (*getfunctionlist)(&f)) != CKR_OK) {
- error("C_GetFunctionList for provider %s failed: %lu",
-- provider_id, rv);
-+ provider_module, rv);
- goto fail;
- }
-- p->function_list = f;
-+ m->function_list = f;
- if ((rv = f->C_Initialize(NULL)) != CKR_OK) {
- error("C_Initialize for provider %s failed: %lu",
-- provider_id, rv);
-+ provider_module, rv);
- goto fail;
- }
- need_finalize = 1;
-- if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) {
-+ if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) {
- error("C_GetInfo for provider %s failed: %lu",
-- provider_id, rv);
-+ provider_module, rv);
- goto fail;
- }
-- debug("provider %s: manufacturerID <%.*s> cryptokiVersion %d.%d"
-- " libraryDescription <%.*s> libraryVersion %d.%d",
-- provider_id,
-- RMSPACE(p->info.manufacturerID),
-- p->info.cryptokiVersion.major,
-- p->info.cryptokiVersion.minor,
-- RMSPACE(p->info.libraryDescription),
-- p->info.libraryVersion.major,
-- p->info.libraryVersion.minor);
-- if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
-+ rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
-+ if (uri->lib_manuf != NULL &&
-+ strncmp(uri->lib_manuf, m->info.manufacturerID, 32)) {
-+ debug_f("Skipping provider %s not matching library_manufacturer",
-+ m->info.manufacturerID);
-+ goto fail;
-+ }
-+ rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
-+ debug("provider %s: manufacturerID <%.32s> cryptokiVersion %d.%d"
-+ " libraryDescription <%.32s> libraryVersion %d.%d",
-+ provider_module,
-+ m->info.manufacturerID,
-+ m->info.cryptokiVersion.major,
-+ m->info.cryptokiVersion.minor,
-+ m->info.libraryDescription,
-+ m->info.libraryVersion.major,
-+ m->info.libraryVersion.minor);
-+
-+ if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) {
- error("C_GetSlotList failed: %lu", rv);
- goto fail;
- }
-- if (p->nslots == 0) {
-- debug_f("provider %s returned no slots", provider_id);
-+ if (m->nslots == 0) {
-+ debug_f("provider %s returned no slots", provider_module);
- ret = -SSH_PKCS11_ERR_NO_SLOTS;
- goto fail;
- }
-- p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID));
-- if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots))
-+ m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID));
-+ if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots))
- != CKR_OK) {
- error("C_GetSlotList for provider %s failed: %lu",
-- provider_id, rv);
-+ provider_module, rv);
- goto fail;
- }
-- p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo));
-+ m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
- p->valid = 1;
-- nkeys = 0;
-- for (i = 0; i < p->nslots; i++) {
-- token = &p->slotinfo[i].token;
-- if ((rv = f->C_GetTokenInfo(p->slotlist[i], token))
-+ m->valid = 1;
-+ for (i = 0; i < m->nslots; i++) {
-+ token = &m->slotinfo[i].token;
-+ if ((rv = f->C_GetTokenInfo(m->slotlist[i], token))
- != CKR_OK) {
- error("C_GetTokenInfo for provider %s slot %lu "
-- "failed: %lu", provider_id, (u_long)i, rv);
-- continue;
-- }
-- if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
-- debug2_f("ignoring uninitialised token in "
-- "provider %s slot %lu", provider_id, (u_long)i);
-+ "failed: %lu", provider_module, (u_long)i, rv);
- continue;
- }
- debug("provider %s slot %lu: label <%.*s> "
- "manufacturerID <%.*s> model <%.*s> serial <%.*s> "
- "flags 0x%lx",
-- provider_id, (unsigned long)i,
-+ provider_module, (unsigned long)i,
- RMSPACE(token->label), RMSPACE(token->manufacturerID),
- RMSPACE(token->model), RMSPACE(token->serialNumber),
- token->flags);
-- /*
-- * open session, login with pin and retrieve public
-- * keys (if keyp is provided)
-- */
-- if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 ||
-- keyp == NULL)
-- continue;
-- pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
--#ifdef WITH_OPENSSL
-- pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
--#endif
-- if (nkeys == 0 && !p->slotinfo[i].logged_in &&
-- pkcs11_interactive) {
-- /*
-- * Some tokens require login before they will
-- * expose keys.
-- */
-- if (pkcs11_login_slot(p, &p->slotinfo[i],
-- CKU_USER) < 0) {
-- error("login failed");
-- continue;
-- }
-- pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
--#ifdef WITH_OPENSSL
-- pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
--#endif
-- }
- }
-+ m->module_path = provider_module;
-+ provider_module = NULL;
-
- /* now owned by caller */
- *providerp = p;
-@@ -1988,21 +2164,22 @@ pkcs11_register_provider(char *provider_id, char *pin,
- TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
- p->refcount++; /* add to provider list */
-
-- return (nkeys);
-+ return 0;
- fail:
- if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
- error("C_Finalize for provider %s failed: %lu",
-- provider_id, rv);
-+ provider_module, rv);
-+ free(provider_module);
-+ if (m) {
-+ free(m->slotlist);
-+ free(m);
-+ }
- if (p) {
- free(p->name);
-- free(p->slotlist);
-- free(p->slotinfo);
- free(p);
- }
- if (handle)
- dlclose(handle);
-- if (ret > 0)
-- ret = -1;
- return (ret);
- }
-
-@@ -2038,18 +2215,163 @@ pkcs11_terminate(void)
- }
-
- /*
-- * register a new provider and get number of keys hold by the token,
-- * fails if provider already exists
-+ * register a new provider, fails if provider already exists. if
-+ * keyp is provided, fetch keys.
- */
-+static int
-+pkcs11_register_provider_by_uri(struct pkcs11_uri *uri, char *pin,
-+ struct sshkey ***keyp, char ***labelsp, struct pkcs11_provider **providerp,
-+ CK_ULONG user)
-+{
-+ int nkeys;
-+ int ret = -1;
-+ struct pkcs11_provider *p = NULL;
-+ CK_ULONG i;
-+ CK_TOKEN_INFO *token;
-+ char *provider_uri = NULL;
-+
-+ if (providerp == NULL)
-+ goto fail;
-+ *providerp = NULL;
-+
-+ if (keyp != NULL)
-+ *keyp = NULL;
-+
-+ if ((ret = pkcs11_initialize_provider(uri, &p)) != 0) {
-+ goto fail;
-+ }
-+
-+ provider_uri = pkcs11_uri_get(uri);
-+ if (pin == NULL && uri->pin != NULL) {
-+ pin = uri->pin;
-+ }
-+ nkeys = 0;
-+ for (i = 0; i < p->module->nslots; i++) {
-+ token = &p->module->slotinfo[i].token;
-+ if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
-+ debug2_f("ignoring uninitialised token in "
-+ "provider %s slot %lu", provider_uri, (u_long)i);
-+ continue;
-+ }
-+ if (uri->token != NULL &&
-+ strncmp(token->label, uri->token, 32) != 0) {
-+ debug2_f("ignoring token not matching label (%.32s) "
-+ "specified by PKCS#11 URI in slot %lu",
-+ token->label, (unsigned long)i);
-+ continue;
-+ }
-+ if (uri->manuf != NULL &&
-+ strncmp(token->manufacturerID, uri->manuf, 32) != 0) {
-+ debug2_f("ignoring token not matching requrested "
-+ "manufacturerID (%.32s) specified by PKCS#11 URI in "
-+ "slot %lu", token->manufacturerID, (unsigned long)i);
-+ continue;
-+ }
-+ if (uri->serial != NULL &&
-+ strncmp(token->serialNumber, uri->serial, 16) != 0) {
-+ debug2_f("ignoring token not matching requrested "
-+ "serialNumber (%s) specified by PKCS#11 URI in "
-+ "slot %lu", token->serialNumber, (unsigned long)i);
-+ continue;
-+ }
-+ debug("provider %s slot %lu: label <%.32s> manufacturerID <%.32s> "
-+ "model <%.16s> serial <%.16s> flags 0x%lx",
-+ provider_uri, (unsigned long)i,
-+ token->label, token->manufacturerID, token->model,
-+ token->serialNumber, token->flags);
-+ /*
-+ * open session if not yet opened, login with pin and
-+ * retrieve public keys (if keyp is provided)
-+ */
-+ if ((p->module->slotinfo[i].session != 0 ||
-+ (ret = pkcs11_open_session(p, i, pin, user)) != 0) && /* ??? */
-+ keyp == NULL)
-+ continue;
-+ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+ if (nkeys == 0 && !p->module->slotinfo[i].logged_in &&
-+ pkcs11_interactive) {
-+ /*
-+ * Some tokens require login before they will
-+ * expose keys.
-+ */
-+ debug3_f("Trying to login as there were no keys found");
-+ if (pkcs11_login_slot(p, &p->module->slotinfo[i],
-+ CKU_USER) < 0) {
-+ error("login failed");
-+ continue;
-+ }
-+ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+ }
-+ if (nkeys == 0 && uri->object != NULL) {
-+ debug3_f("No keys found. Retrying without label (%.32s) ",
-+ uri->object);
-+ /* Try once more without the label filter */
-+ char *label = uri->object;
-+ uri->object = NULL; /* XXX clone uri? */
-+ pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+ pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+ uri->object = label;
-+ }
-+ }
-+ pin = NULL; /* Will be cleaned up with URI */
-+
-+ /* now owned by caller */
-+ *providerp = p;
-+
-+ free(provider_uri);
-+ return (nkeys);
-+ fail:
-+ if (p) {
-+ TAILQ_REMOVE(&pkcs11_providers, p, next);
-+ pkcs11_provider_unref(p);
-+ }
-+ if (ret > 0)
-+ ret = -1;
-+ return (ret);
-+}
-+
-+#ifdef WITH_PKCS11_KEYGEN
-+static int
-+pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
-+ char ***labelsp, struct pkcs11_provider **providerp, CK_ULONG user)
-+{
-+ struct pkcs11_uri *uri = NULL;
-+ int r;
-+
-+ debug_f("called, provider_id = %s", provider_id);
-+
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL)
-+ fatal("failed to init PKCS#11 URI");
-+
-+ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+ if (pkcs11_uri_parse(provider_id, uri) != 0)
-+ fatal("Failed to parse PKCS#11 URI");
-+ } else {
-+ uri->module_path = strdup(provider_id);
-+ }
-+
-+ r = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, providerp, user);
-+ pkcs11_uri_cleanup(uri);
-+
-+ return r;
-+}
-+#endif
-+
- int
--pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
-- char ***labelsp)
-+pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin,
-+ struct sshkey ***keyp, char ***labelsp)
- {
- struct pkcs11_provider *p = NULL;
- int nkeys;
-+ char *provider_uri = pkcs11_uri_get(uri);
-
-- nkeys = pkcs11_register_provider(provider_id, pin, keyp, labelsp,
-- &p, CKU_USER);
-+ debug_f("called, provider_uri = %s", provider_uri);
-+
-+ nkeys = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, &p, CKU_USER);
-
- /* no keys found or some other error, de-register provider */
- if (nkeys <= 0 && p != NULL) {
-@@ -2058,11 +2380,38 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
- pkcs11_provider_unref(p);
- }
- if (nkeys == 0)
-- debug_f("provider %s returned no keys", provider_id);
-+ debug_f("provider %s returned no keys", provider_uri);
-
-+ free(provider_uri);
- return (nkeys);
- }
-
-+int
-+pkcs11_add_provider(char *provider_id, char *pin,
-+ struct sshkey ***keyp, char ***labelsp)
-+{
-+ struct pkcs11_uri *uri;
-+ int nkeys;
-+
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL)
-+ fatal("Failed to init PKCS#11 URI");
-+
-+ if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+ if (pkcs11_uri_parse(provider_id, uri) != 0)
-+ fatal("Failed to parse PKCS#11 URI");
-+ } else {
-+ uri->module_path = strdup(provider_id);
-+ }
-+
-+ nkeys = pkcs11_add_provider_by_uri(uri, pin, keyp, labelsp);
-+ pkcs11_uri_cleanup(uri);
-+
-+ return (nkeys);
-+}
-+
-+
- int
- pkcs11_sign(struct sshkey *key,
- u_char **sigp, size_t *lenp,
-diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
-index 1d0277a6d..13459644c 100644
---- a/ssh-pkcs11.h
-+++ b/ssh-pkcs11.h
-@@ -24,12 +24,16 @@
- #define SSH_PKCS11_ERR_PIN_REQUIRED 4
- #define SSH_PKCS11_ERR_PIN_LOCKED 5
-
-+#include "ssh-pkcs11-uri.h"
-+
- struct sshkey;
-
- int pkcs11_init(int);
- void pkcs11_terminate(void);
- int pkcs11_add_provider(char *, char *, struct sshkey ***, char ***);
-+int pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***, char ***);
- int pkcs11_del_provider(char *);
-+int pkcs11_uri_write(const struct sshkey *, FILE *);
- int pkcs11_sign(struct sshkey *, u_char **, size_t *,
- const u_char *, size_t, const char *, const char *,
- const char *, u_int);
-diff --git a/ssh.c b/ssh.c
-index 4a17c0a7d..b41ae82d2 100644
---- a/ssh.c
-+++ b/ssh.c
-@@ -862,6 +862,14 @@ main(int ac, char **av)
- options.gss_deleg_creds = 1;
- break;
- case 'i':
-+#ifdef ENABLE_PKCS11
-+ if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(optarg, PKCS11_URI_SCHEME,
-+ strlen(PKCS11_URI_SCHEME)) == 0) {
-+ add_identity_file(&options, NULL, optarg, 1);
-+ break;
-+ }
-+#endif
- p = tilde_expand_filename(optarg, getuid());
- if (stat(p, &st) == -1)
- fprintf(stderr, "Warning: Identity file %s "
-@@ -1838,6 +1846,7 @@ main(int ac, char **av)
-
- #ifdef ENABLE_PKCS11
- (void)pkcs11_del_provider(options.pkcs11_provider);
-+ pkcs11_terminate();
- #endif
-
- skip_connect:
-@@ -2349,6 +2358,45 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
- options.escape_char : SSH_ESCAPECHAR_NONE, id);
- }
-
-+#ifdef ENABLE_PKCS11
-+static void
-+load_pkcs11_identity(char *pkcs11_uri, char *identity_files[],
-+ struct sshkey *identity_keys[], int *n_ids)
-+{
-+ int nkeys, i;
-+ struct sshkey **keys;
-+ struct pkcs11_uri *uri;
-+
-+ debug("identity file '%s' from pkcs#11", pkcs11_uri);
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL)
-+ fatal("Failed to init PKCS#11 URI");
-+
-+ if (pkcs11_uri_parse(pkcs11_uri, uri) != 0)
-+ fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri);
-+
-+ /* we need to merge URI and provider together */
-+ if (options.pkcs11_provider != NULL && uri->module_path == NULL)
-+ uri->module_path = strdup(options.pkcs11_provider);
-+
-+ if (options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
-+ (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys, NULL)) > 0) {
-+ for (i = 0; i < nkeys; i++) {
-+ if (*n_ids >= SSH_MAX_IDENTITY_FILES) {
-+ sshkey_free(keys[i]);
-+ continue;
-+ }
-+ identity_keys[*n_ids] = keys[i];
-+ identity_files[*n_ids] = pkcs11_uri_get(uri);
-+ (*n_ids)++;
-+ }
-+ free(keys);
-+ }
-+
-+ pkcs11_uri_cleanup(uri);
-+}
-+#endif /* ENABLE_PKCS11 */
-+
- /* Loads all IdentityFile and CertificateFile keys */
- static void
- load_public_identity_files(const struct ssh_conn_info *cinfo)
-@@ -2363,11 +2411,6 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
- char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
- struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
- int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
--#ifdef ENABLE_PKCS11
-- struct sshkey **keys = NULL;
-- char **comments = NULL;
-- int nkeys;
--#endif /* PKCS11 */
-
- n_ids = n_certs = 0;
- memset(identity_files, 0, sizeof(identity_files));
-@@ -2380,33 +2423,46 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
- sizeof(certificate_file_userprovided));
-
- #ifdef ENABLE_PKCS11
-- if (options.pkcs11_provider != NULL &&
-- options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
-- (pkcs11_init(!options.batch_mode) == 0) &&
-- (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL,
-- &keys, &comments)) > 0) {
-- for (i = 0; i < nkeys; i++) {
-- if (n_ids >= SSH_MAX_IDENTITY_FILES) {
-- sshkey_free(keys[i]);
-- free(comments[i]);
-- continue;
-- }
-- identity_keys[n_ids] = keys[i];
-- identity_files[n_ids] = comments[i]; /* transferred */
-- n_ids++;
-- }
-- free(keys);
-- free(comments);
-+ /* handle fallback from PKCS11Provider option */
-+ pkcs11_init(!options.batch_mode);
-+
-+ if (options.pkcs11_provider != NULL) {
-+ struct pkcs11_uri *uri;
-+
-+ uri = pkcs11_uri_init();
-+ if (uri == NULL)
-+ fatal("Failed to init PKCS#11 URI");
-+
-+ /* Construct simple PKCS#11 URI to simplify access */
-+ uri->module_path = strdup(options.pkcs11_provider);
-+
-+ /* Add it as any other IdentityFile */
-+ cp = pkcs11_uri_get(uri);
-+ add_identity_file(&options, NULL, cp, 1);
-+ free(cp);
-+
-+ pkcs11_uri_cleanup(uri);
- }
- #endif /* ENABLE_PKCS11 */
- for (i = 0; i < options.num_identity_files; i++) {
-+ char *name = options.identity_files[i];
- if (n_ids >= SSH_MAX_IDENTITY_FILES ||
-- strcasecmp(options.identity_files[i], "none") == 0) {
-+ strcasecmp(name, "none") == 0) {
- free(options.identity_files[i]);
- options.identity_files[i] = NULL;
- continue;
- }
-- cp = tilde_expand_filename(options.identity_files[i], getuid());
-+#ifdef ENABLE_PKCS11
-+ if (strlen(name) >= strlen(PKCS11_URI_SCHEME) &&
-+ strncmp(name, PKCS11_URI_SCHEME,
-+ strlen(PKCS11_URI_SCHEME)) == 0) {
-+ load_pkcs11_identity(name, identity_files,
-+ identity_keys, &n_ids);
-+ free(options.identity_files[i]);
-+ continue;
-+ }
-+#endif /* ENABLE_PKCS11 */
-+ cp = tilde_expand_filename(name, getuid());
- filename = default_client_percent_dollar_expand(cp, cinfo);
- free(cp);
- check_load(sshkey_load_public(filename, &public, NULL),
-diff --git a/ssh_config.5 b/ssh_config.5
-index ab98c172f..699b3f67d 100644
---- a/ssh_config.5
-+++ b/ssh_config.5
-@@ -1262,6 +1262,21 @@ may also be used in conjunction with
- .Cm CertificateFile
- in order to provide any certificate also needed for authentication with
- the identity.
-+.Pp
-+The authentication identity can be also specified in a form of PKCS#11 URI
-+starting with a string
-+.Cm pkcs11: .
-+There is supported a subset of the PKCS#11 URI as defined
-+in RFC 7512 (implemented path arguments
-+.Cm id ,
-+.Cm manufacturer ,
-+.Cm object ,
-+.Cm token
-+and query arguments
-+.Cm module-path
-+and
-+.Cm pin-value
-+). The URI can not be in quotes.
- .It Cm IgnoreUnknown
- Specifies a pattern-list of unknown options to be ignored if they are
- encountered in configuration parsing.
-diff --git a/sshkey.c b/sshkey.c
-index 5225eabf2..583f6421d 100644
---- a/sshkey.c
-+++ b/sshkey.c
-@@ -856,8 +856,10 @@ sshkey_free_contents(struct sshkey *k)
-
- if (k == NULL)
- return;
-+#ifdef ENABLE_PKCS11
- if ((k->flags & SSHKEY_FLAG_EXT) != 0)
- pkcs11_key_free(k);
-+#endif
- if ((impl = sshkey_impl_from_type(k->type)) != NULL &&
- impl->funcs->cleanup != NULL)
- impl->funcs->cleanup(k);
-@@ -2304,9 +2306,11 @@ sshkey_sign(struct sshkey *key,
- if (sshkey_is_sk(key)) {
- r = sshsk_sign(sk_provider, key, sigp, lenp, data,
- datalen, compat, sk_pin);
-+#ifdef ENABLE_PKCS11
- } else if ((key->flags & SSHKEY_FLAG_EXT) != 0) {
- r = pkcs11_sign(key, sigp, lenp, data, datalen,
- alg, sk_provider, sk_pin, compat);
-+#endif
- } else {
- if (impl->funcs->sign == NULL)
- r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
---
-2.53.0
-
diff --git a/0054-gssapi-tests.patch b/0054-gssapi-tests.patch
deleted file mode 100644
index 0f2b554..0000000
--- a/0054-gssapi-tests.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-diff --git a/regress/Makefile b/regress/Makefile
-index cbe7a9610..eb9f7ad00 100644
---- a/regress/Makefile
-+++ b/regress/Makefile
-@@ -127,6 +127,7 @@ INTEROP_TESTS+= dropbear-ciphers dropbear-kex dropbear-server
- #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
-
- EXTRA_TESTS= agent-pkcs11
-+EXTRA_TESTS+= gss-auth gss-kex
- #EXTRA_TESTS+= cipher-speed
-
- USERNAME= ${LOGNAME}
-diff --git a/regress/gss-kex.sh b/regress/gss-kex.sh
-new file mode 100644
-index 000000000..17a3bf6f1
---- /dev/null
-+++ b/regress/gss-kex.sh
-@@ -0,0 +1,222 @@
-+tid="GSS-API Key Exchange"
-+
-+# Skip the test if GSSAPI support is not configured
-+if ! grep -E '^#define GSSAPI' "$BUILDDIR/config.h" >/dev/null 2>&1; then
-+ skip "GSSAPI not enabled"
-+fi
-+
-+# We test with MIT Kerberos KDC, skip if not installed
-+if ! which krb5kdc >/dev/null 2>&1; then
-+ skip "MIT Kerberos KDC not installed"
-+fi
-+
-+# The test needs nss_wrapper to emulate gethostname() and /etc/hosts,
-+# we skip if the shared library is not installed
-+nss_wrapper="libnss_wrapper.so"
-+if ! ldconfig -p | grep "$nss_wrapper" >/dev/null 2>&1; then
-+ skip "$nss_wrapper not installed"
-+fi
-+
-+# Set up the username of the SSH client
-+client="$LOGNAME"
-+if [ "x$client" = "x" ]; then
-+ client="$(whoami)"
-+fi
-+
-+# Set up SSHD and KDC hostnames and resolve both to localhost
-+sshd_hostname="sshd.example.org"
-+kdc_hostname="kdc.example.org"
-+kdc_port=2088
-+hosts="$OBJ/hosts"
-+echo "127.0.0.1 $sshd_hostname $kdc_hostname" > "$hosts"
-+
-+# Set up a directory to store Kerberos data
-+gssdir="$OBJ/gss"
-+mkdir -p "$gssdir"
-+export KRB5CCNAME="$gssdir/cc"
-+export KRB5_CONFIG="$gssdir/krb5.conf"
-+export KRB5_KDC_PROFILE="$gssdir/kdc.conf"
-+export KRB5_KTNAME="$gssdir/ssh.keytab"
-+export KRB5RCACHETYPE="none"
-+kdc_pidfile="$gssdir/pid"
-+
-+# Configure Kerberos
-+cat<<EOF > "$KRB5_KDC_PROFILE"
-+[realms]
-+ EXAMPLE.ORG = {
-+ database_name = $gssdir/principal
-+ key_stash_file = $gssdir/stash
-+ kdc_listen = $kdc_hostname:$kdc_port
-+ kdc_tcp_listen = $kdc_hostname:$kdc_port
-+ }
-+[logging]
-+ kdc = FILE:$gssdir/kdc.log
-+ debug = true
-+EOF
-+
-+cat<<EOF > "$KRB5_CONFIG"
-+[libdefaults]
-+ default_realm = EXAMPLE.ORG
-+[realms]
-+ EXAMPLE.ORG = {
-+ kdc = $kdc_hostname:$kdc_port
-+ }
-+EOF
-+
-+# Back up the default sshd_config
-+cp "$OBJ/sshd_config" "$OBJ/sshd_config.orig"
-+
-+setup_sshd() {
-+ kex_alg="$1"
-+
-+ cp "$OBJ/sshd_config.orig" "$OBJ/sshd_config"
-+
-+ cat<<EOF >> "$OBJ/sshd_config"
-+PubkeyAuthentication no
-+PasswordAuthentication no
-+GSSAPIAuthentication yes
-+GSSAPIKeyExchange yes
-+GSSAPIKexAlgorithms $kex_alg
-+GSSAPIStrictAcceptorCheck no
-+EOF
-+
-+ test_ssh_sshd_env_backup="$TEST_SSH_SSHD_ENV"
-+ TEST_SSH_SSHD_ENV="$TEST_SSH_SSHD_ENV \
-+ LD_PRELOAD=$nss_wrapper \
-+ NSS_WRAPPER_HOSTS=$hosts \
-+ NSS_WRAPPER_HOSTNAME=$sshd_hostname \
-+ KRB5_CONFIG=$KRB5_CONFIG \
-+ KRB5_KDC_PROFILE=$KRB5_KDC_PROFILE \
-+ KRB5CCNAME=$KRB5CCNAME \
-+ KRB5_KTNAME=$KRB5_KTNAME \
-+ KRB5RCACHETYPE=$KRB5RCACHETYPE"
-+ start_sshd
-+}
-+
-+teardown_sshd() {
-+ TEST_SSH_SSHD_ENV="$test_ssh_sshd_env_backup"
-+ stop_sshd
-+}
-+
-+setup_kdc() {
-+ kdb5_util create -P "foo" -s
-+ krb5kdc -w 1 -P "$kdc_pidfile"
-+ i=0;
-+ while [ ! -f "$kdc_pidfile" -a $i -lt 10 ]; do
-+ i=$((i + 1))
-+ sleep 1
-+ done
-+ test -f "$kdc_pidfile" || fatal "KDC failed to start"
-+}
-+
-+teardown_kdc() {
-+ kill "$(cat "$kdc_pidfile")"
-+ kdestroy
-+ rm -f "$KRB5_KTNAME" "$kdc_pidfile"
-+ kdb5_util destroy -f
-+}
-+
-+setup_nss_emulation() {
-+ export LD_PRELOAD="$nss_wrapper"
-+ export NSS_WRAPPER_HOSTS="$hosts"
-+}
-+
-+teardown_nss_emulation() {
-+ unset LD_PRELOAD
-+ unset NSS_WRAPPER_HOSTS
-+}
-+
-+# GSS kex family prefixes to test.
-+# GSSAPIKexAlgorithms accepts these prefixes; the full algorithm names
-+# (prefix + Base64(MD5(OID))) are constructed during negotiation.
-+gss_kex_families="
-+ gss-group14-sha256-
-+ gss-group16-sha512-
-+ gss-nistp256-sha256-
-+ gss-curve25519-sha256-
-+ gss-group14-sha1-
-+ gss-gex-sha1-
-+"
-+
-+# Positive tests: each GSS kex algorithm connects successfully
-+for kex_family in $gss_kex_families; do
-+ # Check if the algorithm family is recognized by ssh
-+ if ! ${SSH} -G -F "$OBJ/ssh_config" \
-+ -o "GSSAPIKeyExchange yes" \
-+ -o "GSSAPIKexAlgorithms $kex_family" \
-+ "$client@$sshd_hostname" >/dev/null 2>&1; then
-+ verbose "gss kex $kex_family not supported, skipping"
-+ continue
-+ fi
-+
-+ verbose "gss kex $kex_family"
-+
-+ setup_nss_emulation
-+ setup_kdc
-+
-+ kadmin.local add_principal -randkey "host/$sshd_hostname"
-+ kadmin.local ktadd "host/$sshd_hostname"
-+ kadmin.local add_principal -pw "foo" "$client"
-+ echo "foo" | kinit "$client"
-+
-+ setup_sshd "$kex_family"
-+
-+ ${SSH} -F "$OBJ/ssh_config" \
-+ -o "GSSAPIAuthentication yes" \
-+ -o "GSSAPIKeyExchange yes" \
-+ -o "GSSAPIKexAlgorithms $kex_family" \
-+ -o "GSSAPIDelegateCredentials no" \
-+ "$client@$sshd_hostname" true
-+ status=$?
-+
-+ teardown_sshd
-+ teardown_kdc
-+ teardown_nss_emulation
-+
-+ if [ $status -ne 0 ]; then
-+ fail "gss kex $kex_family failed"
-+ fi
-+done
-+
-+# Negative test: connection must fail when keytab is missing
-+verbose "gss kex negative test: no keytab"
-+kex_neg="gss-group14-sha256-"
-+
-+if ${SSH} -G -F "$OBJ/ssh_config" \
-+ -o "GSSAPIKeyExchange yes" \
-+ -o "GSSAPIKexAlgorithms $kex_neg" \
-+ "$client@$sshd_hostname" >/dev/null 2>&1; then
-+
-+ setup_nss_emulation
-+ setup_kdc
-+
-+ # Create host principal but do NOT export to keytab
-+ kadmin.local add_principal -randkey "host/$sshd_hostname"
-+ kadmin.local add_principal -pw "foo" "$client"
-+ echo "foo" | kinit "$client"
-+
-+ setup_sshd "$kex_neg"
-+
-+ ${SSH} -F "$OBJ/ssh_config" \
-+ -o "GSSAPIAuthentication yes" \
-+ -o "GSSAPIKeyExchange yes" \
-+ -o "GSSAPIKexAlgorithms $kex_neg" \
-+ -o "GSSAPIDelegateCredentials no" \
-+ "$client@$sshd_hostname" true
-+ status=$?
-+
-+ teardown_sshd
-+ teardown_kdc
-+ teardown_nss_emulation
-+
-+ if [ $status -eq 0 ]; then
-+ fail "gss kex succeeded without keytab"
-+ fi
-+fi
-+
-+unset KRB5CCNAME
-+unset KRB5_CONFIG
-+unset KRB5_KDC_PROFILE
-+unset KRB5_KTNAME
-+unset KRB5RCACHETYPE
-+rm -rf "$gssdir"
diff --git a/1000-openssh-6.7p1-coverity.patch b/1000-openssh-6.7p1-coverity.patch
deleted file mode 100644
index a855571..0000000
--- a/1000-openssh-6.7p1-coverity.patch
+++ /dev/null
@@ -1,230 +0,0 @@
-From d0225bcdbc0d783df878ccfb5128bdcb787d82ed Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Fri, 12 Dec 2025 14:25:41 +0100
-Subject: [PATCH 54/54] openssh-6.7p1-coverity
-
-https://bugzilla.mindrot.org/show_bug.cgi?id=2581
----
- auth-krb5.c | 2 ++
- gss-genr.c | 3 ++-
- krl.c | 3 +++
- loginrec.c | 2 ++
- misc.c | 3 +++
- monitor.c | 4 ++--
- openbsd-compat/bindresvport.c | 2 +-
- openbsd-compat/bsd-pselect.c | 8 ++++----
- readconf.c | 1 +
- servconf.c | 5 +++--
- serverloop.c | 2 +-
- 11 files changed, 24 insertions(+), 11 deletions(-)
-
-diff --git a/auth-krb5.c b/auth-krb5.c
-index 7d4fbcc3d..9cd3618b2 100644
---- a/auth-krb5.c
-+++ b/auth-krb5.c
-@@ -427,6 +427,7 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
- umask(old_umask);
- if (tmpfd == -1) {
- logit("mkstemp(): %.100s", strerror(oerrno));
-+ free(ccname);
- return oerrno;
- }
-
-@@ -434,6 +435,7 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
- oerrno = errno;
- logit("fchmod(): %.100s", strerror(oerrno));
- close(tmpfd);
-+ free(ccname);
- return oerrno;
- }
- /* make sure the KRB5CCNAME is set for non-standard location */
-diff --git a/gss-genr.c b/gss-genr.c
-index a07dbf512..ec2c9c6dd 100644
---- a/gss-genr.c
-+++ b/gss-genr.c
-@@ -178,8 +178,9 @@ ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check,
- enclen = __b64_ntop(digest,
- ssh_digest_bytes(SSH_DIGEST_MD5), encoded,
- ssh_digest_bytes(SSH_DIGEST_MD5) * 2);
--
-+#pragma GCC diagnostic ignored "-Wstringop-overflow"
- cp = strncpy(s, kex, strlen(kex));
-+#pragma GCC diagnostic pop
- for ((p = strsep(&cp, ",")); p && *p != '\0';
- (p = strsep(&cp, ","))) {
- if (sshbuf_len(buf) != 0 &&
-diff --git a/krl.c b/krl.c
-index 0e2b5f155..f2d105f23 100644
---- a/krl.c
-+++ b/krl.c
-@@ -1202,6 +1202,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
- return r;
- erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha1s, &rb);
- free(rb.blob);
-+ rb.blob = NULL; /* make coverity happy */
- if (erb != NULL) {
- KRL_DBG(("revoked by key SHA1"));
- return SSH_ERR_KEY_REVOKED;
-@@ -1212,6 +1213,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
- return r;
- erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha256s, &rb);
- free(rb.blob);
-+ rb.blob = NULL; /* make coverity happy */
- if (erb != NULL) {
- KRL_DBG(("revoked by key SHA256"));
- return SSH_ERR_KEY_REVOKED;
-@@ -1223,6 +1225,7 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key)
- return r;
- erb = RB_FIND(revoked_blob_tree, &krl->revoked_keys, &rb);
- free(rb.blob);
-+ rb.blob = NULL; /* make coverity happy */
- if (erb != NULL) {
- KRL_DBG(("revoked by explicit key"));
- return SSH_ERR_KEY_REVOKED;
-diff --git a/loginrec.c b/loginrec.c
-index 7499aa975..2807cad4a 100644
---- a/loginrec.c
-+++ b/loginrec.c
-@@ -680,9 +680,11 @@ construct_utmp(struct logininfo *li,
- */
-
- /* Use strncpy because we don't necessarily want null termination */
-+ /* coverity[buffer_size_warning : FALSE] */
- strncpy(ut->ut_name, li->username,
- MIN_SIZEOF(ut->ut_name, li->username));
- # ifdef HAVE_HOST_IN_UTMP
-+ /* coverity[buffer_size_warning : FALSE] */
- strncpy(ut->ut_host, li->hostname,
- MIN_SIZEOF(ut->ut_host, li->hostname));
- # endif
-diff --git a/misc.c b/misc.c
-index e96fa8d61..556c112c3 100644
---- a/misc.c
-+++ b/misc.c
-@@ -1620,6 +1620,8 @@ sanitise_stdfd(void)
- }
- if (nullfd > STDERR_FILENO)
- close(nullfd);
-+ /* coverity[leaked_handle : FALSE]*/
-+ /* coverity[leaked_handle : FALSE]*/
- }
-
- char *
-@@ -2821,6 +2823,7 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
- }
- if (devnull > STDERR_FILENO)
- close(devnull);
-+ /* coverity[leaked_handle : FALSE]*/
- return ret;
- }
-
-diff --git a/monitor.c b/monitor.c
-index 36db1afee..3b60059a9 100644
---- a/monitor.c
-+++ b/monitor.c
-@@ -410,7 +410,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
- mm_get_keystate(ssh, pmonitor);
-
- /* Drain any buffered messages from the child */
-- while (pmonitor->m_log_recvfd != -1 && monitor_read_log(pmonitor) == 0)
-+ while (pmonitor->m_log_recvfd >= 0 && monitor_read_log(pmonitor) == 0)
- ;
-
- /* Wait for the child's exit status */
-@@ -1855,7 +1855,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
- s->ptymaster = s->ptyfd;
-
- debug3_f("tty %s ptyfd %d", s->tty, s->ttyfd);
--
-+ /* coverity[leaked_handle : FALSE] */
- return (0);
-
- error:
-diff --git a/openbsd-compat/bindresvport.c b/openbsd-compat/bindresvport.c
-index 346c7fe56..f42792fde 100644
---- a/openbsd-compat/bindresvport.c
-+++ b/openbsd-compat/bindresvport.c
-@@ -59,7 +59,7 @@ bindresvport_sa(int sd, struct sockaddr *sa)
- struct sockaddr_in6 *in6;
- u_int16_t *portp;
- u_int16_t port;
-- socklen_t salen;
-+ socklen_t salen = sizeof(struct sockaddr_storage);
- int i;
-
- if (sa == NULL) {
-diff --git a/openbsd-compat/bsd-pselect.c b/openbsd-compat/bsd-pselect.c
-index 26bdc3e08..8e2939b95 100644
---- a/openbsd-compat/bsd-pselect.c
-+++ b/openbsd-compat/bsd-pselect.c
-@@ -85,13 +85,13 @@ pselect_notify_setup(void)
- static void
- pselect_notify_parent(void)
- {
-- if (notify_pipe[1] != -1)
-+ if (notify_pipe[1] >= 0)
- (void)write(notify_pipe[1], "", 1);
- }
- static void
- pselect_notify_prepare(fd_set *readset)
- {
-- if (notify_pipe[0] != -1)
-+ if (notify_pipe[0] >= 0)
- FD_SET(notify_pipe[0], readset);
- }
- static void
-@@ -99,8 +99,8 @@ pselect_notify_done(fd_set *readset)
- {
- char c;
-
-- if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) {
-- while (read(notify_pipe[0], &c, 1) != -1)
-+ if (notify_pipe[0] >= 0 && FD_ISSET(notify_pipe[0], readset)) {
-+ while (read(notify_pipe[0], &c, 1) >= 0)
- debug2_f("reading");
- FD_CLR(notify_pipe[0], readset);
- }
-diff --git a/readconf.c b/readconf.c
-index 058e53cf1..14f149cc1 100644
---- a/readconf.c
-+++ b/readconf.c
-@@ -2164,6 +2164,7 @@ parse_pubkey_algos:
- } else if (r != 0) {
- error("%.200s line %d: glob failed for %s.",
- filename, linenum, arg2);
-+ free(arg2);
- goto out;
- }
- free(arg2);
-diff --git a/servconf.c b/servconf.c
-index e05a31584..3ffa49c3a 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -2382,8 +2382,9 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- if (*activep && *charptr == NULL) {
- *charptr = tilde_expand_filename(arg, getuid());
- /* increase optional counter */
-- if (intptr != NULL)
-- *intptr = *intptr + 1;
-+ /* DEAD CODE intptr is still NULL ;)
-+ if (intptr != NULL)
-+ *intptr = *intptr + 1; */
- }
- break;
-
-diff --git a/serverloop.c b/serverloop.c
-index 1087a6c7d..b65c9dd66 100644
---- a/serverloop.c
-+++ b/serverloop.c
-@@ -536,7 +536,7 @@ server_request_tun(struct ssh *ssh)
- debug_f("invalid tun");
- goto done;
- }
-- if (auth_opts->force_tun_device != -1) {
-+ if (auth_opts->force_tun_device >= 0) {
- if (tun != SSH_TUNID_ANY &&
- auth_opts->force_tun_device != (int)tun)
- goto done;
---
-2.53.0
-
diff --git a/2000-openssh-10.3p1-gsissh.patch b/2000-openssh-10.3p1-gsissh.patch
deleted file mode 100644
index c500089..0000000
--- a/2000-openssh-10.3p1-gsissh.patch
+++ /dev/null
@@ -1,2436 +0,0 @@
-diff -Nur openssh-10.3p1.orig/auth2.c openssh-10.3p1/auth2.c
---- openssh-10.3p1.orig/auth2.c 2026-07-01 09:38:00.621866029 +0200
-+++ openssh-10.3p1/auth2.c 2026-07-01 10:54:56.476802766 +0200
-@@ -286,7 +286,28 @@
- (r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 ||
- (r = sshpkt_get_cstring(ssh, &method, NULL)) != 0)
- goto out;
-- debug("userauth-request for user %s service %s method %s", user, service, method);
-+
-+#ifdef GSSAPI
-+ if (user[0] == '\0') {
-+ debug("received empty username for %s", method);
-+ if (strcmp(method, "gssapi-keyex") == 0) {
-+ char *lname = NULL;
-+ mm_ssh_gssapi_localname(&lname);
-+ if (lname && lname[0] != '\0') {
-+ free(user);
-+ user = lname;
-+ debug("set username to %s from gssapi context", user);
-+ } else {
-+ debug("failed to set username from gssapi context");
-+ ssh_packet_send_debug(ssh,
-+ "failed to set username from gssapi context");
-+ }
-+ }
-+ }
-+#endif
-+
-+ debug("userauth-request for user %s service %s method %s",
-+ user[0] ? user : "<implicit>", service, method);
- debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
-
- #ifdef WITH_SELINUX
-@@ -299,16 +320,38 @@
-
- if (authctxt->attempt >= 1024)
- auth_maxtries_exceeded(ssh);
-- if (authctxt->attempt++ == 0) {
-- /* setup auth context */
-- authctxt->pw = mm_getpwnamallow(ssh, user);
-+ /* If first time or username changed or empty username,
-+ setup/reset authentication context. */
-+ if ((authctxt->attempt++ == 0) ||
-+ (strcmp(user, authctxt->user) != 0) ||
-+ (strcmp(user, "") == 0)) {
-+ if (authctxt->user) {
-+ free(authctxt->user);
-+ authctxt->user = NULL;
-+ }
-+ authctxt->valid = 0;
- authctxt->user = xstrdup(user);
- authctxt->service = xstrdup(service);
- authctxt->style = style ? xstrdup(style) : NULL;
- #ifdef WITH_SELINUX
- authctxt->role = role ? xstrdup(role) : NULL;
- #endif
-- if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
-+ if (strcmp(service, "ssh-connection") != 0) {
-+ ssh_packet_disconnect(ssh, "Unsupported service %s",
-+ service);
-+ }
-+#ifdef GSSAPI
-+ /* If we're going to set the username based on the
-+ GSSAPI context later, then wait until then to
-+ verify it. Just put in placeholders for now. */
-+ if ((strcmp(user, "") == 0) &&
-+ ((strcmp(method, "gssapi") == 0) ||
-+ (strcmp(method, "gssapi-with-mic") == 0))) {
-+ authctxt->pw = fakepw();
-+ } else {
-+#endif
-+ authctxt->pw = mm_getpwnamallow(ssh, user);
-+ if (authctxt->pw) {
- authctxt->valid = 1;
- debug2_f("setting up authctxt for %s", user);
- } else {
-@@ -316,6 +359,9 @@
- /* Invalid user, fake password information */
- authctxt->pw = fakepw();
- }
-+#ifdef GSSAPI
-+ } /* endif for setting username based on GSSAPI context */
-+#endif
- #ifdef USE_PAM
- if (options.use_pam)
- mm_start_pam(ssh);
-@@ -323,6 +369,7 @@
- ssh_packet_set_log_preamble(ssh, "%suser %s",
- authctxt->valid ? "authenticating " : "invalid ", user);
- setproctitle("%s [net]", authctxt->valid ? user : "unknown");
-+ if (authctxt->attempt == 1) {
- mm_inform_authserv(service, style);
- #ifdef WITH_SELINUX
- mm_inform_authrole(role);
-@@ -333,9 +380,10 @@
- if (auth2_setup_methods_lists(authctxt) != 0)
- ssh_packet_disconnect(ssh,
- "no authentication methods enabled");
-- } else if (strcmp(user, authctxt->user) != 0 ||
-- strcmp(service, authctxt->service) != 0) {
-- ssh_packet_disconnect(ssh, "Change of username or service "
-+ }
-+ }
-+ if (strcmp(service, authctxt->service) != 0) {
-+ ssh_packet_disconnect(ssh, "Change of service "
- "not allowed: (%s,%s) -> (%s,%s)",
- authctxt->user, authctxt->service, user, service);
- }
-diff -Nur openssh-10.3p1.orig/auth2-gss.c openssh-10.3p1/auth2-gss.c
---- openssh-10.3p1.orig/auth2-gss.c 2026-07-01 09:38:00.318481002 +0200
-+++ openssh-10.3p1/auth2-gss.c 2026-07-01 10:56:51.479708453 +0200
-@@ -54,6 +54,7 @@
- extern struct authmethod_cfg methodcfg_gsskeyex;
- extern struct authmethod_cfg methodcfg_gssapi;
-
-+static void ssh_gssapi_userauth_error(Gssctxt *ctxt, struct ssh *ssh);
- static int input_gssapi_token(int type, uint32_t plen, struct ssh *ssh);
- static int input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh);
- static int input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh);
-@@ -67,8 +68,8 @@
- {
- Authctxt *authctxt = ssh->authctxt;
- int r, authenticated = 0;
-- struct sshbuf *b = NULL;
-- gss_buffer_desc mic, gssbuf;
-+ struct sshbuf *b = NULL, *b2 = NULL;
-+ gss_buffer_desc mic, gssbuf, gssbuf2;
- u_char *p;
- size_t len;
-
-@@ -79,6 +80,9 @@
- if ((b = sshbuf_new()) == NULL)
- fatal_f("sshbuf_new failed");
-
-+ if ((b2 = sshbuf_new()) == NULL)
-+ fatal_f("sshbuf_new failed");
-+
- mic.value = p;
- mic.length = len;
-
-@@ -89,13 +93,28 @@
- fatal_f("sshbuf_mutable_ptr failed");
- gssbuf.length = sshbuf_len(b);
-
-+ /* client may have used empty username to determine target
-+ name from GSSAPI context */
-+ ssh_gssapi_buildmic(b2, "", authctxt->service, "gssapi-keyex",
-+ ssh->kex->session_id);
-+
-+ if ((gssbuf2.value = sshbuf_mutable_ptr(b2)) == NULL)
-+ fatal_f("sshbuf_mutable_ptr failed");
-+ gssbuf2.length = sshbuf_len(b2);
-+
- /* gss_kex_context is NULL with privsep, so we can't check it here */
- if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context,
-- &gssbuf, &mic)))
-- authenticated = mm_ssh_gssapi_userok(authctxt->user,
-- authctxt->pw, 1);
-+ &gssbuf, &mic)) ||
-+ !GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context,
-+ &gssbuf2, &mic))) {
-+ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
-+ authenticated = mm_ssh_gssapi_userok(authctxt->user,
-+ authctxt->pw, 1);
-+ }
-+ }
-
- sshbuf_free(b);
-+ sshbuf_free(b2);
- free(mic.value);
-
- return (authenticated);
-@@ -154,7 +173,9 @@
- return (0);
- }
-
-- if (!authctxt->valid || authctxt->user == NULL) {
-+ /* authctxt->valid may be 0 if we haven't yet determined
-+ username from gssapi context. */
-+ if (authctxt->user == NULL) {
- debug2_f("disabled because of invalid user");
- free(doid);
- return (0);
-@@ -192,7 +213,7 @@
- Gssctxt *gssctxt;
- gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
- gss_buffer_desc recv_tok;
-- OM_uint32 maj_status, min_status, flags;
-+ OM_uint32 maj_status, min_status, flags = 0;
- u_char *p;
- size_t len;
- int r;
-@@ -213,6 +234,7 @@
- free(p);
-
- if (GSS_ERROR(maj_status)) {
-+ ssh_gssapi_userauth_error(gssctxt, ssh);
- if (send_tok.length != 0) {
- if ((r = sshpkt_start(ssh,
- SSH2_MSG_USERAUTH_GSSAPI_ERRTOK)) != 0 ||
-@@ -287,6 +309,34 @@
- return 0;
- }
-
-+static void
-+gssapi_set_username(struct ssh *ssh)
-+{
-+ Authctxt *authctxt = ssh->authctxt;
-+ char *lname = NULL;
-+
-+ if ((authctxt->user == NULL) || (authctxt->user[0] == '\0')) {
-+ mm_ssh_gssapi_localname(&lname);
-+ if (lname && lname[0] != '\0') {
-+ if (authctxt->user) free(authctxt->user);
-+ authctxt->user = lname;
-+ debug("set username to %s from gssapi context", lname);
-+ authctxt->pw = mm_getpwnamallow(ssh, authctxt->user);
-+ if (authctxt->pw) {
-+ authctxt->valid = 1;
-+#ifdef USE_PAM
-+ if (options.use_pam)
-+ mm_start_pam(ssh);
-+#endif
-+ }
-+ } else {
-+ debug("failed to set username from gssapi context");
-+ ssh_packet_send_debug(ssh,
-+ "failed to set username from gssapi context");
-+ }
-+ }
-+}
-+
- /*
- * This is called when the client thinks we've completed authentication.
- * It should only be enabled in the dispatch handler by the function above,
-@@ -297,11 +347,13 @@
- input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
- {
- Authctxt *authctxt = ssh->authctxt;
-- int r, authenticated;
-+ int r, authenticated = 0;
-
- if (authctxt == NULL)
- fatal("No authentication or GSSAPI context");
-
-+ gssapi_set_username(ssh);
-+
- /*
- * We don't need to check the status, because we're only enabled in
- * the dispatcher once the exchange is complete
-@@ -310,7 +362,11 @@
- if ((r = sshpkt_get_end(ssh)) != 0)
- fatal_fr(r, "parse packet");
-
-- authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1);
-+ /* user should be set if valid but we double-check here */
-+ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
-+ authenticated = mm_ssh_gssapi_userok(authctxt->user,
-+ authctxt->pw, 1);
-+ }
-
- authctxt->postponed = 0;
- ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
-@@ -357,10 +413,16 @@
- fatal_f("sshbuf_mutable_ptr failed");
- gssbuf.length = sshbuf_len(b);
-
-- if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))
-- authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 0);
-- else
-+ gssapi_set_username(ssh);
-+
-+ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) {
-+ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
-+ authenticated = mm_ssh_gssapi_userok(authctxt->user,
-+ authctxt->pw, 0);
-+ }
-+ } else {
- logit("GSSAPI MIC check failed");
-+ }
-
- sshbuf_free(b);
- if (micuser != authctxt->user)
-@@ -376,6 +438,26 @@
- return 0;
- }
-
-+static void ssh_gssapi_userauth_error(Gssctxt *ctxt, struct ssh *ssh) {
-+ char *errstr;
-+ OM_uint32 maj, min;
-+ int r;
-+
-+ errstr = mm_ssh_gssapi_last_error(ctxt, &maj, &min);
-+ if (errstr) {
-+ if ((r = sshpkt_start(ssh,
-+ SSH2_MSG_USERAUTH_GSSAPI_ERROR)) != 0 ||
-+ (r = sshpkt_put_u32(ssh, maj)) != 0 ||
-+ (r = sshpkt_put_u32(ssh, min)) != 0 ||
-+ (r = sshpkt_put_cstring(ssh, errstr)) != 0 ||
-+ (r = sshpkt_put_cstring(ssh, "")) != 0 ||
-+ (r = sshpkt_send(ssh)) != 0 ||
-+ (r = ssh_packet_write_wait(ssh)) != 0)
-+ fatal_fr(r, "");
-+ free(errstr);
-+ }
-+}
-+
- Authmethod method_gsskeyex = {
- &methodcfg_gsskeyex,
- userauth_gsskeyex,
-diff -Nur openssh-10.3p1.orig/auth.c openssh-10.3p1/auth.c
---- openssh-10.3p1.orig/auth.c 2026-07-01 09:38:00.621449133 +0200
-+++ openssh-10.3p1/auth.c 2026-07-01 10:39:43.568565111 +0200
-@@ -296,7 +296,8 @@
- method,
- submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod,
- authctxt->valid ? "" : "invalid user ",
-- authctxt->user,
-+ (authctxt->user && authctxt->user[0]) ?
-+ authctxt->user : "unknown",
- ssh_remote_ipaddr(ssh),
- ssh_remote_port(ssh),
- extra != NULL ? ": " : "",
-@@ -488,13 +489,18 @@
- #endif
-
- pw = getpwnam(user);
-+#ifdef USE_PAM
-+ if (options.use_pam && options.permit_pam_user_change && pw == NULL)
-+ pw = sshpam_getpw(user);
-+#endif
-
- #if defined(_AIX) && defined(HAVE_SETAUTHDB)
- aix_restoreauthdb();
- #endif
- if (pw == NULL) {
- logit("Invalid user %.100s from %.100s port %d",
-- user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
-+ (user && user[0]) ? user : "unknown",
-+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
- #ifdef CUSTOM_FAILED_LOGIN
- record_failed_login(ssh, user,
- auth_get_canonical_hostname(ssh, options.use_dns), "ssh");
-diff -Nur openssh-10.3p1.orig/auth.h openssh-10.3p1/auth.h
---- openssh-10.3p1.orig/auth.h 2026-07-01 09:38:00.621551619 +0200
-+++ openssh-10.3p1/auth.h 2026-07-01 10:39:43.569648498 +0200
-@@ -85,6 +85,8 @@
- krb5_principal krb5_user;
- char *krb5_ticket_file;
- char *krb5_ccname;
-+#endif
-+#ifdef GSSAPI
- int krb5_set_env;
- #endif
- struct sshbuf *loginmsg;
-diff -Nur openssh-10.3p1.orig/auth-pam.c openssh-10.3p1/auth-pam.c
---- openssh-10.3p1.orig/auth-pam.c 2026-07-01 09:38:00.857120063 +0200
-+++ openssh-10.3p1/auth-pam.c 2026-07-01 10:39:43.570397326 +0200
-@@ -254,6 +254,7 @@
- static const char *sshpam_password = NULL;
- static char *sshpam_rhost = NULL;
- static char *sshpam_laddr = NULL;
-+static struct ssh *sshpam_ssh = NULL;
-
- /* Some PAM implementations don't implement this */
- #ifndef HAVE_PAM_GETENVLIST
-@@ -277,6 +278,56 @@
- }
- #endif /* HAVE_PAM_PUTENV */
-
-+struct passwd *
-+sshpam_getpw(const char *user)
-+{
-+ struct passwd *pw;
-+
-+ if ((pw = getpwnam(user)) != NULL)
-+ return(pw);
-+
-+ debug("PAM: faking passwd struct for user '%.100s'", user);
-+ if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL)
-+ return NULL;
-+ pw->pw_name = xstrdup(user); /* XXX leak */
-+ pw->pw_shell = "/bin/true";
-+ pw->pw_gecos = "sshd fake PAM user";
-+ return (pw);
-+}
-+
-+void
-+sshpam_check_userchanged(void)
-+{
-+ int sshpam_err;
-+ struct passwd *pw;
-+ const char *user;
-+
-+ debug("sshpam_check_userchanged");
-+ sshpam_err = pam_get_item(sshpam_handle, PAM_USER,
-+ (sshpam_const void **)&user);
-+ if (sshpam_err != PAM_SUCCESS)
-+ fatal("PAM: could not get PAM_USER: %s",
-+ pam_strerror(sshpam_handle, sshpam_err));
-+ debug("sshpam_check_userchanged: user was '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ if (strcmp(user, sshpam_authctxt->pw->pw_name) != 0) {
-+ debug("PAM: user mapped from '%.100s' to '%.100s'",
-+ sshpam_authctxt->pw->pw_name, user);
-+ if ((pw = getpwnam(user)) == NULL)
-+ fatal("PAM: could not get passwd entry for user "
-+ "'%.100s' provided by PAM_USER", user);
-+ pwfree(sshpam_authctxt->pw);
-+ sshpam_authctxt->pw = pwcopy(pw);
-+ sshpam_authctxt->valid = allowed_user(sshpam_ssh, pw);
-+ free(sshpam_authctxt->user);
-+ sshpam_authctxt->user = xstrdup(user);
-+ debug("PAM: user '%.100s' now %svalid", user,
-+ sshpam_authctxt->valid ? "" : "in");
-+ }
-+ debug("sshpam_check_userchanged: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+}
-+
- static void
- sshpam_password_change_required(int reqd)
- {
-@@ -308,7 +359,7 @@
- static void
- import_environments(struct sshbuf *b)
- {
-- char *env;
-+ char *env, *user;
- u_int n, i, num_env;
- int r;
-
-@@ -324,6 +375,19 @@
- if ((r = sshbuf_get_u32(b, &n)) != 0)
- fatal_fr(r, "buffer error");
- sshpam_password_change_required(n != 0);
-+ if (options.permit_pam_user_change) {
-+ if ((r = sshbuf_get_cstring(b, &user, NULL)) != 0)
-+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
-+ debug("PAM: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ debug("PAM: got username '%.100s' from thread", user);
-+ if ((sshpam_err = pam_set_item(sshpam_handle, PAM_USER, user))
-+ != PAM_SUCCESS)
-+ fatal("PAM: failed to set PAM_USER: %s",
-+ pam_strerror(sshpam_handle, sshpam_err));
-+ pwfree(sshpam_authctxt->pw);
-+ sshpam_authctxt->pw = pwcopy(sshpam_getpw(user));
-+ }
-
- /* Import environment from subprocess */
- if ((r = sshbuf_get_u32(b, &num_env)) != 0)
-@@ -536,6 +600,13 @@
- if ((sshpam_err = check_pam_user(sshpam_authctxt)) != PAM_SUCCESS)
- goto auth_fail;
-
-+ if (options.permit_pam_user_change) {
-+ debug("sshpam_thread: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ sshpam_check_userchanged();
-+ debug("sshpam_thread: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ }
- if (!do_pam_account()) {
- /* Preserve PAM_PERM_DENIED and PAM_USER_UNKNOWN.
- * Backward compatibility for other errors. */
-@@ -560,6 +631,13 @@
- if ((r = sshbuf_put_u32(buffer, sshpam_account_status)) != 0 ||
- (r = sshbuf_put_u32(buffer, sshpam_authctxt->force_pwchange)) != 0)
- fatal_fr(r, "buffer error");
-+ if (options.permit_pam_user_change) {
-+ debug("sshpam_thread: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ if ((r = sshbuf_put_cstring(buffer,
-+ sshpam_authctxt->pw->pw_name)) != 0)
-+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
-+ }
-
- /* Export any environment strings set in child */
- for (i = 0; environ[i] != NULL; i++) {
-@@ -749,6 +827,8 @@
- options.use_dns));
- sshpam_laddr = get_local_ipaddr(
- ssh_packet_get_connection_in(ssh));
-+ /* Save so allowed_user can be called later */
-+ sshpam_ssh = ssh;
- }
- if (sshpam_rhost != NULL && strcmp(sshpam_rhost, "UNKNOWN") != 0) {
- debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost);
-@@ -1083,6 +1163,18 @@
- debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
- pam_strerror(sshpam_handle, sshpam_err));
-
-+ if (options.permit_pam_user_change) {
-+ debug("do_pam_account: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ sshpam_check_userchanged();
-+ debug("do_pam_account: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ if (getpwnam(sshpam_authctxt->pw->pw_name) == NULL)
-+ fatal("PAM: completed authentication but PAM account invalid");
-+ debug("do_pam_account: user is '%.100s'",
-+ sshpam_authctxt->pw->pw_name);
-+ }
-+
- if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
- sshpam_account_status = 0;
- return (sshpam_account_status);
-@@ -1291,6 +1383,9 @@
- expose_authinfo(__func__);
-
- sshpam_err = pam_authenticate(sshpam_handle, flags);
-+ if (options.permit_pam_user_change) {
-+ sshpam_check_userchanged();
-+ }
- sshpam_password = NULL;
- free(fake);
- if (sshpam_err == PAM_SUCCESS)
-diff -Nur openssh-10.3p1.orig/auth-pam.h openssh-10.3p1/auth-pam.h
---- openssh-10.3p1.orig/auth-pam.h 2026-07-01 09:38:00.184042341 +0200
-+++ openssh-10.3p1/auth-pam.h 2026-07-01 10:58:10.049644021 +0200
-@@ -44,5 +44,6 @@
- void sshpam_set_maxtries_reached(int);
- int is_pam_session_open(void);
- int sshpam_priv_kbdint_authdone(void *ctxtp);
-+struct passwd *sshpam_getpw(const char *);
-
- #endif /* USE_PAM */
-diff -Nur openssh-10.3p1.orig/canohost.c openssh-10.3p1/canohost.c
---- openssh-10.3p1.orig/canohost.c 2026-07-01 09:38:00.318724681 +0200
-+++ openssh-10.3p1/canohost.c 2026-07-01 10:39:43.571298682 +0200
-@@ -17,6 +17,7 @@
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/un.h>
-+#include <sys/param.h> /* for MAXHOSTNAMELEN */
-
- #include <netinet/in.h>
- #include <arpa/inet.h>
-@@ -300,3 +301,33 @@
- {
- return get_sock_port(sock, 1);
- }
-+
-+void
-+resolve_localhost(char **host)
-+{
-+ struct hostent *hostinfo;
-+
-+ hostinfo = gethostbyname(*host);
-+ if (hostinfo == NULL || hostinfo->h_name == NULL) {
-+ debug("gethostbyname(%s) failed", *host);
-+ return;
-+ }
-+ if (hostinfo->h_addrtype == AF_INET) {
-+ struct in_addr addr;
-+ addr = *(struct in_addr *)(hostinfo->h_addr);
-+ if (ntohl(addr.s_addr) == INADDR_LOOPBACK) {
-+ char buf[MAXHOSTNAMELEN];
-+ if (gethostname(buf, sizeof(buf)) < 0) {
-+ debug("gethostname() failed");
-+ return;
-+ }
-+ hostinfo = gethostbyname(buf);
-+ free(*host);
-+ if (hostinfo == NULL || hostinfo->h_name == NULL) {
-+ *host = xstrdup(buf);
-+ } else {
-+ *host = xstrdup(hostinfo->h_name);
-+ }
-+ }
-+ }
-+}
-diff -Nur openssh-10.3p1.orig/canohost.h openssh-10.3p1/canohost.h
---- openssh-10.3p1.orig/canohost.h 2026-07-01 09:38:00.318777378 +0200
-+++ openssh-10.3p1/canohost.h 2026-07-01 10:39:43.571619656 +0200
-@@ -26,4 +26,6 @@
-
- #endif /* _CANOHOST_H */
-
-+void resolve_localhost(char **host);
-+
- void ipv64_normalise_mapped(struct sockaddr_storage *, socklen_t *);
-diff -Nur openssh-10.3p1.orig/configure.ac openssh-10.3p1/configure.ac
---- openssh-10.3p1.orig/configure.ac 2026-07-01 09:38:00.885129959 +0200
-+++ openssh-10.3p1/configure.ac 2026-07-01 10:39:43.572570861 +0200
-@@ -5153,6 +5153,14 @@
- AC_CHECK_HEADER([gssapi_krb5.h], ,
- [ CPPFLAGS="$oldCPP" ])
-
-+ # If we're using some other GSSAPI
-+ if test -n "$GSSAPI" ; then
-+ AC_MSG_ERROR([Previously configured GSSAPI library conflicts with Kerberos GSI.])
-+ fi
-+
-+ if test -z "$GSSAPI"; then
-+ GSSAPI="KRB5";
-+ fi
- fi
- fi
- if test -n "${rpath_opt}" ; then
-@@ -5201,6 +5209,40 @@
- AC_SUBST([K5LIBS])
- AC_SUBST([CHANNELLIBS])
-
-+# Check whether the user wants GSI (Globus) support
-+gsi="no"
-+AC_ARG_WITH(gsi,
-+ [ --with-gsi Enable Globus GSI authentication support],
-+ [
-+ gsi="$withval"
-+ ]
-+)
-+
-+if test "x$gsi" != "xno" ; then
-+ # Globus GSSAPI configuration
-+ AC_MSG_CHECKING(for Globus GSI)
-+ AC_DEFINE(GSI, 1, [Define if you want GSI/Globus authentication support.])
-+
-+ if test -n "$GSSAPI" ; then
-+ AC_MSG_ERROR([Previously configured GSSAPI library conflicts with Globus GSI.])
-+ fi
-+
-+ if test -z "$GSSAPI" ; then
-+ GSSAPI="GSI"
-+ fi
-+
-+ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
-+ if test "x$PKGCONFIG" != "xno"; then
-+ LIBS="$LIBS `$PKGCONFIG --libs globus-gss-assist globus-gssapi-gsi globus-common`"
-+ CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags globus-gss-assist globus-gssapi-gsi globus-common`"
-+ fi
-+
-+ AC_DEFINE(GSSAPI)
-+ AC_DEFINE(HAVE_GSSAPI_H)
-+
-+ AC_CHECK_FUNCS(globus_gss_assist_map_and_authorize)
-+fi
-+
- # Looking for programs, paths and files
-
- PRIVSEP_PATH=/var/empty
-diff -Nur openssh-10.3p1.orig/gss-genr.c openssh-10.3p1/gss-genr.c
---- openssh-10.3p1.orig/gss-genr.c 2026-07-01 09:38:00.912831507 +0200
-+++ openssh-10.3p1/gss-genr.c 2026-07-01 10:39:43.573346884 +0200
-@@ -41,6 +41,7 @@
- #include "ssherr.h"
- #include "sshbuf.h"
- #include "log.h"
-+#include "canohost.h"
- #include "ssh2.h"
- #include "cipher.h"
- #include "sshkey.h"
-@@ -426,9 +427,18 @@
- ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
- {
- gss_buffer_desc gssbuf;
-+ char *xhost;
- char *val;
-
-- xasprintf(&val, "host@%s", host);
-+ /* Make a copy of the host name, in case it was returned by a
-+ * previous call to gethostbyname(). */
-+ xhost = xstrdup(host);
-+
-+ /* Make sure we have the FQDN. Some GSSAPI implementations don't do
-+ * this for us themselves */
-+ resolve_localhost(&xhost);
-+
-+ xasprintf(&val, "host@%s", xhost);
- gssbuf.value = val;
- gssbuf.length = strlen(gssbuf.value);
-
-@@ -436,6 +446,7 @@
- &gssbuf, GSS_C_NT_HOSTBASED_SERVICE, &ctx->name)))
- ssh_gssapi_error(ctx);
-
-+ free(xhost);
- free(gssbuf.value);
- return (ctx->major);
- }
-diff -Nur openssh-10.3p1.orig/gss-serv.c openssh-10.3p1/gss-serv.c
---- openssh-10.3p1.orig/gss-serv.c 2026-07-01 09:38:00.868214782 +0200
-+++ openssh-10.3p1/gss-serv.c 2026-07-01 14:05:39.045640059 +0200
-@@ -52,10 +52,12 @@
- #include "monitor_wrap.h"
-
- extern ServerOptions options;
-+extern Authctxt *the_authctxt;
-
- static ssh_gssapi_client gssapi_client =
-- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
-- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
-+ { {0, NULL}, GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
-+ GSS_C_NO_NAME, GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL},
-+ GSS_C_NO_CONTEXT, 0, 0, NULL, 0};
-
- ssh_gssapi_mech gssapi_null_mech =
- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
-@@ -63,14 +65,26 @@
- #ifdef KRB5
- extern ssh_gssapi_mech gssapi_kerberos_mech;
- #endif
-+#ifdef GSI
-+extern ssh_gssapi_mech gssapi_gsi_mech;
-+extern ssh_gssapi_mech gssapi_gsi_mech_micv2;
-+#endif
-
- ssh_gssapi_mech* supported_mechs[]= {
- #ifdef KRB5
- &gssapi_kerberos_mech,
- #endif
-+#ifdef GSI
-+ &gssapi_gsi_mech_micv2,
-+ &gssapi_gsi_mech,
-+#endif
- &gssapi_null_mech,
- };
-
-+#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
-+static int limited = 0;
-+#endif
-+
- /*
- * ssh_gssapi_supported_oids() can cause sandbox violations, so prepare the
- * list of supported mechanisms before privsep is set up.
-@@ -231,6 +245,10 @@
- (*flags & GSS_C_INTEG_FLAG))) && (ctx->major == GSS_S_COMPLETE)) {
- if (ssh_gssapi_getclient(ctx, &gssapi_client))
- fatal("Couldn't convert client name");
-+#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
-+ if (flags && (*flags & GSS_C_GLOBUS_LIMITED_PROXY_FLAG))
-+ limited=1;
-+#endif
- }
-
- return (status);
-@@ -250,6 +268,20 @@
-
- tok = ename->value;
-
-+#ifdef GSI /* GSI gss_export_name() is broken. */
-+ if (((ctx->oid->length == gssapi_gsi_mech.oid.length) &&
-+ (memcmp(ctx->oid->elements, gssapi_gsi_mech.oid.elements,
-+ gssapi_gsi_mech.oid.length) == 0)) ||
-+ ((ctx->oid->length == gssapi_gsi_mech_micv2.oid.length) &&
-+ (memcmp(ctx->oid->elements, gssapi_gsi_mech_micv2.oid.elements,
-+ gssapi_gsi_mech_micv2.oid.length) == 0))) {
-+ name->length = ename->length;
-+ name->value = xmalloc(ename->length+1);
-+ memcpy(name->value, ename->value, ename->length);
-+ return GSS_S_COMPLETE;
-+ }
-+#endif
-+
- /*
- * Check that ename is long enough for all of the fixed length
- * header, and that the initial ID bytes are correct
-@@ -298,6 +330,7 @@
- }
-
-
-+#ifdef KRB5
- /* Extract authentication indicators from the Kerberos ticket. Authentication
- * indicators are GSSAPI name attributes for the name "auth-indicators".
- * Multiple indicators might be present in the ticket.
-@@ -385,6 +418,7 @@
- gss_release_buffer_set(&ctx->minor, &attrs);
- return ctx->major;
- }
-+#endif
-
- /* Extract the client details from a given context. This can only reliably
- * be called once for a context */
-@@ -399,21 +433,24 @@
- gss_buffer_desc ename = GSS_C_EMPTY_BUFFER;
-
- if (options.gss_store_rekey && client->used && ctx->client_creds) {
-- if (client->mech->oid.length != ctx->oid->length ||
-- (memcmp(client->mech->oid.elements,
-+ if (client->oid.length != ctx->oid->length ||
-+ (memcmp(client->oid.elements,
- ctx->oid->elements, ctx->oid->length) !=0)) {
- debug("Rekeyed credentials have different mechanism");
- return GSS_S_COMPLETE;
- }
-
-- if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
-- ctx->client_creds, ctx->oid, &new_name,
-+ /* Call gss_inquire_cred rather than gss_inquire_cred_by_mech
-+ because GSI doesn't support the latter. -jbasney */
-+
-+ if ((ctx->major = gss_inquire_cred(&ctx->minor,
-+ ctx->client_creds, &new_name,
- NULL, NULL, NULL))) {
- ssh_gssapi_error(ctx);
- return (ctx->major);
- }
-
-- ctx->major = gss_compare_name(&ctx->minor, client->name,
-+ ctx->major = gss_compare_name(&ctx->minor, client->cred_name,
- new_name, &equal);
-
- if (GSS_ERROR(ctx->major)) {
-@@ -428,9 +465,9 @@
-
- debug("Marking rekeyed credentials for export");
-
-- gss_release_name(&ctx->minor, &client->name);
-+ gss_release_name(&ctx->minor, &client->cred_name);
- gss_release_cred(&ctx->minor, &client->creds);
-- client->name = new_name;
-+ client->cred_name = new_name;
- client->creds = ctx->client_creds;
- ctx->client_creds = GSS_C_NO_CREDENTIAL;
- client->updated = 1;
-@@ -447,12 +484,17 @@
- i++;
- }
-
-+ if (client->oid.elements == NULL)
-+ client->oid = *ctx->oid;
- if (client->mech == NULL)
- return GSS_S_FAILURE;
-
-+ /* Call gss_inquire_cred rather than gss_inquire_cred_by_mech
-+ because GSI doesn't support the latter. -jbasney */
-+
- if (ctx->client_creds &&
-- (ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
-- ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) {
-+ (ctx->major = gss_inquire_cred(&ctx->minor,
-+ ctx->client_creds, &client->cred_name, NULL, NULL, NULL))) {
- ssh_gssapi_error(ctx);
- return (ctx->major);
- }
-@@ -469,25 +511,37 @@
- return (ctx->major);
- }
-
-- if ((ctx->major = ssh_gssapi_parse_ename(ctx,&ename,
-+ if ((client->mech->oid.elements != NULL) &&
-+ (ctx->major = ssh_gssapi_parse_ename(ctx,&ename,
- &client->exportedname))) {
- return (ctx->major);
- }
-
-+ if ((ctx->major = gss_duplicate_name(&ctx->minor, ctx->client,
-+ &client->ctx_name)))
-+ return ctx->major;
-+
- gss_release_buffer(&ctx->minor, &ename);
-+#ifdef KRB5
- /* Retrieve authentication indicators, if they exist */
- if ((ctx->major = ssh_gssapi_getindicators(ctx,
- ctx->client, client))) {
- ssh_gssapi_error(ctx);
- return (ctx->major);
- }
-+#endif
-
- /* We can't copy this structure, so we just move the pointer to it */
- client->creds = ctx->client_creds;
- ctx->client_creds = GSS_C_NO_CREDENTIAL;
-+
-+ /* needed for globus_gss_assist_map_and_authorize() */
-+ client->context = ctx->context;
-+
- return (ctx->major);
- }
-
-+#ifdef KRB5
- /* Returns non-zero if Kerberos credentials have already been stored. */
- int
- ssh_gssapi_credentials_stored(void)
-@@ -847,12 +901,19 @@
-
- unsetenv("KRB5CCNAME");
- }
-+#endif
-
- #ifndef KRB5
- /* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
- void
- ssh_gssapi_cleanup_creds(void)
- {
-+ if (gssapi_client.store.filename != NULL) {
-+ /* Unlink probably isn't sufficient */
-+ debug("removing gssapi cred file\"%s\"",
-+ gssapi_client.store.filename);
-+ unlink(gssapi_client.store.filename);
-+ }
- }
-
- /*
-@@ -875,6 +936,11 @@
- }
-
- if (gssapi_client.mech && gssapi_client.mech->storecreds) {
-+ if (options.gss_creds_path) {
-+ gssapi_client.store.filename =
-+ expand_authorized_keys(options.gss_creds_path,
-+ the_authctxt->pw);
-+ }
- return (*gssapi_client.mech->storecreds)(&gssapi_client);
- } else
- debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism");
-@@ -908,11 +974,13 @@
-
- (void) kex; /* used in privilege separation */
-
-- if (gssapi_client.exportedname.length == 0 ||
-- gssapi_client.exportedname.value == NULL) {
-- debug("No suitable client data");
-+#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
-+ if (limited && options.gsi_allow_limited_proxy != 1) {
-+ debug("limited proxy not acceptable for remote login");
- return 0;
- }
-+#endif
-+
- if (gssapi_client.mech && gssapi_client.mech->userok)
- if ((*gssapi_client.mech->userok)(&gssapi_client, user)) {
- gssapi_client.used = 1;
-@@ -923,6 +991,7 @@
- gss_release_buffer(&lmin, &gssapi_client.displayname);
- gss_release_buffer(&lmin, &gssapi_client.exportedname);
- gss_release_cred(&lmin, &gssapi_client.creds);
-+ gss_release_name(&lmin, &gssapi_client.ctx_name);
-
- if (gssapi_client.indicators != NULL) {
- for (i = 0; gssapi_client.indicators[i] != NULL; i++)
-@@ -938,6 +1007,24 @@
- return (0);
- }
-
-+/* Priviledged */
-+int
-+ssh_gssapi_localname(char **user)
-+{
-+ *user = NULL;
-+ if (gssapi_client.displayname.length == 0 ||
-+ gssapi_client.displayname.value == NULL) {
-+ debug("No suitable client data");
-+ return (0);
-+ }
-+ if (gssapi_client.mech && gssapi_client.mech->localname) {
-+ return((*gssapi_client.mech->localname)(&gssapi_client,user));
-+ } else {
-+ debug("Unknown client authentication type");
-+ }
-+ return (0);
-+}
-+
- /* These bits are only used for rekeying. The unpriviledged child is running
- * as the user, the monitor is root.
- *
-@@ -964,9 +1051,11 @@
- pam_handle_t *pamh = NULL;
- struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL};
- char *envstr;
-+ char **p; char **pw;
- #endif
-
-- if (gssapi_client.store.envval == NULL)
-+ if (gssapi_client.store.filename == NULL &&
-+ gssapi_client.store.envval == NULL)
- return;
-
- ok = mm_ssh_gssapi_update_creds(&gssapi_client.store);
-@@ -986,6 +1075,18 @@
- if (ret)
- return;
-
-+ /* Put ssh pam stack env variables in this new pam stack env
-+ * Using pam-pkinit, KRB5CCNAME is set during do_pam_session
-+ * this addition enables pam-pkinit to access KRB5CCNAME if used
-+ * in sshd-rekey stack too
-+ */
-+ pw = p = fetch_pam_environment();
-+ while ( *pw != NULL ) {
-+ pam_putenv(pamh, *pw);
-+ pw++;
-+ }
-+ free_pam_environment(p);
-+
- xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar,
- gssapi_client.store.envval);
-
-diff -Nur openssh-10.3p1.orig/gss-serv-gsi.c openssh-10.3p1/gss-serv-gsi.c
---- openssh-10.3p1.orig/gss-serv-gsi.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/gss-serv-gsi.c 2026-07-01 10:39:43.574448578 +0200
-@@ -0,0 +1,328 @@
-+/*
-+ * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
-+ *
-+ * Redistribution and use in source and binary forms, with or without
-+ * modification, are permitted provided that the following conditions
-+ * are met:
-+ * 1. Redistributions of source code must retain the above copyright
-+ * notice, this list of conditions and the following disclaimer.
-+ * 2. Redistributions in binary form must reproduce the above copyright
-+ * notice, this list of conditions and the following disclaimer in the
-+ * documentation and/or other materials provided with the distribution.
-+ *
-+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
-+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-+ */
-+
-+#include "includes.h"
-+
-+#ifdef GSSAPI
-+#ifdef GSI
-+
-+#include <sys/types.h>
-+
-+#include <stdarg.h>
-+#include <string.h>
-+
-+#include "xmalloc.h"
-+#include "hostfile.h"
-+#include "auth.h"
-+#include "log.h"
-+#include "misc.h"
-+#include "servconf.h"
-+#include "ssh-gss.h"
-+
-+extern ServerOptions options;
-+
-+#include <globus_gss_assist.h>
-+
-+static int ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name);
-+static int ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user);
-+static int ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client);
-+static int ssh_gssapi_gsi_storecreds_micv2(ssh_gssapi_client *client);
-+static int ssh_gssapi_gsi_updatecreds(ssh_gssapi_ccache *store,
-+ ssh_gssapi_client *client);
-+static int ssh_gssapi_gsi_updatecreds_micv2(ssh_gssapi_ccache *store,
-+ ssh_gssapi_client *client);
-+
-+ssh_gssapi_mech gssapi_gsi_mech = {
-+ "dZuIebMjgUqaxvbF7hDbAw==",
-+ "GSI",
-+ {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"},
-+ NULL,
-+ &ssh_gssapi_gsi_userok,
-+ &ssh_gssapi_gsi_localname,
-+ &ssh_gssapi_gsi_storecreds,
-+ &ssh_gssapi_gsi_updatecreds
-+};
-+
-+ssh_gssapi_mech gssapi_gsi_mech_micv2 = {
-+ "vz8J1E9PzLr8b1K+0remTg==",
-+ "GSI",
-+ {10, "\x2b\x06\x01\x04\x01\x9b\x50\x01\x01\x01"},
-+ NULL,
-+ &ssh_gssapi_gsi_userok,
-+ &ssh_gssapi_gsi_localname,
-+ &ssh_gssapi_gsi_storecreds_micv2,
-+ &ssh_gssapi_gsi_updatecreds_micv2
-+};
-+
-+/*
-+ * Check if this user is OK to login under GSI. User has been authenticated
-+ * as identity in global 'client_name.value' and is trying to log in as passed
-+ * username in 'name'.
-+ *
-+ * Returns non-zero if user is authorized, 0 otherwise.
-+ */
-+static int
-+ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name)
-+{
-+ int authorized = 0;
-+ globus_result_t res;
-+#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
-+ char lname[256] = "";
-+#endif
-+
-+#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE
-+ if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) {
-+ return 0;
-+ }
-+#endif
-+
-+/* use new globus_gss_assist_map_and_authorize() interface if available */
-+#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
-+ debug("calling globus_gss_assist_map_and_authorize()");
-+ if (GLOBUS_SUCCESS !=
-+ (res = globus_gss_assist_map_and_authorize(client->context, "ssh",
-+ name, lname, 256))) {
-+ debug("%s", globus_error_print_chain(globus_error_get(res)));
-+ } else if (lname[0] && strcmp(name, lname) != 0) {
-+ debug("GSI user maps to %s, not %s", lname, name);
-+ } else {
-+ authorized = 1;
-+ }
-+#else
-+ debug("calling globus_gss_assist_userok()");
-+ if (GLOBUS_SUCCESS !=
-+ (res = (globus_gss_assist_userok(client->displayname.value,
-+ name)))) {
-+ debug("%s", globus_error_print_chain(globus_error_get(res)));
-+ } else {
-+ authorized = 1;
-+ }
-+#endif
-+
-+ logit("GSI user %s is%s authorized as target user %s",
-+ (char *) client->displayname.value, (authorized ? "" : " not"), name);
-+
-+ return authorized;
-+}
-+
-+/*
-+ * Return the local username associated with the GSI credentials.
-+ */
-+int
-+ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user)
-+{
-+ globus_result_t res;
-+#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
-+ char lname[256] = "";
-+#endif
-+
-+#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE
-+ if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) {
-+ return 0;
-+ }
-+#endif
-+
-+/* use new globus_gss_assist_map_and_authorize() interface if available */
-+#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
-+ debug("calling globus_gss_assist_map_and_authorize()");
-+ if (GLOBUS_SUCCESS !=
-+ (res = globus_gss_assist_map_and_authorize(client->context, "ssh",
-+ NULL, lname, 256))) {
-+ debug("%s", globus_error_print_chain(globus_error_get(res)));
-+ logit("failed to map GSI user %s", (char *)client->displayname.value);
-+ return 0;
-+ }
-+ *user = strdup(lname);
-+#else
-+ debug("calling globus_gss_assist_gridmap()");
-+ if (GLOBUS_SUCCESS !=
-+ (res = globus_gss_assist_gridmap(client->displayname.value, user))) {
-+ debug("%s", globus_error_print_chain(globus_error_get(res)));
-+ logit("failed to map GSI user %s", (char *)client->displayname.value);
-+ return 0;
-+ }
-+#endif
-+
-+ logit("GSI user %s mapped to target user %s",
-+ (char *) client->displayname.value, *user);
-+
-+ return 1;
-+}
-+
-+/*
-+ * Export GSI credentials to disk.
-+ */
-+static int
-+ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client)
-+{
-+ OM_uint32 major_status;
-+ OM_uint32 minor_status;
-+ gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER;
-+ char * p;
-+
-+ if (!client || !client->creds) {
-+ return 0;
-+ }
-+
-+ major_status = gss_export_cred(&minor_status,
-+ client->creds,
-+ GSS_C_NO_OID,
-+ 1,
-+ &export_cred);
-+ if (GSS_ERROR(major_status) && major_status != GSS_S_UNAVAILABLE) {
-+ Gssctxt *ctx;
-+ ssh_gssapi_build_ctx(&ctx);
-+ ctx->major = major_status;
-+ ctx->minor = minor_status;
-+ ssh_gssapi_set_oid(ctx, &gssapi_gsi_mech.oid);
-+ ssh_gssapi_error(ctx);
-+ ssh_gssapi_delete_ctx(&ctx);
-+ return 0;
-+ }
-+
-+ p = strchr((char *) export_cred.value, '=');
-+ if (p == NULL) {
-+ logit("Failed to parse exported credentials string '%.100s'",
-+ (char *)export_cred.value);
-+ gss_release_buffer(&minor_status, &export_cred);
-+ return 0;
-+ }
-+ *p++ = '\0';
-+ if (strcmp((char *)export_cred.value,"X509_USER_DELEG_PROXY") == 0) {
-+ client->store.envvar = strdup("X509_USER_PROXY");
-+ } else {
-+ client->store.envvar = strdup((char *)export_cred.value);
-+ }
-+ if (access(p, R_OK) == 0) {
-+ if (client->store.filename) {
-+ if (rename(p, client->store.filename) < 0) {
-+ logit("Failed to rename %s to %s: %s", p,
-+ client->store.filename, strerror(errno));
-+ free(client->store.filename);
-+ client->store.filename = strdup(p);
-+ } else {
-+ p = client->store.filename;
-+ }
-+ } else {
-+ client->store.filename = strdup(p);
-+ }
-+ }
-+ client->store.envval = strdup(p);
-+#ifdef USE_PAM
-+ if (options.use_pam)
-+ do_pam_putenv(client->store.envvar, client->store.envval);
-+#endif
-+ gss_release_buffer(&minor_status, &export_cred);
-+
-+ return 1;
-+}
-+
-+/*
-+ * Export GSI credentials to disk.
-+ */
-+static int
-+ssh_gssapi_gsi_storecreds_micv2(ssh_gssapi_client *client)
-+{
-+ OM_uint32 major_status;
-+ OM_uint32 minor_status;
-+ gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER;
-+ char * p;
-+
-+ if (!client || !client->creds) {
-+ return 0;
-+ }
-+
-+ major_status = gss_export_cred(&minor_status,
-+ client->creds,
-+ GSS_C_NO_OID,
-+ 1,
-+ &export_cred);
-+ if (GSS_ERROR(major_status) && major_status != GSS_S_UNAVAILABLE) {
-+ Gssctxt *ctx;
-+ ssh_gssapi_build_ctx(&ctx);
-+ ctx->major = major_status;
-+ ctx->minor = minor_status;
-+ ssh_gssapi_set_oid(ctx, &gssapi_gsi_mech_micv2.oid);
-+ ssh_gssapi_error(ctx);
-+ ssh_gssapi_delete_ctx(&ctx);
-+ return 0;
-+ }
-+
-+ p = strchr((char *) export_cred.value, '=');
-+ if (p == NULL) {
-+ logit("Failed to parse exported credentials string '%.100s'",
-+ (char *)export_cred.value);
-+ gss_release_buffer(&minor_status, &export_cred);
-+ return 0;
-+ }
-+ *p++ = '\0';
-+ if (strcmp((char *)export_cred.value,"X509_USER_DELEG_PROXY") == 0) {
-+ client->store.envvar = strdup("X509_USER_PROXY");
-+ } else {
-+ client->store.envvar = strdup((char *)export_cred.value);
-+ }
-+ if (access(p, R_OK) == 0) {
-+ if (client->store.filename) {
-+ if (rename(p, client->store.filename) < 0) {
-+ logit("Failed to rename %s to %s: %s", p,
-+ client->store.filename, strerror(errno));
-+ free(client->store.filename);
-+ client->store.filename = strdup(p);
-+ } else {
-+ p = client->store.filename;
-+ }
-+ } else {
-+ client->store.filename = strdup(p);
-+ }
-+ }
-+ client->store.envval = strdup(p);
-+#ifdef USE_PAM
-+ if (options.use_pam)
-+ do_pam_putenv(client->store.envvar, client->store.envval);
-+#endif
-+ gss_release_buffer(&minor_status, &export_cred);
-+
-+ return 1;
-+}
-+
-+/*
-+ * Export updated GSI credentials to disk.
-+ */
-+static int
-+ssh_gssapi_gsi_updatecreds(ssh_gssapi_ccache *store,ssh_gssapi_client *client)
-+{
-+ return ssh_gssapi_gsi_storecreds(client);
-+}
-+
-+/*
-+ * Export updated GSI credentials to disk.
-+ */
-+static int
-+ssh_gssapi_gsi_updatecreds_micv2(ssh_gssapi_ccache *store,ssh_gssapi_client *client)
-+{
-+ return ssh_gssapi_gsi_storecreds_micv2(client);
-+}
-+
-+#endif /* GSI */
-+#endif /* GSSAPI */
-diff -Nur openssh-10.3p1.orig/gss-serv-krb5.c openssh-10.3p1/gss-serv-krb5.c
---- openssh-10.3p1.orig/gss-serv-krb5.c 2026-07-01 09:38:00.868049928 +0200
-+++ openssh-10.3p1/gss-serv-krb5.c 2026-07-01 10:39:43.574808634 +0200
-@@ -450,6 +450,34 @@
- return found_principal;
- }
-
-+/* Retrieve the local username associated with a set of Kerberos
-+ * credentials. Hopefully we can use this for the 'empty' username
-+ * logins discussed in the draft */
-+static int
-+ssh_gssapi_krb5_localname(ssh_gssapi_client *client, char **user) {
-+ krb5_principal princ;
-+ int retval;
-+
-+ if (ssh_gssapi_krb5_init() == 0)
-+ return 0;
-+
-+ if ((retval=krb5_parse_name(krb_context, client->displayname.value,
-+ &princ))) {
-+ logit("krb5_parse_name(): %.100s",
-+ krb5_get_err_text(krb_context,retval));
-+ return 0;
-+ }
-+
-+ /* We've got to return a malloc'd string */
-+ *user = (char *)xmalloc(256);
-+ if (krb5_aname_to_localname(krb_context, princ, 256, *user)) {
-+ free(*user);
-+ *user = NULL;
-+ return(0);
-+ }
-+
-+ return(1);
-+}
-
- /* This writes out any forwarded credentials from the structure populated
- * during userauth. Called after we have setuid to the user */
-@@ -544,7 +572,7 @@
- return set_env;
- }
-
--int
-+static int
- ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
- ssh_gssapi_client *client)
- {
-@@ -840,7 +868,7 @@
- {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"},
- NULL,
- &ssh_gssapi_krb5_userok,
-- NULL,
-+ &ssh_gssapi_krb5_localname,
- &ssh_gssapi_krb5_storecreds,
- &ssh_gssapi_krb5_updatecreds
- };
-diff -Nur openssh-10.3p1.orig/kexgsss.c openssh-10.3p1/kexgsss.c
---- openssh-10.3p1.orig/kexgsss.c 2026-07-01 09:38:00.320680172 +0200
-+++ openssh-10.3p1/kexgsss.c 2026-07-01 10:39:43.575164723 +0200
-@@ -48,6 +48,7 @@
- #include "digest.h"
- #include "ssherr.h"
-
-+static void kex_gss_send_error(Gssctxt *ctxt, struct ssh *ssh);
- extern ServerOptions options;
-
- static int input_kexgss_init(int, u_int32_t, struct ssh *);
-@@ -81,8 +82,10 @@
-
- debug2_f("Acquiring credentials");
-
-- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid)))
-+ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) {
-+ kex_gss_send_error(kex->gss, ssh);
- fatal("Unable to acquire credentials for the server");
-+ }
-
- ssh_gssapi_build_ctx(&kex->gss);
- if (kex->gss == NULL)
-@@ -139,13 +142,14 @@
- ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
-
- if (GSS_ERROR(gss->major)) {
-+ kex_gss_send_error(kex->gss, ssh);
- if (send_tok->length > 0) {
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
- (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
- (r = sshpkt_send(ssh)) != 0)
- fatal("sshpkt failed: %s", ssh_err(r));
- }
-- fatal("accept_ctx died");
-+ ssh_packet_disconnect(ssh, "GSSAPI Key Exchange handshake failed");
- }
-
- if (!(*ret_flags & GSS_C_MUTUAL_FLAG))
-@@ -600,4 +604,26 @@
- return kexgssgex_final(ssh, &send_tok, &ret_flags);
- }
-
-+static void
-+kex_gss_send_error(Gssctxt *ctxt, struct ssh *ssh) {
-+ char *errstr;
-+ OM_uint32 maj, min;
-+ int r;
-+
-+ errstr = mm_ssh_gssapi_last_error(ctxt, &maj, &min);
-+ if (errstr) {
-+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_ERROR)) != 0 ||
-+ (r = sshpkt_put_u32(ssh, maj)) != 0 ||
-+ (r = sshpkt_put_u32(ssh, min)) != 0 ||
-+ (r = sshpkt_put_cstring(ssh, errstr)) != 0 ||
-+ (r = sshpkt_put_cstring(ssh, "")) != 0 ||
-+ (r = sshpkt_send(ssh)) != 0)
-+ fatal("sshpkt failed: %s", ssh_err(r));
-+ if ((r = ssh_packet_write_wait(ssh)) != 0)
-+ fatal("ssh_packet_write_wait: %s", ssh_err(r));
-+ /* XXX - We should probably log the error locally here */
-+ free(errstr);
-+ }
-+}
-+
- #endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
-diff -Nur openssh-10.3p1.orig/Makefile.in openssh-10.3p1/Makefile.in
---- openssh-10.3p1.orig/Makefile.in 2026-07-01 09:38:00.921600766 +0200
-+++ openssh-10.3p1/Makefile.in 2026-07-01 10:39:43.575460349 +0200
-@@ -133,7 +133,7 @@
- auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
- auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
- monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \
-- auth2-gss.o gss-serv.o gss-serv-krb5.o \
-+ auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \
- loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
- sftp-server.o sftp-common.o \
- uidswap.o platform-listen.o $(P11OBJS) $(SKOBJS)
-@@ -144,7 +144,7 @@
- serverloop.o auth.o auth2.o auth-options.o session.o auth2-chall.o \
- groupaccess.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
- auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
-- auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \
-+ auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o kexgsss.o \
- monitor_wrap.o auth-krb5.o \
- audit.o audit-bsm.o audit-linux.o platform.o \
- loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
-diff -Nur openssh-10.3p1.orig/misc.c openssh-10.3p1/misc.c
---- openssh-10.3p1.orig/misc.c 2026-07-01 09:38:00.913617709 +0200
-+++ openssh-10.3p1/misc.c 2026-07-01 10:39:43.575565106 +0200
-@@ -464,11 +464,14 @@
- #define WHITESPACE " \t\r\n"
- #define QUOTE "\""
-
-+/* Characters considered as quotations. */
-+#define QUOTES "'\""
-+
- /* return next token in configuration line */
- static char *
- strdelim_internal(char **s, int split_equals)
- {
-- char *old;
-+ char *old, *p, *q;
- int wspace = 0;
-
- if (*s == NULL)
-@@ -476,6 +479,21 @@
-
- old = *s;
-
-+ if ((q=strchr(QUOTES, (int) *old)) && *q)
-+ {
-+ /* find next quote character, point old to start of quoted
-+ * string */
-+ for (p = ++old; *p && *p != *q; p++)
-+ ;
-+
-+ /* find start of next token */
-+ *s = (*p) ? p + strspn(p + 1, WHITESPACE) + 1 : NULL;
-+
-+ /* terminate 'old' token */
-+ *p = '\0';
-+ return (old);
-+ }
-+
- *s = strpbrk(*s,
- split_equals ? WHITESPACE QUOTE "=" : WHITESPACE QUOTE);
- if (*s == NULL)
-diff -Nur openssh-10.3p1.orig/monitor.c openssh-10.3p1/monitor.c
---- openssh-10.3p1.orig/monitor.c 2026-07-01 09:38:00.913902024 +0200
-+++ openssh-10.3p1/monitor.c 2026-07-01 10:39:43.576746215 +0200
-@@ -142,6 +142,9 @@
- int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *);
- int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *);
- int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *);
-+int mm_answer_gss_error(struct ssh *, int, struct sshbuf *);
-+int mm_answer_gss_indicate_mechs(struct ssh *, int, struct sshbuf *);
-+int mm_answer_gss_localname(struct ssh *, int, struct sshbuf *);
- int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *);
- #endif
-
-@@ -199,7 +202,7 @@
- #endif
- {MONITOR_REQ_SETCOMPAT, MON_ONCE, mm_answer_setcompat},
- {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
-- {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
-+ {MONITOR_REQ_PWNAM, MON_AUTH, mm_answer_pwnamallow},
- {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
- #ifdef WITH_SELINUX
- {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
-@@ -207,7 +210,7 @@
- {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
- {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
- #ifdef USE_PAM
-- {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start},
-+ {MONITOR_REQ_PAM_START, MON_ISAUTH, mm_answer_pam_start},
- {MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account},
- {MONITOR_REQ_PAM_INIT_CTX, MON_ONCE, mm_answer_pam_init_ctx},
- {MONITOR_REQ_PAM_QUERY, 0, mm_answer_pam_query},
-@@ -231,8 +234,11 @@
- {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx},
- {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
- {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok},
-- {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic},
-+ {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic},
- {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign},
-+ {MONITOR_REQ_GSSERR, MON_ISAUTH | MON_ONCE, mm_answer_gss_error},
-+ {MONITOR_REQ_GSSMECHS, MON_ISAUTH, mm_answer_gss_indicate_mechs},
-+ {MONITOR_REQ_GSSLOCALNAME, MON_ISAUTH, mm_answer_gss_localname},
- #endif
- {0, 0, NULL}
- };
-@@ -242,6 +248,8 @@
- {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx},
- {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
- {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign},
-+ {MONITOR_REQ_GSSERR, 0, mm_answer_gss_error},
-+ {MONITOR_REQ_GSSMECHS, 0, mm_answer_gss_indicate_mechs},
- {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds},
- #endif
- {MONITOR_REQ_STATE, MON_ONCE, mm_answer_state},
-@@ -323,6 +331,8 @@
- #ifdef GSSAPI
- /* and for the GSSAPI key exchange */
- monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
-+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1);
-+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSMECHS, 1);
- #endif
-
- /* The first few requests do not require asynchronous access */
-@@ -474,6 +484,8 @@
- #ifdef GSSAPI
- /* and for the GSSAPI key exchange */
- monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
-+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1);
-+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSMECHS, 1);
- #endif
-
- if (auth_opts->permit_pty_flag) {
-@@ -936,14 +948,17 @@
- if (!compat_set)
- fatal_f("state error: setcompat never called");
-
-- if (authctxt->attempt++ != 0)
-- fatal_f("multiple attempts for getpwnam");
--
-+ if (authctxt->user) free(authctxt->user);
- if ((r = sshbuf_get_cstring(m, &authctxt->user, NULL)) != 0)
- fatal_fr(r, "parse");
-
- pwent = getpwnamallow(ssh, authctxt->user);
-
-+#ifdef USE_PAM
-+ if (options.permit_pam_user_change)
-+ setproctitle("%s [priv]", pwent ? "[pam]" : "unknown");
-+ else
-+#endif
- setproctitle("%s [priv]", pwent ? authctxt->user : "unknown");
-
- sshbuf_reset(m);
-@@ -2299,6 +2314,79 @@
- }
-
- int
-+mm_answer_gss_error(struct ssh *ssh, int socket, struct sshbuf *m)
-+{
-+ OM_uint32 major, minor;
-+ char *msg;
-+ int r;
-+
-+ msg=ssh_gssapi_last_error(gsscontext, &major, &minor);
-+ sshbuf_reset(m);
-+ if ((r = sshbuf_put_u32(m, major)) != 0 ||
-+ (r = sshbuf_put_u32(m, minor)) != 0 ||
-+ (r = sshbuf_put_cstring(m, msg)) != 0)
-+ fatal_fr(r, "buffer error");
-+
-+ mm_request_send(socket, MONITOR_ANS_GSSERR, m);
-+
-+ free(msg);
-+
-+ return(0);
-+}
-+
-+int
-+mm_answer_gss_indicate_mechs(struct ssh *ssh, int socket, struct sshbuf *m)
-+{
-+ OM_uint32 major, minor;
-+ gss_OID_set mech_set;
-+ size_t i;
-+ int r;
-+
-+ major=gss_indicate_mechs(&minor, &mech_set);
-+
-+ sshbuf_reset(m);
-+ if ((r = sshbuf_put_u32(m, major)) != 0 ||
-+ (r = sshbuf_put_u32(m, mech_set->count)) != 0)
-+ fatal_fr(r, "buffer error");
-+ for (i = 0; i < mech_set->count; i++) {
-+ if ((r = sshbuf_put_string(m, mech_set->elements[i].elements,
-+ mech_set->elements[i].length)) != 0)
-+ fatal_fr(r, "buffer error");
-+ }
-+
-+ gss_release_oid_set(&minor, &mech_set);
-+
-+ mm_request_send(socket, MONITOR_ANS_GSSMECHS, m);
-+
-+ return(0);
-+}
-+
-+int
-+mm_answer_gss_localname(struct ssh *ssh, int socket, struct sshbuf *m)
-+{
-+ char *name;
-+ int r;
-+
-+ ssh_gssapi_localname(&name);
-+
-+ sshbuf_reset(m);
-+ if (name) {
-+ if ((r = sshbuf_put_cstring(m, name)) != 0)
-+ fatal_fr(r, "buffer error");
-+ debug3_f("sending result %s", name);
-+ free(name);
-+ } else {
-+ if ((r = sshbuf_put_cstring(m, "")) != 0)
-+ fatal_fr(r, "buffer error");
-+ debug3_f("sending result \"\"");
-+ }
-+
-+ mm_request_send(socket, MONITOR_ANS_GSSLOCALNAME, m);
-+
-+ return(0);
-+}
-+
-+int
- mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m)
- {
- gss_buffer_desc data;
-diff -Nur openssh-10.3p1.orig/monitor.h openssh-10.3p1/monitor.h
---- openssh-10.3p1.orig/monitor.h 2026-07-01 09:38:00.622911892 +0200
-+++ openssh-10.3p1/monitor.h 2026-07-01 10:39:43.577070410 +0200
-@@ -77,6 +77,10 @@
-
- MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
- MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
-+
-+ MONITOR_REQ_GSSMECHS = 200, MONITOR_ANS_GSSMECHS = 201,
-+ MONITOR_REQ_GSSLOCALNAME = 202, MONITOR_ANS_GSSLOCALNAME = 203,
-+ MONITOR_REQ_GSSERR = 204, MONITOR_ANS_GSSERR = 205
- };
-
- struct ssh;
-diff -Nur openssh-10.3p1.orig/monitor_wrap.c openssh-10.3p1/monitor_wrap.c
---- openssh-10.3p1.orig/monitor_wrap.c 2026-07-01 09:38:00.642666971 +0200
-+++ openssh-10.3p1/monitor_wrap.c 2026-07-01 10:39:43.577768807 +0200
-@@ -1226,6 +1226,94 @@
- return (authenticated);
- }
-
-+char *
-+mm_ssh_gssapi_last_error(Gssctxt *ctx, OM_uint32 *major, OM_uint32 *minor)
-+{
-+ struct sshbuf *m = NULL;
-+ OM_uint32 maj,min;
-+ char *errstr;
-+ int r;
-+
-+ if ((m = sshbuf_new()) == NULL)
-+ fatal_f("sshbuf_new failed");
-+
-+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSERR, m);
-+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSERR, m);
-+
-+ if ((r = sshbuf_get_u32(m, &maj)) != 0 ||
-+ (r = sshbuf_get_u32(m, &min)) != 0)
-+ fatal_fr(r, "buffer error");
-+
-+ if (major) *major=maj;
-+ if (minor) *minor=min;
-+
-+ if ((r = sshbuf_get_cstring(m, &errstr, NULL)) != 0)
-+ fatal_fr(r, "buffer error");
-+
-+ sshbuf_free(m);
-+
-+ return(errstr);
-+}
-+
-+OM_uint32
-+mm_gss_indicate_mechs(OM_uint32 *minor_status, gss_OID_set *mech_set)
-+{
-+ struct sshbuf *m = NULL;
-+ OM_uint32 major,minor;
-+ int count;
-+ gss_OID_desc oid;
-+ size_t length;
-+ int r;
-+
-+ if ((m = sshbuf_new()) == NULL)
-+ fatal_f("sshbuf_new failed");
-+
-+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSMECHS, m);
-+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSMECHS, m);
-+ if ((r = sshbuf_get_u32(m, &major)) != 0 ||
-+ (r = sshbuf_get_u32(m, &count)) != 0)
-+ fatal_fr(r, "buffer error");
-+
-+ gss_create_empty_oid_set(&minor, mech_set);
-+ while(count-->0) {
-+ if ((r = sshbuf_get_string(m, (u_char **)&oid.elements, &length)) != 0)
-+ fatal_fr(r, "buffer error");
-+ oid.length = length;
-+ gss_add_oid_set_member(&minor, &oid, mech_set);
-+ }
-+
-+ sshbuf_free(m);
-+
-+ return(major);
-+}
-+
-+int
-+mm_ssh_gssapi_localname(char **lname)
-+{
-+ struct sshbuf *m = NULL;
-+ int r;
-+
-+ if ((m = sshbuf_new()) == NULL)
-+ fatal_f("sshbuf_new failed");
-+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSLOCALNAME, m);
-+
-+ debug3_f("waiting for MONITOR_ANS_GSSLOCALNAME");
-+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSLOCALNAME,
-+ m);
-+
-+ if ((r = sshbuf_get_cstring(m, lname, NULL)) != 0)
-+ fatal_fr(r, "buffer error");
-+
-+ sshbuf_free(m);
-+ if ((*lname == NULL) || (*lname[0] == '\0')) {
-+ debug3_f("gssapi identity mapping failed");
-+ } else {
-+ debug3_f("gssapi identity mapped to %s", *lname);
-+ }
-+
-+ return(0);
-+}
-+
- OM_uint32
- mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash)
- {
-diff -Nur openssh-10.3p1.orig/monitor_wrap.h openssh-10.3p1/monitor_wrap.h
---- openssh-10.3p1.orig/monitor_wrap.h 2026-07-01 09:38:00.642830417 +0200
-+++ openssh-10.3p1/monitor_wrap.h 2026-07-01 10:39:43.578483165 +0200
-@@ -77,6 +77,10 @@
- int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex);
- OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
- OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
-+int mm_ssh_gssapi_localname(char **user);
-+OM_uint32 mm_gss_indicate_mechs(OM_uint32 *minor_status,
-+ gss_OID_set *mech_set);
-+char *mm_ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *maj, OM_uint32 *min);
- int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *);
- #endif
-
-diff -Nur openssh-10.3p1.orig/readconf.c openssh-10.3p1/readconf.c
---- openssh-10.3p1.orig/readconf.c 2026-07-01 09:38:00.914346251 +0200
-+++ openssh-10.3p1/readconf.c 2026-07-01 10:39:43.578565103 +0200
-@@ -2943,11 +2943,11 @@
- if (options->pubkey_authentication == -1)
- options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL;
- if (options->gss_authentication == -1)
-- options->gss_authentication = 0;
-+ options->gss_authentication = 1;
- if (options->gss_keyex == -1)
-- options->gss_keyex = 0;
-+ options->gss_keyex = 1;
- if (options->gss_deleg_creds == -1)
-- options->gss_deleg_creds = 0;
-+ options->gss_deleg_creds = 1;
- if (options->gss_trust_dns == -1)
- options->gss_trust_dns = 0;
- if (options->gss_renewal_rekey == -1)
-diff -Nur openssh-10.3p1.orig/readconf.h openssh-10.3p1/readconf.h
---- openssh-10.3p1.orig/readconf.h 2026-07-01 09:38:00.321636232 +0200
-+++ openssh-10.3p1/readconf.h 2026-07-01 10:39:43.579565102 +0200
-@@ -80,6 +80,8 @@
- char *host_key_alias; /* hostname alias for .ssh/known_hosts */
- char *proxy_command; /* Proxy command for connecting the host. */
- char *user; /* User to log in as. */
-+ int implicit; /* Login user was not specified.
-+ Server may choose based on authctxt. */
- int escape_char; /* Escape character; -2 = none */
-
- u_int num_system_hostfiles; /* Paths for /etc/ssh/ssh_known_hosts */
-diff -Nur openssh-10.3p1.orig/servconf.c openssh-10.3p1/servconf.c
---- openssh-10.3p1.orig/servconf.c 2026-07-01 09:38:00.914637850 +0200
-+++ openssh-10.3p1/servconf.c 2026-07-01 11:46:42.482986188 +0200
-@@ -96,6 +96,7 @@
- /* Portable-specific options */
- options->use_pam = -1;
- options->pam_service_name = NULL;
-+ options->permit_pam_user_change = -1;
-
- /* Standard Options */
- options->num_ports = 0;
-@@ -146,6 +147,7 @@
- options->gss_cleanup_creds = -1;
- options->gss_deleg_creds = -1;
- options->gss_strict_acceptor = -1;
-+ options->gsi_allow_limited_proxy = -1;
- options->gss_indicators = NULL;
- options->gss_store_rekey = -1;
- options->gss_kex_algorithms = NULL;
-@@ -323,6 +325,8 @@
- options->use_pam = 0;
- if (options->pam_service_name == NULL)
- options->pam_service_name = xstrdup(SSHD_PAM_SERVICE);
-+ if (options->permit_pam_user_change == -1)
-+ options->permit_pam_user_change = 0;
-
- /* Standard Options */
- if (options->num_host_key_files == 0) {
-@@ -400,15 +404,17 @@
- if (options->kerberos_unique_ccache == -1)
- options->kerberos_unique_ccache = 0;
- if (options->gss_authentication == -1)
-- options->gss_authentication = 0;
-+ options->gss_authentication = 1;
- if (options->gss_keyex == -1)
-- options->gss_keyex = 0;
-+ options->gss_keyex = 1;
- if (options->gss_cleanup_creds == -1)
- options->gss_cleanup_creds = 1;
- if (options->gss_deleg_creds == -1)
- options->gss_deleg_creds = 1;
- if (options->gss_strict_acceptor == -1)
- options->gss_strict_acceptor = 1;
-+ if (options->gsi_allow_limited_proxy == -1)
-+ options->gsi_allow_limited_proxy = 0;
- if (options->gss_allow_s4u2self == -1)
- options->gss_allow_s4u2self = 0;
- if (options->gss_store_rekey == -1)
-@@ -588,7 +594,7 @@
- typedef enum {
- sBadOption, /* == unknown option */
- /* Portable-specific options */
-- sUsePAM, sPAMServiceName,
-+ sUsePAM, sPAMServiceName, sPermitPAMUserChange,
- /* Standard Options */
- sPort, sHostKeyFile, sLoginGraceTime,
- sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose,
-@@ -610,6 +616,8 @@
- sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
- sGssAllowS4U2Self, sGssProxyS4U2Services,
- sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
-+ sGssCredsPath,
-+ sGsiAllowLimitedProxy,
- sGssIndicators, sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- sAcceptEnv, sSetEnv, sPermitTunnel,
- sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
-@@ -644,9 +652,11 @@
- #ifdef USE_PAM
- { "usepam", sUsePAM, SSHCFG_GLOBAL },
- { "pamservicename", sPAMServiceName, SSHCFG_ALL },
-+ { "permitpamuserchange", sPermitPAMUserChange, SSHCFG_GLOBAL },
- #else
- { "usepam", sUnsupported, SSHCFG_GLOBAL },
- { "pamservicename", sUnsupported, SSHCFG_ALL },
-+ { "permitpamuserchange", sUnsupported, SSHCFG_GLOBAL },
- #endif
- { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL },
- /* Standard Options */
-@@ -702,6 +712,12 @@
- { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
- { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL },
- { "gssapidelegatecredentials", sGssDelegateCreds, SSHCFG_GLOBAL },
-+ { "gssapicredentialspath", sGssCredsPath, SSHCFG_GLOBAL },
-+#ifdef GSI
-+ { "gsiallowlimitedproxy", sGsiAllowLimitedProxy, SSHCFG_GLOBAL },
-+#else
-+ { "gsiallowlimitedproxy", sUnsupported, SSHCFG_GLOBAL },
-+#endif
- { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
- { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
- { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
-@@ -715,6 +731,8 @@
- { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
- { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
- { "gssapidelegatecredentials", sUnsupported, SSHCFG_GLOBAL },
-+ { "gssapicredentialspath", sUnsupported, SSHCFG_GLOBAL },
-+ { "gsiallowlimitedproxy", sUnsupported, SSHCFG_GLOBAL },
- { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
- { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
- { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
-@@ -791,6 +809,8 @@
- { "permitlisten", sPermitListen, SSHCFG_ALL },
- { "forcecommand", sForceCommand, SSHCFG_ALL },
- { "chrootdirectory", sChrootDirectory, SSHCFG_ALL },
-+ { "disableusagestats", sUnsupported, SSHCFG_GLOBAL},
-+ { "usagestatstargets", sUnsupported, SSHCFG_GLOBAL},
- { "hostcertificate", sHostCertificate, SSHCFG_GLOBAL },
- { "revokedkeys", sRevokedKeys, SSHCFG_ALL },
- { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
-@@ -1472,6 +1492,10 @@
- *charptr = xstrdup(arg);
- break;
-
-+ case sPermitPAMUserChange:
-+ intptr = &options->permit_pam_user_change;
-+ goto parse_flag;
-+
- /* Standard Options */
- case sBadOption:
- goto out;
-@@ -1735,6 +1759,10 @@
- intptr = &options->gss_deleg_creds;
- goto parse_flag;
-
-+ case sGssCredsPath:
-+ charptr = &options->gss_creds_path;
-+ goto parse_filename;
-+
- case sGssStrictAcceptor:
- intptr = &options->gss_strict_acceptor;
- goto parse_flag;
-@@ -1802,6 +1830,12 @@
- }
- break;
-
-+#ifdef GSI
-+ case sGsiAllowLimitedProxy:
-+ intptr = &options->gsi_allow_limited_proxy;
-+ goto parse_flag;
-+#endif
-+
- case sPasswordAuthentication:
- intptr = &options->password_authentication;
- goto parse_flag;
-@@ -3103,6 +3137,7 @@
- M_CP_INTOPT(password_authentication);
- M_CP_INTOPT(gss_authentication);
- M_CP_INTOPT(gss_allow_s4u2self);
-+ M_CP_INTOPT(gss_deleg_creds);
- M_CP_INTOPT(pubkey_authentication);
- M_CP_INTOPT(pubkey_auth_options);
- M_CP_INTOPT(kerberos_authentication);
-diff -Nur openssh-10.3p1.orig/servconf.h openssh-10.3p1/servconf.h
---- openssh-10.3p1.orig/servconf.h 2026-07-01 09:38:00.868650833 +0200
-+++ openssh-10.3p1/servconf.h 2026-07-01 10:39:43.581358497 +0200
-@@ -156,10 +156,12 @@
- * be stored in per-session ccache */
- int use_kuserok;
- int enable_k5users;
-+ int gsi_allow_limited_proxy; /* If true, accept limited proxies */
- int gss_authentication; /* If true, permit GSSAPI authentication */
- int gss_keyex; /* If true, permit GSSAPI key exchange */
- int gss_cleanup_creds; /* If true, destroy cred cache on logout */
- int gss_deleg_creds; /* If true, accept delegated GSS credentials */
-+ char *gss_creds_path; /* Use non-default credentials path */
- int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */
- int gss_allow_s4u2self; /* 0=no, INT_MAX=yes (GSS_C_INDEFINITE), >0=ticket lifetime s */
- char **gss_proxy_services; /* S4U2Proxy target service principals */
-@@ -227,6 +229,7 @@
-
- int use_pam; /* Enable auth via PAM */
- char *pam_service_name;
-+ int permit_pam_user_change; /* Allow PAM to change user name */
-
- int permit_tun;
-
-diff -Nur openssh-10.3p1.orig/ssh.1 openssh-10.3p1/ssh.1
---- openssh-10.3p1.orig/ssh.1 2026-07-01 09:38:00.322437292 +0200
-+++ openssh-10.3p1/ssh.1 2026-07-01 10:39:43.581840092 +0200
-@@ -1531,6 +1531,18 @@
- on to new connections).
- .It Ev USER
- Set to the name of the user logging in.
-+.It Ev X509_CERT_DIR
-+Used for GSI authentication. Specifies a non-standard location for the
-+CA certificates directory.
-+.It Ev X509_USER_CERT
-+Used for GSI authentication. Specifies a non-standard location for the
-+certificate to be used for authentication to the server.
-+.It Ev X509_USER_KEY
-+Used for GSI authentication. Specifies a non-standard location for the
-+private key to be used for authentication to the server.
-+.It Ev X509_USER_PROXY
-+Used for GSI authentication. Specifies a non-standard location for the
-+proxy credential to be used for authentication to the server.
- .El
- .Pp
- Additionally,
-diff -Nur openssh-10.3p1.orig/ssh.c openssh-10.3p1/ssh.c
---- openssh-10.3p1.orig/ssh.c 2026-07-01 09:38:00.886465932 +0200
-+++ openssh-10.3p1/ssh.c 2026-07-01 10:39:43.582465751 +0200
-@@ -579,6 +579,38 @@
- fatal("Can't open user config file %.100s: "
- "%.100s", config, strerror(errno));
- } else {
-+ /*
-+ * Since the config file parsing code aborts if it sees
-+ * options it doesn't recognize, allow users to put
-+ * options specific to compile-time add-ons in alternate
-+ * config files so their primary config file will
-+ * interoperate SSH versions that don't support those
-+ * options.
-+ */
-+#ifdef GSSAPI
-+ r = snprintf(buf, sizeof buf, "%s/%s.gssapi", pw->pw_dir,
-+ _PATH_SSH_USER_CONFFILE);
-+ if (r > 0 && (size_t)r < sizeof(buf))
-+ (void)read_config_file(buf, pw, host, host_name, cmd,
-+ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
-+ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
-+#ifdef GSI
-+ r = snprintf(buf, sizeof buf, "%s/%s.gsi", pw->pw_dir,
-+ _PATH_SSH_USER_CONFFILE);
-+ if (r > 0 && (size_t)r < sizeof(buf))
-+ (void)read_config_file(buf, pw, host, host_name, cmd,
-+ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
-+ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
-+#endif
-+#if defined(KRB5)
-+ r = snprintf(buf, sizeof buf, "%s/%s.krb", pw->pw_dir,
-+ _PATH_SSH_USER_CONFFILE);
-+ if (r > 0 && (size_t)r < sizeof(buf))
-+ (void)read_config_file(buf, pw, host, host_name, cmd,
-+ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
-+ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
-+#endif
-+#endif
- r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir,
- _PATH_SSH_USER_CONFFILE);
- if (r > 0 && (size_t)r < sizeof(buf))
-@@ -1297,6 +1329,9 @@
- if (options.user == NULL) {
- user_was_default = 1;
- options.user = xstrdup(pw->pw_name);
-+ options.implicit = 1;
-+ } else {
-+ options.implicit = 0;
- }
-
- /*
-diff -Nur openssh-10.3p1.orig/ssh_config openssh-10.3p1/ssh_config
---- openssh-10.3p1.orig/ssh_config 2026-07-01 09:38:00.322768588 +0200
-+++ openssh-10.3p1/ssh_config 2026-07-01 10:39:43.582725496 +0200
-@@ -22,9 +22,9 @@
- # ForwardX11 no
- # PasswordAuthentication yes
- # HostbasedAuthentication no
--# GSSAPIAuthentication no
--# GSSAPIDelegateCredentials no
--# GSSAPIKeyExchange no
-+# GSSAPIAuthentication yes
-+# GSSAPIDelegateCredentials yes
-+# GSSAPIKeyExchange yes
- # GSSAPITrustDNS no
- # BatchMode no
- # CheckHostIP no
-diff -Nur openssh-10.3p1.orig/ssh_config.5 openssh-10.3p1/ssh_config.5
---- openssh-10.3p1.orig/ssh_config.5 2026-07-01 09:38:00.886610170 +0200
-+++ openssh-10.3p1/ssh_config.5 2026-07-01 10:39:43.583232438 +0200
-@@ -52,6 +52,12 @@
- user's configuration file
- .Pq Pa ~/.ssh/config
- .It
-+GSSAPI configuration file
-+.Pq Pa $HOME/.ssh/config.gssapi
-+.It
-+Kerberos configuration file
-+.Pq Pa $HOME/.ssh/config.krb
-+.It
- system-wide configuration file
- .Pq Pa /etc/ssh/ssh_config
- .El
-@@ -969,7 +975,7 @@
- .It Cm GSSAPIAuthentication
- Specifies whether user authentication based on GSSAPI is allowed.
- The default is
--.Cm no .
-+.Cm yes .
- .It Cm GSSAPIClientIdentity
- If set, specifies the GSSAPI client identity that ssh should use when
- connecting to the server. The default is unset, which means that the default
-@@ -977,12 +983,12 @@
- .It Cm GSSAPIDelegateCredentials
- Forward (delegate) credentials to the server.
- The default is
--.Cm no .
-+.Cm yes .
- .It Cm GSSAPIKeyExchange
- Specifies whether key exchange based on GSSAPI may be used. When using
- GSSAPI key exchange the server need not have a host key.
- The default is
--.Dq no .
-+.Dq yes .
- .It Cm GSSAPIRenewalForcesRekey
- If set to
- .Dq yes
-@@ -1646,7 +1652,7 @@
- .Cm password ) .
- The default is:
- .Bd -literal -offset indent
--gssapi-with-mic,hostbased,publickey,
-+gssapi-keyex,gssapi-with-mic,hostbased,publickey,
- keyboard-interactive,password
- .Ed
- .It Cm ProxyCommand
-diff -Nur openssh-10.3p1.orig/sshconnect2.c openssh-10.3p1/sshconnect2.c
---- openssh-10.3p1.orig/sshconnect2.c 2026-07-01 09:38:00.812278719 +0200
-+++ openssh-10.3p1/sshconnect2.c 2026-07-01 10:39:43.583867032 +0200
-@@ -863,6 +863,11 @@
- gss_OID mech = NULL;
- char *gss_host = NULL;
-
-+ if (!options.gss_authentication) {
-+ verbose("GSSAPI authentication disabled.");
-+ return 0;
-+ }
-+
- if (options.gss_server_identity) {
- gss_host = xstrdup(options.gss_server_identity);
- } else if (options.gss_trust_dns) {
-@@ -971,7 +976,8 @@
-
- if (status == GSS_S_COMPLETE) {
- /* send either complete or MIC, depending on mechanism */
-- if (!(flags & GSS_C_INTEG_FLAG)) {
-+ if (strcmp(authctxt->method->name, "gssapi") == 0 ||
-+ !(flags & GSS_C_INTEG_FLAG)) {
- if ((r = sshpkt_start(ssh,
- SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE)) != 0 ||
- (r = sshpkt_send(ssh)) != 0)
-@@ -1138,6 +1144,20 @@
- return r;
- }
-
-+#ifdef GSI
-+extern
-+const gss_OID_desc * const gss_mech_globus_gssapi_openssl;
-+extern
-+const gss_OID_desc * const gss_mech_globus_gssapi_openssl_micv2;
-+#define is_gsi_oid(oid) \
-+ ((oid->length == gss_mech_globus_gssapi_openssl->length && \
-+ (memcmp(oid->elements, gss_mech_globus_gssapi_openssl->elements, \
-+ oid->length) == 0)) || \
-+ (oid->length == gss_mech_globus_gssapi_openssl_micv2->length && \
-+ (memcmp(oid->elements, gss_mech_globus_gssapi_openssl_micv2->elements, \
-+ oid->length) == 0)))
-+#endif
-+
- int
- userauth_gsskeyex(struct ssh *ssh)
- {
-@@ -1160,6 +1180,12 @@
- if ((b = sshbuf_new()) == NULL)
- fatal_f("sshbuf_new failed");
-
-+#ifdef GSI
-+ if (options.implicit && is_gsi_oid(gss_kex_context->oid))
-+ ssh_gssapi_buildmic(b, "", authctxt->service,
-+ "gssapi-keyex", ssh->kex->session_id);
-+ else
-+#endif
- ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service,
- "gssapi-keyex", ssh->kex->session_id);
-
-@@ -1173,7 +1199,9 @@
- }
-
- if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
-- (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 ||
-+ (r = sshpkt_put_cstring(ssh,
-+ (options.implicit && is_gsi_oid(gss_kex_context->oid)) ?
-+ "" : authctxt->server_user)) != 0 ||
- (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 ||
- (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 ||
- (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 ||
-diff -Nur openssh-10.3p1.orig/sshd.8 openssh-10.3p1/sshd.8
---- openssh-10.3p1.orig/sshd.8 2026-07-01 09:38:00.375984174 +0200
-+++ openssh-10.3p1/sshd.8 2026-07-01 10:39:43.584412744 +0200
-@@ -840,6 +840,29 @@
- # A CA key, accepted for any host in *.mydomain.com or *.mydomain.org
- @cert-authority *.mydomain.org,*.mydomain.com ssh-rsa AAAAB5W...
- .Ed
-+.Sh ENVIRONMENT
-+.Nm
-+will normally set the following environment variables:
-+.Bl -tag -width "SSH_ORIGINAL_COMMAND"
-+.It Ev GRIDMAP
-+Applies to GSI authentication/authorization. Specifies the location of the
-+gridmapfile. If not specified, the gridmap file is assumed to be available at
-+/etc/grid-security/grid-mapfile for services running as root and at
-+HOME/.gridmap for services running as non-root where HOME is the home directory
-+of the effective user from the password file entry.
-+.It Ev X509_CERT_DIR
-+Used for GSI authentication. Specifies a non-standard location for the
-+CA certificates directory.
-+.It Ev X509_USER_CERT
-+Used for GSI authentication. Specifies a non-standard location for the
-+certificate to be used for authentication to the client.
-+.It Ev X509_USER_KEY
-+Used for GSI authentication. Specifies a non-standard location for the
-+private key to be used for authentication to the client.
-+.It Ev X509_USER_PROXY
-+Used for GSI authentication. Specifies a non-standard location for the
-+proxy credential to be used for authentication to the client.
-+.El
- .Sh FILES
- .Bl -tag -width Ds -compact
- .It Pa ~/.hushlogin
-diff -Nur openssh-10.3p1.orig/sshd_config openssh-10.3p1/sshd_config
---- openssh-10.3p1.orig/sshd_config 2026-07-01 09:38:00.441954138 +0200
-+++ openssh-10.3p1/sshd_config 2026-07-01 10:39:43.584807988 +0200
-@@ -78,10 +78,11 @@
- #KerberosUseKuserok yes
-
- # GSSAPI options
--#GSSAPIAuthentication no
-+#GSSAPIAuthentication yes
-+#GSSAPIDelegateCredentials yes
- #GSSAPICleanupCredentials yes
- #GSSAPIStrictAcceptorCheck yes
--#GSSAPIKeyExchange no
-+#GSSAPIKeyExchange yes
- #GSSAPIEnablek5users no
-
- # Set this to 'yes' to enable PAM authentication, account processing,
-@@ -97,6 +98,10 @@
- # problems.
- #UsePAM no
-
-+# Set to 'yes' to allow the PAM stack to change the user name during
-+# calls to authentication
-+#PermitPAMUserChange no
-+
- #AllowAgentForwarding yes
- #AllowTcpForwarding yes
- #GatewayPorts no
-diff -Nur openssh-10.3p1.orig/sshd_config.5 openssh-10.3p1/sshd_config.5
---- openssh-10.3p1.orig/sshd_config.5 2026-07-01 09:38:00.869019072 +0200
-+++ openssh-10.3p1/sshd_config.5 2026-07-01 10:39:43.585246089 +0200
-@@ -736,15 +736,30 @@
- to allow the client to select the address to which the forwarding is bound.
- The default is
- .Cm no .
-+.It Cm GSIAllowLimitedProxy
-+Specifies whether to accept limited proxy credentials for authentication.
-+The default is
-+.Cm no .
- .It Cm GSSAPIAuthentication
- Specifies whether user authentication based on GSSAPI is allowed.
- The default is
--.Cm no .
-+.Cm yes .
- .It Cm GSSAPICleanupCredentials
- Specifies whether to automatically destroy the user's credentials cache
- on logout.
- The default is
- .Cm yes .
-+.It Cm GSSAPICredentialsPath
-+If specified, the delegated GSSAPI credential is stored in the
-+given path, overwriting any existing credentials.
-+Paths can be specified with syntax similar to the AuthorizedKeysFile
-+option (i.e., accepting %h and %u tokens).
-+When using this option,
-+setting 'GssapiCleanupCredentials no' is recommended,
-+so logging out of one session
-+doesn't remove the credentials in use by another session of
-+the same user.
-+Currently only implemented for the GSI mechanism.
- .It Cm GSSAPIDelegateCredentials
- Accept delegated credentials on the server side. The default is
- .CM yes .
-@@ -758,7 +773,7 @@
- Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange
- doesn't rely on ssh keys to verify host identity.
- The default is
--.Cm no .
-+.Cm yes .
- .It Cm GSSAPIStrictAcceptorCheck
- Determines whether to be strict about the identity of the GSSAPI acceptor
- a client authenticates against.
-@@ -2193,6 +2208,12 @@
- as a non-root user.
- The default is
- .Cm no .
-+.It Cm PermitPAMUserChange
-+If set to
-+.Cm yes
-+this will enable PAM authentication to change the name of the user being
-+authenticated. The default is
-+.Cm no .
- .It Cm VersionAddendum
- Optionally specifies additional text to append to the SSH protocol banner
- sent by the server upon connection.
-diff -Nur openssh-10.3p1.orig/sshd_config_redhat openssh-10.3p1/sshd_config_redhat
---- openssh-10.3p1.orig/sshd_config_redhat 2026-07-01 09:38:00.923266290 +0200
-+++ openssh-10.3p1/sshd_config_redhat 2026-07-01 10:39:43.585520419 +0200
-@@ -2,9 +2,6 @@
-
- KbdInteractiveAuthentication no
-
--GSSAPIAuthentication yes
--GSSAPICleanupCredentials no
--
- UsePAM yes
-
- X11Forwarding yes
-diff -Nur openssh-10.3p1.orig/sshd-session.c openssh-10.3p1/sshd-session.c
---- openssh-10.3p1.orig/sshd-session.c 2026-07-01 09:38:00.868830863 +0200
-+++ openssh-10.3p1/sshd-session.c 2026-07-01 14:24:34.085748964 +0200
-@@ -1388,11 +1388,12 @@
- #endif
-
- #ifdef GSSAPI
-- if (options.gss_authentication) {
-+ if (options.gss_authentication && options.gss_deleg_creds) {
- temporarily_use_uid(authctxt->pw);
- authctxt->krb5_set_env = ssh_gssapi_storecreds();
- restore_uid();
- }
-+#ifdef KRB5
- /*
- * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
- * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
-@@ -1492,6 +1493,7 @@
- }
- }
- #endif
-+#endif
- #ifdef WITH_SELINUX
- sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
- inetd_flag, do_pam_putenv, the_authctxt,
-diff -Nur openssh-10.3p1.orig/ssh-gss.h openssh-10.3p1/ssh-gss.h
---- openssh-10.3p1.orig/ssh-gss.h 2026-07-01 09:38:00.868721612 +0200
-+++ openssh-10.3p1/ssh-gss.h 2026-07-01 10:39:43.586297522 +0200
-@@ -110,12 +110,14 @@
- } ssh_gssapi_ccache;
-
- typedef struct {
-+ gss_OID_desc oid;
- gss_buffer_desc displayname;
- gss_buffer_desc exportedname;
- gss_cred_id_t creds;
-- gss_name_t name;
-+ gss_name_t cred_name, ctx_name;
- struct ssh_gssapi_mech_struct *mech;
- ssh_gssapi_ccache store;
-+ gss_ctx_id_t context; /* needed for globus_gss_assist_map_and_authorize() */
- int used;
- int updated;
- char **indicators; /* auth indicators */
-@@ -138,7 +140,7 @@
- OM_uint32 minor; /* both */
- gss_ctx_id_t context; /* both */
- gss_name_t name; /* both */
-- gss_OID oid; /* client */
-+ gss_OID oid; /* both */
- gss_cred_id_t creds; /* server */
- gss_name_t client; /* server */
- gss_cred_id_t client_creds; /* both */
-@@ -185,6 +187,9 @@
- OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *);
- int ssh_gssapi_credentials_updated(Gssctxt *);
-
-+int ssh_gssapi_localname(char **name);
-+void ssh_gssapi_rekey_creds();
-+
- /* In the server */
- typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *,
- const char *);
-diff -Nur openssh-10.3p1.orig/version.h openssh-10.3p1/version.h
---- openssh-10.3p1.orig/version.h 2026-04-02 10:09:03.000000000 +0200
-+++ openssh-10.3p1/version.h 2026-07-01 10:39:43.586479123 +0200
-@@ -2,5 +2,19 @@
-
- #define SSH_VERSION "OpenSSH_10.3"
-
-+#ifdef GSI
-+#define GSI_VERSION " GSI"
-+#else
-+#define GSI_VERSION ""
-+#endif
-+
-+#ifdef KRB5
-+#define KRB5_VERSION " KRB5"
-+#else
-+#define KRB5_VERSION ""
-+#endif
-+
- #define SSH_PORTABLE "p1"
--#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
-+#define GSI_PORTABLE "c-GSI"
-+#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \
-+ GSI_VERSION KRB5_VERSION
diff --git a/2000-openssh-10.5p1-gsissh.patch b/2000-openssh-10.5p1-gsissh.patch
new file mode 100644
index 0000000..a03643a
--- /dev/null
+++ b/2000-openssh-10.5p1-gsissh.patch
@@ -0,0 +1,2401 @@
+diff -Nur openssh-10.5p1.orig/auth2.c openssh-10.5p1/auth2.c
+--- openssh-10.5p1.orig/auth2.c 2026-09-21 05:40:58.158733152 +0200
++++ openssh-10.5p1/auth2.c 2026-09-21 05:41:30.827248806 +0200
+@@ -292,7 +292,28 @@
+ (r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 ||
+ (r = sshpkt_get_cstring(ssh, &method, NULL)) != 0)
+ goto out;
+- debug("userauth-request for user %s service %s method %s", user, service, method);
++
++#ifdef GSSAPI
++ if (user[0] == '\0') {
++ debug("received empty username for %s", method);
++ if (strcmp(method, "gssapi-keyex") == 0) {
++ char *lname = NULL;
++ mm_ssh_gssapi_localname(&lname);
++ if (lname && lname[0] != '\0') {
++ free(user);
++ user = lname;
++ debug("set username to %s from gssapi context", user);
++ } else {
++ debug("failed to set username from gssapi context");
++ ssh_packet_send_debug(ssh,
++ "failed to set username from gssapi context");
++ }
++ }
++ }
++#endif
++
++ debug("userauth-request for user %s service %s method %s",
++ user[0] ? user : "<implicit>", service, method);
+ debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
+
+ #ifdef WITH_SELINUX
+@@ -305,16 +326,38 @@
+
+ if (authctxt->attempt >= 1024)
+ auth_maxtries_exceeded(ssh);
+- if (authctxt->attempt++ == 0) {
+- /* setup auth context */
+- authctxt->pw = mm_getpwnamallow(ssh, user);
++ /* If first time or username changed or empty username,
++ setup/reset authentication context. */
++ if ((authctxt->attempt++ == 0) ||
++ (strcmp(user, authctxt->user) != 0) ||
++ (strcmp(user, "") == 0)) {
++ if (authctxt->user) {
++ free(authctxt->user);
++ authctxt->user = NULL;
++ }
++ authctxt->valid = 0;
+ authctxt->user = xstrdup(user);
+ authctxt->service = xstrdup(service);
+ authctxt->style = style ? xstrdup(style) : NULL;
+ #ifdef WITH_SELINUX
+ authctxt->role = role ? xstrdup(role) : NULL;
+ #endif
+- if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
++ if (strcmp(service, "ssh-connection") != 0) {
++ ssh_packet_disconnect(ssh, "Unsupported service %s",
++ service);
++ }
++#ifdef GSSAPI
++ /* If we're going to set the username based on the
++ GSSAPI context later, then wait until then to
++ verify it. Just put in placeholders for now. */
++ if ((strcmp(user, "") == 0) &&
++ ((strcmp(method, "gssapi") == 0) ||
++ (strcmp(method, "gssapi-with-mic") == 0))) {
++ authctxt->pw = fakepw();
++ } else {
++#endif
++ authctxt->pw = mm_getpwnamallow(ssh, user);
++ if (authctxt->pw) {
+ authctxt->valid = 1;
+ debug2_f("setting up authctxt for %s", user);
+ } else {
+@@ -322,6 +365,9 @@
+ /* Invalid user, fake password information */
+ authctxt->pw = fakepw();
+ }
++#ifdef GSSAPI
++ } /* endif for setting username based on GSSAPI context */
++#endif
+ #ifdef USE_PAM
+ if (options.use_pam)
+ mm_start_pam(ssh);
+@@ -329,6 +375,7 @@
+ ssh_packet_set_log_preamble(ssh, "%suser %s",
+ authctxt->valid ? "authenticating " : "invalid ", user);
+ setproctitle("%s [net]", authctxt->valid ? user : "unknown");
++ if (authctxt->attempt == 1) {
+ mm_inform_authserv(service, style);
+ #ifdef WITH_SELINUX
+ mm_inform_authrole(role);
+@@ -339,9 +386,10 @@
+ if (auth2_setup_methods_lists(authctxt) != 0)
+ ssh_packet_disconnect(ssh,
+ "no authentication methods enabled");
+- } else if (strcmp(user, authctxt->user) != 0 ||
+- strcmp(service, authctxt->service) != 0) {
+- ssh_packet_disconnect(ssh, "Change of username or service "
++ }
++ }
++ if (strcmp(service, authctxt->service) != 0) {
++ ssh_packet_disconnect(ssh, "Change of service "
+ "not allowed: (%s,%s) -> (%s,%s)",
+ authctxt->user, authctxt->service, user, service);
+ }
+diff -Nur openssh-10.5p1.orig/auth2-gss.c openssh-10.5p1/auth2-gss.c
+--- openssh-10.5p1.orig/auth2-gss.c 2026-09-21 05:40:57.867739271 +0200
++++ openssh-10.5p1/auth2-gss.c 2026-09-21 05:41:30.827988870 +0200
+@@ -54,6 +54,7 @@
+ extern struct authmethod_cfg methodcfg_gsskeyex;
+ extern struct authmethod_cfg methodcfg_gssapi;
+
++static void ssh_gssapi_userauth_error(Gssctxt *ctxt, struct ssh *ssh);
+ static int input_gssapi_token(int type, uint32_t plen, struct ssh *ssh);
+ static int input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh);
+ static int input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh);
+@@ -67,8 +68,8 @@
+ {
+ Authctxt *authctxt = ssh->authctxt;
+ int r, authenticated = 0;
+- struct sshbuf *b = NULL;
+- gss_buffer_desc mic, gssbuf;
++ struct sshbuf *b = NULL, *b2 = NULL;
++ gss_buffer_desc mic, gssbuf, gssbuf2;
+ u_char *p;
+ size_t len;
+
+@@ -79,6 +80,9 @@
+ if ((b = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
+
++ if ((b2 = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
+ mic.value = p;
+ mic.length = len;
+
+@@ -89,13 +93,28 @@
+ fatal_f("sshbuf_mutable_ptr failed");
+ gssbuf.length = sshbuf_len(b);
+
++ /* client may have used empty username to determine target
++ name from GSSAPI context */
++ ssh_gssapi_buildmic(b2, "", authctxt->service, "gssapi-keyex",
++ ssh->kex->session_id);
++
++ if ((gssbuf2.value = sshbuf_mutable_ptr(b2)) == NULL)
++ fatal_f("sshbuf_mutable_ptr failed");
++ gssbuf2.length = sshbuf_len(b2);
++
+ /* gss_kex_context is NULL with privsep, so we can't check it here */
+ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context,
+- &gssbuf, &mic)))
+- authenticated = mm_ssh_gssapi_userok(authctxt->user,
+- authctxt->pw, 1);
++ &gssbuf, &mic)) ||
++ !GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context,
++ &gssbuf2, &mic))) {
++ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
++ authenticated = mm_ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 1);
++ }
++ }
+
+ sshbuf_free(b);
++ sshbuf_free(b2);
+ free(mic.value);
+
+ return (authenticated);
+@@ -186,7 +205,7 @@
+ Gssctxt *gssctxt;
+ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc recv_tok;
+- OM_uint32 maj_status, min_status, flags;
++ OM_uint32 maj_status, min_status, flags = 0;
+ u_char *p;
+ size_t len;
+ int r;
+@@ -207,6 +226,7 @@
+ free(p);
+
+ if (GSS_ERROR(maj_status)) {
++ ssh_gssapi_userauth_error(gssctxt, ssh);
+ if (send_tok.length != 0) {
+ if ((r = sshpkt_start(ssh,
+ SSH2_MSG_USERAUTH_GSSAPI_ERRTOK)) != 0 ||
+@@ -280,6 +300,34 @@
+ return 0;
+ }
+
++static void
++gssapi_set_username(struct ssh *ssh)
++{
++ Authctxt *authctxt = ssh->authctxt;
++ char *lname = NULL;
++
++ if ((authctxt->user == NULL) || (authctxt->user[0] == '\0')) {
++ mm_ssh_gssapi_localname(&lname);
++ if (lname && lname[0] != '\0') {
++ if (authctxt->user) free(authctxt->user);
++ authctxt->user = lname;
++ debug("set username to %s from gssapi context", lname);
++ authctxt->pw = mm_getpwnamallow(ssh, authctxt->user);
++ if (authctxt->pw) {
++ authctxt->valid = 1;
++#ifdef USE_PAM
++ if (options.use_pam)
++ mm_start_pam(ssh);
++#endif
++ }
++ } else {
++ debug("failed to set username from gssapi context");
++ ssh_packet_send_debug(ssh,
++ "failed to set username from gssapi context");
++ }
++ }
++}
++
+ /*
+ * This is called when the client thinks we've completed authentication.
+ * It should only be enabled in the dispatch handler by the function above,
+@@ -290,12 +338,14 @@
+ input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
+ {
+ Authctxt *authctxt = ssh->authctxt;
+- int r, authenticated;
++ int r, authenticated = 0;
+ double tstart = monotime_double();
+
+ if (authctxt == NULL)
+ fatal("No authentication or GSSAPI context");
+
++ gssapi_set_username(ssh);
++
+ /*
+ * We don't need to check the status, because we're only enabled in
+ * the dispatcher once the exchange is complete
+@@ -304,7 +354,12 @@
+ if ((r = sshpkt_get_end(ssh)) != 0)
+ fatal_fr(r, "parse packet");
+
+- authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1);
++ /* user should be set if valid but we double-check here */
++ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
++ authenticated = mm_ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 1);
++ }
++
+ if (!authenticated)
+ auth_failure_delay(authctxt, tstart);
+
+@@ -354,10 +409,16 @@
+ fatal_f("sshbuf_mutable_ptr failed");
+ gssbuf.length = sshbuf_len(b);
+
+- if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))
+- authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 0);
+- else
++ gssapi_set_username(ssh);
++
++ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) {
++ if (authctxt->valid && authctxt->user && authctxt->user[0]) {
++ authenticated = mm_ssh_gssapi_userok(authctxt->user,
++ authctxt->pw, 0);
++ }
++ } else {
+ logit("GSSAPI MIC check failed");
++ }
+
+ sshbuf_free(b);
+ if (micuser != authctxt->user)
+@@ -376,6 +437,26 @@
+ return 0;
+ }
+
++static void ssh_gssapi_userauth_error(Gssctxt *ctxt, struct ssh *ssh) {
++ char *errstr;
++ OM_uint32 maj, min;
++ int r;
++
++ errstr = mm_ssh_gssapi_last_error(ctxt, &maj, &min);
++ if (errstr) {
++ if ((r = sshpkt_start(ssh,
++ SSH2_MSG_USERAUTH_GSSAPI_ERROR)) != 0 ||
++ (r = sshpkt_put_u32(ssh, maj)) != 0 ||
++ (r = sshpkt_put_u32(ssh, min)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, errstr)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, "")) != 0 ||
++ (r = sshpkt_send(ssh)) != 0 ||
++ (r = ssh_packet_write_wait(ssh)) != 0)
++ fatal_fr(r, "");
++ free(errstr);
++ }
++}
++
+ Authmethod method_gsskeyex = {
+ &methodcfg_gsskeyex,
+ userauth_gsskeyex,
+diff -Nur openssh-10.5p1.orig/auth.c openssh-10.5p1/auth.c
+--- openssh-10.5p1.orig/auth.c 2026-09-21 05:40:58.158334557 +0200
++++ openssh-10.5p1/auth.c 2026-09-21 05:41:30.828648308 +0200
+@@ -296,7 +296,8 @@
+ method,
+ submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod,
+ authctxt->valid ? "" : "invalid user ",
+- authctxt->user,
++ (authctxt->user && authctxt->user[0]) ?
++ authctxt->user : "unknown",
+ ssh_remote_ipaddr(ssh),
+ ssh_remote_port(ssh),
+ extra != NULL ? ": " : "",
+@@ -491,13 +492,18 @@
+ #endif
+
+ pw = getpwnam(user);
++#ifdef USE_PAM
++ if (options.use_pam && options.permit_pam_user_change && pw == NULL)
++ pw = sshpam_getpw(user);
++#endif
+
+ #if defined(_AIX) && defined(HAVE_SETAUTHDB)
+ aix_restoreauthdb();
+ #endif
+ if (pw == NULL) {
+ logit("Invalid user %.100s from %.100s port %d",
+- user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
++ (user && user[0]) ? user : "unknown",
++ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
+ #ifdef CUSTOM_FAILED_LOGIN
+ record_failed_login(ssh, user,
+ auth_get_canonical_hostname(ssh, options.use_dns), "ssh");
+diff -Nur openssh-10.5p1.orig/auth.h openssh-10.5p1/auth.h
+--- openssh-10.5p1.orig/auth.h 2026-09-21 05:40:58.158427993 +0200
++++ openssh-10.5p1/auth.h 2026-09-21 05:41:30.829257318 +0200
+@@ -85,6 +85,8 @@
+ krb5_principal krb5_user;
+ char *krb5_ticket_file;
+ char *krb5_ccname;
++#endif
++#ifdef GSSAPI
+ int krb5_set_env;
+ #endif
+ struct sshbuf *loginmsg;
+diff -Nur openssh-10.5p1.orig/auth-pam.c openssh-10.5p1/auth-pam.c
+--- openssh-10.5p1.orig/auth-pam.c 2026-09-21 05:40:58.371577248 +0200
++++ openssh-10.5p1/auth-pam.c 2026-09-21 05:41:30.829598487 +0200
+@@ -254,6 +254,7 @@
+ static const char *sshpam_password = NULL;
+ static char *sshpam_rhost = NULL;
+ static char *sshpam_laddr = NULL;
++static struct ssh *sshpam_ssh = NULL;
+
+ /* Some PAM implementations don't implement this */
+ #ifndef HAVE_PAM_GETENVLIST
+@@ -277,6 +278,56 @@
+ }
+ #endif /* HAVE_PAM_PUTENV */
+
++struct passwd *
++sshpam_getpw(const char *user)
++{
++ struct passwd *pw;
++
++ if ((pw = getpwnam(user)) != NULL)
++ return(pw);
++
++ debug("PAM: faking passwd struct for user '%.100s'", user);
++ if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL)
++ return NULL;
++ pw->pw_name = xstrdup(user); /* XXX leak */
++ pw->pw_shell = "/bin/true";
++ pw->pw_gecos = "sshd fake PAM user";
++ return (pw);
++}
++
++void
++sshpam_check_userchanged(void)
++{
++ int sshpam_err;
++ struct passwd *pw;
++ const char *user;
++
++ debug("sshpam_check_userchanged");
++ sshpam_err = pam_get_item(sshpam_handle, PAM_USER,
++ (sshpam_const void **)&user);
++ if (sshpam_err != PAM_SUCCESS)
++ fatal("PAM: could not get PAM_USER: %s",
++ pam_strerror(sshpam_handle, sshpam_err));
++ debug("sshpam_check_userchanged: user was '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ if (strcmp(user, sshpam_authctxt->pw->pw_name) != 0) {
++ debug("PAM: user mapped from '%.100s' to '%.100s'",
++ sshpam_authctxt->pw->pw_name, user);
++ if ((pw = getpwnam(user)) == NULL)
++ fatal("PAM: could not get passwd entry for user "
++ "'%.100s' provided by PAM_USER", user);
++ pwfree(sshpam_authctxt->pw);
++ sshpam_authctxt->pw = pwcopy(pw);
++ sshpam_authctxt->valid = allowed_user(sshpam_ssh, pw);
++ free(sshpam_authctxt->user);
++ sshpam_authctxt->user = xstrdup(user);
++ debug("PAM: user '%.100s' now %svalid", user,
++ sshpam_authctxt->valid ? "" : "in");
++ }
++ debug("sshpam_check_userchanged: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++}
++
+ static void
+ sshpam_password_change_required(int reqd)
+ {
+@@ -308,7 +359,7 @@
+ static void
+ import_environments(struct sshbuf *b)
+ {
+- char *env;
++ char *env, *user;
+ u_int n, i, num_env;
+ int r;
+
+@@ -324,6 +375,19 @@
+ if ((r = sshbuf_get_u32(b, &n)) != 0)
+ fatal_fr(r, "buffer error");
+ sshpam_password_change_required(n != 0);
++ if (options.permit_pam_user_change) {
++ if ((r = sshbuf_get_cstring(b, &user, NULL)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++ debug("PAM: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ debug("PAM: got username '%.100s' from thread", user);
++ if ((sshpam_err = pam_set_item(sshpam_handle, PAM_USER, user))
++ != PAM_SUCCESS)
++ fatal("PAM: failed to set PAM_USER: %s",
++ pam_strerror(sshpam_handle, sshpam_err));
++ pwfree(sshpam_authctxt->pw);
++ sshpam_authctxt->pw = pwcopy(sshpam_getpw(user));
++ }
+
+ /* Import environment from subprocess */
+ if ((r = sshbuf_get_u32(b, &num_env)) != 0)
+@@ -536,6 +600,13 @@
+ if ((sshpam_err = check_pam_user(sshpam_authctxt)) != PAM_SUCCESS)
+ goto auth_fail;
+
++ if (options.permit_pam_user_change) {
++ debug("sshpam_thread: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ sshpam_check_userchanged();
++ debug("sshpam_thread: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ }
+ if (!do_pam_account()) {
+ /* Preserve PAM_PERM_DENIED and PAM_USER_UNKNOWN.
+ * Backward compatibility for other errors. */
+@@ -560,6 +631,13 @@
+ if ((r = sshbuf_put_u32(buffer, sshpam_account_status)) != 0 ||
+ (r = sshbuf_put_u32(buffer, sshpam_authctxt->force_pwchange)) != 0)
+ fatal_fr(r, "buffer error");
++ if (options.permit_pam_user_change) {
++ debug("sshpam_thread: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ if ((r = sshbuf_put_cstring(buffer,
++ sshpam_authctxt->pw->pw_name)) != 0)
++ fatal("%s: buffer error: %s", __func__, ssh_err(r));
++ }
+
+ /* Export any environment strings set in child */
+ for (i = 0; environ[i] != NULL; i++) {
+@@ -749,6 +827,8 @@
+ options.use_dns));
+ sshpam_laddr = get_local_ipaddr(
+ ssh_packet_get_connection_in(ssh));
++ /* Save so allowed_user can be called later */
++ sshpam_ssh = ssh;
+ }
+ if (sshpam_rhost != NULL && strcmp(sshpam_rhost, "UNKNOWN") != 0) {
+ debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost);
+@@ -1083,6 +1163,18 @@
+ debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
+ pam_strerror(sshpam_handle, sshpam_err));
+
++ if (options.permit_pam_user_change) {
++ debug("do_pam_account: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ sshpam_check_userchanged();
++ debug("do_pam_account: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ if (getpwnam(sshpam_authctxt->pw->pw_name) == NULL)
++ fatal("PAM: completed authentication but PAM account invalid");
++ debug("do_pam_account: user is '%.100s'",
++ sshpam_authctxt->pw->pw_name);
++ }
++
+ if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
+ sshpam_account_status = 0;
+ return (sshpam_account_status);
+@@ -1291,6 +1383,9 @@
+ expose_authinfo(__func__);
+
+ sshpam_err = pam_authenticate(sshpam_handle, flags);
++ if (options.permit_pam_user_change) {
++ sshpam_check_userchanged();
++ }
+ sshpam_password = NULL;
+ free(fake);
+ if (sshpam_err == PAM_SUCCESS)
+diff -Nur openssh-10.5p1.orig/auth-pam.h openssh-10.5p1/auth-pam.h
+--- openssh-10.5p1.orig/auth-pam.h 2026-09-21 05:40:57.746646367 +0200
++++ openssh-10.5p1/auth-pam.h 2026-09-21 05:41:30.830335140 +0200
+@@ -44,5 +44,6 @@
+ void sshpam_set_maxtries_reached(int);
+ int is_pam_session_open(void);
+ int sshpam_priv_kbdint_authdone(void *ctxtp);
++struct passwd *sshpam_getpw(const char *);
+
+ #endif /* USE_PAM */
+diff -Nur openssh-10.5p1.orig/canohost.c openssh-10.5p1/canohost.c
+--- openssh-10.5p1.orig/canohost.c 2026-09-21 05:40:57.868004172 +0200
++++ openssh-10.5p1/canohost.c 2026-09-21 05:41:30.830520168 +0200
+@@ -17,6 +17,7 @@
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <sys/un.h>
++#include <sys/param.h> /* for MAXHOSTNAMELEN */
+
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+@@ -300,3 +301,33 @@
+ {
+ return get_sock_port(sock, 1);
+ }
++
++void
++resolve_localhost(char **host)
++{
++ struct hostent *hostinfo;
++
++ hostinfo = gethostbyname(*host);
++ if (hostinfo == NULL || hostinfo->h_name == NULL) {
++ debug("gethostbyname(%s) failed", *host);
++ return;
++ }
++ if (hostinfo->h_addrtype == AF_INET) {
++ struct in_addr addr;
++ addr = *(struct in_addr *)(hostinfo->h_addr);
++ if (ntohl(addr.s_addr) == INADDR_LOOPBACK) {
++ char buf[MAXHOSTNAMELEN];
++ if (gethostname(buf, sizeof(buf)) < 0) {
++ debug("gethostname() failed");
++ return;
++ }
++ hostinfo = gethostbyname(buf);
++ free(*host);
++ if (hostinfo == NULL || hostinfo->h_name == NULL) {
++ *host = xstrdup(buf);
++ } else {
++ *host = xstrdup(hostinfo->h_name);
++ }
++ }
++ }
++}
+diff -Nur openssh-10.5p1.orig/canohost.h openssh-10.5p1/canohost.h
+--- openssh-10.5p1.orig/canohost.h 2026-09-21 05:40:57.868061192 +0200
++++ openssh-10.5p1/canohost.h 2026-09-21 05:41:30.830837911 +0200
+@@ -26,4 +26,6 @@
+
+ #endif /* _CANOHOST_H */
+
++void resolve_localhost(char **host);
++
+ void ipv64_normalise_mapped(struct sockaddr_storage *, socklen_t *);
+diff -Nur openssh-10.5p1.orig/configure.ac openssh-10.5p1/configure.ac
+--- openssh-10.5p1.orig/configure.ac 2026-09-21 05:40:58.407176155 +0200
++++ openssh-10.5p1/configure.ac 2026-09-21 05:41:30.831256049 +0200
+@@ -5083,6 +5083,14 @@
+ AC_CHECK_HEADER([gssapi_krb5.h], ,
+ [ CPPFLAGS="$oldCPP" ])
+
++ # If we're using some other GSSAPI
++ if test -n "$GSSAPI" ; then
++ AC_MSG_ERROR([Previously configured GSSAPI library conflicts with Kerberos GSI.])
++ fi
++
++ if test -z "$GSSAPI"; then
++ GSSAPI="KRB5";
++ fi
+ fi
+ fi
+ if test -n "${rpath_opt}" ; then
+@@ -5131,6 +5139,40 @@
+ AC_SUBST([K5LIBS])
+ AC_SUBST([CHANNELLIBS])
+
++# Check whether the user wants GSI (Globus) support
++gsi="no"
++AC_ARG_WITH(gsi,
++ [ --with-gsi Enable Globus GSI authentication support],
++ [
++ gsi="$withval"
++ ]
++)
++
++if test "x$gsi" != "xno" ; then
++ # Globus GSSAPI configuration
++ AC_MSG_CHECKING(for Globus GSI)
++ AC_DEFINE(GSI, 1, [Define if you want GSI/Globus authentication support.])
++
++ if test -n "$GSSAPI" ; then
++ AC_MSG_ERROR([Previously configured GSSAPI library conflicts with Globus GSI.])
++ fi
++
++ if test -z "$GSSAPI" ; then
++ GSSAPI="GSI"
++ fi
++
++ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
++ if test "x$PKGCONFIG" != "xno"; then
++ LIBS="$LIBS `$PKGCONFIG --libs globus-gss-assist globus-gssapi-gsi globus-common`"
++ CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags globus-gss-assist globus-gssapi-gsi globus-common`"
++ fi
++
++ AC_DEFINE(GSSAPI)
++ AC_DEFINE(HAVE_GSSAPI_H)
++
++ AC_CHECK_FUNCS(globus_gss_assist_map_and_authorize)
++fi
++
+ # Looking for programs, paths and files
+
+ PRIVSEP_PATH=/var/empty
+diff -Nur openssh-10.5p1.orig/gss-genr.c openssh-10.5p1/gss-genr.c
+--- openssh-10.5p1.orig/gss-genr.c 2026-09-21 05:40:57.868890667 +0200
++++ openssh-10.5p1/gss-genr.c 2026-09-21 05:41:30.833471314 +0200
+@@ -41,6 +41,7 @@
+ #include "ssherr.h"
+ #include "sshbuf.h"
+ #include "log.h"
++#include "canohost.h"
+ #include "ssh2.h"
+ #include "cipher.h"
+ #include "sshkey.h"
+@@ -426,9 +427,18 @@
+ ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
+ {
+ gss_buffer_desc gssbuf;
++ char *xhost;
+ char *val;
+
+- xasprintf(&val, "host@%s", host);
++ /* Make a copy of the host name, in case it was returned by a
++ * previous call to gethostbyname(). */
++ xhost = xstrdup(host);
++
++ /* Make sure we have the FQDN. Some GSSAPI implementations don't do
++ * this for us themselves */
++ resolve_localhost(&xhost);
++
++ xasprintf(&val, "host@%s", xhost);
+ gssbuf.value = val;
+ gssbuf.length = strlen(gssbuf.value);
+
+@@ -436,6 +446,7 @@
+ &gssbuf, GSS_C_NT_HOSTBASED_SERVICE, &ctx->name)))
+ ssh_gssapi_error(ctx);
+
++ free(xhost);
+ free(gssbuf.value);
+ return (ctx->major);
+ }
+diff -Nur openssh-10.5p1.orig/gss-serv.c openssh-10.5p1/gss-serv.c
+--- openssh-10.5p1.orig/gss-serv.c 2026-09-21 05:40:58.388076073 +0200
++++ openssh-10.5p1/gss-serv.c 2026-09-21 05:41:30.833795720 +0200
+@@ -52,10 +52,12 @@
+ #include "monitor_wrap.h"
+
+ extern ServerOptions options;
++extern Authctxt *the_authctxt;
+
+ static ssh_gssapi_client gssapi_client =
+- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
+- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
++ { {0, NULL}, GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
++ GSS_C_NO_NAME, GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL},
++ GSS_C_NO_CONTEXT, 0, 0, NULL, 0};
+
+ ssh_gssapi_mech gssapi_null_mech =
+ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+@@ -63,14 +65,26 @@
+ #ifdef KRB5
+ extern ssh_gssapi_mech gssapi_kerberos_mech;
+ #endif
++#ifdef GSI
++extern ssh_gssapi_mech gssapi_gsi_mech;
++extern ssh_gssapi_mech gssapi_gsi_mech_micv2;
++#endif
+
+ ssh_gssapi_mech* supported_mechs[]= {
+ #ifdef KRB5
+ &gssapi_kerberos_mech,
+ #endif
++#ifdef GSI
++ &gssapi_gsi_mech_micv2,
++ &gssapi_gsi_mech,
++#endif
+ &gssapi_null_mech,
+ };
+
++#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
++static int limited = 0;
++#endif
++
+ /*
+ * ssh_gssapi_supported_oids() can cause sandbox violations, so prepare the
+ * list of supported mechanisms before privsep is set up.
+@@ -231,6 +245,10 @@
+ (*flags & GSS_C_INTEG_FLAG))) && (ctx->major == GSS_S_COMPLETE)) {
+ if (ssh_gssapi_getclient(ctx, &gssapi_client))
+ fatal("Couldn't convert client name");
++#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
++ if (flags && (*flags & GSS_C_GLOBUS_LIMITED_PROXY_FLAG))
++ limited=1;
++#endif
+ }
+
+ return (status);
+@@ -250,6 +268,20 @@
+
+ tok = ename->value;
+
++#ifdef GSI /* GSI gss_export_name() is broken. */
++ if (((ctx->oid->length == gssapi_gsi_mech.oid.length) &&
++ (memcmp(ctx->oid->elements, gssapi_gsi_mech.oid.elements,
++ gssapi_gsi_mech.oid.length) == 0)) ||
++ ((ctx->oid->length == gssapi_gsi_mech_micv2.oid.length) &&
++ (memcmp(ctx->oid->elements, gssapi_gsi_mech_micv2.oid.elements,
++ gssapi_gsi_mech_micv2.oid.length) == 0))) {
++ name->length = ename->length;
++ name->value = xmalloc(ename->length+1);
++ memcpy(name->value, ename->value, ename->length);
++ return GSS_S_COMPLETE;
++ }
++#endif
++
+ /*
+ * Check that ename is long enough for all of the fixed length
+ * header, and that the initial ID bytes are correct
+@@ -298,6 +330,7 @@
+ }
+
+
++#ifdef KRB5
+ /* Extract authentication indicators from the Kerberos ticket. Authentication
+ * indicators are GSSAPI name attributes for the name "auth-indicators".
+ * Multiple indicators might be present in the ticket.
+@@ -389,6 +422,7 @@
+ gss_release_buffer_set(&ctx->minor, &attrs);
+ return ctx->major;
+ }
++#endif
+
+ /* Extract the client details from a given context. This can only reliably
+ * be called once for a context */
+@@ -403,21 +437,24 @@
+ gss_buffer_desc ename = GSS_C_EMPTY_BUFFER;
+
+ if (options.gss_store_rekey && client->used && ctx->client_creds) {
+- if (client->mech->oid.length != ctx->oid->length ||
+- (memcmp(client->mech->oid.elements,
++ if (client->oid.length != ctx->oid->length ||
++ (memcmp(client->oid.elements,
+ ctx->oid->elements, ctx->oid->length) !=0)) {
+ debug("Rekeyed credentials have different mechanism");
+ return GSS_S_COMPLETE;
+ }
+
+- if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
+- ctx->client_creds, ctx->oid, &new_name,
++ /* Call gss_inquire_cred rather than gss_inquire_cred_by_mech
++ because GSI doesn't support the latter. -jbasney */
++
++ if ((ctx->major = gss_inquire_cred(&ctx->minor,
++ ctx->client_creds, &new_name,
+ NULL, NULL, NULL))) {
+ ssh_gssapi_error(ctx);
+ return (ctx->major);
+ }
+
+- ctx->major = gss_compare_name(&ctx->minor, client->name,
++ ctx->major = gss_compare_name(&ctx->minor, client->cred_name,
+ new_name, &equal);
+
+ if (GSS_ERROR(ctx->major)) {
+@@ -432,9 +469,9 @@
+
+ debug("Marking rekeyed credentials for export");
+
+- gss_release_name(&ctx->minor, &client->name);
++ gss_release_name(&ctx->minor, &client->cred_name);
+ gss_release_cred(&ctx->minor, &client->creds);
+- client->name = new_name;
++ client->cred_name = new_name;
+ client->creds = ctx->client_creds;
+ ctx->client_creds = GSS_C_NO_CREDENTIAL;
+ client->updated = 1;
+@@ -451,12 +488,17 @@
+ i++;
+ }
+
++ if (client->oid.elements == NULL)
++ client->oid = *ctx->oid;
+ if (client->mech == NULL)
+ return GSS_S_FAILURE;
+
++ /* Call gss_inquire_cred rather than gss_inquire_cred_by_mech
++ because GSI doesn't support the latter. -jbasney */
++
+ if (ctx->client_creds &&
+- (ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
+- ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) {
++ (ctx->major = gss_inquire_cred(&ctx->minor,
++ ctx->client_creds, &client->cred_name, NULL, NULL, NULL))) {
+ ssh_gssapi_error(ctx);
+ return (ctx->major);
+ }
+@@ -473,25 +515,37 @@
+ return (ctx->major);
+ }
+
+- if ((ctx->major = ssh_gssapi_parse_ename(ctx,&ename,
++ if ((client->mech->oid.elements != NULL) &&
++ (ctx->major = ssh_gssapi_parse_ename(ctx,&ename,
+ &client->exportedname))) {
+ return (ctx->major);
+ }
+
++ if ((ctx->major = gss_duplicate_name(&ctx->minor, ctx->client,
++ &client->ctx_name)))
++ return ctx->major;
++
+ gss_release_buffer(&ctx->minor, &ename);
++#ifdef KRB5
+ /* Retrieve authentication indicators, if they exist */
+ if ((ctx->major = ssh_gssapi_getindicators(ctx,
+ ctx->client, client))) {
+ ssh_gssapi_error(ctx);
+ return (ctx->major);
+ }
++#endif
+
+ /* We can't copy this structure, so we just move the pointer to it */
+ client->creds = ctx->client_creds;
+ ctx->client_creds = GSS_C_NO_CREDENTIAL;
++
++ /* needed for globus_gss_assist_map_and_authorize() */
++ client->context = ctx->context;
++
+ return (ctx->major);
+ }
+
++#ifdef KRB5
+ /* Returns non-zero if Kerberos credentials have already been stored. */
+ int
+ ssh_gssapi_credentials_stored(void)
+@@ -851,12 +905,19 @@
+
+ unsetenv("KRB5CCNAME");
+ }
++#endif
+
+ #ifndef KRB5
+ /* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
+ void
+ ssh_gssapi_cleanup_creds(void)
+ {
++ if (gssapi_client.store.filename != NULL) {
++ /* Unlink probably isn't sufficient */
++ debug("removing gssapi cred file\"%s\"",
++ gssapi_client.store.filename);
++ unlink(gssapi_client.store.filename);
++ }
+ }
+
+ /*
+@@ -879,6 +940,11 @@
+ }
+
+ if (gssapi_client.mech && gssapi_client.mech->storecreds) {
++ if (options.gss_creds_path) {
++ gssapi_client.store.filename =
++ expand_authorized_keys(options.gss_creds_path,
++ the_authctxt->pw);
++ }
+ return (*gssapi_client.mech->storecreds)(&gssapi_client);
+ } else
+ debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism");
+@@ -912,11 +978,13 @@
+
+ (void) kex; /* used in privilege separation */
+
+- if (gssapi_client.exportedname.length == 0 ||
+- gssapi_client.exportedname.value == NULL) {
+- debug("No suitable client data");
++#ifdef GSS_C_GLOBUS_LIMITED_PROXY_FLAG
++ if (limited && options.gsi_allow_limited_proxy != 1) {
++ debug("limited proxy not acceptable for remote login");
+ return 0;
+ }
++#endif
++
+ if (gssapi_client.mech && gssapi_client.mech->userok)
+ if ((*gssapi_client.mech->userok)(&gssapi_client, user)) {
+ gssapi_client.used = 1;
+@@ -927,6 +995,7 @@
+ gss_release_buffer(&lmin, &gssapi_client.displayname);
+ gss_release_buffer(&lmin, &gssapi_client.exportedname);
+ gss_release_cred(&lmin, &gssapi_client.creds);
++ gss_release_name(&lmin, &gssapi_client.ctx_name);
+
+ if (gssapi_client.indicators != NULL) {
+ for (i = 0; gssapi_client.indicators[i] != NULL; i++)
+@@ -942,6 +1011,24 @@
+ return (0);
+ }
+
++/* Priviledged */
++int
++ssh_gssapi_localname(char **user)
++{
++ *user = NULL;
++ if (gssapi_client.displayname.length == 0 ||
++ gssapi_client.displayname.value == NULL) {
++ debug("No suitable client data");
++ return (0);
++ }
++ if (gssapi_client.mech && gssapi_client.mech->localname) {
++ return((*gssapi_client.mech->localname)(&gssapi_client,user));
++ } else {
++ debug("Unknown client authentication type");
++ }
++ return (0);
++}
++
+ /* These bits are only used for rekeying. The unpriviledged child is running
+ * as the user, the monitor is root.
+ *
+@@ -968,9 +1055,11 @@
+ pam_handle_t *pamh = NULL;
+ struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL};
+ char *envstr;
++ char **p; char **pw;
+ #endif
+
+- if (gssapi_client.store.envval == NULL)
++ if (gssapi_client.store.filename == NULL &&
++ gssapi_client.store.envval == NULL)
+ return;
+
+ ok = mm_ssh_gssapi_update_creds(&gssapi_client.store);
+@@ -990,6 +1079,18 @@
+ if (ret)
+ return;
+
++ /* Put ssh pam stack env variables in this new pam stack env
++ * Using pam-pkinit, KRB5CCNAME is set during do_pam_session
++ * this addition enables pam-pkinit to access KRB5CCNAME if used
++ * in sshd-rekey stack too
++ */
++ pw = p = fetch_pam_environment();
++ while ( *pw != NULL ) {
++ pam_putenv(pamh, *pw);
++ pw++;
++ }
++ free_pam_environment(p);
++
+ xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar,
+ gssapi_client.store.envval);
+
+diff -Nur openssh-10.5p1.orig/gss-serv-gsi.c openssh-10.5p1/gss-serv-gsi.c
+--- openssh-10.5p1.orig/gss-serv-gsi.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/gss-serv-gsi.c 2026-09-21 05:41:30.834253017 +0200
+@@ -0,0 +1,328 @@
++/*
++ * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#ifdef GSSAPI
++#ifdef GSI
++
++#include <sys/types.h>
++
++#include <stdarg.h>
++#include <string.h>
++
++#include "xmalloc.h"
++#include "hostfile.h"
++#include "auth.h"
++#include "log.h"
++#include "misc.h"
++#include "servconf.h"
++#include "ssh-gss.h"
++
++extern ServerOptions options;
++
++#include <globus_gss_assist.h>
++
++static int ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name);
++static int ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user);
++static int ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client);
++static int ssh_gssapi_gsi_storecreds_micv2(ssh_gssapi_client *client);
++static int ssh_gssapi_gsi_updatecreds(ssh_gssapi_ccache *store,
++ ssh_gssapi_client *client);
++static int ssh_gssapi_gsi_updatecreds_micv2(ssh_gssapi_ccache *store,
++ ssh_gssapi_client *client);
++
++ssh_gssapi_mech gssapi_gsi_mech = {
++ "dZuIebMjgUqaxvbF7hDbAw==",
++ "GSI",
++ {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"},
++ NULL,
++ &ssh_gssapi_gsi_userok,
++ &ssh_gssapi_gsi_localname,
++ &ssh_gssapi_gsi_storecreds,
++ &ssh_gssapi_gsi_updatecreds
++};
++
++ssh_gssapi_mech gssapi_gsi_mech_micv2 = {
++ "vz8J1E9PzLr8b1K+0remTg==",
++ "GSI",
++ {10, "\x2b\x06\x01\x04\x01\x9b\x50\x01\x01\x01"},
++ NULL,
++ &ssh_gssapi_gsi_userok,
++ &ssh_gssapi_gsi_localname,
++ &ssh_gssapi_gsi_storecreds_micv2,
++ &ssh_gssapi_gsi_updatecreds_micv2
++};
++
++/*
++ * Check if this user is OK to login under GSI. User has been authenticated
++ * as identity in global 'client_name.value' and is trying to log in as passed
++ * username in 'name'.
++ *
++ * Returns non-zero if user is authorized, 0 otherwise.
++ */
++static int
++ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name)
++{
++ int authorized = 0;
++ globus_result_t res;
++#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
++ char lname[256] = "";
++#endif
++
++#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE
++ if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) {
++ return 0;
++ }
++#endif
++
++/* use new globus_gss_assist_map_and_authorize() interface if available */
++#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
++ debug("calling globus_gss_assist_map_and_authorize()");
++ if (GLOBUS_SUCCESS !=
++ (res = globus_gss_assist_map_and_authorize(client->context, "ssh",
++ name, lname, 256))) {
++ debug("%s", globus_error_print_chain(globus_error_get(res)));
++ } else if (lname[0] && strcmp(name, lname) != 0) {
++ debug("GSI user maps to %s, not %s", lname, name);
++ } else {
++ authorized = 1;
++ }
++#else
++ debug("calling globus_gss_assist_userok()");
++ if (GLOBUS_SUCCESS !=
++ (res = (globus_gss_assist_userok(client->displayname.value,
++ name)))) {
++ debug("%s", globus_error_print_chain(globus_error_get(res)));
++ } else {
++ authorized = 1;
++ }
++#endif
++
++ logit("GSI user %s is%s authorized as target user %s",
++ (char *) client->displayname.value, (authorized ? "" : " not"), name);
++
++ return authorized;
++}
++
++/*
++ * Return the local username associated with the GSI credentials.
++ */
++int
++ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user)
++{
++ globus_result_t res;
++#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
++ char lname[256] = "";
++#endif
++
++#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE
++ if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) {
++ return 0;
++ }
++#endif
++
++/* use new globus_gss_assist_map_and_authorize() interface if available */
++#ifdef HAVE_GLOBUS_GSS_ASSIST_MAP_AND_AUTHORIZE
++ debug("calling globus_gss_assist_map_and_authorize()");
++ if (GLOBUS_SUCCESS !=
++ (res = globus_gss_assist_map_and_authorize(client->context, "ssh",
++ NULL, lname, 256))) {
++ debug("%s", globus_error_print_chain(globus_error_get(res)));
++ logit("failed to map GSI user %s", (char *)client->displayname.value);
++ return 0;
++ }
++ *user = strdup(lname);
++#else
++ debug("calling globus_gss_assist_gridmap()");
++ if (GLOBUS_SUCCESS !=
++ (res = globus_gss_assist_gridmap(client->displayname.value, user))) {
++ debug("%s", globus_error_print_chain(globus_error_get(res)));
++ logit("failed to map GSI user %s", (char *)client->displayname.value);
++ return 0;
++ }
++#endif
++
++ logit("GSI user %s mapped to target user %s",
++ (char *) client->displayname.value, *user);
++
++ return 1;
++}
++
++/*
++ * Export GSI credentials to disk.
++ */
++static int
++ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client)
++{
++ OM_uint32 major_status;
++ OM_uint32 minor_status;
++ gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER;
++ char * p;
++
++ if (!client || !client->creds) {
++ return 0;
++ }
++
++ major_status = gss_export_cred(&minor_status,
++ client->creds,
++ GSS_C_NO_OID,
++ 1,
++ &export_cred);
++ if (GSS_ERROR(major_status) && major_status != GSS_S_UNAVAILABLE) {
++ Gssctxt *ctx;
++ ssh_gssapi_build_ctx(&ctx);
++ ctx->major = major_status;
++ ctx->minor = minor_status;
++ ssh_gssapi_set_oid(ctx, &gssapi_gsi_mech.oid);
++ ssh_gssapi_error(ctx);
++ ssh_gssapi_delete_ctx(&ctx);
++ return 0;
++ }
++
++ p = strchr((char *) export_cred.value, '=');
++ if (p == NULL) {
++ logit("Failed to parse exported credentials string '%.100s'",
++ (char *)export_cred.value);
++ gss_release_buffer(&minor_status, &export_cred);
++ return 0;
++ }
++ *p++ = '\0';
++ if (strcmp((char *)export_cred.value,"X509_USER_DELEG_PROXY") == 0) {
++ client->store.envvar = strdup("X509_USER_PROXY");
++ } else {
++ client->store.envvar = strdup((char *)export_cred.value);
++ }
++ if (access(p, R_OK) == 0) {
++ if (client->store.filename) {
++ if (rename(p, client->store.filename) < 0) {
++ logit("Failed to rename %s to %s: %s", p,
++ client->store.filename, strerror(errno));
++ free(client->store.filename);
++ client->store.filename = strdup(p);
++ } else {
++ p = client->store.filename;
++ }
++ } else {
++ client->store.filename = strdup(p);
++ }
++ }
++ client->store.envval = strdup(p);
++#ifdef USE_PAM
++ if (options.use_pam)
++ do_pam_putenv(client->store.envvar, client->store.envval);
++#endif
++ gss_release_buffer(&minor_status, &export_cred);
++
++ return 1;
++}
++
++/*
++ * Export GSI credentials to disk.
++ */
++static int
++ssh_gssapi_gsi_storecreds_micv2(ssh_gssapi_client *client)
++{
++ OM_uint32 major_status;
++ OM_uint32 minor_status;
++ gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER;
++ char * p;
++
++ if (!client || !client->creds) {
++ return 0;
++ }
++
++ major_status = gss_export_cred(&minor_status,
++ client->creds,
++ GSS_C_NO_OID,
++ 1,
++ &export_cred);
++ if (GSS_ERROR(major_status) && major_status != GSS_S_UNAVAILABLE) {
++ Gssctxt *ctx;
++ ssh_gssapi_build_ctx(&ctx);
++ ctx->major = major_status;
++ ctx->minor = minor_status;
++ ssh_gssapi_set_oid(ctx, &gssapi_gsi_mech_micv2.oid);
++ ssh_gssapi_error(ctx);
++ ssh_gssapi_delete_ctx(&ctx);
++ return 0;
++ }
++
++ p = strchr((char *) export_cred.value, '=');
++ if (p == NULL) {
++ logit("Failed to parse exported credentials string '%.100s'",
++ (char *)export_cred.value);
++ gss_release_buffer(&minor_status, &export_cred);
++ return 0;
++ }
++ *p++ = '\0';
++ if (strcmp((char *)export_cred.value,"X509_USER_DELEG_PROXY") == 0) {
++ client->store.envvar = strdup("X509_USER_PROXY");
++ } else {
++ client->store.envvar = strdup((char *)export_cred.value);
++ }
++ if (access(p, R_OK) == 0) {
++ if (client->store.filename) {
++ if (rename(p, client->store.filename) < 0) {
++ logit("Failed to rename %s to %s: %s", p,
++ client->store.filename, strerror(errno));
++ free(client->store.filename);
++ client->store.filename = strdup(p);
++ } else {
++ p = client->store.filename;
++ }
++ } else {
++ client->store.filename = strdup(p);
++ }
++ }
++ client->store.envval = strdup(p);
++#ifdef USE_PAM
++ if (options.use_pam)
++ do_pam_putenv(client->store.envvar, client->store.envval);
++#endif
++ gss_release_buffer(&minor_status, &export_cred);
++
++ return 1;
++}
++
++/*
++ * Export updated GSI credentials to disk.
++ */
++static int
++ssh_gssapi_gsi_updatecreds(ssh_gssapi_ccache *store,ssh_gssapi_client *client)
++{
++ return ssh_gssapi_gsi_storecreds(client);
++}
++
++/*
++ * Export updated GSI credentials to disk.
++ */
++static int
++ssh_gssapi_gsi_updatecreds_micv2(ssh_gssapi_ccache *store,ssh_gssapi_client *client)
++{
++ return ssh_gssapi_gsi_storecreds_micv2(client);
++}
++
++#endif /* GSI */
++#endif /* GSSAPI */
+diff -Nur openssh-10.5p1.orig/gss-serv-krb5.c openssh-10.5p1/gss-serv-krb5.c
+--- openssh-10.5p1.orig/gss-serv-krb5.c 2026-09-21 05:40:58.387892398 +0200
++++ openssh-10.5p1/gss-serv-krb5.c 2026-09-21 05:41:30.834358356 +0200
+@@ -450,6 +450,34 @@
+ return found_principal;
+ }
+
++/* Retrieve the local username associated with a set of Kerberos
++ * credentials. Hopefully we can use this for the 'empty' username
++ * logins discussed in the draft */
++static int
++ssh_gssapi_krb5_localname(ssh_gssapi_client *client, char **user) {
++ krb5_principal princ;
++ int retval;
++
++ if (ssh_gssapi_krb5_init() == 0)
++ return 0;
++
++ if ((retval=krb5_parse_name(krb_context, client->displayname.value,
++ &princ))) {
++ logit("krb5_parse_name(): %.100s",
++ krb5_get_err_text(krb_context,retval));
++ return 0;
++ }
++
++ /* We've got to return a malloc'd string */
++ *user = (char *)xmalloc(256);
++ if (krb5_aname_to_localname(krb_context, princ, 256, *user)) {
++ free(*user);
++ *user = NULL;
++ return(0);
++ }
++
++ return(1);
++}
+
+ /* This writes out any forwarded credentials from the structure populated
+ * during userauth. Called after we have setuid to the user */
+@@ -544,7 +572,7 @@
+ return set_env;
+ }
+
+-int
++static int
+ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
+ ssh_gssapi_client *client)
+ {
+@@ -840,7 +868,7 @@
+ {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"},
+ NULL,
+ &ssh_gssapi_krb5_userok,
+- NULL,
++ &ssh_gssapi_krb5_localname,
+ &ssh_gssapi_krb5_storecreds,
+ &ssh_gssapi_krb5_updatecreds
+ };
+diff -Nur openssh-10.5p1.orig/kexgsss.c openssh-10.5p1/kexgsss.c
+--- openssh-10.5p1.orig/kexgsss.c 2026-09-21 05:40:57.869779148 +0200
++++ openssh-10.5p1/kexgsss.c 2026-09-21 05:41:30.834892434 +0200
+@@ -48,6 +48,7 @@
+ #include "digest.h"
+ #include "ssherr.h"
+
++static void kex_gss_send_error(Gssctxt *ctxt, struct ssh *ssh);
+ extern ServerOptions options;
+
+ static int input_kexgss_init(int, u_int32_t, struct ssh *);
+@@ -81,8 +82,10 @@
+
+ debug2_f("Acquiring credentials");
+
+- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid)))
++ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) {
++ kex_gss_send_error(kex->gss, ssh);
+ fatal("Unable to acquire credentials for the server");
++ }
+
+ ssh_gssapi_build_ctx(&kex->gss);
+ if (kex->gss == NULL)
+@@ -139,13 +142,14 @@
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
+
+ if (GSS_ERROR(gss->major)) {
++ kex_gss_send_error(kex->gss, ssh);
+ if (send_tok->length > 0) {
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
+ (r = sshpkt_send(ssh)) != 0)
+ fatal("sshpkt failed: %s", ssh_err(r));
+ }
+- fatal("accept_ctx died");
++ ssh_packet_disconnect(ssh, "GSSAPI Key Exchange handshake failed");
+ }
+
+ if (!(*ret_flags & GSS_C_MUTUAL_FLAG))
+@@ -600,4 +604,26 @@
+ return kexgssgex_final(ssh, &send_tok, &ret_flags);
+ }
+
++static void
++kex_gss_send_error(Gssctxt *ctxt, struct ssh *ssh) {
++ char *errstr;
++ OM_uint32 maj, min;
++ int r;
++
++ errstr = mm_ssh_gssapi_last_error(ctxt, &maj, &min);
++ if (errstr) {
++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_ERROR)) != 0 ||
++ (r = sshpkt_put_u32(ssh, maj)) != 0 ||
++ (r = sshpkt_put_u32(ssh, min)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, errstr)) != 0 ||
++ (r = sshpkt_put_cstring(ssh, "")) != 0 ||
++ (r = sshpkt_send(ssh)) != 0)
++ fatal("sshpkt failed: %s", ssh_err(r));
++ if ((r = ssh_packet_write_wait(ssh)) != 0)
++ fatal("ssh_packet_write_wait: %s", ssh_err(r));
++ /* XXX - We should probably log the error locally here */
++ free(errstr);
++ }
++}
++
+ #endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
+diff -Nur openssh-10.5p1.orig/Makefile.in openssh-10.5p1/Makefile.in
+--- openssh-10.5p1.orig/Makefile.in 2026-09-21 05:40:58.406540718 +0200
++++ openssh-10.5p1/Makefile.in 2026-09-21 05:41:30.835178820 +0200
+@@ -134,7 +134,7 @@
+ auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
+ auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
+ monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \
+- auth2-gss.o gss-serv.o gss-serv-krb5.o \
++ auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \
+ loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
+ sftp-server.o sftp-common.o \
+ uidswap.o platform-listen.o $(P11OBJS) $(SKOBJS)
+@@ -145,7 +145,7 @@
+ serverloop.o auth.o auth2.o auth-options.o session.o auth2-chall.o \
+ groupaccess.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
+ auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
+- auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \
++ auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o kexgsss.o \
+ monitor_wrap.o auth-krb5.o \
+ audit.o audit-bsm.o audit-linux.o platform.o \
+ loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
+diff -Nur openssh-10.5p1.orig/misc.c openssh-10.5p1/misc.c
+--- openssh-10.5p1.orig/misc.c 2026-09-21 05:40:57.732140056 +0200
++++ openssh-10.5p1/misc.c 2026-09-21 05:41:30.835560753 +0200
+@@ -464,11 +464,14 @@
+ #define WHITESPACE " \t\r\n"
+ #define QUOTE "\""
+
++/* Characters considered as quotations. */
++#define QUOTES "'\""
++
+ /* return next token in configuration line */
+ static char *
+ strdelim_internal(char **s, int split_equals)
+ {
+- char *old;
++ char *old, *p, *q;
+ int wspace = 0;
+
+ if (*s == NULL)
+@@ -476,6 +479,21 @@
+
+ old = *s;
+
++ if ((q=strchr(QUOTES, (int) *old)) && *q)
++ {
++ /* find next quote character, point old to start of quoted
++ * string */
++ for (p = ++old; *p && *p != *q; p++)
++ ;
++
++ /* find start of next token */
++ *s = (*p) ? p + strspn(p + 1, WHITESPACE) + 1 : NULL;
++
++ /* terminate 'old' token */
++ *p = '\0';
++ return (old);
++ }
++
+ *s = strpbrk(*s,
+ split_equals ? WHITESPACE QUOTE "=" : WHITESPACE QUOTE);
+ if (*s == NULL)
+diff -Nur openssh-10.5p1.orig/monitor.c openssh-10.5p1/monitor.c
+--- openssh-10.5p1.orig/monitor.c 2026-09-21 05:40:58.330403648 +0200
++++ openssh-10.5p1/monitor.c 2026-09-21 05:41:30.836214271 +0200
+@@ -142,6 +142,9 @@
+ int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_error(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_indicate_mechs(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_localname(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *);
+ #endif
+
+@@ -199,7 +202,7 @@
+ #endif
+ {MONITOR_REQ_SETCOMPAT, MON_ONCE, mm_answer_setcompat},
+ {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
+- {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
++ {MONITOR_REQ_PWNAM, MON_AUTH, mm_answer_pwnamallow},
+ {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
+ #ifdef WITH_SELINUX
+ {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
+@@ -207,7 +210,7 @@
+ {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
+ {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
+ #ifdef USE_PAM
+- {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start},
++ {MONITOR_REQ_PAM_START, MON_ISAUTH, mm_answer_pam_start},
+ {MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account},
+ {MONITOR_REQ_PAM_INIT_CTX, MON_ONCE, mm_answer_pam_init_ctx},
+ {MONITOR_REQ_PAM_QUERY, 0, mm_answer_pam_query},
+@@ -231,8 +234,11 @@
+ {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx},
+ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
+ {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok},
+- {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic},
++ {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic},
+ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign},
++ {MONITOR_REQ_GSSERR, MON_ISAUTH | MON_ONCE, mm_answer_gss_error},
++ {MONITOR_REQ_GSSMECHS, MON_ISAUTH, mm_answer_gss_indicate_mechs},
++ {MONITOR_REQ_GSSLOCALNAME, MON_ISAUTH, mm_answer_gss_localname},
+ #endif
+ {0, 0, NULL}
+ };
+@@ -242,6 +248,8 @@
+ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx},
+ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
+ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign},
++ {MONITOR_REQ_GSSERR, 0, mm_answer_gss_error},
++ {MONITOR_REQ_GSSMECHS, 0, mm_answer_gss_indicate_mechs},
+ {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds},
+ #endif
+ {MONITOR_REQ_STATE, MON_ONCE, mm_answer_state},
+@@ -323,6 +331,8 @@
+ #ifdef GSSAPI
+ /* and for the GSSAPI key exchange */
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSMECHS, 1);
+ #endif
+
+ /* The first few requests do not require asynchronous access */
+@@ -474,6 +484,8 @@
+ #ifdef GSSAPI
+ /* and for the GSSAPI key exchange */
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1);
++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSMECHS, 1);
+ #endif
+
+ if (auth_opts->permit_pty_flag) {
+@@ -922,14 +934,17 @@
+ if (!compat_set)
+ fatal_f("state error: setcompat never called");
+
+- if (authctxt->attempt++ != 0)
+- fatal_f("multiple attempts for getpwnam");
+-
++ if (authctxt->user) free(authctxt->user);
+ if ((r = sshbuf_get_cstring(m, &authctxt->user, NULL)) != 0)
+ fatal_fr(r, "parse");
+
+ pwent = getpwnamallow(ssh, authctxt->user);
+
++#ifdef USE_PAM
++ if (options.permit_pam_user_change)
++ setproctitle("%s [priv]", pwent ? "[pam]" : "unknown");
++ else
++#endif
+ setproctitle("%s [priv]", pwent ? authctxt->user : "unknown");
+
+ sshbuf_reset(m);
+@@ -2283,6 +2298,79 @@
+ }
+
+ int
++mm_answer_gss_error(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++ OM_uint32 major, minor;
++ char *msg;
++ int r;
++
++ msg=ssh_gssapi_last_error(gsscontext, &major, &minor);
++ sshbuf_reset(m);
++ if ((r = sshbuf_put_u32(m, major)) != 0 ||
++ (r = sshbuf_put_u32(m, minor)) != 0 ||
++ (r = sshbuf_put_cstring(m, msg)) != 0)
++ fatal_fr(r, "buffer error");
++
++ mm_request_send(socket, MONITOR_ANS_GSSERR, m);
++
++ free(msg);
++
++ return(0);
++}
++
++int
++mm_answer_gss_indicate_mechs(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++ OM_uint32 major, minor;
++ gss_OID_set mech_set;
++ size_t i;
++ int r;
++
++ major=gss_indicate_mechs(&minor, &mech_set);
++
++ sshbuf_reset(m);
++ if ((r = sshbuf_put_u32(m, major)) != 0 ||
++ (r = sshbuf_put_u32(m, mech_set->count)) != 0)
++ fatal_fr(r, "buffer error");
++ for (i = 0; i < mech_set->count; i++) {
++ if ((r = sshbuf_put_string(m, mech_set->elements[i].elements,
++ mech_set->elements[i].length)) != 0)
++ fatal_fr(r, "buffer error");
++ }
++
++ gss_release_oid_set(&minor, &mech_set);
++
++ mm_request_send(socket, MONITOR_ANS_GSSMECHS, m);
++
++ return(0);
++}
++
++int
++mm_answer_gss_localname(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++ char *name;
++ int r;
++
++ ssh_gssapi_localname(&name);
++
++ sshbuf_reset(m);
++ if (name) {
++ if ((r = sshbuf_put_cstring(m, name)) != 0)
++ fatal_fr(r, "buffer error");
++ debug3_f("sending result %s", name);
++ free(name);
++ } else {
++ if ((r = sshbuf_put_cstring(m, "")) != 0)
++ fatal_fr(r, "buffer error");
++ debug3_f("sending result \"\"");
++ }
++
++ mm_request_send(socket, MONITOR_ANS_GSSLOCALNAME, m);
++
++ return(0);
++}
++
++int
+ mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m)
+ {
+ gss_buffer_desc data;
+diff -Nur openssh-10.5p1.orig/monitor.h openssh-10.5p1/monitor.h
+--- openssh-10.5p1.orig/monitor.h 2026-09-21 05:40:58.159623625 +0200
++++ openssh-10.5p1/monitor.h 2026-09-21 05:41:30.836706022 +0200
+@@ -77,6 +77,10 @@
+
+ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
+ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
++
++ MONITOR_REQ_GSSMECHS = 200, MONITOR_ANS_GSSMECHS = 201,
++ MONITOR_REQ_GSSLOCALNAME = 202, MONITOR_ANS_GSSLOCALNAME = 203,
++ MONITOR_REQ_GSSERR = 204, MONITOR_ANS_GSSERR = 205
+ };
+
+ struct ssh;
+diff -Nur openssh-10.5p1.orig/monitor_wrap.c openssh-10.5p1/monitor_wrap.c
+--- openssh-10.5p1.orig/monitor_wrap.c 2026-09-21 05:40:58.176308411 +0200
++++ openssh-10.5p1/monitor_wrap.c 2026-09-21 05:41:30.836896026 +0200
+@@ -1176,6 +1176,94 @@
+ return (authenticated);
+ }
+
++char *
++mm_ssh_gssapi_last_error(Gssctxt *ctx, OM_uint32 *major, OM_uint32 *minor)
++{
++ struct sshbuf *m = NULL;
++ OM_uint32 maj,min;
++ char *errstr;
++ int r;
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSERR, m);
++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSERR, m);
++
++ if ((r = sshbuf_get_u32(m, &maj)) != 0 ||
++ (r = sshbuf_get_u32(m, &min)) != 0)
++ fatal_fr(r, "buffer error");
++
++ if (major) *major=maj;
++ if (minor) *minor=min;
++
++ if ((r = sshbuf_get_cstring(m, &errstr, NULL)) != 0)
++ fatal_fr(r, "buffer error");
++
++ sshbuf_free(m);
++
++ return(errstr);
++}
++
++OM_uint32
++mm_gss_indicate_mechs(OM_uint32 *minor_status, gss_OID_set *mech_set)
++{
++ struct sshbuf *m = NULL;
++ OM_uint32 major,minor;
++ int count;
++ gss_OID_desc oid;
++ size_t length;
++ int r;
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSMECHS, m);
++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSMECHS, m);
++ if ((r = sshbuf_get_u32(m, &major)) != 0 ||
++ (r = sshbuf_get_u32(m, &count)) != 0)
++ fatal_fr(r, "buffer error");
++
++ gss_create_empty_oid_set(&minor, mech_set);
++ while(count-->0) {
++ if ((r = sshbuf_get_string(m, (u_char **)&oid.elements, &length)) != 0)
++ fatal_fr(r, "buffer error");
++ oid.length = length;
++ gss_add_oid_set_member(&minor, &oid, mech_set);
++ }
++
++ sshbuf_free(m);
++
++ return(major);
++}
++
++int
++mm_ssh_gssapi_localname(char **lname)
++{
++ struct sshbuf *m = NULL;
++ int r;
++
++ if ((m = sshbuf_new()) == NULL)
++ fatal_f("sshbuf_new failed");
++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSLOCALNAME, m);
++
++ debug3_f("waiting for MONITOR_ANS_GSSLOCALNAME");
++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSLOCALNAME,
++ m);
++
++ if ((r = sshbuf_get_cstring(m, lname, NULL)) != 0)
++ fatal_fr(r, "buffer error");
++
++ sshbuf_free(m);
++ if ((*lname == NULL) || (*lname[0] == '\0')) {
++ debug3_f("gssapi identity mapping failed");
++ } else {
++ debug3_f("gssapi identity mapped to %s", *lname);
++ }
++
++ return(0);
++}
++
+ OM_uint32
+ mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash)
+ {
+diff -Nur openssh-10.5p1.orig/monitor_wrap.h openssh-10.5p1/monitor_wrap.h
+--- openssh-10.5p1.orig/monitor_wrap.h 2026-09-21 05:40:58.176576994 +0200
++++ openssh-10.5p1/monitor_wrap.h 2026-09-21 05:41:30.837658467 +0200
+@@ -77,6 +77,10 @@
+ int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex);
+ OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
++int mm_ssh_gssapi_localname(char **user);
++OM_uint32 mm_gss_indicate_mechs(OM_uint32 *minor_status,
++ gss_OID_set *mech_set);
++char *mm_ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *maj, OM_uint32 *min);
+ int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *);
+ #endif
+
+diff -Nur openssh-10.5p1.orig/readconf.c openssh-10.5p1/readconf.c
+--- openssh-10.5p1.orig/readconf.c 2026-09-21 05:40:58.200971333 +0200
++++ openssh-10.5p1/readconf.c 2026-09-21 05:41:30.838296824 +0200
+@@ -2943,11 +2943,11 @@
+ if (options->pubkey_authentication == -1)
+ options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL;
+ if (options->gss_authentication == -1)
+- options->gss_authentication = 0;
++ options->gss_authentication = 1;
+ if (options->gss_keyex == -1)
+- options->gss_keyex = 0;
++ options->gss_keyex = 1;
+ if (options->gss_deleg_creds == -1)
+- options->gss_deleg_creds = 0;
++ options->gss_deleg_creds = 1;
+ if (options->gss_trust_dns == -1)
+ options->gss_trust_dns = 0;
+ if (options->gss_renewal_rekey == -1)
+diff -Nur openssh-10.5p1.orig/readconf.h openssh-10.5p1/readconf.h
+--- openssh-10.5p1.orig/readconf.h 2026-09-21 05:40:57.870414662 +0200
++++ openssh-10.5p1/readconf.h 2026-09-21 05:41:30.839745705 +0200
+@@ -80,6 +80,8 @@
+ char *host_key_alias; /* hostname alias for .ssh/known_hosts */
+ char *proxy_command; /* Proxy command for connecting the host. */
+ char *user; /* User to log in as. */
++ int implicit; /* Login user was not specified.
++ Server may choose based on authctxt. */
+ int escape_char; /* Escape character; -2 = none */
+
+ u_int num_system_hostfiles; /* Paths for /etc/ssh/ssh_known_hosts */
+diff -Nur openssh-10.5p1.orig/servconf.c openssh-10.5p1/servconf.c
+--- openssh-10.5p1.orig/servconf.c 2026-09-21 05:40:58.388343090 +0200
++++ openssh-10.5p1/servconf.c 2026-09-21 06:41:22.532395894 +0200
+@@ -1201,6 +1201,9 @@
+ if (*activep && *charptr == NULL)
+ *charptr = xstrdup(arg);
+ break;
++ case sPermitPAMUserChange:
++ intptr = &options->permit_pam_user_change;
++ goto parse_flag;
+ #endif
+
+ /* Standard Options */
+@@ -1442,18 +1445,29 @@
+ case sKerberosTicketCleanup:
+ intptr = &options->kerberos_ticket_cleanup;
+ goto parse_flag;
++
+ #ifdef USE_AFS
+ case sKerberosGetAFSToken:
+ intptr = &options->kerberos_get_afs_token;
+ goto parse_flag;
+ #endif /* USE_AFS */
+-#endif /* KRB5 */
+
+ case sKerberosUniqueCCache:
+ intptr = &options->kerberos_unique_ccache;
+ goto parse_flag;
+
++ case sKerberosUseKuserok:
++ intptr = &options->use_kuserok;
++ goto parse_flag;
++#endif /* KRB5 */
++
+ #ifdef GSSAPI
++#ifdef GSI
++ case sGSIAllowLimitedProxy:
++ intptr = &options->gsi_allow_limited_proxy;
++ goto parse_flag;
++#endif
++
+ case sGSSAPIAuthentication:
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+@@ -1470,6 +1484,10 @@
+ intptr = &options->gss_deleg_creds;
+ goto parse_flag;
+
++ case sGSSAPICredentialsPath:
++ charptr = &options->gss_creds_path;
++ goto parse_filename;
++
+ case sGSSAPIStrictAcceptorCheck:
+ intptr = &options->gss_strict_acceptor;
+ goto parse_flag;
+@@ -2293,10 +2311,6 @@
+ }
+ break;
+
+- case sKerberosUseKuserok:
+- intptr = &options->use_kuserok;
+- goto parse_flag;
+-
+ case sGSSAPIEnablek5users:
+ intptr = &options->enable_k5users;
+ goto parse_flag;
+@@ -4287,6 +4301,7 @@
+ #ifdef USE_PAM
+ dump_cfg_fmtint(sUsePAM, o->use_pam);
+ dump_cfg_string(sPAMServiceName, o->pam_service_name);
++ dump_cfg_fmtint(sPermitPAMUserChange, o->permit_pam_user_change);
+ #endif
+ dump_cfg_int(sLoginGraceTime, o->login_grace_time);
+ dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
+@@ -4319,9 +4334,13 @@
+ dump_cfg_fmtint(sGSSAPIEnablek5users, o->enable_k5users);
+ #endif
+ #ifdef GSSAPI
++#ifdef GSI
++ dump_cfg_fmtint(sGSIAllowLimitedProxy, o->gsi_allow_limited_proxy);
++#endif
+ dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
+ dump_cfg_fmtint(sGSSAPICleanupCredentials, o->gss_cleanup_creds);
+ dump_cfg_fmtint(sGSSAPIDelegateCredentials, o->gss_deleg_creds);
++ dump_cfg_string(sGSSAPICredentialsPath, o->gss_creds_path);
+ dump_cfg_fmtint(sGSSAPIKeyExchange, o->gss_keyex);
+ dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
+ dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
+diff -Nur openssh-10.5p1.orig/servconf.h openssh-10.5p1/servconf.h
+--- openssh-10.5p1.orig/servconf.h 2026-09-21 05:40:58.388570573 +0200
++++ openssh-10.5p1/servconf.h 2026-09-21 06:33:04.752156023 +0200
+@@ -275,11 +275,13 @@
+ #ifdef USE_PAM
+ #define SSHD_CONFIG_ENTRIES_PAM \
+ SSHCONF_INTFLAG(use_pam, UsePAM, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+-SSHCONF_STRING(pam_service_name, PAMServiceName, SSHCFG_ALL, SSHCFG_COPY_NONE)
++SSHCONF_STRING(pam_service_name, PAMServiceName, SSHCFG_ALL, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(permit_pam_user_change, PermitPAMUserChange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE)
+ #else
+ #define SSHD_CONFIG_ENTRIES_PAM \
+ SSHCONF_UNSUPPORTED_INT(use_pam, UsePAM, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_STRING(pam_service_name, PAMServiceName, SSHCFG_ALL)
++SSHCONF_UNSUPPORTED_STRING(pam_service_name, PAMServiceName, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_INT(permit_pam_user_change, PermitPAMUserChange, SSHCFG_GLOBAL)
+ #endif
+
+ #ifdef DISABLE_LASTLOG
+@@ -318,14 +320,23 @@
+ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOBAL)
+ #endif /* KRB5 */
+
++#ifdef GSI
++#define SSHD_CONFIG_ENTRIES_GSI \
++SSHCONF_INTFLAG(gsi_allow_limited_proxy, GSIAllowLimitedProxy, SSHCFG_ALL, 0, SSHCFG_COPY_NONE)
++#else
++#define SSHD_CONFIG_ENTRIES_GSI \
++SSHCONF_UNSUPPORTED_INT(gsi_allow_limited_proxy, GSIAllowLimitedProxy, SSHCFG_ALL)
++#endif
++
+ #ifdef GSSAPI
+ #define SSHD_CONFIG_ENTRIES_GSS \
+-SSHCONF_INTFLAG(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_ALIAS(GSSAPICleanupCreds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+-SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_MATCH) \
++SSHCONF_STRING(gss_creds_path, GSSAPICredentialsPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+@@ -337,6 +348,7 @@
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_STRING(gss_creds_path, GSSAPICredentialsPath, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+@@ -350,6 +362,7 @@
+ #define SSHD_CONFIG_ENTRIES \
+ SSHD_CONFIG_ENTRIES_BASE \
+ SSHD_CONFIG_ENTRIES_KRB5 \
++ SSHD_CONFIG_ENTRIES_GSI \
+ SSHD_CONFIG_ENTRIES_GSS \
+
+ /* Macros to declare ServerOptions member variables */
+diff -Nur openssh-10.5p1.orig/ssh.1 openssh-10.5p1/ssh.1
+--- openssh-10.5p1.orig/ssh.1 2026-09-21 05:40:57.870967025 +0200
++++ openssh-10.5p1/ssh.1 2026-09-21 05:41:30.841046692 +0200
+@@ -1432,6 +1432,18 @@
+ on to new connections).
+ .It Ev USER
+ Set to the name of the user logging in.
++.It Ev X509_CERT_DIR
++Used for GSI authentication. Specifies a non-standard location for the
++CA certificates directory.
++.It Ev X509_USER_CERT
++Used for GSI authentication. Specifies a non-standard location for the
++certificate to be used for authentication to the server.
++.It Ev X509_USER_KEY
++Used for GSI authentication. Specifies a non-standard location for the
++private key to be used for authentication to the server.
++.It Ev X509_USER_PROXY
++Used for GSI authentication. Specifies a non-standard location for the
++proxy credential to be used for authentication to the server.
+ .El
+ .Pp
+ Additionally,
+diff -Nur openssh-10.5p1.orig/ssh.c openssh-10.5p1/ssh.c
+--- openssh-10.5p1.orig/ssh.c 2026-09-21 05:40:58.409804632 +0200
++++ openssh-10.5p1/ssh.c 2026-09-21 05:41:30.841403029 +0200
+@@ -580,6 +580,38 @@
+ fatal("Can't open user config file %.100s: "
+ "%.100s", config, strerror(errno));
+ } else {
++ /*
++ * Since the config file parsing code aborts if it sees
++ * options it doesn't recognize, allow users to put
++ * options specific to compile-time add-ons in alternate
++ * config files so their primary config file will
++ * interoperate SSH versions that don't support those
++ * options.
++ */
++#ifdef GSSAPI
++ r = snprintf(buf, sizeof buf, "%s/%s.gssapi", pw->pw_dir,
++ _PATH_SSH_USER_CONFFILE);
++ if (r > 0 && (size_t)r < sizeof(buf))
++ (void)read_config_file(buf, pw, host, host_name, cmd,
++ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
++ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
++#ifdef GSI
++ r = snprintf(buf, sizeof buf, "%s/%s.gsi", pw->pw_dir,
++ _PATH_SSH_USER_CONFFILE);
++ if (r > 0 && (size_t)r < sizeof(buf))
++ (void)read_config_file(buf, pw, host, host_name, cmd,
++ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
++ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
++#endif
++#if defined(KRB5)
++ r = snprintf(buf, sizeof buf, "%s/%s.krb", pw->pw_dir,
++ _PATH_SSH_USER_CONFFILE);
++ if (r > 0 && (size_t)r < sizeof(buf))
++ (void)read_config_file(buf, pw, host, host_name, cmd,
++ &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
++ (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
++#endif
++#endif
+ r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir,
+ _PATH_SSH_USER_CONFFILE);
+ if (r > 0 && (size_t)r < sizeof(buf))
+@@ -1283,6 +1315,9 @@
+ if (options.user == NULL) {
+ user_was_default = 1;
+ options.user = xstrdup(pw->pw_name);
++ options.implicit = 1;
++ } else {
++ options.implicit = 0;
+ }
+
+ /*
+diff -Nur openssh-10.5p1.orig/ssh_config openssh-10.5p1/ssh_config
+--- openssh-10.5p1.orig/ssh_config 2026-09-21 05:40:57.871171725 +0200
++++ openssh-10.5p1/ssh_config 2026-09-21 05:41:30.841843282 +0200
+@@ -22,9 +22,9 @@
+ # ForwardX11 no
+ # PasswordAuthentication yes
+ # HostbasedAuthentication no
+-# GSSAPIAuthentication no
+-# GSSAPIDelegateCredentials no
+-# GSSAPIKeyExchange no
++# GSSAPIAuthentication yes
++# GSSAPIDelegateCredentials yes
++# GSSAPIKeyExchange yes
+ # GSSAPITrustDNS no
+ # BatchMode no
+ # CheckHostIP no
+diff -Nur openssh-10.5p1.orig/ssh_config.5 openssh-10.5p1/ssh_config.5
+--- openssh-10.5p1.orig/ssh_config.5 2026-09-21 05:40:58.410062978 +0200
++++ openssh-10.5p1/ssh_config.5 2026-09-21 05:41:30.842032994 +0200
+@@ -52,6 +52,12 @@
+ user's configuration file
+ .Pq Pa ~/.ssh/config
+ .It
++GSSAPI configuration file
++.Pq Pa $HOME/.ssh/config.gssapi
++.It
++Kerberos configuration file
++.Pq Pa $HOME/.ssh/config.krb
++.It
+ system-wide configuration file
+ .Pq Pa /etc/ssh/ssh_config
+ .El
+@@ -1006,7 +1012,7 @@
+ .It Cm GSSAPIAuthentication
+ Specifies whether user authentication based on GSSAPI is allowed.
+ The default is
+-.Cm no .
++.Cm yes .
+ .It Cm GSSAPIClientIdentity
+ If set, specifies the GSSAPI client identity that ssh should use when
+ connecting to the server. The default is unset, which means that the default
+@@ -1014,12 +1020,12 @@
+ .It Cm GSSAPIDelegateCredentials
+ Forward (delegate) credentials to the server.
+ The default is
+-.Cm no .
++.Cm yes .
+ .It Cm GSSAPIKeyExchange
+ Specifies whether key exchange based on GSSAPI may be used. When using
+ GSSAPI key exchange the server need not have a host key.
+ The default is
+-.Dq no .
++.Dq yes .
+ .It Cm GSSAPIRenewalForcesRekey
+ If set to
+ .Dq yes
+@@ -1663,7 +1669,7 @@
+ .Cm password ) .
+ The default is:
+ .Bd -literal -offset indent
+-gssapi-with-mic,hostbased,publickey,
++gssapi-keyex,gssapi-with-mic,hostbased,publickey,
+ keyboard-interactive,password
+ .Ed
+ .It Cm ProxyCommand
+diff -Nur openssh-10.5p1.orig/sshconnect2.c openssh-10.5p1/sshconnect2.c
+--- openssh-10.5p1.orig/sshconnect2.c 2026-09-21 05:40:58.330978440 +0200
++++ openssh-10.5p1/sshconnect2.c 2026-09-21 05:41:30.842475976 +0200
+@@ -862,6 +862,11 @@
+ gss_OID mech = NULL;
+ char *gss_host = NULL;
+
++ if (!options.gss_authentication) {
++ verbose("GSSAPI authentication disabled.");
++ return 0;
++ }
++
+ if (options.gss_server_identity) {
+ gss_host = xstrdup(options.gss_server_identity);
+ } else if (options.gss_trust_dns) {
+@@ -970,7 +975,8 @@
+
+ if (status == GSS_S_COMPLETE) {
+ /* send either complete or MIC, depending on mechanism */
+- if (!(flags & GSS_C_INTEG_FLAG)) {
++ if (strcmp(authctxt->method->name, "gssapi") == 0 ||
++ !(flags & GSS_C_INTEG_FLAG)) {
+ if ((r = sshpkt_start(ssh,
+ SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE)) != 0 ||
+ (r = sshpkt_send(ssh)) != 0)
+@@ -1137,6 +1143,20 @@
+ return r;
+ }
+
++#ifdef GSI
++extern
++const gss_OID_desc * const gss_mech_globus_gssapi_openssl;
++extern
++const gss_OID_desc * const gss_mech_globus_gssapi_openssl_micv2;
++#define is_gsi_oid(oid) \
++ ((oid->length == gss_mech_globus_gssapi_openssl->length && \
++ (memcmp(oid->elements, gss_mech_globus_gssapi_openssl->elements, \
++ oid->length) == 0)) || \
++ (oid->length == gss_mech_globus_gssapi_openssl_micv2->length && \
++ (memcmp(oid->elements, gss_mech_globus_gssapi_openssl_micv2->elements, \
++ oid->length) == 0)))
++#endif
++
+ int
+ userauth_gsskeyex(struct ssh *ssh)
+ {
+@@ -1159,6 +1179,12 @@
+ if ((b = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
+
++#ifdef GSI
++ if (options.implicit && is_gsi_oid(gss_kex_context->oid))
++ ssh_gssapi_buildmic(b, "", authctxt->service,
++ "gssapi-keyex", ssh->kex->session_id);
++ else
++#endif
+ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service,
+ "gssapi-keyex", ssh->kex->session_id);
+
+@@ -1172,7 +1198,9 @@
+ }
+
+ if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
+- (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 ||
++ (r = sshpkt_put_cstring(ssh,
++ (options.implicit && is_gsi_oid(gss_kex_context->oid)) ?
++ "" : authctxt->server_user)) != 0 ||
+ (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 ||
+ (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 ||
+ (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 ||
+diff -Nur openssh-10.5p1.orig/sshd.8 openssh-10.5p1/sshd.8
+--- openssh-10.5p1.orig/sshd.8 2026-09-21 05:40:57.927193002 +0200
++++ openssh-10.5p1/sshd.8 2026-09-21 05:41:30.842869209 +0200
+@@ -844,6 +844,29 @@
+ # A CA key, accepted for any host in *.mydomain.com or *.mydomain.org
+ @cert-authority *.mydomain.org,*.mydomain.com ssh-rsa AAAAB5W...
+ .Ed
++.Sh ENVIRONMENT
++.Nm
++will normally set the following environment variables:
++.Bl -tag -width "SSH_ORIGINAL_COMMAND"
++.It Ev GRIDMAP
++Applies to GSI authentication/authorization. Specifies the location of the
++gridmapfile. If not specified, the gridmap file is assumed to be available at
++/etc/grid-security/grid-mapfile for services running as root and at
++HOME/.gridmap for services running as non-root where HOME is the home directory
++of the effective user from the password file entry.
++.It Ev X509_CERT_DIR
++Used for GSI authentication. Specifies a non-standard location for the
++CA certificates directory.
++.It Ev X509_USER_CERT
++Used for GSI authentication. Specifies a non-standard location for the
++certificate to be used for authentication to the client.
++.It Ev X509_USER_KEY
++Used for GSI authentication. Specifies a non-standard location for the
++private key to be used for authentication to the client.
++.It Ev X509_USER_PROXY
++Used for GSI authentication. Specifies a non-standard location for the
++proxy credential to be used for authentication to the client.
++.El
+ .Sh FILES
+ .Bl -tag -width Ds -compact
+ .It Pa ~/.hushlogin
+diff -Nur openssh-10.5p1.orig/sshd_config openssh-10.5p1/sshd_config
+--- openssh-10.5p1.orig/sshd_config 2026-09-21 05:40:57.988597295 +0200
++++ openssh-10.5p1/sshd_config 2026-09-21 05:41:30.843096438 +0200
+@@ -79,10 +79,11 @@
+ #KerberosUseKuserok yes
+
+ # GSSAPI options
+-#GSSAPIAuthentication no
++#GSSAPIAuthentication yes
++#GSSAPIDelegateCredentials yes
+ #GSSAPICleanupCredentials yes
+ #GSSAPIStrictAcceptorCheck yes
+-#GSSAPIKeyExchange no
++#GSSAPIKeyExchange yes
+ #GSSAPIEnablek5users no
+
+ # Set this to 'yes' to enable PAM authentication, account processing,
+@@ -98,6 +99,10 @@
+ # problems.
+ #UsePAM no
+
++# Set to 'yes' to allow the PAM stack to change the user name during
++# calls to authentication
++#PermitPAMUserChange no
++
+ #AllowAgentForwarding yes
+ #AllowTcpForwarding yes
+ #GatewayPorts no
+diff -Nur openssh-10.5p1.orig/sshd_config.5 openssh-10.5p1/sshd_config.5
+--- openssh-10.5p1.orig/sshd_config.5 2026-09-21 05:40:58.388975909 +0200
++++ openssh-10.5p1/sshd_config.5 2026-09-21 05:41:30.843245655 +0200
+@@ -743,15 +743,30 @@
+ to allow the client to select the address to which the forwarding is bound.
+ The default is
+ .Cm no .
++.It Cm GSIAllowLimitedProxy
++Specifies whether to accept limited proxy credentials for authentication.
++The default is
++.Cm no .
+ .It Cm GSSAPIAuthentication
+ Specifies whether user authentication based on GSSAPI is allowed.
+ The default is
+-.Cm no .
++.Cm yes .
+ .It Cm GSSAPICleanupCredentials
+ Specifies whether to automatically destroy the user's credentials cache
+ on logout.
+ The default is
+ .Cm yes .
++.It Cm GSSAPICredentialsPath
++If specified, the delegated GSSAPI credential is stored in the
++given path, overwriting any existing credentials.
++Paths can be specified with syntax similar to the AuthorizedKeysFile
++option (i.e., accepting %h and %u tokens).
++When using this option,
++setting 'GssapiCleanupCredentials no' is recommended,
++so logging out of one session
++doesn't remove the credentials in use by another session of
++the same user.
++Currently only implemented for the GSI mechanism.
+ .It Cm GSSAPIDelegateCredentials
+ Accept delegated credentials on the server side. The default is
+ .CM yes .
+@@ -765,7 +780,7 @@
+ Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange
+ doesn't rely on ssh keys to verify host identity.
+ The default is
+-.Cm no .
++.Cm yes .
+ .It Cm GSSAPIStrictAcceptorCheck
+ Determines whether to be strict about the identity of the GSSAPI acceptor
+ a client authenticates against.
+@@ -2207,6 +2222,12 @@
+ as a non-root user.
+ The default is
+ .Cm no .
++.It Cm PermitPAMUserChange
++If set to
++.Cm yes
++this will enable PAM authentication to change the name of the user being
++authenticated. The default is
++.Cm no .
+ .It Cm VersionAddendum
+ Optionally specifies additional text to append to the SSH protocol banner
+ sent by the server upon connection.
+diff -Nur openssh-10.5p1.orig/sshd_config_redhat openssh-10.5p1/sshd_config_redhat
+--- openssh-10.5p1.orig/sshd_config_redhat 2026-09-21 05:40:57.844000670 +0200
++++ openssh-10.5p1/sshd_config_redhat 2026-09-21 05:41:30.843599630 +0200
+@@ -2,9 +2,6 @@
+
+ KbdInteractiveAuthentication no
+
+-GSSAPIAuthentication yes
+-GSSAPICleanupCredentials no
+-
+ UsePAM yes
+
+ X11Forwarding yes
+diff -Nur openssh-10.5p1.orig/sshd-session.c openssh-10.5p1/sshd-session.c
+--- openssh-10.5p1.orig/sshd-session.c 2026-09-21 05:40:58.388773233 +0200
++++ openssh-10.5p1/sshd-session.c 2026-09-21 05:41:30.843710046 +0200
+@@ -1391,11 +1391,12 @@
+ #endif
+
+ #ifdef GSSAPI
+- if (options.gss_authentication) {
++ if (options.gss_authentication && options.gss_deleg_creds) {
+ temporarily_use_uid(authctxt->pw);
+ authctxt->krb5_set_env = ssh_gssapi_storecreds();
+ restore_uid();
+ }
++#ifdef KRB5
+ /*
+ * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
+ * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
+@@ -1495,6 +1496,7 @@
+ }
+ }
+ #endif
++#endif
+ #ifdef WITH_SELINUX
+ sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
+ inetd_flag, do_pam_putenv, the_authctxt,
+diff -Nur openssh-10.5p1.orig/ssh-gss.h openssh-10.5p1/ssh-gss.h
+--- openssh-10.5p1.orig/ssh-gss.h 2026-09-21 05:40:58.388652800 +0200
++++ openssh-10.5p1/ssh-gss.h 2026-09-21 05:41:30.844001926 +0200
+@@ -110,12 +110,14 @@
+ } ssh_gssapi_ccache;
+
+ typedef struct {
++ gss_OID_desc oid;
+ gss_buffer_desc displayname;
+ gss_buffer_desc exportedname;
+ gss_cred_id_t creds;
+- gss_name_t name;
++ gss_name_t cred_name, ctx_name;
+ struct ssh_gssapi_mech_struct *mech;
+ ssh_gssapi_ccache store;
++ gss_ctx_id_t context; /* needed for globus_gss_assist_map_and_authorize() */
+ int used;
+ int updated;
+ char **indicators; /* auth indicators */
+@@ -138,7 +140,7 @@
+ OM_uint32 minor; /* both */
+ gss_ctx_id_t context; /* both */
+ gss_name_t name; /* both */
+- gss_OID oid; /* client */
++ gss_OID oid; /* both */
+ gss_cred_id_t creds; /* server */
+ gss_name_t client; /* server */
+ gss_cred_id_t client_creds; /* both */
+@@ -185,6 +187,9 @@
+ OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *);
+ int ssh_gssapi_credentials_updated(Gssctxt *);
+
++int ssh_gssapi_localname(char **name);
++void ssh_gssapi_rekey_creds();
++
+ /* In the server */
+ typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *,
+ const char *);
+diff -Nur openssh-10.5p1.orig/version.h openssh-10.5p1/version.h
+--- openssh-10.5p1.orig/version.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/version.h 2026-09-21 05:41:30.844118812 +0200
+@@ -2,5 +2,19 @@
+
+ #define SSH_VERSION "OpenSSH_10.5"
+
++#ifdef GSI
++#define GSI_VERSION " GSI"
++#else
++#define GSI_VERSION ""
++#endif
++
++#ifdef KRB5
++#define KRB5_VERSION " KRB5"
++#else
++#define KRB5_VERSION ""
++#endif
++
+ #define SSH_PORTABLE "p1"
+-#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
++#define GSI_PORTABLE "c-GSI"
++#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \
++ GSI_VERSION KRB5_VERSION
diff --git a/2001-openssh-10.3p1-hpn-18.9.0.patch b/2001-openssh-10.3p1-hpn-18.9.0.patch
deleted file mode 100644
index c661c3e..0000000
--- a/2001-openssh-10.3p1-hpn-18.9.0.patch
+++ /dev/null
@@ -1,15679 +0,0 @@
-diff -Nur openssh-10.3p1.orig/auth2.c openssh-10.3p1/auth2.c
---- openssh-10.3p1.orig/auth2.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/auth2.c 2026-07-02 07:02:04.260910320 +0200
-@@ -52,6 +52,8 @@
- #include "dispatch.h"
- #include "pathnames.h"
- #include "ssherr.h"
-+#include "canohost.h"
-+
- #ifdef GSSAPI
- #include "ssh-gss.h"
- #endif
-@@ -75,6 +77,8 @@
- extern Authmethod method_gssapi;
- #endif
-
-+static int log_flag = 0;
-+
- Authmethod *authmethods[] = {
- &method_none,
- &method_pubkey,
-@@ -104,6 +108,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)
- {
-@@ -308,6 +315,11 @@
-
- debug("userauth-request for user %s service %s method %s",
- user[0] ? user : "<implicit>", 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-10.3p1.orig/binn.c openssh-10.3p1/binn.c
---- openssh-10.3p1.orig/binn.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/binn.c 2026-07-02 00:27:29.718604640 +0200
-@@ -0,0 +1,3541 @@
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <stdint.h>
-+#include <string.h>
-+#include <memory.h>
-+#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 <sys/endian.h>
-+#elif defined(_AIX)
-+#include <sys/machine.h>
-+#else
-+#include <endian.h>
-+#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(const 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(const 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;
-+ } 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(const 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;
-+
-+}
-+
-+/*************************************************************************************/
-+
-+// deprecated: unsecure. the size can be corrupted accidentally or intentionally
-+BOOL APIENTRY binn_load(const 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 = (void*) data;
-+ return TRUE;
-+
-+}
-+
-+BOOL APIENTRY binn_load_ex(const void *data, int size, binn *value) {
-+
-+ if (data == NULL || value == NULL || size <= 0) return FALSE;
-+ memset(value, 0, sizeof(binn));
-+ value->header = BINN_MAGIC;
-+ //value->allocated = FALSE; -- already zeroed
-+ //value->writable = FALSE;
-+
-+ if (binn_is_valid_ex(data, &value->type, &value->count, &size) == FALSE) return FALSE;
-+ value->ptr = (void*) data;
-+ value->size = size;
-+ return TRUE;
-+
-+}
-+
-+/*************************************************************************************/
-+
-+binn * APIENTRY binn_open(const 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 * APIENTRY binn_open_ex(const void *data, int size) {
-+ binn *item;
-+
-+ item = (binn*) binn_malloc(sizeof(binn));
-+
-+ if (binn_load_ex(data, size, item) == FALSE) {
-+ free_fn(item);
-+ return NULL;
-+ }
-+
-+ item->allocated = TRUE;
-+ return item;
-+
-+}
-+
-+/***************************************************************************/
-+
-+BINN_PRIVATE int binn_get_ptr_type(const 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(const 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;
-+ }
-+
-+ 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;
-+ if (p > plimit) return 0;
-+
-+ 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++) {
-+ if (p > plimit) break;
-+ len = *((unsigned char *)p);
-+ p++;
-+ if (p + len > 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;
-+ } 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(const 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) {
-+ if (*psize < MIN_BINN_SIZE) return FALSE;
-+ 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 (size < MIN_BINN_SIZE || count < 0) return FALSE;
-+
-+ // return the values
-+ if (ptype) *ptype = type;
-+ if (pcount) *pcount = count;
-+ if (psize) *psize = size;
-+ if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf);
-+ return TRUE;
-+}
-+
-+/***************************************************************************/
-+
-+BINN_PRIVATE int binn_buf_type(const void *pbuf) {
-+ int type;
-+
-+ if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN;
-+
-+ return type;
-+
-+}
-+
-+/***************************************************************************/
-+
-+BINN_PRIVATE int binn_buf_count(const void *pbuf) {
-+ int nitems;
-+
-+ if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0;
-+
-+ return nitems;
-+
-+}
-+
-+/***************************************************************************/
-+
-+BINN_PRIVATE int binn_buf_size(const void *pbuf) {
-+ int size=0;
-+
-+ if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0;
-+
-+ return size;
-+
-+}
-+
-+/***************************************************************************/
-+
-+void * APIENTRY binn_ptr(const 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 (void*)ptr;
-+ default:
-+ return NULL;
-+ }
-+
-+}
-+
-+/***************************************************************************/
-+
-+int APIENTRY binn_size(const 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(const 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(const 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;
-+ }
-+
-+}
-+
-+/***************************************************************************/
-+
-+// the container can be smaller than the informed size
-+BINN_PRIVATE BOOL binn_is_valid_ex2(const void *ptr, int *ptype, int *pcount, int *psize) {
-+ int i, type, count, size, header_size;
-+ unsigned char *p, *plimit, *base, len;
-+
-+ if (ptr == NULL) return FALSE;
-+
-+ // is there an informed size?
-+ if (psize && *psize > 0) {
-+ size = *psize;
-+ } else {
-+ size = 0;
-+ }
-+
-+ if (!IsValidBinnHeader(ptr, &type, &count, &size, &header_size)) return FALSE;
-+
-+ // is there an informed size?
-+ if (psize && *psize > 0) {
-+ // is it bigger than 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;
-+ }
-+
-+ p = (unsigned char *)ptr;
-+ base = p;
-+ plimit = p + size - 1;
-+
-+ p += header_size;
-+
-+ // process each (key and) value
-+ for (i = 0; i < count; i++) {
-+ switch (type) {
-+ case BINN_OBJECT:
-+ if (p > plimit) goto Invalid;
-+ // get the key (string) size
-+ len = *p;
-+ p++;
-+ //if (len == 0) goto Invalid;
-+ // advance over the key
-+ p += len;
-+ break;
-+ case BINN_MAP:
-+ // advance over the key
-+ read_map_id(&p, plimit);
-+ break;
-+ case BINN_LIST:
-+ // no key
-+ break;
-+ default:
-+ goto Invalid;
-+ }
-+ // check the value
-+ if (p > plimit) goto Invalid;
-+ if ((*p & BINN_STORAGE_MASK) == BINN_STORAGE_CONTAINER) {
-+ // recursively check the internal container
-+ int size2 = plimit - p + 1; // maximum container size
-+ if (binn_is_valid_ex2(p, NULL, NULL, &size2) == FALSE) goto Invalid;
-+ p += size2;
-+ } else {
-+ // advance over the value
-+ 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 = size;
-+ return TRUE;
-+
-+Invalid:
-+ return FALSE;
-+
-+}
-+
-+// the container must have the informed size, if informed
-+BOOL APIENTRY binn_is_valid_ex(const void *ptr, int *ptype, int *pcount, int *psize) {
-+ int size;
-+
-+ if (psize && *psize > 0) {
-+ size = *psize;
-+ } else {
-+ size = 0;
-+ }
-+
-+ if (binn_is_valid_ex2(ptr, ptype, pcount, &size) == FALSE) return FALSE;
-+
-+ if (psize) {
-+ if (*psize > 0) {
-+ if (size != *psize) return FALSE;
-+ } else if (*psize==0) {
-+ *psize = size;
-+ }
-+ }
-+
-+ return TRUE;
-+}
-+
-+/***************************************************************************/
-+
-+BOOL APIENTRY binn_is_valid(const 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, unsigned char *plimit, 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
-+ if (p > plimit) return FALSE;
-+ byte = *p; p++;
-+ storage_type = byte & BINN_STORAGE_MASK;
-+ if (byte & BINN_STORAGE_HAS_MORE) {
-+ data_type = byte << 8;
-+ if (p > plimit) return FALSE;
-+ 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:
-+ if (p > plimit) return FALSE;
-+ value->vuint8 = *((unsigned char *) p);
-+ value->ptr = p; //value->ptr = &value->vuint8;
-+ break;
-+ case BINN_STORAGE_WORD:
-+ if (p + 1 > plimit) return FALSE;
-+ copy_be16((u16*)&value->vint16, (u16*)p);
-+ value->ptr = &value->vint16;
-+ break;
-+ case BINN_STORAGE_DWORD:
-+ if (p + 3 > plimit) return FALSE;
-+ copy_be32((u32*)&value->vint32, (u32*)p);
-+ value->ptr = &value->vint32;
-+ break;
-+ case BINN_STORAGE_QWORD:
-+ if (p + 7 > plimit) return FALSE;
-+ copy_be64((u64*)&value->vint64, (u64*)p);
-+ value->ptr = &value->vint64;
-+ break;
-+ case BINN_STORAGE_BLOB:
-+ case BINN_STORAGE_STRING:
-+ if (p > plimit) return FALSE;
-+ DataSize = *((unsigned char*)p);
-+ if (DataSize & 0x80) {
-+ if (p + 3 > plimit) return FALSE;
-+ copy_be32((u32*)&DataSize, (u32*)p);
-+ DataSize &= 0x7FFFFFFF;
-+ p+=4;
-+ } else {
-+ p++;
-+ }
-+ if (p + DataSize - 1 > plimit) return FALSE;
-+ 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(const void *ptr, const char *key, binn *value) {
-+ int type, count, size=0, header_size;
-+ unsigned char *p, *plimit;
-+
-+ ptr = binn_ptr(ptr);
-+ if (ptr == NULL || key == NULL || value == NULL) 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;
-+ plimit = p + size - 1;
-+
-+ p = SearchForKey(p, header_size, size, count, key);
-+ if (p == FALSE) return FALSE;
-+
-+ return GetValue(p, plimit, value);
-+
-+}
-+
-+/***************************************************************************/
-+
-+BOOL APIENTRY binn_map_get_value(const void *ptr, int id, binn *value) {
-+ int type, count, size=0, header_size;
-+ unsigned char *p, *plimit;
-+
-+ ptr = binn_ptr(ptr);
-+ if (ptr == NULL || value == NULL) 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;
-+ plimit = p + size - 1;
-+
-+ p = SearchForID(p, header_size, size, count, id);
-+ if (p == FALSE) return FALSE;
-+
-+ return GetValue(p, plimit, value);
-+
-+}
-+
-+/***************************************************************************/
-+
-+BOOL APIENTRY binn_list_get_value(const 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 == NULL || value == NULL) 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 - 1;
-+ p += header_size;
-+
-+ for (i = 0; i < pos; i++) {
-+ p = AdvanceDataPos(p, plimit);
-+ if (p == 0 || p < base) return FALSE;
-+ }
-+
-+ return GetValue(p, plimit, value);
-+
-+}
-+
-+/***************************************************************************/
-+/*** READ PAIR BY POSITION *************************************************/
-+/***************************************************************************/
-+
-+BINN_PRIVATE BOOL binn_read_pair(int expected_type, const 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, plimit, value);
-+
-+}
-+
-+/***************************************************************************/
-+
-+BOOL APIENTRY binn_map_get_pair(const 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(const void *ptr, int pos, char *pkey, binn *value) {
-+
-+ return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value);
-+
-+}
-+
-+/***************************************************************************/
-+
-+binn * APIENTRY binn_map_pair(const 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(const 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(const 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(const 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, const void *ptr, int expected_type) {
-+ int type, count, size=0, header_size;
-+
-+ ptr = binn_ptr(ptr);
-+ if (ptr == NULL || iter == NULL) 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 == NULL || iter->pnext == NULL || 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, iter->plimit, 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 == NULL || iter->pnext == NULL || 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, iter->plimit, 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(const 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(const 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(const void *psource, void *pdest, int source_type, int dest_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(const 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, dest_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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const void *list, int pos) {
-+ signed char value;
-+
-+ binn_list_get(list, pos, BINN_INT8, &value, NULL);
-+
-+ return value;
-+}
-+
-+short APIENTRY binn_list_int16(const void *list, int pos) {
-+ short value;
-+
-+ binn_list_get(list, pos, BINN_INT16, &value, NULL);
-+
-+ return value;
-+}
-+
-+int APIENTRY binn_list_int32(const void *list, int pos) {
-+ int value;
-+
-+ binn_list_get(list, pos, BINN_INT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+int64 APIENTRY binn_list_int64(const void *list, int pos) {
-+ int64 value;
-+
-+ binn_list_get(list, pos, BINN_INT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned char APIENTRY binn_list_uint8(const void *list, int pos) {
-+ unsigned char value;
-+
-+ binn_list_get(list, pos, BINN_UINT8, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned short APIENTRY binn_list_uint16(const void *list, int pos) {
-+ unsigned short value;
-+
-+ binn_list_get(list, pos, BINN_UINT16, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned int APIENTRY binn_list_uint32(const void *list, int pos) {
-+ unsigned int value;
-+
-+ binn_list_get(list, pos, BINN_UINT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+uint64 APIENTRY binn_list_uint64(const void *list, int pos) {
-+ uint64 value;
-+
-+ binn_list_get(list, pos, BINN_UINT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+float APIENTRY binn_list_float(const void *list, int pos) {
-+ float value;
-+
-+ binn_list_get(list, pos, BINN_FLOAT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+double APIENTRY binn_list_double(const void *list, int pos) {
-+ double value;
-+
-+ binn_list_get(list, pos, BINN_FLOAT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_list_bool(const void *list, int pos) {
-+ BOOL value = TRUE;
-+
-+ binn_list_get(list, pos, BINN_BOOL, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_list_null(const void *list, int pos) {
-+
-+ return binn_list_get(list, pos, BINN_NULL, NULL, NULL);
-+
-+}
-+
-+char * APIENTRY binn_list_str(const void *list, int pos) {
-+ char *value;
-+
-+ binn_list_get(list, pos, BINN_STRING, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_list_blob(const void *list, int pos, int *psize) {
-+ void *value;
-+
-+ binn_list_get(list, pos, BINN_BLOB, &value, psize);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_list_list(const void *list, int pos) {
-+ void *value;
-+
-+ binn_list_get(list, pos, BINN_LIST, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_list_map(const void *list, int pos) {
-+ void *value;
-+
-+ binn_list_get(list, pos, BINN_MAP, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_list_object(const void *list, int pos) {
-+ void *value;
-+
-+ binn_list_get(list, pos, BINN_OBJECT, &value, NULL);
-+
-+ return value;
-+}
-+
-+/***************************************************************************/
-+
-+signed char APIENTRY binn_map_int8(const void *map, int id) {
-+ signed char value;
-+
-+ binn_map_get(map, id, BINN_INT8, &value, NULL);
-+
-+ return value;
-+}
-+
-+short APIENTRY binn_map_int16(const void *map, int id) {
-+ short value;
-+
-+ binn_map_get(map, id, BINN_INT16, &value, NULL);
-+
-+ return value;
-+}
-+
-+int APIENTRY binn_map_int32(const void *map, int id) {
-+ int value;
-+
-+ binn_map_get(map, id, BINN_INT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+int64 APIENTRY binn_map_int64(const void *map, int id) {
-+ int64 value;
-+
-+ binn_map_get(map, id, BINN_INT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned char APIENTRY binn_map_uint8(const void *map, int id) {
-+ unsigned char value;
-+
-+ binn_map_get(map, id, BINN_UINT8, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned short APIENTRY binn_map_uint16(const void *map, int id) {
-+ unsigned short value;
-+
-+ binn_map_get(map, id, BINN_UINT16, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned int APIENTRY binn_map_uint32(const void *map, int id) {
-+ unsigned int value;
-+
-+ binn_map_get(map, id, BINN_UINT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+uint64 APIENTRY binn_map_uint64(const void *map, int id) {
-+ uint64 value;
-+
-+ binn_map_get(map, id, BINN_UINT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+float APIENTRY binn_map_float(const void *map, int id) {
-+ float value;
-+
-+ binn_map_get(map, id, BINN_FLOAT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+double APIENTRY binn_map_double(const void *map, int id) {
-+ double value;
-+
-+ binn_map_get(map, id, BINN_FLOAT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_map_bool(const void *map, int id) {
-+ BOOL value = TRUE;
-+
-+ binn_map_get(map, id, BINN_BOOL, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_map_null(const void *map, int id) {
-+
-+ return binn_map_get(map, id, BINN_NULL, NULL, NULL);
-+
-+}
-+
-+char * APIENTRY binn_map_str(const void *map, int id) {
-+ char *value;
-+
-+ binn_map_get(map, id, BINN_STRING, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_map_blob(const void *map, int id, int *psize) {
-+ void *value;
-+
-+ binn_map_get(map, id, BINN_BLOB, &value, psize);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_map_list(const void *map, int id) {
-+ void *value;
-+
-+ binn_map_get(map, id, BINN_LIST, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_map_map(const void *map, int id) {
-+ void *value;
-+
-+ binn_map_get(map, id, BINN_MAP, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_map_object(const void *map, int id) {
-+ void *value;
-+
-+ binn_map_get(map, id, BINN_OBJECT, &value, NULL);
-+
-+ return value;
-+}
-+
-+/***************************************************************************/
-+
-+signed char APIENTRY binn_object_int8(const void *obj, const char *key) {
-+ signed char value;
-+
-+ binn_object_get(obj, key, BINN_INT8, &value, NULL);
-+
-+ return value;
-+}
-+
-+short APIENTRY binn_object_int16(const void *obj, const char *key) {
-+ short value;
-+
-+ binn_object_get(obj, key, BINN_INT16, &value, NULL);
-+
-+ return value;
-+}
-+
-+int APIENTRY binn_object_int32(const void *obj, const char *key) {
-+ int value;
-+
-+ binn_object_get(obj, key, BINN_INT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+int64 APIENTRY binn_object_int64(const void *obj, const char *key) {
-+ int64 value;
-+
-+ binn_object_get(obj, key, BINN_INT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+unsigned char APIENTRY binn_object_uint8(const 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(const 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(const void *obj, const char *key) {
-+ unsigned int value;
-+
-+ binn_object_get(obj, key, BINN_UINT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+uint64 APIENTRY binn_object_uint64(const void *obj, const char *key) {
-+ uint64 value;
-+
-+ binn_object_get(obj, key, BINN_UINT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+float APIENTRY binn_object_float(const void *obj, const char *key) {
-+ float value;
-+
-+ binn_object_get(obj, key, BINN_FLOAT32, &value, NULL);
-+
-+ return value;
-+}
-+
-+double APIENTRY binn_object_double(const void *obj, const char *key) {
-+ double value;
-+
-+ binn_object_get(obj, key, BINN_FLOAT64, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_object_bool(const void *obj, const char *key) {
-+ BOOL value = TRUE;
-+
-+ binn_object_get(obj, key, BINN_BOOL, &value, NULL);
-+
-+ return value;
-+}
-+
-+BOOL APIENTRY binn_object_null(const void *obj, const char *key) {
-+
-+ return binn_object_get(obj, key, BINN_NULL, NULL, NULL);
-+
-+}
-+
-+char * APIENTRY binn_object_str(const void *obj, const char *key) {
-+ char *value;
-+
-+ binn_object_get(obj, key, BINN_STRING, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_object_blob(const 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(const void *obj, const char *key) {
-+ void *value;
-+
-+ binn_object_get(obj, key, BINN_LIST, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_object_map(const void *obj, const char *key) {
-+ void *value;
-+
-+ binn_object_get(obj, key, BINN_MAP, &value, NULL);
-+
-+ return value;
-+}
-+
-+void * APIENTRY binn_object_object(const 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;
-+
-+}
-+
-+/*************************************************************************************/
-+
-+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 < (float)INT32_MIN || value->vfloat > (float)INT32_MAX) return FALSE;
-+ *pint = roundval(value->vfloat);
-+ break;
-+ case BINN_DOUBLE:
-+ if (value->vdouble < (double)INT32_MIN || value->vdouble > (double)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 < (float)INT64_MIN || value->vfloat > (float)INT64_MAX) return FALSE;
-+ *pint = roundval(value->vfloat);
-+ break;
-+ case BINN_DOUBLE:
-+ if (value->vdouble < (double)INT64_MIN || value->vdouble > (double)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-10.3p1.orig/binn.h openssh-10.3p1/binn.h
---- openssh-10.3p1.orig/binn.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/binn.h 2026-07-02 00:27:29.718944088 +0200
-@@ -0,0 +1,945 @@
-+
-+// 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
-+
-+#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(const 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(const void *ptr);
-+int APIENTRY binn_size(const void *ptr);
-+int APIENTRY binn_type(const void *ptr);
-+int APIENTRY binn_count(const void *ptr);
-+
-+BOOL APIENTRY binn_is_valid(const 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(const 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(const void *ptr);
-+
-+
-+// Loading a binn buffer into a binn value - this is optional
-+
-+binn * APIENTRY binn_open(const void *data); // allocated - unsecure
-+binn * APIENTRY binn_open_ex(const void *data, int size); // allocated - secure
-+BOOL APIENTRY binn_load(const void *data, binn *item); // on stack - unsecure
-+BOOL APIENTRY binn_load_ex(const void *data, int size, binn *value); // secure
-+
-+
-+// easiest interface to use, but don't check if the value is there
-+
-+signed char APIENTRY binn_list_int8(const void *list, int pos);
-+short APIENTRY binn_list_int16(const void *list, int pos);
-+int APIENTRY binn_list_int32(const void *list, int pos);
-+int64 APIENTRY binn_list_int64(const void *list, int pos);
-+unsigned char APIENTRY binn_list_uint8(const void *list, int pos);
-+unsigned short APIENTRY binn_list_uint16(const void *list, int pos);
-+unsigned int APIENTRY binn_list_uint32(const void *list, int pos);
-+uint64 APIENTRY binn_list_uint64(const void *list, int pos);
-+float APIENTRY binn_list_float(const void *list, int pos);
-+double APIENTRY binn_list_double(const void *list, int pos);
-+BOOL APIENTRY binn_list_bool(const void *list, int pos);
-+BOOL APIENTRY binn_list_null(const void *list, int pos);
-+char * APIENTRY binn_list_str(const void *list, int pos);
-+void * APIENTRY binn_list_blob(const void *list, int pos, int *psize);
-+void * APIENTRY binn_list_list(const void *list, int pos);
-+void * APIENTRY binn_list_map(const void *list, int pos);
-+void * APIENTRY binn_list_object(const void *list, int pos);
-+
-+signed char APIENTRY binn_map_int8(const void *map, int id);
-+short APIENTRY binn_map_int16(const void *map, int id);
-+int APIENTRY binn_map_int32(const void *map, int id);
-+int64 APIENTRY binn_map_int64(const void *map, int id);
-+unsigned char APIENTRY binn_map_uint8(const void *map, int id);
-+unsigned short APIENTRY binn_map_uint16(const void *map, int id);
-+unsigned int APIENTRY binn_map_uint32(const void *map, int id);
-+uint64 APIENTRY binn_map_uint64(const void *map, int id);
-+float APIENTRY binn_map_float(const void *map, int id);
-+double APIENTRY binn_map_double(const void *map, int id);
-+BOOL APIENTRY binn_map_bool(const void *map, int id);
-+BOOL APIENTRY binn_map_null(const void *map, int id);
-+char * APIENTRY binn_map_str(const void *map, int id);
-+void * APIENTRY binn_map_blob(const void *map, int id, int *psize);
-+void * APIENTRY binn_map_list(const void *map, int id);
-+void * APIENTRY binn_map_map(const void *map, int id);
-+void * APIENTRY binn_map_object(const void *map, int id);
-+
-+signed char APIENTRY binn_object_int8(const void *obj, const char *key);
-+short APIENTRY binn_object_int16(const void *obj, const char *key);
-+int APIENTRY binn_object_int32(const void *obj, const char *key);
-+int64 APIENTRY binn_object_int64(const void *obj, const char *key);
-+unsigned char APIENTRY binn_object_uint8(const void *obj, const char *key);
-+unsigned short APIENTRY binn_object_uint16(const void *obj, const char *key);
-+unsigned int APIENTRY binn_object_uint32(const void *obj, const char *key);
-+uint64 APIENTRY binn_object_uint64(const void *obj, const char *key);
-+float APIENTRY binn_object_float(const void *obj, const char *key);
-+double APIENTRY binn_object_double(const void *obj, const char *key);
-+BOOL APIENTRY binn_object_bool(const void *obj, const char *key);
-+BOOL APIENTRY binn_object_null(const void *obj, const char *key);
-+char * APIENTRY binn_object_str(const void *obj, const char *key);
-+void * APIENTRY binn_object_blob(const void *obj, const char *key, int *psize);
-+void * APIENTRY binn_object_list(const void *obj, const char *key);
-+void * APIENTRY binn_object_map(const void *obj, const char *key);
-+void * APIENTRY binn_object_object(const 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(const void *list, int pos);
-+binn * APIENTRY binn_map_value(const void *map, int id);
-+binn * APIENTRY binn_object_value(const void *obj, const char *key);
-+
-+// read the value to a binn structure on the stack
-+BOOL APIENTRY binn_list_get_value(const void *list, int pos, binn *value);
-+BOOL APIENTRY binn_map_get_value(const void *map, int id, binn *value);
-+BOOL APIENTRY binn_object_get_value(const void *obj, const char *key, binn *value);
-+
-+// single interface - these functions check the data type
-+BOOL APIENTRY binn_list_get(const void *list, int pos, int type, void *pvalue, int *psize);
-+BOOL APIENTRY binn_map_get(const void *map, int id, int type, void *pvalue, int *psize);
-+BOOL APIENTRY binn_object_get(const 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(const void *list, int pos, int *ptype, int *psize);
-+void * APIENTRY binn_map_read(const void *map, int id, int *ptype, int *psize);
-+void * APIENTRY binn_object_read(const 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(const void *map, int pos, int *pid, binn *value);
-+BOOL APIENTRY binn_object_get_pair(const void *obj, int pos, char *pkey, binn *value); // the key must be declared as: char key[256];
-+
-+// allocated
-+binn * APIENTRY binn_map_pair(const void *map, int pos, int *pid);
-+binn * APIENTRY binn_object_pair(const void *obj, int pos, char *pkey); // the key must be declared as: char key[256];
-+
-+// 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(const void *ptr, int pos, int *pid, int *ptype, int *psize);
-+void * APIENTRY binn_object_read_pair(const 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, const 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(const void *list, int pos, signed char *pvalue) {
-+ return binn_list_get(list, pos, BINN_INT8, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_int16(const void *list, int pos, short *pvalue) {
-+ return binn_list_get(list, pos, BINN_INT16, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_int32(const void *list, int pos, int *pvalue) {
-+ return binn_list_get(list, pos, BINN_INT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_int64(const void *list, int pos, int64 *pvalue) {
-+ return binn_list_get(list, pos, BINN_INT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_uint8(const void *list, int pos, unsigned char *pvalue) {
-+ return binn_list_get(list, pos, BINN_UINT8, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_uint16(const void *list, int pos, unsigned short *pvalue) {
-+ return binn_list_get(list, pos, BINN_UINT16, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_uint32(const void *list, int pos, unsigned int *pvalue) {
-+ return binn_list_get(list, pos, BINN_UINT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_uint64(const void *list, int pos, uint64 *pvalue) {
-+ return binn_list_get(list, pos, BINN_UINT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_float(const void *list, int pos, float *pvalue) {
-+ return binn_list_get(list, pos, BINN_FLOAT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_double(const void *list, int pos, double *pvalue) {
-+ return binn_list_get(list, pos, BINN_FLOAT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_bool(const void *list, int pos, BOOL *pvalue) {
-+ return binn_list_get(list, pos, BINN_BOOL, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_str(const void *list, int pos, char **pvalue) {
-+ return binn_list_get(list, pos, BINN_STRING, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_blob(const 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(const void *list, int pos, void **pvalue) {
-+ return binn_list_get(list, pos, BINN_LIST, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_map(const void *list, int pos, void **pvalue) {
-+ return binn_list_get(list, pos, BINN_MAP, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_list_get_object(const void *list, int pos, void **pvalue) {
-+ return binn_list_get(list, pos, BINN_OBJECT, pvalue, NULL);
-+}
-+
-+/***************************************************************************/
-+
-+ALWAYS_INLINE BOOL binn_map_get_int8(const void *map, int id, signed char *pvalue) {
-+ return binn_map_get(map, id, BINN_INT8, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_int16(const void *map, int id, short *pvalue) {
-+ return binn_map_get(map, id, BINN_INT16, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_int32(const void *map, int id, int *pvalue) {
-+ return binn_map_get(map, id, BINN_INT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_int64(const void *map, int id, int64 *pvalue) {
-+ return binn_map_get(map, id, BINN_INT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_uint8(const void *map, int id, unsigned char *pvalue) {
-+ return binn_map_get(map, id, BINN_UINT8, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_uint16(const void *map, int id, unsigned short *pvalue) {
-+ return binn_map_get(map, id, BINN_UINT16, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_uint32(const void *map, int id, unsigned int *pvalue) {
-+ return binn_map_get(map, id, BINN_UINT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_uint64(const void *map, int id, uint64 *pvalue) {
-+ return binn_map_get(map, id, BINN_UINT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_float(const void *map, int id, float *pvalue) {
-+ return binn_map_get(map, id, BINN_FLOAT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_double(const void *map, int id, double *pvalue) {
-+ return binn_map_get(map, id, BINN_FLOAT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_bool(const void *map, int id, BOOL *pvalue) {
-+ return binn_map_get(map, id, BINN_BOOL, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_str(const void *map, int id, char **pvalue) {
-+ return binn_map_get(map, id, BINN_STRING, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_blob(const 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(const void *map, int id, void **pvalue) {
-+ return binn_map_get(map, id, BINN_LIST, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_map(const void *map, int id, void **pvalue) {
-+ return binn_map_get(map, id, BINN_MAP, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_map_get_object(const 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(const 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(const void *obj, const char *key, short *pvalue) {
-+ return binn_object_get(obj, key, BINN_INT16, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_int32(const void *obj, const char *key, int *pvalue) {
-+ return binn_object_get(obj, key, BINN_INT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_int64(const void *obj, const char *key, int64 *pvalue) {
-+ return binn_object_get(obj, key, BINN_INT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_uint8(const 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(const 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(const 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(const void *obj, const char *key, uint64 *pvalue) {
-+ return binn_object_get(obj, key, BINN_UINT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_float(const void *obj, const char *key, float *pvalue) {
-+ return binn_object_get(obj, key, BINN_FLOAT32, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_double(const void *obj, const char *key, double *pvalue) {
-+ return binn_object_get(obj, key, BINN_FLOAT64, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_bool(const void *obj, const char *key, BOOL *pvalue) {
-+ return binn_object_get(obj, key, BINN_BOOL, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_str(const void *obj, const char *key, char **pvalue) {
-+ return binn_object_get(obj, key, BINN_STRING, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_blob(const 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(const void *obj, const char *key, void **pvalue) {
-+ return binn_object_get(obj, key, BINN_LIST, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_map(const void *obj, const char *key, void **pvalue) {
-+ return binn_object_get(obj, key, BINN_MAP, pvalue, NULL);
-+}
-+ALWAYS_INLINE BOOL binn_object_get_object(const 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-10.3p1.orig/channels.c openssh-10.3p1/channels.c
---- openssh-10.3p1.orig/channels.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/channels.c 2026-07-02 08:01:51.463010849 +0200
-@@ -90,6 +90,11 @@
- /* Maximum number of fake X11 displays to try. */
- #define MAX_DISPLAYS 1000
-
-+/* in version of OpenSSH later than 8.8 if we advertise a window
-+ * 16MB or larger is causes a pathological behaviour that reduces
-+ * throughput. This is not a great solution. */
-+#define NON_HPN_WINDOW_MAX (15 * 1024 * 1024)
-+
- /* Per-channel callback for pre/post IO actions */
- typedef void chan_fn(struct ssh *, Channel *c);
-
-@@ -222,6 +227,9 @@
- /* Setup helper */
- static void channel_handler_init(struct ssh_channels *sc);
-
-+/* default values to enable hpn and the initial buffer size */
-+static int hpn_disabled = 0;
-+
- /* -- channel core */
-
- void
-@@ -538,6 +546,16 @@
- (c->output = sshbuf_new()) == NULL ||
- (c->extended = sshbuf_new()) == NULL)
- fatal_f("sshbuf_new failed");
-+
-+ /* these buffers are important in terms of tracking channel
-+ * buffer usage so label and type them with descriptive names */
-+ sshbuf_relabel(c->input, "channel input");
-+ sshbuf_type(c->input, BUF_CHANNEL_INPUT);
-+ sshbuf_relabel(c->output, "channel output");
-+ sshbuf_type(c->output, BUF_CHANNEL_OUTPUT);
-+ sshbuf_relabel(c->extended, "channel extended");
-+ sshbuf_type(c->extended, BUF_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;
-@@ -549,6 +567,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 */
-@@ -1323,6 +1342,42 @@
- 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;
-+
-+ /* get the current size of the receive buffer */
-+ ret = getsockopt(ssh_packet_get_connection_in(ssh),
-+ SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz);
-+
-+ /* error on the socket - this should never happen */
-+ /* return OpenSSH's max window size */
-+ if (ret != 0) {
-+ debug_f("getsockopt SO_RCVBUF failed: %s", strerror(errno));
-+ return (2 * 1024 * 1024);
-+ }
-+
-+ /* return no more than SSHBUF_SIZE_MAX (currently 128MB) */
-+ if (tcpwinsz > SSHBUF_SIZE_MAX)
-+ tcpwinsz = SSHBUF_SIZE_MAX;
-+
-+ /* if the remote side is OpenSSH after version 8.8 we need to restrict
-+ * the size of the advertised window. Now this means that any HPN to non-HPN
-+ * connection will be window limited to 15MB of receive space. This is a
-+ * non-optimal solution.
-+ */
-+ if ((ssh->compat & SSH_RESTRICT_WINDOW) && (tcpwinsz > NON_HPN_WINDOW_MAX))
-+ tcpwinsz = NON_HPN_WINDOW_MAX;
-+ return (tcpwinsz);
-+}
-+
- static void
- channel_pre_open(struct ssh *ssh, Channel *c)
- {
-@@ -2178,8 +2233,6 @@
- static int
- channel_handle_rfd(struct ssh *ssh, Channel *c)
- {
-- char buf[CHAN_RBUF];
-- ssize_t len;
- int r, force;
- size_t nr = 0, have, avail, maxlen = CHANNEL_MAX_READ;
- int pty_zeroread = 0;
-@@ -2223,6 +2276,30 @@
- return 1;
- }
-
-+ /*
-+ * Datagram or filtered channel path: needs a temporary stack buffer
-+ * because the data must pass through input_filter() or be framed as
-+ * a datagram before entering c->input.
-+ *
-+ * buf[] (CHAN_RBUF = 32KB) is declared in this inner scope rather
-+ * than at function level to avoid a performance penalty from
-+ * -ftrivial-auto-var-init=zero, which causes the compiler to emit
-+ * a memset() for every local variable on function entry. With buf
-+ * at function scope the 32KB zeroing happens on every call, even
-+ * though the simple-channel fast path above (the common case for
-+ * bulk data) never touches buf. CPU profiling showed this dead
-+ * zeroing consuming ~10% of client CPU during high-throughput
-+ * transfers. Moving buf into this scope limits the zeroing to
-+ * calls that actually take the datagram/filter path.
-+ *
-+ * Security note: buf is still zeroed by the compiler before use in
-+ * this path. The simple-channel path never allocates buf at all,
-+ * so there is no uninitialized memory exposure.
-+ */
-+ {
-+ char buf[CHAN_RBUF];
-+ ssize_t len;
-+
- errno = 0;
- len = read(c->rfd, buf, sizeof(buf));
- /* fixup AIX zero-length read with errno set to look more like errors */
-@@ -2235,15 +2312,7 @@
- debug2("channel %d: read<=0 rfd %d len %zd: %s",
- c->self, c->rfd, len,
- len == 0 ? "closed" : strerror(errno));
-- rfail:
-- if (c->type != SSH_CHANNEL_OPEN) {
-- debug2("channel %d: not open", c->self);
-- chan_mark_dead(ssh, c);
-- return -1;
-- } else {
-- chan_read_failed(ssh, c);
-- }
-- return -1;
-+ goto rfail;
- }
- channel_set_used_time(ssh, c);
- if (c->input_filter != NULL) {
-@@ -2258,6 +2327,17 @@
- fatal_fr(r, "channel %i: put data", c->self);
-
- return 1;
-+ } /* end datagram/filter buf[] scope */
-+
-+ rfail:
-+ if (c->type != SSH_CHANNEL_OPEN) {
-+ debug2("channel %d: not open", c->self);
-+ chan_mark_dead(ssh, c);
-+ return -1;
-+ } else {
-+ chan_read_failed(ssh, c);
-+ }
-+ return -1;
- }
-
- static int
-@@ -2438,18 +2518,29 @@
- c->local_maxpacket*3) ||
- c->local_window < c->local_window_max/2) &&
- c->local_consumed > 0) {
-+ 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)) {
-+ /* aggressively grow the window */
-+ addition = tcpwinsz - c->local_window_max;
-+ c->local_window_max += addition;
-+ debug_f("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;
-+ c->local_window, c->local_consumed + addition);
-+ c->local_window += c->local_consumed + addition;
- c->local_consumed = 0;
- }
- return 1;
-@@ -3038,9 +3129,7 @@
- * in use.
- */
- if (CHANNEL_EFD_INPUT_ACTIVE(c))
-- debug2("channel %d: "
-- "ibuf_empty delayed efd %d/(%zu)",
-- c->self, c->efd, sshbuf_len(c->extended));
-+ {}
- else
- chan_ibuf_empty(ssh, c);
- }
-@@ -3752,7 +3841,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);
-@@ -3867,6 +3956,13 @@
- return addr;
- }
-
-+void
-+channel_set_hpn_disabled(int external_hpn_disabled)
-+{
-+ hpn_disabled = external_hpn_disabled;
-+ debug("HPN Disabled: %d", hpn_disabled);
-+}
-+
- static int
- channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type,
- struct Forward *fwd, int *allocated_listen_port,
-diff -Nur openssh-10.3p1.orig/channels.h openssh-10.3p1/channels.h
---- openssh-10.3p1.orig/channels.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/channels.h 2026-07-02 00:27:29.720575534 +0200
-@@ -180,6 +180,7 @@
- u_int local_window_max;
- u_int local_consumed;
- u_int local_maxpacket;
-+ int dynamic_window;
- int extended_usage;
- int agent_new; /* For agent listeners, use RFC XXX reqests */
- int single_connection;
-@@ -263,7 +264,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
-@@ -413,4 +414,6 @@
- void chan_write_failed(struct ssh *, Channel *);
- void chan_obuf_empty(struct ssh *, Channel *);
-
-+/* hpn handler */
-+void channel_set_hpn_disabled(int);
- #endif
-diff -Nur openssh-10.3p1.orig/cipher.c openssh-10.3p1/cipher.c
---- openssh-10.3p1.orig/cipher.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/cipher.c 2026-07-02 08:05:35.651792106 +0200
-@@ -47,23 +47,40 @@
- #include "misc.h"
- #include "sshbuf.h"
- #include "ssherr.h"
-+#include "digest.h"
-+#include "log.h"
-
- #include "openbsd-compat/openssl-compat.h"
-
-+/* for provider functions */
-+#ifdef WITH_OPENSSL3
-+#include <openssl/err.h>
-+#include <openssl/params.h>
-+#include <openssl/provider.h>
-+#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;
-+#ifdef WITH_OPENSSL
-+ struct chachapoly_ctx_mt *cp_ctx_mt;
-+#endif
- 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 },
-@@ -85,6 +102,10 @@
- #endif
- { "chacha20-poly1305@openssh.com",
- 8, 64, 0, 16, CFLAG_CHACHAPOLY, NULL },
-+#ifdef WITH_OPENSSL
-+ { "chacha20-poly1305-mt@hpnssh.org",
-+ 8, 64, 0, 16, CFLAG_CHACHAPOLY|CFLAG_MT, NULL },
-+#endif
- { "none", 8, 0, 0, 0, CFLAG_NONE, NULL },
-
- { NULL, 0, 0, 0, 0, 0, NULL }
-@@ -121,12 +142,63 @@
- #endif
- }
-
-+/* used to get the cipher name when we are testing to
-+ * see if we can move from a serial to parallel cipher
-+ * only called in cipher-switch.c
-+ */
-+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)
- {
-@@ -170,10 +242,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;
-@@ -195,7 +267,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;
- }
-@@ -216,7 +289,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)
-+ u_int seqnr, int do_encrypt, int enable_threads)
- {
- struct sshcipher_ctx *cc = NULL;
- int ret = SSH_ERR_INTERNAL_ERROR;
-@@ -231,6 +304,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))) {
-@@ -240,8 +314,19 @@
-
- cc->cipher = cipher;
- if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
-+#ifdef WITH_OPENSSL
-+ if ((cc->cipher->flags & CFLAG_MT) != 0) {
-+ cc->cp_ctx_mt = chachapoly_new_mt(seqnr, key, keylen);
-+ ret = cc->cp_ctx_mt != NULL ? 0 :
-+ SSH_ERR_INVALID_ARGUMENT;
-+ } else {
-+ cc->cp_ctx = chachapoly_new(key, keylen);
-+ ret = cc->cp_ctx != NULL ? 0 : SSH_ERR_INVALID_ARGUMENT;
-+ }
-+#else
- cc->cp_ctx = chachapoly_new(key, keylen);
- ret = cc->cp_ctx != NULL ? 0 : SSH_ERR_INVALID_ARGUMENT;
-+#endif
- goto out;
- }
- if ((cc->cipher->flags & CFLAG_NONE) != 0) {
-@@ -263,6 +348,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") && enable_threads) {
-+#ifdef WITH_OPENSSL3
-+ /* 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 /* WITH_OPENSSL3 */
-+ } /* if (strstr()) */
- if (EVP_CipherInit(cc->evp, type, NULL, (u_char *)iv,
- (do_encrypt == CIPHER_ENCRYPT)) == 0) {
- ret = SSH_ERR_LIBCRYPTO_ERROR;
-@@ -317,6 +449,12 @@
- const u_char *src, u_int len, u_int aadlen, u_int authlen)
- {
- if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
-+#ifdef WITH_OPENSSL
-+ if ((cc->cipher->flags & CFLAG_MT) != 0) {
-+ return chachapoly_crypt_mt(cc->cp_ctx_mt, seqnr, dest,
-+ src, len, aadlen, authlen, cc->encrypt);
-+ }
-+#endif
- return chachapoly_crypt(cc->cp_ctx, seqnr, dest, src,
- len, aadlen, authlen, cc->encrypt);
- }
-@@ -379,9 +517,16 @@
- cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
- const u_char *cp, u_int len)
- {
-- if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0)
-+ if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
-+#ifdef WITH_OPENSSL
-+ if ((cc->cipher->flags & CFLAG_MT) != 0) {
-+ return chachapoly_get_length_mt(cc->cp_ctx_mt, plenp,
-+ seqnr, cp, len);
-+ }
-+#endif
- return chachapoly_get_length(cc->cp_ctx, plenp, seqnr,
- cp, len);
-+ }
- if (len < 4)
- return SSH_ERR_MESSAGE_INCOMPLETE;
- *plenp = PEEK_U32(cp);
-@@ -394,13 +539,35 @@
- if (cc == NULL || cc->cipher == NULL)
- return;
- if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
-+#ifdef WITH_OPENSSL
-+ if ((cc->cipher->flags & CFLAG_MT) != 0) {
-+ chachapoly_free_mt(cc->cp_ctx_mt);
-+ cc->cp_ctx_mt = NULL;
-+ } else {
-+ chachapoly_free(cc->cp_ctx);
-+ cc->cp_ctx = NULL;
-+ }
-+#else
- chachapoly_free(cc->cp_ctx);
- cc->cp_ctx = NULL;
-+#endif
- } else if ((cc->cipher->flags & CFLAG_AESCTR) != 0)
- explicit_bzero(&cc->ac_ctx, sizeof(cc->ac_ctx));
- #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 !defined(WITH_OPENSSL3)
-+ if (cc->meth_ptr != NULL) {
-+ EVP_CIPHER_meth_free((void *)(EVP_CIPHER *)cc->meth_ptr);
-+ cc->meth_ptr = NULL;
-+ }
-+#endif
- #endif
- freezero(cc, sizeof(*cc));
- }
-diff -Nur openssh-10.3p1.orig/cipher-chachapoly.c openssh-10.3p1/cipher-chachapoly.c
---- openssh-10.3p1.orig/cipher-chachapoly.c 2026-07-02 00:27:06.092588783 +0200
-+++ openssh-10.3p1/cipher-chachapoly.c 2026-07-02 00:27:29.721144496 +0200
-@@ -88,7 +88,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;
-@@ -108,7 +108,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-10.3p1.orig/cipher-chachapoly-libcrypto.c openssh-10.3p1/cipher-chachapoly-libcrypto.c
---- openssh-10.3p1.orig/cipher-chachapoly-libcrypto.c 2026-07-02 00:27:06.092588783 +0200
-+++ openssh-10.3p1/cipher-chachapoly-libcrypto.c 2026-07-02 00:27:29.721335167 +0200
-@@ -32,8 +32,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 *
-@@ -54,6 +64,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);
-@@ -67,6 +86,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));
- }
-
-@@ -105,7 +127,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;
-@@ -131,7 +153,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-10.3p1.orig/cipher-chachapoly-libcrypto-mt.c openssh-10.3p1/cipher-chachapoly-libcrypto-mt.c
---- openssh-10.3p1.orig/cipher-chachapoly-libcrypto-mt.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-chachapoly-libcrypto-mt.c 2026-07-02 00:27:29.721541024 +0200
-@@ -0,0 +1,705 @@
-+/*
-+ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Mitchell Dorrell <mwd@psc.edu>
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+/* TODO: audit includes */
-+
-+#include "includes.h"
-+#ifdef WITH_OPENSSL
-+#include "openbsd-compat/openssl-compat.h"
-+#endif
-+
-+#if defined(HAVE_EVP_CHACHA20) && !defined(HAVE_BROKEN_CHACHA20)
-+
-+#include <sys/types.h>
-+#include <unistd.h> /* needed for getpid under C99 */
-+#include <stdarg.h> /* needed for log.h */
-+#include <string.h>
-+#include <stdio.h> /* needed for misc.h */
-+#include <pthread.h>
-+
-+#include <openssl/evp.h>
-+
-+#include "defines.h"
-+#include "log.h"
-+#include "sshbuf.h"
-+#include "ssherr.h"
-+
-+#include "xmalloc.h"
-+#include "cipher-chachapoly.h"
-+#include "cipher-chachapoly-libcrypto-mt.h"
-+
-+#ifndef likely
-+# define likely(x) __builtin_expect(!!(x), 1)
-+#endif
-+#ifndef unlikely
-+# define unlikely(x) __builtin_expect(!!(x), 0)
-+#endif
-+
-+/* Size of keystream to pregenerate, measured in bytes
-+ * we want to round up to the nearest chacha block and have
-+ * 128 bytes for overhead */
-+#define ROUND_UP(x,y) (((((x)-1)/(y))+1)*(y))
-+#define KEYSTREAMLEN (ROUND_UP((SSH_IOBUFSZ) + 128, (CHACHA_BLOCKLEN)))
-+
-+/* BEGIN TUNABLES */
-+
-+/* Number of worker threads to spawn. */
-+/* the goal is to ensure that main is never
-+ * waiting on the worker threads for keystream data */
-+#define NUMTHREADS 1
-+
-+/* 64 seems to be a pretty blance between memory and performance
-+ * 128 is another option with somewhat higher memory consumption */
-+#define NUMSTREAMS 64
-+
-+/* END TUNABLES */
-+
-+struct mt_keystream {
-+ u_char poly_key[POLY1305_KEYLEN]; /* POLY1305_KEYLEN == 32 */
-+ u_char headerStream[CHACHA_BLOCKLEN]; /* CHACHA_BLOCKLEN == 64 */
-+ u_char mainStream[KEYSTREAMLEN]; /* KEYSTREAMLEN == 32768 */
-+};
-+
-+struct threadData {
-+ EVP_CIPHER_CTX * main_evp;
-+ EVP_CIPHER_CTX * header_evp;
-+ u_char seqbuf[16];
-+};
-+
-+struct mt_keystream_batch {
-+ u_int batchID;
-+ struct threadData tds[NUMTHREADS];
-+ struct mt_keystream streams[NUMSTREAMS];
-+};
-+
-+struct chachapoly_ctx_mt {
-+ u_int seqnr;
-+ u_int batchID;
-+
-+ struct mt_keystream_batch batches[2];
-+
-+ pthread_t manager_tid[2];
-+ pthread_t self_tid;
-+
-+ pid_t mainpid;
-+ u_char zeros[KEYSTREAMLEN]; /* KEYSTREAMLEN == 32768 */
-+
-+ /* if OpenSSL has support for Poly1305 in the MAC EVPs
-+ * use that (OSSL >= 3.0) if not then it's OSSL 1.1 so
-+ * use the Poly1305 digest methods. Failing that use the
-+ * internal poly1305 methods */
-+#ifdef OPENSSL_HAVE_POLY_EVP
-+ EVP_MAC_CTX *poly_ctx;
-+#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ EVP_PKEY_CTX *poly_ctx;
-+ EVP_MD_CTX *md_ctx;
-+ EVP_PKEY *pkey;
-+ size_t ptaglen;
-+#else
-+ char *poly_ctx;
-+#endif
-+};
-+
-+struct manager_thread_args {
-+ struct chachapoly_ctx_mt * ctx_mt;
-+ u_int oldBatchID;
-+ int retval;
-+};
-+
-+struct worker_thread_args {
-+ u_int batchID;
-+ struct mt_keystream_batch * batch;
-+ int threadIndex;
-+ u_char * zeros;
-+ int retval;
-+};
-+
-+/* generate the keystream and header
-+ * we use nulls for the "data" (the zeros variable) in order to
-+ * get the raw keystream
-+ * Returns 0 on success and -1 on failure */
-+int
-+generate_keystream(struct mt_keystream * ks, u_int seqnr,
-+ struct threadData * td, u_char * zeros)
-+{
-+ /* generate poly1305 key */
-+ memset(td->seqbuf, 0, sizeof(td->seqbuf));
-+ POKE_U64(td->seqbuf + 8, seqnr);
-+ memset(ks->poly_key , 0, sizeof(ks->poly_key));
-+ if (!EVP_CipherInit(td->main_evp, NULL, NULL, td->seqbuf, 1) ||
-+ EVP_Cipher(td->main_evp, ks->poly_key, ks->poly_key,
-+ sizeof(ks->poly_key)) < 0)
-+ return -1;
-+
-+ /* generate header keystream for encrypting payload length */
-+ if (!EVP_CipherInit(td->header_evp, NULL, NULL, td->seqbuf, 1) ||
-+ EVP_Cipher(td->header_evp, ks->headerStream, zeros, CHACHA_BLOCKLEN)
-+ < 0 )
-+ return -1;
-+
-+ /* generate main keystream for encrypting payload */
-+ td->seqbuf[0] = 1;
-+ if (!EVP_CipherInit(td->main_evp, NULL, NULL, td->seqbuf, 1) ||
-+ EVP_Cipher(td->main_evp, ks->mainStream, zeros, KEYSTREAMLEN) < 0)
-+ return -1;
-+
-+ return 0;
-+}
-+
-+/* free the EVP contexts associated with the give thread */
-+void
-+free_threadData(struct threadData * td)
-+{
-+ if (td == NULL)
-+ return;
-+ if (td->main_evp) /* false if initialization didn't get this far */
-+ EVP_CIPHER_CTX_free(td->main_evp);
-+ if (td->header_evp) /* false if initialization didn't get this far */
-+ EVP_CIPHER_CTX_free(td->header_evp);
-+ explicit_bzero(td, sizeof(*td));
-+}
-+
-+/* initialize the EVPs used by the worker thread
-+ Returns 0 on success and -1 on failure */
-+int
-+initialize_threadData(struct threadData * td, const u_char *key)
-+{
-+ memset(td,0,sizeof(*td));
-+ if ((td->main_evp = EVP_CIPHER_CTX_new()) == NULL ||
-+ (td->header_evp = EVP_CIPHER_CTX_new()) == NULL)
-+ goto fail;
-+ if (!EVP_CipherInit(td->main_evp, EVP_chacha20(), key, NULL, 1))
-+ goto fail;
-+ if (!EVP_CipherInit(td->header_evp, EVP_chacha20(), key + 32, NULL, 1))
-+ goto fail;
-+ if (EVP_CIPHER_CTX_iv_length(td->header_evp) != 16)
-+ goto fail;
-+ return 0;
-+ fail:
-+ free_threadData(td);
-+ return -1;
-+}
-+
-+struct worker_thread_args *
-+worker_thread(struct worker_thread_args * args)
-+{
-+ /* check first */
-+ if (args == NULL)
-+ return NULL;
-+ if (args->batch == NULL || args->zeros == NULL) {
-+ args->retval = 1;
-+ return args;
-+ }
-+
-+ int threadIndex = args->threadIndex;
-+ struct threadData * td = &(args->batch->tds[threadIndex]);
-+ u_int refseqnr = args->batchID * NUMSTREAMS;
-+
-+ for (int i = threadIndex; i < NUMSTREAMS; i += NUMTHREADS) {
-+ if (generate_keystream(&(args->batch->streams[i]), refseqnr + i,
-+ td, args->zeros) == -1) {
-+ args->retval = 1;
-+ return args;
-+ }
-+ }
-+
-+ args->retval = 0;
-+ return args;
-+}
-+
-+int
-+join_manager_thread(pthread_t manager_tid)
-+{
-+ struct manager_thread_args * args;
-+ if (pthread_join(manager_tid, (void **) &args) == 0) {
-+ if (args == NULL) {
-+ debug_f("Manager thread returned NULL!");
-+ return 1;
-+ } else if (args == PTHREAD_CANCELED) {
-+ debug_f("Manager thread canceled!");
-+ return 1;
-+ } else if (args->retval != 0) {
-+ debug_f("Manager thread error (%d)", args->retval);
-+ free(args);
-+ return 1;
-+ } else {
-+ free(args);
-+ return 0;
-+ }
-+ } else {
-+ debug_f("pthread_join error!");
-+ return 1;
-+ }
-+}
-+
-+void
-+chachapoly_free_mt(struct chachapoly_ctx_mt * ctx_mt)
-+{
-+ if (ctx_mt == NULL)
-+ return;
-+
-+#ifdef OPENSSL_HAVE_POLY_EVP
-+ if (ctx_mt->poly_ctx != NULL) {
-+ EVP_MAC_CTX_free(ctx_mt->poly_ctx);
-+ ctx_mt->poly_ctx = NULL;
-+ }
-+#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ if (ctx_mt->md_ctx != NULL) {
-+ EVP_MD_CTX_free(ctx_mt->md_ctx);
-+ ctx_mt->md_ctx = NULL;
-+ }
-+ if (ctx_mt->pkey != NULL) {
-+ EVP_PKEY_free(ctx_mt->pkey);
-+ ctx_mt->pkey = NULL;
-+ }
-+#endif
-+
-+ /*
-+ * Only cleanup the manager threads if we are the PID that initialized
-+ * them! If we're a fork, the threads don't really exist.
-+ */
-+
-+ if (getpid() == ctx_mt->mainpid) {
-+ if (ctx_mt->manager_tid[0] != ctx_mt->self_tid) {
-+ join_manager_thread(ctx_mt->manager_tid[0]);
-+ ctx_mt->manager_tid[0] = ctx_mt->self_tid;
-+ }
-+ if (ctx_mt->manager_tid[1] != ctx_mt->self_tid) {
-+ join_manager_thread(ctx_mt->manager_tid[1]);
-+ ctx_mt->manager_tid[1] = ctx_mt->self_tid;
-+ }
-+ }
-+
-+ /* Cleanup thread data structures. */
-+ for (int i=0; i<2; i++)
-+ for (int j=0; j<NUMTHREADS; j++)
-+ free_threadData(&(ctx_mt->batches[i].tds[j]));
-+
-+ /* Zero and free the whole multithreaded cipher context. */
-+ freezero(ctx_mt, sizeof(*ctx_mt));
-+
-+ return;
-+}
-+
-+struct chachapoly_ctx_mt *
-+chachapoly_new_mt(u_int startseqnr, const u_char * key, u_int keylen)
-+{
-+ struct chachapoly_ctx_mt * ctx_mt = xmalloc(sizeof(*ctx_mt));
-+ memset(ctx_mt, 0, sizeof(*ctx_mt));
-+ /* Initialize the sequence number. When rekeying, this won't be zero. */
-+ ctx_mt->seqnr = startseqnr;
-+ ctx_mt->batchID = startseqnr / NUMSTREAMS;
-+ struct threadData mainData;
-+ int tDataI;
-+ int genKSfailed = 0;
-+
-+#ifdef OPENSSL_HAVE_POLY_EVP
-+ EVP_MAC *mac = NULL;
-+ if ((mac = EVP_MAC_fetch(NULL, "POLY1305", NULL)) == NULL)
-+ goto fail;
-+ if ((ctx_mt->poly_ctx = EVP_MAC_CTX_new(mac)) == NULL)
-+ goto fail;
-+#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ if ((ctx_mt->md_ctx = EVP_MD_CTX_new()) == NULL)
-+ goto fail;
-+ if ((ctx_mt->pkey = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL,
-+ ctx_mt->zeros, POLY1305_KEYLEN)) == NULL)
-+ goto fail;
-+ if (EVP_DigestSignInit(ctx_mt->md_ctx, &ctx_mt->poly_ctx, NULL, NULL,
-+ ctx_mt->pkey) == 0)
-+ goto fail;
-+#else
-+ ctx_mt->poly_ctx = NULL;
-+#endif
-+
-+ ctx_mt->batches[ctx_mt->batchID % 2].batchID = ctx_mt->batchID;
-+ ctx_mt->batches[(ctx_mt->batchID + 1) % 2].batchID =
-+ ctx_mt->batchID + 1;
-+
-+ /* initialize batches[0] tds */
-+ for (tDataI = 0; tDataI < NUMTHREADS; tDataI++) {
-+ if (initialize_threadData(&(ctx_mt->batches[0].tds[tDataI]),
-+ key) != 0)
-+ break;
-+ }
-+ if (tDataI < NUMTHREADS) {
-+ /* Backtrack starting with 'tDataI - 1' */
-+ for (tDataI--; tDataI >= 0; tDataI--)
-+ free_threadData(&(ctx_mt->batches[0].tds[tDataI]));
-+ goto fail;
-+ }
-+ /* initialize batches[1] tds */
-+ for (tDataI = 0; tDataI < NUMTHREADS; tDataI++) {
-+ if (initialize_threadData(&(ctx_mt->batches[1].tds[tDataI]),
-+ key) != 0)
-+ break;
-+ }
-+ if (tDataI < NUMTHREADS) {
-+ /* Backtrack starting with 'tDataI - 1' */
-+ for (tDataI--; tDataI >= 0; tDataI--)
-+ free_threadData(&(ctx_mt->batches[1].tds[tDataI]));
-+ /* Free the batches[0] tds too */
-+ for (tDataI = NUMTHREADS; tDataI >= 0; tDataI--)
-+ free_threadData(&(ctx_mt->batches[0].tds[tDataI]));
-+ goto fail;
-+ }
-+
-+ if (initialize_threadData(&mainData, key) != 0) {
-+ chachapoly_free_mt(ctx_mt);
-+ explicit_bzero(&startseqnr, sizeof(startseqnr));
-+ return NULL;
-+ }
-+
-+ for (int i=0; i<2; i++) {
-+ u_int refseqnr = ctx_mt->batches[i].batchID * NUMSTREAMS;
-+ for (int j = startseqnr > refseqnr ? startseqnr - refseqnr : 0;
-+ j<NUMSTREAMS; j++) {
-+ if (generate_keystream(&(ctx_mt->batches[i].streams[j]),
-+ refseqnr + j, &mainData, ctx_mt->zeros) == -1) {
-+ debug_f("generate_keystream failed in "
-+ "chacha20-poly1305@hpnssh.org");
-+ genKSfailed = 1;
-+ break; /* imperfect, but it helps */
-+ }
-+ }
-+ }
-+
-+ free_threadData(&mainData);
-+
-+ if (genKSfailed != 0) {
-+ chachapoly_free_mt(ctx_mt);
-+ explicit_bzero(&startseqnr, sizeof(startseqnr));
-+ return NULL;
-+ }
-+
-+ /* Store the PID so that in the future, we can tell if we're a fork */
-+ ctx_mt->mainpid = getpid();
-+ ctx_mt->self_tid = pthread_self();
-+ ctx_mt->manager_tid[0] = ctx_mt->self_tid;
-+ ctx_mt->manager_tid[1] = ctx_mt->self_tid;
-+ /* was reporting the TID using gettid() but it's not portable */
-+ debug2_f("<main thread: pid=%u, ptid=0x%lx>", getpid(), pthread_self());
-+
-+ /* Success! */
-+ explicit_bzero(&startseqnr, sizeof(startseqnr));
-+ return ctx_mt;
-+
-+ fail:
-+#ifdef OPENSSL_HAVE_POLY_EVP
-+ if (ctx_mt->poly_ctx != NULL) {
-+ EVP_MAC_CTX_free(ctx_mt->poly_ctx);
-+ ctx_mt->poly_ctx = NULL;
-+ }
-+#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ if (ctx_mt->md_ctx != NULL) {
-+ EVP_MD_CTX_free(ctx_mt->md_ctx);
-+ ctx_mt->md_ctx = NULL;
-+ }
-+ if (ctx_mt->pkey != NULL) {
-+ EVP_PKEY_free(ctx_mt->pkey);
-+ ctx_mt->pkey = NULL;
-+ }
-+#endif
-+ freezero(ctx_mt, sizeof(*ctx_mt));
-+ explicit_bzero(&startseqnr, sizeof(startseqnr));
-+ return NULL;
-+}
-+
-+/* Portable FastXOR using memcpy to avoid strict-aliasing and alignment UB.
-+ * Uses 128-bit chunks on compilers that support __uint128_t (GCC/Clang on
-+ * 64-bit platforms), falls back to uint64_t elsewhere. */
-+static inline void
-+fastXOR2(u_char *dest, const u_char *src, const u_char *keystream, u_int len)
-+{
-+#if defined(__SIZEOF_INT128__)
-+ typedef __uint128_t chunk;
-+#else
-+ typedef uint64_t chunk;
-+#endif
-+ size_t i = 0;
-+ while (i + sizeof(chunk) <= len) {
-+ chunk a, b, r;
-+ memcpy(&a, src + i, sizeof(a));
-+ memcpy(&b, keystream + i, sizeof(b));
-+ r = a ^ b;
-+ memcpy(dest + i, &r, sizeof(r));
-+ i += sizeof(chunk);
-+ }
-+ while (i < len) {
-+ dest[i] = src[i] ^ keystream[i];
-+ i++;
-+ }
-+}
-+
-+struct manager_thread_args *
-+manager_thread(struct manager_thread_args * margs) {
-+ /* make sure we have valid data before proceeding */
-+ if (margs == NULL)
-+ return NULL;
-+
-+ struct chachapoly_ctx_mt * ctx_mt = margs->ctx_mt;
-+ if (ctx_mt == NULL) {
-+ margs->retval = 1;
-+ return margs;
-+ }
-+
-+ u_int oldBatchID = margs->oldBatchID;
-+
-+ struct mt_keystream_batch * batch = &(ctx_mt->batches[oldBatchID % 2]);
-+ if (batch->batchID != oldBatchID) {
-+ debug_f("Post-crypt batch miss! Seeking %u, found %u. Failing.",
-+ oldBatchID, batch->batchID);
-+ margs->retval = 1;
-+ return margs;
-+ }
-+
-+ margs->retval = 0;
-+ u_int batchID = oldBatchID + 2;
-+
-+ pthread_t tid[NUMTHREADS];
-+ struct worker_thread_args * wargs = malloc(NUMTHREADS * sizeof(*wargs));
-+ int ti;
-+
-+ for (ti = 0; ti < NUMTHREADS; ti++) {
-+ wargs[ti].batchID = batchID;
-+ wargs[ti].batch = batch;
-+ wargs[ti].threadIndex = ti;
-+ wargs[ti].zeros = ctx_mt->zeros;
-+ if (pthread_create(&(tid[ti]), NULL, (void *) worker_thread,
-+ &(wargs[ti])) != 0) {
-+ margs->retval = 1;
-+ break;
-+ }
-+ }
-+ for (; ti < NUMTHREADS; ti++) /* for error condition */
-+ tid[ti] = pthread_self();
-+
-+ struct worker_thread_args * retwargs;
-+
-+ for (ti = 0; ti < NUMTHREADS; ti++) {
-+ if (tid[ti] == pthread_self()) {
-+ margs->retval = 1; /* redundant, but harmless */
-+ continue;
-+ }
-+ if (pthread_join(tid[ti], (void **) &retwargs) == 0) {
-+ if (retwargs == NULL) {
-+ debug_f("Worker thread returned NULL!");
-+ margs->retval = 1;
-+ } else if (retwargs == PTHREAD_CANCELED) {
-+ debug_f("Worker thread canceled!");
-+ margs->retval = 1;
-+ } else {
-+ if (retwargs->retval != 0) {
-+ debug_f("Worker thread error (%d)",
-+ retwargs->retval);
-+ margs->retval = 1;
-+ }
-+ if (retwargs != &(wargs[ti])) {
-+ debug_f("Worker thread didn't return "
-+ "expected structure!");
-+ margs->retval = 1;
-+ }
-+ }
-+ } else {
-+ debug_f("pthread_join error!");
-+ margs->retval = 1;
-+ }
-+ }
-+ free(wargs);
-+
-+ if (margs->retval == 0) {
-+ batch->batchID = batchID;
-+ }
-+
-+ return margs;
-+}
-+
-+int
-+chachapoly_crypt_mt(struct chachapoly_ctx_mt *ctx_mt, u_int seqnr, u_char *dest,
-+ const u_char *src, u_int len, u_int aadlen, u_int authlen, int do_encrypt)
-+{
-+#ifdef SAFETY
-+ if (ctx_mt->mainpid != getpid()) { /* we're a fork */
-+ /*
-+ * TODO: this is EXTREMELY RARE, may never happen at all (only
-+ * if the fork calls crypt), so we should tell the compiler.
-+ */
-+ /* The worker threads don't exist, we could spawn them? */
-+ debug_f("Fork called crypt without workers!");
-+ chachapoly_free_mt(ctx_mt);
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+#endif
-+
-+ pthread_t * manager_tid = &(ctx_mt->manager_tid[ctx_mt->batchID % 2]);
-+ if (unlikely(*manager_tid != ctx_mt->self_tid)) {
-+ int ret = join_manager_thread(*manager_tid);
-+ *manager_tid = ctx_mt->self_tid;
-+ if (ret != 0)
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+
-+ struct mt_keystream_batch * batch =
-+ &(ctx_mt->batches[ctx_mt->batchID % 2]);
-+
-+ struct mt_keystream * ks = &(batch->streams[seqnr % NUMSTREAMS]);
-+
-+ int r = SSH_ERR_INTERNAL_ERROR;
-+
-+#ifdef SAFETY
-+ if (batch->batchID == ctx_mt->batchID) { /* Safety check */
-+#endif
-+ /* check tag before anything else */
-+ if (!do_encrypt) {
-+ const u_char *tag = src + aadlen + len;
-+ u_char expected_tag[POLY1305_TAGLEN];
-+#if !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ if ((EVP_PKEY_CTX_ctrl(ctx_mt->poly_ctx, -1,
-+ EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_MAC_KEY,
-+ POLY1305_KEYLEN, ks->poly_key) <= 0) ||
-+ (EVP_DigestSignUpdate(ctx_mt->md_ctx, src, aadlen + len) == 0)) {
-+ debug_f("SSL error while decrypting poly1305 tag");
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+ ctx_mt->ptaglen = POLY1305_TAGLEN;
-+ if (EVP_DigestSignFinal(ctx_mt->md_ctx, expected_tag,
-+ &ctx_mt->ptaglen) == 0) {
-+ debug_f("SSL error while finalizing decyrpted poly1305");
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+#else
-+ poly1305_auth(ctx_mt->poly_ctx, expected_tag, src,
-+ aadlen + len, ks->poly_key);
-+#endif
-+ if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN)
-+ != 0)
-+ r = SSH_ERR_MAC_INVALID;
-+ explicit_bzero(expected_tag, sizeof(expected_tag));
-+ }
-+ if (r != SSH_ERR_MAC_INVALID) {
-+ /* Crypt additional data (i.e., packet length) */
-+ /* TODO: is aadlen always four bytes? */
-+ /* TODO: do we always have an aadlen? */
-+ if (aadlen)
-+ for (u_int i=0; i<aadlen; i++)
-+ dest[i] = ks->headerStream[i] ^ src[i];
-+ /* Crypt payload */
-+ fastXOR2(dest+aadlen,src+aadlen,ks->mainStream,len);
-+ /* calculate and append tag */
-+#if !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
-+ if (do_encrypt) {
-+ if ((EVP_PKEY_CTX_ctrl(ctx_mt->poly_ctx, -1,
-+ EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_MAC_KEY,
-+ POLY1305_KEYLEN, ks->poly_key) <=0) ||
-+ (EVP_DigestSignUpdate(ctx_mt->md_ctx, dest, aadlen + len) == 0)) {
-+ debug_f ("SSL error while encrypting poly1305 tag");
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+ ctx_mt->ptaglen = POLY1305_TAGLEN;
-+ if (EVP_DigestSignFinal(ctx_mt->md_ctx, dest+aadlen+len,
-+ &ctx_mt->ptaglen) == 0) {
-+ debug_f("SSL error while finalizing decyrpted poly1305");
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+ }
-+#else
-+ if (do_encrypt)
-+ poly1305_auth(ctx_mt->poly_ctx, dest+aadlen+len,
-+ dest, aadlen+len, ks->poly_key);
-+#endif
-+ r=0; /* Success! */
-+ }
-+ if (r) /* Anything nonzero is an error. */
-+ return r;
-+
-+ ctx_mt->seqnr = seqnr + 1;
-+
-+ if (unlikely(ctx_mt->seqnr / NUMSTREAMS > ctx_mt->batchID)) {
-+ struct manager_thread_args * args =
-+ malloc(sizeof(*args));
-+ if (args == NULL) {
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+ args->ctx_mt = ctx_mt;
-+ args->oldBatchID = ctx_mt->batchID;
-+ if (pthread_create(&(ctx_mt->manager_tid[ctx_mt->batchID
-+ % 2]), NULL, (void *) manager_thread, args) != 0) {
-+ free(args);
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+ ctx_mt->batchID = ctx_mt->seqnr / NUMSTREAMS;
-+ }
-+
-+ /* TODO: Nothing we need to sanitize here? */
-+
-+ return 0;
-+#ifdef SAFETY
-+ } else { /* Bad, it's the wrong batch. */
-+ debug_f( "Pre-crypt batch miss! Seeking %u, found %u. Failing.",
-+ ctx_mt->batchID, batch->batchID);
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+#endif
-+}
-+
-+int
-+chachapoly_get_length_mt(struct chachapoly_ctx_mt *ctx_mt, u_int *plenp,
-+ u_int seqnr, const u_char *cp, u_int len)
-+{
-+ /* TODO: add compiler hints */
-+#ifdef SAFETY
-+ if (ctx_mt->mainpid != getpid()) { /* Use serial mode if we're a fork */
-+ debug_f("We're a fork. Failing.");
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+#endif
-+
-+ if (len < 4)
-+ return SSH_ERR_MESSAGE_INCOMPLETE;
-+
-+ pthread_t * manager_tid = &(ctx_mt->manager_tid[ctx_mt->batchID % 2]);
-+ if (unlikely(*manager_tid != ctx_mt->self_tid)) {
-+ int ret = join_manager_thread(*manager_tid);
-+ *manager_tid = ctx_mt->self_tid;
-+ if (ret != 0)
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+
-+ u_char buf[4];
-+#ifdef SAFETY
-+ u_int sought_batchID = seqnr / NUMSTREAMS;
-+#endif
-+ struct mt_keystream_batch * batch =
-+ &(ctx_mt->batches[ctx_mt->batchID % 2]);
-+ struct mt_keystream * ks = &(batch->streams[seqnr % NUMSTREAMS]);
-+#ifdef SAFETY
-+ if (batch->batchID == sought_batchID) {
-+#endif
-+ for (u_int i=0; i < sizeof(buf); i++)
-+ buf[i]=ks->headerStream[i] ^ cp[i];
-+ *plenp = PEEK_U32(buf);
-+ return 0;
-+#ifdef SAFETY
-+ } else {
-+ debug_f("Batch miss! Seeking %u, found %u. Failing.",
-+ sought_batchID, batch->batchID);
-+ return SSH_ERR_INTERNAL_ERROR;
-+ }
-+#endif
-+}
-+#endif /* defined(HAVE_EVP_CHACHA20) && !defined(HAVE_BROKEN_CHACHA20) */
-diff -Nur openssh-10.3p1.orig/cipher-chachapoly-libcrypto-mt.h openssh-10.3p1/cipher-chachapoly-libcrypto-mt.h
---- openssh-10.3p1.orig/cipher-chachapoly-libcrypto-mt.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-chachapoly-libcrypto-mt.h 2026-07-02 00:27:29.721627302 +0200
-@@ -0,0 +1,45 @@
-+/*
-+ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Mitchell Dorrell <mwd@psc.edu>
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+#ifndef CHACHA_POLY_LIBCRYPTO_MT_H
-+#define CHACHA_POLY_LIBCRYPTO_MT_H
-+
-+#include <sys/types.h>
-+#include "chacha.h"
-+#include "poly1305.h"
-+
-+#ifndef CHACHA_KEYLEN
-+#define CHACHA_KEYLEN 32 /* Only 256 bit keys used here */
-+#endif
-+
-+struct chachapoly_ctx_mt; /* defined in cipher-chachapoly-libcrypto-mt.c */
-+
-+struct chachapoly_ctx_mt *chachapoly_new_mt(u_int startseqnr, const u_char *key, u_int keylen)
-+ __attribute__((__bounded__(__buffer__, 2, 3)));
-+
-+void chachapoly_free_mt(struct chachapoly_ctx_mt *cpctx);
-+
-+int chachapoly_crypt_mt(struct chachapoly_ctx_mt *cpctx, u_int seqnr,
-+ u_char *dest, const u_char *src, u_int len, u_int aadlen,
-+ u_int authlen, int do_encrypt);
-+
-+int chachapoly_get_length_mt(struct chachapoly_ctx_mt *cpctx,
-+ u_int *plenp, u_int seqnr, const u_char *cp, u_int len)
-+ __attribute__((__bounded__(__buffer__, 4, 5)));
-+
-+#endif /* CHACHA_POLY_LIBCRYPTO_MT_H */
-diff -Nur openssh-10.3p1.orig/cipher-ctr-mt.c openssh-10.3p1/cipher-ctr-mt.c
---- openssh-10.3p1.orig/cipher-ctr-mt.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-ctr-mt.c 2026-07-02 00:27:29.721757407 +0200
-@@ -0,0 +1,681 @@
-+/*
-+ * OpenSSH Multi-threaded AES-CTR Cipher
-+ *
-+ * Author: Benjamin Bennett <ben@psc.edu>
-+ * Author: Mike Tasota <tasota@gmail.com>
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ * 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 <markus@openbsd.org>
-+ *
-+ * 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) && !defined(WITH_OPENSSL3)
-+#include <sys/types.h>
-+
-+#include <stdarg.h>
-+#include <string.h>
-+
-+#include <openssl/evp.h>
-+
-+#include "xmalloc.h"
-+#include "log.h"
-+#include <unistd.h>
-+#include "uthash.h"
-+
-+/* compatibility with old or broken OpenSSL versions */
-+#include "openbsd-compat/openssl-compat.h"
-+
-+#ifndef USE_BUILTIN_RIJNDAEL
-+#include <openssl/aes.h>
-+#endif
-+
-+#include <pthread.h>
-+
-+#ifdef __APPLE__
-+#include <sys/types.h>
-+#include <sys/sysctl.h>
-+#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 * volatile 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,
-+ 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 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
-+ /* with GCC 13 we have having consistent seg faults
-+ * in this section of code. Since this is a critical
-+ * code path we are removing this until we have a solution
-+ * in place -cjr 02/22/24
-+ * TODO: FIX THIS
-+ */
-+ /* if ((align & 0xf) == 0) { */
-+ /* destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; */
-+ /* } else */
-+#endif
-+ /* this is causing undefined behaviour in sanitizers
-+ * this is annoying because it's more efficient
-+ * but UB is not something I want to retain */
-+ /* 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;
-+}
-+
-+/* <friedl> */
-+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 Check */
-diff -Nur openssh-10.3p1.orig/cipher-ctr-mt-functions.c openssh-10.3p1/cipher-ctr-mt-functions.c
---- openssh-10.3p1.orig/cipher-ctr-mt-functions.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-ctr-mt-functions.c 2026-07-02 00:27:29.721918987 +0200
-@@ -0,0 +1,682 @@
-+/*
-+ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
-+ *
-+ * Author: Benjamin Bennett <ben@psc.edu>
-+ * Author: Mike Tasota <tasota@gmail.com>
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ * 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 <markus@openbsd.org>
-+ *
-+ * 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"
-+
-+/* only for systems with OSSL 3 */
-+#ifdef WITH_OPENSSL3
-+#include <stdarg.h>
-+#include <string.h>
-+#include <openssl/evp.h>
-+#include "xmalloc.h"
-+#include <unistd.h>
-+#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 * volatile evp_ctx;
-+ struct aes_mt_ctx_st *aes_mt_ctx = job;
-+ struct kq *q;
-+ struct aes_mt_ctx_ptrs *ptr;
-+ 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.
-+ * We init qidx here because if we do it at the top of the function
-+ * we get a warning about it possibly being clobbered. The exact reason
-+ * doesn't make a lot of sense but it has to happen after the
-+ * first pthread_rwlock_rdlock(). Might have something to do with
-+ * incorrect compiler optimizations.
-+ */
-+ int qidx;
-+ 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. Make sure we have enough stack space (under alpine)
-+ * and aren't using more than we need (linux). This can be as low as
-+ * 512KB but that's a minimum. 1024KB gives us a little headroom if we
-+ * need it */
-+#define STACK_SIZE (1024 * 1024)
-+ pthread_attr_t attr;
-+ pthread_attr_init(&attr);
-+ pthread_attr_setstacksize(&attr, STACK_SIZE);
-+ for (int i = 0; i < cipher_threads; i++) {
-+ pthread_rwlock_wrlock(&aes_mt_ctx->tid_lock);
-+ if (pthread_create(&aes_mt_ctx->tid[i], &attr, 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;
-+ uint64_t src_a, key_a;
-+
-+ 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
-+ /* with GCC 13 we have having consistent seg faults
-+ * in this section of code. Since this is a critical
-+ * code path we are removing this until we have a solution
-+ * in place -cjr 02/22/24
-+ * TODO: FIX THIS
-+ */
-+ /* if ((align & 0xf) == 0) { */
-+ /* destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; */
-+ /* } else */
-+#endif
-+ /* 64 bits */
-+ /* this is causing undefined behaviour in sanitizers
-+ * this is annoying because it's more efficient
-+ * but UB is not something I want to retain */
-+ if ((align & 0x7) == 0) {
-+ /* this should resolve the strict aliasing UB
-+ * and the performance doesn't seem to change much
-+ * but the memcpys are killing me 3-5-26 cjr*/
-+ memcpy(&src_a, &srcp.u64[0], sizeof(uint64_t));
-+ memcpy(&key_a, &bufp.u64[0], sizeof(uint64_t));
-+ destp.u64[0] = src_a ^ key_a;
-+ memcpy(&src_a, &srcp.u64[1], sizeof(uint64_t));
-+ memcpy(&key_a, &bufp.u64[1], sizeof(uint64_t));
-+ destp.u64[1] = src_a ^ key_a;
-+ /* 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 /*WITH_OPENSSL3*/
-diff -Nur openssh-10.3p1.orig/cipher-ctr-mt-functions.h openssh-10.3p1/cipher-ctr-mt-functions.h
---- openssh-10.3p1.orig/cipher-ctr-mt-functions.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-ctr-mt-functions.h 2026-07-02 00:27:29.722011492 +0200
-@@ -0,0 +1,142 @@
-+/*
-+ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
-+ *
-+ * Author: Benjamin Bennett <ben@psc.edu>
-+ * Author: Mike Tasota <tasota@gmail.com>
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ * 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 <markus@openbsd.org>
-+ *
-+ * 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 <sys/types.h>
-+#include <pthread.h>
-+#include "cipher-aesctr.h"
-+#include "uthash.h"
-+
-+#ifndef USE_BUILTIN_RIJNDAEL
-+#include <openssl/aes.h>
-+#endif
-+
-+/* only for systems with OSSL 3 */
-+#ifdef WITH_OPENSSL3
-+
-+/*-------------------- 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 /* WITH OPENSSL */
-+#endif /* CTR_MT_FUNCS */
-diff -Nur openssh-10.3p1.orig/cipher-ctr-mt-provider.c openssh-10.3p1/cipher-ctr-mt-provider.c
---- openssh-10.3p1.orig/cipher-ctr-mt-provider.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-ctr-mt-provider.c 2026-07-02 00:27:29.722105347 +0200
-@@ -0,0 +1,390 @@
-+/*
-+ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ * 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"
-+
-+/* only for systems with OSSL 3.0+ */
-+#ifdef WITH_OPENSSL3
-+
-+#include <sys/types.h>
-+#include <string.h>
-+#include <openssl/core.h>
-+#include <openssl/core_dispatch.h>
-+#include <openssl/core_names.h>
-+#include <openssl/err.h>
-+#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, NULL },
-+ { "aes_ctr_mt_192", "provider=hpnssh", aes_mt_funcs_192, NULL },
-+ { "aes_ctr_mt_128", "provider=hpnssh", aes_mt_funcs_128, NULL },
-+ { NULL, 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 /*WITH_OPENSSL3*/
-diff -Nur openssh-10.3p1.orig/cipher.h openssh-10.3p1/cipher.h
---- openssh-10.3p1.orig/cipher.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/cipher.h 2026-07-02 00:46:33.287766758 +0200
-@@ -42,11 +42,17 @@
- #include <openssl/evp.h>
- #endif
- #include "cipher-chachapoly.h"
-+#ifdef WITH_OPENSSL
-+#include "cipher-chachapoly-libcrypto-mt.h"
-+#endif
- #include "cipher-aesctr.h"
-
- #define CIPHER_ENCRYPT 1
- #define CIPHER_DECRYPT 0
-
-+#define CIPHER_MULTITHREAD 1
-+#define CIPHER_SERIAL 0
-+
- struct sshcipher {
- char *name;
- u_int block_size;
-@@ -60,6 +66,7 @@
- #define CFLAG_NONE (1<<3)
- #define CFLAG_INTERNAL CFLAG_NONE /* Don't use "none" for packets */
- #ifdef WITH_OPENSSL
-+#define CFLAG_MT (1<<4)
- const EVP_CIPHER *(*evptype)(void);
- #else
- void *ignored;
-@@ -68,24 +75,27 @@
-
- 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, 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 *);
-
- u_int cipher_ctx_is_plaintext(struct sshcipher_ctx *);
-
-diff -Nur openssh-10.3p1.orig/cipher-switch.c openssh-10.3p1/cipher-switch.c
---- openssh-10.3p1.orig/cipher-switch.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-switch.c 2026-07-02 00:27:29.722363268 +0200
-@@ -0,0 +1,73 @@
-+/*
-+ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+/* This provides the function to switch from a serial to parallel
-+ * cipher. This has been moved into it's own file in order to make it
-+ * available to both the client and server without having to clutter
-+ * up other files.
-+ */
-+
-+#include "includes.h"
-+#include <sys/types.h>
-+#include <string.h>
-+#include "cipher.h"
-+#include "log.h"
-+#include "packet.h"
-+
-+
-+/* if we are using a parallel cipher there can be issues in either
-+ * a fork or sandbox. Essentially, if we switch too early the
-+ * threads get lost and the application hangs. So what we do is
-+ * test if either the send or receive context cipher name
-+ * matches the known available parallel ciphers. If it does
-+ * then we force a rekey which automatically loads the parallel
-+ * cipher. */
-+
-+void
-+cipher_switch(struct ssh *ssh) {
-+#ifdef WITH_OPENSSL
-+ /* get the send and receive context and extract the cipher name */
-+ const void *send_cc = ssh_packet_get_send_context(ssh);
-+ const void *recv_cc = ssh_packet_get_receive_context(ssh);
-+ const char *send = cipher_ctx_name(send_cc);
-+ const char *recv = cipher_ctx_name(recv_cc);
-+
-+ debug_f("Send: %s Recv: %s", send, recv);
-+
-+ /* if the name of the cipher matches then we set the context
-+ * to authenticated (it likely already is though) and then
-+ * force the rekey. Either side can do this. One downside of
-+ * this method is that both sides can request a rekey so you
-+ * can end up duplicating work. This is annoying but the
-+ * performance gains make it worthwhile. Also I
-+ * use strstr here because strcmp would require a 6 part
-+ * if statement */
-+ if (strstr(send, "ctr") || strstr(recv, "ctr")) {
-+ debug("Serial to parallel AES-CTR cipher swap");
-+ /* cipher_reset_multithreaded(); */
-+ ssh_packet_set_authenticated(ssh);
-+ packet_request_rekeying();
-+ }
-+ /* do the same for multithreaded chacha20 but with strcmp */
-+ if ((strcmp(send, "chacha20-poly1305@openssh.com") == 0) ||
-+ (strcmp(recv, "chacha20-poly1305@openssh.com") == 0)) {
-+ debug("Serial to parallel Chacha20-poly1305 cipher swap");
-+ ssh_packet_set_authenticated(ssh);
-+ packet_request_rekeying();
-+ }
-+#endif
-+}
-diff -Nur openssh-10.3p1.orig/cipher-switch.h openssh-10.3p1/cipher-switch.h
---- openssh-10.3p1.orig/cipher-switch.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/cipher-switch.h 2026-07-02 00:27:29.722410341 +0200
-@@ -0,0 +1,18 @@
-+/*
-+ * Copyright (c) 2015 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+void cipher_switch (struct ssh *);
-diff -Nur openssh-10.3p1.orig/clientloop.c openssh-10.3p1/clientloop.c
---- openssh-10.3p1.orig/clientloop.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/clientloop.c 2026-07-02 00:27:29.722601461 +0200
-@@ -100,6 +100,7 @@
- #include "match.h"
- #include "ssherr.h"
- #include "hostfile.h"
-+#include "metrics.h"
-
- #ifdef GSSAPI
- #include "ssh-gss.h"
-@@ -158,6 +159,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 {
-@@ -184,6 +189,8 @@
- static struct global_confirms global_confirms =
- TAILQ_HEAD_INITIALIZER(global_confirms);
-
-+void client_request_metrics(struct ssh *);
-+
- static void quit_message(const char *fmt, ...)
- __attribute__((__format__ (printf, 1, 2)));
-
-@@ -1460,6 +1467,7 @@
- double start_time, total_time;
- int interactive = -1, channel_did_enqueue = 0, r;
- uint64_t ibytes, obytes;
-+ time_t previous_time;
- int conn_in_ready, conn_out_ready;
- sigset_t bsigset, osigset;
-
-@@ -1503,6 +1511,7 @@
- client_repledge();
-
- start_time = monotime_double();
-+ previous_time = time(NULL); /* for metrics polling */
-
- /* Initialize variables. */
- last_was_cr = 1;
-@@ -1547,6 +1556,8 @@
- }
-
- schedule_server_alive_check();
-+ if (options.metrics)
-+ client_request_metrics(ssh); /* initial metrics polling */
-
- if (sigemptyset(&bsigset) == -1 ||
- sigaddset(&bsigset, SIGHUP) == -1 ||
-@@ -1559,8 +1570,27 @@
- error_f("bsigset setup: %s", strerror(errno));
- #endif
-
-+ /*
-+ * Block termination/info signals for the duration of the main loop.
-+ * ppoll() atomically restores the old mask (osigset) while waiting,
-+ * so signals are still delivered promptly during the poll. Keeping
-+ * them blocked during the data-processing phase avoids two
-+ * sigprocmask syscalls per iteration (~76k/sec during bulk transfer,
-+ * ~5.6% of CPU in profiles). Signal-flag checks (quit_pending,
-+ * siginfo_received) still work because the handlers run inside
-+ * ppoll's atomic unblock window.
-+ */
-+ if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
-+ error_f("bsigset sigprocmask: %s", strerror(errno));
-+
- /* 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);
-+ }
-+ }
- channel_did_enqueue = 0;
-
- /* Process buffered packets sent by the server. */
-@@ -1595,8 +1625,6 @@
- * Wait until we have something to do (something becomes
- * available on one of the descriptors).
- */
-- if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
-- error_f("bsigset sigprocmask: %s", strerror(errno));
- if (siginfo_received) {
- siginfo_received = 0;
- channel_report_open(ssh, SYSLOG_LEVEL_INFO);
-@@ -1606,8 +1634,6 @@
- client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc,
- &npfd_active, channel_did_enqueue, &osigset,
- &conn_in_ready, &conn_out_ready);
-- if (sigprocmask(SIG_SETMASK, &osigset, NULL) == -1)
-- error_f("osigset sigprocmask: %s", strerror(errno));
-
- if (quit_pending)
- break;
-@@ -1663,6 +1689,14 @@
- }
- }
- }
-+
-+ /* Restore the pre-loop signal mask now that we are done polling. */
-+ if (sigprocmask(SIG_SETMASK, &osigset, NULL) == -1)
-+ error_f("osigset sigprocmask: %s", strerror(errno));
-+
-+ if (options.metrics)
-+ client_request_metrics(ssh); /* final metrics polling */
-+
- free(pfd);
-
- /* Terminate the session. */
-@@ -2676,6 +2710,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_f("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, uint32_t seq, struct ssh *ssh)
- {
-@@ -2798,6 +2993,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_PREFIX) {
-+ 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_f("Rewriting scp command for hpnscp.");
-+ sprintf(new_cmd, "hpn%s", (const u_char*)sshbuf_ptr(cmd));
-+ debug_f("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-10.3p1.orig/compat.c openssh-10.3p1/compat.c
---- openssh-10.3p1.orig/compat.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/compat.c 2026-07-02 07:53:27.258502868 +0200
-@@ -29,6 +29,7 @@
-
- #include <stdint.h>
- #include <stdlib.h>
-+#include <string.h>
- #include <stdarg.h>
-
- #include "xmalloc.h"
-@@ -136,6 +137,35 @@
- 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) {
-+ /* check if the remote is hpn and if the version
-+ * uses hpn prefixed binaries */
-+ const char *op;
-+ if ((op = strstr(version, "hpn")) != NULL) {
-+ int hpnver = 0;
-+ ssh->compat |= SSH_HPNSSH;
-+ debug("Remote is HPN enabled");
-+ if (sscanf(op, "hpn%d", &hpnver) == 1 &&
-+ hpnver >= 16) {
-+ ssh->compat |= SSH_HPNSSH_PREFIX;
-+ debug("Remote uses HPNSSH prefixes.");
-+ }
-+ }
-+ /* Restrict advertised window for non-HPN OpenSSH >= 8.9. */
-+ if (!(ssh->compat & SSH_HPNSSH)) {
-+ const char *op;
-+ int omaj = 0, omin = 0;
-+ if ((op = strstr(version, "OpenSSH_")) != NULL &&
-+ sscanf(op, "OpenSSH_%d.%d", &omaj, &omin) == 2 &&
-+ (omaj >= 9 || (omaj == 8 && omin >= 9))) {
-+ ssh->compat |= SSH_RESTRICT_WINDOW;
-+ debug("Restricting advertised window size.");
-+ }
-+ }
-+ }
-+ debug("ssh->compat is %u", ssh->compat);
- return;
- }
- }
-diff -Nur openssh-10.3p1.orig/compat.h openssh-10.3p1/compat.h
---- openssh-10.3p1.orig/compat.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/compat.h 2026-07-02 08:53:45.248658363 +0200
-@@ -46,12 +46,12 @@
- /* #define unused 0x00010000 */
- /* #define unused 0x00020000 */
- /* #define unused 0x00040000 */
--/* #define unused 0x00100000 */
-+#define SSH_HPNSSH 0x00100000 /* basically a notice that this is HPN aware */
- #define SSH_BUG_EXTEOF 0x00200000
- #define SSH_BUG_PROBE 0x00400000
--/* #define unused 0x00800000 */
-+#define SSH_RESTRICT_WINDOW 0x00800000 /* restrict advertised window to OpenSSH clients */
- #define SSH_OLD_FORWARD_ADDR 0x01000000
--/* #define unused 0x02000000 */
-+#define SSH_HPNSSH_PREFIX 0x02000000 /* indicates that we have hpn prefixes binaries */
- #define SSH_NEW_OPENSSH 0x04000000
- #define SSH_BUG_DYNAMIC_RPORT 0x08000000
- #define SSH_BUG_CURVE25519PAD 0x10000000
-diff -Nur openssh-10.3p1.orig/configure.ac openssh-10.3p1/configure.ac
---- openssh-10.3p1.orig/configure.ac 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/configure.ac 2026-07-02 00:27:29.723457697 +0200
-@@ -2762,6 +2762,39 @@
- )
- fi
-
-+
-+# Testing MPTCP Support
-+# Does the OS support MPTCP?
-+# We don't use this at the moment
-+# but I am holding it in resrve -cjr 04/04/2025
-+ AC_MSG_CHECKING([whether the OS supports MPTCP])
-+ AC_RUN_IFELSE(
-+ [AC_LANG_PROGRAM([[
-+ #include <stdio.h>
-+ #include <stdlib.h>
-+ #include <unistd.h>
-+ #include <sys/types.h>
-+ #include <sys/socket.h>
-+ #include <stdbool.h>
-+ #include <netinet/in.h>
-+
-+ ]], [[
-+ int sock = -1;
-+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
-+ if (sock < 0) {
-+ exit(1);
-+ }
-+ ]])],
-+ [
-+ AC_MSG_RESULT([yes])
-+ AC_DEFINE([HAVE_MPTCP], [1],
-+ [OS Supports MPTCP])
-+ ],
-+ [
-+ AC_MSG_RESULT([no])
-+ ]
-+ )
-+
- if test "x$ac_cv_func_getaddrinfo" = "xyes" && \
- test "x$check_for_hpux_broken_getaddrinfo" = "x1"; then
- AC_MSG_CHECKING([if getaddrinfo seems to work])
-@@ -3177,8 +3210,8 @@
- 200*) # LibreSSL
- lver=`echo "$sslver" | sed 's/.*libressl-//'`
- case "$lver" in
-- 2*|300*) # 2.x, 3.0.0
-- AC_MSG_ERROR([LibreSSL >= 3.1.0 required (have "$ssl_showver")])
-+ 2*|300*|301*|302*|303*|304*|306*) # 2.x, 3.0.0
-+ AC_MSG_ERROR([LibreSSL >= 3.7.0 required (have "$ssl_showver")])
- ;;
- *) ;; # Assume all other versions are good.
- esac
-@@ -3187,6 +3220,7 @@
- # OpenSSL 3 & 4; we use the 1.1x API
- # https://openssl.org/policies/general/versioning-policy.html
- CPPFLAGS="$CPPFLAGS -DOPENSSL_API_COMPAT=0x10100000L"
-+ AC_DEFINE([WITH_OPENSSL3], [1], [With OpenSSL3])
- ;;
- *)
- AC_MSG_ERROR([Unknown/unsupported OpenSSL version ("$ssl_showver")])
-@@ -3300,6 +3334,29 @@
- EVP_CIPHER_CTX_set_iv \
- ])
-
-+ # 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 <stdlib.h>
-+ #include <stdio.h>
-+ #include <openssl/evp.h>
-+ ]], [[
-+ 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-10.3p1.orig/defines.h openssh-10.3p1/defines.h
---- openssh-10.3p1.orig/defines.h 2026-07-02 00:27:06.092588783 +0200
-+++ openssh-10.3p1/defines.h 2026-07-02 00:27:29.723883369 +0200
-@@ -944,7 +944,11 @@
- #endif
-
- #ifndef SSH_IOBUFSZ
--# define SSH_IOBUFSZ 8192
-+# define SSH_IOBUFSZ (32*1024)
-+#endif
-+
-+#ifndef IPPROTO_MPTCP
-+#define IPPROTO_MPTCP 262
- #endif
-
- /*
-@@ -1000,3 +1004,12 @@
- #endif
-
- #endif /* _DEFINES_H */
-+
-+/* used to enable checking linux kernel versions */
-+#if defined(__linux__) && !defined(__alpine__)
-+#include <linux/version.h>
-+#endif
-+
-+#ifndef KERNEL_VERSION /* shouldn't be necessary to define this */
-+#define KERNEL_VERSION(a,b,c) (((a) <<16) + ((b) << 8) +(c))
-+#endif
-diff -Nur openssh-10.3p1.orig/digest.h openssh-10.3p1/digest.h
---- openssh-10.3p1.orig/digest.h 2026-07-02 00:27:06.092588783 +0200
-+++ openssh-10.3p1/digest.h 2026-07-02 00:27:29.724170607 +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-10.3p1.orig/digest-openssl.c openssh-10.3p1/digest-openssl.c
---- openssh-10.3p1.orig/digest-openssl.c 2026-07-02 00:27:06.092588783 +0200
-+++ openssh-10.3p1/digest-openssl.c 2026-07-02 00:27:29.724254581 +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-10.3p1.orig/FUNDING.yml openssh-10.3p1/FUNDING.yml
---- openssh-10.3p1.orig/FUNDING.yml 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/FUNDING.yml 2026-07-02 00:27:29.724396167 +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-10.3p1.orig/happyeyeballs.c openssh-10.3p1/happyeyeballs.c
---- openssh-10.3p1.orig/happyeyeballs.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/happyeyeballs.c 2026-07-02 00:27:29.724482954 +0200
-@@ -0,0 +1,313 @@
-+/*
-+ * Copyright (c) 2025 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ * Author: Kim Mihn Kaplan (kaplan at kim-mihn.com)
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+/* This is an implementation of RFC 8305 based on a patch
-+ * supplied by Kim Minh Kaplan (kaplan at kim-minh.com).
-+ * Information about RFC 8305 can be found at
-+ * https://www.rfc-editor.org/rfc/rfc8305
-+ * This version *does not* provide support for
-+ * Section 3 where asychronous polling of DNS servers
-+ * is a SHOULD.
-+ * Section 4 requires sorting of DNS results as per
-+ * RFC 6724. This is provided by the getaddrinfo call
-+ * from gcc. This may not be available via musl.
-+ * Interleaving of addresses is not currently supported.
-+ * Section 5 is implemented.
-+ * Section 6 is not implemented outside of getaddrinfo
-+ * Section 7 in under consideration
-+ */
-+
-+
-+/* NOTE: There are a lot of debug statements in here
-+ * because we are still treating this as somewhat
-+ * experimental. A future version will promote this
-+ * to stable and we'll remove a lot of the level 2
-+ * debug statements then.
-+ */
-+
-+#include "includes.h"
-+#ifdef HAVE_SYS_TIME_H
-+# include <sys/time.h>
-+#endif
-+#include <stdlib.h>
-+#include <net/if.h>
-+#include <netinet/in.h>
-+#include <arpa/inet.h>
-+#include <string.h>
-+#include <unistd.h>
-+
-+#include "happyeyeballs.h"
-+#include "sshconnect.h"
-+#include "ssh.h"
-+#include "misc.h"
-+#include "log.h"
-+#include "readconf.h"
-+
-+/* this is from sshconnect.c */
-+extern int ssh_create_socket(struct addrinfo *ai);
-+
-+/* get the options struct if for the delay time */
-+extern Options options;
-+
-+/* have we timed out? */
-+static int
-+timeout(struct timeval *tv, int timeout_ms)
-+ {
-+ if (timeout_ms <= 0)
-+ return 0;
-+ ms_subtract_diff(tv, &timeout_ms);
-+ return timeout_ms <= 0;
-+}
-+
-+/* used to provide debug message in sshconnect */
-+char global_ntop[NI_MAXHOST];
-+
-+/* used to provide information for debug statements */
-+char *return_fam(int fam) {
-+ if (fam == 10)
-+ return "IPv6";
-+ else
-+ return "IPv4";
-+}
-+
-+/*
-+ * Return 0 if the addrinfo was not tried. Return -1 if using it
-+ * failed. Return 1 if it was used.
-+ */
-+static int
-+happy_eyeballs_initiate(const char *host, struct addrinfo *ai,
-+ int *timeout_ms,
-+ struct timeval *initiate,
-+ int *nfds, fd_set *fds,
-+ struct addrinfo *fd_ai[])
-+{
-+ int oerrno, sock;
-+ char ntop[NI_MAXHOST], strport[NI_MAXSERV];
-+
-+ debug2_f ("Happy eyeballs initiating");
-+ memset(ntop, 0, sizeof(ntop));
-+ memset(strport, 0, sizeof(strport));
-+ /* If *nfds != 0 then *initiate is initialised. */
-+ if (*nfds &&
-+ (ai == NULL ||
-+ !timeout(initiate, options.happy_delay))) {
-+ /* Do not initiate new connections yet */
-+ debug2_f ("Waiting to initiate new connection");
-+ return 0;
-+ }
-+ /* trying to use a family we don't support */
-+ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
-+ debug2_f ("Address family not supported");
-+ errno = EAFNOSUPPORT;
-+ return -1;
-+ }
-+
-+ debug2_f ("Running getnameinfo for %s and %d", host, ai->ai_family);
-+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
-+ ntop, sizeof(ntop),
-+ strport, sizeof(strport),
-+ NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
-+ oerrno = errno;
-+ error("%s: getnameinfo failed", __func__);
-+ errno = oerrno;
-+ return -1;
-+ }
-+ memcpy(global_ntop,ntop,sizeof(ntop));
-+ debug2_f("RFC 8305 connecting to %.200s [%.100s] port %s.",
-+ host, ntop, strport);
-+ debug2_f("RFC 8305: %.200s", global_ntop);
-+ /* Create a socket for connecting */
-+ sock = ssh_create_socket(ai);
-+ if (sock < 0) {
-+ /* Any error is already output */
-+ errno = 0;
-+ return -1;
-+ }
-+ if (sock >= FD_SETSIZE) {
-+ error("socket number to big for select: %d", sock);
-+ close(sock);
-+ return -1;
-+ }
-+ fd_ai[sock] = ai;
-+ /* using nonblocking sockets with select allows
-+ * us to fire off new sockets without waiting for a
-+ * connection to be established. This lets us avoid
-+ * the use of threads. set_nonblock is in misc.c
-+ * and uses fnctl */
-+ set_nonblock(sock);
-+ debug2_f("RFC 8305 pre-connect for %s", return_fam(ai->ai_family));
-+ if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
-+ errno != EINPROGRESS) {
-+ error("connect to address %s port %s: %s",
-+ ntop, strport, strerror(errno));
-+ errno = 0;
-+ close(sock);
-+ return -1;
-+ }
-+ debug_f("RFC 8305 post connect for %s", return_fam(ai->ai_family));
-+ monotime_tv(initiate);
-+ FD_SET(sock, fds);
-+ *nfds = MAXIMUM(*nfds, sock + 1);
-+ return 1;
-+}
-+
-+static int
-+happy_eyeballs_process(int *nfds, fd_set *fds,
-+ struct addrinfo *fd_ai[],
-+ int ready, fd_set *wfds)
-+{
-+ socklen_t optlen;
-+ int sock, optval = 0;
-+ char ntop[NI_MAXHOST], strport[NI_MAXSERV];
-+
-+ debug2_f("Processing RFC 8305 connections");
-+ for (sock = *nfds - 1; ready > 0 && sock >= 0; sock--) {
-+ debug2_f("RFC 8305: Processing for %s: %d", return_fam(fd_ai[sock]->ai_family), sock);
-+ if (FD_ISSET(sock, wfds)) {
-+ debug2_f("RFC 8305: FD_ISSET true for %d", sock);
-+ ready--;
-+ optlen = sizeof(optval);
-+ debug_f("RFC 8305: optval is %d for %d", optval, sock);
-+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
-+ &optval, &optlen) < 0) {
-+ optval = errno;
-+ error("getsockopt failed: %s",
-+ strerror(errno));
-+ } else if (optval != 0) {
-+ debug_f("RFC 8305: Copying data for %d on %s", optval, return_fam(fd_ai[sock]->ai_family));
-+ memset(ntop, 0, sizeof(ntop));
-+ memset(strport, 0, sizeof(strport));
-+ if (getnameinfo(fd_ai[sock]->ai_addr,
-+ fd_ai[sock]->ai_addrlen,
-+ ntop, sizeof(ntop),
-+ strport, sizeof(strport),
-+ NI_NUMERICHOST|NI_NUMERICSERV) != 0)
-+ error("connect finally failed: %s",
-+ strerror(optval));
-+ else
-+ error("connect to address %s port %s finally: %s",
-+ ntop, strport, strerror(optval));
-+ }
-+ FD_CLR(sock, fds);
-+ while (*nfds > 0 && ! FD_ISSET(*nfds - 1, fds))
-+ --*nfds;
-+ if (optval == 0) {
-+ debug_f("RFC 8305: unsetting nonblock for %s on %d",
-+ return_fam(fd_ai[sock]->ai_family), sock);
-+ unset_nonblock(sock);
-+ return sock;
-+ }
-+ close(sock);
-+ errno = optval;
-+ }
-+ }
-+ return -1;
-+}
-+
-+int
-+happy_eyeballs(const char * host, struct addrinfo *ai,
-+ struct sockaddr_storage *hostaddr, int *timeout_ms)
-+{
-+ struct addrinfo *fd_ai[FD_SETSIZE]; /* 1024 */
-+ struct timeval initiate_tv, start_tv, select_tv, *tv;
-+ fd_set fds, wfds;
-+ int res, oerrno, diff, diff0, nfds = 0, sock = -1;
-+
-+ debug_f("Starting RFC 8305/Happy Eyeballs connection");
-+
-+ FD_ZERO(&fds);
-+ if (*timeout_ms > 0) /* default value of no timeout - use TCP default instead */
-+ monotime_tv(&start_tv); /* monotime_tv is in misc.c */
-+
-+ /* run through potentials unless we have timed out */
-+ /* timeout_ms being the user defined connection timeout if
-+ * they aren't using the tcp timeout */
-+ while ((ai != NULL || nfds > 0) &&
-+ ! timeout(&start_tv, *timeout_ms)) {
-+ /* set up the sockets */
-+ res = happy_eyeballs_initiate(host, ai,
-+ timeout_ms, &initiate_tv, &nfds, &fds, fd_ai);
-+ if (res != 0)
-+ ai = ai->ai_next;
-+ if (res == -1)
-+ continue;
-+ tv = NULL;
-+
-+ /* this is just to determine the pause between
-+ * calls to select. */
-+ if (ai != NULL || *timeout_ms > 0) {
-+ debug2_f ("RFC 8305: In pause...");
-+ tv = &select_tv;
-+ if (ai != NULL) {
-+ diff = options.happy_delay;
-+ ms_subtract_diff(&initiate_tv, &diff);
-+ if (*timeout_ms > 0) {
-+ diff0 = *timeout_ms;
-+ ms_subtract_diff(&start_tv, &diff0);
-+ diff = MINIMUM(diff, diff0);
-+ }
-+ } else {
-+ diff = *timeout_ms;
-+ ms_subtract_diff(&start_tv, &diff);
-+ }
-+ tv->tv_sec = diff / 1000;
-+ tv->tv_usec = (diff % 1000) * 1000;
-+ }
-+
-+ /* create a writeable set of file descriptors */
-+ wfds = fds;
-+ /* select will pause for time tv determined by the above
-+ * timing caculations */
-+ debug2_f("RFC 8305: Starting select");
-+ res = select(nfds, NULL, &wfds, NULL, tv);
-+ debug2_f("RFC 8305: Leaving select");
-+
-+ /* preserve any errors */
-+ oerrno = errno;
-+ if (res < 0) {
-+ error("select failed: %s", strerror(errno));
-+ errno = oerrno;
-+ continue;
-+ }
-+ /* start processing the sockets */
-+ debug2_f ("RFC 8305: Processing happy eyeballs fds");
-+ sock = happy_eyeballs_process(&nfds, &fds, fd_ai,
-+ res, &wfds);
-+ if (sock >= 0) {
-+ debug_f ("RFC 8305 / Happy Eyeballs connected");
-+ /* we have a connection */
-+ memcpy(hostaddr, fd_ai[sock]->ai_addr,
-+ fd_ai[sock]->ai_addrlen);
-+ break;
-+ }
-+ debug_f("RFC 8305: Restarting while loop.");
-+ }
-+ oerrno = errno;
-+ /* close other connection attempts/sockets */
-+ debug2_f("RFC 8305: NFDS is %d", nfds);
-+ while (nfds-- > 0) {
-+ debug2_f("RFC 8305: Running FD_ISSET on %d", nfds);
-+ if (FD_ISSET(nfds, &fds))
-+ close(nfds);
-+ }
-+ /* we timed out with no valid connections */
-+ if (timeout(&start_tv, *timeout_ms))
-+ errno = ETIMEDOUT;
-+ else
-+ errno = oerrno;
-+ return sock;
-+}
-diff -Nur openssh-10.3p1.orig/happyeyeballs.h openssh-10.3p1/happyeyeballs.h
---- openssh-10.3p1.orig/happyeyeballs.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/happyeyeballs.h 2026-07-02 00:27:29.724543338 +0200
-@@ -0,0 +1,23 @@
-+/*
-+ * Copyright (c) 2025 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+/*
-+ * RFC 8305 Happy Eyeballs Version 2: Better Connectivity Using Concurrency
-+ *
-+ */
-+
-+int happy_eyeballs(const char *, struct addrinfo *,
-+ struct sockaddr_storage *, int *);
-diff -Nur openssh-10.3p1.orig/HPN-README openssh-10.3p1/HPN-README
---- openssh-10.3p1.orig/HPN-README 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/HPN-README 2026-07-02 00:27:29.724622547 +0200
-@@ -0,0 +1,200 @@
-+Notes:
-+
-+FIPS Mode and Parallel Ciphers in 18.7.1
-+Using HPN-SSH in operating systems working in FIPS mode (e.g. RHEL with
-+FIPS enabled) preclude the use of parallel ciphers. This is because
-+the parallel AES-CTR implementation is not FIPS certified and will cause FIPS
-+to exit with an error when loaded. In the case of the parallel ChaCha20 cipher
-+the algorithm itself has not been FIPS certified and no implementation of
-+ChaCha20 should be allowed in FIPS mode. We suggest the use of the AES-GCM
-+cipher when operating under FIPS mode for optimal performance.
-+
-+Happy Eyeballs Support in 18.7.1
-+Happy Eyeballs (RFC 8305) is for use on dual stack systems meaning that they have both
-+IPv4 and IPv6 TCP stacks. When enabled this option will try to connect to the
-+target over both IPv4 and IPv6 with preference given to IPv6. The first connection
-+that completes successfully will be used. Any outstanding connection attempts will
-+be closed. As of version 18.7.1 this option should be considered somewhat experimental.
-+
-+usage:
-+-oHappyEyes=[Yes|No] will enable Happy Eyeballs. The default is no.
-+-oHappyDelay=[N] where N is a positive integer expressed in milliseconds.
-+ The default value of 250ms is suggested by RFC 8305.
-+
-+MPTCP Support in 18.7.0:
-+Multipath TCP is now available as a runtime option for HPN-SSH. MPTCP
-+is available only on Linux and Mac OSX operating systems. Using MPTCP on a system that
-+doesn't support it will result in a notice and failure. The use cases for MPTCP include
-+seamless handovers when changing networks and aggregating multiple interfaces to improve
-+available bandwidth. As of 18.7.0 this option should be considered somewhat experimental.
-+
-+Usage:
-+-oUseMPTCP=[Yes|No] will enable MPTCP. The default is no.
-+
-+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 version of 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.
-+
-+HPNSCP with Resume functionality
-+This feature allows hpnscp 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:
-+'hpnscp myhugefile me@host:~'
-+and it dies halfway through the transfer issuing
-+'hpnscp -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. The 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.
-+
-+MULTI-THREADED AES 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.
-+Usage examples:
-+ ssh -caes128-ctr you@host.com
-+ scp -oCipher=aes256-ctr file you@host.com:~/file
-+
-+MULTI-THREADED ChaCha20-Poly1305 CIPHER:
-+The default cipher used by HPN-SSH is now a threaded implementation of the
-+ChaCha20-Poly1305 cipher. In tests this cipher results in an approximately 90% gain in
-+throughput performance in comparison to the serial implementation found in OpenSSH.
-+This cipher is fully compatible with all known existing implementations of ChaCha20-Poly1305.
-+
-+Internally this cipher is referred to as chacha20-poly1305-mt@hpnssh.org (CC20-MT)
-+similar to how the OpenSSH implementation is known as chacha20-poly1305@openssh.com
-+(CC20-S (for serial)). No changes are required on the part of the user to make use of CC20-MT.
-+However, if the users doesn't want to make use of this cipher they can explicitly load CC20-S using
-+'-cchach20-poly1305@openssh.com` on the command line.
-+
-+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:~
-+
-+HPN Specific Configuration options
-+
-+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.
-+
-+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!
-+
-+
-+Edited: September 19, 2025
-diff -Nur openssh-10.3p1.orig/HPNSSHInstallation.txt openssh-10.3p1/HPNSSHInstallation.txt
---- openssh-10.3p1.orig/HPNSSHInstallation.txt 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/HPNSSHInstallation.txt 2026-07-02 00:27:29.724724844 +0200
-@@ -0,0 +1,354 @@
-+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/hpn-ssh. Get a copy with
-+"git clone https://github.com/rapier1/hpn-ssh”.
-+
-+
-+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: 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 8: 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 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/hpnsshd.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
-+
-+Alternatively, the ./configure command will generate a hpnsshd.service file from
-+hpnsshd.service.in. You can use this file by copying it to
-+/libsystemd/system/hpnsshd.service instead of copying the above text.
-+
-+Then create the defaults file at /etc/default/hpnsshd 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. A sample /etc/defauls/hpnssh file may be found in defaults.hpnsshd.
-+
-+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.
-+
-+Alternatively, you may use the hpnsshd.init file created during configure. This will be
-+prepopulated with the correct paths by the configure script.
-+
-+#! /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-10.3p1.orig/kex.c openssh-10.3p1/kex.c
---- openssh-10.3p1.orig/kex.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/kex.c 2026-07-02 00:27:29.724873183 +0200
-@@ -63,6 +63,7 @@
-
- #include "ssherr.h"
- #include "sshbuf.h"
-+#include "canohost.h"
- #include "digest.h"
- #include "xmalloc.h"
- #include "audit.h"
-@@ -781,17 +782,83 @@
- free(kex);
- }
-
-+/*
-+ * This function seeks through a comma-separated list and checks for instances
-+ * of the multithreaded CC20 cipher. If found, it then ensures that the serial
-+ * CC20 cipher is also in the list, adding it if necessary.
-+ */
-+char *
-+patch_list(char * orig)
-+{
-+ char * adj = xstrdup(orig);
-+ char * match;
-+ u_int next;
-+
-+ const char * ccpstr = "chacha20-poly1305@openssh.com";
-+ const char * ccpmtstr = "chacha20-poly1305-mt@hpnssh.org";
-+
-+ match = match_list(ccpmtstr, orig, &next);
-+ if (match != NULL) { /* CC20-MT found in the list */
-+ free(match);
-+ match = match_list(ccpstr, orig, NULL);
-+ if (match == NULL) { /* CC20-Serial NOT found in the list */
-+ adj = xreallocarray(adj,
-+ strlen(adj) /* original string length */
-+ + 1 /* for the original null-terminator */
-+ + strlen(ccpstr) /* make room for ccpstr */
-+ + 1 /* make room for the comma delimiter */
-+ , sizeof(char));
-+ /*
-+ * adj[next] points to the character after the CC20-MT
-+ * string. adj[next] might be ',' or '\0' at this point.
-+ */
-+ adj[next] = ',';
-+ /* adj + next + 1 is the character after that comma */
-+ memcpy(adj + next + 1, ccpstr, strlen(ccpstr));
-+ /* rewrite the rest of the original list */
-+ memcpy(adj + next + 1 + strlen(ccpstr), orig + next,
-+ strlen(orig + next) + 1);
-+ } else { /* CC20-Serial found in the list, nothing to do */
-+ free(match);
-+ }
-+ }
-+
-+ return adj;
-+}
-+
- int
- kex_ready(struct ssh *ssh, char *proposal[PROPOSAL_MAX])
- {
-- int r;
-+ int r = 0;
-+
-+#ifdef WITH_OPENSSL
-+ char * orig_ctos = proposal[PROPOSAL_ENC_ALGS_CTOS];
-+ char * orig_stoc = proposal[PROPOSAL_ENC_ALGS_STOC];
-+ proposal[PROPOSAL_ENC_ALGS_CTOS] =
-+ patch_list(proposal[PROPOSAL_ENC_ALGS_CTOS]);
-+ proposal[PROPOSAL_ENC_ALGS_STOC] =
-+ patch_list(proposal[PROPOSAL_ENC_ALGS_STOC]);
-+
-+ /*
-+ * TODO: Likely memory leak here. The original contents of
-+ * proposal[PROPOSAL_ENC_ALGS_CTOS] are no longer accessible or
-+ * freeable.
-+ */
-+#endif
-
- if ((r = kex_prop2buf(ssh->kex->my, proposal)) != 0)
-- return r;
-+ goto restoreProposal;
- ssh->kex->flags = KEX_INITIAL;
- kex_reset_dispatch(ssh);
- ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit);
-- return 0;
-+ restoreProposal:
-+#ifdef WITH_OPENSSL
-+ free(proposal[PROPOSAL_ENC_ALGS_CTOS]);
-+ free(proposal[PROPOSAL_ENC_ALGS_STOC]);
-+ proposal[PROPOSAL_ENC_ALGS_CTOS] = orig_ctos;
-+ proposal[PROPOSAL_ENC_ALGS_STOC] = orig_stoc;
-+#endif
-+ return r;
- }
-
- int
-@@ -980,6 +1047,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)
-@@ -1056,6 +1128,31 @@
- peer[nenc] = NULL;
- goto out;
- }
-+#ifdef WITH_OPENSSL
-+ if ((strcmp(newkeys->enc.name, "chacha20-poly1305@openssh.com")
-+ == 0) && (match_list("chacha20-poly1305-mt@hpnssh.org",
-+ my[nenc], NULL) != NULL)) {
-+ /*
-+ * if we're using the serial CC20 cipher while the
-+ * multithreaded implementation is an option...
-+ */
-+ free(newkeys->enc.name);
-+ newkeys->enc.cipher = cipher_by_name(
-+ "chacha20-poly1305-mt@hpnssh.org");
-+ if (newkeys->enc.cipher == NULL) {
-+ error_f("%s cipher not found.",
-+ "chacha20-poly1305-mt@hpnssh.org");
-+ r = SSH_ERR_INTERNAL_ERROR;
-+ kex->failed_choice = peer[nenc];
-+ peer[nenc] = NULL;
-+ goto out;
-+ } else {
-+ newkeys->enc.name = xstrdup(
-+ "chacha20-poly1305-mt@hpnssh.org");
-+ }
-+ /* we promote to the multithreaded implementation */
-+ }
-+#endif
- authlen = cipher_authlen(newkeys->enc.cipher);
- /* ignore mac for authenticated encryption */
- if (authlen == 0 &&
-@@ -1071,11 +1168,43 @@
- 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);
-+ ssh->none_mac = 1;
-+ }
-+ }
-+
- debug("kex: %s cipher: %s MAC: %s compression: %s",
- ctos ? "client->server" : "server->client",
- newkeys->enc.name,
- authlen == 0 ? newkeys->mac.name : "<implicit>",
- 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 : "<implicit>",
-+ newkeys->comp.name);
-+ }
-+ log_flag = 1;
- }
- need = dh_need = 0;
- for (mode = 0; mode < MODE_MAX; mode++) {
-@@ -1435,7 +1564,7 @@
- if (version_addendum != NULL && *version_addendum == '\0')
- version_addendum = NULL;
- if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%s%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;
-@@ -1571,9 +1700,24 @@
- 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);
-+ if (ssh->compat & SSH_HPNSSH)
-+ debug("HPN to HPN Connection.");
-+ else
-+ debug("Non-HPN to HPN Connection.");
-+
-+ if(ssh->compat & SSH_RESTRICT_WINDOW)
-+ debug ("Window size restricted.");
-
- mismatch = 0;
- switch (remote_major) {
-diff -Nur openssh-10.3p1.orig/log.c openssh-10.3p1/log.c
---- openssh-10.3p1.orig/log.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/log.c 2026-07-02 00:27:29.725125481 +0200
-@@ -47,6 +47,11 @@
- #include <string.h>
- #include <syslog.h>
- #include <unistd.h>
-+#include <errno.h>
-+#include "packet.h" /* needed for host and port look ups */
-+#ifdef HAVE_SYS_TIME_H
-+# include <sys/time.h> /* to get current time */
-+#endif
- #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
- # include <vis.h>
- #endif
-@@ -65,6 +70,8 @@
- static size_t nlog_verbose;
- 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-10.3p1.orig/mac.c openssh-10.3p1/mac.c
---- openssh-10.3p1.orig/mac.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/mac.c 2026-07-02 00:27:29.725362455 +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-10.3p1.orig/Makefile.in openssh-10.3p1/Makefile.in
---- openssh-10.3p1.orig/Makefile.in 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/Makefile.in 2026-07-02 01:18:27.740572217 +0200
-@@ -54,7 +54,7 @@
- CFLAGS_NOPIE=@CFLAGS_NOPIE@
- CPPFLAGS=-I. -I$(srcdir) -I$(COMPATINCLUDES) @CPPFLAGS@ $(PATHS) @DEFS@
- PICFLAG=@PICFLAG@
--LIBS=@LIBS@
-+LIBS=@LIBS@ -lpthread
- CHANNELLIBS=@CHANNELLIBS@
- K5LIBS=@K5LIBS@
- GSSLIBS=@GSSLIBS@
-@@ -93,7 +93,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 \
-@@ -103,6 +103,7 @@
- msg.o dns.o entropy.o gss-genr.o umac.o umac128.o \
- smult_curve25519_ref.o \
- poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \
-+ cipher-chachapoly-libcrypto-mt.o \
- ssh-ed25519.o digest-openssl.o digest-libc.o \
- hmac.o ed25519.o ed25519-openssl.o \
- kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
-@@ -110,14 +111,16 @@
- kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
- kexgssc.o \
- sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
-- sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
-+ sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o \
-+ metrics.o binn.o happyeyeballs.o cipher-ctr-mt-provider.o \
-+ cipher-ctr-mt-functions.o ossl3-provider-err.o num.o
-
- P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
-
- SKOBJS= ssh-sk-client.o
-
- SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
-- sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
-+ sshconnect.o sshconnect2.o mux.o cipher-switch.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
-
- SSHDOBJS=sshd.o \
- platform-listen.o \
-@@ -136,7 +139,7 @@
- auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \
- loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
- sftp-server.o sftp-common.o \
-- uidswap.o platform-listen.o $(P11OBJS) $(SKOBJS)
-+ uidswap.o platform-listen.o cipher-switch.o $(P11OBJS) $(SKOBJS)
-
- SSHD_AUTH_OBJS=sshd-auth.o \
- auth2-methods.o \
-@@ -151,7 +154,7 @@
- sandbox-null.o sandbox-rlimit.o sandbox-darwin.o \
- sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-solaris.o \
- sftp-server.o sftp-common.o \
-- uidswap.o $(P11OBJS) $(SKOBJS)
-+ uidswap.o cipher-switch.o $(P11OBJS) $(SKOBJS)
-
- SFTP_CLIENT_OBJS=sftp-common.o sftp-client.o sftp-glob.o ssherr-nolibcrypto.o
-
-@@ -235,7 +238,7 @@
- $(LD) -o $@ $(SSHD_AUTH_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) $(GSSLIBS) $(K5LIBS) $(CHANNELLIBS) $(LIBWTMPDB)
-
- 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-10.3p1.orig/metrics.c openssh-10.3p1/metrics.c
---- openssh-10.3p1.orig/metrics.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/metrics.c 2026-07-02 00:27:29.725869026 +0200
-@@ -0,0 +1,439 @@
-+/*
-+ * Copyright (c) 2022 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+#include "includes.h"
-+#include "metrics.h"
-+#include "ssherr.h"
-+#include <stdlib.h>
-+#include <stdio.h>
-+
-+/* kernel version macro moved to defines.h */
-+
-+/* 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
-+ */
-+#if (defined __linux__) && !defined(__alpine__)
-+ 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-10.3p1.orig/metrics.h openssh-10.3p1/metrics.h
---- openssh-10.3p1.orig/metrics.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/metrics.h 2026-07-02 00:27:29.725946004 +0200
-@@ -0,0 +1,45 @@
-+/*
-+ * Copyright (c) 2022 The Board of Trustees of Carnegie Mellon University.
-+ *
-+ * Author: Chris Rapier <rapier@psc.edu>
-+ *
-+ * This library is free software; you can redistribute it and/or modify it
-+ * under the terms of the MIT License.
-+ *
-+ * This library is distributed in the hope that it will be useful, but WITHOUT
-+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-+ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
-+ *
-+ * You should have received a copy of the MIT License along with this library;
-+ * if not, see http://opensource.org/licenses/MIT.
-+ *
-+ */
-+
-+#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__ && !defined(__alpine__)
-+#define TCP_INFO 1
-+#if defined __linux__ && !defined(__alpine__)
-+#include <linux/tcp.h>
-+#else
-+#include <netinet/tcp.h>
-+#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-10.3p1.orig/misc.c openssh-10.3p1/misc.c
---- openssh-10.3p1.orig/misc.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/misc.c 2026-07-02 00:27:29.726084923 +0200
-@@ -66,6 +66,82 @@
- #include "ssherr.h"
- #include "platform.h"
-
-+/* Function to determine if FIPS is enabled or not.
-+ * We assume that fips is not enabled and then test from there.
-+ * The idea is that if there is an error or we can't read the value
-+ * then either the OS doesn't support FIPS or that FIPS will
-+ * catch anything we try to do that's not FIPS compliant.
-+ * That would be limited to trying to use one of the parallel ciphers.
-+ */
-+int
-+fips_enabled()
-+{
-+ int mode = 0;
-+ const char* fips_path = "/proc/sys/crypto/fips_enabled";
-+ FILE *fips_enabled = NULL;
-+
-+ debug2_f("Checking for FIPS");
-+
-+ /* if we can't open the path to fips_enabled it
-+ * either doesn't exist or there is an error. In either
-+ * case we treat it as if fips is *not* enabled.
-+ * This is because I want to fail towards the most
-+ * common scenario which is that fips_enabled either
-+ * doesn't exist (non-fedora variants) or isn't
-+ * enabled.
-+ */
-+ fips_enabled = fopen(fips_path, "r");
-+ if (!fips_enabled) {
-+ debug3_f("Cannot open path to fips_enabled.");
-+ return 0;
-+ }
-+
-+ /* fips_enabled does exist so read the value.
-+ * It should be either 0 (disabled) or 1 (enabled)
-+ */
-+ if ( 1 != fscanf(fips_enabled,"%d", &mode) ) {
-+ /* if we get some error here then we
-+ * again fail to returning fips being disabled
-+ */
-+ debug3_f("Error processing fips_enabled.");
-+ return 0;
-+ }
-+
-+ /* let the user know the status */
-+ if (mode == 0)
-+ debug3_f("FIPS mode is disabled.");
-+ else
-+ debug3_f("FIPS mode is enabled.");
-+
-+ return mode;
-+}
-+
-+/* helper function used to determine memory usage during
-+ * development process. Not to be used in production.
-+ */
-+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)
-diff -Nur openssh-10.3p1.orig/misc.h openssh-10.3p1/misc.h
---- openssh-10.3p1.orig/misc.h 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/misc.h 2026-07-02 00:27:29.726610254 +0200
-@@ -276,4 +276,17 @@
- strerror(errno)); \
- } while (0)
-
-+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);
-+int fips_enabled();
-+
- #endif /* _MISC_H */
-diff -Nur openssh-10.3p1.orig/myproposal.h openssh-10.3p1/myproposal.h
---- openssh-10.3p1.orig/myproposal.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/myproposal.h 2026-07-02 00:27:29.726824903 +0200
-@@ -74,10 +74,20 @@
- "rsa-sha2-512," \
- "rsa-sha2-256"
-
-+/*if we aren't using OpenSSL we need to remove
-+ * the parallel ChaCha20 cipher from the list */
-+#ifdef WITH_OPENSSL
- #define KEX_SERVER_ENCRYPT \
-+ "chacha20-poly1305-mt@hpnssh.org," \
- "chacha20-poly1305@openssh.com," \
- "aes128-gcm@openssh.com,aes256-gcm@openssh.com," \
- "aes128-ctr,aes192-ctr,aes256-ctr"
-+#else
-+#define KEX_SERVER_ENCRYPT \
-+ "chacha20-poly1305@openssh.com," \
-+ "aes128-gcm@openssh.com,aes256-gcm@openssh.com," \
-+ "aes128-ctr,aes192-ctr,aes256-ctr"
-+#endif
-
- #define KEX_CLIENT_ENCRYPT KEX_SERVER_ENCRYPT
-
-diff -Nur openssh-10.3p1.orig/num.c openssh-10.3p1/num.c
---- openssh-10.3p1.orig/num.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/num.c 2026-07-02 00:27:29.726975985 +0200
-@@ -0,0 +1,156 @@
-+/* CC0 license applied, see LICENCE.md */
-+
-+#include <string.h>
-+#include "num.h"
-+
-+#ifdef WITH_OPENSSL3
-+
-+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 /* WITH_OPENSSL3 */
-diff -Nur openssh-10.3p1.orig/num.h openssh-10.3p1/num.h
---- openssh-10.3p1.orig/num.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/num.h 2026-07-02 00:27:29.727027033 +0200
-@@ -0,0 +1,15 @@
-+/* CC0 license applied, see LICENCE.md */
-+
-+#include "includes.h"
-+#ifdef WITH_OPENSSL3
-+
-+#include <openssl/core.h>
-+
-+/* 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 /* WITH_OPENSSL3 */
-diff -Nur openssh-10.3p1.orig/ossl3-provider-err.c openssh-10.3p1/ossl3-provider-err.c
---- openssh-10.3p1.orig/ossl3-provider-err.c 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/ossl3-provider-err.c 2026-07-02 00:27:29.727084149 +0200
-@@ -0,0 +1,108 @@
-+/* CC0 license applied, see LICENCE.md */
-+
-+#include <assert.h>
-+#include <stdlib.h>
-+#include "ossl3-provider-err.h"
-+
-+#ifdef WITH_OPENSSL3
-+
-+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 <openssl/err.h>
-+ */
-+ 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 /* WITH_OPENSSL3 */
-diff -Nur openssh-10.3p1.orig/ossl3-provider-err.h openssh-10.3p1/ossl3-provider-err.h
---- openssh-10.3p1.orig/ossl3-provider-err.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/ossl3-provider-err.h 2026-07-02 00:27:29.727136550 +0200
-@@ -0,0 +1,73 @@
-+/* CC0 license applied, see LICENCE.md */
-+
-+#include "includes.h"
-+#ifdef WITH_OPENSSL3
-+#include <stdint.h>
-+#include <openssl/core.h>
-+#include <openssl/core_dispatch.h>
-+
-+/*
-+ * 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 <openssl/err.h> 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 /* WITH_OPENSSL3 */
-diff -Nur openssh-10.3p1.orig/packet.c openssh-10.3p1/packet.c
---- openssh-10.3p1.orig/packet.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/packet.c 2026-07-02 00:27:29.727282994 +0200
-@@ -86,6 +86,7 @@
- #include "mac.h"
- #include "log.h"
- #include "canohost.h"
-+#include "channels.h"
- #include "misc.h"
- #include "packet.h"
- #include "ssherr.h"
-@@ -97,7 +98,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 {
- uint32_t seqnr;
-@@ -244,12 +258,23 @@
- (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 and type them with descriptive names */
-+ sshbuf_relabel(state->input, "input");
-+ sshbuf_type(state->input, BUF_PACKET_INPUT);
-+ sshbuf_relabel(state->incoming_packet, "inpacket");
-+ sshbuf_type(state->incoming_packet, BUF_PACKET_INCOMING);
-+ sshbuf_relabel(state->output, "output");
-+ sshbuf_type(state->output, BUF_PACKET_OUTPUT);
-+ sshbuf_relabel(state->outgoing_packet, "outpacket");
-+ sshbuf_type(state->outgoing_packet, BUF_PACKET_OUTGOING);
-+
- 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->interactive_mode = 1;
- state->qos_interactive = state->qos_other = -1;
-@@ -299,7 +324,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) {
-@@ -316,9 +341,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, 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, 0, CIPHER_DECRYPT,
-+ state->after_authentication)) != 0) {
- error_fr(r, "cipher_init failed");
- free(ssh); /* XXX need ssh_free_session_state? */
- return NULL;
-@@ -389,7 +416,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;
-@@ -995,6 +1022,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);
-
-@@ -1035,12 +1063,32 @@
- if ((r = mac_init(mac)) != 0)
- return r;
- }
-- mac->enabled = 1;
-+
-+ /* if we are using NONE MAC then we don't need to enable the
-+ * mac routines. This disables them and we can claw back some cycles
-+ * from the CPU -cjr 3/21/2023 */
-+ if (ssh->none_mac != 1)
-+ mac->enabled = 1;
-+
- DBG(debug_f("cipher_init: %s", dir));
- cipher_free(*ccp);
- *ccp = NULL;
-+#ifdef WITH_OPENSSL
-+ if (strcmp(enc->name, "chacha20-poly1305-mt@hpnssh.org") == 0) {
-+ if (state->after_authentication)
-+ enc->cipher = cipher_by_name(
-+ "chacha20-poly1305-mt@hpnssh.org");
-+ else
-+ enc->cipher = cipher_by_name(
-+ "chacha20-poly1305@openssh.com");
-+ if (enc->cipher == NULL)
-+ return r;
-+ }
-+#endif
- 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->p_send.seqnr : state->p_read.seqnr,
-+ crypt_type, state->after_authentication)) != 0)
- return r;
- if (!state->cipher_warning_done &&
- (wmsg = cipher_warning_message(*ccp)) != NULL) {
-@@ -1064,25 +1112,46 @@
- }
- 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)
-- *hard_max_blocks = (uint64_t)1 << (enc->block_size*2);
-- else
-- *hard_max_blocks = ((uint64_t)1 << 30) / enc->block_size;
-+ /* get the maximum number of blocks the cipher can
-+ * handle safely */
-+ *hard_max_blocks = cipher_rekey_blocks(enc->cipher);
- *max_blocks = *hard_max_blocks;
-+
-+ /* if we have a custom oRekeyLimit use that. */
- 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);
-+
-+ /* 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)
- /*
- * Checks whether the packet- or block- based rekeying limits have been
-@@ -1140,6 +1209,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())
-@@ -1506,7 +1583,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;
-
-@@ -1610,7 +1687,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;
-@@ -1669,7 +1746,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
-@@ -1696,7 +1773,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);
-@@ -1705,7 +1782,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;
-@@ -1728,7 +1805,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
-@@ -1772,11 +1849,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));
-@@ -1998,7 +2075,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) {
-@@ -2077,17 +2154,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 */
-@@ -2129,6 +2210,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
-@@ -2383,10 +2482,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 *
-@@ -3025,6 +3133,20 @@
- return 0;
- }
-
-+/* used for cipher switching
-+ * only called in cipher-swtich.c */
-+void *
-+ssh_packet_get_send_context(struct ssh *ssh)
-+{
-+ return ssh->state->send_context;
-+}
-+
-+void *
-+ssh_packet_get_receive_context(struct ssh *ssh)
-+{
-+ return ssh->state->receive_context;
-+}
-+
- static char *
- format_traffic_stats(struct packet_state *ps)
- {
-diff -Nur openssh-10.3p1.orig/packet.h openssh-10.3p1/packet.h
---- openssh-10.3p1.orig/packet.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/packet.h 2026-07-02 00:27:29.727636223 +0200
-@@ -89,6 +89,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 state */
-+ int none;
-+
-+ /* track if we have disabled the mac as well */
-+ int none_mac;
- };
-
- typedef int (ssh_packet_hook_fn)(struct ssh *, struct sshbuf *,
-@@ -158,6 +169,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 *);
-
-@@ -173,6 +186,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-10.3p1.orig/poly1305.c openssh-10.3p1/poly1305.c
---- openssh-10.3p1.orig/poly1305.c 2026-07-02 00:27:06.097234467 +0200
-+++ openssh-10.3p1/poly1305.c 2026-07-02 00:27:29.727921479 +0200
-@@ -11,6 +11,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) \
-@@ -28,7 +38,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;
-@@ -155,3 +165,4 @@
- U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32);
- U32TO8_LE(&out[12], f3);
- }
-+#endif /* OPENSSL_HAVE_POLY_EVP */
-diff -Nur openssh-10.3p1.orig/poly1305.h openssh-10.3p1/poly1305.h
---- openssh-10.3p1.orig/poly1305.h 2026-07-02 00:27:06.097234467 +0200
-+++ openssh-10.3p1/poly1305.h 2026-07-02 00:27:29.728299005 +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 <openssl/evp.h>
-+
-+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-10.3p1.orig/progressmeter.c openssh-10.3p1/progressmeter.c
---- openssh-10.3p1.orig/progressmeter.c 2026-07-02 00:27:06.097234467 +0200
-+++ openssh-10.3p1/progressmeter.c 2026-07-02 00:27:29.728413322 +0200
-@@ -65,6 +65,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 long long bytes_per_second; /* current speed in bytes per second */
-@@ -131,6 +133,7 @@
- long long cur_speed;
- int hours, minutes, seconds;
- int file_len, cols;
-+ off_t delta_pos;
-
- if (file == NULL || (!force_update && !alarm_fired && !win_resized) ||
- !can_output())
-@@ -147,6 +150,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 {
-@@ -176,7 +183,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 */
-@@ -193,6 +200,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;
-@@ -235,6 +248,7 @@
- }
- free(buf);
- free(obuf);
-+ last_pos = cur_pos;
- }
-
- static void
-diff -Nur openssh-10.3p1.orig/readconf.c openssh-10.3p1/readconf.c
---- openssh-10.3p1.orig/readconf.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/readconf.c 2026-07-02 00:27:29.728854897 +0200
-@@ -62,6 +62,7 @@
- #include "mac.h"
- #include "myproposal.h"
- #include "digest.h"
-+#include "sshbuf.h"
- #include "version.h"
- #include "ssh-gss.h"
-
-@@ -165,6 +166,10 @@
- oHashKnownHosts,
- oTunnel, oTunnelDevice,
- oLocalCommand, oPermitLocalCommand, oRemoteCommand,
-+ oTcpRcvBufPoll, oHPNDisabled,
-+ oNoneEnabled, oNoneMacEnabled, oNoneSwitch,
-+ oDisableMTAES, oUseMPTCP, oHappyEyes, oHappyDelay,
-+ oMetrics, oMetricsPath, oMetricsInterval, oFallback, oFallbackPort,
- oVisualHostKey,
- oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull,
- oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass,
-@@ -312,6 +317,18 @@
- { "kexalgorithms", oKexAlgorithms },
- { "ipqos", oIPQoS },
- { "requesttty", oRequestTTY },
-+ { "noneenabled", oNoneEnabled },
-+ { "nonemacenabled", oNoneMacEnabled },
-+ { "noneswitch", oNoneSwitch },
-+ { "usemptcp", oUseMPTCP},
-+ { "happyeyes", oHappyEyes },
-+ { "happydelay", oHappyDelay },
-+ { "disablemtaes", oDisableMTAES },
-+ { "metrics", oMetrics },
-+ { "metricspath", oMetricsPath },
-+ { "metricsinterval", oMetricsInterval },
-+ { "fallback", oFallback },
-+ { "fallbackport", oFallbackPort },
- { "sessiontype", oSessionType },
- { "stdinnull", oStdinNull },
- { "forkafterauthentication", oForkAfterAuthentication },
-@@ -334,6 +351,8 @@
- { "proxyjump", oProxyJump },
- { "securitykeyprovider", oSecurityKeyProvider },
- { "knownhostscommand", oKnownHostsCommand },
-+ { "tcprcvbufpoll", oTcpRcvBufPoll },
-+ { "hpndisabled", oHPNDisabled },
- { "requiredrsasize", oRequiredRSASize },
- { "enableescapecommandline", oEnableEscapeCommandline },
- { "obscurekeystroketiming", oObscureKeystrokeTiming },
-@@ -534,7 +553,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;
- }
-@@ -1384,6 +1403,75 @@
- intptr = &options->check_host_ip;
- goto parse_flag;
-
-+ case oHPNDisabled:
-+ intptr = &options->hpn_disabled;
-+ goto parse_flag;
-+
-+ 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 oUseMPTCP:
-+ intptr = &options->use_mptcp;
-+ goto parse_flag;
-+
-+ case oHappyEyes:
-+ intptr = &options->use_happyeyes;
-+ goto parse_flag;
-+
-+ case oHappyDelay:
-+ intptr = &options->happy_delay;
-+ goto parse_int;
-+
-+ case oDisableMTAES:
-+ intptr = &options->disable_multithreaded;
-+ 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;
-@@ -2858,6 +2946,20 @@
- 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->use_mptcp = -1;
-+ options->use_happyeyes = -1;
-+ options->happy_delay = -1;
-+ options->disable_multithreaded = -1;
-+ options->metrics = -1;
-+ options->metrics_path = NULL;
-+ options->metrics_interval = -1;
-+ options->hpn_disabled = -1;
-+ options->fallback = -1;
-+ options->fallback_port = -1;
-+ options->tcp_rcv_buf_poll = -1;
- options->session_type = -1;
- options->stdin_null = -1;
- options->fork_after_authentication = -1;
-@@ -3040,8 +3142,47 @@
- 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->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->use_mptcp == -1)
-+ options->use_mptcp = 0;
-+ if (options->use_happyeyes == -1)
-+ options->use_happyeyes = 0;
-+ /* if the user tries to set the delay to 0 then in just loops forever
-+ * so instead of using the standard -1 test we use < 1 to make sure the
-+ * user isn't being too clever for their own good
-+ */
-+ if (options->happy_delay < 1)
-+ options->happy_delay = 250; /* default 250ms as per RFC 8305 Section 5 */
-+ if (options->disable_multithreaded == -1)
-+ options->disable_multithreaded = 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;
-@@ -3058,10 +3199,22 @@
- options->permit_local_command = 0;
- if (options->visual_host_key == -1)
- options->visual_host_key = 0;
-- if (options->ip_qos_interactive == -1)
-+ /* in the event we are using RFC 8305 then we
-+ * need to override the default QOS as these
-+ * interfere with the connection process in our
-+ * test environment. I don't know if it has a real world
-+ * impact but TODO try to find a real world way to test this.
-+ */
-+ if (options->ip_qos_interactive == -1) {
- options->ip_qos_interactive = IPTOS_DSCP_EF;
-- if (options->ip_qos_bulk == -1)
-+ if (options->use_happyeyes == 1)
-+ options->ip_qos_interactive = IPTOS_LOWDELAY;
-+ }
-+ if (options->ip_qos_bulk == -1) {
- options->ip_qos_bulk = IPTOS_DSCP_CS0;
-+ if (options->use_happyeyes == 1)
-+ options->ip_qos_bulk = IPTOS_THROUGHPUT;
-+ }
- if (options->request_tty == -1)
- options->request_tty = REQUEST_TTY_AUTO;
- if (options->session_type == -1)
-@@ -3870,6 +4023,15 @@
- dump_cfg_fmtint(oVisualHostKey, o->visual_host_key);
- dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys);
- dump_cfg_fmtint(oEnableEscapeCommandline, o->enable_escape_commandline);
-+ dump_cfg_fmtint(oTcpRcvBufPoll, o->tcp_rcv_buf_poll);
-+ dump_cfg_fmtint(oHPNDisabled, o->hpn_disabled);
-+ dump_cfg_fmtint(oNoneSwitch, o->none_switch);
-+ dump_cfg_fmtint(oNoneEnabled, o->none_enabled);
-+ dump_cfg_fmtint(oNoneMacEnabled, o->nonemac_enabled);
-+ dump_cfg_fmtint(oFallback, o->fallback);
-+ dump_cfg_fmtint(oMetrics, o->metrics);
-+ dump_cfg_fmtint(oUseMPTCP, o->use_mptcp);
-+ dump_cfg_fmtint(oHappyEyes, o->use_happyeyes);
- dump_cfg_fmtint(oWarnWeakCrypto, o->warn_weak_crypto);
-
- /* Integer options */
-@@ -3882,6 +4044,9 @@
- dump_cfg_int(oRequiredRSASize, o->required_rsa_size);
- dump_cfg_int(oObscureKeystrokeTiming,
- o->obscure_keystroke_timing_interval);
-+ dump_cfg_int(oMetricsInterval, o->metrics_interval);
-+ dump_cfg_int(oFallbackPort, o->fallback_port);
-+ dump_cfg_int(oHappyDelay, o->happy_delay);
-
- /* String options */
- dump_cfg_string(oBindAddress, o->bind_address);
-@@ -3909,6 +4074,7 @@
- dump_cfg_string(oXAuthLocation, o->xauth_location);
- dump_cfg_string(oKnownHostsCommand, o->known_hosts_command);
- dump_cfg_string(oTag, o->tag);
-+ dump_cfg_string(oMetricsPath, o->metrics_path);
- dump_cfg_string(oVersionAddendum, o->version_addendum);
-
- /* Forwards */
-diff -Nur openssh-10.3p1.orig/readconf.h openssh-10.3p1/readconf.h
---- openssh-10.3p1.orig/readconf.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/readconf.h 2026-07-02 00:27:29.729176809 +0200
-@@ -129,6 +129,23 @@
- int enable_ssh_keysign;
- int64_t rekey_limit;
- int rekey_interval;
-+
-+ /* hpnssh options */
-+ int tcp_rcv_buf_poll; /* Option to poll recv buf every window transfer */
-+ int hpn_disabled; /* Switch to disable HPN buffer management */
-+ 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 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 use_mptcp; /* use MultiPath TCP */
-+ int use_happyeyes; /* use RFC 8305 - Happy Eyeballs */
-+ int happy_delay; /* user defined dleay for RFC 8305 */
-+
- int no_host_authentication_for_localhost;
- int identities_only;
- int server_alive_interval;
-diff -Nur openssh-10.3p1.orig/README.md openssh-10.3p1/README.md
---- openssh-10.3p1.orig/README.md 2026-07-02 00:27:06.089394280 +0200
-+++ openssh-10.3p1/README.md 2026-07-02 08:46:24.820019588 +0200
-@@ -1,19 +1,23 @@
--# Portable OpenSSH
-+# HPNSSH: Based on Portable OpenSSH
-+HPN-SSH is a high performance soft fork of OpenSSH that can provide significantly faster throughput for bulk data transfers over a wide range of network paths. In some situations we've seen throughput rates more than 100 times faster than OpenSSH. HPN-SSH is able to do this by optimizing the application layer receive buffer to match the TCP receive buffer. Notably, to see performance improvements HPN-SSH only needs to be the data receiver so users can see notable improvements with many other SSH implementations. HPN-SSH also incorporates two parallelized ciphers, AES-CTR and Chacha20 (the default). When using these ciphers a throughput performance increase of 30% is typical. More information on how we do this work and other features of HPN-SSH is available from [https://hpnssh.org](https://hpnssh.org).
-
--[](../../actions/workflows/c-cpp.yml)
--[](../../actions/workflows/vm.yml)
--[](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml)
--[](../../actions/workflows/cifuzz.yml)
--[](https://issues.oss-fuzz.com/issues?q="Project:+openssh"+is:open)
--[](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
-+
-+Current information about release features are available in the HPN-README document.
-
- ## Documentation
-
--The official documentation for OpenSSH are the man pages for each tool:
-+The official documentation for HPN-SSH are the man pages for each tool.
-
- * [ssh(1)](https://man.openbsd.org/ssh.1)
- * [sshd(8)](https://man.openbsd.org/sshd.8)
-@@ -24,17 +28,18 @@
- * [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.
-
--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 one of [LibreSSL](https://www.libressl.org/), [OpenSSL](https://www.openssl.org), [AWS-LC](https://github.com/aws/aws-lc) or [BoringSSL](https://github.com/google/boringssl) 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.
-+``libcrypto`` from one of [LibreSSL](https://www.libressl.org/), [OpenSSL](https://www.openssl.org), [AWS-LC](https://github.com/aws/aws-lc) or [BoringSSL](https://github.com/google/boringssl) may also be used. HPN-SSH may be built without either of these, but the resulting binaries will have only a subset of the cryptographic algorithms normally available.
-+The developers of HPN-SSH strongly suggest the use of OpenSSL 3.0 or higher to support the advanced cryptography methods used.
-
- [zlib](https://www.zlib.net/) is optional; without it transport compression is not supported.
-
-@@ -47,8 +52,9 @@
- Release tarballs and release branches in git 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 hpn-ssh
-+autoreconf -f -i
- ./configure # [options]
- make && make tests
- ```
-@@ -57,19 +63,19 @@
-
- ### Building from git
-
--If building from the git master branch, 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:
-+If building from the git master branch, you'll need [autoconf](https://www.gnu.org/software/autoconf/) installed to build the ``configure`` script. The following commands will check out and build HPN-SSH from git:
-
- ```
--git clone https://github.com/openssh/openssh-portable # or https://anongit.mindrot.org/openssh.git
--cd openssh-portable
--autoreconf
-+git clone https://github.com/rapier1/hpn-ssh
-+cd hpn-ssh
-+autoreconf -f -i
- ./configure
- make && make tests
- ```
-
- ### Build-time Customisation
-
--There are many build-time customisation options available. All Autoconf destination path flags (e.g. ``--prefix``) are supported (and are usually required if you want to install OpenSSH).
-+There are many build-time customisation options available. All Autoconf destination path flags (e.g. ``--prefix``) are supported (and are usually required if you want to install HPN-SSH).
-
- For a full list of available flags, run ``./configure --help`` but a few of the more frequently-used ones are described below. Some of these flags will require additional libraries and/or headers be installed.
-
-@@ -79,11 +85,13 @@
- ``--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
-
- Portable OpenSSH development is discussed on the [openssh-unix-dev mailing list](https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev) ([archive mirror](https://marc.info/?l=openssh-unix-dev)). Bugs and feature requests are tracked on our [Bugzilla](https://bugzilla.mindrot.org/).
-+HPN-SSH development is discussed on the [hpnssh-community mailing list](https://lists.psc.edu/mailman/listinfo/hpnssh-community) and on the [GitHub Discussions page](https://github.com/rapier1/hpn-ssh/discussions).
-
- ## Reporting bugs
-
--_Non-security_ bugs may be reported to the developers via [Bugzilla](https://bugzilla.mindrot.org/) or via the mailing list above. Security bugs should be reported to [openssh@openssh.com](mailto:openssh.openssh.com).
-+_Non-security_ bugs may be reported to the developers via [GitHub Issues](https://github.com/rapier1/hpn-ssh/issues) or via the mailing list above. Security bugs should be reported to [hpnssh@psc.edu](mailto:hpnssh@psc.edu).
-diff -Nur openssh-10.3p1.orig/sandbox-seccomp-filter.c openssh-10.3p1/sandbox-seccomp-filter.c
---- openssh-10.3p1.orig/sandbox-seccomp-filter.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/sandbox-seccomp-filter.c 2026-07-02 00:27:29.729712548 +0200
-@@ -323,6 +323,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
-@@ -444,6 +447,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-10.3p1.orig/scp.1 openssh-10.3p1/scp.1
---- openssh-10.3p1.orig/scp.1 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/scp.1 2026-07-02 09:18:21.011517375 +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
-@@ -33,6 +33,8 @@
- .Sh DESCRIPTION
- .Nm
- copies files between hosts on a network.
-+It is binary compatible with OpenSSH's scp including
-+the use of the same directives and configuration options except where noted.
- .Pp
- .Nm
- uses the SFTP protocol over an
-@@ -258,6 +260,7 @@
- .It Tunnel
- .It TunnelDevice
- .It UpdateHostKeys
-+.It UseMPTCP
- .It User
- .It UserKnownHostsFile
- .It VerifyHostKeyDNS
-@@ -294,6 +297,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
-@@ -301,6 +308,14 @@
- The program must understand
- .Xr ssh 1
- options.
-+.It Fl z Ar program
-+Path to hpnscp on remote system. Useful if remote has multiple scp installs.
-+For example, using the resume option but the default remote scp does not have the resume option.
-+Use -z to point the version that does - e.g. -z /opt/hpnssh/bin/hpnscp.
-+.Nm
-+only option.
-+.It Fl s
-+Use the SFTP protocol for transfers rather than the original scp protocol.
- .It Fl T
- Disable strict filename checking.
- By default when copying files from a remote host to a local directory
-@@ -327,10 +342,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
-@@ -364,6 +382,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-10.3p1.orig/scp.c openssh-10.3p1/scp.c
---- openssh-10.3p1.orig/scp.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/scp.c 2026-07-02 10:16:16.327279575 +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
-@@ -113,6 +114,12 @@
- #include "misc.h"
- #include "progressmeter.h"
- #include "utf8.h"
-+/* libressl doesn't support the blake2b512 digest so
-+ * we need to prevent libressl from using the resume feature
-+ * cjr 7/18/2023 */
-+#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
-+#include <openssl/evp.h>
-+#endif
- #include "sftp.h"
-
- #include "sftp-common.h"
-@@ -155,6 +162,10 @@
- /* This is the program to execute for the secure 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;
-@@ -169,6 +180,17 @@
- int sftp_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)
- {
-@@ -216,6 +238,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");
-
-@@ -441,6 +466,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 *);
-@@ -459,11 +486,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));
-@@ -488,7 +522,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':
-@@ -569,24 +603,36 @@
- addargs(&remote_remote_args, "-q");
- showprogress = 0;
- break;
-+#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
-+ 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;
-@@ -674,11 +720,19 @@
- 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",
-+ /* 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" : "");
--
-+ 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. */
-@@ -688,6 +742,7 @@
- verifydir(argv[argc - 1]);
- tolocal(argc, argv, mode, sftp_direct); /* Dest is local host. */
- }
-+
- /*
- * Finally check the exit status of the ssh process, if one was forked
- * and no error has occurred yet
-@@ -1309,6 +1364,74 @@
- sftp_free(conn);
- }
-
-+/* 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
-+ * Note: LibreSSL doesn't support blake2b512 so we can't offer them
-+ * the resume feature cjr 7/18/2023 */
-+#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
-+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 or are using libressl */
-+}
-+#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)
-@@ -1396,14 +1519,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] == '/')
-@@ -1425,6 +1557,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) {
-@@ -1446,14 +1586,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);
-@@ -1461,13 +1720,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) {
-@@ -1668,24 +1931,36 @@
- 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");
-
-@@ -1703,9 +1978,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
-@@ -1714,7 +1996,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;
-@@ -1742,6 +2029,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;
- }
-@@ -1749,6 +2039,9 @@
- *--cp = 0;
-
- cp = buf;
-+#ifdef DEBUG
-+ fprintf(stderr, "%s: buf is %s\n", hostname, buf);
-+#endif
- if (*cp == 'T') {
- setimes++;
- cp++;
-@@ -1776,9 +2069,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".
-@@ -1794,6 +2097,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");
-@@ -1804,6 +2119,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);
-@@ -1813,11 +2132,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 ||
-@@ -1887,11 +2227,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;
-@@ -1906,13 +2430,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);
-@@ -1947,8 +2475,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
-@@ -1983,8 +2581,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++)
-@@ -2128,11 +2735,19 @@
- void
- usage(void)
- {
-+#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
-+ (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
-@@ -2271,6 +2886,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-10.3p1.orig/servconf.c openssh-10.3p1/servconf.c
---- openssh-10.3p1.orig/servconf.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/servconf.c 2026-07-02 01:19:47.039496456 +0200
-@@ -69,6 +69,7 @@
- #include "auth.h"
- #include "myproposal.h"
- #include "digest.h"
-+#include "sshbuf.h"
- #include "version.h"
- #include "ssh-gss.h"
-
-@@ -222,6 +223,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->none_enabled = -1;
-+ options->nonemac_enabled = -1;
-+ options->use_mptcp = -1;
-+ options->disable_multithreaded = -1;
- options->ip_qos_interactive = -1;
- options->ip_qos_bulk = -1;
- options->version_addendum = NULL;
-@@ -516,6 +523,22 @@
- }
- 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->tcp_rcv_buf_poll == -1)
-+ options->tcp_rcv_buf_poll = 1;
-+ if (options->disable_multithreaded == -1)
-+ options->disable_multithreaded = 0;
-+ if (options->hpn_disabled == -1)
-+ options->hpn_disabled = 0;
-+ if (options->use_mptcp == -1)
-+ options->use_mptcp = 0;
- if (options->ip_qos_interactive == -1)
- options->ip_qos_interactive = IPTOS_DSCP_EF;
- if (options->ip_qos_bulk == -1)
-@@ -602,6 +625,8 @@
- sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication,
- sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- sPrintMotd, sPrintLastLog, sIgnoreRhosts,
-+ sNoneEnabled, sNoneMacEnabled, sTcpRcvBufPoll, sHPNDisabled,
-+ sDisableMTAES, sUseMPTCP,
- sX11Forwarding, sX11DisplayOffset, sX11MaxDisplays, sX11UseLocalhost,
- sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive,
- sPermitUserEnvironment, sAllowTcpForwarding, sCompression,
-@@ -815,6 +840,12 @@
- { "revokedkeys", sRevokedKeys, SSHCFG_ALL },
- { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
- { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL },
-+ { "hpndisabled", sHPNDisabled, SSHCFG_ALL },
-+ { "tcprcvbufpoll", sTcpRcvBufPoll, SSHCFG_ALL },
-+ { "noneenabled", sNoneEnabled, SSHCFG_ALL },
-+ { "nonemacenabled", sNoneMacEnabled, SSHCFG_ALL },
-+ { "usemptcp", sUseMPTCP, SSHCFG_GLOBAL },
-+ { "disableMTAES", sDisableMTAES, SSHCFG_ALL },
- { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL },
- { "include", sInclude, SSHCFG_ALL },
- { "ipqos", sIPQoS, SSHCFG_ALL },
-@@ -879,6 +910,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;
- }
-@@ -1652,6 +1684,30 @@
- 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 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 sUseMPTCP:
-+ intptr = &options->use_mptcp;
-+ goto parse_flag;
-+
- case sIgnoreUserKnownHosts:
- intptr = &options->ignore_user_known_hosts;
- parse_flag:
-@@ -3526,6 +3582,11 @@
- dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
- dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash);
- dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info);
-+ dump_cfg_fmtint(sHPNDisabled, o->hpn_disabled);
-+ dump_cfg_fmtint(sTcpRcvBufPoll, o->tcp_rcv_buf_poll);
-+ dump_cfg_fmtint(sNoneEnabled, o->none_enabled);
-+ dump_cfg_fmtint(sNoneMacEnabled, o->nonemac_enabled);
-+ dump_cfg_fmtint(sUseMPTCP, o->use_mptcp);
- dump_cfg_fmtint(sRefuseConnection, o->refuse_connection);
-
- /* string arguments */
-diff -Nur openssh-10.3p1.orig/servconf.h openssh-10.3p1/servconf.h
---- openssh-10.3p1.orig/servconf.h 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/servconf.h 2026-07-02 00:27:29.730557630 +0200
-@@ -231,6 +231,14 @@
- char *pam_service_name;
- int permit_pam_user_change; /* Allow PAM to change user name */
-
-+ /* hpnssh options */
-+ int tcp_rcv_buf_poll; /* poll tcp rcv window in autotuning kernels*/
-+ int hpn_disabled; /* disable hpn functionality. false by default */
-+ int none_enabled; /* Enable NONE cipher switch */
-+ int nonemac_enabled; /* Enable NONE MAC switch */
-+ int use_mptcp; /* Use MPTCP - Linux only */
-+ int disable_multithreaded; /* Disable multithreaded aes-ctr cipher */
-+
- int permit_tun;
-
- char **permitted_opens; /* May also be one of PERMITOPEN_* */
-diff -Nur openssh-10.3p1.orig/serverloop.c openssh-10.3p1/serverloop.c
---- openssh-10.3p1.orig/serverloop.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/serverloop.c 2026-07-02 10:11:34.634516985 +0200
-@@ -77,6 +77,8 @@
- #include "serverloop.h"
- #include "ssherr.h"
- #include "compat.h"
-+#include "metrics.h"
-+#include "cipher-switch.h"
-
- extern ServerOptions options;
-
-@@ -341,6 +343,7 @@
- sigset_t bsigset, osigset;
-
- debug("Entering interactive session for SSH2.");
-+ ssh->start_time = monotime_double();
-
- if (sigemptyset(&bsigset) == -1 ||
- sigaddset(&bsigset, SIGCHLD) == -1)
-@@ -395,9 +398,15 @@
- collect_children(ssh);
- free(pfd);
-
-+ /* write final log entry (do we need this here? -cjr)*/
-+ 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);
- }
-@@ -599,6 +608,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);
-@@ -667,6 +678,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)
- {
-@@ -843,6 +915,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-10.3p1.orig/session.c openssh-10.3p1/session.c
---- openssh-10.3p1.orig/session.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/session.c 2026-07-02 00:27:29.730941735 +0200
-@@ -90,6 +90,7 @@
- #include "monitor_wrap.h"
- #include "sftp.h"
- #include "atomicio.h"
-+#include "cipher-switch.h"
-
- #if defined(KRB5) && defined(USE_AFS)
- #include <kafs.h>
-@@ -212,6 +213,7 @@
- restore_uid();
-
- /* 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,
-@@ -375,6 +377,7 @@
- do_exec_no_pty(struct ssh *ssh, Session *s, const char *command)
- {
- pid_t pid;
-+ int fips = 0;
- #ifdef USE_PIPES
- int pin[2], pout[2], perr[2];
-
-@@ -530,6 +533,17 @@
- session_set_fds(ssh, s, inout[1], inout[1], err[1],
- s->is_subsystem, 0);
- #endif
-+ /* switch to the parallel ciphers if necessary
-+ * If FIPS mode exists and is enabled then no parallel ciphers.
-+ */
-+ fips = fips_enabled();
-+ if (fips)
-+ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
-+ else
-+ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
-+
-+ if ((options.disable_multithreaded == 0) && (fips == 0))
-+ cipher_switch(ssh);
- return 0;
- }
-
-@@ -544,6 +558,7 @@
- {
- int fdout, ptyfd, ttyfd, ptymaster;
- pid_t pid;
-+ int fips = 0;
-
- if (s == NULL)
- fatal("do_exec_pty: no session");
-@@ -641,6 +656,17 @@
- /* Enter interactive session. */
- s->ptymaster = ptymaster;
- session_set_fds(ssh, s, ptyfd, fdout, -1, 1, 1);
-+ /* switch to the parallel cipher if appropriate
-+ * If FIPS mode exists and is enabled then no parallel ciphers.
-+ */
-+ fips = fips_enabled();
-+ if (fips)
-+ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
-+ else
-+ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
-+
-+ if ((options.disable_multithreaded == 0) && (fips == 0))
-+ cipher_switch(ssh);
- return 0;
- }
-
-diff -Nur openssh-10.3p1.orig/sftp.1 openssh-10.3p1/sftp.1
---- openssh-10.3p1.orig/sftp.1 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sftp.1 2026-07-02 00:27:29.731120176 +0200
-@@ -55,6 +55,9 @@
- transport.
- It may also use many features of ssh, such as public key authentication and
- compression.
-+.Nm
-+is binary compatible with OpenSSH's sftp and uses the same directive and configuration
-+options except where noted.
- .Pp
- The
- .Ar destination
-@@ -319,6 +322,7 @@
- .It Tunnel
- .It TunnelDevice
- .It UpdateHostKeys
-+.It UseMPTCP
- .It User
- .It UserKnownHostsFile
- .It VerifyHostKeyDNS
-@@ -338,7 +342,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
-@@ -367,10 +372,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-10.3p1.orig/sftp.c openssh-10.3p1/sftp.c
---- openssh-10.3p1.orig/sftp.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sftp.c 2026-07-02 10:19:16.035128109 +0200
-@@ -2589,20 +2589,25 @@
- /* Please keep in sync with ssh.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-10.3p1.orig/sftp-client.c openssh-10.3p1/sftp-client.c
---- openssh-10.3p1.orig/sftp-client.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sftp-client.c 2026-07-02 00:27:29.731855001 +0200
-@@ -60,7 +60,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-10.3p1.orig/ssh.1 openssh-10.3p1/ssh.1
---- openssh-10.3p1.orig/ssh.1 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/ssh.1 2026-07-02 01:45:20.691931533 +0200
-@@ -77,6 +77,14 @@
- X11 connections, arbitrary TCP ports and
- .Ux Ns -domain
- sockets can also be forwarded over the secure channel.
-+.Nm
-+is binary compatible with the more well known OpenSSH and uses the same configuration
-+directives, methods, and keywords except where noted with the exception of the default port.
-+HPN-SSH servers, by default, use port 2222 for connection, and as such,
-+.Nm
-+clients
-+attempt to connect on that port. If the connection attempt on port 2222 fails it will fallback to
-+port 22. Please see the -p switch for more information.
- .Pp
- .Nm
- connects and logs into the specified
-@@ -531,11 +539,13 @@
- .It ControlMaster
- .It ControlPath
- .It ControlPersist
-+.It DisableMTAES*
- .It DynamicForward
- .It EnableEscapeCommandline
- .It EnableSSHKeysign
- .It EscapeChar
- .It ExitOnForwardFailure
-+.It FallbackPort
- .It FingerprintHash
- .It ForkAfterAuthentication
- .It ForwardAgent
-@@ -548,6 +558,8 @@
- .It GSSAPIDelegateCredentials
- .It GatewayPorts
- .It GlobalKnownHostsFile
-+.It HappyEyes
-+.It HappyDelay
- .It GSSAPIKexAlgorithms
- .It GSSAPIRenewalForcesRekey
- .It GSSAPIServerIdentity
-@@ -559,6 +571,7 @@
- .It HostbasedAcceptedAlgorithms
- .It HostbasedAuthentication
- .It Hostname
-+.It HPNDisabled*
- .It IPQoS
- .It IdentitiesOnly
- .It IdentityAgent
-@@ -574,7 +587,13 @@
- .It LogLevel
- .It LogVerbose
- .It MACs
-+.It Metrics
-+.It MetricsInterval
-+.It MetricsPath
- .It NoHostAuthenticationForLocalhost
-+.It NoneCipherEnabled*
-+.It NoneEnabled*
-+.It NoneMacEnabled*
- .It NumberOfPasswordPrompts
- .It ObscureKeystrokeTiming
- .It PKCS11Provider
-@@ -606,15 +625,19 @@
- .It StrictHostKeyChecking
- .It SyslogFacility
- .It TCPKeepAlive
-+.It TcpRcvBufPoll*
- .It Tag
- .It Tunnel
- .It TunnelDevice
- .It UpdateHostKeys
-+.It UseMPTCP
- .It User
- .It UserKnownHostsFile
- .It VerifyHostKeyDNS
- .It VisualHostKey
- .It XAuthLocation
-+.Pp
-+.It * Hpnssh specific configuration option.
- .El
- .Pp
- .It Fl P Ar tag
-@@ -631,6 +654,13 @@
- Port to connect to on the remote host.
- This can be specified on a
- per-host basis in the configuration file.
-+HPN-SSH uses a default port of 2222. It will automatically
-+fallback to use the SSH standard port 22 if it cannot connect
-+on port 2222. This fallback behaviour can be modified with the
-+FallbackPort option. Note: if outbound port 2222 is
-+blocked it may appear that the hpnssh client is non-responsive. In that event,
-+either specify the correct port or use the ConnectTimeout option
-+to trigger the port fallback more quickly.
- .Pp
- .It Fl Q Ar query_option
- Queries for the algorithms supported by one of the following features:
-@@ -1824,3 +1854,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-10.3p1.orig/ssh_api.c openssh-10.3p1/ssh_api.c
---- openssh-10.3p1.orig/ssh_api.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/ssh_api.c 2026-07-02 00:27:29.732565032 +0200
-@@ -431,7 +431,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-10.3p1.orig/sshbuf.c openssh-10.3p1/sshbuf.c
---- openssh-10.3p1.orig/sshbuf.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sshbuf.c 2026-07-02 00:27:29.732672729 +0200
-@@ -28,6 +28,8 @@
- #include "sshbuf.h"
- #include "misc.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", \
-@@ -49,8 +51,29 @@
- 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 */
-+ int type; /* type of buffer enum (sshbuf_types)*/
- };
-
-+/* update the label string for a given sshbuf. Useful
-+ * for debugging */
-+void
-+sshbuf_relabel(struct sshbuf *buf, const char *label)
-+{
-+ if (label != NULL)
-+ strncpy(buf->label, label, MAX_LABEL_LEN-1);
-+}
-+
-+/* set the type (from enum sshbuf_type) of the given sshbuf.
-+ * The purpose is to allow different classes of buffers to
-+ * follow different code paths if necessary */
-+void
-+sshbuf_type(struct sshbuf *buf, int type)
-+{
-+ if (type < BUF_MAX_TYPE)
-+ buf->type = type;
-+}
-+
- static inline int
- sshbuf_check_sanity(const struct sshbuf *buf)
- {
-@@ -90,7 +113,7 @@
- }
-
- struct sshbuf *
--sshbuf_new(void)
-+sshbuf_new_label (const char *label)
- {
- struct sshbuf *ret;
-
-@@ -101,6 +124,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;
-@@ -290,7 +315,21 @@
- {
- if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
- return 0;
-- return buf->max_size - (buf->size - buf->off);
-+ /* Reserve ~4.76% (1/21) headroom on channel input buffers to prevent
-+ * a pathological state during bulk data transfers. The headroom scales
-+ * with buffer size and gives the channel time to drain before accepting
-+ * more data, smoothing flow control and reducing buffer reallocation.
-+ * When the buffer is fuller than the headroom allows we must return 0,
-+ * not underflow: size_t is unsigned so a naive subtraction wraps to
-+ * SIZE_MAX, causing the caller to read into a nearly-full buffer and
-+ * thrash window updates. Only applied to channel input buffers as
-+ * other buffer types fail the regression unit tests with headroom. */
-+ if (buf->type == BUF_CHANNEL_INPUT) {
-+ size_t headroom = buf->max_size - buf->max_size / 21;
-+ size_t used = buf->size - buf->off;
-+ return (used >= headroom) ? 0 : headroom - used;
-+ } else
-+ return buf->max_size - (buf->size - buf->off);
- }
-
- const u_char *
-@@ -350,9 +389,36 @@
- */
- 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 by 4MB at a time. This can result in
-+ * the buffer being over allocated (in terms of actual needs) but the
-+ * process is fast. 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 explcitly check that the buffer is being used for inbound outbound
-+ * channel buffering.
-+ * Updated for 18.4.1 -cjr 04/20/24
-+ */
-+ if (rlen > BUF_WATERSHED && (buf->type == BUF_CHANNEL_OUTPUT || buf->type == BUF_CHANNEL_INPUT)) {
-+ /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu max_size is %zu",
-+ buf->label, buf, rlen, need, 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 max_size is %zu", */
-+ /* buf->label, buf, rlen, need, buf->max_size); */
-+ }
- SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
-+
-+ /* rlen might be above the max allocation */
- if (rlen > buf->max_size)
-- rlen = buf->alloc + need;
-+ rlen = 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-10.3p1.orig/sshbuf.h openssh-10.3p1/sshbuf.h
---- openssh-10.3p1.orig/sshbuf.h 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sshbuf.h 2026-07-02 09:40:01.778044812 +0200
-@@ -30,18 +30,49 @@
- # endif /* OPENSSL_HAS_ECC */
- #endif /* WITH_OPENSSL */
-
--#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size */
-+#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size 128MB */
- #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;
-
-+enum buffer_types {
-+ BUF_CHANNEL_OUTPUT,
-+ BUF_CHANNEL_INPUT,
-+ BUF_CHANNEL_EXTENDED,
-+ BUF_PACKET_INPUT,
-+ BUF_PACKET_INCOMING,
-+ BUF_PACKET_OUTPUT,
-+ BUF_PACKET_OUTGOING,
-+ BUF_MAX_TYPE
-+};
-+
- /*
- * 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 *);
-+
-+/*
-+ * assign a type (from the buffer_types enum) to
-+ * the buffer. Used to quickly identify the purpose of
-+ * the buffer.
-+ */
-+void sshbuf_type(struct sshbuf *, int);
-
- /*
- * Create a new, read-only sshbuf buffer from existing data.
-@@ -78,12 +109,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);
-@@ -381,6 +413,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-10.3p1.orig/ssh.c openssh-10.3p1/ssh.c
---- openssh-10.3p1.orig/ssh.c 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/ssh.c 2026-07-02 11:22:05.869353902 +0200
-@@ -98,6 +98,7 @@
- #include "ssherr.h"
- #include "utf8.h"
- #include "hostfile.h"
-+#include "cipher-switch.h"
-
- #ifdef ENABLE_PKCS11
- #include "ssh-pkcs11.h"
-@@ -1094,6 +1095,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);
-@@ -1739,10 +1744,36 @@
- }
-
- /* 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);
-+ }
-
-
- ssh_packet_set_timeout(ssh, options.server_alive_interval,
-@@ -1945,8 +1976,10 @@
-
- /* Do fork() after authentication. Used by "ssh -f" */
- static void
--fork_postauth(void)
-+fork_postauth(struct ssh *ssh)
- {
-+ int fips = 0;
-+
- if (need_controlpersist_detach)
- control_persist_detach();
- debug("forking to background");
-@@ -1955,17 +1988,29 @@
- fatal("daemon() failed: %.200s", strerror(errno));
- if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1)
- error_f("stdfd_devnull failed");
-+ /* we do the cipher switch here in the event that the client
-+ * is forking or has a delayed fork.
-+ * If FIPS mode exists and is enabled then no parallel ciphers.
-+ */
-+ fips = fips_enabled();
-+ if (fips)
-+ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
-+ else
-+ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
-+
-+ if ((options.disable_multithreaded == 0) && (fips == 0))
-+ cipher_switch(ssh);
- }
-
- static void
--forwarding_success(void)
-+forwarding_success(struct ssh *ssh)
- {
- if (forward_confirms_pending == -1)
- return;
- if (--forward_confirms_pending == 0) {
- debug_f("all expected forwarding replies received");
- if (options.fork_after_authentication)
-- fork_postauth();
-+ fork_postauth(ssh);
- } else {
- debug2_f("%d expected forwarding replies remaining",
- forward_confirms_pending);
-@@ -2032,7 +2077,7 @@
- "for listen port %d", rfwd->listen_port);
- }
- }
-- forwarding_success();
-+ forwarding_success(ssh);
- }
-
- static void
-@@ -2059,7 +2104,7 @@
- }
-
- debug_f("tunnel forward established, id=%d", id);
-- forwarding_success();
-+ forwarding_success(ssh);
- }
-
- static void
-@@ -2250,6 +2295,15 @@
- NULL, fileno(stdin), command, environ);
- }
-
-+/* this used to do a lot more but now it just checks to see
-+ * if we are disabling hpn */
-+static void
-+hpn_options_init(struct ssh *ssh)
-+{
-+ channel_set_hpn_disabled(options.hpn_disabled);
-+ debug_f("HPN disabled: %d", options.hpn_disabled);
-+}
-+
- /* open new channel for a session */
- static int
- ssh_session2_open(struct ssh *ssh)
-@@ -2271,6 +2325,7 @@
- window = CHAN_SES_WINDOW_DEFAULT;
- packetmax = CHAN_SES_PACKET_DEFAULT;
- if (tty_flag) {
-+ window = CHAN_SES_WINDOW_DEFAULT;
- window >>= 1;
- packetmax >>= 1;
- }
-@@ -2278,6 +2333,13 @@
- "session", SSH_CHANNEL_OPENING, in, out, err,
- 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 (tty_flag)
- channel_set_tty(ssh, c);
- debug3_f("channel_new: %d%s", c->self, tty_flag ? " (tty)" : "");
-@@ -2295,6 +2357,14 @@
- {
- int r, id = -1;
- char *cp, *tun_fwd_ifname = NULL;
-+ int fips = 0;
-+
-+ /*
-+ * 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)
-@@ -2386,7 +2456,21 @@
- debug("deferring postauth fork until remote forward "
- "confirmation received");
- } else
-- fork_postauth();
-+ fork_postauth(ssh);
-+ } else {
-+ /* check to see if we are switching ciphers to
-+ * one of our parallel versions. If the client is
-+ * forking then we handle it in fork_postauth()
-+ * If FIPS mode exists and is enabled then no parallel ciphers.
-+ */
-+ fips = fips_enabled();
-+ if (fips)
-+ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
-+ else
-+ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
-+ if ((options.disable_multithreaded == 0)
-+ && (fips == 0))
-+ cipher_switch(ssh);
- }
-
- return client_loop(ssh, tty_flag, tty_flag ?
-diff -Nur openssh-10.3p1.orig/ssh_config openssh-10.3p1/ssh_config
---- openssh-10.3p1.orig/ssh_config 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/ssh_config 2026-07-02 02:12:43.373829710 +0200
-@@ -45,6 +45,8 @@
- # ProxyCommand ssh -q -W %h:%p gateway.example.com
- # RekeyLimit 1G 1h
- # UserKnownHostsFile ~/.ssh/known_hosts.d/%k
-+# UseMPTCP no
-+# HappyEyes no
- #
- # This system is following system-wide crypto policy.
- # To modify the crypto properties (Ciphers, MACs, ...), create a *.conf
-diff -Nur openssh-10.3p1.orig/ssh_config.5 openssh-10.3p1/ssh_config.5
---- openssh-10.3p1.orig/ssh_config.5 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/ssh_config.5 2026-07-02 00:27:29.733400367 +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
-@@ -833,6 +846,9 @@
- or
- .Cm no
- (the default).
-+.It Cm FallbackPort
-+Specifies the port hpnssh should try to connect to if it fails connecting to the
-+default hpnsshd port of 2222. The default is port 22. HPN-SSH only.
- .It Cm FingerprintHash
- Specifies the hash algorithm used when displaying key fingerprints.
- Valid options are:
-@@ -1058,6 +1074,23 @@
- will not be converted automatically,
- but may be manually hashed using
- .Xr ssh-keygen 1 .
-+.It Cm HappyEyes
-+If set to
-+.Cm yes
-+this option specifies that the client should use the Happy Eyeballs connection
-+method as described by RFC 8305. When attempting to connect to a
-+target that supports IPv4 and IPv6 the client will try to connect
-+on IPv6 first, delay a set time (250ms default), and then try IPv4.
-+The client will use which ever socket is returned first. Note:
-+This process may, rarely, produce false positives in some monitoring tools
-+due to multiple connection attempts prior to authentication.
-+The default is
-+.Cm no
-+.It Cm HappyDelay
-+This options specifies how long the HappyEyes connection method should
-+wait before trying IPv4 after the initial IPv6 connection. This value is
-+a positive integer of milliseconds with a default of
-+.Cm 250ms.
- .It Cm HostbasedAcceptedAlgorithms
- The default is handled system-wide by
- .Xr crypto-policies 7 .
-@@ -1161,6 +1194,11 @@
- .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 IdentitiesOnly
- Specifies that
- .Xr ssh 1
-@@ -1360,6 +1398,12 @@
- .Cm none
- (the operating system default)
- for non-interactive sessions.
-+If using the
-+.Cm HappyEyes
-+option these default to
-+.Cm lowlatency and
-+.m throughput ,
-+respectively.
- .It Cm KbdInteractiveAuthentication
- Specifies whether to use keyboard-interactive authentication.
- The argument to this keyword must be
-@@ -1548,6 +1592,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
-@@ -1555,6 +1656,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.
-@@ -2134,6 +2265,12 @@
- Specify a configuration tag name that may be later used by a
- .Cm Match
- directive to select a block of configuration.
-+.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
-@@ -2219,6 +2356,12 @@
- from OpenSSH 6.8 and greater support the
- .Qq hostkeys@openssh.com
- protocol extension used to inform the client of all the server's hostkeys.
-+.It Cm UseMPTCP
-+If set to
-+.Cm yes ,
-+this will enable Multipath TCP (MPTCP) instead of TCP (this only works on Linux).
-+The default is
-+.Cm no .
- .It Cm User
- Specifies the user to log in as.
- This can be useful when a different user name is used on different machines.
-@@ -2528,3 +2671,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-10.3p1.orig/sshconnect2.c openssh-10.3p1/sshconnect2.c
---- openssh-10.3p1.orig/sshconnect2.c 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/sshconnect2.c 2026-07-02 00:27:29.733649081 +0200
-@@ -74,6 +74,7 @@
- #include "utf8.h"
- #include "ssh-sk.h"
- #include "sk-api.h"
-+#include "cipher-switch.h"
-
- #ifdef GSSAPI
- #include "ssh-gss.h"
-@@ -83,6 +84,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
- */
-
-@@ -579,6 +587,42 @@
-
- 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");
-+ }
-+ }
-+
- 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-10.3p1.orig/sshconnect.c openssh-10.3p1/sshconnect.c
---- openssh-10.3p1.orig/sshconnect.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/sshconnect.c 2026-07-02 10:25:46.549798959 +0200
-@@ -55,6 +55,7 @@
- #include "ssherr.h"
- #include "authfd.h"
- #include "kex.h"
-+#include "happyeyeballs.h"
-
- struct sshkey *previous_host_key = NULL;
-
-@@ -66,6 +67,7 @@
- extern int debug_flag;
- extern Options options;
- extern char *__progname;
-+extern char global_ntop[NI_MAXHOST];
-
- static int show_other_keys(struct hostkeys *, struct sshkey *);
- static void warn_changed_key(struct sshkey *);
-@@ -333,7 +335,7 @@
- /*
- * Creates a socket for use as the ssh connection.
- */
--static int
-+int
- ssh_create_socket(struct addrinfo *ai)
- {
- int sock, r;
-@@ -345,9 +347,16 @@
- #endif
- char ntop[NI_MAXHOST];
-
-- sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-+ /* user request for Multipath TCP */
-+ if (options.use_mptcp)
-+ sock = socket(ai->ai_family, ai->ai_socktype, IPPROTO_MPTCP);
-+ else
-+ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-+
- if (sock == -1) {
- error("socket: %s", strerror(errno));
-+ if (options.use_mptcp)
-+ error ("You asked to use MPTCP. Please ensure it is enabled.");
- return -1;
- }
- (void)fcntl(sock, F_SETFD, FD_CLOEXEC);
-@@ -450,6 +459,15 @@
- sleep(1);
- debug("Trying again...");
- }
-+ if (options.use_happyeyes == 1) {
-+ debug_f ("Attempting RFC 8305 / Happy Eyeballs connection.");
-+ sock = happy_eyeballs(host, aitop,
-+ hostaddr, timeout_ms);
-+ if (sock != -1) {
-+ debug_f ("RFC 8305 / Happy Eyeballs connection successful.");
-+ break; /* Successful connection. */
-+ }
-+ } else {
- /*
- * Loop through addresses for this host, and try each one in
- * sequence until the connection succeeds.
-@@ -504,6 +522,7 @@
- }
- if (sock != -1)
- break; /* Successful connection. */
-+ }
- }
-
- /* Return failure if we didn't get a successful connection. */
-@@ -1615,6 +1634,9 @@
- /* key exchange */
- /* authenticate user */
- debug("Authenticating to %s:%d as '%s'", host, port, server_user);
-+ /* if using RFC 8305 clearly state which address we are connected to */
-+ if (options.use_happyeyes == 1)
-+ debug("RFC 8305 authenticating to %.200s", global_ntop);
- ssh_kex2(ssh, host, hostaddr, port, cinfo);
- if (!options.kex_algorithms_set && ssh->kex != NULL &&
- ssh->kex->name != NULL && options.warn_weak_crypto &&
-diff -Nur openssh-10.3p1.orig/sshd-auth.c openssh-10.3p1/sshd-auth.c
---- openssh-10.3p1.orig/sshd-auth.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/sshd-auth.c 2026-07-02 00:27:29.734113080 +0200
-@@ -798,6 +798,25 @@
- struct kex *kex;
- int r;
-
-+ /* this used to be in sshd.c when we read the configuration file
-+ * but needed to be moved here as do_ssh2_kex in sshd-auth wasn't
-+ * picking up the none options. CJR 4/10/2025
-+ */
-+ if (options.none_enabled == 1) {
-+ debug("WARNING: None cipher enabled");
-+ 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) {
-+ debug("WARNING: None MAC enabled");
-+ char *old_macs = options.macs;
-+ xasprintf(&options.macs, "%s,none", old_macs);
-+ free(old_macs);
-+ }
-+ }
-+
- if (options.rekey_limit || options.rekey_interval)
- ssh_packet_set_rekey_limits(ssh, options.rekey_limit,
- options.rekey_interval);
-diff -Nur openssh-10.3p1.orig/sshd.c openssh-10.3p1/sshd.c
---- openssh-10.3p1.orig/sshd.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/sshd.c 2026-07-02 00:27:29.734298621 +0200
-@@ -826,6 +826,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)
-@@ -841,11 +843,18 @@
- continue;
- }
- /* Create socket for listening. */
-- listen_sock = socket(ai->ai_family, ai->ai_socktype,
-- ai->ai_protocol);
-+ if (options.use_mptcp)
-+ listen_sock = socket(ai->ai_family, ai->ai_socktype,
-+ IPPROTO_MPTCP);
-+ else
-+ listen_sock = socket(ai->ai_family, ai->ai_socktype,
-+ ai->ai_protocol);
-+
- if (listen_sock == -1) {
- /* kernel may not support ipv6 */
- verbose("socket: %.100s", strerror(errno));
-+ if (options.use_mptcp)
-+ verbose("MPTCP requested but may not be available.");
- continue;
- }
- if (set_nonblock(listen_sock) == -1) {
-@@ -871,6 +880,10 @@
-
- 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);
-+
- /* 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.",
-diff -Nur openssh-10.3p1.orig/sshd_config openssh-10.3p1/sshd_config
---- openssh-10.3p1.orig/sshd_config 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/sshd_config 2026-07-02 01:48:29.414988166 +0200
-@@ -18,7 +18,7 @@
- # SELinux about this change.
- # semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
- #
--#Port 22
-+Port 2222
- #AddressFamily any
- #ListenAddress 0.0.0.0
- #ListenAddress ::
-@@ -122,6 +122,7 @@
- #PermitTunnel no
- #ChrootDirectory none
- #VersionAddendum none
-+#UseMPTCP no
-
- # no default banner path
- #Banner none
-@@ -129,6 +130,19 @@
- # 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
-+
-+# allow the use of the none cipher
-+#NoneEnabled no
-+
-+# allow the use of the none MAC
-+#NoneMacEnabled no
-+
- # Example of overriding settings on a per-user basis
- #Match User anoncvs
- # X11Forwarding no
-diff -Nur openssh-10.3p1.orig/sshd_config.5 openssh-10.3p1/sshd_config.5
---- openssh-10.3p1.orig/sshd_config.5 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/sshd_config.5 2026-07-02 00:27:29.734597651 +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):
-@@ -671,6 +681,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.
-@@ -1026,6 +1040,11 @@
- that the server offers.
- 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 IgnoreRhosts
- Specifies whether to ignore per-user
- .Pa .rhosts
-@@ -1548,6 +1567,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 PAMServiceName
- Specifies the service name used for Pluggable Authentication Modules (PAM)
- authentication, authorisation and session controls when
-@@ -2124,6 +2156,13 @@
- .Pp
- To disable TCP keepalive messages, the value should be set to
- .Cm no .
-+.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
-@@ -2184,6 +2223,12 @@
- .Cm Match
- .Cm Host
- directives.
-+.It Cm UseMPTCP
-+If set to
-+.Cm yes ,
-+this will enable Multipath TCP (MPTCP) instead of TCP (this only works on Linux).
-+The default is
-+.Cm no .
- .It Cm UsePAM
- Enables the Pluggable Authentication Module interface.
- If set to
-@@ -2429,3 +2474,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-10.3p1.orig/sshd-session.c openssh-10.3p1/sshd-session.c
---- openssh-10.3p1.orig/sshd-session.c 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/sshd-session.c 2026-07-02 00:27:29.734804681 +0200
-@@ -1106,6 +1106,20 @@
- }
- endpwent();
-
-+ /* get NONE 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);
-+ }
-+ }
-+
- if (!debug_flag && !inetd_flag) {
- if ((startup_pipe = dup(REEXEC_CONFIG_PASS_FD)) == -1)
- fatal("internal error: no startup pipe");
-@@ -1319,6 +1333,9 @@
- rdomain == NULL ? "" : "\"");
- free(laddr);
-
-+ /* set the HPN options for the child */
-+ channel_set_hpn_disabled(options.hpn_disabled);
-+
- /*
- * We don't want to listen forever unless the other side
- * successfully authenticates itself. So we set up an alarm which is
-diff -Nur openssh-10.3p1.orig/ssh.h openssh-10.3p1/ssh.h
---- openssh-10.3p1.orig/ssh.h 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/ssh.h 2026-07-02 10:02:02.455969377 +0200
-@@ -14,6 +14,7 @@
-
- /* Default port number. */
- #define SSH_DEFAULT_PORT 22
-+#define HPNSSH_DEFAULT_PORT 2222
-
- /*
- * Maximum number of certificate files that can be specified
-@@ -43,7 +44,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-10.3p1.orig/sshkey.c openssh-10.3p1/sshkey.c
---- openssh-10.3p1.orig/sshkey.c 2026-07-02 00:27:06.104195447 +0200
-+++ openssh-10.3p1/sshkey.c 2026-07-02 00:27:29.735089157 +0200
-@@ -1767,7 +1767,8 @@
- 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), 0,
-+ CIPHER_ENCRYPT, CIPHER_SERIAL)) != 0)
- goto out;
-
- /* Serialise and encrypt the private key using the ephemeral key */
-@@ -1901,7 +1902,8 @@
- 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,
-+ CIPHER_DECRYPT, CIPHER_SERIAL)) != 0)
- goto out;
- #ifdef DEBUG_PK
- fprintf(stderr, "%s: key+iv\n", __func__);
-@@ -2945,6 +2947,13 @@
- kdfname = "none";
- } else if (ciphername == NULL)
- ciphername = DEFAULT_CIPHERNAME;
-+ /*
-+ * NOTE: Without OpenSSL, this string comparison is still safe, even
-+ * though it will never match because the multithreaded cipher is not
-+ * enabled.
-+ */
-+ else if (strcmp(ciphername, "chacha20-poly1305-mt@hpnssh.org") == 0)
-+ ciphername = "chacha20-poly1305@openssh.com";
- if ((cipher = cipher_by_name(ciphername)) == NULL) {
- r = SSH_ERR_INVALID_ARGUMENT;
- goto out;
-@@ -2980,7 +2989,7 @@
- goto out;
- }
- if ((r = cipher_init(&ciphercontext, cipher, key, keylen,
-- key + keylen, ivlen, 1)) != 0)
-+ key + keylen, ivlen, 0, CIPHER_ENCRYPT, CIPHER_SERIAL)) != 0)
- goto out;
-
- if ((r = sshbuf_put(encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC))) != 0 ||
-@@ -3166,6 +3175,8 @@
- (r = sshbuf_get_u32(decoded, &encrypted_len)) != 0)
- goto out;
-
-+ if (strcmp(ciphername, "chacha20-poly1305-mt@hpnssh.org") == 0)
-+ strcpy(ciphername, "chacha20-poly1305@openssh.com");
- if ((cipher = cipher_by_name(ciphername)) == NULL) {
- r = SSH_ERR_KEY_UNKNOWN_CIPHER;
- goto out;
-@@ -3221,7 +3232,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, CIPHER_DECRYPT, CIPHER_SERIAL)) != 0)
- goto out;
- if ((r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(decoded),
- encrypted_len, 0, authlen)) != 0) {
-diff -Nur openssh-10.3p1.orig/umac.c openssh-10.3p1/umac.c
---- openssh-10.3p1.orig/umac.c 2026-07-02 00:27:06.103275307 +0200
-+++ openssh-10.3p1/umac.c 2026-07-02 00:27:29.735450085 +0200
-@@ -136,15 +136,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 <rapier@psc.edu> 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-10.3p1.orig/uthash.h openssh-10.3p1/uthash.h
---- openssh-10.3p1.orig/uthash.h 1970-01-01 01:00:00.000000000 +0100
-+++ openssh-10.3p1/uthash.h 2026-07-02 00:27:29.735807294 +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 <string.h> /* memcmp, memset, strlen */
-+#include <stddef.h> /* ptrdiff_t */
-+#include <stdlib.h> /* 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 <stdint.h> /* 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 <stdio.h> /* 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 <unistd.h> 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-10.3p1.orig/version.h openssh-10.3p1/version.h
---- openssh-10.3p1.orig/version.h 2026-07-02 00:27:06.105195446 +0200
-+++ openssh-10.3p1/version.h 2026-07-02 02:06:54.090965608 +0200
-@@ -16,5 +16,6 @@
-
- #define SSH_PORTABLE "p1"
- #define GSI_PORTABLE "c-GSI"
--#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \
-+#define SSH_HPN "_hpn18.9.0"
-+#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE SSH_HPN \
- GSI_VERSION KRB5_VERSION
diff --git a/2001-openssh-10.5p1-hpn-18.11.1.patch b/2001-openssh-10.5p1-hpn-18.11.1.patch
new file mode 100644
index 0000000..cc3a84e
--- /dev/null
+++ b/2001-openssh-10.5p1-hpn-18.11.1.patch
@@ -0,0 +1,15652 @@
+diff -Nur openssh-10.5p1.orig/auth2.c openssh-10.5p1/auth2.c
+--- openssh-10.5p1.orig/auth2.c 2026-09-21 07:06:05.555213738 +0200
++++ openssh-10.5p1/auth2.c 2026-09-21 10:40:31.129174736 +0200
+@@ -52,6 +52,8 @@
+ #include "dispatch.h"
+ #include "pathnames.h"
+ #include "ssherr.h"
++#include "canohost.h"
++
+ #ifdef GSSAPI
+ #include "ssh-gss.h"
+ #endif
+@@ -75,6 +77,8 @@
+ extern Authmethod method_gssapi;
+ #endif
+
++static int log_flag = 0;
++
+ Authmethod *authmethods[] = {
+ &method_none,
+ &method_pubkey,
+@@ -104,6 +108,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)
+ {
+@@ -314,6 +321,11 @@
+
+ debug("userauth-request for user %s service %s method %s",
+ user[0] ? user : "<implicit>", 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-10.5p1.orig/binn.c openssh-10.5p1/binn.c
+--- openssh-10.5p1.orig/binn.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/binn.c 2026-09-21 07:06:59.880456684 +0200
+@@ -0,0 +1,3541 @@
++#include <stdio.h>
++#include <stdlib.h>
++#include <stdint.h>
++#include <string.h>
++#include <memory.h>
++#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 <sys/endian.h>
++#elif defined(_AIX)
++#include <sys/machine.h>
++#else
++#include <endian.h>
++#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(const 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(const 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;
++ } 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(const 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;
++
++}
++
++/*************************************************************************************/
++
++// deprecated: unsecure. the size can be corrupted accidentally or intentionally
++BOOL APIENTRY binn_load(const 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 = (void*) data;
++ return TRUE;
++
++}
++
++BOOL APIENTRY binn_load_ex(const void *data, int size, binn *value) {
++
++ if (data == NULL || value == NULL || size <= 0) return FALSE;
++ memset(value, 0, sizeof(binn));
++ value->header = BINN_MAGIC;
++ //value->allocated = FALSE; -- already zeroed
++ //value->writable = FALSE;
++
++ if (binn_is_valid_ex(data, &value->type, &value->count, &size) == FALSE) return FALSE;
++ value->ptr = (void*) data;
++ value->size = size;
++ return TRUE;
++
++}
++
++/*************************************************************************************/
++
++binn * APIENTRY binn_open(const 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 * APIENTRY binn_open_ex(const void *data, int size) {
++ binn *item;
++
++ item = (binn*) binn_malloc(sizeof(binn));
++
++ if (binn_load_ex(data, size, item) == FALSE) {
++ free_fn(item);
++ return NULL;
++ }
++
++ item->allocated = TRUE;
++ return item;
++
++}
++
++/***************************************************************************/
++
++BINN_PRIVATE int binn_get_ptr_type(const 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(const 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;
++ }
++
++ 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;
++ if (p > plimit) return 0;
++
++ 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++) {
++ if (p > plimit) break;
++ len = *((unsigned char *)p);
++ p++;
++ if (p + len > 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;
++ } 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(const 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) {
++ if (*psize < MIN_BINN_SIZE) return FALSE;
++ 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 (size < MIN_BINN_SIZE || count < 0) return FALSE;
++
++ // return the values
++ if (ptype) *ptype = type;
++ if (pcount) *pcount = count;
++ if (psize) *psize = size;
++ if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf);
++ return TRUE;
++}
++
++/***************************************************************************/
++
++BINN_PRIVATE int binn_buf_type(const void *pbuf) {
++ int type;
++
++ if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN;
++
++ return type;
++
++}
++
++/***************************************************************************/
++
++BINN_PRIVATE int binn_buf_count(const void *pbuf) {
++ int nitems;
++
++ if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0;
++
++ return nitems;
++
++}
++
++/***************************************************************************/
++
++BINN_PRIVATE int binn_buf_size(const void *pbuf) {
++ int size=0;
++
++ if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0;
++
++ return size;
++
++}
++
++/***************************************************************************/
++
++void * APIENTRY binn_ptr(const 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 (void*)ptr;
++ default:
++ return NULL;
++ }
++
++}
++
++/***************************************************************************/
++
++int APIENTRY binn_size(const 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(const 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(const 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;
++ }
++
++}
++
++/***************************************************************************/
++
++// the container can be smaller than the informed size
++BINN_PRIVATE BOOL binn_is_valid_ex2(const void *ptr, int *ptype, int *pcount, int *psize) {
++ int i, type, count, size, header_size;
++ unsigned char *p, *plimit, *base, len;
++
++ if (ptr == NULL) return FALSE;
++
++ // is there an informed size?
++ if (psize && *psize > 0) {
++ size = *psize;
++ } else {
++ size = 0;
++ }
++
++ if (!IsValidBinnHeader(ptr, &type, &count, &size, &header_size)) return FALSE;
++
++ // is there an informed size?
++ if (psize && *psize > 0) {
++ // is it bigger than 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;
++ }
++
++ p = (unsigned char *)ptr;
++ base = p;
++ plimit = p + size - 1;
++
++ p += header_size;
++
++ // process each (key and) value
++ for (i = 0; i < count; i++) {
++ switch (type) {
++ case BINN_OBJECT:
++ if (p > plimit) goto Invalid;
++ // get the key (string) size
++ len = *p;
++ p++;
++ //if (len == 0) goto Invalid;
++ // advance over the key
++ p += len;
++ break;
++ case BINN_MAP:
++ // advance over the key
++ read_map_id(&p, plimit);
++ break;
++ case BINN_LIST:
++ // no key
++ break;
++ default:
++ goto Invalid;
++ }
++ // check the value
++ if (p > plimit) goto Invalid;
++ if ((*p & BINN_STORAGE_MASK) == BINN_STORAGE_CONTAINER) {
++ // recursively check the internal container
++ int size2 = plimit - p + 1; // maximum container size
++ if (binn_is_valid_ex2(p, NULL, NULL, &size2) == FALSE) goto Invalid;
++ p += size2;
++ } else {
++ // advance over the value
++ 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 = size;
++ return TRUE;
++
++Invalid:
++ return FALSE;
++
++}
++
++// the container must have the informed size, if informed
++BOOL APIENTRY binn_is_valid_ex(const void *ptr, int *ptype, int *pcount, int *psize) {
++ int size;
++
++ if (psize && *psize > 0) {
++ size = *psize;
++ } else {
++ size = 0;
++ }
++
++ if (binn_is_valid_ex2(ptr, ptype, pcount, &size) == FALSE) return FALSE;
++
++ if (psize) {
++ if (*psize > 0) {
++ if (size != *psize) return FALSE;
++ } else if (*psize==0) {
++ *psize = size;
++ }
++ }
++
++ return TRUE;
++}
++
++/***************************************************************************/
++
++BOOL APIENTRY binn_is_valid(const 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, unsigned char *plimit, 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
++ if (p > plimit) return FALSE;
++ byte = *p; p++;
++ storage_type = byte & BINN_STORAGE_MASK;
++ if (byte & BINN_STORAGE_HAS_MORE) {
++ data_type = byte << 8;
++ if (p > plimit) return FALSE;
++ 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:
++ if (p > plimit) return FALSE;
++ value->vuint8 = *((unsigned char *) p);
++ value->ptr = p; //value->ptr = &value->vuint8;
++ break;
++ case BINN_STORAGE_WORD:
++ if (p + 1 > plimit) return FALSE;
++ copy_be16((u16*)&value->vint16, (u16*)p);
++ value->ptr = &value->vint16;
++ break;
++ case BINN_STORAGE_DWORD:
++ if (p + 3 > plimit) return FALSE;
++ copy_be32((u32*)&value->vint32, (u32*)p);
++ value->ptr = &value->vint32;
++ break;
++ case BINN_STORAGE_QWORD:
++ if (p + 7 > plimit) return FALSE;
++ copy_be64((u64*)&value->vint64, (u64*)p);
++ value->ptr = &value->vint64;
++ break;
++ case BINN_STORAGE_BLOB:
++ case BINN_STORAGE_STRING:
++ if (p > plimit) return FALSE;
++ DataSize = *((unsigned char*)p);
++ if (DataSize & 0x80) {
++ if (p + 3 > plimit) return FALSE;
++ copy_be32((u32*)&DataSize, (u32*)p);
++ DataSize &= 0x7FFFFFFF;
++ p+=4;
++ } else {
++ p++;
++ }
++ if (p + DataSize - 1 > plimit) return FALSE;
++ 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(const void *ptr, const char *key, binn *value) {
++ int type, count, size=0, header_size;
++ unsigned char *p, *plimit;
++
++ ptr = binn_ptr(ptr);
++ if (ptr == NULL || key == NULL || value == NULL) 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;
++ plimit = p + size - 1;
++
++ p = SearchForKey(p, header_size, size, count, key);
++ if (p == FALSE) return FALSE;
++
++ return GetValue(p, plimit, value);
++
++}
++
++/***************************************************************************/
++
++BOOL APIENTRY binn_map_get_value(const void *ptr, int id, binn *value) {
++ int type, count, size=0, header_size;
++ unsigned char *p, *plimit;
++
++ ptr = binn_ptr(ptr);
++ if (ptr == NULL || value == NULL) 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;
++ plimit = p + size - 1;
++
++ p = SearchForID(p, header_size, size, count, id);
++ if (p == FALSE) return FALSE;
++
++ return GetValue(p, plimit, value);
++
++}
++
++/***************************************************************************/
++
++BOOL APIENTRY binn_list_get_value(const 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 == NULL || value == NULL) 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 - 1;
++ p += header_size;
++
++ for (i = 0; i < pos; i++) {
++ p = AdvanceDataPos(p, plimit);
++ if (p == 0 || p < base) return FALSE;
++ }
++
++ return GetValue(p, plimit, value);
++
++}
++
++/***************************************************************************/
++/*** READ PAIR BY POSITION *************************************************/
++/***************************************************************************/
++
++BINN_PRIVATE BOOL binn_read_pair(int expected_type, const 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, plimit, value);
++
++}
++
++/***************************************************************************/
++
++BOOL APIENTRY binn_map_get_pair(const 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(const void *ptr, int pos, char *pkey, binn *value) {
++
++ return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value);
++
++}
++
++/***************************************************************************/
++
++binn * APIENTRY binn_map_pair(const 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(const 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(const 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(const 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, const void *ptr, int expected_type) {
++ int type, count, size=0, header_size;
++
++ ptr = binn_ptr(ptr);
++ if (ptr == NULL || iter == NULL) 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 == NULL || iter->pnext == NULL || 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, iter->plimit, 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 == NULL || iter->pnext == NULL || 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, iter->plimit, 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(const 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(const 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(const void *psource, void *pdest, int source_type, int dest_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(const 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, dest_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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const void *list, int pos) {
++ signed char value;
++
++ binn_list_get(list, pos, BINN_INT8, &value, NULL);
++
++ return value;
++}
++
++short APIENTRY binn_list_int16(const void *list, int pos) {
++ short value;
++
++ binn_list_get(list, pos, BINN_INT16, &value, NULL);
++
++ return value;
++}
++
++int APIENTRY binn_list_int32(const void *list, int pos) {
++ int value;
++
++ binn_list_get(list, pos, BINN_INT32, &value, NULL);
++
++ return value;
++}
++
++int64 APIENTRY binn_list_int64(const void *list, int pos) {
++ int64 value;
++
++ binn_list_get(list, pos, BINN_INT64, &value, NULL);
++
++ return value;
++}
++
++unsigned char APIENTRY binn_list_uint8(const void *list, int pos) {
++ unsigned char value;
++
++ binn_list_get(list, pos, BINN_UINT8, &value, NULL);
++
++ return value;
++}
++
++unsigned short APIENTRY binn_list_uint16(const void *list, int pos) {
++ unsigned short value;
++
++ binn_list_get(list, pos, BINN_UINT16, &value, NULL);
++
++ return value;
++}
++
++unsigned int APIENTRY binn_list_uint32(const void *list, int pos) {
++ unsigned int value;
++
++ binn_list_get(list, pos, BINN_UINT32, &value, NULL);
++
++ return value;
++}
++
++uint64 APIENTRY binn_list_uint64(const void *list, int pos) {
++ uint64 value;
++
++ binn_list_get(list, pos, BINN_UINT64, &value, NULL);
++
++ return value;
++}
++
++float APIENTRY binn_list_float(const void *list, int pos) {
++ float value;
++
++ binn_list_get(list, pos, BINN_FLOAT32, &value, NULL);
++
++ return value;
++}
++
++double APIENTRY binn_list_double(const void *list, int pos) {
++ double value;
++
++ binn_list_get(list, pos, BINN_FLOAT64, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_list_bool(const void *list, int pos) {
++ BOOL value = TRUE;
++
++ binn_list_get(list, pos, BINN_BOOL, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_list_null(const void *list, int pos) {
++
++ return binn_list_get(list, pos, BINN_NULL, NULL, NULL);
++
++}
++
++char * APIENTRY binn_list_str(const void *list, int pos) {
++ char *value;
++
++ binn_list_get(list, pos, BINN_STRING, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_list_blob(const void *list, int pos, int *psize) {
++ void *value;
++
++ binn_list_get(list, pos, BINN_BLOB, &value, psize);
++
++ return value;
++}
++
++void * APIENTRY binn_list_list(const void *list, int pos) {
++ void *value;
++
++ binn_list_get(list, pos, BINN_LIST, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_list_map(const void *list, int pos) {
++ void *value;
++
++ binn_list_get(list, pos, BINN_MAP, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_list_object(const void *list, int pos) {
++ void *value;
++
++ binn_list_get(list, pos, BINN_OBJECT, &value, NULL);
++
++ return value;
++}
++
++/***************************************************************************/
++
++signed char APIENTRY binn_map_int8(const void *map, int id) {
++ signed char value;
++
++ binn_map_get(map, id, BINN_INT8, &value, NULL);
++
++ return value;
++}
++
++short APIENTRY binn_map_int16(const void *map, int id) {
++ short value;
++
++ binn_map_get(map, id, BINN_INT16, &value, NULL);
++
++ return value;
++}
++
++int APIENTRY binn_map_int32(const void *map, int id) {
++ int value;
++
++ binn_map_get(map, id, BINN_INT32, &value, NULL);
++
++ return value;
++}
++
++int64 APIENTRY binn_map_int64(const void *map, int id) {
++ int64 value;
++
++ binn_map_get(map, id, BINN_INT64, &value, NULL);
++
++ return value;
++}
++
++unsigned char APIENTRY binn_map_uint8(const void *map, int id) {
++ unsigned char value;
++
++ binn_map_get(map, id, BINN_UINT8, &value, NULL);
++
++ return value;
++}
++
++unsigned short APIENTRY binn_map_uint16(const void *map, int id) {
++ unsigned short value;
++
++ binn_map_get(map, id, BINN_UINT16, &value, NULL);
++
++ return value;
++}
++
++unsigned int APIENTRY binn_map_uint32(const void *map, int id) {
++ unsigned int value;
++
++ binn_map_get(map, id, BINN_UINT32, &value, NULL);
++
++ return value;
++}
++
++uint64 APIENTRY binn_map_uint64(const void *map, int id) {
++ uint64 value;
++
++ binn_map_get(map, id, BINN_UINT64, &value, NULL);
++
++ return value;
++}
++
++float APIENTRY binn_map_float(const void *map, int id) {
++ float value;
++
++ binn_map_get(map, id, BINN_FLOAT32, &value, NULL);
++
++ return value;
++}
++
++double APIENTRY binn_map_double(const void *map, int id) {
++ double value;
++
++ binn_map_get(map, id, BINN_FLOAT64, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_map_bool(const void *map, int id) {
++ BOOL value = TRUE;
++
++ binn_map_get(map, id, BINN_BOOL, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_map_null(const void *map, int id) {
++
++ return binn_map_get(map, id, BINN_NULL, NULL, NULL);
++
++}
++
++char * APIENTRY binn_map_str(const void *map, int id) {
++ char *value;
++
++ binn_map_get(map, id, BINN_STRING, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_map_blob(const void *map, int id, int *psize) {
++ void *value;
++
++ binn_map_get(map, id, BINN_BLOB, &value, psize);
++
++ return value;
++}
++
++void * APIENTRY binn_map_list(const void *map, int id) {
++ void *value;
++
++ binn_map_get(map, id, BINN_LIST, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_map_map(const void *map, int id) {
++ void *value;
++
++ binn_map_get(map, id, BINN_MAP, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_map_object(const void *map, int id) {
++ void *value;
++
++ binn_map_get(map, id, BINN_OBJECT, &value, NULL);
++
++ return value;
++}
++
++/***************************************************************************/
++
++signed char APIENTRY binn_object_int8(const void *obj, const char *key) {
++ signed char value;
++
++ binn_object_get(obj, key, BINN_INT8, &value, NULL);
++
++ return value;
++}
++
++short APIENTRY binn_object_int16(const void *obj, const char *key) {
++ short value;
++
++ binn_object_get(obj, key, BINN_INT16, &value, NULL);
++
++ return value;
++}
++
++int APIENTRY binn_object_int32(const void *obj, const char *key) {
++ int value;
++
++ binn_object_get(obj, key, BINN_INT32, &value, NULL);
++
++ return value;
++}
++
++int64 APIENTRY binn_object_int64(const void *obj, const char *key) {
++ int64 value;
++
++ binn_object_get(obj, key, BINN_INT64, &value, NULL);
++
++ return value;
++}
++
++unsigned char APIENTRY binn_object_uint8(const 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(const 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(const void *obj, const char *key) {
++ unsigned int value;
++
++ binn_object_get(obj, key, BINN_UINT32, &value, NULL);
++
++ return value;
++}
++
++uint64 APIENTRY binn_object_uint64(const void *obj, const char *key) {
++ uint64 value;
++
++ binn_object_get(obj, key, BINN_UINT64, &value, NULL);
++
++ return value;
++}
++
++float APIENTRY binn_object_float(const void *obj, const char *key) {
++ float value;
++
++ binn_object_get(obj, key, BINN_FLOAT32, &value, NULL);
++
++ return value;
++}
++
++double APIENTRY binn_object_double(const void *obj, const char *key) {
++ double value;
++
++ binn_object_get(obj, key, BINN_FLOAT64, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_object_bool(const void *obj, const char *key) {
++ BOOL value = TRUE;
++
++ binn_object_get(obj, key, BINN_BOOL, &value, NULL);
++
++ return value;
++}
++
++BOOL APIENTRY binn_object_null(const void *obj, const char *key) {
++
++ return binn_object_get(obj, key, BINN_NULL, NULL, NULL);
++
++}
++
++char * APIENTRY binn_object_str(const void *obj, const char *key) {
++ char *value;
++
++ binn_object_get(obj, key, BINN_STRING, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_object_blob(const 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(const void *obj, const char *key) {
++ void *value;
++
++ binn_object_get(obj, key, BINN_LIST, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_object_map(const void *obj, const char *key) {
++ void *value;
++
++ binn_object_get(obj, key, BINN_MAP, &value, NULL);
++
++ return value;
++}
++
++void * APIENTRY binn_object_object(const 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;
++
++}
++
++/*************************************************************************************/
++
++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 < (float)INT32_MIN || value->vfloat > (float)INT32_MAX) return FALSE;
++ *pint = roundval(value->vfloat);
++ break;
++ case BINN_DOUBLE:
++ if (value->vdouble < (double)INT32_MIN || value->vdouble > (double)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 < (float)INT64_MIN || value->vfloat > (float)INT64_MAX) return FALSE;
++ *pint = roundval(value->vfloat);
++ break;
++ case BINN_DOUBLE:
++ if (value->vdouble < (double)INT64_MIN || value->vdouble > (double)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-10.5p1.orig/binn.h openssh-10.5p1/binn.h
+--- openssh-10.5p1.orig/binn.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/binn.h 2026-09-21 07:06:59.880814583 +0200
+@@ -0,0 +1,945 @@
++
++// 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
++
++#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(const 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(const void *ptr);
++int APIENTRY binn_size(const void *ptr);
++int APIENTRY binn_type(const void *ptr);
++int APIENTRY binn_count(const void *ptr);
++
++BOOL APIENTRY binn_is_valid(const 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(const 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(const void *ptr);
++
++
++// Loading a binn buffer into a binn value - this is optional
++
++binn * APIENTRY binn_open(const void *data); // allocated - unsecure
++binn * APIENTRY binn_open_ex(const void *data, int size); // allocated - secure
++BOOL APIENTRY binn_load(const void *data, binn *item); // on stack - unsecure
++BOOL APIENTRY binn_load_ex(const void *data, int size, binn *value); // secure
++
++
++// easiest interface to use, but don't check if the value is there
++
++signed char APIENTRY binn_list_int8(const void *list, int pos);
++short APIENTRY binn_list_int16(const void *list, int pos);
++int APIENTRY binn_list_int32(const void *list, int pos);
++int64 APIENTRY binn_list_int64(const void *list, int pos);
++unsigned char APIENTRY binn_list_uint8(const void *list, int pos);
++unsigned short APIENTRY binn_list_uint16(const void *list, int pos);
++unsigned int APIENTRY binn_list_uint32(const void *list, int pos);
++uint64 APIENTRY binn_list_uint64(const void *list, int pos);
++float APIENTRY binn_list_float(const void *list, int pos);
++double APIENTRY binn_list_double(const void *list, int pos);
++BOOL APIENTRY binn_list_bool(const void *list, int pos);
++BOOL APIENTRY binn_list_null(const void *list, int pos);
++char * APIENTRY binn_list_str(const void *list, int pos);
++void * APIENTRY binn_list_blob(const void *list, int pos, int *psize);
++void * APIENTRY binn_list_list(const void *list, int pos);
++void * APIENTRY binn_list_map(const void *list, int pos);
++void * APIENTRY binn_list_object(const void *list, int pos);
++
++signed char APIENTRY binn_map_int8(const void *map, int id);
++short APIENTRY binn_map_int16(const void *map, int id);
++int APIENTRY binn_map_int32(const void *map, int id);
++int64 APIENTRY binn_map_int64(const void *map, int id);
++unsigned char APIENTRY binn_map_uint8(const void *map, int id);
++unsigned short APIENTRY binn_map_uint16(const void *map, int id);
++unsigned int APIENTRY binn_map_uint32(const void *map, int id);
++uint64 APIENTRY binn_map_uint64(const void *map, int id);
++float APIENTRY binn_map_float(const void *map, int id);
++double APIENTRY binn_map_double(const void *map, int id);
++BOOL APIENTRY binn_map_bool(const void *map, int id);
++BOOL APIENTRY binn_map_null(const void *map, int id);
++char * APIENTRY binn_map_str(const void *map, int id);
++void * APIENTRY binn_map_blob(const void *map, int id, int *psize);
++void * APIENTRY binn_map_list(const void *map, int id);
++void * APIENTRY binn_map_map(const void *map, int id);
++void * APIENTRY binn_map_object(const void *map, int id);
++
++signed char APIENTRY binn_object_int8(const void *obj, const char *key);
++short APIENTRY binn_object_int16(const void *obj, const char *key);
++int APIENTRY binn_object_int32(const void *obj, const char *key);
++int64 APIENTRY binn_object_int64(const void *obj, const char *key);
++unsigned char APIENTRY binn_object_uint8(const void *obj, const char *key);
++unsigned short APIENTRY binn_object_uint16(const void *obj, const char *key);
++unsigned int APIENTRY binn_object_uint32(const void *obj, const char *key);
++uint64 APIENTRY binn_object_uint64(const void *obj, const char *key);
++float APIENTRY binn_object_float(const void *obj, const char *key);
++double APIENTRY binn_object_double(const void *obj, const char *key);
++BOOL APIENTRY binn_object_bool(const void *obj, const char *key);
++BOOL APIENTRY binn_object_null(const void *obj, const char *key);
++char * APIENTRY binn_object_str(const void *obj, const char *key);
++void * APIENTRY binn_object_blob(const void *obj, const char *key, int *psize);
++void * APIENTRY binn_object_list(const void *obj, const char *key);
++void * APIENTRY binn_object_map(const void *obj, const char *key);
++void * APIENTRY binn_object_object(const 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(const void *list, int pos);
++binn * APIENTRY binn_map_value(const void *map, int id);
++binn * APIENTRY binn_object_value(const void *obj, const char *key);
++
++// read the value to a binn structure on the stack
++BOOL APIENTRY binn_list_get_value(const void *list, int pos, binn *value);
++BOOL APIENTRY binn_map_get_value(const void *map, int id, binn *value);
++BOOL APIENTRY binn_object_get_value(const void *obj, const char *key, binn *value);
++
++// single interface - these functions check the data type
++BOOL APIENTRY binn_list_get(const void *list, int pos, int type, void *pvalue, int *psize);
++BOOL APIENTRY binn_map_get(const void *map, int id, int type, void *pvalue, int *psize);
++BOOL APIENTRY binn_object_get(const 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(const void *list, int pos, int *ptype, int *psize);
++void * APIENTRY binn_map_read(const void *map, int id, int *ptype, int *psize);
++void * APIENTRY binn_object_read(const 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(const void *map, int pos, int *pid, binn *value);
++BOOL APIENTRY binn_object_get_pair(const void *obj, int pos, char *pkey, binn *value); // the key must be declared as: char key[256];
++
++// allocated
++binn * APIENTRY binn_map_pair(const void *map, int pos, int *pid);
++binn * APIENTRY binn_object_pair(const void *obj, int pos, char *pkey); // the key must be declared as: char key[256];
++
++// 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(const void *ptr, int pos, int *pid, int *ptype, int *psize);
++void * APIENTRY binn_object_read_pair(const 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, const 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(const void *list, int pos, signed char *pvalue) {
++ return binn_list_get(list, pos, BINN_INT8, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_int16(const void *list, int pos, short *pvalue) {
++ return binn_list_get(list, pos, BINN_INT16, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_int32(const void *list, int pos, int *pvalue) {
++ return binn_list_get(list, pos, BINN_INT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_int64(const void *list, int pos, int64 *pvalue) {
++ return binn_list_get(list, pos, BINN_INT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_uint8(const void *list, int pos, unsigned char *pvalue) {
++ return binn_list_get(list, pos, BINN_UINT8, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_uint16(const void *list, int pos, unsigned short *pvalue) {
++ return binn_list_get(list, pos, BINN_UINT16, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_uint32(const void *list, int pos, unsigned int *pvalue) {
++ return binn_list_get(list, pos, BINN_UINT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_uint64(const void *list, int pos, uint64 *pvalue) {
++ return binn_list_get(list, pos, BINN_UINT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_float(const void *list, int pos, float *pvalue) {
++ return binn_list_get(list, pos, BINN_FLOAT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_double(const void *list, int pos, double *pvalue) {
++ return binn_list_get(list, pos, BINN_FLOAT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_bool(const void *list, int pos, BOOL *pvalue) {
++ return binn_list_get(list, pos, BINN_BOOL, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_str(const void *list, int pos, char **pvalue) {
++ return binn_list_get(list, pos, BINN_STRING, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_blob(const 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(const void *list, int pos, void **pvalue) {
++ return binn_list_get(list, pos, BINN_LIST, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_map(const void *list, int pos, void **pvalue) {
++ return binn_list_get(list, pos, BINN_MAP, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_list_get_object(const void *list, int pos, void **pvalue) {
++ return binn_list_get(list, pos, BINN_OBJECT, pvalue, NULL);
++}
++
++/***************************************************************************/
++
++ALWAYS_INLINE BOOL binn_map_get_int8(const void *map, int id, signed char *pvalue) {
++ return binn_map_get(map, id, BINN_INT8, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_int16(const void *map, int id, short *pvalue) {
++ return binn_map_get(map, id, BINN_INT16, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_int32(const void *map, int id, int *pvalue) {
++ return binn_map_get(map, id, BINN_INT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_int64(const void *map, int id, int64 *pvalue) {
++ return binn_map_get(map, id, BINN_INT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_uint8(const void *map, int id, unsigned char *pvalue) {
++ return binn_map_get(map, id, BINN_UINT8, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_uint16(const void *map, int id, unsigned short *pvalue) {
++ return binn_map_get(map, id, BINN_UINT16, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_uint32(const void *map, int id, unsigned int *pvalue) {
++ return binn_map_get(map, id, BINN_UINT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_uint64(const void *map, int id, uint64 *pvalue) {
++ return binn_map_get(map, id, BINN_UINT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_float(const void *map, int id, float *pvalue) {
++ return binn_map_get(map, id, BINN_FLOAT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_double(const void *map, int id, double *pvalue) {
++ return binn_map_get(map, id, BINN_FLOAT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_bool(const void *map, int id, BOOL *pvalue) {
++ return binn_map_get(map, id, BINN_BOOL, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_str(const void *map, int id, char **pvalue) {
++ return binn_map_get(map, id, BINN_STRING, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_blob(const 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(const void *map, int id, void **pvalue) {
++ return binn_map_get(map, id, BINN_LIST, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_map(const void *map, int id, void **pvalue) {
++ return binn_map_get(map, id, BINN_MAP, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_map_get_object(const 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(const 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(const void *obj, const char *key, short *pvalue) {
++ return binn_object_get(obj, key, BINN_INT16, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_int32(const void *obj, const char *key, int *pvalue) {
++ return binn_object_get(obj, key, BINN_INT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_int64(const void *obj, const char *key, int64 *pvalue) {
++ return binn_object_get(obj, key, BINN_INT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_uint8(const 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(const 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(const 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(const void *obj, const char *key, uint64 *pvalue) {
++ return binn_object_get(obj, key, BINN_UINT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_float(const void *obj, const char *key, float *pvalue) {
++ return binn_object_get(obj, key, BINN_FLOAT32, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_double(const void *obj, const char *key, double *pvalue) {
++ return binn_object_get(obj, key, BINN_FLOAT64, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_bool(const void *obj, const char *key, BOOL *pvalue) {
++ return binn_object_get(obj, key, BINN_BOOL, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_str(const void *obj, const char *key, char **pvalue) {
++ return binn_object_get(obj, key, BINN_STRING, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_blob(const 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(const void *obj, const char *key, void **pvalue) {
++ return binn_object_get(obj, key, BINN_LIST, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_map(const void *obj, const char *key, void **pvalue) {
++ return binn_object_get(obj, key, BINN_MAP, pvalue, NULL);
++}
++ALWAYS_INLINE BOOL binn_object_get_object(const 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-10.5p1.orig/channels.c openssh-10.5p1/channels.c
+--- openssh-10.5p1.orig/channels.c 2026-09-21 07:06:05.470667342 +0200
++++ openssh-10.5p1/channels.c 2026-09-21 10:43:13.231236469 +0200
+@@ -90,6 +90,11 @@
+ /* Maximum number of fake X11 displays to try. */
+ #define MAX_DISPLAYS 1000
+
++/* in version of OpenSSH later than 8.8 if we advertise a window
++ * 16MB or larger is causes a pathological behaviour that reduces
++ * throughput. This is not a great solution. */
++#define NON_HPN_WINDOW_MAX (15 * 1024 * 1024)
++
+ /* Per-channel callback for pre/post IO actions */
+ typedef void chan_fn(struct ssh *, Channel *c);
+
+@@ -222,6 +227,9 @@
+ /* Setup helper */
+ static void channel_handler_init(struct ssh_channels *sc);
+
++/* default values to enable hpn and the initial buffer size */
++static int hpn_disabled = 0;
++
+ /* -- channel core */
+
+ void
+@@ -542,6 +550,16 @@
+ (c->output = sshbuf_new()) == NULL ||
+ (c->extended = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new failed");
++
++ /* these buffers are important in terms of tracking channel
++ * buffer usage so label and type them with descriptive names */
++ sshbuf_relabel(c->input, "channel input");
++ sshbuf_type(c->input, BUF_CHANNEL_INPUT);
++ sshbuf_relabel(c->output, "channel output");
++ sshbuf_type(c->output, BUF_CHANNEL_OUTPUT);
++ sshbuf_relabel(c->extended, "channel extended");
++ sshbuf_type(c->extended, BUF_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;
+@@ -553,6 +571,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 */
+@@ -1327,6 +1346,42 @@
+ 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;
++
++ /* get the current size of the receive buffer */
++ ret = getsockopt(ssh_packet_get_connection_in(ssh),
++ SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz);
++
++ /* error on the socket - this should never happen */
++ /* return OpenSSH's max window size */
++ if (ret != 0) {
++ debug_f("getsockopt SO_RCVBUF failed: %s", strerror(errno));
++ return (2 * 1024 * 1024);
++ }
++
++ /* return no more than SSHBUF_SIZE_MAX (currently 128MB) */
++ if (tcpwinsz > SSHBUF_SIZE_MAX)
++ tcpwinsz = SSHBUF_SIZE_MAX;
++
++ /* if the remote side is OpenSSH after version 8.8 we need to restrict
++ * the size of the advertised window. Now this means that any HPN to non-HPN
++ * connection will be window limited to 15MB of receive space. This is a
++ * non-optimal solution.
++ */
++ if ((ssh->compat & SSH_RESTRICT_WINDOW) && (tcpwinsz > NON_HPN_WINDOW_MAX))
++ tcpwinsz = NON_HPN_WINDOW_MAX;
++ return (tcpwinsz);
++}
++
+ static void
+ channel_pre_open(struct ssh *ssh, Channel *c)
+ {
+@@ -2187,8 +2242,6 @@
+ static int
+ channel_handle_rfd(struct ssh *ssh, Channel *c)
+ {
+- char buf[CHAN_RBUF];
+- ssize_t len;
+ int r, force;
+ size_t nr = 0, have, avail, maxlen = CHANNEL_MAX_READ;
+ int pty_zeroread = 0;
+@@ -2232,6 +2285,30 @@
+ return 1;
+ }
+
++ /*
++ * Datagram or filtered channel path: needs a temporary stack buffer
++ * because the data must pass through input_filter() or be framed as
++ * a datagram before entering c->input.
++ *
++ * buf[] (CHAN_RBUF = 32KB) is declared in this inner scope rather
++ * than at function level to avoid a performance penalty from
++ * -ftrivial-auto-var-init=zero, which causes the compiler to emit
++ * a memset() for every local variable on function entry. With buf
++ * at function scope the 32KB zeroing happens on every call, even
++ * though the simple-channel fast path above (the common case for
++ * bulk data) never touches buf. CPU profiling showed this dead
++ * zeroing consuming ~10% of client CPU during high-throughput
++ * transfers. Moving buf into this scope limits the zeroing to
++ * calls that actually take the datagram/filter path.
++ *
++ * Security note: buf is still zeroed by the compiler before use in
++ * this path. The simple-channel path never allocates buf at all,
++ * so there is no uninitialized memory exposure.
++ */
++ {
++ char buf[CHAN_RBUF];
++ ssize_t len;
++
+ errno = 0;
+ len = read(c->rfd, buf, sizeof(buf));
+ /* fixup AIX zero-length read with errno set to look more like errors */
+@@ -2244,15 +2321,7 @@
+ debug2("channel %d: read<=0 rfd %d len %zd: %s",
+ c->self, c->rfd, len,
+ len == 0 ? "closed" : strerror(errno));
+- rfail:
+- if (c->type != SSH_CHANNEL_OPEN) {
+- debug2("channel %d: not open", c->self);
+- chan_mark_dead(ssh, c);
+- return -1;
+- } else {
+- chan_read_failed(ssh, c);
+- }
+- return -1;
++ goto rfail;
+ }
+ channel_set_used_time(ssh, c);
+ if (c->input_filter != NULL) {
+@@ -2267,6 +2336,17 @@
+ fatal_fr(r, "channel %i: put data", c->self);
+
+ return 1;
++ } /* end datagram/filter buf[] scope */
++
++ rfail:
++ if (c->type != SSH_CHANNEL_OPEN) {
++ debug2("channel %d: not open", c->self);
++ chan_mark_dead(ssh, c);
++ return -1;
++ } else {
++ chan_read_failed(ssh, c);
++ }
++ return -1;
+ }
+
+ static int
+@@ -2444,21 +2524,32 @@
+ 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_maxpacket * 8) ||
+ c->local_window < c->local_window_max/2) &&
+ c->local_consumed > 0) {
++ 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)) {
++ /* aggressively grow the window */
++ addition = tcpwinsz - c->local_window_max;
++ c->local_window_max += addition;
++ debug_f("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;
++ c->local_window, c->local_consumed + addition);
++ c->local_window += c->local_consumed + addition;
+ c->local_consumed = 0;
+ }
+ return 1;
+@@ -3047,9 +3138,7 @@
+ * in use.
+ */
+ if (CHANNEL_EFD_INPUT_ACTIVE(c))
+- debug2("channel %d: "
+- "ibuf_empty delayed efd %d/(%zu)",
+- c->self, c->efd, sshbuf_len(c->extended));
++ {}
+ else
+ chan_ibuf_empty(ssh, c);
+ }
+@@ -3876,6 +3965,13 @@
+ return addr;
+ }
+
++void
++channel_set_hpn_disabled(int external_hpn_disabled)
++{
++ hpn_disabled = external_hpn_disabled;
++ debug("HPN Disabled: %d", hpn_disabled);
++}
++
+ static int
+ channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type,
+ struct Forward *fwd, int *allocated_listen_port,
+diff -Nur openssh-10.5p1.orig/channels.h openssh-10.5p1/channels.h
+--- openssh-10.5p1.orig/channels.h 2026-09-21 07:06:05.471058548 +0200
++++ openssh-10.5p1/channels.h 2026-09-21 07:06:59.882507987 +0200
+@@ -181,6 +181,7 @@
+ u_int local_window_max;
+ u_int local_consumed;
+ u_int local_maxpacket;
++ int dynamic_window;
+ int extended_usage;
+ int agent_new; /* For agent listeners, use RFC XXX reqests */
+ int single_connection;
+@@ -264,7 +265,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
+@@ -414,4 +415,6 @@
+ void chan_write_failed(struct ssh *, Channel *);
+ void chan_obuf_empty(struct ssh *, Channel *);
+
++/* hpn handler */
++void channel_set_hpn_disabled(int);
+ #endif
+diff -Nur openssh-10.5p1.orig/cipher.c openssh-10.5p1/cipher.c
+--- openssh-10.5p1.orig/cipher.c 2026-09-21 07:06:05.290125015 +0200
++++ openssh-10.5p1/cipher.c 2026-09-21 10:45:02.132281506 +0200
+@@ -47,23 +47,40 @@
+ #include "misc.h"
+ #include "sshbuf.h"
+ #include "ssherr.h"
++#include "digest.h"
++#include "log.h"
+
+ #include "openbsd-compat/openssl-compat.h"
+
++/* for provider functions */
++#ifdef WITH_OPENSSL3
++#include <openssl/err.h>
++#include <openssl/params.h>
++#include <openssl/provider.h>
++#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;
++#ifdef WITH_OPENSSL
++ struct chachapoly_ctx_mt *cp_ctx_mt;
++#endif
+ 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 },
+@@ -85,6 +102,10 @@
+ #endif
+ { "chacha20-poly1305@openssh.com",
+ 8, 64, 0, 16, CFLAG_CHACHAPOLY, NULL },
++#ifdef WITH_OPENSSL
++ { "chacha20-poly1305-mt@hpnssh.org",
++ 8, 64, 0, 16, CFLAG_CHACHAPOLY|CFLAG_MT, NULL },
++#endif
+ { "none", 8, 0, 0, 0, CFLAG_NONE, NULL },
+
+ { NULL, 0, 0, 0, 0, 0, NULL }
+@@ -121,12 +142,63 @@
+ #endif
+ }
+
++/* used to get the cipher name when we are testing to
++ * see if we can move from a serial to parallel cipher
++ * only called in cipher-switch.c
++ */
++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)
+ {
+@@ -176,10 +248,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;
+@@ -202,7 +274,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;
+ } else
+@@ -224,7 +297,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)
++ u_int seqnr, int do_encrypt, int enable_threads)
+ {
+ struct sshcipher_ctx *cc = NULL;
+ int ret = SSH_ERR_INTERNAL_ERROR;
+@@ -239,6 +312,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))) {
+@@ -248,8 +322,19 @@
+
+ cc->cipher = cipher;
+ if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
++#ifdef WITH_OPENSSL
++ if ((cc->cipher->flags & CFLAG_MT) != 0) {
++ cc->cp_ctx_mt = chachapoly_new_mt(seqnr, key, keylen);
++ ret = cc->cp_ctx_mt != NULL ? 0 :
++ SSH_ERR_INVALID_ARGUMENT;
++ } else {
++ cc->cp_ctx = chachapoly_new(key, keylen);
++ ret = cc->cp_ctx != NULL ? 0 : SSH_ERR_INVALID_ARGUMENT;
++ }
++#else
+ cc->cp_ctx = chachapoly_new(key, keylen);
+ ret = cc->cp_ctx != NULL ? 0 : SSH_ERR_INVALID_ARGUMENT;
++#endif
+ goto out;
+ }
+ if ((cc->cipher->flags & CFLAG_NONE) != 0) {
+@@ -271,6 +356,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") && enable_threads) {
++#ifdef WITH_OPENSSL3
++ /* 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 /* WITH_OPENSSL3 */
++ } /* if (strstr()) */
+ if (EVP_CipherInit(cc->evp, type, NULL, (u_char *)iv,
+ (do_encrypt == CIPHER_ENCRYPT)) == 0) {
+ ret = SSH_ERR_LIBCRYPTO_ERROR;
+@@ -325,6 +457,12 @@
+ const u_char *src, u_int len, u_int aadlen, u_int authlen)
+ {
+ if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
++#ifdef WITH_OPENSSL
++ if ((cc->cipher->flags & CFLAG_MT) != 0) {
++ return chachapoly_crypt_mt(cc->cp_ctx_mt, seqnr, dest,
++ src, len, aadlen, authlen, cc->encrypt);
++ }
++#endif
+ return chachapoly_crypt(cc->cp_ctx, seqnr, dest, src,
+ len, aadlen, authlen, cc->encrypt);
+ }
+@@ -387,9 +525,16 @@
+ cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
+ const u_char *cp, u_int len)
+ {
+- if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0)
++ if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
++#ifdef WITH_OPENSSL
++ if ((cc->cipher->flags & CFLAG_MT) != 0) {
++ return chachapoly_get_length_mt(cc->cp_ctx_mt, plenp,
++ seqnr, cp, len);
++ }
++#endif
+ return chachapoly_get_length(cc->cp_ctx, plenp, seqnr,
+ cp, len);
++ }
+ if (len < 4)
+ return SSH_ERR_MESSAGE_INCOMPLETE;
+ *plenp = PEEK_U32(cp);
+@@ -402,13 +547,35 @@
+ if (cc == NULL || cc->cipher == NULL)
+ return;
+ if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
++#ifdef WITH_OPENSSL
++ if ((cc->cipher->flags & CFLAG_MT) != 0) {
++ chachapoly_free_mt(cc->cp_ctx_mt);
++ cc->cp_ctx_mt = NULL;
++ } else {
++ chachapoly_free(cc->cp_ctx);
++ cc->cp_ctx = NULL;
++ }
++#else
+ chachapoly_free(cc->cp_ctx);
+ cc->cp_ctx = NULL;
++#endif
+ } else if ((cc->cipher->flags & CFLAG_AESCTR) != 0)
+ explicit_bzero(&cc->ac_ctx, sizeof(cc->ac_ctx));
+ #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 !defined(WITH_OPENSSL3)
++ if (cc->meth_ptr != NULL) {
++ EVP_CIPHER_meth_free((void *)(EVP_CIPHER *)cc->meth_ptr);
++ cc->meth_ptr = NULL;
++ }
++#endif
+ #endif
+ freezero(cc, sizeof(*cc));
+ }
+diff -Nur openssh-10.5p1.orig/cipher-chachapoly.c openssh-10.5p1/cipher-chachapoly.c
+--- openssh-10.5p1.orig/cipher-chachapoly.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/cipher-chachapoly.c 2026-09-21 07:06:59.883804492 +0200
+@@ -88,7 +88,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;
+@@ -108,7 +108,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-10.5p1.orig/cipher-chachapoly-libcrypto.c openssh-10.5p1/cipher-chachapoly-libcrypto.c
+--- openssh-10.5p1.orig/cipher-chachapoly-libcrypto.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/cipher-chachapoly-libcrypto.c 2026-09-21 07:06:59.884063669 +0200
+@@ -32,8 +32,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 *
+@@ -54,6 +64,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);
+@@ -67,6 +86,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));
+ }
+
+@@ -105,7 +127,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;
+@@ -131,7 +153,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-10.5p1.orig/cipher-chachapoly-libcrypto-mt.c openssh-10.5p1/cipher-chachapoly-libcrypto-mt.c
+--- openssh-10.5p1.orig/cipher-chachapoly-libcrypto-mt.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-chachapoly-libcrypto-mt.c 2026-09-21 07:06:59.884353459 +0200
+@@ -0,0 +1,708 @@
++/*
++ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Mitchell Dorrell <mwd@psc.edu>
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++/* TODO: audit includes */
++
++#include "includes.h"
++#ifdef WITH_OPENSSL
++#include "openbsd-compat/openssl-compat.h"
++#endif
++
++#if defined(HAVE_EVP_CHACHA20) && !defined(HAVE_BROKEN_CHACHA20)
++
++#include <sys/types.h>
++#include <unistd.h> /* needed for getpid under C99 */
++#include <stdarg.h> /* needed for log.h */
++#include <string.h>
++#include <stdio.h> /* needed for misc.h */
++#include <pthread.h>
++
++#include <openssl/evp.h>
++
++#include "defines.h"
++#include "log.h"
++#include "sshbuf.h"
++#include "ssherr.h"
++
++#include "xmalloc.h"
++#include "cipher-chachapoly.h"
++#include "cipher-chachapoly-libcrypto-mt.h"
++
++#ifndef likely
++# define likely(x) __builtin_expect(!!(x), 1)
++#endif
++#ifndef unlikely
++# define unlikely(x) __builtin_expect(!!(x), 0)
++#endif
++
++/* Size of keystream to pregenerate, measured in bytes
++ * we want to round up to the nearest chacha block and have
++ * 128 bytes for overhead */
++#define ROUND_UP(x,y) (((((x)-1)/(y))+1)*(y))
++#define KEYSTREAMLEN (ROUND_UP((SSH_IOBUFSZ) + 128, (CHACHA_BLOCKLEN)))
++
++/* BEGIN TUNABLES */
++
++/* Number of worker threads to spawn. */
++/* the goal is to ensure that main is never
++ * waiting on the worker threads for keystream data */
++#define NUMTHREADS 1
++
++/* 64 seems to be a pretty blance between memory and performance
++ * 128 is another option with somewhat higher memory consumption */
++#define NUMSTREAMS 64
++
++/* END TUNABLES */
++
++struct mt_keystream {
++ u_char poly_key[POLY1305_KEYLEN]; /* POLY1305_KEYLEN == 32 */
++ u_char headerStream[CHACHA_BLOCKLEN]; /* CHACHA_BLOCKLEN == 64 */
++ u_char mainStream[KEYSTREAMLEN]; /* KEYSTREAMLEN == 32768 */
++};
++
++struct threadData {
++ EVP_CIPHER_CTX * main_evp;
++ EVP_CIPHER_CTX * header_evp;
++ u_char seqbuf[16];
++};
++
++struct mt_keystream_batch {
++ u_int batchID;
++ struct threadData tds[NUMTHREADS];
++ struct mt_keystream streams[NUMSTREAMS];
++};
++
++struct chachapoly_ctx_mt {
++ u_int seqnr;
++ u_int batchID;
++
++ struct mt_keystream_batch batches[2];
++
++ pthread_t manager_tid[2];
++ pthread_t self_tid;
++
++ pid_t mainpid;
++ u_char zeros[KEYSTREAMLEN]; /* KEYSTREAMLEN == 32768 */
++
++ /* if OpenSSL has support for Poly1305 in the MAC EVPs
++ * use that (OSSL >= 3.0) if not then it's OSSL 1.1 so
++ * use the Poly1305 digest methods. Failing that use the
++ * internal poly1305 methods */
++#ifdef OPENSSL_HAVE_POLY_EVP
++ EVP_MAC_CTX *poly_ctx;
++#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ EVP_PKEY_CTX *poly_ctx;
++ EVP_MD_CTX *md_ctx;
++ EVP_PKEY *pkey;
++ size_t ptaglen;
++#else
++ char *poly_ctx;
++#endif
++};
++
++struct manager_thread_args {
++ struct chachapoly_ctx_mt * ctx_mt;
++ u_int oldBatchID;
++ int retval;
++};
++
++struct worker_thread_args {
++ u_int batchID;
++ struct mt_keystream_batch * batch;
++ int threadIndex;
++ u_char * zeros;
++ int retval;
++};
++
++/* generate the keystream and header
++ * we use nulls for the "data" (the zeros variable) in order to
++ * get the raw keystream
++ * Returns 0 on success and -1 on failure */
++int
++generate_keystream(struct mt_keystream * ks, u_int seqnr,
++ struct threadData * td, u_char * zeros)
++{
++ /* generate poly1305 key */
++ memset(td->seqbuf, 0, sizeof(td->seqbuf));
++ POKE_U64(td->seqbuf + 8, seqnr);
++ memset(ks->poly_key , 0, sizeof(ks->poly_key));
++ if (!EVP_CipherInit(td->main_evp, NULL, NULL, td->seqbuf, 1) ||
++ EVP_Cipher(td->main_evp, ks->poly_key, ks->poly_key,
++ sizeof(ks->poly_key)) < 0)
++ return -1;
++
++ /* generate header keystream for encrypting payload length */
++ if (!EVP_CipherInit(td->header_evp, NULL, NULL, td->seqbuf, 1) ||
++ EVP_Cipher(td->header_evp, ks->headerStream, zeros, CHACHA_BLOCKLEN)
++ < 0 )
++ return -1;
++
++ /* generate main keystream for encrypting payload */
++ td->seqbuf[0] = 1;
++ if (!EVP_CipherInit(td->main_evp, NULL, NULL, td->seqbuf, 1) ||
++ EVP_Cipher(td->main_evp, ks->mainStream, zeros, KEYSTREAMLEN) < 0)
++ return -1;
++
++ return 0;
++}
++
++/* free the EVP contexts associated with the give thread */
++void
++free_threadData(struct threadData * td)
++{
++ if (td == NULL)
++ return;
++ if (td->main_evp) /* false if initialization didn't get this far */
++ EVP_CIPHER_CTX_free(td->main_evp);
++ if (td->header_evp) /* false if initialization didn't get this far */
++ EVP_CIPHER_CTX_free(td->header_evp);
++ explicit_bzero(td, sizeof(*td));
++}
++
++/* initialize the EVPs used by the worker thread
++ Returns 0 on success and -1 on failure */
++int
++initialize_threadData(struct threadData * td, const u_char *key)
++{
++ memset(td,0,sizeof(*td));
++ if ((td->main_evp = EVP_CIPHER_CTX_new()) == NULL ||
++ (td->header_evp = EVP_CIPHER_CTX_new()) == NULL)
++ goto fail;
++ if (!EVP_CipherInit(td->main_evp, EVP_chacha20(), key, NULL, 1))
++ goto fail;
++ if (!EVP_CipherInit(td->header_evp, EVP_chacha20(), key + 32, NULL, 1))
++ goto fail;
++ if (EVP_CIPHER_CTX_iv_length(td->header_evp) != 16)
++ goto fail;
++ return 0;
++ fail:
++ free_threadData(td);
++ return -1;
++}
++
++struct worker_thread_args *
++worker_thread(struct worker_thread_args * args)
++{
++ /* check first */
++ if (args == NULL)
++ return NULL;
++ if (args->batch == NULL || args->zeros == NULL) {
++ args->retval = 1;
++ return args;
++ }
++
++ int threadIndex = args->threadIndex;
++ struct threadData * td = &(args->batch->tds[threadIndex]);
++ u_int refseqnr = args->batchID * NUMSTREAMS;
++
++ for (int i = threadIndex; i < NUMSTREAMS; i += NUMTHREADS) {
++ if (generate_keystream(&(args->batch->streams[i]), refseqnr + i,
++ td, args->zeros) == -1) {
++ args->retval = 1;
++ return args;
++ }
++ }
++
++ args->retval = 0;
++ return args;
++}
++
++int
++join_manager_thread(pthread_t manager_tid)
++{
++ struct manager_thread_args * args;
++ if (pthread_join(manager_tid, (void **) &args) == 0) {
++ if (args == NULL) {
++ debug_f("Manager thread returned NULL!");
++ return 1;
++ } else if (args == PTHREAD_CANCELED) {
++ debug_f("Manager thread canceled!");
++ return 1;
++ } else if (args->retval != 0) {
++ debug_f("Manager thread error (%d)", args->retval);
++ free(args);
++ return 1;
++ } else {
++ free(args);
++ return 0;
++ }
++ } else {
++ debug_f("pthread_join error!");
++ return 1;
++ }
++}
++
++void
++chachapoly_free_mt(struct chachapoly_ctx_mt * ctx_mt)
++{
++ if (ctx_mt == NULL)
++ return;
++
++#ifdef OPENSSL_HAVE_POLY_EVP
++ if (ctx_mt->poly_ctx != NULL) {
++ EVP_MAC_CTX_free(ctx_mt->poly_ctx);
++ ctx_mt->poly_ctx = NULL;
++ }
++#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ if (ctx_mt->md_ctx != NULL) {
++ EVP_MD_CTX_free(ctx_mt->md_ctx);
++ ctx_mt->md_ctx = NULL;
++ }
++ if (ctx_mt->pkey != NULL) {
++ EVP_PKEY_free(ctx_mt->pkey);
++ ctx_mt->pkey = NULL;
++ }
++#endif
++
++ /*
++ * Only cleanup the manager threads if we are the PID that initialized
++ * them! If we're a fork, the threads don't really exist.
++ */
++
++ if (getpid() == ctx_mt->mainpid) {
++ if (ctx_mt->manager_tid[0] != ctx_mt->self_tid) {
++ join_manager_thread(ctx_mt->manager_tid[0]);
++ ctx_mt->manager_tid[0] = ctx_mt->self_tid;
++ }
++ if (ctx_mt->manager_tid[1] != ctx_mt->self_tid) {
++ join_manager_thread(ctx_mt->manager_tid[1]);
++ ctx_mt->manager_tid[1] = ctx_mt->self_tid;
++ }
++ }
++
++ /* Cleanup thread data structures. */
++ for (int i=0; i<2; i++)
++ for (int j=0; j<NUMTHREADS; j++)
++ free_threadData(&(ctx_mt->batches[i].tds[j]));
++
++ /* Zero and free the whole multithreaded cipher context. */
++ freezero(ctx_mt, sizeof(*ctx_mt));
++
++ return;
++}
++
++struct chachapoly_ctx_mt *
++chachapoly_new_mt(u_int startseqnr, const u_char * key, u_int keylen)
++{
++ struct chachapoly_ctx_mt * ctx_mt = xmalloc(sizeof(*ctx_mt));
++ memset(ctx_mt, 0, sizeof(*ctx_mt));
++ /* Initialize the sequence number. When rekeying, this won't be zero. */
++ ctx_mt->seqnr = startseqnr;
++ ctx_mt->batchID = startseqnr / NUMSTREAMS;
++ struct threadData mainData;
++ int tDataI;
++ int genKSfailed = 0;
++
++#ifdef OPENSSL_HAVE_POLY_EVP
++ EVP_MAC *mac = NULL;
++ if ((mac = EVP_MAC_fetch(NULL, "POLY1305", NULL)) == NULL)
++ goto fail;
++ if ((ctx_mt->poly_ctx = EVP_MAC_CTX_new(mac)) == NULL)
++ goto fail;
++ EVP_MAC_free(mac); /* no longer needed */
++ mac = NULL;
++#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ if ((ctx_mt->md_ctx = EVP_MD_CTX_new()) == NULL)
++ goto fail;
++ if ((ctx_mt->pkey = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL,
++ ctx_mt->zeros, POLY1305_KEYLEN)) == NULL)
++ goto fail;
++ if (EVP_DigestSignInit(ctx_mt->md_ctx, &ctx_mt->poly_ctx, NULL, NULL,
++ ctx_mt->pkey) == 0)
++ goto fail;
++#else
++ ctx_mt->poly_ctx = NULL;
++#endif
++
++ ctx_mt->batches[ctx_mt->batchID % 2].batchID = ctx_mt->batchID;
++ ctx_mt->batches[(ctx_mt->batchID + 1) % 2].batchID =
++ ctx_mt->batchID + 1;
++
++ /* initialize batches[0] tds */
++ for (tDataI = 0; tDataI < NUMTHREADS; tDataI++) {
++ if (initialize_threadData(&(ctx_mt->batches[0].tds[tDataI]),
++ key) != 0)
++ break;
++ }
++ if (tDataI < NUMTHREADS) {
++ /* Backtrack starting with 'tDataI - 1' */
++ for (tDataI--; tDataI >= 0; tDataI--)
++ free_threadData(&(ctx_mt->batches[0].tds[tDataI]));
++ goto fail;
++ }
++ /* initialize batches[1] tds */
++ for (tDataI = 0; tDataI < NUMTHREADS; tDataI++) {
++ if (initialize_threadData(&(ctx_mt->batches[1].tds[tDataI]),
++ key) != 0)
++ break;
++ }
++ if (tDataI < NUMTHREADS) {
++ /* Backtrack starting with 'tDataI - 1' */
++ for (tDataI--; tDataI >= 0; tDataI--)
++ free_threadData(&(ctx_mt->batches[1].tds[tDataI]));
++ /* Free the batches[0] tds too */
++ for (tDataI = NUMTHREADS - 1; tDataI >= 0; tDataI--)
++ free_threadData(&(ctx_mt->batches[0].tds[tDataI]));
++ goto fail;
++ }
++
++ if (initialize_threadData(&mainData, key) != 0) {
++ chachapoly_free_mt(ctx_mt);
++ explicit_bzero(&startseqnr, sizeof(startseqnr));
++ return NULL;
++ }
++
++ for (int i=0; i<2; i++) {
++ u_int refseqnr = ctx_mt->batches[i].batchID * NUMSTREAMS;
++ for (int j = startseqnr > refseqnr ? startseqnr - refseqnr : 0;
++ j<NUMSTREAMS; j++) {
++ if (generate_keystream(&(ctx_mt->batches[i].streams[j]),
++ refseqnr + j, &mainData, ctx_mt->zeros) == -1) {
++ debug_f("generate_keystream failed in "
++ "chacha20-poly1305@hpnssh.org");
++ genKSfailed = 1;
++ break; /* imperfect, but it helps */
++ }
++ }
++ }
++
++ free_threadData(&mainData);
++
++ if (genKSfailed != 0) {
++ chachapoly_free_mt(ctx_mt);
++ explicit_bzero(&startseqnr, sizeof(startseqnr));
++ return NULL;
++ }
++
++ /* Store the PID so that in the future, we can tell if we're a fork */
++ ctx_mt->mainpid = getpid();
++ ctx_mt->self_tid = pthread_self();
++ ctx_mt->manager_tid[0] = ctx_mt->self_tid;
++ ctx_mt->manager_tid[1] = ctx_mt->self_tid;
++ /* was reporting the TID using gettid() but it's not portable */
++ debug2_f("<main thread: pid=%u, ptid=0x%lx>", getpid(), pthread_self());
++
++ /* Success! */
++ explicit_bzero(&startseqnr, sizeof(startseqnr));
++ return ctx_mt;
++
++ fail:
++#ifdef OPENSSL_HAVE_POLY_EVP
++ EVP_MAC_free(mac);
++ if (ctx_mt->poly_ctx != NULL) {
++ EVP_MAC_CTX_free(ctx_mt->poly_ctx);
++ ctx_mt->poly_ctx = NULL;
++ }
++#elif !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ if (ctx_mt->md_ctx != NULL) {
++ EVP_MD_CTX_free(ctx_mt->md_ctx);
++ ctx_mt->md_ctx = NULL;
++ }
++ if (ctx_mt->pkey != NULL) {
++ EVP_PKEY_free(ctx_mt->pkey);
++ ctx_mt->pkey = NULL;
++ }
++#endif
++ freezero(ctx_mt, sizeof(*ctx_mt));
++ explicit_bzero(&startseqnr, sizeof(startseqnr));
++ return NULL;
++}
++
++/* Portable FastXOR using memcpy to avoid strict-aliasing and alignment UB.
++ * Uses 128-bit chunks on compilers that support __uint128_t (GCC/Clang on
++ * 64-bit platforms), falls back to uint64_t elsewhere. */
++static inline void
++fastXOR2(u_char *dest, const u_char *src, const u_char *keystream, u_int len)
++{
++#if defined(__SIZEOF_INT128__)
++ typedef __uint128_t chunk;
++#else
++ typedef uint64_t chunk;
++#endif
++ size_t i = 0;
++ while (i + sizeof(chunk) <= len) {
++ chunk a, b, r;
++ memcpy(&a, src + i, sizeof(a));
++ memcpy(&b, keystream + i, sizeof(b));
++ r = a ^ b;
++ memcpy(dest + i, &r, sizeof(r));
++ i += sizeof(chunk);
++ }
++ while (i < len) {
++ dest[i] = src[i] ^ keystream[i];
++ i++;
++ }
++}
++
++struct manager_thread_args *
++manager_thread(struct manager_thread_args * margs) {
++ /* make sure we have valid data before proceeding */
++ if (margs == NULL)
++ return NULL;
++
++ struct chachapoly_ctx_mt * ctx_mt = margs->ctx_mt;
++ if (ctx_mt == NULL) {
++ margs->retval = 1;
++ return margs;
++ }
++
++ u_int oldBatchID = margs->oldBatchID;
++
++ struct mt_keystream_batch * batch = &(ctx_mt->batches[oldBatchID % 2]);
++ if (batch->batchID != oldBatchID) {
++ debug_f("Post-crypt batch miss! Seeking %u, found %u. Failing.",
++ oldBatchID, batch->batchID);
++ margs->retval = 1;
++ return margs;
++ }
++
++ margs->retval = 0;
++ u_int batchID = oldBatchID + 2;
++
++ pthread_t tid[NUMTHREADS];
++ struct worker_thread_args * wargs = malloc(NUMTHREADS * sizeof(*wargs));
++ int ti;
++
++ for (ti = 0; ti < NUMTHREADS; ti++) {
++ wargs[ti].batchID = batchID;
++ wargs[ti].batch = batch;
++ wargs[ti].threadIndex = ti;
++ wargs[ti].zeros = ctx_mt->zeros;
++ if (pthread_create(&(tid[ti]), NULL, (void *) worker_thread,
++ &(wargs[ti])) != 0) {
++ margs->retval = 1;
++ break;
++ }
++ }
++ for (; ti < NUMTHREADS; ti++) /* for error condition */
++ tid[ti] = pthread_self();
++
++ struct worker_thread_args * retwargs;
++
++ for (ti = 0; ti < NUMTHREADS; ti++) {
++ if (tid[ti] == pthread_self()) {
++ margs->retval = 1; /* redundant, but harmless */
++ continue;
++ }
++ if (pthread_join(tid[ti], (void **) &retwargs) == 0) {
++ if (retwargs == NULL) {
++ debug_f("Worker thread returned NULL!");
++ margs->retval = 1;
++ } else if (retwargs == PTHREAD_CANCELED) {
++ debug_f("Worker thread canceled!");
++ margs->retval = 1;
++ } else {
++ if (retwargs->retval != 0) {
++ debug_f("Worker thread error (%d)",
++ retwargs->retval);
++ margs->retval = 1;
++ }
++ if (retwargs != &(wargs[ti])) {
++ debug_f("Worker thread didn't return "
++ "expected structure!");
++ margs->retval = 1;
++ }
++ }
++ } else {
++ debug_f("pthread_join error!");
++ margs->retval = 1;
++ }
++ }
++ free(wargs);
++
++ if (margs->retval == 0) {
++ batch->batchID = batchID;
++ }
++
++ return margs;
++}
++
++int
++chachapoly_crypt_mt(struct chachapoly_ctx_mt *ctx_mt, u_int seqnr, u_char *dest,
++ const u_char *src, u_int len, u_int aadlen, u_int authlen, int do_encrypt)
++{
++#ifdef SAFETY
++ if (ctx_mt->mainpid != getpid()) { /* we're a fork */
++ /*
++ * TODO: this is EXTREMELY RARE, may never happen at all (only
++ * if the fork calls crypt), so we should tell the compiler.
++ */
++ /* The worker threads don't exist, we could spawn them? */
++ debug_f("Fork called crypt without workers!");
++ chachapoly_free_mt(ctx_mt);
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++#endif
++
++ pthread_t * manager_tid = &(ctx_mt->manager_tid[ctx_mt->batchID % 2]);
++ if (unlikely(*manager_tid != ctx_mt->self_tid)) {
++ int ret = join_manager_thread(*manager_tid);
++ *manager_tid = ctx_mt->self_tid;
++ if (ret != 0)
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++
++ struct mt_keystream_batch * batch =
++ &(ctx_mt->batches[ctx_mt->batchID % 2]);
++
++ struct mt_keystream * ks = &(batch->streams[seqnr % NUMSTREAMS]);
++
++ int r = SSH_ERR_INTERNAL_ERROR;
++
++#ifdef SAFETY
++ if (batch->batchID == ctx_mt->batchID) { /* Safety check */
++#endif
++ /* check tag before anything else */
++ if (!do_encrypt) {
++ const u_char *tag = src + aadlen + len;
++ u_char expected_tag[POLY1305_TAGLEN];
++#if !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ if ((EVP_PKEY_CTX_ctrl(ctx_mt->poly_ctx, -1,
++ EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_MAC_KEY,
++ POLY1305_KEYLEN, ks->poly_key) <= 0) ||
++ (EVP_DigestSignUpdate(ctx_mt->md_ctx, src, aadlen + len) == 0)) {
++ debug_f("SSL error while decrypting poly1305 tag");
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++ ctx_mt->ptaglen = POLY1305_TAGLEN;
++ if (EVP_DigestSignFinal(ctx_mt->md_ctx, expected_tag,
++ &ctx_mt->ptaglen) == 0) {
++ debug_f("SSL error while finalizing decyrpted poly1305");
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++#else
++ poly1305_auth(ctx_mt->poly_ctx, expected_tag, src,
++ aadlen + len, ks->poly_key);
++#endif
++ if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN)
++ != 0)
++ r = SSH_ERR_MAC_INVALID;
++ explicit_bzero(expected_tag, sizeof(expected_tag));
++ }
++ if (r != SSH_ERR_MAC_INVALID) {
++ /* Crypt additional data (i.e., packet length) */
++ /* TODO: is aadlen always four bytes? */
++ /* TODO: do we always have an aadlen? */
++ if (aadlen)
++ for (u_int i=0; i<aadlen; i++)
++ dest[i] = ks->headerStream[i] ^ src[i];
++ /* Crypt payload */
++ fastXOR2(dest+aadlen,src+aadlen,ks->mainStream,len);
++ /* calculate and append tag */
++#if !defined(WITH_OPENSSL3) && defined(EVP_PKEY_POLY1305)
++ if (do_encrypt) {
++ if ((EVP_PKEY_CTX_ctrl(ctx_mt->poly_ctx, -1,
++ EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_MAC_KEY,
++ POLY1305_KEYLEN, ks->poly_key) <=0) ||
++ (EVP_DigestSignUpdate(ctx_mt->md_ctx, dest, aadlen + len) == 0)) {
++ debug_f ("SSL error while encrypting poly1305 tag");
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++ ctx_mt->ptaglen = POLY1305_TAGLEN;
++ if (EVP_DigestSignFinal(ctx_mt->md_ctx, dest+aadlen+len,
++ &ctx_mt->ptaglen) == 0) {
++ debug_f("SSL error while finalizing decyrpted poly1305");
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++ }
++#else
++ if (do_encrypt)
++ poly1305_auth(ctx_mt->poly_ctx, dest+aadlen+len,
++ dest, aadlen+len, ks->poly_key);
++#endif
++ r=0; /* Success! */
++ }
++ if (r) /* Anything nonzero is an error. */
++ return r;
++
++ ctx_mt->seqnr = seqnr + 1;
++
++ if (unlikely(ctx_mt->seqnr / NUMSTREAMS > ctx_mt->batchID)) {
++ struct manager_thread_args * args =
++ malloc(sizeof(*args));
++ if (args == NULL) {
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++ args->ctx_mt = ctx_mt;
++ args->oldBatchID = ctx_mt->batchID;
++ if (pthread_create(&(ctx_mt->manager_tid[ctx_mt->batchID
++ % 2]), NULL, (void *) manager_thread, args) != 0) {
++ free(args);
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++ ctx_mt->batchID = ctx_mt->seqnr / NUMSTREAMS;
++ }
++
++ /* TODO: Nothing we need to sanitize here? */
++
++ return 0;
++#ifdef SAFETY
++ } else { /* Bad, it's the wrong batch. */
++ debug_f( "Pre-crypt batch miss! Seeking %u, found %u. Failing.",
++ ctx_mt->batchID, batch->batchID);
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++#endif
++}
++
++int
++chachapoly_get_length_mt(struct chachapoly_ctx_mt *ctx_mt, u_int *plenp,
++ u_int seqnr, const u_char *cp, u_int len)
++{
++ /* TODO: add compiler hints */
++#ifdef SAFETY
++ if (ctx_mt->mainpid != getpid()) { /* Use serial mode if we're a fork */
++ debug_f("We're a fork. Failing.");
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++#endif
++
++ if (len < 4)
++ return SSH_ERR_MESSAGE_INCOMPLETE;
++
++ pthread_t * manager_tid = &(ctx_mt->manager_tid[ctx_mt->batchID % 2]);
++ if (unlikely(*manager_tid != ctx_mt->self_tid)) {
++ int ret = join_manager_thread(*manager_tid);
++ *manager_tid = ctx_mt->self_tid;
++ if (ret != 0)
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++
++ u_char buf[4];
++#ifdef SAFETY
++ u_int sought_batchID = seqnr / NUMSTREAMS;
++#endif
++ struct mt_keystream_batch * batch =
++ &(ctx_mt->batches[ctx_mt->batchID % 2]);
++ struct mt_keystream * ks = &(batch->streams[seqnr % NUMSTREAMS]);
++#ifdef SAFETY
++ if (batch->batchID == sought_batchID) {
++#endif
++ for (u_int i=0; i < sizeof(buf); i++)
++ buf[i]=ks->headerStream[i] ^ cp[i];
++ *plenp = PEEK_U32(buf);
++ return 0;
++#ifdef SAFETY
++ } else {
++ debug_f("Batch miss! Seeking %u, found %u. Failing.",
++ sought_batchID, batch->batchID);
++ return SSH_ERR_INTERNAL_ERROR;
++ }
++#endif
++}
++#endif /* defined(HAVE_EVP_CHACHA20) && !defined(HAVE_BROKEN_CHACHA20) */
+diff -Nur openssh-10.5p1.orig/cipher-chachapoly-libcrypto-mt.h openssh-10.5p1/cipher-chachapoly-libcrypto-mt.h
+--- openssh-10.5p1.orig/cipher-chachapoly-libcrypto-mt.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-chachapoly-libcrypto-mt.h 2026-09-21 07:06:59.884458798 +0200
+@@ -0,0 +1,45 @@
++/*
++ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Mitchell Dorrell <mwd@psc.edu>
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++#ifndef CHACHA_POLY_LIBCRYPTO_MT_H
++#define CHACHA_POLY_LIBCRYPTO_MT_H
++
++#include <sys/types.h>
++#include "chacha.h"
++#include "poly1305.h"
++
++#ifndef CHACHA_KEYLEN
++#define CHACHA_KEYLEN 32 /* Only 256 bit keys used here */
++#endif
++
++struct chachapoly_ctx_mt; /* defined in cipher-chachapoly-libcrypto-mt.c */
++
++struct chachapoly_ctx_mt *chachapoly_new_mt(u_int startseqnr, const u_char *key, u_int keylen)
++ __attribute__((__bounded__(__buffer__, 2, 3)));
++
++void chachapoly_free_mt(struct chachapoly_ctx_mt *cpctx);
++
++int chachapoly_crypt_mt(struct chachapoly_ctx_mt *cpctx, u_int seqnr,
++ u_char *dest, const u_char *src, u_int len, u_int aadlen,
++ u_int authlen, int do_encrypt);
++
++int chachapoly_get_length_mt(struct chachapoly_ctx_mt *cpctx,
++ u_int *plenp, u_int seqnr, const u_char *cp, u_int len)
++ __attribute__((__bounded__(__buffer__, 4, 5)));
++
++#endif /* CHACHA_POLY_LIBCRYPTO_MT_H */
+diff -Nur openssh-10.5p1.orig/cipher-ctr-mt.c openssh-10.5p1/cipher-ctr-mt.c
+--- openssh-10.5p1.orig/cipher-ctr-mt.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-ctr-mt.c 2026-09-21 07:06:59.884609395 +0200
+@@ -0,0 +1,681 @@
++/*
++ * OpenSSH Multi-threaded AES-CTR Cipher
++ *
++ * Author: Benjamin Bennett <ben@psc.edu>
++ * Author: Mike Tasota <tasota@gmail.com>
++ * Author: Chris Rapier <rapier@psc.edu>
++ * 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 <markus@openbsd.org>
++ *
++ * 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) && !defined(WITH_OPENSSL3)
++#include <sys/types.h>
++
++#include <stdarg.h>
++#include <string.h>
++
++#include <openssl/evp.h>
++
++#include "xmalloc.h"
++#include "log.h"
++#include <unistd.h>
++#include "uthash.h"
++
++/* compatibility with old or broken OpenSSL versions */
++#include "openbsd-compat/openssl-compat.h"
++
++#ifndef USE_BUILTIN_RIJNDAEL
++#include <openssl/aes.h>
++#endif
++
++#include <pthread.h>
++
++#ifdef __APPLE__
++#include <sys/types.h>
++#include <sys/sysctl.h>
++#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 * volatile 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,
++ 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 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
++ /* with GCC 13 we have having consistent seg faults
++ * in this section of code. Since this is a critical
++ * code path we are removing this until we have a solution
++ * in place -cjr 02/22/24
++ * TODO: FIX THIS
++ */
++ /* if ((align & 0xf) == 0) { */
++ /* destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; */
++ /* } else */
++#endif
++ /* this is causing undefined behaviour in sanitizers
++ * this is annoying because it's more efficient
++ * but UB is not something I want to retain */
++ /* 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;
++}
++
++/* <friedl> */
++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 Check */
+diff -Nur openssh-10.5p1.orig/cipher-ctr-mt-functions.c openssh-10.5p1/cipher-ctr-mt-functions.c
+--- openssh-10.5p1.orig/cipher-ctr-mt-functions.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-ctr-mt-functions.c 2026-09-21 07:06:59.884802406 +0200
+@@ -0,0 +1,683 @@
++/*
++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
++ *
++ * Author: Benjamin Bennett <ben@psc.edu>
++ * Author: Mike Tasota <tasota@gmail.com>
++ * Author: Chris Rapier <rapier@psc.edu>
++ * 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 <markus@openbsd.org>
++ *
++ * 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"
++
++/* only for systems with OSSL 3 */
++#ifdef WITH_OPENSSL3
++#include <stdarg.h>
++#include <string.h>
++#include <openssl/evp.h>
++#include "xmalloc.h"
++#include <unistd.h>
++#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);
++}
++
++/* Check if we should exit, we are doing both cancel and exit condition
++ * since threads can fail to notice that they have been cancelled: on OSX
++ * natively, and on any platform when running under valgrind, whose
++ * scheduler does not deliver a cancel to a thread parked in
++ * pthread_cond_wait(). We want 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);
++}
++
++/*
++ * Helper function to terminate the helper threads
++ */
++static void
++stop_and_join_pregen_threads(struct aes_mt_ctx_st *aes_mt_ctx)
++{
++ int i;
++
++ /* notify threads that they should exit */
++ pthread_rwlock_wrlock(&aes_mt_ctx->stop_lock);
++ aes_mt_ctx->exit_flag = 1;
++ pthread_rwlock_unlock(&aes_mt_ctx->stop_lock);
++
++ /*
++ * Wake anything parked in pthread_cond_wait() so that it rechecks
++ * the exit flag and leaves via thread_loop_check_exit(). Without
++ * this a parked thread can only be removed by pthread_cancel(),
++ * which leaves the join below blocked indefinitely whenever that
++ * cancel is not delivered.
++ */
++ for (i = 0; i < numkq; i++) {
++ pthread_mutex_lock(&aes_mt_ctx->q[i].lock);
++ pthread_cond_broadcast(&aes_mt_ctx->q[i].cond);
++ pthread_mutex_unlock(&aes_mt_ctx->q[i].lock);
++ }
++
++ /* 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 * volatile evp_ctx;
++ struct aes_mt_ctx_st *aes_mt_ctx = job;
++ struct kq *q;
++ struct aes_mt_ctx_ptrs *ptr;
++ 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.
++ * We init qidx here because if we do it at the top of the function
++ * we get a warning about it possibly being clobbered. The exact reason
++ * doesn't make a lot of sense but it has to happen after the
++ * first pthread_rwlock_rdlock(). Might have something to do with
++ * incorrect compiler optimizations.
++ */
++ int qidx;
++ 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);
++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL);
++ aes_mt_ctx->exit_flag = 0;
++
++ 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);
++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL);
++ aes_mt_ctx->exit_flag = 0;
++
++ 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);
++ pthread_rwlock_init(&aes_mt_ctx->stop_lock, NULL);
++ aes_mt_ctx->exit_flag = 0;
++
++ 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);
++
++ /* reset the exit flag */
++ aes_mt_ctx->exit_flag = 0;
++
++ /* 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. Make sure we have enough stack space (under alpine)
++ * and aren't using more than we need (linux). This can be as low as
++ * 512KB but that's a minimum. 1024KB gives us a little headroom if we
++ * need it */
++#define STACK_SIZE (1024 * 1024)
++ pthread_attr_t attr;
++ pthread_attr_init(&attr);
++ pthread_attr_setstacksize(&attr, STACK_SIZE);
++ for (int i = 0; i < cipher_threads; i++) {
++ pthread_rwlock_wrlock(&aes_mt_ctx->tid_lock);
++ if (pthread_create(&aes_mt_ctx->tid[i], &attr, 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;
++ uint64_t src_a, key_a;
++
++ 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
++ /* with GCC 13 we have having consistent seg faults
++ * in this section of code. Since this is a critical
++ * code path we are removing this until we have a solution
++ * in place -cjr 02/22/24
++ * TODO: FIX THIS
++ */
++ /* if ((align & 0xf) == 0) { */
++ /* destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; */
++ /* } else */
++#endif
++ /* 64 bits */
++ /* this is causing undefined behaviour in sanitizers
++ * this is annoying because it's more efficient
++ * but UB is not something I want to retain */
++ if ((align & 0x7) == 0) {
++ /* this should resolve the strict aliasing UB
++ * and the performance doesn't seem to change much
++ * but the memcpys are killing me 3-5-26 cjr*/
++ memcpy(&src_a, &srcp.u64[0], sizeof(uint64_t));
++ memcpy(&key_a, &bufp.u64[0], sizeof(uint64_t));
++ destp.u64[0] = src_a ^ key_a;
++ memcpy(&src_a, &srcp.u64[1], sizeof(uint64_t));
++ memcpy(&key_a, &bufp.u64[1], sizeof(uint64_t));
++ destp.u64[1] = src_a ^ key_a;
++ /* 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 /*WITH_OPENSSL3*/
+diff -Nur openssh-10.5p1.orig/cipher-ctr-mt-functions.h openssh-10.5p1/cipher-ctr-mt-functions.h
+--- openssh-10.5p1.orig/cipher-ctr-mt-functions.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-ctr-mt-functions.h 2026-09-21 07:06:59.884931576 +0200
+@@ -0,0 +1,140 @@
++/*
++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
++ *
++ * Author: Benjamin Bennett <ben@psc.edu>
++ * Author: Mike Tasota <tasota@gmail.com>
++ * Author: Chris Rapier <rapier@psc.edu>
++ * 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 <markus@openbsd.org>
++ *
++ * 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 <sys/types.h>
++#include <pthread.h>
++#include "cipher-aesctr.h"
++#include "uthash.h"
++
++#ifndef USE_BUILTIN_RIJNDAEL
++#include <openssl/aes.h>
++#endif
++
++/* only for systems with OSSL 3 */
++#ifdef WITH_OPENSSL3
++
++/*-------------------- 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 */
++ pthread_rwlock_t stop_lock;
++ int exit_flag;
++ 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 /* WITH OPENSSL */
++#endif /* CTR_MT_FUNCS */
+diff -Nur openssh-10.5p1.orig/cipher-ctr-mt-provider.c openssh-10.5p1/cipher-ctr-mt-provider.c
+--- openssh-10.5p1.orig/cipher-ctr-mt-provider.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-ctr-mt-provider.c 2026-09-21 07:06:59.885046654 +0200
+@@ -0,0 +1,390 @@
++/*
++ * OpenSSH Multi-threaded AES-CTR Cipher Provider for OpenSSL 3
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ * 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"
++
++/* only for systems with OSSL 3.0+ */
++#ifdef WITH_OPENSSL3
++
++#include <sys/types.h>
++#include <string.h>
++#include <openssl/core.h>
++#include <openssl/core_dispatch.h>
++#include <openssl/core_names.h>
++#include <openssl/err.h>
++#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, NULL },
++ { "aes_ctr_mt_192", "provider=hpnssh", aes_mt_funcs_192, NULL },
++ { "aes_ctr_mt_128", "provider=hpnssh", aes_mt_funcs_128, NULL },
++ { NULL, 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 /*WITH_OPENSSL3*/
+diff -Nur openssh-10.5p1.orig/cipher.h openssh-10.5p1/cipher.h
+--- openssh-10.5p1.orig/cipher.h 2026-09-21 07:06:05.290229807 +0200
++++ openssh-10.5p1/cipher.h 2026-09-21 10:45:45.175299310 +0200
+@@ -42,11 +42,17 @@
+ #include <openssl/evp.h>
+ #endif
+ #include "cipher-chachapoly.h"
++#ifdef WITH_OPENSSL
++#include "cipher-chachapoly-libcrypto-mt.h"
++#endif
+ #include "cipher-aesctr.h"
+
+ #define CIPHER_ENCRYPT 1
+ #define CIPHER_DECRYPT 0
+
++#define CIPHER_MULTITHREAD 1
++#define CIPHER_SERIAL 0
++
+ struct sshcipher {
+ char *name;
+ u_int block_size;
+@@ -60,6 +66,7 @@
+ #define CFLAG_NONE (1<<3)
+ #define CFLAG_INTERNAL CFLAG_NONE /* Don't use "none" for packets */
+ #ifdef WITH_OPENSSL
++#define CFLAG_MT (1<<4)
+ const EVP_CIPHER *(*evptype)(void);
+ #else
+ void *ignored;
+@@ -68,24 +75,27 @@
+
+ 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, 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 *);
+ u_int cipher_is_internal(const struct sshcipher *);
+
+ u_int cipher_ctx_is_plaintext(struct sshcipher_ctx *);
+diff -Nur openssh-10.5p1.orig/cipher-switch.c openssh-10.5p1/cipher-switch.c
+--- openssh-10.5p1.orig/cipher-switch.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-switch.c 2026-09-21 07:06:59.885421039 +0200
+@@ -0,0 +1,77 @@
++/*
++ * Copyright (c) 2023 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++/* This provides the function to switch from a serial to parallel
++ * cipher. This has been moved into it's own file in order to make it
++ * available to both the client and server without having to clutter
++ * up other files.
++ */
++
++#include "includes.h"
++#include <sys/types.h>
++#include <string.h>
++#include "cipher.h"
++#include "log.h"
++#include "packet.h"
++
++
++/* if we are using a parallel cipher there can be issues in either
++ * a fork or sandbox. Essentially, if we switch too early the
++ * threads get lost and the application hangs. So what we do is
++ * test if either the send or receive context cipher name
++ * matches the known available parallel ciphers. If it does
++ * then we force a rekey which automatically loads the parallel
++ * cipher. */
++
++void
++cipher_switch(struct ssh *ssh) {
++#ifdef WITH_OPENSSL
++ /* get the send and receive context and extract the cipher name */
++ const void *send_cc = ssh_packet_get_send_context(ssh);
++ const void *recv_cc = ssh_packet_get_receive_context(ssh);
++ const char *send = cipher_ctx_name(send_cc);
++ const char *recv = cipher_ctx_name(recv_cc);
++
++ debug_f("Send: %s Recv: %s", send, recv);
++
++ /* if the name of the cipher matches then we set the context
++ * to authenticated (it likely already is though) and then
++ * force the rekey. Either side can do this. One downside of
++ * this method is that both sides can request a rekey so you
++ * can end up duplicating work. This is annoying but the
++ * performance gains make it worthwhile. Also I
++ * use strstr here because strcmp would require a 6 part
++ * if statement */
++ if (strstr(send, "ctr") || strstr(recv, "ctr")) {
++ debug("Serial to parallel AES-CTR cipher swap");
++ /* cipher_reset_multithreaded(); */
++ /* mark MT active so the promotion in ssh_set_newkeys only
++ * happens when we deliberately switched here. Without this a
++ * later rekey would promote to MT even under DisableMTAES. */
++ ssh->mt_enabled = 1;
++ ssh_packet_set_authenticated(ssh);
++ packet_request_rekeying();
++ }
++ /* do the same for multithreaded chacha20 but with strcmp */
++ if ((strcmp(send, "chacha20-poly1305@openssh.com") == 0) ||
++ (strcmp(recv, "chacha20-poly1305@openssh.com") == 0)) {
++ debug("Serial to parallel Chacha20-poly1305 cipher swap");
++ ssh_packet_set_authenticated(ssh);
++ packet_request_rekeying();
++ }
++#endif
++}
+diff -Nur openssh-10.5p1.orig/cipher-switch.h openssh-10.5p1/cipher-switch.h
+--- openssh-10.5p1.orig/cipher-switch.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/cipher-switch.h 2026-09-21 07:06:59.885482940 +0200
+@@ -0,0 +1,18 @@
++/*
++ * Copyright (c) 2015 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++void cipher_switch (struct ssh *);
+diff -Nur openssh-10.5p1.orig/clientloop.c openssh-10.5p1/clientloop.c
+--- openssh-10.5p1.orig/clientloop.c 2026-09-21 07:06:04.976528386 +0200
++++ openssh-10.5p1/clientloop.c 2026-09-21 07:06:59.885729543 +0200
+@@ -100,6 +100,7 @@
+ #include "match.h"
+ #include "ssherr.h"
+ #include "hostfile.h"
++#include "metrics.h"
+
+ #ifdef GSSAPI
+ #include "ssh-gss.h"
+@@ -160,6 +161,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 {
+@@ -186,6 +191,8 @@
+ static struct global_confirms global_confirms =
+ TAILQ_HEAD_INITIALIZER(global_confirms);
+
++void client_request_metrics(struct ssh *);
++
+ static void quit_message(const char *fmt, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+@@ -1462,6 +1469,7 @@
+ double start_time, total_time;
+ int interactive = -1, channel_did_enqueue = 0, r;
+ uint64_t ibytes, obytes;
++ time_t previous_time;
+ int conn_in_ready, conn_out_ready;
+ sigset_t bsigset, osigset;
+
+@@ -1505,6 +1513,7 @@
+ client_repledge();
+
+ start_time = monotime_double();
++ previous_time = time(NULL); /* for metrics polling */
+
+ /* Initialize variables. */
+ last_was_cr = 1;
+@@ -1549,6 +1558,8 @@
+ }
+
+ schedule_server_alive_check();
++ if (options.metrics)
++ client_request_metrics(ssh); /* initial metrics polling */
+
+ if (sigemptyset(&bsigset) == -1 ||
+ sigaddset(&bsigset, SIGHUP) == -1 ||
+@@ -1561,8 +1572,27 @@
+ error_f("bsigset setup: %s", strerror(errno));
+ #endif
+
++ /*
++ * Block termination/info signals for the duration of the main loop.
++ * ppoll() atomically restores the old mask (osigset) while waiting,
++ * so signals are still delivered promptly during the poll. Keeping
++ * them blocked during the data-processing phase avoids two
++ * sigprocmask syscalls per iteration (~76k/sec during bulk transfer,
++ * ~5.6% of CPU in profiles). Signal-flag checks (quit_pending,
++ * siginfo_received) still work because the handlers run inside
++ * ppoll's atomic unblock window.
++ */
++ if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
++ error_f("bsigset sigprocmask: %s", strerror(errno));
++
+ /* 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);
++ }
++ }
+ channel_did_enqueue = 0;
+
+ /* Process buffered packets sent by the server. */
+@@ -1597,8 +1627,6 @@
+ * Wait until we have something to do (something becomes
+ * available on one of the descriptors).
+ */
+- if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
+- error_f("bsigset sigprocmask: %s", strerror(errno));
+ if (siginfo_received) {
+ char ident[256];
+
+@@ -1612,8 +1640,6 @@
+ client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc,
+ &npfd_active, channel_did_enqueue, &osigset,
+ &conn_in_ready, &conn_out_ready);
+- if (sigprocmask(SIG_SETMASK, &osigset, NULL) == -1)
+- error_f("osigset sigprocmask: %s", strerror(errno));
+
+ if (quit_pending)
+ break;
+@@ -1669,6 +1695,14 @@
+ }
+ }
+ }
++
++ /* Restore the pre-loop signal mask now that we are done polling. */
++ if (sigprocmask(SIG_SETMASK, &osigset, NULL) == -1)
++ error_f("osigset sigprocmask: %s", strerror(errno));
++
++ if (options.metrics)
++ client_request_metrics(ssh); /* final metrics polling */
++
+ free(pfd);
+
+ /* Terminate the session. */
+@@ -2682,6 +2716,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_f("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, uint32_t seq, struct ssh *ssh)
+ {
+@@ -2804,6 +2999,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_PREFIX) {
++ 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_f("Rewriting scp command for hpnscp.");
++ sprintf(new_cmd, "hpn%s", (const u_char*)sshbuf_ptr(cmd));
++ debug_f("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 ((cmdstring = sshbuf_dup_string(cmd)) == NULL)
+ fatal_f("sshbuf_dup_string failed");
+ if (len > 900)
+diff -Nur openssh-10.5p1.orig/compat.c openssh-10.5p1/compat.c
+--- openssh-10.5p1.orig/compat.c 2026-09-21 07:06:05.453267853 +0200
++++ openssh-10.5p1/compat.c 2026-09-21 07:51:15.782552833 +0200
+@@ -29,6 +29,7 @@
+
+ #include <stdint.h>
+ #include <stdlib.h>
++#include <string.h>
+ #include <stdarg.h>
+
+ #include "xmalloc.h"
+@@ -123,6 +124,8 @@
+ SSH_BUG_UTF8TTYMODE },
+ { "Twisted_*", 0 },
+ { "Twisted*", SSH_BUG_DEBUG },
++ /* MobaXterm's SFTP engine, see kex_input_newkeys */
++ { "SecureBlackbox*", SSH_BUG_STRICT_KEX_REKEY },
+ { NULL, 0 }
+ };
+
+@@ -136,6 +139,35 @@
+ 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) {
++ /* check if the remote is hpn and if the version
++ * uses hpn prefixed binaries */
++ const char *op;
++ if ((op = strstr(version, "hpn")) != NULL) {
++ int hpnver = 0;
++ ssh->compat |= SSH_HPNSSH;
++ debug("Remote is HPN enabled");
++ if (sscanf(op, "hpn%d", &hpnver) == 1 &&
++ hpnver >= 16) {
++ ssh->compat |= SSH_HPNSSH_PREFIX;
++ debug("Remote uses HPNSSH prefixes.");
++ }
++ }
++ /* Restrict advertised window for non-HPN OpenSSH >= 8.9. */
++ if (!(ssh->compat & SSH_HPNSSH)) {
++ const char *op;
++ int omaj = 0, omin = 0;
++ if ((op = strstr(version, "OpenSSH_")) != NULL &&
++ sscanf(op, "OpenSSH_%d.%d", &omaj, &omin) == 2 &&
++ (omaj >= 9 || (omaj == 8 && omin >= 9))) {
++ ssh->compat |= SSH_RESTRICT_WINDOW;
++ debug("Restricting advertised window size.");
++ }
++ }
++ }
++ debug("ssh->compat is %u", ssh->compat);
+ return;
+ }
+ }
+diff -Nur openssh-10.5p1.orig/compat.h openssh-10.5p1/compat.h
+--- openssh-10.5p1.orig/compat.h 2026-09-21 07:06:05.453406993 +0200
++++ openssh-10.5p1/compat.h 2026-09-21 10:47:18.185337772 +0200
+@@ -46,12 +46,13 @@
+ /* #define unused 0x00010000 */
+ /* #define unused 0x00020000 */
+ /* #define unused 0x00040000 */
+-/* #define unused 0x00100000 */
++#define SSH_BUG_STRICT_KEX_REKEY 0x00080000 /* peer wants kex-strict on rekeys */
++#define SSH_HPNSSH 0x00100000 /* basically a notice that this is HPN aware */
+ #define SSH_BUG_EXTEOF 0x00200000
+ #define SSH_BUG_PROBE 0x00400000
+-/* #define unused 0x00800000 */
++#define SSH_RESTRICT_WINDOW 0x00800000 /* restrict advertised window to OpenSSH clients */
+ #define SSH_OLD_FORWARD_ADDR 0x01000000
+-/* #define unused 0x02000000 */
++#define SSH_HPNSSH_PREFIX 0x02000000 /* indicates that we have hpn prefixes binaries */
+ #define SSH_NEW_OPENSSH 0x04000000
+ #define SSH_BUG_DYNAMIC_RPORT 0x08000000
+ #define SSH_BUG_CURVE25519PAD 0x10000000
+diff -Nur openssh-10.5p1.orig/configure.ac openssh-10.5p1/configure.ac
+--- openssh-10.5p1.orig/configure.ac 2026-09-21 07:06:05.558185584 +0200
++++ openssh-10.5p1/configure.ac 2026-09-21 07:06:59.886861411 +0200
+@@ -2762,6 +2762,39 @@
+ )
+ fi
+
++
++# Testing MPTCP Support
++# Does the OS support MPTCP?
++# We don't use this at the moment
++# but I am holding it in resrve -cjr 04/04/2025
++ AC_MSG_CHECKING([whether the OS supports MPTCP])
++ AC_RUN_IFELSE(
++ [AC_LANG_PROGRAM([[
++ #include <stdio.h>
++ #include <stdlib.h>
++ #include <unistd.h>
++ #include <sys/types.h>
++ #include <sys/socket.h>
++ #include <stdbool.h>
++ #include <netinet/in.h>
++
++ ]], [[
++ int sock = -1;
++ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
++ if (sock < 0) {
++ exit(1);
++ }
++ ]])],
++ [
++ AC_MSG_RESULT([yes])
++ AC_DEFINE([HAVE_MPTCP], [1],
++ [OS Supports MPTCP])
++ ],
++ [
++ AC_MSG_RESULT([no])
++ ]
++ )
++
+ if test "x$ac_cv_func_getaddrinfo" = "xyes" && \
+ test "x$check_for_hpux_broken_getaddrinfo" = "x1"; then
+ AC_MSG_CHECKING([if getaddrinfo seems to work])
+@@ -3178,8 +3211,8 @@
+ 200*) openssl_impl="libressl"
+ lver=`echo "$sslver" | sed 's/.*libressl-//'`
+ case "$lver" in
+- 2*|300*) # 2.x, 3.0.0
+- AC_MSG_ERROR([LibreSSL >= 3.1.0 required (have "$ssl_showver")])
++ 2*|300*|301*|302*|303*|304*|306*) # 2.x, 3.0.0
++ AC_MSG_ERROR([LibreSSL >= 3.7.0 required (have "$ssl_showver")])
+ ;;
+ *) ;; # Assume all other versions are good.
+ esac
+@@ -3188,6 +3221,7 @@
+ # OpenSSL 3 & 4; we use the 1.1x API
+ # https://openssl.org/policies/general/versioning-policy.html
+ CPPFLAGS="$CPPFLAGS -DOPENSSL_API_COMPAT=0x10100000L"
++ AC_DEFINE([WITH_OPENSSL3], [1], [With OpenSSL3])
+ ;;
+ *)
+ AC_MSG_ERROR([Unknown/unsupported OpenSSL version ("$ssl_showver")])
+@@ -3313,6 +3347,29 @@
+ EVP_CIPHER_CTX_set_iv \
+ ])
+
++ # 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 <stdlib.h>
++ #include <stdio.h>
++ #include <openssl/evp.h>
++ ]], [[
++ 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_CHECK_HEADERS([openssl/opensslconf.h openssl/configuration.h])
+ AC_MSG_CHECKING([for OpenSSL ENGINE support])
+diff -Nur openssh-10.5p1.orig/defines.h openssh-10.5p1/defines.h
+--- openssh-10.5p1.orig/defines.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/defines.h 2026-09-21 07:06:59.887377376 +0200
+@@ -951,7 +951,11 @@
+ #endif
+
+ #ifndef SSH_IOBUFSZ
+-# define SSH_IOBUFSZ 8192
++# define SSH_IOBUFSZ (32*1024)
++#endif
++
++#ifndef IPPROTO_MPTCP
++#define IPPROTO_MPTCP 262
+ #endif
+
+ /*
+@@ -1008,3 +1012,12 @@
+ #endif
+
+ #endif /* _DEFINES_H */
++
++/* used to enable checking linux kernel versions */
++#if defined(__linux__) && !defined(__alpine__)
++#include <linux/version.h>
++#endif
++
++#ifndef KERNEL_VERSION /* shouldn't be necessary to define this */
++#define KERNEL_VERSION(a,b,c) (((a) <<16) + ((b) << 8) +(c))
++#endif
+diff -Nur openssh-10.5p1.orig/digest.h openssh-10.5p1/digest.h
+--- openssh-10.5p1.orig/digest.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/digest.h 2026-09-21 07:06:59.887745693 +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-10.5p1.orig/digest-openssl.c openssh-10.5p1/digest-openssl.c
+--- openssh-10.5p1.orig/digest-openssl.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/digest-openssl.c 2026-09-21 07:06:59.887937042 +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-10.5p1.orig/FUNDING.yml openssh-10.5p1/FUNDING.yml
+--- openssh-10.5p1.orig/FUNDING.yml 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/FUNDING.yml 2026-09-21 07:06:59.888132100 +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-10.5p1.orig/happyeyeballs.c openssh-10.5p1/happyeyeballs.c
+--- openssh-10.5p1.orig/happyeyeballs.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/happyeyeballs.c 2026-09-21 07:06:59.888233826 +0200
+@@ -0,0 +1,313 @@
++/*
++ * Copyright (c) 2025 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ * Author: Kim Mihn Kaplan (kaplan at kim-mihn.com)
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++/* This is an implementation of RFC 8305 based on a patch
++ * supplied by Kim Minh Kaplan (kaplan at kim-minh.com).
++ * Information about RFC 8305 can be found at
++ * https://www.rfc-editor.org/rfc/rfc8305
++ * This version *does not* provide support for
++ * Section 3 where asychronous polling of DNS servers
++ * is a SHOULD.
++ * Section 4 requires sorting of DNS results as per
++ * RFC 6724. This is provided by the getaddrinfo call
++ * from gcc. This may not be available via musl.
++ * Interleaving of addresses is not currently supported.
++ * Section 5 is implemented.
++ * Section 6 is not implemented outside of getaddrinfo
++ * Section 7 in under consideration
++ */
++
++
++/* NOTE: There are a lot of debug statements in here
++ * because we are still treating this as somewhat
++ * experimental. A future version will promote this
++ * to stable and we'll remove a lot of the level 2
++ * debug statements then.
++ */
++
++#include "includes.h"
++#ifdef HAVE_SYS_TIME_H
++# include <sys/time.h>
++#endif
++#include <stdlib.h>
++#include <net/if.h>
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <string.h>
++#include <unistd.h>
++
++#include "happyeyeballs.h"
++#include "sshconnect.h"
++#include "ssh.h"
++#include "misc.h"
++#include "log.h"
++#include "readconf.h"
++
++/* this is from sshconnect.c */
++extern int ssh_create_socket(struct addrinfo *ai);
++
++/* get the options struct if for the delay time */
++extern Options options;
++
++/* have we timed out? */
++static int
++timeout(struct timeval *tv, int timeout_ms)
++ {
++ if (timeout_ms <= 0)
++ return 0;
++ ms_subtract_diff(tv, &timeout_ms);
++ return timeout_ms <= 0;
++}
++
++/* used to provide debug message in sshconnect */
++char global_ntop[NI_MAXHOST];
++
++/* used to provide information for debug statements */
++char *return_fam(int fam) {
++ if (fam == 10)
++ return "IPv6";
++ else
++ return "IPv4";
++}
++
++/*
++ * Return 0 if the addrinfo was not tried. Return -1 if using it
++ * failed. Return 1 if it was used.
++ */
++static int
++happy_eyeballs_initiate(const char *host, struct addrinfo *ai,
++ int *timeout_ms,
++ struct timeval *initiate,
++ int *nfds, fd_set *fds,
++ struct addrinfo *fd_ai[])
++{
++ int oerrno, sock;
++ char ntop[NI_MAXHOST], strport[NI_MAXSERV];
++
++ debug2_f ("Happy eyeballs initiating");
++ memset(ntop, 0, sizeof(ntop));
++ memset(strport, 0, sizeof(strport));
++ /* If *nfds != 0 then *initiate is initialised. */
++ if (*nfds &&
++ (ai == NULL ||
++ !timeout(initiate, options.happy_delay))) {
++ /* Do not initiate new connections yet */
++ debug2_f ("Waiting to initiate new connection");
++ return 0;
++ }
++ /* trying to use a family we don't support */
++ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
++ debug2_f ("Address family not supported");
++ errno = EAFNOSUPPORT;
++ return -1;
++ }
++
++ debug2_f ("Running getnameinfo for %s and %d", host, ai->ai_family);
++ if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
++ ntop, sizeof(ntop),
++ strport, sizeof(strport),
++ NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
++ oerrno = errno;
++ error("%s: getnameinfo failed", __func__);
++ errno = oerrno;
++ return -1;
++ }
++ memcpy(global_ntop,ntop,sizeof(ntop));
++ debug2_f("RFC 8305 connecting to %.200s [%.100s] port %s.",
++ host, ntop, strport);
++ debug2_f("RFC 8305: %.200s", global_ntop);
++ /* Create a socket for connecting */
++ sock = ssh_create_socket(ai);
++ if (sock < 0) {
++ /* Any error is already output */
++ errno = 0;
++ return -1;
++ }
++ if (sock >= FD_SETSIZE) {
++ error("socket number to big for select: %d", sock);
++ close(sock);
++ return -1;
++ }
++ fd_ai[sock] = ai;
++ /* using nonblocking sockets with select allows
++ * us to fire off new sockets without waiting for a
++ * connection to be established. This lets us avoid
++ * the use of threads. set_nonblock is in misc.c
++ * and uses fnctl */
++ set_nonblock(sock);
++ debug2_f("RFC 8305 pre-connect for %s", return_fam(ai->ai_family));
++ if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
++ errno != EINPROGRESS) {
++ error("connect to address %s port %s: %s",
++ ntop, strport, strerror(errno));
++ errno = 0;
++ close(sock);
++ return -1;
++ }
++ debug_f("RFC 8305 post connect for %s", return_fam(ai->ai_family));
++ monotime_tv(initiate);
++ FD_SET(sock, fds);
++ *nfds = MAXIMUM(*nfds, sock + 1);
++ return 1;
++}
++
++static int
++happy_eyeballs_process(int *nfds, fd_set *fds,
++ struct addrinfo *fd_ai[],
++ int ready, fd_set *wfds)
++{
++ socklen_t optlen;
++ int sock, optval = 0;
++ char ntop[NI_MAXHOST], strport[NI_MAXSERV];
++
++ debug2_f("Processing RFC 8305 connections");
++ for (sock = *nfds - 1; ready > 0 && sock >= 0; sock--) {
++ debug2_f("RFC 8305: Processing for %s: %d", return_fam(fd_ai[sock]->ai_family), sock);
++ if (FD_ISSET(sock, wfds)) {
++ debug2_f("RFC 8305: FD_ISSET true for %d", sock);
++ ready--;
++ optlen = sizeof(optval);
++ debug_f("RFC 8305: optval is %d for %d", optval, sock);
++ if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
++ &optval, &optlen) < 0) {
++ optval = errno;
++ error("getsockopt failed: %s",
++ strerror(errno));
++ } else if (optval != 0) {
++ debug_f("RFC 8305: Copying data for %d on %s", optval, return_fam(fd_ai[sock]->ai_family));
++ memset(ntop, 0, sizeof(ntop));
++ memset(strport, 0, sizeof(strport));
++ if (getnameinfo(fd_ai[sock]->ai_addr,
++ fd_ai[sock]->ai_addrlen,
++ ntop, sizeof(ntop),
++ strport, sizeof(strport),
++ NI_NUMERICHOST|NI_NUMERICSERV) != 0)
++ error("connect finally failed: %s",
++ strerror(optval));
++ else
++ error("connect to address %s port %s finally: %s",
++ ntop, strport, strerror(optval));
++ }
++ FD_CLR(sock, fds);
++ while (*nfds > 0 && ! FD_ISSET(*nfds - 1, fds))
++ --*nfds;
++ if (optval == 0) {
++ debug_f("RFC 8305: unsetting nonblock for %s on %d",
++ return_fam(fd_ai[sock]->ai_family), sock);
++ unset_nonblock(sock);
++ return sock;
++ }
++ close(sock);
++ errno = optval;
++ }
++ }
++ return -1;
++}
++
++int
++happy_eyeballs(const char * host, struct addrinfo *ai,
++ struct sockaddr_storage *hostaddr, int *timeout_ms)
++{
++ struct addrinfo *fd_ai[FD_SETSIZE]; /* 1024 */
++ struct timeval initiate_tv, start_tv, select_tv, *tv;
++ fd_set fds, wfds;
++ int res, oerrno, diff, diff0, nfds = 0, sock = -1;
++
++ debug_f("Starting RFC 8305/Happy Eyeballs connection");
++
++ FD_ZERO(&fds);
++ if (*timeout_ms > 0) /* default value of no timeout - use TCP default instead */
++ monotime_tv(&start_tv); /* monotime_tv is in misc.c */
++
++ /* run through potentials unless we have timed out */
++ /* timeout_ms being the user defined connection timeout if
++ * they aren't using the tcp timeout */
++ while ((ai != NULL || nfds > 0) &&
++ ! timeout(&start_tv, *timeout_ms)) {
++ /* set up the sockets */
++ res = happy_eyeballs_initiate(host, ai,
++ timeout_ms, &initiate_tv, &nfds, &fds, fd_ai);
++ if (res != 0)
++ ai = ai->ai_next;
++ if (res == -1)
++ continue;
++ tv = NULL;
++
++ /* this is just to determine the pause between
++ * calls to select. */
++ if (ai != NULL || *timeout_ms > 0) {
++ debug2_f ("RFC 8305: In pause...");
++ tv = &select_tv;
++ if (ai != NULL) {
++ diff = options.happy_delay;
++ ms_subtract_diff(&initiate_tv, &diff);
++ if (*timeout_ms > 0) {
++ diff0 = *timeout_ms;
++ ms_subtract_diff(&start_tv, &diff0);
++ diff = MINIMUM(diff, diff0);
++ }
++ } else {
++ diff = *timeout_ms;
++ ms_subtract_diff(&start_tv, &diff);
++ }
++ tv->tv_sec = diff / 1000;
++ tv->tv_usec = (diff % 1000) * 1000;
++ }
++
++ /* create a writeable set of file descriptors */
++ wfds = fds;
++ /* select will pause for time tv determined by the above
++ * timing caculations */
++ debug2_f("RFC 8305: Starting select");
++ res = select(nfds, NULL, &wfds, NULL, tv);
++ debug2_f("RFC 8305: Leaving select");
++
++ /* preserve any errors */
++ oerrno = errno;
++ if (res < 0) {
++ error("select failed: %s", strerror(errno));
++ errno = oerrno;
++ continue;
++ }
++ /* start processing the sockets */
++ debug2_f ("RFC 8305: Processing happy eyeballs fds");
++ sock = happy_eyeballs_process(&nfds, &fds, fd_ai,
++ res, &wfds);
++ if (sock >= 0) {
++ debug_f ("RFC 8305 / Happy Eyeballs connected");
++ /* we have a connection */
++ memcpy(hostaddr, fd_ai[sock]->ai_addr,
++ fd_ai[sock]->ai_addrlen);
++ break;
++ }
++ debug_f("RFC 8305: Restarting while loop.");
++ }
++ oerrno = errno;
++ /* close other connection attempts/sockets */
++ debug2_f("RFC 8305: NFDS is %d", nfds);
++ while (nfds-- > 0) {
++ debug2_f("RFC 8305: Running FD_ISSET on %d", nfds);
++ if (FD_ISSET(nfds, &fds))
++ close(nfds);
++ }
++ /* we timed out with no valid connections */
++ if (timeout(&start_tv, *timeout_ms))
++ errno = ETIMEDOUT;
++ else
++ errno = oerrno;
++ return sock;
++}
+diff -Nur openssh-10.5p1.orig/happyeyeballs.h openssh-10.5p1/happyeyeballs.h
+--- openssh-10.5p1.orig/happyeyeballs.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/happyeyeballs.h 2026-09-21 07:06:59.888307370 +0200
+@@ -0,0 +1,23 @@
++/*
++ * Copyright (c) 2025 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++/*
++ * RFC 8305 Happy Eyeballs Version 2: Better Connectivity Using Concurrency
++ *
++ */
++
++int happy_eyeballs(const char *, struct addrinfo *,
++ struct sockaddr_storage *, int *);
+diff -Nur openssh-10.5p1.orig/HPN-README openssh-10.5p1/HPN-README
+--- openssh-10.5p1.orig/HPN-README 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/HPN-README 2026-09-21 07:06:59.888407993 +0200
+@@ -0,0 +1,200 @@
++Notes:
++
++FIPS Mode and Parallel Ciphers in 18.7.1
++Using HPN-SSH in operating systems working in FIPS mode (e.g. RHEL with
++FIPS enabled) preclude the use of parallel ciphers. This is because
++the parallel AES-CTR implementation is not FIPS certified and will cause FIPS
++to exit with an error when loaded. In the case of the parallel ChaCha20 cipher
++the algorithm itself has not been FIPS certified and no implementation of
++ChaCha20 should be allowed in FIPS mode. We suggest the use of the AES-GCM
++cipher when operating under FIPS mode for optimal performance.
++
++Happy Eyeballs Support in 18.7.1
++Happy Eyeballs (RFC 8305) is for use on dual stack systems meaning that they have both
++IPv4 and IPv6 TCP stacks. When enabled this option will try to connect to the
++target over both IPv4 and IPv6 with preference given to IPv6. The first connection
++that completes successfully will be used. Any outstanding connection attempts will
++be closed. As of version 18.7.1 this option should be considered somewhat experimental.
++
++usage:
++-oHappyEyes=[Yes|No] will enable Happy Eyeballs. The default is no.
++-oHappyDelay=[N] where N is a positive integer expressed in milliseconds.
++ The default value of 250ms is suggested by RFC 8305.
++
++MPTCP Support in 18.7.0:
++Multipath TCP is now available as a runtime option for HPN-SSH. MPTCP
++is available only on Linux and Mac OSX operating systems. Using MPTCP on a system that
++doesn't support it will result in a notice and failure. The use cases for MPTCP include
++seamless handovers when changing networks and aggregating multiple interfaces to improve
++available bandwidth. As of 18.7.0 this option should be considered somewhat experimental.
++
++Usage:
++-oUseMPTCP=[Yes|No] will enable MPTCP. The default is no.
++
++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 version of 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.
++
++HPNSCP with Resume functionality
++This feature allows hpnscp 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:
++'hpnscp myhugefile me@host:~'
++and it dies halfway through the transfer issuing
++'hpnscp -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. The 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.
++
++MULTI-THREADED AES 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.
++Usage examples:
++ ssh -caes128-ctr you@host.com
++ scp -oCipher=aes256-ctr file you@host.com:~/file
++
++MULTI-THREADED ChaCha20-Poly1305 CIPHER:
++The default cipher used by HPN-SSH is now a threaded implementation of the
++ChaCha20-Poly1305 cipher. In tests this cipher results in an approximately 90% gain in
++throughput performance in comparison to the serial implementation found in OpenSSH.
++This cipher is fully compatible with all known existing implementations of ChaCha20-Poly1305.
++
++Internally this cipher is referred to as chacha20-poly1305-mt@hpnssh.org (CC20-MT)
++similar to how the OpenSSH implementation is known as chacha20-poly1305@openssh.com
++(CC20-S (for serial)). No changes are required on the part of the user to make use of CC20-MT.
++However, if the users doesn't want to make use of this cipher they can explicitly load CC20-S using
++'-cchach20-poly1305@openssh.com` on the command line.
++
++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:~
++
++HPN Specific Configuration options
++
++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.
++
++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!
++
++
++Edited: September 19, 2025
+diff -Nur openssh-10.5p1.orig/HPNSSHInstallation.txt openssh-10.5p1/HPNSSHInstallation.txt
+--- openssh-10.5p1.orig/HPNSSHInstallation.txt 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/HPNSSHInstallation.txt 2026-09-21 07:06:59.888530213 +0200
+@@ -0,0 +1,361 @@
++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/hpn-ssh. Get a copy with
++"git clone https://github.com/rapier1/hpn-ssh”.
++
++
++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
++ * OpenSSL must be built with thread support. This is the default, so
++ packaged versions will work. If you build OpenSSL yourself, do not
++ pass "no-threads" to its config script. HPN-SSH uses threads for the
++ parallel ChaCha20 and AES-CTR ciphers, and without thread support in
++ OpenSSL sessions can abort with a memory error during transfers.
++ To check an installation you already have:
++ grep -r OPENSSL_THREADS <openssl-prefix>/include/openssl/
++* 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: 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 8: 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 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/hpnsshd.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
++
++Alternatively, the ./configure command will generate a hpnsshd.service file from
++hpnsshd.service.in. You can use this file by copying it to
++/libsystemd/system/hpnsshd.service instead of copying the above text.
++
++Then create the defaults file at /etc/default/hpnsshd 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. A sample /etc/defauls/hpnssh file may be found in defaults.hpnsshd.
++
++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.
++
++Alternatively, you may use the hpnsshd.init file created during configure. This will be
++prepopulated with the correct paths by the configure script.
++
++#! /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-10.5p1.orig/kex.c openssh-10.5p1/kex.c
+--- openssh-10.5p1.orig/kex.c 2026-09-21 07:06:05.453755735 +0200
++++ openssh-10.5p1/kex.c 2026-09-21 07:06:59.888748897 +0200
+@@ -63,6 +63,7 @@
+
+ #include "ssherr.h"
+ #include "sshbuf.h"
++#include "canohost.h"
+ #include "digest.h"
+ #include "xmalloc.h"
+ #include "audit.h"
+@@ -557,6 +558,7 @@
+ struct kex *kex = ssh->kex;
+ int r, initial = (kex->flags & KEX_INITIAL) != 0;
+ char *cp, **prop;
++ const char *rekey_denylist;
+
+ debug("SSH2_MSG_NEWKEYS received");
+ if (kex->ext_info_c && initial)
+@@ -568,13 +570,27 @@
+ if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0)
+ return r;
+ if (initial) {
+- /* Remove initial KEX signalling from proposal for rekeying */
++ /*
++ * Remove initial KEX signalling from proposal for rekeying.
++ * SecureBlackbox re-derives strict KEX from every KEXINIT
++ * instead of latching it at the initial exchange. Without
++ * the marker in the rekey it stops resetting its sequence
++ * number while we still reset ours and the MAC desyncs, so
++ * keep advertising kex-strict to it on rekeys. Compliant
++ * peers ignore the marker after the initial exchange.
++ */
++ if (ssh->compat & SSH_BUG_STRICT_KEX_REKEY) {
++ rekey_denylist = kex->server ?
++ "ext-info-s" : "ext-info-c";
++ } else {
++ rekey_denylist = kex->server ?
++ "ext-info-s,kex-strict-s-v00@openssh.com" :
++ "ext-info-c,kex-strict-c-v00@openssh.com";
++ }
+ if ((r = kex_buf2prop(kex->my, NULL, &prop)) != 0)
+ return r;
+ if ((cp = match_filter_denylist(prop[PROPOSAL_KEX_ALGS],
+- kex->server ?
+- "ext-info-s,kex-strict-s-v00@openssh.com" :
+- "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL) {
++ rekey_denylist)) == NULL) {
+ error_f("match_filter_denylist failed");
+ goto fail;
+ }
+@@ -784,17 +800,83 @@
+ free(kex);
+ }
+
++/*
++ * This function seeks through a comma-separated list and checks for instances
++ * of the multithreaded CC20 cipher. If found, it then ensures that the serial
++ * CC20 cipher is also in the list, adding it if necessary.
++ */
++char *
++patch_list(char * orig)
++{
++ char * adj = xstrdup(orig);
++ char * match;
++ u_int next;
++
++ const char * ccpstr = "chacha20-poly1305@openssh.com";
++ const char * ccpmtstr = "chacha20-poly1305-mt@hpnssh.org";
++
++ match = match_list(ccpmtstr, orig, &next);
++ if (match != NULL) { /* CC20-MT found in the list */
++ free(match);
++ match = match_list(ccpstr, orig, NULL);
++ if (match == NULL) { /* CC20-Serial NOT found in the list */
++ adj = xreallocarray(adj,
++ strlen(adj) /* original string length */
++ + 1 /* for the original null-terminator */
++ + strlen(ccpstr) /* make room for ccpstr */
++ + 1 /* make room for the comma delimiter */
++ , sizeof(char));
++ /*
++ * adj[next] points to the character after the CC20-MT
++ * string. adj[next] might be ',' or '\0' at this point.
++ */
++ adj[next] = ',';
++ /* adj + next + 1 is the character after that comma */
++ memcpy(adj + next + 1, ccpstr, strlen(ccpstr));
++ /* rewrite the rest of the original list */
++ memcpy(adj + next + 1 + strlen(ccpstr), orig + next,
++ strlen(orig + next) + 1);
++ } else { /* CC20-Serial found in the list, nothing to do */
++ free(match);
++ }
++ }
++
++ return adj;
++}
++
+ int
+ kex_ready(struct ssh *ssh, char *proposal[PROPOSAL_MAX])
+ {
+- int r;
++ int r = 0;
++
++#ifdef WITH_OPENSSL
++ char * orig_ctos = proposal[PROPOSAL_ENC_ALGS_CTOS];
++ char * orig_stoc = proposal[PROPOSAL_ENC_ALGS_STOC];
++ proposal[PROPOSAL_ENC_ALGS_CTOS] =
++ patch_list(proposal[PROPOSAL_ENC_ALGS_CTOS]);
++ proposal[PROPOSAL_ENC_ALGS_STOC] =
++ patch_list(proposal[PROPOSAL_ENC_ALGS_STOC]);
++
++ /*
++ * TODO: Likely memory leak here. The original contents of
++ * proposal[PROPOSAL_ENC_ALGS_CTOS] are no longer accessible or
++ * freeable.
++ */
++#endif
+
+ if ((r = kex_prop2buf(ssh->kex->my, proposal)) != 0)
+- return r;
++ goto restoreProposal;
+ ssh->kex->flags = KEX_INITIAL;
+ kex_reset_dispatch(ssh);
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit);
+- return 0;
++ restoreProposal:
++#ifdef WITH_OPENSSL
++ free(proposal[PROPOSAL_ENC_ALGS_CTOS]);
++ free(proposal[PROPOSAL_ENC_ALGS_STOC]);
++ proposal[PROPOSAL_ENC_ALGS_CTOS] = orig_ctos;
++ proposal[PROPOSAL_ENC_ALGS_STOC] = orig_stoc;
++#endif
++ return r;
+ }
+
+ int
+@@ -983,6 +1065,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)
+@@ -1059,6 +1146,31 @@
+ peer[nenc] = NULL;
+ goto out;
+ }
++#ifdef WITH_OPENSSL
++ if ((strcmp(newkeys->enc.name, "chacha20-poly1305@openssh.com")
++ == 0) && (match_list("chacha20-poly1305-mt@hpnssh.org",
++ my[nenc], NULL) != NULL)) {
++ /*
++ * if we're using the serial CC20 cipher while the
++ * multithreaded implementation is an option...
++ */
++ free(newkeys->enc.name);
++ newkeys->enc.cipher = cipher_by_name(
++ "chacha20-poly1305-mt@hpnssh.org");
++ if (newkeys->enc.cipher == NULL) {
++ error_f("%s cipher not found.",
++ "chacha20-poly1305-mt@hpnssh.org");
++ r = SSH_ERR_INTERNAL_ERROR;
++ kex->failed_choice = peer[nenc];
++ peer[nenc] = NULL;
++ goto out;
++ } else {
++ newkeys->enc.name = xstrdup(
++ "chacha20-poly1305-mt@hpnssh.org");
++ }
++ /* we promote to the multithreaded implementation */
++ }
++#endif
+ authlen = cipher_authlen(newkeys->enc.cipher);
+ /* ignore mac for authenticated encryption */
+ if (authlen == 0 &&
+@@ -1074,11 +1186,43 @@
+ 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);
++ ssh->none_mac = 1;
++ }
++ }
++
+ debug("kex: %s cipher: %s MAC: %s compression: %s",
+ ctos ? "client->server" : "server->client",
+ newkeys->enc.name,
+ authlen == 0 ? newkeys->mac.name : "<implicit>",
+ 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 : "<implicit>",
++ newkeys->comp.name);
++ }
++ log_flag = 1;
+ }
+ need = dh_need = 0;
+ for (mode = 0; mode < MODE_MAX; mode++) {
+@@ -1438,7 +1582,7 @@
+ if (version_addendum != NULL && *version_addendum == '\0')
+ version_addendum = NULL;
+ if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%s%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;
+@@ -1574,9 +1718,24 @@
+ 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);
++ if (ssh->compat & SSH_HPNSSH)
++ debug("HPN to HPN Connection.");
++ else
++ debug("Non-HPN to HPN Connection.");
++
++ if(ssh->compat & SSH_RESTRICT_WINDOW)
++ debug ("Window size restricted.");
+
+ mismatch = 0;
+ switch (remote_major) {
+diff -Nur openssh-10.5p1.orig/log.c openssh-10.5p1/log.c
+--- openssh-10.5p1.orig/log.c 2026-09-21 07:06:05.072886618 +0200
++++ openssh-10.5p1/log.c 2026-09-21 07:06:59.889106825 +0200
+@@ -47,6 +47,11 @@
+ #include <string.h>
+ #include <syslog.h>
+ #include <unistd.h>
++#include <errno.h>
++#include "packet.h" /* needed for host and port look ups */
++#ifdef HAVE_SYS_TIME_H
++# include <sys/time.h> /* to get current time */
++#endif
+ #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
+ # include <vis.h>
+ #endif
+@@ -65,6 +70,8 @@
+ static size_t nlog_verbose;
+ 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-10.5p1.orig/mac.c openssh-10.5p1/mac.c
+--- openssh-10.5p1.orig/mac.c 2026-09-21 07:06:05.290643514 +0200
++++ openssh-10.5p1/mac.c 2026-09-21 07:06:59.889464565 +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-10.5p1.orig/Makefile.in openssh-10.5p1/Makefile.in
+--- openssh-10.5p1.orig/Makefile.in 2026-09-21 07:06:05.560574980 +0200
++++ openssh-10.5p1/Makefile.in 2026-09-21 07:53:54.447459641 +0200
+@@ -54,7 +54,7 @@
+ CFLAGS_NOPIE=@CFLAGS_NOPIE@
+ CPPFLAGS=-I. -I$(srcdir) -I$(COMPATINCLUDES) @CPPFLAGS@ $(PATHS) @DEFS@
+ PICFLAG=@PICFLAG@
+-LIBS=@LIBS@
++LIBS=@LIBS@ -lpthread
+ CHANNELLIBS=@CHANNELLIBS@
+ K5LIBS=@K5LIBS@
+ GSSLIBS=@GSSLIBS@
+@@ -93,7 +93,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 \
+@@ -103,6 +103,7 @@
+ msg.o dns.o entropy.o gss-genr.o umac.o umac128.o \
+ smult_curve25519_ref.o \
+ poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \
++ cipher-chachapoly-libcrypto-mt.o \
+ ssh-ed25519.o digest-openssl.o digest-libc.o \
+ libcrux-mlkem-mldsa.o ssh-mldsa-eddsa.o \
+ hmac.o ed25519.o ed25519-openssl.o \
+@@ -111,14 +112,16 @@
+ kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
+ kexgssc.o \
+ sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+- sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
++ sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o \
++ metrics.o binn.o happyeyeballs.o cipher-ctr-mt-provider.o \
++ cipher-ctr-mt-functions.o ossl3-provider-err.o num.o
+
+ P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
+
+ SKOBJS= ssh-sk-client.o
+
+ SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
+- sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
++ sshconnect.o sshconnect2.o mux.o cipher-switch.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+
+ SSHDOBJS=sshd.o \
+ platform-listen.o \
+@@ -137,7 +140,7 @@
+ auth2-gss.o gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \
+ loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
+ sftp-server.o sftp-common.o \
+- uidswap.o platform-listen.o $(P11OBJS) $(SKOBJS)
++ uidswap.o platform-listen.o cipher-switch.o $(P11OBJS) $(SKOBJS)
+
+ SSHD_AUTH_OBJS=sshd-auth.o \
+ auth2-methods.o \
+@@ -152,7 +155,7 @@
+ sandbox-null.o sandbox-rlimit.o sandbox-darwin.o \
+ sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-solaris.o \
+ sftp-server.o sftp-common.o \
+- uidswap.o $(P11OBJS) $(SKOBJS)
++ uidswap.o cipher-switch.o $(P11OBJS) $(SKOBJS)
+
+ SFTP_CLIENT_OBJS=sftp-common.o sftp-client.o sftp-glob.o ssherr-nolibcrypto.o
+
+@@ -236,7 +239,7 @@
+ $(LD) -o $@ $(SSHD_AUTH_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) $(GSSLIBS) $(K5LIBS) $(CHANNELLIBS) $(LIBWTMPDB)
+
+ 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-10.5p1.orig/metrics.c openssh-10.5p1/metrics.c
+--- openssh-10.5p1.orig/metrics.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/metrics.c 2026-09-21 07:06:59.889869243 +0200
+@@ -0,0 +1,439 @@
++/*
++ * Copyright (c) 2022 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++#include "includes.h"
++#include "metrics.h"
++#include "ssherr.h"
++#include <stdlib.h>
++#include <stdio.h>
++
++/* kernel version macro moved to defines.h */
++
++/* 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
++ */
++#if (defined __linux__) && !defined(__alpine__)
++ 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-10.5p1.orig/metrics.h openssh-10.5p1/metrics.h
+--- openssh-10.5p1.orig/metrics.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/metrics.h 2026-09-21 07:06:59.889925844 +0200
+@@ -0,0 +1,45 @@
++/*
++ * Copyright (c) 2022 The Board of Trustees of Carnegie Mellon University.
++ *
++ * Author: Chris Rapier <rapier@psc.edu>
++ *
++ * This library is free software; you can redistribute it and/or modify it
++ * under the terms of the MIT License.
++ *
++ * This library is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
++ *
++ * You should have received a copy of the MIT License along with this library;
++ * if not, see http://opensource.org/licenses/MIT.
++ *
++ */
++
++#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__ && !defined(__alpine__)
++#define TCP_INFO 1
++#if defined __linux__ && !defined(__alpine__)
++#include <linux/tcp.h>
++#else
++#include <netinet/tcp.h>
++#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-10.5p1.orig/misc.c openssh-10.5p1/misc.c
+--- openssh-10.5p1.orig/misc.c 2026-09-21 07:06:05.560958082 +0200
++++ openssh-10.5p1/misc.c 2026-09-21 07:06:59.890031551 +0200
+@@ -66,6 +66,84 @@
+ #include "ssherr.h"
+ #include "platform.h"
+
++/* Function to determine if FIPS is enabled or not.
++ * We assume that fips is not enabled and then test from there.
++ * The idea is that if there is an error or we can't read the value
++ * then either the OS doesn't support FIPS or that FIPS will
++ * catch anything we try to do that's not FIPS compliant.
++ * That would be limited to trying to use one of the parallel ciphers.
++ */
++int
++fips_enabled()
++{
++ int mode = 0;
++ const char* fips_path = "/proc/sys/crypto/fips_enabled";
++ FILE *fips_enabled = NULL;
++
++ debug2_f("Checking for FIPS");
++
++ /* if we can't open the path to fips_enabled it
++ * either doesn't exist or there is an error. In either
++ * case we treat it as if fips is *not* enabled.
++ * This is because I want to fail towards the most
++ * common scenario which is that fips_enabled either
++ * doesn't exist (non-fedora variants) or isn't
++ * enabled.
++ */
++ fips_enabled = fopen(fips_path, "r");
++ if (!fips_enabled) {
++ debug3_f("Cannot open path to fips_enabled.");
++ return 0;
++ }
++
++ /* fips_enabled does exist so read the value.
++ * It should be either 0 (disabled) or 1 (enabled)
++ */
++ if ( 1 != fscanf(fips_enabled,"%d", &mode) ) {
++ /* if we get some error here then we
++ * again fail to returning fips being disabled
++ */
++ debug3_f("Error processing fips_enabled.");
++ fclose(fips_enabled);
++ return 0;
++ }
++
++ /* let the user know the status */
++ if (mode == 0)
++ debug3_f("FIPS mode is disabled.");
++ else
++ debug3_f("FIPS mode is enabled.");
++
++ fclose(fips_enabled);
++ return mode;
++}
++
++/* helper function used to determine memory usage during
++ * development process. Not to be used in production.
++ */
++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)
+diff -Nur openssh-10.5p1.orig/misc.h openssh-10.5p1/misc.h
+--- openssh-10.5p1.orig/misc.h 2026-09-21 07:06:04.862051086 +0200
++++ openssh-10.5p1/misc.h 2026-09-21 07:06:59.890595516 +0200
+@@ -276,4 +276,17 @@
+ strerror(errno)); \
+ } while (0)
+
++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);
++int fips_enabled();
++
+ #endif /* _MISC_H */
+diff -Nur openssh-10.5p1.orig/myproposal.h openssh-10.5p1/myproposal.h
+--- openssh-10.5p1.orig/myproposal.h 2026-09-21 07:06:05.454563795 +0200
++++ openssh-10.5p1/myproposal.h 2026-09-21 07:06:59.890799056 +0200
+@@ -74,10 +74,20 @@
+ "rsa-sha2-512," \
+ "rsa-sha2-256"
+
++/*if we aren't using OpenSSL we need to remove
++ * the parallel ChaCha20 cipher from the list */
++#ifdef WITH_OPENSSL
+ #define KEX_SERVER_ENCRYPT \
++ "chacha20-poly1305-mt@hpnssh.org," \
+ "chacha20-poly1305@openssh.com," \
+ "aes128-gcm@openssh.com,aes256-gcm@openssh.com," \
+ "aes128-ctr,aes192-ctr,aes256-ctr"
++#else
++#define KEX_SERVER_ENCRYPT \
++ "chacha20-poly1305@openssh.com," \
++ "aes128-gcm@openssh.com,aes256-gcm@openssh.com," \
++ "aes128-ctr,aes192-ctr,aes256-ctr"
++#endif
+
+ #define KEX_CLIENT_ENCRYPT KEX_SERVER_ENCRYPT
+
+diff -Nur openssh-10.5p1.orig/num.c openssh-10.5p1/num.c
+--- openssh-10.5p1.orig/num.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/num.c 2026-09-21 07:06:59.890901553 +0200
+@@ -0,0 +1,156 @@
++/* CC0 license applied, see LICENCE.md */
++
++#include <string.h>
++#include "num.h"
++
++#ifdef WITH_OPENSSL3
++
++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 /* WITH_OPENSSL3 */
+diff -Nur openssh-10.5p1.orig/num.h openssh-10.5p1/num.h
+--- openssh-10.5p1.orig/num.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/num.h 2026-09-21 07:06:59.890944292 +0200
+@@ -0,0 +1,15 @@
++/* CC0 license applied, see LICENCE.md */
++
++#include "includes.h"
++#ifdef WITH_OPENSSL3
++
++#include <openssl/core.h>
++
++/* 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 /* WITH_OPENSSL3 */
+diff -Nur openssh-10.5p1.orig/ossl3-provider-err.c openssh-10.5p1/ossl3-provider-err.c
+--- openssh-10.5p1.orig/ossl3-provider-err.c 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/ossl3-provider-err.c 2026-09-21 07:06:59.890982204 +0200
+@@ -0,0 +1,108 @@
++/* CC0 license applied, see LICENCE.md */
++
++#include <assert.h>
++#include <stdlib.h>
++#include "ossl3-provider-err.h"
++
++#ifdef WITH_OPENSSL3
++
++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 <openssl/err.h>
++ */
++ 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 /* WITH_OPENSSL3 */
+diff -Nur openssh-10.5p1.orig/ossl3-provider-err.h openssh-10.5p1/ossl3-provider-err.h
+--- openssh-10.5p1.orig/ossl3-provider-err.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/ossl3-provider-err.h 2026-09-21 07:06:59.891019845 +0200
+@@ -0,0 +1,73 @@
++/* CC0 license applied, see LICENCE.md */
++
++#include "includes.h"
++#ifdef WITH_OPENSSL3
++#include <stdint.h>
++#include <openssl/core.h>
++#include <openssl/core_dispatch.h>
++
++/*
++ * 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 <openssl/err.h> 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 /* WITH_OPENSSL3 */
+diff -Nur openssh-10.5p1.orig/packet.c openssh-10.5p1/packet.c
+--- openssh-10.5p1.orig/packet.c 2026-09-21 07:06:05.291477877 +0200
++++ openssh-10.5p1/packet.c 2026-09-21 07:06:59.891131638 +0200
+@@ -84,6 +84,7 @@
+ #include "mac.h"
+ #include "log.h"
+ #include "canohost.h"
++#include "channels.h"
+ #include "misc.h"
+ #include "packet.h"
+ #include "ssherr.h"
+@@ -95,7 +96,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 {
+ uint32_t seqnr;
+@@ -245,12 +259,23 @@
+ (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 and type them with descriptive names */
++ sshbuf_relabel(state->input, "input");
++ sshbuf_type(state->input, BUF_PACKET_INPUT);
++ sshbuf_relabel(state->incoming_packet, "inpacket");
++ sshbuf_type(state->incoming_packet, BUF_PACKET_INCOMING);
++ sshbuf_relabel(state->output, "output");
++ sshbuf_type(state->output, BUF_PACKET_OUTPUT);
++ sshbuf_relabel(state->outgoing_packet, "outpacket");
++ sshbuf_type(state->outgoing_packet, BUF_PACKET_OUTGOING);
++
+ 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->interactive_mode = 1;
+ state->qos_interactive = state->qos_other = -1;
+@@ -318,9 +343,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, 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, 0, CIPHER_DECRYPT,
++ state->after_authentication)) != 0) {
+ error_fr(r, "cipher_init failed");
+ if (wasnull)
+ free(ssh); /* XXX need ssh_free_session_state? */
+@@ -392,7 +419,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;
+@@ -998,6 +1025,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);
+
+@@ -1038,12 +1066,32 @@
+ if ((r = mac_init(mac)) != 0)
+ return r;
+ }
+- mac->enabled = 1;
++
++ /* if we are using NONE MAC then we don't need to enable the
++ * mac routines. This disables them and we can claw back some cycles
++ * from the CPU -cjr 3/21/2023 */
++ if (ssh->none_mac != 1)
++ mac->enabled = 1;
++
+ DBG(debug_f("cipher_init: %s", dir));
+ cipher_free(*ccp);
+ *ccp = NULL;
++#ifdef WITH_OPENSSL
++ if (strcmp(enc->name, "chacha20-poly1305-mt@hpnssh.org") == 0) {
++ if (state->after_authentication)
++ enc->cipher = cipher_by_name(
++ "chacha20-poly1305-mt@hpnssh.org");
++ else
++ enc->cipher = cipher_by_name(
++ "chacha20-poly1305@openssh.com");
++ if (enc->cipher == NULL)
++ return r;
++ }
++#endif
+ 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->p_send.seqnr : state->p_read.seqnr,
++ crypt_type, state->after_authentication && ssh->mt_enabled)) != 0)
+ return r;
+ if (!state->cipher_warning_done &&
+ (wmsg = cipher_warning_message(*ccp)) != NULL) {
+@@ -1067,25 +1115,46 @@
+ }
+ 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)
+- *hard_max_blocks = (uint64_t)1 << (enc->block_size*2);
+- else
+- *hard_max_blocks = ((uint64_t)1 << 30) / enc->block_size;
++ /* get the maximum number of blocks the cipher can
++ * handle safely */
++ *hard_max_blocks = cipher_rekey_blocks(enc->cipher);
+ *max_blocks = *hard_max_blocks;
++
++ /* if we have a custom oRekeyLimit use that. */
+ 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);
++
++ /* 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)
+ /*
+ * Checks whether the packet- or block- based rekeying limits have been
+@@ -1143,6 +1212,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())
+@@ -1509,7 +1586,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;
+
+@@ -1613,7 +1690,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;
+@@ -1672,7 +1749,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
+@@ -1699,7 +1776,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);
+@@ -1708,7 +1785,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;
+@@ -1731,7 +1808,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
+@@ -1775,11 +1852,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));
+@@ -2008,7 +2085,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) {
+@@ -2087,17 +2164,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 */
+@@ -2139,6 +2220,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
+@@ -2396,10 +2495,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 *
+@@ -3045,6 +3153,20 @@
+ return 0;
+ }
+
++/* used for cipher switching
++ * only called in cipher-swtich.c */
++void *
++ssh_packet_get_send_context(struct ssh *ssh)
++{
++ return ssh->state->send_context;
++}
++
++void *
++ssh_packet_get_receive_context(struct ssh *ssh)
++{
++ return ssh->state->receive_context;
++}
++
+ static char *
+ format_traffic_stats(struct packet_state *ps)
+ {
+diff -Nur openssh-10.5p1.orig/packet.h openssh-10.5p1/packet.h
+--- openssh-10.5p1.orig/packet.h 2026-09-21 07:06:05.291633858 +0200
++++ openssh-10.5p1/packet.h 2026-09-21 07:06:59.891408461 +0200
+@@ -83,6 +83,24 @@
+
+ /* 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 state */
++ int none;
++
++ /* track if we have disabled the mac as well */
++ int none_mac;
++
++ /* HPN: MT ciphers are active for this connection. Set only by
++ * cipher_switch(), which runs solely when the MT ciphers are not
++ * disabled by config (DisableMTAES) and FIPS mode is off. Gates the
++ * post-auth promotion of aes-ctr to the threaded implementation so a
++ * later rekey cannot silently switch to MT once it was disabled. */
++ int mt_enabled;
+ };
+
+ typedef int (ssh_packet_hook_fn)(struct ssh *, struct sshbuf *,
+@@ -152,6 +170,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 *);
+
+@@ -167,6 +187,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-10.5p1.orig/poly1305.c openssh-10.5p1/poly1305.c
+--- openssh-10.5p1.orig/poly1305.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/poly1305.c 2026-09-21 07:06:59.891537707 +0200
+@@ -11,6 +11,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) \
+@@ -28,7 +38,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;
+@@ -155,3 +165,4 @@
+ U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32);
+ U32TO8_LE(&out[12], f3);
+ }
++#endif /* OPENSSL_HAVE_POLY_EVP */
+diff -Nur openssh-10.5p1.orig/poly1305.h openssh-10.5p1/poly1305.h
+--- openssh-10.5p1.orig/poly1305.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/poly1305.h 2026-09-21 07:06:59.891653705 +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 <openssl/evp.h>
++
++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-10.5p1.orig/progressmeter.c openssh-10.5p1/progressmeter.c
+--- openssh-10.5p1.orig/progressmeter.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/progressmeter.c 2026-09-21 07:06:59.891740440 +0200
+@@ -65,6 +65,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 long long bytes_per_second; /* current speed in bytes per second */
+@@ -131,6 +133,7 @@
+ long long cur_speed;
+ int hours, minutes, seconds;
+ int file_len, cols;
++ off_t delta_pos;
+
+ if (file == NULL || (!force_update && !alarm_fired && !win_resized) ||
+ !can_output())
+@@ -147,6 +150,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 {
+@@ -176,7 +183,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 */
+@@ -193,6 +200,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;
+@@ -235,6 +248,7 @@
+ }
+ free(buf);
+ free(obuf);
++ last_pos = cur_pos;
+ }
+
+ static void
+diff -Nur openssh-10.5p1.orig/readconf.c openssh-10.5p1/readconf.c
+--- openssh-10.5p1.orig/readconf.c 2026-09-21 07:06:05.562703881 +0200
++++ openssh-10.5p1/readconf.c 2026-09-21 07:06:59.892014629 +0200
+@@ -62,6 +62,7 @@
+ #include "mac.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "sshbuf.h"
+ #include "version.h"
+ #include "ssh-gss.h"
+
+@@ -165,6 +166,10 @@
+ oHashKnownHosts,
+ oTunnel, oTunnelDevice,
+ oLocalCommand, oPermitLocalCommand, oRemoteCommand,
++ oTcpRcvBufPoll, oHPNDisabled,
++ oNoneEnabled, oNoneMacEnabled, oNoneSwitch,
++ oDisableMTAES, oUseMPTCP, oHappyEyes, oHappyDelay,
++ oMetrics, oMetricsPath, oMetricsInterval, oFallback, oFallbackPort,
+ oVisualHostKey,
+ oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull,
+ oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass,
+@@ -312,6 +317,18 @@
+ { "kexalgorithms", oKexAlgorithms },
+ { "ipqos", oIPQoS },
+ { "requesttty", oRequestTTY },
++ { "noneenabled", oNoneEnabled },
++ { "nonemacenabled", oNoneMacEnabled },
++ { "noneswitch", oNoneSwitch },
++ { "usemptcp", oUseMPTCP},
++ { "happyeyes", oHappyEyes },
++ { "happydelay", oHappyDelay },
++ { "disablemtaes", oDisableMTAES },
++ { "metrics", oMetrics },
++ { "metricspath", oMetricsPath },
++ { "metricsinterval", oMetricsInterval },
++ { "fallback", oFallback },
++ { "fallbackport", oFallbackPort },
+ { "sessiontype", oSessionType },
+ { "stdinnull", oStdinNull },
+ { "forkafterauthentication", oForkAfterAuthentication },
+@@ -334,6 +351,8 @@
+ { "proxyjump", oProxyJump },
+ { "securitykeyprovider", oSecurityKeyProvider },
+ { "knownhostscommand", oKnownHostsCommand },
++ { "tcprcvbufpoll", oTcpRcvBufPoll },
++ { "hpndisabled", oHPNDisabled },
+ { "requiredrsasize", oRequiredRSASize },
+ { "enableescapecommandline", oEnableEscapeCommandline },
+ { "obscurekeystroketiming", oObscureKeystrokeTiming },
+@@ -534,7 +553,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;
+ }
+@@ -1384,6 +1403,75 @@
+ intptr = &options->check_host_ip;
+ goto parse_flag;
+
++ case oHPNDisabled:
++ intptr = &options->hpn_disabled;
++ goto parse_flag;
++
++ 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 oUseMPTCP:
++ intptr = &options->use_mptcp;
++ goto parse_flag;
++
++ case oHappyEyes:
++ intptr = &options->use_happyeyes;
++ goto parse_flag;
++
++ case oHappyDelay:
++ intptr = &options->happy_delay;
++ goto parse_int;
++
++ case oDisableMTAES:
++ intptr = &options->disable_multithreaded;
++ 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;
+@@ -2858,6 +2946,20 @@
+ 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->use_mptcp = -1;
++ options->use_happyeyes = -1;
++ options->happy_delay = -1;
++ options->disable_multithreaded = -1;
++ options->metrics = -1;
++ options->metrics_path = NULL;
++ options->metrics_interval = -1;
++ options->hpn_disabled = -1;
++ options->fallback = -1;
++ options->fallback_port = -1;
++ options->tcp_rcv_buf_poll = -1;
+ options->session_type = -1;
+ options->stdin_null = -1;
+ options->fork_after_authentication = -1;
+@@ -3040,8 +3142,47 @@
+ 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->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->use_mptcp == -1)
++ options->use_mptcp = 0;
++ if (options->use_happyeyes == -1)
++ options->use_happyeyes = 0;
++ /* if the user tries to set the delay to 0 then in just loops forever
++ * so instead of using the standard -1 test we use < 1 to make sure the
++ * user isn't being too clever for their own good
++ */
++ if (options->happy_delay < 1)
++ options->happy_delay = 250; /* default 250ms as per RFC 8305 Section 5 */
++ if (options->disable_multithreaded == -1)
++ options->disable_multithreaded = 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;
+@@ -3058,10 +3199,22 @@
+ options->permit_local_command = 0;
+ if (options->visual_host_key == -1)
+ options->visual_host_key = 0;
+- if (options->ip_qos_interactive == -1)
++ /* in the event we are using RFC 8305 then we
++ * need to override the default QOS as these
++ * interfere with the connection process in our
++ * test environment. I don't know if it has a real world
++ * impact but TODO try to find a real world way to test this.
++ */
++ if (options->ip_qos_interactive == -1) {
+ options->ip_qos_interactive = IPTOS_DSCP_EF;
+- if (options->ip_qos_bulk == -1)
++ if (options->use_happyeyes == 1)
++ options->ip_qos_interactive = IPTOS_LOWDELAY;
++ }
++ if (options->ip_qos_bulk == -1) {
+ options->ip_qos_bulk = IPTOS_DSCP_CS0;
++ if (options->use_happyeyes == 1)
++ options->ip_qos_bulk = IPTOS_THROUGHPUT;
++ }
+ if (options->request_tty == -1)
+ options->request_tty = REQUEST_TTY_AUTO;
+ if (options->session_type == -1)
+@@ -3870,6 +4023,15 @@
+ dump_cfg_fmtint(oVisualHostKey, o->visual_host_key);
+ dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys);
+ dump_cfg_fmtint(oEnableEscapeCommandline, o->enable_escape_commandline);
++ dump_cfg_fmtint(oTcpRcvBufPoll, o->tcp_rcv_buf_poll);
++ dump_cfg_fmtint(oHPNDisabled, o->hpn_disabled);
++ dump_cfg_fmtint(oNoneSwitch, o->none_switch);
++ dump_cfg_fmtint(oNoneEnabled, o->none_enabled);
++ dump_cfg_fmtint(oNoneMacEnabled, o->nonemac_enabled);
++ dump_cfg_fmtint(oFallback, o->fallback);
++ dump_cfg_fmtint(oMetrics, o->metrics);
++ dump_cfg_fmtint(oUseMPTCP, o->use_mptcp);
++ dump_cfg_fmtint(oHappyEyes, o->use_happyeyes);
+ dump_cfg_fmtint(oWarnWeakCrypto, o->warn_weak_crypto);
+
+ /* Integer options */
+@@ -3882,6 +4044,9 @@
+ dump_cfg_int(oRequiredRSASize, o->required_rsa_size);
+ dump_cfg_int(oObscureKeystrokeTiming,
+ o->obscure_keystroke_timing_interval);
++ dump_cfg_int(oMetricsInterval, o->metrics_interval);
++ dump_cfg_int(oFallbackPort, o->fallback_port);
++ dump_cfg_int(oHappyDelay, o->happy_delay);
+
+ /* String options */
+ dump_cfg_string(oBindAddress, o->bind_address);
+@@ -3909,6 +4074,7 @@
+ dump_cfg_string(oXAuthLocation, o->xauth_location);
+ dump_cfg_string(oKnownHostsCommand, o->known_hosts_command);
+ dump_cfg_string(oTag, o->tag);
++ dump_cfg_string(oMetricsPath, o->metrics_path);
+ dump_cfg_string(oVersionAddendum, o->version_addendum);
+
+ /* Forwards */
+diff -Nur openssh-10.5p1.orig/readconf.h openssh-10.5p1/readconf.h
+--- openssh-10.5p1.orig/readconf.h 2026-09-21 07:06:05.563224824 +0200
++++ openssh-10.5p1/readconf.h 2026-09-21 07:06:59.892300658 +0200
+@@ -129,6 +129,23 @@
+ int enable_ssh_keysign;
+ int64_t rekey_limit;
+ int rekey_interval;
++
++ /* hpnssh options */
++ int tcp_rcv_buf_poll; /* Option to poll recv buf every window transfer */
++ int hpn_disabled; /* Switch to disable HPN buffer management */
++ 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 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 use_mptcp; /* use MultiPath TCP */
++ int use_happyeyes; /* use RFC 8305 - Happy Eyeballs */
++ int happy_delay; /* user defined dleay for RFC 8305 */
++
+ int no_host_authentication_for_localhost;
+ int identities_only;
+ int server_alive_interval;
+diff -Nur openssh-10.5p1.orig/README.md openssh-10.5p1/README.md
+--- openssh-10.5p1.orig/README.md 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/README.md 2026-09-21 11:46:55.683415926 +0200
+@@ -1,19 +1,23 @@
+-# Portable OpenSSH
++# HPNSSH: Based on Portable OpenSSH
++HPN-SSH is a high performance soft fork of OpenSSH that can provide significantly faster throughput for bulk data transfers over a wide range of network paths. In some situations we've seen throughput rates more than 100 times faster than OpenSSH. HPN-SSH is able to do this by optimizing the application layer receive buffer to match the TCP receive buffer. Notably, to see performance improvements HPN-SSH only needs to be the data receiver so users can see notable improvements with many other SSH implementations. HPN-SSH also incorporates two parallelized ciphers, AES-CTR and Chacha20 (the default). When using these ciphers a throughput performance increase of 30% is typical. More information on how we do this work and other features of HPN-SSH is available from [https://hpnssh.org](https://hpnssh.org).
+
+-[](../../actions/workflows/c-cpp.yml)
+-[](../../actions/workflows/vm.yml)
+-[](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml)
+-[](../../actions/workflows/cifuzz.yml)
+-[](https://issues.oss-fuzz.com/issues?q="Project:+openssh"+is:open)
+-[](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
++
++Current information about release features are available in the HPN-README document.
+
+ ## Documentation
+
+-The official documentation for OpenSSH are the man pages for each tool:
++The official documentation for HPN-SSH are the man pages for each tool.
+
+ * [ssh(1)](https://man.openbsd.org/ssh.1)
+ * [sshd(8)](https://man.openbsd.org/sshd.8)
+@@ -24,17 +28,18 @@
+ * [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.
+
+-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 one of [LibreSSL](https://www.libressl.org/), [OpenSSL](https://www.openssl.org), [AWS-LC](https://github.com/aws/aws-lc) or [BoringSSL](https://github.com/google/boringssl) may also be used. OpenSSH may be built without any of these, but the resulting binaries will have only a subset of the cryptographic algorithms normally available.
++``libcrypto`` from either [LibreSSL](https://www.libressl.org/) or [OpenSSL](https://www.openssl.org) may also be used. HPN-SSH may be built without either of these, but the resulting binaries will have only a subset of the cryptographic algorithms normally available.
++The developers of HPN-SSH strongly suggest the use of OpenSSL 3.0 or higher to support the advanced cryptography methods used.
+
+ [zlib](https://www.zlib.net/) is optional; without it transport compression is not supported.
+
+@@ -47,8 +52,9 @@
+ Release tarballs and release branches in git 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 hpn-ssh
++autoreconf -f -i
+ ./configure # [options]
+ make && make tests
+ ```
+@@ -57,19 +63,19 @@
+
+ ### Building from git
+
+-If building from the git master branch, 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:
++If building from the git master branch, you'll need [autoconf](https://www.gnu.org/software/autoconf/) installed to build the ``configure`` script. The following commands will check out and build HPN-SSH from git:
+
+ ```
+-git clone https://github.com/openssh/openssh-portable # or https://anongit.mindrot.org/openssh.git
+-cd openssh-portable
+-autoreconf
++git clone https://github.com/rapier1/hpn-ssh
++cd hpn-ssh
++autoreconf -f -i
+ ./configure
+ make && make tests
+ ```
+
+ ### Build-time Customisation
+
+-There are many build-time customisation options available. All Autoconf destination path flags (e.g. ``--prefix``) are supported (and are usually required if you want to install OpenSSH).
++There are many build-time customisation options available. All Autoconf destination path flags (e.g. ``--prefix``) are supported (and are usually required if you want to install HPN-SSH).
+
+ For a full list of available flags, run ``./configure --help`` but a few of the more frequently-used ones are described below. Some of these flags will require additional libraries and/or headers be installed.
+
+@@ -79,11 +85,13 @@
+ ``--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
+
+ Portable OpenSSH development is discussed on the [openssh-unix-dev mailing list](https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev) ([archive mirror](https://marc.info/?l=openssh-unix-dev)). Bugs and feature requests are tracked on our [Bugzilla](https://bugzilla.mindrot.org/).
++HPN-SSH development is discussed on the [hpnssh-community mailing list](https://lists.psc.edu/mailman/listinfo/hpnssh-community) and on the [GitHub Discussions page](https://github.com/rapier1/hpn-ssh/discussions).
+
+ ## Reporting bugs
+
+-_Non-security_ bugs may be reported to the developers via [Bugzilla](https://bugzilla.mindrot.org/) or via the mailing list above. Security bugs should be reported to [openssh@openssh.com](mailto:openssh.openssh.com).
++_Non-security_ bugs may be reported to the developers via [GitHub Issues](https://github.com/rapier1/hpn-ssh/issues) or via the mailing list above. Security bugs should be reported to [hpnssh@psc.edu](mailto:hpnssh@psc.edu).
+diff -Nur openssh-10.5p1.orig/sandbox-seccomp-filter.c openssh-10.5p1/sandbox-seccomp-filter.c
+--- openssh-10.5p1.orig/sandbox-seccomp-filter.c 2026-09-21 07:06:05.334038325 +0200
++++ openssh-10.5p1/sandbox-seccomp-filter.c 2026-09-21 07:06:59.892579736 +0200
+@@ -323,6 +323,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
+@@ -370,6 +373,9 @@
+ # ifdef MADV_DONTNEED
+ SC_ALLOW_ARG(__NR_madvise, 2, MADV_DONTNEED),
+ # endif
++# ifdef MADV_DONTNEED_LOCKED
++ SC_ALLOW_ARG(__NR_madvise, 2, MADV_DONTNEED_LOCKED),
++# endif
+ # ifdef MADV_DONTFORK
+ SC_ALLOW_ARG(__NR_madvise, 2, MADV_DONTFORK),
+ # endif
+@@ -441,6 +447,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-10.5p1.orig/scp.1 openssh-10.5p1/scp.1
+--- openssh-10.5p1.orig/scp.1 2026-09-21 07:06:05.251643488 +0200
++++ openssh-10.5p1/scp.1 2026-09-21 07:06:59.892706904 +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
+@@ -33,6 +33,8 @@
+ .Sh DESCRIPTION
+ .Nm
+ copies files between hosts on a network.
++It is binary compatible with OpenSSH's scp including
++the use of the same directives and configuration options except where noted.
+ .Pp
+ .Nm
+ uses the SFTP protocol over an
+@@ -258,6 +260,7 @@
+ .It Tunnel
+ .It TunnelDevice
+ .It UpdateHostKeys
++.It UseMPTCP
+ .It User
+ .It UserKnownHostsFile
+ .It VerifyHostKeyDNS
+@@ -294,6 +297,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
+@@ -301,6 +308,14 @@
+ The program must understand
+ .Xr ssh 1
+ options.
++.It Fl z Ar program
++Path to hpnscp on remote system. Useful if remote has multiple scp installs.
++For example, using the resume option but the default remote scp does not have the resume option.
++Use -z to point the version that does - e.g. -z /opt/hpnssh/bin/hpnscp.
++.Nm
++only option.
++.It Fl s
++Use the SFTP protocol for transfers rather than the original scp protocol.
+ .It Fl T
+ Disable strict filename checking.
+ By default when copying files from a remote host to a local directory
+@@ -327,10 +342,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
+@@ -364,6 +382,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-10.5p1.orig/scp.c openssh-10.5p1/scp.c
+--- openssh-10.5p1.orig/scp.c 2026-09-21 07:06:05.263152938 +0200
++++ openssh-10.5p1/scp.c 2026-09-21 10:56:06.660670295 +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
+@@ -113,6 +114,12 @@
+ #include "misc.h"
+ #include "progressmeter.h"
+ #include "utf8.h"
++/* libressl doesn't support the blake2b512 digest so
++ * we need to prevent libressl from using the resume feature
++ * cjr 7/18/2023 */
++#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
++#include <openssl/evp.h>
++#endif
+ #include "sftp.h"
+
+ #include "sftp-common.h"
+@@ -155,6 +162,10 @@
+ /* This is the program to execute for the secure 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;
+@@ -169,6 +180,17 @@
+ int sftp_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)
+ {
+@@ -216,6 +238,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");
+
+@@ -441,6 +466,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 *);
+@@ -459,11 +486,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));
+@@ -488,7 +522,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':
+@@ -569,24 +603,36 @@
+ addargs(&remote_remote_args, "-q");
+ showprogress = 0;
+ break;
++#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
++ 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;
+@@ -674,10 +720,19 @@
+ 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",
++ /* 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" : "");
++ targetshouldbedirectory ? " -d" : "",
++ resume_flag ? " -Z" : "");
++#ifdef DEBUG
++ fprintf(stderr, "%s: Sending cmd %s\n", hostname, cmd);
++#endif
+
+ (void) ssh_signal(SIGPIPE, lostconn);
+
+@@ -688,6 +743,7 @@
+ verifydir(argv[argc - 1]);
+ tolocal(argc, argv, mode, sftp_direct); /* Dest is local host. */
+ }
++
+ /*
+ * Finally check the exit status of the ssh process, if one was forked
+ * and no error has occurred yet
+@@ -1309,6 +1365,74 @@
+ sftp_free(conn);
+ }
+
++/* 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
++ * Note: LibreSSL doesn't support blake2b512 so we can't offer them
++ * the resume feature cjr 7/18/2023 */
++#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
++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 or are using libressl */
++}
++#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)
+@@ -1396,14 +1520,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] == '/')
+@@ -1425,6 +1558,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) {
+@@ -1446,14 +1587,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);
+@@ -1461,13 +1721,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) {
+@@ -1668,24 +1932,36 @@
+ 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");
+
+@@ -1703,9 +1979,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
+@@ -1714,7 +1997,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;
+@@ -1742,6 +2030,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;
+ }
+@@ -1749,6 +2040,9 @@
+ *--cp = 0;
+
+ cp = buf;
++#ifdef DEBUG
++ fprintf(stderr, "%s: buf is %s\n", hostname, buf);
++#endif
+ if (*cp == 'T') {
+ setimes++;
+ cp++;
+@@ -1776,9 +2070,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".
+@@ -1794,6 +2098,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");
+@@ -1804,6 +2120,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);
+@@ -1813,11 +2133,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 ||
+@@ -1887,11 +2228,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;
+@@ -1906,13 +2431,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);
+@@ -1947,8 +2476,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
+@@ -1983,8 +2582,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++)
+@@ -2132,11 +2740,19 @@
+ void
+ usage(void)
+ {
++#if (defined WITH_OPENSSL) && !defined(LIBRESSL_VERSION_NUMBER)
++ (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
+@@ -2275,6 +2891,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-10.5p1.orig/servconf.c openssh-10.5p1/servconf.c
+--- openssh-10.5p1.orig/servconf.c 2026-09-21 07:06:05.563498734 +0200
++++ openssh-10.5p1/servconf.c 2026-09-21 07:06:59.893459084 +0200
+@@ -69,6 +69,7 @@
+ #include "auth.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "sshbuf.h"
+ #include "version.h"
+ #include "ssh-gss.h"
+
+@@ -425,6 +426,13 @@
+ options->sshd_session_path = xstrdup(_PATH_SSHD_SESSION);
+ if (options->sshd_auth_path == NULL)
+ options->sshd_auth_path = xstrdup(_PATH_SSHD_AUTH);
++ /* HPN: NoneMacEnabled has no effect without NoneEnabled
++ * note this placement of this stanza matters. It has to come after
++ * the macros are run so we have these items in the struct */
++ if (options->nonemac_enabled > 0 && options->none_enabled == 0) {
++ logit("NoneMacEnabled ignored: requires NoneEnabled");
++ options->nonemac_enabled = 0;
++ }
+
+ assemble_algorithms(options);
+
+@@ -587,6 +595,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;
+ }
+@@ -1362,6 +1371,30 @@
+ 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 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 sUseMPTCP:
++ intptr = &options->use_mptcp;
++ goto parse_flag;
++
+ case sIgnoreUserKnownHosts:
+ intptr = &options->ignore_user_known_hosts;
+ parse_flag:
+@@ -4380,6 +4413,11 @@
+ dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
+ dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash);
+ dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info);
++ dump_cfg_fmtint(sHPNDisabled, o->hpn_disabled);
++ dump_cfg_fmtint(sTcpRcvBufPoll, o->tcp_rcv_buf_poll);
++ dump_cfg_fmtint(sNoneEnabled, o->none_enabled);
++ dump_cfg_fmtint(sNoneMacEnabled, o->nonemac_enabled);
++ dump_cfg_fmtint(sUseMPTCP, o->use_mptcp);
+ dump_cfg_fmtint(sRefuseConnection, o->refuse_connection);
+
+ /* string arguments */
+diff -Nur openssh-10.5p1.orig/servconf.h openssh-10.5p1/servconf.h
+--- openssh-10.5p1.orig/servconf.h 2026-09-21 07:06:05.564022570 +0200
++++ openssh-10.5p1/servconf.h 2026-09-21 07:06:59.893677395 +0200
+@@ -264,13 +264,22 @@
+ SSHCONF_ALIAS(SKeyAuthentication, KbdInteractiveAuthentication, SSHCFG_ALL) \
+ SSHCONF_ALIAS(KeepAlive, TCPKeepAlive, SSHCFG_GLOBAL)
+
++#define SSHD_CONFIG_ENTRIES_HPN \
++SSHCONF_INTFLAG(tcp_rcv_buf_poll, TcpRcvBufPoll, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(hpn_disabled, HPNDisabled, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(none_enabled, NoneEnabled, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(nonemac_enabled, NoneMacEnabled, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(disable_multithreaded, DisableMTAES, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(use_mptcp, UseMPTCP, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE)
++
+ #define SSHD_CONFIG_ENTRIES_BASE \
+ SSHD_CONFIG_ENTRIES_CUSTOM \
+ SSHD_CONFIG_ENTRIES_MAIN \
+ SSHD_CONFIG_ENTRIES_LEGACY \
+ SSHD_CONFIG_ENTRIES_ALIASES \
+ SSHD_CONFIG_ENTRIES_PAM \
+- SSHD_CONFIG_ENTRIES_LASTLOG
++ SSHD_CONFIG_ENTRIES_LASTLOG \
++ SSHD_CONFIG_ENTRIES_HPN
+
+ #ifdef USE_PAM
+ #define SSHD_CONFIG_ENTRIES_PAM \
+diff -Nur openssh-10.5p1.orig/serverloop.c openssh-10.5p1/serverloop.c
+--- openssh-10.5p1.orig/serverloop.c 2026-09-21 07:06:05.374851732 +0200
++++ openssh-10.5p1/serverloop.c 2026-09-21 07:55:03.009419374 +0200
+@@ -77,6 +77,8 @@
+ #include "serverloop.h"
+ #include "ssherr.h"
+ #include "compat.h"
++#include "metrics.h"
++#include "cipher-switch.h"
+
+ extern ServerOptions options;
+
+@@ -341,6 +343,7 @@
+ sigset_t bsigset, osigset;
+
+ debug("Entering interactive session for SSH2.");
++ ssh->start_time = monotime_double();
+
+ if (sigemptyset(&bsigset) == -1 ||
+ sigaddset(&bsigset, SIGCHLD) == -1)
+@@ -395,9 +398,15 @@
+ collect_children(ssh);
+ free(pfd);
+
++ /* write final log entry (do we need this here? -cjr)*/
++ 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);
+ }
+@@ -600,6 +609,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);
+@@ -668,6 +679,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)
+ {
+@@ -844,6 +916,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-10.5p1.orig/session.c openssh-10.5p1/session.c
+--- openssh-10.5p1.orig/session.c 2026-09-21 07:06:05.471719268 +0200
++++ openssh-10.5p1/session.c 2026-09-21 07:06:59.894033738 +0200
+@@ -90,6 +90,7 @@
+ #include "monitor_wrap.h"
+ #include "sftp.h"
+ #include "atomicio.h"
++#include "cipher-switch.h"
+
+ #if defined(KRB5) && defined(USE_AFS)
+ #include <kafs.h>
+@@ -212,6 +213,7 @@
+ restore_uid();
+
+ /* 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,
+@@ -375,6 +377,7 @@
+ do_exec_no_pty(struct ssh *ssh, Session *s, const char *command)
+ {
+ pid_t pid;
++ int fips = 0;
+ #ifdef USE_PIPES
+ int pin[2], pout[2], perr[2];
+
+@@ -530,6 +533,17 @@
+ session_set_fds(ssh, s, inout[1], inout[1], err[1],
+ s->is_subsystem, 0);
+ #endif
++ /* switch to the parallel ciphers if necessary
++ * If FIPS mode exists and is enabled then no parallel ciphers.
++ */
++ fips = fips_enabled();
++ if (fips)
++ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
++ else
++ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
++
++ if ((options.disable_multithreaded == 0) && (fips == 0))
++ cipher_switch(ssh);
+ return 0;
+ }
+
+@@ -544,6 +558,7 @@
+ {
+ int fdout, ptyfd, ttyfd, ptymaster;
+ pid_t pid;
++ int fips = 0;
+
+ if (s == NULL)
+ fatal("do_exec_pty: no session");
+@@ -641,6 +656,17 @@
+ /* Enter interactive session. */
+ s->ptymaster = ptymaster;
+ session_set_fds(ssh, s, ptyfd, fdout, -1, 1, 1);
++ /* switch to the parallel cipher if appropriate
++ * If FIPS mode exists and is enabled then no parallel ciphers.
++ */
++ fips = fips_enabled();
++ if (fips)
++ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
++ else
++ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
++
++ if ((options.disable_multithreaded == 0) && (fips == 0))
++ cipher_switch(ssh);
+ return 0;
+ }
+
+diff -Nur openssh-10.5p1.orig/sftp.1 openssh-10.5p1/sftp.1
+--- openssh-10.5p1.orig/sftp.1 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/sftp.1 2026-09-21 07:06:59.894216187 +0200
+@@ -55,6 +55,9 @@
+ transport.
+ It may also use many features of ssh, such as public key authentication and
+ compression.
++.Nm
++is binary compatible with OpenSSH's sftp and uses the same directive and configuration
++options except where noted.
+ .Pp
+ The
+ .Ar destination
+@@ -319,6 +322,7 @@
+ .It Tunnel
+ .It TunnelDevice
+ .It UpdateHostKeys
++.It UseMPTCP
+ .It User
+ .It UserKnownHostsFile
+ .It VerifyHostKeyDNS
+@@ -338,7 +342,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
+@@ -367,10 +372,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-10.5p1.orig/sftp.c openssh-10.5p1/sftp.c
+--- openssh-10.5p1.orig/sftp.c 2026-09-21 07:06:05.263539391 +0200
++++ openssh-10.5p1/sftp.c 2026-09-21 10:57:17.554721313 +0200
+@@ -2595,20 +2595,25 @@
+ /* Please keep in sync with ssh.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",
++ 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-10.5p1.orig/sftp-client.c openssh-10.5p1/sftp-client.c
+--- openssh-10.5p1.orig/sftp-client.c 2026-09-21 07:06:05.263338780 +0200
++++ openssh-10.5p1/sftp-client.c 2026-09-21 07:06:59.894843125 +0200
+@@ -60,7 +60,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-10.5p1.orig/ssh.1 openssh-10.5p1/ssh.1
+--- openssh-10.5p1.orig/ssh.1 2026-09-21 07:06:05.564268099 +0200
++++ openssh-10.5p1/ssh.1 2026-09-21 07:06:59.895245287 +0200
+@@ -77,6 +77,14 @@
+ X11 connections, arbitrary TCP ports and
+ .Ux Ns -domain
+ sockets can also be forwarded over the secure channel.
++.Nm
++is binary compatible with the more well known OpenSSH and uses the same configuration
++directives, methods, and keywords except where noted with the exception of the default port.
++HPN-SSH servers, by default, use port 2222 for connection, and as such,
++.Nm
++clients
++attempt to connect on that port. If the connection attempt on port 2222 fails it will fallback to
++port 22. Please see the -p switch for more information.
+ .Pp
+ .Nm
+ connects and logs into the specified
+@@ -524,6 +532,13 @@
+ Port to connect to on the remote host.
+ This can be specified on a
+ per-host basis in the configuration file.
++HPN-SSH uses a default port of 2222. It will automatically
++fallback to use the SSH standard port 22 if it cannot connect
++on port 2222. This fallback behaviour can be modified with the
++FallbackPort option. Note: if outbound port 2222 is
++blocked it may appear that the hpnssh client is non-responsive. In that event,
++either specify the correct port or use the ConnectTimeout option
++to trigger the port fallback more quickly.
+ .Pp
+ .It Fl Q Ar query_option
+ Queries for the algorithms supported by one of the following features:
+@@ -1728,3 +1743,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-10.5p1.orig/ssh_api.c openssh-10.5p1/ssh_api.c
+--- openssh-10.5p1.orig/ssh_api.c 2026-09-21 07:06:05.454828182 +0200
++++ openssh-10.5p1/ssh_api.c 2026-09-21 07:06:59.895391950 +0200
+@@ -427,7 +427,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-10.5p1.orig/sshbuf.c openssh-10.5p1/sshbuf.c
+--- openssh-10.5p1.orig/sshbuf.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/sshbuf.c 2026-09-21 07:06:59.895517155 +0200
+@@ -28,6 +28,8 @@
+ #include "sshbuf.h"
+ #include "misc.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", \
+@@ -49,8 +51,29 @@
+ 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 */
++ int type; /* type of buffer enum (sshbuf_types)*/
+ };
+
++/* update the label string for a given sshbuf. Useful
++ * for debugging */
++void
++sshbuf_relabel(struct sshbuf *buf, const char *label)
++{
++ if (label != NULL)
++ strncpy(buf->label, label, MAX_LABEL_LEN-1);
++}
++
++/* set the type (from enum sshbuf_type) of the given sshbuf.
++ * The purpose is to allow different classes of buffers to
++ * follow different code paths if necessary */
++void
++sshbuf_type(struct sshbuf *buf, int type)
++{
++ if (type < BUF_MAX_TYPE)
++ buf->type = type;
++}
++
+ static inline int
+ sshbuf_check_sanity(const struct sshbuf *buf)
+ {
+@@ -90,7 +113,7 @@
+ }
+
+ struct sshbuf *
+-sshbuf_new(void)
++sshbuf_new_label (const char *label)
+ {
+ struct sshbuf *ret;
+
+@@ -101,6 +124,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;
+@@ -290,7 +315,21 @@
+ {
+ if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
+ return 0;
+- return buf->max_size - (buf->size - buf->off);
++ /* Reserve ~4.76% (1/21) headroom on channel input buffers to prevent
++ * a pathological state during bulk data transfers. The headroom scales
++ * with buffer size and gives the channel time to drain before accepting
++ * more data, smoothing flow control and reducing buffer reallocation.
++ * When the buffer is fuller than the headroom allows we must return 0,
++ * not underflow: size_t is unsigned so a naive subtraction wraps to
++ * SIZE_MAX, causing the caller to read into a nearly-full buffer and
++ * thrash window updates. Only applied to channel input buffers as
++ * other buffer types fail the regression unit tests with headroom. */
++ if (buf->type == BUF_CHANNEL_INPUT) {
++ size_t headroom = buf->max_size - buf->max_size / 21;
++ size_t used = buf->size - buf->off;
++ return (used >= headroom) ? 0 : headroom - used;
++ } else
++ return buf->max_size - (buf->size - buf->off);
+ }
+
+ const u_char *
+@@ -350,9 +389,36 @@
+ */
+ 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 by 4MB at a time. This can result in
++ * the buffer being over allocated (in terms of actual needs) but the
++ * process is fast. 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 explcitly check that the buffer is being used for inbound outbound
++ * channel buffering.
++ * Updated for 18.4.1 -cjr 04/20/24
++ */
++ if (rlen > BUF_WATERSHED && (buf->type == BUF_CHANNEL_OUTPUT || buf->type == BUF_CHANNEL_INPUT)) {
++ /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu max_size is %zu",
++ buf->label, buf, rlen, need, 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 max_size is %zu", */
++ /* buf->label, buf, rlen, need, buf->max_size); */
++ }
+ SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
++
++ /* rlen might be above the max allocation */
+ if (rlen > buf->max_size)
+- rlen = buf->alloc + need;
++ rlen = 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-10.5p1.orig/sshbuf.h openssh-10.5p1/sshbuf.h
+--- openssh-10.5p1.orig/sshbuf.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/sshbuf.h 2026-09-21 11:31:28.786516577 +0200
+@@ -36,18 +36,49 @@
+ #define EVP_PKEY void
+ #endif /* WITH_OPENSSL */
+
+-#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size */
++#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size 128MB */
+ #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;
+
++enum buffer_types {
++ BUF_CHANNEL_OUTPUT,
++ BUF_CHANNEL_INPUT,
++ BUF_CHANNEL_EXTENDED,
++ BUF_PACKET_INPUT,
++ BUF_PACKET_INCOMING,
++ BUF_PACKET_OUTPUT,
++ BUF_PACKET_OUTGOING,
++ BUF_MAX_TYPE
++};
++
+ /*
+ * 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 *);
++
++/*
++ * assign a type (from the buffer_types enum) to
++ * the buffer. Used to quickly identify the purpose of
++ * the buffer.
++ */
++void sshbuf_type(struct sshbuf *, int);
+
+ /*
+ * Create a new, read-only sshbuf buffer from existing data.
+@@ -84,12 +115,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);
+@@ -383,6 +415,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-10.5p1.orig/ssh.c openssh-10.5p1/ssh.c
+--- openssh-10.5p1.orig/ssh.c 2026-09-21 07:06:05.564688649 +0200
++++ openssh-10.5p1/ssh.c 2026-09-21 09:52:15.257154488 +0200
+@@ -98,6 +98,7 @@
+ #include "ssherr.h"
+ #include "utf8.h"
+ #include "hostfile.h"
++#include "cipher-switch.h"
+
+ #ifdef ENABLE_PKCS11
+ #include "ssh-pkcs11.h"
+@@ -1080,6 +1081,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);
+@@ -1758,10 +1763,34 @@
+ }
+
+ /* 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;
++ }
+ exit(255);
++ }
+
+
+ ssh_packet_set_timeout(ssh, options.server_alive_interval,
+@@ -1939,8 +1968,10 @@
+
+ /* Do fork() after authentication. Used by "ssh -f" */
+ static void
+-fork_postauth(void)
++fork_postauth(struct ssh *ssh)
+ {
++ int fips = 0;
++
+ if (need_controlpersist_detach)
+ control_persist_detach();
+ debug("forking to background");
+@@ -1949,17 +1980,29 @@
+ fatal("daemon() failed: %.200s", strerror(errno));
+ if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1)
+ error_f("stdfd_devnull failed");
++ /* we do the cipher switch here in the event that the client
++ * is forking or has a delayed fork.
++ * If FIPS mode exists and is enabled then no parallel ciphers.
++ */
++ fips = fips_enabled();
++ if (fips)
++ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
++ else
++ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
++
++ if ((options.disable_multithreaded == 0) && (fips == 0))
++ cipher_switch(ssh);
+ }
+
+ static void
+-forwarding_success(void)
++forwarding_success(struct ssh *ssh)
+ {
+ if (forward_confirms_pending == -1)
+ return;
+ if (--forward_confirms_pending == 0) {
+ debug_f("all expected forwarding replies received");
+ if (options.fork_after_authentication)
+- fork_postauth();
++ fork_postauth(ssh);
+ } else {
+ debug2_f("%d expected forwarding replies remaining",
+ forward_confirms_pending);
+@@ -2036,7 +2079,7 @@
+ "for listen port %d", rfwd->listen_port);
+ }
+ }
+- forwarding_success();
++ forwarding_success(ssh);
+ }
+
+ static void
+@@ -2063,7 +2106,7 @@
+ }
+
+ debug_f("tunnel forward established, id=%d", id);
+- forwarding_success();
++ forwarding_success(ssh);
+ }
+
+ static void
+@@ -2257,6 +2300,15 @@
+ NULL, fileno(stdin), command, environ);
+ }
+
++/* this used to do a lot more but now it just checks to see
++ * if we are disabling hpn */
++static void
++hpn_options_init(struct ssh *ssh)
++{
++ channel_set_hpn_disabled(options.hpn_disabled);
++ debug_f("HPN disabled: %d", options.hpn_disabled);
++}
++
+ /* open new channel for a session */
+ static int
+ ssh_session2_open(struct ssh *ssh)
+@@ -2278,6 +2330,7 @@
+ window = CHAN_SES_WINDOW_DEFAULT;
+ packetmax = CHAN_SES_PACKET_DEFAULT;
+ if (tty_flag) {
++ window = CHAN_SES_WINDOW_DEFAULT;
+ window >>= 1;
+ packetmax >>= 1;
+ }
+@@ -2285,6 +2338,13 @@
+ "session", SSH_CHANNEL_OPENING, in, out, err,
+ 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 (tty_flag)
+ channel_set_tty(ssh, c);
+ debug3_f("channel_new: %d%s", c->self, tty_flag ? " (tty)" : "");
+@@ -2302,6 +2362,14 @@
+ {
+ int r, id = -1;
+ char *cp, *tun_fwd_ifname = NULL;
++ int fips = 0;
++
++ /*
++ * 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)
+@@ -2393,7 +2461,21 @@
+ debug("deferring postauth fork until remote forward "
+ "confirmation received");
+ } else
+- fork_postauth();
++ fork_postauth(ssh);
++ } else {
++ /* check to see if we are switching ciphers to
++ * one of our parallel versions. If the client is
++ * forking then we handle it in fork_postauth()
++ * If FIPS mode exists and is enabled then no parallel ciphers.
++ */
++ fips = fips_enabled();
++ if (fips)
++ debug2_f("FIPS mode is enabled. Parallel ciphers are disabled");
++ else
++ debug2_f("FIPS mode not found or disabled. Parallel ciphers are enabled");
++ if ((options.disable_multithreaded == 0)
++ && (fips == 0))
++ cipher_switch(ssh);
+ }
+
+ return client_loop(ssh, tty_flag, tty_flag ?
+diff -Nur openssh-10.5p1.orig/ssh_config openssh-10.5p1/ssh_config
+--- openssh-10.5p1.orig/ssh_config 2026-09-21 07:06:05.565058422 +0200
++++ openssh-10.5p1/ssh_config 2026-09-21 07:57:10.454344416 +0200
+@@ -45,6 +45,8 @@
+ # ProxyCommand ssh -q -W %h:%p gateway.example.com
+ # RekeyLimit 1G 1h
+ # UserKnownHostsFile ~/.ssh/known_hosts.d/%k
++# UseMPTCP no
++# HappyEyes no
+ #
+ # This system is following system-wide crypto policy.
+ # To modify the crypto properties (Ciphers, MACs, ...), create a *.conf
+diff -Nur openssh-10.5p1.orig/ssh_config.5 openssh-10.5p1/ssh_config.5
+--- openssh-10.5p1.orig/ssh_config.5 2026-09-21 07:06:05.565188780 +0200
++++ openssh-10.5p1/ssh_config.5 2026-09-21 07:06:59.896359445 +0200
+@@ -62,6 +62,29 @@
+ .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
++.Cm Host
++specifications, and that section is only applied for hosts that
++match one of the patterns given in the specification.
++The matched host name is usually the one given on the command line
++(see the
++.Cm CanonicalizeHostname
++option for exceptions).
+ Unless noted otherwise, for each configuration directive, the first
+ specified value will be used.
+ .Pp
+@@ -870,6 +893,9 @@
+ or
+ .Cm no
+ (the default).
++.It Cm FallbackPort
++Specifies the port hpnssh should try to connect to if it fails connecting to the
++default hpnsshd port of 2222. The default is port 22. HPN-SSH only.
+ .It Cm FingerprintHash
+ Specifies the hash algorithm used when displaying key fingerprints.
+ Valid options are:
+@@ -1097,6 +1123,23 @@
+ will not be converted automatically,
+ but may be manually hashed using
+ .Xr ssh-keygen 1 .
++.It Cm HappyEyes
++If set to
++.Cm yes
++this option specifies that the client should use the Happy Eyeballs connection
++method as described by RFC 8305. When attempting to connect to a
++target that supports IPv4 and IPv6 the client will try to connect
++on IPv6 first, delay a set time (250ms default), and then try IPv4.
++The client will use which ever socket is returned first. Note:
++This process may, rarely, produce false positives in some monitoring tools
++due to multiple connection attempts prior to authentication.
++The default is
++.Cm no
++.It Cm HappyDelay
++This options specifies how long the HappyEyes connection method should
++wait before trying IPv4 after the initial IPv6 connection. This value is
++a positive integer of milliseconds with a default of
++.Cm 250ms.
+ .It Cm HostbasedAcceptedAlgorithms
+ The default is handled system-wide by
+ .Xr crypto-policies 7 .
+@@ -1202,6 +1245,11 @@
+ .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 IdentitiesOnly
+ Specifies that
+ .Xr ssh 1
+@@ -1377,6 +1425,12 @@
+ .Cm none
+ (the operating system default)
+ for non-interactive sessions.
++If using the
++.Cm HappyEyes
++option these default to
++.Cm lowlatency and
++.m throughput ,
++respectively.
+ .It Cm KbdInteractiveAuthentication
+ Specifies whether to use keyboard-interactive authentication.
+ The argument to this keyword must be
+@@ -1565,6 +1619,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
+@@ -1572,6 +1683,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.
+@@ -2151,6 +2292,12 @@
+ Specify a configuration tag name that may be later used by a
+ .Cm Match
+ directive to select a block of configuration.
++.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
+@@ -2236,6 +2383,12 @@
+ from OpenSSH 6.8 and greater support the
+ .Qq hostkeys@openssh.com
+ protocol extension used to inform the client of all the server's hostkeys.
++.It Cm UseMPTCP
++If set to
++.Cm yes ,
++this will enable Multipath TCP (MPTCP) instead of TCP (this only works on Linux).
++The default is
++.Cm no .
+ .It Cm User
+ Specifies the user to log in as.
+ This can be useful when a different user name is used on different machines.
+@@ -2545,3 +2698,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-10.5p1.orig/sshconnect2.c openssh-10.5p1/sshconnect2.c
+--- openssh-10.5p1.orig/sshconnect2.c 2026-09-21 07:06:05.565778460 +0200
++++ openssh-10.5p1/sshconnect2.c 2026-09-21 07:06:59.896625545 +0200
+@@ -74,6 +74,7 @@
+ #include "utf8.h"
+ #include "ssh-sk.h"
+ #include "sk-api.h"
++#include "cipher-switch.h"
+
+ #ifdef GSSAPI
+ #include "ssh-gss.h"
+@@ -83,6 +84,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
+ */
+
+@@ -578,6 +586,42 @@
+
+ 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");
++ }
++ }
++
+ 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-10.5p1.orig/sshconnect.c openssh-10.5p1/sshconnect.c
+--- openssh-10.5p1.orig/sshconnect.c 2026-09-21 07:06:04.862171723 +0200
++++ openssh-10.5p1/sshconnect.c 2026-09-21 07:06:59.896812792 +0200
+@@ -55,6 +55,7 @@
+ #include "ssherr.h"
+ #include "authfd.h"
+ #include "kex.h"
++#include "happyeyeballs.h"
+
+ struct sshkey *previous_host_key = NULL;
+
+@@ -66,6 +67,7 @@
+ extern int debug_flag;
+ extern Options options;
+ extern char *__progname;
++extern char global_ntop[NI_MAXHOST];
+
+ static int show_other_keys(struct hostkeys *, struct sshkey *);
+ static void warn_changed_key(struct sshkey *);
+@@ -376,7 +378,7 @@
+ /*
+ * Creates a socket for use as the ssh connection.
+ */
+-static int
++int
+ ssh_create_socket(struct addrinfo *ai)
+ {
+ int sock, r;
+@@ -388,9 +390,16 @@
+ #endif
+ char ntop[NI_MAXHOST];
+
+- sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
++ /* user request for Multipath TCP */
++ if (options.use_mptcp)
++ sock = socket(ai->ai_family, ai->ai_socktype, IPPROTO_MPTCP);
++ else
++ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
++
+ if (sock == -1) {
+ error("socket: %s", strerror(errno));
++ if (options.use_mptcp)
++ error ("You asked to use MPTCP. Please ensure it is enabled.");
+ return -1;
+ }
+ (void)fcntl(sock, F_SETFD, FD_CLOEXEC);
+@@ -493,6 +502,15 @@
+ sleep(1);
+ debug("Trying again...");
+ }
++ if (options.use_happyeyes == 1) {
++ debug_f ("Attempting RFC 8305 / Happy Eyeballs connection.");
++ sock = happy_eyeballs(host, aitop,
++ hostaddr, timeout_ms);
++ if (sock != -1) {
++ debug_f ("RFC 8305 / Happy Eyeballs connection successful.");
++ break; /* Successful connection. */
++ }
++ } else {
+ /*
+ * Loop through addresses for this host, and try each one in
+ * sequence until the connection succeeds.
+@@ -547,6 +565,7 @@
+ }
+ if (sock != -1)
+ break; /* Successful connection. */
++ }
+ }
+
+ /* Return failure if we didn't get a successful connection. */
+@@ -1658,6 +1677,9 @@
+ /* key exchange */
+ /* authenticate user */
+ debug("Authenticating to %s:%d as '%s'", host, port, server_user);
++ /* if using RFC 8305 clearly state which address we are connected to */
++ if (options.use_happyeyes == 1)
++ debug("RFC 8305 authenticating to %.200s", global_ntop);
+ ssh_kex2(ssh, host, hostaddr, port, cinfo);
+ if (!options.kex_algorithms_set && ssh->kex != NULL &&
+ ssh->kex->name != NULL && options.warn_weak_crypto &&
+diff -Nur openssh-10.5p1.orig/sshd-auth.c openssh-10.5p1/sshd-auth.c
+--- openssh-10.5p1.orig/sshd-auth.c 2026-09-21 07:06:05.455184818 +0200
++++ openssh-10.5p1/sshd-auth.c 2026-09-21 07:06:59.897257581 +0200
+@@ -772,6 +772,25 @@
+ struct kex *kex;
+ int r;
+
++ /* this used to be in sshd.c when we read the configuration file
++ * but needed to be moved here as do_ssh2_kex in sshd-auth wasn't
++ * picking up the none options. CJR 4/10/2025
++ */
++ if (options.none_enabled == 1) {
++ debug("WARNING: None cipher enabled");
++ 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) {
++ debug("WARNING: None MAC enabled");
++ char *old_macs = options.macs;
++ xasprintf(&options.macs, "%s,none", old_macs);
++ free(old_macs);
++ }
++ }
++
+ ssh_packet_set_rekey_limits(ssh, options.rekey_limit,
+ options.rekey_interval);
+
+diff -Nur openssh-10.5p1.orig/sshd.c openssh-10.5p1/sshd.c
+--- openssh-10.5p1.orig/sshd.c 2026-09-21 07:06:05.335664964 +0200
++++ openssh-10.5p1/sshd.c 2026-09-21 07:06:59.897573098 +0200
+@@ -826,6 +826,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)
+@@ -841,11 +843,18 @@
+ continue;
+ }
+ /* Create socket for listening. */
+- listen_sock = socket(ai->ai_family, ai->ai_socktype,
+- ai->ai_protocol);
++ if (options.use_mptcp)
++ listen_sock = socket(ai->ai_family, ai->ai_socktype,
++ IPPROTO_MPTCP);
++ else
++ listen_sock = socket(ai->ai_family, ai->ai_socktype,
++ ai->ai_protocol);
++
+ if (listen_sock == -1) {
+ /* kernel may not support ipv6 */
+ verbose("socket: %.100s", strerror(errno));
++ if (options.use_mptcp)
++ verbose("MPTCP requested but may not be available.");
+ continue;
+ }
+ if (set_nonblock(listen_sock) == -1) {
+@@ -871,6 +880,10 @@
+
+ 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);
++
+ /* 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.",
+diff -Nur openssh-10.5p1.orig/sshd_config openssh-10.5p1/sshd_config
+--- openssh-10.5p1.orig/sshd_config 2026-09-21 07:06:05.566557294 +0200
++++ openssh-10.5p1/sshd_config 2026-09-21 07:59:21.833267103 +0200
+@@ -18,7 +18,7 @@
+ # SELinux about this change.
+ # semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
+ #
+-#Port 22
++Port 2222
+ #AddressFamily any
+ #ListenAddress 0.0.0.0
+ #ListenAddress ::
+@@ -123,6 +123,7 @@
+ #PermitTunnel no
+ #ChrootDirectory none
+ #VersionAddendum none
++#UseMPTCP no
+
+ # no default banner path
+ #Banner none
+@@ -130,6 +131,19 @@
+ # 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
++
++# allow the use of the none cipher
++#NoneEnabled no
++
++# allow the use of the none MAC
++#NoneMacEnabled no
++
+ # Example of overriding settings on a per-user basis
+ #Match User anoncvs
+ # X11Forwarding no
+diff -Nur openssh-10.5p1.orig/sshd_config.5 openssh-10.5p1/sshd_config.5
+--- openssh-10.5p1.orig/sshd_config.5 2026-09-21 07:06:05.566694771 +0200
++++ openssh-10.5p1/sshd_config.5 2026-09-21 07:06:59.898098486 +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):
+@@ -678,6 +688,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.
+@@ -1038,6 +1052,11 @@
+ that the server offers.
+ 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 IgnoreRhosts
+ Specifies whether to ignore per-user
+ .Pa .rhosts
+@@ -1562,6 +1581,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 PAMServiceName
+ Specifies the service name used for Pluggable Authentication Modules (PAM)
+ authentication, authorisation and session controls when
+@@ -2138,6 +2170,13 @@
+ .Pp
+ To disable TCP keepalive messages, the value should be set to
+ .Cm no .
++.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
+@@ -2198,6 +2237,12 @@
+ .Cm Match
+ .Cm Host
+ directives.
++.It Cm UseMPTCP
++If set to
++.Cm yes ,
++this will enable Multipath TCP (MPTCP) instead of TCP (this only works on Linux).
++The default is
++.Cm no .
+ .It Cm UsePAM
+ Enables the Pluggable Authentication Module interface.
+ If set to
+@@ -2443,3 +2488,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-10.5p1.orig/sshd-session.c openssh-10.5p1/sshd-session.c
+--- openssh-10.5p1.orig/sshd-session.c 2026-09-21 07:06:05.567169165 +0200
++++ openssh-10.5p1/sshd-session.c 2026-09-21 07:06:59.898405237 +0200
+@@ -1107,6 +1107,20 @@
+ }
+ endpwent();
+
++ /* get NONE 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);
++ }
++ }
++
+ if (!debug_flag && !inetd_flag) {
+ if ((startup_pipe = dup(REEXEC_CONFIG_PASS_FD)) == -1)
+ fatal("internal error: no startup pipe");
+@@ -1320,6 +1334,9 @@
+ rdomain == NULL ? "" : "\"");
+ free(laddr);
+
++ /* set the HPN options for the child */
++ channel_set_hpn_disabled(options.hpn_disabled);
++
+ /*
+ * We don't want to listen forever unless the other side
+ * successfully authenticates itself. So we set up an alarm which is
+diff -Nur openssh-10.5p1.orig/ssh.h openssh-10.5p1/ssh.h
+--- openssh-10.5p1.orig/ssh.h 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/ssh.h 2026-09-21 11:32:03.114551736 +0200
+@@ -14,6 +14,7 @@
+
+ /* Default port number. */
+ #define SSH_DEFAULT_PORT 22
++#define HPNSSH_DEFAULT_PORT 2222
+
+ /*
+ * Maximum number of certificate files that can be specified
+@@ -43,7 +44,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-10.5p1.orig/sshkey.c openssh-10.5p1/sshkey.c
+--- openssh-10.5p1.orig/sshkey.c 2026-09-21 07:06:05.530076895 +0200
++++ openssh-10.5p1/sshkey.c 2026-09-21 07:06:59.898715486 +0200
+@@ -1763,7 +1763,8 @@
+ 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), 0,
++ CIPHER_ENCRYPT, CIPHER_SERIAL)) != 0)
+ goto out;
+
+ /* Serialise and encrypt the private key using the ephemeral key */
+@@ -1897,7 +1898,8 @@
+ 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,
++ CIPHER_DECRYPT, CIPHER_SERIAL)) != 0)
+ goto out;
+ #ifdef DEBUG_PK
+ fprintf(stderr, "%s: key+iv\n", __func__);
+@@ -2945,6 +2947,13 @@
+ kdfname = "none";
+ } else if (ciphername == NULL)
+ ciphername = DEFAULT_CIPHERNAME;
++ /*
++ * NOTE: Without OpenSSL, this string comparison is still safe, even
++ * though it will never match because the multithreaded cipher is not
++ * enabled.
++ */
++ else if (strcmp(ciphername, "chacha20-poly1305-mt@hpnssh.org") == 0)
++ ciphername = "chacha20-poly1305@openssh.com";
+ if ((cipher = cipher_by_name(ciphername)) == NULL) {
+ r = SSH_ERR_INVALID_ARGUMENT;
+ goto out;
+@@ -2980,7 +2989,7 @@
+ goto out;
+ }
+ if ((r = cipher_init(&ciphercontext, cipher, key, keylen,
+- key + keylen, ivlen, 1)) != 0)
++ key + keylen, ivlen, 0, CIPHER_ENCRYPT, CIPHER_SERIAL)) != 0)
+ goto out;
+
+ if ((r = sshbuf_put(encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC))) != 0 ||
+@@ -3166,6 +3175,8 @@
+ (r = sshbuf_get_u32(decoded, &encrypted_len)) != 0)
+ goto out;
+
++ if (strcmp(ciphername, "chacha20-poly1305-mt@hpnssh.org") == 0)
++ strcpy(ciphername, "chacha20-poly1305@openssh.com");
+ if ((cipher = cipher_by_name(ciphername)) == NULL) {
+ r = SSH_ERR_KEY_UNKNOWN_CIPHER;
+ goto out;
+@@ -3221,7 +3232,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, CIPHER_DECRYPT, CIPHER_SERIAL)) != 0)
+ goto out;
+ if ((r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(decoded),
+ encrypted_len, 0, authlen)) != 0) {
+diff -Nur openssh-10.5p1.orig/umac.c openssh-10.5p1/umac.c
+--- openssh-10.5p1.orig/umac.c 2026-08-11 04:22:17.000000000 +0200
++++ openssh-10.5p1/umac.c 2026-09-21 07:06:59.899122523 +0200
+@@ -136,15 +136,47 @@
+ /* --- 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 <rapier@psc.edu> 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)
+ #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-10.5p1.orig/uthash.h openssh-10.5p1/uthash.h
+--- openssh-10.5p1.orig/uthash.h 1970-01-01 01:00:00.000000000 +0100
++++ openssh-10.5p1/uthash.h 2026-09-21 07:06:59.899547235 +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 <string.h> /* memcmp, memset, strlen */
++#include <stddef.h> /* ptrdiff_t */
++#include <stdlib.h> /* 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 <stdint.h> /* 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 <stdio.h> /* 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 <unistd.h> 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-10.5p1.orig/version.h openssh-10.5p1/version.h
+--- openssh-10.5p1.orig/version.h 2026-09-21 07:06:05.567643988 +0200
++++ openssh-10.5p1/version.h 2026-09-21 08:01:30.877191173 +0200
+@@ -16,5 +16,6 @@
+
+ #define SSH_PORTABLE "p1"
+ #define GSI_PORTABLE "c-GSI"
+-#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE \
++#define SSH_HPN "_hpn18.11.1"
++#define SSH_RELEASE SSH_VERSION SSH_PORTABLE GSI_PORTABLE SSH_HPN \
+ GSI_VERSION KRB5_VERSION
diff --git a/gsi-openssh.spec b/gsi-openssh.spec
index c143036..0dbfec8 100644
--- a/gsi-openssh.spec
+++ b/gsi-openssh.spec
@@ -23,12 +23,12 @@
# Do we want libedit support
%global libedit 1
-%global openssh_ver 10.3p1
+%global openssh_ver 10.5p1
Summary: An implementation of the SSH protocol with GSI authentication
Name: gsi-openssh
Version: %{openssh_ver}
-Release: 3%{?dist}
+Release: 1%{?dist}
Provides: gsissh = %{version}-%{release}
Obsoletes: gsissh < 5.8p2-2
URL: http://www.openssh.com/portable.html
@@ -48,13 +48,14 @@ Source20: gsissh-host-keys-migration.sh
Source21: gsissh-host-keys-migration.service
Source99: README.sshd-and-gsisshd
-Patch0001: 0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
-Patch0002: 0002-Implement-SELinux-environment-variable-setup-for-sub.patch
-Patch0003: 0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
-Patch0004: 0004-openssh-6.6p1-keycat.patch
-Patch0005: 0005-openssh-6.6p1-allow-ip-opts.patch
-Patch0006: 0006-openssh-5.9p1-ipv6man.patch
-Patch0007: 0007-openssh-5.8p2-sigpipe.patch
+Patch0001: 0001-openssh-6.7p1-coverity.patch
+Patch0002: 0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
+Patch0003: 0003-Implement-SELinux-environment-variable-setup-for-sub.patch
+Patch0004: 0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
+Patch0005: 0005-openssh-6.6p1-keycat.patch
+Patch0006: 0006-openssh-6.6p1-allow-ip-opts.patch
+Patch0007: 0007-openssh-5.9p1-ipv6man.patch
+Patch0008: 0008-openssh-5.8p2-sigpipe.patch
Patch0009: 0009-openssh-5.1p1-askpass-progress.patch
Patch0010: 0010-openssh-4.3p2-askpass-grab-info.patch
Patch0011: 0011-openssh-8.7p1-redhat.patch
@@ -91,25 +92,23 @@ Patch0041: 0041-openssh-8.7p1-nohostsha1proof.patch
Patch0042: 0042-openssh-9.9p1-separate-keysign.patch
Patch0043: 0043-openssh-9.9p1-openssl-mlkem.patch
Patch0044: 0044-openssh-9.9p2-error_processing.patch
-Patch0045: 0045-Provide-better-error-for-non-supported-private-keys.patch
-Patch0046: 0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
-Patch0047: 0047-support-authentication-indicators-in-GSSAPI.patch
-Patch0048: 0048-NIST-curves-hybrid-KEX-implementation.patch
-Patch0049: 0049-openssh-7.3p1-x11-max-displays.patch
-Patch0050: 0050-Fix-ssh-pkcs11-client-helper-termination.patch
-Patch0051: 0051-openssh-10.2p1-pam-auth.patch
-Patch0052: 0052-gssapi-s4u.patch
-Patch0053: 0053-openssh-10.2p1-pkcs11-uri.patch
-Patch0054: 0054-gssapi-tests.patch
-Patch1000: 1000-openssh-6.7p1-coverity.patch
+Patch0045: 0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
+Patch0046: 0046-support-authentication-indicators-in-GSSAPI.patch
+Patch0047: 0047-NIST-curves-hybrid-KEX-implementation.patch
+Patch0048: 0048-openssh-7.3p1-x11-max-displays.patch
+Patch0049: 0049-Fix-ssh-pkcs11-client-helper-termination.patch
+Patch0050: 0050-openssh-10.2p1-pam-auth.patch
+Patch0051: 0051-gssapi-s4u.patch
+Patch0052: 0052-openssh-10.2p1-pkcs11-uri.patch
+Patch0053: 0053-gssapi-tests.patch
# This is the patch that adds GSI support
# Based on hpn_isshd-gsi.7.5p1b.patch from Globus upstream
-Patch2000: 2000-openssh-10.3p1-gsissh.patch
+Patch2000: 2000-openssh-10.5p1-gsissh.patch
# This is the HPN patch
-# Based on https://github.com/rapier1/hpn-ssh/ tag: hpn-18.9.0
-Patch2001: 2001-openssh-10.3p1-hpn-18.9.0.patch
+# Based on https://github.com/rapier1/hpn-ssh/ tag: hpn-18.11.1
+Patch2001: 2001-openssh-10.5p1-hpn-18.11.1.patch
License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND snprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant
Requires: openssl-libs >= 1:3.5.0
@@ -381,7 +380,7 @@ fi
%files
%license LICENCE
-%doc CREDITS ChangeLog OVERVIEW PROTOCOL* README HPN-README README.platform README.privsep README.tun README.dns README.sshd-and-gsisshd TODO
+%doc CREDITS ChangeLog OVERVIEW PROTOCOL* README.md HPN-README README.platform README.privsep README.dns README.sshd-and-gsisshd TODO
%attr(0755,root,root) %dir %{_sysconfdir}/gsissh
%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/gsissh/moduli
%attr(0755,root,root) %{_bindir}/gsissh-keygen
@@ -437,6 +436,9 @@ fi
%ghost %attr(0644,root,root) %{_localstatedir}/lib/.gsissh-host-keys-migration
%changelog
+* Sun Sep 20 2026 Mattias Ellert <mattias.ellert@physics.uu.se> - 10.5p1-1
+- Based on openssh-10.5p1-1.fc45
+
* Thu Jul 16 2026 Fedora Release Engineering <releng@fedoraproject.org> - 10.3p1-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild
diff --git a/sources b/sources
index 5d89229..dc67a06 100644
--- a/sources
+++ b/sources
@@ -1,3 +1,3 @@
-SHA512 (openssh-10.3p1.tar.gz) = cb2bd67086491c25e305879b924c3dfa8236502a60c7f250b2fd17d2d9a79ebfc2e40b2f43e42dcf598cc510996e00cc03df9b8e38f34bc2dc71a3d4ff3788fa
-SHA512 (openssh-10.3p1.tar.gz.asc) = 2c8afbe57f6712f159aa3a160b6ffc43f945a98ccd8e151fa6a047ea30b376e5e1cac844a678a7899da80704b3d23feda612ffada1ee003f4c7fb8291f484600
+SHA512 (openssh-10.5p1.tar.gz) = 5336c1945527b29ed59cc500fa8bfbd918f5ba97e85512daad9ab7241418929bf7152d300e5f3c5f9a8c8d699dfde5a64c5faa4fc33b2758ab52dd04040d6284
+SHA512 (openssh-10.5p1.tar.gz.asc) = b31e61d98420598e40c9f7515e833c140415dd1f59391a558e3f48c878d1fa1defc8ec316a17a9737302cded54a216a95425a785e661adc86d70dd2c95014fe7
SHA512 (gpgkey-736060BA.gpg) = df44f3fdbcd1d596705348c7f5aed3f738c5f626a55955e0642f7c6c082995cf36a1b1891bb41b8715cb2aff34fef1c877e0eff0d3507dd00a055ba695757a21
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-22 19:58 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-22 19:58 [rpms/gsi-openssh] rawhide: Based on openssh-10.5p1-1.fc45 Mattias Ellert
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox