ctm_smail.c revision 8857
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 it
4 * to sendmail.  The encoding is almost the same as MIME BASE64, and is
5 * protected by a simple checksum.
6 *
7 * Author: Stephen McKay
8 *
9 * NOTICE: This is free software.  I hope you get some use from this program.
10 * In return you should think about all the nice people who give away software.
11 * Maybe you should write some free software too.
12 */
13
14#include <stdio.h>
15#include <string.h>
16#include <unistd.h>
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <errno.h>
20#include <paths.h>
21#include "error.h"
22#include "options.h"
23
24#define DEF_MAX_MSG	64000	/* Default maximum mail msg minus headers. */
25
26#define LINE_LENGTH	76	/* Chars per encode line. Divisible by 4. */
27
28void chop_and_send(char *delta, off_t ctm_size, long max_msg_size,
29	char *mail_alias);
30unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size);
31void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
32	int npieces);
33void write_trailer(FILE *sfp, unsigned sum);
34void apologise(char *delta, off_t ctm_size, long max_ctm_size,
35	char *mail_alias);
36FILE *open_sendmail(void);
37int close_sendmail(FILE *fp);
38
39
40int
41main(int argc, char **argv)
42    {
43    char *delta_file;
44    char *mail_alias;
45    long max_msg_size = DEF_MAX_MSG;
46    long max_ctm_size = 0;
47    char *log_file = NULL;
48    struct stat sb;
49
50    err_prog_name(argv[0]);
51
52    OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] ctm-delta mail-alias")
53	NUMBER('m', max_msg_size)
54	NUMBER('c', max_ctm_size)
55	STRING('l', log_file)
56    ENDOPTS
57
58    if (argc != 3)
59	usage();
60
61    if (log_file != NULL)
62	err_set_log(log_file);
63
64    delta_file = argv[1];
65    mail_alias = argv[2];
66
67    if (stat(delta_file, &sb) < 0)
68	{
69	err("%s: %s", delta_file, strerror(errno));
70	exit(1);
71	}
72
73    if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
74	apologise(delta_file, sb.st_size, max_ctm_size, mail_alias);
75    else
76	chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias);
77
78    return 0;
79    }
80
81
82/*
83 * Carve our CTM delta into pieces, encode them, and send them.
84 */
85void
86chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias)
87    {
88    int npieces;
89    long msg_size;
90    long exp_size;
91    int pce;
92    FILE *sfp;
93    FILE *dfp;
94    unsigned sum;
95
96#define howmany(x,y)	(((x)+((y)-1))/(y))
97
98    /*
99     * Work out how many pieces we need, bearing in mind that each piece
100     * grows by 4/3 when encoded.  We count the newlines too, but ignore
101     * all mail headers and piece headers.  They are a "small" (almost
102     * constant) per message overhead that we make the user worry about. :-)
103     */
104    exp_size = ctm_size * 4 / 3;
105    exp_size += howmany(exp_size, LINE_LENGTH);
106    npieces = howmany(exp_size, max_msg_size);
107    msg_size = howmany(ctm_size, npieces);
108
109#undef howmany
110
111    if ((dfp = fopen(delta, "r")) == NULL)
112	{
113	err("cannot open '%s' for reading.", delta);
114	exit(1);
115	}
116
117    for (pce = 1; pce <= npieces; pce++)
118	{
119	sfp = open_sendmail();
120	if (sfp == NULL)
121	    exit(1);
122	write_header(sfp, mail_alias, delta, pce, npieces);
123	sum = encode_body(sfp, dfp, msg_size);
124	write_trailer(sfp, sum);
125	if (!close_sendmail(sfp))
126	    exit(1);
127	err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
128	}
129
130    fclose(dfp);
131    }
132
133
134/*
135 * MIME BASE64 encode table.
136 */
137static char to_b64[0x40] =
138    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
139
140/*
141 * This cheap plastic checksum effectively rotates our checksum-so-far
142 * left one, then adds the character.  We only want 16 bits of it, and
143 * don't care what happens to the rest.  It ain't much, but it's small.
144 */
145#define add_ck(sum,x)	\
146    ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
147
148/*
149 * Encode the body.  Use an encoding almost the same as MIME BASE64.
150 *
151 * Characters are read from delta_fp and encoded characters are written
152 * to sm_fp.  At most 'msg_size' characters should be read from delta_fp.
153 *
154 * The body consists of lines of up to LINE_LENGTH characters.  Each group
155 * of 4 characters encodes 3 input characters.  Each output character encodes
156 * 6 bits.  Thus 64 different characters are needed in this representation.
157 */
158unsigned
159encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size)
160    {
161    unsigned short cksum = 0xffff;
162    unsigned char *ip;
163    char *op;
164    int want, n, i;
165    unsigned char inbuf[LINE_LENGTH*3/4];
166    char outbuf[LINE_LENGTH+1];
167
168    /*
169     * Round up to the nearest line boundary, for the tiniest of gains,
170     * and lots of neatness. :-)
171     */
172    msg_size += (LINE_LENGTH*3/4) - 1;
173    msg_size -= msg_size % (LINE_LENGTH*3/4);
174
175    while (msg_size > 0)
176	{
177	want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf);
178	if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0)
179	    break;
180	msg_size -= n;
181
182	for (i = 0; i < n; i++)
183	    add_ck(cksum, inbuf[i]);
184
185	/*
186	 * Produce a line of encoded data.  Every line length will be a
187	 * multiple of 4, except for, perhaps, the last line.
188	 */
189	ip = inbuf;
190	op = outbuf;
191	while (n >= 3)
192	    {
193	    *op++ = to_b64[ip[0] >> 2];
194	    *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
195	    *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6];
196	    *op++ = to_b64[ip[2] & 0x3f];
197	    ip += 3;
198	    n -= 3;
199	    }
200	if (n > 0)
201	    {
202	    *op++ = to_b64[ip[0] >> 2];
203	    *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
204	    if (n >= 2)
205		*op++ = to_b64[ip[1] << 2 & 0x3f];
206	    }
207	*op++ = '\n';
208	fwrite(outbuf, sizeof(char), op - outbuf, sm_fp);
209	}
210
211    if (ferror(delta_fp))
212	{
213	err("error reading input file.");
214	exit(1);
215	}
216
217    if (ferror(sm_fp))
218	{
219	err("error writing to sendmail");
220	exit(1);
221	}
222
223    return cksum;
224    }
225
226
227/*
228 * Write the mail header and data header.
229 */
230void
231write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces)
232    {
233    char *sn;
234
235    if ((sn = strrchr(delta, '/')) == NULL)
236	sn = delta;
237    else
238	sn++;
239
240    fprintf(sfp, "From: owner-%s\n", mail_alias);
241    fprintf(sfp, "To: %s\n", mail_alias);
242    fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", sn, pce, npieces);
243
244    fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, pce, npieces);
245    }
246
247
248/*
249 * Write the data trailer.
250 */
251void
252write_trailer(FILE *sfp, unsigned sum)
253    {
254    fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum);
255    }
256
257
258/*
259 * We're terribly sorry, but the delta is too big to send.
260 */
261void
262apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias)
263    {
264    FILE *sfp;
265    char *sn;
266
267    sfp = open_sendmail();
268    if (sfp == NULL)
269	exit(1);
270
271    if ((sn = strrchr(delta, '/')) == NULL)
272	sn = delta;
273    else
274	sn++;
275
276    fprintf(sfp, "From: %s-owner\n", mail_alias);
277    fprintf(sfp, "To: %s\n", mail_alias);
278    fprintf(sfp, "Subject: ctm-notice %s\n\n", sn);
279
280    fprintf(sfp, "%s is %ld bytes.  The limit is %ld bytes.\n\n", sn,
281	(long)ctm_size, max_ctm_size);
282    fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n");
283
284    if (!close_sendmail(sfp))
285	exit(1);
286    }
287
288
289/*
290 * Start a pipe to sendmail.  Sendmail will decode the destination
291 * from the message contents.
292 */
293FILE *
294open_sendmail()
295    {
296    FILE *fp;
297    char buf[100];
298
299    sprintf(buf, "%s -t", _PATH_SENDMAIL);
300    if ((fp = popen(buf, "w")) == NULL)
301	err("cannot start sendmail");
302    return fp;
303    }
304
305
306/*
307 * Close a pipe to sendmail.  Sendmail will then do its bit.
308 * Return 1 on success, 0 on failure.
309 */
310int
311close_sendmail(FILE *fp)
312    {
313    int status;
314
315    fflush(fp);
316    if (ferror(fp))
317	{
318	err("error writing to sendmail");
319	return 0;
320	}
321
322    if ((status = pclose(fp)) != 0)
323	err("sendmail failed with status %d", status);
324
325    return (status == 0);
326    }
327