ctm_smail.c revision 14707
10SN/A/* 217110Sserb * Send a compressed CTM delta to a recipient mailing list by encoding it 30SN/A * in safe ASCII characters, in mailer-friendly chunks, and passing it 40SN/A * to sendmail. The encoding is almost the same as MIME BASE64, and is 50SN/A * protected by a simple checksum. 60SN/A * 72362SN/A * Author: Stephen McKay 80SN/A * 92362SN/A * NOTICE: This is free software. I hope you get some use from this program. 100SN/A * In return you should think about all the nice people who give away software. 110SN/A * Maybe you should write some free software too. 120SN/A */ 130SN/A 140SN/A#include <stdio.h> 150SN/A#include <string.h> 160SN/A#include <unistd.h> 170SN/A#include <sys/types.h> 180SN/A#include <sys/stat.h> 190SN/A#include <errno.h> 200SN/A#include <paths.h> 212362SN/A#include "error.h" 222362SN/A#include "options.h" 232362SN/A 240SN/A#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ 250SN/A 260SN/A#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */ 270SN/A 280SN/Avoid chop_and_send(char *delta, off_t ctm_size, long max_msg_size, 290SN/A char *mail_alias); 300SN/Aunsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size); 3113360Sssadetskyvoid write_header(FILE *sfp, char *mail_alias, char *delta, int pce, 320SN/A int npieces); 330SN/Avoid write_trailer(FILE *sfp, unsigned sum); 340SN/Avoid apologise(char *delta, off_t ctm_size, long max_ctm_size, 350SN/A char *mail_alias); 360SN/AFILE *open_sendmail(void); 370SN/Aint close_sendmail(FILE *fp); 380SN/A 390SN/A 400SN/Aint 410SN/Amain(int argc, char **argv) 420SN/A { 430SN/A char *delta_file; 440SN/A char *mail_alias; 450SN/A long max_msg_size = DEF_MAX_MSG; 460SN/A long max_ctm_size = 0; 470SN/A char *log_file = NULL; 480SN/A struct stat sb; 490SN/A 500SN/A err_prog_name(argv[0]); 510SN/A 520SN/A OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] ctm-delta mail-alias") 530SN/A NUMBER('m', max_msg_size) 540SN/A NUMBER('c', max_ctm_size) 550SN/A STRING('l', log_file) 560SN/A ENDOPTS 570SN/A 580SN/A if (argc != 3) 590SN/A usage(); 6017110Sserb 6117230Sserb if (log_file != NULL) 6217230Sserb err_set_log(log_file); 630SN/A 648155SN/A delta_file = argv[1]; 6517110Sserb mail_alias = argv[2]; 660SN/A 670SN/A if (stat(delta_file, &sb) < 0) 680SN/A { 690SN/A err("%s: %s", delta_file, strerror(errno)); 700SN/A exit(1); 710SN/A } 720SN/A 730SN/A if (max_ctm_size != 0 && sb.st_size > max_ctm_size) 7417110Sserb apologise(delta_file, sb.st_size, max_ctm_size, mail_alias); 7517230Sserb else 7617230Sserb chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias); 770SN/A 788155SN/A return 0; 7917110Sserb } 800SN/A 810SN/A 820SN/A/* 830SN/A * Carve our CTM delta into pieces, encode them, and send them. 840SN/A */ 850SN/Avoid 860SN/Achop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias) 8717110Sserb { 8817230Sserb int npieces; 8917230Sserb long msg_size; 900SN/A long exp_size; 918155SN/A int pce; 9217110Sserb FILE *sfp; 930SN/A FILE *dfp; 940SN/A unsigned sum; 950SN/A 960SN/A#define howmany(x, y) (((x) + ((y) - 1)) / (y)) 970SN/A 980SN/A /* 990SN/A * Work out how many pieces we need, bearing in mind that each piece 1000SN/A * grows by 4/3 when encoded. We count the newlines too, but ignore 1010SN/A * all mail headers and piece headers. They are a "small" (almost 1020SN/A * constant) per message overhead that we make the user worry about. :-) 1030SN/A */ 1040SN/A exp_size = ctm_size * 4 / 3; 1050SN/A exp_size += howmany(exp_size, LINE_LENGTH); 1060SN/A npieces = howmany(exp_size, max_msg_size); 1070SN/A msg_size = howmany(ctm_size, npieces); 1080SN/A 1090SN/A#undef howmany 1100SN/A 1110SN/A if ((dfp = fopen(delta, "r")) == NULL) 1120SN/A { 1130SN/A err("cannot open '%s' for reading.", delta); 11417110Sserb exit(1); 11517230Sserb } 11617230Sserb 1170SN/A for (pce = 1; pce <= npieces; pce++) 1188155SN/A { 11917110Sserb sfp = open_sendmail(); 1200SN/A if (sfp == NULL) 1210SN/A exit(1); 1220SN/A write_header(sfp, mail_alias, delta, pce, npieces); 1230SN/A sum = encode_body(sfp, dfp, msg_size); 1240SN/A write_trailer(sfp, sum); 1250SN/A if (!close_sendmail(sfp)) 1260SN/A exit(1); 1270SN/A err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); 1280SN/A } 1290SN/A 1300SN/A fclose(dfp); 1310SN/A } 1320SN/A 1330SN/A 1348241SN/A/* 1350SN/A * MIME BASE64 encode table. 1360SN/A */ 1370SN/Astatic char to_b64[0x40] = 1380SN/A "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 1390SN/A 1409167SN/A/* 1410SN/A * This cheap plastic checksum effectively rotates our checksum-so-far 14213360Sssadetsky * left one, then adds the character. We only want 16 bits of it, and 14313360Sssadetsky * don't care what happens to the rest. It ain't much, but it's small. 14413360Sssadetsky */ 14513360Sssadetsky#define add_ck(sum,x) \ 14613360Sssadetsky ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 1470SN/A 1480SN/A/* 1490SN/A * Encode the body. Use an encoding almost the same as MIME BASE64. 1500SN/A * 1510SN/A * Characters are read from delta_fp and encoded characters are written 1520SN/A * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 1530SN/A * 1540SN/A * The body consists of lines of up to LINE_LENGTH characters. Each group 1550SN/A * of 4 characters encodes 3 input characters. Each output character encodes 1560SN/A * 6 bits. Thus 64 different characters are needed in this representation. 1570SN/A */ 1580SN/Aunsigned 1590SN/Aencode_body(FILE *sm_fp, FILE *delta_fp, long msg_size) 1600SN/A { 1610SN/A unsigned short cksum = 0xffff; 1620SN/A unsigned char *ip; 1630SN/A char *op; 1640SN/A int want, n, i; 1650SN/A unsigned char inbuf[LINE_LENGTH*3/4]; 1660SN/A char outbuf[LINE_LENGTH+1]; 1670SN/A 1680SN/A /* 1690SN/A * Round up to the nearest line boundary, for the tiniest of gains, 1700SN/A * and lots of neatness. :-) 1710SN/A */ 1720SN/A msg_size += (LINE_LENGTH*3/4) - 1; 1730SN/A msg_size -= msg_size % (LINE_LENGTH*3/4); 1740SN/A 1750SN/A while (msg_size > 0) 1760SN/A { 1770SN/A want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 1780SN/A if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 1790SN/A break; 180625SN/A msg_size -= n; 1810SN/A 1820SN/A for (i = 0; i < n; i++) 183625SN/A add_ck(cksum, inbuf[i]); 1840SN/A 1850SN/A /* 1860SN/A * Produce a line of encoded data. Every line length will be a 1870SN/A * multiple of 4, except for, perhaps, the last line. 1880SN/A */ 1890SN/A ip = inbuf; 1900SN/A op = outbuf; 1910SN/A while (n >= 3) 1920SN/A { 1930SN/A *op++ = to_b64[ip[0] >> 2]; 1940SN/A *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 1950SN/A *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 1960SN/A *op++ = to_b64[ip[2] & 0x3f]; 1970SN/A ip += 3; 1980SN/A n -= 3; 1990SN/A } 2000SN/A if (n > 0) 2010SN/A { 2020SN/A *op++ = to_b64[ip[0] >> 2]; 2030SN/A *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 2040SN/A if (n >= 2) 2050SN/A *op++ = to_b64[ip[1] << 2 & 0x3f]; 2060SN/A } 2070SN/A *op++ = '\n'; 2080SN/A fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 2090SN/A } 2100SN/A 2110SN/A if (ferror(delta_fp)) 2120SN/A { 2130SN/A err("error reading input file."); 2140SN/A exit(1); 2150SN/A } 2160SN/A 2170SN/A if (ferror(sm_fp)) 2180SN/A { 2190SN/A err("error writing to sendmail"); 2200SN/A exit(1); 2210SN/A } 2220SN/A 2230SN/A return cksum; 2240SN/A } 2250SN/A 2260SN/A 2270SN/A/* 2280SN/A * Write the mail header and data header. 2290SN/A */ 2300SN/Avoid 2310SN/Awrite_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 2320SN/A { 2330SN/A char *sn; 2340SN/A 2350SN/A if ((sn = strrchr(delta, '/')) == NULL) 2360SN/A sn = delta; 2370SN/A else 2380SN/A sn++; 2390SN/A 2400SN/A fprintf(sfp, "From: owner-%s\n", mail_alias); 2410SN/A fprintf(sfp, "To: %s\n", mail_alias); 2420SN/A fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", sn, pce, npieces); 2430SN/A 2440SN/A fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, pce, npieces); 2450SN/A } 2460SN/A 2470SN/A 2480SN/A/* 2490SN/A * Write the data trailer. 2500SN/A */ 2510SN/Avoid 252625SN/Awrite_trailer(FILE *sfp, unsigned sum) 2530SN/A { 2540SN/A fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 2550SN/A } 2560SN/A 2570SN/A 2580SN/A/* 2590SN/A * We're terribly sorry, but the delta is too big to send. 2600SN/A */ 2610SN/Avoid 2620SN/Aapologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias) 2630SN/A { 2640SN/A FILE *sfp; 2650SN/A char *sn; 2660SN/A 2670SN/A sfp = open_sendmail(); 2680SN/A if (sfp == NULL) 2690SN/A exit(1); 2700SN/A 2710SN/A if ((sn = strrchr(delta, '/')) == NULL) 2720SN/A sn = delta; 2730SN/A else 2740SN/A sn++; 2750SN/A 2760SN/A fprintf(sfp, "From: %s-owner\n", mail_alias); 2770SN/A fprintf(sfp, "To: %s\n", mail_alias); 2780SN/A fprintf(sfp, "Subject: ctm-notice %s\n\n", sn); 2790SN/A 2800SN/A fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", sn, 2810SN/A (long)ctm_size, max_ctm_size); 2820SN/A fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n"); 2830SN/A 2840SN/A if (!close_sendmail(sfp)) 2850SN/A exit(1); 2860SN/A } 2870SN/A 2880SN/A 2890SN/A/* 2900SN/A * Start a pipe to sendmail. Sendmail will decode the destination 2910SN/A * from the message contents. 2920SN/A */ 2930SN/AFILE * 2940SN/Aopen_sendmail() 2950SN/A { 29611099Smartin FILE *fp; 2970SN/A char buf[100]; 2980SN/A 2990SN/A sprintf(buf, "%s -t", _PATH_SENDMAIL); 3000SN/A if ((fp = popen(buf, "w")) == NULL) 3010SN/A err("cannot start sendmail"); 3020SN/A return fp; 3030SN/A } 3040SN/A 305625SN/A 3060SN/A/* 3070SN/A * Close a pipe to sendmail. Sendmail will then do its bit. 3080SN/A * Return 1 on success, 0 on failure. 3090SN/A */ 3100SN/Aint 3110SN/Aclose_sendmail(FILE *fp) 3120SN/A { 3130SN/A int status; 3140SN/A 31511099Smartin fflush(fp); 3160SN/A if (ferror(fp)) 3170SN/A { 3180SN/A err("error writing to sendmail"); 3190SN/A return 0; 3200SN/A } 3210SN/A 3220SN/A if ((status = pclose(fp)) != 0) 3230SN/A err("sendmail failed with status %d", status); 3240SN/A 3250SN/A return (status == 0); 326625SN/A } 3270SN/A