/*
 * Copyright 1993, 1994 by Ulrich Khn. All rights reserved.
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */

/*
 * File : nfssvc.c
 *        service functions for the nfs demon
 */

#include <compiler.h>
#include <osbind.h>
#include <mintbind.h>
#include <unistd.h>
#include <time.h>
#include <utime.h>
#include <stdio.h>
#include <stat.h>
#include <string.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/statfs.h>
#include <support.h>
#include <errno.h>
#include <fcntl.h>
#include <ioctl.h>
#include "rpc.h"
#include "svc.h"
#include "auth_unix.h"
#include "../xfs/nfs.h"
#include "fh.h"
#include "util.h"
#include "nfssvc.h"
#include "auth.h"

#ifdef SYSLOG
#include <arpa/inet.h>
#include "syslog.h"
#endif


/* This is a temporary klude until the mintlib's include file contains
 * a function binding for Psetreuid() and Psetregid().
 * BUG: works only with gcc.
 */
#if 0
#define Psetreuid(a, b) trap_1_www(0x14e, (short)(a), (short)(b))
#define Psetregid(a, b) trap_1_www(0x14f, (short)(a), (short)(b))
#endif



#define PATH_SEP  "\\"

unsigned char buffer[32768];

long eff_uid = 0;
long eff_gid = 0;


/* These buffers are commonly used. As no function takes string arguments and
 * returns also string results, there can not be any conflicts.
 * The same applies to the data buffer, as we either read or write.
 */
static char pathbuf[MAXPATHLEN+1], pathbuf2[MAXPATHLEN+1];
static char pathbuf3[MAXPATHLEN+1], pathbuf4[MAXPATHLEN+1];
static char databuf[MAXDATA];


int debug;


/* these service functions return an rpc result, the nfs result
 * (if rpc result is SUCCESS) is returned in the appropriate structure
 */

long
nfsproc_null_svc(void *nix_in, void * nix_out)
{
	return NFS_OK;
}


long 
nfsproc_getattr_svc(svc_fh *fp, attrstat *rp)
{
	NFSD_CACHE *index;
	long r;
	struct stat stats;

	if ((index = fh_find(fp)) == NULL)
	{
		return NFSERR_STALE;
	}

/* BUG: no access checks */

	/* If we have an exported root here, make sure that symlinks are
	 * followed, because they make not much sense for the client.
	 * Otherwise, get stats for the thing itself.
	 */
	if (index->flags & EXPORT_ROOT)
		r = Fxattr(0, index->fullname, &stats);
	else
		r = Fxattr(1, index->fullname, &stats);
	if (r != 0)
	{
		return nfs_errno(r);
	}

	stat2fattr(&stats, &rp->attrstat_u.attributes);
	fh_update(index);
	return NFS_OK;
}


/* Stripped down version of truncate from the mint lib. This function
 * knows that we are running under MiNT and have filenames as the
 * kernel expects them (with '\').
 */
int
mytruncate(char *filename, off_t length)
{
	long r;

	r = Dcntl(FTRUNCATE, (long)filename, (long)&length);
	if (r == -EINVAL)
	{
		/* Hack to prevent this operation fail on a tosfs if truncating
		 * 0 length.
		 */
		if (length == 0)
		{
			r = Fcreate(filename, 0);
			if (r < 0)
				return r;
			(void)Fclose(r);
		}
		r = 0;
	}
	return r;
}

