 /*
  * Auxiliary routines to shield off random internet hosts and to report
  * service requests (verbose mode only) or violations (always).
  * 
  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  */

#include <stdio.h>
#include <signal.h>
#include <syslog.h>
#include <netconfig.h>
#include <netdir.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <rpc/rpc.h>
#include <rpc/rpcb_prot.h>
#include <rpc/pmap_prot.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rpc/rpcent.h>
#include <sys/timod.h>

#include "rpcbind.h"
#include "rpcb_check.h"

#define YES	1
#define	NO	0

 /*
  * Duplicated from warmstart.c
  */

#define	RPCBFILE	"/tmp/rpcbind.file"
#define	PMAPFILE	"/tmp/portmap.file"

 /*
  * From pmap_svc.c
  */

bool_t chklocal();

 /*
  * These are globally visible so that they can be modified by the wrapper's
  * language extension routines.
  */

int     allow_severity = SEVERITY;	/* run-time adjustable */
int     deny_severity = LOG_WARNING;	/* ditto */

static int verboselog = 0;

/* rpcb_sighup - sighup handler */

static void rpcb_sighup(sig)
int     sig;
{
    signal(sig, rpcb_sighup);
    verboselog = !verboselog;
    syslog(allow_severity, "verbose logging %s", verboselog ? "on" : "off");
}

/* rpcb_check_init - additional initializations */

void    rpcb_check_init()
{
    struct stat st;

    /*
     * Avoid running as root. This used to work OK with the old portmapper,
     * and I haven't found a problem yet when doing the same with rpcbind.
     */
#define MY_UID 0
    setuid(MY_UID);
    signal(SIGHUP, rpcb_sighup);

    /*
     * Ok, there was one small problem: the old rpcbind runs as root and
     * produces root-owned files when saving state; the new rpcbind runs at
     * lower privilege levels and cannot overwrite those files. This problem
     * occurs only after the initial installation of the new rpcbind daemon.
     */
    if (stat(RPCBFILE, &st) == 0 && st.st_uid != MY_UID)
	(void) unlink(RPCBFILE);
    if (stat(PMAPFILE, &st) == 0 && st.st_uid != MY_UID)
	(void) unlink(PMAPFILE);
}

/* valid_inet_transport - check that request is sent via valid transport */

static int valid_inet_transport(transp, procnum, prognum)
SVCXPRT *transp;
u_long  procnum;
u_long  prognum;
{
    struct sockaddr_in *addr;
    struct sockaddr_in local;
    struct netbuf local_buf;

    /*
     * Disallow requests via illegal inet transports. Unfortunately, we
     * cannot count on transp->xp_ltaddr to contain any useful information,
     * so we must do our own TLI lookups. Right now we look only at the
     * server port number. We will also have to look at the server address
     * when we implement the anti-spoofing checks as in portmapper version 5.
     */
    local_buf.buf = (char *) &local;
    local_buf.maxlen = sizeof(local);
    if (ioctl(transp->xp_fd, TI_GETMYNAME, &local_buf) < 0
	|| local.sin_port != htons(PMAPPORT)) {
	addr = svc_getcaller(transp);
	syslog(LOG_ERR,
	       "connect from %s to proc %ld prog %ld with invalid transport",
	       inet_ntoa(addr->sin_addr), procnum, prognum);
	return (NO);
    } else {
	return (YES);
    }
}

/* rpcb_check - filter out requests from random internet hosts */

int     rpcb_check(transp, procnum, prognum)
SVCXPRT *transp;
u_long  procnum;
u_long  prognum;
{
    struct netconfig *conf;

    if ((conf = rpcbind_get_conf(transp->xp_netid)) == 0) {
	syslog(LOG_ERR, "rpcbind_get_conf failed: no client address checks");
	return (YES);
    } else if (strcasecmp(conf->nc_protofmly, "inet") == 0) {
	return (pmap_check(transp, procnum, prognum));
    } else {
	return (YES);
    }
}

/* pmap_check - filter out requests from random internet hosts */

int     pmap_check(transp, procnum, prognum)
SVCXPRT *transp;
u_long  procnum;
u_long  prognum;
{
    struct sockaddr_in *addr = svc_getcaller(transp);
    char   *addr_string = inet_ntoa(addr->sin_addr);

    /*
     * Do not specify an empty or unknown host name, or the UNKNOWN pattern
     * may match unexpectedly.
     */

    if (chklocal(addr->sin_addr) == 0
	&& (hosts_ctl("rpcbind", addr_string, addr_string, "") == 0
	    || valid_inet_transport(transp, procnum, prognum) == 0)) {
	pmap_log(NO, addr, procnum, prognum);
	return (NO);
    } else {
	return (YES);
    }
}

/* find_progname - map program number to name */

static char *find_progname(prognum)
u_long  prognum;
{
    char   *progname;
    char    progbuf[4 * sizeof(u_long)];
    struct rpcent *rpc;

    if (prognum == 0) {
	progname = "";
    } else if (rpc = getrpcbynumber((int) prognum)) {
	progname = rpc->r_name;
    } else {
	sprintf(progname = progbuf, "%lu", prognum);
    }
    return (progname);

}

