/*
 * auth_init.c	This module takes care of request authorization.
 *
 * Authors:	Donald J. Becker, <becker@super.org>
 *		Rick Sladkey, <jrs@world.std.com>
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *
 *		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.
 */

#include "nfsd.h"

#define LINE_SIZE	1024
#define CHUNK_SIZE	1024	/* the 'typical' maximum line length	*/

#ifndef EXPORTSFILE
#define EXPORTSFILE	"/etc/exports"
#endif

clnt_param *clients = NULL;
clnt_param *unknown_clients = NULL;
clnt_param *default_client = NULL;
exportnode *export_list = NULL;
int allow_non_root = 0;
int promiscuous = 0;
int re_export = 0;
int auth_initialized = 0;
options default_options = {
	identity, 1, 1, 0, 0
};

/*
 * These are extern vars in nfsd.h where they are used to determine
 * file system attributes.
 */
struct svc_req		*svc_rqstp;
int			client_authenticate;

static _PRO(int filt_getc, (FILE *));
static _PRO(int getline, (char **, FILE *));
static _PRO(char *parse_opts, (char *, char, options *, char *));
static _PRO(char *h_strerror, (int));

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

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

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);
}

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

	/* skip white */
	while (isspace(*cp))
		cp++;
	while (*cp != terminator) {
		k = kwdbuf;
		while (isalnum(*cp) || *cp == '_')
			*k++ = *cp++;
		*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
			dprintf(0, "Unknown keyword \"%s\"\n", kwdbuf);
		while (isspace(*cp))
			cp++;
		if (*cp == terminator)
			break;
		if (*cp == ',')
			cp++;
		else if (!isalnum(*cp) && *cp != '_' && *cp != '\0') {
			if (client_name == NULL)
				dprintf(0,
					"Comma expected in opt list for dflt clnt (found '%c')\n", *cp);
			else
				dprintf(0,
					"Comma expected in opt list for clnt %s (found '%c')\n",
					client_name, *cp);
		}
		while (isspace(*cp))
			cp++;

		if (*cp == '\0' && *cp != terminator) {
			dprintf(0, "missing terminator \"%c\" on option list\n",
				terminator);
			return (cp);
		}
	}
	if (*cp != terminator)
		dprintf(0, "Trouble in parser, character '%c'.\n", *cp);

	cp++;			/* Skip past terminator */
	while (isspace(*cp))
		cp++;
	return (cp);
}

void auth_init(fname)
char *fname;
{
	FILE *ef;
	char *cp;		/* Current line position */
	char *sp;		/* Secondary pointer */
	char *fs_name;
	clnt_param *new_param;
	exportnode *resex;
	groupnode *resgr;
	char path[PATH_MAX];
	char resolved_path[PATH_MAX];

	if (fname == NULL)
		fname = EXPORTSFILE;

	if ((ef = fopen(fname, "r")) == NULL) {
		dprintf(0, "Could not open exports file %s: %s\n",
			fname, strerror(errno));
		exit(1);
	}
	while (getline(&cp, ef)) {
		char *saved_line = cp;	/* For free()ing it later. */
		char *mount_point, *host_name;
		struct hostent *hent;

		/* Check for "empty" lines. */
		if (*cp == '\0')
			continue;

		while (isspace(*cp))
			cp++;

		/* Get the file-system name. */
		fs_name = cp;
		while (*cp != '\0' && !isspace(*cp))
			cp++;
		for (sp = path; fs_name < cp;)
			*sp++ = *fs_name++;
		*sp = '\0';

		/* Make sure it's symlink-free, if possible. */
		if (realpath(path, resolved_path) == NULL)
			strcpy(resolved_path, path);

		/* Copy it into a new string. */
		if ((mount_point = malloc(strlen(resolved_path) + 1)) == NULL)
			mallocfailed();
		strcpy(mount_point, resolved_path);

		/* Build the RPC mount export list data structure. */
		if ((resex = malloc(sizeof *resex)) == NULL)
			mallocfailed();
		resex->ex_dir = mount_point;
		resex->ex_groups = NULL;
		resex->ex_next = export_list;
		export_list = resex;

		while (isspace(*cp))
			cp++;

#ifndef NEW_STYLE_EXPORTS_FILE
		/* Special case for anononymous NFS. */
		if (*cp == '\0' || *cp == '(') {
			new_param = (clnt_param *) malloc(sizeof *new_param);
			if (new_param == NULL)
				mallocfailed();
			new_param->clnt_name = NULL;
			new_param->mount_point = mount_point;
			new_param->o = default_options;
			new_param->next = unknown_clients;
			unknown_clients = new_param;
			if (*cp == '(')
				cp = parse_opts(cp + 1, ')',
						&(new_param->o),
						new_param->clnt_name);
		}
		while (*cp != '\0') {
			host_name = cp;

			/* Host name. */
			while (*cp != '\0' && !isspace(*cp) && *cp != '(')
				cp++;
			new_param = (clnt_param *) malloc(sizeof *new_param);
			if (new_param == NULL ||
			    (new_param->clnt_name = malloc(cp - host_name + 1)) == NULL)
				mallocfailed();
			for (sp = new_param->clnt_name; host_name < cp;)
				*sp++ = *host_name++;
			*sp = '\0';

			/* Finish parsing options. */
			new_param->o = default_options;
			while (isspace(*cp))
				cp++;
			if (*cp == '(')
				cp = parse_opts(cp + 1, ')', &(new_param->o),
						new_param->clnt_name);

			new_param->mount_point = mount_point;
			if ((resgr = malloc(sizeof *resgr)) == NULL)
				mallocfailed();
			resgr->gr_name = new_param->clnt_name;
			resgr->gr_next = resex->ex_groups;
			resex->ex_groups = resgr;

			if (strchr(new_param->clnt_name, '*') ||
			    strchr(new_param->clnt_name, '?')) {
				/* Add this host pattern to the unknown list. */
				new_param->next = unknown_clients;
				unknown_clients = new_param;
			} else if ((hent = gethostbyname(new_param->clnt_name)) == NULL) {
				dprintf(0, "Unknown host %s in %s (%s)\n",
				    new_param->clnt_name, fname,
				    h_strerror(h_errno));

				/* Add this loser to the list of unknowns. */
				new_param->next = unknown_clients;
				unknown_clients = new_param;
			} else {
				/*
				 * This should be changed to handle the address
				 * list under BSD 4.3 and compatible systems.
				 */
				new_param->clnt_addr = *((struct in_addr *) hent->h_addr);
				new_param->next = clients;	/* Add to client list */
				clients = new_param;
			}
		}
#endif
		free(saved_line);
	}
	fclose(ef);

	if (promiscuous) {
		if ((new_param = (clnt_param *) malloc(sizeof *new_param)) == NULL)
			mallocfailed();
		new_param->clnt_name = NULL;
		new_param->mount_point = NULL;
		default_client = new_param;
		new_param->o = default_options;
	}
	auth_initialized = 1;
}

static char *h_strerror(errnum)
int errnum;
{
	char *reason;

	switch (h_errno) {
#ifdef HOST_NOT_FOUND		/* Only on BSD 4.3 and compatible systems. */
	case HOST_NOT_FOUND:
		reason = "Authoritative -- the host exists only in your imagination.";
		break;
	case TRY_AGAIN:
		reason = "Non-Authoritative -- the host might exist.";
		break;
	case NO_RECOVERY:
		reason = "Non-recoverable error.";
		break;
	case NO_ADDRESS:
		reason = "Valid host name, but no address.";
		break;
#endif
	default:
		reason = "Unknown reason.";
	}
	return reason;
}
