From d1c05fe05dac6eea8ea42e704e0602307962abd5 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 18:33:43 +0000 Subject: [PATCH 1/13] Initial checkin of integrated Meta patches, and specfile changes --- 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 +@@ -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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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: // 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 +@@ -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 + + /* 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 From 5992aa43fd18065e9b30546810474fd0f093a8eb Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 19:52:11 +0000 Subject: [PATCH 2/13] Remove define of %{facebook} --- diff --git a/openssh.spec b/openssh.spec index 2d1b915..3160fb7 100644 --- a/openssh.spec +++ b/openssh.spec @@ -1,4 +1,3 @@ -%global facebook 1 %global facebook_dev 0 # Do we want SELinux & Audit From 8e1394dd94db64b52518d834415ca585592c6c4d Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 20:37:41 +0000 Subject: [PATCH 3/13] Make recommended changes from review. --- diff --git a/openssh.spec b/openssh.spec index 3160fb7..806a78c 100644 --- a/openssh.spec +++ b/openssh.spec @@ -1,5 +1,3 @@ -%global facebook_dev 0 - # Do we want SELinux & Audit %if 0%{?!noselinux:1} %global WITH_SELINUX 1 @@ -7,6 +5,10 @@ %global WITH_SELINUX 0 %endif +# Useful development mode for porting patches from +# a different release +%global use_quilt 0 + %global _hardened_build 1 # OpenSSH privilege separation requires a user & group ID @@ -54,7 +56,7 @@ # Do not forget to bump pam_ssh_agent_auth release if you rewind the main package release to 1 %global openssh_ver 8.7p1 %global openssh_rel 19 -%global hyperscale_rel 2 +%global hyperscale_rel 3 %global facebook_rel fb1 %global pam_ssh_agent_ver 0.10.4 %global pam_ssh_agent_rel 5 @@ -264,16 +266,32 @@ 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} +%if %{facebook} && !%{use_quilt} +# Add a unique log session identifier to output messages for +# each sshd process and its children. Patch2010: fbpatches/fb87_log_session_id.patch +# Add structured logging Patch2011: fbpatches/fb87_slog.patch +# Add a log entry when a session is started over a local forward port. Patch2012: fbpatches/fb87_log_port_forwards.patch +# Add a log line when a session is started over a reverse port forward. Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch +# Increase ssh cert max principals from 256 to 1024. Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch +# Output a line in the logs showing the command run, or shell request +# and the user. Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch +# Output a line in the logs showing which principal was matched when +# certificate authentication was used. Patch2016: fbpatches/fb87_080_logging_certificates.patch +# Add verbose logging for setting env variables. Patch2017: fbpatches/fb87_log_accept_env.patch +# Set an environment variable SSH_CERT_PRINCIPALS in the child process +# to be the full principal list of a user's SSH certificate when forced +# ommand is present and the user is authenticated by the certificate. Patch2018: fbpatches/fb87_pass_principals_to_child.patch +# Log extra authenticaton informatino to the auth_info structured +# logging field, and add tests for pubkey and cert auth. Patch2019: fbpatches/fb87_log_auth_info.patch %endif @@ -482,7 +500,7 @@ popd %patch100 -p1 -b .coverity -%if %{facebook} && !%{facebook_dev} +%if %{facebook} && !%{use_quilt} %patch2010 -p1 -b log_session_id %patch2011 -p1 -b slog %patch2012 -p1 -b log_port_forwards @@ -495,7 +513,7 @@ popd %patch2019 -p1 -b log_auth_info %endif -%if %{facebook} && %{facebook_dev} +%if %{facebook} && %{use_quilt} ln -sf ../../fbpatches patches quilt push -a %endif @@ -777,6 +795,9 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog +* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.2 +- Set up local developer strategy using quilt and incorporate Meta patches + * Wed Jul 20 2022 Davide Cavalca - 8.7p1-19.2 + 0.10.4-5.2 - Refactor and reinstate FIPS patch for el8 From dad6a13811fb24ada261f2dd3357a85d914f2b6b Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 20:46:27 +0000 Subject: [PATCH 4/13] Remove setting of fbxx as facebook_rel. --- diff --git a/openssh.spec b/openssh.spec index 806a78c..6e783fb 100644 --- a/openssh.spec +++ b/openssh.spec @@ -57,18 +57,13 @@ %global openssh_ver 8.7p1 %global openssh_rel 19 %global hyperscale_rel 3 -%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 From c6dbe396fca3e60609a591536b3090f0acaaa88d Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 20:53:07 +0000 Subject: [PATCH 5/13] Fix missing dots on patch -b arguments. --- diff --git a/openssh.spec b/openssh.spec index 6e783fb..1d42e2b 100644 --- a/openssh.spec +++ b/openssh.spec @@ -496,16 +496,16 @@ popd %patch100 -p1 -b .coverity %if %{facebook} && !%{use_quilt} -%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 +%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} && %{use_quilt} From ef9f50299666a22bb461a1901431e46b4b18b6f9 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 22:12:12 +0000 Subject: [PATCH 6/13] Use quilt always to install facebook patches, to fix build breakage. --- diff --git a/fbpatches/series b/fbpatches/series index 8fe0e6e..177f92c 100644 --- a/fbpatches/series +++ b/fbpatches/series @@ -1,10 +1,26 @@ +# Add a unique log session identifier to output messages for +# each sshd process and its children. fb87_log_session_id.patch +# Add structured logging fb87_slog.patch +# Add a log entry when a session is started over a local forward port. fb87_log_port_forwards.patch +# Add a log line when a session is started over a reverse port forward. fb87_070_logging_reverse_port_forward.patch +# Increase ssh cert max principals from 256 to 1024. fb87_810_increase_ssh_cert_max_principals.patch +# Output a line in the logs showing the command run, or shell request +# and the user fb87_090_logging_shell_cmd_pty.patch +# Output a line in the logs showing which principal was matched when +# certificate authentication was used. fb87_080_logging_certificates.patch +# Add verbose logging for setting env variables. fb87_log_accept_env.patch +# Set an environment variable SSH_CERT_PRINCIPALS in the child process +# to be the full principal list of a user's SSH certificate when forced +# command is present and the user is authenticated by the certificate. fb87_pass_principals_to_child.patch +# Log extra authentication information to the auth_info structured +# logging field, and add tests for pubkey and cert auth. fb87_log_auth_info.patch diff --git a/openssh.spec b/openssh.spec index 1d42e2b..858f455 100644 --- a/openssh.spec +++ b/openssh.spec @@ -5,10 +5,6 @@ %global WITH_SELINUX 0 %endif -# Useful development mode for porting patches from -# a different release -%global use_quilt 0 - %global _hardened_build 1 # OpenSSH privilege separation requires a user & group ID @@ -261,35 +257,6 @@ 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} && !%{use_quilt} -# Add a unique log session identifier to output messages for -# each sshd process and its children. -Patch2010: fbpatches/fb87_log_session_id.patch -# Add structured logging -Patch2011: fbpatches/fb87_slog.patch -# Add a log entry when a session is started over a local forward port. -Patch2012: fbpatches/fb87_log_port_forwards.patch -# Add a log line when a session is started over a reverse port forward. -Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch -# Increase ssh cert max principals from 256 to 1024. -Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch -# Output a line in the logs showing the command run, or shell request -# and the user. -Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch -# Output a line in the logs showing which principal was matched when -# certificate authentication was used. -Patch2016: fbpatches/fb87_080_logging_certificates.patch -# Add verbose logging for setting env variables. -Patch2017: fbpatches/fb87_log_accept_env.patch -# Set an environment variable SSH_CERT_PRINCIPALS in the child process -# to be the full principal list of a user's SSH certificate when forced -# ommand is present and the user is authenticated by the certificate. -Patch2018: fbpatches/fb87_pass_principals_to_child.patch -# Log extra authenticaton informatino to the auth_info structured -# logging field, and add tests for pubkey and cert auth. -Patch2019: fbpatches/fb87_log_auth_info.patch -%endif - License: BSD Requires: /sbin/nologin @@ -335,6 +302,11 @@ BuildRequires: xauth # for tarball signature verification BuildRequires: gnupg2 +# Facebook patches are applied using quilt +%if 0%{?facebook} +BuildRequires: quilt +%endif + %package clients Summary: An open source SSH client applications Requires: openssh = %{version}-%{release} @@ -495,21 +467,9 @@ popd %patch100 -p1 -b .coverity -%if %{facebook} && !%{use_quilt} -%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} && %{use_quilt} -ln -sf ../../fbpatches patches +# Apply Facebook patches +%if 0%{?facebook} +ln -sf %{_sourcedir}/fbpatches patches quilt push -a %endif @@ -790,7 +750,7 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog -* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.2 +* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.3 - Set up local developer strategy using quilt and incorporate Meta patches * Wed Jul 20 2022 Davide Cavalca - 8.7p1-19.2 + 0.10.4-5.2 From 9c89b9f1a691e53ab850ef904df7041dcf1f01bf Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 22:41:47 +0000 Subject: [PATCH 7/13] Revert "Use quilt always to install facebook patches, to fix build breakage." This reverts commit ef9f50299666a22bb461a1901431e46b4b18b6f9. --- diff --git a/fbpatches/series b/fbpatches/series index 177f92c..8fe0e6e 100644 --- a/fbpatches/series +++ b/fbpatches/series @@ -1,26 +1,10 @@ -# Add a unique log session identifier to output messages for -# each sshd process and its children. fb87_log_session_id.patch -# Add structured logging fb87_slog.patch -# Add a log entry when a session is started over a local forward port. fb87_log_port_forwards.patch -# Add a log line when a session is started over a reverse port forward. fb87_070_logging_reverse_port_forward.patch -# Increase ssh cert max principals from 256 to 1024. fb87_810_increase_ssh_cert_max_principals.patch -# Output a line in the logs showing the command run, or shell request -# and the user fb87_090_logging_shell_cmd_pty.patch -# Output a line in the logs showing which principal was matched when -# certificate authentication was used. fb87_080_logging_certificates.patch -# Add verbose logging for setting env variables. fb87_log_accept_env.patch -# Set an environment variable SSH_CERT_PRINCIPALS in the child process -# to be the full principal list of a user's SSH certificate when forced -# command is present and the user is authenticated by the certificate. fb87_pass_principals_to_child.patch -# Log extra authentication information to the auth_info structured -# logging field, and add tests for pubkey and cert auth. fb87_log_auth_info.patch diff --git a/openssh.spec b/openssh.spec index 858f455..1d42e2b 100644 --- a/openssh.spec +++ b/openssh.spec @@ -5,6 +5,10 @@ %global WITH_SELINUX 0 %endif +# Useful development mode for porting patches from +# a different release +%global use_quilt 0 + %global _hardened_build 1 # OpenSSH privilege separation requires a user & group ID @@ -257,6 +261,35 @@ 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} && !%{use_quilt} +# Add a unique log session identifier to output messages for +# each sshd process and its children. +Patch2010: fbpatches/fb87_log_session_id.patch +# Add structured logging +Patch2011: fbpatches/fb87_slog.patch +# Add a log entry when a session is started over a local forward port. +Patch2012: fbpatches/fb87_log_port_forwards.patch +# Add a log line when a session is started over a reverse port forward. +Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch +# Increase ssh cert max principals from 256 to 1024. +Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch +# Output a line in the logs showing the command run, or shell request +# and the user. +Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch +# Output a line in the logs showing which principal was matched when +# certificate authentication was used. +Patch2016: fbpatches/fb87_080_logging_certificates.patch +# Add verbose logging for setting env variables. +Patch2017: fbpatches/fb87_log_accept_env.patch +# Set an environment variable SSH_CERT_PRINCIPALS in the child process +# to be the full principal list of a user's SSH certificate when forced +# ommand is present and the user is authenticated by the certificate. +Patch2018: fbpatches/fb87_pass_principals_to_child.patch +# Log extra authenticaton informatino to the auth_info structured +# logging field, and add tests for pubkey and cert auth. +Patch2019: fbpatches/fb87_log_auth_info.patch +%endif + License: BSD Requires: /sbin/nologin @@ -302,11 +335,6 @@ BuildRequires: xauth # for tarball signature verification BuildRequires: gnupg2 -# Facebook patches are applied using quilt -%if 0%{?facebook} -BuildRequires: quilt -%endif - %package clients Summary: An open source SSH client applications Requires: openssh = %{version}-%{release} @@ -467,9 +495,21 @@ popd %patch100 -p1 -b .coverity -# Apply Facebook patches -%if 0%{?facebook} -ln -sf %{_sourcedir}/fbpatches patches +%if %{facebook} && !%{use_quilt} +%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} && %{use_quilt} +ln -sf ../../fbpatches patches quilt push -a %endif @@ -750,7 +790,7 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog -* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.3 +* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.2 - Set up local developer strategy using quilt and incorporate Meta patches * Wed Jul 20 2022 Davide Cavalca - 8.7p1-19.2 + 0.10.4-5.2 From dffbc2d709a0e1853e93557c8681382c53e2d53f Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 22:58:19 +0000 Subject: [PATCH 8/13] Move fbpatches files into base directory, so they can be found and packaged. --- diff --git a/fb87_070_logging_reverse_port_forward.patch b/fb87_070_logging_reverse_port_forward.patch new file mode 100644 index 0000000..339fd35 --- /dev/null +++ b/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/fb87_080_logging_certificates.patch b/fb87_080_logging_certificates.patch new file mode 100644 index 0000000..46cc276 --- /dev/null +++ b/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/fb87_090_logging_shell_cmd_pty.patch b/fb87_090_logging_shell_cmd_pty.patch new file mode 100644 index 0000000..6017884 --- /dev/null +++ b/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/fb87_810_increase_ssh_cert_max_principals.patch b/fb87_810_increase_ssh_cert_max_principals.patch new file mode 100644 index 0000000..2abe0e6 --- /dev/null +++ b/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/fb87_log_accept_env.patch b/fb87_log_accept_env.patch new file mode 100644 index 0000000..24ba1fc --- /dev/null +++ b/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/fb87_log_auth_info.patch b/fb87_log_auth_info.patch new file mode 100644 index 0000000..45f320a --- /dev/null +++ b/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/fb87_log_port_forwards.patch b/fb87_log_port_forwards.patch new file mode 100644 index 0000000..8a7b030 --- /dev/null +++ b/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/fb87_log_session_id.patch b/fb87_log_session_id.patch new file mode 100644 index 0000000..ac9d6a3 --- /dev/null +++ b/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/fb87_pass_principals_to_child.patch b/fb87_pass_principals_to_child.patch new file mode 100644 index 0000000..27bb7ca --- /dev/null +++ b/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 +@@ -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/fb87_slog.patch b/fb87_slog.patch new file mode 100644 index 0000000..427cdda --- /dev/null +++ b/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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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: // 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 +@@ -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 + + /* 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/fb87_070_logging_reverse_port_forward.patch b/fbpatches/fb87_070_logging_reverse_port_forward.patch deleted file mode 100644 index 339fd35..0000000 --- a/fbpatches/fb87_070_logging_reverse_port_forward.patch +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 46cc276..0000000 --- a/fbpatches/fb87_080_logging_certificates.patch +++ /dev/null @@ -1,182 +0,0 @@ -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 deleted file mode 100644 index 6017884..0000000 --- a/fbpatches/fb87_090_logging_shell_cmd_pty.patch +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index 2abe0e6..0000000 --- a/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 24ba1fc..0000000 --- a/fbpatches/fb87_log_accept_env.patch +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 45f320a..0000000 --- a/fbpatches/fb87_log_auth_info.patch +++ /dev/null @@ -1,216 +0,0 @@ -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 deleted file mode 100644 index 8a7b030..0000000 --- a/fbpatches/fb87_log_port_forwards.patch +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index ac9d6a3..0000000 --- a/fbpatches/fb87_log_session_id.patch +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index 27bb7ca..0000000 --- a/fbpatches/fb87_pass_principals_to_child.patch +++ /dev/null @@ -1,224 +0,0 @@ -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 -@@ -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 deleted file mode 100644 index 427cdda..0000000 --- a/fbpatches/fb87_slog.patch +++ /dev/null @@ -1,1148 +0,0 @@ -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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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: // 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 -@@ -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 - - /* 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 deleted file mode 100644 index 8fe0e6e..0000000 --- a/fbpatches/series +++ /dev/null @@ -1,10 +0,0 @@ -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 1d42e2b..2f166f0 100644 --- a/openssh.spec +++ b/openssh.spec @@ -7,7 +7,7 @@ # Useful development mode for porting patches from # a different release -%global use_quilt 0 +%global use_quilt 1 %global _hardened_build 1 @@ -81,6 +81,9 @@ Source12: sshd-keygen@.service Source13: sshd-keygen Source15: sshd-keygen.target Source16: ssh-agent.service +%if 0%{facebook} && 0%{use_quilt} +Source17: series +%endif #https://bugzilla.mindrot.org/show_bug.cgi?id=2581 Patch100: openssh-6.7p1-coverity.patch @@ -261,33 +264,33 @@ 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} && !%{use_quilt} +%if 0%{facebook} && !0%{use_quilt} # Add a unique log session identifier to output messages for # each sshd process and its children. -Patch2010: fbpatches/fb87_log_session_id.patch +Patch2010: fb87_log_session_id.patch # Add structured logging -Patch2011: fbpatches/fb87_slog.patch +Patch2011: fb87_slog.patch # Add a log entry when a session is started over a local forward port. -Patch2012: fbpatches/fb87_log_port_forwards.patch +Patch2012: fb87_log_port_forwards.patch # Add a log line when a session is started over a reverse port forward. -Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch +Patch2013: fb87_070_logging_reverse_port_forward.patch # Increase ssh cert max principals from 256 to 1024. -Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch +Patch2014: fb87_810_increase_ssh_cert_max_principals.patch # Output a line in the logs showing the command run, or shell request # and the user. -Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch +Patch2015: fb87_090_logging_shell_cmd_pty.patch # Output a line in the logs showing which principal was matched when # certificate authentication was used. -Patch2016: fbpatches/fb87_080_logging_certificates.patch +Patch2016: fb87_080_logging_certificates.patch # Add verbose logging for setting env variables. -Patch2017: fbpatches/fb87_log_accept_env.patch +Patch2017: fb87_log_accept_env.patch # Set an environment variable SSH_CERT_PRINCIPALS in the child process # to be the full principal list of a user's SSH certificate when forced # ommand is present and the user is authenticated by the certificate. -Patch2018: fbpatches/fb87_pass_principals_to_child.patch +Patch2018: fb87_pass_principals_to_child.patch # Log extra authenticaton informatino to the auth_info structured # logging field, and add tests for pubkey and cert auth. -Patch2019: fbpatches/fb87_log_auth_info.patch +Patch2019: fb87_log_auth_info.patch %endif License: BSD @@ -495,7 +498,7 @@ popd %patch100 -p1 -b .coverity -%if %{facebook} && !%{use_quilt} +%if 0%{facebook} && !0%{use_quilt} %patch2010 -p1 -b .log_session_id %patch2011 -p1 -b .slog %patch2012 -p1 -b .log_port_forwards @@ -508,8 +511,8 @@ popd %patch2019 -p1 -b .log_auth_info %endif -%if %{facebook} && %{use_quilt} -ln -sf ../../fbpatches patches +%if 0%{facebook} && 0%{use_quilt} +ln -sf %{_sourcedir} patches quilt push -a %endif diff --git a/series b/series new file mode 100644 index 0000000..8fe0e6e --- /dev/null +++ b/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 From 78078c64a62d866d7e11465bbf1f8e3e0fb65152 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 23:00:08 +0000 Subject: [PATCH 9/13] Missed version change in changelog. --- diff --git a/openssh.spec b/openssh.spec index 2f166f0..290400d 100644 --- a/openssh.spec +++ b/openssh.spec @@ -793,7 +793,7 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog -* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.2 +* Wed Aug 24 2022 Kent Peacock 8.7p1-19.3 + 0.10.4-5.3 - Set up local developer strategy using quilt and incorporate Meta patches * Wed Jul 20 2022 Davide Cavalca - 8.7p1-19.2 + 0.10.4-5.2 From 38c1af652c8a34fb6bd664b7bd21ae658a4889d5 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 23:02:10 +0000 Subject: [PATCH 10/13] Typo --- diff --git a/openssh.spec b/openssh.spec index 290400d..e2909a7 100644 --- a/openssh.spec +++ b/openssh.spec @@ -286,7 +286,7 @@ Patch2016: fb87_080_logging_certificates.patch Patch2017: fb87_log_accept_env.patch # Set an environment variable SSH_CERT_PRINCIPALS in the child process # to be the full principal list of a user's SSH certificate when forced -# ommand is present and the user is authenticated by the certificate. +# command is present and the user is authenticated by the certificate. Patch2018: fb87_pass_principals_to_child.patch # Log extra authenticaton informatino to the auth_info structured # logging field, and add tests for pubkey and cert auth. From 487284af47ad16d11392e2378e41ec9525280aa9 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 23:18:03 +0000 Subject: [PATCH 11/13] Turn off use_quilt. --- diff --git a/openssh.spec b/openssh.spec index e2909a7..2722f4e 100644 --- a/openssh.spec +++ b/openssh.spec @@ -7,7 +7,7 @@ # Useful development mode for porting patches from # a different release -%global use_quilt 1 +%global use_quilt 0 %global _hardened_build 1 From 6da40c6384b9065902fc11e3a3a9a50f9002790d Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 23:25:43 +0000 Subject: [PATCH 12/13] Add BuildRequires: quilt --- diff --git a/openssh.spec b/openssh.spec index 2722f4e..86f3d44 100644 --- a/openssh.spec +++ b/openssh.spec @@ -338,6 +338,10 @@ BuildRequires: xauth # for tarball signature verification BuildRequires: gnupg2 +%if 0%{?facebook} && 0%{?use_quilt} +BuildRequires: quilt +%endif + %package clients Summary: An open source SSH client applications Requires: openssh = %{version}-%{release} From e8dcb2ae87ee31b5fbd3a7587f401f25b5b553f3 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 23:30:02 +0000 Subject: [PATCH 13/13] Fix macro usage --- diff --git a/openssh.spec b/openssh.spec index 86f3d44..d7d36e1 100644 --- a/openssh.spec +++ b/openssh.spec @@ -81,8 +81,9 @@ Source12: sshd-keygen@.service Source13: sshd-keygen Source15: sshd-keygen.target Source16: ssh-agent.service -%if 0%{facebook} && 0%{use_quilt} +%if 0%{?facebook} && 0%{?use_quilt} Source17: series +BuildRequires: quilt %endif #https://bugzilla.mindrot.org/show_bug.cgi?id=2581 @@ -264,7 +265,7 @@ 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 0%{facebook} && !0%{use_quilt} +%if 0%{?facebook} && !0%{?use_quilt} # Add a unique log session identifier to output messages for # each sshd process and its children. Patch2010: fb87_log_session_id.patch @@ -288,7 +289,7 @@ Patch2017: fb87_log_accept_env.patch # to be the full principal list of a user's SSH certificate when forced # command is present and the user is authenticated by the certificate. Patch2018: fb87_pass_principals_to_child.patch -# Log extra authenticaton informatino to the auth_info structured +# Log extra authentication information to the auth_info structured # logging field, and add tests for pubkey and cert auth. Patch2019: fb87_log_auth_info.patch %endif @@ -338,10 +339,6 @@ BuildRequires: xauth # for tarball signature verification BuildRequires: gnupg2 -%if 0%{?facebook} && 0%{?use_quilt} -BuildRequires: quilt -%endif - %package clients Summary: An open source SSH client applications Requires: openssh = %{version}-%{release} @@ -502,7 +499,7 @@ popd %patch100 -p1 -b .coverity -%if 0%{facebook} && !0%{use_quilt} +%if 0%{?facebook} && !0%{?use_quilt} %patch2010 -p1 -b .log_session_id %patch2011 -p1 -b .slog %patch2012 -p1 -b .log_port_forwards @@ -515,7 +512,7 @@ popd %patch2019 -p1 -b .log_auth_info %endif -%if 0%{facebook} && 0%{use_quilt} +%if 0%{?facebook} && 0%{?use_quilt} ln -sf %{_sourcedir} patches quilt push -a %endif