xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/rdist/docmd.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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 
44 FILE	*lfp;			/* log file for recording files updated */
45 struct	subcmd *subcmds;	/* list of sub-commands for current cmd */
46 jmp_buf	env;
47 
48 void	cleanup();
49 void	lostconn();
50 static int	init_service(int);
51 static struct servent *sp;
52 
53 static void notify(char *file, char *rhost, struct namelist *to, time_t lmod);
54 static void rcmptime(struct stat *st);
55 static void cmptime(char *name);
56 static void dodcolon(char **filev, struct namelist *files, char *stamp,
57     struct subcmd *cmds);
58 static void closeconn(void);
59 static void doarrow(char **filev, struct namelist *files, char *rhost,
60     struct subcmd *cmds);
61 static int makeconn(char *rhost);
62 static int okname(char *name);
63 
64 #ifdef SYSV
65 #include <libgen.h>
66 
67 static char *recomp;
68 static char *errstring = "regcmp failed for some unknown reason";
69 
70 char *
71 re_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 
83 static int
84 re_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  */
98 void
99 docmds(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  */
166 static void
167 doarrow(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 	}
218 done:
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 
239 static int
240 init_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  */
265 static int
266 makeconn(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
415 do_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  */
450 static void
451 closeconn(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 
463 void
464 lostconn(void)
465 {
466 	if (iamremote)
467 		cleanup();
468 	log(lfp, "rdist: lost connection\n");
469 	longjmp(env, 1);
470 }
471 
472 static int
473 okname(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);
487 bad:
488 	error("invalid user name %s\n", name);
489 	return (0);
490 }
491 
492 time_t	lastmod;
493 FILE	*tfp;
494 extern	char target[], *tp;
495 
496 /*
497  * Process commands for comparing files to time stamp files.
498  */
499 static void
500 dodcolon(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  */
560 static void
561 cmptime(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 
608 static void
609 rcmptime(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  */
652 static void
653 notify(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  */
726 int
727 inlist(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  */
740 int
741 except(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 
766 char *
767 colon(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