long
nfsproc_setattr_svc(sattrargs *ap, attrstat *rp)
{
	NFSD_CACHE *ni;
	long r;
	struct utimbuf tm;

	if ((ni = fh_find((svc_fh*)&ap->file)) == NULL)
	{
		return NFSERR_STALE;
	}
	r = 0;

	if (ap->attributes.mode != (u_long)-1L)
		r = chmod(ni->fullname, mint_mode(ap->attributes.mode, NFREG));
	else if ((ap->attributes.uid != (u_long)-1L) &&
	             (ap->attributes.gid != (u_long)-1L))
		r = chown(ni->fullname, ap->attributes.uid, ap->attributes.gid);
	else if ((ap->attributes.mtime.seconds != (u_long)-1L)  &&
	             (ap->attributes.mtime.useconds != (u_long)-1L))
	{
		tm.modtime = ap->attributes.mtime.seconds;
		tm.actime = ap->attributes.atime.seconds;
		r = utime(ni->fullname, &tm);
	}
	else if (ap->attributes.size != (u_long)-1L)
		r = mytruncate (ni->fullname, ap->attributes.size);

	if (r < 0)
		return NFSERR_IO;
	return nfsproc_getattr_svc((svc_fh*)&ap->file, rp);
}


long
nfsproc_root_svc(void *nix_in, void *nix_out)
{
	/* nothing to do */
	return NFS_OK;
}


long
nfsproc_lookup_svc(diropargs *ap, diropres *rp)
{
	NFSD_CACHE *dir, *newi;
	struct stat stats;

	if ((dir = fh_find((svc_fh*)&ap->dir)) == NULL)
		return NFSERR_STALE;

	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;

	newi = fh_new(dir, ap->name, &stats);
	if (!newi)
		return NFSERR_NOENT;

	memcpy(&rp->diropres_u.diropok.file, &newi->fh, sizeof(nfs_fh));

	stat2fattr(&stats, &rp->diropres_u.diropok.attributes);
	fh_update(dir);
	fh_update(newi);
	return NFS_OK;
}


long
nfsproc_readlink_svc(svc_fh *fp, readlinkres *rp)
{
	NFSD_CACHE *ni;
	long r;

	if ((ni = fh_find(fp)) == NULL)
		return NFSERR_STALE;
	rp->readlinkres_u.data = pathbuf;

	r = Freadlink(MAXPATHLEN, rp->readlinkres_u.data, ni->fullname);
	if (r != 0)
		return nfs_errno(r);

	fh_update(ni);
	return NFS_OK;
}


long
nfsproc_read_svc(readargs *ap, readres *rp)
{
	NFSD_CACHE *ni;
	FILE_DESCR *fd;
	long r, position, count;

	if ((ni = fh_find((svc_fh*)&ap->file)) == NULL)
		return NFSERR_STALE;

	if (!(ni->flags & INDEX_IS_FILE))
		return NFSERR_ISDIR;

	if ((fd = index_to_fd(ni, 0)) == NULL)
		return NFSERR_IO;

	position = ap->offset;
	count = ap->count;

	if (count > MAXDATA)
		return NFSERR_IO;

	if (fd->position != position)
	{
		r = Fseek(position, fd->handle.fh, SEEK_SET);
		if (r < 0)
			return nfs_errno(r);
	}

	rp->readres_u.read_ok.data_val = databuf;
	r = Fread(fd->handle.fh, count, databuf);
	if (r < 0)
		return nfs_errno(r);
	fd->position += r;

	rp->readres_u.read_ok.data_len = r;
	/* fh_update() is called in nfsproc_getattr_svc */
	return nfsproc_getattr_svc((svc_fh*)&ap->file, (attrstat*)rp);
}


long
nfsproc_writecache_svc(void *nix_in, void *nix_out)
{
	/* here we should call sync(), but we dont have such a call */
	return NFS_OK;
}


