Merge branch 'master' into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Fri, 5 Jun 2009 21:03:28 +0000 (23:03 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Fri, 5 Jun 2009 21:14:13 +0000 (23:14 +0200)
Conflicts:
doc/tincd.8.in
lib/pidfile.c
src/graph.c
src/net.c
src/net.h
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/netutl.c
src/node.c
src/node.h
src/protocol_auth.c
src/protocol_key.c
src/tincd.c

23 files changed:
doc/tinc.conf.5.in
doc/tinc.texi
doc/tincd.8.in
lib/utils.c
lib/utils.h
src/graph.c
src/linux/device.c
src/net.h
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/netutl.c
src/netutl.h
src/node.c
src/node.h
src/openssl/digest.c
src/openssl/digest.h
src/protocol.h
src/protocol_auth.c
src/protocol_edge.c
src/protocol_key.c
src/protocol_subnet.c
src/tincd.c

index b3b94f8..223005f 100644 (file)
@@ -304,6 +304,10 @@ or
 .Va PrivateKeyFile
 specified in the configuration file.
 
+.It Va ProcessPriority Li = low | normal | high
+When this option is used the priority of the tincd process will be adjusted.
+Increasing the priority may help to reduce latency and packet loss on the VPN.
+
 .It Va TunnelServer Li = yes | no Po no Pc Bq experimental
 When this option is enabled tinc will no longer forward information between other tinc daemons,
 and will only allow nodes and subnets on the VPN which are present in the
index f1d438f..1378433 100644 (file)
@@ -308,7 +308,7 @@ If the @file{net/if_tun.h} header file is missing, install it from the source pa
 @subsection Configuration of Darwin (MacOS/X) kernels
 
 Tinc on Darwin relies on a tunnel driver for its data acquisition from the kernel.
-Tinc supports either the driver from @uref{http://www-user.rhrk.uni-kl.de/~nissler/tuntap/},
+Tinc supports either the driver from @uref{http://tuntaposx.sourceforge.net/},
 which supports both tun and tap style devices,
 and also the driver from from @uref{http://chrisp.de/en/projects/tunnel.html}.
 The former driver is recommended.
@@ -952,6 +952,11 @@ Note that there must be exactly one of PrivateKey
 or PrivateKeyFile
 specified in the configuration file.
 
+@cindex ProcessPriority
+@item ProcessPriority = <low|normal|high>
+When this option is used the priority of the tincd process will be adjusted.
+Increasing the priority may help to reduce latency and packet loss on the VPN.
+
 @cindex TunnelServer
 @item TunnelServer = <yes|no> (no) [experimental]
 When this option is enabled tinc will no longer forward information between other tinc daemons,
@@ -1523,6 +1528,23 @@ If @var{file} is omitted, the default is @file{@value{localstatedir}/log/tinc.@v
 Disables encryption and authentication.
 Only useful for debugging.
 
+@item -R, --chroot
+Change process root directory to the directory where the config file is
+located (@file{@value{sysconfdir}/tinc/@var{netname}/} as determined by
+-n/--net option or as given by -c/--config option), for added security.
+The chroot is performed after all the initialization is done, after
+writing pid files and opening network sockets.
+
+Note that this option alone does not do any good without -U/--user, below.
+
+Note also that tinc can't run scripts anymore (such as tinc-down or host-up),
+unless it's setup to be runnable inside chroot environment.
+
+@item -U, --user=@var{user}
+Switch to the given @var{user} after initialization, at the same time as
+chroot is performed (see --chroot above).  With this option tinc drops
+privileges, for added security.
+
 @item --help
 Display a short reminder of these runtime options and terminate.
 
index e4bbaeb..966cc77 100644 (file)
@@ -8,7 +8,7 @@
 .Nd tinc VPN daemon
 .Sh SYNOPSIS
 .Nm
-.Op Fl cdDKnL
+.Op Fl cdDKnLRU
 .Op Fl -config Ns = Ns Ar DIR
 .Op Fl -no-detach
 .Op Fl -debug Ns Op = Ns Ar LEVEL
@@ -16,6 +16,8 @@
 .Op Fl -mlock
 .Op Fl -logfile Ns Op = Ns Ar FILE
 .Op Fl -bypass-security
+.Op Fl -chroot
+.Op Fl -user Ns = Ns Ar USER
 .Op Fl -help
 .Op Fl -version
 .Sh DESCRIPTION
@@ -70,6 +72,14 @@ is omitted, the default is
 .It Fl -bypass-security
 Disables encryption and authentication of the meta protocol.
 Only useful for debugging.
+.It Fl -chroot
+With this option tinc chroots into the directory where network
+config is located (@sysconfdir@/tinc/NETNAME if -n option is used,
+or to the directory specified with -c option) after initialization.
+.It Fl -user Ns = Ns Ar USER
+setuid to the specified
+.Ar USER
+after initialization.
 .It Fl -help
 Display short list of options.
 .It Fl -version
index b00a73b..beabfea 100644 (file)
@@ -29,7 +29,7 @@ volatile char (*cp_file[]) = {"?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
 volatile int cp_index = 0;
 #endif
 
-char *hexadecimals = "0123456789ABCDEF";
+const char hexadecimals[] = "0123456789ABCDEF";
 
 int charhex2bin(char c) {
        if(isdigit(c))
@@ -81,7 +81,7 @@ void cp_trace() {
 #include <w32api/windows.h>
 #endif
 
-char *winerror(int err) {
+const char *winerror(int err) {
        static char buf[1024], *newline;
 
        if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
index 6ff2082..04c4ddb 100644 (file)
@@ -39,7 +39,7 @@ extern void hex2bin(char *src, char *dst, int length);
 extern void bin2hex(char *src, char *dst, int length);
 
 #ifdef HAVE_MINGW
-extern char *winerror(int);
+extern const char *winerror(int);
 #define strerror(x) ((x)>0?strerror(x):winerror(GetLastError()))
 #endif
 
index 8e801b3..4e060b7 100644 (file)
@@ -312,27 +312,8 @@ void sssp_bfs(void) {
                        e->to->via = indirect ? n->via : e->to;
                        e->to->options = e->options;
 
-                       if(sockaddrcmp(&e->to->address, &e->address)) {
-                               node = splay_unlink(node_udp_tree, e->to);
-                               sockaddrfree(&e->to->address);
-                               sockaddrcpy(&e->to->address, &e->address);
-
-                               if(e->to->hostname)
-                                       free(e->to->hostname);
-
-                               e->to->hostname = sockaddr2hostname(&e->to->address);
-
-                               if(node)
-                                       splay_insert_node(node_udp_tree, node);
-
-                               if(e->to->options & OPTION_PMTU_DISCOVERY) {
-                                       e->to->mtuprobes = 0;
-                                       e->to->minmtu = 0;
-                                       e->to->maxmtu = MTU;
-                                       if(e->to->status.validkey)
-                                               send_mtu_probe(e->to);
-                               }
-                       }
+                       if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
+                               update_node_udp(e->to, &e->address);
 
                        list_insert_tail(todo_list, e->to);
                }
@@ -364,13 +345,13 @@ void check_reachability() {
                        if(n->status.reachable) {
                                ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became reachable"),
                                           n->name, n->hostname);
-                               splay_insert(node_udp_tree, n);
                        } else {
                                ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became unreachable"),
                                           n->name, n->hostname);
-                               splay_delete(node_udp_tree, n);
                        }
 
+                       /* TODO: only clear status.validkey if node is unreachable? */
+
                        n->status.validkey = false;
                        n->status.waitingforkey = false;
 
index b02e88e..e692ca9 100644 (file)
@@ -62,7 +62,8 @@ bool setup_device(void) {
 
        if(!get_config_string(lookup_config(config_tree, "Interface"), &iface))
 #ifdef HAVE_LINUX_IF_TUN_H
-               iface = xstrdup(netname);
+               if (netname != NULL)
+                       iface = xstrdup(netname);
 #else
                iface = xstrdup(rindex(device, '/') ? rindex(device, '/') + 1 : device);
 #endif
index d320c15..a28f478 100644 (file)
--- a/src/net.h
+++ b/src/net.h
@@ -131,7 +131,7 @@ extern int setup_vpn_in_socket(const sockaddr_t *);
 extern void send_packet(const struct node_t *, vpn_packet_t *);
 extern void receive_tcppacket(struct connection_t *, char *, int);
 extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
-extern bool setup_network_connections(void);
+extern bool setup_network(void);
 extern void setup_outgoing_connection(struct outgoing_t *);
 extern void try_outgoing_connections(void);
 extern void close_network_connections(void);
index 7d640cb..062e0be 100644 (file)
@@ -49,6 +49,7 @@
 #endif
 
 int keylifetime = 0;
+int keyexpires = 0;
 static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS];
 
 static void send_udppacket(node_t *, vpn_packet_t *);
@@ -99,15 +100,15 @@ void send_mtu_probe(node_t *n) {
        send_mtu_probe_handler(0, 0, n);
 }
 
-void mtu_probe_h(node_t *n, vpn_packet_t *packet) {
+void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
        ifdebug(TRAFFIC) logger(LOG_INFO, _("Got MTU probe length %d from %s (%s)"), packet->len, n->name, n->hostname);
 
        if(!packet->data[0]) {
                packet->data[0] = 1;
                send_packet(n, packet);
        } else {
-               if(n->minmtu < packet->len)
-                       n->minmtu = packet->len;
+               if(n->minmtu < len)
+                       n->minmtu = len;
        }
 }
 
@@ -160,7 +161,16 @@ static void receive_packet(node_t *n, vpn_packet_t *packet) {
        route(n, packet);
 }
 
-static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
+static bool try_mac(node_t *n, const vpn_packet_t *inpkt)
+{
+       if(!digest_active(&n->indigest) || !n->inmaclength || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
+               return false;
+
+       return digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
+}
+
+static void receive_udppacket(node_t *n, vpn_packet_t *inpkt)
+{
        vpn_packet_t pkt1, pkt2;
        vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 };
        int nextpkt = 0;
@@ -170,9 +180,15 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
 
        cp();
 
+       if(!cipher_active(&n->incipher)) {
+               ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got packet from %s (%s) but he hasn't got our key yet"),
+                                       n->name, n->hostname);
+               return;
+       }
+
        /* Check packet length */
 
-       if(inpkt->len < sizeof inpkt->seqno + digest_length(&myself->digest)) {
+       if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got too short packet from %s (%s)"),
                                        n->name, n->hostname);
                return;
@@ -180,18 +196,18 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
 
        /* Check the message authentication code */
 
-       if(digest_active(&myself->digest) && !digest_verify(&myself->digest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len)) {
+       if(digest_active(&n->indigest) && !digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got unauthenticated packet from %s (%s)"), n->name, n->hostname);
                return;
        }
 
        /* Decrypt the packet */
 
-       if(cipher_active(&myself->cipher)) {
+       if(cipher_active(&n->incipher)) {
                outpkt = pkt[nextpkt++];
                outlen = MAXSIZE;
 
-               if(!cipher_decrypt(&myself->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+               if(!cipher_decrypt(&n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
                        ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Error decrypting packet from %s (%s)"), n->name, n->hostname);
                        return;
                }
@@ -233,22 +249,26 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
 
        /* Decompress the packet */
 
-       if(myself->compression) {
+       length_t origlen = inpkt->len;
+
+       if(n->incompression) {
                outpkt = pkt[nextpkt++];
 
-               if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, myself->compression)) < 0) {
+               if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression)) < 0) {
                        ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while uncompressing packet from %s (%s)"),
                                                 n->name, n->hostname);
                        return;
                }
 
                inpkt = outpkt;
+
+               origlen -= MTU/64 + 20;
        }
 
        inpkt->priority = 0;
 
        if(!inpkt->data[12] && !inpkt->data[13])
-               mtu_probe_h(n, inpkt);
+               mtu_probe_h(n, inpkt, origlen);
        else
                receive_packet(n, inpkt);
 }
@@ -290,7 +310,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
                                   n->name, n->hostname);
 
                if(!n->status.waitingforkey)
-                       send_req_key(n->nexthop->connection, myself, n);
+                       send_req_key(n);
 
                n->status.waitingforkey = true;
 
@@ -299,12 +319,14 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
                return;
        }
 
-       if(!n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
+       if(n->options & OPTION_PMTU_DISCOVERY && !n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
                ifdebug(TRAFFIC) logger(LOG_INFO,
                                _("No minimum MTU established yet for %s (%s), forwarding via TCP"),
                                n->name, n->hostname);
 
                send_tcppacket(n->nexthop->connection, origpkt);
+
+               return;
        }
 
        origlen = inpkt->len;
@@ -312,10 +334,10 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
 
        /* Compress the packet */
 
-       if(n->compression) {
+       if(n->outcompression) {
                outpkt = pkt[nextpkt++];
 
-               if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->compression)) < 0) {
+               if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) {
                        ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while compressing packet to %s (%s)"),
                                   n->name, n->hostname);
                        return;
@@ -331,11 +353,11 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
 
        /* Encrypt the packet */
 
-       if(cipher_active(&n->cipher)) {
+       if(cipher_active(&n->outcipher)) {
                outpkt = pkt[nextpkt++];
                outlen = MAXSIZE;
 
-               if(!cipher_encrypt(&n->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+               if(!cipher_encrypt(&n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
                        ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while encrypting packet to %s (%s)"), n->name, n->hostname);
                        goto end;
                }
@@ -346,9 +368,9 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
 
        /* Add the message authentication code */
 
-       if(digest_active(&n->digest)) {
-               digest_create(&n->digest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
-               inpkt->len += digest_length(&n->digest);
+       if(digest_active(&n->outdigest)) {
+               digest_create(&n->outdigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
+               inpkt->len += digest_length(&n->outdigest);
        }
 
        /* Determine which socket we have to use */
@@ -434,9 +456,15 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
        ifdebug(TRAFFIC) logger(LOG_INFO, _("Broadcasting packet of %d bytes from %s (%s)"),
                           packet->len, from->name, from->hostname);
 
-       if(from != myself)
+       if(from != myself) {
                send_packet(myself, packet);
 
+               // In TunnelServer mode, do not forward broadcast packets.
+                // The MST might not be valid and create loops.
+               if(tunnelserver)
+                       return;
+       }
+
        for(node = connection_tree->head; node; node = node->next) {
                c = node->data;
 
@@ -445,6 +473,30 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
        }
 }
 
+static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
+       splay_node_t *node;
+       edge_t *e;
+       node_t *n = NULL;
+
+       for(node = edge_weight_tree->head; node; node = node->next) {
+               e = node->data;
+
+               if(sockaddrcmp_noport(from, &e->address))
+                       continue;
+
+               if(!n)
+                       n = e->to;
+
+               if(!try_mac(e->to, pkt))
+                       continue;
+
+               n = e->to;
+               break;
+       }
+
+       return n;
+}
+
 void handle_incoming_vpn_data(int sock, short events, void *data)
 {
        vpn_packet_t pkt;
@@ -467,11 +519,17 @@ void handle_incoming_vpn_data(int sock, short events, void *data)
        n = lookup_node_udp(&from);
 
        if(!n) {
-               hostname = sockaddr2hostname(&from);
-               logger(LOG_WARNING, _("Received UDP packet from unknown source %s"),
-                          hostname);
-               free(hostname);
-               return;
+               n = try_harder(&from, &pkt);
+               if(n)
+                       update_node_udp(n, &from);
+               else ifdebug(PROTOCOL) {
+                       hostname = sockaddr2hostname(&from);
+                       logger(LOG_WARNING, _("Received UDP packet from unknown source %s"), hostname);
+                       free(hostname);
+                       return;
+               }
+               else
+                       return;
        }
 
        receive_udppacket(n, &pkt);
index 43adbc8..17eaec2 100644 (file)
@@ -148,14 +148,8 @@ static void keyexpire_handler(int fd, short events, void *data) {
 }
 
 void regenerate_key() {
-       ifdebug(STATUS) logger(LOG_INFO, _("Regenerating symmetric key"));
-
-       if(!cipher_regenerate_key(&myself->cipher, true)) {
-               logger(LOG_ERR, _("Error regenerating key!"));
-               abort();
-       }
-
        if(timeout_initialized(&keyexpire_event)) {
+               ifdebug(STATUS) logger(LOG_INFO, _("Expiring symmetric keys"));
                event_del(&keyexpire_event);
                send_key_changed(broadcast, myself);
        } else {
@@ -270,7 +264,7 @@ bool setup_myself(void) {
 
 #if !defined(SOL_IP) || !defined(IP_TOS)
        if(priorityinheritance)
-               logger(LOG_WARNING, _("PriorityInheritance not supported on this platform"));
+               logger(LOG_WARNING, _("%s not supported on this platform"), "PriorityInheritance");
 #endif
 
        if(!get_config_int(lookup_config(config_tree, "MACExpire"), &macexpire))
@@ -305,7 +299,7 @@ bool setup_myself(void) {
        if(!get_config_string(lookup_config(myself->connection->config_tree, "Cipher"), &cipher))
                cipher = xstrdup("blowfish");
 
-       if(!cipher_open_by_name(&myself->cipher, cipher)) {
+       if(!cipher_open_by_name(&myself->incipher, cipher)) {
                logger(LOG_ERR, _("Unrecognized cipher type!"));
                return false;
        }
@@ -320,18 +314,18 @@ bool setup_myself(void) {
        if(!get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest))
                digest = xstrdup("sha1");
 
-       if(!digest_open_by_name(&myself->digest, digest)) {
+       if(!digest_open_by_name(&myself->indigest, digest)) {
                logger(LOG_ERR, _("Unrecognized digest type!"));
                return false;
        }
 
-       if(!get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->maclength))
+       if(!get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->inmaclength))
 
-       if(digest_active(&myself->digest)) {
-               if(myself->maclength > digest_length(&myself->digest)) {
+       if(digest_active(&myself->indigest)) {
+               if(myself->inmaclength > digest_length(&myself->indigest)) {
                        logger(LOG_ERR, _("MAC length exceeds size of digest!"));
                        return false;
-               } else if(myself->maclength < 0) {
+               } else if(myself->inmaclength < 0) {
                        logger(LOG_ERR, _("Bogus MAC length!"));
                        return false;
                }
@@ -339,13 +333,13 @@ bool setup_myself(void) {
 
        /* Compression */
 
-       if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), &myself->compression)) {
-               if(myself->compression < 0 || myself->compression > 11) {
+       if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), &myself->incompression)) {
+               if(myself->incompression < 0 || myself->incompression > 11) {
                        logger(LOG_ERR, _("Bogus compression level!"));
                        return false;
                }
        } else
-               myself->compression = 0;
+               myself->incompression = 0;
 
        myself->connection->outcompression = 0;
 
@@ -467,9 +461,10 @@ bool setup_myself(void) {
 }
 
 /*
-  setup all initial network connections
+  initialize network
 */
-bool setup_network_connections(void) {
+bool setup_network(void)
+{
        cp();
 
        init_connections();
@@ -496,8 +491,6 @@ bool setup_network_connections(void) {
        if(!setup_myself())
                return false;
 
-       try_outgoing_connections();
-
        return true;
 }
 
index 43b8ada..2dfaa2f 100644 (file)
@@ -33,6 +33,8 @@
 #include "utils.h"
 #include "xalloc.h"
 
+#include <assert.h>
+
 #ifdef WSAEINPROGRESS
 #define EINPROGRESS WSAEINPROGRESS
 #endif
@@ -80,7 +82,95 @@ static void configure_tcp(connection_t *c) {
 #endif
 }
 
-int setup_listen_socket(const sockaddr_t *sa) {
+static bool bind_to_interface(int sd) { /* {{{ */
+       char *iface;
+
+#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
+       struct ifreq ifr;
+       int status;
+#endif /* defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) */
+
+       if(!get_config_string (lookup_config (config_tree, "BindToInterface"), &iface))
+               return true;
+
+#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
+       ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0;
+
+       status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
+       if(status) {
+               logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface,
+                               strerror(errno));
+               return false;
+       }
+#else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */
+       logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface");
+#endif
+
+       return true;
+} /* }}} bool bind_to_interface */
+
+static bool bind_to_address(connection_t *c) { /* {{{ */
+       char *node;
+       struct addrinfo *ai_list;
+       struct addrinfo *ai_ptr;
+       struct addrinfo ai_hints;
+       int status;
+
+       assert(c != NULL);
+       assert(c->socket >= 0);
+
+       node = NULL;
+       if(!get_config_string(lookup_config(config_tree, "BindToAddress"),
+                               &node))
+               return true;
+
+       assert(node != NULL);
+
+       memset(&ai_hints, 0, sizeof(ai_hints));
+       ai_hints.ai_family = c->address.sa.sa_family;
+       /* We're called from `do_outgoing_connection' only. */
+       ai_hints.ai_socktype = SOCK_STREAM;
+       ai_hints.ai_protocol = IPPROTO_TCP;
+
+       ai_list = NULL;
+
+       status = getaddrinfo(node, /* service = */ NULL,
+                       &ai_hints, &ai_list);
+       if(status) {
+               free(node);
+               logger(LOG_WARNING, _("Error looking up %s port %s: %s"),
+                               node, _("any"), gai_strerror(status));
+               return false;
+       }
+       assert(ai_list != NULL);
+
+       status = -1;
+       for(ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+               status = bind(c->socket,
+                               ai_list->ai_addr, ai_list->ai_addrlen);
+               if(!status)
+                       break;
+       }
+
+
+       if(status) {
+               logger(LOG_ERR, _("Can't bind to %s/tcp: %s"), node,
+                               strerror(errno));
+       } else ifdebug(CONNECTIONS) {
+               logger(LOG_DEBUG, "Successfully bound outgoing "
+                               "TCP socket to %s", node);
+       }
+
+       free(node);
+       freeaddrinfo(ai_list);
+
+       return status ? false : true;
+} /* }}} bool bind_to_address */
+
+int setup_listen_socket(const sockaddr_t *sa)
+{
        int nfd;
        char *addrstr;
        int option;
@@ -120,7 +210,7 @@ int setup_listen_socket(const sockaddr_t *sa) {
                        return -1;
                }
 #else
-               logger(LOG_WARNING, _("BindToInterface not supported on this platform"));
+               logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface");
 #endif
        }
 
@@ -202,24 +292,10 @@ int setup_vpn_in_socket(const sockaddr_t *sa) {
        }
 #endif
 
-#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
-       {
-               char *iface;
-               struct ifreq ifr;
-
-               if(get_config_string(lookup_config(config_tree, "BindToInterface"), &iface)) {
-                       memset(&ifr, 0, sizeof ifr);
-                       strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
-
-                       if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr)) {
-                               closesocket(nfd);
-                               logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface,
-                                          strerror(errno));
-                               return -1;
-                       }
-               }
+       if (!bind_to_interface(nfd)) {
+               closesocket(nfd);
+               return -1;
        }
-#endif
 
        if(bind(nfd, &sa->sa, SALEN(sa->sa))) {
                closesocket(nfd);
@@ -231,7 +307,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) {
        }
 
        return nfd;
-}
+} /* int setup_vpn_in_socket */
 
 static void retry_outgoing_handler(int fd, short events, void *data) {
        setup_outgoing_connection(data);
@@ -329,6 +405,9 @@ begin:
                setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, &option, sizeof option);
 #endif
 
+       bind_to_interface(c->socket);
+       bind_to_address(c);
+
        /* Optimize TCP settings */
 
        configure_tcp(c);
index 116d858..39cd790 100644 (file)
@@ -140,7 +140,41 @@ char *sockaddr2hostname(const sockaddr_t *sa) {
        return str;
 }
 
-int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) {
+int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b)
+{
+       int result;
+
+       cp();
+
+       result = a->sa.sa_family - b->sa.sa_family;
+
+       if(result)
+               return result;
+
+       switch (a->sa.sa_family) {
+               case AF_UNSPEC:
+                       return 0;
+
+               case AF_UNKNOWN:
+                       return strcmp(a->unknown.address, b->unknown.address);
+
+               case AF_INET:
+                       return memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr));
+
+               case AF_INET6:
+                       return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
+
+               default:
+                       logger(LOG_ERR, _("sockaddrcmp() was called with unknown address family %d, exitting!"),
+                                  a->sa.sa_family);
+                       cp_trace();
+                       raise(SIGFPE);
+                       exit(0);
+       }
+}
+
+int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b)
+{
        int result;
 
        cp();
index bd642b1..aa2a173 100644 (file)
@@ -32,6 +32,7 @@ extern sockaddr_t str2sockaddr(const char *, const char *);
 extern void sockaddr2str(const sockaddr_t *, char **, char **);
 extern char *sockaddr2hostname(const sockaddr_t *);
 extern int sockaddrcmp(const sockaddr_t *, const sockaddr_t *);
+extern int sockaddrcmp_noport(const sockaddr_t *, const sockaddr_t *);
 extern void sockaddrunmap(sockaddr_t *);
 extern void sockaddrfree(sockaddr_t *);
 extern void sockaddrcpy(sockaddr_t *, const sockaddr_t *);
index 80d28d6..5df8b66 100644 (file)
@@ -90,8 +90,10 @@ void free_node(node_t *n) {
 
        sockaddrfree(&n->address);
 
-       cipher_close(&n->cipher);
-       digest_close(&n->digest);
+       cipher_close(&n->incipher);
+       digest_close(&n->indigest);
+       cipher_close(&n->outcipher);
+       digest_close(&n->outdigest);
 
        event_del(&n->mtuevent);
        
@@ -130,6 +132,7 @@ void node_del(node_t *n) {
        }
 
        splay_delete(node_tree, n);
+       splay_delete(node_udp_tree, n);
 }
 
 node_t *lookup_node(char *name) {
@@ -153,6 +156,25 @@ node_t *lookup_node_udp(const sockaddr_t *sa) {
        return splay_search(node_udp_tree, &n);
 }
 
+void update_node_udp(node_t *n, const sockaddr_t *sa)
+{
+       splay_delete(node_udp_tree, n);
+
+       if(n->hostname)
+               free(n->hostname);
+
+       if(sa) {
+               n->address = *sa;
+               n->hostname = sockaddr2hostname(&n->address);
+               splay_delete(node_udp_tree, n);
+               splay_insert(node_udp_tree, n);
+               logger(LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname);
+       } else {
+               memset(&n->address, 0, sizeof n->address);
+               logger(LOG_DEBUG, "UDP address of %s cleared", n->name);
+       }
+}
+
 int dump_nodes(struct evbuffer *out) {
        splay_node_t *node;
        node_t *n;
@@ -162,8 +184,8 @@ int dump_nodes(struct evbuffer *out) {
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
                if(evbuffer_add_printf(out, _(" %s at %s cipher %d digest %d maclength %d compression %d options %lx status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)\n"),
-                          n->name, n->hostname, cipher_get_nid(&n->cipher),
-                          digest_get_nid(&n->digest), n->maclength, n->compression,
+                          n->name, n->hostname, cipher_get_nid(&n->outcipher),
+                          digest_get_nid(&n->outdigest), n->outmaclength, n->outcompression,
                           n->options, *(uint32_t *)&n->status, n->nexthop ? n->nexthop->name : "-",
                           n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu) == -1)
                        return errno;
index f4fc88b..9b0d136 100644 (file)
@@ -54,11 +54,16 @@ typedef struct node_t {
 
        node_status_t status;
 
-       cipher_t cipher;                        /* Cipher for UDP packets */
-       digest_t digest;                        /* Digest for UDP packets */    
-       int maclength;                          /* Portion of digest to use */
+       cipher_t incipher;                        /* Cipher for UDP packets */
+       digest_t indigest;                        /* Digest for UDP packets */  
+       int inmaclength;                                /* Portion of digest to use */
 
-       int compression;                        /* Compressionlevel, 0 = no compression */
+       cipher_t outcipher;                        /* Cipher for UDP packets */
+       digest_t outdigest;                        /* Digest for UDP packets */ 
+       int outmaclength;                               /* Portion of digest to use */
+
+       int incompression;                      /* Compressionlevel, 0 = no compression */
+       int outcompression;                     /* Compressionlevel, 0 = no compression */
 
        int distance;
        struct node_t *nexthop;                 /* nearest node from us to him */
@@ -94,5 +99,6 @@ extern void node_del(node_t *);
 extern node_t *lookup_node(char *);
 extern node_t *lookup_node_udp(const sockaddr_t *);
 extern int dump_nodes(struct evbuffer *);
+extern void update_node_udp(node_t *, const sockaddr_t *);
 
 #endif                                                 /* __TINC_NODE_H__ */
index 7c4dfc2..1e2557d 100644 (file)
@@ -52,7 +52,7 @@ bool digest_open_sha1(digest_t *digest) {
 void digest_close(digest_t *digest) {
 }
 
-bool digest_create(digest_t *digest, void *indata, size_t inlen, void *outdata) {
+bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) {
        EVP_MD_CTX ctx;
 
        if(EVP_DigestInit(&ctx, digest->digest)
@@ -64,7 +64,7 @@ bool digest_create(digest_t *digest, void *indata, size_t inlen, void *outdata)
        return false;
 }
 
-bool digest_verify(digest_t *digest, void *indata, size_t inlen, void *cmpdata) {
+bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *cmpdata) {
        size_t len = EVP_MD_size(digest->digest);
        char outdata[len];
 
index f10e67f..deba7d3 100644 (file)
@@ -34,8 +34,8 @@ extern bool digest_open_by_name(struct digest *, const char *);
 extern bool digest_open_by_nid(struct digest *, int);
 extern bool digest_open_sha1(struct digest *);
 extern void digest_close(struct digest *);
-extern bool digest_create(struct digest *, void *indata, size_t inlen, void *outdata);
-extern bool digest_verify(struct digest *, void *indata, size_t inlen, void *digestdata);
+extern bool digest_create(struct digest *, const void *indata, size_t inlen, void *outdata);
+extern bool digest_verify(struct digest *, const void *indata, size_t inlen, const void *digestdata);
 extern int digest_get_nid(const struct digest *);
 extern size_t digest_length(const struct digest *);
 extern bool digest_active(const struct digest *);
index cecc580..8f53072 100644 (file)
@@ -96,9 +96,9 @@ extern bool send_add_subnet(struct connection_t *, const struct subnet_t *);
 extern bool send_del_subnet(struct connection_t *, const struct subnet_t *);
 extern bool send_add_edge(struct connection_t *, const struct edge_t *);
 extern bool send_del_edge(struct connection_t *, const struct edge_t *);
-extern bool send_key_changed(struct connection_t *, const struct node_t *);
-extern bool send_req_key(struct connection_t *, const struct node_t *, const struct node_t *);
-extern bool send_ans_key(struct connection_t *, const struct node_t *, const struct node_t *);
+extern bool send_key_changed();
+extern bool send_req_key(struct node_t *);
+extern bool send_ans_key(struct node_t *);
 extern bool send_tcppacket(struct connection_t *, struct vpn_packet_t *);
 
 /* Request handlers  */
index 2109cd4..93fe23e 100644 (file)
@@ -243,7 +243,7 @@ bool send_challenge(connection_t *c) {
        cp();
 
        if(!c->hischallenge)
-               c->hischallenge = xmalloc(len);
+               c->hischallenge = xrealloc(c->hischallenge, len);
 
        /* Copy random data to the buffer */
 
index f515eee..3577611 100644 (file)
@@ -93,6 +93,17 @@ bool add_edge_h(connection_t *c, char *request) {
        /* Lookup nodes */
 
        from = lookup_node(from_name);
+       to = lookup_node(to_name);
+
+       if(tunnelserver &&
+          from != myself && from != c->node &&
+          to != myself && to != c->node) {
+               /* ignore indirect edge registrations for tunnelserver */
+               ifdebug(PROTOCOL) logger(LOG_WARNING,
+                  _("Ignoring indirect %s from %s (%s)"),
+                  "ADD_EDGE", c->name, c->hostname);
+               return true;
+       }
 
        if(!from) {
                from = new_node();
@@ -100,16 +111,12 @@ bool add_edge_h(connection_t *c, char *request) {
                node_add(from);
        }
 
-       to = lookup_node(to_name);
-
        if(!to) {
                to = new_node();
                to->name = xstrdup(to_name);
                node_add(to);
        }
 
-       if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node)
-               return false;
 
        /* Convert addresses */
 
@@ -206,6 +213,17 @@ bool del_edge_h(connection_t *c, char *request) {
        /* Lookup nodes */
 
        from = lookup_node(from_name);
+       to = lookup_node(to_name);
+
+       if(tunnelserver &&
+          from != myself && from != c->node &&
+          to != myself && to != c->node) {
+               /* ignore indirect edge registrations for tunnelserver */
+               ifdebug(PROTOCOL) logger(LOG_WARNING,
+                  _("Ignoring indirect %s from %s (%s)"),
+                  "DEL_EDGE", c->name, c->hostname);
+               return true;
+       }
 
        if(!from) {
                ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"),
@@ -213,17 +231,12 @@ bool del_edge_h(connection_t *c, char *request) {
                return true;
        }
 
-       to = lookup_node(to_name);
-
        if(!to) {
                ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"),
                                   "DEL_EDGE", c->name, c->hostname);
                return true;
        }
 
-       if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node)
-               return false;
-
        /* Check if edge exists */
 
        e = lookup_edge(from, to);
index 3cf4ab1..06ce733 100644 (file)
@@ -25,6 +25,7 @@
 #include "splay_tree.h"
 #include "cipher.h"
 #include "connection.h"
+#include "crypto.h"
 #include "logger.h"
 #include "net.h"
 #include "netutl.h"
 
 static bool mykeyused = false;
 
-bool send_key_changed(connection_t *c, const node_t *n) {
+bool send_key_changed() {
        cp();
 
        /* Only send this message if some other daemon requested our key previously.
           This reduces unnecessary key_changed broadcasts.
         */
 
-       if(n == myself && !mykeyused)
+       if(!mykeyused)
                return true;
 
-       return send_request(c, "%d %lx %s", KEY_CHANGED, random(), n->name);
+       return send_request(broadcast, "%d %lx %s", KEY_CHANGED, random(), myself->name);
 }
 
 bool key_changed_h(connection_t *c, char *request) {
@@ -82,10 +83,10 @@ bool key_changed_h(connection_t *c, char *request) {
        return true;
 }
 
-bool send_req_key(connection_t *c, const node_t *from, const node_t *to) {
+bool send_req_key(node_t *to) {
        cp();
 
-       return send_request(c, "%d %s %s", REQ_KEY, from->name, to->name);
+       return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
 }
 
 bool req_key_h(connection_t *c, char *request) {
@@ -120,10 +121,8 @@ bool req_key_h(connection_t *c, char *request) {
        /* Check if this key request is for us */
 
        if(to == myself) {                      /* Yes, send our own key back */
-               mykeyused = true;
-               from->received_seqno = 0;
-               memset(from->late, 0, sizeof from->late);
-               send_ans_key(c, myself, from);
+
+               send_ans_key(from);
        } else {
                if(tunnelserver)
                        return false;
@@ -134,27 +133,39 @@ bool req_key_h(connection_t *c, char *request) {
                        return true;
                }
 
-               send_req_key(to->nexthop->connection, from, to);
+               send_request(to->nexthop->connection, "%s", request);
        }
 
        return true;
 }
 
-bool send_ans_key(connection_t *c, const node_t *from, const node_t *to) {
-       size_t keylen = cipher_keylength(&from->cipher);
+bool send_ans_key(node_t *to) {
+       size_t keylen = cipher_keylength(&myself->incipher);
        char key[keylen * 2 + 1];
 
        cp();
 
-       cipher_get_key(&from->cipher, key);
+       cipher_open_by_nid(&to->incipher, cipher_get_nid(&myself->incipher));
+       digest_open_by_nid(&to->indigest, digest_get_nid(&myself->indigest));
+       to->inmaclength = myself->inmaclength;
+       to->incompression = myself->incompression;
+
+       randomize(key, keylen);
+       cipher_set_key(&to->incipher, key, true);
+
        bin2hex(key, key, keylen);
        key[keylen * 2] = '\0';
 
-       return send_request(c, "%d %s %s %s %d %d %d %d", ANS_KEY,
-                                               from->name, to->name, key,
-                                               cipher_get_nid(&from->cipher),
-                                               digest_get_nid(&from->digest), from->maclength,
-                                               from->compression);
+       // Reset sequence number and late packet window
+       mykeyused = true;
+       to->received_seqno = 0;
+       memset(to->late, 0, sizeof(to->late));
+
+       return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY,
+                                               myself->name, to->name, key,
+                                               cipher_get_nid(&to->incipher),
+                                               digest_get_nid(&to->indigest), to->inmaclength,
+                                               to->incompression);
 }
 
 bool ans_key_h(connection_t *c, char *request) {
@@ -207,24 +218,24 @@ bool ans_key_h(connection_t *c, char *request) {
 
        /* Check and lookup cipher and digest algorithms */
 
-       if(!cipher_open_by_nid(&from->cipher, cipher)) {
+       if(!cipher_open_by_nid(&from->outcipher, cipher)) {
                logger(LOG_ERR, _("Node %s (%s) uses unknown cipher!"), from->name, from->hostname);
                return false;
        }
 
-       if(strlen(key) / 2 != cipher_keylength(&from->cipher)) {
+       if(strlen(key) / 2 != cipher_keylength(&from->outcipher)) {
                logger(LOG_ERR, _("Node %s (%s) uses wrong keylength!"), from->name, from->hostname);
                return false;
        }
 
-       from->maclength = maclength;
+       from->outmaclength = maclength;
 
-       if(!digest_open_by_nid(&from->digest, digest)) {
+       if(!digest_open_by_nid(&from->outdigest, digest)) {
                logger(LOG_ERR, _("Node %s (%s) uses unknown digest!"), from->name, from->hostname);
                return false;
        }
 
-       if(from->maclength > digest_length(&from->digest) || from->maclength < 0) {
+       if(from->outmaclength > digest_length(&from->outdigest) || from->outmaclength < 0) {
                logger(LOG_ERR, _("Node %s (%s) uses bogus MAC length!"), from->name, from->hostname);
                return false;
        }
@@ -234,12 +245,12 @@ bool ans_key_h(connection_t *c, char *request) {
                return false;
        }
        
-       from->compression = compression;
+       from->outcompression = compression;
 
        /* Update our copy of the origin's packet key */
 
-       hex2bin(key, key, cipher_keylength(&from->cipher));
-       cipher_set_key(&from->cipher, key, false);
+       hex2bin(key, key, cipher_keylength(&from->outcipher));
+       cipher_set_key(&from->outcipher, key, false);
 
        from->status.validkey = true;
        from->status.waitingforkey = false;
index 00cdde6..e592721 100644 (file)
@@ -60,7 +60,7 @@ bool add_subnet_h(connection_t *c, char *request)
                return false;
        }
 
-       /* Check if owner name is valid */
+       /* Check if owner name is valid */
 
        if(!check_id(name)) {
                logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "ADD_SUBNET", c->name,
@@ -127,8 +127,11 @@ bool add_subnet_h(connection_t *c, char *request)
                        free_subnet(allowed);
                }
 
-               if(!cfg)
+               if(!cfg) {
+                       logger(LOG_WARNING, _("Unauthorized %s from %s (%s) for %s"),
+                               "ADD_SUBNET", c->name, c->hostname, subnetstr);
                        return false;
+               }
 
                free_subnet(allowed);
        }
@@ -176,7 +179,7 @@ bool del_subnet_h(connection_t *c, char *request)
                return false;
        }
 
-       /* Check if owner name is valid */
+       /* Check if owner name is valid */
 
        if(!check_id(name)) {
                logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "DEL_SUBNET", c->name,
@@ -184,19 +187,6 @@ bool del_subnet_h(connection_t *c, char *request)
                return false;
        }
 
-       /* Check if the owner of the new subnet is in the connection list */
-
-       owner = lookup_node(name);
-
-       if(!owner) {
-               ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"),
-                                  "DEL_SUBNET", c->name, c->hostname, name);
-               return true;
-       }
-
-       if(tunnelserver && owner != myself && owner != c->node)
-               return false;
-
        /* Check if subnet string is valid */
 
        if(!str2net(&s, subnetstr)) {
@@ -208,6 +198,23 @@ bool del_subnet_h(connection_t *c, char *request)
        if(seen_request(request))
                return true;
 
+       /* Check if the owner of the subnet being deleted is in the connection list */
+
+       owner = lookup_node(name);
+
+       if(tunnelserver && owner != myself && owner != c->node) {
+               /* in case of tunnelserver, ignore indirect subnet deletion */
+               ifdebug(PROTOCOL) logger(LOG_WARNING, _("Ignoring indirect %s from %s (%s) for %s"),
+                                  "DEL_SUBNET", c->name, c->hostname, subnetstr);
+               return true;
+       }
+
+       if(!owner) {
+               ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"),
+                                  "DEL_SUBNET", c->name, c->hostname, name);
+               return true;
+       }
+
        /* If everything is correct, delete the subnet from the list of the owner */
 
        s.owner = owner;
index ac0cbc6..76053a0 100644 (file)
 
 #include LZO1X_H
 
+#ifndef HAVE_MINGW
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#endif
+
 #include <getopt.h>
 
 #include "conf.h"
@@ -62,6 +68,12 @@ bool bypass_security = false;
 /* If nonzero, disable swapping for this process. */
 bool do_mlock = false;
 
+/* If nonzero, chroot to netdir after startup. */
+static bool do_chroot = false;
+
+/* If !NULL, do setuid to given user after startup */
+static const char *switchuser = NULL;
+
 /* If nonzero, write log entries to a separate file. */
 bool use_logfile = false;
 
@@ -81,6 +93,8 @@ static struct option const long_options[] = {
        {"debug", optional_argument, NULL, 'd'},
        {"bypass-security", no_argument, NULL, 3},
        {"mlock", no_argument, NULL, 'L'},
+       {"chroot", no_argument, NULL, 'R'},
+       {"user", required_argument, NULL, 'U'},
        {"logfile", optional_argument, NULL, 4},
        {"controlsocket", required_argument, NULL, 5},
        {NULL, 0, NULL, 0}
@@ -105,7 +119,8 @@ static void usage(bool status)
                                "      --logfile[=FILENAME]      Write log entries to a logfile.\n"
                                "      --controlsocket=FILENAME  Open control socket at FILENAME.\n"
                                "      --bypass-security         Disables meta protocol security, for debugging.\n"
-                               "      --help                    Display this help and exit.\n"
+                               "  -R, --chroot                  chroot to NET dir at startup.\n"
+                               "  -U, --user=USER               setuid to given USER at startup.\n"                            "      --help                    Display this help and exit.\n"
                                "      --version                 Output version information and exit.\n\n"));
                printf(_("Report bugs to tinc@tinc-vpn.org.\n"));
        }
@@ -116,7 +131,7 @@ static bool parse_options(int argc, char **argv)
        int r;
        int option_index = 0;
 
-       while((r = getopt_long(argc, argv, "c:DLd::n:", long_options, &option_index)) != EOF) {
+       while((r = getopt_long(argc, argv, "c:DLd::n:RU:", long_options, &option_index)) != EOF) {
                switch (r) {
                        case 0:                         /* long option */
                                break;
@@ -130,8 +145,13 @@ static bool parse_options(int argc, char **argv)
                                break;
 
                        case 'L':                               /* no detach */
+#ifndef HAVE_MLOCKALL
+                               logger(LOG_ERR, _("%s not supported on this platform"), "mlockall()");
+                               return false;
+#else
                                do_mlock = true;
                                break;
+#endif
 
                        case 'd':                               /* inc debug level */
                                if(optarg)
@@ -144,6 +164,14 @@ static bool parse_options(int argc, char **argv)
                                netname = xstrdup(optarg);
                                break;
 
+                       case 'R':                               /* chroot to NETNAME dir */
+                               do_chroot = true;
+                               break;
+
+                       case 'U':                               /* setuid to USER */
+                               switchuser = optarg;
+                               break;
+
                        case 1:                                 /* show help */
                                show_help = true;
                                break;
@@ -237,6 +265,63 @@ static void free_names() {
        if (confbase) free(confbase);
 }
 
+static bool drop_privs() {
+#ifdef HAVE_MINGW
+       if (switchuser) {
+               logger(LOG_ERR, _("%s not supported on this platform"), "-U");
+               return false;
+       }
+       if (do_chroot) {
+               logger(LOG_ERR, _("%s not supported on this platform"), "-R");
+               return false;
+       }
+#else
+       uid_t uid = 0;
+       if (switchuser) {
+               struct passwd *pw = getpwnam(switchuser);
+               if (!pw) {
+                       logger(LOG_ERR, _("unknown user `%s'"), switchuser);
+                       return false;
+               }
+               uid = pw->pw_uid;
+               if (initgroups(switchuser, pw->pw_gid) != 0 ||
+                   setgid(pw->pw_gid) != 0) {
+                       logger(LOG_ERR, _("System call `%s' failed: %s"),
+                              "initgroups", strerror(errno));
+                       return false;
+               }
+               endgrent();
+               endpwent();
+       }
+       if (do_chroot) {
+               tzset();        /* for proper timestamps in logs */
+               if (chroot(confbase) != 0 || chdir("/") != 0) {
+                       logger(LOG_ERR, _("System call `%s' failed: %s"),
+                              "chroot", strerror(errno));
+                       return false;
+               }
+               free(confbase);
+               confbase = xstrdup("");
+       }
+       if (switchuser)
+               if (setuid(uid) != 0) {
+                       logger(LOG_ERR, _("System call `%s' failed: %s"),
+                              "setuid", strerror(errno));
+                       return false;
+               }
+#endif
+       return true;
+}
+
+#ifdef HAVE_MINGW
+# define setpriority(level) SetPriorityClass(GetCurrentProcess(), level);
+#else
+# define NORMAL_PRIORITY_CLASS 0
+# define BELOW_NORMAL_PRIORITY_CLASS 10
+# define HIGH_PRIORITY_CLASS -10
+# define setpriority(level) nice(level)
+#endif
+
 int main(int argc, char **argv)
 {
        program_name = argv[0];
@@ -277,20 +362,6 @@ int main(int argc, char **argv)
        if(!init_control())
                return 1;
 
-       /* Lock all pages into memory if requested */
-
-       if(do_mlock)
-#ifdef HAVE_MLOCKALL
-               if(mlockall(MCL_CURRENT | MCL_FUTURE)) {
-                       logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall",
-                                  strerror(errno));
-#else
-       {
-               logger(LOG_ERR, _("mlockall() not supported on this platform!"));
-#endif
-               return -1;
-       }
-
        g_argv = argv;
 
        init_configuration(&config_tree);
@@ -326,11 +397,46 @@ int main2(int argc, char **argv)
 
        if(!detach())
                return 1;
-               
+
+#ifdef HAVE_MLOCKALL
+       /* Lock all pages into memory if requested.
+        * This has to be done after daemon()/fork() so it works for child.
+        * No need to do that in parent as it's very short-lived. */
+       if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
+               logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall",
+                  strerror(errno));
+               return 1;
+       }
+#endif
 
        /* Setup sockets and open device. */
 
-       if(!setup_network_connections())
+       if(!setup_network())
+               goto end;
+
+       /* Initiate all outgoing connections. */
+
+       try_outgoing_connections();
+
+       /* Change process priority */
+
+        char *priority = 0;
+
+        if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) {
+                if(!strcasecmp(priority, "Normal"))
+                        setpriority(NORMAL_PRIORITY_CLASS);
+                else if(!strcasecmp(priority, "Low"))
+                        setpriority(BELOW_NORMAL_PRIORITY_CLASS);
+                else if(!strcasecmp(priority, "High"))
+                        setpriority(HIGH_PRIORITY_CLASS);
+                else {
+                        logger(LOG_ERR, _("Invalid priority `%s`!"), priority);
+                        goto end;
+                }
+        }
+
+       /* drop privileges */
+       if (!drop_privs())
                goto end;
 
        /* Start main loop. It only exits when tinc is killed. */
@@ -353,5 +459,8 @@ end:
 
        crypto_exit();
 
+       exit_configuration(&config_tree);
+       free_names();
+
        return status;
 }