]> git.meshlink.io Git - meshlink/blobdiff - src/meshlink.c
Modify meshlink configuration base file structre
[meshlink] / src / meshlink.c
index 1b4492d63742d3cc87594b3813b0d72f976ed62d..185f6689af767d6e3ab207816619962ede74e4fe 100644 (file)
@@ -530,6 +530,40 @@ int check_port(meshlink_handle_t *mesh) {
        return 0;
 }
 
+static bool write_main_config_files(meshlink_handle_t *mesh) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
+       uint8_t buf[4096];
+
+       /* Write the main config file */
+       packmsg_output_t out = {buf, sizeof buf};
+
+       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+       packmsg_add_str(&out, mesh->name);
+       packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96);
+       packmsg_add_bin(&out, ecdsa_get_private_key(mesh->invitation_key), 96);
+       packmsg_add_uint16(&out, atoi(mesh->myport));
+
+       if(!packmsg_output_ok(&out)) {
+               return false;
+       }
+
+       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)) {
+               return false;
+       }
+
+       return true;
+}
+
 static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len) {
        packmsg_input_t in = {buf, len};
        uint32_t version = packmsg_get_uint32(&in);
@@ -551,48 +585,28 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len
 
        if(!check_id(name)) {
                logger(mesh, MESHLINK_DEBUG, "Invalid Name found in invitation: %s!\n", name);
+               free(name);
                return false;
        }
 
        if(!count) {
                logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n");
+               free(name);
                return false;
        }
 
-       // Initialize configuration directory
-       if(!config_init(mesh)) {
-               return false;
-       }
-
-       // Write main config file
-       uint8_t outbuf[4096];
-       packmsg_output_t out = {outbuf, sizeof(outbuf)};
-       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
-       packmsg_add_str(&out, name);
-       packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96);
-       packmsg_add_uint16(&out, atoi(mesh->myport));
-
-       config_t config = {outbuf, packmsg_output_size(&out, outbuf)};
+       free(mesh->name);
+       free(mesh->self->name);
+       mesh->name = name;
+       mesh->self->name = xstrdup(name);
+       mesh->self->devclass = devclass;
 
