diff extensions/app_radgw/rgw_clients.c @ 706:4ffbc9f1e922

Large UNTESTED commit with the following changes: * Improved DiameterIdentity handling (esp. interationalization issues), and improve efficiency of some string operations in peers, sessions, and dictionary modules (closes #7) * Cleanup in the session module to free only unreferenced sessions (#16) * Removed fd_cpu_flush_cache(), replaced by more robust alternatives. * Improved peer state machine algorithm to counter SCTP multistream race condition.
author Sebastien Decugis <sdecugis@nict.go.jp>
date Wed, 09 Feb 2011 15:26:58 +0900
parents 25440e53a48e
children 571b3abaa5df
line wrap: on
line diff
--- a/extensions/app_radgw/rgw_clients.c	Mon Jan 31 17:22:21 2011 +0900
+++ b/extensions/app_radgw/rgw_clients.c	Wed Feb 09 15:26:58 2011 +0900
@@ -85,10 +85,14 @@
 	/* 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;
+	DiamId_t		 fqdn; /* malloc'd here */
 	size_t			 fqdn_len;
-	char			*realm;
-	char			**aliases;
+	DiamId_t		 realm; /* references another string, do not free */
+	size_t			 realm_len;
+	struct {
+		os0_t		 name;
+		size_t		 len;
+	}			*aliases; /* Received aliases */
 	size_t			 aliases_nb;
 	
 	/* The secret key data. */
@@ -209,7 +213,8 @@
 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];
+	DiamId_t fqdn;
+	size_t fqdn_len;
 	int ret, i;
 	int loc = 0;
 	
@@ -219,6 +224,7 @@
 		/* The client is local */
 		loc = 1;
 	} else {
+		char buf[255];
 	
 		/* Search FQDN for the client */
 		ret = getnameinfo( *ip_port, sizeof(struct sockaddr_storage), &buf[0], sizeof(buf), NULL, 0, 0 );
@@ -226,6 +232,12 @@
 			TRACE_DEBUG(INFO, "Unable to resolve peer name: %s", gai_strerror(ret));
 			return EINVAL;
 		}
+		fqdn = &buf[0];
+		CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&fqdn, &fqdn_len, 1),
+			{
+				TRACE_DEBUG(INFO, "Unable to use resolved peer name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+				return ret;
+			} );
 	}
 	
 	/* Create the new object */
@@ -245,14 +257,19 @@
 		tmp->is_local = 1;
 	} else {
 		/* Copy the fqdn */
-		CHECK_MALLOC( tmp->fqdn = strdup(buf) );
-		tmp->fqdn_len = strlen(tmp->fqdn);
+		tmp->fqdn = fqdn;
+		tmp->fqdn_len = fqdn_len;
+
 		/* Find an appropriate realm */
-		tmp->realm = strchr(tmp->fqdn, '.');
-		if (tmp->realm)
+		tmp->realm = strchr(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_len = tmp->fqdn_len - (tmp->realm - fqdn);
+		}
+		if ((!tmp->realm) || (*tmp->realm == '\0')) { /* in case the fqdn was "localhost." for example, if it is possible... */
 			tmp->realm = fd_g_config->cnf_diamrlm;
+			tmp->realm_len = fd_g_config->cnf_diamrlm_len;
+		}
 	}
 	
 	/* move the sa info reference */
@@ -281,7 +298,7 @@
 		
 		/* Free the data */
 		for (idx = 0; idx < client->aliases_nb; idx++)
-			free(client->aliases[idx]);
+			free(client->aliases[idx].name);
 		free(client->aliases);
 		free(client->fqdn);
 		free(client->sa);
@@ -531,9 +548,10 @@
 	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;
+	size_t nas_id_len;
+	char * oh_str = NULL; size_t oh_strlen; int oh_free = 0;
+	char * or_str = NULL; size_t or_strlen;
+	char * rr_str = NULL; size_t rr_strlen;
 	char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */
 	
 	struct avp *avp = NULL;
@@ -554,6 +572,7 @@
 			
 		if ((attr->type == RADIUS_ATTR_NAS_IDENTIFIER) && (attr_len > 0)) {
 			nas_id = attr;
+			nas_id_len = attr_len;
 			continue;
 		}
 			
@@ -567,7 +586,7 @@
 		TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute.");
 		
 		/* Get information on this peer */
-		CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) );
+		CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 		
 		goto diameter;
 	}
@@ -618,12 +637,14 @@
 				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 {
+				int ret;
+				sSS ss;
 				/* the peer is configured as a proxy, or running on localhost, 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;
+					rr_strlen = cli->fqdn_len;
 				}
 				
 				/* We must DNS-reverse the NAS-IP*-Address */
@@ -640,25 +661,39 @@
 				CHECK_SYS_DO( getnameinfo( (sSA *)&ss, sSAlen(&ss), &buf[0], sizeof(buf), NULL, 0, NI_NAMEREQD),
 					{
 						if (cli->is_local) {
-							CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) );
+							CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 							goto diameter;
 						}
 						
 						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];
