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