xref: /dflybsd-src/libexec/dma/mail.c (revision 577b958f5e57e9712ddc260c6666d661d03397fe)
130833a29SSimon Schubert /*
237d59876SJohn Marino  * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
330833a29SSimon Schubert  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
430833a29SSimon Schubert  *
530833a29SSimon Schubert  * This code is derived from software contributed to The DragonFly Project
637d59876SJohn Marino  * by Simon Schubert <2@0x2c.org>.
730833a29SSimon Schubert  *
830833a29SSimon Schubert  * Redistribution and use in source and binary forms, with or without
930833a29SSimon Schubert  * modification, are permitted provided that the following conditions
1030833a29SSimon Schubert  * are met:
1130833a29SSimon Schubert  *
1230833a29SSimon Schubert  * 1. Redistributions of source code must retain the above copyright
1330833a29SSimon Schubert  *    notice, this list of conditions and the following disclaimer.
1430833a29SSimon Schubert  * 2. Redistributions in binary form must reproduce the above copyright
1530833a29SSimon Schubert  *    notice, this list of conditions and the following disclaimer in
1630833a29SSimon Schubert  *    the documentation and/or other materials provided with the
1730833a29SSimon Schubert  *    distribution.
1830833a29SSimon Schubert  * 3. Neither the name of The DragonFly Project nor the names of its
1930833a29SSimon Schubert  *    contributors may be used to endorse or promote products derived
2030833a29SSimon Schubert  *    from this software without specific, prior written permission.
2130833a29SSimon Schubert  *
2230833a29SSimon Schubert  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2330833a29SSimon Schubert  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2430833a29SSimon Schubert  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
2530833a29SSimon Schubert  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
2630833a29SSimon Schubert  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
2730833a29SSimon Schubert  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
2830833a29SSimon Schubert  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2930833a29SSimon Schubert  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
3030833a29SSimon Schubert  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3130833a29SSimon Schubert  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
3230833a29SSimon Schubert  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3330833a29SSimon Schubert  * SUCH DAMAGE.
3430833a29SSimon Schubert  */
3530833a29SSimon Schubert 
3630833a29SSimon Schubert #include <errno.h>
37c8b07ee5SSascha Wildner #include <inttypes.h>
38c8b07ee5SSascha Wildner #include <signal.h>
3992fe556dSDaniel Fojt #include <strings.h>
4092fe556dSDaniel Fojt #include <string.h>
4130833a29SSimon Schubert #include <syslog.h>
4230833a29SSimon Schubert #include <unistd.h>
4330833a29SSimon Schubert 
4430833a29SSimon Schubert #include "dma.h"
4530833a29SSimon Schubert 
46a5737f81SDaniel Fojt #define MAX_LINE_RFC822	1000
47a5737f81SDaniel Fojt 
4830833a29SSimon Schubert void
bounce(struct qitem * it,const char * reason)4930833a29SSimon Schubert bounce(struct qitem *it, const char *reason)
5030833a29SSimon Schubert {
5130833a29SSimon Schubert 	struct queue bounceq;
5230833a29SSimon Schubert 	char line[1000];
5330833a29SSimon Schubert 	size_t pos;
5430833a29SSimon Schubert 	int error;
5530833a29SSimon Schubert 
5630833a29SSimon Schubert 	/* Don't bounce bounced mails */
5730833a29SSimon Schubert 	if (it->sender[0] == 0) {
5830833a29SSimon Schubert 		syslog(LOG_INFO, "can not bounce a bounce message, discarding");
5992fe556dSDaniel Fojt 		exit(EX_SOFTWARE);
6030833a29SSimon Schubert 	}
6130833a29SSimon Schubert 
62e50076f8SSimon Schubert 	bzero(&bounceq, sizeof(bounceq));
6330833a29SSimon Schubert 	LIST_INIT(&bounceq.queue);
641c9e6b7bSSimon Schubert 	bounceq.sender = "";
65c8b07ee5SSascha Wildner 	if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
6630833a29SSimon Schubert 		goto fail;
6730833a29SSimon Schubert 
681c9e6b7bSSimon Schubert 	if (newspoolf(&bounceq) != 0)
6930833a29SSimon Schubert 		goto fail;
7030833a29SSimon Schubert 
7130833a29SSimon Schubert 	syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
7230833a29SSimon Schubert 	setlogident("%s", bounceq.id);
7330833a29SSimon Schubert 
7430833a29SSimon Schubert 	error = fprintf(bounceq.mailf,
7530833a29SSimon Schubert 		"Received: from MAILER-DAEMON\n"
7630833a29SSimon Schubert 		"\tid %s\n"
77*577b958fSDaniel Fojt 		"\tby %s (%s on %s);\n"
7830833a29SSimon Schubert 		"\t%s\n"
7930833a29SSimon Schubert 		"X-Original-To: <%s>\n"
8030833a29SSimon Schubert 		"From: MAILER-DAEMON <>\n"
8130833a29SSimon Schubert 		"To: %s\n"
8230833a29SSimon Schubert 		"Subject: Mail delivery failed\n"
8330833a29SSimon Schubert 		"Message-Id: <%s@%s>\n"
8430833a29SSimon Schubert 		"Date: %s\n"
8530833a29SSimon Schubert 		"\n"
8630833a29SSimon Schubert 		"This is the %s at %s.\n"
8730833a29SSimon Schubert 		"\n"
8830833a29SSimon Schubert 		"There was an error delivering your mail to <%s>.\n"
8930833a29SSimon Schubert 		"\n"
9030833a29SSimon Schubert 		"%s\n"
9130833a29SSimon Schubert 		"\n"
9230833a29SSimon Schubert 		"%s\n"
9330833a29SSimon Schubert 		"\n",
9430833a29SSimon Schubert 		bounceq.id,
95*577b958fSDaniel Fojt 		hostname(), VERSION, systemhostname(),
9630833a29SSimon Schubert 		rfc822date(),
9730833a29SSimon Schubert 		it->addr,
9830833a29SSimon Schubert 		it->sender,
9930833a29SSimon Schubert 		bounceq.id, hostname(),
10030833a29SSimon Schubert 		rfc822date(),
10130833a29SSimon Schubert 		VERSION, hostname(),
10230833a29SSimon Schubert 		it->addr,
10330833a29SSimon Schubert 		reason,
104ca259d14SSimon Schubert 		config.features & FULLBOUNCE ?
10530833a29SSimon Schubert 		    "Original message follows." :
10630833a29SSimon Schubert 		    "Message headers follow.");
10730833a29SSimon Schubert 	if (error < 0)
10830833a29SSimon Schubert 		goto fail;
10930833a29SSimon Schubert 
110ebffba26SSimon Schubert 	if (fseek(it->mailf, 0, SEEK_SET) != 0)
11130833a29SSimon Schubert 		goto fail;
112ca259d14SSimon Schubert 	if (config.features & FULLBOUNCE) {
11330833a29SSimon Schubert 		while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
11430833a29SSimon Schubert 			if (fwrite(line, 1, pos, bounceq.mailf) != pos)
11530833a29SSimon Schubert 				goto fail;
11630833a29SSimon Schubert 		}
11730833a29SSimon Schubert 	} else {
11830833a29SSimon Schubert 		while (!feof(it->mailf)) {
11930833a29SSimon Schubert 			if (fgets(line, sizeof(line), it->mailf) == NULL)
12030833a29SSimon Schubert 				break;
12130833a29SSimon Schubert 			if (line[0] == '\n')
12230833a29SSimon Schubert 				break;
12330833a29SSimon Schubert 			if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
12430833a29SSimon Schubert 				goto fail;
12530833a29SSimon Schubert 		}
12630833a29SSimon Schubert 	}
12730833a29SSimon Schubert 
1281c9e6b7bSSimon Schubert 	if (linkspool(&bounceq) != 0)
12930833a29SSimon Schubert 		goto fail;
13030833a29SSimon Schubert 	/* bounce is safe */
13130833a29SSimon Schubert 
13230833a29SSimon Schubert 	delqueue(it);
13330833a29SSimon Schubert 
13430833a29SSimon Schubert 	run_queue(&bounceq);
13530833a29SSimon Schubert 	/* NOTREACHED */
13630833a29SSimon Schubert 
13730833a29SSimon Schubert fail:
13830833a29SSimon Schubert 	syslog(LOG_CRIT, "error creating bounce: %m");
13930833a29SSimon Schubert 	delqueue(it);
14092fe556dSDaniel Fojt 	exit(EX_IOERR);
14130833a29SSimon Schubert }
14230833a29SSimon Schubert 
143ff48fce6SSimon Schubert struct parse_state {
144ff48fce6SSimon Schubert 	char addr[1000];
145ff48fce6SSimon Schubert 	int pos;
146ff48fce6SSimon Schubert 
147ff48fce6SSimon Schubert 	enum {
148ff48fce6SSimon Schubert 		NONE = 0,
149ff48fce6SSimon Schubert 		START,
150ff48fce6SSimon Schubert 		MAIN,
151ff48fce6SSimon Schubert 		EOL,
152ff48fce6SSimon Schubert 		QUIT
153ff48fce6SSimon Schubert 	} state;
154ff48fce6SSimon Schubert 	int comment;
155ff48fce6SSimon Schubert 	int quote;
156ff48fce6SSimon Schubert 	int brackets;
157ff48fce6SSimon Schubert 	int esc;
158ff48fce6SSimon Schubert };
159ff48fce6SSimon Schubert 
160ff48fce6SSimon Schubert /*
161ff48fce6SSimon Schubert  * Simplified RFC2822 header/address parsing.
162ff48fce6SSimon Schubert  * We copy escapes and quoted strings directly, since
163ff48fce6SSimon Schubert  * we have to pass them like this to the mail server anyways.
164ff48fce6SSimon Schubert  * XXX local addresses will need treatment
165ff48fce6SSimon Schubert  */
166ff48fce6SSimon Schubert static int
parse_addrs(struct parse_state * ps,char * s,struct queue * queue)167ff48fce6SSimon Schubert parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
16830833a29SSimon Schubert {
169ff48fce6SSimon Schubert 	char *addr;
170ff48fce6SSimon Schubert 
171ff48fce6SSimon Schubert again:
172ff48fce6SSimon Schubert 	switch (ps->state) {
173ff48fce6SSimon Schubert 	case NONE:
174ff48fce6SSimon Schubert 		return (-1);
175ff48fce6SSimon Schubert 
176ff48fce6SSimon Schubert 	case START:
177ff48fce6SSimon Schubert 		/* init our data */
178ff48fce6SSimon Schubert 		bzero(ps, sizeof(*ps));
179ff48fce6SSimon Schubert 
180ff48fce6SSimon Schubert 		/* skip over header name */
181ff48fce6SSimon Schubert 		while (*s != ':')
182ff48fce6SSimon Schubert 			s++;
183ff48fce6SSimon Schubert 		s++;
184ff48fce6SSimon Schubert 		ps->state = MAIN;
185ff48fce6SSimon Schubert 		break;
186ff48fce6SSimon Schubert 
187ff48fce6SSimon Schubert 	case MAIN:
188ff48fce6SSimon Schubert 		/* all fine */
189ff48fce6SSimon Schubert 		break;
190ff48fce6SSimon Schubert 
191ff48fce6SSimon Schubert 	case EOL:
192ff48fce6SSimon Schubert 		switch (*s) {
193ff48fce6SSimon Schubert 		case ' ':
194ff48fce6SSimon Schubert 		case '\t':
195*577b958fSDaniel Fojt 			s++;
196*577b958fSDaniel Fojt 			/* continue */
197ff48fce6SSimon Schubert 			break;
198ff48fce6SSimon Schubert 
199ff48fce6SSimon Schubert 		default:
200ff48fce6SSimon Schubert 			ps->state = QUIT;
201ff48fce6SSimon Schubert 			if (ps->pos != 0)
202ff48fce6SSimon Schubert 				goto newaddr;
203ff48fce6SSimon Schubert 			return (0);
204ff48fce6SSimon Schubert 		}
205c6b7f0daSzrj 		/* FALLTHROUGH */
206ff48fce6SSimon Schubert 	case QUIT:
207ff48fce6SSimon Schubert 		return (0);
208ff48fce6SSimon Schubert 	}
209ff48fce6SSimon Schubert 
210ff48fce6SSimon Schubert 	for (; *s != 0; s++) {
211ff48fce6SSimon Schubert 		if (ps->esc) {
212ff48fce6SSimon Schubert 			ps->esc = 0;
213ff48fce6SSimon Schubert 
214ff48fce6SSimon Schubert 			switch (*s) {
215ff48fce6SSimon Schubert 			case '\r':
216ff48fce6SSimon Schubert 			case '\n':
217ff48fce6SSimon Schubert 				goto err;
218ff48fce6SSimon Schubert 
219ff48fce6SSimon Schubert 			default:
220ff48fce6SSimon Schubert 				goto copy;
221ff48fce6SSimon Schubert 			}
222ff48fce6SSimon Schubert 		}
223ff48fce6SSimon Schubert 
224ff48fce6SSimon Schubert 		if (ps->quote) {
225ff48fce6SSimon Schubert 			switch (*s) {
226ff48fce6SSimon Schubert 			case '"':
227ff48fce6SSimon Schubert 				ps->quote = 0;
228ff48fce6SSimon Schubert 				goto copy;
229ff48fce6SSimon Schubert 
230ff48fce6SSimon Schubert 			case '\\':
231ff48fce6SSimon Schubert 				ps->esc = 1;
232ff48fce6SSimon Schubert 				goto copy;
233ff48fce6SSimon Schubert 
234ff48fce6SSimon Schubert 			case '\r':
235ff48fce6SSimon Schubert 			case '\n':
236ff48fce6SSimon Schubert 				goto eol;
237ff48fce6SSimon Schubert 
238ff48fce6SSimon Schubert 			default:
239ff48fce6SSimon Schubert 				goto copy;
240ff48fce6SSimon Schubert 			}
241ff48fce6SSimon Schubert 		}
242ff48fce6SSimon Schubert 
243ff48fce6SSimon Schubert 		switch (*s) {
244ff48fce6SSimon Schubert 		case '(':
245ff48fce6SSimon Schubert 			ps->comment++;
246ff48fce6SSimon Schubert 			break;
247ff48fce6SSimon Schubert 
248ff48fce6SSimon Schubert 		case ')':
249ff48fce6SSimon Schubert 			if (ps->comment)
250ff48fce6SSimon Schubert 				ps->comment--;
251ff48fce6SSimon Schubert 			else
252ff48fce6SSimon Schubert 				goto err;
253ff48fce6SSimon Schubert 			goto skip;
254ff48fce6SSimon Schubert 
255ff48fce6SSimon Schubert 		case '"':
256ff48fce6SSimon Schubert 			ps->quote = 1;
257ff48fce6SSimon Schubert 			goto copy;
258ff48fce6SSimon Schubert 
259ff48fce6SSimon Schubert 		case '\\':
260ff48fce6SSimon Schubert 			ps->esc = 1;
261ff48fce6SSimon Schubert 			goto copy;
262ff48fce6SSimon Schubert 
263ff48fce6SSimon Schubert 		case '\r':
264ff48fce6SSimon Schubert 		case '\n':
265ff48fce6SSimon Schubert 			goto eol;
266ff48fce6SSimon Schubert 		}
267ff48fce6SSimon Schubert 
268ff48fce6SSimon Schubert 		if (ps->comment)
269ff48fce6SSimon Schubert 			goto skip;
270ff48fce6SSimon Schubert 
271ff48fce6SSimon Schubert 		switch (*s) {
272ff48fce6SSimon Schubert 		case ' ':
273ff48fce6SSimon Schubert 		case '\t':
274ff48fce6SSimon Schubert 			/* ignore whitespace */
275ff48fce6SSimon Schubert 			goto skip;
276ff48fce6SSimon Schubert 
277ff48fce6SSimon Schubert 		case '<':
278c8b07ee5SSascha Wildner 			/* this is the real address now */
279ff48fce6SSimon Schubert 			ps->brackets = 1;
280307f84e1SMatthias Schmidt 			ps->pos = 0;
281ff48fce6SSimon Schubert 			goto skip;
282ff48fce6SSimon Schubert 
283ff48fce6SSimon Schubert 		case '>':
284ff48fce6SSimon Schubert 			if (!ps->brackets)
285ff48fce6SSimon Schubert 				goto err;
286ff48fce6SSimon Schubert 			ps->brackets = 0;
287ff48fce6SSimon Schubert 
288ff48fce6SSimon Schubert 			s++;
289ff48fce6SSimon Schubert 			goto newaddr;
290ff48fce6SSimon Schubert 
291ff48fce6SSimon Schubert 		case ':':
292ff48fce6SSimon Schubert 			/* group - ignore */
293ff48fce6SSimon Schubert 			ps->pos = 0;
294ff48fce6SSimon Schubert 			goto skip;
295ff48fce6SSimon Schubert 
296ff48fce6SSimon Schubert 		case ',':
297ff48fce6SSimon Schubert 		case ';':
298c8b07ee5SSascha Wildner 			/*
299c8b07ee5SSascha Wildner 			 * Next address, copy previous one.
300c8b07ee5SSascha Wildner 			 * However, we might be directly after
301c8b07ee5SSascha Wildner 			 * a <address>, or have two consecutive
302c8b07ee5SSascha Wildner 			 * commas.
303c8b07ee5SSascha Wildner 			 * Skip the comma unless there is
304c8b07ee5SSascha Wildner 			 * really something to copy.
305c8b07ee5SSascha Wildner 			 */
306c8b07ee5SSascha Wildner 			if (ps->pos == 0)
307c8b07ee5SSascha Wildner 				goto skip;
308ff48fce6SSimon Schubert 			s++;
309ff48fce6SSimon Schubert 			goto newaddr;
310ff48fce6SSimon Schubert 
311ff48fce6SSimon Schubert 		default:
312ff48fce6SSimon Schubert 			goto copy;
313ff48fce6SSimon Schubert 		}
314ff48fce6SSimon Schubert 
315ff48fce6SSimon Schubert copy:
316ff48fce6SSimon Schubert 		if (ps->comment)
317ff48fce6SSimon Schubert 			goto skip;
318ff48fce6SSimon Schubert 
319ff48fce6SSimon Schubert 		if (ps->pos + 1 == sizeof(ps->addr))
320ff48fce6SSimon Schubert 			goto err;
321ff48fce6SSimon Schubert 		ps->addr[ps->pos++] = *s;
322ff48fce6SSimon Schubert 
323ff48fce6SSimon Schubert skip:
324ff48fce6SSimon Schubert 		;
325ff48fce6SSimon Schubert 	}
326ff48fce6SSimon Schubert 
327ff48fce6SSimon Schubert eol:
328ff48fce6SSimon Schubert 	ps->state = EOL;
329ff48fce6SSimon Schubert 	return (0);
330ff48fce6SSimon Schubert 
331ff48fce6SSimon Schubert err:
332ff48fce6SSimon Schubert 	ps->state = QUIT;
333ff48fce6SSimon Schubert 	return (-1);
334ff48fce6SSimon Schubert 
335ff48fce6SSimon Schubert newaddr:
336ff48fce6SSimon Schubert 	ps->addr[ps->pos] = 0;
337ff48fce6SSimon Schubert 	ps->pos = 0;
338ff48fce6SSimon Schubert 	addr = strdup(ps->addr);
339ff48fce6SSimon Schubert 	if (addr == NULL)
34092fe556dSDaniel Fojt 		errlog(EX_SOFTWARE, NULL);
341ff48fce6SSimon Schubert 
342c8b07ee5SSascha Wildner 	if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
34392fe556dSDaniel Fojt 		errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
344c8b07ee5SSascha Wildner 
345ff48fce6SSimon Schubert 	goto again;
346ff48fce6SSimon Schubert }
347ff48fce6SSimon Schubert 
348a5737f81SDaniel Fojt static int
writeline(struct queue * queue,const char * line,ssize_t linelen)349a5737f81SDaniel Fojt writeline(struct queue *queue, const char *line, ssize_t linelen)
350a5737f81SDaniel Fojt {
351a5737f81SDaniel Fojt 	ssize_t len;
352a5737f81SDaniel Fojt 
353a5737f81SDaniel Fojt 	while (linelen > 0) {
354a5737f81SDaniel Fojt 		len = linelen;
355a5737f81SDaniel Fojt 		if (linelen > MAX_LINE_RFC822) {
356a5737f81SDaniel Fojt 			len = MAX_LINE_RFC822 - 10;
357a5737f81SDaniel Fojt 		}
358a5737f81SDaniel Fojt 
359a5737f81SDaniel Fojt 		if (fwrite(line, len, 1, queue->mailf) != 1)
360a5737f81SDaniel Fojt 			return (-1);
361a5737f81SDaniel Fojt 
362a5737f81SDaniel Fojt 		if (linelen <= MAX_LINE_RFC822)
363a5737f81SDaniel Fojt 			break;
364a5737f81SDaniel Fojt 
365a5737f81SDaniel Fojt 		if (fwrite("\n", 1, 1, queue->mailf) != 1)
366a5737f81SDaniel Fojt 			return (-1);
367a5737f81SDaniel Fojt 
368a5737f81SDaniel Fojt 		line += MAX_LINE_RFC822 - 10;
369a5737f81SDaniel Fojt 		linelen = strlen(line);
370a5737f81SDaniel Fojt 	}
371a5737f81SDaniel Fojt 	return (0);
372a5737f81SDaniel Fojt }
373a5737f81SDaniel Fojt 
374ff48fce6SSimon Schubert int
readmail(struct queue * queue,int nodot,int recp_from_header)375ff48fce6SSimon Schubert readmail(struct queue *queue, int nodot, int recp_from_header)
376ff48fce6SSimon Schubert {
377ff48fce6SSimon Schubert 	struct parse_state parse_state;
378a5737f81SDaniel Fojt 	char *line = NULL;
379a5737f81SDaniel Fojt 	ssize_t linelen;
380a5737f81SDaniel Fojt 	size_t linecap = 0;
381a5737f81SDaniel Fojt 	char newline[MAX_LINE_RFC822];
38230833a29SSimon Schubert 	size_t error;
38330833a29SSimon Schubert 	int had_headers = 0;
38430833a29SSimon Schubert 	int had_from = 0;
38530833a29SSimon Schubert 	int had_messagid = 0;
38630833a29SSimon Schubert 	int had_date = 0;
38792fe556dSDaniel Fojt 	int had_first_line = 0;
388ff48fce6SSimon Schubert 	int nocopy = 0;
389a5737f81SDaniel Fojt 	int ret = -1;
390ff48fce6SSimon Schubert 
391ff48fce6SSimon Schubert 	parse_state.state = NONE;
39230833a29SSimon Schubert 
39330833a29SSimon Schubert 	error = fprintf(queue->mailf,
39430833a29SSimon Schubert 		"Received: from %s (uid %d)\n"
39530833a29SSimon Schubert 		"\t(envelope-from %s)\n"
39630833a29SSimon Schubert 		"\tid %s\n"
397*577b958fSDaniel Fojt 		"\tby %s (%s on %s);\n"
39830833a29SSimon Schubert 		"\t%s\n",
399c8b07ee5SSascha Wildner 		username, useruid,
4001c9e6b7bSSimon Schubert 		queue->sender,
40130833a29SSimon Schubert 		queue->id,
402*577b958fSDaniel Fojt 		hostname(), VERSION, systemhostname(),
40330833a29SSimon Schubert 		rfc822date());
40430833a29SSimon Schubert 	if ((ssize_t)error < 0)
40530833a29SSimon Schubert 		return (-1);
40630833a29SSimon Schubert 
40730833a29SSimon Schubert 	while (!feof(stdin)) {
408a5737f81SDaniel Fojt 		newline[0] = '\0';
409a5737f81SDaniel Fojt 		if ((linelen = getline(&line, &linecap, stdin)) <= 0)
41030833a29SSimon Schubert 			break;
41192fe556dSDaniel Fojt 		if (!had_first_line) {
41292fe556dSDaniel Fojt 			/*
41392fe556dSDaniel Fojt 			 * Ignore a leading RFC-976 From_ or >From_ line mistakenly
41492fe556dSDaniel Fojt 			 * inserted by some programs.
41592fe556dSDaniel Fojt 			 */
41692fe556dSDaniel Fojt 			if (strprefixcmp(line, "From ") == 0 || strprefixcmp(line, ">From ") == 0)
41792fe556dSDaniel Fojt 				continue;
41892fe556dSDaniel Fojt 			had_first_line = 1;
41992fe556dSDaniel Fojt 		}
42030833a29SSimon Schubert 		if (!had_headers) {
421a5737f81SDaniel Fojt 			if (linelen > MAX_LINE_RFC822) {
422a5737f81SDaniel Fojt 				errlogx(EX_DATAERR, "bad mail input format:"
423a5737f81SDaniel Fojt 				    " from %s (uid %d) (envelope-from %s)",
424a5737f81SDaniel Fojt 				    username, useruid, queue->sender);
425a5737f81SDaniel Fojt 			}
426ff48fce6SSimon Schubert 			/*
427ff48fce6SSimon Schubert 			 * Unless this is a continuation, switch of
428ff48fce6SSimon Schubert 			 * the Bcc: nocopy flag.
429ff48fce6SSimon Schubert 			 */
430ff48fce6SSimon Schubert 			if (!(line[0] == ' ' || line[0] == '\t'))
431ff48fce6SSimon Schubert 				nocopy = 0;
432ff48fce6SSimon Schubert 
43330833a29SSimon Schubert 			if (strprefixcmp(line, "Date:") == 0)
43430833a29SSimon Schubert 				had_date = 1;
43530833a29SSimon Schubert 			else if (strprefixcmp(line, "Message-Id:") == 0)
43630833a29SSimon Schubert 				had_messagid = 1;
43730833a29SSimon Schubert 			else if (strprefixcmp(line, "From:") == 0)
43830833a29SSimon Schubert 				had_from = 1;
439ff48fce6SSimon Schubert 			else if (strprefixcmp(line, "Bcc:") == 0)
440ff48fce6SSimon Schubert 				nocopy = 1;
441ff48fce6SSimon Schubert 
442ff48fce6SSimon Schubert 			if (parse_state.state != NONE) {
443ff48fce6SSimon Schubert 				if (parse_addrs(&parse_state, line, queue) < 0) {
44492fe556dSDaniel Fojt 					errlogx(EX_DATAERR, "invalid address in header\n");
445ff48fce6SSimon Schubert 					/* NOTREACHED */
44630833a29SSimon Schubert 				}
447ff48fce6SSimon Schubert 			}
448ff48fce6SSimon Schubert 
449ff48fce6SSimon Schubert 			if (recp_from_header && (
450ff48fce6SSimon Schubert 					strprefixcmp(line, "To:") == 0 ||
451ff48fce6SSimon Schubert 					strprefixcmp(line, "Cc:") == 0 ||
452ff48fce6SSimon Schubert 					strprefixcmp(line, "Bcc:") == 0)) {
453ff48fce6SSimon Schubert 				parse_state.state = START;
454ff48fce6SSimon Schubert 				if (parse_addrs(&parse_state, line, queue) < 0) {
45592fe556dSDaniel Fojt 					errlogx(EX_DATAERR, "invalid address in header\n");
456ff48fce6SSimon Schubert 					/* NOTREACHED */
457ff48fce6SSimon Schubert 				}
458ff48fce6SSimon Schubert 			}
459ff48fce6SSimon Schubert 		}
460ff48fce6SSimon Schubert 
46130833a29SSimon Schubert 		if (strcmp(line, "\n") == 0 && !had_headers) {
46230833a29SSimon Schubert 			had_headers = 1;
46330833a29SSimon Schubert 			while (!had_date || !had_messagid || !had_from) {
46430833a29SSimon Schubert 				if (!had_date) {
46530833a29SSimon Schubert 					had_date = 1;
466a5737f81SDaniel Fojt 					snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date());
46730833a29SSimon Schubert 				} else if (!had_messagid) {
468c8b07ee5SSascha Wildner 					/* XXX msgid, assign earlier and log? */
46930833a29SSimon Schubert 					had_messagid = 1;
470a5737f81SDaniel Fojt 					snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
471c8b07ee5SSascha Wildner 						 (uintmax_t)time(NULL),
472c8b07ee5SSascha Wildner 						 queue->id,
473c8b07ee5SSascha Wildner 						 (uintmax_t)random(),
474c8b07ee5SSascha Wildner 						 hostname());
47530833a29SSimon Schubert 				} else if (!had_from) {
47630833a29SSimon Schubert 					had_from = 1;
477a5737f81SDaniel Fojt 					snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender);
47830833a29SSimon Schubert 				}
479a5737f81SDaniel Fojt 				if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
480a5737f81SDaniel Fojt 					goto fail;
48130833a29SSimon Schubert 			}
482a5737f81SDaniel Fojt 			strlcpy(newline, "\n", sizeof(newline));
48330833a29SSimon Schubert 		}
48430833a29SSimon Schubert 		if (!nodot && linelen == 2 && line[0] == '.')
48530833a29SSimon Schubert 			break;
486ff48fce6SSimon Schubert 		if (!nocopy) {
487a5737f81SDaniel Fojt 			if (newline[0]) {
488a5737f81SDaniel Fojt 				if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
489a5737f81SDaniel Fojt 					goto fail;
490a5737f81SDaniel Fojt 			} else {
491a5737f81SDaniel Fojt 				if (writeline(queue, line, linelen) != 0)
492a5737f81SDaniel Fojt 					goto fail;
493a5737f81SDaniel Fojt 			}
49430833a29SSimon Schubert 		}
495ff48fce6SSimon Schubert 	}
49630833a29SSimon Schubert 
497a5737f81SDaniel Fojt 	ret = 0;
498a5737f81SDaniel Fojt fail:
499a5737f81SDaniel Fojt 	free(line);
500a5737f81SDaniel Fojt 	return (ret);
50130833a29SSimon Schubert }
502