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