tls.c 13.6 KB
Newer Older
venaas's avatar
venaas committed
1
/*
2
 * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
venaas's avatar
venaas committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *
 * 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>
#include "radsecproxy.h"
30
#include "hostport.h"
venaas's avatar
venaas committed
31

32 33 34 35
#ifdef RADPROT_TLS
#include "debug.h"
#include "util.h"

venaas's avatar
venaas committed
36 37
static void setprotoopts(struct commonprotoopts *opts);
static char **getlistenerargs();
venaas's avatar
venaas committed
38 39 40 41
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);
venaas's avatar
venaas committed
42
void tlssetsrcres();
venaas's avatar
venaas committed
43 44 45

static const struct protodefs protodefs = {
    "tls",
46
    "radsec", /* secretdefault */
venaas's avatar
venaas committed
47 48 49 50 51 52 53
    SOCK_STREAM, /* socktype */
    "2083", /* portdefault */
    0, /* retrycountdefault */
    0, /* retrycountmax */
    REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */
    60, /* retryintervalmax */
    DUPLICATE_INTERVAL, /* duplicateintervaldefault */
venaas's avatar
venaas committed
54 55
    setprotoopts, /* setprotoopts */
    getlistenerargs, /* getlistenerargs */
venaas's avatar
venaas committed
56 57 58 59 60 61 62 63 64
    tlslistener, /* listener */
    tlsconnect, /* connecter */
    tlsclientrd, /* clientconnreader */
    clientradputtls, /* clientradput */
    NULL, /* addclient */
    NULL, /* addserverextra */
    tlssetsrcres, /* setsrcres */
    NULL /* initextra */
};
venaas's avatar
venaas committed
65

venaas's avatar
venaas committed
66
static struct addrinfo *srcres = NULL;
venaas's avatar
venaas committed
67
static uint8_t handle;
venaas's avatar
venaas committed
68
static struct commonprotoopts *protoopts = NULL;
venaas's avatar
venaas committed
69 70 71 72 73

const struct protodefs *tlsinit(uint8_t h) {
    handle = h;
    return &protodefs;
}
venaas's avatar
venaas committed
74

venaas's avatar
venaas committed
75 76 77 78 79 80 81 82 83
static void setprotoopts(struct commonprotoopts *opts) {
    protoopts = opts;
}

static char **getlistenerargs() {
    return protoopts ? protoopts->listenargs : NULL;
}

void tlssetsrcres() {
venaas's avatar
venaas committed
84
    if (!srcres)
85 86 87
	srcres =
            resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL,
                                   AF_UNSPEC, NULL, protodefs.socktype);
venaas's avatar
venaas committed
88 89
}

venaas's avatar
venaas committed
90 91 92 93
int tlsconnect(struct server *server, struct timeval *when, int timeout, char *text) {
    struct timeval now;
    time_t elapsed;
    X509 *cert;
94
    SSL_CTX *ctx = NULL;
venaas's avatar
venaas committed
95
    unsigned long error;
96

venaas's avatar
venaas committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    debug(DBG_DBG, "tlsconnect: 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, "tlsconnect(%s): seems already reconnected", text);
	pthread_mutex_unlock(&server->lock);
	return 1;
    }

    for (;;) {
	gettimeofday(&now, NULL);
	elapsed = now.tv_sec - server->lastconnecttry.tv_sec;
	if (timeout && server->lastconnecttry.tv_sec && elapsed > timeout) {
	    debug(DBG_DBG, "tlsconnect: timeout");
	    if (server->sock >= 0)
		close(server->sock);
	    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, "tlsconnect: sleeping %lds", elapsed);
	    sleep(elapsed);
	} else if (elapsed < 100000) {
	    debug(DBG_INFO, "tlsconnect: sleeping %ds", 60);
	    sleep(60);
	} else
	    server->lastconnecttry.tv_sec = now.tv_sec;  /* no sleep at startup */
131

venaas's avatar
venaas committed
132 133
	if (server->sock >= 0)
	    close(server->sock);
134
	if ((server->sock = connecttcphostlist(server->conf->hostports, srcres)) < 0)
venaas's avatar
venaas committed
135
	    continue;
136

venaas's avatar
venaas committed
137
	SSL_free(server->ssl);
138
	server->ssl = NULL;
venaas's avatar
venaas committed
139
	ctx = tlsgetctx(handle, server->conf->tlsconf);
140 141 142 143 144 145
	if (!ctx)
	    continue;
	server->ssl = SSL_new(ctx);
	if (!server->ssl)
	    continue;

venaas's avatar
venaas committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
	SSL_set_fd(server->ssl, server->sock);
	if (SSL_connect(server->ssl) <= 0) {
	    while ((error = ERR_get_error()))
		debug(DBG_ERR, "tlsconnect: TLS: %s", ERR_error_string(error, NULL));
	    continue;
	}
	cert = verifytlscert(server->ssl);
	if (!cert)
	    continue;
	if (verifyconfcert(cert, server->conf)) {
	    X509_free(cert);
	    break;
	}
	X509_free(cert);
    }
