yppush_main.c revision 144950
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 144950 2005-04-12 15:02:57Z thomas $");
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
55char *progname = "yppush";
56int debug = 1;
57int _rpcpmstart = 0;
58char *yp_dir = _PATH_YP;
59
60char *yppush_mapname = NULL;	/* Map to transfer. */
61char *yppush_domain = NULL;	/* Domain in which map resides. */
62char *yppush_master = NULL;	/* Master NIS server for said domain. */
63int verbose = 0;		/* Toggle verbose mode. */
64unsigned long yppush_transid = 0;
65int yppush_timeout = 80;	/* Default timeout. */
66int yppush_jobs = 0;		/* Number of allowed concurrent jobs. */
67int yppush_running_jobs = 0;	/* Number of currently running jobs. */
68int yppush_alarm_tripped = 0;
69
70/* Structure for holding information about a running job. */
71struct 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
83struct jobs *yppush_joblist;	/* Linked list of running jobs. */
84
85/*
86 * Local error messages.
87 */
88static const char *
89yppusherr_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 */
108static int
109yppush_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. */
146static void
147yppush_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 \
195to %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
207static void
208handler(int sig)
209{
210	if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) {
211		yppush_joblist = NULL;
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 */
226static void
227yppush_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
239retry:
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 */
267static void
268async_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 */
282void *
283yppushproc_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
290void *
291yppushproc_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 */
301static int
302yppush_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 \
344create 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 */
389int
390yp_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 */
473int
474yppush_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
517static 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 */
528int
529main(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