-       if(!main_config_write(mesh, &config)) {
+       // Initialize configuration directory
+       if(!config_init(mesh, "current")) {
                return false;
        }
 
-       // Write our own host config file
-       out.ptr = outbuf;
-       out.len = sizeof(outbuf);
-       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
-       packmsg_add_str(&out, name);
-       packmsg_add_str(&out, CORE_MESH);
-       packmsg_add_int32(&out, devclass);
-       packmsg_add_bool(&out, false);
-       packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
-       packmsg_add_str(&out, ""); // TODO: copy existing canonical address, in case it was added before meshlink_join().
-       packmsg_add_array(&out, 0);
-
-       config.len = packmsg_output_size(&out, outbuf);
-
-       if(!config_write(mesh, name, &config)) {
+       if(!write_main_config_files(mesh)) {
                return false;
        }
 
@@ -608,47 +622,49 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len
 
                packmsg_input_t in2 = {data, len};
                uint32_t version = packmsg_get_uint32(&in2);
+               char *name = packmsg_get_str_dup(&in2);
 
-               if(version != MESHLINK_CONFIG_VERSION) {
-                       logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n");
-                       return false;
+               if(!packmsg_input_ok(&in2) || version != MESHLINK_CONFIG_VERSION || !check_id(name)) {
+                       free(name);
+                       packmsg_input_invalidate(&in);
+                       break;
                }
 
-               char *host = packmsg_get_str_dup(&in2);
-
-               if(!check_id(host)) {
-                       logger(mesh, MESHLINK_ERROR, "Invalid node name in invitation file!\n");
-                       free(host);
-                       return false;
+               if(!check_id(name)) {
+                       free(name);
+                       break;
                }
 
-               if(!strcmp(host, name)) {
+               if(!strcmp(name, mesh->name)) {
                        logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n");
-                       free(host);
+                       free(name);
+                       meshlink_errno = MESHLINK_EPEER;
                        return false;
                }
 
+               node_t *n = new_node();
+               n->name = name;
+
                config_t config = {data, len};
-               config_write(mesh, host, &config);
 
-               node_t *n = new_node();
-               n->name = host;
-               node_read_full(mesh, n);
-               n->devclass = mesh->devclass;
+               if(!node_read_from_config(mesh, n, &config)) {
+                       free_node(n);
+                       logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n");
+                       meshlink_errno = MESHLINK_EPEER;
+                       return false;
+               }
+
                node_add(mesh, n);
+
+               if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) {
+                       return false;
+               }
        }
 
        sptps_send_record(&(mesh->sptps), 1, ecdsa_get_public_key(mesh->private_key), 32);
 
-       free(mesh->name);
-       free(mesh->self->name);
-       mesh->name = xstrdup(name);
-       mesh->self->name = xstrdup(name);
-
        logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
 
-       load_all_nodes(mesh);
-
        return true;
 }
 
@@ -843,27 +859,9 @@ static void add_local_addresses(meshlink_handle_t *mesh) {
        }
 }
 
-#if 0
-static bool meshlink_write_config(meshlink_handle_t *mesh) {
-       uint8_t buf[1024];
-       packmsg_output_t out = {buf, sizeof buf};
-       packmsg_add_str(&out, mesh->name);
-       packmsg_add_uint32(&out, mesh->devclass);
-       packmsg_add_uint16(&out, mesh->port);
-       packmsg_add_bin(&out, ecdsa, sizeof(ecdsa));
-       uint32_t len = packmsg_output_size(&out, buf);
-
-       if(!len) {
-               logger(mesh, MESHLINK_DEBUG, "Could not create configuration data\n",);
-               meshlink_errno = MESHLINK_EINTERNAL;
-               return false;
-       }
-}
-#endif
-
 static bool meshlink_setup(meshlink_handle_t *mesh) {
-       if(!config_init(mesh)) {
-               logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s: %s\n", mesh->confbase, strerror(errno));
+       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;
                return false;
        }
@@ -885,36 +883,15 @@ static bool meshlink_setup(meshlink_handle_t *mesh) {
        mesh->self->devclass = mesh->devclass;
        mesh->self->ecdsa = ecdsa_set_public_key(ecdsa_get_public_key(mesh->private_key));
 
-       // Write the main config file
-       uint8_t buf[4096];
-       packmsg_output_t out = {buf, sizeof(buf)};
-
-       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
-       packmsg_add_str(&out, mesh->name);
-       packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96);
-       packmsg_add_bin(&out, ecdsa_get_private_key(mesh->invitation_key), 96);
-       packmsg_add_uint16(&out, atoi(mesh->myport));
-
-       config_t config = {buf, packmsg_output_size(&out, buf)};
-
-       if(!main_config_write(mesh, &config)) {
+       if(!write_main_config_files(mesh)) {
+               logger(mesh, MESHLINK_ERROR, "Could not write main config files into %s/current: %s\n", mesh->confbase, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
-       // Write our own host config file
-       out.ptr = buf;
-       out.len = sizeof(buf);
-       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
-       packmsg_add_str(&out, mesh->name);
-       packmsg_add_int32(&out, mesh->devclass);
-       packmsg_add_bool(&out, false);
-       packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
-       packmsg_add_str(&out, ""); // TODO: copy existing canonical address, in case it was added before meshlink_join().
-       packmsg_add_array(&out, 0);
-
-       config.len = packmsg_output_size(&out, buf);
-
-       if(!config_write(mesh, mesh->name, &config)) {
+       if(!main_config_lock(mesh)) {
+               logger(NULL, MESHLINK_ERROR, "Cannot lock main config file\n");
+               meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
@@ -931,7 +908,7 @@ static bool meshlink_read_config(meshlink_handle_t *mesh) {
 
        config_t config;
 
-       if(!main_config_read(mesh, &config)) {
+       if(!main_config_read(mesh, "current", &config, mesh->config_key)) {
                logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!");
                return false;
        }
@@ -981,6 +958,7 @@ static bool meshlink_read_config(meshlink_handle_t *mesh) {
 
        if(!node_read_public_key(mesh, mesh->self)) {
                logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
+               meshlink_errno = MESHLINK_ESTORAGE;
                free_node(mesh->self);
                mesh->self = NULL;
                return false;
@@ -1079,6 +1057,22 @@ bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const
        return true;
 }
 
+bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, size_t new_keylen) {
+
+       // While copying old config files to new config files
+       devtool_keyrotate_probe(1);
+       // After completed creating new config files in confbase/new/
+       devtool_keyrotate_probe(2);
+       // Rename confbase/current to confbase/old/
+       devtool_keyrotate_probe(3);
+       // Rename confbase/new/ to confbase/current
+       devtool_keyrotate_probe(4);
+       // Before deleting old sub-directory
+       devtool_keyrotate_probe(5);
+
+       return false;
+}
+
 void meshlink_open_params_free(meshlink_open_params_t *params) {
        if(!params) {
                meshlink_errno = MESHLINK_EINVAL;
@@ -1093,6 +1087,12 @@ void meshlink_open_params_free(meshlink_open_params_t *params) {
 }
 
 meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+       if(!confbase || !*confbase) {
+               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+               meshlink_errno = MESHLINK_EINVAL;
+               return NULL;
+       }
+
        /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
        meshlink_open_params_t params;
        memset(&params, 0, sizeof(params));
@@ -1107,6 +1107,12 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c
 }
 
 meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen) {
+       if(!confbase || !*confbase) {
+               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+               meshlink_errno = MESHLINK_EINVAL;
+               return NULL;
+       }
+
        /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
        meshlink_open_params_t params = {NULL};
 
@@ -1123,18 +1129,24 @@ meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *nam
        return meshlink_open_ex(&params);
 }
 
+meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) {
+       /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
+       meshlink_open_params_t params = {NULL};
+
+       params.name = (char *)name;
+       params.appname = (char *)appname;
+       params.devclass = devclass;
+       params.netns = -1;
+
+       return meshlink_open_ex(&params);
+}
+
 meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        // Validate arguments provided by the application
        bool usingname = false;
 
        logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n");
 
-       if(!params->confbase || !*params->confbase) {
-               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
        if(!params->appname || !*params->appname) {
                logger(NULL, MESHLINK_ERROR, "No appname given!\n");
                meshlink_errno = MESHLINK_EINVAL;
@@ -1174,7 +1186,11 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        }
 
        meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t));
-       mesh->confbase = xstrdup(params->confbase);
+
+       if(params->confbase) {
+               mesh->confbase = xstrdup(params->confbase);
+       }
+
        mesh->appname = xstrdup(params->appname);
        mesh->devclass = params->devclass;
        mesh->discovery = true;
@@ -1192,6 +1208,7 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
 
                if(!prf(params->key, params->keylen, "MeshLink configuration key", 26, mesh->config_key, CHACHA_POLY1305_KEYLEN)) {
                        logger(NULL, MESHLINK_ERROR, "Error creating configuration key!\n");
+                       meshlink_close(mesh);
                        meshlink_errno = MESHLINK_EINTERNAL;
                        return NULL;
                }
@@ -1211,7 +1228,7 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
 
        // If no configuration exists yet, create it.
 
-       if(!main_config_exists(mesh)) {
+       if(!meshlink_confbase_exists(mesh)) {
                if(!meshlink_setup(mesh)) {
                        logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n");
                        meshlink_close(mesh);
@@ -1446,11 +1463,21 @@ void meshlink_stop(meshlink_handle_t *mesh) {
 
        exit_outgoings(mesh);
 
+       // Write out any changed node config files
+       if(mesh->nodes) {
+               for splay_each(node_t, n, mesh->nodes) {
+                       if(n->status.dirty) {
+                               node_write_config(mesh, n);
+                               n->status.dirty = false;
+                       }
+               }
+       }
+
        pthread_mutex_unlock(&(mesh->mesh_mutex));
 }
 
 void meshlink_close(meshlink_handle_t *mesh) {
-       if(!mesh || !mesh->confbase) {
+       if(!mesh) {
                meshlink_errno = MESHLINK_EINVAL;
                return;
        }
@@ -1486,6 +1513,8 @@ void meshlink_close(meshlink_handle_t *mesh) {
        free(mesh->name);
        free(mesh->appname);
        free(mesh->confbase);
+       free(mesh->config_key);
+       ecdsa_free(mesh->private_key);
        pthread_mutex_destroy(&(mesh->mesh_mutex));
 
        main_config_unlock(mesh);
@@ -1501,7 +1530,21 @@ bool meshlink_destroy(const char *confbase) {
                return false;
        }
 
-       return config_destroy(confbase);
+       if(!config_destroy(confbase, "current")) {
+               logger(NULL, MESHLINK_ERROR, "Cannot remove confbase sub-directories %s: %s\n", confbase, strerror(errno));
+               return false;
+       }
+
+       config_destroy(confbase, "new");
+       config_destroy(confbase, "old");
+
+       if(rmdir(confbase) && errno != ENOENT) {
+               logger(NULL, MESHLINK_ERROR, "Cannot remove directory %s: %s\n", confbase, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               return false;
+       }
+
+       return true;
 }
 
 void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) {
@@ -1792,7 +1835,7 @@ static meshlink_node_t **meshlink_get_all_nodes_by_condition(meshlink_handle_t *
 static bool search_node_by_dev_class(const node_t *node, const void *condition) {
        dev_class_t *devclass = (dev_class_t *)condition;
 
-       if(*devclass == node->devclass) {
+       if(*devclass == (dev_class_t)node->devclass) {
                return true;
        }
 
@@ -1954,7 +1997,7 @@ bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *no
        node_t *n = (node_t *)node;
        free(n->canonical_address);
        n->canonical_address = canonical_address;
-       n->status.dirty = true;
+       node_write_config(mesh, n);
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
 
@@ -2023,11 +2066,27 @@ bool meshlink_set_port(meshlink_handle_t *mesh, int port) {
                goto done;
        }
 
+       free(mesh->myport);
+       xasprintf(&mesh->myport, "%d", port);
+
+       /* Write meshlink.conf with the updated port number */
+       write_main_config_files(mesh);
+
+       /* Close down the network. This also deletes mesh->self. */
        close_network_connections(mesh);
 
-       // TODO: write meshlink.conf again
+       /* Recreate mesh->self. */
+       mesh->self = new_node();
+       mesh->self->name = xstrdup(mesh->name);
+       mesh->self->devclass = mesh->devclass;
+       xasprintf(&mesh->myport, "%d", port);
 
-       if(!setup_network(mesh)) {
+       if(!node_read_public_key(mesh, mesh->self)) {
+               logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
+               meshlink_errno = MESHLINK_ESTORAGE;
+               free_node(mesh->self);
+               mesh->self = NULL;
+       } else if(!setup_network(mesh)) {
                meshlink_errno = MESHLINK_ENETWORK;
        } else {
                rval = true;
@@ -2074,7 +2133,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, name)) {
+       if(config_exists(mesh, "current", name)) {
                logger(mesh, MESHLINK_DEBUG, "A host config file for %s already exists!\n", name);
                meshlink_errno = MESHLINK_EEXIST;
                pthread_mutex_unlock(&(mesh->mesh_mutex));
@@ -2137,11 +2196,14 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c
        packmsg_add_str(&inv, s ? s->name : CORE_MESH);
        packmsg_add_int32(&inv, DEV_CLASS_UNKNOWN); /* TODO: allow this to be set by inviter? */
 
-       /* TODO: Add several host config files to bootstrap connections */
+       /* TODO: Add several host config files to bootstrap connections.
+        * Note: make sure we only add config files of nodes that are in the core mesh or the same submesh,
+        * and are not blacklisted.
+        */
        config_t configs[5] = {NULL};
        int count = 0;
 
-       if(config_read(mesh, mesh->self->name, &configs[count])) {
+       if(config_read(mesh, "current", mesh->self->name, &configs[count], mesh->config_key)) {
                count++;
        }
 
@@ -2155,7 +2217,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, cookiehash, &config)) {
+       if(!invitation_write(mesh, "current", cookiehash, &config, mesh->config_key)) {
                logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno));
                meshlink_errno = MESHLINK_ESTORAGE;
                pthread_mutex_unlock(&(mesh->mesh_mutex));
@@ -2309,7 +2371,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
 
        mesh->blen = 0;
 
-       if(!sendline(mesh->sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, 1, mesh->appname)) {
+       if(!sendline(mesh->sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, PROT_MINOR, mesh->appname)) {
                logger(mesh, MESHLINK_DEBUG, "Error sending request to %s port %s: %s\n", address, port, strerror(errno));
                closesocket(mesh->sock);
                meshlink_errno = MESHLINK_ENETWORK;
@@ -2420,38 +2482,64 @@ char *meshlink_export(meshlink_handle_t *mesh) {
                return NULL;
        }
 
-       config_t config;
+       // Create a config file on the fly.
 
-       // Get our config file
+       uint8_t buf[4096];
+       packmsg_output_t out = {buf, sizeof(buf)};
+       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+       packmsg_add_str(&out, mesh->name);
+       packmsg_add_str(&out, CORE_MESH);
 
        pthread_mutex_lock(&(mesh->mesh_mutex));
 
-       if(!config_read(mesh, mesh->self->name, &config)) {
-               meshlink_errno = MESHLINK_ESTORAGE;
-               pthread_mutex_unlock(&mesh->mesh_mutex);
-               return NULL;
+       packmsg_add_int32(&out, mesh->self->devclass);
+       packmsg_add_bool(&out, mesh->self->status.blacklisted);
+       packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
+       packmsg_add_str(&out, mesh->self->canonical_address ? mesh->self->canonical_address : "");
+
+       uint32_t count = 0;
+
+       for(uint32_t i = 0; i < 5; i++) {
+               if(mesh->self->recent[i].sa.sa_family) {
+                       count++;
+               } else {
+                       break;
+               }
+       }
+
+       packmsg_add_array(&out, count);
+
+       for(uint32_t i = 0; i < count; i++) {
+               packmsg_add_sockaddr(&out, &mesh->self->recent[i]);
        }
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
 
+       if(!packmsg_output_ok(&out)) {
+               logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
+               meshlink_errno = MESHLINK_EINTERNAL;
+               return NULL;
+       }
+
        // Prepare a base64-encoded packmsg array containing our config file
 
-       uint8_t *buf = xmalloc(((config.len + 4) * 4) / 3 + 4);
-       packmsg_output_t out = {buf, config.len + 4};
-       packmsg_add_array(&out, 1);
-       packmsg_add_bin(&out, config.buf, config.len);
-       config_free(&config);
+       uint32_t len = packmsg_output_size(&out, buf);
+       uint32_t len2 = ((len + 4) * 4) / 3 + 4;
+       uint8_t *buf2 = xmalloc(len2);
+       packmsg_output_t out2 = {buf2, len2};
+       packmsg_add_array(&out2, 1);
+       packmsg_add_bin(&out2, buf, packmsg_output_size(&out, buf));
 
-       if(!packmsg_output_ok(&out)) {
+       if(!packmsg_output_ok(&out2)) {
                logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
                meshlink_errno = MESHLINK_EINTERNAL;
-               free(buf);
+               free(buf2);
                return NULL;
        }
 
-       b64encode_urlsafe(buf, (char *)buf, packmsg_output_size(&out, buf));
+       b64encode_urlsafe(buf2, (char *)buf2, packmsg_output_size(&out2, buf2));
 
-       return (char *)buf;
+       return (char *)buf2;
 }
 
 bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
@@ -2495,7 +2583,7 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
 
                if(!packmsg_input_ok(&in2) || version != MESHLINK_CONFIG_VERSION || !check_id(name)) {
                        free(name);
-                       packmsg_input_invalidate(&in2);
+                       packmsg_input_invalidate(&in);
                        break;
                }
 
@@ -2507,57 +2595,24 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
                node_t *n = lookup_node(mesh, name);
 
                if(n) {
-                       free(name);
                        logger(mesh, MESHLINK_DEBUG, "Node %s already exists, not importing\n", name);
+                       free(name);
                        continue;
                }
 
                n = new_node();
                n->name = name;
-               char *submesh_name = packmsg_get_str_dup(&in2);
 
-               if(submesh_name) {
-                       if(!strcmp(submesh_name, CORE_MESH)) {
-                               n->submesh = NULL;
-                       } else {
-                               n->submesh = lookup_or_create_submesh(mesh, submesh_name);
-
-                               if(!n->submesh) {
-                                       packmsg_input_invalidate(&in2);
-                               }
-                       }
-
-                       free(submesh_name);
-               }
-
-               n->devclass = packmsg_get_int32(&in2);
-               n->status.blacklisted = packmsg_get_bool(&in2);
-               const void *key;
-               uint32_t keylen = packmsg_get_bin_raw(&in2, &key);
-
-               if(keylen == 32) {
-                       n->ecdsa = ecdsa_set_public_key(key);
-               }
-
-               n->canonical_address = packmsg_get_str_dup(&in2);
-               uint32_t count = packmsg_get_array(&in2);
-
-               if(count > 5) {
-                       count = 5;
-               }
-
-               for(uint32_t i = 0; i < count; i++) {
-                       n->recent[i] = packmsg_get_sockaddr(&in2);
-               }
+               config_t config = {data, len};
 
-               if(!packmsg_done(&in2) || keylen != 32) {
-                       packmsg_input_invalidate(&in2);
+               if(!node_read_from_config(mesh, n, &config)) {
                        free_node(n);
-               } else {
-                       config_t config = {data, len};
-                       config_write(mesh, n->name, &config);
-                       node_add(mesh, n);
+                       packmsg_input_invalidate(&in);
+                       break;
                }
+
+               config_write(mesh, "current", n->name, &config, mesh->config_key);
+               node_add(mesh, n);
        }
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
@@ -2596,7 +2651,7 @@ void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
        }
 
        n->status.blacklisted = true;
-       n->status.dirty = true;
+       node_write_config(mesh, n);
        logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
 
        //Immediately terminate any connections we have with the blacklisted node
@@ -2639,7 +2694,7 @@ void meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) {
        }
 
        n->status.blacklisted = false;
-       n->status.dirty = true;
+       node_write_config(mesh, n);
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
        return;
@@ -2663,7 +2718,7 @@ void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const
        node_t *n = (node_t *)node;
        memmove(n->recent + 1, n->recent, 4 * sizeof(*n->recent));
        memcpy(n->recent, addr, SALEN(*addr));
-       n->status.dirty = true;
+       node_write_config(mesh, n);
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
        // @TODO do we want to fire off a connection attempt right away?