d1c05f
--- /dev/null
Michel Lind 1c324b
+++ b/slog.c
d1c05f
@@ -0,0 +1,619 @@
d1c05f
+/*
d1c05f
+ * Copyright 2004-present Facebook. All Rights Reserved.
d1c05f
+ */
d1c05f
+
d1c05f
+ /* When using slogctxt in any module perform a NULL pointer check */
d1c05f
+
d1c05f
+#include <sys/types.h>
d1c05f
+#include <stdlib.h>
d1c05f
+#include <unistd.h>
d1c05f
+#include <stdio.h>
d1c05f
+#include <string.h>
d1c05f
+#include <syslog.h>
d1c05f
+#include <pwd.h>
d1c05f
+#include <time.h>
d1c05f
+
d1c05f
+#include "includes.h"
d1c05f
+#include "slog.h"
d1c05f
+#include "log.h"
d1c05f
+#include "misc.h"
d1c05f
+#include "servconf.h"
d1c05f
+#include "xmalloc.h"
d1c05f
+
d1c05f
+typedef struct Structuredlogctxt Structuredlogctxt;
d1c05f
+
d1c05f
+struct Structuredlogctxt { /* items we care about logging */
d1c05f
+	char		server_ip[SLOG_SHORT_STRING_LEN];		// server_ip
d1c05f
+	int		server_port;		// server_port
d1c05f
+	char		remote_ip[SLOG_SHORT_STRING_LEN];		// remote_ip
d1c05f
+	int		remote_port;		// remote_port
d1c05f
+	pid_t		pam_process_pid;		// pam_pid
d1c05f
+	char		session_id[SLOG_STRING_LEN];		// session_id
d1c05f
+	char		method[SLOG_STRING_LEN];		// method
d1c05f
+	char		cert_id[SLOG_STRING_LEN];		// cert_id
d1c05f
+	unsigned long long		cert_serial;		// cert_serial
d1c05f
+	char		principal[SLOG_STRING_LEN];		// principal
d1c05f
+	char		user[SLOG_STRING_LEN];		// user
d1c05f
+	char		command[SLOG_LONG_STRING_LEN];		// command
d1c05f
+	SLOG_SESSION_STATE		session_state;		// session_state
d1c05f
+	SLOG_AUTHENTICATED		auth_successful;		// auth_successful
d1c05f
+	time_t		start_time;		// start_time
d1c05f
+	time_t		end_time;		// end_time
d1c05f
+	int		duration;		// duration
d1c05f
+	pid_t		main_pid;		// main_pid
d1c05f
+	char		auth_info[SLOG_MEDIUM_STRING_LEN];		// auth_info
d1c05f
+	char		client_version[SLOG_STRING_LEN];		// client_version
d1c05f
+};
d1c05f
+
d1c05f
+Structuredlogctxt *slogctxt;
d1c05f
+extern ServerOptions options;
d1c05f
+
d1c05f
+// Define token constants and default syntax format
d1c05f
+static const char *server_ip_token         = "server_ip";
d1c05f
+static const char *server_port_token       = "server_port";
d1c05f
+static const char *remote_ip_token         = "remote_ip";
d1c05f
+static const char *remote_port_token       = "remote_port";
d1c05f
+static const char *pam_pid_token           = "pam_pid";
d1c05f
+static const char *process_pid_token       = "pid";
d1c05f
+static const char *session_id_token        = "session_id";
d1c05f
+static const char *method_token            = "method";
d1c05f
+static const char *cert_id_token           = "cert_id";
d1c05f
+static const char *cert_serial_token       = "cert_serial";
d1c05f
+static const char *principal_token         = "principal";
d1c05f
+static const char *user_token              = "user";
d1c05f
+static const char *command_token           = "command";
d1c05f
+static const char *session_state_token     = "session_state";
d1c05f
+static const char *auth_successful_token   = "auth_successful";
d1c05f
+static const char *start_time_token        = "start_time";
d1c05f
+static const char *end_time_token          = "end_time";
d1c05f
+static const char *duration_token          = "duration";
d1c05f
+static const char *main_pid_token          = "main_pid";
d1c05f
+static const char *auth_info_token         = "auth_info";
d1c05f
+static const char *client_version          = "client_version";
d1c05f
+
d1c05f
+/* Example log format sshd_config
d1c05f
+ * LogFormatPrefix sshd_auth_msg:
d1c05f
+ * LogFormatKeys server_ip server_port remote_ip remote_port pid session_id /
d1c05f
+   method cert_id cert_serial principal user session_state auth_successful /
d1c05f
+   start_time command # NO LINE BREAKS
d1c05f
+ * LogFormatJson yes # no makes this output an json array vs json object
d1c05f
+ */
d1c05f
+
d1c05f
+// Set a arbitrary large size so we can feed a potentially
d1c05f
+// large json object to the logger
d1c05f
+#define SLOG_BUF_SIZE 8192
d1c05f
+#define SLOG_TRUNCATED_MESSAGE_JSON ", \"incomplete\": \"true\"}"
d1c05f
+#define SLOG_TRUNCATED_MESSAGE_ARRAY ", \"incomplete\"]"
d1c05f
+#define SLOG_TRUNCATED_SIZE 25
d1c05f
+/* size of format for JSON for quotes_colon_space around key and comma_space
d1c05f
+   after value or closure_null */
d1c05f
+#define SLOG_JSON_FORMAT_SIZE 6
d1c05f
+#define SLOG_BUF_CALC_SIZE  SLOG_BUF_SIZE - SLOG_TRUNCATED_SIZE
d1c05f
+
d1c05f
+/* private declarations */
d1c05f
+static void slog_log(void);
d1c05f
+static void slog_cleanup(void);
d1c05f
+static void slog_generate_auth_payload(char *);
d1c05f
+static void slog_escape_value(char *, char *, size_t);
d1c05f
+static void slog_get_safe_from_token(char *, const char *);
d1c05f
+static const char* slog_get_state_text(void);
d1c05f
+
d1c05f
+/* public functions */
d1c05f
+
d1c05f
+void
d1c05f
+slog_init(void)
d1c05f
+{
d1c05f
+	/* initialize only if we have log_format_keys */
d1c05f
+	if (options.num_log_format_keys != 0) {
d1c05f
+		slogctxt = xcalloc(1, sizeof(Structuredlogctxt));
d1c05f
+		if (slogctxt != NULL)
d1c05f
+			slog_cleanup();
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_pam_session_opened(void)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		slogctxt->session_state = SLOG_SESSION_OPEN;
d1c05f
+		slogctxt->pam_process_pid = getpid();
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_auth_data(int authenticated, const char *method, const char *user)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		slogctxt->auth_successful =
d1c05f
+		    authenticated ? SLOG_AUTHORIZED : SLOG_UNAUTHORIZED;
d1c05f
+		strlcpy(slogctxt->method, method, SLOG_SHORT_STRING_LEN);
d1c05f
+		strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_cert_id(const char *id)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL)
d1c05f
+		strlcpy(slogctxt->cert_id, id, SLOG_STRING_LEN);
d1c05f
+}
d1c05f
+
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_cert_serial(unsigned long long serial)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL)
d1c05f
+		slogctxt->cert_serial = serial;
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_connection(const char *remote_ip, int remote_port,
d1c05f
+    const char *server_ip, int server_port, const char *session)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		strlcpy(slogctxt->remote_ip, remote_ip, SLOG_SHORT_STRING_LEN);
d1c05f
+		slogctxt->remote_port = remote_port;
d1c05f
+		strlcpy(slogctxt->server_ip, server_ip, SLOG_SHORT_STRING_LEN);
d1c05f
+		slogctxt->server_port = server_port;
d1c05f
+		strlcpy(slogctxt->session_id, session, SLOG_STRING_LEN);
d1c05f
+		slogctxt->start_time = time(NULL);
d1c05f
+		slogctxt->main_pid = getpid();
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_client_version(const char *version)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		if (strlen(version) < SLOG_STRING_LEN)
d1c05f
+			strlcpy(slogctxt->client_version, version, SLOG_STRING_LEN);
d1c05f
+		else {
d1c05f
+			// version can be up to 256 bytes, truncate to 95 and add ' ...'
d1c05f
+			// which will fit in SLOG_STRING_LEN
d1c05f
+			snprintf(slogctxt->client_version, SLOG_STRING_LEN, "%.95s ...", version);
d1c05f
+		}
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_command(const char *command)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		if (strlen(command) < SLOG_LONG_STRING_LEN)
d1c05f
+			strlcpy(slogctxt->command, command, SLOG_LONG_STRING_LEN);
d1c05f
+		else {
d1c05f
+			// If command is longer than allowed we truncate it to
d1c05f
+			// 1995 (SLOG_LONG_STRING_LEN - 5) characters and add ' ...\0' to
d1c05f
+			// the end of the command.
d1c05f
+			snprintf(slogctxt->command, SLOG_LONG_STRING_LEN, "%.1995s ...", command);
d1c05f
+		}
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_principal(const char *principal)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL)
d1c05f
+		strlcpy(slogctxt->principal, principal, SLOG_STRING_LEN);
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_user(const char *user)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL)
d1c05f
+		strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_set_auth_info(const char *auth_info)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL)
d1c05f
+		strlcpy(slogctxt->auth_info, auth_info, SLOG_MEDIUM_STRING_LEN);
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_exit_handler(void)
d1c05f
+{
d1c05f
+	/* to prevent duplicate logging we only log based on the pid set */
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		if (slogctxt->server_ip[0] == 0)
d1c05f
+			return; // not initialized
d1c05f
+		if (slogctxt->main_pid != getpid())
d1c05f
+			return; // not main process
d1c05f
+		if (slogctxt->session_state == SLOG_SESSION_INIT)
d1c05f
+			slog_log();
d1c05f
+		else {
d1c05f
+			slogctxt->session_state = SLOG_SESSION_CLOSED;
d1c05f
+			slogctxt->end_time = time(NULL);
d1c05f
+			slogctxt->duration = slogctxt->end_time - slogctxt->start_time;
d1c05f
+			slog_log();
d1c05f
+			slog_cleanup();
d1c05f
+		}
d1c05f
+  }
d1c05f
+}
d1c05f
+
d1c05f
+void
d1c05f
+slog_log_session(void)
d1c05f
+{
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		slogctxt->session_state = SLOG_SESSION_OPEN;
d1c05f
+		slog_log();
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+/* private function scope begin */
d1c05f
+
d1c05f
+static void
d1c05f
+slog_log(void)
d1c05f
+{
d1c05f
+	char *buffer = xmalloc(SLOG_BUF_SIZE);
d1c05f
+
d1c05f
+	if (buffer == NULL)
d1c05f
+		return;
d1c05f
+
d1c05f
+	memset(buffer, 0, SLOG_BUF_SIZE);
d1c05f
+
d1c05f
+	if (options.num_log_format_keys > 0
d1c05f
+	    && slogctxt != NULL
d1c05f
+	    && slogctxt->server_ip[0] != 0
d1c05f
+	    && slogctxt->user[0] != 0) {
d1c05f
+		slog_generate_auth_payload(buffer);
d1c05f
+		do_log_slog_payload(buffer);
d1c05f
+	}
d1c05f
+
d1c05f
+	free(buffer);
d1c05f
+}
d1c05f
+
d1c05f
+static void
d1c05f
+slog_cleanup(void)
d1c05f
+{
d1c05f
+	// Reset the log context values
d1c05f
+	if (slogctxt != NULL) {
d1c05f
+		memset(slogctxt, 0, sizeof(Structuredlogctxt));
d1c05f
+		slogctxt->session_state = SLOG_SESSION_INIT;
d1c05f
+		slogctxt->auth_successful = SLOG_UNAUTHORIZED;
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+/* We use debug3 since the debug is very noisy */
d1c05f
+static void
d1c05f
+slog_generate_auth_payload(char *buf)
d1c05f
+{
d1c05f
+	if (buf == NULL)
d1c05f
+		return;
d1c05f
+
d1c05f
+	// Create large buffer so don't risk overflow
d1c05f
+	char *safe = xmalloc(SLOG_BUF_SIZE);
d1c05f
+	memset(safe, 0, SLOG_BUF_SIZE);
d1c05f
+
d1c05f
+	if (safe == NULL)
d1c05f
+		return;
d1c05f
+
d1c05f
+	int i;
d1c05f
+	size_t remaining;
d1c05f
+	int json = options.log_format_json;
d1c05f
+	int keys = options.num_log_format_keys;
d1c05f
+	int truncated = 0;
d1c05f
+	char *tmpbuf = buf;
d1c05f
+	char *key;
d1c05f
+
d1c05f
+	debug3("JSON format is %d with %d tokens.", json, keys);
d1c05f
+
d1c05f
+	if (options.log_format_prefix != NULL
d1c05f
+	    && strlen(options.log_format_prefix) < SLOG_BUF_CALC_SIZE-1) {
d1c05f
+		tmpbuf += snprintf(tmpbuf, SLOG_BUF_CALC_SIZE,
d1c05f
+	    "%s ", options.log_format_prefix);
d1c05f
+	}
d1c05f
+	*tmpbuf++ = (json) ? '{' : '[';
d1c05f
+	debug3("current buffer after prefix: %s", buf);
d1c05f
+
d1c05f
+	// Loop through the keys filling out the output string
d1c05f
+	for (i = 0; i < keys; i++) {
d1c05f
+		safe[0] = 0;  // clear safe string
d1c05f
+		key = options.log_format_keys[i];
d1c05f
+		remaining = SLOG_BUF_CALC_SIZE - (tmpbuf - buf);
d1c05f
+
d1c05f
+		if (key == NULL)
d1c05f
+			continue;  // Shouldn't happen but if null go to next key
d1c05f
+
d1c05f
+		slog_get_safe_from_token(safe, key);
d1c05f
+		debug3("token: %s, value: %s", key, safe);
d1c05f
+
d1c05f
+		if (json) {
d1c05f
+			if (*safe == '\0')
d1c05f
+				continue; // No value since we are using key pairs skip
d1c05f
+			if (remaining <= SLOG_JSON_FORMAT_SIZE + strlen(key) + strlen(safe)) {
d1c05f
+				debug("Log would exceed buffer size %u, %zu, %zu at key: %s",
d1c05f
+				    (unsigned int)remaining, strlen(key), strlen(safe), key);
d1c05f
+				truncated = 1;
d1c05f
+				break;
d1c05f
+			}
d1c05f
+			tmpbuf += snprintf(tmpbuf, remaining, "%s\"%s\": %s",
d1c05f
+			    i > 0 ? ", " : "", key, safe);
d1c05f
+		} else {
d1c05f
+			if (*safe == '\0')
d1c05f
+				strlcpy(safe, "\"\"", SLOG_SHORT_STRING_LEN);
d1c05f
+			if (remaining < SLOG_JSON_FORMAT_SIZE + strlen(safe)) {
d1c05f
+				debug("Log would exceed remaining buffer size %d, %zu, at key: %s",
d1c05f
+				    (unsigned int)remaining, strlen(safe), key);
d1c05f
+				truncated = 1;
d1c05f
+				break;
d1c05f
+			}
d1c05f
+			tmpbuf += snprintf(tmpbuf, remaining, "%s%s", i > 0 ? ", " : "", safe);
d1c05f
+		}
d1c05f
+		debug3("current buffer after token: %s", buf);
d1c05f
+		debug3("end of loop key: %s, %d out of %d keys", key, i + 1, keys);
d1c05f
+	}
d1c05f
+
d1c05f
+	// Close the log string. If truncated set truncated message and close string
d1c05f
+	if (truncated == 1)
d1c05f
+		strlcpy(tmpbuf, json ? SLOG_TRUNCATED_MESSAGE_JSON :
d1c05f
+		    SLOG_TRUNCATED_MESSAGE_ARRAY, SLOG_TRUNCATED_SIZE);
d1c05f
+	else {
d1c05f
+		*tmpbuf++ = (json) ? '}' : ']';
d1c05f
+	}
d1c05f
+
d1c05f
+	free(safe);
d1c05f
+}
d1c05f
+
d1c05f
+// buffer size is input string * 2 +1
d1c05f
+static void
d1c05f
+slog_escape_value(char *output, char *input, size_t buffer_size)
d1c05f
+{
d1c05f
+	int i;
d1c05f
+	buffer_size -= 2;
d1c05f
+	if (input != NULL) {
d1c05f
+		int input_size = strlen(input);
d1c05f
+		char *temp = output;
d1c05f
+		*temp++ = '"';
d1c05f
+		buffer_size--;
d1c05f
+		for (i = 0; i < input_size && buffer_size > 0; i++) {
d1c05f
+			switch(input[i]) {
d1c05f
+			// characters escaped are the same as folly::json::escapeString
d1c05f
+			case 27: // <escape> ascii control character
d1c05f
+				if (buffer_size > 6) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 'u';
d1c05f
+					*temp++ = '0';
d1c05f
+					*temp++ = '0';
d1c05f
+					*temp++ = '1';
d1c05f
+					*temp++ = 'b';
d1c05f
+					buffer_size -= 6;
d1c05f
+				}
d1c05f
+			case '\b':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 'b';
d1c05f
+					buffer_size -= 2;
d1c05f
+				}
d1c05f
+				break;
d1c05f
+			case '\f':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 'f';
d1c05f
+					buffer_size -= 2;
d1c05f
+				}
d1c05f
+				break;
d1c05f
+			case '\n':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 'n';
d1c05f
+					buffer_size -= 2;
d1c05f
+					}
d1c05f
+				break;
d1c05f
+			case '\r':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 'r';
d1c05f
+					buffer_size -= 2;
d1c05f
+				}
d1c05f
+				break;
d1c05f
+			case '\t':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					*temp++ = 't';
d1c05f
+					buffer_size -= 2;
d1c05f
+				}
d1c05f
+				break;
d1c05f
+			case '\"':
d1c05f
+			case '\\':
d1c05f
+				if (buffer_size > 1) {
d1c05f
+					*temp++ = '\\';
d1c05f
+					buffer_size--;
d1c05f
+			}
d1c05f
+			default:  // Non-escape char
d1c05f
+				*temp++ = input[i];
d1c05f
+				buffer_size--;
d1c05f
+			}
d1c05f
+		}
d1c05f
+		*temp++ = '"';
d1c05f
+		*temp++ = '\0';
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+static void
d1c05f
+slog_get_safe_from_token(char *output, const char *token)
d1c05f
+{
d1c05f
+	if (output == NULL || token == NULL || slogctxt == NULL)
d1c05f
+		return;
d1c05f
+
d1c05f
+	if (strcmp(token, server_ip_token) == 0) {
d1c05f
+		if (slogctxt->server_ip[0] != 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
d1c05f
+			    slogctxt->server_ip);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, server_port_token) == 0) {
d1c05f
+		if (slogctxt->server_port > 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
d1c05f
+			slogctxt->server_port);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, remote_ip_token) == 0) {
d1c05f
+		if (slogctxt->remote_ip[0] != 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
d1c05f
+			    slogctxt->remote_ip);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, remote_port_token) == 0) {
d1c05f
+		if (slogctxt->remote_port > 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
d1c05f
+			    slogctxt->remote_port);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, pam_pid_token) == 0) {
d1c05f
+		if (slogctxt->pam_process_pid > 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"",
d1c05f
+			    (long)slogctxt->pam_process_pid);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, process_pid_token) == 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", (long)getpid());
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, session_id_token) == 0) {
d1c05f
+		if (slogctxt->session_id[0] != 0) {
d1c05f
+				snprintf(output, SLOG_STRING_LEN, "\"%s\"",
d1c05f
+				    slogctxt->session_id);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, method_token) == 0) {
d1c05f
+		if (slogctxt->method[0] != 0) {
d1c05f
+				snprintf(output, SLOG_STRING_LEN, "\"%s\"",
d1c05f
+				    slogctxt->method);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strcmp(token, cert_id_token) == 0) {
d1c05f
+		if (slogctxt->cert_id[0] != 0 &&
d1c05f
+		    strcmp(slogctxt->method, "publickey") == 0) {
d1c05f
+				slog_escape_value(output, slogctxt->cert_id,
d1c05f
+				    SLOG_STRING_LEN * 2 + 1);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, cert_serial_token) == 0) {
d1c05f
+		if (slogctxt->cert_serial > 0 &&
d1c05f
+		    strcmp(slogctxt->method, "publickey") == 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%llu\"",
d1c05f
+			    slogctxt->cert_serial);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strcmp(token, principal_token) == 0) {
d1c05f
+		if (slogctxt->principal[0] != 0) {
d1c05f
+			slog_escape_value(output, slogctxt->principal,
d1c05f
+			    SLOG_STRING_LEN * 2 + 1);
d1c05f
+		}
d1c05f
+	return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strcmp(token, user_token) == 0) {
d1c05f
+		if (slogctxt->user[0] != 0) {
d1c05f
+			slog_escape_value(output, slogctxt->user,
d1c05f
+		    SLOG_STRING_LEN * 2 + 1);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strcmp(token, auth_info_token) == 0) {
d1c05f
+		if (slogctxt->auth_info[0] != 0) {
d1c05f
+			slog_escape_value(output, slogctxt->auth_info,
d1c05f
+			    SLOG_MEDIUM_STRING_LEN * 2 + 1);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strcmp(token, command_token) == 0) {
d1c05f
+		if (slogctxt->command[0] != 0) {
d1c05f
+			slog_escape_value(output, slogctxt->command,
d1c05f
+			    SLOG_LONG_STRING_LEN * 2 + 1);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, auth_successful_token) == 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
d1c05f
+		    slogctxt->auth_successful == SLOG_AUTHORIZED ? "true" : "false");
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, session_state_token) == 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
d1c05f
+		    slog_get_state_text());
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, start_time_token) == 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
d1c05f
+		    (int)slogctxt->start_time);
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, end_time_token) == 0 && slogctxt->end_time > 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
d1c05f
+		    (int)slogctxt->end_time);
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, duration_token) == 0 && slogctxt->end_time > 0) {
d1c05f
+		snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", slogctxt->duration);
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	if (strcmp(token, main_pid_token) == 0) {
d1c05f
+		if (slogctxt->main_pid > 0) {
d1c05f
+			snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"",
d1c05f
+			    (long)slogctxt->main_pid);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+
d1c05f
+	// Arbitrary input
d1c05f
+	if (strncmp(token, client_version, strlen(client_version)) == 0) {
d1c05f
+		if (slogctxt->client_version[0] != 0) {
d1c05f
+			slog_escape_value(output, slogctxt->client_version,
d1c05f
+			    SLOG_STRING_LEN + 2);
d1c05f
+		}
d1c05f
+		return;
d1c05f
+	}
d1c05f
+}
d1c05f
+
d1c05f
+static const char *
d1c05f
+slog_get_state_text(void)
d1c05f
+{
d1c05f
+	if (slogctxt == NULL)
d1c05f
+		return "";
d1c05f
+
d1c05f
+	switch (slogctxt->session_state) {
d1c05f
+		case SLOG_SESSION_INIT:
d1c05f
+			return "Session failed";
d1c05f
+		case SLOG_SESSION_OPEN:
d1c05f
+			return "Session opened";
d1c05f
+		case SLOG_SESSION_CLOSED:
d1c05f
+			return "Session closed";
d1c05f
+		default:
d1c05f
+			return "Unknown session state";  // Should never happen
d1c05f
+	}
d1c05f
+}
Michel Lind 1c324b
--- a/servconf.c
Michel Lind 1c324b
+++ b/servconf.c
4c983d
@@ -205,6 +205,9 @@ initialize_server_options(ServerOptions
d1c05f
 	options->disable_forwarding = -1;
d1c05f
 	options->expose_userauth_info = -1;
4c983d
 	options->required_rsa_size = -1;
4c983d
+ 	options->log_format_prefix = NULL;
4c983d
+ 	options->num_log_format_keys = 0;
4c983d
+ 	options->log_format_json = -1;
d1c05f
 }
d1c05f
 
d1c05f
 /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
4c983d
@@ -474,6 +477,8 @@ fill_default_server_options(ServerOption
d1c05f
 		options->sk_provider = xstrdup("internal");
4c983d
 	if (options->required_rsa_size == -1)
4c983d
 		options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
4c983d
+ 	if (options->log_format_json == -1)
4c983d
+ 		options->log_format_json = 0;
d1c05f
 
d1c05f
 	assemble_algorithms(options);
d1c05f
 
4c983d
@@ -554,6 +559,10 @@ typedef enum {
d1c05f
 	sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
d1c05f
 	sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
4c983d
 	sRequiredRSASize,
4c983d
+ 	/* Structured Logging options.  Unless sLogFormatKeys is set,
4c983d
+ 	    structured logging is disabled */
4c983d
+ 	sLogFormatPrefix, sLogFormatKeys, sLogFormatJson,
d1c05f
+
d1c05f
 	sDeprecated, sIgnore, sUnsupported
d1c05f
 } ServerOpCodes;
d1c05f
 
4c983d
@@ -732,6 +741,9 @@ static struct {
d1c05f
 	{ "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL },
4c983d
 	{ "requiredrsasize", sRequiredRSASize, SSHCFG_ALL },
4c983d
 	{ "rsaminsize", sRequiredRSASize, SSHCFG_ALL }, /* alias */
4c983d
+ 	{ "logformatprefix", sLogFormatPrefix, SSHCFG_GLOBAL },
4c983d
+ 	{ "logformatkeys", sLogFormatKeys, SSHCFG_GLOBAL },
4c983d
+ 	{ "logformatjson", sLogFormatJson, SSHCFG_GLOBAL },
d1c05f
 	{ NULL, sBadOption, 0 }
d1c05f
 };
d1c05f
 
4c983d
@@ -2369,6 +2381,30 @@ process_server_config_line_depth(ServerO
d1c05f
 		}
d1c05f
 		break;
d1c05f
 
d1c05f
+	case sLogFormatPrefix:
f94360
+		arg = argv_next(&ac, &av;;
d1c05f
+		if (!arg || *arg == '\0') {
d1c05f
+			fatal("%.200s line %d: invalid log format prefix",
d1c05f
+			    filename, linenum);
d1c05f
+		}
d1c05f
+		options->log_format_prefix = xstrdup(arg);
d1c05f
+		break;
d1c05f
+
d1c05f
+	case sLogFormatKeys:
f94360
+		while ((arg = argv_next(&ac, &av)) && *arg != '\0') {
d1c05f
+			if (options->num_log_format_keys >= MAX_LOGFORMAT_KEYS)
d1c05f
+				fatal("%s line %d: too long format keys.",
d1c05f
+				    filename, linenum);
d1c05f
+			if (!*activep)
d1c05f
+				continue;
d1c05f
+			options->log_format_keys[options->num_log_format_keys++] = xstrdup(arg);
d1c05f
+		}
d1c05f
+		break;
d1c05f
+
d1c05f
+	case sLogFormatJson:
d1c05f
+		intptr = &options->log_format_json;
d1c05f
+		goto parse_flag;
d1c05f
+
d1c05f
 	case sIPQoS:
d1c05f
 		arg = argv_next(&ac, &av;;
d1c05f
 		if (!arg || *arg == '\0')
4c983d
@@ -3026,6 +3062,7 @@ dump_config(ServerOptions *o)
d1c05f
 	dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
d1c05f
 	dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash);
d1c05f
 	dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info);
d1c05f
+	dump_cfg_fmtint(sLogFormatJson, o->log_format_json);
d1c05f
 
d1c05f
 	/* string arguments */
d1c05f
 	dump_cfg_string(sPidFile, o->pid_file);
4c983d
@@ -3056,6 +3093,7 @@ dump_config(ServerOptions *o)
d1c05f
 #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN)
d1c05f
 	dump_cfg_string(sRDomain, o->routing_domain);
d1c05f
 #endif
d1c05f
+	dump_cfg_string(sLogFormatPrefix, o->log_format_prefix);
d1c05f
 
d1c05f
 	/* string arguments requiring a lookup */
d1c05f
 	dump_cfg_string(sLogLevel, log_level_name(o->log_level));
4c983d
@@ -3078,6 +3116,7 @@ dump_config(ServerOptions *o)
d1c05f
 	    o->num_auth_methods, o->auth_methods);
d1c05f
 	dump_cfg_strarray_oneline(sLogVerbose,
d1c05f
 	    o->num_log_verbose, o->log_verbose);
d1c05f
+	dump_cfg_strarray(sLogFormatKeys, o->num_log_format_keys, o->log_format_keys);
d1c05f
 
d1c05f
 	/* other arguments */
d1c05f
 	for (i = 0; i < o->num_subsystems; i++)
Michel Lind 1c324b
--- a/auth2-pubkey.c
Michel Lind 1c324b
+++ b/auth2-pubkey.c
d1c05f
@@ -66,6 +66,7 @@
d1c05f
 #include "monitor_wrap.h"
d1c05f
 #include "authfile.h"
d1c05f
 #include "match.h"
d1c05f
+#include "slog.h"
d1c05f
 #include "ssherr.h"
d1c05f
 #include "channels.h" /* XXX for session.h */
d1c05f
 #include "session.h" /* XXX for child_set_env(); refactor? */
4c983d
@@ -390,6 +391,7 @@ check_principals_line(struct ssh *ssh, c
d1c05f
 		debug3("%s: matched principal \"%.100s\"",
d1c05f
 		    loc, cert->principals[i]);
d1c05f
 		found = 1;
d1c05f
+		slog_set_principal(cp);
d1c05f
 	}
d1c05f
 	if (found && authoptsp != NULL) {
d1c05f
 		*authoptsp = opts;
4c983d
@@ -715,6 +717,7 @@ check_authkey_line(struct ssh *ssh, stru
d1c05f
 	    (unsigned long long)key->cert->serial,
d1c05f
 	    sshkey_type(found), fp, loc);
d1c05f
 
d1c05f
+	    slog_set_cert_serial(key->cert->serial);
d1c05f
  success:
d1c05f
 	if (finalopts == NULL)
d1c05f
 		fatal_f("internal error: missing options");
4c983d
@@ -865,6 +868,7 @@ user_cert_trusted_ca(struct ssh *ssh, st
d1c05f
 		*authoptsp = final_opts;
d1c05f
 		final_opts = NULL;
d1c05f
 	}
d1c05f
+	slog_set_cert_id(key->cert->key_id);
d1c05f
 	ret = 1;
d1c05f
  out:
d1c05f
 	sshauthopt_free(principals_opts);
Michel Lind 1c324b
--- a/regress/test-exec.sh
Michel Lind 1c324b
+++ b/regress/test-exec.sh
Michel Lind 1c324b
@@ -690,7 +690,7 @@ start_sshd ()
d1c05f
 
d1c05f
 	trace "wait for sshd"
d1c05f
 	i=0;
d1c05f
-	while [ ! -f $PIDFILE -a $i -lt 10 ]; do
d1c05f
+	while [ ! -f $PIDFILE -a $i -lt 3 ]; do
d1c05f
 		i=`expr $i + 1`
d1c05f
 		sleep $i
d1c05f
 	done
Michel Lind 1c324b
--- a/session.c
Michel Lind 1c324b
+++ b/session.c
d1c05f
@@ -96,6 +96,8 @@
d1c05f
 #include "monitor_wrap.h"
d1c05f
 #include "sftp.h"
d1c05f
 #include "atomicio.h"
d1c05f
+#include "slog.h"
d1c05f
+
d1c05f
 
d1c05f
 #if defined(KRB5) && defined(USE_AFS)
d1c05f
 #include <kafs.h>
d1c05f
@@ -753,6 +755,7 @@ do_exec(struct ssh *ssh, Session *s, con
d1c05f
 	    ssh_remote_ipaddr(ssh),
d1c05f
 	    ssh_remote_port(ssh),
d1c05f
 	    s->self);
d1c05f
+	    slog_log_session();
d1c05f
 
d1c05f
 #ifdef SSH_AUDIT_EVENTS
d1c05f
 	if (s->command != NULL || s->command_handle != -1)
Michel Lind 1c324b
@@ -1486,7 +1489,7 @@ do_setusercontext(struct passwd *pw)
d1c05f
 			perror("unable to set user context (setuser)");
d1c05f
 			exit(1);
d1c05f
 		}
d1c05f
-		/* 
d1c05f
+		/*
d1c05f
 		 * FreeBSD's setusercontext() will not apply the user's
d1c05f
 		 * own umask setting unless running with the user's UID.
d1c05f
 		 */
Michel Lind 1c324b
@@ -2163,6 +2166,7 @@ session_exec_req(struct ssh *ssh, Sessio
d1c05f
 	    (r = sshpkt_get_end(ssh)) != 0)
d1c05f
 		sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
d1c05f
 
d1c05f
+	slog_set_command(command);
d1c05f
 	success = do_exec(ssh, s, command) == 0;
d1c05f
 	free(command);
d1c05f
 	return success;
Michel Lind 1c324b
@@ -2873,4 +2877,3 @@ session_get_remote_name_or_ip(struct ssh
d1c05f
 		remote = ssh_remote_ipaddr(ssh);
d1c05f
 	return remote;
d1c05f
 }
d1c05f
-
Michel Lind 1c324b
--- a/log.h
Michel Lind 1c324b
+++ b/log.h
d1c05f
@@ -133,4 +133,6 @@ void	 sshlogdirect(LogLevel, int, const
d1c05f
 #define logdie_fr(r, ...)	sshlogdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__)
d1c05f
 #define sigdie_fr(r, ...)	sshsigdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__)
d1c05f
 
d1c05f
+void     do_log_slog_payload(const char *);
d1c05f
+
d1c05f
 #endif
Michel Lind 1c324b
--- a/log.c
Michel Lind 1c324b
+++ b/log.c
Michel Lind 1c324b
@@ -531,3 +531,39 @@ sshlogdirect(LogLevel level, int forced,
d1c05f
 	do_log(level, forced, NULL, fmt, args);
d1c05f
 	va_end(args);
d1c05f
 }
d1c05f
+
d1c05f
+/* Custom function to log to syslog, to handle the session logging code
d1c05f
+ */
d1c05f
+void
d1c05f
+do_log_slog_payload(const char *payload)
d1c05f
+{
d1c05f
+#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
d1c05f
+	struct syslog_data sdata = SYSLOG_DATA_INIT;
d1c05f
+#endif
d1c05f
+	int pri = LOG_INFO;
d1c05f
+	int saved_errno = errno;
d1c05f
+	LogLevel level = SYSLOG_LEVEL_INFO;
d1c05f
+	log_handler_fn *tmp_handler;
d1c05f
+
d1c05f
+	if (log_handler != NULL) {
d1c05f
+		/* Avoid recursion */
d1c05f
+		tmp_handler = log_handler;
d1c05f
+		log_handler = NULL;
d1c05f
+		tmp_handler(level, 0, payload, log_handler_ctx);
d1c05f
+		log_handler = tmp_handler;
d1c05f
+	} else if (log_on_stderr) {
d1c05f
+		(void)write(log_stderr_fd, payload, strlen(payload));
d1c05f
+		(void)write(log_stderr_fd, "\r\n", 2);
d1c05f
+	} else {
d1c05f
+#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
d1c05f
+		openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata);
d1c05f
+		syslog_r(pri, &sdata, "%s", payload);
d1c05f
+		closelog_r(&sdata);
d1c05f
+#else
d1c05f
+		openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility);
d1c05f
+		syslog(pri, "%s", payload);
d1c05f
+		closelog();
d1c05f
+#endif
d1c05f
+	}
d1c05f
+	errno = saved_errno;
d1c05f
+}
d1c05f
--- /dev/null
Michel Lind 1c324b
+++ b/slog.h
d1c05f
@@ -0,0 +1,41 @@
d1c05f
+/*
d1c05f
+ * Copyright 2004-present Facebook. All Rights Reserved.
d1c05f
+ */
d1c05f
+#ifndef USE_SLOG
d1c05f
+#define USE_SLOG
d1c05f
+
d1c05f
+#define SLOG_STRING_LEN        100
d1c05f
+#define SLOG_MEDIUM_STRING_LEN 1000
d1c05f
+#define SLOG_SHORT_STRING_LEN  50
d1c05f
+#define SLOG_LONG_STRING_LEN   2000
d1c05f
+
d1c05f
+typedef enum {
d1c05f
+	SLOG_SESSION_INIT,
d1c05f
+	SLOG_SESSION_OPEN,
d1c05f
+	SLOG_SESSION_CLOSED,
d1c05f
+} SLOG_SESSION_STATE;
d1c05f
+
d1c05f
+typedef enum {
d1c05f
+	SLOG_UNAUTHORIZED = 0,
d1c05f
+	SLOG_AUTHORIZED = 1
d1c05f
+} SLOG_AUTHENTICATED;
d1c05f
+
d1c05f
+void	slog_init(void);
d1c05f
+
d1c05f
+// setters
d1c05f
+void	slog_pam_session_opened(void);
d1c05f
+void	slog_set_auth_data(int , const char *, const char *);
d1c05f
+void	slog_set_cert_id(const char *);
d1c05f
+void	slog_set_cert_serial(unsigned long long );
d1c05f
+void	slog_set_connection(const char *, int, const char *, int, const char *);
d1c05f
+void	slog_set_command(const char *);
d1c05f
+void	slog_set_principal(const char *);
d1c05f
+void	slog_set_user(const char *);
d1c05f
+void	slog_set_auth_info(const char *);
d1c05f
+void	slog_set_client_version(const char *);
d1c05f
+
d1c05f
+// loggers
d1c05f
+void	slog_exit_handler(void);
d1c05f
+void	slog_log_session(void);
d1c05f
+
d1c05f
+#endif
Michel Lind 1c324b
--- a/sshd.c
Michel Lind 1c324b
+++ b/sshd.c
d1c05f
@@ -132,6 +132,8 @@
d1c05f
 #include "sk-api.h"
d1c05f
 #include "srclimit.h"
d1c05f
 #include "dh.h"
d1c05f
+#include "slog.h"
d1c05f
+#include <time.h>
d1c05f
 
d1c05f
 /* Re-exec fds */
d1c05f
 #define REEXEC_DEVCRYPTO_RESERVED_FD	(STDERR_FILENO + 1)
Michel Lind 1c324b
@@ -2177,6 +2179,7 @@ main(int ac, char **av)
d1c05f
 	}
