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