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 char * 92 yppusherr_string(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 107 yppush_show_status(ypxfrstat status, unsigned long tid) 108 { 109 struct jobs *job; 110 111 job = yppush_joblist; 112 113 while (job) { 114 if (job->tid == tid) 115 break; 116 job = job->next; 117 } 118 119 if (job->polled) { 120 return(0); 121 } 122 123 if (verbose > 1) 124 yp_error("checking return status: transaction ID: %lu", 125 job->tid); 126 if (status != YPPUSH_SUCC || verbose) { 127 yp_error("transfer of map %s to server %s %s", 128 job->map, job->server, status == YPPUSH_SUCC ? 129 "succeeded" : "failed"); 130 yp_error("status returned by ypxfr: %s", status > YPPUSH_AGE ? 131 yppusherr_string(status) : 132 ypxfrerr_string(status)); 133 } 134 135 job->polled = 1; 136 137 svc_unregister(job->prognum, 1); 138 139 yppush_running_jobs--; 140 return(0); 141 } 142 143 /* Exit routine. */ 144 static void 145 yppush_exit(int now) 146 { 147 struct jobs *jptr; 148 int still_pending = 1; 149 150 /* Let all the information trickle in. */ 151 while (!now && still_pending) { 152 jptr = yppush_joblist; 153 still_pending = 0; 154 while (jptr) { 155 if (jptr->polled == 0) { 156 still_pending++; 157 if (verbose > 1) 158 yp_error("%s has not responded", 159 jptr->server); 160 } else { 161 if (verbose > 1) 162 yp_error("%s has responded", 163 jptr->server); 164 } 165 jptr = jptr->next; 166 } 167 if (still_pending) { 168 if (verbose > 1) 169 yp_error("%d transfer%sstill pending", 170 still_pending, 171 still_pending > 1 ? "s " : " "); 172 yppush_alarm_tripped = 0; 173 alarm(YPPUSH_RESPONSE_TIMEOUT); 174 pause(); 175 alarm(0); 176 if (yppush_alarm_tripped == 1) { 177 yp_error("timed out"); 178 now = 1; 179 } 180 } else { 181 if (verbose) 182 yp_error("all transfers complete"); 183 break; 184 } 185 } 186 187 188 /* All stats collected and reported -- kill all the stragglers. */ 189 jptr = yppush_joblist; 190 while (jptr) { 191 if (!jptr->polled) 192 yp_error("warning: exiting with transfer \ 193 to %s (transid = %lu) still pending", jptr->server, jptr->tid); 194 svc_unregister(jptr->prognum, 1); 195 jptr = jptr->next; 196 } 197 198 exit(0); 199 } 200 201 /* 202 * Handler for 'normal' signals. 203 */ 204 205 static void 206 handler(int sig) 207 { 208 if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) { 209 yppush_jobs = 0; 210 yppush_exit(1); 211 } 212 213 if (sig == SIGALRM) { 214 alarm(0); 215 yppush_alarm_tripped++; 216 } 217 218 return; 219 } 220 221 /* 222 * Dispatch loop for callback RPC services. 223 */ 224 static void 225 yppush_svc_run(void) 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 266 async_handler(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 300 yppush_send_xfr(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 388 yp_push(char *server, char *map, unsigned long tid) 389 { 390 unsigned long prognum; 391 int sock = RPC_ANYSOCK; 392 SVCXPRT *xprt; 393 struct jobs *job; 394 395 /* 396 * Register the callback service on the first free 397 * transient program number. 398 */ 399 xprt = svcudp_create(sock); 400 for (prognum = 0x40000000; prognum < 0x5FFFFFFF; prognum++) { 401 if (svc_register(xprt, prognum, 1, 402 yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE) 403 break; 404 } 405 406 /* Register the job in our linked list of jobs. */ 407 if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) { 408 yp_error("malloc failed"); 409 yppush_exit(1); 410 } 411 412 /* Initialize the info for this job. */ 413 job->stat = 0; 414 job->tid = tid; 415 job->port = xprt->xp_port; 416 job->sock = xprt->xp_fd; /*XXX: Evil!! EEEEEEEVIL!!! */ 417 job->server = strdup(server); 418 job->map = strdup(map); 419 job->prognum = prognum; 420 job->polled = 0; 421 job->next = yppush_joblist; 422 yppush_joblist = job; 423 424 /* 425 * Set the RPC sockets to asynchronous mode. This will 426 * cause the system to smack us with a SIGIO when an RPC 427 * callback is delivered. This in turn allows us to handle 428 * the callback even though we may be in the middle of doing 429 * something else at the time. 430 * 431 * XXX This is a horrible thing to do for two reasons, 432 * both of which have to do with portability: 433 * 1) We really ought not to be sticking our grubby mits 434 * into the RPC service transport handle like this. 435 * 2) Even in this day and age, there are still some *NIXes 436 * that don't support async socket I/O. 437 */ 438 if (fcntl(xprt->xp_fd, F_SETOWN, getpid()) == -1 || 439 fcntl(xprt->xp_fd, F_SETFL, O_ASYNC) == -1) { 440 yp_error("failed to set async I/O mode: %s", 441 strerror(errno)); 442 yppush_exit(1); 443 } 444 445 if (verbose) { 446 yp_error("initiating transfer: %s -> %s (transid = %lu)", 447 yppush_mapname, server, tid); 448 } 449 450 /* 451 * Send the XFR request to ypserv. We don't have to wait for 452 * a response here since we can handle them asynchronously. 453 */ 454 455 if (yppush_send_xfr(job)){ 456 /* Transfer request blew up. */ 457 yppush_show_status(job->stat ? job->stat : 458 YPPUSH_YPSERV,job->tid); 459 } else { 460 if (verbose > 1) 461 yp_error("%s has been called", server); 462 } 463 464 return(0); 465 } 466 467 /* 468 * Called for each entry in the ypservers map from yp_get_map(), which 469 * is our private yp_all() routine. 470 */ 471 int 472 yppush_foreach(int status, char *key, int keylen, char *val, int vallen, 473 char *data) 474 { 475 char server[YPMAXRECORD + 2]; 476 477 if (status != YP_TRUE) 478 return (status); 479 480 snprintf(server, sizeof(server), "%.*s", vallen, val); 481 482 /* 483 * Restrict the number of concurrent jobs. If yppush_jobs number 484 * of jobs have already been dispatched and are still pending, 485 * wait for one of them to finish so we can reuse its slot. 486 */ 487 if (yppush_jobs <= 1) { 488 yppush_alarm_tripped = 0; 489 while (!yppush_alarm_tripped && yppush_running_jobs) { 490 alarm(yppush_timeout); 491 yppush_alarm_tripped = 0; 492 pause(); 493 alarm(0); 494 } 495 } else { 496 yppush_alarm_tripped = 0; 497 while (!yppush_alarm_tripped && yppush_running_jobs >= yppush_jobs) { 498 alarm(yppush_timeout); 499 yppush_alarm_tripped = 0; 500 pause(); 501 alarm(0); 502 } 503 } 504 505 /* Cleared for takeoff: set everything in motion. */ 506 if (yp_push(&server, yppush_mapname, yppush_transid)) 507 return(yp_errno); 508 509 /* Bump the job counter and transaction ID. */ 510 yppush_running_jobs++; 511 yppush_transid++; 512 return (0); 513 } 514 515 static void usage() 516 { 517 fprintf (stderr, "%s\n%s\n", 518 "usage: yppush [-d domain] [-t timeout] [-j #parallel jobs] [-h host]", 519 " [-p path] mapname"); 520 exit(1); 521 } 522 523 /* 524 * Entry point. (About time!) 525 */ 526 int 527 main(int argc, char *argv[]) 528 { 529 int ch; 530 DBT key, data; 531 char myname[MAXHOSTNAMELEN]; 532 struct hostlist { 533 char *name; 534 struct hostlist *next; 535 }; 536 struct hostlist *yppush_hostlist = NULL; 537 struct hostlist *tmp; 538 struct sigaction sa; 539 540 while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) { 541 switch (ch) { 542 case 'd': 543 yppush_domain = optarg; 544 break; 545 case 'j': 546 yppush_jobs = atoi(optarg); 547 if (yppush_jobs <= 0) 548 yppush_jobs = 1; 549 break; 550 case 'p': 551 yp_dir = optarg; 552 break; 553 case 'h': /* we can handle multiple hosts */ 554 if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) { 555 yp_error("malloc failed"); 556 yppush_exit(1); 557 } 558 tmp->name = strdup(optarg); 559 tmp->next = yppush_hostlist; 560 yppush_hostlist = tmp; 561 break; 562 case 't': 563 yppush_timeout = atoi(optarg); 564 break; 565 case 'v': 566 verbose++; 567 break; 568 default: 569 usage(); 570 break; 571 } 572 } 573 574 argc -= optind; 575 argv += optind; 576 577 yppush_mapname = argv[0]; 578 579 if (yppush_mapname == NULL) { 580 /* "No guts, no glory." */ 581 usage(); 582 } 583 584 /* 585 * If no domain was specified, try to find the default 586 * domain. If we can't find that, we're doomed and must bail. 587 */ 588 if (yppush_domain == NULL) { 589 char *yppush_check_domain; 590 if (!yp_get_default_domain(&yppush_check_domain) && 591 !_yp_check(&yppush_check_domain)) { 592 yp_error("no domain specified and NIS not running"); 593 usage(); 594 } else 595 yp_get_default_domain(&yppush_domain); 596 } 597 598 /* Check to see that we are the master for this map. */ 599 600 if (gethostname ((char *)&myname, sizeof(myname))) { 601 yp_error("failed to get name of local host: %s", 602 strerror(errno)); 603 yppush_exit(1); 604 } 605 606 key.data = "YP_MASTER_NAME"; 607 key.size = sizeof("YP_MASTER_NAME") - 1; 608 609 if (yp_get_record(yppush_domain, yppush_mapname, 610 &key, &data, 1) != YP_TRUE) { 611 yp_error("couldn't open %s map: %s", yppush_mapname, 612 strerror(errno)); 613 yppush_exit(1); 614 } 615 616 if (strncmp(myname, data.data, data.size)) { 617 yp_error("warning: this host is not the master for %s", 618 yppush_mapname); 619 #ifdef NITPICKY 620 yppush_exit(1); 621 #endif 622 } 623 624 yppush_master = malloc(data.size + 1); 625 strncpy(yppush_master, data.data, data.size); 626 yppush_master[data.size] = '\0'; 627 628 /* Install some handy handlers. */ 629 signal(SIGALRM, handler); 630 signal(SIGTERM, handler); 631 signal(SIGINT, handler); 632 signal(SIGABRT, handler); 633 634 /* 635 * Set up the SIGIO handler. Make sure that some of the 636 * other signals are blocked while the handler is running so 637 * select() doesn't get interrupted. 638 */ 639 sigemptyset(&sa.sa_mask); 640 sigaddset(&sa.sa_mask, SIGIO); /* Goes without saying. */ 641 sigaddset(&sa.sa_mask, SIGPIPE); 642 sigaddset(&sa.sa_mask, SIGCHLD); 643 sigaddset(&sa.sa_mask, SIGALRM); 644 sigaddset(&sa.sa_mask, SIGINT); 645 sa.sa_handler = async_handler; 646 sa.sa_flags = 0; 647 648 sigaction(SIGIO, &sa, NULL); 649 650 /* set initial transaction ID */ 651 yppush_transid = time((time_t *)NULL); 652 653 if (yppush_hostlist) { 654 /* 655 * Host list was specified on the command line: 656 * kick off the transfers by hand. 657 */ 658 tmp = yppush_hostlist; 659 while (tmp) { 660 yppush_foreach(YP_TRUE, NULL, 0, tmp->name, 661 strlen(tmp->name), NULL); 662 tmp = tmp->next; 663 } 664 } else { 665 /* 666 * Do a yp_all() on the ypservers map and initiate a ypxfr 667 * for each one. 668 */ 669 ypxfr_get_map("ypservers", yppush_domain, 670 "localhost", yppush_foreach); 671 } 672 673 if (verbose > 1) 674 yp_error("all jobs dispatched"); 675 676 /* All done -- normal exit. */ 677 yppush_exit(0); 678 679 /* Just in case. */ 680 exit(0); 681 } 682