/*
invitation.c -- Create and accept invitations
- Copyright (C) 2013 Guus Sliepen <guus@tinc-vpn.org>
+ Copyright (C) 2013-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "names.h"
#include "netutl.h"
#include "rsagen.h"
+#include "script.h"
#include "sptps.h"
#include "tincctl.h"
#include "utils.h"
#include "xalloc.h"
-#ifdef HAVE_MINGW
-#define SCRIPTEXTENSION ".bat"
-#else
-#define SCRIPTEXTENSION ""
-#endif
+#include "ed25519/sha512.h"
int addressfamily = AF_UNSPEC;
+static void scan_for_hostname(const char *filename, char **hostname, char **port) {
+ if(!filename || (*hostname && *port))
+ return;
+
+ FILE *f = fopen(filename, "r");
+ if(!f)
+ return;
+
+ while(fgets(line, sizeof line, f)) {
+ if(!rstrip(line))
+ continue;
+ char *p = line, *q;
+ p += strcspn(p, "\t =");
+ if(!*p)
+ continue;
+ q = p + strspn(p, "\t ");
+ if(*q == '=')
+ q += 1 + strspn(q + 1, "\t ");
+ *p = 0;
+ p = q + strcspn(q, "\t ");
+ if(*p)
+ *p++ = 0;
+ p += strspn(p, "\t ");
+ p[strcspn(p, "\t ")] = 0;
+
+ if(!*port && !strcasecmp(line, "Port")) {
+ *port = xstrdup(q);
+ } else if(!*hostname && !strcasecmp(line, "Address")) {
+ *hostname = xstrdup(q);
+ if(*p) {
+ free(*port);
+ *port = xstrdup(p);
+ }
+ }
+
+ if(*hostname && *port)
+ break;
+ }
+
+ fclose(f);
+}
+
char *get_my_hostname() {
char *hostname = NULL;
char *port = NULL;
// Use first Address statement in own host config file
if(check_id(name)) {
xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name);
- FILE *f = fopen(filename, "r");
- if(f) {
- while(fgets(line, sizeof line, f)) {
- if(!rstrip(line))
- continue;
- char *p = line, *q;
- p += strcspn(p, "\t =");
- if(!*p)
- continue;
- q = p + strspn(p, "\t ");
- if(*q == '=')
- q += 1 + strspn(q + 1, "\t ");
- *p = 0;
- p = q + strcspn(q, "\t ");
- if(*p)
- *p++ = 0;
- p += strspn(p, "\t ");
- p[strcspn(p, "\t ")] = 0;
- if(!port && !strcasecmp(line, "Port")) {
- port = xstrdup(q);
- continue;
- }
- if(strcasecmp(line, "Address"))
- continue;
- hostname = xstrdup(q);
- if(*p) {
- free(port);
- port = xstrdup(p);
- }
- break;
- }
- fclose(f);
- }
+ scan_for_hostname(filename, &hostname, &port);
+ scan_for_hostname(tinc_conf, &hostname, &port);
}
if(hostname)
// If that doesn't work, guess externally visible hostname
fprintf(stderr, "Trying to discover externally visible hostname...\n");
- struct addrinfo *ai = str2addrinfo("ifconfig.me", "80", SOCK_STREAM);
- static const char request[] = "GET /host HTTP/1.0\r\n\r\n";
- if(ai) {
- int s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ struct addrinfo *ai = str2addrinfo("tinc-vpn.org", "80", SOCK_STREAM);
+ struct addrinfo *aip = ai;
+ static const char request[] = "GET http://tinc-vpn.org/host.cgi HTTP/1.0\r\n\r\n";
+
+ while(aip) {
+ int s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
if(s >= 0) {
- if(connect(s, ai->ai_addr, ai->ai_addrlen)) {
+ if(connect(s, aip->ai_addr, aip->ai_addrlen)) {
closesocket(s);
s = -1;
}
hostname = xstrdup(p + 1);
}
closesocket(s);
+ if(hostname)
+ break;
}
- freeaddrinfo(ai);
+ aip = aip->ai_next;
+ continue;
}
+ if(ai)
+ freeaddrinfo(ai);
+
// Check that the hostname is reasonable
if(hostname) {
for(char *p = hostname; *p; p++) {
- if(isalnum(*p) || *p == '-' || *p == '.')
+ if(isalnum(*p) || *p == '-' || *p == '.' || *p == ':')
continue;
// If not, forget it.
free(hostname);
}
}
+ if(!tty) {
+ if(!hostname) {
+ fprintf(stderr, "Could not determine the external address or hostname. Please set Address manually.\n");
+ return NULL;
+ }
+ goto save;
+ }
+
again:
- printf("Please enter your host's external address or hostname");
+ fprintf(stderr, "Please enter your host's external address or hostname");
if(hostname)
- printf(" [%s]", hostname);
- printf(": ");
- fflush(stdout);
+ fprintf(stderr, " [%s]", hostname);
+ fprintf(stderr, ": ");
if(!fgets(line, sizeof line, stdin)) {
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
else
xasprintf(&hostport, "%s:%s", hostname, port);
} else {
- hostport = hostname;
- hostname = NULL;
+ if(strchr(hostname, ':'))
+ xasprintf(&hostport, "[%s]", hostname);
+ else
+ hostport = xstrdup(hostname);
}
free(hostname);
}
free(filename);
- // If a daemon is running, ensure no other nodes now about this name
+ // 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);
}
}
- char hash[25];
-
xasprintf(&filename, "%s" SLASH "invitations", confbase);
if(mkdir(filename, 0700) && errno != EEXIST) {
fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno));
free(filename);
ecdsa_t *key;
- xasprintf(&filename, "%s" SLASH "invitations" SLASH "ecdsa_key.priv", confbase);
+ xasprintf(&filename, "%s" SLASH "invitations" SLASH "ed25519_key.priv", confbase);
// Remove the key if there are no outstanding invitations.
if(!count)
}
chmod(filename, 0600);
ecdsa_write_pem_private_key(key, f);
+ 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);
}
- fclose(f);
+
free(filename);
if(!key)
return 1;
// Create a hash of the key.
+ char hash[64];
char *fingerprint = ecdsa_get_base64_public_key(key);
- digest_t *digest = digest_open_by_name("sha256", 18);
- if(!digest)
- abort();
- digest_create(digest, fingerprint, strlen(fingerprint), hash);
+ 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.
- xasprintf(&filename, "%s" SLASH "invitations" SLASH "%s", confbase, cookie);
+ xasprintf(&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));
free(filename);
return 1;
}
- free(filename);
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);
- // TODO: copy Broadcast and Mode
+
+ // 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);
- xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, myname);
- fcopy(f, filename);
+ char *filename2;
+ xasprintf(&filename2, "%s" SLASH "hosts" SLASH "%s", confbase, myname);
+ fcopy(f, filename2);
fclose(f);
+ free(filename2);
// Create an URL from the local address, key hash and cookie
- char *address = get_my_hostname();
- printf("%s/%s%s\n", address, hash, 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(filename);
free(address);
if(!access(tinc_conf, F_OK)) {
fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
- if(!tty || confbasegiven)
+ if(confbasegiven)
return false;
// Generate a random netname, ask for a better one later.
FILE *fh = fopen(filename, "w");
if(!fh) {
fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
+ fclose(f);
return false;
}
if(!b64key)
return false;
- xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase);
+ xasprintf(&filename, "%s" SLASH "ed25519_key.priv", confbase);
f = fopenmask(filename, "w", 0600);
if(!ecdsa_write_pem_private_key(key, f)) {
fclose(f);
- fprintf(fh, "ECDSAPublicKey = %s\n", b64key);
+ fprintf(fh, "Ed25519PublicKey = %s\n", b64key);
sptps_send_record(&sptps, 1, b64key, strlen(b64key));
free(b64key);
+ ecdsa_free(key);
-
+#ifndef DISABLE_LEGACY
rsa_t *rsa = rsa_generate(2048, 0x1001);
xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase);
f = fopenmask(filename, "w", 0600);
rsa_write_pem_public_key(rsa, fh);
fclose(fh);
- ecdsa_free(key);
rsa_free(rsa);
+#endif
check_port(name);
- fprintf(stderr, "Invitation succesfully accepted.\n");
- shutdown(sock, SHUT_RDWR);
- success = true;
-
ask_netname:
- if(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));
make_names();
}
+ fprintf(stderr, "Configuration stored in: %s\n", confbase);
+
return true;
}
-static bool invitation_send(void *handle, uint8_t type, const char *data, size_t len) {
+
+static bool invitation_send(void *handle, uint8_t type, const void *data, size_t len) {
while(len) {
int result = send(sock, data, len, 0);
if(result == -1 && errno == EINTR)
return true;
}
-static bool invitation_receive(void *handle, uint8_t type, const char *msg, uint16_t len) {
+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 1:
return finalize_join();
+ case 2:
+ fprintf(stderr, "Invitation succesfully accepted.\n");
+ shutdown(sock, SHUT_RDWR);
+ success = true;
+ break;
+
default:
return false;
}
}
// Make sure confbase exists and is accessible.
- if(strcmp(confdir, confbase) && mkdir(confdir, 0755) && errno != EEXIST) {
+ if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
return 1;
}
if(argc > 1) {
invitation = argv[1];
} else {
- if(tty) {
- printf("Enter invitation URL: ");
- fflush(stdout);
- }
+ if(tty)
+ fprintf(stderr, "Enter invitation URL: ");
errno = EPIPE;
if(!fgets(line, sizeof line, stdin)) {
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
if(!port || !*port)
port = "655";
- if(!b64decode(slash, hash, 18) || !b64decode(slash + 24, cookie, 18))
+ if(!b64decode(slash, hash, 24) || !b64decode(slash + 24, cookie, 24))
goto invalid;
// Generate a throw-away key for the invitation.
// Check if the hash of the key he gave us matches the hash in the URL.
char *fingerprint = line + 2;
- digest_t *digest = digest_open_by_name("sha256", 18);
- if(!digest)
- abort();
- char hishash[18];
- if(!digest_create(digest, fingerprint, strlen(fingerprint), hishash)) {
+ char hishash[64];
+ if(sha512(fingerprint, strlen(fingerprint), hishash)) {
fprintf(stderr, "Could not create digest\n%s\n", line + 2);
return 1;
}