/*
 * Copyright (C) 1999-2000 Wojciech Purczynski <wp@elzabsoft.pl>
 *
 * 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.
 */

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>

#if CONFIG_MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/kernel.h>
#include <linux/capability.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/sched.h>

#include "global.h"
#include "config.h"
#include "file.h"
#include "lib.h"

cap_name_t cap_names[];
size_t allocated_mem=0;

/* Info on allocating structures:
 * To ensure that close_conf() properly frees all allocated memory
 * every allocated structure must have valid pointer that points
 * to it. Ensure that every created object is joined into the list.
 */

void * malloc(size_t size)
{
	void *ptr;
	ptr=kmalloc(size, GFP_KERNEL);
	if (ptr) allocated_mem+=size;
	else printk(KERN_ERR "Unable to allocate memory (not enough?)\n");
	return ptr;
}

void free(const void * ptr, size_t size)
{
	kfree_s(ptr, size);
	allocated_mem-=size;
}

size_t allocated_memory(void) 
{ 
	return allocated_mem; 
}

rule_t * first_rule=NULL;
rule_t * last_rule=NULL;

#define CAP_INIT_NONE				\
{						\
	{ CAP_EMPTY_SET, CAP_EMPTY_SET },	\
	{ CAP_EMPTY_SET, CAP_EMPTY_SET },	\
	{ CAP_EMPTY_SET, CAP_EMPTY_SET },	\
}

#define CAP_INIT_LOWER_ALL			\
{						\
	{ CAP_EMPTY_SET, CAP_FULL_SET },	\
	{ CAP_EMPTY_SET, CAP_FULL_SET },	\
	{ CAP_EMPTY_SET, CAP_FULL_SET },	\
}

/* Note:
 * All created rules inherit list of executables from default_rule
 * and should be freed from memory once.
 */
rule_t default_rule=
{
	"default",		/* name		*/
	FALSE,			/* check_uid 	*/
	0,			/* uid 		*/
	FALSE,			/* check_gid 	*/
	0,			/* gid 		*/
	TRUE,			/* check_groups	*/
	EXEC_NONE,		/* check_execve */
	EXEC_ALLOW,		/* perm_execve 	*/
	TRUE,			/* check_chroot */
	FALSE,			/* ignore	*/
	LOG_SECURITY,		/* log_level	*/
	NULL,			/* executables  */
	NULL,			/* root */
	CAP_INIT_NONE,		/* caps */
#ifdef USE_UIDCAP
	CAP_INIT_LOWER_ALL,	/* uidcaps */
#endif /* USE_UIDCAP */
	NULL
};

/* Some prototypes */
int log_error(const char *);
void close_conf(void);
int read_rule(const char *);

char * read_word(void)
{
	char * word;
	char chr;
	
	for (;;) {
		word=file_read_word();
		if (!word) return NULL;
		
		if (!strncmp(word, "//", 2) || *word=='#') {
			do chr=file_get_char();
			while (chr>0 && chr!=0x0a);
			if (chr<=0) return NULL;
		} else if (!strncmp(word, "/*",2)) {
			for (;;) {
				int len;
				word=file_read_word();
				if (!word) return NULL;
				len=strlen(word);
				if (len>=2 && !strcmp(word+len-2, "*/")) break;
			}
		} else break;
	}
	return word;
}

/* parser macros */
#define READ_WORD(word) { (word)=read_word(); if (!(word)) return log_error("Unexpected end of file"); }
#define IS_WORD(val) (!strcmp(word, (val)))
#define IS_TRUE(val) (IS_WORD("true") || IS_WORD("yes"))
#define IS_FALSE(val) (IS_WORD("false") || IS_WORD("no"))
#define AT_LINE " at line "

#define LVL_LOWER	0
#define LVL_RAISE	1
#define LVL_SET		2
#define LVL_MASK	3

int read_conf(const char * filename)
{
	char * word;
	int error;

	error=file_open(filename);
	if (error<0) return error;

	while ( (word=read_word())!=NULL ) {
		if IS_WORD("eof") break;
		else if IS_WORD("end") break;
		else {
			error=read_rule(word);
			if (error) break;
		}
	}

	file_close();

	return error;
}

int read_options(rule_t*);

