yppush.c revision 1.13 1 /* $NetBSD: yppush.c,v 1.13 2000/07/04 20:27:41 matt 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 (at) ccrc.wustl.edu>
34 * date: 05-Nov-97
35 *
36 * notes: this is a full rewrite of Mats O Jansson <moj (at) 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 <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 extern char *__progname; /* from crt0.o */
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 ypi.map = argv[0];
149 if (strlen(ypi.map) > YPMAXMAP)
150 errx(1, "%s: map name too long (limit %d)", ypi.map, YPMAXMAP);
151
152 /*
153 * ensure we have a domain
154 */
155 if (ypi.ourdomain == NULL) {
156 c = yp_get_default_domain(&ypi.ourdomain);
157 if (ypi.ourdomain == NULL)
158 errx(1, "unable to get default domain: %s",
159 yperr_string(c));
160 }
161 /*
162 * verify that the domain and specified database exsists
163 *
164 * XXXCDC: this effectively prevents us from pushing from any
165 * host but the master. an alternate plan is to find the master
166 * host for a map, clear it, ask for the order number, and then
167 * send xfr requests. if that was used we would not need local
168 * file access.
169 */
170 if (chdir(YP_DB_PATH) < 0)
171 err(1, "%s", YP_DB_PATH);
172 if (chdir(ypi.ourdomain) < 0)
173 err(1, "%s/%s", YP_DB_PATH, ypi.ourdomain);
174
175 /*
176 * now open the database so we can extract "order number"
177 * (i.e. timestamp) of the map.
178 */
179 ypdb = ypdb_open(ypi.map, 0, O_RDONLY);
180 if (ypdb == NULL)
181 err(1, "ypdb_open %s/%s/%s", YP_DB_PATH, ypi.ourdomain,
182 ypi.map);
183 datum.dptr = YP_LAST_KEY;
184 datum.dsize = YP_LAST_LEN;
185 datum = ypdb_fetch(ypdb, datum);
186 if (datum.dptr == NULL)
187 errx(1,
188 "unable to fetch %s key: check database with 'makedbm -u'",
189 YP_LAST_KEY);
190 ypi.order = 0;
191 cp = datum.dptr;
192 while (cp < datum.dptr + datum.dsize) {
193 if (!isdigit(*cp))
194 errx(1,
195 "invalid order number: check database with 'makedbm -u'");
196 ypi.order = (ypi.order * 10) + *cp - '0';
197 cp++;
198 }
199 ypdb_close(ypdb);
200
201 if (verbo)
202 printf("pushing %s [order=%d] in domain %s\n", ypi.map,
203 ypi.order, ypi.ourdomain);
204
205 /*
206 * ok, we are ready to do it. first we send a clear_2 request
207 * to the local server [should be the master] to make sure it has
208 * the correct database open.
209 *
210 * XXXCDC: note that yp_bind_local exits on failure so ypserv can't
211 * be null. this makes it difficult to print a useful error message.
212 * [it will print "clntudp_create: no contact with localhost"]
213 */
214 tv.tv_sec = 10;
215 tv.tv_usec = 0;
216 ypserv = yp_bind_local(YPPROG, YPVERS);
217 retval = clnt_call(ypserv, YPPROC_CLEAR, xdr_void, 0, xdr_void, 0, tv);
218 if (retval != RPC_SUCCESS)
219 errx(1, "clnt_call CLEAR to local ypserv: %s",
220 clnt_sperrno(retval));
221 clnt_destroy(ypserv);
222
223 /*
224 * now use normal yplib functions to bind to the domain.
225 */
226 rv = yp_bind(ypi.ourdomain);
227 if (rv)
228 errx(1, "error binding to %s: %s", ypi.ourdomain,
229 yperr_string(rv));
230
231 /*
232 * find 'owner' of the map (see pushit for usage)
233 */
234 rv = yp_master(ypi.ourdomain, ypi.map, &ypi.owner);
235 if (rv)
236 errx(1, "error finding master for %s in %s: %s", ypi.map,
237 ypi.ourdomain, yperr_string(rv));
238
239 /*
240 * inform user of our progress
241 */
242 if (verbo) {
243 printf("pushing map %s in %s: order=%d, owner=%s\n", ypi.map,
244 ypi.ourdomain, ypi.order, ypi.owner);
245 printf("pushing to %s\n",
246 (targhost) ? targhost : "<all ypservs>");
247 }
248
249 /*
250 * finally, do it.
251 */
252 if (targhost) {
253 push(targhost, strlen(targhost), &ypi);
254 } else {
255
256 /*
257 * no host specified, do all hosts the master knows about via
258 * the ypservers map.
259 */
260 rv = yp_master(ypi.ourdomain, "ypservers", &master);
261 if (rv)
262 errx(1, "error finding master for ypservers in %s: %s",
263 ypi.ourdomain, yperr_string(rv));
264
265 if (verbo)
266 printf(
267 "contacting ypservers %s master on %s for list of ypservs...\n",
268 ypi.ourdomain, master);
269
270 ypserv = yp_bind_host(master, YPPROG, YPVERS, 0, 1);
271
272 ypallcb.foreach = pushit; /* callback function */
273 ypallcb.data = (char *) &ypi; /* data to pass into callback */
274
275 rv = yp_all_host(ypserv, ypi.ourdomain, "ypservers", &ypallcb);
276 if (rv)
277 errx(1, "pushing %s in %s failed: %s", ypi.map,
278 ypi.ourdomain, yperr_string(rv));
279 }
280 exit(0);
281 }
282
283 /*
284 * usage: print usage and exit
285 */
286 void
287 usage()
288 {
289 fprintf(stderr, "usage: %s [-d domain] [-h host] [-v] map\n",
290 __progname);
291 exit(1);
292 }
293
294 /*
295 * pushit: called from yp_all_host to push a specific host.
296 * the key/value pairs are from the ypservers map.
297 */
298 int
299 pushit(instatus, inkey, inkeylen, inval, invallen, indata)
300 int instatus, inkeylen, invallen;
301 char *inkey, *inval, *indata;
302 {
303 struct yppush_info *ypi = (struct yppush_info *) indata;
304
305 if (instatus != YP_TRUE) /* failure? */
306 return (instatus);
307
308 push(inkey, inkeylen, ypi); /* do it! */
309 return (0);
310 }
311
312 /*
313 * push: push a specific map on a specific host
314 */
315 void
316 push(host, hostlen, ypi)
317 char *host;
318 int hostlen;
319 struct yppush_info *ypi;
320 {
321 char target[YPMAXPEER];
322 CLIENT *ypserv;
323 SVCXPRT *transp;
324 int prog, pid, rv;
325 struct timeval tv;
326 struct ypreq_xfr req;
327
328 /*
329 * get our target host in a null terminated string
330 */
331 snprintf(target, sizeof(target), "%*.*s", hostlen, hostlen, host);
332
333 /*
334 * XXXCDC: arg! we would like to use yp_bind_host here, except that
335 * it exits on failure and we don't want to give up just because
336 * one host fails. thus, we have to do it the hard way.
337 */
338 ypserv = clnt_create(target, YPPROG, YPVERS, "tcp");
339 if (ypserv == NULL) {
340 clnt_pcreateerror(target);
341 return;
342 }
343
344 /*
345 * our XFR rpc request to the client just starts the transfer.
346 * when the client is done, it wants to call a procedure that
347 * we are serving to tell us that it is done. so we must create
348 * and register a procedure for us for it to call.
349 */
350 transp = svcudp_create(RPC_ANYSOCK);
351 if (transp == NULL) {
352 warnx("callback svcudp_create failed");
353 goto error;
354 }
355
356 /* register it with portmap */
357 for (prog = 0x40000000; prog < 0x5fffffff; prog++) {
358 if (svc_register(transp, prog, 1, yppush_xfrrespprog_1,
359 IPPROTO_UDP))
360 break;
361 }
362 if (prog >= 0x5fffffff) {
363 warnx("unable to register callback");
364 goto error;
365 }
366
367 /*
368 * now fork off a server to catch our reply
369 */
370 pid = fork();
371 if (pid == -1) {
372 svc_unregister(prog, 1); /* drop our mapping with
373 * portmap */
374 warn("fork failed");
375 goto error;
376 }
377
378 /*
379 * child process becomes the server
380 */
381 if (pid == 0) {
382 _svc_run();
383 exit(0);
384 }
385
386 /*
387 * we are the parent process: send XFR request to server.
388 * the "owner" field isn't used by ypserv (and shouldn't be, since
389 * the ypserv has no idea if we are a legitimate yppush or not).
390 * instead, the owner of the map is determined by the master value
391 * currently cached on the slave server.
392 */
393 close(transp->xp_fd); /* close child's socket, we don't need it */
394 /* don't wait for anything here, we will wait for child's exit */
395 tv.tv_sec = 0;
396 tv.tv_usec = 0;
397 req.map_parms.domain = ypi->ourdomain;
398 req.map_parms.map = ypi->map;
399 req.map_parms.owner = ypi->owner; /* NOT USED */
400 req.map_parms.ordernum = ypi->order;
401 req.transid = (u_int) pid;
402 req.proto = prog;
403 req.port = transp->xp_port;
404
405 if (verbo)
406 printf("asking host %s to transfer map (xid=%d)\n", target,
407 req.transid);
408
409 rv = clnt_call(ypserv, YPPROC_XFR, xdr_ypreq_xfr, &req,
410 xdr_void, NULL, tv); /* do it! */
411
412 if (rv != RPC_SUCCESS && rv != RPC_TIMEDOUT) {
413 warnx("unable to xfr to host %s: %s", target, clnt_sperrno(rv));
414 kill(pid, SIGTERM);
415 }
416
417 /*
418 * now wait for child to get the reply and exit
419 */
420 wait4(pid, NULL, 0, NULL);
421 svc_unregister(prog, 1);
422
423 /*
424 * ... and we are done. fall through
425 */
426
427 error:
428 if (transp)
429 svc_destroy(transp);
430 clnt_destroy(ypserv);
431 return;
432 }
433
434 /*
435 * _svc_run: this is the main loop for the RPC server that we fork off
436 * to await the reply from ypxfr.
437 */
438 void
439 _svc_run()
440 {
441 fd_set readfds;
442 struct timeval tv;
443 int rv, nfds;
444
445 nfds = sysconf(_SC_OPEN_MAX);
446 while (1) {
447
448 readfds = svc_fdset; /* structure copy from global var */
449 tv.tv_sec = 60;
450 tv.tv_usec = 0;
451
452 rv = select(nfds, &readfds, NULL, NULL, &tv);
453
454 if (rv < 0) {
455 if (errno == EINTR)
456 continue;
457 warn("_svc_run: select failed");
458 return;
459 }
460 if (rv == 0)
461 errx(0, "_svc_run: callback timed out");
462
463 /*
464 * got something
465 */
466 svc_getreqset(&readfds);
467
468 }
469 }
470