/* Dazuko Linux. Allow Linux 2.6 file access control for 3rd-party applications.
   Copyright (c) 2003 H+BEDV Datentechnik GmbH
   Written by John Ogness <jogness@antivir.de>

   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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "dazuko_linux26.h"
#include "dazuko_xp.h"
#include "dazukoio.h"

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/vermagic.h>
#include <linux/security.h>
#include <linux/namei.h>
#include <linux/dcache.h>
#include <linux/mount.h>
#include <linux/devfs_fs_kernel.h>

ssize_t linux_dazuko_device_read(struct file *, char __user *, size_t, loff_t *);
ssize_t linux_dazuko_device_write(struct file *, const char __user *, size_t, loff_t *);
int linux_dazuko_device_open(struct inode *, struct file *);
int linux_dazuko_device_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long param);
int linux_dazuko_device_release(struct inode *, struct file *);

static struct vfsmount  *orig_rootmnt = NULL;
static struct dentry    *orig_root = NULL;
static int dev_major = -1;

static int secondary_register = 0;

static inline int dazuko_inode_permission(struct inode *inode, int mask, struct nameidata *nd);

static struct file_operations	fops = {
		.owner		= THIS_MODULE,
		.read		= linux_dazuko_device_read,
		.write		= linux_dazuko_device_write,
		.ioctl		= linux_dazuko_device_ioctl,
		.open		= linux_dazuko_device_open,
		.release	= linux_dazuko_device_release,
	};


/* mutex */

inline int xp_init_mutex(struct xp_mutex *mutex)
{
	init_MUTEX(&(mutex->mutex));
	return 0;
}

inline int xp_down(struct xp_mutex *mutex)
{
	down(&(mutex->mutex));
	return 0;
}

inline int xp_up(struct xp_mutex *mutex)
{
	up(&(mutex->mutex));
	return 0;
}

inline int xp_destroy_mutex(struct xp_mutex *mutex)
{
	return 0;
}


/* read-write lock */

inline int xp_init_rwlock(struct xp_rwlock *rwlock)
{
	rwlock_init(&(rwlock->rwlock));
	return 0;
}

inline int xp_write_lock(struct xp_rwlock *rwlock)
{
	write_lock(&(rwlock->rwlock));
	return 0;
}

inline int xp_write_unlock(struct xp_rwlock *rwlock)
{
	write_unlock(&(rwlock->rwlock));
	return 0;
}

inline int xp_read_lock(struct xp_rwlock *rlock)
{
	read_lock(&(rlock->rwlock));
	return 0;
}

inline int xp_read_unlock(struct xp_rwlock *rlock)
{
	read_unlock(&(rlock->rwlock));
	return 0;
}

inline int xp_destroy_rwlock(struct xp_rwlock *rwlock)
{
	return 0;
}


/* wait-notify queue */

inline int xp_init_queue(struct xp_queue *queue)
{
	init_waitqueue_head(&(queue->queue));
	return 0;
}

inline int xp_wait_until_condition(struct xp_queue *queue, int (*cfunction)(void *), void *cparam, int allow_interrupt)
{
	/* wait until cfunction(cparam) != 0 (condition is true) */

	if (allow_interrupt)
	{
		return wait_event_interruptible(queue->queue, cfunction(cparam) != 0);
	}
	else
	{
		wait_event(queue->queue, cfunction(cparam) != 0);
	}

	return 0;
}

inline int xp_notify(struct xp_queue *queue)
{
	wake_up(&(queue->queue));
	return 0;
}

inline int xp_destroy_queue(struct xp_queue *queue)
{
	return 0;
}


/* memory */

inline void* xp_malloc(size_t size)
{
	return kmalloc(size, GFP_KERNEL);
}

inline int xp_free(void *ptr)
{
	kfree(ptr);
	return 0;
}

inline int xp_copyin(const void *user_src, void *kernel_dest, size_t size)
{
	return copy_from_user(kernel_dest, user_src, size);
}

inline int xp_copyout(const void *kernel_src, void *user_dest, size_t size)
{
	return copy_to_user(user_dest, kernel_src, size);
}

inline int xp_verify_user_writable(const void *user_ptr, size_t size)
{
	return 0;
}

inline int xp_verify_user_readable(const void *user_ptr, size_t size)
{
	return 0;
}


/* path attribute */

