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