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.
8 * Author: Stephen McKay
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.
22 #include <sys/types.h>
30 #define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */
32 #define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */
34 int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
35 long max_msg_size, char *mail_alias, char *queue_dir);
36 int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
38 int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
39 char *mail_alias, char *queue_dir);
40 void clean_up_queue(char *queue_dir);
41 int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum);
42 void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
44 void write_trailer(FILE *sfp, unsigned sum);
45 int apologise(char *delta, off_t ctm_size, long max_ctm_size,
46 char *mail_alias, char *queue_dir);
47 FILE *open_sendmail(void);
48 int close_sendmail(FILE *fp);
51 main(int argc, char **argv)
56 long max_msg_size = DEF_MAX_MSG;
57 long max_ctm_size = 0;
58 char *log_file = NULL;
59 char *queue_dir = NULL;
64 err_prog_name(argv[0]);
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)
70 STRING('q', queue_dir)
77 err_set_log(log_file);
82 if ((delta = strrchr(delta_file, '/')) == NULL)
87 if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0)
89 err("*%s", delta_file);
93 if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
94 status = apologise(delta, sb.st_size, max_ctm_size, mail_alias,
97 status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size,
98 mail_alias, queue_dir);
107 * Carve our CTM delta into pieces, encode them, and send or queue them.
108 * Returns 0 on success, and 1 on failure.
111 chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
112 long max_msg_size, char *mail_alias, char *queue_dir)
120 #define howmany(x,y) (((x)+((y)-1)) / (y))
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. :-)
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);
135 if (queue_dir == NULL)
136 status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias);
139 status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias,
142 clean_up_queue(queue_dir);
150 * Carve our CTM delta into pieces, encode them, and send them.
151 * Returns 0 on success, and 1 on failure.
154 chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
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
167 for (pce = 1; pce <= npieces; pce++)
171 if ((sfp = open_sendmail()) == NULL)
174 write_header(sfp, mail_alias, delta, pce, npieces);
175 read_error = encode_body(sfp, dfp, msg_size, &sum);
177 write_trailer(sfp, sum);
179 if (!close_sendmail(sfp) || read_error)
182 err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
190 * Construct the tmp queue file name of a delta piece.
192 #define mk_tmp_name(fn,qd,p) \
193 sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p))
196 * Construct the final queue file name of a delta piece.
198 #define mk_queue_name(fn,qd,d,p,n) \
199 sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n))
202 * Carve our CTM delta into pieces, encode them, and queue them.
203 * Returns 0 on success, and 1 on failure.
206 chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
207 char *mail_alias, char *queue_dir)
212 char tname[PATH_MAX];
213 char qname[PATH_MAX];
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.
220 for (pce = 1; pce <= npieces; pce++)
224 mk_tmp_name(tname, queue_dir, pce);
225 if ((qfp = fopen(tname, "w")) == NULL)
227 err("cannot open '%s' for writing", tname);
231 write_header(qfp, mail_alias, delta, pce, npieces);
232 if (encode_body(qfp, dfp, msg_size, &sum))
234 write_trailer(qfp, sum);
237 write_error = ferror(qfp);
241 err("error writing '%s'", tname);
246 * Give the warm success message now, instead of all in a rush
247 * during the rename phase.
249 err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias);
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.
257 for (pce = 1; pce <= npieces; pce++)
259 mk_tmp_name(tname, queue_dir, pce);
260 mk_queue_name(qname, queue_dir, delta, pce, npieces);
261 if (rename(tname, qname) < 0)
263 err("*rename: '%s' to '%s'", tname, qname);
273 * There may be temporary files cluttering up the queue directory.
276 clean_up_queue(char *queue_dir)
279 char tname[PATH_MAX];
281 err("discarding queued delta pieces");
282 for (pce = 1; ; pce++)
284 mk_tmp_name(tname, queue_dir, pce);
285 if (unlink(tname) < 0)
292 * MIME BASE64 encode table.
294 static char to_b64[0x40] =
295 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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.
302 #define add_ck(sum,x) \
303 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
306 * Encode the body. Use an encoding almost the same as MIME BASE64.
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.
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.
316 encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum)
318 unsigned short cksum = 0xffff;
322 unsigned char inbuf[LINE_LENGTH*3/4];
323 char outbuf[LINE_LENGTH+1];
326 * Round up to the nearest line boundary, for the tiniest of gains,
327 * and lots of neatness. :-)
329 msg_size += (LINE_LENGTH*3/4) - 1;
330 msg_size -= msg_size % (LINE_LENGTH*3/4);
334 want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf);
335 if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0)
339 for (i = 0; i < n; i++)
340 add_ck(cksum, inbuf[i]);
343 * Produce a line of encoded data. Every line length will be a
344 * multiple of 4, except for, perhaps, the last line.
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];
359 *op++ = to_b64[ip[0] >> 2];
360 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
362 *op++ = to_b64[ip[1] << 2 & 0x3f];
365 fwrite(outbuf, sizeof(char), op - outbuf, sm_fp);
368 if (ferror(delta_fp))
370 err("error reading input file.");
381 * Write the mail header and data header.
384 write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces)
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);
390 fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces);
395 * Write the data trailer.
398 write_trailer(FILE *sfp, unsigned sum)
400 fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum);
405 * We're terribly sorry, but the delta is too big to send.
406 * Returns 0 on success, 1 on failure.
409 apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias,
413 char qname[PATH_MAX];
415 if (queue_dir == NULL)
417 sfp = open_sendmail();
423 mk_queue_name(qname, queue_dir, delta, 1, 1);
424 sfp = fopen(qname, "w");
427 err("cannot open '%s' for writing", qname);
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);
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");
441 if (queue_dir == NULL)
443 if (!close_sendmail(sfp))
450 err("error writing '%s'", qname);
461 * Start a pipe to sendmail. Sendmail will decode the destination
462 * from the message contents.
470 sprintf(buf, "%s -odq -t", _PATH_SENDMAIL);
471 if ((fp = popen(buf, "w")) == NULL)
472 err("cannot start sendmail");
478 * Close a pipe to sendmail. Sendmail will then do its bit.
479 * Return 1 on success, 0 on failure.
482 close_sendmail(FILE *fp)
489 err("error writing to sendmail");
493 if ((status = pclose(fp)) != 0)
494 err("sendmail failed with status %d", status);
496 return (status == 0);