/*****************************************************************************
 * Filename:    xml.c                                                        *
 * Description: Parsing routines for the configuration file                  *
 * Copyright:   2009 by Alexander Motzkau                                    *
 *****************************************************************************/

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

#include <ctype.h>
#include <iconv.h>
#include <limits.h>
#include <langinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>

#define REVERSE(type, list)			\
    do						\
    {						\
	type *R_oldentry;			\
	type *R_newentry;			\
						\
	R_newentry = NULL;			\
	R_oldentry = list;			\
						\
	while(R_oldentry != NULL)		\
	{					\
	    type *R_temp;			\
						\
	    R_temp = R_oldentry->next;		\
	    R_oldentry->next = R_newentry;	\
	    R_newentry = R_oldentry;		\
	    R_oldentry = R_temp;		\
	}					\
						\
	list = R_newentry;			\
    } while(0)

struct gen_options
{
    enum bool fsck;
    enum bool mount;
    
    const char *filename;
};

struct literal
{
    xmlChar* name;
    union
    {
	int intval;
	void* data;
    };
};

struct literal yesno[] = { {"yes", {.intval = true} }, 
			   {"no",  {.intval = false} },
			   { NULL } };

struct literal onstate[] = { {"success", {.intval = on_success} }, 
			     {"failure", {.intval = on_failure} },
			     {"always",  {.intval = os_always} },
			     { NULL } };

struct literal onmode[]  = { {"map",     {.intval = on_map} }, 
			     {"mount",   {.intval = on_mount} },
			     {"always",  {.intval = om_always} },
			     {"remove",  {.intval = on_remove} },
			     { NULL } };
			     
struct literal keytypes[] = { {"composite",  {.intval = composite} },
			      {"passphrase", {.intval = passphrase} },
			      {"file",       {.intval = file} },
			      {"program",    {.intval = program} },
			      {"literal",    {.intval = literal} },
			      { NULL } };

struct literal ttytypes[] = { {"current", {.intval = current} },
			      {"fixed",   {.intval = fixed} },
			      {"vt",      {.intval = vt} },
			      {"xterm",   {.intval = xterm } },
			      { NULL } };

struct literal nlmodes[] = { {"add",       {.intval = nl_add} },
			     {"remove",    {.intval = nl_remove} },
			     {"unchanged", {.intval = nl_unchanged} },
			     { NULL } };

struct literal retries[] = { {"inf",       {.intval = -1} }, 
			     {"infinite",  {.intval = -1} },
			     { NULL } };

struct literal *parse_literal(const xmlChar* str, struct literal *literals)
{
    struct literal *cur;
    
    for(cur = literals; cur->name != NULL; cur++)
	if(!xmlStrcmp(str, cur->name))
	    return cur;

    return NULL;
}

struct attrlist
{
    xmlChar *name;
    xmlChar **attrvar;
    enum bool required;
};

int parse_attrlist(xmlNodePtr cur, struct attrlist* alist, struct gen_options *opts)
{
    struct attrlist *attr;
    for(attr=alist; attr->name != NULL; attr++)
    {
	*(attr->attrvar) = xmlGetProp(cur, attr->name);
	if(attr->required && *(attr->attrvar) == NULL)
	{
	    errmsg("%s: '%s' element without '%s' attribute.\n",
		opts->filename, cur->name, attr->name);
	    
	    for(attr--; attr>=alist; attr--)
		if(*(attr->attrvar) != NULL)
		    xmlFree(*(attr->attrvar));

	    return false;
	}
    }
    
    return true;
}

char* convert_from_utf8(const char* str, int len)
{
    char *cstr;
    char *cset;
    char *temp;
    
    char *inbuf;
    char *outbuf;
    size_t inbytesleft, outbytesleft;
    iconv_t cd;
    
    cset = nl_langinfo(CODESET);
    cd = iconv_open(cset, "UTF-8");
    if(cd == (iconv_t) -1)
    {
	errnomsg("iconv_open");
	
	cstr = (char*) xmalloc(len+1);
	memcpy(cstr, str, len);
        cstr[len] = 0;
	return cstr;
    }
    
    inbuf = (char*) str;
    inbytesleft = len;
    outbuf = temp = xmalloc(6*len+1);
    outbytesleft = 6*len;
    if(iconv(cd, &inbuf,&inbytesleft, &outbuf, &outbytesleft) == -1)
    {
	errnomsg("iconv");
	iconv_close(cd);
	xfree(temp);

	cstr = (char*) xmalloc(len+1);
	memcpy(cstr, str, len);
        cstr[len] = 0;
	return cstr;
    }
    
    iconv_close(cd);
    *outbuf = 0;
    cstr = xmalloc(outbuf - temp + 1);
    memcpy(cstr, temp, outbuf - temp + 1);
    xfree(temp);
    
    return cstr;
}

