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