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