/*
 * 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 : mountd.c
 *        nfs mount daemon
 */


#include <stdio.h>
#include <stdlib.h>
#include <stat.h>
#include <osbind.h>
#include <mintbind.h>
#include <signal.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "thread.h"
#include "mount.h"
#include "rpc.h"
#include "svc.h"
#include "auth.h"
#include "version.h"
#include "fh.h"
#include "util.h"

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


/* try to use this port, so if we dont have a port mapper it might still
 * work as the mount command knows about this port.
 */
#define MOUNT_PORT  2050


char msg[] = "\r\nMOUNTD " VERSION ", (C) Ulrich Khn\r\n"
"\033pUSE IT AT OWN RISK!\033q\r\n\r\n";

char whatmsg[] = "@(#)mountd " VERSION ", (C) Ulrich Khn  " __DATE__;


#define MOUNTD_NAME  "mountd"


#define RMOUNTED  "\\etc\\rmtab"


/* If the daemon was started by inetd, we see that stdin is a socket, and
 * then we want to remove ourselves after being idle for some time from
 * memory. This is this time out value in seconds.
 */
#define DYNAMIC_TIMEOUT  120


extern int __mint;
int daemon_fd;
int dynamic_rpc = 0;  /* set this if stdin is a socket (started by inetd) */
int debug;


/* These functions deal with the list of clients that have mounted directories
 * from our host via NFS. This list is preserved across crashes within a file
 * which name is given in the constant RMOUNTED.
 */

/* NOTE: the block of memory containing the hostname and the directory is
 *       allocated in one piece and pointed to by the ml_directory member
 */

static mountlist *mntlist = NULL;

long
get_mlist()
{
	char buf[MNTPATHLEN+MNTNAMLEN+5];
	char *p;
	int n;
	long r;
	mountlist *mnt;
	FILE *fp = fopen(RMOUNTED, "r");

	if (!fp)
		return -1;

	/* free a possible old list */
	mnt = mntlist;
	while (mnt)
	{
		mntlist = mnt->ml_next;
		free(mnt);
		mnt = mntlist;
	}
	mntlist = NULL;

	r = 0;
	while ((p = fgets(buf, MNTPATHLEN+MNTNAMLEN+3, fp)) != NULL)
	{
		mnt = malloc(sizeof(mountlist));

		if (!mnt)
		{
			r = -2;
			break;
		}

		n = 1;
		while (*p)
		{
			n++;
			if ((*p == '\r') || (*p == '\n'))
			{
				*p = '\0';
				break;
			}
			p++;
		}

		mnt->ml_directory = malloc(n);
		if (!mnt->ml_directory)
		{
			free(mnt);
			r = -2;
			break;
		}

		strcpy(mnt->ml_directory, buf);
		p = strchr(mnt->ml_directory, ':');
		if (!p)
		{
			/* we have no hostname here, so set it to "" */
			mnt->ml_hostname = mnt->ml_directory + strlen(mnt->ml_directory);
		}
		else
		{
			mnt->ml_hostname = mnt->ml_directory;
			mnt->ml_directory = p+1;
			*p = '\0';
		}

		mnt->ml_next = mntlist;
		mntlist = mnt;
	}
	fclose(fp);
	return r;
}


long
write_mlist()
{
	char buf[MNTPATHLEN+MNTNAMLEN+5];
	mountlist *mnt;
	FILE *fp = fopen(RMOUNTED, "w");

	if (!fp)
		return -1;

	for (mnt = mntlist;  mnt;  mnt = mnt->ml_next)
	{
		strcpy(buf, mnt->ml_hostname);
		strcat(buf, ":");
		strcat(buf, mnt->ml_directory);
		strcat(buf, "\r\n");   /* BUG: Atari specific NL sequence */
		fputs(buf, fp);
	}

	fclose(fp);
	return 0;
}


long
add_mlist(char *host, char *dir)
{
	int n, m;
	mountlist *mnt;

	for (mnt = mntlist;  mnt;  mnt = mnt->ml_next)
	{
		if (!strcmp(mnt->ml_hostname, host) && !strcmp(mnt->ml_directory, dir))
			return 0;
	}

	mnt = malloc(sizeof(mountlist));
	if (!mnt)
		return -1;

	n = strlen(host);
	m = strlen(dir);
	mnt->ml_directory = malloc(n+m+2);
	if (!mnt->ml_directory)
	{
		free(mnt);
		return -1;
	}

	strcpy(mnt->ml_directory, dir);
	mnt->ml_hostname = mnt->ml_directory+m+1;
	strcpy(mnt->ml_hostname, host);

	mnt->ml_next = mntlist;
	mntlist = mnt;
	return write_mlist();
}


long
rem_mlist(char *host, char *dir)
{
	mountlist *pmnt, **ppmnt;

	for (pmnt = mntlist, ppmnt = &mntlist;  pmnt;
	                        ppmnt = &pmnt->ml_next, pmnt = pmnt->ml_next)
	{
		if (!strcmp(pmnt->ml_hostname, host))
			if (!strcmp(pmnt->ml_directory, dir))
			{
				/* we found the right entry, so remove it */
				*ppmnt = pmnt->ml_next;
				free(pmnt->ml_directory);
				free(pmnt);  /* we dont go into the next iteration! */
				return write_mlist();
			}
	}
	return 0;  /* no error returned! */
}


