Allow building WITHOUT_STATS
[freeradius.git] / src / main / listen.c
index 4abe3a7..b6e2b4d 100644 (file)
  *   along with this program; if not, write to the Free Software
  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright 2005  The FreeRADIUS server project
+ * Copyright 2005,2006  The FreeRADIUS server project
  * Copyright 2005  Alan DeKok <aland@ox.org>
  */
 
-#include <freeradius-devel/autoconf.h>
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
 
-#include <stdlib.h>
-#include <string.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+#include <freeradius-devel/vqp.h>
+#include <freeradius-devel/dhcp.h>
 
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#endif
+#include <freeradius-devel/vmps.h>
+#include <freeradius-devel/detail.h>
 
-#ifdef HAVE_ARPA_INET_H
-#include <arpa/inet.h>
+#ifdef WITH_UDPFROMTO
+#include <freeradius-devel/udpfromto.h>
 #endif
 
+#ifdef HAVE_SYS_RESOURCE_H
 #include <sys/resource.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
+#endif
 
 #ifdef HAVE_NET_IF_H
 #include <net/if.h>
 #endif
 
-#ifdef HAVE_SYS_STAT_H
-#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
 #endif
 
-#ifdef WITH_UDPFROMTO
-#include <freeradius-devel/udpfromto.h>
-#endif
 
-#include <fcntl.h>
+void print_packet(RADIUS_PACKET *packet)
+{
+       char src[256], dst[256];
 
-#include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/rad_assert.h>
-#include <freeradius-devel/conffile.h>
-#include <freeradius-devel/token.h>
+       ip_ntoh(&packet->src_ipaddr, src, sizeof(src));
+       ip_ntoh(&packet->dst_ipaddr, dst, sizeof(dst));
 
-#include <freeradius-devel/radius_snmp.h>
-#include <freeradius-devel/request_list.h>
+       fprintf(stderr, "ID %d: %s %d -> %s %d\n", packet->id,
+               src, packet->src_port, dst, packet->dst_port);
 
-static time_t start_time = 0;
+}
 
-/*
- *     FIXME: Delete this crap!
- */
-extern time_t time_now;
 
 /*
  *     We'll use this below.
  */
-typedef int (*rad_listen_parse_t)(const char *, int, const CONF_SECTION *, rad_listen_t *);
+typedef int (*rad_listen_parse_t)(CONF_SECTION *, rad_listen_t *);
 typedef void (*rad_listen_free_t)(rad_listen_t *);
 
 typedef struct rad_listen_master_t {
@@ -80,1565 +74,2106 @@ typedef struct rad_listen_master_t {
        rad_listen_free_t       free;
        rad_listen_recv_t       recv;
        rad_listen_send_t       send;
-       rad_listen_update_t     update;
        rad_listen_print_t      print;
+       rad_listen_encode_t     encode;
+       rad_listen_decode_t     decode;
 } rad_listen_master_t;
 
-typedef struct listen_socket_t {
-       /*
-        *      For normal sockets.
-        */
-       lrad_ipaddr_t   ipaddr;
-       int             port;
-       RADCLIENT_LIST  *clients;
-} listen_socket_t;
-
-typedef struct listen_detail_t {
-       const char      *detail;
-       VALUE_PAIR      *vps;
-       FILE            *fp;
-       int             state;
-       time_t          timestamp;
-       lrad_ipaddr_t   client_ip;
-       int             max_outstanding;
-       int             *outstanding;
-} listen_detail_t;
-                              
+static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type);
 
 /*
  *     Find a per-socket client.
  */
