/*
 * 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/version.h>
#include <linux/capability.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/errno.h>	
#include <linux/smp_lock.h>

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

MODULE_AUTHOR("Copyright (c) 1999-2000 by Wojciech Purczynski <wp@elzabsoft.pl>");
MODULE_DESCRIPTION("capsel - Linux kernel security module"); 
char kernel_version[]=UTS_RELEASE;

/* pointers to store original syscall handlers */
int (*old_execve)(struct pt_regs);
int (*old_chroot)(const char *);
#ifdef USE_UIDCAP
int (*old_setreuid)(uid_t, uid_t);
int (*old_setuid)(uid_t);
int (*old_setresuid)(uid_t, uid_t, uid_t);
#endif /* USE_UIDCAP */

asmlinkage int new_execve(struct pt_regs);
asmlinkage int new_chroot(const char *);
#ifdef USE_UIDCAP
asmlinkage int new_setreuid(uid_t, uid_t);
asmlinkage int new_setuid(uid_t);
asmlinkage int new_setresuid(uid_t, uid_t, uid_t);
#endif /* USE_UIDCAP */

/*** init_module ***/
int init_module(void)
{
	int status;
	unsigned long flags;

	printk(KERN_INFO NAME " module " VERSION " installing...\n");

	if ((status=read_conf(CONFIG_FILE))) {
		close_conf();
		return status;
	}
	save_flags(flags);
	cli();
	
	/* execve */
	old_execve=sys_call_table[__NR_execve];
	sys_call_table[__NR_execve]=new_execve;
	/* chroot */
	old_chroot=sys_call_table[__NR_chroot];
	sys_call_table[__NR_chroot]=new_chroot;
#ifdef USE_UIDCAP
	/* setreuid */
	old_setreuid=sys_call_table[__NR_setreuid];
	sys_call_table[__NR_setreuid]=new_setreuid;
	/* setuid */
	old_setuid=sys_call_table[__NR_setuid];
	sys_call_table[__NR_setuid]=new_setuid;
	/* setresuid */
	old_setresuid=sys_call_table[__NR_setresuid];
	sys_call_table[__NR_setresuid]=new_setresuid;
#endif /* USE_UIDCAP */
	
	restore_flags(flags);

	printk(KERN_INFO NAME " module " VERSION " installed succesfully\n");
	printk(KERN_INFO NAME " module " VERSION " configuration=%i bytes\n",
		allocated_memory());
	return 0;
} /* init_module */

void cleanup_module(void)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	sys_call_table[__NR_execve]=old_execve;
	sys_call_table[__NR_chroot]=old_chroot;
#ifdef USE_UIDCAP
	sys_call_table[__NR_setreuid]=old_setreuid;
	sys_call_table[__NR_setuid]=old_setuid;
	sys_call_table[__NR_setresuid]=old_setresuid;
#endif /* USE_UIDCAP */

	restore_flags(flags);
	close_conf();

	printk(KERN_INFO NAME " module " VERSION " uninstalled\n");
} /* cleanup_module */

/* Checking if process is allowed to use execve */
inline int check_execve(rule_t * rule, const char *filename)
{
	struct executable_t *exec;
	
	if (rule->check_execve==EXEC_LIST) {
		for (exec=rule->executables; exec; exec=exec->next)
			if (!strcmp(exec->name, filename)) break; /* found */

		if (exec) { /* found */
			if (rule->perm_execve==EXEC_DENY)
				return -EPERM;
			else
				return 0;
		} else { /* not found */
			if (rule->perm_execve==EXEC_ALLOW)
				return -EPERM;
			else
				return 0;
		}
	} else if (rule->check_execve==EXEC_ALL)
		return rule->perm_execve==EXEC_ALLOW?0:-EPERM;
	
	/* yes, we can */
	return 0;
} /* check_execve */

/*
 * Important note:
 * If CAP_ROOT_ONLY is not defined default rule should not have
 * any of the capability bits raised because every process matches 
 * default rule and it could be given privileges it should not have.
 * Be careful with lowering capabilities in default rule because it
 * may lead your system to become unusable.
 */
void set_capabilities(capabilities_t * caps)
{
	kernel_cap_t old_effective;
	kernel_cap_t old_permitted;
	kernel_cap_t old_inheritable;

#ifdef CAP_ROOT_ONLY
	if (current->euid!=0) return;
#endif

	old_effective=current->cap_effective;
	old_permitted=current->cap_permitted;
	old_inheritable=current->cap_inheritable;
		
	/* effective */
	current->cap_effective=cap_combine(
		current->cap_effective, 
		caps->effective.raise);
	current->cap_effective=cap_drop(
		current->cap_effective, 
		caps->effective.lower);
	
	/* permitted */
	current->cap_permitted=cap_combine(
		current->cap_permitted, 
		caps->permitted.raise);
	current->cap_permitted=cap_drop(
		current->cap_permitted, 
		caps->permitted.lower);

	/* inheritable */	
	current->cap_inheritable=cap_combine(
		current->cap_inheritable, 
		caps->inheritable.raise);
	current->cap_inheritable=cap_drop(
		current->cap_inheritable, 
		caps->inheritable.lower);

	if (cap_t(old_effective)!=cap_t(current->cap_effective) ||
	    cap_t(old_permitted)!=cap_t(current->cap_permitted) ||
	    cap_t(old_inheritable)!=cap_t(current->cap_inheritable)) {
		current->dumpable=0;
	}
} /* set_capabilities */
	
