/*
 * LuCe.c				Modulo Kernel per Linux per tenere
 *					d'occhio il sistema, ed aggiungere
 *					sicurezza 'al volo' ad un 'running'
 *					preesistente. Contiene una semplice
 *					implementazione dei securelevel BSD
 *					in attesa dell'arrivo ufficiale con
 *					le Linux Capabilities [POSIX 1.e] 
 *					nel kernel 2.4.x di solide ACL.
 *					Per maggiori informazioni leggete
 *					il relativo articolo sul numero 8 
 *					di BFi, liberamente prelevabile e
 *					consultabile dal seguente URL:
 *
 *					---[ http://www.s0ftpj.org/bfi/ ]---
 *
 *                                          __NO__(C)2000 FuSyS [S0ftPj|BFi]
 *                                                        <fusys@s0ftpj.org>
 *
 *
 * Compilate con:       		gcc -c -O2 -fomit-frame-pointer LuCe.c
 * Installate con:      		insmod LuCe.o <secure=level>
 *
 * 3l33t quote:         		"wow, e funzica anche su Solaris ?"
 * Tnx'n'credits:                     	4.4BSD, Daemon9(Hardening the ...), 
 *					Pragmatic(LKM paper), BFi
 *
 */

#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>
#include <linux/dcache.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <sys/syscall.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <asm/segment.h>

#define LKMNAME		"LuCe"
#define DISK		6

volatile int secure=0;
MODULE_PARM(secure, "i");

char *protetti[10]={"/etc/passwd", "/etc/shadow", "/dev/mem", "/dev/kmem", NULL,
			NULL, NULL, NULL, NULL, NULL};
struct inode luxes[10];

extern void *sys_call_table[];
int (*old_open)(const char*, int, mode_t);
int (*old_unlink)(const char*);
int (*old_ioctl) (unsigned int, unsigned int, unsigned long);
int (*old_umount)(char*, int);
int (*old_mount)(char*, char*, char*, unsigned long, void*);
int (*old_execve)(struct pt_regs);
int (*old_query_module)(const char *, int, char *, size_t, size_t *);
unsigned long (*old_create_module)(const char*, size_t);

int inocpy(struct inode * inode, struct inode *dest)
{
        if (!dest) return -EFAULT;
        memcpy(dest,inode,sizeof(struct inode));
        return 0;
}

int inocmp(struct inode *in1, struct inode *in2)
{
        if (kdev_t_to_nr(in1->i_dev)!=kdev_t_to_nr(in2->i_dev)) return 1;
        if (in1->i_ino!=in2->i_ino) return 1;
        return 0;
}

/* ~linux/fs/stat.c */
int do_revalidate(struct dentry *dentry)
{
	struct inode *inode = dentry->d_inode;
	if(inode->i_op && inode->i_op->revalidate)
		return inode->i_op->revalidate(dentry);
	return 0;
}

/* ~linux/fs/namei.c */
struct dentry *kernelnamei(const char *name)
{
	struct dentry *dentry;

	dentry=lookup_dentry(name, NULL, 1);
	if(!IS_ERR(dentry)){
		if(!dentry->d_inode){
			dput(dentry);
			dentry = ERR_PTR(-ENOENT);
		}
	}
	return dentry;
}

/* ~linux/fs/namei.c */
void get_lux_inode(const char *name, struct inode *inode)
{
	struct dentry *dentry;
	int error;

	dentry = kernelnamei(name);
	error=PTR_ERR(dentry);
	if(!IS_ERR(dentry)){
		error = do_revalidate(dentry);
		if(!error) inocpy(dentry->d_inode, inode);
		dput(dentry);
	}
}

void get_luxes()
{
        int i=0;

        while(protetti[i] && i<10) {
                get_lux_inode(protetti[i], &luxes[i]);
                i++;
        }
}

