/* UNFSD - copyright Mark A Shand, May 1988.
 * This software maybe be used for any purpose provided
 * the above copyright notice is retained.  It is supplied
 * as is, with no warranty expressed or implied.
 */

/*
**	Udp socket establishment routine
*/


#ifndef	lint
static char	sccsid[]	= "@(#)makesock.c	1.0	88/03/12";
#endif	lint

#include "unfsd.h"
#include "tmp/libpq-fs.h"
extern char *PQhost;
extern char *PQport;

#define DEFAULT_EXPORTSFILE	EXPORTSFILE

#ifndef SYSERROR
#define SYSERROR	(-1)
#endif

char *SmgrList[] = {
    "magnetic disk",
#ifdef SONY_JUKEBOX
    "sony jukebox",
#endif
#ifdef MAIN_MEMORY
    "main memory",
#endif
    (char *) NULL
};

/*
 *  smgrlookup() -- Look up a storage manager by name.
 *
 *      The offsets in the storage manager table compiled into this
 *      program are the same as those used by the backend.  We rely
 *      on this fact.
 */

int
smgrlookup(smgr)
    char *smgr;
{
    int i;

    for (i = 0; SmgrList[i] != (char *) NULL; i++)
        if (strcmp(smgr, SmgrList[i]) == 0)
            return (i);

    return (-1);
}

int
makesock(port,socksz)
int	port;
int	socksz;
{
	struct sockaddr_in	my_sock;
	int			s;
	extern int		errno;
	extern char		*sys_errlist;

	bzero((char *)&my_sock, sizeof(my_sock));
	my_sock.sin_addr.s_addr = INADDR_ANY;
	my_sock.sin_family = AF_INET;
	my_sock.sin_port = htons(port);

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	{
		syslog(LOG_DAEMON|LOG_ERR, "could not make a socket: %s",
			sys_errlist[errno]);
		return SYSERROR;
	}
#ifdef SO_SNDBUF
	{
		int	sblen, rblen;

		sblen = rblen = socksz + 1024;
		/* 1024 for rpc & transport overheads */
		if (
		setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sblen, sizeof sblen) < 0
		||
		setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rblen, sizeof sblen) < 0
		)
		syslog(LOG_DAEMON|LOG_ERR, "setsockopt failed",
			sys_errlist[errno]);
	}
#endif

	if (bind(s, &my_sock, sizeof(my_sock)) == -1)
	{
		syslog(LOG_DAEMON|LOG_ERR, "could not bind name to socket",
			sys_errlist[errno]);
		return SYSERROR;
	}

	return s;
}

ftype ft_map[16];
int svc_euid;
int svc_egid;
int cur_gid;
int svc_ngids;
int svc_gids[NGROUPS+2];

static clnt_param	*clients = NULL;
static clnt_param	*default_client = NULL;

#define	LINE_SIZE	1024

static char *
parse_opts(s, terminator, o, client_name)
char	*s;
char	terminator;
options	*o;
char	*client_name;
{
	/* parse option string pointed to by s and set o accordingly */
	char	kwdbuf[LINE_SIZE];
	char	*k;

	/* skip white */
	while (isspace(*s))
		s++;
	while (*s != terminator)
	{
		k = kwdbuf;
		while (isalnum(*s) || *s == '_')
			*k++ = *s++;
		*k = '\0';
		/* process keyword */
		if (strcmp(kwdbuf, "secure") == 0)
			o->secure_port = 1;
		else if (strcmp(kwdbuf, "insecure") == 0)
			o->secure_port = 0;
		else if (strcmp(kwdbuf, "root_squash") == 0)
			o->root_squash = 1;
		else if (strcmp(kwdbuf, "no_root_squash") == 0)
			o->root_squash = 0;
		else if (strcmp(kwdbuf, "ro") == 0)
			o->read_only = 1;
		else if (strcmp(kwdbuf, "rw") == 0)
			o->read_only = 0;
		else if (strcmp(kwdbuf, "link_relative") == 0)
			o->link_relative = 1;
		else if (strcmp(kwdbuf, "link_absolute") == 0)
			o->link_relative = 0;
		else if (strcmp(kwdbuf, "map_daemon") == 0)
			o->uidmap = map_daemon;
		else if (strcmp(kwdbuf, "map_identity") == 0)
			o->uidmap = identity;
		else
			syslog(LOG_DAEMON|LOG_ERR, "Unknown keyword \"%s\"", kwdbuf);
		while (isspace(*s))
			s++;
		if (*s == ',')
			s++;
		else if (!isalnum(*s) && *s != '_' && *s != '\0')
		{
			if (client_name == NULL)
				syslog(LOG_DAEMON|LOG_ERR,
				    "comma expected in option list for default client (found %c)", *s);
			else
				syslog(LOG_DAEMON|LOG_ERR,
				    "comma expected in option list for client %s (found %c)",
				    client_name, *s);
		}
		while (isspace(*s))
			s++;
		if (*s == '\0' && *s != terminator)
		{
			syslog(LOG_DAEMON|LOG_ERR,
				"missing terminator \"%c\" on option list",
				terminator);
			return s;
		} else {
			s++;
		}
	}
	while (isspace(*s))
		s++;
	return s;
}

