Merge branch 'libradsec' into libradsec-server-support
authorLinus Nordberg <linus@nordberg.se>
Wed, 15 May 2013 13:39:20 +0000 (15:39 +0200)
committerLinus Nordberg <linus@nordberg.se>
Wed, 15 May 2013 13:39:20 +0000 (15:39 +0200)
Conflicts:
lib/HACKING
lib/Makefile.am
lib/README
lib/compat.h
lib/conf.c
lib/configure.ac
lib/conn.c
lib/conn.h
lib/err.c
lib/err.h
lib/event.c
lib/event.h
lib/examples/Makefile.am
lib/examples/client-blocking.c
lib/examples/client.conf
lib/include/radsec/radsec-impl.h
lib/include/radsec/radsec.h
lib/include/radsec/request-impl.h
lib/include/radsec/request.h
lib/packet.c
lib/packet.h
lib/peer.c
lib/peer.h
lib/request.c
lib/send.c
lib/tcp.c
lib/tests/Makefile.am
lib/tls.c
lib/udp.c
lib/util.c

26 files changed:
1  2 
lib/HACKING
lib/Makefile.am
lib/README
lib/compat.h
lib/conf.c
lib/configure.ac
lib/conn.c
lib/debug.c
lib/debug.h
lib/event.c
lib/event.h
lib/examples/Makefile.am
lib/examples/client-blocking.c
lib/include/radsec/radsec-impl.h
lib/include/radsec/radsec.h
lib/include/radsec/request-impl.h
lib/include/radsec/request.h
lib/peer.c
lib/radsec.c
lib/request.c
lib/send.c
lib/tcp.c
lib/tests/Makefile.am
lib/tls.c
lib/udp.c
lib/udp.h

diff --combined lib/HACKING
@@@ -1,6 -1,6 +1,6 @@@
  HACKING file for libradsec (in Emacs -*- org -*- mode).
  
- Status as of libradsec-0.2.0.dev (2013-05-02).
 -Status as of libradsec-0.0.4.dev (2013-05-06).
++Status as of libradsec-0.2.0.dev (2013-05-06).
  
  * Build instructions
  sh autogen.sh
@@@ -9,6 -9,43 +9,6 @@@ mak
  
  examples/client -r examples/client.conf blocking-tls; echo $?
  
 -* Design of the API
 -- There are three usage modes:
 -
 -  - Application uses blocking send and receive calls (blocking
 -    mode). This is typically fine for a simple client.
 -
 -  - Application registers callbacks with libradsec and runs the
 -    libevent dispatch loop (a.k.a. user dispatch mode). This would
 -    probably how to implement a server or a proxy.
 -
 -  - Application runs its own event loop, using fd's for select and
 -    performs I/O using libradsec send/receive functions
 -    (a.k.a. on-your-own mode). Might be useful for an application
 -    which already has an event loop that wants to add RadSec
 -    functionality.
 -
 -- Apart from configuration and error handling, an application
 -  shouldn't need to handle TCP and UDP connections
 -  differently. Similarly, the use of TLS/DTLS or not shouldn't
 -  influence the libradsec calls made by the application.
 -
 -- Configuration is done either by using the API or by pointing at a
 -  configuration file which is parsed by libradsec.
 -
 -- Fully reentrant.
 -
 -- Application chooses allocation regime.
 -
 -Note that as of 0.0.2.dev libradsec suffers from way too much focus on
 -the behaviour of a blocking client and is totally useless as a server.
 -Not only does it lack most of the functions needed for writing a
 -server but it also contains at least one architectural mishap which
 -kills the server idea -- a connection timeout (TCP) or a retransmit
 -timeout (UDP) will result in the event loop being broken. The same
 -thing will happen if there's an error on a TCP connection, f.ex. a
 -failing certificate validation (TLS).
 -
  * Dependencies
  Details (within parentheses) apply to Debian Wheezy.
  
@@@ -19,7 -56,7 +19,7 @@@
  - OpenSSL (1.0.1c-4) -- optional, for TLS and DTLS support
    sudo apt-get install libssl-dev libssl1.0.0
    
--* Functionality and quality in 0.0.x
++* Functionality and quality in 0.2.x
  ** Not well tested
  - reading config file
  - [TCP] short read
@@@ -29,8 -66,6 +29,8 @@@
  - [TLS] verification of CN
  
  ** Known issues
 +- error handling when server can't bind to given listen_addr+port
 +- configuration of listen_addr/service is per realm
  - error stack is only one entry deep
  - custom allocation scheme is not used in all places
  
@@@ -54,74 -89,3 +54,74 @@@ a crash, catching the crash in gdb and 
  valuable for debugging.
  
  Contact: mailto:linus+libradsec@nordu.net
 +
 +
 +* Design of the API
 +- There are three usage modes:
 +
 +  - Application uses blocking send and receive calls (blocking
 +    mode). This is typically fine for a simple client.
 +
 +  - Application registers callbacks with libradsec and runs the
 +    libevent dispatch loop (a.k.a. user dispatch mode). This would
 +    probably be how one would implement a server or a proxy.
 +
 +  - Application runs its own event loop, using fd's for select and
 +    performs I/O using libradsec send/receive functions
 +    (a.k.a. on-your-own mode). Might be useful for an application
 +    which already has an event loop that wants to add RadSec
 +    functionality.
 +
 +- Apart from configuration and error handling, an application
 +  shouldn't need to handle TCP and UDP connections
 +  differently. Similarly, the use of TLS/DTLS or not shouldn't
 +  influence the libradsec calls made by the application.
 +
 +- Configuration is done either by using the API or by pointing at a
 +  configuration file which is parsed by libradsec.
 +
 +- Fully reentrant.
 +
 +- Application chooses allocation regime.
 +
 +Note that as of 0.0.2.dev libradsec suffers from way too much focus on
 +the behaviour of a blocking client and is totally useless as a server.
 +Not only does it lack most of the functions needed for writing a
 +server but it also contains at least one architectural mishap which
 +kills the server idea -- a connection timeout (TCP) or a retransmit
 +timeout (UDP) will result in the event loop being broken. The same
 +thing will happen if there's an error on a TCP connection, f.ex. a
 +failing certificate validation (TLS).
 +
 +* Notes on internals
 +** How to connect an outgoing connection?
 +Connecting is not done explicitly by the application but implicitly by
 +rs_message_send(). The application can treat connections in the same
 +way regardless of whether they're connection-oriented (i.e. TCP) or
 +not (UDP).
 +
 +rs_message_send(msg)
 +  if msg->conn is open: mesasge_do_send(msg)
 +  else: 
 +    -> _conn_open(conn, msg)
 +      pick configured peer
 +      event_init_socket(peer)
 +      if TCP or TLS:
 +        init tcp timers
 +        event_init_bufferevent(conn, peer)
 +      else:
 +        init udp timers
 +      if not connected and not connecting:
 +        event_do_connect(conn)    
 +
 +  if TCP:
 +    bufferevent_setcb()
 +    bufferevent_enable()
 +  else:
 +    event_assign(write_ev)         ; libevent func?
 +    event_add(write_ev)
 +  
 +  if not in user-dispatch-mode:
 +    event_base_dispatch()
 +** How to bind a listener and start listening for incoming connections?
 +
diff --combined lib/Makefile.am
@@@ -4,23 -4,21 +4,24 @@@ ACLOCAL_AMFLAGS = -I m
  # Shared library interface version, i.e. -version-info to Libtool,
  # expressed as three integers CURRENT:REVISION:AGE.
  #
- # CURRENT is the version number of the current interface.
- # Increment CURRENT when the library interface changes.
+ # CURRENT is the version number of the current interface. Increment
+ # CURRENT when the library interface has changed or has been extended.
  #
  # REVISION is the version number of the _implementation_ of the
- # CURRENT interface.
- # Set REVISION to 0 when CURRENT changes, else increment.
+ # CURRENT interface. Set REVISION to 0 when CURRENT changes, else
+ # increment.
  #
  # AGE is the number of interfaces this library implements, i.e. how
- # many versions before CURRENT that are supported.
- # Increment AGE when the library interface is _extended_.
- # Set AGE to 0 when the library interface is _changed_.
+ # many versions before CURRENT that are supported. Increment AGE when
+ # the library interface is _extended_. Set AGE to 0 when the library
+ # interface is _changed_.
  
 +VER_CUR = 1
 +VER_REV = 0
 +VER_AGE = 0
  
- SUBDIRS = radius radsecproxy . include examples
+ SUBDIRS = radius radsecproxy include . examples
+ DIST_SUBDIRS = $(SUBDIRS) tests
  
  INCLUDES = -I$(srcdir)/include
  AM_CFLAGS = -Wall -Werror -g
@@@ -31,13 -29,11 +32,13 @@@ libradsec_la_SOURCES = 
        avp.c \
        compat.c \
        conf.c \
 +      confutil.c \
        conn.c \
        debug.c \
        err.c \
        event.c \
 -      packet.c \
 +      listener.c \
 +      message.c \
        peer.c \
        radsec.c \
        request.c \
        util.c
  
  if RS_ENABLE_TLS
 -libradsec_la_SOURCES += tls.c
 +  libradsec_la_SOURCES += tls.c
  else
 -libradsec_la_SOURCES += md5.c
 +  libradsec_la_SOURCES += md5.c
  endif
  
- EXTRA_DIST = HACKING LICENSE
+ libradsec_la_SOURCES += \
+       compat.h \
+       conn.h \
+       debug.h \
+       err.h \
+       event.h \
+       md5.h \
 -      packet.h \
+       peer.h \
+       radsec.h \
+       tcp.h \
+       tls.h \
+       udp.h \
+       util.h
 -EXTRA_DIST = HACKING LICENSE libradsec.spec radsec.sym
 -AM_DISTCHECK_CONFIGURE_FLAGS = --enable-tls --enable-tls-psk
++EXTRA_DIST = HACKING LICENSE radsec.sym
 +EXTRA_libradsec_la_DEPENDENCIES = radsec.sym
  
 -libradsec_la_LIBADD = radsecproxy/libradsec-radsecproxy.la radius/libradsec-radius.la
 -libradsec_la_LDFLAGS = -version-info 0:0:0 -export-symbols $(srcdir)/radsec.sym
 -libradsec_la_CFLAGS = $(AM_CFLAGS) -DHAVE_CONFIG_H -Werror # -DDEBUG -DDEBUG_LEVENT 
 +libradsec_la_CFLAGS = \
-       $(AM_CFLAGS) -DHAVE_CONFIG_H -DDEBUG -DDEBUG_LEVENT
++      $(AM_CFLAGS) -DHAVE_CONFIG_H #-DDEBUG -DDEBUG_LEVENT
 +libradsec_la_LDFLAGS = \
 +      -version-info $(VER_CUR):$(VER_REV):$(VER_AGE) \
-       -export-symbols radsec.sym
++      -export-symbols $(srcdir)/radsec.sym
 +libradsec_la_LIBADD = \
 +      radsecproxy/libradsec-radsecproxy.la \
 +      radius/libradsec-radius.la
diff --combined lib/README
@@@ -1,39 -1,42 +1,54 @@@
  Libradsec is a RADIUS library for clients doing RADIUS over UDP or
 -TLS. The goal is to add support for writing servers (and thus proxies)
 -and to add transports TCP and DTLS.
 +TLS. The goal is to eventually add transports TCP and DTLS.
 +
 +
 + * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE *
 +
 +This branch (libradsec-server-support) is extremely unstable and will
 +see changes its to public API:s for sure. It _will_ be rebased without
 +any warning what so ever. Yuo probably don't want to follow this
 +branch.
 +
 + * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE * NOTE *
  
  
  The canonical pickup point is
  http://git.nordu.net/?p=radsecproxy.git;a=shortlog;h=refs/heads/libradsec
  
  
- The source code is licensed under a 3-clause BSD license. See LICENSE.
+ The source code is licensed under a 3-clause BSD license. See the
+ LICENSE file.
  
  
 -Libradsec depends on 
 +Libradsec depends on
  - libconfuse
  - libevent2
  - openssl (if configured with --enable-tls)
  
  
 +For changes between releases, see the CHANGES file.
 +
 +
  To compile the library and the examples, do something like
  
-  sh autogen.sh && ./configure && make
+   sh autogen.sh && ./configure && make
  
- If any of the libraries are not found, try setting environment
- variable LDFLAGS at configure time like so:
+ There are a couple of options that can be used when configuring. See
+   ./configure --help
+ for the full list. Worth mentioning here is --enable-tls and
+ --enable-tls-psk.
+ If the preprocessor has a hard time finding some of the header files
+ are, try setting environment variable CPPFLAGS at configure
+ time. Example:
+   CPPFLAGS="-I/usr/local/include" ./configure --enable-tls
+ If the link editor has trouble finding any of the libraries needed,
+ try setting environment variable LDFLAGS at configure time. Example:
  
    LDFLAGS="-L/usr/local/lib" ./configure --enable-tls
  
diff --combined lib/compat.h
@@@ -1,11 -1,5 +1,11 @@@
- /* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+ /* Copyright 2011 NORDUnet A/S. All rights reserved.
+    See LICENSE for licensing information. */
  
 +#ifdef _WIN32
 +#define INLINE __inline
 +#else
 +#define INLINE inline
 +#endif
 +
  ssize_t compat_send (int sockfd, const void *buf, size_t len, int flags);
  ssize_t compat_recv (int sockfd, void *buf, size_t len, int flags);
