#ifndef lint
static char sccsid[] = "@(#)rpc.mountd.c 1.1 94/10/31 Copyr 1987 Sun Micro";
#endif
/*
 * Copyright (c) 1987 Sun Microsystems, Inc. 
 */
#include <sys/param.h>
#include <rpc/rpc.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <nfs/nfs.h>
#include <rpcsvc/mount.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <exportent.h>
#include <string.h>
#include <sys/pathconf.h>
#include <sys/unistd.h>

#define MAXRMTABLINELEN		(MAXPATHLEN + MAXHOSTNAMELEN + 2)

extern int errno;

char XTAB[]  = "/etc/xtab";
char RMTAB[] = "/etc/rmtab";

int mnt();
char *exmalloc();
struct groups **newgroup();
struct exports **newexport();
void log_cant_reply();
void mnt_pathconf();
void mount();
void check_exports();
void xent_free();

/*
 * mountd's version of a "struct mountlist". It is the same except
 * for the added ml_pos field.
 */
struct mountdlist {
/* same as XDR mountlist */
	char *ml_name;
	char *ml_path;
	struct mountdlist *ml_nxt;
/* private to mountd */
	long ml_pos;		/* position of mount entry in RMTAB */
};

struct mountdlist *mountlist;

struct xent_list {		/* cached export list */
	struct xent_list *x_next;
	struct exportent  x_xent;
} *xent_list;

int nfs_portmon = 1;
char *domain;

void rmtab_load();
void rmtab_delete();
long rmtab_insert();

