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