int change_root(rule_t * rule, const char * exename)
{
	struct dentry * dentry, * tmp;
	
	if (!rule->root) return 0;

	dentry = lookup_dentry(rule->root, NULL, LOOKUP_DIRECTORY);
	if (IS_ERR(dentry)) {
		log(rule, LOG_SECURITY, exename, "can't set root directory to", 
		    rule->root);
		return PTR_ERR(dentry);
	}
	if (!dentry->d_inode) {
		dput(dentry);
		log(rule, LOG_SECURITY, exename, "can't set root directory to",
		    rule->root);
		return -ENOENT;
	}

	/* setting new root and current working directory */
	tmp=current->fs->root;
	current->fs->root=dentry;
	dput(tmp);
	tmp=current->fs->pwd;
	current->fs->pwd=dget(dentry);
	dput(tmp);
	
	log(rule, LOG_DEBUG, exename, "root directory set to", rule->root);
	
	return 0;
}

/* Ripped from linux/arch/i386/kernel/process.c#sys_execve(...) */
asmlinkage int new_execve(struct pt_regs regs)
{
	int error;
	char * filename;
	char * exename;
	rule_t * rule;
	int cantread;
	struct dentry * dentry;

	lock_kernel();

	/* I want to check read access with current UID and capabilities */
	dentry=namei((char*)regs.ebx);
	if (IS_ERR(dentry)) return PTR_ERR(dentry);
	
	cantread=permission(dentry->d_inode, MAY_READ);
	dput(dentry);
	
	if (cantread && (current->flags & PF_PTRACED) &&
	    !capable(CAP_SYS_PTRACE))
		return -EPERM;

	if ((atomic_read(&current->fs->count)>1 ||
	     atomic_read(&current->sig->count)>1 ||
	     atomic_read(&current->files->count)>1 ) && 
	    !capable(CAP_SYS_PTRACE))
	    	return -EPERM;
	
	filename=getname((char *)regs.ebx);
	error=PTR_ERR(filename);
	if (IS_ERR(filename))
		goto out;

	exename=get_exename();
	if (!exename) {
		error=-EBUSY;
		goto put_out;
	}

	rule=find_rule(exename);
	if (!rule) {
		error=-EBUSY;
		goto putexe_out;
	}
	
	error=check_execve(rule, filename);
	
	if (error)
		log(rule, LOG_SECURITY, exename, "execve not permitted", 
		    filename);
	else
		log(rule, LOG_DEBUG, exename, "executing", filename);
	
	if (!error || rule->ignore) {
#ifdef EXECVE_CAP_WORKAROUND
		if (!issecure(SECURE_NOROOT) && current->uid==0)
			current->cap_permitted=cap_bset;
#endif /* EXECVE_CAP_WORKAROUND */
		error=do_execve(filename, (char **)regs.ecx, 
				(char **)regs.edx, &regs);
		
		if (error==0) {
			current->flags&=~PF_DTRACE;

			/* Prevent from core dumping */
			if (cantread)
				current->dumpable=0;
			
			put_exename(exename);
			exename=get_exename();
			if (!exename) {
				error=-EBUSY;
				goto put_out;
			}

			rule=find_rule(exename);
			if (!rule) {
				error=-EBUSY;
				goto putexe_out;
			}
				
			if (rule->ignore) goto putexe_out;

			set_capabilities(&rule->caps);
			error=change_root(rule, exename);
		}
	}
putexe_out:
	put_exename(exename);
put_out:
	putname(filename);
out:
	unlock_kernel();
	return error;
} /* new_execve */

inline int check_chroot(void)
{
	if (IS_ROOT(current->fs->root))
		return 0;

	return -EPERM;
} /* check_chroot */

asmlinkage int new_chroot(const char * filename)
{
	int error;
	rule_t * rule;
	char * exename;
	char * fname;

	exename=get_exename();
	if (!exename) return -EBUSY;
	rule=find_rule(exename);
	if (!rule) {
		error=-EBUSY;
		goto putexe_out;
	}

	fname=getname(filename);
	if (IS_ERR(fname)) {
		error=PTR_ERR(fname);
		goto putexe_out;
	}
	
	if (!rule->check_chroot) error=0;
	else error=check_chroot();
	
	if (!error)
		log(rule, LOG_DEBUG, exename, "changing root to", fname);
	else
		log(rule, LOG_SECURITY, exename, "chroot not permitted", fname);

	if (!error || rule->ignore) {
		error=old_chroot(filename);
		if (error)
			log(rule, LOG_DEBUG, exename, "error changing root to", fname);
	}

	putname(fname);
putexe_out:
	put_exename(exename);
	return error;
} /* new_chroot */

#ifdef USE_UIDCAP /* very large block */
#include <linux/securebits.h>

