bool read_ecdsa_private_key(meshlink_handle_t *mesh) { FILE *fp; char filename[PATH_MAX]; snprintf(filename,PATH_MAX, "%s" SLASH "ecdsa_key.priv", mesh->confbase); fp = fopen(filename, "rb"); if(!fp) { logger(mesh, MESHLINK_ERROR, "Error reading ECDSA private key file: %s", strerror(errno)); return false; } mesh->self->connection->ecdsa = ecdsa_read_pem_private_key(fp); fclose(fp); if(!mesh->self->connection->ecdsa) logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file failed: %s", strerror(errno)); return mesh->self->connection->ecdsa; }
static bool read_invitation_key(meshlink_handle_t *mesh) { FILE *fp; char filename[PATH_MAX]; if(mesh->invitation_key) { ecdsa_free(mesh->invitation_key); mesh->invitation_key = NULL; } snprintf(filename,PATH_MAX, "%s" SLASH "invitations" SLASH "ecdsa_key.priv", mesh->confbase); fp = fopen(filename, "rb"); if(fp) { mesh->invitation_key = ecdsa_read_pem_private_key(fp); fclose(fp); if(!mesh->invitation_key) logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file `%s' failed: %s", filename, strerror(errno)); } return mesh->invitation_key; }
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; }
int fsck(const char *argv0) { #ifdef HAVE_MINGW int uid = 0; #else uid_t uid = getuid(); #endif // Check that tinc.conf is readable. if(access(tinc_conf, R_OK)) { fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno)); if(errno == ENOENT) { fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n"); print_tinc_cmd(argv0, "init"); } else if(errno == EACCES) { if(uid != 0) fprintf(stderr, "You are currently not running tinc as root. Use sudo?\n"); else fprintf(stderr, "Check the permissions of each component of the path %s.\n", tinc_conf); } return 1; } char *name = get_my_name(true); if(!name) { fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n"); return 1; } // Check for private keys. // TODO: use RSAPrivateKeyFile and Ed25519PrivateKeyFile variables if present. struct stat st; char fname[PATH_MAX]; char dname[PATH_MAX]; ecdsa_t *ecdsa_priv = NULL; snprintf(fname, sizeof fname, "%s/ed25519_key.priv", confbase); if(stat(fname, &st)) { if(errno != ENOENT) { // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file. fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno)); fprintf(stderr, "Please correct this error.\n"); return 1; } } else { FILE *f = fopen(fname, "r"); if(!f) { fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno)); return 1; } ecdsa_priv = ecdsa_read_pem_private_key(f); fclose(f); if(!ecdsa_priv) { fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname); fprintf(stderr, "You can generate a new Ed25519 key with:\n\n"); print_tinc_cmd(argv0, "generate-ed25519-keys"); return 1; } if(st.st_mode & 077) { fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname); if(st.st_uid != uid) { fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname); } else if(ask_fix()) { if(chmod(fname, st.st_mode & ~077)) fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno)); else fprintf(stderr, "Fixed permissions of %s.\n", fname); } } } if(!ecdsa_priv) { fprintf(stderr, "ERROR: No Ed25519 private key found.\n"); fprintf(stderr, "You can generate new keys with:\n\n"); print_tinc_cmd(argv0, "generate-keys"); return 1; } // Check for public keys. // TODO: use RSAPublicKeyFile and Ed25519PublicKeyFile variables if present. snprintf(fname, sizeof fname, "%s/hosts/%s", confbase, name); if(access(fname, R_OK)) fprintf(stderr, "WARNING: cannot read %s\n", fname); FILE *f; // TODO: this should read the Ed25519PublicKey config variable instead. ecdsa_t *ecdsa_pub = NULL; f = fopen(fname, "r"); if(f) ecdsa_pub = ecdsa_read_pem_public_key(f); fclose(f); if(ecdsa_priv) { if(!ecdsa_pub) { fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n"); if(ask_fix()) { FILE *f = fopen(fname, "a"); if(f) { if(ecdsa_write_pem_public_key(ecdsa_priv, f)) fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname); else fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname); fclose(f); } else { fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno)); } } } else { // TODO: suggest remedies char *key1 = ecdsa_get_base64_public_key(ecdsa_pub); if(!key1) { fprintf(stderr, "ERROR: public Ed25519 key does not work.\n"); return 1; } char *key2 = ecdsa_get_base64_public_key(ecdsa_priv); if(!key2) { free(key1); fprintf(stderr, "ERROR: private Ed25519 key does not work.\n"); return 1; } int result = strcmp(key1, key2); free(key1); free(key2); if(result) { fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n"); return 1; } } } else { if(ecdsa_pub) fprintf(stderr, "WARNING: A public Ed25519 key was found but no private key is known.\n"); } // Check whether scripts are executable struct dirent *ent; DIR *dir = opendir(confbase); if(!dir) { fprintf(stderr, "ERROR: cannot read directory %s: %s\n", confbase, strerror(errno)); return 1; } while((ent = readdir(dir))) { if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) continue; strncpy(fname, ent->d_name, sizeof fname); char *dash = strrchr(fname, '-'); if(!dash) continue; *dash = 0; if(strcmp(fname, "tinc") && strcmp(fname, "host") && strcmp(fname, "subnet")) { static bool explained = false; fprintf(stderr, "WARNING: Unknown script %s" SLASH "%s found.\n", confbase, ent->d_name); if(!explained) { fprintf(stderr, "The only scripts in %s executed by tinc are:\n", confbase); fprintf(stderr, "tinc-up, tinc-down, host-up, host-down, subnet-up and subnet-down.\n"); explained = true; } continue; } snprintf(fname, sizeof fname, "%s" SLASH "%s", confbase, ent->d_name); if(access(fname, R_OK | X_OK)) { if(errno != EACCES) { fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno)); continue; } fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno)); if(ask_fix()) { if(chmod(fname, 0755)) fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno)); } } } closedir(dir); snprintf(dname, sizeof dname, "%s" SLASH "hosts", confbase); dir = opendir(dname); if(!dir) { fprintf(stderr, "ERROR: cannot read directory %s: %s\n", dname, strerror(errno)); return 1; } while((ent = readdir(dir))) { if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) continue; strncpy(fname, ent->d_name, sizeof fname); char *dash = strrchr(fname, '-'); if(!dash) continue; *dash = 0; snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name); if(access(fname, R_OK | X_OK)) { if(errno != EACCES) { fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno)); continue; } fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno)); if(ask_fix()) { if(chmod(fname, 0755)) fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno)); } } } closedir(dir); // Check for obsolete / unsafe / unknown configuration variables. check_conffile(tinc_conf, true); dir = opendir(dname); if(dir) { while((ent = readdir(dir))) { if(!check_id(ent->d_name)) continue; snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name); check_conffile(fname, false); } closedir(dir); } return 0; }
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; }