char* copy_xmlstr(const xmlChar* str)
{
    return convert_from_utf8(str, xmlStrlen(str));
}

char* copy_xmlstr_and_free(xmlChar* str)
{
    if(str == NULL)
	return NULL;
    else
    {
	char *res;
	
	res = copy_xmlstr(str);
	xmlFree(str);
	
	return res;
    }
}

char* copy_stripped_xmlstr_and_free(xmlChar* str)
{
    if(str == NULL)
	return NULL;
    else
    {
	char *begin;
	char *end;
	char *res;
	
	begin = str;
	while(*begin && isspace(*begin)) begin++;
	
	end = str+xmlStrlen(str)-1;
	while(end>=begin && isspace(*end)) end--;
	
	if(end<begin)
	    res = NULL;
	else
	    res = convert_from_utf8(begin, end-begin+1);
	
	xmlFree(str);
	
	return res;
    }    
}

unsigned long xmlstr2ulong(const xmlChar* str, unsigned long def, enum bool *err)
{
    unsigned long result;
    char *pos;
    
    if(str == NULL)
    {
	*err = false;
	return def;
    }

    result = strtoul(str, &pos, 0);
    if(*pos != 0)
    {
	*err = true;
	return 0;
    }
    
    *err = false;
    return result;
}

struct storage_entry* parse_entry(xmlNodePtr cur, unsigned long *pos, struct gen_options *opts)
{
    struct storage_entry *entry;

    xmlChar *nameattr;
    xmlChar *cipherattr;
    xmlChar *sizeattr;
    xmlChar *startattr;
	    
    enum bool sizeerr;
    enum bool starterr;
	    
    struct attrlist alist[] = { {"name", &nameattr, true },
				{"cipher", &cipherattr, true },
				{"size", &sizeattr, false },
				{"start", &startattr, false },
				{NULL} };
    if(!parse_attrlist(cur, alist, opts))
	return NULL;
	    
    entry = (struct storage_entry*) xmalloc(sizeof(struct storage_entry));
    entry->name = copy_xmlstr(nameattr);
    entry->cipher = copy_xmlstr(cipherattr);
    entry->size = xmlstr2ulong(sizeattr, 0, &sizeerr);
    entry->start = xmlstr2ulong(startattr, *pos, &starterr);
	    
    xmlFree(nameattr);
    xmlFree(cipherattr);
    if(sizeattr!=NULL) xmlFree(sizeattr);
    if(startattr!=NULL) xmlFree(startattr);
	    
    if(starterr)
    {
	errmsg("%s: Illegal 'start' value in 'entry' element.\n", opts->filename);
	xfree(entry);
	return NULL;
    }
	    	    
    if(sizeerr)
    {
	errmsg("%s: Illegal 'size' value in 'entry' element.\n", opts->filename);
	xfree(entry);
	return NULL;
    }
	    
    if(entry->start == ULONG_MAX)
    {
	errmsg("%s: Can't guess 'start' values without 'size'.\n", opts->filename);
	xfree(entry);
	return NULL;
    }
	    
    if(entry->size == 0)
	*pos = ULONG_MAX;
    else if(*pos != ULONG_MAX)
    {
	*pos += entry->size;
	if(*pos < entry->size)
	{
	    errmsg("%s: Integer overflow on 'start' values.\n", opts->filename);
	    xfree(entry);
	    return NULL;
	}
    }
    
    return entry;
}

struct storage* parse_storage(xmlNodePtr cur, struct gen_options *opts)
{
    xmlChar *devattr;
    struct storage* result;
    unsigned long pos;
	    
    devattr = xmlGetProp(cur, (const xmlChar*) "device");
    if(devattr == NULL)
    {
	errmsg("%s: 'storage' element without 'device' attribute.\n", opts->filename);
	xmlFree(devattr);
	return NULL;
    }
    
    result = (struct storage*) xmalloc(sizeof(struct storage));
    result->device = copy_xmlstr(devattr);
    xmlFree(devattr);
    result->next = NULL;
    result->entries = NULL;
    
