1 /* $NetBSD: docmd.c,v 1.30 2013/10/18 20:43:45 christos 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.30 2013/10/18 20:43:45 christos 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
docmds(char ** dhosts,int argc,char ** argv)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
doarrow(char ** filev,struct namelist * xfiles,char * rhost,struct subcmd * xcmds)126 doarrow(char **filev, struct namelist *xfiles, char *rhost,
127 struct subcmd *xcmds)
128 {
129 struct namelist *f;
130 struct subcmd *sc;
131 char **cpp;
132 int n;
133 int volatile ddir;
134 int volatile opts;
135 struct namelist * volatile files = xfiles;
136 struct subcmd * volatile cmds = xcmds;
137
138 opts = options;
139 if (debug)
140 printf("doarrow(%lx, %s, %lx)\n",
141 (long)files, rhost, (long)cmds);
142
143 if (files == NULL) {
144 error("no files to be updated\n");
145 return;
146 }
147
148 subcmds = cmds;
149 ddir = files->n_next != NULL; /* destination is a directory */
150 if (nflag)
151 printf("updating host %s\n", rhost);
152 else {
153 if (setjmp(env))
154 goto done;
155 signal(SIGPIPE, lostconn);
156 if (!makeconn(rhost))
157 return;
158 if ((lfp = fopen(tempfile, "w")) == NULL) {
159 fatal("cannot open %s\n", tempfile);
160 exit(1);
161 }
162 }
163 for (f = files; f != NULL; f = f->n_next) {
164 if (filev) {
165 for (cpp = filev; *cpp; cpp++)
166 if (strcmp(f->n_name, *cpp) == 0)
167 goto found;
168 if (!nflag && lfp)
169 (void) fclose(lfp);
170 continue;
171 }
172 found:
173 n = 0;
174 for (sc = cmds; sc != NULL; sc = sc->sc_next) {
175 if (sc->sc_type != INSTALL)
176 continue;
177 n++;
178 install(f->n_name, sc->sc_name,
179 sc->sc_name == NULL ? 0 : ddir, sc->sc_options);
180 opts = sc->sc_options;
181 }
182 if (n == 0)
183 install(f->n_name, NULL, 0, options);
184 }
185 done:
186 if (!nflag) {
187 (void) signal(SIGPIPE, cleanup);
188 if (lfp)
189 (void) fclose(lfp);
190 lfp = NULL;
191 }
192 for (sc = cmds; sc != NULL; sc = sc->sc_next)
193 if (sc->sc_type == NOTIFY)
194 notify(tempfile, rhost, sc->sc_args, 0);
195 if (!nflag) {
196 for (; ihead != NULL; ihead = ihead->nextp) {
197 free(ihead);
198 if ((opts & IGNLNKS) || ihead->count == 0)
199 continue;
200 if (lfp)
201 dolog(lfp, "%s: Warning: missing links\n",
202 ihead->pathname);
203 }
204 }
205 }
206
207 /*
208 * Create a connection to the rdist server on the machine rhost.
209 */
210 static int
makeconn(char * rhost)211 makeconn(char *rhost)
212 {
213 char *ruser, *cp;
214 static char *cur_host = NULL;
215 static int port = -1;
216 char tuser[20];
217 int n;
218 extern char user[];
219
220 if (debug)
221 printf("makeconn(%s)\n", rhost);
222
223 if (cur_host != NULL && rem >= 0) {
224 if (strcmp(cur_host, rhost) == 0)
225 return(1);
226 closeconn();
227 }
228 cur_host = rhost;
229 cp = strchr(rhost, '@');
230 if (cp != NULL) {
231 char c = *cp;
232
233 *cp = '\0';
234 if (strlcpy(tuser, rhost, sizeof(tuser)) >= sizeof(tuser)) {
235 *cp = c;
236 return(0);
237 }
238 *cp = c;
239 rhost = cp + 1;
240 ruser = tuser;
241 if (*ruser == '\0')
242 ruser = user;
243 else if (!okname(ruser))
244 return(0);
245 } else
246 ruser = user;
247 if (!qflag)
248 printf("updating host %s\n", rhost);
249 (void) snprintf(buf, sizeof(buf), "%s -Server%s", _PATH_RDIST,
250 qflag ? " -q" : "");
251 if (port < 0) {
252 struct servent *sp;
253
254 if ((sp = getservbyname("shell", "tcp")) == NULL)
255 fatal("shell/tcp: unknown service");
256 port = sp->s_port;
257 }
258
259 if (debug) {
260 printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser);
261 printf("buf = %s\n", buf);
262 }
263
264 fflush(stdout);
265 seteuid(0);
266 rem = rcmd(&rhost, port, user, ruser, buf, &remerr);
267 seteuid(userid);
268 if (rem < 0)
269 return(0);
270 cp = buf;
271 if (read(rem, cp, 1) != 1)
272 lostconn(0);
273 if (*cp == 'V') {
274 do {
275 if (read(rem, cp, 1) != 1)
276 lostconn(0);
277 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
278 *--cp = '\0';
279 cp = buf;
280 n = 0;
281 while (*cp >= '0' && *cp <= '9')
282 n = (n * 10) + (*cp++ - '0');
283 if (*cp == '\0' && n == VERSION)
284 return(1);
285 error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n);
286 } else {
287 error("connection failed: version numbers don't match\n");
288 error("got unexpected input:");
289 do {
290 error("%c", *cp);
291 } while (*cp != '\n' && read(rem, cp, 1) == 1);
292 }
293 closeconn();
294 return(0);
295 }
296
297 /*
298 * Signal end of previous connection.
299 */
300 static void
closeconn(void)301 closeconn(void)
302 {
303 if (debug)
304 printf("closeconn()\n");
305
306 if (rem >= 0) {
307 if (write(rem, "\2\n", 2) < 0 && debug)
308 printf("write failed on fd %d: %s\n", rem,
309 strerror(errno));
310 (void) close(rem);
311 (void) close(remerr);
312 rem = -1;
313 remerr = -1;
314 }
315 }
316
317 void
318 /*ARGSUSED*/
lostconn(int signo __unused)319 lostconn(int signo __unused)
320 {
321 char lcbuf[BUFSIZ];
322 int nr = -1;
323
324 if (remerr != -1)
325 if (ioctl(remerr, FIONREAD, &nr) != -1) {
326 if (nr >= (int)sizeof(lcbuf))
327 nr = sizeof(lcbuf) - 1;
328 if ((nr = read(remerr, lcbuf, nr)) > 0) {
329 lcbuf[nr] = '\0';
330 if (lcbuf[nr - 1] == '\n')
331 lcbuf[--nr] = '\0';
332 }
333 }
334
335 if (nr <= 0)
336 (void) strlcpy(lcbuf, "lost connection", sizeof(lcbuf));
337
338 if (iamremote)
339 cleanup(0);
340 if (lfp)
341 dolog(lfp, "rdist: %s\n", lcbuf);
342 else
343 error("%s\n", lcbuf);
344 longjmp(env, 1);
345 }
346
347 static int
okname(char * name)348 okname(char *name)
349 {
350 char *cp = name;
351 int c;
352
353 do {
354 c = *cp;
355 if (c & 0200)
356 goto bad;
357 if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
358 goto bad;
359 cp++;
360 } while (*cp);
361 return(1);
362 bad:
363 error("invalid user name %s\n", name);
364 return(0);
365 }
366
367 time_t lastmod;
368 FILE *tfp;
369 extern char target[], *tp;
370
371 /*
372 * Process commands for comparing files to time stamp files.
373 */
374 static void
dodcolon(char ** filev,struct namelist * files,char * stamp,struct subcmd * cmds)375 dodcolon(char **filev, struct namelist *files, char *stamp, struct subcmd *cmds)
376 {
377 struct subcmd *sc;
378 struct namelist *f;
379 char **cpp;
380 struct timeval tv[2];
381 struct stat stb;
382
383 if (debug)
384 printf("dodcolon()\n");
385
386 if (files == NULL) {
387 error("no files to be updated\n");
388 return;
389 }
390 if (stat(stamp, &stb) < 0) {
391 error("%s: %s\n", stamp, strerror(errno));
392 return;
393 }
394 if (debug)
395 printf("%s: %lu\n", stamp, (u_long)stb.st_mtime);
396
397 subcmds = cmds;
398 lastmod = stb.st_mtime;
399 if (nflag || (options & VERIFY))
400 tfp = NULL;
401 else {
402 if ((tfp = fopen(tempfile, "w")) == NULL) {
403 error("%s: %s\n", tempfile, strerror(errno));
404 return;
405 }
406 (void) gettimeofday(&tv[0], (struct timezone *)0);
407 tv[1] = tv[0];
408 (void) utimes(stamp, tv);
409 }
410
411 for (f = files; f != NULL; f = f->n_next) {
412 if (filev) {
413 for (cpp = filev; *cpp; cpp++)
414 if (strcmp(f->n_name, *cpp) == 0)
415 goto found;
416 continue;
417 }
418 found:
419 tp = NULL;
420 cmptime(f->n_name);
421 }
422
423 if (tfp != NULL)
424 (void) fclose(tfp);
425 for (sc = cmds; sc != NULL; sc = sc->sc_next)
426 if (sc->sc_type == NOTIFY)
427 notify(tempfile, NULL, sc->sc_args, lastmod);
428 }
429
430 /*
431 * Compare the mtime of file to the list of time stamps.
432 */
433 static void
cmptime(char * name)434 cmptime(char *name)
435 {
436 struct stat stb;
437
438 if (debug)
439 printf("cmptime(%s)\n", name);
440
441 if (except(name))
442 return;
443
444 if (nflag) {
445 printf("comparing dates: %s\n", name);
446 return;
447 }
448
449 /*
450 * first time cmptime() is called?
451 */
452 if (tp == NULL) {
453 if (exptilde(target, name) == NULL)
454 return;
455 tp = name = target;
456 while (*tp)
457 tp++;
458 }
459 if (access(name, 4) < 0 || stat(name, &stb) < 0) {
460 error("%s: %s\n", name, strerror(errno));
461 return;
462 }
463
464 switch (stb.st_mode & S_IFMT) {
465 case S_IFREG:
466 break;
467
468 case S_IFDIR:
469 rcmptime(&stb);
470 return;
471
472 default:
473 error("%s: not a plain file\n", name);
474 return;
475 }
476
477 if (stb.st_mtime > lastmod)
478 dolog(tfp, "new: %s\n", name);
479 }
480
481 static void
rcmptime(struct stat * st)482 rcmptime(struct stat *st)
483 {
484 DIR *d;
485 struct dirent *dp;
486 char *cp;
487 char *otp;
488 int len;
489
490 if (debug)
491 printf("rcmptime(%lx)\n", (long)st);
492
493 if ((d = opendir(target)) == NULL) {
494 error("%s: %s\n", target, strerror(errno));
495 return;
496 }
497 otp = tp;
498 len = tp - target;
499 while ((dp = readdir(d)) != NULL) {
500 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
501 continue;
502 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
503 error("%s/%s: Name too long\n", target, dp->d_name);
504 continue;
505 }
506 tp = otp;
507 *tp++ = '/';
508 cp = dp->d_name;
509 while ((*tp++ = *cp++) != 0)
510 ;
511 tp--;
512 cmptime(target);
513 }
514 closedir(d);
515 tp = otp;
516 *tp = '\0';
517 }
518
519 /*
520 * Notify the list of people the changes that were made.
521 * rhost == NULL if we are mailing a list of changes compared to at time
522 * stamp file.
523 */
524 static void
notify(char * file,char * rhost,struct namelist * to,time_t lmod)525 notify(char *file, char *rhost, struct namelist *to, time_t lmod)
526 {
527 int fd, len;
528 struct stat stb;
529 FILE *pf;
530 char *cp, *nrhost = rhost;
531
532 if ((options & VERIFY) || to == NULL)
533 return;
534
535 /* strip any leading user@ prefix from rhost */
536 if (rhost && (cp = strchr(rhost, '@')) != NULL)
537 nrhost = cp + 1;
538
539 if (!qflag) {
540 printf("notify ");
541 if (rhost)
542 printf("@%s ", nrhost);
543 prnames(to);
544 }
545 if (nflag)
546 return;
547
548 if ((fd = open(file, 0)) < 0) {
549 error("%s: %s\n", file, strerror(errno));
550 return;
551 }
552 if (fstat(fd, &stb) < 0) {
553 error("%s: %s\n", file, strerror(errno));
554 (void) close(fd);
555 return;
556 }
557 if (stb.st_size == 0) {
558 (void) close(fd);
559 return;
560 }
561 /*
562 * Create a pipe to mailling program.
563 */
564 (void)snprintf(buf, sizeof(buf), "%s -oi -t", _PATH_SENDMAIL);
565 pf = popen(buf, "w");
566 if (pf == NULL) {
567 error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
568 (void) close(fd);
569 return;
570 }
571 /*
572 * Output the proper header information.
573 */
574 fprintf(pf, "From: rdist (Remote distribution program)\n");
575 fprintf(pf, "To:");
576 if (!any('@', to->n_name) && rhost != NULL)
577 fprintf(pf, " %s@%s", to->n_name, nrhost);
578 else
579 fprintf(pf, " %s", to->n_name);
580 to = to->n_next;
581 while (to != NULL) {
582 if (!any('@', to->n_name) && rhost != NULL)
583 fprintf(pf, ", %s@%s", to->n_name, nrhost);
584 else
585 fprintf(pf, ", %s", to->n_name);
586 to = to->n_next;
587 }
588 putc('\n', pf);
589 if (rhost != NULL)
590 fprintf(pf, "Subject: files updated by rdist from %s to %s\n",
591 host, rhost);
592 else
593 fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod));
594 putc('\n', pf);
595
596 while ((len = read(fd, buf, BUFSIZ)) > 0)
597 if (fwrite(buf, 1, len, pf) < 1)
598 error("%s: %s\n", file, strerror(errno));
599 (void) close(fd);
600 (void) pclose(pf);
601 }
602
603 /*
604 * Return true if name is in the list.
605 */
606 int
inlist(struct namelist * list,char * file)607 inlist(struct namelist *list, char *file)
608 {
609 struct namelist *nl;
610
611 for (nl = list; nl != NULL; nl = nl->n_next)
612 if (!strcmp(file, nl->n_name))
613 return(1);
614 return(0);
615 }
616
617 /*
618 * Return TRUE if file is in the exception list.
619 */
620 int
except(char * file)621 except(char *file)
622 {
623 struct subcmd *sc;
624 struct namelist *nl;
625 int err;
626 regex_t s;
627
628 if (debug)
629 printf("except(%s)\n", file);
630
631 for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
632 if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN)
633 continue;
634 for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
635 if (sc->sc_type == EXCEPT) {
636 if (!strcmp(file, nl->n_name))
637 return(1);
638 continue;
639 }
640 if ((err = regcomp(&s, nl->n_name, 0)) != 0) {
641 char ebuf[BUFSIZ];
642 (void) regerror(err, &s, ebuf, sizeof(ebuf));
643 error("%s: %s\n", nl->n_name, ebuf);
644 }
645 if (regexec(&s, file, 0, NULL, 0) == 0) {
646 regfree(&s);
647 return(1);
648 }
649 regfree(&s);
650 }
651 }
652 return(0);
653 }
654
655 char *
colon(char * cp)656 colon(char *cp)
657 {
658
659 while (*cp) {
660 if (*cp == ':')
661 return(cp);
662 if (*cp == '/')
663 return(0);
664 cp++;
665 }
666 return(0);
667 }
668