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