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