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 *
re_comp(char * s)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
re_exec(char * s)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
docmds(char ** dhosts,int argc,char ** argv)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
doarrow(char ** filev,struct namelist * files,char * rhost,struct subcmd * cmds)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
init_service(int krb5flag)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
makeconn(char * rhost)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
closeconn(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
lostconn(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
okname(char * name)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
dodcolon(char ** filev,struct namelist * files,char * stamp,struct subcmd * cmds)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
cmptime(char * name)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
rcmptime(struct stat * st)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
notify(char * file,char * rhost,struct namelist * to,time_t lmod)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
inlist(struct namelist * list,char * file)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
except(char * file)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 *
colon(char * cp)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