/*
 *Phonecore: A client for the Asterisk PBX
 *
 * Copyright (C) 2000, Linux Support Services, 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 <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <dirent.h>
#include <dlfcn.h>

#include "phonecore.h"
#include "md5.h"

#ifndef USE_AUDIOMOD

#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 char expected_key[] =
//{0xc0,0xd0,0xb,0x12,0x8,0x1e,0x12,0x30,0x3a,0xb,0x6c,0xb7,0x60,0x63,0xbc,0xc3};

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) {
	printf("Loading module %s\n", fn);
							if (!init()) {
								fprintf(stderr, "Loaded and activated '%s'\n", fn);
								return 0;
							} 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;
	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);
			}
		}
	}
	return 0;
}

static int load_specific_modules(char *dirname, char *modules)
{
	char tmp[256];
	char *mod;
	int ret=0;
	strncpy(tmp, modules, sizeof(tmp) - 1);
	mod = strtok(tmp, ";");
	if(mod==NULL)
		fprintf(stderr,"? No Modules in modules list !!\n");
	while(mod) {
		if (strlen(mod)){
			if(load_module(dirname, mod)<0)
				ret=-1;
		}
		mod = strtok(NULL, ";");
	}
	return ret;
}

int audio_load_modules(char *modules)
{
	if (modules) {
		if(load_specific_modules("/usr/lib/phonecore/modules", modules)<0)
			return -1;
	} else {
		load_modules_in_dir("/usr/lib/phonecore/modules");
	}
	return 0;
}
#else
extern int init(void);
int audio_load_modules(char *modules)
{
	init();
	return 0;
}
#endif
