You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pEpEngine/src/platform_unix.c

904 lines
26 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/** @file */
/** @brief File description for doxygen missing. FIXME */
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#define _POSIX_C_SOURCE 200809L
#ifdef ANDROID
#ifndef __LP64__
#include <time64.h>
#endif
#endif
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <glob.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <regex.h>
#include "pEpEngine.h" /* For PEP_STATUS */
#include "platform_unix.h"
#include "dynamic_api.h"
#define MAX_PATH 1024
#ifndef LOCAL_DB_FILENAME
#define OLD_LOCAL_DB_FILENAME ".pEp_management.db"
#define OLD_KEYS_DB_FILENAME ".pEp_keys.db"
#define LOCAL_DB_FILENAME "management.db"
#define KEYS_DB_FILENAME "keys.db"
#endif
#define SYSTEM_DB_FILENAME "system.db"
#ifdef ANDROID
#include <uuid.h>
/* FIXME : timegm will miss when linking for x86_64 on android, when supported */
#ifndef __LP64__
time_t timegm(struct tm* const t) {
static const time_t kTimeMax = ~(1L << (sizeof(time_t) * CHAR_BIT - 1));
static const time_t kTimeMin = (1L << (sizeof(time_t) * CHAR_BIT - 1));
time64_t result = timegm64(t);
if (result < kTimeMin || result > kTimeMax)
return -1;
return result;
}
#endif
char *stpncpy(char *dst, const char *src, size_t n)
{
if (n != 0) {
char *d = dst;
const char *s = src;
dst = &dst[n];
do {
if ((*d++ = *s++) == 0) {
dst = d - 1;
/* NUL pad the remaining n-1 bytes */
while (--n != 0)
*d++ = 0;
break;
}
} while (--n != 0);
}
return (dst);
}
char *stpcpy(char *dst, const char *src)
{
for (;; ++dst, ++src) {
*dst = *src;
if (*dst == 0)
break;
}
return dst;
}
/*
long int random(void)
{
static bool seeded = false;
static unsigned short xsubi[3];
if(!seeded)
{
const long long t = (long long)time(NULL);
xsubi[0] = (unsigned short)t;
xsubi[1] = (unsigned short)(t>>16);
xsubi[2] = (unsigned short)(t>>32);
seeded = true;
}
return nrand48(xsubi);
} */
/* This is a non-caching function: see the comments in "Internal path caching
functionality" below. */
static char *_android_system_db(void)
{
char *buffer = malloc (MAX_PATH);
if (buffer == NULL)
return NULL;
char *tw_env;
if(tw_env = getenv("TRUSTWORDS")){
char *p = stpncpy(buffer, tw_env, MAX_PATH);
ssize_t len = MAX_PATH - (p - buffer) - 2;
if (len < strlen(SYSTEM_DB_FILENAME)) {
assert(0);
return NULL;
}
*p++ = '/';
strncpy(p, SYSTEM_DB_FILENAME, len);
}else{
return NULL;
}
return buffer;
}
void uuid_generate_random(pEpUUID out)
{
uuid_t *uuid;
uuid_rc_t rc_create;
size_t size = sizeof(uuid_string_t);
void *_out = out;
if ((rc_create = uuid_create(&uuid)) != UUID_RC_OK ||
uuid_make(uuid, UUID_MAKE_V1) != UUID_RC_OK ||
uuid_export(uuid, UUID_FMT_BIN, &_out, &size) != UUID_RC_OK)
{
memset(out, 0, sizeof(pEpUUID));
}
if (rc_create == UUID_RC_OK)
{
uuid_destroy(uuid);
}
}
void uuid_unparse_upper(pEpUUID uu, uuid_string_t out)
{
uuid_t *uuid;
uuid_rc_t rc_create;
size_t size = sizeof(uuid_string_t);
void *_out = out;
if ((rc_create = uuid_create(&uuid)) != UUID_RC_OK ||
uuid_import(uuid, UUID_FMT_BIN, uu, sizeof(pEpUUID)) != UUID_RC_OK ||
uuid_export(uuid, UUID_FMT_STR, &_out, &size) != UUID_RC_OK)
{
memset(out, 0, sizeof(uuid_string_t));
}
else
{
out[sizeof(uuid_string_t) - 1] = 0;
}
if (rc_create == UUID_RC_OK)
{
uuid_destroy(uuid);
}
}
#endif
#if !defined(BSD) && !defined(__APPLE__)
size_t strlcpy(char* dst, const char* src, size_t size) {
size_t retval = strlen(src);
size_t size_to_copy = (retval < size ? retval : size - 1);
// strlcpy doc says src and dst not allowed to overlap, as
// it's undefined. So this is acceptable:
memcpy((void*)dst, (void*)src, size_to_copy); // no defined error return, but strcpy doesn't either
dst[size_to_copy] = '\0';
return retval;
}
size_t strlcat(char* dst, const char* src, size_t size) {
size_t start_len = strnlen(dst, size);
if (start_len == size)
return size; // no copy, no null termination in size bytes, according to spec
size_t add_len = strlen(src);
size_t retval = start_len + add_len;
size_t size_to_copy = (retval < size ? add_len : (size - start_len) - 1);
// strlcat doc says src and dst not allowed to overlap, as
// it's undefined. So this is acceptable:
memcpy((void*)(dst + start_len), (void*)src, size_to_copy); // no defined error return, but strcpy doesn't either
dst[start_len + size_to_copy] = '\0';
return retval;
}
char *strnstr(const char *big, const char *little, size_t len) {
if (big == NULL || little == NULL)
return NULL;
if (*little == '\0')
return (char*)big;
const char* curr_big = big;
size_t little_len = strlen(little);
size_t remaining = len;
const char* retval = NULL;
for (remaining = len; remaining >= little_len && *curr_big != '\0'; remaining--, curr_big++) {
// find first-char match
if (*curr_big != *little) {
continue;
}
retval = curr_big;
const char* inner_big = retval + 1;
const char* curr_little = little + 1;
int j;
for (j = 1; j < little_len; j++, inner_big++, curr_little++) {
if (*inner_big != *curr_little) {
retval = NULL;
break;
}
}
if (retval)
break;
}
return (char*)retval;
}
// #ifdef USE_NETPGP
// // FIXME: This may cause problems - this is a quick compatibility fix for netpgp code
// int regnexec(const regex_t* preg, const char* string,
// size_t len, size_t nmatch, regmatch_t pmatch[], int eflags) {
// return regexec(preg, string, nmatch, pmatch, eflags);
// }
// #endif
#endif
/**
* @internal
*
* <!-- _stradd() -->
*
* @brief TODO
*
* @param[in] **first char
* @param[in] *second constchar
*
*/
static char *_stradd(char **first, const char *second)
{
assert(first && *first && second);
if (!(first && *first && second))
return NULL;
size_t len1 = strlen(*first);
size_t len2 = strlen(second);
size_t size = len1 + len2 + 1;
char *_first = realloc(*first, size);
assert(_first);
if (!_first)
return NULL;
*first = _first;
strlcat(*first, second, size);
return *first;
}
/**
* @internal
*
* <!-- _empty() -->
*
* @brief TODO
*
* @param[in] **p char
*
*/
static void _empty(char **p)
{
free(*p);
*p = NULL;
}
/**
* @internal
*
* <!-- _move() -->
*
* @brief TODO
*
* @param[in] *o constchar
* @param[in] *ext constchar
* @param[in] *n constchar
*
*/
static void _move(const char *o, const char *ext, const char *n)
{
assert(o && ext && n);
if (!(o && ext && n))
return;
char *_old = strdup(o);
assert(_old);
if (!_old)
return;
char *r = _stradd(&_old, ext);
if (!r) {
free(_old);
return;
}
char *_new = strdup(n);
assert(_new);
if (!_new) {
free(_old);
return;
}
r = _stradd(&_new, ext);
if (r)
rename(_old, _new);
free(_old);
free(_new);
}
/**
* @internal
*
* <!-- _strdup_or_NULL() -->
*
* @brief Return a malloc-allocated copy of the given string, or (this
* is the added functionality with respect to the standard
* strdup) a malloc-allocated copy of "" if the argument is
* NULL.
*
* @param[in] *original constchar
* @retval NULL out of memory
* @retval non-NULL malloc-allocated buffer
*/
static char *_strdup_or_NULL(const char *original)
{
if (original == NULL)
original = "";
return strdup (original);
}
/*
* Environment variable expansion
* **********************************************************************
*/
/* The state of a DFA implementing variable recognition in _expand_variables ,
below. */
enum _expand_variable_state {
_expand_variable_state_non_variable,
_expand_variable_state_after_dollar,
_expand_variable_state_after_backslash,
_expand_variable_state_in_variable
};
/**
* @internal
*
* <!-- _expand_variables() -->
*
* @brief Set a malloc-allocated '\0'-terminated string which is
* a copy of the argument with shell variables expanded, where
* variable references use Unix shell-style syntax $VARIABLE.
* Notice that the alternative syntax ${VARIABLE} is not
* supported.
* See [FIXME: deployment-engineer documentation].
*
* @param[in] string_with_variables char *
* @param[out] copy_with_variables_expanded char **
* @retval PEP_STATUS_OK success
* @retval PEP_UNBOUND_ENVIRONMENT_VARIABLE unknown variable referenced
* @retval PEP_PATH_SYNTAX_ERROR invalid syntax in argument
* @retval PEP_OUT_OF_MEMORY out of memory
*
*/
static PEP_STATUS _expand_variables(char **out,
const char *string_with_variables)
{
PEP_STATUS res = PEP_STATUS_OK;
size_t in_length = strlen(string_with_variables);
const char *variable_name_beginning; /* This points within the input. */
char *variable_name_copy = NULL /* we free on error. */;
size_t allocated_size
#ifdef NDEBUG
= 1024;
#else
= 1 /* Notice that 0 is incorrect: this grows by doubling. */;
#endif // #ifdef NDEBUG
int out_index = 0; /* The out index is also the used out size */
const char *in = string_with_variables;
/* In the pEp engine we adopt the convention of "" behaving the same as
NULL. Notice that we never free this, so it is not a problem if this
string is not malloc-allocated. */
if (in == NULL)
in = "";
/* We free on error. */
* out = NULL ;
/* Recognise a variable according to POSIX syntax which, luckily for us,
only allows for letters, digits and underscores -- The first character
may not be a digit... */
#define VALID_FIRST_CHARACTER_FOR_VARIABLE(c) \
( ((c) >= 'a' && (c) <= 'z') \
|| ((c) >= 'A' && (c) <= 'Z') \
|| ((c) == '_'))
/* ...But characters after the first may be. */
#define VALID_NON_FIRST_CHARACTER_FOR_VARIABLE(c) \
( VALID_FIRST_CHARACTER_FOR_VARIABLE(c) \
|| ((c) >= '0' && (c) <= '9'))
/* Append the char argument to the result string, automatically resizing it
if needed. */
#define EMIT_CHAR(c) \
do \
{ \
if (out_index == allocated_size) { \
allocated_size *= 2; \
/*fprintf (stderr, "ALLOCATED SIZE: %i -> %i\n", (int) allocated_size / 2, (int) allocated_size);*/\
* out = realloc (* out, allocated_size); \
if (* out == NULL) \
FATAL (PEP_OUT_OF_MEMORY, \
"cannot grow buffer"); \
} \
(* out) [out_index] = (c); \
out_index ++; \
} \
while (false)
/* Append the string argument to the output string, automatically resizing
it as needed. */
#define EMIT_STRING(s) \
do { \
const char *p; \
for (p = (s); (* p) != '\0'; p ++) \
EMIT_CHAR (* p); \
} while (false)
/* Emit the expansion of the environment variable whose name is delimited on
the left by variable_name_beginning and on the right by the character
coming right *before* in. Fail fatally if the variable is unbound.
The expansion is emitted by appending to the result string, automatically
resizing it as needed. */
#define EMIT_CURRENT_VARIABLE \
do { \
const char *variable_past_end = in; \
size_t variable_name_length \
= variable_past_end - variable_name_beginning; \
strcpy (variable_name_copy, variable_name_beginning); \
variable_name_copy [variable_name_length] = '\0'; \
const char *variable_value = getenv (variable_name_copy); \
if (variable_value == NULL) \
FATAL_NAME (PEP_UNBOUND_ENVIRONMENT_VARIABLE, \
"unbound variable", variable_name_copy); \
EMIT_STRING (variable_value); \
} while (false)
#define FATAL(code, message) \
do { res = (code); goto failure; } while (false)
#define FATAL_NAME(code, message, name) \
FATAL((code), (message))
/* We can allocate buffers, now that we have FATAL. */
if ((variable_name_copy
= malloc (in_length + 1 /* a safe upper bound for a sub-string. */))
== NULL)
FATAL (PEP_OUT_OF_MEMORY, "out of mmeory");
if (((* out) = malloc (allocated_size)) == NULL)
FATAL (PEP_OUT_OF_MEMORY, "out of memory");
/* This logic implements a DFA. */
enum _expand_variable_state s = _expand_variable_state_non_variable;
char c;
while (true) {
c = * in;
switch (s) {
case _expand_variable_state_non_variable:
if (c == '$') {
variable_name_beginning = in + 1;
s = _expand_variable_state_after_dollar;
}
else if (c == '\\')
s = _expand_variable_state_after_backslash;
else /* This includes c == '\0'. */
EMIT_CHAR (c);
if (c == '\0')
goto success;
break;
case _expand_variable_state_after_backslash:
if (c == '$' || c == '\\') {
EMIT_CHAR (c);
s = _expand_variable_state_non_variable;
}
else if (c == '\0') /* Just to give a nicer error message */
FATAL (PEP_PATH_SYNTAX_ERROR, "trailing unescaped '\\'");
else /* this would be correct even with '\0' */
FATAL (PEP_PATH_SYNTAX_ERROR, "invalid escape");
break;
case _expand_variable_state_after_dollar:
if (VALID_FIRST_CHARACTER_FOR_VARIABLE (c))
s = _expand_variable_state_in_variable;
else if (c == '\0') /* Just to give a nicer error message */
FATAL (PEP_PATH_SYNTAX_ERROR,"trailing '$' character");
else if (c == '\\') /* Just to give a nicer error message */
FATAL (PEP_PATH_SYNTAX_ERROR,
"empty variable name followed by escape");
else if (c == '$') /* Just to give a nicer error message */
FATAL (PEP_PATH_SYNTAX_ERROR, "two consecutive '$' characters");
else
FATAL (PEP_PATH_SYNTAX_ERROR,
"invalid variable first character after '$'");
break;
case _expand_variable_state_in_variable:
if (VALID_NON_FIRST_CHARACTER_FOR_VARIABLE (c))
/* Do nothing */;
else if (c == '\\') {
EMIT_CURRENT_VARIABLE;
s = _expand_variable_state_after_backslash;
}
else {
/* This includes c == '\0'. */
EMIT_CURRENT_VARIABLE;
EMIT_CHAR (c);
if (c == '\0')
goto success;
else
s = _expand_variable_state_non_variable;
}
break;
default:
FATAL (PEP_STATEMACHINE_INVALID_STATE /* Slightly questionable: this
should be an assertion. */,
"impossible DFA state");
} /* switch */
in ++;
} /* while */
success:
free(variable_name_copy);
return res;
failure:
free(* out);
* out = NULL;
goto success;
#undef VALID_FIRST_CHARACTER_FOR_VARIABLE
#undef VALID_NON_FIRST_CHARACTER_FOR_VARIABLE
#undef EMIT_CHAR
#undef EMIT_STRING
#undef EMIT_CURRENT_VARIABLE
#undef FATAL
#undef FATAL_NAME
}
/*
* Internal path caching functionality
* **********************************************************************
*/
/* Several functions in this compilation unit return paths to files or
* directories, always returning pointers to the same internally managed memory
* at every call.
*
* The cache is filled at engine initialisation, using the value of environment
* variables at initialisation time: after that point no out-of-memory errors
* are possible, until reset.
*
* In debugging mode the cache can be "reset", with every path recomputed on
* demand according to the current environment.
*/
/* For each path we define:
- a static char * variable pointing to the cached value;
- a prototype for a static function returning a malloc-allocated copy of
the value, unexapanded, not using the cache (to be defined below by hand);
- a public API function returning a pointer to cached memory. */
#define DEFINE_CACHED_PATH(name) \
/* A static variable holding the cached path, or NULL. */ \
static char *_ ## name ## _cache = NULL; \
\
/* A prototype for the hand-written function returning the \
computed value for the path, without using the cache and \
without expanding variables. */ \
static char *_ ## name(void); \
\
/* The public version of the function, using the cache. */ \
DYNAMIC_API const char *name(void) \
{ \
if (_ ## name ## _cache == NULL) { \
/* It is unusual and slightly bizarre than a path is \
accessed before initialisation; however it can happen \
in the engine test suite. */ \
fprintf (stderr, \
"WARNING: accessing %s before its cache is set:" \
" this should not happen in production.\n", \
#name); \
reset_path_cache(); \
} \
return _ ## name ## _cache; \
}
/* Define cached paths using the functionality above: */
DEFINE_CACHED_PATH (per_user_relative_directory)
DEFINE_CACHED_PATH (per_user_directory)
DEFINE_CACHED_PATH (per_machine_directory)
#ifdef ANDROID
DEFINE_CACHED_PATH (android_system_db)
#endif
DEFINE_CACHED_PATH (unix_system_db)
DEFINE_CACHED_PATH (unix_local_db)
/* Free every cache variable and re-initialise it to NULL: this
re-initialisation is important when this function is used here,
internally, as part of cleanup on errors. */
DYNAMIC_API void clear_path_cache (void)
{
#define UNSET(name) \
do { \
free((void *) _ ## name ## _cache); \
(_ ## name ## _cache) = NULL; \
} while (false)
UNSET (per_user_relative_directory);
UNSET (per_user_directory);
UNSET (per_machine_directory);
#ifdef ANDROID
UNSET (android_system_db);
#endif
UNSET (unix_system_db);
UNSET (unix_local_db);
#undef UNSET
}
DYNAMIC_API PEP_STATUS reset_path_cache(void)
{
PEP_STATUS res = PEP_STATUS_OK;
#define SET_OR_FAIL(name) \
do { \
unexpanded_path = (_ ## name)(); \
if (unexpanded_path == NULL) { \
res = PEP_OUT_OF_MEMORY; \
goto free_everything_and_fail; \
} \
res = _expand_variables(& _ ## name ## _cache, unexpanded_path); \
if (res != PEP_STATUS_OK) \
goto free_everything_and_fail; \
/* Clear unxpanded_path for the next call of SET_OR_FAIL. */ \
free((void *) unexpanded_path); \
unexpanded_path = NULL; \
} while (false)
/* In case this is not the first invocation, start by releasing memory. */
clear_path_cache ();
const char *unexpanded_path = NULL;
SET_OR_FAIL (per_user_relative_directory);
SET_OR_FAIL (per_user_directory);
SET_OR_FAIL (per_machine_directory);
#ifdef ANDROID
SET_OR_FAIL (android_system_db);
#endif
SET_OR_FAIL (unix_system_db);
SET_OR_FAIL (unix_local_db);
return res;
free_everything_and_fail:
free((void *) unexpanded_path);
clear_path_cache ();
return res;
#undef SET_OR_FAIL
}
/**
* @internal
*
* <!-- _per_user_directory() -->
*
* @brief TODO
*
*/
static char *_per_user_relative_directory(void)
{
return _strdup_or_NULL(PER_USER_DIRECTORY);
}
/**
* @internal
*
* <!-- _per_user_directory() -->
*
* @brief TODO
*
*/
static char *_per_user_directory(void)
{
char *path = NULL;
const char *home = NULL;
#ifndef NDEBUG
home = getenv("PEP_HOME");
if (!home)
#endif
home = getenv("HOME");
assert(home);
if (!home)
return NULL;
path = strdup(home);
assert(path);
if (!path)
return NULL;
char *_path = _stradd(&path, "/");
if (!_path)
goto error;
_path = _stradd(&path, PER_USER_DIRECTORY);
if (!_path)
goto error;
return path;
error:
_empty(&path);
return NULL;
}
char *_unix_local_db(void)
{
char* path = (char *) _per_user_directory() /* This memory is not shared. */;
if (!path)
return NULL;
char *path_c = NULL;
char *old_path = NULL;
char *old_path_c = NULL;
struct stat dir;
int r = stat(path, &dir);
if (r) {
if (errno == ENOENT) {
// directory does not yet exist
r = mkdir(path, 0700);
if (r)
goto error;
}
else {
goto error;
}
}
char *_path = _stradd(&path, "/");
if (!_path)
goto error;
// make a copy of this path in case we need to move files
path_c = strdup(path);
assert(path_c);
if (!path_c)
goto error;
_path = _stradd(&path, LOCAL_DB_FILENAME);
if (!_path)
goto error;
struct stat file;
r = stat(path, &file);
if (r) {
if (errno == ENOENT) {
// we do not have management.db yet, let's test if we need to move
// one with the old name
const char *home = NULL;
#ifndef NDEBUG
home = getenv("PEP_HOME");
if (!home)
#endif
home = getenv("HOME");
// we were already checking for HOME existing, so this is only a
// safeguard
assert(home);
old_path = strdup(home);
assert(old_path);
if (!old_path)
goto error;
char *_old_path = _stradd(&old_path, "/");
if (!_old_path)
goto error;
old_path_c = strdup(old_path);
assert(old_path_c);
if (!old_path_c)
goto error;
_old_path = _stradd(&old_path, OLD_LOCAL_DB_FILENAME);
if (!_old_path)
goto error;
struct stat old;
r = stat(old_path, &old);
if (r == 0) {
// old file existing, new file not yet existing, move
rename(old_path, path);
// if required move associated files, too
_move(old_path, "-shm", path);
_move(old_path, "-wal", path);
// move keys database
_old_path = _stradd(&old_path_c, OLD_KEYS_DB_FILENAME);
if (!_old_path)
goto error;
_path = _stradd(&path_c, KEYS_DB_FILENAME);
if (!_path)
goto error;
rename(old_path_c, path_c);
// if required move associated files, too
_move(old_path_c, "-shm", path_c);
_move(old_path_c, "-wal", path_c);
}
}
else {
goto error;
}
}
goto the_end;
error:
_empty(&path);
the_end:
free(path_c);
free(old_path);
free(old_path_c);
return path;
}
static char *_per_machine_directory(void) {
return _strdup_or_NULL(PER_MACHINE_DIRECTORY);
}
char *_unix_system_db(void)
{
char *path = NULL;
path = _per_machine_directory() /* Use this fresh copy. */;
assert(path);
if (!path)
return NULL;
char *_path = _stradd(&path, "/");
if (!_path)
goto error;
_path = _stradd(&path, SYSTEM_DB_FILENAME);
if (!_path)
goto error;
return path;
error:
_empty(&path);
return NULL;
}