xref: /freebsd/usr.sbin/yppush/yppush_main.c (revision 6af83ee0d2941d18880b6aaa2b4facd1d30c6106)
1 /*
2  * Copyright (c) 1995
3  *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by Bill Paul.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <errno.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <unistd.h>
43 #include <sys/socket.h>
44 #include <sys/fcntl.h>
45 #include <sys/wait.h>
46 #include <sys/param.h>
47 #include <rpc/rpc.h>
48 #include <rpc/clnt.h>
49 #include <rpc/pmap_clnt.h>
50 #include <rpcsvc/yp.h>
51 #include <rpcsvc/ypclnt.h>
52 #include "ypxfr_extern.h"
53 #include "yppush_extern.h"
54 
55 char *progname = "yppush";
56 int debug = 1;
57 int _rpcpmstart = 0;
58 char *yp_dir = _PATH_YP;
59 
60 char *yppush_mapname = NULL;	/* Map to transfer. */
61 char *yppush_domain = NULL;	/* Domain in which map resides. */
62 char *yppush_master = NULL;	/* Master NIS server for said domain. */
63 int verbose = 0;		/* Toggle verbose mode. */
64 unsigned long yppush_transid = 0;
65 int yppush_timeout = 80;	/* Default timeout. */
66 int yppush_jobs = 0;		/* Number of allowed concurrent jobs. */
67 int yppush_running_jobs = 0;	/* Number of currently running jobs. */
68 int yppush_alarm_tripped = 0;
69 
70 /* Structure for holding information about a running job. */
71 struct jobs {
72 	unsigned long tid;
73 	int sock;
74 	int port;
75 	ypxfrstat stat;
76 	unsigned long prognum;
77 	char *server;
78 	char *map;
79 	int polled;
80 	struct jobs *next;
81 };
82 
83 struct jobs *yppush_joblist;	/* Linked list of running jobs. */
84 
85 /*
86  * Local error messages.
87  */
88 static const char *
89 yppusherr_string(int err)
90 {
91 	switch (err) {
92 	case YPPUSH_TIMEDOUT:
93 		return("transfer or callback timed out");
94 	case YPPUSH_YPSERV:
95 		return("failed to contact ypserv");
96 	case YPPUSH_NOHOST:
97 		return("no such host");
98 	case YPPUSH_PMAP:
99 		return("portmapper failure");
100 	default:
101 		return("unknown error code");
102 	}
103 }
104 
105 /*
106  * Report state of a job.
107  */
108 static int
109 yppush_show_status(ypxfrstat status, unsigned long tid)
110 {
111 	struct jobs *job;
112 
113 	job = yppush_joblist;
114 
115 	while (job) {
116 		if (job->tid == tid)
117 			break;
118 		job = job->next;
119 	}
120 
121 	if (job->polled) {
122 		return(0);
123 	}
124 
125 	if (verbose > 1)
126 		yp_error("checking return status: transaction ID: %lu",
127 								job->tid);
128 	if (status != YPPUSH_SUCC || verbose) {
129 		yp_error("transfer of map %s to server %s %s",
130 		 	job->map, job->server, status == YPPUSH_SUCC ?
131 		 	"succeeded" : "failed");
132 		yp_error("status returned by ypxfr: %s", status > YPPUSH_AGE ?
133 			yppusherr_string(status) :
134 			ypxfrerr_string(status));
135 	}
136 
137 	job->polled = 1;
138 
139 	svc_unregister(job->prognum, 1);
140 
141 	yppush_running_jobs--;
142 	return(0);
143 }
144 
145 /* Exit routine. */
146 static void
147 yppush_exit(int now)
148 {
149 	struct jobs *jptr;
150 	int still_pending = 1;
151 
152 	/* Let all the information trickle in. */
153 	while (!now && still_pending) {
154 		jptr = yppush_joblist;
155 		still_pending = 0;
156 		while (jptr) {
157 			if (jptr->polled == 0) {
158 				still_pending++;
159 				if (verbose > 1)
160 					yp_error("%s has not responded",
161 						  jptr->server);
162 			} else {
163 				if (verbose > 1)
164 					yp_error("%s has responded",
165 						  jptr->server);
166 			}
167 			jptr = jptr->next;
168 		}
169 		if (still_pending) {
170 			if (verbose > 1)
171 				yp_error("%d transfer%sstill pending",
172 					still_pending,
173 					still_pending > 1 ? "s " : " ");
174 			yppush_alarm_tripped = 0;
175 			alarm(YPPUSH_RESPONSE_TIMEOUT);
176 			pause();
177 			alarm(0);
178 			if (yppush_alarm_tripped == 1) {
179 				yp_error("timed out");
180 				now = 1;
181 			}
182 		} else {
183 			if (verbose)
184 				yp_error("all transfers complete");
185 			break;
186 		}
187 	}
188 
189 
190 	/* All stats collected and reported -- kill all the stragglers. */
191 	jptr = yppush_joblist;
192 	while (jptr) {
193 		if (!jptr->polled)
194 			yp_error("warning: exiting with transfer \
195 to %s (transid = %lu) still pending", jptr->server, jptr->tid);
196 		svc_unregister(jptr->prognum, 1);
197 		jptr = jptr->next;
198 	}
199 
200 	exit(0);
201 }
202 
203 /*
204  * Handler for 'normal' signals.
205  */
206 
207 static void
208 handler(int sig)
209 {
210 	if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) {
211 		yppush_jobs = 0;
212 		yppush_exit(1);
213 	}
214 
215 	if (sig == SIGALRM) {
216 		alarm(0);
217 		yppush_alarm_tripped++;
218 	}
219 
220 	return;
221 }
222 
223 /*
224  * Dispatch loop for callback RPC services.
225  */
226 static void
227 yppush_svc_run(void)
228 {
229 #ifdef FD_SETSIZE
230 	fd_set readfds;
231 #else
232 	int readfds;
233 #endif /* def FD_SETSIZE */
234 	struct timeval timeout;
235 
236 	timeout.tv_usec = 0;
237 	timeout.tv_sec = 5;
238 
239 retry:
240 #ifdef FD_SETSIZE
241 	readfds = svc_fdset;
242 #else
243 	readfds = svc_fds;
244 #endif /* def FD_SETSIZE */
245 	switch (select(_rpc_dtablesize(), &readfds, NULL, NULL, &timeout)) {
246 	case -1:
247 		if (errno == EINTR)
248 			goto retry;
249 		yp_error("select failed: %s", strerror(errno));
250 		break;
251 	case 0:
252 		yp_error("select() timed out");
253 		break;
254 	default:
255 		svc_getreqset(&readfds);
256 		break;
257 	}
258 	return;
259 }
260 
261 /*
262  * Special handler for asynchronous socket I/O. We mark the
263  * sockets of the callback handlers as O_ASYNC and handle SIGIO
264  * events here, which will occur when the callback handler has
265  * something interesting to tell us.
266  */
267 static void
268 async_handler(int sig)
269 {
270 	yppush_svc_run();
271 
272 	/* reset any pending alarms. */
273 	alarm(0);
274 	yppush_alarm_tripped++;
275 	kill(getpid(), SIGALRM);
276 	return;
277 }
278 
279 /*
280  * RPC service routines for callbacks.
281  */
282 void *
283 yppushproc_null_1_svc(void *argp, struct svc_req *rqstp)
284 {
285 	static char * result;
286 	/* Do nothing -- RPC conventions call for all a null proc. */
287 	return((void *) &result);
288 }
289 
290 void *
291 yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp)
292 {
293 	static char * result;
294 	yppush_show_status(argp->status, argp->transid);
295 	return((void *) &result);
296 }
297 
298 /*
299  * Transmit a YPPROC_XFR request to ypserv.
300  */
301 static int
302 yppush_send_xfr(struct jobs *job)
303 {
304 	ypreq_xfr req;
305 /*	ypresp_xfr *resp; */
306 	DBT key, data;
307 	CLIENT *clnt;
308 	struct rpc_err err;
309 	struct timeval timeout;
310 
311 	timeout.tv_usec = 0;
312 	timeout.tv_sec = 0;
313 
314 	/*
315 	 * The ypreq_xfr structure has a member of type map_parms,
316 	 * which seems to require the order number of the map.
317 	 * It isn't actually used at the other end (at least the
318 	 * FreeBSD ypserv doesn't use it) but we fill it in here
319 	 * for the sake of completeness.
320 	 */
321 	key.data = "YP_LAST_MODIFIED";
322 	key.size = sizeof ("YP_LAST_MODIFIED") - 1;
323 
324 	if (yp_get_record(yppush_domain, yppush_mapname, &key, &data,
325 			  1) != YP_TRUE) {
326 		yp_error("failed to read order number from %s: %s: %s",
327 			  yppush_mapname, yperr_string(yp_errno),
328 			  strerror(errno));
329 		return(1);
330 	}
331 
332 	/* Fill in the request arguments */
333 	req.map_parms.ordernum = atoi(data.data);
334 	req.map_parms.domain = yppush_domain;
335 	req.map_parms.peer = yppush_master;
336 	req.map_parms.map = job->map;
337 	req.transid = job->tid;
338 	req.prog = job->prognum;
339 	req.port = job->port;
340 
341 	/* Get a handle to the remote ypserv. */
342 	if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) {
343 		yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \
344 create udp handle to NIS server"));
345 		switch (rpc_createerr.cf_stat) {
346 			case RPC_UNKNOWNHOST:
347 				job->stat = YPPUSH_NOHOST;
348 				break;
349 			case RPC_PMAPFAILURE:
350 				job->stat = YPPUSH_PMAP;
351 				break;
352 			default:
353 				job->stat = YPPUSH_RPC;
354 				break;
355 			}
356 		return(1);
357 	}
358 
359 	/*
360 	 * Reduce timeout to nothing since we may not
361 	 * get a response from ypserv and we don't want to block.
362 	 */
363 	if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE)
364 		yp_error("failed to set timeout on ypproc_xfr call");
365 
366 	/* Invoke the ypproc_xfr service. */
367 	if (ypproc_xfr_2(&req, clnt) == NULL) {
368 		clnt_geterr(clnt, &err);
369 		if (err.re_status != RPC_SUCCESS &&
370 		    err.re_status != RPC_TIMEDOUT) {
371 			yp_error("%s: %s", job->server, clnt_sperror(clnt,
372 							"yp_xfr failed"));
373 			job->stat = YPPUSH_YPSERV;
374 			clnt_destroy(clnt);
375 			return(1);
376 		}
377 	}
378 
379 	clnt_destroy(clnt);
380 
381 	return(0);
382 }
383 
384 /*
385  * Main driver function. Register the callback service, add the transfer
386  * request to the internal list, send the YPPROC_XFR request to ypserv
387  * do other magic things.
388  */
389 int
390 yp_push(char *server, char *map, unsigned long tid)
391 {
392 	unsigned long prognum;
393 	int sock = RPC_ANYSOCK;
394 	SVCXPRT *xprt;
395 	struct jobs *job;
396 
397 	/*
398 	 * Register the callback service on the first free
399 	 * transient program number.
400 	 */
401 	xprt = svcudp_create(sock);
402 	for (prognum = 0x40000000; prognum < 0x5FFFFFFF; prognum++) {
403 		if (svc_register(xprt, prognum, 1,
404 		    yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE)
405 			break;
406 	}
407 
408 	/* Register the job in our linked list of jobs. */
409 	if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) {
410 		yp_error("malloc failed");
411 		yppush_exit(1);
412 	}
413 
414 	/* Initialize the info for this job. */
415 	job->stat = 0;
416 	job->tid = tid;
417 	job->port = xprt->xp_port;
418 	job->sock = xprt->xp_fd; /*XXX: Evil!! EEEEEEEVIL!!! */
419 	job->server = strdup(server);
420 	job->map = strdup(map);
421 	job->prognum = prognum;
422 	job->polled = 0;
423 	job->next = yppush_joblist;
424 	yppush_joblist = job;
425 
426 	/*
427 	 * Set the RPC sockets to asynchronous mode. This will
428 	 * cause the system to smack us with a SIGIO when an RPC
429 	 * callback is delivered. This in turn allows us to handle
430 	 * the callback even though we may be in the middle of doing
431 	 * something else at the time.
432 	 *
433 	 * XXX This is a horrible thing to do for two reasons,
434 	 * both of which have to do with portability:
435 	 * 1) We really ought not to be sticking our grubby mits
436 	 *    into the RPC service transport handle like this.
437 	 * 2) Even in this day and age, there are still some *NIXes
438 	 *    that don't support async socket I/O.
439 	 */
440 	if (fcntl(xprt->xp_fd, F_SETOWN, getpid()) == -1 ||
441 	    fcntl(xprt->xp_fd, F_SETFL, O_ASYNC) == -1) {
442 		yp_error("failed to set async I/O mode: %s",
443 			 strerror(errno));
444 		yppush_exit(1);
445 	}
446 
447 	if (verbose) {
448 		yp_error("initiating transfer: %s -> %s (transid = %lu)",
449 			yppush_mapname, server, tid);
450 	}
451 
452 	/*
453 	 * Send the XFR request to ypserv. We don't have to wait for
454 	 * a response here since we can handle them asynchronously.
455 	 */
456 
457 	if (yppush_send_xfr(job)){
458 		/* Transfer request blew up. */
459 		yppush_show_status(job->stat ? job->stat :
460 			YPPUSH_YPSERV,job->tid);
461 	} else {
462 		if (verbose > 1)
463 			yp_error("%s has been called", server);
464 	}
465 
466 	return(0);
467 }
468 
469 /*
470  * Called for each entry in the ypservers map from yp_get_map(), which
471  * is our private yp_all() routine.
472  */
473 int
474 yppush_foreach(int status, char *key, int keylen, char *val, int vallen,
475     char *data)
476 {
477 	char server[YPMAXRECORD + 2];
478 
479 	if (status != YP_TRUE)
480 		return (status);
481 
482 	snprintf(server, sizeof(server), "%.*s", vallen, val);
483 
484 	/*
485 	 * Restrict the number of concurrent jobs. If yppush_jobs number
486 	 * of jobs have already been dispatched and are still pending,
487 	 * wait for one of them to finish so we can reuse its slot.
488 	 */
489 	if (yppush_jobs <= 1) {
490 		yppush_alarm_tripped = 0;
491 		while (!yppush_alarm_tripped && yppush_running_jobs) {
492 			alarm(yppush_timeout);
493 			yppush_alarm_tripped = 0;
494 			pause();
495 			alarm(0);
496 		}
497 	} else {
498 		yppush_alarm_tripped = 0;
499 		while (!yppush_alarm_tripped && yppush_running_jobs >= yppush_jobs) {
500 			alarm(yppush_timeout);
501 			yppush_alarm_tripped = 0;
502 			pause();
503 			alarm(0);
504 		}
505 	}
506 
507 	/* Cleared for takeoff: set everything in motion. */
508 	if (yp_push(server, yppush_mapname, yppush_transid))
509 		return(yp_errno);
510 
511 	/* Bump the job counter and transaction ID. */
512 	yppush_running_jobs++;
513 	yppush_transid++;
514 	return (0);
515 }
516 
517 static void usage()
518 {
519 	fprintf (stderr, "%s\n%s\n",
520 	"usage: yppush [-d domain] [-t timeout] [-j #parallel jobs] [-h host]",
521 	"              [-p path] mapname");
522 	exit(1);
523 }
524 
525 /*
526  * Entry point. (About time!)
527  */
528 int
529 main(int argc, char *argv[])
530 {
531 	int ch;
532 	DBT key, data;
533 	char myname[MAXHOSTNAMELEN];
534 	struct hostlist {
535 		char *name;
536 		struct hostlist *next;
537 	};
538 	struct hostlist *yppush_hostlist = NULL;
539 	struct hostlist *tmp;
540 	struct sigaction sa;
541 
542 	while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) {
543 		switch (ch) {
544 		case 'd':
545 			yppush_domain = optarg;
546 			break;
547 		case 'j':
548 			yppush_jobs = atoi(optarg);
549 			if (yppush_jobs <= 0)
550 				yppush_jobs = 1;
551 			break;
552 		case 'p':
553 			yp_dir = optarg;
554 			break;
555 		case 'h': /* we can handle multiple hosts */
556 			if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) {
557 				yp_error("malloc failed");
558 				yppush_exit(1);
559 			}
560 			tmp->name = strdup(optarg);
561 			tmp->next = yppush_hostlist;
562 			yppush_hostlist = tmp;
563 			break;
564 		case 't':
565 			yppush_timeout = atoi(optarg);
566 			break;
567 		case 'v':
568 			verbose++;
569 			break;
570 		default:
571 			usage();
572 			break;
573 		}
574 	}
575 
576 	argc -= optind;
577 	argv += optind;
578 
579 	yppush_mapname = argv[0];
580 
581 	if (yppush_mapname == NULL) {
582 	/* "No guts, no glory." */
583 		usage();
584 	}
585 
586 	/*
587 	 * If no domain was specified, try to find the default
588 	 * domain. If we can't find that, we're doomed and must bail.
589 	 */
590 	if (yppush_domain == NULL) {
591 		char *yppush_check_domain;
592 		if (!yp_get_default_domain(&yppush_check_domain) &&
593 			!_yp_check(&yppush_check_domain)) {
594 			yp_error("no domain specified and NIS not running");
595 			usage();
596 		} else
597 			yp_get_default_domain(&yppush_domain);
598 	}
599 
600 	/* Check to see that we are the master for this map. */
601 
602 	if (gethostname ((char *)&myname, sizeof(myname))) {
603 		yp_error("failed to get name of local host: %s",
604 			strerror(errno));
605 		yppush_exit(1);
606 	}
607 
608 	key.data = "YP_MASTER_NAME";
609 	key.size = sizeof("YP_MASTER_NAME") - 1;
610 
611 	if (yp_get_record(yppush_domain, yppush_mapname,
612 			  &key, &data, 1) != YP_TRUE) {
613 		yp_error("couldn't open %s map: %s", yppush_mapname,
614 			 strerror(errno));
615 		yppush_exit(1);
616 	}
617 
618 	if (strncmp(myname, data.data, data.size)) {
619 		yp_error("warning: this host is not the master for %s",
620 							yppush_mapname);
621 #ifdef NITPICKY
622 		yppush_exit(1);
623 #endif
624 	}
625 
626 	yppush_master = malloc(data.size + 1);
627 	strncpy(yppush_master, data.data, data.size);
628 	yppush_master[data.size] = '\0';
629 
630 	/* Install some handy handlers. */
631 	signal(SIGALRM, handler);
632 	signal(SIGTERM, handler);
633 	signal(SIGINT, handler);
634 	signal(SIGABRT, handler);
635 
636 	/*
637 	 * Set up the SIGIO handler. Make sure that some of the
638 	 * other signals are blocked while the handler is running so
639 	 * select() doesn't get interrupted.
640 	 */
641 	sigemptyset(&sa.sa_mask);
642 	sigaddset(&sa.sa_mask, SIGIO); /* Goes without saying. */
643 	sigaddset(&sa.sa_mask, SIGPIPE);
644 	sigaddset(&sa.sa_mask, SIGCHLD);
645 	sigaddset(&sa.sa_mask, SIGALRM);
646 	sigaddset(&sa.sa_mask, SIGINT);
647 	sa.sa_handler = async_handler;
648 	sa.sa_flags = 0;
649 
650 	sigaction(SIGIO, &sa, NULL);
651 
652 	/* set initial transaction ID */
653 	yppush_transid = time((time_t *)NULL);
654 
655 	if (yppush_hostlist) {
656 	/*
657 	 * Host list was specified on the command line:
658 	 * kick off the transfers by hand.
659 	 */
660 		tmp = yppush_hostlist;
661 		while (tmp) {
662 			yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
663 			    strlen(tmp->name), NULL);
664 			tmp = tmp->next;
665 		}
666 	} else {
667 	/*
668 	 * Do a yp_all() on the ypservers map and initiate a ypxfr
669 	 * for each one.
670 	 */
671 		ypxfr_get_map("ypservers", yppush_domain,
672 			      "localhost", yppush_foreach);
673 	}
674 
675 	if (verbose > 1)
676 		yp_error("all jobs dispatched");
677 
678 	/* All done -- normal exit. */
679 	yppush_exit(0);
680 
681 	/* Just in case. */
682 	exit(0);
683 }
684