1 /* $NetBSD: docmd.c,v 1.19 1999/04/20 07:53:02 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. 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 the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 #if 0 39 static char sccsid[] = "@(#)docmd.c 8.1 (Berkeley) 6/9/93"; 40 #else 41 __RCSID("$NetBSD: docmd.c,v 1.19 1999/04/20 07:53:02 mrg Exp $"); 42 #endif 43 #endif /* not lint */ 44 45 #include <sys/types.h> 46 #include <sys/ioctl.h> 47 48 #include <errno.h> 49 #include <netdb.h> 50 #include <regex.h> 51 #include <setjmp.h> 52 #include <fcntl.h> 53 54 #include "defs.h" 55 56 FILE *lfp; /* log file for recording files updated */ 57 struct subcmd *subcmds; /* list of sub-commands for current cmd */ 58 jmp_buf env; 59 60 static int remerr = -1; /* Remote stderr */ 61 62 static int makeconn __P((char *)); 63 static int okname __P((char *)); 64 static void closeconn __P((void)); 65 static void cmptime __P((char *)); 66 static void doarrow __P((char **, 67 struct namelist *, char *, struct subcmd *)); 68 static void dodcolon __P((char **, 69 struct namelist *, char *, struct subcmd *)); 70 static void notify __P((char *, char *, struct namelist *, time_t)); 71 static void rcmptime __P((struct stat *)); 72 73 /* 74 * Do the commands in cmds (initialized by yyparse). 75 */ 76 void 77 docmds(dhosts, argc, argv) 78 char **dhosts; 79 int argc; 80 char **argv; 81 { 82 struct cmd *c; 83 struct namelist *f; 84 char **cpp; 85 extern struct cmd *cmds; 86 87 signal(SIGHUP, cleanup); 88 signal(SIGINT, cleanup); 89 signal(SIGQUIT, cleanup); 90 signal(SIGTERM, cleanup); 91 92 for (c = cmds; c != NULL; c = c->c_next) { 93 if (dhosts != NULL && *dhosts != NULL) { 94 for (cpp = dhosts; *cpp; cpp++) 95 if (strcmp(c->c_name, *cpp) == 0) 96 goto fndhost; 97 continue; 98 } 99 fndhost: 100 if (argc) { 101 for (cpp = argv; *cpp; cpp++) { 102 if (c->c_label != NULL && 103 strcmp(c->c_label, *cpp) == 0) { 104 cpp = NULL; 105 goto found; 106 } 107 for (f = c->c_files; f != NULL; f = f->n_next) 108 if (strcmp(f->n_name, *cpp) == 0) 109 goto found; 110 } 111 continue; 112 } else 113 cpp = NULL; 114 found: 115 switch (c->c_type) { 116 case ARROW: 117 doarrow(cpp, c->c_files, c->c_name, c->c_cmds); 118 break; 119 case DCOLON: 120 dodcolon(cpp, c->c_files, c->c_name, c->c_cmds); 121 break; 122 default: 123 fatal("illegal command type %d\n", c->c_type); 124 } 125 } 126 closeconn(); 127 } 128 129 /* 130 * Process commands for sending files to other machines. 131 */ 132 static void 133 doarrow(filev, files, rhost, cmds) 134 char **filev; 135 struct namelist *files; 136 char *rhost; 137 struct subcmd *cmds; 138 { 139 struct namelist *f; 140 struct subcmd *sc; 141 char **cpp; 142 int n, ddir, opts = options; 143 144 #if __GNUC__ /* XXX borken compiler alert! */ 145 (void)&ddir; 146 (void)&opts; 147 #endif 148 149 if (debug) 150 printf("doarrow(%lx, %s, %lx)\n", 151 (long)files, rhost, (long)cmds); 152 153 if (files == NULL) { 154 error("no files to be updated\n"); 155 return; 156 } 157 158 subcmds = cmds; 159 ddir = files->n_next != NULL; /* destination is a directory */ 160 if (nflag) 161 printf("updating host %s\n", rhost); 162 else { 163 if (setjmp(env)) 164 goto done; 165 signal(SIGPIPE, lostconn); 166 if (!makeconn(rhost)) 167 return; 168 if ((lfp = fopen(tempfile, "w")) == NULL) { 169 fatal("cannot open %s\n", tempfile); 170 exit(1); 171 } 172 } 173 for (f = files; f != NULL; f = f->n_next) { 174 if (filev) { 175 for (cpp = filev; *cpp; cpp++) 176 if (strcmp(f->n_name, *cpp) == 0) 177 goto found; 178 if (!nflag && lfp) 179 (void) fclose(lfp); 180 continue; 181 } 182 found: 183 n = 0; 184 for (sc = cmds; sc != NULL; sc = sc->sc_next) { 185 if (sc->sc_type != INSTALL) 186 continue; 187 n++; 188 install(f->n_name, sc->sc_name, 189 sc->sc_name == NULL ? 0 : ddir, sc->sc_options); 190 opts = sc->sc_options; 191 } 192 if (n == 0) 193 install(f->n_name, NULL, 0, options); 194 } 195 done: 196 if (!nflag) { 197 (void) signal(SIGPIPE, cleanup); 198 if (lfp) 199 (void) fclose(lfp); 200 lfp = NULL; 201 } 202 for (sc = cmds; sc != NULL; sc = sc->sc_next) 203 if (sc->sc_type == NOTIFY) 204 notify(tempfile, rhost, sc->sc_args, 0); 205 if (!nflag) { 206 (void) unlink(tempfile); 207 for (; ihead != NULL; ihead = ihead->nextp) { 208 free(ihead); 209 if ((opts & IGNLNKS) || ihead->count == 0) 210 continue; 211 if (lfp) 212 log(lfp, "%s: Warning: missing links\n", 213 ihead->pathname); 214 } 215 } 216 } 217 218 /* 219 * Create a connection to the rdist server on the machine rhost. 220 */ 221 static int 222 makeconn(rhost) 223 char *rhost; 224 { 225 char *ruser, *cp; 226 static char *cur_host = NULL; 227 static int port = -1; 228 char tuser[20]; 229 int n; 230 extern char user[]; 231 232 if (debug) 233 printf("makeconn(%s)\n", rhost); 234 235 if (cur_host != NULL && rem >= 0) { 236 if (strcmp(cur_host, rhost) == 0) 237 return(1); 238 closeconn(); 239 } 240 cur_host = rhost; 241 cp = strchr(rhost, '@'); 242 if (cp != NULL) { 243 char c = *cp; 244 245 *cp = '\0'; 246 strncpy(tuser, rhost, sizeof(tuser)-1); 247 *cp = c; 248 rhost = cp + 1; 249 ruser = tuser; 250 if (*ruser == '\0') 251 ruser = user; 252 else if (!okname(ruser)) 253 return(0); 254 } else 255 ruser = user; 256 if (!qflag) 257 printf("updating host %s\n", rhost); 258 (void) snprintf(buf, sizeof(buf), "%s -Server%s", _PATH_RDIST, 259 qflag ? " -q" : ""); 260 if (port < 0) { 261 struct servent *sp; 262 263 if ((sp = getservbyname("shell", "tcp")) == NULL) 264 fatal("shell/tcp: unknown service"); 265 port = sp->s_port; 266 } 267 268 if (debug) { 269 printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser); 270 printf("buf = %s\n", buf); 271 } 272 273 fflush(stdout); 274 seteuid(0); 275 rem = rcmd(&rhost, port, user, ruser, buf, &remerr); 276 seteuid(userid); 277 if (rem < 0) 278 return(0); 279 cp = buf; 280 if (read(rem, cp, 1) != 1) 281 lostconn(0); 282 if (*cp == 'V') { 283 do { 284 if (read(rem, cp, 1) != 1) 285 lostconn(0); 286 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 287 *--cp = '\0'; 288 cp = buf; 289 n = 0; 290 while (*cp >= '0' && *cp <= '9') 291 n = (n * 10) + (*cp++ - '0'); 292 if (*cp == '\0' && n == VERSION) 293 return(1); 294 error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n); 295 } else { 296 error("connection failed: version numbers don't match\n"); 297 error("got unexpected input:"); 298 do { 299 error("%c", *cp); 300 } while (*cp != '\n' && read(rem, cp, 1) == 1); 301 } 302 closeconn(); 303 return(0); 304 } 305 306 /* 307 * Signal end of previous connection. 308 */ 309 static void 310 closeconn() 311 { 312 if (debug) 313 printf("closeconn()\n"); 314 315 if (rem >= 0) { 316 if (write(rem, "\2\n", 2) < 0 && debug) 317 printf("write failed on fd %d: %s\n", rem, 318 strerror(errno)); 319 (void) close(rem); 320 (void) close(remerr); 321 rem = -1; 322 remerr = -1; 323 } 324 } 325 326 void 327 lostconn(signo) 328 int signo; 329 { 330 char buf[BUFSIZ]; 331 int nr = -1; 332 333 if (remerr != -1) 334 if (ioctl(remerr, FIONREAD, &nr) != -1) { 335 if (nr >= sizeof(buf)) 336 nr = sizeof(buf) - 1; 337 if ((nr = read(remerr, buf, nr)) > 0) { 338 buf[nr] = '\0'; 339 if (buf[nr - 1] == '\n') 340 buf[--nr] = '\0'; 341 } 342 } 343 344 if (nr <= 0) 345 (void) strcpy(buf, "lost connection"); 346 347 if (iamremote) 348 cleanup(0); 349 if (lfp) 350 log(lfp, "rdist: %s\n", buf); 351 else 352 error("%s\n", buf); 353 longjmp(env, 1); 354 } 355 356 static int 357 okname(name) 358 char *name; 359 { 360 char *cp = name; 361 int c; 362 363 do { 364 c = *cp; 365 if (c & 0200) 366 goto bad; 367 if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') 368 goto bad; 369 cp++; 370 } while (*cp); 371 return(1); 372 bad: 373 error("invalid user name %s\n", name); 374 return(0); 375 } 376 377 time_t lastmod; 378 FILE *tfp; 379 extern char target[], *tp; 380 381 /* 382 * Process commands for comparing files to time stamp files. 383 */ 384 static void 385 dodcolon(filev, files, stamp, cmds) 386 char **filev; 387 struct namelist *files; 388 char *stamp; 389 struct subcmd *cmds; 390 { 391 struct subcmd *sc; 392 struct namelist *f; 393 char **cpp; 394 struct timeval tv[2]; 395 struct stat stb; 396 397 if (debug) 398 printf("dodcolon()\n"); 399 400 if (files == NULL) { 401 error("no files to be updated\n"); 402 return; 403 } 404 if (stat(stamp, &stb) < 0) { 405 error("%s: %s\n", stamp, strerror(errno)); 406 return; 407 } 408 if (debug) 409 printf("%s: %lu\n", stamp, (u_long)stb.st_mtime); 410 411 subcmds = cmds; 412 lastmod = stb.st_mtime; 413 if (nflag || (options & VERIFY)) 414 tfp = NULL; 415 else { 416 if ((tfp = fopen(tempfile, "w")) == NULL) { 417 error("%s: %s\n", tempfile, strerror(errno)); 418 return; 419 } 420 (void) gettimeofday(&tv[0], (struct timezone *)0); 421 tv[1] = tv[0]; 422 (void) utimes(stamp, tv); 423 } 424 425 for (f = files; f != NULL; f = f->n_next) { 426 if (filev) { 427 for (cpp = filev; *cpp; cpp++) 428 if (strcmp(f->n_name, *cpp) == 0) 429 goto found; 430 continue; 431 } 432 found: 433 tp = NULL; 434 cmptime(f->n_name); 435 } 436 437 if (tfp != NULL) 438 (void) fclose(tfp); 439 for (sc = cmds; sc != NULL; sc = sc->sc_next) 440 if (sc->sc_type == NOTIFY) 441 notify(tempfile, NULL, sc->sc_args, lastmod); 442 if (!nflag && !(options & VERIFY)) 443 (void) unlink(tempfile); 444 } 445 446 /* 447 * Compare the mtime of file to the list of time stamps. 448 */ 449 static void 450 cmptime(name) 451 char *name; 452 { 453 struct stat stb; 454 455 if (debug) 456 printf("cmptime(%s)\n", name); 457 458 if (except(name)) 459 return; 460 461 if (nflag) { 462 printf("comparing dates: %s\n", name); 463 return; 464 } 465 466 /* 467 * first time cmptime() is called? 468 */ 469 if (tp == NULL) { 470 if (exptilde(target, name) == NULL) 471 return; 472 tp = name = target; 473 while (*tp) 474 tp++; 475 } 476 if (access(name, 4) < 0 || stat(name, &stb) < 0) { 477 error("%s: %s\n", name, strerror(errno)); 478 return; 479 } 480 481 switch (stb.st_mode & S_IFMT) { 482 case S_IFREG: 483 break; 484 485 case S_IFDIR: 486 rcmptime(&stb); 487 return; 488 489 default: 490 error("%s: not a plain file\n", name); 491 return; 492 } 493 494 if (stb.st_mtime > lastmod) 495 log(tfp, "new: %s\n", name); 496 } 497 498 static void 499 rcmptime(st) 500 struct stat *st; 501 { 502 DIR *d; 503 struct dirent *dp; 504 char *cp; 505 char *otp; 506 int len; 507 508 if (debug) 509 printf("rcmptime(%lx)\n", (long)st); 510 511 if ((d = opendir(target)) == NULL) { 512 error("%s: %s\n", target, strerror(errno)); 513 return; 514 } 515 otp = tp; 516 len = tp - target; 517 while ((dp = readdir(d)) != NULL) { 518 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 519 continue; 520 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 521 error("%s/%s: Name too long\n", target, dp->d_name); 522 continue; 523 } 524 tp = otp; 525 *tp++ = '/'; 526 cp = dp->d_name; 527 while ((*tp++ = *cp++) != 0) 528 ; 529 tp--; 530 cmptime(target); 531 } 532 closedir(d); 533 tp = otp; 534 *tp = '\0'; 535 } 536 537 /* 538 * Notify the list of people the changes that were made. 539 * rhost == NULL if we are mailing a list of changes compared to at time 540 * stamp file. 541 */ 542 static void 543 notify(file, rhost, to, lmod) 544 char *file, *rhost; 545 struct namelist *to; 546 time_t lmod; 547 { 548 int fd, len; 549 struct stat stb; 550 FILE *pf; 551 552 if ((options & VERIFY) || to == NULL) 553 return; 554 if (!qflag) { 555 printf("notify "); 556 if (rhost) 557 printf("@%s ", rhost); 558 prnames(to); 559 } 560 if (nflag) 561 return; 562 563 if ((fd = open(file, 0)) < 0) { 564 error("%s: %s\n", file, strerror(errno)); 565 return; 566 } 567 if (fstat(fd, &stb) < 0) { 568 error("%s: %s\n", file, strerror(errno)); 569 (void) close(fd); 570 return; 571 } 572 if (stb.st_size == 0) { 573 (void) close(fd); 574 return; 575 } 576 /* 577 * Create a pipe to mailling program. 578 */ 579 (void)snprintf(buf, sizeof(buf), "%s -oi -t", _PATH_SENDMAIL); 580 pf = popen(buf, "w"); 581 if (pf == NULL) { 582 error("notify: \"%s\" failed\n", _PATH_SENDMAIL); 583 (void) close(fd); 584 return; 585 } 586 /* 587 * Output the proper header information. 588 */ 589 fprintf(pf, "From: rdist (Remote distribution program)\n"); 590 fprintf(pf, "To:"); 591 if (!any('@', to->n_name) && rhost != NULL) 592 fprintf(pf, " %s@%s", to->n_name, rhost); 593 else 594 fprintf(pf, " %s", to->n_name); 595 to = to->n_next; 596 while (to != NULL) { 597 if (!any('@', to->n_name) && rhost != NULL) 598 fprintf(pf, ", %s@%s", to->n_name, rhost); 599 else 600 fprintf(pf, ", %s", to->n_name); 601 to = to->n_next; 602 } 603 putc('\n', pf); 604 if (rhost != NULL) 605 fprintf(pf, "Subject: files updated by rdist from %s to %s\n", 606 host, rhost); 607 else 608 fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod)); 609 putc('\n', pf); 610 611 while ((len = read(fd, buf, BUFSIZ)) > 0) 612 if (fwrite(buf, 1, len, pf) < 1) 613 error("%s: %s\n", file, strerror(errno)); 614 (void) close(fd); 615 (void) pclose(pf); 616 } 617 618 /* 619 * Return true if name is in the list. 620 */ 621 int 622 inlist(list, file) 623 struct namelist *list; 624 char *file; 625 { 626 struct namelist *nl; 627 628 for (nl = list; nl != NULL; nl = nl->n_next) 629 if (!strcmp(file, nl->n_name)) 630 return(1); 631 return(0); 632 } 633 634 /* 635 * Return TRUE if file is in the exception list. 636 */ 637 int 638 except(file) 639 char *file; 640 { 641 struct subcmd *sc; 642 struct namelist *nl; 643 int err; 644 regex_t s; 645 646 if (debug) 647 printf("except(%s)\n", file); 648 649 for (sc = subcmds; sc != NULL; sc = sc->sc_next) { 650 if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN) 651 continue; 652 for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) { 653 if (sc->sc_type == EXCEPT) { 654 if (!strcmp(file, nl->n_name)) 655 return(1); 656 continue; 657 } 658 if ((err = regcomp(&s, nl->n_name, 0)) != 0) { 659 char ebuf[BUFSIZ]; 660 (void) regerror(err, &s, ebuf, sizeof(ebuf)); 661 error("%s: %s\n", nl->n_name, ebuf); 662 } 663 if (regexec(&s, file, 0, NULL, 0) == 0) { 664 regfree(&s); 665 return(1); 666 } 667 regfree(&s); 668 } 669 } 670 return(0); 671 } 672 673 char * 674 colon(cp) 675 char *cp; 676 { 677 678 while (*cp) { 679 if (*cp == ':') 680 return(cp); 681 if (*cp == '/') 682 return(0); 683 cp++; 684 } 685 return(0); 686 } 687