/*****************************************************************************
 * Filename:    cryptfs/options.c                                            *
 * Description: Mangement of the configuration information                   *
 * Copyright:   2009 by Alexander Motzkau                                    *
 *****************************************************************************/
#define _GNU_SOURCE
#include <grp.h>
#include <pwd.h>
#include <search.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "misc.h"
#include "options.h"

int compare_action(const void* a, const void* b)
{
    return strcmp(((struct action*)a)->name, ((struct action*)b)->name);
}

int compare_key(const void* a, const void* b)
{
    return strcmp(((struct key*)a)->id, ((struct key*)b)->id);
}

int compare_storage(const void* a, const void* b)
{
    return strcmp(((struct storage_block*)a)->entry->name,
		  ((struct storage_block*)b)->entry->name);
}

enum bool complete_config(struct configuration* config)
{
    struct action* ac;
    struct storage* st;
    
    config->action_tree = NULL;
    config->storage_tree = NULL;
    config->key_tree = NULL;
    config->keys_in_tree = NULL;

    for(st = config->storage; st != NULL; st = st->next)
    {
	struct storage_entry* entry;
	
	for(entry = st->entries; entry != NULL; entry = entry->next)
	{
	    struct storage_block** ste_val;
	    struct storage_block* tree_ent;

	    tree_ent = (struct storage_block*) xmalloc(sizeof(struct storage_block));
	    tree_ent->device = st;
	    tree_ent->entry = entry;
	    tree_ent->next = NULL;
	    
	    ste_val = (struct storage_block**) tsearch(tree_ent, &config->storage_tree, compare_storage);
	    if(ste_val == NULL)
	    {
		errmsg("Out of memory.\n");
		xfree(tree_ent);
		return false;
	    }
	    
	    if(*ste_val != tree_ent)
	    {
		struct storage_block* temp;
		
		for(temp = *ste_val; temp->next != NULL; temp = temp->next);
		    /* Do nothing. */
		    
		temp->next = tree_ent;
	    }
	}
    }
    
    for(ac = config->actions; ac != NULL; ac = ac->next)
    {
	struct action** ac_val;
	enum bool have_key;
	struct map* mapping;

	ac_val = (struct action**) tsearch(ac, &config->action_tree, compare_action);
	if(ac_val == NULL)
	{
	    errmsg("Out of memory.\n");
	    return false;
	}
	    
	if(*ac_val != ac)
	{
	    errmsg("action '%s' occurs more than once.\n", ac->name);
	    return false;
	}
	
	have_key = (ac->key != NULL);
	    
	for(mapping = ac->mappings; mapping != NULL; mapping = mapping->next)
	{
	    if(mapping->key == NULL && !have_key)
	    {
		errmsg("No key given in action '%s'.\n", ac->name);
		return false;
	    }
	    
	    if(find_storage(mapping->name, config)==NULL)
	    {
		errmsg("Unknown device '%s' to be mapped.\n", mapping->name);
		return false;
	    }
	}
    }
    
    if(config->ttys == NULL)
    {
	/* Default TTYs: current, vt */
	struct tty* tty;
	
	tty = (struct tty*) xmalloc(sizeof(struct tty));
	tty->next = NULL;
	tty->type = vt;
	tty->min = 0;
	tty->dev = tty->cmd = tty->display = tty->authority = NULL;
	tty->preexec = tty->postexec = NULL;
	
	config->ttys = (struct tty*) xmalloc(sizeof(struct tty));
	config->ttys->next = tty;
	tty = config->ttys;
	tty->type = current;
	tty->min = 0;
	tty->dev = tty->cmd = tty->display = tty->authority = NULL;
	tty->preexec = tty->postexec = NULL;
    }
    
    return true;
}

struct action* find_action(char *name, struct configuration* config)
{
    struct action** val;
    struct action ac;

    ac.name = name;
    val = (struct action**) tfind(&ac, &config->action_tree, compare_action);
    if(val == NULL)
	return NULL;
	
    return *val;
}

struct storage_block* find_storage(char *name, struct configuration* config)
{
    struct storage_block** val;
    struct storage_entry entry;
    struct storage_block tree_ent;

    tree_ent.entry = &entry;
    entry.name = name;
    
    val = (struct storage_block**) tfind(&tree_ent, &config->storage_tree, compare_storage);
    if(val == NULL)
	return NULL;

    return *val;
}

struct key* find_key(char *name, struct configuration* config)
{
    struct key** val;
    struct key k;

    k.id = name;
    val = (struct key**) tfind(&k, &config->key_tree, compare_key);
    if(val == NULL)
	return NULL;
	
    return *val;
}