int lux_open(const char *filename, int flags, int mode)
{
   	int i=0;
	struct inode lux;
	char *name;

	if(current->pid != 1 || current->p_opptr->pid != 1){
	 name=getname(filename);
         get_lux_inode(name, &lux);

	 while(protetti[i]){
	   if(!inocmp(&lux, &luxes[i])){
		if(flags & (O_RDWR|O_WRONLY)){
	   	   printk(KERN_INFO
	   	   "LuCe: Tentativo di Scrittura su %s mediante %s [UID %d TTY %s]\n", 
		   filename, current->comm, current->uid,
		   current->tty->driver.driver_name);
		putname(name);
	       	return -EACCES;
	   	}
           }
      	   i++;
   	 }
	
         if(secure) {
        	if((S_ISBLK(lux.i_mode))&&(lux.i_gid==DISK)){
                	if(flags & (O_RDWR|O_WRONLY)){
			   if(current->pid != 1 || current->p_opptr->pid != 1){
                        	printk(KERN_INFO
                        	"LuCe: Accesso Raw al disco %s mediante %s [UID %d TTY %s]\n",
                        	filename, current->comm, current->uid,
                        	current->tty->driver.driver_name);
                        	putname(name);
                        	return -EACCES;
			   }
                   	}
           	}
         }
	}
   	return (*old_open)(filename, flags, mode);
}

int lux_unlink(const char *pathname)
{
	int i=0;
	struct inode lux;
	char *name;
	
        name=getname(pathname);
        get_lux_inode(name, &lux);

        while(protetti[i]){
           	if(!inocmp(&lux, &luxes[i])){
			printk(KERN_INFO
			"LuCe: Tentativo di Unlink su %s mediante %s [UID %d TTY %s]\n",
			pathname, current->comm, current->uid, current->tty->driver.driver_name);
			putname(name);
			return -EACCES;
		}
		i++;
	}
	putname(name);
	return (*old_unlink)(pathname);
}

int lux_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
	unsigned int flags;
	struct file *f;
	
	if(cmd == EXT2_IOC_SETFLAGS){
	   f=current->files->fd[fd];
	   if(get_user(flags, (int *)arg)) return -EFAULT;
	   if(secure){
		if(((flags&(EXT2_APPEND_FL|EXT2_IMMUTABLE_FL))^
		   (f->f_dentry->d_inode->u.ext2_i.i_flags&(EXT2_APPEND_FL|EXT2_IMMUTABLE_FL)))){
			printk(KERN_INFO
			"LuCe: Tentativo di Modifica %s su %s mediante %s [UID %d TTY %s]\n",
			(flags&(EXT2_APPEND_FL))?"EXT2_APPEND_FL":"EXT2_IMMUTABLE_FL",
			f->f_dentry->d_name.name, current->comm, current->uid, 
			current->tty->driver.driver_name); 
			return -EPERM;
		}
		else return (*old_ioctl)(fd, cmd, arg);
	   }
	}
	return (*old_ioctl)(fd, cmd, arg);
}

int lux_umount(char *name, int flags)
{
	if(secure){
		printk(KERN_INFO
		"LuCe: Tentativo di Umount su %s [UID %d TTY %s]\n", name,
		current->uid, current->tty->driver.driver_name);
		return -EPERM;
	}
	return (*old_umount)(name, flags);
}

int lux_mount(char * dev_name, char * dir_name, char * type,
		unsigned long new_flags, void * data)
{
	if(secure){
                printk(KERN_INFO
                "LuCe: Tentativo di Mount su %s [UID %d TTY %s]\n", dev_name,
                current->uid, current->tty->driver.driver_name);
                return -EPERM;
        }
	return (*old_mount)(dev_name, dir_name, type, new_flags, data);	
}

