
#include "ipf-linux.h"
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)
# include <linux/devfs_fs_kernel.h>
#endif

#ifdef MODULE
MODULE_SUPPORTED_DEVICE("ipf");
MODULE_AUTHOR("Darren Reed");
MODULE_DESCRIPTION("IP-Filter Firewall");
MODULE_LICENSE("(C)Copyright 2003-2004 Darren Reed");
#endif

static int ipf_open(struct inode *, struct file *);
static ssize_t ipf_write(struct file *, const char *, size_t, loff_t *);
static ssize_t ipf_read(struct file *, char *, size_t, loff_t *);
static u_int ipf_poll(struct file *fp, struct poll_table_struct *wait);
extern int ipf_ioctl(struct inode *, struct file *, u_int, u_long);


#ifdef	CONFIG_DEVFS_FS
static	char	*ipf_devfiles[] = { IPL_NAME, IPNAT_NAME, IPSTATE_NAME,
				    IPAUTH_NAME, IPSYNC_NAME, IPSCAN_NAME,
				    IPLOOKUP_NAME, NULL };
#endif

static struct file_operations ipf_fops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	.owner = THIS_MODULE,
#endif
	open:	ipf_open,
	read:	ipf_read,
	write:	ipf_write,
	ioctl:	ipf_ioctl,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	poll:	ipf_poll,
#endif
};


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static	devfs_handle_t	dh[IPL_LOGSIZE];
#endif
static	int		ipfmajor = 0;

ipf_main_softc_t	ipfmain;

int
uiomove(address, nbytes, rwflag, uiop)
	caddr_t address;
	size_t nbytes;
	int rwflag;
	uio_t *uiop;
{
	int err = 0;

	if (rwflag == UIO_READ) {
		err = copy_to_user(uiop->uio_buf, address, nbytes);
		if (err == 0) {
			uiop->uio_resid -= nbytes;
			uiop->uio_buf += nbytes;
		}
	} else if (rwflag == UIO_WRITE) {
		err = copy_from_user(uiop->uio_buf, address, nbytes);
		if (err == 0) {
			uiop->uio_resid -= nbytes;
			uiop->uio_buf += nbytes;
		}
	}
	if (err)
		return -EFAULT;
	return 0;
}


static int
ipf_open(struct inode *in, struct file *fp)
{
	int unit, err;

	unit = MINOR(in->i_rdev);

	if (unit < 0 || unit > IPL_LOGMAX) {
		err = -ENXIO;
	} else {
		switch (unit)
		{
		case IPL_LOGIPF :
		case IPL_LOGNAT :
		case IPL_LOGSTATE :
		case IPL_LOGAUTH :
		case IPL_LOGLOOKUP :
		case IPL_LOGSYNC :
#ifdef IPFILTER_SCAN
		case IPL_LOGSCAN :
#endif
			err = 0;
			break;
		default :
			err = -ENXIO;
			break;
		}
	}
	return err;
}


static ssize_t
ipf_write(struct file *fp, const char *buf, size_t count, loff_t *posp)
{
	struct inode *i;
	int unit, err;
	uio_t uio;

	i = fp->f_dentry->d_inode;
	unit = MINOR(i->i_rdev);

	if (unit != IPL_LOGSYNC)
		return -ENXIO;

	uio.uio_rw = UIO_WRITE;
	uio.uio_iov = NULL;
	uio.uio_file = fp;
	uio.uio_buf = (char *)buf;
	uio.uio_iovcnt = 0;
	uio.uio_offset = *posp;
	uio.uio_resid = count;

	err = ipf_sync_write(&ipfmain, &uio);
	if (err > 0)
		err = -err;
	return err;
}