long
nfsproc_write_svc(writeargs *ap, attrstat *rp)
{
	NFSD_CACHE *ni;
	FILE_DESCR *fd;
	long r, position, count;

	if ((ni = fh_find((svc_fh*)&ap->file)) == NULL)
		return NFSERR_STALE;

	if (!(ni->flags & INDEX_IS_FILE))
		return NFSERR_ISDIR;

	if ((fd = index_to_fd(ni, O_RDWR)) == NULL)
		return NFSERR_IO;

	position = ap->offset;
	count = ap->totalcount;
/* Kludge: Not all NFS drivers use both fields. So if one is zero let's
 *         check for some useful data in the other...
 * T. Lang, 20.09.1997
 */
	if (count == 0)
		count = ap->data_len;

	if (count > MAXDATA)
		return NFSERR_IO;

	if (fd->position != position)
	{
		r = Fseek(position, fd->handle.fh, SEEK_SET);
		if (r < 0)
			return nfs_errno(r);

/* Kludge: On Un*x NFS drivers it seems to be allowed to position behind the
 *         end of a file for writing to generate a file with the wanted size.
 *         Since that does not work on the Minix fs we write some dummy data...
 * T. Lang, 20.07.1997
 */
		while (r < position)
		{
			if ((position - r) > 32768) {
				Fwrite(fd->handle.fh, 32768, buffer);
				position += 32768;
			} else {
				Fwrite(fd->handle.fh, position - r, buffer);
				r = position;
			}
		}
	}

	r = Fwrite(fd->handle.fh, count, ap->data_val);
	if (r < 0)
		return nfs_errno(r);
	fd->position += r;

	/* fh_update() is called in nfsproc_getattr_svc */
	return nfsproc_getattr_svc((svc_fh*)&ap->file, rp);
}


long
nfsproc_create_svc(createargs *ap, diropres *rp)
{
	NFSD_CACHE *dir, *newi;
	long r;
	struct stat stats;

	if ((dir = fh_find((svc_fh*)&ap->where.dir)) == NULL)
		return NFSERR_STALE;
	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->where.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access checks */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->where.name);

	r = Fopen (pathbuf3, O_RDWR|O_TRUNC|O_CREAT|O_DENYNONE);
	if (r < 0)
		return nfs_errno(r);

	newi = fh_new(dir, ap->where.name, &stats);
	if (NULL == newi)
	{
		(void) Fclose(r);
		return NFSERR_NAMETOOLONG;
	}

	if (!new_fd(newi, O_RDWR, r))
		(void) Fclose(r);   /* leave it for next time */

	/* set correct attributes */
	if (ap->attributes.mode != (u_long)-1L)
		chmod(newi->fullname, mint_mode(ap->attributes.mode, NFREG));
	chown(newi->fullname, ap->attributes.uid, ap->attributes.gid);

/* BUG: time not set */
	memcpy(&rp->diropres_u.diropok.file, &newi->fh, sizeof(nfs_fh));
#if 0
	r = nfsproc_getattr_svc(&newi->fh, &astat);
	if (NFS_OK != r)
	{
		fh_delete(newi);
		return r;
	}

	rp->diropres_u.diropok.attributes = astat.attrstat_u.attributes;
	/* fh_update() is called in nfsproc_getattr_svc */
#endif

	stat2fattr(&stats, &rp->diropres_u.diropok.attributes);
	fh_update(dir);
	fh_update(newi);
	return NFS_OK;
}


long
nfsproc_remove_svc(diropargs *ap, nfsstat *rp)
{
	NFSD_CACHE *dir;
	long r;

	if ((dir = fh_find((svc_fh*)&ap->dir)) == NULL)
		return NFSERR_STALE;
	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access checks */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->name);

	fh_delete_by_name(dir, ap->name);

	r = unlink(pathbuf3);
	if (r != 0)
		return nfs_errno(r);

	fh_update(dir);
	return NFS_OK;
}


