view extensions/app_radgw/rgw_plugins.c @ 1327:82b386714795

Set callback data also when only setting expire callback (and not answer callback as well). It is used when calling the expire callback, so not setting it makes no sense.
author Thomas Klausner <tk@giga.or.at>
date Mon, 27 Nov 2017 15:21:20 +0100
parents 1af09cc156d6
children
line wrap: on
line source

/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
*													 *
* Copyright (c) 2013, 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 plugins that provide handlers for RADIUS messages and attributes */

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

/* List of plugins, in the order they appear in the configuration file. */
static struct fd_list plg_list = FD_LIST_INITIALIZER(plg_list);

/* A plugin entry */
struct plg_descr {
	struct fd_list		 chain; 	/* chaining in plg_list */
	
	void 			*dlo;		/* pointer returned by dlopen for the extension, to use with dlclose later */
	struct rgw_api		*descriptor;	/* Points to the resolved plugin's rgwp_descriptor */
	struct rgwp_config 	*cs;		/* the (private) state returned by rgwp_conf_parse */
	
	int			 type;		/* this extension is called for messages received on this(these) server port(s) only */
	unsigned char 		*cc;		/* array of command codes, or NULL for all cc */
	size_t			 cc_len; 	/* size of the previous array */
};

/* 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 fd_list plg_accel_auth = FD_LIST_INITIALIZER(plg_accel_auth);
static struct fd_list plg_accel_acct = FD_LIST_INITIALIZER(plg_accel_acct);

/* Accelerator list item, one per command code value (only the ones actually used) */
struct plg_accel {
	struct fd_list		chain;	/* link in the plg_accel_* list. List ordered by ccode. */
	unsigned char		ccode;	/* the command code for this accelerator. We don't handle extended CC yet */
	struct fd_list		plugins;/* head for the list of plg_accel_item, corresponding to the extensions to be called for this command code. */
};

/* Accelerator item */
struct plg_accel_item {
	struct fd_list		chain; 	/* link in the plg_accel "plugins" list */
	struct plg_descr *	plg;	/* pointer to the plugin */
	/* Note: we can further optimize by caching the location of plg->descriptor->rgwp_rad_req etc... at this level. */
};

/* RWlock to protect all the previous lists */
static pthread_rwlock_t plg_lock = PTHREAD_RWLOCK_INITIALIZER;

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


/* The read lock must be held before calling this function */
static int get_accelerator(struct fd_list ** list, unsigned char ccode, int type)
{
	struct fd_list *refer, *search;
	struct plg_accel * accel = NULL;
	struct plg_accel_item * item = NULL;
	int upgraded = 0;
	
	TRACE_ENTRY("%p %hhu %i", list, ccode, type);
	
	CHECK_PARAMS( cache_started && list && ((type == RGW_PLG_TYPE_AUTH) || (type == RGW_PLG_TYPE_ACCT)) );
	
	if (type == RGW_PLG_TYPE_AUTH)
		refer = &plg_accel_auth;
	else
		refer = &plg_accel_acct;
restart:	
	/* Check if we have already an accelerator for this ccode */
	for (search = refer->next; search != refer; search = search->next) {
		struct plg_accel * loc = (struct plg_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->plugins;
		return 0;
	}
	
	/* We must create the accelerator list, then save it just before "search" */
	
	/* First , upgrade the lock to write lock, and restart the search. This is the only robust solution to avoid deadlocks */
	if (! upgraded) {
		CHECK_POSIX( pthread_rwlock_unlock(&plg_lock) );
		CHECK_POSIX( pthread_rwlock_wrlock(&plg_lock) );
		upgraded = 1;
		goto restart;
	}
	
	/* Now create the new element */
	CHECK_MALLOC( accel = malloc(sizeof(struct plg_accel)) );
	memset(accel, 0, sizeof(struct plg_accel) );
	fd_list_init(&accel->chain, NULL);
	fd_list_init(&accel->plugins, accel);
	accel->ccode = ccode;
	
	/* Check each extension from the global list for this port and ccode */
	for (refer = plg_list.next; refer != &plg_list; refer = refer->next) {
		struct plg_descr * loc = (struct plg_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 plg_accel_item)) );
		memset(item, 0, sizeof(struct plg_accel_item));
		fd_list_init(&item->chain, NULL);
		item->plg = loc;
		/* Add as last element of the accelerator */
		fd_list_insert_before(&accel->plugins, &item->chain);
	}
	
	/* Now, save this accelerator entry in the global list */
	fd_list_insert_before(search, &accel->chain);
	*list = &accel->plugins;
	
	return 0;
}


