static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) { connection_t *c = handle; if(type == 128) return true; if(type == 1 && c->status.invitation_used) return finalize_invitation(c, data, len); if(type != 0 || len != 18 || c->status.invitation_used) return false; // Recover the filename from the cookie and the key digest_t *digest = digest_open_by_name("sha256", 18); if(!digest) abort(); char *fingerprint = ecdsa_get_base64_public_key(invitation_key); char hashbuf[18 + strlen(fingerprint)]; char cookie[25]; memcpy(hashbuf, data, 18); memcpy(hashbuf + 18, fingerprint, sizeof hashbuf - 18); digest_create(digest, hashbuf, sizeof hashbuf, cookie); b64encode_urlsafe(cookie, cookie, 18); digest_close(digest); free(fingerprint); char filename[PATH_MAX], usedname[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "invitations" SLASH "%s", confbase, cookie); snprintf(usedname, sizeof usedname, "%s" SLASH "invitations" SLASH "%s.used", confbase, cookie); // Atomically rename the invitation file if(rename(filename, usedname)) { if(errno == ENOENT) logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s tried to use non-existing invitation %s\n", c->hostname, cookie); else logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to rename invitation %s\n", cookie); return false; } // Open the renamed file FILE *f = fopen(usedname, "r"); if(!f) { logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to open invitation %s\n", cookie); return false; } // Read the new node's Name from the file char buf[1024]; fgets(buf, sizeof buf, f); if(*buf) buf[strlen(buf) - 1] = 0; len = strcspn(buf, " \t="); char *name = buf + len; name += strspn(name, " \t"); if(*name == '=') { name++; name += strspn(name, " \t"); } buf[len] = 0; if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name)) { logger(DEBUG_ALWAYS, LOG_ERR, "Invalid invitation file %s\n", cookie); fclose(f); return false; } free(c->name); c->name = xstrdup(name); // Send the node the contents of the invitation file rewind(f); size_t result; while((result = fread(buf, 1, sizeof buf, f))) sptps_send_record(&c->sptps, 0, buf, result); sptps_send_record(&c->sptps, 1, buf, 0); fclose(f); unlink(usedname); c->status.invitation_used = true; logger(DEBUG_CONNECTIONS, LOG_INFO, "Invitation %s succesfully sent to %s (%s)", cookie, c->name, c->hostname); return true; }
int cmd_invite(int argc, char *argv[]) { if(argc < 2) { fprintf(stderr, "Not enough arguments!\n"); return 1; } // Check validity of the new node's name if(!check_id(argv[1])) { fprintf(stderr, "Invalid name for node.\n"); return 1; } char *myname = get_my_name(true); if(!myname) return 1; // Ensure no host configuration file with that name exists char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", confbase, argv[1]); if(!access(filename, F_OK)) { fprintf(stderr, "A host config file for %s already exists!\n", argv[1]); return 1; } // If a daemon is running, ensure no other nodes know about this name bool found = false; if(connect_tincd(false)) { sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); while(recvline(fd, line, sizeof line)) { char node[4096]; int code, req; if(sscanf(line, "%d %d %s", &code, &req, node) != 3) break; if(!strcmp(node, argv[1])) found = true; } if(found) { fprintf(stderr, "A node with name %s is already known!\n", argv[1]); return 1; } } snprintf(filename, sizeof filename, "%s" SLASH "invitations", confbase); if(mkdir(filename, 0700) && errno != EEXIST) { fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno)); return 1; } // Count the number of valid invitations, clean up old ones DIR *dir = opendir(filename); if(!dir) { fprintf(stderr, "Could not read directory %s: %s\n", filename, strerror(errno)); return 1; } errno = 0; int count = 0; struct dirent *ent; time_t deadline = time(NULL) - 604800; // 1 week in the past while((ent = readdir(dir))) { if(strlen(ent->d_name) != 24) continue; char invname[PATH_MAX]; struct stat st; snprintf(invname, sizeof invname, "%s" SLASH "%s", filename, ent->d_name); if(!stat(invname, &st)) { if(deadline < st.st_mtime) count++; else unlink(invname); } else { fprintf(stderr, "Could not stat %s: %s\n", invname, strerror(errno)); errno = 0; } } closedir(dir); if(errno) { fprintf(stderr, "Error while reading directory %s: %s\n", filename, strerror(errno)); return 1; } ecdsa_t *key; snprintf(filename, sizeof filename, "%s" SLASH "invitations" SLASH "ed25519_key.priv", confbase); // Remove the key if there are no outstanding invitations. if(!count) unlink(filename); // Create a new key if necessary. FILE *f = fopen(filename, "r"); if(!f) { if(errno != ENOENT) { fprintf(stderr, "Could not read %s: %s\n", filename, strerror(errno)); return 1; } key = ecdsa_generate(); if(!key) return 1; f = fopen(filename, "w"); if(!f) { fprintf(stderr, "Could not write %s: %s\n", filename, strerror(errno)); return 1; } chmod(filename, 0600); if(!ecdsa_write_pem_private_key(key, f)) { fprintf(stderr, "Could not write ECDSA private key\n"); fclose(f); return 1; } fclose(f); if(connect_tincd(false)) sendline(fd, "%d %d", CONTROL, REQ_RELOAD); } else { key = ecdsa_read_pem_private_key(f); fclose(f); if(!key) fprintf(stderr, "Could not read private key from %s\n", filename); } if(!key) return 1; // Create a hash of the key. char hash[64]; char *fingerprint = ecdsa_get_base64_public_key(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); // Create a file containing the details of the invitation. snprintf(filename, sizeof filename, "%s" SLASH "invitations" SLASH "%s", confbase, cookiehash); int ifd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600); if(!ifd) { fprintf(stderr, "Could not create invitation file %s: %s\n", filename, strerror(errno)); return 1; } f = fdopen(ifd, "w"); if(!f) abort(); // Get the local address char *address = get_my_hostname(); // Fill in the details. fprintf(f, "Name = %s\n", argv[1]); if(netname) fprintf(f, "NetName = %s\n", netname); fprintf(f, "ConnectTo = %s\n", myname); // Copy Broadcast and Mode FILE *tc = fopen(tinc_conf, "r"); if(tc) { char buf[1024]; while(fgets(buf, sizeof buf, tc)) { if((!strncasecmp(buf, "Mode", 4) && strchr(" \t=", buf[4])) || (!strncasecmp(buf, "Broadcast", 9) && strchr(" \t=", buf[9]))) { fputs(buf, f); // Make sure there is a newline character. if(!strchr(buf, '\n')) fputc('\n', f); } } fclose(tc); } fprintf(f, "#---------------------------------------------------------------#\n"); fprintf(f, "Name = %s\n", myname); char filename2[PATH_MAX]; snprintf(filename2, sizeof filename2, "%s" SLASH "hosts" SLASH "%s", confbase, myname); fcopy(f, filename2); fclose(f); // Create an URL from the local address, key hash and cookie char *url; xasprintf(&url, "%s/%s%s", address, hash, cookie); // Call the inviation-created script char *envp[6] = {}; xasprintf(&envp[0], "NAME=%s", myname); xasprintf(&envp[1], "NETNAME=%s", netname); xasprintf(&envp[2], "NODE=%s", argv[1]); xasprintf(&envp[3], "INVITATION_FILE=%s", filename); xasprintf(&envp[4], "INVITATION_URL=%s", url); execute_script("invitation-created", envp); for(int i = 0; i < 6 && envp[i]; i++) free(envp[i]); puts(url); free(url); free(address); return 0; }