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