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