d1c05f
 	/* Reinitialize the log (because of the fork above). */
d1c05f
 	log_init(__progname, options.log_level, options.log_facility, log_stderr);
d1c05f
+	slog_init();
d1c05f
 
d1c05f
 	if (FIPS_mode()) {
d1c05f
 		debug("FIPS mode initialized");
Michel Lind 1c324b
@@ -2346,8 +2349,15 @@ main(int ac, char **av)
d1c05f
 	    rdomain == NULL ? "" : " rdomain \"",
d1c05f
 	    rdomain == NULL ? "" : rdomain,
d1c05f
 	    rdomain == NULL ? "" : "\"");
d1c05f
-	free(laddr);
d1c05f
 
d1c05f
+
d1c05f
+	slog_set_connection(remote_ip,
d1c05f
+	    remote_port,
d1c05f
+	    laddr,
d1c05f
+	    ssh_local_port(ssh),
d1c05f
+	    get_log_session_id());
d1c05f
+
d1c05f
+	free(laddr);
d1c05f
 	/*
d1c05f
 	 * We don't want to listen forever unless the other side
d1c05f
 	 * successfully authenticates itself.  So we set up an alarm which is
Michel Lind 1c324b
@@ -2674,6 +2684,7 @@ cleanup_exit(int i)
d1c05f
 	if (in_cleanup)
d1c05f
 		_exit(i);
d1c05f
 	in_cleanup = 1;
d1c05f
+	slog_exit_handler();
d1c05f
 	if (the_active_state != NULL && the_authctxt != NULL) {
d1c05f
 		do_cleanup(the_active_state, the_authctxt);
d1c05f
 		if (use_privsep && privsep_is_preauth &&
Michel Lind 1c324b
--- a/sshd_config
Michel Lind 1c324b
+++ b/sshd_config
d1c05f
@@ -33,6 +33,15 @@ Include /etc/ssh/sshd_config.d/*.conf
d1c05f
 # Logging
d1c05f
 #SyslogFacility AUTH
d1c05f
 #LogLevel INFO
d1c05f
+# Structured logging
d1c05f
+# The default is no structured logging, to enable structured logging at least
d1c05f
+# one LogFormatKeys must be set.  LogFormatJson defaults to no (array) to keep
d1c05f
+# the log line size smaller, if keys are desired set LogFormatJson to yes
d1c05f
+LogFormatPrefix sshd_auth_msg:
d1c05f
+LogFormatKeys server_ip server_port remote_ip remote_port pid session_id method
d1c05f
+LogFormatKeys cert_id cert_serial principal user session_state auth_successful
d1c05f
+LogFormatKeys start_time command
d1c05f
+LogFormatJson yes
d1c05f
 
d1c05f
 # Authentication:
d1c05f
 
Michel Lind 1c324b
--- a/auth-pam.c
Michel Lind 1c324b
+++ b/auth-pam.c
d1c05f
@@ -94,6 +94,7 @@ extern char *__progname;
d1c05f
 #include "auth-pam.h"
d1c05f
 #include "canohost.h"
d1c05f
 #include "log.h"
d1c05f
+#include "slog.h"
d1c05f
 #include "msg.h"
d1c05f
 #include "packet.h"
d1c05f
 #include "misc.h"
Michel Lind 1c324b
@@ -1210,9 +1211,12 @@ do_pam_session(struct ssh *ssh)
d1c05f
 	if (sshpam_err != PAM_SUCCESS)
d1c05f
 		fatal("PAM: failed to set PAM_CONV: %s",
d1c05f
 		    pam_strerror(sshpam_handle, sshpam_err));
d1c05f
+
d1c05f
 	sshpam_err = pam_open_session(sshpam_handle, 0);
d1c05f
-	if (sshpam_err == PAM_SUCCESS)
d1c05f
+	if (sshpam_err == PAM_SUCCESS) {
d1c05f
+		slog_pam_session_opened();
d1c05f
 		sshpam_session_open = 1;
d1c05f
+	}
d1c05f
 	else {
d1c05f
 		sshpam_session_open = 0;
d1c05f
 		auth_restrict_session(ssh);
Michel Lind 1c324b
--- a/servconf.h
Michel Lind 1c324b
+++ b/servconf.h
d1c05f
@@ -22,6 +22,8 @@
d1c05f
 
d1c05f
 #define MAX_SUBSYSTEMS		256	/* Max # subsystems. */
