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