Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
/*
|
|
|
|
invitation.c -- Create and accept invitations
|
|
|
|
Copyright (C) 2013 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
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "system.h"
|
|
|
|
|
|
|
|
#include "control_common.h"
|
|
|
|
#include "crypto.h"
|
|
|
|
#include "ecdsa.h"
|
|
|
|
#include "ecdsagen.h"
|
|
|
|
#include "invitation.h"
|
|
|
|
#include "names.h"
|
|
|
|
#include "netutl.h"
|
|
|
|
#include "rsagen.h"
|
|
|
|
#include "sptps.h"
|
|
|
|
#include "tincctl.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "xalloc.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
#define SCRIPTEXTENSION ".bat"
|
|
|
|
#else
|
|
|
|
#define SCRIPTEXTENSION ""
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int addressfamily = AF_UNSPEC;
|
|
|
|
|
|
|
|
char *get_my_hostname() {
|
|
|
|
char *hostname = NULL;
|
2013-07-25 15:17:33 +00:00
|
|
|
char *port = NULL;
|
|
|
|
char *hostport = NULL;
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
char *name = get_my_name(false);
|
|
|
|
char *filename = 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;
|
2013-07-25 15:17:33 +00:00
|
|
|
if(!port && !strcasecmp(line, "Port")) {
|
|
|
|
port = xstrdup(q);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(strcasecmp(line, "Address"))
|
|
|
|
continue;
|
|
|
|
hostname = xstrdup(q);
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
if(*p) {
|
2013-07-25 15:17:33 +00:00
|
|
|
free(port);
|
|
|
|
port = xstrdup(p);
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-25 15:17:33 +00:00
|
|
|
if(hostname)
|
|
|
|
goto done;
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
if(s >= 0) {
|
|
|
|
if(connect(s, ai->ai_addr, ai->ai_addrlen)) {
|
|
|
|
closesocket(s);
|
|
|
|
s = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(s >= 0) {
|
|
|
|
send(s, request, sizeof request - 1, 0);
|
|
|
|
int len = recv(s, line, sizeof line - 1, MSG_WAITALL);
|
|
|
|
if(len > 0) {
|
|
|
|
line[len] = 0;
|
|
|
|
if(line[len - 1] == '\n')
|
|
|
|
line[--len] = 0;
|
|
|
|
char *p = strrchr(line, '\n');
|
|
|
|
if(p && p[1])
|
|
|
|
hostname = xstrdup(p + 1);
|
|
|
|
}
|
|
|
|
closesocket(s);
|
|
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the hostname is reasonable
|
|
|
|
if(hostname) {
|
|
|
|
for(char *p = hostname; *p; p++) {
|
|
|
|
if(isalnum(*p) || *p == '-' || *p == '.')
|
|
|
|
continue;
|
|
|
|
// If not, forget it.
|
|
|
|
free(hostname);
|
|
|
|
hostname = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
again:
|
|
|
|
printf("Please enter your host's external address or hostname");
|
|
|
|
if(hostname)
|
|
|
|
printf(" [%s]", hostname);
|
|
|
|
printf(": ");
|
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
if(!fgets(line, sizeof line, stdin)) {
|
|
|
|
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
|
|
|
|
free(hostname);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!rstrip(line)) {
|
|
|
|
if(hostname)
|
2013-07-25 15:17:33 +00:00
|
|
|
goto save;
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
else
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(char *p = line; *p; p++) {
|
|
|
|
if(isalnum(*p) || *p == '-' || *p == '.')
|
|
|
|
continue;
|
|
|
|
fprintf(stderr, "Invalid address or hostname.\n");
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(hostname);
|
|
|
|
hostname = xstrdup(line);
|
|
|
|
|
2013-07-25 15:17:33 +00:00
|
|
|
save:
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
if(filename) {
|
|
|
|
FILE *f = fopen(filename, "a");
|
|
|
|
if(f) {
|
|
|
|
fprintf(f, "\nAddress = %s\n", hostname);
|
|
|
|
fclose(f);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Could not append Address to %s: %s\n", filename, strerror(errno));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-25 15:17:33 +00:00
|
|
|
done:
|
|
|
|
if(port) {
|
|
|
|
if(strchr(hostname, ':'))
|
|
|
|
xasprintf(&hostport, "[%s]:%s", hostname, port);
|
|
|
|
else
|
|
|
|
xasprintf(&hostport, "%s:%s", hostname, port);
|
|
|
|
} else {
|
|
|
|
hostport = hostname;
|
|
|
|
hostname = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(hostname);
|
|
|
|
free(port);
|
|
|
|
free(filename);
|
|
|
|
return hostport;
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool fcopy(FILE *out, const char *filename) {
|
|
|
|
FILE *in = fopen(filename, "r");
|
|
|
|
if(!in) {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buf[1024];
|
|
|
|
size_t len;
|
|
|
|
while((len = fread(buf, 1, sizeof buf, in)))
|
|
|
|
fwrite(buf, len, 1, out);
|
|
|
|
fclose(in);
|
|
|
|
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 = NULL;
|
|
|
|
xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, argv[1]);
|
|
|
|
if(!access(filename, F_OK)) {
|
|
|
|
free(filename);
|
|
|
|
fprintf(stderr, "A host config file for %s already exists!\n", argv[1]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
free(filename);
|
|
|
|
|
|
|
|
// If a daemon is running, ensure no other nodes now 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
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));
|
|
|
|
free(filename);
|
|
|
|
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;
|
|
|
|
struct stat st;
|
|
|
|
xasprintf(&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;
|
|
|
|
}
|
|
|
|
free(invname);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(errno) {
|
|
|
|
fprintf(stderr, "Error while reading directory %s: %s\n", filename, strerror(errno));
|
|
|
|
closedir(dir);
|
|
|
|
free(filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
free(filename);
|
|
|
|
|
|
|
|
ecdsa_t *key;
|
|
|
|
xasprintf(&filename, "%s" SLASH "invitations" SLASH "ecdsa_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));
|
|
|
|
free(filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
key = ecdsa_generate();
|
|
|
|
if(!key) {
|
|
|
|
free(filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
f = fopen(filename, "w");
|
|
|
|
if(!f) {
|
|
|
|
fprintf(stderr, "Could not write %s: %s\n", filename, strerror(errno));
|
|
|
|
free(filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
chmod(filename, 0600);
|
|
|
|
ecdsa_write_pem_private_key(key, f);
|
|
|
|
} else {
|
|
|
|
key = ecdsa_read_pem_private_key(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 *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);
|
|
|
|
b64encode_urlsafe(hash, hash, 18);
|
|
|
|
|
|
|
|
// Create a random cookie for this invitation.
|
|
|
|
char cookie[25];
|
|
|
|
randomize(cookie, 18);
|
|
|
|
b64encode_urlsafe(cookie, cookie, 18);
|
|
|
|
|
|
|
|
// Create a file containing the details of the invitation.
|
|
|
|
xasprintf(&filename, "%s" SLASH "invitations" SLASH "%s", confbase, cookie);
|
|
|
|
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();
|
|
|
|
|
|
|
|
// 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
|
|
|
|
fprintf(f, "#---------------------------------------------------------------#\n");
|
|
|
|
fprintf(f, "Name = %s\n", myname);
|
|
|
|
|
|
|
|
xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, myname);
|
|
|
|
fcopy(f, filename);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
// Create an URL from the local address, key hash and cookie
|
|
|
|
char *address = get_my_hostname();
|
|
|
|
printf("%s/%s%s\n", address, hash, cookie);
|
|
|
|
free(filename);
|
|
|
|
free(address);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sock;
|
|
|
|
static char cookie[18];
|
|
|
|
static sptps_t sptps;
|
|
|
|
static char *data;
|
|
|
|
static size_t datalen;
|
|
|
|
static bool success = false;
|
|
|
|
|
|
|
|
static char cookie[18], hash[18];
|
|
|
|
|
|
|
|
static char *get_line(const char **data) {
|
|
|
|
if(!data || !*data)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if(!**data) {
|
|
|
|
*data = NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char line[1024];
|
|
|
|
const char *end = strchr(*data, '\n');
|
|
|
|
size_t len = end ? end - *data : strlen(*data);
|
|
|
|
if(len >= sizeof line) {
|
|
|
|
fprintf(stderr, "Maximum line length exceeded!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if(len && !isprint(**data))
|
|
|
|
abort();
|
|
|
|
|
|
|
|
memcpy(line, *data, len);
|
|
|
|
line[len] = 0;
|
|
|
|
|
|
|
|
if(end)
|
|
|
|
*data = end + 1;
|
|
|
|
else
|
|
|
|
*data = NULL;
|
|
|
|
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *get_value(const char *data, const char *var) {
|
|
|
|
char *line = get_line(&data);
|
|
|
|
if(!line)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
char *sep = line + strcspn(line, " \t=");
|
|
|
|
char *val = sep + strspn(sep, " \t");
|
|
|
|
if(*val == '=')
|
|
|
|
val += 1 + strspn(val + 1, " \t");
|
|
|
|
*sep = 0;
|
|
|
|
if(strcasecmp(line, var))
|
|
|
|
return NULL;
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *grep(const char *data, const char *var) {
|
|
|
|
static char value[1024];
|
|
|
|
|
|
|
|
const char *p = data;
|
|
|
|
int varlen = strlen(var);
|
|
|
|
|
|
|
|
// Skip all lines not starting with var
|
|
|
|
while(strncasecmp(p, var, varlen) || !strchr(" \t=", p[varlen])) {
|
|
|
|
p = strchr(p, '\n');
|
|
|
|
if(!p)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!p)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
p += varlen;
|
|
|
|
p += strspn(p, " \t");
|
|
|
|
if(*p == '=')
|
|
|
|
p += 1 + strspn(p + 1, " \t");
|
|
|
|
|
|
|
|
const char *e = strchr(p, '\n');
|
|
|
|
if(!e)
|
|
|
|
return xstrdup(p);
|
|
|
|
|
|
|
|
if(e - p >= sizeof value) {
|
|
|
|
fprintf(stderr, "Maximum line length exceeded!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(value, p, e - p);
|
|
|
|
value[e - p] = 0;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
2013-07-26 13:48:52 +00:00
|
|
|
bool ask_netname = false;
|
|
|
|
char temp_netname[32];
|
|
|
|
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
make_names:
|
|
|
|
if(!confbasegiven) {
|
|
|
|
free(confbase);
|
|
|
|
confbase = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
make_names();
|
|
|
|
|
|
|
|
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(!tty || confbasegiven)
|
|
|
|
return false;
|
|
|
|
|
2013-07-26 13:48:52 +00:00
|
|
|
// 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;
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
goto make_names;
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
if(mkdir(confbase, 0777) && errno != EEXIST) {
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
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;
|
|
|
|
xasprintf(&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));
|
|
|
|
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);
|
|
|
|
free(filename);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
xasprintf(&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);
|
|
|
|
free(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase);
|
2013-08-02 17:27:06 +00:00
|
|
|
f = fopenmask(filename, "w", 0600);
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
|
|
|
|
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, "ECDSAPublicKey = %s\n", b64key);
|
|
|
|
|
|
|
|
sptps_send_record(&sptps, 1, b64key, strlen(b64key));
|
|
|
|
free(b64key);
|
|
|
|
|
|
|
|
|
|
|
|
rsa_t *rsa = rsa_generate(2048, 0x1001);
|
|
|
|
xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase);
|
2013-08-02 17:27:06 +00:00
|
|
|
f = fopenmask(filename, "w", 0600);
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
|
|
|
|
rsa_write_pem_private_key(rsa, f);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
rsa_write_pem_public_key(rsa, fh);
|
|
|
|
fclose(fh);
|
|
|
|
|
|
|
|
ecdsa_free(key);
|
|
|
|
rsa_free(rsa);
|
|
|
|
|
2013-07-26 12:53:36 +00:00
|
|
|
check_port(name);
|
|
|
|
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
fprintf(stderr, "Invitation succesfully accepted.\n");
|
|
|
|
shutdown(sock, SHUT_RDWR);
|
|
|
|
success = true;
|
|
|
|
|
2013-07-26 13:48:52 +00:00
|
|
|
ask_netname:
|
|
|
|
if(ask_netname) {
|
|
|
|
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;
|
|
|
|
xasprintf(&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));
|
|
|
|
free(newbase);
|
|
|
|
goto ask_netname;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(newbase);
|
|
|
|
netname = line;
|
|
|
|
make_names();
|
|
|
|
}
|
|
|
|
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool invitation_send(void *handle, uint8_t type, const char *data, size_t len) {
|
|
|
|
while(len) {
|
|
|
|
int result = send(sock, data, len, 0);
|
|
|
|
if(result == -1 && errno == EINTR)
|
|
|
|
continue;
|
|
|
|
else if(result <= 0)
|
|
|
|
return false;
|
|
|
|
data += result;
|
|
|
|
len -= result;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool invitation_receive(void *handle, uint8_t type, const char *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();
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cmd_join(int argc, char *argv[]) {
|
|
|
|
free(data);
|
|
|
|
data = NULL;
|
|
|
|
datalen = 0;
|
|
|
|
|
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-07-26 13:44:05 +00:00
|
|
|
// Make sure confbase exists and is accessible.
|
2013-08-02 17:27:06 +00:00
|
|
|
if(strcmp(confdir, confbase) && mkdir(confdir, 0755) && errno != EEXIST) {
|
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mkdir(confbase, 0777) && errno != EEXIST) {
|
2013-07-26 13:44:05 +00:00
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(access(confbase, R_OK | W_OK | X_OK)) {
|
|
|
|
fprintf(stderr, "No permission to write in directory %s: %s\n", confbase, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a netname or explicit configuration directory is specified, check for an existing tinc.conf.
|
|
|
|
if((netname || confbasegiven) && !access(tinc_conf, F_OK)) {
|
|
|
|
fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
|
Add an invitation protocol.
Using the tinc command, an administrator of an existing VPN can generate
invitations for new nodes. The invitation is a small URL that can easily
be copy&pasted into email or live chat. Another person can have tinc
automatically setup the necessary configuration files and exchange keys
with the server, by only using the invitation URL.
The invitation protocol uses temporary ECDSA keys. The invitation URL
consists of the hostname and port of the server, a hash of the server's
temporary ECDSA key and a cookie. When the client wants to accept an
invitation, it also creates a temporary ECDSA key, connects to the server
and says it wants to accept an invitation. Both sides exchange their
temporary keys. The client verifies that the server's key matches the hash
in the invitation URL. After setting up an SPTPS connection using the
temporary keys, the client gives the cookie to the server. If the cookie
is valid, the server sends the client an invitation file containing the
client's new name and a copy of the server's host config file. If everything
is ok, the client will generate a long-term ECDSA key and send it to the
server, which will add it to a new host config file for the client.
The invitation protocol currently allows multiple host config files to be
send from the server to the client. However, the client filters out
most configuration variables for its own host configuration file. In
particular, it only accepts Name, Mode, Broadcast, ConnectTo, Subnet and
AutoConnect. Also, at the moment no tinc-up script is generated.
When an invitation has succesfully been accepted, the client needs to start
the tinc daemon manually.
2013-05-29 16:31:10 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Either read the invitation from the command line or from stdin.
|
|
|
|
char *invitation;
|
|
|
|
|
|
|
|
if(argc > 1) {
|
|
|
|
invitation = argv[1];
|
|
|
|
} else {
|
|
|
|
if(tty) {
|
|
|
|
printf("Enter invitation URL: ");
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
errno = EPIPE;
|
|
|
|
if(!fgets(line, sizeof line, stdin)) {
|
|
|
|
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
invitation = line;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the invitation URL.
|
|
|
|
rstrip(line);
|
|
|
|
|
|
|
|
char *slash = strchr(invitation, '/');
|
|
|
|
if(!slash)
|
|
|
|
goto invalid;
|
|
|
|
|
|
|
|
*slash++ = 0;
|
|
|
|
|
|
|
|
if(strlen(slash) != 48)
|
|
|
|
goto invalid;
|
|
|
|
|
|
|
|
char *address = invitation;
|
|
|
|
char *port = NULL;
|
|
|
|
if(*address == '[') {
|
|
|
|
address++;
|
|
|
|
char *bracket = strchr(address, ']');
|
|
|
|
if(!bracket)
|
|
|
|
goto invalid;
|
|
|
|
*bracket = 0;
|
|
|
|
if(bracket[1] == ':')
|
|
|
|
port = bracket + 2;
|
|
|
|
} else {
|
|
|
|
port = strchr(address, ':');
|
|
|
|
if(port)
|
|
|
|
*port++ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!port || !*port)
|
|
|
|
port = "655";
|
|
|
|
|
|
|
|
if(!b64decode(slash, hash, 18) || !b64decode(slash + 24, cookie, 18))
|
|
|
|
goto invalid;
|
|
|
|
|
|
|
|
// Generate a throw-away key for the invitation.
|
|
|
|
ecdsa_t *key = ecdsa_generate();
|
|
|
|
if(!key)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
char *b64key = ecdsa_get_base64_public_key(key);
|
|
|
|
|
|
|
|
// Connect to the tinc daemon mentioned in the URL.
|
|
|
|
struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
|
|
|
|
if(!ai)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
|
|
|
if(sock <= 0) {
|
|
|
|
fprintf(stderr, "Could not open socket: %s\n", strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(connect(sock, ai->ai_addr, ai->ai_addrlen)) {
|
|
|
|
fprintf(stderr, "Could not connect to %s port %s: %s\n", address, port, strerror(errno));
|
|
|
|
closesocket(sock);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "Connected to %s port %s...\n", address, port);
|
|
|
|
|
|
|
|
// Tell him we have an invitation, and give him our throw-away key.
|
|
|
|
int len = snprintf(line, sizeof line, "0 ?%s %d.%d\n", b64key, PROT_MAJOR, PROT_MINOR);
|
|
|
|
if(len <= 0 || len >= sizeof line)
|
|
|
|
abort();
|
|
|
|
|
|
|
|
if(!sendline(sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) {
|
|
|
|
fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno));
|
|
|
|
closesocket(sock);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char hisname[4096] = "";
|
|
|
|
int code, hismajor, hisminor = 0;
|
|
|
|
|
|
|
|
if(!recvline(sock, line, sizeof line) || sscanf(line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(sock, line, sizeof line) || !rstrip(line) || sscanf(line, "%d ", &code) != 1 || code != ACK || strlen(line) < 3) {
|
|
|
|
fprintf(stderr, "Cannot read greeting from peer\n");
|
|
|
|
closesocket(sock);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the hash of the key he have 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)) {
|
|
|
|
fprintf(stderr, "Could not create digest\n%s\n", line + 2);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if(memcmp(hishash, hash, 18)) {
|
|
|
|
fprintf(stderr, "Peer has an invalid key!\n%s\n", line + 2);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint);
|
|
|
|
if(!hiskey)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Start an SPTPS session
|
|
|
|
if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "tinc invitation", 15, invitation_send, invitation_receive))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Feed rest of input buffer to SPTPS
|
|
|
|
if(!sptps_receive_data(&sptps, buffer, blen))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
while((len = recv(sock, line, sizeof line, 0))) {
|
|
|
|
if(len < 0) {
|
|
|
|
if(errno == EINTR)
|
|
|
|
continue;
|
|
|
|
fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!sptps_receive_data(&sptps, line, len))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sptps_stop(&sptps);
|
|
|
|
ecdsa_free(hiskey);
|
|
|
|
ecdsa_free(key);
|
|
|
|
closesocket(sock);
|
|
|
|
|
|
|
|
if(!success) {
|
|
|
|
fprintf(stderr, "Connection closed by peer, invitation cancelled.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
invalid:
|
|
|
|
fprintf(stderr, "Invalid invitation URL.\n");
|
|
|
|
return 1;
|
|
|
|
}
|