radsecproxy-1.6.5.
[radsecproxy.git] / radsecproxy.c
index 5856113..b7b2063 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
- * Copyright (C) 2010, 2011 NORDUnet A/S
+ * Copyright (C) 2010,2011,2012 NORDUnet A/S
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -52,6 +52,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <limits.h>
+#if defined(HAVE_MALLOPT)
+#include <malloc.h>
+#endif
 #ifdef SYS_SOLARIS9
 #include <fcntl.h>
 #endif
 #include <libgen.h>
 #include <pthread.h>
 #include <errno.h>
+#include <assert.h>
 #include <openssl/ssl.h>
 #include <openssl/rand.h>
 #include <openssl/err.h>
 #include <openssl/md5.h>
 #include "debug.h"
-#include "list.h"
 #include "hash.h"
 #include "util.h"
 #include "hostport.h"
@@ -78,6 +81,9 @@
 #include "tcp.h"
 #include "tls.h"
 #include "dtls.h"
+#if defined(WANT_FTICKS)
+#include "fticks.h"
+#endif
 
 static struct options options;
 static struct list *clconfs, *srvconfs;
@@ -324,8 +330,13 @@ void freeserver(struct server *server, uint8_t destroymutex) {
     if (server->rbios)
        freebios(server->rbios);
     free(server->dynamiclookuparg);
-    if (server->ssl)
-       SSL_free(server->ssl);
+    if (server->ssl) {
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+        if (server->sock >= 0)
+            close(server->sock);
+#endif
+        SSL_free(server->ssl);
+    }
     if (destroymutex) {
        pthread_mutex_destroy(&server->lock);
        pthread_cond_destroy(&server->newrq_cond);
@@ -765,7 +776,12 @@ 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)
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+        if (((struct clsrvconf *)entry->data)->dynamiclookupcommand
+            || ((struct clsrvconf *)entry->data)->servers->in_use)
+#else
+        if (((struct clsrvconf *)entry->data)->dynamiclookupcommand)
+#endif
            return 1;
     return 0;
 }
@@ -1271,7 +1287,9 @@ void acclog(struct radmsg *msg, struct client *from) {
     }
 }
 
