TUCoPS :: Unix :: General :: portmap.txt

Unix Portmap Replacement

There is an increasing interest in access control for the NIS, mount
and other RPC-based services that are normally registered with the
portmap process. These days, popular attacks on RPC daemons involve:

    - theft or NIS (YP) password files

    - ypset to force hosts to bind to a rogue NIS (YP) server

    - theft of NFS file handles

My contribution is a replacement portmap program, derived from source
code in the RPCSRC 4.0 and the TIRPC source distributions.  Access
control is in the style of my tcp wrapper (log_tcp) package. It should
work with all SunOS 4.x and Ultrix >= 3.0 releases. However, the source
is reasonably portable and the code should work on most UNIX systems
that provide SUNRPC on top of BSD-style TCP/IP. System V.4 support is
problematic, though.

The present portmap version attempts to close all portmap security
problems that are known to me. It should be as secure as the portmap
daemon that comes with the SunOS 4.x portmap+NIS patch (patch id
100482-02). The README file gives a complete list of security
features.

Without the availability of portmap source, possible alternatives are
1) packet filtering with a smart router; 2) linking the portmap
executable against the securelib shared library. Linking other RPC
daemons against the securelib library is a good idea, anyway.

The source is available for anonymous FTP from ftp.win.tue.nl directory
/pub/security/portmap.shar.Z. It requires the access control library
that it built with /pub/security/log_tcp.shar.Z.

	Wietse Venema (wietse@wzv.win.tue.nl)
	Mathematics and Computing Science
	Eindhoven University of Technology
	The Netherlands

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README Makefile portmap.c daemon.c strerror.c
#   diffs_wrt_bsd pmap_dump.c pmap_set.c from_local.c pmap_check.c
#   pmap_check.h BLURB
# Wrapped by wietse@wzv on Mon Jul  6 19:57:40 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(6515 characters\)
sed "s/^X//" >README <<'END_OF_README'
X@(#) README 1.2 92/07/06 19:53:25
X
XDescription
X-----------
X
XThis is a portmapper replacement with access control in the style of
Xthe tcp wrapper (log_tcp) package.  It provides a simple mechanism to
Xdiscourage access to the NIS (YP), NFS, and other services registered
Xwith the portmapper. 
X
XIn some cases, better or equivalent alternatives are available: 
X
X    The SunOS portmap that is provided with patch id 100482-02 should
X    close the same security holes.  In addition, it provides NIS
X    daemons with their own access control lists. This is better than
X    just portmapper access control.
X
X    The "securelib" shared library (eecs.nwu.edu:/pub/securelib.tar)
X    implements access control for all kinds of (RPC) services, not
X    just the portmapper.
X
X    Reportedly, Irix 4.0.x already has a secured portmapper.
X
XHowever, many vendors still ship portmap implementations that allow
Xanyone to read or modify its tables and that will happily forward any
Xrequest so that it appears to come from the local system.
X
XFeatures
X--------
X
X- optional: host access control. The local host is always considered
Xauthorized. This feature requires the libwrap.a library that comes with
Xrecent tcp wrapper (log_tcp) implementations.
X
X- requests to change the portmap tables are accepted only when they
Xcome from the local system.
X
X- optional: requests to (un)register services on privileged ports (port
X< 1024) are accepted only when they come from a privileged port. This
Xfeature is optional because some RPC implementations still run all RPC
Xdaemons of an unprivileged port.  Bug your vendor if your RPC is like
Xthat.
X
X- requests that are forwarded by the portmapper will be forwarded
Xthrough an unprivileged port.
X
X- the portmapper refuses to forward requests to rpc daemons that do (or
Xshould) verify the origin of the request: at present, the list includes
Xmost of the calls to the NFS mountd/nfsd daemons and the NIS daemons.
X
XRestrictions
X------------
X
XLimiting access to the portmapper does not protect you from direct
Xattacks on the rpc daemons; the main task of portmap is to maintain a
Xtable of available RPC services and the network ports that they are
Xlistening on. The securelib can be used to protect individual RPC
Xdaemons, and the latest SunOS portmap+NIS fix already protects the NIS
Xdaemons and implements limited forwarding.
X
XOn the other hand, even though a portmapper with access control only
Xmakes an attack more difficult, it still provides an excellent early
Xwarning system.
X
XOrigin and portability
X----------------------
X
XThe sources in this distribution are derived from code on the second
XBSD networking tape, which was derived from Sun's RPCSRC 4.0 code, and
Xfrom Sun's TIRPC (transport-independent rpc) distribution. 
X
XThe code compiles fine with SunOS 4.1.1, Ultrix 4.x and ESIX System V
Xrelease 4.0, but I expect it will work with many other UNIX flavours.
XTested with SunOS 4.1.1; an earlier version was also tested with Ultrix
X3.0.  If anyone can get this thing working with SYSV4 let me know; it
Xdidn't with ESIX.
X
XInstallation
X------------
X
X(1) Follow the instructions in the Makefile, then build the portmap and
Xauxiliary executables.
X
X(2) Before killing the present portmap process, save the present
Xportmapper tables using the command:
X
X	./pmap_dump >table
X
XIf you kill the portmap process without saving its tables you will have
Xto reboot the machine.
X
XNote: the information in the portmap tables is dynamic: For example, it
Xwill be different after each reboot. On a Sun, it even changes each
Xtime a windowing system is started that uses the selection service.
X
X(3) Kill the running portmap process and start the new portmap
Xprogram.  Then (still as root) initialize the portmap tables with:
X
X	./pmap_set <table
X
X(4) If you get error messages of the form: "not registered: xxxx",
Xdisable the CHECK_PORT feature in the Makefile, remove pmap_check.o and
Xrebuild the portmap program.  Then proceed with step 3.
X
XIn order to revert to the original portmap daemon, kill off the running
Xone, restart the original one and reload its tables using the
X"pmap_set" command as shown above.
X
XSuggested entries for the host access-control files are:
X
X    /etc/hosts.allow:
X	portmap: your.sub.net.number/your.sub.net.mask
X	portmap: 255.255.255.255 0.0.0.0
X
X    /etc/hosts.deny
X	portmap: ALL: (finger -l @%h | mail root) &
X
XThe syntax of the access-control files is described in the
Xhosts_access.5 manual page that comes with the tcp wrapper (log_tcp)
Xsources. The second line in the hosts.allow file may be needed if
Xthere are PC-NFS systems on your network segment.
X
XFor security reasons, the portmap process does not run as root. The
Xaccess control files should therefore be group readable.
X
XIn order to avoid deadlocks, the portmap program does not attempt to
Xlook up the remote host name, nor will it try to match NIS netgroups.
XIf you do not want to accept requests from everyone on your subnet you
Xwill have to enumerate the addresses of authorised hosts.  There is no
Xneed to specify the local system: since it runs the portmap daemon, it
Xis authorized by definition.
X
XTesting:
X--------
X
XNormally, only rejected requests will be reported via the syslog
Xdaemon.  Logging is done in a child process, in order to avoid
Xpossible deadlock in case the logging code needs assistance from
Xthe portmapper.
X
XBy default, the portmapper will be utterly silent. In fact, the portmap
Xdaemon is not consulted that often. Sending a SIGINT signal to the
Xportmap process will enable the logging of all requests. 
X
XWarning:  with some systems, such as HP-UX, the logging code needs
Xassistance from the portmapper. If verbose logging is on, these calls
Xfor assistance will also be logged, so that you end up with a system
Xfull of portmap processes.
X
XWith verbose logging turned on, requests such as "ypcat" or "rpcinfo
X-p" should show up with log file entries such as:
X
X MMM dd hh:mm:ss hostname portmap[pid]: connect from x.x.x.x to getport(ypserv)
X MMM dd hh:mm:ss hostname portmap[pid]: connect from y.y.y.y to dump()	
X
XSend another SIGINT to the portmapper to turn the verbose logging off.
X
XAcknowledgements
X----------------
X
XCasper H.S. Dik (casper@fwi.uva.nl) provided lots of valuable
Xinformation on RPC security and tested an intermediate version of the
Xportmapper with SunOS 4.1.2.  Lyford D. Rich (rich@ece.nps.navy.mil)
Xwas helpful with porting the daemon to Ultrix 3.0.
X
X	Wietse Venema (wietse@wzv.win.tue.nl)
X	Mathematics and Computing Science
X	Eindhoven University of Technology
X	The Netherlands
END_OF_README
if test 6515 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f Makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
echo shar: Extracting \"Makefile\" \(2955 characters\)
sed "s/^X//" >Makefile <<'END_OF_Makefile'
X# @(#) Makefile 1.2 92/07/06 19:53:28
X
X####################################
X### Beginning of configurable stuff.
X
X# By default, logfile entries are written to the same file as used for
X# sendmail transaction logs. Change the definition of the following macro
X# if you disagree. See `man 3 syslog' for examples. Some syslog versions
X# do not provide this flexibility.
X#
XFACILITY=LOG_MAIL
X
X# To disable host access control, comment out the following macro definition.
X# Note: host access control requires the strtok() and strchr() routines.
X# Host access control can also be turned off by providing no access control
X# tables. The local system, since it runs the portmap daemon, is always
X# treated as an authorized host.
X
XHOSTS_ACCESS= -DHOSTS_ACCESS
X
X# Comment out if your RPC library does not allocate privileged ports for
X# requests from processes with root privilege, or the new portmap will
X# always reject requests to register/unregister services on privileged
X# ports. You can find out by running "rpcinfo -p"; if the mountd and NIS
X# daemons use a port >= 1024 you should probably disable the next line.
X
XCHECK_PORT = -DCHECK_PORT
X
X# Uncomment the following macro if your system does not have u_long.
X#
X# ULONG	=-Du_long="unsigned long"
X
X# Later versions of the tcp wrapper (log_tcp package) come with a
X# libwrap.a object library. WRAP_DIR should specify the directory with
X# that library.
X
XWRAP_DIR= ../log_tcp
X
X# Auxiliary object files that may be missing from your C library.
X#
XAUX	= daemon.o strerror.o
X
X# Uncomment the following macro definitions if you are running SYSV.4.
X#
X# CC	= /usr/ucb/cc
X# LIBS	= -lrpcsoc
X# SYS	= -DSYSV40
X
X# Auxiliary libraries that you may have to specify
X#
X# LIBS	= -lrpc
X
X### End of configurable stuff.
X##############################
X
XSHELL	= /bin/sh
X
XCOPT	= -Dconst= -Dperror=xperror $(HOSTS_ACCESS) $(CHECK_PORT) \
X	$(SYS) -DFACILITY=$(FACILITY) $(ULONG)
XCFLAGS	= $(COPT) -O
XOBJECTS	= portmap.o pmap_check.o from_local.o $(AUX)
X
Xall:	portmap pmap_dump pmap_set
X
Xportmap: $(OBJECTS) $(WRAP_DIR)/libwrap.a
X	$(CC) $(CFLAGS) -o $@ $(OBJECTS) $(WRAP_DIR)/libwrap.a $(LIBS)
X
Xpmap_dump: pmap_dump.c
X	$(CC) $(CFLAGS) -o $@ $? $(LIBS)
X
Xpmap_set: pmap_set.c
X	$(CC) $(CFLAGS) -o $@ $? $(LIBS)
X
Xfrom_local: from_local.c
X	cc $(CFLAGS) -DTEST -o $@ from_local.c
X
Xlint:	
X	lint $(COPT) $(OBJECTS:%.o=%.c)
X
Xclean:
X	rm -f *.o portmap pmap_dump pmap_set from_local core
X
Xshar:
X	@shar README Makefile portmap.c daemon.c strerror.c diffs_wrt_bsd \
X	pmap_dump.c pmap_set.c from_local.c pmap_check.c pmap_check.h BLURB
X
Xdiffs_wrt_bsd: portmap.c.bsd portmap.c daemon.c.bsd daemon.c
X	-(diff -c portmap.c.bsd portmap.c; diff -c daemon.c.bsd daemon.c) >$@
X
Xdeps:
X	@$(CC) -M $(CFLAGS) *.c | grep -v /usr/include |sed 's/\.\///'
X
Xdaemon.o: daemon.c
Xfrom_local.o: from_local.c
Xpmap_check.o: pmap_check.c pmap_check.h Makefile
Xpmap_dump.o: pmap_dump.c
Xpmap_set.o: pmap_set.c
Xportmap.o: portmap.c
Xportmap.o: pmap_check.h
Xstrerror.o: strerror.c
END_OF_Makefile
if test 2955 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f portmap.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"portmap.c\"
else
echo shar: Extracting \"portmap.c\" \(15524 characters\)
sed "s/^X//" >portmap.c <<'END_OF_portmap.c'
X/*-
X * Copyright (c) 1990 The Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms, with or without
X * modification, are permitted provided that the following conditions
X * are met:
X * 1. Redistributions of source code must retain the above copyright
X *    notice, this list of conditions and the following disclaimer.
X * 2. Redistributions in binary form must reproduce the above copyright
X *    notice, this list of conditions and the following disclaimer in the
X *    documentation and/or other materials provided with the distribution.
X * 3. All advertising materials mentioning features or use of this software
X *    must display the following acknowledgement:
X *	This product includes software developed by the University of
X *	California, Berkeley and its contributors.
X * 4. Neither the name of the University nor the names of its contributors
X *    may be used to endorse or promote products derived from this software
X *    without specific prior written permission.
X *
X * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
X * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
X * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
X * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
X * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
X * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
X * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
X * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
X * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
X * SUCH DAMAGE.
X */
X
X#ifndef lint
Xchar copyright[] =
X"@(#) Copyright (c) 1990 The Regents of the University of California.\n\
X All rights reserved.\n";
X#endif /* not lint */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)portmap.c	5.4 (Berkeley) 4/19/91";
X#endif /* not lint */
X
X/*
X@(#)portmap.c	2.3 88/08/11 4.0 RPCSRC
Xstatic char sccsid[] = "@(#)portmap.c 1.32 87/08/06 Copyr 1984 Sun Micro";
X*/
X
X/*
X * portmap.c, Implements the program,version to port number mapping for
X * rpc.
X */
X
X/*
X * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
X * unrestricted use provided that this legend is included on all tape
X * media and as a part of the software program in whole or part.  Users
X * may copy or modify Sun RPC without charge, but are not authorized
X * to license or distribute it to anyone else except as part of a product or
X * program developed by the user.
X * 
X * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
X * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
X * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
X * 
X * Sun RPC is provided with no support and without any obligation on the
X * part of Sun Microsystems, Inc. to assist in its use, correction,
X * modification or enhancement.
X * 
X * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
X * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
X * OR ANY PART THEREOF.
X * 
X * In no event will Sun Microsystems, Inc. be liable for any lost revenue
X * or profits or other special, indirect and consequential damages, even if
X * Sun has been advised of the possibility of such damages.
X * 
X * Sun Microsystems, Inc.
X * 2550 Garcia Avenue
X * Mountain View, California  94043
X */
X
X#include <rpc/rpc.h>
X#include <rpc/pmap_prot.h>
X#include <stdio.h>
X#include <syslog.h>
X#include <netdb.h>
X#include <sys/socket.h>
X#include <sys/ioctl.h>
X#include <sys/wait.h>
X#include <sys/signal.h>
X#include <sys/time.h>
X#include <sys/resource.h>
X#ifdef SYSV40
X#include <netinet/in.h>
X#endif
X
Xextern char *strerror();
Xextern char *malloc();
X
X#ifndef LOG_PERROR
X#define LOG_PERROR 0
X#endif
X
X#ifndef LOG_DAEMON
X#define LOG_DAEMON 0
X#endif
X
X#ifndef svc_getcaller		/* SYSV4 */
X#  define svc_getcaller svc_getrpccaller
X#endif
X
Xvoid reg_service();
Xvoid reap();
Xstatic void callit();
Xstruct pmaplist *pmaplist;
Xint debugging = 0;
Xextern int errno;
X
X#include "pmap_check.h"
X
Xmain(argc, argv)
X	int argc;
X	char **argv;
X{
X	SVCXPRT *xprt;
X	int sock, c;
X	struct sockaddr_in addr;
X	int len = sizeof(struct sockaddr_in);
X	register struct pmaplist *pml;
X
X	while ((c = getopt(argc, argv, "d")) != EOF) {
X		switch (c) {
X
X		case 'd':
X			debugging = 1;
X			break;
X
X		default:
X			(void) fprintf(stderr, "usage: %s [-d]\n", argv[0]);
X			exit(1);
X		}
X	}
X
X	if (!debugging && daemon(0, 0)) {
X		(void) fprintf(stderr, "portmap: fork: %s", strerror(errno));
X		exit(1);
X	}
X
X#ifdef LOG_MAIL
X	openlog("portmap", debugging ? LOG_PID | LOG_PERROR : LOG_PID,
X	    FACILITY);
X#else
X	openlog("portmap", debugging ? LOG_PID | LOG_PERROR : LOG_PID);
X#endif
X
X	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
X		syslog(LOG_ERR, "cannot create udp socket: %m");
X		exit(1);
X	}
X
X	addr.sin_addr.s_addr = 0;
X	addr.sin_family = AF_INET;
X	addr.sin_port = htons(PMAPPORT);
X	if (bind(sock, (struct sockaddr *)&addr, len) != 0) {
X		syslog(LOG_ERR, "cannot bind udp: %m");
X		exit(1);
X	}
X
X	if ((xprt = svcudp_create(sock)) == (SVCXPRT *)NULL) {
X		syslog(LOG_ERR, "couldn't do udp_create");
X		exit(1);
X	}
X	/* make an entry for ourself */
X	pml = (struct pmaplist *)malloc((u_int)sizeof(struct pmaplist));
X	pml->pml_next = 0;
X	pml->pml_map.pm_prog = PMAPPROG;
X	pml->pml_map.pm_vers = PMAPVERS;
X	pml->pml_map.pm_prot = IPPROTO_UDP;
X	pml->pml_map.pm_port = PMAPPORT;
X	pmaplist = pml;
X
X	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
X		syslog(LOG_ERR, "cannot create tcp socket: %m");
X		exit(1);
X	}
X	if (bind(sock, (struct sockaddr *)&addr, len) != 0) {
X		syslog(LOG_ERR, "cannot bind udp: %m");
X		exit(1);
X	}
X	if ((xprt = svctcp_create(sock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE))
X	    == (SVCXPRT *)NULL) {
X		syslog(LOG_ERR, "couldn't do tcp_create");
X		exit(1);
X	}
X	/* make an entry for ourself */
X	pml = (struct pmaplist *)malloc((u_int)sizeof(struct pmaplist));
X	pml->pml_map.pm_prog = PMAPPROG;
X	pml->pml_map.pm_vers = PMAPVERS;
X	pml->pml_map.pm_prot = IPPROTO_TCP;
X	pml->pml_map.pm_port = PMAPPORT;
X	pml->pml_next = pmaplist;
X	pmaplist = pml;
X
X	(void)svc_register(xprt, PMAPPROG, PMAPVERS, reg_service, FALSE);
X
X	/* additional initializations */
X	check_startup();
X	(void)signal(SIGCHLD, reap);
X	svc_run();
X	syslog(LOG_ERR, "run_svc returned unexpectedly");
X	abort();
X}
X
X#ifndef lint
X/* need to override perror calls in rpc library */
Xvoid
Xperror(what)
X	const char *what;
X{
X
X	syslog(LOG_ERR, "%s: %m", what);
X}
X#endif
X
Xstatic struct pmaplist *
Xfind_service(prog, vers, prot)
X	u_long prog, vers, prot;
X{
X	register struct pmaplist *hit = NULL;
X	register struct pmaplist *pml;
X
X	for (pml = pmaplist; pml != NULL; pml = pml->pml_next) {
X		if ((pml->pml_map.pm_prog != prog) ||
X			(pml->pml_map.pm_prot != prot))
X			continue;
X		hit = pml;
X		if (pml->pml_map.pm_vers == vers)
X		    break;
X	}
X	return (hit);
X}
X
X/* 
X * 1 OK, 0 not
X */
Xvoid
Xreg_service(rqstp, xprt)
X	struct svc_req *rqstp;
X	SVCXPRT *xprt;
X{
X	struct pmap reg;
X	struct pmaplist *pml, *prevpml, *fnd;
X	int ans, port;
X	caddr_t t;
X	
X	if (debugging)
X		(void) fprintf(stderr, "server: about do a switch\n");
X	switch (rqstp->rq_proc) {
X
X	case PMAPPROC_NULL:
X		/*
X		 * Null proc call
X		 */
X		/* remote host authorization check */
X		check_default(svc_getcaller(xprt), rqstp->rq_proc, (u_long) 0);
X		if (!svc_sendreply(xprt, xdr_void, (caddr_t)0) && debugging) {
X			abort();
X		}
X		break;
X
X	case PMAPPROC_SET:
X		/*
X		 * Set a program,version to port mapping
X		 */
X		if (!svc_getargs(xprt, xdr_pmap, &reg))
X			svcerr_decode(xprt);
X		else {
X			/* reject non-local requests, protect priv. ports */
X			if (!check_setunset(svc_getcaller(xprt), 
X			    rqstp->rq_proc, reg.pm_prog, reg.pm_port)) {
X				ans = 0;
X				goto done;
X			} 
X			/*
X			 * check to see if already used
X			 * find_service returns a hit even if
X			 * the versions don't match, so check for it
X			 */
X			fnd = find_service(reg.pm_prog, reg.pm_vers, reg.pm_prot);
X			if (fnd && fnd->pml_map.pm_vers == reg.pm_vers) {
X				if (fnd->pml_map.pm_port == reg.pm_port) {
X					ans = 1;
X					goto done;
X				}
X				else {
X					ans = 0;
X					goto done;
X				}
X			} else {
X				/* 
X				 * add to END of list
X				 */
X				pml = (struct pmaplist *)
X				    malloc((u_int)sizeof(struct pmaplist));
X				pml->pml_map = reg;
X				pml->pml_next = 0;
X				if (pmaplist == 0) {
X					pmaplist = pml;
X				} else {
X					for (fnd= pmaplist; fnd->pml_next != 0;
X					    fnd = fnd->pml_next);
X					fnd->pml_next = pml;
X				}
X				ans = 1;
X			}
X		done:
X			if ((!svc_sendreply(xprt, xdr_long, (caddr_t)&ans)) &&
X			    debugging) {
X				(void) fprintf(stderr, "svc_sendreply\n");
X				abort();
X			}
X		}
X		break;
X
X	case PMAPPROC_UNSET:
X		/*
X		 * Remove a program,version to port mapping.
X		 */
X		if (!svc_getargs(xprt, xdr_pmap, &reg))
X			svcerr_decode(xprt);
X		else {
X			ans = 0;
X			/* reject non-local requests */
X			if (!check_setunset(svc_getcaller(xprt), 
X			    rqstp->rq_proc, reg.pm_prog, (u_long) 0))
X				goto done;
X			for (prevpml = NULL, pml = pmaplist; pml != NULL; ) {
X				if ((pml->pml_map.pm_prog != reg.pm_prog) ||
X					(pml->pml_map.pm_vers != reg.pm_vers)) {
X					/* both pml & prevpml move forwards */
X					prevpml = pml;
X					pml = pml->pml_next;
X					continue;
X				}
X				/* found it; pml moves forward, prevpml stays */
X				/* privileged port check */
X				if (!check_privileged_port(svc_getcaller(xprt), 
X				    rqstp->rq_proc, 
X				    reg.pm_prog, 
X				    pml->pml_map.pm_port)) {
X					ans = 0;
X					break;
X				}
X				ans = 1;
X				t = (caddr_t)pml;
X				pml = pml->pml_next;
X				if (prevpml == NULL)
X					pmaplist = pml;
X				else
X					prevpml->pml_next = pml;
X				free(t);
X			}
X			if ((!svc_sendreply(xprt, xdr_long, (caddr_t)&ans)) &&
X			    debugging) {
X				(void) fprintf(stderr, "svc_sendreply\n");
X				abort();
X			}
X		}
X		break;
X
X	case PMAPPROC_GETPORT:
X		/*
X		 * Lookup the mapping for a program,version and return its port
X		 */
X		if (!svc_getargs(xprt, xdr_pmap, &reg))
X			svcerr_decode(xprt);
X		else {
X			/* remote host authorization check */
X			if (!check_default(svc_getcaller(xprt), 
X			    rqstp->rq_proc, 
X			    reg.pm_prog)) {
X				ans = 0;
X				goto done;
X			}
X			fnd = find_service(reg.pm_prog, reg.pm_vers, reg.pm_prot);
X			if (fnd)
X				port = fnd->pml_map.pm_port;
X			else
X				port = 0;
X			if ((!svc_sendreply(xprt, xdr_long, (caddr_t)&port)) &&
X			    debugging) {
X				(void) fprintf(stderr, "svc_sendreply\n");
X				abort();
X			}
X		}
X		break;
X
X	case PMAPPROC_DUMP:
X		/*
X		 * Return the current set of mapped program,version
X		 */
X		if (!svc_getargs(xprt, xdr_void, NULL))
X			svcerr_decode(xprt);
X		else {
X			/* remote host authorization check */
X			struct pmaplist *p;
X			if (!check_default(svc_getcaller(xprt), 
X			    rqstp->rq_proc, (u_long) 0)) {
X				p = 0;	/* send empty list */
X			} else {
X				p = pmaplist;
X			}
X			if ((!svc_sendreply(xprt, xdr_pmaplist,
X			    (caddr_t)&p)) && debugging) {
X				(void) fprintf(stderr, "svc_sendreply\n");
X				abort();
X			}
X		}
X		break;
X
X	case PMAPPROC_CALLIT:
X		/*
X		 * Calls a procedure on the local machine.  If the requested
X		 * procedure is not registered this procedure does not return
X		 * error information!!
X		 * This procedure is only supported on rpc/udp and calls via 
X		 * rpc/udp.  It passes null authentication parameters.
X		 */
X		callit(rqstp, xprt);
X		break;
X
X	default:
X		/* remote host authorization check */
X		check_default(svc_getcaller(xprt), rqstp->rq_proc, (u_long) 0);
X		svcerr_noproc(xprt);
X		break;
X	}
X}
X
X
X/*
X * Stuff for the rmtcall service
X */
X#define ARGSIZE 9000
X
Xstruct encap_parms {
X	u_long arglen;
X	char *args;
X};
X
Xstatic bool_t
Xxdr_encap_parms(xdrs, epp)
X	XDR *xdrs;
X	struct encap_parms *epp;
X{
X
X	return (xdr_bytes(xdrs, &(epp->args), &(epp->arglen), ARGSIZE));
X}
X
Xstruct rmtcallargs {
X	u_long	rmt_prog;
X	u_long	rmt_vers;
X	u_long	rmt_port;
X	u_long	rmt_proc;
X	struct encap_parms rmt_args;
X};
X
Xstatic bool_t
Xxdr_rmtcall_args(xdrs, cap)
X	register XDR *xdrs;
X	register struct rmtcallargs *cap;
X{
X
X	/* does not get a port number */
X	if (xdr_u_long(xdrs, &(cap->rmt_prog)) &&
X	    xdr_u_long(xdrs, &(cap->rmt_vers)) &&
X	    xdr_u_long(xdrs, &(cap->rmt_proc))) {
X		return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
X	}
X	return (FALSE);
X}
X
Xstatic bool_t
Xxdr_rmtcall_result(xdrs, cap)
X	register XDR *xdrs;
X	register struct rmtcallargs *cap;
X{
X	if (xdr_u_long(xdrs, &(cap->rmt_port)))
X		return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
X	return (FALSE);
X}
X
X/*
X * only worries about the struct encap_parms part of struct rmtcallargs.
X * The arglen must already be set!!
X */
Xstatic bool_t
Xxdr_opaque_parms(xdrs, cap)
X	XDR *xdrs;
X	struct rmtcallargs *cap;
X{
X
X	return (xdr_opaque(xdrs, cap->rmt_args.args, cap->rmt_args.arglen));
X}
X
X/*
X * This routine finds and sets the length of incoming opaque paraters
X * and then calls xdr_opaque_parms.
X */
Xstatic bool_t
Xxdr_len_opaque_parms(xdrs, cap)
X	register XDR *xdrs;
X	struct rmtcallargs *cap;
X{
X	register u_int beginpos, lowpos, highpos, currpos, pos;
X
X	beginpos = lowpos = pos = xdr_getpos(xdrs);
X	highpos = lowpos + ARGSIZE;
X	while ((int)(highpos - lowpos) >= 0) {
X		currpos = (lowpos + highpos) / 2;
X		if (xdr_setpos(xdrs, currpos)) {
X			pos = currpos;
X			lowpos = currpos + 1;
X		} else {
X			highpos = currpos - 1;
X		}
X	}
X	xdr_setpos(xdrs, beginpos);
X	cap->rmt_args.arglen = pos - beginpos;
X	return (xdr_opaque_parms(xdrs, cap));
X}
X
X/*
X * Call a remote procedure service
X * This procedure is very quiet when things go wrong.
X * The proc is written to support broadcast rpc.  In the broadcast case,
X * a machine should shut-up instead of complain, less the requestor be
X * overrun with complaints at the expense of not hearing a valid reply ...
X *
X * This now forks so that the program & process that it calls can call 
X * back to the portmapper.
X */
Xstatic void
Xcallit(rqstp, xprt)
X	struct svc_req *rqstp;
X	SVCXPRT *xprt;
X{
X	struct rmtcallargs a;
X	struct pmaplist *pml;
X	u_short port;
X	struct sockaddr_in me;
X	int pid, so = -1;
X	CLIENT *client;
X	struct authunix_parms *au = (struct authunix_parms *)rqstp->rq_clntcred;
X	struct timeval timeout;
X	char buf[ARGSIZE];
X
X	timeout.tv_sec = 5;
X	timeout.tv_usec = 0;
X	a.rmt_args.args = buf;
X	if (!svc_getargs(xprt, xdr_rmtcall_args, &a))
X		return;
X	/* host and service access control */
X	if (!check_callit(svc_getcaller(xprt), 
X	    rqstp->rq_proc, a.rmt_prog, a.rmt_proc))
X		return;
X	if ((pml = find_service(a.rmt_prog, a.rmt_vers,
X	    (u_long)IPPROTO_UDP)) == NULL)
X		return;
X	/*
X	 * fork a child to do the work.  Parent immediately returns.
X	 * Child exits upon completion.
X	 */
X	if ((pid = fork()) != 0) {
X		if (pid < 0)
X			syslog(LOG_ERR, "CALLIT (prog %lu): fork: %m",
X			    a.rmt_prog);
X		return;
X	}
X	port = pml->pml_map.pm_port;
X	get_myaddress(&me);
X	me.sin_port = htons(port);
X	client = clntudp_create(&me, a.rmt_prog, a.rmt_vers, timeout, &so);
X	if (client != (CLIENT *)NULL) {
X		if (rqstp->rq_cred.oa_flavor == AUTH_UNIX) {
X			client->cl_auth = authunix_create(au->aup_machname,
X			   au->aup_uid, au->aup_gid, au->aup_len, au->aup_gids);
X		}
X		a.rmt_port = (u_long)port;
X		if (clnt_call(client, a.rmt_proc, xdr_opaque_parms, &a,
X		    xdr_len_opaque_parms, &a, timeout) == RPC_SUCCESS) {
X			svc_sendreply(xprt, xdr_rmtcall_result, (caddr_t)&a);
X		}
X		AUTH_DESTROY(client->cl_auth);
X		clnt_destroy(client);
X	}
X	(void)close(so);
X	exit(0);
X}
X
Xvoid
Xreap()
X{
X	while (wait3((int *)NULL, WNOHANG, (struct rusage *)NULL) > 0);
X}
END_OF_portmap.c
if test 15524 -ne `wc -c <portmap.c`; then
    echo shar: \"portmap.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f daemon.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"daemon.c\"