int read_rule(const char * filename)
{
	char * word;
	rule_t * rule;
	int error;

	/* Allocating new rule */
	if (!strcmp(filename, "default")) {
		rule=&default_rule;
	} else {
		rule=malloc(sizeof(rule_t));
		if (!rule) 
			return log_error("Out of memory for rule_t");
		*rule=default_rule;
		rule->name=NULL;
		
		if (default_rule.root) {
			rule->root=malloc(strlen(default_rule.root)+1);
			if (!rule->root)
				return log_error("Out of memory for root");
			strcpy(rule->root, default_rule.root);
		}

		if (!first_rule) first_rule=rule;
		if (last_rule) last_rule->next=rule;
		last_rule=rule;
		rule->next=NULL;
	}

	/* Initializing rule */
	if (*filename=='/') {
		word=malloc(strlen(filename)+1);
		if (!word) 
			return log_error("Out of memory for filename");
		strcpy(word, filename);
		rule->name=word;
	} else if (rule!=&default_rule && strcmp(filename, "any"))
		return log_error("Absolute pathname expected" AT_LINE);

	READ_WORD(word);
	if (!(IS_WORD("{"))) return log_error("'{' expected" AT_LINE);

	error=read_options(rule);
	return error;
}

int read_cap_opt(capabilities_t*, int, capabilities_t*);
int read_exec_opt(rule_t*);

int read_options(rule_t * rule)
{
	char * word;
	int error;
	static int was_def_exec=0;
	int was_exec=0;

	for (;;) {
		READ_WORD(word);
		if IS_WORD("}") break;
		else if IS_WORD("uid") {
			READ_WORD(word);
			if IS_WORD("any") {
				rule->check_uid=FALSE;
				rule->uid=0;
			} else {
				if (!isdigit(*word))
					return log_error("UID value expected" AT_LINE);
				rule->check_uid=TRUE;
				rule->uid=simple_strtoul(word, NULL, 10);
			}
		} else if IS_WORD("gid") {
			READ_WORD(word);
			if IS_WORD("any") {
				rule->check_gid=FALSE;
				rule->gid=0;
			} else {
				if (!isdigit(*word))
					return log_error("GID value expected" AT_LINE);
				rule->check_uid=TRUE;
				rule->gid=simple_strtoul(word, NULL, 10);
			}
		} else if IS_WORD("groups") {
			READ_WORD(word);
			if IS_TRUE(word) {
				rule->check_groups=TRUE;
			} else if IS_FALSE(word) {
				rule->check_groups=FALSE;
			} else return log_error("Boolean value expected" AT_LINE);
		} else if IS_WORD("chroot") {
			READ_WORD(word);
			if IS_TRUE(word) {
				rule->check_chroot=TRUE;
			} else if IS_FALSE(word) {
				rule->check_chroot=FALSE;
			} else return log_error("Boolean value expected" AT_LINE);
		} else if IS_WORD("log") {
			READ_WORD(word);
			if IS_WORD("nothing")
				rule->log_level=LOG_NOTHING;
			else if IS_WORD("debug")
				rule->log_level=LOG_DEBUG;
			else if IS_WORD("security")
				rule->log_level=LOG_SECURITY;
			else return log_error("Syntax error" AT_LINE);
		} else if IS_WORD("cap") {
			capabilities_t def=CAP_INIT_NONE;
			if (rule==&default_rule) 
				printk(KERN_WARNING "%s: warning: "
				       "cap option used in default rule\n",
				       NAME);
			error=read_cap_opt(&rule->caps, LVL_SET, &def);
			if (error) return error;
#ifdef USE_UIDCAP
		} else if IS_WORD("uidcap") {
			capabilities_t def=CAP_INIT_LOWER_ALL;
			if (rule==&default_rule) 
				printk(KERN_WARNING "%s: warning: " 
				       "uidcap option used in default rule\n",
				       NAME);
			error=read_cap_opt(&rule->uidcaps, LVL_MASK, &def);
			if (error) return error;
#endif /* USE_UIDCAP */
		} else if IS_WORD("exec") {
			if (was_exec || (rule==&default_rule && was_def_exec))
				return log_error("Duplicate exec option" AT_LINE);
			if (rule->executables==default_rule.executables)
				rule->executables=NULL;
			error=read_exec_opt(rule);
			if (error) return error;
			
			if (rule==&default_rule) was_def_exec=1;
			was_exec=1;
		} else if IS_WORD("root") {
			READ_WORD(word);
			if (rule->root) {
				free(rule->root, strlen(rule->root)+1);
				rule->root=NULL;
			}
			if (!IS_WORD("default")) {
				rule->root=malloc(strlen(word)+1);
				if (!rule->root)
					return log_error("Out of memory for root");
				strcpy(rule->root, word);
			}
		} else if IS_WORD("ignore") {
			READ_WORD(word);
			if IS_TRUE(word) {
				rule->ignore=TRUE;
			} else if IS_FALSE(word) {
				rule->ignore=FALSE;
			} else return log_error("Boolean value expected" AT_LINE);
		} else return log_error("Unknown option" AT_LINE);
	}
	return 0;
}

