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
58char *progname = "yppush";
59int debug = 1;
60int _rpcpmstart = 0;
61char *yp_dir = _PATH_YP;
62
63static char *yppush_mapname = NULL;	/* Map to transfer. */
64static char *yppush_domain = NULL;	/* Domain in which map resides. */
65static char *yppush_master = NULL;	/* Master NIS server for said domain. */
66static int skip_master = 0;		/* Do not attempt to push map to master. */
67static int verbose = 0;		/* Toggle verbose mode. */
68static unsigned long yppush_transid = 0;
69static int yppush_timeout = 80;	/* Default timeout. */
70static int yppush_jobs = 1;		/* Number of allowed concurrent jobs. */
71static int yppush_running_jobs = 0;	/* Number of currently running jobs. */
72
73/* Structure for holding information about a running job. */
74struct 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
85static struct jobs *yppush_joblist;	/* Linked list of running jobs. */
86static int yppush_svc_run(int);
87
88/*
89 * Local error messages.
90 */
91static const char *
92yppusherr_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 */
111static int
112yppush_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. */
159static void
160yppush_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 \
204to %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
216static void
217handler(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 */
230static int
231yppush_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
240retry:
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 */
262void *
263yppushproc_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
270void *
271yppushproc_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 */
281static int
282yppush_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 \
324create 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 */
369static int
370yp_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 */
437static int
438yppush_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
482static void
483usage()
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 */
494int
495main(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