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