-static RADCLIENT *client_listener_find(const rad_listen_t *listener,
-                                      const lrad_ipaddr_t *ipaddr)
+RADCLIENT *client_listener_find(const rad_listen_t *listener,
+                               const fr_ipaddr_t *ipaddr, int src_port)
 {
-       const RADCLIENT_LIST *clients;
+#ifdef WITH_DYNAMIC_CLIENTS
+       int rcode;
+       REQUEST *request;
+       RADCLIENT *created;
+#endif
+       time_t now;
+       RADCLIENT *client;
+       RADCLIENT_LIST *clients;
+       listen_socket_t *sock;
 
        rad_assert(listener != NULL);
        rad_assert(ipaddr != NULL);
 
-       rad_assert((listener->type == RAD_LISTEN_AUTH) ||
-                  (listener->type == RAD_LISTEN_ACCT));
-
-       clients = ((listen_socket_t *)listener->data)->clients;
-       if (!clients) clients = mainconfig.clients;
-
-       rad_assert(clients != NULL);
-       
-       return client_find(clients, ipaddr);
-}
-
-static int listen_bind(rad_listen_t *this);
-
-/*
- *     FIXME: have the detail reader use another config "exit when done",
- *     so that it can be used as a one-off tool to update stuff.
- */
-
-/*
- *     Process and reply to a server-status request.
- *     Like rad_authenticate and rad_accounting this should
- *     live in it's own file but it's so small we don't bother.
- */
-static int rad_status_server(REQUEST *request)
-{
-       char            reply_msg[64];
-       time_t          t;
-       VALUE_PAIR      *vp;
+       sock = listener->data;
+       clients = sock->clients;
 
        /*
-        *      Reply with an ACK. We might want to add some more
-        *      interesting reply attributes, such as server uptime.
+        *      This HAS to have been initialized previously.
         */
-       t = request->timestamp - start_time;
-       sprintf(reply_msg, "FreeRADIUS up %d day%s, %02d:%02d",
-               (int)(t / 86400), (t / 86400) == 1 ? "" : "s",
-               (int)((t / 3600) % 24), (int)(t / 60) % 60);
-       request->reply->code = PW_AUTHENTICATION_ACK;
+       rad_assert(clients != NULL);
 
-       vp = pairmake("Reply-Message", reply_msg, T_OP_SET);
-       pairadd(&request->reply->vps, vp); /* don't need to check if !vp */
+       client = client_find(clients, ipaddr,sock->proto);
+       if (!client) {
+               char name[256], buffer[128];
+                                       
+#ifdef WITH_DYNAMIC_CLIENTS
+       unknown:                /* used only for dynamic clients */
+#endif
 
-       return 0;
-}
+               /*
+                *      DoS attack quenching, but only in daemon mode.
+                *      If they're running in debug mode, show them
+                *      every packet.
+                */
+               if (debug_flag == 0) {
+                       static time_t last_printed = 0;
 
-static int request_num_counter = 0;
+                       now = time(NULL);
+                       if (last_printed == now) return NULL;
+                       
+                       last_printed = now;
+               }
 
-/*
- *     Check for dups, etc.  Common to Access-Request &&
- *     Accounting-Request packets.
- */
-static int common_checks(rad_listen_t *listener,
-                        RADIUS_PACKET *packet, REQUEST **prequest,
-                        const RADCLIENT *client)
-{
-       REQUEST *curreq;
-       char buffer[128];
+               listener->print(listener, name, sizeof(name));
+
+               radlog(L_ERR, "Ignoring request to %s from unknown client %s port %d"
+#ifdef WITH_TCP
+                      " proto %s"
+#endif
+                      , name, inet_ntop(ipaddr->af, &ipaddr->ipaddr,
+                                        buffer, sizeof(buffer)), src_port
+#ifdef WITH_TCP
+                      , (sock->proto == IPPROTO_UDP) ? "udp" : "tcp"
+#endif
+                      );
+               return NULL;
+       }
 
-       rad_assert(listener->rl != NULL);
+#ifndef WITH_DYNAMIC_CLIENTS
+       return client;          /* return the found client. */
+#else
 
        /*
-        *      If there is no existing request of id, code, etc.,
-        *      then we can return, and let it be processed.
+        *      No server defined, and it's not dynamic.  Return it.
         */
-       if ((curreq = rl_find(listener->rl, packet)) == NULL) {
-               /*
-                *      Count the total number of requests, to see if
-                *      there are too many.  If so, return with an
-                *      error.
-                */
-               if (mainconfig.max_requests) {
-                       /*
-                        *      FIXME: This is now per-socket,
-                        *      when it should really be global
-                        *      to the server!
-                        */
-                       int request_count = rl_num_requests(listener->rl);
-
-                       /*
-                        *      This is a new request.  Let's see if
-                        *      it makes us go over our configured
-                        *      bounds.
-                        */
-                       if (request_count > mainconfig.max_requests) {
-                               radlog(L_ERR, "Dropping request (%d is too many): "
-                                      "from client %s port %d - ID: %d", request_count,
-                                      client->shortname,
-                                      packet->src_port, packet->id);
-                               radlog(L_INFO, "WARNING: Please check the %s file.\n"
-                                      "\tThe value for 'max_requests' is probably set too low.\n", mainconfig.radiusd_conf);
-                               return 0;
-                       } /* else there were a small number of requests */
-               } /* else there was no configured limit for requests */
-
-               /*
-                *      FIXME: Add checks for system load.  If the
-                *      system is busy, start dropping requests...
-                *
-                *      We can probably keep some statistics
-                *      ourselves...  if there are more requests
-                *      coming in than we can handle, start dropping
-                *      some.
-                */
+       if (!client->client_server && !client->dynamic) return client;
 
+       now = time(NULL);
+       
        /*
-        *      The current request isn't finished, which
-        *      means that the NAS sent us a new packet, while
-        *      we are still processing the old request.
+        *      It's a dynamically generated client, check it.
         */
-       } else if (!curreq->finished) {
+       if (client->dynamic && (src_port != 0)) {
                /*
-                *      If the authentication vectors are identical,
-                *      then the NAS is re-transmitting it, trying to
-                *      kick us into responding to the request.
+                *      Lives forever.  Return it.
                 */
-               if (memcmp(curreq->packet->vector, packet->vector,
-                          sizeof(packet->vector)) == 0) {
-                       RAD_SNMP_INC(rad_snmp.auth.total_dup_requests);
-
-                       /*
-                        *      It's not finished because the request
-                        *      was proxied, but there was no reply
-                        *      from the home server.
-                        *
-                        *      This code will never get hit for
-                        *      accounting packets, as they're always
-                        *      updated, and never re-transmitted.
-                        */
-                       if (curreq->proxy && !curreq->proxy_reply) {
-                               DEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d",
-                                      inet_ntop(curreq->proxy->dst_ipaddr.af,
-                                                &curreq->proxy->dst_ipaddr.ipaddr,
-                                                buffer, sizeof(buffer)),                                              curreq->proxy->dst_port,
-                                      
-                                      curreq->proxy->id);
-                               listener->send(curreq->proxy_listener, curreq);
-                               return 0;
-                       } /* else the packet was not proxied */
-
-                       /*
-                        *      Someone's still working on it, so we
-                        *      ignore the duplicate request.
-                        */
-                       radlog(L_ERR, "Discarding duplicate request from "
-                              "client %s port %d - ID: %d due to unfinished request %d",
-                              client->shortname,
-                              packet->src_port, packet->id,
-                              curreq->number);
-                       return 0;
-               } /* else the authentication vectors were different */
+               if (client->lifetime == 0) return client;
+               
+               /*
+                *      Rate-limit the deletion of known clients.
+                *      This makes them last a little longer, but
+                *      prevents the server from melting down if (say)
+                *      10k clients all expire at once.
+                */
+               if (now == client->last_new_client) return client;
 
                /*
-                *      We're waiting for a proxy reply, but no one is
-                *      currently processing the request.  We can
-                *      discard the old request, and ignore any reply
-                *      from the home server, because the NAS will
-                *      never care...
+                *      It's not dead yet.  Return it.
                 */
-               if (curreq->proxy && !curreq->proxy_reply &&
-                   (curreq->child_pid == NO_SUCH_CHILD_PID)) {
-                       radlog(L_ERR, "Discarding old proxied request %d from "
-                              "client %s port %d - ID: %d due to new request from the client",
-                              curreq->number, client->shortname,
-                              packet->src_port, packet->id);
-               } else {
-                       /*
-                        *      The authentication vectors are different, so
-                        *      the NAS has given up on us, as we've taken too
-                        *      long to process the request.  This is a
-                        *      SERIOUS problem!
-                        */
-                       RAD_SNMP_TYPE_INC(listener, total_packets_dropped);
-                       
-                       radlog(L_ERR, "Dropping conflicting packet from "
-                              "client %s port %d - ID: %d due to unfinished request %d",
-                              client->shortname,
-                              packet->src_port, packet->id,
-                              curreq->number);
-                       return 0;
-               }
+               if ((client->created + client->lifetime) > now) return client;
                
                /*
-                *      The old request is finished.  We now check the
-                *      authentication vectors.  If the client has sent us a
-                *      request with identical code && ID, but different
-                *      vector, then they MUST have gotten our response, so we
-                *      can delete the original request, and process the new
-                *      one.
-                *
-                *      If the vectors are the same, then it's a duplicate
-                *      request, and we can send a duplicate reply.
+                *      This really puts them onto a queue for later
+                *      deletion.
                 */
-       } else if (memcmp(curreq->packet->vector, packet->vector,
-                         sizeof(packet->vector)) == 0) {
-               RAD_SNMP_INC(rad_snmp.auth.total_dup_requests);
+               client_delete(clients, client);
 
                /*
-                *      If the packet has been delayed, then silently
-                *      send a response, and clear the delayed flag.
-                *
-                *      Note that this means if the NAS kicks us while
-                *      we're delaying a reject, then the reject may
-                *      be sent sooner than otherwise.
-                *
-                *      This COULD be construed as a bug.  Maybe what
-                *      we want to do is to ignore the duplicate
-                *      packet, and send the reject later.
+                *      Go find the enclosing network again.
                 */
-               if (curreq->options & RAD_REQUEST_OPTION_DELAYED_REJECT) {
-                       curreq->options &= ~RAD_REQUEST_OPTION_DELAYED_REJECT;
-                       rad_assert(curreq->listener == listener);
-                       listener->send(listener, curreq);
-                       return 0;
-               }
+               client = client_find(clients, ipaddr, sock->proto);
 
                /*
-                *      Maybe we've saved a reply packet.  If so,
-                *      re-send it.  Otherwise, just complain.
+                *      WTF?
                 */
-               if (curreq->reply->code != 0) {
-                       DEBUG2("Sending duplicate reply "
-                              "to client %s port %d - ID: %d",
-                              client->shortname,
-                              packet->src_port, packet->id);
-                       rad_assert(curreq->listener == listener);
-                       listener->send(listener, curreq);
-                       return 0;
-               }
+               if (!client) goto unknown;
+               if (!client->client_server) goto unknown;
 
                /*
-                *      Else we never sent a reply to the NAS,
-                *      as we decided somehow we didn't like the request.
-                *
-                *      This shouldn't happen, in general...
+                *      At this point, 'client' is the enclosing
+                *      network that configures where dynamic clients
+                *      can be defined.
                 */
-               DEBUG2("Discarding duplicate request from client %s port %d - ID: %d",
-                      client->shortname, packet->src_port, packet->id);
-               return 0;
-       } /* else the vectors were different, so we discard the old request. */
+               rad_assert(client->dynamic == 0);
 
-       /*
-        *      'packet' has the same source IP, source port, code,
-        *      and Id as 'curreq', but a different authentication
-        *      vector.  We can therefore delete 'curreq', as we were
-        *      only keeping it around to send out duplicate replies,
-        *      if the first reply got lost in the network.
-        */
-       if (curreq) {
-               rl_yank(listener->rl, curreq);
-               request_free(&curreq);
+       } else if (!client->dynamic && client->rate_limit) {
+               /*
+                *      The IP is unknown, so we've found an enclosing
+                *      network.  Enable DoS protection.  We only
+                *      allow one new client per second.  Known
+                *      clients aren't subject to this restriction.
+                */
+               if (now == client->last_new_client) goto unknown;
        }
 
-       /*
-        *      A unique per-request counter.
-        */
-       curreq = request_alloc(); /* never fails */
-       
-       if ((curreq->reply = rad_alloc(0)) == NULL) {
-               radlog(L_ERR, "No memory");
-               exit(1);
-       }
-       curreq->listener = listener;
-       curreq->packet = packet;
-       curreq->packet->timestamp = curreq->timestamp;
-       curreq->number = request_num_counter++;
-       strNcpy(curreq->secret, client->secret, sizeof(curreq->secret));
-       
-       /*
-        *      Remember the request in the list.
-        */
-       if (!rl_add(listener->rl, curreq)) {
-               radlog(L_ERR, "Failed to insert request %d in the list of live requests: discarding", curreq->number);
-               request_free(&curreq);
-               return 0;
+       client->last_new_client = now;
+
+       request = request_alloc();
+       if (!request) goto unknown;
+
+       request->listener = listener;
+       request->client = client;
+       request->packet = rad_recv(listener->fd, 0x02); /* MSG_PEEK */
+       if (!request->packet) {                         /* badly formed, etc */
+               request_free(&request);
+               goto unknown;
        }
-       
-       /*
-        *      The request passes many of our sanity checks.
-        *      From here on in, if anything goes wrong, we
-        *      send a reject message, instead of dropping the
-        *      packet.
-        */
+       request->reply = rad_alloc_reply(request->packet);
+       if (!request->reply) {
+               request_free(&request);
+               goto unknown;
+       }
+       request->packet->timestamp = request->timestamp;
+       request->number = 0;
+       request->priority = listener->type;
+       request->server = client->client_server;
+       request->root = &mainconfig;
 
        /*
-        *      Build the reply template from the request.
+        *      Run a fake request through the given virtual server.
+        *      Look for FreeRADIUS-Client-IP-Address
+        *               FreeRADIUS-Client-Secret
+        *              ...
+        *
+        *      and create the RADCLIENT structure from that.
         */
+       DEBUG("server %s {", request->server);
 
-       curreq->reply->sockfd = curreq->packet->sockfd;
-       curreq->reply->dst_ipaddr = curreq->packet->src_ipaddr;
-       curreq->reply->src_ipaddr = curreq->packet->dst_ipaddr;
-       curreq->reply->dst_port = curreq->packet->src_port;
-       curreq->reply->src_port = curreq->packet->dst_port;
-       curreq->reply->id = curreq->packet->id;
-       curreq->reply->code = 0; /* UNKNOWN code */
-       memcpy(curreq->reply->vector, curreq->packet->vector,
-              sizeof(curreq->reply->vector));
-       curreq->reply->vps = NULL;
-       curreq->reply->data = NULL;
-       curreq->reply->data_len = 0;
-
-       *prequest = curreq;
-       return 1;
-}
+       rcode = module_authorize(0, request);
 
+       DEBUG("} # server %s", request->server);
 
-static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize)
-{
-       size_t len;
-       listen_socket_t *sock = this->data;
+       if (rcode != RLM_MODULE_OK) {
+               request_free(&request);
+               goto unknown;
+       }
 
-       if ((sock->ipaddr.af == AF_INET) &&
-           (sock->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) {
-               strcpy(buffer, "*");
+       /*
+        *      If the client was updated by rlm_dynamic_clients,
+        *      don't create the client from attribute-value pairs.
+        */
+       if (request->client == client) {
+               created = client_create(clients, request);
        } else {
-               ip_ntoh(&sock->ipaddr, buffer, bufsize);
+               created = request->client;
+
+               /*
+                *      This frees the client if it isn't valid.
+                */
+               if (!client_validate(clients, client, created)) goto unknown;
        }
+       request_free(&request);
 
-       len = strlen(buffer);
+       if (!created) goto unknown;
 
-       return len + snprintf(buffer + len, bufsize - len, " port %d",
-                             sock->port);
+       return created;
+#endif
 }
 
+static int listen_bind(rad_listen_t *this);
+
 
 /*
- *     Parse an authentication or accounting socket.
+ *     Process and reply to a server-status request.
+ *     Like rad_authenticate and rad_accounting this should
+ *     live in it's own file but it's so small we don't bother.
  */
-static int common_socket_parse(const char *filename, int lineno,
-                            const CONF_SECTION *cs, rad_listen_t *this)
+static int rad_status_server(REQUEST *request)
 {
-       int             rcode;
-       int             listen_port;
-       lrad_ipaddr_t   ipaddr;
-       listen_socket_t *sock = this->data;
-       const char      *section_name = NULL;
-       CONF_SECTION    *client_cs;
+       int rcode = RLM_MODULE_OK;
+       DICT_VALUE *dval;
 
-       /*
-        *      Try IPv4 first
-        */
-       ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
-       rcode = cf_item_parse(cs, "ipaddr", PW_TYPE_IPADDR,
-                             &ipaddr.ipaddr.ip4addr, NULL);
-       if (rcode < 0) return -1;
-       
-       if (rcode == 0) { /* successfully parsed IPv4 */
-               ipaddr.af = AF_INET;
-               
-       } else {        /* maybe IPv6? */
-               rcode = cf_item_parse(cs, "ipv6addr", PW_TYPE_IPV6ADDR,
-                                     &ipaddr.ipaddr.ip6addr, NULL);
-               if (rcode < 0) return -1;
-               
-               if (rcode == 1) {
-                       radlog(L_ERR, "%s[%d]: No address specified in listen section",
-                              filename, lineno);
-                       return -1;
+       switch (request->listener->type) {
+#ifdef WITH_STATS
+       case RAD_LISTEN_NONE:
+#endif
+       case RAD_LISTEN_AUTH:
+               dval = dict_valbyname(PW_AUTZ_TYPE, 0, "Status-Server");
+               if (dval) {
+                       rcode = module_authorize(dval->value, request);
+               } else {
+                       rcode = RLM_MODULE_OK;
                }
-               ipaddr.af = AF_INET6;
-       }
-       
-       rcode = cf_item_parse(cs, "port", PW_TYPE_INTEGER,
-                             &listen_port, "0");
-       if (rcode < 0) return -1;
-       
-       sock->ipaddr = ipaddr;
-       sock->port = listen_port;
 
-       /*
-        *      And bind it to the port.
-        */
-       if (listen_bind(this) < 0) {
-               char buffer[128];
-               radlog(L_CONS|L_ERR, "%s[%d]: Error binding to port for %s port %d",
-                      filename, cf_section_lineno(cs),
-                      ip_ntoh(&sock->ipaddr, buffer, sizeof(buffer)),
-                      sock->port);
-               return -1;
-       }
+               switch (rcode) {
+               case RLM_MODULE_OK:
+               case RLM_MODULE_UPDATED:
+                       request->reply->code = PW_AUTHENTICATION_ACK;
+                       break;
 
-       /*
-        *      If we can bind to interfaces, do so,
-        *      else don't.
-        */
-       if (cf_pair_find(cs, "interface")) {
-#ifndef SO_BINDTODEVICE
-               radlog(L_CONS|L_ERR, "%s[%d]: System does not support binding to interfaces, delete this line from the configuration file.",
-                      filename, cf_section_lineno(cs));
-               return -1;
-#else
-               const char *value;
-               const CONF_PAIR *cp = cf_pair_find(cs, "interface");
-               struct ifreq ifreq;
+               case RLM_MODULE_FAIL:
+               case RLM_MODULE_HANDLED:
+                       request->reply->code = 0; /* don't reply */
+                       break;
 
-               rad_assert(cp != NULL);
-               value = cf_pair_value(cp);
-               rad_assert(value != NULL);
-               
-               strcpy(ifreq.ifr_name, value);
-       
-               if (setsockopt(this->fd, SOL_SOCKET, SO_BINDTODEVICE,
-                              (char *)&ifreq, sizeof(ifreq)) < 0) {
-                       radlog(L_CONS|L_ERR, "%s[%d]: Failed binding to interface %s: %s",
-                              filename, cf_section_lineno(cs),
-                              value, strerror(errno));
-                       return -1;
-               } /* else it worked. */
+               default:
+               case RLM_MODULE_REJECT:
+                       request->reply->code = PW_AUTHENTICATION_REJECT;
+                       break;
+               }
+               break;
+
+#ifdef WITH_ACCOUNTING
+       case RAD_LISTEN_ACCT:
+               dval = dict_valbyname(PW_ACCT_TYPE, 0, "Status-Server");
+               if (dval) {
+                       rcode = module_accounting(dval->value, request);
+               } else {
+                       rcode = RLM_MODULE_OK;
+               }
+
+               switch (rcode) {
+               case RLM_MODULE_OK:
+               case RLM_MODULE_UPDATED:
+                       request->reply->code = PW_ACCOUNTING_RESPONSE;
+                       break;
+
+               default:
+                       request->reply->code = 0; /* don't reply */
+                       break;
+               }
+               break;
 #endif
-       }
 
-       /*
-        *      Look for the name of a section that holds a list
-        *      of clients.
-        */
-       rcode = cf_item_parse(cs, "clients", PW_TYPE_STRING_PTR,
-                             &section_name, NULL);
-       if (rcode < 0) return -1; /* bad string */
-       if (rcode > 0) return 0; /* non-existent is OK. */
+#ifdef WITH_COA
+               /*
+                *      This is a vendor extension.  Suggested by Glen
+                *      Zorn in IETF 72, and rejected by the rest of
+                *      the WG.  We like it, so it goes in here.
+                */
+       case RAD_LISTEN_COA:
+               dval = dict_valbyname(PW_RECV_COA_TYPE, 0, "Status-Server");
+               if (dval) {
+                       rcode = module_recv_coa(dval->value, request);
+               } else {
+                       rcode = RLM_MODULE_OK;
+               }
 
-       client_cs = cf_section_find(section_name);
-       if (!client_cs) {
-               radlog(L_CONS|L_ERR, "%s[%d]: Failed to find client section %s",
-                      filename, cf_section_lineno(cs), section_name);
-               return -1;
+               switch (rcode) {
+               case RLM_MODULE_OK:
+               case RLM_MODULE_UPDATED:
+                       request->reply->code = PW_COA_ACK;
+                       break;
+
+               default:
+                       request->reply->code = 0; /* don't reply */
+                       break;
+               }
+               break;
+#endif
+
+       default:
+               return 0;
        }
 
-       sock->clients = clients_parse_section(filename, client_cs);
-       if (!sock->clients) {
-               return -1;
+#ifdef WITH_STATS
+       /*
+        *      Full statistics are available only on a statistics
+        *      socket.
+        */
+       if (request->listener->type == RAD_LISTEN_NONE) {
+               request_stats_reply(request);
        }
+#endif
 
        return 0;
 }
 
-/*
- *     Send an authentication response packet
- */
-static int auth_socket_send(rad_listen_t *listener, REQUEST *request)
+#ifdef WITH_TCP
+static int auth_tcp_recv(rad_listen_t *listener,
+                        RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
 {
-       rad_assert(request->listener == listener);
-       rad_assert(listener->send == auth_socket_send);
+       int rcode;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       listen_socket_t *sock = listener->data;
+       RADCLIENT       *client = sock->client;
 
        /*
-        *      Ensure that the reply is sane
+        *      Allocate a packet for partial reads.
         */
-       if (request->reply->code == 0) {
-               DEBUG2("There was no response configured: rejecting request %d", request->number);
-               request->reply->code = PW_AUTHENTICATION_REJECT;
+       if (!sock->packet) {
+               sock->packet = rad_alloc(0);
+               if (!sock->packet) return 0;
+
+               sock->packet->sockfd = listener->fd;
+               sock->packet->src_ipaddr = sock->other_ipaddr;
+               sock->packet->src_port = sock->other_port;
+               sock->packet->dst_ipaddr = sock->my_ipaddr;
+               sock->packet->dst_port = sock->my_port;
        }
+       
+       /*
+        *      Grab the packet currently being processed.
+        */
+       packet = sock->packet;
+
+       rcode = fr_tcp_read_packet(packet, 0);
 
        /*
-        *      If we're delaying authentication rejects, then
-        *      mark the request as delayed, and do NOT send a
-        *      response right now.
-        *
-        *      However, if it's already marked as delayed, then
-        *      send it now.
-        */
-       if ((request->reply->code == PW_AUTHENTICATION_REJECT) &&
-           ((request->options & RAD_REQUEST_OPTION_DELAYED_REJECT) == 0) &&
-           (mainconfig.reject_delay > 0) &&
-           ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0)) {
-               DEBUG2("Delaying request %d for %d seconds",
-                      request->number, mainconfig.reject_delay);
-               request->options |= RAD_REQUEST_OPTION_DELAYED_REJECT;
+        *      Still only a partial packet.  Put it back, and return,
+        *      so that we'll read more data when it's ready.
+        */
+       if (rcode == 0) {
                return 0;
        }
 
-       return rad_send(request->reply, request->packet, request->secret);
-}
+       if (rcode == -1) {      /* error reading packet */
+               char buffer[256];
 
+               radlog(L_ERR, "Invalid packet from %s port %d: closing socket",
+                      ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)),
+                      packet->src_port);
+       }
 
-/*
- *     Send an accounting response packet (or not)
- */
-static int acct_socket_send(rad_listen_t *listener, REQUEST *request)
-{
-       rad_assert(request->listener == listener);
-       rad_assert(listener->send == acct_socket_send);
-
-       /*
-        *      Accounting reject's are silently dropped.
-        *
-        *      We do it here to avoid polluting the rest of the
-        *      code with this knowledge
-        */
-       if (request->reply->code == 0) return 0;
-
-       return rad_send(request->reply, request->packet, request->secret);
-}
-
-
-/*
- *     Send a packet to a home server.
- *
- *     FIXME: have different code for proxy auth & acct!
- */
-static int proxy_socket_send(rad_listen_t *listener, REQUEST *request)
-{
-       listen_socket_t *sock = listener->data;
-
-       rad_assert(request->proxy_listener == listener);
-       rad_assert(listener->send == proxy_socket_send);
-
-       request->proxy->src_ipaddr = sock->ipaddr;
-       request->proxy->src_port = sock->port;
-
-       return rad_send(request->proxy, request->packet, request->proxysecret);
-}
+       if (rcode < 0) {        /* error or connection reset */
+               listener->status = RAD_LISTEN_STATUS_REMOVE_FD;
 
+               /*
+                *      Decrement the number of connections.
+                */
+               if (sock->parent->num_connections > 0) {
+                       sock->parent->num_connections--;
+               }
+               if (sock->client->num_connections > 0) {
+                       sock->client->num_connections--;
+               }
 
-/*
- *     Check if an incoming request is "ok"
- *
- *     It takes packets, not requests.  It sees if the packet looks
- *     OK.  If so, it does a number of sanity checks on it.
-  */
-static int auth_socket_recv(rad_listen_t *listener,
-                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
-{
-       RADIUS_PACKET   *packet;
-       RAD_REQUEST_FUNP fun = NULL;
-       char            buffer[128];
-       RADCLIENT       *client;
+               /*
+                *      Tell the event handler that an FD has disappeared.
+                */
+               DEBUG("Client has closed connection");
+               event_new_fd(listener);
 
-       packet = rad_recv(listener->fd);
-       if (!packet) {
-               radlog(L_ERR, "%s", librad_errstr);
+               /*
+                *      Do NOT free the listener here.  It's in use by
+                *      a request, and will need to hang around until
+                *      all of the requests are done.
+                *
+                *      It is instead free'd in remove_from_request_hash()
+                */
                return 0;
        }
 
-       RAD_SNMP_TYPE_INC(listener, total_requests); /* FIXME: auth specific */
-
-       if ((client = client_listener_find(listener,
-                                          &packet->src_ipaddr)) == NULL) {
-               RAD_SNMP_TYPE_INC(listener, total_invalid_requests);
-               
-               radlog(L_ERR, "Ignoring request from unknown client %s port %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port);
-               rad_free(&packet);
-               return 0;
-       }
+       RAD_STATS_TYPE_INC(listener, total_requests);
 
        /*
         *      Some sanity checks, based on the packet code.
         */
        switch(packet->code) {
        case PW_AUTHENTICATION_REQUEST:
+               RAD_STATS_CLIENT_INC(listener, client, total_requests);
                fun = rad_authenticate;
                break;
-               
+
        case PW_STATUS_SERVER:
                if (!mainconfig.status_server) {
-                       RAD_SNMP_TYPE_INC(listener, total_packets_dropped);
+                       RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+                       RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped);
                        DEBUG("WARNING: Ignoring Status-Server request due to security configuration");
-                       rad_free(&packet);
+                       rad_free(&sock->packet);
                        return 0;
                }
                fun = rad_status_server;
                break;
 
        default:
-               RAD_SNMP_INC(rad_snmp.auth.total_unknown_types);
-               
-               radlog(L_ERR, "Invalid packet code %d sent to authentication port from client %s port %d "
-                      "- ID %d : IGNORED",
-                      packet->code, client->shortname,
-                      packet->src_port, packet->id);
-               rad_free(&packet);
+               RAD_STATS_INC(radius_auth_stats.total_unknown_types);
+               RAD_STATS_CLIENT_INC(listener, client, total_unknown_types);
+
+               DEBUG("Invalid packet code %d sent to authentication port from client %s port %d : IGNORED",
+                     packet->code, client->shortname, packet->src_port);
+               rad_free(&sock->packet);
                return 0;
-               break;
        } /* switch over packet types */
-       
-       if (!common_checks(listener, packet, prequest, client)) {
-               rad_free(&packet);
+
+       if (!received_request(listener, packet, prequest, sock->client)) {
+               RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+               RAD_STATS_CLIENT_INC(listener, sock->client, total_packets_dropped);
+               rad_free(&sock->packet);
                return 0;
        }
 
        *pfun = fun;
+       sock->packet = NULL;    /* we have no need for more partial reads */
        return 1;
 }
 
-
-/*
- *     Receive packets from an accounting socket
- */
-static int acct_socket_recv(rad_listen_t *listener,
-       RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+static int auth_tcp_accept(rad_listen_t *listener,
+                          UNUSED RAD_REQUEST_FUNP *pfun,
+                          UNUSED REQUEST **prequest)
 {
-       RADIUS_PACKET   *packet;
-       RAD_REQUEST_FUNP fun = NULL;
-       char            buffer[128];
-       RADCLIENT       *client;
-       
-       packet = rad_recv(listener->fd);
-       if (!packet) {
-               radlog(L_ERR, "%s", librad_errstr);
-               return 0;
-       }
+       int newfd, src_port;
+       rad_listen_t *this;
+       socklen_t salen;
+       struct sockaddr_storage src;
+       listen_socket_t *sock;
+       fr_ipaddr_t src_ipaddr;
+       RADCLIENT *client;
        
-       RAD_SNMP_TYPE_INC(listener, total_requests); /* FIXME: acct-specific */
-
-       if ((client = client_listener_find(listener,
-                                          &packet->src_ipaddr)) == NULL) {
-               RAD_SNMP_TYPE_INC(listener, total_invalid_requests);
-               
-               radlog(L_ERR, "Ignoring request from unknown client %s port %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port);
-               rad_free(&packet);
-               return 0;
-       }
+       salen = sizeof(src);
 
-       switch(packet->code) {
-       case PW_ACCOUNTING_REQUEST:
-               fun = rad_accounting;
-               break;
-               
-       default:
+       DEBUG2(" ... new connection request on TCP socket.");
+       
+       newfd = accept(listener->fd, (struct sockaddr *) &src, &salen);
+       if (newfd < 0) {
                /*
-                *      FIXME: Update MIB for packet types?
+                *      Non-blocking sockets must handle this.
                 */
-               radlog(L_ERR, "Invalid packet code %d sent to a accounting port "
-                      "from client %s port %d - ID %d : IGNORED",
-                      packet->code, client->shortname,
-                      packet->src_port, packet->id);
-               rad_free(&packet);
-               return 0;
-       }
+               if (errno == EWOULDBLOCK) {
+                       return 0;
+               }
 
-       /*
-        *      FIXME: Accounting duplicates should be handled
-        *      differently than authentication duplicates.
-        */
-       if (!common_checks(listener, packet, prequest, client)) {
-               rad_free(&packet);
-               return 0;
+               DEBUG2(" ... failed to accept connection.");
+               return -1;
        }
 
-       *pfun = fun;
-       return 1;
-}
-
-
-/*
- *     Recieve packets from a proxy socket.
- */
-static int proxy_socket_recv(rad_listen_t *listener,
-                             RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
-{
-       REALM           *cl;
-       REQUEST         *oldreq;
-       RADIUS_PACKET   *packet;
-       RAD_REQUEST_FUNP fun = NULL;
-       char            buffer[128];
-       
-       packet = rad_recv(listener->fd);
-       if (!packet) {
-               radlog(L_ERR, "%s", librad_errstr);
+       if (!fr_sockaddr2ipaddr(&src, salen, &src_ipaddr, &src_port)) {
+               DEBUG2(" ... unknown address family.");
                return 0;
        }
 
        /*
-        *      Unsupported stuff
+        *      Enforce client IP address checks on accept, not on
+        *      every packet.
         */
-       if (packet->src_ipaddr.af != AF_INET) {
-               rad_assert("PROXY IPV6 NOT SUPPORTED" == NULL);
-       }
-       
-       /*
-        *      FIXME: Add support for home servers!
-        */
-       if ((cl = realm_findbyaddr(packet->src_ipaddr.ipaddr.ip4addr.s_addr,
-                                  packet->src_port)) == NULL) {
-               radlog(L_ERR, "Ignoring request from unknown home server %s port %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                              packet->src_port);
-               rad_free(&packet);
+       if ((client = client_listener_find(listener,
+                                          &src_ipaddr, src_port)) == NULL) {
+               close(newfd);
+               RAD_STATS_TYPE_INC(listener, total_invalid_requests);
                return 0;
        }
 
        /*
-        *      FIXME: Client MIB updates?
+        *      Enforce max_connectionsx on client && listen section.
         */
-       switch(packet->code) {
-       case PW_AUTHENTICATION_ACK:
-       case PW_ACCESS_CHALLENGE:
-       case PW_AUTHENTICATION_REJECT:
-               fun = rad_authenticate;
-               break;
-               
-       case PW_ACCOUNTING_RESPONSE:
-               fun = rad_accounting;
-               break;
-               
-       default:
+       if ((client->max_connections != 0) &&
+           (client->max_connections == client->num_connections)) {
                /*
-                *      FIXME: Update MIB for packet types?
+                *      FIXME: Print client IP/port, and server IP/port.
                 */
-               radlog(L_ERR, "Invalid packet code %d sent to a proxy port "
-                      "from home server %s port %d - ID %d : IGNORED",
-                      packet->code,
-                      ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)),
-                      packet->src_port, packet->id);
-               rad_free(&packet);
+               radlog(L_INFO, "Ignoring new connection due to client max_connections (%d)", client->max_connections);
+               close(newfd);
                return 0;
        }
 
-       /*
-        *      Find the original request in the request list
-        */
-       oldreq = rl_find_proxy(packet);
-
-       /*
-        *      If we haven't found the original request which was
-        *      sent, to get this reply.  Complain, and discard this
-        *      request, as there's no way for us to send it to a NAS.
-        */
-       if (!oldreq) {
-               radlog(L_PROXY, "No outstanding request was found for proxy reply from home server %s port %d - ID %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port, packet->id);
-               rad_free(&packet);
+       sock = listener->data;
+       if ((sock->max_connections != 0) &&
+           (sock->max_connections == sock->num_connections)) {
+               /*
+                *      FIXME: Print client IP/port, and server IP/port.
+                */
+               radlog(L_INFO, "Ignoring new connection due to socket max_connections");
+               close(newfd);
                return 0;
        }
+       client->num_connections++;
+       sock->num_connections++;
 
        /*
-        *      The proxy reply has arrived too late, as the original
-        *      (old) request has timed out, been rejected, and marked
-        *      as finished.  The client has already received a
-        *      response, so there is nothing that can be done. Delete
-        *      the tardy reply from the home server, and return nothing.
+        *      Add the new listener.
         */
-       if ((oldreq->reply->code != 0) ||
-           (oldreq->finished)) {
-               radlog(L_ERR, "Reply from home server %s port %d  - ID: %d arrived too late for request %d. Try increasing 'retry_delay' or 'max_request_time'",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port, packet->id,
-                      oldreq->number);
-               rad_free(&packet);
-               return 0;
-       }
+       this = listen_alloc(listener->type);
+       if (!this) return -1;
 
        /*
-        *      If there is already a reply, maybe this one is a
-        *      duplicate?
+        *      Copy everything, including the pointer to the socket
+        *      information.
         */
-       if (oldreq->proxy_reply) {
-               if (memcmp(oldreq->proxy_reply->vector,
-                          packet->vector,
-                          sizeof(oldreq->proxy_reply->vector)) == 0) {
-                       radlog(L_ERR, "Discarding duplicate reply from home server %s port %d  - ID: %d for request %d",
-                              inet_ntop(packet->src_ipaddr.af,
-                                        &packet->src_ipaddr.ipaddr,
-                                        buffer, sizeof(buffer)),
-                              packet->src_port, packet->id,
-                              oldreq->number);
-               } else {
-                       /*
-                        *      ? The home server gave us a new proxy
-                        *      reply, which doesn't match the old
-                        *      one.  Delete it.
-                        */
-                       DEBUG2("Ignoring conflicting proxy reply");
-               }
+       sock = this->data;
+       memcpy(this->data, listener->data, sizeof(*sock));
+       memcpy(this, listener, sizeof(*this));
+       this->next = NULL;
+       this->data = sock;      /* fix it back */
 
-               /*
-                *      We've already received a reply, so
-                *      we discard this one, as we don't want
-                *      to do duplicate work.
-                */
-               rad_free(&packet);
-               return 0;
-       } /* else there wasn't a proxy reply yet, so we can process it */
+       sock->parent = listener->data;
+       sock->other_ipaddr = src_ipaddr;
+       sock->other_port = src_port;
+       sock->client = client;
 
-       /*
-        *       Refresh the old request, and update it with the proxy
-        *       reply.
-        *
-        *      ? Can we delete the proxy request here?  * Is there
-        *      any more need for it?
-        *
-        *      FIXME: we probably shouldn't be updating the time
-        *      stamp here.
-        */
-       oldreq->timestamp = time_now;
-       oldreq->proxy_reply = packet;
+       this->fd = newfd;
+       this->status = RAD_LISTEN_STATUS_INIT;
+       this->recv = auth_tcp_recv;
 
        /*
-        *      FIXME: we should really verify the digest here,
-        *      before marking this packet as a valid response.
-        *
-        *      This is a security problem, I think...
+        *      FIXME: set O_NONBLOCK on the accept'd fd.
+        *      See djb's portability rants for details.
         */
 
        /*
-        *      Now that we've verified the packet IS actually from
-        *      that home server, and not forged, we can go mark the
-        *      entries for this home server as active.
-        *
-        *      If we had done this check in the 'find realm by IP address'
-        *      function, then an attacker could force us to use a home
-        *      server which was inactive, by forging reply packets
-        *      which didn't match any request.  We would think that
-        *      the reply meant the home server was active, would
-        *      re-activate the realms, and THEN bounce the packet
-        *      as garbage.
-        */
-       for (cl = mainconfig.realms; cl != NULL; cl = cl->next) {
-               if (oldreq->proxy_reply->src_ipaddr.af != cl->ipaddr.af) continue;
-               if (cl->ipaddr.af != AF_INET) continue; /* FIXME */
-
-               if (oldreq->proxy_reply->src_ipaddr.ipaddr.ip4addr.s_addr == cl->ipaddr.ipaddr.ip4addr.s_addr) {
-                       if (oldreq->proxy_reply->src_port == cl->auth_port) {
-                               cl->active = TRUE;
-                               cl->last_reply = oldreq->timestamp;
-                       } else if (oldreq->proxy_reply->src_port == cl->acct_port) {
-                               cl->acct_active = TRUE;
-                               cl->last_reply = oldreq->timestamp;
-                       }
-               }
-       }
-
-       rad_assert(fun != NULL);
-       *pfun = fun;
-       *prequest = oldreq;
+        *      Tell the event loop that we have a new FD.
+        *      This can be called from a child thread...
+        */
+       event_new_fd(this);
 
-       return 1;
+       return 0;
 }
+#endif
 
-#define STATE_UNOPENED (0)
-#define STATE_UNLOCKED (1)
-#define STATE_HEADER   (2)
-#define STATE_READING  (3)
-#define STATE_DONE     (4)
-#define STATE_WAITING  (5)
 
 /*
- *     If we're limiting outstanding packets, then mark the response
- *     as being sent.
+ *     This function is stupid and complicated.
  */
-static int detail_send(rad_listen_t *listener, REQUEST *request)
+static int socket_print(const rad_listen_t *this, char *buffer, size_t bufsize)
 {
-       listen_detail_t *data = listener->data;
+       size_t len;
+       listen_socket_t *sock = this->data;
+       const char *name;
 
-       rad_assert(request->listener == listener);
-       rad_assert(listener->send == detail_send);
+       switch (this->type) {
+#ifdef WITH_STATS
+       case RAD_LISTEN_NONE:   /* what a hack... */
+               name = "status";
+               break;
+#endif
+
+       case RAD_LISTEN_AUTH:
+               name = "authentication";
+               break;
+
+#ifdef WITH_ACCOUNTING
+       case RAD_LISTEN_ACCT:
+               name = "accounting";
+               break;
+#endif
+
+#ifdef WITH_PROXY
+       case RAD_LISTEN_PROXY:
+               name = "proxy";
+               break;
+#endif
+
+#ifdef WITH_VMPS
+       case RAD_LISTEN_VQP:
+               name = "vmps";
+               break;
+#endif
+
+#ifdef WITH_DHCP
+       case RAD_LISTEN_DHCP:
+               name = "dhcp";
+               break;
+#endif
 
-       if (request->simul_max >= 0) {
-               rad_assert(data->outstanding != NULL);
-               rad_assert(request->simul_max < data->max_outstanding);
+#ifdef WITH_COA
+       case RAD_LISTEN_COA:
+               name = "coa";
+               break;
+#endif
 
-               data->outstanding[request->simul_max] = 0;
+       default:
+               name = "??";
+               break;
        }
 
-       return 0;
-}
+#define FORWARD len = strlen(buffer); if (len >= (bufsize + 1)) return 0;buffer += len;bufsize -= len
+#define ADDSTRING(_x) strlcpy(buffer, _x, bufsize);FORWARD
 
+       ADDSTRING(name);
 
-/*
- *     Open the detail file..
- *
- *     FIXME: create it, if it's not already there, so that the main
- *     server select() will wake us up if there's anything to read.
- */
-static int detail_open(rad_listen_t *this)
-{
-       struct stat st;
-       char buffer[2048];
-       listen_detail_t *data = this->data;
+       if (sock->interface) {
+               ADDSTRING(" interface ");
+               ADDSTRING(sock->interface);
+       }
 
-       rad_assert(data->state == STATE_UNOPENED);
-       snprintf(buffer, sizeof(buffer), "%s.work", data->detail);
-       
-       /*
-        *      FIXME: Have "one-shot" configuration, where it
-        *      will read the detail file, and exit once it's
-        *      done.
-        *
-        *      FIXME: Try harder to open the detail file.
-        *      Maybe sleep for X usecs if it doesn't exist?
-        */
+#ifdef WITH_TCP
+       if (this->recv == auth_tcp_accept) {
+               ADDSTRING(" proto tcp");
+       }
+#endif
 
+#ifdef WITH_TCP
        /*
-        *      Open detail.work first, so we don't lose
-        *      accounting packets.  It's probably better to
-        *      duplicate them than to lose them.
-        *
-        *      Note that we're not writing to the file, but
-        *      we've got to open it for writing in order to
-        *      establish the lock, to prevent rlm_detail from
-        *      writing to it.
+        *      TCP sockets get printed a little differently, to make
+        *      it clear what's going on.
         */
-       this->fd = open(buffer, O_RDWR);
-       if (this->fd < 0) {
-               /*
-                *      Try reading the detail file.  If it
-                *      doesn't exist, we can't do anything.
-                *
-                *      Doing the stat will tell us if the file
-                *      exists, even if we don't have permissions
-                *      to read it.
-                */
-               if (stat(data->detail, &st) < 0) {
-                       return 0;
-               }
-               
-               /*
-                *      Open it BEFORE we rename it, just to
-                *      be safe...
-                */
-               this->fd = open(data->detail, O_RDWR);
-               if (this->fd < 0) {
-                       radlog(L_ERR, "Failed to open %s: %s",
-                              data->detail, strerror(errno));
-                       return 0;
+       if (sock->client) {
+               ADDSTRING(" from client (");
+               ip_ntoh(&sock->other_ipaddr, buffer, bufsize);
+               FORWARD;
+
+               ADDSTRING(", ");
+               snprintf(buffer, bufsize, "%d", sock->other_port);
+               FORWARD;
+               ADDSTRING(") -> (");
+
+               if ((sock->my_ipaddr.af == AF_INET) &&
+                   (sock->my_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) {
+                       strlcpy(buffer, "*", bufsize);
+               } else {
+                       ip_ntoh(&sock->my_ipaddr, buffer, bufsize);
                }
+               FORWARD;
                
-               /*
-                *      Rename detail to detail.work
-                */
-               if (rename(data->detail, buffer) < 0) {
-                       close(this->fd);
-                       this->fd = -1;
-                       return 0;
-               }
-       } /* else detail.work existed, and we opened it */
-       
-       rad_assert(data->vps == NULL);
-       
-       rad_assert(data->fp == NULL);
-       data->fp = fdopen(this->fd, "r");
-       if (!data->fp) {
-               radlog(L_ERR, "Failed to re-open %s: %s",
-                      data->detail, strerror(errno));
-               return 0;
-       }
-
-       data->state = STATE_UNLOCKED;
-
-       data->client_ip.af = AF_UNSPEC;
-       data->timestamp = 0;
-       
-       return 1;
-}
+               ADDSTRING(", ");
+               snprintf(buffer, bufsize, "%d", sock->my_port);
+               FORWARD;
 
-/*
- *     This is a bad hack, just so complaints have meaningful text.
- */
-static const RADCLIENT detail_client = {
-       {               /* ipaddr */
-               AF_INET,
-               {{ INADDR_NONE }}
-       },
-       32,
-       "<detail-file>",
-       "secret",
-       "UNKNOWN-CLIENT",
-       "other",
-       "",
-       "",
-       -1
-};
+               if (this->server) {
+                       ADDSTRING(", virtual-server=");
+                       ADDSTRING(this->server);
+               }
 
-static int detail_recv(rad_listen_t *listener,
-                      RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
-{
-       int             free_slot = -1;
-       char            key[256], value[1024];
-       VALUE_PAIR      *vp, **tail;
-       RADIUS_PACKET   *packet;
-       char            buffer[2048];
-       listen_detail_t *data = listener->data;
+               ADDSTRING(")");
 
-       if (data->state == STATE_UNOPENED) {
-               rad_assert(listener->fd < 0);
-               if (!detail_open(listener)) return 0;
+               return 1;
        }
-       rad_assert(listener->fd >= 0);
 
+#ifdef WITH_PROXY
        /*
-        *      Try to lock fd.  If we can't, return.  If we can,
-        *      continue.  This means that the server doesn't block
-        *      while waiting for the lock to open...
+        *      Maybe it's a socket that we opened to a home server.
         */
-       if (data->state == STATE_UNLOCKED) {
-               /*
-                *      Note that we do NOT block waiting for the
-                *      lock.  We've re-named the file above, so we've
-                *      already guaranteed that any *new* detail
-                *      writer will not be opening this file.  The
-                *      only purpose of the lock is to catch a race
-                *      condition where the execution "ping-pongs"
-                *      between radiusd & radrelay.
-                */
-               if (rad_lockfd_nonblock(listener->fd, 0) < 0) {
-                       return 0;
+       if ((sock->proto == IPPROTO_TCP) &&
+           (this->type == RAD_LISTEN_PROXY)) {
+               ADDSTRING(" (");
+               ip_ntoh(&sock->my_ipaddr, buffer, bufsize);
+               FORWARD;
+
+               ADDSTRING(", ");
+               snprintf(buffer, bufsize, "%d", sock->my_port);
+               FORWARD;
+               ADDSTRING(") -> home_server (");
+
+               if ((sock->other_ipaddr.af == AF_INET) &&
+                   (sock->other_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) {
+                       strlcpy(buffer, "*", bufsize);
+               } else {
+                       ip_ntoh(&sock->other_ipaddr, buffer, bufsize);
+               }
+               FORWARD;
+               
+               ADDSTRING(", ");
+               snprintf(buffer, bufsize, "%d", sock->other_port);
+               FORWARD;
+
+               ADDSTRING(")");
+
+               return 1;
+       }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
+
+       ADDSTRING(" address ");
+       
+       if ((sock->my_ipaddr.af == AF_INET) &&
+           (sock->my_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) {
+               strlcpy(buffer, "*", bufsize);
+       } else {
+               ip_ntoh(&sock->my_ipaddr, buffer, bufsize);
+       }
+       FORWARD;
+
+       ADDSTRING(" port ");
+       snprintf(buffer, bufsize, "%d", sock->my_port);
+       FORWARD;
+
+       if (this->server) {
+               ADDSTRING(" as server ");
+               ADDSTRING(this->server);
+       }
+
+#undef ADDSTRING
+#undef FORWARD
+
+       return 1;
+}
+
+extern int check_config;       /* radiusd.c */
+
+
+/*
+ *     Parse an authentication or accounting socket.
+ */
+static int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+       int             rcode;
+       int             listen_port;
+       fr_ipaddr_t     ipaddr;
+       listen_socket_t *sock = this->data;
+       char            *section_name = NULL;
+       CONF_SECTION    *client_cs, *parentcs;
+
+       /*
+        *      Try IPv4 first
+        */
+       ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
+       rcode = cf_item_parse(cs, "ipaddr", PW_TYPE_IPADDR,
+                             &ipaddr.ipaddr.ip4addr, NULL);
+       if (rcode < 0) return -1;
+
+       if (rcode == 0) { /* successfully parsed IPv4 */
+               ipaddr.af = AF_INET;
+
+       } else {        /* maybe IPv6? */
+               rcode = cf_item_parse(cs, "ipv6addr", PW_TYPE_IPV6ADDR,
+                                     &ipaddr.ipaddr.ip6addr, NULL);
+               if (rcode < 0) return -1;
+
+               if (rcode == 1) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "No address specified in listen section");
+                       return -1;
+               }
+               ipaddr.af = AF_INET6;
+       }
+
+       rcode = cf_item_parse(cs, "port", PW_TYPE_INTEGER,
+                             &listen_port, "0");
+       if (rcode < 0) return -1;
+
+       if ((listen_port < 0) || (listen_port > 65535)) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "Invalid value for \"port\"");
+                       return -1;
+       }
+
+       sock->proto = IPPROTO_UDP;
+
+       if (cf_pair_find(cs, "proto")) {
+#ifndef WITH_TCP
+               cf_log_err(cf_sectiontoitem(cs),
+                          "System does not support the TCP protocol.  Delete this line from the configuration file.");
+               return -1;
+#else
+               char *proto = NULL;
+
+
+               rcode = cf_item_parse(cs, "proto", PW_TYPE_STRING_PTR,
+                                     &proto, "udp");
+               if (rcode < 0) return -1;
+
+               if (strcmp(proto, "udp") == 0) {
+                       sock->proto = IPPROTO_UDP;
+
+               } else if (strcmp(proto, "tcp") == 0) {
+                       sock->proto = IPPROTO_TCP;
+
+                       rcode = cf_item_parse(cs, "max_connections", PW_TYPE_INTEGER,
+                                             &sock->max_connections, "64");
+                       if (rcode < 0) return -1;
+
+               } else {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "Unknown proto name \"%s\"", proto);
+                       free(proto);
+                       return -1;
                }
+               free(proto);
+
+               /*
+                *      TCP requires a destination IP for sockets.
+                *      UDP doesn't, so it's allowed.
+                */
+#ifdef WITH_PROXY
+               if ((this->type == RAD_LISTEN_PROXY) &&
+                   (sock->proto != IPPROTO_UDP)) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "Proxy listeners can only listen on proto = udp");
+                       return -1;
+               }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
+       }
+
+       sock->my_ipaddr = ipaddr;
+       sock->my_port = listen_port;
+
+       if (check_config) {
+               if (home_server_find(&sock->my_ipaddr, sock->my_port, sock->proto)) {
+                               char buffer[128];
+                               
+                               DEBUG("ERROR: We have been asked to listen on %s port %d, which is also listed as a home server.  This can create a proxy loop.",
+                                     ip_ntoh(&sock->my_ipaddr, buffer, sizeof(buffer)),
+                                     sock->my_port);
+                               return -1;
+               }
+
+               return 0;       /* don't do anything */
+       }
+
+       /*
+        *      If we can bind to interfaces, do so,
+        *      else don't.
+        */
+       if (cf_pair_find(cs, "interface")) {
+               const char *value;
+               CONF_PAIR *cp = cf_pair_find(cs, "interface");
+
+               rad_assert(cp != NULL);
+               value = cf_pair_value(cp);
+               if (!value) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "No interface name given");
+                       return -1;
+               }
+               sock->interface = value;
+       }
+
+#ifdef WITH_DHCP
+       /*
+        *      If we can do broadcasts..
+        */
+       if (cf_pair_find(cs, "broadcast")) {
+#ifndef SO_BROADCAST
+               cf_log_err(cf_sectiontoitem(cs),
+                          "System does not support broadcast sockets.  Delete this line from the configuration file.");
+               return -1;
+#else
+               const char *value;
+               CONF_PAIR *cp = cf_pair_find(cs, "broadcast");
+
+               if (this->type != RAD_LISTEN_DHCP) {
+                       cf_log_err(cf_pairtoitem(cp),
+                                  "Broadcast can only be set for DHCP listeners.  Delete this line from the configuration file.");
+                       return -1;
+               }
+               
+               rad_assert(cp != NULL);
+               value = cf_pair_value(cp);
+               if (!value) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "No broadcast value given");
+                       return -1;
+               }
+
                /*
-                *      Look for the header
+                *      Hack... whatever happened to cf_section_parse?
                 */
-               data->state = STATE_HEADER;
+               sock->broadcast = (strcmp(value, "yes") == 0);
+#endif
+       }
+#endif
+
+       /*
+        *      And bind it to the port.
+        */
+       if (listen_bind(this) < 0) {
+               char buffer[128];
+               cf_log_err(cf_sectiontoitem(cs),
+                          "Error binding to port for %s port %d",
+                          ip_ntoh(&sock->my_ipaddr, buffer, sizeof(buffer)),
+                          sock->my_port);
+               return -1;
+       }
+
+#ifdef WITH_PROXY
+       /*
+        *      Proxy sockets don't have clients.
+        */
+       if (this->type == RAD_LISTEN_PROXY) return 0;
+#endif
+       
+       /*
+        *      The more specific configurations are preferred to more
+        *      generic ones.
+        */
+       client_cs = NULL;
+       parentcs = cf_top_section(cs);
+       rcode = cf_item_parse(cs, "clients", PW_TYPE_STRING_PTR,
+                             &section_name, NULL);
+       if (rcode < 0) return -1; /* bad string */
+       if (rcode == 0) {
+               /*
+                *      Explicit list given: use it.
+                */
+               client_cs = cf_section_sub_find_name2(parentcs,
+                                                     "clients",
+                                                     section_name);
+               if (!client_cs) {
+                       client_cs = cf_section_find(section_name);
+               }
+               if (!client_cs) {
+                       cf_log_err(cf_sectiontoitem(cs),
+                                  "Failed to find clients %s {...}",
+                                  section_name);
+                       free(section_name);
+                       return -1;
+               }
+               free(section_name);
+       } /* else there was no "clients = " entry. */
+
+       if (!client_cs) {
+               CONF_SECTION *server_cs;
+
+               server_cs = cf_section_sub_find_name2(parentcs,
+                                                     "server",
+                                                     this->server);
+               /*
+                *      Found a "server foo" section.  If there are clients
+                *      in it, use them.
+                */
+               if (server_cs &&
+                   (cf_section_sub_find(server_cs, "client") != NULL)) {
+                       client_cs = server_cs;
+               }
        }
 
        /*
-        *      If we keep track of the outstanding requests, do so
-        *      here.  Note that to minimize potential work, we do
-        *      so only once the file is opened & locked.
+        *      Still nothing.  Look for global clients.
+        */
+       if (!client_cs) client_cs = parentcs;
+
+       sock->clients = clients_parse_section(client_cs);
+       if (!sock->clients) {
+               cf_log_err(cf_sectiontoitem(cs),
+                          "Failed to load clients for this listen section");
+               return -1;
+       }
+
+#ifdef WITH_TCP
+       if (sock->proto == IPPROTO_TCP) {
+               /*
+                *      Re-write the listener receive function to
+                *      allow us to accept the socket.
+                */
+               this->recv = auth_tcp_accept;
+       }
+#endif
+
+       return 0;
+}
+
+/*
+ *     Send an authentication response packet
+ */
+static int auth_socket_send(rad_listen_t *listener, REQUEST *request)
+{
+       rad_assert(request->listener == listener);
+       rad_assert(listener->send == auth_socket_send);
+
+       return rad_send(request->reply, request->packet,
+                       request->client->secret);
+}
+
+
+#ifdef WITH_ACCOUNTING
+/*
+ *     Send an accounting response packet (or not)
+ */
+static int acct_socket_send(rad_listen_t *listener, REQUEST *request)
+{
+       rad_assert(request->listener == listener);
+       rad_assert(listener->send == acct_socket_send);
+
+       /*
+        *      Accounting reject's are silently dropped.
+        *
+        *      We do it here to avoid polluting the rest of the
+        *      code with this knowledge
+        */
+       if (request->reply->code == 0) return 0;
+
+       return rad_send(request->reply, request->packet,
+                       request->client->secret);
+}
+#endif
+
+#ifdef WITH_PROXY
+/*
+ *     Send a packet to a home server.
+ *
+ *     FIXME: have different code for proxy auth & acct!
+ */
+static int proxy_socket_send(rad_listen_t *listener, REQUEST *request)
+{
+       rad_assert(request->proxy_listener == listener);
+       rad_assert(listener->send == proxy_socket_send);
+
+       return rad_send(request->proxy, request->packet,
+                       request->home_server->secret);
+}
+#endif
+
+#ifdef WITH_STATS
+/*
+ *     Check if an incoming request is "ok"
+ *
+ *     It takes packets, not requests.  It sees if the packet looks
+ *     OK.  If so, it does a number of sanity checks on it.
+  */
+static int stats_socket_recv(rad_listen_t *listener,
+                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       ssize_t         rcode;
+       int             code, src_port;
+       RADIUS_PACKET   *packet;
+       RADCLIENT       *client;
+       fr_ipaddr_t     src_ipaddr;
+
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_STATS_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               return 0;
+       }
+
+       if ((client = client_listener_find(listener,
+                                          &src_ipaddr, src_port)) == NULL) {
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_invalid_requests);
+               return 0;
+       }
+
+       /*
+        *      We only understand Status-Server on this socket.
+        */
+       if (code != PW_STATUS_SERVER) {
+               DEBUG("Ignoring packet code %d sent to Status-Server port",
+                     code);
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_unknown_types);
+               RAD_STATS_CLIENT_INC(listener, client, total_unknown_types);
+               return 0;
+       }
+
+       /*
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
+        */
+       packet = rad_recv(listener->fd, 1); /* require message authenticator */
+       if (!packet) {
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               DEBUG("%s", fr_strerror());
+               return 0;
+       }
+
+       if (!received_request(listener, packet, prequest, client)) {
+               RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+               RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped);
+               rad_free(&packet);
+               return 0;
+       }
+
+       *pfun = rad_status_server;
+       return 1;
+}
+#endif
+
+
+/*
+ *     Check if an incoming request is "ok"
+ *
+ *     It takes packets, not requests.  It sees if the packet looks
+ *     OK.  If so, it does a number of sanity checks on it.
+  */
+static int auth_socket_recv(rad_listen_t *listener,
+                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       ssize_t         rcode;
+       int             code, src_port;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       RADCLIENT       *client;
+       fr_ipaddr_t     src_ipaddr;
+
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_STATS_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               return 0;
+       }
+
+       if ((client = client_listener_find(listener,
+                                          &src_ipaddr, src_port)) == NULL) {
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_invalid_requests);
+               return 0;
+       }
+
+       /*
+        *      Some sanity checks, based on the packet code.
+        */
+       switch(code) {
+       case PW_AUTHENTICATION_REQUEST:
+               RAD_STATS_CLIENT_INC(listener, client, total_requests);
+               fun = rad_authenticate;
+               break;
+
+       case PW_STATUS_SERVER:
+               if (!mainconfig.status_server) {
+                       rad_recv_discard(listener->fd);
+                       RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+                       RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped);
+                       DEBUG("WARNING: Ignoring Status-Server request due to security configuration");
+                       return 0;
+               }
+               fun = rad_status_server;
+               break;
+
+       default:
+               rad_recv_discard(listener->fd);
+               RAD_STATS_INC(radius_auth_stats.total_unknown_types);
+               RAD_STATS_CLIENT_INC(listener, client, total_unknown_types);
+
+               DEBUG("Invalid packet code %d sent to authentication port from client %s port %d : IGNORED",
+                     code, client->shortname, src_port);
+               return 0;
+               break;
+       } /* switch over packet types */
+
+       /*
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
         */
-       if (data->max_outstanding) {
-               int i;
+       packet = rad_recv(listener->fd, client->message_authenticator);
+       if (!packet) {
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               DEBUG("%s", fr_strerror());
+               return 0;
+       }
 
-               for (i = 0; i < data->max_outstanding; i++) {
-                       if (!data->outstanding[i]) {
-                               free_slot = i;
-                               break;
-                       }
-               }
+       if (!received_request(listener, packet, prequest, client)) {
+               RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+               RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped);
+               rad_free(&packet);
+               return 0;
+       }
 
-               /*
-                *      All of the slots are full, don't read data.
-                */
-               if (free_slot < 0) return 0;
+       *pfun = fun;
+       return 1;
+}
+
+
+#ifdef WITH_ACCOUNTING
+/*
+ *     Receive packets from an accounting socket
+ */
+static int acct_socket_recv(rad_listen_t *listener,
+                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       ssize_t         rcode;
+       int             code, src_port;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       RADCLIENT       *client;
+       fr_ipaddr_t     src_ipaddr;
+
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_STATS_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               return 0;
+       }
+
+       if ((client = client_listener_find(listener,
+                                          &src_ipaddr, src_port)) == NULL) {
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_invalid_requests);
+               return 0;
        }
 
        /*
-        *      Catch an out of memory condition which will most likely
-        *      never be met.
+        *      Some sanity checks, based on the packet code.
         */
-       if (data->state == STATE_DONE) goto alloc_packet;
+       switch(code) {
+       case PW_ACCOUNTING_REQUEST:
+               RAD_STATS_CLIENT_INC(listener, client, total_requests);
+               fun = rad_accounting;
+               break;
+
+       case PW_STATUS_SERVER:
+               if (!mainconfig.status_server) {
+                       rad_recv_discard(listener->fd);
+                       RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+                       RAD_STATS_CLIENT_INC(listener, client, total_unknown_types);
+
+                       DEBUG("WARNING: Ignoring Status-Server request due to security configuration");
+                       return 0;
+               }
+               fun = rad_status_server;
+               break;
+
+       default:
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_unknown_types);
+               RAD_STATS_CLIENT_INC(listener, client, total_unknown_types);
+
+               DEBUG("Invalid packet code %d sent to a accounting port from client %s port %d : IGNORED",
+                     code, client->shortname, src_port);
+               return 0;
+       } /* switch over packet types */
 
        /*
-        *      If we're in another state, then it means that we read
-        *      a partial packet, which is bad.
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
         */
-       rad_assert(data->state == STATE_HEADER);
-       rad_assert(data->vps == NULL);
+       packet = rad_recv(listener->fd, 0);
+       if (!packet) {
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               radlog(L_ERR, "%s", fr_strerror());
+               return 0;
+       }
 
        /*
-        *      We read the last packet, and returned it for
-        *      processing.  We later come back here to shut
-        *      everything down, and unlink the file.
+        *      There can be no duplicate accounting packets.
         */
-       if (feof(data->fp)) {
-               rad_assert(data->state == STATE_HEADER);
+       if (!received_request(listener, packet, prequest, client)) {
+               RAD_STATS_TYPE_INC(listener, total_packets_dropped);
+               RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped);
+               rad_free(&packet);
+               return 0;
+       }
 
-               /*
-                *      Don't unlink the file until we've received
-                *      all of the responses.
-                */
-               if (data->max_outstanding > 0) {
-                       int i;
+       *pfun = fun;
+       return 1;
+}
+#endif
 
-                       for (i = 0; i < data->max_outstanding; i++) {
-                               /*
-                                *      FIXME: close the file?
-                                */
-                               if (data->outstanding[i]) {
-                                       data->state = STATE_WAITING;
-                                       return 0;
-                               }
-                       }
-               }
 
-       cleanup:
-               rad_assert(data->vps == NULL);
+#ifdef WITH_COA
+/*
+ *     For now, all CoA requests are *only* originated, and not
+ *     proxied.  So all of the necessary work is done in the
+ *     post-proxy section, which is automatically handled by event.c.
+ *     As a result, we don't have to do anything here.
+ */
+static int rad_coa_reply(REQUEST *request)
+{
+       VALUE_PAIR *s1, *s2;
 
-               snprintf(buffer, sizeof(buffer), "%s.work", data->detail);
-               unlink(buffer);
-               fclose(data->fp); /* closes listener->fd */
-               data->fp = NULL;
-               listener->fd = -1;
-               data->state = STATE_UNOPENED;
+       /*
+        *      Inform the user about RFC requirements.
+        */
+       s1 = pairfind(request->proxy->vps, PW_STATE, 0);
+       if (s1) {
+               s2 = pairfind(request->proxy_reply->vps, PW_STATE, 0);
 
-               /*
-                *      Try to open "detail" again.  If we're on a
-                *      busy RADIUS server, odds are that it will
-                *      now exist.
-                */
-               detail_open(listener);
-               return 0;
+               if (!s2) {
+                       DEBUG("WARNING: Client was sent State in CoA, and did not respond with State.");
+
+               } else if ((s1->length != s2->length) ||
+                          (memcmp(s1->vp_octets, s2->vp_octets,
+                                  s1->length) != 0)) {
+                       DEBUG("WARNING: Client was sent State in CoA, and did not respond with the same State.");
+               }
        }
 
-       tail = &data->vps;
+       return RLM_MODULE_OK;
+}
+
+/*
+ *     Receive a CoA packet.
+ */
+static int rad_coa_recv(REQUEST *request)
+{
+       int rcode = RLM_MODULE_OK;
+       int ack, nak;
+       VALUE_PAIR *vp;
 
        /*
-        *      Fill the buffer...
+        *      Get the correct response
         */
-       while (fgets(buffer, sizeof(buffer), data->fp)) {
+       switch (request->packet->code) {
+       case PW_COA_REQUEST:
+               ack = PW_COA_ACK;
+               nak = PW_COA_NAK;
+               break;
+
+       case PW_DISCONNECT_REQUEST:
+               ack = PW_DISCONNECT_ACK;
+               nak = PW_DISCONNECT_NAK;
+               break;
+
+       default:                /* shouldn't happen */
+               return RLM_MODULE_FAIL;
+       }
+
+#ifdef WITH_PROXY
+#define WAS_PROXIED (request->proxy)
+#else
+#define WAS_PROXIED (0)
+#endif
+
+       if (!WAS_PROXIED) {
                /*
-                *      No CR, die.
+                *      RFC 5176 Section 3.3.  If we have a CoA-Request
+                *      with Service-Type = Authorize-Only, it MUST
+                *      have a State attribute in it.
                 */
-               if (!strchr(buffer, '\n')) {
-                       pairfree(&data->vps);
-                       goto cleanup;
+               vp = pairfind(request->packet->vps, PW_SERVICE_TYPE, 0);
+               if (request->packet->code == PW_COA_REQUEST) {
+                       if (vp && (vp->vp_integer == 17)) {
+                               vp = pairfind(request->packet->vps, PW_STATE, 0);
+                               if (!vp || (vp->length == 0)) {
+                                       RDEBUG("ERROR: CoA-Request with Service-Type = Authorize-Only MUST contain a State attribute");
+                                       request->reply->code = PW_COA_NAK;
+                                       return RLM_MODULE_FAIL;
+                               }
+                       }
+               } else if (vp) {
+                       /*
+                        *      RFC 5176, Section 3.2.
+                        */
+                       RDEBUG("ERROR: Disconnect-Request MUST NOT contain a Service-Type attribute");
+                       request->reply->code = PW_DISCONNECT_NAK;
+                       return RLM_MODULE_FAIL;
                }
 
-               /*
-                *      We've read a header, possibly packet contents,
-                *      and are now at the end of the packet.
-                */
-               if ((data->state == STATE_READING) &&
-                   (buffer[0] == '\n')) {
-                       data->state = STATE_DONE;
+               rcode = module_recv_coa(0, request);
+               switch (rcode) {
+               case RLM_MODULE_FAIL:
+               case RLM_MODULE_INVALID:
+               case RLM_MODULE_REJECT:
+               case RLM_MODULE_USERLOCK:
+               default:
+                       request->reply->code = nak;
+                       break;
+                       
+               case RLM_MODULE_HANDLED:
+                       return rcode;
+                       
+               case RLM_MODULE_NOOP:
+               case RLM_MODULE_NOTFOUND:
+               case RLM_MODULE_OK:
+               case RLM_MODULE_UPDATED:
+                       request->reply->code = ack;
                        break;
                }
+       } else {
+               /*
+                *      Start the reply code with the proxy reply
+                *      code.
+                */
+               request->reply->code = request->proxy_reply->code;
+       }
+
+       /*
+        *      Copy State from the request to the reply.
+        *      See RFC 5176 Section 3.3.
+        */
+       vp = paircopy2(request->packet->vps, PW_STATE, 0);
+       if (vp) pairadd(&request->reply->vps, vp);
 
+       /*
+        *      We may want to over-ride the reply.
+        */
+       rcode = module_send_coa(0, request);
+       switch (rcode) {
                /*
-                *      Look for date/time header, and read VP's if
-                *      found.  If not, keep reading lines until we
-                *      find one.
+                *      We need to send CoA-NAK back if Service-Type
+                *      is Authorize-Only.  Rely on the user's policy
+                *      to do that.  We're not a real NAS, so this
+                *      restriction doesn't (ahem) apply to us.
                 */
-               if (data->state == STATE_HEADER) {
-                       int y;
+               case RLM_MODULE_FAIL:
+               case RLM_MODULE_INVALID:
+               case RLM_MODULE_REJECT:
+               case RLM_MODULE_USERLOCK:
+               default:
+                       /*
+                        *      Over-ride an ACK with a NAK
+                        */
+                       request->reply->code = nak;
+                       break;
                        
-                       if (sscanf(buffer, "%*s %*s %*d %*d:%*d:%*d %d", &y)) {
-                               data->state = STATE_READING;
+               case RLM_MODULE_HANDLED:
+                       return rcode;
+                       
+               case RLM_MODULE_NOOP:
+               case RLM_MODULE_NOTFOUND:
+               case RLM_MODULE_OK:
+               case RLM_MODULE_UPDATED:
+                       /*
+                        *      Do NOT over-ride a previously set value.
+                        *      Otherwise an "ok" here will re-write a
+                        *      NAK to an ACK.
+                        */
+                       if (request->reply->code == 0) {
+                               request->reply->code = ack;
                        }
-                       continue;
-               }
+                       break;
 
-               /*
-                *      We have a full "attribute = value" line.
-                *      If it doesn't look reasonable, skip it.
-                */
-               if (sscanf(buffer, "%255s = %1023s", key, value) != 2) {
-                       continue;
-               }
+       }
 
-               /*
-                *      Skip non-protocol attributes.
-                */
-               if (!strcasecmp(key, "Request-Authenticator")) continue;
+       return RLM_MODULE_OK;
+}
 
-               /*
-                *      Set the original client IP address, based on
-                *      what's in the detail file.
-                *
-                *      Hmm... we don't set the server IP address.
-                *      or port.  Oh well.
-                */
-               if (!strcasecmp(key, "Client-IP-Address")) {
-                       data->client_ip.af = AF_INET;
-                       ip_hton(value, AF_INET, &data->client_ip);
-                       continue;
-               }
 
-               /*
-                *      The original time at which we received the
-                *      packet.  We need this to properly calculate
-                *      Acct-Delay-Time.
-                */
-               if (!strcasecmp(key, "Timestamp")) {
-                       data->timestamp = atoi(value);
-                       continue;
-               }
+/*
+ *     Check if an incoming request is "ok"
+ *
+ *     It takes packets, not requests.  It sees if the packet looks
+ *     OK.  If so, it does a number of sanity checks on it.
+  */
+static int coa_socket_recv(rad_listen_t *listener,
+                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       ssize_t         rcode;
+       int             code, src_port;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       RADCLIENT       *client;
+       fr_ipaddr_t     src_ipaddr;
 
-               /*
-                *      Read one VP.
-                *
-                *      FIXME: do we want to check for non-protocol
-                *      attributes like radsqlrelay does?
-                */
-               vp = NULL;
-               if ((userparse(buffer, &vp) > 0) &&
-                   (vp != NULL)) {
-                       *tail = vp;
-                       tail = &(vp->next);
-               }               
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_STATS_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               return 0;
        }
 
-       /*
-        *      We got to EOF,  If we're in STATE_HEADER, it's OK.
-        *      Otherwise it's a problem.  In any case, nuke the file
-        *      and start over from scratch,
-        */
-       if (feof(data->fp)) {
-               goto cleanup;
+       if ((client = client_listener_find(listener,
+                                          &src_ipaddr, src_port)) == NULL) {
+               rad_recv_discard(listener->fd);
+               RAD_STATS_TYPE_INC(listener, total_invalid_requests);
+               return 0;
        }
 
        /*
-        *      FIXME: Do load management.
+        *      Some sanity checks, based on the packet code.
         */
+       switch(code) {
+       case PW_COA_REQUEST:
+       case PW_DISCONNECT_REQUEST:
+               fun = rad_coa_recv;
+               break;
 
-       /*
-        *      If we're not done, then there's a problem.  The checks
-        *      above for EOF
-        */
-       rad_assert(data->state == STATE_DONE);
+       default:
+               rad_recv_discard(listener->fd);
+               DEBUG("Invalid packet code %d sent to coa port from client %s port %d : IGNORED",
+                     code, client->shortname, src_port);
+               return 0;
+               break;
+       } /* switch over packet types */
 
        /*
-        *      The packet we read was empty, re-set the state to look
-        *      for a header, and don't return anything.
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
         */
-       if (!data->vps) {
-               data->state = STATE_HEADER;
+       packet = rad_recv(listener->fd, client->message_authenticator);
+       if (!packet) {
+               RAD_STATS_TYPE_INC(listener, total_malformed_requests);
+               DEBUG("%s", fr_strerror());
                return 0;
        }
 
-       /*
-        *      Allocate the packet.  If we fail, it's a serious
-        *      problem.
-        */
- alloc_packet:
-       packet = rad_alloc(1);
-       if (!packet) {
-               return 0;       /* maybe memory will magically free up... */
+       if (!received_request(listener, packet, prequest, client)) {
+               rad_free(&packet);
+               return 0;
        }
 
-       memset(packet, 0, sizeof(*packet));
-       packet->sockfd = -1;
-       packet->src_ipaddr.af = AF_INET;
-       packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
-       packet->code = PW_ACCOUNTING_REQUEST;
-       packet->timestamp = time(NULL);
+       *pfun = fun;
+       return 1;
+}
+#endif
+
+#ifdef WITH_PROXY
+/*
+ *     Recieve packets from a proxy socket.
+ */
+static int proxy_socket_recv(rad_listen_t *listener,
+                             RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       REQUEST         *request;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       char            buffer[128];
 
-       /*
-        *      Look for Acct-Delay-Time, and update
-        *      based on Acct-Delay-Time += (time(NULL) - timestamp)
-        */
-       vp = pairfind(packet->vps, PW_ACCT_DELAY_TIME);
-       if (!vp) {
-               vp = paircreate(PW_ACCT_DELAY_TIME, PW_TYPE_INTEGER);
-               rad_assert(vp != NULL);
-       }
-       if (data->timestamp != 0) {
-               vp->lvalue += time(NULL) - data->timestamp;
+       packet = rad_recv(listener->fd, 0);
+       if (!packet) {
+               radlog(L_ERR, "%s", fr_strerror());
+               return 0;
        }
 
        /*
-        *      Remember where it came from, so that we don't
-        *      proxy it to the place it came from...
+        *      FIXME: Client MIB updates?
         */
-       if (data->client_ip.af != AF_UNSPEC) {
-               packet->src_ipaddr = data->client_ip;
-       }
+       switch(packet->code) {
+       case PW_AUTHENTICATION_ACK:
+       case PW_ACCESS_CHALLENGE:
+       case PW_AUTHENTICATION_REJECT:
+               fun = rad_authenticate;
+               break;
 
-       vp = pairfind(packet->vps, PW_PACKET_SRC_IP_ADDRESS);
-       if (vp) {
-               packet->src_ipaddr.af = AF_INET;
-               packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->lvalue;
-       } else {
-               vp = pairfind(packet->vps, PW_PACKET_SRC_IPV6_ADDRESS);
-               if (vp) {
-                       packet->src_ipaddr.af = AF_INET6;
-                       memcpy(&packet->src_ipaddr.ipaddr.ip6addr,
-                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
-               }
+#ifdef WITH_ACCOUNTING
+       case PW_ACCOUNTING_RESPONSE:
+               fun = rad_accounting;
+               break;
+#endif
+
+#ifdef WITH_COA
+       case PW_DISCONNECT_ACK:
+       case PW_DISCONNECT_NAK:
+       case PW_COA_ACK:
+       case PW_COA_NAK:
+               fun = rad_coa_reply;
+               break;
+#endif
+
+       default:
+               /*
+                *      FIXME: Update MIB for packet types?
+                */
+               radlog(L_ERR, "Invalid packet code %d sent to a proxy port "
+                      "from home server %s port %d - ID %d : IGNORED",
+                      packet->code,
+                      ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)),
+                      packet->src_port, packet->id);
+               rad_free(&packet);
+               return 0;
        }
 
-       vp = pairfind(packet->vps, PW_PACKET_DST_IP_ADDRESS);
-       if (vp) {
-               packet->dst_ipaddr.af = AF_INET;
-               packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->lvalue;
-       } else {
-               vp = pairfind(packet->vps, PW_PACKET_DST_IPV6_ADDRESS);
-               if (vp) {
-                       packet->dst_ipaddr.af = AF_INET6;
-                       memcpy(&packet->dst_ipaddr.ipaddr.ip6addr,
-                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
-               }
+       request = received_proxy_response(packet);
+       if (!request) {
+               rad_free(&packet);
+               return 0;
        }
 
+#ifdef WITH_COA
        /*
-        *      We've got to give SOME value for Id & ports, so that
-        *      the packets can be added to the request queue.
-        *      However, we don't want to keep track of used/unused
-        *      id's and ports, as that's a lot of work.  This hack
-        *      ensures that (if we have real random numbers), that
-        *      there will be a collision on every (2^(16+16+2+24))/2
-        *      packets, on average.  That means we can read 2^32 (4G)
-        *      packets before having a collision, which means it's
-        *      effectively impossible.  Having 4G packets currently
-        *      being process is ridiculous.
+        *      Distinguish proxied CoA requests from ones we
+        *      originate.
         */
-       packet->id = lrad_rand() & 0xff;
-       packet->src_port = lrad_rand() & 0xffff;
-       packet->dst_port = lrad_rand() & 0xffff;
+       if ((fun == rad_coa_reply) &&
+           (request->packet->code == request->proxy->code)) {
+               fun = rad_coa_recv;
+       }
+#endif
+
+       rad_assert(fun != NULL);
+       *pfun = fun;
+       *prequest = request;
 
-       packet->dst_ipaddr.af = AF_INET;
-       packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | (lrad_rand() & 0xffffff));
+       return 1;
+}
+
+#ifdef WITH_TCP
+/*
+ *     Recieve packets from a proxy socket.
+ */
+static int proxy_socket_tcp_recv(rad_listen_t *listener,
+                                RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+{
+       REQUEST         *request;
+       RADIUS_PACKET   *packet;
+       RAD_REQUEST_FUNP fun = NULL;
+       listen_socket_t *sock = listener->data;
+       char            buffer[128];
 
-       packet->vps = data->vps;
+       packet = fr_tcp_recv(listener->fd, 0);
+       if (!packet) {
+               listener->status = RAD_LISTEN_STATUS_REMOVE_FD;
+               event_new_fd(listener);
+               return 0;
+       }
 
        /*
-        *      Re-set the state.
+        *      FIXME: Client MIB updates?
         */
-       data->vps = NULL;
-       data->state = STATE_HEADER;
+       switch(packet->code) {
+       case PW_AUTHENTICATION_ACK:
+       case PW_ACCESS_CHALLENGE:
+       case PW_AUTHENTICATION_REJECT:
+               fun = rad_authenticate;
+               break;
+
+#ifdef WITH_ACCOUNTING
+       case PW_ACCOUNTING_RESPONSE:
+               fun = rad_accounting;
+               break;
+#endif
 
-       /*
-        *      FIXME: many of these checks may not be necessary...
-        */
-       if (!common_checks(listener, packet, prequest, &detail_client)) {
+       default:
+               /*
+                *      FIXME: Update MIB for packet types?
+                */
+               radlog(L_ERR, "Invalid packet code %d sent to a proxy port "
+                      "from home server %s port %d - ID %d : IGNORED",
+                      packet->code,
+                      ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)),
+                      packet->src_port, packet->id);
                rad_free(&packet);
                return 0;
        }
 
+       packet->src_ipaddr = sock->other_ipaddr;
+       packet->src_port = sock->other_port;
+       packet->dst_ipaddr = sock->my_ipaddr;
+       packet->dst_port = sock->my_port;
+
        /*
-        *      Keep track of free slots, as a hack, in an otherwise
-        *      unused 'int'
+        *      FIXME: Have it return an indication of packets that
+        *      are OK to ignore (dups, too late), versus ones that
+        *      aren't OK to ignore (unknown response, spoofed, etc.)
+        *
+        *      Close the socket on bad packets...
         */
-       (*prequest)->simul_max = free_slot;
-       if (free_slot) data->outstanding[free_slot] = 1;
+       request = received_proxy_response(packet);
+       if (!request) {
+               return 0;
+       }
 
-       *pfun = rad_accounting;
+       rad_assert(fun != NULL);
+       sock->opened = sock->last_packet = request->timestamp;
 
-       if (debug_flag) {
-               printf("detail_recv: Read packet from %s\n", data->detail);
-               for (vp = packet->vps; vp; vp = vp->next) {
-                       putchar('\t');
-                       vp_print(stdout, vp);
-                       putchar('\n');
-               }
-       }
+       *pfun = fun;
+       *prequest = request;
 
        return 1;
 }
+#endif
+#endif
 
 
-/*
- *     Free detail-specific stuff.
- */
-static void detail_free(rad_listen_t *this)
+static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request)
 {
-       listen_detail_t *data = this->data;
+       if (!request->reply->code) return 0;
 
-       free(data->detail);
-       pairfree(&data->vps);
-       free(data->outstanding);
+       rad_encode(request->reply, request->packet,
+                  request->client->secret);
+       rad_sign(request->reply, request->packet,
+                request->client->secret);
 
-       if (data->fp != NULL) fclose(data->fp);
-}
-
-
-static int detail_print(rad_listen_t *this, char *buffer, size_t bufsize)
-{
-       return snprintf(buffer, bufsize, "%s",
-                       ((listen_detail_t *)(this->data))->detail);
+       return 0;
 }
 
 
-static const CONF_PARSER detail_config[] = {
-       { "detail",   PW_TYPE_STRING_PTR,
-         offsetof(listen_detail_t, detail), NULL,  NULL },
-       { "max_outstanding",  PW_TYPE_INTEGER,
-          offsetof(listen_detail_t, max_outstanding), NULL, "100" },
-
-       { NULL, -1, 0, NULL, NULL }             /* end the list */
-};
-
-
-/*
- *     Parse a detail section.
- */
-static int detail_parse(const char *filename, int lineno,
-                       const CONF_SECTION *cs, rad_listen_t *this)
+static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request)
 {
-       int             rcode;
-       listen_detail_t *data;
-
-       data = this->data;
-
-       rcode = cf_section_parse(cs, data, detail_config);
-       if (rcode < 0) {
-               radlog(L_ERR, "%s[%d]: Failed parsing listen section",
-                      filename, lineno);
-               return -1;
-       }
-
-       if (!data->detail) {
-               radlog(L_ERR, "%s[%d]: No detail file specified in listen section",
-                      filename, lineno);
+       if (rad_verify(request->packet, NULL,
+                      request->client->secret) < 0) {
                return -1;
        }
-       
-       data->vps = NULL;
-       data->fp = NULL;
-       data->state = STATE_UNOPENED;
 
-       if (data->max_outstanding > 32768) data->max_outstanding = 32768;
+       return rad_decode(request->packet, NULL,
+                         request->client->secret);
+}
 
-       if (data->max_outstanding > 0) {
-               data->outstanding = rad_malloc(sizeof(int) * data->max_outstanding);
-       }
-       
-       detail_open(this);
+#ifdef WITH_PROXY
+static int proxy_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request)
+{
+       rad_encode(request->proxy, NULL, request->home_server->secret);
+       rad_sign(request->proxy, NULL, request->home_server->secret);
 
        return 0;
 }
 
 
-/*
- *     See radiusd.c & request_list.c
- */
-#define SLEEP_FOREVER (65536)
-/*
- *     A generic "update the request list once a second" function.
- */
-static int generic_update(rad_listen_t *this, time_t now)
+static int proxy_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request)
 {
-       if (!this->rl) return SLEEP_FOREVER;
+       /*
+        *      rad_verify is run in event.c, received_proxy_response()
+        */
 
-       return rl_clean_list(this->rl, now);
+       return rad_decode(request->proxy_reply, request->proxy,
+                          request->home_server->secret);
 }
+#endif
 
+#include "dhcpd.c"
 
+#include "command.c"
 
 static const rad_listen_master_t master_listen[RAD_LISTEN_MAX] = {
-       { NULL, NULL, NULL, NULL, NULL, NULL},  /* RAD_LISTEN_NONE */
+#ifdef WITH_STATS
+       { common_socket_parse, NULL,
+         stats_socket_recv, auth_socket_send,
+         socket_print, client_socket_encode, client_socket_decode },
+#else
+       /*
+        *      This always gets defined.
+        */
+       { NULL, NULL, NULL, NULL, NULL, NULL, NULL},    /* RAD_LISTEN_NONE */
+#endif
+
+#ifdef WITH_PROXY
+       /* proxying */
+       { common_socket_parse, NULL,
+         proxy_socket_recv, proxy_socket_send,
+         socket_print, proxy_socket_encode, proxy_socket_decode },
+#endif
 
        /* authentication */
        { common_socket_parse, NULL,
          auth_socket_recv, auth_socket_send,
-         generic_update, socket_print },
+         socket_print, client_socket_encode, client_socket_decode },
 
+#ifdef WITH_ACCOUNTING
        /* accounting */
        { common_socket_parse, NULL,
          acct_socket_recv, acct_socket_send,
-         generic_update, socket_print},
-
-       /* proxying */
-       { NULL, NULL,
-         proxy_socket_recv, proxy_socket_send,
-         generic_update, socket_print }, /* FIXME: update func is wrong! */
+         socket_print, client_socket_encode, client_socket_decode},
+#endif
 
+#ifdef WITH_DETAIL
        /* detail */
        { detail_parse, detail_free,
          detail_recv, detail_send,
-         generic_update, detail_print }
+         detail_print, detail_encode, detail_decode },
+#endif
+
+#ifdef WITH_VMPS
+       /* vlan query protocol */
+       { common_socket_parse, NULL,
+         vqp_socket_recv, vqp_socket_send,
+         socket_print, vqp_socket_encode, vqp_socket_decode },
+#endif
+
+#ifdef WITH_DHCP
+       /* dhcp query protocol */
+       { dhcp_socket_parse, NULL,
+         dhcp_socket_recv, dhcp_socket_send,
+         socket_print, dhcp_socket_encode, dhcp_socket_decode },
+#endif
+
+#ifdef WITH_COMMAND_SOCKET
+       /* TCP command socket */
+       { command_socket_parse, command_socket_free,
+         command_domain_accept, command_domain_send,
+         command_socket_print, command_socket_encode, command_socket_decode },
+#endif
+
+#ifdef WITH_COA
+       /* Change of Authorization */
+       { common_socket_parse, NULL,
+         coa_socket_recv, auth_socket_send, /* CoA packets are same as auth */
+         socket_print, client_socket_encode, client_socket_decode },
+#endif
+
 };
 
 
+
 /*
  *     Binds a listener to a socket.
  */
 static int listen_bind(rad_listen_t *this)
 {
-       rad_listen_t    **last;
+       int rcode;
+       struct sockaddr_storage salocal;
+       socklen_t       salen;
        listen_socket_t *sock = this->data;
+#ifndef WITH_TCP
+#define proto_for_port "udp"
+#define sock_type SOCK_DGRAM
+#else
+       const char *proto_for_port = "udp";
+       int sock_type = SOCK_DGRAM;
+       
+       if (sock->proto == IPPROTO_TCP) {
+#ifdef WITH_VMPS
+               if (this->type == RAD_LISTEN_VQP) {
+                       radlog(L_ERR, "VQP does not support TCP transport");
+                       return -1;
+               }
+#endif
+
+               proto_for_port = "tcp";
+               sock_type = SOCK_STREAM;        
+       }
+#endif
 
        /*
         *      If the port is zero, then it means the appropriate
         *      thing from /etc/services.
         */
-       if (sock->port == 0) {
+       if (sock->my_port == 0) {
                struct servent  *svp;
 
                switch (this->type) {
                case RAD_LISTEN_AUTH:
-                       svp = getservbyname ("radius", "udp");
+                       svp = getservbyname ("radius", proto_for_port);
                        if (svp != NULL) {
-                               sock->port = ntohs(svp->s_port);
+                               sock->my_port = ntohs(svp->s_port);
                        } else {
-                               sock->port = PW_AUTH_UDP_PORT;
+                               sock->my_port = PW_AUTH_UDP_PORT;
                        }
                        break;
 
+#ifdef WITH_ACCOUNTING
                case RAD_LISTEN_ACCT:
-                       svp = getservbyname ("radacct", "udp");
+                       svp = getservbyname ("radacct", proto_for_port);
+                       if (svp != NULL) {
+                               sock->my_port = ntohs(svp->s_port);
+                       } else {
+                               sock->my_port = PW_ACCT_UDP_PORT;
+                       }
+                       break;
+#endif
+
+#ifdef WITH_PROXY
+               case RAD_LISTEN_PROXY:
+                       /* leave it at zero */
+                       break;
+#endif
+
+#ifdef WITH_VMPS
+               case RAD_LISTEN_VQP:
+                       sock->my_port = 1589;
+                       break;
+#endif
+
+#ifdef WITH_COA
+               case RAD_LISTEN_COA:
+                       svp = getservbyname ("radius-dynauth", "udp");
                        if (svp != NULL) {
-                               sock->port = ntohs(svp->s_port);
+                               sock->my_port = ntohs(svp->s_port);
                        } else {
-                               sock->port = PW_ACCT_UDP_PORT;
+                               sock->my_port = PW_COA_UDP_PORT;
                        }
                        break;
+#endif
 
                default:
-                       radlog(L_ERR|L_CONS, "ERROR: Non-fatal internal sanity check failed in bind.");
+                       DEBUG("WARNING: Internal sanity check failed in binding to socket.  Ignoring problem.");
                        return -1;
                }
        }
 
        /*
-        *      Find it in the old list, AFTER updating the port.  If
-        *      it's there, use that, rather than creating a new
-        *      socket.  This allows HUP's to re-use the old sockets,
-        *      which means that packets waiting in the socket queue
-        *      don't get lost.
+        *      Copy fr_socket() here, as we may need to bind to a device.
+        */
+       this->fd = socket(sock->my_ipaddr.af, sock_type, 0);
+       if (this->fd < 0) {
+               radlog(L_ERR, "Failed opening socket: %s", strerror(errno));
+               return -1;
+       }
+               
+       /*
+        *      Bind to a device BEFORE touching IP addresses.
         */
-       for (last = &mainconfig.listen;
-            *last != NULL;
-            last = &((*last)->next)) {
-               if ((this->type == (*last)->type) &&
-                   (sock->port == ((listen_socket_t *)((*last)->data))->port) &&
-                   (sock->ipaddr.af == ((listen_socket_t *)((*last)->data))->ipaddr.af)) {
-                       int equal;
+       if (sock->interface) {
+#ifdef SO_BINDTODEVICE
+               struct ifreq ifreq;
+               strcpy(ifreq.ifr_name, sock->interface);
 
-                       if (sock->ipaddr.af == AF_INET) {
-                               equal = (sock->ipaddr.ipaddr.ip4addr.s_addr == ((listen_socket_t *)((*last)->data))->ipaddr.ipaddr.ip4addr.s_addr);
-                       } else if (sock->ipaddr.af == AF_INET6) {
-                               equal = IN6_ARE_ADDR_EQUAL(&(sock->ipaddr.ipaddr.ip6addr), &(((listen_socket_t *)((*last)->data))->ipaddr.ipaddr.ip6addr));
-                       } else {
-                               equal = 0;
+               fr_suid_up();
+               rcode = setsockopt(this->fd, SOL_SOCKET, SO_BINDTODEVICE,
+                                  (char *)&ifreq, sizeof(ifreq));
+               fr_suid_down();
+               if (rcode < 0) {
+                       close(this->fd);
+                       radlog(L_ERR, "Failed binding to interface %s: %s",
+                              sock->interface, strerror(errno));
+                       return -1;
+               } /* else it worked. */
+#else
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+#ifdef HAVE_NET_IF_H
+               /*
+                *      Odds are that any system supporting "bind to
+                *      device" also supports IPv6, so this next bit
+                *      isn't necessary.  But it's here for
+                *      completeness.
+                *
+                *      If we're doing IPv6, and the scope hasn't yet
+                *      been defined, set the scope to the scope of
+                *      the interface.
+                */
+               if (sock->my_ipaddr.af == AF_INET6) {
+                       if (sock->my_ipaddr.scope == 0) {
+                               sock->my_ipaddr.scope = if_nametoindex(sock->interface);
+                               if (sock->my_ipaddr.scope == 0) {
+                                       close(this->fd);
+                                       radlog(L_ERR, "Failed finding interface %s: %s",
+                                              sock->interface, strerror(errno));
+                                       return -1;
+                               }
+                       } /* else scope was defined: we're OK. */
+               } else
+#endif
+#endif
+                               /*
+                                *      IPv4: no link local addresses,
+                                *      and no bind to device.
+                                */
+               {
+                       close(this->fd);
+                       radlog(L_ERR, "Failed binding to interface %s: \"bind to device\" is unsupported", sock->interface);
+                       return -1;
+               }
+#endif
+       }
+
+#ifdef WITH_TCP
+       if (sock->proto == IPPROTO_TCP) {
+               int on = 1;
+
+               if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+                       close(this->fd);
+                       radlog(L_ERR, "Failed to reuse address: %s", strerror(errno));
+                       return -1;
+               }
+       }
+#endif
+
+#if defined(WITH_TCP) && defined(WITH_UDPFROMTO)
+       else                    /* UDP sockets get UDPfromto */
+#endif
+
+#ifdef WITH_UDPFROMTO
+       /*
+        *      Initialize udpfromto for all sockets.
+        */
+       if (udpfromto_init(this->fd) != 0) {
+               close(this->fd);
+               return -1;
+       }
+#endif
+
+       /*
+        *      Set up sockaddr stuff.
+        */
+       if (!fr_ipaddr2sockaddr(&sock->my_ipaddr, sock->my_port, &salocal, &salen)) {
+               close(this->fd);
+               return -1;
+       }
+               
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       if (sock->my_ipaddr.af == AF_INET6) {
+               /*
+                *      Listening on '::' does NOT get you IPv4 to
+                *      IPv6 mapping.  You've got to listen on an IPv4
+                *      address, too.  This makes the rest of the server
+                *      design a little simpler.
+                */
+#ifdef IPV6_V6ONLY
+               
+               if (IN6_IS_ADDR_UNSPECIFIED(&sock->my_ipaddr.ipaddr.ip6addr)) {
+                       int on = 1;
+                       
+                       setsockopt(this->fd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                  (char *)&on, sizeof(on));
+               }
+#endif /* IPV6_V6ONLY */
+       }
+#endif /* HAVE_STRUCT_SOCKADDR_IN6 */
+
+       if (sock->my_ipaddr.af == AF_INET) {
+               UNUSED int flag;
+               
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+               /*
+                *      Disable PMTU discovery.  On Linux, this
+                *      also makes sure that the "don't fragment"
+                *      flag is zero.
+                */
+               flag = IP_PMTUDISC_DONT;
+               setsockopt(this->fd, IPPROTO_IP, IP_MTU_DISCOVER,
+                          &flag, sizeof(flag));
+#endif
+
+#if defined(IP_DONTFRAG)
+               /*
+                *      Ensure that the "don't fragment" flag is zero.
+                */
+               flag = 0;
+               setsockopt(this->fd, IPPROTO_IP, IP_DONTFRAG,
+                          &flag, sizeof(flag));
+#endif
+       }
+
+#ifdef WITH_DHCP
+#ifdef SO_BROADCAST
+       if (sock->broadcast) {
+               int on = 1;
+               
+               if (setsockopt(this->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
+                       radlog(L_ERR, "Can't set broadcast option: %s\n",
+                              strerror(errno));
+                       return -1;
+               }
+       }
+#endif
+#endif
+
+       /*
+        *      May be binding to priviledged ports.
+        */
+       if (sock->my_port != 0) {
+#ifdef SO_REUSEADDR
+               int on = 1;
+
+               if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+                       radlog(L_ERR, "Can't set re-use address option: %s\n",
+                              strerror(errno));
+                       return -1;
+               }
+#endif
+
+               fr_suid_up();
+               rcode = bind(this->fd, (struct sockaddr *) &salocal, salen);
+               fr_suid_down();
+               if (rcode < 0) {
+                       char buffer[256];
+                       close(this->fd);
+                       
+                       this->print(this, buffer, sizeof(buffer));
+                       radlog(L_ERR, "Failed binding to %s: %s\n",
+                              buffer, strerror(errno));
+                       return -1;
+               }
+       
+               /*
+                *      FreeBSD jail issues.  We bind to 0.0.0.0, but the
+                *      kernel instead binds us to a 1.2.3.4.  If this
+                *      happens, notice, and remember our real IP.
+                */
+               {
+                       struct sockaddr_storage src;
+                       socklen_t               sizeof_src = sizeof(src);
+                       
+                       memset(&src, 0, sizeof_src);
+                       if (getsockname(this->fd, (struct sockaddr *) &src,
+                                       &sizeof_src) < 0) {
+                               radlog(L_ERR, "Failed getting socket name: %s",
+                                      strerror(errno));
+                               return -1;
                        }
                        
-                       if (equal) {
-                               this->rl = (*last)->rl;
-                               this->fd = (*last)->fd;
-                               (*last)->fd = -1;
-                               (*last)->rl = NULL;
-                               return 0;
+                       if (!fr_sockaddr2ipaddr(&src, sizeof_src,
+                                               &sock->my_ipaddr, &sock->my_port)) {
+                               radlog(L_ERR, "Socket has unsupported address family");
+                               return -1;
                        }
                }
        }
 
-       this->fd = lrad_socket(&sock->ipaddr, sock->port);
-       if (this->fd < 0) {
-               radlog(L_ERR|L_CONS, "ERROR: Failed to open socket: %s",
-                      librad_errstr);
-               return -1;
-       }
+#ifdef WITH_TCP
+       if (sock->proto == IPPROTO_TCP) {
+               if (listen(this->fd, 8) < 0) {
+                       close(this->fd);
+                       radlog(L_ERR, "Failed in listen(): %s", strerror(errno));
+                       return -1;
+               }
+       } else
+#endif
+
+         if (fr_nonblock(this->fd) < 0) {
+                 close(this->fd);
+                 radlog(L_ERR, "Failed setting non-blocking on socket: %s",
+                        strerror(errno));
+                 return -1;
+         }
+
+       /*
+        *      Mostly for proxy sockets.
+        */
+       sock->other_ipaddr.af = sock->my_ipaddr.af;
+
+/*
+ *     Don't screw up other people.
+ */
+#undef proto_for_port
+#undef sock_type
 
        return 0;
 }
@@ -1657,119 +2192,302 @@ static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type)
        this->type = type;
        this->recv = master_listen[this->type].recv;
        this->send = master_listen[this->type].send;
-       this->update = master_listen[this->type].update;
        this->print = master_listen[this->type].print;
+       this->encode = master_listen[this->type].encode;
+       this->decode = master_listen[this->type].decode;
 
        switch (type) {
+#ifdef WITH_STATS
+       case RAD_LISTEN_NONE:
+#endif
        case RAD_LISTEN_AUTH:
+#ifdef WITH_ACCOUNTING
        case RAD_LISTEN_ACCT:
+#endif
+#ifdef WITH_PROXY
        case RAD_LISTEN_PROXY:
+#endif
+#ifdef WITH_VMPS
+       case RAD_LISTEN_VQP:
+#endif
+#ifdef WITH_COA
+       case RAD_LISTEN_COA:
+#endif
                this->data = rad_malloc(sizeof(listen_socket_t));
                memset(this->data, 0, sizeof(listen_socket_t));
                break;
 
+#ifdef WITH_DHCP
+       case RAD_LISTEN_DHCP:
+               this->data = rad_malloc(sizeof(dhcp_socket_t));
+               memset(this->data, 0, sizeof(dhcp_socket_t));
+               break;
+#endif
+
+#ifdef WITH_DETAIL
        case RAD_LISTEN_DETAIL:
-               this->data = rad_malloc(sizeof(listen_detail_t));
-               memset(this->data, 0, sizeof(listen_detail_t));
+               this->data = NULL;
+               break;
+#endif
+
+#ifdef WITH_COMMAND_SOCKET
+       case RAD_LISTEN_COMMAND:
+               this->data = rad_malloc(sizeof(fr_command_socket_t));
+               memset(this->data, 0, sizeof(fr_command_socket_t));
+               break;
+#endif
+
+       default:
+               rad_assert("Unsupported option!" == NULL);
+               break;
+       }
+
+       return this;
+}
+
+#ifdef WITH_PROXY
+/*
+ *     Externally visible function for creating a new proxy LISTENER.
+ *
+ *     Not thread-safe, but all calls to it are protected by the
+ *     proxy mutex in event.c
+ */
+int proxy_new_listener(home_server *home, int src_port)
+{
+       rad_listen_t *this;
+       listen_socket_t *sock;
+
+       if (!home) return 0;
+
+       if ((home->max_connections > 0) &&
+           (home->num_connections >= home->max_connections)) {
+               DEBUG("WARNING: Home server has too many open connections (%d)",
+                     home->max_connections);
+               return 0;
+       }
+
+       this = listen_alloc(RAD_LISTEN_PROXY);
+
+       sock = this->data;
+       sock->other_ipaddr = home->ipaddr;
+       sock->other_port = home->port;
+       sock->home = home;
+
+       sock->my_ipaddr = home->src_ipaddr;
+       sock->my_port = src_port;
+       sock->proto = home->proto;
+
+#ifdef WITH_TCP
+       sock->last_packet = time(NULL);
+
+       if (home->proto == IPPROTO_TCP) {
+               this->recv = proxy_socket_tcp_recv;
+
+               /*
+                *      FIXME: connect() is blocking!
+                *      We do this with the proxy mutex locked, which may
+                *      cause large delays!
+                *
+                *      http://www.developerweb.net/forum/showthread.php?p=13486
+                */
+               this->fd = fr_tcp_client_socket(&home->src_ipaddr,
+                                               &home->ipaddr, home->port);
+       } else
+#endif
+               this->fd = fr_socket(&home->src_ipaddr, src_port);
+
+       if (this->fd < 0) {
+               DEBUG("Failed opening client socket: %s", fr_strerror());
+               listen_free(&this);
+               return 0;
+       }
+
+       /*
+        *      Figure out which port we were bound to.
+        */
+       if (sock->my_port == 0) {
+               struct sockaddr_storage src;
+               socklen_t               sizeof_src = sizeof(src);
                
-       default:
-               break;
+               memset(&src, 0, sizeof_src);
+               if (getsockname(this->fd, (struct sockaddr *) &src,
+                               &sizeof_src) < 0) {
+                       radlog(L_ERR, "Failed getting socket name: %s",
+                              strerror(errno));
+                       listen_free(&this);
+                       return 0;
+               }
+               
+               if (!fr_sockaddr2ipaddr(&src, sizeof_src,
+                                       &sock->my_ipaddr, &sock->my_port)) {
+                       radlog(L_ERR, "Socket has unsupported address family");
+                       listen_free(&this);
+                       return 0;
+               }
        }
 
-       return this;
+       /*
+        *      Tell the event loop that we have a new FD
+        */
+       if (!event_new_fd(this)) {
+               listen_free(&this);
+               return 0;
+       }
+       
+       return 1;
 }
+#endif
 
 
-/*
- *     Externally visible function for creating a new proxy LISTENER.
- *
- *     For now, don't take ipaddr or port.
- *
- *     Not thread-safe, but all calls to it are protected by the
- *     proxy mutex in request_list.c
- */
-rad_listen_t *proxy_new_listener()
+static const FR_NAME_NUMBER listen_compare[] = {
+#ifdef WITH_STATS
+       { "status",     RAD_LISTEN_NONE },
+#endif
+       { "auth",       RAD_LISTEN_AUTH },
+#ifdef WITH_ACCOUNTING
+       { "acct",       RAD_LISTEN_ACCT },
+#endif
+#ifdef WITH_DETAIL
+       { "detail",     RAD_LISTEN_DETAIL },
+#endif
+#ifdef WITH_PROXY
+       { "proxy",      RAD_LISTEN_PROXY },
+#endif
+#ifdef WITH_VMPS
+       { "vmps",       RAD_LISTEN_VQP },
+#endif
+#ifdef WITH_DHCP
+       { "dhcp",       RAD_LISTEN_DHCP },
+#endif
+#ifdef WITH_COMMAND_SOCKET
+       { "control",    RAD_LISTEN_COMMAND },
+#endif
+#ifdef WITH_COA
+       { "coa",        RAD_LISTEN_COA },
+#endif
+       { NULL, 0 },
+};
+
+
+static rad_listen_t *listen_parse(CONF_SECTION *cs, const char *server)
 {
-       int last_proxy_port, port;
-       rad_listen_t *this, *tmp, **last;
-       listen_socket_t *sock, *old;
+       int             type, rcode;
+       char            *listen_type;
+       rad_listen_t    *this;
 
-       this = listen_alloc(RAD_LISTEN_PROXY);
+       listen_type = NULL;
+       
+       cf_log_info(cs, "listen {");
+
+       rcode = cf_item_parse(cs, "type", PW_TYPE_STRING_PTR,
+                             &listen_type, "");
+       if (rcode < 0) return NULL;
+       if (rcode == 1) {
+               free(listen_type);
+               cf_log_err(cf_sectiontoitem(cs),
+                          "No type specified in listen section");
+               return NULL;
+       }
+
+       type = fr_str2int(listen_compare, listen_type, -1);
+       if (type < 0) {
+               cf_log_err(cf_sectiontoitem(cs),
+                          "Invalid type \"%s\" in listen section.",
+                          listen_type);
+               free(listen_type);
+               return NULL;
+       }
+       free(listen_type);
 
        /*
-        *      Find an existing proxy socket to copy.
-        *
-        *      FIXME: Make it per-realm, or per-home server!
-        */
-       last_proxy_port = 0;
-       old = NULL;
-       last = &mainconfig.listen;
-       for (tmp = mainconfig.listen; tmp != NULL; tmp = tmp->next) {
-               if (tmp->type == RAD_LISTEN_PROXY) {
-                       sock = tmp->data;
-                       if (sock->port > last_proxy_port) {
-                               last_proxy_port = sock->port + 1;
-                       }
-                       if (!old) old = sock;
+        *      Allow listen sections in the default config to
+        *      refer to a server.
+        */
+       if (!server) {
+               rcode = cf_item_parse(cs, "virtual_server", PW_TYPE_STRING_PTR,
+                                     &server, NULL);
+               if (rcode == 1) { /* compatiblity with 2.0-pre */
+                       rcode = cf_item_parse(cs, "server", PW_TYPE_STRING_PTR,
+                                             &server, NULL);
                }
-
-               last = &(tmp->next);
+               if (rcode < 0) return NULL;
        }
 
-       if (!old) return NULL;  /* This is a serious error. */
+#ifdef WITH_PROXY
+       /*
+        *      We were passed a virtual server, so the caller is
+        *      defining a proxy listener inside of a virtual server.
+        *      This isn't allowed right now.
+        */
+       else if (type == RAD_LISTEN_PROXY) {
+               radlog(L_ERR, "Error: listen type \"proxy\" Cannot appear in a virtual server section");
+               return NULL;
+       }
+#endif
 
        /*
-        *      FIXME: find a new IP address to listen on?
+        *      Set up cross-type data.
         */
-       sock = this->data;
-       memcpy(&sock->ipaddr, &old->ipaddr, sizeof(sock->ipaddr));
+       this = listen_alloc(type);
+       this->server = server;
+       this->fd = -1;
 
        /*
-        *      Keep going until we find an unused port.
+        *      Call per-type parser.
         */
-       for (port = last_proxy_port; port < 64000; port++) {
-               sock->port = port;
-               if (listen_bind(this) == 0) {
-                       /*
-                        *      Add the new listener to the list of
-                        *      listeners.
-                        */
-                       *last = this;
-                       return this;
-               }
+       if (master_listen[type].parse(cs, this) < 0) {
+               listen_free(&this);
+               return NULL;
        }
 
-       return NULL;
-}
+       cf_log_info(cs, "}");
 
+       return this;
+}
 
-static const LRAD_NAME_NUMBER listen_compare[] = {
-       { "auth",       RAD_LISTEN_AUTH },
-       { "acct",       RAD_LISTEN_ACCT },
-       { "detail",     RAD_LISTEN_DETAIL },
-       { NULL, 0 },
-};
+#ifdef WITH_PROXY
+static int is_loopback(const fr_ipaddr_t *ipaddr)
+{
+       /*
+        *      We shouldn't proxy on loopback.
+        */
+       if ((ipaddr->af == AF_INET) &&
+           (ipaddr->ipaddr.ip4addr.s_addr == htonl(INADDR_LOOPBACK))) {
+               return 1;
+       }
+       
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       if ((ipaddr->af == AF_INET6) &&
+           (IN6_IS_ADDR_LINKLOCAL(&ipaddr->ipaddr.ip6addr))) {
+               return 1;
+       }
+#endif
 
+       return 0;
+}
+#endif
 
 /*
  *     Generate a list of listeners.  Takes an input list of
  *     listeners, too, so we don't close sockets with waiting packets.
  */
-int listen_init(const char *filename, rad_listen_t **head)
+int listen_init(CONF_SECTION *config, rad_listen_t **head)
 {
+       int             override = FALSE;
        int             rcode;
-       CONF_SECTION    *cs;
+       CONF_SECTION    *cs = NULL;
        rad_listen_t    **last;
        rad_listen_t    *this;
-       lrad_ipaddr_t   server_ipaddr;
+       fr_ipaddr_t     server_ipaddr;
        int             auth_port = 0;
+#ifdef WITH_PROXY
+       int             defined_proxy = 0;
+#endif
 
        /*
         *      We shouldn't be called with a pre-existing list.
         */
        rad_assert(head && (*head == NULL));
-       
-       if (start_time != 0) start_time = time(NULL);
 
        last = head;
        server_ipaddr.af = AF_UNSPEC;
@@ -1777,18 +2495,10 @@ int listen_init(const char *filename, rad_listen_t **head)
        /*
         *      If the port is specified on the command-line,
         *      it over-rides the configuration file.
+        *
+        *      FIXME: If argv[0] == "vmpsd", then don't listen on auth/acct!
         */
-       if (mainconfig.port >= 0) {
-               auth_port = mainconfig.port;
-       } else {
-               rcode = cf_item_parse(mainconfig.config, "port",
-                                     PW_TYPE_INTEGER, &auth_port,
-                                     Stringify(PW_AUTH_UDP_PORT));
-               if (rcode < 0) return -1; /* error parsing it */
-
-               if (rcode == 0)
-                       radlog(L_INFO, "WARNING: The directive 'port' is deprecated, and will be removed in future versions of FreeRADIUS. Please edit the configuration files to use the directive 'listen'.");
-       }
+       if (mainconfig.port >= 0) auth_port = mainconfig.port;
 
        /*
         *      If the IP address was configured on the command-line,
@@ -1797,6 +2507,7 @@ int listen_init(const char *filename, rad_listen_t **head)
        if (mainconfig.myip.af != AF_UNSPEC) {
                memcpy(&server_ipaddr, &mainconfig.myip,
                       sizeof(server_ipaddr));
+               override = TRUE;
                goto bind_it;
        }
 
@@ -1804,34 +2515,63 @@ int listen_init(const char *filename, rad_listen_t **head)
         *      Else look for bind_address and/or listen sections.
         */
        server_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
-       rcode = cf_item_parse(mainconfig.config, "bind_address",
+       rcode = cf_item_parse(config, "bind_address",
                              PW_TYPE_IPADDR,
                              &server_ipaddr.ipaddr.ip4addr, NULL);
        if (rcode < 0) return -1; /* error parsing it */
-       
+
        if (rcode == 0) { /* successfully parsed IPv4 */
                listen_socket_t *sock;
                server_ipaddr.af = AF_INET;
 
-               radlog(L_INFO, "WARNING: The directive 'bind_adress' is deprecated, and will be removed in future versions of FreeRADIUS. Please edit the configuration files to use the directive 'listen'.");
+               radlog(L_INFO, "WARNING: The directive 'bind_address' is deprecated, and will be removed in future versions of FreeRADIUS. Please edit the configuration files to use the directive 'listen'.");
 
        bind_it:
-               this = listen_alloc(RAD_LISTEN_AUTH);
+#ifdef WITH_VMPS
+               if (strcmp(progname, "vmpsd") == 0) {
+                       this = listen_alloc(RAD_LISTEN_VQP);
+                       if (!auth_port) auth_port = 1589;
+               } else
+#endif
+                       this = listen_alloc(RAD_LISTEN_AUTH);
+
                sock = this->data;
 
-               sock->ipaddr = server_ipaddr;
-               sock->port = auth_port;
-               
-               if (listen_bind(this) < 0) {
+               sock->my_ipaddr = server_ipaddr;
+               sock->my_port = auth_port;
+
+               sock->clients = clients_parse_section(config);
+               if (!sock->clients) {
+                       cf_log_err(cf_sectiontoitem(config),
+                                  "Failed to find any clients for this listen section");
                        listen_free(&this);
+                       return -1;
+               }
+
+               if (listen_bind(this) < 0) {
                        listen_free(head);
-                       radlog(L_CONS|L_ERR, "There appears to be another RADIUS server running on the authentication port %d", sock->port);
+                       radlog(L_ERR, "There appears to be another RADIUS server running on the authentication port %d", sock->my_port);
+                       listen_free(&this);
                        return -1;
                }
-               auth_port = sock->port; /* may have been updated in listen_bind */
+               auth_port = sock->my_port;      /* may have been updated in listen_bind */
+               if (override) {
+                       cs = cf_section_sub_find_name2(config, "server",
+                                                      mainconfig.name);
+                       if (cs) this->server = mainconfig.name;
+               }
+
                *last = this;
                last = &(this->next);
-               
+
+#ifdef WITH_VMPS
+               /*
+                *      No acct for vmpsd
+                */
+               if (strcmp(progname, "vmpsd") == 0) goto add_sockets;
+#endif
+
+#ifdef WITH_ACCOUNTING
                /*
                 *      Open Accounting Socket.
                 *
@@ -1840,144 +2580,159 @@ int listen_init(const char *filename, rad_listen_t **head)
                 */
                this = listen_alloc(RAD_LISTEN_ACCT);
                sock = this->data;
-               
+
                /*
                 *      Create the accounting socket.
                 *
                 *      The accounting port is always the
                 *      authentication port + 1
                 */
-               sock->ipaddr = server_ipaddr;
-               sock->port = auth_port + 1;
-               
+               sock->my_ipaddr = server_ipaddr;
+               sock->my_port = auth_port + 1;
+
+               sock->clients = clients_parse_section(config);
+               if (!sock->clients) {
+                       cf_log_err(cf_sectiontoitem(config),
+                                  "Failed to find any clients for this listen section");
+                       return -1;
+               }
+
                if (listen_bind(this) < 0) {
                        listen_free(&this);
                        listen_free(head);
-                       radlog(L_CONS|L_ERR, "There appears to be another RADIUS server running on the accounting port %d", sock->port);
+                       radlog(L_ERR, "There appears to be another RADIUS server running on the accounting port %d", sock->my_port);
                        return -1;
                }
 
+               if (override) {
+                       cs = cf_section_sub_find_name2(config, "server",
+                                                      mainconfig.name);
+                       if (cs) this->server = mainconfig.name;
+               }
+
                *last = this;
                last = &(this->next);
-
+#endif
        } else if (mainconfig.port > 0) { /* no bind address, but a port */
-               radlog(L_CONS|L_ERR, "The command-line says \"-p %d\", but there is no associated IP address to use",
+               radlog(L_ERR, "The command-line says \"-p %d\", but there is no associated IP address to use",
                       mainconfig.port);
                return -1;
        }
 
        /*
         *      They specified an IP on the command-line, ignore
-        *      all listen sections.
+        *      all listen sections except the one in '-n'.
         */
-       if (mainconfig.myip.af != AF_UNSPEC) goto do_proxy;
+       if (mainconfig.myip.af != AF_UNSPEC) {
+               CONF_SECTION *subcs;
+               const char *name2 = cf_section_name2(cs);
+
+               cs = cf_section_sub_find_name2(config, "server",
+                                              mainconfig.name);
+               if (!cs) goto add_sockets;
+
+               /*
+                *      Should really abstract this code...
+                */
+               for (subcs = cf_subsection_find_next(cs, NULL, "listen");
+                    subcs != NULL;
+                    subcs = cf_subsection_find_next(cs, subcs, "listen")) {
+                       this = listen_parse(subcs, name2);
+                       if (!this) {
+                               listen_free(head);
+                               return -1;
+                       }
+
+                       *last = this;
+                       last = &(this->next);
+               } /* loop over "listen" directives in server <foo> */
+
+               goto add_sockets;
+       }
 
        /*
         *      Walk through the "listen" sections, if they exist.
         */
-       for (cs = cf_subsection_find_next(mainconfig.config, NULL, "listen");
+       for (cs = cf_subsection_find_next(config, NULL, "listen");
             cs != NULL;
-            cs = cf_subsection_find_next(mainconfig.config, cs, "listen")) {
-               int             type;
-               char            *listen_type, *identity;
-               int             lineno = cf_section_lineno(cs);
-
-               listen_type = identity = NULL;
-               
-               rcode = cf_item_parse(cs, "type", PW_TYPE_STRING_PTR,
-                                     &listen_type, "");
-               if (rcode < 0) return -1;
-               if (rcode == 1) {
-                       listen_free(head);
-                       free(listen_type);
-                       radlog(L_ERR, "%s[%d]: No type specified in listen section",
-                              filename, lineno);
-                       return -1;
-               }
-
-               /*
-                *      See if there's an identity.
-                */
-               rcode = cf_item_parse(cs, "identity", PW_TYPE_STRING_PTR,
-                                     &identity, NULL);
-               if (rcode < 0) {
+            cs = cf_subsection_find_next(config, cs, "listen")) {
+               this = listen_parse(cs, NULL);
+               if (!this) {
                        listen_free(head);
-                       free(identity);
                        return -1;
                }
 
-               type = lrad_str2int(listen_compare, listen_type,
-                                   RAD_LISTEN_NONE);
-               free(listen_type);
-               if (type == RAD_LISTEN_NONE) {
-                       listen_free(head);
-                       radlog(L_CONS|L_ERR, "%s[%d]: Invalid type in listen section.",
-                              filename, lineno);
-                       return -1;
-               }
+               *last = this;
+               last = &(this->next);
+       }
 
-               /*
-                *      Set up cross-type data.
-                */
-               this = listen_alloc(type);
-               this->identity = identity;
-               this->fd = -1;
+       /*
+        *      Check virtual servers for "listen" sections, too.
+        *
+        *      FIXME: Move to virtual server init?
+        */
+       for (cs = cf_subsection_find_next(config, NULL, "server");
+            cs != NULL;
+            cs = cf_subsection_find_next(config, cs, "server")) {
+               CONF_SECTION *subcs;
+               const char *name2 = cf_section_name2(cs);
                
-               /*
-                *      Call per-type parser.
-                */
-               if (master_listen[type].parse(filename, lineno,
-                                             cs, this) < 0) {
-                       listen_free(&this);
-                       listen_free(head);
-                       return -1;
+               for (subcs = cf_subsection_find_next(cs, NULL, "listen");
+                    subcs != NULL;
+                    subcs = cf_subsection_find_next(cs, subcs, "listen")) {
+                       this = listen_parse(subcs, name2);
+                       if (!this) {
+                               listen_free(head);
+                               return -1;
+                       }
+                       
+                       *last = this;
+                       last = &(this->next);
+               } /* loop over "listen" directives in virtual servers */
+       } /* loop over virtual servers */
+
+add_sockets:
+       /*
+        *      No sockets to receive packets, this is an error.
+        *      proxying is pointless.
+        */
+       if (!*head) {
+               radlog(L_ERR, "The server is not configured to listen on any ports.  Cannot start.");
+               return -1;
+       }
+
+       /*
+        *      Print out which sockets we're listening on, and
+        *      add them to the event list.
+        */
+       for (this = *head; this != NULL; this = this->next) {
+#ifdef WITH_PROXY
+               if (this->type == RAD_LISTEN_PROXY) {
+                       defined_proxy = 1;
                }
 
-               *last = this;
-               last = &(this->next);   
+#endif
+               event_new_fd(this);
        }
 
        /*
         *      If we're proxying requests, open the proxy FD.
         *      Otherwise, don't do anything.
         */
- do_proxy:
-       if (mainconfig.proxy_requests == TRUE) {
-               int             port = -1;
+#ifdef WITH_PROXY
+       if ((mainconfig.proxy_requests == TRUE) &&
+           (*head != NULL) && !defined_proxy) {
                listen_socket_t *sock = NULL;
+               int             port = 0;
+               home_server     home;
 
-               /*
-                *      No sockets to receive packets, therefore
-                *      proxying is pointless.
-                */
-               if (!*head) return -1;
+               memset(&home, 0, sizeof(home));
 
                /*
-                *      If we previously had proxy sockets, copy them
-                *      to the new config.
+                *      
                 */
-               if (mainconfig.listen != NULL) {
-                       rad_listen_t *old, *next, **tail;
-
-                       tail = &mainconfig.listen;
-                       for (old = mainconfig.listen;
-                            old != NULL;
-                            old = next) {
-                               next = old->next;
-
-                               if (old->type != RAD_LISTEN_PROXY) {
-                                       tail = &((*tail)->next);
-                                       continue;
-                               }
-
-                               *last = old;
-                               *tail = next;
-                               old->next = NULL;
-                               last = &(old->next);
-                       }
-
-                       goto done;
-               }
+               home.proto = IPPROTO_UDP;
+               home.src_ipaddr = server_ipaddr;
 
                /*
                 *      Find the first authentication port,
@@ -1986,88 +2741,54 @@ int listen_init(const char *filename, rad_listen_t **head)
                for (this = *head; this != NULL; this = this->next) {
                        if (this->type == RAD_LISTEN_AUTH) {
                                sock = this->data;
-                               if (server_ipaddr.af == AF_UNSPEC) {
-                                       server_ipaddr = sock->ipaddr;
+
+                               if (is_loopback(&sock->my_ipaddr)) continue;
+
+                               if (home.src_ipaddr.af == AF_UNSPEC) {
+                                       home.src_ipaddr = sock->my_ipaddr;
                                }
-                               port = sock->port + 2; /* skip acct port */
+                               port = sock->my_port + 2;
                                break;
                        }
-               }
+#ifdef WITH_ACCT
+                       if (this->type == RAD_LISTEN_ACCT) {
+                               sock = this->data;
 
-               if (port < 0) port = 1024 + (lrad_rand() & 0x1ff);
+                               if (is_loopback(&sock->my_ipaddr)) continue;
 
-               /*
-                *      Address is still unspecified, use IPv4.
-                */
-               if (server_ipaddr.af == AF_UNSPEC) {
-                       server_ipaddr.af = AF_INET;
-                       server_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_ANY);
+                               if (home.src_ipaddr.af == AF_UNSPEC) {
+                                       home.src_ipaddr = sock->my_ipaddr;
+                               }
+                               port = sock->my_port + 1;
+                               break;
+                       }
+#endif
                }
 
-               this = listen_alloc(RAD_LISTEN_PROXY);
-               sock = this->data;
-
-               /*
-                *      Create the first proxy socket.
-                */
-               sock->ipaddr = server_ipaddr;
-
                /*
-                *      Try to find a proxy port (value doesn't matter)
+                *      Address is still unspecified, use IPv4.
                 */
-               for (sock->port = port;
-                    sock->port < 64000;
-                    sock->port++) {
-                       if (listen_bind(this) == 0) {
-                               *last = this;
-                               last = &(this->next); /* just in case */
-                               break;
-                       }
+               if (home.src_ipaddr.af == AF_UNSPEC) {
+                       home.src_ipaddr.af = AF_INET;
+                       /* everything else is already set to zero */
                }
 
-               if (sock->port >= 64000) {
+               home.ipaddr.af = home.src_ipaddr.af;
+               /* everything else is already set to zero */
+
+               if (!proxy_new_listener(&home, port)) {
                        listen_free(head);
-                       listen_free(&this);
-                       radlog(L_ERR|L_CONS, "Failed to open socket for proxying");
                        return -1;
                }
        }
+#endif
 
        /*
-        *      Sanity check the configuration.
+        *      Haven't defined any sockets.  Die.
         */
-       rcode = 0;
-       for (this = *head; this != NULL; this = this->next) {
-               if ((this->type != RAD_LISTEN_PROXY) &&
-                   !this->rl) {
-                       /*
-                        *      FIXME: Pass type to rl_init, so that
-                        *      it knows how to deal with accounting
-                        *      packets.  i.e. it caches them, but
-                        *      doesn't bother trying to re-transmit.
-                        */
-                       this->rl = rl_init();
-                       if (!this->rl) {
-                               rad_assert(0 == 1); /* FIXME: */
-                       }
-               }
+       if (!*head) return -1;
 
-               if (((this->type == RAD_LISTEN_ACCT) &&
-                    (rcode == RAD_LISTEN_DETAIL)) ||
-                   ((this->type == RAD_LISTEN_DETAIL) &&
-                    (rcode == RAD_LISTEN_ACCT))) {
-                       rad_assert(0 == 1); /* FIXME: configuration error */
-               }
-
-               if (rcode != 0) continue;
-
-               if ((this->type == RAD_LISTEN_ACCT) ||
-                   (this->type == RAD_LISTEN_DETAIL)) {
-                       rcode = this->type;
-               }
-       }
 
- done:
        return 0;
 }
 
@@ -2083,10 +2804,6 @@ void listen_free(rad_listen_t **head)
        this = *head;
        while (this) {
                rad_listen_t *next = this->next;
-               
-               free(this->identity);
-
-               rl_deinit(this->rl);
 
                /*
                 *      Other code may have eaten the FD.
@@ -2096,11 +2813,86 @@ void listen_free(rad_listen_t **head)
                if (master_listen[this->type].free) {
                        master_listen[this->type].free(this);
                }
+
+#ifdef WITH_TCP
+               if ((this->type == RAD_LISTEN_AUTH)
+#ifdef WITH_ACCT
+                   || (this->type == RAD_LISTEN_ACCT)
+#endif
+#ifdef WITH_PROXY
+                   || (this->type == RAD_LISTEN_PROXY)
+#endif
+                       ) {
+                       listen_socket_t *sock = this->data;
+                       rad_free(&sock->packet);
+               }
+#endif
+
                free(this->data);
                free(this);
-               
+
                this = next;
        }
 
        *head = NULL;
 }
+
+#ifdef WITH_STATS
+RADCLIENT_LIST *listener_find_client_list(const fr_ipaddr_t *ipaddr,
+                                         int port)
+{
+       rad_listen_t *this;
+
+       for (this = mainconfig.listen; this != NULL; this = this->next) {
+               listen_socket_t *sock;
+
+               if ((this->type != RAD_LISTEN_AUTH)
+#ifdef WITH_ACCOUNTING
+                   && (this->type != RAD_LISTEN_ACCT)
+#endif
+                   ) continue;
+               
+               sock = this->data;
+
+               if ((sock->my_port == port) &&
+                   (fr_ipaddr_cmp(ipaddr, &sock->my_ipaddr) == 0)) {
+                       return sock->clients;
+               }
+       }
+
+       return NULL;
+}
+#endif
+
+rad_listen_t *listener_find_byipaddr(const fr_ipaddr_t *ipaddr, int port)
+{
+       rad_listen_t *this;
+
+       for (this = mainconfig.listen; this != NULL; this = this->next) {
+               listen_socket_t *sock;
+
+               /*
+                *      FIXME: For TCP, ignore the *secondary*
+                *      listeners associated with the main socket.
+                */
+               if ((this->type != RAD_LISTEN_AUTH)
+#ifdef WITH_ACCOUNTING
+                   && (this->type != RAD_LISTEN_ACCT)
+#endif
+                   ) continue;
+               
+               sock = this->data;
+
+               if ((sock->my_port == port) &&
+                   (fr_ipaddr_cmp(ipaddr, &sock->my_ipaddr) == 0)) {
+                       return this;
+               }
+
+               if ((sock->my_port == port) &&
+                   fr_inaddr_any(&sock->my_ipaddr)) {
+                       return this;
+               }
+       }
+
+       return NULL;
+}