char* xstrdup(char *str)
{
    if(str==NULL)
	return NULL;
    else
	return xmallocstr(str);
}

struct command* copy_command(struct command* command)
{
    struct command* result;
    struct command* previous;
    
    result = NULL;
    previous = NULL;
    
    while(command != NULL)
    {
	struct command* newcommand;

	newcommand = (struct command*) xmalloc(sizeof(struct command));
	
	if(result == NULL)
	    result = newcommand;
	else
	    previous->next = newcommand;
	
	previous = newcommand;
    
	newcommand->on_state = command->on_state;
	newcommand->on_mode = command->on_mode;
        newcommand->ignore_status = command->ignore_status;
	newcommand->needtty = command->needtty;
        newcommand->uid = xstrdup(command->uid);
        newcommand->gid = xstrdup(command->gid);
	newcommand->cmd = xstrdup(command->cmd);
	newcommand->next = NULL;

	command = command->next;
    }
    
    return result;    
}

struct key* copy_key(struct key* key, enum bool chain)
{
    struct key* result;
    struct key* previous;
    
    result = NULL;
    previous = NULL;
    
    while(key != NULL)
    {
	struct key* newkey;

	newkey = (struct key*) xmalloc(sizeof(struct key));

	newkey->id = xstrdup(key->id);
        newkey->type = key->type;
	newkey->len = key->len;
	newkey->vt100reserve = key->vt100reserve;
	newkey->retry = key->retry;
	newkey->next = NULL;
        newkey->subkeys = copy_key(key->subkeys, true);
        newkey->algo = xstrdup(key->algo);
	newkey->value = xstrdup(key->value);
        newkey->preexec = copy_command(key->preexec);
	newkey->postexec = copy_command(key->postexec);
        newkey->ignore_status = key->ignore_status;
	newkey->needtty = key->needtty;
        newkey->newline = key->newline;
	newkey->uid = xstrdup(key->uid);
	newkey->gid = xstrdup(key->gid);

        if(key->result != NULL)
	{
	    newkey->result = xmalloc(key->resultlen);
	    memcpy(newkey->result, key->result, key->resultlen);
	    newkey->resultlen = key->resultlen;
	}
        else
        {
	    newkey->result = NULL;
	    newkey->resultlen = 0;
        }

        if(!chain)
	    return newkey;
	else if(result == NULL)
	    result = newkey;
	else
	    previous->next = newkey;
	    
	previous = newkey;
	key = key->next;
    }
    
    return result;
}

void insert_single_key(struct key* key, struct configuration* config)
{
    struct key** val;
    struct key* subkey;
    
    if(key->id != NULL && key->type != ref)
    {
	val = (struct key**) tsearch(key, &config->key_tree, compare_key);
	if(val == NULL)
	{
	    errmsg("Out of memory.\n");
	    return;
	}

	*val = key;
    }
    
    subkey = key->subkeys;
    while(subkey != NULL)
    {
	insert_single_key(subkey, config);
	subkey = subkey->next;
    }
}

struct key* insert_key(struct key* key, struct configuration* config)
{
    struct key* newkey;
    
    if(key == NULL || key->type == ref)
	return key;
	
    newkey = copy_key(key, false);
    newkey->next = config->keys_in_tree;
    config->keys_in_tree = newkey;

    insert_single_key(newkey, config);
    return newkey;
}

enum tristate check_access(struct access* ac, struct configuration* config)
{
    if(ac->uid)
    {
	uid_t uid;
	
	if(!config->suid)
	    uid = getuid();
	else
	    uid = config->caller_uid;
	
	if(uid) /* root is always allowed. */
	{
	    struct passwd* pwent;
	    pwent = getpwnam(ac->uid);
	    
	    if(pwent == NULL)
		/* We are not an unknown user. */
		return t_unspecified;
	
	    if(uid != pwent->pw_uid)
		return t_unspecified;
	}
    }

    if(ac->gid)
    {
	gid_t gid;
	
	if(!config->sgid)
	    gid = getgid();
	else
	    gid = config->caller_gid;
	
	if(gid) /* root is again always allowed. */
	{
	    struct group* grpent;
	    grpent = getgrnam(ac->gid);
	    
	    if(grpent == NULL)
		/* We are not an unknown group. */
		return t_unspecified;
	
	    if(gid != grpent->gr_gid)
	    {
		/* Let's check supplementary groups. */
		gid_t* grplist;
		int i, grpnum;
		enum bool found;
		
		grpnum = getgroups(0, NULL);
		if(grpnum < 0)
		    return t_unspecified;
		
		grplist = (gid_t*) xmalloc(sizeof(gid_t)*grpnum);
		
		grpnum = getgroups(grpnum, grplist);
		found = false;
		for(i = 0; i < grpnum; i++)
		    if(grplist[i] == grpent->gr_gid)
		    {
			found = true;
			break;
		    }
		
		xfree(grplist);
		if(!found)
		    return t_unspecified;
	    }
	}
    }
    