    pos = 0;
    
    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else if(!xmlStrcmp(cur->name, (const xmlChar*) "entry"))
	{
	    struct storage_entry *entry = parse_entry(cur, &pos, opts);

	    if(entry == NULL)
	    {
		free_storage(result);
		return NULL;
	    }
	    
	    entry->next = result->entries;
	    result->entries = entry;	    
	}
	else
	{
	    errmsg("%s: Illegal occurence of element '%s' within 'storage' element.\n", opts->filename, cur->name);
	    xfree(result);
	    return NULL;
	}

    REVERSE(struct storage_entry, result->entries);
    
    return result;
}

struct command* parse_command(xmlNodePtr cur, struct gen_options *opts, enum on_mode default_mode)
{
    struct command *cmd;
    struct literal *lit;

    xmlChar *onstateattr;
    xmlChar *onmodeattr;
    xmlChar *ignoreattr;
    xmlChar *uidattr;
    xmlChar *gidattr;
    xmlChar *ttyattr;
	    
    struct attrlist alist[] = { {"onstate",      &onstateattr, false },
				{"onmode",       &onmodeattr, false },
				{"ignorestatus", &ignoreattr, false },
				{"uid",          &uidattr, false },
				{"gid",          &gidattr, false },
				{"needtty",      &ttyattr, false },
				{NULL} };
    if(!parse_attrlist(cur, alist, opts))
	return NULL;
	
    cmd = (struct command*) xmalloc(sizeof(struct command));

    cmd->uid = copy_xmlstr_and_free(uidattr);
    cmd->gid = copy_xmlstr_and_free(gidattr);
    cmd->cmd = NULL;
    
    if(onstateattr != NULL)
    {
	lit = parse_literal(onstateattr, onstate);

        xmlFree(onstateattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'onstate' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(onmodeattr != NULL) xmlFree(onmodeattr);
	    if(ignoreattr != NULL) xmlFree(ignoreattr);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    free_command(cmd);
    	    return NULL;
	}
	cmd->on_state = lit->intval;
    }
    else
	cmd->on_state = on_success;
    
    if(onmodeattr != NULL)
    {
	lit = parse_literal(onmodeattr, onmode);

        xmlFree(onmodeattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'onmode' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(ignoreattr != NULL) xmlFree(ignoreattr);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    free_command(cmd);
    	    return NULL;
	}
	cmd->on_mode = lit->intval;
    }
    else
	cmd->on_mode = default_mode;
    
    if(ignoreattr != NULL)
    {
	lit = parse_literal(ignoreattr, yesno);

        xmlFree(ignoreattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'ignorestatus' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    free_command(cmd);
    	    return NULL;
	}
	cmd->ignore_status = lit->intval;
    }
    else
	cmd->ignore_status = false;

    if(ttyattr != NULL)
    {
	lit = parse_literal(ttyattr, yesno);

        xmlFree(ttyattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'needtty' attribute of the '%s' element.\n", opts->filename, cur->name);
	    free_command(cmd);
    	    return NULL;
	}
	cmd->needtty = lit->intval;
    }
    else
	cmd->needtty = false;
    
    cmd->cmd = copy_stripped_xmlstr_and_free(xmlNodeListGetString(cur->doc, cur->xmlChildrenNode, 1));
    
    return cmd;
}

#define PARSE_ELEMENT(elname, type, entry, fun)	\
	if(!xmlStrcmp(cur->name, (const xmlChar*) elname))	\
	{							\
	    type *P_res;					\
	    P_res = fun(cur, opts);				\
	    if(P_res == NULL)					\
		failed = true;					\
	    else						\
	    {							\
		P_res->next = result->entry;			\
		result->entry = P_res;				\
	    }							\
	}							\
	else

#define PARSE_ELEMENT1(elname, type, entry, fun, arg)	\
	if(!xmlStrcmp(cur->name, (const xmlChar*) elname))	\
	{							\
	    type *P_res;					\
	    P_res = fun(cur, opts, arg);			\
	    if(P_res == NULL)					\
		failed = true;					\
	    else						\
	    {							\
		P_res->next = result->entry;			\
		result->entry = P_res;				\
	    }							\
	}							\
	else
	
struct key* parse_key(xmlNodePtr cur, struct gen_options *opts)
{
    struct key* result;
    struct literal *lit;
    xmlChar *idattr;
    xmlChar *typeattr;
    xmlChar *algoattr;
    xmlChar *lenattr;
    xmlChar *ignoreattr;
    xmlChar *uidattr;
    xmlChar *gidattr;
    xmlChar *ttyattr;
    xmlChar *nlattr;
    xmlChar *vt100attr;
    xmlChar *retryattr;
    enum bool dummyerr;

