view extensions/radius_gw/rgw_extensions.c @ 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 03b512313cc1
children 0bd2a40ca008
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 sub-extensions that provide handlers for RADIUS messages / attributes */

#include "radius_gw.h"
#include <dlfcn.h>
#include <libgen.h>

/* List of the extensions, in the order they are written in the configuration file. */
static struct rg_list ext_list;

/* Description of an extension entry */
struct ext_descr {
	struct rg_list		chain; 	/* chaining in the ext_list list */
	
	void * 			dlo;	/* object returned by dlopen for the extension, to use with dlclose later */
	struct radius_gw_api	api;	/* the callbacks registered by rga_register */
	struct rga_conf_state *	cs;	/* the configuration and state returned by rga_conf_parse_cb */
	
	int			type;	/* this extension is called for messages received on this(these) server(s) only */
	unsigned char *		cc;	/* array of command codes, or NULL for any cc */
	size_t			cc_len; /* size of the previous array */
	
	char *			extname; /* baseame of the extension, for debug messages. To be freed when object is detroyed */
	char * 			conffile; /* configuration file passed to the extension, or "(null)". To be freed when object is destroyed */
};

/* Accelerators for each command code (one for each port). These accelerators are built on-demand, as a cache, after start_cache function has been called.  */
static struct rg_list ext_accel_auth, ext_accel_acct;

/* accelerator list, one per command code value (only the ones actually used) */
struct ext_accel {
	struct rg_list		chain;	/* link in the ext_accel_* list */
	unsigned char		ccode;	/* the command code of this accelerator. The previous list is ordered according to this value. We don't handle extended CC yet */
	struct rg_list		extensions; /* head for the list of extensions to be called for this command code. List of ext_accel_item items */
};

/* accelerator item, references to extensions */
struct ext_accel_item {
	struct rg_list		chain; 	/* link in the ext_accel "extensions" list */
	struct ext_descr *	ext;	/* pointer to the extension data */
};

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

/* Has start_cache been called? */
static int cache_started = 0;


/* The lock must be held before calling this function */
static int get_accelerator(struct rg_list ** list, unsigned char ccode, int type)
{
	struct rg_list *refer, *search;
	struct ext_accel * accel = NULL;
	struct ext_accel_item * item = NULL;
	
	TRACE_ENTRY("%p %hhu %i", list, ccode, type);
	
	CHECK_PARAMS( cache_started && list && ((type == RGW_EXT_TYPE_AUTH) || (type == RGW_EXT_TYPE_ACCT)) );
	
	if (type == RGW_EXT_TYPE_AUTH)
		refer = &ext_accel_auth;
	else
		refer = &ext_accel_acct;
	
	/* Check if we have already an accelerator for this ccode */
	for (search = refer->next; search != refer; search = search->next) {
		struct ext_accel * loc = (struct ext_accel *)search;
		
		if (loc->ccode < ccode)
			continue;
		
		if (loc->ccode > ccode)
			break; /* we don't have an accelerator for this value yet */
		
		/* We found the matching accelerator, just return this list */
		*list = &loc->extensions;
		return 0;
	}
	
	/* We must create the accelerator list, then save it just before "search" */
	
	CHECK_MALLOC( accel = malloc(sizeof(struct ext_accel)) );
	memset(accel, 0, sizeof(struct ext_accel) );
	rg_list_init(&accel->chain);
	rg_list_init(&accel->extensions);
	accel->ccode = ccode;
	
	/* Check each extension from the global list for this port and ccode */
	for (refer = ext_list.next; refer != &ext_list; refer = refer->next) {
		struct ext_descr * loc = (struct ext_descr *)refer;
		
		/* Skip if this extension is not registered for this port */
		if (! (loc->type & type) )
			continue;
		
		/* Check if the ccode is there */
		if (loc->cc) {
			int i;
			int match = 0;
			for (i=0; i< loc->cc_len; i++) {
				if (loc->cc[i] < ccode)
					continue;
				if (loc->cc[i] == ccode)
					match = 1;
				break;
			}
			if (!match)
				continue;
		}
		
		/* Ok, this extension must be called for this port / ccode, add to the accelerator */
		CHECK_MALLOC( item = malloc(sizeof(struct ext_accel_item)) );
		memset(item, 0, sizeof(struct ext_accel_item));
		rg_list_init(&item->chain);
		item->ext = loc;
		/* Add as last element of the accelerator */
		rg_list_insert_before(&accel->extensions, &item->chain);
	}
	
	/* Now, save this accelerator entry in the global list */
	rg_list_insert_before(search, &accel->chain);
	*list = &accel->extensions;
	
	return 0;
}


int rgw_extensions_init(void)
{
	TRACE_ENTRY();
	
	rg_list_init(&ext_list);
	rg_list_init(&ext_accel_auth);
	rg_list_init(&ext_accel_acct);
	
	return 0;
}

