+ /* 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 */
+ config_t configs[5] = {NULL};
+ int count = 0;
+
+ if(config_read(mesh, mesh->self->name, &configs[count])) {
+ 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, cookiehash, &config)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ pthread_mutex_unlock(&(mesh->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_DEBUG, "Already connected to a 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, 1, 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;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ if(!sptps_receive_data(&mesh->sptps, mesh->line, len)) {
+ meshlink_errno = MESHLINK_EPEER;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+ }
+
+ sptps_stop(&mesh->sptps);
+ ecdsa_free(hiskey);
+ ecdsa_free(key);
+ closesocket(mesh->sock);
+
+ if(!mesh->success) {
+ logger(mesh, MESHLINK_DEBUG, "Connection closed by peer, invitation cancelled.\n");
+ meshlink_errno = MESHLINK_EPEER;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return true;
+
+invalid:
+ logger(mesh, MESHLINK_DEBUG, "Invalid invitation URL\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return false;
+}
+
+char *meshlink_export(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ // Create a config file on the fly.
+
+ 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));
+
+ 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
+
+ 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(&out2)) {
+ logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
+ meshlink_errno = MESHLINK_EINTERNAL;
+ free(buf2);
+ return NULL;
+ }
+
+ b64encode_urlsafe(buf2, (char *)buf2, packmsg_output_size(&out2, buf2));
+
+ return (char *)buf2;
+}
+
+bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
+ if(!mesh || !data) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ size_t datalen = strlen(data);
+ uint8_t *buf = xmalloc(datalen);
+ int buflen = b64decode(data, buf, datalen);
+
+ if(!buflen) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid data\n");
+ meshlink_errno = MESHLINK_EPEER;
+ return false;
+ }
+
+ packmsg_input_t in = {buf, buflen};
+ uint32_t count = packmsg_get_array(&in);
+
+ if(!count) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid data\n");
+ meshlink_errno = MESHLINK_EPEER;
+ return false;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ while(count--) {
+ const void *data;
+ uint32_t len = packmsg_get_bin_raw(&in, &data);
+
+ if(!len) {
+ break;
+ }
+
+ packmsg_input_t in2 = {data, len};
+ uint32_t version = packmsg_get_uint32(&in2);
+ char *name = packmsg_get_str_dup(&in2);
+
+ if(!packmsg_input_ok(&in2) || version != MESHLINK_CONFIG_VERSION || !check_id(name)) {
+ free(name);
+ packmsg_input_invalidate(&in2);
+ break;
+ }
+
+ if(!check_id(name)) {
+ free(name);
+ break;
+ }
+
+ node_t *n = lookup_node(mesh, name);
+
+ if(n) {
+ free(name);
+ logger(mesh, MESHLINK_DEBUG, "Node %s already exists, not importing\n", 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);
+ }
+
+ if(!packmsg_done(&in2) || keylen != 32) {
+ packmsg_input_invalidate(&in);
+ free_node(n);
+ break;
+ } else {
+ config_t config = {data, len};
+ config_write(mesh, n->name, &config);
+ node_add(mesh, n);
+ }
+ }
+
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ if(!packmsg_done(&in)) {
+ logger(mesh, MESHLINK_ERROR, "Invalid data\n");
+ meshlink_errno = MESHLINK_EPEER;
+ return false;
+ }
+
+ return true;
+}
+
+void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ node_t *n;
+ n = (node_t *)node;
+
+ if(n == mesh->self) {
+ logger(mesh, MESHLINK_ERROR, "%s blacklisting itself?\n", node->name);
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return;
+ }
+
+ if(n->status.blacklisted) {
+ logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", node->name);
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+ return;