int read_exec_opt(rule_t * rule)
{
	char * word;
	
	READ_WORD(word);
	
	if IS_WORD("allow") {
		rule->perm_execve=EXEC_ALLOW;
		READ_WORD(word);
	} if IS_WORD("deny") {
		rule->perm_execve=EXEC_DENY;
		READ_WORD(word);
	}
	
	if IS_WORD("all") {
		rule->check_execve=EXEC_ALL;
		return 0;
	}
	
	if IS_WORD("{") {
		rule->check_execve=EXEC_LIST;
		for (;;) {
			executable_t * exec;
			READ_WORD(word);
			if IS_WORD("}") break;
			if (*word!='/')
				return log_error("Absolute pathname expected" AT_LINE);
			exec=(executable_t*)malloc(sizeof(executable_t));
			if (!exec) return log_error("Out of memory for executable_t");
	
			exec->name=NULL;
			exec->next=rule->executables;
			rule->executables=exec;
		
			exec->name=malloc(strlen(word)+1);
			if (!exec->name) return log_error("Out of memory for exec->name");
			strcpy(exec->name, word);
		} 
	} else return log_error("'{' expected" AT_LINE);
	
	return 0;
}

int read_cap_set(kernel_cap_t*);
void rule_set_cap(cap_mask_t *, kernel_cap_t, int);

int read_cap_opt(capabilities_t * caps, int level, capabilities_t * def)
{
	char * word;
	int error;
	int type;
	kernel_cap_t capset;

	type=0;
	for (;;) {
		READ_WORD(word);
		if IS_WORD("effective")
			type|=CAP_EFFECTIVE;
		else if IS_WORD("permitted") 
			type|=CAP_PERMITTED;
		else if IS_WORD("inheritable") 
			type|=CAP_INHERITABLE;
		else break;
	}
	
	if (type==0) type=CAP_EFFECTIVE|CAP_PERMITTED|CAP_INHERITABLE;
	
	if IS_WORD("set") {
		level=LVL_SET;
		READ_WORD(word);
	} else if IS_WORD("raise") {
		level=LVL_RAISE;
		READ_WORD(word);
	} else if IS_WORD("lower") {
		level=LVL_LOWER;
		READ_WORD(word);
	} else if IS_WORD("mask") {
		level=LVL_MASK;
		READ_WORD(word);
	} else if IS_WORD("default") {
		if (type & CAP_EFFECTIVE)
			caps->effective=def->effective;
		if (type & CAP_PERMITTED)
			caps->permitted=def->permitted;
		if (type & CAP_INHERITABLE)
			caps->inheritable=def->inheritable;
		return 0;
	}

	if IS_WORD("empty")
		cap_clear(capset);
	else if IS_WORD("full")
		cap_set_full(capset);
	else if IS_WORD("{") {
		error=read_cap_set(&capset);
		if (error) return error;
	} else return log_error("Syntax error" AT_LINE);
	
	if (type & CAP_EFFECTIVE) 
		rule_set_cap(&caps->effective, capset, level);
	if (type & CAP_PERMITTED)
		rule_set_cap(&caps->permitted, capset, level);
	if (type & CAP_INHERITABLE)
		rule_set_cap(&caps->inheritable, capset, level);

	return 0;
}

int read_cap_set(kernel_cap_t * caps)
{
	char * word;
	
	cap_clear(*caps);
	
	for (;;) {
		cap_name_t * cap;
		READ_WORD(word);
		if IS_WORD("}") break;
		for (cap=cap_names; cap->name; cap++)
			if IS_WORD(cap->name) break;
		if (cap->name) *caps=cap_combine(*caps, cap->value);
		else return log_error("Invalid capability name" AT_LINE);
	}
	return 0;
}

