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

20 files changed:
1  2 
doc/tinc.texi
doc/tincd.8.in
lib/utils.c
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/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

diff --combined doc/tinc.texi
@@@ -71,7 -71,6 +71,7 @@@ permission notice identical to this one
  * Installation::
  * Configuration::
  * Running tinc::
 +* Controlling tinc::
  * Technical information::
  * Platform specific information::
  * About us::
@@@ -308,7 -307,7 +308,7 @@@ If the @file{net/if_tun.h} header file 
  @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.
@@@ -344,7 -343,6 +344,7 @@@ having them installed, configure will g
  * OpenSSL::
  * zlib::
  * lzo::
 +* libevent::
  @end menu
  
  
@@@ -457,27 -455,6 +457,27 @@@ make sure you build development and run
  default).
  
  
 +@c ==================================================================
 +@node       libevent
 +@subsection libevent
 +
 +@cindex libevent
 +For the main event loop, tinc uses the libevent library.
 +
 +If this library is not installed, you wil get an error when configuring
 +tinc for build.
 +
 +You can use your operating system's package manager to install this if
 +available.  Make sure you install the development AND runtime versions
 +of this package.
 +
 +If you have to install libevent manually, you can get the source code
 +from @url{http://monkey.org/~provos/libevent/}.  Instructions on how to configure,
 +build and install this package are included within the package.  Please
 +make sure you build development and runtime libraries (which is the
 +default).
 +
 +
  @c
  @c
  @c
@@@ -945,13 -922,18 +945,18 @@@ accidental eavesdropping if you are edi
  @cindex PrivateKeyFile
  @item PrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/rsa_key.priv})
  This is the full path name of the RSA private key file that was
 -generated by @samp{tincd --generate-keys}.  It must be a full path, not a
 +generated by @samp{tincctl generate-keys}.  It must be a full path, not a
  relative directory.
  
  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,
@@@ -1026,7 -1008,7 +1031,7 @@@ This is the RSA public key for this hos
  @cindex PublicKeyFile
  @item PublicKeyFile = <@var{path}> [obsolete]
  This is the full path name of the RSA public key file that was generated
 -by @samp{tincd --generate-keys}.  It must be a full path, not a relative
 +by @samp{tincctl generate-keys}.  It must be a full path, not a relative
  directory.
  
  @cindex PEM format
@@@ -1209,7 -1191,7 +1214,7 @@@ Now that you have already created the m
  you can easily create a public/private keypair by entering the following command:
  
  @example
 -tincd -n @var{netname} -K
 +tincctl -n @var{netname} generate-keys
  @end example
  
  Tinc will generate a public and a private key and ask you where to put them.
@@@ -1438,7 -1420,7 +1443,7 @@@ Address = 4.5.6.
  A, B, C and D all have generated a public/private keypair with the following command:
  
  @example
 -tincd -n company -K
 +tincctl -n company generate-keys
  @end example
  
  The private key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv},
@@@ -1504,12 -1486,20 +1509,12 @@@ This will also disable the automatic re
  Set debug level to @var{level}.  The higher the debug level, the more gets
  logged.  Everything goes via syslog.
  
 -@item -k, --kill[=@var{signal}]
 -Attempt to kill a running tincd (optionally with the specified @var{signal} instead of SIGTERM) and exit.
 -Use it in conjunction with the -n option to make sure you kill the right tinc daemon.
 -Under native Windows the optional argument is ignored,
 -the service will always be stopped and removed.
 -
  @item -n, --net=@var{netname}
  Use configuration for net @var{netname}. @xref{Multiple networks}.
  
 -@item -K, --generate-keys[=@var{bits}]
 -Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 -1024 is the default. tinc will ask where you want to store the files,
 -but will default to the configuration directory (you can use the -c or -n option
 -in combination with -K). After that, tinc will quit.
 +@item --controlsocket=@var{filename}
 +Open control socket at @var{filename}. If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.control}.
  
  @item -L, --mlock
  Lock tinc into main memory.
@@@ -1519,10 -1509,30 +1524,27 @@@ This will prevent sensitive data like s
  Write log entries to a file instead of to the system logging facility.
  If @var{file} is omitted, the default is @file{@value{localstatedir}/log/tinc.@var{netname}.log}.
  
 -@item --pidfile=@var{file}
 -Write PID to @var{file} instead of @file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
 -
  @item --bypass-security
  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.
  
@@@ -1541,11 -1551,31 +1563,11 @@@ You can also send the following signal
  @c from the manpage
  @table @samp
  
 -@item ALRM
 -Forces tinc to try to connect to all uplinks immediately.
 -Usually tinc attempts to do this itself,
 -but increases the time it waits between the attempts each time it failed,
 -and if tinc didn't succeed to connect to an uplink the first time after it started,
 -it defaults to the maximum time of 15 minutes.
 -
  @item HUP
  Partially rereads configuration files.
  Connections to hosts whose host config file are removed are closed.
  New outgoing connections specified in @file{tinc.conf} will be made.
  
 -@item INT
 -Temporarily increases debug level to 5.
 -Send this signal again to revert to the original level.
 -
 -@item USR1
 -Dumps the connection list to syslog.
 -
 -@item USR2
 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog.
 -
 -@item WINCH
 -Purges all information remembered about unreachable nodes.
 -
  @end table
  
  @c ==================================================================
@@@ -1740,110 -1770,6 +1762,110 @@@ Be sure to include the following inform
  @item The output of any command that fails to work as it should (like ping or traceroute).
  @end itemize
  
 +@c ==================================================================
 +@node    Controlling tinc
 +@chapter Controlling tinc
 +
 +You can control and inspect a running @samp{tincd} through the @samp{tincctl}
 +command. A quick example:
 +
 +@example
 +tincctl -n @var{netname} reload
 +@end example
 +
 +@menu
 +* tincctl runtime options::
 +* tincctl commands::
 +@end menu
 +
 +
 +@c ==================================================================
 +@node    tincctl runtime options
 +@section tincctl runtime options
 +
 +@c from the manpage
 +@table @option
 +@item -c, --config=@var{path}
 +Read configuration options from the directory @var{path}.  The default is
 +@file{@value{sysconfdir}/tinc/@var{netname}/}.
 +
 +@item -n, --net=@var{netname}
 +Use configuration for net @var{netname}. @xref{Multiple networks}.
 +
 +@item --controlsocket=@var{filename}
 +Open control socket at @var{filename}. If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.control}.
 +
 +@item --help
 +Display a short reminder of runtime options and commands, then terminate.
 +
 +@item --version
 +Output version information and exit.
 +
 +@end table
 +
 +
 +@c ==================================================================
 +@node    tincctl commands
 +@section tincctl commands
 +
 +@c from the manpage
 +@table @code
 +
 +@item start
 +Start @samp{tincd}.
 +
 +@item stop
 +Stop @samp{tincd}.
 +
 +@item restart
 +Restart @samp{tincd}.
 +
 +@item reload
 +Partially rereads configuration files. Connections to hosts whose host
 +config files are removed are closed. New outgoing connections specified
 +in @file{tinc.conf} will be made.
 +
 +@item pid
 +Shows the PID of the currently running @samp{tincd}.
 +
 +@item generate-keys [@var{bits}]
 +Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 +1024 is the default. tinc will ask where you want to store the files,
 +but will default to the configuration directory (you can use the -c or -n
 +option).
 +
 +@item dump nodes
 +Dump a list of all known nodes in the VPN.
 +
 +@item dump edges
 +Dump a list of all known connections in the VPN.
 +
 +@item dump subnets
 +Dump a list of all known subnets in the VPN.
 +
 +@item dump connections
 +Dump a list of all meta connections with ourself.
 +
 +@item dump graph
 +Dump a graph of the VPN in dotty format.
 +
 +@item purge
 +Purges all information remembered about unreachable nodes.
 +
 +@item debug @var{level}
 +Sets debug level to @var{level}.
 +
 +@item retry
 +Forces tinc to try to connect to all uplinks immediately.
 +Usually tinc attempts to do this itself,
 +but increases the time it waits between the attempts each time it failed,
 +and if tinc didn't succeed to connect to an uplink the first time after it started,
 +it defaults to the maximum time of 15 minutes.
 +
 +@end table
 +
 +
  @c ==================================================================
  @node    Technical information
  @chapter Technical information
diff --combined doc/tincd.8.in
@@@ -8,14 -8,19 +8,16 @@@
  .Nd tinc VPN daemon
  .Sh SYNOPSIS
  .Nm
- .Op Fl cdDKnL
 -.Op Fl cdDkKnLRU
++.Op Fl cdDKnLRU
  .Op Fl -config Ns = Ns Ar DIR
  .Op Fl -no-detach
  .Op Fl -debug Ns Op = Ns Ar LEVEL
 -.Op Fl -kill Ns Op = Ns Ar SIGNAL
  .Op Fl -net Ns = Ns Ar NETNAME
 -.Op Fl -generate-keys Ns Op = Ns Ar BITS
  .Op Fl -mlock
  .Op Fl -logfile Ns Op = Ns Ar FILE
 -.Op Fl -pidfile Ns = 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
@@@ -48,9 -53,24 +50,9 @@@ If not mentioned otherwise, this will s
  Increase debug level or set it to
  .Ar LEVEL
  (see below).
 -.It Fl k, -kill Ns Op = Ns Ar SIGNAL
 -Attempt to kill a running
 -.Nm
 -(optionally with the specified
 -.Ar SIGNAL
 -instead of SIGTERM) and exit.
 -Under Windows (not Cygwin) the optional argument is ignored,
 -the service will always be stopped and removed.
  .It Fl n, -net Ns = Ns Ar NETNAME
  Connect to net
  .Ar NETNAME .
 -.It Fl K, -generate-keys Ns Op = Ns Ar BITS
 -Generate public/private RSA keypair and exit.
 -If
 -.Ar BITS
 -is omitted, the default length will be 1024 bits.
 -When saving keys to existing files, tinc will not delete the old keys,
 -you have to remove them manually.
  .It Fl L, -mlock
  Lock tinc into main memory.
  This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
@@@ -60,16 -80,23 +62,24 @@@ I
  .Ar FILE
  is omitted, the default is
  .Pa @localstatedir@/log/tinc. Ns Ar NETNAME Ns Pa .log.
 -.It Fl -pidfile Ns = Ns Ar FILE
 -Write PID to
 +.It Fl -controlsocket Ns = Ns Ar FILENAME
 +Open control socket at
 +.Ar FILENAME .
 +If
  .Ar FILE
 -instead of
 -.Pa @localstatedir@/run/tinc. Ns Ar NETNAME Ns Pa .pid.
 -Under Windows this option will be ignored.
 +is omitted, the default is
 +.Pa @localstatedir@/run/tinc. Ns Ar NETNAME Ns Pa .control.
  .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
@@@ -77,12 -104,33 +87,12 @@@ Output version information and exit
  .El
  .Sh SIGNALS
  .Bl -tag -width indent
 -.It ALRM
 -Forces
 -.Nm
 -to try to connect to all uplinks immediately.
 -Usually
 -.Nm
 -attempts to do this itself,
 -but increases the time it waits between the attempts each time it failed,
 -and if
 -.Nm
 -didn't succeed to connect to an uplink the first time after it started,
 -it defaults to the maximum time of 15 minutes.
  .It HUP
  Partially rereads configuration files.
  Connections to hosts whose host config file are removed are closed.
  New outgoing connections specified in
  .Pa tinc.conf
  will be made.
 -.It INT
 -Temporarily increases debug level to 5.
 -Send this signal again to revert to the original level.
 -.It USR1
 -Dumps the connection list to syslog.
 -.It USR2
 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog.
 -.It WINCH
 -Purges all information remembered about unreachable nodes.
  .El
  .Sh DEBUG LEVELS
  The tinc daemon can send a lot of messages to the syslog.
@@@ -129,7 -177,6 +139,7 @@@ If you find any bugs, report them to ti
  .Sh TODO
  A lot, especially security auditing.
  .Sh SEE ALSO
 +.Xr tincctl 8 ,
  .Xr tinc.conf 5 ,
  .Pa http://www.tinc-vpn.org/ ,
  .Pa http://www.cabal.org/ .
