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