diff --combined lib/conf.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright 2010, 2011, 2013 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include "util.h"
  #include "debug.h"
  
 -#if 0
 -  # common config options
 -
 -  # common realm config options
 +#if 0 /* Configuration file syntax. */
 +  # realm specific configuration
    realm STRING {
        type = "UDP"|"TCP"|"TLS"|"DTLS"
        timeout = INT
        retries = INT
 +  }
 +
 +  # realm configuration inherited (and overwritable) by clients and servers
 +  realm STRING {
        cacertfile = STRING
        #cacertpath = STRING
        certfile = STRING
        certkeyfile = STRING
 -      pskstr = STRING # Transport pre-shared key, UTF-8 form.
 +      pskstr = STRING  # Transport pre-shared key, UTF-8 form.
        pskhexstr = STRING # Transport pre-shared key, ASCII hex form.
        pskid = STRING
        pskex = "PSK"|"DHE_PSK"|"RSA_PSK"
    }
  
 -  # client specific realm config options
 +  # client configuration
    realm STRING {
        server {
            hostname = STRING
 -        service = STRING
 +        service = STRING      # name or port number
            secret = STRING       # RADIUS secret
        }
    }
 +
 +  # server configuration
 +  realm STRING {
 +      listen_addr = STRING
 +      listen_service = STRING
 +      client {
 +          hostname = STRING
 +        service = STRING      # name or port number
 +          secret = STRING       # RADIUS secret
 +      }
 +  }
 +#endif
 +
 +struct confcommon {
 +  struct rs_credentials *transport_cred;
 +  char *cacertfile;
 +  char *cacertpath;
 +  char *certfile;
 +  char *certkeyfile;
 +  char *pskstr;
 +  char *pskhexstr;
 +};
 +
 +#define CONFGET_STR(dst,cfg,key,def) do {  \
 +        (dst) = cfg_getstr ((cfg), (key)); \
 +        if ((dst) == NULL) (dst) = (def);  \
 +      } while (0)
 +#define CONFGET_INT(dst,cfg,key,def) do {  \
 +        (dst) = cfg_getint ((cfg), (key)); \
 +        if ((dst) == -1) (dst) = (def);    \
 +      } while (0)
 +
 +static int
 +confload_peers (struct rs_context *ctx,
 +                /*const*/ cfg_t *cfg_realm,
 +                enum rs_peer_type type,
 +                struct rs_realm *r)
 +{
 +  const char *peer_type_str[] = {"<no type>", "client", "server"};
 +  cfg_t *cfg_peer = NULL;
 +  int j;
 +  char *def_listen_addr = cfg_getstr (cfg_realm, "listen_addr");
 +  char *def_listen_service = cfg_getstr (cfg_realm, "listen_service");
 +  char *def_cacertfile = cfg_getstr (cfg_realm, "cacertfile");
 +  /*char *def_cacertpath = cfg_getstr (cfg_realm, "cacertpath");*/
 +  char *def_certfile = cfg_getstr (cfg_realm, "certfile");
 +  char *def_certkeyfile = cfg_getstr (cfg_realm, "certkeyfile");
 +  char *def_pskstr = cfg_getstr (cfg_realm, "pskstr");
 +  char *def_pskhexstr = cfg_getstr (cfg_realm, "pskhexstr");
 +
 +  for (j = 0; j < cfg_size (cfg_realm, peer_type_str[type]); j++)
 +    {
 +      char *pskstr = NULL;
 +      char *pskhexstr = NULL;
 +      struct rs_peer *p = peer_create (ctx, &r->peers);
 +      if (p == NULL)
 +        return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 +                                   NULL);
 +      p->type = type;
 +      p->realm = r;
 +
 +      cfg_peer = cfg_getnsec (cfg_realm, peer_type_str[type], j);
 +      p->hostname = cfg_getstr (cfg_peer, "hostname");
 +      p->service = cfg_getstr (cfg_peer, "service");
 +      p->secret = cfg_getstr (cfg_peer, "secret");
 +
 +      if (type == RS_PEER_TYPE_CLIENT)
 +        {
 +          struct rs_peer *lp = peer_create (ctx, &r->local_addr); /* FIXME: this should be saved per peer, not realm */
 +          if (lp == NULL)
 +            return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 +                                       NULL);
 +          lp->realm = r;
 +          fprintf (stderr, " *** %s %s ***\n", def_listen_addr, def_listen_service);
 +#if 0
 +          CONFGET_STR (lp->hostname, cfg_peer, "listen_addr", def_listen_addr);
 +          CONFGET_STR (lp->service, cfg_peer, "listen_service",
 +                       def_listen_service);
 +#else
 +          lp->hostname = "127.0.0.1";
 +          lp->service = "4711";
  #endif
 +        }
 +
 +      CONFGET_STR (p->cacertfile, cfg_peer, "cacertfile", def_cacertfile);
 +      CONFGET_STR (p->certfile, cfg_peer, "certfile", def_certfile);
 +      CONFGET_STR (p->certkeyfile, cfg_peer, "certkeyfile", def_certkeyfile);
 +      CONFGET_STR (pskstr, cfg_peer, "pskstr", def_pskstr);
 +      CONFGET_STR (pskhexstr, cfg_peer, "pskhexstr", def_pskhexstr);
 +
 +      if (pskstr || pskhexstr)
 +        {
 +#if defined RS_ENABLE_TLS_PSK
 +          char *def_pskex = cfg_getstr (cfg_realm, "pskex");
 +          char *tmp_pskex = NULL;
 +          rs_cred_type_t type = RS_CRED_NONE;
 +          struct rs_credentials *cred = NULL;
 +
 +          CONFGET_STR (tmp_pskex, cfg_peer, "pskex", def_pskex);
 +          if (!strcmp (tmp_pskex, "PSK"))
 +            type = RS_CRED_TLS_PSK;
 +          else
 +            {
 +              /* TODO: push a warning on the error stack:*/
 +              /*rs_err_ctx_push (ctx, RSE_WARN, "%s: unsupported PSK key exchange"
 +                               " algorithm -- PSK not used", kex);*/
 +            }
 +
 +          if (type != RS_CRED_NONE)
 +            {
 +              char *def_pskid = cfg_getstr (cfg_realm, "pskid");
 +              cred = rs_calloc (ctx, 1, sizeof (*cred));
 +              if (cred == NULL)
 +                return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 +                                           NULL);
 +              cred->type = type;
 +              CONFGET_STR (cred->identity, cfg_peer, "pskid", def_pskid);
 +              if (pskhexstr)
 +                {
 +                  cred->secret_encoding = RS_KEY_ENCODING_ASCII_HEX;
 +                  cred->secret = pskhexstr;
 +                  if (pskstr)
 +                    ;      /* TODO: warn that we're ignoring pskstr */
 +                }
 +              else
 +                {
 +                  cred->secret_encoding = RS_KEY_ENCODING_UTF8;
 +                  cred->secret = pskstr;
 +                }
 +
 +              p->transport_cred = cred;
 +            }
 +#else  /* !RS_ENABLE_TLS_PSK */
 +          /* TODO: push a warning on the error stack: */
 +          /* rs_err_ctx_push (ctx, RSE_WARN, "libradsec wasn't configured with "
 +             "support for TLS preshared keys, ignoring pskstr "
 +             "and pskhexstr");*/
 +#endif  /* RS_ENABLE_TLS_PSK */
 +        }
 +
 +#if defined (RS_ENABLE_TLS)
 +      /* For a TLS or DTLS client or server, validate that we have either of CA
 +         cert file/path or PSK.  */
 +      if ((r->type == RS_CONN_TYPE_TLS || r->type == RS_CONN_TYPE_DTLS)
 +          && (p->cacertfile == NULL && p->cacertpath == NULL)
 +          && p->transport_cred == NULL)
 +        return rs_err_ctx_push (ctx, RSE_CONFIG,
 +                                "%s: missing both CA file/path and PSK",
 +                                r->name);
 +#endif
 +    }
 +
 +  return RSE_OK;
 +}
  
  /* FIXME: Leaking memory in error cases.  */
  int
  rs_context_read_config(struct rs_context *ctx, const char *config_file)
  {
 -  cfg_t *cfg, *cfg_realm, *cfg_server;
 +  cfg_t *cfg, *cfg_realm;
    int err = 0;
 -  int i, j;
 +  int i;
    const char *s;
    struct rs_config *config = NULL;
  
 -  cfg_opt_t server_opts[] =
 +  cfg_opt_t peer_opts[] =
      {
        CFG_STR ("hostname", NULL, CFGF_NONE),
        CFG_STR ("service", "2083", CFGF_NONE),
 +      CFG_STR ("listen_addr", "127.0.0.1", CFGF_NONE), /* Clients only. */
 +      CFG_STR ("listen_service", "0", CFGF_NONE), /* Clients only. */
        CFG_STR ("secret", "radsec", CFGF_NONE),
 +      CFG_STR ("cacertfile", NULL, CFGF_NONE),
 +      /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/
 +      CFG_STR ("certfile", NULL, CFGF_NONE),
 +      CFG_STR ("certkeyfile", NULL, CFGF_NONE),
 +      CFG_STR ("pskstr", NULL, CFGF_NONE),
 +      CFG_STR ("pskhexstr", NULL, CFGF_NONE),
 +      CFG_STR ("pskid", NULL, CFGF_NONE),
 +      CFG_STR ("pskex", "PSK", CFGF_NONE),
 +
        CFG_END ()
      };
    cfg_opt_t realm_opts[] =
      {
        CFG_STR ("type", "UDP", CFGF_NONE),
 +      CFG_STR ("listen_addr", "127.0.0.1", CFGF_NONE),
 +      CFG_STR ("listen_service", "0", CFGF_NONE),
        CFG_INT ("timeout", 2, CFGF_NONE), /* FIXME: Remove?  */
        CFG_INT ("retries", 2, CFGF_NONE), /* FIXME: Remove?  */
        CFG_STR ("cacertfile", NULL, CFGF_NONE),
        CFG_STR ("pskhexstr", NULL, CFGF_NONE),
        CFG_STR ("pskid", NULL, CFGF_NONE),
        CFG_STR ("pskex", "PSK", CFGF_NONE),
 -      CFG_SEC ("server", server_opts, CFGF_MULTI),
 +      CFG_SEC ("server", peer_opts, CFGF_MULTI),
 +      CFG_SEC ("client", peer_opts, CFGF_MULTI),
        CFG_END ()
      };
    cfg_opt_t opts[] =
      {
        struct rs_realm *r = NULL;
        const char *typestr;
 -      char *pskstr = NULL, *pskhexstr = NULL;
 +      struct confcommon cc;
  
 +      memset (&cc, 0, sizeof(cc));
        r = rs_calloc (ctx, 1, sizeof(*r));
        if (r == NULL)
        return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
        r->type = RS_CONN_TYPE_UDP;
        else if (strcmp (typestr, "TCP") == 0)
        r->type = RS_CONN_TYPE_TCP;
 +#if defined (RS_ENABLE_TLS)
        else if (strcmp (typestr, "TLS") == 0)
        r->type = RS_CONN_TYPE_TLS;
        else if (strcmp (typestr, "DTLS") == 0)
        r->type = RS_CONN_TYPE_DTLS;
 +#endif
        else
        return rs_err_ctx_push (ctx, RSE_CONFIG,
                                  "%s: invalid connection type: %s",
                                  r->name, typestr);
 +
        r->timeout = cfg_getint (cfg_realm, "timeout");
        r->retries = cfg_getint (cfg_realm, "retries");
  
 -      r->cacertfile = cfg_getstr (cfg_realm, "cacertfile");
 -      /*r->cacertpath = cfg_getstr (cfg_realm, "cacertpath");*/
 -      r->certfile = cfg_getstr (cfg_realm, "certfile");
 -      r->certkeyfile = cfg_getstr (cfg_realm, "certkeyfile");
 -
 -      pskstr = cfg_getstr (cfg_realm, "pskstr");
 -      pskhexstr = cfg_getstr (cfg_realm, "pskhexstr");
 -      if (pskstr || pskhexstr)
 -        {
 -#if defined RS_ENABLE_TLS_PSK
 -          char *kex = cfg_getstr (cfg_realm, "pskex");
 -          rs_cred_type_t type = RS_CRED_NONE;
 -          struct rs_credentials *cred = NULL;
 -          assert (kex != NULL);
 -
 -          if (!strcmp (kex, "PSK"))
 -            type = RS_CRED_TLS_PSK;
 -          else
 -            {
 -              /* TODO: push a warning on the error stack:*/
 -              /*rs_err_ctx_push (ctx, RSE_WARN, "%s: unsupported PSK key exchange"
 -                               " algorithm -- PSK not used", kex);*/
 -            }
 -
 -          if (type != RS_CRED_NONE)
 -            {
 -              cred = rs_calloc (ctx, 1, sizeof (*cred));
 -              if (cred == NULL)
 -                return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 -                                           NULL);
 -              cred->type = type;
 -              cred->identity = cfg_getstr (cfg_realm, "pskid");
 -              if (pskhexstr)
 -                {
 -                  cred->secret_encoding = RS_KEY_ENCODING_ASCII_HEX;
 -                  cred->secret = pskhexstr;
 -                  if (pskstr)
 -                    ;      /* TODO: warn that we're ignoring pskstr */
 -                }
 -              else
 -                {
 -                  cred->secret_encoding = RS_KEY_ENCODING_UTF8;
 -                  cred->secret = pskstr;
 -                }
 -
 -              r->transport_cred = cred;
 -            }
 -#else  /* !RS_ENABLE_TLS_PSK */
 -          /* TODO: push a warning on the error stack: */
 -          /* rs_err_ctx_push (ctx, RSE_WARN, "libradsec wasn't configured with "
 -                           "support for TLS preshared keys, ignoring pskstr "
 -                           "and pskhexstr");*/
 -#endif  /* RS_ENABLE_TLS_PSK */
 -        }
 -
 -      /* For TLS and DTLS realms, validate that we either have (i) CA
 -         cert file or path or (ii) PSK.  */
 -      if ((r->type == RS_CONN_TYPE_TLS || r->type == RS_CONN_TYPE_DTLS)
 -          && (r->cacertfile == NULL && r->cacertpath == NULL)
 -          && r->transport_cred == NULL)
 -        return rs_err_ctx_push (ctx, RSE_CONFIG,
 -                                "%s: missing both CA file/path and PSK",
 -                                r->name);
 -
 -      /* Add peers, one per server stanza.  */
 -      for (j = 0; j < cfg_size (cfg_realm, "server"); j++)
 -      {
 -        struct rs_peer *p = peer_create (ctx, &r->peers);
 -        if (p == NULL)
 -          return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 -                                     NULL);
 -        p->realm = r;
 -
 -        cfg_server = cfg_getnsec (cfg_realm, "server", j);
 -        p->hostname = cfg_getstr (cfg_server, "hostname");
 -          p->service = cfg_getstr (cfg_server, "service");
 -        p->secret = cfg_getstr (cfg_server, "secret");
 -      }
 +      /* Add client and server peers. */
 +      err = confload_peers (ctx, cfg_realm, RS_PEER_TYPE_CLIENT, r);
 +      if (err)
 +        return err;
 +      err = confload_peers (ctx, cfg_realm, RS_PEER_TYPE_SERVER, r);
 +      if (err)
 +        return err;
      }
  
    /* Save config object in context, for freeing in rs_context_destroy().  */
diff --combined lib/configure.ac
@@@ -1,7 -1,7 +1,7 @@@
  # -*- Autoconf -*- script for libradsec.
  
- AC_PREREQ([2.65])
+ AC_PREREQ([2.63])
 -AC_INIT([libradsec], [0.0.4.dev], [linus+libradsec@nordu.net])
 +AC_INIT([libradsec], [0.2.0.dev], [linus+libradsec@nordu.net])
  AC_CONFIG_MACRO_DIR([m4])
  AC_CONFIG_SRCDIR([radsec.c])
  AC_CONFIG_AUX_DIR([build-aux])
@@@ -53,10 -53,10 +53,10 @@@ AC_TYPE_UINT8_
  # Checks for library functions.
  AC_CHECK_FUNCS([memset socket strdup strerror strrchr])
  
- AC_CONFIG_FILES([Makefile
+ AC_CONFIG_FILES([Makefile libradsec.spec
                   radsecproxy/Makefile
-                radius/Makefile
-                include/Makefile
+                  radius/Makefile
+                  include/Makefile
                   examples/Makefile
                   tests/Makefile])
  AC_OUTPUT
diff --combined lib/conn.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <event2/bufferevent.h>
  #include <radsec/radsec.h>
  #include <radsec/radsec-impl.h>
 +#include "err.h"
  #include "debug.h"
  #include "conn.h"
  #include "event.h"
 -#include "packet.h"
 +#include "message.h"
  #include "tcp.h"
  
  int
@@@ -31,209 -30,89 +31,211 @@@ conn_user_dispatch_p (const struct rs_c
          conn->callbacks.sent_cb);
  }
  
 -
  int
  conn_activate_timeout (struct rs_connection *conn)
  {
++  const struct rs_conn_base *connbase;
    assert (conn);
++  connbase = TO_BASE_CONN (conn);
++  assert (connbase->ctx);
++  assert (connbase->ctx->evb);
    assert (conn->tev);
-   assert (conn->base_.ctx->evb);
-   if (conn->base_.timeout.tv_sec || conn->base_.timeout.tv_usec)
 -  assert (conn->evb);
 -  if (conn->timeout.tv_sec || conn->timeout.tv_usec)
++  if (connbase->timeout.tv_sec || connbase->timeout.tv_usec)
      {
        rs_debug (("%s: activating timer: %d.%d\n", __func__,
-                conn->base_.timeout.tv_sec, conn->base_.timeout.tv_usec));
-       if (evtimer_add (conn->tev, &conn->base_.timeout))
 -               conn->timeout.tv_sec, conn->timeout.tv_usec));
 -      if (evtimer_add (conn->tev, &conn->timeout))
--      return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
--                                  "evtimer_add: %d", errno);
++               connbase->timeout.tv_sec, connbase->timeout.tv_usec));
++      if (evtimer_add (conn->tev, &connbase->timeout))
++      return rs_err_conn_push (conn, RSE_EVENT, "evtimer_add: %d", errno);
      }
    return RSE_OK;
  }
  
  int
 -conn_type_tls (const struct rs_connection *conn)
 +conn_type_tls_p (const struct rs_connection *conn)
  {
 -  return conn->realm->type == RS_CONN_TYPE_TLS
 -    || conn->realm->type == RS_CONN_TYPE_DTLS;
 +  return TO_BASE_CONN(conn)->transport == RS_CONN_TYPE_TLS
 +    || TO_BASE_CONN(conn)->transport == RS_CONN_TYPE_DTLS;
  }
  
  int
 -conn_cred_psk (const struct rs_connection *conn)
 +baseconn_type_datagram_p (const struct rs_conn_base *connbase)
  {
 -  return conn->realm->transport_cred &&
 -    conn->realm->transport_cred->type == RS_CRED_TLS_PSK;
 +  return connbase->transport == RS_CONN_TYPE_UDP
 +    || connbase->transport == RS_CONN_TYPE_DTLS;
  }
  
 +int
 +baseconn_type_stream_p (const struct rs_conn_base *connbase)
 +{
 +  return connbase->transport == RS_CONN_TYPE_TCP
 +    || connbase->transport == RS_CONN_TYPE_TLS;
 +}
  
 -/* Public functions. */
  int
 -rs_conn_create (struct rs_context *ctx,
 -              struct rs_connection **conn,
 -              const char *config)
 +conn_cred_psk (const struct rs_connection *conn)
  {
 -  struct rs_connection *c;
 +  assert (conn);
-   assert (conn->active_peer);
-   return conn->active_peer->transport_cred &&
-     conn->active_peer->transport_cred->type == RS_CRED_TLS_PSK;
++  return conn->active_peer != NULL
++    && conn->active_peer->transport_cred
++    && conn->active_peer->transport_cred->type == RS_CRED_TLS_PSK;
 +}
  
 -  c = (struct rs_connection *) malloc (sizeof(struct rs_connection));
 -  if (!c)
 -    return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
 +void
 +conn_init (struct rs_context *ctx, /* FIXME: rename connbase_init? */
 +           struct rs_conn_base *connbase,
 +           enum rs_conn_subtype type)
 +{
 +  switch (type)
 +    {
 +    case RS_CONN_OBJTYPE_BASE:
 +      connbase->magic = RS_CONN_MAGIC_BASE;
 +      break;
 +    case RS_CONN_OBJTYPE_GENERIC:
 +      connbase->magic = RS_CONN_MAGIC_GENERIC;
 +      break;
 +    case RS_CONN_OBJTYPE_LISTENER:
 +      connbase->magic = RS_CONN_MAGIC_LISTENER;
 +      break;
 +    default:
 +      assert ("invalid connection subtype" == NULL);
 +    }
  
 -  memset (c, 0, sizeof(struct rs_connection));
 -  c->ctx = ctx;
 -  c->fd = -1;
 +  connbase->ctx = ctx;
 +  connbase->fd = -1;
 +}
 +
 +int
 +conn_configure (struct rs_context *ctx, /* FIXME: rename conbbase_configure? */
 +                struct rs_conn_base *connbase,
 +                const char *config)
 +{
    if (config)
      {
        struct rs_realm *r = rs_conf_find_realm (ctx, config);
        if (r)
        {
 -        struct rs_peer *p;
 -
 -        c->realm = r;
 -        c->peers = r->peers;  /* FIXME: Copy instead?  */
 -        for (p = c->peers; p; p = p->next)
 -          p->conn = c;
 -        c->timeout.tv_sec = r->timeout;
 -        c->tryagain = r->retries;
 -      }
 -      else
 -      {
 -        c->realm = rs_malloc (ctx, sizeof (struct rs_realm));
 -        if (!c->realm)
 -          return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
 -                                     NULL);
 -        memset (c->realm, 0, sizeof (struct rs_realm));
 +        connbase->realm = r;
 +        connbase->peers = r->peers; /* FIXME: Copy instead?  */
 +#if 0
 +        for (p = connbase->peers; p != NULL; p = p->next)
 +          p->connbase = connbase;
 +#endif
 +        connbase->timeout.tv_sec = r->timeout;
 +        connbase->tryagain = r->retries;
        }
      }
 +#if 0  /* incoming connections don't have a realm (a config object), update: they do, but "somebody else" is setting this up <-- FIXME */
 +  if (connbase->realm == NULL)
 +    {
 +      struct rs_realm *r = rs_calloc (ctx, 1, sizeof (struct rs_realm));
 +      if (r == NULL)
 +        return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
 +      r->next = ctx->realms;
 +      ctx->realms = connbase->realm = r;
 +    }
 +#else
 +  if (connbase->realm)
 +    connbase->transport = connbase->realm->type;
 +#endif
 +
 +  return RSE_OK;
 +}
 +
 +int
 +conn_add_read_event (struct rs_connection *conn, void *user_data)
 +{
 +  struct rs_conn_base *connbase = TO_BASE_CONN(conn);
 +  int err;
 +
 +  assert(connbase);
 +
 +  if (connbase->bev)          /* TCP (including TLS).  */
 +    {
 +      bufferevent_setwatermark (connbase->bev, EV_READ, RS_HEADER_LEN, 0);
 +      bufferevent_setcb (connbase->bev, tcp_read_cb, NULL, tcp_event_cb,
 +                         user_data);
 +      bufferevent_enable (connbase->bev, EV_READ);
 +    }
 +  else                                /* UDP.  */
 +    {
 +      /* Put fresh message in user_data for the callback and enable the
 +       read event.  */
 +      event_assign (connbase->rev, connbase->ctx->evb,
 +                    event_get_fd (connbase->rev), EV_READ,
 +                    event_get_callback (connbase->rev),
 +                    user_data);
 +      err = event_add (connbase->rev, NULL);
 +      if (err < 0)
 +      return rs_err_connbase_push_fl (connbase, RSE_EVENT, __FILE__, __LINE__,
 +                                        "event_add: %s",
 +                                        evutil_gai_strerror (err));
 +
 +      /* Activate retransmission timer.  */
 +      conn_activate_timeout (conn);
 +    }
 +
 +  return RSE_OK;
 +}
 +
 +/** Return !=0 if \a conn is an originating connection, i.e. if its
 +    peer is a server. */
 +int
 +conn_originating_p (const struct rs_connection *conn)
 +{
 +  return conn->active_peer->type == RS_PEER_TYPE_SERVER;
 +}
 +
 +int
 +baseconn_close (struct rs_conn_base *connbase)
 +{
 +  int err = 0;
 +  assert (connbase);
 +
 +  rs_debug (("%s: closing fd %d\n", __func__, connbase->fd));
 +
 +  err = evutil_closesocket (connbase->fd);
 +  if (err)
 +    err = rs_err_connbase_push (connbase, RSE_EVENT,
 +                                "evutil_closesocket: %d (%s)",
 +                                errno, strerror (errno));
 +  connbase->fd = -1;
 +  return err;
 +}
 +
 +/* Public functions. */
 +int
 +rs_conn_create (struct rs_context *ctx,
 +              struct rs_connection **conn,
 +              const char *config)
 +{
 +  int err = RSE_OK;
 +  struct rs_connection *c = NULL;
 +  assert (ctx);
 +
 +  c = rs_calloc (ctx, 1, sizeof (struct rs_connection));
 +  if (c == NULL)
 +    return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
 +  conn_init (ctx, &c->base_, RS_CONN_OBJTYPE_GENERIC);
 +  err = conn_configure (ctx, &c->base_, config);
 +  if (err)
 +    goto errout;
  
    if (conn)
      *conn = c;
    return RSE_OK;
 +
 + errout:
 +  if (c)
 +    rs_free (ctx, c);
 +  return err;
  }
  
  void
  rs_conn_set_type (struct rs_connection *conn, rs_conn_type_t type)
  {
    assert (conn);
 -  assert (conn->realm);
 -  conn->realm->type = type;
 +  assert (conn->base_.realm);
 +  conn->base_.realm->type = type;
  }
  
  int
