*arg++ = 0;
}
- if(!strcasecmp(buf, "invite")) {
- char *invitation;
-
- if(!arg) {
- fprintf(stderr, "/invite requires an argument!\n");
- return;
- }
-
- invitation = meshlink_invite(mesh, NULL, arg);
-
- if(!invitation) {
- fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno));
- return;
- }
-
- printf("Invitation for %s: %s\n", arg, invitation);
- free(invitation);
- } else if(!strcasecmp(buf, "join")) {
+ if(!strcasecmp(buf, "join")) {
if(!arg) {
fprintf(stderr, "/join requires an argument!\n");
return;
printf(
"<name>: <message> Send a message to the given node.\n"
" Subsequent messages don't need the <name>: prefix.\n"
- "/invite <name> Create an invitation for a new node.\n"
"/join <invitation> Join an existing mesh using an invitation.\n"
"/kick <name> Blacklist the given node.\n"
"/who [<name>] List all nodes or show information about the given node.\n"
*arg++ = 0;
}
- if(!strcasecmp(buf, "invite")) {
- char *invitation;
-
- if(!arg) {
- fprintf(stderr, "/invite requires an argument!\n");
- return;
- }
-
- invitation = mesh->invite(NULL, arg);
-
- if(!invitation) {
- fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink::strerror());
- return;
- }
-
- printf("Invitation for %s: %s\n", arg, invitation);
- free(invitation);
- } else if(!strcasecmp(buf, "join")) {
+ if(!strcasecmp(buf, "join")) {
if(!arg) {
fprintf(stderr, "/join requires an argument!\n");
return;
printf(
"<name>: <message> Send a message to the given node.\n"
" Subsequent messages don't need the <name>: prefix.\n"
- "/invite <name> Create an invitation for a new node.\n"
"/join <invitation> Join an existing mesh using an invitation.\n"
"/kick <name> Blacklist the given node.\n"
"/who [<name>] List all nodes or show information about the given node.\n"
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);
return false;
}
- make_invitation_path(mesh, conf_subdir, "", path, sizeof(path));
-
- if(mkdir(path, 0700)) {
- logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
- return false;
- }
-
return true;
}
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);
-
- if(!mesh->confbase) {
- return false;
- }
-
- 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);
- }
-
- return false;
- }
-
- FILE *f = fopen(used_path, "r");
-
- if(!f) {
- logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
- return false;
- }
-
- // Check the timestamp
- struct stat st;
-
- if(fstat(fileno(f), &st)) {
- logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
- fclose(f);
- unlink(used_path);
- return false;
- }
-
- 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;
- }
-
- 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);
- return false;
- }
-
- fclose(f);
-
- if(unlink(used_path)) {
- logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno));
- return false;
- }
-
- snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir);
-
- if(!sync_path(path)) {
- logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
- return true;
-}
-
-/// 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);
-
- if(!mesh->confbase) {
- return false;
- }
-
- char path[PATH_MAX];
- make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
-
- FILE *f = fopen(path, "w");
-
- if(!f) {
- logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
- if(!config_write_file(mesh, f, config, key)) {
- logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
- fclose(f);
- return false;
- }
-
- if(fclose(f)) {
- logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
- snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir);
-
- if(!sync_path(path)) {
- logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
- return true;
-}
-
-/// Purge old invitation files
-size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
- 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;
- }
-
- errno = 0;
- size_t count = 0;
- struct dirent *ent;
-
- while((ent = readdir(dir))) {
- if(strlen(ent->d_name) != 24) {
- continue;
- }
-
- char invname[PATH_MAX];
- struct stat st;
-
- 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;
- }
-
- 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;
- }
- }
-
- if(errno) {
- logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
- closedir(dir);
- meshlink_errno = MESHLINK_ESTORAGE;
- return 0;
- }
-
- closedir(dir);
-
- return count;
-}
-
-/// Purge invitations for the given node
-size_t invitation_purge_node(meshlink_handle_t *mesh, const char *node_name) {
- 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;
- }
-
- errno = 0;
- size_t count = 0;
- struct dirent *ent;
-
- while((ent = readdir(dir))) {
- if(strlen(ent->d_name) != 24) {
- continue;
- }
-
- char invname[PATH_MAX];
-
- 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;
- }
-
- FILE *f = fopen(invname, "r");
-
- if(!f) {
- errno = 0;
- continue;
- }
-
- config_t config;
-
- 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;
- }
-
- packmsg_input_t in = {config.buf, config.len};
- packmsg_get_uint32(&in); // skip version
- char *name = packmsg_get_str_dup(&in);
-
- if(name && !strcmp(name, node_name)) {
- logger(mesh, MESHLINK_DEBUG, "Removing invitation for %s", node_name);
- unlink(invname);
- }
-
- free(name);
- config_free(&config);
- fclose(f);
- }
-
- if(errno) {
- logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
- closedir(dir);
- meshlink_errno = MESHLINK_ESTORAGE;
- return 0;
- }
-
- closedir(dir);
-
- return count;
-}
return meshlink_clear_canonical_address(handle, node);
}
- /// Add an invitation address for the local node.
- /** This function adds an address for the local node, which will be used only for invitation URLs.
- * This address is not stored permanently.
- * Multiple addresses can be added using multiple calls to this function.
- *
- * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname.
- * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format.
- * If it is NULL, the listening port's number will be used.
- *
- * @return This function returns true if the address was added, false otherwise.
- */
- bool add_invitation_address(const char *address, const char *port) {
- return meshlink_add_invitation_address(handle, address, port);
- }
-
- /// Clears all invitation address for the local node.
- /** This function removes all addresses added with meshlink_add_invitation_address().
- */
- void clear_invitation_addresses() {
- return meshlink_clear_invitation_addresses(handle);
- }
-
/// Add an Address for the local node.
/** This function adds an Address for the local node, which will be used for invitation URLs.
* @deprecated This function is deprecated, use set_canonical_address() and/or add_invitation_address().
return meshlink_set_port(handle, port);
}
- /// Set the timeout for invitations.
- /** This function sets the timeout for invitations.
- * The timeout is retroactively applied to all outstanding invitations.
- *
- * @param timeout The timeout for invitations in seconds.
- */
- void set_invitation_timeout(int timeout) {
- meshlink_set_invitation_timeout(handle, timeout);
- }
-
/// Set the scheduling granularity of the application
/** This should be set to the effective scheduling granularity for the application.
* This depends on the scheduling granularity of the operating system, the application's
meshlink_set_storage_policy(handle, policy);
}
- /// Invite another node into the mesh.
- /** This function generates an invitation that can be used by another node to join the same mesh as the local node.
- * The generated invitation is a string containing a URL.
- * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL.
- * The URL can only be used once, after the user has joined the mesh the URL is no longer valid.
- *
- * @param submesh A handle to the submesh to put the invitee in.
- * @param name The name that the invitee will use in the mesh.
- * @param flags A bitwise-or'd combination of flags that controls how the URL is generated.
- *
- * @return This function returns a string that contains the invitation URL.
- * The application should call free() after it has finished using the URL.
- */
- char *invite(submesh *submesh, const char *name, uint32_t flags = 0) {
- return meshlink_invite_ex(handle, submesh, name, flags);
- }
-
/// Use an invitation to join a mesh.
/** This function allows the local node to join an existing mesh using an invitation URL generated by another node.
* An invitation can only be used if the local node has never connected to other nodes before.
return len;
}
-static void get_canonical_address(node_t *n, char **hostname, char **port) {
- if(!n->canonical_address) {
- return;
- }
-
- *hostname = xstrdup(n->canonical_address);
- char *space = strchr(*hostname, ' ');
-
- if(space) {
- *space++ = 0;
- *port = xstrdup(space);
- }
-}
-
static bool is_valid_hostname(const char *hostname) {
if(!*hostname) {
return false;
return xstrdup(localaddr);
}
-static void remove_duplicate_hostnames(char *host[], char *port[], int n) {
- for(int i = 0; i < n; i++) {
- if(!host[i]) {
- continue;
- }
-
- // Ignore duplicate hostnames
- bool found = false;
-
- for(int j = 0; j < i; j++) {
- if(!host[j]) {
- continue;
- }
-
- if(strcmp(host[i], host[j])) {
- continue;
- }
-
- if(strcmp(port[i], port[j])) {
- continue;
- }
-
- found = true;
- break;
- }
-
- if(found || !is_valid_hostname(host[i])) {
- free(host[i]);
- free(port[i]);
- host[i] = NULL;
- port[i] = NULL;
- continue;
- }
- }
-}
-
-// This gets the hostname part for use in invitation URLs
-static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) {
- int count = 4 + (mesh->invitation_addresses ? mesh->invitation_addresses->count : 0);
- int n = 0;
- char *hostname[count];
- char *port[count];
- char *hostport = NULL;
-
- memset(hostname, 0, sizeof(hostname));
- memset(port, 0, sizeof(port));
-
- if(!(flags & (MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC))) {
- flags |= MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC;
- }
-
- if(!(flags & (MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6))) {
- flags |= MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6;
- }
-
- // Add all explicitly set invitation addresses
- if(mesh->invitation_addresses) {
- for list_each(char, combo, mesh->invitation_addresses) {
- hostname[n] = xstrdup(combo);
- char *slash = strrchr(hostname[n], '/');
-
- if(slash) {
- *slash = 0;
- port[n] = xstrdup(slash + 1);
- }
-
- n++;
- }
- }
-
- // Add local addresses if requested
- if(flags & MESHLINK_INVITE_LOCAL) {
- if(flags & MESHLINK_INVITE_IPV4) {
- hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET);
- }
-
- if(flags & MESHLINK_INVITE_IPV6) {
- hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET6);
- }
- }
-
- // Add public/canonical addresses if requested
- if(flags & MESHLINK_INVITE_PUBLIC) {
- // Try the CanonicalAddress first
- get_canonical_address(mesh->self, &hostname[n], &port[n]);
-
- if(!hostname[n] && count == 4) {
- if(flags & MESHLINK_INVITE_IPV4) {
- hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET);
- }
-
- if(flags & MESHLINK_INVITE_IPV6) {
- hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET6);
- }
- } else {
- n++;
- }
- }
-
- for(int i = 0; i < n; i++) {
- // Ensure we always have a port number
- if(hostname[i] && !port[i]) {
- port[i] = xstrdup(mesh->myport);
- }
- }
-
- remove_duplicate_hostnames(hostname, port, n);
-
- // Resolve the hostnames
- for(int i = 0; i < n; i++) {
- if(!hostname[i]) {
- continue;
- }
-
- // Convert what we have to a sockaddr
- struct addrinfo *ai_in = adns_blocking_request(mesh, xstrdup(hostname[i]), xstrdup(port[i]), SOCK_STREAM, 5);
-
- if(!ai_in) {
- continue;
- }
-
- // Remember the address(es)
- for(struct addrinfo *aip = ai_in; aip; aip = aip->ai_next) {
- node_add_recent_address(mesh, mesh->self, (sockaddr_t *)aip->ai_addr);
- }
-
- freeaddrinfo(ai_in);
- continue;
- }
-
- // Remove duplicates again, since IPv4 and IPv6 addresses might map to the same hostname
- remove_duplicate_hostnames(hostname, port, n);
-
- // Concatenate all unique address to the hostport string
- for(int i = 0; i < n; i++) {
- if(!hostname[i]) {
- continue;
- }
-
- // Append the address to the hostport string
- char *newhostport;
- xasprintf(&newhostport, (strchr(hostname[i], ':') ? "%s%s[%s]:%s" : "%s%s%s:%s"), hostport ? hostport : "", hostport ? "," : "", hostname[i], port[i]);
- free(hostport);
- hostport = newhostport;
-
- free(hostname[i]);
- free(port[i]);
- }
-
- return hostport;
-}
-
static bool try_bind(meshlink_handle_t *mesh, int port) {
struct addrinfo *ai = NULL;
struct addrinfo hint = {
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_nil(&out); // Invitation keys are not supported
packmsg_add_uint16(&out, atoi(mesh->myport));
if(!packmsg_output_ok(&out)) {
}
static bool ecdsa_keygen(meshlink_handle_t *mesh) {
- logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypairs:\n");
+ logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypair:\n");
mesh->private_key = ecdsa_generate();
- mesh->invitation_key = ecdsa_generate();
- if(!mesh->private_key || !mesh->invitation_key) {
+ if(!mesh->private_key) {
logger(mesh, MESHLINK_ERROR, "Error during key generation!\n");
meshlink_errno = MESHLINK_EINTERNAL;
return false;
packmsg_input_t in = {config.buf, config.len};
const void *private_key;
- const void *invitation_key;
uint32_t version = packmsg_get_uint32(&in);
char *name = packmsg_get_str_dup(&in);
uint32_t private_key_len = packmsg_get_bin_raw(&in, &private_key);
- uint32_t invitation_key_len = packmsg_get_bin_raw(&in, &invitation_key);
+ packmsg_skip_element(&in); // Invitation key is not supported
uint16_t myport = packmsg_get_uint16(&in);
- if(!packmsg_done(&in) || version != MESHLINK_CONFIG_VERSION || private_key_len != 96 || invitation_key_len != 96) {
+ if(!packmsg_done(&in) || version != MESHLINK_CONFIG_VERSION || private_key_len != 96) {
logger(NULL, MESHLINK_ERROR, "Error parsing main configuration file!");
free(name);
config_free(&config);
mesh->name = name;
xasprintf(&mesh->myport, "%u", myport);
mesh->private_key = ecdsa_set_private_key(private_key);
- mesh->invitation_key = ecdsa_set_private_key(invitation_key);
config_free(&config);
/* Create a node for ourself and read our host configuration file */
mesh->appname = xstrdup(params->appname);
mesh->devclass = params->devclass;
mesh->discovery.enabled = true;
- mesh->invitation_timeout = 604800; // 1 week
mesh->netns = params->netns;
mesh->submeshes = NULL;
mesh->log_cb = global_log_cb;
#endif
- ecdsa_free(mesh->invitation_key);
-
if(mesh->netns != -1) {
close(mesh->netns);
}
free(mesh->packet);
ecdsa_free(mesh->private_key);
- if(mesh->invitation_addresses) {
- list_delete_list(mesh->invitation_addresses);
- }
-
main_config_unlock(mesh);
pthread_mutex_unlock(&mesh->mutex);
return rval;
}
-static bool refresh_invitation_key(meshlink_handle_t *mesh) {
- if(pthread_mutex_lock(&mesh->mutex) != 0) {
- abort();
- }
-
- size_t count = invitation_purge_old(mesh, time(NULL) - mesh->invitation_timeout);
-
- if(!count) {
- // TODO: Update invitation key if necessary?
- }
-
- pthread_mutex_unlock(&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) {
logger(mesh, MESHLINK_DEBUG, "meshlink_set_canonical_address(%s, %s, %s)", node ? node->name : "(null)", address ? address : "(null)", port ? port : "(null)");
return config_sync(mesh, "current");
}
-bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) {
- logger(mesh, MESHLINK_DEBUG, "meshlink_add_invitation_address(%s, %s)", address ? address : "(null)", port ? port : "(null)");
-
- if(!mesh || !address) {
- meshlink_errno = MESHLINK_EINVAL;
- return false;
- }
-
- if(!is_valid_hostname(address)) {
- logger(mesh, MESHLINK_ERROR, "Invalid character in address: %s\n", address);
- meshlink_errno = MESHLINK_EINVAL;
- return false;
- }
-
- if(port && !is_valid_port(port)) {
- logger(mesh, MESHLINK_ERROR, "Invalid character in port: %s\n", address);
- meshlink_errno = MESHLINK_EINVAL;
- return false;
- }
-
- char *combo;
-
- if(port) {
- xasprintf(&combo, "%s/%s", address, port);
- } else {
- combo = xstrdup(address);
- }
-
- if(pthread_mutex_lock(&mesh->mutex) != 0) {
- abort();
- }
-
- if(!mesh->invitation_addresses) {
- mesh->invitation_addresses = list_alloc((list_action_t)free);
- }
-
- list_insert_tail(mesh->invitation_addresses, combo);
- pthread_mutex_unlock(&mesh->mutex);
-
- return true;
-}
-
-void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh) {
- logger(mesh, MESHLINK_DEBUG, "meshlink_clear_invitation_addresses()");
-
- if(!mesh) {
- meshlink_errno = MESHLINK_EINVAL;
- return;
- }
-
- if(pthread_mutex_lock(&mesh->mutex) != 0) {
- abort();
- }
-
- if(mesh->invitation_addresses) {
- list_delete_list(mesh->invitation_addresses);
- mesh->invitation_addresses = NULL;
- }
-
- pthread_mutex_unlock(&mesh->mutex);
-}
-
bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
logger(mesh, MESHLINK_DEBUG, "meshlink_add_address(%s)", address ? address : "(null)");
return rval && meshlink_get_port(mesh) == port;
}
-void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) {
- logger(mesh, MESHLINK_DEBUG, "meshlink_invitation_timeout(%d)", timeout);
-
- mesh->invitation_timeout = timeout;
-}
-
-char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name, uint32_t flags) {
- logger(mesh, MESHLINK_DEBUG, "meshlink_invite_ex(%s, %s, %u)", submesh ? submesh->name : "(null)", name ? name : "(null)", 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_ERROR, "Invalid submesh handle.\n");
- meshlink_errno = MESHLINK_EINVAL;
- return NULL;
- }
- } else {
- s = (meshlink_submesh_t *)mesh->self->submesh;
- }
-
- if(pthread_mutex_lock(&mesh->mutex) != 0) {
- abort();
- }
-
- // Check validity of the new node's name
- if(!check_id(name)) {
- logger(mesh, MESHLINK_ERROR, "Invalid name for node.\n");
- meshlink_errno = MESHLINK_EINVAL;
- pthread_mutex_unlock(&mesh->mutex);
- return NULL;
- }
-
- // Ensure no host configuration file with that name exists
- if(config_exists(mesh, "current", name)) {
- logger(mesh, MESHLINK_ERROR, "A host config file for %s already exists!\n", name);
- meshlink_errno = MESHLINK_EEXIST;
- pthread_mutex_unlock(&mesh->mutex);
- return NULL;
- }
-
- // Ensure no other nodes know about this name
- if(lookup_node(mesh, name)) {
- logger(mesh, MESHLINK_ERROR, "A node with name %s is already known!\n", name);
- meshlink_errno = MESHLINK_EEXIST;
- pthread_mutex_unlock(&mesh->mutex);
- return NULL;
- }
-
- // Get the local address
- char *address = get_my_hostname(mesh, flags);
-
- if(!address) {
- logger(mesh, MESHLINK_ERROR, "No Address known for ourselves!\n");
- meshlink_errno = MESHLINK_ERESOLV;
- pthread_mutex_unlock(&mesh->mutex);
- return NULL;
- }
-
- if(!refresh_invitation_key(mesh)) {
- meshlink_errno = MESHLINK_EINTERNAL;
- pthread_mutex_unlock(&mesh->mutex);
- return NULL;
- }
-
- // If we changed our own host config file, write it out now
- if(mesh->self->status.dirty) {
- if(!node_write_config(mesh, mesh->self, false)) {
- logger(mesh, MESHLINK_ERROR, "Could not write our own host config file!\n");
- pthread_mutex_unlock(&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->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->mutex);
- return url;
-}
-
-char *meshlink_invite(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name) {
- logger(mesh, MESHLINK_DEBUG, "meshlink_invite_ex(%s, %s)", submesh ? submesh->name : "(null)", name ? name : "(null)");
-
- return meshlink_invite_ex(mesh, submesh, name, 0);
-}
-
bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
logger(mesh, MESHLINK_DEBUG, "meshlink_join(%s)", invitation ? invitation : "(null)");
mesh->node_status_cb(mesh, (meshlink_node_t *)n, false);
}
- /* Remove any outstanding invitations */
- invitation_purge_node(mesh, n->name);
-
return node_write_config(mesh, n, true) && config_sync(mesh, "current");
}
return false;
}
- /* Delete any pending invitations */
- invitation_purge_node(mesh, n->name);
-
/* Delete the node struct and any remaining edges referencing this node */
node_del(mesh, n);
*/
bool meshlink_clear_canonical_address(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__));
-/// Add an invitation address for the local node.
-/** This function adds an address for the local node, which will be used only for invitation URLs.
- * This address is not stored permanently.
- * Multiple addresses can be added using multiple calls to this function.
- *
- * \memberof meshlink_handle
- * @param mesh A handle which represents an instance of MeshLink.
- * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname.
- * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format.
- * If it is NULL, the current listening port's number will be used.
- *
- * @return This function returns true if the address was added, false otherwise.
- */
-bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) __attribute__((__warn_unused_result__));
-
-/// Clears all invitation address for the local node.
-/** This function removes all addresses added with meshlink_add_invitation_address().
- *
- * \memberof meshlink_handle
- * @param mesh A handle which represents an instance of MeshLink.
- */
-void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh);
-
/// Add an Address for the local node.
/** This function adds an Address for the local node, which will be used for invitation URLs.
* @deprecated This function is deprecated, use meshlink_set_canonical_address() and/or meshlink_add_invitation_address().
bool meshlink_set_port(struct meshlink_handle *mesh, int port) __attribute__((__warn_unused_result__));
-/// Set the timeout for invitations.
-/** This function sets the timeout for invitations.
- * Note that timeouts are only checked at the time a node tries to join using an invitation.
- * The default timeout for invitations is 1 week.
- *
- * \memberof meshlink_handle
- * @param mesh A handle which represents an instance of MeshLink.
- * @param timeout The timeout for invitations in seconds.
- */
-void meshlink_set_invitation_timeout(struct meshlink_handle *mesh, int timeout);
-
-/// Invite another node into the mesh.
-/** This function generates an invitation that can be used by another node to join the same mesh as the local node.
- * The generated invitation is a string containing a URL.
- * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL.
- * The URL can only be used once, after the user has joined the mesh the URL is no longer valid.
- *
- * \memberof meshlink_handle
- * @param mesh A handle which represents an instance of MeshLink.
- * @param submesh A handle which represents an instance of SubMesh.
- * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh.
- * After this function returns, the application is free to overwrite or free @a name.
- * @param flags A bitwise-or'd combination of flags that controls how the URL is generated.
- *
- * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error.
- * The application should call free() after it has finished using the URL.
- */
-char *meshlink_invite_ex(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, const char *name, uint32_t flags) __attribute__((__warn_unused_result__));
-
-/// Invite another node into the mesh.
-/** This function generates an invitation that can be used by another node to join the same mesh as the local node.
- * The generated invitation is a string containing a URL.
- * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL.
- * The URL can only be used once, after the user has joined the mesh the URL is no longer valid.
- *
- * Calling this function is equal to callen meshlink_invite_ex() with flags set to 0.
- *
- * \memberof meshlink_handle
- * @param mesh A handle which represents an instance of MeshLink.
- * @param submesh A handle which represents an instance of SubMesh.
- * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh.
- * After this function returns, the application is free to overwrite or free @a name.
- *
- * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error.
- * The application should call free() after it has finished using the URL.
- */
-char *meshlink_invite(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, const char *name) __attribute__((__warn_unused_result__));
-
-/// Use an invitation to join a mesh.
/** This function allows the local node to join an existing mesh using an invitation URL generated by another node.
* An invitation can only be used if the local node has never connected to other nodes before.
* After a successfully accepted invitation, the name of the local node may have changed.
devtool_trybind_probe
meshlink_add_address
meshlink_add_external_address
-meshlink_add_invitation_address
meshlink_blacklist
meshlink_blacklist_by_name
meshlink_channel_abort
meshlink_channel_send
meshlink_channel_shutdown
meshlink_clear_canonical_address
-meshlink_clear_invitation_addresses
meshlink_close
meshlink_destroy
meshlink_destroy_ex
meshlink_hint_address
meshlink_hint_network_change
meshlink_import
-meshlink_invite
-meshlink_invite_ex
meshlink_join
meshlink_main_loop
meshlink_open
meshlink_set_dev_class_timeouts
meshlink_set_error_cb
meshlink_set_external_address_discovery_url
-meshlink_set_invitation_timeout
meshlink_set_inviter_commits_first
meshlink_set_log_cb
meshlink_set_node_channel_timeout
char *myport;
struct ecdsa *private_key;
- struct ecdsa *invitation_key;
dev_class_t devclass;
- int invitation_timeout;
int udp_choice;
dev_class_traits_t dev_class_traits[DEV_CLASS_COUNT];
FILE *lockfile;
void *config_key;
char *external_address_url;
- struct list_t *invitation_addresses;
meshlink_storage_policy_t storage_policy;
// Thread management
return send_request(mesh, c, NULL, "%d %s %d.%d %s", ID, mesh->self->name, PROT_MAJOR, PROT_MINOR, mesh->appname);
}
-static bool commit_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data) {
- // Check if the node is known
- node_t *n = lookup_node(mesh, c->name);
-
- if(n) {
- if(n->status.blacklisted) {
- logger(mesh, MESHLINK_ERROR, "Invitee %s is blacklisted", c->name);
- } else {
- logger(mesh, MESHLINK_ERROR, "Invitee %s already known", c->name);
- }
-
- return false;
- }
-
- // Create a new node
- n = new_node();
- n->name = xstrdup(c->name);
- n->devclass = DEV_CLASS_UNKNOWN;
- n->ecdsa = ecdsa_set_public_key(data);
- n->submesh = c->submesh;
-
- // Remember its current address
- node_add_recent_address(mesh, n, &c->address);
-
- if(!node_write_config(mesh, n, true) || !config_sync(mesh, "current")) {
- logger(mesh, MESHLINK_ERROR, "Error writing configuration file for invited node %s!\n", c->name);
- free_node(n);
- return false;
-
- }
-
- node_add(mesh, n);
-
- logger(mesh, MESHLINK_INFO, "Key successfully received from %s", c->name);
-
- //TODO: callback to application to inform of an accepted invitation
-
- sptps_send_record(&c->sptps, 1, "", 0);
-
- return true;
-}
-
-static bool process_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data) {
- // Recover the filename from the cookie and the key
- char *fingerprint = ecdsa_get_base64_public_key(mesh->invitation_key);
- char hash[64];
- char hashbuf[18 + strlen(fingerprint)];
- char cookie[25];
- memcpy(hashbuf, data, 18);
- memcpy(hashbuf + 18, fingerprint, sizeof(hashbuf) - 18);
- sha512(hashbuf, sizeof(hashbuf), hash);
- b64encode_urlsafe(hash, cookie, 18);
- free(fingerprint);
-
- config_t config;
-
- if(!invitation_read(mesh, "current", cookie, &config, mesh->config_key)) {
- 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
- free(c->name);
- c->name = packmsg_get_str_dup(&in);
-
- // Check if the file contains Sub-Mesh information
- char *submesh_name = packmsg_get_str_dup(&in);
-
- if(!strcmp(submesh_name, CORE_MESH)) {
- free(submesh_name);
- c->submesh = NULL;
- } else {
- if(!check_id(submesh_name)) {
- logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie);
- free(submesh_name);
- return false;
- }
-
- c->submesh = lookup_or_create_submesh(mesh, submesh_name);
- free(submesh_name);
-
- if(!c->submesh) {
- logger(mesh, MESHLINK_ERROR, "Unknown submesh in invitation file %s\n", cookie);
- return false;
- }
- }
-
- if(mesh->inviter_commits_first && !commit_invitation(mesh, c, (const char *)data + 18)) {
- return false;
- }
-
- if(mesh->inviter_commits_first) {
- devtool_set_inviter_commits_first(true);
- }
-
- // Send the node the contents of the invitation file
- sptps_send_record(&c->sptps, 0, config.buf, config.len);
-
- config_free(&config);
-
- c->status.invitation_used = true;
-
- logger(mesh, MESHLINK_INFO, "Invitation %s successfully sent to %s", cookie, c->name);
- return true;
-}
-
-static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) {
- connection_t *c = handle;
- meshlink_handle_t *mesh = c->mesh;
-
- // Extend the time for the invitation exchange upon receiving a valid message
- c->last_ping_time = mesh->loop.now.tv_sec;
-
- if(type == SPTPS_HANDSHAKE) {
- // The peer should send its cookie first.
- return true;
- }
-
- if(mesh->inviter_commits_first) {
- if(type == 2 && len == 18 + 32 && !c->status.invitation_used) {
- return process_invitation(mesh, c, data);
- }
- } else {
- if(type == 0 && len == 18 && !c->status.invitation_used) {
- return process_invitation(mesh, c, data);
- } else if(type == 1 && len == 32 && c->status.invitation_used) {
- return commit_invitation(mesh, c, data);
- }
- }
-
- return false;
-}
-
bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) {
assert(request);
assert(*request);
return false;
}
- /* Check if this is an invitation */
-
- if(name[0] == '?') {
- if(!mesh->invitation_key) {
- logger(mesh, MESHLINK_ERROR, "Got invitation from %s but we don't have an invitation key", c->name);
- return false;
- }
-
- c->ecdsa = ecdsa_set_base64_public_key(name + 1);
-
- if(!c->ecdsa) {
- logger(mesh, MESHLINK_ERROR, "Got bad invitation from %s", c->name);
- return false;
- }
-
- c->status.invitation = true;
- char *mykey = ecdsa_get_base64_public_key(mesh->invitation_key);
-
- if(!mykey) {
- return false;
- }
-
- if(!send_request(mesh, c, NULL, "%d %s", ACK, mykey)) {
- return false;
- }
-
- free(mykey);
-
- c->protocol_minor = 2;
- c->allow_request = 1;
- c->last_ping_time = mesh->loop.now.tv_sec;
-
- return sptps_start(&c->sptps, c, false, false, mesh->invitation_key, c->ecdsa, meshlink_invitation_label, sizeof(meshlink_invitation_label), send_meta_sptps, receive_invitation_sptps);
- }
-
/* Check if identity is a valid name */
if(!check_id(name)) {
ephemeral \
get-all-nodes \
import-export \
- invite-join \
meta-connections \
sign-verify \
storage-policy \
TESTS += \
api_set_node_status_cb
-if BLACKBOX_TESTS
-SUBDIRS = blackbox
-endif
+#if BLACKBOX_TESTS
+#SUBDIRS = blackbox
+#endif
dist_check_SCRIPTS = $(TESTS)
ephemeral \
get-all-nodes \
import-export \
- invite-join \
meta-connections \
sign-verify \
storage-policy \
import_export_SOURCES = import-export.c utils.c utils.h
import_export_LDADD = $(top_builddir)/src/libmeshlink.la
-invite_join_SOURCES = invite-join.c utils.c utils.h
-invite_join_LDADD = $(top_builddir)/src/libmeshlink.la
-
meta_connections_SOURCES = meta-connections.c netns_utils.c netns_utils.h utils.c utils.h
meta_connections_LDADD = $(top_builddir)/src/libmeshlink.la
assert(meshlink_get_node_blacklisted(mesh[1], meshlink_get_node(mesh[1], name[2])));
assert(meshlink_get_node_blacklisted(mesh[2], meshlink_get_node(mesh[2], name[1])));
- // Generate an invitation for a node that is about to be blacklisted
-
- char *invitation = meshlink_invite(mesh[0], NULL, "xyzzy");
- assert(invitation);
- free(invitation);
-
// Whitelisting and blacklisting by name should work.
assert(meshlink_whitelist_by_name(mesh[0], "quux"));
free(nodes);
- // Check that blacklisted nodes are not allowed to be invited, and no invitations are left on disk.
-
- assert(!meshlink_invite(mesh[0], NULL, "xyzzy"));
-
- DIR *dir = opendir("blacklist_conf.0/current/invitations");
- assert(dir);
- struct dirent *ent;
-
- while((ent = readdir(dir))) {
- assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."));
- }
-
- closedir(dir);
-
// Since these nodes now exist we should be able to forget them.
assert(meshlink_forget_node(mesh[0], meshlink_get_node(mesh[0], "quux")));
}
/// Let the first peer in a list invite all the subsequent peers
-static void invite_peers(peer_config_t *peers, int npeers) {
- assert(meshlink_start(peers[0].mesh));
-
+static void link_peers(peer_config_t *peers, int npeers) {
for(int i = 1; i < npeers; i++) {
- char *invitation = meshlink_invite_ex(peers[0].mesh, NULL, peers[i].name, MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_NUMERIC);
- assert(invitation);
- assert(meshlink_join(peers[i].mesh, invitation));
- free(invitation);
+ char *export_relay = meshlink_export(peers[0].mesh);
+ char *export_peer = meshlink_export(peers[i].mesh);
+ assert(export_relay);
+ assert(export_peer);
+ assert(meshlink_import(peers[0].mesh, export_peer));
+ assert(meshlink_import(peers[i].mesh, export_relay));
+ free(export_relay);
+ free(export_peer);
}
-
- meshlink_stop(peers[0].mesh);
}
/// Close meshlink instances and clean up
create_peers(peers, 3, prefix);
setup_lan_topology(peers, 3);
- invite_peers(peers, 3);
+ link_peers(peers, 3);
return peers;
}
assert(last_reachable);
assert(last_unreachable);
- // Start again from scratch, now use invite/join instead of import/export
-
- close_meshlink_pair(mesh1, mesh2);
-
- assert(meshlink_destroy("storage-policy_conf.1"));
- assert(meshlink_destroy("storage-policy_conf.2"));
-
- mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE);
- mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE);
- assert(mesh1);
- assert(mesh2);
- meshlink_enable_discovery(mesh1, false);
- meshlink_enable_discovery(mesh2, false);
- meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_DISABLED);
- meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_DISABLED);
-
- // Check that joining is not possible with storage disabled
-
- assert(meshlink_set_canonical_address(mesh1, meshlink_get_self(mesh1), "localhost", NULL));
- char *invitation = meshlink_invite(mesh1, NULL, "bar");
- assert(invitation);
- assert(meshlink_start(mesh1));
- assert(!meshlink_join(mesh2, invitation));
- assert(meshlink_errno == MESHLINK_EINVAL);
- meshlink_stop(mesh1);
-
- // Try again with KEYS_ONLY
-
- meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY);
- meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY);
-
- assert(meshlink_start(mesh1));
- assert(meshlink_join(mesh2, invitation));
- assert(meshlink_errno == MESHLINK_EINVAL);
- meshlink_stop(mesh1);
-
- start_meshlink_pair(mesh1, mesh2);
-
// Close the instances and reopen them.
close_meshlink_pair(mesh1, mesh2);
// Done.
close_meshlink_pair(mesh1, mesh2);
- free(invitation);
}