diff --combined lib/utils.c
@@@ -29,9 -29,10 +29,9 @@@ volatile char (*cp_file[]) = {"?", "?"
  volatile int cp_index = 0;
  #endif
  
- char *hexadecimals = "0123456789ABCDEF";
+ const char hexadecimals[] = "0123456789ABCDEF";
  
 -int charhex2bin(char c)
 -{
 +int charhex2bin(char c) {
        if(isdigit(c))
                return c - '0';
        else
  }
  
  
 -void hex2bin(char *src, char *dst, int length)
 -{
 +void hex2bin(char *src, char *dst, int length) {
        int i;
        for(i = 0; i < length; i++)
                dst[i] = charhex2bin(src[i * 2]) * 16 + charhex2bin(src[i * 2 + 1]);
  }
  
 -void bin2hex(char *src, char *dst, int length)
 -{
 +void bin2hex(char *src, char *dst, int length) {
        int i;
        for(i = length - 1; i >= 0; i--) {
                dst[i * 2 + 1] = hexadecimals[(unsigned char) src[i] & 15];
@@@ -54,7 -57,8 +54,7 @@@
  }
  
  #ifdef ENABLE_TRACING
 -void cp_trace()
 -{
 +void cp_trace() {
        logger(LOG_DEBUG, "Checkpoint trace: %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d <- %s:%d...",
                   cp_file[(cp_index + 15) % 16], cp_line[(cp_index + 15) % 16],
                   cp_file[(cp_index + 14) % 16], cp_line[(cp_index + 14) % 16],
@@@ -81,7 -85,7 +81,7 @@@
  #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,
diff --combined src/graph.c
@@@ -46,7 -46,7 +46,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "config.h"
  #include "connection.h"
  #include "device.h"
  #include "subnet.h"
  #include "utils.h"
  
 -static bool graph_changed = true;
 -
  /* Implementation of Kruskal's algorithm.
 -   Running time: O(EN)
 +   Running time: O(E)
     Please note that sorting on weight is already done by add_edge().
  */
  
 -void mst_kruskal(void)
 -{
 -      avl_node_t *node, *next;
 +void mst_kruskal(void) {
 +      splay_node_t *node, *next;
        edge_t *e;
        node_t *n;
        connection_t *c;
 -      int nodes = 0;
 -      int safe_edges = 0;
 -      bool skipped;
  
        cp();
        
                c->status.mst = false;
        }
  
 -      /* Do we have something to do at all? */
 -
 -      if(!edge_weight_tree->head)
 -              return;
 -
        ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:");
  
        /* Clear visited status on nodes */
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
                n->status.visited = false;
 -              nodes++;
 -      }
 -
 -      /* Starting point */
 -
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              if(e->from->status.reachable) {
 -                      e->from->status.visited = true;
 -                      break;
 -              }
        }
  
        /* Add safe edges */
  
 -      for(skipped = false, node = edge_weight_tree->head; node; node = next) {
 +      for(node = edge_weight_tree->head; node; node = next) {
                next = node->next;
                e = node->data;
  
 -              if(!e->reverse || e->from->status.visited == e->to->status.visited) {
 -                      skipped = true;
 +              if(!e->reverse || (e->from->status.visited && e->to->status.visited))
                        continue;
 -              }
  
                e->from->status.visited = true;
                e->to->status.visited = true;
                if(e->reverse->connection)
                        e->reverse->connection->status.mst = true;
  
 -              safe_edges++;
 -
                ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
                                   e->to->name, e->weight);
 +      }
 +}
  
 -              if(skipped) {
 -                      skipped = false;
 -                      next = edge_weight_tree->head;
 -                      continue;
 +/* Implementation of Dijkstra's algorithm.
 +   Running time: O(N^2)
 +*/
 +
 +void sssp_dijkstra(void) {
 +      splay_node_t *node, *to;
 +      edge_t *e;
 +      node_t *n, *m;
 +      list_t *todo_list;
 +      list_node_t *lnode, *nnode;
 +      bool indirect;
 +
 +      cp();
 +
 +      todo_list = list_alloc(NULL);
 +
 +      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Dijkstra's algorithm:");
 +
 +      /* Clear visited status on nodes */
 +
 +      for(node = node_tree->head; node; node = node->next) {
 +              n = node->data;
 +              n->status.visited = false;
 +              n->status.indirect = true;
 +              n->distance = -1;
 +      }
 +
 +      /* Begin with myself */
 +
 +      myself->status.indirect = false;
 +      myself->nexthop = myself;
 +      myself->via = myself;
 +      myself->distance = 0;
 +      list_insert_head(todo_list, myself);
 +
 +      /* Loop while todo_list is filled */
 +
 +      while(todo_list->head) {
 +              n = NULL;
 +              nnode = NULL;
 +
 +              /* Select node from todo_list with smallest distance */
 +
 +              for(lnode = todo_list->head; lnode; lnode = lnode->next) {
 +                      m = lnode->data;
 +                      if(!n || m->status.indirect < n->status.indirect || m->distance < n->distance) {
 +                              n = m;
 +                              nnode = lnode;
 +                      }
 +              }
 +
 +              /* Mark this node as visited and remove it from the todo_list */
 +
 +              n->status.visited = true;
 +              list_unlink_node(todo_list, nnode);
 +
 +              /* Update distance of neighbours and add them to the todo_list */
 +
 +              for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
 +                      e = to->data;
 +
 +                      if(e->to->status.visited || !e->reverse)
 +                              continue;
 +
 +                      /* Situation:
 +
 +                                 /
 +                                /
 +                         ----->(n)---e-->(e->to)
 +                                \
 +                                 \
 +
 +                         Where e is an edge, (n) and (e->to) are nodes.
 +                         n->address is set to the e->address of the edge left of n to n.
 +                         We are currently examining the edge e right of n from n:
 +
 +                         - If e->reverse->address != n->address, then e->to is probably
 +                           not reachable for the nodes left of n. We do as if the indirectdata
 +                           flag is set on edge e.
 +                         - If edge e provides for better reachability of e->to, update e->to.
 +                       */
 +
 +                      if(e->to->distance < 0)
 +                              list_insert_tail(todo_list, e->to);
 +
 +                      indirect = n->status.indirect || e->options & OPTION_INDIRECT || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address));
 +
 +                      if(e->to->distance >= 0 && (!e->to->status.indirect || indirect) && e->to->distance <= n->distance + e->weight)
 +                              continue;
 +
 +                      e->to->distance = n->distance + e->weight;
 +                      e->to->status.indirect = indirect;
 +                      e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
 +                      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);
 +                              }
 +                      }
 +
 +                      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Updating edge %s - %s weight %d distance %d", e->from->name,
 +                                         e->to->name, e->weight, e->to->distance);
                }
        }
  
 -      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes,
 -                         safe_edges);
 +      list_free(todo_list);
  }
  
  /* Implementation of a simple breadth-first search algorithm.
     Running time: O(E)
  */
  
 -void sssp_bfs(void)
 -{
 -      avl_node_t *node, *next, *to;
 +void sssp_bfs(void) {
 +      splay_node_t *node, *to;
        edge_t *e;
        node_t *n;
        list_t *todo_list;
        list_node_t *from, *todonext;
        bool indirect;
 -      char *name;
 -      char *address, *port;
 -      char *envp[7];
 -      int i;
  
        cp();
  
                        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);
                }
        }
  
        list_free(todo_list);
 +}
 +
 +void check_reachability() {
 +      splay_node_t *node, *next;
 +      node_t *n;
 +      char *name;
 +      char *address, *port;
 +      char *envp[7];
 +      int i;
  
        /* Check reachability status. */
  
                        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;
  
        }
  }
  
 -void graph(void)
 -{
 -      subnet_cache_flush();
 -      sssp_bfs();
 -      mst_kruskal();
 -      graph_changed = true;
 -}
 -
 -
 -
  /* Dump nodes and edges to a graphviz file.
           
     The file can be converted to an image with
     dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
  */
  
 -void dump_graph(void)
 -{
 -      avl_node_t *node;
 +int dump_graph(struct evbuffer *out) {
 +      splay_node_t *node;
        node_t *n;
        edge_t *e;
 -      char *filename = NULL, *tmpname = NULL;
 -      FILE *file;
 -      
 -      if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename))
 -              return;
 -
 -      graph_changed = false;
 -
 -      ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph");
 -      
 -      if(filename[0] == '|') {
 -              file = popen(filename + 1, "w");
 -      } else {
 -              asprintf(&tmpname, "%s.new", filename);
 -              file = fopen(tmpname, "w");
 -      }
 -
 -      if(!file) {
 -              logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno));
 -              free(tmpname);
 -              return;
 -      }
  
 -      fprintf(file, "digraph {\n");
 +      if(evbuffer_add_printf(out, "digraph {\n") == -1)
 +              return errno;
        
        /* dump all nodes first */
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
 -              fprintf(file, " %s [label = \"%s\"];\n", n->name, n->name);
 +              if(evbuffer_add_printf(out, "   %s [label = \"%s\"];\n",
 +                                                         n->name, n->name) == -1)
 +                      return errno;
        }
  
        /* now dump all edges */
        for(node = edge_weight_tree->head; node; node = node->next) {
                e = node->data;
 -              fprintf(file, " %s -> %s;\n", e->from->name, e->to->name);
 +              if(evbuffer_add_printf(out, "   %s -> %s;\n",
 +                                                         e->from->name, e->to->name) == -1)
 +                      return errno;
        }
  
 -      fprintf(file, "}\n");   
 -      
 -      if(filename[0] == '|') {
 -              pclose(file);
 -      } else {
 -              fclose(file);
 -#ifdef HAVE_MINGW
 -              unlink(filename);
 -#endif
 -              rename(tmpname, filename);
 -              free(tmpname);
 -      }
 +      if(evbuffer_add_printf(out, "}\n") == -1)
 +              return errno;
 +
 +      return 0;
 +}
 +
 +void graph(void) {
 +    subnet_cache_flush();
 +      sssp_dijkstra();
 +      check_reachability();
 +      mst_kruskal();
  }