inline int xp_is_absolute_path(const char *path)
{
	return (path[0] == '/');
}


/* atomic */

inline int xp_atomic_set(struct xp_atomic *atomic, int value)
{
	atomic_set(&(atomic->atomic), value);
	return 0;
}

inline int xp_atomic_inc(struct xp_atomic *atomic)
{
	atomic_inc(&(atomic->atomic));
	return 0;
}

inline int xp_atomic_dec(struct xp_atomic *atomic)
{
	atomic_dec(&(atomic->atomic));
	return 0;
}

inline int xp_atomic_read(struct xp_atomic *atomic)
{
	return atomic_read(&(atomic->atomic));
}


/* file descriptor */

inline int xp_copy_file(struct xp_file *dest, struct xp_file *src)
{
	dest->file = src->file;
	return 0;
}

inline int xp_compare_file(struct xp_file *file1, struct xp_file *file2)
{
	return (file1->file != file2->file);
}


/* file structure */

/* this code is taken directy from:
   linux/fs/dcache.c
   because __d_path is no longer exported.
 */
/**
 * d_path - return the path of a dentry
 * @dentry: dentry to report
 * @vfsmnt: vfsmnt to which the dentry belongs
 * @root: root dentry
 * @rootmnt: vfsmnt to which the root dentry belongs
 * @buffer: buffer to return value in
 * @buflen: buffer length
 *
 * Convert a dentry into an ASCII path name. If the entry has been deleted
 * the string " (deleted)" is appended. Note that this is ambiguous.
 *
 * Returns the buffer or an error code if the path was too long.
 *
 * "buflen" should be positive. Caller holds the dcache_lock.
 */
static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
			struct dentry *root, struct vfsmount *rootmnt,
			char *buffer, int buflen)
{
	char * end = buffer+buflen;
	char * retval;
	int namelen;

	*--end = '\0';
	buflen--;
	if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
		buflen -= 10;
		end -= 10;
		if (buflen < 0)
			goto Elong;
		memcpy(end, " (deleted)", 10);
	}

	if (buflen < 1)
		goto Elong;
	/* Get '/' right */
	retval = end-1;
	*retval = '/';

	for (;;) {
		struct dentry * parent;

		if (dentry == root && vfsmnt == rootmnt)
			break;
		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
			/* Global root? */
			if (vfsmnt->mnt_parent == vfsmnt)
				goto global_root;
			dentry = vfsmnt->mnt_mountpoint;
			vfsmnt = vfsmnt->mnt_parent;
			continue;
		}
		parent = dentry->d_parent;
		prefetch(parent);
		namelen = dentry->d_name.len;
		buflen -= namelen + 1;
		if (buflen < 0)
			goto Elong;
		end -= namelen;
		memcpy(end, dentry->d_name.name, namelen);
		*--end = '/';
		retval = end;
		dentry = parent;
	}

	return retval;

global_root:
	namelen = dentry->d_name.len;
	buflen -= namelen;
	if (buflen < 0)
		goto Elong;
	retval -= namelen-1;	/* hit the slash */
	memcpy(retval, dentry->d_name.name, namelen);
	return retval;
Elong:
	return ERR_PTR(-ENAMETOOLONG);
}

static int dazuko_get_full_filename(struct xp_file_struct *xfs)
{
	struct vfsmount *rootmnt;
	struct dentry *root;
	char *temp;

	if (xfs == NULL)
		return 0;

	if (xfs->inode == NULL)
		return 0;

	if (!S_ISREG(xfs->inode->i_mode))
		return 0;

	if (xfs->nd == NULL || xfs->free_full_filename)
		return 0;
	
	if (xfs->nd->mnt == NULL || xfs->nd->dentry == NULL)
		return 0;

	/* check if we need to allocate a buffer */
	if (!xfs->free_page_buffer)
	{
		/* get pre-requisites for d_path function */
		xfs->buffer = (char *)__get_free_page(GFP_USER);

		/* make sure we got a page */
		if (xfs->buffer == NULL)
			return 0;

		/* the buffer will need to be freed */
		xfs->free_page_buffer = 1;
	}

	/* make sure we don't already have a vfsmount */
	if (!xfs->mntput_vfsmount)
	{
		xfs->vfsmount = mntget(xfs->nd->mnt);

		/* the vfsmount will need to be put back */
		xfs->mntput_vfsmount = 1;
	}

	/* make sure we don't already have a dentry */
	if (!xfs->dput_dentry)
	{
		xfs->dentry = dget(xfs->nd->dentry);

		/* the dentry will need to be put back */
		xfs->dput_dentry = 1;
	}

	rootmnt = mntget(orig_rootmnt);
	root = dget(orig_root);

	spin_lock(&dcache_lock);
	temp = __d_path(xfs->dentry, xfs->vfsmount, root, rootmnt, xfs->buffer, PAGE_SIZE);
	spin_unlock(&dcache_lock);

	dput(root);
	mntput(rootmnt);

	/* make sure we really got a new filename */
	if (temp == NULL)
		return 0;
	
	xfs->full_filename_length = dazuko_get_filename_length(temp);

	xfs->full_filename = (char *)xp_malloc(xfs->full_filename_length + 1);
	if (xfs->full_filename == NULL)
		return 0;
	
	/* the char array will need to be freed */
	xfs->free_full_filename = 1;

	memcpy(xfs->full_filename, temp, xfs->full_filename_length + 1);

	/* we have a filename with the full path */

	return 1;
}

