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