xref: /netbsd/usr.bin/rdist/docmd.c (revision c4a72b64)
1 /*	$NetBSD: docmd.c,v 1.22 2002/12/06 03:09:28 thorpej 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.22 2002/12/06 03:09:28 thorpej 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 		strncpy(tuser, rhost, sizeof(tuser)-1);
238 		*cp = c;
239 		rhost = cp + 1;
240 		ruser = tuser;
241 		if (*ruser == '\0')
242 			ruser = user;
243 		else if (!okname(ruser))
244 			return(0);
245 	} else
246 		ruser = user;
247 	if (!qflag)
248 		printf("updating host %s\n", rhost);
249 	(void) snprintf(buf, sizeof(buf), "%s -Server%s", _PATH_RDIST,
250 	    qflag ? " -q" : "");
251 	if (port < 0) {
252 		struct servent *sp;
253 
254 		if ((sp = getservbyname("shell", "tcp")) == NULL)
255 			fatal("shell/tcp: unknown service");
256 		port = sp->s_port;
257 	}
258 
259 	if (debug) {
260 		printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser);
261 		printf("buf = %s\n", buf);
262 	}
263 
264 	fflush(stdout);
265 	seteuid(0);
266 	rem = rcmd(&rhost, port, user, ruser, buf, &remerr);
267 	seteuid(userid);
268 	if (rem < 0)
269 		return(0);
270 	cp = buf;
271 	if (read(rem, cp, 1) != 1)
272 		lostconn(0);
273 	if (*cp == 'V') {
274 		do {
275 			if (read(rem, cp, 1) != 1)
276 				lostconn(0);
277 		} while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
278 		*--cp = '\0';
279 		cp = buf;
280 		n = 0;
281 		while (*cp >= '0' && *cp <= '9')
282 			n = (n * 10) + (*cp++ - '0');
283 		if (*cp == '\0' && n == VERSION)
284 			return(1);
285 		error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n);
286 	} else {
287 		error("connection failed: version numbers don't match\n");
288 		error("got unexpected input:");
289 		do {
290 			error("%c", *cp);
291 		} while (*cp != '\n' && read(rem, cp, 1) == 1);
292 	}
293 	closeconn();
294 	return(0);
295 }
296 
297 /*
298  * Signal end of previous connection.
299  */
300 static void
301 closeconn(void)
302 {
303 	if (debug)
304 		printf("closeconn()\n");
305 
306 	if (rem >= 0) {
307 		if (write(rem, "\2\n", 2) < 0 && debug)
308 			printf("write failed on fd %d: %s\n", rem,
309 			    strerror(errno));
310 		(void) close(rem);
311 		(void) close(remerr);
312 		rem = -1;
313 		remerr = -1;
314 	}
315 }
316 
317 void
318 lostconn(int signo)
319 {
320 	char buf[BUFSIZ];
321 	int nr = -1;
322 
323 	if (remerr != -1)
324 		if (ioctl(remerr, FIONREAD, &nr) != -1) {
325 			if (nr >= sizeof(buf))
326 				nr = sizeof(buf) - 1;
327 			if ((nr = read(remerr, buf, nr)) > 0) {
328 				buf[nr] = '\0';
329 				if (buf[nr - 1] == '\n')
330 					buf[--nr] = '\0';
331 			}
332 		}
333 
334 	if (nr <= 0)
335 		(void) strcpy(buf, "lost connection");
336 
337 	if (iamremote)
338 		cleanup(0);
339 	if (lfp)
340 		dolog(lfp, "rdist: %s\n", buf);
341 	else
342 		error("%s\n", buf);
343 	longjmp(env, 1);
344 }
345 
346 static int
347 okname(char *name)
348 {
349 	char *cp = name;
350 	int c;
351 
352 	do {
353 		c = *cp;
354 		if (c & 0200)
355 			goto bad;
356 		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
357 			goto bad;
358 		cp++;
359 	} while (*cp);
360 	return(1);
361 bad:
362 	error("invalid user name %s\n", name);
363 	return(0);
364 }
365 
366 time_t	lastmod;
367 FILE	*tfp;
368 extern	char target[], *tp;
369 
370 /*
371  * Process commands for comparing files to time stamp files.
372  */
373 static void
374 dodcolon(char **filev, struct namelist *files, char *stamp, struct subcmd *cmds)
375 {
376 	struct subcmd *sc;
377 	struct namelist *f;
378 	char **cpp;
379 	struct timeval tv[2];
380 	struct stat stb;
381 
382 	if (debug)
383 		printf("dodcolon()\n");
384 
385 	if (files == NULL) {
386 		error("no files to be updated\n");
387 		return;
388 	}
389 	if (stat(stamp, &stb) < 0) {
390 		error("%s: %s\n", stamp, strerror(errno));
391 		return;
392 	}
393 	if (debug)
394 		printf("%s: %lu\n", stamp, (u_long)stb.st_mtime);
395 
396 	subcmds = cmds;
397 	lastmod = stb.st_mtime;
398 	if (nflag || (options & VERIFY))
399 		tfp = NULL;
400 	else {
401 		if ((tfp = fopen(tempfile, "w")) == NULL) {
402 			error("%s: %s\n", tempfile, strerror(errno));
403 			return;
404 		}
405 		(void) gettimeofday(&tv[0], (struct timezone *)0);
406 		tv[1] = tv[0];
407 		(void) utimes(stamp, tv);
408 	}
409 
410 	for (f = files; f != NULL; f = f->n_next) {
411 		if (filev) {
412 			for (cpp = filev; *cpp; cpp++)
413 				if (strcmp(f->n_name, *cpp) == 0)
414 					goto found;
415 			continue;
416 		}
417 	found:
418 		tp = NULL;
419 		cmptime(f->n_name);
420 	}
421 
422 	if (tfp != NULL)
423 		(void) fclose(tfp);
424 	for (sc = cmds; sc != NULL; sc = sc->sc_next)
425 		if (sc->sc_type == NOTIFY)
426 			notify(tempfile, NULL, sc->sc_args, lastmod);
427 }
428 
429 /*
430  * Compare the mtime of file to the list of time stamps.
431  */
432 static void
433 cmptime(char *name)
434 {
435 	struct stat stb;
436 
437 	if (debug)
438 		printf("cmptime(%s)\n", name);
439 
440 	if (except(name))
441 		return;
442 
443 	if (nflag) {
444 		printf("comparing dates: %s\n", name);
445 		return;
446 	}
447 
448 	/*
449 	 * first time cmptime() is called?
450 	 */
451 	if (tp == NULL) {
452 		if (exptilde(target, name) == NULL)
453 			return;
454 		tp = name = target;
455 		while (*tp)
456 			tp++;
457 	}
458 	if (access(name, 4) < 0 || stat(name, &stb) < 0) {
459 		error("%s: %s\n", name, strerror(errno));
460 		return;
461 	}
462 
463 	switch (stb.st_mode & S_IFMT) {
464 	case S_IFREG:
465 		break;
466 
467 	case S_IFDIR:
468 		rcmptime(&stb);
469 		return;
470 
471 	default:
472 		error("%s: not a plain file\n", name);
473 		return;
474 	}
475 
476 	if (stb.st_mtime > lastmod)
477 		dolog(tfp, "new: %s\n", name);
478 }
479 
480 static void
481 rcmptime(struct stat *st)
482 {
483 	DIR *d;
484 	struct dirent *dp;
485 	char *cp;
486 	char *otp;
487 	int len;
488 
489 	if (debug)
490 		printf("rcmptime(%lx)\n", (long)st);
491 
492 	if ((d = opendir(target)) == NULL) {
493 		error("%s: %s\n", target, strerror(errno));
494 		return;
495 	}
496 	otp = tp;
497 	len = tp - target;
498 	while ((dp = readdir(d)) != NULL) {
499 		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
500 			continue;
501 		if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
502 			error("%s/%s: Name too long\n", target, dp->d_name);
503 			continue;
504 		}
505 		tp = otp;
506 		*tp++ = '/';
507 		cp = dp->d_name;
508 		while ((*tp++ = *cp++) != 0)
509 			;
510 		tp--;
511 		cmptime(target);
512 	}
513 	closedir(d);
514 	tp = otp;
515 	*tp = '\0';
516 }
517 
518 /*
519  * Notify the list of people the changes that were made.
520  * rhost == NULL if we are mailing a list of changes compared to at time
521  * stamp file.
522  */
523 static void
524 notify(char *file, char *rhost, struct namelist *to, time_t lmod)
525 {
526 	int fd, len;
527 	struct stat stb;
528 	FILE *pf;
529 
530 	if ((options & VERIFY) || to == NULL)
531 		return;
532 	if (!qflag) {
533 		printf("notify ");
534 		if (rhost)
535 			printf("@%s ", rhost);
536 		prnames(to);
537 	}
538 	if (nflag)
539 		return;
540 
541 	if ((fd = open(file, 0)) < 0) {
542 		error("%s: %s\n", file, strerror(errno));
543 		return;
544 	}
545 	if (fstat(fd, &stb) < 0) {
546 		error("%s: %s\n", file, strerror(errno));
547 		(void) close(fd);
548 		return;
549 	}
550 	if (stb.st_size == 0) {
551 		(void) close(fd);
552 		return;
553 	}
554 	/*
555 	 * Create a pipe to mailling program.
556 	 */
557 	(void)snprintf(buf, sizeof(buf), "%s -oi -t", _PATH_SENDMAIL);
558 	pf = popen(buf, "w");
559 	if (pf == NULL) {
560 		error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
561 		(void) close(fd);
562 		return;
563 	}
564 	/*
565 	 * Output the proper header information.
566 	 */
567 	fprintf(pf, "From: rdist (Remote distribution program)\n");
568 	fprintf(pf, "To:");
569 	if (!any('@', to->n_name) && rhost != NULL)
570 		fprintf(pf, " %s@%s", to->n_name, rhost);
571 	else
572 		fprintf(pf, " %s", to->n_name);
573 	to = to->n_next;
574 	while (to != NULL) {
575 		if (!any('@', to->n_name) && rhost != NULL)
576 			fprintf(pf, ", %s@%s", to->n_name, rhost);
577 		else
578 			fprintf(pf, ", %s", to->n_name);
579 		to = to->n_next;
580 	}
581 	putc('\n', pf);
582 	if (rhost != NULL)
583 		fprintf(pf, "Subject: files updated by rdist from %s to %s\n",
584 			host, rhost);
585 	else
586 		fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod));
587 	putc('\n', pf);
588 
589 	while ((len = read(fd, buf, BUFSIZ)) > 0)
590 		if (fwrite(buf, 1, len, pf) < 1)
591 			error("%s: %s\n", file, strerror(errno));
592 	(void) close(fd);
593 	(void) pclose(pf);
594 }
595 
596 /*
597  * Return true if name is in the list.
598  */
599 int
600 inlist(struct namelist *list, char *file)
601 {
602 	struct namelist *nl;
603 
604 	for (nl = list; nl != NULL; nl = nl->n_next)
605 		if (!strcmp(file, nl->n_name))
606 			return(1);
607 	return(0);
608 }
609 
610 /*
611  * Return TRUE if file is in the exception list.
612  */
613 int
614 except(char *file)
615 {
616 	struct	subcmd *sc;
617 	struct	namelist *nl;
618 	int err;
619 	regex_t s;
620 
621 	if (debug)
622 		printf("except(%s)\n", file);
623 
624 	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
625 		if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN)
626 			continue;
627 		for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
628 			if (sc->sc_type == EXCEPT) {
629 				if (!strcmp(file, nl->n_name))
630 					return(1);
631 				continue;
632 			}
633 			if ((err = regcomp(&s, nl->n_name, 0)) != 0) {
634 				char ebuf[BUFSIZ];
635 				(void) regerror(err, &s, ebuf, sizeof(ebuf));
636 				error("%s: %s\n", nl->n_name, ebuf);
637 			}
638 			if (regexec(&s, file, 0, NULL, 0) == 0) {
639 				regfree(&s);
640 				return(1);
641 			}
642 			regfree(&s);
643 		}
644 	}
645 	return(0);
646 }
647 
648 char *
649 colon(char *cp)
650 {
651 
652 	while (*cp) {
653 		if (*cp == ':')
654 			return(cp);
655 		if (*cp == '/')
656 			return(0);
657 		cp++;
658 	}
659 	return(0);
660 }
661