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