void rule_set_cap(cap_mask_t * capmask, kernel_cap_t capval, int level)
{
	switch (level) {
	case LVL_LOWER: {
		capmask->raise=cap_drop(capmask->raise, capval);
		capmask->lower=cap_combine(capmask->lower, capval);
	} break;
	case LVL_RAISE: {
		capmask->raise=cap_combine(capmask->raise, capval);
		capmask->lower=cap_drop(capmask->lower, capval);
	} break;
	case LVL_SET: {
		cap_t(capmask->raise)=cap_t(capval);
		capmask->lower=cap_invert(capval);
	} break;
	case LVL_MASK: {
		capmask->raise=cap_intersect(capmask->raise, capval);
		capmask->lower=cap_invert(capval);
	} break;
	default:
		printk(KERN_ERR "Invalid level in rule_set_cap() "
				"in capsel module\n");
	}
}

int log_error(const char *reason)
{
	if (strlen(reason)>=strlen(AT_LINE) && 
		!strcmp(AT_LINE, reason+strlen(reason)-strlen(AT_LINE))) 
		printk(KERN_ERR "%s: %s%i\n", NAME, reason, file_line_no());
	else
		printk(KERN_ERR "%s: %s\n", NAME, reason);
	
	return -1;
}

void close_conf(void)
{
	rule_t * rule, * nxtrule;
	executable_t * exec, * nxtexec;
	
	/* Freeing memory */
	for (rule=first_rule; rule; rule=nxtrule) {
		nxtrule=rule->next;
		
		if (rule->name)
			free(rule->name, strlen(rule->name)+1);
		
		if (rule->executables==default_rule.executables) 
			rule->executables=NULL;

		if (rule->root)
			free(rule->root, strlen(rule->root)+1);

		rule->name=NULL;	
		rule->root=NULL;

		for (exec=rule->executables; exec; exec=nxtexec) {
			nxtexec=exec->next;
			
			if (exec->name)
				free(exec->name, strlen(exec->name)+1);

			free(exec, sizeof(executable_t));
		}
		
		free(rule, sizeof(rule_t));
	}
	
	for (exec=default_rule.executables; exec; exec=nxtexec) {
		nxtexec=exec->next;
		if (exec->name)
			free(exec->name, strlen(exec->name)+1);
		free(exec, sizeof(executable_t));
	}

	first_rule=last_rule=NULL;
	
	if (allocated_memory()!=0)
		printk(KERN_ERR "%s: Memory leakage %i\n", NAME, 
			allocated_memory());
}

/* Main function that allows to find matching rule */
rule_t * find_rule(const char * filename)
{
	struct rule_t *rule;
	
	for (rule=first_rule; rule; rule=rule->next) {
		if (rule->name && strcmp(rule->name, filename)) continue;
		/* checking other options if paths are the same */
		if (
			/* checking UID */
			( !rule->check_uid ||
			( UID==rule->uid || EUID==rule->uid )) &&
			/* checking GID */
			( !rule->check_gid || 
		    	( GID==rule->gid || EGID==rule->gid )) &&
			/* checking supplemental group IDs */
		    	( !rule->check_gid || !rule->check_groups ||
			  in_group_p(rule->gid) )
		) break;
	}

	if (!rule) rule=&default_rule;
	
	return rule;
}

