xref: /netbsd-src/usr.bin/rdist/docmd.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
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