diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 10:41:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 10:41:52 +0000 |
commit | 44eafeee62e6982131c62df6f74335114ca53024 (patch) | |
tree | 1cdf833b0a76e52630d717202398ced5900e11e9 /src/main | |
parent | Adding upstream version 3.2.3+dfsg. (diff) | |
download | freeradius-upstream.tar.xz freeradius-upstream.zip |
Adding upstream version 3.2.5+dfsg.upstream/3.2.5+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/main/all.mk | 2 | ||||
-rw-r--r-- | src/main/auth.c | 10 | ||||
-rw-r--r-- | src/main/cb.c | 19 | ||||
-rw-r--r-- | src/main/client.c | 44 | ||||
-rw-r--r-- | src/main/command.c | 2 | ||||
-rw-r--r-- | src/main/conffile.c | 62 | ||||
-rw-r--r-- | src/main/detail.c | 10 | ||||
-rw-r--r-- | src/main/exfile.c | 15 | ||||
-rw-r--r-- | src/main/listen.c | 441 | ||||
-rw-r--r-- | src/main/mainconfig.c | 104 | ||||
-rw-r--r-- | src/main/map.c | 2 | ||||
-rw-r--r-- | src/main/modcall.c | 2 | ||||
-rw-r--r-- | src/main/modules.c | 6 | ||||
-rw-r--r-- | src/main/process.c | 189 | ||||
-rw-r--r-- | src/main/radclient.c | 205 | ||||
-rwxr-xr-x | src/main/radsecret | 7 | ||||
-rw-r--r-- | src/main/radsecret.mk | 5 | ||||
-rw-r--r-- | src/main/radsniff.c | 39 | ||||
-rw-r--r-- | src/main/realms.c | 25 | ||||
-rw-r--r-- | src/main/state.c | 113 | ||||
-rw-r--r-- | src/main/stats.c | 95 | ||||
-rw-r--r-- | src/main/threads.c | 2 | ||||
-rw-r--r-- | src/main/tls.c | 137 | ||||
-rw-r--r-- | src/main/tls_listen.c | 15 | ||||
-rw-r--r-- | src/main/tmpl.c | 11 | ||||
-rw-r--r-- | src/main/unittest.c | 2 | ||||
-rw-r--r-- | src/main/util.c | 6 | ||||
-rw-r--r-- | src/main/version.c | 2 |
28 files changed, 1259 insertions, 313 deletions
diff --git a/src/main/all.mk b/src/main/all.mk index 2517cd2..f3db386 100644 --- a/src/main/all.mk +++ b/src/main/all.mk @@ -1,3 +1,3 @@ SUBMAKEFILES := radclient.mk radiusd.mk radsniff.mk radmin.mk radattr.mk \ - radwho.mk radlast.mk radtest.mk radzap.mk checkrad.mk \ + radwho.mk radlast.mk radtest.mk radzap.mk checkrad.mk radsecret.mk \ libfreeradius-server.mk unittest.mk diff --git a/src/main/auth.c b/src/main/auth.c index 84889b8..2dc3e60 100644 --- a/src/main/auth.c +++ b/src/main/auth.c @@ -850,8 +850,8 @@ int rad_virtual_server(REQUEST *request) break; case PW_AUTH_TYPE_REJECT: - request->reply->code = PW_CODE_ACCESS_REJECT; - break; + request->reply->code = PW_CODE_ACCESS_REJECT; + break; default: break; @@ -864,6 +864,12 @@ int rad_virtual_server(REQUEST *request) if (vp) rad_postauth(request); } + if (request->reply->code == PW_CODE_ACCESS_CHALLENGE) { + fr_pair_delete_by_num(&request->config, PW_POST_AUTH_TYPE, 0, TAG_ANY); + vp = pair_make_config("Post-Auth-Type", "Challenge", T_OP_SET); + if (vp) rad_postauth(request); + } + if (request->reply->code == PW_CODE_ACCESS_ACCEPT) { /* * Check that there is a name which can be used diff --git a/src/main/cb.c b/src/main/cb.c index db764aa..65e484f 100644 --- a/src/main/cb.c +++ b/src/main/cb.c @@ -31,6 +31,7 @@ void cbtls_info(SSL const *s, int where, int ret) { char const *role, *state; REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); + fr_tls_server_conf_t *conf = (fr_tls_server_conf_t *) SSL_get_ex_data(s, FR_TLS_EX_INDEX_CONF); if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { role = "Client "; @@ -58,7 +59,7 @@ void cbtls_info(SSL const *s, int where, int ret) len = strlen(abbrv); if ((len > 1) && (abbrv[len - 1] == ' ')) len--; - RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", + RDEBUG3("(TLS) %s - Handshake state [%.*s] - %s%s (%d)", conf->name, (int)len, abbrv, role, state, SSL_get_state(s)); /* @@ -82,7 +83,7 @@ void cbtls_info(SSL const *s, int where, int ret) client_ciphers = SSL_get_client_ciphers(s); if (client_ciphers) { - RDEBUG3("Client preferred ciphers (by priority)"); + RDEBUG3("(TLS) %s - Client preferred ciphers (by priority)", conf->name); num_ciphers = sk_SSL_CIPHER_num(client_ciphers); for (i = 0; i < num_ciphers; i++) { this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); @@ -92,7 +93,7 @@ void cbtls_info(SSL const *s, int where, int ret) } #endif } else { - RDEBUG2("(TLS) Handshake state - %s%s", role, state); + RDEBUG2("(TLS) %s - Handshake state - %s%s", conf->name, role, state); } return; } @@ -100,23 +101,27 @@ void cbtls_info(SSL const *s, int where, int ret) if (where & SSL_CB_ALERT) { if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; - RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", + RERROR("(TLS) %s - Alert %s:%s:%s", conf->name, (where & SSL_CB_READ) ? "read": "write", SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); return; } if (where & SSL_CB_EXIT) { if (ret == 0) { - RERROR("(TLS) %s: Failed in %s", role, state); + RERROR("(TLS) %s - %s: Failed in %s", conf->name, role, state); return; } if (ret < 0) { if (SSL_want_read(s)) { - RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); + RDEBUG2("(TLS) %s - %s: Need to read more data: %s", conf->name, role, state); return; } - RERROR("(TLS) %s: Error in %s", role, state); + if (SSL_want_write(s)) { + RDEBUG2("(TLS) %s - %s: Need to write more data: %s", conf->name, role, state); + return; + } + RERROR("(TLS) %s - %s: Error in %s", conf->name, role, state); } } } diff --git a/src/main/client.c b/src/main/client.c index 12f7824..58f9faa 100644 --- a/src/main/client.c +++ b/src/main/client.c @@ -328,7 +328,8 @@ check_list: (old->coa_home_server == client->coa_home_server) && (old->coa_home_pool == client->coa_home_pool) && #endif - (old->message_authenticator == client->message_authenticator)) { + (old->require_ma == client->require_ma) && + (old->limit_proxy_state == client->limit_proxy_state)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; @@ -490,6 +491,8 @@ static fr_ipaddr_t cl_ipaddr; static uint32_t cl_netmask; static char const *cl_srcipaddr = NULL; static char const *hs_proto = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; #ifdef WITH_TCP static CONF_PARSER limit_config[] = { @@ -512,7 +515,8 @@ static const CONF_PARSER client_config[] = { { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL }, - { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL }, { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL }, { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL }, @@ -724,7 +728,7 @@ static const CONF_PARSER dynamic_config[] = { { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL }, { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL }, - { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL }, + { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL }, { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" }, { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" }, @@ -906,8 +910,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo c = talloc_zero(ctx, RADCLIENT); c->cs = cs; + /* + * Set the "require message authenticator" and "limit + * proxy state" flags from the global default. If the + * configuration item exists, AND is set, it will + * over-ride the flag. + */ + c->require_ma = main_config.require_ma; + c->limit_proxy_state = main_config.limit_proxy_state; + memset(&cl_ipaddr, 0, sizeof(cl_ipaddr)); cl_netmask = 255; + require_message_authenticator = NULL; + limit_proxy_state = NULL; if (cf_section_parse(cs, c, client_config) < 0) { cf_log_err_cs(cs, "Error parsing client section"); @@ -917,6 +932,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo hs_proto = NULL; cl_srcipaddr = NULL; #endif + require_message_authenticator = NULL; + limit_proxy_state = NULL; return NULL; } @@ -1189,6 +1206,16 @@ done_coa: } #endif + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) { + goto error; + } + + if (c->require_ma != FR_BOOL_TRUE) { + if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) { + goto error; + } + } + return c; } @@ -1233,7 +1260,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons if (shortname) c->shortname = talloc_typed_strdup(c, shortname); if (type) c->nas_type = talloc_typed_strdup(c, type); if (server) c->server = talloc_typed_strdup(c, server); - c->message_authenticator = require_ma; + c->require_ma = require_ma; return c; } @@ -1419,10 +1446,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) *pi = vp->vp_integer; /* - * Same nastiness as above. + * Same nastiness as above, but hard-coded for require Message-Authenticator. */ for (parse = client_config; parse->name; parse++) { - if (parse->offset == dynamic_config[i].offset) break; + if (parse->type == PW_TYPE_BOOLEAN) break; } if (!parse) break; @@ -1513,6 +1540,11 @@ validate: goto error; } + /* + * It can't be set to "auto". Too bad. + */ + c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma; + if (!client_add_dynamic(clients, request->client, c)) { return NULL; } diff --git a/src/main/command.c b/src/main/command.c index 988f43b..266366b 100644 --- a/src/main/command.c +++ b/src/main/command.c @@ -2701,7 +2701,7 @@ static int command_stats_socket(rad_listen_t *listener, int argc, char *argv[]) return command_print_stats(listener, &sock->stats, auth, 0); } -static int command_stats_pool(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) +static int command_stats_pool(rad_listen_t *listener, int argc, char *argv[]) { CONF_SECTION *cs; module_instance_t *mi; diff --git a/src/main/conffile.c b/src/main/conffile.c index 7bb206c..ad5a5fe 100644 --- a/src/main/conffile.c +++ b/src/main/conffile.c @@ -1424,12 +1424,13 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d { int rcode; bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists; + bool ignore_dflt; char **q; char const *value; CONF_PAIR *cp = NULL; fr_ipaddr_t *ipaddr; - char buffer[8192]; CONF_ITEM *c_item; + char buffer[8192]; if (!cs) { cf_log_err(&(cs->item), "No enclosing section for configuration item \"%s\"", name); @@ -1447,6 +1448,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d cant_be_empty = (type & PW_TYPE_NOT_EMPTY); tmpl = (type & PW_TYPE_TMPL); multi = (type & PW_TYPE_MULTI); + ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT); if (attribute) required = true; if (required) cant_be_empty = true; /* May want to review this in the future... */ @@ -1470,7 +1472,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d * section, use the default value. */ if (!cp) { - if (deprecated) return 0; /* Don't set the default value */ + if (deprecated || ignore_dflt) return 0; /* Don't set the default value */ rcode = 1; value = dflt; @@ -1658,6 +1660,62 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d cf_log_err(&(cs->item),"Failed expanding variable %s", name); return -1; } + + } else if (cf_new_escape && (cp->rhs_type == T_DOUBLE_QUOTED_STRING) && (strchr(value, '\\') != NULL)) { + char const *p = value; + char *s = buffer; + char *end = buffer + sizeof(buffer); + unsigned int x; + + /* + * We pass !cf_new_escape() to gettoken() when we parse the RHS of a CONF_PAIR + * above. But gettoken() unescapes the \", and doesn't unescape anything else. + * So we do it here. + */ + while (*p && (s < end)) { + if (*p != '\\') { + *(s++) = *(p++); + continue; + } + + p++; + + switch (*p) { + case 'r': + *s++ = '\r'; + break; + case 'n': + *s++ = '\n'; + break; + case 't': + *s++ = '\t'; + break; + + default: + if (*p >= '0' && *p <= '9' && + sscanf(p, "%3o", &x) == 1) { + if (!x) { + cf_log_err(&(cs->item), "Cannot have embedded zeros in value for %s", name); + return -1; + } + + *s++ = x; + p += 2; + } else + *s++ = *p; + break; + } + p++; + } + + if (s == end) { + cf_log_err(&(cs->item), "Failed expanding value for %s", name); + return -1; + } + + *s = '\0'; + + value = buffer; } if (cant_be_empty && (value[0] == '\0')) goto cant_be_empty; diff --git a/src/main/detail.c b/src/main/detail.c index a5e8437..3b7e382 100644 --- a/src/main/detail.c +++ b/src/main/detail.c @@ -910,8 +910,16 @@ open_file: rad_assert(vp != NULL); fr_pair_add(&packet->vps, vp); } + + /* + * Update Acct-Delay-Time, but make sure that it doesn't go backwards. + */ if (data->timestamp != 0) { - vp->vp_integer += time(NULL) - data->timestamp; + time_t now = time(NULL); + + if (((time_t) data->timestamp) < now) { + vp->vp_integer += time(NULL) - data->timestamp; + } } } diff --git a/src/main/exfile.c b/src/main/exfile.c index 59e6a05..1b498ce 100644 --- a/src/main/exfile.c +++ b/src/main/exfile.c @@ -170,7 +170,20 @@ static int exfile_open_mkdir(exfile_t *ef, char const *filename, mode_t permissi oflag = O_RDWR; } - fd = open(filename, oflag, permissions); + /* + * Just dup stdout / stderr if it's possible. + */ + if ((default_log.dst == L_DST_STDOUT) && + (strcmp(filename, "/dev/stdout") == 0)) { + fd = dup(STDOUT_FILENO); + + } else if ((default_log.dst == L_DST_STDERR) && + (strcmp(filename, "/dev/stderr") == 0)) { + fd = dup(STDERR_FILENO); + } else { + fd = open(filename, oflag, permissions); + } + if (fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); diff --git a/src/main/listen.c b/src/main/listen.c index ee73a57..a6ff080 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -55,7 +55,7 @@ RCSID("$Id$") #ifdef WITH_TLS #include <netinet/tcp.h> -# ifdef __APPLE__ +# if defined(__APPLE__) || defined(__FreeBSD__) || defined(__illumos__) || defined(__sun__) # if !defined(SOL_TCP) && defined(IPPROTO_TCP) # define SOL_TCP IPPROTO_TCP # endif @@ -385,6 +385,7 @@ int rad_status_server(REQUEST *request) if (sock->state == LISTEN_TLS_CHECKING) { int autz_type = PW_AUTZ_TYPE; char const *name = "Autz-Type"; + rad_listen_t *listener = request->listener; if (request->listener->type == RAD_LISTEN_ACCT) { autz_type = PW_ACCT_TYPE; @@ -404,11 +405,22 @@ int rad_status_server(REQUEST *request) if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { RDEBUG("(TLS) Connection is authorized"); request->reply->code = PW_CODE_ACCESS_ACCEPT; + + listener->status = RAD_LISTEN_STATUS_RESUME; + + rad_assert(sock->request->packet != request->packet); + + sock->state = LISTEN_TLS_SETUP; + } else { RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); request->reply->code = PW_CODE_ACCESS_REJECT; + + listener->status = RAD_LISTEN_STATUS_EOL; + listener->tls = NULL; /* parent owns this! */ } + radius_update_listener(listener); return 0; } } @@ -518,6 +530,123 @@ int rad_status_server(REQUEST *request) return 0; } +static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client) +{ + if (client->require_ma == FR_BOOL_TRUE) return; + + if (client->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet without Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + client->require_ma = FR_BOOL_FALSE; + + /* + * And fall through to the + * limit_proxy_state checks, which might + * complain again. Oh well, maybe that + * will make people read the messages. + */ + + } else if (packet->eap_message) { + /* + * Don't set it to "true" for packets + * with EAP-Message. It's already + * required there, and we might get a + * non-EAP packet with (or without) + * Message-Authenticator + */ + return; + } else { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->require_ma = FR_BOOL_TRUE; + return; + } + + } + + /* + * If all of the checks are turned off, then complain for every packet we receive. + */ + if (client->limit_proxy_state == FR_BOOL_FALSE) { + /* + * We have a Message-Authenticator, and it's valid. We don't need to compain. + */ + if (packet->message_authenticator) return; + + if (!fr_debug_lvl) return; /* easier than checking for each line below */ + + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("BlastRADIUS check: Received packet without Message-Authenticator."); + DEBUG("YOU MUST SET \"require_message_authenticator = true\", or"); + DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return; + } + + /* + * Don't complain here. rad_packet_ok() will instead + * complain about every packet with Proxy-State but which + * is missing Message-Authenticator. + */ + if (client->limit_proxy_state == FR_BOOL_TRUE) { + return; + } + + if (packet->proxy_state && !packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator."); + ERROR("This is either a BlastRADIUS attack, OR"); + ERROR("the client is a proxy RADIUS server which has not been upgraded."); + ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->limit_proxy_state = FR_BOOL_FALSE; + + } else { + client->limit_proxy_state = FR_BOOL_TRUE; + + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (!packet->proxy_state) { + ERROR("BlastRADIUS check: Received packet without Proxy-State."); + } else { + ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator."); + } + + ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + if (!packet->message_authenticator) { + ERROR("The packet does not contain Message-Authenticator, which is a security issue."); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + } else { + ERROR("The packet contains Message-Authenticator."); + if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + } + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } +} + #ifdef WITH_TCP static int dual_tcp_recv(rad_listen_t *listener) { @@ -594,6 +723,21 @@ static int dual_tcp_recv(rad_listen_t *listener) switch (packet->code) { case PW_CODE_ACCESS_REQUEST: if (listener->type != RAD_LISTEN_AUTH) goto bad_packet; + + /* + * Enforce BlastRADIUS checks on TCP, too. + */ + if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) { + FR_STATS_INC(auth, total_malformed_requests); + rad_free(&sock->packet); + return 0; + } + + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + FR_STATS_INC(auth, total_requests); fun = rad_authenticate; break; @@ -720,7 +864,12 @@ static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) #endif #ifdef WITH_RADIUSV11 -static const unsigned char radiusv11_alpn_protos[] = { +static const unsigned char radiusv11_allow_protos[] = { + 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1', /* prefer this */ + 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '0', +}; + +static const unsigned char radiusv11_require_protos[] = { 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1', }; @@ -750,23 +899,28 @@ static int radiusv11_server_alpn_cb(SSL *ssl, memcpy(&hack, &out, sizeof(out)); /* const issues */ /* - * The RADIUSv11 configuration for this socket is a combination of what we require, and what we + * The RADIUS/1.1 configuration for this socket is a combination of what we require, and what we * require of the client. */ switch (this->radiusv11) { /* - * If we forbid RADIUSv11, then we never advertised it via ALPN, and this callback should + * If we forbid RADIUS/1.1, then we never advertised it via ALPN, and this callback should * never have been registered. */ case FR_RADIUSV11_FORBID: - *out = NULL; - *outlen = 0; - return SSL_TLSEXT_ERR_OK; + fr_assert(0); + server = radiusv11_allow_protos + 11; + server_len = 11; + break; case FR_RADIUSV11_ALLOW: + server = radiusv11_allow_protos; + server_len = sizeof(radiusv11_allow_protos); + break; + case FR_RADIUSV11_REQUIRE: - server = radiusv11_alpn_protos; - server_len = sizeof(radiusv11_alpn_protos); + server = radiusv11_require_protos; + server_len = sizeof(radiusv11_require_protos); break; } @@ -786,6 +940,7 @@ static int radiusv11_server_alpn_cb(SSL *ssl, */ fr_assert(*outlen == 10); sock->radiusv11 = (server[9] == '1'); + sock->alpn_checked = true; RDEBUG("(TLS) ALPN server negotiated application protocol \"%.*s\"", (int) *outlen, server); return SSL_TLSEXT_ERR_OK; @@ -798,6 +953,26 @@ static int radiusv11_server_alpn_cb(SSL *ssl, return SSL_TLSEXT_ERR_ALERT_FATAL; } +static int radiusv11_client_hello_cb(UNUSED SSL *s, int *alert, void *arg) +{ + rad_listen_t *this = arg; + listen_socket_t *sock = this->data; + + /* + * The server_alpn_cb ran, and checked that the configured ALPN matches the negotiated one. + */ + if (sock->alpn_checked) return SSL_CLIENT_HELLO_SUCCESS; + + /* + * The server_alpn_cb did NOT run (???) but we still have a client hello. We require ALPN and + * none was negotiated, so we return an error. + */ + *alert = SSL_AD_NO_APPLICATION_PROTOCOL; + + return SSL_CLIENT_HELLO_ERROR; +} + + int fr_radiusv11_client_init(fr_tls_server_conf_t *tls); int fr_radiusv11_client_get_alpn(rad_listen_t *listener); @@ -805,13 +980,17 @@ int fr_radiusv11_client_init(fr_tls_server_conf_t *tls) { switch (tls->radiusv11) { case FR_RADIUSV11_ALLOW: - case FR_RADIUSV11_REQUIRE: - if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_alpn_protos, sizeof(radiusv11_alpn_protos)) != 0) { - ERROR("Failed setting RADIUSv11 negotiation flags"); + if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_allow_protos, sizeof(radiusv11_allow_protos)) != 0) { + fail_protos: + ERROR("Failed setting RADIUS/1.1 negotiation flags"); return -1; } break; + case FR_RADIUSV11_REQUIRE: + if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_require_protos, sizeof(radiusv11_require_protos)) != 0) goto fail_protos; + break; + default: break; } @@ -827,41 +1006,53 @@ int fr_radiusv11_client_get_alpn(rad_listen_t *listener) SSL_get0_alpn_selected(sock->ssn->ssl, &data, &len); if (!data) { - DEBUG("(TLS) ALPN home server did not send any application protocol"); + DEBUG("(TLS) ALPN server did not send any application protocol"); if (listener->radiusv11 == FR_RADIUSV11_REQUIRE) { DEBUG("(TLS) We have 'radiusv11 = require', but the home server has not negotiated it - closing socket"); return -1; } - DEBUG("(TLS) ALPN assuming historical RADIUS"); - return 0; + DEBUG("(TLS) ALPN assuming \"radius/1.0\""); + return 0; /* allow radius/1.0 */ } - DEBUG("(TLS) ALPN home server sent application protocol \"%.*s\"", (int) len, data); + DEBUG("(TLS) ALPN server sent application protocol \"%.*s\"", (int) len, data); if (len != 10) { radiusv11_unknown: - DEBUG("(TLS) ALPN home server sent unknown application protocol - closing connection"); + DEBUG("(TLS) ALPN server sent unknown application protocol - closing connection to home server"); return -1; } /* - * Should always be "radius/1.1". The server MUST echo back one of the strings + * Should always be "radius/1.0" or "radius/1.1". The server MUST echo back one of the strings * we sent. If it doesn't, it's a bad server. */ - if (memcmp(data, "radius/1.1", 10) != 0) goto radiusv11_unknown; + if (memcmp(data, "radius/1.", 9) != 0) goto radiusv11_unknown; + + if ((data[9] != '0') && (data[9] != '1')) goto radiusv11_unknown; /* * Double-check what the server sent us. It SHOULD be sane, but it never hurts to check. */ switch (listener->radiusv11) { case FR_RADIUSV11_FORBID: - DEBUG("(TLS) ALPN home server sent \"radius/v1.1\" but we forbid it - closing connection to home server"); - return -1; + if (data[9] != '0') { + DEBUG("(TLS) ALPN server did not send \"radius/v1.0\" - closing connection to home server"); + return -1; + } + break; case FR_RADIUSV11_ALLOW: + sock->radiusv11 = (data[9] == '1'); + break; + case FR_RADIUSV11_REQUIRE: - DEBUG("(TLS) ALPN using \"radius/1.1\""); + if (data[9] != '1') { + DEBUG("(TLS) ALPN server did not send \"radius/v1.1\" - closing connection to home server"); + return -1; + } + sock->radiusv11 = true; break; } @@ -1066,15 +1257,30 @@ static int dual_tcp_accept(rad_listen_t *listener) SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); #ifdef WITH_RADIUSV11 - /* - * Default is "forbid" (0). In which case we don't set any ALPN callbacks, and - * the ServerHello does not contain an ALPN section. - */ - if (client->radiusv11 != FR_RADIUSV11_FORBID) { + switch (client->radiusv11) { + /* + * We don't set any callbacks. If the client sends ALPN (or not), we + * just do normal RADIUS. + */ + case FR_RADIUSV11_FORBID: + DEBUG("(TLS) ALPN radiusv11 = forbid"); + break; + + /* + * Setting the client hello callback catches the case where we send ALPN, + * and the client doesn't send anything. + */ + case FR_RADIUSV11_REQUIRE: + SSL_CTX_set_client_hello_cb(this->tls->ctx, radiusv11_client_hello_cb, this); + /* FALL-THROUGH */ + + /* + * We're willing to do normal RADIUS, but we send ALPN, and then check if + * (or what) the client sends back as ALPN. + */ + case FR_RADIUSV11_ALLOW: SSL_CTX_set_alpn_select_cb(this->tls->ctx, radiusv11_server_alpn_cb, this); DEBUG("(TLS) ALPN radiusv11 = allow / require"); - } else { - DEBUG("(TLS) ALPN radiusv11 = forbid"); } #endif } @@ -1313,6 +1519,12 @@ static CONF_PARSER limit_config[] = { { "max_connections", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.max_connections), "16" }, { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.lifetime), "0" }, { "idle_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.idle_timeout), STRINGIFY(30) }, +#ifdef SO_RCVTIMEO + { "read_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.read_timeout), NULL }, +#endif +#ifdef SO_SNDTIMEO + { "write_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.write_timeout), NULL }, +#endif #endif CONF_PARSER_TERMINATOR }; @@ -1467,6 +1679,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) return -1; } + this->tls->name = "RADIUS/TLS"; + #ifdef HAVE_PTHREAD_H if (pthread_mutex_init(&sock->mutex, NULL) < 0) { rad_assert(0 == 1); @@ -1822,8 +2036,6 @@ static int stats_socket_recv(rad_listen_t *listener) rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); if (rcode < 0) return 0; - FR_STATS_INC(auth, total_requests); - if (rcode < 20) { /* RADIUS_HDR_LEN */ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); FR_STATS_INC(auth, total_malformed_requests); @@ -1871,7 +2083,6 @@ static int stats_socket_recv(rad_listen_t *listener) } #endif - /* * Check if an incoming request is "ok" * @@ -1947,7 +2158,7 @@ static int auth_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2)); if (!packet) { FR_STATS_INC(auth, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -1955,6 +2166,11 @@ static int auth_socket_recv(rad_listen_t *listener) return 0; } + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + #ifdef __APPLE__ #ifdef WITH_UDPFROMTO /* @@ -2343,7 +2559,7 @@ static int coa_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, client->require_ma); if (!packet) { FR_STATS_INC(coa, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -2663,7 +2879,7 @@ static int proxy_socket_encode(RADIUSV11_UNUSED rad_listen_t *listener, REQUEST } -static int proxy_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) +static int proxy_socket_decode(RADIUSV11_UNUSED rad_listen_t *listener, REQUEST *request) { #ifdef WITH_RADIUSV11 listen_socket_t *sock = listener->data; @@ -2907,6 +3123,9 @@ static int listen_bind(rad_listen_t *this) */ if (sock->interface) { #ifdef SO_BINDTODEVICE + /* + * Linux: Bind to an interface by name. + */ struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); @@ -2919,45 +3138,81 @@ static int listen_bind(rad_listen_t *this) if (rcode < 0) { close(this->fd); ERROR("Failed binding to interface %s: %s", - sock->interface, fr_syserror(errno)); + sock->interface, fr_syserror(errno)); return -1; - } /* else it worked. */ + } #else + + /* + * If we don't bind to an interface by name, we usually bind to it by index. + */ + int idx = if_nametoindex(sock->interface); + + if (idx == 0) { + close(this->fd); + ERROR("Failed finding interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + +#ifdef IP_BOUND_IF + /* + * OSX / ?BSD / Solaris: bind to interface by index for IPv4 + */ + if (sock->my_ipaddr.af == AF_INET) { + rad_suid_up(); + rcode = setsockopt(this->fd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)); + rad_suid_down(); + if (rcode < 0) { + close(this->fd); + ERROR("Failed binding to interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + } else +#endif + +#ifdef IPV6_BOUND_IF + /* + * OSX / ?BSD / Solaris: bind to interface by index for IPv6 + */ + if (sock->my_ipaddr.af == AF_INET6) { + rad_suid_up(); + rcode = setsockopt(this->fd, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx)); + rad_suid_down(); + if (rcode < 0) { + close(this->fd); + ERROR("Failed binding to interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + } else +#endif + #ifdef HAVE_STRUCT_SOCKADDR_IN6 #ifdef HAVE_NET_IF_H /* - * Odds are that any system supporting "bind to - * device" also supports IPv6, so this next bit - * isn't necessary. But it's here for - * completeness. - * - * If we're doing IPv6, and the scope hasn't yet - * been defined, set the scope to the scope of - * the interface. + * Otherwise generic IPv6: set the scope to the + * interface, and hope that all of the read/write + * routines respect that. */ if (sock->my_ipaddr.af == AF_INET6) { if (sock->my_ipaddr.scope == 0) { - sock->my_ipaddr.scope = if_nametoindex(sock->interface); - if (sock->my_ipaddr.scope == 0) { - close(this->fd); - ERROR("Failed finding interface %s: %s", - sock->interface, fr_syserror(errno)); - return -1; - } - } /* else scope was defined: we're OK. */ + sock->my_ipaddr.scope = idx; + } /* else scope was already defined */ } else #endif #endif - /* - * IPv4: no link local addresses, - * and no bind to device. - */ + + /* + * IPv4, or no socket options to bind to interface. + */ { close(this->fd); ERROR("Failed binding to interface %s: \"bind to device\" is unsupported", sock->interface); return -1; } -#endif +#endif /* SO_BINDTODEVICE */ } #ifdef WITH_TCP @@ -3067,6 +3322,7 @@ static int listen_bind(rad_listen_t *this) int on = 1; if (setsockopt(this->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { + close(this->fd); ERROR("Can't set broadcast option: %s", fr_syserror(errno)); return -1; @@ -3115,6 +3371,7 @@ static int listen_bind(rad_listen_t *this) memset(&src, 0, sizeof_src); if (getsockname(this->fd, (struct sockaddr *) &src, &sizeof_src) < 0) { + close(this->fd); ERROR("Failed getting socket name: %s", fr_syserror(errno)); return -1; @@ -3122,6 +3379,7 @@ static int listen_bind(rad_listen_t *this) if (!fr_sockaddr2ipaddr(&src, sizeof_src, &sock->my_ipaddr, &sock->my_port)) { + close(this->fd); ERROR("Socket has unsupported address family"); return -1; } @@ -3131,9 +3389,9 @@ static int listen_bind(rad_listen_t *this) #ifdef WITH_TCP if (sock->proto == IPPROTO_TCP) { /* - * Woker threads are blocking. + * If we dedicate a worker thread to each socket, then the socket is blocking. * - * Otherwise, they're non-blocking. + * Otherwise, all input TCP sockets are non-blocking. */ if (!this->workers) { if (fr_nonblock(this->fd) < 0) { @@ -3331,11 +3589,15 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t * FIXME: connect() is blocking! * We do this with the proxy mutex locked, which may * cause large delays! - * - * http://www.developerweb.net/forum/showthread.php?p=13486 */ this->fd = fr_socket_client_tcp(&home->src_ipaddr, - &home->ipaddr, home->port, false); + &home->ipaddr, home->port, +#ifdef WITH_TLS + !this->nonblock +#else + false +#endif + ); /* * Set max_requests, lifetime, and idle_timeout from the home server. @@ -3378,10 +3640,25 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t this->nonblock |= home->nonblock; +#ifdef TCP_NODELAY + /* + * Also set TCP_NODELAY, to force the data to be written quickly. + */ + if (sock->proto == IPPROTO_TCP) { + int on = 1; + + if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { + ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); + goto error; + } + } +#endif + /* * Set non-blocking if it's configured. */ if (this->nonblock) { + fr_assert(0); if (fr_nonblock(this->fd) < 0) { ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); goto error; @@ -3394,15 +3671,34 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t goto error; } -#ifdef TCP_NODELAY + } else { /* - * Also set TCP_NODELAY, to force the data to be written quickly. + * Only set timeouts when the socket is blocking. This allows blocking + * sockets to still time out when the underlying socket is dead. */ - if (sock->proto == IPPROTO_TCP) { - int on = 1; +#ifdef SO_RCVTIMEO + if (sock->limit.read_timeout) { + struct timeval tv; + + tv.tv_sec = sock->limit.read_timeout; + tv.tv_usec = 0; - if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { - ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); + if (setsockopt(this->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + ERROR("(TLS) Failed to set read_timeout: %s", fr_syserror(errno)); + goto error; + } + } +#endif + +#ifdef SO_SNDTIMEO + if (sock->limit.write_timeout) { + struct timeval tv; + + tv.tv_sec = sock->limit.write_timeout; + tv.tv_usec = 0; + + if (setsockopt(this->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { + ERROR("(TLS) Failed to set write_timeout: %s", fr_syserror(errno)); goto error; } } @@ -3427,6 +3723,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t } #endif + sock->connect_timeout = home->connect_timeout; this->recv = proxy_tls_recv; @@ -3575,7 +3872,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) char const *value; fr_dlhandle handle; CONF_SECTION *server_cs; +#ifdef WITH_TCP char const *p; +#endif char buffer[32]; cp = cf_pair_find(cs, "type"); @@ -3864,7 +4163,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) if (override) { cs = cf_section_sub_find_name2(config, "server", main_config.name); - if (cs) this->server = main_config.name; + if (!cs) cs = cf_section_sub_find_name2(config, "server", + "default"); + if (cs) this->server = cf_section_name2(cs); } *last = this; diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index 227ae4a..2b2dda8 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -73,6 +73,8 @@ static char const *gid_name = NULL; static char const *chroot_dir = NULL; static bool allow_core_dumps = false; static char const *radlog_dest = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; /* * These are not used anywhere else.. @@ -87,6 +89,56 @@ static bool do_colourise = false; static char const *radius_dir = NULL; //!< Path to raddb directory +#ifndef HAVE_KQUEUE +static uint32_t max_fds = 0; +#endif + +static const FR_NAME_NUMBER fr_bool_auto_names[] = { + { "false", FR_BOOL_FALSE }, + { "no", FR_BOOL_FALSE }, + { "0", FR_BOOL_FALSE }, + + { "true", FR_BOOL_TRUE }, + { "yes", FR_BOOL_TRUE }, + { "1", FR_BOOL_TRUE }, + + { "auto", FR_BOOL_AUTO }, + + { NULL, 0 } +}; + +/* + * Get decent values for false / true / auto + */ +int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str) +{ + int value; + + /* + * Don't change anything. + */ + if (!str) return 0; + + value = fr_str2int(fr_bool_auto_names, str, -1); + if (value >= 0) { + *out = value; + return 0; + } + + /* + * This should never happen, as the defaults are in the + * source code. If there's no CONF_PAIR, and there's a + * parse error, then the source code is wrong. + */ + if (!cp) { + fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name); + return -1; + } + + cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp)); + return -1; +} + /********************************************************************** * * We need to figure out where the logs go, before doing anything @@ -160,6 +212,8 @@ static const CONF_PARSER security_config[] = { { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) }, { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) }, { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"}, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"}, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"}, #ifdef ENABLE_OPENSSL_VERSION_CHECK { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"}, #endif @@ -195,8 +249,12 @@ static const CONF_PARSER server_config[] = { { "panic_action", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.panic_action), NULL}, { "hostname_lookups", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &fr_dns_lookups), "no" }, { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, + { "proxy_dedup_window", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.proxy_dedup_window), "1" }, { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, +#ifndef HAVE_KQUEUE + { "max_fds", FR_CONF_POINTER(PW_TYPE_INTEGER, &max_fds), "512" }, +#endif { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, @@ -864,6 +922,8 @@ int main_config_init(void) if (!main_config.dictionary_dir) { main_config.dictionary_dir = DICTDIR; } + main_config.require_ma = FR_BOOL_AUTO; + main_config.limit_proxy_state = FR_BOOL_AUTO; /* * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400 @@ -1144,6 +1204,10 @@ do {\ if ((main_config.reject_delay.tv_sec != 0) || (main_config.reject_delay.tv_usec != 0)) { FR_TIMEVAL_BOUND_CHECK("reject_delay", &main_config.reject_delay, >=, 1, 0); } + + FR_INTEGER_BOUND_CHECK("proxy_dedup_window", main_config.proxy_dedup_window, <=, 10); + FR_INTEGER_BOUND_CHECK("proxy_dedup_window", main_config.proxy_dedup_window, >=, 1); + FR_TIMEVAL_BOUND_CHECK("reject_delay", &main_config.reject_delay, <=, 10, 0); FR_INTEGER_BOUND_CHECK("cleanup_delay", main_config.cleanup_delay, <=, 30); @@ -1159,6 +1223,46 @@ do {\ main_config.init_delay.tv_sec = 0; main_config.init_delay.tv_usec = 2* (1000000 / 3); + { + CONF_PAIR *cp = NULL; + + subcs = cf_section_sub_find(cs, "security"); + if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator"); + if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) { + cf_file_free(cs); + return -1; + } + + if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state"); + if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) { + cf_file_free(cs); + return -1; + } + } + +#ifndef HAVE_KQUEUE + /* + * select() is limited to 1024 file descriptors. :( + */ + if (max_fds) { + if (max_fds > FD_SETSIZE) { + fr_ev_max_fds = FD_SETSIZE; + } else { + /* + * Round up to the next highest power of 2. + */ + max_fds--; + max_fds |= max_fds >> 1; + max_fds |= max_fds >> 2; + max_fds |= max_fds >> 4; + max_fds |= max_fds >> 8; + max_fds |= max_fds >> 16; + max_fds++; + fr_ev_max_fds = max_fds; + } + } +#endif + /* * Free the old configuration items, and replace them * with the new ones. diff --git a/src/main/map.c b/src/main/map.c index e59fcec..34683a2 100644 --- a/src/main/map.c +++ b/src/main/map.c @@ -1108,7 +1108,7 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t */ if (((map->lhs->tmpl_list == PAIR_LIST_COA) || (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) { - if (request->parent) { + if (context->parent) { REDEBUG("You can only do 'update coa' when processing a packet which was received from the network"); return -2; } diff --git a/src/main/modcall.c b/src/main/modcall.c index aa6abf8..5a3116c 100644 --- a/src/main/modcall.c +++ b/src/main/modcall.c @@ -311,7 +311,7 @@ static rlm_rcode_t CC_HINT(nonnull) call_modsingle(rlm_components_t component, m */ blocked = (request->master_state == REQUEST_STOP_PROCESSING); if (blocked) { - RWARN("Module %s became unblocked", sp->modinst->entry->name); + RWARN("Module %s(%s) became unblocked", sp->modinst->name, sp->modinst->entry->name); } fail: diff --git a/src/main/modules.c b/src/main/modules.c index fd4334d..9ccb310 100644 --- a/src/main/modules.c +++ b/src/main/modules.c @@ -571,10 +571,10 @@ static int module_conf_parse(module_instance_t *node, void **handle) * If there is supposed to be instance data, allocate it now. * Also parse the configuration data, if required. */ - if (node->entry->module->inst_size) { - *handle = talloc_zero_array(node, uint8_t, node->entry->module->inst_size); - rad_assert(*handle); + *handle = talloc_zero_array(node, uint8_t, node->entry->module->inst_size); + rad_assert(*handle); + if (node->entry->module->inst_size) { talloc_set_name(*handle, "rlm_%s_t", node->entry->module->name ? node->entry->module->name : "config"); diff --git a/src/main/process.c b/src/main/process.c index ed77839..9880e34 100644 --- a/src/main/process.c +++ b/src/main/process.c @@ -1006,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) #ifdef HAVE_PTHREAD_H rad_assert(request->child_pid == NO_SUCH_CHILD_PID); #endif + + /* + * Set the statistics immediately if we can. + */ + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } @@ -1258,6 +1264,9 @@ static void request_cleanup_delay(REQUEST *request, int action) #ifdef DEBUG_STATE_MACHINE if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); #endif + + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } /* else it's time to clean up */ @@ -1486,7 +1495,7 @@ static void request_finish(REQUEST *request, int action) /* * Maybe originate a CoA request. */ - if ((action == FR_ACTION_RUN) && !request->proxy && request->coa) { + if ((action == FR_ACTION_RUN) && (!request->proxy || request->proxy->dst_port == 0) && request->coa) { request_coa_originate(request); } #endif @@ -1591,9 +1600,14 @@ static void request_finish(REQUEST *request, int action) #ifdef WITH_PROXY /* * If we timed out a proxy packet, don't delay - * the reject any more. + * the reject any more. Or, if we proxied it to + * a real home server, then don't delay it. + * + * We don't want to have each proxy in a chain + * adding their own reject delay, which would + * result in N*reject_delays being applied. */ - if (request->proxy && !request->proxy_reply) { + if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { request->response_delay.tv_sec = 0; request->response_delay.tv_usec = 0; } @@ -2024,6 +2038,10 @@ static REQUEST *request_setup(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PA return NULL; } +#ifdef WITH_RADIUSV11 + request->reply->radiusv11 = packet->radiusv11; +#endif + request->listener = listener; request->client = client; request->packet = talloc_steal(request, packet); @@ -2286,16 +2304,6 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) if (!request->in_proxy_hash) return; -#ifdef COA_TUNNEL - /* - * Track how many IDs are used. This information - * helps the listen_coa_find() function get a - * listener which has free IDs. - */ - rad_assert(request->proxy_listener->num_ids_used > 0); - request->proxy_listener->num_ids_used--; -#endif - fr_packet_list_id_free(proxy_list, request->proxy, yank); request->in_proxy_hash = false; @@ -2341,6 +2349,18 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) if (request->proxy_listener) { request->proxy_listener->count--; + +#ifdef WITH_COA_TUNNEL + /* + * Track how many IDs are used. This information + * helps the listen_coa_find() function get a + * listener which has free IDs. + */ + if (request->proxy_listener->send_coa) { + rad_assert(request->proxy_listener->num_ids_used > 0); + request->proxy_listener->num_ids_used--; + } +#endif } request->proxy_listener = NULL; @@ -2361,18 +2381,6 @@ static void remove_from_proxy_hash(REQUEST *request) */ if (!request->in_proxy_hash) return; -#ifdef WITH_TCP - /* - * Status-Server packets aren't removed from the proxy hash. They're reused. - * - * Unless we're tearing down the listener. - */ - if ((request->proxy->proto == IPPROTO_TCP) && (request->proxy->code == PW_CODE_STATUS_SERVER) && - request->proxy_listener && (request->proxy_listener->status < RAD_LISTEN_STATUS_EOL)) { - return; - } -#endif - /* * The "not in hash" flag is definitive. However, if the * flag says that it IS in the hash, there might still be @@ -2493,13 +2501,13 @@ static int insert_into_proxy_hash(REQUEST *request) goto fail; } -#ifdef COA_TUNNEL +#ifdef WITH_COA_TUNNEL /* * Track how many IDs are used. This information * helps the listen_coa_find() function get a * listener which has free IDs. */ - request->proxy_listener->num_ids_used++; + if (request->proxy_listener->send_coa) request->proxy_listener->num_ids_used++; #endif /* @@ -2790,16 +2798,78 @@ int request_proxy_reply(RADIUS_PACKET *packet) * server core, but I guess we can fix that later. */ if (!request->proxy_reply) { + decode_fail_t reason; + + /* + * If the home server configuration requires a Message-Authenticator, then set the flag, + * but only if the proxied packet is Access-Request or Status-Sercer. + * + * The realms.c file already clears require_ma for TLS connections. + */ + bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST); + if (!request->home_server) { proxy_reply_too_late(request); return 0; } + if (!rad_packet_ok(packet, require_ma, &reason)) { + DEBUG("Ignoring invalid packet - %s", fr_strerror()); + return 0; + } + if (rad_verify(packet, request->proxy, request->home_server->secret) != 0) { DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); return 0; } + + /* + * BlastRADIUS checks. We're running in the main + * listener thread, so there's no conflict + * checking or setting these fields. + */ + if ((request->proxy->code == PW_CODE_ACCESS_REQUEST) && +#ifdef WITH_TLS + !request->home_server->tls && +#endif + !packet->eap_message) { + if (request->home_server->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_FALSE; + } else { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack."); + RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_TRUE; + } + + } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) { + /* + * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet. + */ + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + } } /* @@ -2863,6 +2933,18 @@ int request_proxy_reply(RADIUS_PACKET *packet) #ifdef WITH_STATS /* + * The average includes our time to receive packets and + * look them up in the hashes, which should be the same + * for all packets. + * + * We update the response time only for the FIRST packet + * we receive. + */ + if (request->home_server->ema.window > 0) { + radius_stats_ema(&request->home_server->ema, &request->proxy->timestamp, &now); + } + + /* * Update the proxy listener stats here, because only one * thread accesses that at a time. The home_server and * main proxy_*_stats structures are updated once the @@ -3193,6 +3275,14 @@ static int request_will_proxy(REQUEST *request) pool = home_pool_byname(vp->vp_strvalue, pool_type); /* + * If we didn't find an auth only or acct only pool + * fall-back to those which do both. + */ + if (!pool && ((pool_type == HOME_TYPE_AUTH) || (pool_type == HOME_TYPE_ACCT))) { + pool = home_pool_byname(vp->vp_strvalue, HOME_TYPE_AUTH_ACCT); + } + + /* * Send it directly to a home server (i.e. NAS) */ } else if (((vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY)) != NULL) || @@ -3292,6 +3382,15 @@ static int request_will_proxy(REQUEST *request) * Find the home server by name. */ home = home_server_byname(vp->vp_strvalue, type); + + /* + * If we didn't find an auth only or acct only home server + * fall-back to those which do both. + */ + if (!home && ((type == HOME_TYPE_AUTH) || (type == HOME_TYPE_ACCT))) { + home = home_server_byname(vp->vp_strvalue, HOME_TYPE_AUTH_ACCT); + } + if (!home) { RWDEBUG("No such home server %s", vp->vp_strvalue); return 0; @@ -3361,7 +3460,7 @@ static int request_will_proxy(REQUEST *request) home = home_server_ldb(realmname, pool, request); if (!home) { - REDEBUG2("Failed to find live home server: Cancelling proxy"); + REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); return -1; } @@ -3373,7 +3472,11 @@ do_home: * Once we've decided to proxy a request, we cannot send * a CoA packet. So we free up any CoA packet here. */ - if (request->coa) request_done(request->coa, FR_ACTION_COA_CANCELLED); + if (request->coa) { + RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); + request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; + } #endif /* @@ -3605,6 +3708,7 @@ static int request_proxy(REQUEST *request) if (request->coa) { RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; } #endif @@ -3817,6 +3921,7 @@ static void request_ping(REQUEST *request, int action) break; case FR_ACTION_PROXY_REPLY: + default: rad_assert(request->in_proxy_hash); request->home_server->num_received_pings++; @@ -3855,9 +3960,10 @@ static void request_ping(REQUEST *request, int action) mark_home_server_alive(request, home); break; - default: + case FR_ACTION_RUN: + case FR_ACTION_DUP: RDEBUG3("%s: Ignoring action %s", __FUNCTION__, action_codes[action]); - break; + return; } rad_assert(!request->in_request_hash); @@ -4339,10 +4445,10 @@ static void proxy_wait_for_reply(REQUEST *request, int action) * and should be suppressed by the proxy. */ when = request->proxy->timestamp; - when.tv_sec++; + when.tv_sec += main_config.proxy_dedup_window; if (timercmp(&now, &when, <)) { - DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", + DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", inet_ntop(request->proxy->dst_ipaddr.af, &request->proxy->dst_ipaddr.ipaddr, buffer, sizeof(buffer)), @@ -4543,9 +4649,9 @@ static void request_coa_originate(REQUEST *request) VERIFY_REQUEST(request); rad_assert(request->coa != NULL); - rad_assert(request->proxy == NULL); + rad_assert(request->proxy == NULL || request->proxy->dst_port == 0); rad_assert(!request->in_proxy_hash); - rad_assert(request->proxy_reply == NULL); + rad_assert(request->proxy_reply == NULL || request->proxy_reply->src_port == 0); /* * Check whether we want to originate one, or cancel one. @@ -5018,7 +5124,11 @@ static bool coa_max_time(REQUEST *request) buffer, sizeof(buffer)), request->proxy->dst_port, mrd); - request_done(request, FR_ACTION_DONE); + if (setup_post_proxy_fail(request)) { + request_queue_or_run(request, coa_no_reply); + } else { + request_done(request, FR_ACTION_DONE); + } return true; } @@ -5386,7 +5496,6 @@ static void listener_free_cb(void *ctx) talloc_free(this); } -#ifdef WITH_TCP #ifdef WITH_PROXY static int proxy_eol_cb(void *ctx, void *data) { @@ -5426,7 +5535,6 @@ static int proxy_eol_cb(void *ctx, void *data) return 0; } #endif /* WITH_PROXY */ -#endif /* WITH_TCP */ static void event_new_fd(rad_listen_t *this) { @@ -5602,6 +5710,7 @@ static void event_new_fd(rad_listen_t *this) */ this->print(this, buffer, sizeof(buffer)); ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); + this->status = RAD_LISTEN_STATUS_EOL; goto listener_is_eol; } /* end of INIT */ @@ -5645,6 +5754,7 @@ static void event_new_fd(rad_listen_t *this) fr_event_fd_delete(el, 0, this->fd); this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } +#endif /* WITH_TCP */ /* * The socket has had a catastrophic error. Close it. @@ -5708,7 +5818,6 @@ static void event_new_fd(rad_listen_t *this) */ this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } /* socket is at EOL */ -#endif /* WITH_TCP */ if (this->dead) goto wait_some_more; @@ -6147,7 +6256,7 @@ static void check_proxy(rad_listen_t *head) if (sock->my_ipaddr.af == AF_INET) has_v4 = true; if (sock->my_ipaddr.af == AF_INET6) has_v6 = true; break; - + default: break; } diff --git a/src/main/radclient.c b/src/main/radclient.c index 49da461..ab880dd 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -60,11 +60,13 @@ static fr_ipaddr_t server_ipaddr; static int resend_count = 1; static bool done = true; static bool print_filename = false; +static bool blast_radius = false; static fr_ipaddr_t client_ipaddr; static uint16_t client_port = 0; static int sockfd; +static int last_used_id = -1; #ifdef WITH_TCP static char const *proto = NULL; @@ -95,6 +97,7 @@ static void NEVER_RETURNS usage(void) fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n"); fprintf(stderr, " -4 Use IPv4 address of server\n"); fprintf(stderr, " -6 Use IPv6 address of server.\n"); + fprintf(stderr, " -b Mandate checks for Blast RADIUS issue (this is not set by default).\n"); fprintf(stderr, " -c <count> Send each packet 'count' times.\n"); fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n"); fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n"); @@ -416,7 +419,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) #endif request->files = files; - request->packet->id = -1; /* allocate when sending */ + request->packet->id = last_used_id; /* either requested, or allocated by the library */ request->num = num++; /* @@ -892,7 +895,7 @@ static int send_one_packet(rc_request_t *request) /* * Haven't sent the packet yet. Initialize it. */ - if (request->packet->id == -1) { + if (!request->tries || (request->packet->id == -1)) { int i; bool rcode; @@ -949,8 +952,18 @@ static int send_one_packet(rc_request_t *request) assert(request->packet->id != -1); assert(request->packet->data == NULL); - for (i = 0; i < 4; i++) { - ((uint32_t *) request->packet->vector)[i] = fr_rand(); + if (request->packet->code == PW_CODE_ACCESS_REQUEST) { + VALUE_PAIR *vp; + + if (((vp = fr_pair_find_by_num(request->packet->vps, PW_PACKET_AUTHENTICATION_VECTOR, 0, TAG_ANY)) != NULL) && + (vp->vp_length >= 16)) { + memcpy(request->packet->vector, vp->vp_octets, 16); + + } else { + for (i = 0; i < 4; i++) { + ((uint32_t *) request->packet->vector)[i] = fr_rand(); + } + } } /* @@ -1048,11 +1061,15 @@ static int send_one_packet(rc_request_t *request) REDEBUG("Failed to send packet for ID %d", request->packet->id); deallocate_id(request); request->done = true; + stats.lost++; return -1; } if (fr_log_fp) { fr_packet_header_print(fr_log_fp, request->packet, false); + + if (fr_debug_lvl > 2) rad_print_hex(request->packet); + if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, request->packet->vps); } @@ -1060,6 +1077,130 @@ static int send_one_packet(rc_request_t *request) } /* + * Do Blast RADIUS checks. + * + * The request is an Access-Request, and does NOT contain Proxy-State. + * + * The reply is a raw packet, and is NOT yet decoded. + */ +static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply) +{ + uint8_t *attr, *end; + VALUE_PAIR *vp; + bool have_message_authenticator = false; + + /* + * We've received a raw packet. Nothing has (as of yet) checked + * anything in it other than the length, and that it's a + * well-formed RADIUS packet. + */ + switch (reply->data[0]) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + if (reply->data[1] != request->packet->id) { + ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id); + return -1; + } + break; + + default: + ERROR("Invalid reply code %d to Access-Request", reply->data[0]); + return -1; + } + + /* + * If the reply has a Message-Authenticator, then it MIGHT be fine. + */ + attr = reply->data + 20; + end = reply->data + reply->data_len; + + /* + * It should be the first attribute, so we warn if it isn't there. + * + * But it's not a fatal error. + */ + if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) { + RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.", + fr_packet_codes[reply->data[0]]); + } + + /* + * Set up for Proxy-State checks. + * + * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY); + + while (attr < end) { + /* + * Blast RADIUS work-arounds require that + * Message-Authenticator is the first attribute in the + * reply. Note that we don't check for it being the + * first attribute, but simply that it exists. + * + * That check is a balance between securing the reply + * packet from attacks, and not violating the RFCs which + * say that there is no order to attributes in the + * packet. + * + * However, no matter the status of the '-b' flag we + * still can check for the signature of the attack, and + * discard packets which are suspicious. This behavior + * protects radclient from the attack, without mandating + * new behavior on the server side. + * + * Note that we don't set the '-b' flag by default. + * radclient is intended for testing / debugging, and is + * not intended to be used as part of a secure login / + * user checking system. + */ + if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { + have_message_authenticator = true; + goto next; + } + + /* + * If there are Proxy-State attributes in the reply, they must + * match EXACTLY the Proxy-State attributes in the request. + * + * Note that we don't care if there are more Proxy-States + * in the request than in the reply. The Blast RADIUS + * issue requires _adding_ Proxy-State attributes, and + * cannot work when the server _deletes_ Proxy-State + * attributes. + */ + if (attr[0] == PW_PROXY_STATE) { + if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) { + ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id); + ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent."); + return -1; + } + + vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY); + } + + next: + attr += attr[1]; + } + + /* + * If "-b" is set, then we require Message-Authenticator in the reply. + */ + if (blast_radius && !have_message_authenticator) { + ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.", + fr_packet_codes[reply->data[0]]); + return -1; + } + + /* + * The packet doesn't look like it's a Blast RADIUS attack. The + * caller will now verify the packet signature. + */ + return 0; +} + +/* * Receive one packet, maybe. */ static int recv_one_packet(int wait_time) @@ -1101,6 +1242,8 @@ static int recv_one_packet(int wait_time) return -1; /* bad packet */ } + if (fr_debug_lvl > 2) rad_print_hex(reply); + packet_p = fr_packet_list_find_byreply(pl, reply); if (!packet_p) { ERROR("Received reply to request we did not send. (id=%d socket %d)", @@ -1111,6 +1254,20 @@ static int recv_one_packet(int wait_time) request = fr_packet2myptr(rc_request_t, packet, packet_p); /* + * We want radclient to be able to send any packet, including + * imperfect ones. However, we do NOT want to be vulnerable to + * the "Blast RADIUS" issue. Instead of adding command-line + * flags to enable/disable similar flags to what the server + * sends, we just do a few more smart checks to double-check + * things. + */ + if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && + blast_radius_check(request, reply) < 0) { + rad_free(&reply); + return -1; + } + + /* * Fails the signature validation: not a real reply. * FIXME: Silently drop it and listen for another packet. */ @@ -1243,7 +1400,7 @@ int main(int argc, char **argv) exit(1); } - while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx" + while ((c = getopt(argc, argv, "46bc:d:D:f:Fhi:n:p:qr:sS:t:vx" #ifdef WITH_TCP "P:" #endif @@ -1256,6 +1413,10 @@ int main(int argc, char **argv) force_af = AF_INET6; break; + case 'b': + blast_radius = true; + break; + case 'c': if (!isdigit((uint8_t) *optarg)) usage(); @@ -1297,6 +1458,15 @@ int main(int argc, char **argv) print_filename = true; break; + case 'i': + if (!isdigit((uint8_t) *optarg)) + usage(); + last_used_id = atoi(optarg); + if ((last_used_id < 0) || (last_used_id > 255)) { + usage(); + } + break; + case 'n': persec = atoi(optarg); if (persec <= 0) usage(); @@ -1562,6 +1732,7 @@ int main(int argc, char **argv) int n = parallel; rc_request_t *next; char const *filename = NULL; + time_t wake = 0; done = true; sleep_time = -1; @@ -1569,6 +1740,15 @@ int main(int argc, char **argv) /* * Walk over the packets, sending them. */ + for (this = request_head; this != NULL; this = this->next) { + if (this->reply) continue; + + if (!this->timestamp) continue; + + if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) { + wake = this->timestamp + ((int) timeout) * (retries - this->tries); + } + } for (this = request_head; this != NULL; this = next) { next = this->next; @@ -1613,6 +1793,10 @@ int main(int argc, char **argv) break; } + if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) { + wake = this->timestamp + ((int) timeout) * (retries - this->tries); + } + /* * Wait a little before sending * the next packet, if told to. @@ -1664,7 +1848,18 @@ int main(int argc, char **argv) * Still have outstanding requests. */ if (fr_packet_list_num_elements(pl) > 0) { + time_t now = time(NULL); done = false; + + /* + * The last time we wake up for a packet. + * + * If we're past that time, then give up. + */ + if (wake < now) { + break; + } + } else { sleep_time = 0; } diff --git a/src/main/radsecret b/src/main/radsecret new file mode 100755 index 0000000..2a03a2e --- /dev/null +++ b/src/main/radsecret @@ -0,0 +1,7 @@ +#!/usr/bin/env perl +# +# A tool which generates strong shared secrets. +# +use Convert::Base32; +use Crypt::URandom(); +print join('-', unpack("(A4)*", lc encode_base32(Crypt::URandom::urandom(12)))), "\n"; diff --git a/src/main/radsecret.mk b/src/main/radsecret.mk new file mode 100644 index 0000000..c5f43b4 --- /dev/null +++ b/src/main/radsecret.mk @@ -0,0 +1,5 @@ +install: $(R)/$(bindir)/radsecret + +$(R)/$(bindir)/radsecret: ${top_srcdir}/src/main/radsecret + @$(ECHO) INSTALL radsecret + $(Q)${PROGRAM_INSTALL} -c -m 755 $< $@ diff --git a/src/main/radsniff.c b/src/main/radsniff.c index e0d2b65..0458d77 100644 --- a/src/main/radsniff.c +++ b/src/main/radsniff.c @@ -1804,6 +1804,15 @@ static void _unmark_link(void *request) this->in_link_tree = false; } +/** Exit the event loop after a given timeout. + * + */ +static void timeout_event(UNUSED void *ctx) +{ + fr_event_loop_exit(events, 1); +} + + #ifdef HAVE_COLLECTDC_H /** Re-open the collectd socket * @@ -1919,6 +1928,7 @@ static void NEVER_RETURNS usage(int status) fprintf(output, " -R <filter> RADIUS attribute response filter.\n"); fprintf(output, " -s <secret> RADIUS secret.\n"); fprintf(output, " -S Write PCAP data to stdout.\n"); + fprintf(output, " -t <timeout> Stop after <timeout> seconds.\n"); fprintf(output, " -v Show program version information.\n"); fprintf(output, " -w <file> Write output packets to file.\n"); fprintf(output, " -x Print more debugging information.\n"); @@ -1947,6 +1957,8 @@ int main(int argc, char *argv[]) char buffer[1024]; int opt; + unsigned int timeout = 0; + fr_event_t *timeout_ev = NULL; char const *radius_dir = RADDBDIR; char const *dict_dir = DICTDIR; @@ -1999,7 +2011,7 @@ int main(int argc, char *argv[]) /* * Get options */ - while ((opt = getopt(argc, argv, "ab:c:Cd:D:e:Ff:hi:I:l:L:mp:P:qr:R:s:Svw:xXW:T:P:N:O:")) != EOF) { + while ((opt = getopt(argc, argv, "ab:c:Cd:D:e:Ff:hi:I:l:L:mp:P:qr:R:s:St:vw:xXW:T:P:N:O:")) != EOF) { switch (opt) { case 'a': { @@ -2120,6 +2132,10 @@ int main(int argc, char *argv[]) conf->radius_secret = optarg; break; + case 't': + timeout = atoi(optarg); + break; + case 'S': conf->to_stdout = true; break; @@ -2569,7 +2585,7 @@ int main(int argc, char *argv[]) * Setup and enter the main event loop. Who needs libev when you can roll your own... */ { - struct timeval now; + struct timeval now, when; rs_update_t update; char *buff; @@ -2632,17 +2648,26 @@ int main(int argc, char *argv[]) update.stats = &stats; update.in = in; - now.tv_sec += conf->stats.interval; - now.tv_usec = 0; - if (!fr_event_insert(events, rs_stats_process, (void *) &update, &now, &event)) { + when = now; + when.tv_sec += conf->stats.interval; + when.tv_usec = 0; + if (!fr_event_insert(events, rs_stats_process, (void *) &update, &when, &event)) { ERROR("Failed inserting stats event"); } INFO("Muting stats for the next %i milliseconds (warmup)", conf->stats.timeout); - rs_tv_add_ms(&now, conf->stats.timeout, &stats.quiet); + rs_tv_add_ms(&when, conf->stats.timeout, &stats.quiet); } - } + if (timeout) { + when = now; + when.tv_sec += timeout; + + if (!fr_event_insert(events, timeout_event, NULL, &when, &timeout_ev)) { + ERROR("Failed inserting timeout event"); + } + } + } /* * Do this as late as possible so we can return an error code if something went wrong. diff --git a/src/main/realms.c b/src/main/realms.c index 2959d82..fa42813 100644 --- a/src/main/realms.c +++ b/src/main/realms.c @@ -452,6 +452,12 @@ static CONF_PARSER limit_config[] = { { "max_requests", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.max_requests), "0" }, { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.lifetime), "0" }, { "idle_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.idle_timeout), "0" }, +#ifdef SO_RCVTIMEO + { "read_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.read_timeout), NULL }, +#endif +#ifdef SO_SNDTIMEO + { "write_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.write_timeout), NULL }, +#endif CONF_PARSER_TERMINATOR }; @@ -475,8 +481,11 @@ static CONF_PARSER home_server_recv_coa[] = { #endif +static const char *require_message_authenticator = NULL; + static CONF_PARSER home_server_config[] = { { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL }, { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL }, { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL }, @@ -780,6 +789,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE home->cs = cs; home->state = HOME_STATE_UNKNOWN; home->proto = IPPROTO_UDP; + home->require_ma = main_config.require_ma; + + require_message_authenticator = false; /* * Parse the configuration into the home server @@ -787,6 +799,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE */ if (cf_section_parse(cs, home, home_server_config) < 0) goto error; + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) { + goto error; + } + /* * It has an IP address, it must be a remote server. */ @@ -1116,11 +1132,18 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE if (tls) { int rcode; + /* + * We don't require this for TLS connections. + */ + home->require_ma = false; + home->tls = tls_client_conf_parse(tls); if (!home->tls) { goto error; } + home->tls->name = "RADIUS/TLS"; + /* * Connection timeouts for outgoing TLS connections. */ @@ -3181,7 +3204,7 @@ int home_server_afrom_file(char const *filename) goto error; } -#ifdef COA_TUNNEL +#ifdef WITH_COA_TUNNEL if (home->recv_coa) { fr_strerror_printf("Dynamic home_server '%s' cannot receive CoA requests'", p); talloc_free(home); diff --git a/src/main/state.c b/src/main/state.c index 3700062..ab7a180 100644 --- a/src/main/state.c +++ b/src/main/state.c @@ -33,13 +33,14 @@ RCSID("$Id$") #include <freeradius-devel/process.h> typedef struct state_entry_t { - uint8_t state[AUTH_VECTOR_LEN]; + uint8_t state[MD5_DIGEST_LENGTH]; time_t cleanup; struct state_entry_t *prev; struct state_entry_t *next; int tries; + bool ours; TALLOC_CTX *ctx; VALUE_PAIR *vps; @@ -379,10 +380,55 @@ static void fr_state_cleanup(state_entry_t *head) request_inject(request); } + if (entry->opaque) { + entry->free_opaque(entry->opaque); + } + + if (entry->ctx) talloc_free(entry->ctx); + talloc_free(entry); } } +static void state_entry_calc(REQUEST *request, state_entry_t *entry, VALUE_PAIR *vp) +{ + /* + * Assume our own State first. This is where the state + * is the correct size, AND we're not proxying it to an + * external home server. If we are proxying it to an + * external home server, then that home server creates + * the State attribute, and we don't control it. + */ + if (entry->ours || + (vp->vp_length == sizeof(entry->state) && + (!request->proxy || (request->proxy->dst_port == 0)))) { + memcpy(entry->state, vp->vp_octets, sizeof(entry->state)); + entry->ours = true; + + } else { + FR_MD5_CTX ctx; + + /* + * We don't control the external State attribute. + * As a result, different home servers _may_ + * create the same State attribute. In order to + * differentiate them, we "mix in" the User-Name, + * which should contain the realm. And we then + * hope that different home servers in the same + * realm don't create overlapping State + * attributes. + */ + fr_md5_init(&ctx); + fr_md5_update(&ctx, vp->vp_octets, vp->vp_length); + + vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); + if (vp) fr_md5_update(&ctx, vp->vp_octets, vp->vp_length); + + fr_md5_final(entry->state, &ctx); + fr_md5_destroy(&ctx); + } +} + /* * Create a new entry. Called with the mutex held. @@ -430,17 +476,18 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, */ if (old) { entry->tries = old->tries + 1; + entry->ours = old->ours; /* * Track State */ - if (!vp) { + if (!vp && entry->ours) { memcpy(entry->state, old->state, sizeof(entry->state)); entry->state[1] = entry->state[0] ^ entry->tries; - entry->state[8] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); + entry->state[8] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); entry->state[10] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff); - entry->state[12] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); + entry->state[12] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); } /* @@ -457,6 +504,8 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, x = fr_rand(); memcpy(entry->state + (i * 4), &x, sizeof(x)); } + + entry->ours = true; /* we created it */ } /* @@ -464,27 +513,8 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, * one we created above. */ if (vp) { - /* - * Assume our own State first. - */ - if (vp->vp_length == sizeof(entry->state)) { - memcpy(entry->state, vp->vp_octets, sizeof(entry->state)); + state_entry_calc(request, entry, vp); - /* - * Too big? Get the MD5 hash, in order - * to depend on the entire contents of State. - */ - } else if (vp->vp_length > sizeof(entry->state)) { - fr_md5_calc(entry->state, vp->vp_octets, vp->vp_length); - - /* - * Too small? Use the whole thing, and - * set the rest of entry->state to zero. - */ - } else { - memcpy(entry->state, vp->vp_octets, vp->vp_length); - memset(&entry->state[vp->vp_length], 0, sizeof(entry->state) - vp->vp_length); - } } else { vp = fr_pair_afrom_num(packet, PW_STATE, 0); fr_pair_value_memcpy(vp, entry->state, sizeof(entry->state)); @@ -497,7 +527,7 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, /* * Make unique for different virtual servers handling same request */ - *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(request->server); + if (entry->ours) *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(request->server); /* * Copy server to state in case it's needed for cleanup @@ -520,7 +550,7 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, /* * Find the entry, based on the State attribute. */ -static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIUS_PACKET *packet) +static state_entry_t *fr_state_find(REQUEST *request, fr_state_t *state, const char *server, RADIUS_PACKET *packet) { VALUE_PAIR *vp; state_entry_t *entry, my_entry; @@ -528,31 +558,12 @@ static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIU vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY); if (!vp) return NULL; - /* - * Assume our own State first. - */ - if (vp->vp_length == sizeof(my_entry.state)) { - memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state)); - - /* - * Too big? Get the MD5 hash, in order - * to depend on the entire contents of State. - */ - } else if (vp->vp_length > sizeof(my_entry.state)) { - fr_md5_calc(my_entry.state, vp->vp_octets, vp->vp_length); - - /* - * Too small? Use the whole thing, and - * set the rest of my_entry.state to zero. - */ - } else { - memcpy(my_entry.state, vp->vp_octets, vp->vp_length); - memset(&my_entry.state[vp->vp_length], 0, sizeof(my_entry.state) - vp->vp_length); - } + my_entry.ours = false; + state_entry_calc(request, &my_entry, vp); /* Make unique for different virtual servers handling same request */ - if (server) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server); + if (server && my_entry.ours) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server); entry = rbtree_finddata(state->tree, &my_entry); @@ -576,7 +587,7 @@ void fr_state_discard(REQUEST *request, RADIUS_PACKET *original) request->state = NULL; PTHREAD_MUTEX_LOCK(&state->mutex); - entry = fr_state_find(state, request->server, original); + entry = fr_state_find(request, state, request->server, original); if (entry) state_entry_free(state, entry); PTHREAD_MUTEX_UNLOCK(&state->mutex); } @@ -601,7 +612,7 @@ void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet) rad_assert(request->state == NULL); PTHREAD_MUTEX_LOCK(&state->mutex); - entry = fr_state_find(state, request->server, packet); + entry = fr_state_find(request, state, request->server, packet); /* * This has to be done in a mutex lock, because talloc @@ -683,7 +694,7 @@ bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET * cleanup_list = fr_state_cleanup_find(state); if (original) { - old = fr_state_find(state, request->server, original); + old = fr_state_find(request, state, request->server, original); } else { old = NULL; } diff --git a/src/main/stats.c b/src/main/stats.c index a5c672e..29f2c48 100644 --- a/src/main/stats.c +++ b/src/main/stats.c @@ -91,51 +91,57 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, void request_stats_final(REQUEST *request) { rad_listen_t *listener; + RADCLIENT *client; - if (request->master_state == REQUEST_COUNTED) return; + if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; - if (!request->listener) return; - if (!request->client) return; + /* don't count statistic requests */ + if (request->packet->code == PW_CODE_STATUS_SERVER) { + return; + } - if ((request->listener->type != RAD_LISTEN_NONE) && + listener = request->listener; + if (listener) switch (listener->type) { + case RAD_LISTEN_NONE: #ifdef WITH_ACCOUNTING - (request->listener->type != RAD_LISTEN_ACCT) && + case RAD_LISTEN_ACCT: #endif #ifdef WITH_COA - (request->listener->type != RAD_LISTEN_COA) && + case RAD_LISTEN_COA: #endif - (request->listener->type != RAD_LISTEN_AUTH)) return; + case RAD_LISTEN_AUTH: + break; - /* don't count statistic requests */ - if (request->packet->code == PW_CODE_STATUS_SERVER) - return; + default: + return; + } /* * Deal with TCP / TLS issues. The statistics are kept in the parent socket. */ - listener = request->listener; - if (listener->parent) listener = listener->parent; + if (listener && listener->parent) listener = listener->parent; + client = request->client; #undef INC_AUTH -#define INC_AUTH(_x) radius_auth_stats._x++;listener->stats._x++;request->client->auth._x++; +#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; #undef INC_ACCT #ifdef WITH_ACCOUNTING -#define INC_ACCT(_x) radius_acct_stats._x++;listener->stats._x++;request->client->acct._x++ +#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ #else #define INC_ACCT(_x) #endif #undef INC_COA #ifdef WITH_COA -#define INC_COA(_x) radius_coa_stats._x++;listener->stats._x++;request->client->coa._x++ +#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ #else #define INC_COA(_x) #endif #undef INC_DSC #ifdef WITH_DSC -#define INC_DSC(_x) radius_dsc_stats._x++;listener->stats._x++;request->client->dsc._x++ +#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ #else #define INC_DSC(_x) #endif @@ -148,7 +154,7 @@ void request_stats_final(REQUEST *request) * deleted, because only the main server thread calls * this function, which makes it thread-safe. */ - if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { + if (request->reply) switch (request->reply->code) { case PW_CODE_ACCESS_ACCEPT: INC_AUTH(total_access_accepts); @@ -247,25 +253,21 @@ void request_stats_final(REQUEST *request) switch (request->proxy->code) { case PW_CODE_ACCESS_REQUEST: proxy_auth_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: proxy_acct_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: proxy_coa_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; case PW_CODE_DISCONNECT_REQUEST: proxy_dsc_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #endif @@ -276,7 +278,7 @@ void request_stats_final(REQUEST *request) if (!request->proxy_reply) goto done; /* simplifies formatting */ #undef INC -#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; +#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; switch (request->proxy_reply->code) { case PW_CODE_ACCESS_ACCEPT: @@ -347,10 +349,7 @@ void request_stats_final(REQUEST *request) done: #endif /* WITH_PROXY */ - if (request->max_time) { - RADCLIENT *client = request->client; - switch (request->packet->code) { case PW_CODE_ACCESS_REQUEST: FR_STATS_INC(auth, unresponsive_child); @@ -376,7 +375,7 @@ void request_stats_final(REQUEST *request) } } - request->master_state = REQUEST_COUNTED; + request->options |= RAD_REQUEST_OPTION_STATS; } typedef struct fr_stats2vp { @@ -525,8 +524,8 @@ void request_stats_reply(REQUEST *request) /* * Authentication. */ - if (((flag->vp_integer & 0x01) != 0) && - ((flag->vp_integer & 0xc0) == 0)) { + if (((flag->vp_integer & 0x01) != 0) && /* auth */ + ((flag->vp_integer & 0xe0) == 0)) { /* not client, server or home-server */ request_stats_addvp(request, authvp, &radius_auth_stats); } @@ -534,8 +533,8 @@ void request_stats_reply(REQUEST *request) /* * Accounting */ - if (((flag->vp_integer & 0x02) != 0) && - ((flag->vp_integer & 0xc0) == 0)) { + if (((flag->vp_integer & 0x02) != 0) && /* accounting */ + ((flag->vp_integer & 0xe0) == 0)) { /* not client, server or home-server */ request_stats_addvp(request, acctvp, &radius_acct_stats); } #endif @@ -544,8 +543,8 @@ void request_stats_reply(REQUEST *request) /* * Proxied authentication requests. */ - if (((flag->vp_integer & 0x04) != 0) && - ((flag->vp_integer & 0x20) == 0)) { + if (((flag->vp_integer & 0x04) != 0) && /* proxy-auth */ + ((flag->vp_integer & 0x20) == 0)) { /* not client */ request_stats_addvp(request, proxy_authvp, &proxy_auth_stats); } @@ -553,8 +552,8 @@ void request_stats_reply(REQUEST *request) /* * Proxied accounting requests. */ - if (((flag->vp_integer & 0x08) != 0) && - ((flag->vp_integer & 0x20) == 0)) { + if (((flag->vp_integer & 0x08) != 0) && /* proxy-accounting */ + ((flag->vp_integer & 0x20) == 0)) { /* not client */ request_stats_addvp(request, proxy_acctvp, &proxy_acct_stats); } #endif @@ -563,7 +562,7 @@ void request_stats_reply(REQUEST *request) /* * Internal server statistics */ - if ((flag->vp_integer & 0x10) != 0) { + if ((flag->vp_integer & 0x10) != 0) { /* internal */ vp = radius_pair_create(request->reply, &request->reply->vps, PW_FREERADIUS_STATS_START_TIME, VENDORPEC_FREERADIUS); if (vp) vp->vp_date = start_time.tv_sec; @@ -607,7 +606,7 @@ void request_stats_reply(REQUEST *request) /* * For a particular client. */ - if ((flag->vp_integer & 0x20) != 0) { + if ((flag->vp_integer & 0x20) != 0) { /* client */ fr_ipaddr_t ipaddr; VALUE_PAIR *server_ip, *server_port = NULL; RADCLIENT *client = NULL; @@ -764,8 +763,8 @@ void request_stats_reply(REQUEST *request) /* * For a particular "listen" socket. */ - if (((flag->vp_integer & 0x40) != 0) && - ((flag->vp_integer & 0x03) != 0)) { + if (((flag->vp_integer & 0x40) != 0) && /* server */ + ((flag->vp_integer & 0x03) != 0)) { /* auth or accounting */ rad_listen_t *this; VALUE_PAIR *server_ip, *server_port; fr_ipaddr_t ipaddr; @@ -807,7 +806,7 @@ void request_stats_reply(REQUEST *request) fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, server_port)); - if ((flag->vp_integer & 0x01) != 0) { + if ((flag->vp_integer & 0x01) != 0) { /* auth */ if ((request->listener->type == RAD_LISTEN_AUTH) || (request->listener->type == RAD_LISTEN_NONE)) { request_stats_addvp(request, authvp, &this->stats); @@ -817,7 +816,7 @@ void request_stats_reply(REQUEST *request) } #ifdef WITH_ACCOUNTING - if ((flag->vp_integer & 0x02) != 0) { + if ((flag->vp_integer & 0x02) != 0) { /* accounting */ if ((request->listener->type == RAD_LISTEN_ACCT) || (request->listener->type == RAD_LISTEN_NONE)) { request_stats_addvp(request, acctvp, &this->stats); @@ -832,8 +831,8 @@ void request_stats_reply(REQUEST *request) /* * Home servers. */ - if (((flag->vp_integer & 0x80) != 0) && - ((flag->vp_integer & 0x03) != 0)) { + if (((flag->vp_integer & 0x80) != 0) && /* home-server */ + ((flag->vp_integer & 0x03) != 0)) { /* auth or accounting */ home_server_t *home; VALUE_PAIR *server_ip, *server_port; fr_ipaddr_t ipaddr; @@ -935,7 +934,7 @@ void request_stats_reply(REQUEST *request) PW_FREERADIUS_STATS_LAST_PACKET_SENT, VENDORPEC_FREERADIUS); if (vp) vp->vp_date = home->last_packet_sent; - if ((flag->vp_integer & 0x01) != 0) { + if ((flag->vp_integer & 0x01) != 0) { /* auth */ if (home->type == HOME_TYPE_AUTH) { request_stats_addvp(request, proxy_authvp, &home->stats); @@ -945,7 +944,7 @@ void request_stats_reply(REQUEST *request) } #ifdef WITH_ACCOUNTING - if ((flag->vp_integer & 0x02) != 0) { + if ((flag->vp_integer & 0x02) != 0) { /* accounting */ if (home->type == HOME_TYPE_ACCT) { request_stats_addvp(request, proxy_acctvp, &home->stats); @@ -991,14 +990,14 @@ void radius_stats_ema(fr_stats_ema_t *ema, } - tdiff = start->tv_sec; - tdiff -= end->tv_sec; + tdiff = end->tv_sec; + tdiff -= start->tv_sec; micro = (int) tdiff; if (micro > 40) micro = 40; /* don't overflow 32-bit ints */ micro *= USEC; - micro += start->tv_usec; - micro -= end->tv_usec; + micro += end->tv_usec; + micro -= start->tv_usec; micro *= EMA_SCALE; diff --git a/src/main/threads.c b/src/main/threads.c index a187106..5730b5e 100644 --- a/src/main/threads.c +++ b/src/main/threads.c @@ -291,7 +291,7 @@ static void tls_mutexes_destroy(void) #ifdef HAVE_CRYPTO_SET_LOCKING_CALLBACK int i, num; - rad_assert(ssl_mutex != NULL); + rad_assert(ssl_mutexes != NULL); num = CRYPTO_num_locks(); diff --git a/src/main/tls.c b/src/main/tls.c index c8cae3b..736ee41 100644 --- a/src/main/tls.c +++ b/src/main/tls.c @@ -404,7 +404,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, * The passed identity is weird. Deny it. */ if (!identity_is_safe(identity)) { - RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity); + RWDEBUG("(TLS) %s - Invalid characters in PSK identity %s", conf->name, identity); return 0; } @@ -421,7 +421,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query, NULL, NULL); if (!hex_len) { - RWDEBUG("(TLS) PSK expansion returned an empty string."); + RWDEBUG("(TLS) %s - PSK expansion returned an empty string.", conf->name); return 0; } @@ -431,7 +431,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, * the truncation, and complain about it. */ if (hex_len > (2 * max_psk_len)) { - RWDEBUG("(TLS) Returned PSK is too long (%u > %u)", + RWDEBUG("(TLS) %s - Returned PSK is too long (%u > %u)", conf->name, (unsigned int) hex_len, 2 * max_psk_len); return 0; } @@ -635,9 +635,11 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con case SSL_ERROR_WANT_READ: ssn->connected = false; + RDEBUG("(TLS) %s - tls_new_client_session WANT_READ", conf->name); return ssn; case SSL_ERROR_WANT_WRITE: + RDEBUG("(TLS) %s - tls_new_client_session WANT_WRITE", conf->name); ssn->connected = false; return ssn; } @@ -681,7 +683,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU rad_assert(request != NULL); - RDEBUG2("(TLS) Initiating new session"); + RDEBUG2("(TLS) %s -Initiating new session", conf->name); /* * Replace X509 store if it is time to update CRLs/certs in ca_path @@ -690,10 +692,10 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU pthread_mutex_lock(&conf->mutex); /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { - RDEBUG2("Flushing X509 store to re-read data from ca_path dir"); + RDEBUG2("(TLS) Flushing X509 store to re-read data from ca_path dir"); if ((new_cert_store = fr_init_x509_store(conf)) == NULL) { - RERROR("(TLS) Error replacing X509 store, out of memory (?)"); + RERROR("(TLS) %s - Error replacing X509 store, out of memory (?)", conf->name); } else { if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store); /* @@ -752,7 +754,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU SSL_set_app_data(new_tls, NULL); if ((state = talloc_zero(ctx, tls_session_t)) == NULL) { - RERROR("(TLS) Error allocating memory for SSL state"); + RERROR("(TLS) %s - Error allocating memory for SSL state", conf->name); return NULL; } session_init(state); @@ -808,7 +810,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY); if (!key) key = vp; - RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue); + RDEBUG2("(TLS) %s - Loading session certificate file \"%s\"", conf->name, vp->vp_strvalue); if (conf->realms) { fr_realm_ctx_t my_r, *r; @@ -887,11 +889,18 @@ after_chain: * Verify the peer certificate, if asked. */ if (client_cert) { - RDEBUG2("(TLS) Setting verify mode to require certificate from client"); + RDEBUG2("(TLS) %s - Setting verify mode to require certificate from client", conf->name); verify_mode = SSL_VERIFY_PEER; verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; verify_mode |= SSL_VERIFY_CLIENT_ONCE; } +#ifdef PSK_MAX_IDENTITY_LEN + else if (conf->psk_identity) { + RDEBUG2("(TLS) %s - Setting verify peer mode due to PSK", conf->name); + verify_mode = SSL_VERIFY_PEER; + verify_mode |= SSL_VERIFY_CLIENT_ONCE; + } +#endif SSL_set_verify(state->ssl, verify_mode, cbtls_verify); SSL_set_ex_data(state->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); @@ -970,14 +979,14 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) int err; if (ssn->invalid_hb_used) { - REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection"); + REDEBUG("(TLS) %s - OpenSSL Heartbeat attack detected. Closing connection", ssn->conf->name); return 0; } if (ssn->dirty_in.used > 0) { err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used); if (err != (int) ssn->dirty_in.used) { - REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + REDEBUG("(TLS) %s - Failed writing %zd bytes to SSL BIO: %d", ssn->conf->name, ssn->dirty_in.used, err); record_init(&ssn->dirty_in); return 0; } @@ -998,7 +1007,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) VALUE_PAIR *vp; char const *str_version; - RDEBUG2("(TLS) Connection Established"); + RDEBUG2("(TLS) %s - Connection Established", ssn->conf->name); ssn->is_init_finished = true; vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0); @@ -1049,10 +1058,10 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) REXDENT(); } } - else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); } - else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); } - else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); } - else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); } + else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) %s - In Handshake Phase", ssn->conf->name); } + else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) %s - Before Handshake Phase", ssn->conf->name); } + else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) %s- In Accept mode", ssn->conf->name); } + else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) %s - In Connect mode", ssn->conf->name); } #if OPENSSL_VERSION_NUMBER >= 0x10001000L /* @@ -1070,7 +1079,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) * to get the session is a hard fail. */ if (!ssn->ssl_session && ssn->is_init_finished) { - RDEBUG("(TLS) Failed getting session"); + RDEBUG("(TLS) %s - Failed getting session", ssn->conf->name); return 0; } } @@ -1084,12 +1093,12 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, sizeof(ssn->dirty_out.data)); if (err > 0) { - RDEBUG3("(TLS) got %d bytes of data", err); + RDEBUG3("(TLS) %s- got %d bytes of data", ssn->conf->name, err); ssn->dirty_out.used = err; } else if (BIO_should_retry(ssn->from_ssl)) { record_init(&ssn->dirty_in); - RDEBUG2("(TLS) Asking for more data in tunnel."); + RDEBUG2("(TLS) %s - Asking for more data in tunnel.", ssn->conf->name); return 1; } else { @@ -1098,7 +1107,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) return 0; } } else { - RDEBUG2("(TLS) Application data."); + RDEBUG2("(TLS) %s - Application data.", ssn->conf->name); /* Its clean application data, leave whatever is in the buffer */ #if 0 record_init(&ssn->clean_out); @@ -1245,6 +1254,7 @@ void tls_session_information(tls_session_t *tls_session) REQUEST *request; VALUE_PAIR *vp; char content_type[16], alert_buf[16]; + char name_buf[128]; char buffer[32]; /* @@ -1262,7 +1272,12 @@ void tls_session_information(tls_session_t *tls_session) request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); if (!request) return; - str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv"; + if (tls_session->info.origin) { + snprintf(name_buf, sizeof(name_buf), "(TLS) %s - send", tls_session->conf->name); + } else { + snprintf(name_buf, sizeof(name_buf), "(TLS) %s - recv", tls_session->conf->name); + } + str_write_p = name_buf; #define FROM_CLIENT (tls_session->info.origin == 0) @@ -1605,7 +1620,7 @@ void tls_session_information(tls_session_t *tls_session) RDEBUG2("%s", tls_session->info.info_description); - if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details); + if (FROM_CLIENT && details) RDEBUG2("(TLS) %s - The client is informing us that %s.", tls_session->conf->name, details); } static CONF_PARSER cache_config[] = { @@ -1735,6 +1750,10 @@ static CONF_PARSER tls_client_config[] = { { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, fr_tls_server_conf_t, certificate_file), NULL }, { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, fr_tls_server_conf_t, ca_file), NULL }, { "private_key_password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, fr_tls_server_conf_t, private_key_password), NULL }, +#ifdef PSK_MAX_IDENTITY_LEN + { "psk_identity", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, psk_identity), NULL }, + { "psk_hexphrase", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, fr_tls_server_conf_t, psk_password), NULL }, +#endif { "dh_file", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, dh_file), NULL }, { "random_file", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, random_file), NULL }, { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, fragment_size), "1024" }, @@ -1924,7 +1943,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) blob_len = i2d_SSL_SESSION(sess, NULL); if (blob_len < 1) { /* something went wrong */ - if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length"); + if (request) RWDEBUG("(TLS) %s - Session serialisation failed, could not determine required buffer length", conf->name); return 0; } @@ -1932,14 +1951,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) /* alloc and convert to ASN.1 */ sess_blob = malloc(blob_len); if (!sess_blob) { - RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); + RWDEBUG("(TLS) %s - Session serialisation failed, couldn't allocate buffer (%d bytes)", conf->name, blob_len); return 0; } /* openssl mutates &p */ p = sess_blob; rv = i2d_SSL_SESSION(sess, &p); if (rv != blob_len) { - if (request) RWDEBUG("(TLS) Session serialisation failed"); + if (request) RWDEBUG("(TLS) %s - Session serialisation failed", conf->name); goto error; } @@ -1948,8 +1967,8 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) conf->session_cache_path, FR_DIR_SEP, buffer); fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR); if (fd < 0) { - if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s", - filename, fr_syserror(errno)); + if (request) RERROR("(TLS) %s - Session serialisation failed, failed opening session file %s: %s", + conf->name, filename, fr_syserror(errno)); goto error; } @@ -1971,7 +1990,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) while (todo > 0) { rv = write(fd, p, todo); if (rv < 1) { - if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno)); + if (request) RWDEBUG("(TLS) %s - Failed writing session: %s", conf->name, fr_syserror(errno)); close(fd); goto error; } @@ -1979,7 +1998,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) todo -= rv; } close(fd); - if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); + if (request) RWDEBUG("(TLS) %s - Wrote session %s to %s (%d bytes)", conf->name, buffer, filename, blob_len); } error: @@ -2102,20 +2121,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); fd = open(filename, O_RDONLY); if (fd < 0) { - RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno)); + RWDEBUG("(TLS) %s - No persisted session file %s: %s", conf->name, filename, fr_syserror(errno)); goto error; } rv = fstat(fd, &st); if (rv < 0) { - RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); + RWDEBUG("(TLS) %s - Failed stating persisted session file %s: %s", conf->name, filename, fr_syserror(errno)); close(fd); goto error; } sess_data = talloc_array(NULL, unsigned char, st.st_size); if (!sess_data) { - RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); + RWDEBUG("(TLS) %s- Failed allocating buffer for persisted session (%d bytes)", conf->name, (int) st.st_size); close(fd); goto error; } @@ -2125,7 +2144,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l while (todo > 0) { rv = read(fd, q, todo); if (rv < 1) { - RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno)); + RWDEBUG("(TLS) %s - Failed reading persisted session: %s", conf->name, fr_syserror(errno)); close(fd); goto error; } @@ -2149,7 +2168,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l memcpy(&o, &p, sizeof(o)); sess = d2i_SSL_SESSION(NULL, o, st.st_size); if (!sess) { - RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + RWDEBUG("(TLS) %s - Failed loading persisted session: %s", conf->name, ERR_error_string(ERR_get_error(), NULL)); goto error; } @@ -2159,7 +2178,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l rv = pairlist_read(talloc_ctx, filename, &pairlist, 1); if (rv < 0) { /* not safe to un-persist a session w/o VPs */ - RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer); + RWDEBUG("(TLS) %s - Failed loading persisted VPs for session %s", conf->name, buffer); SSL_SESSION_free(sess); sess = NULL; goto error; @@ -2173,7 +2192,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l time_t expires; if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { - RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror()); + RDEBUG2("(TLS) %s - Failed getting certificate expiration, removing cache entry for session %s - %s", conf->name, buffer, fr_strerror()); SSL_SESSION_free(sess); sess = NULL; goto error; @@ -2193,8 +2212,8 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l if (vp) { if ((request->timestamp + vp->vp_integer) > expires) { vp->vp_integer = expires - request->timestamp; - RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); + RWDEBUG2("(TLS) %s - Updating Session-Timeout to %u, due to impending certificate expiration", + conf->name, vp->vp_integer); } } } @@ -2208,8 +2227,8 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); if (type && (type->vp_integer != vp->vp_integer)) { - REDEBUG("Resumption has changed EAP types for session %s", buffer); - REDEBUG("Rejecting session due to protocol violations"); + REDEBUG("(TLS) %s - Resumption has changed EAP types for session %s", conf->name, buffer); + REDEBUG("(TLS) %s - Rejecting session due to protocol violations", conf->name); goto error; } } @@ -2524,8 +2543,8 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le if (vp) { if ((request->timestamp + vp->vp_integer) > expires) { vp->vp_integer = expires - request->timestamp; - RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); + RWDEBUG2("(TLS) %s - Updating Session-Timeout to %u, due to impending certificate expiration", + conf->name, vp->vp_integer); } } } @@ -2535,7 +2554,7 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le */ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY); if (!vp) { - RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer); + RWDEBUG("(TLS) %s - Failed to find TLS-Session-Data in 'session-state' list for session %s", conf->name, buffer); goto error; } @@ -2552,7 +2571,7 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le p = vp->vp_octets; sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length); if (!sess) { - RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + RWDEBUG("(TLS) %s - Failed loading persisted session: %s", conf->name, ERR_error_string(ERR_get_error(), NULL)); goto error; } @@ -2629,7 +2648,7 @@ typedef enum { } ocsp_status_t; static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issuer_cert, X509 *client_cert, - fr_tls_server_conf_t *conf) + STACK_OF(X509) *untrusted, fr_tls_server_conf_t *conf) { OCSP_CERTID *certid; OCSP_REQUEST *req; @@ -2810,7 +2829,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue REDEBUG("ocsp: Response has wrong nonce value"); goto ocsp_end; } - if (OCSP_basic_verify(bresp, NULL, store, 0)!=1){ + if (OCSP_basic_verify(bresp, untrusted, store, 0)!=1){ REDEBUG("ocsp: Couldn't verify OCSP basic response"); goto ocsp_end; } @@ -2931,10 +2950,6 @@ static char const *cert_attr_names[9][2] = { #define FR_TLS_SAN_UPN (7) #define FR_TLS_VALID_SINCE (8) -static const char *cert_names[2] = { - "client", "server", -}; - /* * Before trusting a certificate, you must make sure that the * certificate is 'valid'. There are several steps that your @@ -3048,7 +3063,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) buf[0] = '\0'; sn = X509_get_serialNumber(client_cert); - RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup ]); + RDEBUG2("(TLS) %s - Creating attributes from %d certificate in chain", conf->name, lookup + 1); RINDENT(); /* @@ -3340,8 +3355,8 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) #if 0 ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert)); -#endif break; +#endif } /* @@ -3366,8 +3381,10 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) if (conf->disallow_untrusted || RDEBUG_ENABLED2) { int i; - WARN("Certificate chain - %i cert(s) untrusted", + WARN("Certificate chain - %i intermediate CA cert(s) untrusted", X509_STORE_CTX_get_num_untrusted(ctx)); + WARN("To forbid these certificates see 'reject_unknown_intermediate_ca'"); + for (i = sk_X509_num(untrusted); i > 0 ; i--) { X509 *this_cert = sk_X509_value(untrusted, i - 1); @@ -3417,7 +3434,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) } } /* check_cert_cn */ -#ifdef HAVE_OPENSSL_OCSP_H +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && defined(HAVE_OPENSSL_OCSP_H) if (my_ok) { /* * No OCSP, allow external verification. @@ -3447,7 +3464,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) * run the external verification routine. If it's marked as * "skip verify on OK", then we don't do verify. */ - my_ok = ocsp_check(request, ocsp_store, issuer_cert, client_cert, conf); + my_ok = ocsp_check(request, ocsp_store, issuer_cert, client_cert, untrusted, conf); if (my_ok != OCSP_STATUS_FAILED) { do_verify = !conf->verify_skip_if_ocsp_ok; } @@ -4161,6 +4178,16 @@ post_ca: ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); return NULL; } + +#ifdef WITH_RADIUSV11 + /* + * RADIUS 1.1 requires TLS 1.3 or later. + */ + if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) { + WARN(LOG_PREFIX ": The configuration allows TLS <1.3. RADIUS/1.1 MUST use TLS 1.3"); + WARN(LOG_PREFIX ": Please set: tls_min_version = '1.3'"); + } +#endif } else { #ifdef WITH_RADIUSV11 /* diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c index fa8c382..3dc786b 100644 --- a/src/main/tls_listen.c +++ b/src/main/tls_listen.c @@ -377,7 +377,6 @@ static int tls_socket_recv(rad_listen_t *listener) REQUEST *request; listen_socket_t *sock = listener->data; fr_tls_status_t status; - RADCLIENT *client = sock->client; if (!sock->packet) { sock->packet = rad_alloc(sock, false); @@ -580,6 +579,7 @@ check_for_setup: * or any other contents. */ request->packet->code = PW_CODE_STATUS_SERVER; + request->packet->id = request->reply->id = 0; request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); request->packet->data[0] = PW_CODE_STATUS_SERVER; request->packet->data[3] = 20; @@ -673,6 +673,7 @@ read_application_data: #ifdef WITH_RADIUSV11 packet->radiusv11 = sock->radiusv11; #endif + packet->tls = true; if (!rad_packet_ok(packet, 0, NULL)) { if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -708,8 +709,6 @@ read_application_data: } } - FR_STATS_INC(auth, total_requests); - return 1; } @@ -874,6 +873,7 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) */ if (sock->state == LISTEN_TLS_CHECKING) { if (request->reply->code != PW_CODE_ACCESS_ACCEPT) { + RDEBUG("(TLS) Connection checks failed - closing connection"); listener->status = RAD_LISTEN_STATUS_EOL; listener->tls = NULL; /* parent owns this! */ @@ -887,6 +887,7 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) /* * Resume reading from the listener. */ + RDEBUG("(TLS) Connection checks succeeded - continuing with normal reads"); listener->status = RAD_LISTEN_STATUS_RESUME; radius_update_listener(listener); @@ -1286,6 +1287,7 @@ int proxy_tls_recv(rad_listen_t *listener) } #endif + packet->tls = true; /* * FIXME: Client MIB updates? @@ -1373,6 +1375,7 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) * if there's no packet, encode it here. */ if (!request->proxy->data) { + request->reply->tls = true; request->proxy_listener->proxy_encode(request->proxy_listener, request); } @@ -1406,9 +1409,11 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) return -1; } + RDEBUG3("(TLS) has %zu bytes in the buffer", sock->ssn->clean_out.used); + memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); sock->ssn->clean_out.used += request->proxy->data_len; - RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); + RDEBUG3("(TLS) Saving %zu bytes of RADIUS traffic for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; @@ -1508,6 +1513,8 @@ int proxy_tls_send_reply(rad_listen_t *listener, REQUEST *request) if ((listener->status != RAD_LISTEN_STATUS_INIT && (listener->status != RAD_LISTEN_STATUS_KNOWN))) return 0; + request->reply->tls = true; + /* * Pack the VPs */ diff --git a/src/main/tmpl.c b/src/main/tmpl.c index 6ec2598..6746bde 100644 --- a/src/main/tmpl.c +++ b/src/main/tmpl.c @@ -579,6 +579,7 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, long num; char *q; tmpl_type_t type = TMPL_TYPE_ATTR; + DICT_ATTR const *da; value_pair_tmpl_attr_t attr; /* So we don't fill the tmpl with junk and then error out */ @@ -694,6 +695,16 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, } /* + * Canonicalize the attribute. + * + * We can define multiple names for one attribute. In + * which case we only use the canonical name. + */ + da = dict_attrbyvalue(attr.da->attr, attr.da->vendor); + if (da && (attr.da != da)) attr.da = da; + + + /* * The string MIGHT have a tag. */ if (*p == ':') { diff --git a/src/main/unittest.c b/src/main/unittest.c index 72fdadc..c82d31d 100644 --- a/src/main/unittest.c +++ b/src/main/unittest.c @@ -55,7 +55,7 @@ char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING #endif ; -fr_event_list_t *el = NULL; +static fr_event_list_t *el = NULL; /* * Static functions. diff --git a/src/main/util.c b/src/main/util.c index b216cc9..607bcaa 100644 --- a/src/main/util.c +++ b/src/main/util.c @@ -398,15 +398,15 @@ size_t rad_filename_escape(UNUSED REQUEST *request, char *out, size_t outlen, ch switch (utf8_len) { case 2: - snprintf(out, freespace, "-%x-%x", in[0], in[1]); + snprintf(out, freespace, "-%x-%x", (uint8_t)in[0], (uint8_t)in[1]); break; case 3: - snprintf(out, freespace, "-%x-%x-%x", in[0], in[1], in[2]); + snprintf(out, freespace, "-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2]); break; case 4: - snprintf(out, freespace, "-%x-%x-%x-%x", in[0], in[1], in[2], in[3]); + snprintf(out, freespace, "-%x-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2], (uint8_t)in[3]); break; } diff --git a/src/main/version.c b/src/main/version.c index 2fe3428..c190337 100644 --- a/src/main/version.c +++ b/src/main/version.c @@ -613,7 +613,7 @@ void version_print(void) DEBUG2(" "); } INFO("FreeRADIUS Version " RADIUSD_VERSION_STRING); - INFO("Copyright (C) 1999-2022 The FreeRADIUS server project and contributors"); + INFO("Copyright (C) 1999-2023 The FreeRADIUS server project and contributors"); INFO("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A"); INFO("PARTICULAR PURPOSE"); INFO("You may redistribute copies of FreeRADIUS under the terms of the"); |