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 them 4 * to sendmail. Optionally, the chunks can be queued to be sent later by 5 * ctm_dequeue in controlled bursts. The encoding is almost the same as 6 * MIME BASE64, and is protected by a simple checksum. 7 * 8 * Author: Stephen McKay 9 * 10 * NOTICE: This is free software. I hope you get some use from this program. 11 * In return you should think about all the nice people who give away software. 12 * Maybe you should write some free software too. 13 * 14 * $FreeBSD$ 15 */ 16 17#include <stdio.h> 18#include <stdlib.h> 19#include <string.h> 20#include <unistd.h> 21#include <fcntl.h> 22#include <sys/types.h> 23#include <sys/stat.h> 24#include <errno.h> 25#include <paths.h> 26#include <limits.h> 27#include "error.h" 28#include "options.h" 29 30#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ 31 32#define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */ 33 34int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, 35 long max_msg_size, char *mail_alias, char *queue_dir); 36int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, 37 char *mail_alias); 38int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, 39 char *mail_alias, char *queue_dir); 40void clean_up_queue(char *queue_dir); 41int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum); 42void write_header(FILE *sfp, char *mail_alias, char *delta, int pce, 43 int npieces); 44void write_trailer(FILE *sfp, unsigned sum); 45int apologise(char *delta, off_t ctm_size, long max_ctm_size, 46 char *mail_alias, char *queue_dir); 47FILE *open_sendmail(void); 48int close_sendmail(FILE *fp); 49 50int 51main(int argc, char **argv) 52 { 53 int status = 0; 54 char *delta_file; 55 char *mail_alias; 56 long max_msg_size = DEF_MAX_MSG; 57 long max_ctm_size = 0; 58 char *log_file = NULL; 59 char *queue_dir = NULL; 60 char *delta; 61 FILE *dfp; 62 struct stat sb; 63 64 err_prog_name(argv[0]); 65 66 OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias") 67 NUMBER('m', max_msg_size) 68 NUMBER('c', max_ctm_size) 69 STRING('l', log_file) 70 STRING('q', queue_dir) 71 ENDOPTS 72 73 if (argc != 3) 74 usage(); 75 76 if (log_file != NULL) 77 err_set_log(log_file); 78 79 delta_file = argv[1]; 80 mail_alias = argv[2]; 81 82 if ((delta = strrchr(delta_file, '/')) == NULL) 83 delta = delta_file; 84 else 85 delta++; 86 87 if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0) 88 { 89 err("*%s", delta_file); 90 exit(1); 91 } 92 93 if (max_ctm_size != 0 && sb.st_size > max_ctm_size) 94 status = apologise(delta, sb.st_size, max_ctm_size, mail_alias, 95 queue_dir); 96 else 97 status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size, 98 mail_alias, queue_dir); 99 100 fclose(dfp); 101 102 return status; 103 } 104 105 106/* 107 * Carve our CTM delta into pieces, encode them, and send or queue them. 108 * Returns 0 on success, and 1 on failure. 109 */ 110int 111chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, 112 long max_msg_size, char *mail_alias, char *queue_dir) 113 { 114 int npieces; 115 long msg_size; 116 long exp_size; 117 int status; 118 119#undef howmany 120#define howmany(x,y) (((x)+((y)-1)) / (y)) 121 122 /* 123 * Work out how many pieces we need, bearing in mind that each piece 124 * grows by 4/3 when encoded. We count the newlines too, but ignore 125 * all mail headers and piece headers. They are a "small" (almost 126 * constant) per message overhead that we make the user worry about. :-) 127 */ 128 exp_size = ctm_size * 4 / 3; 129 exp_size += howmany(exp_size, LINE_LENGTH); 130 npieces = howmany(exp_size, max_msg_size); 131 msg_size = howmany(ctm_size, npieces); 132 133#undef howmany 134 135 if (queue_dir == NULL) 136 status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias); 137 else 138 { 139 status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias, 140 queue_dir); 141 if (status) 142 clean_up_queue(queue_dir); 143 } 144 145 return status; 146 } 147 148 149/* 150 * Carve our CTM delta into pieces, encode them, and send them. 151 * Returns 0 on success, and 1 on failure. 152 */ 153int 154chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, 155 char *mail_alias) 156 { 157 int pce; 158 FILE *sfp; 159 unsigned sum; 160 161 /* 162 * Send each chunk directly to sendmail as it is generated. 163 * No temporary files necessary. If things turn ugly, we just 164 * have to live with the fact the we have sent only part of 165 * the delta. 166 */ 167 for (pce = 1; pce <= npieces; pce++) 168 { 169 int read_error; 170 171 if ((sfp = open_sendmail()) == NULL) 172 return 1; 173 174 write_header(sfp, mail_alias, delta, pce, npieces); 175 read_error = encode_body(sfp, dfp, msg_size, &sum); 176 if (!read_error) 177 write_trailer(sfp, sum); 178 179 if (!close_sendmail(sfp) || read_error) 180 return 1; 181 182 err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); 183 } 184 185 return 0; 186 } 187 188 189/* 190 * Construct the tmp queue file name of a delta piece. 191 */ 192#define mk_tmp_name(fn,qd,p) \ 193 sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p)) 194 195/* 196 * Construct the final queue file name of a delta piece. 197 */ 198#define mk_queue_name(fn,qd,d,p,n) \ 199 sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n)) 200 201/* 202 * Carve our CTM delta into pieces, encode them, and queue them. 203 * Returns 0 on success, and 1 on failure. 204 */ 205int 206chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, 207 char *mail_alias, char *queue_dir) 208 { 209 int pce; 210 FILE *qfp; 211 unsigned sum; 212 char tname[PATH_MAX]; 213 char qname[PATH_MAX]; 214 215 /* 216 * Store each piece in the queue directory, but under temporary names, 217 * so that they can be deleted without unpleasant consequences if 218 * anything goes wrong. We could easily fill up a disk, for example. 219 */ 220 for (pce = 1; pce <= npieces; pce++) 221 { 222 int write_error; 223 224 mk_tmp_name(tname, queue_dir, pce); 225 if ((qfp = fopen(tname, "w")) == NULL) 226 { 227 err("cannot open '%s' for writing", tname); 228 return 1; 229 } 230 231 write_header(qfp, mail_alias, delta, pce, npieces); 232 if (encode_body(qfp, dfp, msg_size, &sum)) 233 return 1; 234 write_trailer(qfp, sum); 235 236 fflush(qfp); 237 write_error = ferror(qfp); 238 fclose(qfp); 239 if (write_error) 240 { 241 err("error writing '%s'", tname); 242 return 1; 243 } 244 245 /* 246 * Give the warm success message now, instead of all in a rush 247 * during the rename phase. 248 */ 249 err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias); 250 } 251 252 /* 253 * Rename the pieces into place. If an error occurs now, we are 254 * stuffed, but there is no neat way to back out. rename() should 255 * only fail now under extreme circumstances. 256 */ 257 for (pce = 1; pce <= npieces; pce++) 258 { 259 mk_tmp_name(tname, queue_dir, pce); 260 mk_queue_name(qname, queue_dir, delta, pce, npieces); 261 if (rename(tname, qname) < 0) 262 { 263 err("*rename: '%s' to '%s'", tname, qname); 264 unlink(tname); 265 } 266 } 267 268 return 0; 269 } 270 271 272/* 273 * There may be temporary files cluttering up the queue directory. 274 */ 275void 276clean_up_queue(char *queue_dir) 277 { 278 int pce; 279 char tname[PATH_MAX]; 280 281 err("discarding queued delta pieces"); 282 for (pce = 1; ; pce++) 283 { 284 mk_tmp_name(tname, queue_dir, pce); 285 if (unlink(tname) < 0) 286 break; 287 } 288 } 289 290 291/* 292 * MIME BASE64 encode table. 293 */ 294static char to_b64[0x40] = 295 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 296 297/* 298 * This cheap plastic checksum effectively rotates our checksum-so-far 299 * left one, then adds the character. We only want 16 bits of it, and 300 * don't care what happens to the rest. It ain't much, but it's small. 301 */ 302#define add_ck(sum,x) \ 303 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 304 305/* 306 * Encode the body. Use an encoding almost the same as MIME BASE64. 307 * 308 * Characters are read from delta_fp and encoded characters are written 309 * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 310 * 311 * The body consists of lines of up to LINE_LENGTH characters. Each group 312 * of 4 characters encodes 3 input characters. Each output character encodes 313 * 6 bits. Thus 64 different characters are needed in this representation. 314 */ 315int 316encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum) 317 { 318 unsigned short cksum = 0xffff; 319 unsigned char *ip; 320 char *op; 321 int want, n, i; 322 unsigned char inbuf[LINE_LENGTH*3/4]; 323 char outbuf[LINE_LENGTH+1]; 324 325 /* 326 * Round up to the nearest line boundary, for the tiniest of gains, 327 * and lots of neatness. :-) 328 */ 329 msg_size += (LINE_LENGTH*3/4) - 1; 330 msg_size -= msg_size % (LINE_LENGTH*3/4); 331 332 while (msg_size > 0) 333 { 334 want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 335 if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 336 break; 337 msg_size -= n; 338 339 for (i = 0; i < n; i++) 340 add_ck(cksum, inbuf[i]); 341 342 /* 343 * Produce a line of encoded data. Every line length will be a 344 * multiple of 4, except for, perhaps, the last line. 345 */ 346 ip = inbuf; 347 op = outbuf; 348 while (n >= 3) 349 { 350 *op++ = to_b64[ip[0] >> 2]; 351 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 352 *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 353 *op++ = to_b64[ip[2] & 0x3f]; 354 ip += 3; 355 n -= 3; 356 } 357 if (n > 0) 358 { 359 *op++ = to_b64[ip[0] >> 2]; 360 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 361 if (n >= 2) 362 *op++ = to_b64[ip[1] << 2 & 0x3f]; 363 } 364 *op++ = '\n'; 365 fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 366 } 367 368 if (ferror(delta_fp)) 369 { 370 err("error reading input file."); 371 return 1; 372 } 373 374 *sum = cksum; 375 376 return 0; 377 } 378 379 380/* 381 * Write the mail header and data header. 382 */ 383void 384write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 385 { 386 fprintf(sfp, "From: owner-%s\n", mail_alias); 387 fprintf(sfp, "To: %s\n", mail_alias); 388 fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", delta, pce, npieces); 389 390 fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces); 391 } 392 393 394/* 395 * Write the data trailer. 396 */ 397void 398write_trailer(FILE *sfp, unsigned sum) 399 { 400 fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 401 } 402 403 404/* 405 * We're terribly sorry, but the delta is too big to send. 406 * Returns 0 on success, 1 on failure. 407 */ 408int 409apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias, 410 char *queue_dir) 411 { 412 FILE *sfp; 413 char qname[PATH_MAX]; 414 415 if (queue_dir == NULL) 416 { 417 sfp = open_sendmail(); 418 if (sfp == NULL) 419 return 1; 420 } 421 else 422 { 423 mk_queue_name(qname, queue_dir, delta, 1, 1); 424 sfp = fopen(qname, "w"); 425 if (sfp == NULL) 426 { 427 err("cannot open '%s' for writing", qname); 428 return 1; 429 } 430 } 431 432 433 fprintf(sfp, "From: owner-%s\n", mail_alias); 434 fprintf(sfp, "To: %s\n", mail_alias); 435 fprintf(sfp, "Subject: ctm-notice %s\n\n", delta); 436 437 fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta, 438 (long)ctm_size, max_ctm_size); 439 fprintf(sfp, "You can retrieve this delta via ftp.\n"); 440 441 if (queue_dir == NULL) 442 { 443 if (!close_sendmail(sfp)) 444 return 1; 445 } 446 else 447 { 448 if (fclose(sfp)!=0) 449 { 450 err("error writing '%s'", qname); 451 unlink(qname); 452 return 1; 453 } 454 } 455 456 return 0; 457 } 458 459 460/* 461 * Start a pipe to sendmail. Sendmail will decode the destination 462 * from the message contents. 463 */ 464FILE * 465open_sendmail() 466 { 467 FILE *fp; 468 char buf[100]; 469 470 sprintf(buf, "%s -odq -t", _PATH_SENDMAIL); 471 if ((fp = popen(buf, "w")) == NULL) 472 err("cannot start sendmail"); 473 return fp; 474 } 475 476 477/* 478 * Close a pipe to sendmail. Sendmail will then do its bit. 479 * Return 1 on success, 0 on failure. 480 */ 481int 482close_sendmail(FILE *fp) 483 { 484 int status; 485 486 fflush(fp); 487 if (ferror(fp)) 488 { 489 err("error writing to sendmail"); 490 return 0; 491 } 492 493 if ((status = pclose(fp)) != 0) 494 err("sendmail failed with status %d", status); 495 496 return (status == 0); 497 } 498