/* * curvetun - the cipherspace wormhole creator * Part of the netsniff-ng project * Copyright 2011 Daniel Borkmann , * Copyright 2011 Emmanuel Roullit. * Subject to the GPL, version 2. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "die.h" #include "str.h" #include "sig.h" #include "stun.h" #include "cookie.h" #include "ioexact.h" #include "xmalloc.h" #include "curvetun.h" #include "curve.h" #include "config.h" #include "curvetun_mgmt.h" #include "ioops.h" #include "tprintf.h" #include "crypto.h" 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 const char *copyright = "Please report bugs at https://github.com/netsniff-ng/netsniff-ng/issues\n" "Copyright (C) 2011-2013 Daniel Borkmann ,\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."; static void signal_handler(int number) { switch (number) { case SIGINT: case SIGTERM: sigint = 1; break; default: break; } } static void __noreturn help(void) { printf("curvetun %s, lightweight curve25519-based IP tunnel\n", VERSION_STRING); puts("http://www.netsniff-ng.org\n\n" "Usage: curvetun [options]\n" "Options, general:\n" " -d|--dev Networking tunnel device, e.g. tun0\n" " -p|--port Server port number (mandatory)\n" " -t|--stun 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 and exit\n" " -h|--help Print this help and exit\n\n" "Example:\n" " See curvetun's man page 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"); puts(copyright); die(); } static void __noreturn version(void) { printf("curvetun %s, Git id: %s\n", VERSION_LONG, GITVERSION); puts("lightweight curve25519-based IP tunnel\n" "Note: Einstein-Rosen bridge not yet supported\n" "http://www.netsniff-ng.org\n\n"); puts(copyright); 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", HIG_ENTROPY_SOURCE); gen_key_bytes(secretkey, sizeof(secretkey)); 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_noclose; } 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_noclose; } ret = write(fd, secretkey, sizeof(secretkey)); if (ret != sizeof(secretkey)) { err = EIO; errstr = "Cannot write private key!\n"; goto out; } out: close(fd); out_noclose: 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_noclose; } 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_noclose; } 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); out_noclose: 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, 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, NULL)) != 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(); } free(dev); free(stun); free(port); free(alias); return ret; }