static int
filt_getc(f)
FILE	*f;
{
	int	c;

	c = getc(f);
	if (c == '\\')
	{
		c = getc(f);
		if (c == '\n')
			c = ' ';
		else if (c != EOF)
			ungetc(c, f);
		c = '\\';
	}
	else if (c == '#')
	{
		int lastc = c;
		while ((c = getc(f)) != '\n' && c != EOF)
			lastc = c;
		if (c == '\n' && lastc == '\\')
			c = getc(f);
	}
	return c;
}

#define CHUNK_SIZE	512

static int
getline(lbuf, f)
char	**lbuf;
FILE	*f;
{
	register	c;
	register char	*p;
	char	*buf;
	int	sz = CHUNK_SIZE;

	if ((buf = malloc(CHUNK_SIZE)) == NULL)
		mallocfailed();
	p = buf;
	while ((c = filt_getc(f)) != '\n' && c != EOF)
	{
		if (p - buf == sz-2)
		{
			if ((buf = realloc(buf, sz*2)) == NULL)
				mallocfailed();
			p = buf + sz-2;
			sz *= 2;
		}
		*p++ = c;
	}
	if (c == EOF && p == buf)
	{
		free(buf);
		*lbuf = NULL;
		return 0;
	}
	*p++ = '\0';
	*lbuf = buf;
	return 1;
}

char *dbname;

