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