/*
 * Copyright (C) 2006 International Business Machines Corp.
 * Authors: Theresa Nelson <tmnelson@us.ibm.com>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#ifndef S_SPLINT_S
#include <syslog.h>
#include <stdio.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include "../include/ecryptfs.h"

static int
(*builtin_inits[])(char **pki_name, struct ecryptfs_pki_elem *head) = {
	&passphrase_init_pki,
	NULL
};

int ecryptfs_get_pki_list(struct ecryptfs_ctx* ctx)
{
	DIR *dp = NULL;
	struct dirent *ep;
	char *dir_name = NULL;
	int i;
	struct ecryptfs_pki_elem *curr_pki = &(ctx->pki_list_head);
	int (*init_walker)(char **pki_name, struct ecryptfs_pki_elem *head);
	int rc = 0;

	if (asprintf(&dir_name, "%s", ECRYPTFS_DEFAULT_PKI_DIR) == -1) {
		rc = -ENOMEM;
		goto out;
	}
	if (!(dp = opendir(dir_name))) {
		syslog(LOG_WARNING, "ERROR: Could not open pki directory\n");
		rc = -EPERM;
		goto out;
	}
	while ((ep = readdir(dp))) {
		struct ecryptfs_pki_elem *new_pki = NULL;
		size_t dir_length;
		char *path = NULL;
		char *pki_dir = ECRYPTFS_DEFAULT_PKI_DIR;
		char *pki_name;
		void *handle;
		int (*init)(char **pki_name, struct ecryptfs_pki_elem *head);

		/* Check if file ends with .so */
		dir_length = strlen(ep->d_name);
		if ((dir_length < 3)
		    || strcmp((ep->d_name + (dir_length - 3)), ".so"))
			continue;
		if (asprintf(&path, "%s/%s", pki_dir, ep->d_name) == -1) {
			syslog(LOG_ERR, "Out of memory\n");
			rc = -ENOMEM;
			goto out;
		}
		rc = 0;
		handle = dlopen(path, RTLD_LAZY);
		if (!handle) {
			syslog(LOG_ERR, "Could not open library handle\n");
			goto end_loop;
		}
		init = (int (*)(char **pki_name,
				struct ecryptfs_pki_elem *head))
			dlsym(handle, "init_pki");
		if (!init) {
			syslog (LOG_ERR, "%s\n", dlerror());
			goto end_loop;
		}
		new_pki = malloc(sizeof(struct ecryptfs_pki_elem));
		if (!new_pki) {
			syslog(LOG_ERR, "Out of memory\n");
			free(path);
			rc = -ENOMEM;
			goto out;
		}
		memset(new_pki, 0, sizeof(struct ecryptfs_pki_elem));
		rc = (init)(&pki_name, new_pki);
		if (rc) {
			syslog (LOG_ERR, "ERROR: Library function init_pki() "
				"failed when run\n");
			free(new_pki);
			rc = 0;
			goto end_loop;
		}
		new_pki->pki_name = pki_name;
		new_pki->lib_handle = handle;
		new_pki->libname = path;
		curr_pki->next = new_pki;
		curr_pki = new_pki;
		continue;
	end_loop:
		free(path);
	}
	closedir(dp);
	i = 0;
	init_walker = builtin_inits[i];
	while (init_walker) {
		struct ecryptfs_pki_elem *new_pki;
		char *pki_name;
		struct ecryptfs_pki_elem *tmp_pki;

		if (!(new_pki = malloc(sizeof(struct ecryptfs_pki_elem)))) {
			syslog(LOG_ERR, "Out of memory\n");
			rc = -ENOMEM;
			goto out;
		}
		memset(new_pki, 0, sizeof(struct ecryptfs_pki_elem));
		if ((rc = (init_walker)(&pki_name, new_pki))) {
			syslog (LOG_ERR, "ERROR: Library function init_pki() "
				"failed when run\n");
			free(new_pki);
			rc = 0;
			goto end_loop_2;
		}
		tmp_pki = ctx->pki_list_head.next;
		while (tmp_pki) {
			if (strcmp(tmp_pki->pki_name, pki_name) == 0) {
				free(new_pki->pki_name);
				free(new_pki);
				syslog(LOG_INFO, "Preferring [%s] file over "
				       "built-in module for key module with "
				       "name [%s]\n", tmp_pki->libname,
				       tmp_pki->pki_name);
				goto end_loop_2;
			}
			tmp_pki = tmp_pki->next;
		}
		new_pki->pki_name = pki_name;
		curr_pki->next = new_pki;
		curr_pki = new_pki;
end_loop_2:
		i++;
		init_walker = builtin_inits[i];
	}
out:
	free(dir_name);
	return rc;
}

int ecryptfs_find_pki(struct ecryptfs_ctx *ctx, char *pki_name,
		      struct ecryptfs_pki_elem **desired_pki)
{
	struct ecryptfs_pki_elem *curr;
	int rc = 0;

	curr = ctx->pki_list_head.next;
	while (curr) {
		if (!strncmp(curr->pki_name, pki_name,
			     strlen(curr->pki_name))) {
			*desired_pki = curr;
			goto out;
		}
		curr = curr->next;
	}
	rc = 1;
out:
	return rc;
}

int ecryptfs_reset_pki(struct ecryptfs_ctx *ctx)
{
	struct ecryptfs_pki_elem *curr = ctx->pki_list_head.next;
	int rc = 0;

	while (curr) {
		struct ecryptfs_name_val_pair *pki_nvp = &(curr->nvp_head);
		pki_nvp = pki_nvp->next;
		while(pki_nvp){
			free(pki_nvp->value);
			pki_nvp->value = NULL;
			pki_nvp = pki_nvp->next;
		}
		curr = curr->next;
	}
	return rc;
}

int ecryptfs_free_pki_list(struct ecryptfs_ctx *ctx)
{
	struct ecryptfs_pki_elem *curr = ctx->pki_list_head.next;
	struct ecryptfs_pki_elem *temp;

	while (curr) {
		free(curr->libname);
		temp = curr;
		curr = curr->next;
		free(temp);
	}
	return 0;
}
