Properly handle an error return from udev2dev().
[dragonfly.git] / usr.sbin / ctm / ctm_smail / ctm_smail.c
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: src/usr.sbin/ctm/ctm_smail/ctm_smail.c,v 1.12 1999/08/28 01:16:02 peter Exp $
15  * $DragonFly: src/usr.sbin/ctm/ctm_smail/Attic/ctm_smail.c,v 1.2 2003/06/17 04:29:53 dillon Exp $
16  */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <errno.h>
26 #include <paths.h>
27 #include <limits.h>
28 #include "error.h"
29 #include "options.h"
30
31 #define DEF_MAX_MSG     64000   /* Default maximum mail msg minus headers. */
32
33 #define LINE_LENGTH     76      /* Chars per encoded line. Divisible by 4. */
34
35 int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
36         long max_msg_size, char *mail_alias, char *queue_dir);
37 int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
38         char *mail_alias);
39 int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
40         char *mail_alias, char *queue_dir);
41 void clean_up_queue(char *queue_dir);
42 int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum);
43 void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
44         int npieces);
45 void write_trailer(FILE *sfp, unsigned sum);
46 int apologise(char *delta, off_t ctm_size, long max_ctm_size,
47         char *mail_alias);
48 FILE *open_sendmail(void);
49 int close_sendmail(FILE *fp);
50
51 int
52 main(int argc, char **argv)
53     {
54     int status = 0;
55     char *delta_file;
56     char *mail_alias;
57     long max_msg_size = DEF_MAX_MSG;
58     long max_ctm_size = 0;
59     char *log_file = NULL;
60     char *queue_dir = NULL;
61     char *delta;
62     FILE *dfp;
63     struct stat sb;
64
65     err_prog_name(argv[0]);
66
67     OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias")
68         NUMBER('m', max_msg_size)
69         NUMBER('c', max_ctm_size)
70         STRING('l', log_file)
71         STRING('q', queue_dir)
72     ENDOPTS
73
74     if (argc != 3)
75         usage();
76
77     if (log_file != NULL)
78         err_set_log(log_file);
79
80     delta_file = argv[1];
81     mail_alias = argv[2];
82
83     if ((delta = strrchr(delta_file, '/')) == NULL)
84         delta = delta_file;
85     else
86         delta++;
87
88     if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0)
89         {
90         err("*%s", delta_file);
91         exit(1);
92         }
93
94     if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
95         status = apologise(delta, sb.st_size, max_ctm_size, mail_alias);
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  */
110 int
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)
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  */
153 int
154 chop_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  */
205 int
206 chop_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  */
275 void
276 clean_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  */
294 static 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  */
315 int
316 encode_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  */
383 void
384 write_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  */
397 void
398 write_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  */
408 int
409 apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias)
410     {
411     FILE *sfp;
412
413     sfp = open_sendmail();
414     if (sfp == NULL)
415         return 1;
416
417     fprintf(sfp, "From: owner-%s\n", mail_alias);
418     fprintf(sfp, "To: %s\n", mail_alias);
419     fprintf(sfp, "Subject: ctm-notice %s\n\n", delta);
420
421     fprintf(sfp, "%s is %ld bytes.  The limit is %ld bytes.\n\n", delta,
422         (long)ctm_size, max_ctm_size);
423     fprintf(sfp, "You can retrieve this delta via ftpmail, "
424         "or your good mate at the university.\n");
425
426     if (!close_sendmail(sfp))
427         return 1;
428
429     return 0;
430     }
431
432
433 /*
434  * Start a pipe to sendmail.  Sendmail will decode the destination
435  * from the message contents.
436  */
437 FILE *
438 open_sendmail()
439     {
440     FILE *fp;
441     char buf[100];
442
443     sprintf(buf, "%s -odq -t", _PATH_SENDMAIL);
444     if ((fp = popen(buf, "w")) == NULL)
445         err("cannot start sendmail");
446     return fp;
447     }
448
449
450 /*
451  * Close a pipe to sendmail.  Sendmail will then do its bit.
452  * Return 1 on success, 0 on failure.
453  */
454 int
455 close_sendmail(FILE *fp)
456     {
457     int status;
458
459     fflush(fp);
460     if (ferror(fp))
461         {
462         err("error writing to sendmail");
463         return 0;
464         }
465
466     if ((status = pclose(fp)) != 0)
467         err("sendmail failed with status %d", status);
468
469     return (status == 0);
470     }