+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_close(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ // stop can be called even if mesh has not been started
+ meshlink_stop(mesh);
+
+ // lock is not released after this
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ // Close and free all resources used.
+
+ close_network_connections(mesh);
+
+ logger(mesh, MESHLINK_INFO, "Terminating");
+
+ event_loop_exit(&mesh->loop);
+
+#ifdef HAVE_MINGW
+
+ if(mesh->confbase) {
+ WSACleanup();
+ }
+
+#endif
+
+ ecdsa_free(mesh->invitation_key);
+
+ if(mesh->netns != -1) {
+ close(mesh->netns);
+ }
+
+ for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) {
+ free(packet);
+ }
+
+ meshlink_queue_exit(&mesh->outpacketqueue);
+
+ free(mesh->name);
+ free(mesh->appname);
+ free(mesh->confbase);
+ free(mesh->config_key);
+ ecdsa_free(mesh->private_key);
+
+ main_config_unlock(mesh);
+
+ pthread_mutex_unlock(&mesh->mesh_mutex);
+ pthread_mutex_destroy(&mesh->mesh_mutex);
+
+ memset(mesh, 0, sizeof(*mesh));
+
+ free(mesh);
+}
+
+bool meshlink_destroy(const char *confbase) {
+ if(!confbase) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ 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) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->receive_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_set_connection_try_cb(meshlink_handle_t *mesh, meshlink_connection_try_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->connection_try_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->node_status_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_set_node_pmtu_cb(meshlink_handle_t *mesh, meshlink_node_pmtu_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->node_pmtu_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_set_node_duplicate_cb(meshlink_handle_t *mesh, meshlink_node_duplicate_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->node_duplicate_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb) {
+ if(mesh) {
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->log_cb = cb;
+ mesh->log_level = cb ? level : 0;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ } else {
+ global_log_cb = cb;
+ global_log_level = cb ? level : 0;
+ }
+}
+
+void meshlink_set_error_cb(struct meshlink_handle *mesh, meshlink_error_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ mesh->error_cb = cb;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+}
+
+bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) {
+ meshlink_packethdr_t *hdr;
+
+ // Validate arguments
+ if(!mesh || !destination || len >= MAXSIZE - sizeof(*hdr)) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!len) {
+ return true;
+ }
+
+ if(!data) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ node_t *n = (node_t *)destination;
+
+ if(n->status.blacklisted) {
+ logger(mesh, MESHLINK_ERROR, "Node %s blacklisted, dropping packet\n", n->name);
+ return false;
+ }
+
+ // Prepare the packet
+ vpn_packet_t *packet = malloc(sizeof(*packet));
+
+ if(!packet) {
+ meshlink_errno = MESHLINK_ENOMEM;
+ return false;
+ }
+
+ packet->probe = false;
+ packet->tcp = false;
+ packet->len = len + sizeof(*hdr);
+
+ hdr = (meshlink_packethdr_t *)packet->data;
+ memset(hdr, 0, sizeof(*hdr));
+ // leave the last byte as 0 to make sure strings are always
+ // null-terminated if they are longer than the buffer
+ strncpy((char *)hdr->destination, destination->name, (sizeof(hdr)->destination) - 1);
+ strncpy((char *)hdr->source, mesh->self->name, (sizeof(hdr)->source) - 1);
+
+ memcpy(packet->data + sizeof(*hdr), data, len);
+
+ // Queue it
+ if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) {
+ free(packet);
+ meshlink_errno = MESHLINK_ENOMEM;
+ return false;
+ }
+
+ // Notify event loop
+ signal_trigger(&(mesh->loop), &(mesh->datafromapp));
+
+ return true;
+}
+
+void meshlink_send_from_queue(event_loop_t *loop, void *data) {
+ (void)loop;
+ meshlink_handle_t *mesh = data;
+ vpn_packet_t *packet = meshlink_queue_pop(&mesh->outpacketqueue);
+
+ if(!packet) {
+ return;
+ }
+
+ mesh->self->in_packets++;
+ mesh->self->in_bytes += packet->len;
+ route(mesh, mesh->self, packet);
+
+ free(packet);
+}
+
+ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination) {
+ if(!mesh || !destination) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ node_t *n = (node_t *)destination;
+
+ if(!n->status.reachable) {
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return 0;
+
+ } else if(n->mtuprobes > 30 && n->minmtu) {
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return n->minmtu;
+ } else {
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return MTU;
+ }
+}
+
+char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ node_t *n = (node_t *)node;
+
+ if(!node_read_public_key(mesh, n) || !n->ecdsa) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa);
+
+ if(!fingerprint) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return fingerprint;
+}
+
+meshlink_node_t *meshlink_get_self(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return (meshlink_node_t *)mesh->self;
+}
+
+meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_node_t *node = NULL;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ node = (meshlink_node_t *)lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ if(!node) {
+ meshlink_errno = MESHLINK_ENOENT;
+ }
+
+ return node;
+}
+
+meshlink_submesh_t *meshlink_get_submesh(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_submesh_t *submesh = NULL;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ submesh = (meshlink_submesh_t *)lookup_submesh(mesh, name);
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ if(!submesh) {
+ meshlink_errno = MESHLINK_ENOENT;
+ }
+
+ return submesh;
+}
+
+meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || !nmemb || (*nmemb && !nodes)) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_node_t **result;
+
+ //lock mesh->nodes
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ *nmemb = mesh->nodes->count;
+ result = realloc(nodes, *nmemb * sizeof(*nodes));
+
+ if(result) {
+ meshlink_node_t **p = result;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ *p++ = (meshlink_node_t *)n;
+ }
+ } else {
+ *nmemb = 0;
+ free(nodes);
+ meshlink_errno = MESHLINK_ENOMEM;
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return result;
+}
+
+static meshlink_node_t **meshlink_get_all_nodes_by_condition(meshlink_handle_t *mesh, const void *condition, meshlink_node_t **nodes, size_t *nmemb, search_node_by_condition_t search_node) {
+ meshlink_node_t **result;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ *nmemb = 0;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(true == search_node(n, condition)) {
+ *nmemb = *nmemb + 1;
+ }
+ }
+
+ if(*nmemb == 0) {
+ free(nodes);
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return NULL;
+ }
+
+ result = realloc(nodes, *nmemb * sizeof(*nodes));
+
+ if(result) {
+ meshlink_node_t **p = result;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(true == search_node(n, condition)) {
+ *p++ = (meshlink_node_t *)n;
+ }
+ }
+ } else {
+ *nmemb = 0;
+ free(nodes);
+ meshlink_errno = MESHLINK_ENOMEM;
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return result;
+}
+
+static bool search_node_by_dev_class(const node_t *node, const void *condition) {
+ dev_class_t *devclass = (dev_class_t *)condition;
+
+ if(*devclass == (dev_class_t)node->devclass) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool search_node_by_submesh(const node_t *node, const void *condition) {
+ if(condition == node->submesh) {
+ return true;
+ }
+
+ return false;
+}
+
+meshlink_node_t **meshlink_get_all_nodes_by_dev_class(meshlink_handle_t *mesh, dev_class_t devclass, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT || !nmemb) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_get_all_nodes_by_condition(mesh, &devclass, nodes, nmemb, search_node_by_dev_class);
+}
+
+meshlink_node_t **meshlink_get_all_nodes_by_submesh(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || !submesh || !nmemb) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh);
+}
+
+dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ dev_class_t devclass;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ devclass = ((node_t *)node)->devclass;
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return devclass;
+}
+
+meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ node_t *n = (node_t *)node;
+
+ meshlink_submesh_t *s;
+
+ s = (meshlink_submesh_t *)n->submesh;
+
+ return s;
+}
+
+bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) {
+ if(!mesh || !data || !len || !signature || !siglen) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(*siglen < MESHLINK_SIGLEN) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ if(!ecdsa_sign(mesh->private_key, data, len, signature)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ *siglen = MESHLINK_SIGLEN;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return true;
+}
+
+bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen) {
+ if(!mesh || !data || !len || !signature) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(siglen != MESHLINK_SIGLEN) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ bool rval = false;
+
+ struct node_t *n = (struct node_t *)source;
+
+ if(!node_read_public_key(mesh, n)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ rval = false;
+ } else {
+ rval = ecdsa_verify(((struct node_t *)source)->ecdsa, data, len, signature);
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return rval;
+}
+
+static bool refresh_invitation_key(meshlink_handle_t *mesh) {
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ size_t count = invitation_purge_old(mesh, time(NULL) - mesh->invitation_timeout);
+
+ if(!count) {
+ // TODO: Update invitation key if necessary?
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return mesh->invitation_key;
+}
+
+bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node, const char *address, const char *port) {
+ if(!mesh || !node || !address) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!is_valid_hostname(address)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in address: %s\n", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(port && !is_valid_port(port)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in port: %s\n", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ char *canonical_address;
+
+ if(port) {
+ xasprintf(&canonical_address, "%s %s", address, port);
+ } else {
+ canonical_address = xstrdup(address);
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ node_t *n = (node_t *)node;
+ free(n->canonical_address);
+ n->canonical_address = canonical_address;
+ node_write_config(mesh, n);
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return true;
+}
+
+bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
+ return meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL);
+}
+
+bool meshlink_add_external_address(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ char *address = meshlink_get_external_address(mesh);
+
+ if(!address) {
+ return false;
+ }
+
+ bool rval = meshlink_add_address(mesh, address);
+ free(address);
+
+ return rval;
+}
+
+int meshlink_get_port(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ if(!mesh->myport) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return -1;
+ }
+
+ int port;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+ port = atoi(mesh->myport);
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return port;
+}
+
+bool meshlink_set_port(meshlink_handle_t *mesh, int port) {
+ if(!mesh || port < 0 || port >= 65536 || mesh->threadstarted) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(mesh->myport && port == atoi(mesh->myport)) {
+ return true;
+ }
+
+ if(!try_bind(port)) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ return false;
+ }
+
+ devtool_trybind_probe();
+
+ bool rval = false;
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ if(mesh->threadstarted) {
+ meshlink_errno = MESHLINK_EINVAL;
+ goto done;
+ }
+
+ free(mesh->myport);
+ xasprintf(&mesh->myport, "%d", port);
+
+ /* Close down the network. This also deletes mesh->self. */
+ close_network_connections(mesh);
+
+ /* Recreate mesh->self. */
+ mesh->self = new_node();
+ mesh->self->name = xstrdup(mesh->name);
+ mesh->self->devclass = mesh->devclass;
+ xasprintf(&mesh->myport, "%d", port);
+
+ 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;
+ goto done;
+ } else if(!setup_network(mesh)) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ goto done;
+ }
+
+ /* Rebuild our own list of recent addresses */
+ memset(mesh->self->recent, 0, sizeof(mesh->self->recent));
+ add_local_addresses(mesh);
+
+ /* Write meshlink.conf with the updated port number */
+ write_main_config_files(mesh);
+
+ rval = config_sync(mesh, "current");
+
+done:
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ return rval && meshlink_get_port(mesh) == port;
+}
+
+void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) {
+ mesh->invitation_timeout = timeout;
+}
+
+char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name, uint32_t flags) {
+ meshlink_submesh_t *s = NULL;
+
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(submesh) {
+ s = (meshlink_submesh_t *)lookup_submesh(mesh, submesh->name);
+
+ if(s != submesh) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid SubMesh Handle.\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+ } else {
+ s = (meshlink_submesh_t *)mesh->self->submesh;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ // Check validity of the new node's name
+ if(!check_id(name)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid name for node.\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return NULL;
+ }
+
+ // Ensure no host configuration file with that name exists
+ 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));
+ return NULL;
+ }
+
+ // Ensure no other nodes know about this name
+ if(meshlink_get_node(mesh, name)) {
+ logger(mesh, MESHLINK_DEBUG, "A node with name %s is already known!\n", name);
+ meshlink_errno = MESHLINK_EEXIST;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return NULL;
+ }
+
+ // Get the local address
+ char *address = get_my_hostname(mesh, flags);
+
+ if(!address) {
+ logger(mesh, MESHLINK_DEBUG, "No Address known for ourselves!\n");
+ meshlink_errno = MESHLINK_ERESOLV;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return NULL;
+ }
+
+ if(!refresh_invitation_key(mesh)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return NULL;
+ }
+
+ char hash[64];
+
+ // Create a hash of the key.
+ char *fingerprint = ecdsa_get_base64_public_key(mesh->invitation_key);
+ sha512(fingerprint, strlen(fingerprint), hash);
+ b64encode_urlsafe(hash, hash, 18);
+
+ // Create a random cookie for this invitation.
+ char cookie[25];
+ randomize(cookie, 18);
+
+ // Create a filename that doesn't reveal the cookie itself
+ char buf[18 + strlen(fingerprint)];
+ char cookiehash[64];
+ memcpy(buf, cookie, 18);
+ memcpy(buf + 18, fingerprint, sizeof(buf) - 18);
+ sha512(buf, sizeof(buf), cookiehash);
+ b64encode_urlsafe(cookiehash, cookiehash, 18);
+
+ b64encode_urlsafe(cookie, cookie, 18);
+
+ free(fingerprint);
+
+ /* Construct the invitation file */
+ uint8_t outbuf[4096];
+ packmsg_output_t inv = {outbuf, sizeof(outbuf)};
+
+ packmsg_add_uint32(&inv, MESHLINK_INVITATION_VERSION);
+ 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? */
+
+ /* 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];
+ memset(configs, 0, sizeof(configs));
+ int count = 0;
+
+ if(config_read(mesh, "current", mesh->self->name, &configs[count], mesh->config_key)) {
+ count++;
+ }
+
+ /* Append host config files to the invitation file */
+ packmsg_add_array(&inv, count);
+
+ for(int i = 0; i < count; i++) {
+ packmsg_add_bin(&inv, configs[i].buf, configs[i].len);
+ config_free(&configs[i]);
+ }
+
+ config_t config = {outbuf, packmsg_output_size(&inv, outbuf)};
+
+ 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));
+ return NULL;
+ }
+
+ // Create an URL from the local address, key hash and cookie
+ char *url;
+ xasprintf(&url, "%s/%s%s", address, hash, cookie);
+ free(address);
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return url;
+}
+
+char *meshlink_invite(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name) {
+ return meshlink_invite_ex(mesh, submesh, name, 0);
+}
+
+bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
+ if(!mesh || !invitation) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ //Before doing meshlink_join make sure we are not connected to another mesh
+ if(mesh->threadstarted) {
+ logger(mesh, MESHLINK_ERROR, "Cannot join while started\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ // Refuse to join a mesh if we are already part of one. We are part of one if we know at least one other node.
+ if(mesh->nodes->count > 1) {
+ logger(mesh, MESHLINK_ERROR, "Already part of an existing mesh\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ //TODO: think of a better name for this variable, or of a different way to tokenize the invitation URL.
+ char copy[strlen(invitation) + 1];
+ strcpy(copy, invitation);
+
+ // Split the invitation URL into a list of hostname/port tuples, a key hash and a cookie.
+
+ char *slash = strchr(copy, '/');
+
+ if(!slash) {
+ goto invalid;
+ }
+
+ *slash++ = 0;
+
+ if(strlen(slash) != 48) {
+ goto invalid;
+ }
+
+ char *address = copy;
+ char *port = NULL;
+
+ if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18)) {
+ goto invalid;
+ }
+
+ // Generate a throw-away key for the invitation.
+ ecdsa_t *key = ecdsa_generate();
+
+ if(!key) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ char *b64key = ecdsa_get_base64_public_key(key);
+ char *comma;
+ mesh->sock = -1;
+
+ while(address && *address) {
+ // We allow commas in the address part to support multiple addresses in one invitation URL.
+ comma = strchr(address, ',');
+
+ if(comma) {
+ *comma++ = 0;
+ }
+
+ // Split of the port
+ port = strrchr(address, ':');
+
+ if(!port) {
+ goto invalid;
+ }
+
+ *port++ = 0;
+
+ // IPv6 address are enclosed in brackets, per RFC 3986
+ if(*address == '[') {
+ address++;
+ char *bracket = strchr(address, ']');
+
+ if(!bracket) {
+ goto invalid;
+ }
+
+ *bracket++ = 0;
+
+ if(*bracket) {
+ goto invalid;
+ }
+ }
+
+ // Connect to the meshlink daemon mentioned in the URL.
+ struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
+
+ if(ai) {
+ for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
+ mesh->sock = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns);
+
+ if(mesh->sock == -1) {
+ logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno));
+ meshlink_errno = MESHLINK_ENETWORK;
+ continue;
+ }
+
+ set_timeout(mesh->sock, 5000);
+
+ if(connect(mesh->sock, aip->ai_addr, aip->ai_addrlen)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not connect to %s port %s: %s\n", address, port, strerror(errno));
+ meshlink_errno = MESHLINK_ENETWORK;
+ closesocket(mesh->sock);
+ mesh->sock = -1;
+ continue;
+ }
+ }
+
+ freeaddrinfo(ai);
+ } else {
+ meshlink_errno = MESHLINK_ERESOLV;
+ }
+
+ if(mesh->sock != -1 || !comma) {
+ break;
+ }
+
+ address = comma;
+ }
+
+ if(mesh->sock == -1) {
+ pthread_mutex_unlock(&mesh->mesh_mutex);
+ return false;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "Connected to %s port %s...\n", address, port);
+
+ // Tell him we have an invitation, and give him our throw-away key.
+
+ mesh->blen = 0;
+
+ 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;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ free(b64key);
+
+ char hisname[4096] = "";
+ int code, hismajor, hisminor = 0;
+
+ if(!recvline(mesh, sizeof(mesh)->line) || sscanf(mesh->line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(mesh, sizeof(mesh)->line) || !rstrip(mesh->line) || sscanf(mesh->line, "%d ", &code) != 1 || code != ACK || strlen(mesh->line) < 3) {
+ logger(mesh, MESHLINK_DEBUG, "Cannot read greeting from peer\n");
+ closesocket(mesh->sock);
+ meshlink_errno = MESHLINK_ENETWORK;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ // Check if the hash of the key he gave us matches the hash in the URL.
+ char *fingerprint = mesh->line + 2;
+ char hishash[64];
+
+ if(sha512(fingerprint, strlen(fingerprint), hishash)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create hash\n%s\n", mesh->line + 2);
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ if(memcmp(hishash, mesh->hash, 18)) {
+ logger(mesh, MESHLINK_DEBUG, "Peer has an invalid key!\n%s\n", mesh->line + 2);
+ meshlink_errno = MESHLINK_EPEER;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+
+ }
+
+ ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint);
+
+ if(!hiskey) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ // Start an SPTPS session
+ if(!sptps_start(&mesh->sptps, mesh, true, false, key, hiskey, meshlink_invitation_label, sizeof(meshlink_invitation_label), invitation_send, invitation_receive)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ // Feed rest of input buffer to SPTPS
+ if(!sptps_receive_data(&mesh->sptps, mesh->buffer, mesh->blen)) {
+ meshlink_errno = MESHLINK_EPEER;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ int len;
+
+ while((len = recv(mesh->sock, mesh->line, sizeof(mesh)->line, 0))) {
+ if(len < 0) {
+ if(errno == EINTR) {
+ continue;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "Error reading data from %s port %s: %s\n", address, port, strerror(errno));
+ meshlink_errno = MESHLINK_ENETWORK;