d1c05f
 
d1c05f
+#define MAX_LOGFORMAT_KEYS      256     /* Max # LogFormatKeys */
d1c05f
+
d1c05f
 /* permit_root_login */
d1c05f
 #define	PERMIT_NOT_SET		-1
d1c05f
 #define	PERMIT_NO		0
d1c05f
@@ -239,6 +241,12 @@ typedef struct {
d1c05f
 	u_int64_t timing_secret;
d1c05f
 	char   *sk_provider;
4c983d
 	int	required_rsa_size;	/* minimum size of RSA keys */
d1c05f
+
4c983d
+ 	char   *log_format_prefix;
4c983d
+ 	u_int  num_log_format_keys;
4c983d
+ 	char   *log_format_keys[MAX_LOGFORMAT_KEYS];
4c983d
+ 	int log_format_json;	/* 1 to return "token": "token_val" in log format */
d1c05f
+
d1c05f
 }       ServerOptions;
d1c05f
 
d1c05f
 /* Information about the incoming connection as used by Match */
d1c05f
--- /dev/null
Michel Lind 1c324b
+++ b/regress/slog.sh
d1c05f
@@ -0,0 +1,59 @@
d1c05f
+tid='structured log'
d1c05f
+
d1c05f
+port="4242"
d1c05f
+log_prefix="sshd_auth_msg:"
d1c05f
+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"
d1c05f
+do_log_json="yes"
d1c05f
+test_config="$OBJ/sshd2_config"
d1c05f
+old_config="$OBJ/sshd_config"
d1c05f
+PIDFILE=$OBJ/pidfile
d1c05f
+
d1c05f
+cat << EOF > $test_config
d1c05f
+	#*:
d1c05f
+	StrictModes             no
d1c05f
+	Port                    $port
d1c05f
+	AddressFamily           inet
d1c05f
+	ListenAddress           127.0.0.1
d1c05f
+	#ListenAddress          ::1
d1c05f
+	PidFile                 $PIDFILE
d1c05f
+	AuthorizedKeysFile      $OBJ/authorized_keys_%u
d1c05f
+	LogLevel                ERROR
d1c05f
+	AcceptEnv               _XXX_TEST_*
d1c05f
+	AcceptEnv               _XXX_TEST
d1c05f
+	HostKey $OBJ/host.ssh-ed25519
d1c05f
+	LogFormatPrefix $log_prefix
d1c05f
+	LogFormatJson $do_log_json
d1c05f
+	LogFormatKeys $log_keys
d1c05f
+EOF
d1c05f
+
d1c05f
+
d1c05f
+cp $test_config $old_config
d1c05f
+start_sshd
d1c05f
+
d1c05f
+${SSH} -F $OBJ/ssh_config somehost true
d1c05f
+if [ $? -ne 0 ]; then
d1c05f
+	fail "ssh connect with failed"
d1c05f
+fi
d1c05f
+
d1c05f
+test_log_counts() {
d1c05f
+	cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE")
d1c05f
+	if [ $cnt -ne 2 ]; then
d1c05f
+		fail "expected 2 structured logging lines, got $cnt"
d1c05f
+	fi
d1c05f
+}
d1c05f
+
d1c05f
+test_json_valid() {
d1c05f
+	which python &>/dev/null || echo 'python not found in path, skipping tests'
d1c05f
+
d1c05f
+	loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix")
d1c05f
+	first=$(echo "$loglines" | head -n1)
d1c05f
+	last=$(echo "$loglines" | tail -n1)
d1c05f
+
d1c05f
+	echo ${first:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \
d1c05f
+	    || fail "invalid json structure $first"
d1c05f
+	echo ${last:$(expr length $log_prefix)} | python -m json.tool &>/dev/null  \
d1c05f
+	    || fail "invalid json structure $last"
d1c05f
+}
d1c05f
+
d1c05f
+test_log_counts
d1c05f
+test_json_valid
Michel Lind 1c324b
--- a/Makefile.in
Michel Lind 1c324b
+++ b/Makefile.in
d1c05f
@@ -129,7 +129,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passw
d1c05f
 	monitor.o monitor_wrap.o auth-krb5.o \
d1c05f
 	auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \
d1c05f
 	loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \
d1c05f
-	srclimit.o sftp-server.o sftp-common.o \
d1c05f
+	srclimit.o sftp-server.o sftp-common.o sftp-realpath.o slog.o \
d1c05f
 	sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
d1c05f
 	sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \
d1c05f
 	sandbox-solaris.o uidswap.o $(SKOBJS)
d1c05f
@@ -313,7 +313,7 @@ distclean:	regressclean
d1c05f
 	rm -f *.o *.a $(TARGETS) logintest config.cache config.log
d1c05f
 	rm -f *.out core opensshd.init openssh.xml
d1c05f
 	rm -f Makefile buildpkg.sh config.h config.status
d1c05f
-	rm -f survey.sh openbsd-compat/regress/Makefile *~ 
d1c05f
+	rm -f survey.sh openbsd-compat/regress/Makefile *~
d1c05f
 	rm -rf autom4te.cache
d1c05f
 	rm -f regress/check-perm
d1c05f
 	rm -f regress/mkdtemp
Michel Lind 1c324b
--- a/auth.c
Michel Lind 1c324b
+++ b/auth.c
d1c05f
@@ -76,6 +76,7 @@
d1c05f
 #include "ssherr.h"
d1c05f
 #include "compat.h"
d1c05f
 #include "channels.h"
d1c05f
+#include "slog.h"
d1c05f
 
d1c05f
 /* import */
d1c05f
 extern ServerOptions options;
d1c05f
@@ -351,6 +352,7 @@ auth_log(struct ssh *ssh, int authentica
d1c05f
 	    extra != NULL ? extra : "");
d1c05f
 
d1c05f
 	free(extra);
d1c05f
+	slog_set_auth_data(authenticated, method, authctxt->user);
d1c05f
 
d1c05f
 #if defined(CUSTOM_FAILED_LOGIN) || defined(SSH_AUDIT_EVENTS)
d1c05f
 	if (authenticated == 0 && !(authctxt->postponed || partial)) {
d1c05f
@@ -564,6 +566,7 @@ auth_openprincipals(const char *file, st
d1c05f
 struct passwd *
d1c05f
 getpwnamallow(struct ssh *ssh, const char *user)
d1c05f
 {
d1c05f
+	slog_set_user(user);
d1c05f
 #ifdef HAVE_LOGIN_CAP
d1c05f
 	extern login_cap_t *lc;
d1c05f
 #ifdef BSD_AUTH