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