/* This is copied from portmap 4.0-29 in RedHat. */

 /*
  * pmap_check - additional portmap security.
  * 
  * Always reject non-local requests to update the portmapper tables.
  * 
  * Refuse to forward mount requests to the nfs mount daemon. Otherwise, the
  * requests would appear to come from the local system, and nfs export
  * restrictions could be bypassed.
  * 
  * Refuse to forward requests to the nfsd process.
  * 
  * Refuse to forward requests to NIS (YP) daemons; The only exception is the
  * YPPROC_DOMAIN_NONACK broadcast rpc call that is used to establish initial
  * contact with the NIS server.
  * 
  * Always allocate an unprivileged port when forwarding a request.
  * 
  * If compiled with -DCHECK_PORT, require that requests to register or
  * unregister a privileged port come from a privileged port. This makes it
  * more difficult to replace a critical service by a trojan.
  * 
  * If compiled with -DHOSTS_ACCESS, reject requests from hosts that are not
  * authorized by the /etc/hosts.{allow,deny} files. The local system is
  * always treated as an authorized host. The access control tables are never
  * consulted for requests from the local system, and are always consulted
  * for requests from other hosts.
  * 
  * Author: Wietse Venema (wietse@wzv.win.tue.nl), dept. of Mathematics and
  * Computing Science, Eindhoven University of Technology, The Netherlands.
  */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_LIBWRAP
#include <unistd.h>
#include <string.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <netdb.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <tcpd.h>

#include "sockaddr.h"
#include "tcpwrapper.h"
#include "xlog.h"

#ifdef SYSV40
#include <netinet/in.h>
#include <rpc/rpcent.h>
#endif	/* SYSV40 */

#define ALLOW 1
#define DENY 0

#ifdef IPV6_SUPPORTED
static void
present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
{
	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap;
	socklen_t len = (socklen_t)buflen;

	switch (sap->sa_family) {
	case AF_INET:
		if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0)
			return;
		break;
	case AF_INET6:
		if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len) != 0)
			return;
	}

	memset(buf, 0, buflen);
	strncpy(buf, "unrecognized caller", buflen);
}
#else	/* !IPV6_SUPPORTED */
static void
present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
{
	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
	socklen_t len = (socklen_t)buflen;

	if (sap->sa_family == AF_INET)
		if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0)
			return;

	memset(buf, 0, buflen);
	strncpy(buf, "unrecognized caller", (size_t)buflen);
}
#endif	/* !IPV6_SUPPORTED */

typedef struct _haccess_t {
	TAILQ_ENTRY(_haccess_t)	list;
	int			allowed;
	union nfs_sockaddr	address;
} haccess_t;

#define HASH_TABLE_SIZE 1021
typedef struct _hash_head {
	TAILQ_HEAD(host_list, _haccess_t) h_head;
} hash_head;

static hash_head haccess_tbl[HASH_TABLE_SIZE];

static unsigned long
strtoint(const char *str)
{
	unsigned long i, n = 0;
	size_t len = strlen(str);

	for (i = 0; i < len; i++)
		n += (unsigned char)str[i] * i;

	return n;
}

static unsigned int
hashint(const unsigned long num)
{
	return (unsigned int)(num % HASH_TABLE_SIZE);
}

static unsigned int
HASH(const char *addr, const unsigned long program)
{
	return hashint(strtoint(addr) + program);
}

static void
haccess_add(const struct sockaddr *sap, const char *address,
		const unsigned long program, const int allowed)
{
	hash_head *head;
	haccess_t *hptr;
	unsigned int hash;

	hptr = (haccess_t *)malloc(sizeof(haccess_t));
	if (hptr == NULL)
		return;

	hash = HASH(address, program);
	head = &(haccess_tbl[hash]);

	hptr->allowed = allowed;
	memcpy(&hptr->address, sap, (size_t)nfs_sockaddr_length(sap));

	if (TAILQ_EMPTY(&head->h_head))
		TAILQ_INSERT_HEAD(&head->h_head, hptr, list);
	else
		TAILQ_INSERT_TAIL(&head->h_head, hptr, list);
}

static haccess_t *
haccess_lookup(const struct sockaddr *sap, const char *address,
		const unsigned long program)
{
	hash_head *head;
	haccess_t *hptr;
	unsigned int hash;

	hash = HASH(address, program);
	head = &(haccess_tbl[hash]);

	TAILQ_FOREACH(hptr, &head->h_head, list) {
		if (nfs_compare_sockaddr(&hptr->address.sa, sap))
			return hptr;
	}
	return NULL;
}

static void
logit(const char *address)
{
	xlog_warn("connect from %s denied: request from unauthorized host",
			address);
}

static int
good_client(char *name, struct sockaddr *sap)
{
	struct request_info req;

	request_init(&req, RQ_DAEMON, name, RQ_CLIENT_SIN, sap, 0);
	sock_methods(&req);

	if (hosts_access(&req)) 
		return ALLOW;

	return DENY;
}

static int
check_files(void)
{
	static time_t allow_mtime, deny_mtime;
	struct stat astat, dstat;
	int changed = 0;

	if (stat("/etc/hosts.allow", &astat) < 0)
		astat.st_mtime = 0;
	if (stat("/etc/hosts.deny", &dstat) < 0)
		dstat.st_mtime = 0;

	if(!astat.st_mtime || !dstat.st_mtime)
		return changed;

	if (astat.st_mtime != allow_mtime)
		changed = 1;
	else if (dstat.st_mtime != deny_mtime)
		changed = 1;

	allow_mtime = astat.st_mtime;
	deny_mtime = dstat.st_mtime;

	return changed;
}

/**
 * check_default - additional checks for NULL, DUMP, GETPORT and unknown
 * @name: pointer to '\0'-terminated ASCII string containing name of the
 *		daemon requesting the access check
 * @sap: pointer to sockaddr containing network address of caller
 * @program: RPC program number caller is attempting to access
 *
 * Returns TRUE if the caller is allowed access; otherwise FALSE is returned.
 */
int
check_default(char *name, struct sockaddr *sap, const unsigned long program)
{
	haccess_t *acc = NULL;
	int changed = check_files();
	char buf[INET6_ADDRSTRLEN];

	present_address(sap, buf, sizeof(buf));

	acc = haccess_lookup(sap, buf, program);
	if (acc != NULL && changed == 0) {
		xlog(D_GENERAL, "%s: access by %s %s (cached)", __func__,
			buf, acc->allowed ? "ALLOWED" : "DENIED");
		return acc->allowed;
	}

	if (!(from_local(sap) || good_client(name, sap))) {
		logit(buf);
		if (acc != NULL)
			acc->allowed = FALSE;
		else
			haccess_add(sap, buf, program, FALSE);
		xlog(D_GENERAL, "%s: access by %s DENIED", __func__, buf);
		return (FALSE);
	}

	if (acc != NULL)
		acc->allowed = TRUE;
	else
		haccess_add(sap, buf, program, TRUE);
	xlog(D_GENERAL, "%s: access by %s ALLOWED", __func__, buf);

	return (TRUE);
}

#endif	/* HAVE_LIBWRAP */
