1
0
Files
irix-657m-src/eoe/cmd/sun/portmap.c
2022-09-29 17:59:04 +03:00

1276 lines
30 KiB
C

/*
* Copyright 1991 by Silicon Graphics, Inc.
*/
/*
* Copyright (c) 1984,1990 by Sun Microsystems, Inc.
*/
/*
* portmap.c, Implements the program,version to port number mapping for
* rpc.
*/
#define _BSD_SIGNALS
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h> /* for ntohx() */
#include <net/if.h> /* to find local addresses */
#include <net/route.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/sysctl.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <cap_net.h>
#include <fmtmsg.h>
#ifndef PORTMAP_DEBUG
/* an extremely ugly hack, but it works */
# define perror(string) syslog(LOG_DEBUG, "%s: %m", string)
# define fprintf syslog
# undef stderr
# define stderr LOG_DEBUG
#endif /* PORTMAP_DEBUG */
static void reap();
static void reg_service(struct svc_req *, SVCXPRT *);
static void callit(struct svc_req *, SVCXPRT *);
static bool_t chklocal(struct in_addr addr);
static void getlocal(void);
struct pmaplist *pmaplist;
static int debugging;
static int verbose; /* Catch all errors */
static struct call_table {
pid_t pid;
} *call_table, *reap_table;
static volatile int reap_count;
static int fork_cnt;
static int max_forks = 30;
static int do_mcast;
static int mcast_sock = -1;
static int udp_sock;
static int tcp_fd = -1; /* For checking whether TCP service is up */
static int udp_fd = -1; /* For checking whether UDP service is up */
static SVCXPRT *ludp_xprt, *ltcp_xprt; /* To check for loopback connections */
/*
* Simple access control to restrict any request for useful information
* to a limited set of addresses. The first match in the array of
* mask-match pairs allows the request to be processed. Any address
* for this host is always allowed.
*/
#define MAX_OKNETS 50
static struct oknet {
struct in_addr mask, match;
} oknets[MAX_OKNETS];
static int num_oknets;
static int Aflag;
static int bflag;
static int Secure = 1;
static struct timeval when_local;
static int num_local = 0;
static union addrs {
char buf[1];
struct ifa_msghdr ifam;
struct oknet a[1];
} *addrs;
static size_t addrs_size = 0;
extern int _using_syslog;
extern int __svc_label_agile;
static void moredebug();
static void lessdebug();
/*
* Return 1 if the address is accepted, 0 otherwise.
*/
static int
chknet(struct in_addr addr)
{
register struct oknet *n;
register int i;
for (n = oknets, i = 0; i < num_oknets; n++, i++) {
if ((addr.s_addr & n->mask.s_addr) == n->match.s_addr)
return 1;
}
/*
* If it was not in the explicit list and if allowed, check
* the implicit list.
*/
if (Aflag) {
getlocal();
for (i = num_local, n = &addrs->a[0]; i != 0; i--, n++) {
if (0 == ((addr.s_addr ^ n->mask.s_addr)
& n->match.s_addr))
return 1;
}
}
return 0;
}
main(argc,argv)
int argc;
char *argv[];
{
SVCXPRT *xprt;
int sock, lsock, pid, t;
struct sockaddr_in addr, laddr;
int len = sizeof(struct sockaddr_in);
struct ip_mreq mreq;
register struct pmaplist *pml;
int on = 1, argerr = 0;
openlog(argv[0], LOG_PID|LOG_NOWAIT, LOG_DAEMON);
_using_syslog = 1;
__svc_label_agile = (sysconf(_SC_IP_SECOPTS) > 0);
opterr = 0;
while ((t = getopt(argc, argv, "a:AbCdf:mv")) != EOF) {
switch (t) {
case 'a': {
char *cp;
struct oknet n;
/*
* Option formats: "mask,match" or "network", where
* mask, match and network are valid IP address/network
* numbers. "network" is a shorthand for specifying the
* default mask and match appropriate for the
* network's address class.
*/
if (cp = strchr(optarg, ',')) {
*cp++ = '\0';
if (!inet_isaddr(optarg, &n.mask.s_addr)) {
fprintf(stderr,
"%s: illegal IP address for mask\n",
optarg);
argerr = 1;
break;
}
if (!inet_isaddr(cp, &n.match.s_addr)) {
fprintf(stderr,
"%s: illegal IP address for match\n",
cp);
argerr = 1;
break;
}
} else {
/*
* Treat arg as a network address,
* host part of address is ignored.
*/
if (!inet_isaddr(optarg, &n.match.s_addr)) {
fprintf(stderr,
"%s: illegal network address\n",
optarg);
argerr = 1;
break;
}
if (IN_CLASSA(n.match.s_addr))
n.mask.s_addr = IN_CLASSA_NET;
else if (IN_CLASSB(n.match.s_addr))
n.mask.s_addr = IN_CLASSB_NET;
else if (IN_CLASSC(n.match.s_addr))
n.mask.s_addr = IN_CLASSC_NET;
else {
fprintf(stderr,
"%s: illegal network address class\n",
optarg);
argerr = 1;
break;
}
n.match.s_addr &= n.mask.s_addr;
}
if (num_oknets < MAX_OKNETS)
oknets[num_oknets++] = n;
else
fprintf(stderr,
"too many nets, extra ignored\n");
break;
}
case 'A':
/*
* Trust all directly connected networks.
*/
Aflag = 1;
break;
case 'b':
/*
* Trust non-multicast including broadcast sources
* because we are behind a firewall.
*/
bflag = 1;
break;
case 'f': {
int max = atoi(optarg);
if (max < 1 || max > 1024) {
fprintf(stderr,
"%s: illegal value for fork limit, ignored\n",
optarg);
argerr = 1;
} else
max_forks = max;
break;
}
case 'm':
do_mcast = 1;
break;
case 'd':
debugging = 1;
/* fall thru */
case 'v':
verbose = 1;
break;
case 'C':
Secure = 0;
(void) fmtmsg(MM_CONSOLE, "portmap", MM_WARNING, "The -C option provides backward compatibility for broken applications. It also exposes a widely known security problem.", NULL, NULL);
break;
default:
case '?':
fprintf(stderr,"unknown option: %s\n", argv[optind -1]);
argerr = 1;
break;
}
}
if (argerr) {
/* -d option just for testing, don't show it */
fprintf(stderr,
"usage: portmap [-vmAbC] [-f forklimit] [-a mask,match | -a match]\n");
}
#ifndef PORTMAP_DEBUG
_daemonize(0, -1, -1, -1);
openlog(argv[0], LOG_PID|LOG_NOWAIT, LOG_DAEMON);
#endif
call_table = (struct call_table *)malloc(sizeof(*call_table)
*max_forks);
bzero(call_table, sizeof(*call_table)*max_forks);
reap_table = (struct call_table *)malloc(sizeof(*call_table)
*max_forks);
bzero(reap_table, sizeof(*call_table)*max_forks);
if ((udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror("portmap cannot create UDP socket");
exit(1);
}
/*
* We use SO_REUSEADDR because there is a possibility of kernel
* disallowing the use of the same bind address for some time even
* after that corresponding socket has been closed. In the case
* of TCP, some data transfer may still be pending.
*/
(void) setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
bzero(&addr, sizeof(addr));
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_family = AF_INET;
addr.sin_port = htons(PMAPPORT);
if (cap_bind(udp_sock, (struct sockaddr *)&addr, len) != 0) {
perror("portmap cannot bind");
exit(1);
}
if ((xprt = svcudp_create(udp_sock)) == (SVCXPRT *)NULL) {
fprintf(stderr, "couldn't do udp_create\n");
exit(1);
}
if (Secure) {
/* create a socket bound to the loopback interface so we can
definitively tell connections that originated from this machine */
if ((lsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror("portmap cannot create UDP socket");
exit(1);
}
(void) setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &on,
sizeof on);
bzero(&laddr, sizeof(laddr));
laddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
laddr.sin_family = AF_INET;
laddr.sin_port = htons(PMAPPORT);
if (cap_bind(lsock, (struct sockaddr *)&laddr, len) != 0) {
perror("portmap cannot bind");
exit(1);
}
if ((ludp_xprt = svcudp_create(lsock)) == (SVCXPRT *)NULL) {
fprintf(stderr, "couldn't do local udp_create\n");
exit(1);
}
}
/* make an entry for ourself */
pml = (struct pmaplist *)malloc((u_int)sizeof(struct pmaplist));
pml->pml_next = 0;
pml->pml_map.pm_prog = PMAPPROG;
pml->pml_map.pm_vers = PMAPVERS;
pml->pml_map.pm_prot = IPPROTO_UDP;
pml->pml_map.pm_port = PMAPPORT;
pmaplist = pml;
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("portmap cannot create socket");
exit(1);
}
(void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char *)&on, sizeof on);
if (cap_bind(sock, (struct sockaddr *)&addr, len) != 0) {
perror("portmap cannot bind");
exit(1);
}
if ((xprt = svctcp_create(sock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE))
== (SVCXPRT *)NULL) {
fprintf(stderr, "couldn't do tcp_create\n");
exit(1);
}
if (Secure) {
/* create a socket bound to the loopback interface so we can
definitively tell connections that originated from this machine */
if ((lsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("portmap cannot create socket");
exit(1);
}
(void) setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR,
(char *)&on, sizeof on);
if (cap_bind(lsock, (struct sockaddr *)&laddr, len) != 0) {
perror("portmap cannot bind");
exit(1);
}
if ((ltcp_xprt = svctcp_create(lsock, RPCSMALLMSGSIZE,
RPCSMALLMSGSIZE))
== (SVCXPRT *)NULL) {
fprintf(stderr, "couldn't do tcp_create\n");
exit(1);
}
}
/* make an entry for ourself */
pml = (struct pmaplist *)malloc((u_int)sizeof(struct pmaplist));
pml->pml_map.pm_prog = PMAPPROG;
pml->pml_map.pm_vers = PMAPVERS;
pml->pml_map.pm_prot = IPPROTO_TCP;
pml->pml_map.pm_port = PMAPPORT;
pml->pml_next = pmaplist;
pmaplist = pml;
/*
* Create multicast socket.
* Use a separate socket to detect bad guys sending with a
* forged local source address.
*/
if (do_mcast) {
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror("portmap cannot create multicast socket");
exit(1);
}
(void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&on, sizeof on);
addr.sin_addr.s_addr = htonl(PMAP_MULTICAST_INADDR);
addr.sin_family = AF_INET;
addr.sin_port = htons(PMAPPORT);
mreq.imr_multiaddr.s_addr = htonl(PMAP_MULTICAST_INADDR);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (cap_bind(sock, (struct sockaddr *)&addr, len) != 0) {
perror("portmap cannot bind multcast socket");
} else if (setsockopt(sock, IPPROTO_IP,
IP_ADD_MEMBERSHIP, &mreq, sizeof mreq)) {
perror("cannot enable multicast reception");
} else if ((xprt = svcudp_create(sock)) == 0) {
fprintf(stderr, "couldn't do multicast udp_create\n");
(void)close(sock);
} else {
mcast_sock = sock;
}
}
(void)svc_register(xprt, PMAPPROG, PMAPVERS, reg_service, FALSE);
/*
* sockets send this on write after disconnect --
* live and learn from SVR4 work
*/
(void)signal(SIGPIPE, SIG_IGN);
(void)signal(SIGHUP, SIG_IGN);
(void)signal(SIGCHLD, reap);
(void)signal(SIGUSR1, moredebug);
(void)signal(SIGUSR2, lessdebug);
svc_run();
fprintf(stderr, "svc_run returned unexpectedly\n");
abort();
/*NOTREACHED*/
}
/*
* This routine is called to make sure that the given port number is
* still bound. This is helpful in those cases where the server dies
* and the client gets the wrong information. It is better if the
* client about this fact at create time itself. If the given port
* number is not bound, we return FALSE.
*/
static bool_t
is_bound(pml)
struct pmaplist *pml;
{
int fd;
struct sockaddr_in myaddr;
if (pml->pml_map.pm_prot == IPPROTO_TCP) {
if (tcp_fd < 0)
tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
fd = tcp_fd;
} else if (pml->pml_map.pm_prot == IPPROTO_UDP) {
if (udp_fd < 0)
udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
fd = udp_fd;
} else
return (TRUE);
if (fd < 0)
return (TRUE);
bzero((char *)&myaddr, sizeof (myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(pml->pml_map.pm_port);
if (cap_bind(fd, (struct sockaddr *)&myaddr, sizeof (myaddr)) < 0)
return (TRUE);
if (pml->pml_map.pm_prot == IPPROTO_TCP) {
/* XXX: Too bad that there is no unbind(). */
(void) close(tcp_fd);
tcp_fd = -1;
} else {
(void) close(udp_fd);
udp_fd = -1;
}
return (FALSE);
}
static void
moredebug()
{
if (!debugging) {
debugging = 1;
syslog(LOG_DEBUG, "debugging=1");
} else {
verbose = 1;
syslog(LOG_DEBUG, "verbose=1");
}
}
static void
lessdebug()
{
if (verbose) {
verbose = 0;
syslog(LOG_DEBUG, "verbose=0");
} else {
debugging = 0;
syslog(LOG_DEBUG, "debugging=0");
}
}
/*
* Finds the service for prog, vers. Even if the specified vers
* is not registered, we still return the registeration for that
* program number, with the hope that the client will figure out
* the actual available version number on doing a clnt_call() via
* the RPC_PROGVERSMISMTACH.
*/
static struct pmaplist *
find_service(prog, vers, prot)
u_long prog;
u_long vers;
u_long prot;
{
register struct pmaplist *hit = NULL;
register struct pmaplist *hitp = NULL;
register struct pmaplist *pml;
register struct pmaplist *prevpml;
for (prevpml = NULL, pml = pmaplist; pml != NULL; pml = pml->pml_next) {
if ((pml->pml_map.pm_prog != prog) ||
(pml->pml_map.pm_prot != prot)) {
prevpml = pml;
continue;
}
hit = pml;
hitp = prevpml;
if (pml->pml_map.pm_vers == vers)
break;
prevpml = pml;
}
if (hit == NULL)
return (NULL);
/* Make sure it is bound */
if (is_bound(hit))
return (hit);
/* unhook it from the list */
if (verbose)
fprintf(stderr,
"service failed for prog %u, vers %u, prot %u\n",
hit->pml_map.pm_prog, hit->pml_map.pm_vers,
hit->pml_map.pm_prot);
pml = hit->pml_next;
if (hitp == NULL)
pmaplist = pml;
else
hitp->pml_next = pml;
free((caddr_t) hit);
/*
* Most probably other versions and transports for this
* program number were also registered by this same
* service, so we should check their bindings also.
* This may slow down the response time a bit.
*/
for (prevpml = NULL, pml = pmaplist; pml != NULL;) {
if (pml->pml_map.pm_prog != prog) {
prevpml = pml;
pml = pml->pml_next;
continue;
}
/* Make sure it is bound */
if (is_bound(pml)) {
prevpml = pml;
pml = pml->pml_next;
continue;
} else {
/* unhook it */
hit = pml;
pml = hit->pml_next;
if (prevpml == NULL)
pmaplist = pml;
else
prevpml->pml_next = pml;
free((caddr_t) hit);
}
}
return (NULL);
}
/*
* 1 OK, 0 not
*/
static void
reg_service(struct svc_req *rqstp, SVCXPRT *xprt)
{
struct pmap reg;
struct pmaplist *pml, *prevpml, *fnd;
int ans, local;
u_long port;
caddr_t t;
struct sockaddr_in *who;
who = svc_getcaller(xprt);
if (debugging)
printf("%s: proc %u\n",
inet_ntoa(who->sin_addr), rqstp->rq_proc);
local = chklocal(who->sin_addr);
if (xprt->xp_sock == mcast_sock) {
/*
* Ignore (supposedly) local requests that arrived via
* multicast in case they come from a bad guy on the Internet
* sending poison packets to the universe.
*/
if (local) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"ignore multicast prog %u proc %u call"
" from local %s",
rqstp->rq_prog, rqstp->rq_proc,
inet_ntoa(who->sin_addr));
return;
}
/*
* Ignore unauthorized all multicasts to avoid melting
* multicast forwarders with rejections.
*/
if ((num_oknets > 0 || Aflag) &&
!chknet(who->sin_addr)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"ignore multicast prog %u proc %u call"
" from %s",
rqstp->rq_prog, rqstp->rq_proc,
inet_ntoa(who->sin_addr));
return;
}
} else {
/*
* Allow unicast or broadcast "null" procedure requests from
* anybody since they return no port information.
*/
if ((num_oknets > 0 || Aflag) && !bflag &&
!local && rqstp->rq_proc != PMAPPROC_NULL &&
!chknet(who->sin_addr)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"rejected prog %u proc %u call from %s",
rqstp->rq_prog, rqstp->rq_proc,
inet_ntoa(who->sin_addr));
svcerr_auth(xprt, AUTH_FAILED);
return;
}
}
switch (rqstp->rq_proc) {
case PMAPPROC_NULL:
/*
* Null proc call
*/
if ((!svc_sendreply(xprt, xdr_void, (char *)NULL)) &&
debugging) {
fprintf(stderr, "svc_sendreply\n");
abort();
}
break;
case PMAPPROC_SET:
/*
* Set a program, version to port mapping
*/
if (!svc_getargs(xprt, xdr_pmap, &reg)) {
svcerr_decode(xprt);
return;
}
/* Be a little more restrictive here and require
that users come in using the loopback interface so
they cannot set from a remote host using a packet
with a spoofed loopback addr */
if ((Secure && xprt != ltcp_xprt && xprt != ludp_xprt) ||
(!Secure && !local)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"attempt to set"
" (prog %u vers %u port %u)"
" from host %s port %u",
reg.pm_prog, reg.pm_vers, reg.pm_port,
inet_ntoa(who->sin_addr),
ntohs(who->sin_port));
ans = 0;
goto done_set;
}
/*
* check to see if already used find_service returns
* a hit even if the versions don't match, so check
* for it
*/
fnd = find_service(reg.pm_prog, reg.pm_vers, reg.pm_prot);
if (fnd && fnd->pml_map.pm_vers == reg.pm_vers) {
if (fnd->pml_map.pm_port == reg.pm_port) {
ans = 1;
goto done_set;
} else {
/* Caller should have done UNSET first */
ans = 0;
goto done_set;
}
} else {
if ((reg.pm_port < IPPORT_RESERVED) &&
(ntohs(who->sin_port) >= IPPORT_RESERVED)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"attempt to set"
" (prog %u vers %u reserved"
" port %u) from port %u",
reg.pm_prog,
reg.pm_vers,
reg.pm_port,
ntohs(who->sin_port));
ans = 0;
goto done_set;
}
/*
* add to END of list
*/
pml = (struct pmaplist *)
malloc((u_int) sizeof (struct pmaplist));
if (pml == 0) {
perror("malloc failed in set");
svcerr_systemerr(xprt);
if (debugging)
abort();
return;
}
pml->pml_map = reg;
pml->pml_next = 0;
if (pmaplist == 0) {
pmaplist = pml;
} else {
for (fnd = pmaplist; fnd->pml_next != 0;
fnd = fnd->pml_next);
fnd->pml_next = pml;
}
ans = 1;
}
done_set:
if ((!svc_sendreply(xprt, xdr_long, (caddr_t) &ans)) &&
debugging) {
fprintf(stderr, "svc_sendreply\n");
abort();
}
break;
case PMAPPROC_UNSET:
/*
* Remove a program, version to port mapping.
*/
if (!svc_getargs(xprt, xdr_pmap, &reg)) {
svcerr_decode(xprt);
return;
}
ans = 0;
/* Be a little more restrictive here and require
that users come in using the loopback interface so
they cannot unset from a remote host using a packet
with a spoofed loopback addr */
if ((Secure && xprt != ltcp_xprt && xprt != ludp_xprt) ||
(!Secure && !local)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"attempt to unset (prog %u vers %u)"
" from host %s port %u",
reg.pm_prog, reg.pm_vers,
inet_ntoa(who->sin_addr),
ntohs(who->sin_port));
goto done_unset;
}
for (prevpml = NULL, pml = pmaplist; pml != NULL;) {
if ((pml->pml_map.pm_prog != reg.pm_prog) ||
(pml->pml_map.pm_vers != reg.pm_vers)) {
/* both pml & prevpml move forwards */
prevpml = pml;
pml = pml->pml_next;
continue;
}
if ((pml->pml_map.pm_port < IPPORT_RESERVED) &&
(ntohs(who->sin_port) >= IPPORT_RESERVED)) {
if (verbose)
syslog(LOG_INFO|LOG_AUTH,
"attempt to unset (prog %u vers %u"
" reserved port %u) port from %u",
pml->pml_map.pm_prog,
pml->pml_map.pm_vers,
pml->pml_map.pm_port,
ntohs(who->sin_port));
goto done_unset;
}
/* found it; pml moves forward, prevpml stays */
ans = 1;
t = (caddr_t) pml;
pml = pml->pml_next;
if (prevpml == NULL)
pmaplist = pml;
else
prevpml->pml_next = pml;
free(t);
}
done_unset:
if ((!svc_sendreply(xprt, xdr_long, (caddr_t) &ans)) &&
debugging) {
fprintf(stderr, "svc_sendreply\n");
abort();
}
break;
case PMAPPROC_GETPORT:
/*
* Lookup the mapping for a program, version and
* return its port
*/
if (!svc_getargs(xprt, xdr_pmap, &reg)) {
svcerr_decode(xprt);
return;
}
fnd = find_service(reg.pm_prog, reg.pm_vers, reg.pm_prot);
if (fnd)
port = fnd->pml_map.pm_port;
else
port = 0;
if ((!svc_sendreply(xprt, xdr_long, (caddr_t) &port)) &&
debugging) {
fprintf(stderr, "svc_sendreply\n");
abort();
}
break;
case PMAPPROC_DUMP:
/*
* Return the current set of mapped program, version
*/
if ((!svc_sendreply(xprt, xdr_pmaplist,
(caddr_t) &pmaplist)) && debugging) {
fprintf(stderr, "svc_sendreply\n");
abort();
}
break;
case PMAPPROC_CALLIT:
/*
* Calls a procedure on the local machine. If the requested
* procedure is not registered this procedure does not return
* error information!! This procedure is only supported on
* rpc/udp and calls via rpc/udp. It passes null
* authentication parameters.
*/
callit(rqstp, xprt);
break;
default:
svcerr_noproc(xprt);
break;
}
}
/*
* Stuff for the rmtcall service
*/
#define ARGSIZE 9000
struct encap_parms {
u_int arglen;
char *args;
};
static bool_t
xdr_encap_parms(xdrs, epp)
XDR *xdrs;
struct encap_parms *epp;
{
return (xdr_bytes(xdrs, &(epp->args), &(epp->arglen), ARGSIZE));
}
struct rmtcallargs {
u_long rmt_prog;
u_long rmt_vers;
u_long rmt_port;
u_long rmt_proc;
struct encap_parms rmt_args;
};
static bool_t
xdr_rmtcall_args(xdrs, cap)
register XDR *xdrs;
register struct rmtcallargs *cap;
{
/* does not get a port number */
if (xdr_u_long(xdrs, &(cap->rmt_prog)) &&
xdr_u_long(xdrs, &(cap->rmt_vers)) &&
xdr_u_long(xdrs, &(cap->rmt_proc))) {
return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
}
return (FALSE);
}
static bool_t
xdr_rmtcall_result(xdrs, cap)
register XDR *xdrs;
register struct rmtcallargs *cap;
{
if (xdr_u_long(xdrs, &(cap->rmt_port)))
return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
return (FALSE);
}
/*
* only worries about the struct encap_parms part of struct rmtcallargs.
* The arglen must already be set!!
*/
static bool_t
xdr_opaque_parms(xdrs, cap)
XDR *xdrs;
struct rmtcallargs *cap;
{
return (xdr_opaque(xdrs, cap->rmt_args.args, cap->rmt_args.arglen));
}
/*
* This routine finds and sets the length of incoming opaque paraters
* and then calls xdr_opaque_parms.
*/
static bool_t
xdr_len_opaque_parms(xdrs, cap)
register XDR *xdrs;
struct rmtcallargs *cap;
{
register u_int beginpos, lowpos, highpos, currpos, pos;
beginpos = lowpos = pos = xdr_getpos(xdrs);
highpos = lowpos + ARGSIZE;
while ((int)(highpos - lowpos) >= 0) {
currpos = (lowpos + highpos) / 2;
if (xdr_setpos(xdrs, currpos)) {
pos = currpos;
lowpos = currpos + 1;
} else {
highpos = currpos - 1;
}
}
xdr_setpos(xdrs, beginpos);
cap->rmt_args.arglen = pos - beginpos;
return (xdr_opaque_parms(xdrs, cap));
}
/*
* Call a remote procedure service
* This procedure is very quiet when things go wrong.
* The proc is written to support broadcast rpc. In the broadcast case,
* a machine should shut-up instead of complain, less the requestor be
* overrun with complaints at the expense of not hearing a valid reply ...
*
* This now forks so that the program & process that it calls can call
* back to the portmapper.
*/
static void
callit(struct svc_req *rqstp,
SVCXPRT *xprt)
{
struct rmtcallargs a;
struct pmaplist *pml;
u_short port;
struct sockaddr_in me;
pid_t pid;
int free_slot;
int sock = -1;
CLIENT *client;
struct authunix_parms *au = (struct authunix_parms *)rqstp->rq_clntcred;
struct timeval timeout;
char buf[ARGSIZE];
int prev_mask;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
a.rmt_args.args = buf;
if (!svc_getargs(xprt, xdr_rmtcall_args, &a))
return;
if ((pml = find_service(a.rmt_prog, a.rmt_vers, IPPROTO_UDP)) == NULL)
return;
if (debugging) {
syslog(LOG_DEBUG,
"broadcast or multicast prog %u, proc %u from %s.%u",
a.rmt_prog, a.rmt_proc,
inet_ntoa(svc_getcaller(xprt)->sin_addr),
svc_getcaller(xprt)->sin_port);
}
/*
* Fork a child to do the work. Parent immediately returns.
* Child exits upon completion.
*
* First deal with finished children. Hold off SIGCHLD
* while we are updating call_table data structure and
* potentially killing a process to avoid race with SIGCHLD
* handler.
*/
prev_mask = sigblock(sigmask(SIGCHLD));
free_slot = max_forks;
while (reap_count > 0) {
--fork_cnt;
pid = reap_table[--reap_count].pid;
for (free_slot = 0; free_slot < max_forks; free_slot++) {
if (call_table[free_slot].pid == pid) {
call_table[free_slot].pid = 0;
break;
}
}
if (free_slot >= max_forks)
syslog(LOG_DEBUG, "PID %d not found in table", pid);
}
if (fork_cnt >= max_forks) {
/*
* Too much going on already.
*
* It is too expensive for ordinary portmap calls to
* lock up for an indirect call. It is better to drop
* the new request. But it is better still to try to keep
* a bad system from locking everything up.
*
* It would be possible to zap the oldest child process,
* but that policy can result in making no progress.
* The oldest child is the one most likely to finally finish.
*/
free_slot = rand()%(max_forks+1);
/*
* Include the new request among those subject to random-drop.
*/
if (free_slot != max_forks) {
pid = call_table[free_slot].pid;
if (pid == 0) {
syslog(LOG_ERR,"PID table slot %d empty",
free_slot);
} else {
if (debugging)
syslog(LOG_DEBUG,"abort PID %d",pid);
(void)kill(pid, SIGKILL);
}
}
(void)sigsetmask(prev_mask);
return;
}
/*
* We can now unblock SIGCHLD since the race condition with
* the kill processing above has passed. Unblocking SIGCHLD
* earlier could result in a newly created process
* (with the same pid as a process in the reap_table) mistakenly
* being killed due to IRIX's reuse of pids.
*/
(void)sigsetmask(prev_mask);
if (free_slot >= max_forks) {
free_slot = 0;
for (;;) {
if (call_table[free_slot].pid == 0)
break;
if (++free_slot >= max_forks) {
syslog(LOG_ERR,
"no free PID slots; fork_cnt=%d",
fork_cnt);
return;
}
}
}
pid = fork(); /* background the request */
if (pid > 0) {
fork_cnt++;
call_table[free_slot].pid = pid;
return;
} else if (pid < 0) {
syslog(LOG_ERR, "callit() fork(): %m");
return;
}
(void)signal(SIGUSR1, SIG_IGN);
(void)signal(SIGUSR2, SIG_IGN);
port = pml->pml_map.pm_port;
get_myaddress(&me);
me.sin_port = htons(port);
client = clntudp_create(&me, a.rmt_prog, a.rmt_vers, timeout, &sock);
if (client != (CLIENT *)NULL) {
if (rqstp->rq_cred.oa_flavor == AUTH_UNIX) {
client->cl_auth = authunix_create(au->aup_machname,
au->aup_uid, au->aup_gid, au->aup_len, au->aup_gids);
if (client->cl_auth == (AUTH *)NULL)
goto bail;
}
a.rmt_port = (u_long)port;
if (clnt_call(client, a.rmt_proc, xdr_opaque_parms, &a,
xdr_len_opaque_parms, &a, timeout) == RPC_SUCCESS) {
/* cannot send from multicast socket */
if (xprt->xp_sock == mcast_sock)
xprt->xp_sock = udp_sock;
svc_sendreply(xprt, xdr_rmtcall_result, &a);
}
AUTH_DESTROY(client->cl_auth);
bail:
clnt_destroy(client);
}
exit(0);
}
static void
getlocal(void)
{
int i;
struct oknet *n;
size_t needed;
struct timeval now;
int mib[6];
struct if_msghdr *ifm;
struct ifa_msghdr *ifam, *ifam_lim, *ifam2;
struct sockaddr *sa;
/*
* Get current addresses periodically, in case interfaces change.
*/
gettimeofday(&now,0);
if (addrs_size > 0 && now.tv_sec < when_local.tv_sec+60)
return;
when_local = now;
num_local = 0;
/*
* Fetch the interface list, without too many system calls
* since we do it repeatedly.
*/
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_IFLIST;
mib[5] = 0;
for (;;) {
if ((needed = addrs_size) != 0) {
if (sysctl(mib, 6, addrs,&needed, 0, 0) >= 0)
break;
if (errno != ENOMEM && errno != EFAULT) {
perror("sysctl");
exit(1);
}
free(addrs);
needed = 0;
}
if (sysctl(mib, 6, 0, &needed, 0, 0) < 0) {
perror("sysctl-estimate");
exit(1);
}
addrs = (union addrs *)malloc(addrs_size = needed);
}
n = &addrs->a[0];
ifam_lim = (struct ifa_msghdr *)(addrs->buf + needed);
for (ifam = &addrs->ifam; ifam < ifam_lim; ifam = ifam2) {
ifam2 = (struct ifa_msghdr*)((char*)ifam + ifam->ifam_msglen);
if (ifam->ifam_type == RTM_IFINFO) {
ifm = (struct if_msghdr *)ifam;
continue;
}
if (ifam->ifam_type != RTM_NEWADDR) {
syslog(LOG_ERR, "out of sync");
abort();
}
if (!(ifm->ifm_flags & IFF_UP))
continue;
/*
* Find the interface address among the other addresses.
*/
n->mask.s_addr = 0xffffffff;
sa = (struct sockaddr *)(ifam+1);
for (i = 0;
i <= RTAX_IFA && sa < (struct sockaddr *)ifam2;
i++) {
if ((ifam->ifam_addrs & (1 << i)) == 0)
continue;
if (i == RTAX_NETMASK
&& !(ifm->ifm_flags & IFF_POINTOPOINT))
n->mask = ((struct sockaddr_in *)sa)->sin_addr;
else if (i == RTAX_IFA)
break;
#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(__uint64_t) - 1))) \
: sizeof(__uint64_t))
#ifdef _HAVE_SA_LEN
sa = (struct sockaddr *)((char*)sa
+ ROUNDUP(sa->sa_len));
#else
sa = (struct sockaddr *
)((char*)sa + ROUNDUP(_FAKE_SA_LEN_DST(sa)));
#endif
}
if (i > RTAX_IFA
#ifdef _HAVE_SA_LEN
|| sa->sa_len == 0
#endif
|| sa >= (struct sockaddr *)ifam2
|| sa->sa_family != AF_INET)
continue;
n->match = ((struct sockaddr_in *)sa)->sin_addr;
n++;
num_local++;
}
if (debugging)
fprintf(stderr, "%d local addresses detected\n",
num_local);
}
static bool_t
chklocal(struct in_addr addr)
{
int i;
struct oknet *n;
if (addr.s_addr == INADDR_LOOPBACK)
return (TRUE);
getlocal();
for (i = num_local, n = &addrs->a[0]; i != 0; i--, n++) {
if (addr.s_addr == n->match.s_addr)
return (TRUE);
}
return (FALSE);
}
/*
* Call wait3() to clean up child processes until it returns an error.
* Make sure to save and restore the value of errno so that we don't
* disturb the code which was interrupted by this signal handler.
*/
static void
reap(void)
{
int save_errno;
pid_t pid;
save_errno = errno;
while ((pid=wait3(0, WNOHANG, 0)) > 0) {
/* it shouldn't ever wrap, but just to be safe */
if (reap_count < max_forks)
reap_table[reap_count++].pid = pid;
}
errno = save_errno;
}