diff --git a/.gitignore b/.gitignore
index e69de29..0a4a73d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,47 @@
+openssh-5.5p1-noacss.tar.bz2
+pam_ssh_agent_auth-0.9.2.tar.bz2
+/openssh-5.6p1-noacss.tar.bz2
+/pam_ssh_agent_auth-0.9.2.tar.bz2
+/openssh-5.8p1-noacss.tar.bz2
+/openssh-5.8p2-noacss.tar.bz2
+/openssh-5.9p1-noacss.tar.bz2
+/pam_ssh_agent_auth-0.9.3.tar.bz2
+/openssh-6.0p1-noacss.tar.bz2
+/openssh-6.1p1-noacss.tar.bz2
+/openssh-6.2p1.tar.gz
+/openssh-6.2p2.tar.gz
+/openssh-6.3p1.tar.gz
+/openssh-6.4p1.tar.gz
+/openssh-6.6p1.tar.gz
+/openssh-6.7p1.tar.gz
+/openssh-6.8p1.tar.gz
+/openssh-6.9p1.tar.gz
+/openssh-7.0p1.tar.gz
+/openssh-7.1p1.tar.gz
+/openssh-7.1p2.tar.gz
+/pam_ssh_agent_auth-0.10.2.tar.bz2
+/openssh-7.2p1.tar.gz
+/openssh-7.2p2.tar.gz
+/openssh-7.3p1.tar.gz
+/openssh-7.4p1.tar.gz
+/pam_ssh_agent_auth-0.10.3.tar.bz2
+/openssh-7.5p1.tar.gz
+/openssh-7.6p1.tar.gz
+/openssh-7.7p1.tar.gz
+/openssh-7.7p1.tar.gz.asc
+/DJM-GPG-KEY.gpg
+/openssh-7.8p1.tar.gz
+/openssh-7.8p1.tar.gz.asc
+/openssh-7.9p1.tar.gz
+/openssh-7.9p1.tar.gz.asc
+/openssh-8.0p1.tar.gz
+/openssh-8.0p1.tar.gz.asc
+/openssh-8.1p1.tar.gz
+/openssh-8.1p1.tar.gz.asc
+/openssh-8.2p1.tar.gz
+/openssh-8.2p1.tar.gz.asc
+/openssh-8.3p1.tar.gz
+/openssh-8.3p1.tar.gz.asc
+/openssh-8.4p1.tar.gz
+/openssh-8.4p1.tar.gz.asc
+/pam_ssh_agent_auth-0.10.4.tar.gz
diff --git a/openssh-4.3p2-askpass-grab-info.patch b/openssh-4.3p2-askpass-grab-info.patch
new file mode 100644
index 0000000..e9a0b0d
--- /dev/null
+++ b/openssh-4.3p2-askpass-grab-info.patch
@@ -0,0 +1,19 @@
+diff -up openssh-7.4p1/contrib/gnome-ssh-askpass2.c.grab-info openssh-7.4p1/contrib/gnome-ssh-askpass2.c
+--- openssh-7.4p1/contrib/gnome-ssh-askpass2.c.grab-info	2016-12-23 13:31:22.645213115 +0100
++++ openssh-7.4p1/contrib/gnome-ssh-askpass2.c	2016-12-23 13:31:40.997216691 +0100
+@@ -65,9 +65,12 @@ report_failed_grab (GtkWidget *parent_wi
+ 	err = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
+ 				     GTK_MESSAGE_ERROR,
+ 				     GTK_BUTTONS_CLOSE,
+-				     "Could not grab %s. "
+-				     "A malicious client may be eavesdropping "
+-				     "on your session.", what);
++				     "SSH password dialog could not grab the %s input.\n"
++				     "This might be caused by application such as screensaver, "
++				     "however it could also mean that someone may be eavesdropping "
++				     "on your session.\n"
++				     "Either close the application which grabs the %s or "
++				     "log out and log in again to prevent this from happening.", what, what);
+ 	gtk_window_set_position(GTK_WINDOW(err), GTK_WIN_POS_CENTER);
+ 
+ 	gtk_dialog_run(GTK_DIALOG(err));
diff --git a/openssh-5.1p1-askpass-progress.patch b/openssh-5.1p1-askpass-progress.patch
new file mode 100644
index 0000000..e0ecb80
--- /dev/null
+++ b/openssh-5.1p1-askpass-progress.patch
@@ -0,0 +1,83 @@
+diff -up openssh-7.4p1/contrib/gnome-ssh-askpass2.c.progress openssh-7.4p1/contrib/gnome-ssh-askpass2.c
+--- openssh-7.4p1/contrib/gnome-ssh-askpass2.c.progress	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/contrib/gnome-ssh-askpass2.c	2016-12-23 13:31:16.545211926 +0100
+@@ -53,6 +53,7 @@
+ #include <unistd.h>
+ 
+ #include <X11/Xlib.h>
++#include <glib.h>
+ #include <gtk/gtk.h>
+ #include <gdk/gdkx.h>
+ #include <gdk/gdkkeysyms.h>
+@@ -81,14 +82,25 @@ ok_dialog(GtkWidget *entry, gpointer dia
+ 	return 1;
+ }
+ 
++static void
++move_progress(GtkWidget *entry, gpointer progress)
++{
++	gdouble step;
++	g_return_if_fail(GTK_IS_PROGRESS_BAR(progress));
++	
++	step = g_random_double_range(0.03, 0.1);
++	gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(progress), step);
++	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progress));
++}
++
+ static int
+ passphrase_dialog(char *message, int prompt_type)
+ {
+ 	const char *failed;
+ 	char *passphrase, *local;
+ 	int result, grab_tries, grab_server, grab_pointer;
+ 	int buttons, default_response;
+-	GtkWidget *parent_window, *dialog, *entry;
++	GtkWidget *parent_window, *dialog, *entry, *progress, *hbox;
+ 	GdkGrabStatus status;
+ 	GdkColor fg, bg;
+ 	int fg_set = 0, bg_set = 0;
+@@ -104,14 +116,19 @@ passphrase_dialog(char *message)
+ 		gtk_widget_modify_bg(dialog, GTK_STATE_NORMAL, &bg);
+ 
+ 	if (prompt_type == PROMPT_ENTRY || prompt_type == PROMPT_NONE) {
++		hbox = gtk_hbox_new(FALSE, 0);
++		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE,
++		    FALSE, 0);
++		gtk_widget_show(hbox);
++
+		entry = gtk_entry_new();
+		if (fg_set)
+			gtk_widget_modify_fg(entry, GTK_STATE_NORMAL, &fg);
+		if (bg_set)
+			gtk_widget_modify_bg(entry, GTK_STATE_NORMAL, &bg);
+		gtk_box_pack_start(
+-		    GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+-		    entry, FALSE, FALSE, 0);
++		    GTK_BOX(hbox), entry, TRUE, FALSE, 0);
++		gtk_entry_set_width_chars(GTK_ENTRY(entry), 2);
+ 		gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+ 		gtk_widget_grab_focus(entry);
+ 		if (prompt_type == PROMPT_ENTRY) {
+@@ -130,6 +145,22 @@ passphrase_dialog(char *message)
+ 			g_signal_connect(G_OBJECT(entry), "key_press_event",
+ 			                 G_CALLBACK(check_none), dialog);
+ 		}
++
++		hbox = gtk_hbox_new(FALSE, 0);
++		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
++		    hbox, FALSE, FALSE, 8);
++		gtk_widget_show(hbox);
++
++		progress = gtk_progress_bar_new();
++
++		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress),
++		    "Passphrase length hidden intentionally");
++		gtk_box_pack_start(GTK_BOX(hbox), progress, TRUE,
++		    TRUE, 5);
++		gtk_widget_show(progress);
++		g_signal_connect(G_OBJECT(entry), "changed",
++				 G_CALLBACK(move_progress), progress);
++
+ 	}
+ 
+ 	/* Grab focus */
diff --git a/openssh-5.8p2-sigpipe.patch b/openssh-5.8p2-sigpipe.patch
new file mode 100644
index 0000000..56af045
--- /dev/null
+++ b/openssh-5.8p2-sigpipe.patch
@@ -0,0 +1,12 @@
+diff -up openssh-5.8p2/ssh-keyscan.c.sigpipe openssh-5.8p2/ssh-keyscan.c
+--- openssh-5.8p2/ssh-keyscan.c.sigpipe	2011-08-23 18:30:33.873025916 +0200
++++ openssh-5.8p2/ssh-keyscan.c	2011-08-23 18:32:24.574025362 +0200
+@@ -715,6 +715,8 @@ main(int argc, char **argv)
+ 		fdlim_set(maxfd);
+ 	fdcon = xcalloc(maxfd, sizeof(con));
+ 
++	signal(SIGPIPE, SIG_IGN);
++
+ 	read_wait_nfdset = howmany(maxfd, NFDBITS);
+ 	read_wait = xcalloc(read_wait_nfdset, sizeof(fd_mask));
+ 
diff --git a/openssh-5.9p1-ipv6man.patch b/openssh-5.9p1-ipv6man.patch
new file mode 100644
index 0000000..ece1a73
--- /dev/null
+++ b/openssh-5.9p1-ipv6man.patch
@@ -0,0 +1,24 @@
+diff -up openssh-5.9p0/ssh.1.ipv6man openssh-5.9p0/ssh.1
+--- openssh-5.9p0/ssh.1.ipv6man	2011-08-05 22:17:32.000000000 +0200
++++ openssh-5.9p0/ssh.1	2011-08-31 13:08:34.880024485 +0200
+@@ -1400,6 +1400,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 -up openssh-5.9p0/sshd.8.ipv6man openssh-5.9p0/sshd.8
+--- openssh-5.9p0/sshd.8.ipv6man	2011-08-05 22:17:32.000000000 +0200
++++ openssh-5.9p0/sshd.8	2011-08-31 13:10:34.129039094 +0200
+@@ -940,6 +940,8 @@ concurrently for different ports, this c
+ 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 ,
diff --git a/openssh-6.3p1-ctr-evp-fast.patch b/openssh-6.3p1-ctr-evp-fast.patch
new file mode 100644
index 0000000..ddcb7f1
--- /dev/null
+++ b/openssh-6.3p1-ctr-evp-fast.patch
@@ -0,0 +1,101 @@
+diff -up openssh-5.9p1/cipher-ctr.c.ctr-evp openssh-5.9p1/cipher-ctr.c
+--- openssh-5.9p1/cipher-ctr.c.ctr-evp	2012-01-11 09:24:06.000000000 +0100
++++ openssh-5.9p1/cipher-ctr.c	2012-01-11 15:54:04.675956600 +0100
+@@ -38,7 +38,7 @@ void ssh_aes_ctr_iv(EVP_CIPHER_CTX *, in
+ 
+ struct ssh_aes_ctr_ctx
+ {
+-	AES_KEY		aes_ctx;
++	EVP_CIPHER_CTX	ecbctx;
+ 	u_char		aes_counter[AES_BLOCK_SIZE];
+ };
+ 
+@@ -63,21 +63,42 @@ ssh_aes_ctr(EVP_CIPHER_CTX *ctx, u_char
+ {
+ 	struct ssh_aes_ctr_ctx *c;
+ 	size_t n = 0;
+-	u_char buf[AES_BLOCK_SIZE];
++	u_char ctrbuf[AES_BLOCK_SIZE*256];
++	u_char buf[AES_BLOCK_SIZE*256];
+ 
+ 	if (len == 0)
+ 		return (1);
+ 	if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL)
+ 		return (0);
+ 
+-	while ((len--) > 0) {
++	for (; len > 0; len -= sizeof(u_int)) {
++		u_int r,a,b;
++
+ 		if (n == 0) {
+-			AES_encrypt(c->aes_counter, buf, &c->aes_ctx);
+-			ssh_ctr_inc(c->aes_counter, AES_BLOCK_SIZE);
++			int outl, i, buflen;
++
++			buflen = MIN(len, sizeof(ctrbuf));
++
++			for(i = 0; i < buflen; i += AES_BLOCK_SIZE) {
++				memcpy(&ctrbuf[i], c->aes_counter, AES_BLOCK_SIZE);
++				ssh_ctr_inc(c->aes_counter, AES_BLOCK_SIZE);
++			}
++
++			EVP_EncryptUpdate(&c->ecbctx, buf, &outl,
++				ctrbuf, buflen);
+ 		}
+-		*(dest++) = *(src++) ^ buf[n];
+-		n = (n + 1) % AES_BLOCK_SIZE;
++
++		memcpy(&a, src, sizeof(a));
++		memcpy(&b, &buf[n], sizeof(b));
++		r = a ^ b;
++		memcpy(dest, &r, sizeof(r));
++		src += sizeof(a);
++		dest += sizeof(r);
++
++		n = (n + sizeof(b)) % sizeof(buf);
+ 	}
++	memset(ctrbuf, '\0', sizeof(ctrbuf));
++	memset(buf, '\0', sizeof(buf));
+ 	return (1);
+ }
+ 
+@@ -91,9 +112,28 @@ ssh_aes_ctr_init(EVP_CIPHER_CTX *ctx, co
+ 		c = xmalloc(sizeof(*c));
+ 		EVP_CIPHER_CTX_set_app_data(ctx, c);
+ 	}
+-	if (key != NULL)
+-		AES_set_encrypt_key(key, EVP_CIPHER_CTX_key_length(ctx) * 8,
+-		    &c->aes_ctx);
++
++	EVP_CIPHER_CTX_init(&c->ecbctx);
++
++	if (key != NULL) {
++		const EVP_CIPHER *cipher;
++		switch(EVP_CIPHER_CTX_key_length(ctx)*8) {
++			case 128:
++				cipher = EVP_aes_128_ecb();
++				break;
++			case 192:
++				cipher = EVP_aes_192_ecb();
++				break;
++			case 256:
++				cipher = EVP_aes_256_ecb();
++				break;
++			default:
++				fatal("ssh_aes_ctr_init: wrong aes key length");
++		}
++		if(!EVP_EncryptInit_ex(&c->ecbctx, cipher, NULL, key, NULL))
++			fatal("ssh_aes_ctr_init: cannot initialize aes encryption");
++		EVP_CIPHER_CTX_set_padding(&c->ecbctx, 0);
++	}
+ 	if (iv != NULL)
+ 		memcpy(c->aes_counter, iv, AES_BLOCK_SIZE);
+ 	return (1);
+@@ -105,6 +145,7 @@ ssh_aes_ctr_cleanup(EVP_CIPHER_CTX *ctx)
+ 	struct ssh_aes_ctr_ctx *c;
+ 
+ 	if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) != NULL) {
++		EVP_CIPHER_CTX_cleanup(&c->ecbctx);
+ 		memset(c, 0, sizeof(*c));
+ 		free(c);
+ 		EVP_CIPHER_CTX_set_app_data(ctx, NULL);
diff --git a/openssh-6.4p1-fromto-remote.patch b/openssh-6.4p1-fromto-remote.patch
new file mode 100644
index 0000000..4a7d849
--- /dev/null
+++ b/openssh-6.4p1-fromto-remote.patch
@@ -0,0 +1,16 @@
+diff --git a/scp.c b/scp.c
+index d98fa67..25d347b 100644
+--- a/scp.c
++++ b/scp.c
+@@ -638,7 +638,10 @@ toremote(char *targ, int argc, char **argv)
+ 			addargs(&alist, "%s", ssh_program);
+ 			addargs(&alist, "-x");
+ 			addargs(&alist, "-oClearAllForwardings=yes");
+-			addargs(&alist, "-n");
++			if (isatty(fileno(stdin)))
++				addargs(&alist, "-t");
++			else
++				addargs(&alist, "-n");
+ 			for (j = 0; j < remote_remote_args.num; j++) {
+ 				addargs(&alist, "%s",
+ 				    remote_remote_args.list[j]);
diff --git a/openssh-6.6.1p1-log-in-chroot.patch b/openssh-6.6.1p1-log-in-chroot.patch
new file mode 100644
index 0000000..fa0717f
--- /dev/null
+++ b/openssh-6.6.1p1-log-in-chroot.patch
@@ -0,0 +1,263 @@
+diff -up openssh-7.4p1/log.c.log-in-chroot openssh-7.4p1/log.c
+--- openssh-7.4p1/log.c.log-in-chroot	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/log.c	2016-12-23 15:14:33.330168088 +0100
+@@ -250,6 +250,11 @@ debug3(const char *fmt,...)
+ void
+ log_init(char *av0, LogLevel level, SyslogFacility facility, int on_stderr)
+ {
++	log_init_handler(av0, level, facility, on_stderr, 1);
++}
++
++void
++log_init_handler(char *av0, LogLevel level, SyslogFacility facility, int on_stderr, int reset_handler) {
+ #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
+ 	struct syslog_data sdata = SYSLOG_DATA_INIT;
+ #endif
+@@ -273,8 +278,10 @@ log_init(char *av0, LogLevel level, Sysl
+ 		exit(1);
+ 	}
+ 
+-	log_handler = NULL;
+-	log_handler_ctx = NULL;
++	if (reset_handler) {
++		log_handler = NULL;
++		log_handler_ctx = NULL;
++	}
+ 
+ 	log_on_stderr = on_stderr;
+ 	if (on_stderr)
+diff -up openssh-7.4p1/log.h.log-in-chroot openssh-7.4p1/log.h
+--- openssh-7.4p1/log.h.log-in-chroot	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/log.h	2016-12-23 15:14:33.330168088 +0100
+@@ -49,6 +49,7 @@ typedef enum {
+ typedef void (log_handler_fn)(LogLevel, const char *, void *);
+ 
+ void     log_init(char *, LogLevel, SyslogFacility, int);
++void     log_init_handler(char *, LogLevel, SyslogFacility, int, int);
+ LogLevel log_level_get(void);
+ int      log_change_level(LogLevel);
+ int      log_is_on_stderr(void);
+diff -up openssh-7.4p1/monitor.c.log-in-chroot openssh-7.4p1/monitor.c
+--- openssh-7.4p1/monitor.c.log-in-chroot	2016-12-23 15:14:33.311168085 +0100
++++ openssh-7.4p1/monitor.c	2016-12-23 15:16:42.154193100 +0100
+@@ -307,6 +307,8 @@ monitor_child_preauth(Authctxt *_authctx
+ 		close(pmonitor->m_log_sendfd);
+ 	pmonitor->m_log_sendfd = pmonitor->m_recvfd = -1;
+ 
++	pmonitor->m_state = "preauth";
++
+ 	authctxt = (Authctxt *)ssh->authctxt;
+ 	memset(authctxt, 0, sizeof(*authctxt));
+ 	ssh->authctxt = authctxt;
+@@ -405,6 +407,8 @@ monitor_child_postauth(struct monitor *p
+ 	close(pmonitor->m_recvfd);
+ 	pmonitor->m_recvfd = -1;
+ 
++	pmonitor->m_state = "postauth";
++
+ 	monitor_set_child_handler(pmonitor->m_pid);
+ 	ssh_signal(SIGHUP, &monitor_child_handler);
+ 	ssh_signal(SIGTERM, &monitor_child_handler);
+@@ -472,7 +476,7 @@ monitor_read_log(struct monitor *pmonito
+ 	if (log_level_name(level) == NULL)
+ 		fatal("%s: invalid log level %u (corrupted message?)",
+ 		    __func__, level);
+-	do_log2(level, "%s [preauth]", msg);
++	do_log2(level, "%s [%s]", msg, pmonitor->m_state);
+ 
+ 	sshbuf_free(logmsg);
+ 	free(msg);
+@@ -1719,13 +1723,28 @@ monitor_init(void)
+ 	mon = xcalloc(1, sizeof(*mon));
+ 	monitor_openfds(mon, 1);
+ 
++	mon->m_state = "";
++
+ 	return mon;
+ }
+ 
+ void
+-monitor_reinit(struct monitor *mon)
++monitor_reinit(struct monitor *mon, const char *chroot_dir)
+ {
+-	monitor_openfds(mon, 0);
++	struct stat dev_log_stat;
++	char *dev_log_path;
++	int do_logfds = 0;
++
++	if (chroot_dir != NULL) {
++		xasprintf(&dev_log_path, "%s/dev/log", chroot_dir);
++
++		if (stat(dev_log_path, &dev_log_stat) != 0) {
++			debug("%s: /dev/log doesn't exist in %s chroot - will try to log via monitor using [postauth] suffix", __func__, chroot_dir);
++			do_logfds = 1;
++		}
++		free(dev_log_path);
++	}
++	monitor_openfds(mon, do_logfds);
+ }
+ 
+ #ifdef GSSAPI
+diff -up openssh-7.4p1/monitor.h.log-in-chroot openssh-7.4p1/monitor.h
+--- openssh-7.4p1/monitor.h.log-in-chroot	2016-12-23 15:14:33.330168088 +0100
++++ openssh-7.4p1/monitor.h	2016-12-23 15:16:28.372190424 +0100
+@@ -83,10 +83,11 @@ struct monitor {
+ 	int			 m_log_sendfd;
+ 	struct kex		**m_pkex;
+ 	pid_t			 m_pid;
++	char		*m_state;
+ };
+ 
+ struct monitor *monitor_init(void);
+-void monitor_reinit(struct monitor *);
++void monitor_reinit(struct monitor *, const char *);
+ 
+ struct Authctxt;
+ void monitor_child_preauth(struct ssh *, struct monitor *);
+diff -up openssh-7.4p1/session.c.log-in-chroot openssh-7.4p1/session.c
+--- openssh-7.4p1/session.c.log-in-chroot	2016-12-23 15:14:33.319168086 +0100
++++ openssh-7.4p1/session.c	2016-12-23 15:18:18.742211853 +0100
+@@ -160,6 +160,7 @@ login_cap_t *lc;
+ 
+ static int is_child = 0;
+ static int in_chroot = 0;
++static int have_dev_log = 1;
+ 
+ /* File containing userauth info, if ExposeAuthInfo set */
+ static char *auth_info_file = NULL;
+@@ -619,6 +620,7 @@ do_exec(Session *s, const char *command)
+ 	int ret;
+ 	const char *forced = NULL, *tty = NULL;
+ 	char session_type[1024];
++	struct stat dev_log_stat;
+ 
+ 	if (options.adm_forced_command) {
+ 		original_command = command;
+@@ -676,6 +678,10 @@ do_exec(Session *s, const char *command)
+ 			tty += 5;
+ 	}
+ 
++	if (lstat("/dev/log", &dev_log_stat) != 0) {
++		have_dev_log = 0;
++	}
++
+ 	verbose("Starting session: %s%s%s for %s from %.200s port %d id %d",
+ 	    session_type,
+ 	    tty == NULL ? "" : " on ",
+@@ -1486,14 +1492,6 @@ child_close_fds(void)
+ 
+ 	/* Stop directing logs to a high-numbered fd before we close it */
+ 	log_redirect_stderr_to(NULL);
+-
+-	/*
+-	 * Close any extra open file descriptors so that we don't have them
+-	 * hanging around in clients.  Note that we want to do this after
+-	 * initgroups, because at least on Solaris 2.3 it leaves file
+-	 * descriptors open.
+-	 */
+-	closefrom(STDERR_FILENO + 1);
+ }
+ 
+ /*
+@@ -1629,8 +1627,6 @@ do_child(Session *s, const char *command
+ 			exit(1);
+ 	}
+ 
+-	closefrom(STDERR_FILENO + 1);
+-
+ 	do_rc_files(ssh, s, shell);
+ 
+ 	/* restore SIGPIPE for child */
+@@ -1653,9 +1649,17 @@ do_child(Session *s, const char *command
+ 		argv[i] = NULL;
+ 		optind = optreset = 1;
+ 		__progname = argv[0];
+-		exit(sftp_server_main(i, argv, s->pw));
++		exit(sftp_server_main(i, argv, s->pw, have_dev_log));
+ 	}
+ 
++	/*
++	 * Close any extra open file descriptors so that we don't have them
++	 * hanging around in clients.  Note that we want to do this after
++	 * initgroups, because at least on Solaris 2.3 it leaves file
++	 * descriptors open.
++	 */
++	closefrom(STDERR_FILENO + 1);
++
+ 	fflush(NULL);
+ 
+ 	/* Get the last component of the shell name. */
+diff -up openssh-7.4p1/sftp.h.log-in-chroot openssh-7.4p1/sftp.h
+--- openssh-7.4p1/sftp.h.log-in-chroot	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/sftp.h	2016-12-23 15:14:33.331168088 +0100
+@@ -97,5 +97,5 @@
+ 
+ struct passwd;
+ 
+-int	sftp_server_main(int, char **, struct passwd *);
++int	sftp_server_main(int, char **, struct passwd *, int);
+ void	sftp_server_cleanup_exit(int) __attribute__((noreturn));
+diff -up openssh-7.4p1/sftp-server.c.log-in-chroot openssh-7.4p1/sftp-server.c
+--- openssh-7.4p1/sftp-server.c.log-in-chroot	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/sftp-server.c	2016-12-23 15:14:33.331168088 +0100
+@@ -1497,7 +1497,7 @@ sftp_server_usage(void)
+ }
+ 
+ int
+-sftp_server_main(int argc, char **argv, struct passwd *user_pw)
++sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handler)
+ {
+ 	fd_set *rset, *wset;
+ 	int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0;
+@@ -1511,7 +1511,7 @@ sftp_server_main(int argc, char **argv,
+ 	extern char *__progname;
+ 
+ 	__progname = ssh_get_progname(argv[0]);
+-	log_init(__progname, log_level, log_facility, log_stderr);
++	log_init_handler(__progname, log_level, log_facility, log_stderr, reset_handler);
+ 
+ 	pw = pwcopy(user_pw);
+ 
+@@ -1582,7 +1582,7 @@ sftp_server_main(int argc, char **argv,
+ 		}
+ 	}
+ 
+-	log_init(__progname, log_level, log_facility, log_stderr);
++	log_init_handler(__progname, log_level, log_facility, log_stderr, reset_handler);
+ 
+ 	/*
+ 	 * On platforms where we can, avoid making /proc/self/{mem,maps}
+diff -up openssh-7.4p1/sftp-server-main.c.log-in-chroot openssh-7.4p1/sftp-server-main.c
+--- openssh-7.4p1/sftp-server-main.c.log-in-chroot	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/sftp-server-main.c	2016-12-23 15:14:33.331168088 +0100
+@@ -49,5 +49,5 @@ main(int argc, char **argv)
+ 		return 1;
+ 	}
+ 
+-	return (sftp_server_main(argc, argv, user_pw));
++	return (sftp_server_main(argc, argv, user_pw, 0));
+ }
+diff -up openssh-7.4p1/sshd.c.log-in-chroot openssh-7.4p1/sshd.c
+--- openssh-7.4p1/sshd.c.log-in-chroot	2016-12-23 15:14:33.328168088 +0100
++++ openssh-7.4p1/sshd.c	2016-12-23 15:14:33.332168088 +0100
+@@ -650,7 +650,7 @@ privsep_postauth(Authctxt *authctxt)
+ 	}
+ 
+ 	/* New socket pair */
+-	monitor_reinit(pmonitor);
++	monitor_reinit(pmonitor, options.chroot_directory);
+ 
+ 	pmonitor->m_pid = fork();
+ 	if (pmonitor->m_pid == -1)
+@@ -668,6 +668,11 @@ privsep_postauth(Authctxt *authctxt)
+ 
+ 	close(pmonitor->m_sendfd);
+ 	pmonitor->m_sendfd = -1;
++	close(pmonitor->m_log_recvfd);
++	pmonitor->m_log_recvfd = -1;
++
++	if (pmonitor->m_log_sendfd != -1)
++		set_log_handler(mm_log_handler, pmonitor);
+ 
+ 	/* Demote the private keys to public keys. */
+ 	demote_sensitive_data();
diff --git a/openssh-6.6.1p1-scp-non-existing-directory.patch b/openssh-6.6.1p1-scp-non-existing-directory.patch
new file mode 100644
index 0000000..bb55c0b
--- /dev/null
+++ b/openssh-6.6.1p1-scp-non-existing-directory.patch
@@ -0,0 +1,14 @@
+--- a/scp.c	
++++ a/scp.c	
+@@ -1084,6 +1084,10 @@ sink(int argc, char **argv)
+ 			free(vect[0]);
+ 			continue;
+ 		}
++		if (buf[0] == 'C' && ! exists && np[strlen(np)-1] == '/') {
++			errno = ENOTDIR;
++			goto bad;
++		}
+ 		omode = mode;
+ 		mode |= S_IWUSR;
+ 		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) {
+-- 
diff --git a/openssh-6.6.1p1-selinux-contexts.patch b/openssh-6.6.1p1-selinux-contexts.patch
new file mode 100644
index 0000000..3a7193e
--- /dev/null
+++ b/openssh-6.6.1p1-selinux-contexts.patch
@@ -0,0 +1,132 @@
+diff --git a/openbsd-compat/port-linux-sshd.c b/openbsd-compat/port-linux-sshd.c
+index 8f32464..18a2ca4 100644
+--- a/openbsd-compat/port-linux-sshd.c
++++ b/openbsd-compat/port-linux-sshd.c
+@@ -32,6 +32,7 @@
+ #include "misc.h"      /* servconf.h needs misc.h for struct ForwardOptions */
+ #include "servconf.h"
+ #include "port-linux.h"
++#include "misc.h"
+ #include "sshkey.h"
+ #include "hostfile.h"
+ #include "auth.h"
+@@ -445,7 +446,7 @@ sshd_selinux_setup_exec_context(char *pwname)
+ void
+ sshd_selinux_copy_context(void)
+ {
+-	security_context_t *ctx;
++	char *ctx;
+ 
+ 	if (!sshd_selinux_enabled())
+ 		return;
+@@ -461,6 +462,72 @@ sshd_selinux_copy_context(void)
+ 	}
+ }
+ 
++void
++sshd_selinux_change_privsep_preauth_context(void)
++{
++	int len;
++	char line[1024], *preauth_context = NULL, *cp, *arg;
++	const char *contexts_path;
++	FILE *contexts_file;
++	struct stat sb;
++
++	contexts_path = selinux_openssh_contexts_path();
++	if (contexts_path == NULL) {
++		debug3("%s: Failed to get the path to SELinux context", __func__);
++		return;
++	}
++
++	if ((contexts_file = fopen(contexts_path, "r")) == NULL) {
++		debug("%s: Failed to open SELinux context file", __func__);
++		return;
++	}
++
++	if (fstat(fileno(contexts_file), &sb) != 0 ||
++	    sb.st_uid != 0 || (sb.st_mode & 022) != 0) {
++		logit("%s: SELinux context file needs to be owned by root"
++		    " and not writable by anyone else", __func__);
++		fclose(contexts_file);
++		return;
++	}
++
++	while (fgets(line, sizeof(line), contexts_file)) {
++		/* Strip trailing whitespace */
++		for (len = strlen(line) - 1; len > 0; len--) {
++			if (strchr(" \t\r\n", line[len]) == NULL)
++				break;
++			line[len] = '\0';
++		}
++
++		if (line[0] == '\0')
++			continue;
++
++		cp = line;
++		arg = strdelim(&cp);
++		if (arg && *arg == '\0')
++			arg = strdelim(&cp);
++
++		if (arg && strcmp(arg, "privsep_preauth") == 0) {
++			arg = strdelim(&cp);
++			if (!arg || *arg == '\0') {
++				debug("%s: privsep_preauth is empty", __func__);
++				fclose(contexts_file);
++				return;
++			}
++			preauth_context = xstrdup(arg);
++		}
++	}
++	fclose(contexts_file);
++
++	if (preauth_context == NULL) {
++		debug("%s: Unable to find 'privsep_preauth' option in"
++		    " SELinux context file", __func__);
++		return;
++	}
++
++	ssh_selinux_change_context(preauth_context);
++	free(preauth_context);
++}
++
+ #endif
+ #endif
+ 
+diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
+index 22ea8ef..1fc963d 100644
+--- a/openbsd-compat/port-linux.c
++++ b/openbsd-compat/port-linux.c
+@@ -179,7 +179,7 @@ ssh_selinux_change_context(const char *newname)
+ 	strlcpy(newctx + len, newname, newlen - len);
+ 	if ((cx = index(cx + 1, ':')))
+ 		strlcat(newctx, cx, newlen);
+-	debug3("%s: setting context from '%s' to '%s'", __func__,
++	debug("%s: setting context from '%s' to '%s'", __func__,
+ 	    oldctx, newctx);
+ 	if (setcon(newctx) < 0)
+ 		switchlog("%s: setcon %s from %s failed with %s", __func__,
+diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
+index cb51f99..8b7cda2 100644
+--- a/openbsd-compat/port-linux.h
++++ b/openbsd-compat/port-linux.h
+@@ -29,6 +29,7 @@ int sshd_selinux_enabled(void);
+ void sshd_selinux_copy_context(void);
+ void sshd_selinux_setup_exec_context(char *);
+ int sshd_selinux_setup_env_variables(void);
++void sshd_selinux_change_privsep_preauth_context(void);
+ #endif
+ 
+ #ifdef LINUX_OOM_ADJUST
+diff --git a/sshd.c b/sshd.c
+index 2871fe9..39b9c08 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -629,7 +629,7 @@ privsep_preauth_child(void)
+ 	demote_sensitive_data();
+ 
+ #ifdef WITH_SELINUX
+-	ssh_selinux_change_context("sshd_net_t");
++	sshd_selinux_change_privsep_preauth_context();
+ #endif
+ 
+ 	/* Demote the child */
diff --git a/openssh-6.6p1-GSSAPIEnablek5users.patch b/openssh-6.6p1-GSSAPIEnablek5users.patch
new file mode 100644
index 0000000..01ea156
--- /dev/null
+++ b/openssh-6.6p1-GSSAPIEnablek5users.patch
@@ -0,0 +1,131 @@
+diff -up openssh-7.4p1/gss-serv-krb5.c.GSSAPIEnablek5users openssh-7.4p1/gss-serv-krb5.c
+--- openssh-7.4p1/gss-serv-krb5.c.GSSAPIEnablek5users	2016-12-23 15:18:40.615216100 +0100
++++ openssh-7.4p1/gss-serv-krb5.c	2016-12-23 15:18:40.628216102 +0100
+@@ -279,7 +279,6 @@ ssh_gssapi_krb5_cmdok(krb5_principal pri
+ 	FILE *fp;
+ 	char file[MAXPATHLEN];
+ 	char *line = NULL;
+-	char kuser[65]; /* match krb5_kuserok() */
+ 	struct stat st;
+ 	struct passwd *pw = the_authctxt->pw;
+ 	int found_principal = 0;
+@@ -288,7 +287,7 @@ ssh_gssapi_krb5_cmdok(krb5_principal pri
+ 
+ 	snprintf(file, sizeof(file), "%s/.k5users", pw->pw_dir);
+ 	/* If both .k5login and .k5users DNE, self-login is ok. */
+-	if (!k5login_exists && (access(file, F_OK) == -1)) {
++	if ( !options.enable_k5users || (!k5login_exists && (access(file, F_OK) == -1))) {
+                 return ssh_krb5_kuserok(krb_context, principal, luser,
+                                         k5login_exists);
+ 	}
+diff -up openssh-7.4p1/servconf.c.GSSAPIEnablek5users openssh-7.4p1/servconf.c
+--- openssh-7.4p1/servconf.c.GSSAPIEnablek5users	2016-12-23 15:18:40.615216100 +0100
++++ openssh-7.4p1/servconf.c	2016-12-23 15:35:36.354401156 +0100
+@@ -168,6 +168,7 @@ initialize_server_options(ServerOptions
+ 	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->challenge_response_authentication = -1;
+@@ -345,6 +346,8 @@ fill_default_server_options(ServerOption
+ #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)
+@@ -418,7 +421,7 @@ typedef enum {
+ 	sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedKeyTypes,
+ 	sHostKeyAlgorithms,
+ 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
+-	sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
++	sGssAuthentication, sGssCleanupCreds, sGssEnablek5users, sGssStrictAcceptor,
+ 	sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
+ 	sAcceptEnv, sSetEnv, sPermitTunnel,
+ 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
+@@ -497,14 +500,16 @@ 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 },
+ 	{ "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
+ 	{ "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
+ 	{ "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 },
+@@ -1653,6 +1658,10 @@ process_server_config_line(ServerOptions
+ 		intptr = &options->use_kuserok;
+ 		goto parse_flag;
+ 
++	case sGssEnablek5users:
++		intptr = &options->enable_k5users;
++		goto parse_flag;
++
+ 	case sPermitListen:
+ 	case sPermitOpen:
+ 		if (opcode == sPermitListen) {
+@@ -2026,6 +2035,7 @@ copy_set_server_options(ServerOptions *d
+ 	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);
+@@ -2320,6 +2330,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);
+ #endif
+ #ifdef GSSAPI
+ 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+diff -up openssh-7.4p1/servconf.h.GSSAPIEnablek5users openssh-7.4p1/servconf.h
+--- openssh-7.4p1/servconf.h.GSSAPIEnablek5users	2016-12-23 15:18:40.616216100 +0100
++++ openssh-7.4p1/servconf.h	2016-12-23 15:18:40.629216102 +0100
+@@ -174,6 +174,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 */
+diff -up openssh-7.4p1/sshd_config.5.GSSAPIEnablek5users openssh-7.4p1/sshd_config.5
+--- openssh-7.4p1/sshd_config.5.GSSAPIEnablek5users	2016-12-23 15:18:40.630216103 +0100
++++ openssh-7.4p1/sshd_config.5	2016-12-23 15:36:21.607408435 +0100
+@@ -628,6 +628,12 @@ Specifies whether to automatically destr
+ on logout.
+ The default is
+ .Cm yes .
++.It Cm GSSAPIEnablek5users
++Specifies whether to look at .k5users file for GSSAPI authentication
++access control. Further details are described in
++.Xr ksu 1 .
++The default is
++.Cm no .
+ .It Cm GSSAPIKeyExchange
+ Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange
+ doesn't rely on ssh keys to verify host identity.
+diff -up openssh-7.4p1/sshd_config.GSSAPIEnablek5users openssh-7.4p1/sshd_config
+--- openssh-7.4p1/sshd_config.GSSAPIEnablek5users	2016-12-23 15:18:40.616216100 +0100
++++ openssh-7.4p1/sshd_config	2016-12-23 15:18:40.631216103 +0100
+@@ -80,6 +80,7 @@ GSSAPIAuthentication yes
+ #GSSAPICleanupCredentials yes
+ #GSSAPIStrictAcceptorCheck yes
+ #GSSAPIKeyExchange no
++#GSSAPIEnablek5users no
+ 
+ # Set this to 'yes' to enable PAM authentication, account processing,
+ # and session processing. If this is enabled, PAM authentication will
diff --git a/openssh-6.6p1-allow-ip-opts.patch b/openssh-6.6p1-allow-ip-opts.patch
new file mode 100644
index 0000000..953d613
--- /dev/null
+++ b/openssh-6.6p1-allow-ip-opts.patch
@@ -0,0 +1,39 @@
+diff -up openssh/sshd.c.ip-opts openssh/sshd.c
+--- openssh/sshd.c.ip-opts	2016-07-25 13:58:48.998507834 +0200
++++ openssh/sshd.c	2016-07-25 14:01:28.346469878 +0200
+@@ -1507,12 +1507,29 @@ 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:
++					i += opts[i + 1];
++					break;
++				default:
++				/* Fail, fatally, if we detect either loose or strict
++			 	 * 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);
+ 	}
+ 	return;
+ #endif /* IP_OPTIONS */
diff --git a/openssh-6.6p1-ctr-cavstest.patch b/openssh-6.6p1-ctr-cavstest.patch
new file mode 100644
index 0000000..81da034
--- /dev/null
+++ b/openssh-6.6p1-ctr-cavstest.patch
@@ -0,0 +1,257 @@
+diff -up openssh-6.8p1/Makefile.in.ctr-cavs openssh-6.8p1/Makefile.in
+--- openssh-6.8p1/Makefile.in.ctr-cavs	2015-03-18 11:22:05.493289018 +0100
++++ openssh-6.8p1/Makefile.in	2015-03-18 11:22:44.504196316 +0100
+@@ -28,6 +28,7 @@ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
+ SFTP_SERVER=$(libexecdir)/sftp-server
+ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
+ SSH_KEYCAT=$(libexecdir)/ssh-keycat
++CTR_CAVSTEST=$(libexecdir)/ctr-cavstest
+ SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+ SSH_SK_HELPER=$(libexecdir)/ssh-sk-helper
+ PRIVSEP_PATH=@PRIVSEP_PATH@
+@@ -66,7 +67,7 @@ EXEEXT=@EXEEXT@
+ 
+ .SUFFIXES: .lo
+ 
+-TARGETS=ssh$(EXEEXT) sshd$(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)
++TARGETS=ssh$(EXEEXT) sshd$(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) ctr-cavstest$(EXEEXT)
+ 
+ XMSS_OBJS=\
+ 	ssh-xmss.o \
+@@ -194,6 +195,9 @@ ssh-ldap-helper$(EXEEXT): $(LIBCOMPAT) l
+ ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
+ 	$(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(LIBS)
+ 
++ctr-cavstest$(EXEEXT): $(LIBCOMPAT) libssh.a ctr-cavstest.o
++	$(LD) -o $@ ctr-cavstest.o $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
++
+ ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
+ 	$(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
+ 
+@@ -326,6 +330,7 @@ install-files:
+ 	$(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) ctr-cavstest$(EXEEXT) $(DESTDIR)$(libexecdir)/ctr-cavstest$(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 -up openssh-6.8p1/ctr-cavstest.c.ctr-cavs openssh-6.8p1/ctr-cavstest.c
+--- openssh-6.8p1/ctr-cavstest.c.ctr-cavs	2015-03-18 11:22:05.521288952 +0100
++++ openssh-6.8p1/ctr-cavstest.c	2015-03-18 11:22:05.521288952 +0100
+@@ -0,0 +1,215 @@
++/*
++ *
++ * invocation (all of the following are equal):
++ * ./ctr-cavstest --algo aes128-ctr --key 987212980144b6a632e864031f52dacc --mode encrypt --data a6deca405eef2e8e4609abf3c3ccf4a6
++ * ./ctr-cavstest --algo aes128-ctr --key 987212980144b6a632e864031f52dacc --mode encrypt --data a6deca405eef2e8e4609abf3c3ccf4a6 --iv 00000000000000000000000000000000
++ * echo -n a6deca405eef2e8e4609abf3c3ccf4a6 | ./ctr-cavstest --algo aes128-ctr --key 987212980144b6a632e864031f52dacc --mode encrypt
++ */
++
++#include "includes.h"
++
++#include <sys/types.h>
++#include <sys/param.h>
++#include <stdarg.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <ctype.h>
++
++#include "xmalloc.h"
++#include "log.h"
++#include "ssherr.h"
++#include "cipher.h"
++
++/* compatibility with old or broken OpenSSL versions */
++#include "openbsd-compat/openssl-compat.h"
++
++void usage(void) {
++        fprintf(stderr, "Usage: ctr-cavstest --algo <ssh-crypto-algorithm>\n"
++                        "                    --key <hexadecimal-key> --mode <encrypt|decrypt>\n"
++                        "                    [--iv <hexadecimal-iv>] --data <hexadecimal-data>\n\n"
++                        "Hexadecimal output is printed to stdout.\n"
++                        "Hexadecimal input data can be alternatively read from stdin.\n");
++        exit(1);
++}
++
++void *fromhex(char *hex, size_t *len)
++{
++        unsigned char *bin;
++        char *p;
++        size_t n = 0;
++        int shift = 4;
++        unsigned char out = 0;
++        unsigned char *optr;
++
++        bin = xmalloc(strlen(hex)/2);
++        optr = bin;
++
++        for (p = hex; *p != '\0'; ++p) {
++                unsigned char c;
++
++                c = *p;
++                if (isspace(c))
++                        continue;
++
++                if (c >= '0' && c <= '9') {
++                        c = c - '0';
++                } else if (c >= 'A' && c <= 'F') {
++                        c = c - 'A' + 10;
++                } else if (c >= 'a' && c <= 'f') {
++                        c = c - 'a' + 10;
++                } else {
++                        /* truncate on nonhex cipher */
++                        break;
++                }
++
++                out |= c << shift;
++                shift = (shift + 4) % 8;
++
++                if (shift) {
++                        *(optr++) = out;
++                        out = 0;
++                        ++n;
++                }
++        }
++
++        *len = n;
++        return bin;
++}
++
++#define READ_CHUNK 4096
++#define MAX_READ_SIZE 1024*1024*100
++char *read_stdin(void)
++{
++        char *buf;
++        size_t n, total = 0;
++
++        buf = xmalloc(READ_CHUNK);
++
++        do {
++                n = fread(buf + total, 1, READ_CHUNK, stdin);
++                if (n < READ_CHUNK) /* terminate on short read */
++                        break;
++
++                total += n;
++                buf = xreallocarray(buf, total + READ_CHUNK, 1);
++        } while(total < MAX_READ_SIZE);
++        return buf;
++}
++
++int main (int argc, char *argv[])
++{
++
++        const struct sshcipher *c;
++        struct sshcipher_ctx *cc;
++        char *algo = "aes128-ctr";
++        char *hexkey = NULL;
++        char *hexiv = "00000000000000000000000000000000";
++        char *hexdata = NULL;
++        char *p;
++        int i, r;
++        int encrypt = 1;
++        void *key;
++        size_t keylen;
++        void *iv;
++        size_t ivlen;
++        void *data;
++        size_t datalen;
++        void *outdata;
++
++        for (i = 1; i < argc; ++i) {
++                if (strcmp(argv[i], "--algo") == 0) {
++                        algo = argv[++i];
++                } else if (strcmp(argv[i], "--key") == 0) {
++                        hexkey = argv[++i];
++                } else if (strcmp(argv[i], "--mode") == 0) {
++                        ++i;
++                        if (argv[i] == NULL) {
++                                usage();
++                        }
++                        if (strncmp(argv[i], "enc", 3) == 0) {
++                                encrypt = 1;
++                        } else if (strncmp(argv[i], "dec", 3) == 0) {
++                                encrypt = 0;
++                        } else {
++                                usage();
++                        }
++                } else if (strcmp(argv[i], "--iv") == 0) {
++                        hexiv = argv[++i];
++                } else if (strcmp(argv[i], "--data") == 0) {
++                        hexdata = argv[++i];
++                }
++        }
++
++        if (hexkey == NULL || algo == NULL) {
++                usage();
++        }
++
++	OpenSSL_add_all_algorithms();
++
++	c = cipher_by_name(algo);
++	if (c == NULL) {
++		fprintf(stderr, "Error: unknown algorithm\n");
++		return 2;
++	}
++
++        if (hexdata == NULL) {
++                hexdata = read_stdin();
++        } else {
++                hexdata = xstrdup(hexdata);
++        }
++
++        key = fromhex(hexkey, &keylen);
++
++	if (keylen != 16 && keylen != 24 && keylen == 32) {
++		fprintf(stderr, "Error: unsupported key length\n");
++		return 2;
++	}
++
++        iv = fromhex(hexiv, &ivlen);
++
++        if (ivlen != 16) {
++		fprintf(stderr, "Error: unsupported iv length\n");
++		return 2;
++        }
++
++        data = fromhex(hexdata, &datalen);
++
++	if (data == NULL || datalen == 0) {
++		fprintf(stderr, "Error: no data to encrypt/decrypt\n");
++		return 2;
++	}
++
++	if ((r = cipher_init(&cc, c, key, keylen, iv, ivlen, encrypt)) != 0) {
++		fprintf(stderr, "Error: cipher_init failed: %s\n", ssh_err(r));
++		return 2;
++	}
++
++	free(key);
++	free(iv);
++
++	outdata = malloc(datalen);
++	if(outdata == NULL) {
++		fprintf(stderr, "Error: memory allocation failure\n");
++		return 2;
++	}
++
++	if ((r = cipher_crypt(cc, 0, outdata, data, datalen, 0, 0)) != 0) {
++		fprintf(stderr, "Error: cipher_crypt failed: %s\n", ssh_err(r));
++		return 2;
++	}
++
++	free(data);
++
++	cipher_free(cc);
++
++        for (p = outdata; datalen > 0; ++p, --datalen) {
++		printf("%02X", (unsigned char)*p);
++	}
++
++        free(outdata);
++
++        printf("\n");
++        return 0;
++}
++
diff --git a/openssh-6.6p1-force_krb.patch b/openssh-6.6p1-force_krb.patch
new file mode 100644
index 0000000..90f8322
--- /dev/null
+++ b/openssh-6.6p1-force_krb.patch
@@ -0,0 +1,280 @@
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index 413b845..54dd383 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -32,7 +32,9 @@
+ #include <sys/types.h>
+ 
+ #include <stdarg.h>
++#include <stdio.h>
+ #include <string.h>
++#include <unistd.h>
+ 
+ #include "xmalloc.h"
+ #include "sshkey.h"
+@@ -45,6 +47,7 @@
+ 
+ #include "ssh-gss.h"
+ 
++extern Authctxt *the_authctxt;
+ extern ServerOptions options;
+ 
+ #ifdef HEIMDAL
+@@ -56,6 +59,13 @@ extern ServerOptions options;
+ # include <gssapi/gssapi_krb5.h>
+ #endif
+ 
++/* all commands are allowed by default */
++char **k5users_allowed_cmds = NULL;
++
++static int ssh_gssapi_k5login_exists();
++static int ssh_gssapi_krb5_cmdok(krb5_principal, const char *, const char *,
++    int);
++
+ static krb5_context krb_context = NULL;
+ 
+ /* Initialise the krb5 library, for the stuff that GSSAPI won't do */
+@@ -88,6 +98,7 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ 	krb5_principal princ;
+ 	int retval;
+ 	const char *errmsg;
++	int k5login_exists;
+ 
+ 	if (ssh_gssapi_krb5_init() == 0)
+ 		return 0;
+@@ -99,10 +110,22 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ 		krb5_free_error_message(krb_context, errmsg);
+ 		return 0;
+ 	}
+-	if (krb5_kuserok(krb_context, princ, name)) {
++	/* krb5_kuserok() returns 1 if .k5login DNE and this is self-login.
++	 * We have to make sure to check .k5users in that case. */
++	k5login_exists = ssh_gssapi_k5login_exists();
++	/* NOTE: .k5login and .k5users must opened as root, not the user,
++	 * because if they are on a krb5-protected filesystem, user credentials
++	 * to access these files aren't available yet. */
++	if (krb5_kuserok(krb_context, princ, name) && k5login_exists) {
+ 		retval = 1;
+ 		logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
+ 		    name, (char *)client->displayname.value);
++	} 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
+ 		retval = 0;
+ 
+@@ -110,6 +133,137 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ 	return retval;
+ }
+ 
++/* Test for existence of .k5login.
++ * We need this as part of our .k5users check, because krb5_kuserok()
++ * returns success if .k5login DNE and user is logging in as himself.
++ * With .k5login absent and .k5users present, we don't want absence
++ * of .k5login to authorize self-login.  (absence of both is required)
++ * Returns 1 if .k5login is available, 0 otherwise.
++ */
++static int
++ssh_gssapi_k5login_exists()
++{
++	char file[MAXPATHLEN];
++	struct passwd *pw = the_authctxt->pw;
++
++	snprintf(file, sizeof(file), "%s/.k5login", pw->pw_dir);
++	return access(file, F_OK) == 0;
++}
++
++/* check .k5users for login or command authorization
++ * Returns 1 if principal is authorized, 0 otherwise.
++ * If principal is authorized, (global) k5users_allowed_cmds may be populated.
++ */
++static int
++ssh_gssapi_krb5_cmdok(krb5_principal principal, const char *name,
++    const char *luser, int k5login_exists)
++{
++	FILE *fp;
++	char file[MAXPATHLEN];
++	char *line = NULL;
++	char kuser[65]; /* match krb5_kuserok() */
++	struct stat st;
++	struct passwd *pw = the_authctxt->pw;
++	int found_principal = 0;
++	int ncommands = 0, allcommands = 0;
++	u_long linenum = 0;
++	size_t linesize = 0;
++
++	snprintf(file, sizeof(file), "%s/.k5users", pw->pw_dir);
++	/* If both .k5login and .k5users DNE, self-login is ok. */
++	if (!k5login_exists && (access(file, F_OK) == -1)) {
++		return (krb5_aname_to_localname(krb_context, principal,
++		    sizeof(kuser), kuser) == 0) &&
++		    (strcmp(kuser, luser) == 0);
++	}
++	if ((fp = fopen(file, "r")) == NULL) {
++		int saved_errno = errno;
++		/* 2nd access check to ease debugging if file perms are wrong.
++		 * But we don't want to report this if .k5users simply DNE. */
++		if (access(file, F_OK) == 0) {
++			logit("User %s fopen %s failed: %s",
++			    pw->pw_name, file, strerror(saved_errno));
++		}
++		return 0;
++	}
++	/* .k5users must be owned either by the user or by root */
++	if (fstat(fileno(fp), &st) == -1) {
++		/* can happen, but very wierd error so report it */
++		logit("User %s fstat %s failed: %s",
++		    pw->pw_name, file, strerror(errno));
++		fclose(fp);
++		return 0;
++	}
++	if (!(st.st_uid == pw->pw_uid || st.st_uid == 0)) {
++		logit("User %s %s is not owned by root or user",
++		    pw->pw_name, file);
++		fclose(fp);
++		return 0;
++	}
++	/* .k5users must be a regular file.  krb5_kuserok() doesn't do this
++	  * check, but we don't want to be deficient if they add a check. */
++	if (!S_ISREG(st.st_mode)) {
++		logit("User %s %s is not a regular file", pw->pw_name, file);
++		fclose(fp);
++		return 0;
++	}
++	/* file exists; initialize k5users_allowed_cmds (to none!) */
++	k5users_allowed_cmds = xcalloc(++ncommands,
++	    sizeof(*k5users_allowed_cmds));
++
++	/* Check each line.  ksu allows unlimited length lines. */
++	while (!allcommands && getline(&line, &linesize, fp) != -1) {
++		linenum++;
++		char *token;
++
++		/* we parse just like ksu, even though we could do better */
++		if ((token = strtok(line, " \t\n")) == NULL)
++			continue;
++		if (strcmp(name, token) == 0) {
++			/* we matched on client principal */
++			found_principal = 1;
++			if ((token = strtok(NULL, " \t\n")) == NULL) {
++				/* only shell is allowed */
++				k5users_allowed_cmds[ncommands-1] =
++				    xstrdup(pw->pw_shell);
++				k5users_allowed_cmds =
++				    xreallocarray(k5users_allowed_cmds, ++ncommands,
++					sizeof(*k5users_allowed_cmds));
++				break;
++			}
++			/* process the allowed commands */
++			while (token) {
++				if (strcmp(token, "*") == 0) {
++					allcommands = 1;
++					break;
++				}
++				k5users_allowed_cmds[ncommands-1] =
++				    xstrdup(token);
++				k5users_allowed_cmds =
++				    xreallocarray(k5users_allowed_cmds, ++ncommands,
++					sizeof(*k5users_allowed_cmds));
++				token = strtok(NULL, " \t\n");
++			}
++		}
++       }
++	free(line);
++	if (k5users_allowed_cmds) {
++		/* terminate vector */
++		k5users_allowed_cmds[ncommands-1] = NULL;
++		/* if all commands are allowed, free vector */
++		if (allcommands) {
++			int i;
++			for (i = 0; i < ncommands; i++) {
++				free(k5users_allowed_cmds[i]);
++			}
++			free(k5users_allowed_cmds);
++			k5users_allowed_cmds = NULL;
++		}
++	}
++	fclose(fp);
++	return found_principal;
++}
++ 
+ 
+ /* 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 28659ec..9c94d8e 100644
+--- a/session.c
++++ b/session.c
+@@ -789,6 +789,29 @@ do_exec(Session *s, const char *command)
+ 		command = auth_opts->force_command;
+ 		forced = "(key-option)";
+ 	}
++#ifdef GSSAPI
++#ifdef KRB5 /* k5users_allowed_cmds only available w/ GSSAPI+KRB5 */
++	else if (k5users_allowed_cmds) {
++		const char *match = command;
++		int allowed = 0, i = 0;
++
++		if (!match)
++			match = s->pw->pw_shell;
++		while (k5users_allowed_cmds[i]) {
++			if (strcmp(match, k5users_allowed_cmds[i++]) == 0) {
++				debug("Allowed command '%.900s'", match);
++				allowed = 1;
++				break;
++			}
++		}
++		if (!allowed) {
++			debug("command '%.900s' not allowed", match);
++			return 1;
++		}
++	}
++#endif
++#endif
++
+ 	s->forced = 0;
+ 	if (forced != NULL) {
+ 		s->forced = 1;
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 0374c88..509109a 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -49,6 +49,10 @@
+ #  endif /* !HAVE_DECL_GSS_C_NT_... */
+ 
+ # endif /* !HEIMDAL */
++
++/* .k5users support */
++extern char **k5users_allowed_cmds;
++
+ #endif /* KRB5 */
+ 
+ /* draft-ietf-secsh-gsskeyex-06 */
+diff --git a/sshd.8 b/sshd.8
+index adcaaf9..824163b 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -324,6 +324,7 @@ Finally, the server and the client enter an authentication dialog.
+ The client tries to authenticate itself using
+ host-based authentication,
+ public key authentication,
++GSSAPI authentication,
+ challenge-response authentication,
+ or password authentication.
+ .Pp
+@@ -800,6 +801,12 @@ This file is used in exactly the same way as
+ but allows host-based authentication without permitting login with
+ rlogin/rsh.
+ .Pp
++.It Pa ~/.k5login
++.It Pa ~/.k5users
++These files enforce GSSAPI/Kerberos authentication access control.
++Further details are described in
++.Xr ksu 1 .
++.Pp
+ .It Pa ~/.ssh/
+ This directory is the default location for all user-specific configuration
+ and authentication information.
diff --git a/openssh-6.6p1-keycat.patch b/openssh-6.6p1-keycat.patch
new file mode 100644
index 0000000..9e71efe
--- /dev/null
+++ b/openssh-6.6p1-keycat.patch
@@ -0,0 +1,485 @@
+diff -up openssh/auth.c.keycat openssh/misc.c
+--- openssh/auth.c.keycat	2015-06-24 10:57:50.158849606 +0200
++++ openssh/auth.c	2015-06-24 11:04:23.989868638 +0200
+@@ -966,6 +966,14 @@ subprocess(const char *tag, struct passw
+ 			_exit(1);
+ 		}
+ 
++#ifdef WITH_SELINUX
++		if (sshd_selinux_setup_env_variables() < 0) {
++			error ("failed to copy environment:  %s",
++			    strerror(errno));
++			_exit(127);
++		}
++#endif
++
+ 		execve(av[0], av, child_env);
+ 		error("%s exec \"%s\": %s", tag, command, strerror(errno));
+ 		_exit(127);
+diff -up openssh/HOWTO.ssh-keycat.keycat openssh/HOWTO.ssh-keycat
+--- openssh/HOWTO.ssh-keycat.keycat	2015-06-24 10:57:50.157849608 +0200
++++ openssh/HOWTO.ssh-keycat	2015-06-24 10:57:50.157849608 +0200
+@@ -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 -up openssh/Makefile.in.keycat openssh/Makefile.in
+--- openssh/Makefile.in.keycat	2015-06-24 10:57:50.152849621 +0200
++++ openssh/Makefile.in	2015-06-24 10:57:50.157849608 +0200
+@@ -27,6 +27,7 @@ SFTP_SERVER=$(libexecdir)/sftp-server
+ ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
+ SFTP_SERVER=$(libexecdir)/sftp-server
+ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
++SSH_KEYCAT=$(libexecdir)/ssh-keycat
+ SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+ SSH_SK_HELPER=$(libexecdir)/ssh-sk-helper
+ PRIVSEP_PATH=@PRIVSEP_PATH@
+@@ -52,6 +52,7 @@ K5LIBS=@K5LIBS@
+ K5LIBS=@K5LIBS@
+ GSSLIBS=@GSSLIBS@
+ SSHDLIBS=@SSHDLIBS@
++KEYCATLIBS=@KEYCATLIBS@
+ LIBEDIT=@LIBEDIT@
+ LIBFIDO2=@LIBFIDO2@
+ AR=@AR@
+@@ -65,7 +66,7 @@ EXEEXT=@EXEEXT@
+ 
+ .SUFFIXES: .lo
+ 
+-TARGETS=ssh$(EXEEXT) sshd$(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)
++TARGETS=ssh$(EXEEXT) sshd$(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)
+ 
+ XMSS_OBJS=\
+ 	ssh-xmss.o \
+@@ -190,6 +191,9 @@ ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT)
+ ssh-sk-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(SKHELPER_OBJS)
+ 	$(LD) -o $@ $(SKHELPER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) $(LIBFIDO2)
+ 
++ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
++	$(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(LIBS)
++
+ ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
+ 	$(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
+ 
+@@ -321,6 +325,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 -up openssh/openbsd-compat/port-linux.h.keycat openssh/openbsd-compat/port-linux.h
+--- openssh/openbsd-compat/port-linux.h.keycat	2015-06-24 10:57:50.150849626 +0200
++++ openssh/openbsd-compat/port-linux.h	2015-06-24 10:57:50.160849601 +0200
+@@ -25,8 +25,10 @@ void ssh_selinux_setup_pty(char *, const
+ void ssh_selinux_change_context(const char *);
+ void ssh_selinux_setfscreatecon(const char *);
+ 
++int sshd_selinux_enabled(void);
+ void sshd_selinux_copy_context(void);
+ void sshd_selinux_setup_exec_context(char *);
++int sshd_selinux_setup_env_variables(void);
+ #endif
+ 
+ #ifdef LINUX_OOM_ADJUST
+diff -up openssh/openbsd-compat/port-linux-sshd.c.keycat openssh/openbsd-compat/port-linux-sshd.c
+--- openssh/openbsd-compat/port-linux-sshd.c.keycat	2015-06-24 10:57:50.150849626 +0200
++++ openssh/openbsd-compat/port-linux-sshd.c	2015-06-24 10:57:50.159849603 +0200
+@@ -54,6 +54,20 @@ extern Authctxt *the_authctxt;
+ extern int inetd_flag;
+ extern int rexeced_flag;
+ 
++/* 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,
+@@ -308,7 +322,7 @@ sshd_selinux_getctxbyname(char *pwname,
+ 
+ /* Setup environment variables for pam_selinux */
+ static int
+-sshd_selinux_setup_pam_variables(void)
++sshd_selinux_setup_variables(int(*set_it)(char *, const char *))
+ {
+ 	const char *reqlvl;
+ 	char *role;
+@@ -319,16 +333,16 @@ sshd_selinux_setup_pam_variables(void)
+ 
+ 	ssh_selinux_get_role_level(&role, &reqlvl);
+ 
+-	rv = do_pam_putenv("SELINUX_ROLE_REQUESTED", role ? role : "");
++	rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
+ 
+ 	if (inetd_flag && !rexeced_flag) {
+ 		use_current = "1";
+ 	} else {
+ 		use_current = "";
+-		rv = rv || do_pam_putenv("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
++		rv = rv || set_it("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
+ 	}
+ 
+-	rv = rv || do_pam_putenv("SELINUX_USE_CURRENT_RANGE", use_current);
++	rv = rv || set_it("SELINUX_USE_CURRENT_RANGE", use_current);
+ 
+ 	if (role != NULL)
+ 		free(role);
+@@ -336,6 +350,24 @@ sshd_selinux_setup_pam_variables(void)
+ 	return rv;
+ }
+ 
++static int
++sshd_selinux_setup_pam_variables(void)
++{
++	return sshd_selinux_setup_variables(do_pam_putenv);
++}
++
++static int
++do_setenv(char *name, const char *value)
++{
++	return setenv(name, value, 1);
++}
++
++int
++sshd_selinux_setup_env_variables(void)
++{
++	return sshd_selinux_setup_variables(do_setenv);
++}
++
+ /* Set the execution context to the default for the specified user */
+ void
+ sshd_selinux_setup_exec_context(char *pwname)
+@@ -344,7 +376,7 @@ sshd_selinux_setup_exec_context(char *pw
+ 	int r = 0;
+ 	security_context_t default_ctx = NULL;
+ 
+-	if (!ssh_selinux_enabled())
++	if (!sshd_selinux_enabled())
+ 		return;
+ 
+ 	if (options.use_pam) {
+@@ -415,7 +447,7 @@ sshd_selinux_copy_context(void)
+ {
+ 	security_context_t *ctx;
+ 
+-	if (!ssh_selinux_enabled())
++	if (!sshd_selinux_enabled())
+ 		return;
+ 
+ 	if (getexeccon((security_context_t *)&ctx) != 0) {
+diff -up openssh/platform.c.keycat openssh/platform.c
+--- openssh/platform.c.keycat	2015-06-24 10:57:50.147849633 +0200
++++ openssh/platform.c	2015-06-24 10:57:50.160849601 +0200
+@@ -103,7 +103,7 @@ platform_setusercontext(struct passwd *p
+ {
+ #ifdef WITH_SELINUX
+ 	/* Cache selinux status for later use */
+-	(void)ssh_selinux_enabled();
++	(void)sshd_selinux_enabled();
+ #endif
+ 
+ #ifdef USE_SOLARIS_PROJECTS
+diff -up openssh/ssh-keycat.c.keycat openssh/ssh-keycat.c
+--- openssh/ssh-keycat.c.keycat	2015-06-24 10:57:50.161849599 +0200
++++ openssh/ssh-keycat.c	2015-06-24 10:57:50.161849599 +0200
+@@ -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;
++}
+diff --git a/configure.ac b/configure.ac
+index 3bbccfd..6481f1f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2952,6 +2952,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])
+ 
+@@ -3105,6 +3106,7 @@
+ 					;;
+ 				*)
+ 					SSHDLIBS="$SSHDLIBS -ldl"
++					KEYCATLIBS="$KEYCATLIBS -ldl"
+ 					;;
+ 				esac
+ 			fi
+@@ -4042,6 +4044,7 @@ AC_ARG_WITH([selinux],
+ 	fi ]
+ )
+ AC_SUBST([SSHDLIBS])
++AC_SUBST([KEYCATLIBS])
+ 
+ # Check whether user wants Kerberos 5 support
+ KRB5_MSG="no"
+@@ -5031,6 +5034,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/openssh-6.6p1-keyperm.patch b/openssh-6.6p1-keyperm.patch
new file mode 100644
index 0000000..5e06940
--- /dev/null
+++ b/openssh-6.6p1-keyperm.patch
@@ -0,0 +1,31 @@
+diff -up openssh-8.2p1/authfile.c.keyperm openssh-8.2p1/authfile.c
+--- openssh-8.2p1/authfile.c.keyperm	2020-02-14 01:40:54.000000000 +0100
++++ openssh-8.2p1/authfile.c	2020-02-17 11:55:12.841729758 +0100
+@@ -31,6 +31,7 @@
+ 
+ #include <errno.h>
+ #include <fcntl.h>
++#include <grp.h>
+ #include <stdio.h>
+ #include <stdarg.h>
+ #include <stdlib.h>
+@@ -101,7 +102,19 @@ sshkey_perm_ok(int fd, const char *filen
+ #ifdef HAVE_CYGWIN
+ 	if (check_ntsec(filename))
+ #endif
++
+ 	if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) {
++		if (st.st_mode & 040) {
++			struct group *gr;
++
++			if ((gr = getgrnam("ssh_keys")) && (st.st_gid == gr->gr_gid)) {
++				/* The only additional bit is read
++				 * for ssh_keys group, which is fine */
++				if ((st.st_mode & 077) == 040 ) {
++					return 0;
++				}
++			}
++		}
+ 		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ 		error("@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @");
+ 		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
diff --git a/openssh-6.6p1-kuserok.patch b/openssh-6.6p1-kuserok.patch
new file mode 100644
index 0000000..5009e2a
--- /dev/null
+++ b/openssh-6.6p1-kuserok.patch
@@ -0,0 +1,289 @@
+diff -up openssh-7.4p1/auth-krb5.c.kuserok openssh-7.4p1/auth-krb5.c
+--- openssh-7.4p1/auth-krb5.c.kuserok	2016-12-23 14:36:07.640465939 +0100
++++ openssh-7.4p1/auth-krb5.c	2016-12-23 14:36:07.644465936 +0100
+@@ -56,6 +56,21 @@
+ 
+ extern ServerOptions	 options;
+ 
++int
++ssh_krb5_kuserok(krb5_context krb5_ctx, krb5_principal krb5_user, const char *client,
++                 int k5login_exists)
++{
++	if (options.use_kuserok || !k5login_exists)
++		return krb5_kuserok(krb5_ctx, krb5_user, client);
++	else {
++		char kuser[65];
++
++		if (krb5_aname_to_localname(krb5_ctx, krb5_user, sizeof(kuser), kuser))
++			return 0;
++		return strcmp(kuser, client) == 0;
++	}
++}
++
+ static int
+ krb5_init(void *context)
+ {
+@@ -160,8 +175,9 @@ auth_krb5_password(Authctxt *authctxt, c
+ 	if (problem)
+ 		goto out;
+ 
+-	if (!krb5_kuserok(authctxt->krb5_ctx, authctxt->krb5_user,
+-	    authctxt->pw->pw_name)) {
++	/* Use !options.use_kuserok here to make ssh_krb5_kuserok() not
++	 * depend on the existance of .k5login */
++	if (!ssh_krb5_kuserok(authctxt->krb5_ctx, authctxt->krb5_user, authctxt->pw->pw_name, !options.use_kuserok)) {
+ 		problem = -1;
+ 		goto out;
+ 	}
+diff -up openssh-7.4p1/gss-serv-krb5.c.kuserok openssh-7.4p1/gss-serv-krb5.c
+--- openssh-7.4p1/gss-serv-krb5.c.kuserok	2016-12-23 14:36:07.640465939 +0100
++++ openssh-7.4p1/gss-serv-krb5.c	2016-12-23 14:36:07.644465936 +0100
+@@ -67,6 +67,7 @@ static int ssh_gssapi_krb5_cmdok(krb5_pr
+     int);
+ 
+ static krb5_context krb_context = NULL;
++extern int ssh_krb5_kuserok(krb5_context, krb5_principal, const char *, int);
+ 
+ /* Initialise the krb5 library, for the stuff that GSSAPI won't do */
+ 
+@@ -92,6 +93,103 @@ ssh_gssapi_krb5_init(void)
+  * Returns true if the user is OK to log in, otherwise returns 0
+  */
+ 
++/* The purpose of the function is to find out if a Kerberos principal is
++ * allowed to log in as the given local user. This is a general problem with
++ * Kerberized services because by design the Kerberos principals are
++ * completely independent from the local user names. This is one of the
++ * reasons why Kerberos is working well on different operating systems like
++ * Windows and UNIX/Linux. Nevertheless a relationship between a Kerberos
++ * principal and a local user name must be established because otherwise every
++ * access would be granted for every principal with a valid ticket.
++ *
++ * Since it is a general issue libkrb5 provides some functions for
++ * applications to find out about the relationship between the Kerberos
++ * principal and a local user name. They are krb5_kuserok() and
++ * krb5_aname_to_localname().
++ *
++ * krb5_kuserok() can be used to "Determine if a principal is authorized to
++ * log in as a local user" (from the MIT Kerberos documentation of this
++ * function). Which is exactly what we are looking for and should be the
++ * preferred choice. It accepts the Kerberos principal and a local user name
++ * and let libkrb5 or its plugins determine if they relate to each other or
++ * not.
++ *
++ * krb5_aname_to_localname() can use used to "Convert a principal name to a
++ * local name" (from the MIT Kerberos documentation of this function). It
++ * accepts a Kerberos principle and returns a local name and it is up to the
++ * application to do any additional checks. There are two issues using
++ * krb5_aname_to_localname(). First, since POSIX user names are case
++ * sensitive, the calling application in general has no other choice than
++ * doing a case-sensitive string comparison between the name returned by
++ * krb5_aname_to_localname() and the name used at the login prompt. When the
++ * users are provided by a case in-sensitive server, e.g. Active Directory,
++ * this might lead to login failures because the user typing the name at the
++ * login prompt might not be aware of the right case. Another issue might be
++ * caused if there are multiple alias names available for a single user. E.g.
++ * the canonical name of a user is user@group.department.example.com but there
++ * exists a shorter login name, e.g. user@example.com, to safe typing at the
++ * login prompt. Here krb5_aname_to_localname() can only return the canonical
++ * name, but if the short alias is used at the login prompt authentication
++ * will fail as well. All this can be avoided by using krb5_kuserok() and
++ * configuring krb5.conf or using a suitable plugin to meet the needs of the
++ * given environment.
++ *
++ * The Fedora and RHEL version of openssh contain two patches which modify the
++ * access control behavior:
++ *  - openssh-6.6p1-kuserok.patch
++ *  - openssh-6.6p1-force_krb.patch
++ *
++ * openssh-6.6p1-kuserok.patch adds a new option KerberosUseKuserok for
++ * sshd_config which controls if krb5_kuserok() is used to check if the
++ * principle is authorized or if krb5_aname_to_localname() should be used.
++ * The reason to add this patch was that krb5_kuserok() by default checks if
++ * a .k5login file exits in the users home-directory. With this the user can
++ * give access to his account for any given principal which might be
++ * in violation with company policies and it would be useful if this can be
++ * rejected. Nevertheless the patch ignores the fact that krb5_kuserok() does
++ * no only check .k5login but other sources as well and checking .k5login can
++ * be disabled for all applications in krb5.conf as well. With this new
++ * option KerberosUseKuserok set to 'no' (and this is the default for RHEL7
++ * and Fedora 21) openssh can only use krb5_aname_to_localname() with the
++ * restrictions mentioned above.
++ *
++ * openssh-6.6p1-force_krb.patch adds a ksu like behaviour to ssh, i.e. when
++ * using GSSAPI authentication only commands configured in the .k5user can be
++ * executed. Here the wrong assumption that krb5_kuserok() only checks
++ * .k5login is made as well. In contrast ksu checks .k5login directly and
++ * does not use krb5_kuserok() which might be more useful for the given
++ * purpose. Additionally this patch is not synced with
++ * openssh-6.6p1-kuserok.patch.
++ *
++ * The current patch tries to restore the usage of krb5_kuserok() so that e.g.
++ * localauth plugins can be used. It does so by adding a forth parameter to
++ * ssh_krb5_kuserok() which indicates whether .k5login exists or not. If it
++ * does not exists krb5_kuserok() is called even if KerberosUseKuserok is set
++ * to 'no' because the intent of the option is to not check .k5login and if it
++ * does not exists krb5_kuserok() returns a result without checking .k5login.
++ * If .k5login does exists and KerberosUseKuserok is 'no' we fall back to
++ * krb5_aname_to_localname(). This is in my point of view an acceptable
++ * limitation and does not break the current behaviour.
++ *
++ * Additionally with this patch ssh_krb5_kuserok() is called in
++ * ssh_gssapi_krb5_cmdok() instead of only krb5_aname_to_localname() is
++ * neither .k5login nor .k5users exists to allow plugin evaluation via
++ * krb5_kuserok() as well.
++ *
++ * I tried to keep the patch as minimal as possible, nevertheless I see some
++ * areas for improvement which, if they make sense, have to be evaluated
++ * carefully because they might change existing behaviour and cause breaks
++ * during upgrade:
++ * - I wonder if disabling .k5login usage make sense in sshd or if it should
++ *   be better disabled globally in krb5.conf
++ * - if really needed openssh-6.6p1-kuserok.patch should be fixed to really
++ *   only disable checking .k5login and maybe .k5users
++ * - the ksu behaviour should be configurable and maybe check the .k5login and
++ *   .k5users files directly like ksu itself does
++ * - to make krb5_aname_to_localname() more useful an option for sshd to use
++ *   the canonical name (the one returned by getpwnam()) instead of the name
++ *   given at the login prompt might be useful */
++
+ static int
+ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ {
+@@ -116,7 +214,8 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client
+ 	/* NOTE: .k5login and .k5users must opened as root, not the user,
+ 	 * because if they are on a krb5-protected filesystem, user credentials
+ 	 * to access these files aren't available yet. */
+-	if (krb5_kuserok(krb_context, princ, name) && k5login_exists) {
++	if (ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)
++			&& k5login_exists) {
+ 		retval = 1;
+ 		logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
+ 		    name, (char *)client->displayname.value);
+@@ -190,9 +289,8 @@ ssh_gssapi_krb5_cmdok(krb5_principal pri
+ 	snprintf(file, sizeof(file), "%s/.k5users", pw->pw_dir);
+ 	/* If both .k5login and .k5users DNE, self-login is ok. */
+ 	if (!k5login_exists && (access(file, F_OK) == -1)) {
+-		return (krb5_aname_to_localname(krb_context, principal,
+-		    sizeof(kuser), kuser) == 0) &&
+-		    (strcmp(kuser, luser) == 0);
++                return ssh_krb5_kuserok(krb_context, principal, luser,
++                                        k5login_exists);
+ 	}
+ 	if ((fp = fopen(file, "r")) == NULL) {
+ 		int saved_errno = errno;
+diff -up openssh-7.4p1/servconf.c.kuserok openssh-7.4p1/servconf.c
+--- openssh-7.4p1/servconf.c.kuserok	2016-12-23 14:36:07.630465944 +0100
++++ openssh-7.4p1/servconf.c	2016-12-23 15:11:52.278133344 +0100
+@@ -116,6 +116,7 @@ initialize_server_options(ServerOptions
+ 	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->challenge_response_authentication = -1;
+@@ -278,6 +279,8 @@ fill_default_server_options(ServerOption
+ 	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)
+@@ -399,7 +402,7 @@ typedef enum {
+ 	sPermitRootLogin, sLogFacility, sLogLevel,
+ 	sRhostsRSAAuthentication, sRSAAuthentication,
+ 	sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
+-	sKerberosGetAFSToken, sKerberosUniqueCCache,
++	sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok,
+ 	sChallengeResponseAuthentication,
+ 	sPasswordAuthentication, sKbdInteractiveAuthentication,
+ 	sListenAddress, sAddressFamily,
+@@ -478,12 +481,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 },
+@@ -1644,6 +1649,10 @@ process_server_config_line(ServerOptions
+ 		*inc_flags &= ~SSHCFG_MATCH_ONLY;
+ 		break;
+ 
++	case sKerberosUseKuserok:
++		intptr = &options->use_kuserok;
++		goto parse_flag;
++
+ 	case sPermitListen:
+ 	case sPermitOpen:
+ 		if (opcode == sPermitListen) {
+@@ -2016,6 +2025,7 @@ copy_set_server_options(ServerOptions *d
+ 	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);
+@@ -2309,6 +2319,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);
+diff -up openssh-7.4p1/servconf.h.kuserok openssh-7.4p1/servconf.h
+--- openssh-7.4p1/servconf.h.kuserok	2016-12-23 14:36:07.630465944 +0100
++++ openssh-7.4p1/servconf.h	2016-12-23 14:36:07.645465936 +0100
+@@ -118,6 +118,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 */
+diff -up openssh-7.4p1/sshd_config.5.kuserok openssh-7.4p1/sshd_config.5
+--- openssh-7.4p1/sshd_config.5.kuserok	2016-12-23 14:36:07.637465940 +0100
++++ openssh-7.4p1/sshd_config.5	2016-12-23 15:14:03.117162222 +0100
+@@ -850,6 +850,10 @@ Specifies whether to automatically destr
+ .Cm no
+ can lead to overwriting previous tickets by subseqent connections to the same
+ user account.
++.It Cm KerberosUseKuserok
++Specifies whether to look at .k5login file for user's aliases.
++The default is
++.Cm yes .
+ .It Cm KexAlgorithms
+ Specifies the available KEX (Key Exchange) algorithms.
+ Multiple algorithms must be comma-separated.
+@@ -1078,6 +1082,7 @@ Available keywords are
+ .Cm IPQoS ,
+ .Cm KbdInteractiveAuthentication ,
+ .Cm KerberosAuthentication ,
++.Cm KerberosUseKuserok ,
+ .Cm LogLevel ,
+ .Cm MaxAuthTries ,
+ .Cm MaxSessions ,
+diff -up openssh-7.4p1/sshd_config.kuserok openssh-7.4p1/sshd_config
+--- openssh-7.4p1/sshd_config.kuserok	2016-12-23 14:36:07.631465943 +0100
++++ openssh-7.4p1/sshd_config	2016-12-23 14:36:07.646465935 +0100
+@@ -73,6 +73,7 @@ ChallengeResponseAuthentication no
+ #KerberosOrLocalPasswd yes
+ #KerberosTicketCleanup yes
+ #KerberosGetAFSToken no
++#KerberosUseKuserok yes
+ 
+ # GSSAPI options
+ #GSSAPIAuthentication no
diff --git a/openssh-6.6p1-privsep-selinux.patch b/openssh-6.6p1-privsep-selinux.patch
new file mode 100644
index 0000000..3d4c287
--- /dev/null
+++ b/openssh-6.6p1-privsep-selinux.patch
@@ -0,0 +1,121 @@
+diff -up openssh-7.4p1/openbsd-compat/port-linux.h.privsep-selinux openssh-7.4p1/openbsd-compat/port-linux.h
+--- openssh-7.4p1/openbsd-compat/port-linux.h.privsep-selinux	2016-12-23 18:58:52.972122201 +0100
++++ openssh-7.4p1/openbsd-compat/port-linux.h	2016-12-23 18:58:52.974122201 +0100
+@@ -23,6 +23,7 @@ void ssh_selinux_setup_pty(char *, const
+ void ssh_selinux_change_context(const char *);
+ void ssh_selinux_setfscreatecon(const char *);
+ 
++void sshd_selinux_copy_context(void);
+ void sshd_selinux_setup_exec_context(char *);
+ #endif
+ 
+diff -up openssh-7.4p1/openbsd-compat/port-linux-sshd.c.privsep-selinux openssh-7.4p1/openbsd-compat/port-linux-sshd.c
+--- openssh-7.4p1/openbsd-compat/port-linux-sshd.c.privsep-selinux	2016-12-23 18:58:52.973122201 +0100
++++ openssh-7.4p1/openbsd-compat/port-linux-sshd.c	2016-12-23 18:58:52.974122201 +0100
+@@ -419,6 +419,28 @@ sshd_selinux_setup_exec_context(char *pw
+ 	debug3("%s: done", __func__);
+ }
+ 
++void
++sshd_selinux_copy_context(void)
++{
++	security_context_t *ctx;
++
++	if (!ssh_selinux_enabled())
++		return;
++
++	if (getexeccon((security_context_t *)&ctx) != 0) {
++		logit("%s: getexeccon failed with %s", __func__, strerror(errno));
++		return;
++	}
++	if (ctx != NULL) {
++		/* unset exec context before we will lose this capabililty */
++		if (setexeccon(NULL) != 0)
++			fatal("%s: setexeccon failed with %s", __func__, strerror(errno));
++		if (setcon(ctx) != 0)
++			fatal("%s: setcon failed with %s", __func__, strerror(errno));
++		freecon(ctx);
++	}
++}
++
+ #endif
+ #endif
+ 
+diff -up openssh-7.4p1/session.c.privsep-selinux openssh-7.4p1/session.c
+--- openssh-7.4p1/session.c.privsep-selinux	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/session.c	2016-12-23 18:58:52.974122201 +0100
+@@ -1331,7 +1331,7 @@ do_setusercontext(struct passwd *pw)
+ 
+ 	platform_setusercontext(pw);
+ 
+-	if (platform_privileged_uidswap()) {
++	if (platform_privileged_uidswap() && (!is_child || !use_privsep)) {
+ #ifdef HAVE_LOGIN_CAP
+ 		if (setusercontext(lc, pw, pw->pw_uid,
+ 		    (LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETUSER))) < 0) {
+@@ -1361,6 +1361,9 @@ do_setusercontext(struct passwd *pw)
+			    (unsigned long long)pw->pw_uid);
+			chroot_path = percent_expand(tmp, "h", pw->pw_dir,
+			    "u", pw->pw_name, "U", uidstr, (char *)NULL);
++#ifdef WITH_SELINUX
++			sshd_selinux_copy_context();
++#endif
+ 			safely_chroot(chroot_path, pw->pw_uid);
+ 			free(tmp);
+ 			free(chroot_path);
+@@ -1396,6 +1399,11 @@ do_setusercontext(struct passwd *pw)
+ 		/* Permanently switch to the desired uid. */
+ 		permanently_set_uid(pw);
+ #endif
++
++#ifdef WITH_SELINUX
++		if (in_chroot == 0)
++			sshd_selinux_copy_context();
++#endif
+ 	} else if (options.chroot_directory != NULL &&
+ 	    strcasecmp(options.chroot_directory, "none") != 0) {
+ 		fatal("server lacks privileges to chroot to ChrootDirectory");
+@@ -1413,9 +1421,6 @@ do_pwchange(Session *s)
+ 	if (s->ttyfd != -1) {
+ 		fprintf(stderr,
+ 		    "You must change your password now and login again!\n");
+-#ifdef WITH_SELINUX
+-		setexeccon(NULL);
+-#endif
+ #ifdef PASSWD_NEEDS_USERNAME
+ 		execl(_PATH_PASSWD_PROG, "passwd", s->pw->pw_name,
+ 		    (char *)NULL);
+@@ -1625,9 +1630,6 @@ do_child(Session *s, const char *command
+ 		argv[i] = NULL;
+ 		optind = optreset = 1;
+ 		__progname = argv[0];
+-#ifdef WITH_SELINUX
+-		ssh_selinux_change_context("sftpd_t");
+-#endif
+ 		exit(sftp_server_main(i, argv, s->pw));
+ 	}
+ 
+diff -up openssh-7.4p1/sshd.c.privsep-selinux openssh-7.4p1/sshd.c
+--- openssh-7.4p1/sshd.c.privsep-selinux	2016-12-23 18:58:52.973122201 +0100
++++ openssh-7.4p1/sshd.c	2016-12-23 18:59:13.808124269 +0100
+@@ -540,6 +540,10 @@ privsep_preauth_child(void)
+ 	/* Demote the private keys to public keys. */
+ 	demote_sensitive_data();
+ 
++#ifdef WITH_SELINUX
++	ssh_selinux_change_context("sshd_net_t");
++#endif
++
+ 	/* Demote the child */
+ 	if (privsep_chroot) {
+ 		/* Change our root directory */
+@@ -633,6 +637,9 @@ privsep_postauth(Authctxt *authctxt)
+ {
+ #ifdef DISABLE_FD_PASSING
+ 	if (1) {
++#elif defined(WITH_SELINUX)
++	if (0) {
++		/* even root user can be confined by SELinux */
+ #else
+ 	if (authctxt->pw->pw_uid == 0) {
+ #endif
diff --git a/openssh-6.7p1-coverity.patch b/openssh-6.7p1-coverity.patch
new file mode 100644
index 0000000..3f34464
--- /dev/null
+++ b/openssh-6.7p1-coverity.patch
@@ -0,0 +1,185 @@
+diff -up openssh-7.4p1/channels.c.coverity openssh-7.4p1/channels.c
+--- openssh-7.4p1/channels.c.coverity	2016-12-23 16:40:26.881788686 +0100
++++ openssh-7.4p1/channels.c	2016-12-23 16:42:36.244818763 +0100
+@@ -288,11 +288,11 @@ channel_register_fds(Channel *c, int rfd
+ 
+ 	/* enable nonblocking mode */
+ 	if (nonblock) {
+-		if (rfd != -1)
++		if (rfd >= 0)
+ 			set_nonblock(rfd);
+-		if (wfd != -1)
++		if (wfd >= 0)
+ 			set_nonblock(wfd);
+-		if (efd != -1)
++		if (efd >= 0)
+ 			set_nonblock(efd);
+ 	}
+ }
+diff -up openssh-7.4p1/monitor.c.coverity openssh-7.4p1/monitor.c
+--- openssh-7.4p1/monitor.c.coverity	2016-12-23 16:40:26.888788688 +0100
++++ openssh-7.4p1/monitor.c	2016-12-23 16:40:26.900788691 +0100
+@@ -411,7 +411,7 @@ monitor_child_preauth(Authctxt *_authctx
+ 	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)
+ 		;
+ 
+ 	if (pmonitor->m_recvfd >= 0)
+diff -up openssh-7.4p1/monitor_wrap.c.coverity openssh-7.4p1/monitor_wrap.c
+--- openssh-7.4p1/monitor_wrap.c.coverity	2016-12-23 16:40:26.892788689 +0100
++++ openssh-7.4p1/monitor_wrap.c	2016-12-23 16:40:26.900788691 +0100
+@@ -525,10 +525,10 @@ mm_pty_allocate(int *ptyfd, int *ttyfd,
+ 	if ((tmp1 = dup(pmonitor->m_recvfd)) == -1 ||
+ 	    (tmp2 = dup(pmonitor->m_recvfd)) == -1) {
+ 		error("%s: cannot allocate fds for pty", __func__);
+-		if (tmp1 > 0)
++		if (tmp1 >= 0)
+ 			close(tmp1);
+-		if (tmp2 > 0)
+-			close(tmp2);
++		/*DEAD CODE if (tmp2 >= 0)
++			close(tmp2);*/
+ 		return 0;
+ 	}
+ 	close(tmp1);
+diff -up openssh-7.4p1/openbsd-compat/bindresvport.c.coverity openssh-7.4p1/openbsd-compat/bindresvport.c
+--- openssh-7.4p1/openbsd-compat/bindresvport.c.coverity	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/openbsd-compat/bindresvport.c	2016-12-23 16:40:26.901788691 +0100
+@@ -58,7 +58,7 @@ bindresvport_sa(int sd, struct sockaddr
+ 	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 -up openssh-7.4p1/scp.c.coverity openssh-7.4p1/scp.c
+--- openssh-7.4p1/scp.c.coverity	2016-12-23 16:40:26.856788681 +0100
++++ openssh-7.4p1/scp.c	2016-12-23 16:40:26.901788691 +0100
+@@ -157,7 +157,7 @@ killchild(int signo)
+ {
+ 	if (do_cmd_pid > 1) {
+ 		kill(do_cmd_pid, signo ? signo : SIGTERM);
+-		waitpid(do_cmd_pid, NULL, 0);
++		(void) waitpid(do_cmd_pid, NULL, 0);
+ 	}
+ 
+ 	if (signo)
+diff -up openssh-7.4p1/servconf.c.coverity openssh-7.4p1/servconf.c
+--- openssh-7.4p1/servconf.c.coverity	2016-12-23 16:40:26.896788690 +0100
++++ openssh-7.4p1/servconf.c	2016-12-23 16:40:26.901788691 +0100
+@@ -1547,7 +1547,7 @@ process_server_config_line(ServerOptions
+ 			fatal("%s line %d: Missing subsystem name.",
+ 			    filename, linenum);
+ 		if (!*activep) {
+-			arg = strdelim(&cp);
++			/*arg =*/ (void) strdelim(&cp);
+ 			break;
+ 		}
+ 		for (i = 0; i < options->num_subsystems; i++)
+@@ -1638,8 +1638,9 @@ process_server_config_line(ServerOptions
+ 		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 -up openssh-7.4p1/serverloop.c.coverity openssh-7.4p1/serverloop.c
+--- openssh-7.4p1/serverloop.c.coverity	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/serverloop.c	2016-12-23 16:40:26.902788691 +0100
+@@ -125,13 +125,13 @@ notify_setup(void)
+ static void
+ notify_parent(void)
+ {
+-	if (notify_pipe[1] != -1)
++	if (notify_pipe[1] >= 0)
+ 		(void)write(notify_pipe[1], "", 1);
+ }
+ static void
+ notify_prepare(fd_set *readset)
+ {
+-	if (notify_pipe[0] != -1)
++	if (notify_pipe[0] >= 0)
+ 		FD_SET(notify_pipe[0], readset);
+ }
+ static void
+@@ -139,8 +139,8 @@ 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("%s: reading", __func__);
+ }
+ 
+@@ -518,7 +518,7 @@ server_request_tun(void)
+ 		debug("%s: invalid tun", __func__);
+ 		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;
+diff -up openssh-7.4p1/sftp.c.coverity openssh-7.4p1/sftp.c
+--- openssh-7.4p1/sftp.c.coverity	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/sftp.c	2016-12-23 16:40:26.903788691 +0100
+@@ -224,7 +224,7 @@ killchild(int signo)
+ 	pid = sshpid;
+ 	if (pid > 1) {
+ 		kill(pid, SIGTERM);
+-		waitpid(pid, NULL, 0);
++		(void) waitpid(pid, NULL, 0);
+ 	}
+ 
+ 	_exit(1);
+diff -up openssh-7.4p1/ssh-agent.c.coverity openssh-7.4p1/ssh-agent.c
+--- openssh-7.4p1/ssh-agent.c.coverity	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/ssh-agent.c	2016-12-23 16:40:26.903788691 +0100
+@@ -1220,8 +1220,8 @@ main(int ac, char **av)
+ 	sanitise_stdfd();
+ 
+ 	/* drop */
+-	setegid(getgid());
+-	setgid(getgid());
++	(void) setegid(getgid());
++	(void) setgid(getgid());
+ 
+ 	platform_disable_tracing(0);	/* strict=no */
+ 
+diff -up openssh-7.4p1/sshd.c.coverity openssh-7.4p1/sshd.c
+--- openssh-7.4p1/sshd.c.coverity	2016-12-23 16:40:26.897788690 +0100
++++ openssh-7.4p1/sshd.c	2016-12-23 16:40:26.904788692 +0100
+@@ -691,8 +691,10 @@ privsep_preauth(Authctxt *authctxt)
+ 
+ 		privsep_preauth_child(ssh);
+ 		setproctitle("%s", "[net]");
+-		if (box != NULL)
++		if (box != NULL) {
+ 			ssh_sandbox_child(box);
++			free(box);
++		}
+ 
+ 		return 0;
+ 	}
+@@ -1386,6 +1388,9 @@ server_accept_loop(int *sock_in, int *so
+ 			explicit_bzero(rnd, sizeof(rnd));
+ 		}
+ 	}
++
++	if (fdset != NULL)
++		free(fdset);
+ }
+ 
+ /*
diff --git a/openssh-6.7p1-kdf-cavs.patch b/openssh-6.7p1-kdf-cavs.patch
new file mode 100644
index 0000000..f892bc8
--- /dev/null
+++ b/openssh-6.7p1-kdf-cavs.patch
@@ -0,0 +1,618 @@
+diff -up openssh-6.8p1/Makefile.in.kdf-cavs openssh-6.8p1/Makefile.in
+--- openssh-6.8p1/Makefile.in.kdf-cavs	2015-03-18 11:23:46.346049359 +0100
++++ openssh-6.8p1/Makefile.in	2015-03-18 11:24:20.395968445 +0100
+@@ -29,6 +29,7 @@ SSH_LDAP_HELPER=$(libexecdir)/ssh-ldap-h
+ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
+ SSH_KEYCAT=$(libexecdir)/ssh-keycat
+ CTR_CAVSTEST=$(libexecdir)/ctr-cavstest
++SSH_CAVS=$(libexecdir)/ssh-cavs
+ SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+ SSH_SK_HELPER=$(libexecdir)/ssh-sk-helper
+ PRIVSEP_PATH=@PRIVSEP_PATH@
+@@ -67,7 +68,7 @@ EXEEXT=@EXEEXT@
+ 
+ .SUFFIXES: .lo
+ 
+-TARGETS=ssh$(EXEEXT) sshd$(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) ctr-cavstest$(EXEEXT)
++TARGETS=ssh$(EXEEXT) sshd$(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) ctr-cavstest$(EXEEXT) ssh-cavs$(EXEEXT)
+ 
+ XMSS_OBJS=\
+ 	ssh-xmss.o \
+@@ -198,6 +199,9 @@ ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHD
+ ctr-cavstest$(EXEEXT): $(LIBCOMPAT) libssh.a ctr-cavstest.o
+ 	$(LD) -o $@ ctr-cavstest.o $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
+ 
++ssh-cavs$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-cavs.o $(SKOBJS)
++	$(LD) -o $@ ssh-cavs.o $(SKOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
++
+ ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
+ 	$(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
+ 
+@@ -331,6 +335,8 @@ install-files:
+ 	$(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) ctr-cavstest$(EXEEXT) $(DESTDIR)$(libexecdir)/ctr-cavstest$(EXEEXT)
++	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-cavs$(EXEEXT) $(DESTDIR)$(libexecdir)/ssh-cavs$(EXEEXT)
++	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-cavs_driver.pl $(DESTDIR)$(libexecdir)/ssh-cavs_driver.pl
+ 	$(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 -up openssh-6.8p1/ssh-cavs.c.kdf-cavs openssh-6.8p1/ssh-cavs.c
+--- openssh-6.8p1/ssh-cavs.c.kdf-cavs	2015-03-18 11:23:46.348049354 +0100
++++ openssh-6.8p1/ssh-cavs.c	2015-03-18 11:23:46.348049354 +0100
+@@ -0,0 +1,387 @@
++/*
++ * Copyright (C) 2015, Stephan Mueller <smueller@chronox.de>
++ *
++ * 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 General Public License, in which case the provisions of the GPL2
++ * 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, ALL OF
++ * WHICH ARE HEREBY 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 NOT ADVISED OF THE POSSIBILITY OF SUCH
++ * DAMAGE.
++ */
++
++#include "includes.h"
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <errno.h>
++#include <sys/types.h>
++#include <string.h>
++
++#include <openssl/bn.h>
++
++#include "xmalloc.h"
++#include "sshbuf.h"
++#include "sshkey.h"
++#include "cipher.h"
++#include "kex.h"
++#include "packet.h"
++#include "digest.h"
++
++static int bin_char(unsigned char hex)
++{
++	if (48 <= hex && 57 >= hex)
++		return (hex - 48);
++	if (65 <= hex && 70 >= hex)
++		return (hex - 55);
++	if (97 <= hex && 102 >= hex)
++		return (hex - 87);
++	return 0;
++}
++
++/*
++ * Convert hex representation into binary string
++ * @hex input buffer with hex representation
++ * @hexlen length of hex
++ * @bin output buffer with binary data
++ * @binlen length of already allocated bin buffer (should be at least
++ *	   half of hexlen -- if not, only a fraction of hexlen is converted)
++ */
++static void hex2bin(const char *hex, size_t hexlen,
++		    unsigned char *bin, size_t binlen)
++{
++	size_t i = 0;
++	size_t chars = (binlen > (hexlen / 2)) ? (hexlen / 2) : binlen;
++
++	for (i = 0; i < chars; i++) {
++		bin[i] = bin_char(hex[(i*2)]) << 4;
++		bin[i] |= bin_char(hex[((i*2)+1)]);
++	}
++}
++
++/*
++ * Allocate sufficient space for binary representation of hex
++ * and convert hex into bin
++ *
++ * Caller must free bin
++ * @hex input buffer with hex representation
++ * @hexlen length of hex
++ * @bin return value holding the pointer to the newly allocated buffer
++ * @binlen return value holding the allocated size of bin
++ *
++ * return: 0 on success, !0 otherwise
++ */
++static int hex2bin_alloc(const char *hex, size_t hexlen,
++			 unsigned char **bin, size_t *binlen)
++{
++	unsigned char *out = NULL;
++	size_t outlen = 0;
++
++	if (!hexlen)
++		return -EINVAL;
++
++	outlen = (hexlen + 1) / 2;
++
++	out = calloc(1, outlen);
++	if (!out)
++		return -errno;
++
++	hex2bin(hex, hexlen, out, outlen);
++	*bin = out;
++	*binlen = outlen;
++	return 0;
++}
++
++static char hex_char_map_l[] = { '0', '1', '2', '3', '4', '5', '6', '7',
++				 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
++static char hex_char_map_u[] = { '0', '1', '2', '3', '4', '5', '6', '7',
++				 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
++static char hex_char(unsigned int bin, int u)
++{
++	if (bin < sizeof(hex_char_map_l))
++		return (u) ? hex_char_map_u[bin] : hex_char_map_l[bin];
++	return 'X';
++}
++
++/*
++ * Convert binary string into hex representation
++ * @bin input buffer with binary data
++ * @binlen length of bin
++ * @hex output buffer to store hex data
++ * @hexlen length of already allocated hex buffer (should be at least
++ *	   twice binlen -- if not, only a fraction of binlen is converted)
++ * @u case of hex characters (0=>lower case, 1=>upper case)
++ */
++static void bin2hex(const unsigned char *bin, size_t binlen,
++		    char *hex, size_t hexlen, int u)
++{
++	size_t i = 0;
++	size_t chars = (binlen > (hexlen / 2)) ? (hexlen / 2) : binlen;
++
++	for (i = 0; i < chars; i++) {
++		hex[(i*2)] = hex_char((bin[i] >> 4), u);
++		hex[((i*2)+1)] = hex_char((bin[i] & 0x0f), u);
++	}
++}
++
++struct kdf_cavs {
++	unsigned char *K;
++	size_t Klen;
++	unsigned char *H;
++	size_t Hlen;
++	unsigned char *session_id;
++	size_t session_id_len;
++
++	unsigned int iv_len;
++	unsigned int ek_len;
++	unsigned int ik_len;
++};
++
++static int sshkdf_cavs(struct kdf_cavs *test)
++{
++	int ret = 0;
++	struct kex kex;
++	struct sshbuf *Kb = NULL;
++	BIGNUM *Kbn = NULL;
++	int mode = 0;
++	struct newkeys *ctoskeys;
++	struct newkeys *stockeys;
++	struct ssh *ssh = NULL;
++
++#define HEXOUTLEN 500
++	char hex[HEXOUTLEN];
++
++	memset(&kex, 0, sizeof(struct kex));
++
++	Kbn = BN_new();
++	BN_bin2bn(test->K, test->Klen, Kbn);
++	if (!Kbn) {
++		printf("cannot convert K into bignum\n");
++		ret = 1;
++		goto out;
++	}
++	Kb = sshbuf_new();
++	if (!Kb) {
++		printf("cannot convert K into sshbuf\n");
++		ret = 1;
++		goto out;
++	}
++	sshbuf_put_bignum2(Kb, Kbn);
++
++	kex.session_id = test->session_id;
++	kex.session_id_len = test->session_id_len;
++
++	/* setup kex */
++
++	/* select the right hash based on struct ssh_digest digests */
++	switch (test->ik_len) {
++		case 20:
++			kex.hash_alg = SSH_DIGEST_SHA1;
++			break;
++		case 32:
++			kex.hash_alg = SSH_DIGEST_SHA256;
++			break;
++		case 48:
++			kex.hash_alg = SSH_DIGEST_SHA384;
++			break;
++		case 64:
++			kex.hash_alg = SSH_DIGEST_SHA512;
++			break;
++		default:
++			printf("Wrong hash type %u\n", test->ik_len);
++			ret = 1;
++			goto out;
++	}
++
++	/* implement choose_enc */
++	for (mode = 0; mode < 2; mode++) {
++		kex.newkeys[mode] = calloc(1, sizeof(struct newkeys));
++		if (!kex.newkeys[mode]) {
++			printf("allocation of newkeys failed\n");
++			ret = 1;
++			goto out;
++		}
++		kex.newkeys[mode]->enc.iv_len = test->iv_len;
++		kex.newkeys[mode]->enc.key_len = test->ek_len;
++		kex.newkeys[mode]->enc.block_size = (test->iv_len == 64) ? 8 : 16;
++		kex.newkeys[mode]->mac.key_len = test->ik_len;
++	}
++
++	/* implement kex_choose_conf */
++	kex.we_need = kex.newkeys[0]->enc.key_len;
++	if (kex.we_need < kex.newkeys[0]->enc.block_size)
++		kex.we_need = kex.newkeys[0]->enc.block_size;
++	if (kex.we_need < kex.newkeys[0]->enc.iv_len)
++		kex.we_need = kex.newkeys[0]->enc.iv_len;
++	if (kex.we_need < kex.newkeys[0]->mac.key_len)
++		kex.we_need = kex.newkeys[0]->mac.key_len;
++
++	/* MODE_OUT (1) -> server to client
++	 * MODE_IN (0) -> client to server */
++	kex.server = 1;
++
++	/* do it */
++	if ((ssh = ssh_packet_set_connection(NULL, -1, -1)) == NULL){
++		printf("Allocation error\n");
++		goto out;
++	}
++	ssh->kex = &kex;
++	kex_derive_keys(ssh, test->H, test->Hlen, Kb);
++
++	ctoskeys = kex.newkeys[0];
++	stockeys = kex.newkeys[1];
++
++	/* get data */
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(ctoskeys->enc.iv, (size_t)ctoskeys->enc.iv_len,
++		hex, HEXOUTLEN, 0);
++	printf("Initial IV (client to server) = %s\n", hex);
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(stockeys->enc.iv, (size_t)stockeys->enc.iv_len,
++		hex, HEXOUTLEN, 0);
++	printf("Initial IV (server to client) = %s\n", hex);
++
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(ctoskeys->enc.key, (size_t)ctoskeys->enc.key_len,
++		hex, HEXOUTLEN, 0);
++	printf("Encryption key (client to server) = %s\n", hex);
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(stockeys->enc.key, (size_t)stockeys->enc.key_len,
++		hex, HEXOUTLEN, 0);
++	printf("Encryption key (server to client) = %s\n", hex);
++
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(ctoskeys->mac.key, (size_t)ctoskeys->mac.key_len,
++		hex, HEXOUTLEN, 0);
++	printf("Integrity key (client to server) = %s\n", hex);
++	memset(hex, 0, HEXOUTLEN);
++	bin2hex(stockeys->mac.key, (size_t)stockeys->mac.key_len,
++		hex, HEXOUTLEN, 0);
++	printf("Integrity key (server to client) = %s\n", hex);
++
++out:
++	if (Kbn)
++		BN_free(Kbn);
++	if (Kb)
++		sshbuf_free(Kb);
++	if (ssh)
++		ssh_packet_close(ssh);
++	return ret;
++}
++
++static void usage(void)
++{
++	fprintf(stderr, "\nOpenSSH KDF CAVS Test\n\n");
++	fprintf(stderr, "Usage:\n");
++	fprintf(stderr, "\t-K\tShared secret string\n");
++	fprintf(stderr, "\t-H\tHash string\n");
++	fprintf(stderr, "\t-s\tSession ID string\n");
++	fprintf(stderr, "\t-i\tIV length to be generated\n");
++	fprintf(stderr, "\t-e\tEncryption key length to be generated\n");
++	fprintf(stderr, "\t-m\tMAC key length to be generated\n");
++}
++
++/*
++ * Test command example:
++ * ./ssh-cavs -K 0055d50f2d163cc07cd8a93cc7c3430c30ce786b572c01ad29fec7597000cf8618d664e2ec3dcbc8bb7a1a7eb7ef67f61cdaf291625da879186ac0a5cb27af571b59612d6a6e0627344d846271959fda61c78354aa498773d59762f8ca2d0215ec590d8633de921f920d41e47b3de6ab9a3d0869e1c826d0e4adebf8e3fb646a15dea20a410b44e969f4b791ed6a67f13f1b74234004d5fa5e87eff7abc32d49bbdf44d7b0107e8f10609233b7e2b7eff74a4daf25641de7553975dac6ac1e5117df6f6dbaa1c263d23a6c3e5a3d7d49ae8a828c1e333ac3f85fbbf57b5c1a45be45e43a7be1a4707eac779b8285522d1f531fe23f890fd38a004339932b93eda4 -H d3ab91a850febb417a25d892ec48ed5952c7a5de -s d3ab91a850febb417a25d892ec48ed5952c7a5de -i 8 -e 24 -m 20
++ *
++ * Initial IV (client to server) = 4bb320d1679dfd3a
++ * Initial IV (server to client) = 43dea6fdf263a308
++ * Encryption key (client to server) = 13048cc600b9d3cf9095aa6cf8e2ff9cf1c54ca0520c89ed
++ * Encryption key (server to client) = 1e483c5134e901aa11fc4e0a524e7ec7b75556148a222bb0
++ * Integrity key (client to server) = ecef63a092b0dcc585bdc757e01b2740af57d640
++ * Integrity key (server to client) = 7424b05f3c44a72b4ebd281fb71f9cbe7b64d479
++ */
++int main(int argc, char *argv[])
++{
++	struct kdf_cavs test;
++	int ret = 1;
++	int opt = 0;
++
++	memset(&test, 0, sizeof(struct kdf_cavs));
++	while((opt = getopt(argc, argv, "K:H:s:i:e:m:")) != -1)
++	{
++		size_t len = 0;
++		switch(opt)
++		{
++			/*
++			 * CAVS K is MPINT
++			 * we want a hex (i.e. the caller must ensure the
++			 * following transformations already happened):
++			 * 	1. cut off first four bytes
++			 * 	2. if most significant bit of value is
++			 *	   1, prepend 0 byte
++			 */
++			case 'K':
++				len = strlen(optarg);
++				ret = hex2bin_alloc(optarg, len,
++						    &test.K, &test.Klen);
++				if (ret)
++					goto out;
++				break;
++			case 'H':
++				len = strlen(optarg);
++				ret = hex2bin_alloc(optarg, len,
++						    &test.H, &test.Hlen);
++				if (ret)
++					goto out;
++				break;
++			case 's':
++				len = strlen(optarg);
++				ret = hex2bin_alloc(optarg, len,
++						    &test.session_id,
++						    &test.session_id_len);
++				if (ret)
++					goto out;
++				break;
++			case 'i':
++				test.iv_len = strtoul(optarg, NULL, 10);
++				break;
++			case 'e':
++				test.ek_len = strtoul(optarg, NULL, 10);
++				break;
++			case 'm':
++				test.ik_len = strtoul(optarg, NULL, 10);
++				break;
++			default:
++				usage();
++				goto out;
++		}
++	}
++
++	ret = sshkdf_cavs(&test);
++
++out:
++	if (test.session_id)
++		free(test.session_id);
++	if (test.K)
++		free(test.K);
++	if (test.H)
++		free(test.H);
++	return ret;
++
++}
+diff -up openssh-6.8p1/ssh-cavs_driver.pl.kdf-cavs openssh-6.8p1/ssh-cavs_driver.pl
+--- openssh-6.8p1/ssh-cavs_driver.pl.kdf-cavs	2015-03-18 11:23:46.348049354 +0100
++++ openssh-6.8p1/ssh-cavs_driver.pl	2015-03-18 11:23:46.348049354 +0100
+@@ -0,0 +1,184 @@
++#!/usr/bin/env perl
++#
++# CAVS test driver for OpenSSH
++#
++# Copyright (C) 2015, Stephan Mueller <smueller@chronox.de>
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in
++# all copies or substantial portions of the Software.
++#
++#                            NO WARRANTY
++#
++#    BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
++#    FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
++#    OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
++#    PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
++#    OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
++#    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
++#    TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
++#    PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
++#    REPAIR OR CORRECTION.
++#
++#    IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
++#    WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
++#    REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
++#    INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
++#    OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
++#    TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
++#    YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
++#    PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
++#    POSSIBILITY OF SUCH DAMAGES.
++#
++use strict;
++use warnings;
++use IPC::Open2;
++
++# Executing a program by feeding STDIN and retrieving
++# STDOUT
++# $1: data string to be piped to the app on STDIN
++# rest: program and args
++# returns: STDOUT of program as string
++sub pipe_through_program($@) {
++	my $in = shift;
++	my @args = @_;
++
++	my ($CO, $CI);
++	my $pid = open2($CO, $CI, @args);
++
++	my $out = "";
++	my $len = length($in);
++	my $first = 1;
++	while (1) {
++		my $rin = "";
++		my $win = "";
++		# Output of prog is FD that we read
++		vec($rin,fileno($CO),1) = 1;
++		# Input of prog is FD that we write
++		# check for $first is needed because we can have NULL input
++		# that is to be written to the app
++		if ( $len > 0 || $first) {
++			(vec($win,fileno($CI),1) = 1);
++			$first=0;
++		}
++		# Let us wait for 100ms
++		my $nfound = select(my $rout=$rin, my $wout=$win, undef, 0.1);
++		if ( $wout ) {
++			my $written = syswrite($CI, $in, $len);
++			die "broken pipe" if !defined $written;
++			$len -= $written;
++			substr($in, 0, $written) = "";
++			if ($len <= 0) {
++				close $CI or die "broken pipe: $!";
++			}
++		}
++		if ( $rout ) {
++			my $tmp_out = "";
++			my $bytes_read = sysread($CO, $tmp_out, 4096);
++			$out .= $tmp_out;
++			last if ($bytes_read == 0);
++		}
++	}
++	close $CO or die "broken pipe: $!";
++	waitpid $pid, 0;
++
++	return $out;
++}
++
++# Parser of CAVS test vector file
++# $1: Test vector file
++# $2: Output file for test results
++# return: nothing
++sub parse($$) {
++	my $infile = shift;
++	my $outfile = shift;
++
++	my $out = "";
++
++	my $K = "";
++	my $H = "";
++	my $session_id = "";
++	my $ivlen = 0;
++	my $eklen = "";
++	my $iklen = "";
++
++	open(IN, "<$infile");
++	while(<IN>) {
++
++		my $line = $_;
++		chomp($line);
++		$line =~ s/\r//;
++
++		if ($line =~ /\[SHA-1\]/) {
++			$iklen = 20;
++		} elsif ($line =~ /\[SHA-256\]/) {
++			$iklen = 32;
++		} elsif ($line =~ /\[SHA-384\]/) {
++			$iklen = 48;
++		} elsif ($line =~ /\[SHA-512\]/) {
++			$iklen = 64;
++		} elsif ($line =~ /^\[IV length\s*=\s*(.*)\]/) {
++			$ivlen = $1;
++			$ivlen = $ivlen / 8;
++		} elsif ($line =~ /^\[encryption key length\s*=\s*(.*)\]/) {
++			$eklen = $1;
++			$eklen = $eklen / 8;
++		} elsif ($line =~ /^K\s*=\s*(.*)/) {
++			$K = $1;
++			$K = substr($K, 8);
++			$K = "00" . $K;
++		} elsif ($line =~ /^H\s*=\s*(.*)/) {
++			$H = $1;
++		} elsif ($line =~ /^session_id\s*=\s*(.*)/) {
++			$session_id = $1;
++		}
++		$out .= $line . "\n";
++
++		if ($K ne "" && $H ne "" && $session_id ne "" &&
++		    $ivlen ne "" && $eklen ne "" && $iklen > 0) {
++			$out .= pipe_through_program("", "./ssh-cavs -H $H -K $K -s $session_id -i $ivlen -e $eklen -m $iklen");
++
++			$K = "";
++			$H = "";
++			$session_id = "";
++		}
++	}
++	close IN;
++	$out =~ s/\n/\r\n/g; # make it a dos file
++	open(OUT, ">$outfile") or die "Cannot create output file $outfile: $?";
++	print OUT $out;
++	close OUT;
++}
++
++############################################################
++#
++# let us pretend to be C :-)
++sub main() {
++
++	my $infile=$ARGV[0];
++	die "Error: Test vector file $infile not found" if (! -f $infile);
++
++	my $outfile = $infile;
++	# let us add .rsp regardless whether we could strip .req
++	$outfile =~ s/\.req$//;
++	$outfile .= ".rsp";
++	if (-f $outfile) {
++		die "Output file $outfile could not be removed: $?"
++			unless unlink($outfile);
++	}
++	print STDERR "Performing tests from source file $infile with results stored in destination file $outfile\n";
++
++	# Do the job
++	parse($infile, $outfile);
++}
++
++###########################################
++# Call it
++main();
++1;
diff --git a/openssh-6.7p1-sftp-force-permission.patch b/openssh-6.7p1-sftp-force-permission.patch
new file mode 100644
index 0000000..1cfa309
--- /dev/null
+++ b/openssh-6.7p1-sftp-force-permission.patch
@@ -0,0 +1,100 @@
+diff -up openssh-7.2p2/sftp-server.8.sftp-force-mode openssh-7.2p2/sftp-server.8
+--- openssh-7.2p2/sftp-server.8.sftp-force-mode	2016-03-09 19:04:48.000000000 +0100
++++ openssh-7.2p2/sftp-server.8	2016-06-23 16:18:20.463854117 +0200
+@@ -38,6 +38,7 @@
+ .Op Fl P Ar denied_requests
+ .Op Fl p Ar allowed_requests
+ .Op Fl u Ar umask
++.Op Fl m Ar force_file_perms
+ .Ek
+ .Nm
+ .Fl Q Ar protocol_feature
+@@ -138,6 +139,12 @@ Sets an explicit
+ .Xr umask 2
+ to be applied to newly-created files and directories, instead of the
+ user's default mask.
++.It Fl m Ar force_file_perms
++Sets explicit file permissions to be applied to newly-created files instead
++of the default or client requested mode.  Numeric values include:
++777, 755, 750, 666, 644, 640, etc.  Using both -m and -u switches makes the
++umask (-u) effective only for newly created directories and explicit mode (-m)
++for newly created files.
+ .El
+ .Pp
+ On some systems,
+diff -up openssh-7.2p2/sftp-server.c.sftp-force-mode openssh-7.2p2/sftp-server.c
+--- openssh-7.2p2/sftp-server.c.sftp-force-mode	2016-06-23 16:18:20.446854128 +0200
++++ openssh-7.2p2/sftp-server.c	2016-06-23 16:20:37.950766082 +0200
+@@ -69,6 +69,10 @@ struct sshbuf *oqueue;
+ /* Version of client */
+ static u_int version;
+ 
++/* Force file permissions */
++int permforce = 0;
++long permforcemode;
++
+ /* SSH2_FXP_INIT received */
+ static int init_done;
+ 
+@@ -683,6 +687,7 @@ process_open(u_int32_t id)
+ 	Attrib a;
+ 	char *name;
+ 	int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE;
++	mode_t old_umask = 0;
+ 
+ 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
+ 	    (r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */
+@@ -692,6 +697,10 @@ process_open(u_int32_t id)
+ 	debug3("request %u: open flags %d", id, pflags);
+ 	flags = flags_from_portable(pflags);
+ 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666;
++	if (permforce == 1) {   /* Force perm if -m is set */
++		mode = permforcemode;
++		old_umask = umask(0); /* so umask does not interfere */
++	}	
+ 	logit("open \"%s\" flags %s mode 0%o",
+ 	    name, string_from_portable(pflags), mode);
+ 	if (readonly &&
+@@ -713,6 +722,8 @@ process_open(u_int32_t id)
+ 			}
+ 		}
+ 	}
++	if (permforce == 1)
++		(void) umask(old_umask); /* restore umask to something sane */
+ 	if (status != SSH2_FX_OK)
+ 		send_status(id, status);
+ 	free(name);
+@@ -1494,7 +1505,7 @@ sftp_server_usage(void)
+ 	fprintf(stderr,
+ 	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
+ 	    "[-l log_level]\n\t[-P denied_requests] "
+-	    "[-p allowed_requests] [-u umask]\n"
++	    "[-p allowed_requests] [-u umask] [-m force_file_perms]\n"
+ 	    "       %s -Q protocol_feature\n",
+ 	    __progname, __progname);
+ 	exit(1);
+@@ -1520,7 +1531,7 @@ sftp_server_main(int argc, char **argv,
+ 	pw = pwcopy(user_pw);
+ 
+ 	while (!skipargs && (ch = getopt(argc, argv,
+-	    "d:f:l:P:p:Q:u:cehR")) != -1) {
++	    "d:f:l:P:p:Q:u:m:cehR")) != -1) {
+ 		switch (ch) {
+ 		case 'Q':
+ 			if (strcasecmp(optarg, "requests") != 0) {
+@@ -1580,6 +1591,15 @@ sftp_server_main(int argc, char **argv,
+ 				fatal("Invalid umask \"%s\"", optarg);
+ 			(void)umask((mode_t)mask);
+ 			break;
++		case 'm':
++			/* Force permissions on file received via sftp */
++			permforce = 1;
++			permforcemode = strtol(optarg, &cp, 8);
++			if (permforcemode < 0 || permforcemode > 0777 ||
++			    *cp != '\0' || (permforcemode == 0 &&
++			    errno != 0))
++				fatal("Invalid file mode \"%s\"", optarg);
++			break;
+ 		case 'h':
+ 		default:
+ 			sftp_server_usage();
diff --git a/openssh-6.8p1-sshdT-output.patch b/openssh-6.8p1-sshdT-output.patch
new file mode 100644
index 0000000..156e66d
--- /dev/null
+++ b/openssh-6.8p1-sshdT-output.patch
@@ -0,0 +1,12 @@
+diff -up openssh/servconf.c.sshdt openssh/servconf.c
+--- openssh/servconf.c.sshdt	2015-06-24 11:42:29.041078704 +0200
++++ openssh/servconf.c	2015-06-24 11:44:39.734745802 +0200
+@@ -2317,7 +2317,7 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_string(sXAuthLocation, o->xauth_location);
+ 	dump_cfg_string(sCiphers, o->ciphers);
+ 	dump_cfg_string(sMacs, o->macs);
+-	dump_cfg_string(sBanner, o->banner);
++	dump_cfg_string(sBanner, o->banner != NULL ? o->banner : "none");
+ 	dump_cfg_string(sForceCommand, o->adm_forced_command);
+ 	dump_cfg_string(sChrootDirectory, o->chroot_directory);
+ 	dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys);
diff --git a/openssh-7.1p2-audit-race-condition.patch b/openssh-7.1p2-audit-race-condition.patch
new file mode 100644
index 0000000..de70ff5
--- /dev/null
+++ b/openssh-7.1p2-audit-race-condition.patch
@@ -0,0 +1,187 @@
+diff -up openssh-7.4p1/monitor_wrap.c.audit-race openssh-7.4p1/monitor_wrap.c
+--- openssh-7.4p1/monitor_wrap.c.audit-race	2016-12-23 16:35:52.694685771 +0100
++++ openssh-7.4p1/monitor_wrap.c	2016-12-23 16:35:52.697685772 +0100
+@@ -1107,4 +1107,50 @@ mm_audit_destroy_sensitive_data(const ch
+ 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SERVER_KEY_FREE, m);
+ 	sshbuf_free(m);
+ }
++
++int mm_forward_audit_messages(int fdin)
++{
++	u_char buf[4];
++	u_int blen, msg_len;
++	struct sshbuf *m;
++	int r, ret = 0;
++
++	debug3("%s: entering", __func__);
++	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	do {
++		blen = atomicio(read, fdin, buf, sizeof(buf));
++		if (blen == 0) /* closed pipe */
++			break;
++		if (blen != sizeof(buf)) {
++			error("%s: Failed to read the buffer from child", __func__);
++			ret = -1;
++			break;
++		}
++
++		msg_len = get_u32(buf);
++		if (msg_len > 256 * 1024)
++			fatal("%s: read: bad msg_len %d", __func__, msg_len);
++		sshbuf_reset(m);
++		if ((r = sshbuf_reserve(m, msg_len, NULL)) != 0)
++			fatal("%s: buffer error: %s", __func__, ssh_err(r));
++		if (atomicio(read, fdin, sshbuf_mutable_ptr(m), msg_len) != msg_len) {
++			error("%s: Failed to read the the buffer content from the child", __func__);
++			ret = -1;
++			break;
++		}
++		if (atomicio(vwrite, pmonitor->m_recvfd, buf, blen) != blen || 
++		    atomicio(vwrite, pmonitor->m_recvfd, sshbuf_mutable_ptr(m), msg_len) != msg_len) {
++			error("%s: Failed to write the message to the monitor", __func__);
++			ret = -1;
++			break;
++		}
++	} while (1);
++	sshbuf_free(m);
++	return ret;
++}
++void mm_set_monitor_pipe(int fd)
++{
++	pmonitor->m_recvfd = fd;
++}
+ #endif /* SSH_AUDIT_EVENTS */
+diff -up openssh-7.4p1/monitor_wrap.h.audit-race openssh-7.4p1/monitor_wrap.h
+--- openssh-7.4p1/monitor_wrap.h.audit-race	2016-12-23 16:35:52.694685771 +0100
++++ openssh-7.4p1/monitor_wrap.h	2016-12-23 16:35:52.698685772 +0100
+@@ -83,6 +83,8 @@ void mm_audit_unsupported_body(int);
+ void mm_audit_kex_body(struct ssh *, int, char *, char *, char *, char *, pid_t, uid_t);
+ void mm_audit_session_key_free_body(struct ssh *, int, pid_t, uid_t);
+ void mm_audit_destroy_sensitive_data(struct ssh *, const char *, pid_t, uid_t);
++int mm_forward_audit_messages(int);
++void mm_set_monitor_pipe(int);
+ #endif
+ 
+ struct Session;
+diff -up openssh-7.4p1/session.c.audit-race openssh-7.4p1/session.c
+--- openssh-7.4p1/session.c.audit-race	2016-12-23 16:35:52.695685771 +0100
++++ openssh-7.4p1/session.c	2016-12-23 16:37:26.339730596 +0100
+@@ -162,6 +162,10 @@ static Session *sessions = NULL;
+ login_cap_t *lc;
+ #endif
+ 
++#ifdef SSH_AUDIT_EVENTS
++int paudit[2];
++#endif
++
+ static int is_child = 0;
+ static int in_chroot = 0;
+ static int have_dev_log = 1;
+@@ -289,6 +293,8 @@ xauth_valid_string(const char *s)
+ 	return 1;
+ }
+ 
++void child_destory_sensitive_data(struct ssh *ssh);
++
+ #define USE_PIPES 1
+ /*
+  * This is called to fork and execute a command when we have no tty.  This
+@@ -424,6 +430,8 @@ do_exec_no_pty(Session *s, const char *c
+ 		close(err[0]);
+ #endif
+ 
++		child_destory_sensitive_data(ssh);
++
+ 		/* Do processing for the child (exec command etc). */
+ 		do_child(ssh, s, command);
+ 		/* NOTREACHED */
+@@ -547,6 +555,9 @@ do_exec_pty(Session *s, const char *comm
+ 		/* Close the extra descriptor for the pseudo tty. */
+ 		close(ttyfd);
+ 
++		/* Do this early, so we will not block large MOTDs */
++		child_destory_sensitive_data(ssh);
++
+ 		/* record login, etc. similar to login(1) */
+ #ifndef HAVE_OSF_SIA
+ 		do_login(ssh, s, command);
+@@ -717,6 +728,8 @@ do_exec(Session *s, const char *command)
+ 	}
+ 	if (s->command != NULL && s->ptyfd == -1)
+ 		s->command_handle = PRIVSEP(audit_run_command(ssh, s->command));
++	if (pipe(paudit) < 0)
++		fatal("pipe: %s", strerror(errno));
+ #endif
+ 	if (s->ttyfd != -1)
+ 		ret = do_exec_pty(ssh, s, command);
+@@ -732,6 +745,20 @@ do_exec(Session *s, const char *command)
+ 	 */
+ 	sshbuf_reset(loginmsg);
+ 
++#ifdef SSH_AUDIT_EVENTS
++	close(paudit[1]);
++	if (use_privsep && ret == 0) {
++		/*
++		 * Read the audit messages from forked child and send them
++		 * back to monitor. We don't want to communicate directly,
++		 * because the messages might get mixed up.
++		 * Continue after the pipe gets closed (all messages sent).
++		 */
++		ret = mm_forward_audit_messages(paudit[0]);
++	}
++	close(paudit[0]);
++#endif /* SSH_AUDIT_EVENTS */
++
+ 	return ret;
+ }
+ 
+@@ -1538,6 +1565,34 @@ child_close_fds(void)
+ 	log_redirect_stderr_to(NULL);
+ }
+ 
++void
++child_destory_sensitive_data(struct ssh *ssh)
++{
++#ifdef SSH_AUDIT_EVENTS
++	int pparent = paudit[1];
++	close(paudit[0]);
++	/* Hack the monitor pipe to avoid race condition with parent */
++	if (use_privsep)
++		mm_set_monitor_pipe(pparent);
++#endif
++
++	/* remove hostkey from the child's memory */
++	destroy_sensitive_data(ssh, use_privsep);
++	/*
++	 * We can audit this, because we hacked the pipe to direct the
++	 * messages over postauth child. But this message requires answer
++	 * which we can't do using one-way pipe.
++	 */
++	packet_destroy_all(ssh, 0, 1);
++	/* XXX this will clean the rest but should not audit anymore */
++	/* packet_clear_keys(ssh); */
++
++#ifdef SSH_AUDIT_EVENTS
++	/* Notify parent that we are done */
++	close(pparent);
++#endif
++}
++
+ /*
+  * Performs common processing for the child, such as setting up the
+  * environment, closing extra file descriptors, setting the user and group
+@@ -1554,13 +1608,6 @@ do_child(Session *s, const char *command
+ 
+ 	sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id));
+ 
+-	/* remove hostkey from the child's memory */
+-	destroy_sensitive_data(ssh, 1);
+-	ssh_packet_clear_keys(ssh);
+-	/* Don't audit this - both us and the parent would be talking to the
+-	   monitor over a single socket, with no synchronization. */
+-	packet_destroy_all(ssh, 0, 1);
+-
+ 	/* Force a password change */
+ 	if (s->authctxt->force_pwchange) {
+ 		do_setusercontext(pw);
diff --git a/openssh-7.2p2-k5login_directory.patch b/openssh-7.2p2-k5login_directory.patch
new file mode 100644
index 0000000..242294a
--- /dev/null
+++ b/openssh-7.2p2-k5login_directory.patch
@@ -0,0 +1,87 @@
+diff --git a/auth-krb5.c b/auth-krb5.c
+index 2b02a04..19b9364 100644
+--- a/auth-krb5.c
++++ b/auth-krb5.c
+@@ -375,5 +375,21 @@ cleanup:
+ 		return (krb5_cc_resolve(ctx, ccname, ccache));
+ 	}
+ }
++
++/*
++ * Reads  k5login_directory  option from the  krb5.conf
++ */
++krb5_error_code
++ssh_krb5_get_k5login_directory(krb5_context ctx, char **k5login_directory) {
++	profile_t p;
++	int ret = 0;
++
++	ret = krb5_get_profile(ctx, &p);
++	if (ret)
++		return ret;
++
++	return profile_get_string(p, "libdefaults", "k5login_directory", NULL, NULL,
++		k5login_directory);
++}
+ #endif /* !HEIMDAL */
+ #endif /* KRB5 */
+diff --git a/auth.h b/auth.h
+index f9d191c..c432d2f 100644
+--- a/auth.h
++++ b/auth.h
+@@ -222,6 +222,8 @@ int	 sys_auth_passwd(Authctxt *, const char *);
+ 
+ #if defined(KRB5) && !defined(HEIMDAL)
+ krb5_error_code ssh_krb5_cc_new_unique(krb5_context, krb5_ccache *, int *);
++krb5_error_code ssh_krb5_get_k5login_directory(krb5_context ctx,
++	char **k5login_directory);
+ #endif
+ 
+ #endif /* AUTH_H */
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index a7c0c5f..df8cc9a 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -244,8 +244,27 @@ ssh_gssapi_k5login_exists()
+ {
+ 	char file[MAXPATHLEN];
+ 	struct passwd *pw = the_authctxt->pw;
++	char *k5login_directory = NULL;
++	int ret = 0;
++
++	ret = ssh_krb5_get_k5login_directory(krb_context, &k5login_directory);
++	debug3("%s: k5login_directory = %s (rv=%d)", __func__, k5login_directory, ret);
++	if (k5login_directory == NULL || ret != 0) {
++		/* If not set, the library will look for  k5login
++		 * files in the user's home directory, with the filename  .k5login.
++		 */
++		snprintf(file, sizeof(file), "%s/.k5login", pw->pw_dir);
++	} else {
++		/* If set, the library will look for a local user's k5login file
++		 * within the named directory, with a filename corresponding to the
++		 * local username.
++		 */
++		snprintf(file, sizeof(file), "%s%s%s", k5login_directory, 
++			k5login_directory[strlen(k5login_directory)-1] != '/' ? "/" : "",
++			pw->pw_name);
++	}
++	debug("%s: Checking existence of file %s", __func__, file);
+ 
+-	snprintf(file, sizeof(file), "%s/.k5login", pw->pw_dir);
+ 	return access(file, F_OK) == 0;
+ }
+ 
+diff --git a/sshd.8 b/sshd.8
+index 5c4f15b..135e290 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -806,6 +806,10 @@ rlogin/rsh.
+ These files enforce GSSAPI/Kerberos authentication access control.
+ Further details are described in
+ .Xr ksu 1 .
++The location of the k5login file depends on the configuration option
++.Cm k5login_directory
++in the
++.Xr krb5.conf 5 .
+ .Pp
+ .It Pa ~/.ssh/
+ This directory is the default location for all user-specific configuration
diff --git a/openssh-7.2p2-s390-closefrom.patch b/openssh-7.2p2-s390-closefrom.patch
new file mode 100644
index 0000000..363538c
--- /dev/null
+++ b/openssh-7.2p2-s390-closefrom.patch
@@ -0,0 +1,52 @@
+Zseries only: Leave the hardware filedescriptors open.
+
+All filedescriptors above 2 are getting closed when a new
+sshd process to handle a new client connection is
+spawned. As the process also chroot into an empty filesystem
+without any device nodes, there is no chance to reopen the
+files. This patch filters out the reqired fds in the
+closefrom function so these are skipped in the close loop.
+
+Author: Harald Freudenberger <freude@de.ibm.com>
+
+---
+ openbsd-compat/bsd-closefrom.c |   26 ++++++++++++++++++++++++++
+ 1 file changed, 26 insertions(+)
+
+--- a/openbsd-compat/bsd-closefrom.c
++++ b/openbsd-compat/bsd-closefrom.c
+@@ -82,7 +82,33 @@ closefrom(int lowfd)
+ 	    fd = strtol(dent->d_name, &endp, 10);
+ 	    if (dent->d_name != endp && *endp == '\0' &&
+ 		fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp))
++#ifdef __s390__
++		{
++		    /*
++		     * the filedescriptors used to communicate with
++		     * the device drivers to provide hardware support
++		     * should survive. HF <freude@de.ibm.com>
++		     */
++		    char fpath[PATH_MAX], lpath[PATH_MAX];
++		    len = snprintf(fpath, sizeof(fpath), "%s/%s",
++				   fdpath, dent->d_name);
++		    if (len > 0 && (size_t)len <= sizeof(fpath)) {
++			len = readlink(fpath, lpath, sizeof(lpath));
++			if (len > 0) {
++			    lpath[len] = 0;
++			    if (strstr(lpath, "dev/z90crypt")
++				|| strstr(lpath, "dev/zcrypt")
++				|| strstr(lpath, "dev/prandom")
++				|| strstr(lpath, "dev/shm/icastats"))
++				fd = -1;
++			}
++		    }
++		    if (fd >= 0)
++			(void) close((int) fd);
++		}
++#else
+ 		(void) close((int) fd);
++#endif
+ 	}
+ 	(void) closedir(dirp);
+ 	return;
+
diff --git a/openssh-7.2p2-x11.patch b/openssh-7.2p2-x11.patch
new file mode 100644
index 0000000..0a19ecb
--- /dev/null
+++ b/openssh-7.2p2-x11.patch
@@ -0,0 +1,53 @@
+diff -up openssh-7.2p2/channels.c.x11 openssh-7.2p2/channels.c
+--- openssh-7.2p2/channels.c.x11	2016-03-09 19:04:48.000000000 +0100
++++ openssh-7.2p2/channels.c	2016-06-03 10:42:04.775164520 +0200
+@@ -3990,21 +3990,24 @@ x11_create_display_inet(int x11_display_
+ }
+ 
+ static int
+-connect_local_xsocket_path(const char *pathname)
++connect_local_xsocket_path(const char *pathname, int len)
+ {
+ 	int sock;
+ 	struct sockaddr_un addr;
+ 
++	if (len <= 0)
++		return -1;
+ 	sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ 	if (sock == -1)
+ 		error("socket: %.100s", strerror(errno));
+ 	memset(&addr, 0, sizeof(addr));
+ 	addr.sun_family = AF_UNIX;
+-	strlcpy(addr.sun_path, pathname, sizeof addr.sun_path);
+-	if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
++	if (len > sizeof addr.sun_path)
++		len = sizeof addr.sun_path;
++	memcpy(addr.sun_path, pathname, len);
++	if (connect(sock, (struct sockaddr *)&addr, sizeof addr - (sizeof addr.sun_path - len) ) == 0)
+ 		return sock;
+ 	close(sock);
+-	error("connect %.100s: %.100s", addr.sun_path, strerror(errno));
+ 	return -1;
+ }
+ 
+@@ -4012,8 +4015,18 @@ static int
+ connect_local_xsocket(u_int dnr)
+ {
+ 	char buf[1024];
+-	snprintf(buf, sizeof buf, _PATH_UNIX_X, dnr);
+-	return connect_local_xsocket_path(buf);
++	int len, ret;
++	len = snprintf(buf + 1, sizeof (buf) - 1, _PATH_UNIX_X, dnr);
++#ifdef linux
++	/* try abstract socket first */
++	buf[0] = '\0';
++	if ((ret = connect_local_xsocket_path(buf, len + 1)) >= 0)
++		return ret;
++#endif
++	if ((ret = connect_local_xsocket_path(buf + 1, len)) >= 0)
++		return ret;
++	error("connect %.100s: %.100s", buf + 1, strerror(errno));
++	return -1;
+ }
+ 
+ #ifdef __APPLE__
diff --git a/openssh-7.3p1-x11-max-displays.patch b/openssh-7.3p1-x11-max-displays.patch
new file mode 100644
index 0000000..c8a147b
--- /dev/null
+++ b/openssh-7.3p1-x11-max-displays.patch
@@ -0,0 +1,213 @@
+diff -up openssh-7.4p1/channels.c.x11max openssh-7.4p1/channels.c
+--- openssh-7.4p1/channels.c.x11max	2016-12-23 15:46:32.071506625 +0100
++++ openssh-7.4p1/channels.c	2016-12-23 15:46:32.139506636 +0100
+@@ -152,8 +152,8 @@ static int all_opens_permitted = 0;
+ #define FWD_PERMIT_ANY_HOST	"*"
+ 
+ /* -- X11 forwarding */
+-/* Maximum number of fake X11 displays to try. */
+-#define MAX_DISPLAYS  1000
++/* Minimum port number for X11 forwarding */
++#define X11_PORT_MIN 6000
+ 
+ /* Per-channel callback for pre/post select() actions */
+ typedef void chan_fn(struct ssh *, Channel *c,
+@@ -4228,7 +4228,7 @@ channel_send_window_changes(void)
+  */
+ 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;
+@@ -4240,10 +4241,15 @@ x11_create_display_inet(int x11_display_
+ 	if (chanids == NULL)
+ 		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 < MAX_DISPLAYS;
++	    display_number < x11_max_displays;
+ 	    display_number++) {
+-		port = 6000 + display_number;
++		port = X11_PORT_MIN + display_number;
++		if (port < X11_PORT_MIN) /* overflow */
++			break;
+ 		memset(&hints, 0, sizeof(hints));
+ 		hints.ai_family = ssh->chanctxt->IPv4or6;
+ 		hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE;
+@@ -4295,7 +4301,7 @@ x11_create_display_inet(int x11_display_
+ 		if (num_socks > 0)
+ 			break;
+ 	}
+-	if (display_number >= MAX_DISPLAYS) {
++	if (display_number >= x11_max_displays || port < X11_PORT_MIN ) {
+ 		error("Failed to allocate internet-domain X11 display socket.");
+ 		return -1;
+ 	}
+@@ -4441,7 +4447,7 @@ x11_connect_display(void)
+ 	memset(&hints, 0, sizeof(hints));
+ 	hints.ai_family = ssh->chanctxt->IPv4or6;
+ 	hints.ai_socktype = SOCK_STREAM;
+-	snprintf(strport, sizeof strport, "%u", 6000 + display_number);
++	snprintf(strport, sizeof strport, "%u", X11_PORT_MIN + display_number);
+ 	if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) {
+ 		error("%.100s: unknown host. (%s)", buf,
+ 		ssh_gai_strerror(gaierr));
+@@ -4457,7 +4463,7 @@ x11_connect_display(void)
+ 		/* Connect it to the display. */
+ 		if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+ 			debug2("connect %.100s port %u: %.100s", buf,
+-			    6000 + display_number, strerror(errno));
++			    X11_PORT_MIN + display_number, strerror(errno));
+ 			close(sock);
+ 			continue;
+ 		}
+@@ -4466,8 +4472,8 @@ x11_connect_display(void)
+ 	}
+ 	freeaddrinfo(aitop);
+ 	if (!ai) {
+-		error("connect %.100s port %u: %.100s", buf,
+-		    6000 + display_number, strerror(errno));
++		error("connect %.100s port %u: %.100s", buf,
++		    X11_PORT_MIN + display_number, strerror(errno));
+ 		return -1;
+ 	}
+ 	set_nodelay(sock);
+diff -up openssh-7.4p1/channels.h.x11max openssh-7.4p1/channels.h
+--- openssh-7.4p1/channels.h.x11max	2016-12-19 05:59:41.000000000 +0100
++++ openssh-7.4p1/channels.h	2016-12-23 15:46:32.139506636 +0100
+@@ -293,7 +293,7 @@ int	 permitopen_port(const char *);
+ 
+ void	 channel_set_x11_refuse_time(struct ssh *, u_int);
+ 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);
+ 
+diff -up openssh-7.4p1/servconf.c.x11max openssh-7.4p1/servconf.c
+--- openssh-7.4p1/servconf.c.x11max	2016-12-23 15:46:32.133506635 +0100
++++ openssh-7.4p1/servconf.c	2016-12-23 15:47:27.320519121 +0100
+@@ -95,6 +95,7 @@ initialize_server_options(ServerOptions
+ 	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;
+@@ -243,6 +244,8 @@ fill_default_server_options(ServerOption
+ 		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)
+@@ -419,7 +422,7 @@ typedef enum {
+ 	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,
+@@ -540,6 +543,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 },
+@@ -1316,6 +1320,10 @@ process_server_config_line(ServerOptions
+ 			*intptr = value;
+ 		break;
+ 
++	case sX11MaxDisplays:
++		intptr = &options->x11_max_displays;
++		goto parse_int;
++
+ 	case sX11UseLocalhost:
+ 		intptr = &options->x11_use_localhost;
+ 		goto parse_flag;
+@@ -2063,6 +2071,7 @@ copy_set_server_options(ServerOptions *d
+ 	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);
+@@ -2315,6 +2324,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 -up openssh-7.4p1/servconf.h.x11max openssh-7.4p1/servconf.h
+--- openssh-7.4p1/servconf.h.x11max	2016-12-23 15:46:32.133506635 +0100
++++ openssh-7.4p1/servconf.h	2016-12-23 15:46:32.140506636 +0100
+@@ -55,6 +55,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"
+@@ -85,6 +86,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 -up openssh-7.4p1/session.c.x11max openssh-7.4p1/session.c
+--- openssh-7.4p1/session.c.x11max	2016-12-23 15:46:32.136506636 +0100
++++ openssh-7.4p1/session.c	2016-12-23 15:46:32.141506636 +0100
+@@ -2518,8 +2518,9 @@ session_setup_x11fwd(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 -up openssh-7.4p1/sshd_config.5.x11max openssh-7.4p1/sshd_config.5
+--- openssh-7.4p1/sshd_config.5.x11max	2016-12-23 15:46:32.134506635 +0100
++++ openssh-7.4p1/sshd_config.5	2016-12-23 15:46:32.141506636 +0100
+@@ -1133,6 +1133,7 @@ Available keywords are
+ .Cm StreamLocalBindUnlink ,
+ .Cm TrustedUserCAKeys ,
+ .Cm X11DisplayOffset ,
++.Cm X11MaxDisplays ,
+ .Cm X11Forwarding
+ and
+ .Cm X11UseLocalhost .
+@@ -1566,6 +1567,12 @@ Specifies the first display number avail
+ 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
diff --git a/openssh-7.4p1-systemd.patch b/openssh-7.4p1-systemd.patch
new file mode 100644
index 0000000..4f9e58a
--- /dev/null
+++ b/openssh-7.4p1-systemd.patch
@@ -0,0 +1,98 @@
+commit 0e22b79bfde45a7cf7a2e51a68ec11c4285f3b31
+Author: Jakub Jelen <jjelen@redhat.com>
+Date:   Mon Nov 21 15:04:06 2016 +0100
+
+    systemd stuff
+
+diff --git a/configure.ac b/configure.ac
+index 2ffc369..162ce92 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -4265,6 +4265,30 @@ AC_ARG_WITH([kerberos5],
+ AC_SUBST([GSSLIBS])
+ AC_SUBST([K5LIBS])
+ 
++# Check whether user wants systemd support
++SYSTEMD_MSG="no"
++AC_ARG_WITH(systemd,
++	[  --with-systemd          Enable systemd support],
++	[ if test "x$withval" != "xno" ; then
++		AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
++		if test "$PKGCONFIG" != "no"; then
++			AC_MSG_CHECKING([for libsystemd])
++			if $PKGCONFIG --exists libsystemd; then
++				SYSTEMD_CFLAGS=`$PKGCONFIG --cflags libsystemd`
++				SYSTEMD_LIBS=`$PKGCONFIG --libs libsystemd`
++				CPPFLAGS="$CPPFLAGS $SYSTEMD_CFLAGS"
++				SSHDLIBS="$SSHDLIBS $SYSTEMD_LIBS"
++				AC_MSG_RESULT([yes])
++				AC_DEFINE(HAVE_SYSTEMD, 1, [Define if you want systemd support.])
++				SYSTEMD_MSG="yes"
++			else
++				AC_MSG_RESULT([no])
++			fi
++		fi
++	fi ]
++)
++
++
+ # Looking for programs, paths and files
+ 
+ PRIVSEP_PATH=/var/empty
+@@ -5097,6 +5121,7 @@ echo "                   libedit support: $LIBEDIT_MSG"
+ echo "  Solaris process contract support: $SPC_MSG"
+ echo "           Solaris project support: $SP_MSG"
+ echo "         Solaris privilege support: $SPP_MSG"
++echo "                   systemd support: $SYSTEMD_MSG"
+ echo "       IP address in \$DISPLAY hack: $DISPLAY_HACK_MSG"
+ echo "           Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG"
+ echo "                  BSD Auth support: $BSD_AUTH_MSG"
+diff --git a/contrib/sshd.service b/contrib/sshd.service
+new file mode 100644
+index 0000000..e0d4923
+--- /dev/null
++++ b/contrib/sshd.service
+@@ -0,0 +1,16 @@
++[Unit]
++Description=OpenSSH server daemon
++Documentation=man:sshd(8) man:sshd_config(5)
++After=network.target
++
++[Service]
++Type=notify
++ExecStart=/usr/sbin/sshd -D $OPTIONS
++ExecReload=/bin/kill -HUP $MAINPID
++KillMode=process
++Restart=on-failure
++RestartPreventExitStatus=255
++
++[Install]
++WantedBy=multi-user.target
++
+diff --git a/sshd.c b/sshd.c
+index 816611c..b8b9d13 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -85,6 +85,10 @@
+ #include <prot.h>
+ #endif
+ 
++#ifdef HAVE_SYSTEMD
++#include <systemd/sd-daemon.h>
++#endif
++
+ #include "xmalloc.h"
+ #include "ssh.h"
+ #include "ssh2.h"
+@@ -1888,6 +1892,11 @@ main(int ac, char **av)
+ 			}
+ 		}
+ 
++#ifdef HAVE_SYSTEMD
++		/* Signal systemd that we are ready to accept connections */
++		sd_notify(0, "READY=1");
++#endif
++
+ 		/* Accept a connection and return in a forked child */
+ 		server_accept_loop(&sock_in, &sock_out,
+ 		    &newsock, config_s);
diff --git a/openssh-7.5p1-sandbox.patch b/openssh-7.5p1-sandbox.patch
new file mode 100644
index 0000000..7217c64
--- /dev/null
+++ b/openssh-7.5p1-sandbox.patch
@@ -0,0 +1,86 @@
+In order to use the OpenSSL-ibmpkcs11 engine it is needed to allow flock
+and ipc calls, because this engine calls OpenCryptoki (a PKCS#11
+implementation) which calls the libraries that will communicate with the
+crypto cards. OpenCryptoki makes use of flock and ipc and, as of now,
+this is only need on s390 architecture.
+
+Signed-off-by: Eduardo Barretto <ebarretto@xxxxxxxxxxxxxxxxxx>
+---
+ sandbox-seccomp-filter.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
+index ca75cc7..6e7de31 100644
+--- a/sandbox-seccomp-filter.c
++++ b/sandbox-seccomp-filter.c
+@@ -166,6 +166,9 @@ static const struct sock_filter preauth_insns[] = {
+ #ifdef __NR_exit_group
+ 	SC_ALLOW(__NR_exit_group),
+ #endif
++#if defined(__NR_flock) && defined(__s390__)
++	SC_ALLOW(__NR_flock),
++#endif
+ #ifdef __NR_futex
+ 	SC_ALLOW(__NR_futex),
+ #endif
+@@ -178,6 +181,9 @@ static const struct sock_filter preauth_insns[] = {
+ #ifdef __NR_gettimeofday
+ 	SC_ALLOW(__NR_gettimeofday),
+ #endif
++#if defined(__NR_ipc) && defined(__s390__)
++	SC_ALLOW(__NR_ipc),
++#endif
+ #ifdef __NR_getuid
+ 	SC_ALLOW(__NR_getuid),
+ #endif
+-- 
+1.9.1
+
+getuid and geteuid are needed when using an openssl engine that calls a
+crypto card, e.g. ICA (libica).
+Those syscalls are also needed by the distros for audit code.
+
+Signed-off-by: Eduardo Barretto <ebarretto@xxxxxxxxxxxxxxxxxx>
+---
+ sandbox-seccomp-filter.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
+index 6e7de31..e86aa2c 100644
+--- a/sandbox-seccomp-filter.c
++++ b/sandbox-seccomp-filter.c
+@@ -175,6 +175,18 @@ static const struct sock_filter preauth_insns[] = {
+ #ifdef __NR_getpid
+ 	SC_ALLOW(__NR_getpid),
+ #endif
++#ifdef __NR_getuid
++	SC_ALLOW(__NR_getuid),
++#endif
++#ifdef __NR_getuid32
++	SC_ALLOW(__NR_getuid32),
++#endif
++#ifdef __NR_geteuid
++	SC_ALLOW(__NR_geteuid),
++#endif
++#ifdef __NR_geteuid32
++	SC_ALLOW(__NR_geteuid32),
++#endif
+ #ifdef __NR_getrandom
+ 	SC_ALLOW(__NR_getrandom),
+ #endif
+-- 1.9.1
+1.9.1
+diff -up openssh-7.6p1/sandbox-seccomp-filter.c.sandbox openssh-7.6p1/sandbox-seccomp-filter.c
+--- openssh-7.6p1/sandbox-seccomp-filter.c.sandbox	2017-12-12 13:59:30.563874059 +0100
++++ openssh-7.6p1/sandbox-seccomp-filter.c	2017-12-12 13:59:14.842784083 +0100
+@@ -190,6 +190,9 @@ static const struct sock_filter preauth_
+ #ifdef __NR_geteuid32
+ 	SC_ALLOW(__NR_geteuid32),
+ #endif
++#ifdef __NR_gettid
++	SC_ALLOW(__NR_gettid),
++#endif
+ #ifdef __NR_getrandom
+ 	SC_ALLOW(__NR_getrandom),
+ #endif
+
diff --git a/openssh-7.6p1-audit.patch b/openssh-7.6p1-audit.patch
new file mode 100644
index 0000000..35a1a8a
--- /dev/null
+++ b/openssh-7.6p1-audit.patch
@@ -0,0 +1,2326 @@
+diff -up openssh/audit-bsm.c.audit openssh/audit-bsm.c
+--- openssh/audit-bsm.c.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/audit-bsm.c	2019-04-03 17:02:20.713886041 +0200
+@@ -372,13 +372,26 @@ audit_connection_from(const char *host,
+ #endif
+ }
+ 
++int
++audit_run_command(struct ssh *ssh, const char *command)
++{
++	/* not implemented */
++	return 0;
++}
++
+ void
+-audit_run_command(const char *command)
++audit_end_command(struct ssh *ssh, int handle, const char *command)
+ {
+ 	/* not implemented */
+ }
+ 
+ void
++audit_count_session_open(void)
++{
++	/* not necessary */
++}
++
++void
+ audit_session_open(struct logininfo *li)
+ {
+ 	/* not implemented */
+@@ -390,6 +403,12 @@ audit_session_close(struct logininfo *li
+ 	/* not implemented */
+ }
+ 
++int
++audit_keyusage(struct ssh *ssh, int host_user, char *fp, int rv)
++{
++	/* not implemented */
++}
++
+ void
+ audit_event(struct ssh *ssh, ssh_audit_event_t event)
+ {
+@@ -451,4 +470,28 @@ audit_event(struct ssh *ssh, ssh_audit_e
+ 		debug("%s: unhandled event %d", __func__, event);
+ 	}
+ }
++
++void
++audit_unsupported_body(struct ssh *ssh, int what)
++{
++	/* not implemented */
++}
++
++void
++audit_kex_body(struct ssh *ssh, int ctos, char *enc, char *mac, char *compress, char *pfs, pid_t pid, uid_t uid)
++{
++	/* not implemented */
++}
++
++void
++audit_session_key_free_body(struct ssh * ssh, int ctos, pid_t pid, uid_t uid)
++{
++	/* not implemented */
++}
++
++void
++audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_t uid)
++{
++	/* not implemented */
++}
+ #endif /* BSM */
+diff -up openssh/audit.c.audit openssh/audit.c
+--- openssh/audit.c.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/audit.c	2019-04-03 17:02:20.713886041 +0200
+@@ -34,6 +34,12 @@
+ #include "log.h"
+ #include "hostfile.h"
+ #include "auth.h"
++#include "ssh-gss.h"
++#include "monitor_wrap.h"
++#include "xmalloc.h"
++#include "misc.h"
++#include "servconf.h"
++#include "ssherr.h"
+ 
+ /*
+  * Care must be taken when using this since it WILL NOT be initialized when
+@@ -41,6 +47,7 @@
+  * audit_event(CONNECTION_ABANDON) is called.  Test for NULL before using.
+  */
+ extern Authctxt *the_authctxt;
++extern ServerOptions options;
+ 
+ /* Maybe add the audit class to struct Authmethod? */
+ ssh_audit_event_t
+@@ -69,13 +76,10 @@ audit_classify_auth(const char *method)
+ const char *
+ audit_username(void)
+ {
+-	static const char unknownuser[] = "(unknown user)";
+-	static const char invaliduser[] = "(invalid user)";
++	static const char unknownuser[] = "(unknown)";
+ 
+-	if (the_authctxt == NULL || the_authctxt->user == NULL)
++	if (the_authctxt == NULL || the_authctxt->user == NULL || !the_authctxt->valid)
+ 		return (unknownuser);
+-	if (!the_authctxt->valid)
+-		return (invaliduser);
+ 	return (the_authctxt->user);
+ }
+ 
+@@ -109,6 +113,35 @@ audit_event_lookup(ssh_audit_event_t ev)
+ 	return(event_lookup[i].name);
+ }
+ 
++void
++audit_key(struct ssh *ssh, int host_user, int *rv, const struct sshkey *key)
++{
++	char *fp;
++
++	fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_HEX);
++	if (audit_keyusage(ssh, host_user, fp, (*rv == 0)) == 0)
++		*rv = -SSH_ERR_INTERNAL_ERROR;
++	free(fp);
++}
++
++void
++audit_unsupported(struct ssh *ssh, int what)
++{
++	PRIVSEP(audit_unsupported_body(ssh, what));
++}
++
++void
++audit_kex(struct ssh *ssh, int ctos, char *enc, char *mac, char *comp, char *pfs)
++{
++	PRIVSEP(audit_kex_body(ssh, ctos, enc, mac, comp, pfs, getpid(), getuid()));
++}
++
++void
++audit_session_key_free(struct ssh *ssh, int ctos)
++{
++	PRIVSEP(audit_session_key_free_body(ssh, ctos, getpid(), getuid()));
++}
++
+ # ifndef CUSTOM_SSH_AUDIT_EVENTS
+ /*
+  * Null implementations of audit functions.
+@@ -138,6 +171,17 @@ audit_event(struct ssh *ssh, ssh_audit_e
+ }
+ 
+ /*
++ * Called when a child process has called, or will soon call,
++ * audit_session_open.
++ */
++void
++audit_count_session_open(void)
++{
++	debug("audit count session open euid %d user %s", geteuid(),
++	      audit_username());
++}
++
++/*
+  * Called when a user session is started.  Argument is the tty allocated to
+  * the session, or NULL if no tty was allocated.
+  *
+@@ -172,13 +216,82 @@ audit_session_close(struct logininfo *li
+ /*
+  * This will be called when a user runs a non-interactive command.  Note that
+  * it may be called multiple times for a single connection since SSH2 allows
+- * multiple sessions within a single connection.
++ * multiple sessions within a single connection.  Returns a "handle" for
++ * audit_end_command.
+  */
+-void
+-audit_run_command(const char *command)
++int
++audit_run_command(struct ssh *ssh, const char *command)
+ {
+ 	debug("audit run command euid %d user %s command '%.200s'", geteuid(),
+ 	    audit_username(), command);
++	return 0;
++}
++
++/*
++ * This will be called when the non-interactive command finishes.  Note that
++ * it may be called multiple times for a single connection since SSH2 allows
++ * multiple sessions within a single connection.  "handle" should come from
++ * the corresponding audit_run_command.
++ */
++void
++audit_end_command(struct ssh *ssh, int handle, const char *command)
++{
++	debug("audit end nopty exec  euid %d user %s command '%.200s'", geteuid(),
++	    audit_username(), command);
++}
++
++/*
++ * This will be called when user is successfully autherized by the RSA1/RSA/DSA key.
++ *
++ * Type is the key type, len is the key length(byte) and fp is the fingerprint of the key.
++ */
++int
++audit_keyusage(struct ssh *ssh, int host_user, char *fp, int rv)
++{
++	debug("audit %s key usage euid %d user %s fingerprint %s, result %d",
++		host_user ? "pubkey" : "hostbased", geteuid(), audit_username(),
++		fp, rv);
++}
++
++/*
++ * This will be called when the protocol negotiation fails.
++ */
++void
++audit_unsupported_body(struct ssh *ssh, int what)
++{
++	debug("audit unsupported protocol euid %d type %d", geteuid(), what);
++}
++
++/*
++ * This will be called on succesfull protocol negotiation.
++ */
++void
++audit_kex_body(struct ssh *ssh, int ctos, char *enc, char *mac, char *compress, char *pfs, pid_t pid,
++	       uid_t uid)
++{
++	debug("audit protocol negotiation euid %d direction %d cipher %s mac %s compresion %s pfs %s from pid %ld uid %u",
++		(unsigned)geteuid(), ctos, enc, mac, compress, pfs, (long)pid,
++	        (unsigned)uid);
++}
++
++/*
++ * This will be called on succesfull session key discard
++ */
++void
++audit_session_key_free_body(struct ssh *, int ctos, pid_t pid, uid_t uid)
++{
++	debug("audit session key discard euid %u direction %d from pid %ld uid %u",
++		(unsigned)geteuid(), ctos, (long)pid, (unsigned)uid);
++}
++
++/*
++ * This will be called on destroy private part of the server key
++ */
++void
++audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_t uid)
++{
++	debug("audit destroy sensitive data euid %d fingerprint %s from pid %ld uid %u",
++		geteuid(), fp, (long)pid, (unsigned)uid);
+ }
+ # endif  /* !defined CUSTOM_SSH_AUDIT_EVENTS */
+ #endif /* SSH_AUDIT_EVENTS */
+diff -up openssh/audit.h.audit openssh/audit.h
+--- openssh/audit.h.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/audit.h	2019-04-03 17:02:20.713886041 +0200
+@@ -26,6 +26,7 @@
+ # define _SSH_AUDIT_H
+ 
+ #include "loginrec.h"
++#include "sshkey.h"
+ 
+ struct ssh;
+ 
+@@ -45,13 +46,32 @@ enum ssh_audit_event_type {
+ 	SSH_CONNECTION_ABANDON,	/* closed without completing auth */
+ 	SSH_AUDIT_UNKNOWN
+ };
++
++enum ssh_audit_kex {
++	SSH_AUDIT_UNSUPPORTED_CIPHER,
++	SSH_AUDIT_UNSUPPORTED_MAC,
++	SSH_AUDIT_UNSUPPORTED_COMPRESSION
++};
+ typedef enum ssh_audit_event_type ssh_audit_event_t;
+ 
++int	listening_for_clients(void);
++
+ void	audit_connection_from(const char *, int);
+ void	audit_event(struct ssh *, ssh_audit_event_t);
++void	audit_count_session_open(void);
+ void	audit_session_open(struct logininfo *);
+ void	audit_session_close(struct logininfo *);
+-void	audit_run_command(const char *);
++int	audit_run_command(struct ssh *, const char *);
++void 	audit_end_command(struct ssh *, int, const char *);
+ ssh_audit_event_t audit_classify_auth(const char *);
++int	audit_keyusage(struct ssh *, int, char *, int);
++void	audit_key(struct ssh *, int, int *, const struct sshkey *);
++void	audit_unsupported(struct ssh *, int);
++void	audit_kex(struct ssh *, int, char *, char *, char *, char *);
++void	audit_unsupported_body(struct ssh *, int);
++void	audit_kex_body(struct ssh *, int, char *, char *, char *, char *, pid_t, uid_t);
++void	audit_session_key_free(struct ssh *, int ctos);
++void	audit_session_key_free_body(struct ssh *, int ctos, pid_t, uid_t);
++void	audit_destroy_sensitive_data(struct ssh *, const char *, pid_t, uid_t);
+ 
+ #endif /* _SSH_AUDIT_H */
+diff -up openssh/audit-linux.c.audit openssh/audit-linux.c
+--- openssh/audit-linux.c.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/audit-linux.c	2019-04-03 17:02:20.713886041 +0200
+@@ -33,27 +33,40 @@
+ 
+ #include "log.h"
+ #include "audit.h"
++#include "sshkey.h"
++#include "hostfile.h"
++#include "auth.h"
++#include "misc.h"      /* servconf.h needs misc.h for struct ForwardOptions */
++#include "servconf.h"
+ #include "canohost.h"
+ #include "packet.h"
+-
++#include "cipher.h"
++#include "channels.h"
++#include "session.h"
++
++#define AUDIT_LOG_SIZE 256
++
++extern ServerOptions options;
++extern Authctxt *the_authctxt;
++extern u_int utmp_len;
+ const char *audit_username(void);
+ 
+-int
+-linux_audit_record_event(int uid, const char *username, const char *hostname,
+-    const char *ip, const char *ttyn, int success)
++static void
++linux_audit_user_logxxx(int uid, const char *username,
++    const char *ip, const char *ttyn, int success, int event)
+ {
+ 	int audit_fd, rc, saved_errno;
+ 
+ 	if ((audit_fd = audit_open()) < 0) {
+ 		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
+ 		    errno == EAFNOSUPPORT)
+-			return 1; /* No audit support in kernel */
++			return; /* No audit support in kernel */
+ 		else
+-			return 0; /* Must prevent login */
++			goto fatal_report; /* Must prevent login */
+ 	}
+-	rc = audit_log_acct_message(audit_fd, AUDIT_USER_LOGIN,
++	rc = audit_log_acct_message(audit_fd, event,
+ 	    NULL, "login", username ? username : "(unknown)",
+-	    username == NULL ? uid : -1, hostname, ip, ttyn, success);
++	    username == NULL ? uid : -1, NULL, ip, ttyn, success);
+ 	saved_errno = errno;
+ 	close(audit_fd);
+ 
+@@ -65,9 +78,96 @@ linux_audit_record_event(int uid, const
+ 		rc = 0;
+ 	errno = saved_errno;
+ 
+-	return rc >= 0;
++	if (rc < 0) {
++fatal_report:
++		fatal("linux_audit_write_entry failed: %s", strerror(errno));
++	}
++}
++
++static void
++linux_audit_user_auth(int uid, const char *username,
++    const char *ip, const char *ttyn, int success, int event)
++{
++	int audit_fd, rc, saved_errno;
++	static const char *event_name[] = {
++		"maxtries exceeded",
++		"root denied",
++		"success",
++		"none",
++		"password",
++		"challenge-response",
++		"pubkey",
++		"hostbased",
++		"gssapi",
++		"invalid user",
++		"nologin",
++		"connection closed",
++		"connection abandoned",
++		"unknown"
++	};
++
++	audit_fd = audit_open();
++	if (audit_fd < 0) {
++		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
++		    errno == EAFNOSUPPORT)
++			return; /* No audit support in kernel */
++		else
++			goto fatal_report; /* Must prevent login */
++	}
++
++	if ((event < 0) || (event > SSH_AUDIT_UNKNOWN))
++		event = SSH_AUDIT_UNKNOWN;
++
++	rc = audit_log_acct_message(audit_fd, AUDIT_USER_AUTH,
++	    NULL, event_name[event], username ? username : "(unknown)",
++	    username == NULL ? uid : -1, NULL, ip, ttyn, success);
++	saved_errno = errno;
++	close(audit_fd);
++	/*
++	 * Do not report error if the error is EPERM and sshd is run as non
++	 * root user.
++	 */
++	if ((rc == -EPERM) && (geteuid() != 0))
++		rc = 0;
++	errno = saved_errno;
++	if (rc < 0) {
++fatal_report:
++		fatal("linux_audit_write_entry failed: %s", strerror(errno));
++	}
++}
++
++int
++audit_keyusage(struct ssh *ssh, int host_user, char *fp, int rv)
++{
++	char buf[AUDIT_LOG_SIZE];
++	int audit_fd, rc, saved_errno;
++
++	audit_fd = audit_open();
++	if (audit_fd < 0) {
++		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
++					 errno == EAFNOSUPPORT)
++			return 1; /* No audit support in kernel */
++		else
++			return 0; /* Must prevent login */
++	}
++	snprintf(buf, sizeof(buf), "%s_auth grantors=auth-key", host_user ? "pubkey" : "hostbased");
++	rc = audit_log_acct_message(audit_fd, AUDIT_USER_AUTH, NULL,
++		buf, audit_username(), -1, NULL, ssh_remote_ipaddr(ssh), NULL, rv);
++	if ((rc < 0) && ((rc != -1) || (getuid() == 0)))
++		goto out;
++	snprintf(buf, sizeof(buf), "op=negotiate kind=auth-key fp=%s", fp);
++	rc = audit_log_user_message(audit_fd, AUDIT_CRYPTO_KEY_USER, buf, NULL,
++		ssh_remote_ipaddr(ssh), NULL, rv);
++out:
++	saved_errno = errno;
++	audit_close(audit_fd);
++	errno = saved_errno;
++	/* do not report error if the error is EPERM and sshd is run as non root user */
++	return (rc >= 0) || ((rc == -EPERM) && (getuid() != 0));
+ }
+ 
++static int user_login_count = 0;
++
+ /* Below is the sshd audit API code */
+ 
+ void
+@@ -76,49 +176,210 @@ audit_connection_from(const char *host,
+ 	/* not implemented */
+ }
+ 
++int
++audit_run_command(struct ssh *ssh, const char *command)
++{
++	if (!user_login_count++)
++		linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++		    ssh_remote_ipaddr(ssh),
++		    "ssh", 1, AUDIT_USER_LOGIN);
++	linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++	    ssh_remote_ipaddr(ssh),
++	    "ssh", 1, AUDIT_USER_START);
++	return 0;
++}
++
+ void
+-audit_run_command(const char *command)
++audit_end_command(struct ssh *ssh, int handle, const char *command)
+ {
+-	/* not implemented */
++	linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++	    ssh_remote_ipaddr(ssh),
++	    "ssh", 1, AUDIT_USER_END);
++	if (user_login_count && !--user_login_count)
++		linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++		    ssh_remote_ipaddr(ssh),
++		    "ssh", 1, AUDIT_USER_LOGOUT);
++}
++
++void
++audit_count_session_open(void)
++{
++	user_login_count++;
+ }
+ 
+ void
+ audit_session_open(struct logininfo *li)
+ {
+-	if (linux_audit_record_event(li->uid, NULL, li->hostname, NULL,
+-	    li->line, 1) == 0)
+-		fatal("linux_audit_write_entry failed: %s", strerror(errno));
++	if (!user_login_count++)
++		linux_audit_user_logxxx(li->uid, NULL, li->hostname,
++		    li->line, 1, AUDIT_USER_LOGIN);
++	linux_audit_user_logxxx(li->uid, NULL, li->hostname,
++	    li->line, 1, AUDIT_USER_START);
+ }
+ 
+ void
+ audit_session_close(struct logininfo *li)
+ {
+-	/* not implemented */
++	linux_audit_user_logxxx(li->uid, NULL, li->hostname,
++	    li->line, 1, AUDIT_USER_END);
++	if (user_login_count && !--user_login_count)
++		linux_audit_user_logxxx(li->uid, NULL, li->hostname,
++		    li->line, 1, AUDIT_USER_LOGOUT);
+ }
+ 
+ void
+ audit_event(struct ssh *ssh, ssh_audit_event_t event)
+ {
+ 	switch(event) {
+-	case SSH_AUTH_SUCCESS:
+-	case SSH_CONNECTION_CLOSE:
+ 	case SSH_NOLOGIN:
+-	case SSH_LOGIN_EXCEED_MAXTRIES:
+ 	case SSH_LOGIN_ROOT_DENIED:
++		linux_audit_user_auth(-1, audit_username(),
++			ssh_remote_ipaddr(ssh), "ssh", 0, event);
++		linux_audit_user_logxxx(-1, audit_username(),
++			ssh_remote_ipaddr(ssh), "ssh", 0, AUDIT_USER_LOGIN);
+ 		break;
+-	case SSH_AUTH_FAIL_NONE:
+ 	case SSH_AUTH_FAIL_PASSWD:
++		if (options.use_pam)
++			break;
++	case SSH_LOGIN_EXCEED_MAXTRIES:
+ 	case SSH_AUTH_FAIL_KBDINT:
+ 	case SSH_AUTH_FAIL_PUBKEY:
+ 	case SSH_AUTH_FAIL_HOSTBASED:
+ 	case SSH_AUTH_FAIL_GSSAPI:
++		linux_audit_user_auth(-1, audit_username(),
++			ssh_remote_ipaddr(ssh), "ssh", 0, event);
++		break;
++
++	case SSH_CONNECTION_CLOSE:
++		if (user_login_count) {
++			while (user_login_count--)
++				linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++				    ssh_remote_ipaddr(ssh),
++				    "ssh", 1, AUDIT_USER_END);
++			linux_audit_user_logxxx(the_authctxt->pw->pw_uid, NULL,
++			    ssh_remote_ipaddr(ssh),
++			    "ssh", 1, AUDIT_USER_LOGOUT);
++		}
++		break;
++
++	case SSH_CONNECTION_ABANDON:
+ 	case SSH_INVALID_USER:
+-		linux_audit_record_event(-1, audit_username(), NULL,
+-		    ssh_remote_ipaddr(ssh), "sshd", 0);
++		linux_audit_user_logxxx(-1, audit_username(),
++			ssh_remote_ipaddr(ssh), "ssh", 0, AUDIT_USER_LOGIN);
+ 		break;
+ 	default:
+ 		debug("%s: unhandled event %d", __func__, event);
+ 		break;
+ 	}
+ }
++
++void
++audit_unsupported_body(struct ssh *ssh, int what)
++{
++#ifdef AUDIT_CRYPTO_SESSION
++	char buf[AUDIT_LOG_SIZE];
++	const static char *name[] = { "cipher", "mac", "comp" };
++	char *s;
++	int audit_fd;
++
++	snprintf(buf, sizeof(buf), "op=unsupported-%s direction=? cipher=? ksize=? rport=%d laddr=%s lport=%d ",
++		name[what], ssh_remote_port(ssh), (s = get_local_ipaddr(ssh_packet_get_connection_in(ssh))),
++		ssh_local_port(ssh));
++	free(s);
++	audit_fd = audit_open();
++	if (audit_fd < 0)
++		/* no problem, the next instruction will be fatal() */
++		return;
++	audit_log_user_message(audit_fd, AUDIT_CRYPTO_SESSION,
++			buf, NULL, ssh_remote_ipaddr(ssh), NULL, 0);
++	audit_close(audit_fd);
++#endif
++}
++
++const static char *direction[] = { "from-server", "from-client", "both" };
++
++void
++audit_kex_body(struct ssh *ssh, int ctos, char *enc, char *mac, char *compress,
++    char *pfs, pid_t pid, uid_t uid)
++{
++#ifdef AUDIT_CRYPTO_SESSION
++	char buf[AUDIT_LOG_SIZE];
++	int audit_fd, audit_ok;
++	const struct sshcipher *cipher = cipher_by_name(enc);
++	char *s;
++
++	snprintf(buf, sizeof(buf), "op=start direction=%s cipher=%s ksize=%d mac=%s pfs=%s spid=%jd suid=%jd rport=%d laddr=%s lport=%d ",
++		direction[ctos], enc, cipher ? 8 * cipher->key_len : 0, mac, pfs,
++		(intmax_t)pid, (intmax_t)uid,
++		ssh_remote_port(ssh), (s = get_local_ipaddr(ssh_packet_get_connection_in(ssh))), ssh_local_port(ssh));
++	free(s);
++	audit_fd = audit_open();
++	if (audit_fd < 0) {
++		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
++					 errno == EAFNOSUPPORT)
++			return; /* No audit support in kernel */
++		else
++			fatal("cannot open audit"); /* Must prevent login */
++	}
++	audit_ok = audit_log_user_message(audit_fd, AUDIT_CRYPTO_SESSION,
++			buf, NULL, ssh_remote_ipaddr(ssh), NULL, 1);
++	audit_close(audit_fd);
++	/* do not abort if the error is EPERM and sshd is run as non root user */
++	if ((audit_ok < 0) && ((audit_ok != -1) || (getuid() == 0)))
++		fatal("cannot write into audit"); /* Must prevent login */
++#endif
++}
++
++void
++audit_session_key_free_body(struct ssh *ssh, int ctos, pid_t pid, uid_t uid)
++{
++	char buf[AUDIT_LOG_SIZE];
++	int audit_fd, audit_ok;
++	char *s;
++
++	snprintf(buf, sizeof(buf), "op=destroy kind=session fp=? direction=%s spid=%jd suid=%jd rport=%d laddr=%s lport=%d ",
++		 direction[ctos], (intmax_t)pid, (intmax_t)uid,
++		 ssh_remote_port(ssh),
++		 (s = get_local_ipaddr(ssh_packet_get_connection_in(ssh))),
++		 ssh_local_port(ssh));
++	free(s);
++	audit_fd = audit_open();
++	if (audit_fd < 0) {
++		if (errno != EINVAL && errno != EPROTONOSUPPORT &&
++					 errno != EAFNOSUPPORT)
++			error("cannot open audit");
++		return;
++	}
++	audit_ok = audit_log_user_message(audit_fd, AUDIT_CRYPTO_KEY_USER,
++			buf, NULL, ssh_remote_ipaddr(ssh), NULL, 1);
++	audit_close(audit_fd);
++	/* do not abort if the error is EPERM and sshd is run as non root user */
++	if ((audit_ok < 0) && ((audit_ok != -1) || (getuid() == 0)))
++		error("cannot write into audit");
++}
++
++void
++audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_t uid)
++{
++	char buf[AUDIT_LOG_SIZE];
++	int audit_fd, audit_ok;
++
++	snprintf(buf, sizeof(buf), "op=destroy kind=server fp=%s direction=? spid=%jd suid=%jd ",
++		fp, (intmax_t)pid, (intmax_t)uid);
++	audit_fd = audit_open();
++	if (audit_fd < 0) {
++		if (errno != EINVAL && errno != EPROTONOSUPPORT &&
++					 errno != EAFNOSUPPORT)
++			error("cannot open audit");
++		return;
++	}
++	audit_ok = audit_log_user_message(audit_fd, AUDIT_CRYPTO_KEY_USER,
++			buf, NULL,
++			listening_for_clients() ? NULL : ssh_remote_ipaddr(ssh),
++			NULL, 1);
++	audit_close(audit_fd);
++	/* do not abort if the error is EPERM and sshd is run as non root user */
++	if ((audit_ok < 0) && ((audit_ok != -1) || (getuid() == 0)))
++		error("cannot write into audit");
++}
+ #endif /* USE_LINUX_AUDIT */
+diff -up openssh/auditstub.c.audit openssh/auditstub.c
+--- openssh/auditstub.c.audit	2019-04-03 17:02:20.714886050 +0200
++++ openssh/auditstub.c	2019-04-03 17:02:20.714886050 +0200
+@@ -0,0 +1,52 @@
++/* $Id: auditstub.c,v 1.1 jfch Exp $ */
++
++/*
++ * Copyright 2010 Red Hat, Inc.  All rights reserved.
++ * Use is subject to license terms.
++ *
++ * 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.
++ *
++ * Red Hat author: Jan F. Chadima <jchadima@redhat.com>
++ */
++
++#include <sys/types.h>
++
++struct ssh;
++
++void
++audit_unsupported(struct ssh *ssh, int n)
++{
++}
++
++void
++audit_kex(struct ssh *ssh, int ctos, char *enc, char *mac, char *comp, char *pfs)
++{
++}
++
++void
++audit_session_key_free(struct ssh *ssh, int ctos)
++{
++}
++
++void
++audit_session_key_free_body(struct ssh *ssh, int ctos, pid_t pid, uid_t uid)
++{
++}
+diff -up openssh/auth2.c.audit openssh/auth2.c
+--- openssh/auth2.c.audit	2019-04-03 17:02:20.651885453 +0200
++++ openssh/auth2.c	2019-04-03 17:02:20.714886050 +0200
+@@ -303,9 +303,6 @@ input_userauth_request(int type, u_int32
+ 		} else {
+ 			/* Invalid user, fake password information */
+ 			authctxt->pw = fakepw();
+-#ifdef SSH_AUDIT_EVENTS
+-			PRIVSEP(audit_event(ssh, SSH_INVALID_USER));
+-#endif
+ 		}
+ #ifdef USE_PAM
+ 		if (options.use_pam)
+diff -up openssh/auth2-hostbased.c.audit openssh/auth2-hostbased.c
+--- openssh/auth2-hostbased.c.audit	2019-04-03 17:02:20.612885083 +0200
++++ openssh/auth2-hostbased.c	2019-04-03 17:02:20.714886050 +0200
+@@ -158,7 +158,7 @@ userauth_hostbased(struct ssh *ssh)
+ 	authenticated = 0;
+ 	if (PRIVSEP(hostbased_key_allowed(ssh, authctxt->pw, cuser,
+ 	    chost, key)) &&
+-	    PRIVSEP(sshkey_verify(key, sig, slen,
++	    PRIVSEP(hostbased_key_verify(ssh, key, sig, slen,
+ 	    sshbuf_ptr(b), sshbuf_len(b), pkalg, ssh->compat, NULL)) == 0)
+ 		authenticated = 1;
+ 
+@@ -175,6 +175,20 @@ done:
+ 	return authenticated;
+ }
+ 
++int
++hostbased_key_verify(struct ssh *ssh, const struct sshkey *key, const u_char *sig,
++    size_t slen, const u_char *data, size_t datalen, const char *pkalg, u_int compat,
++    struct sshkey_sig_details **detailsp)
++{
++	int rv;
++
++	rv = sshkey_verify(key, sig, slen, data, datalen, pkalg, compat, detailsp);
++#ifdef SSH_AUDIT_EVENTS
++	audit_key(ssh, 0, &rv, key);
++#endif
++	return rv;
++}
++
+ /* return 1 if given hostkey is allowed */
+ int
+ hostbased_key_allowed(struct ssh *ssh, struct passwd *pw,
+diff -up openssh/auth2-pubkey.c.audit openssh/auth2-pubkey.c
+--- openssh/auth2-pubkey.c.audit	2019-04-03 17:02:20.691885832 +0200
++++ openssh/auth2-pubkey.c	2019-04-03 17:02:20.714886050 +0200
+@@ -219,7 +219,7 @@ userauth_pubkey(struct ssh *ssh)
+ 		/* test for correct signature */
+ 		authenticated = 0;
+ 		if (PRIVSEP(user_key_allowed(ssh, pw, key, 1, &authopts)) &&
+-		    PRIVSEP(sshkey_verify(key, sig, slen,
++		    PRIVSEP(user_key_verify(ssh, key, sig, slen,
+ 		    sshbuf_ptr(b), sshbuf_len(b),
+ 		    (ssh->compat & SSH_BUG_SIGTYPE) == 0 ? pkalg : NULL,
+ 		    ssh->compat, &sig_details)) == 0) {
+@@ -278,6 +278,20 @@ done:
+ 	return authenticated;
+ }
+ 
++int
++user_key_verify(struct ssh *ssh, const struct sshkey *key, const u_char *sig,
++    size_t slen, const u_char *data, size_t datalen, const char *pkalg, u_int compat,
++    struct sshkey_sig_details **detailsp)
++{
++	int rv;
++
++	rv = sshkey_verify(key, sig, slen, data, datalen, pkalg, compat, detailsp);
++#ifdef SSH_AUDIT_EVENTS
++	audit_key(ssh, 1, &rv, key);
++#endif
++	return rv;
++}
++
+ static int
+ match_principals_option(const char *principal_list, struct sshkey_cert *cert)
+ {
+diff -up openssh/auth.c.audit openssh/auth.c
+--- openssh/auth.c.audit	2019-04-03 17:02:20.691885832 +0200
++++ openssh/auth.c	2019-04-03 17:02:20.714886050 +0200
+@@ -366,7 +366,7 @@ auth_log(struct ssh *ssh, int authentica
+ # endif
+ #endif
+ #ifdef SSH_AUDIT_EVENTS
+-	if (authenticated == 0 && !authctxt->postponed)
++	if (authenticated == 0 && !authctxt->postponed && !partial)
+ 		audit_event(ssh, audit_classify_auth(method));
+ #endif
+ }
+@@ -592,9 +592,6 @@ getpwnamallow(struct ssh *ssh, const cha
+ 		record_failed_login(ssh, user,
+ 		    auth_get_canonical_hostname(ssh, options.use_dns), "ssh");
+ #endif
+-#ifdef SSH_AUDIT_EVENTS
+-		audit_event(ssh, SSH_INVALID_USER);
+-#endif /* SSH_AUDIT_EVENTS */
+ 		return (NULL);
+ 	}
+ 	if (!allowed_user(ssh, pw))
+diff -up openssh/auth.h.audit openssh/auth.h
+--- openssh/auth.h.audit	2019-04-03 17:02:20.692885842 +0200
++++ openssh/auth.h	2019-04-03 17:02:20.714886050 +0200
+@@ -195,6 +195,8 @@ struct passwd * getpwnamallow(struct ssh
+ 
+ char	*expand_authorized_keys(const char *, struct passwd *pw);
+ char	*authorized_principals_file(struct passwd *);
++int	 user_key_verify(struct ssh *, const struct sshkey *, const u_char *, size_t,
++    const u_char *, size_t, const char *, u_int, struct sshkey_sig_details **);
+ 
+ FILE	*auth_openkeyfile(const char *, struct passwd *, int);
+ FILE	*auth_openprincipals(const char *, struct passwd *, int);
+@@ -214,6 +216,8 @@ struct sshkey	*get_hostkey_private_by_ty
+ 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 *);
++int	 hostbased_key_verify(struct ssh *, const struct sshkey *, const u_char *, size_t,
++    const u_char *, size_t, const char *, u_int, struct sshkey_sig_details **);
+ 
+ /* Key / cert options linkage to auth layer */
+ const struct sshauthopt *auth_options(struct ssh *);
+diff -up openssh/cipher.c.audit openssh/cipher.c
+--- openssh/cipher.c.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/cipher.c	2019-04-03 17:02:20.714886050 +0200
+@@ -61,25 +61,6 @@ struct sshcipher_ctx {
+ 	const struct sshcipher *cipher;
+ };
+ 
+-struct sshcipher {
+-	char	*name;
+-	u_int	block_size;
+-	u_int	key_len;
+-	u_int	iv_len;		/* defaults to block_size */
+-	u_int	auth_len;
+-	u_int	flags;
+-#define CFLAG_CBC		(1<<0)
+-#define CFLAG_CHACHAPOLY	(1<<1)
+-#define CFLAG_AESCTR		(1<<2)
+-#define CFLAG_NONE		(1<<3)
+-#define CFLAG_INTERNAL		CFLAG_NONE /* Don't use "none" for packets */
+-#ifdef WITH_OPENSSL
+-	const EVP_CIPHER	*(*evptype)(void);
+-#else
+-	void	*ignored;
+-#endif
+-};
+-
+ static const struct sshcipher ciphers[] = {
+ #ifdef WITH_OPENSSL
+ #ifndef OPENSSL_NO_DES
+@@ -410,7 +391,7 @@ cipher_get_length(struct sshcipher_ctx *
+ void
+ cipher_free(struct sshcipher_ctx *cc)
+ {
+-	if (cc == NULL)
++	if (cc == NULL || cc->cipher == NULL)
+ 		return;
+ 	if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
+ 		chachapoly_free(cc->cp_ctx);
+diff -up openssh/cipher.h.audit openssh/cipher.h
+--- openssh/cipher.h.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/cipher.h	2019-04-03 17:02:20.714886050 +0200
+@@ -45,7 +45,25 @@
+ #define CIPHER_ENCRYPT		1
+ #define CIPHER_DECRYPT		0
+ 
+-struct sshcipher;
++struct sshcipher {
++	char	*name;
++	u_int	block_size;
++	u_int	key_len;
++	u_int	iv_len;		/* defaults to block_size */
++	u_int	auth_len;
++	u_int	flags;
++#define CFLAG_CBC		(1<<0)
++#define CFLAG_CHACHAPOLY	(1<<1)
++#define CFLAG_AESCTR		(1<<2)
++#define CFLAG_NONE		(1<<3)
++#define CFLAG_INTERNAL		CFLAG_NONE /* Don't use "none" for packets */
++#ifdef WITH_OPENSSL
++	const EVP_CIPHER	*(*evptype)(void);
++#else
++	void	*ignored;
++#endif
++};
++
+ struct sshcipher_ctx;
+ 
+ const struct sshcipher *cipher_by_name(const char *);
+diff -up openssh/kex.c.audit openssh/kex.c
+--- openssh/kex.c.audit	2019-04-03 17:02:20.652885462 +0200
++++ openssh/kex.c	2019-04-03 17:02:20.715886060 +0200
+@@ -60,6 +60,7 @@
+ #include "ssherr.h"
+ #include "sshbuf.h"
+ #include "digest.h"
++#include "audit.h"
+ 
+ #ifdef GSSAPI
+ #include "ssh-gss.h"
+@@ -758,12 +759,16 @@ kex_start_rekex(struct ssh *ssh)
+ }
+ 
+ static int
+-choose_enc(struct sshenc *enc, char *client, char *server)
++choose_enc(struct ssh *ssh, struct sshenc *enc, char *client, char *server)
+ {
+ 	char *name = match_list(client, server, NULL);
+ 
+-	if (name == NULL)
++	if (name == NULL) {
++#ifdef SSH_AUDIT_EVENTS
++		audit_unsupported(ssh, SSH_AUDIT_UNSUPPORTED_CIPHER);
++#endif
+ 		return SSH_ERR_NO_CIPHER_ALG_MATCH;
++	}
+ 	if ((enc->cipher = cipher_by_name(name)) == NULL) {
+ 		error("%s: unsupported cipher %s", __func__, name);
+ 		free(name);
+@@ -783,8 +788,12 @@ choose_mac(struct ssh *ssh, struct sshma
+ {
+ 	char *name = match_list(client, server, NULL);
+ 
+-	if (name == NULL)
++	if (name == NULL) {
++#ifdef SSH_AUDIT_EVENTS
++		audit_unsupported(ssh, SSH_AUDIT_UNSUPPORTED_MAC);
++#endif
+ 		return SSH_ERR_NO_MAC_ALG_MATCH;
++	}
+ 	if (mac_setup(mac, name) < 0) {
+ 		error("%s: unsupported MAC %s", __func__, name);
+ 		free(name);
+@@ -796,12 +805,16 @@ choose_mac(struct ssh *ssh, struct sshma
+ }
+ 
+ static int
+-choose_comp(struct sshcomp *comp, char *client, char *server)
++choose_comp(struct ssh *ssh, struct sshcomp *comp, char *client, char *server)
+ {
+ 	char *name = match_list(client, server, NULL);
+ 
+-	if (name == NULL)
++	if (name == NULL) {
++#ifdef SSH_AUDIT_EVENTS
++		audit_unsupported(ssh, SSH_AUDIT_UNSUPPORTED_COMPRESSION);
++#endif
+ 		return SSH_ERR_NO_COMPRESS_ALG_MATCH;
++	}
+ #ifdef WITH_ZLIB
+ 	if (strcmp(name, "zlib@openssh.com") == 0) {
+ 		comp->type = COMP_DELAYED;
+@@ -933,7 +946,7 @@ kex_choose_conf(struct ssh *ssh)
+ 		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;
+-		if ((r = choose_enc(&newkeys->enc, cprop[nenc],
++		if ((r = choose_enc(ssh, &newkeys->enc, cprop[nenc],
+ 		    sprop[nenc])) != 0) {
+ 			kex->failed_choice = peer[nenc];
+ 			peer[nenc] = NULL;
+@@ -948,7 +961,7 @@ kex_choose_conf(struct ssh *ssh)
+ 			peer[nmac] = NULL;
+ 			goto out;
+ 		}
+-		if ((r = choose_comp(&newkeys->comp, cprop[ncomp],
++		if ((r = choose_comp(ssh, &newkeys->comp, cprop[ncomp],
+ 		    sprop[ncomp])) != 0) {
+ 			kex->failed_choice = peer[ncomp];
+ 			peer[ncomp] = NULL;
+@@ -971,6 +984,10 @@ kex_choose_conf(struct ssh *ssh)
+ 		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);
++		debug("kex: %s need=%d dh_need=%d", kex->name, need, dh_need);
++#ifdef SSH_AUDIT_EVENTS
++		audit_kex(ssh, mode, newkeys->enc.name, newkeys->mac.name, newkeys->comp.name, kex->name);
++#endif
+ 	}
+ 	/* XXX need runden? */
+ 	kex->we_need = need;
+@@ -1129,6 +1146,36 @@ dump_digest(const char *msg, const u_cha
+ }
+ #endif
+ 
++static void
++enc_destroy(struct sshenc *enc)
++{
++	if (enc == NULL)
++		return;
++
++	if (enc->key) {
++		memset(enc->key, 0, enc->key_len);
++		free(enc->key);
++	}
++
++	if (enc->iv) {
++		memset(enc->iv,  0, enc->iv_len);
++		free(enc->iv);
++	}
++
++	memset(enc, 0, sizeof(*enc));
++}
++
++void
++newkeys_destroy(struct newkeys *newkeys)
++{
++	if (newkeys == NULL)
++		return;
++
++	enc_destroy(&newkeys->enc);
++	mac_destroy(&newkeys->mac);
++	memset(&newkeys->comp, 0, sizeof(newkeys->comp));
++}
++
+ /*
+  * Send a plaintext error message to the peer, suffixed by \r\n.
+  * Only used during banner exchange, and there only for the server.
+diff -up openssh/kex.h.audit openssh/kex.h
+--- openssh/kex.h.audit	2019-04-03 17:02:20.652885462 +0200
++++ openssh/kex.h	2019-04-03 17:02:20.715886060 +0200
+@@ -226,6 +226,8 @@ int	 kexgss_client(struct ssh *);
+ int	 kexgss_server(struct ssh *);
+ #endif
+ 
++void	newkeys_destroy(struct newkeys *newkeys);
++
+ int	 kex_dh_keypair(struct kex *);
+ int	 kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
+     struct sshbuf **);
+diff -up openssh/mac.c.audit openssh/mac.c
+--- openssh/mac.c.audit	2019-04-03 17:02:20.652885462 +0200
++++ openssh/mac.c	2019-04-03 17:02:20.715886060 +0200
+@@ -243,6 +243,20 @@ mac_clear(struct sshmac *mac)
+ 	mac->umac_ctx = NULL;
+ }
+ 
++void
++mac_destroy(struct sshmac *mac)
++{
++	if (mac == NULL)
++		return;
++
++	if (mac->key) {
++		memset(mac->key, 0, mac->key_len);
++		free(mac->key);
++	}
++
++	memset(mac, 0, sizeof(*mac));
++}
++
+ /* XXX copied from ciphers_valid */
+ #define	MAC_SEP	","
+ int
+diff -up openssh/mac.h.audit openssh/mac.h
+--- openssh/mac.h.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/mac.h	2019-04-03 17:02:20.715886060 +0200
+@@ -49,5 +49,6 @@ int	 mac_compute(struct sshmac *, u_int3
+ int	 mac_check(struct sshmac *, u_int32_t, const u_char *, size_t,
+     const u_char *, size_t);
+ void	 mac_clear(struct sshmac *);
++void	 mac_destroy(struct sshmac *);
+ 
+ #endif /* SSHMAC_H */
+diff -up openssh/Makefile.in.audit openssh/Makefile.in
+--- openssh/Makefile.in.audit	2019-04-03 17:02:20.705885965 +0200
++++ openssh/Makefile.in	2019-04-03 17:02:20.715886060 +0200
+@@ -109,7 +109,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ 	sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \
+ 	kexgssc.o \
+	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+-	sshbuf-io.o
++	sshbuf-io.o auditstub.o
+ 
+ SKOBJS=	ssh-sk-client.o
+ 
+diff -up openssh/monitor.c.audit openssh/monitor.c
+--- openssh/monitor.c.audit	2019-04-03 17:02:20.674885671 +0200
++++ openssh/monitor.c	2019-04-03 17:03:17.201421405 +0200
+@@ -93,6 +93,7 @@
+ #include "compat.h"
+ #include "ssh2.h"
+ #include "authfd.h"
++#include "audit.h"
+ #include "match.h"
+ #include "ssherr.h"
+ #include "sk-api.h"
+@@ -107,6 +108,8 @@ extern u_char session_id[];
+ extern struct sshbuf *loginmsg;
+ extern struct sshauthopt *auth_opts; /* XXX move to permanent ssh->authctxt? */
+ 
++extern void destroy_sensitive_data(struct ssh *, int);
++
+ /* State exported from the child */
+ static struct sshbuf *child_state;
+ 
+@@ -157,6 +160,11 @@ int mm_answer_gss_updatecreds(struct ssh
+ #ifdef SSH_AUDIT_EVENTS
+ int mm_answer_audit_event(struct ssh *, int, struct sshbuf *);
+ int mm_answer_audit_command(struct ssh *, int, struct sshbuf *);
++int mm_answer_audit_end_command(struct ssh *, int, struct sshbuf *);
++int mm_answer_audit_unsupported_body(struct ssh *, int, struct sshbuf *);
++int mm_answer_audit_kex_body(struct ssh *, int, struct sshbuf *);
++int mm_answer_audit_session_key_free_body(struct ssh *, int, struct sshbuf *);
++int mm_answer_audit_server_key_free(struct ssh *, int, struct sshbuf *);
+ #endif
+ 
+ static Authctxt *authctxt;
+@@ -215,6 +223,10 @@ struct mon_table mon_dispatch_proto20[]
+ #endif
+ #ifdef SSH_AUDIT_EVENTS
+     {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event},
++    {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body},
++    {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body},
++    {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body},
++    {MONITOR_REQ_AUDIT_SERVER_KEY_FREE, MON_PERMIT, mm_answer_audit_server_key_free},
+ #endif
+ #ifdef BSD_AUTH
+     {MONITOR_REQ_BSDAUTHQUERY, MON_ISAUTH, mm_answer_bsdauthquery},
+@@ -249,6 +261,11 @@ struct mon_table mon_dispatch_postauth20
+ #ifdef SSH_AUDIT_EVENTS
+     {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event},
+     {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT, mm_answer_audit_command},
++    {MONITOR_REQ_AUDIT_END_COMMAND, MON_PERMIT, mm_answer_audit_end_command},
++    {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body},
++    {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body},
++    {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body},
++    {MONITOR_REQ_AUDIT_SERVER_KEY_FREE, MON_PERMIT, mm_answer_audit_server_key_free},
+ #endif
+     {0, 0, NULL}
+ };
+@@ -1445,8 +1462,10 @@ mm_answer_keyverify(struct ssh *ssh, int
+ 	int r, ret, req_presence = 0, req_verify = 0, valid_data = 0;
+ 	int encoded_ret;
+ 	struct sshkey_sig_details *sig_details = NULL;
++	int type = 0;
+ 
+-	if ((r = sshbuf_get_string_direct(m, &blob, &bloblen)) != 0 ||
++	if ((r = sshbuf_get_u32(m, &type)) != 0 ||
++	    (r = sshbuf_get_string_direct(m, &blob, &bloblen)) != 0 ||
+ 	    (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)
+@@ -1455,6 +1474,8 @@ mm_answer_keyverify(struct ssh *ssh, int
+ 	if (hostbased_cuser == NULL || hostbased_chost == NULL ||
+ 	  !monitor_allowed_key(blob, bloblen))
+ 		fatal("%s: bad key, not previously allowed", __func__);
++	if (type != key_blobtype)
++		fatal("%s: bad key type", __func__);
+ 
+ 	/* Empty signature algorithm means NULL. */
+ 	if (*sigalg == '\0') {
+@@ -1470,25 +1491,28 @@ mm_answer_keyverify(struct ssh *ssh, int
+ 	case MM_USERKEY:
+ 		valid_data = monitor_valid_userblob(data, datalen);
+ 		auth_method = "publickey";
++		ret = user_key_verify(ssh, key, signature, signaturelen, data,
++		    datalen, sigalg, ssh->compat, &sig_details);
+ 		break;
+ 	case MM_HOSTKEY:
+ 		valid_data = monitor_valid_hostbasedblob(data, datalen,
+ 		    hostbased_cuser, hostbased_chost);
+ 		auth_method = "hostbased";
++		ret = hostbased_key_verify(ssh, key, signature, signaturelen, data,
++		    datalen, sigalg, ssh->compat, &sig_details);
+ 		break;
+ 	default:
+ 		valid_data = 0;
++		ret = 0;
+ 		break;
+ 	}
+ 	if (!valid_data)
+ 		fatal("%s: bad signature data blob", __func__);
+ 
+ 	if ((fp = sshkey_fingerprint(key, options.fingerprint_hash,
+ 	    SSH_FP_DEFAULT)) == NULL)
+ 		fatal("%s: sshkey_fingerprint failed", __func__);
+ 
+-	ret = sshkey_verify(key, signature, signaturelen, data, datalen,
+-	    sigalg, ssh->compat, &sig_details);
+ 	debug3("%s: %s %p signature %s%s%s", __func__, auth_method, key,
+ 	    (ret == 0) ? "verified" : "unverified",
+ 	    (ret != 0) ? ": " : "", (ret != 0) ? ssh_err(ret) : "");
+@@ -1536,13 +1560,19 @@ mm_record_login(struct ssh *ssh, Session
+ }
+ 
+ static void
+-mm_session_close(Session *s)
++mm_session_close(struct ssh *ssh, Session *s)
+ {
+ 	debug3("%s: session %d pid %ld", __func__, s->self, (long)s->pid);
+ 	if (s->ttyfd != -1) {
+ 		debug3("%s: tty %s ptyfd %d", __func__, s->tty, s->ptyfd);
+ 		session_pty_cleanup2(s);
+ 	}
++#ifdef SSH_AUDIT_EVENTS
++	if (s->command != NULL) {
++		debug3("%s: command %d", __func__, s->command_handle);
++		session_end_command2(ssh, s);
++	}
++#endif
+ 	session_unused(s->self);
+ }
+ 
+@@ -1609,7 +1639,7 @@ mm_answer_pty(struct ssh *ssh, int sock,
+ 
+  error:
+ 	if (s != NULL)
+-		mm_session_close(s);
++		mm_session_close(ssh, s);
+ 	if ((r = sshbuf_put_u32(m, 0)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 	mm_request_send(sock, MONITOR_ANS_PTY, m);
+@@ -1628,7 +1658,7 @@ mm_answer_pty_cleanup(struct ssh *ssh, i
+ 	if ((r = sshbuf_get_cstring(m, &tty, NULL)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 	if ((s = session_by_tty(tty)) != NULL)
+-		mm_session_close(s);
++		mm_session_close(ssh, s);
+ 	sshbuf_reset(m);
+ 	free(tty);
+ 	return (0);
+@@ -1650,6 +1680,8 @@ mm_answer_term(struct ssh *ssh, int sock
+ 		sshpam_cleanup();
+ #endif
+ 
++	destroy_sensitive_data(ssh, 0);
++
+ 	while (waitpid(pmonitor->m_pid, &status, 0) == -1)
+ 		if (errno != EINTR)
+ 			exit(1);
+@@ -1696,12 +1728,47 @@ mm_answer_audit_command(struct ssh *ssh,
+ {
+ 	char *cmd;
+ 	int r;
++	Session *s;
+ 
+ 	debug3("%s entering", __func__);
+ 	if ((r = sshbuf_get_cstring(m, &cmd, NULL)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
+ 	/* sanity check command, if so how? */
+-	audit_run_command(cmd);
++	s = session_new();
++	if (s == NULL)
++		fatal("%s: error allocating a session", __func__);
++	s->command = cmd;
++#ifdef SSH_AUDIT_EVENTS
++	s->command_handle = audit_run_command(ssh, cmd);
++#endif
++
++	sshbuf_reset(m);
++	sshbuf_put_u32(m, s->self);
++
++	mm_request_send(socket, MONITOR_ANS_AUDIT_COMMAND, m);
++
++	return (0);
++}
++
++int
++mm_answer_audit_end_command(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++	int handle, r;
++	size_t len;
++	u_char *cmd = NULL;
++	Session *s;
++
++	debug3("%s entering", __func__);
++	if ((r = sshbuf_get_u32(m, &handle)) != 0 ||
++	    (r = sshbuf_get_string(m, &cmd, &len)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	s = session_by_id(handle);
++	if (s == NULL || s->ttyfd != -1 || s->command == NULL ||
++	    strcmp(s->command, cmd) != 0)
++		fatal("%s: invalid handle", __func__);
++	mm_session_close(ssh, s);
+ 	free(cmd);
+ 	return (0);
+ }
+@@ -1767,6 +1834,7 @@ monitor_apply_keystate(struct ssh *ssh,
+ void
+ mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ {
++	struct sshbuf *m;
+ 	debug3("%s: Waiting for new keys", __func__);
+ 
+ 	if ((child_state = sshbuf_new()) == NULL)
+@@ -1774,6 +1842,19 @@ mm_get_keystate(struct ssh *ssh, struct
+ 	mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT,
+ 	    child_state);
+ 	debug3("%s: GOT new keys", __func__);
++
++#ifdef SSH_AUDIT_EVENTS
++	m = sshbuf_new();
++	mm_request_receive_expect(pmonitor->m_sendfd,
++				  MONITOR_REQ_AUDIT_SESSION_KEY_FREE, m);
++	mm_answer_audit_session_key_free_body(ssh, pmonitor->m_sendfd, m);
++	sshbuf_free(m);
++#endif
++
++	/* Drain any buffered messages from the child */
++	while (pmonitor->m_log_recvfd >= 0 && monitor_read_log(pmonitor) == 0)
++		;
++
+ }
+ 
+ 
+@@ -2066,3 +2147,102 @@ mm_answer_gss_updatecreds(struct ssh *ss
+ 
+ #endif /* GSSAPI */
+ 
++#ifdef SSH_AUDIT_EVENTS
++int
++mm_answer_audit_unsupported_body(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++	int what, r;
++
++	if ((r = sshbuf_get_u32(m, &what)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	audit_unsupported_body(ssh, what);
++
++	sshbuf_reset(m);
++
++	mm_request_send(sock, MONITOR_ANS_AUDIT_UNSUPPORTED, m);
++	return 0;
++}
++
++int
++mm_answer_audit_kex_body(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++	int ctos, r;
++	char *cipher, *mac, *compress, *pfs;
++	u_int64_t tmp;
++	pid_t pid;
++	uid_t uid;
++
++	if ((r = sshbuf_get_u32(m, &ctos)) != 0 ||
++	    (r = sshbuf_get_cstring(m, &cipher, NULL)) != 0 ||
++	    (r = sshbuf_get_cstring(m, &mac, NULL)) != 0 ||
++	    (r = sshbuf_get_cstring(m, &compress, NULL)) != 0 ||
++	    (r = sshbuf_get_cstring(m, &pfs, NULL)) != 0 ||
++	    (r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	pid = (pid_t) tmp;
++	if ((r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	uid = (pid_t) tmp;
++
++	audit_kex_body(ssh, ctos, cipher, mac, compress, pfs, pid, uid);
++
++	free(cipher);
++	free(mac);
++	free(compress);
++	free(pfs);
++	sshbuf_reset(m);
++
++	mm_request_send(sock, MONITOR_ANS_AUDIT_KEX, m);
++	return 0;
++}
++
++int
++mm_answer_audit_session_key_free_body(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++	int ctos, r;
++	u_int64_t tmp;
++	pid_t pid;
++	uid_t uid;
++
++	if ((r = sshbuf_get_u32(m, &ctos)) != 0 ||
++	    (r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	pid = (pid_t) tmp;
++	if ((r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	uid = (uid_t) tmp;
++
++	audit_session_key_free_body(ssh, ctos, pid, uid);
++
++	sshbuf_reset(m);
++
++	mm_request_send(sock, MONITOR_ANS_AUDIT_SESSION_KEY_FREE, m);
++	return 0;
++}
++
++int
++mm_answer_audit_server_key_free(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++	size_t len, r;
++	char *fp;
++	u_int64_t tmp;
++	pid_t pid;
++	uid_t uid;
++
++	if ((r = sshbuf_get_cstring(m, &fp, &len)) != 0 ||
++	    (r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	pid = (pid_t) tmp;
++	if ((r = sshbuf_get_u64(m, &tmp)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	uid = (uid_t) tmp;
++
++	audit_destroy_sensitive_data(ssh, fp, pid, uid);
++
++	free(fp);
++	sshbuf_reset(m);
++
++	return 0;
++}
++#endif /* SSH_AUDIT_EVENTS */
+diff -up openssh/monitor.h.audit openssh/monitor.h
+--- openssh/monitor.h.audit	2019-04-03 17:02:20.674885671 +0200
++++ openssh/monitor.h	2019-04-03 17:02:20.715886060 +0200
+@@ -65,7 +65,13 @@ enum monitor_reqtype {
+ 	MONITOR_REQ_PAM_QUERY = 106, MONITOR_ANS_PAM_QUERY = 107,
+ 	MONITOR_REQ_PAM_RESPOND = 108, MONITOR_ANS_PAM_RESPOND = 109,
+ 	MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111,
+-	MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113,
++	MONITOR_REQ_AUDIT_EVENT = 112,
++	MONITOR_REQ_AUDIT_COMMAND = 114, MONITOR_ANS_AUDIT_COMMAND = 115,
++	MONITOR_REQ_AUDIT_END_COMMAND = 116,
++	MONITOR_REQ_AUDIT_UNSUPPORTED = 118, MONITOR_ANS_AUDIT_UNSUPPORTED = 119,
++	MONITOR_REQ_AUDIT_KEX = 120, MONITOR_ANS_AUDIT_KEX = 121,
++	MONITOR_REQ_AUDIT_SESSION_KEY_FREE = 122, MONITOR_ANS_AUDIT_SESSION_KEY_FREE = 123,
++	MONITOR_REQ_AUDIT_SERVER_KEY_FREE = 124,
+ 
+ 	MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
+ 	MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
+diff -up openssh/monitor_wrap.c.audit openssh/monitor_wrap.c
+--- openssh/monitor_wrap.c.audit	2019-04-03 17:02:20.653885472 +0200
++++ openssh/monitor_wrap.c	2019-04-03 17:02:20.716886069 +0200
+@@ -513,7 +513,7 @@ mm_key_allowed(enum mm_keytype type, con
+  */
+ 
+ int
+-mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
++mm_sshkey_verify(enum mm_keytype type, const struct sshkey *key, const u_char *sig, size_t siglen,
+     const u_char *data, size_t datalen, const char *sigalg, u_int compat,
+     struct sshkey_sig_details **sig_detailsp)
+ {
+@@ -525,7 +525,8 @@ mm_sshkey_verify(const struct sshkey *ke
+ 		*sig_detailsp = NULL;
+ 	if ((m = sshbuf_new()) == NULL)
+ 		fatal("%s: sshbuf_new failed", __func__);
+-	if ((r = sshkey_puts(key, m)) != 0 ||
++	if ((r = sshbuf_put_u32(m, type)) != 0 ||
++	    (r = sshkey_puts(key, m)) != 0 ||
+ 	    (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)
+@@ -547,6 +548,22 @@ mm_sshkey_verify(const struct sshkey *ke
+ 	return 0;
+ }
+ 
++int
++mm_hostbased_key_verify(struct ssh *ssh, const struct sshkey *key, const u_char *sig, size_t siglen,
++    const u_char *data, size_t datalen, const char *pkalg, u_int compat,
++    struct sshkey_sig_details **detailsp)
++{
++	return mm_sshkey_verify(MM_HOSTKEY, key, sig, siglen, data, datalen, pkalg, compat, detailsp);
++}
++
++int
++mm_user_key_verify(struct ssh *ssh, const struct sshkey *key, const u_char *sig, size_t siglen,
++    const u_char *data, size_t datalen, const char *pkalg, u_int compat,
++    struct sshkey_sig_details **detailsp)
++{
++	return mm_sshkey_verify(MM_USERKEY, key, sig, siglen, data, datalen, pkalg, compat, detailsp);
++}
++
+ void
+ mm_send_keystate(struct ssh *ssh, struct monitor *monitor)
+ {
+@@ -900,11 +915,12 @@ mm_audit_event(struct ssh *ssh, ssh_audi
+ 	sshbuf_free(m);
+ }
+ 
+-void
+-mm_audit_run_command(const char *command)
++int
++mm_audit_run_command(struct ssh *ssh, const char *command)
+ {
+ 	struct sshbuf *m;
+ 	int r;
++	int handle;
+ 
+ 	debug3("%s entering command %s", __func__, command);
+ 
+@@ -914,6 +930,30 @@ mm_audit_run_command(const char *command
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+ 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_COMMAND, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_COMMAND, m);
++
++	if ((r = sshbuf_get_u32(m, &handle)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	sshbuf_free(m);
++
++	return (handle);
++}
++
++void
++mm_audit_end_command(struct ssh *ssh, int handle, const char *command)
++{
++	int r;
++	struct sshbuf *m;
++
++	debug3("%s entering command %s", __func__, command);
++
++ 	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_u32(m, handle)) != 0 ||
++	    (r = sshbuf_put_cstring(m, command)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_END_COMMAND, m);
+ 	sshbuf_free(m);
+ }
+ #endif /* SSH_AUDIT_EVENTS */
+@@ -1074,3 +1114,83 @@ mm_ssh_gssapi_update_creds(ssh_gssapi_cc
+ }
+ 
+ #endif /* GSSAPI */
++#ifdef SSH_AUDIT_EVENTS
++void
++mm_audit_unsupported_body(struct ssh *ssh, int what)
++{
++	int r;
++	struct sshbuf *m;
++
++ 	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_u32(m, what)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_UNSUPPORTED, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_UNSUPPORTED,
++				  m);
++
++	sshbuf_free(m);
++}
++
++void
++mm_audit_kex_body(struct ssh *ssh, int ctos, char *cipher, char *mac, char *compress, char *fps, pid_t pid,
++		  uid_t uid)
++{
++	int r;
++	struct sshbuf *m;
++
++ 	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_u32(m, ctos)) != 0 ||
++	    (r = sshbuf_put_cstring(m, cipher)) != 0 ||
++	    (r = sshbuf_put_cstring(m, (mac ? mac : "<implicit>"))) != 0 ||
++	    (r = sshbuf_put_cstring(m, compress)) != 0 ||
++	    (r = sshbuf_put_cstring(m, fps)) != 0 ||
++	    (r = sshbuf_put_u64(m, pid)) != 0 ||
++	    (r = sshbuf_put_u64(m, uid)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_KEX, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_KEX,
++				  m);
++
++	sshbuf_free(m);
++}
++
++void
++mm_audit_session_key_free_body(struct ssh *ssh, int ctos, pid_t pid, uid_t uid)
++{
++	int r;
++	struct sshbuf *m;
++
++ 	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_u32(m, ctos)) != 0 ||
++	    (r = sshbuf_put_u64(m, pid)) != 0 ||
++	    (r = sshbuf_put_u64(m, uid)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SESSION_KEY_FREE, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_SESSION_KEY_FREE,
++				  m);
++	sshbuf_free(m);
++}
++
++void
++mm_audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_t uid)
++{
++	int r;
++	struct sshbuf *m;
++
++ 	if ((m = sshbuf_new()) == NULL)
++ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_cstring(m, fp)) != 0 ||
++	    (r = sshbuf_put_u64(m, pid)) != 0 ||
++	    (r = sshbuf_put_u64(m, uid)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SERVER_KEY_FREE, m);
++	sshbuf_free(m);
++}
++#endif /* SSH_AUDIT_EVENTS */
+diff -up openssh/monitor_wrap.h.audit openssh/monitor_wrap.h
+--- openssh/monitor_wrap.h.audit	2019-04-03 17:02:20.653885472 +0200
++++ openssh/monitor_wrap.h	2019-04-03 17:02:20.716886069 +0200
+@@ -57,7 +57,9 @@ int mm_user_key_allowed(struct ssh *, st
+     struct sshauthopt **);
+ int mm_hostbased_key_allowed(struct ssh *, struct passwd *, const char *,
+     const char *, struct sshkey *);
+-int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t,
++int mm_hostbased_key_verify(struct ssh *, const struct sshkey *, const u_char *, size_t,
++    const u_char *, size_t, const char *, u_int, struct sshkey_sig_details **);
++int mm_user_key_verify(struct ssh*, const struct sshkey *, const u_char *, size_t,
+     const u_char *, size_t, const char *, u_int, struct sshkey_sig_details **);
+ 
+ #ifdef GSSAPI
+@@ -82,7 +84,12 @@ void mm_sshpam_free_ctx(void *);
+ #ifdef SSH_AUDIT_EVENTS
+ #include "audit.h"
+ void mm_audit_event(struct ssh *, ssh_audit_event_t);
+-void mm_audit_run_command(const char *);
++int mm_audit_run_command(struct ssh *ssh, const char *);
++void mm_audit_end_command(struct ssh *ssh, int, const char *);
++void mm_audit_unsupported_body(struct ssh *, int);
++void mm_audit_kex_body(struct ssh *, int, char *, char *, char *, char *, pid_t, uid_t);
++void mm_audit_session_key_free_body(struct ssh *, int, pid_t, uid_t);
++void mm_audit_destroy_sensitive_data(struct ssh *, const char *, pid_t, uid_t);
+ #endif
+ 
+ struct Session;
+diff -up openssh/packet.c.audit openssh/packet.c
+--- openssh/packet.c.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/packet.c	2019-04-03 17:02:20.716886069 +0200
+@@ -77,6 +77,7 @@
+ #endif
+ 
+ #include "xmalloc.h"
++#include "audit.h"
+ #include "compat.h"
+ #include "ssh2.h"
+ #include "cipher.h"
+@@ -510,6 +511,13 @@ ssh_packet_get_connection_out(struct ssh
+ 	return ssh->state->connection_out;
+ }
+ 
++static int
++packet_state_has_keys (const struct session_state *state)
++{
++	return state != NULL &&
++		(state->newkeys[MODE_IN] != NULL || state->newkeys[MODE_OUT] != NULL);
++}
++
+ /*
+  * Returns the IP-address of the remote host as a string.  The returned
+  * string must not be freed.
+@@ -587,22 +595,19 @@ ssh_packet_close_internal(struct ssh *ss
+ {
+ 	struct session_state *state = ssh->state;
+ 	u_int mode;
++	u_int had_keys = packet_state_has_keys(state);
+ 
+ 	if (!state->initialized)
+ 		return;
+ 	state->initialized = 0;
+-	if (do_close) {
+-		if (state->connection_in == state->connection_out) {
+-			close(state->connection_out);
+-		} else {
+-			close(state->connection_in);
+-			close(state->connection_out);
+-		}
+-	}
+ 	sshbuf_free(state->input);
++	state->input = NULL;
+ 	sshbuf_free(state->output);
++	state->output = NULL;
+ 	sshbuf_free(state->outgoing_packet);
++	state->outgoing_packet = NULL;
+ 	sshbuf_free(state->incoming_packet);
++	state->incoming_packet = NULL;
+ 	for (mode = 0; mode < MODE_MAX; mode++) {
+ 		kex_free_newkeys(state->newkeys[mode]);	/* current keys */
+ 		state->newkeys[mode] = NULL;
+@@ -636,8 +641,18 @@ ssh_packet_close_internal(struct ssh *ss
+ #endif	/* WITH_ZLIB */
+ 	cipher_free(state->send_context);
+ 	cipher_free(state->receive_context);
++	if (had_keys && state->server_side) {
++		/* Assuming this is called only from privsep child */
++		audit_session_key_free(ssh, MODE_MAX);
++	}
+ 	state->send_context = state->receive_context = NULL;
+ 	if (do_close) {
++		if (state->connection_in == state->connection_out) {
++			close(state->connection_out);
++		} else {
++			close(state->connection_in);
++			close(state->connection_out);
++		}
+ 		free(ssh->local_ipaddr);
+ 		ssh->local_ipaddr = NULL;
+ 		free(ssh->remote_ipaddr);
+@@ -864,6 +879,7 @@ ssh_set_newkeys(struct ssh *ssh, int mod
+ 		   (unsigned long long)state->p_send.bytes,
+ 		   (unsigned long long)state->p_send.blocks);
+ 		kex_free_newkeys(state->newkeys[mode]);
++		audit_session_key_free(ssh, mode);
+ 		state->newkeys[mode] = NULL;
+ 	}
+ 	/* note that both bytes and the seqnr are not reset */
+@@ -2167,6 +2183,72 @@ ssh_packet_get_output(struct ssh *ssh)
+ 	return (void *)ssh->state->output;
+ }
+ 
++static void
++newkeys_destroy_and_free(struct newkeys *newkeys)
++{
++	if (newkeys == NULL)
++		return;
++
++	free(newkeys->enc.name);
++
++	if (newkeys->mac.enabled) {
++		mac_clear(&newkeys->mac);
++		free(newkeys->mac.name);
++	}
++
++	free(newkeys->comp.name);
++
++	newkeys_destroy(newkeys);
++	free(newkeys);
++}
++
++static void
++packet_destroy_state(struct session_state *state)
++{
++	if (state == NULL)
++		return;
++
++	cipher_free(state->receive_context);
++	cipher_free(state->send_context);
++ 	state->send_context = state->receive_context = NULL;
++
++	sshbuf_free(state->input);
++	state->input = NULL;
++	sshbuf_free(state->output);
++	state->output = NULL;
++	sshbuf_free(state->outgoing_packet);
++	state->outgoing_packet = NULL;
++	sshbuf_free(state->incoming_packet);
++	state->incoming_packet = NULL;
++	if (state->compression_buffer) {
++		sshbuf_free(state->compression_buffer);
++		state->compression_buffer = NULL;
++	}
++	newkeys_destroy_and_free(state->newkeys[MODE_IN]);
++	state->newkeys[MODE_IN] = NULL;
++	newkeys_destroy_and_free(state->newkeys[MODE_OUT]);
++	state->newkeys[MODE_OUT] = NULL;
++	mac_destroy(state->packet_discard_mac);
++//	TAILQ_HEAD(, packet) outgoing;
++//	memset(state, 0, sizeof(state));
++}
++
++void
++packet_destroy_all(struct ssh *ssh, int audit_it, int privsep)
++{
++	if (audit_it)
++		audit_it = packet_state_has_keys(ssh->state);
++	packet_destroy_state(ssh->state);
++	if (audit_it) {
++#ifdef SSH_AUDIT_EVENTS
++		if (privsep)
++			audit_session_key_free(ssh, MODE_MAX);
++		else
++			audit_session_key_free_body(ssh, MODE_MAX, getpid(), getuid());
++#endif
++	}
++}
++
+ /* Reset after_authentication and reset compression in post-auth privsep */
+ static int
+ ssh_packet_set_postauth(struct ssh *ssh)
+diff -up openssh/packet.h.audit openssh/packet.h
+--- openssh/packet.h.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/packet.h	2019-04-03 17:02:20.716886069 +0200
+@@ -217,4 +217,5 @@ const u_char	*sshpkt_ptr(struct ssh *, s
+ # undef EC_POINT
+ #endif
+ 
++void	 packet_destroy_all(struct ssh *, int, int);
+ #endif				/* PACKET_H */
+diff -up openssh/session.c.audit openssh/session.c
+--- openssh/session.c.audit	2019-04-03 17:02:20.712886031 +0200
++++ openssh/session.c	2019-04-03 17:02:20.716886069 +0200
+@@ -136,7 +136,7 @@ extern char *__progname;
+ extern int debug_flag;
+ extern u_int utmp_len;
+ extern int startup_pipe;
+-extern void destroy_sensitive_data(void);
++extern void destroy_sensitive_data(struct ssh *, int);
+ extern struct sshbuf *loginmsg;
+ extern struct sshauthopt *auth_opts;
+ extern char *tun_fwd_ifnames; /* serverloop.c */
+@@ -648,6 +648,14 @@ do_exec_pty(struct ssh *ssh, Session *s,
+ 	/* Parent.  Close the slave side of the pseudo tty. */
+ 	close(ttyfd);
+ 
++#if !defined(HAVE_OSF_SIA) && defined(SSH_AUDIT_EVENTS)
++	/* do_login in the child did not affect state in this process,
++	   compensate.  From an architectural standpoint, this is extremely
++	   ugly. */
++	if (command != NULL)
++		audit_count_session_open();
++#endif
++
+ 	/* Enter interactive session. */
+ 	s->ptymaster = ptymaster;
+ 	ssh_packet_set_interactive(ssh, 1,
+@@ -740,15 +748,19 @@ do_exec(struct ssh *ssh, Session *s, con
+ 	    s->self);
+ 
+ #ifdef SSH_AUDIT_EVENTS
++	if (s->command != NULL || s->command_handle != -1)
++		fatal("do_exec: command already set");
+ 	if (command != NULL)
+-		PRIVSEP(audit_run_command(command));
++		s->command = xstrdup(command);
+ 	else if (s->ttyfd == -1) {
+ 		char *shell = s->pw->pw_shell;
+ 
+ 		if (shell[0] == '\0')	/* empty shell means /bin/sh */
+ 			shell =_PATH_BSHELL;
+-		PRIVSEP(audit_run_command(shell));
++		s->command = xstrdup(shell);
+ 	}
++	if (s->command != NULL && s->ptyfd == -1)
++		s->command_handle = PRIVSEP(audit_run_command(ssh, s->command));
+ #endif
+ 	if (s->ttyfd != -1)
+ 		ret = do_exec_pty(ssh, s, command);
+@@ -1556,8 +1568,11 @@ do_child(struct ssh *ssh, Session *s, co
+ 	sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id));
+ 
+ 	/* remove hostkey from the child's memory */
+-	destroy_sensitive_data();
++	destroy_sensitive_data(ssh, 1);
+ 	ssh_packet_clear_keys(ssh);
++	/* Don't audit this - both us and the parent would be talking to the
++	   monitor over a single socket, with no synchronization. */
++	packet_destroy_all(ssh, 0, 1);
+ 
+ 	/* Force a password change */
+ 	if (s->authctxt->force_pwchange) {
+@@ -1769,6 +1784,9 @@ session_unused(int id)
+ 	sessions[id].ttyfd = -1;
+ 	sessions[id].ptymaster = -1;
+ 	sessions[id].x11_chanids = NULL;
++#ifdef SSH_AUDIT_EVENTS
++	sessions[id].command_handle = -1;
++#endif
+ 	sessions[id].next_unused = sessions_first_unused;
+ 	sessions_first_unused = id;
+ }
+@@ -1851,6 +1869,19 @@ session_open(Authctxt *authctxt, int cha
+ }
+ 
+ Session *
++session_by_id(int id)
++{
++	if (id >= 0 && id < sessions_nalloc) {
++		Session *s = &sessions[id];
++		if (s->used)
++			return s;
++	}
++	debug("%s: unknown id %d", __func__, id);
++	session_dump();
++	return NULL;
++}
++
++Session *
+ session_by_tty(char *tty)
+ {
+ 	int i;
+@@ -2461,6 +2492,32 @@ session_exit_message(struct ssh *ssh, Se
+ 		chan_write_failed(ssh, c);
+ }
+ 
++#ifdef SSH_AUDIT_EVENTS
++void
++session_end_command2(struct ssh *ssh, Session *s)
++{
++	if (s->command != NULL) {
++		if (s->command_handle != -1)
++			audit_end_command(ssh, s->command_handle, s->command);
++		free(s->command);
++		s->command = NULL;
++		s->command_handle = -1;
++	}
++}
++
++static void
++session_end_command(struct ssh *ssh, Session *s)
++{
++	if (s->command != NULL) {
++		if (s->command_handle != -1)
++			PRIVSEP(audit_end_command(ssh, s->command_handle, s->command));
++		free(s->command);
++		s->command = NULL;
++		s->command_handle = -1;
++	}
++}
++#endif
++
+ void
+ session_close(struct ssh *ssh, Session *s)
+ {
+@@ -2474,6 +2531,10 @@ session_close(struct ssh *ssh, Session *
+ 
+ 	if (s->ttyfd != -1)
+ 		session_pty_cleanup(s);
++#ifdef SSH_AUDIT_EVENTS
++	if (s->command)
++		session_end_command(ssh, s);
++#endif
+ 	free(s->term);
+ 	free(s->display);
+ 	free(s->x11_chanids);
+@@ -2549,14 +2610,14 @@ session_close_by_channel(struct ssh *ssh
+ }
+ 
+ void
+-session_destroy_all(struct ssh *ssh, void (*closefunc)(Session *))
++session_destroy_all(struct ssh *ssh, void (*closefunc)(struct ssh *ssh, Session *))
+ {
+ 	int i;
+ 	for (i = 0; i < sessions_nalloc; i++) {
+ 		Session *s = &sessions[i];
+ 		if (s->used) {
+ 			if (closefunc != NULL)
+-				closefunc(s);
++				closefunc(ssh, s);
+ 			else
+ 				session_close(ssh, s);
+ 		}
+@@ -2683,6 +2744,15 @@ do_authenticated2(struct ssh *ssh, Authc
+ 	server_loop2(ssh, authctxt);
+ }
+ 
++static void
++do_cleanup_one_session(struct ssh *ssh, Session *s)
++{
++	session_pty_cleanup2(s);
++#ifdef SSH_AUDIT_EVENTS
++	session_end_command2(ssh, s);
++#endif
++}
++
+ void
+ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+ {
+@@ -2746,7 +2816,7 @@ do_cleanup(struct ssh *ssh, Authctxt *au
+ 	 * or if running in monitor.
+ 	 */
+ 	if (!use_privsep || mm_is_monitor())
+-		session_destroy_all(ssh, session_pty_cleanup2);
++		session_destroy_all(ssh, do_cleanup_one_session);
+ }
+ 
+ /* Return a name for the remote host that fits inside utmp_size */
+diff -up openssh/session.h.audit openssh/session.h
+--- openssh/session.h.audit	2019-03-27 23:26:14.000000000 +0100
++++ openssh/session.h	2019-04-03 17:02:20.717886079 +0200
+@@ -61,6 +61,12 @@ struct Session {
+ 		char	*name;
+ 		char	*val;
+ 	} *env;
++
++	/* exec */
++#ifdef SSH_AUDIT_EVENTS
++	int	command_handle;
++	char	*command;
++#endif
+ };
+ 
+ void	 do_authenticated(struct ssh *, Authctxt *);
+@@ -71,10 +77,12 @@ void	 session_unused(int);
+ int	 session_input_channel_req(struct ssh *, Channel *, const char *);
+ void	 session_close_by_pid(struct ssh *ssh, pid_t, int);
+ void	 session_close_by_channel(struct ssh *, int, void *);
+-void	 session_destroy_all(struct ssh *, void (*)(Session *));
++void	 session_destroy_all(struct ssh *, void (*)(struct ssh*, Session *));
+ void	 session_pty_cleanup2(Session *);
++void	 session_end_command2(struct ssh *ssh, Session *);
+ 
+ Session	*session_new(void);
++Session *session_by_id(int);
+ Session	*session_by_tty(char *);
+ void	 session_close(struct ssh *, Session *);
+ void	 do_setusercontext(struct passwd *);
+diff -up openssh/sshd.c.audit openssh/sshd.c
+--- openssh/sshd.c.audit	2019-04-03 17:02:20.692885842 +0200
++++ openssh/sshd.c	2019-04-03 17:02:20.717886079 +0200
+@@ -122,6 +122,7 @@
+ #include "ssh-gss.h"
+ #endif
+ #include "monitor_wrap.h"
++#include "audit.h"
+ #include "ssh-sandbox.h"
+ #include "auth-options.h"
+ #include "version.h"
+@@ -261,8 +262,8 @@ struct sshbuf *loginmsg;
+ struct passwd *privsep_pw = NULL;
+ 
+ /* Prototypes for various functions defined later in this file. */
+-void destroy_sensitive_data(void);
+-void demote_sensitive_data(void);
++void destroy_sensitive_data(struct ssh *, int);
++void demote_sensitive_data(struct ssh *);
+ static void do_ssh2_kex(struct ssh *);
+ 
+ static char *listener_proctitle;
+@@ -278,6 +279,15 @@ close_listen_socks(void)
+ 	num_listen_socks = -1;
+ }
+ 
++/*
++ * Is this process listening for clients (i.e. not specific to any specific
++ * client connection?)
++ */
++int listening_for_clients(void)
++{
++	return num_listen_socks >= 0;
++}
++
+ static void
+ close_startup_pipes(void)
+ {
+@@ -380,18 +390,45 @@ grace_alarm_handler(int sig)
+ 	    ssh_remote_port(the_active_state));
+ }
+ 
+-/* Destroy the host and server keys.  They will no longer be needed. */
++/*
++ * Destroy the host and server keys.  They will no longer be needed.  Careful,
++ * this can be called from cleanup_exit() - i.e. from just about anywhere.
++ */
+ void
+-destroy_sensitive_data(void)
++destroy_sensitive_data(struct ssh *ssh, int privsep)
+ {
+ 	u_int i;
++#ifdef SSH_AUDIT_EVENTS
++	pid_t pid;
++	uid_t uid;
+ 
++	pid = getpid();
++	uid = getuid();
++#endif
+ 	for (i = 0; i < options.num_host_key_files; i++) {
+ 		if (sensitive_data.host_keys[i]) {
++			char *fp;
++
++			if (sshkey_is_private(sensitive_data.host_keys[i]))
++				fp = sshkey_fingerprint(sensitive_data.host_keys[i], options.fingerprint_hash, SSH_FP_HEX);
++			else
++				fp = NULL;
+ 			sshkey_free(sensitive_data.host_keys[i]);
+ 			sensitive_data.host_keys[i] = NULL;
++			if (fp != NULL) {
++#ifdef SSH_AUDIT_EVENTS
++				if (privsep)
++					PRIVSEP(audit_destroy_sensitive_data(ssh, fp,
++						pid, uid));
++				else
++					audit_destroy_sensitive_data(ssh, fp,
++						pid, uid);
++#endif
++				free(fp);
++			}
+ 		}
+-		if (sensitive_data.host_certificates[i]) {
++		if (sensitive_data.host_certificates
++		    && sensitive_data.host_certificates[i]) {
+ 			sshkey_free(sensitive_data.host_certificates[i]);
+ 			sensitive_data.host_certificates[i] = NULL;
+ 		}
+@@ -400,14 +437,26 @@ destroy_sensitive_data(void)
+ 
+ /* Demote private to public keys for network child */
+ void
+-demote_sensitive_data(void)
++demote_sensitive_data(struct ssh *ssh)
+ {
+ 	struct sshkey *tmp;
+ 	u_int i;
+ 	int r;
++#ifdef SSH_AUDIT_EVENTS
++	pid_t pid;
++	uid_t uid;
+ 
++	pid = getpid();
++	uid = getuid();
++#endif
+ 	for (i = 0; i < options.num_host_key_files; i++) {
+ 		if (sensitive_data.host_keys[i]) {
++			char *fp;
++
++			if (sshkey_is_private(sensitive_data.host_keys[i]))
++				fp = sshkey_fingerprint(sensitive_data.host_keys[i], options.fingerprint_hash, SSH_FP_HEX);
++			else
++				fp = NULL;
+ 			if ((r = sshkey_from_private(
+ 			    sensitive_data.host_keys[i], &tmp)) != 0)
+ 				fatal("could not demote host %s key: %s",
+@@ -415,6 +464,12 @@ demote_sensitive_data(void)
+ 				    ssh_err(r));
+ 			sshkey_free(sensitive_data.host_keys[i]);
+ 			sensitive_data.host_keys[i] = tmp;
++			if (fp != NULL) {
++#ifdef SSH_AUDIT_EVENTS
++				audit_destroy_sensitive_data(ssh, fp, pid, uid);
++#endif
++				free(fp);
++			}
+ 		}
+ 		/* Certs do not need demotion */
+ 	}
+@@ -442,7 +497,7 @@ reseed_prngs(void)
+ }
+ 
+ static void
+-privsep_preauth_child(void)
++privsep_preauth_child(struct ssh *ssh)
+ {
+ 	gid_t gidset[1];
+ 
+@@ -457,7 +512,7 @@ privsep_preauth_child(void)
+ 	reseed_prngs();
+ 
+ 	/* Demote the private keys to public keys. */
+-	demote_sensitive_data();
++	demote_sensitive_data(ssh);
+ 
+ #ifdef WITH_SELINUX
+ 	sshd_selinux_change_privsep_preauth_context();
+@@ -496,7 +551,7 @@ privsep_preauth(struct ssh *ssh)
+ 
+ 	if (use_privsep == PRIVSEP_ON)
+ 		box = ssh_sandbox_init(pmonitor);
+-	pid = fork();
++	pmonitor->m_pid = pid = fork();
+ 	if (pid == -1) {
+ 		fatal("fork of unprivileged child failed");
+ 	} else if (pid != 0) {
+@@ -542,7 +597,7 @@ privsep_preauth(struct ssh *ssh)
+ 		/* Arrange for logging to be sent to the monitor */
+ 		set_log_handler(mm_log_handler, pmonitor);
+ 
+-		privsep_preauth_child();
++		privsep_preauth_child(ssh);
+ 		setproctitle("%s", "[net]");
+ 		if (box != NULL)
+ 			ssh_sandbox_child(box);
+@@ -594,7 +649,7 @@ privsep_postauth(struct ssh *ssh, Authct
+ 		set_log_handler(mm_log_handler, pmonitor);
+ 
+ 	/* Demote the private keys to public keys. */
+-	demote_sensitive_data();
++	demote_sensitive_data(ssh);
+ 
+ 	reseed_prngs();
+ 
+@@ -1057,7 +1112,7 @@ server_listen(void)
+  * from this function are in a forked subprocess.
+  */
+ static void
+-server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
++server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, int *config_s)
+ {
+ 	fd_set *fdset;
+ 	int i, j, ret, maxfd;
+@@ -1112,6 +1167,7 @@ server_accept_loop(int *sock_in, int *so
+ 		if (received_sigterm) {
+ 			logit("Received signal %d; terminating.",
+ 			    (int) received_sigterm);
++			destroy_sensitive_data(ssh, 0);
+ 			close_listen_socks();
+ 			if (options.pid_file != NULL)
+ 				unlink(options.pid_file);
+@@ -1978,7 +2034,7 @@ main(int ac, char **av)
+ #endif
+ 
+ 		/* Accept a connection and return in a forked child */
+-		server_accept_loop(&sock_in, &sock_out,
++		server_accept_loop(ssh, &sock_in, &sock_out,
+ 		    &newsock, config_s);
+ 	}
+ 
+@@ -2222,6 +2278,9 @@ main(int ac, char **av)
+ 	do_authenticated(ssh, authctxt);
+ 
+ 	/* The connection has been terminated. */
++	packet_destroy_all(ssh, 1, 1);
++	destroy_sensitive_data(ssh, 1);
++
+ 	ssh_packet_get_bytes(ssh, &ibytes, &obytes);
+ 	verbose("Transferred: sent %llu, received %llu bytes",
+ 	    (unsigned long long)obytes, (unsigned long long)ibytes);
+@@ -2401,6 +2460,15 @@ do_ssh2_kex(struct ssh *ssh)
+ void
+ cleanup_exit(int i)
+ {
++	static int in_cleanup = 0;
++	int is_privsep_child;
++
++	/* cleanup_exit can be called at the very least from the privsep
++	   wrappers used for auditing.  Make sure we don't recurse
++	   indefinitely. */
++	if (in_cleanup)
++		_exit(i);
++	in_cleanup = 1;
+ 	if (the_active_state != NULL && the_authctxt != NULL) {
+ 		do_cleanup(the_active_state, the_authctxt);
+ 		if (use_privsep && privsep_is_preauth &&
+@@ -2414,9 +2482,16 @@ cleanup_exit(int i)
+ 				    pmonitor->m_pid, strerror(errno));
+ 		}
+ 	}
++	is_privsep_child = use_privsep && pmonitor != NULL && pmonitor->m_pid == 0;
++	if (sensitive_data.host_keys != NULL && the_active_state != NULL)
++		destroy_sensitive_data(the_active_state, is_privsep_child);
++	if (the_active_state != NULL)
++		packet_destroy_all(the_active_state, 1, is_privsep_child);
+ #ifdef SSH_AUDIT_EVENTS
+ 	/* done after do_cleanup so it can cancel the PAM auth 'thread' */
+-	if (the_active_state != NULL && (!use_privsep || mm_is_monitor()))
++	if (the_active_state != NULL &&
++	    (the_authctxt == NULL || !the_authctxt->authenticated) &&
++	    (!use_privsep || mm_is_monitor()))
+ 		audit_event(the_active_state, SSH_CONNECTION_ABANDON);
+ #endif
+ 	_exit(i);
+diff -up openssh/sshkey.c.audit openssh/sshkey.c
+--- openssh/sshkey.c.audit	2019-04-03 17:02:20.657885510 +0200
++++ openssh/sshkey.c	2019-04-03 17:02:20.718886088 +0200
+@@ -331,6 +331,38 @@ sshkey_type_is_valid_ca(int type)
+ }
+ 
+ int
++sshkey_is_private(const struct sshkey *k)
++{
++      switch (k->type) {
++#ifdef WITH_OPENSSL
++      case KEY_RSA_CERT:
++      case KEY_RSA: {
++              const BIGNUM *d;
++              RSA_get0_key(k->rsa, NULL, NULL, &d);
++              return d != NULL;
++          }
++      case KEY_DSA_CERT:
++      case KEY_DSA: {
++              const BIGNUM *priv_key;
++              DSA_get0_key(k->dsa, NULL, &priv_key);
++              return priv_key != NULL;
++          }
++#ifdef OPENSSL_HAS_ECC
++      case KEY_ECDSA_CERT:
++      case KEY_ECDSA:
++              return EC_KEY_get0_private_key(k->ecdsa) != NULL;
++#endif /* OPENSSL_HAS_ECC */
++#endif /* WITH_OPENSSL */
++      case KEY_ED25519_CERT:
++      case KEY_ED25519:
++              return (k->ed25519_pk != NULL);
++      default:
++              /* fatal("key_is_private: bad key type %d", k->type); */
++              return 0;
++      }
++}
++
++int
+ sshkey_is_cert(const struct sshkey *k)
+ {
+ 	if (k == NULL)
+diff -up openssh/sshkey.h.audit openssh/sshkey.h
+--- openssh/sshkey.h.audit	2019-04-03 17:02:20.657885510 +0200
++++ openssh/sshkey.h	2019-04-03 17:02:20.718886088 +0200
+@@ -148,6 +148,7 @@ u_int		 sshkey_size(const struct sshkey
+ int		 sshkey_unshield_private(struct sshkey *);
+ 
+ int	 sshkey_type_from_name(const char *);
++int	 sshkey_is_private(const struct sshkey *);
+ int	 sshkey_is_cert(const struct sshkey *);
+ int	 sshkey_is_sk(const struct sshkey *);
+ int	 sshkey_type_is_cert(int);
diff --git a/openssh-7.6p1-cleanup-selinux.patch b/openssh-7.6p1-cleanup-selinux.patch
new file mode 100644
index 0000000..08cd349
--- /dev/null
+++ b/openssh-7.6p1-cleanup-selinux.patch
@@ -0,0 +1,271 @@
+diff -up openssh/auth2-pubkey.c.refactor openssh/auth2-pubkey.c
+--- openssh/auth2-pubkey.c.refactor	2019-04-04 13:19:12.188821236 +0200
++++ openssh/auth2-pubkey.c	2019-04-04 13:19:12.276822078 +0200
+@@ -72,6 +72,9 @@
+ extern ServerOptions options;
+ extern u_char *session_id2;
+ extern u_int session_id2_len;
++extern int inetd_flag;
++extern int rexeced_flag;
++extern Authctxt *the_authctxt;
+ 
+ static char *
+ format_key(const struct sshkey *key)
+@@ -511,7 +514,8 @@ match_principals_command(struct ssh *ssh
+ 
+ 	if ((pid = subprocess("AuthorizedPrincipalsCommand", runas_pw, command,
+ 	    ac, av, &f,
+-	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0)
++	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
++	    (inetd_flag && !rexeced_flag), the_authctxt)) == 0)
+ 		goto out;
+ 
+ 	uid_swapped = 1;
+@@ -981,7 +985,8 @@ user_key_command_allowed2(struct ssh *ss
+ 
+ 	if ((pid = subprocess("AuthorizedKeysCommand", runas_pw, command,
+ 	    ac, av, &f,
+-	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0)
++	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
++	    (inetd_flag && !rexeced_flag), the_authctxt)) == 0)
+ 		goto out;
+ 
+ 	uid_swapped = 1;
+diff -up openssh/auth.c.refactor openssh/auth.c
+--- openssh/auth.c.refactor	2019-04-04 13:19:12.235821686 +0200
++++ openssh/auth.c	2019-04-04 13:19:12.276822078 +0200
+@@ -756,7 +756,8 @@ auth_get_canonical_hostname(struct ssh *
+  */
+ pid_t
+ subprocess(const char *tag, struct passwd *pw, const char *command,
+-    int ac, char **av, FILE **child, u_int flags)
++    int ac, char **av, FILE **child, u_int flags, int inetd,
++    void *the_authctxt)
+ {
+ 	FILE *f = NULL;
+ 	struct stat st;
+@@ -872,7 +873,7 @@ subprocess(const char *tag, struct passw
+ 		}
+ 
+ #ifdef WITH_SELINUX
+-		if (sshd_selinux_setup_env_variables() < 0) {
++		if (sshd_selinux_setup_env_variables(inetd, the_authctxt) < 0) {
+ 			error ("failed to copy environment:  %s",
+ 			    strerror(errno));
+ 			_exit(127);
+diff -up openssh/auth.h.refactor openssh/auth.h
+--- openssh/auth.h.refactor	2019-04-04 13:19:12.251821839 +0200
++++ openssh/auth.h	2019-04-04 13:19:12.276822078 +0200
+@@ -235,7 +235,7 @@ struct passwd *fakepw(void);
+ #define	SSH_SUBPROCESS_STDOUT_CAPTURE  (1<<1)  /* Redirect stdout */
+ #define	SSH_SUBPROCESS_STDERR_DISCARD  (1<<2)  /* Discard stderr */
+ pid_t	subprocess(const char *, struct passwd *,
+-    const char *, int, char **, FILE **, u_int flags);
++    const char *, int, char **, FILE **, u_int flags, int, void *);
+ 
+ int	 sys_auth_passwd(struct ssh *, const char *);
+ 
+diff -up openssh/openbsd-compat/port-linux.h.refactor openssh/openbsd-compat/port-linux.h
+--- openssh/openbsd-compat/port-linux.h.refactor	2019-04-04 13:19:12.256821887 +0200
++++ openssh/openbsd-compat/port-linux.h	2019-04-04 13:19:12.276822078 +0200
+@@ -26,8 +26,8 @@ void ssh_selinux_setfscreatecon(const ch
+ 
+ int sshd_selinux_enabled(void);
+ void sshd_selinux_copy_context(void);
+-void sshd_selinux_setup_exec_context(char *);
+-int sshd_selinux_setup_env_variables(void);
++void sshd_selinux_setup_exec_context(char *, int, int(char *, const char *), void *, int);
++int sshd_selinux_setup_env_variables(int inetd, void *);
+ void sshd_selinux_change_privsep_preauth_context(void);
+ #endif
+ 
+diff -up openssh/openbsd-compat/port-linux-sshd.c.refactor openssh/openbsd-compat/port-linux-sshd.c
+--- openssh/openbsd-compat/port-linux-sshd.c.refactor	2019-04-04 13:19:12.256821887 +0200
++++ openssh/openbsd-compat/port-linux-sshd.c	2019-04-04 13:19:12.276822078 +0200
+@@ -49,11 +49,6 @@
+ #include <unistd.h>
+ #endif
+ 
+-extern ServerOptions options;
+-extern Authctxt *the_authctxt;
+-extern int inetd_flag;
+-extern int rexeced_flag;
+-
+ /* Wrapper around is_selinux_enabled() to log its return value once only */
+ int
+ sshd_selinux_enabled(void)
+@@ -223,7 +218,8 @@ get_user_context(const char *sename, con
+ }
+ 
+ static void
+-ssh_selinux_get_role_level(char **role, const char **level)
++ssh_selinux_get_role_level(char **role, const char **level,
++    Authctxt *the_authctxt)
+ {
+ 	*role = NULL;
+ 	*level = NULL;
+@@ -241,8 +237,8 @@ ssh_selinux_get_role_level(char **role,
+ 
+ /* 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)
++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;
+@@ -250,7 +246,7 @@ sshd_selinux_getctxbyname(char *pwname,
+ 	int r = 0;
+ 	context_t con = NULL;
+ 
+-	ssh_selinux_get_role_level(&role, &reqlvl);
++	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
+ 
+ #ifdef HAVE_GETSEUSERBYNAME
+ 	if ((r=getseuserbyname(pwname, &sename, &lvl)) != 0) {
+@@ -272,7 +268,7 @@ sshd_selinux_getctxbyname(char *pwname,
+ 
+ 	if (r == 0) {
+ 		/* If launched from xinetd, we must use current level */
+-		if (inetd_flag && !rexeced_flag) {
++		if (inetd) {
+ 			security_context_t sshdsc=NULL;
+ 
+ 			if (getcon_raw(&sshdsc) < 0)
+@@ -333,7 +329,8 @@ sshd_selinux_getctxbyname(char *pwname,
+ 
+ /* Setup environment variables for pam_selinux */
+ static int
+-sshd_selinux_setup_variables(int(*set_it)(char *, const char *))
++sshd_selinux_setup_variables(int(*set_it)(char *, const char *), int inetd,
++    Authctxt *the_authctxt)
+ {
+ 	const char *reqlvl;
+ 	char *role;
+@@ -342,11 +339,11 @@ sshd_selinux_setup_variables(int(*set_it
+ 
+ 	debug3("%s: setting execution context", __func__);
+ 
+-	ssh_selinux_get_role_level(&role, &reqlvl);
++	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
+ 
+ 	rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
+ 
+-	if (inetd_flag && !rexeced_flag) {
++	if (inetd) {
+ 		use_current = "1";
+ 	} else {
+ 		use_current = "";
+@@ -362,9 +359,10 @@ sshd_selinux_setup_variables(int(*set_it
+ }
+ 
+ static int
+-sshd_selinux_setup_pam_variables(void)
++sshd_selinux_setup_pam_variables(int inetd,
++    int(pam_setenv)(char *, const char *), Authctxt *the_authctxt)
+ {
+-	return sshd_selinux_setup_variables(do_pam_putenv);
++	return sshd_selinux_setup_variables(pam_setenv, inetd, the_authctxt);
+ }
+ 
+ static int
+@@ -374,25 +372,28 @@ do_setenv(char *name, const char *value)
+ }
+ 
+ int
+-sshd_selinux_setup_env_variables(void)
++sshd_selinux_setup_env_variables(int inetd, void *the_authctxt)
+ {
+-	return sshd_selinux_setup_variables(do_setenv);
++	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)
++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 (options.use_pam) {
++	if (use_pam) {
+ 		/* do not compute context, just setup environment for pam_selinux */
+-		if (sshd_selinux_setup_pam_variables()) {
++		if (sshd_selinux_setup_pam_variables(inetd, pam_setenv, authctxt)) {
+ 			switch (security_getenforce()) {
+ 			case -1:
+ 				fatal("%s: security_getenforce() failed", __func__);
+@@ -410,7 +411,7 @@ sshd_selinux_setup_exec_context(char *pw
+ 
+ 	debug3("%s: setting execution context", __func__);
+ 
+-	r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx);
++	r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx, inetd, authctxt);
+ 	if (r >= 0) {
+ 		r = setexeccon(user_ctx);
+ 		if (r < 0) {
+diff -up openssh/platform.c.refactor openssh/platform.c
+--- openssh/platform.c.refactor	2019-04-04 13:19:12.204821389 +0200
++++ openssh/platform.c	2019-04-04 13:19:12.277822088 +0200
+@@ -32,6 +32,9 @@
+ 
+ extern int use_privsep;
+ extern ServerOptions options;
++extern int inetd_flag;
++extern int rexeced_flag;
++extern Authctxt *the_authctxt;
+ 
+ void
+ platform_pre_listen(void)
+@@ -183,7 +186,9 @@ platform_setusercontext_post_groups(stru
+ 	}
+ #endif /* HAVE_SETPCRED */
+ #ifdef WITH_SELINUX
+-	sshd_selinux_setup_exec_context(pw->pw_name);
++	sshd_selinux_setup_exec_context(pw->pw_name,
++	    (inetd_flag && !rexeced_flag), do_pam_putenv, the_authctxt,
++	    options.use_pam);
+ #endif
+ }
+ 
+diff -up openssh/sshd.c.refactor openssh/sshd.c
+--- openssh/sshd.c.refactor	2019-04-04 13:19:12.275822068 +0200
++++ openssh/sshd.c	2019-04-04 13:19:51.270195262 +0200
+@@ -158,7 +158,7 @@ int debug_flag = 0;
+ static int test_flag = 0;
+ 
+ /* Flag indicating that the daemon is being started from inetd. */
+-static int inetd_flag = 0;
++int inetd_flag = 0;
+ 
+ /* Flag indicating that sshd should not detach and become a daemon. */
+ static int no_daemon_flag = 0;
+@@ -171,7 +171,7 @@ static char **saved_argv;
+ static int saved_argc;
+ 
+ /* re-exec */
+-static int rexeced_flag = 0;
++int rexeced_flag = 0;
+ static int rexec_flag = 1;
+ static int rexec_argc = 0;
+ static char **rexec_argv;
+@@ -2192,7 +2192,9 @@ main(int ac, char **av)
+ 	}
+ #endif
+ #ifdef WITH_SELINUX
+-	sshd_selinux_setup_exec_context(authctxt->pw->pw_name);
++	sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
++	    (inetd_flag && !rexeced_flag), do_pam_putenv, the_authctxt,
++	    options.use_pam);
+ #endif
+ #ifdef USE_PAM
+ 	if (options.use_pam) {
diff --git a/openssh-7.7p1-fips.patch b/openssh-7.7p1-fips.patch
new file mode 100644
index 0000000..19f3d97
--- /dev/null
+++ b/openssh-7.7p1-fips.patch
@@ -0,0 +1,457 @@
+diff -up openssh-8.0p1/cipher-ctr.c.fips openssh-8.0p1/cipher-ctr.c
+--- openssh-8.0p1/cipher-ctr.c.fips	2019-07-23 14:55:45.326525641 +0200
++++ openssh-8.0p1/cipher-ctr.c	2019-07-23 14:55:45.401526401 +0200
+@@ -179,7 +179,8 @@ evp_aes_128_ctr(void)
+ 	aes_ctr.do_cipher = ssh_aes_ctr;
+ #ifndef SSH_OLD_EVP
+ 	aes_ctr.flags = EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH |
+-	    EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CUSTOM_IV;
++	    EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CUSTOM_IV |
++	    EVP_CIPH_FLAG_FIPS;
+ #endif
+ 	return (&aes_ctr);
+ }
+diff -up openssh-8.0p1/dh.c.fips openssh-8.0p1/dh.c
+--- openssh-8.0p1/dh.c.fips	2019-04-18 00:52:57.000000000 +0200
++++ openssh-8.0p1/dh.c	2019-07-23 14:55:45.401526401 +0200
+@@ -152,6 +152,12 @@ choose_dh(int min, int wantbits, int max
+ 	int best, bestcount, which, linenum;
+ 	struct dhgroup dhg;
+ 
++	if (FIPS_mode()) {
++		logit("Using arbitrary primes is not allowed in FIPS mode."
++		    " Falling back to known groups.");
++		return (dh_new_group_fallback(max));
++	}
++
+ 	if ((f = fopen(_PATH_DH_MODULI, "r")) == NULL) {
+ 		logit("WARNING: could not open %s (%s), using fixed modulus",
+ 		    _PATH_DH_MODULI, strerror(errno));
+@@ -489,4 +495,38 @@ dh_estimate(int bits)
+ 	return 8192;
+ }
+ 
++/*
++ * Compares the received DH parameters with known-good groups,
++ * which might be either from group14, group16 or group18.
++ */
++int
++dh_is_known_group(const DH *dh)
++{
++	const BIGNUM *p, *g;
++	const BIGNUM *known_p, *known_g;
++	DH *known = NULL;
++	int bits = 0, rv = 0;
++
++	DH_get0_pqg(dh, &p, NULL, &g);
++	bits = BN_num_bits(p);
++
++	if (bits <= 3072) {
++		known = dh_new_group14();
++	} else if (bits <= 6144) {
++		known = dh_new_group16();
++	} else {
++		known = dh_new_group18();
++	}
++
++	DH_get0_pqg(known, &known_p, NULL, &known_g);
++
++	if (BN_cmp(g, known_g) == 0 &&
++	    BN_cmp(p, known_p) == 0) {
++		rv = 1;
++	}
++
++	DH_free(known);
++	return rv;
++}
++
+ #endif /* WITH_OPENSSL */
+diff -up openssh-8.0p1/dh.h.fips openssh-8.0p1/dh.h
+--- openssh-8.0p1/dh.h.fips	2019-04-18 00:52:57.000000000 +0200
++++ openssh-8.0p1/dh.h	2019-07-23 14:55:45.401526401 +0200
+@@ -43,6 +43,7 @@ DH	*dh_new_group_fallback(int);
+ 
+ int	 dh_gen_key(DH *, int);
+ int	 dh_pub_is_valid(const DH *, const BIGNUM *);
++int	 dh_is_known_group(const DH *);
+ 
+ u_int	 dh_estimate(int);
+ 
+diff -up openssh-8.0p1/kex.c.fips openssh-8.0p1/kex.c
+--- openssh-8.0p1/kex.c.fips	2019-07-23 14:55:45.395526340 +0200
++++ openssh-8.0p1/kex.c	2019-07-23 14:55:45.402526411 +0200
+@@ -199,7 +199,10 @@ kex_names_valid(const char *names)
+ 	for ((p = strsep(&cp, ",")); p && *p != '\0';
+ 	    (p = strsep(&cp, ","))) {
+ 		if (kex_alg_by_name(p) == NULL) {
+-			error("Unsupported KEX algorithm \"%.100s\"", p);
++			if (FIPS_mode())
++				error("\"%.100s\" is not allowed in FIPS mode", p);
++			else
++				error("Unsupported KEX algorithm \"%.100s\"", p);
+ 			free(s);
+ 			return 0;
+ 		}
+diff -up openssh-8.0p1/kexgexc.c.fips openssh-8.0p1/kexgexc.c
+--- openssh-8.0p1/kexgexc.c.fips	2019-04-18 00:52:57.000000000 +0200
++++ openssh-8.0p1/kexgexc.c	2019-07-23 14:55:45.402526411 +0200
+@@ -28,6 +28,7 @@
+ 
+ #ifdef WITH_OPENSSL
+ 
++#include <openssl/crypto.h>
+ #include <sys/types.h>
+ 
+ #include <openssl/dh.h>
+@@ -113,6 +114,10 @@ input_kex_dh_gex_group(int type, u_int32
+ 		r = SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+ 	}
++	if (FIPS_mode() && dh_is_known_group(kex->dh) == 0) {
++		r = SSH_ERR_INVALID_ARGUMENT;
++		goto out;
++	}
+ 	p = g = NULL; /* belong to kex->dh now */
+ 
+ 	/* generate and send 'e', client DH public key */
+diff -up openssh-8.0p1/myproposal.h.fips openssh-8.0p1/myproposal.h
+--- openssh-8.0p1/myproposal.h.fips	2019-04-18 00:52:57.000000000 +0200
++++ openssh-8.0p1/myproposal.h	2019-07-23 14:55:45.402526411 +0200
+@@ -111,6 +111,20 @@
+ 	"rsa-sha2-256," \
+ 	"ssh-rsa"
+ 
++#define	KEX_FIPS_PK_ALG	\
++	"ecdsa-sha2-nistp256-cert-v01@openssh.com," \
++	"ecdsa-sha2-nistp384-cert-v01@openssh.com," \
++	"ecdsa-sha2-nistp521-cert-v01@openssh.com," \
++	"rsa-sha2-512-cert-v01@openssh.com," \
++	"rsa-sha2-256-cert-v01@openssh.com," \
++	"ssh-rsa-cert-v01@openssh.com," \
++	"ecdsa-sha2-nistp256," \
++	"ecdsa-sha2-nistp384," \
++	"ecdsa-sha2-nistp521," \
++	"rsa-sha2-512," \
++	"rsa-sha2-256," \
++	"ssh-rsa"
++
+ #define	KEX_SERVER_ENCRYPT \
+ 	"chacha20-poly1305@openssh.com," \
+ 	"aes128-ctr,aes192-ctr,aes256-ctr," \
+@@ -134,6 +142,27 @@
+ 
+ #define KEX_CLIENT_MAC KEX_SERVER_MAC
+ 
++#define	KEX_FIPS_ENCRYPT \
++	"aes128-ctr,aes192-ctr,aes256-ctr," \
++	"aes128-cbc,3des-cbc," \
++	"aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se," \
++	"aes128-gcm@openssh.com,aes256-gcm@openssh.com"
++#define KEX_DEFAULT_KEX_FIPS		\
++	"ecdh-sha2-nistp256," \
++	"ecdh-sha2-nistp384," \
++	"ecdh-sha2-nistp521," \
++	"diffie-hellman-group-exchange-sha256," \
++	"diffie-hellman-group16-sha512," \
++	"diffie-hellman-group18-sha512," \
++	"diffie-hellman-group14-sha256"
++#define KEX_FIPS_MAC \
++	"hmac-sha1," \
++	"hmac-sha2-256," \
++	"hmac-sha2-512," \
++	"hmac-sha1-etm@openssh.com," \
++	"hmac-sha2-256-etm@openssh.com," \
++	"hmac-sha2-512-etm@openssh.com"
++
+ /* Not a KEX value, but here so all the algorithm defaults are together */
+ #define	SSH_ALLOWED_CA_SIGALGS	\
+ 	"ecdsa-sha2-nistp256," \
+diff -up openssh-8.0p1/readconf.c.fips openssh-8.0p1/readconf.c
+--- openssh-8.0p1/readconf.c.fips	2019-07-23 14:55:45.334525723 +0200
++++ openssh-8.0p1/readconf.c	2019-07-23 14:55:45.402526411 +0200
+@@ -2179,11 +2179,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 */
+-	def_cipher = match_filter_allowlist(KEX_CLIENT_ENCRYPT, all_cipher);
+-	def_mac = match_filter_allowlist(KEX_CLIENT_MAC, all_mac);
+-	def_kex = match_filter_allowlist(KEX_CLIENT_KEX, all_kex);
+-	def_key = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key);
+-	def_sig = match_filter_allowlist(SSH_ALLOWED_CA_SIGALGS, all_sig);
++	def_cipher = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_ENCRYPT : KEX_CLIENT_ENCRYPT), all_cipher);
++	def_mac = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_MAC : KEX_CLIENT_MAC), all_mac);
++	def_kex = match_filter_allowlist((FIPS_mode() ?
++	    KEX_DEFAULT_KEX_FIPS : KEX_CLIENT_KEX), all_kex);
++	def_key = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_PK_ALG : KEX_DEFAULT_PK_ALG), all_key);
++	def_sig = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_PK_ALG : SSH_ALLOWED_CA_SIGALGS), all_sig);
+ #define ASSEMBLE(what, defaults, all) \
+ 	do { \
+ 		if ((r = kex_assemble_names(&options->what, \
+diff -up openssh-8.0p1/sandbox-seccomp-filter.c.fips openssh-8.0p1/sandbox-seccomp-filter.c
+--- openssh-8.0p1/sandbox-seccomp-filter.c.fips	2019-07-23 14:55:45.373526117 +0200
++++ openssh-8.0p1/sandbox-seccomp-filter.c	2019-07-23 14:55:45.402526411 +0200
+@@ -137,6 +137,9 @@ static const struct sock_filter preauth_
+ #ifdef __NR_open
+ 	SC_DENY(__NR_open, EACCES),
+ #endif
++#ifdef __NR_socket
++	SC_DENY(__NR_socket, EACCES),
++#endif
+ #ifdef __NR_openat
+ 	SC_DENY(__NR_openat, EACCES),
+ #endif
+diff -up openssh-8.0p1/servconf.c.fips openssh-8.0p1/servconf.c
+--- openssh-8.0p1/servconf.c.fips	2019-07-23 14:55:45.361525996 +0200
++++ openssh-8.0p1/servconf.c	2019-07-23 14:55:45.403526421 +0200
+@@ -208,11 +208,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 */
+-	def_cipher = match_filter_allowlist(KEX_SERVER_ENCRYPT, all_cipher);
+-	def_mac = match_filter_allowlist(KEX_SERVER_MAC, all_mac);
+-	def_kex = match_filter_allowlist(KEX_SERVER_KEX, all_kex);
+-	def_key = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key);
+-	def_sig = match_filter_allowlist(SSH_ALLOWED_CA_SIGALGS, all_sig);
++	def_cipher = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_ENCRYPT : KEX_SERVER_ENCRYPT), all_cipher);
++	def_mac = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_MAC : KEX_SERVER_MAC), all_mac);
++	def_kex = match_filter_allowlist((FIPS_mode() ?
++	    KEX_DEFAULT_KEX_FIPS : KEX_SERVER_KEX), all_kex);
++	def_key = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_PK_ALG : KEX_DEFAULT_PK_ALG), all_key);
++	def_sig = match_filter_allowlist((FIPS_mode() ?
++	    KEX_FIPS_PK_ALG : SSH_ALLOWED_CA_SIGALGS), all_sig);
+ #define ASSEMBLE(what, defaults, all) \
+ 	do { \
+ 		if ((r = kex_assemble_names(&o->what, defaults, all)) != 0) \
+diff -up openssh-8.0p1/ssh.c.fips openssh-8.0p1/ssh.c
+--- openssh-8.0p1/ssh.c.fips	2019-07-23 14:55:45.378526168 +0200
++++ openssh-8.0p1/ssh.c	2019-07-23 14:55:45.403526421 +0200
+@@ -76,6 +76,7 @@
+ #include <openssl/evp.h>
+ #include <openssl/err.h>
+ #endif
++#include <openssl/crypto.h>
+ #include "openbsd-compat/openssl-compat.h"
+ #include "openbsd-compat/sys-queue.h"
+ 
+@@ -614,6 +626,10 @@ main(int ac, char **av)
+ 		dump_client_config(&options, host);
+ 		exit(0);
+ 	}
++
++	if (FIPS_mode()) {
++		debug("FIPS mode initialized");
++	}
+ 
+ 	/* Expand SecurityKeyProvider if it refers to an environment variable */
+ 	if (options.sk_provider != NULL && *options.sk_provider == '$' &&
+diff -up openssh-8.0p1/sshconnect2.c.fips openssh-8.0p1/sshconnect2.c
+--- openssh-8.0p1/sshconnect2.c.fips	2019-07-23 14:55:45.336525743 +0200
++++ openssh-8.0p1/sshconnect2.c	2019-07-23 14:55:45.403526421 +0200
+@@ -44,6 +44,8 @@
+ #include <vis.h>
+ #endif
+ 
++#include <openssl/crypto.h>
++
+ #include "openbsd-compat/sys-queue.h"
+ 
+ #include "xmalloc.h"
+@@ -198,36 +203,41 @@ ssh_kex2(struct ssh *ssh, char *host, st
+ 
+ #if defined(GSSAPI) && defined(WITH_OPENSSL)
+ 	if (options.gss_keyex) {
+-		/* Add the GSSAPI mechanisms currently supported on this
+-		 * client to the key exchange algorithm proposal */
+-		orig = myproposal[PROPOSAL_KEX_ALGS];
+-
+-		if (options.gss_server_identity) {
+-			gss_host = xstrdup(options.gss_server_identity);
+-		} else if (options.gss_trust_dns) {
+-			gss_host = remote_hostname(ssh);
+-			/* Fall back to specified host if we are using proxy command
+-			 * and can not use DNS on that socket */
+-			if (strcmp(gss_host, "UNKNOWN") == 0) {
+-				free(gss_host);
+-				gss_host = xstrdup(host);
+-			}
+-		} else {
+-			gss_host = xstrdup(host);
+-		}
+-
+-		gss = ssh_gssapi_client_mechanisms(gss_host,
+-		    options.gss_client_identity, options.gss_kex_algorithms);
+-		if (gss) {
+-			debug("Offering GSSAPI proposal: %s", gss);
+-			xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
+-			    "%s,%s", gss, orig);
+-
+-			/* If we've got GSSAPI algorithms, then we also support the
+-			 * 'null' hostkey, as a last resort */
+-			orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
+-			xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],
+-			    "%s,null", orig);
++		if (FIPS_mode()) {
++			logit("Disabling GSSAPIKeyExchange. Not usable in FIPS mode");
++			options.gss_keyex = 0;
++		} else {
++			/* Add the GSSAPI mechanisms currently supported on this
++			 * client to the key exchange algorithm proposal */
++			orig = myproposal[PROPOSAL_KEX_ALGS];
++
++			if (options.gss_server_identity) {
++				gss_host = xstrdup(options.gss_server_identity);
++			} else if (options.gss_trust_dns) {
++				gss_host = remote_hostname(ssh);
++				/* Fall back to specified host if we are using proxy command
++				 * and can not use DNS on that socket */
++				if (strcmp(gss_host, "UNKNOWN") == 0) {
++					free(gss_host);
++					gss_host = xstrdup(host);
++				}
++			} else {
++				gss_host = xstrdup(host);
++			}
++
++			gss = ssh_gssapi_client_mechanisms(gss_host,
++			    options.gss_client_identity, options.gss_kex_algorithms);
++			if (gss) {
++				debug("Offering GSSAPI proposal: %s", gss);
++				xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++				    "%s,%s", gss, orig);
++
++				/* If we've got GSSAPI algorithms, then we also support the
++				 * 'null' hostkey, as a last resort */
++				orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
++				xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],
++				    "%s,null", orig);
++			}
+ 		}
+ 	}
+ #endif
+diff -up openssh-8.0p1/sshd.c.fips openssh-8.0p1/sshd.c
+--- openssh-8.0p1/sshd.c.fips	2019-07-23 14:55:45.398526371 +0200
++++ openssh-8.0p1/sshd.c	2019-07-23 14:55:45.403526421 +0200
+@@ -66,6 +66,7 @@
+ #include <grp.h>
+ #include <pwd.h>
+ #include <signal.h>
++#include <syslog.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+@@ -77,6 +78,7 @@
+ #include <openssl/dh.h>
+ #include <openssl/bn.h>
+ #include <openssl/rand.h>
++#include <openssl/crypto.h>
+ #include "openbsd-compat/openssl-compat.h"
+ #endif
+ 
+@@ -1529,6 +1532,7 @@ main(int ac, char **av)
+ #endif
+ 	__progname = ssh_get_progname(av[0]);
+ 
++	OpenSSL_add_all_algorithms();
+ 	/* Save argv. Duplicate so setproctitle emulation doesn't clobber it */
+ 	saved_argc = ac;
+ 	rexec_argc = ac;
+@@ -1992,6 +2007,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);
+ 
++	if (FIPS_mode()) {
++		debug("FIPS mode initialized");
++	}
++
+ 	/* Chdir to the root directory so that the current disk can be
+ 	   unmounted if desired. */
+ 	if (chdir("/") == -1)
+@@ -2382,10 +2401,14 @@ do_ssh2_kex(struct ssh *ssh)
+ 	if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0)
+ 		orig = NULL;
+ 
+-	if (options.gss_keyex)
+-		gss = ssh_gssapi_server_mechanisms();
+-	else
+-		gss = NULL;
++	if (options.gss_keyex) {
++		if (FIPS_mode()) {
++			logit("Disabling GSSAPIKeyExchange. Not usable in FIPS mode");
++			options.gss_keyex = 0;
++		} else {
++			gss = ssh_gssapi_server_mechanisms();
++		}
++	}
+ 
+ 	if (gss && orig)
+ 		xasprintf(&newstr, "%s,%s", gss, orig);
+diff -up openssh-8.0p1/sshkey.c.fips openssh-8.0p1/sshkey.c
+--- openssh-8.0p1/sshkey.c.fips	2019-07-23 14:55:45.398526371 +0200
++++ openssh-8.0p1/sshkey.c	2019-07-23 14:55:45.404526431 +0200
+@@ -34,6 +34,7 @@
+ #include <openssl/evp.h>
+ #include <openssl/err.h>
+ #include <openssl/pem.h>
++#include <openssl/crypto.h>
+ #endif
+ 
+ #include "crypto_api.h"
+@@ -57,6 +58,7 @@
+ #define SSHKEY_INTERNAL
+ #include "sshkey.h"
+ #include "match.h"
++#include "log.h"
+ #include "ssh-sk.h"
+ 
+ #ifdef WITH_XMSS
+@@ -1591,6 +1593,8 @@ rsa_generate_private_key(u_int bits, RSA
+ 	}
+ 	if (!BN_set_word(f4, RSA_F4) ||
+ 	    !RSA_generate_key_ex(private, bits, f4, NULL)) {
++			if (FIPS_mode())
++				logit("%s: the key length might be unsupported by FIPS mode approved key generation method", __func__);
+ 		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
+ 	}
+diff -up openssh-8.0p1/ssh-keygen.c.fips openssh-8.0p1/ssh-keygen.c
+--- openssh-8.0p1/ssh-keygen.c.fips	2019-07-23 14:55:45.391526300 +0200
++++ openssh-8.0p1/ssh-keygen.c	2019-07-23 14:57:54.118830056 +0200
+@@ -199,6 +199,12 @@ type_bits_valid(int type, const char *na
+ #endif
+ 	}
+ #ifdef WITH_OPENSSL
++	if (FIPS_mode()) {
++		if (type == KEY_DSA)
++			fatal("DSA keys are not allowed in FIPS mode");
++		if (type == KEY_ED25519)
++			fatal("ED25519 keys are not allowed in FIPS mode");
++	}
+ 	switch (type) {
+ 	case KEY_DSA:
+ 		if (*bitsp != 1024)
+@@ -1029,9 +1035,17 @@ do_gen_all_hostkeys(struct passwd *pw)
+ 			first = 1;
+ 			printf("%s: generating new host keys: ", __progname);
+ 		}
++		type = sshkey_type_from_name(key_types[i].key_type);
++
++		/* Skip the keys that are not supported in FIPS mode */
++		if (FIPS_mode() && (type == KEY_DSA || type == KEY_ED25519)) {
++			logit("Skipping %s key in FIPS mode",
++			    key_types[i].key_type_display);
++			goto next;
++		}
++
+ 		printf("%s ", key_types[i].key_type_display);
+ 		fflush(stdout);
+-		type = sshkey_type_from_name(key_types[i].key_type);
+ 		if ((fd = mkstemp(prv_tmp)) == -1) {
+ 			error("Could not save your private key in %s: %s",
+			    prv_tmp, strerror(errno));
diff --git a/openssh-7.7p1-gssapi-new-unique.patch b/openssh-7.7p1-gssapi-new-unique.patch
new file mode 100644
index 0000000..506c79a
--- /dev/null
+++ b/openssh-7.7p1-gssapi-new-unique.patch
@@ -0,0 +1,647 @@
+diff --git a/auth-krb5.c b/auth-krb5.c
+index a5a81ed2..63f877f2 100644
+--- a/auth-krb5.c
++++ b/auth-krb5.c
+@@ -51,6 +51,7 @@
+ #include <unistd.h>
+ #include <string.h>
+ #include <krb5.h>
++#include <profile.h>
+ 
+ extern ServerOptions	 options;
+ 
+@@ -77,7 +78,7 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
+ #endif
+ 	krb5_error_code problem;
+ 	krb5_ccache ccache = NULL;
+-	int len;
++	char *ticket_name = NULL;
+ 	char *client, *platform_client;
+ 	const char *errmsg;
+ 
+@@ -163,7 +164,8 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
+ 		goto out;
+ 	}
+ 
+-	problem = ssh_krb5_cc_gen(authctxt->krb5_ctx, &authctxt->krb5_fwd_ccache);
++	problem = ssh_krb5_cc_new_unique(authctxt->krb5_ctx,
++	     &authctxt->krb5_fwd_ccache, &authctxt->krb5_set_env);
+ 	if (problem)
+ 		goto out;
+ 
+@@ -172,21 +174,20 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
+ 	if (problem)
+ 		goto out;
+ 
+-	problem= krb5_cc_store_cred(authctxt->krb5_ctx, authctxt->krb5_fwd_ccache,
++	problem = krb5_cc_store_cred(authctxt->krb5_ctx, authctxt->krb5_fwd_ccache,
+ 				 &creds);
+ 	if (problem)
+ 		goto out;
+ #endif
+ 
+-	authctxt->krb5_ticket_file = (char *)krb5_cc_get_name(authctxt->krb5_ctx, authctxt->krb5_fwd_ccache);
++	problem = krb5_cc_get_full_name(authctxt->krb5_ctx,
++	    authctxt->krb5_fwd_ccache, &ticket_name);
+ 
+-	len = strlen(authctxt->krb5_ticket_file) + 6;
+-	authctxt->krb5_ccname = xmalloc(len);
+-	snprintf(authctxt->krb5_ccname, len, "FILE:%s",
+-	    authctxt->krb5_ticket_file);
++	authctxt->krb5_ccname = xstrdup(ticket_name);
++	krb5_free_string(authctxt->krb5_ctx, ticket_name);
+ 
+ #ifdef USE_PAM
+-	if (options.use_pam)
++	if (options.use_pam && authctxt->krb5_set_env)
+ 		do_pam_putenv("KRB5CCNAME", authctxt->krb5_ccname);
+ #endif
+ 
+@@ -222,11 +223,54 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
+ void
+ krb5_cleanup_proc(Authctxt *authctxt)
+ {
++	struct stat krb5_ccname_stat;
++	char krb5_ccname[128], *krb5_ccname_dir_start, *krb5_ccname_dir_end;
++
+ 	debug("krb5_cleanup_proc called");
+ 	if (authctxt->krb5_fwd_ccache) {
+-		krb5_cc_destroy(authctxt->krb5_ctx, authctxt->krb5_fwd_ccache);
++		krb5_context ctx = authctxt->krb5_ctx;
++		krb5_cccol_cursor cursor;
++		krb5_ccache ccache;
++		int ret;
++
++		krb5_cc_destroy(ctx, authctxt->krb5_fwd_ccache);
+ 		authctxt->krb5_fwd_ccache = NULL;
++
++		ret = krb5_cccol_cursor_new(ctx, &cursor);
++		if (ret)
++			goto out;
++
++		ret = krb5_cccol_cursor_next(ctx, cursor, &ccache);
++		if (ret == 0 && ccache != NULL) {
++			/* There is at least one other ccache in collection
++			 * we can switch to */
++			krb5_cc_switch(ctx, ccache);
++		} else if (authctxt->krb5_ccname != NULL) {
++			/* Clean up the collection too */
++			strncpy(krb5_ccname, authctxt->krb5_ccname, sizeof(krb5_ccname) - 10);
++			krb5_ccname_dir_start = strchr(krb5_ccname, ':') + 1;
++			*krb5_ccname_dir_start++ = '\0';
++			if (strcmp(krb5_ccname, "DIR") == 0) {
++
++				strcat(krb5_ccname_dir_start, "/primary");
++
++				if (stat(krb5_ccname_dir_start, &krb5_ccname_stat) == 0) {
++					if (unlink(krb5_ccname_dir_start) == 0) {
++						krb5_ccname_dir_end = strrchr(krb5_ccname_dir_start, '/');
++						*krb5_ccname_dir_end = '\0';
++						if (rmdir(krb5_ccname_dir_start) == -1)
++							debug("cache dir '%s' remove failed: %s",
++							    krb5_ccname_dir_start, strerror(errno));
++					}
++					else
++						debug("cache primary file '%s', remove failed: %s",
++						    krb5_ccname_dir_start, strerror(errno));
++				}
++			}
++		}
++		krb5_cccol_cursor_free(ctx, &cursor);
+ 	}
++out:
+ 	if (authctxt->krb5_user) {
+ 		krb5_free_principal(authctxt->krb5_ctx, authctxt->krb5_user);
+ 		authctxt->krb5_user = NULL;
+@@ -237,36 +281,188 @@ krb5_cleanup_proc(Authctxt *authctxt)
+ 	}
+ }
+ 
+-#ifndef HEIMDAL
++
++#if !defined(HEIMDAL)
++int
++ssh_asprintf_append(char **dsc, const char *fmt, ...) {
++	char *src, *old;
++	va_list ap;
++	int i;
++
++	va_start(ap, fmt);
++	i = vasprintf(&src, fmt, ap);
++	va_end(ap);
++
++	if (i == -1 || src == NULL)
++		return -1;
++
++	old = *dsc;
++
++	i = asprintf(dsc, "%s%s", *dsc, src);
++	if (i == -1 || src == NULL) {
++		free(src);
++		return -1;
++	}
++
++	free(old);
++	free(src);
++
++	return i;
++}
++
++int
++ssh_krb5_expand_template(char **result, const char *template) {
++	char *p_n, *p_o, *r, *tmp_template;
++
++	debug3("%s: called, template = %s", __func__, template);
++	if (template == NULL)
++		return -1;
++
++	tmp_template = p_n = p_o = xstrdup(template);
++	r = xstrdup("");
++
++	while ((p_n = strstr(p_o, "%{")) != NULL) {
++
++		*p_n++ = '\0';
++		if (ssh_asprintf_append(&r, "%s", p_o) == -1)
++			goto cleanup;
++
++		if (strncmp(p_n, "{uid}", 5) == 0 || strncmp(p_n, "{euid}", 6) == 0 ||
++			strncmp(p_n, "{USERID}", 8) == 0) {
++			p_o = strchr(p_n, '}') + 1;
++			if (ssh_asprintf_append(&r, "%d", geteuid()) == -1)
++				goto cleanup;
++			continue;
++		}
++		else if (strncmp(p_n, "{TEMP}", 6) == 0) {
++			p_o = strchr(p_n, '}') + 1;
++			if (ssh_asprintf_append(&r, "/tmp") == -1)
++				goto cleanup;
++			continue;
++		} else {
++			p_o = strchr(p_n, '}') + 1;
++			*p_o = '\0';
++			debug("%s: unsupported token %s in %s", __func__, p_n, template);
++			/* unknown token, fallback to the default */
++			goto cleanup;
++		}
++	}
++
++	if (ssh_asprintf_append(&r, "%s", p_o) == -1)
++		goto cleanup;
++
++	*result = r;
++	free(tmp_template);
++	return 0;
++
++cleanup:
++	free(r);
++	free(tmp_template);
++	return -1;
++}
++
+ krb5_error_code
+-ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) {
+-	int tmpfd, ret, oerrno;
+-	char ccname[40];
++ssh_krb5_get_cctemplate(krb5_context ctx, char **ccname) {
++	profile_t p;
++	int ret = 0;
++	char *value = NULL;
++
++	debug3("%s: called", __func__);
++	ret = krb5_get_profile(ctx, &p);
++	if (ret)
++		return ret;
++
++	ret = profile_get_string(p, "libdefaults", "default_ccache_name", NULL, NULL, &value);
++	if (ret || !value)
++		return ret;
++
++	ret = ssh_krb5_expand_template(ccname, value);
++
++	debug3("%s: returning with ccname = %s", __func__, *ccname);
++	return ret;
++}
++
++krb5_error_code
++ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environment) {
++	int tmpfd, ret, oerrno, type_len;
++	char *ccname = NULL;
+ 	mode_t old_umask;
++	char *type = NULL, *colon = NULL;
+ 
+-	ret = snprintf(ccname, sizeof(ccname),
+-	    "FILE:/tmp/krb5cc_%d_XXXXXXXXXX", geteuid());
+-	if (ret < 0 || (size_t)ret >= sizeof(ccname))
+-		return ENOMEM;
+-
+-	old_umask = umask(0177);
+-	tmpfd = mkstemp(ccname + strlen("FILE:"));
+-	oerrno = errno;
+-	umask(old_umask);
+-	if (tmpfd == -1) {
+-		logit("mkstemp(): %.100s", strerror(oerrno));
+-		return oerrno;
+-	}
++	debug3("%s: called", __func__);
++	if (need_environment)
++		*need_environment = 0;
++	ret = ssh_krb5_get_cctemplate(ctx, &ccname);
++	if (ret || !ccname || options.kerberos_unique_ccache) {
++		/* Otherwise, go with the old method */
++		if (ccname)
++			free(ccname);
++		ccname = NULL;
++
++		ret = asprintf(&ccname,
++		    "FILE:/tmp/krb5cc_%d_XXXXXXXXXX", geteuid());
++		if (ret < 0)
++			return ENOMEM;
+ 
+-	if (fchmod(tmpfd,S_IRUSR | S_IWUSR) == -1) {
++		old_umask = umask(0177);
++		tmpfd = mkstemp(ccname + strlen("FILE:"));
+ 		oerrno = errno;
+-		logit("fchmod(): %.100s", strerror(oerrno));
++		umask(old_umask);
++		if (tmpfd == -1) {
++			logit("mkstemp(): %.100s", strerror(oerrno));
++			return oerrno;
++		}
++
++		if (fchmod(tmpfd,S_IRUSR | S_IWUSR) == -1) {
++			oerrno = errno;
++			logit("fchmod(): %.100s", strerror(oerrno));
++			close(tmpfd);
++			return oerrno;
++		}
++		/* make sure the KRB5CCNAME is set for non-standard location */
++		if (need_environment)
++			*need_environment = 1;
+ 		close(tmpfd);
+-		return oerrno;
+ 	}
+-	close(tmpfd);
+ 
+-	return (krb5_cc_resolve(ctx, ccname, ccache));
++	debug3("%s: setting default ccname to %s", __func__, ccname);
++	/* set the default with already expanded user IDs */
++	ret = krb5_cc_set_default_name(ctx, ccname);
++	if (ret)
++		return ret;
++
++	if ((colon = strstr(ccname, ":")) != NULL) {
++		type_len = colon - ccname;
++		type = malloc((type_len + 1) * sizeof(char));
++		if (type == NULL)
++			return ENOMEM;
++		strncpy(type, ccname, type_len);
++		type[type_len] = 0;
++	} else {
++		type = strdup(ccname);
++	}
++
++	/* If we have a credential cache from krb5.conf, we need to switch
++	 * a primary cache for this collection, if it supports that (non-FILE)
++	 */
++	if (krb5_cc_support_switch(ctx, type)) {
++		debug3("%s: calling cc_new_unique(%s)", __func__, ccname);
++		ret = krb5_cc_new_unique(ctx, type, NULL, ccache);
++		free(type);
++		if (ret)
++			return ret;
++
++		debug3("%s: calling cc_switch()", __func__);
++		return krb5_cc_switch(ctx, *ccache);
++	} else {
++		/* Otherwise, we can not create a unique ccname here (either
++		 * it is already unique from above or the type does not support
++		 * collections
++		 */
++		free(type);
++		debug3("%s: calling cc_resolve(%s)", __func__, ccname);
++		return (krb5_cc_resolve(ctx, ccname, ccache));
++	}
+ }
+ #endif /* !HEIMDAL */
+ #endif /* KRB5 */
+diff --git a/auth.h b/auth.h
+index 29491df9..fdab5040 100644
+--- a/auth.h
++++ b/auth.h
+@@ -82,6 +82,7 @@ struct Authctxt {
+ 	krb5_principal	 krb5_user;
+ 	char		*krb5_ticket_file;
+ 	char		*krb5_ccname;
++	int		 krb5_set_env;
+ #endif
+ 	struct sshbuf	*loginmsg;
+ 
+@@ -238,7 +239,7 @@ int	 sys_auth_passwd(struct ssh *, const char *);
+ int	 sys_auth_passwd(struct ssh *, const char *);
+ 
+ #if defined(KRB5) && !defined(HEIMDAL)
+-krb5_error_code ssh_krb5_cc_gen(krb5_context, krb5_ccache *);
++krb5_error_code ssh_krb5_cc_new_unique(krb5_context, krb5_ccache *, int *);
+ #endif
+ 
+ #endif /* AUTH_H */
+diff -up openssh-7.9p1/gss-serv-krb5.c.ccache_name openssh-7.9p1/gss-serv-krb5.c
+--- openssh-7.9p1/gss-serv-krb5.c.ccache_name	2019-03-01 15:17:42.708611802 +0100
++++ openssh-7.9p1/gss-serv-krb5.c	2019-03-01 15:17:42.713611844 +0100
+@@ -267,7 +267,7 @@ ssh_gssapi_krb5_cmdok(krb5_principal pri
+ /* This writes out any forwarded credentials from the structure populated
+  * during userauth. Called after we have setuid to the user */
+ 
+-static void
++static int
+ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ {
+ 	krb5_ccache ccache;
+@@ -276,14 +276,15 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 	OM_uint32 maj_status, min_status;
+ 	const char *new_ccname, *new_cctype;
+ 	const char *errmsg;
++	int set_env = 0;
+ 
+ 	if (client->creds == NULL) {
+ 		debug("No credentials stored");
+-		return;
++		return 0;
+ 	}
+ 
+ 	if (ssh_gssapi_krb5_init() == 0)
+-		return;
++		return 0;
+ 
+ #ifdef HEIMDAL
+ # ifdef HAVE_KRB5_CC_NEW_UNIQUE
+@@ -297,14 +298,14 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 		krb5_get_err_text(krb_context, problem));
+ # endif
+ 		krb5_free_error_message(krb_context, errmsg);
+-		return;
++		return 0;
+ 	}
+ #else
+-	if ((problem = ssh_krb5_cc_gen(krb_context, &ccache))) {
++	if ((problem = ssh_krb5_cc_new_unique(krb_context, &ccache, &set_env)) != 0) {
+ 		errmsg = krb5_get_error_message(krb_context, problem);
+-		logit("ssh_krb5_cc_gen(): %.100s", errmsg);
++		logit("ssh_krb5_cc_new_unique(): %.100s", errmsg);
+ 		krb5_free_error_message(krb_context, errmsg);
+-		return;
++		return 0;
+ 	}
+ #endif	/* #ifdef HEIMDAL */
+ 
+@@ -313,7 +314,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 		errmsg = krb5_get_error_message(krb_context, problem);
+ 		logit("krb5_parse_name(): %.100s", errmsg);
+ 		krb5_free_error_message(krb_context, errmsg);
+-		return;
++		return 0;
+ 	}
+ 
+ 	if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) {
+@@ -322,7 +323,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 		krb5_free_error_message(krb_context, errmsg);
+ 		krb5_free_principal(krb_context, princ);
+ 		krb5_cc_destroy(krb_context, ccache);
+-		return;
++		return 0;
+ 	}
+ 
+ 	krb5_free_principal(krb_context, princ);
+@@ -331,32 +332,21 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 	    client->creds, ccache))) {
+ 		logit("gss_krb5_copy_ccache() failed");
+ 		krb5_cc_destroy(krb_context, ccache);
+-		return;
++		return 0;
+ 	}
+ 
+ 	new_cctype = krb5_cc_get_type(krb_context, ccache);
+ 	new_ccname = krb5_cc_get_name(krb_context, ccache);
+-
+-	client->store.envvar = "KRB5CCNAME";
+-#ifdef USE_CCAPI
+-	xasprintf(&client->store.envval, "API:%s", new_ccname);
+-	client->store.filename = NULL;
+-#else
+-	if (new_ccname[0] == ':')
+-		new_ccname++;
+ 	xasprintf(&client->store.envval, "%s:%s", new_cctype, new_ccname);
+-	if (strcmp(new_cctype, "DIR") == 0) {
+-		char *p;
+-		p = strrchr(client->store.envval, '/');
+-		if (p)
+-			*p = '\0';
++
++	if (set_env) {
++		client->store.envvar = "KRB5CCNAME";
+ 	}
+ 	if ((strcmp(new_cctype, "FILE") == 0) || (strcmp(new_cctype, "DIR") == 0))
+ 		client->store.filename = xstrdup(new_ccname);
+-#endif
+ 
+ #ifdef USE_PAM
+-	if (options.use_pam)
++	if (options.use_pam && set_env)
+ 		do_pam_putenv(client->store.envvar, client->store.envval);
+ #endif
+ 
+@@ -361,7 +355,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl
+ 
+ 	client->store.data = krb_context;
+ 
+-	return;
++	return set_env;
+ }
+ 
+ int
+diff --git a/gss-serv.c b/gss-serv.c
+index 6cae720e..16e55cbc 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -320,13 +320,15 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ }
+ 
+ /* As user */
+-void
++int
+ ssh_gssapi_storecreds(void)
+ {
+ 	if (gssapi_client.mech && gssapi_client.mech->storecreds) {
+-		(*gssapi_client.mech->storecreds)(&gssapi_client);
++		return (*gssapi_client.mech->storecreds)(&gssapi_client);
+ 	} else
+ 		debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism");
++
++	return 0;
+ }
+ 
+ /* This allows GSSAPI methods to do things to the child's environment based
+@@ -498,9 +500,7 @@ ssh_gssapi_rekey_creds() {
+ 	char *envstr;
+ #endif
+ 
+-	if (gssapi_client.store.filename == NULL &&
+-	    gssapi_client.store.envval == NULL &&
+-	    gssapi_client.store.envvar == NULL)
++	if (gssapi_client.store.envval == NULL)
+ 		return;
+ 
+ 	ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store));
+diff -up openssh-7.9p1/servconf.c.ccache_name openssh-7.9p1/servconf.c
+--- openssh-7.9p1/servconf.c.ccache_name	2019-03-01 15:17:42.704611768 +0100
++++ openssh-7.9p1/servconf.c	2019-03-01 15:17:42.713611844 +0100
+@@ -123,6 +123,7 @@ initialize_server_options(ServerOptions
+ 	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;
+@@ -315,6 +316,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)
+@@ -447,7 +450,8 @@ typedef enum {
+ 	sPermitRootLogin, sLogFacility, sLogLevel,
+ 	sRhostsRSAAuthentication, sRSAAuthentication,
+ 	sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
+-	sKerberosGetAFSToken, sChallengeResponseAuthentication,
++	sKerberosGetAFSToken, sKerberosUniqueCCache,
++	sChallengeResponseAuthentication,
+ 	sPasswordAuthentication, sKbdInteractiveAuthentication,
+ 	sListenAddress, sAddressFamily,
+ 	sPrintMotd, sPrintLastLog, sIgnoreRhosts,
+@@ -526,11 +530,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 },
+@@ -1437,6 +1443,10 @@ process_server_config_line(ServerOptions *options, char *line,
+ 		intptr = &options->kerberos_get_afs_token;
+ 		goto parse_flag;
+ 
++	case sKerberosUniqueCCache:
++		intptr = &options->kerberos_unique_ccache;
++		goto parse_flag;
++
+ 	case sGssAuthentication:
+ 		intptr = &options->gss_authentication;
+ 		goto parse_flag;
+@@ -2507,6 +2517,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);
+diff --git a/servconf.h b/servconf.h
+index db8362c6..4fa42d64 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -123,6 +123,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 */
+diff --git a/session.c b/session.c
+index 85df6a27..480a5ead 100644
+--- a/session.c
++++ b/session.c
+@@ -1033,7 +1033,8 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
+ 	/* Allow any GSSAPI methods that we've used to alter
+ 	 * the child's environment as they see fit
+ 	 */
+-	ssh_gssapi_do_child(&env, &envsize);
++	if (s->authctxt->krb5_set_env)
++		ssh_gssapi_do_child(&env, &envsize);
+ #endif
+ 
+ 	/* Set basic environment. */
+@@ -1105,7 +1106,7 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
+ 	}
+ #endif
+ #ifdef KRB5
+-	if (s->authctxt->krb5_ccname)
++	if (s->authctxt->krb5_ccname && s->authctxt->krb5_set_env)
+ 		child_set_env(&env, &envsize, "KRB5CCNAME",
+ 		    s->authctxt->krb5_ccname);
+ #endif
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 6593e422..245178af 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -83,7 +82,7 @@ typedef struct ssh_gssapi_mech_struct {
+ 	int (*dochild) (ssh_gssapi_client *);
+ 	int (*userok) (ssh_gssapi_client *, char *);
+ 	int (*localname) (ssh_gssapi_client *, char **);
+-	void (*storecreds) (ssh_gssapi_client *);
++	int (*storecreds) (ssh_gssapi_client *);
+ 	int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *);
+ } ssh_gssapi_mech;
+ 
+@@ -127,7 +126,7 @@ int ssh_gssapi_userok(char *name);
+ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_do_child(char ***, u_int *);
+ void ssh_gssapi_cleanup_creds(void);
+-void ssh_gssapi_storecreds(void);
++int ssh_gssapi_storecreds(void);
+ const char *ssh_gssapi_displayname(void);
+ 
+ char *ssh_gssapi_server_mechanisms(void);
+diff --git a/sshd.c b/sshd.c
+index edbe815c..89514e8a 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2162,7 +2162,7 @@ main(int ac, char **av)
+ #ifdef GSSAPI
+ 	if (options.gss_authentication) {
+ 		temporarily_use_uid(authctxt->pw);
+-		ssh_gssapi_storecreds();
++		authctxt->krb5_set_env = ssh_gssapi_storecreds();
+ 		restore_uid();
+ 	}
+ #endif
+diff --git a/sshd_config.5 b/sshd_config.5
+index c0683d4a..2349f477 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -860,6 +860,14 @@ Specifies whether to automatically destroy the user's ticket cache
+ file on logout.
+ The default is
+ .Cm yes .
++.It Cm KerberosUniqueCCache
++Specifies whether to store the acquired tickets in the per-session credential
++cache under /tmp/ or whether to use per-user credential cache as configured in
++.Pa /etc/krb5.conf .
++The default value
++.Cm no
++can lead to overwriting previous tickets by subseqent connections to the same
++user account.
+ .It Cm KexAlgorithms
+ Specifies the available KEX (Key Exchange) algorithms.
+ Multiple algorithms must be comma-separated.
diff --git a/openssh-7.7p1-redhat.patch b/openssh-7.7p1-redhat.patch
new file mode 100644
index 0000000..6011593
--- /dev/null
+++ b/openssh-7.7p1-redhat.patch
@@ -0,0 +1,118 @@
+diff -up openssh/ssh_config.redhat openssh/ssh_config
+--- openssh/ssh_config.redhat	2020-02-11 23:28:35.000000000 +0100
++++ openssh/ssh_config	2020-02-13 18:13:39.180641839 +0100
+@@ -43,3 +43,10 @@
+ #   ProxyCommand ssh -q -W %h:%p gateway.example.com
+ #   RekeyLimit 1G 1h
+ #   UserKnownHostsFile ~/.ssh/known_hosts.d/%k
++#
++# This system is following system-wide crypto policy.
++# To modify the crypto properties (Ciphers, MACs, ...), create a  *.conf
++#  file under  /etc/ssh/ssh_config.d/  which will be automatically
++# included below. For more information, see manual page for
++#  update-crypto-policies(8)  and  ssh_config(5).
++Include /etc/ssh/ssh_config.d/*.conf
+diff -up openssh/ssh_config_redhat.redhat openssh/ssh_config_redhat
+--- openssh/ssh_config_redhat.redhat	2020-02-13 18:13:39.180641839 +0100
++++ openssh/ssh_config_redhat	2020-02-13 18:13:39.180641839 +0100
+@@ -0,0 +1,21 @@
++# The options here are in the "Match final block" to be applied as the last
++# options and could be potentially overwritten by the user configuration
++Match final all
++	# Follow system-wide Crypto Policy, if defined:
++	Include /etc/crypto-policies/back-ends/openssh.config
++
++	GSSAPIAuthentication yes
++
++# If this option is set to yes then remote X11 clients will have full access
++# to the original X11 display. As virtually no X11 client supports the untrusted
++# mode correctly we set this to yes.
++	ForwardX11Trusted yes
++
++# Send locale-related environment variables
++	SendEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
++	SendEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
++	SendEnv LC_IDENTIFICATION LC_ALL LANGUAGE
++	SendEnv XMODIFIERS
++
++# Uncomment this if you want to use .local domain
++# Host *.local
+diff -up openssh/sshd_config.0.redhat openssh/sshd_config.0
+--- openssh/sshd_config.0.redhat	2020-02-12 14:30:04.000000000 +0100
++++ openssh/sshd_config.0	2020-02-13 18:13:39.181641855 +0100
+@@ -970,9 +970,9 @@ DESCRIPTION
+ 
+      SyslogFacility
+              Gives the facility code that is used when logging messages from
+-             sshd(8).  The possible values are: DAEMON, USER, AUTH, LOCAL0,
+-             LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7.  The
+-             default is AUTH.
++             sshd(8).  The possible values are: DAEMON, USER, AUTH, AUTHPRIV,
++             LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7.
++             The default is AUTH.
+ 
+      TCPKeepAlive
+              Specifies whether the system should send TCP keepalive messages
+diff -up openssh/sshd_config.5.redhat openssh/sshd_config.5
+--- openssh/sshd_config.5.redhat	2020-02-11 23:28:35.000000000 +0100
++++ openssh/sshd_config.5	2020-02-13 18:13:39.181641855 +0100
+@@ -1614,7 +1614,7 @@ By default no subsystems are defined.
+ .It Cm SyslogFacility
+ Gives the facility code that is used when logging messages from
+ .Xr sshd 8 .
+-The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2,
++The possible values are: DAEMON, USER, AUTH, AUTHPRIV, LOCAL0, LOCAL1, LOCAL2,
+ LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7.
+ The default is AUTH.
+ .It Cm TCPKeepAlive
+diff -up openssh/sshd_config.redhat openssh/sshd_config
+--- openssh/sshd_config.redhat	2020-02-11 23:28:35.000000000 +0100
++++ openssh/sshd_config	2020-02-13 18:20:16.349913681 +0100
+@@ -10,6 +10,14 @@
+ # possible, but leave them commented.  Uncommented options override the
+ # default value.
+
++# To modify the system-wide sshd configuration, create a  *.conf  file under
++#  /etc/ssh/sshd_config.d/  which will be automatically included below
++Include /etc/ssh/sshd_config.d/*.conf
++
++# If you want to change the port on a SELinux system, you have to tell
++# SELinux about this change.
++# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
++#
+ #Port 22
+ #AddressFamily any
+ #ListenAddress 0.0.0.0
+diff -up openssh/sshd_config_redhat.redhat openssh/sshd_config_redhat
+--- openssh/sshd_config_redhat.redhat	2020-02-13 18:14:02.268006439 +0100
++++ openssh/sshd_config_redhat	2020-02-13 18:19:20.765035947 +0100
+@@ -0,0 +1,29 @@
++# This system is following system-wide crypto policy. The changes to
++# crypto properties (Ciphers, MACs, ...) will not have any effect in
++# this or following included files. To override some configuration option,
++# write it before this block or include it before this file.
++# Please, see manual pages for update-crypto-policies(8) and sshd_config(5).
++Include /etc/crypto-policies/back-ends/opensshserver.config
++
++SyslogFacility AUTHPRIV
++
++PasswordAuthentication yes
++ChallengeResponseAuthentication no
++
++GSSAPIAuthentication yes
++GSSAPICleanupCredentials no
++
++UsePAM yes
++
++X11Forwarding yes
++
++# It is recommended to use pam_motd in /etc/pam.d/sshd instead of PrintMotd,
++# as it is more configurable and versatile than the built-in version.
++PrintMotd no
++
++# Accept locale-related environment variables
++AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
++AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
++AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
++AcceptEnv XMODIFIERS
++
diff --git a/openssh-7.8p1-UsePAM-warning.patch b/openssh-7.8p1-UsePAM-warning.patch
new file mode 100644
index 0000000..d4c53db
--- /dev/null
+++ b/openssh-7.8p1-UsePAM-warning.patch
@@ -0,0 +1,26 @@
+diff --git a/sshd.c b/sshd.c
+--- a/sshd.c
++++ b/sshd.c
+@@ -1701,6 +1701,10 @@ main(int ac, char **av)
+ 	parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
+ 	    cfg, &includes, NULL);
+ 
++	/* 'UsePAM no' is not supported in Fedora */
++	if (! options.use_pam)
++		logit("WARNING: 'UsePAM no' is not supported in Fedora and may cause several problems.");
++
+ 	/* Fill in default values for those options not explicitly set. */
+ 	fill_default_server_options(&options);
+ 
+diff --git a/sshd_config b/sshd_config
+--- a/sshd_config
++++ b/sshd_config
+@@ -101,6 +101,8 @@ GSSAPICleanupCredentials no
+ # If you just want the PAM account and session checks to run without
+ # PAM authentication, then enable this but set PasswordAuthentication
+ # and ChallengeResponseAuthentication to 'no'.
++# WARNING: 'UsePAM no' is not supported in Fedora and may cause several
++# problems.
+ #UsePAM no
+ 
+ #AllowAgentForwarding yes
diff --git a/openssh-7.8p1-role-mls.patch b/openssh-7.8p1-role-mls.patch
new file mode 100644
index 0000000..add4727
--- /dev/null
+++ b/openssh-7.8p1-role-mls.patch
@@ -0,0 +1,871 @@
+diff -up openssh/auth2.c.role-mls openssh/auth2.c
+--- openssh/auth2.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth2.c	2018-08-22 11:14:56.815430916 +0200
+@@ -256,6 +256,9 @@ input_userauth_request(int type, u_int32
+ 	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();
+ 
+@@ -268,6 +271,11 @@ input_userauth_request(int type, u_int32
+ 	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;
+ 
+@@ -296,8 +304,15 @@ input_userauth_request(int type, u_int32
+ 		    use_privsep ? " [net]" : "");
+ 		authctxt->service = xstrdup(service);
+ 		authctxt->style = style ? xstrdup(style) : NULL;
+-		if (use_privsep)
++#ifdef WITH_SELINUX
++		authctxt->role = role ? xstrdup(role) : NULL;
++#endif
++		if (use_privsep) {
+ 			mm_inform_authserv(service, style);
++#ifdef WITH_SELINUX
++			mm_inform_authrole(role);
++#endif
++		}
+ 		userauth_banner(ssh);
+ 		if (auth2_setup_methods_lists(authctxt) != 0)
+ 			ssh_packet_disconnect(ssh,
+diff -up openssh/auth2-gss.c.role-mls openssh/auth2-gss.c
+--- openssh/auth2-gss.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth2-gss.c	2018-08-22 11:15:42.459799171 +0200
+@@ -281,6 +281,7 @@ input_gssapi_mic(int type, u_int32_t ple
+ 	Authctxt *authctxt = ssh->authctxt;
+ 	Gssctxt *gssctxt;
+ 	int r, authenticated = 0;
++	char *micuser;
+ 	struct sshbuf *b;
+ 	gss_buffer_desc mic, gssbuf;
+ 	const char *displayname;
+@@ -298,7 +299,13 @@ input_gssapi_mic(int type, u_int32_t ple
+ 		fatal("%s: sshbuf_new failed", __func__);
+ 	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");
+ 
+ 	if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
+@@ -311,6 +318,8 @@ input_gssapi_mic(int type, u_int32_t ple
+ 		logit("GSSAPI MIC check failed");
+ 
+ 	sshbuf_free(b);
++	if (micuser != authctxt->user)
++		free(micuser);
+ 	free(mic.value);
+ 
+ 	if ((!use_privsep || mm_is_monitor()) &&
+diff -up openssh/auth2-hostbased.c.role-mls openssh/auth2-hostbased.c
+--- openssh/auth2-hostbased.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth2-hostbased.c	2018-08-22 11:14:56.816430924 +0200
+@@ -123,7 +123,16 @@ userauth_hostbased(struct ssh *ssh)
+ 	/* reconstruct packet */
+ 	if ((r = sshbuf_put_string(b, session_id2, session_id2_len)) != 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, "hostbased")) != 0 ||
+ 	    (r = sshbuf_put_string(b, pkalg, alen)) != 0 ||
+diff -up openssh/auth2-pubkey.c.role-mls openssh/auth2-pubkey.c
+--- openssh/auth2-pubkey.c.role-mls	2018-08-22 11:14:56.816430924 +0200
++++ openssh/auth2-pubkey.c	2018-08-22 11:17:07.331483958 +0200
+@@ -169,9 +169,16 @@ userauth_pubkey(struct ssh *ssh)
+ 			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 ||
+diff -up openssh/auth.h.role-mls openssh/auth.h
+--- openssh/auth.h.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth.h	2018-08-22 11:14:56.816430924 +0200
+@@ -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 -up openssh/auth-pam.c.role-mls openssh/auth-pam.c
+--- openssh/auth-pam.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth-pam.c	2018-08-22 11:14:56.816430924 +0200
+@@ -1172,7 +1172,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 -up openssh/auth-pam.h.role-mls openssh/auth-pam.h
+--- openssh/auth-pam.h.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/auth-pam.h	2018-08-22 11:14:56.817430932 +0200
+@@ -33,7 +33,7 @@ u_int do_pam_account(void);
+ void do_pam_session(struct ssh *);
+ void do_pam_setcred(int );
+ void do_pam_chauthtok(void);
+-int do_pam_putenv(char *, char *);
++int do_pam_putenv(char *, const char *);
+ char ** fetch_pam_environment(void);
+ char ** fetch_pam_child_environment(void);
+ void free_pam_environment(char **);
+diff -up openssh/misc.c.role-mls openssh/misc.c
+--- openssh/misc.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/misc.c	2018-08-22 11:14:56.817430932 +0200
+@@ -542,6 +542,7 @@ char *
+ colon(char *cp)
+ {
+ 	int flag = 0;
++	int start = 1;
+ 
+ 	if (*cp == ':')		/* Leading colon is part of file name. */
+ 		return NULL;
+@@ -557,6 +558,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;
+ }
+diff -up openssh/monitor.c.role-mls openssh/monitor.c
+--- openssh/monitor.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/monitor.c	2018-08-22 11:19:56.006844867 +0200
+@@ -115,6 +115,9 @@ int mm_answer_sign(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 *);
+@@ -189,6 +192,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
+@@ -796,6 +802,9 @@ mm_answer_pwnamallow(int sock, struct ss
+ 
+ 	/* 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
+@@ -842,6 +851,26 @@ mm_answer_authserv(int sock, struct sshb
+ 	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("%s: buffer error: %s", __func__, ssh_err(r));
++	debug3("%s: role=%s", __func__, authctxt->role);
++
++	if (strlen(authctxt->role) == 0) {
++		free(authctxt->role);
++		authctxt->role = NULL;
++	}
++
++	return (0);
++}
++#endif
++
+ int
+ mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m)
+ {
+@@ -1218,7 +1247,7 @@ monitor_valid_userblob(u_char *data, u_i
+ {
+ 	struct sshbuf *b;
+ 	const u_char *p;
+-	char *userstyle, *cp;
++	char *userstyle, *s, *cp;
+ 	size_t len;
+ 	u_char type;
+ 	int r, fail = 0;
+@@ -1251,6 +1280,8 @@ monitor_valid_userblob(u_char *data, u_i
+ 		fail++;
+ 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	if ((s = strchr(cp, '/')) != NULL)
++		*s = '\0';
+ 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ 	    authctxt->style ? ":" : "",
+ 	    authctxt->style ? authctxt->style : "");
+@@ -1286,7 +1317,7 @@ monitor_valid_hostbasedblob(u_char *data
+ {
+ 	struct sshbuf *b;
+ 	const u_char *p;
+-	char *cp, *userstyle;
++	char *cp, *s, *userstyle;
+ 	size_t len;
+ 	int r, fail = 0;
+ 	u_char type;
+@@ -1308,6 +1339,8 @@ monitor_valid_hostbasedblob(u_char *data
+ 		fail++;
+ 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	if ((s = strchr(p, '/')) != NULL)
++		*s = '\0';
+ 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ 	    authctxt->style ? ":" : "",
+ 	    authctxt->style ? authctxt->style : "");
+diff -up openssh/monitor.h.role-mls openssh/monitor.h
+--- openssh/monitor.h.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/monitor.h	2018-08-22 11:14:56.818430941 +0200
+@@ -55,6 +55,10 @@ enum monitor_reqtype {
+ 	MONITOR_REQ_GSSCHECKMIC = 48, MONITOR_ANS_GSSCHECKMIC = 49,
+ 	MONITOR_REQ_TERM = 50,
+ 
++#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 -up openssh/monitor_wrap.c.role-mls openssh/monitor_wrap.c
+--- openssh/monitor_wrap.c.role-mls	2018-08-22 11:14:56.818430941 +0200
++++ openssh/monitor_wrap.c	2018-08-22 11:21:47.938747968 +0200
+@@ -390,6 +390,27 @@ mm_inform_authserv(char *service, char *
+ 	sshbuf_free(m);
+ }
+ 
++/* Inform the privileged process about role */
++
++#ifdef WITH_SELINUX
++void
++mm_inform_authrole(char *role)
++{
++	int r;
++	struct sshbuf *m;
++
++	debug3("%s entering", __func__);
++
++	if ((m = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
++
++	sshbuf_free(m);
++}
++#endif
++
+ /* Do the password authentication */
+ int
+ mm_auth_password(struct ssh *ssh, char *password)
+diff -up openssh/monitor_wrap.h.role-mls openssh/monitor_wrap.h
+--- openssh/monitor_wrap.h.role-mls	2018-08-22 11:14:56.818430941 +0200
++++ openssh/monitor_wrap.h	2018-08-22 11:22:10.439929513 +0200
+@@ -44,6 +44,9 @@ DH *mm_choose_dh(int, int, int);
+     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 *);
+diff -up openssh/openbsd-compat/Makefile.in.role-mls openssh/openbsd-compat/Makefile.in
+--- openssh/openbsd-compat/Makefile.in.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/openbsd-compat/Makefile.in	2018-08-22 11:14:56.819430949 +0200
+@@ -92,7 +92,8 @@ PORTS=	port-aix.o \
+ 	port-linux.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 -up openssh/openbsd-compat/port-linux.c.role-mls openssh/openbsd-compat/port-linux.c
+--- openssh/openbsd-compat/port-linux.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/openbsd-compat/port-linux.c	2018-08-22 11:14:56.819430949 +0200
+@@ -100,37 +100,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)
+-{
+-	security_context_t 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)
+@@ -145,7 +114,11 @@ ssh_selinux_setup_pty(char *pwname, cons
+ 
+ 	debug3("%s: setting TTY context on %s", __func__, tty);
+ 
+-	user_ctx = ssh_selinux_getctxbyname(pwname);
++	if (getexeccon(&user_ctx) != 0) {
++		error("%s: getexeccon: %s", __func__, strerror(errno));
++		goto out;
++	}
++
+ 
+ 	/* XXX: should these calls fatal() upon failure in enforcing mode? */
+ 
+diff -up openssh/openbsd-compat/port-linux.h.role-mls openssh/openbsd-compat/port-linux.h
+--- openssh/openbsd-compat/port-linux.h.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/openbsd-compat/port-linux.h	2018-08-22 11:14:56.819430949 +0200
+@@ -20,9 +20,10 @@
+ #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 *);
++
++void sshd_selinux_setup_exec_context(char *);
+ #endif
+ 
+ #ifdef LINUX_OOM_ADJUST
+diff -up openssh/openbsd-compat/port-linux-sshd.c.role-mls openssh/openbsd-compat/port-linux-sshd.c
+--- openssh/openbsd-compat/port-linux-sshd.c.role-mls	2018-08-22 11:14:56.819430949 +0200
++++ openssh/openbsd-compat/port-linux-sshd.c	2018-08-22 11:14:56.819430949 +0200
+@@ -0,0 +1,425 @@
++/*
++ * 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
++
++extern ServerOptions options;
++extern Authctxt *the_authctxt;
++extern int inetd_flag;
++extern int rexeced_flag;
++
++/* 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("%s: src:%s dst:%s", __func__, 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)
++{
++	*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)
++{
++	char *sename, *lvl;
++	char *role;
++	const char *reqlvl;
++	int r = 0;
++	context_t con = NULL;
++
++	ssh_selinux_get_role_level(&role, &reqlvl);
++
++#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_flag && !rexeced_flag) {
++			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("%s: current connection level '%s'", __func__, 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("%s: Failed to get default SELinux security "
++		    "context for %s", __func__, 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_pam_variables(void)
++{
++	const char *reqlvl;
++	char *role;
++	char *use_current;
++	int rv;
++
++	debug3("%s: setting execution context", __func__);
++
++	ssh_selinux_get_role_level(&role, &reqlvl);
++
++	rv = do_pam_putenv("SELINUX_ROLE_REQUESTED", role ? role : "");
++
++	if (inetd_flag && !rexeced_flag) {
++		use_current = "1";
++	} else {
++		use_current = "";
++		rv = rv || do_pam_putenv("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
++	}
++
++	rv = rv || do_pam_putenv("SELINUX_USE_CURRENT_RANGE", use_current);
++
++	if (role != NULL)
++		free(role);
++
++	return rv;
++}
++
++/* Set the execution context to the default for the specified user */
++void
++sshd_selinux_setup_exec_context(char *pwname)
++{
++	security_context_t user_ctx = NULL;
++	int r = 0;
++	security_context_t default_ctx = NULL;
++
++	if (!ssh_selinux_enabled())
++		return;
++
++	if (options.use_pam) {
++		/* do not compute context, just setup environment for pam_selinux */
++		if (sshd_selinux_setup_pam_variables()) {
++			switch (security_getenforce()) {
++			case -1:
++				fatal("%s: security_getenforce() failed", __func__);
++			case 0:
++				error("%s: SELinux PAM variable setup failure. Continuing in permissive mode.",
++				    __func__);
++			break;
++			default:
++				fatal("%s: SELinux PAM variable setup failure. Aborting connection.",
++				    __func__);
++			}
++		}
++		return;
++	}
++
++	debug3("%s: setting execution context", __func__);
++
++	r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx);
++	if (r >= 0) {
++		r = setexeccon(user_ctx);
++		if (r < 0) {
++			error("%s: Failed to set SELinux execution context %s for %s",
++			    __func__, user_ctx, pwname);
++		}
++#ifdef HAVE_SETKEYCREATECON
++		else if (setkeycreatecon(user_ctx) < 0) {
++			error("%s: Failed to set SELinux keyring creation context %s for %s",
++			    __func__, 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("%s: security_getenforce() failed", __func__);
++		case 0:
++			error("%s: SELinux failure. Continuing in permissive mode.",
++			    __func__);
++			break;
++		default:
++			fatal("%s: SELinux failure. Aborting connection.",
++			    __func__);
++		}
++	}
++	if (user_ctx != NULL && user_ctx != default_ctx)
++		freecon(user_ctx);
++	if (default_ctx != NULL)
++		freecon(default_ctx);
++
++	debug3("%s: done", __func__);
++}
++
++#endif
++#endif
++
+diff -up openssh/platform.c.role-mls openssh/platform.c
+--- openssh/platform.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/platform.c	2018-08-22 11:14:56.819430949 +0200
+@@ -183,7 +183,7 @@ platform_setusercontext_post_groups(stru
+ 	}
+ #endif /* HAVE_SETPCRED */
+ #ifdef WITH_SELINUX
+-	ssh_selinux_setup_exec_context(pw->pw_name);
++	sshd_selinux_setup_exec_context(pw->pw_name);
+ #endif
+ }
+ 
+diff -up openssh/sshd.c.role-mls openssh/sshd.c
+--- openssh/sshd.c.role-mls	2018-08-20 07:57:29.000000000 +0200
++++ openssh/sshd.c	2018-08-22 11:14:56.820430957 +0200
+@@ -2186,6 +2186,9 @@ main(int ac, char **av)
+ 		restore_uid();
+ 	}
+ #endif
++#ifdef WITH_SELINUX
++	sshd_selinux_setup_exec_context(authctxt->pw->pw_name);
++#endif
+ #ifdef USE_PAM
+ 	if (options.use_pam) {
+ 		do_pam_setcred(1);
diff --git a/openssh-7.8p1-scp-ipv6.patch b/openssh-7.8p1-scp-ipv6.patch
new file mode 100644
index 0000000..8ae0948
--- /dev/null
+++ b/openssh-7.8p1-scp-ipv6.patch
@@ -0,0 +1,16 @@
+diff --git a/scp.c b/scp.c
+index 60682c68..9344806e 100644
+--- a/scp.c
++++ b/scp.c
+@@ -714,7 +714,9 @@ toremote(int argc, char **argv)
+ 			addargs(&alist, "%s", host);
+ 			addargs(&alist, "%s", cmd);
+ 			addargs(&alist, "%s", src);
+-			addargs(&alist, "%s%s%s:%s",
++			addargs(&alist,
++			    /* IPv6 address needs to be enclosed with sqare brackets */
++			    strchr(host, ':') != NULL ? "%s%s[%s]:%s" : "%s%s%s:%s",
+ 			    tuser ? tuser : "", tuser ? "@" : "",
+ 			    thost, targ);
+ 			if (do_local_cmd(&alist) != 0)
+
diff --git a/openssh-7.9p1-ssh-copy-id.patch b/openssh-7.9p1-ssh-copy-id.patch
new file mode 100644
index 0000000..24598b8
--- /dev/null
+++ b/openssh-7.9p1-ssh-copy-id.patch
@@ -0,0 +1,27 @@
+From 22bfdcf060b632b5a6ff603f8f42ff166c211a66 Mon Sep 17 00:00:00 2001
+From: Jakub Jelen <jjelen@redhat.com>
+Date: Tue, 29 Sep 2020 10:02:45 +0000
+Subject: [PATCH] Fail hard on the first failed attempt to write the
+ authorized_keys_file
+
+---
+ ssh-copy-id | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
+index 392f64f..e69a23f 100755
+--- a/contrib/ssh-copy-id
++++ b/contrib/ssh-copy-id
+@@ -251,7 +251,7 @@ installkeys_sh() {
+ 	cd;
+ 	umask 077;
+ 	mkdir -p $(dirname "${AUTH_KEY_FILE}") &&
+-	  { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || echo >> ${AUTH_KEY_FILE}; } &&
++	  { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || echo >> ${AUTH_KEY_FILE} || exit 1; } &&
+ 	  cat >> ${AUTH_KEY_FILE} ||
+ 	  exit 1;
+ 	if type restorecon >/dev/null 2>&1; then
+-- 
+GitLab
+
+
diff --git a/openssh-8.0p1-crypto-policies.patch b/openssh-8.0p1-crypto-policies.patch
new file mode 100644
index 0000000..fe2f7cd
--- /dev/null
+++ b/openssh-8.0p1-crypto-policies.patch
@@ -0,0 +1,502 @@
+diff -up openssh-8.2p1/ssh_config.5.crypto-policies openssh-8.2p1/ssh_config.5
+--- openssh-8.2p1/ssh_config.5.crypto-policies	2020-03-26 14:40:44.546775605 +0100
++++ openssh-8.2p1/ssh_config.5	2020-03-26 14:52:20.700649727 +0100
+@@ -359,17 +359,17 @@ or
+ .Qq *.c.example.com
+ domains.
+ .It Cm CASignatureAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies which algorithms are allowed for signing of certificates
+ by certificate authorities (CAs).
+-The default is:
+-.Bd -literal -offset indent
+-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
+-ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
+-.Ed
+-.Pp
+ .Xr ssh 1
+ will not accept host certificates signed using algorithms other than those
+ specified.
++.Pp
+ .It Cm CertificateFile
+ Specifies a file from which the user's certificate is read.
+ A corresponding private key must be provided separately in order
+@@ -424,20 +424,25 @@ If the option is set to
+ .Cm no ,
+ the check will not be executed.
+ .It Cm Ciphers
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the ciphers allowed and their order of preference.
+ Multiple ciphers must be comma-separated.
+ If the specified list begins with a
+ .Sq +
+-character, then the specified ciphers will be appended to the default set
+-instead of replacing them.
++character, then the specified ciphers will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified ciphers (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified ciphers will be placed at the head of the
+-default set.
++built-in openssh default set.
+ .Pp
+ The supported ciphers are:
+ .Bd -literal -offset indent
+@@ -453,13 +458,6 @@ aes256-gcm@openssh.com
+ chacha20-poly1305@openssh.com
+ .Ed
+ .Pp
+-The default is:
+-.Bd -literal -offset indent
+-chacha20-poly1305@openssh.com,
+-aes128-ctr,aes192-ctr,aes256-ctr,
+-aes128-gcm@openssh.com,aes256-gcm@openssh.com
+-.Ed
+-.Pp
+ The list of available ciphers may also be obtained using
+ .Qq ssh -Q cipher .
+ .It Cm ClearAllForwardings
+@@ -812,6 +810,11 @@ command line will be passed untouched to
+ The default is
+ .Dq no .
+ .It Cm GSSAPIKexAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ The list of key exchange algorithms that are offered for GSSAPI
+ key exchange. Possible values are
+ .Bd -literal -offset 3n
+@@ -824,10 +827,8 @@ gss-nistp256-sha256-,
+ gss-curve25519-sha256-
+ .Ed
+ .Pp
+-The default is
+-.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,
+-gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- .
+ This option only applies to connections using GSSAPI.
++.Pp
+ .It Cm HashKnownHosts
+ Indicates that
+ .Xr ssh 1
+@@ -1149,29 +1150,25 @@ it may be zero or more of:
+ and
+ .Cm pam .
+ .It Cm KexAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the available KEX (Key Exchange) algorithms.
+ Multiple algorithms must be comma-separated.
+ If the specified list begins with a
+ .Sq +
+-character, then the specified methods will be appended to the default set
+-instead of replacing them.
++character, then the specified methods will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified methods (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified methods will be placed at the head of the
+-default set.
+-The default is:
+-.Bd -literal -offset indent
+-curve25519-sha256,curve25519-sha256@libssh.org,
+-ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,
+-diffie-hellman-group-exchange-sha256,
+-diffie-hellman-group16-sha512,
+-diffie-hellman-group18-sha512,
+-diffie-hellman-group14-sha256
+-.Ed
++built-in openssh default set.
+ .Pp
+ The list of available key exchange algorithms may also be obtained using
+ .Qq ssh -Q kex .
+@@ -1231,37 +1228,33 @@ The default is INFO.
+ DEBUG and DEBUG1 are equivalent.
+ DEBUG2 and DEBUG3 each specify higher levels of verbose output.
+ .It Cm MACs
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the MAC (message authentication code) algorithms
+ in order of preference.
+ The MAC algorithm is used for data integrity protection.
+ Multiple algorithms must be comma-separated.
+ If the specified list begins with a
+ .Sq +
+-character, then the specified algorithms will be appended to the default set
+-instead of replacing them.
++character, then the specified algorithms will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified algorithms (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified algorithms will be placed at the head of the
+-default set.
++built-in openssh default set.
+ .Pp
+ The algorithms that contain
+ .Qq -etm
+ calculate the MAC after encryption (encrypt-then-mac).
+ These are considered safer and their use recommended.
+ .Pp
+-The default is:
+-.Bd -literal -offset indent
+-umac-64-etm@openssh.com,umac-128-etm@openssh.com,
+-hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,
+-hmac-sha1-etm@openssh.com,
+-umac-64@openssh.com,umac-128@openssh.com,
+-hmac-sha2-256,hmac-sha2-512,hmac-sha1
+-.Ed
+-.Pp
+ The list of available MAC algorithms may also be obtained using
+ .Qq ssh -Q mac .
+ .It Cm NoHostAuthenticationForLocalhost
+@@ -1394,36 +1387,25 @@ instead of continuing to execute and pas
+ The default is
+ .Cm no .
+ .It Cm PubkeyAcceptedKeyTypes
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the key types that will be used for public key authentication
+ as a comma-separated list of patterns.
+ If the specified list begins with a
+ .Sq +
+-character, then the key types after it will be appended to the default
+-instead of replacing it.
++character, then the key types after it will be appended to the built-in
++openssh default instead of replacing it.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified key types (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified key types will be placed at the head of the
+-default set.
+-The default for this option is:
+-.Bd -literal -offset 3n
+-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ecdsa-sha2-nistp384-cert-v01@openssh.com,
+-ecdsa-sha2-nistp521-cert-v01@openssh.com,
+-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ssh-ed25519-cert-v01@openssh.com,
+-sk-ssh-ed25519-cert-v01@openssh.com,
+-rsa-sha2-512-cert-v01@openssh.com,
+-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-rsa-cert-v01@openssh.com,
+-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
+-sk-ecdsa-sha2-nistp256@openssh.com,
+-ssh-ed25519,sk-ssh-ed25519@openssh.com,
+-rsa-sha2-512,rsa-sha2-256,ssh-rsa
+-.Ed
++built-in openssh default set.
+ .Pp
+ The list of available key types may also be obtained using
+ .Qq ssh -Q PubkeyAcceptedKeyTypes .
+diff -up openssh-8.2p1/sshd_config.5.crypto-policies openssh-8.2p1/sshd_config.5
+--- openssh-8.2p1/sshd_config.5.crypto-policies	2020-03-26 14:40:44.530775355 +0100
++++ openssh-8.2p1/sshd_config.5	2020-03-26 14:48:56.732468099 +0100
+@@ -375,16 +375,16 @@ If the argument is
+ then no banner is displayed.
+ By default, no banner is displayed.
+ .It Cm CASignatureAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies which algorithms are allowed for signing of certificates
+ by certificate authorities (CAs).
+-The default is:
+-.Bd -literal -offset indent
+-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
+-ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
+-.Ed
+-.Pp
+ Certificates signed using other algorithms will not be accepted for
+ public key or host-based authentication.
++.Pp
+ .It Cm ChallengeResponseAuthentication
+ Specifies whether challenge-response authentication is allowed (e.g. via
+ PAM or through authentication styles supported in
+@@ -446,20 +446,25 @@ The default is
+ indicating not to
+ .Xr chroot 2 .
+ .It Cm Ciphers
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the ciphers allowed.
+ Multiple ciphers must be comma-separated.
+ If the specified list begins with a
+ .Sq +
+-character, then the specified ciphers will be appended to the default set
+-instead of replacing them.
++character, then the specified ciphers will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified ciphers (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified ciphers will be placed at the head of the
+-default set.
++built-in openssh default set.
+ .Pp
+ The supported ciphers are:
+ .Pp
+@@ -486,13 +491,6 @@ aes256-gcm@openssh.com
+ chacha20-poly1305@openssh.com
+ .El
+ .Pp
+-The default is:
+-.Bd -literal -offset indent
+-chacha20-poly1305@openssh.com,
+-aes128-ctr,aes192-ctr,aes256-ctr,
+-aes128-gcm@openssh.com,aes256-gcm@openssh.com
+-.Ed
+-.Pp
+ The list of available ciphers may also be obtained using
+ .Qq ssh -Q cipher .
+ .It Cm ClientAliveCountMax
+@@ -681,22 +679,24 @@ For this to work
+ .Cm GSSAPIKeyExchange
+ needs to be enabled in the server and also used by the client.
+ .It Cm GSSAPIKexAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ The list of key exchange algorithms that are accepted by GSSAPI
+ key exchange. Possible values are
+ .Bd -literal -offset 3n
+-gss-gex-sha1-,
+-gss-group1-sha1-,
+-gss-group14-sha1-,
+-gss-group14-sha256-,
+-gss-group16-sha512-,
+-gss-nistp256-sha256-,
++gss-gex-sha1-
++gss-group1-sha1-
++gss-group14-sha1-
++gss-group14-sha256-
++gss-group16-sha512-
++gss-nistp256-sha256-
+ gss-curve25519-sha256-
+ .Ed
+-.Pp
+-The default is
+-.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,
+-gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- .
+ This option only applies to connections using GSSAPI.
++.Pp
+ .It Cm HostbasedAcceptedKeyTypes
+ Specifies the key types that will be accepted for hostbased authentication
+ as a list of comma-separated patterns.
+@@ -793,25 +793,13 @@ is specified, the location of the socket
+ .Ev SSH_AUTH_SOCK
+ environment variable.
+ .It Cm HostKeyAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the host key algorithms
+ that the server offers.
+-The default for this option is:
+-.Bd -literal -offset 3n
+-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ecdsa-sha2-nistp384-cert-v01@openssh.com,
+-ecdsa-sha2-nistp521-cert-v01@openssh.com,
+-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ssh-ed25519-cert-v01@openssh.com,
+-sk-ssh-ed25519-cert-v01@openssh.com,
+-rsa-sha2-512-cert-v01@openssh.com,
+-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-rsa-cert-v01@openssh.com,
+-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
+-sk-ecdsa-sha2-nistp256@openssh.com,
+-ssh-ed25519,sk-ssh-ed25519@openssh.com,
+-rsa-sha2-512,rsa-sha2-256,ssh-rsa
+-.Ed
+-.Pp
+ The list of available key types may also be obtained using
+ .Qq ssh -Q HostKeyAlgorithms .
+ .It Cm IgnoreRhosts
+@@ -943,20 +931,25 @@ Specifies whether to look at .k5login fi
+ The default is
+ .Cm yes .
+ .It Cm KexAlgorithms
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the available KEX (Key Exchange) algorithms.
+ Multiple algorithms must be comma-separated.
+ Alternately if the specified list begins with a
+ .Sq +
+-character, then the specified methods will be appended to the default set
+-instead of replacing them.
++character, then the specified methods will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified methods (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified methods will be placed at the head of the
+-default set.
++built-in openssh default set.
+ The supported algorithms are:
+ .Pp
+ .Bl -item -compact -offset indent
+@@ -988,15 +981,6 @@ ecdh-sha2-nistp521
+ sntrup4591761x25519-sha512@tinyssh.org
+ .El
+ .Pp
+-The default is:
+-.Bd -literal -offset indent
+-curve25519-sha256,curve25519-sha256@libssh.org,
+-ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,
+-diffie-hellman-group-exchange-sha256,
+-diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,
+-diffie-hellman-group14-sha256
+-.Ed
+-.Pp
+ The list of available key exchange algorithms may also be obtained using
+ .Qq ssh -Q KexAlgorithms .
+ .It Cm ListenAddress
+@@ -1065,21 +1049,26 @@ DEBUG and DEBUG1 are equivalent.
+ DEBUG2 and DEBUG3 each specify higher levels of debugging output.
+ Logging with a DEBUG level violates the privacy of users and is not recommended.
+ .It Cm MACs
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the available MAC (message authentication code) algorithms.
+ The MAC algorithm is used for data integrity protection.
+ Multiple algorithms must be comma-separated.
+ If the specified list begins with a
+ .Sq +
+-character, then the specified algorithms will be appended to the default set
+-instead of replacing them.
++character, then the specified algorithms will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified algorithms (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified algorithms will be placed at the head of the
+-default set.
++built-in openssh default set.
+ .Pp
+ The algorithms that contain
+ .Qq -etm
+@@ -1122,15 +1111,6 @@ umac-64-etm@openssh.com
+ umac-128-etm@openssh.com
+ .El
+ .Pp
+-The default is:
+-.Bd -literal -offset indent
+-umac-64-etm@openssh.com,umac-128-etm@openssh.com,
+-hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,
+-hmac-sha1-etm@openssh.com,
+-umac-64@openssh.com,umac-128@openssh.com,
+-hmac-sha2-256,hmac-sha2-512,hmac-sha1
+-.Ed
+-.Pp
+ The list of available MAC algorithms may also be obtained using
+ .Qq ssh -Q mac .
+ .It Cm Match
+@@ -1480,36 +1460,25 @@ or equivalent.)
+ The default is
+ .Cm yes .
+ .It Cm PubkeyAcceptedKeyTypes
++The default is handled system-wide by
++.Xr crypto-policies 7 .
++To see the defaults and how to modify this default, see manual page
++.Xr update-crypto-policies 8 .
++.Pp
+ Specifies the key types that will be accepted for public key authentication
+ as a list of comma-separated patterns.
+ Alternately if the specified list begins with a
+ .Sq +
+-character, then the specified key types will be appended to the default set
+-instead of replacing them.
++character, then the specified key types will be appended to the built-in
++openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq -
+ character, then the specified key types (including wildcards) will be removed
+-from the default set instead of replacing them.
++from the built-in openssh default set instead of replacing them.
+ If the specified list begins with a
+ .Sq ^
+ character, then the specified key types will be placed at the head of the
+-default set.
+-The default for this option is:
+-.Bd -literal -offset 3n
+-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ecdsa-sha2-nistp384-cert-v01@openssh.com,
+-ecdsa-sha2-nistp521-cert-v01@openssh.com,
+-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,
+-ssh-ed25519-cert-v01@openssh.com,
+-sk-ssh-ed25519-cert-v01@openssh.com,
+-rsa-sha2-512-cert-v01@openssh.com,
+-rsa-sha2-256-cert-v01@openssh.com,
+-ssh-rsa-cert-v01@openssh.com,
+-ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
+-sk-ecdsa-sha2-nistp256@openssh.com,
+-ssh-ed25519,sk-ssh-ed25519@openssh.com,
+-rsa-sha2-512,rsa-sha2-256,ssh-rsa
+-.Ed
++built-in openssh default set.
+ .Pp
+ The list of available key types may also be obtained using
+ .Qq ssh -Q PubkeyAcceptedKeyTypes .
diff --git a/openssh-8.0p1-gssapi-keyex.patch b/openssh-8.0p1-gssapi-keyex.patch
new file mode 100644
index 0000000..770e99e
--- /dev/null
+++ b/openssh-8.0p1-gssapi-keyex.patch
@@ -0,0 +1,3936 @@
+diff --git a/Makefile.in b/Makefile.in
+index e7549470..b68c1710 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -109,6 +109,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ 	kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
+ 	kexgexc.o kexgexs.o \
+ 	sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \
++	kexgssc.o \
+ 	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+ 	sshbuf-io.o
+ 
+@@ -125,7 +126,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \
+ 	auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
+ 	auth2-none.o auth2-passwd.o auth2-pubkey.o \
+ 	monitor.o monitor_wrap.o auth-krb5.o \
+-	auth2-gss.o gss-serv.o gss-serv-krb5.o \
++	auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \
+ 	loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \
+ 	sftp-server.o sftp-common.o \
+ 	sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
+diff --git a/auth.c b/auth.c
+index 086b8ebb..687c57b4 100644
+--- a/auth.c
++++ b/auth.c
+@@ -400,7 +400,8 @@ auth_root_allowed(struct ssh *ssh, const char *method)
+ 	case PERMIT_NO_PASSWD:
+ 		if (strcmp(method, "publickey") == 0 ||
+ 		    strcmp(method, "hostbased") == 0 ||
+-		    strcmp(method, "gssapi-with-mic") == 0)
++		    strcmp(method, "gssapi-with-mic") == 0 ||
++		    strcmp(method, "gssapi-keyex") == 0)
+ 			return 1;
+ 		break;
+ 	case PERMIT_FORCED_ONLY:
+@@ -724,99 +725,6 @@ fakepw(void)
+ 	return (&fake);
+ }
+ 
+-/*
+- * Returns the remote DNS hostname as a string. The returned string must not
+- * be freed. NB. this will usually trigger a DNS query the first time it is
+- * called.
+- * This function does additional checks on the hostname to mitigate some
+- * attacks on legacy rhosts-style authentication.
+- * XXX is RhostsRSAAuthentication vulnerable to these?
+- * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?)
+- */
+-
+-static char *
+-remote_hostname(struct ssh *ssh)
+-{
+-	struct sockaddr_storage from;
+-	socklen_t fromlen;
+-	struct addrinfo hints, *ai, *aitop;
+-	char name[NI_MAXHOST], ntop2[NI_MAXHOST];
+-	const char *ntop = ssh_remote_ipaddr(ssh);
+-
+-	/* Get IP address of client. */
+-	fromlen = sizeof(from);
+-	memset(&from, 0, sizeof(from));
+-	if (getpeername(ssh_packet_get_connection_in(ssh),
+-	    (struct sockaddr *)&from, &fromlen) == -1) {
+-		debug("getpeername failed: %.100s", strerror(errno));
+-		return xstrdup(ntop);
+-	}
+-
+-	ipv64_normalise_mapped(&from, &fromlen);
+-	if (from.ss_family == AF_INET6)
+-		fromlen = sizeof(struct sockaddr_in6);
+-
+-	debug3("Trying to reverse map address %.100s.", ntop);
+-	/* Map the IP address to a host name. */
+-	if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
+-	    NULL, 0, NI_NAMEREQD) != 0) {
+-		/* Host name not found.  Use ip address. */
+-		return xstrdup(ntop);
+-	}
+-
+-	/*
+-	 * if reverse lookup result looks like a numeric hostname,
+-	 * someone is trying to trick us by PTR record like following:
+-	 *	1.1.1.10.in-addr.arpa.	IN PTR	2.3.4.5
+-	 */
+-	memset(&hints, 0, sizeof(hints));
+-	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
+-	hints.ai_flags = AI_NUMERICHOST;
+-	if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
+-		logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
+-		    name, ntop);
+-		freeaddrinfo(ai);
+-		return xstrdup(ntop);
+-	}
+-
+-	/* Names are stored in lowercase. */
+-	lowercase(name);
+-
+-	/*
+-	 * Map it back to an IP address and check that the given
+-	 * address actually is an address of this host.  This is
+-	 * necessary because anyone with access to a name server can
+-	 * define arbitrary names for an IP address. Mapping from
+-	 * name to IP address can be trusted better (but can still be
+-	 * fooled if the intruder has access to the name server of
+-	 * the domain).
+-	 */
+-	memset(&hints, 0, sizeof(hints));
+-	hints.ai_family = from.ss_family;
+-	hints.ai_socktype = SOCK_STREAM;
+-	if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
+-		logit("reverse mapping checking getaddrinfo for %.700s "
+-		    "[%s] failed.", name, ntop);
+-		return xstrdup(ntop);
+-	}
+-	/* Look for the address from the list of addresses. */
+-	for (ai = aitop; ai; ai = ai->ai_next) {
+-		if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
+-		    sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
+-		    (strcmp(ntop, ntop2) == 0))
+-				break;
+-	}
+-	freeaddrinfo(aitop);
+-	/* If we reached the end of the list, the address was not there. */
+-	if (ai == NULL) {
+-		/* Address not found for the host name. */
+-		logit("Address %.100s maps to %.600s, but this does not "
+-		    "map back to the address.", ntop, name);
+-		return xstrdup(ntop);
+-	}
+-	return xstrdup(name);
+-}
+-
+ /*
+  * Return the canonical name of the host in the other side of the current
+  * connection.  The host name is cached, so it is efficient to call this
+diff --git a/auth2-gss.c b/auth2-gss.c
+index 9351e042..d6446c0c 100644
+--- a/auth2-gss.c
++++ b/auth2-gss.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: auth2-gss.c,v 1.29 2018/07/31 03:10:27 djm Exp $ */
+ 
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions
+@@ -54,6 +54,48 @@ static int input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh);
+ static int input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh);
+ static int input_gssapi_errtok(int, u_int32_t, struct ssh *);
+ 
++/*
++ * The 'gssapi_keyex' userauth mechanism.
++ */
++static int
++userauth_gsskeyex(struct ssh *ssh)
++{
++	Authctxt *authctxt = ssh->authctxt;
++	int r, authenticated = 0;
++	struct sshbuf *b = NULL;
++	gss_buffer_desc mic, gssbuf;
++	u_char *p;
++	size_t len;
++
++	if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 ||
++	    (r = sshpkt_get_end(ssh)) != 0)
++		fatal("%s: %s", __func__, ssh_err(r));
++
++	if ((b = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++
++	mic.value = p;
++	mic.length = len;
++
++	ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
++	    "gssapi-keyex");
++
++	if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++		fatal("%s: sshbuf_mutable_ptr failed", __func__);
++	gssbuf.length = sshbuf_len(b);
++
++	/* gss_kex_context is NULL with privsep, so we can't check it here */
++	if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context,
++	    &gssbuf, &mic))))
++		authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++		    authctxt->pw, 1));
++
++	sshbuf_free(b);
++	free(mic.value);
++
++	return (authenticated);
++}
++
+ /*
+  * We only support those mechanisms that we know about (ie ones that we know
+  * how to check local user kuserok and the like)
+@@ -260,7 +302,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh)
+ 	if ((r = sshpkt_get_end(ssh)) != 0)
+ 		fatal("%s: %s", __func__, ssh_err(r));
+ 
+-	authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user));
++	authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++	    authctxt->pw, 1));
+ 
+ 	if ((!use_privsep || mm_is_monitor()) &&
+ 	    (displayname = ssh_gssapi_displayname()) != NULL)
+@@ -306,7 +349,8 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
+ 	gssbuf.length = sshbuf_len(b);
+ 
+ 	if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))))
+-		authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user));
++		authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user,
++		    authctxt->pw, 0));
+ 	else
+ 		logit("GSSAPI MIC check failed");
+ 
+@@ -326,6 +370,12 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
+ 	return 0;
+ }
+ 
++Authmethod method_gsskeyex = {
++	"gssapi-keyex",
++	userauth_gsskeyex,
++	&options.gss_authentication
++};
++
+ Authmethod method_gssapi = {
+ 	"gssapi-with-mic",
+ 	userauth_gssapi,
+diff --git a/auth2.c b/auth2.c
+index 0e776224..1c217268 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -73,6 +73,7 @@ extern Authmethod method_passwd;
+ extern Authmethod method_kbdint;
+ extern Authmethod method_hostbased;
+ #ifdef GSSAPI
++extern Authmethod method_gsskeyex;
+ extern Authmethod method_gssapi;
+ #endif
+ 
+@@ -80,6 +81,7 @@ Authmethod *authmethods[] = {
+ 	&method_none,
+ 	&method_pubkey,
+ #ifdef GSSAPI
++	&method_gsskeyex,
+ 	&method_gssapi,
+ #endif
+ 	&method_passwd,
+diff --git a/canohost.c b/canohost.c
+index abea9c6e..8e81b519 100644
+--- a/canohost.c
++++ b/canohost.c
+@@ -35,6 +35,99 @@
+ #include "canohost.h"
+ #include "misc.h"
+ 
++/*
++ * Returns the remote DNS hostname as a string. The returned string must not
++ * be freed. NB. this will usually trigger a DNS query the first time it is
++ * called.
++ * This function does additional checks on the hostname to mitigate some
++ * attacks on legacy rhosts-style authentication.
++ * XXX is RhostsRSAAuthentication vulnerable to these?
++ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?)
++ */
++
++char *
++remote_hostname(struct ssh *ssh)
++{
++	struct sockaddr_storage from;
++	socklen_t fromlen;
++	struct addrinfo hints, *ai, *aitop;
++	char name[NI_MAXHOST], ntop2[NI_MAXHOST];
++	const char *ntop = ssh_remote_ipaddr(ssh);
++
++	/* Get IP address of client. */
++	fromlen = sizeof(from);
++	memset(&from, 0, sizeof(from));
++	if (getpeername(ssh_packet_get_connection_in(ssh),
++	    (struct sockaddr *)&from, &fromlen) == -1) {
++		debug("getpeername failed: %.100s", strerror(errno));
++		return xstrdup(ntop);
++	}
++
++	ipv64_normalise_mapped(&from, &fromlen);
++	if (from.ss_family == AF_INET6)
++		fromlen = sizeof(struct sockaddr_in6);
++
++	debug3("Trying to reverse map address %.100s.", ntop);
++	/* Map the IP address to a host name. */
++	if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
++	    NULL, 0, NI_NAMEREQD) != 0) {
++		/* Host name not found.  Use ip address. */
++		return xstrdup(ntop);
++	}
++
++	/*
++	 * if reverse lookup result looks like a numeric hostname,
++	 * someone is trying to trick us by PTR record like following:
++	 *	1.1.1.10.in-addr.arpa.	IN PTR	2.3.4.5
++	 */
++	memset(&hints, 0, sizeof(hints));
++	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
++	hints.ai_flags = AI_NUMERICHOST;
++	if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
++		logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
++		    name, ntop);
++		freeaddrinfo(ai);
++		return xstrdup(ntop);
++	}
++
++	/* Names are stored in lowercase. */
++	lowercase(name);
++
++	/*
++	 * Map it back to an IP address and check that the given
++	 * address actually is an address of this host.  This is
++	 * necessary because anyone with access to a name server can
++	 * define arbitrary names for an IP address. Mapping from
++	 * name to IP address can be trusted better (but can still be
++	 * fooled if the intruder has access to the name server of
++	 * the domain).
++	 */
++	memset(&hints, 0, sizeof(hints));
++	hints.ai_family = from.ss_family;
++	hints.ai_socktype = SOCK_STREAM;
++	if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
++		logit("reverse mapping checking getaddrinfo for %.700s "
++		    "[%s] failed.", name, ntop);
++		return xstrdup(ntop);
++	}
++	/* Look for the address from the list of addresses. */
++	for (ai = aitop; ai; ai = ai->ai_next) {
++		if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
++		    sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
++		    (strcmp(ntop, ntop2) == 0))
++				break;
++	}
++	freeaddrinfo(aitop);
++	/* If we reached the end of the list, the address was not there. */
++	if (ai == NULL) {
++		/* Address not found for the host name. */
++		logit("Address %.100s maps to %.600s, but this does not "
++		    "map back to the address.", ntop, name);
++		return xstrdup(ntop);
++	}
++	return xstrdup(name);
++}
++
+ void
+ ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len)
+ {
+diff --git a/canohost.h b/canohost.h
+index 26d62855..0cadc9f1 100644
+--- a/canohost.h
++++ b/canohost.h
+@@ -15,6 +15,9 @@
+ #ifndef _CANOHOST_H
+ #define _CANOHOST_H
+ 
++struct ssh;
++
++char		*remote_hostname(struct ssh *);
+ char		*get_peer_ipaddr(int);
+ int		 get_peer_port(int);
+ char		*get_local_ipaddr(int);
+diff --git a/clientloop.c b/clientloop.c
+index ebd0dbca..1bdac6a4 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -112,6 +112,10 @@
+ #include "ssherr.h"
+ #include "hostfile.h"
+ 
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++
+ /* import options */
+ extern Options options;
+ 
+@@ -1379,9 +1383,18 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
+ 			break;
+ 
+ 		/* Do channel operations unless rekeying in progress. */
+-		if (!ssh_packet_is_rekeying(ssh))
++		if (!ssh_packet_is_rekeying(ssh)) {
+ 			channel_after_select(ssh, readset, writeset);
+ 
++#ifdef GSSAPI
++			if (options.gss_renewal_rekey &&
++			    ssh_gssapi_credentials_updated(NULL)) {
++				debug("credentials updated - forcing rekey");
++				need_rekeying = 1;
++			}
++#endif
++		}
++
+ 		/* Buffer input from the connection.  */
+ 		client_process_net_input(ssh, readset);
+ 
+diff --git a/configure.ac b/configure.ac
+index b689db4b..efafb6bd 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -674,6 +674,30 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16))
+ 	    [Use tunnel device compatibility to OpenBSD])
+ 	AC_DEFINE([SSH_TUN_PREPEND_AF], [1],
+ 	    [Prepend the address family to IP tunnel traffic])
++	AC_MSG_CHECKING([if we have the Security Authorization Session API])
++	AC_TRY_COMPILE([#include <Security/AuthSession.h>],
++		[SessionCreate(0, 0);],
++		[ac_cv_use_security_session_api="yes"
++		 AC_DEFINE([USE_SECURITY_SESSION_API], [1],
++			[platform has the Security Authorization Session API])
++		 LIBS="$LIBS -framework Security"
++		 AC_MSG_RESULT([yes])],
++		[ac_cv_use_security_session_api="no"
++		 AC_MSG_RESULT([no])])
++	AC_MSG_CHECKING([if we have an in-memory credentials cache])
++	AC_TRY_COMPILE(
++		[#include <Kerberos/Kerberos.h>],
++		[cc_context_t c;
++		 (void) cc_initialize (&c, 0, NULL, NULL);],
++		[AC_DEFINE([USE_CCAPI], [1],
++			[platform uses an in-memory credentials cache])
++		 LIBS="$LIBS -framework Security"
++		 AC_MSG_RESULT([yes])
++		 if test "x$ac_cv_use_security_session_api" = "xno"; then
++			AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***])
++		fi],
++		[AC_MSG_RESULT([no])]
++	)
+ 	m4_pattern_allow([AU_IPv])
+ 	AC_CHECK_DECL([AU_IPv4], [],
+ 	    AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records])
+diff --git a/gss-genr.c b/gss-genr.c
+index d56257b4..763a63ff 100644
+--- a/gss-genr.c
++++ b/gss-genr.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-genr.c,v 1.26 2018/07/10 09:13:30 djm Exp $ */
+ 
+ /*
+- * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions
+@@ -41,12 +41,36 @@
+ #include "sshbuf.h"
+ #include "log.h"
+ #include "ssh2.h"
++#include "cipher.h"
++#include "sshkey.h"
++#include "kex.h"
++#include "digest.h"
++#include "packet.h"
+ 
+ #include "ssh-gss.h"
+ 
+ extern u_char *session_id2;
+ extern u_int session_id2_len;
+ 
++typedef struct {
++	char *encoded;
++	gss_OID oid;
++} ssh_gss_kex_mapping;
++
++/*
++ * XXX - It would be nice to find a more elegant way of handling the
++ * XXX   passing of the key exchange context to the userauth routines
++ */
++
++Gssctxt *gss_kex_context = NULL;
++
++static ssh_gss_kex_mapping *gss_enc2oid = NULL;
++
++int
++ssh_gssapi_oid_table_ok(void) {
++	return (gss_enc2oid != NULL);
++}
++
+ /* sshbuf_get for gss_buffer_desc */
+ int
+ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
+@@ -62,6 +86,162 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g)
+ 	return 0;
+ }
+ 
++/* sshpkt_get of gss_buffer_desc */
++int
++ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g)
++{
++	int r;
++	u_char *p;
++	size_t len;
++
++	if ((r = sshpkt_get_string(ssh, &p, &len)) != 0)
++		return r;
++	g->value = p;
++	g->length = len;
++	return 0;
++}
++
++/*
++ * Return a list of the gss-group1-sha1 mechanisms supported by this program
++ *
++ * We test mechanisms to ensure that we can use them, to avoid starting
++ * a key exchange with a bad mechanism
++ */
++
++char *
++ssh_gssapi_client_mechanisms(const char *host, const char *client,
++    const char *kex) {
++	gss_OID_set gss_supported = NULL;
++	OM_uint32 min_status;
++
++	if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported)))
++		return NULL;
++
++	return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism,
++	    host, client, kex);
++}
++
++char *
++ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check,
++    const char *host, const char *client, const char *kex) {
++	struct sshbuf *buf = NULL;
++	size_t i;
++	int r = SSH_ERR_ALLOC_FAIL;
++	int oidpos, enclen;
++	char *mechs, *encoded;
++	u_char digest[SSH_DIGEST_MAX_LENGTH];
++	char deroid[2];
++	struct ssh_digest_ctx *md = NULL;
++	char *s, *cp, *p;
++
++	if (gss_enc2oid != NULL) {
++		for (i = 0; gss_enc2oid[i].encoded != NULL; i++)
++			free(gss_enc2oid[i].encoded);
++		free(gss_enc2oid);
++	}
++
++	gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) *
++	    (gss_supported->count + 1));
++
++	if ((buf = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++
++	oidpos = 0;
++	s = cp = xstrdup(kex);
++	for (i = 0; i < gss_supported->count; i++) {
++		if (gss_supported->elements[i].length < 128 &&
++		    (*check)(NULL, &(gss_supported->elements[i]), host, client)) {
++
++			deroid[0] = SSH_GSS_OIDTYPE;
++			deroid[1] = gss_supported->elements[i].length;
++
++			if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL ||
++			    (r = ssh_digest_update(md, deroid, 2)) != 0 ||
++			    (r = ssh_digest_update(md,
++			        gss_supported->elements[i].elements,
++			        gss_supported->elements[i].length)) != 0 ||
++			    (r = ssh_digest_final(md, digest, sizeof(digest))) != 0)
++				fatal("%s: digest failed: %s", __func__,
++				    ssh_err(r));
++			ssh_digest_free(md);
++			md = NULL;
++
++			encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5)
++			    * 2);
++			enclen = __b64_ntop(digest,
++			    ssh_digest_bytes(SSH_DIGEST_MD5), encoded,
++			    ssh_digest_bytes(SSH_DIGEST_MD5) * 2);
++
++			cp = strncpy(s, kex, strlen(kex));
++			for ((p = strsep(&cp, ",")); p && *p != '\0';
++				(p = strsep(&cp, ","))) {
++				if (sshbuf_len(buf) != 0 &&
++				    (r = sshbuf_put_u8(buf, ',')) != 0)
++					fatal("%s: sshbuf_put_u8 error: %s",
++					    __func__, ssh_err(r));
++				if ((r = sshbuf_put(buf, p, strlen(p))) != 0 ||
++				    (r = sshbuf_put(buf, encoded, enclen)) != 0)
++					fatal("%s: sshbuf_put error: %s",
++					    __func__, ssh_err(r));
++			}
++
++			gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]);
++			gss_enc2oid[oidpos].encoded = encoded;
++			oidpos++;
++		}
++	}
++	free(s);
++	gss_enc2oid[oidpos].oid = NULL;
++	gss_enc2oid[oidpos].encoded = NULL;
++
++	if ((mechs = sshbuf_dup_string(buf)) == NULL)
++		fatal("%s: sshbuf_dup_string failed", __func__);
++
++	sshbuf_free(buf);
++
++	if (strlen(mechs) == 0) {
++		free(mechs);
++		mechs = NULL;
++	}
++
++	return (mechs);
++}
++
++gss_OID
++ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) {
++	int i = 0;
++
++#define SKIP_KEX_NAME(type) \
++	case type: \
++		if (strlen(name) < sizeof(type##_ID)) \
++			return GSS_C_NO_OID; \
++		name += sizeof(type##_ID) - 1; \
++		break;
++
++	switch (kex_type) {
++	SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1)
++	SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1)
++	SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256)
++	SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512)
++	SKIP_KEX_NAME(KEX_GSS_GEX_SHA1)
++	SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256)
++	SKIP_KEX_NAME(KEX_GSS_C25519_SHA256)
++	default:
++		return GSS_C_NO_OID;
++	}
++
++#undef SKIP_KEX_NAME
++
++	while (gss_enc2oid[i].encoded != NULL &&
++	    strcmp(name, gss_enc2oid[i].encoded) != 0)
++		i++;
++
++	if (gss_enc2oid[i].oid != NULL && ctx != NULL)
++		ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid);
++
++	return gss_enc2oid[i].oid;
++}
++
+ /* Check that the OID in a data stream matches that in the context */
+ int
+ ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len)
+@@ -218,7 +398,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
+ 	}
+ 
+ 	ctx->major = gss_init_sec_context(&ctx->minor,
+-	    GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid,
++	    ctx->client_creds, &ctx->context, ctx->name, ctx->oid,
+ 	    GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag,
+ 	    0, NULL, recv_tok, NULL, send_tok, flags, NULL);
+ 
+@@ -247,9 +427,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
+ 	return (ctx->major);
+ }
+ 
++OM_uint32
++ssh_gssapi_client_identity(Gssctxt *ctx, const char *name)
++{
++	gss_buffer_desc gssbuf;
++	gss_name_t gssname;
++	OM_uint32 status;
++	gss_OID_set oidset;
++
++	gssbuf.value = (void *) name;
++	gssbuf.length = strlen(gssbuf.value);
++
++	gss_create_empty_oid_set(&status, &oidset);
++	gss_add_oid_set_member(&status, ctx->oid, &oidset);
++
++	ctx->major = gss_import_name(&ctx->minor, &gssbuf,
++	    GSS_C_NT_USER_NAME, &gssname);
++
++	if (!ctx->major)
++		ctx->major = gss_acquire_cred(&ctx->minor,
++		    gssname, 0, oidset, GSS_C_INITIATE,
++		    &ctx->client_creds, NULL, NULL);
++
++	gss_release_name(&status, &gssname);
++	gss_release_oid_set(&status, &oidset);
++
++	if (ctx->major)
++		ssh_gssapi_error(ctx);
++
++	return(ctx->major);
++}
++
+ OM_uint32
+ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
+ {
++	if (ctx == NULL)
++		return -1;
++
+ 	if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context,
+ 	    GSS_C_QOP_DEFAULT, buffer, hash)))
+ 		ssh_gssapi_error(ctx);
+@@ -257,6 +471,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
+ 	return (ctx->major);
+ }
+ 
++/* Priviledged when used by server */
++OM_uint32
++ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
++{
++	if (ctx == NULL)
++		return -1;
++
++	ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
++	    gssbuf, gssmic, NULL);
++
++	return (ctx->major);
++}
++
+ void
+ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
+     const char *context)
+@@ -273,11 +500,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service,
+ }
+ 
+ int
+-ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
++ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host,
++    const char *client)
+ {
+ 	gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
+ 	OM_uint32 major, minor;
+ 	gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"};
++	Gssctxt *intctx = NULL;
++
++	if (ctx == NULL)
++		ctx = &intctx;
+ 
+ 	/* RFC 4462 says we MUST NOT do SPNEGO */
+ 	if (oid->length == spnego_oid.length && 
+@@ -287,6 +519,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+ 	ssh_gssapi_build_ctx(ctx);
+ 	ssh_gssapi_set_oid(*ctx, oid);
+ 	major = ssh_gssapi_import_name(*ctx, host);
++
++	if (!GSS_ERROR(major) && client)
++		major = ssh_gssapi_client_identity(*ctx, client);
++
+ 	if (!GSS_ERROR(major)) {
+ 		major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, 
+ 		    NULL);
+@@ -296,10 +532,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host)
+ 			    GSS_C_NO_BUFFER);
+ 	}
+ 
+-	if (GSS_ERROR(major)) 
++	if (GSS_ERROR(major) || intctx != NULL)
+ 		ssh_gssapi_delete_ctx(ctx);
+ 
+ 	return (!GSS_ERROR(major));
+ }
+ 
++int
++ssh_gssapi_credentials_updated(Gssctxt *ctxt) {
++	static gss_name_t saved_name = GSS_C_NO_NAME;
++	static OM_uint32 saved_lifetime = 0;
++	static gss_OID saved_mech = GSS_C_NO_OID;
++	static gss_name_t name;
++	static OM_uint32 last_call = 0;
++	OM_uint32 lifetime, now, major, minor;
++	int equal;
++
++	now = time(NULL);
++
++	if (ctxt) {
++		debug("Rekey has happened - updating saved versions");
++
++		if (saved_name != GSS_C_NO_NAME)
++			gss_release_name(&minor, &saved_name);
++
++		major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
++		    &saved_name, &saved_lifetime, NULL, NULL);
++
++		if (!GSS_ERROR(major)) {
++			saved_mech = ctxt->oid;
++		        saved_lifetime+= now;
++		} else {
++			/* Handle the error */
++		}
++		return 0;
++	}
++
++	if (now - last_call < 10)
++		return 0;
++
++	last_call = now;
++
++	if (saved_mech == GSS_C_NO_OID)
++		return 0;
++
++	major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
++	    &name, &lifetime, NULL, NULL);
++	if (major == GSS_S_CREDENTIALS_EXPIRED)
++		return 0;
++	else if (GSS_ERROR(major))
++		return 0;
++
++	major = gss_compare_name(&minor, saved_name, name, &equal);
++	gss_release_name(&minor, &name);
++	if (GSS_ERROR(major))
++		return 0;
++
++	if (equal && (saved_lifetime < lifetime + now - 10))
++		return 1;
++
++	return 0;
++}
++
+ #endif /* GSSAPI */
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index a151bc1e..8d2b677f 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */
+ 
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions
+@@ -120,7 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ 	krb5_error_code problem;
+ 	krb5_principal princ;
+ 	OM_uint32 maj_status, min_status;
+-	int len;
++	const char *new_ccname, *new_cctype;
+ 	const char *errmsg;
+ 
+ 	if (client->creds == NULL) {
+@@ -180,11 +180,26 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ 		return;
+ 	}
+ 
+-	client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache));
++	new_cctype = krb5_cc_get_type(krb_context, ccache);
++	new_ccname = krb5_cc_get_name(krb_context, ccache);
++
+ 	client->store.envvar = "KRB5CCNAME";
+-	len = strlen(client->store.filename) + 6;
+-	client->store.envval = xmalloc(len);
+-	snprintf(client->store.envval, len, "FILE:%s", client->store.filename);
++#ifdef USE_CCAPI
++	xasprintf(&client->store.envval, "API:%s", new_ccname);
++	client->store.filename = NULL;
++#else
++	if (new_ccname[0] == ':')
++		new_ccname++;
++	xasprintf(&client->store.envval, "%s:%s", new_cctype, new_ccname);
++	if (strcmp(new_cctype, "DIR") == 0) {
++		char *p;
++		p = strrchr(client->store.envval, '/');
++		if (p)
++			*p = '\0';
++	}
++	if ((strcmp(new_cctype, "FILE") == 0) || (strcmp(new_cctype, "DIR") == 0))
++		client->store.filename = xstrdup(new_ccname);
++#endif
+ 
+ #ifdef USE_PAM
+ 	if (options.use_pam)
+@@ -193,9 +208,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client)
+ 
+ 	krb5_cc_close(krb_context, ccache);
+ 
++	client->store.data = krb_context;
++
+ 	return;
+ }
+ 
++int
++ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
++    ssh_gssapi_client *client)
++{
++	krb5_ccache ccache = NULL;
++	krb5_principal principal = NULL;
++	char *name = NULL;
++	krb5_error_code problem;
++	OM_uint32 maj_status, min_status;
++
++	if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) {
++                logit("krb5_cc_resolve(): %.100s",
++                    krb5_get_err_text(krb_context, problem));
++                return 0;
++	}
++
++	/* Find out who the principal in this cache is */
++	if ((problem = krb5_cc_get_principal(krb_context, ccache,
++	    &principal))) {
++		logit("krb5_cc_get_principal(): %.100s",
++		    krb5_get_err_text(krb_context, problem));
++		krb5_cc_close(krb_context, ccache);
++		return 0;
++	}
++
++	if ((problem = krb5_unparse_name(krb_context, principal, &name))) {
++		logit("krb5_unparse_name(): %.100s",
++		    krb5_get_err_text(krb_context, problem));
++		krb5_free_principal(krb_context, principal);
++		krb5_cc_close(krb_context, ccache);
++		return 0;
++	}
++
++
++	if (strcmp(name,client->exportedname.value)!=0) {
++		debug("Name in local credentials cache differs. Not storing");
++		krb5_free_principal(krb_context, principal);
++		krb5_cc_close(krb_context, ccache);
++		krb5_free_unparsed_name(krb_context, name);
++		return 0;
++	}
++	krb5_free_unparsed_name(krb_context, name);
++
++	/* Name matches, so lets get on with it! */
++
++	if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) {
++		logit("krb5_cc_initialize(): %.100s",
++		    krb5_get_err_text(krb_context, problem));
++		krb5_free_principal(krb_context, principal);
++		krb5_cc_close(krb_context, ccache);
++		return 0;
++	}
++
++	krb5_free_principal(krb_context, principal);
++
++	if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds,
++	    ccache))) {
++		logit("gss_krb5_copy_ccache() failed. Sorry!");
++		krb5_cc_close(krb_context, ccache);
++		return 0;
++	}
++
++	return 1;
++}
++
+ ssh_gssapi_mech gssapi_kerberos_mech = {
+ 	"toWM5Slw5Ew8Mqkay+al2g==",
+ 	"Kerberos",
+@@ -203,7 +285,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = {
+ 	NULL,
+ 	&ssh_gssapi_krb5_userok,
+ 	NULL,
+-	&ssh_gssapi_krb5_storecreds
++	&ssh_gssapi_krb5_storecreds,
++	&ssh_gssapi_krb5_updatecreds
+ };
+ 
+ #endif /* KRB5 */
+diff --git a/gss-serv.c b/gss-serv.c
+index ab3a15f0..6ce56e92 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -1,7 +1,7 @@
+ /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */
+ 
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions
+@@ -44,17 +44,19 @@
+ #include "session.h"
+ #include "misc.h"
+ #include "servconf.h"
++#include "uidswap.h"
+ 
+ #include "ssh-gss.h"
++#include "monitor_wrap.h"
+ 
+ extern ServerOptions options;
+ 
+ static ssh_gssapi_client gssapi_client =
+-    { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER,
+-    GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}};
++    { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
++    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
+ 
+ ssh_gssapi_mech gssapi_null_mech =
+-    { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL};
++    { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+ 
+ #ifdef KRB5
+ extern ssh_gssapi_mech gssapi_kerberos_mech;
+@@ -140,6 +142,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid)
+ 	return (ssh_gssapi_acquire_cred(*ctx));
+ }
+ 
++/* Unprivileged */
++char *
++ssh_gssapi_server_mechanisms(void) {
++	if (supported_oids == NULL)
++		ssh_gssapi_prepare_supported_oids();
++	return (ssh_gssapi_kex_mechs(supported_oids,
++	    &ssh_gssapi_server_check_mech, NULL, NULL,
++	    options.gss_kex_algorithms));
++}
++
++/* Unprivileged */
++int
++ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data,
++    const char *dummy) {
++	Gssctxt *ctx = NULL;
++	int res;
++
++	res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid)));
++	ssh_gssapi_delete_ctx(&ctx);
++
++	return (res);
++}
++
+ /* Unprivileged */
+ void
+ ssh_gssapi_supported_oids(gss_OID_set *oidset)
+@@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *oidset)
+ 	gss_OID_set supported;
+ 
+ 	gss_create_empty_oid_set(&min_status, oidset);
+-	gss_indicate_mechs(&min_status, &supported);
++
++	if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported)))
++		return;
+ 
+ 	while (supported_mechs[i]->name != NULL) {
+ 		if (GSS_ERROR(gss_test_oid_set_member(&min_status,
+@@ -276,8 +303,48 @@ OM_uint32
+ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ {
+ 	int i = 0;
++	int equal = 0;
++	gss_name_t new_name = GSS_C_NO_NAME;
++	gss_buffer_desc ename = GSS_C_EMPTY_BUFFER;
++
++	if (options.gss_store_rekey && client->used && ctx->client_creds) {
++		if (client->mech->oid.length != ctx->oid->length ||
++		    (memcmp(client->mech->oid.elements,
++		     ctx->oid->elements, ctx->oid->length) !=0)) {
++			debug("Rekeyed credentials have different mechanism");
++			return GSS_S_COMPLETE;
++		}
++
++		if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
++		    ctx->client_creds, ctx->oid, &new_name,
++		    NULL, NULL, NULL))) {
++			ssh_gssapi_error(ctx);
++			return (ctx->major);
++		}
+ 
+-	gss_buffer_desc ename;
++		ctx->major = gss_compare_name(&ctx->minor, client->name,
++		    new_name, &equal);
++
++		if (GSS_ERROR(ctx->major)) {
++			ssh_gssapi_error(ctx);
++			return (ctx->major);
++		}
++
++		if (!equal) {
++			debug("Rekeyed credentials have different name");
++			return GSS_S_COMPLETE;
++		}
++
++		debug("Marking rekeyed credentials for export");
++
++		gss_release_name(&ctx->minor, &client->name);
++		gss_release_cred(&ctx->minor, &client->creds);
++		client->name = new_name;
++		client->creds = ctx->client_creds;
++		ctx->client_creds = GSS_C_NO_CREDENTIAL;
++		client->updated = 1;
++		return GSS_S_COMPLETE;
++	}
+ 
+ 	client->mech = NULL;
+ 
+@@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ 	if (client->mech == NULL)
+ 		return GSS_S_FAILURE;
+ 
++	if (ctx->client_creds &&
++	    (ctx->major = gss_inquire_cred_by_mech(&ctx->minor,
++	     ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) {
++		ssh_gssapi_error(ctx);
++		return (ctx->major);
++	}
++
+ 	if ((ctx->major = gss_display_name(&ctx->minor, ctx->client,
+ 	    &client->displayname, NULL))) {
+ 		ssh_gssapi_error(ctx);
+@@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ 		return (ctx->major);
+ 	}
+ 
++	gss_release_buffer(&ctx->minor, &ename);
++
+ 	/* We can't copy this structure, so we just move the pointer to it */
+ 	client->creds = ctx->client_creds;
+ 	ctx->client_creds = GSS_C_NO_CREDENTIAL;
+@@ -319,11 +395,20 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ void
+ ssh_gssapi_cleanup_creds(void)
+ {
+-	if (gssapi_client.store.filename != NULL) {
+-		/* Unlink probably isn't sufficient */
+-		debug("removing gssapi cred file\"%s\"",
+-		    gssapi_client.store.filename);
+-		unlink(gssapi_client.store.filename);
++	krb5_ccache ccache = NULL;
++	krb5_error_code problem;
++
++	if (gssapi_client.store.data != NULL) {
++		if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) {
++			debug("%s: krb5_cc_resolve(): %.100s", __func__,
++				krb5_get_err_text(gssapi_client.store.data, problem));
++		} else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) {
++			debug("%s: krb5_cc_destroy(): %.100s", __func__,
++				krb5_get_err_text(gssapi_client.store.data, problem));
++		} else {
++			krb5_free_context(gssapi_client.store.data);
++			gssapi_client.store.data = NULL;
++		}
+ 	}
+ }
+ 
+@@ -356,19 +441,23 @@ ssh_gssapi_do_child(char ***envp, u_int *envsizep)
+ 
+ /* Privileged */
+ int
+-ssh_gssapi_userok(char *user)
++ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ 	OM_uint32 lmin;
+ 
++	(void) kex; /* used in privilege separation */
++
+ 	if (gssapi_client.exportedname.length == 0 ||
+ 	    gssapi_client.exportedname.value == NULL) {
+ 		debug("No suitable client data");
+ 		return 0;
+ 	}
+ 	if (gssapi_client.mech && gssapi_client.mech->userok)
+-		if ((*gssapi_client.mech->userok)(&gssapi_client, user))
++		if ((*gssapi_client.mech->userok)(&gssapi_client, user)) {
++			gssapi_client.used = 1;
++			gssapi_client.store.owner = pw;
+ 			return 1;
+-		else {
++		} else {
+ 			/* Destroy delegated credentials if userok fails */
+ 			gss_release_buffer(&lmin, &gssapi_client.displayname);
+ 			gss_release_buffer(&lmin, &gssapi_client.exportedname);
+@@ -382,14 +471,90 @@ ssh_gssapi_userok(char *user)
+ 	return (0);
+ }
+ 
+-/* Privileged */
+-OM_uint32
+-ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
++/* These bits are only used for rekeying. The unpriviledged child is running
++ * as the user, the monitor is root.
++ *
++ * In the child, we want to :
++ *    *) Ask the monitor to store our credentials into the store we specify
++ *    *) If it succeeds, maybe do a PAM update
++ */
++
++/* Stuff for PAM */
++
++#ifdef USE_PAM
++static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg,
++    struct pam_response **resp, void *data)
+ {
+-	ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
+-	    gssbuf, gssmic, NULL);
++	return (PAM_CONV_ERR);
++}
++#endif
+ 
+-	return (ctx->major);
++void
++ssh_gssapi_rekey_creds(void) {
++	int ok;
++#ifdef USE_PAM
++	int ret;
++	pam_handle_t *pamh = NULL;
++	struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL};
++	char *envstr;
++#endif
++
++	if (gssapi_client.store.filename == NULL &&
++	    gssapi_client.store.envval == NULL &&
++	    gssapi_client.store.envvar == NULL)
++		return;
++
++	ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store));
++
++	if (!ok)
++		return;
++
++	debug("Rekeyed credentials stored successfully");
++
++	/* Actually managing to play with the ssh pam stack from here will
++	 * be next to impossible. In any case, we may want different options
++	 * for rekeying. So, use our own :)
++	 */
++#ifdef USE_PAM	
++	if (!use_privsep) {
++		debug("Not even going to try and do PAM with privsep disabled");
++		return;
++	}
++
++	ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name,
++ 	    &pamconv, &pamh);
++	if (ret)
++		return;
++
++	xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar,
++	    gssapi_client.store.envval);
++
++	ret = pam_putenv(pamh, envstr);
++	if (!ret)
++		pam_setcred(pamh, PAM_REINITIALIZE_CRED);
++	pam_end(pamh, PAM_SUCCESS);
++#endif
++}
++
++int
++ssh_gssapi_update_creds(ssh_gssapi_ccache *store) {
++	int ok = 0;
++
++	/* Check we've got credentials to store */
++	if (!gssapi_client.updated)
++		return 0;
++
++	gssapi_client.updated = 0;
++
++	temporarily_use_uid(gssapi_client.store.owner);
++	if (gssapi_client.mech && gssapi_client.mech->updatecreds)
++		ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client);
++	else
++		debug("No update function for this mechanism");
++
++	restore_uid();
++
++	return ok;
+ }
+ 
+ /* Privileged */
+diff --git a/kex.c b/kex.c
+index ce85f043..574c7609 100644
+--- a/kex.c
++++ b/kex.c
+@@ -57,11 +57,16 @@
+ #include "misc.h"
+ #include "dispatch.h"
+ #include "monitor.h"
++#include "xmalloc.h"
+ 
+ #include "ssherr.h"
+ #include "sshbuf.h"
+ #include "digest.h"
+ 
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++
+ /* prototype */
+ static int kex_choose_conf(struct ssh *);
+ static int kex_input_newkeys(int, u_int32_t, struct ssh *);
+@@ -115,15 +120,28 @@ static const struct kexalg kexalgs[] = {
+ #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
+ 	{ NULL, 0, -1, -1},
+ };
++static const struct kexalg gss_kexalgs[] = {
++#ifdef GSSAPI
++	{ KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 },
++	{ KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 },
++	{ KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 },
++	{ KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 },
++	{ KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 },
++	{ KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256,
++	    NID_X9_62_prime256v1, SSH_DIGEST_SHA256 },
++	{ KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
++#endif
++	{ NULL, 0, -1, -1},
++};
+ 
+-char *
+-kex_alg_list(char sep)
++static char *
++kex_alg_list_internal(char sep, const struct kexalg *algs)
+ {
+ 	char *ret = NULL, *tmp;
+ 	size_t nlen, rlen = 0;
+ 	const struct kexalg *k;
+ 
+-	for (k = kexalgs; k->name != NULL; k++) {
++	for (k = algs; k->name != NULL; k++) {
+ 		if (ret != NULL)
+ 			ret[rlen++] = sep;
+ 		nlen = strlen(k->name);
+@@ -138,6 +156,18 @@ kex_alg_list(char sep)
+ 	return ret;
+ }
+ 
++char *
++kex_alg_list(char sep)
++{
++	return kex_alg_list_internal(sep, kexalgs);
++}
++
++char *
++kex_gss_alg_list(char sep)
++{
++	return kex_alg_list_internal(sep, gss_kexalgs);
++}
++
+ static const struct kexalg *
+ kex_alg_by_name(const char *name)
+ {
+@@ -147,6 +177,10 @@ kex_alg_by_name(const char *name)
+ 		if (strcmp(k->name, name) == 0)
+ 			return k;
+ 	}
++	for (k = gss_kexalgs; k->name != NULL; k++) {
++		if (strncmp(k->name, name, strlen(k->name)) == 0)
++			return k;
++	}
+ 	return NULL;
+ }
+ 
+@@ -315,6 +349,29 @@ kex_assemble_names(char **listp, const char *def, const char *all)
+ 	return r;
+ }
+ 
++/* Validate GSS KEX method name list */
++int
++kex_gss_names_valid(const char *names)
++{
++	char *s, *cp, *p;
++
++	if (names == NULL || *names == '\0')
++		return 0;
++	s = cp = xstrdup(names);
++	for ((p = strsep(&cp, ",")); p && *p != '\0';
++	    (p = strsep(&cp, ","))) {
++		if (strncmp(p, "gss-", 4) != 0
++		  || kex_alg_by_name(p) == NULL) {
++			error("Unsupported KEX algorithm \"%.100s\"", p);
++			free(s);
++			return 0;
++		}
++	}
++	debug3("gss kex names ok: [%s]", names);
++	free(s);
++	return 1;
++}
++
+ /* put algorithm proposal into buffer */
+ int
+ kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX])
+@@ -698,6 +755,9 @@ kex_free(struct kex *kex)
+ 	sshbuf_free(kex->server_version);
+ 	sshbuf_free(kex->client_pub);
+ 	free(kex->session_id);
++#ifdef GSSAPI
++	free(kex->gss_host);
++#endif /* GSSAPI */
+ 	free(kex->failed_choice);
+ 	free(kex->hostkey_alg);
+ 	free(kex->name);
+diff --git a/kex.h b/kex.h
+index a5ae6ac0..fe714141 100644
+--- a/kex.h
++++ b/kex.h
+@@ -102,6 +102,15 @@ enum kex_exchange {
+ 	KEX_ECDH_SHA2,
+ 	KEX_C25519_SHA256,
+ 	KEX_KEM_SNTRUP4591761X25519_SHA512,
++#ifdef GSSAPI
++	KEX_GSS_GRP1_SHA1,
++	KEX_GSS_GRP14_SHA1,
++	KEX_GSS_GRP14_SHA256,
++	KEX_GSS_GRP16_SHA512,
++	KEX_GSS_GEX_SHA1,
++	KEX_GSS_NISTP256_SHA256,
++	KEX_GSS_C25519_SHA256,
++#endif
+ 	KEX_MAX
+ };
+ 
+@@ -153,6 +162,12 @@ struct kex {
+ 	u_int	flags;
+ 	int	hash_alg;
+ 	int	ec_nid;
++#ifdef GSSAPI
++	int	gss_deleg_creds;
++	int	gss_trust_dns;
++	char    *gss_host;
++	char	*gss_client;
++#endif
+ 	char	*failed_choice;
+ 	int	(*verify_host_key)(struct sshkey *, struct ssh *);
+ 	struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
+@@ -174,8 +189,10 @@ struct kex {
+ 
+ int	 kex_names_valid(const char *);
+ char	*kex_alg_list(char);
++char	*kex_gss_alg_list(char);
+ char	*kex_names_cat(const char *, const char *);
+ int	 kex_assemble_names(char **, const char *, const char *);
++int	 kex_gss_names_valid(const char *);
+ 
+ int	 kex_exchange_identification(struct ssh *, int, const char *);
+ 
+@@ -202,6 +219,12 @@ int	 kexgex_client(struct ssh *);
+ int	 kexgex_server(struct ssh *);
+ int	 kex_gen_client(struct ssh *);
+ int	 kex_gen_server(struct ssh *);
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++int	 kexgssgex_client(struct ssh *);
++int	 kexgssgex_server(struct ssh *);
++int	 kexgss_client(struct ssh *);
++int	 kexgss_server(struct ssh *);
++#endif
+ 
+ int	 kex_dh_keypair(struct kex *);
+ int	 kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
+@@ -234,6 +257,12 @@ int	 kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
+     const BIGNUM *, const u_char *, size_t,
+     u_char *, size_t *);
+ 
++int	 kex_gen_hash(int hash_alg, const struct sshbuf *client_version,
++    const struct sshbuf *server_version, const struct sshbuf *client_kexinit,
++    const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob,
++    const struct sshbuf *client_pub, const struct sshbuf *server_pub,
++    const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen);
++
+ void	kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE])
+ 	__attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE)))
+ 	__attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE)));
+diff --git a/kexdh.c b/kexdh.c
+index 67133e33..edaa4676 100644
+--- a/kexdh.c
++++ b/kexdh.c
+@@ -48,13 +48,23 @@ kex_dh_keygen(struct kex *kex)
+ {
+ 	switch (kex->kex_type) {
+ 	case KEX_DH_GRP1_SHA1:
++#ifdef GSSAPI
++	case KEX_GSS_GRP1_SHA1:
++#endif
+ 		kex->dh = dh_new_group1();
+ 		break;
+ 	case KEX_DH_GRP14_SHA1:
+ 	case KEX_DH_GRP14_SHA256:
++#ifdef GSSAPI
++	case KEX_GSS_GRP14_SHA1:
++	case KEX_GSS_GRP14_SHA256:
++#endif
+ 		kex->dh = dh_new_group14();
+ 		break;
+ 	case KEX_DH_GRP16_SHA512:
++#ifdef GSSAPI
++	case KEX_GSS_GRP16_SHA512:
++#endif
+ 		kex->dh = dh_new_group16();
+ 		break;
+ 	case KEX_DH_GRP18_SHA512:
+diff --git a/kexgen.c b/kexgen.c
+index 69348b96..c0e8c2f4 100644
+--- a/kexgen.c
++++ b/kexgen.c
+@@ -44,7 +44,7 @@
+ static int input_kex_gen_init(int, u_int32_t, struct ssh *);
+ static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh);
+ 
+-static int
++int
+ kex_gen_hash(
+     int hash_alg,
+     const struct sshbuf *client_version,
+diff --git a/kexgssc.c b/kexgssc.c
+new file mode 100644
+index 00000000..f6e1405e
+--- /dev/null
++++ b/kexgssc.c
+@@ -0,0 +1,606 @@
++/*
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++
++#include "includes.h"
++
++#include <openssl/crypto.h>
++#include <openssl/bn.h>
++
++#include <string.h>
++
++#include "xmalloc.h"
++#include "sshbuf.h"
++#include "ssh2.h"
++#include "sshkey.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "digest.h"
++#include "ssherr.h"
++
++#include "ssh-gss.h"
++
++int
++kexgss_client(struct ssh *ssh)
++{
++	struct kex *kex = ssh->kex;
++	gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
++	    recv_tok = GSS_C_EMPTY_BUFFER,
++	    gssbuf, msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
++	Gssctxt *ctxt;
++	OM_uint32 maj_status, min_status, ret_flags;
++	struct sshbuf *server_blob = NULL;
++	struct sshbuf *shared_secret = NULL;
++	struct sshbuf *server_host_key_blob = NULL;
++	struct sshbuf *empty = NULL;
++	u_char *msg;
++	int type = 0;
++	int first = 1;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	size_t hashlen;
++	u_char c;
++	int r;
++
++	/* Initialise our GSSAPI world */
++	ssh_gssapi_build_ctx(&ctxt);
++	if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
++	    == GSS_C_NO_OID)
++		fatal("Couldn't identify host exchange");
++
++	if (ssh_gssapi_import_name(ctxt, kex->gss_host))
++		fatal("Couldn't import hostname");
++
++	if (kex->gss_client &&
++	    ssh_gssapi_client_identity(ctxt, kex->gss_client))
++		fatal("Couldn't acquire client credentials");
++
++	/* Step 1 */
++	switch (kex->kex_type) {
++	case KEX_GSS_GRP1_SHA1:
++	case KEX_GSS_GRP14_SHA1:
++	case KEX_GSS_GRP14_SHA256:
++	case KEX_GSS_GRP16_SHA512:
++		r = kex_dh_keypair(kex);
++		break;
++	case KEX_GSS_NISTP256_SHA256:
++		r = kex_ecdh_keypair(kex);
++		break;
++	case KEX_GSS_C25519_SHA256:
++		r = kex_c25519_keypair(kex);
++		break;
++	default:
++		fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type);
++	}
++	if (r != 0)
++		return r;
++
++	token_ptr = GSS_C_NO_BUFFER;
++
++	do {
++		debug("Calling gss_init_sec_context");
++
++		maj_status = ssh_gssapi_init_ctx(ctxt,
++		    kex->gss_deleg_creds, token_ptr, &send_tok,
++		    &ret_flags);
++
++		if (GSS_ERROR(maj_status)) {
++			/* XXX Useles code: Missing send? */
++			if (send_tok.length != 0) {
++				if ((r = sshpkt_start(ssh,
++				        SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++				    (r = sshpkt_put_string(ssh, send_tok.value,
++				        send_tok.length)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++			}
++			fatal("gss_init_context failed");
++		}
++
++		/* If we've got an old receive buffer get rid of it */
++		if (token_ptr != GSS_C_NO_BUFFER)
++			gss_release_buffer(&min_status, &recv_tok);
++
++		if (maj_status == GSS_S_COMPLETE) {
++			/* If mutual state flag is not true, kex fails */
++			if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++				fatal("Mutual authentication failed");
++
++			/* If integ avail flag is not true kex fails */
++			if (!(ret_flags & GSS_C_INTEG_FLAG))
++				fatal("Integrity check failed");
++		}
++
++		/*
++		 * If we have data to send, then the last message that we
++		 * received cannot have been a 'complete'.
++		 */
++		if (send_tok.length != 0) {
++			if (first) {
++				if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
++				    (r = sshpkt_put_string(ssh, send_tok.value,
++				        send_tok.length)) != 0 ||
++				    (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0)
++					fatal("failed to construct packet: %s", ssh_err(r));
++				first = 0;
++			} else {
++				if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++				    (r = sshpkt_put_string(ssh, send_tok.value,
++				        send_tok.length)) != 0)
++					fatal("failed to construct packet: %s", ssh_err(r));
++			}
++			if ((r = sshpkt_send(ssh)) != 0)
++				fatal("failed to send packet: %s", ssh_err(r));
++			gss_release_buffer(&min_status, &send_tok);
++
++			/* If we've sent them data, they should reply */
++			do {
++				type = ssh_packet_read(ssh);
++				if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
++					debug("Received KEXGSS_HOSTKEY");
++					if (server_host_key_blob)
++						fatal("Server host key received more than once");
++					if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0)
++						fatal("Failed to read server host key: %s", ssh_err(r));
++				}
++			} while (type == SSH2_MSG_KEXGSS_HOSTKEY);
++
++			switch (type) {
++			case SSH2_MSG_KEXGSS_CONTINUE:
++				debug("Received GSSAPI_CONTINUE");
++				if (maj_status == GSS_S_COMPLETE)
++					fatal("GSSAPI Continue received from server when complete");
++				if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++				        &recv_tok)) != 0 ||
++				    (r = sshpkt_get_end(ssh)) != 0)
++					fatal("Failed to read token: %s", ssh_err(r));
++				break;
++			case SSH2_MSG_KEXGSS_COMPLETE:
++				debug("Received GSSAPI_COMPLETE");
++				if (msg_tok.value != NULL)
++				        fatal("Received GSSAPI_COMPLETE twice?");
++				if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
++				    (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++				        &msg_tok)) != 0)
++					fatal("Failed to read message: %s", ssh_err(r));
++
++				/* Is there a token included? */
++				if ((r = sshpkt_get_u8(ssh, &c)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++				if (c) {
++					if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
++					    ssh, &recv_tok)) != 0)
++						fatal("Failed to read token: %s", ssh_err(r));
++					/* If we're already complete - protocol error */
++					if (maj_status == GSS_S_COMPLETE)
++						sshpkt_disconnect(ssh, "Protocol error: received token when complete");
++				} else {
++					/* No token included */
++					if (maj_status != GSS_S_COMPLETE)
++						sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
++				}
++				if ((r = sshpkt_get_end(ssh)) != 0) {
++					fatal("Expecting end of packet.");
++				}
++				break;
++			case SSH2_MSG_KEXGSS_ERROR:
++				debug("Received Error");
++				if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
++				    (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
++				    (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
++				    (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
++				    (r = sshpkt_get_end(ssh)) != 0)
++					fatal("sshpkt_get failed: %s", ssh_err(r));
++				fatal("GSSAPI Error: \n%.400s", msg);
++			default:
++				sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
++				    type);
++			}
++			token_ptr = &recv_tok;
++		} else {
++			/* No data, and not complete */
++			if (maj_status != GSS_S_COMPLETE)
++				fatal("Not complete, and no token output");
++		}
++	} while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++	/*
++	 * We _must_ have received a COMPLETE message in reply from the
++	 * server, which will have set server_blob and msg_tok
++	 */
++
++	if (type != SSH2_MSG_KEXGSS_COMPLETE)
++		fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
++
++	/* compute shared secret */
++	switch (kex->kex_type) {
++	case KEX_GSS_GRP1_SHA1:
++	case KEX_GSS_GRP14_SHA1:
++	case KEX_GSS_GRP14_SHA256:
++	case KEX_GSS_GRP16_SHA512:
++		r = kex_dh_dec(kex, server_blob, &shared_secret);
++		break;
++	case KEX_GSS_C25519_SHA256:
++		if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80)
++			fatal("The received key has MSB of last octet set!");
++		r = kex_c25519_dec(kex, server_blob, &shared_secret);
++		break;
++	case KEX_GSS_NISTP256_SHA256:
++		if (sshbuf_len(server_blob) != 65)
++			fatal("The received NIST-P256 key did not match"
++			    "expected length (expected 65, got %zu)", sshbuf_len(server_blob));
++
++		if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED)
++			fatal("The received NIST-P256 key does not have first octet 0x04");
++
++		r = kex_ecdh_dec(kex, server_blob, &shared_secret);
++		break;
++	default:
++		r = SSH_ERR_INVALID_ARGUMENT;
++		break;
++	}
++	if (r != 0)
++		goto out;
++
++	if ((empty = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++
++	hashlen = sizeof(hash);
++	if ((r = kex_gen_hash(
++	    kex->hash_alg,
++	    kex->client_version,
++	    kex->server_version,
++	    kex->my,
++	    kex->peer,
++	    (server_host_key_blob ? server_host_key_blob : empty),
++	    kex->client_pub,
++	    server_blob,
++	    shared_secret,
++	    hash, &hashlen)) != 0)
++		fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type);
++
++	gssbuf.value = hash;
++	gssbuf.length = hashlen;
++
++	/* Verify that the hash matches the MIC we just got. */
++	if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
++		sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
++
++	gss_release_buffer(&min_status, &msg_tok);
++
++	if (kex->gss_deleg_creds)
++		ssh_gssapi_credentials_updated(ctxt);
++
++	if (gss_kex_context == NULL)
++		gss_kex_context = ctxt;
++	else
++		ssh_gssapi_delete_ctx(&ctxt);
++
++	if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++		r = kex_send_newkeys(ssh);
++
++out:
++	explicit_bzero(hash, sizeof(hash));
++	explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key));
++	sshbuf_free(empty);
++	sshbuf_free(server_host_key_blob);
++	sshbuf_free(server_blob);
++	sshbuf_free(shared_secret);
++	sshbuf_free(kex->client_pub);
++	kex->client_pub = NULL;
++	return r;
++}
++
++int
++kexgssgex_client(struct ssh *ssh)
++{
++	struct kex *kex = ssh->kex;
++	gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
++	    recv_tok = GSS_C_EMPTY_BUFFER, gssbuf,
++            msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
++	Gssctxt *ctxt;
++	OM_uint32 maj_status, min_status, ret_flags;
++	struct sshbuf *shared_secret = NULL;
++	BIGNUM *p = NULL;
++	BIGNUM *g = NULL;
++	struct sshbuf *buf = NULL;
++	struct sshbuf *server_host_key_blob = NULL;
++	struct sshbuf *server_blob = NULL;
++	BIGNUM *dh_server_pub = NULL;
++	u_char *msg;
++	int type = 0;
++	int first = 1;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	size_t hashlen;
++	const BIGNUM *pub_key, *dh_p, *dh_g;
++	int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX;
++	struct sshbuf *empty = NULL;
++	u_char c;
++	int r;
++
++	/* Initialise our GSSAPI world */
++	ssh_gssapi_build_ctx(&ctxt);
++	if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
++	    == GSS_C_NO_OID)
++		fatal("Couldn't identify host exchange");
++
++	if (ssh_gssapi_import_name(ctxt, kex->gss_host))
++		fatal("Couldn't import hostname");
++
++	if (kex->gss_client &&
++	    ssh_gssapi_client_identity(ctxt, kex->gss_client))
++		fatal("Couldn't acquire client credentials");
++
++	debug("Doing group exchange");
++	nbits = dh_estimate(kex->dh_need * 8);
++
++	kex->min = DH_GRP_MIN;
++	kex->max = DH_GRP_MAX;
++	kex->nbits = nbits;
++	if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 ||
++	    (r = sshpkt_put_u32(ssh, min)) != 0 ||
++	    (r = sshpkt_put_u32(ssh, nbits)) != 0 ||
++	    (r = sshpkt_put_u32(ssh, max)) != 0 ||
++	    (r = sshpkt_send(ssh)) != 0)
++		fatal("Failed to construct a packet: %s", ssh_err(r));
++
++	if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0)
++		fatal("Error: %s", ssh_err(r));
++
++	if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 ||
++	    (r = sshpkt_get_bignum2(ssh, &g)) != 0 ||
++	    (r = sshpkt_get_end(ssh)) != 0)
++		fatal("shpkt_get_bignum2 failed: %s", ssh_err(r));
++
++	if (BN_num_bits(p) < min || BN_num_bits(p) > max)
++		fatal("GSSGRP_GEX group out of range: %d !< %d !< %d",
++		    min, BN_num_bits(p), max);
++
++	if ((kex->dh = dh_new_group(g, p)) == NULL)
++		fatal("dn_new_group() failed");
++	p = g = NULL; /* belong to kex->dh now */
++
++	if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
++		goto out;
++	DH_get0_key(kex->dh, &pub_key, NULL);
++
++	token_ptr = GSS_C_NO_BUFFER;
++
++	do {
++		/* Step 2 - call GSS_Init_sec_context() */
++		debug("Calling gss_init_sec_context");
++
++		maj_status = ssh_gssapi_init_ctx(ctxt,
++		    kex->gss_deleg_creds, token_ptr, &send_tok,
++		    &ret_flags);
++
++		if (GSS_ERROR(maj_status)) {
++			/* XXX Useles code: Missing send? */
++			if (send_tok.length != 0) {
++				if ((r = sshpkt_start(ssh,
++				        SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++				    (r = sshpkt_put_string(ssh, send_tok.value,
++				        send_tok.length)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++			}
++			fatal("gss_init_context failed");
++		}
++
++		/* If we've got an old receive buffer get rid of it */
++		if (token_ptr != GSS_C_NO_BUFFER)
++			gss_release_buffer(&min_status, &recv_tok);
++
++		if (maj_status == GSS_S_COMPLETE) {
++			/* If mutual state flag is not true, kex fails */
++			if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++				fatal("Mutual authentication failed");
++
++			/* If integ avail flag is not true kex fails */
++			if (!(ret_flags & GSS_C_INTEG_FLAG))
++				fatal("Integrity check failed");
++		}
++
++		/*
++		 * If we have data to send, then the last message that we
++		 * received cannot have been a 'complete'.
++		 */
++		if (send_tok.length != 0) {
++			if (first) {
++				if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
++				    (r = sshpkt_put_string(ssh, send_tok.value,
++				        send_tok.length)) != 0 ||
++				    (r = sshpkt_put_bignum2(ssh, pub_key)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++				first = 0;
++			} else {
++				if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++				    (r = sshpkt_put_string(ssh,send_tok.value,
++				        send_tok.length)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++			}
++			if ((r = sshpkt_send(ssh)) != 0)
++				fatal("sshpkt_send failed: %s", ssh_err(r));
++			gss_release_buffer(&min_status, &send_tok);
++
++			/* If we've sent them data, they should reply */
++			do {
++				type = ssh_packet_read(ssh);
++				if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
++					debug("Received KEXGSS_HOSTKEY");
++					if (server_host_key_blob)
++						fatal("Server host key received more than once");
++					if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0)
++						fatal("sshpkt failed: %s", ssh_err(r));
++				}
++			} while (type == SSH2_MSG_KEXGSS_HOSTKEY);
++
++			switch (type) {
++			case SSH2_MSG_KEXGSS_CONTINUE:
++				debug("Received GSSAPI_CONTINUE");
++				if (maj_status == GSS_S_COMPLETE)
++					fatal("GSSAPI Continue received from server when complete");
++				if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++				        &recv_tok)) != 0 ||
++				    (r = sshpkt_get_end(ssh)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++				break;
++			case SSH2_MSG_KEXGSS_COMPLETE:
++				debug("Received GSSAPI_COMPLETE");
++				if (msg_tok.value != NULL)
++				        fatal("Received GSSAPI_COMPLETE twice?");
++				if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
++				    (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++				        &msg_tok)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++
++				/* Is there a token included? */
++				if ((r = sshpkt_get_u8(ssh, &c)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++				if (c) {
++					if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
++					        ssh, &recv_tok)) != 0 ||
++					    (r = sshpkt_get_end(ssh)) != 0)
++						fatal("sshpkt failed: %s", ssh_err(r));
++					/* If we're already complete - protocol error */
++					if (maj_status == GSS_S_COMPLETE)
++						sshpkt_disconnect(ssh, "Protocol error: received token when complete");
++				} else {
++					/* No token included */
++					if (maj_status != GSS_S_COMPLETE)
++						sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
++				}
++				break;
++			case SSH2_MSG_KEXGSS_ERROR:
++				debug("Received Error");
++				if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
++				    (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
++				    (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
++				    (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
++				    (r = sshpkt_get_end(ssh)) != 0)
++					fatal("sshpkt failed: %s", ssh_err(r));
++				fatal("GSSAPI Error: \n%.400s", msg);
++			default:
++				sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
++				    type);
++			}
++			token_ptr = &recv_tok;
++		} else {
++			/* No data, and not complete */
++			if (maj_status != GSS_S_COMPLETE)
++				fatal("Not complete, and no token output");
++		}
++	} while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++	/*
++	 * We _must_ have received a COMPLETE message in reply from the
++	 * server, which will have set dh_server_pub and msg_tok
++	 */
++
++	if (type != SSH2_MSG_KEXGSS_COMPLETE)
++		fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
++
++	/* 7. C verifies that the key Q_S is valid */
++	/* 8. C computes shared secret */
++	if ((buf = sshbuf_new()) == NULL ||
++	    (r = sshbuf_put_stringb(buf, server_blob)) != 0 ||
++	    (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0)
++		goto out;
++	sshbuf_free(buf);
++	buf = NULL;
++
++	if ((shared_secret = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++
++	if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0)
++		goto out;
++	if ((empty = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++
++	DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++	hashlen = sizeof(hash);
++	if ((r = kexgex_hash(
++	    kex->hash_alg,
++	    kex->client_version,
++	    kex->server_version,
++	    kex->my,
++	    kex->peer,
++	    (server_host_key_blob ? server_host_key_blob : empty),
++ 	    kex->min, kex->nbits, kex->max,
++	    dh_p, dh_g,
++	    pub_key,
++	    dh_server_pub,
++	    sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
++	    hash, &hashlen)) != 0)
++		fatal("Failed to calculate hash: %s", ssh_err(r));
++
++	gssbuf.value = hash;
++	gssbuf.length = hashlen;
++
++	/* Verify that the hash matches the MIC we just got. */
++	if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
++		sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
++
++	gss_release_buffer(&min_status, &msg_tok);
++
++	/* save session id */
++	if (kex->session_id == NULL) {
++		kex->session_id_len = hashlen;
++		kex->session_id = xmalloc(kex->session_id_len);
++		memcpy(kex->session_id, hash, kex->session_id_len);
++	}
++
++	if (kex->gss_deleg_creds)
++		ssh_gssapi_credentials_updated(ctxt);
++
++	if (gss_kex_context == NULL)
++		gss_kex_context = ctxt;
++	else
++		ssh_gssapi_delete_ctx(&ctxt);
++
++	/* Finally derive the keys and send them */
++	if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++		r = kex_send_newkeys(ssh);
++out:
++	sshbuf_free(buf);
++	sshbuf_free(server_blob);
++	sshbuf_free(empty);
++	explicit_bzero(hash, sizeof(hash));
++	DH_free(kex->dh);
++	kex->dh = NULL;
++	BN_clear_free(dh_server_pub);
++	sshbuf_free(shared_secret);
++	sshbuf_free(server_host_key_blob);
++	return r;
++}
++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
+diff --git a/kexgsss.c b/kexgsss.c
+new file mode 100644
+index 00000000..60bc02de
+--- /dev/null
++++ b/kexgsss.c
+@@ -0,0 +1,474 @@
++/*
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++
++#include <string.h>
++
++#include <openssl/crypto.h>
++#include <openssl/bn.h>
++
++#include "xmalloc.h"
++#include "sshbuf.h"
++#include "ssh2.h"
++#include "sshkey.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "ssh-gss.h"
++#include "monitor_wrap.h"
++#include "misc.h"      /* servconf.h needs misc.h for struct ForwardOptions */
++#include "servconf.h"
++#include "ssh-gss.h"
++#include "digest.h"
++#include "ssherr.h"
++
++extern ServerOptions options;
++
++int
++kexgss_server(struct ssh *ssh)
++{
++	struct kex *kex = ssh->kex;
++	OM_uint32 maj_status, min_status;
++
++	/*
++	 * Some GSSAPI implementations use the input value of ret_flags (an
++	 * output variable) as a means of triggering mechanism specific
++	 * features. Initializing it to zero avoids inadvertently
++	 * activating this non-standard behaviour.
++	 */
++
++	OM_uint32 ret_flags = 0;
++	gss_buffer_desc gssbuf, recv_tok, msg_tok;
++	gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
++	Gssctxt *ctxt = NULL;
++	struct sshbuf *shared_secret = NULL;
++	struct sshbuf *client_pubkey = NULL;
++	struct sshbuf *server_pubkey = NULL;
++	struct sshbuf *empty = sshbuf_new();
++	int type = 0;
++	gss_OID oid;
++	char *mechs;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	size_t hashlen;
++	int r;
++
++	/* Initialise GSSAPI */
++
++	/* If we're rekeying, privsep means that some of the private structures
++	 * in the GSSAPI code are no longer available. This kludges them back
++	 * into life
++	 */
++	if (!ssh_gssapi_oid_table_ok()) {
++		mechs = ssh_gssapi_server_mechanisms();
++		free(mechs);
++	}
++
++	debug2("%s: Identifying %s", __func__, kex->name);
++	oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++	if (oid == GSS_C_NO_OID)
++	   fatal("Unknown gssapi mechanism");
++
++	debug2("%s: Acquiring credentials", __func__);
++
++	if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
++		fatal("Unable to acquire credentials for the server");
++
++	do {
++		debug("Wait SSH2_MSG_KEXGSS_INIT");
++		type = ssh_packet_read(ssh);
++		switch(type) {
++		case SSH2_MSG_KEXGSS_INIT:
++			if (client_pubkey != NULL)
++				fatal("Received KEXGSS_INIT after initialising");
++			if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++			        &recv_tok)) != 0 ||
++			    (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 ||
++			    (r = sshpkt_get_end(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++
++			switch (kex->kex_type) {
++			case KEX_GSS_GRP1_SHA1:
++			case KEX_GSS_GRP14_SHA1:
++			case KEX_GSS_GRP14_SHA256:
++			case KEX_GSS_GRP16_SHA512:
++				r = kex_dh_enc(kex, client_pubkey, &server_pubkey,
++				    &shared_secret);
++				break;
++			case KEX_GSS_NISTP256_SHA256:
++				r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey,
++				    &shared_secret);
++				break;
++			case KEX_GSS_C25519_SHA256:
++				r = kex_c25519_enc(kex, client_pubkey, &server_pubkey,
++				    &shared_secret);
++				break;
++			default:
++				fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type);
++			}
++			if (r != 0)
++				goto out;
++
++			/* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
++			break;
++		case SSH2_MSG_KEXGSS_CONTINUE:
++			if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++			        &recv_tok)) != 0 ||
++			    (r = sshpkt_get_end(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++			break;
++		default:
++			sshpkt_disconnect(ssh,
++			    "Protocol error: didn't expect packet type %d",
++			    type);
++		}
++
++		maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
++		    &send_tok, &ret_flags));
++
++		gss_release_buffer(&min_status, &recv_tok);
++
++		if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
++			fatal("Zero length token output when incomplete");
++
++		if (client_pubkey == NULL)
++			fatal("No client public key");
++
++		if (maj_status & GSS_S_CONTINUE_NEEDED) {
++			debug("Sending GSSAPI_CONTINUE");
++			if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++			    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++			    (r = sshpkt_send(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++			gss_release_buffer(&min_status, &send_tok);
++		}
++	} while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++	if (GSS_ERROR(maj_status)) {
++		if (send_tok.length > 0) {
++			if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++			    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++			    (r = sshpkt_send(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++		}
++		fatal("accept_ctx died");
++	}
++
++	if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++		fatal("Mutual Authentication flag wasn't set");
++
++	if (!(ret_flags & GSS_C_INTEG_FLAG))
++		fatal("Integrity flag wasn't set");
++
++	hashlen = sizeof(hash);
++	if ((r = kex_gen_hash(
++	    kex->hash_alg,
++	    kex->client_version,
++	    kex->server_version,
++	    kex->peer,
++	    kex->my,
++	    empty,
++	    client_pubkey,
++	    server_pubkey,
++	    shared_secret,
++	    hash, &hashlen)) != 0)
++		goto out;
++
++	gssbuf.value = hash;
++	gssbuf.length = hashlen;
++
++	if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
++		fatal("Couldn't get MIC");
++
++	if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
++	    (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 ||
++	    (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
++		fatal("sshpkt failed: %s", ssh_err(r));
++
++	if (send_tok.length != 0) {
++		if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
++		    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
++			fatal("sshpkt failed: %s", ssh_err(r));
++	} else {
++		if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
++			fatal("sshpkt failed: %s", ssh_err(r));
++	}
++	if ((r = sshpkt_send(ssh)) != 0)
++		fatal("sshpkt_send failed: %s", ssh_err(r));
++
++	gss_release_buffer(&min_status, &send_tok);
++	gss_release_buffer(&min_status, &msg_tok);
++
++	if (gss_kex_context == NULL)
++		gss_kex_context = ctxt;
++	else
++		ssh_gssapi_delete_ctx(&ctxt);
++
++	if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++		r = kex_send_newkeys(ssh);
++
++	/* If this was a rekey, then save out any delegated credentials we
++	 * just exchanged.  */
++	if (options.gss_store_rekey)
++		ssh_gssapi_rekey_creds();
++out:
++	sshbuf_free(empty);
++	explicit_bzero(hash, sizeof(hash));
++	sshbuf_free(shared_secret);
++	sshbuf_free(client_pubkey);
++	sshbuf_free(server_pubkey);
++	return r;
++}
++
++int
++kexgssgex_server(struct ssh *ssh)
++{
++	struct kex *kex = ssh->kex;
++	OM_uint32 maj_status, min_status;
++
++	/*
++	 * Some GSSAPI implementations use the input value of ret_flags (an
++	 * output variable) as a means of triggering mechanism specific
++	 * features. Initializing it to zero avoids inadvertently
++	 * activating this non-standard behaviour.
++	 */
++
++	OM_uint32 ret_flags = 0;
++	gss_buffer_desc gssbuf, recv_tok, msg_tok;
++	gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
++	Gssctxt *ctxt = NULL;
++	struct sshbuf *shared_secret = NULL;
++	int type = 0;
++	gss_OID oid;
++	char *mechs;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	size_t hashlen;
++	BIGNUM *dh_client_pub = NULL;
++	const BIGNUM *pub_key, *dh_p, *dh_g;
++	int min = -1, max = -1, nbits = -1;
++	int cmin = -1, cmax = -1; /* client proposal */
++	struct sshbuf *empty = sshbuf_new();
++	int r;
++
++	/* Initialise GSSAPI */
++
++	/* If we're rekeying, privsep means that some of the private structures
++	 * in the GSSAPI code are no longer available. This kludges them back
++	 * into life
++	 */
++	if (!ssh_gssapi_oid_table_ok())
++		if ((mechs = ssh_gssapi_server_mechanisms()))
++			free(mechs);
++
++	debug2("%s: Identifying %s", __func__, kex->name);
++	oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
++	if (oid == GSS_C_NO_OID)
++	   fatal("Unknown gssapi mechanism");
++
++	debug2("%s: Acquiring credentials", __func__);
++
++	if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
++		fatal("Unable to acquire credentials for the server");
++
++	/* 5. S generates an ephemeral key pair (do the allocations early) */
++	debug("Doing group exchange");
++	ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ);
++	/* store client proposal to provide valid signature */
++	if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 ||
++	    (r = sshpkt_get_u32(ssh, &nbits)) != 0 ||
++	    (r = sshpkt_get_u32(ssh, &cmax)) != 0 ||
++	    (r = sshpkt_get_end(ssh)) != 0)
++		fatal("sshpkt failed: %s", ssh_err(r));
++	kex->nbits = nbits;
++	kex->min = cmin;
++	kex->max = cmax;
++	min = MAX(DH_GRP_MIN, cmin);
++	max = MIN(DH_GRP_MAX, cmax);
++	nbits = MAXIMUM(DH_GRP_MIN, nbits);
++	nbits = MINIMUM(DH_GRP_MAX, nbits);
++	if (max < min || nbits < min || max < nbits)
++		fatal("GSS_GEX, bad parameters: %d !< %d !< %d",
++		    min, nbits, max);
++	kex->dh = PRIVSEP(choose_dh(min, nbits, max));
++	if (kex->dh == NULL) {
++		sshpkt_disconnect(ssh, "Protocol error: no matching group found");
++		fatal("Protocol error: no matching group found");
++	}
++
++	DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++	if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 ||
++	    (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
++	    (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
++	    (r = sshpkt_send(ssh)) != 0)
++		fatal("sshpkt failed: %s", ssh_err(r));
++
++	if ((r = ssh_packet_write_wait(ssh)) != 0)
++		fatal("ssh_packet_write_wait: %s", ssh_err(r));
++
++	/* Compute our exchange value in parallel with the client */
++	if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
++		goto out;
++
++	do {
++		debug("Wait SSH2_MSG_GSSAPI_INIT");
++		type = ssh_packet_read(ssh);
++		switch(type) {
++		case SSH2_MSG_KEXGSS_INIT:
++			if (dh_client_pub != NULL)
++				fatal("Received KEXGSS_INIT after initialising");
++			if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++			        &recv_tok)) != 0 ||
++			    (r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 ||
++			    (r = sshpkt_get_end(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++
++			/* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
++			break;
++		case SSH2_MSG_KEXGSS_CONTINUE:
++			if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
++			        &recv_tok)) != 0 ||
++			    (r = sshpkt_get_end(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++			break;
++		default:
++			sshpkt_disconnect(ssh,
++			    "Protocol error: didn't expect packet type %d",
++			    type);
++		}
++
++		maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
++		    &send_tok, &ret_flags));
++
++		gss_release_buffer(&min_status, &recv_tok);
++
++		if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
++			fatal("Zero length token output when incomplete");
++
++		if (dh_client_pub == NULL)
++			fatal("No client public key");
++
++		if (maj_status & GSS_S_CONTINUE_NEEDED) {
++			debug("Sending GSSAPI_CONTINUE");
++			if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++			    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++			    (r = sshpkt_send(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++			gss_release_buffer(&min_status, &send_tok);
++		}
++	} while (maj_status & GSS_S_CONTINUE_NEEDED);
++
++	if (GSS_ERROR(maj_status)) {
++		if (send_tok.length > 0) {
++			if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
++			    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
++			    (r = sshpkt_send(ssh)) != 0)
++				fatal("sshpkt failed: %s", ssh_err(r));
++		}
++		fatal("accept_ctx died");
++	}
++
++	if (!(ret_flags & GSS_C_MUTUAL_FLAG))
++		fatal("Mutual Authentication flag wasn't set");
++
++	if (!(ret_flags & GSS_C_INTEG_FLAG))
++		fatal("Integrity flag wasn't set");
++
++	/* calculate shared secret */
++	if ((shared_secret = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++	if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0)
++		goto out;
++
++	DH_get0_key(kex->dh, &pub_key, NULL);
++	DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
++	hashlen = sizeof(hash);
++	if ((r = kexgex_hash(
++	    kex->hash_alg,
++	    kex->client_version,
++	    kex->server_version,
++	    kex->peer,
++	    kex->my,
++	    empty,
++	    cmin, nbits, cmax,
++	    dh_p, dh_g,
++	    dh_client_pub,
++	    pub_key,
++	    sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
++	    hash, &hashlen)) != 0)
++		fatal("kexgex_hash failed: %s", ssh_err(r));
++
++	gssbuf.value = hash;
++	gssbuf.length = hashlen;
++
++	if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
++		fatal("Couldn't get MIC");
++
++	if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
++	    (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 ||
++	    (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
++		fatal("sshpkt failed: %s", ssh_err(r));
++
++	if (send_tok.length != 0) {
++		if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
++		    (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
++			fatal("sshpkt failed: %s", ssh_err(r));
++	} else {
++		if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
++			fatal("sshpkt failed: %s", ssh_err(r));
++	}
++	if ((r = sshpkt_send(ssh)) != 0)
++		fatal("sshpkt failed: %s", ssh_err(r));
++
++	gss_release_buffer(&min_status, &send_tok);
++	gss_release_buffer(&min_status, &msg_tok);
++
++	if (gss_kex_context == NULL)
++		gss_kex_context = ctxt;
++	else
++		ssh_gssapi_delete_ctx(&ctxt);
++
++	/* Finally derive the keys and send them */
++	if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
++		r = kex_send_newkeys(ssh);
++
++	/* If this was a rekey, then save out any delegated credentials we
++	 * just exchanged.  */
++	if (options.gss_store_rekey)
++		ssh_gssapi_rekey_creds();
++out:
++	sshbuf_free(empty);
++	explicit_bzero(hash, sizeof(hash));
++	DH_free(kex->dh);
++	kex->dh = NULL;
++	BN_clear_free(dh_client_pub);
++	sshbuf_free(shared_secret);
++	return r;
++}
++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
+diff --git a/monitor.c b/monitor.c
+index 2ce89fe9..ebf76c7f 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -148,6 +148,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *);
+ int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *);
++int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *);
+ #endif
+ 
+ #ifdef SSH_AUDIT_EVENTS
+@@ -220,11 +222,18 @@ struct mon_table mon_dispatch_proto20[] = {
+     {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
+     {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok},
+     {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic},
++    {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign},
+ #endif
+     {0, 0, NULL}
+ };
+ 
+ struct mon_table mon_dispatch_postauth20[] = {
++#ifdef GSSAPI
++    {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx},
++    {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
++    {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign},
++    {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds},
++#endif
+ #ifdef WITH_OPENSSL
+     {MONITOR_REQ_MODULI, 0, mm_answer_moduli},
+ #endif
+@@ -293,6 +302,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
+ 	/* Permit requests for moduli and signatures */
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
++#ifdef GSSAPI
++	/* and for the GSSAPI key exchange */
++	monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++#endif
+ 
+ 	/* The first few requests do not require asynchronous access */
+ 	while (!authenticated) {
+@@ -406,6 +419,10 @@ monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor)
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1);
++#ifdef GSSAPI
++	/* and for the GSSAPI key exchange */
++	monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
++#endif
+ 
+ 	if (auth_opts->permit_pty_flag) {
+ 		monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
+@@ -1713,6 +1730,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ # ifdef OPENSSL_HAS_ECC
+ 		kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
+ # endif
++# ifdef GSSAPI
++		if (options.gss_keyex) {
++			kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
++			kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server;
++			kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server;
++			kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server;
++			kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server;
++			kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server;
++			kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
++		}
++# endif
+ #endif /* WITH_OPENSSL */
+ 		kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 		kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server;
+@@ -1806,8 +1834,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ 	u_char *p;
+ 	int r;
+ 
+-	if (!options.gss_authentication)
+-		fatal("%s: GSSAPI authentication not enabled", __func__);
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
+ 
+ 	if ((r = sshbuf_get_string(m, &p, &len)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+@@ -1839,8 +1867,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ 	OM_uint32 flags = 0; /* GSI needs this */
+ 	int r;
+ 
+-	if (!options.gss_authentication)
+-		fatal("%s: GSSAPI authentication not enabled", __func__);
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
+ 
+ 	if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0)
+ 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+@@ -1860,6 +1888,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+ 		monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0);
+ 		monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1);
+ 		monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1);
++		monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1);
+ 	}
+ 	return (0);
+ }
+@@ -1871,8 +1900,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+ 	OM_uint32 ret;
+ 	int r;
+ 
+-	if (!options.gss_authentication)
+-		fatal("%s: GSSAPI authentication not enabled", __func__);
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
+ 
+ 	if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 ||
+ 	    (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0)
+@@ -1898,13 +1927,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+ int
+ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ {
+-	int r, authenticated;
++	int r, authenticated, kex;
+ 	const char *displayname;
+ 
+-	if (!options.gss_authentication)
+-		fatal("%s: GSSAPI authentication not enabled", __func__);
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
+ 
+-	authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user);
++	if ((r = sshbuf_get_u32(m, &kex)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	authenticated = authctxt->valid &&
++	    ssh_gssapi_userok(authctxt->user, authctxt->pw, kex);
+ 
+ 	sshbuf_reset(m);
+ 	if ((r = sshbuf_put_u32(m, authenticated)) != 0)
+@@ -1913,7 +1946,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ 	debug3("%s: sending result %d", __func__, authenticated);
+ 	mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m);
+ 
+-	auth_method = "gssapi-with-mic";
++	if (kex) {
++		auth_method = "gssapi-keyex";
++	} else {
++		auth_method = "gssapi-with-mic";
++	}
+ 
+ 	if ((displayname = ssh_gssapi_displayname()) != NULL)
+ 		auth2_record_info(authctxt, "%s", displayname);
+@@ -1921,5 +1958,85 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+ 	/* Monitor loop will terminate if authenticated */
+ 	return (authenticated);
+ }
++
++int
++mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m)
++{
++	gss_buffer_desc data;
++	gss_buffer_desc hash = GSS_C_EMPTY_BUFFER;
++	OM_uint32 major, minor;
++	size_t len;
++	u_char *p = NULL;
++	int r;
++
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
++
++	if ((r = sshbuf_get_string(m, &p, &len)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++	data.value = p;
++	data.length = len;
++	/* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */
++	if (data.length != 20 && data.length != 32 && data.length != 64)
++		fatal("%s: data length incorrect: %d", __func__,
++		    (int) data.length);
++
++	/* Save the session ID on the first time around */
++	if (session_id2_len == 0) {
++		session_id2_len = data.length;
++		session_id2 = xmalloc(session_id2_len);
++		memcpy(session_id2, data.value, session_id2_len);
++	}
++	major = ssh_gssapi_sign(gsscontext, &data, &hash);
++
++	free(data.value);
++
++	sshbuf_reset(m);
++
++	if ((r = sshbuf_put_u32(m, major)) != 0 ||
++	    (r = sshbuf_put_string(m, hash.value, hash.length)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(socket, MONITOR_ANS_GSSSIGN, m);
++
++	gss_release_buffer(&minor, &hash);
++
++	/* Turn on getpwnam permissions */
++	monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1);
++
++	/* And credential updating, for when rekeying */
++	monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1);
++
++	return (0);
++}
++
++int
++mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
++	ssh_gssapi_ccache store;
++	int r, ok;
++
++	if (!options.gss_authentication && !options.gss_keyex)
++		fatal("%s: GSSAPI not enabled", __func__);
++
++	if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 ||
++	    (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 ||
++	    (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	ok = ssh_gssapi_update_creds(&store);
++
++	free(store.filename);
++	free(store.envvar);
++	free(store.envval);
++
++	sshbuf_reset(m);
++	if ((r = sshbuf_put_u32(m, ok)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m);
++
++	return(0);
++}
++
+ #endif /* GSSAPI */
+ 
+diff --git a/monitor.h b/monitor.h
+index 683e5e07..2b1a2d59 100644
+--- a/monitor.h
++++ b/monitor.h
+@@ -63,6 +63,8 @@ enum monitor_reqtype {
+ 	MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111,
+ 	MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113,
+ 
++	MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
++	MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
+ };
+ 
+ struct ssh;
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index 001a8fa1..6edb509a 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -993,13 +993,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
+ }
+ 
+ int
+-mm_ssh_gssapi_userok(char *user)
++mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ 	struct sshbuf *m;
+ 	int r, authenticated = 0;
+ 
+ 	if ((m = sshbuf_new()) == NULL)
+ 		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_u32(m, kex)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+ 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m);
+ 	mm_request_receive_expect(pmonitor->m_recvfd,
+@@ -1012,4 +1014,57 @@ mm_ssh_gssapi_userok(char *user)
+ 	debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not ");
+ 	return (authenticated);
+ }
++
++OM_uint32
++mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash)
++{
++	struct sshbuf *m;
++	OM_uint32 major;
++	int r;
++
++	if ((m = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++	if ((r = sshbuf_put_string(m, data->value, data->length)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m);
++
++	if ((r = sshbuf_get_u32(m, &major)) != 0 ||
++	    (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	sshbuf_free(m);
++
++	return (major);
++}
++
++int
++mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store)
++{
++	struct sshbuf *m;
++	int r, ok;
++
++	if ((m = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++
++	if ((r = sshbuf_put_cstring(m,
++	    store->filename ? store->filename : "")) != 0 ||
++	    (r = sshbuf_put_cstring(m,
++	    store->envvar ? store->envvar : "")) != 0 ||
++	    (r = sshbuf_put_cstring(m,
++	    store->envval ? store->envval : "")) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m);
++	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m);
++
++	if ((r = sshbuf_get_u32(m, &ok)) != 0)
++		fatal("%s: buffer error: %s", __func__, ssh_err(r));
++
++	sshbuf_free(m);
++
++	return (ok);
++}
++
+ #endif /* GSSAPI */
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index 23ab096a..485590c1 100644
+--- a/monitor_wrap.h
++++ b/monitor_wrap.h
+@@ -64,8 +64,10 @@ int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t,
+ OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID);
+ OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *,
+    gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *);
+-int mm_ssh_gssapi_userok(char *user);
++int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex);
+ OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
++OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
++int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *);
+ #endif
+ 
+ #ifdef USE_PAM
+diff --git a/readconf.c b/readconf.c
+index f3cac6b3..da8022dd 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -67,6 +67,7 @@
+ #include "uidswap.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "ssh-gss.h"
+ 
+ /* Format of the configuration file:
+ 
+@@ -160,6 +161,8 @@ typedef enum {
+ 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
+ 	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
+ 	oAddressFamily, oGssAuthentication, oGssDelegateCreds,
++	oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey,
++	oGssServerIdentity, oGssKexAlgorithms,
+ 	oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
+ 	oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist,
+ 	oHashKnownHosts,
+@@ -204,10 +207,22 @@ static struct {
+ 	/* Sometimes-unsupported options */
+ #if defined(GSSAPI)
+ 	{ "gssapiauthentication", oGssAuthentication },
++	{ "gssapikeyexchange", oGssKeyEx },
+ 	{ "gssapidelegatecredentials", oGssDelegateCreds },
++	{ "gssapitrustdns", oGssTrustDns },
++	{ "gssapiclientidentity", oGssClientIdentity },
++	{ "gssapiserveridentity", oGssServerIdentity },
++	{ "gssapirenewalforcesrekey", oGssRenewalRekey },
++	{ "gssapikexalgorithms", oGssKexAlgorithms },
+ # else
+ 	{ "gssapiauthentication", oUnsupported },
++	{ "gssapikeyexchange", oUnsupported },
+ 	{ "gssapidelegatecredentials", oUnsupported },
++	{ "gssapitrustdns", oUnsupported },
++	{ "gssapiclientidentity", oUnsupported },
++	{ "gssapiserveridentity", oUnsupported },
++	{ "gssapirenewalforcesrekey", oUnsupported },
++	{ "gssapikexalgorithms", oUnsupported },
+ #endif
+ #ifdef ENABLE_PKCS11
+ 	{ "pkcs11provider", oPKCS11Provider },
+@@ -1029,10 +1044,42 @@ parse_time:
+ 		intptr = &options->gss_authentication;
+ 		goto parse_flag;
+ 
++	case oGssKeyEx:
++		intptr = &options->gss_keyex;
++		goto parse_flag;
++
+ 	case oGssDelegateCreds:
+ 		intptr = &options->gss_deleg_creds;
+ 		goto parse_flag;
+ 
++	case oGssTrustDns:
++		intptr = &options->gss_trust_dns;
++		goto parse_flag;
++
++	case oGssClientIdentity:
++		charptr = &options->gss_client_identity;
++		goto parse_string;
++
++	case oGssServerIdentity:
++		charptr = &options->gss_server_identity;
++		goto parse_string;
++
++	case oGssRenewalRekey:
++		intptr = &options->gss_renewal_rekey;
++		goto parse_flag;
++
++	case oGssKexAlgorithms:
++		arg = strdelim(&s);
++		if (!arg || *arg == '\0')
++			fatal("%.200s line %d: Missing argument.",
++			    filename, linenum);
++		if (!kex_gss_names_valid(arg))
++			fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.",
++			    filename, linenum, arg ? arg : "<NONE>");
++		if (*activep && options->gss_kex_algorithms == NULL)
++			options->gss_kex_algorithms = xstrdup(arg);
++		break;
++
+ 	case oBatchMode:
+ 		intptr = &options->batch_mode;
+ 		goto parse_flag;
+@@ -1911,7 +1958,13 @@ initialize_options(Options * options)
+ 	options->pubkey_authentication = -1;
+ 	options->challenge_response_authentication = -1;
+ 	options->gss_authentication = -1;
++	options->gss_keyex = -1;
+ 	options->gss_deleg_creds = -1;
++	options->gss_trust_dns = -1;
++	options->gss_renewal_rekey = -1;
++	options->gss_client_identity = NULL;
++	options->gss_server_identity = NULL;
++	options->gss_kex_algorithms = NULL;
+ 	options->password_authentication = -1;
+ 	options->kbd_interactive_authentication = -1;
+ 	options->kbd_interactive_devices = NULL;
+@@ -2059,8 +2112,18 @@ fill_default_options(Options * options)
+ 		options->challenge_response_authentication = 1;
+ 	if (options->gss_authentication == -1)
+ 		options->gss_authentication = 0;
++	if (options->gss_keyex == -1)
++		options->gss_keyex = 0;
+ 	if (options->gss_deleg_creds == -1)
+ 		options->gss_deleg_creds = 0;
++	if (options->gss_trust_dns == -1)
++		options->gss_trust_dns = 0;
++	if (options->gss_renewal_rekey == -1)
++		options->gss_renewal_rekey = 0;
++#ifdef GSSAPI
++	if (options->gss_kex_algorithms == NULL)
++		options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
++#endif
+ 	if (options->password_authentication == -1)
+ 		options->password_authentication = 1;
+ 	if (options->kbd_interactive_authentication == -1)
+@@ -2702,7 +2765,14 @@ dump_client_config(Options *o, const char *host)
+ 	dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports);
+ #ifdef GSSAPI
+ 	dump_cfg_fmtint(oGssAuthentication, o->gss_authentication);
++	dump_cfg_fmtint(oGssKeyEx, o->gss_keyex);
+ 	dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds);
++	dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns);
++	dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey);
++	dump_cfg_string(oGssClientIdentity, o->gss_client_identity);
++	dump_cfg_string(oGssServerIdentity, o->gss_server_identity);
++	dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ?
++	    o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX);
+ #endif /* GSSAPI */
+ 	dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts);
+ 	dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication);
+diff --git a/readconf.h b/readconf.h
+index feedb3d2..a8a8870d 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -41,7 +41,13 @@ typedef struct {
+ 	int     challenge_response_authentication;
+ 					/* Try S/Key or TIS, authentication. */
+ 	int     gss_authentication;	/* Try GSS authentication */
++	int     gss_keyex;		/* Try GSS key exchange */
+ 	int     gss_deleg_creds;	/* Delegate GSS credentials */
++	int	gss_trust_dns;		/* Trust DNS for GSS canonicalization */
++	int	gss_renewal_rekey;	/* Credential renewal forces rekey */
++	char    *gss_client_identity;   /* Principal to initiate GSSAPI with */
++	char    *gss_server_identity;   /* GSSAPI target principal */
++	char    *gss_kex_algorithms;	/* GSSAPI kex methods to be offered by client. */
+ 	int     password_authentication;	/* Try password
+ 						 * authentication. */
+ 	int     kbd_interactive_authentication; /* Try keyboard-interactive auth. */
+diff --git a/servconf.c b/servconf.c
+index 70f5f73f..191575a1 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -69,6 +69,7 @@
+ #include "auth.h"
+ #include "myproposal.h"
+ #include "digest.h"
++#include "ssh-gss.h"
+ 
+ static void add_listen_addr(ServerOptions *, const char *,
+     const char *, int);
+@@ -133,8 +134,11 @@ initialize_server_options(ServerOptions *options)
+ 	options->kerberos_ticket_cleanup = -1;
+ 	options->kerberos_get_afs_token = -1;
+ 	options->gss_authentication=-1;
++	options->gss_keyex = -1;
+ 	options->gss_cleanup_creds = -1;
+ 	options->gss_strict_acceptor = -1;
++	options->gss_store_rekey = -1;
++	options->gss_kex_algorithms = NULL;
+ 	options->password_authentication = -1;
+ 	options->kbd_interactive_authentication = -1;
+ 	options->challenge_response_authentication = -1;
+@@ -375,10 +379,18 @@ fill_default_server_options(ServerOptions *options)
+ 		options->kerberos_get_afs_token = 0;
+ 	if (options->gss_authentication == -1)
+ 		options->gss_authentication = 0;
++	if (options->gss_keyex == -1)
++		options->gss_keyex = 0;
+ 	if (options->gss_cleanup_creds == -1)
+ 		options->gss_cleanup_creds = 1;
+ 	if (options->gss_strict_acceptor == -1)
+ 		options->gss_strict_acceptor = 1;
++	if (options->gss_store_rekey == -1)
++		options->gss_store_rekey = 0;
++#ifdef GSSAPI
++	if (options->gss_kex_algorithms == NULL)
++		options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
++#endif
+ 	if (options->password_authentication == -1)
+ 		options->password_authentication = 1;
+ 	if (options->kbd_interactive_authentication == -1)
+@@ -531,6 +543,7 @@ typedef enum {
+ 	sHostKeyAlgorithms,
+ 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
+ 	sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
++	sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
+ 	sAcceptEnv, sSetEnv, sPermitTunnel,
+ 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
+ 	sUsePrivilegeSeparation, sAllowAgentForwarding,
+@@ -607,12 +620,22 @@ static struct {
+ #ifdef GSSAPI
+ 	{ "gssapiauthentication", sGssAuthentication, SSHCFG_ALL },
+ 	{ "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
++	{ "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL },
+ 	{ "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
++	{ "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
++	{ "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
++	{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
+ #else
+ 	{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
+ 	{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
++	{ "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
+ 	{ "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
++	{ "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
++	{ "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
++	{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
+ #endif
++	{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
++	{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
+ 	{ "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL },
+ 	{ "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
+ 	{ "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL },
+@@ -1548,6 +1571,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 		intptr = &options->gss_authentication;
+ 		goto parse_flag;
+ 
++	case sGssKeyEx:
++		intptr = &options->gss_keyex;
++		goto parse_flag;
++
+ 	case sGssCleanupCreds:
+ 		intptr = &options->gss_cleanup_creds;
+ 		goto parse_flag;
+@@ -1556,6 +1583,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 		intptr = &options->gss_strict_acceptor;
+ 		goto parse_flag;
+ 
++	case sGssStoreRekey:
++		intptr = &options->gss_store_rekey;
++		goto parse_flag;
++
++	case sGssKexAlgorithms:
++		arg = strdelim(&cp);
++		if (!arg || *arg == '\0')
++			fatal("%.200s line %d: Missing argument.",
++			    filename, linenum);
++		if (!kex_gss_names_valid(arg))
++			fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.",
++			    filename, linenum, arg ? arg : "<NONE>");
++		if (*activep && options->gss_kex_algorithms == NULL)
++			options->gss_kex_algorithms = xstrdup(arg);
++		break;
++
+ 	case sPasswordAuthentication:
+ 		intptr = &options->password_authentication;
+ 		goto parse_flag;
+@@ -2777,6 +2820,10 @@ dump_config(ServerOptions *o)
+ #ifdef GSSAPI
+ 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ 	dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
++	dump_cfg_fmtint(sGssKeyEx, o->gss_keyex);
++	dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
++	dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
++	dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
+ #endif
+ 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index 4202a2d0..3f47ea25 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -132,8 +132,11 @@ typedef struct {
+ 	int     kerberos_get_afs_token;		/* If true, try to get AFS token if
+ 						 * authenticated with Kerberos. */
+ 	int     gss_authentication;	/* If true, permit GSSAPI authentication */
++	int     gss_keyex;		/* If true, permit GSSAPI key exchange */
+ 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
+ 	int     gss_strict_acceptor;	/* If true, restrict the GSSAPI acceptor name */
++	int 	gss_store_rekey;
++	char   *gss_kex_algorithms;	/* GSSAPI kex methods to be offered by client. */
+ 	int     password_authentication;	/* If true, permit password
+ 						 * authentication. */
+ 	int     kbd_interactive_authentication;	/* If true, permit */
+diff --git a/session.c b/session.c
+index 8c0e54f7..06a33442 100644
+--- a/session.c
++++ b/session.c
+@@ -2678,13 +2678,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+ 
+ #ifdef KRB5
+ 	if (options.kerberos_ticket_cleanup &&
+-	    authctxt->krb5_ctx)
++	    authctxt->krb5_ctx) {
++		temporarily_use_uid(authctxt->pw);
+ 		krb5_cleanup_proc(authctxt);
++		restore_uid();
++	}
+ #endif
+ 
+ #ifdef GSSAPI
+-	if (options.gss_cleanup_creds)
++	if (options.gss_cleanup_creds) {
++		temporarily_use_uid(authctxt->pw);
+ 		ssh_gssapi_cleanup_creds();
++		restore_uid();
++	}
+ #endif
+ 
+ 	/* remove agent socket */
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 36180d07..70dd3665 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -1,6 +1,6 @@
+ /* $OpenBSD: ssh-gss.h,v 1.14 2018/07/10 09:13:30 djm Exp $ */
+ /*
+- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions
+@@ -61,10 +61,34 @@
+ 
+ #define SSH_GSS_OIDTYPE 0x06
+ 
++#define SSH2_MSG_KEXGSS_INIT                            30
++#define SSH2_MSG_KEXGSS_CONTINUE                        31
++#define SSH2_MSG_KEXGSS_COMPLETE                        32
++#define SSH2_MSG_KEXGSS_HOSTKEY                         33
++#define SSH2_MSG_KEXGSS_ERROR                           34
++#define SSH2_MSG_KEXGSS_GROUPREQ			40
++#define SSH2_MSG_KEXGSS_GROUP				41
++#define KEX_GSS_GRP1_SHA1_ID				"gss-group1-sha1-"
++#define KEX_GSS_GRP14_SHA1_ID				"gss-group14-sha1-"
++#define KEX_GSS_GRP14_SHA256_ID			"gss-group14-sha256-"
++#define KEX_GSS_GRP16_SHA512_ID			"gss-group16-sha512-"
++#define KEX_GSS_GEX_SHA1_ID				"gss-gex-sha1-"
++#define KEX_GSS_NISTP256_SHA256_ID			"gss-nistp256-sha256-"
++#define KEX_GSS_C25519_SHA256_ID			"gss-curve25519-sha256-"
++
++#define        GSS_KEX_DEFAULT_KEX \
++	KEX_GSS_GRP14_SHA256_ID "," \
++	KEX_GSS_GRP16_SHA512_ID	"," \
++	KEX_GSS_NISTP256_SHA256_ID "," \
++	KEX_GSS_C25519_SHA256_ID "," \
++	KEX_GSS_GRP14_SHA1_ID "," \
++	KEX_GSS_GEX_SHA1_ID
++
+ typedef struct {
+ 	char *filename;
+ 	char *envvar;
+ 	char *envval;
++	struct passwd *owner;
+ 	void *data;
+ } ssh_gssapi_ccache;
+ 
+@@ -72,8 +92,11 @@ typedef struct {
+ 	gss_buffer_desc displayname;
+ 	gss_buffer_desc exportedname;
+ 	gss_cred_id_t creds;
++	gss_name_t name;
+ 	struct ssh_gssapi_mech_struct *mech;
+ 	ssh_gssapi_ccache store;
++	int used;
++	int updated;
+ } ssh_gssapi_client;
+ 
+ typedef struct ssh_gssapi_mech_struct {
+@@ -84,6 +107,7 @@ typedef struct ssh_gssapi_mech_struct {
+ 	int (*userok) (ssh_gssapi_client *, char *);
+ 	int (*localname) (ssh_gssapi_client *, char **);
+ 	void (*storecreds) (ssh_gssapi_client *);
++	int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *);
+ } ssh_gssapi_mech;
+ 
+ typedef struct {
+@@ -94,10 +118,11 @@ typedef struct {
+ 	gss_OID		oid; /* client */
+ 	gss_cred_id_t	creds; /* server */
+ 	gss_name_t	client; /* server */
+-	gss_cred_id_t	client_creds; /* server */
++	gss_cred_id_t	client_creds; /* both */
+ } Gssctxt;
+ 
+ extern ssh_gssapi_mech *supported_mechs[];
++extern Gssctxt *gss_kex_context;
+ 
+ int  ssh_gssapi_check_oid(Gssctxt *, void *, size_t);
+ void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t);
+@@ -109,6 +134,7 @@ OM_uint32 ssh_gssapi_test_oid_supported(OM_uint32 *, gss_OID, int *);
+ 
+ struct sshbuf;
+ int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *);
++int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *);
+ 
+ OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *);
+ OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int,
+@@ -123,17 +149,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **);
+ OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_buildmic(struct sshbuf *, const char *,
+     const char *, const char *);
+-int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *);
++int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *);
++OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *);
++int ssh_gssapi_credentials_updated(Gssctxt *);
+ 
+ /* In the server */
++typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *,
++    const char *);
++char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *);
++char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *,
++    const char *, const char *);
++gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int);
++int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *,
++    const char *);
+ OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID);
+-int ssh_gssapi_userok(char *name);
++int ssh_gssapi_userok(char *name, struct passwd *, int kex);
+ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_do_child(char ***, u_int *);
+ void ssh_gssapi_cleanup_creds(void);
+ void ssh_gssapi_storecreds(void);
+ const char *ssh_gssapi_displayname(void);
+ 
++char *ssh_gssapi_server_mechanisms(void);
++int ssh_gssapi_oid_table_ok(void);
++
++int ssh_gssapi_update_creds(ssh_gssapi_ccache *store);
++void ssh_gssapi_rekey_creds(void);
++
+ #endif /* GSSAPI */
+ 
+ #endif /* _SSH_GSS_H */
+diff --git a/ssh.1 b/ssh.1
+index 60de6087..db5c65bc 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -503,7 +503,13 @@ For full details of the options listed below, and their possible values, see
+ .It GatewayPorts
+ .It GlobalKnownHostsFile
+ .It GSSAPIAuthentication
++.It GSSAPIKeyExchange
++.It GSSAPIClientIdentity
+ .It GSSAPIDelegateCredentials
++.It GSSAPIKexAlgorithms
++.It GSSAPIRenewalForcesRekey
++.It GSSAPIServerIdentity
++.It GSSAPITrustDns
+ .It HashKnownHosts
+ .It Host
+ .It HostbasedAuthentication
+@@ -579,6 +585,8 @@ flag),
+ (supported message integrity codes),
+ .Ar kex
+ (key exchange algorithms),
++.Ar kex-gss
++(GSSAPI key exchange algorithms),
+ .Ar key
+ (key types),
+ .Ar key-cert
+diff --git a/ssh.c b/ssh.c
+index 15aee569..110cf9c1 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -747,6 +747,8 @@ main(int ac, char **av)
+ 			else if (strcmp(optarg, "kex") == 0 ||
+ 			    strcasecmp(optarg, "KexAlgorithms") == 0)
+ 				cp = kex_alg_list('\n');
++			else if (strcmp(optarg, "kex-gss") == 0)
++				cp = kex_gss_alg_list('\n');
+ 			else if (strcmp(optarg, "key") == 0)
+ 				cp = sshkey_alg_list(0, 0, 0, '\n');
+ 			else if (strcmp(optarg, "key-cert") == 0)
+@@ -772,8 +774,8 @@ main(int ac, char **av)
+ 			} else if (strcmp(optarg, "help") == 0) {
+ 				cp = xstrdup(
+ 				    "cipher\ncipher-auth\ncompression\nkex\n"
+-				    "key\nkey-cert\nkey-plain\nkey-sig\nmac\n"
+-				    "protocol-version\nsig");
++				    "kex-gss\nkey\nkey-cert\nkey-plain\n"
++				    "key-sig\nmac\nprotocol-version\nsig");
+ 			}
+ 			if (cp == NULL)
+ 				fatal("Unsupported query \"%s\"", optarg);
+diff --git a/ssh_config b/ssh_config
+index 5e8ef548..1ff999b6 100644
+--- a/ssh_config
++++ b/ssh_config
+@@ -24,6 +24,8 @@
+ #   HostbasedAuthentication no
+ #   GSSAPIAuthentication no
+ #   GSSAPIDelegateCredentials no
++#   GSSAPIKeyExchange no
++#   GSSAPITrustDNS no
+ #   BatchMode no
+ #   CheckHostIP yes
+ #   AddressFamily any
+diff --git a/ssh_config.5 b/ssh_config.5
+index 06a32d31..3f490697 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -766,10 +766,68 @@ The default is
+ Specifies whether user authentication based on GSSAPI is allowed.
+ The default is
+ .Cm no .
++.It Cm GSSAPIClientIdentity
++If set, specifies the GSSAPI client identity that ssh should use when
++connecting to the server. The default is unset, which means that the default
++identity will be used.
+ .It Cm GSSAPIDelegateCredentials
+ Forward (delegate) credentials to the server.
+ The default is
+ .Cm no .
++.It Cm GSSAPIKeyExchange
++Specifies whether key exchange based on GSSAPI may be used. When using
++GSSAPI key exchange the server need not have a host key.
++The default is
++.Dq no .
++.It Cm GSSAPIRenewalForcesRekey
++If set to
++.Dq yes
++then renewal of the client's GSSAPI credentials will force the rekeying of the
++ssh connection. With a compatible server, this will delegate the renewed
++credentials to a session on the server.
++.Pp
++Checks are made to ensure that credentials are only propagated when the new
++credentials match the old ones on the originating client and where the
++receiving server still has the old set in its cache.
++.Pp
++The default is
++.Dq no .
++.Pp
++For this to work
++.Cm GSSAPIKeyExchange
++needs to be enabled in the server and also used by the client.
++.It Cm GSSAPIServerIdentity
++If set, specifies the GSSAPI server identity that ssh should expect when
++connecting to the server. The default is unset, which means that the
++expected GSSAPI server identity will be determined from the target
++hostname.
++.It Cm GSSAPITrustDns
++Set to
++.Dq yes
++to indicate that the DNS is trusted to securely canonicalize
++the name of the host being connected to. If
++.Dq no ,
++the hostname entered on the
++command line will be passed untouched to the GSSAPI library.
++The default is
++.Dq no .
++.It Cm GSSAPIKexAlgorithms
++The list of key exchange algorithms that are offered for GSSAPI
++key exchange. Possible values are
++.Bd -literal -offset 3n
++gss-gex-sha1-,
++gss-group1-sha1-,
++gss-group14-sha1-,
++gss-group14-sha256-,
++gss-group16-sha512-,
++gss-nistp256-sha256-,
++gss-curve25519-sha256-
++.Ed
++.Pp
++The default is
++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,
++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- .
++This option only applies to connections using GSSAPI.
+ .It Cm HashKnownHosts
+ Indicates that
+ .Xr ssh 1
+diff --git a/sshconnect2.c b/sshconnect2.c
+index af00fb30..03bc87eb 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -80,8 +80,6 @@
+ #endif
+ 
+ /* import */
+-extern char *client_version_string;
+-extern char *server_version_string;
+ extern Options options;
+ 
+ /*
+@@ -163,6 +161,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ 	char *s, *all_key;
+ 	int r, use_known_hosts_order = 0;
+ 
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++	char *orig = NULL, *gss = NULL;
++	char *gss_host = NULL;
++#endif
++
+ 	xxx_host = host;
+ 	xxx_hostaddr = hostaddr;
+ 
+@@ -206,6 +209,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ 		    compat_pkalg_proposal(options.hostkeyalgorithms);
+ 	}
+ 
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++	if (options.gss_keyex) {
++		/* Add the GSSAPI mechanisms currently supported on this
++		 * client to the key exchange algorithm proposal */
++		orig = myproposal[PROPOSAL_KEX_ALGS];
++
++		if (options.gss_server_identity) {
++			gss_host = xstrdup(options.gss_server_identity);
++		} else if (options.gss_trust_dns) {
++			gss_host = remote_hostname(ssh);
++			/* Fall back to specified host if we are using proxy command
++			 * and can not use DNS on that socket */
++			if (strcmp(gss_host, "UNKNOWN") == 0) {
++				free(gss_host);
++				gss_host = xstrdup(host);
++			}
++		} else {
++			gss_host = xstrdup(host);
++		}
++
++		gss = ssh_gssapi_client_mechanisms(gss_host,
++		    options.gss_client_identity, options.gss_kex_algorithms);
++		if (gss) {
++			debug("Offering GSSAPI proposal: %s", gss);
++			xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++			    "%s,%s", gss, orig);
++
++			/* If we've got GSSAPI algorithms, then we also support the
++			 * 'null' hostkey, as a last resort */
++			orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
++			xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],
++			    "%s,null", orig);
++		}
++	}
++#endif
++
+ 	if (options.rekey_limit || options.rekey_interval)
+ 		ssh_packet_set_rekey_limits(ssh, options.rekey_limit,
+ 		    options.rekey_interval);
+@@ -224,16 +256,46 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port)
+ # ifdef OPENSSL_HAS_ECC
+ 	ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client;
+ # endif
+-#endif
++# ifdef GSSAPI
++	if (options.gss_keyex) {
++		ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client;
++		ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client;
++		ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client;
++		ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client;
++		ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client;
++		ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client;
++		ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client;
++	}
++# endif
++#endif /* WITH_OPENSSL */
+ 	ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ 	ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_client;
+ 	ssh->kex->verify_host_key=&verify_host_key_callback;
+ 
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++	if (options.gss_keyex) {
++		ssh->kex->gss_deleg_creds = options.gss_deleg_creds;
++		ssh->kex->gss_trust_dns = options.gss_trust_dns;
++		ssh->kex->gss_client = options.gss_client_identity;
++		ssh->kex->gss_host = gss_host;
++	}
++#endif
++
+ 	ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done);
+ 
+ 	/* remove ext-info from the KEX proposals for rekeying */
+ 	myproposal[PROPOSAL_KEX_ALGS] =
+ 	    compat_kex_proposal(options.kex_algorithms);
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++	/* repair myproposal after it was crumpled by the */
++	/* ext-info removal above */
++	if (gss) {
++		orig = myproposal[PROPOSAL_KEX_ALGS];
++		xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++		    "%s,%s", gss, orig);
++		free(gss);
++	}
++#endif
+ 	if ((r = kex_prop2buf(ssh->kex->my, myproposal)) != 0)
+ 		fatal("kex_prop2buf: %s", ssh_err(r));
+ 
+@@ -330,6 +392,7 @@ static int input_gssapi_response(int type, u_int32_t, struct ssh *);
+ static int input_gssapi_token(int type, u_int32_t, struct ssh *);
+ static int input_gssapi_error(int, u_int32_t, struct ssh *);
+ static int input_gssapi_errtok(int, u_int32_t, struct ssh *);
++static int userauth_gsskeyex(struct ssh *);
+ #endif
+ 
+ void	userauth(struct ssh *, char *);
+@@ -346,6 +409,11 @@ static char *authmethods_get(void);
+ 
+ Authmethod authmethods[] = {
+ #ifdef GSSAPI
++	{"gssapi-keyex",
++		userauth_gsskeyex,
++		NULL,
++		&options.gss_keyex,
++		NULL},
+ 	{"gssapi-with-mic",
+ 		userauth_gssapi,
+ 		userauth_gssapi_cleanup,
+@@ -716,12 +784,32 @@ userauth_gssapi(struct ssh *ssh)
+ 	OM_uint32 min;
+ 	int r, ok = 0;
+ 	gss_OID mech = NULL;
++	char *gss_host = NULL;
++
++	if (options.gss_server_identity) {
++		gss_host = xstrdup(options.gss_server_identity);
++	} else if (options.gss_trust_dns) {
++		gss_host = remote_hostname(ssh);
++		/* Fall back to specified host if we are using proxy command
++		 * and can not use DNS on that socket */
++		if (strcmp(gss_host, "UNKNOWN") == 0) {
++			free(gss_host);
++			gss_host = xstrdup(authctxt->host);
++		}
++	} else {
++		gss_host = xstrdup(authctxt->host);
++	}
+ 
+ 	/* Try one GSSAPI method at a time, rather than sending them all at
+ 	 * once. */
+ 
+ 	if (authctxt->gss_supported_mechs == NULL)
+-		gss_indicate_mechs(&min, &authctxt->gss_supported_mechs);
++		if (GSS_ERROR(gss_indicate_mechs(&min,
++		    &authctxt->gss_supported_mechs))) {
++			authctxt->gss_supported_mechs = NULL;
++			free(gss_host);
++			return 0;
++		}
+ 
+ 	/* Check to see whether the mechanism is usable before we offer it */
+ 	while (authctxt->mech_tried < authctxt->gss_supported_mechs->count &&
+@@ -730,13 +811,15 @@ userauth_gssapi(struct ssh *ssh)
+ 		    elements[authctxt->mech_tried];
+ 		/* My DER encoding requires length<128 */
+ 		if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt,
+-		    mech, authctxt->host)) {
++		    mech, gss_host, options.gss_client_identity)) {
+ 			ok = 1; /* Mechanism works */
+ 		} else {
+ 			authctxt->mech_tried++;
+ 		}
+ 	}
+ 
++	free(gss_host);
++
+ 	if (!ok || mech == NULL)
+ 		return 0;
+ 
+@@ -976,6 +1059,55 @@ input_gssapi_error(int type, u_int32_t plen, struct ssh *ssh)
+ 	free(lang);
+ 	return r;
+ }
++
++int
++userauth_gsskeyex(struct ssh *ssh)
++{
++	struct sshbuf *b = NULL;
++	Authctxt *authctxt = ssh->authctxt;
++	gss_buffer_desc gssbuf;
++	gss_buffer_desc mic = GSS_C_EMPTY_BUFFER;
++	OM_uint32 ms;
++	int r;
++
++	static int attempt = 0;
++	if (attempt++ >= 1)
++		return (0);
++
++	if (gss_kex_context == NULL) {
++		debug("No valid Key exchange context");
++		return (0);
++	}
++
++	if ((b = sshbuf_new()) == NULL)
++		fatal("%s: sshbuf_new failed", __func__);
++
++	ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service,
++	    "gssapi-keyex");
++
++	if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
++		fatal("%s: sshbuf_mutable_ptr failed", __func__);
++	gssbuf.length = sshbuf_len(b);
++
++	if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) {
++		sshbuf_free(b);
++		return (0);
++	}
++
++	if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
++	    (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 ||
++	    (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 ||
++	    (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 ||
++	    (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 ||
++	    (r = sshpkt_send(ssh)) != 0)
++		fatal("%s: %s", __func__, ssh_err(r));
++
++	sshbuf_free(b);
++	gss_release_buffer(&ms, &mic);
++
++	return (1);
++}
++
+ #endif /* GSSAPI */
+ 
+ static int
+diff --git a/sshd.c b/sshd.c
+index 60b2aaf7..d92f03aa 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -817,8 +817,8 @@ notify_hostkeys(struct ssh *ssh)
+ 	}
+ 	debug3("%s: sent %u hostkeys", __func__, nkeys);
+ 	if (nkeys == 0)
+-		fatal("%s: no hostkeys", __func__);
+-	if ((r = sshpkt_send(ssh)) != 0)
++		debug3("%s: no hostkeys", __func__);
++	else if ((r = sshpkt_send(ssh)) != 0)
+ 		sshpkt_fatal(ssh, r, "%s: send", __func__);
+ 	sshbuf_free(buf);
+ }
+@@ -1852,7 +1852,8 @@ main(int ac, char **av)
+ 		free(fp);
+ 	}
+ 	accumulate_host_timing_secret(cfg, NULL);
+-	if (!sensitive_data.have_ssh2_key) {
++	/* The GSSAPI key exchange can run without a host key */
++	if (!sensitive_data.have_ssh2_key && !options.gss_keyex) {
+ 		logit("sshd: no hostkeys available -- exiting.");
+ 		exit(1);
+ 	}
+@@ -2347,6 +2348,48 @@ do_ssh2_kex(struct ssh *ssh)
+ 	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal(
+ 	    list_hostkey_types());
+ 
++#if defined(GSSAPI) && defined(WITH_OPENSSL)
++	{
++	char *orig;
++	char *gss = NULL;
++	char *newstr = NULL;
++	orig = myproposal[PROPOSAL_KEX_ALGS];
++
++	/*
++	 * If we don't have a host key, then there's no point advertising
++	 * the other key exchange algorithms
++	 */
++
++	if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0)
++		orig = NULL;
++
++	if (options.gss_keyex)
++		gss = ssh_gssapi_server_mechanisms();
++	else
++		gss = NULL;
++
++	if (gss && orig)
++		xasprintf(&newstr, "%s,%s", gss, orig);
++	else if (gss)
++		newstr = gss;
++	else if (orig)
++		newstr = orig;
++
++	/*
++	 * If we've got GSSAPI mechanisms, then we've got the 'null' host
++	 * key alg, but we can't tell people about it unless its the only
++	 * host key algorithm we support
++	 */
++	if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0)
++		myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null";
++
++	if (newstr)
++		myproposal[PROPOSAL_KEX_ALGS] = newstr;
++	else
++		fatal("No supported key exchange algorithms");
++	}
++#endif
++
+ 	/* start key exchange */
+ 	if ((r = kex_setup(ssh, myproposal)) != 0)
+ 		fatal("kex_setup: %s", ssh_err(r));
+@@ -2362,7 +2405,18 @@ do_ssh2_kex(struct ssh *ssh)
+ # ifdef OPENSSL_HAS_ECC
+ 	kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
+ # endif
+-#endif
++# ifdef GSSAPI
++	if (options.gss_keyex) {
++		kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
++		kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server;
++		kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server;
++		kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server;
++		kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server;
++		kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server;
++		kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server;
++	}
++# endif
++#endif /* WITH_OPENSSL */
+ 	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 	kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server;
+ 	kex->load_host_public_key=&get_hostkey_public_by_type;
+diff --git a/sshd_config b/sshd_config
+index 19b7c91a..2c48105f 100644
+--- a/sshd_config
++++ b/sshd_config
+@@ -69,6 +69,8 @@ AuthorizedKeysFile	.ssh/authorized_keys
+ # GSSAPI options
+ #GSSAPIAuthentication no
+ #GSSAPICleanupCredentials yes
++#GSSAPIStrictAcceptorCheck yes
++#GSSAPIKeyExchange no
+ 
+ # Set this to 'yes' to enable PAM authentication, account processing,
+ # and session processing. If this is enabled, PAM authentication will
+diff --git a/sshd_config.5 b/sshd_config.5
+index 70ccea44..f6b41a2f 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -646,6 +646,11 @@ Specifies whether to automatically destroy the user's credentials cache
+ on logout.
+ The default is
+ .Cm yes .
++.It Cm GSSAPIKeyExchange
++Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange
++doesn't rely on ssh keys to verify host identity.
++The default is
++.Cm no .
+ .It Cm GSSAPIStrictAcceptorCheck
+ Determines whether to be strict about the identity of the GSSAPI acceptor
+ a client authenticates against.
+@@ -660,6 +665,32 @@ machine's default store.
+ This facility is provided to assist with operation on multi homed machines.
+ The default is
+ .Cm yes .
++.It Cm GSSAPIStoreCredentialsOnRekey
++Controls whether the user's GSSAPI credentials should be updated following a
++successful connection rekeying. This option can be used to accepted renewed
++or updated credentials from a compatible client. The default is
++.Dq no .
++.Pp
++For this to work
++.Cm GSSAPIKeyExchange
++needs to be enabled in the server and also used by the client.
++.It Cm GSSAPIKexAlgorithms
++The list of key exchange algorithms that are accepted by GSSAPI
++key exchange. Possible values are
++.Bd -literal -offset 3n
++gss-gex-sha1-,
++gss-group1-sha1-,
++gss-group14-sha1-,
++gss-group14-sha256-,
++gss-group16-sha512-,
++gss-nistp256-sha256-,
++gss-curve25519-sha256-
++.Ed
++.Pp
++The default is
++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,
++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- .
++This option only applies to connections using GSSAPI.
+ .It Cm HostbasedAcceptedKeyTypes
+ Specifies the key types that will be accepted for hostbased authentication
+ as a list of comma-separated patterns.
+diff --git a/sshkey.c b/sshkey.c
+index 57995ee6..fd5b7724 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -154,6 +154,7 @@ static const struct keytype keytypes[] = {
+ 	    KEY_ECDSA_SK_CERT, NID_X9_62_prime256v1, 1, 0 },
+ # endif /* OPENSSL_HAS_ECC */
+ #endif /* WITH_OPENSSL */
++	{ "null", "null", NULL, KEY_NULL, 0, 0, 0 },
+ 	{ NULL, NULL, NULL, -1, -1, 0, 0 }
+ };
+ 
+@@ -255,7 +256,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
+ 	const struct keytype *kt;
+ 
+ 	for (kt = keytypes; kt->type != -1; kt++) {
+-		if (kt->name == NULL)
++		if (kt->name == NULL || kt->type == KEY_NULL)
+ 			continue;
+ 		if (!include_sigonly && kt->sigonly)
+ 			continue;
+diff --git a/sshkey.h b/sshkey.h
+index 71a3fddc..37a43a67 100644
+--- a/sshkey.h
++++ b/sshkey.h
+@@ -69,6 +69,7 @@ enum sshkey_types {
+ 	KEY_ECDSA_SK_CERT,
+ 	KEY_ED25519_SK,
+ 	KEY_ED25519_SK_CERT,
++	KEY_NULL,
+ 	KEY_UNSPEC
+ };
+ 
diff --git a/openssh-8.0p1-openssl-evp.patch b/openssh-8.0p1-openssl-evp.patch
new file mode 100644
index 0000000..ade0bbb
--- /dev/null
+++ b/openssh-8.0p1-openssl-evp.patch
@@ -0,0 +1,720 @@
+From ed7ec0cdf577ffbb0b15145340cf51596ca3eb89 Mon Sep 17 00:00:00 2001
+From: Jakub Jelen <jjelen@redhat.com>
+Date: Tue, 14 May 2019 10:45:45 +0200
+Subject: [PATCH] Use high-level OpenSSL API for signatures
+
+---
+ digest-openssl.c |  16 ++++
+ digest.h         |   6 ++
+ ssh-dss.c        |  65 ++++++++++------
+ ssh-ecdsa.c      |  69 ++++++++++-------
+ ssh-rsa.c        | 193 +++++++++--------------------------------------
+ sshkey.c         |  77 +++++++++++++++++++
+ sshkey.h         |   4 +
+ 7 files changed, 221 insertions(+), 209 deletions(-)
+
+diff --git a/digest-openssl.c b/digest-openssl.c
+index da7ed72bc..6a21d8adb 100644
+--- a/digest-openssl.c
++++ b/digest-openssl.c
+@@ -63,6 +63,22 @@ const struct ssh_digest digests[] = {
+ 	{ -1,			NULL,		0,	NULL },
+ };
+ 
++const EVP_MD *
++ssh_digest_to_md(int digest_type)
++{
++	switch (digest_type) {
++	case SSH_DIGEST_SHA1:
++		return EVP_sha1();
++	case SSH_DIGEST_SHA256:
++		return EVP_sha256();
++	case SSH_DIGEST_SHA384:
++		return EVP_sha384();
++	case SSH_DIGEST_SHA512:
++		return EVP_sha512();
++	}
++	return NULL;
++}
++
+ static const struct ssh_digest *
+ ssh_digest_by_alg(int alg)
+ {
+diff --git a/digest.h b/digest.h
+index 274574d0e..c7ceeb36f 100644
+--- a/digest.h
++++ b/digest.h
+@@ -32,6 +32,12 @@
+ struct sshbuf;
+ struct ssh_digest_ctx;
+ 
++#ifdef WITH_OPENSSL
++#include <openssl/evp.h>
++/* Converts internal digest representation to the OpenSSL one */
++const EVP_MD *ssh_digest_to_md(int digest_type);
++#endif
++
+ /* Looks up a digest algorithm by name */
+ int ssh_digest_alg_by_name(const char *name);
+ 
+diff --git a/ssh-dss.c b/ssh-dss.c
+index a23c383dc..ea45e7275 100644
+--- a/ssh-dss.c
++++ b/ssh-dss.c
+@@ -52,11 +52,15 @@ int
+ ssh_dss_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+     const u_char *data, size_t datalen, u_int compat)
+ {
++	EVP_PKEY *pkey = NULL;
+ 	DSA_SIG *sig = NULL;
+ 	const BIGNUM *sig_r, *sig_s;
+-	u_char digest[SSH_DIGEST_MAX_LENGTH], sigblob[SIGBLOB_LEN];
+-	size_t rlen, slen, len, dlen = ssh_digest_bytes(SSH_DIGEST_SHA1);
++	u_char sigblob[SIGBLOB_LEN];
++	size_t rlen, slen;
++	int len;
+ 	struct sshbuf *b = NULL;
++	u_char *sigb = NULL;
++	const u_char *psig = NULL;
+ 	int ret = SSH_ERR_INVALID_ARGUMENT;
+ 
+ 	if (lenp != NULL)
+@@ -67,17 +71,24 @@ ssh_dss_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 	if (key == NULL || key->dsa == NULL ||
+ 	    sshkey_type_plain(key->type) != KEY_DSA)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+-	if (dlen == 0)
+-		return SSH_ERR_INTERNAL_ERROR;
+ 
+-	if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, datalen,
+-	    digest, sizeof(digest))) != 0)
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_DSA(pkey, key->dsa) != 1)
++		return SSH_ERR_ALLOC_FAIL;
++	ret = sshkey_calculate_signature(pkey, SSH_DIGEST_SHA1, &sigb, &len,
++	    data, datalen);
++	EVP_PKEY_free(pkey);
++	if (ret < 0) {
+ 		goto out;
++	}
+ 
+-	if ((sig = DSA_do_sign(digest, dlen, key->dsa)) == NULL) {
++	psig = sigb;
++	if ((sig = d2i_DSA_SIG(NULL, &psig, len)) == NULL) {
+ 		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
+ 	}
++	free(sigb);
++	sigb = NULL;
+ 
+ 	DSA_SIG_get0(sig, &sig_r, &sig_s);
+ 	rlen = BN_num_bytes(sig_r);
+@@ -110,7 +121,7 @@ ssh_dss_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 		*lenp = len;
+ 	ret = 0;
+  out:
+-	explicit_bzero(digest, sizeof(digest));
++	free(sigb);
+ 	DSA_SIG_free(sig);
+ 	sshbuf_free(b);
+ 	return ret;
+@@ -121,20 +132,20 @@ ssh_dss_verify(const struct sshkey *key,
+     const u_char *signature, size_t signaturelen,
+     const u_char *data, size_t datalen, u_int compat)
+ {
++	EVP_PKEY *pkey = NULL;
+ 	DSA_SIG *sig = NULL;
+ 	BIGNUM *sig_r = NULL, *sig_s = NULL;
+-	u_char digest[SSH_DIGEST_MAX_LENGTH], *sigblob = NULL;
+-	size_t len, dlen = ssh_digest_bytes(SSH_DIGEST_SHA1);
++	u_char *sigblob = NULL;
++	size_t len, slen;
+ 	int ret = SSH_ERR_INTERNAL_ERROR;
+ 	struct sshbuf *b = NULL;
+ 	char *ktype = NULL;
++	u_char *sigb = NULL, *psig = NULL;
+ 
+ 	if (key == NULL || key->dsa == NULL ||
+ 	    sshkey_type_plain(key->type) != KEY_DSA ||
+ 	    signature == NULL || signaturelen == 0)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+-	if (dlen == 0)
+-		return SSH_ERR_INTERNAL_ERROR;
+ 
+ 	/* fetch signature */
+ 	if ((b = sshbuf_from(signature, signaturelen)) == NULL)
+@@ -176,25 +187,31 @@ ssh_dss_verify(const struct sshkey *key,
+ 	}
+ 	sig_r = sig_s = NULL; /* transferred */
+ 
+-	/* sha1 the data */
+-	if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, datalen,
+-	    digest, sizeof(digest))) != 0)
++	if ((slen = i2d_DSA_SIG(sig, NULL)) == 0) {
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
+-
+-	switch (DSA_do_verify(digest, dlen, sig, key->dsa)) {
+-	case 1:
+-		ret = 0;
+-		break;
+-	case 0:
+-		ret = SSH_ERR_SIGNATURE_INVALID;
++	}
++	if ((sigb = malloc(slen)) == NULL) {
++		ret = SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+-	default:
++	}
++	psig = sigb;
++	if ((slen = i2d_DSA_SIG(sig, &psig)) == 0) {
+ 		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
+ 	}
+ 
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_DSA(pkey, key->dsa) != 1) {
++		ret = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++	ret = sshkey_verify_signature(pkey, SSH_DIGEST_SHA1, data, datalen,
++	    sigb, slen);
++	EVP_PKEY_free(pkey);
++
+  out:
+-	explicit_bzero(digest, sizeof(digest));
++	free(sigb);
+ 	DSA_SIG_free(sig);
+ 	BN_clear_free(sig_r);
+ 	BN_clear_free(sig_s);
+diff --git a/ssh-ecdsa.c b/ssh-ecdsa.c
+index 599c7199d..b036796e8 100644
+--- a/ssh-ecdsa.c
++++ b/ssh-ecdsa.c
+@@ -50,11 +50,13 @@ int
+ ssh_ecdsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+     const u_char *data, size_t datalen, u_int compat)
+ {
++	EVP_PKEY *pkey = NULL;
+ 	ECDSA_SIG *sig = NULL;
++	unsigned char *sigb = NULL;
++	const unsigned char *psig;
+ 	const BIGNUM *sig_r, *sig_s;
+ 	int hash_alg;
+-	u_char digest[SSH_DIGEST_MAX_LENGTH];
+-	size_t len, dlen;
++	int len;
+ 	struct sshbuf *b = NULL, *bb = NULL;
+ 	int ret = SSH_ERR_INTERNAL_ERROR;
+ 
+@@ -67,18 +69,24 @@ ssh_ecdsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 	    sshkey_type_plain(key->type) != KEY_ECDSA)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+ 
+-	if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1 ||
+-	    (dlen = ssh_digest_bytes(hash_alg)) == 0)
++	if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
+ 		return SSH_ERR_INTERNAL_ERROR;
+-	if ((ret = ssh_digest_memory(hash_alg, data, datalen,
+-	    digest, sizeof(digest))) != 0)
++
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa) != 1)
++		return SSH_ERR_ALLOC_FAIL;
++	ret = sshkey_calculate_signature(pkey, hash_alg, &sigb, &len, data,
++	    datalen);
++	EVP_PKEY_free(pkey);
++	if (ret < 0) {
+ 		goto out;
++	}
+ 
+-	if ((sig = ECDSA_do_sign(digest, dlen, key->ecdsa)) == NULL) {
++	psig = sigb;
++	if ((sig = d2i_ECDSA_SIG(NULL, &psig, len)) == NULL) {
+ 		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
+ 	}
+-
+ 	if ((bb = sshbuf_new()) == NULL || (b = sshbuf_new()) == NULL) {
+ 		ret = SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+@@ -102,7 +110,7 @@ ssh_ecdsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 		*lenp = len;
+ 	ret = 0;
+  out:
+-	explicit_bzero(digest, sizeof(digest));
++	free(sigb);
+ 	sshbuf_free(b);
+ 	sshbuf_free(bb);
+ 	ECDSA_SIG_free(sig);
+@@ -115,22 +123,21 @@ ssh_ecdsa_verify(const struct sshkey *key,
+     const u_char *signature, size_t signaturelen,
+     const u_char *data, size_t datalen, u_int compat)
+ {
++	EVP_PKEY *pkey = NULL;
+ 	ECDSA_SIG *sig = NULL;
+ 	BIGNUM *sig_r = NULL, *sig_s = NULL;
+-	int hash_alg;
+-	u_char digest[SSH_DIGEST_MAX_LENGTH];
+-	size_t dlen;
++	int hash_alg, len;
+ 	int ret = SSH_ERR_INTERNAL_ERROR;
+ 	struct sshbuf *b = NULL, *sigbuf = NULL;
+ 	char *ktype = NULL;
++	unsigned char *sigb = NULL, *psig = NULL;
+ 
+ 	if (key == NULL || key->ecdsa == NULL ||
+ 	    sshkey_type_plain(key->type) != KEY_ECDSA ||
+ 	    signature == NULL || signaturelen == 0)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+ 
+-	if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1 ||
+-	    (dlen = ssh_digest_bytes(hash_alg)) == 0)
++	if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
+ 		return SSH_ERR_INTERNAL_ERROR;
+ 
+ 	/* fetch signature */
+@@ -166,28 +173,36 @@ ssh_ecdsa_verify(const struct sshkey *key,
+ 	}
+ 	sig_r = sig_s = NULL; /* transferred */
+ 
+-	if (sshbuf_len(sigbuf) != 0) {
+-		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
++	/* Figure out the length */
++	if ((len = i2d_ECDSA_SIG(sig, NULL)) == 0) {
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	if ((sigb = malloc(len)) == NULL) {
++		ret = SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+ 	}
+-	if ((ret = ssh_digest_memory(hash_alg, data, datalen,
+-	    digest, sizeof(digest))) != 0)
++	psig = sigb;
++	if ((len = i2d_ECDSA_SIG(sig, &psig)) == 0) {
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 		goto out;
++	}
+ 
+-	switch (ECDSA_do_verify(digest, dlen, sig, key->ecdsa)) {
+-	case 1:
+-		ret = 0;
+-		break;
+-	case 0:
+-		ret = SSH_ERR_SIGNATURE_INVALID;
++	if (sshbuf_len(sigbuf) != 0) {
++		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
+ 		goto out;
+-	default:
+-		ret = SSH_ERR_LIBCRYPTO_ERROR;
++	}
++
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa) != 1) {
++		ret =  SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+ 	}
++	ret = sshkey_verify_signature(pkey, hash_alg, data, datalen, sigb, len);
++	EVP_PKEY_free(pkey);
+ 
+  out:
+-	explicit_bzero(digest, sizeof(digest));
++	free(sigb);
+ 	sshbuf_free(sigbuf);
+ 	sshbuf_free(b);
+ 	ECDSA_SIG_free(sig);
+diff --git a/ssh-rsa.c b/ssh-rsa.c
+index 9b14f9a9a..8ef3a6aca 100644
+--- a/ssh-rsa.c
++++ b/ssh-rsa.c
+@@ -37,7 +37,7 @@
+ 
+ #include "openbsd-compat/openssl-compat.h"
+ 
+-static int openssh_RSA_verify(int, u_char *, size_t, u_char *, size_t, RSA *);
++static int openssh_RSA_verify(int, const u_char *, size_t, u_char *, size_t, EVP_PKEY *);
+ 
+ static const char *
+ rsa_hash_alg_ident(int hash_alg)
+@@ -90,21 +90,6 @@ rsa_hash_id_from_keyname(const char *alg)
+ 	return -1;
+ }
+ 
+-static int
+-rsa_hash_alg_nid(int type)
+-{
+-	switch (type) {
+-	case SSH_DIGEST_SHA1:
+-		return NID_sha1;
+-	case SSH_DIGEST_SHA256:
+-		return NID_sha256;
+-	case SSH_DIGEST_SHA512:
+-		return NID_sha512;
+-	default:
+-		return -1;
+-	}
+-}
+-
+ int
+ ssh_rsa_complete_crt_parameters(struct sshkey *key, const BIGNUM *iqmp)
+ {
+@@ -164,11 +149,10 @@ int
+ ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+     const u_char *data, size_t datalen, const char *alg_ident)
+ {
+-	const BIGNUM *rsa_n;
+-	u_char digest[SSH_DIGEST_MAX_LENGTH], *sig = NULL;
+-	size_t slen = 0;
+-	u_int dlen, len;
+-	int nid, hash_alg, ret = SSH_ERR_INTERNAL_ERROR;
++	EVP_PKEY *pkey = NULL;
++	u_char *sig = NULL;
++	int len, slen = 0;
++	int hash_alg, ret = SSH_ERR_INTERNAL_ERROR;
+ 	struct sshbuf *b = NULL;
+ 
+ 	if (lenp != NULL)
+@@ -180,33 +164,24 @@ ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 		hash_alg = SSH_DIGEST_SHA1;
+ 	else
+ 		hash_alg = rsa_hash_id_from_keyname(alg_ident);
++
+ 	if (key == NULL || key->rsa == NULL || hash_alg == -1 ||
+ 	    sshkey_type_plain(key->type) != KEY_RSA)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+-	RSA_get0_key(key->rsa, &rsa_n, NULL, NULL);
+-	if (BN_num_bits(rsa_n) < SSH_RSA_MINIMUM_MODULUS_SIZE)
+-		return SSH_ERR_KEY_LENGTH;
+ 	slen = RSA_size(key->rsa);
+-	if (slen <= 0 || slen > SSHBUF_MAX_BIGNUM)
+-		return SSH_ERR_INVALID_ARGUMENT;
+-
+-	/* hash the data */
+-	nid = rsa_hash_alg_nid(hash_alg);
+-	if ((dlen = ssh_digest_bytes(hash_alg)) == 0)
+-		return SSH_ERR_INTERNAL_ERROR;
+-	if ((ret = ssh_digest_memory(hash_alg, data, datalen,
+-	    digest, sizeof(digest))) != 0)
+-		goto out;
++	if (RSA_bits(key->rsa) < SSH_RSA_MINIMUM_MODULUS_SIZE)
++		return SSH_ERR_KEY_LENGTH;
+ 
+-	if ((sig = malloc(slen)) == NULL) {
+-		ret = SSH_ERR_ALLOC_FAIL;
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_RSA(pkey, key->rsa) != 1)
++		return SSH_ERR_ALLOC_FAIL;
++	ret = sshkey_calculate_signature(pkey, hash_alg, &sig, &len, data,
++	    datalen);
++	EVP_PKEY_free(pkey);
++	if (ret < 0) {
+ 		goto out;
+ 	}
+ 
+-	if (RSA_sign(nid, digest, dlen, sig, &len, key->rsa) != 1) {
+-		ret = SSH_ERR_LIBCRYPTO_ERROR;
+-		goto out;
+-	}
+ 	if (len < slen) {
+ 		size_t diff = slen - len;
+ 		memmove(sig + diff, sig, len);
+@@ -215,6 +190,7 @@ ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 		ret = SSH_ERR_INTERNAL_ERROR;
+ 		goto out;
+ 	}
++
+ 	/* encode signature */
+ 	if ((b = sshbuf_new()) == NULL) {
+ 		ret = SSH_ERR_ALLOC_FAIL;
+@@ -235,7 +211,6 @@ ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+ 		*lenp = len;
+ 	ret = 0;
+  out:
+-	explicit_bzero(digest, sizeof(digest));
+ 	freezero(sig, slen);
+ 	sshbuf_free(b);
+ 	return ret;
+@@ -246,10 +221,10 @@ ssh_rsa_verify(const struct sshkey *key,
+     const u_char *sig, size_t siglen, const u_char *data, size_t datalen,
+     const char *alg)
+ {
+-	const BIGNUM *rsa_n;
++	EVP_PKEY *pkey = NULL;
+ 	char *sigtype = NULL;
+ 	int hash_alg, want_alg, ret = SSH_ERR_INTERNAL_ERROR;
+-	size_t len = 0, diff, modlen, dlen;
++	size_t len = 0, diff, modlen;
+ 	struct sshbuf *b = NULL;
+ 	u_char digest[SSH_DIGEST_MAX_LENGTH], *osigblob, *sigblob = NULL;
+ 
+@@ -257,8 +232,7 @@ ssh_rsa_verify(const struct sshkey *key,
+ 	    sshkey_type_plain(key->type) != KEY_RSA ||
+ 	    sig == NULL || siglen == 0)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+-	RSA_get0_key(key->rsa, &rsa_n, NULL, NULL);
+-	if (BN_num_bits(rsa_n) < SSH_RSA_MINIMUM_MODULUS_SIZE)
++	if (RSA_bits(key->rsa) < SSH_RSA_MINIMUM_MODULUS_SIZE)
+ 		return SSH_ERR_KEY_LENGTH;
+ 
+ 	if ((b = sshbuf_from(sig, siglen)) == NULL)
+@@ -310,16 +284,15 @@ ssh_rsa_verify(const struct sshkey *key,
+ 		explicit_bzero(sigblob, diff);
+ 		len = modlen;
+ 	}
+-	if ((dlen = ssh_digest_bytes(hash_alg)) == 0) {
+-		ret = SSH_ERR_INTERNAL_ERROR;
++
++	if ((pkey = EVP_PKEY_new()) == NULL ||
++	    EVP_PKEY_set1_RSA(pkey, key->rsa) != 1) {
++		ret = SSH_ERR_ALLOC_FAIL;
+ 		goto out;
+ 	}
+-	if ((ret = ssh_digest_memory(hash_alg, data, datalen,
+-	    digest, sizeof(digest))) != 0)
+-		goto out;
++	ret = openssh_RSA_verify(hash_alg, data, datalen, sigblob, len, pkey);
++	EVP_PKEY_free(pkey);
+ 
+-	ret = openssh_RSA_verify(hash_alg, digest, dlen, sigblob, len,
+-	    key->rsa);
+  out:
+ 	freezero(sigblob, len);
+ 	free(sigtype);
+@@ -328,122 +301,26 @@ ssh_rsa_verify(const struct sshkey *key,
+ 	return ret;
+ }
+ 
+-/*
+- * See:
+- * http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/
+- * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.asn
+- */
+-
+-/*
+- * id-sha1 OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
+- *	oiw(14) secsig(3) algorithms(2) 26 }
+- */
+-static const u_char id_sha1[] = {
+-	0x30, 0x21, /* type Sequence, length 0x21 (33) */
+-	0x30, 0x09, /* type Sequence, length 0x09 */
+-	0x06, 0x05, /* type OID, length 0x05 */
+-	0x2b, 0x0e, 0x03, 0x02, 0x1a, /* id-sha1 OID */
+-	0x05, 0x00, /* NULL */
+-	0x04, 0x14  /* Octet string, length 0x14 (20), followed by sha1 hash */
+-};
+-
+-/*
+- * See http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+- * id-sha256 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840)
+- *      organization(1) gov(101) csor(3) nistAlgorithm(4) hashAlgs(2)
+- *      id-sha256(1) }
+- */
+-static const u_char id_sha256[] = {
+-	0x30, 0x31, /* type Sequence, length 0x31 (49) */
+-	0x30, 0x0d, /* type Sequence, length 0x0d (13) */
+-	0x06, 0x09, /* type OID, length 0x09 */
+-	0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, /* id-sha256 */
+-	0x05, 0x00, /* NULL */
+-	0x04, 0x20  /* Octet string, length 0x20 (32), followed by sha256 hash */
+-};
+-
+-/*
+- * See http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+- * id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840)
+- *      organization(1) gov(101) csor(3) nistAlgorithm(4) hashAlgs(2)
+- *      id-sha256(3) }
+- */
+-static const u_char id_sha512[] = {
+-	0x30, 0x51, /* type Sequence, length 0x51 (81) */
+-	0x30, 0x0d, /* type Sequence, length 0x0d (13) */
+-	0x06, 0x09, /* type OID, length 0x09 */
+-	0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, /* id-sha512 */
+-	0x05, 0x00, /* NULL */
+-	0x04, 0x40  /* Octet string, length 0x40 (64), followed by sha512 hash */
+-};
+-
+ static int
+-rsa_hash_alg_oid(int hash_alg, const u_char **oidp, size_t *oidlenp)
++openssh_RSA_verify(int hash_alg, const u_char *data, size_t datalen,
++    u_char *sigbuf, size_t siglen, EVP_PKEY *pkey)
+ {
+-	switch (hash_alg) {
+-	case SSH_DIGEST_SHA1:
+-		*oidp = id_sha1;
+-		*oidlenp = sizeof(id_sha1);
+-		break;
+-	case SSH_DIGEST_SHA256:
+-		*oidp = id_sha256;
+-		*oidlenp = sizeof(id_sha256);
+-		break;
+-	case SSH_DIGEST_SHA512:
+-		*oidp = id_sha512;
+-		*oidlenp = sizeof(id_sha512);
+-		break;
+-	default:
+-		return SSH_ERR_INVALID_ARGUMENT;
+-	}
+-	return 0;
+-}
++	size_t rsasize = 0;
++	const RSA *rsa;
++	int ret;
+ 
+-static int
+-openssh_RSA_verify(int hash_alg, u_char *hash, size_t hashlen,
+-    u_char *sigbuf, size_t siglen, RSA *rsa)
+-{
+-	size_t rsasize = 0, oidlen = 0, hlen = 0;
+-	int ret, len, oidmatch, hashmatch;
+-	const u_char *oid = NULL;
+-	u_char *decrypted = NULL;
+-
+-	if ((ret = rsa_hash_alg_oid(hash_alg, &oid, &oidlen)) != 0)
+-		return ret;
+-	ret = SSH_ERR_INTERNAL_ERROR;
+-	hlen = ssh_digest_bytes(hash_alg);
+-	if (hashlen != hlen) {
+-		ret = SSH_ERR_INVALID_ARGUMENT;
+-		goto done;
+-	}
++	rsa = EVP_PKEY_get0_RSA(pkey);
+ 	rsasize = RSA_size(rsa);
+ 	if (rsasize <= 0 || rsasize > SSHBUF_MAX_BIGNUM ||
+ 	    siglen == 0 || siglen > rsasize) {
+ 		ret = SSH_ERR_INVALID_ARGUMENT;
+ 		goto done;
+ 	}
+-	if ((decrypted = malloc(rsasize)) == NULL) {
+-		ret = SSH_ERR_ALLOC_FAIL;
+-		goto done;
+-	}
+-	if ((len = RSA_public_decrypt(siglen, sigbuf, decrypted, rsa,
+-	    RSA_PKCS1_PADDING)) < 0) {
+-		ret = SSH_ERR_LIBCRYPTO_ERROR;
+-		goto done;
+-	}
+-	if (len < 0 || (size_t)len != hlen + oidlen) {
+-		ret = SSH_ERR_INVALID_FORMAT;
+-		goto done;
+-	}
+-	oidmatch = timingsafe_bcmp(decrypted, oid, oidlen) == 0;
+-	hashmatch = timingsafe_bcmp(decrypted + oidlen, hash, hlen) == 0;
+-	if (!oidmatch || !hashmatch) {
+-		ret = SSH_ERR_SIGNATURE_INVALID;
+-		goto done;
+-	}
+-	ret = 0;
++
++	ret = sshkey_verify_signature(pkey, hash_alg, data, datalen,
++	    sigbuf, siglen);
++
+ done:
+-	freezero(decrypted, rsasize);
+ 	return ret;
+ }
+ #endif /* WITH_OPENSSL */
+diff --git a/sshkey.c b/sshkey.c
+index ad1957762..b95ed0b10 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -358,6 +358,83 @@ sshkey_type_plain(int type)
+ }
+ 
+ #ifdef WITH_OPENSSL
++int
++sshkey_calculate_signature(EVP_PKEY *pkey, int hash_alg, u_char **sigp,
++    int *lenp, const u_char *data, size_t datalen)
++{
++	EVP_MD_CTX *ctx = NULL;
++	u_char *sig = NULL;
++	int ret, slen, len;
++
++	if (sigp == NULL || lenp == NULL) {
++		return SSH_ERR_INVALID_ARGUMENT;
++	}
++
++	slen = EVP_PKEY_size(pkey);
++	if (slen <= 0 || slen > SSHBUF_MAX_BIGNUM)
++		return SSH_ERR_INVALID_ARGUMENT;
++
++	len = slen;
++	if ((sig = malloc(slen)) == NULL) {
++		return SSH_ERR_ALLOC_FAIL;
++	}
++
++	if ((ctx = EVP_MD_CTX_new()) == NULL) {
++		ret = SSH_ERR_ALLOC_FAIL;
++		goto error;
++	}
++	if (EVP_SignInit_ex(ctx, ssh_digest_to_md(hash_alg), NULL) <= 0 ||
++	    EVP_SignUpdate(ctx, data, datalen) <= 0 ||
++	    EVP_SignFinal(ctx, sig, &len, pkey) <= 0) {
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
++		goto error;
++	}
++
++	*sigp = sig;
++	*lenp = len;
++	/* Now owned by the caller */
++	sig = NULL;
++	ret = 0;
++
++error:
++	EVP_MD_CTX_free(ctx);
++	free(sig);
++	return ret;
++}
++
++int
++sshkey_verify_signature(EVP_PKEY *pkey, int hash_alg, const u_char *data,
++    size_t datalen, u_char *sigbuf, int siglen)
++{
++	EVP_MD_CTX *ctx = NULL;
++	int ret;
++
++	if ((ctx = EVP_MD_CTX_new()) == NULL) {
++		return SSH_ERR_ALLOC_FAIL;
++	}
++	if (EVP_VerifyInit_ex(ctx, ssh_digest_to_md(hash_alg), NULL) <= 0 ||
++	    EVP_VerifyUpdate(ctx, data, datalen) <= 0) {
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
++		goto done;
++	}
++	ret = EVP_VerifyFinal(ctx, sigbuf, siglen, pkey);
++	switch (ret) {
++	case 1:
++		ret = 0;
++		break;
++	case 0:
++		ret = SSH_ERR_SIGNATURE_INVALID;
++		break;
++	default:
++		ret = SSH_ERR_LIBCRYPTO_ERROR;
++		break;
++	}
++
++done:
++	EVP_MD_CTX_free(ctx);
++	return ret;
++}
++
+ /* XXX: these are really begging for a table-driven approach */
+ int
+ sshkey_curve_name_to_nid(const char *name)
+diff --git a/sshkey.h b/sshkey.h
+index a91e60436..270901a87 100644
+--- a/sshkey.h
++++ b/sshkey.h
+@@ -179,6 +179,10 @@ const char	*sshkey_ssh_name(const struct sshkey *);
+ const char	*sshkey_ssh_name_plain(const struct sshkey *);
+ int		 sshkey_names_valid2(const char *, int);
+ char		*sshkey_alg_list(int, int, int, char);
++int		 sshkey_calculate_signature(EVP_PKEY*, int, u_char **,
++    int *, const u_char *, size_t);
++int		 sshkey_verify_signature(EVP_PKEY *, int, const u_char *,
++    size_t, u_char *, int);
+ 
+ int	 sshkey_from_blob(const u_char *, size_t, struct sshkey **);
+ int	 sshkey_fromb(struct sshbuf *, struct sshkey **);
+
diff --git a/openssh-8.0p1-openssl-kdf.patch b/openssh-8.0p1-openssl-kdf.patch
new file mode 100644
index 0000000..1db95c3
--- /dev/null
+++ b/openssh-8.0p1-openssl-kdf.patch
@@ -0,0 +1,137 @@
+commit 2c3ef499bfffce3cfd315edeebf202850ba4e00a
+Author: Jakub Jelen <jjelen@redhat.com>
+Date:   Tue Apr 16 15:35:18 2019 +0200
+
+    Use the new OpenSSL KDF
+
+diff --git a/configure.ac b/configure.ac
+index 2a455e4e..e01c3d43 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2712,6 +2712,7 @@ if test "x$openssl" = "xyes" ; then
+ 		HMAC_CTX_init \
+ 		RSA_generate_key_ex \
+ 		RSA_get_default_method \
++		EVP_KDF_CTX_new_id \
+ 	])
+ 
+ 	# OpenSSL_add_all_algorithms may be a macro.
+diff --git a/kex.c b/kex.c
+index b6f041f4..1fbce2bb 100644
+--- a/kex.c
++++ b/kex.c
+@@ -38,6 +38,9 @@
+ #ifdef WITH_OPENSSL
+ #include <openssl/crypto.h>
+ #include <openssl/dh.h>
++# ifdef HAVE_EVP_KDF_CTX_NEW_ID
++# include <openssl/kdf.h>
++# endif
+ #endif
+ 
+ #include "ssh.h"
+@@ -942,6 +945,95 @@ kex_choose_conf(struct ssh *ssh)
+ 	return r;
+ }
+ 
++#ifdef HAVE_EVP_KDF_CTX_NEW_ID
++static const EVP_MD *
++digest_to_md(int digest_type)
++{
++	switch (digest_type) {
++	case SSH_DIGEST_SHA1:
++		return EVP_sha1();
++	case SSH_DIGEST_SHA256:
++		return EVP_sha256();
++	case SSH_DIGEST_SHA384:
++		return EVP_sha384();
++	case SSH_DIGEST_SHA512:
++		return EVP_sha512();
++	}
++	return NULL;
++}
++
++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)
++{
++	struct kex *kex = ssh->kex;
++	EVP_KDF_CTX *ctx = NULL;
++	u_char *key = NULL;
++	int r, key_len;
++
++	if ((key_len = ssh_digest_bytes(kex->hash_alg)) == 0)
++		return SSH_ERR_INVALID_ARGUMENT;
++	key_len = ROUNDUP(need, key_len);
++	if ((key = calloc(1, key_len)) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++
++	ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF);
++	if (!ctx) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++
++	r = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, digest_to_md(kex->hash_alg));
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	r = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY,
++	    sshbuf_ptr(shared_secret), sshbuf_len(shared_secret));
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	r = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, hash, hashlen);
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	r = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, id);
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	r = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID,
++	    kex->session_id, kex->session_id_len);
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++	r = EVP_KDF_derive(ctx, key, key_len);
++	if (r != 1) {
++		r = SSH_ERR_LIBCRYPTO_ERROR;
++		goto out;
++	}
++#ifdef DEBUG_KEX
++	fprintf(stderr, "key '%c'== ", id);
++	dump_digest("key", key, key_len);
++#endif
++	*keyp = key;
++	key = NULL;
++	r = 0;
++
++out:
++	free (key);
++	EVP_KDF_CTX_free(ctx);
++	if (r < 0) {
++		return r;
++	}
++	return 0;
++}
++#else
+ 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)
+@@ -1004,6 +1096,7 @@ derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
+ 	ssh_digest_free(hashctx);
+ 	return r;
+ }
++#endif /* HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID */
+ 
+ #define NKEYS	6
+ int
+
diff --git a/openssh-8.0p1-pkcs11-uri.patch b/openssh-8.0p1-pkcs11-uri.patch
new file mode 100644
index 0000000..d55df23
--- /dev/null
+++ b/openssh-8.0p1-pkcs11-uri.patch
@@ -0,0 +1,3146 @@
+commit ed3eaf7d68c083b6015ca3425b75932999dafaad
+Author: Jakub Jelen <jjelen@redhat.com>
+Date:   Wed Apr 24 17:23:21 2019 +0200
+
+    PKCS#11 URI from Fedora
+    
+     * Print PKCS#11 URIs from ssh-keygen
+     * Accept PKCS#11 URIs in -i argument to ssh
+     * Allow PKCS#11 URI specification in ssh_config
+     * Fallback to p11-kit-proxy
+     * PKCS#11 URI support for ssh-add and ssh-agent
+      * internal representation is URI
+     * Allow to specify pin-value in URI to avoid interactive prompts
+    
+    Currently recognized and used parts of PKCS#11 URI:
+     * path (optional)
+      * token
+      * id
+      * manufacturer
+      * (library-manufacturer)
+     * query (optional)
+      * module-path
+      * pin-value
+    
+    Unit test for PKCS#11 URIs
+    
+     * test PKCS#11 URI parser, generator
+     * test percent_encodeer and decoder
+    
+    Regression tests for PKCS#11 URI support
+    
+     * soft-pkcs11.so  from people.su.se/~lha/soft-pkcs11
+      * Return correct CKR for unknown attributes
+      * Adjust and build it with regress tests (allowing agent-pkcs11 test)
+     * Test PKCS#11 URIs support with soft-pkcs11
+      * Direct usage from commandline (URI, provider and combination)
+      * Usage from configuration files
+      * Usage in ssh-agent (add, sign, remove)
+      * Make sure it is built with correct paths
+
+diff --git a/Makefile.in b/Makefile.in
+index e7549470..4511f82a 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -102,7 +102,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ 	monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-ecdsa-sk.o \
+ 	ssh-ed25519-sk.o ssh-rsa.o dh.o \
+ 	msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \
+-	ssh-pkcs11.o smult_curve25519_ref.o \
++	ssh-pkcs11.o ssh-pkcs11-uri.o smult_curve25519_ref.o \
+ 	poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \
+ 	ssh-ed25519.o digest-openssl.o digest-libc.o \
+ 	hmac.o sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \
+@@ -289,6 +289,8 @@ clean:	regressclean
+ 	rm -f regress/unittests/match/test_match$(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/kexfuzz/*.o
+ 	rm -f regress/misc/kexfuzz/kexfuzz$(EXEEXT)
+ 	rm -f regress/misc/sk-dummy/*.o
+@@ -322,6 +324,8 @@ distclean:	regressclean
+ 	rm -f regress/unittests/match/test_match
+ 	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/kexfuzz/*.o
+ 	rm -f regress/misc/kexfuzz/kexfuzz$(EXEEXT)
+ 	(cd openbsd-compat && $(MAKE) distclean)
+@@ -490,6 +494,7 @@ regress-prep:
+ 	$(MKDIR_P) `pwd`/regress/unittests/kex
+ 	$(MKDIR_P) `pwd`/regress/unittests/match
+ 	$(MKDIR_P) `pwd`/regress/unittests/utf8
++	$(MKDIR_P) `pwd`/regress/unittests/pkcs11
+ 	$(MKDIR_P) `pwd`/regress/misc/kexfuzz
+ 	$(MKDIR_P) `pwd`/regress/misc/sk-dummy
+ 	[ -f `pwd`/regress/Makefile ] || \
+@@ -617,6 +622,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
+ 	    regress/unittests/test_helper/libtest_helper.a \
+ 	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS)
+ 
++UNITTESTS_TEST_PKCS11_OBJS=\
++	regress/unittests/pkcs11/tests.o
++
++regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
++    ${UNITTESTS_TEST_PKCS11_OBJS} \
++    regress/unittests/test_helper/libtest_helper.a libssh.a
++	$(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
++	    regress/unittests/test_helper/libtest_helper.a \
++	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS)
++
+ MISC_KEX_FUZZ_OBJS=\
+ 	regress/misc/kexfuzz/kexfuzz.o \
+ 	$(SKOBJS)
+@@ -655,6 +670,7 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
+ 	regress/unittests/kex/test_kex$(EXEEXT) \
+ 	regress/unittests/match/test_match$(EXEEXT) \
+ 	regress/unittests/utf8/test_utf8$(EXEEXT) \
++	regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
+ 	regress/misc/kexfuzz/kexfuzz$(EXEEXT)
+ 
+ tests:	file-tests t-exec interop-tests unit
+diff --git a/configure.ac b/configure.ac
+index b689db4b..98d3ce4f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1911,12 +1911,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
+ 	]
+ )
+@@ -1945,6 +1947,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])
+@@ -5401,6 +5437,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 774c10d4..6bf3b627 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -116,7 +116,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 \
+@@ -246,6 +247,7 @@ unit:
+ 		V="" ; \
+ 		test "x${USE_VALGRIND}" = "x" || \
+ 		    V=${.CURDIR}/valgrind-unit.sh ; \
++		$$V ${.OBJDIR}/unittests/pkcs11/test_pkcs11 ; \
+ 		$$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \
+ 		$$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
+ 			-d ${.CURDIR}/unittests/sshkey/testdata ; \
+diff --git a/regress/agent-pkcs11.sh b/regress/agent-pkcs11.sh
+index fbbaea51..5d75d69f 100644
+--- a/regress/agent-pkcs11.sh
++++ b/regress/agent-pkcs11.sh
+@@ -113,7 +113,7 @@ else
+ 	done
+ 
+ 	trace "remove pkcs11 keys"
+-	echo ${TEST_SSH_PIN} | notty ${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1
++	${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1
+ 	r=$?
+ 	if [ $r -ne 0 ]; then
+ 		fail "ssh-add -e failed: exit code $r"
+diff --git a/regress/pkcs11.sh b/regress/pkcs11.sh
+new file mode 100644
+index 00000000..a91aee94
+--- /dev/null
++++ b/regress/pkcs11.sh
+@@ -0,0 +1,349 @@
++#
++#  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
++sed -i 's/.*BatchMode.*//g' $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"
++RSA=${DIR}/RSA
++EC=${DIR}/EC
++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
++
++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
++
++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
++
++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
++
++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
++
++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 "Test PKCS#11 URI specification in configuration files with bogus spaces"
++echo "IdentityFile \"    pkcs11:?module-path=${TEST_SSH_PKCS11}    \"" \
++    >> $OBJ/ssh_proxy
++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 with bogus spaces in config failed" \
++	    "(exit code $r)"
++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
+diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
+index 4e56e110..2690ebeb 100644
+--- a/regress/unittests/Makefile
++++ b/regress/unittests/Makefile
+@@ -2,6 +2,6 @@
+ 
+ REGRESS_FAIL_EARLY?=	yes
+ 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 00000000..b637cb13
+--- /dev/null
++++ b/regress/unittests/pkcs11/tests.c
+@@ -0,0 +1,337 @@
++/*
++ * 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)
++
++/* 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);
++}
++
++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 *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->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));
++	check_parse("pkcs11:id=%00%01",
++	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:token=SSH%20Keys",
++	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:library-manufacturer=OpenSC",
++	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:manufacturer=piv_II",
++	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL));
++	check_parse("pkcs11:object=SIGN%20Key",
++	    compose_uri(NULL, 0, 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, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++	check_parse("pkcs11:?pin-value=123456",
++	    compose_uri(NULL, 0, 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));
++	check_parse(
++	    "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri(NULL, 0, NULL, NULL, "CAC",
++	    "/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,
++	    "/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, "/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));
++	/* Percent character needs to be percent-encoded */
++	check_parse("pkcs11:token=%25",
++	     compose_uri(NULL, 0, "%", 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));
++	/* 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));
++	check_gen("pkcs11:id=%00%01",
++	    compose_uri("\x00\x01", 2, 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));
++	/* library-manufacturer is not implmented now */
++	/*check_gen("pkcs11:library-manufacturer=OpenSC",
++	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL));*/
++	check_gen("pkcs11:manufacturer=piv_II",
++	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL));
++	check_gen("pkcs11:object=RSA%20Key",
++	    compose_uri(NULL, 0, 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, "/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));
++	check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri("\xEE\x02", 2, 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, "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&amp;#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();
++}
+diff --git a/ssh-add.c b/ssh-add.c
+index 8057eb1f..0c470e32 100644
+--- a/ssh-add.c
++++ b/ssh-add.c
+@@ -67,6 +67,7 @@
+ #include "digest.h"
+ #include "ssh-sk.h"
+ #include "sk-api.h"
++#include "ssh-pkcs11-uri.h"
+ 
+ /* argv0 */
+ extern char *__progname;
+@@ -193,6 +194,32 @@ delete_all(int agent_fd, int qflag)
+ 	return ret;
+ }
+ 
++#ifdef ENABLE_PKCS11
++static int update_card(int, int, const char *, int, char *);
++
++int
++update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag)
++{
++	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, pin);
++}
++#endif
++
+ static int
+ add_file(int agent_fd, const char *filename, int key_only, int qflag,
+     const char *skprovider)
+@@ -402,12 +429,11 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
+ }
+ 
+ static int
+-update_card(int agent_fd, int add, const char *id, int qflag)
++update_card(int agent_fd, int add, const char *id, int qflag, char *pin)
+ {
+-	char *pin = NULL;
+ 	int r, ret = -1;
+ 
+-	if (add) {
++	if (add && pin == NULL) {
+ 		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
+ 		    RP_ALLOW_STDIN)) == NULL)
+ 			return -1;
+@@ -591,6 +617,13 @@ static int
+ do_file(int agent_fd, int deleting, int key_only, char *file, int qflag,
+     const char *skprovider)
+ {
++#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);
++	}
++#endif
+ 	if (deleting) {
+ 		if (delete_file(agent_fd, file, key_only, qflag) == -1)
+ 			return -1;
+@@ -773,7 +806,7 @@ main(int argc, char **argv)
+ 	}
+ 	if (pkcs11provider != NULL) {
+ 		if (update_card(agent_fd, !deleting, pkcs11provider,
+-		    qflag) == -1)
++		    qflag, NULL) == -1)
+ 			ret = 1;
+ 		goto done;
+ 	}
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 7eb6f0dc..27d8e4af 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -641,10 +641,72 @@ no_identities(SocketEntry *e)
+ }
+ 
+ #ifdef ENABLE_PKCS11
++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;
++
++	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");
++			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;
+@@ -681,33 +743,28 @@ process_add_smartcard_key(SocketEntry *e)
+ 			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("%s: add %.100s", __func__, canonical_provider);
++
+ 	if (lifetime && !death)
+ 		death = monotime() + lifetime;
+ 
+-	count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
++	debug("%s: add %.100s", __func__, sane_uri);
++	count = pkcs11_add_provider(sane_uri, pin, &keys, &comments);
+ 	for (i = 0; i < count; i++) {
+ 		k = keys[i];
+ 		if (lookup_identity(k) == NULL) {
+ 			id = xcalloc(1, sizeof(Identity));
+ 			id->key = k;
+ 			keys[i] = NULL; /* transferred */
+-			id->provider = xstrdup(canonical_provider);
++			id->provider = xstrdup(sane_uri);
+ 			if (*comments[i] != '\0') {
+ 				id->comment = comments[i];
+ 				comments[i] = NULL; /* transferred */
+ 			} else {
+-				id->comment = xstrdup(canonical_provider);
++				id->comment = xstrdup(sane_uri);
+ 			}
+ 			id->death = death;
+ 			id->confirm = confirm;
+@@ -721,6 +778,7 @@ process_add_smartcard_key(SocketEntry *e)
+ send:
+ 	free(pin);
+ 	free(provider);
++	free(sane_uri);
+ 	free(keys);
+ 	free(comments);
+ 	send_status(e, success);
+@@ -729,7 +787,7 @@ send:
+ static void
+ process_remove_smartcard_key(SocketEntry *e)
+ {
+-	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
++	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
+ 	int r, success = 0;
+ 	Identity *id, *nxt;
+ 
+@@ -740,30 +798,29 @@ 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("%s: remove %.100s", __func__, canonical_provider);
++	debug("%s: remove %.100s", __func__, 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--;
+ 		}
+ 	}
+-	if (pkcs11_del_provider(canonical_provider) == 0)
++	if (pkcs11_del_provider(sane_uri) == 0)
+ 		success = 1;
+ 	else
+ 		error("%s: pkcs11_del_provider failed", __func__);
+ send:
+ 	free(provider);
++	free(sane_uri);
+ 	send_status(e, success);
+ }
+ #endif /* ENABLE_PKCS11 */
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index 0d6ed1ff..182f4f2b 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -855,8 +855,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 8a0ffef5..ead8a562 100644
+--- a/ssh-pkcs11-client.c
++++ b/ssh-pkcs11-client.c
+@@ -323,6 +323,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ 	u_int nkeys, i;
+ 	struct sshbuf *msg;
+ 
++	debug("%s: called, name = %s", __func__, name);
++
+ 	if (fd < 0 && pkcs11_start_helper() < 0)
+ 		return (-1);
+ 
+@@ -342,6 +344,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("%s: nkeys = %u", __func__, nkeys);
+ 		for (i = 0; i < nkeys; i++) {
+ 			/* XXX clean up properly instead of fatal() */
+ 			if ((r = sshbuf_get_string(msg, &blob, &blen)) != 0 ||
+diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
+new file mode 100644
+index 00000000..e1a7b4e0
+--- /dev/null
++++ b/ssh-pkcs11-uri.c
+@@ -0,0 +1,425 @@
++/*
++ * 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 "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_MODULE_PATH "module-path"
++#define PKCS11_URI_PIN_VALUE "pin-value"
++
++/* Keyword tokens. */
++typedef enum {
++	pId, pToken, pObject, pLibraryManufacturer, pManufacturer, 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_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 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);
++	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("%s: The '%s' does not look like PKCS#11 URI",
++		    __func__, uri);
++		return -1;
++	}
++
++	if (pkcs11 == NULL) {
++		error("%s: Bad arguments. The pkcs11 can't be null", __func__);
++		return -1;
++	}
++
++	/* skip URI schema name */
++	p = strdup(uri);
++	str1 = p;
++
++	/* everything before ? */
++	tok = strtok_r(str1, "?", &saveptr1);
++	if (tok == NULL) {
++		error("%s: pk11-path expected, got EOF", __func__);
++		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("%s: The id already set in the PKCS#11 URI",
++					__func__);
++				rv = -1;
++				goto out;
++			}
++			len = percent_decode(arg, &pkcs11->id);
++			if (len <= 0) {
++				verbose("%s: Failed to percent-decode CKA_ID: %s",
++				    __func__, arg);
++				rv = -1;
++				goto out;
++			} else
++				pkcs11->id_len = len;
++			debug3("%s: Setting CKA_ID = %s from PKCS#11 URI",
++			    __func__, arg);
++			break;
++		case pToken:
++			/* CK_TOKEN_INFO -> label */
++			charptr = &pkcs11->token;
++ parse_string:
++			if (*charptr != NULL) {
++				verbose("%s: The %s already set in the PKCS#11 URI",
++				    keywords[opcode].name, __func__);
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, charptr);
++			debug3("%s: Setting %s = %s from PKCS#11 URI",
++			    __func__, 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 pLibraryManufacturer:
++			/* CK_INFO -> manufacturerID */
++			charptr = &pkcs11->lib_manuf;
++			goto parse_string;
++
++		default:
++			/* Unrecognized attribute in the URI path SHOULD be error */
++			verbose("%s: Unknown part of path in PKCS#11 URI: %s",
++			    __func__, 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("%s: Multiple module-path attributes are"
++				    "not supported the PKCS#11 URI", __func__);
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, &pkcs11->module_path);
++			debug3("%s: Setting PKCS11Provider = %s from PKCS#11 URI",
++			    __func__, pkcs11->module_path);
++			break;
++
++		case pPinValue:
++			/* pin-value */
++			if (pkcs11->pin != NULL) {
++				verbose("%s: Multiple pin-value attributes are"
++				    "not supported the PKCS#11 URI", __func__);
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, &pkcs11->pin);
++			debug3("%s: Setting PIN from PKCS#11 URI", __func__);
++			break;
++
++		default:
++			/* Unrecognized attribute in the URI query SHOULD be ignored */
++			verbose("%s: Unknown part of query in PKCS#11 URI: %s",
++			    __func__, 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 00000000..942a5a5a
+--- /dev/null
++++ b/ssh-pkcs11-uri.h
+@@ -0,0 +1,42 @@
++/*
++ * 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.
++ */
++
++#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;
++	/* 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);
++
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index a302c79c..879fe917 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -54,8 +54,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;
+@@ -64,6 +64,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;
+ };
+ 
+@@ -74,6 +81,7 @@ struct pkcs11_key {
+ 	CK_ULONG		slotidx;
+ 	char			*keyid;
+ 	int			keyid_len;
++	char			*label;
+ };
+ 
+ int pkcs11_interactive = 0;
+@@ -106,26 +114,63 @@ pkcs11_init(int interactive)
+  * 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("pkcs11_provider_finalize: %p refcount %d valid %d",
+-	    p, p->refcount, p->valid);
+-	if (!p->valid)
++	debug("%s: %p refcount %d valid %d", __func__,
++	    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("%s: %p refcount %d", __func__, m, m->refcount);
++	if (--m->refcount <= 0) {
++		pkcs11_module_finalize(m);
++		if (m->valid)
++			error("%s: %p still valid", __func__, m);
++		free(m->slotlist);
++		free(m->slotinfo);
++		free(m->module_path);
++		free(m);
++	}
++}
++
++/*
++ * finalize a provider shared libarary, it's no longer usable.
++ * however, there might still be keys referencing this provider,
++ * so the actuall 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("%s: %p refcount %d valid %d", __func__,
++	    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);
+ }
+ 
+ /*
+@@ -135,13 +180,11 @@ pkcs11_provider_finalize(struct pkcs11_provider *p)
+ static void
+ pkcs11_provider_unref(struct pkcs11_provider *p)
+ {
+-	debug("pkcs11_provider_unref: %p refcount %d", p, p->refcount);
++	debug("%s: %p refcount %d", __func__, p, p->refcount);
+ 	if (--p->refcount <= 0) {
+-		if (p->valid)
+-			error("pkcs11_provider_unref: %p still valid", p);
+ 		free(p->name);
+-		free(p->slotlist);
+-		free(p->slotinfo);
++		if (p->module)
++			pkcs11_module_unref(p->module);
+ 		free(p);
+ 	}
+ }
+@@ -159,6 +202,20 @@ pkcs11_terminate(void)
+ 	}
+ }
+ 
++/* 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)
+@@ -173,19 +230,52 @@ 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("%s: called, provider_id = %s", __func__, 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);
++	}
++
++	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("%s(%s): called", __func__, 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;
+ }
+ 
+ static RSA_METHOD *rsa_method;
+@@ -195,6 +285,55 @@ static EC_KEY_METHOD *ec_key_method;
+ static int ec_key_idx = 0;
+ #endif
+ 
++/*
++ * 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;
++
++	/* sanity - is it a RSA key with associated app_data? */
++	switch (key->type) {
++	case KEY_RSA:
++		k11 = RSA_get_ex_data(key->rsa, rsa_idx);
++		break;
++#ifdef HAVE_EC_KEY_METHOD_NEW
++	case KEY_ECDSA:
++		k11 = EC_KEY_get_ex_data(key->ecdsa, ec_key_idx);
++		break;
++#endif
++	default:
++		error("Unknown key type %d", key->type);
++		return -1;
++	}
++	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;
++
++	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;
++}
++
+ /* release a wrapped object */
+ static void
+ pkcs11_k11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
+@@ -208,6 +347,7 @@ pkcs11_k11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
+ 	if (k11->provider)
+ 		pkcs11_provider_unref(k11->provider);
+ 	free(k11->keyid);
++	free(k11->label);
+ 	free(k11);
+ }
+ 
+@@ -222,8 +362,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);
+@@ -262,12 +402,12 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
+ 	else {
+ 		snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ",
+ 		    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("%s: no pin specified", __func__);
+ 			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));
+@@ -282,13 +422,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);
+ }
+ 
+ 
+@@ -304,13 +445,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;
+@@ -341,13 +483,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) {
+@@ -424,8 +567,8 @@ pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa,
+ 		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];
+ 	tlen = RSA_size(rsa);
+ 
+ 	/* XXX handle CKR_BUFFER_TOO_SMALL */
+@@ -469,7 +612,7 @@ pkcs11_rsa_start_wrapper(void)
+ /* redirect private key operations for rsa key to pkcs11 token */
+ static int
+ pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
+-    CK_ATTRIBUTE *keyid_attrib, RSA *rsa)
++    CK_ATTRIBUTE *keyid_attrib, CK_ATTRIBUTE *label_attrib, RSA *rsa)
+ {
+ 	struct pkcs11_key	*k11;
+ 
+@@ -487,6 +630,12 @@ pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
+ 		memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
+ 	}
+ 
++	if (label_attrib->ulValueLen > 0 ) {
++		k11->label = xmalloc(label_attrib->ulValueLen+1);
++		memcpy(k11->label, label_attrib->pValue, label_attrib->ulValueLen);
++		k11->label[label_attrib->ulValueLen] = 0;
++	}
++
+ 	RSA_set_method(rsa, rsa_method);
+ 	RSA_set_ex_data(rsa, rsa_idx, k11);
+ 	return (0);
+@@ -517,8 +666,8 @@ ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv,
+ 		return (NULL);
+ 	}
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	siglen = ECDSA_size(ec);
+ 	sig = xmalloc(siglen);
+@@ -583,7 +732,7 @@ pkcs11_ecdsa_start_wrapper(void)
+ 
+ static int
+ pkcs11_ecdsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
+-    CK_ATTRIBUTE *keyid_attrib, EC_KEY *ec)
++    CK_ATTRIBUTE *keyid_attrib, CK_ATTRIBUTE *label_attrib, EC_KEY *ec)
+ {
+ 	struct pkcs11_key	*k11;
+ 
+@@ -599,6 +748,12 @@ pkcs11_ecdsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx,
+ 	k11->keyid = xmalloc(k11->keyid_len);
+ 	memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
+ 
++	if (label_attrib->ulValueLen > 0 ) {
++		k11->label = xmalloc(label_attrib->ulValueLen+1);
++		memcpy(k11->label, label_attrib->pValue, label_attrib->ulValueLen);
++		k11->label[label_attrib->ulValueLen] = 0;
++	}
++
+ 	EC_KEY_set_method(ec, ec_key_method);
+ 	EC_KEY_set_ex_data(ec, ec_key_idx, k11);
+ 
+@@ -635,8 +790,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;
+ 
+@@ -646,9 +801,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) {
+@@ -684,7 +839,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;
+@@ -698,14 +854,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);
+@@ -717,18 +874,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+ 	if (key_attr[1].ulValueLen == 0 ||
+-	    key_attr[2].ulValueLen == 0) {
++	    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;
+@@ -740,8 +898,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;
+@@ -752,13 +910,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;
+@@ -775,7 +933,7 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		goto fail;
+ 	}
+ 
+-	if (pkcs11_ecdsa_wrap(p, slotidx, &key_attr[0], ec))
++	if (pkcs11_ecdsa_wrap(p, slotidx, &key_attr[0], &key_attr[1], ec))
+ 		goto fail;
+ 
+ 	key = sshkey_new(KEY_UNSPEC);
+@@ -791,7 +949,7 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	ec = NULL;	/* now owned by key */
+ 
+ fail:
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		free(key_attr[i].pValue);
+ 	if (ec)
+ 		EC_KEY_free(ec);
+@@ -808,7 +966,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;
+@@ -819,14 +978,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);
+@@ -838,18 +998,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+ 	if (key_attr[1].ulValueLen == 0 ||
+-	    key_attr[2].ulValueLen == 0) {
++	    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;
+@@ -861,8 +1022,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;
+@@ -871,7 +1032,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		fatal("%s: set key", __func__);
+ 	rsa_n = rsa_e = NULL; /* transferred */
+ 
+-	if (pkcs11_rsa_wrap(p, slotidx, &key_attr[0], rsa))
++	if (pkcs11_rsa_wrap(p, slotidx, &key_attr[0], &key_attr[1], rsa))
+ 		goto fail;
+ 
+ 	key = sshkey_new(KEY_UNSPEC);
+@@ -886,7 +1047,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	rsa = NULL;	/* now owned by key */
+ 
+ fail:
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		free(key_attr[i].pValue);
+ 	RSA_free(rsa);
+ 
+@@ -897,7 +1058,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;
+@@ -921,14 +1083,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;
+@@ -940,18 +1103,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;
+@@ -965,8 +1129,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;
+ 	}
+@@ -986,7 +1150,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 			goto out;
+ 		}
+ 
+-		if (pkcs11_rsa_wrap(p, slotidx, &cert_attr[0], rsa))
++		if (pkcs11_rsa_wrap(p, slotidx, &cert_attr[0], &cert_attr[1], rsa))
+ 			goto out;
+ 
+ 		key = sshkey_new(KEY_UNSPEC);
+@@ -1016,7 +1180,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 			goto out;
+ 		}
+ 
+-		if (pkcs11_ecdsa_wrap(p, slotidx, &cert_attr[0], ec))
++		if (pkcs11_ecdsa_wrap(p, slotidx, &cert_attr[0], &cert_attr[1], ec))
+ 			goto out;
+ 
+ 		key = sshkey_new(KEY_UNSPEC);
+@@ -1036,7 +1200,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);
+@@ -1071,11 +1235,12 @@ have_rsa_key(const RSA *rsa)
+  */
+ 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;
+@@ -1092,10 +1257,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;
+@@ -1175,11 +1353,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;
+@@ -1195,10 +1374,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;
+@@ -1466,16 +1658,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;
+@@ -1484,167 +1670,303 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ 	CK_FUNCTION_LIST *f = NULL;
+ 	CK_TOKEN_INFO *token;
+ 	CK_ULONG i;
+-
+-	if (providerp == NULL)
++	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("%s: No module path provided", __func__);
+ 		goto fail;
+-	*providerp = NULL;
+-
+-	if (keyp != NULL)
+-		*keyp = NULL;
+-	if (labelsp != NULL)
+-		*labelsp = NULL;
++#endif
++	} else {
++		provider_module = strdup(uri->module_path);
++	}
+ 
+-	if (pkcs11_provider_lookup(provider_id) != NULL) {
+-		debug("%s: provider already registered: %s",
+-		    __func__, provider_id);
+-		goto fail;
++	p = xcalloc(1, sizeof(*p));
++	p->name = pkcs11_uri_get(uri);
++
++	if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
++	   && m->valid) {
++		debug("%s: provider module already initialized: %s",
++		    __func__, 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) {
+ 		error("dlsym(C_GetFunctionList) failed: %s", dlerror());
+ 		goto fail;
+ 	}
+-	p = xcalloc(1, sizeof(*p));
+-	p->name = xstrdup(provider_id);
+-	p->handle = handle;
++
++	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;
+ 	}
+-	rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID));
+-	rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription));
++	rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
++	if (uri->lib_manuf != NULL &&
++	    strcmp(uri->lib_manuf, m->info.manufacturerID)) {
++		debug("%s: Skipping provider %s not matching library_manufacturer",
++		    __func__, m->info.manufacturerID);
++		goto fail;
++	}
++	rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
+ 	debug("provider %s: manufacturerID <%s> cryptokiVersion %d.%d"
+ 	    " libraryDescription <%s> libraryVersion %d.%d",
+-	    provider_id,
+-	    p->info.manufacturerID,
+-	    p->info.cryptokiVersion.major,
+-	    p->info.cryptokiVersion.minor,
+-	    p->info.libraryDescription,
+-	    p->info.libraryVersion.major,
+-	    p->info.libraryVersion.minor);
+-	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
++	    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) {
++	if (m->nslots == 0) {
+ 		debug("%s: provider %s returned no slots", __func__,
+-		    provider_id);
++		    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));
+ 	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->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
++	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, (unsigned long)i, rv);
++			    "failed: %lu", provider_module, (unsigned long)i, rv);
++			token->flags = 0;
+ 			continue;
+ 		}
++		rmspace(token->label, sizeof(token->label));
++		rmspace(token->manufacturerID, sizeof(token->manufacturerID));
++		rmspace(token->model, sizeof(token->model));
++		rmspace(token->serialNumber, sizeof(token->serialNumber));
++	}
++	m->module_path = provider_module;
++	provider_module = NULL;
++
++	/* insert unconditionally -- remove if there will be no keys later */
++	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
++	p->refcount++;	/* add to provider list */
++	*providerp = p;
++	return 0;
++
++fail:
++	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
++		error("C_Finalize for provider %s failed: %lu",
++		    provider_module, rv);
++	free(provider_module);
++	if (m) {
++		free(m->slotlist);
++		free(m);
++	}
++	if (p) {
++		free(p->name);
++		free(p);
++	}
++	if (handle)
++		dlclose(handle);
++	return ret;
++}
++
++/*
++ * 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("%s: ignoring uninitialised token in "
+ 			    "provider %s slot %lu", __func__,
+-			    provider_id, (unsigned long)i);
++			    provider_uri, (unsigned long)i);
++			continue;
++		}
++		if (uri->token != NULL &&
++		    strcmp(token->label, uri->token) != 0) {
++			debug2("%s: ignoring token not matching label (%s) "
++			    "specified by PKCS#11 URI in slot %lu", __func__,
++			    token->label, (unsigned long)i);
++			continue;
++		}
++		if (uri->manuf != NULL &&
++		    strcmp(token->manufacturerID, uri->manuf) != 0) {
++			debug2("%s: ignoring token not matching requrested "
++			    "manufacturerID (%s) specified by PKCS#11 URI in "
++			    "slot %lu", __func__,
++			    token->manufacturerID, (unsigned long)i);
+ 			continue;
+ 		}
+-		rmspace(token->label, sizeof(token->label));
+-		rmspace(token->manufacturerID, sizeof(token->manufacturerID));
+-		rmspace(token->model, sizeof(token->model));
+-		rmspace(token->serialNumber, sizeof(token->serialNumber));
+ 		debug("provider %s slot %lu: label <%s> manufacturerID <%s> "
+ 		    "model <%s> serial <%s> flags 0x%lx",
+-		    provider_id, (unsigned long)i,
++		    provider_uri, (unsigned long)i,
+ 		    token->label, token->manufacturerID, token->model,
+ 		    token->serialNumber, token->flags);
+ 		/*
+-		 * open session, login with pin and retrieve public
+-		 * keys (if keyp is provided)
++		 * open session if not yet openend, login with pin and
++		 * retrieve public keys (if keyp is provided)
+ 		 */
+-		if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 ||
++		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);
+-		pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
+-		if (nkeys == 0 && !p->slotinfo[i].logged_in &&
++		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.
+ 			 */
+-			if (pkcs11_login_slot(p, &p->slotinfo[i],
++			debug3("%s: Trying to login as there were no keys found",
++			    __func__);
++			if (pkcs11_login_slot(p, &p->module->slotinfo[i],
+ 			    CKU_USER) < 0) {
+ 				error("login failed");
+ 				continue;
+ 			}
+-			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
+-			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
++			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("%s: No keys found. Retrying without label (%s) ",
++			    __func__, 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;
+ 
+-	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
+-	p->refcount++;	/* add to provider list */
+-
++	free(provider_uri);
+ 	return (nkeys);
+ fail:
+-	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
+-		error("C_Finalize for provider %s failed: %lu",
+-		    provider_id, rv);
+ 	if (p) {
+-		free(p->name);
+-		free(p->slotlist);
+-		free(p->slotinfo);
+-		free(p);
++ 		TAILQ_REMOVE(&pkcs11_providers, p, next);
++		pkcs11_provider_unref(p);
+ 	}
+-	if (handle)
+-		dlclose(handle);
+ 	if (ret > 0)
+ 		ret = -1;
+ 	return (ret);
+ }
+ 
+-/*
+- * register a new provider and get number of keys hold by the token,
+- * fails if provider already exists
+- */
++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("%s: called, provider_id = %s", __func__, 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;
++}
++
+ 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;
++	struct pkcs11_provider *p = NULL;
++	char *provider_uri = pkcs11_uri_get(uri);
++
++	debug("%s: called, provider_uri = %s", __func__, provider_uri);
+ 
+-	nkeys = pkcs11_register_provider(provider_id, pin, keyp, labelsp,
+-	    &p, CKU_USER);
++	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) {
+@@ -1652,7 +1974,37 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
+ 	}
+ 	if (nkeys == 0)
+ 		debug("%s: provider %s returned no keys", __func__,
+-		    provider_id);
++		    provider_uri);
++
++	free(provider_uri);
++	return nkeys;
++}
++
++/*
++ * register a new provider and get number of keys hold by the token,
++ * fails if provider already exists
++ */
++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);
+ }
+@@ -1674,7 +2026,7 @@ pkcs11_gakp(char *provider_id, char *pin, unsigned int slotidx, char *label,
+ 
+ 	if ((p = pkcs11_provider_lookup(provider_id)) != NULL)
+ 		debug("%s: provider \"%s\" available", __func__, provider_id);
+-	else if ((ret = pkcs11_register_provider(provider_id, pin, NULL, NULL,
++	else if ((rv = pkcs11_register_provider(provider_id, pin, NULL, NULL,
+ 	    &p, CKU_SO)) < 0) {
+ 		debug("%s: could not register provider %s", __func__,
+ 		    provider_id);
+@@ -1746,8 +2098,8 @@ pkcs11_destroy_keypair(char *provider_id, char *pin, unsigned long slotidx,
+ 
+ 	if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
+ 		debug("%s: using provider \"%s\"", __func__, provider_id);
+-	} else if (pkcs11_register_provider(provider_id, pin, NULL, NULL, &p,
+-	    CKU_SO) < 0) {
++	} else if ((rv = pkcs11_register_provider(provider_id, pin, NULL, NULL,
++	    &p, CKU_SO)) < 0) {
+ 		debug("%s: could not register provider %s", __func__,
+ 		    provider_id);
+ 		goto out;
+diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
+index 81f1d7c5..feaf74de 100644
+--- a/ssh-pkcs11.h
++++ b/ssh-pkcs11.h
+@@ -22,10 +22,14 @@
+ #define	SSH_PKCS11_ERR_PIN_REQUIRED		4
+ #define	SSH_PKCS11_ERR_PIN_LOCKED		5
+ 
++#include "ssh-pkcs11-uri.h"
++
+ 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 *);
+ #ifdef WITH_PKCS11_KEYGEN
+ struct sshkey *
+ 	pkcs11_gakp(char *, char *, unsigned int, char *, unsigned int,
+diff --git a/ssh.c b/ssh.c
+index 15aee569..976844cb 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -795,6 +795,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 "
+@@ -1603,6 +1611,7 @@ main(int ac, char **av)
+ 		free(options.certificate_files[i]);
+ 		options.certificate_files[i] = NULL;
+ 	}
++	pkcs11_terminate();
+ 
+  skip_connect:
+ 	exit_status = ssh_session2(ssh, pw);
+@@ -2076,6 +2085,45 @@ ssh_session2(struct ssh *ssh, struct passwd *pw)
+ 	    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(struct passwd *pw)
+@@ -2090,11 +2138,6 @@ load_public_identity_files(struct passwd *pw)
+ 	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));
+@@ -2107,33 +2150,46 @@ load_public_identity_files(struct passwd *pw)
+ 	    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,
+ 		    pw->pw_dir, host, options.user, pw->pw_name);
+ 		free(cp);
+diff --git a/ssh_config.5 b/ssh_config.5
+index 06a32d31..4b2763bd 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -986,6 +986,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/openssh-8.2p1-visibility.patch b/openssh-8.2p1-visibility.patch
new file mode 100644
index 0000000..89c35ef
--- /dev/null
+++ b/openssh-8.2p1-visibility.patch
@@ -0,0 +1,40 @@
+diff --git a/regress/misc/sk-dummy/sk-dummy.c b/regress/misc/sk-dummy/sk-dummy.c
+index dca158de..afdcb1d2 100644
+--- a/regress/misc/sk-dummy/sk-dummy.c
++++ b/regress/misc/sk-dummy/sk-dummy.c
+@@ -71,7 +71,7 @@ skdebug(const char *func, const char *fmt, ...)
+ #endif
+ }
+ 
+-uint32_t
++uint32_t __attribute__((visibility("default")))
+ sk_api_version(void)
+ {
+ 	return SSH_SK_VERSION_MAJOR;
+@@ -220,7 +220,7 @@ check_options(struct sk_option **options)
+ 	return 0;
+ }
+ 
+-int
++int __attribute__((visibility("default")))
+ sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
+     const char *application, uint8_t flags, const char *pin,
+     struct sk_option **options, struct sk_enroll_response **enroll_response)
+@@ -467,7 +467,7 @@ sig_ed25519(const uint8_t *message, size_t message_len,
+ 	return ret;
+ }
+ 
+-int
++int __attribute__((visibility("default")))
+ sk_sign(uint32_t alg, const uint8_t *data, size_t datalen,
+     const char *application, const uint8_t *key_handle, size_t key_handle_len,
+     uint8_t flags, const char *pin, struct sk_option **options,
+@@ -518,7 +518,7 @@ sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
+ 	return ret;
+ }
+ 
+-int
++int __attribute__((visibility("default")))
+ sk_load_resident_keys(const char *pin, struct sk_option **options,
+     struct sk_resident_key ***rks, size_t *nrks)
+ {
diff --git a/openssh-8.2p1-x11-without-ipv6.patch b/openssh-8.2p1-x11-without-ipv6.patch
new file mode 100644
index 0000000..18b0376
--- /dev/null
+++ b/openssh-8.2p1-x11-without-ipv6.patch
@@ -0,0 +1,30 @@
+diff --git a/channels.c b/channels.c
+--- a/channels.c
++++ b/channels.c
+@@ -3933,16 +3933,26 @@ x11_create_display_inet(int x11_display_
+ 			if (ai->ai_family == AF_INET6)
+ 				sock_set_v6only(sock);
+ 			if (x11_use_localhost)
+ 				set_reuseaddr(sock);
+ 			if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+ 				debug2("%s: bind port %d: %.100s", __func__,
+ 				    port, strerror(errno));
+ 				close(sock);
++
++				/* do not remove successfully opened
++				 * sockets if the request failed because
++				 * the protocol IPv4/6 is not available
++				 * (e.g. IPv6 may be disabled while being
++				 * supported)
++				 */
++				if (EADDRNOTAVAIL == errno)
++    					continue;
++
+ 				for (n = 0; n < num_socks; n++)
+ 					close(socks[n]);
+ 				num_socks = 0;
+ 				break;
+ 			}
+ 			socks[num_socks++] = sock;
+ 			if (num_socks == NUM_SOCKS)
+ 				break;
diff --git a/openssh-8.4p1-ssh-copy-id.patch b/openssh-8.4p1-ssh-copy-id.patch
new file mode 100644
index 0000000..7bc4c7d
--- /dev/null
+++ b/openssh-8.4p1-ssh-copy-id.patch
@@ -0,0 +1,130 @@
+From 66f16e5425eb881570e82bfef7baeac2e7accc0a Mon Sep 17 00:00:00 2001
+From: Oleg <Fallmay@users.noreply.github.com>
+Date: Thu, 1 Oct 2020 12:09:08 +0300
+Subject: [PATCH] Fix `EOF: command not found` error in ssh-copy-id
+
+---
+ contrib/ssh-copy-id | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
+index 392f64f94..a76907717 100644
+--- a/contrib/ssh-copy-id
++++ b/contrib/ssh-copy-id
+@@ -247,7 +247,7 @@ installkeys_sh() {
+   #    the -z `tail ...` checks for a trailing newline. The echo adds one if was missing
+   #    the cat adds the keys we're getting via STDIN
+   #    and if available restorecon is used to restore the SELinux context
+-  INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF)
++  INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF
+ 	cd;
+ 	umask 077;
+ 	mkdir -p $(dirname "${AUTH_KEY_FILE}") &&
+@@ -258,6 +258,7 @@ installkeys_sh() {
+ 	  restorecon -F .ssh ${AUTH_KEY_FILE};
+ 	fi
+ EOF
++  )
+ 
+   # to defend against quirky remote shells: use 'exec sh -c' to get POSIX;
+   printf "exec sh -c '%s'" "${INSTALLKEYS_SH}"
+
+From de59a431cdec833e3ec15691dd950402b4c052cf Mon Sep 17 00:00:00 2001
+From: Philip Hands <phil@hands.com>
+Date: Sat, 3 Oct 2020 00:20:07 +0200
+Subject: [PATCH] un-nest $() to make ksh cheerful
+
+---
+ ssh-copy-id | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+From 02ac2c3c3db5478a440dfb1b90d15f686f2cbfc6 Mon Sep 17 00:00:00 2001
+From: Philip Hands <phil@hands.com>
+Date: Fri, 2 Oct 2020 21:30:10 +0200
+Subject: [PATCH] ksh doesn't grok 'local'
+
+and AFAICT it's not actually doing anything useful in the code, so let's
+see how things go without it.
+---
+ ssh-copy-id | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
+index a769077..11c9463 100755
+--- a/contrib/ssh-copy-id
++++ b/contrib/ssh-copy-id
+@@ -76,7 +76,7 @@ quote() {
+ }
+ 
+ use_id_file() {
+-  local L_ID_FILE="$1"
++  L_ID_FILE="$1"
+ 
+   if [ -z "$L_ID_FILE" ] ; then
+     printf '%s: ERROR: no ID file found\n' "$0"
+@@ -94,7 +94,7 @@ use_id_file() {
+   # check that the files are readable
+   for f in "$PUB_ID_FILE" ${PRIV_ID_FILE:+"$PRIV_ID_FILE"} ; do
+     ErrMSG=$( { : < "$f" ; } 2>&1 ) || {
+-      local L_PRIVMSG=""
++      L_PRIVMSG=""
+       [ "$f" = "$PRIV_ID_FILE" ] && L_PRIVMSG="	(to install the contents of '$PUB_ID_FILE' anyway, look at the -f option)"
+       printf "\\n%s: ERROR: failed to open ID file '%s': %s\\n" "$0" "$f" "$(printf '%s\n%s\n' "$ErrMSG" "$L_PRIVMSG" | sed -e 's/.*: *//')"
+       exit 1
+@@ -169,7 +169,7 @@ fi
+ # populate_new_ids() uses several global variables ($USER_HOST, $SSH_OPTS ...)
+ # and has the side effect of setting $NEW_IDS
+ populate_new_ids() {
+-  local L_SUCCESS="$1"
++  L_SUCCESS="$1"
+ 
+   # shellcheck disable=SC2086
+   if [ "$FORCED" ] ; then
+@@ -181,13 +181,12 @@ populate_new_ids() {
+   eval set -- "$SSH_OPTS"
+ 
+   umask 0177
+-  local L_TMP_ID_FILE
+   L_TMP_ID_FILE=$(mktemp ~/.ssh/ssh-copy-id_id.XXXXXXXXXX)
+   if test $? -ne 0 || test "x$L_TMP_ID_FILE" = "x" ; then
+     printf '%s: ERROR: mktemp failed\n' "$0" >&2
+     exit 1
+   fi
+-  local L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\""
++  L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\""
+   # shellcheck disable=SC2064
+   trap "$L_CLEANUP" EXIT TERM INT QUIT
+   printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2
+@@ -237,7 +236,7 @@ populate_new_ids() {
+ #    produce a one-liner to add the keys to remote authorized_keys file
+ #    optionally takes an alternative path for authorized_keys
+ installkeys_sh() {
+-  local AUTH_KEY_FILE=${1:-.ssh/authorized_keys}
++  AUTH_KEY_FILE=${1:-.ssh/authorized_keys}
+ 
+   # In setting INSTALLKEYS_SH:
+   #    the tr puts it all on one line (to placate tcsh)
+-- 
+
+diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
+index 11c9463..ee3f637 100755
+--- a/contrib/ssh-copy-id
++++ b/contrib/ssh-copy-id
+@@ -237,6 +237,7 @@ populate_new_ids() {
+ #    optionally takes an alternative path for authorized_keys
+ installkeys_sh() {
+   AUTH_KEY_FILE=${1:-.ssh/authorized_keys}
++  AUTH_KEY_DIR=$(dirname "${AUTH_KEY_FILE}")
+ 
+   # In setting INSTALLKEYS_SH:
+   #    the tr puts it all on one line (to placate tcsh)
+@@ -249,7 +250,7 @@ installkeys_sh() {
+   INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF
+ 	cd;
+ 	umask 077;
+-	mkdir -p $(dirname "${AUTH_KEY_FILE}") &&
++	mkdir -p "${AUTH_KEY_DIR}" &&
+ 	  { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || echo >> ${AUTH_KEY_FILE} || exit 1; } &&
+ 	  cat >> ${AUTH_KEY_FILE} ||
+ 	  exit 1;
+-- 
diff --git a/openssh.rpmlintrc b/openssh.rpmlintrc
new file mode 100644
index 0000000..0a7a50e
--- /dev/null
+++ b/openssh.rpmlintrc
@@ -0,0 +1,21 @@
+# I do not know about any better place where to put profile files
+addFilter(r'openssh-askpass.x86_64: W: non-conffile-in-etc /etc/profile.d/gnome-ssh-askpass.c?sh')
+
+# The ssh-keysign is not supposed to have standard permissions
+addFilter(r'openssh.x86_64: E: non-standard-executable-perm /usr/libexec/openssh/ssh-keysign 2555')
+addFilter(r'openssh.x86_64: E: setgid-binary /usr/libexec/openssh/ssh-keysign ssh_keys 2555')
+addFilter(r'openssh.x86_64: W: non-standard-gid /usr/libexec/openssh/ssh-keysign ssh_keys')
+
+# The -cavs subpackage is internal without documentation
+# The -askpass is not intended to be used directly so it is missing documentation
+addFilter(r'openssh-(askpass|cavs).x86_64: W: no-documentation')
+
+# sshd config and sysconfig is not supposed to be world readable
+addFilter(r'non-readable /etc/(ssh/sshd_config|sysconfig/sshd)')
+
+# The /var/empty/sshd is supposed to have the given permissions
+addFilter(r'non-standard-dir-perm /var/empty/sshd 711')
+addFilter(r'non-standard-dir-in-var empty')
+
+# Spelling false-positives
+addFilter(r'spelling-error (Summary\(en_US\)|.* en_US) (mls|su|sudo|rlogin|rsh|untrusted) ')
diff --git a/openssh.spec b/openssh.spec
new file mode 100644
index 0000000..df39c02
--- /dev/null
+++ b/openssh.spec
@@ -0,0 +1,2862 @@
+# Do we want SELinux & Audit
+%if 0%{?!noselinux:1}
+%global WITH_SELINUX 1
+%else
+%global WITH_SELINUX 0
+%endif
+
+%global _hardened_build 1
+
+# OpenSSH privilege separation requires a user & group ID
+%global sshd_uid    74
+%global sshd_gid    74
+
+# Do we want to disable building of gnome-askpass? (1=yes 0=no)
+%global no_gnome_askpass 0
+
+# Do we want to link against a static libcrypto? (1=yes 0=no)
+%global static_libcrypto 0
+
+# Use GTK2 instead of GNOME in gnome-ssh-askpass
+%global gtk2 1
+
+# Build position-independent executables (requires toolchain support)?
+%global pie 1
+
+# Do we want kerberos5 support (1=yes 0=no)
+%global kerberos5 1
+
+# Do we want libedit support
+%global libedit 1
+
+# Whether to build pam_ssh_agent_auth
+%if 0%{?!nopam:1}
+%global pam_ssh_agent 1
+%else
+%global pam_ssh_agent 0
+%endif
+
+# Reserve options to override askpass settings with:
+# rpm -ba|--rebuild --define 'skip_xxx 1'
+%{?skip_gnome_askpass:%global no_gnome_askpass 1}
+
+# Add option to build without GTK2 for older platforms with only GTK+.
+# Red Hat Linux <= 7.2 and Red Hat Advanced Server 2.1 are examples.
+# rpm -ba|--rebuild --define 'no_gtk2 1'
+%{?no_gtk2:%global gtk2 0}
+
+# Options for static OpenSSL link:
+# rpm -ba|--rebuild --define "static_openssl 1"
+%{?static_openssl:%global static_libcrypto 1}
+
+# Do not forget to bump pam_ssh_agent_auth release if you rewind the main package release to 1
+%global openssh_ver 8.4p1
+%global openssh_rel 2
+%global pam_ssh_agent_ver 0.10.4
+%global pam_ssh_agent_rel 1
+
+Summary: An open source implementation of SSH protocol version 2
+Name: openssh
+Version: %{openssh_ver}
+Release: %{openssh_rel}%{?dist}
+URL: http://www.openssh.com/portable.html
+#URL1: https://github.com/jbeverly/pam_ssh_agent_auth/
+Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz
+Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc
+Source2: sshd.pam
+Source3: DJM-GPG-KEY.gpg
+Source4: https://github.com/jbeverly/pam_ssh_agent_auth/archive/pam_ssh_agent_auth-%{pam_ssh_agent_ver}.tar.gz
+Source5: pam_ssh_agent-rmheaders
+Source6: ssh-keycat.pam
+Source7: sshd.sysconfig
+Source9: sshd@.service
+Source10: sshd.socket
+Source11: sshd.service
+Source12: sshd-keygen@.service
+Source13: sshd-keygen
+Source14: sshd.tmpfiles
+Source15: sshd-keygen.target
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=2581
+Patch100: openssh-6.7p1-coverity.patch
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1402
+# https://bugzilla.redhat.com/show_bug.cgi?id=1171248
+# record pfs= field in CRYPTO_SESSION audit event
+Patch200: openssh-7.6p1-audit.patch
+# Audit race condition in forked child (#1310684)
+Patch201: openssh-7.1p2-audit-race-condition.patch
+
+# --- pam_ssh-agent ---
+# make it build reusing the openssh sources
+Patch300: pam_ssh_agent_auth-0.9.3-build.patch
+# check return value of seteuid()
+# https://sourceforge.net/p/pamsshagentauth/bugs/23/
+Patch301: pam_ssh_agent_auth-0.10.3-seteuid.patch
+# explicitly make pam callbacks visible
+Patch302: pam_ssh_agent_auth-0.9.2-visibility.patch
+# update to current version of agent structure
+Patch305: pam_ssh_agent_auth-0.9.3-agent_structure.patch
+# remove prefixes to be able to build against current openssh library
+Patch306: pam_ssh_agent_auth-0.10.2-compat.patch
+# Fix NULL dereference from getpwuid() return value
+# https://sourceforge.net/p/pamsshagentauth/bugs/22/
+Patch307: pam_ssh_agent_auth-0.10.2-dereference.patch
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1641 (WONTFIX)
+Patch400: openssh-7.8p1-role-mls.patch
+#https://bugzilla.redhat.com/show_bug.cgi?id=781634
+Patch404: openssh-6.6p1-privsep-selinux.patch
+#?
+Patch502: openssh-6.6p1-keycat.patch
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1644
+Patch601: openssh-6.6p1-allow-ip-opts.patch
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1893 (WONTFIX)
+Patch604: openssh-6.6p1-keyperm.patch
+#(drop?) https://bugzilla.mindrot.org/show_bug.cgi?id=1925
+Patch606: openssh-5.9p1-ipv6man.patch
+#?
+Patch607: openssh-5.8p2-sigpipe.patch
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1789
+Patch609: openssh-7.2p2-x11.patch
+
+#?
+Patch700: openssh-7.7p1-fips.patch
+#?
+Patch702: openssh-5.1p1-askpass-progress.patch
+#https://bugzilla.redhat.com/show_bug.cgi?id=198332
+Patch703: openssh-4.3p2-askpass-grab-info.patch
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1635 (WONTFIX)
+Patch707: openssh-7.7p1-redhat.patch
+# warn users for unsupported UsePAM=no (#757545)
+Patch711: openssh-7.8p1-UsePAM-warning.patch
+# make aes-ctr ciphers use EVP engines such as AES-NI from OpenSSL
+Patch712: openssh-6.3p1-ctr-evp-fast.patch
+# add cavs test binary for the aes-ctr
+Patch713: openssh-6.6p1-ctr-cavstest.patch
+# add SSH KDF CAVS test driver
+Patch714: openssh-6.7p1-kdf-cavs.patch
+
+# GSSAPI Key Exchange (RFC 4462 + draft-ietf-curdle-gss-keyex-sha2-08)
+# from https://github.com/openssh-gsskex/openssh-gsskex/tree/fedora/master
+Patch800: openssh-8.0p1-gssapi-keyex.patch
+#http://www.mail-archive.com/kerberos@mit.edu/msg17591.html
+Patch801: openssh-6.6p1-force_krb.patch
+# add new option GSSAPIEnablek5users and disable using ~/.k5users by default (#1169843)
+# CVE-2014-9278
+Patch802: openssh-6.6p1-GSSAPIEnablek5users.patch
+# Improve ccache handling in openssh (#991186, #1199363, #1566494)
+# https://bugzilla.mindrot.org/show_bug.cgi?id=2775
+Patch804: openssh-7.7p1-gssapi-new-unique.patch
+# Respect k5login_directory option in krk5.conf (#1328243)
+Patch805: openssh-7.2p2-k5login_directory.patch
+
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1780
+Patch901: openssh-6.6p1-kuserok.patch
+# Use tty allocation for a remote scp (#985650)
+Patch906: openssh-6.4p1-fromto-remote.patch
+# privsep_preauth: use SELinux context from selinux-policy (#1008580)
+Patch916: openssh-6.6.1p1-selinux-contexts.patch
+# log via monitor in chroots without /dev/log (#2681)
+Patch918: openssh-6.6.1p1-log-in-chroot.patch
+# scp file into non-existing directory (#1142223)
+Patch919: openssh-6.6.1p1-scp-non-existing-directory.patch
+# apply upstream patch and make sshd -T more consistent (#1187521)
+Patch922: openssh-6.8p1-sshdT-output.patch
+# Add sftp option to force mode of created files (#1191055)
+Patch926: openssh-6.7p1-sftp-force-permission.patch
+# make s390 use /dev/ crypto devices -- ignore closefrom
+Patch939: openssh-7.2p2-s390-closefrom.patch
+# Move MAX_DISPLAYS to a configuration option (#1341302)
+Patch944: openssh-7.3p1-x11-max-displays.patch
+# Help systemd to track the running service
+Patch948: openssh-7.4p1-systemd.patch
+# Pass inetd flags for SELinux down to openbsd compat level
+Patch949: openssh-7.6p1-cleanup-selinux.patch
+# Sandbox adjustments for s390 and audit
+Patch950: openssh-7.5p1-sandbox.patch
+# PKCS#11 URIs (upstream #2817, 2nd iteration)
+# https://github.com/Jakuje/openssh-portable/commits/jjelen-pkcs11
+# git show > ~/devel/fedora/openssh/openssh-8.0p1-pkcs11-uri.patch
+Patch951: openssh-8.0p1-pkcs11-uri.patch
+# Unbreak scp between two IPv6 hosts (#1620333)
+Patch953: openssh-7.8p1-scp-ipv6.patch
+# ssh-copy-id is unmaintained: Aggreagete patches
+# https://gitlab.com/phil_hands/ssh-copy-id/-/merge_requests/2
+Patch958: openssh-7.9p1-ssh-copy-id.patch
+# Mention crypto-policies in manual pages (#1668325)
+Patch962: openssh-8.0p1-crypto-policies.patch
+# Use OpenSSL high-level API to produce and verify signatures (#1707485)
+Patch963: openssh-8.0p1-openssl-evp.patch
+# Use OpenSSL KDF (#1631761)
+Patch964: openssh-8.0p1-openssl-kdf.patch
+# sk-dummy.so built with -fvisibility=hidden does not work
+Patch965: openssh-8.2p1-visibility.patch
+# Do not break X11 without IPv6
+Patch966: openssh-8.2p1-x11-without-ipv6.patch
+Patch967: openssh-8.4p1-ssh-copy-id.patch
+
+License: BSD
+Requires: /sbin/nologin
+
+%if ! %{no_gnome_askpass}
+%if %{gtk2}
+BuildRequires: gtk2-devel
+BuildRequires: libX11-devel
+%else
+BuildRequires: gnome-libs-devel
+%endif
+%endif
+
+BuildRequires: autoconf, automake, perl-interpreter, perl-generators, zlib-devel
+BuildRequires: audit-libs-devel >= 2.0.5
+BuildRequires: util-linux, groff
+BuildRequires: pam-devel
+BuildRequires: openssl-devel >= 0.9.8j
+BuildRequires: perl-podlators
+BuildRequires: systemd-devel
+BuildRequires: gcc make
+BuildRequires: p11-kit-devel
+BuildRequires: libfido2-devel
+Recommends: p11-kit
+Obsoletes: openssh-ldap <= 8.3p1-3
+
+%if %{kerberos5}
+BuildRequires: krb5-devel
+%endif
+
+%if %{libedit}
+BuildRequires: libedit-devel ncurses-devel
+%endif
+
+%if %{WITH_SELINUX}
+Requires: libselinux >= 2.3-5
+BuildRequires: libselinux-devel >= 2.3-5
+Requires: audit-libs >= 1.0.8
+BuildRequires: audit-libs >= 1.0.8
+%endif
+
+BuildRequires: xauth
+# for tarball signature verification
+BuildRequires: gnupg2
+
+%package clients
+Summary: An open source SSH client applications
+Requires: openssh = %{version}-%{release}
+Requires: crypto-policies >= 20200610-1
+
+%package server
+Summary: An open source SSH server daemon
+Requires: openssh = %{version}-%{release}
+Requires(pre): /usr/sbin/useradd
+Requires: pam >= 1.0.1-3
+Requires: crypto-policies >= 20200610-1
+%{?systemd_requires}
+
+%package keycat
+Summary: A mls keycat backend for openssh
+Requires: openssh = %{version}-%{release}
+
+%package askpass
+Summary: A passphrase dialog for OpenSSH and X
+Requires: openssh = %{version}-%{release}
+
+%package cavs
+Summary: CAVS tests for FIPS validation
+Requires: openssh = %{version}-%{release}
+
+%package -n pam_ssh_agent_auth
+Summary: PAM module for authentication with ssh-agent
+Version: %{pam_ssh_agent_ver}
+Release: %{pam_ssh_agent_rel}.%{openssh_rel}%{?dist}.2
+License: BSD
+
+%description
+SSH (Secure SHell) is a program for logging into and executing
+commands on a remote machine. SSH is intended to replace rlogin and
+rsh, and to provide secure encrypted communications between two
+untrusted hosts over an insecure network. X11 connections and
+arbitrary TCP/IP ports can also be forwarded over the secure channel.
+
+OpenSSH is OpenBSD's version of the last free version of SSH, bringing
+it up to date in terms of security and features.
+
+This package includes the core files necessary for both the OpenSSH
+client and server. To make this package useful, you should also
+install openssh-clients, openssh-server, or both.
+
+%description clients
+OpenSSH is a free version of SSH (Secure SHell), a program for logging
+into and executing commands on a remote machine. This package includes
+the clients necessary to make encrypted connections to SSH servers.
+
+%description server
+OpenSSH is a free version of SSH (Secure SHell), a program for logging
+into and executing commands on a remote machine. This package contains
+the secure shell daemon (sshd). The sshd daemon allows SSH clients to
+securely connect to your SSH server.
+
+%description keycat
+OpenSSH mls keycat is backend for using the authorized keys in the
+openssh in the mls mode.
+
+%description askpass
+OpenSSH is a free version of SSH (Secure SHell), a program for logging
+into and executing commands on a remote machine. This package contains
+an X11 passphrase dialog for OpenSSH.
+
+%description cavs
+This package contains test binaries and scripts to make FIPS validation
+easier. Now contains CTR and KDF CAVS test driver.
+
+%description -n pam_ssh_agent_auth
+This package contains a PAM module which can be used to authenticate
+users using ssh keys stored in a ssh-agent. Through the use of the
+forwarding of ssh-agent connection it also allows to authenticate with
+remote ssh-agent instance.
+
+The module is most useful for su and sudo service stacks.
+
+%prep
+gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0}
+%setup -q -a 4
+
+%if %{pam_ssh_agent}
+pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
+%patch300 -p2 -b .psaa-build
+%patch301 -p2 -b .psaa-seteuid
+%patch302 -p2 -b .psaa-visibility
+%patch306 -p2 -b .psaa-compat
+%patch305 -p2 -b .psaa-agent
+%patch307 -p2 -b .psaa-deref
+# Remove duplicate headers and library files
+rm -f $(cat %{SOURCE5})
+popd
+%endif
+
+%patch400 -p1 -b .role-mls
+%patch404 -p1 -b .privsep-selinux
+
+%patch502 -p1 -b .keycat
+
+%patch601 -p1 -b .ip-opts
+%patch604 -p1 -b .keyperm
+%patch606 -p1 -b .ipv6man
+%patch607 -p1 -b .sigpipe
+%patch609 -p1 -b .x11
+%patch702 -p1 -b .progress
+%patch703 -p1 -b .grab-info
+%patch707 -p1 -b .redhat
+%patch711 -p1 -b .log-usepam-no
+%patch712 -p1 -b .evp-ctr
+%patch713 -p1 -b .ctr-cavs
+%patch714 -p1 -b .kdf-cavs
+# 
+%patch800 -p1 -b .gsskex
+%patch801 -p1 -b .force_krb
+%patch804 -p1 -b .ccache_name
+%patch805 -p1 -b .k5login
+# 
+%patch901 -p1 -b .kuserok
+%patch906 -p1 -b .fromto-remote
+%patch916 -p1 -b .contexts
+%patch918 -p1 -b .log-in-chroot
+%patch919 -p1 -b .scp
+%patch802 -p1 -b .GSSAPIEnablek5users
+%patch922 -p1 -b .sshdt
+%patch926 -p1 -b .sftp-force-mode
+%patch939 -p1 -b .s390-dev
+%patch944 -p1 -b .x11max
+%patch948 -p1 -b .systemd
+%patch949 -p1 -b .refactor
+%patch950 -p1 -b .sandbox
+%patch951 -p1 -b .pkcs11-uri
+%patch953 -p1 -b .scp-ipv6
+%patch958 -p1 -b .ssh-copy-id
+%patch962 -p1 -b .crypto-policies
+%patch963 -p1 -b .openssl-evp
+%patch964 -p1 -b .openssl-kdf
+%patch965 -p1 -b .visibility
+%patch966 -p1 -b .x11-ipv6
+%patch967 -p1 -b .ssh-copy-id
+
+%patch200 -p1 -b .audit
+%patch201 -p1 -b .audit-race
+%patch700 -p1 -b .fips
+
+%patch100 -p1 -b .coverity
+
+autoreconf
+pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
+autoreconf
+popd
+
+%build
+# the -fvisibility=hidden is needed for clean build of the pam_ssh_agent_auth
+# it is needed for lib(open)ssh build too since it is linked to the pam module too
+CFLAGS="$RPM_OPT_FLAGS -fvisibility=hidden"; export CFLAGS
+%if %{pie}
+%ifarch s390 s390x sparc sparcv9 sparc64
+CFLAGS="$CFLAGS -fPIC"
+%else
+CFLAGS="$CFLAGS -fpic"
+%endif
+SAVE_LDFLAGS="$LDFLAGS"
+LDFLAGS="$LDFLAGS -pie -z relro -z now"
+
+export CFLAGS
+export LDFLAGS
+
+%endif
+%if %{kerberos5}
+if test -r /etc/profile.d/krb5-devel.sh ; then
+	source /etc/profile.d/krb5-devel.sh
+fi
+krb5_prefix=`krb5-config --prefix`
+if test "$krb5_prefix" != "%{_prefix}" ; then
+	CPPFLAGS="$CPPFLAGS -I${krb5_prefix}/include -I${krb5_prefix}/include/gssapi"; export CPPFLAGS
+	CFLAGS="$CFLAGS -I${krb5_prefix}/include -I${krb5_prefix}/include/gssapi"
+	LDFLAGS="$LDFLAGS -L${krb5_prefix}/%{_lib}"; export LDFLAGS
+else
+	krb5_prefix=
+	CPPFLAGS="-I%{_includedir}/gssapi"; export CPPFLAGS
+	CFLAGS="$CFLAGS -I%{_includedir}/gssapi"
+fi
+%endif
+
+%configure \
+	--sysconfdir=%{_sysconfdir}/ssh \
+	--libexecdir=%{_libexecdir}/openssh \
+	--datadir=%{_datadir}/openssh \
+	--with-default-path=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin \
+	--with-superuser-path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \
+	--with-privsep-path=%{_var}/empty/sshd \
+	--disable-strip \
+	--without-zlib-version-check \
+	--with-ssl-engine \
+	--with-ipaddr-display \
+	--with-pie=no \
+	--without-hardening `# The hardening flags are configured by system` \
+	--with-systemd \
+	--with-default-pkcs11-provider=yes \
+	--with-security-key-builtin=yes \
+	--with-pam \
+%if %{WITH_SELINUX}
+	--with-selinux --with-audit=linux \
+	--with-sandbox=seccomp_filter \
+%endif
+%if %{kerberos5}
+	--with-kerberos5${krb5_prefix:+=${krb5_prefix}} \
+%else
+	--without-kerberos5 \
+%endif
+%if %{libedit}
+	--with-libedit
+%else
+	--without-libedit
+%endif
+
+%if %{static_libcrypto}
+perl -pi -e "s|-lcrypto|%{_libdir}/libcrypto.a|g" Makefile
+%endif
+
+%make_build
+
+# Define a variable to toggle gnome1/gtk2 building.  This is necessary
+# because RPM doesn't handle nested %%if statements.
+%if %{gtk2}
+	gtk2=yes
+%else
+	gtk2=no
+%endif
+
+%if ! %{no_gnome_askpass}
+pushd contrib
+if [ $gtk2 = yes ] ; then
+	CFLAGS="$CFLAGS %{?__global_ldflags}" \
+	    make gnome-ssh-askpass2
+	mv gnome-ssh-askpass2 gnome-ssh-askpass
+else
+	CFLAGS="$CFLAGS %{?__global_ldflags}"
+	    make gnome-ssh-askpass1
+	mv gnome-ssh-askpass1 gnome-ssh-askpass
+fi
+popd
+%endif
+
+%if %{pam_ssh_agent}
+pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
+LDFLAGS="$SAVE_LDFLAGS"
+%configure --with-selinux \
+	--libexecdir=/%{_libdir}/security \
+	--with-mantype=man \
+	--without-openssl-header-check `# The check is broken`
+%make_build
+popd
+%endif
+
+%check
+#to run tests use "--with check"
+%if %{?_with_check:1}%{!?_with_check:0}
+make tests
+%endif
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p -m755 $RPM_BUILD_ROOT%{_sysconfdir}/ssh
+mkdir -p -m755 $RPM_BUILD_ROOT%{_sysconfdir}/ssh/ssh_config.d
+mkdir -p -m755 $RPM_BUILD_ROOT%{_sysconfdir}/ssh/sshd_config.d
+mkdir -p -m755 $RPM_BUILD_ROOT%{_libexecdir}/openssh
+mkdir -p -m755 $RPM_BUILD_ROOT%{_var}/empty/sshd
+%make_install
+
+install -d $RPM_BUILD_ROOT/etc/pam.d/
+install -d $RPM_BUILD_ROOT/etc/sysconfig/
+install -d $RPM_BUILD_ROOT%{_libexecdir}/openssh
+install -m644 %{SOURCE2} $RPM_BUILD_ROOT/etc/pam.d/sshd
+install -m644 %{SOURCE6} $RPM_BUILD_ROOT/etc/pam.d/ssh-keycat
+install -m644 %{SOURCE7} $RPM_BUILD_ROOT/etc/sysconfig/sshd
+install -m644 ssh_config_redhat $RPM_BUILD_ROOT/etc/ssh/ssh_config.d/50-redhat.conf
+install -m644 sshd_config_redhat $RPM_BUILD_ROOT/etc/ssh/sshd_config.d/50-redhat.conf
+install -d -m755 $RPM_BUILD_ROOT/%{_unitdir}
+install -m644 %{SOURCE9} $RPM_BUILD_ROOT/%{_unitdir}/sshd@.service
+install -m644 %{SOURCE10} $RPM_BUILD_ROOT/%{_unitdir}/sshd.socket
+install -m644 %{SOURCE11} $RPM_BUILD_ROOT/%{_unitdir}/sshd.service
+install -m644 %{SOURCE12} $RPM_BUILD_ROOT/%{_unitdir}/sshd-keygen@.service
+install -m644 %{SOURCE15} $RPM_BUILD_ROOT/%{_unitdir}/sshd-keygen.target
+install -m744 %{SOURCE13} $RPM_BUILD_ROOT/%{_libexecdir}/openssh/sshd-keygen
+install -m755 contrib/ssh-copy-id $RPM_BUILD_ROOT%{_bindir}/
+install contrib/ssh-copy-id.1 $RPM_BUILD_ROOT%{_mandir}/man1/
+install -m644 -D %{SOURCE14} $RPM_BUILD_ROOT%{_tmpfilesdir}/%{name}.conf
+
+%if ! %{no_gnome_askpass}
+install contrib/gnome-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/gnome-ssh-askpass
+%endif
+
+%if ! %{no_gnome_askpass}
+ln -s gnome-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/ssh-askpass
+install -m 755 -d $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/
+install -m 755 contrib/redhat/gnome-ssh-askpass.csh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/
+install -m 755 contrib/redhat/gnome-ssh-askpass.sh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/
+%endif
+
+%if %{no_gnome_askpass}
+rm -f $RPM_BUILD_ROOT/etc/profile.d/gnome-ssh-askpass.*
+%endif
+
+perl -pi -e "s|$RPM_BUILD_ROOT||g" $RPM_BUILD_ROOT%{_mandir}/man*/*
+
+%if %{pam_ssh_agent}
+pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
+%make_install
+popd
+%endif
+%pre
+getent group ssh_keys >/dev/null || groupadd -r ssh_keys || :
+
+%pre server
+getent group sshd >/dev/null || groupadd -g %{sshd_uid} -r sshd || :
+getent passwd sshd >/dev/null || \
+  useradd -c "Privilege-separated SSH" -u %{sshd_uid} -g sshd \
+  -s /sbin/nologin -r -d /var/empty/sshd sshd 2> /dev/null || :
+
+%post server
+%systemd_post sshd.service sshd.socket
+# Migration scriptlet for Fedora 31 and 32 installations to sshd_config
+# drop-in directory (in F32+).
+# Do this only if the file generated by anaconda exists, contains our config
+# directive and sshd_config contains include directive as shipped in our package
+%global sysconfig_anaconda /etc/sysconfig/sshd-permitrootlogin
+test -f %{sysconfig_anaconda} && \
+  test ! -f /etc/ssh/sshd_config.d/01-permitrootlogin.conf && \
+  grep -q '^PERMITROOTLOGIN="-oPermitRootLogin=yes"' %{sysconfig_anaconda} && \
+  grep -q '^Include /etc/ssh/sshd_config.d/\*.conf' /etc/ssh/sshd_config && \
+  echo "PermitRootLogin yes" >> /etc/ssh/sshd_config.d/25-permitrootlogin.conf && \
+  rm %{sysconfig_anaconda} || :
+
+%preun server
+%systemd_preun sshd.service sshd.socket
+
+%postun server
+%systemd_postun_with_restart sshd.service
+
+%files
+%license LICENCE
+%doc CREDITS ChangeLog OVERVIEW PROTOCOL* README README.platform README.privsep README.tun README.dns TODO
+%attr(0755,root,root) %dir %{_sysconfdir}/ssh
+%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/moduli
+%attr(0755,root,root) %{_bindir}/ssh-keygen
+%attr(0644,root,root) %{_mandir}/man1/ssh-keygen.1*
+%attr(0755,root,root) %dir %{_libexecdir}/openssh
+%attr(2555,root,ssh_keys) %{_libexecdir}/openssh/ssh-keysign
+%attr(0644,root,root) %{_mandir}/man8/ssh-keysign.8*
+
+%files clients
+%attr(0755,root,root) %{_bindir}/ssh
+%attr(0644,root,root) %{_mandir}/man1/ssh.1*
+%attr(0755,root,root) %{_bindir}/scp
+%attr(0644,root,root) %{_mandir}/man1/scp.1*
+%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/ssh_config
+%dir %attr(0755,root,root) %{_sysconfdir}/ssh/ssh_config.d/
+%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/ssh_config.d/50-redhat.conf
+%attr(0644,root,root) %{_mandir}/man5/ssh_config.5*
+%attr(0755,root,root) %{_bindir}/ssh-agent
+%attr(0755,root,root) %{_bindir}/ssh-add
+%attr(0755,root,root) %{_bindir}/ssh-keyscan
+%attr(0755,root,root) %{_bindir}/sftp
+%attr(0755,root,root) %{_bindir}/ssh-copy-id
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-pkcs11-helper
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-sk-helper
+%attr(0644,root,root) %{_mandir}/man1/ssh-agent.1*
+%attr(0644,root,root) %{_mandir}/man1/ssh-add.1*
+%attr(0644,root,root) %{_mandir}/man1/ssh-keyscan.1*
+%attr(0644,root,root) %{_mandir}/man1/sftp.1*
+%attr(0644,root,root) %{_mandir}/man1/ssh-copy-id.1*
+%attr(0644,root,root) %{_mandir}/man8/ssh-pkcs11-helper.8*
+%attr(0644,root,root) %{_mandir}/man8/ssh-sk-helper.8*
+
+%files server
+%dir %attr(0711,root,root) %{_var}/empty/sshd
+%attr(0755,root,root) %{_sbindir}/sshd
+%attr(0755,root,root) %{_libexecdir}/openssh/sftp-server
+%attr(0755,root,root) %{_libexecdir}/openssh/sshd-keygen
+%attr(0644,root,root) %{_mandir}/man5/sshd_config.5*
+%attr(0644,root,root) %{_mandir}/man5/moduli.5*
+%attr(0644,root,root) %{_mandir}/man8/sshd.8*
+%attr(0644,root,root) %{_mandir}/man8/sftp-server.8*
+%attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/sshd_config
+%dir %attr(0700,root,root) %{_sysconfdir}/ssh/sshd_config.d/
+%attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/sshd_config.d/50-redhat.conf
+%attr(0644,root,root) %config(noreplace) /etc/pam.d/sshd
+%attr(0640,root,root) %config(noreplace) /etc/sysconfig/sshd
+%attr(0644,root,root) %{_unitdir}/sshd.service
+%attr(0644,root,root) %{_unitdir}/sshd@.service
+%attr(0644,root,root) %{_unitdir}/sshd.socket
+%attr(0644,root,root) %{_unitdir}/sshd-keygen@.service
+%attr(0644,root,root) %{_unitdir}/sshd-keygen.target
+%attr(0644,root,root) %{_tmpfilesdir}/openssh.conf
+
+%files keycat
+%doc HOWTO.ssh-keycat
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-keycat
+%attr(0644,root,root) %config(noreplace) /etc/pam.d/ssh-keycat
+
+%if ! %{no_gnome_askpass}
+%files askpass
+%attr(0644,root,root) %{_sysconfdir}/profile.d/gnome-ssh-askpass.*
+%attr(0755,root,root) %{_libexecdir}/openssh/gnome-ssh-askpass
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-askpass
+%endif
+
+%files cavs
+%attr(0755,root,root) %{_libexecdir}/openssh/ctr-cavstest
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-cavs
+%attr(0755,root,root) %{_libexecdir}/openssh/ssh-cavs_driver.pl
+
+%if %{pam_ssh_agent}
+%files -n pam_ssh_agent_auth
+%license pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}/OPENSSH_LICENSE
+%attr(0755,root,root) %{_libdir}/security/pam_ssh_agent_auth.so
+%attr(0644,root,root) %{_mandir}/man8/pam_ssh_agent_auth.8*
+%endif
+
+%changelog
+* Tue Oct 06 2020 Jakub Jelen <jjelen@redhat.com> - 8.4p1-2 + 0.10.4-1
+- Unbreak ssh-copy-id after a release (#1884231)
+- Remove misleading comment from sysconfig
+
+* Tue Sep 29 2020 Jakub Jelen <jjelen@redhat.com> - 8.4p1-1 + 0.10.4-1
+- New upstream release of OpenSSH and pam_ssh_agent_auth (#1882995)
+
+* Fri Aug 21 2020 Jakub Jelen <jjelen@redhat.com> - 8.3p1-4 + 0.10.3-10
+- Remove openssh-ldap subpackage (#1871025)
+- pkcs11: Do not crash with invalid paths in ssh-agent (#1868996)
+- Clarify documentation about sftp-server -m (#1862504)
+
+* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 8.3p1-3.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
+
+* Wed Jun 10 2020 Jakub Jelen <jjelen@redhat.com> - 8.3p1-3 + 0.10.3-10
+- Do not lose PIN when more slots match PKCS#11 URI (#1843372)
+- Update to new crypto-policies version on server (using sshd_config include)
+- Move redhat configuraion files to larger number to allow simpler override
+- Move sshd_config include before any other definitions (#1824913)
+
+* Mon Jun 01 2020 Jakub Jelen <jjelen@redhat.com> - 8.3p1-2 + 0.10.3-10
+- Fix crash on cleanup (#1842281)
+
+* Wed May 27 2020 Jakub Jelen <jjelen@redhat.com> - 8.3p1-1 + 0.10.3-10
+- New upstream release (#1840503)
+- Unbreak corner cases of sshd_config include
+- Fix order of gssapi key exchange algorithms
+
+* Wed Apr 08 2020 Jakub Jelen <jjelen@redhat.com> - 8.2p1-3 + 0.10.3-9
+- Simplify reference to crypto policies in configuration files
+- Unbreak gssapi authentication with GSSAPITrustDNS over jump hosts
+- Correctly print FIPS mode initialized in debug mode
+- Enable SHA2-based GSSAPI key exchange methods (#1666781)
+- Do not break X11 forwarding when IPv6 is disabled
+- Remove fipscheck dependency as OpenSSH is no longer FIPS module
+- Improve documentation about crypto policies defaults in manual pages
+
+* Thu Feb 20 2020 Jakub Jelen <jjelen@redhat.com> - 8.2p1-2 + 0.10.3-9
+- Build against libfido2 to unbreak internal u2f support
+
+* Mon Feb 17 2020 Jakub Jelen <jjelen@redhat.com> - 8.2p1-1 + 0.10.3-9
+- New upstrem reelase (#1803290)
+- New /etc/ssh/sshd_config.d drop in directory
+- Support for U2F security keys
+- Correctly report invalid key permissions (#1801459)
+- Do not write bogus information on stderr in FIPS mode (#1778224)
+
+* Mon Feb 03 2020 Jakub Jelen <jjelen@redhat.com> - 8.1p1-4 + 0.10.3-8
+- Unbreak seccomp filter on ARM (#1796267)
+
+* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 8.1p1-3.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
+
+* Wed Nov 27 2019 Jakub Jelen <jjelen@redhat.com> - 8.1p1-3 + 0.10.3-8
+- Unbreak seccomp filter also on ARM (#1777054)
+
+* Thu Nov 14 2019 Jakub Jelen <jjelen@redhat.com> - 8.1p1-2 + 0.10.3-8
+- Unbreak seccomp filter with latest glibc (#1771946)
+
+* Wed Oct 09 2019 Jakub Jelen <jjelen@redhat.com> - 8.1p1-1 + 0.10.3-8
+- New upstream release (#1759750)
+
+* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 8.0p1-8.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
+
+* Tue Jul 23 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-8 + 0.10.3-7
+- Use the upstream-accepted version of the PKCS#8 PEM support (#1722285)
+
+* Fri Jul 12 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-7 + 0.10.3-7
+- Use the environment file under /etc/sysconfig for anaconda configuration (#1722928)
+
+* Wed Jul 03 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-6 + 0.10.3-7
+- Provide the entry point for anaconda configuration in service file (#1722928)
+
+* Wed Jun 26 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-5 + 0.10.3-7
+- Disable root password logins (#1722928)
+- Fix typo in manual pages related to crypto-policies
+- Fix the gating test to make sure it removes the test user
+- Cleanu up spec file and get rid of some rpmlint warnings
+
+* Mon Jun 17 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-4 + 0.10.3-7
+- Compatibility with ibmca engine for ECC
+- Generate more modern PEM files using new OpenSSL API
+- Provide correct signature types for RSA keys using SHA2 from agent
+
+* Mon May 27 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-3 + 0.10.3-7
+- Remove problematic patch updating cached pw structure
+- Do not require the labels on the public objects (#1710832)
+
+* Tue May 14 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-2 + 0.10.3-7
+- Use OpenSSL KDF
+- Use high-level OpenSSL API for signatures handling
+- Mention crypto-policies in manual pages instead of hardcoded defaults
+- Verify in package testsuite that SCP vulnerabilities are fixed
+- Do not fail in FIPS mode, when unsupported algorithm is listed in configuration
+
+* Fri Apr 26 2019 Jakub Jelen <jjelen@redhat.com> - 8.0p1-1 + 0.10.3-7
+- New upstream release (#1701072)
+- Removed support for VendroPatchLevel configuration option
+- Significant rework of GSSAPI Key Exchange
+- Significant rework of PKCS#11 URI support
+
+* Mon Mar 11 2019 Jakub Jelen <jjelen@redhat.com> - 7.9p1-5 + 0.10.3.6
+- Fix kerberos cleanup procedures with GSSAPI
+- Update cached passwd structure after PAM authentication
+- Do not fall back to sshd_net_t SELinux context
+- Fix corner cases of PKCS#11 URI implementation
+- Do not negotiate arbitrary primes with DH GEX in FIPS 
+
+* Wed Feb 06 2019 Jakub Jelen <jjelen@redhat.com> - 7.9p1-4 + 0.10.3.6
+- Log when a client requests an interactive session and only sftp is allowed
+- Fix minor issues in ssh-copy-id
+- Enclose redhat specific configuration with Match final block
+
+* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 7.9p1-3.2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
+
+* Mon Jan 14 2019 Björn Esser <besser82@fedoraproject.org> - 7.9p1-3.1
+- Rebuilt for libcrypt.so.2 (#1666033)
+
+* Mon Jan 14 2019 Jakub Jelen <jjelen@redhat.com> - 7.9p1-3 + 0.10.3.6
+- Backport Match final to unbreak canonicalization with crypto-policies (#1630166)
+- gsskex: Dump correct option
+- Backport several fixes from 7_9 branch, mostly related to certificate authentication (#1665611)
+- Backport patch for CVE-2018-20685 (#1665786)
+- Correctly initialize ECDSA key structures from PKCS#11
+
+* Wed Nov 14 2018 Jakub Jelen <jjelen@redhat.com> - 7.9p1-2 + 0.10.3-6
+- Fix LDAP configure test (#1642414)
+- Avoid segfault on kerberos authentication failure
+- Reference correct file in configuration example (#1643274)
+- Dump missing GSSAPI configuration options
+- Allow to disable RSA signatures with SHA-1
+
+* Fri Oct 19 2018 Jakub Jelen <jjelen@redhat.com> - 7.9p1-1 + 0.10.3-6
+- New upstream release OpenSSH 7.9p1 (#1632902, #1630166)
+- Honor GSSAPIServerIdentity option for GSSAPI key exchange
+- Do not break gsssapi-keyex authentication method when specified in
+  AuthenticationMethods
+- Follow the system-wide PATH settings (#1633756)
+- Address some coverity issues
+
+* Mon Sep 24 2018 Jakub Jelen <jjelen@redhat.com> - 7.8p1-3 + 0.10.3-5
+- Disable OpenSSH hardening flags and use the ones provided by system
+- Ignore unknown parts of PKCS#11 URI
+- Do not fail with GSSAPI enabled in match blocks (#1580017)
+- Fix the segfaulting cavs test (#1628962)
+
+* Fri Aug 31 2018 Jakub Jelen <jjelen@redhat.com> - 7.8p1-2 + 0.10.3-5
+- New upstream release fixing CVE 2018-15473
+- Remove unused patches
+- Remove reference to unused enviornment variable SSH_USE_STRONG_RNG
+- Address coverity issues
+- Unbreak scp between two IPv6 hosts
+- Unbreak GSSAPI key exchange (#1624344)
+- Unbreak rekeying with GSSAPI key exchange (#1624344)
+
+* Thu Aug 09 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-6 + 0.10.3-4
+- Fix listing of kex algoritms in FIPS mode
+- Allow aes-gcm cipher modes in FIPS mode
+- Coverity fixes
+
+* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 7.7p1-5.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
+
+* Tue Jul 03 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-5 + 0.10.3-4
+- Disable manual printing of motd by default (#1591381)
+
+* Wed Jun 27 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-4 + 0.10.3-4
+- Better handling of kerberos tickets storage (#1566494)
+- Add pam_motd to pam stack (#1591381)
+
+* Mon Apr 16 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-3 + 0.10.3-4
+- Fix tun devices and other issues fixed after release upstream (#1567775)
+
+* Thu Apr 12 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-2 + 0.10.3-4
+- Do not break quotes parsing in configuration file (#1566295)
+
+* Wed Apr 04 2018 Jakub Jelen <jjelen@redhat.com> - 7.7p1-1 + 0.10.3-4
+- New upstream release (#1563223)
+- Add support for ECDSA keys in PKCS#11 (#1354510)
+- Add support for PKCS#11 URIs
+
+* Tue Mar 06 2018 Jakub Jelen <jjelen@redhat.com> - 7.6p1-7 + 0.10.3-3
+- Require crypto-policies version and new path
+- Remove bogus NSS linking
+
+* Thu Feb 08 2018 Fedora Release Engineering <releng@fedoraproject.org> - 7.6p1-6.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
+
+* Fri Jan 26 2018 Jakub Jelen <jjelen@redhat.com> - 7.6p1-6 + 0.10.3-3
+- Rebuild for gcc bug on i386 (#1536555)
+
+* Thu Jan 25 2018 Florian Weimer <fweimer@redhat.com> - 7.6p1-5.2
+- Rebuild to work around gcc bug leading to sshd miscompilation (#1538648)
+
+* Sat Jan 20 2018 Björn Esser <besser82@fedoraproject.org> - 7.6p1-5.1.1
+- Rebuilt for switch to libxcrypt
+
+* Wed Jan 17 2018 Jakub Jelen <jjelen@redhat.com> - 7.6p1-5 + 0.10.3-3
+- Drop support for TCP wrappers (#1530163)
+- Do not pass hostnames to audit -- UseDNS is usually disabled (#1534577)
+
+* Thu Dec 14 2017 Jakub Jelen <jjelen@redhat.com> - 7.6p1-4 + 0.10.3-3
+- Whitelist gettid() syscall in seccomp filter (#1524392)
+
+* Mon Dec 11 2017 Jakub Jelen <jjelen@redhat.com> - 7.6p1-3 + 0.10.3-3
+- Do not segfault during audit cleanup (#1524233)
+- Avoid gcc warnings about uninitialized variables
+
+* Wed Nov 22 2017 Jakub Jelen <jjelen@redhat.com> - 7.6p1-2 + 0.10.3-3
+- Do not build everything against libldap
+- Do not segfault for ECC keys in PKCS#11
+
+* Thu Oct 19 2017 Jakub Jelen <jjelen@redhat.com> - 7.6p1-1 + 0.10.3-3
+- New upstream release OpenSSH 7.6
+- Addressing review remarks for OpenSSL 1.1.0 patch
+- Fix PermitOpen bug in OpenSSH 7.6
+- Drop support for ExposeAuthenticationMethods option
+
+* Mon Sep 11 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-6 + 0.10.3-2
+- Do not export KRB5CCNAME if the default path is used (#1199363)
+- Add enablement for openssl-ibmca and openssl-ibmpkcs11 (#1477636)
+- Add new GSSAPI kex algorithms with SHA-2, but leave them disabled for now
+- Enforce pam_sepermit for all logins in SSH (#1492313)
+- Remove pam_reauthorize, since it is not needed by cockpit anymore (#1492313)
+
+* Mon Aug 14 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-5 + 0.10.3-2
+- Another less-intrusive approach to crypto policy (#1479271)
+
+* Tue Aug 01 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-4 + 0.10.3-2
+- Remove SSH-1 subpackage for Fedora 27 (#1474942)
+- Follow system-wide crypto policy in server (#1479271)
+
+* Thu Jul 27 2017 Fedora Release Engineering <releng@fedoraproject.org> - 7.5p1-3.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
+
+* Fri Jun 30 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-2 + 0.10.3-2
+- Sync downstream patches with RHEL (FIPS)
+- Resolve potential issues with OpenSSL 1.1.0 patch
+
+* Wed Mar 22 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-2 + 0.10.3-2
+- Fix various after-release typos including failed build in s390x (#1434341)
+- Revert chroot magic with SELinux
+
+* Mon Mar 20 2017 Jakub Jelen <jjelen@redhat.com> - 7.5p1-1 + 0.10.3-2
+- New upstream release
+
+* Fri Mar 03 2017 Jakub Jelen <jjelen@redhat.com> - 7.4p1-4 + 0.10.3-1
+- Avoid sending the SD_NOTIFY messages from wrong processes (#1427526)
+- Address reports by coverity
+
+* Mon Feb 20 2017 Jakub Jelen <jjelen@redhat.com> - 7.4p1-3 + 0.10.3-1
+- Properly report errors from included files (#1408558)
+- New pam_ssh_agent_auth 0.10.3 release
+- Switch to SD_NOTIFY to make systemd happy
+
+* Mon Feb 06 2017 Jakub Jelen <jjelen@redhat.com> - 7.4p1-2 + 0.10.2-5
+- Fix ssh-agent cert signing error (#1416584)
+- Fix wrong path to crypto policies
+- Attempt to resolve issue with systemd
+
+* Tue Jan 03 2017 Jakub Jelen <jjelen@redhat.com> - 7.4p1-1 + 0.10.2-5
+- New upstream release (#1406204)
+- Cache supported OIDs for GSSAPI key exchange (#1395288)
+- Fix typo causing heap corruption (use-after-free) (#1409433)
+- Prevent hangs with long MOTD
+
+* Thu Dec 08 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-7 + 0.10.2-4
+- Properly deserialize received RSA certificates in ssh-agent (#1402029)
+- Move MAX_DISPLAYS to a configuration option
+
+* Wed Nov 16 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-6 + 0.10.2-4
+- GSSAPI requires futex syscall in privsep child (#1395288)
+
+* Thu Oct 27 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-5 + 0.10.2-4
+- Build against OpenSSL 1.1.0 with compat changes
+- Recommend crypto-policies
+- Fix chroot dropping capabilities (#1386755)
+
+* Thu Sep 29 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-4 + 0.10.2-4
+- Fix NULL dereference (#1380297)
+- Include client Crypto Policy (#1225752)
+
+* Mon Aug 15 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-3 + 0.10.2-4
+- Proper content of included configuration file
+
+* Tue Aug 09 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-2 + 0.10.2-4
+- Fix permissions on the include directory (#1365270)
+
+* Tue Aug 02 2016 Jakub Jelen <jjelen@redhat.com> - 7.3p1-1 + 0.10.2-4
+- New upstream release (#1362156)
+
+* Tue Jul 26 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-11 + 0.10.2-3
+- Remove slogin and sshd-keygen (#1359762)
+- Prevent guest_t from running sudo (#1357860)
+
+* Mon Jul 18 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-10 + 0.10.2-3
+- CVE-2016-6210: User enumeration via covert timing channel (#1357443)
+- Expose more information about authentication to PAM
+- Make closefrom() ignore softlinks to the /dev/ devices on s390
+
+* Fri Jul 01 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-9 + 0.10.2-3
+- Fix wrong detection of UseLogin in server configuration (#1350347)
+
+* Fri Jun 24 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-8 + 0.10.2-3
+- Enable seccomp filter for MIPS architectures
+- UseLogin=yes is not supported in Fedora
+- SFTP server forced permissions should restore umask
+- pam_ssh_agent_auth: Fix conflict bewteen two getpwuid() calls (#1349551)
+
+* Mon Jun 06 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-7
+- Fix regression in certificate-based authentication (#1333498)
+- Check for real location of .k5login file (#1328243)
+- Fix unchecked dereference in pam_ssh_agent_auth
+- Clean up old patches
+- Build with seccomp filter on ppc64(le) (#1195065)
+
+* Fri Apr 29 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-6 + 0.10.2-3
+- Add legacy sshd-keygen for anaconda (#1331077)
+
+* Fri Apr 22 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-5 + 0.10.2-3
+- CVE-2015-8325: ignore PAM environment vars when UseLogin=yes (#1328013)
+- Fix typo in sysconfig/sshd (#1325535)
+
+* Fri Apr 15 2016 Jakub Jelen <jjelen@redhat.com> - 7.2p2-4 + 0.10.2-3
+- Revise socket activation and services dependencies (#1325535)
+- Drop unused init script
+
+* Wed Apr 13 2016 Jakub Jelen <jjelen@redhat.com> 7.2p2-3 + 0.10.2-3
+- Make sshd-keygen comply with packaging guidelines (#1325535)
+- Soft-deny socket() syscall in seccomp sandbox (#1324493)
+- Remove *sha1 Kex in FIPS mode (#1324493)
+- Remove *gcm ciphers in FIPS mode (#1324493)
+
+* Wed Apr 06 2016 Jakub Jelen <jjelen@redhat.com> 7.2p2-2 + 0.10.2-3
+- Fix GSSAPI Key Exchange according to RFC (#1323622)
+- Remove init.d/functions dependency from sshd-keygen (#1317722)
+- Do not use MD5 in pam_ssh_agent_auth in FIPS mode
+
+* Thu Mar 10 2016 Jakub Jelen <jjelen@redhat.com> 7.2p2-1 + 0.10.2-3
+- New upstream (security) release (#1316529)
+- Clean up audit patch
+
+* Thu Mar 03 2016 Jakub Jelen <jjelen@redhat.com> 7.2p1-2 + 0.10.2-2
+- Restore slogin symlinks to preserve backward compatibility
+
+* Mon Feb 29 2016 Jakub Jelen <jjelen@redhat.com> 7.2p1-1 + 0.10.2-2
+- New upstream release (#1312870)
+
+* Wed Feb 24 2016 Jakub Jelen <jjelen@redhat.com> 7.1p2-4.1 + 0.10.2-1
+- Fix race condition in auditing events when using multiplexing (#1308295)
+- Fix X11 forwarding CVE according to upstream
+- Fix problem when running without privsep (#1303910)
+- Remove hard glob limit in SFTP
+
+* Thu Feb 04 2016 Fedora Release Engineering <releng@fedoraproject.org> - 7.1p2-3.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
+
+* Sat Jan 30 2016 Jakub Jelen <jjelen@redhat.com> 7.1p2-3 + 0.10.2-1
+- Fix segfaults with pam_ssh_agent_auth (#1303036)
+- Silently disable X11 forwarding on problems
+- Systemd service should be forking to detect immediate failures
+
+* Mon Jan 25 2016 Jakub Jelen <jjelen@redhat.com> 7.1p2-2 + 0.10.2-1
+- Rebased to recent version of pam_ssh_agent_auth
+- Upstream fix for CVE-2016-1908
+- Remove useless defattr
+
+* Thu Jan 14 2016 Jakub Jelen <jjelen@redhat.com> 7.1p2-1 + 0.9.2-9
+- New security upstream release for CVE-2016-0777
+
+* Tue Jan 12 2016 Jakub Jelen <jjelen@redhat.com> 7.1p1-7 + 0.9.2-8
+- Change RPM define macros to global according to packaging guidelines
+- Fix wrong handling of SSH_COPY_ID_LEGACY environment variable
+- Update ssh-agent and ssh-keysign permissions (#1296724)
+- Fix few problems with alternative builds without GSSAPI or openSSL
+- Fix condition to run sshd-keygen
+
+* Fri Dec 18 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-6 + 0.9.2-8
+- Preserve IUTF8 tty mode flag over ssh connections (#1270248)
+- Do not require sysconfig file to start service (#1279521)
+- Update ssh-copy-id to upstream version
+- GSSAPI Key Exchange documentation improvements
+- Remove unused patches
+
+* Wed Nov 04 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-5 + 0.9.2-8
+- Do not set user context too many times for root logins (#1269072)
+
+* Thu Oct 22 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-4 + 0.9.2-8
+- Review SELinux user context handling after authentication (#1269072)
+- Handle root logins the same way as other users (#1269072)
+- Audit implicit mac, if mac is covered in cipher (#1271694)
+- Increase size limit for remote glob over sftp
+
+* Fri Sep 25 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-3 + 0.9.2-8
+- Fix FIPS mode for DH kex (#1260253)
+- Provide full RELRO and PIE form askpass helper (#1264036)
+- Fix gssapi key exchange on server and client (#1261414)
+- Allow gss-keyex root login when without-password is set (upstream #2456)
+- Fix obsolete usage of SELinux constants (#1261496)
+
+* Wed Sep 09 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-2 + 0.9.2-8
+- Fix warnings reported by gcc related to keysign and keyAlgorithms
+
+* Sat Aug 22 2015 Jakub Jelen <jjelen@redhat.com> 7.1p1-1 + 0.9.2-8
+- New upstream release
+
+* Wed Aug 19 2015 Jakub Jelen <jjelen@redhat.com> 7.0p1-2 + 0.9.3-7
+- Fix problem with DSA keys using pam_ssh_agent_auth (#1251777)
+- Add GSSAPIKexAlgorithms option for server and client application
+- Possibility to validate legacy systems by more fingerprints (#1249626)
+
+* Wed Aug 12 2015 Jakub Jelen <jjelen@redhat.com> 7.0p1-1 + 0.9.3-7
+- New upstream release (#1252639)
+- Fix pam_ssh_agent_auth package (#1251777)
+- Security: Use-after-free bug related to PAM support (#1252853)
+- Security: Privilege separation weakness related to PAM support (#1252854)
+- Security: Incorrectly set TTYs to be world-writable (#1252862)
+
+* Tue Jul 28 2015 Jakub Jelen <jjelen@redhat.com> 6.9p1-4 + 0.9.3-6
+- Handle terminal control characters in scp progressmeter (#1247204)
+
+* Thu Jul 23 2015 Jakub Jelen <jjelen@redhat.com> 6.9p1-3 + 0.9.3-6
+- CVE-2015-5600: only query each keyboard-interactive device once (#1245971)
+
+* Wed Jul 15 2015 Jakub Jelen <jjelen@redhat.com> 6.9p1-2 + 0.9.3-6
+- Enable SECCOMP filter for s390* architecture (#1195065)
+- Fix race condition when multiplexing connection (#1242682)
+
+* Wed Jul 01 2015 Jakub Jelen <jjelen@redhat.com> 6.9p1-1 + 0.9.3-6
+- New upstream release (#1238253)
+- Increase limitation number of files which can be listed using glob in sftp
+- Correctly revert "PermitRootLogin no" option from upstream sources (#89216)
+
+* Wed Jun 24 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-9 + 0.9.3-5
+- Allow socketcall(SYS_SHUTDOWN) for net_child on ix86 architecture
+
+* Thu Jun 18 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 6.8p1-8.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
+
+* Mon Jun 08 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-8 + 0.9.3-5
+- Return stat syscall to seccomp filter (#1228323)
+
+* Wed Jun 03 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-7 + 0.9.3-5
+- Handle pam_ssh_agent_auth memory, buffers and variable sizes (#1225106)
+
+* Thu May 28 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-6 + 0.9.3-5
+- Resolve problem with pam_ssh_agent_auth after rebase (#1225106)
+- ssh-copy-id: tcsh doesnt work with multiline strings
+- Fix upstream memory problems
+- Add missing options in testmode output and manual pages
+- Provide LDIF version of LPK schema
+- Document required selinux boolean for working ssh-ldap-helper
+
+* Mon Apr 20 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-5 + 0.9.3-5
+- Fix segfault on daemon exit caused by API change (#1213423)
+
+* Thu Apr 02 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-4 + 0.9.3-5
+- Fix audit_end_command to restore ControlPersist function (#1203900)
+
+* Tue Mar 31 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-3 + 0.9.3-5
+- Fixed issue with GSSAPI key exchange (#1207719)
+- Add pam_namespace to sshd pam stack (based on #1125110)
+- Remove krb5-config workaround for #1203900
+- Fix handling SELinux context in MLS systems
+- Regression: solve sshd segfaults if other instance already running
+
+* Thu Mar 26 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-2 + 0.9.3-5
+- Update audit and gss patches after rebase
+- Fix reintroduced upstrem bug #1878
+
+* Tue Mar 24 2015 Jakub Jelen <jjelen@redhat.com> 6.8p1-1 + 0.9.3-5
+- new upstream release openssh-6.8p1 (#1203245)
+- Resolve segfault with auditing commands (#1203900)
+- Workaround krb5-config bug (#1204646)
+
+* Thu Mar 12 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-11 + 0.9.3-4
+- Ability to specify LDAP filter in ldap.conf for ssh-ldap-helper
+- Fix auditing when using combination of ForceCommand and PTY
+- Add sftp option to force mode of created files (from rhel)
+- Fix tmpfiles.d entries to be more consistent (#1196807)
+
+* Mon Mar 02 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-10 + 0.9.3-4
+- Add tmpfiles.d entries (#1196807)
+
+* Fri Feb 27 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-9 + 0.9.3-4
+- Adjust seccomp filter for primary architectures and solve aarch64 issue (#1197051)
+- Solve issue with ssh-copy-id and keys without trailing newline (#1093168)
+
+* Tue Feb 24 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-8 + 0.9.3-4
+- Add AArch64 support for seccomp_filter sandbox (#1195065)
+
+* Mon Feb 23 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-7 + 0.9.3-4
+- Fix seccomp filter on architectures without getuid32
+
+* Mon Feb 23 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-6 + 0.9.3-4
+- Update seccomp filter to work on i686 architectures (#1194401)
+- Fix previous failing build (#1195065)
+
+* Sun Feb 22 2015 Peter Robinson <pbrobinson@fedoraproject.org> 6.7p1-5 + 0.9.3-4
+- Only use seccomp for sandboxing on supported platforms
+
+* Fri Feb 20 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-4 + 0.9.3-4
+- Move cavs tests into subpackage -cavs (#1194320)
+
+* Wed Feb 18 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-3 + 0.9.3-4
+- update coverity patch
+- make output of sshd -T more consistent (#1187521)
+- enable seccomp for sandboxing instead of rlimit (#1062953)
+- update hardening to compile on gcc5
+- Add SSH KDF CAVS test driver (#1193045)
+- Fix ssh-copy-id on non-sh remote shells (#1045191)
+
+* Tue Jan 27 2015 Jakub Jelen <jjelen@redhat.com> 6.7p1-2 + 0.9.3-4
+- fixed audit patch after rebase
+
+* Tue Jan 20 2015 Petr Lautrbach <plautrba@redhat.com> 6.7p1-1 + 0.9.3-4
+- new upstream release openssh-6.7p1
+
+* Thu Jan 15 2015 Jakub Jelen <jjelen@redhat.com> 6.6.1p1-11.1 + 0.9.3-3
+- error message if scp when directory doesn't exist (#1142223)
+- parsing configuration file values (#1130733)
+- documentation in service and socket files for systemd (#1181593)
+- updated ldap patch (#981058)
+- fixed vendor-patchlevel
+- add new option GSSAPIEnablek5users and disable using ~/.k5users by default CVE-2014-9278 (#1170745)
+
+* Fri Dec 19 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-10 + 0.9.3-3
+- log via monitor in chroots without /dev/log
+
+* Wed Dec 03 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-9 + 0.9.3-3
+- the .local domain example should be in ssh_config, not in sshd_config
+- use different values for DH for Cisco servers (#1026430)
+
+* Thu Nov 13 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-8 + 0.9.3-3
+- fix gsskex patch to correctly handle MONITOR_REQ_GSSSIGN request (#1118005)
+
+* Fri Nov 07 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-7 + 0.9.3-3
+- correct the calculation of bytes for authctxt->krb5_ccname <ams@corefiling.com> (#1161073)
+
+* Tue Nov 04 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-6 + 0.9.3-3
+- privsep_preauth: use SELinux context from selinux-policy (#1008580)
+- change audit trail for unknown users (mindrot#2245)
+- fix kuserok patch which checked for the existence of .k5login
+  unconditionally and hence prevented other mechanisms to be used properly
+- revert the default of KerberosUseKuserok back to yes (#1153076)
+- ignore SIGXFSZ in postauth monitor (mindrot#2263)
+- sshd-keygen - don't generate DSA and ED25519 host keys in FIPS mode
+
+* Mon Sep 08 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-5 + 0.9.3-3
+- set a client's address right after a connection is set (mindrot#2257)
+- apply RFC3454 stringprep to banners when possible (mindrot#2058)
+- don't consider a partial success as a failure (mindrot#2270)
+
+* Sun Aug 17 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 6.6.1p1-4.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
+
+* Fri Jul 18 2014 Tom Callaway <spot@fedoraproject.org> 6.6.1p1-4 + 0.9.3-3
+- fix license handling (both)
+
+* Fri Jul 18 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-3 + 0.9.3-2
+- standardise on NI_MAXHOST for gethostname() string lengths (#1051490)
+
+* Mon Jul 14 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-2 + 0.9.3-2
+- add pam_reauthorize.so to sshd.pam (#1115977)
+- spec file and patches clenup
+
+* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 6.6.1p1-1.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
+
+* Tue Jun 03 2014 Petr Lautrbach <plautrba@redhat.com> 6.6.1p1-1 + 0.9.3-2
+- disable the curve25519 KEX when speaking to OpenSSH 6.5 or 6.6
+- add support for ED25519 keys to sshd-keygen and sshd.sysconfig
+- drop openssh-server-sysvinit subpackage
+- slightly change systemd units logic - use sshd-keygen.service (#1066615)
+
+* Tue Jun 03 2014 Petr Lautrbach <plautrba@redhat.com> 6.6p1-1 + 0.9.3-2
+- new upstream release openssh-6.6p1
+
+* Thu May 15 2014 Petr Lautrbach <plautrba@redhat.com> 6.4p1-4 + 0.9.3-1
+- use SSH_COPY_ID_LEGACY variable to run ssh-copy-id in the legacy mode
+- make /etc/ssh/moduli file public (#1043661)
+- test existence of /etc/ssh/ssh_host_ecdsa_key in sshd-keygen.service
+- don't clean up gssapi credentials by default (#1055016)
+- ssh-agent - try CLOCK_BOOTTIME with fallback (#1091992)
+- prevent a server from skipping SSHFP lookup - CVE-2014-2653 (#1081338)
+- ignore environment variables with embedded '=' or '\0' characters - CVE-2014-2532
+  (#1077843)
+
+* Wed Dec 11 2013 Petr Lautrbach <plautrba@redhat.com> 6.4p1-3 + 0.9.3-1
+- sshd-keygen - use correct permissions on ecdsa host key (#1023945)
+- use only rsa and ecdsa host keys by default
+
+* Tue Nov 26 2013 Petr Lautrbach <plautrba@redhat.com> 6.4p1-2 + 0.9.3-1
+- fix fatal() cleanup in the audit patch (#1029074)
+- fix parsing logic of ldap.conf file (#1033662)
+
+* Fri Nov 08 2013 Petr Lautrbach <plautrba@redhat.com> 6.4p1-1 + 0.9.3-1
+- new upstream release
+
+* Fri Nov 01 2013 Petr Lautrbach <plautrba@redhat.com> 6.3p1-5 + 0.9.3-7
+- adjust gss kex mechanism to the upstream changes (#1024004)
+- don't use xfree in pam_ssh_agent_auth sources <geertj@gmail.com> (#1024965)
+
+* Fri Oct 25 2013 Petr Lautrbach <plautrba@redhat.com> 6.3p1-4 + 0.9.3-6
+- rebuild with the openssl with the ECC support
+
+* Thu Oct 24 2013 Petr Lautrbach <plautrba@redhat.com> 6.3p1-3 + 0.9.3-6
+- don't use SSH_FP_MD5 for fingerprints in FIPS mode
+
+* Wed Oct 23 2013 Petr Lautrbach <plautrba@redhat.com> 6.3p1-2 + 0.9.3-6
+- use default_ccache_name from /etc/krb5.conf for a kerberos cache (#991186)
+- increase the size of the Diffie-Hellman groups (#1010607)
+- sshd-keygen to generate ECDSA keys <i.grok@comcast.net> (#1019222)
+
+* Tue Oct 15 2013 Petr Lautrbach <plautrba@redhat.com> 6.3p1-1.1 + 0.9.3-6
+- new upstream release (#1007769)
+
+* Tue Oct 08 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-9 + 0.9.3-5
+- use dracut-fips package to determine if a FIPS module is installed
+- revert -fips subpackages and hmac files suffixes
+
+* Wed Sep 25 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-8 + 0.9.3-5
+- sshd-keygen: generate only RSA keys by default (#1010092)
+- use dist tag in suffixes for hmac checksum files
+
+* Wed Sep 11 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-7 + 0.9.3-5
+- use hmac_suffix for ssh{,d} hmac checksums
+- bump the minimum value of SSH_USE_STRONG_RNG to 14 according to SP800-131A
+- automatically restart sshd.service on-failure after 42s interval
+
+* Thu Aug 29 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-6.1 + 0.9.3-5
+- add -fips subpackages that contains the FIPS module files
+
+* Wed Jul 31 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-5 + 0.9.3-5
+- gssapi credentials need to be stored before a pam session opened (#987792)
+
+* Tue Jul 23 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-4 + 0.9.3-5
+- don't show Success for EAI_SYSTEM (#985964)
+- make sftp's libedit interface marginally multibyte aware (#841771)
+
+* Mon Jun 17 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-3 + 0.9.3-5
+- move default gssapi cache to /run/user/<uid> (#848228)
+
+* Tue May 21 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-2 + 0.9.3-5
+- add socket activated sshd units to the package (#963268)
+- fix the example in the HOWTO.ldap-keys
+
+* Mon May 20 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p2-1 + 0.9.3-5
+- new upstream release (#963582)
+
+* Wed Apr 17 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p1-4 + 0.9.3-4
+- don't use export in sysconfig file (#953111)
+
+* Tue Apr 16 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p1-3 + 0.9.3-4
+- sshd.service: use KillMode=process (#890376)
+- add latest config.{sub,guess} to support aarch64 (#926284)
+
+* Tue Apr 09 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p1-2 + 0.9.3-4
+- keep track of which IndentityFile options were manually supplied and
+  which were default options, and don't warn if the latter are missing.
+  (mindrot#2084)
+
+* Tue Apr 09 2013 Petr Lautrbach <plautrba@redhat.com> 6.2p1-1 + 0.9.3-4
+- new upstream release (#924727)
+
+* Wed Mar 06 2013 Petr Lautrbach <plautrba@redhat.com> 6.1p1-7 + 0.9.3-3
+- use SELinux type sshd_net_t for [net] childs (#915085)
+
+* Thu Feb 14 2013 Petr Lautrbach <plautrba@redhat.com> 6.1p1-6 + 0.9.3-3
+- fix AuthorizedKeysCommand option
+
+* Fri Feb 08 2013 Petr Lautrbach <plautrba@redhat.com> 6.1p1-5 + 0.9.3-3
+- change default value of MaxStartups - CVE-2010-5107 (#908707)
+
+* Mon Dec 03 2012 Petr Lautrbach <plautrba@redhat.com> 6.1p1-4 + 0.9.3-3
+- fix segfault in openssh-5.8p2-force_krb.patch (#882541)
+
+* Mon Dec 03 2012 Petr Lautrbach <plautrba@redhat.com> 6.1p1-3 + 0.9.3-3
+- replace RequiredAuthentications2 with AuthenticationMethods based on upstream
+- obsolete RequiredAuthentications[12] options
+- fix openssh-6.1p1-privsep-selinux.patch
+
+* Fri Oct 26 2012 Petr Lautrbach <plautrba@redhat.com> 6.1p1-2
+- add SELinux comment to /etc/ssh/sshd_config about SELinux command to modify port (#861400)
+- drop required chkconfig (#865498)
+- drop openssh-5.9p1-sftp-chroot.patch (#830237)
+
+* Sat Sep 15 2012 Petr Lautrbach <plautrba@redhat.com> 6.1p1-1 + 0.9.3-3
+- new upstream release (#852651)
+- use DIR: kerberos type cache (#848228)
+- don't use chroot_user_t for chrooted users (#830237)
+- replace scriptlets with systemd macros (#850249)
+- don't use /bin and /sbin paths (#856590)
+
+* Mon Aug 06 2012 Petr Lautrbach <plautrba@redhat.com> 6.0p1-1 + 0.9.3-2
+- new upstream release
+
+* Mon Aug 06 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-26 + 0.9.3-1
+- change SELinux context also for root user (#827109)
+
+* Fri Jul 27 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-25 + 0.9.3-1
+- fix various issues in openssh-5.9p1-required-authentications.patch
+
+* Tue Jul 17 2012 Tomas Mraz <tmraz@redhat.com> 5.9p1-24 + 0.9.3-1
+- allow sha256 and sha512 hmacs in the FIPS mode
+
+* Fri Jun 22 2012 Tomas Mraz <tmraz@redhat.com> 5.9p1-23 + 0.9.3-1
+- fix segfault in su when pam_ssh_agent_auth is used and the ssh-agent
+  is not running, most probably not exploitable
+- update pam_ssh_agent_auth to 0.9.3 upstream version
+
+* Fri Apr 06 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-22 + 0.9.2-32
+- don't create RSA1 key in FIPS mode
+- don't install sshd-keygen.service (#810419)
+
+* Fri Mar 30 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-21 + 0.9.2-32
+- fix various issues in openssh-5.9p1-required-authentications.patch
+
+* Wed Mar 21 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-20 + 0.9.2-32
+- Fix dependencies in systemd units, don't enable sshd-keygen.service (#805338)
+
+* Wed Feb 22 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-19 + 0.9.2-32
+- Look for x11 forward sockets with AI_ADDRCONFIG flag getaddrinfo (#735889)
+
+* Mon Feb 06 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-18 + 0.9.2-32
+- replace TwoFactorAuth with RequiredAuthentications[12]
+  https://bugzilla.mindrot.org/show_bug.cgi?id=983
+
+* Tue Jan 31 2012 Petr Lautrbach <plautrba@redhat.com> 5.9p1-17 + 0.9.2-32
+- run privsep slave process as the users SELinux context (#781634)
+
+* Tue Dec 13 2011 Tomas Mraz <tmraz@redhat.com> 5.9p1-16 + 0.9.2-32
+- add CAVS test driver for the aes-ctr ciphers
+
+* Sun Dec 11 2011 Tomas Mraz <tmraz@redhat.com> 5.9p1-15 + 0.9.2-32
+- enable aes-ctr ciphers use the EVP engines from OpenSSL such as the AES-NI
+
+* Tue Dec 06 2011 Petr Lautrbach <plautrba@redhat.com> 5.9p1-14 + 0.9.2-32
+- warn about unsupported option UsePAM=no (#757545)
+
+* Mon Nov 21 2011 Tomas Mraz <tmraz@redhat.com> - 5.9p1-13 + 0.9.2-32
+- add back the restorecon call to ssh-copy-id - it might be needed on older
+  distributions (#739989)
+
+* Fri Nov 18 2011 Tomas Mraz <tmraz@redhat.com> - 5.9p1-12 + 0.9.2-32
+- still support /etc/sysconfig/sshd loading in sshd service (#754732)
+- fix incorrect key permissions generated by sshd-keygen script (#754779)
+
+* Fri Oct 14 2011 Tomas Mraz <tmraz@redhat.com> - 5.9p1-11 + 0.9.2-32
+- remove unnecessary requires on initscripts
+- set VerifyHostKeyDNS to ask in the default configuration (#739856)
+
+* Mon Sep 19 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-10 + 0.9.2-32
+- selinux sandbox rewrite
+- two factor authentication tweaking
+
+* Wed Sep 14 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-9 + 0.9.2-32
+- coverity upgrade
+- wipe off nonfunctional nss
+- selinux sandbox tweaking
+
+* Tue Sep 13 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-8 + 0.9.2-32
+- coverity upgrade
+- experimental selinux sandbox
+
+* Tue Sep 13 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-7 + 0.9.2-32
+- fully reanable auditing
+
+* Mon Sep 12 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-6 + 0.9.2-32
+- repair signedness in akc patch
+
+* Mon Sep 12 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-5 + 0.9.2-32
+- temporarily disable part of audit4 patch
+
+* Fri Sep  9 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-3 + 0.9.2-32
+- Coverity second pass
+- Reenable akc patch
+
+* Thu Sep  8 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-2 + 0.9.2-32
+- Coverity first pass
+
+* Wed Sep  7 2011 Jan F. Chadima <jchadima@redhat.com> - 5.9p1-1 + 0.9.2-32
+- Rebase to 5.9p1
+- Add chroot sftp patch
+- Add two factor auth patch
+
+* Tue Aug 23 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-21 + 0.9.2-31
+- ignore SIGPIPE in ssh keyscan
+
+* Tue Aug  9 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-20 + 0.9.2-31
+- save ssh-askpass's debuginfo
+
+* Mon Aug  8 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-19 + 0.9.2-31
+- compile ssh-askpass with corect CFLAGS
+
+* Mon Aug  8 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-18 + 0.9.2-31
+- improve selinux's change context log 
+
+* Mon Aug  8 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-17 + 0.9.2-31
+- repair broken man pages
+
+* Mon Jul 25 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-16 + 0.9.2-31
+- rebuild due to broken rpmbiild
+
+* Thu Jul 21 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-15 + 0.9.2-31
+- Do not change context when run under unconfined_t
+
+* Thu Jul 14 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-14 + 0.9.2-31
+- Add postlogin to pam. (#718807)
+
+* Tue Jun 28 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-12 + 0.9.2-31
+- Systemd compatibility according to Mathieu Bridon <bochecha@fedoraproject.org>
+- Split out the host keygen into their own command, to ease future migration
+  to systemd. Compatitbility with the init script was kept.
+- Migrate the package to full native systemd unit files, according to the Fedora
+  packaging guidelines.
+- Prepate the unit files for running an ondemand server. (do not add it actually)
+
+* Tue Jun 21 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-10 + 0.9.2-31
+- Mention IPv6 usage in man pages
+
+* Mon Jun 20 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-9 + 0.9.2-31
+- Improve init script
+
+* Thu Jun 16 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-7 + 0.9.2-31
+- Add possibility to compile openssh without downstream patches
+
+* Thu Jun  9 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-6 + 0.9.2-31
+- remove stale control sockets (#706396)
+
+* Tue May 31 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-5 + 0.9.2-31
+- improove entropy manuals
+
+* Fri May 27 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-4 + 0.9.2-31
+- improove entropy handling
+- concat ldap patches
+
+* Tue May 24 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-3 + 0.9.2-31
+- improove ldap manuals
+
+* Mon May 23 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-2 + 0.9.2-31
+- add gssapi forced command
+
+* Tue May  3 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p2-1 + 0.9.2-31
+- update the openssh version
+
+* Thu Apr 28 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-34 + 0.9.2-30
+- temporarily disabling systemd units
+
+* Wed Apr 27 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-33 + 0.9.2-30
+- add flags AI_V4MAPPED and AI_ADDRCONFIG to getaddrinfo
+
+* Tue Apr 26 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-32 + 0.9.2-30
+- update scriptlets
+
+* Fri Apr 22 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-30 + 0.9.2-30
+- add systemd units
+
+* Fri Apr 22 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-28 + 0.9.2-30
+- improving sshd -> passwd transation
+- add template for .local domain to sshd_config
+
+* Thu Apr 21 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-27 + 0.9.2-30
+- the private keys may be 640 root:ssh_keys ssh_keysign is sgid
+
+* Wed Apr 20 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-26 + 0.9.2-30
+- improving sshd -> passwd transation
+
+* Tue Apr  5 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-25 + 0.9.2-30
+- the intermediate context is set to sshd_sftpd_t
+- do not crash in packet.c if no connection
+
+* Thu Mar 31 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-24 + 0.9.2-30
+- resolve warnings in port_linux.c
+
+* Tue Mar 29 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-23 + 0.9.2-30
+- add /etc/sysconfig/sshd
+
+* Mon Mar 28 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-22 + 0.9.2-30
+- improve reseeding and seed source (documentation)
+
+* Tue Mar 22 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-20 + 0.9.2-30
+- use /dev/random or /dev/urandom for seeding prng
+- improve periodical reseeding of random generator
+
+* Thu Mar 17 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-18 + 0.9.2-30
+- add periodical reseeding of random generator 
+- change selinux contex for internal sftp in do_usercontext
+- exit(0) after sigterm
+
+* Thu Mar 10 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-17 + 0.9.2-30
+- improove ssh-ldap (documentation)
+
+* Tue Mar  8 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-16 + 0.9.2-30
+- improve session keys audit
+
+* Mon Mar  7 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-15 + 0.9.2-30
+- CVE-2010-4755
+
+* Fri Mar  4 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-14 + 0.9.2-30
+- improove ssh-keycat (documentation)
+
+* Thu Mar  3 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-13 + 0.9.2-30
+- improve audit of logins and auths
+
+* Tue Mar  1 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-12 + 0.9.2-30
+- improove ssk-keycat
+
+* Mon Feb 28 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-11 + 0.9.2-30
+- add ssk-keycat
+
+* Fri Feb 25 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-10 + 0.9.2-30
+- reenable auth-keys ldap backend
+
+* Fri Feb 25 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-9 + 0.9.2-30
+- another audit improovements
+
+* Thu Feb 24 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-8 + 0.9.2-30
+- another audit improovements
+- switchable fingerprint mode
+
+* Thu Feb 17 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-4 + 0.9.2-30
+- improve audit of server key management
+
+* Wed Feb 16 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-3 + 0.9.2-30
+- improve audit of logins and auths
+
+* Mon Feb 14 2011 Jan F. Chadima <jchadima@redhat.com> - 5.8p1-1 + 0.9.2-30
+- bump openssh version to 5.8p1
+
+* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 5.6p1-30.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
+
+* Mon Feb  7 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-30 + 0.9.2-29
+- clean the data structures in the non privileged process
+- clean the data structures when roaming
+
+* Wed Feb  2 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-28 + 0.9.2-29
+- clean the data structures in the privileged process
+
+* Tue Jan 25 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-25 + 0.9.2-29
+- clean the data structures before exit net process
+
+* Mon Jan 17 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-24 + 0.9.2-29
+- make audit compatible with the fips mode
+
+* Fri Jan 14 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-23 + 0.9.2-29
+- add audit of destruction the server keys
+
+* Wed Jan 12 2011 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-22 + 0.9.2-29
+- add audit of destruction the session keys
+
+* Fri Dec 10 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-21 + 0.9.2-29
+- reenable run sshd as non root user
+- renable rekeying
+
+* Wed Nov 24 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-20 + 0.9.2-29
+- reapair clientloop crash (#627332)
+- properly restore euid in case connect to the ssh-agent socket fails
+
+* Mon Nov 22 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-19 + 0.9.2-28
+- striped read permissions from suid and sgid binaries
+
+* Mon Nov 15 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-18 + 0.9.2-27
+- used upstream version of the biguid patch
+
+* Mon Nov 15 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-17 + 0.9.2-27
+- improoved kuserok patch
+
+* Fri Nov  5 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-16 + 0.9.2-27
+- add auditing the host based key ussage
+- repait X11 abstract layer socket (#648896)
+
+* Wed Nov  3 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-15 + 0.9.2-27
+- add auditing the kex result
+
+* Tue Nov  2 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-14 + 0.9.2-27
+- add auditing the key ussage
+
+* Wed Oct 20 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-12 + 0.9.2-27
+- update gsskex patch (#645389)
+
+* Wed Oct 20 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-11 + 0.9.2-27
+- rebase linux audit according to upstream
+
+* Fri Oct  1 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-10 + 0.9.2-27
+- add missing headers to linux audit
+
+* Wed Sep 29 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-9 + 0.9.2-27
+- audit module now uses openssh audit framevork
+
+* Wed Sep 15 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-8 + 0.9.2-27
+- Add the GSSAPI kuserok switch to the kuserok patch
+
+* Wed Sep 15 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-7 + 0.9.2-27
+- Repaired the kuserok patch
+
+* Mon Sep 13 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-6 + 0.9.2-27
+- Repaired the problem with puting entries with very big uid into lastlog
+
+* Mon Sep 13 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-5 + 0.9.2-27
+- Merging selabel patch with the upstream version. (#632914)
+
+* Mon Sep 13 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-4 + 0.9.2-27
+- Tweaking selabel patch to work properly without selinux rules loaded. (#632914)
+
+* Wed Sep  8 2010 Tomas Mraz <tmraz@redhat.com> - 5.6p1-3 + 0.9.2-27
+- Make fipscheck hmacs compliant with FHS - requires new fipscheck
+
+* Fri Sep  3 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-2 + 0.9.2-27
+- Added -z relro -z now to LDFLAGS
+
+* Fri Sep  3 2010 Jan F. Chadima <jchadima@redhat.com> - 5.6p1-1 + 0.9.2-27
+- Rebased to openssh5.6p1
+
+* Wed Jul  7 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-18 + 0.9.2-26
+- merged with newer bugzilla's version of authorized keys command patch
+
+* Wed Jun 30 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-17 + 0.9.2-26
+- improved the x11 patch according to upstream (#598671)
+
+* Fri Jun 25 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-16 + 0.9.2-26
+- improved the x11 patch (#598671)
+
+* Thu Jun 24 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-15 + 0.9.2-26
+- changed _PATH_UNIX_X to unexistent file name (#598671)
+
+* Wed Jun 23 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-14 + 0.9.2-26
+- sftp works in deviceless chroot again (broken from 5.5p1-3)
+
+* Tue Jun  8 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-13 + 0.9.2-26
+- add option to switch out krb5_kuserok
+
+* Fri May 21 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-12 + 0.9.2-26
+- synchronize uid and gid for the user sshd
+
+* Thu May 20 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-11 + 0.9.2-26
+- Typo in ssh-ldap.conf(5) and ssh-ladap-helper(8)
+
+* Fri May 14 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-10 + 0.9.2-26
+- Repair the reference in man ssh-ldap-helper(8)
+- Repair the PubkeyAgent section in sshd_config(5)
+- Provide example ldap.conf
+
+* Thu May 13 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-9 + 0.9.2-26
+- Make the Ldap configuration widely compatible
+- create the aditional docs for LDAP support.
+
+* Thu May  6 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-8 + 0.9.2-26
+- Make LDAP config elements TLS_CACERT and TLS_REQCERT compatiple with pam_ldap (#589360)
+
+* Thu May  6 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-7 + 0.9.2-26
+- Make LDAP config element tls_checkpeer compatiple with nss_ldap (#589360)
+
+* Tue May  4 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-6 + 0.9.2-26
+- Comment spec.file
+- Sync patches from upstream
+
+* Mon May  3 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-5 + 0.9.2-26
+- Create separate ldap package
+- Tweak the ldap patch
+- Rename stderr patch properly
+
+* Thu Apr 29 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-4 + 0.9.2-26
+- Added LDAP support
+
+* Mon Apr 26 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-3 + 0.9.2-26
+- Ignore .bashrc output to stderr in the subsystems
+
+* Tue Apr 20 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-2 + 0.9.2-26
+- Drop dependency on man
+
+* Fri Apr 16 2010 Jan F. Chadima <jchadima@redhat.com> - 5.5p1-1 + 0.9.2-26
+- Update to 5.5p1
+
+* Fri Mar 12 2010 Jan F. Chadima <jchadima@redhat.com> - 5.4p1-3 + 0.9.2-25
+- repair configure script of pam_ssh_agent
+- repair error mesage in ssh-keygen
+
+* Fri Mar 12 2010 Jan F. Chadima <jchadima@redhat.com> - 5.4p1-2
+- source krb5-devel profile script only if exists
+
+* Tue Mar  9 2010 Jan F. Chadima <jchadima@redhat.com> - 5.4p1-1
+- Update to 5.4p1
+- discontinued support for nss-keys
+- discontinued support for scard
+
+* Wed Mar  3 2010 Jan F. Chadima <jchadima@redhat.com> - 5.4p1-0.snap20100302.1
+- Prepare update to 5.4p1
+
+* Mon Feb 15 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-22
+- ImplicitDSOLinking (#564824)
+
+* Fri Jan 29 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-21
+- Allow to use hardware crypto if awailable (#559555)
+
+* Mon Jan 25 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-20
+- optimized FD_CLOEXEC on accept socket (#541809)
+
+* Mon Jan 25 2010 Tomas Mraz <tmraz@redhat.com> - 5.3p1-19
+- updated pam_ssh_agent_auth to new version from upstream (just
+  a licence change)
+
+* Thu Jan 21 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-18
+- optimized RAND_cleanup patch (#557166)
+
+* Wed Jan 20 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-17
+- add RAND_cleanup at the exit of each program using RAND (#557166)
+
+* Tue Jan 19 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-16
+- set FD_CLOEXEC on accepted socket (#541809)
+
+* Fri Jan  8 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-15
+- replaced define by global in macros
+
+* Tue Jan  5 2010 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-14
+- Update the pka patch
+
+* Mon Dec 21 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-13
+- Update the audit patch
+
+* Fri Dec  4 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-12
+- Add possibility to autocreate only RSA key into initscript (#533339)
+
+* Fri Nov 27 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-11
+- Prepare NSS key patch for future SEC_ERROR_LOCKED_PASSWORD (#537411)
+
+* Tue Nov 24 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-10
+- Update NSS key patch (#537411, #356451)
+
+* Fri Nov 20 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-9
+- Add gssapi key exchange patch (#455351)
+
+* Fri Nov 20 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-8
+- Add public key agent patch (#455350)
+
+* Mon Nov  2 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-7
+- Repair canohost patch to allow gssapi to work when host is acessed via pipe proxy (#531849)
+
+* Thu Oct 29 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-6
+- Modify the init script to prevent it to hang during generating the keys (#515145)
+
+* Tue Oct 27 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-5
+- Add README.nss
+
+* Mon Oct 19 2009 Tomas Mraz <tmraz@redhat.com> - 5.3p1-4
+- Add pam_ssh_agent_auth module to a subpackage.
+
+* Fri Oct 16 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-3
+- Reenable audit.
+
+* Fri Oct  2 2009 Jan F. Chadima <jchadima@redhat.com> - 5.3p1-2
+- Upgrade to new wersion 5.3p1
+
+* Tue Sep 29 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-29
+- Resolve locking in ssh-add (#491312)
+
+* Thu Sep 24 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-28
+- Repair initscript to be acord to guidelines (#521860)
+- Add bugzilla# to application of edns and xmodifiers patch
+
+* Wed Sep 16 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-26
+- Changed pam stack to password-auth
+
+* Fri Sep 11 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-25
+- Dropped homechroot patch
+
+* Mon Sep  7 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-24
+- Add check for nosuid, nodev in homechroot
+
+* Tue Sep  1 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-23
+- add correct patch for ip-opts
+
+* Tue Sep  1 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-22
+- replace ip-opts patch by an upstream candidate version
+
+* Mon Aug 31 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-21
+- rearange selinux patch to be acceptable for upstream
+- replace seftp patch by an upstream version
+
+* Fri Aug 28 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-20
+- merged xmodifiers to redhat patch
+- merged gssapi-role to selinux patch
+- merged cve-2007_3102 to audit patch
+- sesftp patch only with WITH_SELINUX flag
+- rearange sesftp patch according to upstream request
+
+* Wed Aug 26 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-19
+- minor change in sesftp patch
+
+* Fri Aug 21 2009 Tomas Mraz <tmraz@redhat.com> - 5.2p1-18
+- rebuilt with new openssl
+
+* Thu Jul 30 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-17
+- Added dnssec support. (#205842)
+
+* Sat Jul 25 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 5.2p1-16
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild
+
+* Fri Jul 24 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-15
+- only INTERNAL_SFTP can be home-chrooted
+- save _u and _r parts of context changing to sftpd_t
+
+* Fri Jul 17 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-14
+- changed internal-sftp context to sftpd_t
+
+* Fri Jul  3 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-13
+- changed home length path patch to upstream version
+
+* Tue Jun 30 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-12
+- create '~/.ssh/known_hosts' within proper context
+
+* Mon Jun 29 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-11
+- length of home path in ssh now limited by PATH_MAX
+- correct timezone with daylight processing
+
+* Sat Jun 27 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-10
+- final version chroot %%h (sftp only)
+
+* Tue Jun 23 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-9
+- repair broken ls in chroot %%h
+
+* Fri Jun 12 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-8
+- add XMODIFIERS to exported environment (#495690)
+
+* Fri May 15 2009 Tomas Mraz <tmraz@redhat.com> - 5.2p1-6
+- allow only protocol 2 in the FIPS mode
+
+* Thu Apr 30 2009 Tomas Mraz <tmraz@redhat.com> - 5.2p1-5
+- do integrity verification only on binaries which are part
+  of the OpenSSH FIPS modules
+
+* Mon Apr 20 2009 Tomas Mraz <tmraz@redhat.com> - 5.2p1-4
+- log if FIPS mode is initialized
+- make aes-ctr cipher modes work in the FIPS mode
+
+* Fri Apr  3 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-3
+- fix logging after chroot
+- enable non root users to use chroot %%h in internal-sftp
+
+* Fri Mar 13 2009 Tomas Mraz <tmraz@redhat.com> - 5.2p1-2
+- add AES-CTR ciphers to the FIPS mode proposal
+
+* Mon Mar  9 2009 Jan F. Chadima <jchadima@redhat.com> - 5.2p1-1
+- upgrade to new upstream release
+
+* Thu Feb 26 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 5.1p1-8
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild
+
+* Thu Feb 12 2009 Tomas Mraz <tmraz@redhat.com> - 5.1p1-7
+- drop obsolete triggers
+- add testing FIPS mode support
+- LSBize the initscript (#247014)
+
+* Fri Jan 30 2009 Tomas Mraz <tmraz@redhat.com> - 5.1p1-6
+- enable use of ssl engines (#481100)
+
+* Thu Jan 15 2009 Tomas Mraz <tmraz@redhat.com> - 5.1p1-5
+- remove obsolete --with-rsh (#478298)
+- add pam_sepermit to allow blocking confined users in permissive mode
+  (#471746)
+- move system-auth after pam_selinux in the session stack
+
+* Thu Dec 11 2008 Tomas Mraz <tmraz@redhat.com> - 5.1p1-4
+- set FD_CLOEXEC on channel sockets (#475866)
+- adjust summary
+- adjust nss-keys patch so it is applicable without selinux patches (#470859)
+
+* Fri Oct 17 2008 Tomas Mraz <tmraz@redhat.com> - 5.1p1-3
+- fix compatibility with some servers (#466818)
+
+* Thu Jul 31 2008 Tomas Mraz <tmraz@redhat.com> - 5.1p1-2
+- fixed zero length banner problem (#457326)
+
+* Wed Jul 23 2008 Tomas Mraz <tmraz@redhat.com> - 5.1p1-1
+- upgrade to new upstream release
+- fixed a problem with public key authentication and explicitely
+  specified SELinux role
+
+* Wed May 21 2008 Tomas Mraz <tmraz@redhat.com> - 5.0p1-3
+- pass the connection socket to ssh-keysign (#447680)
+
+* Mon May 19 2008 Tomas Mraz <tmraz@redhat.com> - 5.0p1-2
+- add LANGUAGE to accepted/sent environment variables (#443231)
+- use pam_selinux to obtain the user context instead of doing it itself
+- unbreak server keep alive settings (patch from upstream)
+- small addition to scp manpage
+
+* Mon Apr  7 2008 Tomas Mraz <tmraz@redhat.com> - 5.0p1-1
+- upgrade to new upstream (#441066)
+- prevent initscript from killing itself on halt with upstart (#438449)
+- initscript status should show that the daemon is running
+  only when the main daemon is still alive (#430882)
+
+* Thu Mar  6 2008 Tomas Mraz <tmraz@redhat.com> - 4.7p1-10
+- fix race on control master and cleanup stale control socket (#436311)
+  patches by David Woodhouse
+
+* Fri Feb 29 2008 Tomas Mraz <tmraz@redhat.com> - 4.7p1-9
+- set FD_CLOEXEC on client socket
+- apply real fix for window size problem (#286181) from upstream
+- apply fix for the spurious failed bind from upstream
+- apply open handle leak in sftp fix from upstream
+
+* Tue Feb 12 2008 Dennis Gilmore <dennis@ausil.us> - 4.7p1-8
+- we build for sparcv9 now  and it needs -fPIE
+
+* Thu Jan  3 2008 Tomas Mraz <tmraz@redhat.com> - 4.7p1-7
+- fix gssapi auth with explicit selinux role requested (#427303) - patch
+  by Nalin Dahyabhai
+
+* Tue Dec  4 2007 Tomas Mraz <tmraz@redhat.com> - 4.7p1-6
+- explicitly source krb5-devel profile script
+
+* Tue Dec 04 2007 Release Engineering <rel-eng at fedoraproject dot org> - 4.7p1-5
+- Rebuild for openssl bump
+
+* Tue Nov 20 2007 Tomas Mraz <tmraz@redhat.com> - 4.7p1-4
+- do not copy /etc/localtime into the chroot as it is not
+  necessary anymore (#193184)
+- call setkeycreatecon when selinux context is established
+- test for NULL privk when freeing key (#391871) - patch by
+  Pierre Ossman
+
+* Mon Sep 17 2007 Tomas Mraz <tmraz@redhat.com> - 4.7p1-2
+- revert default window size adjustments (#286181)
+
+* Thu Sep  6 2007 Tomas Mraz <tmraz@redhat.com> - 4.7p1-1
+- upgrade to latest upstream
+- use libedit in sftp (#203009)
+- fixed audit log injection problem (CVE-2007-3102)
+
+* Thu Aug  9 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-8
+- fix sftp client problems on write error (#247802)
+- allow disabling autocreation of server keys (#235466)
+
+* Wed Jun 20 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-7
+- experimental NSS keys support
+- correctly setup context when empty level requested (#234951)
+
+* Tue Mar 20 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-6
+- mls level check must be done with default role same as requested
+
+* Mon Mar 19 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-5
+- make profile.d/gnome-ssh-askpass.* regular files (#226218)
+
+* Tue Feb 27 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-4
+- reject connection if requested mls range is not obtained (#229278)
+
+* Thu Feb 22 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-3
+- improve Buildroot
+- remove duplicate /etc/ssh from files
+
+* Tue Jan 16 2007 Tomas Mraz <tmraz@redhat.com> - 4.5p1-2
+- support mls on labeled networks (#220487)
+- support mls level selection on unlabeled networks
+- allow / in usernames in scp (only beginning /, ./, and ../ is special) 
+
+* Thu Dec 21 2006 Tomas Mraz <tmraz@redhat.com> - 4.5p1-1
+- update to 4.5p1 (#212606)
+
+* Thu Nov 30 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-14
+- fix gssapi with DNS loadbalanced clusters (#216857)
+
+* Tue Nov 28 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-13
+- improved pam_session patch so it doesn't regress, the patch is necessary
+  for the pam_session_close to be called correctly as uid 0
+
+* Fri Nov 10 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-12
+- CVE-2006-5794 - properly detect failed key verify in monitor (#214641)
+
+* Thu Nov  2 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-11
+- merge sshd initscript patches
+- kill all ssh sessions when stop is called in halt or reboot runlevel
+- remove -TERM option from killproc so we don't race on sshd restart
+
+* Mon Oct  2 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-10
+- improve gssapi-no-spnego patch (#208102)
+- CVE-2006-4924 - prevent DoS on deattack detector (#207957)
+- CVE-2006-5051 - don't call cleanups from signal handler (#208459)
+
+* Wed Aug 23 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-9
+- don't report duplicate syslog messages, use correct local time (#189158)
+- don't allow spnego as gssapi mechanism (from upstream)
+- fixed memleaks found by Coverity (from upstream)
+- allow ip options except source routing (#202856) (patch by HP)
+
+* Tue Aug  8 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-8
+- drop the pam-session patch from the previous build (#201341)
+- don't set IPV6_V6ONLY sock opt when listening on wildcard addr (#201594)
+
+* Thu Jul 20 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-7
+- dropped old ssh obsoletes
+- call the pam_session_open/close from the monitor when privsep is
+  enabled so it is always called as root (patch by Darren Tucker)
+
+* Mon Jul 17 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-6
+- improve selinux patch (by Jan Kiszka)
+- upstream patch for buffer append space error (#191940)
+- fixed typo in configure.ac (#198986)
+- added pam_keyinit to pam configuration (#198628)
+- improved error message when askpass dialog cannot grab
+  keyboard input (#198332)
+- buildrequires xauth instead of xorg-x11-xauth
+- fixed a few rpmlint warnings
+
+* Wed Jul 12 2006 Jesse Keating <jkeating@redhat.com> - 4.3p2-5.1
+- rebuild
+
+* Fri Apr 14 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-5
+- don't request pseudoterminal allocation if stdin is not tty (#188983)
+
+* Thu Mar  2 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-4
+- allow access if audit is not compiled in kernel (#183243)
+
+* Fri Feb 24 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-3
+- enable the subprocess in chroot to send messages to system log
+- sshd should prevent login if audit call fails
+
+* Tue Feb 21 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-2
+- print error from scp if not remote (patch by Bjorn Augustsson #178923)
+
+* Mon Feb 13 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p2-1
+- new version
+
+* Fri Feb 10 2006 Jesse Keating <jkeating@redhat.com> - 4.3p1-2.1
+- bump again for double-long bug on ppc(64)
+
+* Mon Feb  6 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p1-2
+- fixed another place where syslog was called in signal handler
+- pass locale environment variables to server, accept them there (#179851)
+
+* Wed Feb  1 2006 Tomas Mraz <tmraz@redhat.com> - 4.3p1-1
+- new version, dropped obsolete patches
+
+* Tue Dec 20 2005 Tomas Mraz <tmraz@redhat.com> - 4.2p1-10
+- hopefully make the askpass dialog less confusing (#174765)
+
+* Fri Dec 09 2005 Jesse Keating <jkeating@redhat.com>
+- rebuilt
+
+* Tue Nov 22 2005 Tomas Mraz <tmraz@redhat.com> - 4.2p1-9
+- drop x11-ssh-askpass from the package
+- drop old build_6x ifs from spec file
+- improve gnome-ssh-askpass so it doesn't reveal number of passphrase 
+  characters to person looking at the display
+- less hackish fix for the __USE_GNU problem
+
+* Fri Nov 18 2005 Nalin Dahyabhai <nalin@redhat.com> - 4.2p1-8
+- work around missing gccmakedep by wrapping makedepend in a local script
+- remove now-obsolete build dependency on "xauth"
+
+* Thu Nov 17 2005 Warren Togami <wtogami@redhat.com> - 4.2p1-7
+- xorg-x11-devel -> libXt-devel
+- rebuild for new xauth location so X forwarding works
+- buildreq audit-libs-devel
+- buildreq automake for aclocal
+- buildreq imake for xmkmf
+-  -D_GNU_SOURCE in flags in order to get it to build
+   Ugly hack to workaround openssh defining __USE_GNU which is
+   not allowed and causes problems according to Ulrich Drepper
+   fix this the correct way after FC5test1
+
+* Wed Nov  9 2005 Jeremy Katz <katzj@redhat.com> - 4.2p1-6
+- rebuild against new openssl
+
+* Fri Oct 28 2005 Tomas Mraz <tmraz@redhat.com> 4.2p1-5
+- put back the possibility to skip SELinux patch
+- add patch for user login auditing by Steve Grubb
+
+* Tue Oct 18 2005 Dan Walsh <dwalsh@redhat.com> 4.2p1-4
+- Change selinux patch to use get_default_context_with_rolelevel in libselinux.
+
+* Thu Oct 13 2005 Tomas Mraz <tmraz@redhat.com> 4.2p1-3
+- Update selinux patch to use getseuserbyname
+
+* Fri Oct  7 2005 Tomas Mraz <tmraz@redhat.com> 4.2p1-2
+- use include instead of pam_stack in pam config
+- use fork+exec instead of system in scp - CVE-2006-0225 (#168167)
+- upstream patch for displaying authentication errors
+
+* Tue Sep 06 2005 Tomas Mraz <tmraz@redhat.com> 4.2p1-1
+- upgrade to a new upstream version
+
+* Tue Aug 16 2005 Tomas Mraz <tmraz@redhat.com> 4.1p1-5
+- use x11-ssh-askpass if openssh-askpass-gnome is not installed (#165207)
+- install ssh-copy-id from contrib (#88707)
+
+* Wed Jul 27 2005 Tomas Mraz <tmraz@redhat.com> 4.1p1-4
+- don't deadlock on exit with multiple X forwarded channels (#152432)
+- don't use X11 port which can't be bound on all IP families (#163732)
+
+* Wed Jun 29 2005 Tomas Mraz <tmraz@redhat.com> 4.1p1-3
+- fix small regression caused by the nologin patch (#161956)
+- fix race in getpeername error checking (mindrot #1054)
+
+* Thu Jun  9 2005 Tomas Mraz <tmraz@redhat.com> 4.1p1-2
+- use only pam_nologin for nologin testing
+
+* Mon Jun  6 2005 Tomas Mraz <tmraz@redhat.com> 4.1p1-1
+- upgrade to a new upstream version
+- call pam_loginuid as a pam session module
+
+* Mon May 16 2005 Tomas Mraz <tmraz@redhat.com> 4.0p1-3
+- link libselinux only to sshd (#157678)
+
+* Mon Apr  4 2005 Tomas Mraz <tmraz@redhat.com> 4.0p1-2
+- fixed Local/RemoteForward in ssh_config.5 manpage
+- fix fatal when Local/RemoteForward is used and scp run (#153258)
+- don't leak user validity when using krb5 authentication
+
+* Thu Mar 24 2005 Tomas Mraz <tmraz@redhat.com> 4.0p1-1
+- upgrade to 4.0p1
+- remove obsolete groups patch
+
+* Wed Mar 16 2005 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Mon Feb 28 2005 Nalin Dahyabhai <nalin@redhat.com> 3.9p1-12
+- rebuild so that configure can detect that krb5_init_ets is gone now
+
+* Mon Feb 21 2005 Tomas Mraz <tmraz@redhat.com> 3.9p1-11
+- don't call syslog in signal handler
+- allow password authentication when copying from remote
+  to remote machine (#103364)
+
+* Wed Feb  9 2005 Tomas Mraz <tmraz@redhat.com>
+- add spaces to messages in initscript (#138508)
+
+* Tue Feb  8 2005 Tomas Mraz <tmraz@redhat.com> 3.9p1-10
+- enable trusted forwarding by default if X11 forwarding is 
+  required by user (#137685 and duplicates)
+- disable protocol 1 support by default in sshd server config (#88329)
+- keep the gnome-askpass dialog above others (#69131)
+
+* Fri Feb  4 2005 Tomas Mraz <tmraz@redhat.com>
+- change permissions on pam.d/sshd to 0644 (#64697)
+- patch initscript so it doesn't kill opened sessions if
+  the sshd daemon isn't running anymore (#67624)
+
+* Mon Jan  3 2005 Bill Nottingham <notting@redhat.com> 3.9p1-9
+- don't use initlog
+
+* Mon Nov 29 2004 Thomas Woerner <twoerner@redhat.com> 3.9p1-8.1
+- fixed PIE build for all architectures
+
+* Mon Oct  4 2004 Nalin Dahyabhai <nalin@redhat.com> 3.9p1-8
+- add a --enable-vendor-patchlevel option which allows a ShowPatchLevel option
+  to enable display of a vendor patch level during version exchange (#120285)
+- configure with --disable-strip to build useful debuginfo subpackages
+
+* Mon Sep 20 2004 Bill Nottingham <notting@redhat.com> 3.9p1-7
+- when using gtk2 for askpass, don't buildprereq gnome-libs-devel
+
+* Tue Sep 14 2004 Nalin Dahyabhai <nalin@redhat.com> 3.9p1-6
+- build
+
+* Mon Sep 13 2004 Nalin Dahyabhai <nalin@redhat.com>
+- disable ACSS support
+
+* Thu Sep 2 2004 Daniel Walsh <dwalsh@redhat.com> 3.9p1-5
+- Change selinux patch to use get_default_context_with_role in libselinux.
+
+* Thu Sep 2 2004 Daniel Walsh <dwalsh@redhat.com> 3.9p1-4
+- Fix patch
+	* Bad debug statement.
+	* Handle root/sysadm_r:kerberos
+
+* Thu Sep 2 2004 Daniel Walsh <dwalsh@redhat.com> 3.9p1-3
+- Modify Colin Walter's patch to allow specifying rule during connection
+
+* Tue Aug 31 2004 Daniel Walsh <dwalsh@redhat.com> 3.9p1-2
+- Fix TTY handling for SELinux
+
+* Tue Aug 24 2004 Daniel Walsh <dwalsh@redhat.com> 3.9p1-1
+- Update to upstream
+
+* Sun Aug 1 2004 Alan Cox <alan@redhat.com> 3.8.1p1-5
+- Apply buildreq fixup patch (#125296)
+
+* Tue Jun 15 2004 Daniel Walsh <dwalsh@redhat.com> 3.8.1p1-4
+- Clean up patch for upstream submission.
+
+* Tue Jun 15 2004 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Wed Jun 9 2004 Daniel Walsh <dwalsh@redhat.com> 3.8.1p1-2
+- Remove use of pam_selinux and patch selinux in directly.  
+
+* Mon Jun  7 2004 Nalin Dahyabhai <nalin@redhat.com> 3.8.1p1-1
+- request gssapi-with-mic by default but not delegation (flag day for anyone
+  who used previous gssapi patches)
+- no longer request x11 forwarding by default
+
+* Thu Jun 3 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-36
+- Change pam file to use open and close with pam_selinux
+
+* Tue Jun  1 2004 Nalin Dahyabhai <nalin@redhat.com> 3.8.1p1-0
+- update to 3.8.1p1
+- add workaround from CVS to reintroduce passwordauth using pam
+
+* Tue Jun 1 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-35
+- Remove CLOSEXEC on STDERR
+
+* Tue Mar 16 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-34
+
+* Wed Mar 03 2004 Phil Knirsch <pknirsch@redhat.com> 3.6.1p2-33.30.1
+- Built RHLE3 U2 update package.
+
+* Wed Mar 3 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-33
+- Close file descriptors on exec 
+
+* Mon Mar  1 2004 Thomas Woerner <twoerner@redhat.com> 3.6.1p2-32
+- fixed pie build
+
+* Thu Feb 26 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-31
+- Add restorecon to startup scripts
+
+* Thu Feb 26 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-30
+- Add multiple qualified to openssh
+
+* Mon Feb 23 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-29
+- Eliminate selinux code and use pam_selinux
+
+* Fri Feb 13 2004 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Mon Jan 26 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-27
+- turn off pie on ppc
+
+* Mon Jan 26 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-26
+- fix is_selinux_enabled
+
+* Wed Jan 14 2004 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-25
+- Rebuild to grab shared libselinux
+
+* Wed Dec 3 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-24
+- turn on selinux
+
+* Tue Nov 18 2003 Nalin Dahyabhai <nalin@redhat.com>
+- un#ifdef out code for reporting password expiration in non-privsep
+  mode (#83585)
+
+* Mon Nov 10 2003 Nalin Dahyabhai <nalin@redhat.com>
+- add machinery to build with/without -fpie/-pie, default to doing so
+
+* Thu Nov 06 2003 David Woodhouse <dwmw2@redhat.com> 3.6.1p2-23
+- Don't whinge about getsockopt failing (#109161)
+
+* Fri Oct 24 2003 Nalin Dahyabhai <nalin@redhat.com>
+- add missing buildprereq on zlib-devel (#104558)
+
+* Mon Oct 13 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-22
+- turn selinux off
+
+* Mon Oct 13 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-21.sel
+- turn selinux on
+
+* Fri Sep 19 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-21
+- turn selinux off
+
+* Fri Sep 19 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-20.sel
+- turn selinux on
+
+* Fri Sep 19 2003 Nalin Dahyabhai <nalin@redhat.com>
+- additional fix for apparently-never-happens double-free in buffer_free()
+- extend fix for #103998 to cover SSH1
+
+* Wed Sep 17 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-19
+- rebuild
+
+* Wed Sep 17 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-18
+- additional buffer manipulation cleanups from Solar Designer
+
+* Wed Sep 17 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-17
+- turn selinux off
+
+* Wed Sep 17 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-16.sel
+- turn selinux on
+
+* Tue Sep 16 2003 Bill Nottingham <notting@redhat.com> 3.6.1p2-15
+- rebuild
+
+* Tue Sep 16 2003 Bill Nottingham <notting@redhat.com> 3.6.1p2-14
+- additional buffer manipulation fixes (CAN-2003-0695)
+
+* Tue Sep 16 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-13.sel
+- turn selinux on
+
+* Tue Sep 16 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-12
+- rebuild
+
+* Tue Sep 16 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-11
+- apply patch to store the correct buffer size in allocated buffers
+  (CAN-2003-0693)
+- skip the initial PAM authentication attempt with an empty password if
+  empty passwords are not permitted in our configuration (#103998)
+
+* Fri Sep 5 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-10
+- turn selinux off
+
+* Fri Sep 5 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-9.sel
+- turn selinux on
+
+* Tue Aug 26 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-8
+- Add BuildPreReq gtk2-devel if gtk2
+
+* Tue Aug 12 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-7
+- rebuild
+
+* Tue Aug 12 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-6
+- modify patch which clears the supplemental group list at startup to only
+  complain if setgroups() fails if sshd has euid == 0
+- handle krb5 installed in %%{_prefix} or elsewhere by using krb5-config
+
+* Mon Jul 28 2003 Daniel Walsh <dwalsh@redhat.com> 3.6.1p2-5
+- Add SELinux patch
+
+* Tue Jul 22 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-4
+- rebuild
+
+* Wed Jul 16 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-3
+- rebuild
+
+* Wed Jul 16 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-2
+- rebuild
+
+* Thu Jun  5 2003 Nalin Dahyabhai <nalin@redhat.com> 3.6.1p2-1
+- update to 3.6.1p2
+
+* Wed Jun 04 2003 Elliot Lee <sopwith@redhat.com>
+6 rebuilt
+
+* Mon Mar 24 2003 Florian La Roche <Florian.LaRoche@redhat.de>
+- add patch for getsockopt() call to work on bigendian 64bit archs
+
+* Fri Feb 14 2003 Nalin Dahyabhai <nalin@redhat.com> 3.5p1-6
+- move scp to the -clients subpackage, because it directly depends on ssh
+  which is also in -clients (#84329)
+
+* Mon Feb 10 2003 Nalin Dahyabhai <nalin@redhat.com> 3.5p1-5
+- rebuild
+
+* Wed Jan 22 2003 Tim Powers <timp@redhat.com>
+- rebuilt
+
+* Tue Jan  7 2003 Nalin Dahyabhai <nalin@redhat.com> 3.5p1-3
+- rebuild
+
+* Tue Nov 12 2002 Nalin Dahyabhai <nalin@redhat.com> 3.5p1-2
+- patch PAM configuration to use relative path names for the modules, allowing
+  us to not worry about which arch the modules are built for on multilib systems
+
+* Tue Oct 15 2002 Nalin Dahyabhai <nalin@redhat.com> 3.5p1-1
+- update to 3.5p1, merging in filelist/perm changes from the upstream spec
+
+* Fri Oct  4 2002 Nalin Dahyabhai <nalin@redhat.com> 3.4p1-3
+- merge
+
+* Thu Sep 12 2002  Than Ngo <than@redhat.com> 3.4p1-2.1
+- fix to build on multilib systems
+
+* Thu Aug 29 2002 Curtis Zinzilieta <curtisz@redhat.com> 3.4p1-2gss
+- added gssapi patches and uncommented patch here
+
+* Wed Aug 14 2002 Nalin Dahyabhai <nalin@redhat.com> 3.4p1-2
+- pull patch from CVS to fix too-early free in ssh-keysign (#70009)
+
+* Thu Jun 27 2002 Nalin Dahyabhai <nalin@redhat.com> 3.4p1-1
+- 3.4p1
+- drop anon mmap patch
+
+* Tue Jun 25 2002 Nalin Dahyabhai <nalin@redhat.com> 3.3p1-2
+- rework the close-on-exit docs
+- include configuration file man pages
+- make use of nologin as the privsep shell optional
+
+* Mon Jun 24 2002 Nalin Dahyabhai <nalin@redhat.com> 3.3p1-1
+- update to 3.3p1
+- merge in spec file changes from upstream (remove setuid from ssh, ssh-keysign)
+- disable gtk2 askpass
+- require pam-devel by filename rather than by package for erratum
+- include patch from Solar Designer to work around anonymous mmap failures
+
+* Fri Jun 21 2002 Tim Powers <timp@redhat.com>
+- automated rebuild
+
+* Fri Jun  7 2002 Nalin Dahyabhai <nalin@redhat.com> 3.2.3p1-3
+- don't require autoconf any more
+
+* Fri May 31 2002 Nalin Dahyabhai <nalin@redhat.com> 3.2.3p1-2
+- build gnome-ssh-askpass with gtk2
+
+* Tue May 28 2002 Nalin Dahyabhai <nalin@redhat.com> 3.2.3p1-1
+- update to 3.2.3p1
+- merge in spec file changes from upstream
+
+* Fri May 17 2002 Nalin Dahyabhai <nalin@redhat.com> 3.2.2p1-1
+- update to 3.2.2p1
+
+* Fri May 17 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-4
+- drop buildreq on db1-devel
+- require pam-devel by package name
+- require autoconf instead of autoconf253 again
+
+* Tue Apr  2 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-3
+- pull patch from CVS to avoid printing error messages when some of the
+  default keys aren't available when running ssh-add
+- refresh to current revisions of Simon's patches
+ 
+* Thu Mar 21 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-2gss
+- reintroduce Simon's gssapi patches
+- add buildprereq for autoconf253, which is needed to regenerate configure
+  after applying the gssapi patches
+- refresh to the latest version of Markus's patch to build properly with
+  older versions of OpenSSL
+
+* Thu Mar  7 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-2
+- bump and grind (through the build system)
+
+* Thu Mar  7 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-1
+- require sharutils for building (mindrot #137)
+- require db1-devel only when building for 6.x (#55105), which probably won't
+  work anyway (3.1 requires OpenSSL 0.9.6 to build), but what the heck
+- require pam-devel by file (not by package name) again
+- add Markus's patch to compile with OpenSSL 0.9.5a (from
+  http://bugzilla.mindrot.org/show_bug.cgi?id=141) and apply it if we're
+  building for 6.x
+
+* Thu Mar  7 2002 Nalin Dahyabhai <nalin@redhat.com> 3.1p1-0
+- update to 3.1p1
+
+* Tue Mar  5 2002 Nalin Dahyabhai <nalin@redhat.com> SNAP-20020305
+- update to SNAP-20020305
+- drop debug patch, fixed upstream
+
+* Wed Feb 20 2002 Nalin Dahyabhai <nalin@redhat.com> SNAP-20020220
+- update to SNAP-20020220 for testing purposes (you've been warned, if there's
+  anything to be warned about, gss patches won't apply, I don't mind)
+
+* Wed Feb 13 2002 Nalin Dahyabhai <nalin@redhat.com> 3.0.2p1-3
+- add patches from Simon Wilkinson and Nicolas Williams for GSSAPI key
+  exchange, authentication, and named key support
+
+* Wed Jan 23 2002 Nalin Dahyabhai <nalin@redhat.com> 3.0.2p1-2
+- remove dependency on db1-devel, which has just been swallowed up whole
+  by gnome-libs-devel
+
+* Sat Dec 29 2001 Nalin Dahyabhai <nalin@redhat.com>
+- adjust build dependencies so that build6x actually works right (fix
+  from Hugo van der Kooij)
+
+* Tue Dec  4 2001 Nalin Dahyabhai <nalin@redhat.com> 3.0.2p1-1
+- update to 3.0.2p1
+
+* Fri Nov 16 2001 Nalin Dahyabhai <nalin@redhat.com> 3.0.1p1-1
+- update to 3.0.1p1
+
+* Tue Nov 13 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to current CVS (not for use in distribution)
+
+* Thu Nov  8 2001 Nalin Dahyabhai <nalin@redhat.com> 3.0p1-1
+- merge some of Damien Miller <djm@mindrot.org> changes from the upstream
+  3.0p1 spec file and init script
+
+* Wed Nov  7 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to 3.0p1
+- update to x11-ssh-askpass 1.2.4.1
+- change build dependency on a file from pam-devel to the pam-devel package
+- replace primes with moduli
+
+* Thu Sep 27 2001 Nalin Dahyabhai <nalin@redhat.com> 2.9p2-9
+- incorporate fix from Markus Friedl's advisory for IP-based authorization bugs
+
+* Thu Sep 13 2001 Bernhard Rosenkraenzer <bero@redhat.com> 2.9p2-8
+- Merge changes to rescue build from current sysadmin survival cd
+
+* Thu Sep  6 2001 Nalin Dahyabhai <nalin@redhat.com> 2.9p2-7
+- fix scp's server's reporting of file sizes, and build with the proper
+  preprocessor define to get large-file capable open(), stat(), etc.
+  (sftp has been doing this correctly all along) (#51827)
+- configure without --with-ipv4-default on RHL 7.x and newer (#45987,#52247)
+- pull cvs patch to fix support for /etc/nologin for non-PAM logins (#47298)
+- mark profile.d scriptlets as config files (#42337)
+- refer to Jason Stone's mail for zsh workaround for exit-hanging quasi-bug
+- change a couple of log() statements to debug() statements (#50751)
+- pull cvs patch to add -t flag to sshd (#28611)
+- clear fd_sets correctly (one bit per FD, not one byte per FD) (#43221)
+
+* Mon Aug 20 2001 Nalin Dahyabhai <nalin@redhat.com> 2.9p2-6
+- add db1-devel as a BuildPrerequisite (noted by Hans Ecke)
+
+* Thu Aug 16 2001 Nalin Dahyabhai <nalin@redhat.com>
+- pull cvs patch to fix remote port forwarding with protocol 2
+
+* Thu Aug  9 2001 Nalin Dahyabhai <nalin@redhat.com>
+- pull cvs patch to add session initialization to no-pty sessions
+- pull cvs patch to not cut off challengeresponse auth needlessly
+- refuse to do X11 forwarding if xauth isn't there, handy if you enable
+  it by default on a system that doesn't have X installed (#49263)
+
+* Wed Aug  8 2001 Nalin Dahyabhai <nalin@redhat.com>
+- don't apply patches to code we don't intend to build (spotted by Matt Galgoci)
+
+* Mon Aug  6 2001 Nalin Dahyabhai <nalin@redhat.com>
+- pass OPTIONS correctly to initlog (#50151)
+
+* Wed Jul 25 2001 Nalin Dahyabhai <nalin@redhat.com>
+- switch to x11-ssh-askpass 1.2.2
+
+* Wed Jul 11 2001 Nalin Dahyabhai <nalin@redhat.com>
+- rebuild in new environment
+
+* Mon Jun 25 2001 Nalin Dahyabhai <nalin@redhat.com>
+- disable the gssapi patch
+
+* Mon Jun 18 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to 2.9p2
+- refresh to a new version of the gssapi patch
+
+* Thu Jun  7 2001 Nalin Dahyabhai <nalin@redhat.com>
+- change Copyright: BSD to License: BSD
+- add Markus Friedl's unverified patch for the cookie file deletion problem
+  so that we can verify it
+- drop patch to check if xauth is present (was folded into cookie patch)
+- don't apply gssapi patches for the errata candidate
+- clear supplemental groups list at startup
+
+* Fri May 25 2001 Nalin Dahyabhai <nalin@redhat.com>
+- fix an error parsing the new default sshd_config
+- add a fix from Markus Friedl (via openssh-unix-dev) for ssh-keygen not
+  dealing with comments right
+
+* Thu May 24 2001 Nalin Dahyabhai <nalin@redhat.com>
+- add in Simon Wilkinson's GSSAPI patch to give it some testing in-house,
+  to be removed before the next beta cycle because it's a big departure
+  from the upstream version
+
+* Thu May  3 2001 Nalin Dahyabhai <nalin@redhat.com>
+- finish marking strings in the init script for translation
+- modify init script to source /etc/sysconfig/sshd and pass $OPTIONS to sshd
+  at startup (change merged from openssh.com init script, originally by
+  Pekka Savola)
+- refuse to do X11 forwarding if xauth isn't there, handy if you enable
+  it by default on a system that doesn't have X installed
+
+* Wed May  2 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to 2.9
+- drop various patches that came from or went upstream or to or from CVS
+
+* Wed Apr 18 2001 Nalin Dahyabhai <nalin@redhat.com>
+- only require initscripts 5.00 on 6.2 (reported by Peter Bieringer)
+
+* Sun Apr  8 2001 Preston Brown <pbrown@redhat.com>
+- remove explicit openssl requirement, fixes builddistro issue
+- make initscript stop() function wait until sshd really dead to avoid 
+  races in condrestart
+
+* Mon Apr  2 2001 Nalin Dahyabhai <nalin@redhat.com>
+- mention that challengereponse supports PAM, so disabling password doesn't
+  limit users to pubkey and rsa auth (#34378)
+- bypass the daemon() function in the init script and call initlog directly,
+  because daemon() won't start a daemon it detects is already running (like
+  open connections)
+- require the version of openssl we had when we were built
+
+* Fri Mar 23 2001 Nalin Dahyabhai <nalin@redhat.com>
+- make do_pam_setcred() smart enough to know when to establish creds and
+  when to reinitialize them
+- add in a couple of other fixes from Damien for inclusion in the errata
+
+* Thu Mar 22 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to 2.5.2p2
+- call setcred() again after initgroups, because the "creds" could actually
+  be group memberships
+
+* Tue Mar 20 2001 Nalin Dahyabhai <nalin@redhat.com>
+- update to 2.5.2p1 (includes endianness fixes in the rijndael implementation)
+- don't enable challenge-response by default until we find a way to not
+  have too many userauth requests (we may make up to six pubkey and up to
+  three password attempts as it is)
+- remove build dependency on rsh to match openssh.com's packages more closely
+
+* Sat Mar  3 2001 Nalin Dahyabhai <nalin@redhat.com>
+- remove dependency on openssl -- would need to be too precise
+
+* Fri Mar  2 2001 Nalin Dahyabhai <nalin@redhat.com>
+- rebuild in new environment
+
+* Mon Feb 26 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Revert the patch to move pam_open_session.
+- Init script and spec file changes from Pekka Savola. (#28750)
+- Patch sftp to recognize '-o protocol' arguments. (#29540)
+
+* Thu Feb 22 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Chuck the closing patch.
+- Add a trigger to add host keys for protocol 2 to the config file, now that
+  configuration file syntax requires us to specify it with HostKey if we
+  specify any other HostKey values, which we do.
+
+* Tue Feb 20 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Redo patch to move pam_open_session after the server setuid()s to the user.
+- Rework the nopam patch to use be picked up by autoconf.
+
+* Mon Feb 19 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Update for 2.5.1p1.
+- Add init script mods from Pekka Savola.
+- Tweak the init script to match the CVS contrib script more closely.
+- Redo patch to ssh-add to try to adding both identity and id_dsa to also try
+  adding id_rsa.
+
+* Fri Feb 16 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Update for 2.5.0p1.
+- Use $RPM_OPT_FLAGS instead of -O when building gnome-ssh-askpass
+- Resync with parts of Damien Miller's openssh.spec from CVS, including
+  update of x11 askpass to 1.2.0.
+- Only require openssl (don't prereq) because we generate keys in the init
+  script now.
+
+* Tue Feb 13 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Don't open a PAM session until we've forked and become the user (#25690).
+- Apply Andrew Bartlett's patch for letting pam_authenticate() know which
+  host the user is attempting a login from.
+- Resync with parts of Damien Miller's openssh.spec from CVS.
+- Don't expose KbdInt responses in debug messages (from CVS).
+- Detect and handle errors in rsa_{public,private}_decrypt (from CVS).
+
+* Wed Feb  7 2001 Trond Eivind Glomsrxd <teg@redhat.com>
+- i18n-tweak to initscript.
+
+* Tue Jan 23 2001 Nalin Dahyabhai <nalin@redhat.com>
+- More gettextizing.
+- Close all files after going into daemon mode (needs more testing).
+- Extract patch from CVS to handle auth banners (in the client).
+- Extract patch from CVS to handle compat weirdness.
+
+* Fri Jan 19 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Finish with the gettextizing.
+
+* Thu Jan 18 2001 Nalin Dahyabhai <nalin@redhat.com>
+- Fix a bug in auth2-pam.c (#23877)
+- Gettextize the init script.
+
+* Wed Dec 20 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Incorporate a switch for using PAM configs for 6.x, just in case.
+
+* Tue Dec  5 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Incorporate Bero's changes for a build specifically for rescue CDs.
+
+* Wed Nov 29 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Don't treat pam_setcred() failure as fatal unless pam_authenticate() has
+  succeeded, to allow public-key authentication after a failure with "none"
+  authentication.  (#21268)
+
+* Tue Nov 28 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to x11-askpass 1.1.1. (#21301)
+- Don't second-guess fixpaths, which causes paths to get fixed twice. (#21290)
+
+* Mon Nov 27 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Merge multiple PAM text messages into subsequent prompts when possible when
+  doing keyboard-interactive authentication.
+
+* Sun Nov 26 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Disable the built-in MD5 password support.  We're using PAM.
+- Take a crack at doing keyboard-interactive authentication with PAM, and
+  enable use of it in the default client configuration so that the client
+  will try it when the server disallows password authentication.
+- Build with debugging flags.  Build root policies strip all binaries anyway.
+
+* Tue Nov 21 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Use DESTDIR instead of %%makeinstall.
+- Remove /usr/X11R6/bin from the path-fixing patch.
+
+* Mon Nov 20 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Add the primes file from the latest snapshot to the main package (#20884).
+- Add the dev package to the prereq list (#19984).
+- Remove the default path and mimic login's behavior in the server itself.
+
+* Fri Nov 17 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Resync with conditional options in Damien Miller's .spec file for an errata.
+- Change libexecdir from %%{_libexecdir}/ssh to %%{_libexecdir}/openssh.
+
+* Tue Nov  7 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to OpenSSH 2.3.0p1.
+- Update to x11-askpass 1.1.0.
+- Enable keyboard-interactive authentication.
+
+* Mon Oct 30 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to ssh-askpass-x11 1.0.3.
+- Change authentication related messages to be private (#19966).
+
+* Tue Oct 10 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Patch ssh-keygen to be able to list signatures for DSA public key files
+  it generates.
+
+* Thu Oct  5 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Add BuildPreReq on /usr/include/security/pam_appl.h to be sure we always
+  build PAM authentication in.
+- Try setting SSH_ASKPASS if gnome-ssh-askpass is installed.
+- Clean out no-longer-used patches.
+- Patch ssh-add to try to add both identity and id_dsa, and to error only
+  when neither exists.
+
+* Mon Oct  2 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update x11-askpass to 1.0.2. (#17835)
+- Add BuildPreReqs for /bin/login and /usr/bin/rsh so that configure will
+  always find them in the right place. (#17909)
+- Set the default path to be the same as the one supplied by /bin/login, but
+  add /usr/X11R6/bin. (#17909)
+- Try to handle obsoletion of ssh-server more cleanly.  Package names
+  are different, but init script name isn't. (#17865)
+
+* Wed Sep  6 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 2.2.0p1. (#17835)
+- Tweak the init script to allow proper restarting. (#18023)
+
+* Wed Aug 23 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 20000823 snapshot.
+- Change subpackage requirements from %%{version} to %%{version}-%%{release}
+- Back out the pipe patch.
+
+* Mon Jul 17 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 2.1.1p4, which includes fixes for config file parsing problems.
+- Move the init script back.
+- Add Damien's quick fix for wackiness.
+
+* Wed Jul 12 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 2.1.1p3, which includes fixes for X11 forwarding and strtok().
+
+* Thu Jul  6 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Move condrestart to server postun.
+- Move key generation to init script.
+- Actually use the right patch for moving the key generation to the init script.
+- Clean up the init script a bit.
+
+* Wed Jul  5 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Fix X11 forwarding, from mail post by Chan Shih-Ping Richard.
+
+* Sun Jul  2 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 2.1.1p2.
+- Use of strtok() considered harmful.
+
+* Sat Jul  1 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Get the build root out of the man pages.
+
+* Thu Jun 29 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Add and use condrestart support in the init script.
+- Add newer initscripts as a prereq.
+
+* Tue Jun 27 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Build in new environment (release 2)
+- Move -clients subpackage to Applications/Internet group
+
+* Fri Jun  9 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Update to 2.2.1p1
+
+* Sat Jun  3 2000 Nalin Dahyabhai <nalin@redhat.com>
+- Patch to build with neither RSA nor RSAref.
+- Miscellaneous FHS-compliance tweaks.
+- Fix for possibly-compressed man pages.
+
+* Wed Mar 15 2000 Damien Miller <djm@ibs.com.au>
+- Updated for new location
+- Updated for new gnome-ssh-askpass build
+
+* Sun Dec 26 1999 Damien Miller <djm@mindrot.org>
+- Added Jim Knoble's <jmknoble@pobox.com> askpass
+
+* Mon Nov 15 1999 Damien Miller <djm@mindrot.org>
+- Split subpackages further based on patch from jim knoble <jmknoble@pobox.com>
+
+* Sat Nov 13 1999 Damien Miller <djm@mindrot.org>
+- Added 'Obsoletes' directives
+
+* Tue Nov 09 1999 Damien Miller <djm@ibs.com.au>
+- Use make install
+- Subpackages
+
+* Mon Nov 08 1999 Damien Miller <djm@ibs.com.au>
+- Added links for slogin
+- Fixed perms on manpages
+
+* Sat Oct 30 1999 Damien Miller <djm@ibs.com.au>
+- Renamed init script
+
+* Fri Oct 29 1999 Damien Miller <djm@ibs.com.au>
+- Back to old binary names
+
+* Thu Oct 28 1999 Damien Miller <djm@ibs.com.au>
+- Use autoconf
+- New binary names
+
+* Wed Oct 27 1999 Damien Miller <djm@ibs.com.au>
+- Initial RPMification, based on Jan "Yenya" Kasprzak's <kas@fi.muni.cz> spec.
diff --git a/pam_ssh_agent-rmheaders b/pam_ssh_agent-rmheaders
new file mode 100644
index 0000000..ab5899f
--- /dev/null
+++ b/pam_ssh_agent-rmheaders
@@ -0,0 +1,36 @@
+authfd.c
+authfd.h
+atomicio.c
+atomicio.h
+bufaux.c
+bufbn.c
+buffer.h
+buffer.c
+cleanup.c
+cipher.h
+compat.h
+entropy.c
+entropy.h
+fatal.c
+includes.h
+kex.h
+key.c
+key.h
+log.c
+log.h
+match.h
+misc.c
+misc.h
+pathnames.h
+platform.h
+rsa.h
+ssh-dss.c
+ssh-rsa.c
+ssh.h
+ssh2.h
+uidswap.c
+uidswap.h
+uuencode.c
+uuencode.h
+xmalloc.c
+xmalloc.h
diff --git a/pam_ssh_agent_auth-0.10.2-compat.patch b/pam_ssh_agent_auth-0.10.2-compat.patch
new file mode 100644
index 0000000..0822b61
--- /dev/null
+++ b/pam_ssh_agent_auth-0.10.2-compat.patch
@@ -0,0 +1,992 @@
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/get_command_line.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/get_command_line.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/get_command_line.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/get_command_line.c	2020-09-23 10:52:16.424001475 +0200
+@@ -27,6 +27,7 @@
+  * or implied, of Jamie Beverly.
+  */
+ 
++#include <stdlib.h>
+ #include <stdio.h>
+ #include <errno.h>
+ #include <string.h>
+@@ -66,8 +67,8 @@ proc_pid_cmdline(char *** inargv)
+                 case EOF:
+                 case '\0':
+                     if (len > 0) { 
+-                        argv = pamsshagentauth_xrealloc(argv, count + 1, sizeof(*argv));
+-                        argv[count] = pamsshagentauth_xcalloc(len + 1, sizeof(*argv[count]));
++                        argv = xreallocarray(argv, count + 1, sizeof(*argv));
++                        argv[count] = xcalloc(len + 1, sizeof(*argv[count]));
+                         strncpy(argv[count++], argbuf, len);
+                         memset(argbuf, '\0', MAX_LEN_PER_CMDLINE_ARG + 1);
+                         len = 0;
+@@ -106,9 +107,9 @@ pamsshagentauth_free_command_line(char *
+ {
+     size_t i;
+     for (i = 0; i < n_args; i++)
+-        pamsshagentauth_xfree(argv[i]);
++        free(argv[i]);
+ 
+-    pamsshagentauth_xfree(argv);
++    free(argv);
+     return;
+ }
+ 
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/identity.h.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/identity.h
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/identity.h.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/identity.h	2020-09-23 10:52:16.424001475 +0200
+@@ -30,8 +30,8 @@
+ #include "openbsd-compat/sys-queue.h"
+ #include "xmalloc.h"
+ #include "log.h"
+-#include "buffer.h"
+-#include "key.h"
++#include "sshbuf.h"
++#include "sshkey.h"
+ #include "authfd.h"
+ #include <stdio.h>
+ 
+@@ -41,7 +41,7 @@ typedef struct idlist Idlist;
+ struct identity {
+     TAILQ_ENTRY(identity) next;
+     AuthenticationConnection *ac;   /* set if agent supports key */
+-    Key *key;           /* public/private key */
++    struct sshkey *key;           /* public/private key */
+     char    *filename;      /* comment for agent-only keys */
+     int tried;
+     int isprivate;      /* key points to the private key */
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/iterate_ssh_agent_keys.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/iterate_ssh_agent_keys.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/iterate_ssh_agent_keys.c.psaa-compat	2020-09-23 10:52:16.421001434 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/iterate_ssh_agent_keys.c	2020-09-23 10:52:16.424001475 +0200
+@@ -36,8 +36,8 @@
+ #include "openbsd-compat/sys-queue.h"
+ #include "xmalloc.h"
+ #include "log.h"
+-#include "buffer.h"
+-#include "key.h"
++#include "sshbuf.h"
++#include "sshkey.h"
+ #include "authfd.h"
+ #include <stdio.h>
+ #include <openssl/evp.h>
+@@ -58,6 +58,8 @@
+ #include "get_command_line.h"
+ extern char **environ;
+ 
++#define PAM_SSH_AGENT_AUTH_REQUESTv1 101
++
+ /* 
+  * Added by Jamie Beverly, ensure socket fd points to a socket owned by the user 
+  * A cursory check is done, but to avoid race conditions, it is necessary 
+@@ -77,7 +79,7 @@ log_action(char ** action, size_t count)
+     if (count == 0)
+         return NULL;
+    
+-    buf = pamsshagentauth_xcalloc((count * MAX_LEN_PER_CMDLINE_ARG) + (count * 3), sizeof(*buf));
++    buf = xcalloc((count * MAX_LEN_PER_CMDLINE_ARG) + (count * 3), sizeof(*buf));
+     for (i = 0; i < count; i++) {
+         strcat(buf, (i > 0) ? " '" : "'");
+         strncat(buf, action[i], MAX_LEN_PER_CMDLINE_ARG);
+@@ -87,21 +89,25 @@ log_action(char ** action, size_t count)
+ }
+ 
+ void
+-agent_action(Buffer *buf, char ** action, size_t count)
++agent_action(struct sshbuf **buf, char ** action, size_t count)
+ {
+     size_t i;
+-    pamsshagentauth_buffer_init(buf);
++    int r;
+ 
+-    pamsshagentauth_buffer_put_int(buf, count);
++    if ((*buf = sshbuf_new()) == NULL)
++        fatal("%s: sshbuf_new failed", __func__);
++    if ((r = sshbuf_put_u32(*buf, count)) != 0)
++        fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+     for (i = 0; i < count; i++) {
+-        pamsshagentauth_buffer_put_cstring(buf, action[i]);
++        if ((r = sshbuf_put_cstring(*buf, action[i])) != 0)
++            fatal("%s: buffer error: %s", __func__, ssh_err(r));
+     }
+ }
+ 
+ 
+-void
+-pamsshagentauth_session_id2_gen(Buffer * session_id2, const char * user,
++static void
++pamsshagentauth_session_id2_gen(struct sshbuf ** session_id2, const char * user,
+                                 const char * ruser, const char * servicename)
+ {
+     u_char *cookie = NULL;
+@@ -114,22 +120,23 @@ pamsshagentauth_session_id2_gen(Buffer *
+     char ** reported_argv = NULL;
+     size_t count = 0;
+     char * action_logbuf = NULL;
+-    Buffer action_agentbuf;
++    struct sshbuf *action_agentbuf = NULL;
+     uint8_t free_logbuf = 0;
+     char * retc;
+     int32_t reti;
++    int r;
+ 
+-    rnd = pamsshagentauth_arc4random();
++    rnd = arc4random();
+     cookie_len = ((uint8_t) rnd);
+     while (cookie_len < 16) { 
+         cookie_len += 16;                                          /* Add 16 bytes to the size to ensure that while the length is random, the length is always reasonable; ticket #18 */
+     }
+ 
+-    cookie = pamsshagentauth_xcalloc(1,cookie_len);
++    cookie = xcalloc(1, cookie_len);
+ 
+     for (i = 0; i < cookie_len; i++) {
+         if (i % 4 == 0) {
+-            rnd = pamsshagentauth_arc4random();
++            rnd = arc4random();
+         }
+         cookie[i] = (u_char) rnd;
+         rnd >>= 8;
+@@ -144,7 +151,8 @@ pamsshagentauth_session_id2_gen(Buffer *
+     }
+     else {
+         action_logbuf = "unknown on this platform";
+-        pamsshagentauth_buffer_init(&action_agentbuf); /* stays empty, means unavailable */
++        if ((action_agentbuf = sshbuf_new()) == NULL) /* stays empty, means unavailable */
++            fatal("%s: sshbuf_new failed", __func__);
+     }
+     
+     /*
+@@ -161,35 +169,39 @@ pamsshagentauth_session_id2_gen(Buffer *
+     retc = getcwd(pwd, sizeof(pwd) - 1);
+     time(&ts);
+ 
+-    pamsshagentauth_buffer_init(session_id2);
++    if ((*session_id2 = sshbuf_new()) == NULL)
++        fatal("%s: sshbuf_new failed", __func__);
+ 
+-    pamsshagentauth_buffer_put_int(session_id2, PAM_SSH_AGENT_AUTH_REQUESTv1);
+-    /* pamsshagentauth_debug3("cookie: %s", pamsshagentauth_tohex(cookie, cookie_len)); */
+-    pamsshagentauth_buffer_put_string(session_id2, cookie, cookie_len);
+-    /* pamsshagentauth_debug3("user: %s", user); */
+-    pamsshagentauth_buffer_put_cstring(session_id2, user);
+-    /* pamsshagentauth_debug3("ruser: %s", ruser); */
+-    pamsshagentauth_buffer_put_cstring(session_id2, ruser);
+-    /* pamsshagentauth_debug3("servicename: %s", servicename); */
+-    pamsshagentauth_buffer_put_cstring(session_id2, servicename);
+-    /* pamsshagentauth_debug3("pwd: %s", pwd); */
+-    if(retc)
+-        pamsshagentauth_buffer_put_cstring(session_id2, pwd);
+-    else
+-        pamsshagentauth_buffer_put_cstring(session_id2, "");
+-    /* pamsshagentauth_debug3("action: %s", action_logbuf); */
+-    pamsshagentauth_buffer_put_string(session_id2, action_agentbuf.buf + action_agentbuf.offset, action_agentbuf.end - action_agentbuf.offset);
++    if ((r = sshbuf_put_u32(*session_id2, PAM_SSH_AGENT_AUTH_REQUESTv1)) != 0 ||
++        (r = sshbuf_put_string(*session_id2, cookie, cookie_len)) != 0 ||
++        (r = sshbuf_put_cstring(*session_id2, user)) != 0 ||
++        (r = sshbuf_put_cstring(*session_id2, ruser)) != 0 ||
++        (r = sshbuf_put_cstring(*session_id2, servicename)) != 0)
++        fatal("%s: buffer error: %s", __func__, ssh_err(r));
++    if (retc) {
++        if ((r = sshbuf_put_cstring(*session_id2, pwd)) != 0)
++            fatal("%s: buffer error: %s", __func__, ssh_err(r));
++    } else {
++        if ((r = sshbuf_put_cstring(*session_id2, "")) != 0)
++            fatal("%s: buffer error: %s", __func__, ssh_err(r));
++    }
++    if ((r = sshbuf_put_stringb(*session_id2, action_agentbuf)) != 0)
++        fatal("%s: buffer error: %s", __func__, ssh_err(r));
+     if (free_logbuf) { 
+-        pamsshagentauth_xfree(action_logbuf);
+-        pamsshagentauth_buffer_free(&action_agentbuf);
++        free(action_logbuf);
++        sshbuf_free(action_agentbuf);
++    }
++    /* debug3("hostname: %s", hostname); */
++    if (reti >= 0) {
++        if ((r = sshbuf_put_cstring(*session_id2, hostname)) != 0)
++            fatal("%s: buffer error: %s", __func__, ssh_err(r));
++    } else {
++        if ((r = sshbuf_put_cstring(*session_id2, "")) != 0)
++            fatal("%s: buffer error: %s", __func__, ssh_err(r));
+     }
+-    /* pamsshagentauth_debug3("hostname: %s", hostname); */
+-    if(reti >= 0)
+-        pamsshagentauth_buffer_put_cstring(session_id2, hostname);
+-    else
+-        pamsshagentauth_buffer_put_cstring(session_id2, "");
+-    /* pamsshagentauth_debug3("ts: %ld", ts); */
+-    pamsshagentauth_buffer_put_int64(session_id2, (uint64_t) ts);
++    /* debug3("ts: %ld", ts); */
++    if ((r = sshbuf_put_u64(*session_id2, (uint64_t) ts)) != 0)
++        fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+     free(cookie);
+     return;
+@@ -278,7 +290,8 @@ ssh_get_authentication_connection_for_ui
+ 
+ 	auth = xmalloc(sizeof(*auth));
+ 	auth->fd = sock;
+-	buffer_init(&auth->identities);
++	if ((auth->identities = sshbuf_new()) == NULL)
++           fatal("%s: sshbuf_new failed", __func__);
+ 	auth->howmany = 0;
+ 
+ 	return auth;
+@@ -287,9 +300,9 @@ ssh_get_authentication_connection_for_ui
+ int
+ pamsshagentauth_find_authorized_keys(const char * user, const char * ruser, const char * servicename)
+ {
+-    Buffer session_id2 = { 0 };
++    struct sshbuf *session_id2 = NULL;
+     Identity *id;
+-    Key *key;
++    struct sshkey *key;
+     AuthenticationConnection *ac;
+     char *comment;
+     uint8_t retval = 0;
+@@ -299,31 +312,30 @@ pamsshagentauth_find_authorized_keys(con
+     pamsshagentauth_session_id2_gen(&session_id2, user, ruser, servicename);
+ 
+     if ((ac = ssh_get_authentication_connection_for_uid(uid))) {
+-        pamsshagentauth_verbose("Contacted ssh-agent of user %s (%u)", ruser, uid);
++        verbose("Contacted ssh-agent of user %s (%u)", ruser, uid);
+         for (key = ssh_get_first_identity(ac, &comment, 2); key != NULL; key = ssh_get_next_identity(ac, &comment, 2)) 
+         {
+             if(key != NULL) {
+-                id = pamsshagentauth_xcalloc(1, sizeof(*id));
++                id = xcalloc(1, sizeof(*id));
+                 id->key = key;
+                 id->filename = comment;
+                 id->ac = ac;
+-                if(userauth_pubkey_from_id(ruser, id, &session_id2)) {
++                if(userauth_pubkey_from_id(ruser, id, session_id2)) {
+                     retval = 1;
+                 }
+-                pamsshagentauth_xfree(id->filename);
+-                pamsshagentauth_key_free(id->key);
+-                pamsshagentauth_xfree(id);
++                free(id->filename);
++                key_free(id->key);
++                free(id);
+                 if(retval == 1)
+                     break;
+             }
+         }
+-        pamsshagentauth_buffer_free(&session_id2);
++        sshbuf_free(session_id2);
+         ssh_close_authentication_connection(ac);
+     }
+     else {
+-        pamsshagentauth_verbose("No ssh-agent could be contacted");
++        verbose("No ssh-agent could be contacted");
+     }
+-    /* pamsshagentauth_xfree(session_id2); */
+     EVP_cleanup();
+     return retval;
+ }
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_ssh_agent_auth.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_ssh_agent_auth.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_ssh_agent_auth.c.psaa-compat	2020-09-23 10:52:16.423001461 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_ssh_agent_auth.c	2020-09-23 10:53:10.631727657 +0200
+@@ -106,7 +106,7 @@ pam_sm_authenticate(pam_handle_t * pamh,
+  * a patch 8-)
+  */
+ #if ! HAVE___PROGNAME || HAVE_BUNDLE
+-    __progname = pamsshagentauth_xstrdup(servicename);
++    __progname = xstrdup(servicename);
+ #endif
+ 
+     for(i = argc, argv_ptr = (char **) argv; i > 0; ++argv_ptr, i--) {
+@@ -132,11 +132,11 @@ pam_sm_authenticate(pam_handle_t * pamh,
+ #endif
+     }
+ 
+-    pamsshagentauth_log_init(__progname, log_lvl, facility, getenv("PAM_SSH_AGENT_AUTH_DEBUG") ? 1 : 0);
++    log_init(__progname, log_lvl, facility, getenv("PAM_SSH_AGENT_AUTH_DEBUG") ? 1 : 0);
+     pam_get_item(pamh, PAM_USER, (void *) &user);
+     pam_get_item(pamh, PAM_RUSER, (void *) &ruser_ptr);
+ 
+-    pamsshagentauth_verbose("Beginning pam_ssh_agent_auth for user %s", user);
++    verbose("Beginning pam_ssh_agent_auth for user %s", user);
+ 
+     if(ruser_ptr) {
+         strncpy(ruser, ruser_ptr, sizeof(ruser) - 1);
+@@ -151,12 +151,12 @@ pam_sm_authenticate(pam_handle_t * pamh,
+ #ifdef ENABLE_SUDO_HACK
+         if( (strlen(sudo_service_name) > 0) && strncasecmp(servicename, sudo_service_name, sizeof(sudo_service_name) - 1) == 0 && getenv("SUDO_USER") ) {
+             strncpy(ruser, getenv("SUDO_USER"), sizeof(ruser) - 1 );
+-            pamsshagentauth_verbose( "Using environment variable SUDO_USER (%s)", ruser );
++            verbose( "Using environment variable SUDO_USER (%s)", ruser );
+         } else
+ #endif
+         {
+             if( ! getpwuid(getuid()) ) {
+-                pamsshagentauth_verbose("Unable to getpwuid(getuid())");
++                verbose("Unable to getpwuid(getuid())");
+                 goto cleanexit;
+             }
+             strncpy(ruser, getpwuid(getuid())->pw_name, sizeof(ruser) - 1);
+@@ -165,11 +165,11 @@ pam_sm_authenticate(pam_handle_t * pamh,
+ 
+     /* Might as well explicitely confirm the user exists here */
+     if(! getpwnam(ruser) ) {
+-        pamsshagentauth_verbose("getpwnam(%s) failed, bailing out", ruser);
++        verbose("getpwnam(%s) failed, bailing out", ruser);
+         goto cleanexit;
+     }
+     if( ! getpwnam(user) ) {
+-        pamsshagentauth_verbose("getpwnam(%s) failed, bailing out", user);
++        verbose("getpwnam(%s) failed, bailing out", user);
+         goto cleanexit;
+     }
+ 
+@@ -179,8 +179,8 @@ pam_sm_authenticate(pam_handle_t * pamh,
+          */
+         parse_authorized_key_file(user, authorized_keys_file_input);
+     } else {
+-        pamsshagentauth_verbose("Using default file=/etc/security/authorized_keys");
+-        authorized_keys_file = pamsshagentauth_xstrdup("/etc/security/authorized_keys");
++        verbose("Using default file=/etc/security/authorized_keys");
++        authorized_keys_file = xstrdup("/etc/security/authorized_keys");
+     }
+ 
+     /*
+@@ -189,7 +189,7 @@ pam_sm_authenticate(pam_handle_t * pamh,
+      */
+ 
+     if(user && strlen(ruser) > 0) {
+-        pamsshagentauth_verbose("Attempting authentication: `%s' as `%s' using %s", ruser, user, authorized_keys_file);
++        verbose("Attempting authentication: `%s' as `%s' using %s", ruser, user, authorized_keys_file);
+ 
+         /*
+          * Attempt to read data from the sshd if we're being called as an auth agent.
+@@ -197,10 +197,10 @@ pam_sm_authenticate(pam_handle_t * pamh,
+         const char* ssh_user_auth = pam_getenv(pamh, "SSH_AUTH_INFO_0");
+         int sshd_service = strncasecmp(servicename, sshd_service_name, sizeof(sshd_service_name) - 1);
+         if (sshd_service == 0 && ssh_user_auth != NULL) {
+-            pamsshagentauth_verbose("Got SSH_AUTH_INFO_0: `%.20s...'", ssh_user_auth);
++            verbose("Got SSH_AUTH_INFO_0: `%.20s...'", ssh_user_auth);
+             if (userauth_pubkey_from_pam(ruser, ssh_user_auth) > 0) {
+                 retval = PAM_SUCCESS;
+-                pamsshagentauth_logit("Authenticated (sshd): `%s' as `%s' using %s", ruser, user, authorized_keys_file);
++                logit("Authenticated (sshd): `%s' as `%s' using %s", ruser, user, authorized_keys_file);
+                 goto cleanexit;
+             }
+         }
+@@ -208,13 +208,13 @@ pam_sm_authenticate(pam_handle_t * pamh,
+          * this pw_uid is used to validate the SSH_AUTH_SOCK, and so must be the uid of the ruser invoking the program, not the target-user
+          */
+         if(pamsshagentauth_find_authorized_keys(user, ruser, servicename)) { /* getpwnam(ruser)->pw_uid)) { */
+-            pamsshagentauth_logit("Authenticated (agent): `%s' as `%s' using %s", ruser, user, authorized_keys_file);
++            logit("Authenticated (agent): `%s' as `%s' using %s", ruser, user, authorized_keys_file);
+             retval = PAM_SUCCESS;
+         } else {
+-            pamsshagentauth_logit("Failed Authentication: `%s' as `%s' using %s", ruser, user, authorized_keys_file);
++            logit("Failed Authentication: `%s' as `%s' using %s", ruser, user, authorized_keys_file);
+         }
+     } else {
+-        pamsshagentauth_logit("No %s specified, cannot continue with this form of authentication", (user) ? "ruser" : "user" );
++        logit("No %s specified, cannot continue with this form of authentication", (user) ? "ruser" : "user" );
+     }
+ 
+ cleanexit:
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.c	2020-09-23 10:52:16.424001475 +0200
+@@ -66,8 +66,8 @@
+ #include "xmalloc.h"
+ #include "match.h"
+ #include "log.h"
+-#include "buffer.h"
+-#include "key.h"
++#include "sshbuf.h"
++#include "sshkey.h"
+ #include "misc.h"
+ 
+ #include "xmalloc.h"
+@@ -77,7 +77,6 @@
+ #include "pathnames.h"
+ #include "secure_filename.h"
+ 
+-#include "identity.h"
+ #include "pam_user_key_allowed2.h"
+ 
+ extern char *authorized_keys_file;
+@@ -117,12 +116,12 @@ parse_authorized_key_file(const char *us
+         } else {
+             slash_ptr = strchr(auth_keys_file_buf, '/');
+             if(!slash_ptr)
+-                pamsshagentauth_fatal
++                fatal
+                     ("cannot expand tilde in path without a `/'");
+ 
+             owner_uname_len = slash_ptr - auth_keys_file_buf - 1;
+             if(owner_uname_len > (sizeof(owner_uname) - 1))
+-                pamsshagentauth_fatal("Username too long");
++                fatal("Username too long");
+ 
+             strncat(owner_uname, auth_keys_file_buf + 1, owner_uname_len);
+             if(!authorized_keys_file_allowed_owner_uid)
+@@ -130,11 +129,11 @@ parse_authorized_key_file(const char *us
+                     getpwnam(owner_uname)->pw_uid;
+         }
+         authorized_keys_file =
+-            pamsshagentauth_tilde_expand_filename(auth_keys_file_buf,
++            tilde_expand_filename(auth_keys_file_buf,
+                                                   authorized_keys_file_allowed_owner_uid);
+         strncpy(auth_keys_file_buf, authorized_keys_file,
+                 sizeof(auth_keys_file_buf) - 1);
+-        pamsshagentauth_xfree(authorized_keys_file)        /* when we
++        free(authorized_keys_file)        /* when we
+                                                               percent_expand
+                                                               later, we'd step
+                                                               on this, so free
+@@ -150,13 +149,13 @@ parse_authorized_key_file(const char *us
+     strncat(hostname, fqdn, strcspn(fqdn, "."));
+ #endif
+     authorized_keys_file =
+-        pamsshagentauth_percent_expand(auth_keys_file_buf, "h",
++        percent_expand(auth_keys_file_buf, "h",
+                                        getpwnam(user)->pw_dir, "H", hostname,
+                                        "f", fqdn, "u", user, NULL);
+ }
+ 
+ int
+-pam_user_key_allowed(const char *ruser, Key * key)
++pam_user_key_allowed(const char *ruser, struct sshkey * key)
+ {
+     return
+         pamsshagentauth_user_key_allowed2(getpwuid(authorized_keys_file_allowed_owner_uid),
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.h.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.h
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.h.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_authorized_keys.h	2020-09-23 10:52:16.424001475 +0200
+@@ -32,7 +32,7 @@
+ #define _PAM_USER_KEY_ALLOWED_H
+ 
+ #include "identity.h"
+-int pam_user_key_allowed(const char *, Key *);
++int pam_user_key_allowed(const char *, struct sshkey *);
+ void parse_authorized_key_file(const char *, const char *);
+ 
+ #endif
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.c	2020-09-23 10:52:16.424001475 +0200
+@@ -45,44 +45,46 @@
+ #include "xmalloc.h"
+ #include "ssh.h"
+ #include "ssh2.h"
+-#include "buffer.h"
++#include "sshbuf.h"
+ #include "log.h"
+ #include "compat.h"
+-#include "key.h"
++#include "digest.h"
++#include "sshkey.h"
+ #include "pathnames.h"
+ #include "misc.h"
+ #include "secure_filename.h"
+ #include "uidswap.h"
+-
+-#include "identity.h"
++#include <unistd.h>
+ 
+ /* return 1 if user allows given key */
+ /* Modified slightly from original found in auth2-pubkey.c */
+ static int
+-pamsshagentauth_check_authkeys_file(FILE * f, char *file, Key * key)
++pamsshagentauth_check_authkeys_file(FILE * f, char *file, struct sshkey * key)
+ {
+-    char line[SSH_MAX_PUBKEY_BYTES];
++    char *line = NULL;
+     int found_key = 0;
+     u_long linenum = 0;
+-    Key *found;
++    struct sshkey *found;
+     char *fp;
++    size_t linesize = 0;
+ 
+     found_key = 0;
+-    found = pamsshagentauth_key_new(key->type);
++    found = sshkey_new(key->type);
+ 
+-    while(read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
++    while ((getline(&line, &linesize, f)) != -1) {
+         char *cp = NULL; /* *key_options = NULL; */
+ 
++        linenum++;
+         /* Skip leading whitespace, empty and comment lines. */
+         for(cp = line; *cp == ' ' || *cp == '\t'; cp++);
+         if(!*cp || *cp == '\n' || *cp == '#')
+             continue;
+ 
+-        if(pamsshagentauth_key_read(found, &cp) != 1) {
++        if (sshkey_read(found, &cp) != 0) {
+             /* no key? check if there are options for this key */
+             int quoted = 0;
+ 
+-            pamsshagentauth_verbose("user_key_allowed: check options: '%s'", cp);
++            verbose("user_key_allowed: check options: '%s'", cp);
+             /* key_options = cp; */
+             for(; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
+                 if(*cp == '\\' && cp[1] == '"')
+@@ -92,26 +94,27 @@ pamsshagentauth_check_authkeys_file(FILE
+             }
+             /* Skip remaining whitespace. */
+             for(; *cp == ' ' || *cp == '\t'; cp++);
+-            if(pamsshagentauth_key_read(found, &cp) != 1) {
+-                pamsshagentauth_verbose("user_key_allowed: advance: '%s'", cp);
++            if(sshkey_read(found, &cp) != 0) {
++                verbose("user_key_allowed: advance: '%s'", cp);
+                 /* still no key? advance to next line */
+                 continue;
+             }
+         }
+-        if(pamsshagentauth_key_equal(found, key)) {
++        if(sshkey_equal(found, key)) {
+             found_key = 1;
+-            pamsshagentauth_logit("matching key found: file/command %s, line %lu", file,
++            logit("matching key found: file/command %s, line %lu", file,
+                                   linenum);
+-            fp = pamsshagentauth_key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
+-            pamsshagentauth_logit("Found matching %s key: %s",
+-                                  pamsshagentauth_key_type(found), fp);
+-            pamsshagentauth_xfree(fp);
++            fp = sshkey_fingerprint(found, SSH_DIGEST_SHA256, SSH_FP_BASE64);
++            logit("Found matching %s key: %s",
++                                  sshkey_type(found), fp);
++            free(fp);
+             break;
+         }
+     }
+-    pamsshagentauth_key_free(found);
++    free(line);
++    sshkey_free(found);
+     if(!found_key)
+-        pamsshagentauth_verbose("key not found");
++        verbose("key not found");
+     return found_key;
+ }
+ 
+@@ -120,19 +123,19 @@ pamsshagentauth_check_authkeys_file(FILE
+  * returns 1 if the key is allowed or 0 otherwise.
+  */
+ int
+-pamsshagentauth_user_key_allowed2(struct passwd *pw, Key * key, char *file)
++pamsshagentauth_user_key_allowed2(struct passwd *pw, struct sshkey * key, char *file)
+ {
+     FILE *f;
+     int found_key = 0;
+     struct stat st;
+-    char buf[SSH_MAX_PUBKEY_BYTES];
++    char buf[256];
+ 
+     /* Temporarily use the user's uid. */
+-    pamsshagentauth_verbose("trying public key file %s", file);
++    verbose("trying public key file %s", file);
+ 
+     /* Fail not so quietly if file does not exist */
+     if(stat(file, &st) < 0) {
+-        pamsshagentauth_verbose("File not found: %s", file);
++        verbose("File not found: %s", file);
+         return 0;
+     }
+ 
+@@ -144,7 +147,7 @@ pamsshagentauth_user_key_allowed2(struct
+ 
+     if(pamsshagentauth_secure_filename(f, file, pw, buf, sizeof(buf)) != 0) {
+         fclose(f);
+-        pamsshagentauth_logit("Authentication refused: %s", buf);
++        logit("Authentication refused: %s", buf);
+         return 0;
+     }
+ 
+@@ -160,7 +163,7 @@ pamsshagentauth_user_key_allowed2(struct
+ int
+ pamsshagentauth_user_key_command_allowed2(char *authorized_keys_command,
+                           char *authorized_keys_command_user,
+-                          struct passwd *user_pw, Key * key)
++                          struct passwd *user_pw, struct sshkey * key)
+ {
+     FILE *f;
+     int ok, found_key = 0;
+@@ -187,44 +190,44 @@ pamsshagentauth_user_key_command_allowed
+     else {
+         pw = getpwnam(authorized_keys_command_user);
+         if(pw == NULL) {
+-            pamsshagentauth_logerror("authorized_keys_command_user \"%s\" not found: %s",
++            error("authorized_keys_command_user \"%s\" not found: %s",
+                  authorized_keys_command_user, strerror(errno));
+             return 0;
+         }
+     }
+ 
+-    pamsshagentauth_temporarily_use_uid(pw);
++    temporarily_use_uid(pw);
+ 
+     if(stat(authorized_keys_command, &st) < 0) {
+-        pamsshagentauth_logerror
++        error
+             ("Could not stat AuthorizedKeysCommand \"%s\": %s",
+              authorized_keys_command, strerror(errno));
+         goto out;
+     }
+     if(pamsshagentauth_auth_secure_path
+        (authorized_keys_command, &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
+-        pamsshagentauth_logerror("Unsafe AuthorizedKeysCommand: %s", errmsg);
++        error("Unsafe AuthorizedKeysCommand: %s", errmsg);
+         goto out;
+     }
+ 
+     /* open the pipe and read the keys */
+     if(pipe(p) != 0) {
+-        pamsshagentauth_logerror("%s: pipe: %s", __func__, strerror(errno));
++        error("%s: pipe: %s", __func__, strerror(errno));
+         goto out;
+     }
+ 
+-    pamsshagentauth_debug("Running AuthorizedKeysCommand: \"%s\" as \"%s\" with argument: \"%s\"",
++    debug("Running AuthorizedKeysCommand: \"%s\" as \"%s\" with argument: \"%s\"",
+                           authorized_keys_command, pw->pw_name, username);
+ 
+     /* 
+      * Don't want to call this in the child, where it can fatal() and
+      * run cleanup_exit() code.
+      */
+-    pamsshagentauth_restore_uid();
++    restore_uid();
+ 
+     switch ((pid = fork())) {
+     case -1:                                              /* error */
+-        pamsshagentauth_logerror("%s: fork: %s", __func__, strerror(errno));
++        error("%s: fork: %s", __func__, strerror(errno));
+         close(p[0]);
+         close(p[1]);
+         return 0;
+@@ -234,13 +237,13 @@ pamsshagentauth_user_key_command_allowed
+ 
+         /* do this before the setresuid so thta they can be logged */
+         if((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+-            pamsshagentauth_logerror("%s: open %s: %s", __func__, _PATH_DEVNULL,
++            error("%s: open %s: %s", __func__, _PATH_DEVNULL,
+                                      strerror(errno));
+             _exit(1);
+         }
+         if(dup2(devnull, STDIN_FILENO) == -1 || dup2(p[1], STDOUT_FILENO) == -1
+            || dup2(devnull, STDERR_FILENO) == -1) {
+-            pamsshagentauth_logerror("%s: dup2: %s", __func__, strerror(errno));
++            error("%s: dup2: %s", __func__, strerror(errno));
+             _exit(1);
+         }
+ #if defined(HAVE_SETRESGID) && !defined(BROKEN_SETRESGID)
+@@ -248,7 +251,7 @@ pamsshagentauth_user_key_command_allowed
+ #else
+         if (setgid(pw->pw_gid) != 0 || setegid(pw->pw_gid) != 0) {
+ #endif
+-            pamsshagentauth_logerror("setresgid %u: %s", (u_int) pw->pw_gid,
++            error("setresgid %u: %s", (u_int) pw->pw_gid,
+                                      strerror(errno));
+             _exit(1);
+         }
+@@ -258,7 +261,7 @@ pamsshagentauth_user_key_command_allowed
+ #else
+         if (setuid(pw->pw_uid) != 0 || seteuid(pw->pw_uid) != 0) {
+ #endif
+-            pamsshagentauth_logerror("setresuid %u: %s", (u_int) pw->pw_uid,
++            error("setresuid %u: %s", (u_int) pw->pw_uid,
+                                      strerror(errno));
+             _exit(1);
+         }
+@@ -270,18 +273,18 @@ pamsshagentauth_user_key_command_allowed
+ 
+         /* pretty sure this will barf because we are now suid, but since we
+            should't reach this anyway, I'll leave it here */
+-        pamsshagentauth_logerror("AuthorizedKeysCommand %s exec failed: %s",
++        error("AuthorizedKeysCommand %s exec failed: %s",
+                                  authorized_keys_command, strerror(errno));
+         _exit(127);
+     default:                                              /* parent */
+         break;
+     }
+ 
+-    pamsshagentauth_temporarily_use_uid(pw);
++    temporarily_use_uid(pw);
+ 
+     close(p[1]);
+     if((f = fdopen(p[0], "r")) == NULL) {
+-        pamsshagentauth_logerror("%s: fdopen: %s", __func__, strerror(errno));
++        error("%s: fdopen: %s", __func__, strerror(errno));
+         close(p[0]);
+         /* Don't leave zombie child */
+         while(waitpid(pid, NULL, 0) == -1 && errno == EINTR);
+@@ -292,22 +295,22 @@ pamsshagentauth_user_key_command_allowed
+ 
+     while(waitpid(pid, &status, 0) == -1) {
+         if(errno != EINTR) {
+-            pamsshagentauth_logerror("%s: waitpid: %s", __func__,
++            error("%s: waitpid: %s", __func__,
+                                      strerror(errno));
+             goto out;
+         }
+     }
+     if(WIFSIGNALED(status)) {
+-        pamsshagentauth_logerror("AuthorizedKeysCommand %s exited on signal %d",
++        error("AuthorizedKeysCommand %s exited on signal %d",
+                                  authorized_keys_command, WTERMSIG(status));
+         goto out;
+     } else if(WEXITSTATUS(status) != 0) {
+-        pamsshagentauth_logerror("AuthorizedKeysCommand %s returned status %d",
++        error("AuthorizedKeysCommand %s returned status %d",
+                                  authorized_keys_command, WEXITSTATUS(status));
+         goto out;
+     }
+     found_key = ok;
+   out:
+-    pamsshagentauth_restore_uid();
++    restore_uid();
+     return found_key;
+ }
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.h.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.h
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.h.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/pam_user_key_allowed2.h	2020-09-23 10:52:16.424001475 +0200
+@@ -32,7 +32,7 @@
+ #define _PAM_USER_KEY_ALLOWED_H
+ 
+ #include "identity.h"
+-int pamsshagentauth_user_key_allowed2(struct passwd *, Key *, char *);
+-int pamsshagentauth_user_key_command_allowed2(char *, char *, struct passwd *, Key *);
++int pamsshagentauth_user_key_allowed2(struct passwd *, struct sshkey *, char *);
++int pamsshagentauth_user_key_command_allowed2(char *, char *, struct passwd *, struct sshkey *);
+ 
+ #endif
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/secure_filename.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/secure_filename.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/secure_filename.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/secure_filename.c	2020-09-23 10:52:16.424001475 +0200
+@@ -53,8 +53,8 @@
+ #include "xmalloc.h"
+ #include "match.h"
+ #include "log.h"
+-#include "buffer.h"
+-#include "key.h"
++#include "sshbuf.h"
++#include "sshkey.h"
+ #include "misc.h"
+ 
+ 
+@@ -80,7 +80,7 @@ pamsshagentauth_auth_secure_path(const c
+ 	int comparehome = 0;
+ 	struct stat st;
+ 
+-    pamsshagentauth_verbose("auth_secure_filename: checking for uid: %u", uid);
++    verbose("auth_secure_filename: checking for uid: %u", uid);
+ 
+ 	if (realpath(name, buf) == NULL) {
+ 		snprintf(err, errlen, "realpath %s failed: %s", name,
+@@ -115,9 +115,9 @@ pamsshagentauth_auth_secure_path(const c
+ 			snprintf(err, errlen, "dirname() failed");
+ 			return -1;
+ 		}
+-		pamsshagentauth_strlcpy(buf, cp, sizeof(buf));
++		strlcpy(buf, cp, sizeof(buf));
+ 
+-		pamsshagentauth_verbose("secure_filename: checking '%s'", buf);
++		verbose("secure_filename: checking '%s'", buf);
+ 		if (stat(buf, &st) < 0 ||
+ 		    (st.st_uid != 0 && st.st_uid != uid) ||
+ 		    (st.st_mode & 022) != 0) {
+@@ -128,7 +128,7 @@ pamsshagentauth_auth_secure_path(const c
+ 
+ 		/* If are passed the homedir then we can stop */
+ 		if (comparehome && strcmp(homedir, buf) == 0) {
+-			pamsshagentauth_verbose("secure_filename: terminating check at '%s'",
++			verbose("secure_filename: terminating check at '%s'",
+ 			    buf);
+ 			break;
+ 		}
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.c	2020-09-23 10:52:16.424001475 +0200
+@@ -37,10 +37,11 @@
+ #include "xmalloc.h"
+ #include "ssh.h"
+ #include "ssh2.h"
+-#include "buffer.h"
++#include "sshbuf.h"
+ #include "log.h"
+ #include "compat.h"
+-#include "key.h"
++#include "sshkey.h"
++#include "ssherr.h"
+ #include "pathnames.h"
+ #include "misc.h"
+ #include "secure_filename.h"
+@@ -48,54 +49,59 @@
+ #include "identity.h"
+ #include "pam_user_authorized_keys.h"
+ 
++#define SSH2_MSG_USERAUTH_TRUST_REQUEST          54
++
+ /* extern u_char  *session_id2;
+ extern uint8_t  session_id_len;
+  */
+ 
+ int
+-userauth_pubkey_from_id(const char *ruser, Identity * id, Buffer * session_id2)
++userauth_pubkey_from_id(const char *ruser, Identity * id, struct sshbuf * session_id2)
+ {
+-    Buffer          b = { 0 };
++    struct sshbuf  *b = NULL;
+     char           *pkalg = NULL;
+     u_char         *pkblob = NULL, *sig = NULL;
+-    u_int           blen = 0, slen = 0;
+-    int             authenticated = 0;
++    size_t          blen = 0, slen = 0;
++    int             r, authenticated = 0;
+ 
+-    pkalg = (char *) key_ssh_name(id->key);
++    pkalg = (char *) sshkey_ssh_name(id->key);
+ 
+     /* first test if this key is even allowed */
+     if(! pam_user_key_allowed(ruser, id->key))
+-        goto user_auth_clean_exit;
++        goto user_auth_clean_exit_without_buffer;
+ 
+-    if(pamsshagentauth_key_to_blob(id->key, &pkblob, &blen) == 0)
+-        goto user_auth_clean_exit;
++    if(sshkey_to_blob(id->key, &pkblob, &blen) != 0)
++        goto user_auth_clean_exit_without_buffer;
+ 
+     /* construct packet to sign and test */
+-    pamsshagentauth_buffer_init(&b);
++    if ((b = sshbuf_new()) == NULL)
++        fatal("%s: sshbuf_new failed", __func__);
+ 
+-    pamsshagentauth_buffer_put_string(&b, session_id2->buf + session_id2->offset, session_id2->end - session_id2->offset);
+-    pamsshagentauth_buffer_put_char(&b, SSH2_MSG_USERAUTH_TRUST_REQUEST); 
+-    pamsshagentauth_buffer_put_cstring(&b, ruser);
+-    pamsshagentauth_buffer_put_cstring(&b, "pam_ssh_agent_auth");
+-    pamsshagentauth_buffer_put_cstring(&b, "publickey");
+-    pamsshagentauth_buffer_put_char(&b, 1);
+-    pamsshagentauth_buffer_put_cstring(&b, pkalg);
+-    pamsshagentauth_buffer_put_string(&b, pkblob, blen);
++    if ((r = sshbuf_put_string(b, sshbuf_ptr(session_id2), sshbuf_len(session_id2))) != 0 ||
++        (r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_TRUST_REQUEST)) != 0 ||
++        (r = sshbuf_put_cstring(b, ruser)) != 0 ||
++        (r = sshbuf_put_cstring(b, "pam_ssh_agent_auth")) != 0 ||
++        (r = sshbuf_put_cstring(b, "publickey")) != 0 ||
++        (r = sshbuf_put_u8(b, 1)) != 0 ||
++        (r = sshbuf_put_cstring(b, pkalg)) != 0 ||
++        (r = sshbuf_put_string(b, pkblob, blen)) != 0)
++        fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+-    if(ssh_agent_sign(id->ac, id->key, &sig, &slen, pamsshagentauth_buffer_ptr(&b), pamsshagentauth_buffer_len(&b)) != 0)
++    if (ssh_agent_sign(id->ac, id->key, &sig, &slen, sshbuf_ptr(b), sshbuf_len(b)) != 0)
+         goto user_auth_clean_exit;
+ 
+     /* test for correct signature */
+-    if(pamsshagentauth_key_verify(id->key, sig, slen, pamsshagentauth_buffer_ptr(&b), pamsshagentauth_buffer_len(&b)) == 1)
++    if (sshkey_verify(id->key, sig, slen, sshbuf_ptr(b), sshbuf_len(b), NULL, 0, NULL) == 0)
+         authenticated = 1;
+ 
+   user_auth_clean_exit:
+     /* if(&b != NULL) */
+-    pamsshagentauth_buffer_free(&b);
++    sshbuf_free(b);
++  user_auth_clean_exit_without_buffer:
+     if(sig != NULL)
+-        pamsshagentauth_xfree(sig);
++        free(sig);
+     if(pkblob != NULL)
+-        pamsshagentauth_xfree(pkblob);
++        free(pkblob);
+     CRYPTO_cleanup_all_ex_data();
+     return authenticated;
+ }
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.h.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.h
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.h.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_id.h	2020-09-23 10:52:16.424001475 +0200
+@@ -31,7 +31,7 @@
+ #ifndef _USERAUTH_PUBKEY_FROM_ID_H
+ #define _USERAUTH_PUBKEY_FROM_ID_H
+ 
+-#include <identity.h>
+-int userauth_pubkey_from_id(const char *, Identity *, Buffer *);
++#include "identity.h"
++int userauth_pubkey_from_id(const char *, Identity *, struct sshbuf *);
+ 
+ #endif
+diff -up openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/uuencode.c.psaa-compat openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/uuencode.c
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/uuencode.c.psaa-compat	2019-07-08 18:36:13.000000000 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/uuencode.c	2020-09-23 10:52:16.424001475 +0200
+@@ -56,7 +56,7 @@ pamsshagentauth_uudecode(const char *src
+ 	/* and remove trailing whitespace because __b64_pton needs this */
+ 	*p = '\0';
+ 	len = pamsshagentauth___b64_pton(encoded, target, targsize);
+-	pamsshagentauth_xfree(encoded);
++	xfree(encoded);
+ 	return len;
+ }
+ 
+@@ -70,7 +70,7 @@ pamsshagentauth_dump_base64(FILE *fp, co
+ 		fprintf(fp, "dump_base64: len > 65536\n");
+ 		return;
+ 	}
+-	buf = pamsshagentauth_xmalloc(2*len);
++	buf = malloc(2*len);
+ 	n = pamsshagentauth_uuencode(data, len, buf, 2*len);
+ 	for (i = 0; i < n; i++) {
+ 		fprintf(fp, "%c", buf[i]);
+@@ -79,5 +79,5 @@ pamsshagentauth_dump_base64(FILE *fp, co
+ 	}
+ 	if (i % 70 != 69)
+ 		fprintf(fp, "\n");
+-	pamsshagentauth_xfree(buf);
++	free(buf);
+ }
+--- openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_pam.c.compat	2020-09-23 11:32:30.783695267 +0200
++++ openssh/pam_ssh_agent_auth-pam_ssh_agent_auth-0.10.4/userauth_pubkey_from_pam.c	2020-09-23 11:33:21.383389036 +0200
+@@ -33,7 +33,8 @@
+ #include <string.h>
+ 
+ #include "defines.h"
+-#include "key.h"
++#include <includes.h>
++#include "sshkey.h"
+ #include "log.h"
+ 
+ #include "pam_user_authorized_keys.h"
+@@ -42,28 +42,28 @@
+     int authenticated = 0;
+     const char method[] = "publickey ";
+ 
+-    char* ai = pamsshagentauth_xstrdup(ssh_auth_info);
++    char* ai = xstrdup(ssh_auth_info);
+     char* saveptr;
+ 
+     char* auth_line = strtok_r(ai, "\n", &saveptr);
+     while (auth_line != NULL) {
+         if (strncmp(auth_line, method, sizeof(method) - 1) == 0) {
+             char* key_str = auth_line + sizeof(method) - 1;
+-            Key* key = pamsshagentauth_key_new(KEY_UNSPEC);
++            struct sshkey* key = sshkey_new(KEY_UNSPEC);
+             if (key == NULL) {
+                 continue;
+             }
+-            int r = pamsshagentauth_key_read(key, &key_str);
++            int r = sshkey_read(key, &key_str);
+             if (r == 1) {
+                 if (pam_user_key_allowed(ruser, key)) {
+                     authenticated = 1;
+-                    pamsshagentauth_key_free(key);
++                    sshkey_free(key);
+                     break;
+                 }
+             } else {
+-                pamsshagentauth_verbose("Failed to create key for %s: %d", auth_line, r);
++                verbose("Failed to create key for %s: %d", auth_line, r);
+             }
+-            pamsshagentauth_key_free(key);
++            sshkey_free(key);
+         }
+         auth_line = strtok_r(NULL, "\n", &saveptr);
+     }
diff --git a/pam_ssh_agent_auth-0.10.2-dereference.patch b/pam_ssh_agent_auth-0.10.2-dereference.patch
new file mode 100644
index 0000000..bf49c37
--- /dev/null
+++ b/pam_ssh_agent_auth-0.10.2-dereference.patch
@@ -0,0 +1,20 @@
+diff --git a/pam_ssh_agent_auth-0.10.2/pam_user_authorized_keys.c b/pam_ssh_agent_auth-0.10.2/pam_user_authorized_keys.c
+--- a/pam_ssh_agent_auth-0.10.2/pam_user_authorized_keys.c
++++ b/pam_ssh_agent_auth-0.10.2/pam_user_authorized_keys.c
+@@ -158,11 +158,12 @@ parse_authorized_key_file(const char *user,
+ int
+ pam_user_key_allowed(const char *ruser, struct sshkey * key)
+ {
++    struct passwd *pw;
+     return
+-        pamsshagentauth_user_key_allowed2(getpwuid(authorized_keys_file_allowed_owner_uid),
+-                                          key, authorized_keys_file)
+-        || pamsshagentauth_user_key_allowed2(getpwuid(0), key,
+-                                             authorized_keys_file)
++        ( (pw = getpwuid(authorized_keys_file_allowed_owner_uid)) &&
++            pamsshagentauth_user_key_allowed2(pw, key, authorized_keys_file))
++        || ((pw = getpwuid(0)) &&
++            pamsshagentauth_user_key_allowed2(pw, key, authorized_keys_file))
+         || pamsshagentauth_user_key_command_allowed2(authorized_keys_command,
+                                                      authorized_keys_command_user,
+                                                      getpwnam(ruser), key);
diff --git a/pam_ssh_agent_auth-0.10.3-seteuid.patch b/pam_ssh_agent_auth-0.10.3-seteuid.patch
new file mode 100644
index 0000000..be1f8e5
--- /dev/null
+++ b/pam_ssh_agent_auth-0.10.3-seteuid.patch
@@ -0,0 +1,37 @@
+diff -up openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-seteuid openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c
+--- openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-seteuid	2017-02-07 15:41:53.172334151 +0100
++++ openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c	2017-02-07 15:41:53.174334149 +0100
+@@ -238,17 +238,26 @@ ssh_get_authentication_socket_for_uid(ui
+ 	}
+ 
+ 	errno = 0; 
+-	seteuid(uid); /* To ensure a race condition is not used to circumvent the stat
+-	             above, we will temporarily drop UID to the caller */
+-	if (connect(sock, (struct sockaddr *)&sunaddr, sizeof sunaddr) < 0) {
++	/* To ensure a race condition is not used to circumvent the stat
++	   above, we will temporarily drop UID to the caller */
++	if (seteuid(uid) == -1) {
+ 		close(sock);
+-        if(errno == EACCES)
+-		fatal("MAJOR SECURITY WARNING: uid %lu made a deliberate and malicious attempt to open an agent socket owned by another user", (unsigned long) uid);
++		error("seteuid(%lu) failed with error: %s",
++		    (unsigned long) uid, strerror(errno));
+ 		return -1;
+ 	}
++	if (connect(sock, (struct sockaddr *)&sunaddr, sizeof sunaddr) < 0) {
++		close(sock);
++		sock = -1;
++		if(errno == EACCES)
++			fatal("MAJOR SECURITY WARNING: uid %lu made a deliberate and malicious attempt to open an agent socket owned by another user", (unsigned long) uid);
++	}
+ 
+-	seteuid(0); /* we now continue the regularly scheduled programming */
+-
++	/* we now continue the regularly scheduled programming */
++	if (0 != seteuid(0)) {
++		fatal("setuid(0) failed with error: %s", strerror(errno));
++		return -1;
++	}
+ 	return sock;
+ }
+ 
diff --git a/pam_ssh_agent_auth-0.9.2-visibility.patch b/pam_ssh_agent_auth-0.9.2-visibility.patch
new file mode 100644
index 0000000..aea068d
--- /dev/null
+++ b/pam_ssh_agent_auth-0.9.2-visibility.patch
@@ -0,0 +1,21 @@
+diff -up openssh-7.1p2/pam_ssh_agent_auth-0.10.2/pam_ssh_agent_auth.c.psaa-visibility openssh-7.1p2/pam_ssh_agent_auth-0.10.2/pam_ssh_agent_auth.c
+--- openssh-7.1p2/pam_ssh_agent_auth-0.10.2/pam_ssh_agent_auth.c.psaa-visibility	2014-03-31 19:35:17.000000000 +0200
++++ openssh-7.1p2/pam_ssh_agent_auth-0.10.2/pam_ssh_agent_auth.c	2016-01-22 15:22:40.984469774 +0100
+@@ -72,7 +72,7 @@ char           *__progname;
+ extern char    *__progname;
+ #endif
+ 
+-PAM_EXTERN int
++PAM_EXTERN int __attribute__ ((visibility ("default")))
+ pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char **argv)
+ {
+     char          **argv_ptr;
+@@ -214,7 +214,7 @@ cleanexit:
+ }
+ 
+ 
+-PAM_EXTERN int
++PAM_EXTERN int __attribute__ ((visibility ("default")))
+ pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char **argv)
+ {
+     UNUSED(pamh);
diff --git a/pam_ssh_agent_auth-0.9.3-agent_structure.patch b/pam_ssh_agent_auth-0.9.3-agent_structure.patch
new file mode 100644
index 0000000..1f2c02c
--- /dev/null
+++ b/pam_ssh_agent_auth-0.9.3-agent_structure.patch
@@ -0,0 +1,96 @@
+diff -up openssh/pam_ssh_agent_auth-0.10.3/identity.h.psaa-agent openssh/pam_ssh_agent_auth-0.10.3/identity.h
+--- openssh/pam_ssh_agent_auth-0.10.3/identity.h.psaa-agent	2016-11-13 04:24:32.000000000 +0100
++++ openssh/pam_ssh_agent_auth-0.10.3/identity.h	2017-09-27 14:25:49.421739027 +0200
+@@ -38,6 +38,12 @@
+ typedef struct identity Identity;
+ typedef struct idlist Idlist;
+ 
++typedef struct {
++       int     fd;
++       struct sshbuf *identities;
++       int     howmany;
++}      AuthenticationConnection;
++
+ struct identity {
+     TAILQ_ENTRY(identity) next;
+     AuthenticationConnection *ac;   /* set if agent supports key */
+diff -up openssh/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-agent openssh/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c
+--- openssh/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-agent	2017-09-27 14:25:49.420739021 +0200
++++ openssh/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c	2017-09-27 14:25:49.421739027 +0200
+@@ -39,6 +39,7 @@
+ #include "sshbuf.h"
+ #include "sshkey.h"
+ #include "authfd.h"
++#include "ssherr.h"
+ #include <stdio.h>
+ #include <openssl/evp.h>
+ #include "ssh2.h"
+@@ -291,36 +292,43 @@ pamsshagentauth_find_authorized_keys(con
+ {
+     struct sshbuf *session_id2 = NULL;
+     Identity *id;
+-    struct sshkey *key;
+     AuthenticationConnection *ac;
+-    char *comment;
+     uint8_t retval = 0;
+     uid_t uid = getpwnam(ruser)->pw_uid;
++    struct ssh_identitylist *idlist;
++    int r;
++    unsigned int i;
+ 
+     OpenSSL_add_all_digests();
+     pamsshagentauth_session_id2_gen(&session_id2, user, ruser, servicename);
+ 
+     if ((ac = ssh_get_authentication_connection_for_uid(uid))) {
+         verbose("Contacted ssh-agent of user %s (%u)", ruser, uid);
+-        for (key = ssh_get_first_identity(ac, &comment, 2); key != NULL; key = ssh_get_next_identity(ac, &comment, 2)) 
+-        {
+-            if(key != NULL) {
++        if ((r = ssh_fetch_identitylist(ac->fd, &idlist)) != 0) {
++            if (r != SSH_ERR_AGENT_NO_IDENTITIES)
++               fprintf(stderr, "error fetching identities for "
++                               "protocol %d: %s\n", 2, ssh_err(r));
++        } else {
++            for (i = 0; i < idlist->nkeys; i++)
++            {
++              if (idlist->keys[i] != NULL) {
+                 id = xcalloc(1, sizeof(*id));
+-                id->key = key;
+-                id->filename = comment;
++                id->key = idlist->keys[i];
++                id->filename = idlist->comments[i];
+                 id->ac = ac;
+                 if(userauth_pubkey_from_id(ruser, id, session_id2)) {
+                     retval = 1;
+                 }
+-                free(id->filename);
+-                key_free(id->key);
+                 free(id);
+                 if(retval == 1)
+                     break;
+-            }
+-        }
++              }
++            }
+-        sshbuf_free(session_id2);
+-        ssh_close_authentication_connection(ac);
++            sshbuf_free(session_id2);
++            ssh_free_identitylist(idlist);
++        }
++        ssh_close_authentication_socket(ac->fd);
++        free(ac);
+     }
+     else {
+         verbose("No ssh-agent could be contacted");
+diff -up openssh/pam_ssh_agent_auth-0.10.3/userauth_pubkey_from_id.c.psaa-agent openssh/pam_ssh_agent_auth-0.10.3/userauth_pubkey_from_id.c
+--- openssh/pam_ssh_agent_auth-0.10.3/userauth_pubkey_from_id.c.psaa-agent	2017-09-27 14:25:49.420739021 +0200
++++ openssh/pam_ssh_agent_auth-0.10.3/userauth_pubkey_from_id.c	2017-09-27 14:25:49.422739032 +0200
+@@ -84,7 +85,7 @@ userauth_pubkey_from_id(const char *ruse
+         (r = sshbuf_put_string(b, pkblob, blen)) != 0)
+         fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ 
+-    if (ssh_agent_sign(id->ac, id->key, &sig, &slen, sshbuf_ptr(b), sshbuf_len(b)) != 0)
++    if (ssh_agent_sign(id->ac->fd, id->key, &sig, &slen, sshbuf_ptr(b), sshbuf_len(b), NULL, 0) != 0)
+         goto user_auth_clean_exit;
+ 
+     /* test for correct signature */
diff --git a/pam_ssh_agent_auth-0.9.3-build.patch b/pam_ssh_agent_auth-0.9.3-build.patch
new file mode 100644
index 0000000..4018c4d
--- /dev/null
+++ b/pam_ssh_agent_auth-0.9.3-build.patch
@@ -0,0 +1,198 @@
+diff -up openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-build openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c
+--- openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c.psaa-build	2016-11-13 04:24:32.000000000 +0100
++++ openssh-7.4p1/pam_ssh_agent_auth-0.10.3/iterate_ssh_agent_keys.c	2017-02-07 14:29:41.626116675 +0100
+@@ -43,12 +43,31 @@
+ #include <openssl/evp.h>
+ #include "ssh2.h"
+ #include "misc.h"
++#include "ssh.h"
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/socket.h>
++#include <sys/un.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <errno.h>
++#include <fcntl.h>
+ 
+ #include "userauth_pubkey_from_id.h"
+ #include "identity.h"
+ #include "get_command_line.h"
+ extern char **environ;
+ 
++/* 
++ * Added by Jamie Beverly, ensure socket fd points to a socket owned by the user 
++ * A cursory check is done, but to avoid race conditions, it is necessary 
++ * to drop effective UID when connecting to the socket. 
++ *
++ * If the cause of error is EACCES, because we verified we would not have that 
++ * problem initially, we can safely assume that somebody is attempting to find a 
++ * race condition; so a more "direct" log message is generated.
++ */
++
+ static char *
+ log_action(char ** action, size_t count)
+ {
+@@ -85,7 +104,7 @@ void
+ pamsshagentauth_session_id2_gen(Buffer * session_id2, const char * user,
+                                 const char * ruser, const char * servicename)
+ {
+-    char *cookie = NULL;
++    u_char *cookie = NULL;
+     uint8_t i = 0;
+     uint32_t rnd = 0;
+     uint8_t cookie_len;
+@@ -112,7 +131,7 @@ pamsshagentauth_session_id2_gen(Buffer *
+         if (i % 4 == 0) {
+             rnd = pamsshagentauth_arc4random();
+         }
+-        cookie[i] = (char) rnd;
++        cookie[i] = (u_char) rnd;
+         rnd >>= 8;
+     }
+ 
+@@ -177,6 +196,86 @@ pamsshagentauth_session_id2_gen(Buffer *
+ }
+ 
+ int
++ssh_get_authentication_socket_for_uid(uid_t uid)
++{
++	const char *authsocket;
++	int sock;
++	struct sockaddr_un sunaddr;
++	struct stat sock_st;
++
++	authsocket = getenv(SSH_AUTHSOCKET_ENV_NAME);
++	if (!authsocket)
++		return -1;
++
++	/* Advisory only; seteuid ensures no race condition; but will only log if we see EACCES */
++	if( stat(authsocket,&sock_st) == 0) {
++		if(uid != 0 && sock_st.st_uid != uid) {
++			fatal("uid %lu attempted to open an agent socket owned by uid %lu", (unsigned long) uid, (unsigned long) sock_st.st_uid);
++			return -1;
++		}
++	}
++
++	/* 
++	 * Ensures that the EACCES tested for below can _only_ happen if somebody 
++	 * is attempting to race the stat above to bypass authentication.
++	 */
++	if( (sock_st.st_mode & S_IWUSR) != S_IWUSR || (sock_st.st_mode & S_IRUSR) != S_IRUSR) {
++		error("ssh-agent socket has incorrect permissions for owner");
++		return -1;
++	}
++
++	sunaddr.sun_family = AF_UNIX;
++	strlcpy(sunaddr.sun_path, authsocket, sizeof(sunaddr.sun_path));
++
++	sock = socket(AF_UNIX, SOCK_STREAM, 0);
++	if (sock < 0)
++		return -1;
++
++	/* close on exec */
++	if (fcntl(sock, F_SETFD, 1) == -1) {
++		close(sock);
++		return -1;
++	}
++
++	errno = 0; 
++	seteuid(uid); /* To ensure a race condition is not used to circumvent the stat
++	             above, we will temporarily drop UID to the caller */
++	if (connect(sock, (struct sockaddr *)&sunaddr, sizeof sunaddr) < 0) {
++		close(sock);
++        if(errno == EACCES)
++		fatal("MAJOR SECURITY WARNING: uid %lu made a deliberate and malicious attempt to open an agent socket owned by another user", (unsigned long) uid);
++		return -1;
++	}
++
++	seteuid(0); /* we now continue the regularly scheduled programming */
++
++	return sock;
++}
++
++AuthenticationConnection *
++ssh_get_authentication_connection_for_uid(uid_t uid)
++{
++	AuthenticationConnection *auth;
++	int sock;
++
++	sock = ssh_get_authentication_socket_for_uid(uid);
++
++	/*
++	 * Fail if we couldn't obtain a connection.  This happens if we
++	 * exited due to a timeout.
++	 */
++	if (sock < 0)
++		return NULL;
++
++	auth = xmalloc(sizeof(*auth));
++	auth->fd = sock;
++	buffer_init(&auth->identities);
++	auth->howmany = 0;
++
++	return auth;
++}
++
++int
+ pamsshagentauth_find_authorized_keys(const char * user, const char * ruser, const char * servicename)
+ {
+     Buffer session_id2 = { 0 };
+@@ -190,7 +289,7 @@ pamsshagentauth_find_authorized_keys(con
+     OpenSSL_add_all_digests();
+     pamsshagentauth_session_id2_gen(&session_id2, user, ruser, servicename);
+ 
+-    if ((ac = ssh_get_authentication_connection(uid))) {
++    if ((ac = ssh_get_authentication_connection_for_uid(uid))) {
+         pamsshagentauth_verbose("Contacted ssh-agent of user %s (%u)", ruser, uid);
+         for (key = ssh_get_first_identity(ac, &comment, 2); key != NULL; key = ssh_get_next_identity(ac, &comment, 2)) 
+         {
+diff -up openssh-7.4p1/pam_ssh_agent_auth-0.10.3/Makefile.in.psaa-build openssh-7.4p1/pam_ssh_agent_auth-0.10.3/Makefile.in
+--- openssh-7.4p1/pam_ssh_agent_auth-0.10.3/Makefile.in.psaa-build	2016-11-13 04:24:32.000000000 +0100
++++ openssh-7.4p1/pam_ssh_agent_auth-0.10.3/Makefile.in	2017-02-07 14:40:14.407566921 +0100
+@@ -52,7 +52,7 @@ PATHS=
+ CC=@CC@
+ LD=@LD@
+ CFLAGS=@CFLAGS@
+-CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@
++CPPFLAGS=-I.. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@
+ LIBS=@LIBS@
+ AR=@AR@
+ AWK=@AWK@
+@@ -61,8 +61,8 @@ INSTALL=@INSTALL@
+ PERL=@PERL@
+ SED=@SED@
+ ENT=@ENT@
+-LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@
+-LDFLAGS_SHARED = @LDFLAGS_SHARED@
++LDFLAGS=-L.. -L../openbsd-compat/ @LDFLAGS@
++LDFLAGS_SHARED =-Wl,-z,defs @LDFLAGS_SHARED@
+ EXEEXT=@EXEEXT@
+ 
+ INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@
+@@ -74,7 +74,7 @@ SSHOBJS=xmalloc.o atomicio.o authfd.o bu
+ 
+ ED25519OBJS=ed25519-donna/ed25519.o
+ 
+-PAM_SSH_AGENT_AUTH_OBJS=pam_user_key_allowed2.o iterate_ssh_agent_keys.o userauth_pubkey_from_id.o pam_user_authorized_keys.o get_command_line.o userauth_pubkey_from_pam.o
++PAM_SSH_AGENT_AUTH_OBJS=pam_user_key_allowed2.o iterate_ssh_agent_keys.o userauth_pubkey_from_id.o pam_user_authorized_keys.o get_command_line.o userauth_pubkey_from_pam.o secure_filename.o
+ 
+ 
+ MANPAGES_IN	= pam_ssh_agent_auth.pod
+@@ -94,13 +94,13 @@ $(PAM_MODULES): Makefile.in config.h
+ .c.o:
+ 	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+ 
+-LIBCOMPAT=openbsd-compat/libopenbsd-compat.a
++LIBCOMPAT=../openbsd-compat/libopenbsd-compat.a
+ $(LIBCOMPAT): always
+ 	(cd openbsd-compat && $(MAKE))
+ always:
+ 
+-pam_ssh_agent_auth.so: $(LIBCOMPAT) $(SSHOBJS) $(ED25519OBJS) $(PAM_SSH_AGENT_AUTH_OBJS)  pam_ssh_agent_auth.o
+-	$(LD) $(LDFLAGS_SHARED) -o $@ $(SSHOBJS) $(ED25519OBJS) $(PAM_SSH_AGENT_AUTH_OBJS) $(LDFLAGS) -lopenbsd-compat pam_ssh_agent_auth.o $(LIBS) -lpam
++pam_ssh_agent_auth.so: $(PAM_SSH_AGENT_AUTH_OBJS)  pam_ssh_agent_auth.o ../uidswap.o ../ssh-sk-client.o
++	$(LD) $(LDFLAGS_SHARED) -o $@ $(PAM_SSH_AGENT_AUTH_OBJS) ../ssh-sk-client.o $(LDFLAGS) -lssh -lopenbsd-compat pam_ssh_agent_auth.o ../uidswap.o $(LIBS) -lpam
+ 
+ $(MANPAGES): $(MANPAGES_IN)
+ 	pod2man --section=8 --release=v0.10.3 --name=pam_ssh_agent_auth --official --center "PAM" pam_ssh_agent_auth.pod > pam_ssh_agent_auth.8
diff --git a/sources b/sources
new file mode 100644
index 0000000..41d33d0
--- /dev/null
+++ b/sources
@@ -0,0 +1,4 @@
+SHA512 (openssh-8.4p1.tar.gz) = d65275b082c46c5efe7cf3264fa6794d6e99a36d4a54b50554fc56979d6c0837381587fd5399195e1db680d2a5ad1ef0b99a180eac2b4de5637906cb7a89e9ce
+SHA512 (openssh-8.4p1.tar.gz.asc) = 3d9a026db27729a5a56785db3824230ccf2a3beca4bb48ef465e44d869b944dbc5d443152a1b1be21bc9c213c465d3d7ca1f876a387d0a6b9682a0cfec3e6e32
+SHA512 (pam_ssh_agent_auth-0.10.4.tar.gz) = caccf72174d15e43f4c86a459ac6448682e62116557cf1e1e828955f3d1731595b238df42adec57860e7f341e92daf5d8285020bcb5018f3b8a5145aa32ee1c2
+SHA512 (DJM-GPG-KEY.gpg) = db1191ed9b6495999e05eed2ef863fb5179bdb63e94850f192dad68eed8579836f88fbcfffd9f28524fe1457aff8cd248ee3e0afc112c8f609b99a34b80ecc0d
diff --git a/ssh-keycat.pam b/ssh-keycat.pam
new file mode 100644
index 0000000..d7a3f67
--- /dev/null
+++ b/ssh-keycat.pam
@@ -0,0 +1,8 @@
+#%PAM-1.0
+# pam_selinux.so close should be the first session rule
+session    required     pam_selinux.so close
+session    required     pam_loginuid.so
+# pam_selinux.so open should only be followed by sessions to be executed in the user context
+session    required     pam_selinux.so open env_params
+session    required     pam_namespace.so
+
diff --git a/sshd-keygen b/sshd-keygen
new file mode 100644
index 0000000..141814c
--- /dev/null
+++ b/sshd-keygen
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# Create the host keys for the OpenSSH server.
+KEYTYPE=$1
+case $KEYTYPE in
+	"dsa") ;& # disabled in FIPS
+	"ed25519")
+		FIPS=/proc/sys/crypto/fips_enabled
+		if [[ -r "$FIPS" && $(cat $FIPS) == "1" ]]; then
+			exit 0
+		fi ;;
+	"rsa") ;; # always ok
+	"ecdsa") ;;
+	*) # wrong argument
+		exit 12 ;;
+esac
+KEY=/etc/ssh/ssh_host_${KEYTYPE}_key
+
+KEYGEN=/usr/bin/ssh-keygen
+if [[ ! -x $KEYGEN ]]; then
+	exit 13
+fi
+
+# remove old keys
+rm -f $KEY{,.pub}
+
+# create new keys
+if ! $KEYGEN -q -t $KEYTYPE -f $KEY -C '' -N '' >&/dev/null; then
+	exit 1
+fi
+
+# sanitize permissions
+/usr/bin/chgrp ssh_keys $KEY
+/usr/bin/chmod 640 $KEY
+/usr/bin/chmod 644 $KEY.pub
+if [[ -x /usr/sbin/restorecon ]]; then
+	/usr/sbin/restorecon $KEY{,.pub}
+fi
+
+exit 0
diff --git a/sshd-keygen.target b/sshd-keygen.target
new file mode 100644
index 0000000..9efb4e2
--- /dev/null
+++ b/sshd-keygen.target
@@ -0,0 +1,5 @@
+[Unit]
+Wants=sshd-keygen@rsa.service
+Wants=sshd-keygen@ecdsa.service
+Wants=sshd-keygen@ed25519.service
+PartOf=sshd.service
diff --git a/sshd-keygen@.service b/sshd-keygen@.service
new file mode 100644
index 0000000..f27f729
--- /dev/null
+++ b/sshd-keygen@.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=OpenSSH %i Server Key Generation
+ConditionFileNotEmpty=|!/etc/ssh/ssh_host_%i_key
+
+[Service]
+Type=oneshot
+EnvironmentFile=-/etc/sysconfig/sshd
+ExecStart=/usr/libexec/openssh/sshd-keygen %i
+
+[Install]
+WantedBy=sshd-keygen.target
diff --git a/sshd.pam b/sshd.pam
new file mode 100644
index 0000000..780f62e
--- /dev/null
+++ b/sshd.pam
@@ -0,0 +1,17 @@
+#%PAM-1.0
+auth       substack     password-auth
+auth       include      postlogin
+account    required     pam_sepermit.so
+account    required     pam_nologin.so
+account    include      password-auth
+password   include      password-auth
+# pam_selinux.so close should be the first session rule
+session    required     pam_selinux.so close
+session    required     pam_loginuid.so
+# pam_selinux.so open should only be followed by sessions to be executed in the user context
+session    required     pam_selinux.so open env_params
+session    required     pam_namespace.so
+session    optional     pam_keyinit.so force revoke
+session    optional     pam_motd.so
+session    include      password-auth
+session    include      postlogin
diff --git a/sshd.service b/sshd.service
new file mode 100644
index 0000000..e8afb86
--- /dev/null
+++ b/sshd.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=OpenSSH server daemon
+Documentation=man:sshd(8) man:sshd_config(5)
+After=network.target sshd-keygen.target
+Wants=sshd-keygen.target
+
+[Service]
+Type=notify
+EnvironmentFile=-/etc/sysconfig/sshd
+ExecStart=/usr/sbin/sshd -D $OPTIONS
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=process
+Restart=on-failure
+RestartSec=42s
+
+[Install]
+WantedBy=multi-user.target
diff --git a/sshd.socket b/sshd.socket
new file mode 100644
index 0000000..caa50c4
--- /dev/null
+++ b/sshd.socket
@@ -0,0 +1,11 @@
+[Unit]
+Description=OpenSSH Server Socket
+Documentation=man:sshd(8) man:sshd_config(5)
+Conflicts=sshd.service
+
+[Socket]
+ListenStream=22
+Accept=yes
+
+[Install]
+WantedBy=sockets.target
diff --git a/sshd.sysconfig b/sshd.sysconfig
new file mode 100644
index 0000000..a217ce7
--- /dev/null
+++ b/sshd.sysconfig
@@ -0,0 +1,7 @@
+# Configuration file for the sshd service.
+
+# The server keys are automatically generated if they are missing.
+# To change the automatic creation, adjust sshd.service options for
+# example using  systemctl enable sshd-keygen@dsa.service  to allow creation
+# of DSA key or  systemctl mask sshd-keygen@rsa.service  to disable RSA key
+# creation.
diff --git a/sshd.tmpfiles b/sshd.tmpfiles
new file mode 100644
index 0000000..c35a2b8
--- /dev/null
+++ b/sshd.tmpfiles
@@ -0,0 +1 @@
+d /var/empty/sshd 711 root root -
diff --git a/sshd@.service b/sshd@.service
new file mode 100644
index 0000000..196c555
--- /dev/null
+++ b/sshd@.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=OpenSSH per-connection server daemon
+Documentation=man:sshd(8) man:sshd_config(5)
+Wants=sshd-keygen.target
+After=sshd-keygen.target
+
+[Service]
+EnvironmentFile=-/etc/sysconfig/sshd
+ExecStart=-/usr/sbin/sshd -i $OPTIONS
+StandardInput=socket
diff --git a/tests/pam_ssh_agent_auth/Makefile b/tests/pam_ssh_agent_auth/Makefile
new file mode 100644
index 0000000..f77eb4d
--- /dev/null
+++ b/tests/pam_ssh_agent_auth/Makefile
@@ -0,0 +1,64 @@
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Makefile of /CoreOS/openssh/Sanity/pam_ssh_agent_auth
+#   Description: This is a basic sanity test for pam_ssh_agent_auth
+#   Author: Jakub Jelen <jjelen@redhat.com>
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Copyright (c) 2015 Red Hat, Inc.
+#
+#   This program is free software: you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as
+#   published by the Free Software Foundation, either version 2 of
+#   the License, or (at your option) any later version.
+#
+#   This program 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 GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program. If not, see http://www.gnu.org/licenses/.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+export TEST=/CoreOS/openssh/Sanity/pam_ssh_agent_auth
+export TESTVERSION=1.0
+
+BUILT_FILES=
+
+FILES=$(METADATA) runtest.sh Makefile PURPOSE pam_save_ssh_var.c
+
+.PHONY: all install download clean
+
+run: $(FILES) build
+	./runtest.sh
+
+build: $(BUILT_FILES)
+	test -x runtest.sh || chmod a+x runtest.sh
+
+clean:
+	rm -f *~ $(BUILT_FILES)
+
+
+-include /usr/share/rhts/lib/rhts-make.include
+
+$(METADATA): Makefile
+	@echo "Owner:           Jakub Jelen <jjelen@redhat.com>" > $(METADATA)
+	@echo "Name:            $(TEST)" >> $(METADATA)
+	@echo "TestVersion:     $(TESTVERSION)" >> $(METADATA)
+	@echo "Path:            $(TEST_DIR)" >> $(METADATA)
+	@echo "Description:     This is basic sanity test for pam_ssh_agent_auth" >> $(METADATA)
+	@echo "Type:            Sanity" >> $(METADATA)
+	@echo "TestTime:        5m" >> $(METADATA)
+	@echo "RunFor:          openssh" >> $(METADATA)
+	@echo "Requires:        openssh pam_ssh_agent_auth pam-devel expect" >> $(METADATA)
+	@echo "RhtsRequires:    library(distribution/fips)" >> $(METADATA)
+	@echo "Priority:        Normal" >> $(METADATA)
+	@echo "License:         GPLv2+" >> $(METADATA)
+	@echo "Confidential:    no" >> $(METADATA)
+	@echo "Destructive:     no" >> $(METADATA)
+	@echo "Releases:        -RHEL4 -RHELClient5 -RHELServer5" >> $(METADATA)
+
+	rhts-lint $(METADATA)
diff --git a/tests/pam_ssh_agent_auth/PURPOSE b/tests/pam_ssh_agent_auth/PURPOSE
new file mode 100644
index 0000000..59557de
--- /dev/null
+++ b/tests/pam_ssh_agent_auth/PURPOSE
@@ -0,0 +1,7 @@
+PURPOSE of /CoreOS/openssh/Sanity/pam_ssh_agent_auth
+Description: This is basic sanity test for pam_ssh_agent_auth
+Author: Jakub Jelen <jjelen@redhat.com>
+
+Created as a response to rhbz#1251777 and previous one rhbz#1225106.
+The code of pam module is outdated and compiled with current openssh
+version which went through quite enough refactoring.
diff --git a/tests/pam_ssh_agent_auth/pam_save_ssh_var.c b/tests/pam_ssh_agent_auth/pam_save_ssh_var.c
new file mode 100644
index 0000000..e422fff
--- /dev/null
+++ b/tests/pam_ssh_agent_auth/pam_save_ssh_var.c
@@ -0,0 +1,73 @@
+/*
+This simple pam module saves the content of SSH_USER_AUTH variable to /tmp/SSH_USER_AUTH
+file.
+
+Setup:
+  - gcc -fPIC -DPIC -shared -rdynamic -o pam_save_ssh_var.o pam_save_ssh_var.c
+  - copy pam_save_ssh_var.o to /lib/security resp. /lib64/security
+  - add to /etc/pam.d/sshd
+	auth	requisite	pam_save_ssh_var.o
+*/
+
+/* Define which PAM interfaces we provide */
+#define PAM_SM_ACCOUNT
+#define PAM_SM_AUTH
+#define PAM_SM_PASSWORD
+#define PAM_SM_SESSION
+
+/* Include PAM headers */
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int save_ssh_var(pam_handle_t *pamh, const char *phase) {
+	FILE *fp;
+	const char *var;
+
+	fp = fopen("/tmp/SSH_USER_AUTH","a");
+	fprintf(fp, "BEGIN (%s)\n", phase);
+	var = pam_getenv(pamh, "SSH_USER_AUTH");
+	if (var != NULL) {
+		fprintf(fp, "SSH_USER_AUTH: '%s'\n", var);
+	}
+	fprintf(fp, "END (%s)\n", phase);
+	fclose(fp);
+
+	return 0;
+}
+
+/* PAM entry point for session creation */
+int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	return(PAM_IGNORE);
+}
+
+/* PAM entry point for session cleanup */
+int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	return(PAM_IGNORE);
+}
+
+/* PAM entry point for accounting */
+int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	return(PAM_IGNORE);
+}
+
+/* PAM entry point for authentication verification */
+int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	save_ssh_var(pamh, "auth");
+	return(PAM_IGNORE);
+}
+
+/*
+   PAM entry point for setting user credentials (that is, to actually
+   establish the authenticated user's credentials to the service provider)
+ */
+int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	return(PAM_IGNORE);
+}
+
+/* PAM entry point for authentication token (password) changes */
+int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+	return(PAM_IGNORE);
+}
+
diff --git a/tests/pam_ssh_agent_auth/runtest.sh b/tests/pam_ssh_agent_auth/runtest.sh
new file mode 100755
index 0000000..297be39
--- /dev/null
+++ b/tests/pam_ssh_agent_auth/runtest.sh
@@ -0,0 +1,184 @@
+#!/bin/bash
+# vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   runtest.sh of /CoreOS/openssh/Sanity/pam_ssh_agent_auth
+#   Description: This is a basic sanity test for pam_ssh_agent_auth
+#   Author: Jakub Jelen <jjelen@redhat.com>
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Copyright (c) 2015 Red Hat, Inc.
+#
+#   This program is free software: you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as
+#   published by the Free Software Foundation, either version 2 of
+#   the License, or (at your option) any later version.
+#
+#   This program 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 GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program. If not, see http://www.gnu.org/licenses/.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Include Beaker environment
+. /usr/bin/rhts-environment.sh || exit 1
+. /usr/share/beakerlib/beakerlib.sh || exit 1
+
+PACKAGE="openssh"
+PAM_SUDO="/etc/pam.d/sudo"
+PAM_SSHD="/etc/pam.d/sshd"
+PAM_MODULE="pam_save_ssh_var"
+SUDOERS_CFG="/etc/sudoers.d/01_pam_ssh_auth"
+SSHD_CFG="/etc/ssh/sshd_config"
+USER="testuser$RANDOM"
+PASS="testpassxy4re.3298fhdsaf"
+AUTH_KEYS="/etc/security/authorized_keys"
+AK_COMMAND_BIN="/root/ak.sh"
+AK_COMMAND_KEYS="/root/akeys"
+declare -a KEYS=("rsa" "ecdsa")
+
+rlJournalStart
+    rlPhaseStartSetup
+        rlAssertRpm $PACKAGE
+        rlAssertRpm pam_ssh_agent_auth
+        rlImport distribution/fips
+        rlServiceStart sshd
+        rlRun "TmpDir=\$(mktemp -d)" 0 "Creating tmp directory"
+        rlRun "cp ${PAM_MODULE}.c $TmpDir/"
+        rlRun "pushd $TmpDir"
+        rlFileBackup --clean $PAM_SUDO /etc/sudoers /etc/sudoers.d/ /etc/security/ $AUTH_KEYS
+        rlRun "sed -i '1 a\
+auth       sufficient   pam_ssh_agent_auth.so file=$AUTH_KEYS' $PAM_SUDO"
+        rlRun "echo 'Defaults    env_keep += \"SSH_AUTH_SOCK\"' > $SUDOERS_CFG"
+        rlRun "echo 'Defaults    !requiretty' >> $SUDOERS_CFG"
+        grep '^%wheel' /etc/sudoers || \
+           rlRun "echo '%wheel        ALL=(ALL)       ALL' >> $SUDOERS_CFG"
+        rlRun "useradd $USER -G wheel"
+        rlRun "echo $PASS |passwd --stdin $USER"
+    rlPhaseEnd
+
+    if ! fipsIsEnabled; then
+        KEYS+=("dsa")
+    fi
+
+    for KEY in "${KEYS[@]}"; do
+        rlPhaseStartTest "Test with key type $KEY"
+            rlRun "su $USER -c 'ssh-keygen -t $KEY -f ~/.ssh/my_id_$KEY -N \"\"'" 0
+
+            # Without authorized_keys, the authentication should fail
+            rlRun -s "su $USER -c 'eval \`ssh-agent\`; sudo id; ssh-agent -k'" 0
+            rlAssertNotGrep "uid=0(root) gid=0(root)" $rlRun_LOG
+
+            # Append the keys only to make sure we can match also the non-first line
+            rlRun "cat ~$USER/.ssh/my_id_${KEY}.pub >> $AUTH_KEYS"
+            rlRun -s "su $USER -c 'eval \`ssh-agent\`; ssh-add ~/.ssh/my_id_$KEY; sudo id; ssh-agent -k'"
+            rlAssertGrep "uid=0(root) gid=0(root)" $rlRun_LOG
+        rlPhaseEnd
+    done
+
+    if rlIsRHEL '<6.8' || ( rlIsRHEL '<7.3' && rlIsRHEL 7 ) ; then
+        : # not available
+    else
+        rlPhaseStartSetup "Setup for authorized_keys_command"
+            rlFileBackup --namespace ak_command $PAM_SUDO
+            rlRun "rm -f $AUTH_KEYS"
+            cat >$AK_COMMAND_BIN <<_EOF
+#!/bin/bash
+cat $AK_COMMAND_KEYS
+_EOF
+            rlRun "chmod +x $AK_COMMAND_BIN"
+            rlRun "sed -i 's|.*pam_ssh_agent_auth.*|auth sufficient pam_ssh_agent_auth.so authorized_keys_command=$AK_COMMAND_BIN authorized_keys_command_user=root|' $PAM_SUDO"
+            rlRun "cat $PAM_SUDO"
+        rlPhaseEnd
+
+        for KEY in "${KEYS[@]}"; do
+            rlPhaseStartTest "Test authorized_keys_command with key type $KEY (bz1299555, bz1317858)"
+                rlRun "cat ~$USER/.ssh/my_id_${KEY}.pub >$AK_COMMAND_KEYS"
+                rlRun -s "su $USER -c 'eval \`ssh-agent\`; ssh-add ~/.ssh/my_id_$KEY; sudo id; ssh-agent -k'"
+                rlAssertGrep "uid=0(root) gid=0(root)" $rlRun_LOG
+            rlPhaseEnd
+        done
+
+        rlPhaseStartCleanup "Cleanup for authorized_keys_command"
+            rlFileRestore --namespace ak_command
+            rlRun "rm -f $AK_COMMAND_BIN $AK_COMMAND_KEYS"
+        rlPhaseEnd
+    fi
+
+    if rlIsRHEL '>=7.3'; then # not in Fedora anymore
+        rlPhaseStartTest "bz1312304 - Exposing information about succesful auth"
+            rlRun "rlFileBackup --namespace exposing $PAM_SSHD"
+            rlRun "rlFileBackup --namespace exposing $SSHD_CFG"
+            rlRun "rlFileBackup --namespace exposing /root/.ssh/"
+            rlRun "rm -f ~/.ssh/id_rsa*"
+            rlRun "ssh-keygen -f ~/.ssh/id_rsa -N \"\"" 0
+            rlRun "ssh-keyscan localhost >~/.ssh/known_hosts" 0
+            USER_AK_FILE=~$USER/.ssh/authorized_keys
+            rlRun "cat ~/.ssh/id_rsa.pub >$USER_AK_FILE"
+            rlRun "chown $USER:$USER $USER_AK_FILE"
+            rlRun "chmod 0600 $USER_AK_FILE"
+            rlRun "gcc -fPIC -DPIC -shared -rdynamic -o $PAM_MODULE.o $PAM_MODULE.c"
+            rlRun "test -d /lib64/security && cp $PAM_MODULE.o /lib64/security/" 0,1
+            rlRun "test -d /lib/security && cp $PAM_MODULE.o /lib/security/" 0,1
+            rlRun "sed -i '1 i auth       optional         $PAM_MODULE.o' $PAM_SSHD"
+
+            # pam-and-env should expose information to both PAM and environmental variable;
+            # we will be testing only env variable here for the time being,
+            rlRun "echo 'ExposeAuthenticationMethods pam-and-env' >>$SSHD_CFG"
+            rlRun "sed -i '/^ChallengeResponseAuthentication/ d' $SSHD_CFG"
+            rlRun "service sshd restart"
+            rlWaitForSocket 22 -t 5
+            rlRun -s "ssh -i ~/.ssh/id_rsa $USER@localhost \"env|grep SSH_USER_AUTH\"" 0 \
+                "Environment variable SSH_USER_AUTH is set"
+            rlAssertGrep "^SSH_USER_AUTH=publickey:" $rlRun_LOG
+            rlRun "rm -f $rlRun_LOG"
+
+            # pam-only should expose information only to PAM and not to environment variable
+            rlRun "sed -i 's/pam-and-env/pam-only/' $SSHD_CFG"
+            rlRun "echo 'AuthenticationMethods publickey,keyboard-interactive:pam' >>$SSHD_CFG"
+            rlRun "service sshd restart"
+            rlWaitForSocket 22 -t 5
+ssh_with_pass() {
+    ssh_args=("-i /root/.ssh/id_rsa")
+    ssh_args+=("$USER@localhost")
+    cat >ssh.exp <<_EOF
+#!/usr/bin/expect -f
+
+set timeout 5
+spawn ssh ${ssh_args[*]} "echo CONNECTED; env|grep SSH_USER_AUTH"
+expect {
+    -re {.*[Pp]assword.*} { send -- "$PASS\r"; exp_continue }
+    timeout { exit 1 }
+    eof { exit 0 }
+}
+_EOF
+    rlRun -s "expect -f ssh.exp"
+}
+            #rlRun -s "ssh ${ssh_args[*]} \"echo CONNECTED; env|grep SSH_USER_AUTH\"" 1 \
+                #"Environment variable SSH_USER_AUTH is NOT set"
+            rlRun "ssh_with_pass"
+            rlRun "grep -q CONNECTED $rlRun_LOG" 0 "Connection was successful"
+            rlAssertGrep "^SSH_USER_AUTH: 'publickey:" /tmp/SSH_USER_AUTH
+            rlRun "cat /tmp/SSH_USER_AUTH"
+            rlRun "rm -f $rlRun_LOG /tmp/SSH_USER_AUTH"
+            for pm in /lib64/security/$PAM_MODULE.o /lib/security/$PAM_MODULE.o; do
+                rlRun "test -e $pm && rm -f $pm" 0,1
+            done
+            rlRun "rlFileRestore --namespace exposing"
+        rlPhaseEnd
+    fi
+
+    rlPhaseStartCleanup
+        rlRun "popd"
+        rlRun "rm -r $TmpDir" 0 "Removing tmp directory"
+        rlRun "userdel -fr $USER"
+        rlFileRestore
+        rlServiceRestore sshd
+    rlPhaseEnd
+rlJournalPrintText
+rlJournalEnd
diff --git a/tests/port-forwarding/Makefile b/tests/port-forwarding/Makefile
new file mode 100644
index 0000000..d325bdd
--- /dev/null
+++ b/tests/port-forwarding/Makefile
@@ -0,0 +1,63 @@
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Makefile of /CoreOS/openssh/Sanity/port-forwarding
+#   Description: Testing port forwarding (ideally all possibilities: -L, -R, -D)
+#   Author: Stanislav Zidek <szidek@redhat.com>
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Copyright (c) 2015 Red Hat, Inc.
+#
+#   This program is free software: you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as
+#   published by the Free Software Foundation, either version 2 of
+#   the License, or (at your option) any later version.
+#
+#   This program 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 GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program. If not, see http://www.gnu.org/licenses/.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+export TEST=/CoreOS/openssh/Sanity/port-forwarding
+export TESTVERSION=1.0
+
+BUILT_FILES=
+
+FILES=$(METADATA) runtest.sh Makefile PURPOSE
+
+.PHONY: all install download clean
+
+run: $(FILES) build
+	./runtest.sh
+
+build: $(BUILT_FILES)
+	test -x runtest.sh || chmod a+x runtest.sh
+
+clean:
+	rm -f *~ $(BUILT_FILES)
+
+
+-include /usr/share/rhts/lib/rhts-make.include
+
+$(METADATA): Makefile
+	@echo "Owner:           Stanislav Zidek <szidek@redhat.com>" > $(METADATA)
+	@echo "Name:            $(TEST)" >> $(METADATA)
+	@echo "TestVersion:     $(TESTVERSION)" >> $(METADATA)
+	@echo "Path:            $(TEST_DIR)" >> $(METADATA)
+	@echo "Description:     Testing port forwarding (ideally all possibilities: -L, -R, -D)" >> $(METADATA)
+	@echo "Type:            Sanity" >> $(METADATA)
+	@echo "TestTime:        5m" >> $(METADATA)
+	@echo "RunFor:          openssh" >> $(METADATA)
+	@echo "Requires:        openssh net-tools nc" >> $(METADATA)
+	@echo "Priority:        Normal" >> $(METADATA)
+	@echo "License:         GPLv2+" >> $(METADATA)
+	@echo "Confidential:    yes" >> $(METADATA)
+	@echo "Destructive:     no" >> $(METADATA)
+	@echo "Releases:        -RHEL4 -RHELClient5 -RHELServer5" >> $(METADATA)
+
+	rhts-lint $(METADATA)
diff --git a/tests/port-forwarding/PURPOSE b/tests/port-forwarding/PURPOSE
new file mode 100644
index 0000000..5a8bc87
--- /dev/null
+++ b/tests/port-forwarding/PURPOSE
@@ -0,0 +1,3 @@
+PURPOSE of /CoreOS/openssh/Sanity/port-forwarding
+Description: Testing port forwarding (ideally all possibilities: -L, -R, -D)
+Author: Stanislav Zidek <szidek@redhat.com>
diff --git a/tests/port-forwarding/runtest.sh b/tests/port-forwarding/runtest.sh
new file mode 100755
index 0000000..f18f2ae
--- /dev/null
+++ b/tests/port-forwarding/runtest.sh
@@ -0,0 +1,152 @@
+#!/bin/bash
+# vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   runtest.sh of /CoreOS/openssh/Sanity/port-forwarding
+#   Description: Testing port forwarding (ideally all possibilities: -L, -R, -D)
+#   Author: Stanislav Zidek <szidek@redhat.com>
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Copyright (c) 2015 Red Hat, Inc.
+#
+#   This program is free software: you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as
+#   published by the Free Software Foundation, either version 2 of
+#   the License, or (at your option) any later version.
+#
+#   This program 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 GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program. If not, see http://www.gnu.org/licenses/.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Include Beaker environment
+. /usr/share/beakerlib/beakerlib.sh || exit 1
+
+PACKAGE="openssh"
+USER="user$RANDOM"
+FORWARDED=$((RANDOM % 100 + 6800))
+LISTEN=$((RANDOM % 100 + 6900))
+TIMEOUT=5
+MESSAGE="HUGE_SUCCESS"
+SSH_OPTIONS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
+
+rlJournalStart
+    rlPhaseStartSetup
+        rlAssertRpm $PACKAGE
+        rlFileBackup /etc/ssh/sshd_config
+        rlRun "useradd -m $USER"
+        rlRun "su - $USER -c \"mkdir .ssh; chmod 700 .ssh; cd .ssh; ssh-keygen -N '' -f id_rsa; cat id_rsa.pub >authorized_keys; chmod 600 authorized_keys\""
+        rlRun "echo 'LogLevel DEBUG' >>/etc/ssh/sshd_config"
+        rlServiceStart sshd
+        rlRun "IP=\$( ip a |grep 'scope global' |grep -w inet |cut -d'/' -f1 |awk '{ print \$2 }' |tail -1 )"
+        rlRun "echo 'IP=$IP'"
+        rlRun "TmpDir=\$(mktemp -d)" 0 "Creating tmp directory"
+        rlRun "pushd $TmpDir"
+    rlPhaseEnd
+
+forwarding_test() {
+    EXP_RESULT=$1
+    FORWARDED=$2
+    HOST=$3
+    LISTEN=$4
+
+    rlRun "nc -l $LISTEN &>listen.log &"
+    LISTEN_PID=$!
+    rlWaitForSocket $LISTEN -t $TIMEOUT
+    rlRun "ps -fp $LISTEN_PID"
+    rlRun "su - $USER -c \"ssh $SSH_OPTIONS -N -L $FORWARDED:$HOST:$LISTEN $USER@localhost &\" &>tunnel.log"
+    rlRun "ps -fC ssh"
+    rlRun "SSH_PID=\$( pgrep -n -u $USER ssh )"
+    rlRun "echo SSH_PID is '$SSH_PID'"
+    rlWaitForSocket $FORWARDED -t $TIMEOUT
+    rlRun "[[ -n '$SSH_PID' ]] && ps -fp $SSH_PID"
+    rlRun "echo '$MESSAGE'|nc localhost $FORWARDED" 0,1
+
+    if [[ $EXP_RESULT == "success" ]]; then
+        rlAssertGrep "$MESSAGE" listen.log
+    else # failure expected
+        rlAssertGrep "open failed" tunnel.log -i
+        rlAssertGrep "administratively prohibited" tunnel.log -i
+        rlAssertNotGrep "$MESSAGE" listen.log
+    fi
+
+    rlRun "kill -9 $LISTEN_PID $SSH_PID" 0,1 "Killing cleanup"
+    rlWaitForSocket $LISTEN -t $TIMEOUT --close
+    rlWaitForSocket $FORWARDED -t $TIMEOUT --close
+    if ! rlGetPhaseState; then
+        rlRun "cat listen.log"
+        rlRun "cat tunnel.log"
+    fi
+    rlFileSubmit listen.log tunnel.log
+    rlRun "rm -f *.log;"
+}
+
+    rlPhaseStartTest "Local forwarding"
+        forwarding_test "success" $FORWARDED localhost $LISTEN
+        ((FORWARDED+=1))
+        ((LISTEN+=1))
+    rlPhaseEnd
+
+    rlPhaseStartTest "PermitOpen with 'any'"
+        rlFileBackup --namespace permitopen_any /etc/ssh/sshd_config /etc/hosts
+        rlRun "echo 'PermitOpen any' >>/etc/ssh/sshd_config"
+        rlRun "echo '$IP anyhost1 anyhost2' >>/etc/hosts"
+        rlRun "service sshd restart"
+        for i in `seq 3`; do
+            forwarding_test "success" $FORWARDED anyhost1 $LISTEN
+            forwarding_test "success" $FORWARDED anyhost2 $LISTEN
+            ((FORWARDED+=1))
+            ((LISTEN+=1))
+        done
+        rlFileRestore --namespace permitopen_any
+    rlPhaseEnd
+
+    if ! rlIsRHEL '<6.7'; then
+        # PermitOpen with wildcards is new feature in RHEL-6.7
+        rlPhaseStartTest "PermitOpen with port wildcard"
+            rlFileBackup --namespace port_wildcard /etc/ssh/sshd_config /etc/hosts
+            rlRun "echo 'PermitOpen wildportallow:*' >>/etc/ssh/sshd_config"
+            rlRun "echo '$IP wildportallow wildportdeny' >>/etc/hosts"
+            rlRun "service sshd restart"
+            forwarding_test "success" $FORWARDED wildportallow $LISTEN
+            ((FORWARDED+=1))
+            ((LISTEN+=1))
+            forwarding_test "failure" $FORWARDED wildportdeny $LISTEN
+            ((FORWARDED+=1))
+            ((LISTEN+=1))
+            rlFileRestore --namespace port_wildcard
+            rlRun "service sshd restart"
+        rlPhaseEnd
+    fi
+
+    if ! rlIsRHEL '<7.3'; then
+        rlPhaseStartTest "PermitOpen with host wildcard and specific port"
+            rlFileBackup --namespace host_wildcard /etc/ssh/sshd_config /etc/hosts
+            rlRun "echo 'PermitOpen *:$LISTEN' >>/etc/ssh/sshd_config"
+            rlRun "echo '$IP wildhost1 wildhost2' >>/etc/hosts"
+            rlRun "service sshd restart"
+            forwarding_test "success" $FORWARDED wildhost1 $LISTEN
+            ((FORWARDED+=1))
+            forwarding_test "success" $FORWARDED wildhost2 $LISTEN
+            ((FORWARDED+=1))
+            ((LISTEN+=1)) # different listen port, should fail
+            forwarding_test "failure" $FORWARDED wildhost2 $LISTEN
+            rlFileRestore --namespace host_wildcard
+        rlPhaseEnd
+    fi
+
+    rlPhaseStartCleanup
+        rlRun "userdel -rf $USER"
+        rlRun "popd"
+        rlFileRestore
+        rlServiceRestore sshd
+        rlRun "rm -r $TmpDir" 0 "Removing tmp directory"
+    rlPhaseEnd
+rlJournalPrintText
+rlJournalEnd
diff --git a/tests/tests.yml b/tests/tests.yml
new file mode 100644
index 0000000..8086af2
--- /dev/null
+++ b/tests/tests.yml
@@ -0,0 +1,31 @@
+---
+# Tests for docker container
+- hosts: localhost
+  tags:
+    - container
+  # no compatible tests
+
+# Tests for classic environment and Atomic Host
+- hosts: localhost
+  tags:
+    - all
+    - classic
+    - atomic
+  roles:
+  - role: standard-test-beakerlib
+    tests:
+    - port-forwarding
+    - pam_ssh_agent_auth
+    required_packages:
+    - iproute           # needs ip command
+    - procps-ng         # needs ps and pgrep commands
+    - initscripts       # needs service command
+    - openssh-clients   # needs ssh command
+    - findutils         # needs find command
+    - net-tools         # needs netstat command
+    - libselinux-utils  # needs selinuxenabled command
+    - nmap-ncat         # needs nc command
+    - pam_ssh_agent_auth
+    - gcc               # needs to test pam_ssh_agent_auth
+    - pam-devel         # needs to test pam_ssh_agent_auth
+    - expect            # needs to test pam_ssh_agent_auth