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