/* find_procname - map procedure number to name */

static char *find_procname(procnum)
u_long  procnum;
{
    char   *procname;
    static char procbuf[4 * sizeof(u_long)];
    struct proc_map {
	u_long  code;
	char   *proc;
    };
    struct proc_map *procp;
    static struct proc_map procmap[] = {
	PMAPPROC_CALLIT, "callit",
	PMAPPROC_DUMP, "dump",
	PMAPPROC_GETPORT, "getport",
	PMAPPROC_NULL, "null",
	PMAPPROC_SET, "set",
	PMAPPROC_UNSET, "unset",
	NULLPROC, "null",
	RPCBPROC_SET, "set",
	RPCBPROC_UNSET, "unset",
	RPCBPROC_GETADDR, "getaddr",
	RPCBPROC_DUMP, "dump",
	RPCBPROC_CALLIT, "callit",
	RPCBPROC_GETTIME, "gettime",
	RPCBPROC_UADDR2TADDR, "uaddr2taddr",
	RPCBPROC_TADDR2UADDR, "taddr2uaddr",
	0, 0,
    };

    for (procp = procmap; procp->proc && procp->code != procnum; procp++)
	 /* void */ ;
    if ((procname = procp->proc) == 0)
	sprintf(procname = procbuf, "%lu", (u_long) procnum);
    return (procname);
}

/* log_it - log request for service */

static void logit(verdict, client, procnum, prognum)
int     verdict;
char   *client;
u_long  procnum;
u_long  prognum;
{

    if (fork() == 0) {
	syslog(verdict ? allow_severity : deny_severity,
	       "%sconnect from %s to %s(%s)",
	       verdict ? "" : "refused ",
	       client,
	       find_procname(procnum),
	       find_progname(prognum));
	exit(0);
    }
}

/* showtrans - display transport endpoints */

char   *showtrans(transp)
SVCXPRT *transp;
{
    struct netconfig *conf;
    static char buf[BUFSIZ];
    char   *remote;
    char   *local;
    char   *netid = transp->xp_netid ? transp->xp_netid : "udp";

    if ((conf = rpcbind_get_conf(netid)) == 0) {
	fprintf(stderr, "unknown transport (rpcbind_get_conf failed)");
	return ("remote ?? dst ??");
    } else if ((local = taddr2uaddr(conf, &(transp->xp_ltaddr))) == 0
	  ||      (remote = taddr2uaddr(conf, &(transp->xp_rtaddr))) == 0) {
	fprintf(stderr, "unknown address (taddr2uaddr failed)");
	return ("remote ?? dst ??");
    } else {
	sprintf(buf, "local(%s) remote(%s)", local, remote);
	free(local);
	free(remote);
	return (buf);
    }
}

/* rpcb_log - log request for service */

void    rpcb_log(verdict, transp, procnum, prognum)
int     verdict;
SVCXPRT *transp;
u_long  procnum;
u_long  prognum;
{
    struct netconfig *conf;
    char   *client = "unknown";
    char   *uaddr;
    struct sockaddr_in *sin;
    char    buf[BUFSIZ];

    /*
     * Fork off a child to prevent the daemon from hanging when it does rpc
     * lookups. Transform the transport address into something printable.
     */
    if (verboselog != 0 || verdict == 0) {
	if (fork() == 0) {
	    if ((conf = rpcbind_get_conf(transp->xp_netid)) == 0) {
		syslog(LOG_WARNING, "unknown transport (rpcbind_get_conf failed)");
	    } else if (strcasecmp(conf->nc_protofmly, "inet") == 0) {
		sin = (struct sockaddr_in *) (transp->xp_rtaddr.buf);
		client = inet_ntoa(sin->sin_addr);
	    } else {
		if ((uaddr = taddr2uaddr(conf, &(transp->xp_rtaddr))) == 0) {
		    syslog(LOG_WARNING, "unknown address (taddr2uaddr failed)");
		} else {
		    sprintf(buf, "%s(%s)", conf->nc_protofmly, uaddr);
		    free(uaddr);
		    client = buf;
		}
	    }
	    logit(verdict, client, procnum, prognum);
	    exit(0);
	} else {
	    wait((int *) 0);
	}
    }
}

/* pmap_log - log request for service */

void    pmap_log(verdict, addr, procnum, prognum)
int     verdict;
struct sockaddr_in *addr;
u_long  procnum;
u_long  prognum;
{

    /*
     * Fork off a child to prevent the daemon from hanging when it does rpc
     * lookups.
     */
    if (verboselog != 0 || verdict == 0) {
	if (fork() == 0) {
	    logit(verdict, inet_ntoa(addr->sin_addr), procnum, prognum);
	    exit(0);
	} else {
	    wait((int *) 0);
	}
    }
}
