/* 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 #endif #include #include #include #include #include #include #include #ifdef __MACH__ #include #else #include #endif #include "eyeinterface.h" #include "util.h" #include "eyeconstants.h" #ifndef WIN32 #pragma GCC diagnostic ignored "-Wunused-function" #ifdef __MACH__ #include #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(&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 bytes of 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; }