diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | COPYING | 5 | ||||
-rw-r--r-- | ChangeLog | 0 | ||||
-rw-r--r-- | Makefile.am | 14 | ||||
-rw-r--r-- | README | 19 | ||||
-rw-r--r-- | acinclude.m4 | 47 | ||||
-rw-r--r-- | autogen.sh | 3 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | radsecproxy.c | 784 | ||||
-rw-r--r-- | radsecproxy.conf-example | 86 | ||||
-rw-r--r-- | radsecproxy.h | 18 |
11 files changed, 557 insertions, 426 deletions
@@ -0,0 +1 @@ +Stig Venaas <venaas@uninett.no> @@ -0,0 +1,5 @@ +Copyright (C) 2006, 2007 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..119021b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,14 @@ +bin_PROGRAMS = radsecproxy + +radsecproxy_SOURCES = radsecproxy.c \ + util.c \ + debug.c \ + radsecproxy.h \ + debug.h + +radsecproxy_CFLAGS = -g -Wall -pedantic -pthread @SSL_CFLAGS@ +radsecproxy_LDFLAGS = @SSL_LDFLAGS@ +radsecproxy_LDADD = @SSL_LIBS@ + +sysconfdir = ${prefix}/etc/radsecproxy +dist_sysconf_DATA = $(srcdir)/radsecproxy.conf-example @@ -2,20 +2,31 @@ This is a beta version of a generic RADIUS proxy that can support various RADIUS clients over UDP or TLS (RadSec). It should build on most Linux and BSD platforms by simply typing -"make". To use it you need to create a config file called +"make". You may also try to use autoconf if you like. + +To use it you need to create a config file called "radsecproxy.conf" which must be in /etc/radsecproxy (unless you alter it in the header file), the current directory, or you can specify the location with the "-c" command line option (see below). See the enclosed example file for further instructions. -There are three options that may be specified on the command line. +There are four options that may be specified on the command line. "-c configfile" to specify a non-default config file path; "-d loglevel" to set a loglevel of 1, 2, 3 or 4 where 4 is the most detailed; and "-f" to run the proxy in the foreground with logging to stderr. Without "-f" the default is to detach as a daemon and -log to syslog. +log to syslog. Finally "-v" just prints version information and +exits. + +Thanks to Stefan Winter and Andreas Solberg for making me do this, +and the funding from GEANT2. Stefan as well as Kolbjørn Barmen +and Maja Wolniewicz have helped with early testing of the code. +All of the above plus Milan Sova have provided good feedback on +several implementation choices. Finally thanks to Hans Zandbelt +for providing the autoconf stuff. I may have forgotten someone, +let me know if you feel left out. For more information, feedback etc. contact <venaas@uninett.no>. -Stig Venaas, 2007.05.15 +Stig Venaas, 2007.05.24 diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..f854b0e --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,47 @@ +dnl Based on the one from the Boinc project by Reinhard + +AC_DEFUN([AX_CHECK_SSL], +[AC_MSG_CHECKING(for OpenSSL) +SSL_DIR= +found_ssl="no" +AC_ARG_WITH(ssl, + AC_HELP_STRING([--with-ssl], + [Use SSL (in specified installation directory)]), + [check_ssl_dir="$withval"], + [check_ssl_dir=]) +for dir in $check_ssl_dir /usr /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local ; do + ssldir="$dir" + if test -f "$dir/include/openssl/ssl.h"; then + found_ssl="yes"; + SSL_DIR="${ssldir}" + SSL_CFLAGS="-I$ssldir/include -I$ssldir/include/openssl"; + break; + fi + if test -f "$dir/include/ssl.h"; then + found_ssl="yes"; + SSL_DIR="${ssldir}" + SSL_CFLAGS="-I$ssldir/include/"; + break + fi +done +AC_MSG_RESULT($found_ssl) +if test x_$found_ssl != x_yes; then + AC_MSG_ERROR([ +---------------------------------------------------------------------- + Cannot find SSL libraries. + + Please install OpenSSL or specify installation directory with + --with-ssl=(dir). +---------------------------------------------------------------------- +]) +else + printf "OpenSSL found in $ssldir\n"; + SSL_LIBS="-lssl -lcrypto"; + SSL_LDFLAGS="-L$ssldir/lib"; + AC_DEFINE_UNQUOTED([USE_OPENSSL],[1], + ["Define to 1 if you want to use the OpenSSL crypto library"]) + AC_SUBST(SSL_CFLAGS) + AC_SUBST(SSL_LDFLAGS) + AC_SUBST(SSL_LIBS) +fi +])dnl diff --git a/autogen.sh b/autogen.sh new file mode 100644 index 0000000..25bf74f --- /dev/null +++ b/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh +touch NEWS +autoreconf --force --install diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c5ea60d --- /dev/null +++ b/configure.ac @@ -0,0 +1,6 @@ +AC_INIT(radsecproxy, 0.1, venaas@uninett.no) +AM_INIT_AUTOMAKE +AC_PROG_CC +AM_PROG_CC_C_O +AX_CHECK_SSL +AC_OUTPUT(Makefile) diff --git a/radsecproxy.c b/radsecproxy.c index 76a4860..779e9f4 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -6,12 +6,6 @@ * copyright notice and this permission notice appear in all copies. */ -/* TODO: - * accounting - * radius keep alives (server status) - * setsockopt(keepalive...), check if openssl has some keepalive feature -*/ - /* For UDP there is one server instance consisting of udpserverrd and udpserverth * rd is responsible for init and launching wr * For TLS there is a server instance that launches tlsserverrd for each TLS peer @@ -56,6 +50,7 @@ static struct options options; static struct client *clients = NULL; static struct server *servers = NULL; static struct realm *realms = NULL; +static struct tls *tls = NULL; static int client_udp_count = 0; static int client_tls_count = 0; @@ -64,6 +59,7 @@ static int server_udp_count = 0; static int server_tls_count = 0; static int server_count = 0; static int realm_count = 0; +static int tls_count = 0; static struct peer *tcp_server_listen; static struct peer *udp_server_listen; @@ -71,7 +67,6 @@ static struct replyq udp_server_replyq; static int udp_server_sock = -1; static pthread_mutex_t *ssl_locks; static long *ssl_lock_count; -static SSL_CTX *ssl_ctx = NULL; extern int optind; extern char *optarg; @@ -138,56 +133,6 @@ static int verify_cb(int ok, X509_STORE_CTX *ctx) { return ok; } -SSL_CTX *ssl_init() { - SSL_CTX *ctx; - int i; - unsigned long error; - - if (!options.tlscertificatefile || !options.tlscertificatekeyfile) - debugx(1, DBG_ERR, "TLSCertificateFile and TLSCertificateKeyFile must be specified for TLS"); - - if (!options.tlscacertificatefile && !options.tlscacertificatepath) - debugx(1, DBG_ERR, "CA Certificate file/path need to be configured"); - - ssl_locks = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); - ssl_lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long)); - for (i = 0; i < CRYPTO_num_locks(); i++) { - ssl_lock_count[i] = 0; - pthread_mutex_init(&ssl_locks[i], NULL); - } - CRYPTO_set_id_callback(ssl_thread_id); - CRYPTO_set_locking_callback(ssl_locking_callback); - - SSL_load_error_strings(); - SSL_library_init(); - - while (!RAND_status()) { - time_t t = time(NULL); - pid_t pid = getpid(); - RAND_seed((unsigned char *)&t, sizeof(time_t)); - RAND_seed((unsigned char *)&pid, sizeof(pid)); - } - - ctx = SSL_CTX_new(TLSv1_method()); - if (options.tlscertificatekeypassword) { - SSL_CTX_set_default_passwd_cb_userdata(ctx, options.tlscertificatekeypassword); - SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); - } - if (SSL_CTX_use_certificate_chain_file(ctx, options.tlscertificatefile) && - SSL_CTX_use_PrivateKey_file(ctx, options.tlscertificatekeyfile, SSL_FILETYPE_PEM) && - SSL_CTX_check_private_key(ctx) && - SSL_CTX_load_verify_locations(ctx, options.tlscacertificatefile, options.tlscacertificatepath)) { - 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); - return ctx; - } - - while ((error = ERR_get_error())) - debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); - debug(DBG_ERR, "Error initialising SSL/TLS"); - exit(1); -} - #ifdef DEBUG void printauth(char *s, unsigned char *t) { int i; @@ -219,7 +164,8 @@ int resolvepeer(struct peer *peer, int ai_flags) { int connecttoserver(struct addrinfo *addrinfo) { int s; struct addrinfo *res; - + + s = -1; for (res = addrinfo; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) { @@ -257,7 +203,7 @@ int bindtoaddr(struct addrinfo *addrinfo) { /* returns the client with matching address, or NULL */ /* if client argument is not NULL, we only check that one client */ struct client *find_client(char type, struct sockaddr *addr, struct client *client) { - struct sockaddr_in6 *sa6; + struct sockaddr_in6 *sa6 = NULL; struct in_addr *a4 = NULL; struct client *c; int i; @@ -276,7 +222,7 @@ struct client *find_client(char type, struct sockaddr *addr, struct client *clie for (res = c->peer.addrinfo; res; res = res->ai_next) if ((a4 && res->ai_family == AF_INET && !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) || - (res->ai_family == AF_INET6 && + (sa6 && res->ai_family == AF_INET6 && !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16))) return c; if (client) @@ -289,7 +235,7 @@ struct client *find_client(char type, struct sockaddr *addr, struct client *clie /* returns the server with matching address, or NULL */ /* if server argument is not NULL, we only check that one server */ struct server *find_server(char type, struct sockaddr *addr, struct server *server) { - struct sockaddr_in6 *sa6; + struct sockaddr_in6 *sa6 = NULL; struct in_addr *a4 = NULL; struct server *s; int i; @@ -308,7 +254,7 @@ struct server *find_server(char type, struct sockaddr *addr, struct server *serv for (res = s->peer.addrinfo; res; res = res->ai_next) if ((a4 && res->ai_family == AF_INET && !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) || - (res->ai_family == AF_INET6 && + (sa6 && res->ai_family == AF_INET6 && !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16))) return s; if (server) @@ -466,7 +412,7 @@ void tlsconnect(struct server *server, struct timeval *when, char *text) { } SSL_free(server->peer.ssl); - server->peer.ssl = SSL_new(ssl_ctx); + server->peer.ssl = SSL_new(server->peer.ssl_ctx); SSL_set_fd(server->peer.ssl, server->sock); if (SSL_connect(server->peer.ssl) > 0 && tlsverifycert(&server->peer)) break; @@ -681,7 +627,7 @@ unsigned char *attrget(unsigned char *attrs, int length, uint8_t type) { return NULL; } -void sendrq(struct server *to, struct client *from, struct request *rq) { +void sendrq(struct server *to, struct request *rq) { int i; uint8_t *attr; @@ -723,9 +669,15 @@ void sendrq(struct server *to, struct client *from, struct request *rq) { pthread_mutex_unlock(&to->newrq_mutex); } -void sendreply(struct client *to, struct server *from, unsigned char *buf, struct sockaddr_storage *tosa) { +void sendreply(struct client *to, unsigned char *buf, struct sockaddr_storage *tosa) { struct replyq *replyq = to->replyq; + if (!radsign(buf, (unsigned char *)to->peer.secret)) { + free(buf); + debug(DBG_WARN, "sendreply: failed to sign message"); + return; + } + pthread_mutex_lock(&replyq->count_mutex); if (replyq->count == replyq->size) { debug(DBG_WARN, "No room in queue, dropping request"); @@ -740,7 +692,7 @@ void sendreply(struct client *to, struct server *from, unsigned char *buf, struc replyq->count++; if (replyq->count == 1) { - debug(DBG_DBG, "signalling client writer"); + debug(DBG_DBG, "signalling server writer"); pthread_cond_signal(&replyq->count_cond); } pthread_mutex_unlock(&replyq->count_mutex); @@ -994,12 +946,12 @@ int msmppdecrypt(uint8_t *text, uint8_t len, uint8_t *shared, uint8_t sharedlen, return 1; } -struct server *id2server(char *id, uint8_t len) { +struct realm *id2realm(char *id, uint8_t len) { int i; for (i = 0; i < realm_count; i++) if (!regexec(&realms[i].regex, id, 0, NULL, 0)) { - debug(DBG_DBG, "found matching realm: %s, host %s", realms[i].name, realms[i].server->peer.host); - return realms[i].server; + debug(DBG_DBG, "found matching realm: %s", realms[i].name); + return realms + i; } return NULL; } @@ -1086,13 +1038,54 @@ int msmppe(unsigned char *attrs, int length, uint8_t type, char *attrtxt, struct return 1; } -struct server *radsrv(struct request *rq, unsigned char *buf, struct client *from) { +void respondstatusserver(struct request *rq) { + unsigned char *resp; + + resp = malloc(20); + if (!resp) { + debug(DBG_ERR, "respondstatusserver: malloc failed"); + return; + } + memcpy(resp, rq->buf, 20); + resp[0] = RAD_Access_Accept; + resp[2] = 0; + resp[3] = 20; + debug(DBG_DBG, "respondstatusserver: responding to %s", rq->from->peer.host); + sendreply(rq->from, resp, rq->from->peer.type == 'U' ? &rq->fromsa : NULL); +} + +void respondreject(struct request *rq, char *message) { + unsigned char *resp; + int len = 20; + + if (message) + len += 2 + strlen(message); + + resp = malloc(len); + if (!resp) { + debug(DBG_ERR, "respondreject: malloc failed"); + return; + } + memcpy(resp, rq->buf, 20); + resp[0] = RAD_Access_Reject; + *(uint16_t *)(resp + 2) = htons(len); + if (message) { + resp[20] = RAD_Attr_Reply_Message; + resp[21] = len - 20; + memcpy(resp + 22, message, len - 22); + } + sendreply(rq->from, resp, rq->from->peer.type == 'U' ? &rq->fromsa : NULL); +} + +void radsrv(struct request *rq) { uint8_t code, id, *auth, *attrs, *attr; uint16_t len; - struct server *to; + struct server *to = NULL; char username[256]; - unsigned char newauth[16]; + unsigned char *buf, newauth[16]; + struct realm *realm = NULL; + buf = rq->buf; code = *(uint8_t *)buf; id = *(uint8_t *)(buf + 1); len = RADLEN(buf); @@ -1100,9 +1093,10 @@ struct server *radsrv(struct request *rq, unsigned char *buf, struct client *fro debug(DBG_DBG, "radsrv: code %d, id %d, length %d", code, id, len); - if (code != RAD_Access_Request) { - debug(DBG_INFO, "radsrv: server currently accepts only access-requests, ignoring"); - return NULL; + if (code != RAD_Access_Request && code != RAD_Status_Server) { + debug(DBG_INFO, "radsrv: server currently accepts only access-requests and status-server, ignoring"); + free(buf); + return; } len -= 20; @@ -1110,64 +1104,86 @@ struct server *radsrv(struct request *rq, unsigned char *buf, struct client *fro if (!attrvalidate(attrs, len)) { debug(DBG_WARN, "radsrv: attribute validation failed, ignoring packet"); - return NULL; + free(buf); + return; } - - attr = attrget(attrs, len, RAD_Attr_User_Name); - if (!attr) { - debug(DBG_WARN, "radsrv: ignoring request, no username attribute"); - return NULL; + + if (code == RAD_Access_Request) { + attr = attrget(attrs, len, RAD_Attr_User_Name); + if (!attr) { + debug(DBG_WARN, "radsrv: ignoring request, no username attribute"); + free(buf); + return; + } + memcpy(username, ATTRVAL(attr), ATTRVALLEN(attr)); + username[ATTRVALLEN(attr)] = '\0'; + debug(DBG_DBG, "Access Request with username: %s", username); + + realm = id2realm(username, strlen(username)); + if (!realm) { + debug(DBG_INFO, "radsrv: ignoring request, don't know where to send it"); + free(buf); + return; + } + to = realm->server; + + if (to && rqinqueue(to, rq->from, id)) { + debug(DBG_INFO, "radsrv: already got request from host %s with id %d, ignoring", rq->from->peer.host, id); + free(buf); + return; + } } - memcpy(username, ATTRVAL(attr), ATTRVALLEN(attr)); - username[ATTRVALLEN(attr)] = '\0'; - debug(DBG_DBG, "Access Request with username: %s", username); - to = id2server(username, strlen(username)); - if (!to) { - debug(DBG_INFO, "radsrv: ignoring request, don't know where to send it"); - return NULL; + attr = attrget(attrs, len, RAD_Attr_Message_Authenticator); + if (attr && (ATTRVALLEN(attr) != 16 || !checkmessageauth(buf, ATTRVAL(attr), rq->from->peer.secret))) { + debug(DBG_WARN, "radsrv: message authentication failed"); + free(buf); + return; } - if (rqinqueue(to, from, id)) { - debug(DBG_INFO, "radsrv: ignoring request from host %s with id %d, already got one", from->peer.host, id); - return NULL; + if (code == RAD_Status_Server) { + respondstatusserver(rq); + return; } - attr = attrget(attrs, len, RAD_Attr_Message_Authenticator); - if (attr && (ATTRVALLEN(attr) != 16 || !checkmessageauth(buf, ATTRVAL(attr), from->peer.secret))) { - debug(DBG_WARN, "radsrv: message authentication failed"); - return NULL; + if (!to) { + debug(DBG_INFO, "radsrv: sending reject to %s for %s", rq->from->peer.host, username); + respondreject(rq, realm->message); + return; + } + + if (!RAND_bytes(newauth, 16)) { + debug(DBG_WARN, "radsrv: failed to generate random auth"); + free(buf); + return; } - - if (!RAND_bytes(newauth, 16)) { - debug(DBG_WARN, "radsrv: failed to generate random auth"); - return NULL; - } #ifdef DEBUG printauth("auth", auth); #endif - + attr = attrget(attrs, len, RAD_Attr_User_Password); if (attr) { debug(DBG_DBG, "radsrv: found userpwdattr with value length %d", ATTRVALLEN(attr)); - if (!pwdrecrypt(ATTRVAL(attr), ATTRVALLEN(attr), from->peer.secret, to->peer.secret, auth, newauth)) - return NULL; + if (!pwdrecrypt(ATTRVAL(attr), ATTRVALLEN(attr), rq->from->peer.secret, to->peer.secret, auth, newauth)) { + free(buf); + return; + } } attr = attrget(attrs, len, RAD_Attr_Tunnel_Password); if (attr) { debug(DBG_DBG, "radsrv: found tunnelpwdattr with value length %d", ATTRVALLEN(attr)); - if (!pwdrecrypt(ATTRVAL(attr), ATTRVALLEN(attr), from->peer.secret, to->peer.secret, auth, newauth)) - return NULL; + if (!pwdrecrypt(ATTRVAL(attr), ATTRVALLEN(attr), rq->from->peer.secret, to->peer.secret, auth, newauth)) { + free(buf); + return; + } } - rq->buf = buf; - rq->from = from; rq->origid = id; memcpy(rq->origauth, auth, 16); memcpy(auth, newauth, 16); - return to; + sendrq(to, rq); } void *clientrd(void *arg) { @@ -1265,7 +1281,7 @@ void *clientrd(void *arg) { server->requests[i].received = 1; pthread_mutex_unlock(&server->newrq_mutex); free(buf); - debug(DBG_INFO, "clientrd: got status server response"); + debug(DBG_INFO, "clientrd: got status server response from %s", server->peer.host); continue; } @@ -1329,16 +1345,8 @@ void *clientrd(void *arg) { server->requests[i].received = 1; pthread_mutex_unlock(&server->newrq_mutex); - if (!radsign(buf, (unsigned char *)from->peer.secret)) { - free(buf); - debug(DBG_WARN, "clientrd: failed to sign message"); - continue; - } -#ifdef DEBUG - printauth("signedorigauth/buf+4", buf + 4); -#endif debug(DBG_DBG, "clientrd: giving packet back to where it came from"); - sendreply(from, server, buf, from->peer.type == 'U' ? &fromsa : NULL); + sendreply(from, buf, from->peer.type == 'U' ? &fromsa : NULL); } } @@ -1357,8 +1365,7 @@ void *clientwr(void *arg) { if (server->statusserver) { memset(&statsrvrq, 0, sizeof(struct request)); - statsrvrq.buf = statsrvbuf; - memset(&statsrvbuf, 0, sizeof(statsrvbuf)); + memset(statsrvbuf, 0, sizeof(statsrvbuf)); statsrvbuf[0] = RAD_Status_Server; statsrvbuf[3] = 38; statsrvbuf[20] = RAD_Attr_Message_Authenticator; @@ -1413,11 +1420,13 @@ void *clientwr(void *arg) { rq = server->requests + i; if (rq->received) { - debug(DBG_DBG, "clientwr: removing received packet from queue"); - if (*rq->buf != RAD_Status_Server) + debug(DBG_DBG, "clientwr: packet %d in queue is marked as received", i); + if (rq->buf) { + debug(DBG_DBG, "clientwr: freeing received packet %d from queue", i); free(rq->buf); - /* setting this to NULL means that it can be reused */ - rq->buf = NULL; + /* setting this to NULL means that it can be reused */ + rq->buf = NULL; + } pthread_mutex_unlock(&server->newrq_mutex); continue; } @@ -1434,9 +1443,8 @@ void *clientwr(void *arg) { ? 1 : REQUEST_RETRIES)) { debug(DBG_DBG, "clientwr: removing expired packet from queue"); if (*rq->buf == RAD_Status_Server) - debug(DBG_WARN, "clientwr: no status server response, server dead?"); - else - free(rq->buf); + debug(DBG_WARN, "clientwr: no status server response, %s dead?", server->peer.host); + free(rq->buf); /* setting this to NULL means that it can be reused */ rq->buf = NULL; pthread_mutex_unlock(&server->newrq_mutex); @@ -1452,7 +1460,6 @@ void *clientwr(void *arg) { rq->tries++; clientradput(server, server->requests[i].buf); gettimeofday(&lastsend, NULL); - usleep(200000); } if (server->statusserver) { gettimeofday(&now, NULL); @@ -1461,9 +1468,15 @@ void *clientwr(void *arg) { debug(DBG_WARN, "clientwr: failed to generate random auth"); continue; } + statsrvrq.buf = malloc(sizeof(statsrvbuf)); + if (!statsrvrq.buf) { + debug(DBG_ERR, "clientwr: malloc failed"); + continue; + } + memcpy(statsrvrq.buf, statsrvbuf, sizeof(statsrvbuf)); debug(DBG_DBG, "clientwr: sending status server to %s", server->peer.host); lastsend.tv_sec = now.tv_sec; - sendrq(server, NULL, &statsrvrq); + sendrq(server, &statsrvrq); } } } @@ -1496,9 +1509,6 @@ void *udpserverwr(void *arg) { void *udpserverrd(void *arg) { struct request rq; - unsigned char *buf; - struct server *to; - struct client *fr; pthread_t udpserverwrth; if ((udp_server_sock = bindtoaddr(udp_server_listen->addrinfo)) < 0) @@ -1511,16 +1521,9 @@ void *udpserverrd(void *arg) { debugx(1, DBG_ERR, "pthread_create failed"); for (;;) { - fr = NULL; memset(&rq, 0, sizeof(struct request)); - buf = radudpget(udp_server_sock, &fr, NULL, &rq.fromsa); - to = radsrv(&rq, buf, fr); - if (!to) { - free(buf); - debug(DBG_INFO, "udpserverrd: ignoring request, no place to send it"); - continue; - } - sendrq(to, fr, &rq); + rq.buf = radudpget(udp_server_sock, &rq.from, NULL, &rq.fromsa); + radsrv(&rq); } } @@ -1565,9 +1568,7 @@ void *tlsserverwr(void *arg) { void *tlsserverrd(void *arg) { struct request rq; - char unsigned *buf; unsigned long error; - struct server *to; int s; struct client *client = (struct client *)arg; pthread_t tlsserverwrth; @@ -1588,18 +1589,13 @@ void *tlsserverrd(void *arg) { goto errexit; } for (;;) { - buf = radtlsget(client->peer.ssl); - if (!buf) + memset(&rq, 0, sizeof(struct request)); + rq.buf = radtlsget(client->peer.ssl); + if (!rq.buf) break; debug(DBG_DBG, "tlsserverrd: got Radius message from %s", client->peer.host); - memset(&rq, 0, sizeof(struct request)); - to = radsrv(&rq, buf, client); - if (!to) { - free(buf); - debug(DBG_INFO, "tlsserverrd: ignoring request, no place to send it"); - continue; - } - sendrq(to, client, &rq); + rq.from = client; + radsrv(&rq); } debug(DBG_ERR, "tlsserverrd: connection lost"); /* stop writer by setting peer.ssl to NULL and give signal in case waiting for data */ @@ -1657,7 +1653,7 @@ int tlslistener() { close(snew); continue; } - client->peer.ssl = SSL_new(ssl_ctx); + client->peer.ssl = SSL_new(client->peer.ssl_ctx); SSL_set_fd(client->peer.ssl, snew); if (pthread_create(&tlsserverth, NULL, tlsserverrd, (void *)client)) { debug(DBG_ERR, "tlslistener: pthread_create failed"); @@ -1672,23 +1668,139 @@ int tlslistener() { return 0; } -void addrealm(char *value, char *server) { +void tlsadd(char *value, char *cacertfile, char *cacertpath, char *certfile, char *certkeyfile, char *certkeypwd) { + struct tls *new; + SSL_CTX *ctx; int i; - struct realm *realm; + unsigned long error; - for (i = 0; i < server_count; i++) - if (!strcasecmp(server, servers[i].peer.host)) + if (!certfile || !certkeyfile) + debugx(1, DBG_ERR, "TLSCertificateFile and TLSCertificateKeyFile must be specified in TLS context %s", value); + + if (!cacertfile && !cacertpath) + debugx(1, DBG_ERR, "CA Certificate file or path need to be specified in TLS context %s", value); + + if (!ssl_locks) { + ssl_locks = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); + ssl_lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long)); + for (i = 0; i < CRYPTO_num_locks(); i++) { + ssl_lock_count[i] = 0; + pthread_mutex_init(&ssl_locks[i], NULL); + } + CRYPTO_set_id_callback(ssl_thread_id); + CRYPTO_set_locking_callback(ssl_locking_callback); + + SSL_load_error_strings(); + SSL_library_init(); + + while (!RAND_status()) { + time_t t = time(NULL); + pid_t pid = getpid(); + RAND_seed((unsigned char *)&t, sizeof(time_t)); + RAND_seed((unsigned char *)&pid, sizeof(pid)); + } + } + ctx = SSL_CTX_new(TLSv1_method()); + if (certkeypwd) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, certkeypwd); + SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); + } + if (!SSL_CTX_use_certificate_chain_file(ctx, certfile) || + !SSL_CTX_use_PrivateKey_file(ctx, certkeyfile, SSL_FILETYPE_PEM) || + !SSL_CTX_check_private_key(ctx) || + !SSL_CTX_load_verify_locations(ctx, cacertfile, cacertpath)) { + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + debugx(1, DBG_ERR, "Error initialising SSL/TLS in TLS context %s", value); + } + + 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); + + tls_count++; + tls = realloc(tls, tls_count * sizeof(struct tls)); + if (!tls) + debugx(1, DBG_ERR, "malloc failed"); + new = tls + tls_count - 1; + memset(new, 0, sizeof(struct tls)); + new->name = stringcopy(value, 0); + if (!new->name) + debugx(1, DBG_ERR, "malloc failed"); + new->ctx = ctx; + new->count = 0; + debug(DBG_DBG, "tlsadd: added TLS context %s", value); +} + +void tlsfree() { + int i; + for (i = 0; i < tls_count; i++) + if (!tls[i].count) + SSL_CTX_free(tls[i].ctx); + tls_count = 0; + free(tls); + tls = NULL; +} + +SSL_CTX *tlsgetctx(char *alt1, char *alt2) { + int i, c1 = -1, c2 = -1; + for (i = 0; i < tls_count; i++) { + if (!strcasecmp(tls[i].name, alt1)) { + c1 = i; break; - if (i == server_count) - debugx(1, DBG_ERR, "addrealm failed, no server %s", server); + } + if (c2 == -1 && alt2 && !strcasecmp(tls[i].name, alt2)) + c2 = i; + } + + i = (c1 == -1 ? c2 : c1); + if (i == -1) + return NULL; + tls[i].count++; + return tls[i].ctx; +} + +void addrealm(char *value, char *server, char *message) { + int i, n; + struct realm *realm; + char *s, *regex = NULL; - /* temporary warnings */ - if (*value == '*') - debugx(1, DBG_ERR, "Regexps are now used for specifying realms, a string\nstarting with '*' is meaningless, you probably want '.*' for matching everything\nEXITING\n"); - if (value[strlen(value) - 1] != '$' && value[strlen(value) - 1] != '*') { - debug(DBG_ERR, "Regexps are now used for specifying realms, you\nprobably want to rewrite this as e.g. '@example\\.com$' or '\\.com$'\nYou can even do things like '^[a-n].*@example\\.com$' to make about half of the\nusers use this server. Note that the matching is case insensitive.\n"); - sleep(3); + if (server) { + for (i = 0; i < server_count; i++) + if (!strcasecmp(server, servers[i].peer.host)) + break; + if (i == server_count) + debugx(1, DBG_ERR, "addrealm failed, no server %s", server); } + + if (*value == '/') { + /* regexp, remove optional trailing / if present */ + if (value[strlen(value) - 1] == '/') + value[strlen(value) - 1] = '\0'; + } else { + /* not a regexp, let us make it one */ + if (*value == '*' && !value[1]) + regex = stringcopy(".*", 0); + else { + for (n = 0, s = value; *s;) + if (*s++ == '.') + n++; + regex = malloc(strlen(value) + n + 3); + if (regex) { + regex[0] = '@'; + for (n = 1, s = value; *s; s++) { + if (*s == '.') + regex[n++] = '\\'; + regex[n++] = *s; + } + regex[n++] = '$'; + regex[n] = '\0'; + } + } + if (!regex) + debugx(1, DBG_ERR, "malloc failed"); + debug(DBG_DBG, "addrealm: constructed regexp %s from %s", regex, value); + } + realm_count++; realms = realloc(realms, realm_count * sizeof(struct realm)); if (!realms) @@ -1696,9 +1808,17 @@ void addrealm(char *value, char *server) { realm = realms + realm_count - 1; memset(realm, 0, sizeof(struct realm)); realm->name = stringcopy(value, 0); - realm->server = servers + i; - if (regcomp(&realm->regex, value, REG_ICASE | REG_NOSUB)) - debugx(1, DBG_ERR, "addrealm: failed to compile regular expression %s", value); + if (!realm->name) + debugx(1, DBG_ERR, "malloc failed"); + if (message && strlen(message) > 253) + debugx(1, DBG_ERR, "ReplyMessage can be at most 253 bytes"); + realm->message = message; + if (server) + realm->server = servers + i; + if (regcomp(&realm->regex, regex ? regex : value + 1, REG_ICASE | REG_NOSUB)) + debugx(1, DBG_ERR, "addrealm: failed to compile regular expression %s", regex ? regex : value + 1); + if (regex) + free(regex); debug(DBG_DBG, "addrealm: added realm %s for server %s", value, server); } @@ -1740,58 +1860,9 @@ char *parsehostport(char *s, struct peer *peer) { return p; } -/* TODO remove this */ -/* * is default, else longest match ... ";" used for separator */ -char *parserealmlist(char *s, struct server *server) { -#if 0 - char *p; - int i, n, l; - char *realmdata; - char **realms; - - for (p = s, n = 1; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++) - if (*p == ';') - n++; - l = p - s; - if (!l) - debugx(1, DBG_ERR, "realm list must be specified"); - - realmdata = stringcopy(s, l); - realms = malloc((1+n) * sizeof(char *)); - if (!realms) - debugx(1, DBG_ERR, "malloc failed"); - realms[0] = realmdata; - for (n = 1, i = 0; i < l; i++) - if (realmdata[i] == ';') { - realmdata[i] = '\0'; - realms[n++] = realmdata + i + 1; - } - for (i = 0; i < n; i++) - addrealm(realms[i], server->peer.host); - free(realms); - free(realmdata); - return p; -#else - char *start; - char *realm; - - for (start = s;; s++) - if (!*s || *s == ';' || *s == ' ' || *s == '\t' || *s == '\n') { - if (s - start > 0) { - realm = stringcopy(start, s - start); - addrealm(realm, server->peer.host); - free(realm); - } - if (*s != ';') - return s; - start = s + 1; - } -#endif -} - FILE *openconfigfile(const char *filename) { FILE *f; - char pathname[100], *base; + char pathname[100], *base = NULL; f = fopen(filename, "r"); if (f) { @@ -1813,133 +1884,6 @@ FILE *openconfigfile(const char *filename) { return f; } -/* exactly one argument must be non-NULL */ -void getconfig(const char *serverfile, const char *clientfile) { - FILE *f; - char line[1024]; - char *p, *field; - struct client *client; - struct server *server; - struct peer *peer; - int i, count, *ucount, *tcount; - - f = openconfigfile(serverfile ? serverfile : clientfile); - if (serverfile) { - ucount = &server_udp_count; - tcount = &server_tls_count; - } else { - ucount = &client_udp_count; - tcount = &client_tls_count; - } - while (fgets(line, 1024, f)) { - for (p = line; *p == ' ' || *p == '\t'; p++); - switch (*p) { - case '#': - case '\n': - break; - case 'T': - (*tcount)++; - break; - case 'U': - (*ucount)++; - break; - default: - debugx(1, DBG_ERR, "type must be U or T, got %c", *p); - } - } - - if (serverfile) { - count = server_count = server_udp_count + server_tls_count; - servers = calloc(count, sizeof(struct server)); - if (!servers) - debugx(1, DBG_ERR, "malloc failed"); - } else { - if (client_udp_count) { - udp_server_replyq.replies = malloc(client_udp_count * MAX_REQUESTS * sizeof(struct reply)); - if (!udp_server_replyq.replies) - debugx(1, DBG_ERR, "malloc failed"); - udp_server_replyq.size = client_udp_count * MAX_REQUESTS; - udp_server_replyq.count = 0; - pthread_mutex_init(&udp_server_replyq.count_mutex, NULL); - pthread_cond_init(&udp_server_replyq.count_cond, NULL); - } - - count = client_count = client_udp_count + client_tls_count; - clients = calloc(count, sizeof(struct client)); - if (!clients) - debugx(1, DBG_ERR, "malloc failed"); - } - - rewind(f); - for (i = 0; i < count && fgets(line, 1024, f);) { - if (serverfile) { - server = &servers[i]; - peer = &server->peer; - } else { - client = &clients[i]; - peer = &client->peer; - } - for (p = line; *p == ' ' || *p == '\t'; p++); - if (*p == '#' || *p == '\n') - continue; - peer->type = *p; /* we already know it must be U or T */ - for (p++; *p == ' ' || *p == '\t'; p++); - p = parsehostport(p, peer); - for (; *p == ' ' || *p == '\t'; p++); - if (serverfile) { - p = parserealmlist(p, server); - for (; *p == ' ' || *p == '\t'; p++); - } - field = p; - for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++); - if (field == p) { - /* no secret set and end of line, line is complete if TLS */ - if (peer->type == 'U') - debugx(1, DBG_ERR, "secret must be specified for UDP"); - peer->secret = stringcopy(DEFAULT_TLS_SECRET, 0); - } else { - peer->secret = stringcopy(field, p - field); - /* check that rest of line only white space */ - for (; *p == ' ' || *p == '\t'; p++); - if (*p && *p != '\n') - debugx(1, DBG_ERR, "max 4 fields per line, found a 5th"); - } - - if ((serverfile && !resolvepeer(&server->peer, 0)) || - (clientfile && !resolvepeer(&client->peer, 0))) - debugx(1, DBG_ERR, "failed to resolve host %s port %s, exiting", peer->host, peer->port); - - if (serverfile) { - pthread_mutex_init(&server->lock, NULL); - server->sock = -1; - server->requests = calloc(MAX_REQUESTS, sizeof(struct request)); - if (!server->requests) - debugx(1, DBG_ERR, "malloc failed"); - server->newrq = 0; - pthread_mutex_init(&server->newrq_mutex, NULL); - pthread_cond_init(&server->newrq_cond, NULL); - } else { - if (peer->type == 'U') - client->replyq = &udp_server_replyq; - else { - client->replyq = malloc(sizeof(struct replyq)); - if (!client->replyq) - debugx(1, DBG_ERR, "malloc failed"); - client->replyq->replies = calloc(MAX_REQUESTS, sizeof(struct reply)); - if (!client->replyq->replies) - debugx(1, DBG_ERR, "malloc failed"); - client->replyq->size = MAX_REQUESTS; - client->replyq->count = 0; - pthread_mutex_init(&client->replyq->count_mutex, NULL); - pthread_cond_init(&client->replyq->count_cond, NULL); - } - } - debug(DBG_DBG, "got type %c, host %s, port %s, secret %s", peer->type, peer->host, peer->port, peer->secret); - i++; - } - fclose(f); -} - struct peer *server_create(char type) { struct peer *server; char *conf; @@ -1963,6 +1907,37 @@ struct peer *server_create(char type) { return server; } +/* returns NULL on error, where to continue parsing if token and ok. E.g. "" will return token with empty string */ +char *strtokenquote(char *s, char **token, char *del, char *quote, char *comment) { + char *t = s, *q, *r; + + if (!t || !token || !del) + return NULL; + while (*t && strchr(del, *t)) + t++; + if (!*t || (comment && strchr(comment, *t))) { + *token = NULL; + return t + 1; /* needs to be non-NULL, but value doesn't matter */ + } + if (quote && (q = strchr(quote, *t))) { + t++; + r = t; + while (*t && *t != *q) + t++; + if (!*t || (t[1] && !strchr(del, t[1]))) + return NULL; + *t = '\0'; + *token = r; + return t + 1; + } + *token = t; + t++; + while (*t && !strchr(del, *t)) + t++; + *t = '\0'; + return t + 1; +} + /* Parses config with following syntax: * One of these: * option-name value @@ -1976,17 +1951,24 @@ struct peer *server_create(char type) { void getgeneralconfig(FILE *f, char *block, ...) { va_list ap; char line[1024]; - char *tokens[3], *opt, *val, *word, **str; - int type, tcount, conftype; - void (*cbk)(FILE *, char *, char *); + /* initialise lots of stuff to avoid stupid compiler warnings */ + char *tokens[3], *s, *opt = NULL, *val = NULL, *word, *optval, **str = NULL; + int type = 0, tcount, conftype = 0; + void (*cbk)(FILE *, char *, char *, char *) = NULL; while (fgets(line, 1024, f)) { - tokens[0] = strtok(line, " \t\n"); - if (!*tokens || **tokens == '#') + s = line; + for (tcount = 0; tcount < 3; tcount++) { + s = strtokenquote(s, &tokens[tcount], " \t\n", "\"'", tcount ? NULL : "#"); + if (!s) + debugx(1, DBG_ERR, "Syntax error in line starting with: %s", line); + if (!tokens[tcount]) + break; + } + if (!tcount || **tokens == '#') continue; - for (tcount = 1; tcount < 3 && (tokens[tcount] = strtok(NULL, " \t\n")); tcount++); - - if (tcount && **tokens == '}') { + + if (**tokens == '}') { if (block) return; debugx(1, DBG_ERR, "configuration error, found } with no matching {"); @@ -2018,6 +2000,9 @@ void getgeneralconfig(FILE *f, char *block, ...) { debugx(1, DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]); } + if (!*val) + debugx(1, DBG_ERR, "configuration error, option %s needs a non-empty value", opt); + va_start(ap, block); while ((word = va_arg(ap, char *))) { type = va_arg(ap, int); @@ -2028,7 +2013,7 @@ void getgeneralconfig(FILE *f, char *block, ...) { debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error"); break; case CONF_CBK: - cbk = va_arg(ap, void (*)(FILE *, char *, char *)); + cbk = va_arg(ap, void (*)(FILE *, char *, char *, char *)); break; default: debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error"); @@ -2059,7 +2044,12 @@ void getgeneralconfig(FILE *f, char *block, ...) { *str = stringcopy(val, 0); break; case CONF_CBK: - cbk(f, opt, val); + optval = malloc(strlen(opt) + strlen(val) + 2); + if (!optval) + debugx(1, DBG_ERR, "malloc failed"); + sprintf(optval, "%s %s", opt, val); + cbk(f, optval, opt, val); + free(optval); break; default: debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error"); @@ -2067,23 +2057,19 @@ void getgeneralconfig(FILE *f, char *block, ...) { } } -void confclsrv_cb(FILE *f, char *opt, char *val) { - char *type = NULL, *secret = NULL, *port = NULL, *statusserver = NULL; - char *block; +void confclsrv_cb(FILE *f, char *block, char *opt, char *val) { + char *type = NULL, *secret = NULL, *port = NULL, *tls = NULL, *statusserver = NULL; struct client *client = NULL; struct server *server = NULL; struct peer *peer; - - block = malloc(strlen(opt) + strlen(val) + 2); - if (!block) - debugx(1, DBG_ERR, "malloc failed"); - sprintf(block, "%s %s", opt, val); + debug(DBG_DBG, "confclsrv_cb called for %s", block); if (!strcasecmp(opt, "client")) { getgeneralconfig(f, block, "type", CONF_STR, &type, "secret", CONF_STR, &secret, + "tls", CONF_STR, &tls, NULL ); client_count++; @@ -2098,6 +2084,7 @@ void confclsrv_cb(FILE *f, char *opt, char *val) { "type", CONF_STR, &type, "secret", CONF_STR, &secret, "port", CONF_STR, &port, + "tls", CONF_STR, &tls, "StatusServer", CONF_STR, &statusserver, NULL ); @@ -2130,14 +2117,18 @@ void confclsrv_cb(FILE *f, char *opt, char *val) { peer->port = stringcopy(DEFAULT_UDP_PORT, 0); } } else if (type && !strcasecmp(type, "tls")) { - peer->type = 'T'; - if (client) + if (client) { + peer->ssl_ctx = tls ? tlsgetctx(tls, NULL) : tlsgetctx("defaultclient", "default"); client_tls_count++; - else { + } else { + peer->ssl_ctx = tls ? tlsgetctx(tls, NULL) : tlsgetctx("defaultserver", "default"); server_tls_count++; if (!port) peer->port = stringcopy(DEFAULT_TLS_PORT, 0); } + if (!peer->ssl_ctx) + debugx(1, DBG_ERR, "error in block %s, no tls context defined", block); + peer->type = 'T'; } else debugx(1, DBG_ERR, "error in block %s, type must be set to UDP or TLS", block); free(type); @@ -2178,30 +2169,43 @@ void confclsrv_cb(FILE *f, char *opt, char *val) { pthread_mutex_init(&server->newrq_mutex, NULL); pthread_cond_init(&server->newrq_cond, NULL); } - - free(block); } -void confrealm_cb(FILE *f, char *opt, char *val) { - char *server = NULL; - char *block; +void confrealm_cb(FILE *f, char *block, char *opt, char *val) { + char *server = NULL, *msg = NULL; - block = malloc(strlen(opt) + strlen(val) + 2); - if (!block) - debugx(1, DBG_ERR, "malloc failed"); - sprintf(block, "%s %s", opt, val); debug(DBG_DBG, "confrealm_cb called for %s", block); getgeneralconfig(f, block, "server", CONF_STR, &server, + "ReplyMessage", CONF_STR, &msg, NULL ); - if (!server) - debugx(1, DBG_ERR, "error in block %s, server must be specified", block); - addrealm(val, server); + addrealm(val, server, msg); free(server); - free(block); +} + +void conftls_cb(FILE *f, char *block, char *opt, char *val) { + char *cacertfile = NULL, *cacertpath = NULL, *certfile = NULL, *certkeyfile = NULL, *certkeypwd = NULL; + + debug(DBG_DBG, "conftls_cb called for %s", block); + + getgeneralconfig(f, block, + "CACertificateFile", CONF_STR, &cacertfile, + "CACertificatePath", CONF_STR, &cacertpath, + "CertificateFile", CONF_STR, &certfile, + "CertificateKeyFile", CONF_STR, &certkeyfile, + "CertificateKeyPassword", CONF_STR, &certkeypwd, + NULL + ); + + tlsadd(val, cacertfile, cacertpath, certfile, certkeyfile, certkeypwd); + free(cacertfile); + free(cacertpath); + free(certfile); + free(certkeyfile); + free(certkeypwd); } void getmainconfig(const char *configfile) { @@ -2212,11 +2216,6 @@ void getmainconfig(const char *configfile) { memset(&options, 0, sizeof(options)); getgeneralconfig(f, NULL, - "TLSCACertificateFile", CONF_STR, &options.tlscacertificatefile, - "TLSCACertificatePath", CONF_STR, &options.tlscacertificatepath, - "TLSCertificateFile", CONF_STR, &options.tlscertificatefile, - "TLSCertificateKeyFile", CONF_STR, &options.tlscertificatekeyfile, - "TLSCertificateKeyPassword", CONF_STR, &options.tlscertificatekeypassword, "ListenUDP", CONF_STR, &options.listenudp, "ListenTCP", CONF_STR, &options.listentcp, "LogLevel", CONF_STR, &loglevel, @@ -2224,10 +2223,12 @@ void getmainconfig(const char *configfile) { "Client", CONF_CBK, confclsrv_cb, "Server", CONF_CBK, confclsrv_cb, "Realm", CONF_CBK, confrealm_cb, + "TLS", CONF_CBK, conftls_cb, NULL ); fclose(f); - + tlsfree(); + if (loglevel) { if (strlen(loglevel) != 1 || *loglevel < '1' || *loglevel > '4') debugx(1, DBG_ERR, "error in %s, value of option LogLevel is %s, must be 1, 2, 3 or 4", configfile, loglevel); @@ -2287,6 +2288,7 @@ int main(int argc, char **argv) { getargs(argc, argv, &foreground, &loglevel, &configfile); if (loglevel) debug_set_level(loglevel); + debug(DBG_INFO, "radsecproxy revision $Rev$ starting"); getmainconfig(configfile ? configfile : CONFIG_MAIN); if (loglevel) options.loglevel = loglevel; @@ -2300,13 +2302,10 @@ int main(int argc, char **argv) { debug_set_destination(options.logdestination); } - /* TODO remove getconfig completely when all use new config method */ if (!server_count) - getconfig(CONFIG_SERVERS, NULL); + debugx(1, DBG_ERR, "No servers configured, nothing to do, exiting"); if (!client_count) - getconfig(NULL, CONFIG_CLIENTS); - - /* TODO exit if not at least one client and one server configured */ + debugx(1, DBG_ERR, "No clients configured, nothing to do, exiting"); if (!realm_count) debugx(1, DBG_ERR, "No realms configured, nothing to do, exiting"); @@ -2319,9 +2318,6 @@ int main(int argc, char **argv) { debugx(1, DBG_ERR, "pthread_create failed"); } - if (client_tls_count || server_tls_count) - ssl_ctx = ssl_init(); - for (i = 0; i < server_count; i++) if (pthread_create(&servers[i].clientth, NULL, clientwr, (void *)&servers[i])) debugx(1, DBG_ERR, "pthread_create failed"); diff --git a/radsecproxy.conf-example b/radsecproxy.conf-example index 9facde7..029c7e1 100644 --- a/radsecproxy.conf-example +++ b/radsecproxy.conf-example @@ -1,17 +1,8 @@ #Master config file, must be in /etc/radsecproxy or proxy's current directory # All possible config options are listed below -# -# You must specify at least one of TLSCACertificateFile or TLSCACertificatePath -# for TLS to work. We always verify peer certificate (both client and server) -#TLSCACertificateFile /etc/cacerts/CA.pem -TLSCACertificatePath /etc/cacerts - -# You must specify the below for TLS, we will always present our certificate -TLSCertificateFile /etc/hostcertkey/host.example.com.pem -TLSCertificateKeyFile /etc/hostcertkey/host.example.com.key.pem -# Optionally specify password if key is encrypted (not very secure) -TLSCertificateKeyPassword follow the white rabbit +# First you may define any global options, these are: +# # You can optionally specify addresses and ports to listen on # Max one of each, below are just multiple examples #ListenUDP *:1814 @@ -29,20 +20,55 @@ TLSCertificateKeyPassword follow the white rabbit #LogDestination x-syslog:// #LogDestination x-syslog://log_local2 +#If we have TLS clients or servers we must define at least one tls block. +#You can name them whatever you like and then reference them by name when +#specifying clients or servers later. There are however three special names +#"default", "defaultclient" and "defaultserver". If no name is defined for +#a client, the "defaultclient" block will be used if it exists, if not the +#"default" will be used. For a server, "defaultserver" followed by "default" +#will be checked. +# +#The simplest configuration you can do is: +tls default { + # You must specify at least one of CACertificateFile or CACertificatePath + # for TLS to work. We always verify peer certificate (client and server) + # CACertificateFile /etc/cacerts/CA.pem + CACertificatePath /etc/cacerts + + # You must specify the below for TLS, we always present our certificate + CertificateFile /etc/hostcertkey/host.example.com.pem + CertificateKeyFile /etc/hostcertkey/host.example.com.key.pem + # Optionally specify password if key is encrypted (not very secure) + CertificateKeyPassword "follow the white rabbit" +} + +#If you want one cert for all clients and another for all servers, use +#defaultclient and defaultserver instead of default. If we wanted some +#particular server to use something else you could specify a block +#"tls myserver" and then reference that for that server. If you always +#name the tls block in the client/server config you don't need a default + #Now we configure clients, servers and realms. Note that these and #also the lines above may be in any order, except that a realm #can only be configured to use a server that is previously configured. -#Also note that case insensitive regexp is used for realms, matching -#the entire username string. The matching is done in the order the -#realms are specified, using the first match found. Some examples are +#A realm can be a literal domain name, * which matches all, or a +#regexp. A regexp is specified by the character prefix / +#For regexp we do case insensitive matching of the entire username string. +#The matching of realms is done in the order they are specified, using the +#first match found. Some examples are #"@example\.com$", "\.com$", ".*" and "^[a-z].*@example\.com$". #To treat local users separately you might try first specifying "@" -#and after that ".*". +#and after that "*". client 2001:db8::1 { type tls secret verysecret +#we could specify tls here, e.g. +# tls myclient +#in order to use tls parameters named myclient. We don't, so we will +#use "tls defaultclient" if defined, or look for "tls default" as a +#last resort } client 127.0.0.1 { type udp @@ -57,7 +83,7 @@ server 127.0.0.1 { type UDP secret secret } -realm @eduroam\.cc$ { +realm eduroam.cc { server 127.0.0.1 } @@ -65,18 +91,38 @@ server 2001:db8::1 { type TLS port 2283 # secret is optional for TLS +#we could specify tls here, e.g. +# tls myserver +#in order to use tls parameters named myserver. We don't, so we will +#use "tls defaultserver" if defined, or look for "tls default" as a +#last resort } server radius.example.com { type tls secret verysecret + StatusServer on +# statusserver is optional, can be on or off. Off is default } -realm @example\.com$ { +# Equivalent to example.com +realm /@example\.com$ { server 2001:db8::1 } -realm \.com$ { - server 2001:db8::1 +# One can define a realm without servers, the proxy will then reject +# and requests matching this. Optionally one can specify ReplyMessage +# attribute to be included in the reject message. +# +realm /\.com$ { } -realm .* { +realm /^anonymous$ { + replymessage "No Access" +} +# The realm below is equivalent to /.* +realm * { server radius.example.com } +#If you don't have a default server you probably want to +#reject all unknowns. Optionally you can also include a message +#realm * { + replymessage "User unknown" +} diff --git a/radsecproxy.h b/radsecproxy.h index b4dcfe6..c6b2b19 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -8,9 +8,7 @@ #define DEBUG_LEVEL 3 -#define CONFIG_MAIN "/etc/radsecproxy/radsecproxy.conf" -#define CONFIG_SERVERS "/etc/radsecproxy/servers.conf" -#define CONFIG_CLIENTS "/etc/radsecproxy/clients.conf" +#define CONFIG_MAIN "/etc/radsecproxy.conf" /* MAX_REQUESTS must be 256 due to Radius' 8 bit ID field */ #define MAX_REQUESTS 256 @@ -32,6 +30,7 @@ #define RAD_Attr_User_Name 1 #define RAD_Attr_User_Password 2 +#define RAD_Attr_Reply_Message 18 #define RAD_Attr_Vendor_Specific 26 #define RAD_Attr_Tunnel_Password 69 #define RAD_Attr_Message_Authenticator 80 @@ -43,11 +42,6 @@ #define CONF_CBK 2 struct options { - char *tlscacertificatefile; - char *tlscacertificatepath; - char *tlscertificatefile; - char *tlscertificatekeyfile; - char *tlscertificatekeypassword; char *listenudp; char *listentcp; char *logdestination; @@ -86,6 +80,7 @@ struct peer { char *port; char *secret; SSL *ssl; + SSL_CTX *ssl_ctx; struct addrinfo *addrinfo; }; @@ -111,10 +106,17 @@ struct server { struct realm { char *name; + char *message; regex_t regex; struct server *server; }; +struct tls { + char *name; + SSL_CTX *ctx; + int count; +}; + #define RADLEN(x) ntohs(((uint16_t *)(x))[1]) #define ATTRTYPE(x) ((x)[0]) |