Sleds/K2Client/util.cpp

754 lines
17 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
/*
Copyright (c) 2014 IOnU Security Inc. All rights reserved
Created April 2014 by Kendrick Webster
K2Client/util.c - implementation of util.h
*/
#ifdef WIN32
#include <Windows.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#ifdef __MACH__
#include <mach/mach_time.h>
#else
#include <time.h>
#endif
#include "eyeinterface.h"
#include "util.h"
#include "eyeconstants.h"
#ifndef WIN32
#pragma GCC diagnostic ignored "-Wunused-function"
#ifdef __MACH__
#include <sys/syslimits.h>
#define MAX_PATH PATH_MAX
#else
#define MAX_PATH 128
#endif
#endif
/* =============================== Logging ============================== */
static log_level_t log_filter_level = LOG_VERBOSE;
static int is_log_open = 0;
static int is_log_level_set = 0;
static int is_config_json_parsed = 0;
static void expand_log_file_path(const char* name, char* path_and_name, size_t path_and_name_size)
{
#ifdef WIN32
char tmpPathBuf[MAX_PATH + 1] = {0};
::GetTempPath(MAX_PATH, reinterpret_cast<LPSTR>(&tmpPathBuf));
snprintf(path_and_name, path_and_name_size, "%s%s", tmpPathBuf, name);
path_and_name[path_and_name_size - 1] = 0;
#else // not WIN32
snprintf(path_and_name, path_and_name_size, "%s%s", LOG_FILE_PATH, name);
#endif // not WIN32
}
static void open_log_file(void)
{
char log_file[MAX_PATH + 1];
expand_log_file_path(LOG_FILE_NAME, log_file, sizeof(log_file));
/* TEB 1.2.7 12/11/2014 - log to CONSOLE because only one log config is supported via these api's
ionu_configfilelog(LOG_SHORT_IDENTIFIER, "", IONU_VERBOSE, log_file);
*/
ionu_configlog(LOG_SHORT_IDENTIFIER, SL_EYE_TYPE, IONU_VERBOSE, IONU_CONSOLE);
is_log_open = 1;
}
static void parse_sl_config_json(void);
void initialize_logging(void)
{
if (!(is_log_level_set || is_config_json_parsed))
{
parse_sl_config_json();
}
#if DEBUG
#else // not DEBUG
open_log_file();
#endif // not DEBUG
}
void finalize_logging(void)
{
#if DEBUG
#else // not DEBUG
ionu_closelog();
is_log_open = 0;
#endif // not DEBUG
}
void set_log_level(log_level_t level)
{
log_filter_level = level;
is_log_level_set = 1;
}
static void log_message(log_level_t lev, const char* msg)
{
#if DEBUG
#else // not DEBUG
if (is_log_open)
{
switch (lev)
{
case LOG_FATAL: ionu_log(IONU_FATAL, msg); break;
case LOG_ERROR: ionu_log(IONU_ERROR, msg); break;
case LOG_WARNING: ionu_log(IONU_WARN, msg); break;
case LOG_INFORMATIONAL: ionu_log(IONU_INFO, msg); break;
case LOG_DEBUG: ionu_log(IONU_DEBUG, msg); break;
case LOG_VERBOSE: ionu_log(IONU_VERBOSE, msg); break;
}
}
#endif // not DEBUG
if (!is_log_open) // handles DEBUG, also handles messages generated at startup before opening log (i.e. parsing config json)
{
switch (lev)
{
case LOG_FATAL: printf("K2 fatal: %s\n", msg); break;
case LOG_ERROR: printf("K2 error: %s\n", msg); break;
case LOG_WARNING: printf("K2 warn: %s\n", msg); break;
case LOG_INFORMATIONAL: printf("K2 info: %s\n", msg); break;
case LOG_DEBUG: printf("K2 debug: %s\n", msg); break;
case LOG_VERBOSE: printf("K2 verbose: %s\n", msg); break;
}
}
}
static void log_printf_va(log_level_t lev, const char* fmt, va_list marker)
{
char buf[256];
vsnprintf(buf, sizeof(buf), fmt, marker);
#ifdef WIN32
buf[sizeof(buf) - 1] = 0;
#endif
log_message(lev, buf);
}
void log_printf(log_level_t lev, const char* fmt, ...)
{
va_list marker;
va_start(marker, fmt);
if (lev <= log_filter_level)
{
log_printf_va(lev, fmt, marker);
}
va_end(marker);
}
/* ==================== configuration settings for logging verbosity ==================== */
/*
parse_sl_config_json() and helpers
Parses /sequencelogic/config/{env}.conf.json to extract logging verbosity.
This is not a full json parser. It looks for a specific 2nd-level object containing
a specific item while ignoring all other objects.
Example json format:
// comments
{
"other": {
"dontCare": "some value"
},
"K2Client": {
"dontCare": "some value",
"logLevel": "informational", // this is the only thing parsed
"dontCare2": "some value"
},
"other2": {
"dontCare2":
[
{
"dontCare3": "some value"
},
"dontCare4",
"dontCare5"
]
}
}
Example to cut-and-pase into a config file:
"K2Client": {
"logLevel": "debug" // choices: fatal, error, warning, informational, debug, verbose
},
*/
#define JSON_STRING_BUFSIZE 16 // sufficient for "K2Client", "logLevel", and "informational"
static struct
{
char fname[256];
FILE *f;
char c;
int line;
int column;
int depth;
char s[JSON_STRING_BUFSIZE];
int is_error;
int is_this_k2cli;
int is_this_loglevel;
int parent_k2cli_depth;
}
json_state;
// advances char pos (including line and column numbers)
static void json_next(void)
{
int c;
if (json_state.c)
{
c = fgetc(json_state.f);
c = (EOF == c) ? 0 : c;
json_state.c = (char)c;
switch (json_state.c)
{
case '\r':
json_state.column = 0;
break;
case '\n':
json_state.column = 0;
json_state.line++;
break;
default:
json_state.column++;
break;
}
}
}
// compares c to current char,
// returns 1 and advances char pos if matched
// returns 0 and logs error if mismatched
static int json_check(char c)
{
if (!json_state.is_error)
{
if (json_state.c != c)
{
log_error("Expected '%c', found '%c' at line %d char %d of %s", c, json_state.c,
json_state.line, json_state.column, json_state.fname);
json_state.c = 0;
json_state.is_error = 1;
return 0;
}
else
{
json_next();
return 1;
}
}
return 0;
}
// alias, used to report errors
#define json_expected json_check
// compares string, returns 1 if matched
static int json_check_str(const char* s)
{
char c = *s++;
while (c)
{
if (!json_check(c))
{
return 0;
}
c = *s++;
}
return 1;
}
static void json_comment(void)
{
json_check('/');
json_check('/');
while (json_state.c)
{
switch (json_state.c)
{
case '\r':
case '\n':
return;
default:
json_next();
break;
}
}
}
static void json_white(void)
{
while (json_state.c)
{
switch (json_state.c)
{
case ' ':
case '\r':
case '\n':
case '\t':
json_next();
break;
case '/':
json_comment();
break;
default:
return;
}
}
}
static int is_string(const char* s) // case insensitive
{
const char *p = json_state.s;
while (*s)
{
if (toupper(*s++) != toupper(*p++))
{
return 0;
}
}
return *p ? 0 : 1;
}
static void on_json_loglevel(void)
{
#if DEBUG
log_info("/sequencelogic/config json logLevel = \"%s\" at line %d char %d of %s", json_state.s, json_state.line, json_state.column, json_state.fname);
#endif
if (is_string("FATAL"))
{
log_filter_level = LOG_FATAL;
}
else if (is_string("ERROR"))
{
log_filter_level = LOG_ERROR;
}
else if (is_string("WARNING"))
{
log_filter_level = LOG_WARNING;
}
else if (is_string("INFORMATIONAL"))
{
log_filter_level = LOG_INFORMATIONAL;
}
else if (is_string("DEBUG"))
{
log_filter_level = LOG_DEBUG;
}
else if (is_string("VERBOSE"))
{
log_filter_level = LOG_VERBOSE;
}
else
{
log_error("logLevel \"%s\" not recognized, choices: fatal, error, warning, informational, debug, verbose", json_state.s);
}
}
static void on_json_string(void)
{
if (json_state.is_this_loglevel && (1 == json_state.parent_k2cli_depth) && (2 == json_state.depth))
{
on_json_loglevel();
}
json_state.is_this_k2cli = is_string("K2CLIENT");
json_state.is_this_loglevel = is_string("LOGLEVEL");
}
static char hex_to_bin(char c)
{
return (c <= '9') ? (c - '0') : ((c <= 'F') ? (10 + c - 'A') : (10 + c - 'a'));
}
static char json_string_u_escape(void)
{
int i = 4;
char c = 0;
do
{
json_next();
if (isxdigit(json_state.c))
{
c <<= 4;
c |= hex_to_bin(json_state.c);
}
else
{
json_expected('0');
return 0;
}
}
while (--i);
return c;
}
static void json_string(void)
{
char *p = json_state.s;
json_check('"');
while (json_state.c)
{
switch (json_state.c)
{
default:
put_c:
if (p < (json_state.s + JSON_STRING_BUFSIZE - 1))
{
*p++ = json_state.c;
}
break;
case '"':
*p = 0;
json_next();
on_json_string();
return;
case '\\':
json_next();
switch (json_state.c)
{
case '"': goto put_c;
case '\\': goto put_c;
case '/': goto put_c;
case 'b': json_state.c = '\b'; goto put_c;
case 'f': json_state.c = '\f'; goto put_c;
case 'n': json_state.c = '\n'; goto put_c;
case 'r': json_state.c = '\r'; goto put_c;
case 't': json_state.c = '\t'; goto put_c;
case 'u': json_state.c = json_string_u_escape(); goto put_c;
default:
json_expected('n');
return;
}
break;
}
json_next();
}
}
static void json_value(void);
static void json_object(void)
{
json_state.parent_k2cli_depth = json_state.parent_k2cli_depth ? (json_state.parent_k2cli_depth + 1) : json_state.is_this_k2cli;
json_state.depth++;
json_check('{');
json_white();
while (json_state.c)
{
if ('}' == json_state.c)
{
json_next();
json_state.parent_k2cli_depth = json_state.parent_k2cli_depth ? (json_state.parent_k2cli_depth - 1) : 0;
json_state.depth--;
return;
}
next_pair:
json_string();
json_white();
json_check(':');
json_white();
json_value();
json_white();
switch (json_state.c)
{
case ',':
json_next();
json_white();
goto next_pair;
case '}':
break;
default:
json_expected('}');
return;
}
}
json_expected('}');
}
static void json_array(void)
{
json_check('[');
json_white();
while (json_state.c)
{
if (']' == json_state.c)
{
json_next();
return;
}
next_value:
json_value();
json_white();
switch (json_state.c)
{
case ',':
json_next();
json_white();
goto next_value;
case ']':
break;
default:
json_expected(']');
return;
}
}
json_expected(']');
}
static int json_one_or_more_digits(void)
{
if (!isdigit(json_state.c))
{
json_expected('0');
return 0;
}
do
{
json_next();
}
while (isdigit(json_state.c));
return 1;
}
static void json_number(void)
{
if ('-' == json_state.c)
{
json_next();
}
if ('0' == json_state.c)
{
json_next();
}
else if (!json_one_or_more_digits())
{
return;
}
if ('.' == json_state.c)
{
json_next();
if (!json_one_or_more_digits())
{
return;
}
}
if ('E' == toupper(json_state.c))
{
json_next();
if ('+' == json_state.c)
{
json_next();
}
else if ('-' == json_state.c)
{
json_next();
}
json_one_or_more_digits();
}
}
static void json_value(void)
{
if (isdigit(json_state.c))
{
json_number();
}
else
{
switch (json_state.c)
{
case '"': json_string(); break;
case '-': json_number(); break;
case '{': json_object(); break;
case '[': json_array(); break;
case 't': json_check_str("true"); break;
case 'f': json_check_str("false"); break;
case 'n': json_check_str("null"); break;
default:
json_expected('0');
break;
}
}
}
static FILE* open_sequencelogic_config_file(void)
{
FILE *f;
const char *sequencelogicname = getenv(SL_NAME_ENVIRONMENT_KEY);
if (NULL == sequencelogicname)
{
log_info("%s not found in environment", SL_NAME_ENVIRONMENT_KEY);
log_info("Unable to find and read the SL configuration json file");
log_info("Log verbosity thus remains at the hard-coded default");
return NULL;
}
#ifdef WIN32
_snprintf(json_state.fname, sizeof(json_state.fname), "%s%s%s", CONFIG_JSON_PATH_PREFIX, sequencelogicname, CONFIG_JSON_SUFFIX);
json_state.fname[sizeof(json_state.fname) - 1] = 0;
#else
snprintf(json_state.fname, sizeof(json_state.fname), "%s%s%s", CONFIG_JSON_PATH_PREFIX, sequencelogicname, CONFIG_JSON_SUFFIX);
#endif
f = fopen(json_state.fname, "r");
if (NULL == f)
{
log_info("Unable to open configuration file \"%s\":", json_state.fname);
log_info("%s", strerror(errno));
log_info("Log verbosity thus remains at the hard-coded default");
}
return f;
}
static void parse_sl_config_json(void)
{
is_config_json_parsed = 1;
json_state.f = open_sequencelogic_config_file();
if (NULL == json_state.f)
{
return;
}
json_state.line = 1;
json_state.column = 0;
json_state.depth = 0;
json_state.is_error = 0;
json_state.is_this_k2cli = 0;
json_state.is_this_loglevel = 0;
json_state.parent_k2cli_depth = 0;
json_state.c = 1;
json_next();
json_white();
while (json_state.c)
{
switch (json_state.c)
{
case '{':
json_object();
json_white();
break;
default:
json_expected('{'); // expecting a top-level object for sequencelogic config json
fclose(json_state.f);
return;
}
}
fclose(json_state.f);
}
/* =============================== 'Random' number source =============================== */
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX pthread_mutex_lock(&mutex);
#define UNLOCK_MUTEX pthread_mutex_unlock(&mutex);
static sc_hash_state_t rng_entropy_pool; /* random number generator entropy pool */
/* hash an entropy source (high-resolution clock, etc.) into the pool */
void seed_random(void)
{
#define RNG_ADD_HASH(t) do {LOCK_MUTEX sc_hash_vector(&rng_entropy_pool, (uint8_t*)&t, sizeof(t)); UNLOCK_MUTEX} while(0)
#ifdef __MACH__
uint64_t t = mach_absolute_time();
RNG_ADD_HASH(t);
#else
#ifdef WIN32
LARGE_INTEGER t;
QueryPerformanceCounter(&t);
RNG_ADD_HASH(t);
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
RNG_ADD_HASH(t);
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t);
RNG_ADD_HASH(t);
#endif
#endif
}
/* hash a buffer into the RNG entropy pool */
void seed_random_with_bytes(void* buf, size_t len)
{
LOCK_MUTEX
{
sc_hash_vector(&rng_entropy_pool, buf, len);
}
UNLOCK_MUTEX
}
/* returns a random number between 0 and (modulus - 1) */
unsigned int get_random(unsigned int modulus)
{
unsigned int r = 0;
if (modulus > 0)
{
LOCK_MUTEX
{
sc_get_vector(&rng_entropy_pool, &r, sizeof(r));
}
UNLOCK_MUTEX
/*
The modulo operation below introduces a small bias, but it
is insignificant for this application (modulus almost always
below 100, bias only affects server load balancing).
*/
r %= modulus;
}
return r;
}
/* fills <len> bytes of <buf> with randomness */
void get_random_bytes(void* buf, size_t len)
{
LOCK_MUTEX
{
sc_get_vector(&rng_entropy_pool, buf, len);
}
UNLOCK_MUTEX
}
/* =============================== Misc ============================== */
/* returns TRUE if string was truncated */
int safe_strcpy(char* d, unsigned int n, const char* s)
{
if (0 < n)
{
while (n--)
{
if (0 == (*(d++) = *(s++)))
{
return FALSE;
}
}
*(--d) = 0;
}
return TRUE;
}