Mercurial > hg > freeDiameter
diff extensions/app_radgw/rgw_clients.c @ 516:1c2f5ee38039
Allow RADIUS Proxies with the app_radgw extension
author | Sebastien Decugis <sdecugis@nict.go.jp> |
---|---|
date | Fri, 27 Aug 2010 10:59:51 +0900 |
parents | d4fc98a3b79c |
children | 3f43713be92d |
line wrap: on
line diff
--- a/extensions/app_radgw/rgw_clients.c Thu Aug 26 14:10:03 2010 +0900 +++ b/extensions/app_radgw/rgw_clients.c Fri Aug 27 10:59:51 2010 +0900 @@ -39,6 +39,8 @@ #include "rgw.h" +#define REVERSE_DNS_SIZE_MAX 512 /* length of our buffer for reverse DNS */ + /* Ordered lists of clients. The order relationship is a memcmp on the address zone. For same addresses, the port is compared. The same address cannot be added twice, once with a 0-port and once with another port value. @@ -66,6 +68,7 @@ /* The FQDN, realm, and optional aliases */ int is_local; /* true if the RADIUS client runs on the same host -- we use Diameter Identity in that case */ + enum rgw_cli_type type; /* is it a proxy ? */ char *fqdn; size_t fqdn_len; char *realm; @@ -90,7 +93,7 @@ /* create a new rgw_client. the arguments are moved into the structure (to limit malloc & free calls). */ -static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen ) +static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) { struct rgw_client *tmp = NULL; char buf[255]; @@ -117,6 +120,8 @@ memset(tmp, 0, sizeof(struct rgw_client)); fd_list_init(&tmp->chain, NULL); + tmp->type = type; + if (loc) { tmp->is_local = 1; } else { @@ -230,6 +235,14 @@ return 0; } +int rgw_clients_gettype(struct rgw_client * cli, enum rgw_cli_type *type) +{ + CHECK_PARAMS( cli && type ); + *type = cli->type; + return 0; +} + + int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref) { int ret = 0; @@ -303,15 +316,71 @@ /* 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 */ -/* NOTE: This function will require changes to allow RADIUS Proxy on the path... */ -int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli) +/* NOTE: This function does nothing if the client is a RADIUS Proxy... */ +/* Check if the message has a valid authenticator, and update the meta-data accordingly */ +int rgw_clients_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth) +{ + unsigned char * key; + size_t keylen; + int count; + + TRACE_ENTRY("%p %p %p", msg, cli, req_auth); + + CHECK_PARAMS(msg && cli); + + CHECK_FCT(rgw_clients_getkey(cli, &key, &keylen)); + + count = radius_msg_count_attr(&msg->radius, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0); + if (count > 1) { + TRACE_DEBUG(INFO, "Too many Message-Authenticator attributes (%d), discarding message.", count); + return EINVAL; + } + if (count == 0) { + TRACE_DEBUG(FULL, "Message does not contain a Message-Authenticator attributes."); + msg->valid_mac = 0; + } else { + if (radius_msg_verify_msg_auth( &msg->radius, key, keylen, req_auth )) { + TRACE_DEBUG(INFO, "Invalid Message-Authenticator received, discarding message."); + return EINVAL; + } + msg->valid_mac = 1; + } + + return 0; +} + +static struct dict_object * cache_orig_host = NULL; +static struct dict_object * cache_orig_realm = NULL; +static struct dict_object * cache_route_record = NULL; + +int rgw_clients_init(void) +{ + TRACE_ENTRY(); + CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &cache_orig_host, ENOENT) ); + CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &cache_orig_realm, ENOENT) ); + CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &cache_route_record, ENOENT) ); + return 0; +} + + +/* The following function checks if a RADIUS message contains a valid NAS identifier, and initializes an empty Diameter + message with the appropriate routing information */ +int rgw_clients_create_origin(struct rgw_radius_msg_meta *msg, struct rgw_client * cli, struct msg ** diam) { int idx; + int valid_nas_info = 0; struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL; + char * oh_str = NULL; + char * or_str = NULL; + char * rr_str = NULL; + char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */ - TRACE_ENTRY("%p %p", msg, cli); - CHECK_PARAMS(msg && cli && !msg->valid_nas_info ); - + struct avp *avp = NULL; + union avp_value avp_val; + + TRACE_ENTRY("%p %p %p", msg, cli, diam); + CHECK_PARAMS(msg && cli && diam && (*diam == NULL)); + /* 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]); @@ -335,57 +404,111 @@ if (!nas_ip && !nas_ip6 && !nas_id) { TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute."); - goto end; + + /* Get information on this peer */ + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); + + goto diameter; } /* 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; + 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; + valid_nas_info |= 2; } - /* 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)) { - /* - In RADIUS it would be possible for a rogue NAS to forge the NAS-IP- - Address attribute value. Diameter/RADIUS translation agents MUST - check a received NAS-IP-Address or NAS-IPv6-Address attribute against - the source address of the RADIUS packet. If they do not match and - the Diameter/RADIUS translation agent does not know whether the - packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State - attribute), then by default it is assumed that the source address - corresponds to a RADIUS proxy, and that the NAS Address is behind - that proxy, potentially with some additional RADIUS proxies in - between. The Diameter/RADIUS translation agent MUST insert entries - in the Route-Record AVP corresponding to the apparent route. This - implies doing a reverse lookup on the source address and NAS-IP- - Address or NAS-IPv6-Address attributes to determine the corresponding - FQDNs. + + /* + In RADIUS it would be possible for a rogue NAS to forge the NAS-IP- + Address attribute value. Diameter/RADIUS translation agents MUST + check a received NAS-IP-Address or NAS-IPv6-Address attribute against + the source address of the RADIUS packet. If they do not match and + the Diameter/RADIUS translation agent does not know whether the + packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State + attribute), then by default it is assumed that the source address + corresponds to a RADIUS proxy, and that the NAS Address is behind + that proxy, potentially with some additional RADIUS proxies in + between. The Diameter/RADIUS translation agent MUST insert entries + in the Route-Record AVP corresponding to the apparent route. This + implies doing a reverse lookup on the source address and NAS-IP- + Address or NAS-IPv6-Address attributes to determine the corresponding + FQDNs. + + If the source address and the NAS-IP-Address or NAS-IPv6-Address do + not match, and the Diameter/RADIUS translation agent knows that it is + talking directly to the NAS (e.g., there are no RADIUS proxies + between it and the NAS), then the error should be logged, and the + packet MUST be discarded. - If the source address and the NAS-IP-Address or NAS-IPv6-Address do - not match, and the Diameter/RADIUS translation agent knows that it is - talking directly to the NAS (e.g., there are no RADIUS proxies - between it and the NAS), then the error should be logged, and the - packet MUST be discarded. - - Diameter agents and servers MUST check whether the NAS-IP-Address AVP - corresponds to an entry in the Route-Record AVP. This is done by - doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve - the corresponding FQDN, and by checking for a match with the Route- - Record AVP. If no match is found, then an error is logged, but no - other action is taken. - */ - TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different from the sender's. Discarding..."); - return ENOTSUP; + Diameter agents and servers MUST check whether the NAS-IP-Address AVP + corresponds to an entry in the Route-Record AVP. This is done by + doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve + the corresponding FQDN, and by checking for a match with the Route- + Record AVP. If no match is found, then an error is logged, but no + other action is taken. + */ + if (nas_ip || nas_ip6) { + if (!valid_nas_info) { + if (cli->type == RGW_CLI_NAS) { + TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different \nfrom the sender's. Please configure as Proxy if this is expected.\n Message discarded."); + return EINVAL; + } else { + /* the peer is configured as a proxy, so accept the message */ + sSS ss; + + /* In that case, the cli will be stored as Route-Record and the NAS-IP-Address as origin */ + if (!cli->is_local) { + rr_str = cli->fqdn; + } + + /* We must DNS-reverse the NAS-IP*-Address */ + memset(&ss, 0 , sizeof(sSS)); + if (nas_ip) { + sSA4 * sin = (sSA4 *)&ss; + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr, nas_ip + 1, sizeof(struct in_addr)); + } else { + sSA6 * sin6 = (sSA6 *)&ss; + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, nas_ip6 + 1, sizeof(struct in6_addr)); + } + CHECK_SYS_DO( getnameinfo( (sSA *)&ss, sSAlen(&ss), &buf[0], sizeof(buf), NULL, 0, NI_NAMEREQD), + { + TRACE_DEBUG(INFO, "The NAS-IP*-Address cannot be DNS reversed in order to create the Origin-Host AVP; rejecting the message (translation is impossible)."); + return EINVAL; + } ); + + oh_str = &buf[0]; + or_str = strchr(oh_str, '.'); + if (or_str) { + or_str ++; /* move after the first dot */ + if (*or_str == '\0') + or_str = NULL; /* Discard this realm, we will use the local realm later */ + } + } + } else { + /* The attribute matches the source address, just use this in origin-host */ + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); + } + + goto diameter; /* we ignore the nas_id in that case */ } - /* Now check the nas_id, but only for non-local hosts */ - if (nas_id && (! cli->is_local) ) { - char * str; + /* We don't have a NAS-IP*-Address attribute if we are here */ + if (cli->is_local) { + /* Simple: we use our own configuration */ + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); + goto diameter; + } + + /* At this point, we only have nas_id, and the client is not local */ + ASSERT(nas_id); + + { int found, ret; struct addrinfo hint, *res, *ptr; @@ -409,7 +532,7 @@ /* first, check if the nas_id is the fqdn of the peer or a known alias */ if ((cli->fqdn_len == (nas_id->length - sizeof(struct radius_attr_hdr))) && (!strncasecmp((char *)(nas_id + 1), cli->fqdn, nas_id->length - sizeof(struct radius_attr_hdr)))) { - TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the NAS"); + TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the client"); found = 1; } else { for (idx = 0; idx < cli->aliases_nb; idx++) { @@ -423,52 +546,106 @@ } if (found) { - msg->valid_nas_info |= 2; - goto end; + /* The NAS-Identifier matches the source IP */ + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); + + goto diameter; } - /* copy the identifier, we try to DNS resolve it */ - 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'; + /* Attempt DNS resolution of the identifier */ + ASSERT( nas_id->length - sizeof(struct radius_attr_hdr) < sizeof(buf) ); + memcpy(buf, nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); + buf[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0'; /* 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); + ret = getaddrinfo(buf, NULL, &hint, &res); if (ret == 0) { - /* The name was resolved correctly, it must match the IP of the client: */ + strncpy(buf, res->ai_canonname, sizeof(buf)); + /* The name was resolved correctly, does it match the IP of the client? */ for (ptr = res; ptr != NULL; ptr = ptr->ai_next) { if (cli->sa->sa_family != ptr->ai_family) continue; if (memcmp(cli->sa, ptr->ai_addr, sSAlen(cli->sa))) continue; - /* It matches: the alias is valid */ found = 1; break; } freeaddrinfo(res); if (!found) { - TRACE_DEBUG(INFO, "The NAS-Identifier value '%s' resolves to a different IP from the NAS's, discarding the message.", str); - free(str); - return EINVAL; + if (cli->type == RGW_CLI_NAS) { + TRACE_DEBUG(INFO, "The NAS-Identifier value '%.*s' resolves to a different IP than the client's, discarding the message. \nConfigure this client as a Proxy if this message should be valid.", + nas_id->length - sizeof(struct radius_attr_hdr), nas_id + 1); + return EINVAL; + } else { + /* This identifier matches a different IP, assume it is a proxied message */ + if (!cli->is_local) { + rr_str = cli->fqdn; + } + oh_str = &buf[0]; /* The canonname resolved */ + or_str = strchr(oh_str, '.'); + if (or_str) { + or_str ++; /* move after the first dot */ + if (*or_str == '\0') + or_str = NULL; /* Discard this realm, we will use the local realm later */ + } + } + } else { + /* It is a valid alias, save it */ + CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(char *)) ); + CHECK_MALLOC( cli->aliases[cli->aliases_nb + 1] = malloc( 1 + nas_id->length - sizeof(struct radius_attr_hdr) )); + memcpy( cli->aliases[cli->aliases_nb + 1], nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); + *(cli->aliases[cli->aliases_nb + 1] + nas_id->length - sizeof(struct radius_attr_hdr)) = '\0'; + cli->aliases_nb ++; + TRACE_DEBUG(FULL, "Saved valid alias for client: '%s' -> '%s'", cli->aliases[cli->aliases_nb + 1], cli->fqdn); + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); } } else { /* Error resolving the name */ - TRACE_DEBUG(INFO, "Error while resolving NAS-Identifier value '%s': %s. Ignoring...", str, gai_strerror(ret)); + TRACE_DEBUG(INFO, "NAS-Identifier '%s' cannot be resolved: %s. Ignoring...", buf, gai_strerror(ret)); + /* Assume this is a valid identifier for the client */ + CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); } - - /* It is a valid alias, save it */ - 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: + + /* Now, let's create the empty Diameter message with Origin-Host, -Realm, and Route-Record if needed. */ +diameter: + ASSERT(oh_str); /* If it is not defined here, there is a bug... */ + if (!or_str) + or_str = fd_g_config->cnf_diamrlm; /* Use local realm in that case */ + + /* Create an empty Diameter message so that extensions can store their AVPs */ + CHECK_FCT( fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam ) ); + + /* Add the Origin-Host as next AVP */ + CHECK_FCT( fd_msg_avp_new ( cache_orig_host, 0, &avp ) ); + memset(&avp_val, 0, sizeof(avp_val)); + avp_val.os.data = (unsigned char *)oh_str; + avp_val.os.len = strlen(oh_str); + CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); + CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); + + /* Add the Origin-Realm as next AVP */ + CHECK_FCT( fd_msg_avp_new ( cache_orig_realm, 0, &avp ) ); + memset(&avp_val, 0, sizeof(avp_val)); + avp_val.os.data = (unsigned char *)or_str; + avp_val.os.len = strlen(or_str); + CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); + CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); + + if (rr_str) { + CHECK_FCT( fd_msg_avp_new ( cache_route_record, 0, &avp ) ); + memset(&avp_val, 0, sizeof(avp_val)); + avp_val.os.data = (unsigned char *)rr_str; + avp_val.os.len = strlen(rr_str); + CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); + CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); + } + + /* Done! */ return 0; } @@ -507,7 +684,7 @@ CHECK_POSIX_DO( pthread_mutex_unlock(&cli_mtx), ); } -int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen ) +int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) { struct rgw_client * prev = NULL, *new = NULL; int ret; @@ -516,10 +693,11 @@ CHECK_PARAMS( ip_port && key && *key && keylen ); CHECK_PARAMS( (ip_port->sa_family == AF_INET) || (ip_port->sa_family == AF_INET6) ); + CHECK_PARAMS( (type == RGW_CLI_NAS) || (type == RGW_CLI_PXY) ); /* Dump the entry in debug mode */ if (TRACE_BOOL(FULL + 1 )) { - TRACE_DEBUG(FULL, "Adding client:"); + TRACE_DEBUG(FULL, "Adding %s:", (type == RGW_CLI_NAS) ? "NAS" : "PROXY" ); TRACE_DEBUG_sSA(FULL, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); TRACE_DEBUG_BUFFER(FULL, "\tKey: [", *key, keylen, "]" ); } @@ -531,7 +709,7 @@ ret = client_search(&prev, ip_port ); if (ret == ENOENT) { /* No duplicate found, Ok to add */ - CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen ), goto end ); + CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen, type ), goto end ); fd_list_insert_after(&prev->chain, &new->chain); new->refcount++; ret = 0; @@ -540,17 +718,17 @@ if (ret == EEXIST) { /* Check if the key is the same, then skip or return an error */ - if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) )) { + if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) ) && (type == prev->type)) { TRACE_DEBUG(INFO, "Skipping duplicate client description"); ret = 0; goto end; } fd_log_debug("ERROR: Conflicting RADIUS clients descriptions!\n"); - TRACE_DEBUG(NONE, "Previous entry:"); + TRACE_DEBUG(NONE, "Previous entry: %s", (prev->type == RGW_CLI_NAS) ? "NAS" : "PROXY"); TRACE_DEBUG_sSA(NONE, "\tIP : ", prev->sa, NI_NUMERICHOST | NI_NUMERICSERV, "" ); TRACE_DEBUG_BUFFER(NONE, "\tKey: [", prev->key.data, prev->key.len, "]" ); - TRACE_DEBUG(NONE, "Conflicting entry:"); + TRACE_DEBUG(NONE, "Conflicting entry: %s", (type == RGW_CLI_NAS) ? "NAS" : "PROXY"); TRACE_DEBUG_sSA(NONE, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); TRACE_DEBUG_BUFFER(NONE, "\tKey: [", *key, keylen, "]" ); }