int xp_file_struct_check(struct dazuko_file_struct *dfs)
{
	if (dfs == NULL)
		return -1;

	/* make sure we can get the full path */
	if (dazuko_get_full_filename(dfs->extra_data))
	{
		/* reference copy of full path */
		dfs->filename = dfs->extra_data->full_filename;

		dfs->filename_length = dfs->extra_data->full_filename_length;

		dfs->file_p.size = dfs->extra_data->inode->i_size;
		dfs->file_p.set_size = 1;
		dfs->file_p.uid = dfs->extra_data->inode->i_uid;
		dfs->file_p.set_uid = 1;
		dfs->file_p.gid = dfs->extra_data->inode->i_gid;
		dfs->file_p.set_gid = 1;
		dfs->file_p.mode = dfs->extra_data->inode->i_mode;
		dfs->file_p.set_mode = 1;
		dfs->file_p.device_type = dfs->extra_data->inode->i_rdev;
		dfs->file_p.set_device_type = 1;

		return 0;
	}

	return -1;
}

int xp_file_struct_check_cleanup(struct dazuko_file_struct *dfs)
{
	if (dfs == NULL)
		return 0;

	if (dfs->extra_data == NULL)
		return 0;

	if (dfs->extra_data->free_page_buffer)
	{
		free_page((unsigned long)dfs->extra_data->buffer);
		dfs->extra_data->free_page_buffer = 0;
	}

	if (dfs->extra_data->dput_dentry)
	{
		dput(dfs->extra_data->dentry);
		dfs->extra_data->dput_dentry = 0;
	}

	if (dfs->extra_data->mntput_vfsmount)
	{
		mntput(dfs->extra_data->vfsmount);
		dfs->extra_data->mntput_vfsmount = 0;
	}

	return 0;
}

static int dazuko_file_struct_cleanup(struct dazuko_file_struct **dfs)
{
	if (dfs == NULL)
		return 0;

	if (*dfs == NULL)
		return 0;

	xp_file_struct_check_cleanup(*dfs);

	if ((*dfs)->extra_data)
	{
		if ((*dfs)->extra_data->free_full_filename)
			xp_free((*dfs)->extra_data->full_filename);

		xp_free((*dfs)->extra_data);
	}

	xp_free(*dfs);

	*dfs = NULL;

	return 0;
}


/* daemon id */

int xp_id_compare(struct xp_daemon_id *id1, struct xp_daemon_id *id2)
{
	if (id1 == NULL || id2 == NULL)
		return -1;

	/* if file's are available and they match,
	 * then we say that the id's match */
	if (id1->file != NULL && id1->file == id2->file)
		return 0;

	if (id1->pid == id2->pid)
		return 0;

	return 1;
}

int xp_id_free(struct xp_daemon_id *id)
{
	xp_free(id);

	return 0;
}

struct xp_daemon_id* xp_id_copy(struct xp_daemon_id *id)
{
	struct xp_daemon_id	*ptr;

	if (id == NULL)
		return NULL;

	ptr = (struct xp_daemon_id *)xp_malloc(sizeof(struct xp_daemon_id));

	if (ptr != NULL)
	{
		ptr->pid = id->pid;
		ptr->file = id->file;
	}

	return ptr;
}


/* system hooks */

