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