else
echo shar: Extracting \"daemon.c\" \(2590 characters\)
sed "s/^X//" >daemon.c <<'END_OF_daemon.c'
X/*-
X * Copyright (c) 1990 The Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms, with or without
X * modification, are permitted provided that the following conditions
X * are met:
X * 1. Redistributions of source code must retain the above copyright
X *    notice, this list of conditions and the following disclaimer.
X * 2. Redistributions in binary form must reproduce the above copyright
X *    notice, this list of conditions and the following disclaimer in the
X *    documentation and/or other materials provided with the distribution.
X * 3. All advertising materials mentioning features or use of this software
X *    must display the following acknowledgement:
X *	This product includes software developed by the University of
X *	California, Berkeley and its contributors.
X * 4. Neither the name of the University nor the names of its contributors
X *    may be used to endorse or promote products derived from this software
X *    without specific prior written permission.
X *
X * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
X * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
X * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
X * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
X * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
X * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
X * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
X * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
X * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
X * SUCH DAMAGE.
X */
X
X#if defined(LIBC_SCCS) && !defined(lint)
Xstatic char sccsid[] = "@(#)daemon.c	5.3 (Berkeley) 12/28/90";
X#endif /* LIBC_SCCS and not lint */
X
X#include <fcntl.h>
X
X/* From unistd.h */
X#define STDIN_FILENO	0
X#define STDOUT_FILENO	1
X#define STDERR_FILENO	2
X
X/* From paths.h */
X#define _PATH_DEVNULL	"/dev/null"
X
Xdaemon(nochdir, noclose)
X	int nochdir, noclose;
X{
X	int cpid;
X
X	if ((cpid = fork()) == -1)
X		return (-1);
X	if (cpid)
X		exit(0);
X	(void) setsid();
X	if (!nochdir)
X		(void) chdir("/");
X	if (!noclose) {
X		int devnull = open(_PATH_DEVNULL, O_RDWR, 0);
X
X		if (devnull != -1) {
X			(void) dup2(devnull, STDIN_FILENO);
X			(void) dup2(devnull, STDOUT_FILENO);
X			(void) dup2(devnull, STDERR_FILENO);
X			if (devnull > 2)
X				(void) close(devnull);
X		}
X	}
X	return(0);
X}
END_OF_daemon.c
if test 2590 -ne `wc -c <daemon.c`; then
    echo shar: \"daemon.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f strerror.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"strerror.c\"