    struct attrlist alist[] = { {"id",           &idattr, false },
				{"type",         &typeattr, false },
				{"algo",         &algoattr, false },
				{"length",       &lenattr, false },
				{"ignorestatus", &ignoreattr, false },
				{"uid",          &uidattr, false },
				{"gid",          &gidattr, false },
				{"needtty",      &ttyattr, false },
				{"newline",      &nlattr, false },
				{"vt100",        &vt100attr, false },
				{"retry",        &retryattr, false },
				{NULL} };

    if(!parse_attrlist(cur, alist, opts))
	return NULL;

    result = (struct key*) xmalloc(sizeof(struct key));
    result->subkeys = NULL;
    result->value = NULL;
    result->result = NULL;
    result->resultlen = 0;
    result->id = copy_xmlstr_and_free(idattr);
    result->algo = copy_xmlstr_and_free(algoattr);
    result->uid = copy_xmlstr_and_free(uidattr);
    result->gid = copy_xmlstr_and_free(gidattr);
    result->preexec = result->postexec = NULL;
    
    if(typeattr == NULL)
	result->type = ref;
    else
    {
	lit = parse_literal(typeattr, keytypes);
	xmlFree(typeattr);
    
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'type' attribute of the 'key' element.\n", opts->filename);
	    if(lenattr != NULL) xmlFree(lenattr);
	    if(ignoreattr != NULL) xmlFree(ignoreattr);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    if(nlattr != NULL) xmlFree(nlattr);
	    if(vt100attr != NULL) xmlFree(vt100attr);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
	    return NULL;
        }
	result->type = lit->intval;
    }

    if(lenattr != NULL)
    {
	result->len = xmlstr2ulong(lenattr, 0, &dummyerr);
	xmlFree(lenattr);
	
	if(dummyerr)
	{
    	    errmsg("%s: Illegal value for 'length' attribute of the 'key' element.\n", opts->filename);
	    if(ignoreattr != NULL) xmlFree(ignoreattr);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    if(nlattr != NULL) xmlFree(nlattr);
	    if(vt100attr != NULL) xmlFree(vt100attr);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
	    return NULL;
	}
    }
    else
	result->len = 0;

    if(ignoreattr != NULL)
    {
	lit = parse_literal(ignoreattr, yesno);

        xmlFree(ignoreattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'ignorestatus' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(ttyattr != NULL) xmlFree(ttyattr);
	    if(nlattr != NULL) xmlFree(nlattr);
	    if(vt100attr != NULL) xmlFree(vt100attr);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
    	    return NULL;
	}
	result->ignore_status = lit->intval;
    }
    else
	result->ignore_status = false;

    if(ttyattr != NULL)
    {
	lit = parse_literal(ttyattr, yesno);

        xmlFree(ttyattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'needtty' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(nlattr != NULL) xmlFree(nlattr);
	    if(vt100attr != NULL) xmlFree(vt100attr);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
    	    return NULL;
	}
	result->needtty = lit->intval;
    }
    else
	result->needtty = false;

    if(nlattr != NULL)
    {
	lit = parse_literal(nlattr, nlmodes);

        xmlFree(nlattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'newline' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(vt100attr != NULL) xmlFree(vt100attr);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
    	    return NULL;
	}
	result->newline = lit->intval;
	
    }
    else
	result->newline = nl_unchanged;
    
    if(vt100attr != NULL)
    {
	result->vt100reserve = xmlstr2ulong(vt100attr, 0, &dummyerr);
	xmlFree(vt100attr);
	
	if(dummyerr)
	{
    	    errmsg("%s: Illegal value for 'vt100' attribute of the 'key' element.\n", opts->filename);
	    if(retryattr != NULL) xmlFree(retryattr);
	    free_key(result);
	    return NULL;
	}
	
	if(result->type != passphrase) /* Just warn. */
    	    errmsg("%s: 'vt100' attribute is only valid for 'passphrase' type keys.\n", opts->filename);
    }
    else
	result->vt100reserve = 0;

    if(retryattr != NULL)
    {
	lit = parse_literal(retryattr, retries);
	if(lit == NULL)
	{
	    result->retry = xmlstr2ulong(retryattr, 0, &dummyerr);
	    xmlFree(retryattr);
	
	    if(dummyerr || result->retry < 0)
	    {
    		errmsg("%s: Illegal value for 'retry' attribute of the 'key' element.\n", opts->filename);
		free_key(result);
		return NULL;
	    }
	}
	else
	    result->retry = lit->intval;
    }
    else
	result->retry = 0;

    if(result->type == composite)
    {
	for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
	{
	    enum bool failed;

	    failed = false;

	    if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	    else
	    PARSE_ELEMENT1("preexec", struct command, preexec, parse_command, on_map)
	    PARSE_ELEMENT1("postexec", struct command, postexec, parse_command, on_map)
	    if(!xmlStrcmp(cur->name, (const xmlChar*) "key"))
	    {
		struct key *sk;
		
		sk = parse_key(cur, opts);
		if(sk == NULL)
		    failed = true;
		else
		{
		    sk->next = result->subkeys;
		    result->subkeys = sk;
		}
	    }
	    else
	    {
		errmsg("%s: Illegal occurence of element '%s' within 'key' element.\n", opts->filename, cur->name);
		failed = true;
	    }

	    if(failed)
	    {
		free_key(result);
		return NULL;
	    }
	}
	
	if(result->subkeys == NULL)
	{
	    errmsg("%s: Composite key doesn't contain a subkey.\n", opts->filename);
	    free_key(result);
	    return NULL;
	}
	REVERSE(struct key, result->subkeys);
    }
    else if(result->id == NULL && result->type == ref)
    {
	errmsg("%s: Key element without 'id' and 'type' attribute.\n", opts->filename);
	free_key(result);
	return NULL;
    }
    else
    {
	result->value = copy_stripped_xmlstr_and_free(xmlNodeListGetString(cur->doc, cur->xmlChildrenNode, 1));

	for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
	{
	    enum bool failed;

	    failed = false;

	    if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	    else
	    PARSE_ELEMENT1("preexec", struct command, preexec, parse_command, on_map)
	    PARSE_ELEMENT1("postexec", struct command, postexec, parse_command, on_map)
	    {
		errmsg("%s: Illegal occurence of element '%s' within 'key' element.\n", opts->filename, cur->name);
		failed = true;
	    }
	    
	    if(failed)
	    {
		free_key(result);
		return NULL;
	    }
	}
    }
        
    REVERSE(struct command, result->preexec);
    REVERSE(struct command, result->postexec);

    return result;
}

