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