xref: /netbsd-src/usr.sbin/ypserv/yppush/yppush.c (revision 7991f5a7b8fc83a3d55dc2a1767cca3b84103969)
1 /*	$NetBSD: yppush.c,v 1.25 2021/07/24 21:31:39 andvar Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 Charles D. Cranor
5  * 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  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  * yppush
30  * author: Chuck Cranor <chuck@netbsd>
31  * date: 05-Nov-97
32  *
33  * notes: this is a full rewrite of Mats O Jansson <moj@stacken.kth.se>'s
34  * yppush.c.   i have restructured and cleaned up the entire file.
35  */
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <sys/wait.h>
41 
42 #include <ctype.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <signal.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <unistd.h>
51 
52 #include <rpc/rpc.h>
53 #include <rpcsvc/yp_prot.h>
54 #include <rpcsvc/ypclnt.h>
55 
56 #include "ypdb.h"
57 #include "ypdef.h"
58 #include "yplib_host.h"
59 #include "yppush.h"
60 
61 /*
62  * yppush: push a new YP map out YP servers
63  *
64  * usage:
65  *   yppush [-d domain] [-h host] [-v] mapname
66  *
67  *   -d: the domainname the map lives in [if different from default]
68  *   -h: push only to this host [otherwise, push to all hosts]
69  *   -v: verbose
70  */
71 
72 /*
73  * structures
74  */
75 
76 struct yppush_info {
77 	char   *ourdomain;	/* domain of interest */
78 	char   *map;		/* map we are pushing */
79 	char   *owner;		/* owner of map */
80 	int     order;		/* order number of map (version) */
81 };
82 /*
83  * global vars
84  */
85 
86 int     verbo = 0;		/* verbose */
87 
88 /*
89  * prototypes
90  */
91 
92 static int	pushit(int, char *, int, char *, int, char *);
93 void	push(char *, int, struct yppush_info *);
94 void	_svc_run(void);
95 __dead static void	usage(void);
96 
97 
98 /*
99  * main
100  */
101 
102 int
main(int argc,char * argv[])103 main(int argc, char *argv[])
104 
105 {
106 	char   *targhost = NULL;
107 	struct yppush_info ypi = {NULL, NULL, NULL, 0};
108 	int     c, rv;
109 	const char *cp;
110 	char   *master;
111 	DBM    *ypdb;
112 	datum   dat;
113 	CLIENT *ypserv;
114 	struct timeval tv;
115 	enum clnt_stat retval;
116 	struct ypall_callback ypallcb;
117 
118 	/*
119          * parse command line
120          */
121 	while ((c = getopt(argc, argv, "d:h:v")) != -1) {
122 		switch (c) {
123 		case 'd':
124 			ypi.ourdomain = optarg;
125 			break;
126 		case 'h':
127 			targhost = optarg;
128 			break;
129 		case 'v':
130 			verbo = 1;
131 			break;
132 		default:
133 			usage();
134 			/* NOTREACHED */
135 		}
136 	}
137 	argc -= optind;
138 	argv += optind;
139 	if (argc != 1)
140 		usage();
141 	openlog("yppush", LOG_PID, LOG_DAEMON);
142 	ypi.map = argv[0];
143 	if (strlen(ypi.map) > YPMAXMAP)
144 		errx(1, "%s: map name too long (limit %d)", ypi.map, YPMAXMAP);
145 
146 	/*
147          * ensure we have a domain
148          */
149 	if (ypi.ourdomain == NULL) {
150 		c = yp_get_default_domain(&ypi.ourdomain);
151 		if (ypi.ourdomain == NULL)
152 			errx(1, "unable to get default domain: %s",
153 			    yperr_string(c));
154 	}
155 	/*
156          * verify that the domain and specified database exists
157          *
158          * XXXCDC: this effectively prevents us from pushing from any
159          * host but the master.   an alternate plan is to find the master
160          * host for a map, clear it, ask for the order number, and then
161          * send xfr requests.   if that was used we would not need local
162          * file access.
163          */
164 	if (chdir(YP_DB_PATH) < 0)
165 		err(1, "%s", YP_DB_PATH);
166 	if (chdir(ypi.ourdomain) < 0)
167 		err(1, "%s/%s", YP_DB_PATH, ypi.ourdomain);
168 
169 	/*
170          * now open the database so we can extract "order number"
171          * (i.e. timestamp) of the map.
172          */
173 	ypdb = ypdb_open(ypi.map);
174 	if (ypdb == NULL)
175 		err(1, "ypdb_open %s/%s/%s", YP_DB_PATH, ypi.ourdomain,
176 		    ypi.map);
177 	dat.dptr = YP_LAST_KEY;
178 	dat.dsize = YP_LAST_LEN;
179 	dat = ypdb_fetch(ypdb, dat);
180 	if (dat.dptr == NULL)
181 		errx(1,
182 		    "unable to fetch %s key: check database with 'makedbm -u'",
183 		    YP_LAST_KEY);
184 	ypi.order = 0;
185 	cp = dat.dptr;
186 	while (cp < dat.dptr + dat.dsize) {
187 		if (!isdigit((unsigned char)*cp))
188 			errx(1,
189 		    "invalid order number: check database with 'makedbm -u'");
190 		ypi.order = (ypi.order * 10) + *cp - '0';
191 		cp++;
192 	}
193 	ypdb_close(ypdb);
194 
195 	if (verbo)
196 		printf("pushing %s [order=%d] in domain %s\n", ypi.map,
197 		    ypi.order, ypi.ourdomain);
198 
199 	/*
200          * ok, we are ready to do it.   first we send a clear_2 request
201          * to the local server [should be the master] to make sure it has
202          * the correct database open.
203          *
204          * XXXCDC: note that yp_bind_local exits on failure so ypserv can't
205          * be null.   this makes it difficult to print a useful error message.
206          * [it will print "clntudp_create: no contact with localhost"]
207          */
208 	tv.tv_sec = 10;
209 	tv.tv_usec = 0;
210 	ypserv = yp_bind_local(YPPROG, YPVERS);
211 	retval = clnt_call(ypserv, YPPROC_CLEAR, xdr_void, 0, xdr_void, 0, tv);
212 	if (retval != RPC_SUCCESS)
213 		errx(1, "clnt_call CLEAR to local ypserv: %s",
214 		    clnt_sperrno(retval));
215 	clnt_destroy(ypserv);
216 
217 	/*
218          * now use normal yplib functions to bind to the domain.
219          */
220 	rv = yp_bind(ypi.ourdomain);
221 	if (rv)
222 		errx(1, "error binding to %s: %s", ypi.ourdomain,
223 		    yperr_string(rv));
224 
225 	/*
226          * find 'owner' of the map (see pushit for usage)
227          */
228 	rv = yp_master(ypi.ourdomain, ypi.map, &ypi.owner);
229 	if (rv)
230 		errx(1, "error finding master for %s in %s: %s", ypi.map,
231 		    ypi.ourdomain, yperr_string(rv));
232 
233 	/*
234          * inform user of our progress
235          */
236 	if (verbo) {
237 		printf("pushing map %s in %s: order=%d, owner=%s\n", ypi.map,
238 		    ypi.ourdomain, ypi.order, ypi.owner);
239 		printf("pushing to %s\n",
240 		    (targhost) ? targhost : "<all ypservs>");
241 	}
242 
243 	/*
244          * finally, do it.
245          */
246 	if (targhost) {
247 		push(targhost, strlen(targhost), &ypi);
248 	} else {
249 
250 		/*
251 	         * no host specified, do all hosts the master knows about via
252 	         * the ypservers map.
253 	         */
254 		rv = yp_master(ypi.ourdomain, "ypservers", &master);
255 		if (rv)
256 			errx(1, "error finding master for ypservers in %s: %s",
257 			    ypi.ourdomain, yperr_string(rv));
258 
259 		if (verbo)
260 			printf(
261 		"contacting ypservers %s master on %s for list of ypservs...\n",
262 			    ypi.ourdomain, master);
263 
264 		ypserv = yp_bind_host(master, YPPROG, YPVERS, 0, 1);
265 
266 		ypallcb.foreach = pushit;	/* callback function */
267 		ypallcb.data = (char *) &ypi;	/* data to pass into callback */
268 
269 		rv = yp_all_host(ypserv, ypi.ourdomain, "ypservers", &ypallcb);
270 		if (rv)
271 			errx(1, "pushing %s in %s failed: %s", ypi.map,
272 			    ypi.ourdomain, yperr_string(rv));
273 	}
274 	exit(0);
275 }
276 
277 /*
278  * usage: print usage and exit
279  */
280 static void
usage(void)281 usage(void)
282 {
283 	fprintf(stderr, "usage: %s [-d domain] [-h host] [-v] map\n",
284 	    getprogname());
285 	exit(1);
286 }
287 
288 /*
289  * pushit: called from yp_all_host to push a specific host.
290  * the key/value pairs are from the ypservers map.
291  */
292 static int
pushit(int instatus,char * inkey,int inkeylen,char * inval,int invallen,char * indata)293 pushit(int instatus, char *inkey, int inkeylen, char *inval,
294        int invallen, char *indata)
295 {
296 	struct yppush_info *ypi = (struct yppush_info *) indata;
297 
298 	push(inkey, inkeylen, ypi);		/* do it! */
299 	return (0);
300 }
301 
302 /*
303  * push: push a specific map on a specific host
304  */
305 void
push(char * host,int hostlen,struct yppush_info * ypi)306 push(char *host, int hostlen, struct yppush_info *ypi)
307 {
308 	char    target[YPMAXPEER];
309 	CLIENT *ypserv;
310 	SVCXPRT *transp;
311 	int     prog, pid, rv;
312 	struct timeval tv;
313 	struct ypreq_xfr req;
314 
315 	/*
316          * get our target host in a null terminated string
317          */
318 	snprintf(target, sizeof(target), "%*.*s", hostlen, hostlen, host);
319 
320 	/*
321          * XXXCDC: arg!  we would like to use yp_bind_host here, except that
322          * it exits on failure and we don't want to give up just because
323          * one host fails.  thus, we have to do it the hard way.
324          */
325 	ypserv = clnt_create(target, YPPROG, YPVERS, "tcp");
326 	if (ypserv == NULL) {
327 		clnt_pcreateerror(target);
328 		return;
329 	}
330 
331 	/*
332          * our XFR rpc request to the client just starts the transfer.
333          * when the client is done, it wants to call a procedure that
334          * we are serving to tell us that it is done.   so we must create
335          * and register a procedure for us for it to call.
336          */
337 	transp = svcudp_create(RPC_ANYSOCK);
338 	if (transp == NULL) {
339 		warnx("callback svcudp_create failed");
340 		goto error;
341 	}
342 
343 	/* register it with portmap */
344 	for (prog = 0x40000000; prog < 0x5fffffff; prog++) {
345 		if (svc_register(transp, prog, 1, yppush_xfrrespprog_1,
346 		    IPPROTO_UDP))
347 			break;
348 	}
349 	if (prog >= 0x5fffffff) {
350 		warnx("unable to register callback");
351 		goto error;
352 	}
353 
354 	/*
355          * now fork off a server to catch our reply
356          */
357 	pid = fork();
358 	if (pid == -1) {
359 		svc_unregister(prog, 1);	/* drop our mapping with
360 						 * portmap */
361 		warn("fork failed");
362 		goto error;
363 	}
364 
365 	/*
366          * child process becomes the server
367          */
368 	if (pid == 0) {
369 		_svc_run();
370 		exit(0);
371 	}
372 
373 	/*
374          * we are the parent process: send XFR request to server.
375          * the "owner" field isn't used by ypserv (and shouldn't be, since
376          * the ypserv has no idea if we are a legitimate yppush or not).
377          * instead, the owner of the map is determined by the master value
378          * currently cached on the slave server.
379          */
380 	close(transp->xp_fd);	/* close child's socket, we don't need it */
381 	/* don't wait for anything here, we will wait for child's exit */
382 	tv.tv_sec = 0;
383 	tv.tv_usec = 0;
384 	req.map_parms.domain = ypi->ourdomain;
385 	req.map_parms.map = ypi->map;
386 	req.map_parms.owner = ypi->owner;	/* NOT USED */
387 	req.map_parms.ordernum = ypi->order;
388 	req.transid = (u_int) pid;
389 	req.proto = prog;
390 	req.port = transp->xp_port;
391 
392 	if (verbo)
393 		printf("asking host %s to transfer map (xid=%d)\n", target,
394 		    req.transid);
395 
396 	rv = clnt_call(ypserv, YPPROC_XFR, xdr_ypreq_xfr, &req,
397 	    		xdr_void, NULL, tv);			/* do it! */
398 
399 	if (rv != RPC_SUCCESS && rv != RPC_TIMEDOUT) {
400 		warnx("unable to xfr to host %s: %s", target, clnt_sperrno(rv));
401 		kill(pid, SIGTERM);
402 	}
403 
404 	/*
405          * now wait for child to get the reply and exit
406          */
407 	wait4(pid, NULL, 0, NULL);
408 	svc_unregister(prog, 1);
409 
410 	/*
411          * ... and we are done.   fall through
412          */
413 
414 error:
415 	if (transp)
416 		svc_destroy(transp);
417 	clnt_destroy(ypserv);
418 	return;
419 }
420 
421 /*
422  * _svc_run: this is the main loop for the RPC server that we fork off
423  * to await the reply from ypxfr.
424  */
425 void
_svc_run(void)426 _svc_run(void)
427 {
428 	fd_set  readfds;
429 	struct timeval tv;
430 	int     rv, nfds;
431 
432 	nfds = sysconf(_SC_OPEN_MAX);
433 	while (1) {
434 
435 		readfds = svc_fdset;	/* structure copy from global var */
436 		tv.tv_sec = 60;
437 		tv.tv_usec = 0;
438 
439 		rv = select(nfds, &readfds, NULL, NULL, &tv);
440 
441 		if (rv < 0) {
442 			if (errno == EINTR)
443 				continue;
444 			warn("_svc_run: select failed");
445 			return;
446 		}
447 		if (rv == 0)
448 			errx(0, "_svc_run: callback timed out");
449 
450 		/*
451 	         * got something
452 	         */
453 		svc_getreqset(&readfds);
454 
455 	}
456 }
457