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