diff --git a/fbpatches/fb87_070_logging_reverse_port_forward.patch b/fbpatches/fb87_070_logging_reverse_port_forward.patch
new file mode 100644
index 0000000..339fd35
--- /dev/null
+++ b/fbpatches/fb87_070_logging_reverse_port_forward.patch
@@ -0,0 +1,48 @@
+Index: b/channels.c
+===================================================================
+--- b.orig/channels.c
++++ b/channels.c
+@@ -3774,6 +3774,7 @@ int
+ channel_setup_remote_fwd_listener(struct ssh *ssh, struct Forward *fwd,
+     int *allocated_listen_port, struct ForwardOptions *fwd_opts)
+ {
++  int success = 0;
+ 	if (!check_rfwd_permission(ssh, fwd)) {
+ 		ssh_packet_send_debug(ssh, "port forwarding refused");
+ 		if (fwd->listen_path != NULL)
+@@ -3795,14 +3796,23 @@ channel_setup_remote_fwd_listener(struct
+ 			    ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
+ 		return 0;
+ 	}
+-	if (fwd->listen_path != NULL) {
+-		return channel_setup_fwd_listener_streamlocal(ssh,
++  if (fwd->listen_path != NULL) {
++		success = channel_setup_fwd_listener_streamlocal(ssh,
+ 		    SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts);
+ 	} else {
+-		return channel_setup_fwd_listener_tcpip(ssh,
++		success = channel_setup_fwd_listener_tcpip(ssh,
+ 		    SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port,
+ 		    fwd_opts);
+ 	}
++  logit("Remote forward request %s: listen=%s:%d connect=%s:%d"
++         " uid=%d",
++         success ? "succeeded" : "failed",
++         fwd->listen_host,
++         fwd->listen_port,
++         ssh_remote_ipaddr(ssh),
++         ssh_remote_port(ssh),
++         getuid());
++  return success;
+ }
+ 
+ /*
+@@ -4593,7 +4603,7 @@ x11_create_display_inet(struct ssh *ssh,
+ 				if ((errno != EINVAL) && (errno != EAFNOSUPPORT)
+ #ifdef EPFNOSUPPORT
+ 				    && (errno != EPFNOSUPPORT)
+-#endif 
++#endif
+ 				    ) {
+ 					error("socket: %.100s", strerror(errno));
+ 					freeaddrinfo(aitop);
diff --git a/fbpatches/fb87_080_logging_certificates.patch b/fbpatches/fb87_080_logging_certificates.patch
new file mode 100644
index 0000000..46cc276
--- /dev/null
+++ b/fbpatches/fb87_080_logging_certificates.patch
@@ -0,0 +1,182 @@
+Index: b/auth2-pubkey.c
+===================================================================
+--- b.orig/auth2-pubkey.c
++++ b/auth2-pubkey.c
+@@ -389,6 +389,10 @@ check_principals_line(struct ssh *ssh, c
+ 			continue;
+ 		debug3("%s: matched principal \"%.100s\"",
+ 		    loc, cert->principals[i]);
++		verbose("Matched principal \"%.100s\" from %s against \"%.100s\" "
++		    "from cert",
++		    cp, loc, cert->principals[i]);
++
+ 		found = 1;
+ 		slog_set_principal(cp);
+ 	}
+@@ -432,6 +436,8 @@ process_principals(struct ssh *ssh, FILE
+ 			found_principal = 1;
+ 	}
+ 	free(line);
++	if (!found_principal)
++		verbose("Did not match any principals from auth_principals_* files");
+ 	return found_principal;
+ }
+ 
+@@ -710,7 +716,7 @@ check_authkey_line(struct ssh *ssh, stru
+ 	    &reason) != 0)
+ 		goto fail_reason;
+ 
+-	verbose("Accepted certificate ID \"%s\" (serial %llu) "
++	verbose("Accepted cert ID \"%s\" (serial %llu) "
+ 	    "signed by CA %s %s found at %s",
+ 	    key->cert->key_id,
+ 	    (unsigned long long)key->cert->serial,
+@@ -780,7 +786,7 @@ static int
+ user_cert_trusted_ca(struct ssh *ssh, struct passwd *pw, struct sshkey *key,
+     struct sshauthopt **authoptsp)
+ {
+-	char *ca_fp, *principals_file = NULL;
++	char *ca_fp, *key_fp, *principals_file = NULL;
+ 	const char *reason;
+ 	struct sshauthopt *principals_opts = NULL, *cert_opts = NULL;
+ 	struct sshauthopt *final_opts = NULL;
+@@ -796,11 +802,16 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+ 		return 0;
+ 
++	key_fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT);
++
+ 	if ((r = sshkey_in_file(key->cert->signature_key,
+ 	    options.trusted_user_ca_keys, 1, 0)) != 0) {
+ 		debug2_fr(r, "CA %s %s is not listed in %s",
+ 		    sshkey_type(key->cert->signature_key), ca_fp,
+ 		    options.trusted_user_ca_keys);
++		verbose("CA %s %s is not listed in %s",
++		    sshkey_type(key->cert->signature_key), ca_fp,
++		    options.trusted_user_ca_keys);
+ 		goto out;
+ 	}
+ 	/*
+@@ -851,6 +862,11 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 		if ((final_opts = sshauthopt_merge(principals_opts,
+ 		    cert_opts, &reason)) == NULL) {
+  fail_reason:
++			verbose("Rejected cert ID \"%s\" with signature "
++			    "%s signed by %s CA %s via %s",
++			    key->cert->key_id, key_fp,
++			    sshkey_type(key->cert->signature_key), ca_fp,
++			    options.trusted_user_ca_keys);
+ 			error("%s", reason);
+ 			auth_debug_add("%s", reason);
+ 			goto out;
+@@ -858,9 +874,10 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 	}
+ 
+ 	/* Success */
+-	verbose("Accepted certificate ID \"%s\" (serial %llu) signed by "
+-	    "%s CA %s via %s", key->cert->key_id,
+-	    (unsigned long long)key->cert->serial,
++	verbose("Accepted cert ID \"%s\" (serial %llu) with signature %s "
++	    "signed by %s CA %s via %s",
++	    key->cert->key_id,
++	    (unsigned long long)key->cert->serial, key_fp,
+ 	    sshkey_type(key->cert->signature_key), ca_fp,
+ 	    options.trusted_user_ca_keys);
+ 	if (authoptsp != NULL) {
+@@ -875,6 +892,7 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 	sshauthopt_free(final_opts);
+ 	free(principals_file);
+ 	free(ca_fp);
++	free(key_fp);
+ 	return ret;
+ }
+ 
+Index: b/regress/cert-logging.sh
+===================================================================
+--- /dev/null
++++ b/regress/cert-logging.sh
+@@ -0,0 +1,84 @@
++tid="cert logging"
++
++CERT_ID="cert_id"
++PRINCIPAL=$USER
++SERIAL=0
++
++log_grep() {
++    if [ "$(grep -c -G "$1" "$TEST_SSHD_LOGFILE")" == "0" ]; then
++        return 1;
++    else
++        return 0;
++    fi
++}
++
++cat << EOF >> $OBJ/sshd_config
++TrustedUserCAKeys $OBJ/ssh-rsa.pub
++Protocol 2
++PubkeyAuthentication yes
++AuthenticationMethods publickey
++AuthorizedPrincipalsFile $OBJ/auth_principals
++EOF
++
++if [ ! -f $OBJ/trusted_rsa ]; then
++    ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/trusted_rsa
++fi
++if [ ! -f $OBJ/untrusted_rsa ]; then
++    ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/untrusted_rsa
++fi
++
++${SSHKEYGEN} -q -s $OBJ/ssh-rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/trusted_rsa.pub ||
++    fatal "Could not create trusted SSH cert"
++
++${SSHKEYGEN} -q -s $OBJ/untrusted_rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/untrusted_rsa.pub ||
++    fatal "Could not create untrusted SSH cert"
++
++CA_FP="$(${SSHKEYGEN} -l -E sha256 -f ssh-rsa | cut -d' ' -f2)"
++KEY_FP="$(${SSHKEYGEN} -l -E sha256 -f trusted_rsa | cut -d' ' -f2)"
++UNTRUSTED_CA_FP="$(${SSHKEYGEN} -l -E sha256 -f untrusted_rsa | cut -d' ' -f2)"
++
++start_sshd
++
++
++test_no_principals() {
++    echo > $OBJ/auth_principals
++    ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true ||
++        fatal "SSH failed"
++
++    if ! log_grep 'Did not match any principals from auth_principals_\* files'; then
++        fail "No 'Did not match any principals' message"
++    fi
++
++    if ! log_grep "Rejected cert ID \"$CERT_ID\" with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then
++        fail "No 'Rejected cert ID' message"
++    fi
++}
++
++
++test_with_principals() {
++    echo $USER > $OBJ/auth_principals
++    ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true ||
++        fatal "SSH failed"
++
++    if ! log_grep "Matched principal \"$PRINCIPAL\" from $OBJ/auth_principals:1 against \"$PRINCIPAL\" from cert"; then
++        fail "No 'Matched principal' message"
++    fi
++    if ! log_grep "Accepted cert ID \"$CERT_ID\" (serial $SERIAL) with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then
++        fail "No 'Accepted cert ID' message"
++    fi
++}
++
++
++test_untrusted_cert() {
++    ${SSH} -F $OBJ/ssh_config -i $OBJ/untrusted_rsa-cert.pub somehost true ||
++        fatal "SSH failed"
++
++    if ! log_grep "CA RSA $UNTRUSTED_CA_FP is not listed in $OBJ/ssh-rsa.pub"; then
++        fail "No 'CA is not listed' message"
++    fi
++}
++
++
++test_no_principals
++test_with_principals
++test_untrusted_cert
diff --git a/fbpatches/fb87_090_logging_shell_cmd_pty.patch b/fbpatches/fb87_090_logging_shell_cmd_pty.patch
new file mode 100644
index 0000000..6017884
--- /dev/null
+++ b/fbpatches/fb87_090_logging_shell_cmd_pty.patch
@@ -0,0 +1,73 @@
+Index: b/session.c
+===================================================================
+--- b.orig/session.c
++++ b/session.c
+@@ -2049,6 +2049,8 @@ session_pty_req(struct ssh *ssh, Session
+ 		return 0;
+ 	}
+ 	debug("session_pty_req: session %d alloc %s", s->self, s->tty);
++	verbose("Allocated pty %s for user %s session %d",
++					s->tty, s->pw->pw_name, s->self);
+ 
+ 	ssh_tty_parse_modes(ssh, s->ttyfd);
+ 
+@@ -2148,6 +2150,7 @@ session_shell_req(struct ssh *ssh, Sessi
+ 
+ 	if ((r = sshpkt_get_end(ssh)) != 0)
+ 		sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
++	verbose("Shell Request for user %s", s->pw->pw_name);
+ 	return do_exec(ssh, s, NULL) == 0;
+ }
+ 
+@@ -2163,6 +2166,7 @@ session_exec_req(struct ssh *ssh, Sessio
+ 		sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
+ 
+ 	slog_set_command(command);
++	verbose("Exec Request for user %s with command %s", s->pw->pw_name, command);
+ 	success = do_exec(ssh, s, command) == 0;
+ 	free(command);
+ 	return success;
+Index: b/regress/session-req.sh
+===================================================================
+--- /dev/null
++++ b/regress/session-req.sh
+@@ -0,0 +1,39 @@
++tid="session req"
++
++start_sshd
++
++test_user_shell_exec_req() {
++  session_shell_req_expected="Exec Request for user $USER with command true"
++  cnt=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE")
++  if [ $cnt == "0" ]; then
++  	fail "No exec request for user log lines found"
++  fi
++}
++
++test_user_pty() {
++  session_pty_req_expected="Allocated pty .* for user $USER session .*"
++  line_count=$(grep -c "$session_req_expected" "$TEST_SSHD_LOGFILE")
++  if [ $line_count == "0" ]; then
++  	fail "No Allocated pty for user session found in log lines"
++  fi
++}
++
++test_user_shell_req() {
++  exit | ${SSH} -F $OBJ/ssh_config somehost
++  if [ $? -ne 0 ]; then
++  	fail "ssh connect with failed"
++  fi
++  session_shell_req_expected="Shell Request for user $USER"
++  line_count=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE")
++  if [ $line_count == "0" ]; then
++  	fail "No session request for user log lines found"
++  fi
++}
++
++${SSH} -F $OBJ/ssh_config somehost true
++if [ $? -ne 0 ]; then
++	fail "ssh connect with failed"
++fi
++test_user_shell_exec_req
++test_user_pty
++test_user_shell_req
diff --git a/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch b/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch
new file mode 100644
index 0000000..2abe0e6
--- /dev/null
+++ b/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch
@@ -0,0 +1,13 @@
+Index: b/sshkey.h
+===================================================================
+--- b.orig/sshkey.h
++++ b/sshkey.h
+@@ -106,7 +106,7 @@ enum sshkey_private_format {
+ /* key is stored in external hardware */
+ #define SSHKEY_FLAG_EXT		0x0001
+ 
+-#define SSHKEY_CERT_MAX_PRINCIPALS	256
++#define SSHKEY_CERT_MAX_PRINCIPALS	1024
+ /* XXX opaquify? */
+ struct sshkey_cert {
+ 	struct sshbuf	*certblob; /* Kept around for use on wire */
diff --git a/fbpatches/fb87_log_accept_env.patch b/fbpatches/fb87_log_accept_env.patch
new file mode 100644
index 0000000..24ba1fc
--- /dev/null
+++ b/fbpatches/fb87_log_accept_env.patch
@@ -0,0 +1,22 @@
+diff --git a/session.c b/session.c
+--- a/session.c
++++ b/session.c
+@@ -2106,16 +2106,16 @@
+ 
+ 	for (i = 0; i < options.num_accept_env; i++) {
+ 		if (match_pattern(name, options.accept_env[i])) {
+-			debug2("Setting env %d: %s=%s", s->num_env, name, val);
++			verbose("Setting env %d: %s=%s user=%s", s->num_env, name, val, s->pw->pw_name);
+ 			s->env = xrecallocarray(s->env, s->num_env,
+ 			    s->num_env + 1, sizeof(*s->env));
+ 			s->env[s->num_env].name = name;
+ 			s->env[s->num_env].val = val;
+ 			s->num_env++;
+ 			return (1);
+ 		}
+ 	}
+-	debug2("Ignoring env request %s: disallowed name", name);
++	verbose("Ignoring env request %s user=%s : disallowed name", name, s->pw->pw_name);
+ 
+  fail:
+ 	free(name);
diff --git a/fbpatches/fb87_log_auth_info.patch b/fbpatches/fb87_log_auth_info.patch
new file mode 100644
index 0000000..45f320a
--- /dev/null
+++ b/fbpatches/fb87_log_auth_info.patch
@@ -0,0 +1,216 @@
+Index: b/regress/slog.sh
+===================================================================
+--- b.orig/regress/slog.sh
++++ b/regress/slog.sh
+@@ -1,41 +1,60 @@
+ tid='structured log'
+ 
+-port="4242"
+ log_prefix="sshd_auth_msg:"
+-log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version"
++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful command end_time duration auth_info client_version"
+ do_log_json="yes"
+-test_config="$OBJ/sshd2_config"
+-old_config="$OBJ/sshd_config"
+-PIDFILE=$OBJ/pidfile
+-
+-cat << EOF > $test_config
+-	#*:
+-	StrictModes             no
+-	Port                    $port
+-	AddressFamily           inet
+-	ListenAddress           127.0.0.1
+-	#ListenAddress          ::1
+-	PidFile                 $PIDFILE
+-	AuthorizedKeysFile      $OBJ/authorized_keys_%u
+-	LogLevel                ERROR
+-	AcceptEnv               _XXX_TEST_*
+-	AcceptEnv               _XXX_TEST
+-	HostKey $OBJ/host.ssh-ed25519
+-	LogFormatPrefix $log_prefix
+-	LogFormatJson $do_log_json
+-	LogFormatKeys $log_keys
++
++AUTH_PRINC_FILE="$OBJ/auth_principals"
++CA_FILE="$OBJ/ca-rsa"
++IDENTITY_FILE="$OBJ/$USER-rsa"
++CERT_ID=$USER
++
++cat << EOF >>	$OBJ/sshd_config
++TrustedUserCAKeys $CA_FILE.pub
++PubkeyAuthentication yes
++AuthenticationMethods publickey
++AuthorizedPrincipalsFile $AUTH_PRINC_FILE
++LogFormatPrefix $log_prefix
++LogFormatJson $do_log_json
++LogFormatKeys $log_keys
+ EOF
+ 
++sed -i 's/DEBUG3/VERBOSE/g' $OBJ/sshd_config
+ 
+-cp $test_config $old_config
+-start_sshd
++cleanup() {
++	rm -f $CA_FILE{.pub,}
++	rm -f $IDENTITY_FILE{-cert.pub,.pub,}
++	rm -f $AUTH_PRINC_FILE
++	rm -f $TEST_SSHD_LOGFILE
++}
++
++make_keys() {
++	local keytype=$1
++
++	rm -f $IDENTITY_FILE{.pub,}
++	${SSHKEYGEN} -q -t $keytype -C '' -N '' -f $IDENTITY_FILE ||
++	    fatal 'Could not create keypair'
++
++	cat $IDENTITY_FILE.pub > authorized_keys_$USER
++	${SSHKEYGEN} -lf $IDENTITY_FILE
++}
+ 
+-${SSH} -F $OBJ/ssh_config somehost true
+-if [ $? -ne 0 ]; then
+-	fail "ssh connect with failed"
+-fi
++make_cert() {
++	local princs=$1
++	local certtype=$2
++	local serial=$3
+ 
+-test_log_counts() {
++	rm -f $CA_FILE
++	rm -f "$IDENTITY_FILE-cert.pub"
++
++	${SSHKEYGEN} -q -t $certtype -C '' -N '' -f $CA_FILE ||
++	    fatal 'Could not create CA key'
++
++	${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z $serial "$IDENTITY_FILE.pub" ||
++	    fatal "Could not create SSH cert"
++}
++
++do_test_log_counts() {
+ 	cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE")
+ 	if [ $cnt -ne 2 ]; then
+ 		fail "expected 2 structured logging lines, got $cnt"
+@@ -43,7 +62,10 @@ test_log_counts() {
+ }
+ 
+ test_json_valid() {
+-	which python &>/dev/null || echo 'python not found in path, skipping tests'
++	if ! $(which python &>/dev/null) ; then
++		 echo 'python not found in path, skipping JSON tests'
++		 return 1
++	fi
+ 
+ 	loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix")
+ 	first=$(echo "$loglines" | head -n1)
+@@ -55,5 +77,72 @@ test_json_valid() {
+ 	    || fail "invalid json structure $last"
+ }
+ 
+-test_log_counts
+-test_json_valid
++# todo: first/last line
++extract_key() {
++	local key=$1
++	loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix")
++	last=$(echo "$loglines" | tail -n1)
++	json=${last:$(expr length $log_prefix)}
++
++	val=$(echo $json | python -c "import sys, json; print(json.load(sys.stdin)[\"$key\"])") ||
++	    fail "error extracting $key from $json"
++	echo "$val"
++}
++
++test_basic_logging() {
++	${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true ||
++		    fatal "SSH failed"
++
++	do_test_log_counts
++	test_json_valid || return 1
++}
++
++extract_hash() {
++	local source=$1
++	echo $source | sed "s/.*\(SHA256:[[:print:]]\{43\}\).*$/\1/"
++}
++
++test_auth_info() {
++	local keyfp=$1
++	local keytype=$2
++	local princ=$3
++	local serial=$4
++
++	${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true ||
++	    fatal "SSH failed"
++
++	auth_info=$(extract_key 'auth_info')
++	digest=$(extract_hash "$keyfp")
++
++	[ -z "$keyfp" ] || echo "$auth_info" | grep -q "$digest" ||
++		echo "hash digest not found"
++	[ -z "$keytype" ] || echo "$auth_info" | grep -q "$keytype" ||
++		echo "keytype not found"
++	[ -z "$princ" ] || echo "$auth_info" | grep -q "$princ" ||
++		echo "princ not found"
++	[ -z "$serial" ] || echo "$auth_info" | grep -q "$serial" ||
++		echo "serial not found"
++}
++
++test_cert_serial() {
++	local serial=$1
++	logged_serial=$(extract_key 'cert_serial')
++	 [ $serial = $logged_serial ] || fail 'cert serial mismatch'
++}
++
++start_sshd
++
++keytype="RSA"
++keyfp=$(make_keys $keytype)
++test_basic_logging || return
++test_auth_info "$keyfp" "$keytype"
++
++rm authorized_keys_$USER # force cert auth
++
++princ="$USER"
++echo $princ > $AUTH_PRINC_FILE
++
++serial='42'
++make_cert "$princ" "$keytype" "$serial"
++test_auth_info "$keyfp" "$keytype" "$princ" "$serial"
++test_cert_serial "$serial"
+Index: b/auth.c
+===================================================================
+--- b.orig/auth.c
++++ b/auth.c
+@@ -351,6 +351,8 @@ auth_log(struct ssh *ssh, int authentica
+ 	    extra != NULL ? ": " : "",
+ 	    extra != NULL ? extra : "");
+ 
++	if (extra != NULL)
++		slog_set_auth_info(extra);
+ 	free(extra);
+ 	slog_set_auth_data(authenticated, method, authctxt->user);
+ 
+Index: b/auth2-pubkey.c
+===================================================================
+--- b.orig/auth2-pubkey.c
++++ b/auth2-pubkey.c
+@@ -722,7 +722,7 @@ check_authkey_line(struct ssh *ssh, stru
+ 	    (unsigned long long)key->cert->serial,
+ 	    sshkey_type(found), fp, loc);
+ 
+-	    slog_set_cert_serial(key->cert->serial);
++	slog_set_cert_serial(key->cert->serial);
+  success:
+ 	if (finalopts == NULL)
+ 		fatal_f("internal error: missing options");
+@@ -885,6 +885,8 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 		final_opts = NULL;
+ 	}
+ 	slog_set_cert_id(key->cert->key_id);
++	slog_set_cert_serial(key->cert->serial);
++
+ 	ret = 1;
+  out:
+ 	sshauthopt_free(principals_opts);
diff --git a/fbpatches/fb87_log_port_forwards.patch b/fbpatches/fb87_log_port_forwards.patch
new file mode 100644
index 0000000..8a7b030
--- /dev/null
+++ b/fbpatches/fb87_log_port_forwards.patch
@@ -0,0 +1,24 @@
+Index: b/serverloop.c
+===================================================================
+--- b.orig/serverloop.c
++++ b/serverloop.c
+@@ -433,6 +433,7 @@ server_request_direct_tcpip(struct ssh *
+ 	char *target = NULL, *originator = NULL;
+ 	u_int target_port = 0, originator_port = 0;
+ 	int r;
++	uid_t user;
+ 
+ 	if ((r = sshpkt_get_cstring(ssh, &target, NULL)) != 0 ||
+ 	    (r = sshpkt_get_u32(ssh, &target_port)) != 0 ||
+@@ -451,6 +452,11 @@ server_request_direct_tcpip(struct ssh *
+ 		goto out;
+ 	}
+ 
++	user = getuid();
++	logit("Tunnel: %s:%d -> %s:%d UID(%d) user %s",
++	    originator, originator_port, target, target_port, user,
++	    getpwuid(user)->pw_name);
++
+ 	debug_f("originator %s port %u, target %s port %u",
+ 	    originator, originator_port, target, target_port);
+ 
diff --git a/fbpatches/fb87_log_session_id.patch b/fbpatches/fb87_log_session_id.patch
new file mode 100644
index 0000000..ac9d6a3
--- /dev/null
+++ b/fbpatches/fb87_log_session_id.patch
@@ -0,0 +1,141 @@
+Index: b/sshd.c
+===================================================================
+--- b.orig/sshd.c
++++ b/sshd.c
+@@ -1420,6 +1420,8 @@ server_accept_loop(struct ssh *ssh, int
+ 				    options.log_level,
+ 				    options.log_facility,
+ 				    log_stderr);
++
++				set_log_session_id();  // Set log session ID for this session
+ 				if (rexec_flag)
+ 					close(config_s[0]);
+ 				else {
+Index: b/auth-pam.c
+===================================================================
+--- b.orig/auth-pam.c
++++ b/auth-pam.c
+@@ -766,7 +766,20 @@ sshpam_init(struct ssh *ssh, Authctxt *a
+ 		return (-1);
+ 	}
+ #endif
+-	return (0);
++	debug("PAM: setting PAM LOG_SESSION_ID to \"%s\"", get_log_session_id());
++	{
++		char log_session_id_env[HOST_NAME_MAX + 50];
++		snprintf(log_session_id_env, sizeof(log_session_id_env),
++				"LOG_SESSION_ID=%s", get_log_session_id());
++		sshpam_err = pam_putenv(sshpam_handle, log_session_id_env);
++		if (sshpam_err != PAM_SUCCESS) {
++			pam_end(sshpam_handle, sshpam_err);
++			sshpam_handle = NULL;
++			return (-1);
++		}
++	}
++
++        return (0);
+ }
+ 
+ static void
+Index: b/log.c
+===================================================================
+--- b.orig/log.c
++++ b/log.c
+@@ -410,17 +410,17 @@ do_log(LogLevel level, int force, const
+ 		tmp_handler(level, force, fmtbuf, log_handler_ctx);
+ 		log_handler = tmp_handler;
+ 	} else if (log_on_stderr) {
+-		snprintf(msgbuf, sizeof msgbuf, "%.*s\r\n",
+-		    (int)sizeof msgbuf - 3, fmtbuf);
++		snprintf(msgbuf, sizeof msgbuf, "%.*s session=%s\r\n",
++		    (int)sizeof msgbuf - 3, fmtbuf, get_log_session_id());
+ 		(void)write(log_stderr_fd, msgbuf, strlen(msgbuf));
+ 	} else {
+ #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
+ 		openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata);
+-		syslog_r(pri, &sdata, "%.500s", fmtbuf);
++		syslog_r(pri, &sdata, "%.500s session=%s", fmtbuf, get_log_session_id());
+ 		closelog_r(&sdata);
+ #else
+ 		openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility);
+-		syslog(pri, "%.500s", fmtbuf);
++		syslog(pri, "%.500s session=%s", fmtbuf, get_log_session_id());
+ 		closelog();
+ #endif
+ 	}
+@@ -452,6 +452,33 @@ sshlogdie(const char *file, const char *
+ }
+ 
+ void
++set_log_session_id()
++{
++        struct timeval tv;
++        char hostname[HOST_NAME_MAX + 1];
++        char session_id[HOST_NAME_MAX + 20];
++        char *s;
++        if (gethostname(hostname, sizeof(hostname)) != 0) {
++                *hostname = '\0';
++        }
++        gettimeofday(&tv, NULL);
++        snprintf(session_id, sizeof(session_id), "%s:%x.%x",
++                 hostname, tv.tv_sec, tv.tv_usec);
++        setenv("LOG_SESSION_ID", session_id, 1);
++}
++
++const char *
++get_log_session_id()
++{
++        const char *id = getenv("LOG_SESSION_ID");
++        if (!id) {
++                 set_log_session_id();
++                 id = getenv("LOG_SESSION_ID");
++        }
++        return id;
++}
++
++void
+ sshsigdie(const char *file, const char *func, int line, int showfunc,
+     LogLevel level, const char *suffix, const char *fmt, ...)
+ {
+Index: b/regress/session-id.sh
+===================================================================
+--- /dev/null
++++ b/regress/session-id.sh
+@@ -0,0 +1,23 @@
++tid="session id"
++
++start_sshd
++
++${SSH} -F $OBJ/ssh_config somehost true
++if [ $? -ne 0 ]; then
++	fail "ssh connect with failed"
++fi
++
++expected="session=$(hostname)"
++
++# grab the first session ID which will be stable across session
++sessionid=$(grep -m1 $expected $TEST_SSHD_LOGFILE | sed -E 's/.*(session=.*)/\1/')
++
++line_count=$(grep -c $expected $TEST_SSHD_LOGFILE)
++if [ $line_count == "0" ]; then
++	fail "No session ID lines found"
++fi
++
++stable_id_count=$(grep -c $sessionid $TEST_SSHD_LOGFILE)
++if [ $line_count != $stable_id_count ]; then
++	fail 'Mismatching session ids found'
++fi
+Index: b/log.h
+===================================================================
+--- b.orig/log.h
++++ b/log.h
+@@ -68,6 +68,9 @@ const char *	log_level_name(LogLevel);
+ void	 set_log_handler(log_handler_fn *, void *);
+ void	 cleanup_exit(int) __attribute__((noreturn));
+ 
++void		set_log_session_id();
++const char *	get_log_session_id();
++
+ void	 sshlog(const char *, const char *, int, int,
+     LogLevel, const char *, const char *, ...)
+     __attribute__((format(printf, 7, 8)));
diff --git a/fbpatches/fb87_pass_principals_to_child.patch b/fbpatches/fb87_pass_principals_to_child.patch
new file mode 100644
index 0000000..27bb7ca
--- /dev/null
+++ b/fbpatches/fb87_pass_principals_to_child.patch
@@ -0,0 +1,224 @@
+diff --git a/session.c b/session.c
+--- a/session.c
++++ b/session.c
+@@ -98,6 +98,7 @@
+ #include "atomicio.h"
+ #include "slog.h"
+ 
++#define SSH_MAX_PUBKEY_BYTES 16384
+ 
+ #if defined(KRB5) && defined(USE_AFS)
+ #include <kafs.h>
+@@ -990,11 +991,18 @@
+ static char **
+ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
+ {
+-	char buf[256];
++	char buf[SSH_MAX_PUBKEY_BYTES];
++	char *pbuf = &buf[0];
+ 	size_t n;
+ 	u_int i, envsize;
+ 	char *ocp, *cp, *value, **env, *laddr;
+ 	struct passwd *pw = s->pw;
++	Authctxt *authctxt = s->authctxt;
++	struct sshkey *key;
++	size_t len = 0;
++	ssize_t total = 0;
++	struct sshkey_cert *cert;
++
+ #if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN)
+ 	char *path = NULL;
+ #endif
+@@ -1188,9 +1196,57 @@
+ 		child_set_env(&env, &envsize, "SSH_USER_AUTH", auth_info_file);
+ 	if (s->ttyfd != -1)
+ 		child_set_env(&env, &envsize, "SSH_TTY", s->tty);
+-	if (original_command)
++	if (original_command) {
+ 		child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND",
+ 		    original_command);
++		/*
++		* Set SSH_CERT_PRINCIPALS to be the principals on the ssh certificate.
++		* Only do so when a force command is present to prevent the client
++		* from changing the value of SSH_CERT_PRINCIPALS. For example, when a
++		* client is given shell access, the client can easily change the
++		* value of an environment variable by running, e.g.,
++		* ssh user@host.address 'SSH_CERT_PRINCIPALS=attacker env'
++		*/
++
++		if (authctxt->nprev_keys > 0) {
++			key = authctxt->prev_keys[authctxt->nprev_keys-1];
++			/* If a user was authorized by a certificate, set SSH_CERT_PRINCIPALS */
++			if (sshkey_is_cert(key)) {
++				cert = key->cert;
++
++				for (i = 0; i < cert->nprincipals - 1; ++i) {
++					/*
++					* total: bytes written to buf so far
++					* 2: one for comma and one for '\0' to be added by snprintf
++					* We stop at the first principal overflowing buf.
++					*/
++					if (total + strlen(cert->principals[i]) + 2 > SSH_MAX_PUBKEY_BYTES)
++						break;
++
++					len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s,",
++					    cert->principals[i]);
++					/* pbuf advances by len, the '\0' at the end will be overwritten */
++					pbuf += len;
++					total += len;
++				}
++
++				if (total + strlen(cert->principals[i]) + 1 <= SSH_MAX_PUBKEY_BYTES) {
++					len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s",
++					    cert->principals[i]);
++					total += len;
++				} else if (total > 0)
++					/*
++					* If we hit the overflow condition, remove the trailing comma.
++					* We only do so if the overflowing principal is not the first one on the
++					* certificate so that there is at least one principal in buf
++					*/
++					buf[total-1] = '\0';
++
++				if (total > 0)
++					child_set_env(&env, &envsize, "SSH_CERT_PRINCIPALS", buf);
++			}
++		}
++	}
+ 
+ 	if (debug_flag) {
+ 		/* dump the environment */
+diff --git a/regress/cert-princ-env.sh b/regress/cert-princ-env.sh
+new file mode 100644
+--- /dev/null
++++ b/regress/cert-princ-env.sh
+@@ -0,0 +1,129 @@
++tid="cert principal env"
++
++# change to ecdsa
++CERT_ID="$USER"
++AUTH_PRINC_FILE="$OBJ/auth_principals"
++CA_FILE="$OBJ/ca-rsa"
++IDENTITY_FILE="$OBJ/$USER-rsa"
++SSH_MAX_PUBKEY_BYTES=16384
++
++cat << EOF >> $OBJ/sshd_config
++TrustedUserCAKeys $CA_FILE.pub
++Protocol 2
++PubkeyAuthentication yes
++AuthenticationMethods publickey
++AuthorizedPrincipalsFile $AUTH_PRINC_FILE
++ForceCommand=/bin/env
++EOF
++
++cleanup() {
++	rm -f $CA_FILE{.pub,}
++	rm -f $IDENTITY_FILE{-cert.pub,.pub,}
++	rm -f $AUTH_PRINC_FILE
++}
++
++make_keys_and_certs() {
++	rm -f $CA_FILE{.pub,}
++	rm -f $IDENTITY_FILE{-cert.pub,.pub,}
++
++  local princs=$1
++
++	${SSHKEYGEN} -q -t rsa -C '' -N '' -f $CA_FILE ||
++	    fatal 'Could not create CA key'
++
++	${SSHKEYGEN} -q -t rsa -C '' -N '' -f $IDENTITY_FILE ||
++	    fatal 'Could not create keypair'
++
++	${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z "42" "$IDENTITY_FILE.pub" ||
++	    fatal "Could not create SSH cert"
++}
++
++test_with_expected_principals() {
++	local princs=$1
++
++	out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) ||
++	    fatal "SSH failed"
++
++	echo "$out" | grep -q "SSH_CERT_PRINCIPALS=$princs$" ||
++	    fatal "SSH_CERT_PRINCIPALS has incorrect value"
++}
++
++test_with_no_expected_principals() {
++	local princs=$1
++
++	out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) ||
++	    fatal "SSH failed"
++
++	echo "$out" | grep -vq "SSH_CERT_PRINCIPALS" ||
++	    fatal "SSH_CERT_PRINCIPALS env should not be set"
++
++	echo "$out" | grep -vq "SSH_CERT_PRINCIPALS=$princs" ||
++	    fatal "SSH_CERT_PRINCIPALS has incorrect value"
++}
++
++
++echo 'a' > $AUTH_PRINC_FILE
++start_sshd
++
++principals="a,b,c,d"
++make_keys_and_certs "$principals"
++test_with_expected_principals "$principals"
++
++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16381 | head -n 1)
++make_keys_and_certs "a,$big_princ"
++test_with_expected_principals "a,$big_princ"
++
++# No room for two principals
++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16382 | head -n 1)
++make_keys_and_certs "a,$big_princ"
++test_with_expected_principals "a"
++
++make_keys_and_certs "$big_princ,a"
++test_with_expected_principals "$big_princ"
++
++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16384 | head -n 1)
++make_keys_and_certs "a,$big_princ"
++test_with_expected_principals "a"
++
++# principal too big for buffer
++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $SSH_MAX_PUBKEY_BYTES | head -n 1)
++make_keys_and_certs "$big_princ"
++test_with_no_expected_principals "$big_princ"
++
++# no matching principals in certificate and auth princ file
++principals="b,c,d"
++make_keys_and_certs "$principals"
++test_with_no_expected_principals "$principals"
++
++stop_sshd
++
++cat << EOF >> $OBJ/sshd_config
++TrustedUserCAKeys $CA_FILE.pub
++Protocol 2
++PubkeyAuthentication yes
++AuthenticationMethods publickey
++AuthorizedPrincipalsFile $AUTH_PRINC_FILE
++EOF
++
++start_sshd
++
++# no force command, no princpals
++principals="a,b,c,d"
++make_keys_and_certs "$principals"
++test_with_no_expected_principals "$principals"
++
++stop_sshd
++
++cat << EOF >> $OBJ/sshd_config
++Protocol 2
++PubkeyAuthentication yes
++AuthenticationMethods publickey
++AuthorizedPrincipalsFile $AUTH_PRINC_FILE
++EOF
++
++start_sshd
++
++# No TrustedUserCAKeys causes pubkey auth, no principals
++principals="a,b,c,d"
++make_keys_and_certs "$principals"
++test_with_no_expected_principals "$principals"
diff --git a/fbpatches/fb87_slog.patch b/fbpatches/fb87_slog.patch
new file mode 100644
index 0000000..427cdda
--- /dev/null
+++ b/fbpatches/fb87_slog.patch
@@ -0,0 +1,1148 @@
+Index: b/slog.c
+===================================================================
+--- /dev/null
++++ b/slog.c
+@@ -0,0 +1,619 @@
++/*
++ * Copyright 2004-present Facebook. All Rights Reserved.
++ */
++
++ /* When using slogctxt in any module perform a NULL pointer check */
++
++#include <sys/types.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <stdio.h>
++#include <string.h>
++#include <syslog.h>
++#include <pwd.h>
++#include <time.h>
++
++#include "includes.h"
++#include "slog.h"
++#include "log.h"
++#include "misc.h"
++#include "servconf.h"
++#include "xmalloc.h"
++
++typedef struct Structuredlogctxt Structuredlogctxt;
++
++struct Structuredlogctxt { /* items we care about logging */
++	char		server_ip[SLOG_SHORT_STRING_LEN];		// server_ip
++	int		server_port;		// server_port
++	char		remote_ip[SLOG_SHORT_STRING_LEN];		// remote_ip
++	int		remote_port;		// remote_port
++	pid_t		pam_process_pid;		// pam_pid
++	char		session_id[SLOG_STRING_LEN];		// session_id
++	char		method[SLOG_STRING_LEN];		// method
++	char		cert_id[SLOG_STRING_LEN];		// cert_id
++	unsigned long long		cert_serial;		// cert_serial
++	char		principal[SLOG_STRING_LEN];		// principal
++	char		user[SLOG_STRING_LEN];		// user
++	char		command[SLOG_LONG_STRING_LEN];		// command
++	SLOG_SESSION_STATE		session_state;		// session_state
++	SLOG_AUTHENTICATED		auth_successful;		// auth_successful
++	time_t		start_time;		// start_time
++	time_t		end_time;		// end_time
++	int		duration;		// duration
++	pid_t		main_pid;		// main_pid
++	char		auth_info[SLOG_MEDIUM_STRING_LEN];		// auth_info
++	char		client_version[SLOG_STRING_LEN];		// client_version
++};
++
++Structuredlogctxt *slogctxt;
++extern ServerOptions options;
++
++// Define token constants and default syntax format
++static const char *server_ip_token         = "server_ip";
++static const char *server_port_token       = "server_port";
++static const char *remote_ip_token         = "remote_ip";
++static const char *remote_port_token       = "remote_port";
++static const char *pam_pid_token           = "pam_pid";
++static const char *process_pid_token       = "pid";
++static const char *session_id_token        = "session_id";
++static const char *method_token            = "method";
++static const char *cert_id_token           = "cert_id";
++static const char *cert_serial_token       = "cert_serial";
++static const char *principal_token         = "principal";
++static const char *user_token              = "user";
++static const char *command_token           = "command";
++static const char *session_state_token     = "session_state";
++static const char *auth_successful_token   = "auth_successful";
++static const char *start_time_token        = "start_time";
++static const char *end_time_token          = "end_time";
++static const char *duration_token          = "duration";
++static const char *main_pid_token          = "main_pid";
++static const char *auth_info_token         = "auth_info";
++static const char *client_version          = "client_version";
++
++/* Example log format sshd_config
++ * LogFormatPrefix sshd_auth_msg:
++ * LogFormatKeys server_ip server_port remote_ip remote_port pid session_id /
++   method cert_id cert_serial principal user session_state auth_successful /
++   start_time command # NO LINE BREAKS
++ * LogFormatJson yes # no makes this output an json array vs json object
++ */
++
++// Set a arbitrary large size so we can feed a potentially
++// large json object to the logger
++#define SLOG_BUF_SIZE 8192
++#define SLOG_TRUNCATED_MESSAGE_JSON ", \"incomplete\": \"true\"}"
++#define SLOG_TRUNCATED_MESSAGE_ARRAY ", \"incomplete\"]"
++#define SLOG_TRUNCATED_SIZE 25
++/* size of format for JSON for quotes_colon_space around key and comma_space
++   after value or closure_null */
++#define SLOG_JSON_FORMAT_SIZE 6
++#define SLOG_BUF_CALC_SIZE  SLOG_BUF_SIZE - SLOG_TRUNCATED_SIZE
++
++/* private declarations */
++static void slog_log(void);
++static void slog_cleanup(void);
++static void slog_generate_auth_payload(char *);
++static void slog_escape_value(char *, char *, size_t);
++static void slog_get_safe_from_token(char *, const char *);
++static const char* slog_get_state_text(void);
++
++/* public functions */
++
++void
++slog_init(void)
++{
++	/* initialize only if we have log_format_keys */
++	if (options.num_log_format_keys != 0) {
++		slogctxt = xcalloc(1, sizeof(Structuredlogctxt));
++		if (slogctxt != NULL)
++			slog_cleanup();
++	}
++}
++
++void
++slog_pam_session_opened(void)
++{
++	if (slogctxt != NULL) {
++		slogctxt->session_state = SLOG_SESSION_OPEN;
++		slogctxt->pam_process_pid = getpid();
++	}
++}
++
++void
++slog_set_auth_data(int authenticated, const char *method, const char *user)
++{
++	if (slogctxt != NULL) {
++		slogctxt->auth_successful =
++		    authenticated ? SLOG_AUTHORIZED : SLOG_UNAUTHORIZED;
++		strlcpy(slogctxt->method, method, SLOG_SHORT_STRING_LEN);
++		strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
++	}
++}
++
++void
++slog_set_cert_id(const char *id)
++{
++	if (slogctxt != NULL)
++		strlcpy(slogctxt->cert_id, id, SLOG_STRING_LEN);
++}
++
++
++void
++slog_set_cert_serial(unsigned long long serial)
++{
++	if (slogctxt != NULL)
++		slogctxt->cert_serial = serial;
++}
++
++void
++slog_set_connection(const char *remote_ip, int remote_port,
++    const char *server_ip, int server_port, const char *session)
++{
++	if (slogctxt != NULL) {
++		strlcpy(slogctxt->remote_ip, remote_ip, SLOG_SHORT_STRING_LEN);
++		slogctxt->remote_port = remote_port;
++		strlcpy(slogctxt->server_ip, server_ip, SLOG_SHORT_STRING_LEN);
++		slogctxt->server_port = server_port;
++		strlcpy(slogctxt->session_id, session, SLOG_STRING_LEN);
++		slogctxt->start_time = time(NULL);
++		slogctxt->main_pid = getpid();
++	}
++}
++
++void
++slog_set_client_version(const char *version)
++{
++	if (slogctxt != NULL) {
++		if (strlen(version) < SLOG_STRING_LEN)
++			strlcpy(slogctxt->client_version, version, SLOG_STRING_LEN);
++		else {
++			// version can be up to 256 bytes, truncate to 95 and add ' ...'
++			// which will fit in SLOG_STRING_LEN
++			snprintf(slogctxt->client_version, SLOG_STRING_LEN, "%.95s ...", version);
++		}
++	}
++}
++
++void
++slog_set_command(const char *command)
++{
++	if (slogctxt != NULL) {
++		if (strlen(command) < SLOG_LONG_STRING_LEN)
++			strlcpy(slogctxt->command, command, SLOG_LONG_STRING_LEN);
++		else {
++			// If command is longer than allowed we truncate it to
++			// 1995 (SLOG_LONG_STRING_LEN - 5) characters and add ' ...\0' to
++			// the end of the command.
++			snprintf(slogctxt->command, SLOG_LONG_STRING_LEN, "%.1995s ...", command);
++		}
++	}
++}
++
++void
++slog_set_principal(const char *principal)
++{
++	if (slogctxt != NULL)
++		strlcpy(slogctxt->principal, principal, SLOG_STRING_LEN);
++}
++
++void
++slog_set_user(const char *user)
++{
++	if (slogctxt != NULL)
++		strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
++}
++
++void
++slog_set_auth_info(const char *auth_info)
++{
++	if (slogctxt != NULL)
++		strlcpy(slogctxt->auth_info, auth_info, SLOG_MEDIUM_STRING_LEN);
++}
++
++void
++slog_exit_handler(void)
++{
++	/* to prevent duplicate logging we only log based on the pid set */
++	if (slogctxt != NULL) {
++		if (slogctxt->server_ip[0] == 0)
++			return; // not initialized
++		if (slogctxt->main_pid != getpid())
++			return; // not main process
++		if (slogctxt->session_state == SLOG_SESSION_INIT)
++			slog_log();
++		else {
++			slogctxt->session_state = SLOG_SESSION_CLOSED;
++			slogctxt->end_time = time(NULL);
++			slogctxt->duration = slogctxt->end_time - slogctxt->start_time;
++			slog_log();
++			slog_cleanup();
++		}
++  }
++}
++
++void
++slog_log_session(void)
++{
++	if (slogctxt != NULL) {
++		slogctxt->session_state = SLOG_SESSION_OPEN;
++		slog_log();
++	}
++}
++
++/* private function scope begin */
++
++static void
++slog_log(void)
++{
++	char *buffer = xmalloc(SLOG_BUF_SIZE);
++
++	if (buffer == NULL)
++		return;
++
++	memset(buffer, 0, SLOG_BUF_SIZE);
++
++	if (options.num_log_format_keys > 0
++	    && slogctxt != NULL
++	    && slogctxt->server_ip[0] != 0
++	    && slogctxt->user[0] != 0) {
++		slog_generate_auth_payload(buffer);
++		do_log_slog_payload(buffer);
++	}
++
++	free(buffer);
++}
++
++static void
++slog_cleanup(void)
++{
++	// Reset the log context values
++	if (slogctxt != NULL) {
++		memset(slogctxt, 0, sizeof(Structuredlogctxt));
++		slogctxt->session_state = SLOG_SESSION_INIT;
++		slogctxt->auth_successful = SLOG_UNAUTHORIZED;
++	}
++}
++
++/* We use debug3 since the debug is very noisy */
++static void
++slog_generate_auth_payload(char *buf)
++{
++	if (buf == NULL)
++		return;
++
++	// Create large buffer so don't risk overflow
++	char *safe = xmalloc(SLOG_BUF_SIZE);
++	memset(safe, 0, SLOG_BUF_SIZE);
++
++	if (safe == NULL)
++		return;
++
++	int i;
++	size_t remaining;
++	int json = options.log_format_json;
++	int keys = options.num_log_format_keys;
++	int truncated = 0;
++	char *tmpbuf = buf;
++	char *key;
++
++	debug3("JSON format is %d with %d tokens.", json, keys);
++
++	if (options.log_format_prefix != NULL
++	    && strlen(options.log_format_prefix) < SLOG_BUF_CALC_SIZE-1) {
++		tmpbuf += snprintf(tmpbuf, SLOG_BUF_CALC_SIZE,
++	    "%s ", options.log_format_prefix);
++	}
++	*tmpbuf++ = (json) ? '{' : '[';
++	debug3("current buffer after prefix: %s", buf);
++
++	// Loop through the keys filling out the output string
++	for (i = 0; i < keys; i++) {
++		safe[0] = 0;  // clear safe string
++		key = options.log_format_keys[i];
++		remaining = SLOG_BUF_CALC_SIZE - (tmpbuf - buf);
++
++		if (key == NULL)
++			continue;  // Shouldn't happen but if null go to next key
++
++		slog_get_safe_from_token(safe, key);
++		debug3("token: %s, value: %s", key, safe);
++
++		if (json) {
++			if (*safe == '\0')
++				continue; // No value since we are using key pairs skip
++			if (remaining <= SLOG_JSON_FORMAT_SIZE + strlen(key) + strlen(safe)) {
++				debug("Log would exceed buffer size %u, %zu, %zu at key: %s",
++				    (unsigned int)remaining, strlen(key), strlen(safe), key);
++				truncated = 1;
++				break;
++			}
++			tmpbuf += snprintf(tmpbuf, remaining, "%s\"%s\": %s",
++			    i > 0 ? ", " : "", key, safe);
++		} else {
++			if (*safe == '\0')
++				strlcpy(safe, "\"\"", SLOG_SHORT_STRING_LEN);
++			if (remaining < SLOG_JSON_FORMAT_SIZE + strlen(safe)) {
++				debug("Log would exceed remaining buffer size %d, %zu, at key: %s",
++				    (unsigned int)remaining, strlen(safe), key);
++				truncated = 1;
++				break;
++			}
++			tmpbuf += snprintf(tmpbuf, remaining, "%s%s", i > 0 ? ", " : "", safe);
++		}
++		debug3("current buffer after token: %s", buf);
++		debug3("end of loop key: %s, %d out of %d keys", key, i + 1, keys);
++	}
++
++	// Close the log string. If truncated set truncated message and close string
++	if (truncated == 1)
++		strlcpy(tmpbuf, json ? SLOG_TRUNCATED_MESSAGE_JSON :
++		    SLOG_TRUNCATED_MESSAGE_ARRAY, SLOG_TRUNCATED_SIZE);
++	else {
++		*tmpbuf++ = (json) ? '}' : ']';
++	}
++
++	free(safe);
++}
++
++// buffer size is input string * 2 +1
++static void
++slog_escape_value(char *output, char *input, size_t buffer_size)
++{
++	int i;
++	buffer_size -= 2;
++	if (input != NULL) {
++		int input_size = strlen(input);
++		char *temp = output;
++		*temp++ = '"';
++		buffer_size--;
++		for (i = 0; i < input_size && buffer_size > 0; i++) {
++			switch(input[i]) {
++			// characters escaped are the same as folly::json::escapeString
++			case 27: // <escape> ascii control character
++				if (buffer_size > 6) {
++					*temp++ = '\\';
++					*temp++ = 'u';
++					*temp++ = '0';
++					*temp++ = '0';
++					*temp++ = '1';
++					*temp++ = 'b';
++					buffer_size -= 6;
++				}
++			case '\b':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					*temp++ = 'b';
++					buffer_size -= 2;
++				}
++				break;
++			case '\f':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					*temp++ = 'f';
++					buffer_size -= 2;
++				}
++				break;
++			case '\n':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					*temp++ = 'n';
++					buffer_size -= 2;
++					}
++				break;
++			case '\r':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					*temp++ = 'r';
++					buffer_size -= 2;
++				}
++				break;
++			case '\t':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					*temp++ = 't';
++					buffer_size -= 2;
++				}
++				break;
++			case '\"':
++			case '\\':
++				if (buffer_size > 1) {
++					*temp++ = '\\';
++					buffer_size--;
++			}
++			default:  // Non-escape char
++				*temp++ = input[i];
++				buffer_size--;
++			}
++		}
++		*temp++ = '"';
++		*temp++ = '\0';
++	}
++}
++
++static void
++slog_get_safe_from_token(char *output, const char *token)
++{
++	if (output == NULL || token == NULL || slogctxt == NULL)
++		return;
++
++	if (strcmp(token, server_ip_token) == 0) {
++		if (slogctxt->server_ip[0] != 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
++			    slogctxt->server_ip);
++		}
++		return;
++	}
++
++	if (strcmp(token, server_port_token) == 0) {
++		if (slogctxt->server_port > 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
++			slogctxt->server_port);
++		}
++		return;
++	}
++
++	if (strcmp(token, remote_ip_token) == 0) {
++		if (slogctxt->remote_ip[0] != 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
++			    slogctxt->remote_ip);
++		}
++		return;
++	}
++
++	if (strcmp(token, remote_port_token) == 0) {
++		if (slogctxt->remote_port > 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
++			    slogctxt->remote_port);
++		}
++		return;
++	}
++
++	if (strcmp(token, pam_pid_token) == 0) {
++		if (slogctxt->pam_process_pid > 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"",
++			    (long)slogctxt->pam_process_pid);
++		}
++		return;
++	}
++
++	if (strcmp(token, process_pid_token) == 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", (long)getpid());
++		return;
++	}
++
++	if (strcmp(token, session_id_token) == 0) {
++		if (slogctxt->session_id[0] != 0) {
++				snprintf(output, SLOG_STRING_LEN, "\"%s\"",
++				    slogctxt->session_id);
++		}
++		return;
++	}
++
++	if (strcmp(token, method_token) == 0) {
++		if (slogctxt->method[0] != 0) {
++				snprintf(output, SLOG_STRING_LEN, "\"%s\"",
++				    slogctxt->method);
++		}
++		return;
++	}
++
++	// Arbitrary input
++	if (strcmp(token, cert_id_token) == 0) {
++		if (slogctxt->cert_id[0] != 0 &&
++		    strcmp(slogctxt->method, "publickey") == 0) {
++				slog_escape_value(output, slogctxt->cert_id,
++				    SLOG_STRING_LEN * 2 + 1);
++		}
++		return;
++	}
++
++	if (strcmp(token, cert_serial_token) == 0) {
++		if (slogctxt->cert_serial > 0 &&
++		    strcmp(slogctxt->method, "publickey") == 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%llu\"",
++			    slogctxt->cert_serial);
++		}
++		return;
++	}
++
++	// Arbitrary input
++	if (strcmp(token, principal_token) == 0) {
++		if (slogctxt->principal[0] != 0) {
++			slog_escape_value(output, slogctxt->principal,
++			    SLOG_STRING_LEN * 2 + 1);
++		}
++	return;
++	}
++
++	// Arbitrary input
++	if (strcmp(token, user_token) == 0) {
++		if (slogctxt->user[0] != 0) {
++			slog_escape_value(output, slogctxt->user,
++		    SLOG_STRING_LEN * 2 + 1);
++		}
++		return;
++	}
++
++	// Arbitrary input
++	if (strcmp(token, auth_info_token) == 0) {
++		if (slogctxt->auth_info[0] != 0) {
++			slog_escape_value(output, slogctxt->auth_info,
++			    SLOG_MEDIUM_STRING_LEN * 2 + 1);
++		}
++		return;
++	}
++
++	// Arbitrary input
++	if (strcmp(token, command_token) == 0) {
++		if (slogctxt->command[0] != 0) {
++			slog_escape_value(output, slogctxt->command,
++			    SLOG_LONG_STRING_LEN * 2 + 1);
++		}
++		return;
++	}
++
++	if (strcmp(token, auth_successful_token) == 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
++		    slogctxt->auth_successful == SLOG_AUTHORIZED ? "true" : "false");
++		return;
++	}
++
++	if (strcmp(token, session_state_token) == 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
++		    slog_get_state_text());
++		return;
++	}
++
++	if (strcmp(token, start_time_token) == 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
++		    (int)slogctxt->start_time);
++		return;
++	}
++
++	if (strcmp(token, end_time_token) == 0 && slogctxt->end_time > 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
++		    (int)slogctxt->end_time);
++		return;
++	}
++
++	if (strcmp(token, duration_token) == 0 && slogctxt->end_time > 0) {
++		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", slogctxt->duration);
++		return;
++	}
++
++	if (strcmp(token, main_pid_token) == 0) {
++		if (slogctxt->main_pid > 0) {
++			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"",
++			    (long)slogctxt->main_pid);
++		}
++		return;
++	}
++
++	// Arbitrary input
++	if (strncmp(token, client_version, strlen(client_version)) == 0) {
++		if (slogctxt->client_version[0] != 0) {
++			slog_escape_value(output, slogctxt->client_version,
++			    SLOG_STRING_LEN + 2);
++		}
++		return;
++	}
++}
++
++static const char *
++slog_get_state_text(void)
++{
++	if (slogctxt == NULL)
++		return "";
++
++	switch (slogctxt->session_state) {
++		case SLOG_SESSION_INIT:
++			return "Session failed";
++		case SLOG_SESSION_OPEN:
++			return "Session opened";
++		case SLOG_SESSION_CLOSED:
++			return "Session closed";
++		default:
++			return "Unknown session state";  // Should never happen
++	}
++}
+Index: b/servconf.c
+===================================================================
+--- b.orig/servconf.c
++++ b/servconf.c
+@@ -204,6 +204,9 @@ initialize_server_options(ServerOptions
+ 	options->disable_forwarding = -1;
+ 	options->expose_userauth_info = -1;
+ 	options->rsa_min_size = -1;
++	options->log_format_prefix = NULL;
++	options->num_log_format_keys = 0;
++	options->log_format_json = -1;
+ }
+ 
+ /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
+@@ -473,6 +476,8 @@ fill_default_server_options(ServerOption
+ 		options->sk_provider = xstrdup("internal");
+ 	if (options->rsa_min_size == -1)
+ 		options->rsa_min_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
++	if (options->log_format_json == -1)
++		options->log_format_json = 0;
+ 
+ 	assemble_algorithms(options);
+ 
+@@ -553,6 +558,10 @@ typedef enum {
+ 	sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
+ 	sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
+ 	sRSAMinSize,
++	/* Structured Logging options.  Unless sLogFormatKeys is set,
++	    structured logging is disabled */
++	sLogFormatPrefix, sLogFormatKeys, sLogFormatJson,
++
+ 	sDeprecated, sIgnore, sUnsupported
+ } ServerOpCodes;
+ 
+@@ -730,6 +739,9 @@ static struct {
+ 	{ "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL },
+ 	{ "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL },
+ 	{ "rsaminsize", sRSAMinSize, SSHCFG_ALL },
++	{ "logformatprefix", sLogFormatPrefix, SSHCFG_GLOBAL },
++	{ "logformatkeys", sLogFormatKeys, SSHCFG_GLOBAL },
++	{ "logformatjson", sLogFormatJson, SSHCFG_GLOBAL },
+ 	{ NULL, sBadOption, 0 }
+ };
+ 
+@@ -2367,6 +2379,30 @@ process_server_config_line_depth(ServerO
+ 		}
+ 		break;
+ 
++	case sLogFormatPrefix:
++		arg = strdelim(&str);
++		if (!arg || *arg == '\0') {
++			fatal("%.200s line %d: invalid log format prefix",
++			    filename, linenum);
++		}
++		options->log_format_prefix = xstrdup(arg);
++		break;
++
++	case sLogFormatKeys:
++		while ((arg = strdelim(&str)) && *arg != '\0') {
++			if (options->num_log_format_keys >= MAX_LOGFORMAT_KEYS)
++				fatal("%s line %d: too long format keys.",
++				    filename, linenum);
++			if (!*activep)
++				continue;
++			options->log_format_keys[options->num_log_format_keys++] = xstrdup(arg);
++		}
++		break;
++
++	case sLogFormatJson:
++		intptr = &options->log_format_json;
++		goto parse_flag;
++
+ 	case sIPQoS:
+ 		arg = argv_next(&ac, &av);
+ 		if (!arg || *arg == '\0')
+@@ -3024,6 +3060,7 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
+ 	dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash);
+ 	dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info);
++	dump_cfg_fmtint(sLogFormatJson, o->log_format_json);
+ 
+ 	/* string arguments */
+ 	dump_cfg_string(sPidFile, o->pid_file);
+@@ -3054,6 +3091,7 @@ dump_config(ServerOptions *o)
+ #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN)
+ 	dump_cfg_string(sRDomain, o->routing_domain);
+ #endif
++	dump_cfg_string(sLogFormatPrefix, o->log_format_prefix);
+ 
+ 	/* string arguments requiring a lookup */
+ 	dump_cfg_string(sLogLevel, log_level_name(o->log_level));
+@@ -3076,6 +3114,7 @@ dump_config(ServerOptions *o)
+ 	    o->num_auth_methods, o->auth_methods);
+ 	dump_cfg_strarray_oneline(sLogVerbose,
+ 	    o->num_log_verbose, o->log_verbose);
++	dump_cfg_strarray(sLogFormatKeys, o->num_log_format_keys, o->log_format_keys);
+ 
+ 	/* other arguments */
+ 	for (i = 0; i < o->num_subsystems; i++)
+Index: b/auth2-pubkey.c
+===================================================================
+--- b.orig/auth2-pubkey.c
++++ b/auth2-pubkey.c
+@@ -66,6 +66,7 @@
+ #include "monitor_wrap.h"
+ #include "authfile.h"
+ #include "match.h"
++#include "slog.h"
+ #include "ssherr.h"
+ #include "channels.h" /* XXX for session.h */
+ #include "session.h" /* XXX for child_set_env(); refactor? */
+@@ -389,6 +390,7 @@ check_principals_line(struct ssh *ssh, c
+ 		debug3("%s: matched principal \"%.100s\"",
+ 		    loc, cert->principals[i]);
+ 		found = 1;
++		slog_set_principal(cp);
+ 	}
+ 	if (found && authoptsp != NULL) {
+ 		*authoptsp = opts;
+@@ -714,6 +716,7 @@ check_authkey_line(struct ssh *ssh, stru
+ 	    (unsigned long long)key->cert->serial,
+ 	    sshkey_type(found), fp, loc);
+ 
++	    slog_set_cert_serial(key->cert->serial);
+  success:
+ 	if (finalopts == NULL)
+ 		fatal_f("internal error: missing options");
+@@ -864,6 +867,7 @@ user_cert_trusted_ca(struct ssh *ssh, st
+ 		*authoptsp = final_opts;
+ 		final_opts = NULL;
+ 	}
++	slog_set_cert_id(key->cert->key_id);
+ 	ret = 1;
+  out:
+ 	sshauthopt_free(principals_opts);
+Index: b/regress/test-exec.sh
+===================================================================
+--- b.orig/regress/test-exec.sh
++++ b/regress/test-exec.sh
+@@ -689,7 +689,7 @@ start_sshd ()
+ 
+ 	trace "wait for sshd"
+ 	i=0;
+-	while [ ! -f $PIDFILE -a $i -lt 10 ]; do
++	while [ ! -f $PIDFILE -a $i -lt 3 ]; do
+ 		i=`expr $i + 1`
+ 		sleep $i
+ 	done
+Index: b/session.c
+===================================================================
+--- b.orig/session.c
++++ b/session.c
+@@ -96,6 +96,8 @@
+ #include "monitor_wrap.h"
+ #include "sftp.h"
+ #include "atomicio.h"
++#include "slog.h"
++
+ 
+ #if defined(KRB5) && defined(USE_AFS)
+ #include <kafs.h>
+@@ -753,6 +755,7 @@ do_exec(struct ssh *ssh, Session *s, con
+ 	    ssh_remote_ipaddr(ssh),
+ 	    ssh_remote_port(ssh),
+ 	    s->self);
++	    slog_log_session();
+ 
+ #ifdef SSH_AUDIT_EVENTS
+ 	if (s->command != NULL || s->command_handle != -1)
+@@ -1482,7 +1485,7 @@ do_setusercontext(struct passwd *pw)
+ 			perror("unable to set user context (setuser)");
+ 			exit(1);
+ 		}
+-		/* 
++		/*
+ 		 * FreeBSD's setusercontext() will not apply the user's
+ 		 * own umask setting unless running with the user's UID.
+ 		 */
+@@ -2159,6 +2162,7 @@ session_exec_req(struct ssh *ssh, Sessio
+ 	    (r = sshpkt_get_end(ssh)) != 0)
+ 		sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
+ 
++	slog_set_command(command);
+ 	success = do_exec(ssh, s, command) == 0;
+ 	free(command);
+ 	return success;
+@@ -2869,4 +2873,3 @@ session_get_remote_name_or_ip(struct ssh
+ 		remote = ssh_remote_ipaddr(ssh);
+ 	return remote;
+ }
+-
+Index: b/log.h
+===================================================================
+--- b.orig/log.h
++++ b/log.h
+@@ -133,4 +133,6 @@ void	 sshlogdirect(LogLevel, int, const
+ #define logdie_fr(r, ...)	sshlogdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__)
+ #define sigdie_fr(r, ...)	sshsigdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__)
+ 
++void     do_log_slog_payload(const char *);
++
+ #endif
+Index: b/log.c
+===================================================================
+--- b.orig/log.c
++++ b/log.c
+@@ -529,3 +529,39 @@ sshlogdirect(LogLevel level, int forced,
+ 	do_log(level, forced, NULL, fmt, args);
+ 	va_end(args);
+ }
++
++/* Custom function to log to syslog, to handle the session logging code
++ */
++void
++do_log_slog_payload(const char *payload)
++{
++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
++	struct syslog_data sdata = SYSLOG_DATA_INIT;
++#endif
++	int pri = LOG_INFO;
++	int saved_errno = errno;
++	LogLevel level = SYSLOG_LEVEL_INFO;
++	log_handler_fn *tmp_handler;
++
++	if (log_handler != NULL) {
++		/* Avoid recursion */
++		tmp_handler = log_handler;
++		log_handler = NULL;
++		tmp_handler(level, 0, payload, log_handler_ctx);
++		log_handler = tmp_handler;
++	} else if (log_on_stderr) {
++		(void)write(log_stderr_fd, payload, strlen(payload));
++		(void)write(log_stderr_fd, "\r\n", 2);
++	} else {
++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
++		openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata);
++		syslog_r(pri, &sdata, "%s", payload);
++		closelog_r(&sdata);
++#else
++		openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility);
++		syslog(pri, "%s", payload);
++		closelog();
++#endif
++	}
++	errno = saved_errno;
++}
+Index: b/slog.h
+===================================================================
+--- /dev/null
++++ b/slog.h
+@@ -0,0 +1,41 @@
++/*
++ * Copyright 2004-present Facebook. All Rights Reserved.
++ */
++#ifndef USE_SLOG
++#define USE_SLOG
++
++#define SLOG_STRING_LEN        100
++#define SLOG_MEDIUM_STRING_LEN 1000
++#define SLOG_SHORT_STRING_LEN  50
++#define SLOG_LONG_STRING_LEN   2000
++
++typedef enum {
++	SLOG_SESSION_INIT,
++	SLOG_SESSION_OPEN,
++	SLOG_SESSION_CLOSED,
++} SLOG_SESSION_STATE;
++
++typedef enum {
++	SLOG_UNAUTHORIZED = 0,
++	SLOG_AUTHORIZED = 1
++} SLOG_AUTHENTICATED;
++
++void	slog_init(void);
++
++// setters
++void	slog_pam_session_opened(void);
++void	slog_set_auth_data(int , const char *, const char *);
++void	slog_set_cert_id(const char *);
++void	slog_set_cert_serial(unsigned long long );
++void	slog_set_connection(const char *, int, const char *, int, const char *);
++void	slog_set_command(const char *);
++void	slog_set_principal(const char *);
++void	slog_set_user(const char *);
++void	slog_set_auth_info(const char *);
++void	slog_set_client_version(const char *);
++
++// loggers
++void	slog_exit_handler(void);
++void	slog_log_session(void);
++
++#endif
+Index: b/sshd.c
+===================================================================
+--- b.orig/sshd.c
++++ b/sshd.c
+@@ -132,6 +132,8 @@
+ #include "sk-api.h"
+ #include "srclimit.h"
+ #include "dh.h"
++#include "slog.h"
++#include <time.h>
+ 
+ /* Re-exec fds */
+ #define REEXEC_DEVCRYPTO_RESERVED_FD	(STDERR_FILENO + 1)
+@@ -2149,6 +2151,7 @@ main(int ac, char **av)
+ 	}
+ 	/* Reinitialize the log (because of the fork above). */
+ 	log_init(__progname, options.log_level, options.log_facility, log_stderr);
++	slog_init();
+ 
+ 	if (FIPS_mode()) {
+ 		debug("FIPS mode initialized");
+@@ -2315,8 +2318,15 @@ main(int ac, char **av)
+ 	    rdomain == NULL ? "" : " rdomain \"",
+ 	    rdomain == NULL ? "" : rdomain,
+ 	    rdomain == NULL ? "" : "\"");
+-	free(laddr);
+ 
++
++	slog_set_connection(remote_ip,
++	    remote_port,
++	    laddr,
++	    ssh_local_port(ssh),
++	    get_log_session_id());
++
++	free(laddr);
+ 	/*
+ 	 * We don't want to listen forever unless the other side
+ 	 * successfully authenticates itself.  So we set up an alarm which is
+@@ -2638,6 +2648,7 @@ cleanup_exit(int i)
+ 	if (in_cleanup)
+ 		_exit(i);
+ 	in_cleanup = 1;
++	slog_exit_handler();
+ 	if (the_active_state != NULL && the_authctxt != NULL) {
+ 		do_cleanup(the_active_state, the_authctxt);
+ 		if (use_privsep && privsep_is_preauth &&
+Index: b/sshd_config
+===================================================================
+--- b.orig/sshd_config
++++ b/sshd_config
+@@ -33,6 +33,15 @@ Include /etc/ssh/sshd_config.d/*.conf
+ # Logging
+ #SyslogFacility AUTH
+ #LogLevel INFO
++# Structured logging
++# The default is no structured logging, to enable structured logging at least
++# one LogFormatKeys must be set.  LogFormatJson defaults to no (array) to keep
++# the log line size smaller, if keys are desired set LogFormatJson to yes
++LogFormatPrefix sshd_auth_msg:
++LogFormatKeys server_ip server_port remote_ip remote_port pid session_id method
++LogFormatKeys cert_id cert_serial principal user session_state auth_successful
++LogFormatKeys start_time command
++LogFormatJson yes
+ 
+ # Authentication:
+ 
+Index: b/auth-pam.c
+===================================================================
+--- b.orig/auth-pam.c
++++ b/auth-pam.c
+@@ -94,6 +94,7 @@ extern char *__progname;
+ #include "auth-pam.h"
+ #include "canohost.h"
+ #include "log.h"
++#include "slog.h"
+ #include "msg.h"
+ #include "packet.h"
+ #include "misc.h"
+@@ -1223,9 +1224,12 @@ do_pam_session(struct ssh *ssh)
+ 	if (sshpam_err != PAM_SUCCESS)
+ 		fatal("PAM: failed to set PAM_CONV: %s",
+ 		    pam_strerror(sshpam_handle, sshpam_err));
++
+ 	sshpam_err = pam_open_session(sshpam_handle, 0);
+-	if (sshpam_err == PAM_SUCCESS)
++	if (sshpam_err == PAM_SUCCESS) {
++		slog_pam_session_opened();
+ 		sshpam_session_open = 1;
++	}
+ 	else {
+ 		sshpam_session_open = 0;
+ 		auth_restrict_session(ssh);
+Index: b/servconf.h
+===================================================================
+--- b.orig/servconf.h
++++ b/servconf.h
+@@ -22,6 +22,8 @@
+ 
+ #define MAX_SUBSYSTEMS		256	/* Max # subsystems. */
+ 
++#define MAX_LOGFORMAT_KEYS      256     /* Max # LogFormatKeys */
++
+ /* permit_root_login */
+ #define	PERMIT_NOT_SET		-1
+ #define	PERMIT_NO		0
+@@ -239,6 +241,12 @@ typedef struct {
+ 	u_int64_t timing_secret;
+ 	char   *sk_provider;
+ 	int	rsa_min_size;	/* minimum size of RSA keys */
++
++	char   *log_format_prefix;
++	u_int  num_log_format_keys;
++	char   *log_format_keys[MAX_LOGFORMAT_KEYS];
++	int log_format_json;	/* 1 to return "token": "token_val" in log format */
++
+ }       ServerOptions;
+ 
+ /* Information about the incoming connection as used by Match */
+Index: b/regress/slog.sh
+===================================================================
+--- /dev/null
++++ b/regress/slog.sh
+@@ -0,0 +1,59 @@
++tid='structured log'
++
++port="4242"
++log_prefix="sshd_auth_msg:"
++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version"
++do_log_json="yes"
++test_config="$OBJ/sshd2_config"
++old_config="$OBJ/sshd_config"
++PIDFILE=$OBJ/pidfile
++
++cat << EOF > $test_config
++	#*:
++	StrictModes             no
++	Port                    $port
++	AddressFamily           inet
++	ListenAddress           127.0.0.1
++	#ListenAddress          ::1
++	PidFile                 $PIDFILE
++	AuthorizedKeysFile      $OBJ/authorized_keys_%u
++	LogLevel                ERROR
++	AcceptEnv               _XXX_TEST_*
++	AcceptEnv               _XXX_TEST
++	HostKey $OBJ/host.ssh-ed25519
++	LogFormatPrefix $log_prefix
++	LogFormatJson $do_log_json
++	LogFormatKeys $log_keys
++EOF
++
++
++cp $test_config $old_config
++start_sshd
++
++${SSH} -F $OBJ/ssh_config somehost true
++if [ $? -ne 0 ]; then
++	fail "ssh connect with failed"
++fi
++
++test_log_counts() {
++	cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE")
++	if [ $cnt -ne 2 ]; then
++		fail "expected 2 structured logging lines, got $cnt"
++	fi
++}
++
++test_json_valid() {
++	which python &>/dev/null || echo 'python not found in path, skipping tests'
++
++	loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix")
++	first=$(echo "$loglines" | head -n1)
++	last=$(echo "$loglines" | tail -n1)
++
++	echo ${first:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \
++	    || fail "invalid json structure $first"
++	echo ${last:$(expr length $log_prefix)} | python -m json.tool &>/dev/null  \
++	    || fail "invalid json structure $last"
++}
++
++test_log_counts
++test_json_valid
+Index: b/Makefile.in
+===================================================================
+--- b.orig/Makefile.in
++++ b/Makefile.in
+@@ -129,7 +129,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passw
+ 	monitor.o monitor_wrap.o auth-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 \
+-	srclimit.o sftp-server.o sftp-common.o \
++	srclimit.o sftp-server.o sftp-common.o sftp-realpath.o slog.o \
+ 	sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
+ 	sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \
+ 	sandbox-solaris.o uidswap.o $(SKOBJS)
+@@ -313,7 +313,7 @@ distclean:	regressclean
+ 	rm -f *.o *.a $(TARGETS) logintest config.cache config.log
+ 	rm -f *.out core opensshd.init openssh.xml
+ 	rm -f Makefile buildpkg.sh config.h config.status
+-	rm -f survey.sh openbsd-compat/regress/Makefile *~ 
++	rm -f survey.sh openbsd-compat/regress/Makefile *~
+ 	rm -rf autom4te.cache
+ 	rm -f regress/check-perm
+ 	rm -f regress/mkdtemp
+Index: b/auth.c
+===================================================================
+--- b.orig/auth.c
++++ b/auth.c
+@@ -76,6 +76,7 @@
+ #include "ssherr.h"
+ #include "compat.h"
+ #include "channels.h"
++#include "slog.h"
+ 
+ /* import */
+ extern ServerOptions options;
+@@ -351,6 +352,7 @@ auth_log(struct ssh *ssh, int authentica
+ 	    extra != NULL ? extra : "");
+ 
+ 	free(extra);
++	slog_set_auth_data(authenticated, method, authctxt->user);
+ 
+ #if defined(CUSTOM_FAILED_LOGIN) || defined(SSH_AUDIT_EVENTS)
+ 	if (authenticated == 0 && !(authctxt->postponed || partial)) {
+@@ -564,6 +566,7 @@ auth_openprincipals(const char *file, st
+ struct passwd *
+ getpwnamallow(struct ssh *ssh, const char *user)
+ {
++	slog_set_user(user);
+ #ifdef HAVE_LOGIN_CAP
+ 	extern login_cap_t *lc;
+ #ifdef BSD_AUTH
diff --git a/fbpatches/series b/fbpatches/series
new file mode 100644
index 0000000..8fe0e6e
--- /dev/null
+++ b/fbpatches/series
@@ -0,0 +1,10 @@
+fb87_log_session_id.patch
+fb87_slog.patch
+fb87_log_port_forwards.patch
+fb87_070_logging_reverse_port_forward.patch
+fb87_810_increase_ssh_cert_max_principals.patch
+fb87_090_logging_shell_cmd_pty.patch
+fb87_080_logging_certificates.patch
+fb87_log_accept_env.patch
+fb87_pass_principals_to_child.patch
+fb87_log_auth_info.patch
diff --git a/openssh.spec b/openssh.spec
index 8a66c22..2d1b915 100644
--- a/openssh.spec
+++ b/openssh.spec
@@ -1,3 +1,6 @@
+%global facebook 1
+%global facebook_dev 0
+
 # Do we want SELinux & Audit
 %if 0%{?!noselinux:1}
 %global WITH_SELINUX 1
@@ -53,13 +56,18 @@
 %global openssh_ver 8.7p1
 %global openssh_rel 19
 %global hyperscale_rel 2
+%global facebook_rel fb1
 %global pam_ssh_agent_ver 0.10.4
 %global pam_ssh_agent_rel 5
 
 Summary: An open source implementation of SSH protocol version 2
 Name: openssh
 Version: %{openssh_ver}
+%if %{facebook}
+Release: %{openssh_rel}.%{facebook_rel}%{?dist}
+%else
 Release: %{openssh_rel}.%{hyperscale_rel}%{?dist}
+%endif
 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
@@ -257,6 +265,19 @@ Patch1006: openssh-8.7p1-negotiate-supported-algs.patch
 # c9s specific logic factored out of openssh-7.7p1-fips.patch
 Patch2000: openssh-7.7p1-fips-warning.patch
 
+%if %{facebook} && !%{facebook_dev}
+Patch2010: fbpatches/fb87_log_session_id.patch
+Patch2011: fbpatches/fb87_slog.patch
+Patch2012: fbpatches/fb87_log_port_forwards.patch
+Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch
+Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch
+Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch
+Patch2016: fbpatches/fb87_080_logging_certificates.patch
+Patch2017: fbpatches/fb87_log_accept_env.patch
+Patch2018: fbpatches/fb87_pass_principals_to_child.patch
+Patch2019: fbpatches/fb87_log_auth_info.patch
+%endif
+
 License: BSD
 Requires: /sbin/nologin
 
@@ -462,6 +483,24 @@ popd
 
 %patch100 -p1 -b .coverity
 
+%if %{facebook} && !%{facebook_dev}
+%patch2010 -p1 -b log_session_id
+%patch2011 -p1 -b slog
+%patch2012 -p1 -b log_port_forwards
+%patch2013 -p1 -b logging_reverse_port_forward
+%patch2014 -p1 -b increase_ssh_cert_max_principals
+%patch2015 -p1 -b logging_shell_cmd_pty
+%patch2016 -p1 -b logging_certificates
+%patch2017 -p1 -b log_accept_env
+%patch2018 -p1 -b pass_principals_to_child
+%patch2019 -p1 -b log_auth_info
+%endif
+
+%if %{facebook} && %{facebook_dev}
+ln -sf ../../fbpatches patches
+quilt push -a
+%endif
+
 autoreconf
 pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
 autoreconf