int rgw_plg_add( char * plgfile, char * conffile, int type, unsigned char ** codes_array, size_t codes_sz )
{
	struct plg_descr * new;
	
	TRACE_ENTRY("%p %p %d %p %zi", plgfile, conffile, type, codes_array, codes_sz);
	
	CHECK_PARAMS( plgfile && type && codes_array && (cache_started == 0) );
	
	CHECK_MALLOC( new = malloc(sizeof(struct plg_descr)) );
	memset(new, 0, sizeof(struct plg_descr));
	
	fd_list_init(&new->chain, new);
	
	/* Try and load the plugin */
	TRACE_DEBUG(FULL, "Loading plugin: %s", plgfile);
	new->dlo = dlopen(plgfile, RTLD_NOW | RTLD_GLOBAL);
	if (new->dlo == NULL) {
		/* An error occured */
		fd_log_debug("Loading of plugin '%s' failed: %s", plgfile, dlerror());
		goto error;
	}
	
	/* Resolve the descriptor */
	new->descriptor = dlsym( new->dlo, "rgwp_descriptor" );
	if (new->descriptor == NULL) {
		/* An error occured */
		fd_log_debug("Unable to resolve 'rgwp_descriptor' in plugin '%s': %s", plgfile, dlerror());
		goto error;
	}
	
	TRACE_DEBUG(FULL, "Plugin '%s' found in file '%s'", new->descriptor->rgwp_name, plgfile);
	
	/* Now parse the configuration file, this will initialize all plugin states and store it in the "cs" pointer (the plugin must be re-entrant, so no global state) */
	if (new->descriptor->rgwp_conf_parse) {
		CHECK_FCT_DO( (*(new->descriptor->rgwp_conf_parse))(conffile, &new->cs), 
			{
				fd_log_debug("An error occurred while parsing configuration file '%s' in plugin '%s', aborting...", conffile, plgfile);
				goto error;
			} );
	}
	
	/* Now sort the array (very simple 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. We don't need to lock at this point because we are single threaded. */
	fd_list_insert_before(&plg_list, &new->chain);
	
	return 0;
	
	
error:
	if (new && new->dlo)
		dlclose(new->dlo);
	if (new)
		free(new);	
	return EINVAL;
}

void rgw_plg_dump(void)
{
	struct plg_descr * plg;
	struct fd_list * ptr, *ptraccel;
	
	if ( ! TRACE_BOOL(FULL) )
		return;
	
	CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
	
	if ( ! FD_IS_LIST_EMPTY( &plg_list ) )
		fd_log_debug("[app_radgw]  --- List of registered plugins:");
	for (ptr = plg_list.next; ptr != &plg_list; ptr = ptr->next) {
		char buf[1024];
		plg = (struct plg_descr *)ptr;
		
		snprintf(buf, sizeof(buf), "  %-25s ( %p ) - types: %s%s, codes: ", 
			 plg->descriptor->rgwp_name, 
			 plg->cs,
			 plg->type & RGW_PLG_TYPE_AUTH ? "Au" : "  ",
			 plg->type & RGW_PLG_TYPE_ACCT ? "Ac" : "  ");
		
		if (plg->cc) {
			int i;
			
			for (i = 0; i < plg->cc_len; i++) {
				snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx ", plg->cc[i]);
			}
			fd_log_debug("%s", buf);
		} else {
			fd_log_debug("%s*", buf);
		}
	}
	
	CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
	
	/* Dump the list of accelerators */
	if ( ! TRACE_BOOL(FULL + 1) )
		return;
	
	CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
	if ( !FD_IS_LIST_EMPTY( &plg_accel_auth ) || !FD_IS_LIST_EMPTY( &plg_accel_acct ))
		fd_log_debug("  --- Accelerators:");
	
	for (ptraccel = plg_accel_auth.next; ptraccel != &plg_accel_auth; ptraccel = ptraccel->next) {
		struct plg_accel * accel = (struct plg_accel *)ptraccel;
		fd_log_debug("  auth, code %02hhu:", accel->ccode);

		for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
			struct plg_accel_item * item = (struct plg_accel_item *)ptr;
			fd_log_debug("     %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
		}
	}
	for (ptraccel = plg_accel_acct.next; ptraccel != &plg_accel_acct; ptraccel = ptraccel->next) {
		struct plg_accel * accel = (struct plg_accel *)ptraccel;
		fd_log_debug("  acct, code %02hhu:", accel->ccode);

		for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
			struct plg_accel_item * item = (struct plg_accel_item *)ptr;
			fd_log_debug("     %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
		}
	}
	
	
	CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
	
}

void rgw_plg_start_cache(void)
{
	cache_started++;
}

