From: Linus Nordberg Date: Fri, 27 Apr 2012 15:00:17 +0000 (+0200) Subject: Merge libradsec-new-client. X-Git-Tag: libradsec-0.0.4~61^2~17 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=libradsec.git;a=commitdiff_plain;h=4b0ff99282a91bba93eec9db37831be73b8134e4;hp=efb18a601811888127be69499cf10891aa3a4c37 Merge libradsec-new-client. --- diff --git a/lib/HACKING b/lib/HACKING index 1494941..824cb77 100644 --- a/lib/HACKING +++ b/lib/HACKING @@ -20,6 +20,14 @@ examples/client -r examples/client.conf blocking-tls; echo $? (a.k.a. on-your-own mode) - User chooses allocation regime +Note that as of 0.0.2.dev libradsec suffers from way too much focus on +the behaviour of a blocking client and is totally useless as a server. +Not only does it lack most of the functions needed for writing a +server but it also contains at least one architectural mishap which +kills the server idea. A connection timeout (TCP) or a retransmit +timeout (UDP) will result in the event loop being broken. The same is +thing will happen if there's an error on a TCP connection, f.ex. a +failing certificate validation (TLS). * Dependencies Details apply to Ubuntu 10.10. @@ -38,13 +46,13 @@ Details apply to Ubuntu 10.10. - [TCP] short read - [TCP] short write - [TLS] basic tls support +- [TLS] preshared key support +- [TLS] verification of CN ** Known issues - error stack is only one entry deep - custom allocation scheme is not used in all places ** Not implemented - server failover -- [TLS] verification of CN -- [TLS] preshared key support - [DTLS] support * Found a bug? diff --git a/lib/Makefile.am b/lib/Makefile.am index 51aebf9..9a74a7f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,6 +1,22 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 +# Shared library interface version, i.e. -version-info to Libtool, +# expressed as three integers CURRENT:REVISION:AGE. + +# CURRENT is the version number of the current interface. Increment +# CURRENT when the library interface changes. + +# REVISION is the version number of the _implementation_ of the +# CURRENT interface. Set REVISION to 0 when CURRENT changes, +# else increment. + +# AGE is the number of interfaces this library implements, i.e. how +# many versions before CURRENT that are supported. Increment AGE +# when the library interface is _extended_. Set AGE to 0 when the +# library interface is _changed_. + + SUBDIRS = radius . include examples INCLUDES = -I$(srcdir)/include @@ -22,7 +38,8 @@ libradsec_la_SOURCES = \ request.c \ send.c \ tcp.c \ - udp.c + udp.c \ + util.c libradsec_la_SOURCES += \ rsp_debug.c \ diff --git a/lib/conf.c b/lib/conf.c index 84bd1a8..1cb7049 100644 --- a/lib/conf.c +++ b/lib/conf.c @@ -8,9 +8,11 @@ #include #include #include +#include #include #include #include "peer.h" +#include "util.h" #include "debug.h" #if 0 @@ -25,6 +27,10 @@ #cacertpath = STRING certfile = STRING certkeyfile = STRING + pskstr = STRING # Transport pre-shared key, UTF-8 form. + pskhexstr = STRING # Transport pre-shared key, ASCII hex form. + pskid = STRING + pskex = "PSK"|"DHE_PSK"|"RSA_PSK" } # client specific realm config options @@ -32,12 +38,12 @@ server { hostname = STRING service = STRING - secret = STRING + secret = STRING # RADIUS secret } } #endif -/* FIXME: Leaking memory in error cases? */ +/* FIXME: Leaking memory in error cases. */ int rs_context_read_config(struct rs_context *ctx, const char *config_file) { @@ -63,6 +69,10 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/ CFG_STR ("certfile", NULL, CFGF_NONE), CFG_STR ("certkeyfile", NULL, CFGF_NONE), + CFG_STR ("pskstr", NULL, CFGF_NONE), + CFG_STR ("pskhexstr", NULL, CFGF_NONE), + CFG_STR ("pskid", NULL, CFGF_NONE), + CFG_STR ("pskex", "PSK", CFGF_NONE), CFG_SEC ("server", server_opts, CFGF_MULTI), CFG_END () }; @@ -101,6 +111,7 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) { struct rs_realm *r = NULL; const char *typestr; + char *pskstr = NULL, *pskhexstr = NULL; r = rs_calloc (ctx, 1, sizeof(*r)); if (r == NULL) @@ -115,14 +126,14 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) config->realms = r; } cfg_realm = cfg_getnsec (cfg, "realm", i); - /* We use a copy of the return value of cfg_title() since it's const. */ s = cfg_title (cfg_realm); if (s == NULL) return rs_err_ctx_push_fl (ctx, RSE_CONFIG, __FILE__, __LINE__, "missing realm name"); - r->name = strdup (s); + /* We use a copy of the return value of cfg_title() since it's const. */ + r->name = rs_strdup (ctx, s); if (r->name == NULL) - return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL); + return RSE_NOMEM; typestr = cfg_getstr (cfg_realm, "type"); if (strcmp (typestr, "UDP") == 0) @@ -134,8 +145,9 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) else if (strcmp (typestr, "DTLS") == 0) r->type = RS_CONN_TYPE_DTLS; else - return rs_err_ctx_push_fl (ctx, RSE_CONFIG, __FILE__, __LINE__, - "invalid connection type: %s", typestr); + return rs_err_ctx_push (ctx, RSE_CONFIG, + "%s: invalid connection type: %s", + r->name, typestr); r->timeout = cfg_getint (cfg_realm, "timeout"); r->retries = cfg_getint (cfg_realm, "retries"); @@ -144,6 +156,65 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) r->certfile = cfg_getstr (cfg_realm, "certfile"); r->certkeyfile = cfg_getstr (cfg_realm, "certkeyfile"); + pskstr = cfg_getstr (cfg_realm, "pskstr"); + pskhexstr = cfg_getstr (cfg_realm, "pskhexstr"); + if (pskstr || pskhexstr) + { +#if defined RS_ENABLE_TLS_PSK + char *kex = cfg_getstr (cfg_realm, "pskex"); + rs_cred_type_t type = RS_CRED_NONE; + struct rs_credentials *cred = NULL; + assert (kex != NULL); + + if (!strcmp (kex, "PSK")) + type = RS_CRED_TLS_PSK; + else + { + /* TODO: push a warning on the error stack:*/ + /*rs_err_ctx_push (ctx, RSE_WARN, "%s: unsupported PSK key exchange" + " algorithm -- PSK not used", kex);*/ + } + + if (type != RS_CRED_NONE) + { + cred = rs_calloc (ctx, 1, sizeof (*cred)); + if (cred == NULL) + return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, + NULL); + cred->type = type; + cred->identity = cfg_getstr (cfg_realm, "pskid"); + if (pskhexstr) + { + cred->secret_encoding = RS_KEY_ENCODING_ASCII_HEX; + cred->secret = pskhexstr; + if (pskstr) + ; /* TODO: warn that we're ignoring pskstr */ + } + else + { + cred->secret_encoding = RS_KEY_ENCODING_UTF8; + cred->secret = pskstr; + } + + r->transport_cred = cred; + } +#else /* !RS_ENABLE_TLS_PSK */ + /* TODO: push a warning on the error stack: */ + /* rs_err_ctx_push (ctx, RSE_WARN, "libradsec wasn't configured with " + "support for TLS preshared keys, ignoring pskstr " + "and pskhexstr");*/ +#endif /* RS_ENABLE_TLS_PSK */ + } + + /* For TLS and DTLS realms, validate that we either have (i) CA + cert file or path or (ii) PSK. */ + if ((r->type == RS_CONN_TYPE_TLS || r->type == RS_CONN_TYPE_DTLS) + && (r->cacertfile == NULL && r->cacertpath == NULL) + && r->transport_cred == NULL) + return rs_err_ctx_push (ctx, RSE_CONFIG, + "%s: missing both CA file/path and PSK", + r->name); + /* Add peers, one per server stanza. */ for (j = 0; j < cfg_size (cfg_realm, "server"); j++) { @@ -154,10 +225,8 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file) p->realm = r; cfg_server = cfg_getnsec (cfg_realm, "server", j); - /* FIXME: Handle resolve errors, possibly by postponing name - resolution. */ - rs_resolv (&p->addr, r->type, cfg_getstr (cfg_server, "hostname"), - cfg_getstr (cfg_server, "service")); + p->hostname = cfg_getstr (cfg_server, "hostname"); + p->service = cfg_getstr (cfg_server, "service"); p->secret = cfg_getstr (cfg_server, "secret"); } } diff --git a/lib/configure.ac b/lib/configure.ac index 9b1d304..bb71a6a 100644 --- a/lib/configure.ac +++ b/lib/configure.ac @@ -19,14 +19,22 @@ AC_CHECK_LIB([event_core], [event_get_version],, AC_MSG_ERROR([required library libevent_core not found])) # Enable-knobs. +## Enable TLS (RadSec). AH_TEMPLATE([RS_ENABLE_TLS], [TLS (RadSec) enabled]) -AH_TEMPLATE([RADPROT_TLS], []) +AH_TEMPLATE([RADPROT_TLS], []) dnl Legacy. AC_ARG_ENABLE([tls], AS_HELP_STRING([--enable-tls], [enable TLS (RadSec)]), [AC_CHECK_LIB([event_openssl], [bufferevent_openssl_socket_new],, AC_MSG_ERROR([required library event_openssl not found])) AC_DEFINE([RS_ENABLE_TLS]) - AC_DEFINE([RADPROT_TLS])]) + AC_DEFINE([RADPROT_TLS])]) dnl Legacy. AM_CONDITIONAL([RS_ENABLE_TLS], [test "${enable_tls+set}" = set]) +## Enable TLS-PSK (preshared keys). +AH_TEMPLATE([RS_ENABLE_TLS_PSK], [TLS-PSK (TLS preshared keys) enabled]) +AC_ARG_ENABLE([tls-psk], AS_HELP_STRING([--enable-tls-psk], [enable TLS-PSK (TLS preshared keys)]), + [AC_CHECK_LIB([ssl], [SSL_set_psk_client_callback],, + AC_MSG_ERROR([required library openssl with SSL_set_psk_client_callback() not found])) + AC_DEFINE([RS_ENABLE_TLS_PSK])]) +AM_CONDITIONAL([RS_ENABLE_TLS_PSK], [test "${enable_tls_psk+set}" = set]) # Checks for header files. AC_CHECK_HEADERS( diff --git a/lib/conn.c b/lib/conn.c index 7d0a794..fa63727 100644 --- a/lib/conn.c +++ b/lib/conn.c @@ -224,7 +224,7 @@ rs_conn_receive_packet (struct rs_connection *conn, assert (conn); assert (conn->realm); - assert (!conn_user_dispatch_p (conn)); /* Dispatching mode only. */ + assert (!conn_user_dispatch_p (conn)); /* Blocking mode only. */ if (rs_packet_create (conn, &pkt)) return -1; @@ -254,7 +254,7 @@ rs_conn_receive_packet (struct rs_connection *conn, "event_add: %s", evutil_gai_strerror (err)); - /* Activae retransmission timer. */ + /* Activate retransmission timer. */ conn_activate_timeout (pkt->conn); } @@ -271,7 +271,10 @@ rs_conn_receive_packet (struct rs_connection *conn, || (req_msg && packet_verify_response (pkt->conn, pkt, req_msg) != RSE_OK)) { - assert (rs_err_conn_peek_code (pkt->conn)); + if (rs_err_conn_peek_code (pkt->conn) == RSE_OK) + /* No packet and no error on the stack _should_ mean that the + server hung up on us. */ + rs_err_conn_push (pkt->conn, RSE_DISCO, "no response"); return rs_err_conn_peek_code (conn); } diff --git a/lib/err.c b/lib/err.c index e318047..60ef82f 100644 --- a/lib/err.c +++ b/lib/err.c @@ -56,6 +56,8 @@ static const char *_errtxt[] = { "response from the wrong source address", /* 40 RSE_INVALID_RESPONSE_SRC */ "no packet data", /* 41 RSE_NO_PACKET_DATA */ "vendor is unknown", /* 42 RSE_VENDOR_UNKNOWN */ + "invalid credentials", /* 43 RSE_CRED */ + "certificate validation error", /* 44 RSE_CERT */ }; #define ERRTXT_SIZE (sizeof(_errtxt) / sizeof(*_errtxt)) diff --git a/lib/event.c b/lib/event.c index b2096bc..4f83394 100644 --- a/lib/event.c +++ b/lib/event.c @@ -22,6 +22,8 @@ #if defined (RS_ENABLE_TLS) #include "tls.h" #endif +#include "err.h" +#include "radsec.h" #include "event.h" #include "packet.h" #include "conn.h" @@ -100,9 +102,16 @@ event_init_socket (struct rs_connection *conn, struct rs_peer *p) if (conn->fd != -1) return RSE_OK; - assert (p->addr); - conn->fd = socket (p->addr->ai_family, p->addr->ai_socktype, - p->addr->ai_protocol); + if (p->addr_cache == NULL) + { + struct rs_error *err = + rs_resolve (&p->addr_cache, p->realm->type, p->hostname, p->service); + if (err != NULL) + return err_conn_push_err (conn, err); + } + + conn->fd = socket (p->addr_cache->ai_family, p->addr_cache->ai_socktype, + p->addr_cache->ai_protocol); if (conn->fd < 0) return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, "socket: %d (%s)", @@ -171,8 +180,8 @@ event_do_connect (struct rs_connection *conn) { char host[80], serv[80]; - getnameinfo (p->addr->ai_addr, - p->addr->ai_addrlen, + getnameinfo (p->addr_cache->ai_addr, + p->addr_cache->ai_addrlen, host, sizeof(host), serv, sizeof(serv), 0 /* NI_NUMERICHOST|NI_NUMERICSERV*/); rs_debug (("%s: connecting to %s:%s\n", __func__, host, serv)); @@ -182,8 +191,8 @@ event_do_connect (struct rs_connection *conn) if (p->conn->bev) /* TCP */ { conn_activate_timeout (conn); /* Connect timeout. */ - err = bufferevent_socket_connect (p->conn->bev, p->addr->ai_addr, - p->addr->ai_addrlen); + err = bufferevent_socket_connect (p->conn->bev, p->addr_cache->ai_addr, + p->addr_cache->ai_addrlen); if (err < 0) rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__, "bufferevent_socket_connect: %s", @@ -193,7 +202,9 @@ event_do_connect (struct rs_connection *conn) } else /* UDP */ { - err = connect (p->conn->fd, p->addr->ai_addr, p->addr->ai_addrlen); + err = connect (p->conn->fd, + p->addr_cache->ai_addr, + p->addr_cache->ai_addrlen); if (err < 0) { sockerr = evutil_socket_geterror (p->conn->fd); @@ -228,10 +239,22 @@ event_on_disconnect (struct rs_connection *conn) conn->callbacks.disconnected_cb (conn->user_data); } -void +/** Internal connect event returning 0 on success or -1 on error. */ +int event_on_connect (struct rs_connection *conn, struct rs_packet *pkt) { assert (!conn->is_connecting); + +#if defined (RS_ENABLE_TLS) + if (conn->realm->type == RS_CONN_TYPE_TLS + || conn->realm->type == RS_CONN_TYPE_DTLS) + if (tls_verify_cert (conn) != RSE_OK) + { + rs_debug (("%s: server cert verification failed\n", __func__)); + return -1; + } +#endif /* RS_ENABLE_TLS */ + conn->is_connected = 1; rs_debug (("%s: %p connected\n", __func__, conn->active_peer)); @@ -240,6 +263,8 @@ event_on_connect (struct rs_connection *conn, struct rs_packet *pkt) if (pkt) packet_do_send (pkt); + + return 0; } int diff --git a/lib/event.h b/lib/event.h index e042599..befbd0d 100644 --- a/lib/event.h +++ b/lib/event.h @@ -2,7 +2,7 @@ See the file COPYING for licensing information. */ void event_on_disconnect (struct rs_connection *conn); -void event_on_connect (struct rs_connection *conn, struct rs_packet *pkt); +int event_on_connect (struct rs_connection *conn, struct rs_packet *pkt); int event_loopbreak (struct rs_connection *conn); int event_init_eventbase (struct rs_connection *conn); int event_init_socket (struct rs_connection *conn, struct rs_peer *p); diff --git a/lib/examples/client-blocking.c b/lib/examples/client-blocking.c index 2cfd617..7d3869a 100644 --- a/lib/examples/client-blocking.c +++ b/lib/examples/client-blocking.c @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include #include +#include "err.h" #include "debug.h" /* For rs_dump_packet(). */ #define SECRET "sikrit" @@ -15,7 +17,8 @@ #define USER_PW "password" struct rs_error * -blocking_client (const char *av1, const char *av2, int use_request_object_flag) +blocking_client (const char *config_fn, const char *configuration, + int use_request_object_flag) { struct rs_context *h = NULL; struct rs_connection *conn = NULL; @@ -24,7 +27,11 @@ blocking_client (const char *av1, const char *av2, int use_request_object_flag) struct rs_error *err = NULL; if (rs_context_create (&h)) - return NULL; + { + err = err_create (RSE_INTERNAL, NULL, 0, "unable to create context"); + assert (err != NULL); + return err; + } #if !defined (USE_CONFIG_FILE) { @@ -43,9 +50,9 @@ blocking_client (const char *av1, const char *av2, int use_request_object_flag) goto cleanup; } #else /* defined (USE_CONFIG_FILE) */ - if (rs_context_read_config (h, av1)) + if (rs_context_read_config (h, config_fn)) goto cleanup; - if (rs_conn_create (h, &conn, av2)) + if (rs_conn_create (h, &conn, configuration)) goto cleanup; #endif /* defined (USE_CONFIG_FILE) */ @@ -93,6 +100,13 @@ blocking_client (const char *av1, const char *av2, int use_request_object_flag) return err; } +void +usage (int argc, char *argv[]) +{ + fprintf (stderr, "usage: %s: [-r] config-file config-name\n", argv[0]); + exit (1); +} + int main (int argc, char *argv[]) { @@ -105,6 +119,8 @@ main (int argc, char *argv[]) argc--; argv++; } + if (argc < 3) + usage (argc, argv); err = blocking_client (argv[1], argv[2], use_request_object_flag); if (err) { diff --git a/lib/examples/client.conf b/lib/examples/client.conf index 47528c8..bf57434 100644 --- a/lib/examples/client.conf +++ b/lib/examples/client.conf @@ -16,6 +16,10 @@ realm blocking-tls { cacertfile = "tests/demoCA/newcerts/01.pem" certfile = "tests/demoCA/newcerts/02.pem" certkeyfile = "tests/demoCA/private/c2key.pem" + #pskstr = "sikrit psk" + pskhexstr = "deadbeef4711" + pskid = "Client_identity" + pskex = "PSK" server { hostname = "localhost" service = "2083" diff --git a/lib/include/radsec/radsec-impl.h b/lib/include/radsec/radsec-impl.h index 2274a99..6339e74 100644 --- a/lib/include/radsec/radsec-impl.h +++ b/lib/include/radsec/radsec-impl.h @@ -18,10 +18,19 @@ /* Data types. */ enum rs_cred_type { RS_CRED_NONE = 0, - RS_CRED_TLS_PSK_RSA, /* RFC 4279. */ + /* TLS pre-shared keys, RFC 4279. */ + RS_CRED_TLS_PSK, + /* RS_CRED_TLS_DH_PSK, */ + /* RS_CRED_TLS_RSA_PSK, */ }; typedef unsigned int rs_cred_type_t; +enum rs_key_encoding { + RS_KEY_ENCODING_UTF8 = 1, + RS_KEY_ENCODING_ASCII_HEX = 2, +}; +typedef unsigned int rs_key_encoding_t; + #if defined (__cplusplus) extern "C" { #endif @@ -30,6 +39,8 @@ struct rs_credentials { enum rs_cred_type type; char *identity; char *secret; + enum rs_key_encoding secret_encoding; + unsigned int secret_len; }; struct rs_error { @@ -41,8 +52,10 @@ struct rs_error { struct rs_peer { struct rs_connection *conn; struct rs_realm *realm; - struct evutil_addrinfo *addr; - char *secret; + char *hostname; + char *service; + char *secret; /* RADIUS secret. */ + struct evutil_addrinfo *addr_cache; struct rs_peer *next; }; @@ -56,6 +69,7 @@ struct rs_realm { char *cacertpath; char *certfile; char *certkeyfile; + struct rs_credentials *transport_cred; struct rs_peer *peers; struct rs_realm *next; }; @@ -77,7 +91,6 @@ struct rs_connection { struct rs_realm *realm; /* Owned by ctx. */ struct event_base *evb; /* Event base. */ struct event *tev; /* Timeout event. */ - struct rs_credentials transport_credentials; struct rs_conn_callbacks callbacks; void *user_data; struct rs_peer *peers; @@ -118,12 +131,6 @@ struct rs_packet { struct rs_packet *next; /* Used for UDP output queue. */ }; -/* Nonpublic functions (in radsec.c -- FIXME: move?). */ -struct rs_error *rs_resolv (struct evutil_addrinfo **addr, - rs_conn_type_t type, - const char *hostname, - const char *service); - #if defined (__cplusplus) } #endif diff --git a/lib/include/radsec/radsec.h b/lib/include/radsec/radsec.h index 6e967af..6c4f6a7 100644 --- a/lib/include/radsec/radsec.h +++ b/lib/include/radsec/radsec.h @@ -42,7 +42,7 @@ enum rs_error_code { RSE_TIMEOUT_CONN = 16, /* Connection timeout. */ RSE_INVAL = 17, /* Invalid argument. */ RSE_TIMEOUT_IO = 18, /* I/O timeout. */ - RSE_TIMEOUT= 19, /* High level timeout. */ + RSE_TIMEOUT = 19, /* High level timeout. */ RSE_DISCO = 20, RSE_INUSE = 21, RSE_PACKET_TOO_SMALL = 22, @@ -66,7 +66,9 @@ enum rs_error_code { RSE_INVALID_RESPONSE_SRC = 40, RSE_NO_PACKET_DATA = 41, RSE_VENDOR_UNKNOWN = 42, - RSE_MAX = RSE_VENDOR_UNKNOWN + RSE_CRED = 43, + RSE_CERT = 44, + RSE_MAX = RSE_CERT }; enum rs_conn_type { diff --git a/lib/packet.c b/lib/packet.c index 86a16c3..ce68bea 100644 --- a/lib/packet.c +++ b/lib/packet.c @@ -92,8 +92,8 @@ packet_do_send (struct rs_packet *pkt) { char host[80], serv[80]; - getnameinfo (pkt->conn->active_peer->addr->ai_addr, - pkt->conn->active_peer->addr->ai_addrlen, + getnameinfo (pkt->conn->active_peer->addr_cache->ai_addr, + pkt->conn->active_peer->addr_cache->ai_addrlen, host, sizeof(host), serv, sizeof(serv), 0 /* NI_NUMERICHOST|NI_NUMERICSERV*/); rs_debug (("%s: about to send this to %s:%s:\n", __func__, host, serv)); diff --git a/lib/peer.c b/lib/peer.c index 256ff76..01dc243 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -13,6 +13,7 @@ #include #include "err.h" #include "peer.h" +#include "util.h" struct rs_peer * peer_pick_peer (struct rs_connection *conn) @@ -69,16 +70,17 @@ rs_peer_create (struct rs_connection *conn, struct rs_peer **peer_out) int rs_peer_set_address (struct rs_peer *peer, const char *hostname, - const char *service) + const char *service) { - struct rs_error *err; - assert (peer); - assert (peer->realm); + assert (peer->conn); + assert (peer->conn->ctx); + + peer->hostname = rs_strdup (peer->conn->ctx, hostname); + peer->service = rs_strdup (peer->conn->ctx, service); + if (peer->hostname == NULL || peer->service == NULL) + return RSE_NOMEM; - err = rs_resolv (&peer->addr, peer->realm->type, hostname, service); - if (err) - return err_conn_push_err (peer->conn, err); return RSE_OK; } diff --git a/lib/radsec.c b/lib/radsec.c index 7421755..347a48b 100644 --- a/lib/radsec.c +++ b/lib/radsec.c @@ -49,10 +49,10 @@ rs_context_create (struct rs_context **ctx) } struct rs_error * -rs_resolv (struct evutil_addrinfo **addr, - rs_conn_type_t type, - const char *hostname, - const char *service) +rs_resolve (struct evutil_addrinfo **addr, + rs_conn_type_t type, + const char *hostname, + const char *service) { int err; struct evutil_addrinfo hints, *res = NULL; @@ -102,12 +102,16 @@ rs_context_destroy (struct rs_context *ctx) for (p = r->peers; p; ) { struct rs_peer *tmp = p; - if (p->addr) - evutil_freeaddrinfo (p->addr); + if (p->addr_cache) + { + evutil_freeaddrinfo (p->addr_cache); + p->addr_cache = NULL; + } p = p->next; rs_free (ctx, tmp); } free (r->name); + rs_free (ctx, r->transport_cred); r = r->next; rs_free (ctx, tmp); } diff --git a/lib/radsec.h b/lib/radsec.h new file mode 100644 index 0000000..9e64692 --- /dev/null +++ b/lib/radsec.h @@ -0,0 +1,7 @@ +/* Copyright 2012 NORDUnet A/S. All rights reserved. + See the file COPYING for licensing information. */ + +struct rs_error *rs_resolve (struct evutil_addrinfo **addr, + rs_conn_type_t type, + const char *hostname, + const char *service); diff --git a/lib/rsp_tlscommon.c b/lib/rsp_tlscommon.c index a34fe33..abc395e 100644 --- a/lib/rsp_tlscommon.c +++ b/lib/rsp_tlscommon.c @@ -11,7 +11,6 @@ #endif #include -#if defined(RADPROT_TLS) || defined(RADPROT_DTLS) #include #include #include @@ -271,14 +270,15 @@ static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { } } - if (!tlsaddcacrl(ctx, conf)) { - if (conf->vpm) { - X509_VERIFY_PARAM_free(conf->vpm); - conf->vpm = NULL; - } - SSL_CTX_free(ctx); - return NULL; - } + if (conf->cacertfile != NULL || conf->cacertpath != 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; @@ -352,7 +352,7 @@ X509 *verifytlscert(SSL *ssl) { return cert; } -static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) { +int subjectaltnameaddr(X509 *cert, int family, const struct in6_addr *addr) { int loc, i, l, n, r = 0; char *v; X509_EXTENSION *ex; @@ -388,7 +388,7 @@ static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) { return r; } -static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) { +int subjectaltnameregexp(X509 *cert, int type, const char *exact, const regex_t *regex) { int loc, i, l, n, r = 0; char *s, *v; X509_EXTENSION *ex; @@ -441,7 +441,7 @@ static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *reg return r; } -static int cnregexp(X509 *cert, char *exact, regex_t *regex) { +int cnregexp(X509 *cert, const char *exact, const regex_t *regex) { int loc, l; char *v, *s; X509_NAME *nm; @@ -544,118 +544,6 @@ int verifyconfcert(X509 *cert, struct clsrvconf *conf) { 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 index 6819cd0..d96f553 100644 --- a/lib/rsp_tlscommon.h +++ b/lib/rsp_tlscommon.h @@ -6,6 +6,7 @@ * copyright notice and this permission notice appear in all copies. */ +#include #include #if defined (__cplusplus) @@ -34,9 +35,10 @@ void ssl_init(); struct tls *tlsgettls(char *alt1, char *alt2); SSL_CTX *tlsgetctx(uint8_t type, struct tls *t); X509 *verifytlscert(SSL *ssl); +int subjectaltnameaddr(X509 *cert, int family, const struct in6_addr *addr); +int subjectaltnameregexp(X509 *cert, int type, const char *exact, const regex_t *regex); +int cnregexp(X509 *cert, const char *exact, const regex_t *regex); 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 #if defined (__cplusplus) diff --git a/lib/tcp.c b/lib/tcp.c index 3a59d6f..841f905 100644 --- a/lib/tcp.c +++ b/lib/tcp.c @@ -154,24 +154,32 @@ tcp_event_cb (struct bufferevent *bev, short events, void *user_data) { struct rs_packet *pkt = (struct rs_packet *) user_data; struct rs_connection *conn = NULL; - struct rs_peer *p = NULL; int sockerr = 0; #if defined (RS_ENABLE_TLS) unsigned long tlserr = 0; #endif +#if defined (DEBUG) + struct rs_peer *p = NULL; +#endif assert (pkt); assert (pkt->conn); - assert (pkt->conn->active_peer); conn = pkt->conn; +#if defined (DEBUG) + assert (pkt->conn->active_peer); p = conn->active_peer; +#endif conn->is_connecting = 0; if (events & BEV_EVENT_CONNECTED) { if (conn->tev) evtimer_del (conn->tev); /* Cancel connect timer. */ - event_on_connect (conn, pkt); + if (event_on_connect (conn, pkt)) + { + event_on_disconnect (conn); + event_loopbreak (conn); + } } else if (events & BEV_EVENT_EOF) { @@ -181,7 +189,7 @@ tcp_event_cb (struct bufferevent *bev, short events, void *user_data) { rs_debug (("%s: %p times out on %s\n", __func__, p, (events & BEV_EVENT_READING) ? "read" : "write")); - rs_err_conn_push_fl (pkt->conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); + rs_err_conn_push_fl (conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); } else if (events & BEV_EVENT_ERROR) { @@ -189,13 +197,13 @@ tcp_event_cb (struct bufferevent *bev, short events, void *user_data) if (sockerr == 0) /* FIXME: True that errno == 0 means closed? */ { event_on_disconnect (conn); - rs_err_conn_push_fl (pkt->conn, RSE_DISCO, __FILE__, __LINE__, NULL); + rs_err_conn_push_fl (conn, RSE_DISCO, __FILE__, __LINE__, NULL); } else { rs_debug (("%s: %d: %d (%s)\n", __func__, conn->fd, sockerr, evutil_socket_error_to_string (sockerr))); - rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, + rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, "%d: %d (%s)", conn->fd, sockerr, evutil_socket_error_to_string (sockerr)); } @@ -208,7 +216,7 @@ tcp_event_cb (struct bufferevent *bev, short events, void *user_data) { rs_debug (("%s: openssl error: %s\n", __func__, ERR_error_string (tlserr, NULL))); - rs_err_conn_push_fl (pkt->conn, RSE_SSLERR, __FILE__, __LINE__, + rs_err_conn_push_fl (conn, RSE_SSLERR, __FILE__, __LINE__, ERR_error_string (tlserr, NULL)); } } diff --git a/lib/tests/Makefile.am b/lib/tests/Makefile.am index 526dcdc..7c5408b 100644 --- a/lib/tests/Makefile.am +++ b/lib/tests/Makefile.am @@ -2,7 +2,9 @@ AUTOMAKE_OPTIONS = foreign INCLUDES = -I$(top_srcdir)/include AM_CFLAGS = -Wall -g -bin_PROGRAMS = test-udp udp-server +TESTS = test-udp + +check_PROGRAMS = test-udp udp-server test_udp_SOURCES = test-udp.c udp.c test_udp_LDADD = ../libradsec.la -lcgreen -lm diff --git a/lib/tests/README b/lib/tests/README index 8280b9e..4d68bde 100644 --- a/lib/tests/README +++ b/lib/tests/README @@ -1,24 +1,34 @@ Build ----- -In order to build the tests, you'll need libcgreen -(http://www.lastcraft.com/cgreen.php). +In order to build and run the tests, you'll need to have libcgreen +installed (http://www.lastcraft.com/cgreen.php). Run --- -NOTE: To run the tests you need -- a RADIUS server running at localhost:1820 with shared sekret - "sikrit" configured (or whatever "test-udp-auth" in test.conf says) -- a user "molgan" with password "password" present in the RADIUS - database -This requirement will disappear in the future. +NOTE: To run the tests you currently need +- a RADIUS server running at localhost:1820 with the shared RADIUS + secret "sikrit" configured (or whatever "test-udp-auth" in test.conf + says) +- a user "molgan@PROJECT-MOONSHOT.ORG" with password "password" + present in the RADIUS database +These requirements will be removed in a future libradsec release. + Run the tests by typing - ./test-udp + make check -The output should be something like +The output should read something like Completed "main": 32 passes, 0 failures, 0 exceptions. + + +When trying to debug the test programs under GDB you might run into +trouble with multiple threads being executed by the test framework. +If so, make sure to run a single test rather than the full test suite. +For example: + + libtool --mode execute gdb --args test-udp test_auth diff --git a/lib/tests/test-udp.c b/lib/tests/test-udp.c index f66eebd..ccad607 100644 --- a/lib/tests/test-udp.c +++ b/lib/tests/test-udp.c @@ -34,11 +34,12 @@ send_more_than_one_msg_in_one_packet (struct rs_connection *conn) assert_true (rs_packet_send (msg1, NULL) == 0); } +#if 0 static void send_large_packet (struct rs_connection *conn) { struct rs_packet *msg0; - struct rs_attr *attr_x; + struct radius_packet *frpkt = NULL; char *buf; int f; @@ -51,11 +52,12 @@ send_large_packet (struct rs_connection *conn) for (f = 0; f < 15; f++) { memset (buf, 'a' + f, 252); - rs_attr_create (conn, &attr_x, "EAP-Message", buf); - rs_packet_add_attr (msg0, attr_x); + //vp = pairmake ("EAP-Message", buf, T_OP_EQ); + assert_true (rs_packet_append_avp (msg0, fixme...) == RSE_OK); } assert_true (rs_packet_send (msg0, NULL) == 0); } +#endif /* 0 */ /* ************************************************************ */ static struct setup { @@ -73,11 +75,12 @@ test_auth () setup.config_file = "test.conf"; setup.config_name = "test-udp-auth"; - setup.username = "molgan"; + setup.username = "molgan@PROJECT-MOONSHOT.ORG"; setup.pw = "password"; - assert_true (rs_context_create (&ctx, NULL) == 0); + assert_true (rs_context_create (&ctx) == 0); assert_true (rs_context_read_config (ctx, setup.config_file) == 0); + assert_true (rs_context_init_freeradius_dict (ctx, NULL) == 0); assert_true (rs_conn_create (ctx, &conn, setup.config_name) == 0); authenticate (conn, setup.username, setup.pw); @@ -108,7 +111,7 @@ test_buffering () struct timeval timeout; struct polldata *polldata; - assert_true (rs_context_create (&ctx, NULL) == 0); + assert_true (rs_context_create (&ctx) == 0); assert_true (rs_context_read_config (ctx, "test.conf") == 0); assert_true (rs_conn_create (ctx, &conn, "test-udp-buffering") == 0); @@ -121,8 +124,21 @@ test_buffering () assert_true (udp_poll (polldata) > 0); assert_true (udp_poll (polldata) > 0); +#if 0 +" +send_large_packet() disabled, it's hanging after + +Sending Access-Request of id 1 to (null) port 0 + Message-Authenticator = 0x00000000000000000000000000000000 +packet_do_send: about to send this to localhost:11820: + Code: 1, Identifier: 1, Lenght: 38 +rs_packet_send: entering event loop +_evcb: fd=5 what = WRITE +rs_packet_send: event loop done +" send_large_packet (conn); assert_true (udp_poll (polldata) > 0); +#endif /* 0 */ udp_free_polldata (polldata); rs_conn_destroy (conn); diff --git a/lib/tests/test.conf b/lib/tests/test.conf index 9be968d..839fd75 100644 --- a/lib/tests/test.conf +++ b/lib/tests/test.conf @@ -1,4 +1,6 @@ -config test-udp-auth { +dictionary = "/home/linus/usr/moonshot/share/freeradius/dictionary" + +realm test-udp-auth { type = "UDP" server { hostname = "localhost" @@ -7,7 +9,7 @@ config test-udp-auth { } } -config test-udp-buffering { +realm test-udp-buffering { type = "UDP" server { hostname = "localhost" @@ -16,7 +18,7 @@ config test-udp-buffering { } } -config test-tls-test { +realm test-tls-test { type = "TLS" cacertfile = "/home/linus/nordberg-ca.crt" certfile = "/home/linus/p/radsecproxy/src/maatuska.nordberg.se.crt" diff --git a/lib/tls.c b/lib/tls.c index 6fcf5a0..610df98 100644 --- a/lib/tls.c +++ b/lib/tls.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -41,6 +43,74 @@ _get_tlsconf (struct rs_connection *conn, const struct rs_realm *realm) return c; } +#if defined RS_ENABLE_TLS_PSK +static unsigned int +psk_client_cb (SSL *ssl, + const char *hint, + char *identity, + unsigned int max_identity_len, + unsigned char *psk, + unsigned int max_psk_len) +{ + struct rs_connection *conn = NULL; + struct rs_credentials *cred = NULL; + + conn = SSL_get_ex_data (ssl, 0); + assert (conn != NULL); + cred = conn->active_peer->realm->transport_cred; + assert (cred != NULL); + /* NOTE: Ignoring identity hint from server. */ + + if (strlen (cred->identity) + 1 > max_identity_len) + { + rs_err_conn_push (conn, RSE_CRED, "PSK identity longer than max %d", + max_identity_len - 1); + return 0; + } + strcpy (identity, cred->identity); + + switch (cred->secret_encoding) + { + case RS_KEY_ENCODING_UTF8: + cred->secret_len = strlen (cred->secret); + if (cred->secret_len > max_psk_len) + { + rs_err_conn_push (conn, RSE_CRED, "PSK secret longer than max %d", + max_psk_len); + return 0; + } + memcpy (psk, cred->secret, cred->secret_len); + break; + case RS_KEY_ENCODING_ASCII_HEX: + { + BIGNUM *bn = NULL; + + if (BN_hex2bn (&bn, cred->secret) == 0) + { + rs_err_conn_push (conn, RSE_CRED, "Unable to convert pskhexstr"); + if (bn != NULL) + BN_clear_free (bn); + return 0; + } + if ((unsigned int) BN_num_bytes (bn) > max_psk_len) + { + rs_err_conn_push (conn, RSE_CRED, "PSK secret longer than max %d", + max_psk_len); + BN_clear_free (bn); + return 0; + } + cred->secret_len = BN_bn2bin (bn, psk); + BN_clear_free (bn); + } + break; + default: + assert (!"unknown psk encoding"); + } + + return cred->secret_len; +} +#endif /* RS_ENABLE_TLS_PSK */ + int rs_tls_init (struct rs_connection *conn) { @@ -56,7 +126,7 @@ rs_tls_init (struct rs_connection *conn) tlsconf = _get_tlsconf (conn, conn->active_peer->realm); if (!tlsconf) return -1; - ssl_ctx = tlsgetctx (RADPROT_TLS, tlsconf); + ssl_ctx = tlsgetctx (RAD_TLS, tlsconf); if (!ssl_ctx) { for (sslerr = ERR_get_error (); sslerr; sslerr = ERR_get_error ()) @@ -73,8 +143,93 @@ rs_tls_init (struct rs_connection *conn) return -1; } +#if defined RS_ENABLE_TLS_PSK + if (conn->active_peer->realm->transport_cred != NULL) + { + SSL_set_psk_client_callback (ssl, psk_client_cb); + SSL_set_ex_data (ssl, 0, conn); + } +#endif /* RS_ENABLE_TLS_PSK */ + conn->tls_ctx = ssl_ctx; conn->tls_ssl = ssl; rs_free (ctx, tlsconf); return RSE_OK; } + +/* draft-ietf-radext-radsec-11.txt + + * Certificate validation MUST include the verification rules as + per [RFC5280]. + + * Implementations SHOULD indicate their acceptable Certification + Authorities as per section 7.4.4 (server side) and x.y.z + ["Trusted CA Indication"] (client side) of [RFC5246] (see + Section 3.2) + + * Implementations SHOULD allow to configure a list of acceptable + certificates, identified via certificate fingerprint. When a + fingerprint configured, the fingerprint is prepended with an + ASCII label identifying the hash function followed by a colon. + Implementations MUST support SHA-1 as the hash algorithm and + use the ASCII label "sha-1" to identify the SHA-1 algorithm. + The length of a SHA-1 hash is 20 bytes and the length of the + corresponding fingerprint string is 65 characters. An example + certificate fingerprint is: sha- + 1:E1:2D:53:2B:7C:6B:8A:29:A2:76:C8:64:36:0B:08:4B:7A:F1:9E:9D + + * Peer validation always includes a check on whether the locally + configured expected DNS name or IP address of the server that + is contacted matches its presented certificate. DNS names and + IP addresses can be contained in the Common Name (CN) or + subjectAltName entries. For verification, only one of these + entries is to be considered. The following precedence + applies: for DNS name validation, subjectAltName:DNS has + precedence over CN; for IP address validation, subjectAltName: + iPAddr has precedence over CN. + + * Implementations SHOULD allow to configure a set of acceptable + values for subjectAltName:URI. + */ +int +tls_verify_cert (struct rs_connection *conn) +{ + int err = 0; + int success = 0; + X509 *peer_cert = NULL; + struct in6_addr addr; + const char *hostname = NULL; + + assert (conn->active_peer->conn == conn); + assert (conn->active_peer->hostname != NULL); + hostname = conn->active_peer->hostname; + + /* verifytlscert() performs basic verification as described by + OpenSSL VERIFY(1), i.e. verification of the certificate chain. */ + peer_cert = verifytlscert (conn->tls_ssl); + if (peer_cert == NULL) + { + err = rs_err_conn_push (conn, RSE_SSLERR, + "basic certificate validation failed"); + goto out; + } + + if (inet_pton(AF_INET, hostname, &addr)) + success = (subjectaltnameaddr (peer_cert, AF_INET, &addr) == 1); + else if (inet_pton(AF_INET6, hostname, &addr)) + success = (subjectaltnameaddr (peer_cert, AF_INET6, &addr) == 1); + else + success = (subjectaltnameregexp (peer_cert, GEN_DNS, hostname, NULL) == 1); + + if (!success) + success = (cnregexp(peer_cert, hostname, NULL) == 1); + + if (!success) + err = rs_err_conn_push (conn, RSE_CERT, "server certificate doesn't " + "match configured hostname \"%s\"", hostname); + + out: + if (peer_cert != NULL) + X509_free (peer_cert); + return err; +} diff --git a/lib/tls.h b/lib/tls.h index d457cfd..0dc2ebd 100644 --- a/lib/tls.h +++ b/lib/tls.h @@ -6,6 +6,7 @@ extern "C" { #endif int rs_tls_init (struct rs_connection *conn); +int tls_verify_cert (struct rs_connection *conn); #if defined (__cplusplus) } diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..3c9fef6 --- /dev/null +++ b/lib/util.c @@ -0,0 +1,19 @@ +/* Copyright 2012 NORDUnet A/S. All rights reserved. + See the file COPYING for licensing information. */ + +#include +#include +#include +#include "util.h" + +char * +rs_strdup (struct rs_context *ctx, const char *s) +{ + char *buf = rs_calloc (ctx, 1, strlen (s) + 1); + + if (buf != NULL) + return strcpy (buf, s); + + rs_err_ctx_push (ctx, RSE_NOMEM, NULL); + return NULL; +} diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..90c55d8 --- /dev/null +++ b/lib/util.h @@ -0,0 +1,4 @@ +/* Copyright 2012 NORDUnet A/S. All rights reserved. + See the file COPYING for licensing information. */ + +char *rs_strdup (struct rs_context *ctx, const char *s);