/*****************************************************************************
 * Filename:    cryptfs/cryptfs.c                                            *
 * Description: The main routine                                             *
 * Copyright:   2009 by Alexander Motzkau                                    *
 *****************************************************************************/
#include <getopt.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

#include "actions.h"
#include "misc.h"
#include "xml.h"
#include "tty.h"

#define DEFAULT_XML "/etc/cryptfs.xml"

struct actionname
{
    char* name;
    struct actionname* next;
};

struct main_options
{
    char* xmlfile;
    struct actionname* actions;
    int ignore;
    enum on_mode mode;
    enum bool noenc;
    enum bool remove;
    enum bool mode_set;
};

void free_main_options(struct main_options* opts)
{
    struct actionname* ac;
    
    xfree(opts->xmlfile);

    ac = opts->actions;
    while(ac!=NULL)
    {
	struct actionname* temp;
	temp = ac;
	ac = ac->next;
	xfree(temp->name);
	xfree(temp);
    };
    
    xfree(opts);
};

struct main_options* parse_options(int argc, char* argv[])
{
    struct main_options* result;
    
    result = (struct main_options*)xmalloc(sizeof(struct main_options));
    result->xmlfile = NULL;
    result->actions = NULL;
    result->ignore = 0;
    result->mode = om_always;
    result->noenc = false;
    result->remove = false;
    result->mode_set = false;
    
    while(1)
    {
	int o;
	static struct option long_opts[] =
	    {
		{ "filename", 1, NULL, 'f'},
		{ "ignore", 0, NULL, 'i'},
		{ "only-map", 0, NULL, 'm'},
		{ "only-mount", 0, NULL, 'n'},
		{ "remove", 0, NULL, 'r'},
		{ "syslog", 0, NULL, 's'},
		{ "help", 0, NULL, 'h'},
		{ "no-enc", 0, NULL, 'e'},
		{ NULL, 0, NULL, 0 },
	    };
	    
	o = getopt_long(argc, argv, "f:rishmen", long_opts, NULL);
	if(o == -1)
	    break;
	
	switch(o)
	{
	    case '?':
		free_main_options(result);
		return NULL;
	    case 'f':
		if(result->xmlfile != NULL)
		{
		    errmsg("Filename given twice.\n");
		    free_main_options(result);
		    return NULL;
		}
		result->xmlfile = xmallocstr(optarg);
		break;
	    case 'i':
		result->ignore = 1;
		break;
	    case 'm':
		if(result->mode == on_mount)
		    result->mode = om_always;
		else
		    result->mode = on_map;
		result->mode_set = true;
		break;
	    case 'n':
		if(result->mode == on_map)
		    result->mode = om_always;
		else
		    result->mode = on_mount;
		result->mode_set = true;
		break;
	    case 'e':
		result->noenc = true;
		break;
	    case 'r':
		result->remove = true;
		break;
	    case 's':
		use_syslog = 1;
		break;
	    case 'h':
		printf("Usage: cryptfs [options] [action ...]\n\n"
		       "Options:\n"
		       "  -f, --filename=XMLFILE  File containing the definitions.\n"
		       "                          Default: /etc/cryptfs.xml\n"
		       "  -i, --ignore            Ignore unknown actions.\n"
		       "  -m, --only-map          Don't fsck and mount.\n"
		       "  -n, --only-mount        Do everything after mapping.\n"
		       "  -e, --no-enc            Don't encrypt, just map the area.\n"
		       "  -s, --syslog            Error messages to syslog.\n"
		       "  -h, --help              Show options.\n");
		free_main_options(result);
		return NULL;
	}
    }
    
    while(optind < argc)
    {
	struct actionname* action;
	action = (struct actionname*) xmalloc(sizeof(struct actionname));
	
	action->name = xmallocstr(argv[--argc]);
	action->next = result->actions;
	result->actions = action;
    }
    
    return result;
};

