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