@@@ -249,8 -128,31 +251,31 @@@ rs_conn_add_listener (struct rs_connect
  int
  rs_conn_disconnect (struct rs_connection *conn)
  {
-   int err = baseconn_close (TO_BASE_CONN (conn));
-   conn->state = RS_CONN_STATE_UNDEFINED;
+   int err = 0;
+   assert (conn);
 -  if (conn->is_connected)
++  if (conn->state == RS_CONN_STATE_CONNECTED)
+     event_on_disconnect (conn);
 -  if (conn->bev)
++  if (TO_BASE_CONN (conn)->bev)
+     {
 -      bufferevent_free (conn->bev);
 -      conn->bev = NULL;
++      bufferevent_free (TO_BASE_CONN (conn)->bev);
++      TO_BASE_CONN (conn)->bev = NULL;
+     }
 -  if (conn->rev)
++  if (TO_BASE_CONN (conn)->rev)
+     {
 -      event_free (conn->rev);
 -      conn->rev = NULL;
++      event_free (TO_BASE_CONN (conn)->rev);
++      TO_BASE_CONN (conn)->rev = NULL;
+     }
 -  if (conn->wev)
++  if (TO_BASE_CONN (conn)->wev)
+     {
 -      event_free (conn->wev);
 -      conn->wev = NULL;
++      event_free (TO_BASE_CONN (conn)->wev);
++      TO_BASE_CONN (conn)->wev = NULL;
+     }
 -  err = evutil_closesocket (conn->fd);
 -  conn->fd = -1;
++  err = evutil_closesocket (TO_BASE_CONN (conn)->fd);
++  TO_BASE_CONN (conn)->fd = -1;
    return err;
  }
  
@@@ -264,7 -166,7 +289,7 @@@ rs_conn_destroy (struct rs_connection *
    /* NOTE: conn->realm is owned by context.  */
    /* NOTE: conn->peers is owned by context.  */
  
 -  if (conn->is_connected)
 +  if (conn->state == RS_CONN_STATE_CONNECTED)
      err = rs_conn_disconnect (conn);
  
  #if defined (RS_ENABLE_TLS)
  
    if (conn->tev)
      event_free (conn->tev);
 -  if (conn->bev)
 -    bufferevent_free (conn->bev);
 -  if (conn->rev)
 -    event_free (conn->rev);
 -  if (conn->wev)
 -    event_free (conn->wev);
 -  if (conn->evb)
 -    event_base_free (conn->evb);
 +  if (conn->base_.bev)
 +    bufferevent_free (conn->base_.bev);
 +  if (conn->base_.rev)
 +    event_free (conn->base_.rev);
 +  if (conn->base_.wev)
 +    event_free (conn->base_.wev);
  
 -  rs_free (conn->ctx, conn);
 +  rs_free (conn->base_.ctx, conn);
  
    return err;
  }
@@@ -295,12 -199,9 +320,12 @@@ rs_conn_set_eventbase (struct rs_connec
  }
  
  void
 -rs_conn_set_callbacks (struct rs_connection *conn, struct rs_conn_callbacks *cb)
 +rs_conn_set_callbacks (struct rs_connection *conn,
 +                       struct rs_conn_callbacks *cb,
 +                       void *user_data)
  {
    assert (conn);
 +  TO_BASE_CONN(conn)->user_data = user_data;
    memcpy (&conn->callbacks, cb, sizeof (conn->callbacks));
  }
  
@@@ -312,7 -213,7 +337,7 @@@ rs_conn_del_callbacks (struct rs_connec
  }
  
  struct rs_conn_callbacks *
 -rs_conn_get_callbacks(struct rs_connection *conn)
 +rs_conn_get_callbacks (struct rs_connection *conn)
  {
    assert (conn);
    return &conn->callbacks;
@@@ -332,92 -233,93 +357,92 @@@ rs_conn_get_current_peer (struct rs_con
    return rs_err_conn_push_fl (conn, RSE_NOSYS, __FILE__, __LINE__, NULL);
  }
  
 -int rs_conn_fd (struct rs_connection *conn)
 +int
 +rs_conn_dispatch (struct rs_connection *conn)
 +{
 +  assert (conn);
 +  return event_base_loop (conn->base_.ctx->evb, EVLOOP_ONCE);
 +}
 +
 +#if 0
 +struct event_base
 +*rs_conn_get_evb (const struct rs_connection *conn)
  {
    assert (conn);
 -  assert (conn->active_peer);
 -  return conn->fd;
 +  return conn->evb;
 +}
 +#endif
 +
 +int rs_conn_get_fd (struct rs_connection *conn)
 +{
 +  assert (conn);
 +  return conn->base_.fd;
  }
  
  static void
 -_rcb (struct rs_packet *packet, void *user_data)
 +_rcb (struct rs_message *message, void *user_data)
  {
 -  struct rs_packet *pkt = (struct rs_packet *) user_data;
 -  assert (pkt);
 -  assert (pkt->conn);
 -
 -  pkt->flags |= RS_PACKET_RECEIVED;
 -  if (pkt->conn->bev)
 -    bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ);
 -  else
 -    event_del (pkt->conn->rev);
 +  struct rs_message *msg = (struct rs_message *) user_data;
 +  assert (msg);
 +  assert (msg->conn);
 +
 +  msg->flags |= RS_MESSAGE_RECEIVED;
 +  if (msg->conn->base_.bev)     /* TCP -- disable bufferevent. */
 +    bufferevent_disable (msg->conn->base_.bev, EV_WRITE|EV_READ);
 +  else                          /* UDP -- remove read event. */
 +    event_del (msg->conn->base_.rev);
  }
  
  int
 -rs_conn_receive_packet (struct rs_connection *conn,
 -                      struct rs_packet *req_msg,
 -                      struct rs_packet **pkt_out)
 +rs_conn_receive_message (struct rs_connection *conn,
 +                         struct rs_message *req_msg,
 +                         struct rs_message **msg_out)
  {
    int err = 0;
 -  struct rs_packet *pkt = NULL;
 +  struct rs_message *msg = NULL;
  
    assert (conn);
 -  assert (conn->realm);
 +  assert (conn->base_.realm);
    assert (!conn_user_dispatch_p (conn)); /* Blocking mode only.  */
  
 -  if (rs_packet_create (conn, &pkt))
 +  if (rs_message_create (conn, &msg))
      return -1;
  
 -  assert (conn->evb);
 -  assert (conn->fd >= 0);
 +  assert (conn->base_.ctx->evb);
 +  assert (conn->base_.fd >= 0);
  
    conn->callbacks.received_cb = _rcb;
 -  conn->user_data = pkt;
 -  pkt->flags &= ~RS_PACKET_RECEIVED;
 +  conn->base_.user_data = msg;
 +  msg->flags &= ~RS_MESSAGE_RECEIVED;
  
 -  if (conn->bev)              /* TCP.  */
 -    {
 -      bufferevent_setwatermark (conn->bev, EV_READ, RS_HEADER_LEN, 0);
 -      bufferevent_setcb (conn->bev, tcp_read_cb, NULL, tcp_event_cb, pkt);
 -      bufferevent_enable (conn->bev, EV_READ);
 -    }
 -  else                                /* UDP.  */
 -    {
 -      /* Put fresh packet in user_data for the callback and enable the
 -       read event.  */
 -      event_assign (conn->rev, conn->evb, event_get_fd (conn->rev),
 -                  EV_READ, event_get_callback (conn->rev), pkt);
 -      err = event_add (conn->rev, NULL);
 -      if (err < 0)
 -      return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__,
 -                                  "event_add: %s",
 -                                  evutil_gai_strerror (err));
 -
 -      /* Activate retransmission timer.  */
 -      conn_activate_timeout (pkt->conn);
 -    }
 +  err = conn_add_read_event (conn, msg);
 +  if (err)
 +    return err;
  
    rs_debug (("%s: entering event loop\n", __func__));
 -  err = event_base_dispatch (conn->evb);
 +  err = event_base_dispatch (conn->base_.ctx->evb);
    conn->callbacks.received_cb = NULL;
    if (err < 0)
 -    return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__,
 +    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                "event_base_dispatch: %s",
                                evutil_gai_strerror (err));
    rs_debug (("%s: event loop done\n", __func__));
  
 -  if ((pkt->flags & RS_PACKET_RECEIVED) == 0
 +  if ((msg->flags & RS_MESSAGE_RECEIVED) == 0 /* No message. */
        || (req_msg
 -        && packet_verify_response (pkt->conn, pkt, req_msg) != RSE_OK))
 +        && message_verify_response (conn, msg, req_msg) != RSE_OK))
      {
 -      if (rs_err_conn_peek_code (pkt->conn) == RSE_OK)
 -        /* No packet and no error on the stack _should_ mean that the
 -           server hung up on us.  */
 -        rs_err_conn_push (pkt->conn, RSE_DISCO, "no response");
 +      if (rs_err_conn_peek_code (conn) == RSE_OK)
 +        {
 +          /* No message and no error on the stack _should_ mean that the
 +             server hung up on us. */
 +          rs_err_conn_push (conn, RSE_DISCO, "no response");
 +        }
        return rs_err_conn_peek_code (conn);
      }
  
 -  if (pkt_out)
 -    *pkt_out = pkt;
 +  if (msg_out)
 +    *msg_out = msg;
    return RSE_OK;
  }
  
