view extensions/radius_gw/rgw_clients.c @ 385:03b512313cc1

Added code to handle sessions
author Sebastien Decugis <sdecugis@nict.go.jp>
date Thu, 28 May 2009 13:32:37 +0900
parents e86dba02630a
children 1a4902b216f8
line wrap: on
line source

/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
*													 *
* Copyright (c) 2009, WIDE Project and NICT								 *
* All rights reserved.											 *
* 													 *
* Redistribution and use of this software in source and binary forms, with or without modification, are  *
* permitted provided that the following conditions are met:						 *
* 													 *
* * Redistributions of source code must retain the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer.										 *
*    													 *
* * Redistributions in binary form must reproduce the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer in the documentation and/or other						 *
*   materials provided with the distribution.								 *
* 													 *
* * Neither the name of the WIDE Project or NICT nor the 						 *
*   names of its contributors may be used to endorse or 						 *
*   promote products derived from this software without 						 *
*   specific prior written permission of WIDE Project and 						 *
*   NICT.												 *
* 													 *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
*********************************************************************************************************/

/* Manage the list of RADIUS clients, along with their shared secrets. */

#include "radius_gw.h"

/* How many bytes of secret keys to dump? */
#define KEY_DUMP_BYTES	16

/* Ordered lists of clients. The order relationship is a memcmp on the address zone. 
   For same addresses, the port is compared.
   A given address cannot be added with a 0-port and another port value.
 */
static struct rg_list cli_ip, cli_ip6;

/* Mutex to protect the previous lists */
static pthread_mutex_t cli_mtx = PTHREAD_MUTEX_INITIALIZER;

/* Structure describing one client */
struct rgw_client {
	/* Link information in global list */
	struct rg_list		chain;
	
	/* Reference count */
	int			refcount;
	
	/* The address and optional port. The head list determines which one to use */
	union {
		struct sockaddr_in	*sin;
		struct sockaddr_in6	*sin6;
	};
	
	/* The secret key data. */
	struct {
		unsigned char * data;
		size_t		len;
	} 			key;
	
	/* information of previous msg received, for duplicate checks. [0] for auth, [1] for acct. */
	struct {
		uint16_t	port;
		uint8_t		id;
	} last[2];
};


/* create a new rgw_client. the arguments are moved into the structure. */
static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen )
{
	struct rgw_client *tmp = NULL;
	
	/* 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);
	*ip_port = NULL;
	
	/* move the key material */
	tmp->key.data = *key;
	tmp->key.len = keylen;
	*key = NULL;
	
	/* Done! */
	*res = tmp;
	return 0;
}


/* Decrease refcount on a client; the lock must be held when this function is called. */
static void client_unlink(struct rgw_client * client)
{
	client->refcount -= 1;
	
	if (client->refcount <= 0) {
		/* to be sure */
		ASSERT( rg_list_is_empty(&client->chain) );
		
		/* Free the data */
		free(client->sin);
		free(client->key.data);
		free(client);
	}
}


/* Function to look for an existing rgw_client, or the previous element. 
   The cli_mtx must be held when calling this func. 
   Returns ENOENT if the matching client does not exist, and res points to the previous element in the list. 
   Returns EEXIST if the matching client is found, and res points to this element. 
   Returns other error code on other error. */