161
    debug(DBG_WARN, "tlsconnect: TLS connection to %s up", server->conf->name);
162
    server->connectionok = 1;
venaas's avatar
venaas committed
163 164 165 166 167 168 169 170 171
    gettimeofday(&server->lastconnecttry, NULL);
    pthread_mutex_unlock(&server->lock);
    return 1;
}

/* timeout in seconds, 0 means no timeout (blocking), returns when num bytes have been read, or timeout */
/* returns 0 on timeout, -1 on error and num if ok */
int sslreadtimeout(SSL *ssl, unsigned char *buf, int num, int timeout) {
    int s, ndesc, cnt, len;
172
    fd_set readfds;
venaas's avatar
venaas committed
173
    struct timeval timer;
174

venaas's avatar
venaas committed
175 176 177 178 179
    s = SSL_get_fd(ssl);
    if (s < 0)
	return -1;
    /* make socket non-blocking? */
    for (len = 0; len < num; len += cnt) {
180 181 182 183 184 185 186 187 188 189
	if (SSL_pending(ssl) == 0) {
            FD_ZERO(&readfds);
            FD_SET(s, &readfds);
            if (timeout) {
                timer.tv_sec = timeout;
                timer.tv_usec = 0;
            }
	    ndesc = select(s + 1, &readfds, NULL, NULL, timeout ? &timer : NULL);
            if (ndesc < 1)
                return ndesc;
venaas's avatar
venaas committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
	}

	cnt = SSL_read(ssl, buf + len, num - len);
	if (cnt <= 0)
	    switch (SSL_get_error(ssl, cnt)) {
	    case SSL_ERROR_WANT_READ:
	    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;
}

/* timeout in seconds, 0 means no timeout (blocking) */
unsigned char *radtlsget(SSL *ssl, int timeout) {
    int cnt, len;
    unsigned char buf[4], *rad;

    for (;;) {
	cnt = sslreadtimeout(ssl, buf, 4, timeout);
	if (cnt < 1) {
	    debug(DBG_DBG, cnt ? "radtlsget: connection lost" : "radtlsget: timeout");
	    return NULL;
	}

	len = RADLEN(buf);
223 224 225 226
	if (len < 4) {
	    debug(DBG_ERR, "radtlsget: length too small");
	    continue;
	}
venaas's avatar
venaas committed
227 228 229 230 231 232
	rad = malloc(len);
	if (!rad) {
	    debug(DBG_ERR, "radtlsget: malloc failed");
	    continue;
	}
	memcpy(rad, buf, 4);
233

venaas's avatar
venaas committed
234 235 236 237 238 239
	cnt = sslreadtimeout(ssl, rad + 4, len - 4, timeout);
	if (cnt < 1) {
	    debug(DBG_DBG, cnt ? "radtlsget: connection lost" : "radtlsget: timeout");
	    free(rad);
	    return NULL;
	}
240

venaas's avatar
venaas committed
241 242
	if (len >= 20)
	    break;
243

venaas's avatar
venaas committed
244 245 246
	free(rad);
	debug(DBG_WARN, "radtlsget: packet smaller than minimum radius size");
    }
247

venaas's avatar
venaas committed
248 249 250 251 252 253 254 255 256
    debug(DBG_DBG, "radtlsget: got %d bytes", len);
    return rad;
}

int clientradputtls(struct server *server, unsigned char *rad) {
    int cnt;
    size_t len;
    unsigned long error;
    struct clsrvconf *conf = server->conf;
257 258 259

    if (!server->connectionok)
	return 0;
venaas's avatar
venaas committed
260
    len = RADLEN(rad);
261
    if ((cnt = SSL_write(server->ssl, rad, len)) <= 0) {
venaas's avatar
venaas committed
262 263
	while ((error = ERR_get_error()))
	    debug(DBG_ERR, "clientradputtls: TLS: %s", ERR_error_string(error, NULL));
264
	return 0;
venaas's avatar
venaas committed
265 266
    }

267
    debug(DBG_DBG, "clientradputtls: Sent %d bytes, Radius packet of length %d to TLS peer %s", cnt, len, conf->name);
venaas's avatar
venaas committed
268 269 270 271 272 273 274
    return 1;
}

void *tlsclientrd(void *arg) {
    struct server *server = (struct server *)arg;
    unsigned char *buf;
    struct timeval now, lastconnecttry;
275

venaas's avatar
venaas committed
276 277 278 279 280 281 282 283 284 285 286
    for (;;) {
	/* yes, lastconnecttry is really necessary */
	lastconnecttry = server->lastconnecttry;
	buf = radtlsget(server->ssl, server->dynamiclookuparg ? IDLE_TIMEOUT : 0);
	if (!buf) {
	    if (server->dynamiclookuparg)
		break;
	    tlsconnect(server, &lastconnecttry, 0, "tlsclientrd");
	    continue;
	}

venaas's avatar
venaas committed
287 288
	replyh(server, buf);

venaas's avatar
venaas committed
289 290 291 292 293 294 295 296
	if (server->dynamiclookuparg) {
	    gettimeofday(&now, NULL);
	    if (now.tv_sec - server->lastreply.tv_sec > IDLE_TIMEOUT) {
		debug(DBG_INFO, "tlsclientrd: idle timeout for %s", server->conf->name);
		break;
	    }
	}
    }
297
    debug(DBG_INFO, "tlsclientrd: exiting for %s", server->conf->name);
venaas's avatar
venaas committed
298
    ERR_remove_state(0);
299 300 301 302 303
    SSL_shutdown(server->ssl);
    shutdown(server->sock, SHUT_RDWR);
    close(server->sock);

    /* Wake up clientwr(). */
venaas's avatar
venaas committed
304
    server->clientrdgone = 1;
305 306 307
    pthread_mutex_lock(&server->newrq_mutex);
    pthread_cond_signal(&server->newrq_cond);
    pthread_mutex_unlock(&server->newrq_mutex);
venaas's avatar
venaas committed
308 309 310 311 312 313 314
    return NULL;
}

void *tlsserverwr(void *arg) {
    int cnt;
    unsigned long error;
    struct client *client = (struct client *)arg;
315
    struct gqueue *replyq;
venaas's avatar
venaas committed
316
    struct request *reply;
317

318
    debug(DBG_DBG, "tlsserverwr: starting for %s", addr2string(client->addr));
venaas's avatar
venaas committed
319 320 321 322
    replyq = client->replyq;
    for (;;) {
	pthread_mutex_lock(&replyq->mutex);
	while (!list_first(replyq->entries)) {
323
	    if (client->ssl) {
venaas's avatar
venaas committed
324 325 326 327 328 329 330 331 332 333 334 335
		debug(DBG_DBG, "tlsserverwr: waiting for signal");
		pthread_cond_wait(&replyq->cond, &replyq->mutex);
		debug(DBG_DBG, "tlsserverwr: got signal");
	    }
	    if (!client->ssl) {
		/* ssl might have changed while waiting */
		pthread_mutex_unlock(&replyq->mutex);
		debug(DBG_DBG, "tlsserverwr: exiting as requested");
		ERR_remove_state(0);
		pthread_exit(NULL);
	    }
	}
venaas's avatar
venaas committed
336
	reply = (struct request *)list_shift(replyq->entries);
venaas's avatar
venaas committed
337
	pthread_mutex_unlock(&replyq->mutex);
venaas's avatar
venaas committed
338
	cnt = SSL_write(client->ssl, reply->replybuf, RADLEN(reply->replybuf));
venaas's avatar
venaas committed
339
	if (cnt > 0)
340 341
	    debug(DBG_DBG, "tlsserverwr: sent %d bytes, Radius packet of length %d to %s",
		  cnt, RADLEN(reply->replybuf), addr2string(client->addr));
venaas's avatar
venaas committed
342 343 344
	else
	    while ((error = ERR_get_error()))
		debug(DBG_ERR, "tlsserverwr: SSL: %s", ERR_error_string(error, NULL));
venaas's avatar
venaas committed
345
	freerq(reply);
venaas's avatar
venaas committed
346 347 348 349
    }
}

void tlsserverrd(struct client *client) {
350 351
    struct request *rq;
    uint8_t *buf;
venaas's avatar
venaas committed
352
    pthread_t tlsserverwrth;
353

354
    debug(DBG_DBG, "tlsserverrd: starting for %s", addr2string(client->addr));
355

356
    if (pthread_create(&tlsserverwrth, &pthread_attr, tlsserverwr, (void *)client)) {
venaas's avatar
venaas committed
357 358 359 360 361
	debug(DBG_ERR, "tlsserverrd: pthread_create failed");
	return;
    }

    for (;;) {
362
	buf = radtlsget(client->ssl, IDLE_TIMEOUT * 3);
363
	if (!buf) {
364
	    debug(DBG_ERR, "tlsserverrd: connection from %s lost", addr2string(client->addr));
venaas's avatar
venaas committed
365 366
	    break;
	}
367
	debug(DBG_DBG, "tlsserverrd: got Radius message from %s", addr2string(client->addr));
368 369 370 371 372 373 374 375
	rq = newrequest();
	if (!rq) {
	    free(buf);
	    continue;
	}
	rq->buf = buf;
	rq->from = client;
	if (!radsrv(rq)) {
376
	    debug(DBG_ERR, "tlsserverrd: message authentication/validation failed, closing connection from %s", addr2string(client->addr));
venaas's avatar
venaas committed
377 378 379
	    break;
	}
    }
380

venaas's avatar
venaas committed
381 382 383 384 385 386 387
    /* 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, "tlsserverrd: waiting for writer to end");
    pthread_join(tlsserverwrth, NULL);
388
    debug(DBG_DBG, "tlsserverrd: reader for %s exiting", addr2string(client->addr));
venaas's avatar
venaas committed
389 390 391 392 393
}

void *tlsservernew(void *arg) {
    int s;
    struct sockaddr_storage from;
venaas's avatar
venaas committed
394
    socklen_t fromlen = sizeof(from);
venaas's avatar
venaas committed
395 396 397 398
    struct clsrvconf *conf;
    struct list_node *cur = NULL;
    SSL *ssl = NULL;
    X509 *cert = NULL;
399
    SSL_CTX *ctx = NULL;
venaas's avatar
venaas committed
400 401
    unsigned long error;
    struct client *client;
402
    struct tls *accepted_tls = NULL;
venaas's avatar
venaas committed
403 404

    s = *(int *)arg;
405
    free(arg);
venaas's avatar
venaas committed
406 407 408 409
    if (getpeername(s, (struct sockaddr *)&from, &fromlen)) {
	debug(DBG_DBG, "tlsservernew: getpeername failed, exiting");
	goto exit;
    }
410
    debug(DBG_WARN, "tlsservernew: incoming TLS connection from %s", addr2string((struct sockaddr *)&from));
venaas's avatar
venaas committed
411

venaas's avatar
venaas committed
412
    conf = find_clconf(handle, (struct sockaddr *)&from, &cur);
venaas's avatar
venaas committed
413
    if (conf) {
venaas's avatar
venaas committed
414
	ctx = tlsgetctx(handle, conf->tlsconf);
415 416 417 418 419
	if (!ctx)
	    goto exit;
	ssl = SSL_new(ctx);
	if (!ssl)
	    goto exit;
venaas's avatar
venaas committed
420 421 422 423 424 425 426 427 428 429 430
	SSL_set_fd(ssl, s);

	if (SSL_accept(ssl) <= 0) {
	    while ((error = ERR_get_error()))
		debug(DBG_ERR, "tlsservernew: SSL: %s", ERR_error_string(error, NULL));
	    debug(DBG_ERR, "tlsservernew: SSL_accept failed");
	    goto exit;
	}
	cert = verifytlscert(ssl);
	if (!cert)
	    goto exit;
431
        accepted_tls = conf->tlsconf;
venaas's avatar
venaas committed
432
    }
433

venaas's avatar
venaas committed
434
    while (conf) {
435 436 437 438 439 440 441 442 443 444 445 446 447
        if (accepted_tls == conf->tlsconf && verifyconfcert(cert, conf)) {
            X509_free(cert);
            client = addclient(conf, 1);
            if (client) {
                client->ssl = ssl;
                client->addr = addr_copy((struct sockaddr *)&from);
                tlsserverrd(client);
                removeclient(client);
            } else
                debug(DBG_WARN, "tlsservernew: failed to create new client instance");
            goto exit;
        }
        conf = find_clconf(handle, (struct sockaddr *)&from, &cur);
venaas's avatar
venaas committed
448 449 450 451 452
    }
    debug(DBG_WARN, "tlsservernew: ignoring request, no matching TLS client");
    if (cert)
	X509_free(cert);

453
exit:
454 455 456 457
    if (ssl) {
	SSL_shutdown(ssl);
	SSL_free(ssl);
    }
venaas's avatar
venaas committed
458 459 460 461 462 463 464 465
    ERR_remove_state(0);
    shutdown(s, SHUT_RDWR);
    close(s);
    pthread_exit(NULL);
}

void *tlslistener(void *arg) {
    pthread_t tlsserverth;
466
    int s, *sp = (int *)arg, *s_arg = NULL;
venaas's avatar
venaas committed
467
    struct sockaddr_storage from;
venaas's avatar
venaas committed
468
    socklen_t fromlen = sizeof(from);
venaas's avatar
venaas committed
469 470 471 472 473 474 475 476 477

    listen(*sp, 0);

    for (;;) {
	s = accept(*sp, (struct sockaddr *)&from, &fromlen);
	if (s < 0) {
	    debug(DBG_WARN, "accept failed");
	    continue;
	}
478 479 480 481 482
        s_arg = malloc(sizeof(s));
        if (!s_arg)
            debugx(1, DBG_ERR, "malloc failed");
        *s_arg = s;
	if (pthread_create(&tlsserverth, &pthread_attr, tlsservernew, (void *) s_arg)) {
venaas's avatar
venaas committed
483
	    debug(DBG_ERR, "tlslistener: pthread_create failed");
484
            free(s_arg);
venaas's avatar
venaas committed
485 486 487 488 489 490 491 492 493
	    shutdown(s, SHUT_RDWR);
	    close(s);
	    continue;
	}
	pthread_detach(tlsserverth);
    }
    free(sp);
    return NULL;
}
494 495 496 497 498
#else
const struct protodefs *tlsinit(uint8_t h) {
    return NULL;
}
#endif
499 500 501 502

/* Local Variables: */
/* c-file-style: "stroustrup" */
/* End: */