Deleted Added
full compact
ctm_smail.c (14707) ctm_smail.c (16880)
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.
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 * $Id$
12 */
13
14#include <stdio.h>
14 */
15
16#include <stdio.h>
17#include <stdlib.h>
15#include <string.h>
16#include <unistd.h>
18#include <string.h>
19#include <unistd.h>
20#include <fcntl.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);
21#include <sys/types.h>
22#include <sys/stat.h>
23#include <errno.h>
24#include <paths.h>
25#include "error.h"
26#include "options.h"
27
28#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */
29
30#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */
31
32void chop_and_send(char *delta, off_t ctm_size, long max_msg_size,
33 char *mail_alias);
34void chop_and_queue(char *delta, off_t ctm_size, long max_msg_size,
35 char *queue_dir, 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);
36unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size);
37void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
38 int npieces);
39void write_trailer(FILE *sfp, unsigned sum);
40void apologise(char *delta, off_t ctm_size, long max_ctm_size,
41 char *mail_alias);
42FILE *open_sendmail(void);
43int close_sendmail(FILE *fp);
44int lock_queuedir(char *queue_dir);
45void free_lock(int lockf, char *queue_dir);
46void add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames);
38
47
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;
48int
49main(int argc, char **argv)
50 {
51 char *delta_file;
52 char *mail_alias;
53 long max_msg_size = DEF_MAX_MSG;
54 long max_ctm_size = 0;
55 char *log_file = NULL;
56 char *queue_dir = NULL;
48 struct stat sb;
49
50 err_prog_name(argv[0]);
51
57 struct stat sb;
58
59 err_prog_name(argv[0]);
60
52 OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] ctm-delta mail-alias")
61 OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias")
53 NUMBER('m', max_msg_size)
54 NUMBER('c', max_ctm_size)
55 STRING('l', log_file)
62 NUMBER('m', max_msg_size)
63 NUMBER('c', max_ctm_size)
64 STRING('l', log_file)
65 STRING('q', queue_dir)
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);
66 ENDOPTS
67
68 if (argc != 3)
69 usage();
70
71 if (log_file != NULL)
72 err_set_log(log_file);
73
74 delta_file = argv[1];
75 mail_alias = argv[2];
76
77 if (stat(delta_file, &sb) < 0)
78 {
79 err("%s: %s", delta_file, strerror(errno));
80 exit(1);
81 }
82
83 if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
84 apologise(delta_file, sb.st_size, max_ctm_size, mail_alias);
75 else
85 else if (queue_dir == NULL)
76 chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias);
86 chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias);
87 else
88 chop_and_queue(delta_file, sb.st_size, max_msg_size, queue_dir, 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
89
90 return 0;
91 }
92
93
94/*
95 * Carve our CTM delta into pieces, encode them, and send them.
96 */
97void
98chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias)
99 {
100 int npieces;
101 long msg_size;
102 long exp_size;
103 int pce;
104 FILE *sfp;
105 FILE *dfp;
106 unsigned sum;
107
108#ifdef howmany
109#undef howmany
110#endif
111
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 */

--- 21 unchanged lines hidden (view full) ---

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
112#define howmany(x, y) (((x) + ((y) - 1)) / (y))
113
114 /*
115 * Work out how many pieces we need, bearing in mind that each piece
116 * grows by 4/3 when encoded. We count the newlines too, but ignore
117 * all mail headers and piece headers. They are a "small" (almost
118 * constant) per message overhead that we make the user worry about. :-)
119 */

--- 21 unchanged lines hidden (view full) ---

141 if (!close_sendmail(sfp))
142 exit(1);
143 err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
144 }
145
146 fclose(dfp);
147 }
148
149/*
150 * Carve our CTM delta into pieces, encode them, and drop them in the
151 * queue dir.
152 *
153 * Basic algorythm:
154 *
155 * - for (each piece)
156 * - gen. temp. file name (one which the de-queuer will ignore)
157 * - record in array
158 * - open temp. file
159 * - encode delta (including headers) into the temp file
160 * - close temp. file
161 * - end
162 * - lock queue directory
163 * - foreach (temp. file)
164 * - rename to the proper filename
165 * - end
166 * - unlock queue directory
167 *
168 * This is probably overkill, but it means that incomplete deltas
169 * don't get mailed, and also reduces the window for lock races
170 * between ctm_smail and the de-queueing process.
171 */
133
172
173void
174chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, char *queue_dir, char *mail_alias)
175{
176 int npieces, pce, len;
177 long msg_size, exp_size;
178 FILE *sfp, *dfp;
179 unsigned sum;
180 char **tempnames, *tempnam, *sn;
181
182#define howmany(x, y) (((x) + ((y) - 1)) / (y))
183
184 /*
185 * Work out how many pieces we need, bearing in mind that each piece
186 * grows by 4/3 when encoded. We count the newlines too, but ignore
187 * all mail headers and piece headers. They are a "small" (almost
188 * constant) per message overhead that we make the user worry about. :-)
189 */
190 exp_size = ctm_size * 4 / 3;
191 exp_size += howmany(exp_size, LINE_LENGTH);
192 npieces = howmany(exp_size, max_msg_size);
193 msg_size = howmany(ctm_size, npieces);
194
195#undef howmany
196
197 /*
198 * allocate space for the array of filenames. Try to be portable
199 * by not assuming anything to do with sizeof(char *)
200 */
201 tempnames = malloc(npieces * sizeof(char *));
202 if (tempnames == NULL)
203 {
204 err("malloc for tempnames failed");
205 exit(1);
206 }
207
208 len = strlen(queue_dir) + 16;
209 tempnam = malloc(len);
210 if (tempnam == NULL)
211 {
212 err("malloc for tempnames failed");
213 exit(1);
214 }
215
216 if ((dfp = fopen(delta, "r")) == NULL)
217 {
218 err("cannot open '%s' for reading.", delta);
219 exit(1);
220 }
221
222 if ((sn = strrchr(delta, '/')) == NULL)
223 sn = delta;
224 else
225 sn++;
226
227 for (pce = 1; pce <= npieces; pce++)
228 {
229 if (snprintf(tempnam, len, "%s/.%08d-%03d", queue_dir, getpid(), pce) >= len)
230 err("Whoops! tempnam isn't long enough");
231
232 tempnames[pce - 1] = strdup(tempnam);
233 if (tempnames[pce - 1] == NULL)
234 {
235 err("strdup failed for temp. filename");
236 exit(1);
237 }
238
239 sfp = fopen(tempnam, "w");
240 if (sfp == NULL)
241 exit(1);
242
243 write_header(sfp, mail_alias, delta, pce, npieces);
244 sum = encode_body(sfp, dfp, msg_size);
245 write_trailer(sfp, sum);
246
247 if (fclose(sfp) != 0)
248 exit(1);
249
250 err("%s %d/%d created succesfully", sn, pce, npieces);
251 }
252
253 add_to_queue(queue_dir, mail_alias, delta, npieces, tempnames);
254
255 fclose(dfp);
256
257}
258
259
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

