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