From: Guus Sliepen Date: Sun, 14 Nov 2021 15:35:09 +0000 (+0100) Subject: Use a key/value store with configurable storage callbacks. X-Git-Url: https://git.meshlink.io/?a=commitdiff_plain;h=refs%2Fheads%2Ffeature%2Fstorage-callbacks;p=meshlink-tiny Use a key/value store with configurable storage callbacks. For greater flexibility with how configuration files are stored, we assume a key/value store, and allow the application to provide callbacks for storing and loading values, and listing all the keys. By default, we use the filesystem as a backing store, with one file per key in a flat directory. --- diff --git a/examples/chat.c b/examples/chat.c index 5d06538..75d6acc 100644 --- a/examples/chat.c +++ b/examples/chat.c @@ -1,7 +1,13 @@ +#define _POSIX_C_SOURCE 200809L + #include #include #include #include +#include +#include +#include +#include #include "../src/meshlink-tiny.h" static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { @@ -150,6 +156,55 @@ static void parse_input(meshlink_handle_t *mesh, char *buf) { printf("Message sent to '%s'.\n", destination->name); } +static char *flatten(const char *filename) { + char *result = strdup(filename); + assert(result); + + for(char *c = result; *c; c++) { + if(*c == '/') { + *c = ':'; + } + } + + return result; +} + +static bool load_cb(meshlink_handle_t *mesh, const char *key, void *data, size_t *len) { + fprintf(stderr, "load_cb(%s, %s, %p, %zu)\n", mesh->name, key, data, *len); + FILE *f = fopen(flatten(key), "r"); + assert(f); + fread(data, 1, *len, f); + fseek(f, 0, SEEK_END); + *len = ftell(f); + assert(!fclose(f)); + return true; +} + +static bool store_cb(meshlink_handle_t *mesh, const char *key, const void *data, size_t len) { + fprintf(stderr, "store_cb(%s, %s, %p, %zu)\n", mesh->name, key, data, len); + FILE *f = fopen(flatten(key), "w"); + assert(f); + assert(fwrite(data, len, 1, f) == 1); + assert(!fclose(f)); + return true; +} + +static bool ls_cb(meshlink_handle_t *mesh, meshlink_ls_entry_cb_t entry_cb) { + fprintf(stderr, "ls_cb()"); + DIR *dir = opendir("."); + struct dirent *ent; + + while((ent = readdir(dir))) { + if(ent->d_name[0] != '.') { + entry_cb(mesh, ent->d_name, 0); + } + } + + closedir(dir); + + return true; +} + int main(int argc, char *argv[]) { const char *confbase = ".chat"; const char *nick = NULL; @@ -163,9 +218,15 @@ int main(int argc, char *argv[]) { nick = argv[2]; } - meshlink_set_log_cb(NULL, MESHLINK_INFO, log_message); + assert(mkdir(confbase, 0700) == 0); + assert(chdir(confbase) == 0); - meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + meshlink_open_params_t *params = meshlink_open_params_init(confbase, nick, "chat", DEV_CLASS_STATIONARY); + //assert(meshlink_open_params_set_storage_key(params, "12345", 5)); + assert(meshlink_open_params_set_storage_callbacks(params, load_cb, store_cb, ls_cb)); + meshlink_handle_t *mesh = meshlink_open_ex(params); + meshlink_open_params_free(params); if(!mesh) { fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); diff --git a/src/conf.c b/src/conf.c index cae7bd1..925171d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -18,6 +18,7 @@ */ #include "system.h" + #include #include #include @@ -26,63 +27,11 @@ #include "crypto.h" #include "logger.h" #include "meshlink_internal.h" -#include "xalloc.h" #include "packmsg.h" +#include "protocol.h" +#include "xalloc.h" -/// Generate a path to the main configuration file. -static void make_main_path(meshlink_handle_t *mesh, const char *conf_subdir, char *path, size_t len) { - assert(conf_subdir); - assert(path); - assert(len); - - snprintf(path, len, "%s" SLASH "%s" SLASH "meshlink.conf", mesh->confbase, conf_subdir); -} - -/// Generate a path to a host configuration file. -static void make_host_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) { - assert(conf_subdir); - assert(name); - assert(path); - assert(len); - - snprintf(path, len, "%s" SLASH "%s" SLASH "hosts" SLASH "%s", mesh->confbase, conf_subdir, name); -} - -/// Remove a directory recursively -static bool deltree(const char *dirname) { - assert(dirname); - - DIR *d = opendir(dirname); - - if(d) { - struct dirent *ent; - - while((ent = readdir(d))) { - if(ent->d_name[0] == '.') { - if(!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2])) { - continue; - } - } - - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name); - - if(unlink(filename)) { - if(!deltree(filename)) { - return false; - } - } - } - - closedir(d); - } else { - return errno == ENOENT; - } - - return rmdir(dirname) == 0; -} - -bool sync_path(const char *pathname) { +static bool sync_path(const char *pathname) { assert(pathname); int fd = open(pathname, O_RDONLY); @@ -110,434 +59,265 @@ bool sync_path(const char *pathname) { return true; } -/// Try decrypting the main configuration file from the given sub-directory. -static bool main_config_decrypt(meshlink_handle_t *mesh, const char *conf_subdir) { - assert(mesh->config_key); - assert(mesh->confbase); - assert(conf_subdir); - - config_t config; - - if(!main_config_read(mesh, conf_subdir, &config, mesh->config_key)) { - logger(mesh, MESHLINK_ERROR, "Could not read main configuration file"); - return false; - } - - packmsg_input_t in = {config.buf, config.len}; - - uint32_t version = packmsg_get_uint32(&in); - config_free(&config); - - return version == MESHLINK_CONFIG_VERSION; +static bool invalidate_config_file(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; + config_t empty_config = {NULL, 0}; + return config_store(mesh, name, &empty_config); } -/// Create a fresh configuration directory -bool config_init(meshlink_handle_t *mesh, const char *conf_subdir) { - assert(conf_subdir); - - if(!mesh->confbase) { +/// Wipe an existing configuration directory +bool config_destroy(const struct meshlink_open_params *params) { + if(!params->confbase) { return true; } - char path[PATH_MAX]; - - // Create "current" sub-directory in the confbase - snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir); + FILE *lockfile = NULL; - if(!deltree(path)) { - logger(mesh, MESHLINK_DEBUG, "Could not delete directory %s: %s\n", path, strerror(errno)); - return false; - } + if(!params->load_cb) { + /* Exit early if the confbase directory itself doesn't exist */ + if(access(params->confbase, F_OK) && errno == ENOENT) { + return true; + } - if(mkdir(path, 0700)) { - logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); - return false; - } + /* Take the lock the same way meshlink_open() would. */ + lockfile = fopen(params->lock_filename, "w+"); - make_host_path(mesh, conf_subdir, "", path, sizeof(path)); + if(!lockfile) { + logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } - if(mkdir(path, 0700)) { - logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); - return false; - } +#ifdef FD_CLOEXEC + fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC); +#endif - return true; -} +#ifdef HAVE_MINGW + // TODO: use _locking()? +#else -/// Wipe an existing configuration directory -bool config_destroy(const char *confbase, const char *conf_subdir) { - assert(conf_subdir); + if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) { + logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename); + fclose(lockfile); + meshlink_errno = MESHLINK_EBUSY; + return false; + } - if(!confbase) { - return true; +#endif } - struct stat st; - - char path[PATH_MAX]; + { + meshlink_handle_t tmp_mesh; + memset(&tmp_mesh, 0, sizeof tmp_mesh); - // Check the presence of configuration base sub directory. - snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir); + tmp_mesh.confbase = params->confbase; + tmp_mesh.name = params->name; + tmp_mesh.load_cb = params->load_cb; + tmp_mesh.store_cb = params->store_cb; + tmp_mesh.ls_cb = params->ls_cb; - if(stat(path, &st)) { - if(errno == ENOENT) { - return true; - } else { - logger(NULL, MESHLINK_ERROR, "Cannot stat %s: %s\n", path, strerror(errno)); + if(!config_ls(&tmp_mesh, invalidate_config_file)) { + logger(NULL, MESHLINK_ERROR, "Cannot remove configuration files\n"); + fclose(lockfile); meshlink_errno = MESHLINK_ESTORAGE; return false; } } - // Remove meshlink.conf - snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "meshlink.conf", confbase, conf_subdir); - - if(unlink(path)) { - if(errno != ENOENT) { - logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno)); + if(!params->load_cb) { + if(unlink(params->lock_filename) && errno != ENOENT) { + logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno)); + fclose(lockfile); meshlink_errno = MESHLINK_ESTORAGE; return false; } - } - - snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir); - - if(!deltree(path)) { - logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - return sync_path(confbase); -} - -static bool copytree(const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) { - assert(src_dir_name); - assert(dst_dir_name); - - char src_filename[PATH_MAX]; - char dst_filename[PATH_MAX]; - struct dirent *ent; - - DIR *src_dir = opendir(src_dir_name); - - if(!src_dir) { - logger(NULL, MESHLINK_ERROR, "Could not open directory file %s\n", src_dir_name); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - // Delete if already exists and create a new destination directory - if(!deltree(dst_dir_name)) { - logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", dst_dir_name, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - if(mkdir(dst_dir_name, 0700)) { - logger(NULL, MESHLINK_ERROR, "Could not create directory %s\n", dst_filename); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } + fclose(lockfile); - while((ent = readdir(src_dir))) { - if(ent->d_name[0] == '.') { - continue; + if(!sync_path(params->confbase)) { + logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; } - snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", dst_dir_name, ent->d_name); - snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", src_dir_name, ent->d_name); - - if(ent->d_type == DT_DIR) { - if(!copytree(src_filename, src_key, dst_filename, dst_key)) { - logger(NULL, MESHLINK_ERROR, "Copying %s to %s failed\n", src_filename, dst_filename); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(!sync_path(dst_filename)) { - return false; - } - } else if(ent->d_type == DT_REG) { - struct stat st; - config_t config; - - if(stat(src_filename, &st)) { - logger(NULL, MESHLINK_ERROR, "Could not stat file `%s': %s\n", src_filename, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - FILE *f = fopen(src_filename, "r"); - - if(!f) { - logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s\n", src_filename, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(!config_read_file(NULL, f, &config, src_key)) { - logger(NULL, MESHLINK_ERROR, "Failed to read `%s': %s\n", src_filename, strerror(errno)); - fclose(f); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(fclose(f)) { - logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s\n", src_filename, strerror(errno)); - config_free(&config); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - f = fopen(dst_filename, "w"); - - if(!f) { - logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s", dst_filename, strerror(errno)); - config_free(&config); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(!config_write_file(NULL, f, &config, dst_key)) { - logger(NULL, MESHLINK_ERROR, "Failed to write `%s': %s", dst_filename, strerror(errno)); - config_free(&config); - fclose(f); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(fclose(f)) { - logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s", dst_filename, strerror(errno)); - config_free(&config); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - config_free(&config); - - struct utimbuf times; - times.modtime = st.st_mtime; - times.actime = st.st_atime; - - if(utime(dst_filename, ×)) { - logger(NULL, MESHLINK_ERROR, "Failed to utime `%s': %s", dst_filename, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - } + rmdir(params->confbase); } - closedir(src_dir); return true; } -bool config_copy(meshlink_handle_t *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) { - assert(src_dir_name); - assert(dst_dir_name); +/// Read a blob of data. +static bool load(meshlink_handle_t *mesh, const char *key, void *data, size_t *len) { + logger(mesh, MESHLINK_DEBUG, "load(%s, %p, %zu)", key ? key : "(null)", data, *len); - char src_filename[PATH_MAX]; - char dst_filename[PATH_MAX]; - - snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", mesh->confbase, dst_dir_name); - snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", mesh->confbase, src_dir_name); + if(mesh->load_cb) { + if(!mesh->load_cb(mesh, key, data, len)) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s'\n", key); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } else { + return true; + } + } - return copytree(src_filename, src_key, dst_filename, dst_key); -} + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s", mesh->confbase, key); -/// Check the presence of the main configuration file. -bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) { - assert(conf_subdir); + FILE *f = fopen(filename, "r"); - if(!mesh->confbase) { + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; return false; } - char path[PATH_MAX]; - make_main_path(mesh, conf_subdir, path, sizeof(path)); - return access(path, F_OK) == 0; -} + long actual_len; -bool config_rename(meshlink_handle_t *mesh, const char *old_conf_subdir, const char *new_conf_subdir) { - assert(old_conf_subdir); - assert(new_conf_subdir); - - if(!mesh->confbase) { + if(fseek(f, 0, SEEK_END) || (actual_len = ftell(f)) <= 0 || fseek(f, 0, SEEK_SET)) { + logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + fclose(f); return false; } - char old_path[PATH_MAX]; - char new_path[PATH_MAX]; + size_t todo = (size_t)actual_len < *len ? (size_t)actual_len : *len; + *len = actual_len; - snprintf(old_path, sizeof(old_path), "%s" SLASH "%s", mesh->confbase, old_conf_subdir); - snprintf(new_path, sizeof(new_path), "%s" SLASH "%s", mesh->confbase, new_conf_subdir); - - return rename(old_path, new_path) == 0 && sync_path(mesh->confbase); -} - -bool config_sync(meshlink_handle_t *mesh, const char *conf_subdir) { - assert(conf_subdir); - - if(!mesh->confbase || mesh->storage_policy == MESHLINK_STORAGE_DISABLED) { + if(!data) { + fclose(f); return true; } - char path[PATH_MAX]; - snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "hosts", mesh->confbase, conf_subdir); - - if(!sync_path(path)) { + if(fread(data, todo, 1, f) != 1) { + logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + fclose(f); return false; } - snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir); - - if(!sync_path(path)) { + if(fclose(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; return false; } return true; } -bool meshlink_confbase_exists(meshlink_handle_t *mesh) { - if(!mesh->confbase) { - return false; - } - - bool confbase_exists = false; - bool confbase_decryptable = false; - - if(main_config_exists(mesh, "current")) { - confbase_exists = true; +/// Store a blob of data. +static bool store(meshlink_handle_t *mesh, const char *key, const void *data, size_t len) { + logger(mesh, MESHLINK_DEBUG, "store(%s, %p, %zu)", key ? key : "(null)", data, len); - if(mesh->config_key && main_config_decrypt(mesh, "current")) { - confbase_decryptable = true; + if(mesh->store_cb) { + if(!mesh->store_cb(mesh, key, data, len)) { + logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } else { + return true; } } - if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "new")) { - confbase_exists = true; - - if(main_config_decrypt(mesh, "new")) { - if(!config_destroy(mesh->confbase, "current")) { - return false; - } + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "%s", mesh->confbase, key); - if(!config_rename(mesh, "new", "current")) { - return false; - } - - confbase_decryptable = true; + if(!len) { + if(unlink(filename) && errno != ENOENT) { + logger(mesh, MESHLINK_ERROR, "Failed to remove `%s': %s", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } else { + return true; } } - if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) { - confbase_exists = true; + char tmp_filename[PATH_MAX]; + snprintf(tmp_filename, sizeof(tmp_filename), "%s" SLASH "%s.tmp", mesh->confbase, key); - if(main_config_decrypt(mesh, "old")) { - if(!config_destroy(mesh->confbase, "current")) { - return false; - } + FILE *f = fopen(tmp_filename, "w"); - if(!config_rename(mesh, "old", "current")) { - return false; - } - - confbase_decryptable = true; - } + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; } - // Cleanup if current is existing with old and new - if(confbase_exists && confbase_decryptable) { - if(!config_destroy(mesh->confbase, "old") || !config_destroy(mesh->confbase, "new")) { - return false; - } + if(fwrite(data, len, 1, f) != 1) { + logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + fclose(f); + return false; } - return confbase_exists; -} - -/// Lock the main configuration file. Creates confbase if necessary. -bool main_config_lock(meshlink_handle_t *mesh, const char *lock_filename) { - if(!mesh->confbase) { - return true; + if(fflush(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + fclose(f); + return false; } - assert(lock_filename); - - if(mkdir(mesh->confbase, 0700) && errno != EEXIST) { - logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno)); - meshlink_close(mesh); + if(fsync(fileno(f))) { + logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno)); meshlink_errno = MESHLINK_ESTORAGE; - return NULL; + fclose(f); + return false; } - mesh->lockfile = fopen(lock_filename, "w+"); - - if(!mesh->lockfile) { - logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", lock_filename, strerror(errno)); + if(fclose(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno)); meshlink_errno = MESHLINK_ESTORAGE; return false; } -#ifdef FD_CLOEXEC - fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC); -#endif - -#ifdef HAVE_FLOCK - - if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) { - logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", lock_filename, strerror(errno)); - fclose(mesh->lockfile); - mesh->lockfile = NULL; - meshlink_errno = MESHLINK_EBUSY; + if(rename(tmp_filename, filename)) { + logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_filename, filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; return false; } -#endif - return true; } -/// Unlock the main configuration file. -void main_config_unlock(meshlink_handle_t *mesh) { - if(mesh->lockfile) { - fclose(mesh->lockfile); - mesh->lockfile = NULL; +/// Read a configuration file, decrypting it if necessary. +bool config_load(meshlink_handle_t *mesh, const char *name, config_t *config) { + size_t buflen = 256; + uint8_t *buf = xmalloc(buflen); + size_t len = buflen; + + if(!load(mesh, name, buf, &len)) { + return false; } -} -/// Read a configuration file from a FILE handle. -bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) { - assert(f); + buf = xrealloc(buf, len); - long len; + if(len > buflen) { + buflen = len; - if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) { - logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(!load(mesh, name, (void **)&buf, &len) || len != buflen) { + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } } - uint8_t *buf = xmalloc(len); + if(mesh->config_key) { + if(len < 12 + 16) { + logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n"); + meshlink_errno = MESHLINK_ESTORAGE; + config_free(config); + return false; + } - if(fread(buf, len, 1, f) != 1) { - logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } + size_t decrypted_len = len - 12 - 16; + uint8_t *decrypted = xmalloc(decrypted_len); - if(key) { - uint8_t *decrypted = xmalloc(len); - size_t decrypted_len = len; chacha_poly1305_ctx_t *ctx = chacha_poly1305_init(); - chacha_poly1305_set_key(ctx, key); + chacha_poly1305_set_key(ctx, mesh->config_key); - if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) { + if(chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) { chacha_poly1305_exit(ctx); free(buf); - config->buf = decrypted; - config->len = decrypted_len; - return true; + buf = decrypted; + len = decrypted_len; } else { logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n"); meshlink_errno = MESHLINK_ESTORAGE; @@ -554,55 +334,35 @@ bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const return true; } -/// Write a configuration file to a FILE handle. -bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) { - assert(f); +bool config_exists(meshlink_handle_t *mesh, const char *name) { + size_t len = 0; - if(key) { - uint8_t buf[config->len + 16]; - size_t len = sizeof(buf); - uint8_t seqbuf[12]; - randomize(&seqbuf, sizeof(seqbuf)); - chacha_poly1305_ctx_t *ctx = chacha_poly1305_init(); - chacha_poly1305_set_key(ctx, key); - bool success = false; + return load(mesh, name, NULL, &len) && len; +} - if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) { - success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1; +/// Write a configuration file, encrypting it if necessary. +bool config_store(meshlink_handle_t *mesh, const char *name, const config_t *config) { + if(mesh->config_key) { + size_t encrypted_len = config->len + 16; // length of encrypted data + uint8_t encrypted[12 + encrypted_len]; // store sequence number at the start - if(!success) { - logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); - } + randomize(encrypted, 12); + chacha_poly1305_ctx_t *ctx = chacha_poly1305_init(); + chacha_poly1305_set_key(ctx, mesh->config_key); - meshlink_errno = MESHLINK_ESTORAGE; - } else { + if(!chacha_poly1305_encrypt_iv96(ctx, encrypted, config->buf, config->len, encrypted + 12, &encrypted_len)) { logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n"); meshlink_errno = MESHLINK_ESTORAGE; + chacha_poly1305_exit(ctx); + return false; } chacha_poly1305_exit(ctx); - return success; - } - if(fwrite(config->buf, config->len, 1, f) != 1) { - logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + return store(mesh, name, encrypted, 12 + encrypted_len); } - if(fflush(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - if(fsync(fileno(f))) { - logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - return true; + return store(mesh, name, config->buf, config->len); } /// Free resources of a loaded configuration file. @@ -614,66 +374,24 @@ void config_free(config_t *config) { config->len = 0; } -/// Check the presence of a host configuration file. -bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) { - assert(conf_subdir); - - if(!mesh->confbase) { - return false; - } - - char path[PATH_MAX]; - make_host_path(mesh, conf_subdir, name, path, sizeof(path)); - - return access(path, F_OK) == 0; -} - -/// Read a host configuration file. -bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) { - assert(conf_subdir); +bool config_ls(meshlink_handle_t *mesh, config_scan_action_t action) { + logger(mesh, MESHLINK_DEBUG, "ls(%p)", (void *)(intptr_t)action); if(!mesh->confbase) { - return false; - } - - char path[PATH_MAX]; - make_host_path(mesh, conf_subdir, name, path, sizeof(path)); - - FILE *f = fopen(path, "r"); - - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); - return false; - } - - if(!config_read_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); - fclose(f); - return false; + return true; } - fclose(f); - - return true; -} - -bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) { - assert(conf_subdir); - assert(conf_type); - - if(!mesh->confbase) { - return true; + if(mesh->ls_cb) { + return mesh->ls_cb(mesh, action); } DIR *dir; struct dirent *ent; - char dname[PATH_MAX]; - snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type); - dir = opendir(dname); + dir = opendir(mesh->confbase); if(!dir) { - logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", mesh->confbase, strerror(errno)); meshlink_errno = MESHLINK_ESTORAGE; return false; } @@ -683,7 +401,7 @@ bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const cha continue; } - if(!action(mesh, ent->d_name, arg)) { + if(!action(mesh, ent->d_name, 0)) { closedir(dir); return false; } @@ -693,141 +411,142 @@ bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const cha return true; } -/// Write a host configuration file. -bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) { - assert(conf_subdir); - assert(name); - assert(config); +/// Re-encrypt a configuration file. +static bool change_key(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; + config_t config; - if(!mesh->confbase) { - return true; + if(!config_load(mesh, name, &config)) { + return false; } - char path[PATH_MAX]; - char tmp_path[PATH_MAX + 4]; - make_host_path(mesh, conf_subdir, name, path, sizeof(path)); - snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path); - - FILE *f = fopen(tmp_path, "w"); + size_t name_len = strlen(name); + char new_name[name_len + 3]; + memcpy(new_name, name, name_len); - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(name_len == 13 && name[8] == '.') { + // Update meshlink.conf in-place + new_name[name_len] = 0; + } else { + memcpy(new_name + name_len, ".r", 3); } - if(!config_write_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno)); - fclose(f); - return false; - } + void *orig_key = mesh->config_key; + mesh->config_key = mesh->config_new_key; + bool result = config_store(mesh, new_name, &config); + mesh->config_key = orig_key; - if(fclose(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } + return result; +} - if(rename(tmp_path, path)) { - logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; +static bool change_node_key(meshlink_handle_t *mesh, const char *name, size_t len) { + if(!check_id(name)) { + return true; } - return true; + return change_key(mesh, name, len); } -/// Delete a host configuration file. -bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) { - assert(conf_subdir); - assert(name); +static bool cleanup_old_file(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; + size_t name_len = strlen(name); - if(!mesh->confbase) { + if(name_len < 3 || name[name_len - 2] != '.' || name[name_len - 1] != 'r') { return true; } - char path[PATH_MAX]; - make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + config_t config; - if(unlink(path) && errno != ENOENT) { - logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; + if(!config_load(mesh, name, &config)) { return false; } - return true; + char new_name[name_len - 1]; + memcpy(new_name, name, name_len - 2); + new_name[name_len - 2] = '\0'; + + return config_store(mesh, new_name, &config) && store(mesh, name, NULL, 0); } -/// Read the main configuration file. -bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) { - assert(conf_subdir); - assert(config); +bool config_cleanup_old_files(meshlink_handle_t *mesh) { + return config_ls(mesh, cleanup_old_file); +} - if(!mesh->confbase) { +bool config_change_key(meshlink_handle_t *mesh, void *new_key) { + extern bool (*devtool_keyrotate_probe)(int stage); + mesh->config_new_key = new_key; + + if(!config_ls(mesh, change_node_key)) { return false; } - char path[PATH_MAX]; - make_main_path(mesh, conf_subdir, path, sizeof(path)); - - FILE *f = fopen(path, "r"); - - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); + if(!devtool_keyrotate_probe(1)) { return false; } - if(!config_read_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); - fclose(f); + if(!change_key(mesh, "meshlink.conf", 0)) { return false; } - fclose(f); + free(mesh->config_key); + mesh->config_key = new_key; + + if(!devtool_keyrotate_probe(2)) { + return true; + } + + config_cleanup_old_files(mesh); + + devtool_keyrotate_probe(3); return true; } -/// Write the main configuration file. -bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) { - assert(conf_subdir); - assert(config); - +/// Initialize the configuration directory +bool config_init(meshlink_handle_t *mesh, const struct meshlink_open_params *params) { if(!mesh->confbase) { return true; } - char path[PATH_MAX]; - char tmp_path[PATH_MAX + 4]; - make_main_path(mesh, conf_subdir, path, sizeof(path)); - snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path); + if(!mesh->load_cb) { + if(mkdir(mesh->confbase, 0700) && errno != EEXIST) { + logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno)); + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; + } - FILE *f = fopen(tmp_path, "w"); + mesh->lockfile = fopen(params->lock_filename, "w+"); - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } + if(!mesh->lockfile) { + logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", params->lock_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } - if(!config_write_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno)); - fclose(f); - return false; - } +#ifdef FD_CLOEXEC + fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC); +#endif - if(rename(tmp_path, path)) { - logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - fclose(f); - return false; - } +#ifdef HAVE_FLOCK - if(fclose(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) { + logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", params->lock_filename, strerror(errno)); + fclose(mesh->lockfile); + mesh->lockfile = NULL; + meshlink_errno = MESHLINK_EBUSY; + return false; + } + +#endif } return true; } + +void config_exit(meshlink_handle_t *mesh) { + if(mesh->lockfile) { + fclose(mesh->lockfile); + mesh->lockfile = NULL; + } +} \ No newline at end of file diff --git a/src/conf.h b/src/conf.h index 4560f2a..23ad900 100644 --- a/src/conf.h +++ b/src/conf.h @@ -21,37 +21,28 @@ */ struct meshlink_handle; +struct meshlink_open_params; typedef struct config_t { const uint8_t *buf; size_t len; } config_t; -typedef bool (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name, void *arg); +typedef bool (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name, size_t len); -bool config_read_file(struct meshlink_handle *mesh, FILE *f, struct config_t *, const void *key) __attribute__((__warn_unused_result__)); -bool config_write_file(struct meshlink_handle *mesh, FILE *f, const struct config_t *, const void *key) __attribute__((__warn_unused_result__)); void config_free(struct config_t *config); -bool meshlink_confbase_exists(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); - -bool config_init(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); -bool config_destroy(const char *confbase, const char *conf_subdir) __attribute__((__warn_unused_result__)); -bool config_copy(struct meshlink_handle *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) __attribute__((__warn_unused_result__)); -bool config_rename(struct meshlink_handle *mesh, const char *old_conf_subdir, const char *new_conf_subdir) __attribute__((__warn_unused_result__)); -bool config_sync(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); -bool sync_path(const char *path) __attribute__((__warn_unused_result__)); - -bool main_config_exists(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); -bool main_config_lock(struct meshlink_handle *mesh, const char *lock_filename) __attribute__((__warn_unused_result__)); -void main_config_unlock(struct meshlink_handle *mesh); -bool main_config_read(struct meshlink_handle *mesh, const char *conf_subdir, struct config_t *, void *key) __attribute__((__warn_unused_result__)); -bool main_config_write(struct meshlink_handle *mesh, const char *conf_subdir, const struct config_t *, void *key) __attribute__((__warn_unused_result__)); - -bool config_exists(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__)); -bool config_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__)); -bool config_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __attribute__((__warn_unused_result__)); -bool config_delete(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__)); -bool config_scan_all(struct meshlink_handle *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) __attribute__((__warn_unused_result__)); +bool config_init(struct meshlink_handle *mesh, const struct meshlink_open_params *params) __attribute__((__warn_unused_result__)); +void config_exit(struct meshlink_handle *mesh); +bool config_destroy(const struct meshlink_open_params *params) __attribute__((__warn_unused_result__)); + +bool config_load(struct meshlink_handle *mesh, const char *name, struct config_t *) __attribute__((__warn_unused_result__)); +bool config_store(struct meshlink_handle *mesh, const char *name, const struct config_t *) __attribute__((__warn_unused_result__)); +bool config_ls(struct meshlink_handle *mesh, config_scan_action_t action) __attribute__((__warn_unused_result__)); + +bool config_exists(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); + +bool config_change_key(struct meshlink_handle *mesh, void *new_key) __attribute__((__warn_unused_result__)); +bool config_cleanup_old_files(struct meshlink_handle *mesh); #endif diff --git a/src/devtools.c b/src/devtools.c index 1a64721..cebd79c 100644 --- a/src/devtools.c +++ b/src/devtools.c @@ -33,9 +33,9 @@ static void nop_probe(void) { return; } -static void keyrotate_nop_probe(int stage) { +static bool keyrotate_nop_probe(int stage) { (void)stage; - return; + return true; } static void inviter_commits_first_nop_probe(bool stage) { @@ -49,7 +49,7 @@ static void sptps_renewal_nop_probe(meshlink_node_t *node) { } void (*devtool_trybind_probe)(void) = nop_probe; -void (*devtool_keyrotate_probe)(int stage) = keyrotate_nop_probe; +bool (*devtool_keyrotate_probe)(int stage) = keyrotate_nop_probe; void (*devtool_set_inviter_commits_first)(bool inviter_commited_first) = inviter_commits_first_nop_probe; void (*devtool_sptps_renewal_probe)(meshlink_node_t *node) = sptps_renewal_nop_probe; diff --git a/src/devtools.h b/src/devtools.h index 32b58ff..4a5a17e 100644 --- a/src/devtools.h +++ b/src/devtools.h @@ -57,8 +57,10 @@ extern void (*devtool_trybind_probe)(void); * On assigning a debug function variable invokes callback for each stage from the key rotate API. * * @param stage Debug stage number. + * + * @return True if key rotation should continue as normal, false to introduce an error. */ -extern void (*devtool_keyrotate_probe)(int stage); +extern bool (*devtool_keyrotate_probe)(int stage); /// Debug function pointer variable for SPTPS key renewal /** This function pointer variable is a userspace tracepoint or debugger callback for diff --git a/src/meshlink-tiny.h b/src/meshlink-tiny.h index 953a0e9..ee3d32b 100644 --- a/src/meshlink-tiny.h +++ b/src/meshlink-tiny.h @@ -160,6 +160,73 @@ void meshlink_open_params_free(meshlink_open_params_t *params); */ bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) __attribute__((__warn_unused_result__)); +/// A callback that loads a configuration object. +/** @param mesh A handle which represents an instance of MeshLink. + * @param key The key under which the configuration object was stored before. + * @param[out] data A pointer to a pointer to the buffer where the object has to be copied into. + * No more than len bytes should be written to this buffer. + * @param[in,out] len A pointer to the size of object. + * MeshLink sets this to the size of the buffer. + * The application must write the actual size of the object to it, + * regardless of whether it is larger or smaller than the buffer. + * + * @return This function must return true if the object was read and copied into the buffer succesfully, + * false otherwise. + * If the buffer provided by MeshLink was too small for the whole object, but no other errors + * occured, true must be returned. + */ +typedef bool (*meshlink_load_cb_t)(struct meshlink_handle *mesh, const char *filename, void *data, size_t *len); + +/// A callback that stores a configuration object. +/** @param mesh A handle which represents an instance of MeshLink. + * @param key The key under which the object should be stored. + * @param data A pointer to the buffer holding the data that must be stored. + * @param len The size of the buffer that must be stored. + * + * @return This function must return true if the file was written succesfully, false otherwise. + */ +typedef bool (*meshlink_store_cb_t)(struct meshlink_handle *mesh, const char *filename, const void *data, size_t len); + +/// A callback that reports the presence of a configuration object. +/** @param mesh A handle which represents an instance of MeshLink. + * @param key The key the object is stored under. + * @param len The size of the object if easy to obtain, zero otherwise. + * + * @return The return value. If it is false, the ls callback should immediately stop and return false as well. + */ +typedef bool (*meshlink_ls_entry_cb_t)(struct meshlink_handle *mesh, const char *key, size_t len); + +/// A callback that lists all configuration files. +/** @param mesh A handle which represents an instance of MeshLink. + * @param entry_cb A callback that must be called once for every configuration file found. + * + * @return This function must return true if all configuration files were listed + * and all entry callbacks return true as well, false otherwise. + */ +typedef bool (*meshlink_ls_cb_t)(struct meshlink_handle *mesh, meshlink_ls_entry_cb_t entry_cb); + +/// Set the callbacks MeshLink should use for local storage. +/** This function allows the application to provide callbacks which handle loading and storing configuration files. + * If storage callbacks are used, the application is in control of how and where configuration files are stored. + * The application should then also ensure that only one MeshLink instance can access the configuration files at a time. + * + * The callbacks get a relative filename which may contain directory separators, but there is no requirement that + * the application also stores the files in directories. The only requirement is that a file stored with a given filename + * can be loaded back using the same filename. + * + * When storing files, it must ensure stores are done atomically. + * + * If a storage key is set, MeshLink will take care of encrypting and decrypting the buffers passed to the storage callbacks. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + * @param load_cb A pointer to the function that will be called when MeshLink wants to load a configuration file. + * @param store_cb A pointer to the function that will be called when MeshLink wants to store a configuration file. + * @param ls_cb A pointer to the function that will be called when MeshLink wants to know which configuration files are available. + * + * @return This function will return true if the callbacks have been successfully updated, false otherwise. + */ +bool meshlink_open_params_set_storage_callbacks(meshlink_open_params_t *params, meshlink_load_cb_t load_cb, meshlink_store_cb_t store_cb, meshlink_ls_cb_t ls_cb) __attribute__((__warn_unused_result__)); + /// Set the encryption key MeshLink should use for local storage. /** This function changes the open parameters to use the given key for encrypting MeshLink's own configuration files. * diff --git a/src/meshlink.c b/src/meshlink.c index aadbaf3..4d7491a 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -154,6 +154,11 @@ static bool write_main_config_files(meshlink_handle_t *mesh) { return true; } + /* Write our own host config file */ + if(!node_write_config(mesh, mesh->self, true)) { + return false; + } + uint8_t buf[BUFSIZE]; /* Write the main config file */ @@ -171,16 +176,7 @@ static bool write_main_config_files(meshlink_handle_t *mesh) { config_t config = {buf, packmsg_output_size(&out, buf)}; - if(!main_config_write(mesh, "current", &config, mesh->config_key)) { - return false; - } - - /* Write our own host config file */ - if(!node_write_config(mesh, mesh->self, true)) { - return false; - } - - return true; + return config_store(mesh, "meshlink.conf", &config); } typedef struct { @@ -230,11 +226,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { mesh->self->name = xstrdup(name); mesh->self->devclass = devclass == DEV_CLASS_UNKNOWN ? mesh->devclass : devclass; - // Initialize configuration directory - if(!config_init(mesh, "current")) { - return false; - } - if(!write_main_config_files(mesh)) { return false; } @@ -303,11 +294,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { node_add(mesh, n); } - /* Ensure the configuration directory metadata is on disk */ - if(!config_sync(mesh, "current") || (mesh->confbase && !sync_path(mesh->confbase))) { - return false; - } - if(!mesh->inviter_commits_first) { devtool_set_inviter_commits_first(false); } @@ -500,22 +486,20 @@ static struct timespec idle(event_loop_t *loop, void *data) { }; } -static bool meshlink_setup(meshlink_handle_t *mesh) { - if(!config_destroy(mesh->confbase, "new")) { - logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/new: %s\n", mesh->confbase, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } +static bool invalidate_config_file(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; - if(!config_destroy(mesh->confbase, "old")) { - logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/old: %s\n", mesh->confbase, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(!check_id(name)) { + return true; } - if(!config_init(mesh, "current")) { - logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/current: %s\n", mesh->confbase, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; + config_t empty_config = {NULL, 0}; + return config_store(mesh, name, &empty_config); +} + +static bool meshlink_setup(meshlink_handle_t *mesh) { + // Remove stray config files + if(!config_ls(mesh, invalidate_config_file)) { return false; } @@ -540,18 +524,13 @@ static bool meshlink_setup(meshlink_handle_t *mesh) { return false; } - /* Ensure the configuration directory metadata is on disk */ - if(!config_sync(mesh, "current")) { - return false; - } - return true; } static bool meshlink_read_config(meshlink_handle_t *mesh) { config_t config; - if(!main_config_read(mesh, "current", &config, mesh->config_key)) { + if(!config_load(mesh, "meshlink.conf", &config)) { logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!"); return false; } @@ -586,6 +565,8 @@ static bool meshlink_read_config(meshlink_handle_t *mesh) { mesh->private_key = ecdsa_set_private_key(private_key); config_free(&config); + config_cleanup_old_files(mesh); + /* Create a node for ourself and read our host configuration file */ mesh->self = new_node(); @@ -676,6 +657,31 @@ bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) { return true; } +bool meshlink_open_params_set_storage_callbacks(meshlink_open_params_t *params, meshlink_load_cb_t load_cb, meshlink_store_cb_t store_cb, meshlink_ls_cb_t ls_cb) { + logger(NULL, MESHLINK_DEBUG, "meshlink_set_storage_callbacks(%p, %p)", (void *)(intptr_t)load_cb, (void *)(intptr_t)store_cb); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(load_cb && (!store_cb || !ls_cb)) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!load_cb && (store_cb || ls_cb)) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + params->load_cb = load_cb; + params->store_cb = store_cb; + params->ls_cb = ls_cb; + + return true; +} + bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const void *key, size_t keylen) { logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_storage_key(%p, %zu)", key, keylen); @@ -737,8 +743,7 @@ bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, } // Create hash for the new key - void *new_config_key; - new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN); + void *new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN); if(!prf(new_key, new_keylen, "MeshLink configuration key", 26, new_config_key, CHACHA_POLY1305_KEYLEN)) { logger(mesh, MESHLINK_ERROR, "Error creating new configuration key!\n"); @@ -747,51 +752,12 @@ bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, return false; } - // Copy contents of the "current" confbase sub-directory to "new" confbase sub-directory with the new key - - if(!config_copy(mesh, "current", mesh->config_key, "new", new_config_key)) { - logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/old: %s\n", mesh->confbase, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - pthread_mutex_unlock(&mesh->mutex); - return false; - } - - devtool_keyrotate_probe(1); - - // Rename confbase/current/ to confbase/old - - if(!config_rename(mesh, "current", "old")) { - logger(mesh, MESHLINK_ERROR, "Cannot rename %s/current to %s/old\n", mesh->confbase, mesh->confbase); - meshlink_errno = MESHLINK_ESTORAGE; - pthread_mutex_unlock(&mesh->mutex); - return false; - } - - devtool_keyrotate_probe(2); - - // Rename confbase/new/ to confbase/current - - if(!config_rename(mesh, "new", "current")) { - logger(mesh, MESHLINK_ERROR, "Cannot rename %s/new to %s/current\n", mesh->confbase, mesh->confbase); - meshlink_errno = MESHLINK_ESTORAGE; - pthread_mutex_unlock(&mesh->mutex); - return false; - } - - devtool_keyrotate_probe(3); - - // Cleanup the "old" confbase sub-directory - - if(!config_destroy(mesh->confbase, "old")) { + if(!config_change_key(mesh, new_config_key)) { + logger(mesh, MESHLINK_ERROR, "Error rotating the configuration key!\n"); pthread_mutex_unlock(&mesh->mutex); return false; } - // Change the mesh handle key with new key - - free(mesh->config_key); - mesh->config_key = new_config_key; - pthread_mutex_unlock(&mesh->mutex); return true; @@ -965,6 +931,9 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { mesh->log_cb = global_log_cb; mesh->log_level = global_log_level; mesh->packet = xmalloc(sizeof(vpn_packet_t)); + mesh->load_cb = params->load_cb; + mesh->store_cb = params->store_cb; + mesh->ls_cb = params->ls_cb; randomize(&mesh->prng_state, sizeof(mesh->prng_state)); @@ -1006,7 +975,7 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { meshlink_queue_init(&mesh->outpacketqueue); // Atomically lock the configuration directory. - if(!main_config_lock(mesh, params->lock_filename)) { + if(!config_init(mesh, params)) { meshlink_close(mesh); return NULL; } @@ -1015,24 +984,23 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { bool new_configuration = false; - if(!meshlink_confbase_exists(mesh)) { + if(!meshlink_read_config(mesh)) { if(!mesh->name) { - logger(NULL, MESHLINK_ERROR, "No configuration files found!\n"); + logger(NULL, MESHLINK_ERROR, "No valid configuration files found!\n"); meshlink_close(mesh); meshlink_errno = MESHLINK_ESTORAGE; return NULL; } - if(!meshlink_setup(mesh)) { - logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n"); + if(config_exists(mesh, "meshlink.conf")) { + logger(NULL, MESHLINK_ERROR, "Cannot read existing configuration!\n"); meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; return NULL; } - new_configuration = true; - } else { - if(!meshlink_read_config(mesh)) { - logger(NULL, MESHLINK_ERROR, "Cannot read main configuration\n"); + if(!meshlink_setup(mesh)) { + logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n"); meshlink_close(mesh); return NULL; } @@ -1279,7 +1247,7 @@ void meshlink_close(meshlink_handle_t *mesh) { free(mesh->packet); ecdsa_free(mesh->private_key); - main_config_unlock(mesh); + config_exit(mesh); pthread_mutex_unlock(&mesh->mutex); pthread_mutex_destroy(&mesh->mutex); @@ -1297,63 +1265,7 @@ bool meshlink_destroy_ex(const meshlink_open_params_t *params) { return false; } - if(!params->confbase) { - /* Ephemeral instances */ - return true; - } - - /* Exit early if the confbase directory itself doesn't exist */ - if(access(params->confbase, F_OK) && errno == ENOENT) { - return true; - } - - /* Take the lock the same way meshlink_open() would. */ - FILE *lockfile = fopen(params->lock_filename, "w+"); - - if(!lockfile) { - logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - -#ifdef FD_CLOEXEC - fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC); -#endif - -#ifdef HAVE_MINGW - // TODO: use _locking()? -#else - - if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) { - logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename); - fclose(lockfile); - meshlink_errno = MESHLINK_EBUSY; - return false; - } - -#endif - - if(!config_destroy(params->confbase, "current") || !config_destroy(params->confbase, "new") || !config_destroy(params->confbase, "old")) { - logger(NULL, MESHLINK_ERROR, "Cannot remove sub-directories in %s: %s\n", params->confbase, strerror(errno)); - return false; - } - - if(unlink(params->lock_filename)) { - logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno)); - fclose(lockfile); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - fclose(lockfile); - - if(!sync_path(params->confbase)) { - logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } - - return true; + return config_destroy(params); } bool meshlink_destroy(const char *confbase) { @@ -1688,8 +1600,7 @@ bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *no } pthread_mutex_unlock(&mesh->mutex); - - return config_sync(mesh, "current"); + return true; } bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node) { @@ -1714,8 +1625,7 @@ bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t * } pthread_mutex_unlock(&mesh->mutex); - - return config_sync(mesh, "current"); + return true; } bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { @@ -2168,10 +2078,6 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { return false; } - if(!config_sync(mesh, "current")) { - return false; - } - return true; } diff --git a/src/meshlink.sym b/src/meshlink.sym index 44c99d0..226e682 100644 --- a/src/meshlink.sym +++ b/src/meshlink.sym @@ -40,6 +40,7 @@ meshlink_open_params_free meshlink_open_params_init meshlink_open_params_set_lock_filename meshlink_open_params_set_netns +meshlink_open_params_set_storage_callbacks meshlink_open_params_set_storage_key meshlink_open_params_set_storage_policy meshlink_reset_timers diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h index f77d700..032873a 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -59,6 +59,9 @@ struct meshlink_open_params { const void *key; size_t keylen; meshlink_storage_policy_t storage_policy; + meshlink_load_cb_t load_cb; + meshlink_store_cb_t store_cb; + meshlink_ls_cb_t ls_cb; }; /// Device class traits @@ -135,6 +138,11 @@ struct meshlink_handle { void *config_key; char *external_address_url; meshlink_storage_policy_t storage_policy; + meshlink_load_cb_t load_cb; + meshlink_store_cb_t store_cb; + meshlink_ls_cb_t ls_cb; + + void *config_new_key; // Thread management pthread_t thread; diff --git a/src/net_setup.c b/src/net_setup.c index 7f8804b..8364e4f 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -33,7 +33,7 @@ /// Helper function to start parsing a host config file static bool node_get_config(meshlink_handle_t *mesh, node_t *n, config_t *config, packmsg_input_t *in) { - if(!config_read(mesh, "current", n->name, config, mesh->config_key)) { + if(!config_load(mesh, n->name, config)) { return false; } @@ -260,7 +260,7 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n, bool new_key) { config_t config = {buf, packmsg_output_size(&out, buf)}; - if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) { + if(!config_store(mesh, n->name, &config)) { call_error_cb(mesh, MESHLINK_ESTORAGE); return false; } @@ -269,19 +269,11 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n, bool new_key) { return true; } -static bool load_node(meshlink_handle_t *mesh, const char *name, void *priv) { - (void)priv; +static bool load_node(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; if(!check_id(name)) { - // Check if this is a temporary file, if so remove it - const char *suffix = strstr(name, ".tmp"); - - if(suffix && !suffix[4]) { - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s" SLASH "current" SLASH "hosts", mesh->confbase); - unlink(filename); - } - + // TODO: detect partial key rotation return true; } @@ -324,7 +316,7 @@ static bool setup_myself(meshlink_handle_t *mesh) { node_add(mesh, mesh->self); - if(!config_scan_all(mesh, "current", "hosts", load_node, NULL)) { + if(!config_ls(mesh, load_node)) { logger(mesh, MESHLINK_WARNING, "Could not scan all host config files"); } diff --git a/test/basic.c b/test/basic.c index 1fd39ec..0682c03 100644 --- a/test/basic.c +++ b/test/basic.c @@ -87,24 +87,14 @@ int main(void) { // Check that messing with the config directory will create a new instance. - assert(unlink("basic_conf/current/meshlink.conf") == 0); + assert(unlink("basic_conf/meshlink.conf") == 0); mesh = meshlink_open("basic_conf", "bar", "basic", DEV_CLASS_BACKBONE); assert(mesh); assert(!meshlink_get_node(mesh, "foo")); self = meshlink_get_self(mesh); assert(self); assert(!strcmp(self->name, "bar")); - assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); - meshlink_close(mesh); - - assert(rename("basic_conf/current", "basic_conf/new") == 0); - mesh = meshlink_open("basic_conf", "baz", "basic", DEV_CLASS_BACKBONE); - assert(mesh); - assert(!meshlink_get_node(mesh, "bar")); - self = meshlink_get_self(mesh); - assert(self); - assert(!strcmp(self->name, "baz")); - assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); + assert(access("basic_conf/foo", X_OK) == -1 && errno == ENOENT); meshlink_close(mesh); // Destroy the mesh. @@ -114,14 +104,18 @@ int main(void) { // Check that the configuration directory is completely empty. DIR *dir = opendir("basic_conf"); - assert(dir); - struct dirent *ent; - while((ent = readdir(dir))) { - assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); - } + if(dir) { + struct dirent *ent; - closedir(dir); + while((ent = readdir(dir))) { + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + + closedir(dir); + } else { + assert(errno == ENOENT); + } // Check that we can destroy it again. diff --git a/test/encrypted.c b/test/encrypted.c index 4f354e4..28ac327 100644 --- a/test/encrypted.c +++ b/test/encrypted.c @@ -11,10 +11,19 @@ #include #include "meshlink-tiny.h" +#include "devtools.h" #include "utils.h" +static int keyrotate_fail_stage; + +static bool keyrotate_probe(int stage) { + fprintf(stderr, "%d\n", stage); + return stage != keyrotate_fail_stage; +} + int main(void) { meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + devtool_keyrotate_probe = keyrotate_probe; // Open a new meshlink instance. @@ -34,6 +43,44 @@ int main(void) { mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5); assert(mesh); + // Rotate the key + + assert(meshlink_encrypted_key_rotate(mesh, "newkey", 6)); + + // Check that we cannot open it with the old key + + meshlink_close(mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5); + assert(!mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert(mesh); + + // Check how key rotation failures are handled + + keyrotate_fail_stage = 1; // Fail before committing to the new key + assert(!meshlink_encrypted_key_rotate(mesh, "newkey2", 7)); + meshlink_close(mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey2", 7); + assert(!mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert(mesh); + + keyrotate_fail_stage = 2; // Fail after committing to the new key, before cleaning up old files + assert(meshlink_encrypted_key_rotate(mesh, "newkey3", 7)); + meshlink_close(mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert(!mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey3", 7); + assert(mesh); + + keyrotate_fail_stage = 3; // Fail after committing to the new key and cleaning up old files + assert(meshlink_encrypted_key_rotate(mesh, "newkey4", 7)); + meshlink_close(mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey3", 7); + assert(!mesh); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey4", 7); + assert(mesh); + // That's it. meshlink_close(mesh); @@ -43,13 +90,18 @@ int main(void) { assert(meshlink_destroy("encrypted_conf")); DIR *dir = opendir("encrypted_conf"); - assert(dir); - struct dirent *ent; - while((ent = readdir(dir))) { - fprintf(stderr, "%s\n", ent->d_name); - assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + if(dir) { + struct dirent *ent; + + while((ent = readdir(dir))) { + fprintf(stderr, "%s\n", ent->d_name); + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + + closedir(dir); + } else { + assert(errno == ENOENT); } - closedir(dir); } diff --git a/test/invite-join.c b/test/invite-join.c index c025304..b6c34fc 100644 --- a/test/invite-join.c +++ b/test/invite-join.c @@ -34,7 +34,7 @@ static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reach static void invitee_commits_first_cb(bool inviter_first) { // Check that eight has committed foo's host config file, but foo hasn't committed eight's - assert(access("invite_join_conf.8/current/hosts/foo", F_OK) == 0); + assert(access("invite_join_conf.8/foo", F_OK) == 0); assert(access("invite_join_conf.1/current/hosts/eight", F_OK) == -1 && errno == ENOENT); set_sync_flag(&commits_first_flag, !inviter_first); } @@ -42,7 +42,7 @@ static void invitee_commits_first_cb(bool inviter_first) { static void inviter_commits_first_cb(bool inviter_first) { // Check that foo has committed nine's host config file, but nine hasn't committed foo's assert(access("invite_join_conf.1/current/hosts/nine", F_OK) == 0); - assert(access("invite_join_conf.9/current/hosts/foo", F_OK) == -1 && errno == ENOENT); + assert(access("invite_join_conf.9/foo", F_OK) == -1 && errno == ENOENT); set_sync_flag(&commits_first_flag, inviter_first); } @@ -55,7 +55,7 @@ int main(void) { meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); - assert(meshlink_destroy("invite_join_conf.1")); + assert(full_meshlink_destroy("invite_join_conf.1")); assert(meshlink_destroy("invite_join_conf.2")); assert(meshlink_destroy("invite_join_conf.3")); assert(meshlink_destroy("invite_join_conf.4"));