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