main(argc, argv)
	int argc;
	char **argv;
{
	SVCXPRT *transp;
	int pid;
	register int i;
	int sock;
	int proto;

	if (argc == 2) {
		if (strcmp(argv[1], "-n") == 0) {
			nfs_portmon = 0;
		} else {
			usage();
		}
	} else if (argc > 2) {
		usage();
	}

	if (issock(0)) {
		/*
		 * Started from inetd 
		 */
		sock = 0;
		proto = 0;	/* don't register with portmapper */
	} else {
		/*
		 * Started from shell, background.
		 */
		pid = fork();
		if (pid < 0) {
			perror("mountd: can't fork");
			exit(1);
		}
		if (pid) {
			exit(0);
		}

		/*
		 * Close existing file descriptors, open "/dev/null" as
		 * standard input, output, and error, and detach from
		 * controlling terminal.
		 */
		i = getdtablesize();
		while (--i >= 0)
			(void) close(i);
		(void) open("/dev/null", O_RDONLY);
		(void) open("/dev/null", O_WRONLY);
		(void) dup(1);
		i = open("/dev/tty", O_RDWR);
		if (i >= 0) {
			(void) ioctl(i, TIOCNOTTY, (char *)0);
			(void) close(i);
		}
		(void) pmap_unset(MOUNTPROG, MOUNTVERS);
		(void) pmap_unset(MOUNTPROG, MOUNTVERS_POSIX);
		sock = RPC_ANYSOCK;
		proto = IPPROTO_UDP;
	}

	openlog("mountd", LOG_PID, LOG_DAEMON);

	/*
	 * Create UDP service
	 */
	if ((transp = svcudp_create(sock)) == NULL) {
		syslog(LOG_ERR, "couldn't create UDP transport");
		exit(1);
	}
	if (!svc_register(transp, MOUNTPROG, MOUNTVERS, mnt, proto)) {
		syslog(LOG_ERR, "couldn't register UDP MOUNTPROG_ORIG");
		exit(1);
	}
	if (!svc_register(transp, MOUNTPROG, MOUNTVERS_POSIX, mnt, proto)) {
		syslog(LOG_ERR, "couldn't register UDP MOUNTPROG");
		exit(1);
	}

	/*
	 * Create TCP service
	 */
	if ((transp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL) {
		syslog(LOG_ERR, "couldn't create TCP transport");
		exit(1);
	}
	if (!svc_register(transp, MOUNTPROG, MOUNTVERS, mnt, 
			  IPPROTO_TCP)) {
		syslog(LOG_ERR, "couldn't register TCP MOUNTPROG_ORIG");
		exit(1);
	}
	if (!svc_register(transp, MOUNTPROG, MOUNTVERS_POSIX, mnt, 
			  IPPROTO_TCP)) {
		syslog(LOG_ERR, "couldn't register TCP MOUNTPROG");
		exit(1);
	}

	/*
	 * Initalize the world 
	 */
	(void) yp_get_default_domain(&domain);

	/*
	 * Start serving 
	 */
	rmtab_load();
	svc_run();
	syslog(LOG_ERR, "Error: svc_run shouldn't have returned");
	abort();
	/* NOTREACHED */
}


/*
 * Determine if a descriptor belongs to a socket or not 
 */
issock(fd)
	int fd;
{
	struct stat st;

	if (fstat(fd, &st) < 0) {
		return (0);
	}
	/*
	 * SunOS returns S_IFIFO for sockets, while 4.3 returns 0 and does not
	 * even have an S_IFIFO mode.  Since there is confusion about what the
	 * mode is, we check for what it is not instead of what it is. 
	 */
	switch (st.st_mode & S_IFMT) {
	case S_IFCHR:
	case S_IFREG:
	case S_IFLNK:
	case S_IFDIR:
	case S_IFBLK:
		return (0);
	default:
		return (1);
	}
}

/*
 * Server procedure switch routine 
 */
mnt(rqstp, transp)
	struct svc_req *rqstp;
	SVCXPRT *transp;
{
	switch (rqstp->rq_proc) {
	case NULLPROC:
		errno = 0;
		if (!svc_sendreply(transp, xdr_void, (char *)0))
			log_cant_reply(transp);
		return;
	case MOUNTPROC_MNT:
		mount(rqstp);
		return;
	case MOUNTPROC_DUMP:
		errno = 0;
		if (!svc_sendreply(transp, xdr_mountlist, (char *)&mountlist))
			log_cant_reply(transp);
		return;
	case MOUNTPROC_UMNT:
		umount(rqstp);
		return;
	case MOUNTPROC_UMNTALL:
		umountall(rqstp);
		return;
	case MOUNTPROC_EXPORT:
	case MOUNTPROC_EXPORTALL:
		export(rqstp);
		return;
	case MOUNTPROC_PATHCONF:
		if (rqstp->rq_vers == MOUNTVERS_POSIX) {
			mnt_pathconf(rqstp);
			return;
		}
		/* else fall through to error out */
	default:
		svcerr_noproc(transp);
		return;
	}
}

struct hostent *
getclientsname(transp)
	SVCXPRT *transp;
{
	struct sockaddr_in actual;
	struct hostent *hp;
	static struct hostent h;
	static struct in_addr prev;
	static char *null_alias;

	actual = *svc_getcaller(transp);
	if (nfs_portmon) {
		if (ntohs(actual.sin_port) >= IPPORT_RESERVED) {
			return (NULL);
		}
	}
	/*
	 * Don't use the unix credentials to get the machine name,
	 * instead use the source IP address. 
	 * Used cached hostent if previous call was for the
	 * same client.
	 */

	if (bcmp(&actual.sin_addr, &prev, sizeof(struct in_addr)) == 0)
		return (&h);
	prev = actual.sin_addr;

	hp = gethostbyaddr((char *) &actual.sin_addr, sizeof(actual.sin_addr), 
			   AF_INET);
	if (hp == NULL) {			/* dummy one up */
		h.h_name = inet_ntoa(actual.sin_addr);
		h.h_aliases = &null_alias;
		h.h_addrtype = AF_INET;
		h.h_length = sizeof (u_long);
		hp = &h;
	} else {
		bcopy(hp, &h, sizeof(struct hostent));
	}

	return (hp);
}

void
log_cant_reply(transp)
	SVCXPRT *transp;
{
	int saverrno;
	struct sockaddr_in actual;
	register struct hostent *hp;
	register char *name;

	saverrno = errno;	/* save error code */
	actual = *svc_getcaller(transp);
	/*
	 * Don't use the unix credentials to get the machine name, instead use
	 * the source IP address. 
	 */
	if ((hp = gethostbyaddr(&actual.sin_addr, sizeof(actual.sin_addr),
	   AF_INET)) != NULL)
		name = hp->h_name;
	else
		name = inet_ntoa(actual.sin_addr);

	errno = saverrno;
	if (errno == 0)
		syslog(LOG_ERR, "couldn't send reply to %s", name);
	else
		syslog(LOG_ERR, "couldn't send reply to %s: %m", name);
}

/*
 * Answer pathconf questions for the mount point fs
 */
void
mnt_pathconf(rqstp)
	struct svc_req *rqstp;
{
	SVCXPRT *transp;
	struct pathcnf p;
	char *path, rpath[MAXPATHLEN];
	char *gr, *grl;
	struct exportent *xent;
	struct exportent *findentry();
	struct stat st;
	char **aliases;
	struct hostent *client;

	transp = rqstp->rq_xprt;
	path = NULL;
	bzero((caddr_t)&p, sizeof(p));
	client = getclientsname(transp);
	if (client == NULL) {
		_PC_SET(_PC_ERROR, p.pc_mask);
		goto done;	
	}
	if (!svc_getargs(transp, xdr_path, &path)) {
		svcerr_decode(transp);
		return;
	}
	if (lstat(path, &st) < 0) {
		_PC_SET(_PC_ERROR, p.pc_mask);
		goto done;	
	}
	/*
	 * Get a path without symbolic links.
	 */
	if (realpath(path, rpath) == NULL) {
		syslog(LOG_DEBUG,
			"mount request: realpath failed on %s: %m",
			path);
		_PC_SET(_PC_ERROR, p.pc_mask);
		goto done;
	}
	bzero(&p, sizeof(p));
	/*
	 * can't ask about devices over NFS
	 */
	_PC_SET(_PC_MAX_CANON, p.pc_mask);
	_PC_SET(_PC_MAX_INPUT, p.pc_mask);
	_PC_SET(_PC_PIPE_BUF, p.pc_mask);
	_PC_SET(_PC_VDISABLE, p.pc_mask);

	errno = 0;
	p.pc_link_max = pathconf(rpath, _PC_LINK_MAX);
	if (errno)
		_PC_SET(_PC_LINK_MAX, p.pc_mask);
	p.pc_name_max = pathconf(rpath, _PC_NAME_MAX);
	if (errno)
		_PC_SET(_PC_NAME_MAX, p.pc_mask);
	p.pc_path_max = pathconf(rpath, _PC_PATH_MAX);
	if (errno)
		_PC_SET(_PC_PATH_MAX, p.pc_mask);
	if (pathconf(rpath, _PC_NO_TRUNC) == 1)
		_PC_SET(_PC_NO_TRUNC, p.pc_mask);
	if (pathconf(rpath, _PC_CHOWN_RESTRICTED) == 1)
		_PC_SET(_PC_CHOWN_RESTRICTED, p.pc_mask);

done:
	errno = 0;
	if (!svc_sendreply(transp, xdr_pathcnf, (char *)&p))
		log_cant_reply(transp);
	if (path != NULL)
		svc_freeargs(transp, xdr_path, &path);
	return;
}

/*
 * Check mount requests, add to mounted list if ok 
 */
void
mount(rqstp)
	struct svc_req *rqstp;
{
	SVCXPRT *transp;
	fhandle_t fh;
	struct fhstatus fhs;
	char *path, rpath[MAXPATHLEN];
	struct mountdlist *ml;
	char *gr, *grl;
	struct exportent *xent;
	struct exportent *findentry();
	struct stat st;
	char **aliases;
	struct hostent *client;

	transp = rqstp->rq_xprt;
	path = NULL;
	fhs.fhs_status = 0;
	client = getclientsname(transp);
	if (client == NULL) {
		fhs.fhs_status = EACCES;
		goto done;	
	}
	if (!svc_getargs(transp, xdr_path, &path)) {
		svcerr_decode(transp);
		return;
	}
	if (lstat(path, &st) < 0) {
		fhs.fhs_status = EACCES;
		goto done;	
	}

	/*
	 * Get a path without symbolic links.
	 */
	if (realpath(path, rpath) == NULL) {
		syslog(LOG_DEBUG,
			"mount request: realpath failed on %s: %m",
			path);
		fhs.fhs_status = EACCES;
		goto done;
	}

	if (do_getfh(rpath, &fh) < 0) {
		fhs.fhs_status = errno == EINVAL ? EACCES : errno;
		syslog(LOG_DEBUG, "mount request: getfh failed on %s: %m",
		    path);
		goto done;
	}

	xent = findentry(rpath);
	if (xent == NULL) {
		fhs.fhs_status = EACCES;
		goto done;
	}

	/* Check access list - hostnames first */

	grl = getexportopt(xent, ACCESS_OPT);
	if (grl == NULL)
		goto done;

	while ((gr = strtok(grl, ":")) != NULL) {
		grl = NULL;
		if (strcmp(gr, client->h_name) == 0)
			goto done;
		for (aliases = client->h_aliases; *aliases != NULL;
		     aliases++) {
			if (strcmp(gr, *aliases) == 0)
				goto done;
		}
	}
	
	/* no hostname match - try netgroups */

	grl = getexportopt(xent, ACCESS_OPT);
	if (grl == NULL)
		goto done;

	while ((gr = strtok(grl, ":")) != NULL) {
		grl = NULL;
		if (in_netgroup(gr, client->h_name, domain))
			goto done;
		for (aliases = client->h_aliases; *aliases != NULL;
		     aliases++) {
			if (in_netgroup(gr, *aliases, domain))
				goto done;
		}
	}

	/* Check root and rw lists */

	grl = getexportopt(xent, ROOT_OPT);
	if (grl != NULL) {
		while ((gr = strtok(grl, ":")) != NULL) {
			grl = NULL;
			if (strcmp(gr, client->h_name) == 0)
				goto done;
		}
	}
	grl = getexportopt(xent, RW_OPT);
	if (grl != NULL) {
		while ((gr = strtok(grl, ":")) != NULL) {
			grl = NULL;
			if (strcmp(gr, client->h_name) == 0)
				goto done;
		}
	}
	fhs.fhs_status = EACCES;

done:
	if (fhs.fhs_status == 0)
		fhs.fhs_fh = fh;
	errno = 0;
	if (!svc_sendreply(transp, xdr_fhstatus, (char *)&fhs))
		log_cant_reply(transp);
	if (path != NULL)
		svc_freeargs(transp, xdr_path, &path);
	if (fhs.fhs_status)
		return;

	/*
	 *  Add an entry for this mount to the mount list.
	 *  First check whether it's there already - the client
	 *  may have crashed and be rebooting.
	 */
	for (ml = mountlist; ml != NULL ; ml = ml->ml_nxt) {
		if (strcmp(ml->ml_path, rpath) == 0) {
			if (strcmp(ml->ml_name, client->h_name) == 0) {
				return;
			}
			for (aliases = client->h_aliases; *aliases != NULL;
			     aliases++) {
				if (strcmp(ml->ml_name, *aliases) == 0) {
					return;
				}
			}
		}
	}

	/*
	 * Add this mount to the mount list.
	 */
	ml = (struct mountdlist *) exmalloc(sizeof(struct mountdlist));
	ml->ml_path = (char *) exmalloc(strlen(rpath) + 1);
	(void) strcpy(ml->ml_path, rpath);
	ml->ml_name = (char *) exmalloc(strlen(client->h_name) + 1);
	(void) strcpy(ml->ml_name, client->h_name);
	ml->ml_nxt = mountlist;
	ml->ml_pos = rmtab_insert(client->h_name, rpath);
	mountlist = ml;
	return;
}

struct exportent *
findentry(path)
	char *path;
{
	struct exportent *xent;
	struct xent_list *xp;
	register char *p1, *p2;

	check_exports();

	for (xp = xent_list ; xp ; xp = xp->x_next) {
		xent = &xp->x_xent;
		for (p1 = xent->xent_dirname, p2 = path ; *p1 == *p2 ; p1++, p2++)
			if (*p1 == '\0')
				return xent;	/* exact match */

		if ((*p1 == '\0' && *p2 == '/' ) ||
		    (*p1 == '\0' && *(p1-1) == '/') ||
		    (*p2 == '\0' && *p1 == '/' && *(p1+1) == '\0')) {
			if (issubdir(path, xent->xent_dirname))
				return xent;
		}
	}
	return ((struct exportent *)0);
}

#define MAXGRPLIST 256
/*
 * Use cached netgroup info if the previous call was
 * from the same client.  Two lists are maintained
 * here: groups the client is a member of, and groups
 * the client is not a member of.
 */
int
in_netgroup(group, hostname, domain)
	char *group, *hostname, *domain;
{
	static char prev_hostname[MAXHOSTNAMELEN+1];
	static char grplist[MAXGRPLIST+1], nogrplist[MAXGRPLIST+1];
	char key[256];
	char *ypline = NULL;
	int yplen;
	register char *gr, *p;
	static time_t last;
	time_t time();
	time_t time_now;
	static int cache_time = 30; /* sec */

        if (domain == NULL) {
		(void) yp_get_default_domain(&domain);
		if (domain == NULL) {
			syslog(LOG_ERR, "NIS: no domain");
			return (NULL);
		}
	}
	time_now = time((long *)0);
	if (time_now > last + cache_time ||
	    strcmp(hostname, prev_hostname) != 0) {
		last = time_now;
		(void) strcpy(key, hostname);
		(void) strcat(key, ".");
		(void) strcat(key, domain);
		bzero(grplist, sizeof(grplist));
		if (yp_match(domain, "netgroup.byhost", key,
		    strlen(key), &ypline, &yplen) == 0) {
			(void) strncpy(grplist, ypline, MIN(yplen, MAXGRPLIST));
			free(ypline);
		} else {
			grplist[0] = '\0';
		}
		nogrplist[0] = '\0';
		(void) strcpy(prev_hostname, hostname);
	}

	for (gr = grplist ; *gr ; gr = p ) {
		for (p = gr ; *p && *p != ',' ; p++)
			;
		if (strncmp(group, gr, p - gr) == 0)
			return 1;
		if (*p == ',')
			p++;
	}
	for (gr = nogrplist ; *gr ; gr = p ) {
		for (p = gr ; *p && *p != ',' ; p++)
			;
		if (strncmp(group, gr, p - gr) == 0)
			return 0;
		if (*p == ',')
			p++;
	}

	if (innetgr(group, hostname, (char *)NULL, domain)) {
		if (strlen(grplist)+1+strlen(group) > MAXGRPLIST)
			return 1;
		if (*grplist)
			(void) strcat(grplist, ",");
		(void) strcat(grplist, group);
		return 1;
	} else {
		if (strlen(nogrplist)+1+strlen(group) > MAXGRPLIST)
			return 0;
		if (*nogrplist)
			(void) strcat(nogrplist, ",");
		(void) strcat(nogrplist, group);
		return 0;
	}
}

void
check_exports()
{
	FILE *f;
	struct stat st;
	static long last_xtab_time;
	struct exportent *xent;
	struct xent_list *xp, *xp_prev;
	char rpath[MAXPATHLEN];

	/*
	 *  read in /etc/xtab if it has changed 
	 */

	if (stat(XTAB, &st) != 0) {
		syslog(LOG_ERR, "Cannot stat %s: %m", XTAB);
		return;
	}
	if (st.st_mtime == last_xtab_time)
		return;				/* no change */

	xent_free(xent_list);			/* free old list */
	xent_list = NULL;
	
	f = setexportent();
	if (f == NULL)
		return;

	while (xent = getexportent(f)) {
		/*
		 * Get a path without symbolic links.
		 */
		if (realpath(xent->xent_dirname, rpath) == NULL) {
			syslog(LOG_ERR,
				"check_exports: realpath failed on %s: %m",
				xent->xent_dirname);
			continue;
		}

		xp = (struct xent_list *)malloc(sizeof(struct xent_list));
		if (xp == NULL)
			goto alloc_failed;
		if (xent_list == NULL)
			xent_list = xp;
		else
			xp_prev->x_next = xp;
		xp_prev = xp;
		bzero((char *)xp, sizeof(struct xent_list));
		xp->x_xent.xent_dirname = strdup(rpath);
		if (xp->x_xent.xent_dirname == NULL)
			goto alloc_failed;
		if (xent->xent_options) {
			xp->x_xent.xent_options = strdup(xent->xent_options);
			if (xp->x_xent.xent_options == NULL)
				goto alloc_failed;
		}
	}
	endexportent(f);
	last_xtab_time = st.st_mtime;
	return;

alloc_failed:
	syslog(LOG_ERR, "Memory allocation failed: %m");
	xent_free(xent_list);
	xent_list = NULL;
	endexportent(f);
	return;
}

void
xent_free(xp)
	struct xent_list *xp;
{
	register struct xent_list *next;

	while (xp) {
		if (xp->x_xent.xent_dirname)
			free(xp->x_xent.xent_dirname);
		if (xp->x_xent.xent_options)
			free(xp->x_xent.xent_options);
		next = xp->x_next;
		free((char *)xp);
		xp = next;
	}
}


/*
 * Remove an entry from mounted list 
 */
umount(rqstp)
	struct svc_req *rqstp;
{
	char *path;
	struct mountdlist *ml, *oldml;
	struct hostent *client;
	SVCXPRT *transp;

	transp = rqstp->rq_xprt;
	path = NULL;
	if (!svc_getargs(transp, xdr_path, &path)) {
		svcerr_decode(transp);
		return;
	}
	errno = 0;
	if (!svc_sendreply(transp, xdr_void, (char *)NULL))
		log_cant_reply(transp);

	client = getclientsname(transp);
	if (client != NULL) {
		oldml = mountlist;
		for (ml = mountlist; ml != NULL;
		     oldml = ml, ml = ml->ml_nxt) {
			if (strcmp(ml->ml_path, path) == 0 &&
			    strcmp(ml->ml_name, client->h_name) == 0) {
				if (ml == mountlist) {
					mountlist = ml->ml_nxt;
				} else {
					oldml->ml_nxt = ml->ml_nxt;
				}
				rmtab_delete(ml->ml_pos);
				free(ml->ml_path);
				free(ml->ml_name);
				free((char *)ml);
				break;
			}
		}
	}
	svc_freeargs(transp, xdr_path, &path);
}

/*
 * Remove all entries for one machine from mounted list 
 */
umountall(rqstp)
	struct svc_req *rqstp;
{
	struct mountdlist *ml, *oldml;
	struct hostent *client;
	SVCXPRT *transp;

	transp = rqstp->rq_xprt;
	if (!svc_getargs(transp, xdr_void, NULL)) {
		svcerr_decode(transp);
		return;
	}
	/*
	 * We assume that this call is asynchronous and made via the portmapper
	 * callit routine.  Therefore return control immediately. The error
	 * causes the portmapper to remain silent, as apposed to every machine
	 * on the net blasting the requester with a response. 
	 */
	svcerr_systemerr(transp);
	client = getclientsname(transp);
	if (client == NULL) {
		return;
	}
	oldml = mountlist;
	for (ml = mountlist; ml != NULL; ml = ml->ml_nxt) {
		if (strcmp(ml->ml_name, client->h_name) == 0) {
			if (ml == mountlist) {
				mountlist = ml->ml_nxt;
				oldml = mountlist;
			} else {
				oldml->ml_nxt = ml->ml_nxt;
			}
			rmtab_delete(ml->ml_pos);
			free(ml->ml_path);
			free(ml->ml_name);
			free((char *)ml);
		} else {
			oldml = ml;
		}
	}
}

/*
 * send current export list 
 */
export(rqstp)
	struct svc_req *rqstp;
{
	struct exportent *xent;
	struct exports *ex;
	struct exports **tail;
	char *grl;
	char *gr;
	struct groups *groups;
	struct groups **grtail;
	SVCXPRT *transp;
	struct xent_list *xp;

	transp = rqstp->rq_xprt;
	if (!svc_getargs(transp, xdr_void, NULL)) {
		svcerr_decode(transp);
		return;
	}

	check_exports();

	ex = NULL;
	tail = &ex;
	for (xp = xent_list ; xp ; xp = xp->x_next) {
		xent = &xp->x_xent;

		grl = getexportopt(xent, ACCESS_OPT);
		groups = NULL;
		if (grl != NULL) {
			grtail = &groups;
			while ((gr = strtok(grl, ":")) != NULL) {
				grl = NULL;
				grtail = newgroup(gr, grtail);
			}
		}
		tail = newexport(xent->xent_dirname, groups, tail);
	}

	errno = 0;
	if (!svc_sendreply(transp, xdr_exports, (char *)&ex))
		log_cant_reply(transp);
	freeexports(ex);
}


freeexports(ex)
	struct exports *ex;
{
	struct groups *groups, *tmpgroups;
	struct exports *tmpex;

	while (ex) {
		groups = ex->ex_groups;
		while (groups) {
			tmpgroups = groups->g_next;
			free(groups->g_name);
			free((char *)groups);
			groups = tmpgroups;
		}
		tmpex = ex->ex_next;
		free(ex->ex_name);
		free((char *)ex);
		ex = tmpex;
	}
}


struct groups **
newgroup(name, tail)
	char *name;
	struct groups **tail;
{
	struct groups *new;
	char *newname;

	new = (struct groups *) exmalloc(sizeof(*new));
	newname = (char *) exmalloc(strlen(name) + 1);
	(void) strcpy(newname, name);

	new->g_name = newname;
	new->g_next = NULL;
	*tail = new;
	return (&new->g_next);
}


struct exports **
newexport(name, groups, tail)
	char *name;
	struct groups *groups;
	struct exports **tail;
{
	struct exports *new;
	char *newname;

	new = (struct exports *) exmalloc(sizeof(*new));
	newname = (char *) exmalloc(strlen(name) + 1);
	(void) strcpy(newname, name);

	new->ex_name = newname;
	new->ex_groups = groups;
	new->ex_next = NULL;
	*tail = new;
	return (&new->ex_next);
}

char *
exmalloc(size)
	int size;
{
	char *ret;

	if ((ret = (char *) malloc((u_int)size)) == 0) {
		syslog(LOG_ERR, "Out of memory");
		exit(1);
	}
	return (ret);
}

usage()
{
	(void) fprintf(stderr, "usage: rpc.mountd [-n]\n");
	exit(1);
}

/*
 * Old geth() took a file descriptor. New getfh() takes a pathname.
 * So the the mount daemon can run on both old and new kernels, we try
 * the old version of getfh() if the new one fails.
 */
do_getfh(path, fh)
	char *path;
	fhandle_t *fh;
{
	int fd;
	int res;
	int save;

	res = getfh(path, fh);
	if (res < 0 && errno == EBADF) {	
		/*
		 * This kernel does not have the new-style getfh()
		 */
		fd = open(path, 0, 0);
		if (fd >= 0) {
			res = getfh((char *)fd, fh);
			save = errno;
			(void) close(fd);
			errno = save;
		}
	}
	return (res);
}


FILE *f;

void
rmtab_load()
{
	char *path;
	char *name;
	char *end;
	struct mountdlist *ml;
	char line[MAXRMTABLINELEN];

	f = fopen(RMTAB, "r");
	if (f != NULL) {
		while (fgets(line, sizeof(line), f) != NULL) {
			name = line;
			path = strchr(name, ':');
			if (*name != '#' && path != NULL) {
				*path = 0;
				path++;
				end = strchr(path, '\n');
				if (end != NULL) {
					*end = 0;
				}
				ml = (struct mountdlist *) 
					exmalloc(sizeof(struct mountdlist));
				ml->ml_path = (char *)
					exmalloc(strlen(path) + 1);
				(void) strcpy(ml->ml_path, path);
				ml->ml_name = (char *)
					exmalloc(strlen(name) + 1);
				(void) strcpy(ml->ml_name, name);
				ml->ml_nxt = mountlist;
				mountlist = ml;
			}
		}
		(void) fclose(f);
		(void) truncate(RMTAB, (off_t)0);
	} 
	f = fopen(RMTAB, "w+");
	if (f != NULL) {
		setlinebuf(f);
		for (ml = mountlist; ml != NULL; ml = ml->ml_nxt) {
			ml->ml_pos = rmtab_insert(ml->ml_name, ml->ml_path);
		}
	}
}


long
rmtab_insert(name, path)
	char *name;
	char *path;
{
	long pos;

	if (f == NULL || fseek(f, 0L, 2) == -1) {
		return (-1);
	}
	pos = ftell(f);
	if (fprintf(f, "%s:%s\n", name, path) == EOF) {
		return (-1);
	}
	return (pos);
}

void
rmtab_delete(pos)
	long pos;
{
	if (f != NULL && pos != -1 && fseek(f, pos, 0) == 0) {
		(void) fprintf(f, "#");
		(void) fflush(f);
	}
}

