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