--- 69 unchanged lines hidden (view full) ---

211 if (ferror(delta_fp))
212 {
213 err("error reading input file.");
214 exit(1);
215 }
216
217 if (ferror(sm_fp))
218 {
260/*
261 * MIME BASE64 encode table.
262 */
263static char to_b64[0x40] =
264 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
265
266/*
267 * This cheap plastic checksum effectively rotates our checksum-so-far

--- 69 unchanged lines hidden (view full) ---

337 if (ferror(delta_fp))
338 {
339 err("error reading input file.");
340 exit(1);
341 }
342
343 if (ferror(sm_fp))
344 {
219 err("error writing to sendmail");
345 err("error writing encoded file");
220 exit(1);
221 }
222
223 return cksum;
224 }
225
226
227/*

--- 91 unchanged lines hidden (view full) ---

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 }
346 exit(1);
347 }
348
349 return cksum;
350 }
351
352
353/*

--- 91 unchanged lines hidden (view full) ---

445 return 0;
446 }
447
448 if ((status = pclose(fp)) != 0)
449 err("sendmail failed with status %d", status);
450
451 return (status == 0);
452 }
453
454/*
455 * Lock the queuedir so we're the only guy messing about in there.
456 */
457int
458lock_queuedir(char *queue_dir)
459{
460 int fp, len;
461 char *buffer;
462 struct stat sb;
463
464 len = strlen(queue_dir) + 8;
465
466 buffer = malloc(len);
467 if (buffer == NULL)
468 {
469 err("malloc failed in lock_queuedir");
470 exit(1);
471 }
472
473 if (snprintf(buffer, len, "%s/.lock", queue_dir) >= len)
474 err("Whoops. lock buffer too small in lock_queuedir");
475
476 /*
477 * We do our own lockfile scanning to avoid unlink races. 60
478 * seconds should be enough to ensure that we won't get more races
479 * happening between the stat and the open/flock.
480 */
481
482 while (stat(buffer, &sb) == 0)
483 sleep(60);
484
485 if ((fp = open(buffer, O_WRONLY | O_CREAT | O_EXLOCK, 0600)) < 0)
486 {
487 err("can't open `%s' in lock_queuedir", buffer);
488 exit(1);
489 }
490
491 snprintf(buffer, len, "%8ld", getpid());
492 write(fp, buffer, 8);
493
494 free(buffer);
495
496 return(fp);
497}
498
499/*
500 * Lock the queuedir so we're the only guy messing about in there.
501 */
502void
503free_lock(int lockf, char *queue_dir)
504{
505 int len;
506 char *path;
507
508 /*
509 * Most important: free the lock before we do anything else!
510 */
511
512 close(lockf);
513
514 len = strlen(queue_dir) + 7;
515
516 path = malloc(len);
517 if (path == NULL)
518 {
519 err("malloc failed in free_lock");
520 exit(1);
521 }
522
523 if (snprintf(path, len, "%s/.lock", queue_dir) >= len)
524 err("lock path buffer too small in free_lock");
525
526 if (unlink(path) != 0)
527 {
528 err("can't unlink lockfile `%s'", path);
529 exit(1);
530 }
531
532 free(path);
533}
534
535/* move everything into the queue directory. */
536
537void
538add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames)
539{
540 char *queuefile, *sn;
541 int pce, len, lockf;
542
543 if ((sn = strrchr(delta, '/')) == NULL)
544 sn = delta;
545 else
546 sn++;
547
548 /* try to malloc all we need BEFORE entering the lock loop */
549
550 len = strlen(queue_dir) + strlen(sn) + 7;
551 queuefile = malloc(len);
552 if (queuefile == NULL)
553 {
554 err("can't malloc for queuefile");
555 exit(1);
556 }
557
558 /*
559 * We should be the only process mucking around in the queue
560 * directory while we add the new queue files ... it could be
561 * awkward if the de-queue process starts it's job while we're
562 * adding files ...
563 */
564
565 lockf = lock_queuedir(queue_dir);
566 for (pce = 0; pce < npieces; pce++)
567 {
568 struct stat sb;
569
570 if (snprintf(queuefile, len, "%s/%s+%03d", queue_dir, sn, pce + 1) >= len)
571 err("whoops, queuefile buffer is too small");
572
573 if (stat(queuefile, &sb) == 0)
574 {
575 err("WOAH! Queue file `%s' already exists! Bailing out.", queuefile);
576 free_lock(lockf, queue_dir);
577 exit(1);
578 }
579
580 rename(tempnames[pce], queuefile);
581 err("Queue file %s now exists", queuefile);
582 }
583
584 free_lock(lockf, queue_dir);
585
586 free(queuefile);
587}