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