Newer
Older
* 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
* copyright notice and this permission notice appear in all copies.
*/
#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 <pthread.h>
#include <openssl/ssl.h>
#include <openssl/err.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 initextradtls();
static const struct protodefs protodefs = {
"dtls",
"radsec", /* 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;
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 gqueue *rbios;
struct timeval expiry;
};
struct dtlsservernewparams {
struct sessioncacheentry *sesscache;
int sock;
srcres =
resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL,
AF_UNSPEC, NULL, protodefs.socktype);
int udp2bio(int s, struct gqueue *q, int cnt) {
unsigned char *buf;
BIO *rbio;
if (cnt < 1)
return 0;
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
buf = malloc(cnt);
if (!buf) {
unsigned char err;
debug(DBG_ERR, "udp2bio: malloc failed");
recv(s, &err, 1, 0);
return 0;
}
cnt = recv(s, buf, cnt, 0);
if (cnt < 1) {
debug(DBG_WARN, "udp2bio: recv failed");
free(buf);
return 0;
}
rbio = BIO_new_mem_buf(buf, cnt);
BIO_set_mem_eof_return(rbio, -1);
pthread_mutex_lock(&q->mutex);
if (!list_push(q->entries, rbio)) {
BIO_free(rbio);
pthread_mutex_unlock(&q->mutex);
return 0;
}
pthread_cond_signal(&q->cond);
pthread_mutex_unlock(&q->mutex);
return 1;
}
BIO *getrbio(SSL *ssl, struct gqueue *q, int timeout) {
BIO *rbio;
struct timeval now;
struct timespec to;
pthread_mutex_lock(&q->mutex);
if (!(rbio = (BIO *)list_shift(q->entries))) {
if (timeout) {
gettimeofday(&now, NULL);
memset(&to, 0, sizeof(struct timespec));
to.tv_sec = now.tv_sec + timeout;
pthread_cond_timedwait(&q->cond, &q->mutex, &to);
} else
pthread_cond_wait(&q->cond, &q->mutex);
rbio = (BIO *)list_shift(q->entries);
}
pthread_mutex_unlock(&q->mutex);
return rbio;
}
int dtlsread(SSL *ssl, struct gqueue *q, unsigned char *buf, int num, int timeout) {
for (len = 0; len < num; len += cnt) {
cnt = SSL_read(ssl, buf + len, num - len);
if (cnt <= 0)
switch (cnt = SSL_get_error(ssl, cnt)) {
case SSL_ERROR_WANT_READ:
rbio = getrbio(ssl, q, timeout);
if (!rbio)
return 0;
cnt = 0;
continue;
case SSL_ERROR_WANT_WRITE:
cnt = 0;
continue;
case SSL_ERROR_ZERO_RETURN:
/* remote end sent close_notify, send one back */
SSL_shutdown(ssl);
return -1;
default:
return -1;
}
}
return num;
}
/* accept if acc == 1, else connect */
SSL *dtlsacccon(uint8_t acc, SSL_CTX *ctx, int s, struct sockaddr *addr, struct gqueue *rbios) {
SSL *ssl;
int i, res;
unsigned long error;
BIO *mem0bio, *wbio;
ssl = SSL_new(ctx);
if (!ssl)
return NULL;
mem0bio = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(mem0bio, -1);
wbio = BIO_new_dgram(s, BIO_NOCLOSE);
i = BIO_dgram_set_peer(wbio, addr); /* i just to avoid warning */
SSL_set_bio(ssl, mem0bio, wbio);
for (i = 0; i < 5; i++) {
res = acc ? SSL_accept(ssl) : SSL_connect(ssl);
if (res > 0)
return ssl;
if (res == 0)
break;
if (SSL_get_error(ssl, res) == SSL_ERROR_WANT_READ) {
BIO_free(ssl->rbio);
ssl->rbio = getrbio(ssl, rbios, 5);
if (!ssl->rbio)
break;
}
while ((error = ERR_get_error()))
debug(DBG_ERR, "dtls%st: DTLS: %s", acc ? "accep" : "connec", ERR_error_string(error, NULL));
}
SSL_free(ssl);
return NULL;
}
unsigned char *raddtlsget(SSL *ssl, struct gqueue *rbios, int timeout) {
int cnt, len;
unsigned char buf[4], *rad;
for (;;) {
cnt = dtlsread(ssl, rbios, buf, 4, timeout);
debug(DBG_DBG, cnt ? "raddtlsget: connection lost" : "raddtlsget: timeout");
return NULL;
}
len = RADLEN(buf);
if (len < 4) {
debug(DBG_ERR, "raddtlsget: length too small");
continue;
}
rad = malloc(len);
if (!rad) {
debug(DBG_ERR, "raddtlsget: malloc failed");
continue;
}
memcpy(rad, buf, 4);
cnt = dtlsread(ssl, rbios, rad + 4, len - 4, timeout);
debug(DBG_DBG, cnt ? "raddtlsget: connection lost" : "raddtlsget: timeout");
free(rad);
debug(DBG_WARN, "raddtlsget: packet smaller than minimum radius size");
}
debug(DBG_DBG, "raddtlsget: got %d bytes", len);
return rad;
}
void *dtlsserverwr(void *arg) {
int cnt;
unsigned long error;
struct client *client = (struct client *)arg;
struct gqueue *replyq;
debug(DBG_DBG, "dtlsserverwr: starting for %s", addr2string(client->addr));
replyq = client->replyq;
for (;;) {
pthread_mutex_lock(&replyq->mutex);
while (!list_first(replyq->entries)) {
debug(DBG_DBG, "dtlsserverwr: waiting for signal");
pthread_cond_wait(&replyq->cond, &replyq->mutex);
debug(DBG_DBG, "dtlsserverwr: got signal");
}
if (!client->ssl) {
/* ssl might have changed while waiting */
pthread_mutex_unlock(&replyq->mutex);
debug(DBG_DBG, "dtlsserverwr: exiting as requested");
ERR_remove_state(0);
pthread_exit(NULL);
}
}
pthread_mutex_unlock(&replyq->mutex);
cnt = SSL_write(client->ssl, reply->replybuf, RADLEN(reply->replybuf));
debug(DBG_DBG, "dtlsserverwr: sent %d bytes, Radius packet of length %d to %s",
cnt, RADLEN(reply->replybuf), addr2string(client->addr));
else
while ((error = ERR_get_error()))
debug(DBG_ERR, "dtlsserverwr: SSL: %s", ERR_error_string(error, NULL));
struct request *rq;
uint8_t *buf;
debug(DBG_DBG, "dtlsserverrd: starting for %s", addr2string(client->addr));
Linus Nordberg
committed
if (pthread_create(&dtlsserverwrth, &pthread_attr, dtlsserverwr, (void *)client)) {
debug(DBG_ERR, "dtlsserverrd: pthread_create failed");
return;
}
for (;;) {
buf = raddtlsget(client->ssl, client->rbios, IDLE_TIMEOUT);
if (!buf) {
debug(DBG_ERR, "dtlsserverrd: connection from %s lost", addr2string(client->addr));
debug(DBG_DBG, "dtlsserverrd: got Radius message from %s", addr2string(client->addr));
rq = newrequest();
if (!rq) {
free(buf);
continue;
}
rq->buf = buf;
rq->from = client;
if (!radsrv(rq)) {
debug(DBG_ERR, "dtlsserverrd: message authentication/validation failed, closing connection from %s", addr2string(client->addr));
/* stop writer by setting ssl to NULL and give signal in case waiting for data */
client->ssl = NULL;
pthread_mutex_lock(&client->replyq->mutex);
pthread_cond_signal(&client->replyq->cond);
pthread_mutex_unlock(&client->replyq->mutex);
debug(DBG_DBG, "dtlsserverrd: waiting for writer to end");
pthread_join(dtlsserverwrth, NULL);
debug(DBG_DBG, "dtlsserverrd: reader for %s exiting", addr2string(client->addr));
struct dtlsservernewparams *params = (struct dtlsservernewparams *)arg;
struct client *client;
struct clsrvconf *conf;
struct list_node *cur = NULL;
SSL *ssl = NULL;
SSL_CTX *ctx = NULL;
uint8_t delay = 60;
struct tls *accepted_tls = NULL;
debug(DBG_DBG, "dtlsservernew: starting");
conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, NULL);
if (!ctx)
goto exit;
ssl = dtlsacccon(1, ctx, params->sock, (struct sockaddr *)¶ms->addr, params->sesscache->rbios);
if (!ssl)
goto exit;
cert = verifytlscert(ssl);
if (!cert)
goto exit;
accepted_tls = conf->tlsconf;
if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf)) {
client = addclient(conf, 1);
client->sock = params->sock;
client->addr = addr_copy((struct sockaddr *)¶ms->addr);
client->rbios = params->sesscache->rbios;
client->ssl = ssl;
dtlsserverrd(client);
removeclient(client);
} else {
debug(DBG_WARN, "dtlsservernew: failed to create new client instance");
}
goto exit;
}
conf = find_clconf(handle, (struct sockaddr *)¶ms->addr, &cur);
}
debug(DBG_WARN, "dtlsservernew: ignoring request, no matching TLS client");
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
}
pthread_mutex_lock(¶ms->sesscache->mutex);
freebios(params->sesscache->rbios);
params->sesscache->rbios = NULL;
gettimeofday(¶ms->sesscache->expiry, NULL);
params->sesscache->expiry.tv_sec += delay;
pthread_mutex_unlock(¶ms->sesscache->mutex);
free(params);
ERR_remove_state(0);
pthread_exit(NULL);
debug(DBG_DBG, "dtlsservernew: exiting");
}
void cacheexpire(struct hash *cache, struct timeval *last) {
struct timeval now;
struct hash_entry *he;
struct sessioncacheentry *e;
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
gettimeofday(&now, NULL);
if (now.tv_sec - last->tv_sec < 19)
return;
for (he = hash_first(cache); he; he = hash_next(he)) {
e = (struct sessioncacheentry *)he->data;
pthread_mutex_lock(&e->mutex);
if (!e->expiry.tv_sec || e->expiry.tv_sec > now.tv_sec) {
pthread_mutex_unlock(&e->mutex);
continue;
}
debug(DBG_DBG, "cacheexpire: freeing entry");
hash_extract(cache, he->key, he->keylen);
if (e->rbios) {
freebios(e->rbios);
e->rbios = NULL;
}
pthread_mutex_unlock(&e->mutex);
pthread_mutex_destroy(&e->mutex);
}
last->tv_sec = now.tv_sec;
}
void *udpdtlsserverrd(void *arg) {
int ndesc, cnt, s = *(int *)arg;
unsigned char buf[4];
struct sockaddr_storage from;
socklen_t fromlen = sizeof(from);
struct dtlsservernewparams *params;
fd_set readfds;
struct timeval timeout, lastexpiry;
pthread_t dtlsserverth;
struct hash *sessioncache;
struct sessioncacheentry *cacheentry;
sessioncache = hash_create();
if (!sessioncache)
debugx(1, DBG_ERR, "udpdtlsserverrd: malloc failed");
gettimeofday(&lastexpiry, NULL);
for (;;) {
FD_ZERO(&readfds);
FD_SET(s, &readfds);
memset(&timeout, 0, sizeof(struct timeval));
timeout.tv_sec = 60;
ndesc = select(s + 1, &readfds, NULL, NULL, &timeout);
if (ndesc < 1) {
cacheexpire(sessioncache, &lastexpiry);
continue;
}
cnt = recvfrom(s, buf, 4, MSG_PEEK | MSG_TRUNC, (struct sockaddr *)&from, &fromlen);
if (cnt == -1) {
debug(DBG_WARN, "udpdtlsserverrd: recv failed");
cacheexpire(sessioncache, &lastexpiry);
continue;
}
cacheentry = hash_read(sessioncache, &from, fromlen);
if (cacheentry) {
debug(DBG_DBG, "udpdtlsserverrd: cache hit");
pthread_mutex_lock(&cacheentry->mutex);
if (cacheentry->rbios) {
if (udp2bio(s, cacheentry->rbios, cnt))
debug(DBG_DBG, "udpdtlsserverrd: got DTLS in UDP from %s", addr2string((struct sockaddr *)&from));
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
} else
recv(s, buf, 1, 0);
pthread_mutex_unlock(&cacheentry->mutex);
cacheexpire(sessioncache, &lastexpiry);
continue;
}
/* from new source */
debug(DBG_DBG, "udpdtlsserverrd: cache miss");
params = malloc(sizeof(struct dtlsservernewparams));
if (!params) {
cacheexpire(sessioncache, &lastexpiry);
recv(s, buf, 1, 0);
continue;
}
memset(params, 0, sizeof(struct dtlsservernewparams));
params->sesscache = malloc(sizeof(struct sessioncacheentry));
if (!params->sesscache) {
free(params);
cacheexpire(sessioncache, &lastexpiry);
recv(s, buf, 1, 0);
continue;
}
memset(params->sesscache, 0, sizeof(struct sessioncacheentry));
pthread_mutex_init(¶ms->sesscache->mutex, NULL);
params->sesscache->rbios = newqueue();
if (hash_insert(sessioncache, &from, fromlen, params->sesscache)) {
params->sock = s;
memcpy(¶ms->addr, &from, fromlen);
if (udp2bio(s, params->sesscache->rbios, cnt)) {
debug(DBG_DBG, "udpdtlsserverrd: got DTLS in UDP from %s", addr2string((struct sockaddr *)&from));
Linus Nordberg
committed
if (!pthread_create(&dtlsserverth, &pthread_attr, dtlsservernew, (void *)params)) {
pthread_detach(dtlsserverth);
cacheexpire(sessioncache, &lastexpiry);
continue;
}
debug(DBG_ERR, "udpdtlsserverrd: pthread_create failed");
}
hash_extract(sessioncache, &from, fromlen);
}
freebios(params->sesscache->rbios);
pthread_mutex_destroy(¶ms->sesscache->mutex);
free(params->sesscache);
free(params);
cacheexpire(sessioncache, &lastexpiry);
}
}
int dtlsconnect(struct server *server, struct timeval *when, int timeout, char *text) {
struct timeval now;
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))) {
/* already reconnected, nothing to do */
debug(DBG_DBG, "dtlsconnect(%s): seems already reconnected", text);
pthread_mutex_unlock(&server->lock);
return 1;
}
hp = (struct hostportres *)list_first(server->conf->hostports)->data;
for (;;) {
gettimeofday(&now, NULL);
elapsed = now.tv_sec - server->lastconnecttry.tv_sec;
if (timeout && server->lastconnecttry.tv_sec && elapsed > timeout) {
debug(DBG_DBG, "dtlsconnect: timeout");
SSL_free(server->ssl);
server->ssl = NULL;
pthread_mutex_unlock(&server->lock);
return 0;
}
if (server->connectionok) {
server->connectionok = 0;
sleep(2);
} else if (elapsed < 1)
sleep(2);
else if (elapsed < 60) {
debug(DBG_INFO, "dtlsconnect: sleeping %lds", elapsed);
sleep(elapsed);
} else if (elapsed < 100000) {
debug(DBG_INFO, "dtlsconnect: sleeping %ds", 60);
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", hp->host, hp->port);
server->ssl = NULL;
if (!ctx)
continue;
server->ssl = dtlsacccon(0, ctx, server->sock, hp->addrinfo->ai_addr, server->rbios);
if (!server->ssl)
continue;
debug(DBG_DBG, "dtlsconnect: DTLS: ok");
cert = verifytlscert(server->ssl);
if (!cert)
continue;
if (verifyconfcert(cert, server->conf))
break;
X509_free(cert);
}
X509_free(cert);
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);
return 1;
}
int clientradputdtls(struct server *server, unsigned char *rad) {
int cnt;
size_t len;
unsigned long error;
struct clsrvconf *conf = server->conf;
if (!server->connectionok)
if ((cnt = SSL_write(server->ssl, rad, len)) <= 0) {
while ((error = ERR_get_error()))
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->name);
return 1;
}
/* reads UDP containing DTLS and passes it on to dtlsclientrd */
void *udpdtlsclientrd(void *arg) {
int cnt, s = *(int *)arg;
unsigned char buf[4];
struct sockaddr_storage from;
socklen_t fromlen = sizeof(from);
struct clsrvconf *conf;
fd_set readfds;
for (;;) {
FD_ZERO(&readfds);
FD_SET(s, &readfds);
if (select(s + 1, &readfds, NULL, NULL, NULL) < 1)
continue;
cnt = recvfrom(s, buf, 4, MSG_PEEK | MSG_TRUNC, (struct sockaddr *)&from, &fromlen);
if (cnt == -1) {
debug(DBG_WARN, "udpdtlsclientrd: recv failed");
continue;
}
debug(DBG_WARN, "udpdtlsclientrd: got packet from wrong or unknown DTLS peer %s, ignoring", addr2string((struct sockaddr *)&from));
recv(s, buf, 4, 0);
continue;
}
if (udp2bio(s, conf->servers->rbios, cnt))
debug(DBG_DBG, "radudpget: got DTLS in UDP from %s", addr2string((struct sockaddr *)&from));
}
}
void *dtlsclientrd(void *arg) {
struct server *server = (struct server *)arg;
unsigned char *buf;
struct timeval lastconnecttry;
for (;;) {
/* yes, lastconnecttry is really necessary */
lastconnecttry = server->lastconnecttry;
for (secs = 0; !(buf = raddtlsget(server->ssl, server->rbios, 10)) && !server->lostrqs && secs < IDLE_TIMEOUT; secs += 10);
if (!buf) {
dtlsconnect(server, &lastconnecttry, 0, "dtlsclientrd");
continue;
}
ERR_remove_state(0);
server->clientrdgone = 1;
return NULL;
switch (((struct hostportres *)list_first(conf->hostports)->data)->addrinfo->ai_family) {
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) {
debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name);
}
conf->servers->sock = client6_sock;
break;
default:
debugx(1, DBG_ERR, "addserver: unsupported address family");
}
}
void initextradtls() {
pthread_t cl4th, cl6th;
if (srcres) {
freeaddrinfo(srcres);
srcres = NULL;
}
Linus Nordberg
committed
if (pthread_create(&cl4th, &pthread_attr, udpdtlsclientrd, (void *)&client4_sock))
debugx(1, DBG_ERR, "pthread_create failed");
if (client6_sock >= 0)
Linus Nordberg
committed
if (pthread_create(&cl6th, &pthread_attr, udpdtlsclientrd, (void *)&client6_sock))
#else
const struct protodefs *dtlsinit(uint8_t h) {
return NULL;
}
#endif