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