cap_name_t cap_names[]={
	{ "chown", to_cap_t(CAP_TO_MASK(CAP_CHOWN)) },
	
	{ "dac_override", to_cap_t(CAP_TO_MASK(CAP_DAC_OVERRIDE)) },
	{ "dac", to_cap_t(CAP_TO_MASK(CAP_DAC_OVERRIDE)) },
	
	{ "dac_read_search", to_cap_t(CAP_TO_MASK(CAP_DAC_READ_SEARCH)) },
	{ "dacrs", to_cap_t(CAP_TO_MASK(CAP_DAC_READ_SEARCH)) },
	
	{ "fowner", to_cap_t(CAP_TO_MASK(CAP_FOWNER)) },
	
	{ "fsetid", to_cap_t(CAP_TO_MASK(CAP_FSETID)) },
	
	{ "fs_mask", to_cap_t(CAP_FS_MASK) },
	{ "fs", to_cap_t(CAP_FS_MASK) },
	
	{ "kill", to_cap_t(CAP_TO_MASK(CAP_KILL)) },
	
	{ "setgid", to_cap_t(CAP_TO_MASK(CAP_SETGID)) },
	
	{ "setuid", to_cap_t(CAP_TO_MASK(CAP_SETUID)) },
	
	{ "setpcap", to_cap_t(CAP_TO_MASK(CAP_SETPCAP)) },
	
	{ "linux_immutable", to_cap_t(CAP_TO_MASK(CAP_LINUX_IMMUTABLE)) },
	{ "immutable", to_cap_t(CAP_TO_MASK(CAP_LINUX_IMMUTABLE)) },
	
	{ "net_bind_service", to_cap_t(CAP_TO_MASK(CAP_NET_BIND_SERVICE)) },
	{ "bind", to_cap_t(CAP_TO_MASK(CAP_NET_BIND_SERVICE)) },
	
	{ "net_broadcast", to_cap_t(CAP_TO_MASK(CAP_NET_BROADCAST)) },
	{ "broadcast", to_cap_t(CAP_TO_MASK(CAP_NET_BROADCAST)) },
	
	{ "net_admin", to_cap_t(CAP_TO_MASK(CAP_NET_ADMIN)) },
	{ "netadmin", to_cap_t(CAP_TO_MASK(CAP_NET_ADMIN)) },
	{ "net", to_cap_t(CAP_TO_MASK(CAP_NET_ADMIN)) },
	
	{ "net_raw", to_cap_t(CAP_TO_MASK(CAP_NET_RAW)) },
	{ "raw", to_cap_t(CAP_TO_MASK(CAP_NET_RAW)) },
	
	{ "ipc_lock", to_cap_t(CAP_TO_MASK(CAP_IPC_LOCK)) },
	
	{ "ipc_owner", to_cap_t(CAP_TO_MASK(CAP_IPC_OWNER)) },
	{ "ipc", to_cap_t(CAP_TO_MASK(CAP_IPC_OWNER)) },
	
	{ "sys_module", to_cap_t(CAP_TO_MASK(CAP_SYS_MODULE)) },
	{ "module", to_cap_t(CAP_TO_MASK(CAP_SYS_MODULE)) },
	
	{ "sys_rawio", to_cap_t(CAP_TO_MASK(CAP_SYS_RAWIO)) },
	{ "rawio", to_cap_t(CAP_TO_MASK(CAP_SYS_RAWIO)) },
	
	{ "sys_chroot", to_cap_t(CAP_TO_MASK(CAP_SYS_CHROOT)) },
	{ "chroot", to_cap_t(CAP_TO_MASK(CAP_SYS_CHROOT)) },
	
	{ "sys_ptrace", to_cap_t(CAP_TO_MASK(CAP_SYS_PTRACE)) },
	{ "ptrace", to_cap_t(CAP_TO_MASK(CAP_SYS_PTRACE)) },
	
	{ "sys_pacct", to_cap_t(CAP_TO_MASK(CAP_SYS_PACCT)) },
	{ "pacct", to_cap_t(CAP_TO_MASK(CAP_SYS_PACCT)) },
	
	{ "sys_admin", to_cap_t(CAP_TO_MASK(CAP_SYS_ADMIN)) },
	{ "admin", to_cap_t(CAP_TO_MASK(CAP_SYS_ADMIN)) },
	
	{ "sys_boot", to_cap_t(CAP_TO_MASK(CAP_SYS_BOOT)) },
	{ "boot", to_cap_t(CAP_TO_MASK(CAP_SYS_BOOT)) },
	
	{ "sys_nice", to_cap_t(CAP_TO_MASK(CAP_SYS_NICE)) },
	{ "nice", to_cap_t(CAP_TO_MASK(CAP_SYS_NICE)) },
	
	{ "sys_resource", to_cap_t(CAP_TO_MASK(CAP_SYS_RESOURCE)) },
	{ "resource", to_cap_t(CAP_TO_MASK(CAP_SYS_RESOURCE)) },
	
	{ "sys_time", to_cap_t(CAP_TO_MASK(CAP_SYS_TIME)) },
	{ "time", to_cap_t(CAP_TO_MASK(CAP_SYS_TIME)) },
	
	{ "sys_tty_config", to_cap_t(CAP_TO_MASK(CAP_SYS_TTY_CONFIG)) },
	{ "ttyconfig", to_cap_t(CAP_TO_MASK(CAP_SYS_TTY_CONFIG)) },
	
	{ NULL, CAP_EMPTY_SET }
};