static ssize_t
ipf_read(struct file *fp, char *buf, size_t count, loff_t *posp)
{
	struct inode *i;
	int unit, err;
	uio_t uio;

	i = fp->f_dentry->d_inode;
	unit = MINOR(i->i_rdev);

	uio.uio_rw = UIO_READ;
	uio.uio_iov = NULL;
	uio.uio_file = fp;
	uio.uio_buf = (char *)buf;
	uio.uio_iovcnt = 0;
	uio.uio_offset = *posp;
	uio.uio_resid = count;

	switch (unit)
	{
	case IPL_LOGSYNC :
		err = ipf_sync_read(&ipfmain, &uio);
		break;
	default :
		err = ipf_log_read(&ipfmain, unit, &uio);
		if (err == 0)
			return count - uio.uio_resid;
		break;
	}

	if (err > 0)
		err = -err;
	return err;
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
static u_int
ipf_poll(struct file *fp, poll_table *wait)
{
	struct inode *i;
	u_int revents;
	int unit;

	revents = 0;
	i = fp->f_dentry->d_inode;
	unit = MINOR(i->i_rdev);
	if (unit < 0 || unit > IPL_LOGMAX)
		return 0;

	poll_wait(fp, &ipfmain.iplh_linux[unit], wait);

	switch (unit)
	{
	case IPL_LOGIPF :
	case IPL_LOGNAT :
	case IPL_LOGSTATE :
# ifdef IPFILTER_LOG
		if (ipf_log_canread(&ipfmain, unit))
			revents = (POLLIN | POLLRDNORM);
# endif
		break;
	case IPL_LOGAUTH :
		if (ipf_auth_waiting(&ipfmain))
			revents = (POLLIN | POLLRDNORM);
		break;
	case IPL_LOGSYNC :
		if (ipf_sync_canread(&ipfmain))
			revents = (POLLIN | POLLRDNORM);
		break;
	case IPL_LOGSCAN :
	case IPL_LOGLOOKUP :
	default :
		break;
	}

	return revents;
}
#endif


static int
ipfilter_init(void)
{
	char *defpass;
	int i;

	ipfmajor = register_chrdev(0, "ipf", &ipf_fops);
	if (ipfmajor < 0) {
		printf("unable to get major for ipf devs (%d)\n", ipfmajor);
		return -EINVAL;
	}

#ifdef	CONFIG_DEVFS_FS
	for (i = 0; ipf_devfiles[i] != NULL; i++) {
		char *s;

		s = strrchr(ipf_devfiles[i], '/');
		if (s != NULL) {
# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			dh[i] = devfs_register(NULL, s + 1, DEVFS_FL_DEFAULT,
					       ipfmajor, i, 0600|S_IFCHR,
					       &ipf_fops, NULL);
# else
			devfs_mk_cdev(MKDEV(ipfmajor, i),0600|S_IFCHR,s+1);
# endif
		}
	}
#endif

	i = ipf_load_all();
	if (i != 0)
		return i;

	ipf_create_all(&ipfmain);

	i = ipfattach(&ipfmain);

	if (i != 0) {
		ipf_destroy_all(&ipfmain);
		ipf_unload_all();
		return i;
	}

	if (FR_ISPASS(ipfmain.ipf_pass))
		defpass = "pass";
	else if (FR_ISBLOCK(ipfmain.ipf_pass))
		defpass = "block";
	else
		defpass = "no-match -> block";

	printk(KERN_INFO "%s initialized.  Default = %s all, Logging = %s%s\n",
	       ipfilter_version, defpass,
# ifdef IPFILTER_LOG
		"enabled",
# else
		"disabled",
# endif
# ifdef IPFILTER_COMPILED
		" (COMPILED)"
# else
		""
# endif
		);

	ipfmain.ipf_running = 1;

	return 0;
}


static int
ipfilter_fini(void)
{
	int result;
#ifdef	CONFIG_DEVFS_FS
	int i;
#endif

	if (ipfmain.ipf_refcnt)
		return -EBUSY;

	if (ipfmain.ipf_running >= 0) {
		result = ipfdetach(&ipfmain);
		if (result != 0) {
			if (result > 0)
				result = -result;
			return result;
		} else {
			ipf_destroy_all(&ipfmain);
			ipf_unload_all();
		}
	}

	ipfmain.ipf_running = -2;


#ifdef	CONFIG_DEVFS_FS
	for (i = 0; ipf_devfiles[i] != NULL; i++) {
		char *s;

		s = strrchr(ipf_devfiles[i], '/');
		if (s != NULL)
# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			devfs_unregister_chrdev(ipfmajor, s + 1);
# else
			devfs_remove(s+1);
# endif
	}
#endif

	if (ipfmajor >= 0)
		unregister_chrdev(ipfmajor, "ipf");
	printk(KERN_INFO "%s unloaded\n", ipfilter_version);

	return 0;
}


static int __init
ipf_init(void)
{
	int result;

	result = ipfilter_init();
	return result;
}


static void __exit ipf_fini(void)
{
	(void) ipfilter_fini();
}

module_init(ipf_init)
module_exit(ipf_fini)