#define client_search_family( _family_ )												\
		case AF_INET##_family_: {												\
			struct sockaddr_in##_family_ * sin##_family_ = (struct sockaddr_in##_family_ *)ip_port;				\
			for (ref = cli_ip##_family_.next; ref != &cli_ip##_family_; ref = ref->next) {					\
				cmp = memcmp(&sin##_family_->sin##_family_##_addr, 							\
					     &((struct rgw_client *)ref)->sin##_family_->sin##_family_##_addr, 				\
					     sizeof(struct in##_family_##_addr));							\
				if (cmp > 0) continue; /* search further in the list */							\
				if (cmp < 0) break; /* this IP is not in the list */							\
				/* Now compare the ports as follow: */									\
				     /* If the ip_port we are searching does not contain a port, just return the first match result */	\
				if ( (sin##_family_->sin##_family_##_port == 0) 							\
				     /* If the entry in the list does not contain a port, return it as a match */			\
				  || (((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port == 0) 				\
				     /* If both ports are equal, it is a match */							\
				  || (sin##_family_->sin##_family_##_port == 								\
				  		((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port)) {			\
					*res = (struct rgw_client *)ref;								\
					return EEXIST;											\
				}													\
				/* Otherwise, the list is ordered by port value (byte order does not matter */				\
				if (sin##_family_->sin##_family_##_port 								\
					> ((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port) continue;			\
				else break;												\
			}														\
			*res = (struct rgw_client *)(ref->prev);									\
			return ENOENT;													\
		}
static int client_search(struct rgw_client ** res, struct sockaddr * ip_port )
{
	int ret = 0;
	int cmp;
	struct rg_list *ref = NULL;
	
	CHECK_PARAMS(res && ip_port);
	
	switch (ip_port->sa_family) {
		client_search_family()
				break;
		
		client_search_family( 6 )
				break;
	}
	
	/* We're never supposed to reach this point */
	ASSERT(0);
	return EINVAL;
}

#define KEY_DUMP_BUF_SIZE (KEY_DUMP_BYTES * 3 + KEY_DUMP_BYTES / 4 + 3)

/* Display the first KEY_DUMP_BYTES bytes of a secret key */
static void client_key_dump(char * keydump /* size: KEY_DUMP_BUF_SIZE */, char * key, size_t keylen)
{
	int i, j, idx;

	memset(keydump, 0, KEY_DUMP_BUF_SIZE);
	idx = 0;

	for (i = 0; i < KEY_DUMP_BYTES / 8; i++) {
		for (j = 0; j < 8; j++) {
			if (idx >= keylen)
				break;

			if (j == 4) {
				*keydump = ' '; keydump++;
			}

			sprintf(keydump, "%02hhX ", key[idx]);
			keydump += 3;
			idx ++;
		}
		if (idx >= keylen)
			break;		
		*keydump = '\t'; keydump++;
	}
	if (keylen > idx) {
		sprintf(keydump, "...");
	}
}


int rgw_clients_getkey(struct rgw_client * cli, unsigned char **key, size_t *key_len)
{
	CHECK_PARAMS( cli && key && key_len );
	*key = cli->key.data;
	*key_len = cli->key.len;
	return 0;
}

int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref)
{
	int ret = 0;
	
	TRACE_ENTRY("%p %p", ip_port, ref);
	
	CHECK_PARAMS(ip_port && ref);
	
	CHECK_POSIX( pthread_mutex_lock(&cli_mtx) );

	ret = client_search(ref, ip_port);
	if (ret == EEXIST) {
		(*ref)->refcount ++;
		ret = 0;
	} else {
		*ref = NULL;
	}
	
	CHECK_POSIX( pthread_mutex_unlock(&cli_mtx) );
	
	return ret;
}

int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli)
{
	int idx;
	
	TRACE_ENTRY("%p %p", msg, cli);
	
	CHECK_PARAMS( msg && cli );
	
	if ((*msg)->serv_type == RGW_EXT_TYPE_AUTH)
		idx = 0;
	else
		idx = 1;
	
	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));
		rgw_msg_free(msg);
	} else {
		/* Update information for future message */
		cli->last[idx].id = (*msg)->radius.hdr->identifier;
		cli->last[idx].port = (*msg)->port;
	}
	
	return 0;
}

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 */
	
	ASSERT(0);
	return ENOTSUP;
}

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)
{
	ASSERT(0);
	return ENOTSUP;
}


void rgw_clients_dispose(struct rgw_client ** ref)
{
	TRACE_ENTRY("%p", ref);
	CHECK_PARAMS_DO(ref, return);
	
	CHECK_POSIX_DO( pthread_mutex_lock(&cli_mtx),  );
	client_unlink(*ref);
	*ref = NULL;
	CHECK_POSIX_DO( pthread_mutex_unlock(&cli_mtx), );
}

int rgw_clients_init(void)
{
	rg_list_init(&cli_ip);
	rg_list_init(&cli_ip6);
	return 0;
}

int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen )
{
	struct rgw_client * prev = NULL, *new = NULL;
	int ret;
	
	TRACE_ENTRY("%p %p %lu", ip_port, key, keylen);
	
	CHECK_PARAMS( ip_port && key && *key && keylen );
	CHECK_PARAMS( (ip_port->sa_family == AF_INET) || (ip_port->sa_family == AF_INET6) );
	
	/* Dump the entry in debug mode */
	if (TRACE_BOOL(FULL + 1 )) {
		char ipstr[INET6_ADDRSTRLEN];
		char keydump[KEY_DUMP_BUF_SIZE];
		uint16_t port;
		
		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);
		}
		
		client_key_dump(&keydump[0], *key, keylen);
		
		TRACE_DEBUG(FULL, "Adding client [%s]:%hu with %d bytes key: %s", ipstr, port, keylen, keydump);
	}
	
	/* Lock the lists */
	CHECK_POSIX( pthread_mutex_lock(&cli_mtx) );
	
	/* Check if the same entry does not already exist */
	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 );
		rg_list_insert_after(&prev->chain, &new->chain);
		new->refcount++;
		ret = 0;
		goto end;
	}
	
	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) )) {
			TRACE_DEBUG(INFO, "Skipping duplicate client description");
			goto end;
		}
		
		TRACE_DEBUG(INFO, "Error: conflicting entries");	
		log_error("Error adding a RADIUS client in conflict with a previous entry.\n");
		{
			char ipstr[INET6_ADDRSTRLEN];
			char keydump[KEY_DUMP_BUF_SIZE];
			uint16_t port;

			if (prev->sin->sin_family == AF_INET) {
				inet_ntop(AF_INET, &prev->sin->sin_addr,ipstr,sizeof(ipstr));
				port = ntohs(prev->sin->sin_port);
			} 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]:%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);
			} 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]:%hu, key (%db): %s\n", ipstr, port, keylen, keydump);			
		}
	}
