/* This is the module loader. It provides the facility for loading match
 * and target modules, which will do whatever relevant handling is necessary
 * for the data associated with matches and targets in the rules.
 */

/*
 * Author: Derrik Pates <dpates@dsdk12.net>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#define __USE_GNU
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/* for dlopen()/dlsym() and associated symbols */
#include <dlfcn.h>
#include <stdio.h>

/* for getenv() */
#include <stdlib.h>

#include <string.h>

#include "loader.h"

/* Keep a list of the modules we've loaded */
static ModuleDef *module_list = NULL;

/* Keep an internal reference count; when this is decremented to 0, release
 * any loaded modules. */
static int _refcount = 0;

static void register_module(ModuleDef *, HANDLE *, void *);

/* If we have a known built-in target, then we use the 'standard' target
 * module */
static bool use_std_target(char *targetname) {
	if(!strcmp(targetname, ""))
		return(TRUE);
	if(!strcmp(targetname, "ACCEPT"))
		return(TRUE);
	if(!strcmp(targetname, "DROP"))
		return(TRUE);
	if(!strcmp(targetname, "QUEUE"))
		return(TRUE);
	if(!strcmp(targetname, "RETURN"))
		return(TRUE);

	return(FALSE);
}

/* Load module, using a private call */
static ModuleDef *find_module_int(char *name, ModuleType type,
				HANDLE *table, bool dont_load) {
	ModuleDef *ptr = NULL;
	void *libhandle;
	ModuleDef *(*initf)(void);
	char *dlname = name;

#ifdef INET6
	/* What. the. hell. This is an ugly, ugly hack. */
	if (!strcmp(name, "icmpv6") || !strcmp(name, "ipv6-icmp") ||
					!strcmp(name, "icmp6")) {
		dlname = "icmpv6";
		name = "icmp6";
	}
#endif /* INET6 */

	/* If we're looking for a target module, and it's a built-in target or a
	 * chain, then load the 'standard' target module instead... */
	if((type == MODULE_TARGET) && (use_std_target(name) ||
							(table && IS_CHAIN(name, *table))))
		dlname = name = STD_TARGET;

	/* If the module we need has already been loaded, then just kick back
	 * a pointer to its data structure */
	for(ptr = module_list; ptr; ptr = ptr->next) {
		if(!strcmp(ptr->name, name) && type == ptr->type)
			return(ptr);
	}

	/* Ok, it's not loaded... if the don't load flag is set, don't try to
	 * dynamically load a module (normally only for this routine to call
	 * itself */
	if(!dont_load) {
		char *path, *basepath = NULL;
		basepath = getenv("IPT_MODPATH");
		if (!basepath || !strcmp(basepath, ""))
			basepath = MODULE_PATH;
#ifdef INET6
		asprintf(&path, "%s/ip6t_pl_%s.so", basepath, dlname);
#else /* !INET6 */
		asprintf(&path, "%s/ipt_pl_%s.so", basepath, dlname);
#endif /* INET6 */
		if((libhandle = dlopen(path, RTLD_NOW))) {
			initf = dlsym(libhandle, "init");
			register_module(initf(), table, libhandle);
			if(!(ptr = find_module_int(name, type, table, TRUE)))
				SET_ERRSTR("Couldn't lookup module %s after registration", name);
		} else
			SET_ERRSTR("dlopen() failed: %s", dlerror());
		free(path);
	}
	return(ptr);
}

/* Look up, or load, a module for me */
ModuleDef *ipt_find_module(char *name, ModuleType type, HANDLE *table) {
	return find_module_int(name, type, table, FALSE);
}

/* Keep an internal count of how many times we've been instantiated, so that
 * we can unload the modules we've loaded */
void ipt_loader_setup(void) {
	_refcount++;
}

/* Release all loaded modules when the refcount is 0 */
void ipt_release_modules(void) {
	ModuleDef *next;
	--_refcount;
	if (_refcount < 0)
		printf("refcount less than 0, wtf?\n");
	if (_refcount)
		return;
	/* This way, if someone really doesn't want to unload the modules, they
	 * can say so. This is useful with tools like valgrind and gdb. */
	if (!getenv("IPT_DONT_UNLOAD")) {
		while (module_list) {
			next = module_list->next;
			dlclose(module_list->libh);
			module_list = next;
		}
	}
}

/* Indoctrinate the newly-loaded module into the ways of our lands... */
static void register_module(ModuleDef *def, HANDLE *table,
				void *libhandle) {
	ModuleDef *i;
	def->libh = libhandle;
	/* Make sure this module isn't already loaded - if it is, something is
	 * broken. This should never happen, but if it does, then we probably 
	 * have a faulty module. */
	if(find_module_int(def->name, def->type, table, TRUE)) {
		fprintf(stderr, "Uhh. I already know module %s, something bad "
						"happened\n", def->name);
		return;
	}

	/* Check the size alignment - if alignment is wrong, we've got problems */
	if(def->size != ALIGN(def->size)) {
		fprintf(stderr, "Size is not properly aligned for this "
						"architecture!\n");
		exit(1);
	}

	/* Go ahead and stash the newly loaded module, now that we are reasonably
	 * sure that everything is OK. */
	for(i = module_list; i && i->next; i = i->next);
	if(i)
		i->next = def;
	else
		module_list = def;
}

/* vim: ts=4
 */