ctm_smail.c revision 21673
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: head/usr.sbin/ctm/ctm_smail/ctm_smail.c 21673 1997-01-14 07:20:47Z jkh $ 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 76 /* 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); 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 else 96 status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size, 97 mail_alias, queue_dir); 98 99 fclose(dfp); 100 101 return status; 102 } 103 104 105/* 106 * Carve our CTM delta into pieces, encode them, and send or queue them. 107 * Returns 0 on success, and 1 on failure. 108 */ 109int 110chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, 111 long max_msg_size, char *mail_alias, char *queue_dir) 112 { 113 int npieces; 114 long msg_size; 115 long exp_size; 116 int status; 117 118#undef howmany 119#define howmany(x,y) (((x)+((y)-1)) / (y)) 120 121 /* 122 * Work out how many pieces we need, bearing in mind that each piece 123 * grows by 4/3 when encoded. We count the newlines too, but ignore 124 * all mail headers and piece headers. They are a "small" (almost 125 * constant) per message overhead that we make the user worry about. :-) 126 */ 127 exp_size = ctm_size * 4 / 3; 128 exp_size += howmany(exp_size, LINE_LENGTH); 129 npieces = howmany(exp_size, max_msg_size); 130 msg_size = howmany(ctm_size, npieces); 131 132#undef howmany 133 134 if (queue_dir == NULL) 135 status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias); 136 else 137 { 138 status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias, 139 queue_dir); 140 if (status) 141 clean_up_queue(queue_dir); 142 } 143 144 return status; 145 } 146 147 148/* 149 * Carve our CTM delta into pieces, encode them, and send them. 150 * Returns 0 on success, and 1 on failure. 151 */ 152int 153chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, 154 char *mail_alias) 155 { 156 int pce; 157 FILE *sfp; 158 unsigned sum; 159 160 /* 161 * Send each chunk directly to sendmail as it is generated. 162 * No temporary files necessary. If things turn ugly, we just 163 * have to live with the fact the we have sent only part of 164 * the delta. 165 */ 166 for (pce = 1; pce <= npieces; pce++) 167 { 168 int read_error; 169 170 if ((sfp = open_sendmail()) == NULL) 171 return 1; 172 173 write_header(sfp, mail_alias, delta, pce, npieces); 174 read_error = encode_body(sfp, dfp, msg_size, &sum); 175 if (!read_error) 176 write_trailer(sfp, sum); 177 178 if (!close_sendmail(sfp) || read_error) 179 return 1; 180 181 err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); 182 } 183 184 return 0; 185 } 186 187 188/* 189 * Construct the tmp queue file name of a delta piece. 190 */ 191#define mk_tmp_name(fn,qd,p) \ 192 sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p)) 193 194/* 195 * Construct the final queue file name of a delta piece. 196 */ 197#define mk_queue_name(fn,qd,d,p,n) \ 198 sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n)) 199 200/* 201 * Carve our CTM delta into pieces, encode them, and queue them. 202 * Returns 0 on success, and 1 on failure. 203 */ 204int 205chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, 206 char *mail_alias, char *queue_dir) 207 { 208 int pce; 209 FILE *qfp; 210 unsigned sum; 211 char tname[PATH_MAX]; 212 char qname[PATH_MAX]; 213 214 /* 215 * Store each piece in the queue directory, but under temporary names, 216 * so that they can be deleted without unpleasant consequences if 217 * anything goes wrong. We could easily fill up a disk, for example. 218 */ 219 for (pce = 1; pce <= npieces; pce++) 220 { 221 int write_error; 222 223 mk_tmp_name(tname, queue_dir, pce); 224 if ((qfp = fopen(tname, "w")) == NULL) 225 { 226 err("cannot open '%s' for writing", tname); 227 return 1; 228 } 229 230 write_header(qfp, mail_alias, delta, pce, npieces); 231 if (encode_body(qfp, dfp, msg_size, &sum)) 232 return 1; 233 write_trailer(qfp, sum); 234 235 fflush(qfp); 236 write_error = ferror(qfp); 237 fclose(qfp); 238 if (write_error) 239 { 240 err("error writing '%s'", tname); 241 return 1; 242 } 243 244 /* 245 * Give the warm success message now, instead of all in a rush 246 * during the rename phase. 247 */ 248 err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias); 249 } 250 251 /* 252 * Rename the pieces into place. If an error occurs now, we are 253 * stuffed, but there is no neat way to back out. rename() should 254 * only fail now under extreme circumstances. 255 */ 256 for (pce = 1; pce <= npieces; pce++) 257 { 258 mk_tmp_name(tname, queue_dir, pce); 259 mk_queue_name(qname, queue_dir, delta, pce, npieces); 260 if (rename(tname, qname) < 0) 261 { 262 err("*rename: '%s' to '%s'", tname, qname); 263 unlink(tname); 264 } 265 } 266 267 return 0; 268 } 269 270 271/* 272 * There may be temporary files cluttering up the queue directory. 273 */ 274void 275clean_up_queue(char *queue_dir) 276 { 277 int pce; 278 char tname[PATH_MAX]; 279 280 err("discarding queued delta pieces"); 281 for (pce = 1; ; pce++) 282 { 283 mk_tmp_name(tname, queue_dir, pce); 284 if (unlink(tname) < 0) 285 break; 286 } 287 } 288 289 290/* 291 * MIME BASE64 encode table. 292 */ 293static char to_b64[0x40] = 294 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 295 296/* 297 * This cheap plastic checksum effectively rotates our checksum-so-far 298 * left one, then adds the character. We only want 16 bits of it, and 299 * don't care what happens to the rest. It ain't much, but it's small. 300 */ 301#define add_ck(sum,x) \ 302 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 303 304/* 305 * Encode the body. Use an encoding almost the same as MIME BASE64. 306 * 307 * Characters are read from delta_fp and encoded characters are written 308 * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 309 * 310 * The body consists of lines of up to LINE_LENGTH characters. Each group 311 * of 4 characters encodes 3 input characters. Each output character encodes 312 * 6 bits. Thus 64 different characters are needed in this representation. 313 */ 314int 315encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum) 316 { 317 unsigned short cksum = 0xffff; 318 unsigned char *ip; 319 char *op; 320 int want, n, i; 321 unsigned char inbuf[LINE_LENGTH*3/4]; 322 char outbuf[LINE_LENGTH+1]; 323 324 /* 325 * Round up to the nearest line boundary, for the tiniest of gains, 326 * and lots of neatness. :-) 327 */ 328 msg_size += (LINE_LENGTH*3/4) - 1; 329 msg_size -= msg_size % (LINE_LENGTH*3/4); 330 331 while (msg_size > 0) 332 { 333 want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 334 if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 335 break; 336 msg_size -= n; 337 338 for (i = 0; i < n; i++) 339 add_ck(cksum, inbuf[i]); 340 341 /* 342 * Produce a line of encoded data. Every line length will be a 343 * multiple of 4, except for, perhaps, the last line. 344 */ 345 ip = inbuf; 346 op = outbuf; 347 while (n >= 3) 348 { 349 *op++ = to_b64[ip[0] >> 2]; 350 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 351 *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 352 *op++ = to_b64[ip[2] & 0x3f]; 353 ip += 3; 354 n -= 3; 355 } 356 if (n > 0) 357 { 358 *op++ = to_b64[ip[0] >> 2]; 359 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 360 if (n >= 2) 361 *op++ = to_b64[ip[1] << 2 & 0x3f]; 362 } 363 *op++ = '\n'; 364 fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 365 } 366 367 if (ferror(delta_fp)) 368 { 369 err("error reading input file."); 370 return 1; 371 } 372 373 *sum = cksum; 374 375 return 0; 376 } 377 378 379/* 380 * Write the mail header and data header. 381 */ 382void 383write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 384 { 385 fprintf(sfp, "From: owner-%s\n", mail_alias); 386 fprintf(sfp, "To: %s\n", mail_alias); 387 fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", delta, pce, npieces); 388 389 fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces); 390 } 391 392 393/* 394 * Write the data trailer. 395 */ 396void 397write_trailer(FILE *sfp, unsigned sum) 398 { 399 fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 400 } 401 402 403/* 404 * We're terribly sorry, but the delta is too big to send. 405 * Returns 0 on success, 1 on failure. 406 */ 407int 408apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias) 409 { 410 FILE *sfp; 411 412 sfp = open_sendmail(); 413 if (sfp == NULL) 414 return 1; 415 416 fprintf(sfp, "From: owner-%s\n", mail_alias); 417 fprintf(sfp, "To: %s\n", mail_alias); 418 fprintf(sfp, "Subject: ctm-notice %s\n\n", delta); 419 420 fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta, 421 (long)ctm_size, max_ctm_size); 422 fprintf(sfp, "You can retrieve this delta via ftpmail, " 423 "or your good mate at the university.\n"); 424 425 if (!close_sendmail(sfp)) 426 return 1; 427 428 return 0; 429 } 430 431 432/* 433 * Start a pipe to sendmail. Sendmail will decode the destination 434 * from the message contents. 435 */ 436FILE * 437open_sendmail() 438 { 439 FILE *fp; 440 char buf[100]; 441 442 sprintf(buf, "%s -odq -t", _PATH_SENDMAIL); 443 if ((fp = popen(buf, "w")) == NULL) 444 err("cannot start sendmail"); 445 return fp; 446 } 447 448 449/* 450 * Close a pipe to sendmail. Sendmail will then do its bit. 451 * Return 1 on success, 0 on failure. 452 */ 453int 454close_sendmail(FILE *fp) 455 { 456 int status; 457 458 fflush(fp); 459 if (ferror(fp)) 460 { 461 err("error writing to sendmail"); 462 return 0; 463 } 464 465 if ((status = pclose(fp)) != 0) 466 err("sendmail failed with status %d", status); 467 468 return (status == 0); 469 } 470