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