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
30char *piece_dir = NULL;		/* Where to store pieces of deltas. */
31char *delta_dir = NULL;		/* Where to store completed deltas. */
32char *base_dir = NULL;		/* The tree to apply deltas to. */
33int delete_after = 0;		/* Delete deltas after ctm applies them. */
34int apply_verbose = 0;		/* Run with '-v' */
35int set_time = 0;		/* Set the time of the files that is changed. */
36int mask = 0;			/* The current umask */
37
38void apply_complete(void);
39int read_piece(char *input_file);
40int combine_if_complete(char *delta, int pce, int npieces);
41int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
42int decode_line(char *line, char *out_buf);
43int 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 */
59int
60main(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 */
140void
141apply_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 */
258int
259read_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 */
432int
433combine_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 */
499int
500combine(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 */
583static 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 */
609int
610decode_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 */
655int
656lock_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    }
673