rpcbind.c revision 100505
1/*	$NetBSD: rpcbind.c,v 1.1 2000/06/02 23:15:42 fvdl Exp $	*/
2/*	$FreeBSD: head/usr.sbin/rpcbind/rpcbind.c 100505 2002-07-22 15:22:53Z ume $ */
3
4/*
5 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
6 * unrestricted use provided that this legend is included on all tape
7 * media and as a part of the software program in whole or part.  Users
8 * may copy or modify Sun RPC without charge, but are not authorized
9 * to license or distribute it to anyone else except as part of a product or
10 * program developed by the user.
11 *
12 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
13 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
14 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
15 *
16 * Sun RPC is provided with no support and without any obligation on the
17 * part of Sun Microsystems, Inc. to assist in its use, correction,
18 * modification or enhancement.
19 *
20 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
21 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
22 * OR ANY PART THEREOF.
23 *
24 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
25 * or profits or other special, indirect and consequential damages, even if
26 * Sun has been advised of the possibility of such damages.
27 *
28 * Sun Microsystems, Inc.
29 * 2550 Garcia Avenue
30 * Mountain View, California  94043
31 */
32/*
33 * Copyright (c) 1984 - 1991 by Sun Microsystems, Inc.
34 */
35
36/* #ident	"@(#)rpcbind.c	1.19	94/04/25 SMI" */
37
38#if 0
39#ifndef lint
40static	char sccsid[] = "@(#)rpcbind.c 1.35 89/04/21 Copyr 1984 Sun Micro";
41#endif
42#endif
43
44/*
45 * rpcbind.c
46 * Implements the program, version to address mapping for rpc.
47 *
48 */
49
50#include <sys/types.h>
51#include <sys/stat.h>
52#include <sys/errno.h>
53#include <sys/time.h>
54#include <sys/resource.h>
55#include <sys/wait.h>
56#include <sys/signal.h>
57#include <sys/socket.h>
58#include <sys/un.h>
59#include <rpc/rpc.h>
60#ifdef PORTMAP
61#include <netinet/in.h>
62#endif
63#include <netdb.h>
64#include <stdio.h>
65#include <netconfig.h>
66#include <stdlib.h>
67#include <unistd.h>
68#include <syslog.h>
69#include <err.h>
70#include <libutil.h>
71#include <pwd.h>
72#include <string.h>
73#include <errno.h>
74#include "rpcbind.h"
75
76/* Global variables */
77int debugging = 0;	/* Tell me what's going on */
78int doabort = 0;	/* When debugging, do an abort on errors */
79rpcblist_ptr list_rbl;	/* A list of version 3/4 rpcbind services */
80
81/* who to suid to if -s is given */
82#define RUN_AS  "daemon"
83
84int runasdaemon = 0;
85int insecure = 0;
86int oldstyle_local = 0;
87int verboselog = 0;
88
89char **hosts = NULL;
90int nhosts = 0;
91int on = 1;
92
93#ifdef WARMSTART
94/* Local Variable */
95static int warmstart = 0;	/* Grab a old copy of registrations */
96#endif
97
98#ifdef PORTMAP
99struct pmaplist *list_pml;	/* A list of version 2 rpcbind services */
100char *udptrans;		/* Name of UDP transport */
101char *tcptrans;		/* Name of TCP transport */
102char *udp_uaddr;	/* Universal UDP address */
103char *tcp_uaddr;	/* Universal TCP address */
104#endif
105static char servname[] = "rpcbind";
106static char superuser[] = "superuser";
107
108int main __P((int, char *[]));
109
110static int init_transport __P((struct netconfig *));
111static void rbllist_add __P((rpcprog_t, rpcvers_t, struct netconfig *,
112			     struct netbuf *));
113static void terminate __P((int));
114static void parseargs __P((int, char *[]));
115
116int
117main(int argc, char *argv[])
118{
119	struct netconfig *nconf;
120	void *nc_handle;	/* Net config handle */
121	struct rlimit rl;
122
123	parseargs(argc, argv);
124
125	getrlimit(RLIMIT_NOFILE, &rl);
126	if (rl.rlim_cur < 128) {
127		if (rl.rlim_max <= 128)
128			rl.rlim_cur = rl.rlim_max;
129		else
130			rl.rlim_cur = 128;
131		setrlimit(RLIMIT_NOFILE, &rl);
132	}
133	openlog("rpcbind", LOG_CONS, LOG_DAEMON);
134	if (geteuid()) { /* This command allowed only to root */
135		fprintf(stderr, "Sorry. You are not superuser\n");
136		exit(1);
137	}
138	nc_handle = setnetconfig(); 	/* open netconfig file */
139	if (nc_handle == NULL) {
140		syslog(LOG_ERR, "could not read /etc/netconfig");
141		exit(1);
142	}
143#ifdef PORTMAP
144	udptrans = "";
145	tcptrans = "";
146#endif
147
148	nconf = getnetconfigent("unix");
149	if (nconf == NULL) {
150		syslog(LOG_ERR, "%s: can't find local transport\n", argv[0]);
151		exit(1);
152	}
153	init_transport(nconf);
154
155	while ((nconf = getnetconfig(nc_handle))) {
156		if (nconf->nc_flag & NC_VISIBLE)
157			init_transport(nconf);
158	}
159	endnetconfig(nc_handle);
160
161	/* catch the usual termination signals for graceful exit */
162	(void) signal(SIGCHLD, reap);
163	(void) signal(SIGINT, terminate);
164	(void) signal(SIGTERM, terminate);
165	(void) signal(SIGQUIT, terminate);
166	/* ignore others that could get sent */
167	(void) signal(SIGPIPE, SIG_IGN);
168	(void) signal(SIGHUP, SIG_IGN);
169	(void) signal(SIGUSR1, SIG_IGN);
170	(void) signal(SIGUSR2, SIG_IGN);
171#ifdef WARMSTART
172	if (warmstart) {
173		read_warmstart();
174	}
175#endif
176	if (debugging) {
177		printf("rpcbind debugging enabled.");
178		if (doabort) {
179			printf("  Will abort on errors!\n");
180		} else {
181			printf("\n");
182		}
183	} else {
184		if (daemon(0, 0))
185			err(1, "fork failed");
186	}
187
188	if (runasdaemon) {
189		struct passwd *p;
190
191		if((p = getpwnam(RUN_AS)) == NULL) {
192			syslog(LOG_ERR, "cannot get uid of daemon: %m");
193			exit(1);
194		}
195		if (setuid(p->pw_uid) == -1) {
196			syslog(LOG_ERR, "setuid to daemon failed: %m");
197			exit(1);
198		}
199	}
200
201	network_init();
202
203	my_svc_run();
204	syslog(LOG_ERR, "svc_run returned unexpectedly");
205	rpcbind_abort();
206	/* NOTREACHED */
207
208	return 0;
209}
210
211/*
212 * Adds the entry into the rpcbind database.
213 * If PORTMAP, then for UDP and TCP, it adds the entries for version 2 also
214 * Returns 0 if succeeds, else fails
215 */
216static int
217init_transport(struct netconfig *nconf)
218{
219	int fd;
220	struct t_bind taddr;
221	struct addrinfo hints, *res = NULL;
222	struct __rpc_sockinfo si;
223	SVCXPRT	*my_xprt;
224	int status;	/* bound checking ? */
225	int aicode;
226	int addrlen;
227	int nhostsbak;
228	int checkbind;
229	struct sockaddr *sa;
230	u_int32_t host_addr[4];  /* IPv4 or IPv6 */
231	struct sockaddr_un sun;
232	mode_t oldmask;
233
234	if ((nconf->nc_semantics != NC_TPI_CLTS) &&
235		(nconf->nc_semantics != NC_TPI_COTS) &&
236		(nconf->nc_semantics != NC_TPI_COTS_ORD))
237		return (1);	/* not my type */
238#ifdef ND_DEBUG
239	if (debugging) {
240		int i;
241		char **s;
242
243		(void) fprintf(stderr, "%s: %ld lookup routines :\n",
244			nconf->nc_netid, nconf->nc_nlookups);
245		for (i = 0, s = nconf->nc_lookups; i < nconf->nc_nlookups;
246		     i++, s++)
247			fprintf(stderr, "[%d] - %s\n", i, *s);
248	}
249#endif
250
251	/*
252	 * XXX - using RPC library internal functions. For NC_TPI_CLTS
253	 * we call this later, for each socket we like to bind.
254	 */
255	if (nconf->nc_semantics != NC_TPI_CLTS) {
256		if ((fd = __rpc_nconf2fd(nconf)) < 0) {
257			syslog(LOG_ERR, "cannot create socket for %s", nconf->nc_netid);
258			return (1);
259		}
260	}
261
262	if (!__rpc_nconf2sockinfo(nconf, &si)) {
263		syslog(LOG_ERR, "cannot get information for %s",
264		    nconf->nc_netid);
265		return (1);
266	}
267
268	if (!strcmp(nconf->nc_netid, "unix")) {
269		memset(&sun, 0, sizeof sun);
270		sun.sun_family = AF_LOCAL;
271		unlink(_PATH_RPCBINDSOCK);
272		strcpy(sun.sun_path, _PATH_RPCBINDSOCK);
273		sun.sun_len = SUN_LEN(&sun);
274		addrlen = sizeof (struct sockaddr_un);
275		sa = (struct sockaddr *)&sun;
276	} else {
277		/* Get rpcbind's address on this transport */
278
279		memset(&hints, 0, sizeof hints);
280		hints.ai_flags = AI_PASSIVE;
281		hints.ai_family = si.si_af;
282		hints.ai_socktype = si.si_socktype;
283		hints.ai_protocol = si.si_proto;
284	}
285	if (nconf->nc_semantics == NC_TPI_CLTS) {
286		/*
287		 * If no hosts were specified, just bind to INADDR_ANY.  Otherwise
288		 * make sure 127.0.0.1 is added to the list.
289		 */
290		nhostsbak = nhosts;
291		nhostsbak++;
292		hosts = realloc(hosts, nhostsbak * sizeof(char *));
293		if (nhostsbak == 1)
294			hosts[0] = "*";
295		else {
296			if (hints.ai_family == AF_INET) {
297				hosts[nhostsbak - 1] = "127.0.0.1";
298			} else if (hints.ai_family == AF_INET6) {
299				hosts[nhostsbak - 1] = "::1";
300			} else
301				return 1;
302		}
303
304	       /*
305		* Bind to specific IPs if asked to
306		*/
307		checkbind = 1;
308		while (nhostsbak > 0) {
309			--nhostsbak;
310			/*
311			 * XXX - using RPC library internal functions.
312			 */
313			if ((fd = __rpc_nconf2fd(nconf)) < 0) {
314				syslog(LOG_ERR, "cannot create socket for %s", nconf->nc_netid);
315				return (1);
316			}
317			switch (hints.ai_family) {
318			case AF_INET:
319				if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) {
320					hints.ai_flags &= AI_NUMERICHOST;
321				} else {
322					/*
323					 * Skip if we have a AF_INET6 adress
324					 */
325					if (inet_pton(AF_INET6, hosts[nhostsbak],
326					    host_addr) == 1)
327						continue;
328				}
329				break;
330			case AF_INET6:
331				if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) {
332					hints.ai_flags &= AI_NUMERICHOST;
333				} else {
334					/*
335					 * Skip if we have a AF_INET adress
336					 */
337					if (inet_pton(AF_INET, hosts[nhostsbak],
338					    host_addr) == 1)
339						continue;
340				}
341				if (setsockopt(fd, IPPROTO_IPV6,
342                                    IPV6_V6ONLY, &on, sizeof on) < 0) {
343                                        syslog(LOG_ERR, "can't set v6-only binding for "
344                                            "udp6 socket: %m");
345					continue;
346				}
347				break;
348			default:
349				break;
350			}
351
352			/*
353			 * If no hosts were specified, just bind to INADDR_ANY
354			 */
355			if (strcmp("*", hosts[nhostsbak]) == 0)
356				hosts[nhostsbak] = NULL;
357
358			if ((aicode = getaddrinfo(hosts[nhostsbak],
359			    servname, &hints, &res)) != 0) {
360				syslog(LOG_ERR, "cannot get local address for %s: %s",
361				    nconf->nc_netid, gai_strerror(aicode));
362				continue;
363			}
364			addrlen = res->ai_addrlen;
365			sa = (struct sockaddr *)res->ai_addr;
366			oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
367			if (bind(fd, sa, addrlen) != 0) {
368				syslog(LOG_ERR, "cannot bind %s on %s: %m",
369					hosts[nhostsbak], nconf->nc_netid);
370				if (res != NULL)
371					freeaddrinfo(res);
372				continue;
373			} else
374				checkbind++;
375			(void) umask(oldmask);
376
377			/* Copy the address */
378			taddr.addr.len = taddr.addr.maxlen = addrlen;
379			taddr.addr.buf = malloc(addrlen);
380			if (taddr.addr.buf == NULL) {
381				syslog(LOG_ERR, "cannot allocate memory for %s address",
382				    nconf->nc_netid);
383				if (res != NULL)
384					freeaddrinfo(res);
385				return 1;
386			}
387			memcpy(taddr.addr.buf, sa, addrlen);
388#ifdef ND_DEBUG
389			if (debugging) {
390				/* for debugging print out our universal address */
391				char *uaddr;
392				struct netbuf nb;
393
394				nb.buf = sa;
395				nb.len = nb.maxlen = sa->sa_len;
396				uaddr = taddr2uaddr(nconf, &nb);
397				(void) fprintf(stderr, "rpcbind : my address is %s\n", uaddr);
398				(void) free(uaddr);
399			}
400#endif
401
402			if (nconf->nc_semantics != NC_TPI_CLTS)
403				listen(fd, SOMAXCONN);
404
405			my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, 0, 0);
406			if (my_xprt == (SVCXPRT *)NULL) {
407				syslog(LOG_ERR, "%s: could not create service",
408					nconf->nc_netid);
409				goto error;
410			}
411		}
412		if (!checkbind)
413			return 1;
414	} else {
415		if (strcmp(nconf->nc_netid, "unix") != 0) {
416			if ((aicode = getaddrinfo(NULL, servname, &hints, &res)) != 0) {
417				syslog(LOG_ERR, "cannot get local address for %s: %s",
418				    nconf->nc_netid, gai_strerror(aicode));
419				return 1;
420			}
421			addrlen = res->ai_addrlen;
422			sa = (struct sockaddr *)res->ai_addr;
423		}
424		oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
425		if (bind(fd, sa, addrlen) < 0) {
426			syslog(LOG_ERR, "cannot bind %s: %m", nconf->nc_netid);
427			if (res != NULL)
428				freeaddrinfo(res);
429			return 1;
430		}
431		(void) umask(oldmask);
432
433		/* Copy the address */
434		taddr.addr.len = taddr.addr.maxlen = addrlen;
435		taddr.addr.buf = malloc(addrlen);
436		if (taddr.addr.buf == NULL) {
437			syslog(LOG_ERR, "cannot allocate memory for %s address",
438			    nconf->nc_netid);
439			if (res != NULL)
440				freeaddrinfo(res);
441			return 1;
442		}
443		memcpy(taddr.addr.buf, sa, addrlen);
444#ifdef ND_DEBUG
445		if (debugging) {
446			/* for debugging print out our universal address */
447			char *uaddr;
448			struct netbuf nb;
449
450			nb.buf = sa;
451			nb.len = nb.maxlen = sa->sa_len;
452			uaddr = taddr2uaddr(nconf, &nb);
453			(void) fprintf(stderr, "rpcbind : my address is %s\n", uaddr);
454			(void) free(uaddr);
455		}
456#endif
457
458		if (nconf->nc_semantics != NC_TPI_CLTS)
459			listen(fd, SOMAXCONN);
460
461		my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, 0, 0);
462		if (my_xprt == (SVCXPRT *)NULL) {
463			syslog(LOG_ERR, "%s: could not create service",
464					nconf->nc_netid);
465			goto error;
466		}
467	}
468
469#ifdef PORTMAP
470	/*
471	 * Register both the versions for tcp/ip, udp/ip and local.
472	 */
473	if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 &&
474		(strcmp(nconf->nc_proto, NC_TCP) == 0 ||
475		strcmp(nconf->nc_proto, NC_UDP) == 0)) ||
476		strcmp(nconf->nc_netid, "unix") == 0) {
477		struct pmaplist *pml;
478
479		if (!svc_register(my_xprt, PMAPPROG, PMAPVERS,
480			pmap_service, NULL)) {
481			syslog(LOG_ERR, "could not register on %s",
482					nconf->nc_netid);
483			goto error;
484		}
485		pml = malloc(sizeof (struct pmaplist));
486		if (pml == NULL) {
487			syslog(LOG_ERR, "no memory!");
488			exit(1);
489		}
490		pml->pml_map.pm_prog = PMAPPROG;
491		pml->pml_map.pm_vers = PMAPVERS;
492		pml->pml_map.pm_port = PMAPPORT;
493		if (strcmp(nconf->nc_proto, NC_TCP) == 0) {
494			if (tcptrans[0]) {
495				syslog(LOG_ERR,
496				"cannot have more than one TCP transport");
497				goto error;
498			}
499			tcptrans = strdup(nconf->nc_netid);
500			pml->pml_map.pm_prot = IPPROTO_TCP;
501
502			/* Let's snarf the universal address */
503			/* "h1.h2.h3.h4.p1.p2" */
504			tcp_uaddr = taddr2uaddr(nconf, &taddr.addr);
505		} else if (strcmp(nconf->nc_proto, NC_UDP) == 0) {
506			if (udptrans[0]) {
507				syslog(LOG_ERR,
508				"cannot have more than one UDP transport");
509				goto error;
510			}
511			udptrans = strdup(nconf->nc_netid);
512			pml->pml_map.pm_prot = IPPROTO_UDP;
513
514			/* Let's snarf the universal address */
515			/* "h1.h2.h3.h4.p1.p2" */
516			udp_uaddr = taddr2uaddr(nconf, &taddr.addr);
517		} else if (strcmp(nconf->nc_netid, "unix") == 0)
518			pml->pml_map.pm_prot = IPPROTO_ST;
519		pml->pml_next = list_pml;
520		list_pml = pml;
521
522		/* Add version 3 information */
523		pml = malloc(sizeof (struct pmaplist));
524		if (pml == NULL) {
525			syslog(LOG_ERR, "no memory!");
526			exit(1);
527		}
528		pml->pml_map = list_pml->pml_map;
529		pml->pml_map.pm_vers = RPCBVERS;
530		pml->pml_next = list_pml;
531		list_pml = pml;
532
533		/* Add version 4 information */
534		pml = malloc (sizeof (struct pmaplist));
535		if (pml == NULL) {
536			syslog(LOG_ERR, "no memory!");
537			exit(1);
538		}
539		pml->pml_map = list_pml->pml_map;
540		pml->pml_map.pm_vers = RPCBVERS4;
541		pml->pml_next = list_pml;
542		list_pml = pml;
543
544		/* Also add version 2 stuff to rpcbind list */
545		rbllist_add(PMAPPROG, PMAPVERS, nconf, &taddr.addr);
546	}
547#endif
548
549	/* version 3 registration */
550	if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS, rpcb_service_3, NULL)) {
551		syslog(LOG_ERR, "could not register %s version 3",
552				nconf->nc_netid);
553		goto error;
554	}
555	rbllist_add(RPCBPROG, RPCBVERS, nconf, &taddr.addr);
556
557	/* version 4 registration */
558	if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS4, rpcb_service_4, NULL)) {
559		syslog(LOG_ERR, "could not register %s version 4",
560				nconf->nc_netid);
561		goto error;
562	}
563	rbllist_add(RPCBPROG, RPCBVERS4, nconf, &taddr.addr);
564
565	/* decide if bound checking works for this transport */
566	status = add_bndlist(nconf, &taddr.addr);
567#ifdef BIND_DEBUG
568	if (debugging) {
569		if (status < 0) {
570			fprintf(stderr, "Error in finding bind status for %s\n",
571				nconf->nc_netid);
572		} else if (status == 0) {
573			fprintf(stderr, "check binding for %s\n",
574				nconf->nc_netid);
575		} else if (status > 0) {
576			fprintf(stderr, "No check binding for %s\n",
577				nconf->nc_netid);
578		}
579	}
580#endif
581	/*
582	 * rmtcall only supported on CLTS transports for now.
583	 */
584	if (nconf->nc_semantics == NC_TPI_CLTS) {
585		status = create_rmtcall_fd(nconf);
586
587#ifdef BIND_DEBUG
588		if (debugging) {
589			if (status < 0) {
590				fprintf(stderr,
591				    "Could not create rmtcall fd for %s\n",
592					nconf->nc_netid);
593			} else {
594				fprintf(stderr, "rmtcall fd for %s is %d\n",
595					nconf->nc_netid, status);
596			}
597		}
598#endif
599	}
600	return (0);
601error:
602	close(fd);
603	return (1);
604}
605
606static void
607rbllist_add(rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf,
608	    struct netbuf *addr)
609{
610	rpcblist_ptr rbl;
611
612	rbl = malloc(sizeof (rpcblist));
613	if (rbl == NULL) {
614		syslog(LOG_ERR, "no memory!");
615		exit(1);
616	}
617
618	rbl->rpcb_map.r_prog = prog;
619	rbl->rpcb_map.r_vers = vers;
620	rbl->rpcb_map.r_netid = strdup(nconf->nc_netid);
621	rbl->rpcb_map.r_addr = taddr2uaddr(nconf, addr);
622	rbl->rpcb_map.r_owner = strdup(superuser);
623	rbl->rpcb_next = list_rbl;	/* Attach to global list */
624	list_rbl = rbl;
625}
626
627/*
628 * Catch the signal and die
629 */
630static void
631terminate(int dummy)
632{
633#ifdef WARMSTART
634	syslog(LOG_ERR,
635		"rpcbind terminating on signal. Restart with \"rpcbind -w\"");
636	write_warmstart();	/* Dump yourself */
637#endif
638	exit(2);
639}
640
641void
642rpcbind_abort()
643{
644#ifdef WARMSTART
645	write_warmstart();	/* Dump yourself */
646#endif
647	abort();
648}
649
650/* get command line options */
651static void
652parseargs(int argc, char *argv[])
653{
654	int c;
655
656	while ((c = getopt(argc, argv, "dwah:ilLs")) != -1) {
657		switch (c) {
658		case 'a':
659			doabort = 1;	/* when debugging, do an abort on */
660			break;		/* errors; for rpcbind developers */
661					/* only! */
662		case 'd':
663			debugging = 1;
664			break;
665		case 'h':
666			++nhosts;
667			hosts = realloc(hosts, nhosts * sizeof(char *));
668			if (hosts == NULL)
669				errx(1, "Out of memory");
670			hosts[nhosts - 1] = strdup(optarg);
671			if (hosts[nhosts - 1] == NULL)
672				errx(1, "Out of memory");
673			break;
674		case 'i':
675			insecure = 1;
676			break;
677		case 'L':
678			oldstyle_local = 1;
679			break;
680		case 'l':
681			verboselog = 1;
682			break;
683		case 's':
684			runasdaemon = 1;
685			break;
686#ifdef WARMSTART
687		case 'w':
688			warmstart = 1;
689			break;
690#endif
691		default:	/* error */
692			fprintf(stderr,	"usage: rpcbind [-Idwils]\n");
693			exit (1);
694		}
695	}
696	if (doabort && !debugging) {
697	    fprintf(stderr,
698		"-a (abort) specified without -d (debugging) -- ignored.\n");
699	    doabort = 0;
700	}
701}
702
703void
704reap(int dummy)
705{
706	int save_errno = errno;
707
708	while (wait3(NULL, WNOHANG, NULL) > 0)
709		;
710	errno = save_errno;
711}
712
713void
714toggle_verboselog(int dummy)
715{
716	verboselog = !verboselog;
717}
718