long
nfsproc_rename_svc(renameargs *ap, nfsstat *rp)
{
	NFSD_CACHE *olddir, *newdir;
	long r;

	if ((olddir = fh_find((svc_fh*)&ap->from.dir)) == NULL)
		return NFSERR_STALE;

	if ((newdir = fh_find((svc_fh*)&ap->to.dir)) == NULL)
		return NFSERR_STALE;

	if (strlen(olddir->fullname)+1+strlen(ap->from.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;
	if (strlen(newdir->fullname)+1+strlen(ap->to.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access checks */
	strcpy(pathbuf3, olddir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->from.name);
	strcpy(pathbuf4, newdir->fullname);
	strcat(pathbuf4, PATH_SEP);
	strcat(pathbuf4, ap->to.name);

/* BUG: we should remove the file's index from our cache */
	r = rename(pathbuf3, pathbuf4);
	if (r != 0)
		return nfs_errno(r);

	fh_update(olddir);
	fh_update(newdir);
	return NFS_OK;
}


long
nfsproc_link_svc(linkargs *ap, nfsstat *rp)
{
	NFSD_CACHE *from, *dir;
	long r;

	if ((from = fh_find((svc_fh*)&ap->from)) == NULL)
		return NFSERR_STALE;

	if ((dir = fh_find((svc_fh*)&ap->to.dir)) == NULL)
		return NFSERR_STALE;

	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->to.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access check */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->to.name);

	r = Flink(from->fullname, pathbuf3);
	if (r != 0)
		return nfs_errno(r);

	fh_update(dir);
	fh_update(from);
	return NFS_OK;
}


long
nfsproc_symlink_svc(symlinkargs *ap, nfsstat *rp)
{
	NFSD_CACHE *dir;
	long r;

	if ((dir = fh_find((svc_fh*)&ap->from.dir)) == NULL)
		return NFSERR_STALE;

	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->from.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access check */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->from.name);

	r = Fsymlink(ap->to, pathbuf3);
	if (r != 0)
		return nfs_errno(r);

/* BUG, FEATURE: the attributes are ignored here, like unix servers do */

	fh_update(dir);
	return NFS_OK;
}


long
nfsproc_mkdir_svc(createargs *ap, diropres *rp)
{
	NFSD_CACHE *dir, *newi;
	long r;
	struct stat stats;

	if ((dir = fh_find((svc_fh*)&ap->where.dir)) == NULL)
		return NFSERR_STALE;
	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->where.name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

/* BUG: no access checks */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->where.name);

	r = mkdir(pathbuf3, O_RDWR);
	if (r != 0)
		return nfs_errno(r);
	newi = fh_new(dir, ap->where.name, &stats);
	if (NULL == newi)
		return NFSERR_NAMETOOLONG;

	/* set correct attributes */
	if (ap->attributes.mode != (u_long)-1L)
		chmod(newi->fullname, mint_mode(ap->attributes.mode, NFDIR));
	chown(newi->fullname, ap->attributes.uid, ap->attributes.gid);

/* BUG: time not set */
	memcpy(&rp->diropres_u.diropok.file, &newi->fh, sizeof(nfs_fh));

	stat2fattr(&stats, &rp->diropres_u.diropok.attributes);
	fh_update(dir);
	fh_update(newi);
	return NFS_OK;
}


long
nfsproc_rmdir_svc(diropargs *ap, nfsstat *rp)
{
	NFSD_CACHE *dir;
	long r;

	if ((dir = fh_find((svc_fh*)&ap->dir)) == NULL)
		return NFSERR_STALE;
	if (dir->flags & INDEX_IS_FILE)
		return NFSERR_NOTDIR;
	if (strlen(dir->fullname)+1+strlen(ap->name) > MAXPATHLEN)
		return NFSERR_NAMETOOLONG;

	/* BUG: no access checks */
	strcpy(pathbuf3, dir->fullname);
	strcat(pathbuf3, PATH_SEP);
	strcat(pathbuf3, ap->name);

	fh_delete_by_name(dir, ap->name);

	r = rmdir(pathbuf3);
	if (r != 0)
		return nfs_errno(r);

	fh_update(dir);
	return NFS_OK;
}


long
nfsproc_readdir_svc(readdirargs *ap, readdirres *rp)
{
	static entry *oldlist = NULL, *curr_entry, *ep;
	NFSD_CACHE *dir;
	FILE_DESCR *fd;
	long position, len, r;
	struct dbuf
	{
		long ino;
		char name[MAXPATHLEN+1];
	} dbuf;

	if (oldlist)
	{
		/* free results from previous reads */
		entry *next, *p = oldlist;

		while (p)
		{
			if (p->name)
				free(p->name);
			next = p->nextentry;
			free(p);
			p = next;
		}
		oldlist = NULL;
	}

	if ((dir = fh_find((svc_fh*)&ap->dir)) == NULL)
		return NFSERR_STALE;

	if ((fd = index_to_fd(dir, 0)) == NULL)
		return NFSERR_IO;

	/* get the right position */
	position = *(long*)&ap->cookie;
	if (fd->position != position)
	{
		/* start again at the beginning */
		Drewinddir(fd->handle.dirh);
		fd->position = 0;
		while (fd->position != position)
		{
			r = Dreaddir(MAXPATHLEN+1+sizeof(long), fd->handle.dirh, &dbuf);
			if (r < 0)
				break;
			fd->position += 1;
		}
	}

	len = ap->count;
	curr_entry = NULL;
	rp->readdirres_u.readdirok.eof = 0;
	while (len > 0)
	{
		r = Dreaddir(MAXPATHLEN+1+sizeof(long), fd->handle.dirh, &dbuf);
		if (r < 0)
		{
			rp->readdirres_u.readdirok.eof = 1;
			break;
		}
		fd->position += 1;

		ep = malloc(sizeof(entry));
		ep->fileid = dbuf.ino;
		*(long*)&ep->cookie = fd->position;
		ep->nextentry = NULL;
		ep->name = malloc(strlen(dbuf.name)+1);
		strcpy(ep->name, dbuf.name);
		if ((len -= xdr_size_entry(ep)) >= 0)
		{
			if (curr_entry)
				curr_entry->nextentry = ep;
			else
				oldlist = ep;
			curr_entry = ep;
		}
		else
		{
			free(ep->name);
			free(ep);
			break;
		}
	}
	rp->readdirres_u.readdirok.entries = oldlist;
	fh_update(dir);
	return NFS_OK;
}


long
nfsproc_statfs_svc(svc_fh *fp, statfsres *rp)
{
	NFSD_CACHE *index;
	long r;
	struct statfs stats;

	if ((index = fh_find(fp)) == NULL)
		return NFSERR_STALE;
	r = statfs(index->fullname, &stats);
	if (r != 0)
		return nfs_errno(r);
	rp->statfsres_u.info.tsize = 1024;   /* is there a better value?? */
	rp->statfsres_u.info.bsize = stats.f_bsize;
	rp->statfsres_u.info.blocks = stats.f_blocks;
	rp->statfsres_u.info.bfree = stats.f_bfree;
	rp->statfsres_u.info.bavail = stats.f_bavail;
	fh_update(index);
	return NFS_OK;
}




void
init_service()
{
	fh_init();
}





/* This could be a separate file. It is the service dispatcher for
 * the nfs daemon together with some functions which prepare the
 * arguments etc. That way we do not need to malloc the argument and
 * result strings, they are just static.
 */




bool_t
prep_arguments(SVCXPRT *xprt, u_long proc, nfs_arguments *ap)
{
	switch (proc)
	{
		case NFSPROC_NULL:
		case NFSPROC_GETATTR:
		case NFSPROC_SETATTR:
		case NFSPROC_ROOT:
		case NFSPROC_READLINK:
		case NFSPROC_READ:
		case NFSPROC_WRITECACHE:
		case NFSPROC_READDIR:
		case NFSPROC_STATFS:
			/* here is nothing to prepare, as the arguments to these function fit
			 * directly into their normal counterparts without additional storage
			 * requirements.
			 */
			return TRUE;
		case NFSPROC_LOOKUP:
		case NFSPROC_REMOVE:
		case NFSPROC_RMDIR:
			ap->dirop_arg.name = pathbuf;
			break;
		case NFSPROC_WRITE:
			ap->write_arg.data_val = databuf;
			break;
		case NFSPROC_CREATE:
		case NFSPROC_MKDIR:
			ap->create_arg.where.name = pathbuf;
			break;
		case NFSPROC_RENAME:
			ap->rename_arg.from.name = pathbuf;
			ap->rename_arg.to.name = pathbuf2;
			break;
		case NFSPROC_LINK:
			ap->link_arg.to.name = pathbuf;
			break;
		case NFSPROC_SYMLINK:
			ap->symlink_arg.from.name = pathbuf;
			ap->symlink_arg.to = pathbuf2;
			break;
		default:
			return FALSE;
	}
	return TRUE;
}


bool_t
free_arguments(nfs_arguments *ap, u_long proc)
{
	/* As we do not allocate any memory for the arguments, we also have
	 * nothing to free here.
	 */
	return TRUE;
}


bool_t
free_results(nfs_results *rp, long proc)
{
	/* We do not allocate memory in the service functions, so nothing to
	 * do here.
	 */
	return TRUE;
}


void
set_status(nfs_results *rp, long status, long proc)
{
	rp->stat_res = status;
}


/* Functions to extract the nfs handle from the arguments. This is
 * essential for access checking.
 */

svc_fh *xfh_null(void *nix)
{ return NULL; }

svc_fh *xfh_fh(svc_fh *fh)
{ return fh; }

svc_fh *xfh_sattr(sattrargs *ap)
{ return (svc_fh*)&ap->file; }

svc_fh *xfh_dirop(diropargs *ap)
{ return (svc_fh*)&ap->dir; }

svc_fh *xfh_read(readargs *ap)
{ return (svc_fh*)&ap->file; }

svc_fh *xfh_write(writeargs *ap)
{ return (svc_fh*)&ap->file; }

svc_fh *xfh_create(createargs *ap)
{ return (svc_fh*)&ap->where.dir; }

svc_fh *xfh_rename(renameargs *ap)
{ return (svc_fh*)&ap->from.dir; }

svc_fh *xfh_link(linkargs *ap)
{ return (svc_fh*)&ap->from; }

svc_fh *xfh_symlink(symlinkargs *ap)
{ return (svc_fh*)&ap->from.dir; }

svc_fh *xfh_readdir(readdirargs *ap)
{ return (svc_fh*)&ap->dir; }




nfs_arguments argument;
nfs_results result;


/* types used for the service dispatcher */

typedef long (*svc_func)(void*, void*);
typedef svc_fh * (*extract_func)(void *ap);
typedef bool_t (*preparg_func)(SVCXPRT *, u_long proc, void *ap);
typedef bool_t (*freearg_func)(void *ap, u_long proc);
typedef bool_t (*freeres_func)(void *rp, u_long proc);
typedef void (*resstat_func)(void *rp, u_long status, u_long proc);



/* set table entries to: service function, arg decoder, res encoder */
#define T_ENTRY(a, c, r, f, d, e, x)  \
  { a, c, r, (svc_func)f, (xdrproc_t)d, (xdrproc_t)e, (extract_func)x, #f }

/* NOTE the cast, as the argument and result types are aligned
 * to the front of the union
 */
struct dispatch_entry
{
	int needs_auth;    /* zero if permission for everyone */
	int needs_cred;    /* zero if no credentials needed */
	int ro_restricted; /* zero if permission even on an R/O FS */
	svc_func svc;
	xdrproc_t xargs;
	xdrproc_t xresults;
	extract_func extract;
	char *name;
};


typedef struct
{
	preparg_func prep_args;
	freearg_func free_args;
	freeres_func free_res;
	resstat_func result_status;
	void *argument;
	long arg_size;
	void *result;
	long res_size;
	struct dispatch_entry *table;
} DISPATCH_INFO;




struct dispatch_entry nfsv2_table[] = 
{
	T_ENTRY(0, 0, 0, nfsproc_null_svc, xdr_void, xdr_void, xfh_null),
	T_ENTRY(1, 1, 0, nfsproc_getattr_svc, xdr_nfsfh, xdr_attrstat, xfh_fh),
	T_ENTRY(1, 1, 1, nfsproc_setattr_svc, xdr_sattrargs, xdr_attrstat, xfh_sattr),
	T_ENTRY(0, 0, 0, nfsproc_root_svc, xdr_void, xdr_void, xfh_null),
	T_ENTRY(1, 1, 0, nfsproc_lookup_svc, xdr_diropargs, xdr_diropres, xfh_dirop),
	T_ENTRY(1, 1, 0, nfsproc_readlink_svc, xdr_nfsfh, xdr_readlinkres, xfh_fh),
	T_ENTRY(1, 1, 0, nfsproc_read_svc, xdr_readargs, xdr_readres, xfh_read),
	T_ENTRY(0, 0, 0, nfsproc_writecache_svc, xdr_void, xdr_void, xfh_null),
	T_ENTRY(1, 1, 1, nfsproc_write_svc, xdr_writeargs, xdr_attrstat, xfh_write),
	T_ENTRY(1, 1, 1, nfsproc_create_svc, xdr_createargs, xdr_diropres, xfh_create),
	T_ENTRY(1, 1, 1, nfsproc_remove_svc, xdr_diropargs, xdr_nfsstat, xfh_dirop),
	T_ENTRY(1, 1, 1, nfsproc_rename_svc, xdr_renameargs, xdr_nfsstat, xfh_rename),
	T_ENTRY(1, 1, 1, nfsproc_link_svc, xdr_linkargs, xdr_nfsstat, xfh_link),
	T_ENTRY(1, 1, 1, nfsproc_symlink_svc, xdr_symlinkargs, xdr_nfsstat, xfh_symlink),
	T_ENTRY(1, 1, 1, nfsproc_mkdir_svc, xdr_createargs, xdr_diropres, xfh_create),
	T_ENTRY(1, 1, 1, nfsproc_rmdir_svc, xdr_diropargs, xdr_nfsstat, xfh_dirop),
	T_ENTRY(1, 1, 0, nfsproc_readdir_svc, xdr_readdirargs, xdr_readdirres, xfh_readdir),
	T_ENTRY(1, 0, 0, nfsproc_statfs_svc, xdr_nfsfh, xdr_statfsres, xfh_fh)
};


DISPATCH_INFO nfsv2_info =
{
	(preparg_func)prep_arguments,
	(freearg_func)free_arguments,
	(freeres_func)free_results,
	(resstat_func)set_status,
	(void*)&argument,
	sizeof(argument),
	(void*)&result,
	sizeof(result),
	&nfsv2_table[0]
};



/* These variabels are used to control the behavior of the daemon when
 * recieving signal 1 (SIGHUP). When the daemon is in service, do_reread
 * is set to indicate that at the end of the service the config file
 * should be read again. Otherwise this is done immediatly.
 */
static int in_service = 0;
static int do_reread = 0;


void
nfsprog_2(struct svc_req *req, SVCXPRT *xprt)
{
	u_long proc;
	long r;
	xdrproc_t xarg, xresult;
	accesslist *ac;
	int auth_kind = 0;
	extern long ticks;

	/* reset timer for dynamic rpc */
	ticks = 0;

	in_service = 1;
	proc = req->rq_proc;
	if (proc > NFS_MAXPROC)
		svcerr_noproc(xprt);

	/* set up arguments and call service function */
	bzero(nfsv2_info.result, nfsv2_info.res_size);
	if (!(*nfsv2_info.prep_args)(xprt, proc, nfsv2_info.argument))
	{
		svcerr_decode(xprt);  /* not correct, but who cares? */
		goto service_done;
	}

	xarg = nfsv2_info.table[proc].xargs;
	xresult = nfsv2_info.table[proc].xresults;
	if (!xarg || !xresult)
	{
		svcerr_decode(xprt);  /* this is an internal error! */
		goto service_done;
	}

	if (!svc_getargs(xprt, xarg, nfsv2_info.argument))
	{
		svcerr_decode(xprt);
		goto service_done;
	}

	/* Check if the requested file system hierarchie is exported to
	 * the system sending the request.
	 */

	/* Now do the per-host access check */
	ac = NULL;
	auth_kind = AUTH_NO;
	if (nfsv2_info.table[proc].needs_auth)
	{
		svc_fh *fh;

		fh = (*nfsv2_info.table[proc].extract)(nfsv2_info.argument);
		ac = auth_nfs(fh, xprt, &auth_kind);
		
		if (ac == NULL)
		{
#ifdef SYSLOG
			if (debug > 1)
				syslog(LOG_INFO, "access not allowed for %s",
				         inet_ntoa((struct in_addr)xprt->xp_raddr.sin_addr));
#endif
			r = NFSERR_ACCES;
			goto send_result;
		}
		if (ac->read_only && nfsv2_info.table[proc].ro_restricted)
		{
			/* The accessing host needs to be listed with rw=... in the
			 * exports file, that means, auth_kind must be AUTH_RW
			 */
			if (!(auth_kind & AUTH_RW))
			{
#ifdef SYSLOG
				if (debug > 1)
					syslog(LOG_INFO, "read only FS for %s", 
				           inet_ntoa((struct in_addr)xprt->xp_raddr.sin_addr));
#endif
				r = NFSERR_ROFS;
				goto send_result;
			}
		}
		if (auth_kind == AUTH_NO)
		{
#ifdef SYSLOG
			if (debug > 1)
				syslog(LOG_INFO, "access denied for %s", 
				           inet_ntoa((struct in_addr)xprt->xp_raddr.sin_addr));
#endif
			r = NFSERR_ACCES;
			goto send_result;
		}
	}


	/* Now set the effective user and group id supplied with
	 * the credentials, so the system can check for access permission
	 * of the requesting user
	 */
	if (nfsv2_info.table[proc].needs_cred)
	{
		long cred_uid, cred_gid;

		if (req->rq_cred.flavor == AUTH_UNIX)
		{
			auth_unix *au = (auth_unix*)req->rq_clntcred;

			cred_uid = au->au_uid;
			cred_gid = au->au_gid;
			if (cred_uid == ROOT_UID)
			{
				if (!ac || !(auth_kind & AUTH_ROOT))
				{
					/* Root access from that host not allowed */
					if (ac)
					{
						cred_uid = ac->anon_uid;
						cred_gid = ac->anon_gid;
					}
					else
					{
						cred_uid = NOBODY_UID;
						cred_gid = NOBODY_GID;
					}
				}
			}
		}
		else if (ac)
		{
			cred_uid = ac->anon_uid;
			cred_gid = ac->anon_gid;
		}
		else
		{
			cred_uid = NOBODY_UID;
			cred_gid = NOBODY_GID;
		}

#ifdef SYSLOG
		if (debug > 2)
		{
			syslog(LOG_INFO, "switching to uid %ld/gid %ld",
			                     (long)cred_uid, (long)cred_gid);
		}
#endif

		if (eff_gid != cred_gid)
		{
			if (eff_uid != ROOT_UID)
			{
				Psetreuid(-1, ROOT_UID);
				eff_uid = ROOT_UID;
			}
			Psetregid(-1, cred_gid);
		}
		if (eff_uid != cred_uid)
		{
			if (eff_uid != ROOT_UID)
				Psetreuid(-1, ROOT_UID);
			Psetreuid(-1, cred_uid);
			eff_uid = cred_uid;
		}
	}

	r = (nfsv2_info.table[proc].svc)(nfsv2_info.argument, nfsv2_info.result);

send_result:
	(*nfsv2_info.result_status)(nfsv2_info.result, r, proc);
	(*nfsv2_info.free_args)(nfsv2_info.argument, proc);
	svc_sendreply(xprt, xresult, nfsv2_info.result);
	(*nfsv2_info.free_res)(nfsv2_info.result, proc);

service_done:

	if (do_reread)
	{
		auth_init();
		fh_reinit();
		do_reread = 0;
	}
	in_service = 0;
}


void handle_sighup(int sig)
{
	if (in_service)
	{
		do_reread = 1;
	}
	else
	{
		auth_init();
		fh_reinit();
		do_reread = 0;
	}
}
