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