long
remall_mlist(char *host)
{
	mountlist *pmnt, **ppmnt;

	pmnt = mntlist;
	ppmnt = &mntlist;
	while (pmnt)
	{
		if (!strcmp(pmnt->ml_hostname, host))
		{
			mountlist *next = pmnt->ml_next;

			*ppmnt = pmnt->ml_next;
			free(pmnt->ml_directory);
			free(pmnt);
			pmnt = next;
			continue;
		}
		ppmnt = &pmnt->ml_next;
		pmnt = pmnt->ml_next;
	}
	return write_mlist();
}



/* This variable is set when the service procedure is entered to show
 * that there is a request being in service. So the reread of the
 * config file is done later.
 */
static int in_service = 0;

/* This variable is used to indicate that the config file shoulb be
 * read at the end of the current service request.
 */
static int do_reread = 0;



/* Timeout stuff for the dynamic rpc feature */
static long ticks = 0;

static void
do_timeout (long x)
{
	if (!dynamic_rpc)
		return;
	ticks += 10;
	if (ticks >= DYNAMIC_TIMEOUT)
		exit (0);
}



/*
 * This is the dispatcher for the RPC mount program, version 1. Some
 * services are not implemented yet.
 */
void
mntprog_1(struct svc_req *req, SVCXPRT *xprt)
{
	long r;
	char dirname[MNTPATHLEN+1];
	fhstatus status;
	struct hostent *hp;
	struct in_addr addr;
	accesslist *ac;

	/* There was an access to the daemon, so retrigger the dynamic timeout */
	ticks = 0;

	in_service = 1;
	addr = xprt->xp_raddr.sin_addr;

	switch (req->rq_proc)
	{
		case MOUNTPROC_NULL:
			svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL);
			break;
		case MOUNTPROC_MNT:
			if (!svc_getargs(xprt, (xdrproc_t)xdr_dirpath, dirname))
				return;

#ifdef SYSLOG
			if (debug)
				syslog(LOG_INFO, "mount request for `%s' from %s",
				                               dirname, inet_ntoa(addr));
#endif

			ac = auth_mnt(dirname, xprt);
			if (ac)
			{
				r = fh_create(dirname, (svc_fh*)&status.fhstatus_u.directory);
			}
			else
			{
				r = 1;   /* maybe there is a better return code?? */
#ifdef SYSLOG
				syslog(LOG_WARNING, "host %s is not allowed to mount `%s'",
				                                  inet_ntoa(addr), dirname);
#endif
			}		
			if (r == 0)
			{
				/* no error checks as the mount operation itself was successful */
				hp = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
				if (hp)
					add_mlist(hp->h_name, dirname);
			}
			status.status = r;
			svc_sendreply(xprt, (xdrproc_t)xdr_fhstatus, &status);
			break;
		case MOUNTPROC_UMNT:
			svc_getargs(xprt, (xdrproc_t)xdr_dirpath, dirname);
#ifdef SYSLOG
			if (debug)
				syslog(LOG_INFO, "unmount request for `%s' from %s",
				                                dirname, inet_ntoa(addr));
#endif
			hp = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
			rem_mlist(hp->h_name, dirname);
			svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL);
			break;
		case MOUNTPROC_DUMP:
#ifdef SYSLOG
			if (debug)
				syslog(LOG_INFO, "dump request from %s", inet_ntoa(addr));
#endif
			/* no args required */
			svc_sendreply(xprt, (xdrproc_t)xdr_mountlist, mntlist);
			break;
		case MOUNTPROC_UMNTALL:
#ifdef SYSLOG
			if (debug)
				syslog(LOG_INFO, "unmount all request from %s", inet_ntoa(addr));
#endif
			/* no args required */
			hp = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
			remall_mlist(hp->h_name);
			svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL);
			break;
		case MOUNTPROC_EXPORT:
			/* no args required */
			/* BUG: not implemented yet */
			svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL);
			break;
		default:
			svcerr_noproc(xprt);
			break;
	}
	if (do_reread)
	{
		auth_init();
		get_mlist();
		do_reread = 0;
	}
	in_service = 0;
}


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


void
handle_sigterm(int sig)
{
#ifdef SYSLOG
	syslog(LOG_NOTICE, "exiting on signal %d", sig);
	closelog();
#endif
	exit(0);
}


void
init_signals()
{
	signal(SIGHUP, handle_sighup);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGTERM, handle_sigterm);
}



