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