int rgw_plg_loop_req(struct rgw_radius_msg_meta **rad, struct msg **diam_msg, struct rgw_client * cli)
{
	int ret = 0;
	struct fd_list * head = NULL, *li;
	struct radius_msg * rad_ans = NULL;
	
	TRACE_ENTRY("%p %p %p", rad, diam_msg, cli);
	CHECK_PARAMS( rad && *rad && diam_msg && *diam_msg && cli);
	
	/* First, get the list of extensions for this message */
	CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
	CHECK_FCT_DO( ret = get_accelerator(&head, (*rad)->radius.hdr->code, (*rad)->serv_type), 
		{ CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
	
	/* Loop in the list of extensions */
	for (li = head->next; li != head; li = li->next) {
		struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
		
		if (plg->descriptor->rgwp_rad_req) {
			TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
			ret = (*plg->descriptor->rgwp_rad_req)(plg->cs, &(*rad)->radius, &rad_ans, diam_msg, cli);
			if (ret)
				break;
		} else {
			TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
		}					
	}
	
	CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
	
	/* 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( fd_msg_free(*diam_msg), );
		*diam_msg = NULL;
	}
	
	/* Send the radius message back if required */
	if ((ret == -2) && rad_ans && rad) {
		CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, *rad, cli), /* It failed, it can't be helped... */);
	}
	
	if (ret > 0) {
		/* Critical error, log and exit */
		TRACE_DEBUG(NONE, "An error occurred while handling a RADIUS message from '%s': %s", rgw_clients_id(cli), strerror(ret));
		return ret;
	}
	
	/* Now, discard the message and return */
	rgw_msg_free(rad);
	return 0;
}

/* Loop in the extension list (same as req) to convert data from diam_ans to rad_ans */
int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli)
{
	int ret = 0;
	struct fd_list * head = NULL, *li;
	
	TRACE_ENTRY("%p %p %p %p", req, diam_ans, rad_ans, cli);
	CHECK_PARAMS( req && diam_ans && *diam_ans && rad_ans && *rad_ans && cli);
	
	/* Get the list of extensions of the RADIUS request */
	CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
	CHECK_FCT_DO( ret = get_accelerator(&head, req->radius.hdr->code, req->serv_type), 
		{ CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
	
	/* Loop in the list of extensions */
	for (li = head->next; li != head; li = li->next) {
		struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
		
		if (plg->descriptor->rgwp_diam_ans) {
			TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
			ret = (*plg->descriptor->rgwp_diam_ans)(plg->cs, diam_ans, rad_ans, (void *)cli);
			if (ret)
				break;
		} else {
			TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
		}					
	}
	
	CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
	
	/* If no error encountered, we're done here */
	if (ret == 0)
		return 0;
	
	/* Destroy the temporary RADIUS answer */
	if (*rad_ans) {
		radius_msg_free(*rad_ans);
		free(*rad_ans);
		*rad_ans = NULL;
	}
	
	if (ret > 0) {
		/* Critical error, log and exit */
		fd_log_debug("[app_radgw] An error occurred while handling a DIAMETER answer to a converted RADIUS request, turn on DEBUG for details: %s", strerror(ret));
		return ret;
	}
	
	/* We might define other return values with special meaning here (ret == -1, ...) for example create a new Diameter request */
	
	/* -1: just abord the translation with no more processing. */
	
	return 0;
}

void rgw_plg_fini(void)
{
	struct fd_list * item, *subitem;
	
	TRACE_ENTRY();
	
	CHECK_POSIX_DO( pthread_rwlock_rdlock( &plg_lock), /* continue anyway */ );
	
	/* Remove all elements from all accelerators */
	while ( ! FD_IS_LIST_EMPTY(&plg_accel_auth) ) {
		item = plg_accel_auth.next;
		fd_list_unlink(item);
		{
			struct plg_accel * accel = (struct plg_accel *)item;
			while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
				subitem = accel->plugins.next;
				fd_list_unlink(subitem);
				free(subitem);
			}
		}
		free(item);
	}
	while ( ! FD_IS_LIST_EMPTY(&plg_accel_acct) ) {
		item = plg_accel_acct.next;
		fd_list_unlink(item);
		{
			struct plg_accel * accel = (struct plg_accel *)item;
			while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
				subitem = accel->plugins.next;
				fd_list_unlink(subitem);
				free(subitem);
			}
		}
		free(item);
	}
	
	/* Now destroy all plugins information */
	while ( ! FD_IS_LIST_EMPTY(&plg_list) ) {
		struct plg_descr * plg = (struct plg_descr *) plg_list.next;
		fd_list_unlink(&plg->chain);
		free(plg->cc);
		if (plg->descriptor && plg->descriptor->rgwp_conf_free ) {
			TRACE_DEBUG(INFO, "RADIUS/Diameter gateway plugin '%s' cleaning up...", plg->descriptor->rgwp_name);
			(*plg->descriptor->rgwp_conf_free)(plg->cs);
		}
		if (plg->dlo)
			dlclose(plg->dlo);
		free(plg);
	}
	
	CHECK_POSIX_DO( pthread_rwlock_unlock( &plg_lock), );
}
"Welcome to our mercurial repository"