/*
 * Gnophone: A client for the Asterisk PBX
 *
 * Copyright (C) 2000-2005, Digium, Inc.
 *
 * Written by Mark Spencer
 *
 * Linux/UNIX version distributed under the terms of
 * the GNU General Public License
 *
 * module.c: Simplistic dynamic module loader
 *
 */

#include "gnophone.h"

#define MAX_MOD_COUNT 100

static char *names[MAX_MOD_COUNT];

static int modcount = 0;

static char expected_key[] =
{ 0x8e, 0x93, 0x22, 0x83, 0xf5, 0xc3, 0xc0, 0x75,
  0xff, 0x8b, 0xa9, 0xbe, 0x7c, 0x43, 0x74, 0x63 };

static int printdigest(unsigned char *d)
{
	int x;
	printf("Unexpected signature:");
	for (x=0;x<16;x++)
		printf(" %#02x", *(d++));
	printf("\n");
	return 0;
}

static int key_matches(char *key1, char *key2)
{
	int match = 1;
	int x;
	for (x=0;x<16;x++) {
		match &= (key1[x] == key2[x]);
	}
	return match;
}

static int verify_key(char *key)
{
	struct MD5Context c;
	char digest[16];
	MD5Init(&c);
	MD5Update(&c, key, strlen(key));
	MD5Final(digest, &c);
	if (key_matches(expected_key, digest))
		return 0;
	printdigest(digest);
	return -1;
}

static int name_in_use(char *s)
{
	int x;
	if (!s)
		return -1;
	x = 0;
	while(names[x] && strcmp(names[x], s))
		x++;
	if (names[x])
		return -1;
	else
		names[x] = strdup(s);
	modcount = x;
	return 0;
}

static int load_module(char *dir, char *file)
{
	void *dlh;
	char fn[80];
	int (*init)(void);
	char* (*key)(void);
	char* (*name)(void);
	if (modcount >= MAX_MOD_COUNT - 1) {
		fprintf(stderr, "Too many modules (max = %d)\n", MAX_MOD_COUNT);
		return -1;
	}
	snprintf(fn, sizeof(fn), "%s/%s", dir, file);
	dlh = dlopen(fn, RTLD_NOW);
	if (dlh) {
		key = dlsym(dlh, "key");
		if (key) {
			if (!verify_key(key())) {
				name = dlsym(dlh, "name");
				if (name) {
					if (!name_in_use(name())) {
						init = dlsym(dlh, "init");
						if (init) {
							if (!init()) {
								fprintf(stderr, "Loaded and activated '%s'\n", fn);
							} else
								fprintf(stderr, "Notice: '%s' initialization failed\n", fn);
						} else
							fprintf(stderr, "Notice: '%s' had no initialization routine\n", fn);
					} else
						fprintf(stderr, "Notice: '%s' uses name '%s', already in use\n", fn, name());
				} else
					fprintf(stderr, "Notice: '%s' lacks function that tells us its name\n", fn);		
			} else
				fprintf(stderr, "Notice: '%s' did not return expected key\n", fn);
		} else 
			fprintf(stderr, "Notice: '%s' did not contain key function\n", fn);
	} else {
		fprintf(stderr, "Notice: Could not load '%s': %s\n", fn, dlerror());
	}
	return -1;
}

static int load_modules_in_dir(char *dirname)
{
	DIR *dir;
	char *c;
	struct dirent *file;

	printf("load_modules_in_dir(): beginning in %s\n", dirname);
	dir = opendir(dirname);
	if (dir) {
		while((file = readdir(dir))) {
			if ((c = strstr(file->d_name, ".so")) &&
			     !(*(c+3))) {
				 	/* It ends in ".so", so load it */
				load_module(dirname, file->d_name);
			}
		}
	}
	printf("load_modules_in_dir(): ending\n");
	return 0;
}

int load_modules(void)
{
	char homedir[256];
	snprintf(homedir, 256, "%s/.gnophone/modules", getenv("HOME"));
	load_modules_in_dir(BR_DATADIR(DEFAULT_MODULE_LOCATION));
	load_modules_in_dir(homedir);
	return 0;
}
