changeset 388:1a4902b216f8

Improved initial handling of RADIUS messages
author Sebastien Decugis <sdecugis@nict.go.jp>
date Fri, 29 May 2009 12:57:43 +0900
parents 43c0e3f67958
children 0bd2a40ca008
files extensions/radius_gw/radius_gw.h extensions/radius_gw/rg_common.h extensions/radius_gw/rgw_clients.c extensions/radius_gw/rgw_extensions.c extensions/radius_gw/rgw_msg.c extensions/radius_gw/rgw_servers.c
diffstat 6 files changed, 227 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- 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);
--- 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 <errno.h>
 #include <stdint.h>
 #include <dlfcn.h>
-
+#include <sys/socket.h>
+#include <netdb.h>
 
 /* This should be overwritten before including this file */
 #ifndef DEFINE_DEBUG_MACRO
--- 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 */ );
 }
--- 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);
--- 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) {
--- 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 */
"Welcome to our mercurial repository"