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