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