1 /* 2 * Copyright (c) 1995 3 * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Bill Paul. 16 * 4. Neither the name of the author nor the names of any co-contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #ifndef lint 34 static const char rcsid[] = 35 "$Id: yppush_main.c,v 1.9 1997/11/03 07:53:44 charnier Exp $"; 36 #endif /* not lint */ 37 38 #include <errno.h> 39 #include <signal.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <time.h> 44 #include <unistd.h> 45 #include <sys/socket.h> 46 #include <sys/fcntl.h> 47 #include <sys/wait.h> 48 #include <sys/param.h> 49 #include <rpc/rpc.h> 50 #include <rpc/clnt.h> 51 #include <rpc/pmap_clnt.h> 52 #include <rpcsvc/yp.h> 53 struct dom_binding {}; 54 #include <rpcsvc/ypclnt.h> 55 #include "ypxfr_extern.h" 56 #include "yppush_extern.h" 57 58 char *progname = "yppush"; 59 int debug = 1; 60 int _rpcpmstart = 0; 61 char *yp_dir = _PATH_YP; 62 63 char *yppush_mapname = NULL; /* Map to transfer. */ 64 char *yppush_domain = NULL; /* Domain in which map resides. */ 65 char *yppush_master = NULL; /* Master NIS server for said domain. */ 66 int verbose = 0; /* Toggle verbose mode. */ 67 unsigned long yppush_transid = 0; 68 int yppush_timeout = 80; /* Default timeout. */ 69 int yppush_jobs = 0; /* Number of allowed concurrent jobs. */ 70 int yppush_running_jobs = 0; /* Number of currently running jobs. */ 71 int yppush_alarm_tripped = 0; 72 73 /* Structure for holding information about a running job. */ 74 struct jobs { 75 unsigned long tid; 76 int sock; 77 int port; 78 ypxfrstat stat; 79 unsigned long prognum; 80 char *server; 81 char *map; 82 int polled; 83 struct jobs *next; 84 }; 85 86 struct jobs *yppush_joblist; /* Linked list of running jobs. */ 87 88 /* 89 * Local error messages. 90 */ 91 static char *yppusherr_string(err) 92 int err; 93 { 94 switch(err) { 95 case YPPUSH_TIMEDOUT: return("transfer or callback timed out"); 96 case YPPUSH_YPSERV: return("failed to contact ypserv"); 97 case YPPUSH_NOHOST: return("no such host"); 98 case YPPUSH_PMAP: return("portmapper failure"); 99 default: return("unknown error code"); 100 } 101 } 102 103 /* 104 * Report state of a job. 105 */ 106 static int yppush_show_status(status, tid) 107 ypxfrstat status; 108 unsigned long tid; 109 { 110 struct jobs *job; 111 112 job = yppush_joblist; 113 114 while(job) { 115 if (job->tid == tid) 116 break; 117 job = job->next; 118 } 119 120 if (job->polled) { 121 return(0); 122 } 123 124 if (verbose > 1) 125 yp_error("checking return status: transaction ID: %lu", 126 job->tid); 127 if (status != YPPUSH_SUCC || verbose) { 128 yp_error("transfer of map %s to server %s %s", 129 job->map, job->server, status == YPPUSH_SUCC ? 130 "succeeded" : "failed"); 131 yp_error("status returned by ypxfr: %s", status > YPPUSH_AGE ? 132 yppusherr_string(status) : 133 ypxfrerr_string(status)); 134 } 135 136 job->polled = 1; 137 138 svc_unregister(job->prognum, 1); 139 140 yppush_running_jobs--; 141 return(0); 142 } 143 144 /* Exit routine. */ 145 static void yppush_exit(now) 146 int now; 147 { 148 struct jobs *jptr; 149 int still_pending = 1; 150 151 /* Let all the information trickle in. */ 152 while(!now && still_pending) { 153 jptr = yppush_joblist; 154 still_pending = 0; 155 while (jptr) { 156 if (jptr->polled == 0) { 157 still_pending++; 158 if (verbose > 1) 159 yp_error("%s has not responded", 160 jptr->server); 161 } else { 162 if (verbose > 1) 163 yp_error("%s has responded", 164 jptr->server); 165 } 166 jptr = jptr->next; 167 } 168 if (still_pending) { 169 if (verbose > 1) 170 yp_error("%d transfer%sstill pending", 171 still_pending, 172 still_pending > 1 ? "s " : " "); 173 yppush_alarm_tripped = 0; 174 alarm(YPPUSH_RESPONSE_TIMEOUT); 175 pause(); 176 alarm(0); 177 if (yppush_alarm_tripped == 1) { 178 yp_error("timed out"); 179 now = 1; 180 } 181 } else { 182 if (verbose) 183 yp_error("all transfers complete"); 184 break; 185 } 186 } 187 188 189 /* All stats collected and reported -- kill all the stragglers. */ 190 jptr = yppush_joblist; 191 while(jptr) { 192 if (!jptr->polled) 193 yp_error("warning: exiting with transfer \ 194 to %s (transid = %lu) still pending", jptr->server, jptr->tid); 195 svc_unregister(jptr->prognum, 1); 196 jptr = jptr->next; 197 } 198 199 exit(0); 200 } 201 202 /* 203 * Handler for 'normal' signals. 204 */ 205 206 static void handler(sig) 207 int sig; 208 { 209 if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) { 210 yppush_jobs = 0; 211 yppush_exit(1); 212 } 213 214 if (sig == SIGALRM) { 215 alarm(0); 216 yppush_alarm_tripped++; 217 } 218 219 return; 220 } 221 222 /* 223 * Dispatch loop for callback RPC services. 224 */ 225 static void yppush_svc_run() 226 { 227 #ifdef FD_SETSIZE 228 fd_set readfds; 229 #else 230 int readfds; 231 #endif /* def FD_SETSIZE */ 232 struct timeval timeout; 233 234 timeout.tv_usec = 0; 235 timeout.tv_sec = 5; 236 237 retry: 238 #ifdef FD_SETSIZE 239 readfds = svc_fdset; 240 #else 241 readfds = svc_fds; 242 #endif /* def FD_SETSIZE */ 243 switch (select(_rpc_dtablesize(), &readfds, NULL, NULL, &timeout)) { 244 case -1: 245 if (errno == EINTR) 246 goto retry; 247 yp_error("select failed: %s", strerror(errno)); 248 break; 249 case 0: 250 yp_error("select() timed out"); 251 break; 252 default: 253 svc_getreqset(&readfds); 254 break; 255 } 256 return; 257 } 258 259 /* 260 * Special handler for asynchronous socket I/O. We mark the 261 * sockets of the callback handlers as O_ASYNC and handle SIGIO 262 * events here, which will occur when the callback handler has 263 * something interesting to tell us. 264 */ 265 static void async_handler(sig) 266 int sig; 267 { 268 yppush_svc_run(); 269 270 /* reset any pending alarms. */ 271 alarm(0); 272 yppush_alarm_tripped++; 273 kill(getpid(), SIGALRM); 274 return; 275 } 276 277 /* 278 * RPC service routines for callbacks. 279 */ 280 void * 281 yppushproc_null_1_svc(void *argp, struct svc_req *rqstp) 282 { 283 static char * result; 284 /* Do nothing -- RPC conventions call for all a null proc. */ 285 return((void *) &result); 286 } 287 288 void * 289 yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp) 290 { 291 static char * result; 292 yppush_show_status(argp->status, argp->transid); 293 return((void *) &result); 294 } 295 296 /* 297 * Transmit a YPPROC_XFR request to ypserv. 298 */ 299 static int yppush_send_xfr(job) 300 struct jobs *job; 301 { 302 ypreq_xfr req; 303 /* ypresp_xfr *resp; */ 304 DBT key, data; 305 CLIENT *clnt; 306 struct rpc_err err; 307 struct timeval timeout; 308 309 timeout.tv_usec = 0; 310 timeout.tv_sec = 0; 311 312 /* 313 * The ypreq_xfr structure has a member of type map_parms, 314 * which seems to require the order number of the map. 315 * It isn't actually used at the other end (at least the 316 * FreeBSD ypserv doesn't use it) but we fill it in here 317 * for the sake of completeness. 318 */ 319 key.data = "YP_LAST_MODIFIED"; 320 key.size = sizeof ("YP_LAST_MODIFIED") - 1; 321 322 if (yp_get_record(yppush_domain, yppush_mapname, &key, &data, 323 1) != YP_TRUE) { 324 yp_error("failed to read order number from %s: %s: %s", 325 yppush_mapname, yperr_string(yp_errno), 326 strerror(errno)); 327 return(1); 328 } 329 330 /* Fill in the request arguments */ 331 req.map_parms.ordernum = atoi(data.data); 332 req.map_parms.domain = yppush_domain; 333 req.map_parms.peer = yppush_master; 334 req.map_parms.map = job->map; 335 req.transid = job->tid; 336 req.prog = job->prognum; 337 req.port = job->port; 338 339 /* Get a handle to the remote ypserv. */ 340 if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) { 341 yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \ 342 create udp handle to NIS server")); 343 switch(rpc_createerr.cf_stat) { 344 case RPC_UNKNOWNHOST: 345 job->stat = YPPUSH_NOHOST; 346 break; 347 case RPC_PMAPFAILURE: 348 job->stat = YPPUSH_PMAP; 349 break; 350 default: 351 job->stat = YPPUSH_RPC; 352 break; 353 } 354 return(1); 355 } 356 357 /* 358 * Reduce timeout to nothing since we may not 359 * get a response from ypserv and we don't want to block. 360 */ 361 if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE) 362 yp_error("failed to set timeout on ypproc_xfr call"); 363 364 /* Invoke the ypproc_xfr service. */ 365 if (ypproc_xfr_2(&req, clnt) == NULL) { 366 clnt_geterr(clnt, &err); 367 if (err.re_status != RPC_SUCCESS && 368 err.re_status != RPC_TIMEDOUT) { 369 yp_error("%s: %s", job->server, clnt_sperror(clnt, 370 "yp_xfr failed")); 371 job->stat = YPPUSH_YPSERV; 372 clnt_destroy(clnt); 373 return(1); 374 } 375 } 376 377 clnt_destroy(clnt); 378 379 return(0); 380 } 381 382 /* 383 * Main driver function. Register the callback service, add the transfer 384 * request to the internal list, send the YPPROC_XFR request to ypserv 385 * do other magic things. 386 */ 387 int yp_push(server, map, tid) 388 char *server; 389 char *map; 390 unsigned long tid; 391 { 392 unsigned long prognum; 393 int sock = RPC_ANYSOCK; 394 SVCXPRT *xprt; 395 struct jobs *job; 396 397 /* 398 * Register the callback service on the first free 399 * transient program number. 400 */ 401 xprt = svcudp_create(sock); 402 for (prognum = 0x40000000; prognum < 0x5FFFFFFF; prognum++) { 403 if (svc_register(xprt, prognum, 1, 404 yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE) 405 break; 406 } 407 408 /* Register the job in our linked list of jobs. */ 409 if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) { 410 yp_error("malloc failed"); 411 yppush_exit(1); 412 } 413 414 /* Initialize the info for this job. */ 415 job->stat = 0; 416 job->tid = tid; 417 job->port = xprt->xp_port; 418 job->sock = xprt->xp_sock; /*XXX: Evil!! EEEEEEEVIL!!! */ 419 job->server = strdup(server); 420 job->map = strdup(map); 421 job->prognum = prognum; 422 job->polled = 0; 423 job->next = yppush_joblist; 424 yppush_joblist = job; 425 426 /* 427 * Set the RPC sockets to asynchronous mode. This will 428 * cause the system to smack us with a SIGIO when an RPC 429 * callback is delivered. This in turn allows us to handle 430 * the callback even though we may be in the middle of doing 431 * something else at the time. 432 * 433 * XXX This is a horrible thing to do for two reasons, 434 * both of which have to do with portability: 435 * 1) We really ought not to be sticking our grubby mits 436 * into the RPC service transport handle like this. 437 * 2) Even in this day and age, there are still some *NIXes 438 * that don't support async socket I/O. 439 */ 440 if (fcntl(xprt->xp_sock, F_SETOWN, getpid()) == -1 || 441 fcntl(xprt->xp_sock, F_SETFL, O_ASYNC) == -1) { 442 yp_error("failed to set async I/O mode: %s", 443 strerror(errno)); 444 yppush_exit(1); 445 } 446 447 if (verbose) { 448 yp_error("initiating transfer: %s -> %s (transid = %lu)", 449 yppush_mapname, server, tid); 450 } 451 452 /* 453 * Send the XFR request to ypserv. We don't have to wait for 454 * a response here since we can handle them asynchronously. 455 */ 456 457 if (yppush_send_xfr(job)){ 458 /* Transfer request blew up. */ 459 yppush_show_status(job->stat ? job->stat : 460 YPPUSH_YPSERV,job->tid); 461 } else { 462 if (verbose > 1) 463 yp_error("%s has been called", server); 464 } 465 466 return(0); 467 } 468 469 /* 470 * Called for each entry in the ypservers map from yp_get_map(), which 471 * is our private yp_all() routine. 472 */ 473 int yppush_foreach(status, key, keylen, val, vallen, data) 474 int status; 475 char *key; 476 int keylen; 477 char *val; 478 int vallen; 479 char *data; 480 { 481 char server[YPMAXRECORD + 2]; 482 483 if (status != YP_TRUE) 484 return (status); 485 486 snprintf(server, sizeof(server), "%.*s", vallen, val); 487 488 /* 489 * Restrict the number of concurrent jobs. If yppush_jobs number 490 * of jobs have already been dispatched and are still pending, 491 * wait for one of them to finish so we can reuse its slot. 492 */ 493 if (yppush_jobs <= 1) { 494 yppush_alarm_tripped = 0; 495 while (!yppush_alarm_tripped && yppush_running_jobs) { 496 alarm(yppush_timeout); 497 yppush_alarm_tripped = 0; 498 pause(); 499 alarm(0); 500 } 501 } else { 502 yppush_alarm_tripped = 0; 503 while (!yppush_alarm_tripped && yppush_running_jobs >= yppush_jobs) { 504 alarm(yppush_timeout); 505 yppush_alarm_tripped = 0; 506 pause(); 507 alarm(0); 508 } 509 } 510 511 /* Cleared for takeoff: set everything in motion. */ 512 if (yp_push(&server, yppush_mapname, yppush_transid)) 513 return(yp_errno); 514 515 /* Bump the job counter and transaction ID. */ 516 yppush_running_jobs++; 517 yppush_transid++; 518 return (0); 519 } 520 521 static void usage() 522 { 523 fprintf (stderr, "%s\n%s\n", 524 "usage: yppush [-d domain] [-t timeout] [-j #parallel jobs] [-h host]", 525 " [-p path] mapname"); 526 exit(1); 527 } 528 529 /* 530 * Entry point. (About time!) 531 */ 532 int 533 main(argc,argv) 534 int argc; 535 char *argv[]; 536 { 537 int ch; 538 DBT key, data; 539 char myname[MAXHOSTNAMELEN]; 540 struct hostlist { 541 char *name; 542 struct hostlist *next; 543 }; 544 struct hostlist *yppush_hostlist = NULL; 545 struct hostlist *tmp; 546 struct sigaction sa; 547 548 while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) { 549 switch(ch) { 550 case 'd': 551 yppush_domain = optarg; 552 break; 553 case 'j': 554 yppush_jobs = atoi(optarg); 555 if (yppush_jobs <= 0) 556 yppush_jobs = 1; 557 break; 558 case 'p': 559 yp_dir = optarg; 560 break; 561 case 'h': /* we can handle multiple hosts */ 562 if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) { 563 yp_error("malloc failed"); 564 yppush_exit(1); 565 } 566 tmp->name = strdup(optarg); 567 tmp->next = yppush_hostlist; 568 yppush_hostlist = tmp; 569 break; 570 case 't': 571 yppush_timeout = atoi(optarg); 572 break; 573 case 'v': 574 verbose++; 575 break; 576 default: 577 usage(); 578 break; 579 } 580 } 581 582 argc -= optind; 583 argv += optind; 584 585 yppush_mapname = argv[0]; 586 587 if (yppush_mapname == NULL) { 588 /* "No guts, no glory." */ 589 usage(); 590 } 591 592 /* 593 * If no domain was specified, try to find the default 594 * domain. If we can't find that, we're doomed and must bail. 595 */ 596 if (yppush_domain == NULL) { 597 char *yppush_check_domain; 598 if (!yp_get_default_domain(&yppush_check_domain) && 599 !_yp_check(&yppush_check_domain)) { 600 yp_error("no domain specified and NIS not running"); 601 usage(); 602 } else 603 yp_get_default_domain(&yppush_domain); 604 } 605 606 /* Check to see that we are the master for this map. */ 607 608 if (gethostname ((char *)&myname, sizeof(myname))) { 609 yp_error("failed to get name of local host: %s", 610 strerror(errno)); 611 yppush_exit(1); 612 } 613 614 key.data = "YP_MASTER_NAME"; 615 key.size = sizeof("YP_MASTER_NAME") - 1; 616 617 if (yp_get_record(yppush_domain, yppush_mapname, 618 &key, &data, 1) != YP_TRUE) { 619 yp_error("couldn't open %s map: %s", yppush_mapname, 620 strerror(errno)); 621 yppush_exit(1); 622 } 623 624 if (strncmp(myname, data.data, data.size)) { 625 yp_error("warning: this host is not the master for %s", 626 yppush_mapname); 627 #ifdef NITPICKY 628 yppush_exit(1); 629 #endif 630 } 631 632 yppush_master = malloc(data.size + 1); 633 strncpy(yppush_master, data.data, data.size); 634 yppush_master[data.size] = '\0'; 635 636 /* Install some handy handlers. */ 637 signal(SIGALRM, handler); 638 signal(SIGTERM, handler); 639 signal(SIGINT, handler); 640 signal(SIGABRT, handler); 641 642 /* 643 * Set up the SIGIO handler. Make sure that some of the 644 * other signals are blocked while the handler is running so 645 * select() doesn't get interrupted. 646 */ 647 sigemptyset(&sa.sa_mask); 648 sigaddset(&sa.sa_mask, SIGIO); /* Goes without saying. */ 649 sigaddset(&sa.sa_mask, SIGPIPE); 650 sigaddset(&sa.sa_mask, SIGCHLD); 651 sigaddset(&sa.sa_mask, SIGALRM); 652 sigaddset(&sa.sa_mask, SIGINT); 653 sa.sa_handler = async_handler; 654 655 sigaction(SIGIO, &sa, NULL); 656 657 /* set initial transaction ID */ 658 yppush_transid = time((time_t *)NULL); 659 660 if (yppush_hostlist) { 661 /* 662 * Host list was specified on the command line: 663 * kick off the transfers by hand. 664 */ 665 tmp = yppush_hostlist; 666 while(tmp) { 667 yppush_foreach(YP_TRUE, NULL, 0, tmp->name, 668 strlen(tmp->name)); 669 tmp = tmp->next; 670 } 671 } else { 672 /* 673 * Do a yp_all() on the ypservers map and initiate a ypxfr 674 * for each one. 675 */ 676 ypxfr_get_map("ypservers", yppush_domain, 677 "localhost", yppush_foreach); 678 } 679 680 if (verbose > 1) 681 yp_error("all jobs dispatched"); 682 683 /* All done -- normal exit. */ 684 yppush_exit(0); 685 686 /* Just in case. */ 687 exit(0); 688 } 689