xref: /netbsd/usr.bin/rdist/docmd.c (revision b405ddbb)
1 /*	$NetBSD: docmd.c,v 1.30 2013/10/18 20:43:45 christos 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.30 2013/10/18 20:43:45 christos 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
docmds(char ** dhosts,int argc,char ** argv)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
doarrow(char ** filev,struct namelist * xfiles,char * rhost,struct subcmd * xcmds)126 doarrow(char **filev, struct namelist *xfiles, char *rhost,
127     struct subcmd *xcmds)
128 {
129 	struct namelist *f;
130 	struct subcmd *sc;
131 	char **cpp;
132 	int n;
133 	int volatile ddir;
134 	int volatile opts;
135 	struct namelist * volatile files = xfiles;
136 	struct subcmd * volatile cmds = xcmds;
137 
138 	opts = options;
139 	if (debug)
140 		printf("doarrow(%lx, %s, %lx)\n",
141 		    (long)files, rhost, (long)cmds);
142 
143 	if (files == NULL) {
144 		error("no files to be updated\n");
145 		return;
146 	}
147 
148 	subcmds = cmds;
149 	ddir = files->n_next != NULL;	/* destination is a directory */
150 	if (nflag)
151 		printf("updating host %s\n", rhost);
152 	else {
153 		if (setjmp(env))
154 			goto done;
155 		signal(SIGPIPE, lostconn);
156 		if (!makeconn(rhost))
157 			return;
158 		if ((lfp = fopen(tempfile, "w")) == NULL) {
159 			fatal("cannot open %s\n", tempfile);
160 			exit(1);
161 		}
162 	}
163 	for (f = files; f != NULL; f = f->n_next) {
164 		if (filev) {
165 			for (cpp = filev; *cpp; cpp++)
166 				if (strcmp(f->n_name, *cpp) == 0)
167 					goto found;
168 			if (!nflag && lfp)
169 				(void) fclose(lfp);
170 			continue;
171 		}
172 	found:
173 		n = 0;
174 		for (sc = cmds; sc != NULL; sc = sc->sc_next) {
175 			if (sc->sc_type != INSTALL)
176 				continue;
177 			n++;
178 			install(f->n_name, sc->sc_name,
179 				sc->sc_name == NULL ? 0 : ddir, sc->sc_options);
180 			opts = sc->sc_options;
181 		}
182 		if (n == 0)
183 			install(f->n_name, NULL, 0, options);
184 	}
185 done:
186 	if (!nflag) {
187 		(void) signal(SIGPIPE, cleanup);
188 		if (lfp)
189 			(void) fclose(lfp);
190 		lfp = NULL;
191 	}
192 	for (sc = cmds; sc != NULL; sc = sc->sc_next)
193 		if (sc->sc_type == NOTIFY)
194 			notify(tempfile, rhost, sc->sc_args, 0);
195 	if (!nflag) {
196 		for (; ihead != NULL; ihead = ihead->nextp) {
197 			free(ihead);
198 			if ((opts & IGNLNKS) || ihead->count == 0)
199 				continue;
200 			if (lfp)
201 				dolog(lfp, "%s: Warning: missing links\n",
202 					ihead->pathname);
203 		}
204 	}
205 }
206 
207 /*
208  * Create a connection to the rdist server on the machine rhost.
209  */
210 static int
makeconn(char * rhost)211 makeconn(char *rhost)
212 {
213 	char *ruser, *cp;
214 	static char *cur_host = NULL;
215 	static int port = -1;
216 	char tuser[20];
217 	int n;
218 	extern char user[];
219 
220 	if (debug)
221 		printf("makeconn(%s)\n", rhost);
222 
223 	if (cur_host != NULL && rem >= 0) {
224 		if (strcmp(cur_host, rhost) == 0)
225 			return(1);
226 		closeconn();
227 	}
228 	cur_host = rhost;
229 	cp = strchr(rhost, '@');
230 	if (cp != NULL) {
231 		char c = *cp;
232 
233 		*cp = '\0';
234 		if (strlcpy(tuser, rhost, sizeof(tuser)) >= sizeof(tuser)) {
235 			*cp = c;
236 			return(0);
237 		}
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
closeconn(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 /*ARGSUSED*/
lostconn(int signo __unused)319 lostconn(int signo __unused)
320 {
321 	char lcbuf[BUFSIZ];
322 	int nr = -1;
323 
324 	if (remerr != -1)
325 		if (ioctl(remerr, FIONREAD, &nr) != -1) {
326 			if (nr >= (int)sizeof(lcbuf))
327 				nr = sizeof(lcbuf) - 1;
328 			if ((nr = read(remerr, lcbuf, nr)) > 0) {
329 				lcbuf[nr] = '\0';
330 				if (lcbuf[nr - 1] == '\n')
331 					lcbuf[--nr] = '\0';
332 			}
333 		}
334 
335 	if (nr <= 0)
336 		(void) strlcpy(lcbuf, "lost connection", sizeof(lcbuf));
337 
338 	if (iamremote)
339 		cleanup(0);
340 	if (lfp)
341 		dolog(lfp, "rdist: %s\n", lcbuf);
342 	else
343 		error("%s\n", lcbuf);
344 	longjmp(env, 1);
345 }
346 
347 static int
okname(char * name)348 okname(char *name)
349 {
350 	char *cp = name;
351 	int c;
352 
353 	do {
354 		c = *cp;
355 		if (c & 0200)
356 			goto bad;
357 		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
358 			goto bad;
359 		cp++;
360 	} while (*cp);
361 	return(1);
362 bad:
363 	error("invalid user name %s\n", name);
364 	return(0);
365 }
366 
367 time_t	lastmod;
368 FILE	*tfp;
369 extern	char target[], *tp;
370 
371 /*
372  * Process commands for comparing files to time stamp files.
373  */
374 static void
dodcolon(char ** filev,struct namelist * files,char * stamp,struct subcmd * cmds)375 dodcolon(char **filev, struct namelist *files, char *stamp, struct subcmd *cmds)
376 {
377 	struct subcmd *sc;
378 	struct namelist *f;
379 	char **cpp;
380 	struct timeval tv[2];
381 	struct stat stb;
382 
383 	if (debug)
384 		printf("dodcolon()\n");
385 
386 	if (files == NULL) {
387 		error("no files to be updated\n");
388 		return;
389 	}
390 	if (stat(stamp, &stb) < 0) {
391 		error("%s: %s\n", stamp, strerror(errno));
392 		return;
393 	}
394 	if (debug)
395 		printf("%s: %lu\n", stamp, (u_long)stb.st_mtime);
396 
397 	subcmds = cmds;
398 	lastmod = stb.st_mtime;
399 	if (nflag || (options & VERIFY))
400 		tfp = NULL;
401 	else {
402 		if ((tfp = fopen(tempfile, "w")) == NULL) {
403 			error("%s: %s\n", tempfile, strerror(errno));
404 			return;
405 		}
406 		(void) gettimeofday(&tv[0], (struct timezone *)0);
407 		tv[1] = tv[0];
408 		(void) utimes(stamp, tv);
409 	}
410 
411 	for (f = files; f != NULL; f = f->n_next) {
412 		if (filev) {
413 			for (cpp = filev; *cpp; cpp++)
414 				if (strcmp(f->n_name, *cpp) == 0)
415 					goto found;
416 			continue;
417 		}
418 	found:
419 		tp = NULL;
420 		cmptime(f->n_name);
421 	}
422 
423 	if (tfp != NULL)
424 		(void) fclose(tfp);
425 	for (sc = cmds; sc != NULL; sc = sc->sc_next)
426 		if (sc->sc_type == NOTIFY)
427 			notify(tempfile, NULL, sc->sc_args, lastmod);
428 }
429 
430 /*
431  * Compare the mtime of file to the list of time stamps.
432  */
433 static void
cmptime(char * name)434 cmptime(char *name)
435 {
436 	struct stat stb;
437 
438 	if (debug)
439 		printf("cmptime(%s)\n", name);
440 
441 	if (except(name))
442 		return;
443 
444 	if (nflag) {
445 		printf("comparing dates: %s\n", name);
446 		return;
447 	}
448 
449 	/*
450 	 * first time cmptime() is called?
451 	 */
452 	if (tp == NULL) {
453 		if (exptilde(target, name) == NULL)
454 			return;
455 		tp = name = target;
456 		while (*tp)
457 			tp++;
458 	}
459 	if (access(name, 4) < 0 || stat(name, &stb) < 0) {
460 		error("%s: %s\n", name, strerror(errno));
461 		return;
462 	}
463 
464 	switch (stb.st_mode & S_IFMT) {
465 	case S_IFREG:
466 		break;
467 
468 	case S_IFDIR:
469 		rcmptime(&stb);
470 		return;
471 
472 	default:
473 		error("%s: not a plain file\n", name);
474 		return;
475 	}
476 
477 	if (stb.st_mtime > lastmod)
478 		dolog(tfp, "new: %s\n", name);
479 }
480 
481 static void
rcmptime(struct stat * st)482 rcmptime(struct stat *st)
483 {
484 	DIR *d;
485 	struct dirent *dp;
486 	char *cp;
487 	char *otp;
488 	int len;
489 
490 	if (debug)
491 		printf("rcmptime(%lx)\n", (long)st);
492 
493 	if ((d = opendir(target)) == NULL) {
494 		error("%s: %s\n", target, strerror(errno));
495 		return;
496 	}
497 	otp = tp;
498 	len = tp - target;
499 	while ((dp = readdir(d)) != NULL) {
500 		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
501 			continue;
502 		if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
503 			error("%s/%s: Name too long\n", target, dp->d_name);
504 			continue;
505 		}
506 		tp = otp;
507 		*tp++ = '/';
508 		cp = dp->d_name;
509 		while ((*tp++ = *cp++) != 0)
510 			;
511 		tp--;
512 		cmptime(target);
513 	}
514 	closedir(d);
515 	tp = otp;
516 	*tp = '\0';
517 }
518 
519 /*
520  * Notify the list of people the changes that were made.
521  * rhost == NULL if we are mailing a list of changes compared to at time
522  * stamp file.
523  */
524 static void
notify(char * file,char * rhost,struct namelist * to,time_t lmod)525 notify(char *file, char *rhost, struct namelist *to, time_t lmod)
526 {
527 	int fd, len;
528 	struct stat stb;
529 	FILE *pf;
530 	char *cp, *nrhost = rhost;
531 
532 	if ((options & VERIFY) || to == NULL)
533 		return;
534 
535 	/* strip any leading user@ prefix from rhost */
536 	if (rhost && (cp = strchr(rhost, '@')) != NULL)
537 		nrhost = cp + 1;
538 
539 	if (!qflag) {
540 		printf("notify ");
541 		if (rhost)
542 			printf("@%s ", nrhost);
543 		prnames(to);
544 	}
545 	if (nflag)
546 		return;
547 
548 	if ((fd = open(file, 0)) < 0) {
549 		error("%s: %s\n", file, strerror(errno));
550 		return;
551 	}
552 	if (fstat(fd, &stb) < 0) {
553 		error("%s: %s\n", file, strerror(errno));
554 		(void) close(fd);
555 		return;
556 	}
557 	if (stb.st_size == 0) {
558 		(void) close(fd);
559 		return;
560 	}
561 	/*
562 	 * Create a pipe to mailling program.
563 	 */
564 	(void)snprintf(buf, sizeof(buf), "%s -oi -t", _PATH_SENDMAIL);
565 	pf = popen(buf, "w");
566 	if (pf == NULL) {
567 		error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
568 		(void) close(fd);
569 		return;
570 	}
571 	/*
572 	 * Output the proper header information.
573 	 */
574 	fprintf(pf, "From: rdist (Remote distribution program)\n");
575 	fprintf(pf, "To:");
576 	if (!any('@', to->n_name) && rhost != NULL)
577 		fprintf(pf, " %s@%s", to->n_name, nrhost);
578 	else
579 		fprintf(pf, " %s", to->n_name);
580 	to = to->n_next;
581 	while (to != NULL) {
582 		if (!any('@', to->n_name) && rhost != NULL)
583 			fprintf(pf, ", %s@%s", to->n_name, nrhost);
584 		else
585 			fprintf(pf, ", %s", to->n_name);
586 		to = to->n_next;
587 	}
588 	putc('\n', pf);
589 	if (rhost != NULL)
590 		fprintf(pf, "Subject: files updated by rdist from %s to %s\n",
591 			host, rhost);
592 	else
593 		fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod));
594 	putc('\n', pf);
595 
596 	while ((len = read(fd, buf, BUFSIZ)) > 0)
597 		if (fwrite(buf, 1, len, pf) < 1)
598 			error("%s: %s\n", file, strerror(errno));
599 	(void) close(fd);
600 	(void) pclose(pf);
601 }
602 
603 /*
604  * Return true if name is in the list.
605  */
606 int
inlist(struct namelist * list,char * file)607 inlist(struct namelist *list, char *file)
608 {
609 	struct namelist *nl;
610 
611 	for (nl = list; nl != NULL; nl = nl->n_next)
612 		if (!strcmp(file, nl->n_name))
613 			return(1);
614 	return(0);
615 }
616 
617 /*
618  * Return TRUE if file is in the exception list.
619  */
620 int
except(char * file)621 except(char *file)
622 {
623 	struct	subcmd *sc;
624 	struct	namelist *nl;
625 	int err;
626 	regex_t s;
627 
628 	if (debug)
629 		printf("except(%s)\n", file);
630 
631 	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
632 		if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN)
633 			continue;
634 		for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
635 			if (sc->sc_type == EXCEPT) {
636 				if (!strcmp(file, nl->n_name))
637 					return(1);
638 				continue;
639 			}
640 			if ((err = regcomp(&s, nl->n_name, 0)) != 0) {
641 				char ebuf[BUFSIZ];
642 				(void) regerror(err, &s, ebuf, sizeof(ebuf));
643 				error("%s: %s\n", nl->n_name, ebuf);
644 			}
645 			if (regexec(&s, file, 0, NULL, 0) == 0) {
646 				regfree(&s);
647 				return(1);
648 			}
649 			regfree(&s);
650 		}
651 	}
652 	return(0);
653 }
654 
655 char *
colon(char * cp)656 colon(char *cp)
657 {
658 
659 	while (*cp) {
660 		if (*cp == ':')
661 			return(cp);
662 		if (*cp == '/')
663 			return(0);
664 		cp++;
665 	}
666 	return(0);
667 }
668