16081Sphk/* 28857Srgrimes * Send a compressed CTM delta to a recipient mailing list by encoding it 319983Smckay * in safe ASCII characters, in mailer-friendly chunks, and passing them 419983Smckay * to sendmail. Optionally, the chunks can be queued to be sent later by 519983Smckay * ctm_dequeue in controlled bursts. The encoding is almost the same as 619983Smckay * MIME BASE64, and is protected by a simple checksum. 76081Sphk * 86081Sphk * Author: Stephen McKay 96081Sphk * 108857Srgrimes * NOTICE: This is free software. I hope you get some use from this program. 118857Srgrimes * In return you should think about all the nice people who give away software. 128857Srgrimes * Maybe you should write some free software too. 1316880Sgpalmer * 1450479Speter * $FreeBSD$ 156081Sphk */ 166081Sphk 176081Sphk#include <stdio.h> 1816880Sgpalmer#include <stdlib.h> 196081Sphk#include <string.h> 206290Sphk#include <unistd.h> 2116880Sgpalmer#include <fcntl.h> 226081Sphk#include <sys/types.h> 236081Sphk#include <sys/stat.h> 246081Sphk#include <errno.h> 256081Sphk#include <paths.h> 2619983Smckay#include <limits.h> 276081Sphk#include "error.h" 286081Sphk#include "options.h" 296081Sphk 306081Sphk#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ 316081Sphk 32202918Smckay#define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */ 336081Sphk 3419983Smckayint chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, 3519983Smckay long max_msg_size, char *mail_alias, char *queue_dir); 3619983Smckayint chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, 376081Sphk char *mail_alias); 3819983Smckayint chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, 3919983Smckay char *mail_alias, char *queue_dir); 4019983Smckayvoid clean_up_queue(char *queue_dir); 4119983Smckayint encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum); 426081Sphkvoid write_header(FILE *sfp, char *mail_alias, char *delta, int pce, 436081Sphk int npieces); 446081Sphkvoid write_trailer(FILE *sfp, unsigned sum); 4519983Smckayint apologise(char *delta, off_t ctm_size, long max_ctm_size, 46117503Skris char *mail_alias, char *queue_dir); 476081SphkFILE *open_sendmail(void); 486081Sphkint close_sendmail(FILE *fp); 496081Sphk 506290Sphkint 516081Sphkmain(int argc, char **argv) 526081Sphk { 5319983Smckay int status = 0; 546081Sphk char *delta_file; 556081Sphk char *mail_alias; 566081Sphk long max_msg_size = DEF_MAX_MSG; 576081Sphk long max_ctm_size = 0; 586081Sphk char *log_file = NULL; 5916880Sgpalmer char *queue_dir = NULL; 6019983Smckay char *delta; 6119983Smckay FILE *dfp; 626081Sphk struct stat sb; 636081Sphk 646081Sphk err_prog_name(argv[0]); 656081Sphk 6616880Sgpalmer OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias") 676081Sphk NUMBER('m', max_msg_size) 686081Sphk NUMBER('c', max_ctm_size) 696081Sphk STRING('l', log_file) 7016880Sgpalmer STRING('q', queue_dir) 716081Sphk ENDOPTS 726081Sphk 736081Sphk if (argc != 3) 746081Sphk usage(); 756081Sphk 766081Sphk if (log_file != NULL) 776081Sphk err_set_log(log_file); 786081Sphk 796081Sphk delta_file = argv[1]; 806081Sphk mail_alias = argv[2]; 816081Sphk 8219983Smckay if ((delta = strrchr(delta_file, '/')) == NULL) 8319983Smckay delta = delta_file; 8419983Smckay else 8519983Smckay delta++; 8619983Smckay 8719983Smckay if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0) 886081Sphk { 8919983Smckay err("*%s", delta_file); 906081Sphk exit(1); 916081Sphk } 928857Srgrimes 936081Sphk if (max_ctm_size != 0 && sb.st_size > max_ctm_size) 94117503Skris status = apologise(delta, sb.st_size, max_ctm_size, mail_alias, 95117503Skris queue_dir); 966081Sphk else 9719983Smckay status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size, 9819983Smckay mail_alias, queue_dir); 996081Sphk 10019983Smckay fclose(dfp); 10119983Smckay 10219983Smckay return status; 1036081Sphk } 1046081Sphk 1056081Sphk 1066081Sphk/* 10719983Smckay * Carve our CTM delta into pieces, encode them, and send or queue them. 10819983Smckay * Returns 0 on success, and 1 on failure. 1096081Sphk */ 11019983Smckayint 11119983Smckaychop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, 11219983Smckay long max_msg_size, char *mail_alias, char *queue_dir) 1136081Sphk { 1146081Sphk int npieces; 1156081Sphk long msg_size; 1166081Sphk long exp_size; 11719983Smckay int status; 1186081Sphk 11916880Sgpalmer#undef howmany 12019983Smckay#define howmany(x,y) (((x)+((y)-1)) / (y)) 12116880Sgpalmer 1226081Sphk /* 1236081Sphk * Work out how many pieces we need, bearing in mind that each piece 1246081Sphk * grows by 4/3 when encoded. We count the newlines too, but ignore 1256081Sphk * all mail headers and piece headers. They are a "small" (almost 1266081Sphk * constant) per message overhead that we make the user worry about. :-) 1276081Sphk */ 1286081Sphk exp_size = ctm_size * 4 / 3; 1296081Sphk exp_size += howmany(exp_size, LINE_LENGTH); 1306081Sphk npieces = howmany(exp_size, max_msg_size); 1316081Sphk msg_size = howmany(ctm_size, npieces); 1326081Sphk 1336081Sphk#undef howmany 1346081Sphk 13519983Smckay if (queue_dir == NULL) 13619983Smckay status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias); 13719983Smckay else 1386081Sphk { 13919983Smckay status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias, 14019983Smckay queue_dir); 14119983Smckay if (status) 14219983Smckay clean_up_queue(queue_dir); 1436081Sphk } 1446081Sphk 14519983Smckay return status; 14619983Smckay } 14718120Speter 14819983Smckay 14919983Smckay/* 15019983Smckay * Carve our CTM delta into pieces, encode them, and send them. 15119983Smckay * Returns 0 on success, and 1 on failure. 15219983Smckay */ 15319983Smckayint 15419983Smckaychop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, 15519983Smckay char *mail_alias) 15619983Smckay { 15719983Smckay int pce; 15819983Smckay FILE *sfp; 15919983Smckay unsigned sum; 16019983Smckay 16119983Smckay /* 16219983Smckay * Send each chunk directly to sendmail as it is generated. 16319983Smckay * No temporary files necessary. If things turn ugly, we just 16419983Smckay * have to live with the fact the we have sent only part of 16519983Smckay * the delta. 16619983Smckay */ 1676081Sphk for (pce = 1; pce <= npieces; pce++) 1686081Sphk { 16919983Smckay int read_error; 17019983Smckay 17119983Smckay if ((sfp = open_sendmail()) == NULL) 17219983Smckay return 1; 17319983Smckay 1746081Sphk write_header(sfp, mail_alias, delta, pce, npieces); 17519983Smckay read_error = encode_body(sfp, dfp, msg_size, &sum); 17619983Smckay if (!read_error) 17719983Smckay write_trailer(sfp, sum); 17819983Smckay 17919983Smckay if (!close_sendmail(sfp) || read_error) 18019983Smckay return 1; 18119983Smckay 18219983Smckay err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); 1836081Sphk } 1846081Sphk 18519983Smckay return 0; 1866081Sphk } 1876081Sphk 18819983Smckay 18919983Smckay/* 19019983Smckay * Construct the tmp queue file name of a delta piece. 19116880Sgpalmer */ 19219983Smckay#define mk_tmp_name(fn,qd,p) \ 19319983Smckay sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p)) 1946081Sphk 19519983Smckay/* 19619983Smckay * Construct the final queue file name of a delta piece. 19719983Smckay */ 19819983Smckay#define mk_queue_name(fn,qd,d,p,n) \ 19919983Smckay sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n)) 20019983Smckay 20119983Smckay/* 20219983Smckay * Carve our CTM delta into pieces, encode them, and queue them. 20319983Smckay * Returns 0 on success, and 1 on failure. 20419983Smckay */ 20519983Smckayint 20619983Smckaychop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, 20719983Smckay char *mail_alias, char *queue_dir) 20819983Smckay { 20919983Smckay int pce; 21019983Smckay FILE *qfp; 21116880Sgpalmer unsigned sum; 21219983Smckay char tname[PATH_MAX]; 21319983Smckay char qname[PATH_MAX]; 21416880Sgpalmer 21516880Sgpalmer /* 21619983Smckay * Store each piece in the queue directory, but under temporary names, 21719983Smckay * so that they can be deleted without unpleasant consequences if 21819983Smckay * anything goes wrong. We could easily fill up a disk, for example. 21916880Sgpalmer */ 22019983Smckay for (pce = 1; pce <= npieces; pce++) 22119983Smckay { 22219983Smckay int write_error; 22316880Sgpalmer 22419983Smckay mk_tmp_name(tname, queue_dir, pce); 22519983Smckay if ((qfp = fopen(tname, "w")) == NULL) 22619983Smckay { 22719983Smckay err("cannot open '%s' for writing", tname); 22819983Smckay return 1; 22919983Smckay } 23016880Sgpalmer 23119983Smckay write_header(qfp, mail_alias, delta, pce, npieces); 23219983Smckay if (encode_body(qfp, dfp, msg_size, &sum)) 23319983Smckay return 1; 23419983Smckay write_trailer(qfp, sum); 23519983Smckay 23619983Smckay fflush(qfp); 23719983Smckay write_error = ferror(qfp); 23819983Smckay fclose(qfp); 23919983Smckay if (write_error) 24019983Smckay { 24119983Smckay err("error writing '%s'", tname); 24219983Smckay return 1; 24319983Smckay } 24419983Smckay 24519983Smckay /* 24619983Smckay * Give the warm success message now, instead of all in a rush 24719983Smckay * during the rename phase. 24819983Smckay */ 24919983Smckay err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias); 25019983Smckay } 25119983Smckay 25216880Sgpalmer /* 25319983Smckay * Rename the pieces into place. If an error occurs now, we are 25419983Smckay * stuffed, but there is no neat way to back out. rename() should 25519983Smckay * only fail now under extreme circumstances. 25616880Sgpalmer */ 25719983Smckay for (pce = 1; pce <= npieces; pce++) 25819983Smckay { 25919983Smckay mk_tmp_name(tname, queue_dir, pce); 26019983Smckay mk_queue_name(qname, queue_dir, delta, pce, npieces); 26119983Smckay if (rename(tname, qname) < 0) 26219983Smckay { 26319983Smckay err("*rename: '%s' to '%s'", tname, qname); 26419983Smckay unlink(tname); 26519983Smckay } 26619983Smckay } 26719983Smckay 26819983Smckay return 0; 26916880Sgpalmer } 27016880Sgpalmer 27116880Sgpalmer 27219983Smckay/* 27319983Smckay * There may be temporary files cluttering up the queue directory. 27419983Smckay */ 27519983Smckayvoid 27619983Smckayclean_up_queue(char *queue_dir) 27716880Sgpalmer { 27819983Smckay int pce; 27919983Smckay char tname[PATH_MAX]; 28016880Sgpalmer 28119983Smckay err("discarding queued delta pieces"); 28219983Smckay for (pce = 1; ; pce++) 28316880Sgpalmer { 28419983Smckay mk_tmp_name(tname, queue_dir, pce); 28519983Smckay if (unlink(tname) < 0) 28619983Smckay break; 28716880Sgpalmer } 28816880Sgpalmer } 28916880Sgpalmer 29016880Sgpalmer 2916081Sphk/* 2926081Sphk * MIME BASE64 encode table. 2936081Sphk */ 2946081Sphkstatic char to_b64[0x40] = 2956081Sphk "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 2966081Sphk 2976081Sphk/* 2986081Sphk * This cheap plastic checksum effectively rotates our checksum-so-far 2996081Sphk * left one, then adds the character. We only want 16 bits of it, and 3006081Sphk * don't care what happens to the rest. It ain't much, but it's small. 3016081Sphk */ 3026081Sphk#define add_ck(sum,x) \ 3036081Sphk ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 3046081Sphk 3056081Sphk/* 3066081Sphk * Encode the body. Use an encoding almost the same as MIME BASE64. 3076081Sphk * 3086081Sphk * Characters are read from delta_fp and encoded characters are written 3096081Sphk * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 3106081Sphk * 3116081Sphk * The body consists of lines of up to LINE_LENGTH characters. Each group 3126081Sphk * of 4 characters encodes 3 input characters. Each output character encodes 3136081Sphk * 6 bits. Thus 64 different characters are needed in this representation. 3146081Sphk */ 31519983Smckayint 31619983Smckayencode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum) 3176081Sphk { 3186081Sphk unsigned short cksum = 0xffff; 3196081Sphk unsigned char *ip; 3206081Sphk char *op; 3216081Sphk int want, n, i; 3226081Sphk unsigned char inbuf[LINE_LENGTH*3/4]; 3236081Sphk char outbuf[LINE_LENGTH+1]; 3246081Sphk 3256081Sphk /* 3266081Sphk * Round up to the nearest line boundary, for the tiniest of gains, 3276081Sphk * and lots of neatness. :-) 3286081Sphk */ 3296081Sphk msg_size += (LINE_LENGTH*3/4) - 1; 3306081Sphk msg_size -= msg_size % (LINE_LENGTH*3/4); 3316081Sphk 3326081Sphk while (msg_size > 0) 3336081Sphk { 3346081Sphk want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 3356081Sphk if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 3366081Sphk break; 3376081Sphk msg_size -= n; 3386081Sphk 3396081Sphk for (i = 0; i < n; i++) 3406081Sphk add_ck(cksum, inbuf[i]); 3416081Sphk 3426081Sphk /* 3436081Sphk * Produce a line of encoded data. Every line length will be a 3446081Sphk * multiple of 4, except for, perhaps, the last line. 3456081Sphk */ 3466081Sphk ip = inbuf; 3476081Sphk op = outbuf; 3486081Sphk while (n >= 3) 3496081Sphk { 3506081Sphk *op++ = to_b64[ip[0] >> 2]; 3516081Sphk *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 3526081Sphk *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 3536081Sphk *op++ = to_b64[ip[2] & 0x3f]; 3546081Sphk ip += 3; 3556081Sphk n -= 3; 3566081Sphk } 3576081Sphk if (n > 0) 3586081Sphk { 3596081Sphk *op++ = to_b64[ip[0] >> 2]; 3606081Sphk *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 3616081Sphk if (n >= 2) 3626081Sphk *op++ = to_b64[ip[1] << 2 & 0x3f]; 3636081Sphk } 3646081Sphk *op++ = '\n'; 3656081Sphk fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 3666081Sphk } 3676081Sphk 3686081Sphk if (ferror(delta_fp)) 3696081Sphk { 3706081Sphk err("error reading input file."); 37119983Smckay return 1; 3726081Sphk } 3736081Sphk 37419983Smckay *sum = cksum; 3756081Sphk 37619983Smckay return 0; 3776081Sphk } 3786081Sphk 3796081Sphk 3806081Sphk/* 3816081Sphk * Write the mail header and data header. 3826081Sphk */ 3836081Sphkvoid 3846081Sphkwrite_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 3856081Sphk { 3866457Sphk fprintf(sfp, "From: owner-%s\n", mail_alias); 3876081Sphk fprintf(sfp, "To: %s\n", mail_alias); 38819983Smckay fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", delta, pce, npieces); 3896081Sphk 39019983Smckay fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces); 3916081Sphk } 3926081Sphk 3936081Sphk 3946081Sphk/* 3956081Sphk * Write the data trailer. 3966081Sphk */ 3976081Sphkvoid 3986081Sphkwrite_trailer(FILE *sfp, unsigned sum) 3996081Sphk { 4006081Sphk fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 4016081Sphk } 4026081Sphk 4036081Sphk 4046081Sphk/* 4056081Sphk * We're terribly sorry, but the delta is too big to send. 40619983Smckay * Returns 0 on success, 1 on failure. 4076081Sphk */ 40819983Smckayint 409117503Skrisapologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias, 410117503Skris char *queue_dir) 4116081Sphk { 4126081Sphk FILE *sfp; 413117503Skris char qname[PATH_MAX]; 4146081Sphk 415117503Skris if (queue_dir == NULL) 416117503Skris { 417117503Skris sfp = open_sendmail(); 418117503Skris if (sfp == NULL) 419117503Skris return 1; 420117503Skris } 421117503Skris else 422117503Skris { 423117503Skris mk_queue_name(qname, queue_dir, delta, 1, 1); 424117503Skris sfp = fopen(qname, "w"); 425117503Skris if (sfp == NULL) 426117503Skris { 427117503Skris err("cannot open '%s' for writing", qname); 428117503Skris return 1; 429117503Skris } 430117503Skris } 4316081Sphk 432117503Skris 43319983Smckay fprintf(sfp, "From: owner-%s\n", mail_alias); 4346081Sphk fprintf(sfp, "To: %s\n", mail_alias); 43519983Smckay fprintf(sfp, "Subject: ctm-notice %s\n\n", delta); 4366081Sphk 43719983Smckay fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta, 4386081Sphk (long)ctm_size, max_ctm_size); 439117503Skris fprintf(sfp, "You can retrieve this delta via ftp.\n"); 4406081Sphk 441117503Skris if (queue_dir == NULL) 442117503Skris { 443117503Skris if (!close_sendmail(sfp)) 444117503Skris return 1; 445117503Skris } 446117503Skris else 447117503Skris { 448117503Skris if (fclose(sfp)!=0) 449117503Skris { 450117503Skris err("error writing '%s'", qname); 451117503Skris unlink(qname); 452117503Skris return 1; 453117503Skris } 454117503Skris } 45519983Smckay 45619983Smckay return 0; 4576081Sphk } 4586081Sphk 4596081Sphk 4606081Sphk/* 4616081Sphk * Start a pipe to sendmail. Sendmail will decode the destination 4626081Sphk * from the message contents. 4636081Sphk */ 4646081SphkFILE * 4656081Sphkopen_sendmail() 4666081Sphk { 4676081Sphk FILE *fp; 4686081Sphk char buf[100]; 4696081Sphk 47018110Speter sprintf(buf, "%s -odq -t", _PATH_SENDMAIL); 4716081Sphk if ((fp = popen(buf, "w")) == NULL) 4726081Sphk err("cannot start sendmail"); 4736081Sphk return fp; 4746081Sphk } 4756081Sphk 4766081Sphk 4776081Sphk/* 4786081Sphk * Close a pipe to sendmail. Sendmail will then do its bit. 4796081Sphk * Return 1 on success, 0 on failure. 4806081Sphk */ 4816081Sphkint 4826081Sphkclose_sendmail(FILE *fp) 4836081Sphk { 4846081Sphk int status; 4856081Sphk 4866081Sphk fflush(fp); 4876081Sphk if (ferror(fp)) 4886081Sphk { 4896081Sphk err("error writing to sendmail"); 4906081Sphk return 0; 4916081Sphk } 4926081Sphk 4936081Sphk if ((status = pclose(fp)) != 0) 4946081Sphk err("sendmail failed with status %d", status); 4956081Sphk 4966081Sphk return (status == 0); 4976081Sphk } 498