static inline int dazuko_inode_permission(struct inode *inode, int mask, struct nameidata *nd)
{
	struct dazuko_file_struct *dfs = NULL;
	int error = 0;
	int check_error = 0;
	struct event_properties event_p;
	struct xp_daemon_id xp_id;

	xp_id.pid = current->pid;
	xp_id.file = NULL;

	check_error = dazuko_sys_check(DAZUKO_ON_OPEN, 1, &xp_id);

	if (!check_error)
	{
		dazuko_bzero(&event_p, sizeof(event_p));
/*
		event_p.flags = flags;
		event_p.set_flags = 1;
		event_p.mode = mode;
		event_p.set_mode = 1;
*/
		event_p.pid = current->pid;
		event_p.set_pid = 1;
		event_p.uid = current->uid;
		event_p.set_uid = 1;

		dfs = (struct dazuko_file_struct *)xp_malloc(sizeof(struct dazuko_file_struct));
		if (dfs != NULL)
		{
			dazuko_bzero(dfs, sizeof(struct dazuko_file_struct));

			dfs->extra_data = (struct xp_file_struct *)xp_malloc(sizeof(struct xp_file_struct));
			if (dfs->extra_data != NULL)
			{
				dazuko_bzero(dfs->extra_data, sizeof(struct xp_file_struct));

				dfs->extra_data->nd = nd;
				dfs->extra_data->inode = inode;

				error = dazuko_sys_pre(DAZUKO_ON_OPEN, dfs, &event_p);
			}
			else
			{
				xp_free(dfs);
				dfs = NULL;
			}

			dazuko_file_struct_cleanup(&dfs);
		}
	}

	if (error)
		return XP_ERROR_PERMISSION;

	return 0;
}

static struct security_operations dazuko_ops = {
	.inode_permission =	dazuko_inode_permission,
};

inline int xp_sys_hook()
{
	/* Make sure we have a valid task_struct. */

	if (current == NULL)
	{
		xp_print("dazuko: panic (current == NULL)\n");
		return -1;
	}
	if (current->fs == NULL)
	{
		xp_print("dazuko: panic (current->fs == NULL)\n");
		return -1;
	}
	if (current->fs->root == NULL)
	{
		xp_print("dazuko: panic (current->fs->root == NULL)\n");
		return -1;
	}
	if (current->fs->rootmnt == NULL)
	{
		xp_print("dazuko: panic (current->fs->rootmnt == NULL)\n");
		return -1;
	}

	/* Grab the current root. This is assumed to be the real.
	 * If it is not the real root, we could have problems
	 * looking up filenames. */

	read_lock(&current->fs->lock);
	orig_rootmnt = current->fs->rootmnt;
	orig_root = current->fs->root;
	read_unlock(&current->fs->lock);

	/* register with the security manager */

	if (register_security(&dazuko_ops) != 0)
	{
		if (mod_reg_security(DEVICE_NAME, &dazuko_ops) != 0)
		{
			xp_print("dazuko: failed to register\n");
			return XP_ERROR_INVALID;
		}

		secondary_register = 1;
	}

	dev_major = register_chrdev(0, DEVICE_NAME, &fops);
	if (dev_major < 0)
	{
		xp_print("dazuko: unable to register device, err=%d\n", dev_major);
		return dev_major;
	}

	devfs_mk_cdev(MKDEV(dev_major, 0), S_IFCHR | S_IRUSR | S_IWUSR, DEVICE_NAME);

	/* initialization complete */

	return 0;
}

inline int xp_sys_unhook()
{
	unregister_chrdev(dev_major, DEVICE_NAME);

	devfs_remove(DEVICE_NAME);

	if (secondary_register)
		mod_unreg_security(DEVICE_NAME, &dazuko_ops);
	else
		unregister_security(&dazuko_ops);

	return 0;
}


/* output */

int xp_print(const char *fmt, ...)
{
	va_list args;
	char *p;
	size_t size = 1024;

	p = (char *)xp_malloc(size);
	if (!p)
		return -1;

	va_start(args, fmt);
	vsprintf(p, fmt, args);
	va_end(args);

	p[size-1] = 0;

	printk(p);

	xp_free(p);

	return 0;
}


/* ioctl's */

int linux_dazuko_device_open(struct inode *inode, struct file *file)
{
	DPRINT(("dazuko: linux_dazuko_device_open() [%d]\n", current->pid));

	return 0;
}

