summaryrefslogtreecommitdiff
path: root/curvetun.c
diff options
context:
space:
mode:
Diffstat (limited to 'curvetun.c')
-rw-r--r--curvetun.c692
1 files changed, 692 insertions, 0 deletions
diff --git a/curvetun.c b/curvetun.c
new file mode 100644
index 0000000..3e7bdb4
--- /dev/null
+++ b/curvetun.c
@@ -0,0 +1,692 @@
+/*
+ * curvetun - the cipherspace wormhole creator
+ * Part of the netsniff-ng project
+ * Copyright 2011 Daniel Borkmann <dborkma@tik.ee.ethz.ch>,
+ * Copyright 2011 Emmanuel Roullit.
+ * Subject to the GPL, version 2.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/ptrace.h>
+#include <sys/fsuid.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "xutils.h"
+#include "die.h"
+#include "xmalloc.h"
+#include "curvetun.h"
+#include "curve.h"
+#include "ct_usermgmt.h"
+#include "ct_servmgmt.h"
+#include "xio.h"
+#include "tprintf.h"
+#include "crypto_verify_32.h"
+#include "crypto_box_curve25519xsalsa20poly1305.h"
+#include "crypto_scalarmult_curve25519.h"
+#include "crypto_auth_hmacsha512256.h"
+
+#define CURVETUN_ENTROPY_SOURCE "/dev/random"
+
+extern void print_stun_probe(char *server, uint16_t sport, uint16_t tunport);
+
+enum working_mode {
+ MODE_UNKNOW,
+ MODE_KEYGEN,
+ MODE_EXPORT,
+ MODE_DUMPC,
+ MODE_DUMPS,
+ MODE_CLIENT,
+ MODE_SERVER,
+};
+
+volatile sig_atomic_t sigint = 0;
+
+static const char *short_options = "kxc::svhp:t:d:uCS46DN";
+static const struct option long_options[] = {
+ {"client", optional_argument, NULL, 'c'},
+ {"dev", required_argument, NULL, 'd'},
+ {"port", required_argument, NULL, 'p'},
+ {"stun", required_argument, NULL, 't'},
+ {"keygen", no_argument, NULL, 'k'},
+ {"export", no_argument, NULL, 'x'},
+ {"dumpc", no_argument, NULL, 'C'},
+ {"dumps", no_argument, NULL, 'S'},
+ {"no-logging", no_argument, NULL, 'N'},
+ {"server", no_argument, NULL, 's'},
+ {"udp", no_argument, NULL, 'u'},
+ {"ipv4", no_argument, NULL, '4'},
+ {"ipv6", no_argument, NULL, '6'},
+ {"nofork", no_argument, NULL, 'D'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+};
+
+static void signal_handler(int number)
+{
+ switch (number) {
+ case SIGINT:
+ case SIGTERM:
+ sigint = 1;
+ break;
+ default:
+ break;
+ }
+}
+
+static void help(void)
+{
+ printf("\ncurvetun %s, lightweight curve25519-based VPN/IP tunnel\n", VERSION_STRING);
+ puts("http://www.netsniff-ng.org\n\n"
+ "Usage: curvetun [options]\n"
+ "Options, general:\n"
+ " -d|--dev <tun> Networking tunnel device, e.g. tun0\n"
+ " -p|--port <num> Server port number (mandatory)\n"
+ " -t|--stun <server> Show public IP/Port mapping via STUN\n"
+ " -c|--client[=alias] Client mode, server alias optional\n"
+ " -k|--keygen Generate public/private keypair\n"
+ " -x|--export Export your public data for remote servers\n"
+ " -C|--dumpc Dump parsed clients\n"
+ " -S|--dumps Dump parsed servers\n"
+ " -D|--nofork Do not daemonize\n"
+ " -s|--server Server mode, options follow below\n"
+ " -N|--no-logging Disable server logging (for better anonymity)\n"
+ " -u|--udp Use UDP as carrier instead of TCP\n"
+ " -4|--ipv4 Tunnel devices are IPv4\n"
+ " -6|--ipv6 Tunnel devices are IPv6\n"
+ " -v|--version Print version\n"
+ " -h|--help Print this help\n\n"
+ "Example:\n"
+ " See Documentation/Curvetun for a configuration example.\n"
+ " curvetun --server -4 -u -N --port 6666 --stun stunserver.org\n"
+ " curvetun --client=ethz\n\n"
+ " curvetun --keygen\n"
+ " curvetun --export\n"
+ "Note:\n"
+ " There is no default port specified, so that you are forced\n"
+ " to select your own! For client/server status messages see syslog!\n"
+ " This software is an experimental prototype intended for researchers.\n\n"
+ "Secret ingredient: 7647-14-5\n\n"
+ "Please report bugs to <bugs@netsniff-ng.org>\n"
+ "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>,\n"
+ "Swiss federal institute of technology (ETH Zurich)\n"
+ "License: GNU GPL version 2.0\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+ die();
+}
+
+static void version(void)
+{
+ printf("\ncurvetun %s, lightweight curve25519-based VPN/IP tunnel\n", VERSION_STRING);
+ puts("http://www.netsniff-ng.org\n\n"
+ "Please report bugs to <bugs@netsniff-ng.org>\n"
+ "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>,\n"
+ "Swiss federal institute of technology (ETH Zurich)\n"
+ "License: GNU GPL version 2.0\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+ die();
+}
+
+static void check_file_or_die(char *home, char *file, int maybeempty)
+{
+ char path[PATH_MAX];
+ struct stat st;
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, file);
+
+ if (stat(path, &st))
+ panic("No such file %s! Type --help for further information\n",
+ path);
+
+ if (!S_ISREG(st.st_mode))
+ panic("%s is not a regular file!\n", path);
+
+ if ((st.st_mode & ~S_IFREG) != (S_IRUSR | S_IWUSR))
+ panic("You have set too many permissions on %s (%o)!\n",
+ path, st.st_mode);
+
+ if (maybeempty == 0 && st.st_size == 0)
+ panic("%s is empty!\n", path);
+}
+
+static void check_config_exists_or_die(char *home)
+{
+ if (!home)
+ panic("No home dir specified!\n");
+
+ check_file_or_die(home, FILE_CLIENTS, 1);
+ check_file_or_die(home, FILE_SERVERS, 1);
+ check_file_or_die(home, FILE_PRIVKEY, 0);
+ check_file_or_die(home, FILE_PUBKEY, 0);
+ check_file_or_die(home, FILE_USERNAM, 0);
+}
+
+static char *fetch_home_dir(void)
+{
+ char *home = getenv("HOME");
+ if (!home)
+ panic("No HOME defined!\n");
+ return home;
+}
+
+static void write_username(char *home)
+{
+ int fd, ret;
+ char path[PATH_MAX];
+ char user[512];
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_USERNAM);
+
+ printf("Username: [%s] ", getenv("USER"));
+ fflush(stdout);
+
+ memset(user, 0, sizeof(user));
+ if (fgets(user, sizeof(user), stdin) == NULL)
+ panic("Could not read from stdin!\n");
+ user[sizeof(user) - 1] = 0;
+ user[strlen(user) - 1] = 0; /* omit last \n */
+ if (strlen(user) == 0)
+ strlcpy(user, getenv("USER"), sizeof(user));
+
+ fd = open_or_die_m(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+
+ ret = write(fd, user, strlen(user));
+ if (ret != strlen(user))
+ panic("Could not write username!\n");
+
+ close(fd);
+
+ printf("Username written to %s!\n", path);
+}
+
+static void create_curvedir(char *home)
+{
+ int ret;
+ char path[PATH_MAX];
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, ".curvetun/");
+
+ errno = 0;
+
+ ret = mkdir(path, S_IRWXU);
+ if (ret < 0 && errno != EEXIST)
+ panic("Cannot create curvetun dir!\n");
+
+ printf("curvetun directory %s created!\n", path);
+ /* We also create empty files for clients and servers! */
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_CLIENTS);
+
+ create_or_die(path, S_IRUSR | S_IWUSR);
+
+ printf("Empty client file written to %s!\n", path);
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_SERVERS);
+
+ create_or_die(path, S_IRUSR | S_IWUSR);
+
+ printf("Empty server file written to %s!\n", path);
+}
+
+static void create_keypair(char *home)
+{
+ int fd, err = 0;
+ ssize_t ret;
+ unsigned char publickey[crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES] = { 0 };
+ unsigned char secretkey[crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES] = { 0 };
+ char path[PATH_MAX];
+ const char * errstr = NULL;
+
+ printf("Reading from %s (this may take a while) ...\n", CURVETUN_ENTROPY_SOURCE);
+
+ fd = open_or_die(CURVETUN_ENTROPY_SOURCE, O_RDONLY);
+
+ ret = read_exact(fd, secretkey, sizeof(secretkey), 0);
+ if (ret != sizeof(secretkey)) {
+ err = EIO;
+ errstr = "Cannot read from "CURVETUN_ENTROPY_SOURCE"!\n";
+ goto out;
+ }
+
+ close(fd);
+
+ crypto_scalarmult_curve25519_base(publickey, secretkey);
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_PUBKEY);
+
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ err = EIO;
+ errstr = "Cannot open pubkey file!\n";
+ goto out;
+ }
+
+ ret = write(fd, publickey, sizeof(publickey));
+ if (ret != sizeof(publickey)) {
+ err = EIO;
+ errstr = "Cannot write public key!\n";
+ goto out;
+ }
+
+ close(fd);
+
+ printf("Public key written to %s!\n", path);
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_PRIVKEY);
+
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ err = EIO;
+ errstr = "Cannot open privkey file!\n";
+ goto out;
+ }
+
+ ret = write(fd, secretkey, sizeof(secretkey));
+ if (ret != sizeof(secretkey)) {
+ err = EIO;
+ errstr = "Cannot write private key!\n";
+ goto out;
+ }
+out:
+ close(fd);
+
+ xmemset(publickey, 0, sizeof(publickey));
+ xmemset(secretkey, 0, sizeof(secretkey));
+
+ if (err)
+ panic("%s: %s", errstr, strerror(errno));
+ else
+ printf("Private key written to %s!\n", path);
+}
+
+static void check_config_keypair_or_die(char *home)
+{
+ int fd, err;
+ ssize_t ret;
+ const char * errstr = NULL;
+ unsigned char publickey[crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES];
+ unsigned char publicres[crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES];
+ unsigned char secretkey[crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES];
+ char path[PATH_MAX];
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_PRIVKEY);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ err = EIO;
+ errstr = "Cannot open privkey file!\n";
+ goto out;
+ }
+
+ ret = read(fd, secretkey, sizeof(secretkey));
+ if (ret != sizeof(secretkey)) {
+ err = EIO;
+ errstr = "Cannot read private key!\n";
+ goto out;
+ }
+
+ close(fd);
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_PUBKEY);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ err = EIO;
+ errstr = "Cannot open pubkey file!\n";
+ goto out;
+ }
+
+ ret = read(fd, publickey, sizeof(publickey));
+ if (ret != sizeof(publickey)) {
+ err = EIO;
+ errstr = "Cannot read public key!\n";
+ goto out;
+ }
+
+ crypto_scalarmult_curve25519_base(publicres, secretkey);
+
+ err = crypto_verify_32(publicres, publickey);
+ if (err) {
+ err = EINVAL;
+ errstr = "WARNING: your keypair is corrupted!!! You need to "
+ "generate new keys!!!\n";
+ goto out;
+ }
+out:
+ close(fd);
+
+ xmemset(publickey, 0, sizeof(publickey));
+ xmemset(publicres, 0, sizeof(publicres));
+ xmemset(secretkey, 0, sizeof(secretkey));
+
+ if (err)
+ panic("%s: %s\n", errstr, strerror(errno));
+}
+
+static int main_keygen(char *home)
+{
+ create_curvedir(home);
+ write_username(home);
+ create_keypair(home);
+ check_config_keypair_or_die(home);
+
+ return 0;
+}
+
+static int main_export(char *home)
+{
+ int fd, i;
+ ssize_t ret;
+ char path[PATH_MAX], tmp[64];
+
+ check_config_exists_or_die(home);
+ check_config_keypair_or_die(home);
+
+ printf("Your exported public information:\n\n");
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_USERNAM);
+
+ fd = open_or_die(path, O_RDONLY);
+
+ while ((ret = read(fd, tmp, sizeof(tmp))) > 0) {
+ ret = write(STDOUT_FILENO, tmp, ret);
+ }
+
+ close(fd);
+
+ printf(";");
+
+ memset(path, 0, sizeof(path));
+ slprintf(path, sizeof(path), "%s/%s", home, FILE_PUBKEY);
+
+ fd = open_or_die(path, O_RDONLY);
+
+ ret = read(fd, tmp, sizeof(tmp));
+ if (ret != crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES)
+ panic("Cannot read public key!\n");
+
+ for (i = 0; i < ret; ++i)
+ if (i == ret - 1)
+ printf("%02x\n\n", (unsigned char) tmp[i]);
+ else
+ printf("%02x:", (unsigned char) tmp[i]);
+
+ close(fd);
+ fflush(stdout);
+
+ return 0;
+}
+
+static int main_dumpc(char *home)
+{
+ check_config_exists_or_die(home);
+ check_config_keypair_or_die(home);
+
+ printf("Your clients:\n\n");
+
+ parse_userfile_and_generate_user_store_or_die(home);
+
+ dump_user_store();
+
+ destroy_user_store();
+
+ printf("\n");
+ die();
+ return 0;
+}
+
+static int main_dumps(char *home)
+{
+ check_config_exists_or_die(home);
+ check_config_keypair_or_die(home);
+
+ printf("Your servers:\n\n");
+
+ parse_userfile_and_generate_serv_store_or_die(home);
+
+ dump_serv_store();
+
+ destroy_serv_store();
+
+ printf("\n");
+ die();
+ return 0;
+}
+
+static void daemonize(const char *lockfile)
+{
+ char pidstr[8];
+ mode_t lperm = S_IRWXU | S_IRGRP | S_IXGRP; /* 0750 */
+ int lfp;
+
+ if (getppid() == 1)
+ return;
+
+ if (daemon(0, 1))
+ panic("Cannot daemonize: %s", strerror(errno));
+
+ to_std_log(&stdout);
+ to_std_log(&stderr);
+
+ umask(lperm);
+ if (lockfile) {
+ lfp = open(lockfile, O_RDWR | O_CREAT | O_EXCL,
+ S_IRUSR | S_IWUSR | S_IRGRP);
+ if (lfp < 0)
+ syslog_panic("Cannot create lockfile at %s! "
+ "curvetun server already running?\n",
+ lockfile);
+
+ slprintf(pidstr, sizeof(pidstr), "%u", getpid());
+ if (write(lfp, pidstr, strlen(pidstr)) <= 0)
+ syslog_panic("Could not write pid to pidfile %s",
+ lockfile);
+
+ close(lfp);
+ }
+}
+
+static int main_client(char *home, char *dev, char *alias, int daemon)
+{
+ int ret, udp;
+ char *host, *port;
+
+ check_config_exists_or_die(home);
+ check_config_keypair_or_die(home);
+
+ parse_userfile_and_generate_serv_store_or_die(home);
+
+ get_serv_store_entry_by_alias(alias, alias ? strlen(alias) + 1 : 0,
+ &host, &port, &udp);
+ if (!host || !port || udp < 0)
+ panic("Did not find alias/entry in configuration!\n");
+
+ printf("Using [%s] -> %s:%s via %s as endpoint!\n",
+ alias ? : "default", host, port, udp ? "udp" : "tcp");
+ if (daemon)
+ daemonize(NULL);
+
+ ret = client_main(home, dev, host, port, udp);
+
+ destroy_serv_store();
+
+ return ret;
+}
+
+static int main_server(char *home, char *dev, char *port, int udp,
+ int ipv4, int daemon, int log)
+{
+ int ret;
+
+ check_config_exists_or_die(home);
+ check_config_keypair_or_die(home);
+
+ if (daemon)
+ daemonize(LOCKFILE);
+
+ ret = server_main(home, dev, port, udp, ipv4, log);
+
+ unlink(LOCKFILE);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0, c, opt_index, udp = 0, ipv4 = -1, daemon = 1, log = 1;
+ char *port = NULL, *stun = NULL, *dev = NULL, *home = NULL, *alias = NULL;
+ enum working_mode wmode = MODE_UNKNOW;
+
+ setfsuid(getuid());
+ setfsgid(getgid());
+
+ home = fetch_home_dir();
+
+ while ((c = getopt_long(argc, argv, short_options, long_options,
+ &opt_index)) != EOF) {
+ switch (c) {
+ case 'h':
+ help();
+ break;
+ case 'v':
+ version();
+ break;
+ case 'D':
+ daemon = 0;
+ break;
+ case 'N':
+ log = 0;
+ break;
+ case 'C':
+ wmode = MODE_DUMPC;
+ break;
+ case 'S':
+ wmode = MODE_DUMPS;
+ break;
+ case 'c':
+ wmode = MODE_CLIENT;
+ if (optarg) {
+ if (*optarg == '=')
+ optarg++;
+ alias = xstrdup(optarg);
+ }
+ break;
+ case 'd':
+ dev = xstrdup(optarg);
+ break;
+ case 'k':
+ wmode = MODE_KEYGEN;
+ break;
+ case '4':
+ ipv4 = 1;
+ break;
+ case '6':
+ ipv4 = 0;
+ break;
+ case 'x':
+ wmode = MODE_EXPORT;
+ break;
+ case 's':
+ wmode = MODE_SERVER;
+ break;
+ case 'u':
+ udp = 1;
+ break;
+ case 't':
+ stun = xstrdup(optarg);
+ break;
+ case 'p':
+ port = xstrdup(optarg);
+ break;
+ case '?':
+ switch (optopt) {
+ case 't':
+ case 'd':
+ case 'u':
+ case 'p':
+ panic("Option -%c requires an argument!\n",
+ optopt);
+ default:
+ if (isprint(optopt))
+ printf("Unknown option character `0x%X\'!\n", optopt);
+ die();
+ }
+ default:
+ break;
+ }
+ }
+
+ if (argc < 2)
+ help();
+
+ register_signal(SIGINT, signal_handler);
+ register_signal(SIGHUP, signal_handler);
+ register_signal(SIGTERM, signal_handler);
+ register_signal(SIGPIPE, signal_handler);
+
+ curve25519_selftest();
+
+ switch (wmode) {
+ case MODE_KEYGEN:
+ ret = main_keygen(home);
+ break;
+ case MODE_EXPORT:
+ ret = main_export(home);
+ break;
+ case MODE_DUMPC:
+ ret = main_dumpc(home);
+ break;
+ case MODE_DUMPS:
+ ret = main_dumps(home);
+ break;
+ case MODE_CLIENT:
+ ret = main_client(home, dev, alias, daemon);
+ break;
+ case MODE_SERVER:
+ if (!port)
+ panic("No port specified!\n");
+ if (stun)
+ print_stun_probe(stun, 3478, strtoul(port, NULL, 10));
+ ret = main_server(home, dev, port, udp, ipv4, daemon, log);
+ break;
+ default:
+ die();
+ }
+
+ if (dev)
+ xfree(dev);
+ if (stun)
+ xfree(stun);
+ if (port)
+ xfree(port);
+ if (alias)
+ xfree(alias);
+
+ return ret;
+}