else
echo shar: Extracting \"strerror.c\" \(2566 characters\)
sed "s/^X//" >strerror.c <<'END_OF_strerror.c'
X/*
X * Copyright (c) 1988 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms, with or without
X * modification, are permitted provided that the following conditions
X * are met:
X * 1. Redistributions of source code must retain the above copyright
X *    notice, this list of conditions and the following disclaimer.
X * 2. Redistributions in binary form must reproduce the above copyright
X *    notice, this list of conditions and the following disclaimer in the
X *    documentation and/or other materials provided with the distribution.
X * 3. All advertising materials mentioning features or use of this software
X *    must display the following acknowledgement:
X *	This product includes software developed by the University of
X *	California, Berkeley and its contributors.
X * 4. Neither the name of the University nor the names of its contributors
X *    may be used to endorse or promote products derived from this software
X *    without specific prior written permission.
X *
X * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
X * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
X * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
X * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
X * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
X * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
X * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
X * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
X * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
X * SUCH DAMAGE.
X */
X
X#if defined(LIBC_SCCS) && !defined(lint)
Xstatic char sccsid[] = "@(#)strerror.c	5.6 (Berkeley) 5/4/91";
X#endif /* LIBC_SCCS and not lint */
X
X#include <string.h>
X
Xchar *
Xstrerror(num)
X	int num;
X{
X	extern int sys_nerr;
X	extern char *sys_errlist[];
X#define	UPREFIX	"Unknown error: "
X	static char ebuf[40] = UPREFIX;		/* 64-bit number + slop */
X	register unsigned int errnum;
X	register char *p, *t;
X	char tmp[40];
X
X	errnum = num;				/* convert to unsigned */
X	if (errnum < sys_nerr)
X		return(sys_errlist[errnum]);
X
X	/* Do this by hand, so we don't include stdio(3). */
X	t = tmp;
X	do {
X		*t++ = "0123456789"[errnum % 10];
X	} while (errnum /= 10);
X	for (p = ebuf + sizeof(UPREFIX) - 1;;) {
X		*p++ = *--t;
X		if (t <= tmp)
X			break;
X	}
X	return(ebuf);
X}
END_OF_strerror.c
if test 2566 -ne `wc -c <strerror.c`; then
    echo shar: \"strerror.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f diffs_wrt_bsd -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"diffs_wrt_bsd\"
