2007-05-18 16:52:34 +00:00
|
|
|
/*
|
|
|
|
tincctl.c -- Controlling a running tincd
|
2014-02-07 19:38:48 +00:00
|
|
|
Copyright (C) 2007-2014 Guus Sliepen <guus@tinc-vpn.org>
|
2007-05-18 16:52:34 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2009-09-29 13:33:58 +00:00
|
|
|
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.
|
2007-05-18 16:52:34 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "system.h"
|
|
|
|
|
|
|
|
#include <getopt.h>
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
#include "readline/readline.h"
|
|
|
|
#include "readline/history.h"
|
|
|
|
#endif
|
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
#include "xalloc.h"
|
2007-11-07 02:48:33 +00:00
|
|
|
#include "protocol.h"
|
|
|
|
#include "control_common.h"
|
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
|
|
|
#include "crypto.h"
|
2011-07-03 20:25:29 +00:00
|
|
|
#include "ecdsagen.h"
|
2012-07-15 23:05:25 +00:00
|
|
|
#include "info.h"
|
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
|
|
|
#include "invitation.h"
|
2013-01-17 15:39:02 +00:00
|
|
|
#include "names.h"
|
2008-12-14 12:47:26 +00:00
|
|
|
#include "rsagen.h"
|
2009-11-05 22:29:28 +00:00
|
|
|
#include "utils.h"
|
2011-05-15 11:16:48 +00:00
|
|
|
#include "tincctl.h"
|
|
|
|
#include "top.h"
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2014-03-09 14:32:10 +00:00
|
|
|
#ifndef MSG_NOSIGNAL
|
|
|
|
#define MSG_NOSIGNAL 0
|
|
|
|
#endif
|
|
|
|
|
2012-08-03 12:15:50 +00:00
|
|
|
static char **orig_argv;
|
|
|
|
static int orig_argc;
|
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
/* If nonzero, display usage information and exit. */
|
2011-05-28 01:56:06 +00:00
|
|
|
static bool show_help = false;
|
2007-05-18 16:52:34 +00:00
|
|
|
|
|
|
|
/* If nonzero, print the version on standard output and exit. */
|
2011-05-28 01:56:06 +00:00
|
|
|
static bool show_version = false;
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
static char *name = NULL;
|
2012-11-10 22:09:31 +00:00
|
|
|
static char controlcookie[1025];
|
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 *tinc_conf = NULL;
|
|
|
|
char *hosts_dir = NULL;
|
2012-11-29 11:28:23 +00:00
|
|
|
struct timeval now;
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
// Horrible global variables...
|
|
|
|
static int pid = 0;
|
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
|
|
|
int fd = -1;
|
|
|
|
char line[4096];
|
2012-07-15 13:46:16 +00:00
|
|
|
static int code;
|
|
|
|
static int req;
|
|
|
|
static int result;
|
2012-07-16 14:48:24 +00:00
|
|
|
static bool force = false;
|
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
|
|
|
bool tty = true;
|
|
|
|
bool confbasegiven = false;
|
|
|
|
bool netnamegiven = false;
|
2013-08-23 17:24:36 +00:00
|
|
|
char *scriptinterpreter = NULL;
|
|
|
|
char *scriptextension = "";
|
2014-02-26 10:00:30 +00:00
|
|
|
static char *prompt;
|
2012-07-15 13:46:16 +00:00
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
static struct option const long_options[] = {
|
|
|
|
{"config", required_argument, NULL, 'c'},
|
|
|
|
{"net", required_argument, NULL, 'n'},
|
|
|
|
{"help", no_argument, NULL, 1},
|
|
|
|
{"version", no_argument, NULL, 2},
|
2013-05-30 14:53:16 +00:00
|
|
|
{"pidfile", required_argument, NULL, 3},
|
|
|
|
{"force", no_argument, NULL, 4},
|
2007-05-18 16:52:34 +00:00
|
|
|
{NULL, 0, NULL, 0}
|
|
|
|
};
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static void version(void) {
|
|
|
|
printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
|
|
|
|
VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR);
|
|
|
|
printf("Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others.\n"
|
|
|
|
"See the AUTHORS file for a complete list.\n\n"
|
|
|
|
"tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
|
|
|
|
"and you are welcome to redistribute it under certain conditions;\n"
|
|
|
|
"see the file COPYING for details.\n");
|
|
|
|
}
|
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
static void usage(bool status) {
|
2013-01-17 15:39:02 +00:00
|
|
|
if(status) {
|
|
|
|
fprintf(stderr, "Try `%s --help\' for more information.\n", program_name);
|
|
|
|
} else {
|
2009-09-29 13:19:55 +00:00
|
|
|
printf("Usage: %s [options] command\n\n", program_name);
|
|
|
|
printf("Valid options are:\n"
|
2011-06-25 19:21:36 +00:00
|
|
|
" -c, --config=DIR Read configuration options from DIR.\n"
|
|
|
|
" -n, --net=NETNAME Connect to net NETNAME.\n"
|
|
|
|
" --pidfile=FILENAME Read control cookie from FILENAME.\n"
|
|
|
|
" --help Display this help and exit.\n"
|
|
|
|
" --version Output version information and exit.\n"
|
2007-05-19 14:13:21 +00:00
|
|
|
"\n"
|
2007-05-18 16:52:34 +00:00
|
|
|
"Valid commands are:\n"
|
2012-07-15 12:49:36 +00:00
|
|
|
" init [name] Create initial configuration files.\n"
|
2013-03-08 10:40:40 +00:00
|
|
|
" get VARIABLE Print current value of VARIABLE\n"
|
|
|
|
" set VARIABLE VALUE Set VARIABLE to VALUE\n"
|
|
|
|
" add VARIABLE VALUE Add VARIABLE with the given VALUE\n"
|
|
|
|
" del VARIABLE [VALUE] Remove VARIABLE [only ones with watching VALUE]\n"
|
2012-07-15 19:17:10 +00:00
|
|
|
" start [tincd options] Start tincd.\n"
|
2007-05-18 16:52:34 +00:00
|
|
|
" stop Stop tincd.\n"
|
2013-07-20 22:20:54 +00:00
|
|
|
" restart [tincd options] Restart tincd.\n"
|
2011-08-07 07:25:03 +00:00
|
|
|
" reload Partially reload configuration of running tincd.\n"
|
2007-05-19 15:21:26 +00:00
|
|
|
" pid Show PID of currently running tincd.\n"
|
2014-05-18 18:47:04 +00:00
|
|
|
" generate-keys [bits] Generate new RSA and Ed25519 public/private keypairs.\n"
|
2011-07-03 20:25:29 +00:00
|
|
|
" generate-rsa-keys [bits] Generate a new RSA public/private keypair.\n"
|
2014-05-18 18:47:04 +00:00
|
|
|
" generate-ed25519-keys Generate a new Ed25519 public/private keypair.\n"
|
2007-05-18 16:52:34 +00:00
|
|
|
" dump Dump a list of one of the following things:\n"
|
2012-12-03 12:08:03 +00:00
|
|
|
" [reachable] nodes - all known nodes in the VPN\n"
|
2007-05-18 16:52:34 +00:00
|
|
|
" edges - all known connections in the VPN\n"
|
|
|
|
" subnets - all known subnets in the VPN\n"
|
|
|
|
" connections - all meta connections with ourself\n"
|
2012-09-26 21:52:36 +00:00
|
|
|
" [di]graph - graph of the VPN in dotty format\n"
|
2012-07-15 23:05:25 +00:00
|
|
|
" info NODE|SUBNET|ADDRESS Give information about a particular NODE, SUBNET or ADDRESS.\n"
|
2007-11-07 02:49:57 +00:00
|
|
|
" purge Purge unreachable nodes\n"
|
2007-11-07 02:50:27 +00:00
|
|
|
" debug N Set debug level\n"
|
2007-11-07 02:50:58 +00:00
|
|
|
" retry Retry all outgoing connections\n"
|
2009-12-16 20:16:56 +00:00
|
|
|
" disconnect NODE Close meta connection with NODE\n"
|
2011-05-15 11:16:48 +00:00
|
|
|
#ifdef HAVE_CURSES
|
|
|
|
" top Show real-time statistics\n"
|
|
|
|
#endif
|
2012-02-26 17:37:36 +00:00
|
|
|
" pcap [snaplen] Dump traffic in pcap format [up to snaplen bytes per packet]\n"
|
|
|
|
" log [level] Dump log output [up to the specified level]\n"
|
2012-07-16 14:48:24 +00:00
|
|
|
" export Export host configuration of local node to standard output\n"
|
|
|
|
" export-all Export all host configuration files to standard output\n"
|
|
|
|
" import [--force] Import host configuration file(s) from standard input\n"
|
2013-01-15 12:31:51 +00:00
|
|
|
" exchange [--force] Same as export followed by import\n"
|
|
|
|
" exchange-all [--force] Same as export-all followed by import\n"
|
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
|
|
|
" invite NODE [...] Generate an invitation for NODE\n"
|
|
|
|
" join INVITATION Join a VPN using an INVITIATION\n"
|
2014-02-26 10:00:30 +00:00
|
|
|
" network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
|
2009-09-29 13:19:55 +00:00
|
|
|
"\n");
|
|
|
|
printf("Report bugs to tinc@tinc-vpn.org.\n");
|
2007-05-18 16:52:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool parse_options(int argc, char **argv) {
|
|
|
|
int r;
|
|
|
|
int option_index = 0;
|
|
|
|
|
2013-05-30 14:53:16 +00:00
|
|
|
while((r = getopt_long(argc, argv, "+c:n:", long_options, &option_index)) != EOF) {
|
2007-05-18 16:52:34 +00:00
|
|
|
switch (r) {
|
2012-10-10 15:17:49 +00:00
|
|
|
case 0: /* long option */
|
2007-05-18 16:52:34 +00:00
|
|
|
break;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
case 'c': /* config file */
|
2007-05-18 16:52:34 +00:00
|
|
|
confbase = xstrdup(optarg);
|
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
|
|
|
confbasegiven = true;
|
2007-05-18 16:52:34 +00:00
|
|
|
break;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
case 'n': /* net name given */
|
2007-05-18 16:52:34 +00:00
|
|
|
netname = xstrdup(optarg);
|
|
|
|
break;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
case 1: /* show help */
|
2007-05-18 16:52:34 +00:00
|
|
|
show_help = true;
|
|
|
|
break;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
case 2: /* show version */
|
2007-05-18 16:52:34 +00:00
|
|
|
show_version = true;
|
|
|
|
break;
|
|
|
|
|
2013-05-30 14:53:16 +00:00
|
|
|
case 3: /* open control socket here */
|
2011-06-25 19:21:36 +00:00
|
|
|
pidfilename = xstrdup(optarg);
|
2007-05-18 16:52:34 +00:00
|
|
|
break;
|
|
|
|
|
2013-05-30 14:53:16 +00:00
|
|
|
case 4: /* force */
|
2012-07-16 14:48:24 +00:00
|
|
|
force = true;
|
|
|
|
break;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
case '?': /* wrong options */
|
2007-05-18 16:52:34 +00:00
|
|
|
usage(true);
|
|
|
|
return false;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
if(!netname && (netname = getenv("NETNAME")))
|
|
|
|
netname = xstrdup(netname);
|
2012-07-21 13:15:04 +00:00
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
/* netname "." is special: a "top-level name" */
|
2012-07-21 13:15:04 +00:00
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
if(netname && (!*netname || !strcmp(netname, "."))) {
|
|
|
|
free(netname);
|
|
|
|
netname = NULL;
|
|
|
|
}
|
2012-02-26 11:39:46 +00:00
|
|
|
|
2012-08-01 14:51:59 +00:00
|
|
|
if(netname && (strpbrk(netname, "\\/") || *netname == '.')) {
|
|
|
|
fprintf(stderr, "Invalid character in netname!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
/* Open a file with the desired permissions, minus the umask.
|
|
|
|
Also, if we want to create an executable file, we call fchmod()
|
|
|
|
to set the executable bits. */
|
|
|
|
|
|
|
|
FILE *fopenmask(const char *filename, const char *mode, mode_t perms) {
|
|
|
|
mode_t mask = umask(0);
|
|
|
|
perms &= ~mask;
|
|
|
|
umask(~perms);
|
|
|
|
FILE *f = fopen(filename, mode);
|
|
|
|
#ifdef HAVE_FCHMOD
|
|
|
|
if((perms & 0444) && f)
|
|
|
|
fchmod(fileno(f), perms);
|
|
|
|
#endif
|
|
|
|
umask(mask);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
2012-09-27 13:45:02 +00:00
|
|
|
static void disable_old_keys(const char *filename, const char *what) {
|
|
|
|
char tmpfile[PATH_MAX] = "";
|
|
|
|
char buf[1024];
|
|
|
|
bool disabled = false;
|
|
|
|
bool block = false;
|
|
|
|
bool error = false;
|
|
|
|
FILE *r, *w;
|
|
|
|
|
|
|
|
r = fopen(filename, "r");
|
|
|
|
if(!r)
|
|
|
|
return;
|
|
|
|
|
|
|
|
snprintf(tmpfile, sizeof tmpfile, "%s.tmp", filename);
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
struct stat st = {.st_mode = 0600};
|
|
|
|
fstat(fileno(r), &st);
|
|
|
|
w = fopenmask(tmpfile, "w", st.st_mode);
|
2013-07-22 20:58:13 +00:00
|
|
|
|
2012-09-27 13:45:02 +00:00
|
|
|
while(fgets(buf, sizeof buf, r)) {
|
|
|
|
if(!block && !strncmp(buf, "-----BEGIN ", 11)) {
|
2014-05-18 18:49:35 +00:00
|
|
|
if((strstr(buf, " ED25519 ") && strstr(what, "Ed25519")) || (strstr(buf, " RSA ") && strstr(what, "RSA"))) {
|
2012-09-27 13:45:02 +00:00
|
|
|
disabled = true;
|
|
|
|
block = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
bool ed25519pubkey = !strncasecmp(buf, "Ed25519PublicKey", 16) && strchr(" \t=", buf[16]) && strstr(what, "Ed25519");
|
2012-09-27 13:45:02 +00:00
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
if(ed25519pubkey)
|
2012-09-27 13:45:02 +00:00
|
|
|
disabled = true;
|
|
|
|
|
|
|
|
if(w) {
|
2014-05-18 18:47:04 +00:00
|
|
|
if(block || ed25519pubkey)
|
2012-09-27 13:45:02 +00:00
|
|
|
fputc('#', w);
|
|
|
|
if(fputs(buf, w) < 0) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(block && !strncmp(buf, "-----END ", 9))
|
|
|
|
block = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(w)
|
|
|
|
if(fclose(w) < 0)
|
|
|
|
error = true;
|
|
|
|
if(ferror(r) || fclose(r) < 0)
|
|
|
|
error = true;
|
|
|
|
|
|
|
|
if(disabled) {
|
|
|
|
if(!w || error) {
|
|
|
|
fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
|
|
|
|
if(w)
|
|
|
|
unlink(tmpfile);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
// We cannot atomically replace files on Windows.
|
|
|
|
char bakfile[PATH_MAX] = "";
|
|
|
|
snprintf(bakfile, sizeof bakfile, "%s.bak", filename);
|
|
|
|
if(rename(filename, bakfile) || rename(tmpfile, filename)) {
|
|
|
|
rename(bakfile, filename);
|
|
|
|
#else
|
|
|
|
if(rename(tmpfile, filename)) {
|
|
|
|
#endif
|
|
|
|
fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
|
|
|
|
} else {
|
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
unlink(bakfile);
|
|
|
|
#endif
|
|
|
|
fprintf(stderr, "Warning: old key(s) found and disabled.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unlink(tmpfile);
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask, mode_t perms) {
|
2007-05-19 14:55:35 +00:00
|
|
|
FILE *r;
|
|
|
|
char *directory;
|
|
|
|
char buf[PATH_MAX];
|
|
|
|
char buf2[PATH_MAX];
|
|
|
|
|
|
|
|
/* Check stdin and stdout */
|
2012-08-02 15:24:42 +00:00
|
|
|
if(ask && tty) {
|
2007-05-19 14:55:35 +00:00
|
|
|
/* Ask for a file and/or directory name. */
|
2014-01-29 16:17:59 +00:00
|
|
|
fprintf(stderr, "Please enter a file to save %s to [%s]: ", what, filename);
|
2007-05-19 14:55:35 +00:00
|
|
|
|
2011-05-28 01:48:07 +00:00
|
|
|
if(fgets(buf, sizeof buf, stdin) == 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
|
|
|
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
|
2007-05-19 14:55:35 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2011-06-02 16:22:26 +00:00
|
|
|
size_t len = strlen(buf);
|
2007-05-19 14:55:35 +00:00
|
|
|
if(len)
|
|
|
|
buf[--len] = 0;
|
|
|
|
|
|
|
|
if(len)
|
|
|
|
filename = buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
if(filename[0] != '\\' && filename[0] != '/' && !strchr(filename, ':')) {
|
|
|
|
#else
|
|
|
|
if(filename[0] != '/') {
|
|
|
|
#endif
|
|
|
|
/* The directory is a relative path or a filename. */
|
|
|
|
directory = get_current_dir_name();
|
2012-07-21 14:26:55 +00:00
|
|
|
snprintf(buf2, sizeof buf2, "%s" SLASH "%s", directory, filename);
|
2007-05-19 14:55:35 +00:00
|
|
|
filename = buf2;
|
|
|
|
}
|
|
|
|
|
2012-09-27 13:45:02 +00:00
|
|
|
disable_old_keys(filename, what);
|
|
|
|
|
2007-05-19 14:55:35 +00:00
|
|
|
/* Open it first to keep the inode busy */
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
r = fopenmask(filename, mode, perms);
|
2007-05-19 14:55:35 +00:00
|
|
|
|
|
|
|
if(!r) {
|
2009-09-29 13:19:55 +00:00
|
|
|
fprintf(stderr, "Error opening file `%s': %s\n", filename, strerror(errno));
|
2007-05-19 14:55:35 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2011-07-03 20:25:29 +00:00
|
|
|
/*
|
2014-05-18 18:47:04 +00:00
|
|
|
Generate a public/private Ed25519 keypair, and ask for a file to store
|
2011-07-03 20:25:29 +00:00
|
|
|
them in.
|
|
|
|
*/
|
2014-05-18 18:47:04 +00:00
|
|
|
static bool ed25519_keygen(bool ask) {
|
2013-05-01 15:17:22 +00:00
|
|
|
ecdsa_t *key;
|
2011-07-03 20:25:29 +00:00
|
|
|
FILE *f;
|
2012-10-07 15:53:23 +00:00
|
|
|
char *pubname, *privname;
|
2011-07-03 20:25:29 +00:00
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
fprintf(stderr, "Generating Ed25519 keypair:\n");
|
2011-07-03 20:25:29 +00:00
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
if(!(key = ecdsa_generate())) {
|
2011-07-03 20:25:29 +00:00
|
|
|
fprintf(stderr, "Error during key generation!\n");
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
fprintf(stderr, "Done.\n");
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
xasprintf(&privname, "%s" SLASH "ed25519_key.priv", confbase);
|
|
|
|
f = ask_and_open(privname, "private Ed25519 key", "a", ask, 0600);
|
2012-10-07 15:53:23 +00:00
|
|
|
free(privname);
|
2011-07-03 20:25:29 +00:00
|
|
|
|
|
|
|
if(!f)
|
|
|
|
return false;
|
2012-10-10 15:17:49 +00:00
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
if(!ecdsa_write_pem_private_key(key, f)) {
|
|
|
|
fprintf(stderr, "Error writing private key!\n");
|
|
|
|
ecdsa_free(key);
|
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
2011-07-03 20:25:29 +00:00
|
|
|
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
if(name)
|
2012-10-07 15:53:23 +00:00
|
|
|
xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name);
|
2011-07-03 20:25:29 +00:00
|
|
|
else
|
2014-05-18 18:47:04 +00:00
|
|
|
xasprintf(&pubname, "%s" SLASH "ed25519_key.pub", confbase);
|
2011-07-03 20:25:29 +00:00
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
f = ask_and_open(pubname, "public Ed25519 key", "a", ask, 0666);
|
2012-10-07 15:53:23 +00:00
|
|
|
free(pubname);
|
2011-07-03 20:25:29 +00:00
|
|
|
|
|
|
|
if(!f)
|
|
|
|
return false;
|
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
char *pubkey = ecdsa_get_base64_public_key(key);
|
2014-05-18 18:47:04 +00:00
|
|
|
fprintf(f, "Ed25519PublicKey = %s\n", pubkey);
|
2011-07-03 20:25:29 +00:00
|
|
|
free(pubkey);
|
|
|
|
|
|
|
|
fclose(f);
|
2013-05-01 15:17:22 +00:00
|
|
|
ecdsa_free(key);
|
2011-07-03 20:25:29 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
/*
|
|
|
|
Generate a public/private RSA keypair, and ask for a file to store
|
|
|
|
them in.
|
|
|
|
*/
|
2012-08-02 15:24:42 +00:00
|
|
|
static bool rsa_keygen(int bits, bool ask) {
|
2013-05-01 15:17:22 +00:00
|
|
|
rsa_t *key;
|
2007-05-18 16:52:34 +00:00
|
|
|
FILE *f;
|
2012-10-07 15:53:23 +00:00
|
|
|
char *pubname, *privname;
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2014-05-13 18:29:09 +00:00
|
|
|
// Make sure the key size is a multiple of 8 bits.
|
|
|
|
bits &= ~0x7;
|
|
|
|
|
|
|
|
// Force them to be between 1024 and 8192 bits long.
|
|
|
|
if(bits < 1024)
|
|
|
|
bits = 1024;
|
|
|
|
if(bits > 8192)
|
|
|
|
bits = 8192;
|
|
|
|
|
2009-09-29 13:19:55 +00:00
|
|
|
fprintf(stderr, "Generating %d bits keys:\n", bits);
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
if(!(key = rsa_generate(bits, 0x10001))) {
|
2009-09-29 13:19:55 +00:00
|
|
|
fprintf(stderr, "Error during key generation!\n");
|
2007-05-18 16:52:34 +00:00
|
|
|
return false;
|
|
|
|
} else
|
2009-09-29 13:19:55 +00:00
|
|
|
fprintf(stderr, "Done.\n");
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2012-10-07 15:53:23 +00:00
|
|
|
xasprintf(&privname, "%s" SLASH "rsa_key.priv", confbase);
|
2013-08-02 17:27:06 +00:00
|
|
|
f = ask_and_open(privname, "private RSA key", "a", ask, 0600);
|
2012-10-07 15:53:23 +00:00
|
|
|
free(privname);
|
2007-05-18 16:52:34 +00:00
|
|
|
|
|
|
|
if(!f)
|
|
|
|
return false;
|
2012-10-10 15:17:49 +00:00
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
if(!rsa_write_pem_private_key(key, f)) {
|
|
|
|
fprintf(stderr, "Error writing private key!\n");
|
|
|
|
fclose(f);
|
|
|
|
rsa_free(key);
|
|
|
|
return false;
|
|
|
|
}
|
2008-12-14 12:47:26 +00:00
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
if(name)
|
2012-10-07 15:53:23 +00:00
|
|
|
xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name);
|
2007-05-18 16:52:34 +00:00
|
|
|
else
|
2012-10-07 15:53:23 +00:00
|
|
|
xasprintf(&pubname, "%s" SLASH "rsa_key.pub", confbase);
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
f = ask_and_open(pubname, "public RSA key", "a", ask, 0666);
|
2012-10-07 15:53:23 +00:00
|
|
|
free(pubname);
|
2007-05-18 16:52:34 +00:00
|
|
|
|
|
|
|
if(!f)
|
|
|
|
return false;
|
|
|
|
|
2013-05-01 15:17:22 +00:00
|
|
|
if(!rsa_write_pem_public_key(key, f)) {
|
|
|
|
fprintf(stderr, "Error writing public key!\n");
|
|
|
|
fclose(f);
|
|
|
|
rsa_free(key);
|
|
|
|
return false;
|
|
|
|
}
|
2008-12-14 12:47:26 +00:00
|
|
|
|
2007-05-18 16:52:34 +00:00
|
|
|
fclose(f);
|
2013-05-01 15:17:22 +00:00
|
|
|
rsa_free(key);
|
2007-05-18 16:52:34 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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 buffer[4096];
|
|
|
|
size_t blen = 0;
|
2011-05-22 12:17:30 +00:00
|
|
|
|
2011-05-15 11:16:48 +00:00
|
|
|
bool recvline(int fd, char *line, size_t len) {
|
2009-11-07 22:43:25 +00:00
|
|
|
char *newline = NULL;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
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(!fd)
|
2013-05-30 15:38:48 +00:00
|
|
|
abort();
|
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
while(!(newline = memchr(buffer, '\n', blen))) {
|
|
|
|
int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
|
|
|
|
if(result == -1 && errno == EINTR)
|
2007-11-07 02:48:33 +00:00
|
|
|
continue;
|
2009-11-07 22:43:25 +00:00
|
|
|
else if(result <= 0)
|
|
|
|
return false;
|
|
|
|
blen += result;
|
2007-11-07 02:48:33 +00:00
|
|
|
}
|
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
if(newline - buffer >= len)
|
|
|
|
return false;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
len = newline - buffer;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
memcpy(line, buffer, len);
|
|
|
|
line[len] = 0;
|
|
|
|
memmove(buffer, newline + 1, blen - len - 1);
|
|
|
|
blen -= len + 1;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
2007-11-07 02:48:33 +00:00
|
|
|
|
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
|
|
|
bool recvdata(int fd, char *data, size_t len) {
|
|
|
|
if(len == -1)
|
|
|
|
len = blen;
|
|
|
|
|
2011-05-22 12:17:30 +00:00
|
|
|
while(blen < len) {
|
|
|
|
int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
|
|
|
|
if(result == -1 && errno == EINTR)
|
|
|
|
continue;
|
|
|
|
else if(result <= 0)
|
|
|
|
return false;
|
|
|
|
blen += result;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(data, buffer, len);
|
|
|
|
memmove(buffer, buffer + len, blen - len);
|
|
|
|
blen -= len;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-05-15 11:16:48 +00:00
|
|
|
bool sendline(int fd, char *format, ...) {
|
2009-11-07 22:43:25 +00:00
|
|
|
static char buffer[4096];
|
|
|
|
char *p = buffer;
|
2011-07-13 20:52:52 +00:00
|
|
|
int blen = 0;
|
2009-11-07 22:43:25 +00:00
|
|
|
va_list ap;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
va_start(ap, format);
|
|
|
|
blen = vsnprintf(buffer, sizeof buffer, format, ap);
|
|
|
|
va_end(ap);
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2011-07-13 20:52:52 +00:00
|
|
|
if(blen < 1 || blen >= sizeof buffer)
|
2009-11-07 22:43:25 +00:00
|
|
|
return false;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
buffer[blen] = '\n';
|
|
|
|
blen++;
|
2007-11-07 02:48:33 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
while(blen) {
|
2014-03-09 14:32:10 +00:00
|
|
|
int result = send(fd, p, blen, MSG_NOSIGNAL);
|
2009-11-07 22:43:25 +00:00
|
|
|
if(result == -1 && errno == EINTR)
|
|
|
|
continue;
|
2011-07-13 20:52:52 +00:00
|
|
|
else if(result <= 0)
|
2009-11-07 22:43:25 +00:00
|
|
|
return false;
|
|
|
|
p += result;
|
|
|
|
blen -= result;
|
2007-11-07 02:48:33 +00:00
|
|
|
}
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
return true;
|
2007-11-07 02:48:33 +00:00
|
|
|
}
|
|
|
|
|
2012-02-26 17:37:36 +00:00
|
|
|
static void pcap(int fd, FILE *out, int snaplen) {
|
|
|
|
sendline(fd, "%d %d %d", CONTROL, REQ_PCAP, snaplen);
|
2011-05-22 12:17:30 +00:00
|
|
|
char data[9018];
|
|
|
|
|
|
|
|
struct {
|
|
|
|
uint32_t magic;
|
|
|
|
uint16_t major;
|
|
|
|
uint16_t minor;
|
|
|
|
uint32_t tz_offset;
|
|
|
|
uint32_t tz_accuracy;
|
|
|
|
uint32_t snaplen;
|
|
|
|
uint32_t ll_type;
|
|
|
|
} header = {
|
|
|
|
0xa1b2c3d4,
|
|
|
|
2, 4,
|
|
|
|
0, 0,
|
2012-02-26 17:37:36 +00:00
|
|
|
snaplen ?: sizeof data,
|
2011-05-22 12:17:30 +00:00
|
|
|
1,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct {
|
|
|
|
uint32_t tv_sec;
|
|
|
|
uint32_t tv_usec;
|
|
|
|
uint32_t len;
|
|
|
|
uint32_t origlen;
|
|
|
|
} packet;
|
|
|
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
|
|
|
fwrite(&header, sizeof header, 1, out);
|
|
|
|
fflush(out);
|
|
|
|
|
|
|
|
char line[32];
|
|
|
|
while(recvline(fd, line, sizeof line)) {
|
|
|
|
int code, req, len;
|
|
|
|
int n = sscanf(line, "%d %d %d", &code, &req, &len);
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
if(n != 3 || code != CONTROL || req != REQ_PCAP || len < 0 || len > sizeof data)
|
|
|
|
break;
|
|
|
|
if(!recvdata(fd, data, len))
|
|
|
|
break;
|
|
|
|
packet.tv_sec = tv.tv_sec;
|
|
|
|
packet.tv_usec = tv.tv_usec;
|
|
|
|
packet.len = len;
|
|
|
|
packet.origlen = len;
|
|
|
|
fwrite(&packet, sizeof packet, 1, out);
|
|
|
|
fwrite(data, len, 1, out);
|
|
|
|
fflush(out);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-26 17:37:36 +00:00
|
|
|
static void logcontrol(int fd, FILE *out, int level) {
|
|
|
|
sendline(fd, "%d %d %d", CONTROL, REQ_LOG, level);
|
|
|
|
char data[1024];
|
|
|
|
char line[32];
|
|
|
|
|
|
|
|
while(recvline(fd, line, sizeof line)) {
|
|
|
|
int code, req, len;
|
|
|
|
int n = sscanf(line, "%d %d %d", &code, &req, &len);
|
|
|
|
if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || len > sizeof data)
|
|
|
|
break;
|
|
|
|
if(!recvdata(fd, data, len))
|
|
|
|
break;
|
|
|
|
fwrite(data, len, 1, out);
|
|
|
|
fputc('\n', out);
|
|
|
|
fflush(out);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-17 17:23:52 +00:00
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
static bool remove_service(void) {
|
|
|
|
SC_HANDLE manager = NULL;
|
|
|
|
SC_HANDLE service = NULL;
|
|
|
|
SERVICE_STATUS status = {0};
|
|
|
|
|
|
|
|
manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|
|
|
if(!manager) {
|
|
|
|
fprintf(stderr, "Could not open service manager: %s\n", winerror(GetLastError()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
service = OpenService(manager, identname, SERVICE_ALL_ACCESS);
|
|
|
|
|
|
|
|
if(!service) {
|
|
|
|
fprintf(stderr, "Could not open %s service: %s\n", identname, winerror(GetLastError()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!ControlService(service, SERVICE_CONTROL_STOP, &status))
|
|
|
|
fprintf(stderr, "Could not stop %s service: %s\n", identname, winerror(GetLastError()));
|
|
|
|
else
|
2011-07-17 18:06:06 +00:00
|
|
|
fprintf(stderr, "%s service stopped\n", identname);
|
2011-07-17 17:23:52 +00:00
|
|
|
|
|
|
|
if(!DeleteService(service)) {
|
|
|
|
fprintf(stderr, "Could not remove %s service: %s\n", identname, winerror(GetLastError()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s service removed\n", identname);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
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
|
|
|
bool connect_tincd(bool verbose) {
|
2012-09-28 15:36:25 +00:00
|
|
|
if(fd >= 0) {
|
|
|
|
fd_set r;
|
|
|
|
FD_ZERO(&r);
|
|
|
|
FD_SET(fd, &r);
|
|
|
|
struct timeval tv = {0, 0};
|
|
|
|
if(select(fd + 1, &r, NULL, NULL, &tv)) {
|
|
|
|
fprintf(stderr, "Previous connection to tincd lost, reconnecting.\n");
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2007-11-08 19:18:44 +00:00
|
|
|
|
2011-06-25 19:21:36 +00:00
|
|
|
FILE *f = fopen(pidfilename, "r");
|
2009-11-07 22:43:25 +00:00
|
|
|
if(!f) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno));
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2009-11-07 22:43:25 +00:00
|
|
|
}
|
2012-07-15 13:46:16 +00:00
|
|
|
|
2012-11-10 22:09:31 +00:00
|
|
|
char host[129];
|
|
|
|
char port[129];
|
2012-07-15 13:46:16 +00:00
|
|
|
|
2011-06-25 19:35:27 +00:00
|
|
|
if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Could not parse pid file %s\n", pidfilename);
|
|
|
|
fclose(f);
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2009-11-07 22:43:25 +00:00
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
fclose(f);
|
|
|
|
|
2013-01-17 17:12:55 +00:00
|
|
|
#ifndef HAVE_MINGW
|
|
|
|
struct sockaddr_un sa;
|
|
|
|
sa.sun_family = AF_UNIX;
|
|
|
|
strncpy(sa.sun_path, unixsocketname, sizeof sa.sun_path);
|
|
|
|
|
|
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
if(fd < 0) {
|
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot create UNIX socket: %s\n", sockstrerror(sockerrno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(connect(fd, (struct sockaddr *)&sa, sizeof sa) < 0) {
|
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot connect to UNIX socket %s: %s\n", unixsocketname, sockstrerror(sockerrno));
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#else
|
2011-05-29 12:41:05 +00:00
|
|
|
struct addrinfo hints = {
|
|
|
|
.ai_family = AF_UNSPEC,
|
|
|
|
.ai_socktype = SOCK_STREAM,
|
|
|
|
.ai_protocol = IPPROTO_TCP,
|
|
|
|
.ai_flags = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct addrinfo *res = NULL;
|
2009-11-05 22:29:28 +00:00
|
|
|
|
2011-05-29 12:41:05 +00:00
|
|
|
if(getaddrinfo(host, port, &hints, &res) || !res) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno));
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2011-05-29 12:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
2009-11-05 22:29:28 +00:00
|
|
|
if(fd < 0) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno));
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2009-11-05 22:29:28 +00:00
|
|
|
}
|
|
|
|
|
2009-12-11 21:24:07 +00:00
|
|
|
#ifdef HAVE_MINGW
|
2009-11-05 22:29:28 +00:00
|
|
|
unsigned long arg = 0;
|
|
|
|
|
|
|
|
if(ioctlsocket(fd, FIONBIO, &arg) != 0) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno));
|
2009-11-05 22:29:28 +00:00
|
|
|
}
|
2009-12-11 21:24:07 +00:00
|
|
|
#endif
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2011-05-29 12:41:05 +00:00
|
|
|
if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno));
|
2012-09-28 15:36:25 +00:00
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2007-05-18 16:52:34 +00:00
|
|
|
}
|
|
|
|
|
2011-05-29 12:41:05 +00:00
|
|
|
freeaddrinfo(res);
|
2013-01-17 17:12:55 +00:00
|
|
|
#endif
|
2011-05-29 12:41:05 +00:00
|
|
|
|
2014-03-09 14:32:10 +00:00
|
|
|
#ifdef SO_NOSIGPIPE
|
|
|
|
static const int one = 1;
|
|
|
|
setsockopt(c, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof one);
|
|
|
|
#endif
|
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
char data[4096];
|
2012-07-15 13:46:16 +00:00
|
|
|
int version;
|
2009-11-07 22:43:25 +00:00
|
|
|
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno));
|
2012-09-28 15:36:25 +00:00
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2007-11-07 02:48:33 +00:00
|
|
|
}
|
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
|
2012-10-10 15:17:49 +00:00
|
|
|
|
2009-11-07 22:43:25 +00:00
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Could not fully establish control socket connection\n");
|
2012-09-28 15:36:25 +00:00
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
2012-07-15 13:46:16 +00:00
|
|
|
return false;
|
2007-11-07 02:48:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int cmd_start(int argc, char *argv[]) {
|
2012-08-03 12:15:50 +00:00
|
|
|
if(connect_tincd(false)) {
|
|
|
|
if(netname)
|
|
|
|
fprintf(stderr, "A tincd is already running for net `%s' with pid %d.\n", netname, pid);
|
|
|
|
else
|
|
|
|
fprintf(stderr, "A tincd is already running with pid %d.\n", pid);
|
|
|
|
return 0;
|
|
|
|
}
|
2012-07-15 19:15:35 +00:00
|
|
|
|
2012-08-03 12:15:50 +00:00
|
|
|
char *c;
|
|
|
|
char *slash = strrchr(program_name, '/');
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
#ifdef HAVE_MINGW
|
2012-08-03 12:15:50 +00:00
|
|
|
if ((c = strrchr(program_name, '\\')) > slash)
|
2012-07-15 13:46:16 +00:00
|
|
|
slash = c;
|
|
|
|
#endif
|
|
|
|
|
2012-07-21 11:53:22 +00:00
|
|
|
if (slash++)
|
2012-08-03 12:15:50 +00:00
|
|
|
xasprintf(&c, "%.*stincd", (int)(slash - program_name), program_name);
|
2012-07-15 13:46:16 +00:00
|
|
|
else
|
|
|
|
c = "tincd";
|
|
|
|
|
2012-08-03 12:15:50 +00:00
|
|
|
int nargc = 0;
|
2013-05-01 15:31:33 +00:00
|
|
|
char **nargv = xzalloc((optind + argc) * sizeof *nargv);
|
2012-07-15 13:46:16 +00:00
|
|
|
|
2012-10-06 15:45:03 +00:00
|
|
|
nargv[nargc++] = c;
|
|
|
|
for(int i = 1; i < optind; i++)
|
2012-08-03 12:15:50 +00:00
|
|
|
nargv[nargc++] = orig_argv[i];
|
|
|
|
for(int i = 1; i < argc; i++)
|
|
|
|
nargv[nargc++] = argv[i];
|
2007-05-18 16:52:34 +00:00
|
|
|
|
2012-08-03 12:15:50 +00:00
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
execvp(c, nargv);
|
|
|
|
fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno));
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2012-08-03 12:15:50 +00:00
|
|
|
#else
|
|
|
|
pid_t pid = fork();
|
|
|
|
if(pid == -1) {
|
|
|
|
fprintf(stderr, "Could not fork: %s\n", strerror(errno));
|
2012-10-07 15:53:23 +00:00
|
|
|
free(nargv);
|
2012-08-03 12:15:50 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!pid)
|
|
|
|
exit(execvp(c, nargv));
|
2012-10-10 15:17:49 +00:00
|
|
|
|
2012-10-07 15:53:23 +00:00
|
|
|
free(nargv);
|
|
|
|
|
2013-07-25 15:14:07 +00:00
|
|
|
int status = -1, result;
|
|
|
|
#ifdef SIGINT
|
|
|
|
signal(SIGINT, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
result = waitpid(pid, &status, 0);
|
|
|
|
#ifdef SIGINT
|
|
|
|
signal(SIGINT, SIG_DFL);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(result != pid || !WIFEXITED(status) || WEXITSTATUS(status)) {
|
2012-08-03 12:15:50 +00:00
|
|
|
fprintf(stderr, "Error starting %s\n", c);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
#endif
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_stop(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-07-17 17:23:52 +00:00
|
|
|
#ifndef HAVE_MINGW
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true)) {
|
2012-08-01 13:14:48 +00:00
|
|
|
if(pid) {
|
2012-10-06 15:45:03 +00:00
|
|
|
if(kill(pid, SIGTERM)) {
|
|
|
|
fprintf(stderr, "Could not send TERM signal to process with PID %u: %s\n", pid, strerror(errno));
|
2012-08-01 13:14:48 +00:00
|
|
|
return 1;
|
2012-10-06 15:45:03 +00:00
|
|
|
}
|
|
|
|
|
2012-08-01 13:14:48 +00:00
|
|
|
fprintf(stderr, "Sent TERM signal to process with PID %u.\n", pid);
|
2012-10-06 15:45:03 +00:00
|
|
|
waitpid(pid, NULL, 0);
|
2012-08-01 13:14:48 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2012-08-01 13:14:48 +00:00
|
|
|
}
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_STOP);
|
2012-10-06 15:45:03 +00:00
|
|
|
|
2013-02-08 15:44:50 +00:00
|
|
|
while(recvline(fd, line, sizeof line)) {
|
|
|
|
// Wait for tincd to close the connection...
|
|
|
|
}
|
2011-07-17 17:23:52 +00:00
|
|
|
#else
|
2012-07-15 13:46:16 +00:00
|
|
|
if(!remove_service())
|
|
|
|
return 1;
|
2011-07-17 17:23:52 +00:00
|
|
|
#endif
|
2012-08-03 12:15:50 +00:00
|
|
|
close(fd);
|
|
|
|
pid = 0;
|
|
|
|
fd = -1;
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2007-05-19 15:21:26 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static int cmd_restart(int argc, char *argv[]) {
|
2013-07-20 22:20:54 +00:00
|
|
|
cmd_stop(1, argv);
|
2012-08-01 13:15:37 +00:00
|
|
|
return cmd_start(argc, argv);
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
2009-11-07 22:43:25 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static int cmd_reload(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) {
|
|
|
|
fprintf(stderr, "Could not reload configuration.\n");
|
|
|
|
return 1;
|
2009-11-07 22:43:25 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_dump(int argc, char *argv[]) {
|
2012-12-03 12:08:03 +00:00
|
|
|
bool only_reachable = false;
|
|
|
|
|
|
|
|
if(argc > 2 && !strcasecmp(argv[1], "reachable")) {
|
|
|
|
if(strcasecmp(argv[2], "nodes")) {
|
|
|
|
fprintf(stderr, "`reachable' only supported for nodes.\n");
|
|
|
|
usage(true);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
only_reachable = true;
|
|
|
|
argv++;
|
|
|
|
argc--;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
usage(true);
|
|
|
|
return 1;
|
2007-05-19 15:21:26 +00:00
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 20:53:03 +00:00
|
|
|
return 1;
|
|
|
|
|
2012-09-26 21:52:36 +00:00
|
|
|
int do_graph = 0;
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
if(!strcasecmp(argv[1], "nodes"))
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
|
|
|
|
else if(!strcasecmp(argv[1], "edges"))
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
|
|
|
|
else if(!strcasecmp(argv[1], "subnets"))
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
|
|
|
|
else if(!strcasecmp(argv[1], "connections"))
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
|
|
|
|
else if(!strcasecmp(argv[1], "graph")) {
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
|
2012-09-26 21:52:36 +00:00
|
|
|
do_graph = 1;
|
|
|
|
} else if(!strcasecmp(argv[1], "digraph")) {
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
|
|
|
|
do_graph = 2;
|
2012-07-15 13:46:16 +00:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Unknown dump type '%s'.\n", argv[1]);
|
|
|
|
usage(true);
|
|
|
|
return 1;
|
|
|
|
}
|
2007-11-07 02:49:25 +00:00
|
|
|
|
2012-09-26 21:52:36 +00:00
|
|
|
if(do_graph == 1)
|
|
|
|
printf("graph {\n");
|
|
|
|
else if(do_graph == 2)
|
2012-07-15 13:46:16 +00:00
|
|
|
printf("digraph {\n");
|
2007-11-07 02:49:25 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
while(recvline(fd, line, sizeof line)) {
|
|
|
|
char node1[4096], node2[4096];
|
2012-09-26 21:18:32 +00:00
|
|
|
int n = sscanf(line, "%d %d %s %s", &code, &req, node1, node2);
|
2012-07-15 13:46:16 +00:00
|
|
|
if(n == 2) {
|
|
|
|
if(do_graph && req == REQ_DUMP_NODES)
|
|
|
|
continue;
|
2009-11-07 22:43:25 +00:00
|
|
|
else {
|
2012-07-15 13:46:16 +00:00
|
|
|
if(do_graph)
|
|
|
|
printf("}\n");
|
|
|
|
return 0;
|
2009-11-07 22:43:25 +00:00
|
|
|
}
|
2007-11-07 02:49:25 +00:00
|
|
|
}
|
2012-07-15 13:46:16 +00:00
|
|
|
if(n < 2)
|
|
|
|
break;
|
|
|
|
|
2012-10-14 14:07:35 +00:00
|
|
|
char node[4096];
|
|
|
|
char from[4096];
|
|
|
|
char to[4096];
|
|
|
|
char subnet[4096];
|
|
|
|
char host[4096];
|
|
|
|
char port[4096];
|
|
|
|
char via[4096];
|
|
|
|
char nexthop[4096];
|
|
|
|
int cipher, digest, maclength, compression, distance, socket, weight;
|
|
|
|
short int pmtu, minmtu, maxmtu;
|
|
|
|
unsigned int options, status_int;
|
|
|
|
node_status_t status;
|
|
|
|
long int last_state_change;
|
|
|
|
|
|
|
|
switch(req) {
|
|
|
|
case REQ_DUMP_NODES: {
|
|
|
|
int n = sscanf(line, "%*d %*d %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", node, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
|
|
|
|
if(n != 16) {
|
|
|
|
fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line);
|
|
|
|
return 1;
|
|
|
|
}
|
2012-12-03 12:08:03 +00:00
|
|
|
|
|
|
|
memcpy(&status, &status_int, sizeof status);
|
|
|
|
|
2012-10-14 14:07:35 +00:00
|
|
|
if(do_graph) {
|
|
|
|
const char *color = "black";
|
|
|
|
if(!strcmp(host, "MYSELF"))
|
|
|
|
color = "green";
|
|
|
|
else if(!status.reachable)
|
|
|
|
color = "red";
|
|
|
|
else if(strcmp(via, node))
|
|
|
|
color = "orange";
|
|
|
|
else if(!status.validkey)
|
|
|
|
color = "black";
|
|
|
|
else if(minmtu > 0)
|
|
|
|
color = "green";
|
|
|
|
printf(" %s [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\"");
|
|
|
|
} else {
|
2012-12-03 12:08:03 +00:00
|
|
|
if(only_reachable && !status.reachable)
|
|
|
|
continue;
|
2012-09-26 21:18:32 +00:00
|
|
|
printf("%s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %hd (min %hd max %hd)\n",
|
2012-10-14 14:07:35 +00:00
|
|
|
node, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case REQ_DUMP_EDGES: {
|
|
|
|
int n = sscanf(line, "%*d %*d %s %s %s port %s %x %d", from, to, host, port, &options, &weight);
|
|
|
|
if(n != 6) {
|
|
|
|
fprintf(stderr, "Unable to parse edge dump from tincd.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(do_graph) {
|
|
|
|
float w = 1 + 65536.0 / weight;
|
|
|
|
if(do_graph == 1 && strcmp(node1, node2) > 0)
|
|
|
|
printf(" %s -- %s [w = %f, weight = %f];\n", node1, node2, w, w);
|
|
|
|
else if(do_graph == 2)
|
|
|
|
printf(" %s -> %s [w = %f, weight = %f];\n", node1, node2, w, w);
|
|
|
|
} else {
|
2012-09-26 21:18:32 +00:00
|
|
|
printf("%s to %s at %s port %s options %x weight %d\n", from, to, host, port, options, weight);
|
2012-10-14 14:07:35 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case REQ_DUMP_SUBNETS: {
|
|
|
|
int n = sscanf(line, "%*d %*d %s %s", subnet, node);
|
|
|
|
if(n != 2) {
|
|
|
|
fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
|
2012-09-26 21:18:32 +00:00
|
|
|
return 1;
|
2012-10-14 14:07:35 +00:00
|
|
|
}
|
|
|
|
printf("%s owner %s\n", strip_weight(subnet), node);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case REQ_DUMP_CONNECTIONS: {
|
|
|
|
int n = sscanf(line, "%*d %*d %s %s port %s %x %d %x", node, host, port, &options, &socket, &status_int);
|
|
|
|
if(n != 6) {
|
|
|
|
fprintf(stderr, "Unable to parse connection dump from tincd.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
printf("%s at %s port %s options %x socket %d status %x\n", node, host, port, options, socket, status_int);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unable to parse dump from tincd.\n");
|
|
|
|
return 1;
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "Error receiving dump.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_purge(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2007-11-07 02:49:25 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_PURGE);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) {
|
|
|
|
fprintf(stderr, "Could not purge old information.\n");
|
2007-11-07 02:49:25 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_debug(int argc, char *argv[]) {
|
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
2007-11-07 02:49:57 +00:00
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2007-11-07 02:50:27 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
int debuglevel = atoi(argv[1]);
|
|
|
|
int origlevel;
|
2007-11-07 02:50:27 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &origlevel) != 3 || code != CONTROL || req != REQ_SET_DEBUG) {
|
|
|
|
fprintf(stderr, "Could not set debug level.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
2007-11-07 02:50:58 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
fprintf(stderr, "Old level %d, new level %d.\n", origlevel, debuglevel);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_retry(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_RETRY);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) {
|
|
|
|
fprintf(stderr, "Could not retry outgoing connections.\n");
|
|
|
|
return 1;
|
2007-11-07 02:51:24 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_connect(int argc, char *argv[]) {
|
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-30 12:00:34 +00:00
|
|
|
if(!check_id(argv[1])) {
|
2012-07-15 18:22:21 +00:00
|
|
|
fprintf(stderr, "Invalid name for node.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_CONNECT || result) {
|
|
|
|
fprintf(stderr, "Could not connect to %s.\n", argv[1]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_disconnect(int argc, char *argv[]) {
|
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-30 12:00:34 +00:00
|
|
|
if(!check_id(argv[1])) {
|
2012-07-15 18:22:21 +00:00
|
|
|
fprintf(stderr, "Invalid name for node.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]);
|
|
|
|
if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_DISCONNECT || result) {
|
|
|
|
fprintf(stderr, "Could not disconnect %s.\n", argv[1]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_top(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
#ifdef HAVE_CURSES
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
top(fd);
|
|
|
|
return 0;
|
|
|
|
#else
|
2013-03-08 15:22:56 +00:00
|
|
|
fprintf(stderr, "This version of tinc was compiled without support for the curses library.\n");
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_pcap(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-30 15:38:48 +00:00
|
|
|
#ifdef SIGINT
|
|
|
|
static void sigint_handler(int sig) {
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
shutdown(fd, SHUT_RDWR);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static int cmd_log(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
2013-05-30 15:38:48 +00:00
|
|
|
#ifdef SIGINT
|
|
|
|
signal(SIGINT, sigint_handler);
|
|
|
|
#endif
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1);
|
2013-05-30 15:38:48 +00:00
|
|
|
|
|
|
|
#ifdef SIGINT
|
|
|
|
signal(SIGINT, SIG_DFL);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
2012-07-15 13:46:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_pid(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true) && !pid)
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
printf("%d\n", pid);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
int rstrip(char *value) {
|
2012-07-15 16:16:35 +00:00
|
|
|
int len = strlen(value);
|
|
|
|
while(len && strchr("\t\r\n ", value[len - 1]))
|
|
|
|
value[--len] = 0;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
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 *get_my_name(bool verbose) {
|
2012-07-15 16:16:35 +00:00
|
|
|
FILE *f = fopen(tinc_conf, "r");
|
|
|
|
if(!f) {
|
2013-03-08 10:24:37 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno));
|
2012-07-15 16:16:35 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buf[4096];
|
|
|
|
char *value;
|
|
|
|
while(fgets(buf, sizeof buf, f)) {
|
|
|
|
int len = strcspn(buf, "\t =");
|
|
|
|
value = buf + len;
|
|
|
|
value += strspn(value, "\t ");
|
|
|
|
if(*value == '=') {
|
|
|
|
value++;
|
|
|
|
value += strspn(value, "\t ");
|
|
|
|
}
|
|
|
|
if(!rstrip(value))
|
|
|
|
continue;
|
|
|
|
buf[len] = 0;
|
|
|
|
if(strcasecmp(buf, "Name"))
|
|
|
|
continue;
|
|
|
|
if(*value) {
|
|
|
|
fclose(f);
|
|
|
|
return strdup(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(f);
|
2013-03-08 10:24:37 +00:00
|
|
|
if(verbose)
|
|
|
|
fprintf(stderr, "Could not find Name in %s.\n", tinc_conf);
|
2012-07-15 16:16:35 +00:00
|
|
|
return 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
|
|
|
const var_t variables[] = {
|
2012-07-21 13:02:17 +00:00
|
|
|
/* Server configuration */
|
|
|
|
{"AddressFamily", VAR_SERVER},
|
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
|
|
|
{"AutoConnect", VAR_SERVER | VAR_SAFE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"BindToAddress", VAR_SERVER | VAR_MULTIPLE},
|
|
|
|
{"BindToInterface", VAR_SERVER},
|
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
|
|
|
{"Broadcast", VAR_SERVER | VAR_SAFE},
|
|
|
|
{"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"DecrementTTL", VAR_SERVER},
|
|
|
|
{"Device", VAR_SERVER},
|
|
|
|
{"DeviceType", VAR_SERVER},
|
|
|
|
{"DirectOnly", VAR_SERVER},
|
2014-05-18 18:47:04 +00:00
|
|
|
{"Ed25519PrivateKeyFile", VAR_SERVER},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"ExperimentalProtocol", VAR_SERVER},
|
|
|
|
{"Forwarding", VAR_SERVER},
|
2012-10-07 15:53:23 +00:00
|
|
|
{"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"Hostnames", VAR_SERVER},
|
|
|
|
{"IffOneQueue", VAR_SERVER},
|
|
|
|
{"Interface", VAR_SERVER},
|
|
|
|
{"KeyExpire", VAR_SERVER},
|
2014-01-20 20:19:13 +00:00
|
|
|
{"ListenAddress", VAR_SERVER | VAR_MULTIPLE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"LocalDiscovery", VAR_SERVER},
|
|
|
|
{"MACExpire", VAR_SERVER},
|
2013-07-11 21:38:38 +00:00
|
|
|
{"MaxConnectionBurst", VAR_SERVER},
|
2012-08-01 14:13:23 +00:00
|
|
|
{"MaxOutputBufferSize", VAR_SERVER},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"MaxTimeout", VAR_SERVER},
|
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
|
|
|
{"Mode", VAR_SERVER | VAR_SAFE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"Name", VAR_SERVER},
|
|
|
|
{"PingInterval", VAR_SERVER},
|
|
|
|
{"PingTimeout", VAR_SERVER},
|
|
|
|
{"PriorityInheritance", VAR_SERVER},
|
|
|
|
{"PrivateKey", VAR_SERVER | VAR_OBSOLETE},
|
|
|
|
{"PrivateKeyFile", VAR_SERVER},
|
|
|
|
{"ProcessPriority", VAR_SERVER},
|
2012-08-01 14:13:23 +00:00
|
|
|
{"Proxy", VAR_SERVER},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"ReplayWindow", VAR_SERVER},
|
2012-10-07 15:53:23 +00:00
|
|
|
{"ScriptsExtension", VAR_SERVER},
|
|
|
|
{"ScriptsInterpreter", VAR_SERVER},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"StrictSubnets", VAR_SERVER},
|
|
|
|
{"TunnelServer", VAR_SERVER},
|
|
|
|
{"UDPRcvBuf", VAR_SERVER},
|
|
|
|
{"UDPSndBuf", VAR_SERVER},
|
2012-08-01 14:13:23 +00:00
|
|
|
{"VDEGroup", VAR_SERVER},
|
|
|
|
{"VDEPort", VAR_SERVER},
|
2012-07-21 13:02:17 +00:00
|
|
|
/* Host configuration */
|
|
|
|
{"Address", VAR_HOST | VAR_MULTIPLE},
|
|
|
|
{"Cipher", VAR_SERVER | VAR_HOST},
|
|
|
|
{"ClampMSS", VAR_SERVER | VAR_HOST},
|
|
|
|
{"Compression", VAR_SERVER | VAR_HOST},
|
|
|
|
{"Digest", VAR_SERVER | VAR_HOST},
|
2014-05-18 18:47:04 +00:00
|
|
|
{"Ed25519PublicKey", VAR_HOST},
|
|
|
|
{"Ed25519PublicKeyFile", VAR_SERVER | VAR_HOST},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"IndirectData", VAR_SERVER | VAR_HOST},
|
|
|
|
{"MACLength", VAR_SERVER | VAR_HOST},
|
|
|
|
{"PMTU", VAR_SERVER | VAR_HOST},
|
|
|
|
{"PMTUDiscovery", VAR_SERVER | VAR_HOST},
|
|
|
|
{"Port", VAR_HOST},
|
2012-08-01 14:13:23 +00:00
|
|
|
{"PublicKey", VAR_HOST | VAR_OBSOLETE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE},
|
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
|
|
|
{"Subnet", VAR_HOST | VAR_MULTIPLE | VAR_SAFE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{"TCPOnly", VAR_SERVER | VAR_HOST},
|
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
|
|
|
{"Weight", VAR_HOST | VAR_SAFE},
|
2012-07-21 13:02:17 +00:00
|
|
|
{NULL, 0}
|
2012-07-15 16:16:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int cmd_config(int argc, char *argv[]) {
|
|
|
|
if(argc < 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-08 10:40:40 +00:00
|
|
|
if(strcasecmp(argv[0], "config"))
|
|
|
|
argv--, argc++;
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
int action = -2;
|
|
|
|
if(!strcasecmp(argv[1], "get")) {
|
|
|
|
argv++, argc--;
|
|
|
|
} else if(!strcasecmp(argv[1], "add")) {
|
2012-07-15 16:16:35 +00:00
|
|
|
argv++, argc--, action = 1;
|
|
|
|
} else if(!strcasecmp(argv[1], "del")) {
|
|
|
|
argv++, argc--, action = -1;
|
|
|
|
} else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) {
|
|
|
|
argv++, argc--, action = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(argc < 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Concatenate the rest of the command line
|
|
|
|
strncpy(line, argv[1], sizeof line - 1);
|
|
|
|
for(int i = 2; i < argc; i++) {
|
|
|
|
strncat(line, " ", sizeof line - 1 - strlen(line));
|
|
|
|
strncat(line, argv[i], sizeof line - 1 - strlen(line));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Liberal parsing into node name, variable name and value.
|
|
|
|
char *node = NULL;
|
|
|
|
char *variable;
|
|
|
|
char *value;
|
|
|
|
int len;
|
|
|
|
|
2012-10-10 15:17:49 +00:00
|
|
|
len = strcspn(line, "\t =");
|
|
|
|
value = line + len;
|
|
|
|
value += strspn(value, "\t ");
|
|
|
|
if(*value == '=') {
|
|
|
|
value++;
|
|
|
|
value += strspn(value, "\t ");
|
|
|
|
}
|
|
|
|
line[len] = '\0';
|
2012-07-15 16:16:35 +00:00
|
|
|
variable = strchr(line, '.');
|
|
|
|
if(variable) {
|
|
|
|
node = line;
|
|
|
|
*variable++ = 0;
|
|
|
|
} else {
|
|
|
|
variable = line;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!*variable) {
|
|
|
|
fprintf(stderr, "No variable given.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(action >= 0 && !*value) {
|
|
|
|
fprintf(stderr, "No value for variable given.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
if(action < -1 && *value)
|
|
|
|
action = 0;
|
|
|
|
|
2012-07-21 13:02:17 +00:00
|
|
|
/* Some simple checks. */
|
|
|
|
bool found = false;
|
2013-07-25 14:21:11 +00:00
|
|
|
bool warnonremove = false;
|
2012-07-21 13:02:17 +00:00
|
|
|
|
|
|
|
for(int i = 0; variables[i].name; i++) {
|
|
|
|
if(strcasecmp(variables[i].name, variable))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
found = true;
|
|
|
|
variable = (char *)variables[i].name;
|
|
|
|
|
|
|
|
/* Discourage use of obsolete variables. */
|
|
|
|
|
|
|
|
if(variables[i].type & VAR_OBSOLETE && action >= 0) {
|
|
|
|
if(force) {
|
|
|
|
fprintf(stderr, "Warning: %s is an obsolete variable!\n", variable);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s is an obsolete variable! Use --force to use it anyway.\n", variable);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Don't put server variables in host config files */
|
|
|
|
|
|
|
|
if(node && !(variables[i].type & VAR_HOST) && action >= 0) {
|
|
|
|
if(force) {
|
|
|
|
fprintf(stderr, "Warning: %s is not a host configuration variable!\n", variable);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s is not a host configuration variable! Use --force to use it anyway.\n", variable);
|
|
|
|
return 1;
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-21 13:02:17 +00:00
|
|
|
|
|
|
|
/* Should this go into our own host config file? */
|
|
|
|
|
|
|
|
if(!node && !(variables[i].type & VAR_SERVER)) {
|
2013-03-08 10:24:37 +00:00
|
|
|
node = get_my_name(true);
|
2012-07-21 13:02:17 +00:00
|
|
|
if(!node)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-07-25 14:21:11 +00:00
|
|
|
/* Change "add" into "set" for variables that do not allow multiple occurences.
|
|
|
|
Turn on warnings when it seems variables might be removed unintentionally. */
|
|
|
|
|
|
|
|
if(action == 1 && !(variables[i].type & VAR_MULTIPLE)) {
|
|
|
|
warnonremove = true;
|
|
|
|
action = 0;
|
|
|
|
} else if(action == 0 && (variables[i].type & VAR_MULTIPLE)) {
|
|
|
|
warnonremove = true;
|
|
|
|
}
|
|
|
|
|
2012-07-21 13:02:17 +00:00
|
|
|
break;
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 18:22:21 +00:00
|
|
|
if(node && !check_id(node)) {
|
|
|
|
fprintf(stderr, "Invalid name for node.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
if(!found) {
|
|
|
|
if(force || action < 0) {
|
2012-07-21 13:02:17 +00:00
|
|
|
fprintf(stderr, "Warning: %s is not a known configuration variable!\n", variable);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s: is not a known configuration variable! Use --force to use it anyway.\n", variable);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-15 16:16:35 +00:00
|
|
|
// Open the right configuration file.
|
|
|
|
char *filename;
|
|
|
|
if(node)
|
2012-07-21 14:26:55 +00:00
|
|
|
xasprintf(&filename, "%s" SLASH "%s", hosts_dir, node);
|
2012-07-15 16:16:35 +00:00
|
|
|
else
|
|
|
|
filename = tinc_conf;
|
|
|
|
|
|
|
|
FILE *f = fopen(filename, "r");
|
|
|
|
if(!f) {
|
2013-04-28 17:33:04 +00:00
|
|
|
fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
char *tmpfile = NULL;
|
|
|
|
FILE *tf = NULL;
|
|
|
|
|
|
|
|
if(action >= -1) {
|
|
|
|
xasprintf(&tmpfile, "%s.config.tmp", filename);
|
|
|
|
tf = fopen(tmpfile, "w");
|
|
|
|
if(!tf) {
|
|
|
|
fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno));
|
2012-08-02 15:24:42 +00:00
|
|
|
fclose(f);
|
2012-08-01 13:50:45 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
// Copy the file, making modifications on the fly, unless we are just getting a value.
|
2012-07-15 16:16:35 +00:00
|
|
|
char buf1[4096];
|
|
|
|
char buf2[4096];
|
|
|
|
bool set = false;
|
|
|
|
bool removed = false;
|
2012-08-01 13:50:45 +00:00
|
|
|
found = false;
|
2012-07-15 16:16:35 +00:00
|
|
|
|
|
|
|
while(fgets(buf1, sizeof buf1, f)) {
|
|
|
|
buf1[sizeof buf1 - 1] = 0;
|
2012-07-21 13:50:50 +00:00
|
|
|
strncpy(buf2, buf1, sizeof buf2);
|
2012-07-15 16:16:35 +00:00
|
|
|
|
|
|
|
// Parse line in a simple way
|
|
|
|
char *bvalue;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
len = strcspn(buf2, "\t =");
|
|
|
|
bvalue = buf2 + len;
|
|
|
|
bvalue += strspn(bvalue, "\t ");
|
|
|
|
if(*bvalue == '=') {
|
|
|
|
bvalue++;
|
|
|
|
bvalue += strspn(bvalue, "\t ");
|
|
|
|
}
|
|
|
|
rstrip(bvalue);
|
|
|
|
buf2[len] = '\0';
|
|
|
|
|
|
|
|
// Did it match?
|
|
|
|
if(!strcasecmp(buf2, variable)) {
|
2012-08-01 13:50:45 +00:00
|
|
|
// Get
|
|
|
|
if(action < -1) {
|
|
|
|
found = true;
|
|
|
|
printf("%s\n", bvalue);
|
2012-07-15 16:16:35 +00:00
|
|
|
// Del
|
2012-08-01 13:50:45 +00:00
|
|
|
} else if(action == -1) {
|
2012-07-15 16:16:35 +00:00
|
|
|
if(!*value || !strcasecmp(bvalue, value)) {
|
|
|
|
removed = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Set
|
|
|
|
} else if(action == 0) {
|
2013-07-25 14:21:11 +00:00
|
|
|
// Warn if "set" was used for variables that can occur multiple times
|
|
|
|
if(warnonremove && strcasecmp(bvalue, value))
|
|
|
|
fprintf(stderr, "Warning: removing %s = %s\n", variable, bvalue);
|
|
|
|
|
2012-07-15 16:16:35 +00:00
|
|
|
// Already set? Delete the rest...
|
|
|
|
if(set)
|
|
|
|
continue;
|
2013-07-25 14:21:11 +00:00
|
|
|
|
2012-07-15 16:16:35 +00:00
|
|
|
// Otherwise, replace.
|
|
|
|
if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
|
|
|
|
fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
set = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
if(action >= -1) {
|
|
|
|
// Copy original line...
|
|
|
|
if(fputs(buf1, tf) < 0) {
|
2012-07-21 13:02:44 +00:00
|
|
|
fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
2012-08-01 13:50:45 +00:00
|
|
|
|
|
|
|
// Add newline if it is missing...
|
|
|
|
if(*buf1 && buf1[strlen(buf1) - 1] != '\n') {
|
|
|
|
if(fputc('\n', tf) < 0) {
|
|
|
|
fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2012-07-21 13:02:44 +00:00
|
|
|
}
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we read everything...
|
|
|
|
if(ferror(f) || !feof(f)) {
|
|
|
|
fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fclose(f)) {
|
|
|
|
fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add new variable if necessary.
|
|
|
|
if(action > 0 || (action == 0 && !set)) {
|
|
|
|
if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
|
|
|
|
fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-01 13:50:45 +00:00
|
|
|
if(action < -1) {
|
|
|
|
if(!found)
|
|
|
|
fprintf(stderr, "No matching configuration variables found.\n");
|
2014-06-15 10:14:01 +00:00
|
|
|
return 1;
|
2012-08-01 13:50:45 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 16:16:35 +00:00
|
|
|
// Make sure we wrote everything...
|
|
|
|
if(fclose(tf)) {
|
|
|
|
fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Could we find what we had to remove?
|
|
|
|
if(action < 0 && !removed) {
|
|
|
|
remove(tmpfile);
|
|
|
|
fprintf(stderr, "No configuration variables deleted.\n");
|
2014-06-15 10:19:10 +00:00
|
|
|
return 1;
|
2012-07-15 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the configuration file with the new one
|
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
if(remove(filename)) {
|
|
|
|
fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if(rename(tmpfile, filename)) {
|
|
|
|
fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-15 18:59:17 +00:00
|
|
|
// Silently try notifying a running tincd of changes.
|
2012-08-02 15:24:42 +00:00
|
|
|
if(connect_tincd(false))
|
2012-07-15 18:59:17 +00:00
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
|
|
|
|
|
2012-07-15 16:16:35 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 18:22:21 +00:00
|
|
|
bool check_id(const char *name) {
|
2012-08-01 14:51:59 +00:00
|
|
|
if(!name || !*name)
|
|
|
|
return false;
|
|
|
|
|
2012-07-15 18:22:21 +00:00
|
|
|
for(int i = 0; i < strlen(name); i++) {
|
|
|
|
if(!isalnum(name[i]) && name[i] != '_')
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-07-26 12:17:15 +00:00
|
|
|
static bool try_bind(int port) {
|
|
|
|
struct addrinfo *ai = NULL;
|
|
|
|
struct addrinfo hint = {
|
|
|
|
.ai_flags = AI_PASSIVE,
|
|
|
|
.ai_family = AF_UNSPEC,
|
|
|
|
.ai_socktype = SOCK_STREAM,
|
|
|
|
.ai_protocol = IPPROTO_TCP,
|
|
|
|
};
|
|
|
|
|
|
|
|
char portstr[16];
|
|
|
|
snprintf(portstr, sizeof portstr, "%d", port);
|
|
|
|
|
|
|
|
if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
while(ai) {
|
|
|
|
int fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
if(!fd)
|
|
|
|
return false;
|
|
|
|
int result = bind(fd, ai->ai_addr, ai->ai_addrlen);
|
|
|
|
closesocket(fd);
|
|
|
|
if(result)
|
|
|
|
return false;
|
|
|
|
ai = ai->ai_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-07-26 12:53:36 +00:00
|
|
|
int check_port(char *name) {
|
|
|
|
if(try_bind(655))
|
|
|
|
return 655;
|
|
|
|
|
|
|
|
fprintf(stderr, "Warning: could not bind to port 655. ");
|
|
|
|
|
|
|
|
for(int i = 0; i < 100; i++) {
|
|
|
|
int port = 0x1000 + (rand() & 0x7fff);
|
|
|
|
if(try_bind(port)) {
|
|
|
|
char *filename;
|
|
|
|
xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name);
|
|
|
|
FILE *f = fopen(filename, "a");
|
|
|
|
free(filename);
|
|
|
|
if(!f) {
|
|
|
|
fprintf(stderr, "Please change tinc's Port manually.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(f, "Port = %d\n", port);
|
|
|
|
fclose(f);
|
|
|
|
fprintf(stderr, "Tinc will instead listen on port %d.\n", port);
|
|
|
|
return port;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "Please change tinc's Port manually.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static int cmd_init(int argc, char *argv[]) {
|
2012-07-15 16:16:35 +00:00
|
|
|
if(!access(tinc_conf, F_OK)) {
|
2012-07-15 13:46:16 +00:00
|
|
|
fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
} else if(argc < 2) {
|
2012-08-02 15:24:42 +00:00
|
|
|
if(tty) {
|
2012-07-15 13:46:16 +00:00
|
|
|
char buf[1024];
|
2014-01-29 16:17:59 +00:00
|
|
|
fprintf(stderr, "Enter the Name you want your tinc node to have: ");
|
2012-07-15 13:46:16 +00:00
|
|
|
if(!fgets(buf, sizeof buf, stdin)) {
|
|
|
|
fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
2012-07-15 16:16:35 +00:00
|
|
|
int len = rstrip(buf);
|
2012-07-15 13:46:16 +00:00
|
|
|
if(!len) {
|
|
|
|
fprintf(stderr, "No name given!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
name = strdup(buf);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "No Name given!\n");
|
2009-12-16 20:16:56 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2012-07-15 13:46:16 +00:00
|
|
|
} else {
|
2012-07-15 16:16:35 +00:00
|
|
|
name = strdup(argv[1]);
|
2012-07-15 13:46:16 +00:00
|
|
|
if(!*name) {
|
|
|
|
fprintf(stderr, "No Name given!\n");
|
2009-12-16 20:16:56 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-15 18:22:21 +00:00
|
|
|
if(!check_id(name)) {
|
|
|
|
fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n");
|
|
|
|
return 1;
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
2009-12-16 20:16:56 +00:00
|
|
|
|
2013-09-08 13:03:06 +00:00
|
|
|
if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
|
2013-08-02 17:27:06 +00:00
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2009-12-16 20:16:56 +00:00
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
if(mkdir(confbase, 0777) && errno != EEXIST) {
|
2012-07-15 13:46:16 +00:00
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
|
|
|
|
return 1;
|
2011-05-15 11:16:48 +00:00
|
|
|
}
|
|
|
|
|
2013-08-02 17:27:06 +00:00
|
|
|
if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
|
2012-07-15 13:46:16 +00:00
|
|
|
fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE *f = fopen(tinc_conf, "w");
|
|
|
|
if(!f) {
|
|
|
|
fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(f, "Name = %s\n", name);
|
|
|
|
fclose(f);
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
if(!rsa_keygen(2048, false) || !ed25519_keygen(false))
|
2012-07-22 10:52:31 +00:00
|
|
|
return 1;
|
2012-07-15 13:46:16 +00:00
|
|
|
|
2013-07-26 12:53:36 +00:00
|
|
|
check_port(name);
|
2013-07-26 12:17:15 +00:00
|
|
|
|
2012-07-22 10:52:31 +00:00
|
|
|
#ifndef HAVE_MINGW
|
|
|
|
char *filename;
|
|
|
|
xasprintf(&filename, "%s" SLASH "tinc-up", confbase);
|
|
|
|
if(access(filename, F_OK)) {
|
2013-08-02 17:27:06 +00:00
|
|
|
FILE *f = fopenmask(filename, "w", 0777);
|
2012-07-22 10:52:31 +00:00
|
|
|
if(!f) {
|
|
|
|
fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
2013-12-08 20:31:50 +00:00
|
|
|
fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE <your vpn IP address> netmask <netmask of whole VPN>\n");
|
2012-07-22 10:52:31 +00:00
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_generate_keys(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-08 10:24:37 +00:00
|
|
|
if(!name)
|
|
|
|
name = get_my_name(false);
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true) && ed25519_keygen(true));
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_generate_rsa_keys(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-08 10:24:37 +00:00
|
|
|
if(!name)
|
|
|
|
name = get_my_name(false);
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true);
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-08 10:24:37 +00:00
|
|
|
if(!name)
|
|
|
|
name = get_my_name(false);
|
|
|
|
|
2014-05-18 18:47:04 +00:00
|
|
|
return !ed25519_keygen(true);
|
2012-07-15 13:46:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_help(int argc, char *argv[]) {
|
|
|
|
usage(false);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_version(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
version();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 23:05:25 +00:00
|
|
|
static int cmd_info(int argc, char *argv[]) {
|
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(!connect_tincd(true))
|
2012-07-15 23:05:25 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
return info(fd, argv[1]);
|
|
|
|
}
|
|
|
|
|
2012-07-15 18:37:38 +00:00
|
|
|
static const char *conffiles[] = {
|
|
|
|
"tinc.conf",
|
|
|
|
"tinc-up",
|
|
|
|
"tinc-down",
|
|
|
|
"subnet-up",
|
|
|
|
"subnet-down",
|
|
|
|
"host-up",
|
|
|
|
"host-down",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int cmd_edit(int argc, char *argv[]) {
|
|
|
|
if(argc != 2) {
|
|
|
|
fprintf(stderr, "Invalid number of arguments.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *filename = NULL;
|
|
|
|
|
2012-07-21 14:26:55 +00:00
|
|
|
if(strncmp(argv[1], "hosts" SLASH, 6)) {
|
2012-07-15 18:37:38 +00:00
|
|
|
for(int i = 0; conffiles[i]; i++) {
|
|
|
|
if(!strcmp(argv[1], conffiles[i])) {
|
2012-07-21 14:26:55 +00:00
|
|
|
xasprintf(&filename, "%s" SLASH "%s", confbase, argv[1]);
|
2012-07-15 18:37:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
argv[1] += 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!filename) {
|
2012-07-21 14:26:55 +00:00
|
|
|
xasprintf(&filename, "%s" SLASH "%s", hosts_dir, argv[1]);
|
2012-07-15 18:37:38 +00:00
|
|
|
char *dash = strchr(argv[1], '-');
|
|
|
|
if(dash) {
|
|
|
|
*dash++ = 0;
|
|
|
|
if((strcmp(dash, "up") && strcmp(dash, "down")) || !check_id(argv[1])) {
|
|
|
|
fprintf(stderr, "Invalid configuration filename.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-21 14:33:09 +00:00
|
|
|
char *command;
|
2012-07-15 18:37:38 +00:00
|
|
|
#ifndef HAVE_MINGW
|
2012-07-21 14:33:09 +00:00
|
|
|
xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ?: getenv("EDITOR") ?: "vi", filename);
|
2012-07-15 18:37:38 +00:00
|
|
|
#else
|
2012-07-21 14:33:09 +00:00
|
|
|
xasprintf(&command, "edit \"%s\"", filename);
|
2012-07-15 18:37:38 +00:00
|
|
|
#endif
|
2012-07-15 18:59:17 +00:00
|
|
|
int result = system(command);
|
|
|
|
if(result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
// Silently try notifying a running tincd of changes.
|
2012-08-02 15:24:42 +00:00
|
|
|
if(connect_tincd(false))
|
2012-07-15 18:59:17 +00:00
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
|
|
|
|
|
|
|
|
return 0;
|
2012-07-15 18:37:38 +00:00
|
|
|
}
|
|
|
|
|
2012-07-16 14:48:24 +00:00
|
|
|
static int export(const char *name, FILE *out) {
|
|
|
|
char *filename;
|
2012-07-21 14:26:55 +00:00
|
|
|
xasprintf(&filename, "%s" SLASH "%s", hosts_dir, name);
|
2012-07-16 14:48:24 +00:00
|
|
|
FILE *in = fopen(filename, "r");
|
|
|
|
if(!in) {
|
|
|
|
fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(out, "Name = %s\n", name);
|
|
|
|
char buf[4096];
|
2012-07-16 16:49:39 +00:00
|
|
|
while(fgets(buf, sizeof buf, in)) {
|
|
|
|
if(strcspn(buf, "\t =") != 4 || strncasecmp(buf, "Name", 4))
|
|
|
|
fputs(buf, out);
|
|
|
|
}
|
2012-07-16 14:48:24 +00:00
|
|
|
|
|
|
|
if(ferror(in)) {
|
|
|
|
fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno));
|
2012-08-02 15:24:42 +00:00
|
|
|
fclose(in);
|
2012-07-16 14:48:24 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(in);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_export(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-03-08 10:24:37 +00:00
|
|
|
char *name = get_my_name(true);
|
2012-07-16 14:48:24 +00:00
|
|
|
if(!name)
|
|
|
|
return 1;
|
|
|
|
|
2013-01-15 12:31:51 +00:00
|
|
|
int result = export(name, stdout);
|
|
|
|
if(!tty)
|
|
|
|
fclose(stdout);
|
2013-02-20 12:59:50 +00:00
|
|
|
|
|
|
|
free(name);
|
2013-01-15 12:31:51 +00:00
|
|
|
return result;
|
2012-07-16 14:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_export_all(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-16 14:48:24 +00:00
|
|
|
DIR *dir = opendir(hosts_dir);
|
|
|
|
if(!dir) {
|
|
|
|
fprintf(stderr, "Could not open host configuration directory %s: %s\n", hosts_dir, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool first = true;
|
|
|
|
int result = 0;
|
|
|
|
struct dirent *ent;
|
|
|
|
|
|
|
|
while((ent = readdir(dir))) {
|
|
|
|
if(!check_id(ent->d_name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(first)
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
printf("#---------------------------------------------------------------#\n");
|
|
|
|
|
|
|
|
result |= export(ent->d_name, stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
2013-01-15 12:31:51 +00:00
|
|
|
if(!tty)
|
|
|
|
fclose(stdout);
|
2012-07-16 14:48:24 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_import(int argc, char *argv[]) {
|
2013-01-14 12:02:39 +00:00
|
|
|
if(argc > 1) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-07-16 14:48:24 +00:00
|
|
|
FILE *in = stdin;
|
|
|
|
FILE *out = NULL;
|
|
|
|
|
|
|
|
char buf[4096];
|
|
|
|
char name[4096];
|
2013-01-15 12:31:51 +00:00
|
|
|
char *filename = NULL;
|
2012-07-16 14:48:24 +00:00
|
|
|
int count = 0;
|
|
|
|
bool firstline = true;
|
|
|
|
|
|
|
|
while(fgets(buf, sizeof buf, in)) {
|
|
|
|
if(sscanf(buf, "Name = %s", name) == 1) {
|
2013-01-14 11:59:17 +00:00
|
|
|
firstline = false;
|
|
|
|
|
2012-07-16 14:48:24 +00:00
|
|
|
if(!check_id(name)) {
|
|
|
|
fprintf(stderr, "Invalid Name in input!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(out)
|
|
|
|
fclose(out);
|
|
|
|
|
|
|
|
free(filename);
|
2012-07-21 14:26:55 +00:00
|
|
|
xasprintf(&filename, "%s" SLASH "%s", hosts_dir, name);
|
2012-07-16 14:48:24 +00:00
|
|
|
|
|
|
|
if(!force && !access(filename, F_OK)) {
|
|
|
|
fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename);
|
|
|
|
out = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
out = fopen(filename, "w");
|
|
|
|
if(!out) {
|
|
|
|
fprintf(stderr, "Error creating configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
count++;
|
|
|
|
continue;
|
|
|
|
} else if(firstline) {
|
|
|
|
fprintf(stderr, "Junk at the beginning of the input, ignoring.\n");
|
|
|
|
firstline = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(!strcmp(buf, "#---------------------------------------------------------------#\n"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(out) {
|
|
|
|
if(fputs(buf, out) < 0) {
|
|
|
|
fprintf(stderr, "Error writing to host configuration file %s: %s\n", filename, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(out)
|
|
|
|
fclose(out);
|
|
|
|
|
|
|
|
if(count) {
|
|
|
|
fprintf(stderr, "Imported %d host configuration files.\n", count);
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "No host configuration files imported.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-15 12:31:51 +00:00
|
|
|
static int cmd_exchange(int argc, char *argv[]) {
|
|
|
|
return cmd_export(argc, argv) ?: cmd_import(argc, argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_exchange_all(int argc, char *argv[]) {
|
|
|
|
return cmd_export_all(argc, argv) ?: cmd_import(argc, argv);
|
|
|
|
}
|
|
|
|
|
2014-02-26 10:00:30 +00:00
|
|
|
static int switch_network(char *name) {
|
|
|
|
if(fd >= 0) {
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(confbase);
|
|
|
|
confbase = NULL;
|
|
|
|
free(pidfilename);
|
|
|
|
pidfilename = NULL;
|
|
|
|
free(logfilename);
|
|
|
|
logfilename = NULL;
|
|
|
|
free(unixsocketname);
|
|
|
|
unixsocketname = NULL;
|
|
|
|
free(tinc_conf);
|
|
|
|
free(hosts_dir);
|
|
|
|
free(prompt);
|
|
|
|
|
|
|
|
free(netname);
|
|
|
|
netname = strcmp(name, ".") ? xstrdup(name) : NULL;
|
|
|
|
|
|
|
|
make_names();
|
|
|
|
xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
|
|
|
|
xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
|
|
|
|
xasprintf(&prompt, "%s> ", identname);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmd_network(int argc, char *argv[]) {
|
|
|
|
if(argc > 2) {
|
|
|
|
fprintf(stderr, "Too many arguments!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(argc == 2)
|
|
|
|
return switch_network(argv[1]);
|
|
|
|
|
|
|
|
DIR *dir = opendir(confdir);
|
|
|
|
if(!dir) {
|
|
|
|
fprintf(stderr, "Could not read directory %s: %s\n", confdir, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dirent *ent;
|
|
|
|
while((ent = readdir(dir))) {
|
|
|
|
if(*ent->d_name == '.')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(!strcmp(ent->d_name, "tinc.conf")) {
|
|
|
|
printf(".\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *fname;
|
|
|
|
xasprintf(&fname, "%s/%s/tinc.conf", confdir, ent->d_name);
|
|
|
|
if(!access(fname, R_OK))
|
|
|
|
printf("%s\n", ent->d_name);
|
|
|
|
free(fname);
|
|
|
|
}
|
|
|
|
|
2014-05-12 12:35:56 +00:00
|
|
|
closedir(dir);
|
|
|
|
|
2014-02-26 10:00:30 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
static const struct {
|
|
|
|
const char *command;
|
|
|
|
int (*function)(int argc, char *argv[]);
|
2013-03-12 10:28:40 +00:00
|
|
|
bool hidden;
|
2012-07-15 13:46:16 +00:00
|
|
|
} commands[] = {
|
|
|
|
{"start", cmd_start},
|
|
|
|
{"stop", cmd_stop},
|
|
|
|
{"restart", cmd_restart},
|
|
|
|
{"reload", cmd_reload},
|
|
|
|
{"dump", cmd_dump},
|
|
|
|
{"purge", cmd_purge},
|
|
|
|
{"debug", cmd_debug},
|
|
|
|
{"retry", cmd_retry},
|
|
|
|
{"connect", cmd_connect},
|
|
|
|
{"disconnect", cmd_disconnect},
|
|
|
|
{"top", cmd_top},
|
|
|
|
{"pcap", cmd_pcap},
|
|
|
|
{"log", cmd_log},
|
|
|
|
{"pid", cmd_pid},
|
2013-03-12 10:28:40 +00:00
|
|
|
{"config", cmd_config, true},
|
2013-03-08 10:40:40 +00:00
|
|
|
{"add", cmd_config},
|
|
|
|
{"del", cmd_config},
|
|
|
|
{"get", cmd_config},
|
|
|
|
{"set", cmd_config},
|
2012-07-15 13:46:16 +00:00
|
|
|
{"init", cmd_init},
|
|
|
|
{"generate-keys", cmd_generate_keys},
|
|
|
|
{"generate-rsa-keys", cmd_generate_rsa_keys},
|
2014-05-18 18:47:04 +00:00
|
|
|
{"generate-ed25519-keys", cmd_generate_ed25519_keys},
|
2012-07-15 13:46:16 +00:00
|
|
|
{"help", cmd_help},
|
|
|
|
{"version", cmd_version},
|
2012-07-15 23:05:25 +00:00
|
|
|
{"info", cmd_info},
|
2012-07-15 18:37:38 +00:00
|
|
|
{"edit", cmd_edit},
|
2012-07-16 14:48:24 +00:00
|
|
|
{"export", cmd_export},
|
|
|
|
{"export-all", cmd_export_all},
|
|
|
|
{"import", cmd_import},
|
2013-01-15 12:31:51 +00:00
|
|
|
{"exchange", cmd_exchange},
|
|
|
|
{"exchange-all", cmd_exchange_all},
|
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
|
|
|
{"invite", cmd_invite},
|
|
|
|
{"join", cmd_join},
|
2014-02-26 10:00:30 +00:00
|
|
|
{"network", cmd_network},
|
2012-07-15 13:46:16 +00:00
|
|
|
{NULL, NULL},
|
|
|
|
};
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
static char *complete_command(const char *text, int state) {
|
|
|
|
static int i;
|
|
|
|
|
|
|
|
if(!state)
|
|
|
|
i = 0;
|
|
|
|
else
|
|
|
|
i++;
|
|
|
|
|
|
|
|
while(commands[i].command) {
|
2013-03-12 10:28:40 +00:00
|
|
|
if(!commands[i].hidden && !strncasecmp(commands[i].command, text, strlen(text)))
|
2012-08-02 15:24:42 +00:00
|
|
|
return xstrdup(commands[i].command);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *complete_dump(const char *text, int state) {
|
2012-12-03 12:08:03 +00:00
|
|
|
const char *matches[] = {"reachable", "nodes", "edges", "subnets", "connections", "graph", NULL};
|
2012-08-02 15:24:42 +00:00
|
|
|
static int i;
|
|
|
|
|
|
|
|
if(!state)
|
|
|
|
i = 0;
|
|
|
|
else
|
|
|
|
i++;
|
|
|
|
|
|
|
|
while(matches[i]) {
|
|
|
|
if(!strncasecmp(matches[i], text, strlen(text)))
|
|
|
|
return xstrdup(matches[i]);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-08-03 11:23:07 +00:00
|
|
|
static char *complete_config(const char *text, int state) {
|
|
|
|
static int i;
|
2013-03-12 10:28:40 +00:00
|
|
|
|
|
|
|
if(!state)
|
2012-08-03 11:23:07 +00:00
|
|
|
i = 0;
|
2013-03-12 10:28:40 +00:00
|
|
|
else
|
2012-08-03 11:23:07 +00:00
|
|
|
i++;
|
|
|
|
|
2013-03-12 10:28:40 +00:00
|
|
|
while(variables[i].name) {
|
|
|
|
char *dot = strchr(text, '.');
|
|
|
|
if(dot) {
|
|
|
|
if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) {
|
|
|
|
char *match;
|
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
|
|
|
xasprintf(&match, "%.*s.%s", (int)(dot - text), text, variables[i].name);
|
2013-03-12 10:28:40 +00:00
|
|
|
return match;
|
2012-08-03 11:23:07 +00:00
|
|
|
}
|
2013-03-12 10:28:40 +00:00
|
|
|
} else {
|
|
|
|
if(!strncasecmp(variables[i].name, text, strlen(text)))
|
|
|
|
return xstrdup(variables[i].name);
|
2012-08-03 11:23:07 +00:00
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *complete_info(const char *text, int state) {
|
|
|
|
static int i;
|
|
|
|
if(!state) {
|
|
|
|
i = 0;
|
|
|
|
if(!connect_tincd(false))
|
|
|
|
return NULL;
|
|
|
|
// Check the list of nodes
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
|
|
|
|
sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
|
|
|
|
}
|
|
|
|
|
|
|
|
while(recvline(fd, line, sizeof line)) {
|
|
|
|
char item[4096];
|
|
|
|
int n = sscanf(line, "%d %d %s", &code, &req, item);
|
|
|
|
if(n == 2) {
|
|
|
|
i++;
|
|
|
|
if(i >= 2)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(n != 3) {
|
|
|
|
fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!strncmp(item, text, strlen(text)))
|
|
|
|
return xstrdup(strip_weight(item));
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *complete_nothing(const char *text, int state) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
static char **completion (const char *text, int start, int end) {
|
|
|
|
char **matches = NULL;
|
|
|
|
|
|
|
|
if(!start)
|
|
|
|
matches = rl_completion_matches(text, complete_command);
|
|
|
|
else if(!strncasecmp(rl_line_buffer, "dump ", 5))
|
|
|
|
matches = rl_completion_matches(text, complete_dump);
|
2013-03-12 10:28:40 +00:00
|
|
|
else if(!strncasecmp(rl_line_buffer, "add ", 4))
|
|
|
|
matches = rl_completion_matches(text, complete_config);
|
|
|
|
else if(!strncasecmp(rl_line_buffer, "del ", 4))
|
|
|
|
matches = rl_completion_matches(text, complete_config);
|
|
|
|
else if(!strncasecmp(rl_line_buffer, "get ", 4))
|
|
|
|
matches = rl_completion_matches(text, complete_config);
|
|
|
|
else if(!strncasecmp(rl_line_buffer, "set ", 4))
|
2012-08-03 11:23:07 +00:00
|
|
|
matches = rl_completion_matches(text, complete_config);
|
|
|
|
else if(!strncasecmp(rl_line_buffer, "info ", 5))
|
|
|
|
matches = rl_completion_matches(text, complete_info);
|
2012-08-02 15:24:42 +00:00
|
|
|
|
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int cmd_shell(int argc, char *argv[]) {
|
|
|
|
xasprintf(&prompt, "%s> ", identname);
|
|
|
|
int result = 0;
|
|
|
|
char buf[4096];
|
|
|
|
char *line = NULL;
|
|
|
|
int maxargs = argc + 16;
|
|
|
|
char **nargv = xmalloc(maxargs * sizeof *nargv);
|
|
|
|
|
|
|
|
for(int i = 0; i < argc; i++)
|
|
|
|
nargv[i] = argv[i];
|
|
|
|
|
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
rl_readline_name = "tinc";
|
2012-08-03 11:23:07 +00:00
|
|
|
rl_completion_entry_function = complete_nothing;
|
2012-08-02 15:24:42 +00:00
|
|
|
rl_attempted_completion_function = completion;
|
|
|
|
rl_filename_completion_desired = 0;
|
|
|
|
char *copy = NULL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
if(tty) {
|
|
|
|
free(copy);
|
|
|
|
free(line);
|
2012-08-03 11:23:07 +00:00
|
|
|
rl_basic_word_break_characters = "\t\n ";
|
2012-08-02 15:24:42 +00:00
|
|
|
line = readline(prompt);
|
|
|
|
if(line)
|
|
|
|
copy = xstrdup(line);
|
|
|
|
} else {
|
|
|
|
line = fgets(buf, sizeof buf, stdin);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if(tty)
|
2012-08-05 15:03:57 +00:00
|
|
|
fputs(prompt, stdout);
|
2012-08-02 15:24:42 +00:00
|
|
|
|
|
|
|
line = fgets(buf, sizeof buf, stdin);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(!line)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Ignore comments */
|
|
|
|
|
|
|
|
if(*line == '#')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Split */
|
|
|
|
|
|
|
|
int nargc = argc;
|
|
|
|
char *p = line + strspn(line, " \t\n");
|
|
|
|
char *next = strtok(p, " \t\n");
|
|
|
|
|
|
|
|
while(p && *p) {
|
|
|
|
if(nargc >= maxargs) {
|
|
|
|
fprintf(stderr, "next %p '%s', p %p '%s'\n", next, next, p, p);
|
|
|
|
abort();
|
|
|
|
maxargs *= 2;
|
|
|
|
nargv = xrealloc(nargv, maxargs * sizeof *nargv);
|
|
|
|
}
|
|
|
|
|
|
|
|
nargv[nargc++] = p;
|
|
|
|
p = next;
|
|
|
|
next = strtok(NULL, " \t\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(nargc == argc)
|
|
|
|
continue;
|
|
|
|
|
2012-08-05 15:25:31 +00:00
|
|
|
if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit"))
|
|
|
|
return result;
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
for(int i = 0; commands[i].command; i++) {
|
|
|
|
if(!strcasecmp(nargv[argc], commands[i].command)) {
|
|
|
|
result |= commands[i].function(nargc - argc - 1, nargv + argc + 1);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_READLINE
|
2012-08-05 15:25:31 +00:00
|
|
|
if(tty && found)
|
2012-08-02 15:24:42 +00:00
|
|
|
add_history(copy);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(!found) {
|
|
|
|
fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]);
|
|
|
|
result |= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-07 15:53:23 +00:00
|
|
|
free(nargv);
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
if(tty)
|
|
|
|
printf("\n");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
program_name = argv[0];
|
2012-08-03 12:15:50 +00:00
|
|
|
orig_argv = argv;
|
|
|
|
orig_argc = argc;
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
if(!parse_options(argc, argv))
|
|
|
|
return 1;
|
2012-10-10 15:17:49 +00:00
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
make_names();
|
2013-01-17 15:39:02 +00:00
|
|
|
xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
|
|
|
|
xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
if(show_version) {
|
|
|
|
version();
|
2012-02-26 17:37:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-15 13:46:16 +00:00
|
|
|
if(show_help) {
|
|
|
|
usage(false);
|
2011-05-22 12:17:30 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-08-27 19:19:50 +00:00
|
|
|
#ifdef HAVE_MINGW
|
|
|
|
static struct WSAData wsa_state;
|
|
|
|
|
|
|
|
if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
|
|
|
|
fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-07-26 13:48:52 +00:00
|
|
|
srand(time(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
|
|
|
crypto_init();
|
|
|
|
|
2012-08-02 15:24:42 +00:00
|
|
|
tty = isatty(0) && isatty(1);
|
|
|
|
|
|
|
|
if(optind >= argc)
|
|
|
|
return cmd_shell(argc, argv);
|
2012-07-15 13:46:16 +00:00
|
|
|
|
|
|
|
for(int i = 0; commands[i].command; i++) {
|
|
|
|
if(!strcasecmp(argv[optind], commands[i].command))
|
|
|
|
return commands[i].function(argc - optind, argv + optind);
|
|
|
|
}
|
|
|
|
|
2009-09-29 13:19:55 +00:00
|
|
|
fprintf(stderr, "Unknown command `%s'.\n", argv[optind]);
|
2007-05-19 15:21:26 +00:00
|
|
|
usage(true);
|
2012-07-15 13:46:16 +00:00
|
|
|
return 1;
|
2007-05-18 16:52:34 +00:00
|
|
|
}
|