]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dma/mail.c
bhnd(9): Fix a few mandoc related issues
[FreeBSD/FreeBSD.git] / contrib / dma / mail.c
1 /*
2  * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
3  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
4  *
5  * This code is derived from software contributed to The DragonFly Project
6  * by Simon Schubert <2@0x2c.org>.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  * 3. Neither the name of The DragonFly Project nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific, prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35
36 #include <errno.h>
37 #include <inttypes.h>
38 #include <signal.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <unistd.h>
42
43 #include "dma.h"
44
45 #define MAX_LINE_RFC822 1000
46
47 void
48 bounce(struct qitem *it, const char *reason)
49 {
50         struct queue bounceq;
51         char line[1000];
52         size_t pos;
53         int error;
54
55         /* Don't bounce bounced mails */
56         if (it->sender[0] == 0) {
57                 syslog(LOG_INFO, "can not bounce a bounce message, discarding");
58                 exit(EX_SOFTWARE);
59         }
60
61         bzero(&bounceq, sizeof(bounceq));
62         LIST_INIT(&bounceq.queue);
63         bounceq.sender = "";
64         if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
65                 goto fail;
66
67         if (newspoolf(&bounceq) != 0)
68                 goto fail;
69
70         syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
71         setlogident("%s", bounceq.id);
72
73         error = fprintf(bounceq.mailf,
74                 "Received: from MAILER-DAEMON\n"
75                 "\tid %s\n"
76                 "\tby %s (%s);\n"
77                 "\t%s\n"
78                 "X-Original-To: <%s>\n"
79                 "From: MAILER-DAEMON <>\n"
80                 "To: %s\n"
81                 "Subject: Mail delivery failed\n"
82                 "Message-Id: <%s@%s>\n"
83                 "Date: %s\n"
84                 "\n"
85                 "This is the %s at %s.\n"
86                 "\n"
87                 "There was an error delivering your mail to <%s>.\n"
88                 "\n"
89                 "%s\n"
90                 "\n"
91                 "%s\n"
92                 "\n",
93                 bounceq.id,
94                 hostname(), VERSION,
95                 rfc822date(),
96                 it->addr,
97                 it->sender,
98                 bounceq.id, hostname(),
99                 rfc822date(),
100                 VERSION, hostname(),
101                 it->addr,
102                 reason,
103                 config.features & FULLBOUNCE ?
104                     "Original message follows." :
105                     "Message headers follow.");
106         if (error < 0)
107                 goto fail;
108
109         if (fseek(it->mailf, 0, SEEK_SET) != 0)
110                 goto fail;
111         if (config.features & FULLBOUNCE) {
112                 while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
113                         if (fwrite(line, 1, pos, bounceq.mailf) != pos)
114                                 goto fail;
115                 }
116         } else {
117                 while (!feof(it->mailf)) {
118                         if (fgets(line, sizeof(line), it->mailf) == NULL)
119                                 break;
120                         if (line[0] == '\n')
121                                 break;
122                         if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
123                                 goto fail;
124                 }
125         }
126
127         if (linkspool(&bounceq) != 0)
128                 goto fail;
129         /* bounce is safe */
130
131         delqueue(it);
132
133         run_queue(&bounceq);
134         /* NOTREACHED */
135
136 fail:
137         syslog(LOG_CRIT, "error creating bounce: %m");
138         delqueue(it);
139         exit(EX_IOERR);
140 }
141
142 struct parse_state {
143         char addr[1000];
144         int pos;
145
146         enum {
147                 NONE = 0,
148                 START,
149                 MAIN,
150                 EOL,
151                 QUIT
152         } state;
153         int comment;
154         int quote;
155         int brackets;
156         int esc;
157 };
158
159 /*
160  * Simplified RFC2822 header/address parsing.
161  * We copy escapes and quoted strings directly, since
162  * we have to pass them like this to the mail server anyways.
163  * XXX local addresses will need treatment
164  */
165 static int
166 parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
167 {
168         char *addr;
169
170 again:
171         switch (ps->state) {
172         case NONE:
173                 return (-1);
174
175         case START:
176                 /* init our data */
177                 bzero(ps, sizeof(*ps));
178
179                 /* skip over header name */
180                 while (*s != ':')
181                         s++;
182                 s++;
183                 ps->state = MAIN;
184                 break;
185
186         case MAIN:
187                 /* all fine */
188                 break;
189
190         case EOL:
191                 switch (*s) {
192                 case ' ':
193                 case '\t':
194                         s++;
195                         /* continue */
196                         break;
197
198                 default:
199                         ps->state = QUIT;
200                         if (ps->pos != 0)
201                                 goto newaddr;
202                         return (0);
203                 }
204
205         case QUIT:
206                 return (0);
207         }
208
209         for (; *s != 0; s++) {
210                 if (ps->esc) {
211                         ps->esc = 0;
212
213                         switch (*s) {
214                         case '\r':
215                         case '\n':
216                                 goto err;
217
218                         default:
219                                 goto copy;
220                         }
221                 }
222
223                 if (ps->quote) {
224                         switch (*s) {
225                         case '"':
226                                 ps->quote = 0;
227                                 goto copy;
228
229                         case '\\':
230                                 ps->esc = 1;
231                                 goto copy;
232
233                         case '\r':
234                         case '\n':
235                                 goto eol;
236
237                         default:
238                                 goto copy;
239                         }
240                 }
241
242                 switch (*s) {
243                 case '(':
244                         ps->comment++;
245                         break;
246
247                 case ')':
248                         if (ps->comment)
249                                 ps->comment--;
250                         else
251                                 goto err;
252                         goto skip;
253
254                 case '"':
255                         ps->quote = 1;
256                         goto copy;
257
258                 case '\\':
259                         ps->esc = 1;
260                         goto copy;
261
262                 case '\r':
263                 case '\n':
264                         goto eol;
265                 }
266
267                 if (ps->comment)
268                         goto skip;
269
270                 switch (*s) {
271                 case ' ':
272                 case '\t':
273                         /* ignore whitespace */
274                         goto skip;
275
276                 case '<':
277                         /* this is the real address now */
278                         ps->brackets = 1;
279                         ps->pos = 0;
280                         goto skip;
281
282                 case '>':
283                         if (!ps->brackets)
284                                 goto err;
285                         ps->brackets = 0;
286
287                         s++;
288                         goto newaddr;
289
290                 case ':':
291                         /* group - ignore */
292                         ps->pos = 0;
293                         goto skip;
294
295                 case ',':
296                 case ';':
297                         /*
298                          * Next address, copy previous one.
299                          * However, we might be directly after
300                          * a <address>, or have two consecutive
301                          * commas.
302                          * Skip the comma unless there is
303                          * really something to copy.
304                          */
305                         if (ps->pos == 0)
306                                 goto skip;
307                         s++;
308                         goto newaddr;
309
310                 default:
311                         goto copy;
312                 }
313
314 copy:
315                 if (ps->comment)
316                         goto skip;
317
318                 if (ps->pos + 1 == sizeof(ps->addr))
319                         goto err;
320                 ps->addr[ps->pos++] = *s;
321
322 skip:
323                 ;
324         }
325
326 eol:
327         ps->state = EOL;
328         return (0);
329
330 err:
331         ps->state = QUIT;
332         return (-1);
333
334 newaddr:
335         ps->addr[ps->pos] = 0;
336         ps->pos = 0;
337         addr = strdup(ps->addr);
338         if (addr == NULL)
339                 errlog(EX_SOFTWARE, "strdup");
340
341         if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
342                 errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
343
344         goto again;
345 }
346
347 static int
348 writeline(struct queue *queue, const char *line, ssize_t linelen)
349 {
350         ssize_t len;
351
352         while (linelen > 0) {
353                 len = linelen;
354                 if (linelen > MAX_LINE_RFC822) {
355                         len = MAX_LINE_RFC822 - 10;
356                 }
357
358                 if (fwrite(line, len, 1, queue->mailf) != 1)
359                         return (-1);
360
361                 if (linelen <= MAX_LINE_RFC822)
362                         break;
363
364                 if (fwrite("\n", 1, 1, queue->mailf) != 1)
365                         return (-1);
366
367                 line += MAX_LINE_RFC822 - 10;
368                 linelen = strlen(line);
369         }
370         return (0);
371 }
372
373 int
374 readmail(struct queue *queue, int nodot, int recp_from_header)
375 {
376         struct parse_state parse_state;
377         char *line = NULL;
378         ssize_t linelen;
379         size_t linecap = 0;
380         char newline[MAX_LINE_RFC822];
381         size_t error;
382         int had_headers = 0;
383         int had_from = 0;
384         int had_messagid = 0;
385         int had_date = 0;
386         int nocopy = 0;
387         int ret = -1;
388
389         parse_state.state = NONE;
390
391         error = fprintf(queue->mailf,
392                 "Received: from %s (uid %d)\n"
393                 "\t(envelope-from %s)\n"
394                 "\tid %s\n"
395                 "\tby %s (%s);\n"
396                 "\t%s\n",
397                 username, useruid,
398                 queue->sender,
399                 queue->id,
400                 hostname(), VERSION,
401                 rfc822date());
402         if ((ssize_t)error < 0)
403                 return (-1);
404
405         while (!feof(stdin)) {
406                 newline[0] = '\0';
407                 if ((linelen = getline(&line, &linecap, stdin)) <= 0)
408                         break;
409
410                 if (!had_headers) {
411                         if (linelen > MAX_LINE_RFC822) {
412                                 /* XXX also split headers */
413                                 errlogx(EX_DATAERR, "bad mail input format:"
414                                     " from %s (uid %d) (envelope-from %s)",
415                                     username, useruid, queue->sender);
416                         }
417                         /*
418                          * Unless this is a continuation, switch of
419                          * the Bcc: nocopy flag.
420                          */
421                         if (!(line[0] == ' ' || line[0] == '\t'))
422                                 nocopy = 0;
423
424                         if (strprefixcmp(line, "Date:") == 0)
425                                 had_date = 1;
426                         else if (strprefixcmp(line, "Message-Id:") == 0)
427                                 had_messagid = 1;
428                         else if (strprefixcmp(line, "From:") == 0)
429                                 had_from = 1;
430                         else if (strprefixcmp(line, "Bcc:") == 0)
431                                 nocopy = 1;
432
433                         if (parse_state.state != NONE) {
434                                 if (parse_addrs(&parse_state, line, queue) < 0) {
435                                         errlogx(EX_DATAERR, "invalid address in header\n");
436                                         /* NOTREACHED */
437                                 }
438                         }
439
440                         if (recp_from_header && (
441                                         strprefixcmp(line, "To:") == 0 ||
442                                         strprefixcmp(line, "Cc:") == 0 ||
443                                         strprefixcmp(line, "Bcc:") == 0)) {
444                                 parse_state.state = START;
445                                 if (parse_addrs(&parse_state, line, queue) < 0) {
446                                         errlogx(EX_DATAERR, "invalid address in header\n");
447                                         /* NOTREACHED */
448                                 }
449                         }
450                 }
451
452                 if (strcmp(line, "\n") == 0 && !had_headers) {
453                         had_headers = 1;
454                         while (!had_date || !had_messagid || !had_from) {
455                                 if (!had_date) {
456                                         had_date = 1;
457                                         snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date());
458                                 } else if (!had_messagid) {
459                                         /* XXX msgid, assign earlier and log? */
460                                         had_messagid = 1;
461                                         snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
462                                                  (uintmax_t)time(NULL),
463                                                  queue->id,
464                                                  (uintmax_t)random(),
465                                                  hostname());
466                                 } else if (!had_from) {
467                                         had_from = 1;
468                                         snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender);
469                                 }
470                                 if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
471                                         goto fail;
472                         }
473                         strlcpy(newline, "\n", sizeof(newline));
474                 }
475                 if (!nodot && linelen == 2 && line[0] == '.')
476                         break;
477                 if (!nocopy) {
478                         if (newline[0]) {
479                                 if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
480                                         goto fail;
481                         } else {
482                                 if (writeline(queue, line, linelen) != 0)
483                                         goto fail;
484                         }
485                 }
486         }
487
488         ret = 0;
489 fail:
490         free(line);
491         return (ret);
492 }