+ 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);
+
+ if(mesh->self->canonical_address && !strchr(mesh->self->canonical_address, ' ')) {
+ char *canonical_address = NULL;
+ xasprintf(&canonical_address, "%s %s", mesh->self->canonical_address, mesh->myport);
+ packmsg_add_str(&out, canonical_address);
+ free(canonical_address);
+ } else {
+ 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;