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