/* * 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 #include #include #include #include #include #include /* for ntohx() */ #include /* to find local addresses */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, ®)) { 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, ®)) { 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, ®)) { 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; }