@@@ -426,12 -328,5 +451,12 @@@ rs_conn_set_timeout(struct rs_connectio
  {
    assert (conn);
    assert (tv);
 -  conn->timeout = *tv;
 +  conn->base_.timeout = *tv;
 +}
 +
 +struct rs_peer *
 +connbase_get_peers (const struct rs_conn_base *connbase)
 +{
 +  assert (connbase);
 +  return connbase->peers;
  }
diff --combined lib/debug.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright 2011 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
  #include "debug.h"
  
  void
 -rs_dump_packet (const struct rs_packet *pkt)
 +rs_dump_message (const struct rs_message *msg)
  {
    const RADIUS_PACKET *p = NULL;
  
 -  if (!pkt || !pkt->rpkt)
 +  if (!msg || !msg->rpkt)
      return;
 -  p = pkt->rpkt;
 +  p = msg->rpkt;
  
    fprintf (stderr, "\tCode: %u, Identifier: %u, Lenght: %zu\n",
           p->code,
diff --combined lib/debug.h
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright 2011 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+    See LICENSE for licensing information. */
  
  #define hd(p, l) { int i;             \
      for (i = 1; i <= l; i++) {                \
@@@ -12,8 -12,8 +12,8 @@@
  extern "C" {
  #endif
  
 -struct rs_packet;
 -void rs_dump_packet (const struct rs_packet *pkt);
 +struct rs_message;
 +void rs_dump_message (const struct rs_message *pkt);
  int _rs_debug (const char *fmt, ...);
  
  #if defined (DEBUG)
diff --combined lib/event.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
+ /* Copyright 2011-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
@@@ -10,7 -10,6 +10,7 @@@
  #include <errno.h>
  
  #include <event2/event.h>
 +#include <event2/listener.h>
  #include <event2/bufferevent.h>
  #if defined (RS_ENABLE_TLS)
  #include <event2/bufferevent_ssl.h>
@@@ -26,9 -25,8 +26,9 @@@
  #include "err.h"
  #include "radsec.h"
  #include "event.h"
 -#include "packet.h"
 +#include "message.h"
  #include "conn.h"
 +#include "listener.h"
  #include "debug.h"
  
  #if defined (DEBUG)
@@@ -74,8 -72,8 +74,8 @@@ event_conn_timeout_cb (int fd, short ev
    if (event & EV_TIMEOUT)
      {
        rs_debug (("%s: connection timeout on %p (fd %d) connecting to %p\n",
 -               __func__, conn, conn->fd, conn->active_peer));
 -      conn->is_connecting = 0;
 +               __func__, conn, conn->base_.fd, conn->active_peer));
 +      conn->state = RS_CONN_STATE_UNDEFINED;
        rs_err_conn_push_fl (conn, RSE_TIMEOUT_CONN, __FILE__, __LINE__, NULL);
        event_loopbreak (conn);
      }
@@@ -92,118 -90,68 +92,118 @@@ event_retransmit_timeout_cb (int fd, sh
    if (event & EV_TIMEOUT)
      {
        rs_debug (("%s: retransmission timeout on %p (fd %d) sending to %p\n",
 -               __func__, conn, conn->fd, conn->active_peer));
 +               __func__, conn, conn->base_.fd, conn->active_peer));
        rs_err_conn_push_fl (conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL);
        event_loopbreak (conn);
      }
  }
  
 +/* FIXME: event_ is actually not such a great prefix given that we
 +   link with libevent which exports 113 symbols prefixed 'event_'. */
  int
 -event_init_socket (struct rs_connection *conn, struct rs_peer *p)
 +event_init_socket (struct rs_conn_base *connbase, struct rs_peer *p)
  {
 -  if (conn->fd != -1)
 +  if (connbase->fd != -1)
      return RSE_OK;
  
 +  assert (p);
 +  assert (p->realm);
 +
 +  /* Resolve potential DNS name for peer. */
    if (p->addr_cache == NULL)
      {
        struct rs_error *err =
          rs_resolve (&p->addr_cache, p->realm->type, p->hostname, p->service);
        if (err != NULL)
 -        return err_conn_push_err (conn, err);
 +        return err_connbase_push_err (connbase, err);
      }
  
 -  conn->fd = socket (p->addr_cache->ai_family, p->addr_cache->ai_socktype,
 -                   p->addr_cache->ai_protocol);
 -  if (conn->fd < 0)
 -    return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__,
 -                              "socket: %d (%s)",
 -                              errno, strerror (errno));
 -  if (evutil_make_socket_nonblocking (conn->fd) < 0)
 +  /* Create the socket and make it non-blocking. */
 +  connbase->fd = socket (p->addr_cache->ai_family,
 +                         p->addr_cache->ai_socktype,
 +                         p->addr_cache->ai_protocol);
 +  if (connbase->fd < 0)
 +    return rs_err_connbase_push_fl (connbase, RSE_SOCKERR, __FILE__, __LINE__,
 +                                    "socket: %d (%s)",
 +                                    errno, strerror (errno));
 +  if (evutil_make_socket_nonblocking (connbase->fd) < 0)
      {
 -      evutil_closesocket (conn->fd);
 -      conn->fd = -1;
 -      return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__,
 -                                "evutil_make_socket_nonblocking: %d (%s)",
 -                                errno, strerror (errno));
 +      evutil_closesocket (connbase->fd);
 +      connbase->fd = -1;
 +      return rs_err_connbase_push_fl (connbase, RSE_SOCKERR, __FILE__, __LINE__,
 +                                      "evutil_make_socket_nonblocking: %d (%s)",
 +                                      errno, strerror (errno));
 +    }
 +
 +  /* If we're inititalising the socket for a listener, bind to the
 +     peer address. */
 +  if (connbase->magic == RS_CONN_MAGIC_LISTENER)
 +    {
 +      assert (p->realm->type == connbase->transport);
 +      if (p->realm->type == RS_CONN_TYPE_TLS
 +          || p->realm->type == RS_CONN_TYPE_TCP)
 +        {
 +          struct rs_listener *listener = TO_LISTENER_CONN (connbase);
 +          listener->evlistener =
 +            evconnlistener_new_bind (listener->base_.ctx->evb,
 +                                     listener_accept_cb_,
 +                                     listener, LEV_OPT_REUSEABLE,
 +                                     LISTENER_BACKLOG,
 +                                     p->addr_cache->ai_addr,
 +                                     p->addr_cache->ai_addrlen);
 +          if (listener->evlistener == NULL)
 +            return rs_err_connbase_push (connbase, RSE_EVENT,
 +                                         "evconnlistener_new_bind: %d (%s)",
 +                                         errno, strerror (errno));
 +
 +          evconnlistener_set_error_cb (listener->evlistener, listener_err_cb_);
 +        }
 +      else
 +        {
 +          return rs_err_connbase_push_fl (connbase, RSE_NOSYS,
 +                                          __FILE__, __LINE__, NULL);
 +        }
      }
 +
    return RSE_OK;
  }
  
  int
 -event_init_bufferevent (struct rs_connection *conn, struct rs_peer *peer)
 +event_init_bufferevent (struct rs_connection *conn)
  {
 -  if (conn->bev)
 +  struct rs_conn_base *connbase = NULL;
 +  assert (conn);
 +  connbase = TO_BASE_CONN(conn);
 +
 +  if (connbase->bev)
      return RSE_OK;
  
 -  if (conn->realm->type == RS_CONN_TYPE_TCP)
 +  if (connbase->transport == RS_CONN_TYPE_TCP)
      {
 -      conn->bev = bufferevent_socket_new (conn->evb, conn->fd, 0);
 -      if (!conn->bev)
 +      connbase->bev = bufferevent_socket_new (connbase->ctx->evb,
 +                                              connbase->fd, 0);
 +      if (!connbase->bev)
        return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
 -                                  "bufferevent_socket_new");
 +                                    "bufferevent_socket_new");
      }
  #if defined (RS_ENABLE_TLS)
 -  else if (conn->realm->type == RS_CONN_TYPE_TLS)
 +  else if (connbase->transport == RS_CONN_TYPE_TLS)
      {
 +      enum bufferevent_ssl_state bev_ssl_state;
 +
        if (rs_tls_init (conn))
        return -1;
 -      /* Would be convenient to pass BEV_OPT_CLOSE_ON_FREE but things
 -       seem to break when be_openssl_ctrl() (in libevent) calls
 -       SSL_set_bio() after BIO_new_socket() with flag=1.  */
 -      conn->bev =
 -      bufferevent_openssl_socket_new (conn->evb, conn->fd, conn->tls_ssl,
 -                                      BUFFEREVENT_SSL_CONNECTING, 0);
 -      if (!conn->bev)
 +      bev_ssl_state = conn_originating_p (conn)
 +        ? BUFFEREVENT_SSL_CONNECTING : BUFFEREVENT_SSL_ACCEPTING;
 +
 +      /* It would be convenient to pass BEV_OPT_CLOSE_ON_FREE in last
 +       argument (options) but things seem to break when
 +       be_openssl_ctrl() (in libevent) calls SSL_set_bio() after
 +       BIO_new_socket() with flag=1. */
 +      connbase->bev =
 +      bufferevent_openssl_socket_new (connbase->ctx->evb, connbase->fd,
 +                                        conn->tls_ssl, bev_ssl_state, 0);
 +      if (!connbase->bev)
        return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                    "bufferevent_openssl_socket_new");
      }
      {
        return rs_err_conn_push_fl (conn, RSE_INTERNAL, __FILE__, __LINE__,
                                  "%s: unknown connection type: %d", __func__,
 -                                conn->realm->type);
 +                                connbase->transport);
      }
  
    return RSE_OK;
  void
  event_do_connect (struct rs_connection *conn)
  {
 -  struct rs_peer *p;
    int err, sockerr;
 +  struct sockaddr *peer_addr;
 +  size_t peer_addrlen;
  
    assert (conn);
    assert (conn->active_peer);
 -  p = conn->active_peer;
 +  assert (conn->active_peer->addr_cache);
 +  peer_addr = conn->active_peer->addr_cache->ai_addr;
 +  peer_addrlen = conn->active_peer->addr_cache->ai_addrlen;
 +
 +  /* We don't connect listeners. */
 +  assert (conn->base_.magic == RS_CONN_MAGIC_GENERIC);
  
  #if defined (DEBUG)
    {
      char host[80], serv[80];
  
 -    getnameinfo (p->addr_cache->ai_addr,
 -               p->addr_cache->ai_addrlen,
 -               host, sizeof(host), serv, sizeof(serv),
 +    getnameinfo (peer_addr, peer_addrlen,
 +               host, sizeof(host),
 +                 serv, sizeof(serv),
                 0 /* NI_NUMERICHOST|NI_NUMERICSERV*/);
      rs_debug (("%s: connecting to %s:%s\n", __func__, host, serv));
    }
  #endif
  
 -  if (p->conn->bev)           /* TCP */
 +  if (conn->base_.bev)                /* TCP */
      {
        conn_activate_timeout (conn); /* Connect timeout.  */
 -      err = bufferevent_socket_connect (p->conn->bev, p->addr_cache->ai_addr,
 -                                      p->addr_cache->ai_addrlen);
 +      err = bufferevent_socket_connect (conn->base_.bev,
 +                                        peer_addr, peer_addrlen);
        if (err < 0)
 -      rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__,
 -                           "bufferevent_socket_connect: %s",
 -                           evutil_gai_strerror (err));
 +      rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
 +                             "bufferevent_socket_connect: %s",
 +                             evutil_gai_strerror (err));
        else
 -      p->conn->is_connecting = 1;
 +      conn->state = RS_CONN_STATE_CONNECTING;
      }
    else                                /* UDP */
      {
 -      err = connect (p->conn->fd,
 -                     p->addr_cache->ai_addr,
 -                     p->addr_cache->ai_addrlen);
 +      err = connect (conn->base_.fd, peer_addr, peer_addrlen);
        if (err < 0)
        {
 -        sockerr = evutil_socket_geterror (p->conn->fd);
 -        rs_debug (("%s: %d: connect: %d (%s)\n", __func__, p->conn->fd,
 +        sockerr = evutil_socket_geterror (conn->base_.fd);
 +        rs_debug (("%s: %d: connect: %d (%s)\n", __func__,
 +                     conn->base_.fd,
                     sockerr, evutil_socket_error_to_string (sockerr)));
 -        rs_err_conn_push_fl (p->conn, RSE_SOCKERR, __FILE__, __LINE__,
 -                             "%d: connect: %d (%s)", p->conn->fd, sockerr,
 -                             evutil_socket_error_to_string (sockerr));
 +        rs_err_conn_push (conn, RSE_SOCKERR,
 +                            "%d: connect: %d (%s)", conn->base_.fd,
 +                            sockerr, evutil_socket_error_to_string (sockerr));
        }
 +      else
 +      conn->state = RS_CONN_STATE_CONNECTING;
      }
  }
  
  int
  event_loopbreak (struct rs_connection *conn)
  {
 -  int err = event_base_loopbreak (conn->evb);
 +  int err = event_base_loopbreak (TO_BASE_CONN(conn)->ctx->evb);
    if (err < 0)
      rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                         "event_base_loopbreak: %s",
 -                       evutil_gai_strerror (err));
 +                       evutil_gai_strerror (err)); /* FIXME: really gai_strerror? */
    return err;
  }
  
  void
  event_on_disconnect (struct rs_connection *conn)
  {
 -  conn->is_connecting = 0;
 -  conn->is_connected = 0;
 +  conn->state = RS_CONN_STATE_UNDEFINED;
    rs_debug (("%s: %p disconnected\n", __func__, conn->active_peer));
    if (conn->callbacks.disconnected_cb)
 -    conn->callbacks.disconnected_cb (conn->user_data);
 +    conn->callbacks.disconnected_cb (conn->base_.user_data);
  }
  
 -/** Internal connect event returning 0 on success or -1 on error.  */
 +/** Internal connect event for originating connections. Returns 0 on
 +    success and -1 on TLS certificate verification failure.  */
  int
 -event_on_connect (struct rs_connection *conn, struct rs_packet *pkt)
 +event_on_connect_orig (struct rs_connection *conn, struct rs_message *msg)
  {
 -  assert (!conn->is_connecting);
 +  assert (conn->state == RS_CONN_STATE_CONNECTING);
 +  assert (conn->active_peer);
  
  #if defined (RS_ENABLE_TLS)
 -  if (conn_type_tls(conn) && !conn_cred_psk(conn))
 +  if (conn_type_tls_p (conn) && !conn_cred_psk (conn))
      if (tls_verify_cert (conn) != RSE_OK)
        {
          rs_debug (("%s: server cert verification failed\n", __func__));
        }
  #endif        /* RS_ENABLE_TLS */
  
 -  conn->is_connected = 1;
 +  conn->state = RS_CONN_STATE_CONNECTED;
    rs_debug (("%s: %p connected\n", __func__, conn->active_peer));
  
    if (conn->callbacks.connected_cb)
 -    conn->callbacks.connected_cb (conn->user_data);
 +    conn->callbacks.connected_cb (conn->base_.user_data);
  
 -  if (pkt)
 -    packet_do_send (pkt);
 +  if (msg)
 +    message_do_send (msg);
  
    return 0;
  }
  
 +/** FIXME: DOC */
  int
 -event_init_eventbase (struct rs_connection *conn)
 +event_on_connect_term (struct rs_connection *conn, struct rs_message *msg)
  {
 -  assert (conn);
 -  if (conn->evb)
 +  /* TODO: verify client */
 +  conn->state = RS_CONN_STATE_CONNECTED;
 +  rs_debug (("%s: WARNING: not checking client cert!!!\n", __func__));
 +  if (conn->callbacks.connected_cb)
 +    conn->callbacks.connected_cb (conn->base_.user_data);
 +  return 0;
 +}
 +
 +int
 +event_init_eventbase (struct rs_conn_base *connbase)
 +{
 +  assert (connbase);
 +  assert (connbase->ctx);
 +  if (connbase->ctx->evb)
      return RSE_OK;
  
  #if defined (DEBUG)
      event_enable_debug_mode ();
  #endif
    event_set_log_callback (_evlog_cb);
 -  conn->evb = event_base_new ();
 -  if (!conn->evb)
 -    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
 -                              "event_base_new");
 +  connbase->ctx->evb = event_base_new ();
 +  if (!connbase->ctx->evb)
 +    return rs_err_connbase_push_fl (connbase, RSE_EVENT, __FILE__, __LINE__,
 +                                    "event_base_new");
  
    return RSE_OK;
  }
