blob: 84f2c12bc3d057081eb2bee6f5be1f00e74e7f5e [file] [log] [blame]
/*
* 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. Also, require
* that requests to set/unset the NFSD port come form a privileged port.
*
* 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. Access control is based on IP addresses
* only; attempts to map an address to a host name might cause the
* portmapper to hang.
*
* Author: Wietse Venema (wietse@wzv.win.tue.nl), dept. of Mathematics and
* Computing Science, Eindhoven University of Technology, The Netherlands.
*/
#include <sys/types.h>
#include <unistd.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/signal.h>
#ifdef SYSV40
#include <netinet/in.h>
#include <rpc/rpcent.h>
#endif
#include <tcpd.h>
#include <arpa/inet.h>
#include <grp.h>
#include "pmap_check.h"
/* Explicit #defines in case the include files are not available. */
#define NFSPROG ((u_long) 100003)
#define MOUNTPROG ((u_long) 100005)
#define YPXPROG ((u_long) 100069)
#define YPPROG ((u_long) 100004)
#define YPPROC_DOMAIN_NONACK ((u_long) 2)
#define MOUNTPROC_MNT ((u_long) 1)
#define NFS_PORT 2049
static void logit(int severity, struct sockaddr_in *addr,
u_long procnum, u_long prognum, char *text);
static void toggle_verboselog(int sig);
int verboselog __attribute ((visibility ("hidden"))) = 0;
int allow_severity __attribute ((visibility ("hidden"))) = LOG_INFO;
int deny_severity __attribute ((visibility ("hidden"))) = LOG_WARNING;
/* A handful of macros for "readability". */
#define reserved_port(p) (IPPORT_RESERVED/2 < (p) && (p) < IPPORT_RESERVED)
#define unreserved_port(p) (IPPORT_RESERVED <= (p) && (p) != NFS_PORT)
#define legal_port(a,p) \
(reserved_port(ntohs((a)->sin_port)) || unreserved_port(p))
#define log_bad_port(addr, proc, prog) \
logit(deny_severity, addr, proc, prog, ": request from unprivileged port")
#define log_bad_host(addr, proc, prog) \
logit(deny_severity, addr, proc, prog, ": request from unauthorized host")
#define log_bad_owner(addr, proc, prog) \
logit(deny_severity, addr, proc, prog, ": request from non-local host")
#define log_no_forward(addr, proc, prog) \
logit(deny_severity, addr, proc, prog, ": request not forwarded")
#define log_client(addr, proc, prog) \
logit(allow_severity, addr, proc, prog, "")
/* check_startup - additional startup code */
void check_startup(void)
{
/*
* Give up root privileges so that we can never allocate a privileged
* port when forwarding an rpc request.
*/
setgid(daemon_gid);
setgroups(0, NULL);
if (setuid(daemon_uid) == -1) {
syslog(LOG_ERR, "setuid(1) failed: %m");
exit(1);
}
(void) signal(SIGINT, toggle_verboselog);
}
#ifdef HOSTS_ACCESS
static int
good_client(struct sockaddr_in *addr)
{
if (hosts_ctl("portmap", "", inet_ntoa(addr->sin_addr), ""))
return 1;
#ifdef ENABLE_DNS
{
struct hostent *hp;
char **sp;
char *tmpname;
/* Check the hostname. */
hp = gethostbyaddr ((const char *) &(addr->sin_addr),
sizeof (addr->sin_addr), AF_INET);
if (!hp)
return 0;
/* must make sure the hostent is authoritative. */
tmpname = alloca (strlen (hp->h_name) + 1);
strcpy (tmpname, hp->h_name);
hp = gethostbyname(tmpname);
if (hp) {
/* now make sure the "addr->sin_addr" is on the list */
for (sp = hp->h_addr_list ; *sp ; sp++) {
if (memcmp(*sp, &(addr->sin_addr), hp->h_length)==0)
break;
}
if (!*sp)
/* it was a FAKE. */
return 0;
} else
/* never heard of it. misconfigured DNS? */
return 0;
/* Check the official name first. */
if (hosts_ctl("portmap", "", hp->h_name, ""))
return 1;
/* Check aliases. */
for (sp = hp->h_aliases; *sp ; sp++) {
if (hosts_ctl("portmap", "", *sp, ""))
return 1;
}
}
#endif /* ENABLE_DNS */
return 0;
}
#endif /* HOSTS_ACCESS */
/* check_default - additional checks for NULL, DUMP, GETPORT and unknown */
int
check_default(struct sockaddr_in *addr, u_long proc,
u_long prog)
{
#ifdef HOSTS_ACCESS
if (!(from_local(addr) || good_client(addr))) {
log_bad_host(addr, proc, prog);
return (FALSE);
}
#endif
if (verboselog)
log_client(addr, proc, prog);
return (TRUE);
}
/* check_privileged_port - additional checks for privileged-port updates */
int
check_privileged_port(struct sockaddr_in *addr, u_long proc,
u_long prog, u_long port)
{
#ifdef CHECK_PORT
if (!legal_port(addr, port)) {
log_bad_port(addr, proc, prog);
return (FALSE);
}
#endif
return (TRUE);
}
/* check_setunset - additional checks for update requests */
#ifdef LOOPBACK_SETUNSET
int
check_setunset(SVCXPRT *xprt, SVCXPRT *ludp_xprt, SVCXPRT *ltcp_xprt,
u_long proc, u_long prog, u_long port)
{
struct sockaddr_in *addr = svc_getcaller(xprt);
if (xprt != ludp_xprt && xprt != ltcp_xprt) {
#ifdef HOSTS_ACCESS
(void) good_client(addr); /* because of side effects */
#endif
log_bad_owner(addr, proc, prog);
return (FALSE);
}
if (port && !check_privileged_port(addr, proc, prog, port))
return (FALSE);
if (verboselog)
log_client(addr, proc, prog);
return (TRUE);
}
#else
int
check_setunset(struct sockaddr_in *addr, u_long proc,
u_long prog, u_long port)
{
if (!from_local(addr)) {
#ifdef HOSTS_ACCESS
(void) good_client(addr); /* because of side effects */
#endif
log_bad_owner(addr, proc, prog);
return (FALSE);
}
if (port && !check_privileged_port(addr, proc, prog, port))
return (FALSE);
if (verboselog)
log_client(addr, proc, prog);
return (TRUE);
}
#endif
/* check_callit - additional checks for forwarded requests */
int
check_callit(struct sockaddr_in *addr, u_long proc,
u_long prog, u_long aproc)
{
#ifdef HOSTS_ACCESS
if (!(from_local(addr) || good_client(addr))) {
log_bad_host(addr, proc, prog);
return (FALSE);
}
#endif
if (prog == PMAPPROG || prog == NFSPROG || prog == YPXPROG ||
(prog == MOUNTPROG && aproc == MOUNTPROC_MNT) ||
(prog == YPPROG && aproc != YPPROC_DOMAIN_NONACK)) {
log_no_forward(addr, proc, prog);
return (FALSE);
}
if (verboselog)
log_client(addr, proc, prog);
return (TRUE);
}
/* toggle_verboselog - toggle verbose logging flag */
static void toggle_verboselog(int sig)
{
(void) signal(sig, toggle_verboselog);
verboselog = !verboselog;
}
/* logit - report events of interest via the syslog daemon */
static void logit(int severity, struct sockaddr_in *addr,
u_long procnum, u_long prognum, char *text)
{
char *procname;
char procbuf[4 * sizeof(u_long)];
char *progname;
char progbuf[4 * sizeof(u_long)];
struct rpcent *rpc;
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"} ,
{ 0, 0} ,
};
/*
* Fork off a process or the portmap daemon might hang while
* getrpcbynumber() or syslog() does its thing.
*/
if (fork() == 0) {
/* Try to map program number to name. */
if (prognum == 0) {
progname = "";
} else if ((rpc = getrpcbynumber((int) prognum))) {
progname = rpc->r_name;
} else {
sprintf(progname = progbuf, "%lu", prognum);
}
/* Try to map procedure number to name. */
for (procp = procmap; procp->proc && procp->code != procnum; procp++)
/* void */ ;
if ((procname = procp->proc) == 0)
sprintf(procname = procbuf, "%lu", (u_long) procnum);
/* Write syslog record. */
syslog(severity, "connect from %s to %s(%s)%s",
inet_ntoa(addr->sin_addr), procname, progname, text);
exit(0);
}
}