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