# HG changeset patch # User Sebastien Decugis # Date 1243569463 -32400 # Node ID 1a4902b216f8041e358630ab459df19bc32571c2 # Parent 43c0e3f6795860eb8f5d64361e4a3fda56e98868 Improved initial handling of RADIUS messages diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/radius_gw.h --- a/extensions/radius_gw/radius_gw.h Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/radius_gw.h Fri May 29 12:57:43 2009 +0900 @@ -89,6 +89,9 @@ /* The message has a valid Message-Authenticator attribute */ unsigned valid_mac :1; + + /* The message has a valid NAS-IP(v6)-Address (1) and/or NAS-Identifier (2) attribute */ + unsigned valid_nas_info :2; }; }; @@ -107,7 +110,7 @@ int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref); int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli); int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli); -int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm); +int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm, int strict); void rgw_clients_dispose(struct rgw_client ** ref); void rgw_clients_dump(void); void rgw_clients_fini(void); diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/rg_common.h --- a/extensions/radius_gw/rg_common.h Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/rg_common.h Fri May 29 12:57:43 2009 +0900 @@ -45,7 +45,8 @@ #include #include #include - +#include +#include /* This should be overwritten before including this file */ #ifndef DEFINE_DEBUG_MACRO diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/rgw_clients.c --- a/extensions/radius_gw/rgw_clients.c Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/rgw_clients.c Fri May 29 12:57:43 2009 +0900 @@ -57,12 +57,19 @@ /* Reference count */ int refcount; - /* The address and optional port. The head list determines which one to use */ + /* The address and optional port. */ union { + struct sockaddr *sa; /* generic pointer */ struct sockaddr_in *sin; struct sockaddr_in6 *sin6; }; + /* The FQDN and optional aliases */ + char *fqdn; + char *realm; + char **aliases; + size_t aliases_nb; + /* The secret key data. */ struct { unsigned char * data; @@ -71,8 +78,9 @@ /* information of previous msg received, for duplicate checks. [0] for auth, [1] for acct. */ struct { - uint16_t port; - uint8_t id; + uint16_t port; + uint8_t id; + struct radius_msg * ans; /* to be able to resend the lost answer */ } last[2]; }; @@ -81,19 +89,32 @@ static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen ) { struct rgw_client *tmp = NULL; + char buf[255]; + int ret; + + /* Search FQDN for the client */ + ret = getnameinfo( *ip_port, sizeof(struct sockaddr_storage), &buf[0], sizeof(buf), NULL, 0, 0 ); + if (ret) { + TRACE_DEBUG(INFO, "Unable to resolve peer name: %s", gai_strerror(ret)); + return EINVAL; + } /* Create the new object */ CHECK_MALLOC( tmp = malloc(sizeof (struct rgw_client)) ); memset(tmp, 0, sizeof(struct rgw_client)); - - /* Initialize the chain */ rg_list_init(&tmp->chain); - /* move the sa info reference (the two if alternatives should be equivalent, but just in case... ) */ - if ((*ip_port)->sa_family == AF_INET) - tmp->sin = (struct sockaddr_in *) (*ip_port); - else - tmp->sin6 = (struct sockaddr_in6 *) (*ip_port); + /* Copy the fqdn */ + CHECK_MALLOC( tmp->fqdn = strdup(buf) ); + /* Find an appropriate realm */ + tmp->realm = strchr(tmp->fqdn, '.'); + if (tmp->realm) + tmp->realm += 1; + if ((!tmp->realm) || (*tmp->realm == '\0')) /* in case the fqdn was "localhost." for example, if it is possible... */ + tmp->realm = g_pconf->diameter_realm; + + /* move the sa info reference */ + tmp->sa = *ip_port; *ip_port = NULL; /* move the key material */ @@ -113,11 +134,16 @@ client->refcount -= 1; if (client->refcount <= 0) { + int idx; /* to be sure */ ASSERT( rg_list_is_empty(&client->chain) ); /* Free the data */ - free(client->sin); + for (idx = 0; idx < client->aliases_nb; idx++) + free(client->aliases[idx]); + free(client->aliases); + free(client->fqdn); + free(client->sa); free(client->key.data); free(client); } @@ -257,10 +283,20 @@ if ((cli->last[idx].id == (*msg)->radius.hdr->identifier) && (cli->last[idx].port == (*msg)->port)) { /* Duplicate! */ - TRACE_DEBUG(INFO, "Received duplicated RADIUS message (id: %02hhx, port: %hu), discarding.", (*msg)->radius.hdr->identifier, ntohs((*msg)->port)); + TRACE_DEBUG(INFO, "Received duplicated RADIUS message (id: %02hhx, port: %hu).", (*msg)->radius.hdr->identifier, ntohs((*msg)->port)); + if (cli->last[idx].ans) { + /* Resend the answer */ + ASSERT(0); + } rgw_msg_free(msg); } else { - /* Update information for future message */ + /* Update information for new message */ + if (cli->last[idx].ans) { + /* Free it */ + radius_msg_free(cli->last[idx].ans); + free(cli->last[idx].ans); + cli->last[idx].ans = NULL; + } cli->last[idx].id = (*msg)->radius.hdr->identifier; cli->last[idx].port = (*msg)->port; } @@ -268,19 +304,142 @@ return 0; } +/* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */ +/* Also update the client list of aliases if needed */ int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli) { - /* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */ - /* Also update the client FQDN and list of aliases if needed */ + int idx; + struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL; + + TRACE_ENTRY("%p %p", msg, cli); + CHECK_PARAMS(msg && cli && !msg->valid_nas_info ); + + /* Find the relevant attributes, if any */ + for (idx = 0; idx < msg->radius.attr_used; idx++) { + struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[idx]); + unsigned char * attr_val = (unsigned char *)(attr + 1); + size_t attr_len = attr->length - sizeof(struct radius_attr_hdr); + + if ((attr->type == RADIUS_ATTR_NAS_IP_ADDRESS) && (attr_len = 4)) { + nas_ip = attr; + continue; + } + + if ((attr->type == RADIUS_ATTR_NAS_IDENTIFIER) && (attr_len > 0)) { + nas_id = attr; + continue; + } + + if ((attr->type == RADIUS_ATTR_NAS_IPV6_ADDRESS) && (attr_len = 16)) { + nas_ip6 = attr; + continue; + } + } + + if (!nas_ip && !nas_ip6 && !nas_id) { + TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute."); + goto end; + } + + /* Check if the message was received from the IP in NAS-IP-Address attribute */ + if (nas_ip && (cli->sa->sa_family == AF_INET) && !memcmp(nas_ip+1, &cli->sin->sin_addr, sizeof(struct in_addr))) { + TRACE_DEBUG(FULL, "NAS-IP-Address contains the same address as the message was received from."); + msg->valid_nas_info |= 1; + } + if (nas_ip6 && (cli->sa->sa_family == AF_INET6) && !memcmp(nas_ip6+1, &cli->sin6->sin6_addr, sizeof(struct in6_addr))) { + TRACE_DEBUG(FULL, "NAS-IPv6-Address contains the same address as the message was received from."); + msg->valid_nas_info |= 1; + } + + /* If these conditions are not met, the message is probably forged (well, this might be false...) */ + if ((! msg->valid_nas_info) && (nas_ip || nas_ip6)) { + TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different from the sender's. Discarding..."); + return EINVAL; + } - ASSERT(0); - return ENOTSUP; + /* Now check the nas_id */ + if (nas_id) { + /* copy the alias */ + char * str; + int found, ret; + struct addrinfo hint, *res; + CHECK_MALLOC( str = malloc(nas_id->length - sizeof(struct radius_attr_hdr) + 1) ); + memcpy(str, nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); + str[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0'; + + /* Check if this alias is already in the aliases list */ + if (!strcasecmp(str, cli->fqdn)) { + TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the NAS"); + found = 1; + } else { + for (idx = 0; idx < cli->aliases_nb; idx++) { + if (!strcasecmp(str, cli->aliases[idx])) { + TRACE_DEBUG(FULL, "NAS-Identifier valid value found in the cache"); + found = 1; + break; + } + } + } + + if (found) { + free(str); + msg->valid_nas_info |= 2; + goto end; + } + + /* Now check if this alias is valid for this peer */ + memset(&hint, 0, sizeof(hint)); + hint.ai_family = cli->sa->sa_family; + hint.ai_flags = AI_CANONNAME; + ret = getaddrinfo(str, NULL, &hint, &res); + if (ret) { + TRACE_DEBUG(INFO, "Error while resolving NAS-Identifier value '%s': %s. Discarding message...", str, gai_strerror(ret)); + free(str); + return EINVAL; + } + if (strcasecmp(cli->fqdn, res->ai_canonname)) { + TRACE_DEBUG(INFO, "The NAS-Identifier value is not valid: '%s' resolved to '%s', expected '%s'. Discarding...", str, res->ai_canonname, cli->fqdn); + free(str); + freeaddrinfo(res); + return EINVAL; + } + + /* It is a valid alias, save it */ + freeaddrinfo(res); + CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(char *)) ); + cli->aliases[cli->aliases_nb + 1] = str; + cli->aliases_nb ++; + TRACE_DEBUG(FULL, "Saved valid alias for client: '%s' -> '%s'", str, cli->fqdn); + msg->valid_nas_info |= 2; + } +end: + return 0; } -int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm) +int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm, int strict) { - ASSERT(0); - return ENOTSUP; + TRACE_ENTRY("%p %p %g %p %g %p %p", cli, oh, oh_len, or, or_len, fqdn, realm); + CHECK_PARAMS(cli && fqdn && realm); + + if (oh && oh_len) { + if (strncasecmp(oh, cli->fqdn, oh_len)) { + TRACE_DEBUG(INFO, "Received unexpected '%.*s' Origin-Host in RADIUS State attribute, replacing with '%s'", oh_len, oh, cli->fqdn); + if (strict) + return EINVAL; + } + } + + if (or && or_len) { + if (strncasecmp(or, cli->realm, or_len)) { + TRACE_DEBUG(INFO, "Received unexpected '%.*s' Origin-Realm in RADIUS State attribute, replacing with '%s'", or_len, or, cli->realm); + if (strict) + return EINVAL; + } + } + + *fqdn = cli->fqdn; + *realm= cli->realm; + return 0; } @@ -316,19 +475,17 @@ if (TRACE_BOOL(FULL + 1 )) { char ipstr[INET6_ADDRSTRLEN]; char keydump[KEY_DUMP_BUF_SIZE]; - uint16_t port; + char portstr[8]; + int ret; - if (ip_port->sa_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)ip_port)->sin_addr,ipstr,sizeof(ipstr)); - port = ntohs(((struct sockaddr_in *)ip_port)->sin_port); - } else { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ip_port)->sin6_addr,ipstr,sizeof(ipstr)); - port = ntohs(((struct sockaddr_in6 *)ip_port)->sin6_port); + if (ret = getnameinfo(ip_port, sizeof(struct sockaddr_storage), &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) { + TRACE_DEBUG(INFO, "Error adding client: %s", gai_strerror(ret)); + return EINVAL; } client_key_dump(&keydump[0], *key, keylen); - TRACE_DEBUG(FULL, "Adding client [%s]:%hu with %d bytes key: %s", ipstr, port, keylen, keydump); + TRACE_DEBUG(FULL, "Adding client [%s]:%s with %d bytes key: %s", ipstr, portstr, keylen, keydump); } /* Lock the lists */ @@ -357,27 +514,22 @@ { char ipstr[INET6_ADDRSTRLEN]; char keydump[KEY_DUMP_BUF_SIZE]; - uint16_t port; + char portstr[8]; + int rc; - if (prev->sin->sin_family == AF_INET) { - inet_ntop(AF_INET, &prev->sin->sin_addr,ipstr,sizeof(ipstr)); - port = ntohs(prev->sin->sin_port); + if (rc = getnameinfo(prev->sa, sizeof(struct sockaddr_storage), &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) { + TRACE_DEBUG(INFO, "Previous entry: ERROR (%s)", gai_strerror(rc)); } else { - inet_ntop(AF_INET6, &prev->sin6->sin6_addr,ipstr,sizeof(ipstr)); - port = ntohs(prev->sin6->sin6_port); + client_key_dump(&keydump[0], prev->key.data, prev->key.len); + log_error( "Previous entry: [%s]:%s, key (%db): %s\n", ipstr, portstr, prev->key.len, keydump); } - client_key_dump(&keydump[0], prev->key.data, prev->key.len); - log_error( "Previous entry: [%s]:%hu, key (%db): %s\n", ipstr, port, prev->key.len, keydump); - if (ip_port->sa_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)ip_port)->sin_addr,ipstr,sizeof(ipstr)); - port = ntohs(((struct sockaddr_in *)ip_port)->sin_port); + if (rc = getnameinfo(ip_port, sizeof(struct sockaddr_storage), &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) { + TRACE_DEBUG(INFO, "New entry: ERROR (%s)", gai_strerror(rc)); } else { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ip_port)->sin6_addr,ipstr,sizeof(ipstr)); - port = ntohs(((struct sockaddr_in6 *)ip_port)->sin6_port); + client_key_dump(&keydump[0], *key, keylen); + log_error( "New entry: [%s]:%s, key (%db): %s\n", ipstr, portstr, keylen, keydump); } - client_key_dump(&keydump[0], *key, keylen); - log_error( "New entry: [%s]:%hu, key (%db): %s\n", ipstr, port, keylen, keydump); } } end: @@ -387,14 +539,28 @@ return ret; } -void rgw_clients_dump(void) +static void dump_cli_list(struct rg_list *senti) { struct rgw_client * client = NULL; struct rg_list *ref = NULL; char ipstr[INET6_ADDRSTRLEN]; char keydump[KEY_DUMP_BUF_SIZE]; - uint16_t port; + char portstr[8]; + int rc; + for (ref = senti->next; ref != senti; ref = ref->next) { + client = (struct rgw_client *)ref; + if (rc = getnameinfo(client->sa, sizeof(struct sockaddr_storage), &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) { + TRACE_DEBUG(INFO, "Error dumping entry: %s", gai_strerror(rc)); + continue; + } + client_key_dump(&keydump[0], client->key.data, client->key.len); + log_debug(" [%s]:%s, %d bytes: %s\n", ipstr, portstr, client->key.len, keydump); + } +} + +void rgw_clients_dump(void) +{ if ( ! TRACE_BOOL(FULL) ) return; @@ -402,23 +568,11 @@ if (!rg_list_is_empty(&cli_ip)) log_debug(" RADIUS IP clients list:\n"); - for (ref = cli_ip.next; ref != &cli_ip; ref = ref->next) { - client = (struct rgw_client *)ref; - inet_ntop(AF_INET, &client->sin->sin_addr,ipstr,sizeof(ipstr)); - port = ntohs(client->sin->sin_port); - client_key_dump(&keydump[0], client->key.data, client->key.len); - log_debug(" [%s]:%hu, %d bytes: %s\n", ipstr, port, client->key.len, keydump); - } + dump_cli_list(&cli_ip); if (!rg_list_is_empty(&cli_ip6)) log_debug(" RADIUS IPv6 clients list:\n"); - for (ref = cli_ip6.next; ref != &cli_ip6; ref = ref->next) { - client = (struct rgw_client *)ref; - inet_ntop(AF_INET6, &client->sin6->sin6_addr,ipstr,sizeof(ipstr)); - port = ntohs(client->sin6->sin6_port); - client_key_dump(&keydump[0], client->key.data, client->key.len); - log_debug(" [%s]:%hu, %d bytes: %s\n", ipstr, port, client->key.len, keydump); - } + dump_cli_list(&cli_ip6); CHECK_POSIX_DO( pthread_mutex_unlock(&cli_mtx), /* ignore error */ ); } diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/rgw_extensions.c --- a/extensions/radius_gw/rgw_extensions.c Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/rgw_extensions.c Fri May 29 12:57:43 2009 +0900 @@ -390,6 +390,7 @@ /* destination ip: derived from cli */ /* post-processing: depends on (*rad)->serv_type */ /* message content: rad_ans */ + /* update the duplicate cache in rgw_clients */ /* not implemented */ ASSERT(0); diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/rgw_msg.c --- a/extensions/radius_gw/rgw_msg.c Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/rgw_msg.c Fri May 29 12:57:43 2009 +0900 @@ -224,7 +224,7 @@ } /* Get information on this peer */ - CHECK_FCT( rgw_clients_get_origin(cli, oh, oh_len, or, or_len, &fqdn, &realm) ); + CHECK_FCT( rgw_clients_get_origin(cli, oh, oh_len, or, or_len, &fqdn, &realm, 0) ); /* Create the session object */ if (si_len) { diff -r 43c0e3f67958 -r 1a4902b216f8 extensions/radius_gw/rgw_servers.c --- a/extensions/radius_gw/rgw_servers.c Fri May 29 12:57:22 2009 +0900 +++ b/extensions/radius_gw/rgw_servers.c Fri May 29 12:57:43 2009 +0900 @@ -132,19 +132,14 @@ if (TRACE_BOOL(FULL)) { char ipstr[INET6_ADDRSTRLEN]; + char portstr[8]; + int ret; - switch (from.ss_family) { - case AF_INET: - inet_ntop(AF_INET, &((struct sockaddr_in *)&from)->sin_addr,ipstr,sizeof(ipstr)); - break; - case AF_INET6: - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&from)->sin6_addr,ipstr,sizeof(ipstr)); - break; - default: - snprintf(ipstr,sizeof(ipstr),"(unknown AF:%d)", from.ss_family); + if (ret = getnameinfo((struct sockaddr *)&from, fromlen, &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) { + TRACE_DEBUG(FULL, "Received %d bytes from unknown source: %s", len, gai_strerror(ret)); + } else { + TRACE_DEBUG(FULL, "Received %d bytes from [%s]:%s", len, ipstr, portstr); } - - TRACE_DEBUG(FULL, "Received %d bytes from [%s]:%hu", len, ipstr, ntohs(port)); } /* Search the associated client definition, if any */