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