struct access* parse_access(xmlNodePtr cur, struct gen_options *opts)
{
    struct access *result;

    xmlChar *uidattr;
    xmlChar *gidattr;

    struct attrlist alist[] = { {"uid",      &uidattr,     false },
				{"gid",      &gidattr,     false },
				{NULL} };
    if(!parse_attrlist(cur, alist, opts))
	return NULL;

    result = (struct access*) xmalloc(sizeof(struct access));

    result->allow = !xmlStrcmp(cur->name, (const xmlChar*) "allow");
    result->uid = copy_xmlstr_and_free(uidattr);
    result->gid = copy_xmlstr_and_free(gidattr);
    
    result->subrules = NULL;
    result->next = NULL;

    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
    {
	enum bool failed;

	failed = false;

	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else
	PARSE_ELEMENT("allow", struct access, subrules, parse_access)
	PARSE_ELEMENT("deny",  struct access, subrules, parse_access)
	{
	    errmsg("%s: Illegal occurence of element '%s' within '%s' element.\n", opts->filename, cur->name, result->allow?"allow":"deny");
	    failed = true;
	}
	
	if(failed)
	{
	    free_access(result);
	    return NULL;
	}
	
    }

    REVERSE(struct access, result->subrules);

    return result;
}

enum bool parse_map_options(xmlNodePtr cur, struct gen_options *opts, struct map_options* result)
{
    struct literal *lit;

    xmlChar *fsckattr;
    xmlChar *mountattr;
    xmlChar *uidattr;
    xmlChar *gidattr;
    xmlChar *roattr;
    xmlChar *modeattr;

    enum bool modeerr;
	    
    struct attrlist alist[] = { {"fsck",     &fsckattr,    false },
				{"mount",    &mountattr,   false },
				{"uid",      &uidattr,     false },
				{"gid",      &gidattr,     false },
				{"ro",       &roattr,      false },
				{"mode",     &modeattr,    false },
				{NULL} };

    if(!parse_attrlist(cur, alist, opts))
	return false;
    
    result->uid = copy_xmlstr_and_free(uidattr);
    result->gid = copy_xmlstr_and_free(gidattr);

    result->mode = xmlstr2ulong(modeattr, 0, &modeerr);
    if(modeattr != NULL)
	xmlFree(modeattr);
    