int
do_daemon(long dummy)
{
	long res, maxmsgsiz = 4096;
	SVCXPRT *xprt;
	struct sockaddr_in sin;
	long addrsize = sizeof(struct sockaddr_in);

#ifdef SYSLOG
	/* maybe we should just use LOG_DAEMON here, but this is for system
	 * security reasons.
	 */
	openlog("mountd", LOG_PID | LOG_NDELAY, LOG_AUTH);
#endif

	init_signals();
	daemon_fd = 0;

	/* First check if stdin is a socket. That is the case, if we were
	 * started by the inetd due to an RPC request. Then we must not
	 * open a new socket, as it could not be bound to the correct port
	 * anyway. Instead, enable dynamic timeout checking for shutting
	 * down after a certain idle time.
	 */
	res = getsockname(0, (struct sockaddr*)&sin, &addrsize);
	if (res == 0)
	{
		/* Here we know that stdin is a socket, so set up all the stuff
		 * accordingly.
		 */
		long ssize = sizeof(long);
		long sock_type = 0;

		if (sin.sin_family != AF_INET)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "started on non-INET socket");
#endif
			exit(1);   /* only INET stuff supported */
		}
		dynamic_rpc = 1;
		res = getsockopt(0, SOL_SOCKET, SO_TYPE, &sock_type, &ssize);

		/* For now, only SOCK_DGRAM is supported */
		if ((res < 0) || (sock_type != SOCK_DGRAM))
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "up to now only UDP service supported");
#endif
			exit(1);
		}

		/* Get the default message size from the socket */
		ssize = sizeof(long);
		res = getsockopt(0, SOL_SOCKET, SO_RCVBUF, &maxmsgsiz, &ssize);
		if (res < 0)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "could not set socket buffer size");
#endif
			exit(1);
		}

		daemon_fd = 0;
	}
	else
	{
		/* Open a new socket, set the default message size, and bind it
		 * to the desired port.
		 */
		dynamic_rpc = 0;
		res = socket(PF_INET, SOCK_DGRAM, 0);
		if (res < 0)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "could not open socket");
#else
			Cconws("mountd: could not open socket\r\n");
#endif
			return res;
		}
		daemon_fd = res;

		res = setsockopt(daemon_fd, SOL_SOCKET, SO_RCVBUF,
		                                  &maxmsgsiz, sizeof(long));
		if (res < 0)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "could not set socket options");
#else
			Cconws("mountd: could not set socket options\r\n");
#endif
			return res;
		}

		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = htonl(INADDR_ANY);
		sin.sin_port = htons(MOUNT_PORT);

		res = bind(daemon_fd, (struct sockaddr*)&sin, sizeof(sin));
		if (res < 0)
		{
			/* we could not get our desired port, so try to get a random one */
			sin.sin_port = htons(0);
			res = bind(daemon_fd, (struct sockaddr*)&sin, sizeof(sin));
			if (res < 0)
			{
#ifdef SYSLOG
				syslog(LOG_ERR, "could not bind socket");
#else
				Cconws("mountd: could not bind socket\r\n");
#endif
				return 1;
			}
		}
	}

	xprt = svc_create(daemon_fd, maxmsgsiz, maxmsgsiz);
	if (!xprt)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not create service");
#else
		Cconws("mountd: could not create service\r\n");
#endif
		return -1;
	}

	/*
	 * Keep svc_register from doing pmap_set() by passing a protocol of
	 * zero if run by inetd.
	 */
	res = 0;
	if (dynamic_rpc)
		res = svc_register(xprt, MOUNT_PROGRAM, MOUNT_VERSION, mntprog_1, 0);
	else
		res = svc_register(xprt, MOUNT_PROGRAM,
		                    MOUNT_VERSION, mntprog_1, IPPROTO_UDP);
	if (!res)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not register service");
#else
		Cconws("mountd: could not register service\r\n");
#endif
		return -1;
	}

	if (dynamic_rpc)
		svc_timeout (xprt, 10000, do_timeout);

	xprt_register(xprt);
	fh_init();
	get_mlist();

	svc_run();
	return 0;
}



int
main(int argc, char *argv[])
{
	long r;
	long addr_size = sizeof(struct sockaddr_in);
	struct sockaddr_in sin;

	/* switch to MiNT's root drive */
	Dsetdrv('u'-'a');
	Dsetpath("\\");

	/* Test if stdin is a socket */
	if (getsockname(0, (struct sockaddr*)&sin, &addr_size) < 0)
		dynamic_rpc = 0;
	else
		dynamic_rpc = 1;

	/* set up command line options */
	if ((argc == 2) && !strncmp(argv[1], "-d", 2))
	{
		if (argv[1][2])
			debug = argv[1][2] - '0';
		else
			debug = 1;

		Cconws("Debugging enabled at level");
		Cconout(debug+'0');
		Cconws("\r\n");
	}
	else
		debug = 0;

	/* read th exports file */
	r = auth_init();
	if (r < 0)
	{
		if (!dynamic_rpc)
		{
			if (argv[0])
				Cconws(argv[0]);
			else
				Cconws("MOUNTD");
			Cconws(": error while reading exports file.\r\n");
		}
		return 1;
	}

	/* now fork off the daemon process into the background */
	if (!dynamic_rpc)
	{
		if (thread(MOUNTD_NAME, do_daemon, 0) >= 0)
			Cconws(msg);
		else
		{
			if (argv[0])
				Cconws(argv[0]);
			else
				Cconws("MOUNTD");
			Cconws(": cannot fork.\r\n");
			return 1;
		}
	}
	else
		do_daemon(0);
	return 0;
}
