1 /* 2 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 /* 7 * Copyright (c) 1983 Regents of the University of California. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms are permitted 11 * provided that the above copyright notice and this paragraph are 12 * duplicated in all such forms and that any documentation, 13 * advertising materials, and other materials related to such 14 * distribution and use acknowledge that the software was developed 15 * by the University of California, Berkeley. The name of the 16 * University may not be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 */ 19 #pragma ident "%Z%%M% %I% %E% SMI" 20 21 #include "defs.h" 22 #include <string.h> 23 #include <setjmp.h> 24 #include <netdb.h> 25 #include <signal.h> 26 #include <krb5defs.h> 27 28 #ifndef RDIST 29 #ifdef SYSV 30 /* 31 * Historically, the rdist program has had the following hard-coded 32 * pathname. Some operating systems attempt to "improve" the 33 * directory layout, in the process re-locating the rdist binary 34 * to some other location. However, the first original implementation 35 * sets a standard of sorts. In order to interoperate with other 36 * systems, our implementation must do two things: It must provide 37 * the an rdist binary at the pathname below, and it must use this 38 * pathname when executing rdist on remote systems via the rcmd() 39 * library. Thus the hard-coded path name below can never be changed. 40 */ 41 #endif /* SYSV */ 42 #define RDIST "/usr/ucb/rdist" 43 #endif 44 45 FILE *lfp; /* log file for recording files updated */ 46 struct subcmd *subcmds; /* list of sub-commands for current cmd */ 47 jmp_buf env; 48 49 void cleanup(); 50 void lostconn(); 51 static int init_service(int); 52 static struct servent *sp; 53 54 #ifdef SYSV 55 #include <libgen.h> 56 57 static char *recomp; 58 static char *errstring = "regcmp failed for some unknown reason"; 59 60 char * 61 re_comp(s) 62 char *s; 63 { 64 if ((int)recomp != 0) 65 free(recomp); 66 recomp = regcmp(s, (char *)0); 67 if (recomp == NULL) 68 return (errstring); 69 else 70 return ((char *)0); 71 } 72 73 74 re_exec(s) 75 char *s; 76 { 77 if ((int)recomp == 0) 78 return (-1); 79 if (regex(recomp, s) == NULL) 80 return (0); 81 else 82 return (1); 83 } 84 #endif /* SYSV */ 85 86 /* 87 * Do the commands in cmds (initialized by yyparse). 88 */ 89 docmds(dhosts, argc, argv) 90 char **dhosts; 91 int argc; 92 char **argv; 93 { 94 register struct cmd *c; 95 register struct namelist *f; 96 register char **cpp; 97 extern struct cmd *cmds; 98 99 /* protect backgrounded rdist */ 100 if (signal(SIGINT, SIG_IGN) != SIG_IGN) 101 (void) signal(SIGINT, cleanup); 102 103 /* ... and running via nohup(1) */ 104 if (signal(SIGHUP, SIG_IGN) != SIG_IGN) 105 (void) signal(SIGHUP, cleanup); 106 if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) 107 (void) signal(SIGQUIT, cleanup); 108 109 (void) signal(SIGTERM, cleanup); 110 111 if (debug) 112 if (!cmds) 113 printf("docmds: cmds == NULL\n"); 114 else { 115 printf("docmds: cmds "); 116 prcmd(cmds); 117 } 118 for (c = cmds; c != NULL; c = c->c_next) { 119 if (dhosts != NULL && *dhosts != NULL) { 120 for (cpp = dhosts; *cpp; cpp++) 121 if (strcmp(c->c_name, *cpp) == 0) 122 goto fndhost; 123 continue; 124 } 125 fndhost: 126 if (argc) { 127 for (cpp = argv; *cpp; cpp++) { 128 if (c->c_label != NULL && 129 strcmp(c->c_label, *cpp) == 0) { 130 cpp = NULL; 131 goto found; 132 } 133 for (f = c->c_files; f != NULL; f = f->n_next) 134 if (strcmp(f->n_name, *cpp) == 0) 135 goto found; 136 } 137 continue; 138 } else 139 cpp = NULL; 140 found: 141 switch (c->c_type) { 142 case ARROW: 143 doarrow(cpp, c->c_files, c->c_name, c->c_cmds); 144 break; 145 case DCOLON: 146 dodcolon(cpp, c->c_files, c->c_name, c->c_cmds); 147 break; 148 default: 149 fatal("illegal command type %d\n", c->c_type); 150 } 151 } 152 closeconn(); 153 } 154 155 /* 156 * Process commands for sending files to other machines. 157 */ 158 doarrow(filev, files, rhost, cmds) 159 char **filev; 160 struct namelist *files; 161 char *rhost; 162 struct subcmd *cmds; 163 { 164 register struct namelist *f; 165 register struct subcmd *sc; 166 register char **cpp; 167 int n, ddir, opts = options; 168 169 if (debug) 170 printf("doarrow(%x, %s, %x)\n", files, rhost, cmds); 171 172 if (files == NULL) { 173 error("no files to be updated\n"); 174 return; 175 } 176 177 subcmds = cmds; 178 ddir = files->n_next != NULL; /* destination is a directory */ 179 if (nflag) 180 printf("updating host %s\n", rhost); 181 else { 182 if (setjmp(env)) 183 goto done; 184 (void) signal(SIGPIPE, lostconn); 185 if (!makeconn(rhost)) 186 return; 187 if (!nflag) 188 if ((lfp = fopen(Tmpfile, "w")) == NULL) { 189 fatal("cannot open %s\n", Tmpfile); 190 exit(1); 191 } 192 } 193 for (f = files; f != NULL; f = f->n_next) { 194 if (filev) { 195 for (cpp = filev; *cpp; cpp++) 196 if (strcmp(f->n_name, *cpp) == 0) 197 goto found; 198 continue; 199 } 200 found: 201 n = 0; 202 for (sc = cmds; sc != NULL; sc = sc->sc_next) { 203 if (sc->sc_type != INSTALL) 204 continue; 205 n++; 206 install(f->n_name, sc->sc_name, 207 sc->sc_name == NULL ? 0 : ddir, sc->sc_options); 208 opts = sc->sc_options; 209 } 210 if (n == 0) 211 install(f->n_name, NULL, 0, options); 212 } 213 done: 214 if (!nflag) { 215 (void) signal(SIGPIPE, cleanup); 216 (void) fclose(lfp); 217 lfp = NULL; 218 } 219 for (sc = cmds; sc != NULL; sc = sc->sc_next) 220 if (sc->sc_type == NOTIFY) 221 notify(Tmpfile, rhost, sc->sc_args, 0); 222 if (!nflag) { 223 (void) unlink(Tmpfile); 224 for (; ihead != NULL; ihead = ihead->nextp) { 225 free(ihead); 226 if ((opts & IGNLNKS) || ihead->count == 0) 227 continue; 228 log(lfp, "%s: Warning: missing links\n", 229 ihead->pathname); 230 } 231 } 232 } 233 234 static int 235 init_service(int krb5flag) 236 { 237 boolean_t success = B_FALSE; 238 239 if (krb5flag > 0) { 240 if ((sp = getservbyname("kshell", "tcp")) == NULL) { 241 fatal("kshell/tcp: unknown service"); 242 (void) fprintf(stderr, 243 gettext("trying shell/tcp service...\n")); 244 } else { 245 success = B_TRUE; 246 } 247 } else { 248 if ((sp = getservbyname("shell", "tcp")) == NULL) { 249 fatal("shell/tcp: unknown service"); 250 exit(1); 251 } else { 252 success = B_TRUE; 253 } 254 } 255 return (success); 256 } 257 /* 258 * Create a connection to the rdist server on the machine rhost. 259 */ 260 makeconn(rhost) 261 char *rhost; 262 { 263 register char *ruser, *cp; 264 static char *cur_host = NULL; 265 static int port = -1; 266 char tuser[20]; 267 int n; 268 extern char user[]; 269 270 if (debug) 271 printf("makeconn(%s)\n", rhost); 272 273 if (cur_host != NULL && rem >= 0) { 274 if (strcmp(cur_host, rhost) == 0) 275 return (1); 276 closeconn(); 277 } 278 cur_host = rhost; 279 cp = index(rhost, '@'); 280 if (cp != NULL) { 281 char c = *cp; 282 283 *cp = '\0'; 284 strncpy(tuser, rhost, sizeof (tuser)-1); 285 *cp = c; 286 rhost = cp + 1; 287 ruser = tuser; 288 if (*ruser == '\0') 289 ruser = user; 290 else if (!okname(ruser)) 291 return (0); 292 } else 293 ruser = user; 294 if (!qflag) 295 printf("updating host %s\n", rhost); 296 (void) snprintf(buf, RDIST_BUFSIZ, "%s%s -Server%s", 297 encrypt_flag ? "-x " : "", RDIST, qflag ? " -q" : ""); 298 if (port < 0) { 299 if (debug_port == 0) { 300 if ((retval = (int)init_service(krb5auth_flag)) == 0) { 301 krb5auth_flag = encrypt_flag = 0; 302 (void) init_service(krb5auth_flag); 303 } 304 port = sp->s_port; 305 306 } else { 307 port = debug_port; 308 } 309 } 310 311 if (debug) { 312 printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), 313 user, ruser); 314 printf("buf = %s\n", buf); 315 } 316 317 fflush(stdout); 318 319 if (krb5auth_flag > 0) { 320 if ((encrypt_flag > 0) && (!krb5_privacy_allowed())) { 321 (void) fprintf(stderr, gettext("rdist: Encryption " 322 " not supported.\n")); 323 exit(1); 324 } 325 326 authopts = AP_OPTS_MUTUAL_REQUIRED; 327 328 status = kcmd(&rem, &rhost, port, 329 user, ruser, 330 buf, 0, "host", krb_realm, 331 bsd_context, 332 &auth_context, 333 &cred, 334 0, /* No need for sequence number */ 335 0, /* No need for server seq # */ 336 authopts, 337 1, /* Always set anyport */ 338 &kcmd_proto); 339 if (status) { 340 /* 341 * If new protocol requested, we dont 342 * fallback to less secure ones. 343 */ 344 if (kcmd_proto == KCMD_NEW_PROTOCOL) { 345 (void) fprintf(stderr, gettext("rdist: kcmdv2 " 346 "to host %s failed - %s\n" 347 "Fallback to normal rdist denied."), 348 host, error_message(status)); 349 exit(1); 350 } 351 /* check NO_TKT_FILE or equivalent... */ 352 if (status != -1) { 353 (void) fprintf(stderr, gettext("rdist: " 354 "kcmd to host %s failed - %s\n" 355 "trying normal rdist...\n\n"), 356 host, error_message(status)); 357 } else { 358 (void) fprintf(stderr, 359 gettext("trying normal rdist...\n")); 360 } 361 /* 362 * kcmd() failed, so we now fallback to normal rdist 363 */ 364 krb5auth_flag = encrypt_flag = 0; 365 (void) init_service(krb5auth_flag); 366 port = sp->s_port; 367 goto do_rcmd; 368 } 369 #ifdef DEBUG 370 else { 371 (void) fprintf(stderr, gettext("Kerberized rdist " 372 "session, port %d in use "), port); 373 if (kcmd_proto == KCMD_OLD_PROTOCOL) 374 (void) fprintf(stderr, 375 gettext("[kcmd ver.1].\n")); 376 else 377 (void) fprintf(stderr, 378 gettext("[kcmd ver.2].\n")); 379 } 380 #endif /* DEBUG */ 381 session_key = &cred->keyblock; 382 383 if (kcmd_proto == KCMD_NEW_PROTOCOL) { 384 status = krb5_auth_con_getlocalsubkey(bsd_context, 385 auth_context, 386 &session_key); 387 if (status) { 388 com_err("rdist", status, 389 "determining subkey for session"); 390 exit(1); 391 } 392 if (!session_key) { 393 com_err("rdist", 0, 394 "no subkey negotiated for connection"); 395 exit(1); 396 } 397 } 398 399 eblock.crypto_entry = session_key->enctype; 400 eblock.key = (krb5_keyblock *)session_key; 401 402 init_encrypt(encrypt_flag, bsd_context, kcmd_proto, &desinbuf, 403 &desoutbuf, CLIENT, &eblock); 404 405 406 if (encrypt_flag > 0) { 407 char *s = gettext("This rdist session is using " 408 "encryption for all data transmissions.\r\n"); 409 (void) write(2, s, strlen(s)); 410 } 411 412 } 413 else 414 do_rcmd: 415 { 416 rem = rcmd_af(&rhost, port, user, ruser, buf, 0, AF_INET6); 417 } 418 419 if (rem < 0) 420 return (0); 421 422 cp = buf; 423 if (desread(rem, cp, 1, 0) != 1) 424 lostconn(); 425 if (*cp == 'V') { 426 do { 427 if (desread(rem, cp, 1, 0) != 1) 428 lostconn(); 429 } while (*cp++ != '\n' && cp < &buf[RDIST_BUFSIZ]); 430 *--cp = '\0'; 431 cp = buf; 432 n = 0; 433 while (*cp >= '0' && *cp <= '9') 434 n = (n * 10) + (*cp++ - '0'); 435 if (*cp == '\0' && n == VERSION) 436 return (1); 437 error("connection failed: version numbers don't match" 438 " (local %d, remote %d)\n", VERSION, n); 439 } else { 440 error("connection failed: version numbers don't match\n"); 441 } 442 closeconn(); 443 return (0); 444 } 445 446 /* 447 * Signal end of previous connection. 448 */ 449 closeconn() 450 { 451 if (debug) 452 printf("closeconn()\n"); 453 454 if (rem >= 0) { 455 (void) deswrite(rem, "\2\n", 2, 0); 456 (void) close(rem); 457 rem = -1; 458 } 459 } 460 461 void 462 lostconn() 463 { 464 if (iamremote) 465 cleanup(); 466 log(lfp, "rdist: lost connection\n"); 467 longjmp(env, 1); 468 } 469 470 okname(name) 471 register char *name; 472 { 473 register char *cp = name; 474 register int c; 475 476 do { 477 c = *cp; 478 if (c & 0200) 479 goto bad; 480 if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') 481 goto bad; 482 cp++; 483 } while (*cp); 484 return (1); 485 bad: 486 error("invalid user name %s\n", name); 487 return (0); 488 } 489 490 time_t lastmod; 491 FILE *tfp; 492 extern char target[], *tp; 493 494 /* 495 * Process commands for comparing files to time stamp files. 496 */ 497 dodcolon(filev, files, stamp, cmds) 498 char **filev; 499 struct namelist *files; 500 char *stamp; 501 struct subcmd *cmds; 502 { 503 register struct subcmd *sc; 504 register struct namelist *f; 505 register char **cpp; 506 struct timeval tv[2]; 507 struct stat stb; 508 509 if (debug) 510 printf("dodcolon()\n"); 511 512 if (files == NULL) { 513 error("no files to be updated\n"); 514 return; 515 } 516 if (stat(stamp, &stb) < 0) { 517 error("%s: %s\n", stamp, strerror(errno)); 518 return; 519 } 520 if (debug) 521 printf("%s: %d\n", stamp, stb.st_mtime); 522 523 subcmds = cmds; 524 lastmod = stb.st_mtime; 525 if (nflag || (options & VERIFY)) 526 tfp = NULL; 527 else { 528 if ((tfp = fopen(Tmpfile, "w")) == NULL) { 529 error("%s: %s\n", stamp, strerror(errno)); 530 return; 531 } 532 (void) gettimeofday(&tv[0], (struct timezone *)NULL); 533 tv[1] = tv[0]; 534 (void) utimes(stamp, tv); 535 } 536 537 for (f = files; f != NULL; f = f->n_next) { 538 if (filev) { 539 for (cpp = filev; *cpp; cpp++) 540 if (strcmp(f->n_name, *cpp) == 0) 541 goto found; 542 continue; 543 } 544 found: 545 tp = NULL; 546 cmptime(f->n_name); 547 } 548 549 if (tfp != NULL) 550 (void) fclose(tfp); 551 for (sc = cmds; sc != NULL; sc = sc->sc_next) 552 if (sc->sc_type == NOTIFY) 553 notify(Tmpfile, NULL, sc->sc_args, lastmod); 554 if (!nflag && !(options & VERIFY)) 555 (void) unlink(Tmpfile); 556 } 557 558 /* 559 * Compare the mtime of file to the list of time stamps. 560 */ 561 cmptime(name) 562 char *name; 563 { 564 struct stat stb; 565 566 if (debug) 567 printf("cmptime(%s)\n", name); 568 569 if (except(name)) 570 return; 571 572 if (nflag) { 573 printf("comparing dates: %s\n", name); 574 return; 575 } 576 577 /* 578 * first time cmptime() is called? 579 */ 580 if (tp == NULL) { 581 if (exptilde(target, RDIST_BUFSIZ, name) == NULL) 582 return; 583 tp = name = target; 584 while (*tp) 585 tp++; 586 } 587 if (access(name, 4) < 0 || stat(name, &stb) < 0) { 588 error("%s: %s\n", name, strerror(errno)); 589 return; 590 } 591 592 switch (stb.st_mode & S_IFMT) { 593 case S_IFREG: 594 break; 595 596 case S_IFDIR: 597 rcmptime(&stb); 598 return; 599 600 default: 601 error("%s: not a plain file\n", name); 602 return; 603 } 604 605 if (stb.st_mtime > lastmod) 606 log(tfp, "new: %s\n", name); 607 } 608 609 rcmptime(st) 610 struct stat *st; 611 { 612 register DIR *d; 613 register struct dirent *dp; 614 register char *cp; 615 char *otp; 616 int len; 617 618 if (debug) 619 printf("rcmptime(%x)\n", st); 620 621 if ((d = opendir(target)) == NULL) { 622 error("%s: %s\n", target, strerror(errno)); 623 return; 624 } 625 otp = tp; 626 len = tp - target; 627 while (dp = readdir(d)) { 628 if ((strcmp(dp->d_name, ".") == 0) || 629 (strcmp(dp->d_name, "..") == 0)) 630 continue; 631 if (len + 1 + strlen(dp->d_name) >= RDIST_BUFSIZ - 1) { 632 error("%s/%s: Name too long\n", target, dp->d_name); 633 continue; 634 } 635 tp = otp; 636 *tp++ = '/'; 637 cp = dp->d_name; 638 while (*tp++ = *cp++) 639 ; 640 tp--; 641 cmptime(target); 642 } 643 closedir(d); 644 tp = otp; 645 *tp = '\0'; 646 } 647 648 /* 649 * Notify the list of people the changes that were made. 650 * rhost == NULL if we are mailing a list of changes compared to at time 651 * stamp file. 652 */ 653 notify(file, rhost, to, lmod) 654 char *file, *rhost; 655 register struct namelist *to; 656 time_t lmod; 657 { 658 register int fd, len; 659 FILE *pf, *popen(); 660 struct stat stb; 661 662 if ((options & VERIFY) || to == NULL) 663 return; 664 if (!qflag) { 665 printf("notify "); 666 if (rhost) 667 printf("@%s ", rhost); 668 prnames(to); 669 } 670 if (nflag) 671 return; 672 673 if ((fd = open(file, 0)) < 0) { 674 error("%s: %s\n", file, strerror(errno)); 675 return; 676 } 677 if (fstat(fd, &stb) < 0) { 678 error("%s: %s\n", file, strerror(errno)); 679 (void) close(fd); 680 return; 681 } 682 if (stb.st_size == 0) { 683 (void) close(fd); 684 return; 685 } 686 /* 687 * Create a pipe to mailling program. 688 */ 689 pf = popen(MAILCMD, "w"); 690 if (pf == NULL) { 691 error("notify: \"%s\" failed\n", MAILCMD); 692 (void) close(fd); 693 return; 694 } 695 /* 696 * Output the proper header information. 697 */ 698 fprintf(pf, "From: rdist (Remote distribution program)\n"); 699 fprintf(pf, "To:"); 700 if (!any('@', to->n_name) && rhost != NULL) 701 fprintf(pf, " %s@%s", to->n_name, rhost); 702 else 703 fprintf(pf, " %s", to->n_name); 704 to = to->n_next; 705 while (to != NULL) { 706 if (!any('@', to->n_name) && rhost != NULL) 707 fprintf(pf, ", %s@%s", to->n_name, rhost); 708 else 709 fprintf(pf, ", %s", to->n_name); 710 to = to->n_next; 711 } 712 putc('\n', pf); 713 if (rhost != NULL) 714 fprintf(pf, "Subject: files updated by rdist from %s to %s\n", 715 host, rhost); 716 else 717 fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod)); 718 putc('\n', pf); 719 720 while ((len = read(fd, buf, RDIST_BUFSIZ)) > 0) 721 (void) fwrite(buf, 1, len, pf); 722 (void) close(fd); 723 (void) pclose(pf); 724 } 725 726 /* 727 * Return true if name is in the list. 728 */ 729 inlist(list, file) 730 struct namelist *list; 731 char *file; 732 { 733 register struct namelist *nl; 734 735 for (nl = list; nl != NULL; nl = nl->n_next) 736 if (strcmp(file, nl->n_name) == 0) 737 return (1); 738 return (0); 739 } 740 741 /* 742 * Return TRUE if file is in the exception list. 743 */ 744 except(file) 745 char *file; 746 { 747 register struct subcmd *sc; 748 register struct namelist *nl; 749 750 if (debug) 751 printf("except(%s)\n", file); 752 753 for (sc = subcmds; sc != NULL; sc = sc->sc_next) { 754 if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN) 755 continue; 756 for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) { 757 if (sc->sc_type == EXCEPT) { 758 if (strcmp(file, nl->n_name) == 0) 759 return (1); 760 continue; 761 } 762 re_comp(nl->n_name); 763 if (re_exec(file) > 0) 764 return (1); 765 } 766 } 767 return (0); 768 } 769 770 char * 771 colon(cp) 772 register char *cp; 773 { 774 775 while (*cp) { 776 if (*cp == ':') 777 return (cp); 778 if (*cp == '/') 779 return (0); 780 cp++; 781 } 782 return (0); 783 } 784