slog.c (562 lines of code) (raw):
/*
* Copyright 2004-present Facebook. All Rights Reserved.
*/
/* When using slogctxt in any module perform a NULL pointer check */
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <time.h>
#include "includes.h"
#include "slog.h"
#include "log.h"
#include "misc.h"
#include "servconf.h"
#include "xmalloc.h"
typedef struct Structuredlogctxt Structuredlogctxt;
struct Structuredlogctxt { /* items we care about logging */
char server_ip[SLOG_SHORT_STRING_LEN]; // server_ip
int server_port; // server_port
char remote_ip[SLOG_SHORT_STRING_LEN]; // remote_ip
int remote_port; // remote_port
pid_t pam_process_pid; // pam_pid
char session_id[SLOG_STRING_LEN]; // session_id
char method[SLOG_STRING_LEN]; // method
char cert_id[SLOG_STRING_LEN]; // cert_id
unsigned long long cert_serial; // cert_serial
char principal[SLOG_STRING_LEN]; // principal
char user[SLOG_STRING_LEN]; // user
char command[SLOG_LONG_STRING_LEN]; // command
SLOG_SESSION_STATE session_state; // session_state
SLOG_AUTHENTICATED auth_successful; // auth_successful
time_t start_time; // start_time
time_t end_time; // end_time
int duration; // duration
pid_t main_pid; // main_pid
char auth_info[SLOG_MEDIUM_STRING_LEN]; // auth_info
char client_version[SLOG_STRING_LEN]; // client_version
struct timeval auth_start_time;
struct timeval last_partial_auth_time;
struct timeval auth_end_time;
double auth_duration; // auth_duration
double last_partial_auth_duration; // last_partial_auth_duration
};
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";
static const char *auth_duration_token = "auth_duration";
static const char *last_partial_auth_duration_token =
"last_partial_auth_duration";
/* 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_start(void)
{
if (slogctxt != NULL) {
gettimeofday(&slogctxt->auth_start_time, NULL);
}
}
void
slog_set_last_partial_auth_time(void)
{
if (slogctxt != NULL) {
if (gettimeofday(&slogctxt->last_partial_auth_time, NULL) == 0) {
struct timeval res;
timersub(&slogctxt->last_partial_auth_time, &slogctxt->auth_start_time,
&res);
slogctxt->last_partial_auth_duration = res.tv_sec +
(res.tv_usec / 1000000.0);
}
}
}
void
slog_set_auth_end(void)
{
if (slogctxt != NULL) {
if (gettimeofday(&slogctxt->auth_end_time, NULL) == 0) {
struct timeval res;
timersub(&slogctxt->auth_end_time, &slogctxt->auth_start_time, &res);
slogctxt->auth_duration = res.tv_sec + (res.tv_usec / 1000000.0);
}
}
}
void
slog_set_auth_data(int authenticated, const char *method, const char *user)
{
if (slogctxt != NULL) {
slogctxt->auth_successful =
authenticated ? SLOG_AUTHORIZED : SLOG_UNAUTHORIZED;
if (slogctxt->method[0] != '\0') {
strlcat(slogctxt->method, ",", SLOG_SHORT_STRING_LEN);
}
strlcat(slogctxt->method, method, SLOG_SHORT_STRING_LEN);
strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
}
}
void
slog_set_cert_id(const char *id)
{
if (slogctxt != NULL)
strlcpy(slogctxt->cert_id, id, SLOG_STRING_LEN);
}
void
slog_set_cert_serial(unsigned long long serial)
{
if (slogctxt != NULL)
slogctxt->cert_serial = serial;
}
void
slog_set_connection(const char *remote_ip, int remote_port,
const char *server_ip, int server_port, const char *session)
{
if (slogctxt != NULL) {
strlcpy(slogctxt->remote_ip, remote_ip, SLOG_SHORT_STRING_LEN);
slogctxt->remote_port = remote_port;
strlcpy(slogctxt->server_ip, server_ip, SLOG_SHORT_STRING_LEN);
slogctxt->server_port = server_port;
strlcpy(slogctxt->session_id, session, SLOG_STRING_LEN);
slogctxt->start_time = time(NULL);
slogctxt->main_pid = getpid();
}
}
void
slog_set_client_version(const char *version)
{
if (slogctxt != NULL) {
if (strlen(version) < SLOG_STRING_LEN)
strlcpy(slogctxt->client_version, version, SLOG_STRING_LEN);
else {
// version can be up to 256 bytes, truncate to 95 and add ' ...'
// which will fit in SLOG_STRING_LEN
snprintf(slogctxt->client_version, SLOG_STRING_LEN, "%.95s ...", version);
}
}
}
void
slog_set_command(const char *command)
{
if (slogctxt != NULL) {
if (strlen(command) < SLOG_LONG_STRING_LEN)
strlcpy(slogctxt->command, command, SLOG_LONG_STRING_LEN);
else {
// If command is longer than allowed we truncate it to
// 1995 (SLOG_LONG_STRING_LEN - 5) characters and add ' ...\0' to
// the end of the command.
snprintf(slogctxt->command, SLOG_LONG_STRING_LEN, "%.1995s ...", command);
}
}
}
void
slog_set_principal(const char *principal)
{
if (slogctxt != NULL)
strlcpy(slogctxt->principal, principal, SLOG_STRING_LEN);
}
void
slog_set_user(const char *user)
{
if (slogctxt != NULL)
strlcpy(slogctxt->user, user, SLOG_STRING_LEN);
}
void
slog_set_auth_info(const char *auth_info)
{
if (slogctxt != NULL)
strlcpy(slogctxt->auth_info, auth_info, SLOG_MEDIUM_STRING_LEN);
}
void
slog_exit_handler(void)
{
/* to prevent duplicate logging we only log based on the pid set */
if (slogctxt != NULL) {
if (slogctxt->server_ip[0] == 0)
return; // not initialized
if (slogctxt->main_pid != getpid())
return; // not main process
if (slogctxt->session_state == SLOG_SESSION_INIT)
slog_log();
else {
slogctxt->session_state = SLOG_SESSION_CLOSED;
slogctxt->end_time = time(NULL);
slogctxt->duration = slogctxt->end_time - slogctxt->start_time;
slog_log();
slog_cleanup();
}
}
}
void
slog_log_session(void)
{
if (slogctxt != NULL) {
slogctxt->session_state = SLOG_SESSION_OPEN;
slog_log();
}
}
/* private function scope begin */
static void
slog_log(void)
{
char *buffer = xmalloc(SLOG_BUF_SIZE);
if (buffer == NULL)
return;
memset(buffer, 0, SLOG_BUF_SIZE);
if (options.num_log_format_keys > 0
&& slogctxt != NULL
&& slogctxt->server_ip[0] != 0
&& slogctxt->user[0] != 0) {
slog_generate_auth_payload(buffer);
do_log_slog_payload(buffer);
}
free(buffer);
}
static void
slog_cleanup(void)
{
// Reset the log context values
if (slogctxt != NULL) {
memset(slogctxt, 0, sizeof(Structuredlogctxt));
slogctxt->session_state = SLOG_SESSION_INIT;
slogctxt->auth_successful = SLOG_UNAUTHORIZED;
}
}
/* We use debug3 since the debug is very noisy */
static void
slog_generate_auth_payload(char *buf)
{
if (buf == NULL)
return;
// Create large buffer so don't risk overflow
char *safe = xmalloc(SLOG_BUF_SIZE);
memset(safe, 0, SLOG_BUF_SIZE);
if (safe == NULL)
return;
int i;
size_t remaining;
int json = options.log_format_json;
int keys = options.num_log_format_keys;
int truncated = 0;
char *tmpbuf = buf;
char *key;
debug3("JSON format is %d with %d tokens.", json, keys);
if (options.log_format_prefix != NULL
&& strlen(options.log_format_prefix) < SLOG_BUF_CALC_SIZE-1) {
tmpbuf += snprintf(tmpbuf, SLOG_BUF_CALC_SIZE,
"%s ", options.log_format_prefix);
}
*tmpbuf++ = (json) ? '{' : '[';
debug3("current buffer after prefix: %s", buf);
// Loop through the keys filling out the output string
for (i = 0; i < keys; i++) {
safe[0] = 0; // clear safe string
key = options.log_format_keys[i];
remaining = SLOG_BUF_CALC_SIZE - (tmpbuf - buf);
if (key == NULL)
continue; // Shouldn't happen but if null go to next key
slog_get_safe_from_token(safe, key);
debug3("token: %s, value: %s", key, safe);
if (json) {
if (*safe == '\0')
continue; // No value since we are using key pairs skip
if (remaining <= SLOG_JSON_FORMAT_SIZE + strlen(key) + strlen(safe)) {
debug("Log would exceed buffer size %u, %zu, %zu at key: %s",
(unsigned int)remaining, strlen(key), strlen(safe), key);
truncated = 1;
break;
}
tmpbuf += snprintf(tmpbuf, remaining, "%s\"%s\": %s",
i > 0 ? ", " : "", key, safe);
} else {
if (*safe == '\0')
strlcpy(safe, "\"\"", SLOG_SHORT_STRING_LEN);
if (remaining < SLOG_JSON_FORMAT_SIZE + strlen(safe)) {
debug("Log would exceed remaining buffer size %d, %zu, at key: %s",
(unsigned int)remaining, strlen(safe), key);
truncated = 1;
break;
}
tmpbuf += snprintf(tmpbuf, remaining, "%s%s", i > 0 ? ", " : "", safe);
}
debug3("current buffer after token: %s", buf);
debug3("end of loop key: %s, %d out of %d keys", key, i + 1, keys);
}
// Close the log string. If truncated set truncated message and close string
if (truncated == 1)
strlcpy(tmpbuf, json ? SLOG_TRUNCATED_MESSAGE_JSON :
SLOG_TRUNCATED_MESSAGE_ARRAY, SLOG_TRUNCATED_SIZE);
else {
*tmpbuf++ = (json) ? '}' : ']';
}
free(safe);
}
// buffer size is input string * 2 +1
static void
slog_escape_value(char *output, char *input, size_t buffer_size)
{
int i;
buffer_size -= 2;
if (input != NULL) {
int input_size = strlen(input);
char *temp = output;
*temp++ = '"';
buffer_size--;
for (i = 0; i < input_size && buffer_size > 0; i++) {
switch(input[i]) {
// characters escaped are the same as folly::json::escapeString
case 27: // <escape> ascii control character
if (buffer_size > 6) {
*temp++ = '\\';
*temp++ = 'u';
*temp++ = '0';
*temp++ = '0';
*temp++ = '1';
*temp++ = 'b';
buffer_size -= 6;
}
case '\b':
if (buffer_size > 1) {
*temp++ = '\\';
*temp++ = 'b';
buffer_size -= 2;
}
break;
case '\f':
if (buffer_size > 1) {
*temp++ = '\\';
*temp++ = 'f';
buffer_size -= 2;
}
break;
case '\n':
if (buffer_size > 1) {
*temp++ = '\\';
*temp++ = 'n';
buffer_size -= 2;
}
break;
case '\r':
if (buffer_size > 1) {
*temp++ = '\\';
*temp++ = 'r';
buffer_size -= 2;
}
break;
case '\t':
if (buffer_size > 1) {
*temp++ = '\\';
*temp++ = 't';
buffer_size -= 2;
}
break;
case '\"':
case '\\':
if (buffer_size > 1) {
*temp++ = '\\';
buffer_size--;
}
default: // Non-escape char
*temp++ = input[i];
buffer_size--;
}
}
*temp++ = '"';
*temp++ = '\0';
}
}
static void
slog_get_safe_from_token(char *output, const char *token)
{
if (output == NULL || token == NULL || slogctxt == NULL)
return;
if (strcmp(token, server_ip_token) == 0) {
if (slogctxt->server_ip[0] != 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
slogctxt->server_ip);
}
return;
}
if (strcmp(token, server_port_token) == 0) {
if (slogctxt->server_port > 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
slogctxt->server_port);
}
return;
}
if (strcmp(token, remote_ip_token) == 0) {
if (slogctxt->remote_ip[0] != 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"",
slogctxt->remote_ip);
}
return;
}
if (strcmp(token, remote_port_token) == 0) {
if (slogctxt->remote_port > 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"",
slogctxt->remote_port);
}
return;
}
if (strcmp(token, pam_pid_token) == 0) {
if (slogctxt->pam_process_pid > 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"",
(long)slogctxt->pam_process_pid);
}
return;
}
if (strcmp(token, process_pid_token) == 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", (long)getpid());
return;
}
if (strcmp(token, session_id_token) == 0) {
if (slogctxt->session_id[0] != 0) {
snprintf(output, SLOG_STRING_LEN, "\"%s\"",
slogctxt->session_id);
}
return;
}
if (strcmp(token, method_token) == 0) {
if (slogctxt->method[0] != 0) {
snprintf(output, SLOG_STRING_LEN, "\"%s\"",
slogctxt->method);
}
return;
}
// Arbitrary input
if (strcmp(token, cert_id_token) == 0) {
if (slogctxt->cert_id[0] != 0) {
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) {
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, auth_duration_token) == 0 &&
slogctxt->auth_end_time.tv_sec > 0) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%f\"", slogctxt->auth_duration);
return;
}
if (strcmp(token, last_partial_auth_duration_token) == 0 &&
timerisset(&slogctxt->last_partial_auth_time)) {
snprintf(output, SLOG_SHORT_STRING_LEN, "\"%f\"",
slogctxt->last_partial_auth_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
}
}