end:
	/* release the lists */
	CHECK_POSIX( pthread_mutex_unlock(&cli_mtx) );
	
	return ret;
}

void rgw_clients_dump(void)
{
	struct rgw_client * client = NULL;
	struct rg_list *ref = NULL;
	char ipstr[INET6_ADDRSTRLEN];
	char keydump[KEY_DUMP_BUF_SIZE];
	uint16_t port;
	
	if ( ! TRACE_BOOL(FULL) )
		return;
	
	CHECK_POSIX_DO( pthread_mutex_lock(&cli_mtx), /* ignore error */ );
	
	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);
	}
		
	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);
	}
		
	CHECK_POSIX_DO( pthread_mutex_unlock(&cli_mtx), /* ignore error */ );
}

void rgw_clients_fini(void)
{
	struct rg_list * client;
	
	TRACE_ENTRY();
	
	CHECK_POSIX_DO( pthread_mutex_lock(&cli_mtx), /* ignore error */ );
	
	/* empty the lists */
	while ( ! rg_list_is_empty(&cli_ip) ) {
		client = cli_ip.next;
		rg_list_unlink(client);
		client_unlink((struct rgw_client *)client);
	}
	while (! rg_list_is_empty(&cli_ip6)) {
		client = cli_ip6.next;
		rg_list_unlink(client);
		client_unlink((struct rgw_client *)client);
	}
	
	CHECK_POSIX_DO( pthread_mutex_unlock(&cli_mtx), /* ignore error */ );
	
}

"Welcome to our mercurial repository"