diff --combined src/linux/device.c
@@@ -52,7 -52,8 +52,7 @@@ static char *device_info
  static int device_total_in = 0;
  static int device_total_out = 0;
  
 -bool setup_device(void)
 -{
 +bool setup_device(void) {
        struct ifreq ifr;
  
        cp();
@@@ -62,7 -63,8 +62,8 @@@
  
        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
@@@ -76,7 -78,7 +77,7 @@@
  #ifdef HAVE_LINUX_IF_TUN_H
        /* Ok now check if this is an old ethertap or a new tun/tap thingie */
  
 -      memset(&ifr, 0, sizeof(ifr));
 +      memset(&ifr, 0, sizeof ifr);
        if(routing_mode == RMODE_ROUTER) {
                ifr.ifr_flags = IFF_TUN;
                device_type = DEVICE_TYPE_TUN;
        return true;
  }
  
 -void close_device(void)
 -{
 +void close_device(void) {
        cp();
        
        close(device_fd);
        free(iface);
  }
  
 -bool read_packet(vpn_packet_t *packet)
 -{
 -      int lenin;
 +bool read_packet(vpn_packet_t *packet) {
 +      int inlen;
        
        cp();
  
        switch(device_type) {
                case DEVICE_TYPE_TUN:
 -                      lenin = read(device_fd, packet->data + 10, MTU - 10);
 +                      inlen = read(device_fd, packet->data + 10, MTU - 10);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, _("Error while reading from %s %s: %s"),
                                           device_info, device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin + 10;
 +                      packet->len = inlen + 10;
                        break;
                case DEVICE_TYPE_TAP:
 -                      lenin = read(device_fd, packet->data, MTU);
 +                      inlen = read(device_fd, packet->data, MTU);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, _("Error while reading from %s %s: %s"),
                                           device_info, device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin;
 +                      packet->len = inlen;
                        break;
                case DEVICE_TYPE_ETHERTAP:
 -                      lenin = read(device_fd, packet->data - 2, MTU + 2);
 +                      inlen = read(device_fd, packet->data - 2, MTU + 2);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, _("Error while reading from %s %s: %s"),
                                           device_info, device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin - 2;
 +                      packet->len = inlen - 2;
                        break;
        }
  
        return true;
  }
  
 -bool write_packet(vpn_packet_t *packet)
 -{
 +bool write_packet(vpn_packet_t *packet) {
        cp();
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Writing packet of %d bytes to %s"),
        return true;
  }
  
 -void dump_device_stats(void)
 -{
 +void dump_device_stats(void) {
        cp();
  
        logger(LOG_DEBUG, _("Statistics for %s %s:"), device_info, device);
diff --combined src/net.h
+++ b/src/net.h
@@@ -23,9 -23,9 +23,9 @@@
  #ifndef __TINC_NET_H__
  #define __TINC_NET_H__
  
 -#include <openssl/evp.h>
 -
  #include "ipv6.h"
 +#include "cipher.h"
 +#include "digest.h"
  
  #ifdef ENABLE_JUMBOGRAMS
  #define MTU 9018                              /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #define MTU 1518                              /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #endif
  
 -#define MAXSIZE (MTU + 4 + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE + MTU/64 + 20)      /* MTU + seqno + padding + HMAC + compressor overhead */
 +#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20)     /* MTU + seqno + padding + HMAC + compressor overhead */
  #define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128)  /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */
  
 -#define MAXSOCKETS 128                        /* Overkill... */
 +#define MAXSOCKETS 8                  /* Probably overkill... */
  
  typedef struct mac_t {
        uint8_t x[6];
@@@ -86,8 -86,6 +86,8 @@@ typedef struct vpn_packet_t 
  } vpn_packet_t;
  
  typedef struct listen_socket_t {
 +      struct event ev_tcp;
 +      struct event ev_udp;
        int tcp;
        int udp;
        sockaddr_t sa;
@@@ -102,7 -100,6 +102,7 @@@ typedef struct outgoing_t 
        struct config_t *cfg;
        struct addrinfo *ai;
        struct addrinfo *aip;
 +      struct event ev;
  } outgoing_t;
  
  extern list_t *outgoing_list;
@@@ -113,25 -110,28 +113,25 @@@ extern int addressfamily
  
  extern listen_socket_t listen_socket[MAXSOCKETS];
  extern int listen_sockets;
 -extern int keyexpires;
  extern int keylifetime;
  extern bool do_prune;
 -extern bool do_purge;
  extern char *myport;
 -extern time_t now;
  
  /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */
  #include "connection.h"
  #include "node.h"
  
  extern void retry_outgoing(outgoing_t *);
 -extern void handle_incoming_vpn_data(int);
 +extern void handle_incoming_vpn_data(int, short, void *);
  extern void finish_connecting(struct connection_t *);
  extern void do_outgoing_connection(struct connection_t *);
 -extern bool handle_new_meta_connection(int);
 +extern void handle_new_meta_connection(int, short, void *);
  extern int setup_listen_socket(const sockaddr_t *);
  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);
@@@ -140,12 -140,6 +140,12 @@@ extern void terminate_connection(struc
  extern void flush_queue(struct node_t *);
  extern bool read_rsa_public_key(struct connection_t *);
  extern void send_mtu_probe(struct node_t *);
 +extern void handle_device_data(int, short, void *);
 +extern void handle_meta_connection_data(int, short, void *);
 +extern void regenerate_key();
 +extern void purge(void);
 +extern void retry(void);
 +extern int reload_configuration(void);
  
  #ifndef HAVE_MINGW
  #define closesocket(s) close(s)
diff --combined src/net_packet.c
  
  #include "system.h"
  
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -#include <openssl/pem.h>
 -#include <openssl/hmac.h>
 -
  #include <zlib.h>
  #include LZO1X_H
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
  #include "connection.h"
 +#include "crypto.h"
 +#include "digest.h"
  #include "device.h"
  #include "ethernet.h"
 -#include "event.h"
  #include "graph.h"
  #include "list.h"
  #include "logger.h"
  #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 *);
  
  #define MAX_SEQNO 1073741824
  
 -void send_mtu_probe(node_t *n)
 -{
 +static void send_mtu_probe_handler(int fd, short events, void *data) {
 +      node_t *n = data;
        vpn_packet_t packet;
        int len, i;
        
        cp();
  
        n->mtuprobes++;
 -      n->mtuevent = NULL;
  
        if(n->mtuprobes >= 10 && !n->minmtu) {
                ifdebug(TRAFFIC) logger(LOG_INFO, _("No response to MTU probes from %s (%s)"), n->name, n->hostname);
@@@ -81,7 -87,7 +82,7 @@@
                        len = 64;
                
                memset(packet.data, 0, 14);
 -              RAND_pseudo_bytes(packet.data + 14, len - 14);
 +              randomize(packet.data + 14, len - 14);
                packet.len = len;
                packet.priority = 0;
  
                send_udppacket(n, &packet);
        }
  
 -      n->mtuevent = new_event();
 -      n->mtuevent->handler = (event_handler_t)send_mtu_probe;
 -      n->mtuevent->data = n;
 -      n->mtuevent->time = now + 1;
 -      event_add(n->mtuevent);
 +      event_add(&n->mtuevent, &(struct timeval){1, 0});
 +}
 +
 +void send_mtu_probe(node_t *n) {
 +      if(!timeout_initialized(&n->mtuevent))
 +              timeout_set(&n->mtuevent, send_mtu_probe_handler, 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;
        }
  }
  
 -static length_t compress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level)
 -{
 +static length_t compress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level) {
        if(level == 10) {
                lzo_uint lzolen = MAXSIZE;
                lzo1x_1_compress(source, len, dest, &lzolen, lzo_wrkmem);
        return -1;
  }
  
 -static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level)
 -{
 +static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level) {
        if(level > 9) {
                lzo_uint lzolen = MAXSIZE;
                if(lzo1x_decompress_safe(source, len, dest, &lzolen, NULL) == LZO_E_OK)
  
  /* VPN packet I/O */
  
 -static void receive_packet(node_t *n, vpn_packet_t *packet)
 -{
 +static void receive_packet(node_t *n, vpn_packet_t *packet) {
        cp();
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Received packet of %d bytes from %s (%s)"),
        route(n, packet);
  }
  
- static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
 -static bool try_mac(const node_t *n, const vpn_packet_t *inpkt)
++static bool try_mac(node_t *n, const vpn_packet_t *inpkt)
+ {
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 -
 -      if(!n->indigest || !n->inmaclength || !n->inkey || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
++      if(!digest_active(&n->indigest) || !n->inmaclength || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
+               return false;
 -      HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len - n->inmaclength, (unsigned char *)hmac, NULL);
 -
 -      return !memcmp(hmac, (char *) &inpkt->seqno + inpkt->len - n->inmaclength, n->inmaclength);
++      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;
        vpn_packet_t *outpkt = pkt[0];
 -      int outlen, outpad;
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 +      size_t outlen;
        int i;
  
        cp();
  
 -      if(!n->inkey) {
++      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) + n->inmaclength) {
++      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;
  
        /* Check the message authentication code */
  
-       if(digest_active(&myself->digest) && !digest_verify(&myself->digest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len)) {
 -      if(n->indigest && n->inmaclength) {
 -              inpkt->len -= n->inmaclength;
 -              HMAC(n->indigest, n->inkey, n->inkeylength,
 -                       (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *)hmac, NULL);
 -
 -              if(memcmp(hmac, (char *) &inpkt->seqno + inpkt->len, n->inmaclength)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got unauthenticated packet from %s (%s)"),
 -                                         n->name, n->hostname);
 -                      return;
 -              }
++      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(n->incipher) {
++      if(cipher_active(&n->incipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
-               if(!cipher_decrypt(&myself->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 -              if(!EVP_DecryptInit_ex(&n->inctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_DecryptUpdate(&n->inctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_DecryptFinal_ex(&n->inctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Error decrypting packet from %s (%s): %s"),
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
++              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;
                }
                
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* Check the sequence number */
  
 -      inpkt->len -= sizeof(inpkt->seqno);
 +      inpkt->len -= sizeof inpkt->seqno;
        inpkt->seqno = ntohl(inpkt->seqno);
  
        if(inpkt->seqno != n->received_seqno + 1) {
 -              if(inpkt->seqno >= n->received_seqno + sizeof(n->late) * 8) {
 +              if(inpkt->seqno >= n->received_seqno + sizeof n->late * 8) {
                        logger(LOG_WARNING, _("Lost %d packets from %s (%s)"),
                                           inpkt->seqno - n->received_seqno - 1, n->name, n->hostname);
                        
 -                      memset(n->late, 0, sizeof(n->late));
 +                      memset(n->late, 0, sizeof n->late);
                } else if (inpkt->seqno <= n->received_seqno) {
 -                      if((n->received_seqno >= sizeof(n->late) * 8 && inpkt->seqno <= n->received_seqno - sizeof(n->late) * 8) || !(n->late[(inpkt->seqno / 8) % sizeof(n->late)] & (1 << inpkt->seqno % 8))) {
 +                      if((n->received_seqno >= sizeof n->late * 8 && inpkt->seqno <= n->received_seqno - sizeof n->late * 8) || !(n->late[(inpkt->seqno / 8) % sizeof n->late] & (1 << inpkt->seqno % 8))) {
                                logger(LOG_WARNING, _("Got late or replayed packet from %s (%s), seqno %d, last received %d"),
                                           n->name, n->hostname, inpkt->seqno, n->received_seqno);
                                return;
                        }
                } else {
                        for(i = n->received_seqno + 1; i < inpkt->seqno; i++)
 -                              n->late[(i / 8) % sizeof(n->late)] |= 1 << i % 8;
 +                              n->late[(i / 8) % sizeof n->late] |= 1 << i % 8;
                }
        }
        
 -      n->late[(inpkt->seqno / 8) % sizeof(n->late)] &= ~(1 << inpkt->seqno % 8);
 +      n->late[(inpkt->seqno / 8) % sizeof n->late] &= ~(1 << inpkt->seqno % 8);
  
        if(inpkt->seqno > n->received_seqno)
                n->received_seqno = inpkt->seqno;
                        
        if(n->received_seqno > MAX_SEQNO)
 -              keyexpires = 0;
 +              regenerate_key();
  
        /* 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(n->connection)
 -              n->connection->last_ping_time = now;
 -
        if(!inpkt->data[12] && !inpkt->data[13])
-               mtu_probe_h(n, inpkt);
+               mtu_probe_h(n, inpkt, origlen);
        else
                receive_packet(n, inpkt);
  }
  
 -void receive_tcppacket(connection_t *c, char *buffer, int len)
 -{
 +void receive_tcppacket(connection_t *c, char *buffer, int len) {
        vpn_packet_t outpkt;
  
        cp();
        receive_packet(c->node, &outpkt);
  }
  
 -static void send_udppacket(node_t *n, vpn_packet_t *origpkt)
 -{
 +static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
        vpn_packet_t pkt1, pkt2;
        vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 };
        vpn_packet_t *inpkt = origpkt;
        int nextpkt = 0;
        vpn_packet_t *outpkt;
        int origlen;
 -      int outlen, outpad;
 +      size_t outlen;
        static int priority = 0;
        int origpriority;
        int sock;
                                   n->name, n->hostname);
  
                if(!n->status.waitingforkey)
-                       send_req_key(n->nexthop->connection, myself, n);
+                       send_req_key(n);
  
                n->status.waitingforkey = true;
  
                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;
  
        /* 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;
        /* Add sequence number */
  
        inpkt->seqno = htonl(++(n->sent_seqno));
 -      inpkt->len += sizeof(inpkt->seqno);
 +      inpkt->len += sizeof inpkt->seqno;
  
        /* Encrypt the packet */
  
-       if(cipher_active(&n->cipher)) {
 -      if(n->outcipher) {
++      if(cipher_active(&n->outcipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
-               if(!cipher_encrypt(&n->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 -              if(!EVP_EncryptInit_ex(&n->outctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_EncryptUpdate(&n->outctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_EncryptFinal_ex(&n->outctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while encrypting packet to %s (%s): %s"),
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
++              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;
                }
  
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* 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(n->outdigest && n->outmaclength) {
 -              HMAC(n->outdigest, n->outkey, n->outkeylength, (unsigned char *) &inpkt->seqno,
 -                       inpkt->len, (unsigned char *) &inpkt->seqno + inpkt->len, NULL);
 -              inpkt->len += n->outmaclength;
++      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 */
           && listen_socket[sock].sa.sa.sa_family == AF_INET) {
                priority = origpriority;
                ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Setting outgoing packet priority to %d"), priority);
 -              if(setsockopt(listen_socket[sock].udp, SOL_IP, IP_TOS, &priority, sizeof(priority)))    /* SO_PRIORITY doesn't seem to work */
 +              if(setsockopt(listen_socket[sock].udp, SOL_IP, IP_TOS, &priority, sizeof priority))     /* SO_PRIORITY doesn't seem to work */
                        logger(LOG_ERR, _("System call `%s' failed: %s"), "setsockopt", strerror(errno));
        }
  #endif
@@@ -389,7 -441,8 +411,7 @@@ end
  /*
    send a packet to the given vpn ip.
  */
 -void send_packet(const node_t *n, vpn_packet_t *packet)
 -{
 +void send_packet(const node_t *n, vpn_packet_t *packet) {
        node_t *via;
  
        cp();
  
  /* Broadcast a packet using the minimum spanning tree */
  
 -void broadcast_packet(const node_t *from, vpn_packet_t *packet)
 -{
 -      avl_node_t *node;
 +void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
 +      splay_node_t *node;
        connection_t *c;
  
        cp();
        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;
  
        }
  }
  
 -      avl_node_t *node;
+ static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
 -void handle_incoming_vpn_data(int sock)
++      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;
        char *hostname;
        sockaddr_t from;
 -      socklen_t fromlen = sizeof(from);
 +      socklen_t fromlen = sizeof from;
        node_t *n;
  
        cp();
        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);
  }
 +
 +void handle_device_data(int sock, short events, void *data) {
 +      vpn_packet_t packet;
 +
 +      if(read_packet(&packet))
 +              route(myself, &packet);
 +}
diff --combined src/net_setup.c
  
  #include "system.h"
  
 -#include <openssl/pem.h>
 -#include <openssl/rsa.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
  #include "device.h"
 -#include "event.h"
 +#include "digest.h"
  #include "graph.h"
  #include "logger.h"
  #include "net.h"
  #include "process.h"
  #include "protocol.h"
  #include "route.h"
 +#include "rsa.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
  
  char *myport;
 +static struct event device_ev;
  
 -bool read_rsa_public_key(connection_t *c)
 -{
 +bool read_rsa_public_key(connection_t *c) {
        FILE *fp;
        char *fname;
 -      char *key;
 +      char *n;
 +      bool result;
  
        cp();
  
 -      if(!c->rsa_key) {
 -              c->rsa_key = RSA_new();
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -      }
 -
        /* First, check for simple PublicKey statement */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) {
 -              BN_hex2bn(&c->rsa_key->n, key);
 -              BN_hex2bn(&c->rsa_key->e, "FFFF");
 -              free(key);
 -              return true;
 +      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &n)) {
 +              result = rsa_set_hex_public_key(&c->rsa, n, "FFFF");
 +              free(n);
 +              return result;
        }
  
        /* Else, check for PublicKeyFile statement and read it */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
 -              fp = fopen(fname, "r");
 -
 -              if(!fp) {
 -                      logger(LOG_ERR, _("Error reading RSA public key file `%s': %s"),
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 -
 -              free(fname);
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -
 -              if(c->rsa_key)
 -                      return true;            /* Woohoo. */
 +      if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname))
 +              asprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              fp = fopen(fname, "r");
 -
 -              if(!fp) {
 -                      logger(LOG_ERR, _("Error reading RSA public key file `%s': %s"),
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 -
 -              free(fname);
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -
 -              if(c->rsa_key) {
 -//                            RSA_blinding_on(c->rsa_key, NULL);
 -                      return true;
 -              }
 +      fp = fopen(fname, "r");
  
 -              logger(LOG_ERR, _("Reading RSA public key file `%s' failed: %s"),
 +      if(!fp) {
 +              logger(LOG_ERR, _("Error reading RSA public key file `%s': %s"),
                           fname, strerror(errno));
 +              free(fname);
                return false;
        }
  
 -      /* Else, check if a harnessed public key is in the config file */
 -
 -      asprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -      fp = fopen(fname, "r");
 -
 -      if(fp) {
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -      }
 -
 -      free(fname);
 -
 -      if(c->rsa_key)
 -              return true;
 -
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 -
 -      asprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -      fp = fopen(fname, "r");
 -
 -      if(fp) {
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -              fclose(fp);
 -      }
 +      result = rsa_read_pem_public_key(&c->rsa, fp);
 +      fclose(fp);
  
 +      if(!result) 
 +              logger(LOG_ERR, _("Reading RSA public key file `%s' failed: %s"), fname, strerror(errno));
        free(fname);
 -
 -      if(c->rsa_key)
 -              return true;
 -
 -      logger(LOG_ERR, _("No public key for %s specified!"), c->name);
 -
 -      return false;
 +      return result;
  }
  
 -bool read_rsa_private_key(void)
 -{
 +bool read_rsa_private_key() {
        FILE *fp;
 -      char *fname, *key, *pubkey;
 -      struct stat s;
 +      char *fname;
 +      char *n, *d;
 +      bool result;
  
        cp();
  
 -      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) {
 -              if(!get_config_string(lookup_config(myself->connection->config_tree, "PublicKey"), &pubkey)) {
 +      /* First, check for simple PrivateKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) {
 +              if(!get_config_string(lookup_config(myself->connection->config_tree, "PublicKey"), &n)) {
                        logger(LOG_ERR, _("PrivateKey used but no PublicKey found!"));
 +                      free(d);
                        return false;
                }
 -              myself->connection->rsa_key = RSA_new();
 -//            RSA_blinding_on(myself->connection->rsa_key, NULL);
 -              BN_hex2bn(&myself->connection->rsa_key->d, key);
 -              BN_hex2bn(&myself->connection->rsa_key->n, pubkey);
 -              BN_hex2bn(&myself->connection->rsa_key->e, "FFFF");
 -              free(key);
 -              free(pubkey);
 +              result = rsa_set_hex_private_key(&myself->connection->rsa, n, "FFFF", d);
 +              free(n);
 +              free(d);
                return true;
        }
  
 +      /* Else, check for PrivateKeyFile statement and read it */
 +
        if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname))
                asprintf(&fname, "%s/rsa_key.priv", confbase);
  
        }
  
  #if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
        if(fstat(fileno(fp), &s)) {
 -              logger(LOG_ERR, _("Could not stat RSA private key file `%s': %s'"),
 -                              fname, strerror(errno));
 +              logger(LOG_ERR, _("Could not stat RSA private key file `%s': %s'"), fname, strerror(errno));
                free(fname);
                return false;
        }
                logger(LOG_WARNING, _("Warning: insecure file permissions for RSA private key file `%s'!"), fname);
  #endif
  
 -      myself->connection->rsa_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
 +      result = rsa_read_pem_private_key(&myself->connection->rsa, fp);
        fclose(fp);
  
 -      if(!myself->connection->rsa_key) {
 -              logger(LOG_ERR, _("Reading RSA private key file `%s' failed: %s"),
 -                         fname, strerror(errno));
 -              free(fname);
 -              return false;
 +      if(!result) 
 +              logger(LOG_ERR, _("Reading RSA private key file `%s' failed: %s"), fname, strerror(errno));
 +      free(fname);
 +      return result;
 +}
 +
 +static struct event keyexpire_event;
 +
 +static void keyexpire_handler(int fd, short events, void *data) {
 +      regenerate_key();
 +}
 +
 +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 {
 +              timeout_set(&keyexpire_event, keyexpire_handler, NULL);
        }
  
 -      free(fname);
 -      return true;
 +      event_add(&keyexpire_event, &(struct timeval){keylifetime, 0});
  }
  
  /*
    Configure node_t myself and set up the local sockets (listen only)
  */
 -bool setup_myself(void)
 -{
 +bool setup_myself(void) {
        config_t *cfg;
        subnet_t *subnet;
        char *name, *hostname, *mode, *afname, *cipher, *digest;
  
  #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))
  
        /* Generate packet encryption key */
  
 -      if(get_config_string
 -         (lookup_config(myself->connection->config_tree, "Cipher"), &cipher)) {
 -              if(!strcasecmp(cipher, "none")) {
 -                      myself->incipher = NULL;
 -              } else {
 -                      myself->incipher = EVP_get_cipherbyname(cipher);
 -
 -                      if(!myself->incipher) {
 -                              logger(LOG_ERR, _("Unrecognized cipher type!"));
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->incipher = EVP_bf_cbc();
 -
 -      if(myself->incipher)
 -              myself->inkeylength = myself->incipher->key_len + myself->incipher->iv_len;
 -      else
 -              myself->inkeylength = 1;
 +      if(!get_config_string(lookup_config(myself->connection->config_tree, "Cipher"), &cipher))
 +              cipher = xstrdup("blowfish");
  
-       if(!cipher_open_by_name(&myself->cipher, cipher)) {
 -      myself->connection->outcipher = EVP_bf_ofb();
++      if(!cipher_open_by_name(&myself->incipher, cipher)) {
 +              logger(LOG_ERR, _("Unrecognized cipher type!"));
 +              return false;
 +      }
  
        if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime))
                keylifetime = 3600;
  
 -      keyexpires = now + keylifetime;
 -      
 +      regenerate_key();
 +
        /* Check if we want to use message authentication codes... */
  
 -      if(get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest)) {
 -              if(!strcasecmp(digest, "none")) {
 -                      myself->indigest = NULL;
 -              } else {
 -                      myself->indigest = EVP_get_digestbyname(digest);
 +      if(!get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest))
 +              digest = xstrdup("sha1");
  
-       if(!digest_open_by_name(&myself->digest, digest)) {
 -                      if(!myself->indigest) {
 -                              logger(LOG_ERR, _("Unrecognized digest type!"));
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->indigest = EVP_sha1();
 -
 -      myself->connection->outdigest = EVP_sha1();
 -
 -      if(get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->inmaclength)) {
 -              if(myself->indigest) {
 -                      if(myself->inmaclength > myself->indigest->md_size) {
 -                              logger(LOG_ERR, _("MAC length exceeds size of digest!"));
 -                              return false;
 -                      } else if(myself->inmaclength < 0) {
 -                              logger(LOG_ERR, _("Bogus MAC length!"));
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->inmaclength = 4;
++      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))
 -      myself->connection->outmaclength = 0;
++      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;
 +              }
 +      }
  
        /* 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;
  
        if(!setup_device())
                return false;
  
 +      event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL);
 +
 +      if (event_add(&device_ev, NULL) < 0) {
 +              logger(LOG_ERR, _("event_add failed: %s"), strerror(errno));
 +              close_device();
 +              return false;
 +      }
 +
        /* Run tinc-up script to further initialize the tap interface */
        asprintf(&envp[0], "NETNAME=%s", netname ? : "");
        asprintf(&envp[1], "DEVICE=%s", device ? : "");
                listen_socket[listen_sockets].udp =
                        setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
 -              if(listen_socket[listen_sockets].udp < 0)
 +              if(listen_socket[listen_sockets].udp < 0) {
 +                      close(listen_socket[listen_sockets].tcp);
                        continue;
 +              }
 +
 +              event_set(&listen_socket[listen_sockets].ev_tcp,
 +                                listen_socket[listen_sockets].tcp,
 +                                EV_READ|EV_PERSIST,
 +                                handle_new_meta_connection, NULL);
 +              if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
 +                      logger(LOG_EMERG, _("event_add failed: %s"), strerror(errno));
 +                      abort();
 +              }
 +
 +              event_set(&listen_socket[listen_sockets].ev_udp,
 +                                listen_socket[listen_sockets].udp,
 +                                EV_READ|EV_PERSIST,
 +                                handle_incoming_vpn_data, NULL);
 +              if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
 +                      logger(LOG_EMERG, _("event_add failed: %s"), strerror(errno));
 +                      abort();
 +              }
  
                ifdebug(CONNECTIONS) {
                        hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
  
                memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
                listen_sockets++;
 +
 +              if(listen_sockets >= MAXSOCKETS) {
 +                      logger(LOG_WARNING, _("Maximum of %d listening sockets reached"), MAXSOCKETS);
 +                      break;
 +              }
        }
  
        freeaddrinfo(ai);
  }
  
  /*
-   setup all initial network connections
+   initialize network
  */
- bool setup_network_connections(void) {
+ bool setup_network(void)
+ {
        cp();
  
 -      now = time(NULL);
 -
 -      init_events();
        init_connections();
        init_subnets();
        init_nodes();
        if(!setup_myself())
                return false;
  
-       try_outgoing_connections();
        return true;
  }
  
  /*
    close all open network connections
  */
 -void close_network_connections(void)
 -{
 -      avl_node_t *node, *next;
 +void close_network_connections(void) {
 +      splay_node_t *node, *next;
        connection_t *c;
        char *envp[5];
        int i;
        }
  
        for(i = 0; i < listen_sockets; i++) {
 +              event_del(&listen_socket[i].ev_tcp);
 +              event_del(&listen_socket[i].ev_udp);
                close(listen_socket[i].tcp);
                close(listen_socket[i].udp);
        }
        exit_subnets();
        exit_nodes();
        exit_connections();
 -      exit_events();
  
        execute_script("tinc-down", envp);
  
diff --combined src/net_socket.c
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 -#include "event.h"
  #include "logger.h"
  #include "meta.h"
  #include "net.h"
@@@ -33,6 -34,8 +33,8 @@@
  #include "utils.h"
  #include "xalloc.h"
  
+ #include <assert.h>
  #ifdef WSAEINPROGRESS
  #define EINPROGRESS WSAEINPROGRESS
  #endif
@@@ -52,7 -55,8 +54,7 @@@ list_t *outgoing_list = NULL
  
  /* Setup sockets */
  
 -static void configure_tcp(connection_t *c)
 -{
 +static void configure_tcp(connection_t *c) {
        int option;
  
  #ifdef O_NONBLOCK
  
  #if defined(SOL_TCP) && defined(TCP_NODELAY)
        option = 1;
 -      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, &option, sizeof(option));
 +      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, &option, sizeof option);
  #endif
  
  #if defined(SOL_IP) && defined(IP_TOS) && defined(IPTOS_LOWDELAY)
        option = IPTOS_LOWDELAY;
 -      setsockopt(c->socket, SOL_IP, IP_TOS, &option, sizeof(option));
 +      setsockopt(c->socket, SOL_IP, IP_TOS, &option, sizeof option);
  #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;
        /* Optimize TCP settings */
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option);
  
  #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
  #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
                struct ifreq ifr;
  
 -              memset(&ifr, 0, sizeof(ifr));
 +              memset(&ifr, 0, sizeof ifr);
                strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
  
 -              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) {
 +              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;
                }
  #else
-               logger(LOG_WARNING, _("BindToInterface not supported on this platform"));
+               logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface");
  #endif
        }
  
        return nfd;
  }
  
 -int setup_vpn_in_socket(const sockaddr_t *sa)
 -{
 +int setup_vpn_in_socket(const sockaddr_t *sa) {
        int nfd;
        char *addrstr;
        int option;
  #endif
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option);
  
  #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
        }
  #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);
        }
  
        return nfd;
- }
+ } /* int setup_vpn_in_socket */
  
 -void retry_outgoing(outgoing_t *outgoing)
 -{
 -      event_t *event;
 +static void retry_outgoing_handler(int fd, short events, void *data) {
 +      setup_outgoing_connection(data);
 +}
  
 +void retry_outgoing(outgoing_t *outgoing) {
        cp();
  
        outgoing->timeout += 5;
        if(outgoing->timeout > maxtimeout)
                outgoing->timeout = maxtimeout;
  
 -      event = new_event();
 -      event->handler = (event_handler_t) setup_outgoing_connection;
 -      event->time = now + outgoing->timeout;
 -      event->data = outgoing;
 -      event_add(event);
 +      timeout_set(&outgoing->ev, retry_outgoing_handler, outgoing);
 +      event_add(&outgoing->ev, &(struct timeval){outgoing->timeout, 0});
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE,
                           _("Trying to re-establish outgoing connection in %d seconds"),
                           outgoing->timeout);
  }
  
 -void finish_connecting(connection_t *c)
 -{
 +void finish_connecting(connection_t *c) {
        cp();
  
        ifdebug(CONNECTIONS) logger(LOG_INFO, _("Connected to %s (%s)"), c->name, c->hostname);
  
        configure_tcp(c);
  
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
 +      c->status.connecting = false;
  
        send_id(c);
  }
  
 -void do_outgoing_connection(connection_t *c)
 -{
 +void do_outgoing_connection(connection_t *c) {
        char *address, *port;
        int result;
  
@@@ -277,9 -359,8 +353,9 @@@ begin
                if(!c->outgoing->cfg) {
                        ifdebug(CONNECTIONS) logger(LOG_ERR, _("Could not set up a meta connection to %s"),
                                           c->name);
 -                      c->status.remove = true;
                        retry_outgoing(c->outgoing);
 +                      c->outgoing = NULL;
 +                      connection_del(c);
                        return;
                }
  
                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);
        return;
  }
  
 -void setup_outgoing_connection(outgoing_t *outgoing)
 -{
 +void handle_meta_read(struct bufferevent *event, void *data) {
 +      logger(LOG_EMERG, _("handle_meta_read() called"));
 +      abort();
 +}
 +
 +void handle_meta_write(struct bufferevent *event, void *data) {
 +      ifdebug(META) logger(LOG_DEBUG, _("handle_meta_write() called"));
 +}
 +
 +void handle_meta_connection_error(struct bufferevent *event, short what, void *data) {
 +      connection_t *c = data;
 +      logger(LOG_EMERG, _("handle_meta_connection_error() called: %d: %s"), what, strerror(errno));
 +      terminate_connection(c, c->status.active);
 +}
 +
 +void setup_outgoing_connection(outgoing_t *outgoing) {
        connection_t *c;
        node_t *n;
  
        }
  
        c->outgoing = outgoing;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        connection_add(c);
  
        do_outgoing_connection(c);
 +
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_add(&c->inevent, NULL);
 +      c->buffer = bufferevent_new(c->socket, handle_meta_read, handle_meta_write, handle_meta_connection_error, c);
 +      if(!c->buffer) {
 +              logger(LOG_EMERG, _("bufferevent_new() failed: %s"), strerror(errno));
 +              abort();
 +      }
 +      bufferevent_disable(c->buffer, EV_READ);
  }
  
  /*
    accept a new tcp connect and create a
    new connection
  */
 -bool handle_new_meta_connection(int sock)
 -{
 +void handle_new_meta_connection(int sock, short events, void *data) {
        connection_t *c;
        sockaddr_t sa;
        int fd;
 -      socklen_t len = sizeof(sa);
 +      socklen_t len = sizeof sa;
  
        cp();
  
        fd = accept(sock, &sa.sa, &len);
  
        if(fd < 0) {
 -              logger(LOG_ERR, _("Accepting a new connection failed: %s"),
 -                         strerror(errno));
 -              return false;
 +              logger(LOG_ERR, _("Accepting a new connection failed: %s"), strerror(errno));
 +              return;
        }
  
        sockaddrunmap(&sa);
        c->address = sa;
        c->hostname = sockaddr2hostname(&sa);
        c->socket = fd;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE, _("Connection from %s"), c->hostname);
  
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_add(&c->inevent, NULL);
 +      c->buffer = bufferevent_new(c->socket, NULL, handle_meta_write, handle_meta_connection_error, c);
 +      if(!c->buffer) {
 +              logger(LOG_EMERG, _("bufferevent_new() failed: %s"), strerror(errno));
 +              abort();
 +      }
 +      bufferevent_disable(c->buffer, EV_READ);
 +              
        configure_tcp(c);
  
        connection_add(c);
  
        c->allow_request = ID;
        send_id(c);
 -
 -      return true;
  }
  
  void free_outgoing(outgoing_t *outgoing) {
@@@ -493,7 -549,7 +572,7 @@@ void try_outgoing_connections(void
        char *name;
        outgoing_t *outgoing;
        connection_t *c;
 -      avl_node_t *node;
 +      splay_node_t *node;
        
        cp();
  
                        continue;
                }
  
 -              outgoing = xmalloc_and_zero(sizeof(*outgoing));
 +              outgoing = xmalloc_and_zero(sizeof *outgoing);
                outgoing->name = name;
                list_insert_tail(outgoing_list, outgoing);
                setup_outgoing_connection(outgoing);
diff --combined src/netutl.c
@@@ -34,7 -34,8 +34,7 @@@ bool hostnames = false
    Turn a string into a struct addrinfo.
    Return NULL on failure.
  */
 -struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype)
 -{
 +struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype) {
        struct addrinfo *ai, hint = {0};
        int err;
  
@@@ -54,7 -55,8 +54,7 @@@
        return ai;
  }
  
 -sockaddr_t str2sockaddr(const char *address, const char *port)
 -{
 +sockaddr_t str2sockaddr(const char *address, const char *port) {
        struct addrinfo *ai, hint = {0};
        sockaddr_t result;
        int err;
@@@ -82,7 -84,8 +82,7 @@@
        return result;
  }
  
 -void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr)
 -{
 +void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) {
        char address[NI_MAXHOST];
        char port[NI_MAXSERV];
        char *scopeid;
@@@ -96,7 -99,7 +96,7 @@@
                return;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV);
  
        if(err) {
                logger(LOG_ERR, _("Error while translating addresses: %s"),
        *portstr = xstrdup(port);
  }
  
 -char *sockaddr2hostname(const sockaddr_t *sa)
 -{
 +char *sockaddr2hostname(const sockaddr_t *sa) {
        char *str;
        char address[NI_MAXHOST] = "unknown";
        char port[NI_MAXSERV] = "unknown";
                return str;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port),
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port,
                                        hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV));
        if(err) {
                logger(LOG_ERR, _("Error while looking up hostname: %s"),
        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();
                        return strcmp(a->unknown.port, b->unknown.port);
  
                case AF_INET:
 -                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr));
 +                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof a->in.sin_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof(a->in.sin_port));
 +                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof a->in.sin_port);
  
                case AF_INET6:
 -                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
 +                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof a->in6.sin6_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof(a->in6.sin6_port));
 +                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof a->in6.sin6_port);
  
                default:
                        logger(LOG_ERR, _("sockaddrcmp() was called with unknown address family %d, exitting!"),
@@@ -208,7 -246,8 +242,7 @@@ void sockaddrfree(sockaddr_t *a) 
        }
  }
        
 -void sockaddrunmap(sockaddr_t *sa)
 -{
 +void sockaddrunmap(sockaddr_t *sa) {
        cp();
  
        if(sa->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sa->in6.sin6_addr)) {
  
  /* Subnet mask handling */
  
 -int maskcmp(const void *va, const void *vb, int masklen)
 -{
 +int maskcmp(const void *va, const void *vb, int masklen) {
        int i, m, result;
        const char *a = va;
        const char *b = vb;
        return 0;
  }
  
 -void mask(void *va, int masklen, int len)
 -{
 +void mask(void *va, int masklen, int len) {
        int i;
        char *a = va;
  
                a[i] = 0;
  }
  
 -void maskcpy(void *va, const void *vb, int masklen, int len)
 -{
 +void maskcpy(void *va, const void *vb, int masklen, int len) {
        int i, m;
        char *a = va;
        const char *b = vb;
                a[i] = 0;
  }
  
 -bool maskcheck(const void *va, int masklen, int len)
 -{
 +bool maskcheck(const void *va, int masklen, int len) {
        int i;
        const char *a = va;
  
diff --combined src/node.c
@@@ -22,7 -22,7 +22,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -avl_tree_t *node_tree;                        /* Known nodes, sorted by name */
 -avl_tree_t *node_udp_tree;            /* Known nodes, sorted by address and port */
 +splay_tree_t *node_tree;                      /* Known nodes, sorted by name */
 +splay_tree_t *node_udp_tree;          /* Known nodes, sorted by address and port */
  
  node_t *myself;
  
 -static int node_compare(const node_t *a, const node_t *b)
 -{
 +static int node_compare(const node_t *a, const node_t *b) {
        return strcmp(a->name, b->name);
  }
  
 -static int node_udp_compare(const node_t *a, const node_t *b)
 -{
 -       return sockaddrcmp(&a->address, &b->address);
 +static int node_udp_compare(const node_t *a, const node_t *b) {
 +      int result;
 +
 +      cp();
 +
 +      result = sockaddrcmp(&a->address, &b->address);
 +
 +      if(result)
 +              return result;
 +
 +      return (a->name && b->name) ? strcmp(a->name, b->name) : 0;
  }
  
 -void init_nodes(void)
 -{
 +void init_nodes(void) {
        cp();
  
 -      node_tree = avl_alloc_tree((avl_compare_t) node_compare, (avl_action_t) free_node);
 -      node_udp_tree = avl_alloc_tree((avl_compare_t) node_udp_compare, NULL);
 +      node_tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node);
 +      node_udp_tree = splay_alloc_tree((splay_compare_t) node_udp_compare, NULL);
  }
  
 -void exit_nodes(void)
 -{
 +void exit_nodes(void) {
        cp();
  
 -      avl_delete_tree(node_udp_tree);
 -      avl_delete_tree(node_tree);
 +      splay_delete_tree(node_udp_tree);
 +      splay_delete_tree(node_tree);
  }
  
 -node_t *new_node(void)
 -{
 -      node_t *n = xmalloc_and_zero(sizeof(*n));
 +node_t *new_node(void) {
 +      node_t *n = xmalloc_and_zero(sizeof *n);
  
        cp();
  
        n->subnet_tree = new_subnet_tree();
        n->edge_tree = new_edge_tree();
 -      EVP_CIPHER_CTX_init(&n->inctx);
 -      EVP_CIPHER_CTX_init(&n->outctx);
        n->mtu = MTU;
        n->maxmtu = MTU;
  
        return n;
  }
  
 -void free_node(node_t *n)
 -{
 +void free_node(node_t *n) {
        cp();
  
 -      if(n->inkey)
 -              free(n->inkey);
 -
 -      if(n->outkey)
 -              free(n->outkey);
 -
        if(n->subnet_tree)
                free_subnet_tree(n->subnet_tree);
  
  
        sockaddrfree(&n->address);
  
-       cipher_close(&n->cipher);
-       digest_close(&n->digest);
 -      EVP_CIPHER_CTX_cleanup(&n->inctx);
 -      EVP_CIPHER_CTX_cleanup(&n->outctx);
++      cipher_close(&n->incipher);
++      digest_close(&n->indigest);
++      cipher_close(&n->outcipher);
++      digest_close(&n->outdigest);
  
 -      if(n->mtuevent)
 -              event_del(n->mtuevent);
 +      event_del(&n->mtuevent);
        
        if(n->hostname)
                free(n->hostname);
        free(n);
  }
  
 -void node_add(node_t *n)
 -{
 +void node_add(node_t *n) {
        cp();
  
 -      avl_insert(node_tree, n);
 +      splay_insert(node_tree, n);
  }
  
 -void node_del(node_t *n)
 -{
 -      avl_node_t *node, *next;
 +void node_del(node_t *n) {
 +      splay_node_t *node, *next;
        edge_t *e;
        subnet_t *s;
  
                edge_del(e);
        }
  
 -      avl_delete(node_tree, n);
 -      avl_delete(node_udp_tree, n);
 +      splay_delete(node_tree, n);
++      splay_delete(node_udp_tree, n);
  }
  
 -node_t *lookup_node(char *name)
 -{
 +node_t *lookup_node(char *name) {
        node_t n = {0};
  
        cp();
        
        n.name = name;
  
 -      return avl_search(node_tree, &n);
 +      return splay_search(node_tree, &n);
  }
  
 -node_t *lookup_node_udp(const sockaddr_t *sa)
 -{
 +node_t *lookup_node_udp(const sockaddr_t *sa) {
        node_t n = {0};
  
        cp();
        n.address = *sa;
        n.name = NULL;
  
 -      return avl_search(node_udp_tree, &n);
 +      return splay_search(node_udp_tree, &n);
  }
  
 -      avl_delete(node_udp_tree, n);
+ void update_node_udp(node_t *n, const sockaddr_t *sa)
+ {
 -              avl_delete(node_udp_tree, n);
 -              avl_insert(node_udp_tree, n);
++      splay_delete(node_udp_tree, n);
+       if(n->hostname)
+               free(n->hostname);
+       if(sa) {
+               n->address = *sa;
+               n->hostname = sockaddr2hostname(&n->address);
 -void dump_nodes(void)
 -{
 -      avl_node_t *node;
++              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;
  
        cp();
  
 -      logger(LOG_DEBUG, _("Nodes:"));
 -
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
 -              logger(LOG_DEBUG, _(" %s at %s cipher %d digest %d maclength %d compression %d options %lx status %04x nexthop %s via %s pmtu %d (min %d max %d)"),
 -                         n->name, n->hostname, n->outcipher ? n->outcipher->nid : 0,
 -                         n->outdigest ? n->outdigest->type : 0, n->outmaclength, n->outcompression,
 +              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->mtu, n->minmtu, n->maxmtu);
 +                         n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu) == -1)
 +                      return errno;
        }
  
 -      logger(LOG_DEBUG, _("End of nodes."));
 +      return 0;
  }
diff --combined src/node.h
  #ifndef __TINC_NODE_H__
  #define __TINC_NODE_H__
  
 -#include "avl_tree.h"
 +#include <event.h>
 +
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
 -#include "event.h"
 +#include "digest.h"
  #include "list.h"
  #include "subnet.h"
  
@@@ -54,19 -51,31 +54,24 @@@ 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 */
 -      const EVP_CIPHER *incipher;             /* Cipher type for UDP packets received from him */
 -      char *inkey;                            /* Cipher key and iv */
 -      int inkeylength;                        /* Cipher key and iv length */
 -      EVP_CIPHER_CTX inctx;                   /* Cipher context */
 -      
 -      const EVP_CIPHER *outcipher;            /* Cipher type for UDP packets sent to him*/
 -      char *outkey;                           /* Cipher key and iv */
 -      int outkeylength;                       /* Cipher key and iv length */
 -      EVP_CIPHER_CTX outctx;                  /* Cipher context */
 -      
 -      const EVP_MD *indigest;                 /* Digest type for MAC of packets received from him */
 -      int inmaclength;                        /* Length of MAC */
 -
 -      const EVP_MD *outdigest;                /* Digest type for MAC of packets sent to him*/
 -      int outmaclength;                       /* Length of MAC */
++      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 */
        struct node_t *via;                     /* next hop for UDP packets */
  
 -      avl_tree_t *subnet_tree;                /* Pointer to a tree of subnets belonging to this node */
 +      splay_tree_t *subnet_tree;              /* Pointer to a tree of subnets belonging to this node */
  
 -      avl_tree_t *edge_tree;                  /* Edges with this node as one of the endpoints */
 +      splay_tree_t *edge_tree;                        /* Edges with this node as one of the endpoints */
  
        struct connection_t *connection;        /* Connection associated with this node (if a direct connection exists) */
  
        length_t minmtu;                        /* Probed minimum MTU */
        length_t maxmtu;                        /* Probed maximum MTU */
        int mtuprobes;                          /* Number of probes */
 -      event_t *mtuevent;                      /* Probe event */
 +      struct event mtuevent;                  /* Probe event */
  } node_t;
  
  extern struct node_t *myself;
 -extern avl_tree_t *node_tree;
 -extern avl_tree_t *node_udp_tree;
 +extern splay_tree_t *node_tree;
 +extern splay_tree_t *node_udp_tree;
  
  extern void init_nodes(void);
  extern void exit_nodes(void);
@@@ -93,6 -102,7 +98,7 @@@ extern void node_add(node_t *)
  extern void node_del(node_t *);
  extern node_t *lookup_node(char *);
  extern node_t *lookup_node_udp(const sockaddr_t *);
 -extern void dump_nodes(void);
 +extern int dump_nodes(struct evbuffer *);
+ extern void update_node_udp(node_t *, const sockaddr_t *);
  
  #endif                                                        /* __TINC_NODE_H__ */
diff --combined src/openssl/digest.c
index 7c4dfc2,0000000..1e2557d
mode 100644,000000..100644
--- /dev/null
@@@ -1,84 -1,0 +1,84 @@@
- bool digest_create(digest_t *digest, void *indata, size_t inlen, void *outdata) {
 +/*
 +    digest.c -- Digest handling
 +    Copyright (C) 2007 Guus Sliepen <guus@tinc-vpn.org>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +    $Id$
 +*/
 +
 +#include "system.h"
 +
 +#include <openssl/err.h>
 +
 +#include "digest.h"
 +#include "logger.h"
 +
 +bool digest_open_by_name(digest_t *digest, const char *name) {
 +      digest->digest = EVP_get_digestbyname(name);
 +      if(digest->digest)
 +              return true;
 +
 +      logger(LOG_DEBUG, _("Unknown digest name '%s'!"), name);
 +      return false;
 +}
 +
 +bool digest_open_by_nid(digest_t *digest, int nid) {
 +      digest->digest = EVP_get_digestbynid(nid);
 +      if(digest->digest)
 +              return true;
 +
 +      logger(LOG_DEBUG, _("Unknown digest nid %d!"), nid);
 +      return false;
 +}
 +
 +bool digest_open_sha1(digest_t *digest) {
 +      digest->digest = EVP_sha1();
 +      return true;
 +}
 +
 +void digest_close(digest_t *digest) {
 +}
 +
- bool digest_verify(digest_t *digest, void *indata, size_t inlen, void *cmpdata) {
++bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) {
 +      EVP_MD_CTX ctx;
 +
 +      if(EVP_DigestInit(&ctx, digest->digest)
 +                      && EVP_DigestUpdate(&ctx, indata, inlen)
 +                      && EVP_DigestFinal(&ctx, outdata, NULL))
 +              return true;
 +      
 +      logger(LOG_DEBUG, _("Error creating digest: %s"), ERR_error_string(ERR_get_error(), NULL));
 +      return false;
 +}
 +
++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];
 +
 +      return digest_create(digest, indata, inlen, outdata) && !memcmp(cmpdata, outdata, len);
 +}
 +
 +int digest_get_nid(const digest_t *digest) {
 +      return digest->digest ? digest->digest->type : 0;
 +}
 +
 +size_t digest_length(const digest_t *digest) {
 +      return EVP_MD_size(digest->digest);
 +}
 +
 +bool digest_active(const digest_t *digest) {
 +      return digest->digest && digest->digest->type != 0;
 +}
diff --combined src/openssl/digest.h
index f10e67f,0000000..deba7d3
mode 100644,000000..100644
--- /dev/null
@@@ -1,43 -1,0 +1,43 @@@
- 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);
 +/*
 +    digest.h -- header file digest.c
 +    Copyright (C) 2007 Guus Sliepen <guus@tinc-vpn.org>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +    $Id$
 +*/
 +
 +#ifndef __TINC_DIGEST_H__
 +#define __TINC_DIGEST_H__
 +
 +#include <openssl/evp.h>
 +
 +#define DIGEST_MAX_SIZE EVP_MAX_MD_SIZE
 +
 +typedef struct digest {
 +      const EVP_MD *digest;
 +} digest_t;
 +
 +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 *, 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 *);
 +
 +#endif
diff --combined src/protocol.h
@@@ -72,13 -72,14 +72,13 @@@ extern bool tunnelserver
  /* Basic functions */
  
  extern bool send_request(struct connection_t *, const char *, ...) __attribute__ ((__format__(printf, 2, 3)));
 -extern void forward_request(struct connection_t *);
 -extern bool receive_request(struct connection_t *);
 +extern void forward_request(struct connection_t *, char *);
 +extern bool receive_request(struct connection_t *, char *);
  extern bool check_id(const char *);
  
  extern void init_requests(void);
  extern void exit_requests(void);
  extern bool seen_request(char *);
 -extern void age_past_requests(void);
  
  /* Requests */
  
@@@ -96,30 -97,30 +96,30 @@@ extern bool send_add_subnet(struct conn
  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  */
  
 -extern bool id_h(struct connection_t *);
 -extern bool metakey_h(struct connection_t *);
 -extern bool challenge_h(struct connection_t *);
 -extern bool chal_reply_h(struct connection_t *);
 -extern bool ack_h(struct connection_t *);
 -extern bool status_h(struct connection_t *);
 -extern bool error_h(struct connection_t *);
 -extern bool termreq_h(struct connection_t *);
 -extern bool ping_h(struct connection_t *);
 -extern bool pong_h(struct connection_t *);
 -extern bool add_subnet_h(struct connection_t *);
 -extern bool del_subnet_h(struct connection_t *);
 -extern bool add_edge_h(struct connection_t *);
 -extern bool del_edge_h(struct connection_t *);
 -extern bool key_changed_h(struct connection_t *);
 -extern bool req_key_h(struct connection_t *);
 -extern bool ans_key_h(struct connection_t *);
 -extern bool tcppacket_h(struct connection_t *);
 +extern bool id_h(struct connection_t *, char *);
 +extern bool metakey_h(struct connection_t *, char *);
 +extern bool challenge_h(struct connection_t *, char *);
 +extern bool chal_reply_h(struct connection_t *, char *);
 +extern bool ack_h(struct connection_t *, char *);
 +extern bool status_h(struct connection_t *, char *);
 +extern bool error_h(struct connection_t *, char *);
 +extern bool termreq_h(struct connection_t *, char *);
 +extern bool ping_h(struct connection_t *, char *);
 +extern bool pong_h(struct connection_t *, char *);
 +extern bool add_subnet_h(struct connection_t *, char *);
 +extern bool del_subnet_h(struct connection_t *, char *);
 +extern bool add_edge_h(struct connection_t *, char *);
 +extern bool del_edge_h(struct connection_t *, char *);
 +extern bool key_changed_h(struct connection_t *, char *);
 +extern bool req_key_h(struct connection_t *, char *);
 +extern bool ans_key_h(struct connection_t *, char *);
 +extern bool tcppacket_h(struct connection_t *, char *);
  
  #endif                                                        /* __TINC_PROTOCOL_H__ */
diff --combined src/protocol_auth.c
  
  #include "system.h"
  
 -#include <openssl/sha.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 +#include "crypto.h"
  #include "edge.h"
  #include "graph.h"
  #include "logger.h"
  #include "netutl.h"
  #include "node.h"
  #include "protocol.h"
 +#include "rsa.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -bool send_id(connection_t *c)
 -{
 +bool send_id(connection_t *c) {
        cp();
  
 +      gettimeofday(&c->start, NULL);
 +
        return send_request(c, "%d %s %d", ID, myself->connection->name,
                                                myself->connection->protocol_version);
  }
  
 -bool id_h(connection_t *c)
 -{
 +bool id_h(connection_t *c, char *request) {
        char name[MAX_STRING_SIZE];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " %d", name, &c->protocol_version) != 2) {
 +      if(sscanf(request, "%*d " MAX_STRING " %d", name, &c->protocol_version) != 2) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "ID", c->name,
                           c->hostname);
                return false;
        return send_metakey(c);
  }
  
 -bool send_metakey(connection_t *c)
 -{
 -      char *buffer;
 -      int len;
 -      bool x;
 +bool send_metakey(connection_t *c) {
 +      size_t len = rsa_size(&c->rsa);
 +      char key[len];
 +      char enckey[len];
 +      char hexkey[2 * len + 1];
  
        cp();
  
 -      len = RSA_size(c->rsa_key);
 -
 -      /* Allocate buffers for the meta key */
 -
 -      buffer = alloca(2 * len + 1);
 +      if(!cipher_open_blowfish_ofb(&c->outcipher))
 +              return false;
        
 -      c->outkey = xrealloc(c->outkey, len);
 +      if(!digest_open_sha1(&c->outdigest))
 +              return false;
  
 -      if(!c->outctx)
 -              c->outctx = xmalloc_and_zero(sizeof(*c->outctx));
 -      cp();
 -      /* Copy random data to the buffer */
 +      /* Create a random key */
  
 -      RAND_pseudo_bytes((unsigned char *)c->outkey, len);
 +      randomize(key, len);
  
        /* The message we send must be smaller than the modulus of the RSA key.
           By definition, for a key of k bits, the following formula holds:
           This can be done by setting the most significant bit to zero.
         */
  
 -      c->outkey[0] &= 0x7F;
 +      key[0] &= 0x7F;
 +
 +      cipher_set_key_from_rsa(&c->outcipher, key, len, true);
  
        ifdebug(SCARY_THINGS) {
 -              bin2hex(c->outkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, _("Generated random meta key (unencrypted): %s"),
 -                         buffer);
 +              bin2hex(key, hexkey, len);
 +              hexkey[len * 2] = '\0';
 +              logger(LOG_DEBUG, _("Generated random meta key (unencrypted): %s"), hexkey);
        }
  
        /* Encrypt the random data
           with a length equal to that of the modulus of the RSA key.
         */
  
 -      if(RSA_public_encrypt(len, (unsigned char *)c->outkey, (unsigned char *)buffer, c->rsa_key, RSA_NO_PADDING) != len) {
 -              logger(LOG_ERR, _("Error during encryption of meta key for %s (%s)"),
 -                         c->name, c->hostname);
 +      if(!rsa_public_encrypt(&c->rsa, key, len, enckey)) {
 +              logger(LOG_ERR, _("Error during encryption of meta key for %s (%s)"), c->name, c->hostname);
                return false;
        }
  
        /* Convert the encrypted random data to a hexadecimal formatted string */
  
 -      bin2hex(buffer, buffer, len);
 -      buffer[len * 2] = '\0';
 +      bin2hex(enckey, hexkey, len);
 +      hexkey[len * 2] = '\0';
  
        /* Send the meta key */
  
 -      x = send_request(c, "%d %d %d %d %d %s", METAKEY,
 -                                       c->outcipher ? c->outcipher->nid : 0,
 -                                       c->outdigest ? c->outdigest->type : 0, c->outmaclength,
 -                                       c->outcompression, buffer);
 -
 -      /* Further outgoing requests are encrypted with the key we just generated */
 -
 -      if(c->outcipher) {
 -              if(!EVP_EncryptInit(c->outctx, c->outcipher,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len -
 -                                      c->outcipher->iv_len)) {
 -                      logger(LOG_ERR, _("Error during initialisation of cipher for %s (%s): %s"),
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.encryptout = true;
 -      }
 -
 -      return x;
 +      bool result = send_request(c, "%d %d %d %d %d %s", METAKEY,
 +                       cipher_get_nid(&c->outcipher),
 +                       digest_get_nid(&c->outdigest), c->outmaclength,
 +                       c->outcompression, hexkey);
 +      
 +      c->status.encryptout = true;
 +      return result;
  }
  
 -bool metakey_h(connection_t *c)
 -{
 -      char buffer[MAX_STRING_SIZE];
 +bool metakey_h(connection_t *c, char *request) {
 +      char hexkey[MAX_STRING_SIZE];
        int cipher, digest, maclength, compression;
 -      int len;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      char enckey[len];
 +      char key[len];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, buffer) != 5) {
 -              logger(LOG_ERR, _("Got bad %s from %s (%s)"), "METAKEY", c->name,
 -                         c->hostname);
 +      if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) {
 +              logger(LOG_ERR, _("Got bad %s from %s (%s)"), "METAKEY", c->name, c->hostname);
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 -
        /* Check if the length of the meta key is all right */
  
 -      if(strlen(buffer) != len * 2) {
 +      if(strlen(hexkey) != len * 2) {
                logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name, c->hostname, "wrong keylength");
                return false;
        }
  
 -      /* Allocate buffers for the meta key */
 -
 -      c->inkey = xrealloc(c->inkey, len);
 -
 -      if(!c->inctx)
 -              c->inctx = xmalloc_and_zero(sizeof(*c->inctx));
 -
        /* Convert the challenge from hexadecimal back to binary */
  
 -      hex2bin(buffer, buffer, len);
 +      hex2bin(hexkey, enckey, len);
  
        /* Decrypt the meta key */
  
 -      if(RSA_private_decrypt(len, (unsigned char *)buffer, (unsigned char *)c->inkey, myself->connection->rsa_key, RSA_NO_PADDING) != len) {  /* See challenge() */
 -              logger(LOG_ERR, _("Error during decryption of meta key for %s (%s)"),
 -                         c->name, c->hostname);
 +      if(!rsa_private_decrypt(&myself->connection->rsa, enckey, len, key)) {
 +              logger(LOG_ERR, _("Error during decryption of meta key for %s (%s)"), c->name, c->hostname);
                return false;
        }
  
        ifdebug(SCARY_THINGS) {
 -              bin2hex(c->inkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, _("Received random meta key (unencrypted): %s"), buffer);
 +              bin2hex(key, hexkey, len);
 +              hexkey[len * 2] = '\0';
 +              logger(LOG_DEBUG, _("Received random meta key (unencrypted): %s"), hexkey);
        }
  
 -      /* All incoming requests will now be encrypted. */
 -
        /* Check and lookup cipher and digest algorithms */
  
 -      if(cipher) {
 -              c->incipher = EVP_get_cipherbynid(cipher);
 -              
 -              if(!c->incipher) {
 -                      logger(LOG_ERR, _("%s (%s) uses unknown cipher!"), c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(!EVP_DecryptInit(c->inctx, c->incipher,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len -
 -                                      c->incipher->iv_len)) {
 -                      logger(LOG_ERR, _("Error during initialisation of cipher from %s (%s): %s"),
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.decryptin = true;
 -      } else {
 -              c->incipher = NULL;
 +      if(!cipher_open_by_nid(&c->incipher, cipher) || !cipher_set_key_from_rsa(&c->incipher, key, len, false)) {
 +              logger(LOG_ERR, _("Error during initialisation of cipher from %s (%s)"), c->name, c->hostname);
 +              return false;
        }
  
 -      c->inmaclength = maclength;
 -
 -      if(digest) {
 -              c->indigest = EVP_get_digestbynid(digest);
 -
 -              if(!c->indigest) {
 -                      logger(LOG_ERR, _("Node %s (%s) uses unknown digest!"), c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(c->inmaclength > c->indigest->md_size || c->inmaclength < 0) {
 -                      logger(LOG_ERR, _("%s (%s) uses bogus MAC length!"), c->name, c->hostname);
 -                      return false;
 -              }
 -      } else {
 -              c->indigest = NULL;
 +      if(!digest_open_by_nid(&c->indigest, digest)) {
 +              logger(LOG_ERR, _("Error during initialisation of digest from %s (%s)"), c->name, c->hostname);
 +              return false;
        }
  
 -      c->incompression = compression;
 +      c->status.decryptin = true;
  
        c->allow_request = CHALLENGE;
  
        return send_challenge(c);
  }
  
 -bool send_challenge(connection_t *c)
 -{
 -      char *buffer;
 -      int len;
 +bool send_challenge(connection_t *c) {
 +      size_t len = rsa_size(&c->rsa);
 +      char buffer[len * 2 + 1];
  
        cp();
  
 -      /* CHECKME: what is most reasonable value for len? */
 -
 -      len = RSA_size(c->rsa_key);
 -
 -      /* Allocate buffers for the challenge */
 -
 -      buffer = alloca(2 * len + 1);
 -
 -      c->hischallenge = xrealloc(c->hischallenge, len);
 +      if(!c->hischallenge)
-               c->hischallenge = xmalloc(len);
++              c->hischallenge = xrealloc(c->hischallenge, len);
  
        /* Copy random data to the buffer */
  
 -      RAND_pseudo_bytes((unsigned char *)c->hischallenge, len);
 +      randomize(c->hischallenge, len);
  
        /* Convert to hex */
  
        return send_request(c, "%d %s", CHALLENGE, buffer);
  }
  
 -bool challenge_h(connection_t *c)
 -{
 +bool challenge_h(connection_t *c, char *request) {
        char buffer[MAX_STRING_SIZE];
 -      int len;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      size_t digestlen = digest_length(&c->outdigest);
 +      char digest[digestlen];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING, buffer) != 1) {
 -              logger(LOG_ERR, _("Got bad %s from %s (%s)"), "CHALLENGE", c->name,
 -                         c->hostname);
 +      if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) {
 +              logger(LOG_ERR, _("Got bad %s from %s (%s)"), "CHALLENGE", c->name, c->hostname);
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 -
        /* Check if the length of the challenge is all right */
  
        if(strlen(buffer) != len * 2) {
 -              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name,
 -                         c->hostname, "wrong challenge length");
 +              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name, c->hostname, "wrong challenge length");
                return false;
        }
  
 -      /* Allocate buffers for the challenge */
 -
 -      c->mychallenge = xrealloc(c->mychallenge, len);
 -
        /* Convert the challenge from hexadecimal back to binary */
  
 -      hex2bin(buffer, c->mychallenge, len);
 +      hex2bin(buffer, buffer, len);
  
        c->allow_request = CHAL_REPLY;
  
 -      /* Rest is done by send_chal_reply() */
 -
 -      return send_chal_reply(c);
 -}
 -
 -bool send_chal_reply(connection_t *c)
 -{
 -      char hash[EVP_MAX_MD_SIZE * 2 + 1];
 -      EVP_MD_CTX ctx;
 -
        cp();
  
        /* Calculate the hash from the challenge we received */
  
 -      if(!EVP_DigestInit(&ctx, c->indigest)
 -                      || !EVP_DigestUpdate(&ctx, c->mychallenge, RSA_size(myself->connection->rsa_key))
 -                      || !EVP_DigestFinal(&ctx, (unsigned char *)hash, NULL)) {
 -              logger(LOG_ERR, _("Error during calculation of response for %s (%s): %s"),
 -                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -              return false;
 -      }
 +      digest_create(&c->indigest, buffer, len, digest);
  
        /* Convert the hash to a hexadecimal formatted string */
  
 -      bin2hex(hash, hash, c->indigest->md_size);
 -      hash[c->indigest->md_size * 2] = '\0';
 +      bin2hex(digest, buffer, digestlen);
 +      buffer[digestlen * 2] = '\0';
  
        /* Send the reply */
  
 -      return send_request(c, "%d %s", CHAL_REPLY, hash);
 +      return send_request(c, "%d %s", CHAL_REPLY, buffer);
  }
  
 -bool chal_reply_h(connection_t *c)
 -{
 +bool chal_reply_h(connection_t *c, char *request) {
        char hishash[MAX_STRING_SIZE];
 -      char myhash[EVP_MAX_MD_SIZE];
 -      EVP_MD_CTX ctx;
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING, hishash) != 1) {
 +      if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "CHAL_REPLY", c->name,
                           c->hostname);
                return false;
  
        /* Check if the length of the hash is all right */
  
 -      if(strlen(hishash) != c->outdigest->md_size * 2) {
 -              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name,
 -                         c->hostname, _("wrong challenge reply length"));
 +      if(strlen(hishash) != digest_length(&c->outdigest) * 2) {
 +              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name, c->hostname, _("wrong challenge reply length"));
                return false;
        }
  
        /* Convert the hash to binary format */
  
 -      hex2bin(hishash, hishash, c->outdigest->md_size);
 +      hex2bin(hishash, hishash, digest_length(&c->outdigest));
  
 -      /* Calculate the hash from the challenge we sent */
 -
 -      if(!EVP_DigestInit(&ctx, c->outdigest)
 -                      || !EVP_DigestUpdate(&ctx, c->hischallenge, RSA_size(c->rsa_key))
 -                      || !EVP_DigestFinal(&ctx, (unsigned char *)myhash, NULL)) {
 -              logger(LOG_ERR, _("Error during calculation of response from %s (%s): %s"),
 -                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -              return false;
 -      }
 -
 -      /* Verify the incoming hash with the calculated hash */
 -
 -      if(memcmp(hishash, myhash, c->outdigest->md_size)) {
 -              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name,
 -                         c->hostname, _("wrong challenge reply"));
 -
 -              ifdebug(SCARY_THINGS) {
 -                      bin2hex(myhash, hishash, SHA_DIGEST_LENGTH);
 -                      hishash[SHA_DIGEST_LENGTH * 2] = '\0';
 -                      logger(LOG_DEBUG, _("Expected challenge reply: %s"), hishash);
 -              }
 +      /* Verify the hash */
  
 +      if(!digest_verify(&c->outdigest, c->hischallenge, rsa_size(&c->rsa), hishash)) {
 +              logger(LOG_ERR, _("Possible intruder %s (%s): %s"), c->name, c->hostname, _("wrong challenge reply"));
                return false;
        }
  
           Send an acknowledgement with the rest of the information needed.
         */
  
 +      free(c->hischallenge);
 +      c->hischallenge = NULL;
        c->allow_request = ACK;
  
        return send_ack(c);
  }
  
 -bool send_ack(connection_t *c)
 -{
 +bool send_ack(connection_t *c) {
        /* ACK message contains rest of the information the other end needs
           to create node_t and edge_t structures. */
  
        return send_request(c, "%d %s %d %lx", ACK, myport, c->estimated_weight, c->options);
  }
  
 -static void send_everything(connection_t *c)
 -{
 -      avl_node_t *node, *node2;
 +static void send_everything(connection_t *c) {
 +      splay_node_t *node, *node2;
        node_t *n;
        subnet_t *s;
        edge_t *e;
        }
  }
  
 -bool ack_h(connection_t *c)
 -{
 +bool ack_h(connection_t *c, char *request) {
        char hisport[MAX_STRING_SIZE];
        char *hisaddress, *dummy;
        int weight, mtu;
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " %d %lx", hisport, &weight, &options) != 3) {
 +      if(sscanf(request, "%*d " MAX_STRING " %d %lx", hisport, &weight, &options) != 3) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "ACK", c->name,
                           c->hostname);
                return false;
        } else {
                if(n->connection) {
                        /* Oh dear, we already have a connection to this node. */
 -                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, _("Established a second connection with %s (%s), closing old connection"),
 -                                         n->name, n->hostname);
 +                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, _("Established a second connection with %s (%s), closing old connection"), n->connection->name, n->connection->hostname);
 +
 +                      if(n->connection->outgoing) {
 +                              if(c->outgoing)
 +                                      logger(LOG_WARNING, _("Two outgoing connections to the same node!"));
 +                              else
 +                                      c->outgoing = n->connection->outgoing;
 +
 +                              n->connection->outgoing = NULL;
 +                      }
 +
                        terminate_connection(n->connection, false);
                        /* Run graph algorithm to purge key and make sure up/down scripts are rerun with new IP addresses and stuff */
                        graph();
diff --combined src/protocol_edge.c
@@@ -22,7 -22,7 +22,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
  #include "edge.h"
@@@ -36,7 -36,8 +36,7 @@@
  #include "utils.h"
  #include "xalloc.h"
  
 -bool send_add_edge(connection_t *c, const edge_t *e)
 -{
 +bool send_add_edge(connection_t *c, const edge_t *e) {
        bool x;
        char *address, *port;
  
@@@ -53,7 -54,8 +53,7 @@@
        return x;
  }
  
 -bool add_edge_h(connection_t *c)
 -{
 +bool add_edge_h(connection_t *c, char *request) {
        edge_t *e;
        node_t *from, *to;
        char from_name[MAX_STRING_SIZE];
@@@ -66,7 -68,7 +66,7 @@@
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %lx %d",
 +      if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %lx %d",
                          from_name, to_name, to_address, to_port, &options, &weight) != 6) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "ADD_EDGE", c->name,
                           c->hostname);
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* 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();
                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 */
  
        /* Tell the rest about the new edge */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Run MST before or after we tell the rest? */
  
        return true;
  }
  
 -bool send_del_edge(connection_t *c, const edge_t *e)
 -{
 +bool send_del_edge(connection_t *c, const edge_t *e) {
        cp();
  
        return send_request(c, "%d %lx %s %s", DEL_EDGE, random(),
                                                e->from->name, e->to->name);
  }
  
 -bool del_edge_h(connection_t *c)
 -{
 +bool del_edge_h(connection_t *c, char *request) {
        edge_t *e;
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) {
 +      if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "DEL_EDGE", c->name,
                           c->hostname);
                return false;
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* 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"),
                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);
        /* Tell the rest about the deleted edge */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Delete the edge */
  
diff --combined src/protocol_key.c
  
  #include "system.h"
  
 -#include <openssl/evp.h>
 -#include <openssl/err.h>
 -#include <openssl/rand.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
++#include "crypto.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -bool mykeyused = false;
 +static bool mykeyused = false;
  
- bool send_key_changed(connection_t *c, const node_t *n) {
 -bool send_key_changed()
 -{
++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)
 -{
 +bool key_changed_h(connection_t *c, char *request) {
        char name[MAX_STRING_SIZE];
        node_t *n;
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING, name) != 1) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "KEY_CHANGED",
                           c->name, c->hostname);
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        n = lookup_node(name);
        /* Tell the others */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, 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)
 -{
++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)
 -{
 +bool req_key_h(connection_t *c, char *request) {
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
        node_t *from, *to;
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) {
 +      if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "REQ_KEY", c->name,
                           c->hostname);
                return false;
        /* 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;
                        return true;
                }
  
-               send_req_key(to->nexthop->connection, from, to);
 -              send_request(to->nexthop->connection, "%s", c->buffer);
++              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)
 -{
 -      char *key;
++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);
 -      // Set key parameters
 -      to->incipher = myself->incipher;
 -      to->inkeylength = myself->inkeylength;
 -      to->indigest = myself->indigest;
++      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;
 -      // Allocate memory for key
 -      to->inkey = xrealloc(to->inkey, to->inkeylength);
++      randomize(key, keylen);
++      cipher_set_key(&to->incipher, key, true);
 -      // Create a new key
 -      RAND_pseudo_bytes((unsigned char *)to->inkey, to->inkeylength);
 -      if(to->incipher)
 -              EVP_DecryptInit_ex(&to->inctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + to->incipher->key_len);
 +      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));
 -      // Convert to hexadecimal and send
 -      key = alloca(2 * to->inkeylength + 1);
 -      bin2hex(to->inkey, key, to->inkeylength);
 -      key[to->inkeylength * 2] = '\0';
 -
+       return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY,
 -                      myself->name, to->name, key,
 -                      to->incipher ? to->incipher->nid : 0,
 -                      to->indigest ? to->indigest->type : 0, to->inmaclength,
 -                      to->incompression);
++                                              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)
 -{
 +bool ans_key_h(connection_t *c, char *request) {
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
        char key[MAX_STRING_SIZE];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d",
 +      if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d",
                from_name, to_name, key, &cipher, &digest, &maclength,
                &compression) != 7) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "ANS_KEY", c->name,
  
                if(!to->status.reachable) {
                        logger(LOG_WARNING, _("Got %s from %s (%s) destination %s which is not reachable"),
 -                              "ANS_KEY", c->name, c->hostname, to_name);
 +                                 "ANS_KEY", c->name, c->hostname, to_name);
                        return true;
                }
  
 -              return send_request(to->nexthop->connection, "%s", c->buffer);
 +              return send_request(to->nexthop->connection, "%s", request);
        }
  
 -      /* Update our copy of the origin's packet key */
 -      from->outkey = xrealloc(from->outkey, strlen(key) / 2);
 -
 -      from->outkey = xstrdup(key);
 -      from->outkeylength = strlen(key) / 2;
 -      hex2bin(key, from->outkey, from->outkeylength);
 -
 -      from->status.waitingforkey = false;
        /* Check and lookup cipher and digest algorithms */
  
-       if(!cipher_open_by_nid(&from->cipher, cipher)) {
 -      if(cipher) {
 -              from->outcipher = EVP_get_cipherbynid(cipher);
 -
 -              if(!from->outcipher) {
 -                      logger(LOG_ERR, _("Node %s (%s) uses unknown cipher!"), from->name,
 -                                 from->hostname);
 -                      return false;
 -              }
++      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(from->outkeylength != from->outcipher->key_len + from->outcipher->iv_len) {
 -                      logger(LOG_ERR, _("Node %s (%s) uses wrong keylength!"), from->name,
 -                                 from->hostname);
 -                      return false;
 -              }
 -      } else {
 -              from->outcipher = NULL;
++      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) {
 -              from->outdigest = EVP_get_digestbynid(digest);
 -
 -              if(!from->outdigest) {
 -                      logger(LOG_ERR, _("Node %s (%s) uses unknown digest!"), from->name,
 -                                 from->hostname);
 -                      return false;
 -              }
++      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 > from->outdigest->md_size || from->outmaclength < 0) {
 -                      logger(LOG_ERR, _("Node %s (%s) uses bogus MAC length!"),
 -                                 from->name, from->hostname);
 -                      return false;
 -              }
 -      } else {
 -              from->outdigest = NULL;
++      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;
        }
  
        if(compression < 0 || compression > 11) {
                return false;
        }
        
-       from->compression = compression;
+       from->outcompression = compression;
  
 -      if(from->outcipher)
 -              if(!EVP_EncryptInit_ex(&from->outctx, from->outcipher, NULL, (unsigned char *)from->outkey, (unsigned char *)from->outkey + from->outcipher->key_len)) {
 -                      logger(LOG_ERR, _("Error during initialisation of key from %s (%s): %s"),
 -                                      from->name, from->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 +      /* 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;
        from->sent_seqno = 0;
  
        if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuprobes)
diff --combined src/protocol_subnet.c
@@@ -45,7 -45,7 +45,7 @@@ bool send_add_subnet(connection_t *c, c
        return send_request(c, "%d %lx %s %s", ADD_SUBNET, random(), subnet->owner->name, netstr);
  }
  
 -bool add_subnet_h(connection_t *c)
 +bool add_subnet_h(connection_t *c, char *request)
  {
        char subnetstr[MAX_STRING_SIZE];
        char name[MAX_STRING_SIZE];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "ADD_SUBNET", c->name,
                           c->hostname);
                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,
@@@ -76,7 -76,7 +76,7 @@@
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Check if the owner of the new subnet is in the connection list */
                        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);
        }
        /* Tell the rest */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        return true;
  }
@@@ -161,7 -164,7 +164,7 @@@ bool send_del_subnet(connection_t *c, c
        return send_request(c, "%d %lx %s %s", DEL_SUBNET, random(), s->owner->name, netstr);
  }
  
 -bool del_subnet_h(connection_t *c)
 +bool del_subnet_h(connection_t *c, char *request)
  {
        char subnetstr[MAX_STRING_SIZE];
        char name[MAX_STRING_SIZE];
  
        cp();
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
                logger(LOG_ERR, _("Got bad %s from %s (%s)"), "DEL_SUBNET", c->name,
                           c->hostname);
                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,
                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)) {
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      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;
        /* Tell the rest */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Finally, delete it. */
  
diff --combined src/tincd.c
  #include <sys/mman.h>
  #endif
  
 -#include <openssl/rand.h>
 -#include <openssl/rsa.h>
 -#include <openssl/pem.h>
 -#include <openssl/evp.h>
 -#include <openssl/engine.h>
 -
  #include LZO1X_H
  
+ #ifndef HAVE_MINGW
+ #include <pwd.h>
+ #include <grp.h>
+ #include <time.h>
+ #endif
  #include <getopt.h>
 -#include "pidfile.h"
  
  #include "conf.h"
 +#include "control.h"
 +#include "crypto.h"
  #include "device.h"
  #include "logger.h"
  #include "net.h"
@@@ -56,17 -67,29 +62,23 @@@ bool show_help = false
  /* If nonzero, print the version on standard output and exit.  */
  bool show_version = false;
  
 -/* If nonzero, it will attempt to kill a running tincd and exit. */
 -int kill_tincd = 0;
 -
 -/* If nonzero, generate public/private keypair for this host/net. */
 -int generate_keys = 0;
 -
  /* If nonzero, use null ciphers and skip all key exchanges. */
  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;
  
  char *identname = NULL;                               /* program name for syslog */
 -char *pidfilename = NULL;                     /* pid file location */
 +char *controlsocketname = NULL;                       /* control socket location */
  char *logfilename = NULL;                     /* log file location */
  char **g_argv;                                        /* a copy of the cmdline arguments */
  
@@@ -74,15 -97,19 +86,17 @@@ static int status
  
  static struct option const long_options[] = {
        {"config", required_argument, NULL, 'c'},
 -      {"kill", optional_argument, NULL, 'k'},
        {"net", required_argument, NULL, 'n'},
        {"help", no_argument, NULL, 1},
        {"version", no_argument, NULL, 2},
        {"no-detach", no_argument, NULL, 'D'},
 -      {"generate-keys", optional_argument, NULL, 'K'},
        {"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},
 -      {"pidfile", required_argument, NULL, 5},
 +      {"controlsocket", required_argument, NULL, 5},
        {NULL, 0, NULL, 0}
  };
  
@@@ -97,16 -124,19 +111,17 @@@ static void usage(bool status
                                program_name);
        else {
                printf(_("Usage: %s [option]...\n\n"), program_name);
 -              printf(_("  -c, --config=DIR           Read configuration options from DIR.\n"
 -                              "  -D, --no-detach            Don't fork and detach.\n"
 -                              "  -d, --debug[=LEVEL]        Increase debug level or set it to LEVEL.\n"
 -                              "  -k, --kill[=SIGNAL]        Attempt to kill a running tincd and exit.\n"
 -                              "  -n, --net=NETNAME          Connect to net NETNAME.\n"
 -                              "  -K, --generate-keys[=BITS] Generate public/private RSA keypair.\n"
 -                              "  -L, --mlock                Lock tinc into main memory.\n"
 -                              "      --logfile[=FILENAME]   Write log entries to a logfile.\n"
 -                              "      --pidfile=FILENAME     Write PID to FILENAME.\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(_(       "  -c, --config=DIR              Read configuration options from DIR.\n"
 +                              "  -D, --no-detach               Don't fork and detach.\n"
 +                              "  -d, --debug[=LEVEL]           Increase debug level or set it to LEVEL.\n"
 +                              "  -n, --net=NETNAME             Connect to net NETNAME.\n"
 +                              "  -L, --mlock                   Lock tinc into main memory.\n"
 +                              "      --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 -146,7 +131,7 @@@ static bool parse_options(int argc, cha
        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::k::n:K::RU:", 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;
                                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)
                                        debug_level++;
                                break;
  
 -                      case 'k':                               /* kill old tincds */
 -#ifndef HAVE_MINGW
 -                              if(optarg) {
 -                                      if(!strcasecmp(optarg, "HUP"))
 -                                              kill_tincd = SIGHUP;
 -                                      else if(!strcasecmp(optarg, "TERM"))
 -                                              kill_tincd = SIGTERM;
 -                                      else if(!strcasecmp(optarg, "KILL"))
 -                                              kill_tincd = SIGKILL;
 -                                      else if(!strcasecmp(optarg, "USR1"))
 -                                              kill_tincd = SIGUSR1;
 -                                      else if(!strcasecmp(optarg, "USR2"))
 -                                              kill_tincd = SIGUSR2;
 -                                      else if(!strcasecmp(optarg, "WINCH"))
 -                                              kill_tincd = SIGWINCH;
 -                                      else if(!strcasecmp(optarg, "INT"))
 -                                              kill_tincd = SIGINT;
 -                                      else if(!strcasecmp(optarg, "ALRM"))
 -                                              kill_tincd = SIGALRM;
 -                                      else {
 -                                              kill_tincd = atoi(optarg);
 -
 -                                              if(!kill_tincd) {
 -                                                      fprintf(stderr, _("Invalid argument `%s'; SIGNAL must be a number or one of HUP, TERM, KILL, USR1, USR2, WINCH, INT or ALRM.\n"),
 -                                                                      optarg);
 -                                                      usage(true);
 -                                                      return false;
 -                                              }
 -                                      }
 -                              } else
 -                                      kill_tincd = SIGTERM;
 -#else
 -                                      kill_tincd = 1;
 -#endif
 -                              break;
 -
                        case 'n':                               /* net name given */
                                netname = xstrdup(optarg);
                                break;
  
 -                      case 'K':                               /* generate public/private keypair */
 -                              if(optarg) {
 -                                      generate_keys = atoi(optarg);
 -
 -                                      if(generate_keys < 512) {
 -                                              fprintf(stderr, _("Invalid argument `%s'; BITS must be a number equal to or greater than 512.\n"),
 -                                                              optarg);
 -                                              usage(true);
 -                                              return false;
 -                                      }
 -
 -                                      generate_keys &= ~7;    /* Round it to bytes */
 -                              } else
 -                                      generate_keys = 1024;
 -                              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;
                                        logfilename = xstrdup(optarg);
                                break;
  
 -                      case 5:                                 /* write PID to a file */
 -                              pidfilename = xstrdup(optarg);
 +                      case 5:                                 /* open control socket here */
 +                              controlsocketname = xstrdup(optarg);
                                break;
  
                        case '?':
        return true;
  }
  
 -/* This function prettyprints the key generation process */
 -
 -static void indicator(int a, int b, void *p)
 -{
 -      switch (a) {
 -              case 0:
 -                      fprintf(stderr, ".");
 -                      break;
 -
 -              case 1:
 -                      fprintf(stderr, "+");
 -                      break;
 -
 -              case 2:
 -                      fprintf(stderr, "-");
 -                      break;
 -
 -              case 3:
 -                      switch (b) {
 -                              case 0:
 -                                      fprintf(stderr, " p\n");
 -                                      break;
 -
 -                              case 1:
 -                                      fprintf(stderr, " q\n");
 -                                      break;
 -
 -                              default:
 -                                      fprintf(stderr, "?");
 -                      }
 -                      break;
 -
 -              default:
 -                      fprintf(stderr, "?");
 -      }
 -}
 -
 -/*
 -  Generate a public/private RSA keypair, and ask for a file to store
 -  them in.
 -*/
 -static bool keygen(int bits)
 -{
 -      RSA *rsa_key;
 -      FILE *f;
 -      char *name = NULL;
 -      char *filename;
 -
 -      get_config_string(lookup_config(config_tree, "Name"), &name);
 -
 -      if(name && !check_id(name)) {
 -              fprintf(stderr, _("Invalid name for myself!\n"));
 -              return false;
 -      }
 -
 -      fprintf(stderr, _("Generating %d bits keys:\n"), bits);
 -      rsa_key = RSA_generate_key(bits, 0x10001, indicator, NULL);
 -
 -      if(!rsa_key) {
 -              fprintf(stderr, _("Error during key generation!\n"));
 -              return false;
 -      } else
 -              fprintf(stderr, _("Done.\n"));
 -
 -      asprintf(&filename, "%s/rsa_key.priv", confbase);
 -      f = ask_and_open(filename, _("private RSA key"));
 -
 -      if(!f)
 -              return false;
 -
 -      if(disable_old_keys(f))
 -              fprintf(stderr, _("Warning: old key(s) found and disabled.\n"));
 -  
 -#ifdef HAVE_FCHMOD
 -      /* Make it unreadable for others. */
 -      fchmod(fileno(f), 0600);
 -#endif
 -              
 -      PEM_write_RSAPrivateKey(f, rsa_key, NULL, NULL, 0, NULL, NULL);
 -      fclose(f);
 -      free(filename);
 -
 -      if(name)
 -              asprintf(&filename, "%s/hosts/%s", confbase, name);
 -      else
 -              asprintf(&filename, "%s/rsa_key.pub", confbase);
 -
 -      f = ask_and_open(filename, _("public RSA key"));
 -
 -      if(!f)
 -              return false;
 -
 -      if(disable_old_keys(f))
 -              fprintf(stderr, _("Warning: old key(s) found and disabled.\n"));
 -
 -      PEM_write_RSAPublicKey(f, rsa_key);
 -      fclose(f);
 -      free(filename);
 -      if(name)
 -              free(name);
 -
 -      return true;
 -}
 -
  /*
    Set all files and paths according to netname
  */
@@@ -186,7 -385,7 +214,7 @@@ static void make_names(void
  #ifdef HAVE_MINGW
        HKEY key;
        char installdir[1024] = "";
 -      long len = sizeof(installdir);
 +      long len = sizeof installdir;
  #endif
  
        if(netname)
        }
  #endif
  
 -      if(!pidfilename)
 -              asprintf(&pidfilename, LOCALSTATEDIR "/run/%s.pid", identname);
 +      if(!controlsocketname)
 +              asprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
  
        if(!logfilename)
                asprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname);
  static void free_names() {
        if (identname) free(identname);
        if (netname) free(netname);
 -      if (pidfilename) free(pidfilename);
 +      if (controlsocketname) free(controlsocketname);
        if (logfilename) free(logfilename);
        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];
                return 0;
        }
  
 -      if(kill_tincd)
 -              return !kill_other(kill_tincd);
 -
        openlogger("tinc", use_logfile?LOGMODE_FILE:LOGMODE_STDERR);
  
-       /* 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;
-       }
 +      if(!event_init()) {
 +              logger(LOG_ERR, _("Error initializing libevent!"));
 +              return 1;
 +      }
 +
 +      if(!init_control())
 +              return 1;
 +
        g_argv = argv;
  
        init_configuration(&config_tree);
  
        /* Slllluuuuuuurrrrp! */
  
 -      RAND_load_file("/dev/urandom", 1024);
 -
 -      ENGINE_load_builtin_engines();
 -      ENGINE_register_all_complete();
 -
 -      OpenSSL_add_all_algorithms();
 -
 -      if(generate_keys) {
 -              read_server_config();
 -              return !keygen(generate_keys);
 -      }
 +      srand(time(NULL));
 +      crypto_init();
  
        if(!read_server_config())
                return 1;
@@@ -326,11 -572,46 +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. */
@@@ -348,10 -629,17 +454,13 @@@ end
        logger(LOG_NOTICE, _("Terminating"));
  
  #ifndef HAVE_MINGW
 -      remove_pid(pidfilename);
 +      exit_control();
  #endif
  
 -      EVP_cleanup();
 -      ENGINE_cleanup();
 -      CRYPTO_cleanup_all_ex_data();
 -      ERR_remove_state(0);
 -      ERR_free_strings();
 +      crypto_exit();
  
+       exit_configuration(&config_tree);
+       free_names();
        return status;
  }