]>
Commit | Line | Data |
---|---|---|
28be0b96 | 1 | /* |
e458e5f0 | 2 | * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. |
28be0b96 SS |
3 | * Copyright (c) 2008 The DragonFly Project. All rights reserved. |
4 | * | |
5 | * This code is derived from software contributed to The DragonFly Project | |
e458e5f0 | 6 | * by Simon Schubert <2@0x2c.org>. |
28be0b96 SS |
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> | |
ee613428 SS |
37 | #include <inttypes.h> |
38 | #include <signal.h> | |
28be0b96 SS |
39 | #include <syslog.h> |
40 | #include <unistd.h> | |
41 | ||
42 | #include "dma.h" | |
43 | ||
44 | void | |
45 | bounce(struct qitem *it, const char *reason) | |
46 | { | |
47 | struct queue bounceq; | |
48 | char line[1000]; | |
49 | size_t pos; | |
50 | int error; | |
51 | ||
52 | /* Don't bounce bounced mails */ | |
53 | if (it->sender[0] == 0) { | |
54 | syslog(LOG_INFO, "can not bounce a bounce message, discarding"); | |
de196e33 | 55 | exit(EX_SOFTWARE); |
28be0b96 SS |
56 | } |
57 | ||
6f5efe4f | 58 | bzero(&bounceq, sizeof(bounceq)); |
28be0b96 | 59 | LIST_INIT(&bounceq.queue); |
057afad5 | 60 | bounceq.sender = ""; |
a803c7a6 | 61 | if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0) |
28be0b96 SS |
62 | goto fail; |
63 | ||
057afad5 | 64 | if (newspoolf(&bounceq) != 0) |
28be0b96 SS |
65 | goto fail; |
66 | ||
67 | syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id); | |
68 | setlogident("%s", bounceq.id); | |
69 | ||
70 | error = fprintf(bounceq.mailf, | |
71 | "Received: from MAILER-DAEMON\n" | |
72 | "\tid %s\n" | |
5283d577 | 73 | "\tby %s (%s);\n" |
28be0b96 SS |
74 | "\t%s\n" |
75 | "X-Original-To: <%s>\n" | |
76 | "From: MAILER-DAEMON <>\n" | |
77 | "To: %s\n" | |
78 | "Subject: Mail delivery failed\n" | |
79 | "Message-Id: <%s@%s>\n" | |
80 | "Date: %s\n" | |
81 | "\n" | |
82 | "This is the %s at %s.\n" | |
83 | "\n" | |
84 | "There was an error delivering your mail to <%s>.\n" | |
85 | "\n" | |
86 | "%s\n" | |
87 | "\n" | |
88 | "%s\n" | |
89 | "\n", | |
90 | bounceq.id, | |
91 | hostname(), VERSION, | |
92 | rfc822date(), | |
93 | it->addr, | |
94 | it->sender, | |
95 | bounceq.id, hostname(), | |
96 | rfc822date(), | |
97 | VERSION, hostname(), | |
98 | it->addr, | |
99 | reason, | |
a0c4afa6 | 100 | config.features & FULLBOUNCE ? |
28be0b96 SS |
101 | "Original message follows." : |
102 | "Message headers follow."); | |
103 | if (error < 0) | |
104 | goto fail; | |
105 | ||
6ddd63e1 | 106 | if (fseek(it->mailf, 0, SEEK_SET) != 0) |
28be0b96 | 107 | goto fail; |
a0c4afa6 | 108 | if (config.features & FULLBOUNCE) { |
28be0b96 SS |
109 | while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) { |
110 | if (fwrite(line, 1, pos, bounceq.mailf) != pos) | |
111 | goto fail; | |
112 | } | |
113 | } else { | |
114 | while (!feof(it->mailf)) { | |
115 | if (fgets(line, sizeof(line), it->mailf) == NULL) | |
116 | break; | |
117 | if (line[0] == '\n') | |
118 | break; | |
119 | if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1) | |
120 | goto fail; | |
121 | } | |
122 | } | |
123 | ||
057afad5 | 124 | if (linkspool(&bounceq) != 0) |
28be0b96 SS |
125 | goto fail; |
126 | /* bounce is safe */ | |
127 | ||
128 | delqueue(it); | |
129 | ||
130 | run_queue(&bounceq); | |
131 | /* NOTREACHED */ | |
132 | ||
133 | fail: | |
134 | syslog(LOG_CRIT, "error creating bounce: %m"); | |
135 | delqueue(it); | |
de196e33 | 136 | exit(EX_IOERR); |
28be0b96 SS |
137 | } |
138 | ||
f734c0ae SS |
139 | struct parse_state { |
140 | char addr[1000]; | |
141 | int pos; | |
142 | ||
143 | enum { | |
144 | NONE = 0, | |
145 | START, | |
146 | MAIN, | |
147 | EOL, | |
148 | QUIT | |
149 | } state; | |
150 | int comment; | |
151 | int quote; | |
152 | int brackets; | |
153 | int esc; | |
154 | }; | |
155 | ||
156 | /* | |
157 | * Simplified RFC2822 header/address parsing. | |
158 | * We copy escapes and quoted strings directly, since | |
159 | * we have to pass them like this to the mail server anyways. | |
160 | * XXX local addresses will need treatment | |
161 | */ | |
162 | static int | |
163 | parse_addrs(struct parse_state *ps, char *s, struct queue *queue) | |
164 | { | |
165 | char *addr; | |
166 | ||
167 | again: | |
168 | switch (ps->state) { | |
169 | case NONE: | |
170 | return (-1); | |
171 | ||
172 | case START: | |
173 | /* init our data */ | |
174 | bzero(ps, sizeof(*ps)); | |
175 | ||
176 | /* skip over header name */ | |
177 | while (*s != ':') | |
178 | s++; | |
179 | s++; | |
180 | ps->state = MAIN; | |
181 | break; | |
182 | ||
183 | case MAIN: | |
184 | /* all fine */ | |
185 | break; | |
186 | ||
187 | case EOL: | |
188 | switch (*s) { | |
189 | case ' ': | |
190 | case '\t': | |
191 | s++; | |
192 | /* continue */ | |
193 | break; | |
194 | ||
195 | default: | |
196 | ps->state = QUIT; | |
197 | if (ps->pos != 0) | |
198 | goto newaddr; | |
199 | return (0); | |
200 | } | |
201 | ||
202 | case QUIT: | |
203 | return (0); | |
204 | } | |
205 | ||
206 | for (; *s != 0; s++) { | |
207 | if (ps->esc) { | |
208 | ps->esc = 0; | |
209 | ||
210 | switch (*s) { | |
211 | case '\r': | |
212 | case '\n': | |
213 | goto err; | |
214 | ||
215 | default: | |
216 | goto copy; | |
217 | } | |
218 | } | |
219 | ||
220 | if (ps->quote) { | |
221 | switch (*s) { | |
222 | case '"': | |
223 | ps->quote = 0; | |
224 | goto copy; | |
225 | ||
226 | case '\\': | |
227 | ps->esc = 1; | |
228 | goto copy; | |
229 | ||
230 | case '\r': | |
231 | case '\n': | |
232 | goto eol; | |
233 | ||
234 | default: | |
235 | goto copy; | |
236 | } | |
237 | } | |
238 | ||
239 | switch (*s) { | |
240 | case '(': | |
241 | ps->comment++; | |
242 | break; | |
243 | ||
244 | case ')': | |
245 | if (ps->comment) | |
246 | ps->comment--; | |
247 | else | |
248 | goto err; | |
249 | goto skip; | |
250 | ||
251 | case '"': | |
252 | ps->quote = 1; | |
253 | goto copy; | |
254 | ||
255 | case '\\': | |
256 | ps->esc = 1; | |
257 | goto copy; | |
258 | ||
259 | case '\r': | |
260 | case '\n': | |
261 | goto eol; | |
262 | } | |
263 | ||
264 | if (ps->comment) | |
265 | goto skip; | |
266 | ||
267 | switch (*s) { | |
268 | case ' ': | |
269 | case '\t': | |
270 | /* ignore whitespace */ | |
271 | goto skip; | |
272 | ||
273 | case '<': | |
1aa8a6d7 | 274 | /* this is the real address now */ |
f734c0ae | 275 | ps->brackets = 1; |
1aa8a6d7 | 276 | ps->pos = 0; |
f734c0ae SS |
277 | goto skip; |
278 | ||
279 | case '>': | |
280 | if (!ps->brackets) | |
281 | goto err; | |
282 | ps->brackets = 0; | |
283 | ||
284 | s++; | |
285 | goto newaddr; | |
286 | ||
287 | case ':': | |
288 | /* group - ignore */ | |
289 | ps->pos = 0; | |
290 | goto skip; | |
291 | ||
292 | case ',': | |
293 | case ';': | |
a5355bb2 SS |
294 | /* |
295 | * Next address, copy previous one. | |
296 | * However, we might be directly after | |
297 | * a <address>, or have two consecutive | |
298 | * commas. | |
299 | * Skip the comma unless there is | |
300 | * really something to copy. | |
301 | */ | |
302 | if (ps->pos == 0) | |
303 | goto skip; | |
f734c0ae SS |
304 | s++; |
305 | goto newaddr; | |
306 | ||
307 | default: | |
308 | goto copy; | |
309 | } | |
310 | ||
311 | copy: | |
312 | if (ps->comment) | |
313 | goto skip; | |
314 | ||
315 | if (ps->pos + 1 == sizeof(ps->addr)) | |
316 | goto err; | |
317 | ps->addr[ps->pos++] = *s; | |
318 | ||
319 | skip: | |
320 | ; | |
321 | } | |
322 | ||
323 | eol: | |
324 | ps->state = EOL; | |
325 | return (0); | |
326 | ||
327 | err: | |
328 | ps->state = QUIT; | |
329 | return (-1); | |
330 | ||
331 | newaddr: | |
332 | ps->addr[ps->pos] = 0; | |
333 | ps->pos = 0; | |
334 | addr = strdup(ps->addr); | |
335 | if (addr == NULL) | |
de196e33 | 336 | errlog(EX_SOFTWARE, NULL); |
f734c0ae | 337 | |
a803c7a6 | 338 | if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) |
de196e33 | 339 | errlogx(EX_DATAERR, "invalid recipient `%s'", addr); |
72ff257f | 340 | |
f734c0ae SS |
341 | goto again; |
342 | } | |
343 | ||
28be0b96 | 344 | int |
f734c0ae | 345 | readmail(struct queue *queue, int nodot, int recp_from_header) |
28be0b96 | 346 | { |
f734c0ae | 347 | struct parse_state parse_state; |
28be0b96 SS |
348 | char line[1000]; /* by RFC2822 */ |
349 | size_t linelen; | |
350 | size_t error; | |
351 | int had_headers = 0; | |
352 | int had_from = 0; | |
353 | int had_messagid = 0; | |
354 | int had_date = 0; | |
807f777a | 355 | int had_last_line = 0; |
f734c0ae SS |
356 | int nocopy = 0; |
357 | ||
358 | parse_state.state = NONE; | |
28be0b96 SS |
359 | |
360 | error = fprintf(queue->mailf, | |
361 | "Received: from %s (uid %d)\n" | |
362 | "\t(envelope-from %s)\n" | |
363 | "\tid %s\n" | |
5283d577 | 364 | "\tby %s (%s);\n" |
28be0b96 | 365 | "\t%s\n", |
75fdf182 | 366 | username, useruid, |
057afad5 | 367 | queue->sender, |
28be0b96 SS |
368 | queue->id, |
369 | hostname(), VERSION, | |
370 | rfc822date()); | |
371 | if ((ssize_t)error < 0) | |
372 | return (-1); | |
373 | ||
374 | while (!feof(stdin)) { | |
807f777a | 375 | if (fgets(line, sizeof(line) - 1, stdin) == NULL) |
28be0b96 | 376 | break; |
807f777a | 377 | if (had_last_line) |
de196e33 | 378 | errlogx(EX_DATAERR, "bad mail input format:" |
b1056e43 DH |
379 | " from %s (uid %d) (envelope-from %s)", |
380 | username, useruid, queue->sender); | |
28be0b96 SS |
381 | linelen = strlen(line); |
382 | if (linelen == 0 || line[linelen - 1] != '\n') { | |
807f777a SS |
383 | /* |
384 | * This line did not end with a newline character. | |
385 | * If we fix it, it better be the last line of | |
386 | * the file. | |
387 | */ | |
388 | line[linelen] = '\n'; | |
389 | line[linelen + 1] = 0; | |
390 | had_last_line = 1; | |
28be0b96 SS |
391 | } |
392 | if (!had_headers) { | |
f734c0ae SS |
393 | /* |
394 | * Unless this is a continuation, switch of | |
395 | * the Bcc: nocopy flag. | |
396 | */ | |
397 | if (!(line[0] == ' ' || line[0] == '\t')) | |
398 | nocopy = 0; | |
399 | ||
28be0b96 SS |
400 | if (strprefixcmp(line, "Date:") == 0) |
401 | had_date = 1; | |
402 | else if (strprefixcmp(line, "Message-Id:") == 0) | |
403 | had_messagid = 1; | |
404 | else if (strprefixcmp(line, "From:") == 0) | |
405 | had_from = 1; | |
f734c0ae SS |
406 | else if (strprefixcmp(line, "Bcc:") == 0) |
407 | nocopy = 1; | |
408 | ||
409 | if (parse_state.state != NONE) { | |
410 | if (parse_addrs(&parse_state, line, queue) < 0) { | |
de196e33 | 411 | errlogx(EX_DATAERR, "invalid address in header\n"); |
f734c0ae SS |
412 | /* NOTREACHED */ |
413 | } | |
414 | } | |
415 | ||
416 | if (recp_from_header && ( | |
417 | strprefixcmp(line, "To:") == 0 || | |
418 | strprefixcmp(line, "Cc:") == 0 || | |
419 | strprefixcmp(line, "Bcc:") == 0)) { | |
420 | parse_state.state = START; | |
421 | if (parse_addrs(&parse_state, line, queue) < 0) { | |
de196e33 | 422 | errlogx(EX_DATAERR, "invalid address in header\n"); |
f734c0ae SS |
423 | /* NOTREACHED */ |
424 | } | |
425 | } | |
28be0b96 | 426 | } |
f734c0ae | 427 | |
28be0b96 SS |
428 | if (strcmp(line, "\n") == 0 && !had_headers) { |
429 | had_headers = 1; | |
430 | while (!had_date || !had_messagid || !had_from) { | |
431 | if (!had_date) { | |
432 | had_date = 1; | |
433 | snprintf(line, sizeof(line), "Date: %s\n", rfc822date()); | |
434 | } else if (!had_messagid) { | |
ee613428 | 435 | /* XXX msgid, assign earlier and log? */ |
28be0b96 | 436 | had_messagid = 1; |
ee613428 SS |
437 | snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n", |
438 | (uintmax_t)time(NULL), | |
439 | queue->id, | |
6ec95705 | 440 | (uintmax_t)random(), |
ee613428 | 441 | hostname()); |
28be0b96 SS |
442 | } else if (!had_from) { |
443 | had_from = 1; | |
057afad5 | 444 | snprintf(line, sizeof(line), "From: <%s>\n", queue->sender); |
28be0b96 SS |
445 | } |
446 | if (fwrite(line, strlen(line), 1, queue->mailf) != 1) | |
447 | return (-1); | |
448 | } | |
449 | strcpy(line, "\n"); | |
450 | } | |
451 | if (!nodot && linelen == 2 && line[0] == '.') | |
452 | break; | |
f734c0ae SS |
453 | if (!nocopy) { |
454 | if (fwrite(line, strlen(line), 1, queue->mailf) != 1) | |
455 | return (-1); | |
456 | } | |
28be0b96 SS |
457 | } |
458 | ||
459 | return (0); | |
460 | } |