diff options
author | venaas <venaas> | 2009-02-18 15:37:01 +0000 |
---|---|---|
committer | venaas <venaas@e88ac4ed-0b26-0410-9574-a7f39faa03bf> | 2009-02-18 15:37:01 +0000 |
commit | f2d6619255e9644f4f134dfe98a24342d35e618b (patch) | |
tree | 8f98185b835dbe76483b332724e17094e3d48e74 | |
parent | 851dd8c2292a077089bfffb5dd52943b8f7c53b4 (diff) |
updated files for 1.3-beta release
git-svn-id: https://svn.testnett.uninett.no/radsecproxy/branches/release-1.3@480 e88ac4ed-0b26-0410-9574-a7f39faa03bf
-rw-r--r-- | COPYING | 2 | ||||
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | README | 16 | ||||
-rw-r--r-- | configure.ac | 64 | ||||
-rw-r--r-- | debug.c | 30 | ||||
-rw-r--r-- | debug.h | 1 | ||||
-rw-r--r-- | dtls.c | 119 | ||||
-rw-r--r-- | dtls.h | 7 | ||||
-rw-r--r-- | gconfig.h | 2 | ||||
-rw-r--r-- | hostport.c | 295 | ||||
-rw-r--r-- | hostport.h | 24 | ||||
-rw-r--r-- | list.c | 14 | ||||
-rw-r--r-- | list.h | 12 | ||||
-rw-r--r-- | radmsg.c | 4 | ||||
-rwxr-xr-x | radsec-dynsrv.sh | 51 | ||||
-rw-r--r-- | radsecproxy.1 | 32 | ||||
-rw-r--r-- | radsecproxy.c | 1413 | ||||
-rw-r--r-- | radsecproxy.conf.5 | 26 | ||||
-rw-r--r-- | radsecproxy.conf.5.xml | 34 | ||||
-rw-r--r-- | radsecproxy.h | 58 | ||||
-rw-r--r-- | tcp.c | 78 | ||||
-rw-r--r-- | tcp.h | 5 | ||||
-rw-r--r-- | tls.c | 87 | ||||
-rw-r--r-- | tls.h | 5 | ||||
-rw-r--r-- | tlscommon.c | 637 | ||||
-rw-r--r-- | tlscommon.h | 35 | ||||
-rw-r--r-- | tlv11.c | 4 | ||||
-rw-r--r-- | udp.c | 133 | ||||
-rw-r--r-- | udp.h | 7 | ||||
-rw-r--r-- | util.c | 75 | ||||
-rw-r--r-- | util.h | 6 |
34 files changed, 2041 insertions, 1252 deletions
@@ -1,3 +1,3 @@ -Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> +Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> See the LICENSE file for licensing terms. @@ -50,4 +50,8 @@ the TTL attribute if present, discards message if becomes 0. If addTTL option is used, the TTL attribute is added with the specified value if the forwarded message does not have one. - PolicyOID option can be used to require certain CA policies. + PolicyOID option can be used to require certain CA policies. +2009-02-18 1.3-beta + Client and Server blocks may contain multiple host options. + Configure (Makefile) options for specifying which transports + should be supported in a build. @@ -7,7 +7,7 @@ Public License Version 2 or later (GPL). Alternatively the radsecproxy source code is subject to the terms of the below BSD style license. -* Copyright (c) 2006-2008, UNINETT AS +* Copyright (c) 2006-2009, UNINETT AS * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -1,6 +1,7 @@ -CFLAGS = -g -Wall -pedantic -pthread +CFLAGS = -g -Wall -pedantic -pthread -DRADPROT_UDP -DRADPROT_TLS +# -DRADPROT_TCP -DRADPROT_DTLS LDFLAGS = -lssl -OBJ = util.o debug.o list.o hash.o gconfig.o tlv11.o radmsg.o udp.o tcp.o tls.o dtls.o radsecproxy.o +OBJ = util.o debug.o list.o hash.o gconfig.o tlv11.o hostport.o radmsg.o udp.o tcp.o tls.o dtls.o tlscommon.o radsecproxy.o all: radsecproxy diff --git a/Makefile.am b/Makefile.am index 2e7d1cc..06d9c1f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,24 +1,28 @@ sbin_PROGRAMS = radsecproxy radsecproxy_SOURCES = radsecproxy.c \ + tlscommon.c \ gconfig.c \ util.c \ debug.c \ list.c \ hash.c \ tlv11.c \ + hostport.c \ radmsg.c \ udp.c \ tcp.c \ tls.c \ dtls.c \ radsecproxy.h \ + tlscommon.h \ gconfig.h \ debug.h \ util.h \ list.h \ hash.h \ tlv11.h \ + hostport.h \ radmsg.h \ udp.h \ tcp.h \ @@ -1,11 +1,17 @@ -This is radsecproxy 1.3-alpha from Dec 4, 2008 +This is radsecproxy 1.3-beta from Feb 18, 2009 radsecproxy is a generic RADIUS proxy that can support various -RADIUS clients over UDP, TLS (RadSec), TCP and DTLS. +RADIUS clients over UDP or TLS (RadSec). It should build on most Linux and BSD platforms by simply typing -"make". You may also try to use autoconf, but this is currently -unsupported. +"make". You may also try to use autoconf. It is possible to +specify which RADIUS transport the build should support. With +just doing "make" one will support only UDP and TLS. See the +Makefile for how to change this. With autoconf (configure) there +will normally be support for all transport. You can use the +configure options --enable-udp, --enable-tcp, --enable-tls and +--enable-dtls where each of them may be set to yes or no to +enable or disable them. To use it you need to create a config file which normally is called "/etc/radsecproxy.conf". You can also specify the location @@ -37,4 +43,4 @@ let me know if you feel left out. For more information, feedback etc. please see the information at http://software.uninett.no/radsecproxy/ -Stig Venaas <venaas@uninett.no> -- 2008.12.04 +Stig Venaas <venaas@uninett.no> -- 2009.02.18 diff --git a/configure.ac b/configure.ac index ee71115..3fdc0d5 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,51 @@ -AC_INIT(radsecproxy, 1.3-alpha, venaas@uninett.no) +AC_INIT(radsecproxy, 1.3-beta, venaas@uninett.no) AM_INIT_AUTOMAKE AC_PROG_CC AM_PROG_CC_C_O +udp=yes +AC_ARG_ENABLE(udp, + [ --enable-udp whether to enable UDP transport: yes/no; default yes ], + [ if test "x$enableval" = "xyes" -o "x$enableval" = "xno" ; then + udp=$enableval + else + echo "--enable-udp argument must be yes or no" + exit -1 + fi + ]) + +tcp=yes +AC_ARG_ENABLE(tcp, + [ --enable-tcp whether to enable TCP transport: yes/no; default yes ], + [ if test "x$enableval" = "xyes" -o "x$enableval" = "xno" ; then + tcp=$enableval + else + echo "--enable-tcp argument must be yes or no" + exit -1 + fi + ]) + +tls=yes +AC_ARG_ENABLE(tls, + [ --enable-tls whether to enable TLS (RadSec) transport: yes/no; default yes ], + [ if test "x$enableval" = "xyes" -o "x$enableval" = "xno" ; then + tls=$enableval + else + echo "--enable-tls argument must be yes or no" + exit -1 + fi + ]) + +dtls=yes +AC_ARG_ENABLE(dtls, + [ --enable-dtls whether to enable DTLS transport: yes/no; default yes ], + [ if test "x$enableval" = "xyes" -o "x$enableval" = "xno" ; then + dtls=$enableval + else + echo "--enable-dtls argument must be yes or no" + exit -1 + fi + ]) + dnl Check if we're on Solaris and set CFLAGS accordingly AC_CANONICAL_SYSTEM case "${target_os}" in @@ -13,6 +57,24 @@ case "${target_os}" in TARGET_CFLAGS="" TARGET_LDFLAGS="" esac +dnl Adding enabled options +if test "x$udp" = "xyes" ; then + echo "UDP transport enabled" + TARGET_CFLAGS="$TARGET_CFLAGS -DRADPROT_UDP" +fi +if test "x$tcp" = "xyes" ; then + echo "TCP transport enabled" + TARGET_CFLAGS="$TARGET_CFLAGS -DRADPROT_TCP" +fi +if test "x$tls" = "xyes" ; then + echo "TLS (RadSec) transport enabled" + TARGET_CFLAGS="$TARGET_CFLAGS -DRADPROT_TLS" +fi +if test "x$dtls" = "xyes" ; then + echo "DTLS transport enabled" + TARGET_CFLAGS="$TARGET_CFLAGS -DRADPROT_DTLS" +fi + AC_SUBST(TARGET_CFLAGS) AC_SUBST(TARGET_LDFLAGS) AX_CHECK_SSL @@ -19,9 +19,11 @@ #include <syslog.h> #include <errno.h> #include "debug.h" +#include "util.h" static char *debug_ident = NULL; static uint8_t debug_level = DBG_INFO; +static char *debug_filepath = NULL; static FILE *debug_file = NULL; static int debug_syslogfacility = 0; static uint8_t debug_timestamp = 0; @@ -68,11 +70,12 @@ int debug_set_destination(char *dest) { int i; if (!strncasecmp(dest, "file:///", 8)) { - debug_file = fopen(dest + 7, "a"); + debug_filepath = stringcopy(dest + 7, 0); + debug_file = fopen(debug_filepath, "a"); if (!debug_file) { debug_file = stderr; debugx(1, DBG_ERR, "Failed to open logfile %s\n%s", - dest + 7, strerror(errno)); + debug_filepath, strerror(errno)); } setvbuf(debug_file, NULL, _IONBF, 0); return 1; @@ -97,6 +100,29 @@ int debug_set_destination(char *dest) { exit(1); } +void debug_reopen_log() { + extern int errno; + + /* not a file, noop, return success */ + if (!debug_filepath) { + debug(DBG_ERR, "skipping reopen"); + return; + } + + if (debug_file != stderr) + fclose(debug_file); + + debug_file = fopen(debug_filepath, "a"); + if (debug_file) + debug(DBG_ERR, "Reopened logfile %s", debug_filepath); + else { + debug_file = stderr; + debug(DBG_ERR, "Failed to open logfile %s, using stderr\n%s", + debug_filepath, strerror(errno)); + } + setvbuf(debug_file, NULL, _IONBF, 0); +} + void debug_logit(uint8_t level, const char *format, va_list ap) { struct timeval now; char *timebuf; @@ -22,3 +22,4 @@ uint8_t debug_get_level(); void debug(uint8_t level, char *format, ...); void debugx(int status, uint8_t level, char *format, ...); int debug_set_destination(char *dest); +void debug_reopen_log(); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2008-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,19 +26,69 @@ #include <pthread.h> #include <openssl/ssl.h> #include <openssl/err.h> -#include "debug.h" #include "list.h" #include "hash.h" -#include "util.h" #include "radsecproxy.h" -#include "dtls.h" + +#ifdef RADPROT_DTLS +#include "debug.h" +#include "util.h" +#include "hostport.h" + +static void setprotoopts(struct commonprotoopts *opts); +static char **getlistenerargs(); +void *udpdtlsserverrd(void *arg); +int dtlsconnect(struct server *server, struct timeval *when, int timeout, char *text); +void *dtlsclientrd(void *arg); +int clientradputdtls(struct server *server, unsigned char *rad); +void addserverextradtls(struct clsrvconf *conf); +void dtlssetsrcres(); +void initextradtls(); + +static const struct protodefs protodefs = { + "dtls", + "mysecret", /* secretdefault */ + SOCK_DGRAM, /* socktype */ + "2083", /* portdefault */ + REQUEST_RETRY_COUNT, /* retrycountdefault */ + 10, /* retrycountmax */ + REQUEST_RETRY_INTERVAL, /* retryintervaldefault */ + 60, /* retryintervalmax */ + DUPLICATE_INTERVAL, /* duplicateintervaldefault */ + setprotoopts, /* setprotoopts */ + getlistenerargs, /* getlistenerargs */ + udpdtlsserverrd, /* listener */ + dtlsconnect, /* connecter */ + dtlsclientrd, /* clientconnreader */ + clientradputdtls, /* clientradput */ + NULL, /* addclient */ + addserverextradtls, /* addserverextra */ + dtlssetsrcres, /* setsrcres */ + initextradtls /* initextra */ +}; static int client4_sock = -1; static int client6_sock = -1; +static struct addrinfo *srcres = NULL; +static uint8_t handle; +static struct commonprotoopts *protoopts = NULL; + +const struct protodefs *dtlsinit(uint8_t h) { + handle = h; + return &protodefs; +} + +static void setprotoopts(struct commonprotoopts *opts) { + protoopts = opts; +} + +static char **getlistenerargs() { + return protoopts ? protoopts->listenargs : NULL; +} struct sessioncacheentry { pthread_mutex_t mutex; - struct queue *rbios; + struct gqueue *rbios; struct timeval expiry; }; @@ -48,7 +98,12 @@ struct dtlsservernewparams { struct sockaddr_storage addr; }; -int udp2bio(int s, struct queue *q, int cnt) { +void dtlssetsrcres() { + if (!srcres) + srcres = resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL, NULL, protodefs.socktype); +} + +int udp2bio(int s, struct gqueue *q, int cnt) { unsigned char *buf; BIO *rbio; @@ -84,7 +139,7 @@ int udp2bio(int s, struct queue *q, int cnt) { return 1; } -BIO *getrbio(SSL *ssl, struct queue *q, int timeout) { +BIO *getrbio(SSL *ssl, struct gqueue *q, int timeout) { BIO *rbio; struct timeval now; struct timespec to; @@ -104,7 +159,7 @@ BIO *getrbio(SSL *ssl, struct queue *q, int timeout) { return rbio; } -int dtlsread(SSL *ssl, struct queue *q, unsigned char *buf, int num, int timeout) { +int dtlsread(SSL *ssl, struct gqueue *q, unsigned char *buf, int num, int timeout) { int len, cnt; BIO *rbio; @@ -135,7 +190,7 @@ int dtlsread(SSL *ssl, struct queue *q, unsigned char *buf, int num, int timeout } /* accept if acc == 1, else connect */ -SSL *dtlsacccon(uint8_t acc, SSL_CTX *ctx, int s, struct sockaddr *addr, struct queue *rbios) { +SSL *dtlsacccon(uint8_t acc, SSL_CTX *ctx, int s, struct sockaddr *addr, struct gqueue *rbios) { SSL *ssl; int i, res; unsigned long error; @@ -171,7 +226,7 @@ SSL *dtlsacccon(uint8_t acc, SSL_CTX *ctx, int s, struct sockaddr *addr, struct return NULL; } -unsigned char *raddtlsget(SSL *ssl, struct queue *rbios, int timeout) { +unsigned char *raddtlsget(SSL *ssl, struct gqueue *rbios, int timeout) { int cnt, len; unsigned char buf[4], *rad; @@ -212,7 +267,7 @@ void *dtlsserverwr(void *arg) { int cnt; unsigned long error; struct client *client = (struct client *)arg; - struct queue *replyq; + struct gqueue *replyq; struct request *reply; debug(DBG_DBG, "dtlsserverwr: starting for %s", addr2string(client->addr)); @@ -300,9 +355,9 @@ void *dtlsservernew(void *arg) { uint8_t delay = 60; debug(DBG_DBG, "dtlsservernew: starting"); - conf = find_clconf(RAD_DTLS, (struct sockaddr *)¶ms->addr, NULL); + conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, NULL); if (conf) { - ctx = tlsgetctx(RAD_DTLS, conf->tlsconf); + ctx = tlsgetctx(handle, conf->tlsconf); if (!ctx) goto exit; ssl = dtlsacccon(1, ctx, params->sock, (struct sockaddr *)¶ms->addr, params->sesscache->rbios); @@ -330,7 +385,7 @@ void *dtlsservernew(void *arg) { } goto exit; } - conf = find_clconf(RAD_DTLS, (struct sockaddr *)¶ms->addr, &cur); + conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, &cur); } debug(DBG_WARN, "dtlsservernew: ignoring request, no matching TLS client"); @@ -476,7 +531,8 @@ int dtlsconnect(struct server *server, struct timeval *when, int timeout, char * time_t elapsed; X509 *cert; SSL_CTX *ctx = NULL; - + struct hostportres *hp; + debug(DBG_DBG, "dtlsconnect: called from %s", text); pthread_mutex_lock(&server->lock); if (when && memcmp(&server->lastconnecttry, when, sizeof(struct timeval))) { @@ -486,6 +542,7 @@ int dtlsconnect(struct server *server, struct timeval *when, int timeout, char * return 1; } + hp = (struct hostportres *)list_first(server->conf->hostports)->data; for (;;) { gettimeofday(&now, NULL); elapsed = now.tv_sec - server->lastconnecttry.tv_sec; @@ -511,14 +568,14 @@ int dtlsconnect(struct server *server, struct timeval *when, int timeout, char * sleep(60); } else server->lastconnecttry.tv_sec = now.tv_sec; /* no sleep at startup */ - debug(DBG_WARN, "dtlsconnect: trying to open DTLS connection to %s port %s", server->conf->host, server->conf->port); + debug(DBG_WARN, "dtlsconnect: trying to open DTLS connection to %s port %s", hp->host, hp->port); SSL_free(server->ssl); server->ssl = NULL; - ctx = tlsgetctx(RAD_DTLS, server->conf->tlsconf); + ctx = tlsgetctx(handle, server->conf->tlsconf); if (!ctx) continue; - server->ssl = dtlsacccon(0, ctx, server->sock, server->conf->addrinfo->ai_addr, server->rbios); + server->ssl = dtlsacccon(0, ctx, server->sock, hp->addrinfo->ai_addr, server->rbios); if (!server->ssl) continue; debug(DBG_DBG, "dtlsconnect: DTLS: ok"); @@ -532,7 +589,7 @@ int dtlsconnect(struct server *server, struct timeval *when, int timeout, char * X509_free(cert); } X509_free(cert); - debug(DBG_WARN, "dtlsconnect: DTLS connection to %s port %s up", server->conf->host, server->conf->port); + debug(DBG_WARN, "dtlsconnect: DTLS connection to %s port %s up", hp->host, hp->port); server->connectionok = 1; gettimeofday(&server->lastconnecttry, NULL); pthread_mutex_unlock(&server->lock); @@ -553,7 +610,7 @@ int clientradputdtls(struct server *server, unsigned char *rad) { debug(DBG_ERR, "clientradputdtls: DTLS: %s", ERR_error_string(error, NULL)); return 0; } - debug(DBG_DBG, "clientradputdtls: Sent %d bytes, Radius packet of length %d to DTLS peer %s", cnt, len, conf->host); + debug(DBG_DBG, "clientradputdtls: Sent %d bytes, Radius packet of length %d to DTLS peer %s", cnt, len, conf->name); return 1; } @@ -577,7 +634,7 @@ void *udpdtlsclientrd(void *arg) { continue; } - conf = find_srvconf(RAD_DTLS, (struct sockaddr *)&from, NULL); + conf = find_srvconf(handle, (struct sockaddr *)&from, NULL); if (!conf) { debug(DBG_WARN, "udpdtlsclientrd: got packet from wrong or unknown DTLS peer %s, ignoring", addr2string((struct sockaddr *)&from)); recv(s, buf, 4, 0); @@ -610,20 +667,20 @@ void *dtlsclientrd(void *arg) { } void addserverextradtls(struct clsrvconf *conf) { - switch (conf->addrinfo->ai_family) { + switch (((struct hostportres *)list_first(conf->hostports)->data)->addrinfo->ai_family) { case AF_INET: if (client4_sock < 0) { - client4_sock = bindtoaddr(getsrcprotores(RAD_DTLS), AF_INET, 0, 1); + client4_sock = bindtoaddr(srcres, AF_INET, 0, 1); if (client4_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); + debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name); } conf->servers->sock = client4_sock; break; case AF_INET6: if (client6_sock < 0) { - client6_sock = bindtoaddr(getsrcprotores(RAD_DTLS), AF_INET6, 0, 1); + client6_sock = bindtoaddr(srcres, AF_INET6, 0, 1); if (client6_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); + debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name); } conf->servers->sock = client6_sock; break; @@ -634,6 +691,11 @@ void addserverextradtls(struct clsrvconf *conf) { void initextradtls() { pthread_t cl4th, cl6th; + + if (srcres) { + freeaddrinfo(srcres); + srcres = NULL; + } if (client4_sock >= 0) if (pthread_create(&cl4th, NULL, udpdtlsclientrd, (void *)&client4_sock)) @@ -642,3 +704,8 @@ void initextradtls() { if (pthread_create(&cl6th, NULL, udpdtlsclientrd, (void *)&client6_sock)) debugx(1, DBG_ERR, "pthread_create failed"); } +#else +const struct protodefs *dtlsinit(uint8_t h) { + return NULL; +} +#endif @@ -6,9 +6,4 @@ * copyright notice and this permission notice appear in all copies. */ -void *udpdtlsserverrd(void *arg); -int dtlsconnect(struct server *server, struct timeval *when, int timeout, char *text); -void *dtlsclientrd(void *arg); -int clientradputdtls(struct server *server, unsigned char *rad); -void addserverextradtls(struct clsrvconf *conf); -void initextradtls(); +const struct protodefs *dtlsinit(uint8_t h); @@ -4,6 +4,8 @@ #define CONF_BLN 4 #define CONF_LINT 5 +#include <stdio.h> + struct gconffile { char *path; FILE *file; diff --git a/hostport.c b/hostport.c new file mode 100644 index 0000000..22ff2dc --- /dev/null +++ b/hostport.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include <stdlib.h> +#include <string.h> +#include <netdb.h> +#include "debug.h" +#include "util.h" +#include "list.h" +#include "hostport.h" + +void freehostport(struct hostportres *hp) { + if (hp) { + free(hp->host); + free(hp->port); + if (hp->addrinfo) + freeaddrinfo(hp->addrinfo); + free(hp); + } +} + +static int parsehostport(struct hostportres *hp, char *hostport, char *default_port) { + char *p, *field; + int ipv6 = 0; + + if (!hostport) { + hp->port = default_port ? stringcopy(default_port, 0) : NULL; + return 1; + } + p = hostport; + /* allow literal addresses and port, e.g. [2001:db8::1]:1812 */ + if (*p == '[') { + p++; + field = p; + for (; *p && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n'; p++); + if (*p != ']') { + debug(DBG_ERR, "no ] matching initial ["); + return 0; + } + ipv6 = 1; + } else { + field = p; + for (; *p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n'; p++); + } + if (field == p) { + debug(DBG_ERR, "missing host/address"); + return 0; + } + + hp->host = stringcopy(field, p - field); + if (ipv6) { + p++; + if (*p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n') { + debug(DBG_ERR, "unexpected character after ]"); + return 0; + } + } + if (*p == ':') { + /* port number or service name is specified */; + field = ++p; + for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++); + if (field == p) { + debug(DBG_ERR, "syntax error, : but no following port"); + return 0; + } + hp->port = stringcopy(field, p - field); + } else + hp->port = default_port ? stringcopy(default_port, 0) : NULL; + return 1; +} + +struct hostportres *newhostport(char *hostport, char *default_port, uint8_t prefixok) { + struct hostportres *hp; + char *slash, *s; + int plen; + + hp = malloc(sizeof(struct hostportres)); + if (!hp) { + debug(DBG_ERR, "resolve_newhostport: malloc failed"); + goto errexit; + } + memset(hp, 0, sizeof(struct hostportres)); + + if (!parsehostport(hp, hostport, default_port)) + goto errexit; + + if (hp->host && !strcmp(hp->host, "*")) { + free(hp->host); + hp->host = NULL; + } + + slash = hostport ? strchr(hostport, '/') : NULL; + if (slash) { + if (!prefixok) { + debug(DBG_WARN, "newhostport: prefix not allowed here", hp->host); + goto errexit; + } + s = slash + 1; + if (!*s) { + debug(DBG_WARN, "newhostport: prefix length must be specified after the / in %s", hp->host); + goto errexit; + } + for (; *s; s++) + if (*s < '0' || *s > '9') { + debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host); + goto errexit; + } + plen = atoi(slash + 1); + if (plen < 0 || plen > 128) { + debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host); + goto errexit; + } + hp->prefixlen = plen; + } else + hp->prefixlen = 255; + return hp; + + errexit: + freehostport(hp); + return NULL; +} + +int resolvehostport(struct hostportres *hp, int socktype, uint8_t passive) { + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = socktype; + hints.ai_family = AF_UNSPEC; + if (passive) + hints.ai_flags = AI_PASSIVE; + + if (!hp->host && !hp->port) { + /* getaddrinfo() doesn't like host and port to be NULL */ + if (getaddrinfo(hp->host, "1812" /* can be anything */, &hints, &hp->addrinfo)) { + debug(DBG_WARN, "resolvehostport: can't resolve (null) port (null)"); + goto errexit; + } + for (res = hp->addrinfo; res; res = res->ai_next) + port_set(res->ai_addr, 0); + } else { + if (hp->prefixlen != 255) + hints.ai_flags |= AI_NUMERICHOST; + if (getaddrinfo(hp->host, hp->port, &hints, &hp->addrinfo)) { + debug(DBG_WARN, "resolvehostport: can't resolve %s port %s", hp->host ? hp->host : "(null)", hp->port ? hp->port : "(null)"); + goto errexit; + } + if (hp->prefixlen != 255) { + switch (hp->addrinfo->ai_family) { + case AF_INET: + if (hp->prefixlen > 32) { + debug(DBG_WARN, "resolvehostport: prefix length must be <= 32 in %s", hp->host); + goto errexit; + } + break; + case AF_INET6: + break; + default: + debug(DBG_WARN, "resolvehostport: prefix must be IPv4 or IPv6 in %s", hp->host); + goto errexit; + } + } + } + return 1; + + errexit: + if (hp->addrinfo) + freeaddrinfo(hp->addrinfo); + return 0; +} + +int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok) { + struct hostportres *hp; + int i; + + if (!*hostports) { + *hostports = list_create(); + if (!*hostports) { + debug(DBG_ERR, "addhostport: malloc failed"); + return 0; + } + } + + for (i = 0; hostport[i]; i++) { + hp = newhostport(hostport[i], portdefault, prefixok); + if (!hp) + return 0; + if (!list_push(*hostports, hp)) { + freehostport(hp); + debug(DBG_ERR, "addhostport: malloc failed"); + return 0; + } + } + return 1; +} + +void freehostports(struct list *hostports) { + struct hostportres *hp; + + while ((hp = (struct hostportres *)list_shift(hostports))) + freehostport(hp); + list_destroy(hostports); +} + +int resolvehostports(struct list *hostports, int socktype) { + struct list_node *entry; + struct hostportres *hp; + + for (entry = list_first(hostports); entry; entry = list_next(entry)) { + hp = (struct hostportres *)entry->data; + if (!hp->addrinfo && !resolvehostport(hp, socktype, 0)) + return 0; + } + return 1; +} + +struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype) { + struct addrinfo *ai = NULL; + struct hostportres *hp = newhostport(hostport, default_port, 0); + if (hp && resolvehostport(hp, socktype, 1)) { + ai = hp->addrinfo; + hp->addrinfo = NULL; + } + freehostport(hp); + return ai; +} + +/* returns 1 if the len first bits are equal, else 0 */ +static int prefixmatch(void *a1, void *a2, uint8_t len) { + static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; + uint8_t r, l = len / 8; + if (l && memcmp(a1, a2, l)) + return 0; + r = len % 8; + if (!r) + return 1; + return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]); +} + +int addressmatches(struct list *hostports, struct sockaddr *addr) { + struct sockaddr_in6 *sa6 = NULL; + struct in_addr *a4 = NULL; + struct addrinfo *res; + struct list_node *entry; + struct hostportres *hp = NULL; + + if (addr->sa_family == AF_INET6) { + sa6 = (struct sockaddr_in6 *)addr; + if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { + a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12]; + sa6 = NULL; + } + } else + a4 = &((struct sockaddr_in *)addr)->sin_addr; + + for (entry = list_first(hostports); entry; entry = list_next(entry)) { + hp = (struct hostportres *)entry->data; + for (res = hp->addrinfo; res; res = res->ai_next) + if (hp->prefixlen == 255) { + if ((a4 && res->ai_family == AF_INET && + !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) || + (sa6 && res->ai_family == AF_INET6 && + !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16))) + return 1; + } else { + 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))) + return 1; + } + } + return 0; +} + +int connecttcphostlist(struct list *hostports, struct addrinfo *src) { + int s; + struct list_node *entry; + struct hostportres *hp = NULL; + + for (entry = list_first(hostports); entry; entry = list_next(entry)) { + hp = (struct hostportres *)entry->data; + 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); + return s; + } + } + debug(DBG_ERR, "connecttcphostlist: failed"); + return -1; +} diff --git a/hostport.h b/hostport.h new file mode 100644 index 0000000..d28f8ee --- /dev/null +++ b/hostport.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +struct hostportres { + char *host; + char *port; + uint8_t prefixlen; + struct addrinfo *addrinfo; +}; + +struct hostportres *newhostport(char *hostport, char *default_port, uint8_t prefixok); +int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok); +void freehostport(struct hostportres *hp); +void freehostports(struct list *hostports); +int resolvehostport(struct hostportres *hp, int socktype, uint8_t passive); +int resolvehostports(struct list *hostports, int socktype); +struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype); +int addressmatches(struct list *hostports, struct sockaddr *addr); +int connecttcphostlist(struct list *hostports, struct addrinfo *src); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -49,7 +49,8 @@ int list_push(struct list *list, void *data) { else list->first = node; list->last = node; - + + list->count++; return 1; } @@ -67,7 +68,7 @@ void *list_shift(struct list *list) { list->last = NULL; data = node->data; free(node); - + list->count--; return data; } @@ -82,6 +83,7 @@ void list_removedata(struct list *list, void *data) { while (node->data == data) { list->first = node->next; free(node); + list->count--; node = list->first; if (!node) { list->last = NULL; @@ -93,6 +95,7 @@ void list_removedata(struct list *list, void *data) { t = node->next; node->next = t->next; free(t); + list->count--; if (!node->next) { /* we removed the last one */ list->last = node; return; @@ -109,3 +112,8 @@ struct list_node *list_first(struct list *list) { struct list_node *list_next(struct list_node *node) { return node->next; } + +/* returns number of nodes */ +uint32_t list_count(struct list *list) { + return list->count; +} @@ -1,11 +1,17 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. */ +#ifdef SYS_SOLARIS9 +#include <sys/inttypes.h> +#else +#include <stdint.h> +#endif + struct list_node { struct list_node *next; void *data; @@ -13,6 +19,7 @@ struct list_node { struct list { struct list_node *first, *last; + uint32_t count; }; /* allocates and initialises list structure; returns NULL if malloc fails */ @@ -35,3 +42,6 @@ struct list_node *list_first(struct list *list); /* returns the next node after the argument */ struct list_node *list_next(struct list_node *node); + +/* returns number of nodes */ +uint32_t list_count(struct list *list); @@ -6,7 +6,11 @@ * copyright notice and this permission notice appear in all copies. */ +#ifdef SYS_SOLARIS9 +#include <sys/inttypes.h> +#else #include <stdint.h> +#endif #include <stdlib.h> #include <string.h> #include <arpa/inet.h> diff --git a/radsec-dynsrv.sh b/radsec-dynsrv.sh new file mode 100755 index 0000000..7a74b6d --- /dev/null +++ b/radsec-dynsrv.sh @@ -0,0 +1,51 @@ +#! /bin/bash + +# Example script! +# This script looks up radsec srv records in DNS for the one +# realm given as argument, and creates a server template based +# on that. It currently ignores weight markers, but does sort +# servers on priority marker, lowest number first. +# For host command this is coloumn 5, for dig it is coloumn 1. + +usage() { + echo "Usage: ${0} <realm>" + exit 1 +} + +test -n "${1}" || usage + +REALM="${1}" +DIGCMD=$(command -v digaaa) +HOSTCMD=$(command -v host) + +dig_it() { + ${DIGCMD} +short srv _radsec._tcp.${REALM} | sort -k1 | + while read line ; do + set $line ; PORT=$3 ; HOST=$4 + echo -e "\thost ${HOST%.}:${PORT}" + done +} + +host_it() { + ${HOSTCMD} -t srv _radsec._tcp.${REALM} | sort -k5 | + while read line ; do + set $line ; PORT=$7 ; HOST=$8 + echo -e "\thost ${HOST%.}:${PORT}" + done +} + +if test -x "${DIGCMD}" ; then + SERVERS=$(dig_it) +elif test -x "${HOSTCMD}" ; then + SERVERS=$(host_it) +else + echo "${0} requires either \"dig\" or \"host\" command." + exit 1 +fi + +if test -n "${SERVERS}" ; then + echo -e "server dynamic_radsec.${REALM} {\n${SERVERS}\n\ttype TLS\n}" + exit 0 +fi + +exit 0 diff --git a/radsecproxy.1 b/radsecproxy.1 index f4d7760..8fc21af 100644 --- a/radsecproxy.1 +++ b/radsecproxy.1 @@ -1,10 +1,11 @@ -.TH radsecproxy 1 "2 October 2008" +.TH radsecproxy 1 "17 February 2009" .SH "NAME" radsecproxy - a generic RADIUS proxy that provides both RADIUS UDP and TCP/TLS (RadSec) transport. .SH "SYNOPSIS" -radsecproxy [ -c configfile ] [ -d debuglevel ] [ -f ] [ -p ] [ -v ] +.HP 12 +radsecproxy [-c configfile] [-d debuglevel] [-f] [-i pidfile] [-p] [-v] .sp .SH "DESCRIPTION" @@ -31,6 +32,7 @@ a proxy on a site boundary. Since the proxy \fBsupports both IPv4 and IPv6\fR, it could also be used to allow communication in cases where some RADIUS nodes use only IPv4 and some only IPv6. +.SH "OPTIONS" .TP .B -f .sp @@ -75,6 +77,32 @@ information and exit. This option allows you to specify which config file to use. This is useful if you want to use a config file that is not in any of the default locations. +.TP +.B -i <pid file path> +.sp +\fIPID file path\fR +.sp +This option tells the proxy to create a PID file with the specified path. + +.SH "SIGNALS" +The proxy generally exits on all signals. The exceptions are listed below. + +.TP +.B SIGHUP +.sp +When logging to a file, this signal forces a reopen of the log file. + +.TP +.B SIGPIPE +.sp +This signal is ignored. + +.SH "FILES" +.TP +.B /etc/radsecproxy.conf +.sp +The default configuration file. + .SH "SEE ALSO" radsecproxy.conf(5), RadSec internet draft http://tools.ietf.org/html/draft-ietf-radext-radsec diff --git a/radsecproxy.c b/radsecproxy.c index 89e3622..b78d46b 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -30,7 +30,7 @@ * If TLS peers are configured, there will initially be 2 * #peers TLS threads * For each TLS peer connecting to us there will be 2 more TLS threads * This is only for connected peers - * Example: With 3 UDP peer and 30 TLS peers, there will be a max of + * Example: With 3 UDP peers and 30 TLS peers, there will be a max of * 1 + (2 + 2 * 3) + (2 * 30) + (2 * 30) = 129 threads */ @@ -53,7 +53,6 @@ #endif #include <sys/time.h> #include <sys/types.h> -#include <sys/select.h> #include <ctype.h> #include <sys/wait.h> #include <arpa/inet.h> @@ -64,13 +63,11 @@ #include <openssl/rand.h> #include <openssl/err.h> #include <openssl/md5.h> -#include <openssl/hmac.h> -#include <openssl/x509v3.h> #include "debug.h" #include "list.h" #include "hash.h" #include "util.h" -#include "gconfig.h" +#include "hostport.h" #include "radsecproxy.h" #include "udp.h" #include "tcp.h" @@ -79,18 +76,17 @@ static struct options options; static struct list *clconfs, *srvconfs; -struct list *realms; -struct hash *tlsconfs, *rewriteconfs; - -static struct addrinfo *srcprotores[RAD_PROTOCOUNT]; +static struct list *realms; +static struct hash *rewriteconfs; static pthread_mutex_t *ssl_locks = NULL; static long *ssl_lock_count; extern int optind; extern char *optarg; +static const struct protodefs *protodefs[RAD_PROTOCOUNT]; /* minimum required declarations to avoid reordering code */ -struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf, char *id); +struct realm *adddynamicrealmserver(struct realm *realm, char *id); int dynamicconfig(struct server *server); int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); void freerealm(struct realm *realm); @@ -99,88 +95,15 @@ void freerq(struct request *rq); void freerqoutdata(struct rqout *rqout); void rmclientrq(struct request *rq, uint8_t id); -static const struct protodefs protodefs[] = { - { "udp", /* UDP, assuming RAD_UDP defined as 0 */ - NULL, /* secretdefault */ - SOCK_DGRAM, /* socktype */ - "1812", /* portdefault */ - REQUEST_RETRY_COUNT, /* retrycountdefault */ - 10, /* retrycountmax */ - REQUEST_RETRY_INTERVAL, /* retryintervaldefault */ - 60, /* retryintervalmax */ - DUPLICATE_INTERVAL, /* duplicateintervaldefault */ - udpserverrd, /* listener */ - NULL, /* connecter */ - NULL, /* clientconnreader */ - clientradputudp, /* clientradput */ - addclientudp, /* addclient */ - addserverextraudp, /* addserverextra */ - 1, /* freesrcprotores */ - initextraudp /* initextra */ - }, - { "tls", /* TLS, assuming RAD_TLS defined as 1 */ - "mysecret", /* secretdefault */ - SOCK_STREAM, /* socktype */ - "2083", /* portdefault */ - 0, /* retrycountdefault */ - 0, /* retrycountmax */ - REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */ - 60, /* retryintervalmax */ - DUPLICATE_INTERVAL, /* duplicateintervaldefault */ - tlslistener, /* listener */ - tlsconnect, /* connecter */ - tlsclientrd, /* clientconnreader */ - clientradputtls, /* clientradput */ - NULL, /* addclient */ - NULL, /* addserverextra */ - 0, /* freesrcprotores */ - NULL /* initextra */ - }, - { "tcp", /* TCP, assuming RAD_TCP defined as 2 */ - NULL, /* secretdefault */ - SOCK_STREAM, /* socktype */ - "1812", /* portdefault */ - 0, /* retrycountdefault */ - 0, /* retrycountmax */ - REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */ - 60, /* retryintervalmax */ - DUPLICATE_INTERVAL, /* duplicateintervaldefault */ - tcplistener, /* listener */ - tcpconnect, /* connecter */ - tcpclientrd, /* clientconnreader */ - clientradputtcp, /* clientradput */ - NULL, /* addclient */ - NULL, /* addserverextra */ - 0, /* freesrcprotores */ - NULL /* initextra */ - }, - { "dtls", /* DTLS, assuming RAD_DTLS defined as 3 */ - "mysecret", /* secretdefault */ - SOCK_DGRAM, /* socktype */ - "2083", /* portdefault */ - REQUEST_RETRY_COUNT, /* retrycountdefault */ - 10, /* retrycountmax */ - REQUEST_RETRY_INTERVAL, /* retryintervaldefault */ - 60, /* retryintervalmax */ - DUPLICATE_INTERVAL, /* duplicateintervaldefault */ - udpdtlsserverrd, /* listener */ - dtlsconnect, /* connecter */ - dtlsclientrd, /* clientconnreader */ - clientradputdtls, /* clientradput */ - NULL, /* addclient */ - addserverextradtls, /* addserverextra */ - 1, /* freesrcprotores */ - initextradtls /* initextra */ - }, - { NULL, NULL, 0, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL - } -}; +static const struct protodefs *(*protoinits[])(uint8_t) = { udpinit, tlsinit, tcpinit, dtlsinit }; uint8_t protoname2int(const char *name) { uint8_t i; - for (i = 0; protodefs[i].name && strcasecmp(protodefs[i].name, name); i++); - return i; + for (i = 0; i < RAD_PROTOCOUNT; i++) + if (protodefs[i] && protodefs[i]->name && !strcasecmp(protodefs[i]->name, name)) + return i; + return 255; } /* callbacks for making OpenSSL thread safe */ @@ -196,210 +119,6 @@ void ssl_locking_callback(int mode, int type, const char *file, int line) { pthread_mutex_unlock(&ssl_locks[type]); } -static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) { - int pwdlen = strlen(userdata); - if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */ - return 0; - memcpy(buf, userdata, pwdlen); - return pwdlen; -} - -static int verify_cb(int ok, X509_STORE_CTX *ctx) { - char *buf = NULL; - X509 *err_cert; - int err, depth; - - err_cert = X509_STORE_CTX_get_current_cert(ctx); - err = X509_STORE_CTX_get_error(ctx); - depth = X509_STORE_CTX_get_error_depth(ctx); - - if (depth > MAX_CERT_DEPTH) { - ok = 0; - err = X509_V_ERR_CERT_CHAIN_TOO_LONG; - X509_STORE_CTX_set_error(ctx, err); - } - - if (!ok) { - if (err_cert) - buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); - debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : ""); - free(buf); - buf = NULL; - - switch (err) { - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - if (err_cert) { - buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0); - if (buf) { - debug(DBG_WARN, "\tIssuer=%s", buf); - free(buf); - buf = NULL; - } - } - break; - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - debug(DBG_WARN, "\tCertificate not yet valid"); - break; - case X509_V_ERR_CERT_HAS_EXPIRED: - debug(DBG_WARN, "Certificate has expired"); - break; - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - debug(DBG_WARN, "Certificate no longer valid (after notAfter)"); - break; - case X509_V_ERR_NO_EXPLICIT_POLICY: - debug(DBG_WARN, "No Explicit Certificate Policy"); - break; - } - } -#ifdef DEBUG - printf("certificate verify returns %d\n", ok); -#endif - return ok; -} - -struct addrinfo *getsrcprotores(uint8_t type) { - return srcprotores[type]; -} - -int resolvepeer(struct clsrvconf *conf, int ai_flags) { - struct addrinfo hints, *addrinfo, *res; - char *slash, *s; - int plen = 0; - - slash = conf->host ? strchr(conf->host, '/') : NULL; - if (slash) { - s = slash + 1; - if (!*s) { - debug(DBG_WARN, "resolvepeer: prefix length must be specified after the / in %s", conf->host); - return 0; - } - for (; *s; s++) - if (*s < '0' || *s > '9') { - debug(DBG_WARN, "resolvepeer: %s in %s is not a valid prefix length", slash + 1, conf->host); - return 0; - } - plen = atoi(slash + 1); - if (plen < 0 || plen > 128) { - debug(DBG_WARN, "resolvepeer: %s in %s is not a valid prefix length", slash + 1, conf->host); - return 0; - } - *slash = '\0'; - } - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = conf->pdef->socktype; - hints.ai_family = AF_UNSPEC; - hints.ai_flags = ai_flags; - if (!conf->host && !conf->port) { - /* getaddrinfo() doesn't like host and port to be NULL */ - if (getaddrinfo(conf->host, conf->pdef->portdefault, &hints, &addrinfo)) { - debug(DBG_WARN, "resolvepeer: can't resolve (null) port (null)"); - return 0; - } - for (res = addrinfo; res; res = res->ai_next) - port_set(res->ai_addr, 0); - } else { - if (slash) - hints.ai_flags |= AI_NUMERICHOST; - if (getaddrinfo(conf->host, conf->port, &hints, &addrinfo)) { - debug(DBG_WARN, "resolvepeer: can't resolve %s port %s", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)"); - return 0; - } - if (slash) { - *slash = '/'; - switch (addrinfo->ai_family) { - case AF_INET: - if (plen > 32) { - debug(DBG_WARN, "resolvepeer: prefix length must be <= 32 in %s", conf->host); - freeaddrinfo(addrinfo); - return 0; - } - break; - case AF_INET6: - break; - default: - debug(DBG_WARN, "resolvepeer: prefix must be IPv4 or IPv6 in %s", conf->host); - freeaddrinfo(addrinfo); - return 0; - } - conf->prefixlen = (uint8_t)plen; - } else - conf->prefixlen = 255; - } - if (conf->addrinfo) - freeaddrinfo(conf->addrinfo); - conf->addrinfo = addrinfo; - return 1; -} - -char *parsehostport(char *s, struct clsrvconf *conf, char *default_port) { - char *p, *field; - int ipv6 = 0; - - p = s; - /* allow literal addresses and port, e.g. [2001:db8::1]:1812 */ - if (*p == '[') { - p++; - field = p; - for (; *p && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n'; p++); - if (*p != ']') - debugx(1, DBG_ERR, "no ] matching initial ["); - ipv6 = 1; - } else { - field = p; - for (; *p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n'; p++); - } - if (field == p) - debugx(1, DBG_ERR, "missing host/address"); - - conf->host = stringcopy(field, p - field); - if (ipv6) { - p++; - if (*p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n') - debugx(1, DBG_ERR, "unexpected character after ]"); - } - if (*p == ':') { - /* port number or service name is specified */; - field = ++p; - for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++); - if (field == p) - debugx(1, DBG_ERR, "syntax error, : but no following port"); - conf->port = stringcopy(field, p - field); - } else - conf->port = default_port ? stringcopy(default_port, 0) : NULL; - return p; -} - -struct clsrvconf *resolve_hostport(uint8_t type, char *lconf, char *default_port) { - struct clsrvconf *conf; - - conf = malloc(sizeof(struct clsrvconf)); - if (!conf) - debugx(1, DBG_ERR, "malloc failed"); - memset(conf, 0, sizeof(struct clsrvconf)); - conf->type = type; - conf->pdef = &protodefs[conf->type]; - if (lconf) { - parsehostport(lconf, conf, default_port); - if (!strcmp(conf->host, "*")) { - free(conf->host); - conf->host = NULL; - } - } else - conf->port = default_port ? stringcopy(default_port, 0) : NULL; - if (!resolvepeer(conf, AI_PASSIVE)) - debugx(1, DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)"); - return conf; -} - -void freeclsrvres(struct clsrvconf *res) { - free(res->host); - free(res->port); - if (res->addrinfo) - freeaddrinfo(res->addrinfo); - free(res); -} - /* returns 1 if the len first bits are equal, else 0 */ int prefixmatch(void *a1, void *a2, uint8_t len) { static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; @@ -414,46 +133,15 @@ int prefixmatch(void *a1, void *a2, uint8_t len) { /* returns next config with matching address, or NULL */ struct clsrvconf *find_conf(uint8_t type, struct sockaddr *addr, struct list *confs, struct list_node **cur) { - struct sockaddr_in6 *sa6 = NULL; - struct in_addr *a4 = NULL; - struct addrinfo *res; struct list_node *entry; struct clsrvconf *conf; - if (addr->sa_family == AF_INET6) { - sa6 = (struct sockaddr_in6 *)addr; - if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { - a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12]; - sa6 = NULL; - } - } else - a4 = &((struct sockaddr_in *)addr)->sin_addr; - for (entry = (cur && *cur ? list_next(*cur) : list_first(confs)); entry; entry = list_next(entry)) { conf = (struct clsrvconf *)entry->data; - if (conf->type == type) { - if (conf->prefixlen == 255) { - for (res = conf->addrinfo; res; res = res->ai_next) - if ((a4 && res->ai_family == AF_INET && - !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) || - (sa6 && res->ai_family == AF_INET6 && - !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16))) { - if (cur) - *cur = entry; - return conf; - } - } else { - res = conf->addrinfo; - if (res && - ((a4 && res->ai_family == AF_INET && - prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, conf->prefixlen)) || - (sa6 && res->ai_family == AF_INET6 && - prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, conf->prefixlen)))) { - if (cur) - *cur = entry; - return conf; - } - } + if (conf->type == type && addressmatches(conf->hostports, addr)) { + if (cur) + *cur = entry; + return conf; } } return NULL; @@ -483,10 +171,10 @@ struct clsrvconf *find_clconf_type(uint8_t type, struct list_node **cur) { return NULL; } -struct queue *newqueue() { - struct queue *q; +struct gqueue *newqueue() { + struct gqueue *q; - q = malloc(sizeof(struct queue)); + q = malloc(sizeof(struct gqueue)); if (!q) debugx(1, DBG_ERR, "malloc failed"); q->entries = list_create(); @@ -497,7 +185,7 @@ struct queue *newqueue() { return q; } -void removequeue(struct queue *q) { +void removequeue(struct gqueue *q) { struct list_node *entry; if (!q) @@ -512,7 +200,7 @@ void removequeue(struct queue *q) { free(q); } -void freebios(struct queue *q) { +void freebios(struct gqueue *q) { BIO *bio; pthread_mutex_lock(&q->mutex); @@ -643,8 +331,6 @@ void freeserver(struct server *server, uint8_t destroymutex) { } int addserver(struct clsrvconf *conf) { - struct clsrvconf *res; - uint8_t type; int i; if (conf->servers) { @@ -659,16 +345,11 @@ int addserver(struct clsrvconf *conf) { memset(conf->servers, 0, sizeof(struct server)); conf->servers->conf = conf; - type = conf->type; - if (type == RAD_DTLS) +#ifdef RADPROT_DTLS + if (conf->type == RAD_DTLS) conf->servers->rbios = newqueue(); - - if (!srcprotores[type]) { - res = resolve_hostport(type, options.sourcearg[type], NULL); - srcprotores[type] = res->addrinfo; - res->addrinfo = NULL; - freeclsrvres(res); - } +#endif + conf->pdef->setsrcres(); conf->servers->sock = -1; if (conf->pdef->addserverextra) @@ -717,194 +398,6 @@ int addserver(struct clsrvconf *conf) { return 0; } -int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) { - int loc, i, l, n, r = 0; - char *v; - X509_EXTENSION *ex; - STACK_OF(GENERAL_NAME) *alt; - GENERAL_NAME *gn; - - debug(DBG_DBG, "subjectaltnameaddr"); - - loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); - if (loc < 0) - return r; - - ex = X509_get_ext(cert, loc); - alt = X509V3_EXT_d2i(ex); - if (!alt) - return r; - - n = sk_GENERAL_NAME_num(alt); - for (i = 0; i < n; i++) { - gn = sk_GENERAL_NAME_value(alt, i); - if (gn->type != GEN_IPADD) - continue; - r = -1; - v = (char *)ASN1_STRING_data(gn->d.ia5); - l = ASN1_STRING_length(gn->d.ia5); - if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr))) - && !memcmp(v, &addr, l)) { - r = 1; - break; - } - } - GENERAL_NAMES_free(alt); - return r; -} - -int cnregexp(X509 *cert, char *exact, regex_t *regex) { - int loc, l; - char *v, *s; - X509_NAME *nm; - X509_NAME_ENTRY *e; - ASN1_STRING *t; - - nm = X509_get_subject_name(cert); - loc = -1; - for (;;) { - loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc); - if (loc == -1) - break; - e = X509_NAME_get_entry(nm, loc); - t = X509_NAME_ENTRY_get_data(e); - v = (char *) ASN1_STRING_data(t); - l = ASN1_STRING_length(t); - if (l < 0) - continue; - if (exact) { - if (l == strlen(exact) && !strncasecmp(exact, v, l)) - return 1; - } else { - s = stringcopy((char *)v, l); - if (!s) { - debug(DBG_ERR, "malloc failed"); - continue; - } - if (regexec(regex, s, 0, NULL, 0)) { - free(s); - continue; - } - free(s); - return 1; - } - } - return 0; -} - -int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) { - int loc, i, l, n, r = 0; - char *s, *v; - X509_EXTENSION *ex; - STACK_OF(GENERAL_NAME) *alt; - GENERAL_NAME *gn; - - debug(DBG_DBG, "subjectaltnameregexp"); - - loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); - if (loc < 0) - return r; - - ex = X509_get_ext(cert, loc); - alt = X509V3_EXT_d2i(ex); - if (!alt) - return r; - - n = sk_GENERAL_NAME_num(alt); - for (i = 0; i < n; i++) { - gn = sk_GENERAL_NAME_value(alt, i); - if (gn->type != type) - continue; - r = -1; - v = (char *)ASN1_STRING_data(gn->d.ia5); - l = ASN1_STRING_length(gn->d.ia5); - if (l <= 0) - continue; -#ifdef DEBUG - printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l); -#endif - if (exact) { - if (memcmp(v, exact, l)) - continue; - } else { - s = stringcopy((char *)v, l); - if (!s) { - debug(DBG_ERR, "malloc failed"); - continue; - } - if (regexec(regex, s, 0, NULL, 0)) { - free(s); - continue; - } - free(s); - } - r = 1; - break; - } - GENERAL_NAMES_free(alt); - return r; -} - -X509 *verifytlscert(SSL *ssl) { - X509 *cert; - unsigned long error; - - if (SSL_get_verify_result(ssl) != X509_V_OK) { - debug(DBG_ERR, "verifytlscert: basic validation failed"); - while ((error = ERR_get_error())) - debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL)); - return NULL; - } - - cert = SSL_get_peer_certificate(ssl); - if (!cert) - debug(DBG_ERR, "verifytlscert: failed to obtain certificate"); - return cert; -} - -int verifyconfcert(X509 *cert, struct clsrvconf *conf) { - int r; - uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */ - struct in6_addr addr; - - if (conf->certnamecheck && conf->prefixlen == 255) { - if (inet_pton(AF_INET, conf->host, &addr)) - type = AF_INET; - else if (inet_pton(AF_INET6, conf->host, &addr)) - type = AF_INET6; - - r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, conf->host, NULL); - if (r) { - if (r < 0) { - debug(DBG_WARN, "verifyconfcert: No subjectaltname matching %s %s", type ? "address" : "host", conf->host); - return 0; - } - debug(DBG_DBG, "verifyconfcert: Found subjectaltname matching %s %s", type ? "address" : "host", conf->host); - } else { - if (!cnregexp(cert, conf->host, NULL)) { - debug(DBG_WARN, "verifyconfcert: cn not matching host %s", conf->host); - return 0; - } - debug(DBG_DBG, "verifyconfcert: Found cn matching host %s", conf->host); - } - } - if (conf->certcnregex) { - if (cnregexp(cert, NULL, conf->certcnregex) < 1) { - debug(DBG_WARN, "verifyconfcert: CN not matching regex"); - return 0; - } - debug(DBG_DBG, "verifyconfcert: CN matching regex"); - } - if (conf->certuriregex) { - if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) { - debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex"); - return 0; - } - debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex"); - } - return 1; -} - unsigned char *attrget(unsigned char *attrs, int length, uint8_t type) { while (length > 1) { if (ATTRTYPE(attrs) == type) @@ -1008,7 +501,7 @@ void sendrq(struct request *rq) { goto errexit; } - debug(DBG_DBG, "sendrq: inserting packet with id %d in queue for %s", i, to->conf->host); + debug(DBG_DBG, "sendrq: inserting packet with id %d in queue for %s", i, to->conf->name); to->requests[i].rq = rq; pthread_mutex_unlock(to->requests[i].lock); if (i >= start) /* i is not reserved for statusserver */ @@ -1296,6 +789,15 @@ struct realm *id2realm(struct list *realmlist, char *id) { return NULL; } +int hasdynamicserver(struct list *srvconfs) { + struct list_node *entry; + + for (entry = list_first(srvconfs); entry; entry = list_next(entry)) + if (((struct clsrvconf *)entry->data)->dynamiclookupcommand) + return 1; + return 0; +} + /* helper function, only used by removeserversubrealms() */ void _internal_removeserversubrealms(struct list *realmlist, struct clsrvconf *srv) { struct list_node *entry, *entry2; @@ -1311,26 +813,26 @@ void _internal_removeserversubrealms(struct list *realmlist, struct clsrvconf *s if (entry2->data == srv) freerealm(realm); list_removedata(realm->srvconfs, srv); - if (!list_first(realm->srvconfs)) { - list_destroy(realm->srvconfs); - realm->srvconfs = NULL; - } } if (realm->accsrvconfs) { for (entry2 = list_first(realm->accsrvconfs); entry2; entry2 = list_next(entry2)) if (entry2->data == srv) freerealm(realm); list_removedata(realm->accsrvconfs, srv); - if (!list_first(realm->accsrvconfs)) { - list_destroy(realm->accsrvconfs); - realm->accsrvconfs = NULL; - } } - /* remove subrealm if no servers */ - if (!realm->srvconfs && !realm->accsrvconfs) + /* remove subrealm if no dynamic servers left */ + if (!hasdynamicserver(realm->srvconfs) && !hasdynamicserver(realm->accsrvconfs)) { + while (list_shift(realm->srvconfs)) + freerealm(realm); + list_destroy(realm->srvconfs); + realm->srvconfs = NULL; + while (list_shift(realm->accsrvconfs)) + freerealm(realm); + list_destroy(realm->accsrvconfs); + realm->accsrvconfs = NULL; list_removedata(realmlist, realm); - + } pthread_mutex_unlock(&realm->mutex); freerealm(realm); } @@ -1418,7 +920,7 @@ int msmppe(unsigned char *attrs, int length, uint8_t type, char *attrtxt, struct return 1; } -int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint8_t subattr) { +int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint32_t subattr) { if (!attrs) return 0; @@ -1444,7 +946,7 @@ int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { if (!*removevendorattrs) return 0; - if (findvendorsubattr(removevendorattrs, vendor, -1)) + if (findvendorsubattr(removevendorattrs, vendor, 256)) return 1; /* remove entire vendor attribute */ sublen = attr->l - 4; @@ -1636,7 +1138,7 @@ void addttlattr(struct radmsg *msg, uint32_t *attrtype, uint8_t addttl) { memset(ttl, 0, 4); ttl[3] = addttl; - if (attrtype[1] == -1) { /* not vendor */ + if (attrtype[1] == 256) { /* not vendor */ attr = maketlv(attrtype[0], 4, ttl); if (attr && !radmsg_add(msg, attr)) freetlv(attr); @@ -1677,7 +1179,7 @@ int checkttl(struct radmsg *msg, uint32_t *attrtype) { uint32_t vendor; int sublen; - if (attrtype[1] == -1) { /* not vendor */ + if (attrtype[1] == 256) { /* not vendor */ attr = radmsg_gettype(msg, attrtype[0]); if (attr) return decttl(attr->l, attr->v); @@ -1752,18 +1254,19 @@ uint8_t *radattr2ascii(struct tlv *attr) { return a; } -void acclog(struct radmsg *msg, char *host) { +void acclog(struct radmsg *msg, struct client *from) { struct tlv *attr; uint8_t *username; - + attr = radmsg_gettype(msg, RAD_Attr_User_Name); if (!attr) { - debug(DBG_INFO, "acclog: accounting-request from %s without username attribute", host); + debug(DBG_INFO, "acclog: accounting-request from client %s (%s) without username attribute", from->conf->name, addr2string(from->addr)); return; } username = radattr2ascii(attr); if (username) { - debug(DBG_INFO, "acclog: accounting-request from %s with username: %s", host, username); + debug(DBG_INFO, "acclog: accounting-request from client %s (%s) with username: %s", from->conf->name, addr2string(from->addr), username); + free(username); } } @@ -1789,7 +1292,7 @@ void respond(struct request *rq, uint8_t code, char *message) { radmsg_free(rq->msg); rq->msg = msg; - debug(DBG_DBG, "respond: sending %s to %s", radmsgtype2string(msg->code), rq->from->conf->host); + debug(DBG_DBG, "respond: sending %s to %s (%s)", radmsgtype2string(msg->code), rq->from->conf->name, addr2string(rq->from->addr)); sendreply(newrqref(rq)); } @@ -1803,7 +1306,7 @@ struct clsrvconf *choosesrvconf(struct list *srvconfs) { return server; if (!first) first = server; - if (!server->servers->connectionok) + if (!server->servers->connectionok && !server->servers->dynstartup) continue; if (!server->servers->lostrqs) return server; @@ -1832,19 +1335,19 @@ struct server *findserver(struct realm **realm, struct tlv *username, uint8_t ac goto exit; debug(DBG_DBG, "found matching realm: %s", (*realm)->name); srvconf = choosesrvconf(acc ? (*realm)->accsrvconfs : (*realm)->srvconfs); - if (!srvconf) - goto exit; - server = srvconf->servers; - if (!acc && !(*realm)->parent && !srvconf->servers) { - subrealm = adddynamicrealmserver(*realm, srvconf, id); + if (srvconf && !(*realm)->parent && !srvconf->servers && srvconf->dynamiclookupcommand) { + subrealm = adddynamicrealmserver(*realm, id); if (subrealm) { pthread_mutex_lock(&subrealm->mutex); pthread_mutex_unlock(&(*realm)->mutex); freerealm(*realm); *realm = subrealm; - server = ((struct clsrvconf *)subrealm->srvconfs->first->data)->servers; + srvconf = choosesrvconf(acc ? (*realm)->accsrvconfs : (*realm)->srvconfs); } } + if (srvconf) + server = srvconf->servers; + exit: free(id); return server; @@ -1950,7 +1453,7 @@ int radsrv(struct request *rq) { attr = radmsg_gettype(msg, RAD_Attr_User_Name); if (!attr) { if (msg->code == RAD_Accounting_Request) { - acclog(msg, from->conf->host); + acclog(msg, from); respond(rq, RAD_Accounting_Response, NULL); } else debug(DBG_WARN, "radsrv: ignoring access request, no username attribute"); @@ -1976,10 +1479,10 @@ int radsrv(struct request *rq) { if (!to) { if (realm->message && msg->code == RAD_Access_Request) { - debug(DBG_INFO, "radsrv: sending reject to %s for %s", from->conf->host, userascii); + debug(DBG_INFO, "radsrv: sending reject to %s (%s) for %s", from->conf->name, addr2string(from->addr), userascii); respond(rq, RAD_Access_Reject, realm->message); } else if (realm->accresp && msg->code == RAD_Accounting_Request) { - acclog(msg, from->conf->host); + acclog(msg, from); respond(rq, RAD_Accounting_Response, NULL); } goto exit; @@ -2081,7 +1584,7 @@ void replyh(struct server *server, unsigned char *buf) { if (rqout->rq->msg->code == RAD_Status_Server) { freerqoutdata(rqout); - debug(DBG_DBG, "replyh: got status server response from %s", server->conf->host); + debug(DBG_DBG, "replyh: got status server response from %s", server->conf->name); goto errunlock; } @@ -2095,7 +1598,7 @@ void replyh(struct server *server, unsigned char *buf) { ttlres = checkttl(msg, options.ttlattrtype); if (!ttlres) { - debug(DBG_WARN, "replyh: ignoring reply from server %s, ttl exceeded", server->conf->host); + debug(DBG_WARN, "replyh: ignoring reply from server %s, ttl exceeded", server->conf->name); goto errunlock; } @@ -2131,20 +1634,20 @@ void replyh(struct server *server, unsigned char *buf) { if (stationid) { if (replymsg) { debug(DBG_INFO, "%s for user %s stationid %s from %s (%s)", - radmsgtype2string(msg->code), username, stationid, server->conf->host, replymsg); + radmsgtype2string(msg->code), username, stationid, server->conf->name, replymsg); free(replymsg); } else debug(DBG_INFO, "%s for user %s stationid %s from %s", - radmsgtype2string(msg->code), username, stationid, server->conf->host); + radmsgtype2string(msg->code), username, stationid, server->conf->name); free(stationid); } else { if (replymsg) { debug(DBG_INFO, "%s for user %s from %s (%s)", - radmsgtype2string(msg->code), username, server->conf->host, replymsg); + radmsgtype2string(msg->code), username, server->conf->name, replymsg); free(replymsg); } else debug(DBG_INFO, "%s for user %s from %s", - radmsgtype2string(msg->code), username, server->conf->host); + radmsgtype2string(msg->code), username, server->conf->name); } free(username); } @@ -2228,11 +1731,15 @@ void *clientwr(void *arg) { if (server->dynamiclookuparg && !dynamicconfig(server)) { dynconffail = 1; + server->dynstartup = 0; + sleep(900); goto errexit; } - if (!conf->addrinfo && !resolvepeer(conf, 0)) { - debug(DBG_WARN, "failed to resolve host %s port %s", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)"); + if (!resolvehostports(conf->hostports, conf->pdef->socktype)) { + debug(DBG_WARN, "clientwr: resolve failed"); + server->dynstartup = 0; + sleep(900); goto errexit; } @@ -2244,8 +1751,13 @@ void *clientwr(void *arg) { } if (conf->pdef->connecter) { - if (!conf->pdef->connecter(server, NULL, server->dynamiclookuparg ? 6 : 0, "clientwr")) + if (!conf->pdef->connecter(server, NULL, server->dynamiclookuparg ? 5 : 0, "clientwr")) { + if (server->dynamiclookuparg) { + server->dynstartup = 0; + sleep(900); + } goto errexit; + } server->connectionok = 1; if (pthread_create(&clientrdth, NULL, conf->pdef->clientconnreader, (void *)server)) { debug(DBG_ERR, "clientwr: pthread_create failed"); @@ -2253,6 +1765,7 @@ void *clientwr(void *arg) { } } else server->connectionok = 1; + server->dynstartup = 0; for (;;) { pthread_mutex_lock(&server->newrq_mutex); @@ -2319,12 +1832,12 @@ void *clientwr(void *arg) { debug(DBG_DBG, "clientwr: removing expired packet from queue"); if (conf->statusserver) { if (*rqout->rq->buf == RAD_Status_Server) { - debug(DBG_WARN, "clientwr: no status server response, %s dead?", conf->host); + debug(DBG_WARN, "clientwr: no status server response, %s dead?", conf->name); if (server->lostrqs < 255) server->lostrqs++; } } else { - debug(DBG_WARN, "clientwr: no server response, %s dead?", conf->host); + debug(DBG_WARN, "clientwr: no server response, %s dead?", conf->name); if (server->lostrqs < 255) server->lostrqs++; } @@ -2348,7 +1861,7 @@ void *clientwr(void *arg) { statsrvrq = createstatsrvrq(); if (statsrvrq) { statsrvrq->to = server; - debug(DBG_DBG, "clientwr: sending status server to %s", conf->host); + debug(DBG_DBG, "clientwr: sending status server to %s", conf->name); sendrq(statsrvrq); } } @@ -2370,15 +1883,14 @@ void *clientwr(void *arg) { void createlistener(uint8_t type, char *arg) { pthread_t th; - struct clsrvconf *listenres; struct addrinfo *res; int s = -1, on = 1, *sp = NULL; + struct hostportres *hp = newhostport(arg, protodefs[type]->portdefault, 0); - listenres = resolve_hostport(type, arg, protodefs[type].portdefault); - if (!listenres) + if (!hp || !resolvehostport(hp, protodefs[type]->socktype, 1)) debugx(1, DBG_ERR, "createlistener: failed to resolve %s", arg); - for (res = listenres->addrinfo; res; res = res->ai_next) { + for (res = hp->addrinfo; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) { debug(DBG_WARN, "createlistener: socket failed"); @@ -2388,7 +1900,7 @@ void createlistener(uint8_t type, char *arg) { #ifdef IPV6_V6ONLY if (res->ai_family == AF_INET6) setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); -#endif +#endif if (bind(s, res->ai_addr, res->ai_addrlen)) { debug(DBG_WARN, "createlistener: bind failed"); close(s); @@ -2400,21 +1912,22 @@ 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, NULL, protodefs[type]->listener, (void *)sp)) debugx(1, DBG_ERR, "pthread_create failed"); pthread_detach(th); } if (!sp) debugx(1, DBG_ERR, "createlistener: socket/bind failed"); - debug(DBG_WARN, "createlistener: listening for %s on %s:%s", protodefs[type].name, - listenres->host ? listenres->host : "*", listenres->port); - freeclsrvres(listenres); + debug(DBG_WARN, "createlistener: listening for %s on %s:%s", protodefs[type]->name, hp->host ? hp->host : "*", hp->port); + freehostport(hp); } -void createlisteners(uint8_t type, char **args) { +void createlisteners(uint8_t type) { int i; + char **args; + args = protodefs[type]->getlistenerargs(); if (args) for (i = 0; args[i]; i++) createlistener(type, args[i]); @@ -2422,36 +1935,7 @@ void createlisteners(uint8_t type, char **args) { createlistener(type, NULL); } -#ifdef DEBUG -void ssl_info_callback(const SSL *ssl, int where, int ret) { - const char *s; - int w; - - w = where & ~SSL_ST_MASK; - - if (w & SSL_ST_CONNECT) - s = "SSL_connect"; - else if (w & SSL_ST_ACCEPT) - s = "SSL_accept"; - else - s = "undefined"; - - if (where & SSL_CB_LOOP) - debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl)); - else if (where & SSL_CB_ALERT) { - s = (where & SSL_CB_READ) ? "read" : "write"; - debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); - } - else if (where & SSL_CB_EXIT) { - if (ret == 0) - debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl)); - else if (ret < 0) - debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl)); - } -} -#endif - -void tlsinit() { +void sslinit() { int i; time_t t; pid_t pid; @@ -2476,187 +1960,6 @@ void tlsinit() { } } -X509_VERIFY_PARAM *createverifyparams(char **poids) { - X509_VERIFY_PARAM *pm; - ASN1_OBJECT *pobject; - int i; - - pm = X509_VERIFY_PARAM_new(); - if (!pm) - return NULL; - - for (i = 0; poids[i]; i++) { - pobject = OBJ_txt2obj(poids[i], 0); - if (!pobject) { - X509_VERIFY_PARAM_free(pm); - return NULL; - } - X509_VERIFY_PARAM_add0_policy(pm, pobject); - } - - X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY); - return pm; -} - -int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) { - STACK_OF(X509_NAME) *calist; - X509_STORE *x509_s; - unsigned long error; - - if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) { - while ((error = ERR_get_error())) - debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); - debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name); - return 0; - } - - calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL; - if (!conf->cacertfile || calist) { - if (conf->cacertpath) { - if (!calist) - calist = sk_X509_NAME_new_null(); - if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) { - sk_X509_NAME_free(calist); - calist = NULL; - } - } - } - if (!calist) { - while ((error = ERR_get_error())) - debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); - debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name); - return 0; - } - ERR_clear_error(); /* add_dir_cert_subj returns errors on success */ - SSL_CTX_set_client_CA_list(ctx, calist); - - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); - SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1); - - if (conf->crlcheck || conf->vpm) { - x509_s = SSL_CTX_get_cert_store(ctx); - if (conf->crlcheck) - X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); - if (conf->vpm) - X509_STORE_set1_param(x509_s, conf->vpm); - } - - debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name); - return 1; -} - -SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { - SSL_CTX *ctx = NULL; - unsigned long error; - - if (!ssl_locks) - tlsinit(); - - switch (type) { - case RAD_TLS: - ctx = SSL_CTX_new(TLSv1_method()); -#ifdef DEBUG - SSL_CTX_set_info_callback(ctx, ssl_info_callback); -#endif - break; - case RAD_DTLS: - ctx = SSL_CTX_new(DTLSv1_method()); -#ifdef DEBUG - SSL_CTX_set_info_callback(ctx, ssl_info_callback); -#endif - SSL_CTX_set_read_ahead(ctx, 1); - break; - } - if (!ctx) { - debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); - return NULL; - } - - if (conf->certkeypwd) { - SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd); - SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); - } - if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) || - !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) || - !SSL_CTX_check_private_key(ctx)) { - while ((error = ERR_get_error())) - debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); - debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); - SSL_CTX_free(ctx); - return NULL; - } - - if (conf->policyoids) { - if (!conf->vpm) { - conf->vpm = createverifyparams(conf->policyoids); - if (!conf->vpm) { - debug(DBG_ERR, "tlsaddcacrl: Failed to add policyOIDs in TLS context %s", conf->name); - SSL_CTX_free(ctx); - return NULL; - } - } - } - - if (!tlsaddcacrl(ctx, conf)) { - if (conf->vpm) { - X509_VERIFY_PARAM_free(conf->vpm); - conf->vpm = NULL; - } - SSL_CTX_free(ctx); - return NULL; - } - - debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name); - return ctx; -} - -struct tls *tlsgettls(char *alt1, char *alt2) { - struct tls *t; - - t = hash_read(tlsconfs, alt1, strlen(alt1)); - if (!t) - t = hash_read(tlsconfs, alt2, strlen(alt2)); - return t; -} - -SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) { - struct timeval now; - - if (!t) - return NULL; - gettimeofday(&now, NULL); - - switch (type) { - case RAD_TLS: - if (t->tlsexpiry && t->tlsctx) { - if (t->tlsexpiry < now.tv_sec) { - t->tlsexpiry = now.tv_sec + t->cacheexpiry; - tlsaddcacrl(t->tlsctx, t); - } - } - if (!t->tlsctx) { - t->tlsctx = tlscreatectx(RAD_TLS, t); - if (t->cacheexpiry) - t->tlsexpiry = now.tv_sec + t->cacheexpiry; - } - return t->tlsctx; - case RAD_DTLS: - if (t->dtlsexpiry && t->dtlsctx) { - if (t->dtlsexpiry < now.tv_sec) { - t->dtlsexpiry = now.tv_sec + t->cacheexpiry; - tlsaddcacrl(t->dtlsctx, t); - } - } - if (!t->dtlsctx) { - t->dtlsctx = tlscreatectx(RAD_DTLS, t); - if (t->cacheexpiry) - t->dtlsexpiry = now.tv_sec + t->cacheexpiry; - } - return t->dtlsctx; - } - return NULL; -} - struct list *addsrvconfs(char *value, char **names) { struct list *conflist; int n; @@ -2825,15 +2128,53 @@ struct realm *addrealm(struct list *realmlist, char *value, char **servers, char return newrealmref(realm); } -struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf, char *id) { - struct clsrvconf *srvconf; +struct list *createsubrealmservers(struct realm *realm, struct list *srvconfs) { + struct list_node *entry; + struct clsrvconf *conf, *srvconf; + struct list *subrealmservers = NULL; + pthread_t clientth; + + if (list_first(srvconfs)) { + subrealmservers = list_create(); + if (!subrealmservers) + return NULL; + } + + for (entry = list_first(srvconfs); entry; entry = list_next(entry)) { + conf = (struct clsrvconf *)entry->data; + if (!conf->servers && conf->dynamiclookupcommand) { + srvconf = malloc(sizeof(struct clsrvconf)); + if (!srvconf) { + debug(DBG_ERR, "malloc failed"); + continue; + } + *srvconf = *conf; + if (addserver(srvconf)) { + srvconf->servers->dynamiclookuparg = stringcopy(realm->name, 0); + srvconf->servers->dynstartup = 1; + if (pthread_create(&clientth, NULL, clientwr, (void *)(srvconf->servers))) { + debug(DBG_ERR, "pthread_create failed"); + freeserver(srvconf->servers, 1); + srvconf->servers = NULL; + } else + pthread_detach(clientth); + } + conf = srvconf; + } + if (conf->servers) { + if (list_push(subrealmservers, conf)) + newrealmref(realm); + else + debug(DBG_ERR, "malloc failed"); + } + } + return subrealmservers; +} + +struct realm *adddynamicrealmserver(struct realm *realm, char *id) { struct realm *newrealm = NULL; char *realmname, *s; - pthread_t clientth; - if (!conf->dynamiclookupcommand) - return NULL; - /* create dynamic for the realm (string after last @, exit if nothing after @ */ realmname = strrchr(id, '@'); if (!realmname) @@ -2845,65 +2186,23 @@ struct realm *adddynamicrealmserver(struct realm *realm, struct clsrvconf *conf, if (*s != '.' && *s != '-' && !isalnum((int)*s)) return NULL; - srvconf = malloc(sizeof(struct clsrvconf)); - if (!srvconf) { - debug(DBG_ERR, "malloc failed"); - return NULL; - } - *srvconf = *conf; - if (!addserver(srvconf)) - goto errexit; - if (!realm->subrealms) realm->subrealms = list_create(); if (!realm->subrealms) - goto errexit; - newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, NULL, 0); - if (!newrealm) - goto errexit; - newrealm->parent = newrealmref(realm); + return NULL; - /* add server and accserver to newrealm */ - newrealm->srvconfs = list_create(); - if (!newrealm->srvconfs || !list_push(newrealm->srvconfs, srvconf)) { - debug(DBG_ERR, "malloc failed"); - goto errexit; - } - newrealmref(newrealm); - newrealm->accsrvconfs = list_create(); - if (!newrealm->accsrvconfs || !list_push(newrealm->accsrvconfs, srvconf)) { - debug(DBG_ERR, "malloc failed"); - list_shift(realm->srvconfs); - freerealm(newrealm); - goto errexit; + newrealm = addrealm(realm->subrealms, realmname, NULL, NULL, stringcopy(realm->message, 0), realm->accresp); + if (!newrealm) { + list_destroy(realm->subrealms); + realm->subrealms = NULL; + return NULL; } - newrealmref(newrealm); - srvconf->servers->dynamiclookuparg = stringcopy(realmname, 0); - if (pthread_create(&clientth, NULL, clientwr, (void *)(srvconf->servers))) { - debug(DBG_ERR, "pthread_create failed"); - list_shift(realm->srvconfs); - freerealm(newrealm); - list_shift(realm->accsrvconfs); - freerealm(newrealm); - goto errexit; - } - pthread_detach(clientth); + newrealm->parent = newrealmref(realm); + /* add server and accserver to newrealm */ + newrealm->srvconfs = createsubrealmservers(newrealm, realm->srvconfs); + newrealm->accsrvconfs = createsubrealmservers(newrealm, realm->accsrvconfs); return newrealm; - - errexit: - if (newrealm) { - list_removedata(realm->subrealms, newrealm); - freerealm(newrealm); - if (!list_first(realm->subrealms)) { - list_destroy(realm->subrealms); - realm->subrealms = NULL; - } - } - freeserver(srvconf->servers, 1); - free(srvconf); - debug(DBG_ERR, "failed to create dynamic server"); - return NULL; } int dynamicconfig(struct server *server) { @@ -2963,40 +2262,6 @@ int dynamicconfig(struct server *server) { return 0; } -int addmatchcertattr(struct clsrvconf *conf) { - char *v; - regex_t **r; - - if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) { - r = &conf->certcnregex; - v = conf->matchcertattr + 4; - } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) { - r = &conf->certuriregex; - v = conf->matchcertattr + 20; - } else - return 0; - if (!*v) - return 0; - /* regexp, remove optional trailing / if present */ - if (v[strlen(v) - 1] == '/') - v[strlen(v) - 1] = '\0'; - if (!*v) - return 0; - - *r = malloc(sizeof(regex_t)); - if (!*r) { - debug(DBG_ERR, "malloc failed"); - return 0; - } - if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { - free(*r); - *r = NULL; - debug(DBG_ERR, "failed to compile regular expression %s", v); - return 0; - } - return 1; -} - /* should accept both names and numeric values, only numeric right now */ uint8_t attrname2val(char *attrname) { int val = 0; @@ -3012,11 +2277,11 @@ int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type) { *vendor = atoi(attrname); s = strchr(attrname, ':'); if (!s) { - *type = -1; + *type = 256; return 1; } *type = atoi(s + 1); - return *type >= 0 && *type < 256; + return *type < 256; } /* should accept both names and numeric values, only numeric right now */ @@ -3196,7 +2461,7 @@ int setttlattr(struct options *opts, char *defaultattr) { char *ttlattr = opts->ttlattr ? opts->ttlattr : defaultattr; if (vattrname2val(ttlattr, opts->ttlattrtype, opts->ttlattrtype + 1) && - (opts->ttlattrtype[1] != -1 || opts->ttlattrtype[0] < 256)) + (opts->ttlattrtype[1] != 256 || opts->ttlattrtype[0] < 256)) return 1; debug(DBG_ERR, "setttlattr: invalid TTLAttribute value %s", ttlattr); return 0; @@ -3204,8 +2469,9 @@ int setttlattr(struct options *opts, char *defaultattr) { void freeclsrvconf(struct clsrvconf *conf) { free(conf->name); - free(conf->host); - free(conf->port); + if (conf->hostsrc) + freegconfmstr(conf->hostsrc); + free(conf->portsrc); free(conf->secret); free(conf->tls); free(conf->matchcertattr); @@ -3224,8 +2490,8 @@ void freeclsrvconf(struct clsrvconf *conf) { free(conf->dynamiclookupcommand); free(conf->rewritein); free(conf->rewriteout); - if (conf->addrinfo) - freeaddrinfo(conf->addrinfo); + if (conf->hostports) + freehostports(conf->hostports); if (conf->lock) { pthread_mutex_destroy(conf->lock); free(conf->lock); @@ -3253,11 +2519,52 @@ int mergeconfstring(char **dst, char **src) { return 1; } +char **mstringcopy(char **in) { + char **out; + int n; + + if (!in) + return NULL; + + for (n = 0; in[n]; n++); + out = malloc((n + 1) * sizeof(char *)); + if (!out) + return NULL; + for (n = 0; in[n]; n++) { + out[n] = stringcopy(in[n], 0); + if (!out[n]) { + freegconfmstr(out); + return NULL; + } + } + out[n] = NULL; + return out; +} + +int mergeconfmstring(char ***dst, char ***src) { + char **t; + + if (*src) { + *dst = *src; + *src = NULL; + return 1; + } + if (*dst) { + t = mstringcopy(*dst); + if (!t) { + debug(DBG_ERR, "malloc failed"); + return 0; + } + *dst = t; + } + return 1; +} + /* assumes dst is a shallow copy */ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) { if (!mergeconfstring(&dst->name, &src->name) || - !mergeconfstring(&dst->host, &src->host) || - !mergeconfstring(&dst->port, &src->port) || + !mergeconfmstring(&dst->hostsrc, &src->hostsrc) || + !mergeconfstring(&dst->portsrc, &src->portsrc) || !mergeconfstring(&dst->secret, &src->secret) || !mergeconfstring(&dst->tls, &src->tls) || !mergeconfstring(&dst->matchcertattr, &src->matchcertattr) || @@ -3291,11 +2598,13 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char if (!getgenericconfig(cf, block, "type", CONF_STR, &conftype, - "host", CONF_STR, &conf->host, + "host", CONF_MSTR, &conf->hostsrc, "secret", CONF_STR, &conf->secret, +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, "matchcertificateattribute", CONF_STR, &conf->matchcertattr, "CertificateNameCheck", CONF_BLN, &conf->certnamecheck, +#endif "DuplicateInterval", CONF_LINT, &dupinterval, "addTTL", CONF_LINT, &addttl, "rewrite", CONF_STR, &rewriteinalias, @@ -3307,19 +2616,25 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char debugx(1, DBG_ERR, "configuration error"); conf->name = stringcopy(val, 0); - if (!conf->host) - conf->host = stringcopy(val, 0); - if (!conf->name || !conf->host) + if (conf->name && !conf->hostsrc) { + conf->hostsrc = malloc(2 * sizeof(char *)); + if (conf->hostsrc) { + conf->hostsrc[0] = stringcopy(val, 0); + conf->hostsrc[1] = NULL; + } + } + if (!conf->name || !conf->hostsrc || !conf->hostsrc[0]) debugx(1, DBG_ERR, "malloc failed"); if (!conftype) debugx(1, DBG_ERR, "error in block %s, option type missing", block); conf->type = protoname2int(conftype); - conf->pdef = &protodefs[conf->type]; - if (!conf->pdef->name) + if (conf->type == 255) debugx(1, DBG_ERR, "error in block %s, unknown transport %s", block, conftype); free(conftype); - + conf->pdef = protodefs[conf->type]; + +#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"); if (!conf->tlsconf) @@ -3327,6 +2642,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char if (conf->matchcertattr && !addmatchcertattr(conf)) debugx(1, DBG_ERR, "error in block %s, invalid MatchCertificateAttributeValue", block); } +#endif if (dupinterval != LONG_MIN) { if (dupinterval < 0 || dupinterval > 255) @@ -3354,9 +2670,10 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char if (!conf->rewriteusername) debugx(1, DBG_ERR, "error in block %s, invalid RewriteAttributeValue", block); } - - if (!resolvepeer(conf, 0)) - debugx(1, DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)"); + + if (!addhostport(&conf->hostports, conf->hostsrc, conf->pdef->portdefault, 1) || + !resolvehostports(conf->hostports, conf->pdef->socktype)) + debugx(1, DBG_ERR, "resolve failed, exiting"); if (!conf->secret) { if (!conf->pdef->secretdefault) @@ -3377,6 +2694,7 @@ 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"); if (!conf->tlsconf) { @@ -3388,38 +2706,32 @@ int compileserverconfig(struct clsrvconf *conf, const char *block) { return 0; } } - - if (!conf->port) { - conf->port = stringcopy(conf->pdef->portdefault, 0); - if (!conf->port) { +#endif + + if (!conf->portsrc) { + conf->portsrc = stringcopy(conf->pdef->portdefault, 0); + if (!conf->portsrc) { debug(DBG_ERR, "malloc failed"); return 0; } } if (conf->retryinterval == 255) - conf->retryinterval = protodefs[conf->type].retryintervaldefault; + conf->retryinterval = conf->pdef->retryintervaldefault; if (conf->retrycount == 255) - conf->retrycount = protodefs[conf->type].retrycountdefault; + conf->retrycount = conf->pdef->retrycountdefault; conf->rewritein = conf->confrewritein ? getrewrite(conf->confrewritein, NULL) : getrewrite("defaultserver", "default"); if (conf->confrewriteout) conf->rewriteout = getrewrite(conf->confrewriteout, NULL); - - if (!conf->secret) { - if (!conf->pdef->secretdefault) { - debug(DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name); - return 0; - } - conf->secret = stringcopy(conf->pdef->secretdefault, 0); - if (!conf->secret) { - debug(DBG_ERR, "malloc failed"); - return 0; - } - } - if (!conf->dynamiclookupcommand && !resolvepeer(conf, 0)) { - debug(DBG_ERR, "failed to resolve host %s port %s, exiting", conf->host ? conf->host : "(null)", conf->port ? conf->port : "(null)"); + if (!addhostport(&conf->hostports, conf->hostsrc, conf->portsrc, 0)) { + debug(DBG_ERR, "error in block %s, failed to parse %s", block, conf->hostsrc); + return 0; + } + + if (!conf->dynamiclookupcommand && !resolvehostports(conf->hostports, conf->pdef->socktype)) { + debug(DBG_ERR, "resolve failed, exiting"); return 0; } return 1; @@ -3447,11 +2759,14 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char if (!getgenericconfig(cf, block, "type", CONF_STR, &conftype, - "host", CONF_STR, &conf->host, - "port", CONF_STR, &conf->port, + "host", CONF_MSTR, &conf->hostsrc, + "port", CONF_STR, &conf->portsrc, "secret", CONF_STR, &conf->secret, +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, "MatchCertificateAttribute", CONF_STR, &conf->matchcertattr, + "CertificateNameCheck", CONF_BLN, &conf->certnamecheck, +#endif "addTTL", CONF_LINT, &addttl, "rewrite", CONF_STR, &rewriteinalias, "rewriteIn", CONF_STR, &conf->confrewritein, @@ -3459,7 +2774,6 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char "StatusServer", CONF_BLN, &conf->statusserver, "RetryInterval", CONF_LINT, &retryinterval, "RetryCount", CONF_LINT, &retrycount, - "CertificateNameCheck", CONF_BLN, &conf->certnamecheck, "DynamicLookupCommand", CONF_STR, &conf->dynamiclookupcommand, NULL )) { @@ -3468,28 +2782,31 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char } conf->name = stringcopy(val, 0); - if (!conf->name) { + if (conf->name && !conf->hostsrc) { + conf->hostsrc = malloc(2 * sizeof(char *)); + if (conf->hostsrc) { + conf->hostsrc[0] = stringcopy(val, 0); + conf->hostsrc[1] = NULL; + } + } + if (!conf->name || !conf->hostsrc || !conf->hostsrc[0]) { debug(DBG_ERR, "malloc failed"); goto errexit; } - if (!conf->host) { - conf->host = stringcopy(val, 0); - if (!conf->host) { - debug(DBG_ERR, "malloc failed"); - goto errexit; - } - } - if (!conftype) - debugx(1, DBG_ERR, "error in block %s, option type missing", block); + if (!conftype) { + debug(DBG_ERR, "error in block %s, option type missing", block); + goto errexit; + } conf->type = protoname2int(conftype); - conf->pdef = &protodefs[conf->type]; - if (!conf->pdef->name) { + 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]; if (!conf->confrewritein) conf->confrewritein = rewriteinalias; @@ -3539,9 +2856,21 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char goto errexit; } + if (!conf->secret) { + if (!conf->pdef->secretdefault) { + debug(DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name); + return 0; + } + conf->secret = stringcopy(conf->pdef->secretdefault, 0); + if (!conf->secret) { + debug(DBG_ERR, "malloc failed"); + return 0; + } + } + if (resconf) return 1; - + if (!list_push(srvconfs, conf)) { debug(DBG_ERR, "malloc failed"); goto errexit; @@ -3574,75 +2903,6 @@ int confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char return 1; } -int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { - struct tls *conf; - long int expiry = LONG_MIN; - - debug(DBG_DBG, "conftls_cb called for %s", block); - - conf = malloc(sizeof(struct tls)); - if (!conf) { - debug(DBG_ERR, "conftls_cb: malloc failed"); - return 0; - } - memset(conf, 0, sizeof(struct tls)); - - if (!getgenericconfig(cf, block, - "CACertificateFile", CONF_STR, &conf->cacertfile, - "CACertificatePath", CONF_STR, &conf->cacertpath, - "CertificateFile", CONF_STR, &conf->certfile, - "CertificateKeyFile", CONF_STR, &conf->certkeyfile, - "CertificateKeyPassword", CONF_STR, &conf->certkeypwd, - "CacheExpiry", CONF_LINT, &expiry, - "CRLCheck", CONF_BLN, &conf->crlcheck, - "PolicyOID", CONF_MSTR, &conf->policyoids, - NULL - )) { - debug(DBG_ERR, "conftls_cb: configuration error in block %s", val); - goto errexit; - } - if (!conf->certfile || !conf->certkeyfile) { - debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val); - goto errexit; - } - if (!conf->cacertfile && !conf->cacertpath) { - debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val); - goto errexit; - } - if (expiry != LONG_MIN) { - if (expiry < 0) { - debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry); - goto errexit; - } - conf->cacheexpiry = expiry; - } - - conf->name = stringcopy(val, 0); - if (!conf->name) { - debug(DBG_ERR, "conftls_cb: malloc failed"); - goto errexit; - } - - if (!hash_insert(tlsconfs, val, strlen(val), conf)) { - debug(DBG_ERR, "conftls_cb: malloc failed"); - goto errexit; - } - if (!tlsgetctx(RAD_TLS, conf)) - debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val); - debug(DBG_DBG, "conftls_cb: added TLS block %s", val); - return 1; - - errexit: - free(conf->cacertfile); - free(conf->cacertpath); - free(conf->certfile); - free(conf->certkeyfile); - free(conf->certkeypwd); - freegconfmstr(conf->policyoids); - free(conf); - return 0; -} - int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { char **rmattrs = NULL, **rmvattrs = NULL, **addattrs = NULL, **modattrs = NULL; @@ -3660,12 +2920,30 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha return 1; } +int setprotoopts(uint8_t type, char **listenargs, char *sourcearg) { + struct commonprotoopts *protoopts; + + protoopts = malloc(sizeof(struct commonprotoopts)); + if (!protoopts) + return 0; + memset(protoopts, 0, sizeof(struct commonprotoopts)); + protoopts->listenargs = listenargs; + protoopts->sourcearg = sourcearg; + protodefs[type]->setprotoopts(protoopts); + return 1; +} + void getmainconfig(const char *configfile) { long int addttl = LONG_MIN, loglevel = LONG_MIN; struct gconffile *cfs; - + char **listenargs[RAD_PROTOCOUNT]; + char *sourcearg[RAD_PROTOCOUNT]; + int i; + cfs = openconfigfile(configfile); memset(&options, 0, sizeof(options)); + memset(&listenargs, 0, sizeof(listenargs)); + memset(&sourcearg, 0, sizeof(sourcearg)); clconfs = list_create(); if (!clconfs) @@ -3679,23 +2957,27 @@ void getmainconfig(const char *configfile) { if (!realms) debugx(1, DBG_ERR, "malloc failed"); - tlsconfs = hash_create(); - if (!tlsconfs) - debugx(1, DBG_ERR, "malloc failed"); - rewriteconfs = hash_create(); if (!rewriteconfs) debugx(1, DBG_ERR, "malloc failed"); if (!getgenericconfig(&cfs, NULL, - "ListenUDP", CONF_MSTR, &options.listenargs[RAD_UDP], - "ListenTCP", CONF_MSTR, &options.listenargs[RAD_TCP], - "ListenTLS", CONF_MSTR, &options.listenargs[RAD_TLS], - "ListenDTLS", CONF_MSTR, &options.listenargs[RAD_DTLS], - "SourceUDP", CONF_STR, &options.sourcearg[RAD_UDP], - "SourceTCP", CONF_STR, &options.sourcearg[RAD_TCP], - "SourceTLS", CONF_STR, &options.sourcearg[RAD_TLS], - "SourceDTLS", CONF_STR, &options.sourcearg[RAD_DTLS], +#ifdef RADPROT_UDP + "ListenUDP", CONF_MSTR, &listenargs[RAD_UDP], + "SourceUDP", CONF_STR, &sourcearg[RAD_UDP], +#endif +#ifdef RADPROT_TCP + "ListenTCP", CONF_MSTR, &listenargs[RAD_TCP], + "SourceTCP", CONF_STR, &sourcearg[RAD_TCP], +#endif +#ifdef RADPROT_TLS + "ListenTLS", CONF_MSTR, &listenargs[RAD_TLS], + "SourceTLS", CONF_STR, &sourcearg[RAD_TLS], +#endif +#ifdef RADPROT_DTLS + "ListenDTLS", CONF_MSTR, &listenargs[RAD_DTLS], + "SourceDTLS", CONF_STR, &sourcearg[RAD_DTLS], +#endif "TTLAttribute", CONF_STR, &options.ttlattr, "addTTL", CONF_LINT, &addttl, "LogLevel", CONF_LINT, &loglevel, @@ -3704,7 +2986,9 @@ void getmainconfig(const char *configfile) { "Client", CONF_CBK, confclient_cb, NULL, "Server", CONF_CBK, confserver_cb, NULL, "Realm", CONF_CBK, confrealm_cb, NULL, +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "TLS", CONF_CBK, conftls_cb, NULL, +#endif "Rewrite", CONF_CBK, confrewrite_cb, NULL, NULL )) @@ -3722,12 +3006,16 @@ void getmainconfig(const char *configfile) { } if (!setttlattr(&options, DEFAULT_TTL_ATTR)) debugx(1, DBG_ERR, "Failed to set TTLAttribute, exiting"); + + for (i = 0; i < RAD_PROTOCOUNT; i++) + if (listenargs[i] || sourcearg[i]) + setprotoopts(i, listenargs[i], sourcearg[i]); } -void getargs(int argc, char **argv, uint8_t *foreground, uint8_t *pretend, uint8_t *loglevel, char **configfile) { +void getargs(int argc, char **argv, uint8_t *foreground, uint8_t *pretend, uint8_t *loglevel, char **configfile, char **pidfile) { int c; - while ((c = getopt(argc, argv, "c:d:fpv")) != -1) { + while ((c = getopt(argc, argv, "c:d:i:fpv")) != -1) { switch (c) { case 'c': *configfile = optarg; @@ -3740,11 +3028,28 @@ void getargs(int argc, char **argv, uint8_t *foreground, uint8_t *pretend, uint8 case 'f': *foreground = 1; break; + case 'i': + *pidfile = optarg; + break; case 'p': *pretend = 1; break; case 'v': - debugx(0, DBG_ERR, "radsecproxy 1.3-alpha"); + debug(DBG_ERR, "radsecproxy 1.3-beta"); + debug(DBG_ERR, "This binary was built with support for the following transports:"); +#ifdef RADPROT_UDP + debug(DBG_ERR, " UDP"); +#endif +#ifdef RADPROT_TCP + debug(DBG_ERR, " TCP"); +#endif +#ifdef RADPROT_TLS + debug(DBG_ERR, " TLS"); +#endif +#ifdef RADPROT_DTLS + debug(DBG_ERR, " DTLS"); +#endif + exit(0); default: goto usage; } @@ -3753,7 +3058,7 @@ void getargs(int argc, char **argv, uint8_t *foreground, uint8_t *pretend, uint8 return; usage: - debugx(1, DBG_ERR, "Usage:\n%s [ -c configfile ] [ -d debuglevel ] [ -f ] [ -p ] [ -v ]", argv[0]); + debugx(1, DBG_ERR, "Usage:\n%s [ -c configfile ] [ -d debuglevel ] [ -f ] [ -i pidfile ] [ -p ] [ -v ]", argv[0]); } #ifdef SYS_SOLARIS9 @@ -3779,13 +3084,17 @@ void *sighandler(void *arg) { for(;;) { sigemptyset(&sigset); + sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGPIPE); sigwait(&sigset, &sig); - /* only get SIGPIPE right now, so could simplify below code */ switch (sig) { case 0: /* completely ignoring this */ break; + case SIGHUP: + debug(DBG_INFO, "sighandler: got SIGHUP"); + debug_reopen_log(); + break; case SIGPIPE: debug(DBG_WARN, "sighandler: got SIGPIPE, TLS write error?"); break; @@ -3795,19 +3104,33 @@ void *sighandler(void *arg) { } } +int createpidfile(const char *pidfile) { + int r; + FILE *f = fopen(pidfile, "w"); + if (f) + r = fprintf(f, "%d\n", getpid()); + return f && !fclose(f) && r >= 0; +} + int main(int argc, char **argv) { pthread_t sigth; sigset_t sigset; struct list_node *entry; uint8_t foreground = 0, pretend = 0, loglevel = 0; - char *configfile = NULL; + char *configfile = NULL, *pidfile = NULL; struct clsrvconf *srvconf; int i; debug_init("radsecproxy"); debug_set_level(DEBUG_LEVEL); - - getargs(argc, argv, &foreground, &pretend, &loglevel, &configfile); + + for (i = 0; i < RAD_PROTOCOUNT; i++) + protodefs[i] = protoinits[i](i); + + /* needed even if no TLS/DTLS transport */ + sslinit(); + + getargs(argc, argv, &foreground, &pretend, &loglevel, &configfile, &pidfile); if (loglevel) debug_set_level(loglevel); getmainconfig(configfile ? configfile : CONFIG_MAIN); @@ -3831,15 +3154,17 @@ int main(int argc, char **argv) { debugx(1, DBG_ERR, "daemon() failed: %s", strerror(errno)); debug_timestamp_on(); - debug(DBG_INFO, "radsecproxy 1.3-alpha starting"); - + debug(DBG_INFO, "radsecproxy 1.3-beta starting"); + if (pidfile && !createpidfile(pidfile)) + debugx(1, DBG_ERR, "failed to create pidfile %s: %s", pidfile, strerror(errno)); + sigemptyset(&sigset); - /* exit on all but SIGPIPE, ignore more? */ + /* exit on all but SIGHUP|SIGPIPE, ignore more? */ + sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGPIPE); pthread_sigmask(SIG_BLOCK, &sigset, NULL); pthread_create(&sigth, NULL, sighandler, NULL); - memset(srcprotores, 0, sizeof(srcprotores)); for (entry = list_first(srvconfs); entry; entry = list_next(entry)) { srvconf = (struct clsrvconf *)entry->data; if (srvconf->dynamiclookupcommand) @@ -3851,15 +3176,13 @@ int main(int argc, char **argv) { debugx(1, DBG_ERR, "pthread_create failed"); } - for (i = 0; protodefs[i].name; i++) { - if (protodefs[i].freesrcprotores && srcprotores[i]) { - freeaddrinfo(srcprotores[i]); - srcprotores[i] = NULL; - } - if (protodefs[i].initextra) - protodefs[i].initextra(); + for (i = 0; i < RAD_PROTOCOUNT; i++) { + if (!protodefs[i]) + continue; + if (protodefs[i]->initextra) + protodefs[i]->initextra(); if (find_clconf_type(i, NULL)) - createlisteners(i, options.listenargs[i]); + createlisteners(i); } /* just hang around doing nothing, anything to do here? */ diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index fe48bb1..63fc7b0 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -5,7 +5,7 @@ \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac -.TH "radsecproxy.conf " 5 2008-12-04 "radsecproxy 1.3-alpha" "" +.TH "radsecproxy.conf " 5 2009-02-18 "radsecproxy 1.3-beta" "" .SH NAME radsecproxy.conf \- Radsec proxy configuration file @@ -130,7 +130,7 @@ you can do e.g. \*(T<192.168.1.1:1812\*(T> or \*(T<[2001:db8::1]:1812\*(T>. The port may be omitted if you want the default one (like in these examples). These examples are equivalent to \*(T<192.168.1.1\*(T> and \*(T<2001:db8::1\*(T>. Note that -you must use brackets around the IPv6 address if you specify port number. +you must use brackets around the IPv6 address. This option may be specified multiple times to listen to multiple addresses and/or ports. .TP @@ -150,11 +150,6 @@ This is similar to the \*(T<listenUDP\*(T> option, except that it is used for receiving connections from DTLS clients. The default port number is \*(T<2083\*(T>. .TP -\*(T<listenAccountingUDP\*(T> -This is similar to the \*(T<listenUDP\*(T> option, except that it is -used for specifying port and optionally the address to receive UDP Accounting -messages on. -.TP \*(T<sourceUDP\*(T> This can be used to specify source address and/or source port that the proxy will use for sending UDP client messages (e.g. Access Request). @@ -215,7 +210,8 @@ The client block is used to configure a client. That is, tell the proxy about a client, and what parameters should be used for that client. The name of the client block must (with one exception, see below) be either the IP address (IPv4 or IPv6) of the client, an IP prefix (IPv4 or IPv6) of the form -IpAddress/PrefixLength, or a domain name (FQDN). +IpAddress/PrefixLength, or a domain name (FQDN). Note that literal IPv6 +addresses must be enclosed in brackets. .PP If a domain name is specified, then this will be resolved immediately to all the addresses associated with the name, and the proxy will not care about any @@ -234,7 +230,8 @@ client name is an IP prefix. Alternatively one may use the \*(T<host\*(T> option inside a client block. In that case, the value of the \*(T<host\*(T> option is used as above, while the name of the block is only used as a descriptive name for the -administrator. +administrator. The host option may be used multiple times, and can be a mix of +addresses, FQDNs and prefixes. .PP The allowed options in a client block are \*(T<host\*(T>, \*(T<type\*(T>, \*(T<secret\*(T>, \*(T<tls\*(T>, @@ -325,7 +322,16 @@ name of the server must match the FQDN or IP address in the server certificate. Alternatively one may use the \*(T<host\*(T> option inside a server block. In that case, the value of the \*(T<host\*(T> option is used as above, while the name of the block is only used as a descriptive name for the -administrator. +administrator. Note that multiple host options may be used. This will then be +treated as multiple names/addresses for the same server. When initiating a TCP/TLS +connection, all addresses of all names may be attempted, but there is no failover +between the different host values. For failover one must use separate server +blocks. +.PP +Note that the name of the block, or values of host options may include a +port number (separated with a column). This port number will then override the +default port or a port option in the server block. Also note that literal IPv6 +addresses must be enclosed in brackets. .PP The allowed options in a server block are \*(T<host\*(T>, \*(T<port\*(T>, \*(T<type\*(T>, \*(T<secret\*(T>, diff --git a/radsecproxy.conf.5.xml b/radsecproxy.conf.5.xml index 01003cd..96a7f44 100644 --- a/radsecproxy.conf.5.xml +++ b/radsecproxy.conf.5.xml @@ -2,14 +2,14 @@ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"> <refentry> <refentryinfo> - <date>2008-12-04</date> + <date>2009-02-18</date> </refentryinfo> <refmeta> <refentrytitle> <application>radsecproxy.conf</application> </refentrytitle> <manvolnum>5</manvolnum> - <refmiscinfo>radsecproxy 1.3-alpha</refmiscinfo> + <refmiscinfo>radsecproxy 1.3-beta</refmiscinfo> </refmeta> <refnamediv> <refname> @@ -172,7 +172,7 @@ you can do e.g. <literal>192.168.1.1:1812</literal> or <literal>[2001:db8::1]:1812</literal>. The port may be omitted if you want the default one (like in these examples). These examples are equivalent to <literal>192.168.1.1</literal> and <literal>2001:db8::1</literal>. Note that -you must use brackets around the IPv6 address if you specify port number. +you must use brackets around the IPv6 address. This option may be specified multiple times to listen to multiple addresses and/or ports. </para> @@ -210,16 +210,6 @@ used for receiving connections from DTLS clients. The default port number is </listitem> </varlistentry> <varlistentry> - <term><literal>listenAccountingUDP</literal></term> - <listitem> - <para> -This is similar to the <literal>listenUDP</literal> option, except that it is -used for specifying port and optionally the address to receive UDP Accounting -messages on. - </para> - </listitem> - </varlistentry> - <varlistentry> <term><literal>sourceUDP</literal></term> <listitem> <para> @@ -328,7 +318,8 @@ The client block is used to configure a client. That is, tell the proxy about a client, and what parameters should be used for that client. The name of the client block must (with one exception, see below) be either the IP address (IPv4 or IPv6) of the client, an IP prefix (IPv4 or IPv6) of the form -IpAddress/PrefixLength, or a domain name (FQDN). +IpAddress/PrefixLength, or a domain name (FQDN). Note that literal IPv6 +addresses must be enclosed in brackets. </para> <para> If a domain name is specified, then this will be resolved immediately to all @@ -351,7 +342,8 @@ client name is an IP prefix. Alternatively one may use the <literal>host</literal> option inside a client block. In that case, the value of the <literal>host</literal> option is used as above, while the name of the block is only used as a descriptive name for the -administrator. +administrator. The host option may be used multiple times, and can be a mix of +addresses, FQDNs and prefixes. </para> <para> The allowed options in a client block are <literal>host</literal>, @@ -459,7 +451,17 @@ name of the server must match the FQDN or IP address in the server certificate. Alternatively one may use the <literal>host</literal> option inside a server block. In that case, the value of the <literal>host</literal> option is used as above, while the name of the block is only used as a descriptive name for the -administrator. +administrator. Note that multiple host options may be used. This will then be +treated as multiple names/addresses for the same server. When initiating a TCP/TLS +connection, all addresses of all names may be attempted, but there is no failover +between the different host values. For failover one must use separate server +blocks. + </para> + <para> +Note that the name of the block, or values of host options may include a +port number (separated with a column). This port number will then override the +default port or a port option in the server block. Also note that literal IPv6 +addresses must be enclosed in brackets. </para> <para> The allowed options in a server block are <literal>host</literal>, diff --git a/radsecproxy.h b/radsecproxy.h index fd48964..c06ec07 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -8,6 +8,7 @@ #include "tlv11.h" #include "radmsg.h" +#include "gconfig.h" #define DEBUG_LEVEL 3 @@ -32,8 +33,6 @@ #define RAD_PROTOCOUNT 4 struct options { - char **listenargs[RAD_PROTOCOUNT]; - char *sourcearg[RAD_PROTOCOUNT]; char *logdestination; char *ttlattr; uint32_t ttlattrtype[2]; @@ -42,6 +41,11 @@ struct options { uint8_t loopprevention; }; +struct commonprotoopts { + char **listenargs; + char *sourcearg; +}; + struct request { struct timeval created; uint32_t refcount; @@ -65,7 +69,7 @@ struct rqout { struct timeval expiry; }; -struct queue { +struct gqueue { struct list *entries; pthread_mutex_t mutex; pthread_cond_t cond; @@ -75,8 +79,9 @@ struct clsrvconf { char *name; uint8_t type; /* RAD_UDP/RAD_TLS/RAD_TCP */ const struct protodefs *pdef; - char *host; - char *port; + char **hostsrc; + char *portsrc; + struct list *hostports; char *secret; char *tls; char *matchcertattr; @@ -95,21 +100,21 @@ struct clsrvconf { uint8_t addttl; struct rewrite *rewritein; struct rewrite *rewriteout; - struct addrinfo *addrinfo; - uint8_t prefixlen; pthread_mutex_t *lock; /* only used for updating clients so far */ struct tls *tlsconf; struct list *clients; struct server *servers; }; +#include "tlscommon.h" + struct client { struct clsrvconf *conf; int sock; SSL *ssl; struct request *rqs[MAX_REQUESTS]; - struct queue *replyq; - struct queue *rbios; /* for dtls */ + struct gqueue *replyq; + struct gqueue *rbios; /* for dtls */ struct sockaddr *addr; time_t expiry; /* for udp */ }; @@ -125,6 +130,7 @@ struct server { struct timeval lastreply; uint8_t connectionok; uint8_t lostrqs; + uint8_t dynstartup; char *dynamiclookuparg; int nextid; struct timeval lastrcv; @@ -132,7 +138,7 @@ struct server { uint8_t newrq; pthread_mutex_t newrq_mutex; pthread_cond_t newrq_cond; - struct queue *rbios; /* for dtls */ + struct gqueue *rbios; /* for dtls */ }; struct realm { @@ -148,23 +154,6 @@ struct realm { struct list *accsrvconfs; }; -struct tls { - char *name; - char *cacertfile; - char *cacertpath; - char *certfile; - char *certkeyfile; - char *certkeypwd; - uint8_t crlcheck; - char **policyoids; - uint32_t cacheexpiry; - uint32_t tlsexpiry; - uint32_t dtlsexpiry; - X509_VERIFY_PARAM *vpm; - SSL_CTX *tlsctx; - SSL_CTX *dtlsctx; -}; - struct modattr { uint8_t t; char *replacement; @@ -188,13 +177,15 @@ struct protodefs { uint8_t retryintervaldefault; uint8_t retryintervalmax; uint8_t duplicateintervaldefault; + void (*setprotoopts)(struct commonprotoopts *); + char **(*getlistenerargs)(); void *(*listener)(void*); int (*connecter)(struct server *, struct timeval *, int, char *); void *(*clientconnreader)(void*); int (*clientradput)(struct server *, unsigned char *); void (*addclient)(struct client *); void (*addserverextra)(struct clsrvconf *); - uint8_t freesrcprotores; + void (*setsrcres)(); void (*initextra)(); }; @@ -205,19 +196,16 @@ struct protodefs { #define ATTRVAL(x) ((x) + 2) #define ATTRVALLEN(x) ((x)[1] - 2) -struct addrinfo *getsrcprotores(uint8_t type); struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur); 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); void removelockedclient(struct client *client); void removeclient(struct client *client); -struct queue *newqueue(); -void freebios(struct queue *q); +struct gqueue *newqueue(); +void freebios(struct gqueue *q); struct request *newrequest(); void freerq(struct request *rq); int radsrv(struct request *rq); -X509 *verifytlscert(SSL *ssl); -int verifyconfcert(X509 *cert, struct clsrvconf *conf); void replyh(struct server *server, unsigned char *buf); -SSL_CTX *tlsgetctx(uint8_t type, struct tls *t); +struct addrinfo *resolve_hostport_addrinfo(uint8_t type, char *hostport); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2008-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,12 +24,63 @@ #include <arpa/inet.h> #include <regex.h> #include <pthread.h> -#include <openssl/ssl.h> -#include "debug.h" #include "list.h" -#include "util.h" +#include "hostport.h" #include "radsecproxy.h" -#include "tcp.h" + +#ifdef RADPROT_TCP +#include "debug.h" +#include "util.h" +static void setprotoopts(struct commonprotoopts *opts); +static char **getlistenerargs(); +void *tcplistener(void *arg); +int tcpconnect(struct server *server, struct timeval *when, int timeout, char * text); +void *tcpclientrd(void *arg); +int clientradputtcp(struct server *server, unsigned char *rad); +void tcpsetsrcres(); + +static const struct protodefs protodefs = { + "tcp", + NULL, /* secretdefault */ + SOCK_STREAM, /* socktype */ + "1812", /* portdefault */ + 0, /* retrycountdefault */ + 0, /* retrycountmax */ + REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */ + 60, /* retryintervalmax */ + DUPLICATE_INTERVAL, /* duplicateintervaldefault */ + setprotoopts, /* setprotoopts */ + getlistenerargs, /* getlistenerargs */ + tcplistener, /* listener */ + tcpconnect, /* connecter */ + tcpclientrd, /* clientconnreader */ + clientradputtcp, /* clientradput */ + NULL, /* addclient */ + NULL, /* addserverextra */ + tcpsetsrcres, /* setsrcres */ + NULL /* initextra */ +}; + +static struct addrinfo *srcres = NULL; +static uint8_t handle; +static struct commonprotoopts *protoopts = NULL; +const struct protodefs *tcpinit(uint8_t h) { + handle = h; + return &protodefs; +} + +static void setprotoopts(struct commonprotoopts *opts) { + protoopts = opts; +} + +static char **getlistenerargs() { + return protoopts ? protoopts->listenargs : NULL; +} + +void tcpsetsrcres() { + if (!srcres) + srcres = resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL, NULL, protodefs.socktype); +} int tcpconnect(struct server *server, struct timeval *when, int timeout, char *text) { struct timeval now; @@ -67,14 +118,12 @@ int tcpconnect(struct server *server, struct timeval *when, int timeout, char *t sleep(60); } else server->lastconnecttry.tv_sec = now.tv_sec; /* no sleep at startup */ - debug(DBG_WARN, "tcpconnect: trying to open TCP connection to %s port %s", server->conf->host, server->conf->port); + if (server->sock >= 0) close(server->sock); - if ((server->sock = connecttcp(server->conf->addrinfo, getsrcprotores(RAD_TCP))) >= 0) + if ((server->sock = connecttcphostlist(server->conf->hostports, srcres)) >= 0) break; - debug(DBG_ERR, "tcpconnect: connecttcp failed"); } - debug(DBG_WARN, "tcpconnect: TCP connection to %s port %s up", server->conf->host, server->conf->port); server->connectionok = 1; gettimeofday(&server->lastconnecttry, NULL); pthread_mutex_unlock(&server->lock); @@ -160,7 +209,7 @@ int clientradputtcp(struct server *server, unsigned char *rad) { debug(DBG_ERR, "clientradputtcp: write error"); return 0; } - debug(DBG_DBG, "clientradputtcp: Sent %d bytes, Radius packet of length %d to TCP peer %s", cnt, len, conf->host); + debug(DBG_DBG, "clientradputtcp: Sent %d bytes, Radius packet of length %d to TCP peer %s", cnt, len, conf->name); return 1; } @@ -187,7 +236,7 @@ void *tcpclientrd(void *arg) { void *tcpserverwr(void *arg) { int cnt; struct client *client = (struct client *)arg; - struct queue *replyq; + struct gqueue *replyq; struct request *reply; debug(DBG_DBG, "tcpserverwr: starting for %s", addr2string(client->addr)); @@ -274,7 +323,7 @@ void *tcpservernew(void *arg) { } debug(DBG_WARN, "tcpservernew: incoming TCP connection from %s", addr2string((struct sockaddr *)&from)); - conf = find_clconf(RAD_TCP, (struct sockaddr *)&from, NULL); + conf = find_clconf(handle, (struct sockaddr *)&from, NULL); if (conf) { client = addclient(conf, 1); if (client) { @@ -318,3 +367,8 @@ void *tcplistener(void *arg) { free(sp); return NULL; } +#else +const struct protodefs *tcpinit(uint8_t h) { + return NULL; +} +#endif @@ -6,7 +6,4 @@ * copyright notice and this permission notice appear in all copies. */ -int tcpconnect(struct server *server, struct timeval *when, int timeout, char *text); -int clientradputtcp(struct server *server, unsigned char *rad); -void *tcpclientrd(void *arg); -void *tcplistener(void *arg); +const struct protodefs *tcpinit(uint8_t h); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,11 +26,65 @@ #include <pthread.h> #include <openssl/ssl.h> #include <openssl/err.h> -#include "debug.h" #include "list.h" -#include "util.h" +#include "hostport.h" #include "radsecproxy.h" -#include "tls.h" + +#ifdef RADPROT_TLS +#include "debug.h" +#include "util.h" + +static void setprotoopts(struct commonprotoopts *opts); +static char **getlistenerargs(); +void *tlslistener(void *arg); +int tlsconnect(struct server *server, struct timeval *when, int timeout, char *text); +void *tlsclientrd(void *arg); +int clientradputtls(struct server *server, unsigned char *rad); +void tlssetsrcres(); + +static const struct protodefs protodefs = { + "tls", + "mysecret", /* secretdefault */ + SOCK_STREAM, /* socktype */ + "2083", /* portdefault */ + 0, /* retrycountdefault */ + 0, /* retrycountmax */ + REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */ + 60, /* retryintervalmax */ + DUPLICATE_INTERVAL, /* duplicateintervaldefault */ + setprotoopts, /* setprotoopts */ + getlistenerargs, /* getlistenerargs */ + tlslistener, /* listener */ + tlsconnect, /* connecter */ + tlsclientrd, /* clientconnreader */ + clientradputtls, /* clientradput */ + NULL, /* addclient */ + NULL, /* addserverextra */ + tlssetsrcres, /* setsrcres */ + NULL /* initextra */ +}; + +static struct addrinfo *srcres = NULL; +static uint8_t handle; +static struct commonprotoopts *protoopts = NULL; + +const struct protodefs *tlsinit(uint8_t h) { + handle = h; + return &protodefs; +} + +static void setprotoopts(struct commonprotoopts *opts) { + protoopts = opts; +} + +static char **getlistenerargs() { + return protoopts ? protoopts->listenargs : NULL; +} + +void tlssetsrcres() { + if (!srcres) + srcres = resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL, NULL, protodefs.socktype); +} int tlsconnect(struct server *server, struct timeval *when, int timeout, char *text) { struct timeval now; @@ -73,17 +127,15 @@ int tlsconnect(struct server *server, struct timeval *when, int timeout, char *t sleep(60); } else server->lastconnecttry.tv_sec = now.tv_sec; /* no sleep at startup */ - debug(DBG_WARN, "tlsconnect: trying to open TLS connection to %s port %s", server->conf->host, server->conf->port); + if (server->sock >= 0) close(server->sock); - if ((server->sock = connecttcp(server->conf->addrinfo, getsrcprotores(RAD_TLS))) < 0) { - debug(DBG_ERR, "tlsconnect: connecttcp failed"); + if ((server->sock = connecttcphostlist(server->conf->hostports, srcres)) < 0) continue; - } SSL_free(server->ssl); server->ssl = NULL; - ctx = tlsgetctx(RAD_TLS, server->conf->tlsconf); + ctx = tlsgetctx(handle, server->conf->tlsconf); if (!ctx) continue; server->ssl = SSL_new(ctx); @@ -105,7 +157,7 @@ int tlsconnect(struct server *server, struct timeval *when, int timeout, char *t } X509_free(cert); } - debug(DBG_WARN, "tlsconnect: TLS connection to %s port %s up", server->conf->host, server->conf->port); + debug(DBG_WARN, "tlsconnect: TLS connection to %s up", server->conf->name); server->connectionok = 1; gettimeofday(&server->lastconnecttry, NULL); pthread_mutex_unlock(&server->lock); @@ -206,7 +258,7 @@ int clientradputtls(struct server *server, unsigned char *rad) { return 0; } - debug(DBG_DBG, "clientradputtls: Sent %d bytes, Radius packet of length %d to TLS peer %s", cnt, len, conf->host); + debug(DBG_DBG, "clientradputtls: Sent %d bytes, Radius packet of length %d to TLS peer %s", cnt, len, conf->name); return 1; } @@ -245,7 +297,7 @@ void *tlsserverwr(void *arg) { int cnt; unsigned long error; struct client *client = (struct client *)arg; - struct queue *replyq; + struct gqueue *replyq; struct request *reply; debug(DBG_DBG, "tlsserverwr: starting for %s", addr2string(client->addr)); @@ -340,9 +392,9 @@ void *tlsservernew(void *arg) { } debug(DBG_WARN, "tlsservernew: incoming TLS connection from %s", addr2string((struct sockaddr *)&from)); - conf = find_clconf(RAD_TLS, (struct sockaddr *)&from, &cur); + conf = find_clconf(handle, (struct sockaddr *)&from, &cur); if (conf) { - ctx = tlsgetctx(RAD_TLS, conf->tlsconf); + ctx = tlsgetctx(handle, conf->tlsconf); if (!ctx) goto exit; ssl = SSL_new(ctx); @@ -374,7 +426,7 @@ void *tlsservernew(void *arg) { debug(DBG_WARN, "tlsservernew: failed to create new client instance"); goto exit; } - conf = find_clconf(RAD_TLS, (struct sockaddr *)&from, &cur); + conf = find_clconf(handle, (struct sockaddr *)&from, &cur); } debug(DBG_WARN, "tlsservernew: ignoring request, no matching TLS client"); if (cert) @@ -416,3 +468,8 @@ void *tlslistener(void *arg) { free(sp); return NULL; } +#else +const struct protodefs *tlsinit(uint8_t h) { + return NULL; +} +#endif @@ -6,7 +6,4 @@ * copyright notice and this permission notice appear in all copies. */ -int tlsconnect(struct server *server, struct timeval *when, int timeout, char *text); -int clientradputtls(struct server *server, unsigned char *rad); -void *tlsclientrd(void *arg); -void *tlslistener(void *arg); +const struct protodefs *tlsinit(uint8_t h); diff --git a/tlscommon.c b/tlscommon.c new file mode 100644 index 0000000..6260e37 --- /dev/null +++ b/tlscommon.c @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) +#include <signal.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#ifdef SYS_SOLARIS9 +#include <fcntl.h> +#endif +#include <sys/time.h> +#include <sys/types.h> +#include <sys/select.h> +#include <ctype.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <regex.h> +#include <libgen.h> +#include <pthread.h> +#include <openssl/ssl.h> +#include <openssl/rand.h> +#include <openssl/err.h> +#include <openssl/md5.h> +#include <openssl/x509v3.h> +#include "debug.h" +#include "list.h" +#include "hash.h" +#include "util.h" +#include "hostport.h" +#include "radsecproxy.h" + +static struct hash *tlsconfs = NULL; + +static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) { + int pwdlen = strlen(userdata); + if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */ + return 0; + memcpy(buf, userdata, pwdlen); + return pwdlen; +} + +static int verify_cb(int ok, X509_STORE_CTX *ctx) { + char *buf = NULL; + X509 *err_cert; + int err, depth; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + if (depth > MAX_CERT_DEPTH) { + ok = 0; + err = X509_V_ERR_CERT_CHAIN_TOO_LONG; + X509_STORE_CTX_set_error(ctx, err); + } + + if (!ok) { + if (err_cert) + buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); + debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : ""); + free(buf); + buf = NULL; + + switch (err) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + if (err_cert) { + buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0); + if (buf) { + debug(DBG_WARN, "\tIssuer=%s", buf); + free(buf); + buf = NULL; + } + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + debug(DBG_WARN, "\tCertificate not yet valid"); + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + debug(DBG_WARN, "Certificate has expired"); + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + debug(DBG_WARN, "Certificate no longer valid (after notAfter)"); + break; + case X509_V_ERR_NO_EXPLICIT_POLICY: + debug(DBG_WARN, "No Explicit Certificate Policy"); + break; + } + } +#ifdef DEBUG + printf("certificate verify returns %d\n", ok); +#endif + return ok; +} + +#ifdef DEBUG +static void ssl_info_callback(const SSL *ssl, int where, int ret) { + const char *s; + int w; + + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + s = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + s = "SSL_accept"; + else + s = "undefined"; + + if (where & SSL_CB_LOOP) + debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl)); + else if (where & SSL_CB_ALERT) { + s = (where & SSL_CB_READ) ? "read" : "write"; + debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_EXIT) { + if (ret == 0) + debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl)); + else if (ret < 0) + debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl)); + } +} +#endif + +static X509_VERIFY_PARAM *createverifyparams(char **poids) { + X509_VERIFY_PARAM *pm; + ASN1_OBJECT *pobject; + int i; + + pm = X509_VERIFY_PARAM_new(); + if (!pm) + return NULL; + + for (i = 0; poids[i]; i++) { + pobject = OBJ_txt2obj(poids[i], 0); + if (!pobject) { + X509_VERIFY_PARAM_free(pm); + return NULL; + } + X509_VERIFY_PARAM_add0_policy(pm, pobject); + } + + X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY); + return pm; +} + +static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) { + STACK_OF(X509_NAME) *calist; + X509_STORE *x509_s; + unsigned long error; + + if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) { + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name); + return 0; + } + + calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL; + if (!conf->cacertfile || calist) { + if (conf->cacertpath) { + if (!calist) + calist = sk_X509_NAME_new_null(); + if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) { + sk_X509_NAME_free(calist); + calist = NULL; + } + } + } + if (!calist) { + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name); + return 0; + } + ERR_clear_error(); /* add_dir_cert_subj returns errors on success */ + SSL_CTX_set_client_CA_list(ctx, calist); + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); + SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1); + + if (conf->crlcheck || conf->vpm) { + x509_s = SSL_CTX_get_cert_store(ctx); + if (conf->crlcheck) + X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + if (conf->vpm) + X509_STORE_set1_param(x509_s, conf->vpm); + } + + debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name); + return 1; +} + +static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { + SSL_CTX *ctx = NULL; + unsigned long error; + + switch (type) { +#ifdef RADPROT_TLS + case RAD_TLS: + ctx = SSL_CTX_new(TLSv1_method()); +#ifdef DEBUG + SSL_CTX_set_info_callback(ctx, ssl_info_callback); +#endif + break; +#endif +#ifdef RADPROT_DTLS + case RAD_DTLS: + ctx = SSL_CTX_new(DTLSv1_method()); +#ifdef DEBUG + SSL_CTX_set_info_callback(ctx, ssl_info_callback); +#endif + SSL_CTX_set_read_ahead(ctx, 1); + break; +#endif + } + if (!ctx) { + debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); + return NULL; + } + + if (conf->certkeypwd) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd); + SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); + } + if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) || + !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) || + !SSL_CTX_check_private_key(ctx)) { + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); + SSL_CTX_free(ctx); + return NULL; + } + + if (conf->policyoids) { + if (!conf->vpm) { + conf->vpm = createverifyparams(conf->policyoids); + if (!conf->vpm) { + debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name); + SSL_CTX_free(ctx); + return NULL; + } + } + } + + if (!tlsaddcacrl(ctx, conf)) { + if (conf->vpm) { + X509_VERIFY_PARAM_free(conf->vpm); + conf->vpm = NULL; + } + SSL_CTX_free(ctx); + return NULL; + } + + debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name); + return ctx; +} + +struct tls *tlsgettls(char *alt1, char *alt2) { + struct tls *t; + + t = hash_read(tlsconfs, alt1, strlen(alt1)); + if (!t) + t = hash_read(tlsconfs, alt2, strlen(alt2)); + return t; +} + +SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) { + struct timeval now; + + if (!t) + return NULL; + gettimeofday(&now, NULL); + + switch (type) { +#ifdef RADPROT_TLS + case RAD_TLS: + if (t->tlsexpiry && t->tlsctx) { + if (t->tlsexpiry < now.tv_sec) { + t->tlsexpiry = now.tv_sec + t->cacheexpiry; + tlsaddcacrl(t->tlsctx, t); + } + } + if (!t->tlsctx) { + t->tlsctx = tlscreatectx(RAD_TLS, t); + if (t->cacheexpiry) + t->tlsexpiry = now.tv_sec + t->cacheexpiry; + } + return t->tlsctx; +#endif +#ifdef RADPROT_DTLS + case RAD_DTLS: + if (t->dtlsexpiry && t->dtlsctx) { + if (t->dtlsexpiry < now.tv_sec) { + t->dtlsexpiry = now.tv_sec + t->cacheexpiry; + tlsaddcacrl(t->dtlsctx, t); + } + } + if (!t->dtlsctx) { + t->dtlsctx = tlscreatectx(RAD_DTLS, t); + if (t->cacheexpiry) + t->dtlsexpiry = now.tv_sec + t->cacheexpiry; + } + return t->dtlsctx; +#endif + } + return NULL; +} + +X509 *verifytlscert(SSL *ssl) { + X509 *cert; + unsigned long error; + + if (SSL_get_verify_result(ssl) != X509_V_OK) { + debug(DBG_ERR, "verifytlscert: basic validation failed"); + while ((error = ERR_get_error())) + debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL)); + return NULL; + } + + cert = SSL_get_peer_certificate(ssl); + if (!cert) + debug(DBG_ERR, "verifytlscert: failed to obtain certificate"); + return cert; +} + +static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) { + int loc, i, l, n, r = 0; + char *v; + X509_EXTENSION *ex; + STACK_OF(GENERAL_NAME) *alt; + GENERAL_NAME *gn; + + debug(DBG_DBG, "subjectaltnameaddr"); + + loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); + if (loc < 0) + return r; + + ex = X509_get_ext(cert, loc); + alt = X509V3_EXT_d2i(ex); + if (!alt) + return r; + + n = sk_GENERAL_NAME_num(alt); + for (i = 0; i < n; i++) { + gn = sk_GENERAL_NAME_value(alt, i); + if (gn->type != GEN_IPADD) + continue; + r = -1; + v = (char *)ASN1_STRING_data(gn->d.ia5); + l = ASN1_STRING_length(gn->d.ia5); + if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr))) + && !memcmp(v, &addr, l)) { + r = 1; + break; + } + } + GENERAL_NAMES_free(alt); + return r; +} + +static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) { + int loc, i, l, n, r = 0; + char *s, *v; + X509_EXTENSION *ex; + STACK_OF(GENERAL_NAME) *alt; + GENERAL_NAME *gn; + + debug(DBG_DBG, "subjectaltnameregexp"); + + loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); + if (loc < 0) + return r; + + ex = X509_get_ext(cert, loc); + alt = X509V3_EXT_d2i(ex); + if (!alt) + return r; + + n = sk_GENERAL_NAME_num(alt); + for (i = 0; i < n; i++) { + gn = sk_GENERAL_NAME_value(alt, i); + if (gn->type != type) + continue; + r = -1; + v = (char *)ASN1_STRING_data(gn->d.ia5); + l = ASN1_STRING_length(gn->d.ia5); + if (l <= 0) + continue; +#ifdef DEBUG + printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l); +#endif + if (exact) { + if (memcmp(v, exact, l)) + continue; + } else { + s = stringcopy((char *)v, l); + if (!s) { + debug(DBG_ERR, "malloc failed"); + continue; + } + if (regexec(regex, s, 0, NULL, 0)) { + free(s); + continue; + } + free(s); + } + r = 1; + break; + } + GENERAL_NAMES_free(alt); + return r; +} + +static int cnregexp(X509 *cert, char *exact, regex_t *regex) { + int loc, l; + char *v, *s; + X509_NAME *nm; + X509_NAME_ENTRY *e; + ASN1_STRING *t; + + nm = X509_get_subject_name(cert); + loc = -1; + for (;;) { + loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc); + if (loc == -1) + break; + e = X509_NAME_get_entry(nm, loc); + t = X509_NAME_ENTRY_get_data(e); + v = (char *) ASN1_STRING_data(t); + l = ASN1_STRING_length(t); + if (l < 0) + continue; + if (exact) { + if (l == strlen(exact) && !strncasecmp(exact, v, l)) + return 1; + } else { + s = stringcopy((char *)v, l); + if (!s) { + debug(DBG_ERR, "malloc failed"); + continue; + } + if (regexec(regex, s, 0, NULL, 0)) { + free(s); + continue; + } + free(s); + return 1; + } + } + return 0; +} + +/* this is a bit sloppy, should not always accept match to any */ +int certnamecheck(X509 *cert, struct list *hostports) { + struct list_node *entry; + struct hostportres *hp; + int r; + uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */ + struct in6_addr addr; + + for (entry = list_first(hostports); entry; entry = list_next(entry)) { + hp = (struct hostportres *)entry->data; + if (hp->prefixlen != 255) { + /* we disable the check for prefixes */ + return 1; + } + if (inet_pton(AF_INET, hp->host, &addr)) + type = AF_INET; + else if (inet_pton(AF_INET6, hp->host, &addr)) + type = AF_INET6; + else + type = 0; + + r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL); + if (r) { + if (r > 0) { + debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host); + return 1; + } + debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host); + } else { + if (cnregexp(cert, hp->host, NULL)) { + 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) { + if (conf->certnamecheck) { + if (!certnamecheck(cert, conf->hostports)) { + debug(DBG_WARN, "verifyconfcert: certificate name check failed"); + return 0; + } + debug(DBG_WARN, "verifyconfcert: certificate name check ok"); + } + if (conf->certcnregex) { + if (cnregexp(cert, NULL, conf->certcnregex) < 1) { + debug(DBG_WARN, "verifyconfcert: CN not matching regex"); + return 0; + } + debug(DBG_DBG, "verifyconfcert: CN matching regex"); + } + if (conf->certuriregex) { + if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) { + debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex"); + return 0; + } + debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex"); + } + return 1; +} + +int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { + struct tls *conf; + long int expiry = LONG_MIN; + + debug(DBG_DBG, "conftls_cb called for %s", block); + + conf = malloc(sizeof(struct tls)); + if (!conf) { + debug(DBG_ERR, "conftls_cb: malloc failed"); + return 0; + } + memset(conf, 0, sizeof(struct tls)); + + if (!getgenericconfig(cf, block, + "CACertificateFile", CONF_STR, &conf->cacertfile, + "CACertificatePath", CONF_STR, &conf->cacertpath, + "CertificateFile", CONF_STR, &conf->certfile, + "CertificateKeyFile", CONF_STR, &conf->certkeyfile, + "CertificateKeyPassword", CONF_STR, &conf->certkeypwd, + "CacheExpiry", CONF_LINT, &expiry, + "CRLCheck", CONF_BLN, &conf->crlcheck, + "PolicyOID", CONF_MSTR, &conf->policyoids, + NULL + )) { + debug(DBG_ERR, "conftls_cb: configuration error in block %s", val); + goto errexit; + } + if (!conf->certfile || !conf->certkeyfile) { + debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val); + goto errexit; + } + if (!conf->cacertfile && !conf->cacertpath) { + debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val); + goto errexit; + } + if (expiry != LONG_MIN) { + if (expiry < 0) { + debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry); + goto errexit; + } + conf->cacheexpiry = expiry; + } + + conf->name = stringcopy(val, 0); + if (!conf->name) { + debug(DBG_ERR, "conftls_cb: malloc failed"); + goto errexit; + } + + if (!tlsconfs) + tlsconfs = hash_create(); + if (!hash_insert(tlsconfs, val, strlen(val), conf)) { + debug(DBG_ERR, "conftls_cb: malloc failed"); + goto errexit; + } + if (!tlsgetctx(RAD_TLS, conf)) + debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val); + debug(DBG_DBG, "conftls_cb: added TLS block %s", val); + return 1; + + errexit: + free(conf->cacertfile); + free(conf->cacertpath); + free(conf->certfile); + free(conf->certkeyfile); + free(conf->certkeypwd); + freegconfmstr(conf->policyoids); + free(conf); + return 0; +} + +int addmatchcertattr(struct clsrvconf *conf) { + char *v; + regex_t **r; + + if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) { + r = &conf->certcnregex; + v = conf->matchcertattr + 4; + } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) { + r = &conf->certuriregex; + v = conf->matchcertattr + 20; + } else + return 0; + if (!*v) + return 0; + /* regexp, remove optional trailing / if present */ + if (v[strlen(v) - 1] == '/') + v[strlen(v) - 1] = '\0'; + if (!*v) + return 0; + + *r = malloc(sizeof(regex_t)); + if (!*r) { + debug(DBG_ERR, "malloc failed"); + return 0; + } + if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { + free(*r); + *r = NULL; + debug(DBG_ERR, "failed to compile regular expression %s", v); + return 0; + } + return 1; +} +#else +/* Just to makes file non-empty, should rather avoid compiling this file when not needed */ +static void tlsdummy() { +} +#endif diff --git a/tlscommon.h b/tlscommon.h new file mode 100644 index 0000000..97388e4 --- /dev/null +++ b/tlscommon.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include <openssl/ssl.h> + +struct tls { + char *name; + char *cacertfile; + char *cacertpath; + char *certfile; + char *certkeyfile; + char *certkeypwd; + uint8_t crlcheck; + char **policyoids; + uint32_t cacheexpiry; + uint32_t tlsexpiry; + uint32_t dtlsexpiry; + X509_VERIFY_PARAM *vpm; + SSL_CTX *tlsctx; + SSL_CTX *dtlsctx; +}; + +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) +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 conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); +int addmatchcertattr(struct clsrvconf *conf); +#endif @@ -6,7 +6,11 @@ * copyright notice and this permission notice appear in all copies. */ +#ifdef SYS_SOLARIS9 +#include <sys/inttypes.h> +#else #include <stdint.h> +#endif #include "list.h" #include "tlv11.h" #include <stdlib.h> @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no> + * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,16 +24,70 @@ #include <arpa/inet.h> #include <regex.h> #include <pthread.h> -#include <openssl/ssl.h> -#include "debug.h" #include "list.h" -#include "util.h" +#include "hostport.h" #include "radsecproxy.h" -#include "tls.h" + +#ifdef RADPROT_UDP +#include "debug.h" +#include "util.h" + +static void setprotoopts(struct commonprotoopts *opts); +static char **getlistenerargs(); +void *udpserverrd(void *arg); +int clientradputudp(struct server *server, unsigned char *rad); +void addclientudp(struct client *client); +void addserverextraudp(struct clsrvconf *conf); +void udpsetsrcres(); +void initextraudp(); + +static const struct protodefs protodefs = { + "udp", + NULL, /* secretdefault */ + SOCK_DGRAM, /* socktype */ + "1812", /* portdefault */ + REQUEST_RETRY_COUNT, /* retrycountdefault */ + 10, /* retrycountmax */ + REQUEST_RETRY_INTERVAL, /* retryintervaldefault */ + 60, /* retryintervalmax */ + DUPLICATE_INTERVAL, /* duplicateintervaldefault */ + setprotoopts, /* setprotoopts */ + getlistenerargs, /* getlistenerargs */ + udpserverrd, /* listener */ + NULL, /* connecter */ + NULL, /* clientconnreader */ + clientradputudp, /* clientradput */ + addclientudp, /* addclient */ + addserverextraudp, /* addserverextra */ + udpsetsrcres, /* setsrcres */ + initextraudp /* initextra */ +}; static int client4_sock = -1; static int client6_sock = -1; -static struct queue *server_replyq = NULL; +static struct gqueue *server_replyq = NULL; + +static struct addrinfo *srcres = NULL; +static uint8_t handle; +static struct commonprotoopts *protoopts = NULL; + +const struct protodefs *udpinit(uint8_t h) { + handle = h; + return &protodefs; +} + +static void setprotoopts(struct commonprotoopts *opts) { + protoopts = opts; +} + +static char **getlistenerargs() { + return protoopts ? protoopts->listenargs : NULL; +} + +void udpsetsrcres() { + if (!srcres) + srcres = resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL, NULL, protodefs.socktype); +} void removeudpclientfromreplyq(struct client *c) { struct list_node *n; @@ -49,6 +103,31 @@ void removeudpclientfromreplyq(struct client *c) { pthread_mutex_unlock(&c->replyq->mutex); } +static int addr_equal(struct sockaddr *a, struct sockaddr *b) { + switch (a->sa_family) { + case AF_INET: + return !memcmp(&((struct sockaddr_in*)a)->sin_addr, + &((struct sockaddr_in*)b)->sin_addr, + sizeof(struct in_addr)); + case AF_INET6: + return IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6*)a)->sin6_addr, + &((struct sockaddr_in6*)b)->sin6_addr); + default: + /* Must not reach */ + return 0; + } +} + +uint16_t port_get(struct sockaddr *sa) { + switch (sa->sa_family) { + case AF_INET: + return ntohs(((struct sockaddr_in *)sa)->sin_port); + case AF_INET6: + return ntohs(((struct sockaddr_in6 *)sa)->sin6_port); + } + return 0; +} + /* exactly one of client and server must be non-NULL */ /* return who we received from in *client or *server */ /* return from in sa if not NULL */ @@ -78,15 +157,10 @@ unsigned char *radudpget(int s, struct client **client, struct server **server, debug(DBG_WARN, "radudpget: recv failed"); continue; } - if (cnt < 20) { - debug(DBG_WARN, "radudpget: length too small"); - recv(s, buf, 4, 0); - continue; - } p = client - ? find_clconf(RAD_UDP, (struct sockaddr *)&from, NULL) - : find_srvconf(RAD_UDP, (struct sockaddr *)&from, NULL); + ? find_clconf(handle, (struct sockaddr *)&from, 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)); recv(s, buf, 4, 0); @@ -170,10 +244,12 @@ unsigned char *radudpget(int s, struct client **client, struct server **server, int clientradputudp(struct server *server, unsigned char *rad) { size_t len; struct clsrvconf *conf = server->conf; - + struct addrinfo *ai; + len = RADLEN(rad); - if (sendto(server->sock, rad, len, 0, conf->addrinfo->ai_addr, conf->addrinfo->ai_addrlen) >= 0) { - debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, conf->host, port_get(conf->addrinfo->ai_addr)); + ai = ((struct hostportres *)list_first(conf->hostports)->data)->addrinfo; + if (sendto(server->sock, rad, len, 0, ai->ai_addr, ai->ai_addrlen) >= 0) { + debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, addr2string(ai->ai_addr), port_get(ai->ai_addr)); return 1; } @@ -208,10 +284,11 @@ void *udpserverrd(void *arg) { radsrv(rq); } free(sp); + return NULL; } void *udpserverwr(void *arg) { - struct queue *replyq = (struct queue *)arg; + struct gqueue *replyq = (struct gqueue *)arg; struct request *reply; struct sockaddr_storage to; @@ -241,20 +318,20 @@ void addclientudp(struct client *client) { } void addserverextraudp(struct clsrvconf *conf) { - switch (conf->addrinfo->ai_family) { + switch (((struct hostportres *)list_first(conf->hostports)->data)->addrinfo->ai_family) { case AF_INET: if (client4_sock < 0) { - client4_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET, 0, 1); + client4_sock = bindtoaddr(srcres, AF_INET, 0, 1); if (client4_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); + debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name); } conf->servers->sock = client4_sock; break; case AF_INET6: if (client6_sock < 0) { - client6_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET6, 0, 1); + client6_sock = bindtoaddr(srcres, AF_INET6, 0, 1); if (client6_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); + debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name); } conf->servers->sock = client6_sock; break; @@ -265,6 +342,11 @@ void addserverextraudp(struct clsrvconf *conf) { void initextraudp() { pthread_t cl4th, cl6th, srvth; + + if (srcres) { + freeaddrinfo(srcres); + srcres = NULL; + } if (client4_sock >= 0) if (pthread_create(&cl4th, NULL, udpclientrd, (void *)&client4_sock)) @@ -273,9 +355,14 @@ void initextraudp() { if (pthread_create(&cl6th, NULL, udpclientrd, (void *)&client6_sock)) debugx(1, DBG_ERR, "pthread_create failed"); - if (find_clconf_type(RAD_UDP, NULL)) { + if (find_clconf_type(handle, NULL)) { server_replyq = newqueue(); if (pthread_create(&srvth, NULL, udpserverwr, (void *)server_replyq)) debugx(1, DBG_ERR, "pthread_create failed"); } } +#else +const struct protodefs *udpinit(uint8_t h) { + return NULL; +} +#endif @@ -6,9 +6,4 @@ * copyright notice and this permission notice appear in all copies. */ -int clientradputudp(struct server *server, unsigned char *rad); -void *udpclientrd(void *arg); -void *udpserverrd(void *arg); -void addclientudp(struct client *client); -void addserverextraudp(struct clsrvconf *conf); -void initextraudp(); +const struct protodefs *udpinit(uint8_t h); @@ -13,12 +13,17 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/select.h> #include <stdarg.h> #include "debug.h" #include "util.h" char *stringcopy(const char *s, int len) { char *r; + if (!s) + return NULL; if (!len) len = strlen(s); r = malloc(len + 1); @@ -39,16 +44,6 @@ void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int printf("\n"); } -uint16_t port_get(struct sockaddr *sa) { - switch (sa->sa_family) { - case AF_INET: - return ntohs(((struct sockaddr_in *)sa)->sin_port); - case AF_INET6: - return ntohs(((struct sockaddr_in6 *)sa)->sin6_port); - } - return 0; -} - void port_set(struct sockaddr *sa, uint16_t port) { switch (sa->sa_family) { case AF_INET: @@ -60,21 +55,6 @@ void port_set(struct sockaddr *sa, uint16_t port) { } } -int addr_equal(struct sockaddr *a, struct sockaddr *b) { - switch (a->sa_family) { - case AF_INET: - return !memcmp(&((struct sockaddr_in*)a)->sin_addr, - &((struct sockaddr_in*)b)->sin_addr, - sizeof(struct in_addr)); - case AF_INET6: - return IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6*)a)->sin6_addr, - &((struct sockaddr_in6*)b)->sin6_addr); - default: - /* Must not reach */ - return 0; - } -} - struct sockaddr *addr_copy(struct sockaddr *in) { struct sockaddr *out = NULL; @@ -122,6 +102,8 @@ char *addr2string(struct sockaddr *addr) { return addr_buf[i]; } +#if 0 +/* not in use */ int connectport(int type, char *host, char *port) { struct addrinfo hints, *res0, *res; int s = -1; @@ -150,6 +132,7 @@ int connectport(int type, char *host, char *port) { freeaddrinfo(res0); return s; } +#endif int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) { int s, on = 1; @@ -177,18 +160,56 @@ int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) { return -1; } -int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src) { +int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) { + int origflags, error = 0, r = -1; + fd_set writefds; + socklen_t len; + + origflags = fcntl(s, F_GETFL, 0); + fcntl(s, F_SETFL, origflags | O_NONBLOCK); + if (!connect(s, addr, addrlen)) { + r = 0; + goto exit; + } + if (errno != EINPROGRESS) + goto exit; + + FD_ZERO(&writefds); + FD_SET(s, &writefds); + if (select(s + 1, NULL, &writefds, NULL, timeout) < 1) + goto exit; + + len = sizeof(error); + if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) && !error) + r = 0; + + exit: + fcntl(s, F_SETFL, origflags); + return r; +} + +int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) { int s; struct addrinfo *res; + struct timeval to; s = -1; + if (timeout) { + if (addrinfo && addrinfo->ai_next && timeout > 5) + timeout = 5; + to.tv_sec = timeout; + to.tv_usec = 0; + } + for (res = addrinfo; res; res = res->ai_next) { s = bindtoaddr(src, res->ai_family, 1, 1); if (s < 0) { debug(DBG_WARN, "connecttoserver: socket failed"); continue; } - if (connect(s, res->ai_addr, res->ai_addrlen) == 0) + if ((timeout + ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, &to) + : connect(s, res->ai_addr, res->ai_addrlen)) == 0) break; debug(DBG_WARN, "connecttoserver: connect failed"); close(s); @@ -11,12 +11,10 @@ char *stringcopy(const char *s, int len); char *addr2string(struct sockaddr *addr); -int addr_equal(struct sockaddr *a, struct sockaddr *b); struct sockaddr *addr_copy(struct sockaddr *in); -uint16_t port_get(struct sockaddr *sa); void port_set(struct sockaddr *sa, uint16_t port); void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len); -int connectport(int type, char *host, char *port); int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only); -int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src); +int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout); + |