int rgw_extensions_add( char * extfile, char * conffile, int type, unsigned char ** codes_array, size_t codes_sz )
{
	struct ext_descr * new;
	int (* ext_rga_register)(int version, waaad_api_t * waaad, struct radius_gw_api * api);
	int ret = 0;
	char * myextfile;
	
	TRACE_ENTRY();
	
	CHECK_PARAMS( extfile && type && codes_array && (cache_started == 0) );
	
	CHECK_MALLOC( myextfile = strdup(extfile) );
	
	CHECK_MALLOC( new = malloc(sizeof(struct ext_descr)) );
	memset(new, 0, sizeof(struct ext_descr));
	
	rg_list_init(&new->chain);
	
	/* Copy names, for debug */
	CHECK_MALLOC( new->extname = strdup(basename(myextfile)) );
	free(myextfile);
	CHECK_MALLOC( new->conffile = conffile ? conffile : strdup("(null)") );
	
	/* Try and load the extension */
	TRACE_DEBUG(FULL, "Loading subextension: %s", extfile);
	new->dlo = dlopen(extfile, RTLD_LAZY | RTLD_LOCAL);
	if (new->dlo == NULL) {
		/* An error occured */
		log_error("Loading of subextension '%s' failed:\n %s\n", extfile, dlerror());
		goto error;
	}
	
	/* Resolve the entry point */
	ext_rga_register = dlsym( new->dlo, "rga_register" );
	if (ext_rga_register == NULL) {
		/* An error occured */
		log_error("Unable to resolve 'rga_register' in subextension '%s':\n %s\n", extfile, dlerror());
		goto error;
	}
	
	/* Call the entry point */
	TRACE_DEBUG(FULL, "Calling subextension entry point...");
	CHECK_FCT_DO(  (*ext_rga_register) (RADIUS_GW_API_VER, waaad_api, &new->api),  goto error  );
	
	/* Now parse the configuration file, this will initialize all extension states and store it in the returned pointer (the subextensions must be re-entrant) */
	TRACE_DEBUG(FULL, "Parsing subext conf file: %s", new->conffile );
	new->cs = (*(new->api.rga_conf_parse_cb))(conffile);
	if (new->cs == NULL) {
		log_error("An error occurred while parsing configuration parameter for extension '%s' (%s), aborting...\n", new->extname, new->conffile);
		goto error;
	}
	
	/* Now sort the array (very naive algorithm, but this list is usually small) of command codes and save */
	if (*codes_array && codes_sz) {
		int i;
		
		new->cc = *codes_array;
		*codes_array = NULL;
		
		for (i = 0; i < codes_sz - 1; i++) {
			int j, idx = i, min = new->cc[i];
			
			/* find the smallest remaining element */
			for (j = i + 1; j < codes_sz; j++) {
				if (min > new->cc[j]) {
					min = new->cc[j];
					idx = j;
				}
			}
			
			/* swap if needed */
			if (idx != i) {
				int tmp = new->cc[i];
				new->cc[i] = new->cc[idx];
				new->cc[idx] = tmp;
			}
		}
		new->cc_len = codes_sz;
	}
		
	new->type = type;
	
	/* And save this new extension in the list */
	CHECK_POSIX( pthread_mutex_lock(&ext_mtx) );
	rg_list_insert_before(&ext_list, &new->chain);
	CHECK_POSIX( pthread_mutex_unlock(&ext_mtx) );
	
	return 0;
	
	
error:
	if (new && new->dlo)
		dlclose(new->dlo);
	if (new)
		free(new);	
	return EINVAL;
}

void rgw_extensions_dump(void)
{
	struct ext_descr * ext;
	struct rg_list * ptr, *ptraccel;
	
	if ( ! TRACE_BOOL(FULL) )
		return;
	
	CHECK_POSIX_DO( pthread_mutex_lock(&ext_mtx), );
	
	if ( ! rg_list_is_empty( &ext_list ) )
		log_debug("  --- List of registered sub-extensions:\n");
	for (ptr = ext_list.next; ptr != &ext_list; ptr = ptr->next) {
		char * codes = NULL;
		
		ext = (struct ext_descr *)ptr;
		if (ext->cc) {
			int i;
			char * nxt;
			
			CHECK_MALLOC_DO( codes = malloc(ext->cc_len * 3 + 1), break );
			nxt = codes;
			for (i = 0; i < ext->cc_len; i++) {
				sprintf(nxt, "%02hhx ", ext->cc[i]);
				nxt += 3;
			}
		}
		
		log_debug("  %-25s ( %-25s ) - types: %s%s, codes: %s\n", 
				ext->extname, 
				basename(ext->conffile),
				ext->type & RGW_EXT_TYPE_AUTH ? "Au" : "  ",
				ext->type & RGW_EXT_TYPE_ACCT ? "Ac" : "  ",
				ext->cc ? codes : "*");
		
		free(codes);
	}
	
	CHECK_POSIX_DO( pthread_mutex_unlock(&ext_mtx), );
	
	/* Dump the list of accelerators */
	if ( ! TRACE_BOOL(FULL + 1) )
		return;
	
	CHECK_POSIX_DO( pthread_mutex_lock(&ext_mtx), );
	if ( !rg_list_is_empty( &ext_accel_auth ) || !rg_list_is_empty( &ext_accel_acct ))
		log_debug("  --- Accelerators:\n");
	
	for (ptraccel = ext_accel_auth.next; ptraccel != &ext_accel_auth; ptraccel = ptraccel->next) {
		struct ext_accel * accel = (struct ext_accel *)ptraccel;
		log_debug("  auth, code %02hhu:\n", accel->ccode);

		for (ptr = accel->extensions.next; ptr != &accel->extensions; ptr = ptr->next) {
			struct ext_accel_item * item = (struct ext_accel_item *)ptr;
			log_debug("     %-15s (%s)\n", item->ext->extname, basename(item->ext->conffile));
		}
	}
	for (ptraccel = ext_accel_acct.next; ptraccel != &ext_accel_acct; ptraccel = ptraccel->next) {
		struct ext_accel * accel = (struct ext_accel *)ptraccel;
		log_debug("  acct, code %02hhu:\n", accel->ccode);

		for (ptr = accel->extensions.next; ptr != &accel->extensions; ptr = ptr->next) {
			struct ext_accel_item * item = (struct ext_accel_item *)ptr;
			log_debug("     %-15s (%s)\n", item->ext->extname, basename(item->ext->conffile));
		}
	}
	
	
	CHECK_POSIX_DO( pthread_mutex_unlock(&ext_mtx), );
	
}

