timeout for connect
[radsecproxy.git] / radsecproxy.c
index 7aaf9f3..5aae539 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
+ * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -30,7 +30,7 @@
  * If TLS peers are configured, there will initially be 2 * #peers TLS threads
  * For each TLS peer connecting to us there will be 2 more TLS threads
  *       This is only for connected peers
- * Example: With 3 UDP peer and 30 TLS peers, there will be a max of
+ * Example: With 3 UDP peers and 30 TLS peers, there will be a max of
  *          1 + (2 + 2 * 3) + (2 * 30) + (2 * 30) = 129 threads
 */
 
@@ -53,7 +53,6 @@
 #endif
 #include <sys/time.h>
 #include <sys/types.h>
-#include <sys/select.h>
 #include <ctype.h>
 #include <sys/wait.h>
 #include <arpa/inet.h>
 #include <openssl/rand.h>
 #include <openssl/err.h>
 #include <openssl/md5.h>
-#include <openssl/hmac.h>
-#include <openssl/x509v3.h>
 #include "debug.h"
 #include "list.h"
 #include "hash.h"
 #include "util.h"
+#include "hostport.h"
 #include "radsecproxy.h"
 #include "udp.h"
 #include "tcp.h"
@@ -88,7 +86,7 @@ extern char *optarg;
 static const struct protodefs *protodefs[RAD_PROTOCOUNT];
 
 /* minimum required declarations to avoid reordering code */
-struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf, char *id);
+struct realm *adddynamicrealmserver(struct realm *realm, char *id);
 int dynamicconfig(struct server *server);
 int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val);
 void freerealm(struct realm *realm);
@@ -121,155 +119,6 @@ void ssl_locking_callback(int mode, int type, const char *file, int line) {
        pthread_mutex_unlock(&ssl_locks[type]);
 }
 
