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