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