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