From: Jan Rękorajski Date: Wed, 13 Oct 2021 19:49:35 +0000 (+0200) Subject: - sync with upstream git X-Git-Tag: auto/th/radsecproxy-1.9.0-1 X-Git-Url: http://git.pld-linux.org/?p=packages%2Fradsecproxy.git;a=commitdiff_plain;h=aaedaf6f0d799f103ed257ec95ba4a7e21bcb2b6 - sync with upstream git --- diff --git a/git.patch b/git.patch new file mode 100644 index 0000000..a0fdca1 --- /dev/null +++ b/git.patch @@ -0,0 +1,2058 @@ +diff --git a/ChangeLog b/ChangeLog +index c1943fb..d6552db 100644 +--- a/ChangeLog ++++ b/ChangeLog +@@ -1,3 +1,20 @@ ++unreleased changes ++ New features: ++ - Optionally log accounting requests when respoinding directly (#72) ++ - SNI support for outgoing connections (#90) ++ ++ Misc: ++ - Don't require server type to be set by dyndisc scripts ++ - OpenSSL 3.0 compatibility (#70) ++ ++ Bug Fixes: ++ - Fix refused startup with openssl <1.1 (#82) ++ - Fix compiler issue for Fedora 33 on s390x (#84) ++ - Fix small memory leak in config parser ++ - Fix lazy certificate check when connecting to TLS servers ++ - Fix connect is aborted if first host in list has invalid certificate ++ - Fix setstacksize for glibc 2.34 (#91) ++ + 2021-05-28 1.9.0 + New features: + - Accept multiple source* configs for IPv4/v6 +diff --git a/README b/README +index 20700fa..5074c77 100644 +--- a/README ++++ b/README +@@ -8,11 +8,12 @@ flexible, while at the same time to be small, efficient and easy to configure. + Official packages are available: + + Debian: apt-get install radsecproxy ++CentOS/RHEL/Rocky: yum install epel-release; yum install radsecproxy + Fedora: dnf install radsecproxy + FreeBSD: pkg install radsecproxy + NetBSD: pkgin install radsecproxy + +-Or built it from this rouce on most Unix like systems by simply typing ++Or built it from this source on most Unix like systems by simply typing + + ./configure && make + +diff --git a/configure.ac b/configure.ac +index 630fcac..d486e47 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -11,6 +11,12 @@ AC_PROG_RANLIB + AC_CHECK_FUNCS([mallopt]) + AC_REQUIRE_AUX_FILE([tap-driver.sh]) + ++m4_version_prereq(2.70, [], [AC_PROG_CC_C99]) ++if test "$ac_cv_prog_cc_c99" = "no"; then ++ echo "requires C99 compatible compiler" ++ exit -1 ++fi ++ + udp=yes + AC_ARG_ENABLE(udp, + [ --enable-udp whether to enable UDP transport: yes/no; default yes ], +diff --git a/dtls.c b/dtls.c +index 09aa9ee..15835d6 100644 +--- a/dtls.c ++++ b/dtls.c +@@ -142,6 +142,7 @@ int dtlsread(SSL *ssl, unsigned char *buf, int num, int timeout, pthread_mutex_t + continue; + case SSL_ERROR_ZERO_RETURN: + debug(DBG_DBG, "dtlsread: got ssl shutdown"); ++ /* fallthrough */ + default: + while ((error = ERR_get_error())) + debug(DBG_ERR, "dtlsread: SSL: %s", ERR_error_string(error, NULL)); +@@ -305,10 +306,11 @@ void *dtlsservernew(void *arg) { + struct timeval timeout; + struct addrinfo tmpsrvaddr; + char tmp[INET6_ADDRSTRLEN], *subj; ++ struct hostportres *hp; + + debug(DBG_WARN, "dtlsservernew: incoming DTLS connection from %s", addr2string((struct sockaddr *)¶ms->addr, tmp, sizeof(tmp))); + +- conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, NULL); ++ conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, NULL, &hp); + if (!conf) + goto exit; + +@@ -342,7 +344,7 @@ void *dtlsservernew(void *arg) { + accepted_tls = conf->tlsconf; + + while (conf) { +- if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf)) { ++ if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf, NULL)) { + subj = getcertsubject(cert); + if(subj) { + debug(DBG_WARN, "dtlsservernew: DTLS connection from %s, client %s, subject %s up", +@@ -362,9 +364,10 @@ void *dtlsservernew(void *arg) { + } + goto exit; + } +- conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, &cur); ++ conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, &cur, &hp); + } +- debug(DBG_WARN, "dtlsservernew: ignoring request, no matching TLS client"); ++ debug(DBG_WARN, "dtlsservernew: ignoring request, no matching TLS client for %s", ++ addr2string((struct sockaddr *)¶ms->addr, tmp, sizeof(tmp))); + + if (cert) + X509_free(cert); +@@ -467,7 +470,7 @@ void *dtlslistener(void *arg) { + continue; + } + +- conf = find_clconf(handle, (struct sockaddr *)&from, NULL); ++ conf = find_clconf(handle, (struct sockaddr *)&from, NULL, NULL); + if (!conf) { + debug(DBG_INFO, "dtlslistener: got UDP from unknown peer %s, ignoring", addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); + if (recv(s, buf, 4, 0) == -1) +@@ -486,6 +489,7 @@ void *dtlslistener(void *arg) { + ssl = SSL_new(ctx); + if (!ssl) { + pthread_mutex_unlock(&conf->tlsconf->lock); ++ debug(DBG_ERR, "dtlslistener: failed to create SSL connection"); + continue; + } + bio = BIO_new_dgram(s, BIO_NOCLOSE); +@@ -514,12 +518,28 @@ void *dtlslistener(void *arg) { + } else { + free(params); + } ++ } else { ++ unsigned long error; ++ while ((error = ERR_get_error())) ++ debug(DBG_ERR, "dtlslistener: DTLS_listen failed: %s", ERR_error_string(error, NULL)); ++ debug(DBG_ERR, "dtlslistener: DTLS_listen failed from %s", addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); + } + pthread_mutex_unlock(&conf->tlsconf->lock); + } + return NULL; + } + ++static void cleanup_connection(struct server *server) { ++ if (server->ssl) ++ SSL_shutdown(server->ssl); ++ if (server->sock >= 0) ++ close(server->sock); ++ server->sock = -1; ++ if (server->ssl) ++ SSL_free(server->ssl); ++ server->ssl = NULL; ++} ++ + int dtlsconnect(struct server *server, int timeout, char *text) { + struct timeval socktimeout, now, start; + time_t wait; +@@ -531,6 +551,7 @@ int dtlsconnect(struct server *server, int timeout, char *text) { + BIO *bio; + struct addrinfo *source = NULL; + char *subj; ++ struct list_node *entry; + + debug(DBG_DBG, "dtlsconnect: called from %s", text); + pthread_mutex_lock(&server->lock); +@@ -540,8 +561,6 @@ int dtlsconnect(struct server *server, int timeout, char *text) { + + pthread_mutex_unlock(&server->lock); + +- hp = (struct hostportres *)list_first(server->conf->hostports)->data; +- + if(server->conf->source) { + source = resolvepassiveaddrinfo(server->conf->source, AF_UNSPEC, NULL, protodefs.socktype); + if(!source) +@@ -552,13 +571,7 @@ int dtlsconnect(struct server *server, int timeout, char *text) { + + for (;;) { + /* ensure previous connection is properly closed */ +- if (server->ssl) +- SSL_shutdown(server->ssl); +- if (server->sock >= 0) +- close(server->sock); +- if (server->ssl) +- SSL_free(server->ssl); +- server->ssl = NULL; ++ cleanup_connection(server); + + wait = connect_wait(start, server->connecttime, firsttry); + gettimeofday(&now, NULL); +@@ -567,55 +580,84 @@ int dtlsconnect(struct server *server, int timeout, char *text) { + if (source) freeaddrinfo(source); + return 0; + } +- debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); ++ if (wait) debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); + sleep(wait); + firsttry = 0; + +- debug(DBG_INFO, "dtlsconnect: connecting to %s port %s", hp->host, hp->port); ++ for (entry = list_first(server->conf->hostports); entry; entry = list_next(entry)) { ++ hp = (struct hostportres *)entry->data; ++ debug(DBG_INFO, "dtlsconnect: trying to open DTLS connection to server %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ if ((server->sock = bindtoaddr(source ? source : srcres, hp->addrinfo->ai_family, 0)) < 0) { ++ debug(DBG_ERR, "dtlsconnect: faild to bind socket for server %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } ++ if (connect(server->sock, hp->addrinfo->ai_addr, hp->addrinfo->ai_addrlen)) { ++ debug(DBG_ERR, "dtlsconnect: faild to connect socket for server %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } + +- if ((server->sock = bindtoaddr(source ? source : srcres, hp->addrinfo->ai_family, 0)) < 0) +- continue; +- if (connect(server->sock, hp->addrinfo->ai_addr, hp->addrinfo->ai_addrlen)) +- continue; ++ pthread_mutex_lock(&server->conf->tlsconf->lock); ++ if (!(ctx = tlsgetctx(handle, server->conf->tlsconf))){ ++ pthread_mutex_unlock(&server->conf->tlsconf->lock); ++ debug(DBG_ERR, "dtlsconnect: failed to get TLS context for server %s", server->conf->name); ++ goto concleanup; ++ } + +- pthread_mutex_lock(&server->conf->tlsconf->lock); +- if (!(ctx = tlsgetctx(handle, server->conf->tlsconf))){ ++ server->ssl = SSL_new(ctx); + pthread_mutex_unlock(&server->conf->tlsconf->lock); +- continue; +- } ++ if (!server->ssl) { ++ debug(DBG_ERR, "dtlsconnect: failed to create SSL conneciton for server %s", server->conf->name); ++ goto concleanup; ++ } + +- server->ssl = SSL_new(ctx); +- pthread_mutex_unlock(&server->conf->tlsconf->lock); +- if (!server->ssl) +- continue; ++ if (server->conf->sni) { ++ struct in6_addr tmp; ++ char *servername = server->conf->sniservername ? server->conf->sniservername : ++ (inet_pton(AF_INET, hp->host, &tmp) || inet_pton(AF_INET6, hp->host, &tmp)) ? NULL : hp->host; ++ if (servername && !tlssetsni(server->ssl, servername)) { ++ debug(DBG_ERR, "tlsconnect: set SNI %s failed", servername); ++ goto concleanup; ++ } ++ } + +- bio = BIO_new_dgram(server->sock, BIO_CLOSE); +- BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, hp->addrinfo->ai_addr); +- SSL_set_bio(server->ssl, bio, bio); +- if (sslconnecttimeout(server->ssl, 5) <= 0) { +- while ((error = ERR_get_error())) +- debug(DBG_ERR, "dtlsconnect: SSL connect to %s failed: %s", server->conf->name, ERR_error_string(error, NULL)); +- debug(DBG_ERR, "dtlsconnect: SSL connect to %s failed", server->conf->name); +- continue; +- } +- socktimeout.tv_sec = 5; +- socktimeout.tv_usec = 0; +- if (BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &socktimeout) == -1) +- debug(DBG_WARN, "dtlsconnect: BIO_CTRL_DGRAM_SET_RECV_TIMEOUT failed"); ++ bio = BIO_new_dgram(server->sock, BIO_CLOSE); ++ BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, hp->addrinfo->ai_addr); ++ SSL_set_bio(server->ssl, bio, bio); ++ if (sslconnecttimeout(server->ssl, 5) <= 0) { ++ while ((error = ERR_get_error())) ++ debug(DBG_ERR, "dtlsconnect: SSL connect to %s failed: %s", server->conf->name, ERR_error_string(error, NULL)); ++ debug(DBG_ERR, "dtlsconnect: SSL connect to %s (%s port %s) failed", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } ++ socktimeout.tv_sec = 5; ++ socktimeout.tv_usec = 0; ++ if (BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &socktimeout) == -1) ++ debug(DBG_WARN, "dtlsconnect: BIO_CTRL_DGRAM_SET_RECV_TIMEOUT failed"); ++ ++ cert = verifytlscert(server->ssl); ++ if (!cert) { ++ debug(DBG_ERR, "tlsconnect: certificate verification failed for %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } + +- cert = verifytlscert(server->ssl); +- if (!cert) +- continue; +- if (verifyconfcert(cert, server->conf)) { +- subj = getcertsubject(cert); +- if(subj) { +- debug(DBG_WARN, "dtlsconnect: DTLS connection to %s, subject %s up", server->conf->name, subj); +- free(subj); ++ if (verifyconfcert(cert, server->conf, hp)) { ++ subj = getcertsubject(cert); ++ if(subj) { ++ debug(DBG_WARN, "dtlsconnect: DTLS connection to %s (%s port %s), subject %s up", server->conf->name, hp->host, hp->port, subj); ++ free(subj); ++ } ++ X509_free(cert); ++ break; ++ } else { ++ debug(DBG_ERR, "tlsconnect: certificate verification failed for %s (%s port %s)", server->conf->name, hp->host, hp->port); + } + X509_free(cert); +- break; ++ ++concleanup: ++ /* ensure previous connection is properly closed */ ++ cleanup_connection(server); + } +- X509_free(cert); ++ if (server->ssl) break; + } + + pthread_mutex_lock(&server->lock); +diff --git a/hostport.c b/hostport.c +index 6408505..d0651d4 100644 +--- a/hostport.c ++++ b/hostport.c +@@ -222,7 +222,7 @@ int resolvehostports(struct list *hostports, int af, int socktype) { + } + + struct addrinfo *resolvepassiveaddrinfo(char **hostport, int af, char *default_port, int socktype) { +- struct addrinfo *ai = NULL, *last_ai; ++ struct addrinfo *ai = NULL, *last_ai = NULL; + int i; + char *any[2] = {"*", NULL}; + +@@ -258,7 +258,7 @@ static int prefixmatch(void *a1, void *a2, uint8_t len) { + return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]); + } + +-int _internal_addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t prefixlen, uint8_t checkport) { ++int _internal_addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t prefixlen, uint8_t checkport, struct hostportres **hpreturn) { + struct sockaddr_in6 *sa6 = NULL; + struct in_addr *a4 = NULL; + struct addrinfo *res; +@@ -287,16 +287,20 @@ int _internal_addressmatches(struct list *hostports, struct sockaddr *addr, uint + !memcmp(&sa6->sin6_addr, + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16) && + (!checkport || ((struct sockaddr_in6 *)res->ai_addr)->sin6_port == +- ((struct sockaddr_in6 *)addr)->sin6_port))) ++ ((struct sockaddr_in6 *)addr)->sin6_port))) { + ++ if (hpreturn) *hpreturn = hp; + return 1; ++ } + } else if (hp->prefixlen <= prefixlen) { + if ((a4 && res->ai_family == AF_INET && + prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, hp->prefixlen)) || + (sa6 && res->ai_family == AF_INET6 && +- prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, hp->prefixlen))) ++ prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, hp->prefixlen))) { + ++ if (hpreturn) *hpreturn = hp; + return 1; ++ } + } + } + } +@@ -312,18 +316,18 @@ int hostportmatches(struct list *hostports, struct list *matchhostports, uint8_t + match = (struct hostportres *)entry->data; + + for (res = match->addrinfo; res; res = res->ai_next) { +- if (_internal_addressmatches(hostports, res->ai_addr, match->prefixlen, checkport)) ++ if (_internal_addressmatches(hostports, res->ai_addr, match->prefixlen, checkport, NULL)) + return 1; + } + } + return 0; + } + +-int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport) { +- return _internal_addressmatches(hostports, addr, 255, checkport); ++int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport, struct hostportres **hp) { ++ return _internal_addressmatches(hostports, addr, 255, checkport, hp); + } + +-int connecttcphostlist(struct list *hostports, struct addrinfo *src) { ++int connecttcphostlist(struct list *hostports, struct addrinfo *src, struct hostportres** hpreturn) { + int s; + struct list_node *entry; + struct hostportres *hp = NULL; +@@ -333,6 +337,7 @@ int connecttcphostlist(struct list *hostports, struct addrinfo *src) { + debug(DBG_WARN, "connecttcphostlist: trying to open TCP connection to %s port %s", hp->host, hp->port); + if ((s = connecttcp(hp->addrinfo, src, list_count(hostports) > 1 ? 5 : 30)) >= 0) { + debug(DBG_WARN, "connecttcphostlist: TCP connection to %s port %s up", hp->host, hp->port); ++ if (hpreturn) *hpreturn = hp; + return s; + } + } +diff --git a/hostport.h b/hostport.h +index 9069ecd..a554a77 100644 +--- a/hostport.h ++++ b/hostport.h +@@ -2,6 +2,9 @@ + * Copyright (c) 2012, NORDUnet A/S */ + /* See LICENSE for licensing information. */ + ++#ifndef _HOSTPORT_H ++#define _HOSTPORT_H ++ + struct hostportres { + char *host; + char *port; +@@ -17,9 +20,10 @@ int resolvehostport(struct hostportres *hp, int af, int socktype, uint8_t passiv + int resolvehostports(struct list *hostports, int af, int socktype); + struct addrinfo *resolvepassiveaddrinfo(char **hostport, int af, char *default_port, int socktype); + int hostportmatches(struct list *hostports, struct list *matchhostports, uint8_t checkport); +-int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport); +-int connecttcphostlist(struct list *hostports, struct addrinfo *src); ++int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport, struct hostportres **hp); ++int connecttcphostlist(struct list *hostports, struct addrinfo *src, struct hostportres **hpreturn); + ++#endif /* _HOSTPORT_H */ + /* Local Variables: */ + /* c-file-style: "stroustrup" */ + /* End: */ +diff --git a/list.h b/list.h +index f015b9d..c8c4531 100644 +--- a/list.h ++++ b/list.h +@@ -35,7 +35,7 @@ int list_push(struct list *list, void *data); + /* removes first entry from list and returns data */ + void *list_shift(struct list *list); + +-/* removes first entry with matching data pointer */ ++/* removes all entries with matching data pointer */ + void list_removedata(struct list *list, void *data); + + /* returns first node */ +diff --git a/raddict.h b/raddict.h +new file mode 100644 +index 0000000..8a9c598 +--- /dev/null ++++ b/raddict.h +@@ -0,0 +1,42 @@ ++#ifndef _RADDICT_H ++#define _RADDICT_H ++ ++const char* RAD_Attr_Acct_Terminate_Cause_Dict[] = { ++ [1] = "User-Request", ++ "Lost-Carrier", ++ "Lost-Service", ++ "Idle-Timeout", ++ "Session-Timeout", ++ "Admin-Reset", ++ "Admin-Reboot", ++ "Port-Error", ++ "NAS-Error", ++ "NAS-Request", ++ "NAS-Reboot", ++ "Port-Unneeded", ++ "Port-Preempted", ++ "Port-Suspended", ++ "Service-Unavailable", ++ "Callback", ++ "User-Error", ++ "Host-Request", ++}; ++ ++const char* RAD_Attr_Acct_Status_Type_Dict[] = { ++ [1] = "Start", ++ [2] = "Stop", ++ [3] = "Interim-Update", ++ [7] = "Accounting-On", ++ [8] = "Accounting-Off", ++ [9] = "Tunnel-Start", ++ [10] = "Tunnel-Stop", ++ [11] = "Tunnel-Reject", ++ [12] = "Tunnel-Link-Start", ++ [13] = "Tunnel-Link-Stop", ++ [14] = "Tunnel-Link-Reject", ++ [15] = "Failed", ++}; ++ ++#define RAD_Attr_Dict_Undef "UNKNOWN" ++ ++#endif /*_RADDICT_H*/ +diff --git a/radmsg.c b/radmsg.c +index 5f49237..afd7c60 100644 +--- a/radmsg.c ++++ b/radmsg.c +@@ -15,6 +15,8 @@ + #include + #include + #include ++#include "raddict.h" ++#include "util.h" + + #define RADLEN(x) ntohs(((uint16_t *)(x))[1]) + +@@ -414,6 +416,28 @@ int resizeattr(struct tlv *attr, uint8_t newlen) { + return 0; + } + ++const char* attrval2strdict(struct tlv *attr) { ++ uint32_t val; ++ ++ if(!attr) return NULL; ++ val = tlv2longint(attr); ++ switch (attr->t) { ++ case RAD_Attr_Acct_Status_Type: ++ if(val < sizeof(RAD_Attr_Acct_Status_Type_Dict)/sizeof(RAD_Attr_Acct_Status_Type_Dict[0])) ++ return RAD_Attr_Acct_Status_Type_Dict[val] ? RAD_Attr_Acct_Status_Type_Dict[val] : RAD_Attr_Dict_Undef; ++ break; ++ ++ case RAD_Attr_Acct_Terminate_Cause: ++ if(val < sizeof(RAD_Attr_Acct_Terminate_Cause_Dict)/sizeof(RAD_Attr_Acct_Terminate_Cause_Dict[0])) ++ return RAD_Attr_Acct_Terminate_Cause_Dict[val] ? RAD_Attr_Acct_Terminate_Cause_Dict[val] : RAD_Attr_Dict_Undef; ++ break; ++ ++ default: ++ break; ++ } ++ return NULL; ++} ++ + /* Local Variables: */ + /* c-file-style: "stroustrup" */ + /* End: */ +diff --git a/radmsg.h b/radmsg.h +index 1738c8e..634cf13 100644 +--- a/radmsg.h ++++ b/radmsg.h +@@ -21,16 +21,37 @@ + #define RAD_Attr_User_Name 1 + #define RAD_Attr_User_Password 2 + #define RAD_Attr_CHAP_Password 3 ++#define RAD_Attr_NAS_IP_Address 4 ++#define RAD_Attr_Framed_IP_Address 8 + #define RAD_Attr_Reply_Message 18 + #define RAD_Attr_Vendor_Specific 26 ++#define RAD_Attr_Called_Station_Id 30 + #define RAD_Attr_Calling_Station_Id 31 + #define RAD_Attr_Proxy_State 33 ++#define RAD_Attr_Acct_Status_Type 40 ++#define RAD_Attr_Acct_Input_Octets 42 ++#define RAD_Attr_Acct_Output_Octets 43 ++#define RAD_Attr_Acct_Session_Id 44 ++#define RAD_Attr_Acct_Session_Time 46 ++#define RAD_Attr_Acct_Input_Packets 47 ++#define RAD_Attr_Acct_Output_Packets 48 ++#define RAD_Attr_Acct_Terminate_Cause 49 ++#define RAD_Attr_Event_Timestamp 55 + #define RAD_Attr_CHAP_Challenge 60 + #define RAD_Attr_Tunnel_Password 69 + #define RAD_Attr_Message_Authenticator 80 + #define RAD_Attr_CUI 89 + #define RAD_Attr_Operator_Name 126 + ++#define RAD_Acct_Status_Start 1 ++#define RAD_Acct_Status_Stop 2 ++#define RAD_Acct_Status_Alive 3 ++#define RAD_Acct_Status_Interim_Update 3 ++#define RAD_Acct_Status_Accounting_On 7 ++#define RAD_Acct_Status_Accounting_Off 8 ++#define RAD_Acct_Status_Failed 15 ++ ++ + #define RAD_VS_ATTR_MS_MPPE_Send_Key 16 + #define RAD_VS_ATTR_MS_MPPE_Recv_Key 17 + +@@ -63,6 +84,16 @@ int attrvalidate(unsigned char *attrs, int length); + struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr); + int resizeattr(struct tlv *attr, uint8_t newlen); + ++/** ++ * convert the attribute value to its string representation form the dictionary ++ * (see raddict.h) ++ * ++ * @param attr the attribute to convert ++ * @return the string representation or NULL, if the attribute/value is not in the ++ * dictionary ++ */ ++const char* attrval2strdict(struct tlv *attr); ++ + #endif /*_RADMSG_H*/ + + /* Local Variables: */ +diff --git a/radsecproxy.c b/radsecproxy.c +index c755acb..75f5ef3 100644 +--- a/radsecproxy.c ++++ b/radsecproxy.c +@@ -121,13 +121,13 @@ 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, uint8_t server_p) { ++struct clsrvconf *find_conf(uint8_t type, struct sockaddr *addr, struct list *confs, struct list_node **cur, uint8_t server_p, struct hostportres **hp) { + struct list_node *entry; + struct clsrvconf *conf; + + for (entry = (cur && *cur ? list_next(*cur) : list_first(confs)); entry; entry = list_next(entry)) { + conf = (struct clsrvconf *)entry->data; +- if (conf->type == type && addressmatches(conf->hostports, addr, server_p)) { ++ if (conf->type == type && addressmatches(conf->hostports, addr, server_p, hp)) { + if (cur) + *cur = entry; + return conf; +@@ -136,12 +136,12 @@ struct clsrvconf *find_conf(uint8_t type, struct sockaddr *addr, struct list *co + return NULL; + } + +-struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur) { +- return find_conf(type, addr, clconfs, cur, 0); ++struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur, struct hostportres **hp) { ++ return find_conf(type, addr, clconfs, cur, 0, hp); + } + + struct clsrvconf *find_srvconf(uint8_t type, struct sockaddr *addr, struct list_node **cur) { +- return find_conf(type, addr, srvconfs, cur, 1); ++ return find_conf(type, addr, srvconfs, cur, 1, NULL); + } + + /* returns next config of given type, or NULL */ +@@ -1241,6 +1241,49 @@ void rmclientrq(struct request *rq, uint8_t id) { + } + } + ++static void log_accounting_resp(struct client *from, struct radmsg *msg, char *user) { ++ char tmp[INET6_ADDRSTRLEN]; ++ const char* status_type = attrval2strdict(radmsg_gettype(msg, RAD_Attr_Acct_Status_Type)); ++ char* nas_ip_address = tlv2ipv4addr(radmsg_gettype(msg, RAD_Attr_NAS_IP_Address)); ++ char* framed_ip_address = tlv2ipv4addr(radmsg_gettype(msg, RAD_Attr_Framed_IP_Address)); ++ ++ time_t event_timestamp_i = tlv2longint(radmsg_gettype(msg, RAD_Attr_Event_Timestamp)); ++ char event_timestamp[32]; /* timestamp should be at most 21 bytes, leave a few spare */ ++ ++ uint8_t *session_id = radattr2ascii(radmsg_gettype(msg, RAD_Attr_Acct_Session_Id)); ++ uint8_t *called_station_id = radattr2ascii(radmsg_gettype(msg, RAD_Attr_Called_Station_Id)); ++ uint8_t *calling_station_id = radattr2ascii(radmsg_gettype(msg, RAD_Attr_Calling_Station_Id)); ++ const char* terminate_cause = attrval2strdict(radmsg_gettype(msg, RAD_Attr_Acct_Terminate_Cause)); ++ ++ strftime(event_timestamp, sizeof(event_timestamp), "%FT%TZ", gmtime(&event_timestamp_i)); ++ ++ debug(DBG_NOTICE, "Accounting %s (id %d) at %s from client %s (%s): { SID=%s, User-Name=%s, Ced-S-Id=%s, Cing-S-Id=%s, NAS-IP=%s, Framed-IP=%s, Sess-Time=%u, In-Packets=%u, In-Octets=%u, Out-Packets=%u, Out-Octets=%u, Terminate-Cause=%s }", ++ status_type ? status_type : "UNKNOWN", ++ msg->id, ++ event_timestamp_i ? event_timestamp : "UNKNOWN", ++ from->conf->name, ++ addr2string(from->addr, tmp, sizeof(tmp)), ++ ++ session_id ? session_id : (uint8_t*)"", ++ user, ++ called_station_id ? called_station_id : (uint8_t*)"", ++ calling_station_id ? calling_station_id : (uint8_t*)"", ++ nas_ip_address ? nas_ip_address : "0.0.0.0", ++ framed_ip_address ? framed_ip_address : "0.0.0.0", ++ tlv2longint(radmsg_gettype(msg, RAD_Attr_Acct_Session_Time)), ++ tlv2longint(radmsg_gettype(msg, RAD_Attr_Acct_Input_Packets)), ++ tlv2longint(radmsg_gettype(msg, RAD_Attr_Acct_Input_Octets)), ++ tlv2longint(radmsg_gettype(msg, RAD_Attr_Acct_Output_Packets)), ++ tlv2longint(radmsg_gettype(msg, RAD_Attr_Acct_Output_Octets)), ++ terminate_cause ? terminate_cause : "" ++ ); ++ free(framed_ip_address); ++ free(nas_ip_address); ++ free(session_id); ++ free(called_station_id); ++ free(calling_station_id); ++} ++ + /* Called from server readers, handling incoming requests from + * clients. */ + /* returns 0 if validation/authentication fails, else 1 */ +@@ -1321,13 +1364,15 @@ int radsrv(struct request *rq) { + } + + if (!to) { +- if (realm->message && msg->code == RAD_Access_Request) { +- debug(DBG_INFO, "radsrv: sending %s (id %d) to %s (%s) for %s", radmsgtype2string(RAD_Access_Reject), msg->id, from->conf->name, addr2string(from->addr, tmp, sizeof(tmp)), userascii); +- respond(rq, RAD_Access_Reject, realm->message, 1, 1); +- } else if (realm->accresp && msg->code == RAD_Accounting_Request) { +- respond(rq, RAD_Accounting_Response, NULL, 1, 0); +- } +- goto exit; ++ if (realm->message && msg->code == RAD_Access_Request) { ++ debug(DBG_INFO, "radsrv: sending %s (id %d) to %s (%s) for %s", radmsgtype2string(RAD_Access_Reject), msg->id, from->conf->name, addr2string(from->addr, tmp, sizeof(tmp)), userascii); ++ respond(rq, RAD_Access_Reject, realm->message, 1, 1); ++ } else if (realm->accresp && msg->code == RAD_Accounting_Request) { ++ if (realm->acclog) ++ log_accounting_resp(from, msg, (char *)userascii); ++ respond(rq, RAD_Accounting_Response, NULL, 1, 0); ++ } ++ goto exit; + } + + if ((to->conf->loopprevention == 1 +@@ -1592,7 +1637,7 @@ void *clientwr(void *arg) { + if (server->dynamiclookuparg && !dynamicconfig(server)) { + dynconffail = 1; + server->state = RSP_SERVER_STATE_FAILING; +- debug(DBG_WARN, "%s: dynamicconfig(%s: %s) failed, sleeping %ds", ++ debug(DBG_WARN, "%s: dynamicconfig(%s: %s) failed, Not trying again for %ds", + __func__, server->conf->name, server->dynamiclookuparg, ZZZ); + goto errexitwait; + } +@@ -1600,7 +1645,7 @@ void *clientwr(void *arg) { + * 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); ++ debug(DBG_WARN, "%s: resolve failed, Not trying again for %ds", __func__, ZZZ); + server->state = RSP_SERVER_STATE_FAILING; + goto errexitwait; + } +@@ -1615,7 +1660,7 @@ void *clientwr(void *arg) { + if (!conf->pdef->connecter(server, server->dynamiclookuparg ? 5 : 0, "clientwr")) { + server->state = RSP_SERVER_STATE_FAILING; + if (server->dynamiclookuparg) { +- debug(DBG_WARN, "%s: connect failed, sleeping %ds", __func__, ZZZ); ++ debug(DBG_WARN, "%s: connect failed, giving up. Not trying again for %ds", __func__, ZZZ); + goto errexitwait; + } + goto errexit; +@@ -1927,7 +1972,7 @@ void freerealm(struct realm *realm) { + free(realm); + } + +-struct realm *addrealm(struct list *realmlist, char *value, char **servers, char **accservers, char *message, uint8_t accresp) { ++struct realm *addrealm(struct list *realmlist, char *value, char **servers, char **accservers, char *message, uint8_t accresp, uint8_t acclog) { + int n; + struct realm *realm; + char *s, *regex = NULL; +@@ -1991,6 +2036,7 @@ struct realm *addrealm(struct list *realmlist, char *value, char **servers, char + } + realm->message = message; + realm->accresp = accresp; ++ realm->acclog = acclog; + + if (regcomp(&realm->regex, regex ? regex : value + 1, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { + debug(DBG_ERR, "addrealm: failed to compile regular expression %s", regex ? regex : value + 1); +@@ -2111,7 +2157,7 @@ struct realm *adddynamicrealmserver(struct realm *realm, char *id) { + if (!realm->subrealms) + return NULL; + +- newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, stringcopy(realm->message, 0), realm->accresp); ++ newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, stringcopy(realm->message, 0), realm->accresp, realm->acclog); + if (!newrealm) { + list_destroy(realm->subrealms); + realm->subrealms = NULL; +@@ -2223,6 +2269,7 @@ void freeclsrvconf(struct clsrvconf *conf) { + freematchcertattr(conf); + free(conf->confrewritein); + free(conf->confrewriteout); ++ free(conf->sniservername); + if (conf->rewriteusername) { + if (conf->rewriteusername->regex) + regfree(conf->rewriteusername->regex); +@@ -2316,7 +2363,8 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) { + !mergeconfstring(&dst->confrewriteusername, &src->confrewriteusername) || + !mergeconfstring(&dst->dynamiclookupcommand, &src->dynamiclookupcommand) || + !mergeconfstring(&dst->fticks_viscountry, &src->fticks_viscountry) || +- !mergeconfstring(&dst->fticks_visinst, &src->fticks_visinst)) ++ !mergeconfstring(&dst->fticks_visinst, &src->fticks_visinst) || ++ !mergeconfstring(&dst->sniservername, &src->sniservername)) + return 0; + if (src->pdef) + dst->pdef = src->pdef; +@@ -2327,6 +2375,7 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) { + if (src->retrycount != 255) + dst->retrycount = src->retrycount; + dst->blockingstartup = src->blockingstartup; ++ dst->sni = src->sni; + return 1; + } + +@@ -2416,7 +2465,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + ? tlsgettls(conf->tls, NULL) + : tlsgettls("defaultClient", "default"); + if (!conf->tlsconf) +- debugx(1, DBG_ERR, "error in block %s, no tls context defined", block); ++ debugx(1, DBG_ERR, "error in block %s, tls context not defined", block); + if (matchcertattrs) { + for (i=0; matchcertattrs[i]; i++){ + if (!addmatchcertattr(conf, matchcertattrs[i])) { +@@ -2485,7 +2534,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + existing->tlsconf != conf->tlsconf && + hostportmatches(existing->hostports, conf->hostports, 0)) { + +- debugx(1, DBG_ERR, "error in block %s, overlapping clients must reference the same tls block", block); ++ debugx(1, DBG_ERR, "error in block %s, masked by overlapping (equal or less specific IP/prefix) client %s with different tls block", block, existing->name); + } + } + } +@@ -2573,8 +2622,12 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + if (resconf) { + conf->statusserver = resconf->statusserver; + conf->certnamecheck = resconf->certnamecheck; ++ conf->blockingstartup = resconf->blockingstartup; ++ conf->type = resconf->type; ++ conf->sni = resconf->sni; + } else { + conf->certnamecheck = 1; ++ conf->sni = options.sni; + } + + if (!getgenericconfig(cf, block, +@@ -2601,6 +2654,8 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + "DynamicLookupCommand", CONF_STR, &conf->dynamiclookupcommand, + "LoopPrevention", CONF_BLN, &conf->loopprevention, + "BlockingStartup", CONF_BLN, &conf->blockingstartup, ++ "SNI", CONF_BLN, &conf->sni, ++ "SNIservername", CONF_STR, &conf->sniservername, + NULL + )) { + debug(DBG_ERR, "configuration error"); +@@ -2621,16 +2676,20 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + } + + 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); +- goto errexit; ++ if (!resconf) { ++ debug(DBG_ERR, "error in block %s, option type missing", block); ++ goto errexit; ++ } ++ } else { ++ conf->type = protoname2int(conftype); ++ if (conf->type == 255) { ++ debug(DBG_ERR, "error in block %s, unknown transport %s", block, conftype); ++ goto errexit; ++ } ++ free(conftype); ++ conftype = NULL; ++ conf->pdef = protodefs[conf->type]; + } +- free(conftype); +- conftype = NULL; + + conf->hostaf = AF_UNSPEC; + if (config_hostaf("top level", options.ipv4only, options.ipv6only, &conf->hostaf)) +@@ -2638,8 +2697,6 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + if (config_hostaf(block, ipv4only, ipv6only, &conf->hostaf)) + goto errexit; + +- conf->pdef = protodefs[conf->type]; +- + if (!conf->confrewritein) + conf->confrewritein = rewriteinalias; + else +@@ -2683,8 +2740,12 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + conf->statusserver = RSP_STATSRV_AUTO; + else + debugx(1, DBG_ERR, "config error in blocck %s: invalid StatusServer value: %s", block, statusserver); ++ free(statusserver); + } + ++ if(conf->sniservername) ++ conf->sni = 1; ++ + if (resconf) { + if (!mergesrvconf(resconf, conf)) + goto errexit; +@@ -2769,7 +2830,7 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha + + int confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { + char **servers = NULL, **accservers = NULL, *msg = NULL; +- uint8_t accresp = 0; ++ uint8_t accresp = 0, acclog = 0; + + debug(DBG_DBG, "confrealm_cb called for %s", block); + +@@ -2778,11 +2839,12 @@ int confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char + "accountingServer", CONF_MSTR, &accservers, + "ReplyMessage", CONF_STR, &msg, + "AccountingResponse", CONF_BLN, &accresp, ++ "AccountingLog", CONF_BLN, &acclog, + NULL + )) + debugx(1, DBG_ERR, "configuration error"); + +- addrealm(realms, val, servers, accservers, msg, accresp); ++ addrealm(realms, val, servers, accservers, msg, accresp, acclog); + return 1; + } + +@@ -2871,6 +2933,7 @@ void getmainconfig(const char *configfile) { + "FTicksPrefix", CONF_STR, &options.fticksprefix, + "IPv4Only", CONF_BLN, &options.ipv4only, + "IPv6Only", CONF_BLN, &options.ipv6only, ++ "SNI", CONF_BLN, &options.sni, + NULL + )) + debugx(1, DBG_ERR, "configuration error"); +@@ -3031,6 +3094,7 @@ int createpidfile(const char *pidfile) { + int radsecproxy_main(int argc, char **argv) { + pthread_t sigth; + sigset_t sigset; ++ size_t stacksize; + struct list_node *entry; + uint8_t foreground = 0, pretend = 0, loglevel = 0; + char *configfile = NULL, *pidfile = NULL; +@@ -3042,8 +3106,13 @@ int radsecproxy_main(int argc, char **argv) { + + 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(PTHREAD_STACK_MIN) ++ stacksize = THREAD_STACK_SIZE > PTHREAD_STACK_MIN ? THREAD_STACK_SIZE : PTHREAD_STACK_MIN; ++#else ++ stacksize = THREAD_STACK_SIZE; ++#endif ++ if (pthread_attr_setstacksize(&pthread_attr, stacksize)) ++ debug(DBG_WARN, "pthread_attr_setstacksize failed! Using system default. Memory footprint might be increased!"); + #if defined(HAVE_MALLOPT) + if (mallopt(M_TRIM_THRESHOLD, 4 * 1024) != 1) + debugx(1, DBG_ERR, "mallopt failed"); +diff --git a/radsecproxy.conf-example b/radsecproxy.conf-example +index 1730e55..3b2dd64 100644 +--- a/radsecproxy.conf-example ++++ b/radsecproxy.conf-example +@@ -203,6 +203,8 @@ server radius.example.com { + # statusserver is optional, can be on or off. Off is default + tcpKeepalive on + # tcp and tls connections also support TCP keepalives. ++# Optionally specify the SNI for the outgoing connection ++# sni www.example.com + } + #server radius.example.com { + # type dtls +diff --git a/radsecproxy.conf.5.in b/radsecproxy.conf.5.in +index 87482c1..ebedd6c 100644 +--- a/radsecproxy.conf.5.in ++++ b/radsecproxy.conf.5.in +@@ -297,6 +297,15 @@ clients and servers. At most one of IPv4Only and IPv6Only can be enabled. + Note that this can be overridden in client and server blocks, see below. + .RE + ++.BR "SNI (" on | off ) ++.RS ++Server Name Indication (SNI) is an extension to the TLS protocol. It allows a ++client to indicate which hostname it is trying to connect to at the start of ++the TLS handshake. Enabling this will use the extension for all TLS and DTLS ++servers which specify a hostname (not IP address). This can be overridden in ++server blocks, see below. ++.RE ++ + .BI "Include " file + .RS + This is not a normal configuration option; it can be specified multiple times. +@@ -350,10 +359,11 @@ this might mask clients defined later, which then will never be matched. + + In the case of TLS/DTLS, the name of the client must match the FQDN or IP + address in the client certificate (CN or SubectAltName:DNS or SubjectAltName:IP +-respectively). Note that this is not required when the client name is an IP +-prefix. If overlapping clients are defined (see section above), they will be +-searched for matching \fBMatchCertificateAttribute\fR, but they must reference +-the same tls block. ++respectively) and any \fBMatchCertificateAttribute\fR to be positively identified. ++Note that no FQDN/IP is checked when using an IP prefix. ++If overlapping clients are defined (see section above), they will be searched for ++positive identification, but only among clients referencing the same tls block ++(selected by the first matching IP address or prefix). + + The allowed options in a client block are: + +@@ -620,6 +630,19 @@ Warning: when the dynamic lookup and connection process is slow, this wil block + respective realm for that time. + .RE + ++.BR "SNI (" on | off ) ++ ++.RS ++Override gobal SNI setting (see above). This is implicitly enabled if \fBSNIservername\fR ++is set. ++.RE ++ ++.BI "SNIservername " sni ++.RS ++Explicitly set the \fIsni\fR to request from the server, in case the server is specified ++by IP address or to override the hostname. Implicitly enables \fBSNI\fR for this server. ++.RE ++ + The meaning and syntax of the following options are exactly the same as for the client + block. The details are not repeated here. Please refer to the definitions in the \fBCLIENT BLOCK\fR section. + +@@ -679,6 +702,12 @@ Enable sending Accounting-Response instead of ignoring Accounting-Requests when + no \fBaccoutingServer\fR are configured. + .RE + ++.BR "AccountingLog (" on | off ) ++.RS ++When responding to Accounting-Requests (\fBAccountingResponse on\fR), log the ++accounting data. ++.RE ++ + .BI "ReplyMessage " message + .RS + Specify a message to be sent back to the client if a Access-Request is denied +@@ -852,7 +881,8 @@ for DTLS. + .BI "DhFile " file + .RS + DH parameter \fIfile\fR to use. See \fBopenssl-dhparam\fR(1) +- ++.br ++Note: starting with OpenSSL 3.0, use of custom DH parameters is discouraged. + + .SH "REWRITE BLOCK" + .nf +diff --git a/radsecproxy.h b/radsecproxy.h +index cf493b4..8844415 100644 +--- a/radsecproxy.h ++++ b/radsecproxy.h +@@ -11,6 +11,7 @@ + #include "radmsg.h" + #include "gconfig.h" + #include "rewrite.h" ++#include "hostport.h" + + #include + +@@ -28,15 +29,9 @@ + #define STATUS_SERVER_PERIOD 25 + #define IDLE_TIMEOUT 300 + +-/* We want PTHREAD_STACK_SIZE to be 32768, but some platforms +- * have a higher minimum value defined in PTHREAD_STACK_MIN. */ +-#define PTHREAD_STACK_SIZE 32768 +-#if defined(PTHREAD_STACK_MIN) +-#if PTHREAD_STACK_MIN > PTHREAD_STACK_SIZE +-#undef PTHREAD_STACK_SIZE +-#define PTHREAD_STACK_SIZE PTHREAD_STACK_MIN +-#endif +-#endif ++/* Target value for stack size. ++ * Some platforms might define higher minimums in PTHREAD_STACK_MIN. */ ++#define THREAD_STACK_SIZE 32768 + + /* For systems that only support RFC 2292 Socket API, but not RFC 3542 + * like Cygwin */ +@@ -103,6 +98,7 @@ struct options { + uint8_t *fticks_key; + uint8_t ipv4only; + uint8_t ipv6only; ++ uint8_t sni; + }; + + struct commonprotoopts { +@@ -176,6 +172,8 @@ struct clsrvconf { + struct server *servers; + char *fticks_viscountry; + char *fticks_visinst; ++ uint8_t sni; ++ char *sniservername; + }; + + #include "tlscommon.h" +@@ -216,6 +214,7 @@ struct realm { + char *name; + char *message; + uint8_t accresp; ++ uint8_t acclog; + regex_t regex; + uint32_t refcount; + pthread_mutex_t refmutex; +@@ -250,7 +249,7 @@ struct protodefs { + + #define RADLEN(x) ntohs(((uint16_t *)(x))[1]) + +-struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur); ++struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur, struct hostportres **hp); + struct clsrvconf *find_srvconf(uint8_t type, struct sockaddr *addr, struct list_node **cur); + struct clsrvconf *find_clconf_type(uint8_t type, struct list_node **cur); + struct client *addclient(struct clsrvconf *conf, uint8_t lock); +diff --git a/tcp.c b/tcp.c +index ffb2f4c..e148ed0 100644 +--- a/tcp.c ++++ b/tcp.c +@@ -22,6 +22,7 @@ + #include + #include "radsecproxy.h" + #include "hostport.h" ++#include "list.h" + + #ifdef RADPROT_TCP + #include "debug.h" +@@ -84,6 +85,8 @@ int tcpconnect(struct server *server, int timeout, char *text) { + int firsttry = 1; + time_t wait; + struct addrinfo *source = NULL; ++ struct list_node *entry; ++ struct hostportres *hp; + + debug(DBG_DBG, "tcpconnect: called from %s", text); + pthread_mutex_lock(&server->lock); +@@ -112,16 +115,25 @@ int tcpconnect(struct server *server, int timeout, char *text) { + if (source) freeaddrinfo(source); + return 0; + } +- debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); ++ if (wait) debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); + sleep(wait); + firsttry = 0; + +- + pthread_mutex_lock(&server->lock); + +- debug(DBG_INFO, "tcpconnect: connecting to %s", server->conf->name); +- if ((server->sock = connecttcphostlist(server->conf->hostports, source ? source : srcres)) < 0) ++ for (entry = list_first(server->conf->hostports); entry; entry = list_next(entry)) { ++ hp = (struct hostportres *)entry->data; ++ debug(DBG_INFO, "tcpconnect: trying to open TCP connection to server %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ if ((server->sock = connecttcp(hp->addrinfo, source ? source : srcres, list_count(server->conf->hostports) > 1 ? 5 : 30)) >= 0) { ++ debug(DBG_WARN, "tcpconnect: TCP connection to server %s (%s port %s) up", hp->host, hp->port); ++ break; ++ } ++ } ++ if (server->sock < 0) { ++ debug(DBG_ERR, "tcpconnect: TCP connection to server %s failed", server->conf->name); + continue; ++ } ++ + if (server->conf->keepalive) + enable_keepalive(server->sock); + break; +@@ -339,7 +351,7 @@ void *tcpservernew(void *arg) { + } + debug(DBG_WARN, "tcpservernew: incoming TCP connection from %s", addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); + +- conf = find_clconf(handle, (struct sockaddr *)&from, NULL); ++ conf = find_clconf(handle, (struct sockaddr *)&from, NULL, NULL); + if (conf) { + client = addclient(conf, 1); + if (client) { +diff --git a/tests/t_verify_cert.c b/tests/t_verify_cert.c +index ca0d47e..b52a990 100644 +--- a/tests/t_verify_cert.c ++++ b/tests/t_verify_cert.c +@@ -234,7 +234,7 @@ vY/uPjA=\n\ + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + +- ok(1, verifyconfcert(certsimple, &conf), "check disabled"); ++ ok(1, verifyconfcert(certsimple, &conf, &hp), "check disabled"); + + while(list_shift(conf.hostports)); + } +@@ -249,7 +249,7 @@ vY/uPjA=\n\ + hp.prefixlen = 0; + list_push(conf.hostports, &hp); + +- ok(1,verifyconfcert(certsimple, &conf),"cidr prefix"); ++ ok(1,verifyconfcert(certsimple, &conf, &hp),"cidr prefix"); + + while(list_shift(conf.hostports)); + } +@@ -264,11 +264,11 @@ vY/uPjA=\n\ + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + +- ok(1,verifyconfcert(certsimple, &conf), "simple cert cn"); +- ok(0,verifyconfcert(certsimpleother, &conf), "negative simple cert cn"); ++ ok(1,verifyconfcert(certsimple, &conf, &hp), "simple cert cn"); ++ ok(0,verifyconfcert(certsimpleother, &conf, &hp), "negative simple cert cn"); + + /* as per RFC 6125 6.4.4: CN MUST NOT be matched if SAN is present */ +- ok(0,verifyconfcert(certsandns, &conf), "simple cert cn vs san dns, RFC6125"); ++ ok(0,verifyconfcert(certsandns, &conf, &hp), "simple cert cn vs san dns, RFC6125"); + + while(list_shift(conf.hostports)); + } +@@ -284,11 +284,11 @@ vY/uPjA=\n\ + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + +- ok(1,verifyconfcert(certsanip, &conf),"san ip"); +- ok(0,verifyconfcert(certsanipother, &conf),"wrong san ip"); +- ok(0,verifyconfcert(certsimple, &conf), "negative san ip"); +- ok(1,verifyconfcert(certsanipindns, &conf),"san ip in dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"san ip in complex cert"); ++ ok(1,verifyconfcert(certsanip, &conf, &hp),"san ip"); ++ ok(0,verifyconfcert(certsanipother, &conf, &hp),"wrong san ip"); ++ ok(0,verifyconfcert(certsimple, &conf, &hp), "negative san ip"); ++ ok(1,verifyconfcert(certsanipindns, &conf, &hp),"san ip in dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, &hp),"san ip in complex cert"); + + freeaddrinfo(hp.addrinfo); + while(list_shift(conf.hostports)); +@@ -306,11 +306,11 @@ vY/uPjA=\n\ + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + +- ok(1,verifyconfcert(certsanipv6, &conf),"san ipv6"); +- ok(0,verifyconfcert(certsanipother, &conf),"wrong san ipv6"); +- ok(0,verifyconfcert(certsimple, &conf),"negative san ipv6"); +- ok(1,verifyconfcert(certsanipv6indns, &conf),"san ipv6 in dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"san ipv6 in complex cert"); ++ ok(1,verifyconfcert(certsanipv6, &conf, &hp),"san ipv6"); ++ ok(0,verifyconfcert(certsanipother, &conf, &hp),"wrong san ipv6"); ++ ok(0,verifyconfcert(certsimple, &conf, &hp),"negative san ipv6"); ++ ok(1,verifyconfcert(certsanipv6indns, &conf, &hp),"san ipv6 in dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, &hp),"san ipv6 in complex cert"); + + freeaddrinfo(hp.addrinfo); + while(list_shift(conf.hostports)); +@@ -326,10 +326,10 @@ vY/uPjA=\n\ + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + +- ok(1,verifyconfcert(certsandns, &conf),"san dns"); +- ok(0,verifyconfcert(certsandnsother, &conf),"negative san dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"san dns in complex cert"); +- ok(0,verifyconfcert(certsimple, &conf),"missing san dns"); ++ ok(1,verifyconfcert(certsandns, &conf, &hp),"san dns"); ++ ok(0,verifyconfcert(certsandnsother, &conf, &hp),"negative san dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, &hp),"san dns in complex cert"); ++ ok(0,verifyconfcert(certsimple, &conf, &hp),"missing san dns"); + + while(list_shift(conf.hostports)); + } +@@ -347,8 +347,8 @@ vY/uPjA=\n\ + hp2.prefixlen = 255; + list_push(conf.hostports, &hp2); + +- ok(1,verifyconfcert(certsimple, &conf),"multi hostport cn"); +- ok(0,verifyconfcert(certsimpleother, &conf),"negative multi hostport cn"); ++ ok(1,verifyconfcert(certsimple, &conf, NULL),"multi hostport cn"); ++ ok(0,verifyconfcert(certsimpleother, &conf, NULL),"negative multi hostport cn"); + + while(list_shift(conf.hostports)); + } +@@ -366,9 +366,14 @@ vY/uPjA=\n\ + hp2.prefixlen = 255; + list_push(conf.hostports, &hp2); + +- ok(1,verifyconfcert(certsandns, &conf),"multi hostport san dns"); +- ok(0,verifyconfcert(certsandnsother, &conf),"negative multi hostport san dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"multi hostport san dns in complex cert"); ++ ok(1,verifyconfcert(certsandns, &conf, NULL),"multi hostport san dns"); ++ ok(0,verifyconfcert(certsandnsother, &conf, NULL),"negative multi hostport san dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, NULL),"multi hostport san dns in complex cert"); ++ ++ ok(0,verifyconfcert(certsandns, &conf, &hp1),"multi hostport explicit wrong cert"); ++ ok(1,verifyconfcert(certsandns, &conf, &hp2),"multi hostport explicit matching cert"); ++ ok(0,verifyconfcert(certcomplex, &conf, &hp1),"multi hostport explicit wrong complex cert"); ++ ok(1,verifyconfcert(certcomplex, &conf, &hp2),"multi hostport explicit matching complex cert"); + + while(list_shift(conf.hostports)); + } +@@ -380,9 +385,9 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "CN:/t..t/"),"explicit cn regex config"); + +- ok(1,verifyconfcert(certsimple, &conf),"explicit cn regex"); +- ok(0,verifyconfcert(certsimpleother, &conf),"negative explicit cn regex"); +- ok(1,verifyconfcert(certsandns, &conf), "explicit cn regex with SAN DNS"); ++ ok(1,verifyconfcert(certsimple, &conf, NULL),"explicit cn regex"); ++ ok(0,verifyconfcert(certsimpleother, &conf, NULL),"negative explicit cn regex"); ++ ok(1,verifyconfcert(certsandns, &conf, NULL), "explicit cn regex with SAN DNS"); + + freematchcertattr(&conf); + } +@@ -394,10 +399,10 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:IP:192.0.2.1"),"explicit san ip config"); + +- ok(1,verifyconfcert(certsanip, &conf),"explicit san ip"); +- ok(0,verifyconfcert(certsanipother, &conf),"wrong explicit san ip"); +- ok(0,verifyconfcert(certsimple, &conf), "missing explicit san ip"); +- ok(1,verifyconfcert(certcomplex,&conf),"explicit san ip in complex cert"); ++ ok(1,verifyconfcert(certsanip, &conf, NULL),"explicit san ip"); ++ ok(0,verifyconfcert(certsanipother, &conf, NULL),"wrong explicit san ip"); ++ ok(0,verifyconfcert(certsimple, &conf, NULL), "missing explicit san ip"); ++ ok(1,verifyconfcert(certcomplex,&conf, NULL),"explicit san ip in complex cert"); + + freematchcertattr(&conf); + } +@@ -409,10 +414,10 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:IP:2001:db8::1"),"explicit san ipv6 config"); + +- ok(1,verifyconfcert(certsanipv6, &conf),"explicit san ipv6"); +- ok(0,verifyconfcert(certsanipother, &conf),"wrong explicit san ipv6"); +- ok(0,verifyconfcert(certsimple, &conf),"missing explicitsan ipv6"); +- ok(1,verifyconfcert(certcomplex,&conf),"explicit san ipv6 in complex cert"); ++ ok(1,verifyconfcert(certsanipv6, &conf, NULL),"explicit san ipv6"); ++ ok(0,verifyconfcert(certsanipother, &conf, NULL),"wrong explicit san ipv6"); ++ ok(0,verifyconfcert(certsimple, &conf, NULL),"missing explicitsan ipv6"); ++ ok(1,verifyconfcert(certcomplex,&conf, NULL),"explicit san ipv6 in complex cert"); + + freematchcertattr(&conf); + } +@@ -424,10 +429,10 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:DNS:/t..t\\.local/"),"explicit san dns regex config"); + +- ok(1,verifyconfcert(certsandns, &conf),"explicit san dns"); +- ok(0,verifyconfcert(certsandnsother, &conf),"negative explicit san dns"); +- ok(0,verifyconfcert(certsimple,&conf),"missing explicit san dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"explicit san dns in complex cert"); ++ ok(1,verifyconfcert(certsandns, &conf, NULL),"explicit san dns"); ++ ok(0,verifyconfcert(certsandnsother, &conf, NULL),"negative explicit san dns"); ++ ok(0,verifyconfcert(certsimple,&conf, NULL),"missing explicit san dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, NULL),"explicit san dns in complex cert"); + + freematchcertattr(&conf); + } +@@ -439,9 +444,9 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:URI:/https:\\/\\/test.local\\/profile#me/"),"explicit cn regex config"); + +- ok(1,verifyconfcert(certsanuri, &conf),"explicit san uri regex"); +- ok(0,verifyconfcert(certsanuriother, &conf),"negative explicit san uri"); +- ok(0,verifyconfcert(certsimple, &conf), "missing explicit san uri"); ++ ok(1,verifyconfcert(certsanuri, &conf, NULL),"explicit san uri regex"); ++ ok(0,verifyconfcert(certsanuriother, &conf, NULL),"negative explicit san uri"); ++ ok(0,verifyconfcert(certsimple, &conf, NULL), "missing explicit san uri"); + + freematchcertattr(&conf); + } +@@ -453,9 +458,9 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:rID:1.2.3.4"),"explicit san rid config"); + +- ok(1,verifyconfcert(certsanrid, &conf),"explicit san rid"); +- ok(0,verifyconfcert(certsanridother, &conf),"negative explicit san rid"); +- ok(0,verifyconfcert(certsimple, &conf), "missing explicit san rid"); ++ ok(1,verifyconfcert(certsanrid, &conf, NULL),"explicit san rid"); ++ ok(0,verifyconfcert(certsanridother, &conf, NULL),"negative explicit san rid"); ++ ok(0,verifyconfcert(certsimple, &conf, NULL), "missing explicit san rid"); + + freematchcertattr(&conf); + } +@@ -467,9 +472,9 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "SubjectAltName:otherName:1.3.6.1.5.5.7.8.8:/test.local/"),"explicit san otherName config"); + +- ok(1,verifyconfcert(certsanothername, &conf),"explicit san otherName"); +- ok(0,verifyconfcert(certsanothernameother, &conf),"negative explicit san otherName"); +- ok(0,verifyconfcert(certsimple, &conf), "missing explicit san otherName"); ++ ok(1,verifyconfcert(certsanothername, &conf, NULL),"explicit san otherName"); ++ ok(0,verifyconfcert(certsanothernameother, &conf, NULL),"negative explicit san otherName"); ++ ok(0,verifyconfcert(certsimple, &conf, NULL), "missing explicit san otherName"); + + freematchcertattr(&conf); + } +@@ -480,7 +485,7 @@ vY/uPjA=\n\ + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "CN:/t..t"),"test regex config syntax"); +- ok(1,verifyconfcert(certsimple, &conf),"test regex config syntax execution"); ++ ok(1,verifyconfcert(certsimple, &conf, NULL),"test regex config syntax execution"); + + freematchcertattr(&conf); + } +@@ -518,10 +523,10 @@ vY/uPjA=\n\ + + ok(1,addmatchcertattr(&conf, "CN:/t..t"),"combined config"); + +- ok(1,verifyconfcert(certsandns, &conf),"combined san dns"); +- ok(0,verifyconfcert(certsandnsother, &conf),"negative combined san dns"); +- ok(1,verifyconfcert(certcomplex,&conf),"combined san dns in complex cert"); +- ok(0,verifyconfcert(certsimple, &conf),"combined missing san dns"); ++ ok(1,verifyconfcert(certsandns, &conf, &hp),"combined san dns"); ++ ok(0,verifyconfcert(certsandnsother, &conf, &hp),"negative combined san dns"); ++ ok(1,verifyconfcert(certcomplex,&conf, &hp),"combined san dns in complex cert"); ++ ok(0,verifyconfcert(certsimple, &conf, &hp),"combined missing san dns"); + + while(list_shift(conf.hostports)); + freematchcertattr(&conf); +@@ -540,12 +545,12 @@ vY/uPjA=\n\ + ok(1,addmatchcertattr(&conf, "SubjectAltName:DNS:/test\\.local/"),"multiple check 1"); + ok(1,addmatchcertattr(&conf, "SubjectAltName:rID:1.2.3.4"),"multiple check 2"); + +- ok(0,verifyconfcert(certsandns, &conf),"multiple missing rID"); +- ok(0,verifyconfcert(certsanrid, &conf), "multiple missing DNS"); +- ok(1,verifyconfcert(certmulti, &conf),"multiple SANs"); +- ok(0,verifyconfcert(certmultiother, &conf),"multiple negative match"); +- ok(0,verifyconfcert(certcomplex, &conf),"multiple missing rID in complex cert"); +- ok(0,verifyconfcert(certsimple, &conf),"multiple missing everything"); ++ ok(0,verifyconfcert(certsandns, &conf, &hp),"multiple missing rID"); ++ ok(0,verifyconfcert(certsanrid, &conf, &hp), "multiple missing DNS"); ++ ok(1,verifyconfcert(certmulti, &conf, &hp),"multiple SANs"); ++ ok(0,verifyconfcert(certmultiother, &conf, &hp),"multiple negative match"); ++ ok(0,verifyconfcert(certcomplex, &conf, &hp),"multiple missing rID in complex cert"); ++ ok(0,verifyconfcert(certsimple, &conf, &hp),"multiple missing everything"); + + while(list_shift(conf.hostports)); + freematchcertattr(&conf); +diff --git a/tls.c b/tls.c +index 87bbe2c..049f3a9 100644 +--- a/tls.c ++++ b/tls.c +@@ -23,11 +23,11 @@ + #include + #include "radsecproxy.h" + #include "hostport.h" +- +-#ifdef RADPROT_TLS + #include "debug.h" + #include "util.h" + ++#ifdef RADPROT_TLS ++ + static void setprotoopts(struct commonprotoopts *opts); + static char **getlistenerargs(); + void *tlslistener(void *arg); +@@ -82,6 +82,17 @@ void tlssetsrcres() { + AF_UNSPEC, NULL, protodefs.socktype); + } + ++static void cleanup_connection(struct server *server) { ++ if (server->ssl) ++ SSL_shutdown(server->ssl); ++ if (server->sock >= 0) ++ close(server->sock); ++ server->sock = -1; ++ if (server->ssl) ++ SSL_free(server->ssl); ++ server->ssl = NULL; ++} ++ + int tlsconnect(struct server *server, int timeout, char *text) { + struct timeval now, start; + time_t wait; +@@ -92,6 +103,8 @@ int tlsconnect(struct server *server, int timeout, char *text) { + int origflags; + struct addrinfo *source = NULL; + char *subj; ++ struct list_node *entry; ++ struct hostportres *hp; + + debug(DBG_DBG, "tlsconnect: called from %s", text); + pthread_mutex_lock(&server->lock); +@@ -108,15 +121,7 @@ int tlsconnect(struct server *server, int timeout, char *text) { + gettimeofday(&start, NULL); + + for (;;) { +- /* ensure previous connection is properly closed */ +- if (server->ssl) +- SSL_shutdown(server->ssl); +- if (server->sock >= 0) +- close(server->sock); +- if (server->ssl) +- SSL_free(server->ssl); +- server->ssl = NULL; +- ++ cleanup_connection(server); + wait = connect_wait(start, server->connecttime, firsttry); + gettimeofday(&now, NULL); + if (timeout && (now.tv_sec - start.tv_sec) + wait > timeout) { +@@ -124,7 +129,7 @@ int tlsconnect(struct server *server, int timeout, char *text) { + if (source) freeaddrinfo(source); + return 0; + } +- debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); ++ if (wait) debug(DBG_INFO, "Next connection attempt to %s in %lds", server->conf->name, wait); + sleep(wait); + firsttry = 0; + +@@ -135,46 +140,76 @@ int tlsconnect(struct server *server, int timeout, char *text) { + return 0; + } + +- debug(DBG_INFO, "tlsconnect: connecting to %s", server->conf->name); +- if ((server->sock = connecttcphostlist(server->conf->hostports, source ? source : srcres)) < 0) +- continue; +- if (server->conf->keepalive) +- enable_keepalive(server->sock); ++ for (entry = list_first(server->conf->hostports); entry; entry = list_next(entry)) { ++ hp = (struct hostportres *)entry->data; ++ debug(DBG_INFO, "tlsconnect: trying to open TLS connection to server %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ if ((server->sock = connecttcp(hp->addrinfo, source ? source : srcres, list_count(server->conf->hostports) > 1 ? 5 : 30)) < 0 ) { ++ debug(DBG_ERR, "tlsconnect: TLS connection to %s (%s port %s) failed: TCP connect failed", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } ++ ++ if (server->conf->keepalive) ++ enable_keepalive(server->sock); ++ ++ pthread_mutex_lock(&server->conf->tlsconf->lock); ++ if (!(ctx = tlsgetctx(handle, server->conf->tlsconf))) { ++ pthread_mutex_unlock(&server->conf->tlsconf->lock); ++ debug(DBG_ERR, "tlsconnect: failed to get TLS context for server %s", server->conf->name); ++ goto concleanup; ++ } + +- pthread_mutex_lock(&server->conf->tlsconf->lock); +- if (!(ctx = tlsgetctx(handle, server->conf->tlsconf))){ ++ server->ssl = SSL_new(ctx); + pthread_mutex_unlock(&server->conf->tlsconf->lock); +- continue; +- } ++ if (!server->ssl) { ++ debug(DBG_ERR, "tlsconnect: failed to create SSL conneciton for server %s", server->conf->name); ++ goto concleanup; ++ } + +- server->ssl = SSL_new(ctx); +- pthread_mutex_unlock(&server->conf->tlsconf->lock); +- if (!server->ssl) +- continue; ++ if (server->conf->sni) { ++ struct in6_addr tmp; ++ char *servername = server->conf->sniservername ? server->conf->sniservername : ++ (inet_pton(AF_INET, hp->host, &tmp) || inet_pton(AF_INET6, hp->host, &tmp)) ? NULL : hp->host; ++ if (servername && !tlssetsni(server->ssl, servername)) { ++ debug(DBG_ERR, "tlsconnect: set SNI %s failed", servername); ++ goto concleanup; ++ } ++ } ++ ++ SSL_set_fd(server->ssl, server->sock); ++ if (sslconnecttimeout(server->ssl, 5) <= 0) { ++ while ((error = ERR_get_error())) ++ debug(DBG_ERR, "tlsconnect: SSL connect to %s failed: %s", server->conf->name, ERR_error_string(error, NULL)); ++ debug(DBG_ERR, "tlsconnect: SSL connect to %s (%s port %s) failed", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } + +- SSL_set_fd(server->ssl, server->sock); +- if (sslconnecttimeout(server->ssl, 5) <= 0) { +- while ((error = ERR_get_error())) +- debug(DBG_ERR, "tlsconnect: SSL connect to %s failed: %s", server->conf->name, ERR_error_string(error, NULL)); +- debug(DBG_ERR, "tlsconnect: SSL connect to %s failed", server->conf->name); +- continue; +- } ++ cert = verifytlscert(server->ssl); ++ if (!cert) { ++ debug(DBG_ERR, "tlsconnect: certificate verification failed for %s (%s port %s)", server->conf->name, hp->host, hp->port); ++ goto concleanup; ++ } + +- cert = verifytlscert(server->ssl); +- if (!cert) +- continue; +- if (verifyconfcert(cert, server->conf)) { +- subj = getcertsubject(cert); +- if(subj) { +- debug(DBG_WARN, "tlsconnect: TLS connection to %s, subject %s up", server->conf->name, subj); +- free(subj); ++ if (verifyconfcert(cert, server->conf, hp)) { ++ subj = getcertsubject(cert); ++ if(subj) { ++ debug(DBG_WARN, "tlsconnect: TLS connection to %s (%s port %s), subject %s, %s with cipher %s up", ++ server->conf->name, hp->host, hp->port, subj, ++ SSL_get_version(server->ssl), SSL_CIPHER_get_name(SSL_get_current_cipher(server->ssl))); ++ free(subj); ++ } ++ X509_free(cert); ++ break; ++ } else { ++ debug(DBG_ERR, "tlsconnect: certificate verification failed for %s (%s port %s)", server->conf->name, hp->host, hp->port); + } + X509_free(cert); +- break; ++ ++concleanup: ++ /* ensure previous connection is properly closed */ ++ cleanup_connection(server); + } +- X509_free(cert); ++ if (server->ssl) break; + } +- debug(DBG_WARN, "tlsconnect: TLS connection to %s up", server->conf->name); + + origflags = fcntl(server->sock, F_GETFL, 0); + if (origflags == -1) { +@@ -251,6 +286,7 @@ int sslreadtimeout(SSL *ssl, unsigned char *buf, int num, int timeout, pthread_m + continue; + case SSL_ERROR_ZERO_RETURN: + debug(DBG_DBG, "sslreadtimeout: got ssl shutdown"); ++ /* fallthrough */ + default: + while ((error = ERR_get_error())) + debug(DBG_ERR, "sslreadtimeout: SSL: %s", ERR_error_string(error, NULL)); +@@ -505,6 +541,7 @@ void *tlsservernew(void *arg) { + struct client *client; + struct tls *accepted_tls = NULL; + char tmp[INET6_ADDRSTRLEN], *subj; ++ struct hostportres *hp; + + s = *(int *)arg; + free(arg); +@@ -514,7 +551,7 @@ void *tlsservernew(void *arg) { + } + debug(DBG_WARN, "tlsservernew: incoming TLS connection from %s", addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); + +- conf = find_clconf(handle, (struct sockaddr *)&from, &cur); ++ conf = find_clconf(handle, (struct sockaddr *)&from, &cur, &hp); + if (conf) { + pthread_mutex_lock(&conf->tlsconf->lock); + ctx = tlsgetctx(handle, conf->tlsconf); +@@ -549,11 +586,12 @@ void *tlsservernew(void *arg) { + } + + while (conf) { +- if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf)) { ++ if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf, NULL)) { + subj = getcertsubject(cert); + if(subj) { +- debug(DBG_WARN, "tlsservernew: TLS connection from %s, client %s, subject %s up", +- addr2string((struct sockaddr *)&from,tmp, sizeof(tmp)), conf->name, subj); ++ debug(DBG_WARN, "tlsservernew: TLS connection from %s, client %s, subject %s, %s with cipher %s up", ++ addr2string((struct sockaddr *)&from,tmp, sizeof(tmp)), conf->name, subj, ++ SSL_get_version(ssl), SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); + free(subj); + } + X509_free(cert); +@@ -569,9 +607,10 @@ void *tlsservernew(void *arg) { + debug(DBG_WARN, "tlsservernew: failed to create new client instance"); + goto exit; + } +- conf = find_clconf(handle, (struct sockaddr *)&from, &cur); ++ conf = find_clconf(handle, (struct sockaddr *)&from, &cur, &hp); + } +- debug(DBG_WARN, "tlsservernew: ignoring request, no matching TLS client"); ++ debug(DBG_WARN, "tlsservernew: ignoring request, no matching TLS client for %s", ++ addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); + if (cert) + X509_free(cert); + +diff --git a/tlscommon.c b/tlscommon.c +index 0bd7da4..2dc0f48 100644 +--- a/tlscommon.c ++++ b/tlscommon.c +@@ -492,12 +492,26 @@ static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { + #endif + + if (conf->dhparam) { ++#if OPENSSL_VERSION_NUMBER >= 0x30000000 ++ if (!SSL_CTX_set0_tmp_dh_pkey(ctx, conf->dhparam)) { ++#else + if (!SSL_CTX_set_tmp_dh(ctx, conf->dhparam)) { ++#endif + while ((error = ERR_get_error())) + debug(DBG_WARN, "tlscreatectx: SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_WARN, "tlscreatectx: Failed to set dh params. Can continue, but some ciphers might not be available."); + } + } ++#if OPENSSL_VERSION_NUMBER >= 0x10100000 ++ else { ++ if (!SSL_CTX_set_dh_auto(ctx, 1)) { ++ while ((error = ERR_get_error())) ++ debug(DBG_WARN, "tlscreatectx: SSL: %s", ERR_error_string(error, NULL)); ++ debug(DBG_WARN, "tlscreatectx: Failed to set automatic dh params. Can continue, but some ciphers might not be available."); ++ } ++ } ++#endif ++ + debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name); + return ctx; + } +@@ -506,7 +520,7 @@ struct tls *tlsgettls(char *alt1, char *alt2) { + struct tls *t; + + t = hash_read(tlsconfs, alt1, strlen(alt1)); +- if (!t) ++ if (!t && alt2) + t = hash_read(tlsconfs, alt2, strlen(alt2)); + return t; + } +@@ -711,64 +725,67 @@ static int matchsubjaltname(X509 *cert, struct certattrmatch* match) { + } + + if (r<1) +- debug(DBG_WARN, "matchsubjaltname: no matching Subject Alt Name found! (%s)", fail); ++ debug(DBG_DBG, "matchsubjaltname: no matching Subject Alt Name found! (%s)", fail); + free(fail); + + GENERAL_NAMES_free(alt); + return r; + } + +-int certnamecheck(X509 *cert, struct list *hostports) { +- struct list_node *entry; +- struct hostportres *hp; ++int certnamecheck(X509 *cert, struct hostportres *hp) { + int r = 0; + struct certattrmatch match; + + memset(&match, 0, sizeof(struct certattrmatch)); + +- for (entry = list_first(hostports); entry; entry = list_next(entry)) { +- r = 0; +- hp = (struct hostportres *)entry->data; +- if (hp->prefixlen != 255) { +- /* we disable the check for prefixes */ ++ r = 0; ++ if (hp->prefixlen != 255) { ++ /* we disable the check for prefixes */ ++ return 1; ++ } ++ if (inet_pton(AF_INET, hp->host, &match.ipaddr)) ++ match.af = AF_INET; ++ else if (inet_pton(AF_INET6, hp->host, &match.ipaddr)) ++ match.af = AF_INET6; ++ else ++ match.af = 0; ++ match.exact = hp->host; ++ ++ if (match.af) { ++ match.matchfn = &certattr_matchip; ++ match.type = GEN_IPADD; ++ r = matchsubjaltname(cert, &match); ++ } ++ if (!r) { ++ match.matchfn = &certattr_matchregex; ++ match.type = GEN_DNS; ++ r = matchsubjaltname(cert, &match); ++ } ++ if (r) { ++ if (r > 0) { ++ debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); + return 1; + } +- if (inet_pton(AF_INET, hp->host, &match.ipaddr)) +- match.af = AF_INET; +- else if (inet_pton(AF_INET6, hp->host, &match.ipaddr)) +- match.af = AF_INET6; +- else +- match.af = 0; +- match.exact = hp->host; +- +- if (match.af) { +- match.matchfn = &certattr_matchip; +- match.type = GEN_IPADD; +- r = matchsubjaltname(cert, &match); +- } +- if (!r) { +- match.matchfn = &certattr_matchregex; +- match.type = GEN_DNS; +- r = matchsubjaltname(cert, &match); +- } +- if (r) { +- if (r > 0) { +- debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); +- return 1; +- } +- debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); +- } else { +- if (certattr_matchcn(cert, &match)) { +- debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host); +- return 1; +- } +- debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host); ++ debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); ++ } else { /* as per RFC 6125 6.4.4: CN MUST NOT be matched if SAN is present */ ++ if (certattr_matchcn(cert, &match)) { ++ debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host); ++ return 1; + } ++ debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host); + } + return 0; + } + +-int verifyconfcert(X509 *cert, struct clsrvconf *conf) { ++int certnamecheckany(X509 *cert, struct list *hostports) { ++ struct list_node *entry; ++ for (entry = list_first(hostports); entry; entry = list_next(entry)) { ++ if (certnamecheck(cert, (struct hostportres *)entry->data)) return 1; ++ } ++ return 0; ++} ++ ++int verifyconfcert(X509 *cert, struct clsrvconf *conf, struct hostportres *hpconnected) { + char *subject; + int ok = 1; + struct list_node *entry; +@@ -777,9 +794,16 @@ int verifyconfcert(X509 *cert, struct clsrvconf *conf) { + debug(DBG_DBG, "verifyconfcert: verify certificate for host %s, subject %s", conf->name, subject); + if (conf->certnamecheck) { + debug(DBG_DBG, "verifyconfcert: verify hostname"); +- if (!certnamecheck(cert, conf->hostports)) { +- debug(DBG_DBG, "verifyconfcert: certificate name check failed for host %s", conf->name); +- ok = 0; ++ if (hpconnected) { ++ if (!certnamecheck(cert, hpconnected)) { ++ debug(DBG_WARN, "verifyconfcert: certificate name check failed for host %s (%s)", conf->name, hpconnected->host); ++ ok = 0; ++ } ++ } else { ++ if (!certnamecheckany(cert, conf->hostports)) { ++ debug(DBG_DBG, "verifyconfcert: no matching CN or SAN found for host %s", conf->name); ++ ok = 0; ++ } + } + } + +@@ -913,11 +937,27 @@ int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *v + dtlsversion = NULL; + } + #else ++ if (tlsversion || dtlsversion) { + debug(DBG_ERR, "error in block %s, setting tls/dtls version requires openssl 1.1.0 or later", val); + goto errexit; ++ } + #endif + + if (dhfile) { ++#if OPENSSL_VERSION_NUMBER >= 0x30000000 ++ BIO *bio = BIO_new_file(dhfile, "r"); ++ if (bio) { ++ conf->dhparam = EVP_PKEY_new(); ++ if (!PEM_read_bio_Parameters(bio, &conf->dhparam)) { ++ BIO_free(bio); ++ while ((error = ERR_get_error())) ++ debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); ++ debug(DBG_ERR, "error in block %s: Failed to load DhFile %s.", val, dhfile); ++ goto errexit; ++ } ++ BIO_free(bio); ++ } ++#else + FILE *dhfp = fopen(dhfile, "r"); + if (dhfp) { + conf->dhparam = PEM_read_DHparams(dhfp, NULL, NULL, NULL); +@@ -934,6 +974,7 @@ int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *v + } + free(dhfile); + dhfile = NULL; ++#endif + } + + conf->name = stringcopy(val, 0); +@@ -964,7 +1005,11 @@ errexit: + free(tlsversion); + free(dtlsversion); + free(dhfile); ++#if OPENSSL_VERSION_NUMBER >= 0x30000000 ++ EVP_PKEY_free(conf->dhparam); ++#else + DH_free(conf->dhparam); ++#endif + free(conf); + return 0; + } +@@ -1091,6 +1136,10 @@ void freematchcertattr(struct clsrvconf *conf) { + } + } + ++int tlssetsni(SSL *ssl, char *sni) { ++ return SSL_set_tlsext_host_name(ssl, sni); ++} ++ + int sslaccepttimeout (SSL *ssl, int timeout) { + int socket, origflags, ndesc, r = -1, sockerr = 0; + socklen_t errlen = sizeof(sockerr); +diff --git a/tlscommon.h b/tlscommon.h +index 6be9079..1006626 100644 +--- a/tlscommon.h ++++ b/tlscommon.h +@@ -3,6 +3,7 @@ + /* See LICENSE for licensing information. */ + + #include ++#include "hostport.h" + + #if OPENSSL_VERSION_NUMBER < 0x10100000L + #define ASN1_STRING_get0_data(o) ((o)->data) +@@ -25,7 +26,11 @@ struct tls { + int tlsmaxversion; + int dtlsminversion; + int dtlsmaxversion; ++#if OPENSSL_VERSION_NUMBER >= 0x30000000 ++ EVP_PKEY* dhparam; ++#else + DH *dhparam; ++#endif + uint32_t tlsexpiry; + uint32_t dtlsexpiry; + X509_VERIFY_PARAM *vpm; +@@ -40,12 +45,13 @@ void sslinit(); + struct tls *tlsgettls(char *alt1, char *alt2); + SSL_CTX *tlsgetctx(uint8_t type, struct tls *t); + X509 *verifytlscert(SSL *ssl); +-int verifyconfcert(X509 *cert, struct clsrvconf *conf); ++int verifyconfcert(X509 *cert, struct clsrvconf *conf, struct hostportres *); + char *getcertsubject(X509 *cert); + int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); + int addmatchcertattr(struct clsrvconf *conf, const char *match); + void freematchcertattr(struct clsrvconf *conf); + void tlsreloadcrls(); ++int tlssetsni(SSL *ssl, char *sni); + int sslconnecttimeout(SSL *ssl, int timeout); + int sslaccepttimeout (SSL *ssl, int timeout); + #endif +diff --git a/tlv11.c b/tlv11.c +index d570b39..9eaf6d9 100644 +--- a/tlv11.c ++++ b/tlv11.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + struct tlv *maketlv(uint8_t t, uint8_t l, void *v) { + struct tlv *tlv; +@@ -97,6 +98,8 @@ void rmtlv(struct list *tlvs, uint8_t t) { + } + + uint8_t *tlv2str(struct tlv *tlv) { ++ if(!tlv) ++ return NULL; + uint8_t *s = malloc(tlv->l + 1); + if (s) { + memcpy(s, tlv->v, tlv->l); +@@ -117,6 +120,28 @@ struct tlv *resizetlv(struct tlv *tlv, uint8_t newlen) { + return tlv; + } + ++uint32_t tlv2longint(struct tlv *tlv) { ++ if (!tlv) return 0; ++ if (tlv->l != sizeof(uint32_t)) return 0; ++ return ntohl(*(uint32_t *)tlv->v); ++} ++ ++char* tlv2ipv4addr(struct tlv *tlv) { ++ char *result; ++ ++ if (!tlv) return NULL; ++ if (tlv->l != sizeof(in_addr_t)) return NULL; ++ ++ result = malloc(INET_ADDRSTRLEN); ++ if (!result) return NULL; ++ ++ if (!inet_ntop(AF_INET, tlv->v, result, INET_ADDRSTRLEN)) { ++ free(result); ++ return NULL; ++ } ++ return result; ++} ++ + /* Local Variables: */ + /* c-file-style: "stroustrup" */ + /* End: */ +diff --git a/tlv11.h b/tlv11.h +index 84db3d7..c565d13 100644 +--- a/tlv11.h ++++ b/tlv11.h +@@ -17,6 +17,8 @@ void freetlvlist(struct list *); + void rmtlv(struct list *, uint8_t); + uint8_t *tlv2str(struct tlv *tlv); + struct tlv *resizetlv(struct tlv *, uint8_t); ++uint32_t tlv2longint(struct tlv *tlv); ++char* tlv2ipv4addr(struct tlv *tlv); + + /* Local Variables: */ + /* c-file-style: "stroustrup" */ +diff --git a/tools/naptr-eduroam.sh b/tools/naptr-eduroam.sh +index 5402d18..2f90601 100755 +--- a/tools/naptr-eduroam.sh ++++ b/tools/naptr-eduroam.sh +@@ -14,7 +14,6 @@ usage() { + + test -n "${1}" || usage + +-REALM="${1}" + DIGCMD=$(command -v dig) + HOSTCMD=$(command -v host) + PRINTCMD=$(command -v printf) +@@ -38,7 +37,7 @@ dig_it_srv() { + } + + dig_it_naptr() { +- ${DIGCMD} +short naptr ${REALM} | grep x-eduroam:radius.tls | sort -n -k1 | ++ ${DIGCMD} +short naptr "${REALM}" | grep x-eduroam:radius.tls | sort -n -k1 | + while read line; do + set $line ; TYPE=$3 ; HOST=$(validate_host $6) + if ( [ "$TYPE" = "\"s\"" ] || [ "$TYPE" = "\"S\"" ] ) && [ -n "${HOST}" ]; then +@@ -59,7 +58,7 @@ host_it_srv() { + } + + host_it_naptr() { +- ${HOSTCMD} -t naptr ${REALM} | grep x-eduroam:radius.tls | sort -n -k5 | ++ ${HOSTCMD} -t naptr "${REALM}" | grep x-eduroam:radius.tls | sort -n -k5 | + while read line; do + set $line ; TYPE=$7 ; HOST=$(validate_host ${10}) + if ( [ "$TYPE" = "\"s\"" ] || [ "$TYPE" = "\"S\"" ] ) && [ -n "${HOST}" ]; then +@@ -69,6 +68,12 @@ host_it_naptr() { + done + } + ++REALM=$(validate_host ${1}) ++if [ -z "${REALM}" ]; then ++ echo "Error: realm \"${1}\" failed validation" ++ usage ++fi ++ + if [ -x "${DIGCMD}" ]; then + SERVERS=$(dig_it_naptr) + elif [ -x "${HOSTCMD}" ]; then +@@ -79,7 +84,7 @@ else + fi + + if [ -n "${SERVERS}" ]; then +- $PRINTCMD "server dynamic_radsec.${REALM} {\n${SERVERS}\n\ttype TLS\n}\n" ++ $PRINTCMD "server dynamic_radsec.${REALM} {\n${SERVERS}\n}\n" + exit 0 + fi + +diff --git a/tools/radsec-dynsrv.sh b/tools/radsec-dynsrv.sh +index 68bb5ba..d8318ed 100755 +--- a/tools/radsec-dynsrv.sh ++++ b/tools/radsec-dynsrv.sh +@@ -14,7 +14,6 @@ usage() { + + test -n "${1}" || usage + +-REALM="${1}" + DIGCMD=$(command -v digaaa) + HOSTCMD=$(command -v host) + PRINTCMD=$(command -v printf) +@@ -28,7 +27,7 @@ validate_port() { + } + + dig_it() { +- ${DIGCMD} +short srv _radsec._tcp.${REALM} | sort -n -k1 | ++ ${DIGCMD} +short srv "_radsec._tcp.${REALM}" | sort -n -k1 | + while read line ; do + set $line ; PORT=$(validate_port $3) ; HOST=$(validate_host $4) + if [ -n "${HOST}" ] && [ -n "${PORT}" ]; then +@@ -38,7 +37,7 @@ dig_it() { + } + + host_it() { +- ${HOSTCMD} -t srv _radsec._tcp.${REALM} | sort -n -k5 | ++ ${HOSTCMD} -t srv "_radsec._tcp.${REALM}" | sort -n -k5 | + while read line ; do + set $line ; PORT=$(validate_port $7) ; HOST=$(validate_host $8) + if [ -n "${HOST}" ] && [ -n "${PORT}" ]; then +@@ -47,6 +46,12 @@ host_it() { + done + } + ++REALM=$(validate_host ${1}) ++if test -z "${REALM}" ; then ++ echo "Error: realm \"${1}\" failed validation" ++ usage ++fi ++ + if test -x "${DIGCMD}" ; then + SERVERS=$(dig_it) + elif test -x "${HOSTCMD}" ; then +@@ -57,7 +62,7 @@ else + fi + + if test -n "${SERVERS}" ; then +- $PRINTCMD "server dynamic_radsec.${REALM} {\n${SERVERS}\n\ttype TLS\n}\n" ++ $PRINTCMD "server dynamic_radsec.${REALM} {\n${SERVERS}\n}\n" + exit 0 + fi + +diff --git a/udp.c b/udp.c +index e3e1464..6e86fbe 100644 +--- a/udp.c ++++ b/udp.c +@@ -160,7 +160,7 @@ unsigned char *radudpget(int s, struct client **client, struct server **server) + } + + p = client +- ? find_clconf(handle, (struct sockaddr *)&from, NULL) ++ ? find_clconf(handle, (struct sockaddr *)&from, NULL, NULL) + : find_srvconf(handle, (struct sockaddr *)&from, NULL); + if (!p) { + debug(DBG_WARN, "radudpget: got packet from wrong or unknown UDP peer %s, ignoring", addr2string((struct sockaddr *)&from, tmp, sizeof(tmp))); diff --git a/radsecproxy.spec b/radsecproxy.spec index c00a2d9..5821988 100644 --- a/radsecproxy.spec +++ b/radsecproxy.spec @@ -12,6 +12,7 @@ Source0: https://github.com/radsecproxy/radsecproxy/releases/download/%{version} # Source0-md5: 4d4df9b333d4e901b7fefcddeabc9ce0 Source1: %{name}.init Source2: %{name}.logrotate +Patch0: git.patch URL: https://github.com/radsecproxy/radsecproxy BuildRequires: autoconf >= 2.50 BuildRequires: automake @@ -40,6 +41,7 @@ działania zużywa około 64 kB (w zależności od liczby partnerów). %prep %setup -q +%patch0 -p1 %{__rm} -r autom4te.cache @@ -89,6 +91,6 @@ fi %attr(755,root,root) %{_bindir}/radsecproxy-conf %attr(755,root,root) %{_bindir}/radsecproxy-hash %attr(754,root,root) /etc/rc.d/init.d/%{name} -%{_mandir}/man1/radsecproxy.1* -%{_mandir}/man1/radsecproxy-hash.1* %{_mandir}/man5/radsecproxy.conf.5* +%{_mandir}/man8/radsecproxy.8* +%{_mandir}/man8/radsecproxy-hash.8*