    return check_access_list(ac->subrules, config, ac->allow);
}

enum bool check_access_list(struct access* ac, struct configuration* config, enum bool default_result)
{
    enum bool result;

    if(!config->suid)
	return true;

    result = default_result;
    for(; ac; ac = ac->next)
    {
	enum tristate check;
	
	check = check_access(ac, config);
	if(check != t_unspecified)
	    result = check;
    }
    
    return result;
}


struct map_options* copy_map_options(struct map_options* co)
{
    struct map_options* result;
    
    result = (struct map_options*) xmalloc(sizeof(struct map_options));
    
    result->fsck = co->fsck;
    result->mount = co->mount;
    result->ro = co->ro;
    result->mode = co->mode;
    result->uid = xstrdup(co->uid);
    result->gid = xstrdup(co->gid);
    
    return result;
}


#define REMOVE(type, list, fun)			\
    do						\
    {						\
	type *R_entry;				\
	R_entry = list;				\
						\
	while(R_entry != NULL)			\
	{					\
	    type *R_temp;			\
						\
	    R_temp = R_entry;			\
	    R_entry = R_entry->next;		\
	    fun(R_temp);			\
	}					\
    } while(0)

void free_entry(struct storage_entry* entry)
{
    xfree(entry->name);
    xfree(entry->cipher);
    xfree(entry);
}

void free_storage(struct storage* storage)
{
    xfree(storage->device);
    REMOVE(struct storage_entry, storage->entries, free_entry);
    xfree(storage);
}

void free_command(struct command *cmd)
{
    xfree(cmd->uid);
    xfree(cmd->gid);
    xfree(cmd->cmd);
    xfree(cmd);
}

void free_key(struct key *key)
{
    xfree(key->id);
    xfree(key->algo);
    xfree(key->value);
    xfree(key->uid);
    xfree(key->gid);
    
    REMOVE(struct command, key->preexec, free_command);
    REMOVE(struct command, key->postexec, free_command);
    REMOVE(struct key, key->subkeys, free_key);
    
    if(key->result != NULL && key->type != ref)
	xfree(key->result);

    xfree(key);
}

void free_access(struct access *ac)
{
    xfree(ac->uid);
    xfree(ac->gid);
    
    REMOVE(struct access, ac->subrules, free_access);
    
    xfree(ac);
}

void free_map_options_content(struct map_options* opt)
{
    xfree(opt->uid);
    xfree(opt->gid);
}

void free_map_options(struct map_options* opt)
{
    free_map_options_content(opt);
    xfree(opt);
}


void free_map(struct map *map)
{
    xfree(map->name);
    xfree(map->dname);
    xfree(map->point);
    xfree(map->fs);
    xfree(map->options);
    free_map_options_content(&map->opts);

    REMOVE(struct access, map->access, free_access);
    REMOVE(struct command, map->preexec, free_command);
    REMOVE(struct command, map->postexec, free_command);

    if(map->key != NULL) free_key(map->key);
    
    xfree(map);
}

void free_call(struct call *call)
{
    xfree(call->action);
    xfree(call->filename);
    free_map_options_content(&call->overrides);
    xfree(call);
}

void free_action(struct action* act)
{
    xfree(act->name);
    REMOVE(struct access, act->access, free_access);
    REMOVE(struct command, act->preexec, free_command);
    REMOVE(struct command, act->postexec, free_command);
    REMOVE(struct map, act->mappings, free_map);
    REMOVE(struct call, act->calls, free_call);
    if(act->key != NULL) free_key(act->key);
    xfree(act);
}

void free_tty(struct tty* tty)
{
    REMOVE(struct command, tty->preexec, free_command);
    REMOVE(struct command, tty->postexec, free_command);
    xfree(tty->dev);
    xfree(tty->cmd);
    xfree(tty->display);
    xfree(tty->authority);
    xfree(tty);
}

void free_dummy(void *node) {}

void free_config(struct configuration *config)
{
    tdestroy(config->action_tree, free_dummy);
    tdestroy(config->key_tree, free_dummy);
    tdestroy(config->storage_tree, xfree);
    REMOVE(struct tty, config->ttys, free_tty);
    REMOVE(struct action, config->actions, free_action);
    REMOVE(struct storage, config->storage, free_storage);
    REMOVE(struct key, config->keys_in_tree, free_key);
    REMOVE(struct access, config->access, free_access);
    xfree(config);
}