extern char **environ;
void clean_environment()
{
    /* We'll keep LC_* (unless it contains a '/'), LANG, LANGUAGE (as LC_*),
       DISPLAY, XAUTHORITY, TZ, TERM, POSIXLY_CORRECT
       We'll set: PATH=/bin:/usr/bin, IFS= \t\n, 
     */
    const char * allowed[] =
	{
	    "DISPLAY=",
	    "XAUTHORITY=",
	    "TZ=",
	    "TERM=",
	    "POSIXLY_CORRECT=",
	    "HOME=",
	    "USER=",
	    NULL
	};

    const char * checked[] =
	{
	    "LC_",
	    "LANG=",
	    "LANGUAGE=",
	    NULL
	};
    const char * done = "";
    
    char **esrc, **edest;

    for(esrc = edest = environ; *esrc; esrc++)
    {
	const char ** good;
	
	for (good = allowed; *good; good++)
	    if (*good != done && !strncmp(*esrc, *good, strlen(*good)))
	    {
		/* It's one of the good ones. */
		*(edest++) = *esrc;
		*good = done;
		break;
	    }

	for (good = checked; *good; good++)
	    if (*good != done &&
	        !strncmp(*esrc, *good, strlen(*good)) &&
		!strchr(*esrc, '/'))
	    {
		/* This one is also okay. */
		*(edest++) = *esrc;
		*good = done;
		break;
	    }
    }
    
    *edest = NULL;

    setenv("PATH","/bin:/usr/bin", 1);
    setenv("IFS"," \t\n", 1);
}

int main(int argc, char* argv[])
{
    struct configuration *config;
    struct main_options* opts;
    struct actionname* ac;
    struct actionname* pac;
    enum bool access;

    reserve_tty();
    setlocale(LC_CTYPE, "");
    
    if (getuid() != geteuid() || getgid() != getegid())
    {
	/* Run with setuid or setgid bit. */
	/*  uid = user running the program
           euid = user owning the file
	*/
	clean_environment();
	umask(022);
    }
        
    opts = parse_options(argc, argv);
    if(opts == NULL)
	return 1;

    if(getuid() != geteuid() || getgid() != getegid())
    {
	if(opts->xmlfile != NULL)
	{
	    errmsg("-f is not allowed while running with setuid or setgid bit.\n");
	    free_main_options(opts);
	    return 1;
	}
    }
    
    if(opts->remove && (opts->noenc || opts->mode_set))
    {
	errmsg("-r conflicts with -m, -n or -e.\n");
	free_main_options(opts);
	return 1;
    }
    
    if(opts->xmlfile==NULL)
	opts->xmlfile = xmallocstr(DEFAULT_XML);

    config = parse_file(opts->xmlfile);
    if(config == NULL)
	return 1;

    if(!complete_config(config))
	return 1;

    config->noenc = opts->noenc;
    if(config->noenc)
	config->mode = on_map;
    else if(opts->remove)
	config->mode = on_remove;
    else
	config->mode = opts->mode;
    if (getuid() != geteuid())
    {
	config->suid = true;
	config->caller_uid = getuid();
	config->priv_uid = geteuid();
    }
    else
	config->suid = false;
    if (getgid() != getegid())
    {
	config->sgid = true;
	config->caller_gid = getgid();
	config->priv_gid = getegid();
    }
    else
	config->sgid = false;
    
    access = check_access_list(config->access, config, false);
    
    if(opts->actions == NULL) /* No action given. */
    {
	struct action *act;
	for(act = config->actions; act != NULL; act = act->next)
	    if(check_action(act, access, config))
		printf("%s\n", act->name);
	return 0;
    }

    for(pac = NULL, ac = opts->actions; ac != NULL; )
    {
	if(find_action(ac->name, config)==NULL)
	{
	    if(opts->ignore)
	    {
		if(pac == NULL)
		{
		    opts->actions = ac->next;
		    xfree(ac);
		    ac = opts->actions;
		}
		else
		{
		    pac->next = ac->next;
		    xfree(ac);
		    ac = pac->next;
		}

		continue;
	    }
	    else
	    {
		errmsg("Action '%s' undefined.\n", ac->name);
		return 1;
	    }
	}
	
	pac = ac;
	ac = ac->next;
    }
    
    for(ac = opts->actions; ac != NULL; ac = ac->next)
	if(!handle_action(ac->name, NULL, access, true, config))
	    errmsg("Action '%s' failed.\n", ac->name);

    release_tty();
        
    return 0;
}
