From 2edc54a7728084cc024b12e64a1c7c7eb533bb93 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 9 Oct 2022 21:02:31 +0200 Subject: [PATCH] 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. --- configure.ac | 2 +- src/conf.c | 1222 ++++++++++++++---------------------- src/conf.h | 45 +- src/devtools.c | 6 +- src/devtools.h | 7 +- src/meshlink++.h | 112 ++++ src/meshlink.c | 241 +++---- src/meshlink.h | 67 ++ src/meshlink.sym | 1 + src/meshlink_internal.h | 8 + src/net_setup.c | 14 +- src/protocol_auth.c | 35 +- test/Makefile.am | 5 + test/basic.c | 16 +- test/basicpp.cpp | 7 +- test/blacklist.c | 13 +- test/encrypted.c | 52 +- test/invite-join.c | 8 +- test/storage-callbacks.cpp | 128 ++++ test/utils.h | 8 + 20 files changed, 984 insertions(+), 1013 deletions(-) create mode 100644 test/storage-callbacks.cpp diff --git a/configure.ac b/configure.ac index 022bba7b..872c95b2 100644 --- a/configure.ac +++ b/configure.ac @@ -128,7 +128,7 @@ MeshLink_ATTRIBUTE(__malloc__) MeshLink_ATTRIBUTE(__warn_unused_result__) dnl Checks for library functions. -AC_CHECK_FUNCS([asprintf fchmod fork gettimeofday random pselect select setns strdup usleep getifaddrs freeifaddrs], +AC_CHECK_FUNCS([asprintf fchmod fork gettimeofday random pselect select setns strdup usleep getifaddrs freeifaddrs flock], [], [], [#include "$srcdir/src/have.h"] ) diff --git a/src/conf.c b/src/conf.c index db91d5fb..22d3845e 100644 --- a/src/conf.c +++ b/src/conf.c @@ -18,6 +18,7 @@ */ #include "system.h" + #include #include #include @@ -26,83 +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); -} - -/// Generate a path to an unused invitation file. -static void make_invitation_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 "invitations" SLASH "%s", mesh->confbase, conf_subdir, name); -} - -/// Generate a path to a used invitation file. -static void make_used_invitation_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 "invitations" SLASH "%s.used", 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); @@ -130,443 +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); - - if(!deltree(path)) { - logger(mesh, MESHLINK_DEBUG, "Could not delete directory %s: %s\n", path, strerror(errno)); - return false; - } - - if(mkdir(path, 0700)) { - logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); - return false; - } + FILE *lockfile = NULL; - make_host_path(mesh, conf_subdir, "", path, sizeof(path)); + 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_invitation_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; + { + meshlink_handle_t tmp_mesh; + memset(&tmp_mesh, 0, sizeof tmp_mesh); - char path[PATH_MAX]; - - // 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); + fclose(lockfile); - 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; - } - - 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]; + 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; + } + } - 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); + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s", mesh->confbase, key); - return copytree(src_filename, src_key, dst_filename, dst_key); -} + FILE *f = fopen(filename, "r"); -/// Check the presence of the main configuration file. -bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) { - assert(conf_subdir); - - 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; -} - -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); + long actual_len; - 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]; - - 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); + size_t todo = (size_t)actual_len < *len ? (size_t)actual_len : *len; + *len = actual_len; - 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; +/// 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(main_config_exists(mesh, "current")) { - confbase_exists = true; - - 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; - - if(main_config_decrypt(mesh, "old")) { - if(!config_destroy(mesh->confbase, "current")) { - return false; - } + char tmp_filename[PATH_MAX]; + snprintf(tmp_filename, sizeof(tmp_filename), "%s" SLASH "%s.tmp", mesh->confbase, key); - if(!config_rename(mesh, "old", "current")) { - return false; - } + FILE *f = fopen(tmp_filename, "w"); - 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_MINGW - // TODO: use _locking()? -#else - - 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; @@ -583,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; - } - if(fflush(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + return store(mesh, name, encrypted, 12 + encrypted_len); } - 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. @@ -643,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); +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)); - - 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); - - 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; } @@ -712,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; } @@ -722,394 +411,391 @@ 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); - - if(!mesh->confbase) { - return true; - } - - 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"); +/// 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(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; + if(!config_load(mesh, name, &config)) { 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; - } + size_t name_len = strlen(name); + char new_name[name_len + 3]; + memcpy(new_name, name, name_len); - if(fclose(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(!strcmp(name, "meshlink.conf")) { + // Update meshlink.conf in-place + new_name[name_len] = 0; + } else { + memcpy(new_name + name_len, ".r", 3); } - 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; - } + void *orig_key = mesh->config_key; + mesh->config_key = mesh->ls_priv; + bool result = config_store(mesh, new_name, &config); + mesh->config_key = orig_key; - return true; + return result; } -/// Delete a host configuration file. -bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) { - assert(conf_subdir); - assert(name); +extern bool (*devtool_keyrotate_probe)(int stage); - if(!mesh->confbase) { +static bool change_node_key(meshlink_handle_t *mesh, const char *name, size_t len) { + /* Skip the main config and lock files */ + if(!strcmp(name, "meshlink.conf") || !strcmp(name, "meshlink.lock")) { return true; } - char path[PATH_MAX]; - make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + /* Skip any already rotated file */ + int namelen = strlen(name); - if(unlink(path) && errno != ENOENT) { - logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(namelen >= 2 && !strcmp(name + namelen - 2, ".r")) { + return true; } - return true; -} - -/// 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); - - if(!mesh->confbase) { + if(!devtool_keyrotate_probe(0)) { return false; } - char path[PATH_MAX]; - make_main_path(mesh, conf_subdir, path, sizeof(path)); + return change_key(mesh, name, len); +} - FILE *f = fopen(path, "r"); +static bool cleanup_old_file(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; + size_t name_len = strlen(name); - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); - return false; + if(name_len < 3 || strcmp(name + name_len - 2, ".r")) { + return true; } - if(!config_read_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); - fclose(f); - return false; + config_t config; + + if(!config_load(mesh, name, &config)) { + store(mesh, name, NULL, 0); + return true; } - fclose(f); + char new_name[name_len - 1]; + memcpy(new_name, name, name_len - 2); + new_name[name_len - 2] = '\0'; - return true; + return config_store(mesh, new_name, &config) && store(mesh, name, NULL, 0); } -/// 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); - - 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); +bool config_cleanup_old_files(meshlink_handle_t *mesh) { + return config_ls(mesh, cleanup_old_file); +} - FILE *f = fopen(tmp_path, "w"); +bool config_change_key(meshlink_handle_t *mesh, void *new_key) { + mesh->ls_priv = new_key; - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; + if(!config_ls(mesh, change_node_key)) { 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); + if(!devtool_keyrotate_probe(1)) { return false; } - 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); + if(!change_key(mesh, "meshlink.conf", 0)) { return false; } - if(fclose(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + 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; } -/// Read an invitation file from the confbase sub-directory, and immediately delete it. -bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) { - assert(conf_subdir); - assert(name); - assert(config); +/// Migrate old format configuration directory +static bool config_migrate(meshlink_handle_t *mesh) { + char base[PATH_MAX]; + char path[PATH_MAX]; + char new_path[PATH_MAX]; - if(!mesh->confbase) { - return false; - } + // Check if there we need to migrate - char path[PATH_MAX]; - char used_path[PATH_MAX]; - make_invitation_path(mesh, conf_subdir, name, path, sizeof(path)); - make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path)); - - // Atomically rename the invitation file - if(rename(path, used_path)) { - if(errno == ENOENT) { - logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name); - } else { - logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name); - } + snprintf(path, sizeof path, "%s/meshlink.conf", mesh->confbase); - return false; + if(access(path, F_OK) == 0) { + return true; } - FILE *f = fopen(used_path, "r"); + snprintf(path, sizeof path, "%s/current/meshlink.conf", mesh->confbase); - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); - return false; + if(access(path, F_OK) == -1) { + return true; } - // Check the timestamp - struct stat st; + // Migrate host config files - if(fstat(fileno(f), &st)) { - logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name); - fclose(f); - unlink(used_path); - return false; - } + DIR *dir; + struct dirent *ent; - if(time(NULL) >= st.st_mtime + mesh->invitation_timeout) { - logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name); - fclose(f); - unlink(used_path); - return false; - } + snprintf(base, sizeof base, "%s/current/hosts", mesh->confbase); + dir = opendir(base); - if(!config_read_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); - fclose(f); - unlink(used_path); + if(!dir) { + logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", base, mesh->confbase, strerror(errno)); return false; } - fclose(f); + while((ent = readdir(dir))) { + if(ent->d_name[0] == '.') { + continue; + } - if(unlink(used_path)) { - logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno)); - return false; - } + if(!check_id(ent->d_name)) { + continue; + } - snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir); + snprintf(path, sizeof path, "%s/current/hosts/%s", mesh->confbase, ent->d_name); + snprintf(new_path, sizeof new_path, "%s/%s", mesh->confbase, ent->d_name); - if(!sync_path(path)) { - logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(rename(path, new_path) == -1) { + logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno)); + closedir(dir); + return false; + } } - return true; -} + closedir(dir); -/// Write an invitation file. -bool invitation_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); + // Migrate invitation files - if(!mesh->confbase) { + snprintf(base, sizeof base, "%s/current/invitations", mesh->confbase); + dir = opendir(base); + + if(!dir) { + logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", base, mesh->confbase, strerror(errno)); return false; } - char path[PATH_MAX]; - make_invitation_path(mesh, conf_subdir, name, path, sizeof(path)); + while((ent = readdir(dir))) { + if(ent->d_name[0] == '.') { + continue; + } - FILE *f = fopen(path, "w"); + snprintf(path, sizeof path, "%s/current/invitations/%s", mesh->confbase, ent->d_name); + snprintf(new_path, sizeof new_path, "%s/%s.inv", mesh->confbase, ent->d_name); - if(!f) { - logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; + if(rename(path, new_path) == -1) { + logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno)); + closedir(dir); + return false; + } } - if(!config_write_file(mesh, f, config, key)) { - logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno)); - fclose(f); - return false; - } + closedir(dir); - if(fclose(f)) { - logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return false; - } + // Migrate meshlink.conf - snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir); + snprintf(path, sizeof path, "%s/current/meshlink.conf", mesh->confbase); + snprintf(new_path, sizeof new_path, "%s/meshlink.conf", mesh->confbase); - if(!sync_path(path)) { - logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; + if(rename(path, new_path) == -1) { + logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno)); return false; } + // Remove directories that should now be empty + + snprintf(base, sizeof base, "%s/current/hosts", mesh->confbase); + rmdir(base); + snprintf(base, sizeof base, "%s/current/invitations", mesh->confbase); + rmdir(base); + snprintf(base, sizeof base, "%s/current", mesh->confbase); + rmdir(base); + + // Done. + + return true; } -/// Purge old invitation files -size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) { +/// 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]; - make_invitation_path(mesh, "current", "", path, sizeof(path)); - - DIR *dir = opendir(path); - - if(!dir) { - logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return 0; - } + 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; + } - errno = 0; - size_t count = 0; - struct dirent *ent; + mesh->lockfile = fopen(params->lock_filename, "w+"); - while((ent = readdir(dir))) { - if(strlen(ent->d_name) != 24) { - continue; + if(!mesh->lockfile) { + logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", params->lock_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; } - char invname[PATH_MAX]; - struct stat st; +#ifdef FD_CLOEXEC + fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC); +#endif - if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) { - logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name); - continue; +#ifdef HAVE_FLOCK + + 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; } - if(!stat(invname, &st)) { - if(mesh->invitation_key && deadline < st.st_mtime) { - count++; - } else { - unlink(invname); - } - } else { - logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno)); - errno = 0; +#endif + + if(!config_migrate(mesh)) { + return false; } } - if(errno) { - logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno)); - closedir(dir); - meshlink_errno = MESHLINK_ESTORAGE; - return 0; + return true; +} + +void config_exit(meshlink_handle_t *mesh) { + if(mesh->lockfile) { + fclose(mesh->lockfile); + mesh->lockfile = NULL; } +} - closedir(dir); +static void make_invitation_path(const char *name, char *path, size_t len) { + assert(name); + assert(path); + assert(len); - return count; + snprintf(path, len, "%s.inv", name); } -/// Purge invitations for the given node -size_t invitation_purge_node(meshlink_handle_t *mesh, const char *node_name) { - if(!mesh->confbase) { - return true; +/// Read an invitation file from the confbase sub-directory, and immediately delete it. +bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) { + assert(name); + assert(config); + + char invitation_name[PATH_MAX]; + make_invitation_path(name, invitation_name, sizeof(invitation_name)); + + if(!config_load(mesh, invitation_name, config)) { + logger(mesh, MESHLINK_ERROR, "Could not read invitation file %s\n", invitation_name); + return false; } - char path[PATH_MAX]; - make_invitation_path(mesh, "current", "", path, sizeof(path)); + // Make sure the file is deleted so it cannot be reused + if(!invalidate_config_file(mesh, invitation_name, 0)) { + logger(mesh, MESHLINK_ERROR, "Could not delete invitation file %s\n", invitation_name); + config_free(config); + return false; + } - DIR *dir = opendir(path); + return true; +} - if(!dir) { - logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno)); - meshlink_errno = MESHLINK_ESTORAGE; - return 0; +/// Write an invitation file. +bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) { + assert(name); + assert(config); + + char invitation_name[PATH_MAX]; + make_invitation_path(name, invitation_name, sizeof(invitation_name)); + + if(!config_store(mesh, invitation_name, config)) { + logger(mesh, MESHLINK_ERROR, "Could not write invitation file %s\n", invitation_name); + return false; } - errno = 0; - size_t count = 0; - struct dirent *ent; + return true; +} - while((ent = readdir(dir))) { - if(strlen(ent->d_name) != 24) { - continue; - } +typedef struct { + time_t deadline; + const char *node_name; + size_t count; +} purge_info_t; - char invname[PATH_MAX]; +static bool purge_cb(meshlink_handle_t *mesh, const char *name, size_t len) { + (void)len; + purge_info_t *info = mesh->ls_priv; + size_t namelen = strlen(name); - if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) { - logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name); - continue; - } + // Skip anything that is not an invitation + if(namelen < 4 || strcmp(name + namelen - 4, ".inv") != 0) { + return true; + } - FILE *f = fopen(invname, "r"); + config_t config; - if(!f) { - errno = 0; - continue; - } + if(!config_load(mesh, name, &config)) { + logger(mesh, MESHLINK_ERROR, "Could not read invitation file %s\n", name); + // Purge anything we can't read + invalidate_config_file(mesh, name, 0); + info->count++; + return true; + } - config_t config; + packmsg_input_t in = {config.buf, config.len}; + uint32_t version = packmsg_get_uint32(&in); + time_t timestamp = packmsg_get_int64(&in); - if(!config_read_file(mesh, f, &config, mesh->config_key)) { - logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", invname, strerror(errno)); - config_free(&config); - fclose(f); - errno = 0; - continue; - } + if(!packmsg_input_ok(&in) || version != MESHLINK_INVITATION_VERSION) { + logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", name); + invalidate_config_file(mesh, name, 0); + info->count++; + } - packmsg_input_t in = {config.buf, config.len}; - packmsg_get_uint32(&in); // skip version - char *name = packmsg_get_str_dup(&in); + if(info->node_name) { + char *node_name = packmsg_get_str_dup(&in); - if(name && !strcmp(name, node_name)) { - logger(mesh, MESHLINK_DEBUG, "Removing invitation for %s", node_name); - unlink(invname); + if(!node_name || strcmp(node_name, info->node_name) == 0) { + invalidate_config_file(mesh, name, 0); + info->count++; } - free(name); - config_free(&config); - fclose(f); + free(node_name); + } else if(timestamp < info->deadline) { + invalidate_config_file(mesh, name, 0); + info->count++; } - if(errno) { - logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno)); - closedir(dir); - meshlink_errno = MESHLINK_ESTORAGE; - return 0; + config_free(&config); + return true; +} + +/// Purge old invitation files +size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) { + purge_info_t info = {deadline, NULL, 0}; + mesh->ls_priv = &info; + + if(!config_ls(mesh, purge_cb)) { + /* Ignore any failure */ } - closedir(dir); + return info.count; +} + +/// Purge invitations for the given node +size_t invitation_purge_node(meshlink_handle_t *mesh, const char *node_name) { + purge_info_t info = {0, node_name, 0}; + mesh->ls_priv = &info; + + if(!config_ls(mesh, purge_cb)) { + /* Ignore any failure */ + } - return count; + return info.count; } diff --git a/src/conf.h b/src/conf.h index 656a0acc..b0f10db2 100644 --- a/src/conf.h +++ b/src/conf.h @@ -21,42 +21,33 @@ */ 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 invitation_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__)); -bool invitation_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __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); + +bool invitation_read(struct meshlink_handle *mesh, const char *name, struct config_t *) __attribute__((__warn_unused_result__)); +bool invitation_write(struct meshlink_handle *mesh, const char *name, const struct config_t *) __attribute__((__warn_unused_result__)); size_t invitation_purge_old(struct meshlink_handle *mesh, time_t deadline); -size_t invitation_purge_node(struct meshlink_handle *mesh, const char *node_name); +size_t invitation_purge_node(struct meshlink_handle *mesh, const char *name); #endif diff --git a/src/devtools.c b/src/devtools.c index 12c5432e..50ff149a 100644 --- a/src/devtools.c +++ b/src/devtools.c @@ -34,9 +34,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) { @@ -50,7 +50,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_adns_resolve_probe)(void) = 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 78e36428..14fbfb8b 100644 --- a/src/devtools.h +++ b/src/devtools.h @@ -179,10 +179,13 @@ extern void (*devtool_trybind_probe)(void); /** This function pointer variable is a userspace tracepoint or debugger callback for * encrypted key rotation function @a meshlink_encrypted_key_rotate @a. * On assigning a debug function variable invokes callback for each stage from the key rotate API. + * Returning false will cause the key rotation to be aborted. * - * @param stage Debug stage number. + * @param stage Debug stage number. + * + * @return Whether the key rotation should continue succesfully. */ -extern void (*devtool_keyrotate_probe)(int stage); +extern bool (*devtool_keyrotate_probe)(int stage); /// Debug function pointer variable for asynchronous DNS resolving extern void (*devtool_adns_resolve_probe)(void); diff --git a/src/meshlink++.h b/src/meshlink++.h index 9e97fc82..4af10518 100644 --- a/src/meshlink++.h +++ b/src/meshlink++.h @@ -138,6 +138,52 @@ typedef void (*aio_cb_t)(mesh *mesh, channel *channel, const void *data, size_t */ typedef void (*aio_fd_cb_t)(mesh *mesh, channel *channel, int fd, size_t len, void *priv); +/// 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 (*load_cb_t)(mesh *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 (*store_cb_t)(mesh *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 (*ls_entry_cb_t)(mesh *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 (*ls_cb_t)(mesh *mesh, ls_entry_cb_t entry_cb); + + /// A class describing a MeshLink node. class node: public meshlink_node_t { }; @@ -158,6 +204,39 @@ public: static const uint32_t UDP = MESHLINK_CHANNEL_UDP; }; +class open_params { + friend class mesh; + meshlink_open_params_t *params; + +public: + open_params(const char *confbase, const char *name, const char *appname, dev_class_t devclass): + params(meshlink_open_params_init(confbase, name, appname, devclass)) {} + + ~open_params() { + meshlink_open_params_free(params); + } + + bool set_netns(int netns) { + return meshlink_open_params_set_netns(params, netns); + } + + bool set_storage_callbacks(meshlink_load_cb_t load_cb, meshlink_store_cb_t store_cb, meshlink_ls_cb_t ls_cb) { + return meshlink_open_params_set_storage_callbacks(params, load_cb, store_cb, ls_cb); + } + + bool set_storage_key(const void *key, size_t keylen) { + return meshlink_open_params_set_storage_key(params, key, keylen); + } + + bool set_storage_policy(meshlink_storage_policy_t policy) { + return meshlink_open_params_set_storage_policy(params, policy); + } + + bool set_lock_filename(const char *filename) { + return meshlink_open_params_set_lock_filename(params, filename); + } +}; + /// A class describing a MeshLink mesh. class mesh { public: @@ -207,10 +286,24 @@ public: return isOpen(); } + bool open(const open_params ¶ms) { + handle = meshlink_open_ex(params.params); + + if(handle) { + handle->priv = this; + } + + return isOpen(); + } + mesh(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { open(confbase, name, appname, devclass); } + mesh(const open_params ¶ms) { + open(params); + } + /// Close the MeshLink handle. /** This function calls meshlink_stop() if necessary, * and frees all memory allocated by MeshLink. @@ -1196,6 +1289,20 @@ public: meshlink_hint_network_change(handle); } + /// Performs key rotation for an encrypted storage + /** This rotates the (master) key for an encrypted storage and discards the old key + * if the call succeeded. This is an atomic call. + * + * \memberof meshlink_handle + * @param key A pointer to the new key used to encrypt storage. + * @param keylen The length of the new key in bytes. + * + * @return This function returns true if the key rotation for the encrypted storage succeeds, false otherwise. + */ + bool encrypted_key_rotate(const void *key, size_t keylen) { + return meshlink_encrypted_key_rotate(handle, key, keylen); + } + /// Set device class timeouts /** This sets the ping interval and timeout for a given device class. * @@ -1401,6 +1508,11 @@ static inline const char *strerror(errno_t err = meshlink_errno) { static inline bool destroy(const char *confbase) { return meshlink_destroy(confbase); } + + +static inline void set_log_cb(meshlink_log_level_t level, meshlink_log_cb_t cb) { + meshlink_set_log_cb(NULL, level, cb); +} } #endif diff --git a/src/meshlink.c b/src/meshlink.c index f425b4b2..5eea05ce 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -670,6 +670,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[4096]; /* Write the main config file */ @@ -687,16 +692,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 { @@ -723,6 +719,7 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { return false; } + packmsg_skip_element(&in); // timestamp char *name = packmsg_get_str_dup(&in); char *submesh_name = packmsg_get_str_dup(&in); dev_class_t devclass = packmsg_get_int32(&in); @@ -757,11 +754,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { free(submesh_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; } @@ -834,11 +826,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); } @@ -1072,22 +1059,20 @@ static void add_local_addresses(meshlink_handle_t *mesh) { } } -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) { + // Destroy stray config files + if(!config_ls(mesh, invalidate_config_file)) { return false; } @@ -1115,18 +1100,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; } @@ -1163,6 +1143,8 @@ static bool meshlink_read_config(meshlink_handle_t *mesh) { mesh->invitation_key = ecdsa_set_private_key(invitation_key); config_free(&config); + config_cleanup_old_files(mesh); + /* Create a node for ourself and read our host configuration file */ mesh->self = new_node(); @@ -1253,6 +1235,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); @@ -1314,8 +1321,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"); @@ -1324,51 +1330,13 @@ 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")) { + // Change the mesh handle key with new key + 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; @@ -1545,6 +1513,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)); @@ -1588,7 +1559,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; } @@ -1597,24 +1568,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; } @@ -1954,7 +1924,7 @@ void meshlink_close(meshlink_handle_t *mesh) { list_delete_list(mesh->invitation_addresses); } - main_config_unlock(mesh); + config_exit(mesh); pthread_mutex_unlock(&mesh->mutex); pthread_mutex_destroy(&mesh->mutex); @@ -1972,63 +1942,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) { @@ -2790,7 +2704,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) { @@ -2816,7 +2730,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_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) { @@ -2949,8 +2863,6 @@ bool meshlink_set_port(meshlink_handle_t *mesh, int port) { devtool_trybind_probe(); - bool rval = false; - if(pthread_mutex_lock(&mesh->mutex) != 0) { abort(); } @@ -2991,12 +2903,10 @@ bool meshlink_set_port(meshlink_handle_t *mesh, int port) { /* Write meshlink.conf with the updated port number */ write_main_config_files(mesh); - rval = config_sync(mesh, "current"); - done: pthread_mutex_unlock(&mesh->mutex); - return rval && meshlink_get_port(mesh) == port; + return meshlink_get_port(mesh) == port; } void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) { @@ -3040,7 +2950,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c } // Ensure no host configuration file with that name exists - if(config_exists(mesh, "current", name)) { + if(config_exists(mesh, name)) { logger(mesh, MESHLINK_ERROR, "A host config file for %s already exists!\n", name); meshlink_errno = MESHLINK_EEXIST; pthread_mutex_unlock(&mesh->mutex); @@ -3108,6 +3018,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c packmsg_output_t inv = {outbuf, sizeof(outbuf)}; packmsg_add_uint32(&inv, MESHLINK_INVITATION_VERSION); + packmsg_add_int64(&inv, time(NULL)); packmsg_add_str(&inv, name); packmsg_add_str(&inv, s ? s->name : CORE_MESH); packmsg_add_int32(&inv, DEV_CLASS_UNKNOWN); /* TODO: allow this to be set by inviter? */ @@ -3120,7 +3031,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c memset(configs, 0, sizeof(configs)); int count = 0; - if(config_read(mesh, "current", mesh->self->name, &configs[count], mesh->config_key)) { + if(config_load(mesh, mesh->self->name, &configs[count])) { count++; } @@ -3134,7 +3045,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c config_t config = {outbuf, packmsg_output_size(&inv, outbuf)}; - if(!invitation_write(mesh, "current", cookiehash, &config, mesh->config_key)) { + if(!invitation_write(mesh, cookiehash, &config)) { logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno)); meshlink_errno = MESHLINK_ESTORAGE; pthread_mutex_unlock(&mesh->mutex); @@ -3601,10 +3512,6 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { return false; } - if(!config_sync(mesh, "current")) { - return false; - } - return true; } @@ -3657,7 +3564,7 @@ static bool blacklist(meshlink_handle_t *mesh, node_t *n) { /* Remove any outstanding invitations */ invitation_purge_node(mesh, n->name); - return node_write_config(mesh, n, true) && config_sync(mesh, "current"); + return node_write_config(mesh, n, true); } bool meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) { @@ -3733,7 +3640,7 @@ static bool whitelist(meshlink_handle_t *mesh, node_t *n) { update_node_status(mesh, n); } - return node_write_config(mesh, n, true) && config_sync(mesh, "current"); + return node_write_config(mesh, n, true); } bool meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) { @@ -3841,7 +3748,7 @@ bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) { } /* Delete the config file for this node */ - if(!config_delete(mesh, "current", n->name)) { + if(!invalidate_config_file(mesh, n->name, 0)) { pthread_mutex_unlock(&mesh->mutex); return false; } @@ -3854,7 +3761,7 @@ bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) { pthread_mutex_unlock(&mesh->mutex); - return config_sync(mesh, "current"); + return true; } /* Hint that a hostname may be found at an address diff --git a/src/meshlink.h b/src/meshlink.h index 903bab36..15b940f5 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -185,6 +185,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.sym b/src/meshlink.sym index 8ece4dc5..5a3f9738 100644 --- a/src/meshlink.sym +++ b/src/meshlink.sym @@ -72,6 +72,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 b57f212c..8867eb9b 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -64,6 +64,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 @@ -168,6 +171,11 @@ struct meshlink_handle { char *external_address_url; struct list_t *invitation_addresses; 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 *ls_priv; // Thread management pthread_t thread; diff --git a/src/net_setup.c b/src/net_setup.c index a7a538f0..d4761ae8 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -36,7 +36,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; } @@ -281,7 +281,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; } @@ -290,10 +290,12 @@ 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)) { +#if 0 + // TODO: check partial key rotation // Check if this is a temporary file, if so remove it const char *suffix = strstr(name, ".tmp"); @@ -303,6 +305,8 @@ static bool load_node(meshlink_handle_t *mesh, const char *name, void *priv) { unlink(filename); } +#endif + return true; } @@ -581,7 +585,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/src/protocol_auth.c b/src/protocol_auth.c index 0a107e4c..d49f7ad2 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -71,7 +71,7 @@ static bool commit_invitation(meshlink_handle_t *mesh, connection_t *c, const vo // Remember its current address node_add_recent_address(mesh, n, &c->address); - if(!node_write_config(mesh, n, true) || !config_sync(mesh, "current")) { + if(!node_write_config(mesh, n, true)) { logger(mesh, MESHLINK_ERROR, "Error writing configuration file for invited node %s!\n", c->name); free_node(n); return false; @@ -103,16 +103,40 @@ static bool process_invitation(meshlink_handle_t *mesh, connection_t *c, const v config_t config; - if(!invitation_read(mesh, "current", cookie, &config, mesh->config_key)) { + if(!invitation_read(mesh, cookie, &config)) { logger(mesh, MESHLINK_ERROR, "Error while trying to read invitation file\n"); return false; } // Read the new node's Name from the file packmsg_input_t in = {config.buf, config.len}; - packmsg_get_uint32(&in); // skip version + uint32_t version = packmsg_get_uint32(&in); + + if(version != MESHLINK_INVITATION_VERSION) { + logger(mesh, MESHLINK_ERROR, "Invalid invitation file\n"); + config_free(&config); + return false; + } + + int64_t timestamp = packmsg_get_int64(&in); + + if(time(NULL) >= timestamp + mesh->invitation_timeout) { + logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", cookie); + config_free(&config); + return false; + } + + char *name = packmsg_get_str_dup(&in); + + if(!check_id(name)) { + logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie); + free(name); + config_free(&config); + return false; + } + free(c->name); - c->name = packmsg_get_str_dup(&in); + c->name = name; // Check if the file contains Sub-Mesh information char *submesh_name = packmsg_get_str_dup(&in); @@ -124,6 +148,7 @@ static bool process_invitation(meshlink_handle_t *mesh, connection_t *c, const v if(!check_id(submesh_name)) { logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie); free(submesh_name); + config_free(&config); return false; } @@ -132,11 +157,13 @@ static bool process_invitation(meshlink_handle_t *mesh, connection_t *c, const v if(!c->submesh) { logger(mesh, MESHLINK_ERROR, "Unknown submesh in invitation file %s\n", cookie); + config_free(&config); return false; } } if(mesh->inviter_commits_first && !commit_invitation(mesh, c, (const char *)data + 18)) { + config_free(&config); return false; } diff --git a/test/Makefile.am b/test/Makefile.am index 688d8b7d..e78cea07 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -26,6 +26,7 @@ TESTS = \ metering-tcponly \ meta-connections \ sign-verify \ + storage-callbacks \ storage-policy \ trio \ trio2 \ @@ -74,6 +75,7 @@ check_PROGRAMS = \ metering-tcponly \ meta-connections \ sign-verify \ + storage-callbacks \ storage-policy \ stream \ trio \ @@ -170,6 +172,9 @@ meta_connections_LDADD = $(top_builddir)/src/libmeshlink.la sign_verify_SOURCES = sign-verify.c utils.c utils.h sign_verify_LDADD = $(top_builddir)/src/libmeshlink.la +storage_callbacks_SOURCES = storage-callbacks.cpp utils.c utils.h +storage_callbacks_LDADD = $(top_builddir)/src/libmeshlink.la + storage_policy_SOURCES = storage-policy.c utils.c utils.h storage_policy_LDADD = $(top_builddir)/src/libmeshlink.la diff --git a/test/basic.c b/test/basic.c index 004e7186..e5c2eb8e 100644 --- a/test/basic.c +++ b/test/basic.c @@ -113,7 +113,7 @@ 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")); @@ -123,6 +123,7 @@ int main(void) { assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); meshlink_close(mesh); +#if 0 assert(rename("basic_conf/current", "basic_conf/new") == 0); mesh = meshlink_open("basic_conf", "baz", "basic", DEV_CLASS_BACKBONE); assert(mesh); @@ -132,22 +133,15 @@ int main(void) { assert(!strcmp(self->name, "baz")); assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); meshlink_close(mesh); +#endif // Destroy the mesh. assert(meshlink_destroy("basic_conf")); - // 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, "..")); - } + // Check that the configuration directory is completely gone. - closedir(dir); + assert(access("basic_conf", F_OK) == -1 && errno == ENOENT); // Check that we can destroy it again. diff --git a/test/basicpp.cpp b/test/basicpp.cpp index c0550ecf..79565887 100644 --- a/test/basicpp.cpp +++ b/test/basicpp.cpp @@ -66,12 +66,7 @@ int main(void) { assert(meshlink::destroy("basicpp_conf")); DIR *dir = opendir("basicpp_conf"); - assert(dir); - struct dirent *ent; - while((ent = readdir(dir))) { - assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); - } - closedir(dir); + assert(!dir && errno == ENOENT); return 0; } diff --git a/test/blacklist.c b/test/blacklist.c index cd545853..6b793d9c 100644 --- a/test/blacklist.c +++ b/test/blacklist.c @@ -146,12 +146,13 @@ int main(void) { assert(!meshlink_invite(mesh[0], NULL, "xyzzy")); - DIR *dir = opendir("blacklist_conf.0/current/invitations"); + DIR *dir = opendir("blacklist_conf.0"); assert(dir); struct dirent *ent; while((ent = readdir(dir))) { - assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + size_t len = strlen(ent->d_name); + assert(len < 4 || strcmp(ent->d_name + len - 4, ".inv") != 0); } closedir(dir); @@ -240,12 +241,12 @@ int main(void) { } // Check that foo has a config file for xyzzy but not quux - assert(access("blacklist_conf.0/current/hosts/xyzzy", F_OK) == 0); - assert(access("blacklist_conf.0/current/hosts/quux", F_OK) != 0 && errno == ENOENT); + assert(access("blacklist_conf.0/xyzzy", F_OK) == 0); + assert(access("blacklist_conf.0/quux", F_OK) != 0 && errno == ENOENT); // Check that bar has no config file for baz - assert(access("blacklist_conf.2/current/hosts/bar", F_OK) == 0); - assert(access("blacklist_conf.1/current/hosts/baz", F_OK) != 0 && errno == ENOENT); + assert(access("blacklist_conf.2/bar", F_OK) == 0); + assert(access("blacklist_conf.1/baz", F_OK) != 0 && errno == ENOENT); // Check that we remember xyzzy but not quux after reopening the mesh mesh[0] = meshlink_open("blacklist_conf.0", "foo", "blacklist", DEV_CLASS_BACKBONE); diff --git a/test/encrypted.c b/test/encrypted.c index ac0dfa92..37beacdc 100644 --- a/test/encrypted.c +++ b/test/encrypted.c @@ -13,6 +13,16 @@ #include "meshlink.h" #include "utils.h" +#include "devtools.h" + +static bool fail_stage1(int stage) { + return stage != 1; +} + +static bool fail_stage2(int stage) { + return stage != 2; +} + int main(void) { meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); @@ -34,6 +44,38 @@ int main(void) { mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5); assert(mesh); + // Change the encryption key. + + assert(meshlink_encrypted_key_rotate(mesh, "newkey", 6)); + meshlink_close(mesh); + + // Check that we can only reopen it with the new key + + 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); + + // Simulate a failed rotation, we should only be able to open it with the old key + + devtool_keyrotate_probe = fail_stage1; + 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); + + // Simulate a succesful rotation that was interrupted before cleaning up old files + + devtool_keyrotate_probe = fail_stage2; + 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); + // That's it. meshlink_close(mesh); @@ -43,13 +85,5 @@ 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, "..")); - } - - closedir(dir); + assert(!dir && errno == ENOENT); } diff --git a/test/invite-join.c b/test/invite-join.c index 5bd5d415..04167764 100644 --- a/test/invite-join.c +++ b/test/invite-join.c @@ -32,15 +32,15 @@ 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.1/current/hosts/eight", F_OK) == -1 && errno == ENOENT); + assert(access("invite_join_conf.8/foo", F_OK) == 0); + assert(access("invite_join_conf.1/eight", F_OK) == -1 && errno == ENOENT); set_sync_flag(&commits_first_flag, !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.1/nine", F_OK) == 0); + assert(access("invite_join_conf.9/foo", F_OK) == -1 && errno == ENOENT); set_sync_flag(&commits_first_flag, inviter_first); } diff --git a/test/storage-callbacks.cpp b/test/storage-callbacks.cpp new file mode 100644 index 00000000..0f4292ea --- /dev/null +++ b/test/storage-callbacks.cpp @@ -0,0 +1,128 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "meshlink++.h" +#include "utils.h" + +#include "devtools.h" + +std::map>> storage; + +static bool load_cb(meshlink_handle_t *mesh, const char *name, void *buf, size_t *len) { + const auto& data = storage[mesh->name][name]; + if (data.empty()) + return false; + + char* begin = static_cast(buf); + auto todo = std::min(data.size(), *len); + *len = data.size(); + std::copy_n(data.begin(), todo, begin); + return true; +} + +static bool store_cb(meshlink_handle_t *mesh, const char *name, const void *buf, size_t len) { + auto& data = storage[mesh->name][name]; + const char* begin = static_cast(buf); + data.assign(begin, begin + len); + return true; +} + +static bool ls_cb(meshlink_handle_t *mesh, meshlink_ls_entry_cb_t entry_cb) { + for (auto& it: storage[mesh->name]) { + auto& name = it.first; + auto& data = it.second; + if (data.empty()) { + continue; + } + if (!entry_cb(mesh, name.c_str(), data.size())) { + return false; + } + } + + return true; +} + +int main() { + meshlink::set_log_cb(MESHLINK_DEBUG, log_cb); + + meshlink::open_params params1("storage-callbacks_conf.1", "foo", "storage-callbacks", DEV_CLASS_BACKBONE); + meshlink::open_params params2("storage-callbacks_conf.2", "bar", "storage-callbacks", DEV_CLASS_BACKBONE); + meshlink::open_params params3("storage-callbacks_conf.3", "baz", "storage-callbacks", DEV_CLASS_BACKBONE); + + params1.set_storage_callbacks(load_cb, store_cb, ls_cb); + params1.set_storage_key("hunter42", 8); + params2.set_storage_callbacks(load_cb, store_cb, ls_cb); + params3.set_storage_callbacks(load_cb, store_cb, ls_cb); + + // Start nodes and let foo invite bar + { + meshlink::mesh mesh1(params1); + meshlink::mesh mesh2(params2); + + mesh1.enable_discovery(false); + mesh2.enable_discovery(false); + + char *invitation = mesh1.invite({}, "bar", 0); + assert(invitation); + assert(mesh1.start()); + + assert(mesh2.join(invitation)); + free(invitation); + } + + + // Start the nodes again and check that they know each other + { + meshlink::mesh mesh1(params1); + meshlink::mesh mesh2(params2); + + mesh1.enable_discovery(false); + mesh2.enable_discovery(false); + + assert(mesh1.start()); + assert(mesh2.start()); + + assert(mesh1.get_node("bar")); + assert(mesh2.get_node("foo")); + } + + // Test key rotation + { + meshlink::mesh mesh1(params1); + meshlink::mesh mesh3(params3); + + mesh1.enable_discovery(false); + mesh3.enable_discovery(false); + + char *invitation = mesh1.invite({}, "baz", 0); + assert(invitation); + + devtool_keyrotate_probe = [](int stage){ return stage != 1; }; + assert(!mesh1.encrypted_key_rotate("newkey", 6)); + mesh1.close(); + + params1.set_storage_key("newkey", 6); + assert(!mesh1.open(params1)); + params1.set_storage_key("hunter42", 8); + assert(mesh1.open(params1)); + + devtool_keyrotate_probe = [](int stage){ return stage != 2; }; + assert(mesh1.encrypted_key_rotate("newkey", 6)); + mesh1.close(); + params1.set_storage_key("newkey", 6); + assert(mesh1.open(params1)); + + assert(mesh1.start()); + assert(mesh3.join(invitation)); + assert(mesh1.get_node("baz")); + assert(mesh3.get_node("foo")); + } +} diff --git a/test/utils.h b/test/utils.h index 11facfc3..38e38a3b 100644 --- a/test/utils.h +++ b/test/utils.h @@ -7,6 +7,10 @@ #include "../src/meshlink.h" +#ifdef __cplusplus +extern "C" { +#endif + // Simple synchronisation between threads struct sync_flag { pthread_mutex_t mutex; @@ -60,3 +64,7 @@ static inline bool timespec_lt(const struct timespec *a, const struct timespec * return a->tv_sec < b->tv_sec; } } + +#ifdef __cplusplus +} +#endif -- 2.39.2