    if(modeerr)
    {
	errmsg("%s: Illegal 'mode' value in '%s' element.\n", opts->filename, cur->name);
	if(fsckattr != NULL) xmlFree(fsckattr);
	if(mountattr != NULL) xmlFree(mountattr);
	if(roattr != NULL) xmlFree(roattr);
	return false;
    }
    
    if(roattr != NULL)
    {
	lit = parse_literal(roattr, yesno);

        xmlFree(roattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'ro' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(fsckattr != NULL) xmlFree(fsckattr);
	    if(mountattr != NULL) xmlFree(mountattr);
	    return false;
	}
	result->ro = lit->intval;
    }
    else
	result->ro = t_unspecified;

    if(fsckattr != NULL)
    {
	lit = parse_literal(fsckattr, yesno);

        xmlFree(fsckattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'fsck' attribute of the '%s' element.\n", opts->filename, cur->name);
	    if(mountattr != NULL) xmlFree(mountattr);
	    return false;
	}
	result->fsck = lit->intval;
    }
    else
	result->fsck = t_unspecified;

    if(mountattr != NULL)
    {
	lit = parse_literal(mountattr, yesno);

        xmlFree(mountattr);
	if(lit == NULL)
	{
    	    errmsg("%s: Illegal value for 'mount' attribute of the '%s' element.\n", opts->filename, cur->name);
	    return false;
	}
	result->mount = lit->intval;
    }
    else
	result->mount = t_unspecified;

    return true;
}

struct map* parse_map(xmlNodePtr cur, struct gen_options *opts)
{
    struct map *result;

    xmlChar *nameattr;
    xmlChar *asattr;
    xmlChar *pointattr;
    xmlChar *fsattr;
    xmlChar *optionsattr;

    struct attrlist alist[] = { {"name",     &nameattr,    true },
				{"as",       &asattr,      false },
				{"point",    &pointattr,   false },
				{"fs",       &fsattr,      false },
				{"options",  &optionsattr, false },
				{NULL} };
    if(!parse_attrlist(cur, alist, opts))
	return NULL;

    result = (struct map*) xmalloc(sizeof(struct map));
    result->preexec = result->postexec = NULL;
    result->access = NULL;
    result->key = NULL;
    
    result->name = copy_xmlstr(nameattr);
    xmlFree(nameattr);

    result->dname = copy_xmlstr_and_free(asattr);
    if(result->dname == NULL)
	result->dname = xmallocstr(result->name);
    
    result->point = copy_xmlstr_and_free(pointattr);
    result->fs = copy_xmlstr_and_free(fsattr);
    result->options = copy_xmlstr_and_free(optionsattr);

    if(!parse_map_options(cur, opts, &result->opts))
    {
	free_map(result);
	return NULL;
    }

    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
    {
	enum bool failed;

	failed = false;

	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else
	PARSE_ELEMENT("allow", struct access, access, parse_access)
	PARSE_ELEMENT("deny",  struct access, access, parse_access)
	PARSE_ELEMENT1("preexec", struct command, preexec, parse_command, on_mount)
	PARSE_ELEMENT1("postexec", struct command, postexec, parse_command, on_mount)
	if(!xmlStrcmp(cur->name, (const xmlChar*) "key"))
	{
	    if(result->key != NULL)
	    {
		errmsg("%s: Double 'key' element in 'map' element.\n", opts->filename);
		failed = true;
	    }
	    else
	    {
		struct key *key;
		
		key = parse_key(cur, opts);
		if(key == NULL)
		    failed = true;
		else
		    result->key = key;
	    }
	}
	else
	{
	    errmsg("%s: Illegal occurence of element '%s' within 'map' element.\n", opts->filename, cur->name);
	    failed = true;
	}
	
	if(failed)
	{
	    free_map(result);
	    return NULL;
	}
	
    }

    REVERSE(struct access, result->access);
    REVERSE(struct command, result->preexec);
    REVERSE(struct command, result->postexec);

    return result;
}

struct call* parse_call(xmlNodePtr cur, struct gen_options *opts)
{
    struct call *result;

    xmlChar *actionattr;
    xmlChar *fileattr;

    struct attrlist alist[] = { {"action",   &actionattr,  true },
				{"file",     &fileattr,    false },
				{NULL} };
    if(!parse_attrlist(cur, alist, opts))
	return NULL;

    result = (struct call*) xmalloc(sizeof(struct call));
    result->action = copy_xmlstr(actionattr);
    xmlFree(actionattr);
    
    result->filename = copy_xmlstr_and_free(fileattr);

    if(!parse_map_options(cur, opts, &result->overrides))
    {
	free_call(result);
	return NULL;
    }