diff --combined lib/event.h
@@@ -1,13 -1,12 +1,13 @@@
- /* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
 -/* Copyright 2011-2012 NORDUnet A/S. All rights reserved.
++/* Copyright 2011-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  void event_on_disconnect (struct rs_connection *conn);
 -int event_on_connect (struct rs_connection *conn, struct rs_packet *pkt);
 +int event_on_connect_orig (struct rs_connection *conn, struct rs_message *msg);
 +int event_on_connect_term (struct rs_connection *conn, struct rs_message *msg);
  int event_loopbreak (struct rs_connection *conn);
 -int event_init_eventbase (struct rs_connection *conn);
 -int event_init_socket (struct rs_connection *conn, struct rs_peer *p);
 -int event_init_bufferevent (struct rs_connection *conn, struct rs_peer *peer);
 +int event_init_eventbase (struct rs_conn_base *connbase);
 +int event_init_socket (struct rs_conn_base *connbase, struct rs_peer *p);
 +int event_init_bufferevent (struct rs_connection *conn);
  void event_do_connect (struct rs_connection *conn);
  void event_conn_timeout_cb (int fd, short event, void *data);
  void event_retransmit_timeout_cb (int fd, short event, void *data);
diff --combined lib/examples/Makefile.am
@@@ -1,10 -1,8 +1,10 @@@
  AUTOMAKE_OPTIONS = foreign
- INCLUDES = -I$(top_srcdir)/include
- AM_CFLAGS = -Wall -Werror -g -DDEBUG -DDEBUG_LEVENT
+ INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)
 -AM_CFLAGS = -Wall -Werror -g
++AM_CFLAGS = -Wall -Werror -g #-DDEBUG -DDEBUG_LEVENT
  
 -noinst_PROGRAMS = client
 +LDADD = ../libradsec.la #-lefence
 +CFLAGS = $(AM_CFLAGS) -DUSE_CONFIG_FILE
 +
 +noinst_PROGRAMS = client client2 server
  client_SOURCES = client-blocking.c
 -client_LDADD = ../libradsec.la #-lefence
 -client_CFLAGS = $(AM_CFLAGS) -DUSE_CONFIG_FILE
 +client2_SOURCES = client-dispatch.c
@@@ -1,40 -1,27 +1,40 @@@
  /* RADIUS/RadSec client using libradsec in blocking mode. */
  
 +/* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
 +   See LICENSE for licensing information. */
 +
  #include <stdio.h>
  #include <stdlib.h>
 +#include <string.h>
  #include <assert.h>
  #include <radsec/radsec.h>
  #include <radsec/request.h>
  #include "err.h"
 -#include "debug.h"            /* For rs_dump_packet().  */
 +#include "debug.h"            /* For rs_dump_message().  */
  
  #define SECRET "sikrit"
  #define USER_NAME "molgan@PROJECT-MOONSHOT.ORG"
  #define USER_PW "password"
  
  struct rs_error *
 -blocking_client (const char *config_fn, const char *configuration,
 +blocking_client (const char *av1, const char *av2, const char *av3,
                   int use_request_object_flag)
  {
    struct rs_context *h = NULL;
    struct rs_connection *conn = NULL;
    struct rs_request *request = NULL;
 -  struct rs_packet *req = NULL, *resp = NULL;
 +  struct rs_message *req = NULL, *resp = NULL;
    struct rs_error *err = NULL;
    int r;
 +#if defined (USE_CONFIG_FILE)
 +  const char *config_fn= av1;
 +  const char *configuration = av2;
 +#else
 +  const char *host = av1;
 +  const char *service = av2;
 +  const char *proto = av3;
 +  struct rs_peer *server;
 +#endif
  
    r = rs_context_create (&h);
    if (r)
      }
  
  #if !defined (USE_CONFIG_FILE)
 +  /* Do it without a configuration file by setting all stuff "by
 +   hand".  Doesn't work for TLS at the moment because we don't have an
 +   API for setting the X509 cert file names and such. */
    {
 -    struct rs_peer *server;
 +    int conn_type = RS_CONN_TYPE_UDP;
  
      if (rs_conn_create (h, &conn, NULL))
        goto cleanup;
 -    rs_conn_set_type (conn, RS_CONN_TYPE_UDP);
 -    if (rs_peer_create (conn, &server))
 +    if (proto)
 +      {
 +        if (!strncmp (proto, "udp", strlen ("udp")))
 +          conn_type = RS_CONN_TYPE_UDP;
 +        else if (!strncmp (proto, "tls", strlen ("tls")))
 +          conn_type = RS_CONN_TYPE_TLS;
 +      }
 +    rs_conn_set_type (conn, conn_type);
 +    if (rs_peer_create_for_conn (conn, &server))
        goto cleanup;
 -    if (rs_peer_set_address (server, av1, av2))
 +    if (rs_peer_set_address (server, host, service))
        goto cleanup;
      rs_peer_set_timeout (server, 1);
      rs_peer_set_retries (server, 3);
  
    if (use_request_object_flag)
      {
-       if (rs_request_create_authn (conn, &request, USER_NAME, USER_PW, SECRET))
+       if (rs_request_create_authn (conn, &request, USER_NAME, USER_PW))
        goto cleanup;
        if (rs_request_send (request, &resp))
        goto cleanup;
      }
    else
      {
 -      if (rs_packet_create_authn_request (conn, &req, USER_NAME, USER_PW))
 +      if (rs_message_create_authn_request (conn, &req, USER_NAME, USER_PW))
        goto cleanup;
 -      if (rs_packet_send (req, NULL))
 +      if (rs_message_send (req))
        goto cleanup;
 -      if (rs_conn_receive_packet (conn, req, &resp))
 +      if (rs_conn_receive_message (conn, req, &resp))
        goto cleanup;
      }
  
    if (resp)
      {
 -      rs_dump_packet (resp);
 -      if (rs_packet_code (resp) == PW_ACCESS_ACCEPT)
 +      rs_dump_message (resp);
 +      if (rs_message_code (resp) == PW_ACCESS_ACCEPT)
        printf ("Good auth.\n");
        else
 -      printf ("Bad auth: %d\n", rs_packet_code (resp));
 +      printf ("Bad auth: %d\n", rs_message_code (resp));
      }
    else
      fprintf (stderr, "%s: no response\n", __func__);
    err = rs_err_ctx_pop (h);
    if (err == RSE_OK)
      err = rs_err_conn_pop (conn);
 +#if !defined (USE_CONFIG_FILE)
 +  rs_peer_free_address (server);
 +  rs_peer_free_secret (server);
 +#endif
    if (resp)
 -    rs_packet_destroy (resp);
 +    rs_message_destroy (resp);
    if (request)
      rs_request_destroy (request);
    if (conn)
@@@ -145,8 -118,7 +145,8 @@@ main (int argc, char *argv[]
      }
    if (argc < 3)
      usage (argc, argv);
 -  err = blocking_client (argv[1], argv[2], use_request_object_flag);
 +  err = blocking_client (argv[1], argv[2], argc >= 3 ? argv[3] : NULL,
 +                         use_request_object_flag);
    if (err)
      {
        fprintf (stderr, "error: %s: %d\n", rs_err_msg (err), rs_err_code (err, 0));
@@@ -1,32 -1,25 +1,32 @@@
  /** @file libradsec-impl.h
 -    @brief Libraray internal header file for libradsec.  */
 +    @brief Libraray internal header file for libradsec. */
  
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #ifndef _RADSEC_RADSEC_IMPL_H_
  #define _RADSEC_RADSEC_IMPL_H_ 1
  
 +#include <assert.h>
  #include <event2/util.h>
  #include <confuse.h>
  #if defined(RS_ENABLE_TLS)
  #include <openssl/ssl.h>
  #endif
 +#include "compat.h"
  
 -/* Constants.  */
 +/**************/
 +/* Constants. */
  #define RS_HEADER_LEN 4
 +#define RS_CONN_MAGIC_BASE 0xAE004711u
 +#define RS_CONN_MAGIC_GENERIC 0x843AEF47u
 +#define RS_CONN_MAGIC_LISTENER 0xDCB04783u
  
 -/* Data types.  */
 +/***************/
 +/* Data types. */
  enum rs_cred_type {
      RS_CRED_NONE = 0,
 -    /* TLS pre-shared keys, RFC 4279.  */
 +    /* TLS pre-shared keys, RFC 4279. */
      RS_CRED_TLS_PSK,
      /* RS_CRED_TLS_DH_PSK, */
      /* RS_CRED_TLS_RSA_PSK, */
@@@ -39,17 -32,6 +39,17 @@@ enum rs_key_encoding 
  };
  typedef unsigned int rs_key_encoding_t;
  
 +enum rs_peer_type {
 +    RS_PEER_TYPE_CLIENT = 1,
 +    RS_PEER_TYPE_SERVER = 2
 +};
 +
 +enum rs_conn_subtype {
 +    RS_CONN_OBJTYPE_BASE = 1,
 +    RS_CONN_OBJTYPE_GENERIC,
 +    RS_CONN_OBJTYPE_LISTENER,
 +};
 +
  #if defined (__cplusplus)
  extern "C" {
  #endif
@@@ -59,6 -41,7 +59,6 @@@ struct rs_credentials 
      char *identity;
      char *secret;
      enum rs_key_encoding secret_encoding;
 -    unsigned int secret_len;
  };
  
  struct rs_error {
      char buf[1024];
  };
  
 -/** Configuration object for a connection.  */
 +/** Configuration object for a connection. */
  struct rs_peer {
 -    struct rs_connection *conn;
 +    enum rs_peer_type type;
 +    struct rs_conn_base *connbase; /**< For error handling. */
      struct rs_realm *realm;
      char *hostname;
      char *service;
 -    char *secret;               /* RADIUS secret.  */
 +    char *secret;               /* RADIUS secret. */
      struct evutil_addrinfo *addr_cache;
 +    char *cacertfile;
 +    char *cacertpath;
 +    char *certfile;
 +    char *certkeyfile;
 +    struct rs_credentials *transport_cred;
      struct rs_peer *next;
  };
  
 -/** Configuration object for a RADIUS realm.  */
 +/** Configuration object for a RADIUS realm. */
  struct rs_realm {
      char *name;
      enum rs_conn_type type;
      int timeout;
      int retries;
 -    char *cacertfile;
 -    char *cacertpath;
 -    char *certfile;
 -    char *certkeyfile;
 -    struct rs_credentials *transport_cred;
 +    struct rs_listener *listeners;
 +    struct rs_peer *local_addr;
      struct rs_peer *peers;
      struct rs_realm *next;
  };
  
 -/** Top configuration object.  */
 +/** Top configuration object. */
  struct rs_config {
      struct rs_realm *realms;
      cfg_t *cfg;
  };
  
 +/** Libradsec context. */
  struct rs_context {
      struct rs_config *config;
 +    struct rs_realm *realms;
      struct rs_alloc_scheme alloc_scheme;
      struct rs_error *err;
 +    struct event_base *evb;
  };
  
 -struct rs_connection {
 +/** Base class for a connection. */
 +struct rs_conn_base {
 +    uint32_t magic;             /* Must be one of RS_CONN_MAGIC_*. */
      struct rs_context *ctx;
 -    struct rs_realm *realm;   /* Owned by ctx.  */
 -    struct event_base *evb;   /* Event base.  */
 -    struct event *tev;                /* Timeout event.  */
 -    struct rs_conn_callbacks callbacks;
 -    void *user_data;
 +    struct rs_realm *realm;   /* Owned by ctx. */
 +    enum rs_conn_type transport;
 +    /** For a listener, allowed client addr/port pairs.
 +     For an outgoing connection, set of configured servers.
 +     For an incoming connection, the peer (as the only entry). */
      struct rs_peer *peers;
 -    struct rs_peer *active_peer;
 -    struct rs_error *err;
      struct timeval timeout;
 -    char is_connecting;               /* FIXME: replace with a single state member */
 -    char is_connected;                /* FIXME: replace with a single state member */
 -    int fd;                   /* Socket.  */
 -    int tryagain;             /* For server failover.  */
 -    int nextid;                       /* Next RADIUS packet identifier.  */
 -    /* TCP transport specifics.  */
 -    struct bufferevent *bev;  /* Buffer event.  */
 -    /* UDP transport specifics.  */
 -    struct event *wev;                /* Write event (for UDP).  */
 -    struct event *rev;                /* Read event (for UDP).  */
 -    struct rs_packet *out_queue; /* Queue for outgoing UDP packets.  */
 +    int tryagain;             /* For server failover. */
 +    void *user_data;
 +    struct rs_error *err;
 +    int fd;                   /* Socket. */
 +    /* TCP transport specifics. */
 +    struct bufferevent *bev;  /* Buffer event. */
 +    /* UDP transport specifics. */
 +    struct event *wev;                /* Write event (for UDP). */
 +    struct event *rev;                /* Read event (for UDP). */
 +};
 +
 +enum rs_conn_state {
 +    RS_CONN_STATE_UNDEFINED = 0,
 +    RS_CONN_STATE_CONNECTING,
 +    RS_CONN_STATE_CONNECTED,
 +};
 +
 +/** A generic connection. */
 +struct rs_connection {
 +    struct rs_conn_base base_;
 +    struct event *tev;                /* Timeout event. */
 +    struct rs_conn_callbacks callbacks;
 +    enum rs_conn_state state;
 +    struct rs_peer *active_peer;
 +    struct rs_message *out_queue; /* Queue for outgoing UDP packets. */
  #if defined(RS_ENABLE_TLS)
 -    /* TLS specifics.  */
 +    /* TLS specifics. */
      SSL_CTX *tls_ctx;
      SSL *tls_ssl;
  #endif
  };
  
 -enum rs_packet_flags {
 -    RS_PACKET_HEADER_READ,
 -    RS_PACKET_RECEIVED,
 -    RS_PACKET_SENT,
 +/** A listening connection. Spawns generic connections when peers
 +    connect to it. */
 +struct rs_listener {
 +    struct rs_conn_base base_;
 +    struct evconnlistener *evlistener;
 +    struct rs_listener_callbacks callbacks;
 +    struct rs_listener *next;
 +};
 +
 +enum rs_message_flags {
 +    RS_MESSAGE_HEADER_READ,
 +    RS_MESSAGE_RECEIVED,
 +    RS_MESSAGE_SENT,
  };
  
  struct radius_packet;
  
 -struct rs_packet {
 +struct rs_message {
      struct rs_connection *conn;
      unsigned int flags;
      uint8_t hdr[RS_HEADER_LEN];
 -    struct radius_packet *rpkt;       /* FreeRADIUS object.  */
 -    struct rs_packet *next;   /* Used for UDP output queue.  */
 +    struct radius_packet *rpkt;       /* FreeRADIUS object. */
 +    struct rs_message *next;  /* Used for UDP output queue. */
  };
  
  #if defined (__cplusplus)
  }
  #endif
  
 -/* Convenience macros.  */
 -#define rs_calloc(h, nmemb, size) \
 -    (h->alloc_scheme.calloc ? h->alloc_scheme.calloc : calloc)(nmemb, size)
 -#define rs_malloc(h, size) \
 -    (h->alloc_scheme.malloc ? h->alloc_scheme.malloc : malloc)(size)
 -#define rs_free(h, ptr) \
 -    (h->alloc_scheme.free ? h->alloc_scheme.free : free)(ptr)
 -#define rs_realloc(h, realloc, ptr, size) \
 -    (h->alloc_scheme.realloc ? h->alloc_scheme.realloc : realloc)(ptr, size)
 +/***********************/
 +/* Convenience macros. */
 +
 +/* Memory allocation. */
 +#define rs_calloc(h, nmemb, size) ((h)->alloc_scheme.calloc != NULL \
 +     ? (h)->alloc_scheme.calloc : calloc)((nmemb), (size))
 +#define rs_malloc(h, size) ((h)->alloc_scheme.malloc != NULL \
 +     ? (h)->alloc_scheme.malloc : malloc)((size))
 +#define rs_free(h, ptr) ((h)->alloc_scheme.free != NULL \
 +     ? (h)->alloc_scheme.free : free)((ptr))
 +#define rs_realloc(h, ptr, size) ((h)->alloc_scheme.realloc != NULL \
 +     ? (h)->alloc_scheme.realloc : realloc)((ptr), (size))
  #define min(a, b) ((a) < (b) ? (a) : (b))
  #define max(a, b) ((a) > (b) ? (a) : (b))
  
 +/* Basic CPP-based classes, proudly borrowed from Tor. */
 +#if defined(__GNUC__) && __GNUC__ > 3
 + #define STRUCT_OFFSET(tp, member) __builtin_offsetof(tp, member)
 +#else
 + #define STRUCT_OFFSET(tp, member) \
 +   ((off_t) (((char*)&((tp*)0)->member)-(char*)0))
 +#endif
 +#define SUBTYPE_P(p, subtype, basemember) \
 +  ((void*) (((char*)(p)) - STRUCT_OFFSET(subtype, basemember)))
 +#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_))
 +#define TO_BASE_CONN(c) (&((c)->base_))
 +static struct rs_connection *TO_GENERIC_CONN (struct rs_conn_base *);
 +static struct rs_listener *TO_LISTENER_CONN (struct rs_conn_base *);
 +static INLINE struct rs_connection *TO_GENERIC_CONN (struct rs_conn_base *b)
 +{
 +  assert (b->magic == RS_CONN_MAGIC_GENERIC);
 +  return DOWNCAST (struct rs_connection, b);
 +}
 +static INLINE struct rs_listener *TO_LISTENER_CONN (struct rs_conn_base *b)
 +{
 +  assert (b->magic == RS_CONN_MAGIC_LISTENER);
 +  return DOWNCAST (struct rs_listener, b);
 +}
 +
  #endif /* _RADSEC_RADSEC_IMPL_H_ */
  
  /* Local Variables: */
@@@ -1,7 -1,7 +1,7 @@@
  /** \file radsec.h
 -    \brief Public interface for libradsec.  */
 +    \brief Public interface for libradsec. */
  
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #ifndef _RADSEC_RADSEC_H_
@@@ -22,7 -22,6 +22,7 @@@
  #ifdef HAVE_STDINT_H
  #include <stdint.h>
  #endif
 +#include "compat.h"
  
  enum rs_error_code {
      RSE_OK = 0,
@@@ -31,7 -30,6 +31,6 @@@
      RSE_INVALID_CTX = 3,
      RSE_INVALID_CONN = 4,
      RSE_CONN_TYPE_MISMATCH = 5,
-     RSE_FR = 6,
      RSE_BADADDR = 7,
      RSE_NOPEER = 8,
      RSE_EVENT = 9,            /* libevent error.  */
@@@ -40,7 -38,7 +39,7 @@@
      RSE_BADAUTH = 12,
      RSE_INTERNAL = 13,
      RSE_SSLERR = 14,          /* OpenSSL error.  */
 -    RSE_INVALID_PKT = 15,
 +    RSE_INVALID_MSG = 15,
      RSE_TIMEOUT_CONN = 16,    /* Connection timeout.  */
      RSE_INVAL = 17,           /* Invalid argument.  */
      RSE_TIMEOUT_IO = 18,      /* I/O timeout.  */
@@@ -73,7 -71,7 +72,7 @@@
      RSE_MAX = RSE_CERT
  };
  
 -enum rs_conn_type {
 +enum rs_conn_type {             /* FIXME: Rename rs_transport_type? */
      RS_CONN_TYPE_NONE = 0,
      RS_CONN_TYPE_UDP,
      RS_CONN_TYPE_TCP,
@@@ -120,27 -118,10 +119,27 @@@ typedef enum rs_attr_type_t 
  extern "C" {
  #endif
  
 +/* Backwards compatible with 0.0.2. */
 +#define RSE_INVALID_PKT RSE_INVALID_MSG
 +#define rs_packet rs_message
 +#define rs_conn_packet_received_cb rs_conn_message_received_cb
 +#define rs_conn_packet_sent_cb rs_conn_message_sent_cb
 +#define rs_conn_receive_packet rs_conn_receive_message
 +#define rs_dump_packet rs_dump_message
 +#define rs_packet_append_avp rs_message_append_avp
 +#define rs_packet_avps rs_message_avps
 +#define rs_packet_code rs_message_code
 +#define rs_packet_create rs_message_create
 +#define rs_packet_create_authn_request rs_message_create_authn_request
 +#define rs_packet_destroy rs_message_destroy
 +#define rs_packet_send rs_message_send
 +
  /* Data types.  */
  struct rs_context;            /* radsec-impl.h */
 +struct rs_conn_base;            /* radsec-impl.h */
  struct rs_connection;         /* radsec-impl.h */
 -struct rs_packet;             /* radsec-impl.h */
 +struct rs_listener;             /* radsec-impl.h */
 +struct rs_message;            /* radsec-impl.h */
  struct rs_conn;                       /* radsec-impl.h */
  struct rs_error;              /* radsec-impl.h */
  struct rs_peer;                       /* radsec-impl.h */
@@@ -161,30 -142,18 +160,30 @@@ struct rs_alloc_scheme 
  
  typedef void (*rs_conn_connected_cb) (void *user_data /* FIXME: peer? */ );
  typedef void (*rs_conn_disconnected_cb) (void *user_data /* FIXME: reason? */ );
 -typedef void (*rs_conn_packet_received_cb) (struct rs_packet *packet,
 -                                          void *user_data);
 -typedef void (*rs_conn_packet_sent_cb) (void *user_data);
 +typedef void (*rs_conn_message_received_cb) (struct rs_message *message,
 +                                             void *user_data);
 +typedef void (*rs_conn_message_sent_cb) (void *user_data);
  struct rs_conn_callbacks {
 -    /** Callback invoked when the connection has been established.  */
 +    /** Callback invoked when an outgoing connection has been established.  */
      rs_conn_connected_cb connected_cb;
      /** Callback invoked when the connection has been torn down.  */
      rs_conn_disconnected_cb disconnected_cb;
 -    /** Callback invoked when a packet was received.  */
 -    rs_conn_packet_received_cb received_cb;
 -    /** Callback invoked when a packet was successfully sent.  */
 -    rs_conn_packet_sent_cb sent_cb;
 +    /** Callback invoked when a message was received.  */
 +    rs_conn_message_received_cb received_cb;
 +    /** Callback invoked when a message was successfully sent.  */
 +    rs_conn_message_sent_cb sent_cb;
 +};
 +
 +typedef struct rs_peer *(*rs_listener_client_filter_cb)
 +    (const struct rs_listener *listener, void *user_data);
 +typedef void (*rs_listener_new_conn_cb)
 +    (struct rs_connection *conn, void *user_data);
 +typedef void (*rs_listener_error_cb)
 +    (struct rs_connection *conn, void *user_data);
 +struct rs_listener_callbacks {
 +    rs_listener_client_filter_cb client_filter_cb;
 +    rs_listener_new_conn_cb new_conn_cb;
 +    rs_listener_error_cb error_cb;
  };
  
  typedef struct value_pair rs_avp;
@@@ -224,36 -193,18 +223,36 @@@ int rs_context_set_alloc_scheme(struct 
      accessed using \a rs_err_ctx_pop.  */
  int rs_context_read_config(struct rs_context *ctx, const char *config_file);
  
 +int rs_context_print_config (struct rs_context *ctx, char **buf_out);
 +
 +/*************/
 +/* Listener. */
 +/*************/
 +int rs_listener_create (struct rs_context *ctx,
 +                        struct rs_listener **listener,
 +                        const char *config);
 +void rs_listener_set_callbacks (struct rs_listener *listener,
 +                                const struct rs_listener_callbacks *cb,
 +                                void *user_data);
 +int rs_listener_listen (struct rs_listener *listener);
 +int rs_listener_dispatch (const struct rs_listener *listener);
 +int rs_listener_close (struct rs_listener *l);
 +struct event_base *rs_listener_get_eventbase (const struct rs_listener *l);
 +int rs_listener_get_fd (const struct rs_listener *l);
 +
  /****************/
  /* Connection.  */
  /****************/
  /** Create a connection.  \a conn is the address of a pointer to an \a
      rs_connection, the output.  Free the connection using \a
      rs_conn_destroy.  Note that a connection must not be freed before
 -    all packets associated with the connection have been freed.  A
 -    packet is associated with a connection when it's created (\a
 -    rs_packet_create) or received (\a rs_conn_receive_packet).
 +    all messages associated with the connection have been freed.  A
 +    message is associated with a connection when it's created
 +    (\a rs_message_create) or received (\a rs_conn_receive_message).
 +
 +    If \a config is not NULL it should be the name of a realm found in
 +    a config file that has already been read using \a rs_context_read_config.
  
 -    If \a config is not NULL it should be the name of a configuration
 -    found in the config file read in using \a rs_context_read_config.
      \return On success, RSE_OK (0) is returned.  On error, !0 is
      returned and a struct \a rs_error is pushed on the error stack for
      the context.  The error can be accessed using \a
@@@ -272,10 -223,10 +271,10 @@@ int rs_conn_add_listener(struct rs_conn
  int rs_conn_disconnect (struct rs_connection *conn);
  
  /** Disconnect and free memory allocated for connection \a conn.  Note
 -    that a connection must not be freed before all packets associated
 -    with the connection have been freed.  A packet is associated with
 -    a connection when it's created (\a rs_packet_create) or received
 -    (\a rs_conn_receive_packet).  \return RSE_OK (0) on success, !0 *
 +    that a connection must not be freed before all messages associated
 +    with the connection have been freed.  A message is associated with
 +    a connection when it's created (\a rs_message_create) or received
 +    (\a rs_conn_receive_message).  \return RSE_OK (0) on success, !0 *
      on error.  On error, errno is set appropriately. */
  int rs_conn_destroy(struct rs_connection *conn);
  
@@@ -287,8 -238,7 +286,8 @@@ int rs_conn_set_eventbase(struct rs_con
  
  /** Register callbacks \a cb for connection \a conn.  */
  void rs_conn_set_callbacks(struct rs_connection *conn,
 -                         struct rs_conn_callbacks *cb);
 +                         struct rs_conn_callbacks *cb,
 +                           void *user_data);
  
  /** Remove callbacks for connection \a conn.  */
  void rs_conn_del_callbacks(struct rs_connection *conn);
@@@ -311,106 -261,88 +310,115 @@@ int rs_conn_get_current_peer(struct rs_
  
      If \a req_msg is not NULL, a successfully received RADIUS message
      is verified against it.  If \a pkt_out is not NULL it will upon
 -    return contain a pointer to an \a rs_packet containing the new
 +    return contain a pointer to an \a rs_message containing the new
      message.
  
      \return On error or if the connect (TCP only) or read times out,
      \a pkt_out will not be changed and one or more errors are pushed
      on \a conn (available through \a rs_err_conn_pop).  */
 -int rs_conn_receive_packet(struct rs_connection *conn,
 -                         struct rs_packet *request,
 -                         struct rs_packet **pkt_out);
 +int rs_conn_receive_message(struct rs_connection *conn,
 +                            struct rs_message *request,
 +                            struct rs_message **pkt_out);
 +
 +/** Run the dispatcher for the event base associated with \a conn. A
 + * wrapper around event_base_dispatch() for not having to hand out the
 + * event base. */
 +int rs_conn_dispatch(struct rs_connection *conn);
 +
 +#if 0
 +/** Get the event base associated with connection \a conn.
 + * \return struct event_base*. */
 +struct event_base *rs_conn_get_evb(const struct rs_connection *conn);
 +#endif
  
 +#define rs_conn_fd rs_conn_get_fd /* Old name. */
  /** Get the file descriptor associated with connection \a conn.
   * \return File descriptor.  */
 -int rs_conn_fd(struct rs_connection *conn);
 +int rs_conn_get_fd(struct rs_connection *conn);
  
  /** Set the timeout value for connection \a conn.  */
  void rs_conn_set_timeout(struct rs_connection *conn, struct timeval *tv);
  
  /* Peer -- client and server.  */
 -int rs_peer_create(struct rs_connection *conn, struct rs_peer **peer_out);
 +/** Create a peer and add it to list of peers held by \a conn. */
 +int rs_peer_create_for_conn (struct rs_connection *conn,
 +                             struct rs_peer **peer_out);
 +/** Create a peer and add it to list of peers held by \a listener. */
 +int rs_peer_create_for_listener (struct rs_listener *listener,
 +                                 struct rs_peer **peer_out);
 +/** Set RADIUS secret for \a peer. Free resurces with \a rs_peer_free_secret. */
 +int rs_peer_set_secret(struct rs_peer *peer, const char *secret);
 +/** Free resources allocated by \a rs_peer_set_secret. */
 +void rs_peer_free_secret (struct rs_peer *peer);
  int rs_peer_set_address(struct rs_peer *peer,
                        const char *hostname,
                        const char *service);
 -int rs_peer_set_secret(struct rs_peer *peer, const char *secret);
 +void rs_peer_free_address (struct rs_peer *peer);
  void rs_peer_set_timeout(struct rs_peer *peer, int timeout);
  void rs_peer_set_retries(struct rs_peer *peer, int retries);
  
  /************/
 -/* Packet.  */
 +/* Message. */
  /************/
 -/** Create a packet associated with connection \a conn.  */
 -int rs_packet_create(struct rs_connection *conn, struct rs_packet **pkt_out);
 -
 -/** Free all memory allocated for packet \a pkt.  */
 -void rs_packet_destroy(struct rs_packet *pkt);
 -
 -/** Send packet \a pkt on the connection associated with \a pkt.
 -    \a user_data is passed to the \a rs_conn_packet_received_cb callback
 -    registered with the connection. If no callback is registered with
 -    the connection, the event loop is run by \a rs_packet_send and it
 -    blocks until the full packet has been sent. Note that sending can
 -    fail in several ways, f.ex. if the transmission protocol in use
 -    is connection oriented (\a RS_CONN_TYPE_TCP and \a RS_CONN_TYPE_TLS)
 -    and the connection can not be established. Also note that no
 -    retransmission is done, something that is required for connectionless
 -    transport protocols (\a RS_CONN_TYPE_UDP and \a RS_CONN_TYPE_DTLS).
 -    The "request" API with \a rs_request_send can help with this.
 +/** Create a message associated with connection \a conn.  */
 +int rs_message_create(struct rs_connection *conn, struct rs_message **pkt_out);
 +
 +/** Free all memory allocated for message \a msg.  */
 +void rs_message_destroy(struct rs_message *msg);
 +
 +/** Send \a msg on the connection associated with \a msg.
 +    If no callback is registered with the connection
 +    (\a rs_conn_set_callbacks), the event loop is run by
 +    \a rs_message_send and it blocks until the message has been
 +    succesfully sent.
 +
++    Note that sending can fail in several ways, f.ex. if the
++    transmission protocol in use is connection oriented
++    (\a RS_CONN_TYPE_TCP and \a RS_CONN_TYPE_TLS) and the connection
++    can not be established.
++
++    Also note that no retransmission is being done. This is required
++    for connectionless transport protocols (\a RS_CONN_TYPE_UDP and
++    \a RS_CONN_TYPE_DTLS). The "request" API with \a rs_request_send can
++    help with this.
      \return On success, RSE_OK (0) is returned. On error, !0 is
      returned and a struct \a rs_error is pushed on the error stack for
-     the connection. The error can be accessed using \a
-     rs_err_conn_pop. */
+     the connection. The error can be accessed using \a rs_err_conn_pop. */
 -int rs_packet_send(struct rs_packet *pkt, void *user_data);
 -
 -/** Create a RADIUS authentication request packet associated with
 -    connection \a conn.  Optionally, User-Name and User-Password
 -    attributes are added to the packet using the data in \a user_name
 -    and \a user_pw.  */
 -int rs_packet_create_authn_request(struct rs_connection *conn,
 -                                 struct rs_packet **pkt,
 -                                 const char *user_name,
 -                                 const char *user_pw);
 -
 -/*** Append \a tail to packet \a pkt.  */
 +int rs_message_send(struct rs_message *msg);
 +
 +/** Create a RADIUS authentication request message associated with
 +    connection \a conn. Optionally, User-Name and User-Password
 +    attributes are added to the message using the data in \a user_name
 +    and \a user_pw.
 +    FIXME: describe what RADIUS shared secret is being used */
 +int rs_message_create_authn_request(struct rs_connection *conn,
 +                                    struct rs_message **msg,
 +                                    const char *user_name,
 +                                    const char *user_pw);
 +
 +/*** Append \a tail to message \a msg.  */
  int
 -rs_packet_append_avp(struct rs_packet *pkt,
 -                   unsigned int attribute, unsigned int vendor,
 -                   const void *data, size_t data_len);
 +rs_message_append_avp(struct rs_message *msg,
 +                      unsigned int attribute, unsigned int vendor,
 +                      const void *data, size_t data_len);
  
 -/*** Get pointer to \a pkt attribute value pairs. */
 +/*** Get pointer to \a msg attribute value pairs. */
  void
 -rs_packet_avps(struct rs_packet *pkt, rs_avp ***vps);
 +rs_message_avps(struct rs_message *msg, rs_avp ***vps);
  
 -/*** Get RADIUS packet type of \a pkt. */
 +/*** Get RADIUS message type of \a msg. */
  unsigned int
 -rs_packet_code(struct rs_packet *pkt);
 +rs_message_code(struct rs_message *msg);
  
 -/*** Get RADIUS AVP from \a pkt. */
 +/*** Get RADIUS AVP from \a msg. */
  rs_const_avp *
 -rs_packet_find_avp(struct rs_packet *pkt, unsigned int attr, unsigned int vendor);
 +rs_message_find_avp(struct rs_message *msg, unsigned int attr,
 +                    unsigned int vendor);
  
 -/*** Set packet identifier in \a pkt; returns old identifier */
 +/*** Set packet identifier in \a msg; returns old identifier */
  int
 -rs_packet_set_id (struct rs_packet *pkt, int id);
 +rs_message_set_id (struct rs_message *msg, int id);
  
  /************/
  /* Config.  */
@@@ -449,17 -381,9 +457,17 @@@ int rs_err_conn_push_fl(struct rs_conne
                        int line,
                        const char *fmt,
                        ...);
 +int rs_err_connbase_push_fl(struct rs_conn_base *connbase,
 +                            int code,
 +                            const char *file,
 +                            int line,
 +                            const char *fmt,
 +                            ...);
  /** Pop the first error from the error FIFO associated with connection
 -    \a conn or NULL if there are no errors in the FIFO.  */
 +    \a conn or NULL if there are no errors in the FIFO. */
  struct rs_error *rs_err_conn_pop(struct rs_connection *conn);
 +struct rs_error *rs_err_connbase_pop(struct rs_conn_base *connbase);
 +struct rs_error *rs_err_listener_pop(struct rs_listener *l);
  
  int rs_err_conn_peek_code (struct rs_connection *conn);
  void rs_err_free(struct rs_error *err);
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright 20102011 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2011 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #ifndef _RADSEC_REQUEST_IMPL_H_
@@@ -12,7 -12,7 +12,7 @@@ struct rs_reques
  {
    struct rs_connection *conn;
    struct event *timer;
 -  struct rs_packet *req_msg;
 +  struct rs_message *req_msg;
    struct rs_conn_callbacks saved_cb;
    void *saved_user_data;
  };
@@@ -1,16 -1,12 +1,16 @@@
  /** \file request.h
      \brief Public interface for libradsec request's.  */
  
- /* Copyright 2010, 2011 NORDUnet A/S. All rights reserved.
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #ifndef _RADSEC_REQUEST_H_
  #define _RADSEC_REQUEST_H_ 1
  
 +/* Backwards compatible with 0.0.2. */
 +#define rs_request_add_reqpkt rs_request_add_reqmsg
 +#define rs_request_get_reqpkt rs_request_get_reqmsg
 +
  struct rs_request;
  
  #if defined (__cplusplus)
@@@ -20,34 -16,32 +20,31 @@@ extern "C" 
  /** Create a request associated with connection \a conn.  */
  int rs_request_create(struct rs_connection *conn, struct rs_request **req_out);
  
 -/** Add RADIUS request message \a req_msg to request \a req.
 -    FIXME: Rename to rs_request_add_reqmsg?  */
 -void rs_request_add_reqpkt(struct rs_request *req, struct rs_packet *req_msg);
 +/** Add RADIUS request message \a req_msg to request \a req. */
 +void rs_request_add_reqmsg(struct rs_request *req, struct rs_message *req_msg);
  
  /** Create a request associated with connection \a conn containing a
-     newly created RADIUS authentication message, possibly with
-     \a user_name and \a user_pw attributes.  \a user_name and \a user_pw
-     are optional and can be NULL. If \a user_name and \a user_pw are provided,
-     \a secret must also be provided. \a secret is used for "hiding" the
-     password. */
+     newly created RADIUS authentication message, possibly with \a
+     user_name and \a user_pw attributes.  \a user_name and _user_pw
+     are optional and can be NULL.  */
  int rs_request_create_authn(struct rs_connection *conn,
                            struct rs_request **req_out,
                            const char *user_name,
-                           const char *user_pw,
-                             const char *secret);
+                           const char *user_pw);
  
  /** Send request \a req and wait for a matching response.  The
      response is put in \a resp_msg (if not NULL).  NOTE: At present,
      no more than one outstanding request to a given realm is
      supported.  This will change in a future version.  */
 -int rs_request_send(struct rs_request *req, struct rs_packet **resp_msg);
 +int rs_request_send(struct rs_request *req, struct rs_message **resp_msg);
  
  /** Free all memory allocated by request \a req including any request
 -    packet associated with the request.  Note that a request must be
 +    message associated with the request.  Note that a request must be
      freed before its associated connection can be freed.  */
  void rs_request_destroy(struct rs_request *req);
  
  /** Return request message in request \a req.  */
 -struct rs_packet *rs_request_get_reqmsg(const struct rs_request *req);
 +struct rs_message *rs_request_get_reqmsg(const struct rs_request *req);
  
  #if defined (__cplusplus)
  }
diff --combined lib/peer.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
 -/* Copyright 2010-2012 NORDUnet A/S. All rights reserved.
++/* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
     See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
@@@ -22,8 -22,8 +22,8 @@@ peer_pick_peer (struct rs_connection *c
  
    if (conn->active_peer)
      conn->active_peer = conn->active_peer->next; /* Next.  */
 -  if (!conn->active_peer)
 -    conn->active_peer = conn->peers; /* From the top.  */
 +  if (conn->active_peer == NULL)
 +    conn->active_peer = TO_BASE_CONN (conn)->peers; /* From the top.  */
  
    return conn->active_peer;
  }
@@@ -33,9 -33,10 +33,9 @@@ peer_create (struct rs_context *ctx, st
  {
    struct rs_peer *p;
  
 -  p = (struct rs_peer *) rs_malloc (ctx, sizeof(*p));
 +  p = (struct rs_peer *) rs_calloc (ctx, 1, sizeof(*p));
    if (p)
      {
 -      memset (p, 0, sizeof(struct rs_peer));
        if (*rootp)
        {
          p->next = (*rootp)->next;
    return p;
  }
  
 -/* Public functions.  */
  int
 -rs_peer_create (struct rs_connection *conn, struct rs_peer **peer_out)
 +peer_create_for_connbase (struct rs_conn_base *connbase,
 +                          struct rs_peer **peer_out)
  {
    struct rs_peer *peer;
  
 -  peer = peer_create (conn->ctx, &conn->peers);
 -  if (peer)
 -    {
 -      peer->conn = conn;
 -      peer->realm->timeout = 2;       /* FIXME: Why?  */
 -      peer->realm->retries = 2;       /* FIXME: Why?  */
 -    }
 -  else
 -    return rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL);
 +  peer = peer_create (connbase->ctx, &connbase->peers);
 +  if (peer == NULL)
 +    return rs_err_connbase_push_fl (connbase, RSE_NOMEM, __FILE__, __LINE__,
 +                                    NULL);
 +  peer->connbase = connbase;
 +  peer->realm = connbase->realm;
 +
    if (*peer_out)
      *peer_out = peer;
    return RSE_OK;
  }
  
 +/* Public functions.  */
 +int
 +rs_peer_create_for_conn (struct rs_connection *conn, struct rs_peer **peer_out)
 +{
 +  return peer_create_for_connbase (TO_BASE_CONN (conn), peer_out);
 +}
 +
  int
 -rs_peer_set_address (struct rs_peer *peer, const char *hostname,
 +rs_peer_create_for_listener (struct rs_listener *listener,
 +                             struct rs_peer **peer_out)
 +{
 +  return peer_create_for_connbase (TO_BASE_CONN (listener), peer_out);
 +}
 +
 +int
 +rs_peer_set_address (struct rs_peer *peer,
 +                     const char *hostname,
                       const char *service)
  {
    assert (peer);
 -  assert (peer->conn);
 -  assert (peer->conn->ctx);
 +  assert (peer->connbase);
 +  assert (peer->connbase->ctx);
  
 -  peer->hostname = rs_strdup (peer->conn->ctx, hostname);
 -  peer->service = rs_strdup (peer->conn->ctx, service);
 +  peer->hostname = rs_strdup (peer->connbase->ctx, hostname);
 +  peer->service = rs_strdup (peer->connbase->ctx, service);
    if (peer->hostname == NULL || peer->service == NULL)
      return RSE_NOMEM;
  
  }
  
  void
 +rs_peer_free_address (struct rs_peer *peer)
 +{
 +  assert (peer);
 +  assert (peer->connbase);
 +  assert (peer->connbase->ctx);
 +
 +  if (peer->hostname)
 +    rs_free (peer->connbase->ctx, peer->hostname);
 +  peer->hostname = NULL;
 +  if (peer->service)
 +    rs_free (peer->connbase->ctx, peer->service);
 +  peer->service = NULL;
 +}
 +
 +void
  rs_peer_set_timeout (struct rs_peer *peer, int timeout)
  {
    assert (peer);
    assert (peer->realm);
    peer->realm->timeout = timeout;
  }
 +
  void
  rs_peer_set_retries (struct rs_peer *peer, int retries)
  {
  int
  rs_peer_set_secret (struct rs_peer *peer, const char *secret)
  {
 -  if (peer->secret)
 -    free (peer->secret);
 -  peer->secret = (char *) malloc (strlen(secret) + 1);
 +  assert (peer);
 +  assert (peer->connbase);
 +  assert (peer->connbase->ctx);
 +
 +  rs_peer_free_secret (peer);
 +  peer->secret = rs_calloc (peer->connbase->ctx, 1, strlen(secret) + 1);
    if (!peer->secret)
 -    return rs_err_conn_push (peer->conn, RSE_NOMEM, NULL);
 +    return rs_err_connbase_push (peer->connbase, RSE_NOMEM, NULL);
    strcpy (peer->secret, secret);
    return RSE_OK;
  }
  
 +void
 +rs_peer_free_secret (struct rs_peer *peer)
 +{
 +  assert (peer);
 +  assert (peer->connbase);
 +  assert (peer->connbase->ctx);
 +
 +  if (peer->secret)
 +    rs_free (peer->connbase->ctx, peer->secret);
 +  peer->secret = NULL;
 +}
diff --combined lib/radsec.c
@@@ -1,5 -1,5 +1,5 @@@
- /* Copyright 2010, 2011 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
@@@ -107,11 -107,11 +107,11 @@@ rs_context_destroy (struct rs_context *
                    evutil_freeaddrinfo (p->addr_cache);
                    p->addr_cache = NULL;
                  }
 +              rs_free (ctx, p->transport_cred);
              p = p->next;
              rs_free (ctx, tmp);
            }
          free (r->name);
 -          rs_free (ctx, r->transport_cred);
          r = r->next;
          rs_free (ctx, tmp);
        }
        rs_free (ctx, ctx->config);
      }
  
 +  if (ctx->evb)
 +    event_base_free (ctx->evb);
 +
    free (ctx);
  }
  
diff --combined lib/request.c
@@@ -1,5 -1,5 +1,5 @@@
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
 -/* Copyright 2010-2011 NORDUnet A/S. All rights reserved.
++/* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
@@@ -30,7 -30,7 +30,7 @@@
  int
  rs_request_create (struct rs_connection *conn, struct rs_request **req_out)
  {
 -  struct rs_request *req = rs_malloc (conn->ctx, sizeof(*req));
 +  struct rs_request *req = rs_malloc (conn->base_.ctx, sizeof(*req));
    assert (req_out);
    if (!req)
      return rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL);
@@@ -41,7 -41,7 +41,7 @@@
  }
  
  void
 -rs_request_add_reqpkt (struct rs_request *req, struct rs_packet *req_msg)
 +rs_request_add_reqmsg (struct rs_request *req, struct rs_message *req_msg)
  {
    assert (req);
    req->req_msg = req_msg;
@@@ -51,8 -51,7 +51,7 @@@ in
  rs_request_create_authn (struct rs_connection *conn,
                         struct rs_request **req_out,
                         const char *user_name,
-                        const char *user_pw,
-                          const char *secret)
+                        const char *user_pw)
  {
    struct rs_request *req = NULL;
    assert (req_out);
@@@ -60,7 -59,7 +59,7 @@@
    if (rs_request_create (conn, &req))
      return -1;
  
 -  if (rs_packet_create_authn_request (conn, &req->req_msg, user_name, user_pw))
 +  if (rs_message_create_authn_request (conn, &req->req_msg, user_name, user_pw))
      return -1;
  
    if (req_out)
@@@ -73,11 -72,11 +72,11 @@@ rs_request_destroy (struct rs_request *
  {
    assert (request);
    assert (request->conn);
 -  assert (request->conn->ctx);
 +  assert (request->conn->base_.ctx);
  
    if (request->req_msg)
 -    rs_packet_destroy (request->req_msg);
 -  rs_free (request->conn->ctx, request);
 +    rs_message_destroy (request->req_msg);
 +  rs_free (request->conn->base_.ctx, request);
  }
  
  static void
@@@ -89,7 -88,7 +88,7 @@@ _rand_rt (struct timeval *res, uint32_
  }
  
  int
 -rs_request_send (struct rs_request *request, struct rs_packet **resp_msg)
 +rs_request_send (struct rs_request *request, struct rs_message **resp_msg)
  {
    int r = 0;
    struct rs_connection *conn = NULL;
    if (!request || !request->conn || !request->req_msg || !resp_msg)
      return rs_err_conn_push_fl (conn, RSE_INVAL, __FILE__, __LINE__, NULL);
    conn = request->conn;
 -  assert (!conn_user_dispatch_p (conn)); /* This function is high level.  */
 +  assert (!conn_user_dispatch_p (conn)); /* This function is high level. */
  
    gettimeofday (&end, NULL);
    end.tv_sec += MRD;
      {
        rs_conn_set_timeout (conn, &rt);
  
 -      r = rs_packet_send (request->req_msg, NULL);
 +      r = rs_message_send (request->req_msg);
        if (r == RSE_OK)
        {
 -        r = rs_conn_receive_packet (request->conn,
 -                                    request->req_msg,
 -                                    resp_msg);
 +        r = rs_conn_receive_message (request->conn,
 +                                       request->req_msg,
 +                                       resp_msg);
          if (r == RSE_OK)
            break;              /* Success.  */
  
    return r;
  }
  
 -struct rs_packet *
 +struct rs_message *
  rs_request_get_reqmsg (const struct rs_request *request)
  {
    assert (request);
diff --combined lib/send.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
@@@ -11,7 -11,7 +11,7 @@@
  #include <radsec/radsec.h>
  #include <radsec/radsec-impl.h>
  #include "debug.h"
 -#include "packet.h"
 +#include "message.h"
  #include "event.h"
  #include "peer.h"
  #include "conn.h"
@@@ -19,9 -19,9 +19,9 @@@
  #include "udp.h"
  
  static int
 -_conn_open (struct rs_connection *conn, struct rs_packet *pkt)
 +_conn_open (struct rs_connection *conn, struct rs_message *msg)
  {
 -  if (event_init_eventbase (conn))
 +  if (event_init_eventbase (TO_BASE_CONN (conn)))
      return -1;
  
    if (!conn->active_peer)
    if (!conn->active_peer)
      return rs_err_conn_push_fl (conn, RSE_NOPEER, __FILE__, __LINE__, NULL);
  
 -  if (event_init_socket (conn, conn->active_peer))
 +  if (event_init_socket (&conn->base_, conn->active_peer))
      return -1;
  
 -  if (conn->realm->type == RS_CONN_TYPE_TCP
 -      || conn->realm->type == RS_CONN_TYPE_TLS)
 +  if (conn->base_.realm->type == RS_CONN_TYPE_TCP
 +      || conn->base_.realm->type == RS_CONN_TYPE_TLS)
      {
        if (tcp_init_connect_timer (conn))
        return -1;
 -      if (event_init_bufferevent (conn, conn->active_peer))
 +      if (event_init_bufferevent (conn))
        return -1;
      }
    else
      {
 -      if (udp_init (conn, pkt))
 +      if (udp_init (conn, msg))
        return -1;
        if (udp_init_retransmit_timer (conn))
        return -1;
      }
  
 -  if (!conn->is_connected)
 -    if (!conn->is_connecting)
 -      event_do_connect (conn);
 +  if (conn->state != RS_CONN_STATE_CONNECTED
 +      && conn->state != RS_CONN_STATE_CONNECTING)
 +    event_do_connect (conn);
  
    return RSE_OK;
  }
  static int
  _conn_is_open_p (struct rs_connection *conn)
  {
 -  return conn->active_peer && conn->is_connected;
 +  return conn->state == RS_CONN_STATE_CONNECTED
 +    && conn->active_peer != NULL;
  }
  
  /* User callback used when we're dispatching for user.  */
  static void
  _wcb (void *user_data)
  {
 -  struct rs_packet *pkt = (struct rs_packet *) user_data;
 -  assert (pkt);
 -  pkt->flags |= RS_PACKET_SENT;
 -  if (pkt->conn->bev)
 -    bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ);
 +  struct rs_message *msg = (struct rs_message *) user_data;
 +  assert (msg);
 +  msg->flags |= RS_MESSAGE_SENT;
 +  if (msg->conn->base_.bev)
 +    bufferevent_disable (msg->conn->base_.bev, EV_WRITE|EV_READ);
    else
 -    event_del (pkt->conn->wev);
 +    event_del (msg->conn->base_.wev);
  }
  
  int
 -rs_packet_send (struct rs_packet *pkt, void *user_data)
 +rs_message_send (struct rs_message *msg)
  {
    struct rs_connection *conn = NULL;
    int err = 0;
  
 -  assert (pkt);
 -  assert (pkt->conn);
 -  conn = pkt->conn;
 +  assert (msg);
 +  assert (msg->conn);
 +  conn = msg->conn;
  
    if (_conn_is_open_p (conn))
 -    packet_do_send (pkt);
 +    message_do_send (msg);
    else
 -    if (_conn_open (conn, pkt))
 +    if (_conn_open (conn, msg))
        return -1;
  
 -  assert (conn->evb);
 +  assert (conn->base_.ctx);
 +  assert (conn->base_.ctx->evb);
    assert (conn->active_peer);
 -  assert (conn->fd >= 0);
 +  assert (conn->base_.fd >= 0);
  
 -  conn->user_data = user_data;
 -
 -  if (conn->bev)              /* TCP */
 +  if (conn->base_.bev)                /* TCP */
      {
 -      bufferevent_setcb (conn->bev, NULL, tcp_write_cb, tcp_event_cb, pkt);
 -      bufferevent_enable (conn->bev, EV_WRITE);
 +      bufferevent_setcb (conn->base_.bev, NULL, tcp_write_cb, tcp_event_cb,
 +                         msg);
 +      bufferevent_enable (conn->base_.bev, EV_WRITE);
      }
    else                                /* UDP */
      {
 -      event_assign (conn->wev, conn->evb, event_get_fd (conn->wev),
 -                  EV_WRITE, event_get_callback (conn->wev), pkt);
 -      err = event_add (conn->wev, NULL);
 +      event_assign (conn->base_.wev, conn->base_.ctx->evb,
 +                    event_get_fd (conn->base_.wev),
 +                  EV_WRITE, event_get_callback (conn->base_.wev), msg);
 +      err = event_add (conn->base_.wev, NULL);
        if (err < 0)
        return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                    "event_add: %s",
    /* Do dispatch, unless the user wants to do it herself.  */
    if (!conn_user_dispatch_p (conn))
      {
 +      /* Blocking mode. */
        conn->callbacks.sent_cb = _wcb;
 -      conn->user_data = pkt;
 +      conn->base_.user_data = msg;
        rs_debug (("%s: entering event loop\n", __func__));
 -      err = event_base_dispatch (conn->evb);
 +      err = event_base_dispatch (conn->base_.ctx->evb);
        if (err < 0)
        return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                    "event_base_dispatch: %s",
                                    evutil_gai_strerror (err));
        rs_debug (("%s: event loop done\n", __func__));
        conn->callbacks.sent_cb = NULL;
 -      conn->user_data = NULL;
 +      conn->base_.user_data = NULL;
  
 -      if ((pkt->flags & RS_PACKET_SENT) == 0)
 +      if ((msg->flags & RS_MESSAGE_SENT) == 0)
        {
          assert (rs_err_conn_peek_code (conn));
          return rs_err_conn_peek_code (conn);
diff --combined lib/tcp.c
+++ b/lib/tcp.c
@@@ -1,5 -1,5 +1,5 @@@
- /* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+ /* Copyright 2011-2013 NORDUnet A/S. All rights reserved.
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
@@@ -16,7 -16,7 +16,7 @@@
  #include <radsec/radsec.h>
  #include <radsec/radsec-impl.h>
  #include "tcp.h"
 -#include "packet.h"
 +#include "message.h"
  #include "conn.h"
  #include "debug.h"
  #include "event.h"
  #include <event2/buffer.h>
  #endif
  
 -/** Read one RADIUS packet header. Return !0 on error. */
 +/** Read one RADIUS message header. Return !0 on error. */
  static int
 -_read_header (struct rs_packet *pkt)
 +_read_header (struct rs_message *msg)
  {
    size_t n = 0;
  
 -  n = bufferevent_read (pkt->conn->bev, pkt->hdr, RS_HEADER_LEN);
 +  n = bufferevent_read (TO_BASE_CONN(msg->conn)->bev, msg->hdr, RS_HEADER_LEN);
    if (n == RS_HEADER_LEN)
      {
 -      pkt->flags |= RS_PACKET_HEADER_READ;
 -      pkt->rpkt->length = (pkt->hdr[2] << 8) + pkt->hdr[3];
 -      if (pkt->rpkt->length < 20 || pkt->rpkt->length > RS_MAX_PACKET_LEN)
 -      {
 -          rs_debug (("%s: invalid packet length: %d\n",
 -                     __func__, pkt->rpkt->length));
 -          rs_conn_disconnect (pkt->conn);
 -        return rs_err_conn_push (pkt->conn, RSE_INVALID_PKT,
 -                                 "invalid packet length: %d",
 -                                 pkt->rpkt->length);
 -      }
 -      memcpy (pkt->rpkt->data, pkt->hdr, RS_HEADER_LEN);
 -      bufferevent_setwatermark (pkt->conn->bev, EV_READ,
 -                              pkt->rpkt->length - RS_HEADER_LEN, 0);
 -      rs_debug (("%s: packet header read, total pkt len=%d\n",
 -               __func__, pkt->rpkt->length));
 +      msg->flags |= RS_MESSAGE_HEADER_READ;
 +      msg->rpkt->length = (msg->hdr[2] << 8) + msg->hdr[3];
 +      if (msg->rpkt->length < 20 || msg->rpkt->length > RS_MAX_PACKET_LEN)
-         return  rs_err_conn_push (msg->conn, RSE_INVALID_MSG,
-                                   "invalid message length: %d",
-                                   msg->rpkt->length);
++        {
++          rs_debug (("%s: invalid packet length: %d\n", __func__,
++                     msg->rpkt->length));
++          rs_conn_disconnect (msg->conn);
++          return  rs_err_conn_push (msg->conn, RSE_INVALID_MSG,
++                                    "invalid message length: %d",
++                                    msg->rpkt->length);
++        }
 +      memcpy (msg->rpkt->data, msg->hdr, RS_HEADER_LEN);
 +      bufferevent_setwatermark (TO_BASE_CONN(msg->conn)->bev, EV_READ,
 +                              msg->rpkt->length - RS_HEADER_LEN, 0);
 +      rs_debug (("%s: message header read, total msg len=%d\n",
 +               __func__, msg->rpkt->length));
      }
    else if (n < 0)
 -    {
 -      rs_debug (("%s: buffer frozen while reading header\n", __func__));
 -    }
 +    rs_debug (("%s: buffer frozen while reading header\n", __func__));
    else            /* Error: libevent gave us less than the low watermark. */
-     return rs_err_conn_push_fl (msg->conn, RSE_INTERNAL, __FILE__, __LINE__,
-                                 "got %d octets reading header", n);
+     {
+       rs_debug (("%s: got: %d octets reading header\n", __func__, n));
 -      rs_conn_disconnect (pkg->conn);
 -      return rs_err_conn_push_fl (pkt->conn, RSE_INTERNAL, __FILE__, __LINE__,
 -                                "got %d octets reading header", n);
++      rs_conn_disconnect (msg->conn);
++      return rs_err_conn_push (msg->conn, RSE_INTERNAL,
++                               "got %d octets reading header", n);
+     }
 -  return 0;
 +  return RSE_OK;
  }
  
  /** Read a message, check that it's valid RADIUS and hand it off to
      registered user callback.
  
 -    The packet is read from the bufferevent associated with \a pkt and
 -    the data is stored in \a pkt->rpkt.
 +    The message is read from the bufferevent associated with \a msg and
 +    the data is stored in \a msg->rpkt.
  
      Return 0 on success and !0 on failure. */
  static int
 -_read_packet (struct rs_packet *pkt)
 +_read_message (struct rs_message *msg)
  {
    size_t n = 0;
    int err;
  
 -  rs_debug (("%s: trying to read %d octets of packet data\n", __func__,
 -           pkt->rpkt->length - RS_HEADER_LEN));
 +  rs_debug (("%s: trying to read %d octets of message data\n", __func__,
 +           msg->rpkt->length - RS_HEADER_LEN));
  
 -  n = bufferevent_read (pkt->conn->bev,
 -                      pkt->rpkt->data + RS_HEADER_LEN,
 -                      pkt->rpkt->length - RS_HEADER_LEN);
 +  n = bufferevent_read (msg->conn->base_.bev,
 +                      msg->rpkt->data + RS_HEADER_LEN,
 +                      msg->rpkt->length - RS_HEADER_LEN);
  
 -  rs_debug (("%s: read %ld octets of packet data\n", __func__, n));
 +  rs_debug (("%s: read %ld octets of message data\n", __func__, n));
  
 -  if (n == pkt->rpkt->length - RS_HEADER_LEN)
 +  if (n == msg->rpkt->length - RS_HEADER_LEN)
      {
 -      bufferevent_disable (pkt->conn->bev, EV_READ);
 -      rs_debug (("%s: complete packet read\n", __func__));
 -      pkt->flags &= ~RS_PACKET_HEADER_READ;
 -      memset (pkt->hdr, 0, sizeof(*pkt->hdr));
 +      bufferevent_disable (msg->conn->base_.bev, EV_READ);
 +      rs_debug (("%s: complete message read\n", __func__));
 +      msg->flags &= ~RS_MESSAGE_HEADER_READ;
 +      memset (msg->hdr, 0, sizeof(*msg->hdr));
  
 -      /* Checks done by rad_packet_ok:
 +      /* Checks done by nr_packet_ok:
         - lenghts (FIXME: checks really ok for tcp?)
         - invalid code field
         - attribute lengths >= 2
         - attribute sizes adding up correctly  */
 -      err = nr_packet_ok (pkt->rpkt);
 -      if (err != RSE_OK)
 +      err = nr_packet_ok (msg->rpkt);
 +      if (err)
-         return rs_err_conn_push_fl (msg->conn, err, __FILE__, __LINE__,
-                                     "invalid message");
+       {
+           rs_debug (("%s: %d: invalid packet\n", __func__, -err));
 -          rs_conn_disconnect (pkt->conn);
 -        return rs_err_conn_push_fl (pkt->conn, -err, __FILE__, __LINE__,
 -                                    "invalid packet");
 -      }
++          rs_conn_disconnect (msg->conn);
++          return rs_err_conn_push (msg->conn, -err, "invalid message");
++        }
  
  #if defined (DEBUG)
        /* Find out what happens if there's data left in the buffer.  */
        {
        size_t rest = 0;
 -      rest = evbuffer_get_length (bufferevent_get_input (pkt->conn->bev));
 +      rest =
 +          evbuffer_get_length (bufferevent_get_input (msg->conn->base_.bev));
        if (rest)
          rs_debug (("%s: returning with %d octets left in buffer\n", __func__,
                     rest));
        }
  #endif
  
 -      /* Hand over message to user.  This changes ownership of pkt.
 +      /* Hand over message to user.  This changes ownership of msg.
         Don't touch it afterwards -- it might have been freed.  */
 -      if (pkt->conn->callbacks.received_cb)
 -      pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data);
 +      if (msg->conn->callbacks.received_cb)
 +      msg->conn->callbacks.received_cb (msg, msg->conn->base_.user_data);
      }
    else if (n < 0)             /* Buffer frozen.  */
 -    rs_debug (("%s: buffer frozen when reading packet\n", __func__));
 -  else                                /* Short packet.  */
 +    rs_debug (("%s: buffer frozen when reading message\n", __func__));
 +  else                                /* Short message.  */
      rs_debug (("%s: waiting for another %d octets\n", __func__,
 -             pkt->rpkt->length - RS_HEADER_LEN - n));
 +             msg->rpkt->length - RS_HEADER_LEN - n));
  
    return 0;
  }
  
  /* The read callback for TCP.
  
 -   Read exactly one RADIUS message from BEV and store it in struct
 -   rs_packet passed in USER_DATA.
 +   Read exactly one RADIUS message from \a bev and store it in the
 +   struct rs_message passed in \a user_data.
  
     Inform upper layer about successful reception of received RADIUS
 -   message by invoking conn->callbacks.recevied_cb(), if !NULL.  */
 +   message by invoking conn->callbacks.recevied_cb(), if not NULL. */
  void
  tcp_read_cb (struct bufferevent *bev, void *user_data)
  {
 -  struct rs_packet *pkt = (struct rs_packet *) user_data;
 +  struct rs_message *msg = (struct rs_message *) user_data;
  
 -  assert (pkt);
 -  assert (pkt->conn);
 -  assert (pkt->rpkt);
 +  assert (msg);
 +  assert (msg->conn);
 +  assert (msg->rpkt);
  
 -  pkt->rpkt->sockfd = pkt->conn->fd;
 -  pkt->rpkt->vps = NULL;        /* FIXME: can this be done when initializing pkt? */
 +  msg->rpkt->sockfd = msg->conn->base_.fd;
 +  msg->rpkt->vps = NULL; /* FIXME: can this be done when initializing msg? */
  
    /* Read a message header if not already read, return if that
       fails. Read a message and have it dispatched to the user
  
       Room for improvement: Peek inside buffer (evbuffer_copyout()) to
       avoid the extra copying. */
 -  if ((pkt->flags & RS_PACKET_HEADER_READ) == 0)
 -    if (_read_header (pkt))
 -      return;                 /* Error.  */
 -  _read_packet (pkt);
 +  if ((msg->flags & RS_MESSAGE_HEADER_READ) == 0)
 +    if (_read_header (msg))
 +      return;                   /* Invalid header. */
-   if (_read_message (msg))
-     return;                     /* Invalid message. */
++  _read_message (msg);
  }
  
  void
  tcp_event_cb (struct bufferevent *bev, short events, void *user_data)
  {
 -  struct rs_packet *pkt = (struct rs_packet *) user_data;
 +  struct rs_message *msg = (struct rs_message *) user_data;
    struct rs_connection *conn = NULL;
    int sockerr = 0;
  #if defined (RS_ENABLE_TLS)
    struct rs_peer *p = NULL;
  #endif
  
 -  assert (pkt);
 -  assert (pkt->conn);
 -  conn = pkt->conn;
 +  assert (msg);
 +  assert (msg->conn);
 +  conn = msg->conn;
  #if defined (DEBUG)
 -  assert (pkt->conn->active_peer);
 +  assert (conn->active_peer);
    p = conn->active_peer;
  #endif
  
 -  conn->is_connecting = 0;
    if (events & BEV_EVENT_CONNECTED)
      {
 -      if (conn->tev)
 -      evtimer_del (conn->tev); /* Cancel connect timer.  */
 -      if (event_on_connect (conn, pkt))
 +      int err = -1;
 +
 +      if (conn_originating_p (conn)) /* We're a client. */
 +        {
 +          assert (conn->tev);
 +          if (conn->tev)
 +            evtimer_del (conn->tev); /* Cancel connect timer.  */
 +          err = event_on_connect_orig (conn, msg);
 +        }
 +      else                      /* We're a server. */
 +        {
 +          assert (conn->tev == NULL);
 +          err = event_on_connect_term (conn, msg);
 +        }
 +      if (err)
          {
            event_on_disconnect (conn);
            event_loopbreak (conn);
        }
        else
        {
 -        rs_debug (("%s: %d: %d (%s)\n", __func__, conn->fd, sockerr,
 +        rs_debug (("%s: %d: %d (%s)\n", __func__, conn->base_.fd, sockerr,
                     evutil_socket_error_to_string (sockerr)));
          rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__,
 -                             "%d: %d (%s)", conn->fd, sockerr,
 +                             "%d: %d (%s)", conn->base_.fd, sockerr,
                               evutil_socket_error_to_string (sockerr));
        }
  #if defined (RS_ENABLE_TLS)
        if (conn->tls_ssl)      /* FIXME: correct check?  */
        {
 -        for (tlserr = bufferevent_get_openssl_error (conn->bev);
 +        for (tlserr = bufferevent_get_openssl_error (conn->base_.bev);
               tlserr;
 -             tlserr = bufferevent_get_openssl_error (conn->bev))
 +             tlserr = bufferevent_get_openssl_error (conn->base_.bev))
            {
              rs_debug (("%s: openssl error: %s\n", __func__,
                         ERR_error_string (tlserr, NULL)));
  void
  tcp_write_cb (struct bufferevent *bev, void *ctx)
  {
 -  struct rs_packet *pkt = (struct rs_packet *) ctx;
 +  struct rs_message *msg = (struct rs_message *) ctx;
  
 -  assert (pkt);
 -  assert (pkt->conn);
 +  assert (msg);
 +  assert (msg->conn);
  
 -  if (pkt->conn->callbacks.sent_cb)
 -    pkt->conn->callbacks.sent_cb (pkt->conn->user_data);
 +  if (msg->conn->callbacks.sent_cb)
 +    msg->conn->callbacks.sent_cb (msg->conn->base_.user_data);
  }
  
  int
  tcp_init_connect_timer (struct rs_connection *conn)
  {
    assert (conn);
 +  assert (conn->base_.ctx);
  
    if (conn->tev)
      event_free (conn->tev);
 -  conn->tev = evtimer_new (conn->evb, event_conn_timeout_cb, conn);
 +  conn->tev = evtimer_new (conn->base_.ctx->evb, event_conn_timeout_cb, conn);
    if (!conn->tev)
      return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                "evtimer_new");
diff --combined lib/tests/Makefile.am
@@@ -1,16 -1,12 +1,15 @@@
  AUTOMAKE_OPTIONS = foreign
- INCLUDES = -I$(top_srcdir)/include
+ INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)
  AM_CFLAGS = -Wall -Werror -g
  
  TESTS = test-udp
  
 -check_PROGRAMS = test-udp udp-server
 +check_PROGRAMS = test-udp udp-server tls-server
  
- test_udp_SOURCES = test-udp.c udp.c
- test_udp_LDADD = ../libradsec.la -lcgreen -lm
+ test_udp_SOURCES = test-udp.c udp.c udp.h
+ test_udp_LDADD = ../libradsec.la -lcunit -lm
  
- udp_server_SOURCES = udp-server.c udp.c
+ udp_server_SOURCES = udp-server.c udp.c udp.h
 +
 +tls_server_SOURCES = server.c
 +tls_server_LDADD = ../libradsec.la
diff --combined lib/tls.c
+++ b/lib/tls.c
@@@ -1,10 -1,11 +1,11 @@@
- /* Copyright 2010,2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+ /* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
  #endif
  
+ #include <stdlib.h>
  #include <assert.h>
  #include <openssl/ssl.h>
  #include <openssl/err.h>
  static struct tls *
  _get_tlsconf (struct rs_connection *conn, const struct rs_realm *realm)
  {
 -  struct tls *c = rs_malloc (conn->ctx, sizeof (struct tls));
 +  struct tls *c = rs_calloc (conn->base_.ctx, 1, sizeof (struct tls));
  
    if (c)
      {
 -      memset (c, 0, sizeof (struct tls));
 +      assert (realm);
 +      /* _conn_open() should've picked a peer by now. */
 +      assert (conn->active_peer);
        /* TODO: Make sure old radsecproxy code doesn't free these all
         of a sudden, or strdup them.  */
        c->name = realm->name;
 -      c->cacertfile = realm->cacertfile;
 +      c->cacertfile = conn->active_peer->cacertfile;
        c->cacertpath = NULL;   /* NYI */
 -      c->certfile = realm->certfile;
 -      c->certkeyfile = realm->certkeyfile;
 +      c->certfile = conn->active_peer->certfile;
 +      c->certkeyfile = conn->active_peer->certkeyfile;
        c->certkeypwd = NULL;   /* NYI */
        c->cacheexpiry = 0;     /* NYI */
        c->crlcheck = 0;                /* NYI */
@@@ -56,11 -55,10 +57,11 @@@ psk_client_cb (SSL *ssl
  {
    struct rs_connection *conn = NULL;
    struct rs_credentials *cred = NULL;
 +  unsigned int secret_len;
  
    conn = SSL_get_ex_data (ssl, 0);
    assert (conn != NULL);
 -  cred = conn->active_peer->realm->transport_cred;
 +  cred = conn->active_peer->transport_cred;
    assert (cred != NULL);
    /* NOTE: Ignoring identity hint from server.  */
  
    switch (cred->secret_encoding)
      {
      case RS_KEY_ENCODING_UTF8:
 -      cred->secret_len = strlen (cred->secret);
 -      if (cred->secret_len > max_psk_len)
 +      secret_len = strlen (cred->secret);
 +      if (secret_len > max_psk_len)
          {
            rs_err_conn_push (conn, RSE_CRED, "PSK secret longer than max %d",
                              max_psk_len);
            return 0;
          }
 -      memcpy (psk, cred->secret, cred->secret_len);
 +      memcpy (psk, cred->secret, secret_len);
        break;
      case RS_KEY_ENCODING_ASCII_HEX:
        {
              BN_clear_free (bn);
              return 0;
            }
 -        cred->secret_len = BN_bn2bin (bn, psk);
 +        secret_len = BN_bn2bin (bn, psk);
          BN_clear_free (bn);
        }
        break;
        assert (!"unknown psk encoding");
      }
  
 -  return cred->secret_len;
 +  return secret_len;
  }
  #endif  /* RS_ENABLE_TLS_PSK */
  
@@@ -123,9 -121,8 +124,9 @@@ rs_tls_init (struct rs_connection *conn
    SSL *ssl = NULL;
    unsigned long sslerr = 0;
  
 -  assert (conn->ctx);
 -  ctx = conn->ctx;
 +  assert (conn->base_.ctx);
 +  ctx = conn->base_.ctx;
 +  assert (conn->active_peer);
  
    tlsconf = _get_tlsconf (conn, conn->active_peer->realm);
    if (!tlsconf)
      }
  
  #if defined RS_ENABLE_TLS_PSK
 -  if (conn->active_peer->realm->transport_cred != NULL)
 +  if (conn->active_peer->transport_cred != NULL)
      {
        SSL_set_psk_client_callback (ssl, psk_client_cb);
        SSL_set_ex_data (ssl, 0, conn);
@@@ -204,7 -201,7 +205,7 @@@ tls_verify_cert (struct rs_connection *
    struct in6_addr addr;
    const char *hostname = NULL;
  
 -  assert (conn->active_peer->conn == conn);
 +  assert (conn->active_peer != NULL);
    assert (conn->active_peer->hostname != NULL);
    hostname = conn->active_peer->hostname;
  
diff --combined lib/udp.c
+++ b/lib/udp.c
@@@ -1,5 -1,5 +1,5 @@@
 -/* Copyright 2011 NORDUnet A/S. All rights reserved.
 +/* Copyright 2011,2013 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+    See LICENSE for licensing information. */
  
  #if defined HAVE_CONFIG_H
  #include <config.h>
  #include "event.h"
  #include "compat.h"
  #include "udp.h"
 +#include "conn.h"
  
  /* Send one packet, the first in queue.  */
  static int
  _send (struct rs_connection *conn, int fd)
  {
    ssize_t r = 0;
 -  struct rs_packet *pkt = conn->out_queue;
 +  struct rs_message *msg = conn->out_queue;
  
 -  assert (pkt->rpkt);
 -  assert (pkt->rpkt->data);
 +  assert (msg->rpkt);
 +  assert (msg->rpkt->data);
  
    /* Send.  */
 -  r = compat_send (fd, pkt->rpkt->data, pkt->rpkt->length, 0);
 +  r = compat_send (fd, msg->rpkt->data, msg->rpkt->length, 0);
    if (r == -1)
      {
 -      int sockerr = evutil_socket_geterror (pkt->conn->fd);
 +      int sockerr = evutil_socket_geterror (msg->conn->fd);
        if (sockerr != EAGAIN)
 -      return rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__,
 +      return rs_err_conn_push_fl (msg->conn, RSE_SOCKERR, __FILE__, __LINE__,
                                    "%d: send: %d (%s)", fd, sockerr,
                                    evutil_socket_error_to_string (sockerr));
      }
  
 -  assert (r == pkt->rpkt->length);
 -  /* Unlink the packet.  */
 -  conn->out_queue = pkt->next;
 +  assert (r == msg->rpkt->length);
 +  /* Unlink the message.  */
 +  conn->out_queue = msg->next;
  
 -  /* If there are more packets in queue, add the write event again.  */
 -  if (pkt->conn->out_queue)
 +  /* If there are more messages in queue, add the write event again.  */
 +  if (msg->conn->out_queue)
      {
 -      r = event_add (pkt->conn->wev, NULL);
 +      r = event_add (msg->conn->base_.wev, NULL);
        if (r < 0)
 -      return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__,
 +      return rs_err_conn_push_fl (msg->conn, RSE_EVENT, __FILE__, __LINE__,
                                    "event_add: %s", evutil_gai_strerror (r));
        rs_debug (("%s: re-adding the write event\n", __func__));
      }
    return RSE_OK;
  }
  
 -/* Callback for conn->wev and conn->rev.  FIXME: Rename.
 +/** Callback for conn->wev and conn->rev.  FIXME: Rename.
  
 -   USER_DATA contains connection for EV_READ and a packet for
 -   EV_WRITE.  This is because we don't have a connect/establish entry
 -   point at the user level -- send implies connect so when we're
 -   connected we need the packet to send.  */
 +    \a user_data holds a message. */
  static void
  _evcb (evutil_socket_t fd, short what, void *user_data)
  {
    int err;
 -  struct rs_packet *pkt = (struct rs_packet *) user_data;
 +  struct rs_message *msg = (struct rs_message *) user_data;
-   assert (msg);
-   assert (msg->conn);
  
    rs_debug (("%s: fd=%d what =", __func__, fd));
-   if (what & EV_TIMEOUT) rs_debug ((" TIMEOUT"));
+   if (what & EV_TIMEOUT) rs_debug ((" TIMEOUT -- shouldn't happen!"));
    if (what & EV_READ) rs_debug ((" READ"));
    if (what & EV_WRITE) rs_debug ((" WRITE"));
    rs_debug (("\n"));
  
 -  assert (pkt);
 -  assert (pkt->conn);
++  assert (msg);
++  assert (msg->conn);
    if (what & EV_READ)
      {
 -      /* Read a single UDP packet and stick it in USER_DATA.  */
 +      /* Read a single UDP packet and stick it in the struct
 +         rs_message passed in user_data. */
        /* TODO: Verify that unsolicited packets are dropped.  */
        ssize_t r = 0;
 +      assert (msg->rpkt);
 +      assert (msg->rpkt->data);
  
 -      assert (pkt->rpkt->data);
 -
 -      r = compat_recv (fd, pkt->rpkt->data, RS_MAX_PACKET_LEN, MSG_TRUNC);
 +      r = compat_recv (fd, msg->rpkt->data, RS_MAX_PACKET_LEN, MSG_TRUNC);
        if (r == -1)
        {
 -        int sockerr = evutil_socket_geterror (pkt->conn->fd);
 +        int sockerr = evutil_socket_geterror (msg->conn->fd);
          if (sockerr == EAGAIN)
            {
              /* FIXME: Really shouldn't happen since we've been told
                 that fd is readable!  */
              rs_debug (("%s: EAGAIN reading UDP packet -- wot?"));
-             return;
+               goto err_out;
            }
  
          /* Hard error.  */
 -        rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__,
 -                             "%d: recv: %d (%s)", fd, sockerr,
 -                             evutil_socket_error_to_string (sockerr));
 -        event_del (pkt->conn->tev);
 +        rs_err_conn_push (msg->conn, RSE_SOCKERR,
 +                            "%d: recv: %d (%s)", fd, sockerr,
 +                            evutil_socket_error_to_string (sockerr));
 +        event_del (msg->conn->tev);
-         return;
+           goto err_out;
        }
 -      event_del (pkt->conn->tev);
 +      event_del (msg->conn->tev);
        if (r < 20 || r > RS_MAX_PACKET_LEN)    /* Short or long packet.  */
        {
 -        rs_err_conn_push (pkt->conn, RSE_INVALID_PKT,
 -                            "invalid packet length: %d", r);
 +        rs_err_conn_push (msg->conn, RSE_INVALID_MSG,
-                           "invalid message length: %d",
-                           msg->rpkt->length);
-         return;
++                          "invalid message length: %d", r);
+           goto err_out;
        }
 -      pkt->rpkt->length = (pkt->rpkt->data[2] << 8) + pkt->rpkt->data[3];
 -      err = nr_packet_ok (pkt->rpkt);
 +      msg->rpkt->length = (msg->rpkt->data[2] << 8) + msg->rpkt->data[3];
 +      err = nr_packet_ok (msg->rpkt);
        if (err)
        {
-         rs_err_conn_push_fl (msg->conn, err, __FILE__, __LINE__,
-                              "invalid message");
-         return;
 -        rs_err_conn_push_fl (pkt->conn, -err, __FILE__, __LINE__,
 -                             "invalid packet");
++        rs_err_conn_push (msg->conn, -err, "invalid message");
+           goto err_out;
        }
 -      /* Hand over message to user.  This changes ownership of pkt.
 +      /* Hand over message to user.  This changes ownership of msg.
         Don't touch it afterwards -- it might have been freed.  */
 -      if (pkt->conn->callbacks.received_cb)
 -      pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data);
 +      if (msg->conn->callbacks.received_cb)
 +      msg->conn->callbacks.received_cb (msg, msg->conn->base_.user_data);
      }
    else if (what & EV_WRITE)
      {
 -      if (!pkt->conn->is_connected)
 -      event_on_connect (pkt->conn, pkt);
 -
 -      if (pkt->conn->out_queue)
 -      if (_send (pkt->conn, fd) == RSE_OK)
 -        if (pkt->conn->callbacks.sent_cb)
 -          pkt->conn->callbacks.sent_cb (pkt->conn->user_data);
 +      if (conn_originating_p (msg->conn))
 +        {
 +          /* We're a client. */
 +          if (msg->conn->state == RS_CONN_STATE_CONNECTING)
 +            event_on_connect_orig (msg->conn, msg);
 +        }
 +      else
 +        {
 +          /* We're a server. */
 +          rs_debug (("%s: write event on terminating conn %p\n",
 +                     __func__, msg->conn));
 +        }
 +
 +      if (msg->conn->out_queue)
 +      if (_send (msg->conn, fd) == RSE_OK)
 +        if (msg->conn->callbacks.sent_cb)
 +          msg->conn->callbacks.sent_cb (msg->conn->base_.user_data);
      }
+   return;
  
- #if defined (DEBUG)
-   if (what & EV_TIMEOUT)
-     rs_debug (("%s: timeout on UDP event, shouldn't happen\n", __func__));
- #endif
+  err_out:
 -  rs_conn_disconnect (pkt->conn);
++  rs_conn_disconnect (msg->conn);
  }
  
  int
 -udp_init (struct rs_connection *conn, struct rs_packet *pkt)
 +udp_init (struct rs_connection *conn, struct rs_message *msg)
  {
 -  assert (!conn->bev);
 -
 -  conn->rev = event_new (conn->evb, conn->fd, EV_READ|EV_PERSIST, _evcb, NULL);
 -  conn->wev = event_new (conn->evb, conn->fd, EV_WRITE, _evcb, NULL);
 -  if (!conn->rev || !conn->wev)
 +  assert (!conn->base_.bev);
 +
 +  /* FIXME: Explain why we set EV_PERSIST on the read event but not on
 +     the write event. */
 +  conn->base_.rev = event_new (conn->base_.ctx->evb, conn->base_.fd,
 +                               EV_READ|EV_PERSIST, _evcb, NULL);
 +  conn->base_.wev = event_new (conn->base_.ctx->evb, conn->base_.fd,
 +                               EV_WRITE, _evcb, NULL);
 +  if (!conn->base_.rev || !conn->base_.wev)
      {
 -      if (conn->rev)
 +      if (conn->base_.rev)
        {
 -        event_free (conn->rev);
 -        conn->rev = NULL;
 +        event_free (conn->base_.rev);
 +        conn->base_.rev = NULL;
        }
        /* ENOMEM _or_ EINVAL but EINVAL only if we use EV_SIGNAL, at
         least for now (libevent-2.0.5).  */
@@@ -178,13 -163,10 +175,13 @@@ in
  udp_init_retransmit_timer (struct rs_connection *conn)
  {
    assert (conn);
 +  assert (conn->base_.ctx);
 +  assert (conn->base_.ctx->evb);
  
    if (conn->tev)
      event_free (conn->tev);
 -  conn->tev = evtimer_new (conn->evb, event_retransmit_timeout_cb, conn);
 +  conn->tev =
 +    evtimer_new (conn->base_.ctx->evb, event_retransmit_timeout_cb, conn);
    if (!conn->tev)
      return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__,
                                "evtimer_new");
diff --combined lib/udp.h
+++ b/lib/udp.h
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright 2011 NORDUnet A/S. All rights reserved.
-    See LICENSE for licensing information.  */
+    See LICENSE for licensing information. */
  
 -int udp_init (struct rs_connection *conn, struct rs_packet *pkt);
 +int udp_init (struct rs_connection *conn, struct rs_message *msg);
  int udp_init_retransmit_timer (struct rs_connection *conn);