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