+				CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&oh_str, &oh_strlen, 1),
+					{
+						if (cli->is_local) {
+							CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+							goto diameter;
+						}
+						
+						TRACE_DEBUG(INFO, "Unable to use resolved client name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+						return ret;
+					} );
+				oh_free = 1;
 				
-				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
+						or_strlen = oh_strlen - (or_str - oh_str);
 				}
 			}
 		} else {
 			/* The attribute matches the source address, just use this in origin-host */
-			CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) );
+			CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 		}
 		
 		goto diameter; /* we ignore the nas_id in that case */
@@ -667,7 +702,7 @@
 	/* 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) );
+		CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 		goto diameter;
 	}
 	
@@ -696,14 +731,14 @@
 		*/
 		
 		/* 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)))) {
+		if (!fd_os_almostcasecmp(nas_id + 1, nas_id_len, 
+						cli->fqdn, cli->fqdn_len)) {
 			TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the client");
 			found = 1;
 		} else {
 			for (idx = 0; idx < cli->aliases_nb; idx++) {
-				if (((nas_id->length - sizeof(struct radius_attr_hdr)) == strlen(cli->aliases[idx])) 
-				&& (!strncasecmp((char *)(nas_id + 1), cli->aliases[idx], nas_id->length - sizeof(struct radius_attr_hdr)))) {
+				if (!fd_os_cmp(nas_id + 1, nas_id_len, 
+						cli->aliases[idx].name, cli->aliases[idx].len)) {
 					TRACE_DEBUG(FULL, "NAS-Identifier valid value found in the cache");
 					found = 1;
 					break;
@@ -713,14 +748,14 @@
 		
 		if (found) {
 			/* The NAS-Identifier matches the source IP */
-			CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) );
+			CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 
 			goto diameter;
 		}
 		
 		/* 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));
+		ASSERT( nas_id_len < sizeof(buf) );
+		memcpy(buf, nas_id + 1, nas_id_len);
 		buf[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0';
 		
 		/* Now check if this alias is valid for this peer */
@@ -744,44 +779,56 @@
 			if (!found) {
 				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);
+						nas_id_len, 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;
+						rr_strlen = cli->fqdn_len;
 					}
 					oh_str = &buf[0]; /* The canonname resolved */
+					CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&oh_str, &oh_strlen, 1),
+						{
+							TRACE_DEBUG(INFO, "Unable to use resolved client name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+							return ret;
+						} );
+					oh_free = 1;
 					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
+							or_strlen = oh_strlen - (or_str - oh_str);
 					}
 				}
 			} 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';
+				CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(cli->aliases[0])) );
+				
+				CHECK_MALLOC( cli->aliases[cli->aliases_nb + 1].name = os0dup(nas_id + 1, nas_id_len ) );
+				cli->aliases[cli->aliases_nb + 1].len = nas_id_len;
+
 				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) );
+				TRACE_DEBUG(FULL, "Saved valid alias for client: '%.*s' -> '%s'", nas_id_len, nas_id + 1, cli->fqdn);
+				CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 			}
 		} else {
 			/* Error resolving the name */
 			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) );
+			CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
 		}
 	}
 	
 	/* 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)
+	if (!or_str) {
 		or_str = fd_g_config->cnf_diamrlm; /* Use local realm in that case */
+		or_strlen = fd_g_config->cnf_diamrlm_len;
+	}
 	
 	/* Create an empty Diameter message so that extensions can store their AVPs */
 	CHECK_FCT(  fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam )  );
@@ -790,7 +837,7 @@
 	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);
+	avp_val.os.len = oh_strlen;
 	CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
 	CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
 	
@@ -798,7 +845,7 @@
 	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);
+	avp_val.os.len = or_strlen;
 	CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
 	CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
 	
@@ -806,28 +853,37 @@
 		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);
+		avp_val.os.len = rr_strlen;
 		CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
 		CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
 	}
 	
+	if (oh_free)
+		free(oh_str);
+	
 	/* Done! */
 	return 0;
 }
 
-int rgw_clients_get_origin(struct rgw_client *cli, char **fqdn, char **realm)
+int rgw_clients_get_origin(struct rgw_client *cli, DiamId_t *fqdn, size_t *fqdnlen, DiamId_t *realm, size_t *realmlen)
 {
-	TRACE_ENTRY("%p %p %p", cli, fqdn, realm);
-	CHECK_PARAMS(cli && fqdn);
+	TRACE_ENTRY("%p %p %p %p %p", cli, fqdn, fqdnlen, realm, realmlen);
+	CHECK_PARAMS(cli && fqdn && fqdnlen);
 	
 	if (cli->is_local) {
 		*fqdn = fd_g_config->cnf_diamid;
+		*fqdnlen = fd_g_config->cnf_diamid_len;
 		if (realm)
 			*realm= fd_g_config->cnf_diamrlm;
+		if (realmlen)
+			*realmlen= fd_g_config->cnf_diamrlm_len;
 	} else {
 		*fqdn = cli->fqdn;
+		*fqdnlen = cli->fqdn_len;
 		if (realm)
 			*realm= cli->realm;
+		if (realmlen)
+			*realmlen= cli->realm_len;
 	}
 		
 	return 0;
"Welcome to our mercurial repository"