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