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