/*
 * This is modified version of cap_emulate_setxuid() which may be configured
 * not to clear all capability bits if changing UID from privileged to
 * non-privileged. Privileged UID don't have to be 0. It is specified
 * by rule->uid if rule->check_uid is set.
 *
 * It breaks POSIX.1e but allows to gracefully drop unneeded privileges
 * and switch to non-privileged user without clearing all privileges.
 * (ie: CAP_NET_BIND_SERVICE needed by most net daemons).
 * Unfortunatelly, most daemons need to be patched if you want them
 * to work correctly. But it may highly increase your system security.
 */
extern inline void new_cap_emulate_setxuid(
	rule_t * rule, uid_t old_ruid, uid_t old_euid, uid_t old_suid)
{
	uid_t uid;
	
	if (cap_isclear(cap_combine(rule->uidcaps.permitted.raise,
				    rule->uidcaps.effective.raise)) &&
	    !cap_isclear(cap_invert(cap_intersect(rule->uidcaps.permitted.lower, 
				    rule->uidcaps.effective.lower))) &&
	    rule->check_uid) {
	    	uid=rule->uid;
	} else {
		uid=0;
	}

	if ((old_ruid==uid || old_euid==uid || old_suid==uid) &&
	    (current->uid!=uid && current->euid!=uid && current->suid!=uid)) {
		/* instead of: cap_clear(current->cap_permitted); */
		current->cap_permitted=cap_combine(current->cap_permitted,
					rule->uidcaps.permitted.raise);
		current->cap_permitted=cap_drop(current->cap_permitted, 
					rule->uidcaps.permitted.lower);
		/* instead of: cap_clear(current->cap_effective); */
	    	current->cap_effective=cap_combine(current->cap_effective,
					rule->uidcaps.effective.raise);
		current->cap_effective=cap_drop(current->cap_effective,
					rule->uidcaps.effective.lower);
	}
	
	if (old_euid==uid && current->euid!=uid) {
		/* instead of: cap_clear(current->cap_effective); */
	    	current->cap_effective=cap_combine(current->cap_effective,
					rule->uidcaps.effective.raise);
		current->cap_effective=cap_drop(current->cap_effective,
					rule->uidcaps.effective.lower);
	}
	
	if (old_euid!=uid && current->euid==uid) {
		current->cap_effective=current->cap_permitted;
	}
} /* new_cap_emulate_setxuid */

/* securebits idea by wojtekka :) */
asmlinkage int new_setreuid(uid_t ruid, uid_t euid)
{
	rule_t * rule;
	uid_t old_ruid, old_euid, old_suid;
	unsigned old_securebits;
	int error;
	char * exename;
	
	exename=get_exename();
	if (!exename) return -EBUSY;
	rule=find_rule(exename);
	put_exename(exename);
	if (!rule) return -EBUSY;

	old_securebits=securebits;
	old_ruid=current->uid;
	old_euid=current->euid;
	old_suid=current->suid;
	
	if (!rule->ignore) securebits|=1<<SECURE_NO_SETUID_FIXUP;
	error=old_setreuid(ruid, euid);
	securebits=old_securebits;

	if (!error && !rule->ignore && !issecure(SECURE_NO_SETUID_FIXUP))
		new_cap_emulate_setxuid(rule, old_ruid, old_euid, old_suid);

	return error;
} /* new_setreuid */

asmlinkage int new_setuid(uid_t uid)
{
	rule_t * rule;
	uid_t old_ruid, old_euid, old_suid;
	unsigned old_securebits;
	int error;
	char * exename;
	
	exename=get_exename();
	if (!exename) return -EBUSY;
	rule=find_rule(exename);
	put_exename(exename);
	if (!rule) return -EBUSY;

	old_securebits=securebits;
	old_ruid=current->uid;
	old_euid=current->euid;
	old_suid=current->suid;
	
	if (!rule->ignore) securebits|=1<<SECURE_NO_SETUID_FIXUP;
	error=old_setuid(uid);
	securebits=old_securebits;

	if (!error && !rule->ignore && !issecure(SECURE_NO_SETUID_FIXUP))
		new_cap_emulate_setxuid(rule, old_ruid, old_euid, old_suid);

	return error;
} /* new_setuid */

asmlinkage int new_setresuid(uid_t ruid, uid_t euid, uid_t suid)
{
	rule_t * rule;
	uid_t old_ruid, old_euid, old_suid;
	unsigned old_securebits;
	int error;
	char * exename;
	
	exename=get_exename();
	if (!exename) return -EBUSY;
	rule=find_rule(exename);
	put_exename(exename);
	if (!rule) return -EBUSY;

	old_securebits=securebits;
	old_ruid=current->uid;
	old_euid=current->euid;
	old_suid=current->suid;

	if (!rule->ignore) securebits|=1<<SECURE_NO_SETUID_FIXUP;
	error=old_setresuid(ruid, euid, suid);
	securebits=old_securebits;

	if (!error && !rule->ignore && !issecure(SECURE_NO_SETUID_FIXUP))
		new_cap_emulate_setxuid(rule, old_ruid, old_euid, old_suid);

	return error;
} /* new_setresuid */

#endif /* USE_UIDCAP */
