+ 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 = adns_blocking_request(mesh, xstrdup(address), xstrdup(port), 5);
+
+ if(ai) {
+ for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
+ state.sock = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns);
+
+ if(state.sock == -1) {
+ logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno));
+ meshlink_errno = MESHLINK_ENETWORK;
+ continue;
+ }
+
+ set_timeout(state.sock, 5000);
+
+ if(connect(state.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(state.sock);
+ state.sock = -1;
+ continue;
+ }
+
+ break;
+ }
+
+ freeaddrinfo(ai);
+ } else {
+ meshlink_errno = MESHLINK_ERESOLV;
+ }
+
+ if(state.sock != -1 || !comma) {
+ break;
+ }
+
+ address = comma;
+ }
+
+ if(state.sock == -1) {
+ goto exit;
+ }
+
+ 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.
+
+ state.blen = 0;
+
+ if(!sendline(state.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));
+ meshlink_errno = MESHLINK_ENETWORK;
+ goto exit;
+ }
+
+ free(b64key);
+
+ char hisname[4096] = "";
+ int code, hismajor, hisminor = 0;
+
+ if(!recvline(&state) || sscanf(state.line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(&state) || !rstrip(state.line) || sscanf(state.line, "%d ", &code) != 1 || code != ACK || strlen(state.line) < 3) {
+ logger(mesh, MESHLINK_DEBUG, "Cannot read greeting from peer\n");
+ meshlink_errno = MESHLINK_ENETWORK;
+ goto exit;
+ }
+
+ // Check if the hash of the key he gave us matches the hash in the URL.
+ char *fingerprint = state.line + 2;
+ char hishash[64];
+
+ if(sha512(fingerprint, strlen(fingerprint), hishash)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create hash\n%s\n", state.line + 2);
+ meshlink_errno = MESHLINK_EINTERNAL;
+ goto exit;
+ }
+
+ if(memcmp(hishash, state.hash, 18)) {
+ logger(mesh, MESHLINK_DEBUG, "Peer has an invalid key!\n%s\n", state.line + 2);
+ meshlink_errno = MESHLINK_EPEER;
+ goto exit;
+ }
+
+ hiskey = ecdsa_set_base64_public_key(fingerprint);
+
+ if(!hiskey) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ goto exit;
+ }
+
+ // Start an SPTPS session
+ if(!sptps_start(&state.sptps, &state, true, false, key, hiskey, meshlink_invitation_label, sizeof(meshlink_invitation_label), invitation_send, invitation_receive)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ goto exit;
+ }
+
+ // Feed rest of input buffer to SPTPS
+ if(!sptps_receive_data(&state.sptps, state.buffer, state.blen)) {
+ meshlink_errno = MESHLINK_EPEER;
+ goto exit;
+ }
+
+ ssize_t len;
+ logger(mesh, MESHLINK_DEBUG, "Starting invitation recv loop: %d %zu\n", state.sock, sizeof(state.line));
+
+ while((len = recv(state.sock, state.line, sizeof(state.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;
+ goto exit;
+ }
+
+ if(!sptps_receive_data(&state.sptps, state.line, len)) {
+ meshlink_errno = MESHLINK_EPEER;
+ goto exit;
+ }
+ }
+
+ if(!state.success) {
+ logger(mesh, MESHLINK_DEBUG, "Connection closed by peer, invitation cancelled.\n");
+ meshlink_errno = MESHLINK_EPEER;
+ goto exit;
+ }
+
+ sptps_stop(&state.sptps);
+ ecdsa_free(hiskey);
+ ecdsa_free(key);
+ closesocket(state.sock);
+
+ pthread_mutex_unlock(&mesh->mutex);
+ return true;
+
+invalid:
+ logger(mesh, MESHLINK_DEBUG, "Invalid invitation URL\n");
+ meshlink_errno = MESHLINK_EINVAL;
+exit:
+ sptps_stop(&state.sptps);
+ ecdsa_free(hiskey);
+ ecdsa_free(key);
+
+ if(state.sock != -1) {
+ closesocket(state.sock);
+ }
+
+ pthread_mutex_unlock(&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->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 < MAX_RECENT; 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]);
+ }
+
+ packmsg_add_int64(&out, 0);
+ packmsg_add_int64(&out, 0);
+
+ pthread_mutex_unlock(&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->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(&in);
+ break;
+ }
+
+ if(!check_id(name)) {
+ free(name);
+ break;
+ }
+
+ node_t *n = lookup_node(mesh, name);
+
+ if(n) {
+ logger(mesh, MESHLINK_DEBUG, "Node %s already exists, not importing\n", name);
+ free(name);
+ continue;
+ }
+
+ n = new_node();
+ n->name = name;
+
+ config_t config = {data, len};
+
+ if(!node_read_from_config(mesh, n, &config)) {
+ free_node(n);
+ packmsg_input_invalidate(&in);
+ break;
+ }
+
+ /* Clear the reachability times, since we ourself have never seen these nodes yet */
+ n->last_reachable = 0;
+ n->last_unreachable = 0;
+
+ if(!node_write_config(mesh, n)) {
+ free_node(n);
+ return false;
+ }
+
+ node_add(mesh, n);
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);