stun

simple point to point tunnel
git clone git://git.2f30.org/stun.git
Log | Files | Refs | README

commit 0fc4cc5cfd37590c4853f2ac4fd8ee2d2a77aa2a
parent 2799734eefc076f74bdd5e6bf055b4f431b11b33
Author: sin <sin@2f30.org>
Date:   Fri Apr 22 14:36:49 +0100

Add multi-network support

This patch brings multiple network support to stun.  The config file
is comprised of network blocks as shown below.

server side /etc/stun.conf:

network "2f30-net-srv" {
	listen on "1.2.3.4"
	interface "tun0"
	passwd    "changeme"
}

client side /etc/stun.conf:

network "2f30-net-cli" {
	connect   "1.2.3.4"
	interface "tun0"
	passwd    "changeme"
}

An include directive is also supported to include networks from
separate files into the master /etc/stun.conf config file.

The main stun process forks once for each network.  Each stun instance
revokes privileges to a user specified by the `user' option.  The default
user is `nobody'.

Each network instance can be either a client or server.  A network
block without a `listen' directive is expected to contain a `connect'
directive.

The parent process stays around and waits on the children.  It will
exit once all the children have terminated.

Diffstat:
Makefile | 15++++++++++++---
README | 22++++++++++++++--------
WHATSNEW | 13+++++++++----
auth.c | 23+++++++++++++++--------
client.c | 12+++++++-----
config.mk | 2++
crypto.c | 6+++---
dev_bsd.c | 35+++++++++++++++++++++++------------
dev_linux.c | 27+++++++++++++++++++--------
log.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
netpkt.c | 18++++++++++--------
parse.y | 637+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
queue.h | 533+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
server.c | 14+++++++-------
stun.8 | 106++++++++++++++++++++++++++++++++++++-------------------------------------------
stun.c | 274+++++++++++++++++++++++++++++++++++++++----------------------------------------
stun.conf.5 | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stun.h | 41+++++++++++++++++++++++++++++++++++++----
tunnel.c | 5++++-
util.c | 12++++++------
20 files changed, 1767 insertions(+), 278 deletions(-)
diff --git a/Makefile b/Makefile @@ -14,9 +14,12 @@ DISTFILES = \ dev_linux.c \ log.c \ netpkt.c \ + parse.y \ + queue.h \ server.c \ stun.8 \ stun.c \ + stun.conf.5 \ stun.h \ tunnel.c \ util.c @@ -31,26 +34,32 @@ OBJ = \ server.o \ stun.o \ tunnel.o \ - util.o + util.o \ + y.tab.o BIN = stun -all: $(BIN) +all: y.tab.c $(BIN) $(BIN): $(OBJ) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(OBJ) $(LDLIBS) $(OBJ): stun.h +y.tab.c: stun.h parse.y + $(YACC) parse.y install: all mkdir -p $(DESTDIR)$(PREFIX)/bin cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(MANPREFIX)/man8 cp -f $(BIN).8 $(DESTDIR)$(MANPREFIX)/man8 + mkdir -p $(DESTDIR)$(MANPREFIX)/man5 + cp -f $(BIN).conf.5 $(DESTDIR)$(MANPREFIX)/man5 uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) rm -f $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8 + rm -f $(DESTDIR)$(MANPREFIX)/man8/$(BIN).conf.5 dist: mkdir -p stun-$(VERSION) @@ -60,4 +69,4 @@ dist: rm -rf stun-$(VERSION) clean: - rm -f $(BIN) $(OBJ) stun-$(VERSION).tar.gz + rm -f $(BIN) $(OBJ) stun-$(VERSION).tar.gz y.tab.c diff --git a/README b/README @@ -14,17 +14,21 @@ Usage On the server: - ifconfig tun0 create - ifconfig tun0 10.0.0.1 10.0.0.2 - STUNPW=pass stun -s tun0 +/etc/stun.conf: -On the client: + network "example-net" { + listen on "1.2.3.4" + interface "tun0" + passwd "changeme" + } - ifconfig tun0 create - ifconfig tun0 10.0.0.2 10.0.0.1 - STUNPW=pass stun -h hostname tun0 +On the client: -TCP port 12080 must be opened on the server side. + network "example-net" { + connect "1.2.3.4" + interface "tun0" + passwd "changeme" + } Portability @@ -38,3 +42,5 @@ to build stun. On Linux based systems you need libressl and libbsd to build stun. + +Before building review and adjust config.mk as required. diff --git a/WHATSNEW b/WHATSNEW @@ -1,7 +1,12 @@ -0.2 release notes +0.3 release notes - * Packet format was changed. This version is incompatible - with 0.1. + * multi-network support. + * stun is now configurable through a config file. + * logging facility has been improved. + +0.2 release notes - April 17 2016 + + * Packet format was changed. This version is incompatible with 0.1. * EVP_AEAD_* high level API support. Choice between four different ciphers depending on version of libressl used. * Default cipher switched to chacha20-poly1305. @@ -12,6 +17,6 @@ when headers are modified in transit. * General code cleanup. -0.1 release notes +0.1 release notes - April 02 2016 * Initial release diff --git a/auth.c b/auth.c @@ -1,3 +1,4 @@ +#include <errno.h> #include <poll.h> #include <stdint.h> #include <stdlib.h> @@ -15,8 +16,9 @@ challenge(int netfd) size_t outlen; struct pollfd pfd[1]; uint64_t n, reply; - int ret; + int ret, timeout; + timeout = MIN(AUTHTIMEO, netconfig.timeout); arc4random_buf(&n, sizeof(buf)); pack64(buf, n); if (netwrite(netfd, buf, sizeof(buf), &outlen) == PKTFAILED) @@ -25,12 +27,14 @@ challenge(int netfd) pfd[0].fd = netfd; pfd[0].events = POLLIN; for (;;) { - ret = poll(pfd, 1, AUTHTIMEO * 1000); + ret = poll(pfd, 1, timeout * 1000); if (ret < 0) { - logwarn("poll failed"); + if (errno == EINTR) + continue; + logwarn("poll"); return -1; } else if (ret == 0) { - logwarn("challenge-response timed out"); + logwarnx("challenge-response timed out"); return -1; } @@ -57,17 +61,20 @@ response(int netfd) size_t outlen; struct pollfd pfd[1]; uint64_t reply; - int ret; + int ret, timeout; + timeout = MIN(AUTHTIMEO, netconfig.timeout); pfd[0].fd = netfd; pfd[0].events = POLLIN; for (;;) { - ret = poll(pfd, 1, AUTHTIMEO * 1000); + ret = poll(pfd, 1, timeout * 1000); if (ret < 0) { - logwarn("poll failed"); + if (errno == EINTR) + continue; + logwarn("poll"); return -1; } else if (ret == 0) { - logwarn("challenge-response timed out"); + logwarnx("challenge-response timed out"); return -1; } diff --git a/client.c b/client.c @@ -20,11 +20,13 @@ clientconnect(char *host, char *port) memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - if ((ret = getaddrinfo(host, port, &hints, &ai))) - logerr("getaddrinfo: %s", gai_strerror(ret)); + if ((ret = getaddrinfo(host, port, &hints, &ai))) { + logwarnx("getaddrinfo: %s", gai_strerror(ret)); + return -1; + } for (p = ai; p; p = p->ai_next) { - if (p->ai_family != aftype) + if (p->ai_family != netconfig.proto) continue; if ((netfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) @@ -37,7 +39,7 @@ clientconnect(char *host, char *port) } freeaddrinfo(ai); if (!p) { - logwarn("failed to connect to %s:%s", host, port); + logwarnx("failed to connect to %s:%s", host, port); return -1; } @@ -47,7 +49,7 @@ clientconnect(char *host, char *port) if (response(netfd) < 0 || challenge(netfd) < 0) { close(netfd); - logwarn("challenge-response failed"); + logwarnx("challenge-response failed"); return -1; } return netfd; diff --git a/config.mk b/config.mk @@ -6,6 +6,8 @@ MANPREFIX = $(PREFIX)/man INC = /usr/local/include LIB = /usr/local/lib +YACC = yacc + # BSD CFLAGS = -std=c99 -Wall -I$(INC) LDLIBS = -L$(LIB) -lcrypto diff --git a/crypto.c b/crypto.c @@ -41,7 +41,7 @@ setcipher(char *name) return; } } - logerr("unknown cipher: %s", name); + fatalx("unknown cipher: %s", name); } void @@ -49,7 +49,7 @@ derivekey(char *pw) { if (!PKCS5_PBKDF2_HMAC_SHA1(pw, strlen(pw), NULL, 0, NROUNDS, EVP_AEAD_key_length(aead), key)) - logerr("PKCS5_PBKDF2_HMAC_SHA1 failed"); + fatalx("PKCS5_PBKDF2_HMAC_SHA1 failed"); } void @@ -59,7 +59,7 @@ cryptoinit(void) EVP_AEAD_DEFAULT_TAG_LENGTH, NULL) || !EVP_AEAD_CTX_init(&dctx, aead, key, EVP_AEAD_key_length(aead), EVP_AEAD_DEFAULT_TAG_LENGTH, NULL)) - logerr("EVP_AEAD_CTX_init failed"); + fatalx("EVP_AEAD_CTX_init failed"); } size_t diff --git a/dev_bsd.c b/dev_bsd.c @@ -11,6 +11,7 @@ #endif #include <netinet/in.h> +#include <errno.h> #include <fcntl.h> #include <libgen.h> #include <limits.h> @@ -28,20 +29,20 @@ devopen(char *ifname) snprintf(dev, sizeof(dev), "/dev/%s", basename(ifname)); if ((fd = open(dev, O_RDWR)) < 0) - logerr("failed to open %s", dev); + fatal("open %s", dev); if (ioctl(fd, TUNGIFINFO, &ti) < 0) - logerr("failed to set TUNGIFINFO on %s", dev); - if (devtype == TUNDEV) + fatal("TUNGIFINFO %s", dev); + if (netconfig.devtype == TUNDEV) ti.mtu = MAXPAYLOADLEN; else ti.mtu = MAXPAYLOADLEN - 14; /* make some room for ethernet header */ if (ioctl(fd, TUNSIFINFO, &ti) < 0) - logerr("failed to set TUNSIFINFO on %s", dev); - if (devtype == TUNDEV) { + fatal("TUNSIFINFO %s", dev); + if (netconfig.devtype == TUNDEV) { #if defined(TUNSIFHEAD) int one = 1; if (ioctl(fd, TUNSIFHEAD, &one) < 0) - logerr("failed to set TUNSIFHEAD on %s", dev); + fatal("TUNSIFHEAD %s", dev); #endif } return fd; @@ -54,9 +55,12 @@ devwrite(int fd, unsigned char *buf, int len) uint32_t type; int n, af; - switch (devtype) { + switch (netconfig.devtype) { case TAPDEV: - return write(fd, buf, len); + do { + n = write(fd, buf, len); + } while (n < 0 && errno == EINTR); + return n; case TUNDEV: if ((af = ipversion(buf)) < 0) return -1; @@ -65,7 +69,9 @@ devwrite(int fd, unsigned char *buf, int len) iov[0].iov_len = sizeof(type); iov[1].iov_base = buf; iov[1].iov_len = len; - n = writev(fd, iov, 2); + do { + n = writev(fd, iov, 2); + } while (n < 0 && errno == EINTR); if (n > 0) n -= sizeof(type); break; @@ -80,15 +86,20 @@ devread(int fd, unsigned char *buf, int len) uint32_t type; int n; - switch (devtype) { + switch (netconfig.devtype) { case TAPDEV: - return read(fd, buf, len); + do { + n = read(fd, buf, len); + } while (n < 0 && errno == EINTR); + return n; case TUNDEV: iov[0].iov_base = &type; iov[0].iov_len = sizeof(type); iov[1].iov_base = buf; iov[1].iov_len = len; - n = readv(fd, iov, 2); + do { + n = readv(fd, iov, 2); + } while (n < 0 && errno == EINTR); if (n > 0) n -= sizeof(type); break; diff --git a/dev_linux.c b/dev_linux.c @@ -5,6 +5,7 @@ #include <net/if.h> #include <linux/if_tun.h> +#include <errno.h> #include <fcntl.h> #include <string.h> #include <unistd.h> @@ -18,24 +19,24 @@ devopen(char *ifname) int fd, s; if ((fd = open("/dev/net/tun", O_RDWR)) < 0) - logerr("failed to open %s", "/dev/net/tun"); + fatal("open %s", "/dev/net/tun"); memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_flags = (devtype == TUNDEV ? IFF_TUN : IFF_TAP) | IFF_NO_PI; + ifr.ifr_flags = (netconfig.devtype == TUNDEV ? IFF_TUN : IFF_TAP) | IFF_NO_PI; strncpy(ifr.ifr_name, ifname, IF_NAMESIZE); ifr.ifr_name[IF_NAMESIZE - 1] = '\0'; if (ioctl(fd, TUNSETIFF, &ifr) < 0) - logerr("failed to set TUNSETIFF on %s", ifname); + fatal("TUNSETIFF %s", ifname); /* dummy socket so we can manipulate the params */ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) - logerr("failed to create socket"); - if (devtype == TUNDEV) + fatal("socket"); + if (netconfig.devtype == TUNDEV) ifr.ifr_mtu = MAXPAYLOADLEN; else ifr.ifr_mtu = MAXPAYLOADLEN - 14; /* make some room for ethernet header */ if (ioctl(s, SIOCSIFMTU, &ifr) < 0) - logerr("failed to set MTU on %s", ifname); + fatal("SIOCSIFMTU %s", ifname); close(s); return fd; @@ -44,11 +45,21 @@ devopen(char *ifname) int devwrite(int fd, unsigned char *buf, int len) { - return write(fd, buf, len); + int n; + + do { + n = write(fd, buf, len); + } while (n < 0 && errno == EINTR); + return n; } int devread(int fd, unsigned char *buf, int len) { - return read(fd, buf, len); + int n; + + do { + n = read(fd, buf, len); + } while (n < 0 && errno == EINTR); + return n; } diff --git a/log.c b/log.c @@ -1,14 +1,19 @@ +#include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <syslog.h> #include "stun.h" +int debug; +int verbose; + static char *progname; static void -logmsg(int priority, char *msg, va_list ap) +vlog(int priority, char *msg, va_list ap) { if (debug) { fprintf(stderr, "%s: ", progname); @@ -30,30 +35,89 @@ loginit(char *prog) void logdbg(char *msg, ...) { + char buf[512]; va_list ap; va_start(ap, msg); - logmsg(LOG_DAEMON | LOG_DEBUG, msg, ap); + snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno)); + vlog(LOG_DAEMON | LOG_DEBUG, buf, ap); + va_end(ap); +} + +void +logdbgx(char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vlog(LOG_DAEMON | LOG_DEBUG, msg, ap); va_end(ap); } void logwarn(char *msg, ...) { + char buf[512]; va_list ap; va_start(ap, msg); - logmsg(LOG_DAEMON | LOG_WARNING, msg, ap); + snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno)); + vlog(LOG_DAEMON | LOG_WARNING, buf, ap); + va_end(ap); +} + +void +logwarnx(char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vlog(LOG_DAEMON | LOG_WARNING, msg, ap); va_end(ap); } void logerr(char *msg, ...) { + char buf[512]; + va_list ap; + + va_start(ap, msg); + snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno)); + vlog(LOG_DAEMON | LOG_ERR, buf, ap); + va_end(ap); +} + +void +logerrx(char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vlog(LOG_DAEMON | LOG_ERR, msg, ap); + va_end(ap); +} + +void +fatal(char *msg, ...) +{ + char buf[512]; + va_list ap; + + va_start(ap, msg); + snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno)); + vlog(LOG_DAEMON | LOG_ERR, buf, ap); + va_end(ap); + exit(1); +} + +void +fatalx(char *msg, ...) +{ va_list ap; va_start(ap, msg); - logmsg(LOG_DAEMON | LOG_ERR, msg, ap); + vlog(LOG_DAEMON | LOG_ERR, msg, ap); va_end(ap); exit(1); } diff --git a/netpkt.c b/netpkt.c @@ -41,7 +41,7 @@ static size_t taglen; /* state tracking for input handling */ static int state; -/* create a packet and write it out to the network */ +/* create a packet and write it to the network */ int netwrite(int fd, unsigned char *pt, size_t ptlen, size_t *outlen) { @@ -57,7 +57,7 @@ netwrite(int fd, unsigned char *pt, size_t ptlen, size_t *outlen) if (!cryptoseal(&wbuf[noncelen + HDRLEN], outlen, ptlen + taglen, wbuf, noncelen, pt, ptlen, &wbuf[noncelen], HDRLEN)) { - logwarn("cryptoseal failed"); + logwarnx("cryptoseal failed"); return -1; } *outlen = ptlen; @@ -68,7 +68,7 @@ netwrite(int fd, unsigned char *pt, size_t ptlen, size_t *outlen) if (n == 0) { return PKTFAILED; } else if (n < 0) { - if (errno == EWOULDBLOCK) + if (errno == EWOULDBLOCK || errno == EINTR) continue; return PKTFAILED; } @@ -147,7 +147,7 @@ netread(int fd, unsigned char *pt, size_t ptlen, size_t *outlen) &rbuf[noncelen + HDRLEN], rbuftotal - noncelen - HDRLEN, &rbuf[noncelen], HDRLEN)) { - logwarn("cryptoopen failed"); + logwarnx("cryptoopen failed"); return PKTPARTIAL; } return PKTCOMPLETE; @@ -160,8 +160,10 @@ netread(int fd, unsigned char *pt, size_t ptlen, size_t *outlen) } } out: - if (n < 0 && errno == EWOULDBLOCK) - return PKTPARTIAL; + if (n < 0) { + if (errno == EWOULDBLOCK || errno == EINTR) + return PKTPARTIAL; + } return PKTFAILED; } @@ -179,8 +181,8 @@ netinit(void) taglen = cryptotaglen(); maxbuflen = noncelen + HDRLEN + MAXPAYLOADLEN + taglen; if (!(wbuf = malloc(maxbuflen))) - logerr("oom"); + fatal("oom"); if (!(rbuf = malloc(maxbuflen))) - logerr("oom"); + fatal("oom"); netreset(); } diff --git a/parse.y b/parse.y @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2006 Bob Beck <beck@openbsd.org> + * Copyright (c) 2002-2006 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <sys/stat.h> + +#include <ctype.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __linux__ +#include <bsd/stdlib.h> +#include <bsd/string.h> +#endif + +#include "stun.h" + +static TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; + +static struct file *pushfile(const char *); +static int popfile(void); +static int yyparse(void); +static int yylex(void); +static int yyerror(const char *, ...); +static int kwcmp(const void *, const void *); +static int lookup(char *); +static int lgetc(int); +static int lungetc(int); +static int findeol(void); +static void resetnetconfig(struct netcfg *); +int parseconf(const char *); + +static struct netcfg curnet; + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; +%} + +%token CIPHER CONNECT DEVTYPE ERROR INCLUDE +%token INTERFACE LISTEN NETWORK ON PASSWD PORT PROTO TIMEOUT USER +%token <v.string> STRING +%token <v.number> NUMBER +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar main '\n' + | grammar error '\n' { + file->errors++; + } + ; + +optnl : + | '\n' + ; + +nl : '\n' optnl + ; + +optnl_l : + | '\n' optnl_l + ; + +include : INCLUDE STRING { + struct stat sb; + struct file *nfile; + + if (stat($2, &sb) == 0 && sb.st_mode & 007) { + yyerror("%s: shouldn't be readable by others", $2); + free($2); + YYERROR; + } + + if (!(nfile = pushfile($2))) { + yyerror("failed to include %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + nfile->lineno--; + } + ; + +main : NETWORK STRING optnl networkopts_b { + struct netcfg *np; + + TAILQ_FOREACH(np, &netcfgs, entry) { + if (strcmp(np->name, $2) == 0) { + yyerror("duplicate network block: %s", np->name); + resetnetconfig(&curnet); + free($2); + YYERROR; + } + } + + if (curnet.host[0] != '\0' && curnet.bindaddr[0] != '\0') { + yyerror("network %s: configured as both client and server", $2); + resetnetconfig(&curnet); + free($2); + YYERROR; + } + + if (strlcpy(curnet.name, $2, sizeof(curnet.name)) >= + sizeof(curnet.name)) { + yyerror("network name is too long"); + resetnetconfig(&curnet); + free($2); + YYERROR; + } + free($2); + + if (!(np = calloc(1, sizeof(*np)))) + fatal("calloc"); + *np = curnet; + TAILQ_INSERT_TAIL(&netcfgs, np, entry); + resetnetconfig(&curnet); + } + ; + +networkopts_b : '{' optnl_l networkopts_l optnl_l '}' + ; + +networkopts_l : networkopts_l nl networkopts + | networkopts + ; + +networkopts : CIPHER STRING { + if (strlcpy(curnet.cipher, $2, sizeof(curnet.cipher)) >= + sizeof(curnet.cipher)) { + yyerror("cipher name is too long"); + free($2); + YYERROR; + } + free($2); + } + | CONNECT STRING PORT NUMBER { + if (strlcpy(curnet.host, $2, sizeof(curnet.host)) >= + sizeof(curnet.host)) { + yyerror("connect address is too long"); + free($2); + YYERROR; + } + free($2); + snprintf(curnet.port, sizeof(curnet.port), + "%d", (int)$4); + } + | CONNECT STRING { + if (strlcpy(curnet.host, $2, sizeof(curnet.host)) >= + sizeof(curnet.host)) { + yyerror("connect address is too long"); + free($2); + YYERROR; + } + free($2); + } + | DEVTYPE STRING { + if (strcmp("tun", $2) == 0) { + curnet.devtype = TUNDEV; + } else if (strcmp("tap", $2) == 0) { + curnet.devtype = TAPDEV; + } else { + yyerror("unknown device type: %s", $2); + free($2); + YYERROR; + } + free($2); + } + | INTERFACE STRING { + if (strlcpy(curnet.ifname, $2, sizeof(curnet.ifname)) >= + sizeof(curnet.ifname)) { + yyerror("interface name is too long"); + free($2); + YYERROR; + } + free($2); + } + | LISTEN ON STRING PORT NUMBER { + if (strlcpy(curnet.bindaddr, $3, sizeof(curnet.bindaddr)) >= + sizeof(curnet.bindaddr)) { + yyerror("listen address is too long"); + free($3); + YYERROR; + } + free($3); + snprintf(curnet.port, sizeof(curnet.port), + "%d", (int)$5); + } + | LISTEN ON STRING { + if (strlcpy(curnet.bindaddr, $3, sizeof(curnet.bindaddr)) >= + sizeof(curnet.bindaddr)) { + yyerror("listen address is too long"); + free($3); + YYERROR; + } + free($3); + } + | PASSWD STRING { + if (strlcpy(curnet.passwd, $2, sizeof(curnet.passwd)) >= + sizeof(curnet.passwd)) { + yyerror("passwd is too long"); + free($2); + YYERROR; + } + free($2); + } + | PROTO STRING { + /* only applicable for clients */ + if (strcmp("inet4", $2) == 0) { + curnet.proto = AF_INET; + } else if (strcmp("inet6", $2) == 0) { + curnet.proto = AF_INET6; + } else { + yyerror("invalid proto: %s", $2); + free($2); + YYERROR; + } + free($2); + } + | TIMEOUT NUMBER { + curnet.timeout = $2; + } + | USER STRING { + if (strlcpy(curnet.user, $2, sizeof(curnet.user)) >= + sizeof(curnet.user)) { + yyerror("user name is too long"); + free($2); + YYERROR; + } + free($2); + } + ; +%% + +struct keywords { + const char *name; + int val; +}; + +static int +yyerror(const char *fmt, ...) +{ + char buf[512]; + va_list ap; + + file->errors++; + va_start(ap, fmt); + if (vsnprintf(buf, sizeof(buf), fmt, ap) < 0) + fatal("vsnprintf"); + va_end(ap); + logerrx("%s:%d: %s", file->name, yylval.lineno, buf); + return 0; +} + +static int +kwcmp(const void *k, const void *e) +{ + return strcmp(k, ((const struct keywords *)e)->name); +} + +static int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "cipher", CIPHER }, /* optional */ + { "connect", CONNECT }, + { "devtype", DEVTYPE }, /* optional */ + { "include", INCLUDE }, /* optional */ + { "interface", INTERFACE }, + { "listen", LISTEN }, + { "network", NETWORK }, + { "on", ON }, + { "passwd", PASSWD }, + { "port", PORT }, /* optional */ + { "proto", PROTO }, /* optional */ + { "timeout", TIMEOUT }, /* optional */ + { "user", USER } /* optional */ + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kwcmp); + + if (p) + return p->val; + else + return STRING; +} + +#define MAXPUSHBACK 128 + +static unsigned char *parsebuf; +static int parseindex; +static unsigned char pushback_buffer[MAXPUSHBACK]; +static int pushback_index = 0; + +static int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return c; + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return pushback_buffer[--pushback_index]; + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return EOF; + return quotec; + } + return c; + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return EOF; + c = getc(file->stream); + } + return c; +} + +static int +lungetc(int c) +{ + if (c == EOF) + return EOF; + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return c; + } + if (pushback_index < MAXPUSHBACK-1) + return pushback_buffer[pushback_index++] = c; + else + return EOF; +} + +static int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return ERROR; +} + +static int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return 0; + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return 0; + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') + continue; + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return findeol(); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + *p++ = c; + } + yylval.v.string = strdup((char *)buf); + if (!yylval.v.string) + fatal("strdup"); + return STRING; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum((char *)buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return findeol(); + } + return NUMBER; + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return c; + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup((char *)buf)) == STRING) + if (!(yylval.v.string = strdup((char *)buf))) + fatal("strdup"); + return token; + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return 0; + return c; +} + +static struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if (!(nfile = calloc(1, sizeof(struct file)))) + return NULL; + if (!(nfile->name = strdup(name))) { + free(nfile); + return NULL; + } + if (!(nfile->stream = fopen(nfile->name, "r"))) { + free(nfile->name); + free(nfile); + return NULL; + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return nfile; +} + +static int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry))) + prev->errors += file->errors; + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return file ? 0 : EOF; +} + +static void +resetnetconfig(struct netcfg *np) +{ + memset(np, 0, sizeof(*np)); + strlcpy(np->cipher, DEFCIPHER, sizeof(np->cipher)); + strlcpy(np->port, DEFPORT, sizeof(np->port)); + strlcpy(np->user, STUNUSER, sizeof(np->user)); + np->proto = AF_INET; + np->devtype = TUNDEV; + np->timeout = RECONNECTTIMEO; +} + +int +parseconf(const char *filename) +{ + struct stat sb; + struct netcfg *np; + int errors = 0; + + TAILQ_INIT(&netcfgs); + resetnetconfig(&curnet); + + if (stat(filename, &sb) == 0 && sb.st_mode & 007) { + logerrx("%s: shouldn't be readable by others", filename); + return -1; + } + + if (!(file = pushfile(filename))) + return -1; + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + if (errors != 0) + return -1; + + if (TAILQ_EMPTY(&netcfgs)) { + logerrx("%s: no network blocks defined", filename); + return -1; + } + + TAILQ_FOREACH(np, &netcfgs, entry) { + if (np->host[0] == '\0' && np->bindaddr[0] == '\0') { + logerrx("network %s: no connect or listen keyword", + np->name); + errors++; + } + if (np->passwd[0] == '\0') { + logerrx("network %s: passwd not specified", + np->name); + errors++; + } + if (np->ifname[0] == '\0') { + logerrx("network %s: interface not specified", + np->name); + errors++; + } + } + + return errors != 0 ? -1 : 0; +} diff --git a/queue.h b/queue.h @@ -0,0 +1,533 @@ +/* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues and XOR simple queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * An XOR simple queue is used in the same way as a regular simple queue. + * The difference is that the head structure also includes a "cookie" that + * is XOR'd with the queue pointer (first, last or next) to generate the + * real pointer value. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ + _Q_INVALIDATE((elm)->field.sle_next); \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods. + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_CONCAT(head1, head2) do { \ + if (!SIMPLEQ_EMPTY((head2))) { \ + *(head1)->sqh_last = (head2)->sqh_first; \ + (head1)->sqh_last = (head2)->sqh_last; \ + SIMPLEQ_INIT((head2)); \ + } \ +} while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqx_first; /* first element */ \ + struct type **sqx_last; /* addr of last next element */ \ + unsigned long sqx_cookie; \ +} + +#define XSIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqx_next; /* next element */ \ +} + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ + (unsigned long)(ptr))) +#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define XSIMPLEQ_END(head) NULL +#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + + +#define XSIMPLEQ_FOREACH(var, head, field) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) != XSIMPLEQ_END(head); \ + (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ + (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define XSIMPLEQ_INIT(head) do { \ + arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqx_next = (head)->sqx_first) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ + *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ + (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ + (elm)->field.sqx_next)->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * Tail queue access methods. + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/server.c b/server.c @@ -24,12 +24,12 @@ serverinit(char *host, char *port) hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; - if ((ret = getaddrinfo(host, port, &hints, &ai))) - logerr("getaddrinfo: %s", gai_strerror(ret)); + if ((ret = getaddrinfo(host, port, &hints, &ai))) { + logwarnx("getaddrinfo: %s", gai_strerror(ret)); + return -1; + } for (p = ai; p; p = p->ai_next) { - if (p->ai_family != aftype) - continue; if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; @@ -46,7 +46,7 @@ serverinit(char *host, char *port) break; } if (!p) - logerr("failed to bind socket"); + fatalx("failed to bind on %s:%s", host, port); freeaddrinfo(ai); return listenfd; } @@ -61,7 +61,7 @@ serveraccept(int listenfd) (socklen_t []){sizeof(remote)}); if (netfd < 0) { if (errno != ECONNABORTED) - logwarn("accept failed"); + logwarn("accept"); return -1; } @@ -71,7 +71,7 @@ serveraccept(int listenfd) if (challenge(netfd) < 0 || response(netfd) < 0) { close(netfd); - logwarn("challenge-response failed"); + logwarnx("challenge-response failed"); return -1; } return netfd; diff --git a/stun.8 b/stun.8 @@ -1,4 +1,4 @@ -.Dd April 21, 2016 +.Dd April 25, 2016 .Dt STUN 8 .Os .Sh NAME @@ -6,70 +6,60 @@ .Nd simple tunnel .Sh SYNOPSIS .Nm stun -.Op Fl 46 -.Op Fl d -.Fl s -.Op Fl b Ar address -.Op Fl p Ar port -.Op Fl t Ar devtype -.Op Fl c Ar cipher -.Op Fl u Ar user -.Ar interface -.Nm stun -.Op Fl 46 -.Op Fl d -.Fl h Ar host -.Op Fl p Ar port -.Op Fl t Ar devtype -.Op Fl c Ar cipher -.Op Fl u Ar user -.Ar interface +.Op Fl dnv +.Op Fl f Ar file .Sh DESCRIPTION .Nm -is a simple point to point tunnelling program. There can only be one -client per server. Routing between clients is done by the networking -stack on the server side. -.Sh OPTIONS -.Bl -tag -width "-b address" -.It Fl 4 -Force +is a simple point to point tunnelling daemon. It supports layer 2 +and layer 3 tunnels via tap and tun respectively. TCP is the only +supported transport in this release. +.Pp .Nm -to use IPv4 addresses only. -.It Fl 6 -Force +supports multiple AEAD ciphers. The default cipher is +.Ar chacha20-poly1305 . +The encryption key is derived with PBKDF. .Nm -to use IPv6 addresses only. +uses 100k rounds for the key derivation. +.Pp +When a client connects there is a mutual challenge-response phase +as shown below: +.Bd -literal -offset indent +t0: server challenges client +t1: client responds to server's challenge +t2: client challenges server +t3: server responds to client's challenge +.Ed +.Pp +The challenge is a randomly generated 64-bit integer encrypted +with the pre-shared key and sent to the receiver. +The receiver decrypts it, adds 1, encrypts it and sends it back to +the sender. The sender verifies the response. +.Sh OPTIONS +.Bl -tag -width "-f file" .It Fl d -Enable debug output. This will make +Debug mode. Start .Nm -run in the foreground. -.It Fl s -Enable server mode. Default is off. -.It Fl b Ar address -Bind server to the given -.Ar address . -.It Fl p Ar port -Listen on or connect to specified -.Ar port . -The default port is 12080. -.It Fl h Ar host -Connect to specified -.Ar host . -.It Fl t Ar devtype -Select the tunnel -.Ar device type . -The two available device types are TUN and TAP. The default is TUN. -.It Fl c Ar cipher -Select the given -.Ar cipher . -If the argument is ? then -.Nm -will list the available ciphers. The default cipher is chacha20-poly1305. -.It Fl u Ar user -Drop privileges to the specified -.Ar user . -The default user is nobody. +and do not detach or become a daemon. +.It Fl n +Check that the configuration is valid and exit. +.It Fl v +Enable verbose output. +.It Fl f Ar file +Specifies the configuration file. +.El +.Sh FILES +.Bl -tag -width "/etc/stun.conf" -compact +.It Pa /etc/stun.conf +Default configuration file. .El +.Sh SEE ALSO +.Xr stun.conf 5 +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Dimitris Papastamos Aq Mt sin@2f30.org . .Sh BUGS This program is an experiment and may not be secure. Use at your own risk. diff --git a/stun.c b/stun.c @@ -1,165 +1,61 @@ -/* - * Design overview: - * - * stun implements a point to point encrypted tunnel. It supports - * layer 2 (TAP) and layer 3 (TUN) tunnels. TCP is the only supported - * transport. - * - * stun is a client-server design. There can only be a single client - * connected to the server at any time. Routing between clients is handled - * by the networking stack on the server side. - * - * stun supports multiple AEAD ciphers: - * - * aes-128-gcm - * aes-256-gcm - * chacha20-poly1305 - * chacha20-poly1305-ietf - * - * The default cipher is chacha20-poly1305. The key is derived with - * PBKDF. - * - * When a client connects there is a mutual challenge-response phase - * as shown below: - * - * t0: server challenges client - * t1: client responds to server's challenge - * t2: client challenges server - * t3: server responds to client's challenge - * - * The challenge-response algorithm is as follows: - * - * The challenge is a randomly generated 64-bit integer encrypted - * with the pre-shared key and sent to the receiver. - * The receiver decrypts it, adds 1, encrypts it and sends it back to - * the sender. The sender verifies the response. - * This algorithm is the same as what Kerberos uses for authentication. - * - * The stun packet format is shown below: - * - * [NONCE] [PAYLOAD LEN] [PAYLOAD] [TAG] - * - */ - #include <sys/resource.h> +#include <sys/wait.h> +#include <errno.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#ifdef __linux__ +#include <bsd/unistd.h> +#endif + #include "arg.h" #include "stun.h" char *argv0; -char *bindaddr; -char *host; -char *port = DEFPORT; -char *cipher = DEFCIPHER; -int devtype = TUNDEV; -int aftype = AF_INET; -int debug; -int sflag; -void -usage(void) -{ - fprintf(stderr, "usage: stun [-46] [-d] -s [-b address] [-p port] [-t devtype] [-c cipher] [-u user] interface\n"); - fprintf(stderr, " stun [-46] [-d] -h host [-p port] [-t devtype] [-c cipher] [-u user] interface\n"); - exit(1); -} +/* + * Config options are passed via this global to each stun + * instance. + */ +struct netcfg netconfig; -int -main(int argc, char *argv[]) +struct pident { + pid_t pid; + SLIST_ENTRY(pident) entry; +}; +static SLIST_HEAD(pidents, pident) pidents; + +static void +chldrun(void) { - struct rlimit rlim; - char *arg, *pw; - char *user = NOPRIVUSER; int devfd, listenfd, netfd; - ARGBEGIN { - case '4': - aftype = AF_INET; - break; - case '6': - aftype = AF_INET6; - break; - case 'd': - debug = 1; - break; - case 's': - sflag = 1; - break; - case 'b': - bindaddr = EARGF(usage()); - break; - case 'h': - host = EARGF(usage()); - break; - case 'p': - port = EARGF(usage()); - break; - case 't': - arg = EARGF(usage()); - if (strcasecmp(arg, "tun") == 0) - devtype = TUNDEV; - else if (strcasecmp(arg, "tap") == 0) - devtype = TAPDEV; - else - usage(); - break; - case 'c': - cipher = EARGF(usage()); - if (strcmp(cipher, "?") == 0) { - listciphers(); - return 0; - } - break; - case 'u': - user = EARGF(usage()); - break; - default: - usage(); - } ARGEND - - if (argc != 1 || !(sflag ^ (host != NULL))) - usage(); - - loginit("stun"); - - /* disable core dumps as memory contains the pre-shared key */ - rlim.rlim_cur = rlim.rlim_max = 0; - if (setrlimit(RLIMIT_CORE, &rlim) < 0) - logerr("failed to disable core dumps"); - - signal(SIGPIPE, SIG_IGN); - if (!debug) - daemon(0, 0); + setproctitle("network: %s", netconfig.name); /* initialize tun/tap device */ - devfd = devopen(argv[0]); + devfd = devopen(netconfig.ifname); /* initialize crypto engine */ - if (!(pw = getenv("STUNPW"))) - logerr("STUNPW is not set"); - setcipher(cipher); - derivekey(pw); + setcipher(netconfig.cipher); + derivekey(netconfig.passwd); cryptoinit(); - memset(pw, 0, strlen(pw)); /* initialize networking engine */ netinit(); - if (sflag) { + if (netconfig.bindaddr[0] != '\0') { /* invoked as server */ - listenfd = serverinit(bindaddr, port); - revokeprivs(user); + listenfd = serverinit(netconfig.bindaddr, netconfig.port); + revokeprivs(netconfig.user); #if defined(__OpenBSD__) #include <sys/param.h> #if OpenBSD >= 201605 if (pledge("stdio inet", NULL) < 0) - logerr("pledge failed"); + fatal("pledge"); #endif #endif for (;;) { @@ -167,26 +63,27 @@ main(int argc, char *argv[]) netreset(); continue; } - logdbg("client %s is ready", peer_ntop(netfd)); + logdbgx("client %s is ready", peer_ntop(netfd)); tunnel(netfd, devfd); - logdbg("client %s disconnected", peer_ntop(netfd)); + logdbgx("client %s disconnected", peer_ntop(netfd)); close(netfd); netreset(); } } else { /* invoked as client */ - revokeprivs(user); + revokeprivs(netconfig.user); #if defined(__OpenBSD__) #include <sys/param.h> #if OpenBSD >= 201605 if (pledge("stdio dns inet", NULL) < 0) - logerr("pledge failed"); + fatal("pledge"); #endif #endif for (;;) { - if ((netfd = clientconnect(host, port)) < 0) { + if ((netfd = clientconnect(netconfig.host, + netconfig.port)) < 0) { netreset(); - sleep(RECONNECTTIMEO); + sleep(netconfig.timeout); continue; } logdbg("connected to %s", peer_ntop(netfd)); @@ -194,8 +91,107 @@ main(int argc, char *argv[]) logdbg("disconnected from %s", peer_ntop(netfd)); close(netfd); netreset(); - sleep(RECONNECTTIMEO); + sleep(netconfig.timeout); } } - return 0; +} + +void +usage(void) +{ + fprintf(stderr, "usage: stun [-dnv] [-f file]\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct pident *pe, *petmp; + struct netcfg *np; + struct rlimit rlim; + char *confname = PATHCONFIG; + int ret, status, checkonly = 0; + pid_t pid; + + ARGBEGIN { + case 'd': + debug = 1; + break; + case 'n': + checkonly = 1; + break; + case 'v': + verbose++; + break; + case 'f': + confname = EARGF(usage()); + break; + default: + usage(); + } ARGEND + + if (argc > 0) + usage(); + + loginit("stun"); + setproctitle("parent"); + + /* disable core dumps to avoid leaking pre-shared key */ + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + fatal("setrlimit RLIMIT_CORE"); + + signal(SIGPIPE, SIG_IGN); + if (!debug && daemon(1, 0) < 0) + fatal("daemon"); + + if (parseconf(confname) < 0) + fatalx("%s: invalid configuration", confname); + if (checkonly == 1) { + logdbgx("configuration OK"); + return 0; + } + + ret = 0; + TAILQ_FOREACH(np, &netcfgs, entry) { + /* global options per stun instance */ + netconfig = *np; + switch (pid = fork()) { + case -1: + ret = 1; + logwarn("fork"); + goto reap; + case 0: + chldrun(); + break; + default: + if (!(pe = malloc(sizeof(*pe)))) { + logerr("malloc"); + goto reap; + } + pe->pid = pid; + SLIST_INSERT_HEAD(&pidents, pe, entry); + break; + } + } + +reap: + while (!SLIST_EMPTY(&pidents)) { + if ((pid = wait(&status)) < 0) { + if (errno == EINTR) + continue; + fatal("wait"); + } + logdbgx("child %d exited with status %d", (int)pid, status); + SLIST_FOREACH_SAFE(pe, &pidents, entry, petmp) { + if (pid == pe->pid) { + SLIST_REMOVE(&pidents, pe, pident, entry); + free(pe); + break; + } + } + } + + logdbgx("parent terminated"); + return ret; } diff --git a/stun.conf.5 b/stun.conf.5 @@ -0,0 +1,178 @@ +.Dd April 26, 2016 +.Dt STUN.CONF 5 +.Os +.Sh NAME +.Nm stun.conf +.Nd stun daemon configuration file +.Sh DESCRIPTION +.Nm +is comprised of a sequence of network blocks. +.Pp +Each network block specifies a server or client but not both. The +use of the +.Ic listen +keyword implies a server. The use of the +.Ic connect +keyword implies a client. +.Pp +Within a network block a host can be specified as an IPv4 address, +IPv6 address or DNS hostname. The address +.Ar "0.0.0.0" +can be used to bind to all IPv4 addresses. Likewise, +the address +.Ar "::" +is used to bind to all IPv6 addresses. +A +.Ar port +can only be specified by a number. +.Pp +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Pp +Additional configuration files can be included with the +.Ic include +keyword. +.Sh NETWORKS +The configured networks. +.Pp +Each +.Ic network +block starts with a declaration of the network +.Ar name : +.Bl -tag -width Ds +.It Ic network Ar name Brq ... +Declare a +.Ic network +with the specified +.Ar name . +.El +.Pp +Followed by a block of options that is enclosed in curly brackets. +.Bl -tag -width Ds +.It Ic interface Ar name +Set the tunnel interface +.Ar name . +.It Ic devtype Ar type +Set the device +.Ar type . +Valid types are +.Ar "tun" +and +.Ar "tap" . +The default device type is +.Ar "tun". +.It Ic passwd Ar password +Set the +.Ar password . +.It Ic proto Ar name +For a client, force the protocol family to +the one specified by +.Ar name . +Valid names are +.Ar "inet4" +for IPv4 and +.Ar "inet6" +for IPv6. The default protocol family is +.Ar "inet4" . +This is useful to force DNS resolution to a particular +protocol family. +This option is not used for a server configuration. +.It Ic cipher Ar name +Set the cipher +.Ar name +for the given network. The supported ciphers are: +.Ar aes-128-gcm , +.Ar aes-256-gcm , +.Ar chacha20-poly1305 +and +.Ar chacha20-poly1305-ietf . +The default cipher is +.Ar chacha20-poly1305 . +Depending on the version of LibreSSL used some of these +ciphers may not be available. +.It Ic user Ar name +Set the user +.Ar name +used when revoking privileges. The default user account is +.Ar nobody . +.It Ic timeout Ar seconds +Set the reconnection timeout to the specified number of +.Ar seconds . +The default timeout is +.Ar 60 +seconds. +.It Ic listen on Ar address Oo Ic port Ar number Oc +Listen on specified +.Ar address +and port +.Ar number . +The default port +.Ar number +is +.Ar 12080 . +The last parsed specification is used if the +.Ic listen +keyword is set multiple times. +.It Ic connect Ar address Oo Ic port Ar number Oc +Connect to specified +.Ar address +and port +.Ar number . +The default port +.Ar number +is +.Ar 12080 . +The last parsed specification is used if the +.Ic connect +keyword is set multiple times. +.It Ic include Ar file +Include another configuration +.Ar file . +This allows one to have network blocks laid out in separate +files. +.El +.Sh EXAMPLES +The following example shows the server side configuration +for two networks. +.Bd -literal -offset indent +network "net1-srv" { + listen on "1.2.3.4" + interface "tun0" + password "changeme1" +} + +network "net2-srv" { + listen on "4.3.2.1" + interface "tun1" + cipher "aes-128-gcm" + password "changeme2" +} +.Ed +.Pp +The corresponding client side configuration would be: +.Bd -literal -offset indent +network "net1-cli" { + connect "1.2.3.4" + interface "tun0" + password "changeme1" +} + +network "net2-cli" { + connect "4.3.2.1" + interface "tun1" + cipher "aes-128-gcm" + password "changeme2" +} +.Ed +.Pp +Routing between the two networks is accomplished by the +networking stack on the server. +.Sh SEE ALSO +.Xr stun 8 +.Sh AUTHORS +.An -nosplit +The +.Xr stun 8 +program was written by +.An Dimitris Papastamos Aq Mt sin@2f30.org . diff --git a/stun.h b/stun.h @@ -3,7 +3,9 @@ #include <stddef.h> #include <stdint.h> -#define NOPRIVUSER "nobody" +#include "queue.h" + +#define STUNUSER "nobody" #define AUTHTIMEO 10 /* in seconds */ #define RECONNECTTIMEO 60 /* in seconds */ #define HDRLEN 2 @@ -11,6 +13,12 @@ #define NROUNDS 100000 #define DEFPORT "12080" #define DEFCIPHER "chacha20-poly1305" +#define PATHCONFIG "/etc/stun.conf" + +#undef MIN +#define MIN(x,y) ((x) < (y) ? (x) : (y)) +#undef MAX +#define MAX(x,y) ((x) > (y) ? (x) : (y)) enum { PKTFAILED, @@ -23,10 +31,24 @@ enum { TAPDEV }; +struct netcfg { + TAILQ_ENTRY(netcfg) entry; + int proto; + int devtype; + int timeout; + char bindaddr[256]; + char host[256]; + char port[32]; + char name[256]; + char cipher[256]; + char ifname[256]; + char passwd[256]; + char user[256]; +}; +TAILQ_HEAD(netcfgs, netcfg) netcfgs; + /* stun.c */ -extern int devtype; -extern int aftype; -extern int debug; +extern struct netcfg netconfig; /* auth.c */ int challenge(int); @@ -55,10 +77,18 @@ int devwrite(int, unsigned char *, int); int devread(int, unsigned char *, int); /* log.c */ +extern int debug; +extern int verbose; + void loginit(char *); void logdbg(char *, ...); +void logdbgx(char *, ...); void logwarn(char *, ...); +void logwarnx(char *, ...); void logerr(char *, ...); +void logerrx(char *, ...); +void fatal(char *, ...); +void fatalx(char *, ...); /* netpkt.c */ int netwrite(int, unsigned char *, size_t, size_t *); @@ -66,6 +96,9 @@ int netread(int, unsigned char *, size_t, size_t *); void netreset(void); void netinit(void); +/* parse.y */ +int parseconf(const char *); + /* server.c */ int serverinit(char *, char *); int serveraccept(int); diff --git a/tunnel.c b/tunnel.c @@ -1,3 +1,4 @@ +#include <errno.h> #include <poll.h> #include "stun.h" @@ -16,7 +17,9 @@ tunnel(int netfd, int devfd) pfd[1].events = POLLIN; for (;;) { if (poll(pfd, 2, -1) < 0) { - logwarn("poll failed"); + if (errno == EINTR) + continue; + logwarn("poll"); return -1; } diff --git a/util.c b/util.c @@ -58,11 +58,11 @@ revokeprivs(char *user) struct passwd *pw; if (!(pw = getpwnam(user))) - logerr("no %s user", user); + fatalx("no %s user", user); if (setgroups(1, &pw->pw_gid) < 0 || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) < 0 || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0) - logerr("failed to revoke privs"); + fatalx("failed to revoke privs"); } int @@ -89,10 +89,10 @@ saddr_ntop(struct sockaddr *sa, socklen_t salen) port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV))) { if (ret == EAI_SYSTEM) { - logwarn("getnameinfo failed"); + logwarnx("getnameinfo failed"); return NULL; } else { - logwarn("getnameinfo: %s", gai_strerror(ret)); + logwarnx("getnameinfo: %s", gai_strerror(ret)); return NULL; } } @@ -107,7 +107,7 @@ peer_ntop(int fd) socklen_t sslen = sizeof(ss); if (getpeername(fd, (struct sockaddr *)&ss, &sslen) < 0) { - logwarn("getpeername failed"); + logwarn("getpeername"); return NULL; } return saddr_ntop((struct sockaddr *)&ss, sslen); @@ -124,7 +124,7 @@ ipversion(unsigned char *pkt) case 6: return AF_INET6; default: - logwarn("unknown protocol version: %d", (int)ip->ip_v); + logwarnx("unknown protocol version: %d", (int)ip->ip_v); break; } return -1;