]> git.meshlink.io Git - meshlink/commitdiff
Use a key/value store with configurable storage callbacks.
authorGuus Sliepen <guus@meshlink.io>
Sun, 9 Oct 2022 19:02:31 +0000 (21:02 +0200)
committerGuus Sliepen <guus@meshlink.io>
Fri, 14 Oct 2022 14:29:21 +0000 (16:29 +0200)
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.

20 files changed:
configure.ac
src/conf.c
src/conf.h
src/devtools.c
src/devtools.h
src/meshlink++.h
src/meshlink.c
src/meshlink.h
src/meshlink.sym
src/meshlink_internal.h
src/net_setup.c
src/protocol_auth.c
test/Makefile.am
test/basic.c
test/basicpp.cpp
test/blacklist.c
test/encrypted.c
test/invite-join.c
test/storage-callbacks.cpp [new file with mode: 0644]
test/utils.h

index 022bba7bb817b296b7a61358da89d24c06c94c59..872c95b2a864ec5e59596a4e877fb35e2a7703a0 100644 (file)
@@ -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"]
 )
 
index db91d5fb3c9b900a1d4d20d78a8b0df2a9929501..22d3845e6f22894ec1d0f96be1d8e1da3e71a45c 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "system.h"
+
 #include <assert.h>
 #include <sys/types.h>
 #include <utime.h>
 #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, &times)) {
-                               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;
 }
index 656a0acc7e1c605e9a9a7e9185e789aad29440ca..b0f10db2408f9e71c94088e65d1c27eb859a2255 100644 (file)
 */
 
 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
index 12c5432e2ca8dbf9ae7ece2accb612fa543c15e6..50ff149a3e166a0f74a1ed01cbe46301d8e24d9e 100644 (file)
@@ -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;
index 78e3642859e50301ce84399419735eb579337fe1..14fbfb8bf02900bcc0bfa72f529e9f5634ea0de1 100644 (file)
@@ -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);
index 9e97fc820d846b29e9570da08bbc1fb47b65e4bd..4af1051868a382014f9bf045510d92c06b479954 100644 (file)
@@ -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 &params) {
+               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 &params) {
+               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
index f425b4b2f2bad692fd3030673dfe63b4c4056056..5eea05ce4d5987f7be2590a5b1248d6c70e6d26f 100644 (file)
@@ -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
index 903bab36db1d1481035667b11ac90f046169a104..15b940f5a5da4f7e16c069aa00983809e1bee76f 100644 (file)
@@ -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.
  *
index 8ece4dc505593333273745758f36843cc89b62af..5a3f97386cc1e45f6abab8dc6946052a12e07ef3 100644 (file)
@@ -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
index b57f212c5fdf6569243c2256497f8e11f5514d6f..8867eb9b477e8024dab984f7b8071dffd1b60c3d 100644 (file)
@@ -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;
index a7a538f01c75d1b4ed66fa77114391bbce123ce1..d4761ae8ac3eb3c38e169bae68367d3ffe877fab 100644 (file)
@@ -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");
        }
 
index 0a107e4c2820f19caa88dea3c6e5f41cab3f99c9..d49f7ad2915ec7eb93a82502c82db0c3584a0cee 100644 (file)
@@ -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;
        }
 
index 688d8b7d5861d29b4e53a6cb8d17206ad43f97ed..e78cea0733fe9eb28775353b4f95f4f4f6557a41 100644 (file)
@@ -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
 
index 004e7186437fdc2b9fe8c3e64391eefb81523890..e5c2eb8ec53af27d136057b05619a280bebf1e7e 100644 (file)
@@ -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.
 
index c0550ecf71027e07f1de4cb5f751138c3536419d..79565887b291549687623281f183a7e73484b60d 100644 (file)
@@ -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;
 }
index cd54585341148922894b7220a365a45b1b93c838..6b793d9cc133225914a190b85cb6100154d4ffa3 100644 (file)
@@ -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);
index ac0dfa92023afdb77a47a49170444b76fc3eeb69..37beacdc3a4c48ed7c00a1e6bb18441bbd0858de 100644 (file)
 #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);
 }
index 5bd5d415044e4ceb9a79a748ebe6abc9f806f676..04167764d570460e91c510079484e5055b35f267 100644 (file)
@@ -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 (file)
index 0000000..0f4292e
--- /dev/null
@@ -0,0 +1,128 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "meshlink++.h"
+#include "utils.h"
+
+#include "devtools.h"
+
+std::map<std::string, std::map<std::string, std::vector<char>>> 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<char *>(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<const char *>(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"));
+       }
+}
index 11facfc3c025b248772db1ae7e273ce04c0b2278..38e38a3bd0e42376eb542127a41bd157a5538227 100644 (file)
@@ -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