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