From 83e82dba47aced4a93f9e431b4d8bca94c2f8517 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 11 Nov 2010 10:30:35 +0100 Subject: Bringing up TLS connections working. NOTE: Clean up of resources not yet sane. Expect resource leakages. NOTE: Most failure cases are not handled properly. With the wind at your back and the sun shining, it might work. --- debug.c | 217 ------------- debug.h | 32 -- hash.c | 136 -------- hash.h | 48 --- hostport.h | 7 +- hostport_types.h | 6 + lib/Makefile.am | 13 +- lib/attr.c | 4 + lib/conf.c | 23 +- lib/configure.ac | 17 +- lib/conn.c | 7 +- lib/debug.c | 4 + lib/err.c | 14 +- lib/examples/client.conf | 22 +- lib/include/radsec/radsec-impl.h | 12 + lib/include/radsec/radsec.h | 2 + lib/packet.c | 62 +++- lib/radsec.c | 14 + lib/request.c | 12 +- lib/rsp_debug.c | 217 +++++++++++++ lib/rsp_debug.h | 32 ++ lib/rsp_hash.c | 136 ++++++++ lib/rsp_hash.h | 48 +++ lib/rsp_list.c | 127 ++++++++ lib/rsp_list.h | 51 +++ lib/rsp_tlscommon.c | 664 +++++++++++++++++++++++++++++++++++++++ lib/rsp_tlscommon.h | 40 +++ lib/rsp_util.c | 256 +++++++++++++++ lib/rsp_util.h | 25 ++ lib/tls.c | 73 +++++ lib/tls.h | 1 + list.c | 123 -------- list.h | 51 --- radsecproxy.h | 2 +- tlscommon.c | 641 ------------------------------------- tlscommon.h | 39 --- util.c | 256 --------------- util.h | 25 -- 38 files changed, 1863 insertions(+), 1596 deletions(-) delete mode 100644 debug.c delete mode 100644 debug.h delete mode 100644 hash.c delete mode 100644 hash.h create mode 100644 hostport_types.h create mode 100644 lib/rsp_debug.c create mode 100644 lib/rsp_debug.h create mode 100644 lib/rsp_hash.c create mode 100644 lib/rsp_hash.h create mode 100644 lib/rsp_list.c create mode 100644 lib/rsp_list.h create mode 100644 lib/rsp_tlscommon.c create mode 100644 lib/rsp_tlscommon.h create mode 100644 lib/rsp_util.c create mode 100644 lib/rsp_util.h create mode 100644 lib/tls.c create mode 100644 lib/tls.h delete mode 100644 list.c delete mode 100644 list.h delete mode 100644 tlscommon.c delete mode 100644 tlscommon.h delete mode 100644 util.c delete mode 100644 util.h diff --git a/debug.c b/debug.c deleted file mode 100644 index 3bbcd0a..0000000 --- a/debug.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2007 Stig Venaas - * - * 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. - */ - -#ifndef SYS_SOLARIS9 -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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; - -void debug_init(char *ident) { - debug_file = stderr; - setvbuf(debug_file, NULL, _IONBF, 0); - debug_ident = ident; -} - -void debug_set_level(uint8_t level) { - switch (level) { - case 1: - debug_level = DBG_ERR; - return; - case 2: - debug_level = DBG_WARN; - return; - case 3: - debug_level = DBG_NOTICE; - return; - case 4: - debug_level = DBG_INFO; - return; - case 5: - debug_level = DBG_DBG; - return; - } -} - -void debug_timestamp_on() { - debug_timestamp = 1; -} - -uint8_t debug_get_level() { - return debug_level; -} - -int debug_set_destination(char *dest) { - static const char *facstrings[] = { "LOG_DAEMON", "LOG_MAIL", "LOG_USER", "LOG_LOCAL0", - "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", - "LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", NULL }; - static const int facvals[] = { LOG_DAEMON, LOG_MAIL, LOG_USER, LOG_LOCAL0, - LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, - LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 }; - extern int errno; - int i; - - if (!strncasecmp(dest, "file:///", 8)) { - 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", - debug_filepath, strerror(errno)); - } - setvbuf(debug_file, NULL, _IONBF, 0); - return 1; - } - if (!strncasecmp(dest, "x-syslog://", 11)) { - dest += 11; - if (*dest == '/') - dest++; - if (*dest) { - for (i = 0; facstrings[i]; i++) - if (!strcasecmp(dest, facstrings[i])) - break; - if (!facstrings[i]) - debugx(1, DBG_ERR, "Unknown syslog facility %s", dest); - debug_syslogfacility = facvals[i]; - } else - debug_syslogfacility = LOG_DAEMON; - openlog(debug_ident, LOG_PID, debug_syslogfacility); - return 1; - } - debug(DBG_ERR, "Unknown log destination, exiting %s", 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; - int priority; - - if (debug_syslogfacility) { - switch (level) { - case DBG_DBG: - priority = LOG_DEBUG; - break; - case DBG_INFO: - priority = LOG_INFO; - break; - case DBG_NOTICE: - priority = LOG_NOTICE; - break; - case DBG_WARN: - priority = LOG_WARNING; - break; - case DBG_ERR: - priority = LOG_ERR; - break; - default: - priority = LOG_DEBUG; - } - vsyslog(priority, format, ap); - } else { - if (debug_timestamp && (timebuf = malloc(256))) { - gettimeofday(&now, NULL); - ctime_r(&now.tv_sec, timebuf); - timebuf[strlen(timebuf) - 1] = '\0'; - fprintf(debug_file, "%s: ", timebuf + 4); - free(timebuf); - } - vfprintf(debug_file, format, ap); - fprintf(debug_file, "\n"); - } -} - -void debug(uint8_t level, char *format, ...) { - va_list ap; - if (level < debug_level) - return; - va_start(ap, format); - debug_logit(level, format, ap); - va_end(ap); -} - -void debugx(int status, uint8_t level, char *format, ...) { - if (level >= debug_level) { - va_list ap; - va_start(ap, format); - debug_logit(level, format, ap); - va_end(ap); - } - exit(status); -} - -void debugerrno(int err, uint8_t level, char *format, ...) { - if (level >= debug_level) { - va_list ap; - size_t len = strlen(format); - char *tmp = malloc(len + 1024 + 2); - assert(tmp); - strcpy(tmp, format); - tmp[len++] = ':'; - tmp[len++] = ' '; - if (strerror_r(err, tmp + len, 1024)) - tmp = format; - va_start(ap, format); - debug_logit(level, tmp, ap); - va_end(ap); - } -} - -void debugerrnox(int err, uint8_t level, char *format, ...) { - if (level >= debug_level) { - va_list ap; - va_start(ap, format); - debugerrno(err, level, format, ap); - va_end(ap); - } - exit(err); -} - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/debug.h b/debug.h deleted file mode 100644 index 803f406..0000000 --- a/debug.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2007 Stig Venaas - * - * 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. - */ - -#ifndef SYS_SOLARIS9 -#include -#endif - -#define DBG_DBG 8 -#define DBG_INFO 16 -#define DBG_NOTICE 32 -#define DBG_WARN 64 -#define DBG_ERR 128 - -void debug_init(char *ident); -void debug_set_level(uint8_t level); -void debug_timestamp_on(); -uint8_t debug_get_level(); -void debug(uint8_t level, char *format, ...); -void debugx(int status, uint8_t level, char *format, ...); -void debugerrno(int err, uint8_t level, char *format, ...); -void debugerrnox(int err, uint8_t level, char *format, ...); -int debug_set_destination(char *dest); -void debug_reopen_log(); - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/hash.c b/hash.c deleted file mode 100644 index fd3c04b..0000000 --- a/hash.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2008 Stig Venaas - * - * 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 -#include -#include -#include "list.h" -#include "hash.h" - -/* allocates and initialises hash structure; returns NULL if malloc fails */ -struct hash *hash_create() { - struct hash *h = malloc(sizeof(struct hash)); - if (!h) - return NULL; - h->hashlist = list_create(); - if (!h->hashlist) { - free(h); - return NULL; - } - pthread_mutex_init(&h->mutex, NULL); - return h; -} - -/* frees all memory associated with the hash */ -void hash_destroy(struct hash *h) { - struct list_node *ln; - - if (!h) - return; - for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { - free(((struct hash_entry *)ln->data)->key); - free(((struct hash_entry *)ln->data)->data); - } - list_destroy(h->hashlist); - pthread_mutex_destroy(&h->mutex); -} - -/* insert entry in hash; returns 1 if ok, 0 if malloc fails */ -int hash_insert(struct hash *h, void *key, uint32_t keylen, void *data) { - struct hash_entry *e; - - if (!h) - return 0; - e = malloc(sizeof(struct hash_entry)); - if (!e) - return 0; - memset(e, 0, sizeof(struct hash_entry)); - e->key = malloc(keylen); - if (!e->key) { - free(e); - return 0; - } - memcpy(e->key, key, keylen); - e->keylen = keylen; - e->data = data; - pthread_mutex_lock(&h->mutex); - if (!list_push(h->hashlist, e)) { - pthread_mutex_unlock(&h->mutex); - free(e->key); - free(e); - return 0; - } - pthread_mutex_unlock(&h->mutex); - return 1; -} - -/* reads entry from hash */ -void *hash_read(struct hash *h, void *key, uint32_t keylen) { - struct list_node *ln; - struct hash_entry *e; - - if (!h) - return 0; - pthread_mutex_lock(&h->mutex); - for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { - e = (struct hash_entry *)ln->data; - if (e->keylen == keylen && !memcmp(e->key, key, keylen)) { - pthread_mutex_unlock(&h->mutex); - return e->data; - } - } - pthread_mutex_unlock(&h->mutex); - return NULL; -} - -/* extracts entry from hash */ -void *hash_extract(struct hash *h, void *key, uint32_t keylen) { - struct list_node *ln; - struct hash_entry *e; - - if (!h) - return 0; - pthread_mutex_lock(&h->mutex); - for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { - e = (struct hash_entry *)ln->data; - if (e->keylen == keylen && !memcmp(e->key, key, keylen)) { - free(e->key); - list_removedata(h->hashlist, e); - free(e); - pthread_mutex_unlock(&h->mutex); - return e->data; - } - } - pthread_mutex_unlock(&h->mutex); - return NULL; -} - -/* returns first entry */ -struct hash_entry *hash_first(struct hash *hash) { - struct list_node *ln; - struct hash_entry *e; - if (!hash || !((ln = list_first(hash->hashlist)))) - return NULL; - e = (struct hash_entry *)ln->data; - e->next = ln->next; - return e; -} - -/* returns the next node after the argument */ -struct hash_entry *hash_next(struct hash_entry *entry) { - struct hash_entry *e; - if (!entry || !entry->next) - return NULL; - e = (struct hash_entry *)entry->next->data; - e->next = (struct list_node *)entry->next->next; - return e; -} - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/hash.h b/hash.h deleted file mode 100644 index 48f54a3..0000000 --- a/hash.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 Stig Venaas - * - * 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. - */ - -#ifndef SYS_SOLARIS9 -#include -#endif - -struct hash { - struct list *hashlist; - pthread_mutex_t mutex; -}; - -struct hash_entry { - void *key; - uint32_t keylen; - void *data; - struct list_node *next; /* used when walking through hash */ -}; - -/* allocates and initialises hash structure; returns NULL if malloc fails */ -struct hash *hash_create(); - -/* frees all memory associated with the hash */ -void hash_destroy(struct hash *hash); - -/* insert entry in hash; returns 1 if ok, 0 if malloc fails */ -int hash_insert(struct hash *hash, void *key, uint32_t keylen, void *data); - -/* reads entry from hash */ -void *hash_read(struct hash *hash, void *key, uint32_t keylen); - -/* extracts (read and remove) entry from hash */ -void *hash_extract(struct hash *hash, void *key, uint32_t keylen); - -/* returns first entry */ -struct hash_entry *hash_first(struct hash *hash); - -/* returns the next entry after the argument */ -struct hash_entry *hash_next(struct hash_entry *entry); - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/hostport.h b/hostport.h index 425bb8f..01237e2 100644 --- a/hostport.h +++ b/hostport.h @@ -6,12 +6,7 @@ * copyright notice and this permission notice appear in all copies. */ -struct hostportres { - char *host; - char *port; - uint8_t prefixlen; - struct addrinfo *addrinfo; -}; +#include "hostport_types.h" struct hostportres *newhostport(char *hostport, char *default_port, uint8_t prefixok); int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok); diff --git a/hostport_types.h b/hostport_types.h new file mode 100644 index 0000000..01fb443 --- /dev/null +++ b/hostport_types.h @@ -0,0 +1,6 @@ +struct hostportres { + char *host; + char *port; + uint8_t prefixlen; + struct addrinfo *addrinfo; +}; diff --git a/lib/Makefile.am b/lib/Makefile.am index 5d7cd0a..ca7de1f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -13,7 +13,16 @@ libradsec_la_SOURCES = \ debug.c \ err.c \ packet.c \ + radsec.c \ request.c \ - radsec.c + tls.c + +libradsec_la_SOURCES += \ + rsp_debug.c \ + rsp_hash.c \ + rsp_list.c \ + rsp_tlscommon.c \ + rsp_util.c + libradsec_la_LDFLAGS = -version-info 0:0:0 -libradsec_la_CFLAGS = $(CFLAGS) #-DDEBUG -DDEBUG_LEVENT +libradsec_la_CFLAGS = $(CFLAGS) -DDEBUG -DDEBUG_LEVENT diff --git a/lib/attr.c b/lib/attr.c index 4e66235..29384d5 100644 --- a/lib/attr.c +++ b/lib/attr.c @@ -1,5 +1,9 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include diff --git a/lib/conf.c b/lib/conf.c index cedb84c..c235ffe 100644 --- a/lib/conf.c +++ b/lib/conf.c @@ -1,3 +1,9 @@ +/* See the file COPYING for licensing information. */ + +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include @@ -6,7 +12,11 @@ #if 0 # example of client config config NAME { - type = "UDP|TCP|TLS|DTLS" + type = "UDP"|"TCP"|"TLS"|"DTLS" + cacertfile = STRING + #cacertpath = STRING + certfile = STRING + certkeyfile = STRING server { hostname = STRING service = STRING @@ -33,6 +43,10 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) cfg_opt_t config_opts[] = { CFG_STR ("type", "UDP", CFGF_NONE), + CFG_STR ("cacertfile", NULL, CFGF_NONE), + /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/ + CFG_STR ("certfile", NULL, CFGF_NONE), + CFG_STR ("certkeyfile", NULL, CFGF_NONE), CFG_SEC ("server", server_opts, CFGF_MULTI), CFG_END () }; @@ -62,6 +76,7 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) ctx->realms = r; cfg_config = cfg_getnsec (cfg, "config", i); r->name = strdup (cfg_title (cfg_config)); + typestr = cfg_getstr (cfg_config, "type"); if (!strcmp (typestr, "UDP")) r->type = RS_CONN_TYPE_UDP; @@ -75,6 +90,11 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) return rs_err_ctx_push_fl (ctx, RSE_CONFIG, __FILE__, __LINE__, "%s: invalid connection type", typestr); + r->cacertfile = cfg_getstr (cfg_config, "cacertfile"); + /*r->cacertpath = cfg_getstr (cfg_config, "cacertpath");*/ + r->certfile = cfg_getstr (cfg_config, "certfile"); + r->certkeyfile = cfg_getstr (cfg_config, "certkeyfile"); + /* Add peers, one per server stanza. */ for (j = 0; j < cfg_size (cfg_config, "server"); j++) { @@ -82,6 +102,7 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) if (!p) return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL); + p->realm = r; cfg_server = cfg_getnsec (cfg_config, "server", j); _rs_resolv (&p->addr, r->type, cfg_getstr (cfg_server, "hostname"), diff --git a/lib/configure.ac b/lib/configure.ac index 5ae729f..2039233 100644 --- a/lib/configure.ac +++ b/lib/configure.ac @@ -11,6 +11,13 @@ AC_PROG_LIBTOOL # Checks for programs. AC_PROG_CC +# Enable-knobs. +AH_TEMPLATE([RS_ENABLE_TLS], [TLS (RadSec) enabled]) +AH_TEMPLATE([RADPROT_TLS], []) +AC_ARG_ENABLE([tls], AS_HELP_STRING([--enable-tls], [enable TLS (RadSec)]), + [AC_DEFINE([RS_ENABLE_TLS]) + AC_DEFINE([RADPROT_TLS])]) + # Checks for libraries. AC_CHECK_LIB([confuse], [cfg_init],, AC_MSG_ERROR([required library libconfuse not found])) @@ -18,9 +25,15 @@ AC_CHECK_LIB([event_core], [event_get_version],, AC_MSG_ERROR([required library libevent_core not found])) AC_CHECK_LIB([freeradius-radius], [rad_alloc],, AC_MSG_ERROR([required library libfreeradius-radius not found])) +dnl TODO: Only do this if --enable-tls or --enable-dtls. +#AC_CHECK_LIB([ssl], [SSL_new],, +# AC_MSG_ERROR([required library libssl not found])) +AC_CHECK_LIB([event_openssl], [bufferevent_openssl_socket_new],, + AC_MSG_ERROR([required library event_openssl not found])) # Checks for header files. -AC_CHECK_HEADERS([netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h unistd.h]) +AC_CHECK_HEADERS( + [netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T @@ -28,8 +41,6 @@ AC_TYPE_SSIZE_T AC_TYPE_UINT8_T # Checks for library functions. -AC_FUNC_MALLOC -AC_FUNC_REALLOC AC_CHECK_FUNCS([memset socket strdup strerror strrchr]) AC_CONFIG_FILES([Makefile diff --git a/lib/conn.c b/lib/conn.c index 62105e6..b65b1da 100644 --- a/lib/conn.c +++ b/lib/conn.c @@ -1,6 +1,11 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include +#include #include #include @@ -49,7 +54,7 @@ _rs_resolv (struct evutil_addrinfo **addr, rs_conn_type_t type, struct evutil_addrinfo hints, *res = NULL; memset (&hints, 0, sizeof(struct evutil_addrinfo)); - hints.ai_family = AF_UNSPEC; /* v4 or v6. */ + hints.ai_family = AF_INET; /* IPv4 only. TODO: Set AF_UNSPEC. */ hints.ai_flags = AI_ADDRCONFIG; switch (type) { diff --git a/lib/debug.c b/lib/debug.c index e6c6afe..604ab23 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -1,5 +1,9 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include diff --git a/lib/err.c b/lib/err.c index 51e4421..8d26d3c 100644 --- a/lib/err.c +++ b/lib/err.c @@ -1,5 +1,9 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include @@ -8,8 +12,8 @@ const char *_errtxt[] = { "SUCCESS", /* 0 RSE_OK */ - "NOMEM", /* 1 RSE_NOMEM */ - "NYI -- not yet implemented", /* 2 RSE_NOSYS */ + "out of memory", /* 1 RSE_NOMEM */ + "not yet implemented", /* 2 RSE_NOSYS */ "invalid handle" /* 3 RSE_INVALID_CTX */ "invalid connection" /* 4 RSE_INVALID_CONN */ "connection type mismatch" /* 5 RSE_CONN_TYPE_MISMATCH */ @@ -19,9 +23,9 @@ const char *_errtxt[] = { "libevent error" /* 9 RSE_EVENT */ "connection error" /* 10 RSE_CONNERR */ "invalid configuration file" /* 11 RSE_CONFIG */ - "authentication failed" /* RSE_BADAUTH */ - "ERR 13" /* RSE_ */ - "ERR 14" /* RSE_ */ + "authentication failed" /* 12 RSE_BADAUTH */ + "internal error" /* 13 RSE_INTERNAL */ + "SSL error" /* 14 RSE_SSLERR */ "ERR 15" /* RSE_ */ "ERR 16" /* RSE_ */ "ERR 17" /* RSE_ */ diff --git a/lib/examples/client.conf b/lib/examples/client.conf index fe2ded5..e939756 100644 --- a/lib/examples/client.conf +++ b/lib/examples/client.conf @@ -1,4 +1,4 @@ -config blocking { +config blocking-udp { type = "UDP" server { hostname = "localhost" @@ -8,3 +8,23 @@ config blocking { tries = 10 /* optional */ } } +config blocking-tls { + type = "TLS" + + cacertfile = "/home/linus/nordberg-ca.crt" + #cacertpath = + certfile = "/home/linus/p/radsecproxy/src/maatuska.nordberg.se.crt" + certkeyfile = "/home/linus/p/radsecproxy/src/maatuska.nordberg.se.key" + #certkeypwd = "passphrase" + #cacheexpiry = + #crlcheck = "on" | "off" + #policyoids = ? + + server { + hostname = "localhost" + service = "4433" + secret = "sikrit" + timeout = 1 /* optional */ + tries = 10 /* optional */ + } +} diff --git a/lib/include/radsec/radsec-impl.h b/lib/include/radsec/radsec-impl.h index 6e5ee83..2b3d878 100644 --- a/lib/include/radsec/radsec-impl.h +++ b/lib/include/radsec/radsec-impl.h @@ -5,6 +5,9 @@ #include #include +#if defined(RS_ENABLE_TLS) +#include +#endif /* Constants. */ #define RS_HEADER_LEN 4 @@ -32,6 +35,7 @@ struct rs_error { struct rs_peer { struct rs_connection *conn; + struct rs_realm *realm; struct evutil_addrinfo *addr; int fd; /* Socket. */ char is_connecting; /* FIXME: replace with a single state member */ @@ -45,6 +49,10 @@ struct rs_peer { struct rs_realm { char *name; enum rs_conn_type type; + char *cacertfile; + char *cacertpath; + char *certfile; + char *certkeyfile; struct rs_peer *peers; struct rs_realm *next; }; @@ -69,6 +77,10 @@ struct rs_connection { struct rs_error *err; int nextid; int user_dispatch_flag : 1; /* User does the dispatching. */ +#if defined(RS_ENABLE_TLS) + SSL_CTX *tls_ctx; + SSL *tls_ssl; +#endif }; struct rs_packet { diff --git a/lib/include/radsec/radsec.h b/lib/include/radsec/radsec.h index 8e97072..d80c296 100644 --- a/lib/include/radsec/radsec.h +++ b/lib/include/radsec/radsec.h @@ -18,6 +18,8 @@ enum rs_err_code { RSE_CONNERR = 10, RSE_CONFIG = 11, RSE_BADAUTH = 12, + RSE_INTERNAL = 13, + RSE_SSLERR = 14, RSE_SOME_ERROR = 21, }; diff --git a/lib/packet.c b/lib/packet.c index cdd094a..154abc8 100644 --- a/lib/packet.c +++ b/lib/packet.c @@ -1,12 +1,22 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + +#include #include #include #include #include #include +#if defined RS_ENABLE_TLS +#include +#include +#endif #include #include +#include "tls.h" #if defined DEBUG #include #include @@ -89,7 +99,7 @@ _event_cb (struct bufferevent *bev, short events, void *ctx) struct rs_packet *pkt = (struct rs_packet *)ctx; struct rs_connection *conn; struct rs_peer *p; - int err; + unsigned long err; assert (pkt); assert (pkt->conn); @@ -103,7 +113,6 @@ _event_cb (struct bufferevent *bev, short events, void *ctx) p->is_connected = 1; if (conn->callbacks.connected_cb) conn->callbacks.connected_cb (conn->user_data); - #if defined (DEBUG) fprintf (stderr, "%s: connected\n", __func__); #endif @@ -114,7 +123,24 @@ _event_cb (struct bufferevent *bev, short events, void *ctx) /* Packet will be freed in write callback. */ } else if (events & BEV_EVENT_ERROR) - rs_err_conn_push_fl (pkt->conn, RSE_CONNERR, __FILE__, __LINE__, NULL); + { +#if defined RS_ENABLE_TLS + if (conn->tls_ssl) /* FIXME: correct check? */ + { + for (err = bufferevent_get_openssl_error (conn->bev); + err; + err = bufferevent_get_openssl_error (conn->bev)) + { + fprintf (stderr, "%s: openssl error: %s\n", __func__, + ERR_error_string (err, NULL)); /* DEBUG, until verified that pushed errors will actually be handled */ + rs_err_conn_push_fl (pkt->conn, RSE_SSLERR, __FILE__, __LINE__, + "%d", err); + } + } +#endif + rs_err_conn_push_fl (pkt->conn, RSE_CONNERR, __FILE__, __LINE__, NULL); + fprintf (stderr, "%s: BEV_EVENT_ERROR\n", __func__); /* DEBUG, until verified that pushed errors will actually be handled */ + } } static void @@ -301,13 +327,41 @@ _pick_peer (struct rs_connection *conn) static int _init_bev (struct rs_connection *conn, struct rs_peer *peer) { - if (!conn->bev) + if (conn->bev) + return RSE_OK; + + switch (conn->type) { + case RS_CONN_TYPE_UDP: + case RS_CONN_TYPE_TCP: conn->bev = bufferevent_socket_new (conn->evb, peer->fd, 0); if (!conn->bev) return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, "bufferevent_socket_new"); + break; + case RS_CONN_TYPE_TLS: + if (rs_tls_init (conn)) + return -1; + /* Would be convenient to pass BEV_OPT_CLOSE_ON_FREE but things + seem to break when be_openssl_ctrl() (in libevent) calls + SSL_set_bio() after BIO_new_socket() with flag=1. */ + conn->bev = + bufferevent_openssl_socket_new (conn->evb, peer->fd, conn->tls_ssl, + BUFFEREVENT_SSL_CONNECTING, 0); + if (!conn->bev) + return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, + "bufferevent_openssl_socket_new"); + + break; + case RS_CONN_TYPE_DTLS: + return rs_err_conn_push_fl (conn, RSE_NOSYS, __FILE__, __LINE__, + "%s: NYI", __func__); + default: + return rs_err_conn_push_fl (conn, RSE_INTERNAL, __FILE__, __LINE__, + "%s: invalid connection type: %d", __func__, + conn->type); } + return RSE_OK; } diff --git a/lib/radsec.c b/lib/radsec.c index b7ac9ba..40d14fc 100644 --- a/lib/radsec.c +++ b/lib/radsec.c @@ -1,5 +1,9 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include @@ -12,6 +16,12 @@ #include #include #include +#if defined RS_ENABLE_TLS +#include +#include "rsp_list.h" +#include "../radsecproxy.h" +#endif +#include "rsp_debug.h" int rs_context_create(struct rs_context **ctx, const char *dict) @@ -48,10 +58,14 @@ rs_context_create(struct rs_context **ctx, const char *dict) } free (buf1); free (buf2); +#if defined RS_ENABLE_TLS + ssl_init (); +#endif #if defined (DEBUG) fr_log_fp = stderr; fr_debug_flag = 1; #endif + debug_init ("libradsec"); /* radsecproxy compat, FIXME: remove */ memset (h, 0, sizeof(struct rs_context)); fr_randinit (&h->fr_randctx, 0); diff --git a/lib/request.c b/lib/request.c index 5cb87bb..bc6f795 100644 --- a/lib/request.c +++ b/lib/request.c @@ -1,5 +1,9 @@ /* See the file COPYING for licensing information. */ +#if defined HAVE_CONFIG_H +#include +#endif + #include #include #include @@ -39,25 +43,25 @@ _timer_cb(evutil_socket_t fd, short what, void *arg) static void _rs_req_connected(void *user_data) { - struct rs_request *request = (struct rs_request *)user_data; + //struct rs_request *request = (struct rs_request *)user_data; } static void _rs_req_disconnected(void *user_data) { - struct rs_request *request = (struct rs_request *)user_data; + //struct rs_request *request = (struct rs_request *)user_data; } static void _rs_req_packet_received(const struct rs_packet *pkt, void *user_data) { - struct rs_request *request = (struct rs_request *)user_data; + //struct rs_request *request = (struct rs_request *)user_data; } static void _rs_req_packet_sent(void *user_data) { - struct rs_request *request = (struct rs_request *)user_data; + //struct rs_request *request = (struct rs_request *)user_data; } int diff --git a/lib/rsp_debug.c b/lib/rsp_debug.c new file mode 100644 index 0000000..dd7c053 --- /dev/null +++ b/lib/rsp_debug.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2007 Stig Venaas + * + * 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. + */ + +#ifndef SYS_SOLARIS9 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rsp_debug.h" +#include "rsp_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; + +void debug_init(char *ident) { + debug_file = stderr; + setvbuf(debug_file, NULL, _IONBF, 0); + debug_ident = ident; +} + +void debug_set_level(uint8_t level) { + switch (level) { + case 1: + debug_level = DBG_ERR; + return; + case 2: + debug_level = DBG_WARN; + return; + case 3: + debug_level = DBG_NOTICE; + return; + case 4: + debug_level = DBG_INFO; + return; + case 5: + debug_level = DBG_DBG; + return; + } +} + +void debug_timestamp_on() { + debug_timestamp = 1; +} + +uint8_t debug_get_level() { + return debug_level; +} + +int debug_set_destination(char *dest) { + static const char *facstrings[] = { "LOG_DAEMON", "LOG_MAIL", "LOG_USER", "LOG_LOCAL0", + "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", + "LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", NULL }; + static const int facvals[] = { LOG_DAEMON, LOG_MAIL, LOG_USER, LOG_LOCAL0, + LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, + LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 }; + extern int errno; + int i; + + if (!strncasecmp(dest, "file:///", 8)) { + 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", + debug_filepath, strerror(errno)); + } + setvbuf(debug_file, NULL, _IONBF, 0); + return 1; + } + if (!strncasecmp(dest, "x-syslog://", 11)) { + dest += 11; + if (*dest == '/') + dest++; + if (*dest) { + for (i = 0; facstrings[i]; i++) + if (!strcasecmp(dest, facstrings[i])) + break; + if (!facstrings[i]) + debugx(1, DBG_ERR, "Unknown syslog facility %s", dest); + debug_syslogfacility = facvals[i]; + } else + debug_syslogfacility = LOG_DAEMON; + openlog(debug_ident, LOG_PID, debug_syslogfacility); + return 1; + } + debug(DBG_ERR, "Unknown log destination, exiting %s", 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; + int priority; + + if (debug_syslogfacility) { + switch (level) { + case DBG_DBG: + priority = LOG_DEBUG; + break; + case DBG_INFO: + priority = LOG_INFO; + break; + case DBG_NOTICE: + priority = LOG_NOTICE; + break; + case DBG_WARN: + priority = LOG_WARNING; + break; + case DBG_ERR: + priority = LOG_ERR; + break; + default: + priority = LOG_DEBUG; + } + vsyslog(priority, format, ap); + } else { + if (debug_timestamp && (timebuf = malloc(256))) { + gettimeofday(&now, NULL); + ctime_r(&now.tv_sec, timebuf); + timebuf[strlen(timebuf) - 1] = '\0'; + fprintf(debug_file, "%s: ", timebuf + 4); + free(timebuf); + } + vfprintf(debug_file, format, ap); + fprintf(debug_file, "\n"); + } +} + +void debug(uint8_t level, char *format, ...) { + va_list ap; + if (level < debug_level) + return; + va_start(ap, format); + debug_logit(level, format, ap); + va_end(ap); +} + +void debugx(int status, uint8_t level, char *format, ...) { + if (level >= debug_level) { + va_list ap; + va_start(ap, format); + debug_logit(level, format, ap); + va_end(ap); + } + exit(status); +} + +void debugerrno(int err, uint8_t level, char *format, ...) { + if (level >= debug_level) { + va_list ap; + size_t len = strlen(format); + char *tmp = malloc(len + 1024 + 2); + assert(tmp); + strcpy(tmp, format); + tmp[len++] = ':'; + tmp[len++] = ' '; + if (strerror_r(err, tmp + len, 1024)) + tmp = format; + va_start(ap, format); + debug_logit(level, tmp, ap); + va_end(ap); + } +} + +void debugerrnox(int err, uint8_t level, char *format, ...) { + if (level >= debug_level) { + va_list ap; + va_start(ap, format); + debugerrno(err, level, format, ap); + va_end(ap); + } + exit(err); +} + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_debug.h b/lib/rsp_debug.h new file mode 100644 index 0000000..803f406 --- /dev/null +++ b/lib/rsp_debug.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 Stig Venaas + * + * 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. + */ + +#ifndef SYS_SOLARIS9 +#include +#endif + +#define DBG_DBG 8 +#define DBG_INFO 16 +#define DBG_NOTICE 32 +#define DBG_WARN 64 +#define DBG_ERR 128 + +void debug_init(char *ident); +void debug_set_level(uint8_t level); +void debug_timestamp_on(); +uint8_t debug_get_level(); +void debug(uint8_t level, char *format, ...); +void debugx(int status, uint8_t level, char *format, ...); +void debugerrno(int err, uint8_t level, char *format, ...); +void debugerrnox(int err, uint8_t level, char *format, ...); +int debug_set_destination(char *dest); +void debug_reopen_log(); + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_hash.c b/lib/rsp_hash.c new file mode 100644 index 0000000..cc37e7f --- /dev/null +++ b/lib/rsp_hash.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008 Stig Venaas + * + * 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 +#include +#include +#include "rsp_list.h" +#include "rsp_hash.h" + +/* allocates and initialises hash structure; returns NULL if malloc fails */ +struct hash *hash_create() { + struct hash *h = malloc(sizeof(struct hash)); + if (!h) + return NULL; + h->hashlist = list_create(); + if (!h->hashlist) { + free(h); + return NULL; + } + pthread_mutex_init(&h->mutex, NULL); + return h; +} + +/* frees all memory associated with the hash */ +void hash_destroy(struct hash *h) { + struct list_node *ln; + + if (!h) + return; + for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { + free(((struct hash_entry *)ln->data)->key); + free(((struct hash_entry *)ln->data)->data); + } + list_destroy(h->hashlist); + pthread_mutex_destroy(&h->mutex); +} + +/* insert entry in hash; returns 1 if ok, 0 if malloc fails */ +int hash_insert(struct hash *h, void *key, uint32_t keylen, void *data) { + struct hash_entry *e; + + if (!h) + return 0; + e = malloc(sizeof(struct hash_entry)); + if (!e) + return 0; + memset(e, 0, sizeof(struct hash_entry)); + e->key = malloc(keylen); + if (!e->key) { + free(e); + return 0; + } + memcpy(e->key, key, keylen); + e->keylen = keylen; + e->data = data; + pthread_mutex_lock(&h->mutex); + if (!list_push(h->hashlist, e)) { + pthread_mutex_unlock(&h->mutex); + free(e->key); + free(e); + return 0; + } + pthread_mutex_unlock(&h->mutex); + return 1; +} + +/* reads entry from hash */ +void *hash_read(struct hash *h, void *key, uint32_t keylen) { + struct list_node *ln; + struct hash_entry *e; + + if (!h) + return 0; + pthread_mutex_lock(&h->mutex); + for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { + e = (struct hash_entry *)ln->data; + if (e->keylen == keylen && !memcmp(e->key, key, keylen)) { + pthread_mutex_unlock(&h->mutex); + return e->data; + } + } + pthread_mutex_unlock(&h->mutex); + return NULL; +} + +/* extracts entry from hash */ +void *hash_extract(struct hash *h, void *key, uint32_t keylen) { + struct list_node *ln; + struct hash_entry *e; + + if (!h) + return 0; + pthread_mutex_lock(&h->mutex); + for (ln = list_first(h->hashlist); ln; ln = list_next(ln)) { + e = (struct hash_entry *)ln->data; + if (e->keylen == keylen && !memcmp(e->key, key, keylen)) { + free(e->key); + list_removedata(h->hashlist, e); + free(e); + pthread_mutex_unlock(&h->mutex); + return e->data; + } + } + pthread_mutex_unlock(&h->mutex); + return NULL; +} + +/* returns first entry */ +struct hash_entry *hash_first(struct hash *hash) { + struct list_node *ln; + struct hash_entry *e; + if (!hash || !((ln = list_first(hash->hashlist)))) + return NULL; + e = (struct hash_entry *)ln->data; + e->next = ln->next; + return e; +} + +/* returns the next node after the argument */ +struct hash_entry *hash_next(struct hash_entry *entry) { + struct hash_entry *e; + if (!entry || !entry->next) + return NULL; + e = (struct hash_entry *)entry->next->data; + e->next = (struct list_node *)entry->next->next; + return e; +} + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_hash.h b/lib/rsp_hash.h new file mode 100644 index 0000000..48f54a3 --- /dev/null +++ b/lib/rsp_hash.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 Stig Venaas + * + * 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. + */ + +#ifndef SYS_SOLARIS9 +#include +#endif + +struct hash { + struct list *hashlist; + pthread_mutex_t mutex; +}; + +struct hash_entry { + void *key; + uint32_t keylen; + void *data; + struct list_node *next; /* used when walking through hash */ +}; + +/* allocates and initialises hash structure; returns NULL if malloc fails */ +struct hash *hash_create(); + +/* frees all memory associated with the hash */ +void hash_destroy(struct hash *hash); + +/* insert entry in hash; returns 1 if ok, 0 if malloc fails */ +int hash_insert(struct hash *hash, void *key, uint32_t keylen, void *data); + +/* reads entry from hash */ +void *hash_read(struct hash *hash, void *key, uint32_t keylen); + +/* extracts (read and remove) entry from hash */ +void *hash_extract(struct hash *hash, void *key, uint32_t keylen); + +/* returns first entry */ +struct hash_entry *hash_first(struct hash *hash); + +/* returns the next entry after the argument */ +struct hash_entry *hash_next(struct hash_entry *entry); + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_list.c b/lib/rsp_list.c new file mode 100644 index 0000000..b6cfe33 --- /dev/null +++ b/lib/rsp_list.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas + * + * 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 HAVE_CONFIG_H +#include +#endif + +#include +#include +#include "rsp_list.h" + +/* allocates and initialises list structure; returns NULL if malloc fails */ +struct list *list_create() { + struct list *list = malloc(sizeof(struct list)); + if (list) + memset(list, 0, sizeof(struct list)); + return list; +} + +/* frees all memory associated with the list */ +void list_destroy(struct list *list) { + struct list_node *node, *next; + + if (!list) + return; + + for (node = list->first; node; node = next) { + free(node->data); + next = node->next; + free(node); + } + free(list); +} + +/* appends entry to list; returns 1 if ok, 0 if malloc fails */ +int list_push(struct list *list, void *data) { + struct list_node *node; + + node = malloc(sizeof(struct list_node)); + if (!node) + return 0; + + node->next = NULL; + node->data = data; + + if (list->first) + list->last->next = node; + else + list->first = node; + list->last = node; + + list->count++; + return 1; +} + +/* removes first entry from list and returns data */ +void *list_shift(struct list *list) { + struct list_node *node; + void *data; + + if (!list || !list->first) + return NULL; + + node = list->first; + list->first = node->next; + if (!list->first) + list->last = NULL; + data = node->data; + free(node); + list->count--; + return data; +} + +/* removes all entries with matching data pointer */ +void list_removedata(struct list *list, void *data) { + struct list_node *node, *t; + + if (!list || !list->first) + return; + + node = list->first; + while (node->data == data) { + list->first = node->next; + free(node); + list->count--; + node = list->first; + if (!node) { + list->last = NULL; + return; + } + } + for (; node->next; node = node->next) + if (node->next->data == data) { + t = node->next; + node->next = t->next; + free(t); + list->count--; + if (!node->next) { /* we removed the last one */ + list->last = node; + return; + } + } +} + +/* returns first node */ +struct list_node *list_first(struct list *list) { + return list ? list->first : NULL; +} + +/* returns the next node after the argument */ +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; +} + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_list.h b/lib/rsp_list.h new file mode 100644 index 0000000..80c0128 --- /dev/null +++ b/lib/rsp_list.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas + * + * 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 +#else +#include +#endif + +struct list_node { + struct list_node *next; + void *data; +}; + +struct list { + struct list_node *first, *last; + uint32_t count; +}; + +/* allocates and initialises list structure; returns NULL if malloc fails */ +struct list *list_create(); + +/* frees all memory associated with the list */ +void list_destroy(struct list *list); + +/* appends entry to list; returns 1 if ok, 0 if malloc fails */ +int list_push(struct list *list, void *data); + +/* removes first entry from list and returns data */ +void *list_shift(struct list *list); + +/* removes first entry with matching data pointer */ +void list_removedata(struct list *list, void *data); + +/* returns first node */ +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); + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_tlscommon.c b/lib/rsp_tlscommon.c new file mode 100644 index 0000000..6002c41 --- /dev/null +++ b/lib/rsp_tlscommon.c @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2006-2009 Stig Venaas + * + * 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 HAVE_CONFIG_H +#include +#endif + +#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) +#include +#include +#include +#include +#include +#include +#include +#ifdef SYS_SOLARIS9 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rsp_debug.h" +#include "rsp_list.h" +#include "rsp_hash.h" +#include "rsp_util.h" +#include "../hostport_types.h" +#include "../radsecproxy.h" + +static struct hash *tlsconfs = NULL; + +void ssl_init(void) { + time_t t; + pid_t pid; + + SSL_load_error_strings(); + SSL_library_init(); + + while (!RAND_status()) { + t = time(NULL); + pid = getpid(); + RAND_seed((unsigned char *)&t, sizeof(time_t)); + RAND_seed((unsigned char *)&pid, sizeof(pid)); + } +} + +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()); + break; +#endif +#ifdef RADPROT_DTLS + case RAD_DTLS: + ctx = SSL_CTX_new(DTLSv1_method()); + 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); + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + return NULL; + } +#ifdef DEBUG + SSL_CTX_set_info_callback(ctx, ssl_info_callback); +#endif + + if (conf->certkeypwd) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd); + SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); + } + if (conf->certfile || conf->certkeyfile) { + 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 (certfile issues) 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; +} + +#if 0 +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; +} +#endif + +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 + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_tlscommon.h b/lib/rsp_tlscommon.h new file mode 100644 index 0000000..d7e0930 --- /dev/null +++ b/lib/rsp_tlscommon.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2006-2008 Stig Venaas + * + * 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 + +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) +void ssl_init(); +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 + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_util.c b/lib/rsp_util.c new file mode 100644 index 0000000..22b8352 --- /dev/null +++ b/lib/rsp_util.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2006-2008 Stig Venaas + * + * 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. + */ + +/* Code contributions from: + * + * Stefan Winter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rsp_debug.h" +#include "rsp_util.h" + +char *stringcopy(const char *s, int len) { + char *r; + if (!s) + return NULL; + if (!len) + len = strlen(s); + r = malloc(len + 1); + if (!r) + debug(DBG_ERR, "stringcopy: malloc failed"); + memcpy(r, s, len); + r[len] = '\0'; + return r; +} + +void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) { + int i; + unsigned char *s = (unsigned char *)chars; + if (prefix) + printf(prefixfmt ? prefixfmt : "%s: ", prefix); + for (i = 0; i < len; i++) + printf(charfmt ? charfmt : "%c", s[i]); + printf("\n"); +} + +void port_set(struct sockaddr *sa, uint16_t port) { + switch (sa->sa_family) { + case AF_INET: + ((struct sockaddr_in *)sa)->sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); + break; + } +} + +struct sockaddr *addr_copy(struct sockaddr *in) { + struct sockaddr *out = NULL; + + switch (in->sa_family) { + case AF_INET: + out = malloc(sizeof(struct sockaddr_in)); + if (out) { + memset(out, 0, sizeof(struct sockaddr_in)); + ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr; + } + break; + case AF_INET6: + out = malloc(sizeof(struct sockaddr_in6)); + if (out) { + memset(out, 0, sizeof(struct sockaddr_in6)); + ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr; + } + break; + } + out->sa_family = in->sa_family; +#ifdef SIN6_LEN + out->sa_len = in->sa_len; +#endif + return out; +} + +char *addr2string(struct sockaddr *addr) { + struct sockaddr_in6 *sa6; + struct sockaddr_in sa4; + static char addr_buf[2][INET6_ADDRSTRLEN]; + static int i = 0; + i = !i; + if (addr->sa_family == AF_INET6) { + sa6 = (struct sockaddr_in6 *)addr; + if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + sa4.sin_port = sa6->sin6_port; + memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4); + addr = (struct sockaddr *)&sa4; + } + } + if (getnameinfo(addr, SOCKADDRP_SIZE(addr), addr_buf[i], sizeof(addr_buf[i]), + NULL, 0, NI_NUMERICHOST)) { + debug(DBG_WARN, "getnameinfo failed"); + return "getnameinfo_failed"; + } + 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; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = type; + hints.ai_family = AF_UNSPEC; + + if (getaddrinfo(host, port, &hints, &res0) != 0) { + debug(DBG_ERR, "connectport: can't resolve host %s port %s", host, port); + return -1; + } + + for (res = res0; res; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s < 0) { + debug(DBG_WARN, "connectport: socket failed"); + continue; + } + if (connect(s, res->ai_addr, res->ai_addrlen) == 0) + break; + debug(DBG_WARN, "connectport: connect failed"); + close(s); + s = -1; + } + freeaddrinfo(res0); + return s; +} +#endif + +/* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized" + RADIUS packet to be discarded on first attempt (due to Path MTU discovery). +*/ + +void disable_DF_bit(int socket, struct addrinfo *res) { + if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) { +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + /* + * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant. + */ + int r, action; + debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)"); + action = IP_PMTUDISC_DONT; + r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action)); + if (r == -1) + debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER"); +#else + debug(DBG_INFO, "Non-Linux platform, unable to unset DF bit for UDP. You should check with tcpdump whether radsecproxy will send its UDP packets with DF bit set!"); +#endif + } +} + +int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) { + int s, on = 1; + struct addrinfo *res; + + for (res = addrinfo; res; res = res->ai_next) { + if (family != AF_UNSPEC && family != res->ai_family) + continue; + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s < 0) { + debug(DBG_WARN, "bindtoaddr: socket failed"); + continue; + } + + disable_DF_bit(s,res); + + if (reuse) + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); +#ifdef IPV6_V6ONLY + if (v6only) + setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); +#endif + if (!bind(s, res->ai_addr, res->ai_addrlen)) + return s; + debug(DBG_WARN, "bindtoaddr: bind failed"); + close(s); + } + return -1; +} + +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 ((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); + s = -1; + } + return s; +} + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/rsp_util.h b/lib/rsp_util.h new file mode 100644 index 0000000..d8d002c --- /dev/null +++ b/lib/rsp_util.h @@ -0,0 +1,25 @@ +#include +#include + +#define SOCKADDR_SIZE(addr) ((addr).ss_family == AF_INET ? \ + sizeof(struct sockaddr_in) : \ + sizeof(struct sockaddr_in6)) + +#define SOCKADDRP_SIZE(addr) ((addr)->sa_family == AF_INET ? \ + sizeof(struct sockaddr_in) : \ + sizeof(struct sockaddr_in6)) + +char *stringcopy(const char *s, int len); +char *addr2string(struct sockaddr *addr); +struct sockaddr *addr_copy(struct sockaddr *in); +void port_set(struct sockaddr *sa, uint16_t port); + +void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len); +void disable_DF_bit(int socket, struct addrinfo *res); +int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only); +int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout); + + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/lib/tls.c b/lib/tls.c new file mode 100644 index 0000000..15929d2 --- /dev/null +++ b/lib/tls.c @@ -0,0 +1,73 @@ +/* See the file COPYING for licensing information. */ + +#if defined HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include "rsp_list.h" +#include "../radsecproxy.h" + +static struct tls * +_get_tlsconf (const struct rs_context *ctx, const struct rs_realm *realm) +{ + struct tls *c = rs_malloc (ctx, sizeof (struct tls)); + + if (c) + { + memset (c, 0, sizeof (struct tls)); + /* TODO: Make sure old radsecproxy code doesn't free these all + of a sudden, or strdup them. */ + c->name = realm->name; + c->cacertfile = realm->cacertfile; + c->cacertpath = NULL; /* NYI */ + c->certfile = realm->certfile; + c->certkeyfile = realm->certkeyfile; + c->certkeypwd = NULL; /* NYI */ + c->cacheexpiry = 0; /* NYI */ + c->crlcheck = 0; /* NYI */ + c->policyoids = (char **) NULL; /* NYI */ + } + + return c; +} + +int +rs_tls_init (struct rs_connection *conn) +{ + struct rs_context *ctx; + struct tls *tlsconf; + SSL_CTX *ssl_ctx; + SSL *ssl; + assert (conn->ctx); + ctx = conn->ctx; + + tlsconf = _get_tlsconf (ctx, conn->active_peer->realm); + assert (tlsconf); + ssl_ctx = tlsgetctx (RADPROT_TLS, tlsconf); + if (!ssl_ctx) + { + /* TODO: check radsecproxy error */ + return rs_err_conn_push_fl (conn, RSE_SOME_ERROR, __FILE__, __LINE__, + NULL); + } + + ssl = SSL_new (ssl_ctx); + if (!ssl) + { + /* TODO: check and report SSL error */ + /* TODO: free ssl_ctx */ + return rs_err_conn_push_fl (conn, RSE_SOME_ERROR, __FILE__, __LINE__, + NULL); + } + + conn->tls_ctx = ssl_ctx; + conn->tls_ssl = ssl; + rs_free (ctx, tlsconf); + return RSE_OK; +} diff --git a/lib/tls.h b/lib/tls.h new file mode 100644 index 0000000..7e10a46 --- /dev/null +++ b/lib/tls.h @@ -0,0 +1 @@ +int rs_tls_init (struct rs_connection *conn); diff --git a/list.c b/list.c deleted file mode 100644 index 58ab7aa..0000000 --- a/list.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2006-2009 Stig Venaas - * - * 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 -#include -#include "list.h" - -/* allocates and initialises list structure; returns NULL if malloc fails */ -struct list *list_create() { - struct list *list = malloc(sizeof(struct list)); - if (list) - memset(list, 0, sizeof(struct list)); - return list; -} - -/* frees all memory associated with the list */ -void list_destroy(struct list *list) { - struct list_node *node, *next; - - if (!list) - return; - - for (node = list->first; node; node = next) { - free(node->data); - next = node->next; - free(node); - } - free(list); -} - -/* appends entry to list; returns 1 if ok, 0 if malloc fails */ -int list_push(struct list *list, void *data) { - struct list_node *node; - - node = malloc(sizeof(struct list_node)); - if (!node) - return 0; - - node->next = NULL; - node->data = data; - - if (list->first) - list->last->next = node; - else - list->first = node; - list->last = node; - - list->count++; - return 1; -} - -/* removes first entry from list and returns data */ -void *list_shift(struct list *list) { - struct list_node *node; - void *data; - - if (!list || !list->first) - return NULL; - - node = list->first; - list->first = node->next; - if (!list->first) - list->last = NULL; - data = node->data; - free(node); - list->count--; - return data; -} - -/* removes all entries with matching data pointer */ -void list_removedata(struct list *list, void *data) { - struct list_node *node, *t; - - if (!list || !list->first) - return; - - node = list->first; - while (node->data == data) { - list->first = node->next; - free(node); - list->count--; - node = list->first; - if (!node) { - list->last = NULL; - return; - } - } - for (; node->next; node = node->next) - if (node->next->data == data) { - t = node->next; - node->next = t->next; - free(t); - list->count--; - if (!node->next) { /* we removed the last one */ - list->last = node; - return; - } - } -} - -/* returns first node */ -struct list_node *list_first(struct list *list) { - return list ? list->first : NULL; -} - -/* returns the next node after the argument */ -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; -} - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/list.h b/list.h deleted file mode 100644 index 80c0128..0000000 --- a/list.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2006-2009 Stig Venaas - * - * 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 -#else -#include -#endif - -struct list_node { - struct list_node *next; - void *data; -}; - -struct list { - struct list_node *first, *last; - uint32_t count; -}; - -/* allocates and initialises list structure; returns NULL if malloc fails */ -struct list *list_create(); - -/* frees all memory associated with the list */ -void list_destroy(struct list *list); - -/* appends entry to list; returns 1 if ok, 0 if malloc fails */ -int list_push(struct list *list, void *data); - -/* removes first entry from list and returns data */ -void *list_shift(struct list *list); - -/* removes first entry with matching data pointer */ -void list_removedata(struct list *list, void *data); - -/* returns first node */ -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); - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/radsecproxy.h b/radsecproxy.h index 7528f7f..09b5d6e 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -107,7 +107,7 @@ struct clsrvconf { struct server *servers; }; -#include "tlscommon.h" +#include "rsp_tlscommon.h" struct client { struct clsrvconf *conf; diff --git a/tlscommon.c b/tlscommon.c deleted file mode 100644 index 6d36ebb..0000000 --- a/tlscommon.c +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright (C) 2006-2009 Stig Venaas - * - * 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 -#include -#include -#include -#include -#include -#include -#ifdef SYS_SOLARIS9 -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/tlscommon.h b/tlscommon.h deleted file mode 100644 index 084cb1c..0000000 --- a/tlscommon.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2006-2008 Stig Venaas - * - * 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 - -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 - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/util.c b/util.c deleted file mode 100644 index 5235d8a..0000000 --- a/util.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2006-2008 Stig Venaas - * - * 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. - */ - -/* Code contributions from: - * - * Stefan Winter - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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); - if (!r) - debug(DBG_ERR, "stringcopy: malloc failed"); - memcpy(r, s, len); - r[len] = '\0'; - return r; -} - -void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) { - int i; - unsigned char *s = (unsigned char *)chars; - if (prefix) - printf(prefixfmt ? prefixfmt : "%s: ", prefix); - for (i = 0; i < len; i++) - printf(charfmt ? charfmt : "%c", s[i]); - printf("\n"); -} - -void port_set(struct sockaddr *sa, uint16_t port) { - switch (sa->sa_family) { - case AF_INET: - ((struct sockaddr_in *)sa)->sin_port = htons(port); - break; - case AF_INET6: - ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); - break; - } -} - -struct sockaddr *addr_copy(struct sockaddr *in) { - struct sockaddr *out = NULL; - - switch (in->sa_family) { - case AF_INET: - out = malloc(sizeof(struct sockaddr_in)); - if (out) { - memset(out, 0, sizeof(struct sockaddr_in)); - ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr; - } - break; - case AF_INET6: - out = malloc(sizeof(struct sockaddr_in6)); - if (out) { - memset(out, 0, sizeof(struct sockaddr_in6)); - ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr; - } - break; - } - out->sa_family = in->sa_family; -#ifdef SIN6_LEN - out->sa_len = in->sa_len; -#endif - return out; -} - -char *addr2string(struct sockaddr *addr) { - struct sockaddr_in6 *sa6; - struct sockaddr_in sa4; - static char addr_buf[2][INET6_ADDRSTRLEN]; - static int i = 0; - i = !i; - if (addr->sa_family == AF_INET6) { - sa6 = (struct sockaddr_in6 *)addr; - if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { - memset(&sa4, 0, sizeof(sa4)); - sa4.sin_family = AF_INET; - sa4.sin_port = sa6->sin6_port; - memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4); - addr = (struct sockaddr *)&sa4; - } - } - if (getnameinfo(addr, SOCKADDRP_SIZE(addr), addr_buf[i], sizeof(addr_buf[i]), - NULL, 0, NI_NUMERICHOST)) { - debug(DBG_WARN, "getnameinfo failed"); - return "getnameinfo_failed"; - } - 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; - - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = type; - hints.ai_family = AF_UNSPEC; - - if (getaddrinfo(host, port, &hints, &res0) != 0) { - debug(DBG_ERR, "connectport: can't resolve host %s port %s", host, port); - return -1; - } - - for (res = res0; res; res = res->ai_next) { - s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (s < 0) { - debug(DBG_WARN, "connectport: socket failed"); - continue; - } - if (connect(s, res->ai_addr, res->ai_addrlen) == 0) - break; - debug(DBG_WARN, "connectport: connect failed"); - close(s); - s = -1; - } - freeaddrinfo(res0); - return s; -} -#endif - -/* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized" - RADIUS packet to be discarded on first attempt (due to Path MTU discovery). -*/ - -void disable_DF_bit(int socket, struct addrinfo *res) { - if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) { -#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) - /* - * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant. - */ - int r, action; - debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)"); - action = IP_PMTUDISC_DONT; - r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action)); - if (r == -1) - debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER"); -#else - debug(DBG_INFO, "Non-Linux platform, unable to unset DF bit for UDP. You should check with tcpdump whether radsecproxy will send its UDP packets with DF bit set!"); -#endif - } -} - -int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) { - int s, on = 1; - struct addrinfo *res; - - for (res = addrinfo; res; res = res->ai_next) { - if (family != AF_UNSPEC && family != res->ai_family) - continue; - s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (s < 0) { - debug(DBG_WARN, "bindtoaddr: socket failed"); - continue; - } - - disable_DF_bit(s,res); - - if (reuse) - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); -#ifdef IPV6_V6ONLY - if (v6only) - setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); -#endif - if (!bind(s, res->ai_addr, res->ai_addrlen)) - return s; - debug(DBG_WARN, "bindtoaddr: bind failed"); - close(s); - } - return -1; -} - -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 ((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); - s = -1; - } - return s; -} - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ diff --git a/util.h b/util.h deleted file mode 100644 index d8d002c..0000000 --- a/util.h +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include - -#define SOCKADDR_SIZE(addr) ((addr).ss_family == AF_INET ? \ - sizeof(struct sockaddr_in) : \ - sizeof(struct sockaddr_in6)) - -#define SOCKADDRP_SIZE(addr) ((addr)->sa_family == AF_INET ? \ - sizeof(struct sockaddr_in) : \ - sizeof(struct sockaddr_in6)) - -char *stringcopy(const char *s, int len); -char *addr2string(struct sockaddr *addr); -struct sockaddr *addr_copy(struct sockaddr *in); -void port_set(struct sockaddr *sa, uint16_t port); - -void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len); -void disable_DF_bit(int socket, struct addrinfo *res); -int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only); -int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout); - - -/* Local Variables: */ -/* c-file-style: "stroustrup" */ -/* End: */ -- cgit v1.1