ssize_t linux_dazuko_device_read(struct file *file, char *buffer, size_t length, loff_t *pos)
{
	/* Reading from the dazuko device simply
	 * returns the device number. This is to
	 * help out the daemon. */

	char    tmp[20];
	size_t  dev_major_len;

	DPRINT(("dazuko: linux_dazuko_device_read() [%d]\n", current->pid));

	/* non-root daemons are ignored */
	if (current->uid != 0)
		return 0;
	
	if (*pos != 0)
		return 0;

	if (dev_major < 0)
		return XP_ERROR_NODEVICE;

	/* print dev_major to a string
	 * and get length (with terminator) */
	dazuko_bzero(tmp, sizeof(tmp));

	dev_major_len = dazuko_snprintf(tmp, sizeof(tmp), "%d", dev_major) + 1;

	if (tmp[sizeof(tmp)-1] != 0)
	{
		xp_print("dazuko: failing device_read, device number overflow for dameon %d (dev_major=%d)\n", current->pid, dev_major);
		return XP_ERROR_FAULT;
	}

	if (length < dev_major_len)
		return XP_ERROR_INVALID;

	/* copy dev_major string to userspace */
	if (xp_copyout(tmp, buffer, dev_major_len) != 0)
		return XP_ERROR_FAULT;

	*pos += dev_major_len;

	return dev_major_len;
}

ssize_t linux_dazuko_device_write(struct file *file, const char *buffer, size_t length, loff_t *pos)
{
	struct dazuko_request   *u_request;
	struct xp_daemon_id	xp_id;
	char			tmpbuffer[32];
	char			*value;
	int			size;

	/* non-root daemons are ignored */
	if (current->uid != 0)
		return length;

	size = length;
	if (length >= sizeof(tmpbuffer))
		size = sizeof(tmpbuffer) -1;

	/* copy request pointer string to kernelspace */
	if (xp_copyin(buffer, tmpbuffer, size) != 0)
		return XP_ERROR_FAULT;

	tmpbuffer[size] = 0;

	if (dazuko_get_value("\nRA=", tmpbuffer, &value) != 0)
	{
		xp_print("dazuko: error: linux_dazuko_device_write.RA missing\n");
		return XP_ERROR_FAULT;
	}

	u_request = (struct dazuko_request *)simple_strtoul(value, NULL, 10);

	xp_free(value);

	xp_id.pid = current->pid;
	xp_id.file = file;

	if (dazuko_handle_user_request(u_request, &xp_id) == 0)
		return size;

	return XP_ERROR_INTERRUPT;
}

int linux_dazuko_device_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long param)
{
	/* A daemon uses this function to interact with
	 * the kernel. A daemon can set scanning parameters,
	 * give scanning response, and get filenames to scan. */

	struct xp_daemon_id	xp_id;
	int			error = 0;

	/* non-root daemons are ignored */
	if (current->uid != 0)
		return 0;

	if (param == 0)
	{
		xp_print("dazuko: error: linux_dazuko_device_ioctl(..., 0)\n");
		return XP_ERROR_INVALID;
	}

	xp_id.pid = current->pid;
	xp_id.file = file;

	error = dazuko_handle_user_request_compat12((void *)param, _IOC_NR(cmd), &xp_id);

	if (error != 0)
	{
		/* general error occurred */

		return XP_ERROR_PERMISSION;
	}

	return error;
}

int linux_dazuko_device_release(struct inode *inode, struct file *file)
{
	struct xp_daemon_id	xp_id;

	DPRINT(("dazuko: dazuko_device_release() [%d]\n", current->pid));

	/* non-root daemons are ignored */
	if (current->uid != 0)
		return 0;

	xp_id.pid = current->pid;
	xp_id.file = file;

	return dazuko_unregister_daemon(&xp_id);
}


/* init/exit */

static int __init linux_dazuko_init(void)
{
	return dazuko_init();
}

static void __exit linux_dazuko_exit(void)
{
	dazuko_exit();
}


MODULE_AUTHOR("H+BEDV Datentechnik GmbH <linux_support@antivir.de>");
MODULE_DESCRIPTION("allow 3rd-party file access control");
MODULE_LICENSE("GPL");
MODULE_INFO(vermagic, VERMAGIC_STRING);

security_initcall(linux_dazuko_init);
module_exit(linux_dazuko_exit);
