docmd.c revision 1.25 1 /* $NetBSD: docmd.c,v 1.25 2003/08/07 11:15:35 agc Exp $ */
2
3 /*
4 * Copyright (c) 1983, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)docmd.c 8.1 (Berkeley) 6/9/93";
36 #else
37 __RCSID("$NetBSD: docmd.c,v 1.25 2003/08/07 11:15:35 agc Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include <sys/types.h>
42 #include <sys/ioctl.h>
43
44 #include <errno.h>
45 #include <netdb.h>
46 #include <regex.h>
47 #include <setjmp.h>
48 #include <fcntl.h>
49
50 #include "defs.h"
51
52 FILE *lfp; /* log file for recording files updated */
53 struct subcmd *subcmds; /* list of sub-commands for current cmd */
54 jmp_buf env;
55
56 static int remerr = -1; /* Remote stderr */
57
58 static int makeconn(char *);
59 static int okname(char *);
60 static void closeconn(void);
61 static void cmptime(char *);
62 static void doarrow(char **,
63 struct namelist *, char *, struct subcmd *);
64 static void dodcolon(char **,
65 struct namelist *, char *, struct subcmd *);
66 static void notify(char *, char *, struct namelist *, time_t);
67 static void rcmptime(struct stat *);
68
69 /*
70 * Do the commands in cmds (initialized by yyparse).
71 */
72 void
73 docmds(char **dhosts, int argc, char **argv)
74 {
75 struct cmd *c;
76 struct namelist *f;
77 char **cpp;
78 extern struct cmd *cmds;
79
80 signal(SIGHUP, cleanup);
81 signal(SIGINT, cleanup);
82 signal(SIGQUIT, cleanup);
83 signal(SIGTERM, cleanup);
84
85 for (c = cmds; c != NULL; c = c->c_next) {
86 if (dhosts != NULL && *dhosts != NULL) {
87 for (cpp = dhosts; *cpp; cpp++)
88 if (strcmp(c->c_name, *cpp) == 0)
89 goto fndhost;
90 continue;
91 }
92 fndhost:
93 if (argc) {
94 for (cpp = argv; *cpp; cpp++) {
95 if (c->c_label != NULL &&
96 strcmp(c->c_label, *cpp) == 0) {
97 cpp = NULL;
98 goto found;
99 }
100 for (f = c->c_files; f != NULL; f = f->n_next)
101 if (strcmp(f->n_name, *cpp) == 0)
102 goto found;
103 }
104 continue;
105 } else
106 cpp = NULL;
107 found:
108 switch (c->c_type) {
109 case ARROW:
110 doarrow(cpp, c->c_files, c->c_name, c->c_cmds);
111 break;
112 case DCOLON:
113 dodcolon(cpp, c->c_files, c->c_name, c->c_cmds);
114 break;
115 default:
116 fatal("illegal command type %d\n", c->c_type);
117 }
118 }
119 closeconn();
120 }
121
122 /*
123 * Process commands for sending files to other machines.
124 */
125 static void
126 doarrow(char **filev, struct namelist *files, char *rhost, struct subcmd *cmds)
127 {
128 struct namelist *f;
129 struct subcmd *sc;
130 char **cpp;
131 int n, ddir, opts = options;
132
133 #if __GNUC__ /* XXX borken compiler alert! */
134 (void)&ddir;
135 (void)&opts;
136 #endif
137
138 if (debug)
139 printf("doarrow(%lx, %s, %lx)\n",
140 (long)files, rhost, (long)cmds);
141
142 if (files == NULL) {
143 error("no files to be updated\n");
144 return;
145 }
146
147 subcmds = cmds;
148 ddir = files->n_next != NULL; /* destination is a directory */
149 if (nflag)
150 printf("updating host %s\n", rhost);
151 else {
152 if (setjmp(env))
153 goto done;
154 signal(SIGPIPE, lostconn);
155 if (!makeconn(rhost))
156 return;
157 if ((lfp = fopen(tempfile, "w")) == NULL) {
158 fatal("cannot open %s\n", tempfile);
159 exit(1);
160 }
161 }
162 for (f = files; f != NULL; f = f->n_next) {
163 if (filev) {
164 for (cpp = filev; *cpp; cpp++)
165 if (strcmp(f->n_name, *cpp) == 0)
166 goto found;
167 if (!nflag && lfp)
168 (void) fclose(lfp);
169 continue;
170 }
171 found:
172 n = 0;
173 for (sc = cmds; sc != NULL; sc = sc->sc_next) {
174 if (sc->sc_type != INSTALL)
175 continue;
176 n++;
177 install(f->n_name, sc->sc_name,
178 sc->sc_name == NULL ? 0 : ddir, sc->sc_options);
179 opts = sc->sc_options;
180 }
181 if (n == 0)
182 install(f->n_name, NULL, 0, options);
183 }
184 done:
185 if (!nflag) {
186 (void) signal(SIGPIPE, cleanup);
187 if (lfp)
188 (void) fclose(lfp);
189 lfp = NULL;
190 }
191 for (sc = cmds; sc != NULL; sc = sc->sc_next)
192 if (sc->sc_type == NOTIFY)
193 notify(tempfile, rhost, sc->sc_args, 0);
194 if (!nflag) {
195 for (; ihead != NULL; ihead = ihead->nextp) {
196 free(ihead);
197 if ((opts & IGNLNKS) || ihead->count == 0)
198 continue;
199 if (lfp)
200 dolog(lfp, "%s: Warning: missing links\n",
201 ihead->pathname);
202 }
203 }
204 }
205
206 /*
207 * Create a connection to the rdist server on the machine rhost.
208 */
209 static int
210 makeconn(char *rhost)
211 {
212 char *ruser, *cp;
213 static char *cur_host = NULL;
214 static int port = -1;
215 char tuser[20];
216 int n;
217 extern char user[];
218
219 if (debug)
220 printf("makeconn(%s)\n", rhost);
221
222 if (cur_host != NULL && rem >= 0) {
223 if (strcmp(cur_host, rhost) == 0)
224 return(1);
225 closeconn();
226 }
227 cur_host = rhost;
228 cp = strchr(rhost, '@');
229 if (cp != NULL) {
230 char c = *cp;
231
232 *cp = '\0';
233 if (strlcpy(tuser, rhost, sizeof(tuser)) >= sizeof(tuser)) {
234 *cp = c;
235 return(0);
236 }
237 *cp = c;
238 rhost = cp + 1;
239 ruser = tuser;
240 if (*ruser == '\0')
241 ruser = user;
242 else if (!okname(ruser))
243 return(0);
244 } else
245 ruser = user;
246 if (!qflag)
247 printf("updating host %s\n", rhost);
248 (void) snprintf(buf, sizeof(buf), "%s -Server%s", _PATH_RDIST,
249 qflag ? " -q" : "");
250 if (port < 0) {
251 struct servent *sp;
252
253 if ((sp = getservbyname("shell", "tcp")) == NULL)
254 fatal("shell/tcp: unknown service");
255 port = sp->s_port;
256 }
257
258 if (debug) {
259 printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser);
260 printf("buf = %s\n", buf);
261 }
262
263 fflush(stdout);
264 seteuid(0);
265 rem = rcmd(&rhost, port, user, ruser, buf, &remerr);
266 seteuid(userid);
267 if (rem < 0)
268 return(0);
269 cp = buf;
270 if (read(rem, cp, 1) != 1)
271 lostconn(0);
272 if (*cp == 'V') {
273 do {
274 if (read(rem, cp, 1) != 1)
275 lostconn(0);
276 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
277 *--cp = '\0';
278 cp = buf;
279 n = 0;
280 while (*cp >= '0' && *cp <= '9')
281 n = (n * 10) + (*cp++ - '0');
282 if (*cp == '\0' && n == VERSION)
283 return(1);
284 error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n);
285 } else {
286 error("connection failed: version numbers don't match\n");
287 error("got unexpected input:");
288 do {
289 error("%c", *cp);
290 } while (*cp != '\n' && read(rem, cp, 1) == 1);
291 }
292 closeconn();
293 return(0);
294 }
295
296 /*
297 * Signal end of previous connection.
298 */
299 static void
300 closeconn(void)
301 {
302 if (debug)
303 printf("closeconn()\n");
304
305 if (rem >= 0) {
306 if (write(rem, "\2\n", 2) < 0 && debug)
307 printf("write failed on fd %d: %s\n", rem,
308 strerror(errno));
309 (void) close(rem);
310 (void) close(remerr);
311 rem = -1;
312 remerr = -1;
313 }
314 }
315
316 void
317 lostconn(int signo)
318 {
319 char buf[BUFSIZ];
320 int nr = -1;
321
322 if (remerr != -1)
323 if (ioctl(remerr, FIONREAD, &nr) != -1) {
324 if (nr >= sizeof(buf))
325 nr = sizeof(buf) - 1;
326 if ((nr = read(remerr, buf, nr)) > 0) {
327 buf[nr] = '\0';
328 if (buf[nr - 1] == '\n')
329 buf[--nr] = '\0';
330 }
331 }
332
333 if (nr <= 0)
334 (void) strlcpy(buf, "lost connection", sizeof(buf));
335
336 if (iamremote)
337 cleanup(0);
338 if (lfp)
339 dolog(lfp, "rdist: %s\n", buf);
340 else
341 error("%s\n", buf);
342 longjmp(env, 1);
343 }
344
345 static int
346 okname(char *name)
347 {
348 char *cp = name;
349 int c;
350
351 do {
352 c = *cp;
353 if (c & 0200)
354 goto bad;
355 if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
356 goto bad;
357 cp++;
358 } while (*cp);
359 return(1);
360 bad:
361 error("invalid user name %s\n", name);
362 return(0);
363 }
364
365 time_t lastmod;
366 FILE *tfp;
367 extern char target[], *tp;
368
369 /*
370 * Process commands for comparing files to time stamp files.
371 */
372 static void
373 dodcolon(char **filev, struct namelist *files, char *stamp, struct subcmd *cmds)
374 {
375 struct subcmd *sc;
376 struct namelist *f;
377 char **cpp;
378 struct timeval tv[2];
379 struct stat stb;
380
381 if (debug)
382 printf("dodcolon()\n");
383
384 if (files == NULL) {
385 error("no files to be updated\n");
386 return;
387 }
388 if (stat(stamp, &stb) < 0) {
389 error("%s: %s\n", stamp, strerror(errno));
390 return;
391 }
392 if (debug)
393 printf("%s: %lu\n", stamp, (u_long)stb.st_mtime);
394
395 subcmds = cmds;
396 lastmod = stb.st_mtime;
397 if (nflag || (options & VERIFY))
398 tfp = NULL;
399 else {
400 if ((tfp = fopen(tempfile, "w")) == NULL) {
401 error("%s: %s\n", tempfile, strerror(errno));
402 return;
403 }
404 (void) gettimeofday(&tv[0], (struct timezone *)0);
405 tv[1] = tv[0];
406 (void) utimes(stamp, tv);
407 }
408
409 for (f = files; f != NULL; f = f->n_next) {
410 if (filev) {
411 for (cpp = filev; *cpp; cpp++)
412 if (strcmp(f->n_name, *cpp) == 0)
413 goto found;
414 continue;
415 }
416 found:
417 tp = NULL;
418 cmptime(f->n_name);
419 }
420
421 if (tfp != NULL)
422 (void) fclose(tfp);
423 for (sc = cmds; sc != NULL; sc = sc->sc_next)
424 if (sc->sc_type == NOTIFY)
425 notify(tempfile, NULL, sc->sc_args, lastmod);
426 }
427
428 /*
429 * Compare the mtime of file to the list of time stamps.
430 */
431 static void
432 cmptime(char *name)
433 {
434 struct stat stb;
435
436 if (debug)
437 printf("cmptime(%s)\n", name);
438
439 if (except(name))
440 return;
441
442 if (nflag) {
443 printf("comparing dates: %s\n", name);
444 return;
445 }
446
447 /*
448 * first time cmptime() is called?
449 */
450 if (tp == NULL) {
451 if (exptilde(target, name) == NULL)
452 return;
453 tp = name = target;
454 while (*tp)
455 tp++;
456 }
457 if (access(name, 4) < 0 || stat(name, &stb) < 0) {
458 error("%s: %s\n", name, strerror(errno));
459 return;
460 }
461
462 switch (stb.st_mode & S_IFMT) {
463 case S_IFREG:
464 break;
465
466 case S_IFDIR:
467 rcmptime(&stb);
468 return;
469
470 default:
471 error("%s: not a plain file\n", name);
472 return;
473 }
474
475 if (stb.st_mtime > lastmod)
476 dolog(tfp, "new: %s\n", name);
477 }
478
479 static void
480 rcmptime(struct stat *st)
481 {
482 DIR *d;
483 struct dirent *dp;
484 char *cp;
485 char *otp;
486 int len;
487
488 if (debug)
489 printf("rcmptime(%lx)\n", (long)st);
490
491 if ((d = opendir(target)) == NULL) {
492 error("%s: %s\n", target, strerror(errno));
493 return;
494 }
495 otp = tp;
496 len = tp - target;
497 while ((dp = readdir(d)) != NULL) {
498 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
499 continue;
500 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
501 error("%s/%s: Name too long\n", target, dp->d_name);
502 continue;
503 }
504 tp = otp;
505 *tp++ = '/';
506 cp = dp->d_name;
507 while ((*tp++ = *cp++) != 0)
508 ;
509 tp--;
510 cmptime(target);
511 }
512 closedir(d);
513 tp = otp;
514 *tp = '\0';
515 }
516
517 /*
518 * Notify the list of people the changes that were made.
519 * rhost == NULL if we are mailing a list of changes compared to at time
520 * stamp file.
521 */
522 static void
523 notify(char *file, char *rhost, struct namelist *to, time_t lmod)
524 {
525 int fd, len;
526 struct stat stb;
527 FILE *pf;
528
529 if ((options & VERIFY) || to == NULL)
530 return;
531 if (!qflag) {
532 printf("notify ");
533 if (rhost)
534 printf("@%s ", rhost);
535 prnames(to);
536 }
537 if (nflag)
538 return;
539
540 if ((fd = open(file, 0)) < 0) {
541 error("%s: %s\n", file, strerror(errno));
542 return;
543 }
544 if (fstat(fd, &stb) < 0) {
545 error("%s: %s\n", file, strerror(errno));
546 (void) close(fd);
547 return;
548 }
549 if (stb.st_size == 0) {
550 (void) close(fd);
551 return;
552 }
553 /*
554 * Create a pipe to mailling program.
555 */
556 (void)snprintf(buf, sizeof(buf), "%s -oi -t", _PATH_SENDMAIL);
557 pf = popen(buf, "w");
558 if (pf == NULL) {
559 error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
560 (void) close(fd);
561 return;
562 }
563 /*
564 * Output the proper header information.
565 */
566 fprintf(pf, "From: rdist (Remote distribution program)\n");
567 fprintf(pf, "To:");
568 if (!any('@', to->n_name) && rhost != NULL)
569 fprintf(pf, " %s@%s", to->n_name, rhost);
570 else
571 fprintf(pf, " %s", to->n_name);
572 to = to->n_next;
573 while (to != NULL) {
574 if (!any('@', to->n_name) && rhost != NULL)
575 fprintf(pf, ", %s@%s", to->n_name, rhost);
576 else
577 fprintf(pf, ", %s", to->n_name);
578 to = to->n_next;
579 }
580 putc('\n', pf);
581 if (rhost != NULL)
582 fprintf(pf, "Subject: files updated by rdist from %s to %s\n",
583 host, rhost);
584 else
585 fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod));
586 putc('\n', pf);
587
588 while ((len = read(fd, buf, BUFSIZ)) > 0)
589 if (fwrite(buf, 1, len, pf) < 1)
590 error("%s: %s\n", file, strerror(errno));
591 (void) close(fd);
592 (void) pclose(pf);
593 }
594
595 /*
596 * Return true if name is in the list.
597 */
598 int
599 inlist(struct namelist *list, char *file)
600 {
601 struct namelist *nl;
602
603 for (nl = list; nl != NULL; nl = nl->n_next)
604 if (!strcmp(file, nl->n_name))
605 return(1);
606 return(0);
607 }
608
609 /*
610 * Return TRUE if file is in the exception list.
611 */
612 int
613 except(char *file)
614 {
615 struct subcmd *sc;
616 struct namelist *nl;
617 int err;
618 regex_t s;
619
620 if (debug)
621 printf("except(%s)\n", file);
622
623 for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
624 if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN)
625 continue;
626 for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
627 if (sc->sc_type == EXCEPT) {
628 if (!strcmp(file, nl->n_name))
629 return(1);
630 continue;
631 }
632 if ((err = regcomp(&s, nl->n_name, 0)) != 0) {
633 char ebuf[BUFSIZ];
634 (void) regerror(err, &s, ebuf, sizeof(ebuf));
635 error("%s: %s\n", nl->n_name, ebuf);
636 }
637 if (regexec(&s, file, 0, NULL, 0) == 0) {
638 regfree(&s);
639 return(1);
640 }
641 regfree(&s);
642 }
643 }
644 return(0);
645 }
646
647 char *
648 colon(char *cp)
649 {
650
651 while (*cp) {
652 if (*cp == ':')
653 return(cp);
654 if (*cp == '/')
655 return(0);
656 cp++;
657 }
658 return(0);
659 }
660