void rgw_extensions_start_cache(void)
{
	cache_started++;
}

int rgw_extensions_loop_req(struct rgw_radius_msg_meta **rad, sess_id_t **session, msg_t **diam_msg, struct rgw_client * cli)
{
	int ret = 0;
	struct rg_list * head = NULL, *li;
	struct radius_msg * rad_ans = NULL;
	
	TRACE_ENTRY("%p %p %p %p", rad, session, diam_msg, cli);
	CHECK_PARAMS( rad && *rad && session && *session && diam_msg && *diam_msg && cli);
	
	/* First, get the list of extensions for this message */
	CHECK_FCT( get_accelerator(&head, (*rad)->radius.hdr->code, (*rad)->serv_type) );
	
	/* Loop in the list of extensions */
	for (li = head->next; li != head; li = li->next) {
		struct ext_descr * ext = ((struct ext_accel_item *) li)->ext;
		
		TRACE_DEBUG(ANNOYING, "Calling next extension: %s", ext->extname);
		ret = (*ext->api.rga_rad_req_cb)(ext->cs, session, &(*rad)->radius, &rad_ans, diam_msg, (void *)cli);
		if (ret)
			break;
	}
	
	/* If no error encountered, we're done here */
	if (ret == 0)
		return 0;
	
	/* Destroy the Diameter temp message, if any */
	if (*diam_msg) {
		CHECK_FCT_DO( msg_free(*diam_msg, 1), );
		*diam_msg = NULL;
	}
	
	/* Destroy the session unless instructed to keep it */
	if (*session && (ret != -2) && (ret != -4)) {
		CHECK_FCT_DO( sess_unlink(*session), );
		*session = NULL;
	}
	
	/* Send the radius message back if required */
	if (((ret == -3) || (ret == -4)) && rad_ans) {
		/* destination port: (*rad)->port */
		/* 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);
	}
	
	if (ret > 0) {
		/* Critical error, log and exit */
		log_error("An error occurred while handling a RADIUS message, turn on DEBUG for details: %s\n", strerror(ret));
		return ret;
	}
	
	/* Now, discard the message and return */
	rgw_msg_free(rad);
	return 0;
	
}

void rgw_extensions_fini(void)
{
	struct rg_list * item, *subitem;
	
	TRACE_ENTRY();
	
	CHECK_POSIX_DO( pthread_mutex_lock(&ext_mtx), );
	
	/* Remove all elements from all accelerators */
	while ( ! rg_list_is_empty(&ext_accel_auth) ) {
		item = ext_accel_auth.next;
		rg_list_unlink(item);
		{
			struct ext_accel * accel = (struct ext_accel *)item;
			while ( ! rg_list_is_empty(&accel->extensions) ) {
				subitem = accel->extensions.next;
				rg_list_unlink(subitem);
				free(subitem);
			}
		}
		free(item);
	}
	while ( ! rg_list_is_empty(&ext_accel_acct) ) {
		item = ext_accel_acct.next;
		rg_list_unlink(item);
		{
			struct ext_accel * accel = (struct ext_accel *)item;
			while ( ! rg_list_is_empty(&accel->extensions) ) {
				subitem = accel->extensions.next;
				rg_list_unlink(subitem);
				free(subitem);
			}
		}
		free(item);
	}
	
	/* Now destroy all extensions */
	while ( ! rg_list_is_empty(&ext_list) ) {
		struct ext_descr * ext = (struct ext_descr *) ext_list.next;
		rg_list_unlink(&ext->chain);
		free(ext->conffile);
		free(ext->extname);
		free(ext->cc);
		if (ext->cs)
			(*ext->api.rga_conf_free_cb)(ext->cs);
		dlclose(ext->dlo);
		free(ext);
	}
	
	CHECK_POSIX_DO( pthread_mutex_unlock(&ext_mtx), );
}
"Welcome to our mercurial repository"