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