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