int lux_execve(struct pt_regs regs)
{
	char *filename;
	char **argvs;
	int error;

	filename=getname((char *) regs.ebx);
	argvs = (char **)regs.ecx;
	error = PTR_ERR(filename);
        if (IS_ERR(filename)) return error;
	if(strstr(filename, "/sbin/init")){
		if((strstr(argvs[1], "6"))||(strstr(argvs[1], "0"))){
			printk(KERN_INFO "LuCe: Chiusura del Sistema\n");
			secure = 0;
		}
	}
	error = do_execve(filename,(char **)regs.ecx,(char **)regs.edx,&regs);
        if (error == 0) current->flags &= ~PF_DTRACE;
	putname(filename);
	return error;
}

void ttycredit(char *str)
{
   	struct tty_struct *mytty;

   	if((mytty = current->tty) != NULL) {
   		(*(mytty->driver).write)(mytty, 0, str, strlen(str));
   	}
}

unsigned long lux_create_module(const char *name, size_t size)
{
	if(secure){
		printk(KERN_INFO
		"LuCe: Tentativo di Caricamento Modulo %s [UID %d TTY %s]\n",
		name, current->uid, current->tty->driver.driver_name);
		return -EPERM;
	}
	else return (*old_create_module)(name, size);
}

int new_query_module(const char *name, int which, char *buf, size_t bufsize,
        size_t *ret)
{
        int res, cnt, errno=0;
        char *ptr, *match;

        res = (*old_query_module)(name, which, buf, bufsize, ret);

        if(res == -1)
                return(-errno);

        if(which != QM_MODULES)
                return(res);

        ptr = buf;

        for(cnt = 0; cnt < *ret; cnt++) {
                if(!strcmp(LKMNAME, ptr)) {
                        match = ptr;
                        while(*ptr)
                                ptr++;
                        ptr++;
                        memcpy(match, ptr, bufsize - (ptr - (char *)buf));
                        (*ret)--;
                        return(res);
                }
                while(*ptr)
                        ptr++;
                ptr++;
        }

        return(res);
}

int init_module(void)
{
   	EXPORT_NO_SYMBOLS;

	get_luxes();
   	old_open = sys_call_table[SYS_open];
   	sys_call_table[SYS_open] = (void *) lux_open;
	old_unlink = sys_call_table[SYS_unlink];
	sys_call_table[SYS_unlink] = (void *) lux_unlink;
	old_ioctl = sys_call_table[SYS_ioctl];
	sys_call_table[SYS_ioctl] = (void *) lux_ioctl;
	old_umount = sys_call_table[SYS_umount];
	sys_call_table[SYS_umount] = (void *) lux_umount;
	old_mount = sys_call_table[SYS_mount];
	sys_call_table[SYS_mount] = (void *) lux_mount;
	old_execve = sys_call_table[SYS_execve];
	sys_call_table[SYS_execve] = (void *) lux_execve;
	old_create_module = sys_call_table[SYS_create_module];
	sys_call_table[SYS_create_module] = (void *) lux_create_module;
        old_query_module = sys_call_table[SYS_query_module];
        sys_call_table[SYS_query_module]=(void *) new_query_module;

   	ttycredit("\n\033[1;32mLuCe \033[1;34m[Linux 2.2.x LKM] by FuSyS [S0ftPj|BFi]\033[0m\r\n\r\n");
   	printk(KERN_INFO "LuCe LKM Attivato\n");
   	return 0;
}

void cleanup_module(void)
{
   	sys_call_table[SYS_open] = old_open;
	sys_call_table[SYS_unlink] = old_unlink;
	sys_call_table[SYS_ioctl] = old_ioctl;
	sys_call_table[SYS_umount] = old_umount;
	sys_call_table[SYS_mount] = old_mount;
	sys_call_table[SYS_execve] = old_execve;
	sys_call_table[SYS_create_module] = old_create_module;
        sys_call_table[SYS_query_module] = old_query_module; 
	ttycredit("\n\033[1;34mLuCe LKM Disattivato\033[0m\r\n\r\n");
	printk(KERN_INFO "LuCe LKM Disattivato\n");
}