unfsd_init(argc, argv)
int	argc;
char	**argv;
{
	int	i, n;
	FILE	*f;
	char	*lbuf;
	char	*p, *q, *r;
	char	*exportsfile = DEFAULT_EXPORTSFILE;
	struct hostent	*hent;
	clnt_param *tmp;
	char	*mount_point;

	/* options */
	int	promiscuous = 0;
	char	*o_string = NULL;
	options	def_opts;
	int ch, dflag, sflag;
	char *smgrname = NULL;
	extern char *optarg;
	extern char *PQhost, *PQport;
	extern int optind;
	extern int smgr;
	
	sflag = dflag = 0;
#if 0
#ifndef DEBUG
	{
		int fd;

		if (fork())
			exit(0);
		close(0);
		close(1);
		close(2);
		if ((fd = open("/dev/tty", 2)) >= 0)
		{
			ioctl(fd, TIOCNOTTY, (char *)0);
			(void) close(fd);
		}
	}
#endif DEBUG
#endif
	/* setup defaults */
	def_opts.uidmap = identity;
	def_opts.root_squash = 0;
	def_opts.secure_port = 1;
	def_opts.read_only = 1;
	def_opts.link_relative = 1;

	openlog("unfsd", LOG_PID|LOG_TIME);

/*	argc--; argv++;*/
	
	while ((ch = getopt(argc, argv, "H:P:D:S:po:f:")) != EOF) {
	    switch(ch) {
	      case 'o':
		o_string = optarg;
		break;
	      case 'p':
		promiscuous = 1;
		break;
	      case 'P':
		PQport = optarg;
		break;
	      case 'D':
		dflag = 1;
		dbname = optarg;
		break;
	      case 'H':
		PQhost = optarg;
		break;
	      case 'S':
		smgrname = optarg;
		if ((smgr = smgrlookup(smgrname)) < 0) {
		    fprintf(stderr,"storage manager %s unknown\n",smgrname);
		    exit(1);
		}
		sflag =1;
		break;
	      case 'f':
		exportsfile = optarg;
		break;
	      case '?':
	      default:
		syslog(LOG_DAEMON|LOG_ERR, "Unknown option");
	    }
	}

	{
	    extern int p_attr_caching;
	    p_attr_caching = 1;
	}
	if (!dflag) {
	    usage();
	    exit(1);
	}
#ifdef INVERSION
	if (!sflag) {
	    fprintf(stderr,"storage manager must be specified.\n");
	    exit(1);
	}
#endif
	PQsetdb(dbname);

	fh_init();

	ft_map[0] = NFNON;
	for (i = 1; i < 16; i++)
		ft_map[i] = NFBAD;
#ifdef S_IFIFO
	ft_map[ft_extr(S_IFIFO)] = NFFIFO;
#endif
	ft_map[ft_extr(S_IFCHR)] = NFCHR;
	ft_map[ft_extr(S_IFDIR)] = NFDIR;
	ft_map[ft_extr(S_IFBLK)] = NFBLK;
	ft_map[ft_extr(S_IFREG)] = NFREG;
	ft_map[ft_extr(0)] = NFREG;
	ft_map[ft_extr(S_IFLNK)] = NFLNK;
	ft_map[ft_extr(S_IFSOCK)] = NFSOCK;

	umask(0);

	svc_euid = geteuid();
	svc_ngids = getgroups(NGROUPS, svc_gids);
	/* Does this always include gid and egid? I don't know. Play it safe */
	if (svc_ngids < 0)
		svc_ngids = 0;
	n = getgid();
	for (i = 0; i < svc_ngids; i++)
		if (svc_gids[i] == n)
			break;
	if (i == svc_ngids)
		svc_gids[svc_ngids++] = n;
	n = svc_egid = getegid();
	for (i = 0; i < svc_ngids; i++)
		if (svc_gids[i] == n)
			break;
	if (i == svc_ngids)
		svc_gids[svc_ngids++] = n;
	cur_gid = svc_gids[0];
	if (o_string != NULL)
		parse_opts(o_string, '\0', &def_opts, NULL);

	if (exportsfile != NULL)
	{
		if ((f = fopen(exportsfile, "r")) == NULL)
		{
			syslog(LOG_DAEMON|LOG_WARNING, "Could not open %s: %m", exportsfile);
			exit(1);
		}
		/* process exports file */
		while (getline(&lbuf, f))
		{
			p = lbuf;
			while (isspace(*p))
				p++;
			q = p;
			/* file-system name */
			while (*q != '\0' && !isspace(*q))
				q++;
			if ((mount_point = malloc(q-p+1)) == NULL)
				mallocfailed();
			for (r = mount_point; p < q;)
				*r++ = *p++;
			*r = '\0';
			p = q;
			while (isspace(*p))
				p++;
			while (*p != '\0')
			{
				q = p;
				/* host name */
				while (*q != '\0' && !isspace(*q) && *q != '(')
					q++;
				if ((tmp = (clnt_param *) malloc(sizeof *tmp)) == NULL
				 || (tmp->clnt_name = malloc(q-p+1)) == NULL)
					mallocfailed();
				for (r = tmp->clnt_name; p < q;)
					*r++ = *p++;
				*r = '\0';
				tmp->mount_point = mount_point;
				if ((hent = gethostbyname(tmp->clnt_name)) == NULL)
				{
					syslog(LOG_DAEMON|LOG_WARNING, "Unknown host %s in %s",
						tmp->clnt_name, exportsfile);
					free(tmp->clnt_name); free(tmp);
					continue;
				}
				tmp->clnt_addr = *((struct in_addr *)hent->h_addr);
				tmp->next = clients;
				clients = tmp;
				tmp->o = def_opts;
				while (isspace(*p))
					p++;
				if (*p == '(')
					p = parse_opts(p+1, ')', &(tmp->o), tmp->clnt_name);
			}
			free(lbuf);
		}
		fclose(f);
	}
	if (promiscuous)
	{
		if ((tmp = (clnt_param *) malloc(sizeof *tmp)) == NULL)
			mallocfailed();
		tmp->clnt_name = NULL;
		tmp->mount_point = NULL;
		default_client = tmp;
		tmp->o = def_opts;
	}
}

usage()
{
    fprintf(stderr,"unfsd -D database [-S storage_manager] [-H host] [-P port] [-o opts] [-p] [-f exports]\n");
}

int
_in_gid_set(gid)
int	gid;
{
	int i;

	for (i = 0; i < svc_ngids; i++)
		if (svc_gids[i] == gid)
		{
			cur_gid = gid;
			return 1;
		}
	return 0;
}

clnt_param *
knownclient(rqstp)
	struct svc_req *rqstp;
{
	clnt_param	**cpp, *cp;

	/* find host parameter struct */
	for (cpp = &clients; *cpp != NULL; cpp = &((*cpp)->next))
	{
		if ((*cpp)->clnt_addr.s_addr == svc_getcaller(rqstp->rq_xprt)->sin_addr.s_addr)
		{
			cp = *cpp;
			if (cp != clients)
			{
				/* Move to front */
				*cpp = cp->next;
				cp->next = clients;
				clients = cp;
			}
			goto found_it;
		}
	}
	if (default_client != NULL)
		cp = default_client;
	else
	{
		syslog(LOG_DAEMON|LOG_CRIT, "Access attempt by unknown client %08X",
			ntohl(svc_getcaller(rqstp->rq_xprt)->sin_addr));
		return NULL;
	}
    found_it:
	/* check request originated on a privileged port */
	if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) >= IPPORT_RESERVED && cp->o.secure_port)
	{
		syslog(LOG_DAEMON|LOG_CRIT, "NFS request from %08X originated on insecure port",
			ntohl(svc_getcaller(rqstp->rq_xprt)->sin_addr));
		return NULL;
	}
	return cp;
}
