static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) { switch(type) { case SPTPS_HANDSHAKE: return sptps_send_record(&sptps, 0, cookie, sizeof cookie); case 0: data = xrealloc(data, datalen + len + 1); memcpy(data + datalen, msg, len); datalen += len; data[datalen] = 0; break; case 1: return finalize_join(); case 2: fprintf(stderr, "Invitation succesfully accepted.\n"); shutdown(sock, SHUT_RDWR); success = true; break; default: return false; } return true; }
static bool finalize_invitation(connection_t *c, const char *data, uint16_t len) { if(strchr(data, '\n')) { logger(DEBUG_ALWAYS, LOG_ERR, "Received invalid key from invited node %s (%s)!\n", c->name, c->hostname); return false; } // Create a new host config file char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); if(!access(filename, F_OK)) { logger(DEBUG_ALWAYS, LOG_ERR, "Host config file for %s (%s) already exists!\n", c->name, c->hostname); return false; } FILE *f = fopen(filename, "w"); if(!f) { logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to create %s: %s\n", filename, strerror(errno)); return false; } fprintf(f, "Ed25519PublicKey = %s\n", data); fclose(f); logger(DEBUG_CONNECTIONS, LOG_INFO, "Key succesfully received from %s (%s)", c->name, c->hostname); // Call invitation-accepted script char *envp[7] = {NULL}; char *address, *port; xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); xasprintf(&envp[1], "DEVICE=%s", device ? : ""); xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); xasprintf(&envp[3], "NODE=%s", c->name); sockaddr2str(&c->address, &address, &port); xasprintf(&envp[4], "REMOTEADDRESS=%s", address); xasprintf(&envp[5], "NAME=%s", myself->name); execute_script("invitation-accepted", envp); for(int i = 0; envp[i] && i < 7; i++) free(envp[i]); sptps_send_record(&c->sptps, 2, data, 0); return true; }
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; }
static bool finalize_join(void) { char *name = xstrdup(get_value(data, "Name")); if(!name) { fprintf(stderr, "No Name found in invitation!\n"); return false; } if(!check_id(name)) { fprintf(stderr, "Invalid Name found in invitation: %s!\n", name); return false; } if(!netname) netname = grep(data, "NetName"); bool ask_netname = false; char temp_netname[32]; make_names: if(!confbasegiven) { free(confbase); confbase = NULL; } make_names(false); free(tinc_conf); free(hosts_dir); xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); if(!access(tinc_conf, F_OK)) { fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); if(confbasegiven) return false; // Generate a random netname, ask for a better one later. ask_netname = true; snprintf(temp_netname, sizeof temp_netname, "join_%x", rand()); netname = temp_netname; goto make_names; } if(mkdir(confbase, 0777) && errno != EEXIST) { fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno)); return false; } if(mkdir(hosts_dir, 0777) && errno != EEXIST) { fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); return false; } FILE *f = fopen(tinc_conf, "w"); if(!f) { fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno)); return false; } fprintf(f, "Name = %s\n", name); char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, name); FILE *fh = fopen(filename, "w"); if(!fh) { fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); fclose(f); return false; } // Filter first chunk on approved keywords, split between tinc.conf and hosts/Name // Other chunks go unfiltered to their respective host config files const char *p = data; char *l, *value; while((l = get_line(&p))) { // Ignore comments if(*l == '#') continue; // Split line into variable and value int len = strcspn(l, "\t ="); value = l + len; value += strspn(value, "\t "); if(*value == '=') { value++; value += strspn(value, "\t "); } l[len] = 0; // Is it a Name? if(!strcasecmp(l, "Name")) if(strcmp(value, name)) break; else continue; else if(!strcasecmp(l, "NetName")) continue; // Check the list of known variables bool found = false; int i; for(i = 0; variables[i].name; i++) { if(strcasecmp(l, variables[i].name)) continue; found = true; break; } // Ignore unknown and unsafe variables if(!found) { fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l); continue; } else if(!(variables[i].type & VAR_SAFE)) { fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l); continue; } // Copy the safe variable to the right config file fprintf(variables[i].type & VAR_HOST ? fh : f, "%s = %s\n", l, value); } fclose(f); while(l && !strcasecmp(l, "Name")) { if(!check_id(value)) { fprintf(stderr, "Invalid Name found in invitation.\n"); return false; } if(!strcmp(value, name)) { fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n"); return false; } snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, value); f = fopen(filename, "w"); if(!f) { fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); return false; } while((l = get_line(&p))) { if(!strcmp(l, "#---------------------------------------------------------------#")) continue; int len = strcspn(l, "\t ="); if(len == 4 && !strncasecmp(l, "Name", 4)) { value = l + len; value += strspn(value, "\t "); if(*value == '=') { value++; value += strspn(value, "\t "); } l[len] = 0; break; } fputs(l, f); fputc('\n', f); } fclose(f); } // Generate our key and send a copy to the server ecdsa_t *key = ecdsa_generate(); if(!key) return false; char *b64key = ecdsa_get_base64_public_key(key); if(!b64key) return false; snprintf(filename, sizeof filename, "%s" SLASH "ed25519_key.priv", confbase); f = fopenmask(filename, "w", 0600); if(!f) return false; if(!ecdsa_write_pem_private_key(key, f)) { fprintf(stderr, "Error writing private key!\n"); ecdsa_free(key); fclose(f); return false; } fclose(f); fprintf(fh, "Ed25519PublicKey = %s\n", b64key); sptps_send_record(&sptps, 1, b64key, strlen(b64key)); free(b64key); ecdsa_free(key); check_port(name); ask_netname: if(ask_netname && tty) { fprintf(stderr, "Enter a new netname: "); if(!fgets(line, sizeof line, stdin)) { fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); return false; } if(!*line || *line == '\n') goto ask_netname; line[strlen(line) - 1] = 0; char newbase[PATH_MAX]; snprintf(newbase, sizeof newbase, CONFDIR SLASH "tinc" SLASH "%s", line); if(rename(confbase, newbase)) { fprintf(stderr, "Error trying to rename %s to %s: %s\n", confbase, newbase, strerror(errno)); goto ask_netname; } netname = line; make_names(false); } fprintf(stderr, "Configuration stored in: %s\n", confbase); return true; }
int main(int argc, char *argv[]) { program_name = argv[0]; bool initiator = false; bool datagram = false; #ifdef HAVE_LINUX bool tun = false; #endif int packetloss = 0; int r; int option_index = 0; ecdsa_t *mykey = NULL, *hiskey = NULL; bool quit = false; while((r = getopt_long(argc, argv, "dqrtwL:W:v", long_options, &option_index)) != EOF) { switch (r) { case 0: /* long option */ break; case 'd': /* datagram mode */ datagram = true; break; case 'q': /* close connection on EOF from stdin */ quit = true; break; case 'r': /* read only */ readonly = true; break; case 't': /* read only */ #ifdef HAVE_LINUX tun = true; #else fprintf(stderr, "--tun is only supported on Linux.\n"); usage(); return 1; #endif break; case 'w': /* write only */ writeonly = true; break; case 'L': /* packet loss rate */ packetloss = atoi(optarg); break; case 'W': /* replay window size */ sptps_replaywin = atoi(optarg); break; case 'v': /* be verbose */ verbose = true; break; case '?': /* wrong options */ usage(); return 1; case 1: /* help */ usage(); return 0; default: break; } } argc -= optind - 1; argv += optind - 1; if(argc < 4 || argc > 5) { fprintf(stderr, "Wrong number of arguments.\n"); usage(); return 1; } if(argc > 4) initiator = true; srand(time(NULL)); #ifdef HAVE_LINUX if(tun) { in = out = open("/dev/net/tun", O_RDWR | O_NONBLOCK); if(in < 0) { fprintf(stderr, "Could not open tun device: %s\n", strerror(errno)); return 1; } struct ifreq ifr = { .ifr_flags = IFF_TUN }; if(ioctl(in, TUNSETIFF, &ifr)) { fprintf(stderr, "Could not configure tun interface: %s\n", strerror(errno)); return 1; } ifr.ifr_name[IFNAMSIZ - 1] = 0; fprintf(stderr, "Using tun interface %s\n", ifr.ifr_name); } #endif #ifdef HAVE_MINGW static struct WSAData wsa_state; if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) return 1; #endif struct addrinfo *ai, hint; memset(&hint, 0, sizeof hint); hint.ai_family = AF_UNSPEC; hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM; hint.ai_protocol = datagram ? IPPROTO_UDP : IPPROTO_TCP; hint.ai_flags = initiator ? 0 : AI_PASSIVE; if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) { fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno)); return 1; } int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if(sock < 0) { fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno)); return 1; } int one = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one); if(initiator) { if(connect(sock, ai->ai_addr, ai->ai_addrlen)) { fprintf(stderr, "Could not connect to peer: %s\n", sockstrerror(sockerrno)); return 1; } fprintf(stderr, "Connected\n"); } else { if(bind(sock, ai->ai_addr, ai->ai_addrlen)) { fprintf(stderr, "Could not bind socket: %s\n", sockstrerror(sockerrno)); return 1; } if(!datagram) { if(listen(sock, 1)) { fprintf(stderr, "Could not listen on socket: %s\n", sockstrerror(sockerrno)); return 1; } fprintf(stderr, "Listening...\n"); sock = accept(sock, NULL, NULL); if(sock < 0) { fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno)); return 1; } } else { fprintf(stderr, "Listening...\n"); char buf[65536]; struct sockaddr addr; socklen_t addrlen = sizeof addr; if(recvfrom(sock, buf, sizeof buf, MSG_PEEK, &addr, &addrlen) <= 0) { fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno)); return 1; } if(connect(sock, &addr, addrlen)) { fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno)); return 1; } } fprintf(stderr, "Connected\n"); } crypto_init(); FILE *fp = fopen(argv[1], "r"); if(!fp) { fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno)); return 1; } if(!(mykey = ecdsa_read_pem_private_key(fp))) return 1; fclose(fp); fp = fopen(argv[2], "r"); if(!fp) { fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno)); return 1; } if(!(hiskey = ecdsa_read_pem_public_key(fp))) return 1; fclose(fp); if(verbose) fprintf(stderr, "Keys loaded\n"); sptps_t s; if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record)) return 1; while(true) { if(writeonly && readonly) break; char buf[65535] = ""; fd_set fds; FD_ZERO(&fds); #ifndef HAVE_MINGW if(!readonly && s.instate) FD_SET(in, &fds); #endif FD_SET(sock, &fds); if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0) return 1; if(FD_ISSET(in, &fds)) { ssize_t len = read(in, buf, sizeof buf); if(len < 0) { fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno)); return 1; } if(len == 0) { if(quit) break; readonly = true; continue; } if(buf[0] == '#') s.outseqno = atoi(buf + 1); if(buf[0] == '^') sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0); else if(buf[0] == '$') { sptps_force_kex(&s); if(len > 1) sptps_send_record(&s, 0, buf, len); } else if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, (len == 1 && buf[0] == '\n') ? 0 : buf[0] == '*' ? sizeof buf : len)) return 1; } if(FD_ISSET(sock, &fds)) { ssize_t len = recv(sock, buf, sizeof buf, 0); if(len < 0) { fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno)); return 1; } if(len == 0) { fprintf(stderr, "Connection terminated by peer.\n"); break; } if(verbose) { char hex[len * 2 + 1]; bin2hex(buf, hex, len); fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex); } if(packetloss && (rand() % 100) < packetloss) { if(verbose) fprintf(stderr, "Dropped.\n"); continue; } char *bufp = buf; while(len) { size_t done = sptps_receive_data(&s, bufp, len); if(!done) { if(!datagram) return 1; } else { break; } bufp += done; len -= done; } } } if(!sptps_stop(&s)) return 1; return 0; }
int main(int argc, char *argv[]) { ecdsa_t *key1, *key2; ecdh_t *ecdh1, *ecdh2; sptps_t sptps1, sptps2; char buf1[4096], buf2[4096], buf3[4096]; double duration = argc > 1 ? atof(argv[1]) : 10; crypto_init(); randomize(buf1, sizeof buf1); randomize(buf2, sizeof buf2); randomize(buf3, sizeof buf3); // Key generation fprintf(stderr, "Generating keys for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) ecdsa_free(ecdsa_generate()); fprintf(stderr, "%17.2lf op/s\n", rate); key1 = ecdsa_generate(); key2 = ecdsa_generate(); // Ed25519 signatures fprintf(stderr, "Ed25519 sign for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) if(!ecdsa_sign(key1, buf1, 256, buf2)) return 1; fprintf(stderr, "%20.2lf op/s\n", rate); fprintf(stderr, "Ed25519 verify for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) if(!ecdsa_verify(key1, buf1, 256, buf2)) { fprintf(stderr, "Signature verification failed\n"); return 1; } fprintf(stderr, "%18.2lf op/s\n", rate); ecdh1 = ecdh_generate_public(buf1); fprintf(stderr, "ECDH for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) { ecdh2 = ecdh_generate_public(buf2); if(!ecdh2) return 1; if(!ecdh_compute_shared(ecdh2, buf1, buf3)) return 1; } fprintf(stderr, "%28.2lf op/s\n", rate); ecdh_free(ecdh1); // SPTPS authentication phase int fd[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno)); return 1; } struct pollfd pfd[2] = {{fd[0], POLLIN}, {fd[1], POLLIN}}; fprintf(stderr, "SPTPS/TCP authenticate for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) { sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record); sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record); while(poll(pfd, 2, 0)) { if(pfd[0].revents) receive_data(&sptps1); if(pfd[1].revents) receive_data(&sptps2); } sptps_stop(&sptps1); sptps_stop(&sptps2); } fprintf(stderr, "%10.2lf op/s\n", rate * 2); // SPTPS data sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record); sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record); while(poll(pfd, 2, 0)) { if(pfd[0].revents) receive_data(&sptps1); if(pfd[1].revents) receive_data(&sptps2); } fprintf(stderr, "SPTPS/TCP transmit for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) { if(!sptps_send_record(&sptps1, 0, buf1, 1451)) abort(); receive_data(&sptps2); } rate *= 2 * 1451 * 8; if(rate > 1e9) fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9); else if(rate > 1e6) fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6); else if(rate > 1e3) fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3); sptps_stop(&sptps1); sptps_stop(&sptps2); // SPTPS datagram authentication phase close(fd[0]); close(fd[1]); if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)) { fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno)); return 1; } fprintf(stderr, "SPTPS/UDP authenticate for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) { sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record); sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record); while(poll(pfd, 2, 0)) { if(pfd[0].revents) receive_data(&sptps1); if(pfd[1].revents) receive_data(&sptps2); } sptps_stop(&sptps1); sptps_stop(&sptps2); } fprintf(stderr, "%10.2lf op/s\n", rate * 2); // SPTPS datagram data sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record); sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record); while(poll(pfd, 2, 0)) { if(pfd[0].revents) receive_data(&sptps1); if(pfd[1].revents) receive_data(&sptps2); } fprintf(stderr, "SPTPS/UDP transmit for %lg seconds: ", duration); for(clock_start(); clock_countto(duration);) { if(!sptps_send_record(&sptps1, 0, buf1, 1451)) abort(); receive_data(&sptps2); } rate *= 2 * 1451 * 8; if(rate > 1e9) fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9); else if(rate > 1e6) fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6); else if(rate > 1e3) fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3); sptps_stop(&sptps1); sptps_stop(&sptps2); // Clean up close(fd[0]); close(fd[1]); ecdsa_free(key1); ecdsa_free(key2); crypto_exit(); return 0; }