-int resolvepeer(struct clsrvconf *conf, int ai_flags) {
-    struct addrinfo hints, *addrinfo, *res;
-    char *slash, *s;
-    int plen = 0;
-
-    slash = conf->host ? strchr(conf->host, '/') : NULL;
-    if (slash) {
-       s = slash + 1;
-       if (!*s) {
-           debug(DBG_WARN, "resolvepeer: prefix length must be specified after the / in %s", conf->host);
-           return 0;
-       }
-       for (; *s; s++)
-           if (*s < '0' || *s > '9') {
-               debug(DBG_WARN, "resolvepeer: %s in %s is not a valid prefix length", slash + 1, conf->host);
-               return 0;
-           }
-       plen = atoi(slash + 1);
-       if (plen < 0 || plen > 128) {
-           debug(DBG_WARN, "resolvepeer: %s in %s is not a valid prefix length", slash + 1, conf->host);
-           return 0;
-       }
-       *slash = '\0';
-    }
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_socktype = conf->pdef->socktype;
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_flags = ai_flags;
-    if (!conf->host && !conf->port) {
-       /* getaddrinfo() doesn't like host and port to be NULL */
-       if (getaddrinfo(conf->host, conf->pdef->portdefault, &hints, &addrinfo)) {
-           debug(DBG_WARN, "resolvepeer: can't resolve (null) port (null)");
-           return 0;
-       }
-       for (res = addrinfo; res; res = res->ai_next)
-           port_set(res->ai_addr, 0);
-    } else {
-       if (slash)
-           hints.ai_flags |= AI_NUMERICHOST;
-       if (getaddrinfo(conf->host, conf->port, &hints, &addrinfo)) {
-           debug(DBG_WARN, "resolvepeer: can't resolve %s port %s", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)");
-           return 0;
-       }
-       if (slash) {
-           *slash = '/';
-           switch (addrinfo->ai_family) {
-           case AF_INET:
-               if (plen > 32) {
-                   debug(DBG_WARN, "resolvepeer: prefix length must be <= 32 in %s", conf->host);
-                   freeaddrinfo(addrinfo);
-                   return 0;
-               }
-               break;
-           case AF_INET6:
-               break;
-           default:
-               debug(DBG_WARN, "resolvepeer: prefix must be IPv4 or IPv6 in %s", conf->host);
-               freeaddrinfo(addrinfo);
-               return 0;
-           }
-           conf->prefixlen = (uint8_t)plen;
-       } else
-           conf->prefixlen = 255;
-    }
-    if (conf->addrinfo)
-       freeaddrinfo(conf->addrinfo);
-    conf->addrinfo = addrinfo;
-    return 1;
-}        
-
-char *parsehostport(char *s, struct clsrvconf *conf, char *default_port) {
-    char *p, *field;
-    int ipv6 = 0;
-
-    p = s;
-    /* allow literal addresses and port, e.g. [2001:db8::1]:1812 */
-    if (*p == '[') {
-       p++;
-       field = p;
-       for (; *p && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
-       if (*p != ']')
-           debugx(1, DBG_ERR, "no ] matching initial [");
-       ipv6 = 1;
-    } else {
-       field = p;
-       for (; *p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
-    }
-    if (field == p)
-       debugx(1, DBG_ERR, "missing host/address");
-
-    conf->host = stringcopy(field, p - field);
-    if (ipv6) {
-       p++;
-       if (*p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n')
-           debugx(1, DBG_ERR, "unexpected character after ]");
-    }
-    if (*p == ':') {
-           /* port number or service name is specified */;
-           field = ++p;
-           for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++);
-           if (field == p)
-               debugx(1, DBG_ERR, "syntax error, : but no following port");
-           conf->port = stringcopy(field, p - field);
-    } else
-       conf->port = default_port ? stringcopy(default_port, 0) : NULL;
-    return p;
-}
-
-struct clsrvconf *resolve_hostport(uint8_t type, char *lconf, char *default_port) {
-    struct clsrvconf *conf;
-
-    conf = malloc(sizeof(struct clsrvconf));
-    if (!conf)
-       debugx(1, DBG_ERR, "malloc failed");
-    memset(conf, 0, sizeof(struct clsrvconf));
-    conf->type = type;
-    conf->pdef = protodefs[conf->type];
-    if (lconf) {
-       parsehostport(lconf, conf, default_port);
-       if (!strcmp(conf->host, "*")) {
-           free(conf->host);
-           conf->host = NULL;
-       }
-    } else
-       conf->port = default_port ? stringcopy(default_port, 0) : NULL;
-    if (!resolvepeer(conf, AI_PASSIVE))
-       debugx(1, DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)");
-    return conf;
-}
-
-void freeclsrvres(struct clsrvconf *res) {
-    free(res->host);
-    free(res->port);
-    if (res->addrinfo)
-       freeaddrinfo(res->addrinfo);
-    free(res);
-}
-
-struct addrinfo *resolve_hostport_addrinfo(uint8_t type, char *hostport) {
-    struct addrinfo *ai;
-    struct clsrvconf *res;
-
-    res = resolve_hostport(type, hostport, NULL);
-    ai = res->addrinfo;
-    res->addrinfo = NULL;
-    freeclsrvres(res);
-    return ai;
-}
-    
 /* returns 1 if the len first bits are equal, else 0 */
 int prefixmatch(void *a1, void *a2, uint8_t len) {
     static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
@@ -284,46 +133,15 @@ int prefixmatch(void *a1, void *a2, uint8_t len) {
 
 /* returns next config with matching address, or NULL */
 struct clsrvconf *find_conf(uint8_t type, struct sockaddr *addr, struct list *confs, struct list_node **cur) {
-    struct sockaddr_in6 *sa6 = NULL;
-    struct in_addr *a4 = NULL;
-    struct addrinfo *res;
     struct list_node *entry;
     struct clsrvconf *conf;
     
-    if (addr->sa_family == AF_INET6) {
-        sa6 = (struct sockaddr_in6 *)addr;
-        if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
-            a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12];
-           sa6 = NULL;
-       }
-    } else
-       a4 = &((struct sockaddr_in *)addr)->sin_addr;
-
     for (entry = (cur && *cur ? list_next(*cur) : list_first(confs)); entry; entry = list_next(entry)) {
        conf = (struct clsrvconf *)entry->data;
-       if (conf->type == type) {
-           if (conf->prefixlen == 255) {
-               for (res = conf->addrinfo; res; res = res->ai_next)
-                   if ((a4 && res->ai_family == AF_INET &&
-                        !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) ||
-                       (sa6 && res->ai_family == AF_INET6 &&
-                        !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16))) {
-                       if (cur)
-                           *cur = entry;
-                       return conf;
-                   }
-           } else {
-               res = conf->addrinfo;
-               if (res &&
-                   ((a4 && res->ai_family == AF_INET &&
-                     prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, conf->prefixlen)) ||
-                    (sa6 && res->ai_family == AF_INET6 &&
-                     prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, conf->prefixlen)))) {
-                   if (cur)
-                       *cur = entry;
-                   return conf;
-               }
-           }
+       if (conf->type == type && addressmatches(conf->hostports, addr)) {
+           if (cur)
+               *cur = entry;
+           return conf;
        }
     }    
     return NULL;
@@ -353,10 +171,10 @@ struct clsrvconf *find_clconf_type(uint8_t type, struct list_node **cur) {
     return NULL;
 }
 
-struct queue *newqueue() {
-    struct queue *q;
+struct gqueue *newqueue() {
+    struct gqueue *q;
     
-    q = malloc(sizeof(struct queue));
+    q = malloc(sizeof(struct gqueue));
     if (!q)
        debugx(1, DBG_ERR, "malloc failed");
     q->entries = list_create();
@@ -367,7 +185,7 @@ struct queue *newqueue() {
     return q;
 }
 
-void removequeue(struct queue *q) {
+void removequeue(struct gqueue *q) {
     struct list_node *entry;
 
     if (!q)
@@ -382,7 +200,7 @@ void removequeue(struct queue *q) {
     free(q);
 }
 
-void freebios(struct queue *q) {
+void freebios(struct gqueue *q) {
     BIO *bio;
     
     pthread_mutex_lock(&q->mutex);
@@ -683,7 +501,7 @@ void sendrq(struct request *rq) {
        goto errexit;
     }
     
-    debug(DBG_DBG, "sendrq: inserting packet with id %d in queue for %s", i, to->conf->host);
+    debug(DBG_DBG, "sendrq: inserting packet with id %d in queue for %s", i, to->conf->name);
     to->requests[i].rq = rq;
     pthread_mutex_unlock(to->requests[i].lock);
     if (i >= start) /* i is not reserved for statusserver */
@@ -971,6 +789,15 @@ struct realm *id2realm(struct list *realmlist, char *id) {
     return NULL;
 }
 
+int hasdynamicserver(struct list *srvconfs) {
+    struct list_node *entry;
+
+    for (entry = list_first(srvconfs); entry; entry = list_next(entry))
+       if (((struct clsrvconf *)entry->data)->dynamiclookupcommand)
+           return 1;
+    return 0;
+}
+
 /* helper function, only used by removeserversubrealms() */
 void _internal_removeserversubrealms(struct list *realmlist, struct clsrvconf *srv) {
     struct list_node *entry, *entry2;
@@ -986,26 +813,26 @@ void _internal_removeserversubrealms(struct list *realmlist, struct clsrvconf *s
                if (entry2->data == srv)
                    freerealm(realm);
            list_removedata(realm->srvconfs, srv);
-           if (!list_first(realm->srvconfs)) {
-               list_destroy(realm->srvconfs);
-               realm->srvconfs = NULL;
-           }
        }
        if (realm->accsrvconfs) {
            for (entry2 = list_first(realm->accsrvconfs); entry2; entry2 = list_next(entry2))
                if (entry2->data == srv)
                    freerealm(realm);
            list_removedata(realm->accsrvconfs, srv);
-           if (!list_first(realm->accsrvconfs)) {
-               list_destroy(realm->accsrvconfs);
-               realm->accsrvconfs = NULL;
-           }
        }
 
-       /* remove subrealm if no servers */
-       if (!realm->srvconfs && !realm->accsrvconfs)
+       /* remove subrealm if no dynamic servers left */
+       if (!hasdynamicserver(realm->srvconfs) && !hasdynamicserver(realm->accsrvconfs)) {
+           while (list_shift(realm->srvconfs))
+               freerealm(realm);
+           list_destroy(realm->srvconfs);
+           realm->srvconfs = NULL;
+           while (list_shift(realm->accsrvconfs))
+               freerealm(realm);
+           list_destroy(realm->accsrvconfs);
+           realm->accsrvconfs = NULL;
            list_removedata(realmlist, realm);
-
+       }
        pthread_mutex_unlock(&realm->mutex);
        freerealm(realm);
     }
@@ -1427,18 +1254,19 @@ uint8_t *radattr2ascii(struct tlv *attr) {
     return a;
 }
 
-void acclog(struct radmsg *msg, char *host) {
+void acclog(struct radmsg *msg, struct client *from) {
     struct tlv *attr;
     uint8_t *username;
-    
+
     attr = radmsg_gettype(msg, RAD_Attr_User_Name);
     if (!attr) {
-       debug(DBG_INFO, "acclog: accounting-request from %s without username attribute", host);
+       debug(DBG_INFO, "acclog: accounting-request from client %s (%s) without username attribute", from->conf->name, addr2string(from->addr));
        return;
     }
     username = radattr2ascii(attr);
     if (username) {
-       debug(DBG_INFO, "acclog: accounting-request from %s with username: %s", host, username);
+       debug(DBG_INFO, "acclog: accounting-request from client %s (%s) with username: %s", from->conf->name, addr2string(from->addr), username);
+             
        free(username);
     }
 }
@@ -1464,7 +1292,7 @@ void respond(struct request *rq, uint8_t code, char *message) {
 
     radmsg_free(rq->msg);
     rq->msg = msg;
-    debug(DBG_DBG, "respond: sending %s to %s", radmsgtype2string(msg->code), rq->from->conf->host);
+    debug(DBG_DBG, "respond: sending %s to %s (%s)", radmsgtype2string(msg->code), rq->from->conf->name, addr2string(rq->from->addr));
     sendreply(newrqref(rq));
 }
 
@@ -1478,7 +1306,7 @@ struct clsrvconf *choosesrvconf(struct list *srvconfs) {
            return server;
        if (!first)
            first = server;
-       if (!server->servers->connectionok)
+       if (!server->servers->connectionok && !server->servers->dynstartup)
            continue;
        if (!server->servers->lostrqs)
            return server;
@@ -1507,19 +1335,19 @@ struct server *findserver(struct realm **realm, struct tlv *username, uint8_t ac
        goto exit;
     debug(DBG_DBG, "found matching realm: %s", (*realm)->name);
     srvconf = choosesrvconf(acc ? (*realm)->accsrvconfs : (*realm)->srvconfs);
-    if (!srvconf)
-       goto exit;
-    server = srvconf->servers;
-    if (!acc && !(*realm)->parent && !srvconf->servers) {
-       subrealm = adddynamicrealmserver(*realm, srvconf, id);
+    if (srvconf && !(*realm)->parent && !srvconf->servers && srvconf->dynamiclookupcommand) {
+       subrealm = adddynamicrealmserver(*realm, id);
        if (subrealm) {
            pthread_mutex_lock(&subrealm->mutex);
            pthread_mutex_unlock(&(*realm)->mutex);
            freerealm(*realm);
            *realm = subrealm;
-           server = ((struct clsrvconf *)subrealm->srvconfs->first->data)->servers;
+           srvconf = choosesrvconf(acc ? (*realm)->accsrvconfs : (*realm)->srvconfs);
        }
     }
+    if (srvconf)
+       server = srvconf->servers;
+
  exit:    
     free(id);
     return server;
@@ -1625,7 +1453,7 @@ int radsrv(struct request *rq) {
     attr = radmsg_gettype(msg, RAD_Attr_User_Name);
     if (!attr) {
        if (msg->code == RAD_Accounting_Request) {
-           acclog(msg, from->conf->host);
+           acclog(msg, from);
            respond(rq, RAD_Accounting_Response, NULL);
        } else
            debug(DBG_WARN, "radsrv: ignoring access request, no username attribute");
@@ -1651,10 +1479,10 @@ int radsrv(struct request *rq) {
 
     if (!to) {
        if (realm->message && msg->code == RAD_Access_Request) {
-           debug(DBG_INFO, "radsrv: sending reject to %s for %s", from->conf->host, userascii);
+           debug(DBG_INFO, "radsrv: sending reject to %s (%s) for %s", from->conf->name, addr2string(from->addr), userascii);
            respond(rq, RAD_Access_Reject, realm->message);
        } else if (realm->accresp && msg->code == RAD_Accounting_Request) {
-           acclog(msg, from->conf->host);
+           acclog(msg, from);
            respond(rq, RAD_Accounting_Response, NULL);
        }
        goto exit;
@@ -1756,7 +1584,7 @@ void replyh(struct server *server, unsigned char *buf) {
     
     if (rqout->rq->msg->code == RAD_Status_Server) {
        freerqoutdata(rqout);
-       debug(DBG_DBG, "replyh: got status server response from %s", server->conf->host);
+       debug(DBG_DBG, "replyh: got status server response from %s", server->conf->name);
        goto errunlock;
     }
 
@@ -1770,7 +1598,7 @@ void replyh(struct server *server, unsigned char *buf) {
     
     ttlres = checkttl(msg, options.ttlattrtype);
     if (!ttlres) {    
-       debug(DBG_WARN, "replyh: ignoring reply from server %s, ttl exceeded", server->conf->host);
+       debug(DBG_WARN, "replyh: ignoring reply from server %s, ttl exceeded", server->conf->name);
        goto errunlock;
     }
     
@@ -1806,20 +1634,20 @@ void replyh(struct server *server, unsigned char *buf) {
            if (stationid) {
                if (replymsg) {
                    debug(DBG_INFO, "%s for user %s stationid %s from %s (%s)",
-                         radmsgtype2string(msg->code), username, stationid, server->conf->host, replymsg);
+                         radmsgtype2string(msg->code), username, stationid, server->conf->name, replymsg);
                    free(replymsg);
                } else
                    debug(DBG_INFO, "%s for user %s stationid %s from %s",
-                         radmsgtype2string(msg->code), username, stationid, server->conf->host);
+                         radmsgtype2string(msg->code), username, stationid, server->conf->name);
                free(stationid);
            } else {
                if (replymsg) {
                    debug(DBG_INFO, "%s for user %s from %s (%s)",
-                         radmsgtype2string(msg->code), username, server->conf->host, replymsg);
+                         radmsgtype2string(msg->code), username, server->conf->name, replymsg);
                    free(replymsg);
                } else
                    debug(DBG_INFO, "%s for user %s from %s",
-                         radmsgtype2string(msg->code), username, server->conf->host);
+                         radmsgtype2string(msg->code), username, server->conf->name);
            }
            free(username);
        }
@@ -1903,11 +1731,15 @@ void *clientwr(void *arg) {
     
     if (server->dynamiclookuparg && !dynamicconfig(server)) {
        dynconffail = 1;
+       server->dynstartup = 0;
+       sleep(900);
        goto errexit;
     }
     
-    if (!conf->addrinfo && !resolvepeer(conf, 0)) {
-       debug(DBG_WARN, "failed to resolve host %s port %s", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)");
+    if (!resolvehostports(conf->hostports, conf->pdef->socktype)) {
+       debug(DBG_WARN, "clientwr: resolve failed");
+       server->dynstartup = 0;
+       sleep(900);
        goto errexit;
     }
 
@@ -1919,8 +1751,13 @@ void *clientwr(void *arg) {
     }
 
     if (conf->pdef->connecter) {
-       if (!conf->pdef->connecter(server, NULL, server->dynamiclookuparg ? 6 : 0, "clientwr"))
+       if (!conf->pdef->connecter(server, NULL, server->dynamiclookuparg ? 6 : 0, "clientwr")) {
+           if (server->dynamiclookuparg) {
+               server->dynstartup = 0;
+               sleep(900);
+           }
            goto errexit;
+       }
        server->connectionok = 1;
        if (pthread_create(&clientrdth, NULL, conf->pdef->clientconnreader, (void *)server)) {
            debug(DBG_ERR, "clientwr: pthread_create failed");
@@ -1928,6 +1765,7 @@ void *clientwr(void *arg) {
        }
     } else
        server->connectionok = 1;
+    server->dynstartup = 0;
     
     for (;;) {
        pthread_mutex_lock(&server->newrq_mutex);
@@ -1994,12 +1832,12 @@ void *clientwr(void *arg) {
                debug(DBG_DBG, "clientwr: removing expired packet from queue");
                if (conf->statusserver) {
                    if (*rqout->rq->buf == RAD_Status_Server) {
-                       debug(DBG_WARN, "clientwr: no status server response, %s dead?", conf->host);
+                       debug(DBG_WARN, "clientwr: no status server response, %s dead?", conf->name);
                        if (server->lostrqs < 255)
                            server->lostrqs++;
                    }
                 } else {
-                   debug(DBG_WARN, "clientwr: no server response, %s dead?", conf->host);
+                   debug(DBG_WARN, "clientwr: no server response, %s dead?", conf->name);
                    if (server->lostrqs < 255)
                        server->lostrqs++;
                }
@@ -2023,7 +1861,7 @@ void *clientwr(void *arg) {
                statsrvrq = createstatsrvrq();
                if (statsrvrq) {
                    statsrvrq->to = server;
-                   debug(DBG_DBG, "clientwr: sending status server to %s", conf->host);
+                   debug(DBG_DBG, "clientwr: sending status server to %s", conf->name);
                    sendrq(statsrvrq);
                }
            }
@@ -2045,15 +1883,14 @@ void *clientwr(void *arg) {
 
 void createlistener(uint8_t type, char *arg) {
     pthread_t th;
-    struct clsrvconf *listenres;
     struct addrinfo *res;
     int s = -1, on = 1, *sp = NULL;
+    struct hostportres *hp = newhostport(arg, protodefs[type]->portdefault, 0);
     
-    listenres = resolve_hostport(type, arg, protodefs[type]->portdefault);
-    if (!listenres)
+    if (!hp || !resolvehostport(hp, protodefs[type]->socktype, 1))
        debugx(1, DBG_ERR, "createlistener: failed to resolve %s", arg);
     
-    for (res = listenres->addrinfo; res; res = res->ai_next) {
+    for (res = hp->addrinfo; res; res = res->ai_next) {
         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
         if (s < 0) {
             debug(DBG_WARN, "createlistener: socket failed");
@@ -2063,7 +1900,7 @@ void createlistener(uint8_t type, char *arg) {
 #ifdef IPV6_V6ONLY
        if (res->ai_family == AF_INET6)
            setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
-#endif         
+#endif
        if (bind(s, res->ai_addr, res->ai_addrlen)) {
            debug(DBG_WARN, "createlistener: bind failed");
            close(s);
@@ -2082,9 +1919,8 @@ void createlistener(uint8_t type, char *arg) {
     if (!sp)
        debugx(1, DBG_ERR, "createlistener: socket/bind failed");
     
-    debug(DBG_WARN, "createlistener: listening for %s on %s:%s", protodefs[type]->name,
-         listenres->host ? listenres->host : "*", listenres->port);
-    freeclsrvres(listenres);
+    debug(DBG_WARN, "createlistener: listening for %s on %s:%s", protodefs[type]->name, hp->host ? hp->host : "*", hp->port);
+    freehostport(hp);
 }
 
 void createlisteners(uint8_t type) {
@@ -2292,15 +2128,53 @@ struct realm *addrealm(struct list *realmlist, char *value, char **servers, char
     return newrealmref(realm);
 }
 
-struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf, char *id) {
-    struct clsrvconf *srvconf;
+struct list *createsubrealmservers(struct realm *realm, struct list *srvconfs) {
+    struct list_node *entry;
+    struct clsrvconf *conf, *srvconf;
+    struct list *subrealmservers = NULL;
+    pthread_t clientth;
+
+    if (list_first(srvconfs)) {
+       subrealmservers = list_create();
+       if (!subrealmservers)
+           return NULL;
+    }
+    
+    for (entry = list_first(srvconfs); entry; entry = list_next(entry)) {
+       conf = (struct clsrvconf *)entry->data;
+       if (!conf->servers && conf->dynamiclookupcommand) {
+           srvconf = malloc(sizeof(struct clsrvconf));
+           if (!srvconf) {
+               debug(DBG_ERR, "malloc failed");
+               continue;
+           }
+           *srvconf = *conf;
+           if (addserver(srvconf)) {
+               srvconf->servers->dynamiclookuparg = stringcopy(realm->name, 0);
+               srvconf->servers->dynstartup = 1;
+               if (pthread_create(&clientth, NULL, clientwr, (void *)(srvconf->servers))) {
+                   debug(DBG_ERR, "pthread_create failed");
+                   freeserver(srvconf->servers, 1);
+                   srvconf->servers = NULL;
+               } else
+                   pthread_detach(clientth);
+           }
+           conf = srvconf;
+       }
+       if (conf->servers) {
+           if (list_push(subrealmservers, conf))
+               newrealmref(realm);
+           else
+               debug(DBG_ERR, "malloc failed");
+       }
+    }
+    return subrealmservers;
+}
+    
+struct realm *adddynamicrealmserver(struct realm *realm, char *id) {
     struct realm *newrealm = NULL;
     char *realmname, *s;
-    pthread_t clientth;
     
-    if (!conf->dynamiclookupcommand)
-       return NULL;
-
     /* create dynamic for the realm (string after last @, exit if nothing after @ */
     realmname = strrchr(id, '@');
     if (!realmname)
@@ -2312,65 +2186,23 @@ struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf,
        if (*s != '.' && *s != '-' && !isalnum((int)*s))
            return NULL;
     
-    srvconf = malloc(sizeof(struct clsrvconf));
-    if (!srvconf) {
-       debug(DBG_ERR, "malloc failed");
-       return NULL;
-    }
-    *srvconf = *conf;
-    if (!addserver(srvconf))
-       goto errexit;
-
     if (!realm->subrealms)
        realm->subrealms = list_create();
     if (!realm->subrealms)
-       goto errexit;
-    newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, NULL, 0);
-    if (!newrealm)
-       goto errexit;
-    newrealm->parent = newrealmref(realm);
+       return NULL;
     
-    /* add server and accserver to newrealm */
-    newrealm->srvconfs = list_create();
-    if (!newrealm->srvconfs || !list_push(newrealm->srvconfs, srvconf)) {
-       debug(DBG_ERR, "malloc failed");
-       goto errexit;
-    }
-    newrealmref(newrealm);
-    newrealm->accsrvconfs = list_create();
-    if (!newrealm->accsrvconfs || !list_push(newrealm->accsrvconfs, srvconf)) {
-       debug(DBG_ERR, "malloc failed");
-       list_shift(realm->srvconfs);
-       freerealm(newrealm);
-       goto errexit;
+    newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, stringcopy(realm->message, 0), realm->accresp);
+    if (!newrealm) {
+       list_destroy(realm->subrealms);
+       realm->subrealms = NULL;
+       return NULL;
     }
-    newrealmref(newrealm);
 
-    srvconf->servers->dynamiclookuparg = stringcopy(realmname, 0);
-    if (pthread_create(&clientth, NULL, clientwr, (void *)(srvconf->servers))) {
-       debug(DBG_ERR, "pthread_create failed");
-       list_shift(realm->srvconfs);
-       freerealm(newrealm);
-       list_shift(realm->accsrvconfs);
-       freerealm(newrealm);
-       goto errexit;
-    }
-    pthread_detach(clientth);
+    newrealm->parent = newrealmref(realm);
+    /* add server and accserver to newrealm */
+    newrealm->srvconfs = createsubrealmservers(newrealm, realm->srvconfs);
+    newrealm->accsrvconfs = createsubrealmservers(newrealm, realm->accsrvconfs);
     return newrealm;
-    
- errexit:
-    if (newrealm) {
-       list_removedata(realm->subrealms, newrealm);
-       freerealm(newrealm);
-       if (!list_first(realm->subrealms)) {
-           list_destroy(realm->subrealms);
-           realm->subrealms = NULL;
-       }
-    }
-    freeserver(srvconf->servers, 1);
-    free(srvconf);
-    debug(DBG_ERR, "failed to create dynamic server");
-    return NULL;
 }
 
 int dynamicconfig(struct server *server) {
@@ -2637,8 +2469,9 @@ int setttlattr(struct options *opts, char *defaultattr) {
 
 void freeclsrvconf(struct clsrvconf *conf) {
     free(conf->name);
-    free(conf->host);
-    free(conf->port);
+    if (conf->hostsrc)
+       freegconfmstr(conf->hostsrc);
+    free(conf->portsrc);
     free(conf->secret);
     free(conf->tls);
     free(conf->matchcertattr);
@@ -2657,8 +2490,8 @@ void freeclsrvconf(struct clsrvconf *conf) {
     free(conf->dynamiclookupcommand);
     free(conf->rewritein);
     free(conf->rewriteout);
-    if (conf->addrinfo)
-       freeaddrinfo(conf->addrinfo);
+    if (conf->hostports)
+       freehostports(conf->hostports);
     if (conf->lock) {
        pthread_mutex_destroy(conf->lock);
        free(conf->lock);
@@ -2686,11 +2519,52 @@ int mergeconfstring(char **dst, char **src) {
     return 1;
 }
 
+char **mstringcopy(char **in) {
+    char **out;
+    int n;
+    
+    if (!in)
+       return NULL;
+
+    for (n = 0; in[n]; n++);
+    out = malloc((n + 1) * sizeof(char *));
+    if (!out)
+       return NULL;
+    for (n = 0; in[n]; n++) {
+       out[n] = stringcopy(in[n], 0);
+       if (!out[n]) {
+           freegconfmstr(out);
+           return NULL;
+       }
+    }
+    out[n] = NULL;
+    return out;
+}
+
+int mergeconfmstring(char ***dst, char ***src) {
+    char **t;
+    
+    if (*src) {
+       *dst = *src;
+       *src = NULL;
+       return 1;
+    }
+    if (*dst) {
+       t = mstringcopy(*dst);
+       if (!t) {
+           debug(DBG_ERR, "malloc failed");
+           return 0;
+       }
+       *dst = t;
+    }
+    return 1;
+}
+
 /* assumes dst is a shallow copy */
 int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) {
     if (!mergeconfstring(&dst->name, &src->name) ||
-       !mergeconfstring(&dst->host, &src->host) ||
-       !mergeconfstring(&dst->port, &src->port) ||
+       !mergeconfmstring(&dst->hostsrc, &src->hostsrc) ||
+       !mergeconfstring(&dst->portsrc, &src->portsrc) ||
        !mergeconfstring(&dst->secret, &src->secret) ||
        !mergeconfstring(&dst->tls, &src->tls) ||
        !mergeconfstring(&dst->matchcertattr, &src->matchcertattr) ||
@@ -2724,7 +2598,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     
     if (!getgenericconfig(cf, block,
                          "type", CONF_STR, &conftype,
-                         "host", CONF_STR, &conf->host,
+                         "host", CONF_MSTR, &conf->hostsrc,
                          "secret", CONF_STR, &conf->secret,
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)    
                          "tls", CONF_STR, &conf->tls,
@@ -2742,9 +2616,14 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
        debugx(1, DBG_ERR, "configuration error");
     
     conf->name = stringcopy(val, 0);
-    if (!conf->host)
-       conf->host = stringcopy(val, 0);
-    if (!conf->name || !conf->host)
+    if (conf->name && !conf->hostsrc) {
+       conf->hostsrc = malloc(2 * sizeof(char *));
+       if (conf->hostsrc) {
+           conf->hostsrc[0] = stringcopy(val, 0);
+           conf->hostsrc[1] = NULL;
+       }
+    }
+    if (!conf->name || !conf->hostsrc || !conf->hostsrc[0])
        debugx(1, DBG_ERR, "malloc failed");
        
     if (!conftype)
@@ -2791,9 +2670,10 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
        if (!conf->rewriteusername)
            debugx(1, DBG_ERR, "error in block %s, invalid RewriteAttributeValue", block);
     }
-    
-    if (!resolvepeer(conf, 0))
-       debugx(1, DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)");
+
+    if (!addhostport(&conf->hostports, conf->hostsrc, conf->pdef->portdefault, 1) ||
+       !resolvehostports(conf->hostports, conf->pdef->socktype))
+       debugx(1, DBG_ERR, "resolve failed, exiting");
     
     if (!conf->secret) {
        if (!conf->pdef->secretdefault)
@@ -2828,9 +2708,9 @@ int compileserverconfig(struct clsrvconf *conf, const char *block) {
     }
 #endif
     
-    if (!conf->port) {
-       conf->port = stringcopy(conf->pdef->portdefault, 0);
-       if (!conf->port) {
+    if (!conf->portsrc) {
+       conf->portsrc = stringcopy(conf->pdef->portdefault, 0);
+       if (!conf->portsrc) {
            debug(DBG_ERR, "malloc failed");
            return 0;
        }
@@ -2844,21 +2724,14 @@ int compileserverconfig(struct clsrvconf *conf, const char *block) {
     conf->rewritein = conf->confrewritein ? getrewrite(conf->confrewritein, NULL) : getrewrite("defaultserver", "default");
     if (conf->confrewriteout)
        conf->rewriteout = getrewrite(conf->confrewriteout, NULL);
-
-    if (!conf->secret) {
-       if (!conf->pdef->secretdefault) {
-           debug(DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name);
-           return 0;
-       }
-       conf->secret = stringcopy(conf->pdef->secretdefault, 0);
-       if (!conf->secret) {
-           debug(DBG_ERR, "malloc failed");
-           return 0;
-       }
-    }
     
-    if (!conf->dynamiclookupcommand && !resolvepeer(conf, 0)) {
-       debug(DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)");
+    if (!addhostport(&conf->hostports, conf->hostsrc, conf->portsrc, 0)) {
+       debug(DBG_ERR, "error in block %s, failed to parse %s", block, conf->hostsrc);
+       return 0;
+    }
+
+    if (!conf->dynamiclookupcommand && !resolvehostports(conf->hostports, conf->pdef->socktype)) {
+       debug(DBG_ERR, "resolve failed, exiting");
        return 0;
     }
     return 1;
@@ -2886,8 +2759,8 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
 
     if (!getgenericconfig(cf, block,
                          "type", CONF_STR, &conftype,
-                         "host", CONF_STR, &conf->host,
-                         "port", CONF_STR, &conf->port,
+                         "host", CONF_MSTR, &conf->hostsrc,
+                         "port", CONF_STR, &conf->portsrc,
                          "secret", CONF_STR, &conf->secret,
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)    
                          "tls", CONF_STR, &conf->tls,
@@ -2909,20 +2782,22 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     }
     
     conf->name = stringcopy(val, 0);
-    if (!conf->name) {
+    if (conf->name && !conf->hostsrc) {
+       conf->hostsrc = malloc(2 * sizeof(char *));
+       if (conf->hostsrc) {
+           conf->hostsrc[0] = stringcopy(val, 0);
+           conf->hostsrc[1] = NULL;
+       }
+    }
+    if (!conf->name || !conf->hostsrc || !conf->hostsrc[0]) {
         debug(DBG_ERR, "malloc failed");
        goto errexit;
     }
-    if (!conf->host) {
-       conf->host = stringcopy(val, 0);
-       if (!conf->host) {
-            debug(DBG_ERR, "malloc failed");
-           goto errexit;
-        }
-    }
 
-    if (!conftype)
-       debugx(1, DBG_ERR, "error in block %s, option type missing", block);
+    if (!conftype) {
+       debug(DBG_ERR, "error in block %s, option type missing", block);
+       goto errexit;
+    }
     conf->type = protoname2int(conftype);
     if (conf->type == 255) {
        debug(DBG_ERR, "error in block %s, unknown transport %s", block, conftype);
@@ -2981,9 +2856,21 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
            goto errexit;
     }
     
+    if (!conf->secret) {
+       if (!conf->pdef->secretdefault) {
+           debug(DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name);
+           return 0;
+       }
+       conf->secret = stringcopy(conf->pdef->secretdefault, 0);
+       if (!conf->secret) {
+           debug(DBG_ERR, "malloc failed");
+           return 0;
+       }
+    }
+    
     if (resconf)
        return 1;
-       
+
     if (!list_push(srvconfs, conf)) {
        debug(DBG_ERR, "malloc failed");
        goto errexit;
@@ -3145,7 +3032,21 @@ void getargs(int argc, char **argv, uint8_t *foreground, uint8_t *pretend, uint8
            *pretend = 1;
            break;
        case 'v':
-               debugx(0, DBG_ERR, "radsecproxy revision $Rev$");
+               debug(DBG_ERR, "radsecproxy revision $Rev$");
+               debug(DBG_ERR, "This binary was built with support for the following transports:");
+#ifdef RADPROT_UDP
+               debug(DBG_ERR, "  UDP");
+#endif         
+#ifdef RADPROT_TCP
+               debug(DBG_ERR, "  TCP");
+#endif         
+#ifdef RADPROT_TLS
+               debug(DBG_ERR, "  TLS");
+#endif         
+#ifdef RADPROT_DTLS
+               debug(DBG_ERR, "  DTLS");
+#endif
+               exit(0);
        default:
            goto usage;
        }