    return result;
}

struct action* parse_action(xmlNodePtr cur, struct gen_options *opts)
{
    xmlChar *nameattr;
    struct action* result;
	    
    nameattr = xmlGetProp(cur, (const xmlChar*) "name");
    if(nameattr == NULL)
    {
	errmsg("%s: 'action' element without 'name' attribute.\n", opts->filename);
	xmlFree(nameattr);
	return NULL;
    }
    
    result = (struct action*) xmalloc(sizeof(struct action));
    result->name = copy_xmlstr(nameattr);
    xmlFree(nameattr);
    result->preexec = result->postexec = NULL;
    result->access = NULL;
    result->key = NULL;
    result->mappings = NULL;
    result->calls = NULL;
    result->done = false;
    
    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
    {
	enum bool failed;

	failed = false;

	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else
	PARSE_ELEMENT("allow", struct access, access, parse_access)
	PARSE_ELEMENT("deny",  struct access, access, parse_access)
	PARSE_ELEMENT1("preexec", struct command, preexec, parse_command, on_mount)
	PARSE_ELEMENT1("postexec", struct command, postexec, parse_command, on_mount)
	PARSE_ELEMENT("map", struct map, mappings, parse_map)
	PARSE_ELEMENT("call", struct call, calls, parse_call)
	if(!xmlStrcmp(cur->name, (const xmlChar*) "key"))
	{
	    if(result->key != NULL)
	    {
		errmsg("%s: Double 'key' element in 'action' element.\n", opts->filename);
		failed = true;
	    }
	    else
	    {
		struct key *key;
		
		key = parse_key(cur, opts);
		if(key == NULL)
		    failed = true;
		else
		    result->key = key;
	    }
	}
	else
	{
	    errmsg("%s: Illegal occurence of element '%s' within 'action' element.\n", opts->filename, cur->name);
	    failed = true;
	}
	
	if(failed)
	{
	    free_action(result);
	    return NULL;
	}
	
    }

    REVERSE(struct access, result->access);
    REVERSE(struct command, result->preexec);
    REVERSE(struct command, result->postexec);
    REVERSE(struct map, result->mappings);
    REVERSE(struct call, result->calls);
    return result;
}

struct tty *parse_tty(xmlNodePtr cur, struct gen_options *opts)
{
    struct tty* result;
    struct literal *lit;
    xmlChar *typeattr;
    xmlChar *devattr;
    xmlChar *cmdattr;
    xmlChar *dispattr;
    xmlChar *authattr;
    xmlChar *minattr;
    enum bool dummyerr;

    struct attrlist alist[] = { {"type",      &typeattr, true },
				{"dev",       &devattr,  false },
				{"cmd",       &cmdattr,  false },
				{"display",   &dispattr, false },
				{"authority", &authattr, false },
				{"min",       &minattr,  false },
				{NULL} };

    if(!parse_attrlist(cur, alist, opts))
	return NULL;

    result = (struct tty*) xmalloc(sizeof(struct tty));
    result->preexec = result->postexec = NULL;
    result->dev = copy_xmlstr_and_free(devattr);
    result->cmd = copy_xmlstr_and_free(cmdattr);
    result->display = copy_xmlstr_and_free(dispattr);
    result->authority = copy_xmlstr_and_free(authattr);
    
    lit = parse_literal(typeattr, ttytypes);
    xmlFree(typeattr);
    
    if(lit == NULL)
    {
	errmsg("%s: Illegal value for 'type' attribute of the 'tty' element.\n", opts->filename);
        if(minattr != NULL) xmlFree(minattr);
        free_tty(result);
        return NULL;
    }
    
    result->type = lit->intval;
    
    if(result->type == fixed && result->dev == NULL)
    {
	errmsg("%s: Missing 'dev' attribute for a fixed tty.\n", opts->filename);
        free_tty(result);
        return NULL;
    }

    if(minattr != NULL)
    {
	result->min = xmlstr2ulong(minattr, 0, &dummyerr);
	xmlFree(minattr);
	
	if(dummyerr)
	{
    	    errmsg("%s: Illegal value for 'min' attribute of the 'tty' element.\n", opts->filename);
	    free_tty(result);
	    return NULL;
	}
    }
    else
	result->min = 0;

    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
    {
	enum bool failed;

	failed = false;

	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else
	PARSE_ELEMENT1("preexec", struct command, preexec, parse_command, om_always)
	PARSE_ELEMENT1("postexec", struct command, postexec, parse_command, om_always)
	{
	    errmsg("%s: Illegal occurence of element '%s' within 'tty' element.\n", opts->filename, cur->name);
	    failed = true;
	}
	
	if(failed)
	{
	    free_tty(result);
	    return NULL;
	}
    }

