]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/ctm/ctm_rmail/ctm_rmail.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / ctm / ctm_rmail / ctm_rmail.c
1 /*
2  * Accept one (or more) ASCII encoded chunks that together make a compressed
3  * CTM delta.  Decode them and reconstruct the deltas.  Any completed
4  * deltas may be passed to ctm for unpacking.
5  *
6  * Author: Stephen McKay
7  *
8  * NOTICE: This is free software.  I hope you get some use from this program.
9  * In return you should think about all the nice people who give away software.
10  * Maybe you should write some free software too.
11  *
12  * $FreeBSD$
13  */
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <errno.h>
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include "error.h"
26 #include "options.h"
27
28 #define CTM_STATUS      ".ctm_status"
29
30 char *piece_dir = NULL;         /* Where to store pieces of deltas. */
31 char *delta_dir = NULL;         /* Where to store completed deltas. */
32 char *base_dir = NULL;          /* The tree to apply deltas to. */
33 int delete_after = 0;           /* Delete deltas after ctm applies them. */
34 int apply_verbose = 0;          /* Run with '-v' */
35 int set_time = 0;               /* Set the time of the files that is changed. */
36 int mask = 0;                   /* The current umask */
37
38 void apply_complete(void);
39 int read_piece(char *input_file);
40 int combine_if_complete(char *delta, int pce, int npieces);
41 int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
42 int decode_line(char *line, char *out_buf);
43 int lock_file(char *name);
44
45 /*
46  * If given a '-p' flag, read encoded delta pieces from stdin or file
47  * arguments, decode them and assemble any completed deltas.  If given
48  * a '-b' flag, pass any completed deltas to 'ctm' for application to
49  * the source tree.  The '-d' flag is mandatory, but either of '-p' or
50  * '-b' can be omitted.  If given the '-l' flag, notes and errors will
51  * be timestamped and written to the given file.
52  *
53  * Exit status is 0 for success or 1 for indigestible input.  That is,
54  * 0 means the encode input pieces were decoded and stored, and 1 means
55  * some input was discarded.  If a delta fails to apply, this won't be
56  * reflected in the exit status.  In this case, the delta is left in
57  * 'deltadir'.
58  */
59 int
60 main(int argc, char **argv)
61     {
62     char *log_file = NULL;
63     int status = 0;
64     int fork_ctm = 0;
65
66     mask = umask(0);
67     umask(mask);
68
69     err_prog_name(argv[0]);
70
71     OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
72         FLAG('D', delete_after)
73         FLAG('f', fork_ctm)
74         FLAG('u', set_time)
75         FLAG('v', apply_verbose)
76         STRING('p', piece_dir)
77         STRING('d', delta_dir)
78         STRING('b', base_dir)
79         STRING('l', log_file)
80     ENDOPTS
81
82     if (delta_dir == NULL)
83         usage();
84
85     if (piece_dir == NULL && (base_dir == NULL || argc > 1))
86         usage();
87
88     if (log_file != NULL)
89         err_set_log(log_file);
90
91     /*
92      * Digest each file in turn, or just stdin if no files were given.
93      */
94     if (argc <= 1)
95         {
96         if (piece_dir != NULL)
97             status = read_piece(NULL);
98         }
99     else
100         {
101         while (*++argv != NULL)
102             status |= read_piece(*argv);
103         }
104
105     /*
106      * Maybe it's time to look for and apply completed deltas with ctm.
107      *
108      * Shall we report back to sendmail immediately, and let a child do
109      * the work?  Sendmail will be waiting for us to complete, delaying
110      * other mail, and possibly some intermediate process (like MH slocal)
111      * will terminate us if we take too long!
112      *
113      * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
114      * Also, the child exit status is unimportant.
115      */
116     if (base_dir != NULL)
117         if (!fork_ctm || fork() == 0)
118             apply_complete();
119
120     return status;
121     }
122
123
124 /*
125  * Construct the file name of a piece of a delta.
126  */
127 #define mk_piece_name(fn,d,p,n) \
128     sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
129
130 /*
131  * Construct the file name of an assembled delta.
132  */
133 #define mk_delta_name(fn,d)     \
134     sprintf((fn), "%s/%s", delta_dir, (d))
135
136 /*
137  * If the next required delta is now present, let ctm lunch on it and any
138  * contiguous deltas.
139  */
140 void
141 apply_complete()
142     {
143     int i, dn;
144     int lfd;
145     FILE *fp, *ctm;
146     struct stat sb;
147     char class[20];
148     char delta[30];
149     char junk[2];
150     char fname[PATH_MAX];
151     char here[PATH_MAX];
152     char buf[PATH_MAX*2];
153
154     /*
155      * Grab a lock on the ctm mutex file so that we can be sure we are
156      * working alone, not fighting another ctm_rmail!
157      */
158     strcpy(fname, delta_dir);
159     strcat(fname, "/.mutex_apply");
160     if ((lfd = lock_file(fname)) < 0)
161         return;
162
163     /*
164      * Find out which delta ctm needs next.
165      */
166     sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
167     if ((fp = fopen(fname, "r")) == NULL)
168         {
169         close(lfd);
170         return;
171         }
172
173     i = fscanf(fp, "%19s %d %c", class, &dn, junk);
174     fclose(fp);
175     if (i != 2)
176         {
177         close(lfd);
178         return;
179         }
180
181     /*
182      * We might need to convert the delta filename to an absolute pathname.
183      */
184     here[0] = '\0';
185     if (delta_dir[0] != '/')
186         {
187         getcwd(here, sizeof(here)-1);
188         i = strlen(here) - 1;
189         if (i >= 0 && here[i] != '/')
190             {
191             here[++i] = '/';
192             here[++i] = '\0';
193             }
194         }
195
196     /*
197      * Keep applying deltas until we run out or something bad happens.
198      */
199     for (;;)
200         {
201         sprintf(delta, "%s.%04d.gz", class, ++dn);
202         mk_delta_name(fname, delta);
203
204         if (stat(fname, &sb) < 0)
205             break;
206
207         sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir,
208                                 set_time ? "-u " : "",
209                                 apply_verbose ? "-v " : "", here, fname);
210         if ((ctm = popen(buf, "r")) == NULL)
211             {
212             err("ctm failed to apply %s", delta);
213             break;
214             }
215
216         while (fgets(buf, sizeof(buf), ctm) != NULL)
217             {
218             i = strlen(buf) - 1;
219             if (i >= 0 && buf[i] == '\n')
220                 buf[i] = '\0';
221             err("ctm: %s", buf);
222             }
223
224         if (pclose(ctm) != 0)
225             {
226             err("ctm failed to apply %s", delta);
227             break;
228             }
229
230         if (delete_after)
231             unlink(fname);
232
233         err("%s applied%s", delta, delete_after ? " and deleted" : "");
234         }
235
236     /*
237      * Closing the lock file clears the lock.
238      */
239     close(lfd);
240     }
241
242
243 /*
244  * This cheap plastic checksum effectively rotates our checksum-so-far
245  * left one, then adds the character.  We only want 16 bits of it, and
246  * don't care what happens to the rest.  It ain't much, but it's small.
247  */
248 #define add_ck(sum,x)   \
249     ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
250
251
252 /*
253  * Decode the data between BEGIN and END, and stash it in the staging area.
254  * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
255  * If we have all pieces of a delta, combine them.  Returns 0 on success,
256  * and 1 for any sort of failure.
257  */
258 int
259 read_piece(char *input_file)
260     {
261     int status = 0;
262     FILE *ifp, *ofp = 0;
263     int decoding = 0;
264     int got_one = 0;
265     int line_no = 0;
266     int i, n;
267     int pce, npieces;
268     unsigned claimed_cksum;
269     unsigned short cksum = 0;
270     char out_buf[200];
271     char line[200];
272     char delta[30];
273     char pname[PATH_MAX];
274     char tname[PATH_MAX];
275     char junk[2];
276
277     ifp = stdin;
278     if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
279         {
280         err("cannot open '%s' for reading", input_file);
281         return 1;
282         }
283
284     while (fgets(line, sizeof(line), ifp) != NULL)
285         {
286         line_no++;
287
288         /*
289          * Remove all trailing white space.
290          */
291         i = strlen(line) - 1;
292         while (i > 0 && isspace(line[i]))
293                 line[i--] = '\0';
294
295         /*
296          * Look for the beginning of an encoded piece.
297          */
298         if (!decoding)
299             {
300             char *s;
301             int fd = -1;
302
303             if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c",
304                     delta, &pce, &npieces, junk) != 3)
305                 continue;
306
307             while ((s = strchr(delta, '/')) != NULL)
308                 *s = '_';
309
310             got_one++;
311             strcpy(tname, piece_dir);
312             strcat(tname, "/p.XXXXXXXXXX");
313             if ((fd = mkstemp(tname)) == -1 ||
314                 (ofp = fdopen(fd, "w")) == NULL)
315                 {
316                 if (fd != -1) {
317                     err("cannot open '%s' for writing", tname);
318                     close(fd);
319                     }
320                 else
321                     err("*mkstemp: '%s'", tname);
322                 status++;
323                 continue;
324                 }
325
326             cksum = 0xffff;
327             decoding++;
328             continue;
329             }
330
331         /*
332          * We are decoding.  Stop if we see the end flag.
333          */
334         if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
335             {
336             int e;
337
338             decoding = 0;
339
340             fflush(ofp);
341             e = ferror(ofp);
342             fclose(ofp);
343
344             if (e)
345                 err("error writing %s", tname);
346
347             if (cksum != claimed_cksum)
348                 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
349
350             if (e || cksum != claimed_cksum)
351                 {
352                 err("%s %d/%d discarded", delta, pce, npieces);
353                 unlink(tname);
354                 status++;
355                 continue;
356                 }
357
358             mk_piece_name(pname, delta, pce, npieces);
359             if (rename(tname, pname) < 0)
360                 {
361                 err("*rename: '%s' to '%s'", tname, pname);
362                 err("%s %d/%d lost!", delta, pce, npieces);
363                 unlink(tname);
364                 status++;
365                 continue;
366                 }
367
368             err("%s %d/%d stored", delta, pce, npieces);
369
370             if (!combine_if_complete(delta, pce, npieces))
371                 status++;
372             continue;
373             }
374
375         /*
376          * Must be a line of encoded data.  Decode it, sum it, and save it.
377          */
378         n = decode_line(line, out_buf);
379         if (n <= 0)
380             {
381             err("line %d: illegal character: '%c'", line_no, line[-n]);
382             err("%s %d/%d discarded", delta, pce, npieces);
383
384             fclose(ofp);
385             unlink(tname);
386
387             status++;
388             decoding = 0;
389             continue;
390             }
391
392         for (i = 0; i < n; i++)
393             add_ck(cksum, out_buf[i]);
394
395         fwrite(out_buf, sizeof(char), n, ofp);
396         }
397
398     if (decoding)
399         {
400         err("truncated file");
401         err("%s %d/%d discarded", delta, pce, npieces);
402
403         fclose(ofp);
404         unlink(tname);
405
406         status++;
407         }
408
409     if (ferror(ifp))
410         {
411         err("error reading %s", input_file == NULL ? "stdin" : input_file);
412         status++;
413         }
414
415     if (input_file != NULL)
416         fclose(ifp);
417
418     if (!got_one)
419         {
420         err("message contains no delta");
421         status++;
422         }
423
424     return (status != 0);
425     }
426
427
428 /*
429  * Put the pieces together to form a delta, if they are all present.
430  * Returns 1 on success (even if we didn't do anything), and 0 on failure.
431  */
432 int
433 combine_if_complete(char *delta, int pce, int npieces)
434     {
435     int i, e;
436     int lfd;
437     struct stat sb;
438     char pname[PATH_MAX];
439     char dname[PATH_MAX];
440     char tname[PATH_MAX];
441
442     /*
443      * We can probably just rename() it into place if it is a small delta.
444      */
445     if (npieces == 1)
446         {
447         mk_delta_name(dname, delta);
448         mk_piece_name(pname, delta, 1, 1);
449         if (rename(pname, dname) == 0)
450             {
451             chmod(dname, 0666 & ~mask);
452             err("%s complete", delta);
453             return 1;
454             }
455         }
456
457     /*
458      * Grab a lock on the reassembly mutex file so that we can be sure we are
459      * working alone, not fighting another ctm_rmail!
460      */
461     strcpy(tname, delta_dir);
462     strcat(tname, "/.mutex_build");
463     if ((lfd = lock_file(tname)) < 0)
464         return 0;
465
466     /*
467      * Are all of the pieces present?  Of course the current one is,
468      * unless all pieces are missing because another ctm_rmail has
469      * processed them already.
470      */
471     for (i = 1; i <= npieces; i++)
472         {
473         if (i == pce)
474             continue;
475         mk_piece_name(pname, delta, i, npieces);
476         if (stat(pname, &sb) < 0)
477             {
478             close(lfd);
479             return 1;
480             }
481         }
482
483     /*
484      * Stick them together.  Let combine() use our file name buffers, since
485      * we're such good buddies. :-)
486      */
487     e = combine(delta, npieces, dname, pname, tname);
488     close(lfd);
489     return e;
490     }
491
492
493 /*
494  * Put the pieces together to form a delta.
495  * Returns 1 on success, and 0 on failure.
496  * Note: dname, pname, and tname are room for some file names that just
497  * happened to by lying around in the calling routine.  Waste not, want not!
498  */
499 int
500 combine(char *delta, int npieces, char *dname, char *pname, char *tname)
501     {
502     FILE *dfp, *pfp;
503     int i, n, e;
504     char buf[BUFSIZ];
505     int fd = -1;
506
507     strcpy(tname, delta_dir);
508     strcat(tname, "/d.XXXXXXXXXX");
509     if ((fd = mkstemp(tname)) == -1 ||
510         (dfp = fdopen(fd, "w")) == NULL)
511         {
512         if (fd != -1) {
513             close(fd);
514             err("cannot open '%s' for writing", tname);
515             }
516         else
517             err("*mkstemp: '%s'", tname);
518         return 0;
519         }
520
521     /*
522      * Reconstruct the delta by reading each piece in order.
523      */
524     for (i = 1; i <= npieces; i++)
525         {
526         mk_piece_name(pname, delta, i, npieces);
527         if ((pfp = fopen(pname, "r")) == NULL)
528             {
529             err("cannot open '%s' for reading", pname);
530             fclose(dfp);
531             unlink(tname);
532             return 0;
533             }
534         while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
535             fwrite(buf, sizeof(char), n, dfp);
536         e = ferror(pfp);
537         fclose(pfp);
538         if (e)
539             {
540             err("error reading '%s'", pname);
541             fclose(dfp);
542             unlink(tname);
543             return 0;
544             }
545         }
546     fflush(dfp);
547     e = ferror(dfp);
548     fclose(dfp);
549     if (e)
550         {
551         err("error writing '%s'", tname);
552         unlink(tname);
553         return 0;
554         }
555
556     mk_delta_name(dname, delta);
557     if (rename(tname, dname) < 0)
558         {
559         err("*rename: '%s' to '%s'", tname, dname);
560         unlink(tname);
561         return 0;
562         }
563     chmod(dname, 0666 & ~mask);
564
565     /*
566      * Throw the pieces away.
567      */
568     for (i = 1; i <= npieces; i++)
569         {
570         mk_piece_name(pname, delta, i, npieces);
571         if (unlink(pname) < 0)
572             err("*unlink: '%s'", pname);
573         }
574
575     err("%s complete", delta);
576     return 1;
577     }
578
579
580 /*
581  * MIME BASE64 decode table.
582  */
583 static unsigned char from_b64[0x80] =
584     {
585     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
586     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
587     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
588     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
589     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
590     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
591     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
592     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
593     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
594     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
595     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
596     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
597     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
598     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
599     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
600     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
601     };
602
603
604 /*
605  * Decode a line of ASCII into binary.  Returns the number of bytes in
606  * the output buffer, or < 0 on indigestable input.  Error output is
607  * the negative of the index of the inedible character.
608  */
609 int
610 decode_line(char *line, char *out_buf)
611     {
612     unsigned char *ip = (unsigned char *)line;
613     unsigned char *op = (unsigned char *)out_buf;
614     unsigned long bits;
615     unsigned x;
616
617     for (;;)
618         {
619         if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
620             break;
621         bits = x << 18;
622         ip++;
623         if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
624             {
625             bits |= x << 12;
626             *op++ = bits >> 16;
627             ip++;
628             if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
629                 {
630                 bits |= x << 6;
631                 *op++ = bits >> 8;
632                 ip++;
633                 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
634                     {
635                     bits |= x;
636                     *op++ = bits;
637                     ip++;
638                     }
639                 }
640             }
641         }
642
643     if (*ip == '\0' || *ip == '\n')
644         return op - (unsigned char *)out_buf;
645     else
646         return -(ip - (unsigned char *)line);
647     }
648
649
650 /*
651  * Create and lock the given file.
652  *
653  * Clearing the lock is as simple as closing the file descriptor we return.
654  */
655 int
656 lock_file(char *name)
657     {
658     int lfd;
659
660     if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
661         {
662         err("*open: '%s'", name);
663         return -1;
664         }
665     if (flock(lfd, LOCK_EX) < 0)
666         {
667         close(lfd);
668         err("*flock: '%s'", name);
669         return -1;
670         }
671     return lfd;
672     }