ctm_smail.c revision 18120
1/* 2 * Send a compressed CTM delta to a recipient mailing list by encoding it 3 * in safe ASCII characters, in mailer-friendly chunks, and passing it 4 * to sendmail. The encoding is almost the same as MIME BASE64, and is 5 * protected by a simple checksum. 6 * 7 * Author: Stephen McKay 8 * 9 * NOTICE: This is free software. I hope you get some use from this program. 10 * In return you should think about all the nice people who give away software. 11 * Maybe you should write some free software too. 12 * 13 * $Id: ctm_smail.c,v 1.7 1996/09/07 18:48:44 peter Exp $ 14 */ 15 16#include <stdio.h> 17#include <stdlib.h> 18#include <string.h> 19#include <unistd.h> 20#include <fcntl.h> 21#include <sys/types.h> 22#include <sys/stat.h> 23#include <errno.h> 24#include <paths.h> 25#include "error.h" 26#include "options.h" 27 28#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ 29 30#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */ 31 32void chop_and_send(char *delta, off_t ctm_size, long max_msg_size, 33 char *mail_alias); 34void chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, 35 char *queue_dir, char *mail_alias); 36unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size); 37void write_header(FILE *sfp, char *mail_alias, char *delta, int pce, 38 int npieces); 39void write_trailer(FILE *sfp, unsigned sum); 40void apologise(char *delta, off_t ctm_size, long max_ctm_size, 41 char *mail_alias); 42FILE *open_sendmail(void); 43int close_sendmail(FILE *fp); 44int lock_queuedir(char *queue_dir); 45void free_lock(int lockf, char *queue_dir); 46void add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames); 47 48int 49main(int argc, char **argv) 50 { 51 char *delta_file; 52 char *mail_alias; 53 long max_msg_size = DEF_MAX_MSG; 54 long max_ctm_size = 0; 55 char *log_file = NULL; 56 char *queue_dir = NULL; 57 struct stat sb; 58 59 err_prog_name(argv[0]); 60 61 OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias") 62 NUMBER('m', max_msg_size) 63 NUMBER('c', max_ctm_size) 64 STRING('l', log_file) 65 STRING('q', queue_dir) 66 ENDOPTS 67 68 if (argc != 3) 69 usage(); 70 71 if (log_file != NULL) 72 err_set_log(log_file); 73 74 delta_file = argv[1]; 75 mail_alias = argv[2]; 76 77 if (stat(delta_file, &sb) < 0) 78 { 79 err("%s: %s", delta_file, strerror(errno)); 80 exit(1); 81 } 82 83 if (max_ctm_size != 0 && sb.st_size > max_ctm_size) 84 apologise(delta_file, sb.st_size, max_ctm_size, mail_alias); 85 else if (queue_dir == NULL) 86 chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias); 87 else 88 chop_and_queue(delta_file, sb.st_size, max_msg_size, queue_dir, mail_alias); 89 90 return 0; 91 } 92 93 94/* 95 * Carve our CTM delta into pieces, encode them, and send them. 96 */ 97void 98chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias) 99 { 100 int npieces; 101 long msg_size; 102 long exp_size; 103 int pce; 104 FILE *sfp; 105 FILE *dfp; 106 unsigned sum; 107 char *deltaname; 108 109#ifdef howmany 110#undef howmany 111#endif 112 113#define howmany(x, y) (((x) + ((y) - 1)) / (y)) 114 115 /* 116 * Work out how many pieces we need, bearing in mind that each piece 117 * grows by 4/3 when encoded. We count the newlines too, but ignore 118 * all mail headers and piece headers. They are a "small" (almost 119 * constant) per message overhead that we make the user worry about. :-) 120 */ 121 exp_size = ctm_size * 4 / 3; 122 exp_size += howmany(exp_size, LINE_LENGTH); 123 npieces = howmany(exp_size, max_msg_size); 124 msg_size = howmany(ctm_size, npieces); 125 126#undef howmany 127 128 if ((dfp = fopen(delta, "r")) == NULL) 129 { 130 err("cannot open '%s' for reading.", delta); 131 exit(1); 132 } 133 134 deltaname = strrchr(delta, '/'); 135 if (deltaname) 136 deltaname++; /* skip slash */ 137 else 138 deltaname = delta; 139 140 for (pce = 1; pce <= npieces; pce++) 141 { 142 sfp = open_sendmail(); 143 if (sfp == NULL) 144 exit(1); 145 write_header(sfp, mail_alias, delta, pce, npieces); 146 sum = encode_body(sfp, dfp, msg_size); 147 write_trailer(sfp, sum); 148 if (!close_sendmail(sfp)) 149 exit(1); 150 err("%s %d/%d sent to %s", deltaname, pce, npieces, mail_alias); 151 } 152 153 fclose(dfp); 154 } 155 156/* 157 * Carve our CTM delta into pieces, encode them, and drop them in the 158 * queue dir. 159 * 160 * Basic algorythm: 161 * 162 * - for (each piece) 163 * - gen. temp. file name (one which the de-queuer will ignore) 164 * - record in array 165 * - open temp. file 166 * - encode delta (including headers) into the temp file 167 * - close temp. file 168 * - end 169 * - lock queue directory 170 * - foreach (temp. file) 171 * - rename to the proper filename 172 * - end 173 * - unlock queue directory 174 * 175 * This is probably overkill, but it means that incomplete deltas 176 * don't get mailed, and also reduces the window for lock races 177 * between ctm_smail and the de-queueing process. 178 */ 179 180void 181chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, char *queue_dir, char *mail_alias) 182{ 183 int npieces, pce, len; 184 long msg_size, exp_size; 185 FILE *sfp, *dfp; 186 unsigned sum; 187 char **tempnames, *tempnam, *sn; 188 189#define howmany(x, y) (((x) + ((y) - 1)) / (y)) 190 191 /* 192 * Work out how many pieces we need, bearing in mind that each piece 193 * grows by 4/3 when encoded. We count the newlines too, but ignore 194 * all mail headers and piece headers. They are a "small" (almost 195 * constant) per message overhead that we make the user worry about. :-) 196 */ 197 exp_size = ctm_size * 4 / 3; 198 exp_size += howmany(exp_size, LINE_LENGTH); 199 npieces = howmany(exp_size, max_msg_size); 200 msg_size = howmany(ctm_size, npieces); 201 202#undef howmany 203 204 /* 205 * allocate space for the array of filenames. Try to be portable 206 * by not assuming anything to do with sizeof(char *) 207 */ 208 tempnames = malloc(npieces * sizeof(char *)); 209 if (tempnames == NULL) 210 { 211 err("malloc for tempnames failed"); 212 exit(1); 213 } 214 215 len = strlen(queue_dir) + 16; 216 tempnam = malloc(len); 217 if (tempnam == NULL) 218 { 219 err("malloc for tempnames failed"); 220 exit(1); 221 } 222 223 if ((dfp = fopen(delta, "r")) == NULL) 224 { 225 err("cannot open '%s' for reading.", delta); 226 exit(1); 227 } 228 229 if ((sn = strrchr(delta, '/')) == NULL) 230 sn = delta; 231 else 232 sn++; 233 234 for (pce = 1; pce <= npieces; pce++) 235 { 236 if (snprintf(tempnam, len, "%s/.%08d-%03d", queue_dir, getpid(), pce) >= len) 237 err("Whoops! tempnam isn't long enough"); 238 239 tempnames[pce - 1] = strdup(tempnam); 240 if (tempnames[pce - 1] == NULL) 241 { 242 err("strdup failed for temp. filename"); 243 exit(1); 244 } 245 246 sfp = fopen(tempnam, "w"); 247 if (sfp == NULL) 248 exit(1); 249 250 write_header(sfp, mail_alias, delta, pce, npieces); 251 sum = encode_body(sfp, dfp, msg_size); 252 write_trailer(sfp, sum); 253 254 if (fclose(sfp) != 0) 255 exit(1); 256 257 err("%s %d/%d created succesfully", sn, pce, npieces); 258 } 259 260 add_to_queue(queue_dir, mail_alias, delta, npieces, tempnames); 261 262 fclose(dfp); 263 264} 265 266 267/* 268 * MIME BASE64 encode table. 269 */ 270static char to_b64[0x40] = 271 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 272 273/* 274 * This cheap plastic checksum effectively rotates our checksum-so-far 275 * left one, then adds the character. We only want 16 bits of it, and 276 * don't care what happens to the rest. It ain't much, but it's small. 277 */ 278#define add_ck(sum,x) \ 279 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 280 281/* 282 * Encode the body. Use an encoding almost the same as MIME BASE64. 283 * 284 * Characters are read from delta_fp and encoded characters are written 285 * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 286 * 287 * The body consists of lines of up to LINE_LENGTH characters. Each group 288 * of 4 characters encodes 3 input characters. Each output character encodes 289 * 6 bits. Thus 64 different characters are needed in this representation. 290 */ 291unsigned 292encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size) 293 { 294 unsigned short cksum = 0xffff; 295 unsigned char *ip; 296 char *op; 297 int want, n, i; 298 unsigned char inbuf[LINE_LENGTH*3/4]; 299 char outbuf[LINE_LENGTH+1]; 300 301 /* 302 * Round up to the nearest line boundary, for the tiniest of gains, 303 * and lots of neatness. :-) 304 */ 305 msg_size += (LINE_LENGTH*3/4) - 1; 306 msg_size -= msg_size % (LINE_LENGTH*3/4); 307 308 while (msg_size > 0) 309 { 310 want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 311 if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 312 break; 313 msg_size -= n; 314 315 for (i = 0; i < n; i++) 316 add_ck(cksum, inbuf[i]); 317 318 /* 319 * Produce a line of encoded data. Every line length will be a 320 * multiple of 4, except for, perhaps, the last line. 321 */ 322 ip = inbuf; 323 op = outbuf; 324 while (n >= 3) 325 { 326 *op++ = to_b64[ip[0] >> 2]; 327 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 328 *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 329 *op++ = to_b64[ip[2] & 0x3f]; 330 ip += 3; 331 n -= 3; 332 } 333 if (n > 0) 334 { 335 *op++ = to_b64[ip[0] >> 2]; 336 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 337 if (n >= 2) 338 *op++ = to_b64[ip[1] << 2 & 0x3f]; 339 } 340 *op++ = '\n'; 341 fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 342 } 343 344 if (ferror(delta_fp)) 345 { 346 err("error reading input file."); 347 exit(1); 348 } 349 350 if (ferror(sm_fp)) 351 { 352 err("error writing encoded file"); 353 exit(1); 354 } 355 356 return cksum; 357 } 358 359 360/* 361 * Write the mail header and data header. 362 */ 363void 364write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 365 { 366 char *sn; 367 368 if ((sn = strrchr(delta, '/')) == NULL) 369 sn = delta; 370 else 371 sn++; 372 373 fprintf(sfp, "From: owner-%s\n", mail_alias); 374 fprintf(sfp, "To: %s\n", mail_alias); 375 fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", sn, pce, npieces); 376 377 fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, pce, npieces); 378 } 379 380 381/* 382 * Write the data trailer. 383 */ 384void 385write_trailer(FILE *sfp, unsigned sum) 386 { 387 fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 388 } 389 390 391/* 392 * We're terribly sorry, but the delta is too big to send. 393 */ 394void 395apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias) 396 { 397 FILE *sfp; 398 char *sn; 399 400 sfp = open_sendmail(); 401 if (sfp == NULL) 402 exit(1); 403 404 if ((sn = strrchr(delta, '/')) == NULL) 405 sn = delta; 406 else 407 sn++; 408 409 fprintf(sfp, "From: %s-owner\n", mail_alias); 410 fprintf(sfp, "To: %s\n", mail_alias); 411 fprintf(sfp, "Subject: ctm-notice %s\n\n", sn); 412 413 fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", sn, 414 (long)ctm_size, max_ctm_size); 415 fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n"); 416 417 if (!close_sendmail(sfp)) 418 exit(1); 419 } 420 421 422/* 423 * Start a pipe to sendmail. Sendmail will decode the destination 424 * from the message contents. 425 */ 426FILE * 427open_sendmail() 428 { 429 FILE *fp; 430 char buf[100]; 431 432 sprintf(buf, "%s -odq -t", _PATH_SENDMAIL); 433 if ((fp = popen(buf, "w")) == NULL) 434 err("cannot start sendmail"); 435 return fp; 436 } 437 438 439/* 440 * Close a pipe to sendmail. Sendmail will then do its bit. 441 * Return 1 on success, 0 on failure. 442 */ 443int 444close_sendmail(FILE *fp) 445 { 446 int status; 447 448 fflush(fp); 449 if (ferror(fp)) 450 { 451 err("error writing to sendmail"); 452 return 0; 453 } 454 455 if ((status = pclose(fp)) != 0) 456 err("sendmail failed with status %d", status); 457 458 return (status == 0); 459 } 460 461/* 462 * Lock the queuedir so we're the only guy messing about in there. 463 */ 464int 465lock_queuedir(char *queue_dir) 466{ 467 int fp, len; 468 char *buffer; 469 struct stat sb; 470 471 len = strlen(queue_dir) + 8; 472 473 buffer = malloc(len); 474 if (buffer == NULL) 475 { 476 err("malloc failed in lock_queuedir"); 477 exit(1); 478 } 479 480 if (snprintf(buffer, len, "%s/.lock", queue_dir) >= len) 481 err("Whoops. lock buffer too small in lock_queuedir"); 482 483 /* 484 * We do our own lockfile scanning to avoid unlink races. 60 485 * seconds should be enough to ensure that we won't get more races 486 * happening between the stat and the open/flock. 487 */ 488 489 while (stat(buffer, &sb) == 0) 490 sleep(60); 491 492 if ((fp = open(buffer, O_WRONLY | O_CREAT | O_EXLOCK, 0600)) < 0) 493 { 494 err("can't open `%s' in lock_queuedir", buffer); 495 exit(1); 496 } 497 498 snprintf(buffer, len, "%8ld", getpid()); 499 write(fp, buffer, 8); 500 501 free(buffer); 502 503 return(fp); 504} 505 506/* 507 * Lock the queuedir so we're the only guy messing about in there. 508 */ 509void 510free_lock(int lockf, char *queue_dir) 511{ 512 int len; 513 char *path; 514 515 /* 516 * Most important: free the lock before we do anything else! 517 */ 518 519 close(lockf); 520 521 len = strlen(queue_dir) + 7; 522 523 path = malloc(len); 524 if (path == NULL) 525 { 526 err("malloc failed in free_lock"); 527 exit(1); 528 } 529 530 if (snprintf(path, len, "%s/.lock", queue_dir) >= len) 531 err("lock path buffer too small in free_lock"); 532 533 if (unlink(path) != 0) 534 { 535 err("can't unlink lockfile `%s'", path); 536 exit(1); 537 } 538 539 free(path); 540} 541 542/* move everything into the queue directory. */ 543 544void 545add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames) 546{ 547 char *queuefile, *sn; 548 int pce, len, lockf; 549 550 if ((sn = strrchr(delta, '/')) == NULL) 551 sn = delta; 552 else 553 sn++; 554 555 /* try to malloc all we need BEFORE entering the lock loop */ 556 557 len = strlen(queue_dir) + strlen(sn) + 7; 558 queuefile = malloc(len); 559 if (queuefile == NULL) 560 { 561 err("can't malloc for queuefile"); 562 exit(1); 563 } 564 565 /* 566 * We should be the only process mucking around in the queue 567 * directory while we add the new queue files ... it could be 568 * awkward if the de-queue process starts it's job while we're 569 * adding files ... 570 */ 571 572 lockf = lock_queuedir(queue_dir); 573 for (pce = 0; pce < npieces; pce++) 574 { 575 struct stat sb; 576 577 if (snprintf(queuefile, len, "%s/%s+%03d", queue_dir, sn, pce + 1) >= len) 578 err("whoops, queuefile buffer is too small"); 579 580 if (stat(queuefile, &sb) == 0) 581 { 582 err("WOAH! Queue file `%s' already exists! Bailing out.", queuefile); 583 free_lock(lockf, queue_dir); 584 exit(1); 585 } 586 587 rename(tempnames[pce], queuefile); 588 } 589 590 free_lock(lockf, queue_dir); 591 592 free(queuefile); 593} 594