1/*	$NetBSD: docmd.c,v 1.29 2013/10/18 20:41:58 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
35static char sccsid[] = "@(#)docmd.c	8.1 (Berkeley) 6/9/93";
36#else
37__RCSID("$NetBSD: docmd.c,v 1.29 2013/10/18 20:41:58 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
52FILE	*lfp;			/* log file for recording files updated */
53struct	subcmd *subcmds;	/* list of sub-commands for current cmd */
54jmp_buf	env;
55
56static int	 remerr = -1;	/* Remote stderr */
57
58static int	 makeconn(char *);
59static int	 okname(char *);
60static void	 closeconn(void);
61static void	 cmptime(char *);
62static void	 doarrow(char **,
63		    struct namelist *, char *, struct subcmd *);
64static void	 dodcolon(char **,
65		    struct namelist *, char *, struct subcmd *);
66static void	 notify(char *, char *, struct namelist *, time_t);
67static void	 rcmptime(struct stat *);
68
69/*
70 * Do the commands in cmds (initialized by yyparse).
71 */
72void
73docmds(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 */
125static void
126doarrow(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	}
185done:
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 */
210static int
211makeconn(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 */
300static void
301closeconn(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
317void
318/*ARGSUSED*/
319lostconn(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
347static int
348okname(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);
362bad:
363	error("invalid user name %s\n", name);
364	return(0);
365}
366
367time_t	lastmod;
368FILE	*tfp;
369extern	char target[], *tp;
370
371/*
372 * Process commands for comparing files to time stamp files.
373 */
374static void
375dodcolon(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 */
433static void
434cmptime(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
481static void
482rcmptime(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 */
524static void
525notify(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 */
606int
607inlist(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 */
620int
621except(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
655char *
656colon(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