yppush.c revision 1.8 1 /* $NetBSD: yppush.c,v 1.8 1997/11/18 00:32:59 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 (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/errno.h>
41 #include <sys/param.h>
42 #include <sys/stat.h>
43 #include <sys/time.h>
44 #include <sys/wait.h>
45
46 #include <ctype.h>
47 #include <err.h>
48 #include <fcntl.h>
49 #include <signal.h>
50 #include <stdio.h>
51 #include <unistd.h>
52
53 #include <rpc/rpc.h>
54 #include <rpcsvc/yp_prot.h>
55 #include <rpcsvc/ypclnt.h>
56
57 #include "ypdb.h"
58 #include "ypdef.h"
59 #include "yplib_host.h"
60 #include "yppush.h"
61
62 /*
63 * yppush: push a new YP map out YP servers
64 *
65 * usage:
66 * yppush [-d domain] [-h host] [-v] mapname
67 *
68 * -d: the domainname the map lives in [if different from default]
69 * -h: push only to this host [otherwise, push to all hosts]
70 * -v: verbose
71 */
72
73 /*
74 * structures
75 */
76
77 struct yppush_info {
78 char *ourdomain; /* domain of interest */
79 char *map; /* map we are pushing */
80 char *owner; /* owner of map */
81 int order; /* order number of map (version) */
82 };
83 /*
84 * global vars
85 */
86
87 extern char *__progname; /* from crt0.o */
88 int verbo = 0; /* verbose */
89
90 /*
91 * prototypes
92 */
93
94 int main __P((int, char *[]));
95 int pushit __P((int, char *, int, char *, int, char *));
96 void push __P((char *, int, struct yppush_info *));
97 void _svc_run __P((void));
98 void usage __P((void));
99
100
101 /*
102 * main
103 */
104
105 int
106 main(argc, argv)
107 int argc;
108 char *argv[];
109
110 {
111 char *targhost = NULL;
112 struct yppush_info ypi = {NULL, NULL, NULL, 0};
113 int c, rv;
114 const char *cp;
115 char *master;
116 DBM *ypdb;
117 datum datum;
118 CLIENT *ypserv;
119 struct timeval tv;
120 enum clnt_stat retval;
121 struct ypall_callback ypallcb;
122
123 /*
124 * parse command line
125 */
126 while ((c = getopt(argc, argv, "d:h:v")) != -1) {
127 switch (c) {
128 case 'd':
129 ypi.ourdomain = optarg;
130 break;
131 case 'h':
132 targhost = optarg;
133 break;
134 case 'v':
135 verbo = 1;
136 break;
137 default:
138 usage();
139 /* NOTREACHED */
140 }
141 }
142 argc -= optind;
143 argv += optind;
144 if (argc != 1)
145 usage();
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, 0, O_RDONLY);
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 ypdb_close(ypdb);
185 if (datum.dptr == NULL)
186 errx(1,
187 "unable to fetch %s key: check database with 'makedbm -u'",
188 YP_LAST_KEY);
189 ypi.order = 0;
190 cp = datum.dptr;
191 while (cp < datum.dptr + datum.dsize) {
192 if (!isdigit(*cp))
193 errx(1,
194 "invalid order number: check database with 'makedbm -u'");
195 ypi.order = (ypi.order * 10) + *cp - '0';
196 cp++;
197 }
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()
286 {
287 fprintf(stderr, "usage: %s [-d domain] [-h host] [-v] map\n",
288 __progname);
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(instatus, inkey, inkeylen, inval, invallen, indata)
298 int instatus, inkeylen, invallen;
299 char *inkey, *inval, *indata;
300 {
301 struct yppush_info *ypi = (struct yppush_info *) indata;
302
303 if (instatus != YP_TRUE) /* failure? */
304 return (instatus);
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_sock); /* 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