else
echo shar: Extracting \"diffs_wrt_bsd\" \(6210 characters\)
sed "s/^X//" >diffs_wrt_bsd <<'END_OF_diffs_wrt_bsd'
X*** portmap.c.bsd	Sun May  3 22:39:52 1992
X--- portmap.c	Thu Jun  4 23:16:36 1992
X***************
X*** 83,99 ****
X  #include <rpc/rpc.h>
X  #include <rpc/pmap_prot.h>
X  #include <stdio.h>
X- #include <stdlib.h>
X- #include <string.h>
X  #include <syslog.h>
X- #include <unistd.h>
X  #include <netdb.h>
X  #include <sys/socket.h>
X  #include <sys/ioctl.h>
X  #include <sys/wait.h>
X  #include <sys/signal.h>
X  #include <sys/resource.h>
X  
X  void reg_service();
X  void reap();
X  static void callit();
X--- 83,115 ----
X  #include <rpc/rpc.h>
X  #include <rpc/pmap_prot.h>
X  #include <stdio.h>
X  #include <syslog.h>
X  #include <netdb.h>
X  #include <sys/socket.h>
X  #include <sys/ioctl.h>
X  #include <sys/wait.h>
X  #include <sys/signal.h>
X+ #include <sys/time.h>
X  #include <sys/resource.h>
X+ #ifdef SYSV40
X+ #include <netinet/in.h>
X+ #endif
X  
X+ extern char *strerror();
X+ extern char *malloc();
X+ 
X+ #ifndef LOG_PERROR
X+ #define LOG_PERROR 0
X+ #endif
X+ 
X+ #ifndef LOG_DAEMON
X+ #define LOG_DAEMON 0
X+ #endif
X+ 
X+ #ifndef svc_getcaller		/* SYSV4 */
X+ #  define svc_getcaller svc_getrpccaller
X+ #endif
X+ 
X  void reg_service();
X  void reap();
X  static void callit();
X***************
X*** 101,106 ****
X--- 117,124 ----
X  int debugging = 0;
X  extern int errno;
X  
X+ #include "pmap_check.h"
X+ 
X  main(argc, argv)
X  	int argc;
X  	char **argv;
X***************
X*** 130,136 ****
X  	}
X  
X  	openlog("portmap", debugging ? LOG_PID | LOG_PERROR : LOG_PID,
X! 	    LOG_DAEMON);
X  
X  	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
X  		syslog(LOG_ERR, "cannot create udp socket: %m");
X--- 148,154 ----
X  	}
X  
X  	openlog("portmap", debugging ? LOG_PID | LOG_PERROR : LOG_PID,
X! 	    FACILITY);
X  
X  	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
X  		syslog(LOG_ERR, "cannot create udp socket: %m");
X***************
X*** 182,187 ****
X--- 200,207 ----
X  
X  	(void)svc_register(xprt, PMAPPROG, PMAPVERS, reg_service, FALSE);
X  
X+ 	/* additional initializations */
X+ 	check_startup();
X  	(void)signal(SIGCHLD, reap);
X  	svc_run();
X  	syslog(LOG_ERR, "run_svc returned unexpectedly");
X***************
X*** 238,243 ****
X--- 258,265 ----
X  		/*
X  		 * Null proc call
X  		 */
X+ 		/* remote host authorization check */
X+ 		check_default(svc_getcaller(xprt), rqstp->rq_proc, (u_long) 0);
X  		if (!svc_sendreply(xprt, xdr_void, (caddr_t)0) && debugging) {
X  			abort();
X  		}
X***************
X*** 250,255 ****
X--- 272,283 ----
X  		if (!svc_getargs(xprt, xdr_pmap, &reg))
X  			svcerr_decode(xprt);
X  		else {
X+ 			/* reject non-local requests, protect priv. ports */
X+ 			if (!check_setunset(svc_getcaller(xprt), 
X+ 			    rqstp->rq_proc, reg.pm_prog, reg.pm_port)) {
X+ 				ans = 0;
X+ 				goto done;
X+ 			} 
X  			/*
X  			 * check to see if already used
X  			 * find_service returns a hit even if
X***************
X*** 299,304 ****
X--- 327,336 ----
X  			svcerr_decode(xprt);
X  		else {
X  			ans = 0;
X+ 			/* reject non-local requests */
X+ 			if (!check_setunset(svc_getcaller(xprt), 
X+ 			    rqstp->rq_proc, reg.pm_prog, (u_long) 0))
X+ 				goto done;
X  			for (prevpml = NULL, pml = pmaplist; pml != NULL; ) {
X  				if ((pml->pml_map.pm_prog != reg.pm_prog) ||
X  					(pml->pml_map.pm_vers != reg.pm_vers)) {
X***************
X*** 308,313 ****
X--- 340,353 ----
X  					continue;
X  				}
X  				/* found it; pml moves forward, prevpml stays */
X+ 				/* privileged port check */
X+ 				if (!check_privileged_port(svc_getcaller(xprt), 
X+ 				    rqstp->rq_proc, 
X+ 				    reg.pm_prog, 
X+ 				    pml->pml_map.pm_port)) {
X+ 					ans = 0;
X+ 					break;
X+ 				}
X  				ans = 1;
X  				t = (caddr_t)pml;
X  				pml = pml->pml_next;
X***************
X*** 332,337 ****
X--- 372,384 ----
X  		if (!svc_getargs(xprt, xdr_pmap, &reg))
X  			svcerr_decode(xprt);
X  		else {
X+ 			/* remote host authorization check */
X+ 			if (!check_default(svc_getcaller(xprt), 
X+ 			    rqstp->rq_proc, 
X+ 			    reg.pm_prog)) {
X+ 				ans = 0;
X+ 				goto done;
X+ 			}
X  			fnd = find_service(reg.pm_prog, reg.pm_vers, reg.pm_prot);
X  			if (fnd)
X  				port = fnd->pml_map.pm_port;
X***************
X*** 352,359 ****
X  		if (!svc_getargs(xprt, xdr_void, NULL))
X  			svcerr_decode(xprt);
X  		else {
X  			if ((!svc_sendreply(xprt, xdr_pmaplist,
X! 			    (caddr_t)&pmaplist)) && debugging) {
X  				(void) fprintf(stderr, "svc_sendreply\n");
X  				abort();
X  			}
X--- 399,414 ----
X  		if (!svc_getargs(xprt, xdr_void, NULL))
X  			svcerr_decode(xprt);
X  		else {
X+ 			/* remote host authorization check */
X+ 			struct pmaplist *p;
X+ 			if (!check_default(svc_getcaller(xprt), 
X+ 			    rqstp->rq_proc, (u_long) 0)) {
X+ 				p = 0;	/* send empty list */
X+ 			} else {
X+ 				p = pmaplist;
X+ 			}
X  			if ((!svc_sendreply(xprt, xdr_pmaplist,
X! 			    (caddr_t)&p)) && debugging) {
X  				(void) fprintf(stderr, "svc_sendreply\n");
X  				abort();
X  			}
X***************
X*** 372,377 ****
X--- 427,434 ----
X  		break;
X  
X  	default:
X+ 		/* remote host authorization check */
X+ 		check_default(svc_getcaller(xprt), rqstp->rq_proc, (u_long) 0);
X  		svcerr_noproc(xprt);
X  		break;
X  	}
X***************
X*** 499,504 ****
X--- 556,565 ----
X  	timeout.tv_usec = 0;
X  	a.rmt_args.args = buf;
X  	if (!svc_getargs(xprt, xdr_rmtcall_args, &a))
X+ 		return;
X+ 	/* host and service access control */
X+ 	if (!check_callit(svc_getcaller(xprt), 
X+ 	    rqstp->rq_proc, a.rmt_prog, a.rmt_proc))
X  		return;
X  	if ((pml = find_service(a.rmt_prog, a.rmt_vers,
X  	    (u_long)IPPROTO_UDP)) == NULL)
X*** daemon.c.bsd	Sun May  3 22:45:10 1992
X--- daemon.c	Sat May 23 16:38:02 1992
X***************
X*** 35,44 ****
X  static char sccsid[] = "@(#)daemon.c	5.3 (Berkeley) 12/28/90";
X  #endif /* LIBC_SCCS and not lint */
X  
X! #include <sys/fcntl.h>
X! #include <unistd.h>
X! #include <paths.h>
X  
X  daemon(nochdir, noclose)
X  	int nochdir, noclose;
X  {
X--- 35,50 ----
X  static char sccsid[] = "@(#)daemon.c	5.3 (Berkeley) 12/28/90";
X  #endif /* LIBC_SCCS and not lint */
X  
X! #include <fcntl.h>
X  
X+ /* From unistd.h */
X+ #define STDIN_FILENO	0
X+ #define STDOUT_FILENO	1
X+ #define STDERR_FILENO	2
X+ 
X+ /* From paths.h */
X+ #define _PATH_DEVNULL	"/dev/null"
X+ 
X  daemon(nochdir, noclose)
X  	int nochdir, noclose;
X  {
X***************
X*** 62,65 ****
X--- 68,72 ----
X  				(void) close(devnull);
X  		}
X  	}
X+ 	return(0);
X  }
END_OF_diffs_wrt_bsd
if test 6210 -ne `wc -c <diffs_wrt_bsd`; then
    echo shar: \"diffs_wrt_bsd\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f pmap_dump.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"pmap_dump.c\"
else
echo shar: Extracting \"pmap_dump.c\" \(1379 characters\)
sed "s/^X//" >pmap_dump.c <<'END_OF_pmap_dump.c'
X /*
X  * pmap_dump - dump portmapper table in format readable by pmap_set
X  * 
X  * Author: Wietse Venema (wietse@wzv.win.tue.nl), dept. of Mathematics and
X  * Computing Science, Eindhoven University of Technology, The Netherlands.
X  */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#) pmap_dump.c 1.1 92/06/11 22:53:15";
X#endif
X
X#include <stdio.h>
X#include <sys/types.h>
X#ifdef SYSV40
X#include <netinet/in.h>
X#include <rpc/rpcent.h>
X#else
X#include <netdb.h>
X#endif
X#include <rpc/rpc.h>
X#include <rpc/pmap_clnt.h>
X#include <rpc/pmap_prot.h>
X
Xstatic char *protoname();
X
Xmain(argc, argv)
Xint     argc;
Xchar  **argv;
X{
X    struct sockaddr_in addr;
X    register struct pmaplist *list;
X    register struct rpcent *rpc;
X
X    get_myaddress(&addr);
X
X    for (list = pmap_getmaps(&addr); list; list = list->pml_next) {
X	rpc = getrpcbynumber((int) list->pml_map.pm_prog);
X	printf("%10lu %4lu %5s %6lu  %s\n",
X	       list->pml_map.pm_prog,
X	       list->pml_map.pm_vers,
X	       protoname(list->pml_map.pm_prot),
X	       list->pml_map.pm_port,
X	       rpc ? rpc->r_name : "");
X    }
X#undef perror
X    return (fclose(stdout) ? (perror(argv[0]), 1) : 0);
X}
X
Xstatic char *protoname(proto)
Xu_long  proto;
X{
X    static char buf[BUFSIZ];
X
X    switch (proto) {
X    case IPPROTO_UDP:
X	return ("udp");
X    case IPPROTO_TCP:
X	return ("tcp");
X    default:
X	sprintf(buf, "%lu", proto);
X	return (buf);
X    }
X}
END_OF_pmap_dump.c
if test 1379 -ne `wc -c <pmap_dump.c`; then
    echo shar: \"pmap_dump.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f pmap_set.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"pmap_set.c\"
else
echo shar: Extracting \"pmap_set.c\" \(1511 characters\)
sed "s/^X//" >pmap_set.c <<'END_OF_pmap_set.c'
X /*
X  * pmap_set - set portmapper table from data produced by pmap_dump
X  * 
X  * Author: Wietse Venema (wietse@wzv.win.tue.nl), dept. of Mathematics and
X  * Computing Science, Eindhoven University of Technology, The Netherlands.
X  */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#) pmap_set.c 1.1 92/06/11 22:53:16";
X#endif
X
X#include <stdio.h>
X#include <sys/types.h>
X#ifdef SYSV40
X#include <netinet/in.h>
X#endif
X#include <rpc/rpc.h>
X#include <rpc/pmap_clnt.h>
X
Xmain(argc, argv)
Xint     argc;
Xchar  **argv;
X{
X    struct sockaddr_in addr;
X    char    buf[BUFSIZ];
X    u_long  prog;
X    u_long  vers;
X    int     prot;
X    unsigned port;
X
X    get_myaddress(&addr);
X
X    while (fgets(buf, sizeof(buf), stdin)) {
X	if (parse_line(buf, &prog, &vers, &prot, &port) == 0) {
X	    fprintf(stderr, "%s: malformed line: %s", argv[0], buf);
X	    return (1);
X	}
X	if (pmap_set(prog, vers, prot, (unsigned short) port) == 0)
X	    fprintf(stderr, "not registered: %s", buf);
X    }
X    return (0);
X}
X
X/* parse_line - convert line to numbers */
X
Xparse_line(buf, prog, vers, prot, port)
Xchar   *buf;
Xu_long *prog;
Xu_long *vers;
Xint    *prot;
Xunsigned *port;
X{
X    char    proto_name[BUFSIZ];
X
X    if (sscanf(buf, "%lu %lu %s %u", prog, vers, proto_name, port) != 4) {
X	return (0);
X    }
X    if (strcmp(proto_name, "tcp") == 0) {
X	*prot = IPPROTO_TCP;
X	return (1);
X    }
X    if (strcmp(proto_name, "udp") == 0) {
X	*prot = IPPROTO_UDP;
X	return (1);
X    }
X    if (sscanf(proto_name, "%d", prot) == 1) {
X	return (1);
X    }
X    return (0);
X}
END_OF_pmap_set.c
if test 1511 -ne `wc -c <pmap_set.c`; then
    echo shar: \"pmap_set.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f from_local.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"from_local.c\"
else
echo shar: Extracting \"from_local.c\" \(4052 characters\)
sed "s/^X//" >from_local.c <<'END_OF_from_local.c'
X /*
X  * Check if an address belongs to the local system. Adapted from:
X  * 
X  * @(#)pmap_svc.c 1.32 91/03/11 Copyright 1984,1990 Sun Microsystems, Inc.
X  * @(#)get_myaddress.c  2.1 88/07/29 4.0 RPCSRC.
X  */
X
X/*
X * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
X * unrestricted use provided that this legend is included on all tape
X * media and as a part of the software program in whole or part.  Users
X * may copy or modify Sun RPC without charge, but are not authorized
X * to license or distribute it to anyone else except as part of a product or
X * program developed by the user or with the express written consent of
X * Sun Microsystems, Inc.
X *
X * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
X * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
X * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
X *
X * Sun RPC is provided with no support and without any obligation on the
X * part of Sun Microsystems, Inc. to assist in its use, correction,
X * modification or enhancement.
X *
X * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
X * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
X * OR ANY PART THEREOF.
X *
X * In no event will Sun Microsystems, Inc. be liable for any lost revenue
X * or profits or other special, indirect and consequential damages, even if
X * Sun has been advised of the possibility of such damages.
X *
X * Sun Microsystems, Inc.
X * 2550 Garcia Avenue
X * Mountain View, California  94043
X */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#) from_local.c 1.1 92/06/11 22:53:17";
X#endif
X
X#ifdef TEST
X#undef perror
X#endif
X
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <netdb.h>
X#include <netinet/in.h>
X#include <net/if.h>
X#include <sys/ioctl.h>
X#include <syslog.h>
X
X#ifndef TRUE
X#define	TRUE	1
X#define FALSE	0
X#endif
X
X/* How many interfaces could there be on a computer? */
X
X#define	MAX_LOCAL 16
Xstatic int num_local = -1;
Xstatic struct in_addr addrs[MAX_LOCAL];
X
X/* find_local - find all IP addresses for this host */
X
Xfind_local()
X{
X    struct ifconf ifc;
X    struct ifreq ifreq;
X    struct ifreq *ifr;
X    struct ifreq *the_end;
X    int     sock;
X    char    buf[MAX_LOCAL * sizeof(struct ifreq)];
X
X    /* Get list of network interfaces. */
X
X    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
X	perror("socket");
X	return (0);
X    }
X    ifc.ifc_len = sizeof(buf);
X    ifc.ifc_buf = buf;
X    if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
X	perror("SIOCGIFCONF");
X	(void) close(sock);
X	return (0);
X    }
X    /* Get IP address of each active IP network interface. */
X
X    the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
X    num_local = 0;
X    for (ifr = ifc.ifc_req; ifr < the_end; ifr++) {
X	if (ifr->ifr_addr.sa_family == AF_INET) {	/* IP net interface */
X	    ifreq = *ifr;
X	    if (ioctl(sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) {
X		perror("SIOCGIFFLAGS");
X	    } else if (ifreq.ifr_flags & IFF_UP) {	/* active interface */
X		if (ioctl(sock, SIOCGIFADDR, (char *) &ifreq) < 0) {
X		    perror("SIOCGIFADDR");
X		} else {
X		    addrs[num_local++] = ((struct sockaddr_in *)
X					  & ifreq.ifr_addr)->sin_addr;
X		}
X	    }
X#ifdef TIRPC
X	    /* Support for variable-length addresses. */
X	    if (num_local >= MAX_LOCAL)
X		break;
X	    ifr = (struct ifreq *) ((caddr_t) ifr
X			  + ifr->ifr_addr.sa_len - sizeof(struct sockaddr));
X#endif
X	}
X    }
X    (void) close(sock);
X    return (num_local);
X}
X
X/* from_local - determine whether request comes from the local system */
X
Xfrom_local(addr)
Xstruct sockaddr_in *addr;
X{
X    int     i;
X
X    if (num_local == -1 && find_local() == 0)
X	syslog(LOG_ERR, "cannot find any active local network interfaces");
X
X    for (i = 0; i < num_local; i++) {
X	if (memcmp((char *) &(addr->sin_addr), (char *) &(addrs[i]),
X		   sizeof(struct in_addr)) == 0)
X	    return (TRUE);
X    }
X    return (FALSE);
X}
X
X#ifdef TEST
X
Xmain()
X{
X    char   *inet_ntoa();
X    int     i;
X
X    find_local();
X    for (i = 0; i < num_local; i++)
X	printf("%s\n", inet_ntoa(addrs[i]));
X}
X
X#endif
END_OF_from_local.c
if test 4052 -ne `wc -c <from_local.c`; then
    echo shar: \"from_local.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f pmap_check.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"pmap_check.c\"
else
echo shar: Extracting \"pmap_check.c\" \(6443 characters\)
sed "s/^X//" >pmap_check.c <<'END_OF_pmap_check.c'
X /*
X  * pmap_check - additional portmap security.
X  * 
X  * Always reject non-local requests to update the portmapper tables.
X  * 
X  * Refuse to forward mount requests to the nfs mount daemon. Otherwise, the
X  * requests would appear to come from the local system, and nfs export
X  * restrictions could be bypassed.
X  * 
X  * Refuse to forward requests to the nfsd process.
X  * 
X  * Refuse to forward requests to NIS (YP) daemons; The only exception is the
X  * YPPROC_DOMAIN_NONACK broadcast rpc call that is used to establish initial
X  * contact with the NIS server.
X  * 
X  * Always allocate an unprivileged port when forwarding a request.
X  * 
X  * If compiled with -DCHECK_PORT, require that requests to register or
X  * unregister a privileged port come from a privileged port. This makes it
X  * more difficult to replace a critical service by a trojan.
X  * 
X  * If compiled with -DHOSTS_ACCESS, reject requests from hosts that are not
X  * authorized by the /etc/hosts.{allow,deny} files. The local system is
X  * always treated as an authorized host. Access control is based on IP
X  * addresses only; attempts to map an address to a host name might cause the
X  * portmapper to hang.
X  * 
X  * Author: Wietse Venema (wietse@wzv.win.tue.nl), dept. of Mathematics and
X  * Computing Science, Eindhoven University of Technology, The Netherlands.
X  */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#) pmap_check.c 1.2 92/07/06 19:53:32";
X#endif
X
X#include <rpc/rpc.h>
X#include <rpc/pmap_prot.h>
X#include <syslog.h>
X#include <netdb.h>
X#include <sys/signal.h>
X#ifdef SYSV40
X#include <netinet/in.h>
X#include <rpc/rpcent.h>
X#endif
X
Xextern char *inet_ntoa();
X
X#include "pmap_check.h"
X
X/* Explicit #defines in case the include files are not available. */
X
X#define NFSPROG		((u_long) 100003)
X#define MOUNTPROG	((u_long) 100005)
X#define	YPXPROG		((u_long) 100069)
X#define YPPROG          ((u_long) 100004)
X#define YPPROC_DOMAIN_NONACK ((u_long) 2)
X#define MOUNTPROC_MNT	((u_long) 1)
X
Xstatic void logit();
Xstatic void toggle_verboselog();
Xstatic int verboselog = 0;
X
X/* A handful of macros for "readability". */
X
X#define	legal_host(a) \
X  (from_local(a) || hosts_ctl("portmap", "", inet_ntoa(a->sin_addr), ""))
X
X#define	legal_port(a,p) \
X  (ntohs((a)->sin_port) < IPPORT_RESERVED || (p) >= IPPORT_RESERVED)
X
X#define log_bad_port(addr, proc, prog) \
X  logit(LOG_ERR, addr, proc, prog, ": request from unprivileged port")
X
X#define log_bad_host(addr, proc, prog) \
X  logit(LOG_ERR, addr, proc, prog, ": request from unauthorized host")
X
X#define log_bad_owner(addr, proc, prog) \
X  logit(LOG_ERR, addr, proc, prog, ": request from non-local host")
X
X#define	log_no_forward(addr, proc, prog) \
X  logit(LOG_ERR, addr, proc, prog, ": request not forwarded")
X
X#define log_client(addr, proc, prog) \
X  logit(LOG_INFO, addr, proc, prog, "")
X
X/* check_startup - additional startup code */
X
Xvoid    check_startup()
X{
X
X    /*
X     * Give up root privileges so that we can never allocate a privileged
X     * port when forwarding an rpc request.
X     */
X    if (setuid(1) == -1) {
X	syslog(LOG_ERR, "setuid(1) failed: %m");
X	exit(1);
X    }
X    (void) signal(SIGINT, toggle_verboselog);
X}
X
X/* check_default - additional checks for NULL, DUMP, GETPORT and unknown */
X
Xcheck_default(addr, proc, prog)
Xstruct sockaddr_in *addr;
Xu_long  proc;
Xu_long  prog;
X{
X#ifdef HOSTS_ACCESS
X    if (!legal_host(addr)) {
X	log_bad_host(addr, proc, prog);
X	return (FALSE);
X    }
X#endif
X    if (verboselog)
X	log_client(addr, proc, prog);
X    return (TRUE);
X}
X
X/* check_privileged_port - additional checks for privileged-port updates */
X
Xcheck_privileged_port(addr, proc, prog, port)
Xstruct sockaddr_in *addr;
Xu_long  proc;
Xu_long  prog;
Xu_long  port;
X{
X#ifdef CHECK_PORT
X    if (!legal_port(addr, port)) {
X	log_bad_port(addr, proc, prog);
X	return (FALSE);
X    }
X#endif
X    return (TRUE);
X}
X
X/* check_setunset - additional checks for update requests */
X
Xcheck_setunset(addr, proc, prog, port)
Xstruct sockaddr_in *addr;
Xu_long  proc;
Xu_long  prog;
Xu_long  port;
X{
X    if (!from_local(addr)) {
X	log_bad_owner(addr, proc, prog);
X	return (FALSE);
X    }
X    if (port && !check_privileged_port(addr, proc, prog, port))
X	return (FALSE);
X    if (verboselog)
X	log_client(addr, proc, prog);
X    return (TRUE);
X}
X
X/* check_callit - additional checks for forwarded requests */
X
Xcheck_callit(addr, proc, prog, aproc)
Xstruct sockaddr_in *addr;
Xu_long  proc;
Xu_long  prog;
Xu_long  aproc;
X{
X#ifdef HOSTS_ACCESS
X    if (!legal_host(addr)) {
X	log_bad_host(addr, proc, prog);
X	return (FALSE);
X    }
X#endif
X    if (prog == PMAPPROG || prog == NFSPROG || prog == YPXPROG ||
X	(prog == MOUNTPROG && aproc == MOUNTPROC_MNT) ||
X	(prog == YPPROG && aproc != YPPROC_DOMAIN_NONACK)) {
X	log_no_forward(addr, proc, prog);
X	return (FALSE);
X    }
X    if (verboselog)
X	log_client(addr, proc, prog);
X    return (TRUE);
X}
X
X/* toggle_verboselog - toggle verbose logging flag */
X
Xstatic void toggle_verboselog(sig)
Xint     sig;
X{
X    (void) signal(sig, toggle_verboselog);
X    verboselog = !verboselog;
X}
X
X/* logit - report events of interest via the syslog daemon */
X
Xstatic void logit(severity, addr, procnum, prognum, text)
Xint     severity;
Xstruct sockaddr_in *addr;
Xu_long  procnum;
Xu_long  prognum;
Xchar   *text;
X{
X    char   *procname;
X    char    procbuf[4 * sizeof(u_long)];
X    char   *progname;
X    char    progbuf[4 * sizeof(u_long)];
X    struct rpcent *rpc;
X    struct proc_map {
X	u_long  code;
X	char   *proc;
X    };
X    struct proc_map *procp;
X    static struct proc_map procmap[] = {
X	PMAPPROC_CALLIT, "callit",
X	PMAPPROC_DUMP, "dump",
X	PMAPPROC_GETPORT, "getport",
X	PMAPPROC_NULL, "null",
X	PMAPPROC_SET, "set",
X	PMAPPROC_UNSET, "unset",
X	0, 0,
X    };
X
X    /*
X     * Fork off a process or the portmap daemon might hang while
X     * getrpcbynumber() or syslog() does its thing.
X     */
X
X    if (fork() == 0) {
X
X	/* Try to map program number to name. */
X
X	if (prognum == 0) {
X	    progname = "";
X	} else if (rpc = getrpcbynumber((int) prognum)) {
X	    progname = rpc->r_name;
X	} else {
X	    sprintf(progname = progbuf, "%lu", prognum);
X	}
X
X	/* Try to map procedure number to name. */
X
X	for (procp = procmap; procp->proc && procp->code != procnum; procp++)
X	     /* void */ ;
X	if ((procname = procp->proc) == 0)
X	    sprintf(procname = procbuf, "%lu", (u_long) procnum);
X
X	/* Write syslog record. */
X
X	syslog(severity, "connect from %s to %s(%s)%s",
X	       inet_ntoa(addr->sin_addr), procname, progname, text);
X	exit(0);
X    }
X}
END_OF_pmap_check.c
if test 6443 -ne `wc -c <pmap_check.c`; then
    echo shar: \"pmap_check.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f pmap_check.h -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"pmap_check.h\"
else
echo shar: Extracting \"pmap_check.h\" \(221 characters\)
sed "s/^X//" >pmap_check.h <<'END_OF_pmap_check.h'
X/* @(#) pmap_check.h 1.1 92/06/11 22:53:20 */
X
Xextern int from_local();
Xextern void check_startup();
Xextern int check_default();
Xextern int check_setunset();
Xextern int check_privileged_port();
Xextern int check_callit();
END_OF_pmap_check.h
if test 221 -ne `wc -c <pmap_check.h`; then
    echo shar: \"pmap_check.h\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f BLURB -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"BLURB\"
else
echo shar: Extracting \"BLURB\" \(1585 characters\)
sed "s/^X//" >BLURB <<'END_OF_BLURB'
X@(#) BLURB 1.2 92/07/06 19:57:28
X
XThere is an increasing interest in access control for the NIS, mount
Xand other RPC-based services that are normally registered with the
Xportmap process. These days, popular attacks on RPC daemons involve:
X
X    - theft or NIS (YP) password files
X
X    - ypset to force hosts to bind to a rogue NIS (YP) server
X
X    - theft of NFS file handles
X
XMy contribution is a replacement portmap program, derived from source
Xcode in the RPCSRC 4.0 and the TIRPC source distributions.  Access
Xcontrol is in the style of my tcp wrapper (log_tcp) package. It should
Xwork with all SunOS 4.x and Ultrix >= 3.0 releases. However, the source
Xis reasonably portable and the code should work on most UNIX systems
Xthat provide SUNRPC on top of BSD-style TCP/IP. System V.4 support is
Xproblematic, though.
X
XThe present portmap version attempts to close all portmap security
Xproblems that are known to me. It should be as secure as the portmap
Xdaemon that comes with the SunOS 4.x portmap+NIS patch (patch id
X100482-02). The README file gives a complete list of security
Xfeatures.
X
XWithout the availability of portmap source, possible alternatives are
X1) packet filtering with a smart router; 2) linking the portmap
Xexecutable against the securelib shared library. Linking other RPC
Xdaemons against the securelib library is a good idea, anyway.
X
XThe source is available for anonymous FTP from ftp.win.tue.nl directory
X/pub/security/portmap.shar.Z.
X
X	Wietse Venema (wietse@wzv.win.tue.nl)
X	Mathematics and Computing Science
X	Eindhoven University of Technology
X	The Netherlands
END_OF_BLURB
if test 1585 -ne `wc -c <BLURB`; then
    echo shar: \"BLURB\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0


TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH