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