-void respond(struct request *rq, uint8_t code, char *message) {
+void respond(struct request *rq, uint8_t code, char *message,
+             int copy_proxystate_flag)
+{
     struct radmsg *msg;
     struct tlv *attr;
 
@@ -1289,6 +1307,12 @@ void respond(struct request *rq, uint8_t code, char *message) {
            return;
        }
     }
+    if (copy_proxystate_flag) {
+        if (radmsg_copy_attrs(msg, rq->msg, RAD_Proxy_State) < 0) {
+            debug(DBG_ERR, "%s: unable to copy all Proxy-State attributes",
+                  __func__);
+        }
+    }
 
     radmsg_free(rq->msg);
     rq->msg = msg;
@@ -1304,6 +1328,8 @@ struct clsrvconf *choosesrvconf(struct list *srvconfs) {
        server = (struct clsrvconf *)entry->data;
        if (!server->servers)
            return server;
+        if (server->servers->dynfailing)
+            continue;
        if (!first)
            first = server;
        if (!server->servers->connectionok && !server->servers->dynstartup)
@@ -1342,11 +1368,15 @@ struct server *findserver(struct realm **realm, struct tlv *username, uint8_t ac
            pthread_mutex_unlock(&(*realm)->mutex);
            freerealm(*realm);
            *realm = subrealm;
+            debug(DBG_DBG, "added realm: %s", (*realm)->name);
            srvconf = choosesrvconf(acc ? (*realm)->accsrvconfs : (*realm)->srvconfs);
+            debug(DBG_DBG, "found conf for new realm: %s", srvconf->name);
        }
     }
-    if (srvconf)
+    if (srvconf) {
+        debug(DBG_DBG, "found matching conf: %s", srvconf->name);
        server = srvconf->servers;
+    }
 
 exit:
     free(id);
@@ -1368,6 +1398,22 @@ struct request *newrequest() {
     return rq;
 }
 
+static void
+purgedupcache(struct client *client) {
+    struct request *r;
+    struct timeval now;
+    int i;
+
+    gettimeofday(&now, NULL);
+    for (i = 0; i < MAX_REQUESTS; i++) {
+       r = client->rqs[i];
+       if (r && now.tv_sec - r->created.tv_sec > r->from->conf->dupinterval) {
+           freerq(r);
+           client->rqs[i] = NULL;
+       }
+    }
+}
+
 int addclientrq(struct request *rq) {
     struct request *r;
     struct timeval now;
@@ -1431,11 +1477,12 @@ int radsrv(struct request *rq) {
        goto exit;
     }
 
+    purgedupcache(from);
     if (!addclientrq(rq))
        goto exit;
 
     if (msg->code == RAD_Status_Server) {
-       respond(rq, RAD_Access_Accept, NULL);
+       respond(rq, RAD_Access_Accept, NULL, 0);
        goto exit;
     }
 
@@ -1454,7 +1501,7 @@ int radsrv(struct request *rq) {
     if (!attr) {
        if (msg->code == RAD_Accounting_Request) {
            acclog(msg, from);
-           respond(rq, RAD_Accounting_Response, NULL);
+           respond(rq, RAD_Accounting_Response, NULL, 1);
        } else
            debug(DBG_INFO, "radsrv: ignoring access request, no username attribute");
        goto exit;
@@ -1480,10 +1527,10 @@ int radsrv(struct request *rq) {
     if (!to) {
        if (realm->message && msg->code == RAD_Access_Request) {
            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);
+           respond(rq, RAD_Access_Reject, realm->message, 1);
        } else if (realm->accresp && msg->code == RAD_Accounting_Request) {
            acclog(msg, from);
-           respond(rq, RAD_Accounting_Response, NULL);
+           respond(rq, RAD_Accounting_Response, NULL, 1);
        }
        goto exit;
     }
@@ -1665,6 +1712,12 @@ void replyh(struct server *server, unsigned char *buf) {
        }
     }
 
+#if defined(WANT_FTICKS)
+    if (msg->code == RAD_Access_Accept || msg->code == RAD_Access_Reject)
+       if (options.fticks_reporting && from->conf->fticks_viscountry != NULL)
+           fticks_log(&options, from, msg, rqout);
+#endif
+
     msg->id = (char)rqout->rq->rqid;
     memcpy(msg->auth, rqout->rq->rqauth, 16);
 
@@ -1743,18 +1796,30 @@ void *clientwr(void *arg) {
 
     conf = server->conf;
 
+#define ZZZ 900
+
     if (server->dynamiclookuparg && !dynamicconfig(server)) {
        dynconffail = 1;
        server->dynstartup = 0;
-       sleep(900);
+       server->dynfailing = 1;
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+       pthread_mutex_unlock(&server->lock);
+#endif
+       debug(DBG_WARN, "%s: dynamicconfig(%s: %s) failed, sleeping %ds",
+              __func__, server->conf->name, server->dynamiclookuparg, ZZZ);
+       sleep(ZZZ);
        goto errexit;
     }
-
-    if (!resolvehostports(conf->hostports, conf->pdef->socktype)) {
-       debug(DBG_WARN, "clientwr: resolve failed");
-       server->dynstartup = 0;
-       sleep(900);
-       goto errexit;
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+    pthread_mutex_unlock(&server->lock);
+#endif
+    /* FIXME: Is resolving not always done by compileserverconfig(),
+     * either as part of static configuration setup or by
+     * dynamicconfig() above?  */
+    if (!resolvehostports(conf->hostports, conf->hostaf, conf->pdef->socktype)) {
+        debug(DBG_WARN, "%s: resolve failed, sleeping %ds", __func__, ZZZ);
+        sleep(ZZZ);
+        goto errexit;
     }
 
     memset(&timeout, 0, sizeof(struct timespec));
@@ -1767,13 +1832,19 @@ void *clientwr(void *arg) {
     if (conf->pdef->connecter) {
        if (!conf->pdef->connecter(server, NULL, server->dynamiclookuparg ? 5 : 0, "clientwr")) {
            if (server->dynamiclookuparg) {
-               server->dynstartup = 0;
-               sleep(900);
+                server->dynstartup = 0;
+               server->dynfailing = 1;
+                debug(DBG_WARN, "%s: connect failed, sleeping %ds",
+                      __func__, ZZZ);
+               sleep(ZZZ);
            }
            goto errexit;
        }
        server->connectionok = 1;
-       if (pthread_create(&clientrdth, NULL, conf->pdef->clientconnreader, (void *)server)) {
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+       server->in_use = 1;
+#endif
+       if (pthread_create(&clientrdth, &pthread_attr, conf->pdef->clientconnreader, (void *)server)) {
            debugerrno(errno, DBG_ERR, "clientwr: pthread_create failed");
            goto errexit;
        }
@@ -1882,6 +1953,9 @@ void *clientwr(void *arg) {
        }
     }
 errexit:
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+    server->in_use = 0;
+#endif
     conf->servers = NULL;
     if (server->dynamiclookuparg) {
        removeserversubrealms(realms, conf);
@@ -1901,7 +1975,7 @@ void createlistener(uint8_t type, char *arg) {
     int s = -1, on = 1, *sp = NULL;
     struct hostportres *hp = newhostport(arg, protodefs[type]->portdefault, 0);
 
-    if (!hp || !resolvehostport(hp, protodefs[type]->socktype, 1))
+    if (!hp || !resolvehostport(hp, AF_UNSPEC, protodefs[type]->socktype, 1))
        debugx(1, DBG_ERR, "createlistener: failed to resolve %s", arg);
 
     for (res = hp->addrinfo; res; res = res->ai_next) {
@@ -1929,7 +2003,7 @@ void createlistener(uint8_t type, char *arg) {
         if (!sp)
             debugx(1, DBG_ERR, "malloc failed");
        *sp = s;
-       if (pthread_create(&th, NULL, protodefs[type]->listener, (void *)sp))
+       if (pthread_create(&th, &pthread_attr, protodefs[type]->listener, (void *)sp))
             debugerrnox(errno, DBG_ERR, "pthread_create failed");
        pthread_detach(th);
     }
@@ -2165,16 +2239,40 @@ struct list *createsubrealmservers(struct realm *realm, struct list *srvconfs) {
                debug(DBG_ERR, "malloc failed");
                continue;
            }
+            debug(DBG_DBG, "%s: copying config %s", __func__, conf->name);
            *srvconf = *conf;
+            /* Shallow copy -- sharing all the pointers.  addserver()
+             * will take care of servers (which btw has to be NUL) but
+             * the rest of them are shared with the config found in
+             * the srvconfs list.  */
            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_DBG, "%s: new client writer for %s",
+                      __func__, srvconf->servers->conf->name);
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+               pthread_mutex_lock(&srvconf->servers->lock);
+#endif
+               if (pthread_create(&clientth, &pthread_attr, clientwr, (void *)(srvconf->servers))) {
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+                    pthread_mutex_unlock(&srvconf->servers->lock);
+#endif
                    debugerrno(errno, DBG_ERR, "pthread_create failed");
                    freeserver(srvconf->servers, 1);
                    srvconf->servers = NULL;
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+                   conf = srvconf;
+                   continue;
+#endif
                } else
                    pthread_detach(clientth);
+
+#if defined ENABLE_EXPERIMENTAL_DYNDISC
+                /* If clientwr() could not find a NAPTR we have to
+                 * wait for dynfailing=1 what is set in clientwr().  */
+                pthread_mutex_lock(&srvconf->servers->lock);
+                pthread_mutex_unlock(&srvconf->servers->lock);
+#endif
            }
            conf = srvconf;
        }
@@ -2255,10 +2353,8 @@ int dynamicconfig(struct server *server) {
 
     close(fd[1]);
     pushgconffile(&cf, fdopen(fd[0], "r"), conf->dynamiclookupcommand);
-    ok = getgenericconfig(&cf, NULL,
-                         "Server", CONF_CBK, confserver_cb, (void *)conf,
-                         NULL
-       );
+    ok = getgenericconfig(&cf, NULL, "Server", CONF_CBK, confserver_cb,
+                         (void *) conf, NULL);
     freegconf(&cf);
 
     if (waitpid(pid, &status, 0) < 0) {
@@ -2267,8 +2363,9 @@ int dynamicconfig(struct server *server) {
     }
 
     if (status) {
-       debug(DBG_INFO, "dynamicconfig: command exited with status %d", WEXITSTATUS(status));
-       goto errexit;
+        debug(DBG_INFO, "dynamicconfig: command exited with status %d",
+              WEXITSTATUS(status));
+        goto errexit;
     }
 
     if (ok)
@@ -2376,7 +2473,9 @@ struct modattr *extractmodattr(char *nameval) {
     if (s[strlen(s) - 1] == '/')
        s[strlen(s) - 1] = '\0';
 
-    t = strchr(s, '/');
+    for (t = strchr(s, '/'); t; t = strchr(t+1, '/'))
+        if (t == s || t[-1] != '\\')
+            break;
     if (!t)
        return NULL;
     *t = '\0';
@@ -2532,6 +2631,9 @@ int setttlattr(struct options *opts, char *defaultattr) {
 }
 
 void freeclsrvconf(struct clsrvconf *conf) {
+    assert(conf);
+    assert(conf->name);
+    debug(DBG_DBG, "%s: freeing %p (%s)", __func__, conf, conf->name);
     free(conf->name);
     if (conf->hostsrc)
        freegconfmstr(conf->hostsrc);
@@ -2552,8 +2654,8 @@ void freeclsrvconf(struct clsrvconf *conf) {
        free(conf->rewriteusername);
     }
     free(conf->dynamiclookupcommand);
-    free(conf->rewritein);
-    free(conf->rewriteout);
+    conf->rewritein=NULL;
+    conf->rewriteout=NULL;
     if (conf->hostports)
        freehostports(conf->hostports);
     if (conf->lock) {
@@ -2634,7 +2736,10 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) {
        !mergeconfstring(&dst->matchcertattr, &src->matchcertattr) ||
        !mergeconfstring(&dst->confrewritein, &src->confrewritein) ||
        !mergeconfstring(&dst->confrewriteout, &src->confrewriteout) ||
-       !mergeconfstring(&dst->dynamiclookupcommand, &src->dynamiclookupcommand))
+       !mergeconfstring(&dst->confrewriteusername, &src->confrewriteusername) ||
+       !mergeconfstring(&dst->dynamiclookupcommand, &src->dynamiclookupcommand) ||
+       !mergeconfstring(&dst->fticks_viscountry, &src->fticks_viscountry) ||
+       !mergeconfstring(&dst->fticks_visinst, &src->fticks_visinst))
        return 0;
     if (src->pdef)
        dst->pdef = src->pdef;
@@ -2647,10 +2752,31 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) {
     return 1;
 }
 
+/** Set *AF according to IPV4ONLY and IPV6ONLY:
+    - If both are set, the function fails.
+    - If exactly one is set, *AF is set accordingly.
+    - If none is set, *AF is not affected.
+    Return 0 on success and !0 on failure.
+    In the case of an error, *AF is not affected.  */
+int config_hostaf(const char *desc, int ipv4only, int ipv6only, int *af) {
+    assert(af != NULL);
+    if (ipv4only && ipv6only) {
+       debug(DBG_ERR, "error in block %s, at most one of IPv4Only and "
+              "IPv6Only can be enabled", desc);
+        return -1;
+    }
+    if (ipv4only)
+        *af = AF_INET;
+    if (ipv6only)
+        *af = AF_INET6;
+    return 0;
+}
+
 int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     struct clsrvconf *conf;
     char *conftype = NULL, *rewriteinalias = NULL;
     long int dupinterval = LONG_MIN, addttl = LONG_MIN;
+    uint8_t ipv4only = 0, ipv6only = 0;
 
     debug(DBG_DBG, "confclient_cb called for %s", block);
 
@@ -2664,6 +2790,8 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
            cf, block,
            "type", CONF_STR, &conftype,
            "host", CONF_MSTR, &conf->hostsrc,
+            "IPv4Only", CONF_BLN, &ipv4only,
+            "IPv6Only", CONF_BLN, &ipv6only,
            "secret", CONF_STR, &conf->secret,
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
            "tls", CONF_STR, &conf->tls,
@@ -2676,6 +2804,10 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
            "rewriteIn", CONF_STR, &conf->confrewritein,
            "rewriteOut", CONF_STR, &conf->confrewriteout,
            "rewriteattribute", CONF_STR, &conf->confrewriteusername,
+#if defined(WANT_FTICKS)
+           "fticksVISCOUNTRY", CONF_STR, &conf->fticks_viscountry,
+           "fticksVISINST", CONF_STR, &conf->fticks_visinst,
+#endif
            NULL
            ))
        debugx(1, DBG_ERR, "configuration error");
@@ -2701,7 +2833,9 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
 
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
     if (conf->type == RAD_TLS || conf->type == RAD_DTLS) {
-       conf->tlsconf = conf->tls ? tlsgettls(conf->tls, NULL) : tlsgettls("defaultclient", "default");
+       conf->tlsconf = conf->tls
+            ? tlsgettls(conf->tls, NULL)
+            : tlsgettls("defaultClient", "default");
        if (!conf->tlsconf)
            debugx(1, DBG_ERR, "error in block %s, no tls context defined", block);
        if (conf->matchcertattr && !addmatchcertattr(conf))
@@ -2709,6 +2843,12 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     }
 #endif
 
+    conf->hostaf = AF_UNSPEC;
+    if (config_hostaf("top level", options.ipv4only, options.ipv6only, &conf->hostaf))
+        debugx(1, DBG_ERR, "config error: ^");
+    if (config_hostaf(block, ipv4only, ipv6only, &conf->hostaf))
+        debugx(1, DBG_ERR, "error in block %s: ^", block);
+
     if (dupinterval != LONG_MIN) {
        if (dupinterval < 0 || dupinterval > 255)
            debugx(1, DBG_ERR, "error in block %s, value of option DuplicateInterval is %d, must be 0-255", block, dupinterval);
@@ -2726,7 +2866,9 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
        conf->confrewritein = rewriteinalias;
     else
        free(rewriteinalias);
-    conf->rewritein = conf->confrewritein ? getrewrite(conf->confrewritein, NULL) : getrewrite("defaultclient", "default");
+    conf->rewritein = conf->confrewritein
+        ? getrewrite(conf->confrewritein, NULL)
+        : getrewrite("defaultClient", "default");
     if (conf->confrewriteout)
        conf->rewriteout = getrewrite(conf->confrewriteout, NULL);
 
@@ -2737,8 +2879,8 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     }
 
     if (!addhostport(&conf->hostports, conf->hostsrc, conf->pdef->portdefault, 1) ||
-       !resolvehostports(conf->hostports, conf->pdef->socktype))
-       debugx(1, DBG_ERR, "resolve failed, exiting");
+       !resolvehostports(conf->hostports, conf->hostaf, conf->pdef->socktype))
+       debugx(1, DBG_ERR, "%s: resolve failed, exiting", __func__);
 
     if (!conf->secret) {
        if (!conf->pdef->secretdefault)
@@ -2761,7 +2903,9 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
 int compileserverconfig(struct clsrvconf *conf, const char *block) {
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
     if (conf->type == RAD_TLS || conf->type == RAD_DTLS) {
-       conf->tlsconf = conf->tls ? tlsgettls(conf->tls, NULL) : tlsgettls("defaultserver", "default");
+       conf->tlsconf = conf->tls
+            ? tlsgettls(conf->tls, NULL)
+            : tlsgettls("defaultServer", "default");
        if (!conf->tlsconf) {
            debug(DBG_ERR, "error in block %s, no tls context defined", block);
            return 0;
@@ -2786,17 +2930,21 @@ int compileserverconfig(struct clsrvconf *conf, const char *block) {
     if (conf->retrycount == 255)
        conf->retrycount = conf->pdef->retrycountdefault;
 
-    conf->rewritein = conf->confrewritein ? getrewrite(conf->confrewritein, NULL) : getrewrite("defaultserver", "default");
+    conf->rewritein = conf->confrewritein
+        ? getrewrite(conf->confrewritein, NULL)
+        : getrewrite("defaultServer", "default");
     if (conf->confrewriteout)
        conf->rewriteout = getrewrite(conf->confrewriteout, NULL);
 
     if (!addhostport(&conf->hostports, conf->hostsrc, conf->portsrc, 0)) {
-       debug(DBG_ERR, "error in block %s, failed to parse %s", block, conf->hostsrc);
+       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");
+    if (!conf->dynamiclookupcommand &&
+        !resolvehostports(conf->hostports, conf->hostaf,
+                          conf->pdef->socktype)) {
+       debug(DBG_ERR, "%s: resolve failed", __func__);
        return 0;
     }
     return 1;
@@ -2806,6 +2954,7 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     struct clsrvconf *conf, *resconf;
     char *conftype = NULL, *rewriteinalias = NULL;
     long int retryinterval = LONG_MIN, retrycount = LONG_MIN, addttl = LONG_MIN;
+    uint8_t ipv4only = 0, ipv6only = 0;
 
     debug(DBG_DBG, "confserver_cb called for %s", block);
 
@@ -2826,6 +2975,8 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     if (!getgenericconfig(cf, block,
                          "type", CONF_STR, &conftype,
                          "host", CONF_MSTR, &conf->hostsrc,
+                          "IPv4Only", CONF_BLN, &ipv4only,
+                          "IPv6Only", CONF_BLN, &ipv6only,
                          "port", CONF_STR, &conf->portsrc,
                          "secret", CONF_STR, &conf->secret,
 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
@@ -2873,6 +3024,12 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
     free(conftype);
     conftype = NULL;
 
+    conf->hostaf = AF_UNSPEC;
+    if (config_hostaf("top level", options.ipv4only, options.ipv6only, &conf->hostaf))
+        debugx(1, DBG_ERR, "config error: ^");
+    if (config_hostaf(block, ipv4only, ipv6only, &conf->hostaf))
+        goto errexit;
+
     conf->pdef = protodefs[conf->type];
 
     if (!conf->confrewritein)
@@ -2920,7 +3077,8 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char
 
     if (resconf || !conf->dynamiclookupcommand) {
        if (!compileserverconfig(conf, block))
-           goto errexit;
+            return 0; /* Don't goto errexit and free resconf -- it's
+                       * not ours to free.  */
     }
 
     if (!conf->secret) {
@@ -3003,76 +3161,16 @@ int setprotoopts(uint8_t type, char **listenargs, char *sourcearg) {
     return 1;
 }
 
-int configure_fticks(char **reporting, char **mac, char **key) {
-    int r = 0;
-
-    if (*reporting == NULL)
-       goto out;
-
-    if (strcasecmp(*reporting, "None") == 0)
-       options.fticks_reporting = RSP_FTICKS_REPORTING_NONE;
-    else if (strcasecmp(*reporting, "Basic") == 0)
-       options.fticks_reporting = RSP_FTICKS_REPORTING_BASIC;
-    else if (strcasecmp(*reporting, "Full") == 0)
-       options.fticks_reporting = RSP_FTICKS_REPORTING_FULL;
-    else {
-       debugx(1, DBG_ERR, "config error: invalid F-Ticks-Reporting value: %s",
-              *reporting);
-       r = 1;
-       goto out;
-    }
-
-    if (strcasecmp(*mac, "Static") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_STATIC;
-    else if (strcasecmp(*mac, "Original") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_ORIGINAL;
-    else if (strcasecmp(*mac, "VendorHashed") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_VENDOR_HASHED;
-    else if (strcasecmp(*mac, "VendorKeyHashed") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_VENDOR_KEY_HASHED;
-    else if (strcasecmp(*mac, "FullyHashed") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_FULLY_HASHED;
-    else if (strcasecmp(*mac, "FullyKeyHashed") == 0)
-       options.fticks_mac = RSP_FTICKS_MAC_FULLY_KEY_HASHED;
-    else {
-       debugx(1, DBG_ERR, "config error: invalid F-Ticks-MAC value: %s", *mac);
-       r = 1;
-       goto out;
-    }
-
-    if (*key == NULL
-       && (options.fticks_mac == RSP_FTICKS_MAC_VENDOR_KEY_HASHED
-           || options.fticks_mac == RSP_FTICKS_MAC_FULLY_KEY_HASHED)) {
-       debugx(1, DBG_ERR,
-              "config error: F-Ticks-MAC %s requires an F-Ticks-Key", *mac);
-       options.fticks_mac = RSP_FTICKS_MAC_STATIC;
-       r = 1;
-       goto out;
-    }
-
-    if (*key != NULL)
-       options.fticks_key = *key;
-
-out:
-    if (*reporting != NULL) {
-       free(*reporting);
-       *reporting = NULL;
-    }
-    if (*mac != NULL) {
-       free(*mac);
-       *mac = NULL;
-    }
-    return r;
-}
-
 void getmainconfig(const char *configfile) {
     long int addttl = LONG_MIN, loglevel = LONG_MIN;
     struct gconffile *cfs;
     char **listenargs[RAD_PROTOCOUNT];
     char *sourcearg[RAD_PROTOCOUNT];
-    char *fticks_reporting_str = NULL;
-    char *fticks_mac_str = NULL;
-    char *fticks_key_str = NULL;
+#if defined(WANT_FTICKS)
+    uint8_t *fticks_reporting_str = NULL;
+    uint8_t *fticks_mac_str = NULL;
+    uint8_t *fticks_key_str = NULL;
+#endif
     int i;
 
     cfs = openconfigfile(configfile);
@@ -3114,6 +3212,7 @@ void getmainconfig(const char *configfile) {
            "ListenDTLS", CONF_MSTR, &listenargs[RAD_DTLS],
            "SourceDTLS", CONF_STR, &sourcearg[RAD_DTLS],
 #endif
+            "PidFile", CONF_STR, &options.pidfile,
            "TTLAttribute", CONF_STR, &options.ttlattr,
            "addTTL", CONF_LINT, &addttl,
            "LogLevel", CONF_LINT, &loglevel,
@@ -3126,9 +3225,14 @@ void getmainconfig(const char *configfile) {
            "TLS", CONF_CBK, conftls_cb, NULL,
 #endif
            "Rewrite", CONF_CBK, confrewrite_cb, NULL,
-           "F-Ticks-Reporting", CONF_STR, &fticks_reporting_str,
-           "F-Ticks-MAC", CONF_STR, &fticks_mac_str,
-           "F-Ticks-Key", CONF_STR, &fticks_key_str,
+#if defined(WANT_FTICKS)
+           "FTicksReporting", CONF_STR, &fticks_reporting_str,
+           "FTicksMAC", CONF_STR, &fticks_mac_str,
+           "FTicksKey", CONF_STR, &fticks_key_str,
+           "FTicksSyslogFacility", CONF_STR, &options.ftickssyslogfacility,
+#endif
+            "IPv4Only", CONF_BLN, &options.ipv4only,
+            "IPv6Only", CONF_BLN, &options.ipv6only,
            NULL
            ))
        debugx(1, DBG_ERR, "configuration error");
@@ -3146,7 +3250,10 @@ void getmainconfig(const char *configfile) {
     if (!setttlattr(&options, DEFAULT_TTL_ATTR))
        debugx(1, DBG_ERR, "Failed to set TTLAttribute, exiting");
 
-    configure_fticks(&fticks_reporting_str, &fticks_mac_str, &fticks_key_str);
+#if defined(WANT_FTICKS)
+    fticks_configure(&options, &fticks_reporting_str, &fticks_mac_str,
+                    &fticks_key_str);
+#endif
 
     for (i = 0; i < RAD_PROTOCOUNT; i++)
        if (listenargs[i] || sourcearg[i])
@@ -3253,7 +3360,7 @@ int createpidfile(const char *pidfile) {
     return f && !fclose(f) && r >= 0;
 }
 
-int main(int argc, char **argv) {
+int radsecproxy_main(int argc, char **argv) {
     pthread_t sigth;
     sigset_t sigset;
     struct list_node *entry;
@@ -3265,6 +3372,15 @@ int main(int argc, char **argv) {
     debug_init("radsecproxy");
     debug_set_level(DEBUG_LEVEL);
 
+    if (pthread_attr_init(&pthread_attr))
+       debugx(1, DBG_ERR, "pthread_attr_init failed");
+    if (pthread_attr_setstacksize(&pthread_attr, PTHREAD_STACK_SIZE))
+       debugx(1, DBG_ERR, "pthread_attr_setstacksize failed");
+#if defined(HAVE_MALLOPT)
+    if (mallopt(M_TRIM_THRESHOLD, 4 * 1024) != 1)
+       debugx(1, DBG_ERR, "mallopt failed");
+#endif
+
     for (i = 0; i < RAD_PROTOCOUNT; i++)
        protodefs[i] = protoinits[i](i);
 
@@ -3279,8 +3395,18 @@ int main(int argc, char **argv) {
        options.loglevel = loglevel;
     else if (options.loglevel)
        debug_set_level(options.loglevel);
-    if (!foreground)
-       debug_set_destination(options.logdestination ? options.logdestination : "x-syslog:///");
+    if (!foreground) {
+       debug_set_destination(options.logdestination
+                              ? options.logdestination
+                              : "x-syslog:///", LOG_TYPE_DEBUG);
+#if defined(WANT_FTICKS)
+       if (options.ftickssyslogfacility) {
+            debug_set_destination(options.ftickssyslogfacility,
+                                  LOG_TYPE_FTICKS);
+            free(options.ftickssyslogfacility);
+       }
+#endif
+    }
     free(options.logdestination);
 
     if (!list_first(clconfs))
@@ -3296,6 +3422,8 @@ int main(int argc, char **argv) {
 
     debug_timestamp_on();
     debug(DBG_INFO, "radsecproxy revision %s starting", PACKAGE_VERSION);
+    if (!pidfile)
+        pidfile = options.pidfile;
     if (pidfile && !createpidfile(pidfile))
        debugx(1, DBG_ERR, "failed to create pidfile %s: %s", pidfile, strerror(errno));
 
@@ -3304,7 +3432,7 @@ int main(int argc, char **argv) {
     sigaddset(&sigset, SIGHUP);
     sigaddset(&sigset, SIGPIPE);
     pthread_sigmask(SIG_BLOCK, &sigset, NULL);
-    pthread_create(&sigth, NULL, sighandler, NULL);
+    pthread_create(&sigth, &pthread_attr, sighandler, NULL);
 
     for (entry = list_first(srvconfs); entry; entry = list_next(entry)) {
        srvconf = (struct clsrvconf *)entry->data;
@@ -3312,7 +3440,7 @@ int main(int argc, char **argv) {
            continue;
        if (!addserver(srvconf))
            debugx(1, DBG_ERR, "failed to add server");
-       if (pthread_create(&srvconf->servers->clientth, NULL, clientwr,
+       if (pthread_create(&srvconf->servers->clientth, &pthread_attr, clientwr,
                           (void *)(srvconf->servers)))
            debugx(1, DBG_ERR, "pthread_create failed");
     }