    REVERSE(struct command, result->preexec);
    REVERSE(struct command, result->postexec);

    return result;
}

struct configuration *parse_file(const char *filename)
{
    xmlDocPtr doc;
    xmlNodePtr cur;
    
    struct gen_options gen_options;
    struct gen_options *opts;
    struct configuration *result;
    
    doc = xmlParseFile(filename);
    if(doc == NULL)
    {
	errmsg("%s: Document not parsed successfully.\n", filename);
	return NULL;
    }
    
    cur = xmlDocGetRootElement(doc);
    
    if (cur == NULL)
    {
	errmsg("%s: Empty document.\n", filename);
	xmlFreeDoc(doc);
	return NULL;
    }
    
    if(xmlStrcmp(cur->name, (const xmlChar*) "dmcrypt"))
    {
	errmsg("%s: XML document has a wrong root element.\n", filename);
	xmlFreeDoc(doc);
	return NULL;
    }
    
    gen_options.filename = filename;
    gen_options.fsck = true; /* Default values */
    gen_options.mount = true;
    
    opts = &gen_options;

    result = (struct configuration*) xmalloc(sizeof(struct configuration));
    result->actions = NULL;
    result->storage = NULL;
    result->ttys = NULL;
    result->access = NULL;
    result->action_tree = result->storage_tree = result->key_tree = NULL;
        
    for(cur = cur->xmlChildrenNode; cur!=NULL; cur = cur->next)
    {
	enum bool failed;
	
	failed = false;
	
	if(cur->type != XML_ELEMENT_NODE); /* Do nothing. */
	else if(!xmlStrcmp(cur->name, (const xmlChar*) "option"))
	{
	    xmlChar *nameattr;
	    xmlChar *valueattr;
	    
	    nameattr = xmlGetProp(cur, (const xmlChar*) "name");
	    if(nameattr == NULL)
	    {
	        errmsg("%s: 'option' element without 'name' attribute.\n", filename);
	        xmlFreeDoc(doc);
		free_config(result);
		return NULL;
	    }

	    valueattr = xmlGetProp(cur, (const xmlChar*) "value");
	    if(valueattr == NULL)
	    {
	        errmsg("%s: 'option' element without 'value' attribute.\n", filename);
		xmlFree(nameattr);
	        xmlFreeDoc(doc);
		free_config(result);
		return NULL;
	    }
	    
	    if(!xmlStrcmp(nameattr, (const xmlChar*) "fsck"))
	    {
		struct literal *lit;
		lit = parse_literal(valueattr, yesno);
		if(lit == NULL)
		{
		    errmsg("%s: Illegal value for 'fsck' option.\n", filename);
		    
		    failed = true;
		}
		else
		    gen_options.fsck = lit->intval;
	    }
	    else if(!xmlStrcmp(nameattr, (const xmlChar*) "mount"))
	    {
		struct literal *lit;
		lit = parse_literal(valueattr, yesno);
		if(lit == NULL)
		{
		    errmsg("%s: Illegal value for 'mount' option.\n", filename);
		    
		    failed = true;
		}
		else
		    gen_options.mount = lit->intval;
	    }
	    else
	    {
	        errmsg("%s: 'option' element with unknown name.\n", filename);
	        
	        failed = true;
	    }
	    
	    xmlFree(nameattr);
	    xmlFree(valueattr);
	}
	else
	PARSE_ELEMENT("tty", struct tty, ttys, parse_tty)
	PARSE_ELEMENT("storage", struct storage, storage, parse_storage)
	PARSE_ELEMENT("action", struct action, actions, parse_action)
	PARSE_ELEMENT("allow", struct access, access, parse_access)
	PARSE_ELEMENT("deny", struct access, access, parse_access)
	{
	    errmsg("%s: Illegal occurence of element '%s' within 'dmcrypt' element.\n", filename, cur->name);
	    
	    failed = true;
	}
	
	if(failed)
	{
	    xmlFreeDoc(doc);
	    free_config(result);
	    return NULL;
	}
    }

    xmlFreeDoc(doc);
    
    REVERSE(struct storage, result->storage);
    REVERSE(struct action, result->actions);
    REVERSE(struct tty, result->ttys);
    REVERSE(struct access, result->access);
    
    result->fsck = gen_options.fsck;
    result->mount = gen_options.mount;

    return result;
}
