1/*
2 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6/*
7 * Copyright (c) 1983 Regents of the University of California.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms are permitted
11 * provided that the above copyright notice and this paragraph are
12 * duplicated in all such forms and that any documentation,
13 * advertising materials, and other materials related to such
14 * distribution and use acknowledge that the software was developed
15 * by the University of California, Berkeley.  The name of the
16 * University may not be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 */
19
20#include "defs.h"
21#include <string.h>
22#include <setjmp.h>
23#include <netdb.h>
24#include <signal.h>
25#include <krb5defs.h>
26
27#ifndef RDIST
28#ifdef SYSV
29/*
30 * Historically, the rdist program has had the following hard-coded
31 * pathname.  Some operating systems attempt to "improve" the
32 * directory layout, in the process re-locating the rdist binary
33 * to some other location.  However, the first original implementation
34 * sets a standard of sorts.  In order to interoperate with other
35 * systems, our implementation must do two things: It must provide
36 * the an rdist binary at the pathname below, and it must use this
37 * pathname when executing rdist on remote systems via the rcmd()
38 * library.  Thus the hard-coded path name below can never be changed.
39 */
40#endif /* SYSV */
41#define	RDIST "/usr/ucb/rdist"
42#endif
43
44FILE	*lfp;			/* log file for recording files updated */
45struct	subcmd *subcmds;	/* list of sub-commands for current cmd */
46jmp_buf	env;
47
48void	cleanup();
49void	lostconn();
50static int	init_service(int);
51static struct servent *sp;
52
53static void notify(char *file, char *rhost, struct namelist *to, time_t lmod);
54static void rcmptime(struct stat *st);
55static void cmptime(char *name);
56static void dodcolon(char **filev, struct namelist *files, char *stamp,
57    struct subcmd *cmds);
58static void closeconn(void);
59static void doarrow(char **filev, struct namelist *files, char *rhost,
60    struct subcmd *cmds);
61static int makeconn(char *rhost);
62static int okname(char *name);
63
64#ifdef SYSV
65#include <libgen.h>
66
67static char *recomp;
68static char *errstring = "regcmp failed for some unknown reason";
69
70char *
71re_comp(char *s)
72{
73	if ((int)recomp != 0)
74		free(recomp);
75	recomp = regcmp(s, (char *)0);
76	if (recomp == NULL)
77		return (errstring);
78	else
79		return ((char *)0);
80}
81
82
83static int
84re_exec(char *s)
85{
86	if ((int)recomp == 0)
87		return (-1);
88	if (regex(recomp, s) == NULL)
89		return (0);
90	else
91		return (1);
92}
93#endif /* SYSV */
94
95/*
96 * Do the commands in cmds (initialized by yyparse).
97 */
98void
99docmds(char **dhosts, int argc, char **argv)
100{
101	struct cmd *c;
102	struct namelist *f;
103	char **cpp;
104	extern struct cmd *cmds;
105
106	/* protect backgrounded rdist */
107	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
108		(void) signal(SIGINT, cleanup);
109
110	/* ... and running via nohup(1) */
111	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
112		(void) signal(SIGHUP, cleanup);
113	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
114		(void) signal(SIGQUIT, cleanup);
115
116	(void) signal(SIGTERM, cleanup);
117
118	if (debug) {
119		if (!cmds)
120			printf("docmds:  cmds == NULL\n");
121		else {
122			printf("docmds:  cmds ");
123			prcmd(cmds);
124		}
125	}
126	for (c = cmds; c != NULL; c = c->c_next) {
127		if (dhosts != NULL && *dhosts != NULL) {
128			for (cpp = dhosts; *cpp; cpp++)
129				if (strcmp(c->c_name, *cpp) == 0)
130					goto fndhost;
131			continue;
132		}
133	fndhost:
134		if (argc) {
135			for (cpp = argv; *cpp; cpp++) {
136				if (c->c_label != NULL &&
137				    strcmp(c->c_label, *cpp) == 0) {
138					cpp = NULL;
139					goto found;
140				}
141				for (f = c->c_files; f != NULL; f = f->n_next)
142					if (strcmp(f->n_name, *cpp) == 0)
143						goto found;
144			}
145			continue;
146		} else
147			cpp = NULL;
148	found:
149		switch (c->c_type) {
150		case ARROW:
151			doarrow(cpp, c->c_files, c->c_name, c->c_cmds);
152			break;
153		case DCOLON:
154			dodcolon(cpp, c->c_files, c->c_name, c->c_cmds);
155			break;
156		default:
157			fatal("illegal command type %d\n", c->c_type);
158		}
159	}
160	closeconn();
161}
162
163/*
164 * Process commands for sending files to other machines.
165 */
166static void
167doarrow(char **filev, struct namelist *files, char *rhost, struct subcmd *cmds)
168{
169	struct namelist *f;
170	struct subcmd *sc;
171	char **cpp;
172	int n, ddir, opts = options;
173
174	if (debug)
175		printf("doarrow(%x, %s, %x)\n", files, rhost, cmds);
176
177	if (files == NULL) {
178		error("no files to be updated\n");
179		return;
180	}
181
182	subcmds = cmds;
183	ddir = files->n_next != NULL;	/* destination is a directory */
184	if (nflag)
185		printf("updating host %s\n", rhost);
186	else {
187		if (setjmp(env))
188			goto done;
189		(void) signal(SIGPIPE, lostconn);
190		if (!makeconn(rhost))
191			return;
192		if (!nflag)
193			if ((lfp = fopen(Tmpfile, "w")) == NULL) {
194				fatal("cannot open %s\n", Tmpfile);
195				exit(1);
196			}
197	}
198	for (f = files; f != NULL; f = f->n_next) {
199		if (filev) {
200			for (cpp = filev; *cpp; cpp++)
201				if (strcmp(f->n_name, *cpp) == 0)
202					goto found;
203			continue;
204		}
205	found:
206		n = 0;
207		for (sc = cmds; sc != NULL; sc = sc->sc_next) {
208			if (sc->sc_type != INSTALL)
209				continue;
210			n++;
211			install(f->n_name, sc->sc_name,
212			    sc->sc_name == NULL ? 0 : ddir, sc->sc_options);
213			opts = sc->sc_options;
214		}
215		if (n == 0)
216			install(f->n_name, NULL, 0, options);
217	}
218done:
219	if (!nflag) {
220		(void) signal(SIGPIPE, cleanup);
221		(void) fclose(lfp);
222		lfp = NULL;
223	}
224	for (sc = cmds; sc != NULL; sc = sc->sc_next)
225		if (sc->sc_type == NOTIFY)
226			notify(Tmpfile, rhost, sc->sc_args, 0);
227	if (!nflag) {
228		(void) unlink(Tmpfile);
229		for (; ihead != NULL; ihead = ihead->nextp) {
230			free(ihead);
231			if ((opts & IGNLNKS) || ihead->count == 0)
232				continue;
233			log(lfp, "%s: Warning: missing links\n",
234			    ihead->pathname);
235		}
236	}
237}
238
239static int
240init_service(int krb5flag)
241{
242	boolean_t success = B_FALSE;
243
244	if (krb5flag > 0) {
245		if ((sp = getservbyname("kshell", "tcp")) == NULL) {
246			fatal("kshell/tcp: unknown service");
247			(void) fprintf(stderr,
248			    gettext("trying shell/tcp service...\n"));
249		} else {
250			success = B_TRUE;
251		}
252	} else {
253		if ((sp = getservbyname("shell", "tcp")) == NULL) {
254			fatal("shell/tcp: unknown service");
255			exit(1);
256		} else {
257			success = B_TRUE;
258		}
259	}
260	return (success);
261}
262/*
263 * Create a connection to the rdist server on the machine rhost.
264 */
265static int
266makeconn(char *rhost)
267{
268	char *ruser, *cp;
269	static char *cur_host = NULL;
270	static int port = -1;
271	char tuser[20];
272	int n;
273	extern char user[];
274
275	if (debug)
276		printf("makeconn(%s)\n", rhost);
277
278	if (cur_host != NULL && rem >= 0) {
279		if (strcmp(cur_host, rhost) == 0)
280			return (1);
281		closeconn();
282	}
283	cur_host = rhost;
284	cp = index(rhost, '@');
285	if (cp != NULL) {
286		char c = *cp;
287
288		*cp = '\0';
289		strncpy(tuser, rhost, sizeof (tuser)-1);
290		*cp = c;
291		rhost = cp + 1;
292		ruser = tuser;
293		if (*ruser == '\0')
294			ruser = user;
295		else if (!okname(ruser))
296			return (0);
297	} else
298		ruser = user;
299	if (!qflag)
300		printf("updating host %s\n", rhost);
301	(void) snprintf(buf, RDIST_BUFSIZ, "%s%s -Server%s",
302	    encrypt_flag ? "-x " : "", RDIST, qflag ? " -q" : "");
303	if (port < 0) {
304		if (debug_port == 0) {
305			if ((retval = (int)init_service(krb5auth_flag)) == 0) {
306				krb5auth_flag = encrypt_flag = 0;
307				(void) init_service(krb5auth_flag);
308			}
309			port = sp->s_port;
310
311		} else {
312			port = debug_port;
313		}
314	}
315
316	if (debug) {
317		printf("port = %d, luser = %s, ruser = %s\n", ntohs(port),
318		    user, ruser);
319		printf("buf = %s\n", buf);
320	}
321
322	fflush(stdout);
323
324	if (krb5auth_flag > 0) {
325		if ((encrypt_flag > 0) && (!krb5_privacy_allowed())) {
326			(void) fprintf(stderr, gettext("rdist: Encryption "
327			    " not supported.\n"));
328			exit(1);
329		}
330
331		authopts = AP_OPTS_MUTUAL_REQUIRED;
332
333		status = kcmd(&rem, &rhost, port, user, ruser,
334		    buf, 0, "host", krb_realm, bsd_context, &auth_context,
335		    &cred,
336		    0,	/* No need for sequence number */
337		    0,	/* No need for server seq # */
338		    authopts,
339		    1,	/* Always set anyport */
340		    &kcmd_proto);
341		if (status) {
342			/*
343			 * If new protocol requested, we dont
344			 * fallback to less secure ones.
345			 */
346			if (kcmd_proto == KCMD_NEW_PROTOCOL) {
347				(void) fprintf(stderr, gettext("rdist: kcmdv2 "
348				    "to host %s failed - %s\n"
349				    "Fallback to normal rdist denied."),
350				    host, error_message(status));
351				exit(1);
352			}
353			/* check NO_TKT_FILE or equivalent... */
354			if (status != -1) {
355				(void) fprintf(stderr, gettext("rdist: "
356				    "kcmd to host %s failed - %s\n"
357				    "trying normal rdist...\n\n"),
358				    host, error_message(status));
359			} else {
360				(void) fprintf(stderr,
361				    gettext("trying normal rdist...\n"));
362			}
363			/*
364			 * kcmd() failed, so we now fallback to normal rdist
365			 */
366			krb5auth_flag = encrypt_flag = 0;
367			(void) init_service(krb5auth_flag);
368			port = sp->s_port;
369			goto do_rcmd;
370		}
371#ifdef DEBUG
372		else {
373			(void) fprintf(stderr, gettext("Kerberized rdist "
374			    "session, port %d in use "), port);
375			if (kcmd_proto == KCMD_OLD_PROTOCOL)
376				(void) fprintf(stderr,
377				    gettext("[kcmd ver.1].\n"));
378			else
379				(void) fprintf(stderr,
380				    gettext("[kcmd ver.2].\n"));
381		}
382#endif /* DEBUG */
383		session_key = &cred->keyblock;
384
385		if (kcmd_proto == KCMD_NEW_PROTOCOL) {
386			status = krb5_auth_con_getlocalsubkey(bsd_context,
387			    auth_context, &session_key);
388			if (status) {
389				com_err("rdist", status,
390				    "determining subkey for session");
391				exit(1);
392			}
393			if (!session_key) {
394				com_err("rdist", 0,
395				    "no subkey negotiated for connection");
396				exit(1);
397			}
398		}
399
400		eblock.crypto_entry = session_key->enctype;
401		eblock.key = (krb5_keyblock *)session_key;
402
403		init_encrypt(encrypt_flag, bsd_context, kcmd_proto, &desinbuf,
404		    &desoutbuf, CLIENT, &eblock);
405
406
407		if (encrypt_flag > 0) {
408			char *s = gettext("This rdist session is using "
409			    "encryption for all data transmissions.\r\n");
410			(void) write(2, s, strlen(s));
411		}
412
413	}
414	else
415do_rcmd:
416	{
417		rem = rcmd_af(&rhost, port, user, ruser, buf, 0, AF_INET6);
418	}
419
420	if (rem < 0)
421		return (0);
422
423	cp = buf;
424	if (desread(rem, cp, 1, 0) != 1)
425		lostconn();
426	if (*cp == 'V') {
427		do {
428			if (desread(rem, cp, 1, 0) != 1)
429				lostconn();
430		} while (*cp++ != '\n' && cp < &buf[RDIST_BUFSIZ]);
431		*--cp = '\0';
432		cp = buf;
433		n = 0;
434		while (*cp >= '0' && *cp <= '9')
435			n = (n * 10) + (*cp++ - '0');
436		if (*cp == '\0' && n == VERSION)
437			return (1);
438		error("connection failed: version numbers don't match"
439		    " (local %d, remote %d)\n", VERSION, n);
440	} else {
441		error("connection failed: version numbers don't match\n");
442	}
443	closeconn();
444	return (0);
445}
446
447/*
448 * Signal end of previous connection.
449 */
450static void
451closeconn(void)
452{
453	if (debug)
454		printf("closeconn()\n");
455
456	if (rem >= 0) {
457		(void) deswrite(rem, "\2\n", 2, 0);
458		(void) close(rem);
459		rem = -1;
460	}
461}
462
463void
464lostconn(void)
465{
466	if (iamremote)
467		cleanup();
468	log(lfp, "rdist: lost connection\n");
469	longjmp(env, 1);
470}
471
472static int
473okname(char *name)
474{
475	char *cp = name;
476	int c;
477
478	do {
479		c = *cp;
480		if (c & 0200)
481			goto bad;
482		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
483			goto bad;
484		cp++;
485	} while (*cp);
486	return (1);
487bad:
488	error("invalid user name %s\n", name);
489	return (0);
490}
491
492time_t	lastmod;
493FILE	*tfp;
494extern	char target[], *tp;
495
496/*
497 * Process commands for comparing files to time stamp files.
498 */
499static void
500dodcolon(char **filev, struct namelist *files, char *stamp, struct subcmd *cmds)
501{
502	struct subcmd *sc;
503	struct namelist *f;
504	char **cpp;
505	struct timeval tv[2];
506	struct stat stb;
507
508	if (debug)
509		printf("dodcolon()\n");
510
511	if (files == NULL) {
512		error("no files to be updated\n");
513		return;
514	}
515	if (stat(stamp, &stb) < 0) {
516		error("%s: %s\n", stamp, strerror(errno));
517		return;
518	}
519	if (debug)
520		printf("%s: %d\n", stamp, stb.st_mtime);
521
522	subcmds = cmds;
523	lastmod = stb.st_mtime;
524	if (nflag || (options & VERIFY))
525		tfp = NULL;
526	else {
527		if ((tfp = fopen(Tmpfile, "w")) == NULL) {
528			error("%s: %s\n", stamp, strerror(errno));
529			return;
530		}
531		(void) gettimeofday(&tv[0], (struct timezone *)NULL);
532		tv[1] = tv[0];
533		(void) utimes(stamp, tv);
534	}
535
536	for (f = files; f != NULL; f = f->n_next) {
537		if (filev) {
538			for (cpp = filev; *cpp; cpp++)
539				if (strcmp(f->n_name, *cpp) == 0)
540					goto found;
541			continue;
542		}
543	found:
544		tp = NULL;
545		cmptime(f->n_name);
546	}
547
548	if (tfp != NULL)
549		(void) fclose(tfp);
550	for (sc = cmds; sc != NULL; sc = sc->sc_next)
551		if (sc->sc_type == NOTIFY)
552			notify(Tmpfile, NULL, sc->sc_args, lastmod);
553	if (!nflag && !(options & VERIFY))
554		(void) unlink(Tmpfile);
555}
556
557/*
558 * Compare the mtime of file to the list of time stamps.
559 */
560static void
561cmptime(char *name)
562{
563	struct stat stb;
564
565	if (debug)
566		printf("cmptime(%s)\n", name);
567
568	if (except(name))
569		return;
570
571	if (nflag) {
572		printf("comparing dates: %s\n", name);
573		return;
574	}
575
576	/*
577	 * first time cmptime() is called?
578	 */
579	if (tp == NULL) {
580		if (exptilde(target, RDIST_BUFSIZ, name) == NULL)
581			return;
582		tp = name = target;
583		while (*tp)
584			tp++;
585	}
586	if (access(name, 4) < 0 || stat(name, &stb) < 0) {
587		error("%s: %s\n", name, strerror(errno));
588		return;
589	}
590
591	switch (stb.st_mode & S_IFMT) {
592	case S_IFREG:
593		break;
594
595	case S_IFDIR:
596		rcmptime(&stb);
597		return;
598
599	default:
600		error("%s: not a plain file\n", name);
601		return;
602	}
603
604	if (stb.st_mtime > lastmod)
605		log(tfp, "new: %s\n", name);
606}
607
608static void
609rcmptime(struct stat *st)
610{
611	DIR *d;
612	struct dirent *dp;
613	char *cp;
614	char *otp;
615	int len;
616
617	if (debug)
618		printf("rcmptime(%x)\n", st);
619
620	if ((d = opendir(target)) == NULL) {
621		error("%s: %s\n", target, strerror(errno));
622		return;
623	}
624	otp = tp;
625	len = tp - target;
626	while (dp = readdir(d)) {
627		if ((strcmp(dp->d_name, ".") == 0) ||
628		    (strcmp(dp->d_name, "..") == 0))
629			continue;
630		if (len + 1 + strlen(dp->d_name) >= RDIST_BUFSIZ - 1) {
631			error("%s/%s: Name too long\n", target, dp->d_name);
632			continue;
633		}
634		tp = otp;
635		*tp++ = '/';
636		cp = dp->d_name;
637		while (*tp++ = *cp++)
638			;
639		tp--;
640		cmptime(target);
641	}
642	closedir(d);
643	tp = otp;
644	*tp = '\0';
645}
646
647/*
648 * Notify the list of people the changes that were made.
649 * rhost == NULL if we are mailing a list of changes compared to at time
650 * stamp file.
651 */
652static void
653notify(char *file, char *rhost, struct namelist *to, time_t lmod)
654{
655	int fd, len;
656	FILE *pf, *popen();
657	struct stat stb;
658
659	if ((options & VERIFY) || to == NULL)
660		return;
661	if (!qflag) {
662		printf("notify ");
663		if (rhost)
664			printf("@%s ", rhost);
665		prnames(to);
666	}
667	if (nflag)
668		return;
669
670	if ((fd = open(file, 0)) < 0) {
671		error("%s: %s\n", file, strerror(errno));
672		return;
673	}
674	if (fstat(fd, &stb) < 0) {
675		error("%s: %s\n", file, strerror(errno));
676		(void) close(fd);
677		return;
678	}
679	if (stb.st_size == 0) {
680		(void) close(fd);
681		return;
682	}
683	/*
684	 * Create a pipe to mailling program.
685	 */
686	pf = popen(MAILCMD, "w");
687	if (pf == NULL) {
688		error("notify: \"%s\" failed\n", MAILCMD);
689		(void) close(fd);
690		return;
691	}
692	/*
693	 * Output the proper header information.
694	 */
695	fprintf(pf, "From: rdist (Remote distribution program)\n");
696	fprintf(pf, "To:");
697	if (!any('@', to->n_name) && rhost != NULL)
698		fprintf(pf, " %s@%s", to->n_name, rhost);
699	else
700		fprintf(pf, " %s", to->n_name);
701	to = to->n_next;
702	while (to != NULL) {
703		if (!any('@', to->n_name) && rhost != NULL)
704			fprintf(pf, ", %s@%s", to->n_name, rhost);
705		else
706			fprintf(pf, ", %s", to->n_name);
707		to = to->n_next;
708	}
709	putc('\n', pf);
710	if (rhost != NULL)
711		fprintf(pf, "Subject: files updated by rdist from %s to %s\n",
712		    host, rhost);
713	else
714		fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod));
715	putc('\n', pf);
716
717	while ((len = read(fd, buf, RDIST_BUFSIZ)) > 0)
718		(void) fwrite(buf, 1, len, pf);
719	(void) close(fd);
720	(void) pclose(pf);
721}
722
723/*
724 * Return true if name is in the list.
725 */
726int
727inlist(struct namelist *list, char *file)
728{
729	struct namelist *nl;
730
731	for (nl = list; nl != NULL; nl = nl->n_next)
732		if (strcmp(file, nl->n_name) == 0)
733			return (1);
734	return (0);
735}
736
737/*
738 * Return TRUE if file is in the exception list.
739 */
740int
741except(char *file)
742{
743	struct	subcmd *sc;
744	struct	namelist *nl;
745
746	if (debug)
747		printf("except(%s)\n", file);
748
749	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
750		if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN)
751			continue;
752		for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
753			if (sc->sc_type == EXCEPT) {
754				if (strcmp(file, nl->n_name) == 0)
755					return (1);
756				continue;
757			}
758			re_comp(nl->n_name);
759			if (re_exec(file) > 0)
760				return (1);
761		}
762	}
763	return (0);
764}
765
766char *
767colon(char *cp)
768{
769	while (*cp) {
770		if (*cp == ':')
771			return (cp);
772		if (*cp == '/')
773			return (0);
774		cp++;
775	}
776	return (0);
777}
778