cleanup_envelope.c revision 1.3
1/*	$NetBSD: cleanup_envelope.c,v 1.3 2010/06/17 18:18:15 tron Exp $	*/
2
3/*++
4/* NAME
5/*	cleanup_envelope 3
6/* SUMMARY
7/*	process envelope segment
8/* SYNOPSIS
9/*	#include <cleanup.h>
10/*
11/*	void	cleanup_envelope(state, type, buf, len)
12/*	CLEANUP_STATE *state;
13/*	int	type;
14/*	const char *buf;
15/*	ssize_t	len;
16/* DESCRIPTION
17/*	This module processes envelope records and writes the result
18/*	to the queue file.  It validates the message structure, rewrites
19/*	sender/recipient addresses to canonical form, and expands recipients
20/*	according to entries in the virtual table. This routine absorbs but
21/*	does not emit the envelope to content boundary record.
22/*
23/*	Arguments:
24/* .IP state
25/*	Queue file and message processing state. This state is updated
26/*	as records are processed and as errors happen.
27/* .IP type
28/*	Record type.
29/* .IP buf
30/*	Record content.
31/* .IP len
32/*	Record content length.
33/* LICENSE
34/* .ad
35/* .fi
36/*	The Secure Mailer license must be distributed with this software.
37/* AUTHOR(S)
38/*	Wietse Venema
39/*	IBM T.J. Watson Research
40/*	P.O. Box 704
41/*	Yorktown Heights, NY 10598, USA
42/*--*/
43
44/* System library. */
45
46#include <sys_defs.h>
47#include <string.h>
48#include <stdlib.h>
49#include <ctype.h>
50
51/* Utility library. */
52
53#include <msg.h>
54#include <vstring.h>
55#include <vstream.h>
56#include <mymalloc.h>
57#include <stringops.h>
58#include <nvtable.h>
59
60/* Global library. */
61
62#include <record.h>
63#include <rec_type.h>
64#include <cleanup_user.h>
65#include <qmgr_user.h>
66#include <mail_params.h>
67#include <verp_sender.h>
68#include <mail_proto.h>
69#include <dsn_mask.h>
70#include <rec_attr_map.h>
71
72/* Application-specific. */
73
74#include "cleanup.h"
75
76#define STR	vstring_str
77#define STREQ(x,y) (strcmp((x), (y)) == 0)
78
79static void cleanup_envelope_process(CLEANUP_STATE *, int, const char *, ssize_t);
80
81/* cleanup_envelope - initialize message envelope */
82
83void    cleanup_envelope(CLEANUP_STATE *state, int type,
84			         const char *str, ssize_t len)
85{
86
87    /*
88     * The message size and count record goes first, so it can easily be
89     * updated in place. This information takes precedence over any size
90     * estimate provided by the client. It's all in one record, data size
91     * first, for backwards compatibility reasons.
92     */
93    cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
94		       (REC_TYPE_SIZE_CAST1) 0,	/* extra offs - content offs */
95		       (REC_TYPE_SIZE_CAST2) 0,	/* content offset */
96		       (REC_TYPE_SIZE_CAST3) 0,	/* recipient count */
97		       (REC_TYPE_SIZE_CAST4) 0,	/* qmgr options */
98		       (REC_TYPE_SIZE_CAST5) 0);	/* content length */
99
100    /*
101     * Pass control to the actual envelope processing routine.
102     */
103    state->action = cleanup_envelope_process;
104    cleanup_envelope_process(state, type, str, len);
105}
106
107/* cleanup_envelope_process - process one envelope record */
108
109static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
110				             const char *buf, ssize_t len)
111{
112    const char *myname = "cleanup_envelope_process";
113    char   *attr_name;
114    char   *attr_value;
115    const char *error_text;
116    int     extra_opts;
117    int     junk;
118    int     mapped_type = type;
119    const char *mapped_buf = buf;
120    int     milter_count;
121
122#ifdef DELAY_ACTION
123    int     defer_delay;
124
125#endif
126
127    if (msg_verbose)
128	msg_info("initial envelope %c %.*s", type, (int) len, buf);
129
130    if (type == REC_TYPE_FLGS) {
131	/* Not part of queue file format. */
132	extra_opts = atoi(buf);
133	if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA)
134	    msg_warn("%s: ignoring bad extra flags: 0x%x",
135		     state->queue_id, extra_opts);
136	else
137	    state->flags |= extra_opts;
138	return;
139    }
140#ifdef DELAY_ACTION
141    if (type == REC_TYPE_DELAY) {
142	/* Not part of queue file format. */
143	defer_delay = atoi(buf);
144	if (defer_delay <= 0)
145	    msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf);
146	else
147	    state->defer_delay = defer_delay;
148	return;
149    }
150#endif
151
152    /*
153     * XXX We instantiate a MILTERS structure even when the filter count is
154     * zero (for example, all filters are in ACCEPT state, or the SMTP server
155     * sends a dummy MILTERS structure without any filters), otherwise the
156     * cleanup server would apply the non_smtpd_milters setting
157     * inappropriately.
158     */
159    if (type == REC_TYPE_MILT_COUNT) {
160	/* Not part of queue file format. */
161	if ((milter_count = atoi(buf)) >= 0)
162	    cleanup_milter_receive(state, milter_count);
163	return;
164    }
165
166    /*
167     * Map DSN attribute name to pseudo record type so that we don't have to
168     * pollute the queue file with records that are incompatible with past
169     * Postfix versions. Preferably, people should be able to back out from
170     * an upgrade without losing mail.
171     */
172    if (type == REC_TYPE_ATTR) {
173	vstring_strcpy(state->attr_buf, buf);
174	error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value);
175	if (error_text != 0) {
176	    msg_warn("%s: message rejected: malformed attribute: %s: %.100s",
177		     state->queue_id, error_text, buf);
178	    state->errs |= CLEANUP_STAT_BAD;
179	    return;
180	}
181	/* Zero-length values are place holders for unavailable values. */
182	if (*attr_value == 0) {
183	    msg_warn("%s: spurious null attribute value for \"%s\" -- ignored",
184		     state->queue_id, attr_name);
185	    return;
186	}
187	if ((junk = rec_attr_map(attr_name)) != 0) {
188	    mapped_buf = attr_value;
189	    mapped_type = junk;
190	}
191    }
192
193    /*
194     * Sanity check.
195     */
196    if (strchr(REC_TYPE_ENVELOPE, type) == 0) {
197	msg_warn("%s: message rejected: unexpected record type %d in envelope",
198		 state->queue_id, type);
199	state->errs |= CLEANUP_STAT_BAD;
200	return;
201    }
202
203    /*
204     * Although recipient records appear at the end of the initial or
205     * extracted envelope, the code for processing recipient records is first
206     * because there can be lots of them.
207     *
208     * Recipient records may be mixed with other information (such as FILTER or
209     * REDIRECT actions from SMTPD). In that case the queue manager needs to
210     * examine all queue file records before it can start delivery. This is
211     * not a problem when SMTPD recipient lists are small.
212     *
213     * However, if recipient records are not mixed with other records
214     * (typically, mailing list mail) then we can make an optimization: the
215     * queue manager does not need to examine every envelope record before it
216     * can start deliveries. This can help with very large mailing lists.
217     */
218
219    /*
220     * On the transition from non-recipient records to recipient records,
221     * emit some records and do some sanity checks.
222     *
223     * XXX Moving the envelope sender (and the test for its presence) to the
224     * extracted segment can reduce qmqpd memory requirements because it no
225     * longer needs to read the entire message into main memory.
226     */
227    if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
228	&& strchr(REC_TYPE_ENV_RECIPIENT, type) != 0) {
229	if (state->sender == 0) {
230	    msg_warn("%s: message rejected: missing sender envelope record",
231		     state->queue_id);
232	    state->errs |= CLEANUP_STAT_BAD;
233	    return;
234	}
235	if (state->arrival_time.tv_sec == 0) {
236	    msg_warn("%s: message rejected: missing time envelope record",
237		     state->queue_id);
238	    state->errs |= CLEANUP_STAT_BAD;
239	    return;
240	}
241
242	/*
243	 * XXX This works by accident, because the sender is recorded at the
244	 * beginning of the envelope segment.
245	 */
246	if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0
247	    && state->sender && *state->sender
248	    && var_delay_warn_time > 0) {
249	    cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
250			       REC_TYPE_WARN_ARG(state->arrival_time.tv_sec
251						 + var_delay_warn_time));
252	}
253	state->flags |= CLEANUP_FLAG_INRCPT;
254    }
255
256    /*
257     * Initial envelope recipient record processing.
258     */
259    if (type == REC_TYPE_RCPT) {
260	if (state->sender == 0) {		/* protect showq */
261	    msg_warn("%s: message rejected: envelope recipient precedes sender",
262		     state->queue_id);
263	    state->errs |= CLEANUP_STAT_BAD;
264	    return;
265	}
266	if (state->orig_rcpt == 0)
267	    state->orig_rcpt = mystrdup(buf);
268	cleanup_addr_recipient(state, buf);
269	if (cleanup_milters != 0
270	    && state->milters == 0
271	    && CLEANUP_MILTER_OK(state))
272	    cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip);
273	myfree(state->orig_rcpt);
274	state->orig_rcpt = 0;
275	if (state->dsn_orcpt != 0) {
276	    myfree(state->dsn_orcpt);
277	    state->dsn_orcpt = 0;
278	}
279	state->dsn_notify = 0;
280	return;
281    }
282    if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) {
283	if (state->orig_rcpt != 0) {
284	    myfree(state->orig_rcpt);
285	    state->orig_rcpt = 0;
286	}
287	if (state->dsn_orcpt != 0) {
288	    myfree(state->dsn_orcpt);
289	    state->dsn_orcpt = 0;
290	}
291	state->dsn_notify = 0;
292	return;
293    }
294    if (mapped_type == REC_TYPE_DSN_ORCPT) {
295	if (state->dsn_orcpt) {
296	    msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>",
297		     state->queue_id, state->dsn_orcpt);
298	    myfree(state->dsn_orcpt);
299	}
300	state->dsn_orcpt = mystrdup(mapped_buf);
301	return;
302    }
303    if (mapped_type == REC_TYPE_DSN_NOTIFY) {
304	if (state->dsn_notify) {
305	    msg_warn("%s: ignoring out-of-order DSN notify record <%d>",
306		     state->queue_id, state->dsn_notify);
307	    state->dsn_notify = 0;
308	}
309	if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
310	    || DSN_NOTIFY_OK(junk) == 0)
311	    msg_warn("%s: ignoring malformed DSN notify record <%.200s>",
312		     state->queue_id, buf);
313	else
314	    state->qmgr_opts |=
315		QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk);
316	return;
317    }
318    if (type == REC_TYPE_ORCP) {
319	if (state->orig_rcpt != 0) {
320	    msg_warn("%s: ignoring out-of-order original recipient record <%.200s>",
321		     state->queue_id, state->orig_rcpt);
322	    myfree(state->orig_rcpt);
323	}
324	state->orig_rcpt = mystrdup(buf);
325	return;
326    }
327    if (type == REC_TYPE_MESG) {
328	state->action = cleanup_message;
329	if (state->flags & CLEANUP_FLAG_INRCPT) {
330	    if (state->milters || cleanup_milters) {
331		/* Make room to append recipient. */
332		if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0)
333		    msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
334		cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
335		if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0)
336		    msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
337	    }
338	    state->flags &= ~CLEANUP_FLAG_INRCPT;
339	}
340	return;
341    }
342
343    /*
344     * Initial envelope non-recipient record processing.
345     */
346    if (state->flags & CLEANUP_FLAG_INRCPT)
347	/* Tell qmgr that recipient records are mixed with other information. */
348	state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER;
349    if (type == REC_TYPE_SIZE)
350	/* Use our own SIZE record instead. */
351	return;
352    if (mapped_type == REC_TYPE_CTIME)
353	/* Use our own expiration time base record instead. */
354	return;
355    if (type == REC_TYPE_TIME) {
356	/* First instance wins. */
357	if (state->arrival_time.tv_sec == 0) {
358	    REC_TYPE_TIME_SCAN(buf, state->arrival_time);
359	    cleanup_out(state, type, buf, len);
360	}
361	/* Generate our own expiration time base record. */
362	cleanup_out_format(state, REC_TYPE_ATTR, "%s=%ld",
363			   MAIL_ATTR_CREATE_TIME, (long) time((time_t *) 0));
364	return;
365    }
366    if (type == REC_TYPE_FULL) {
367	/* First instance wins. */
368	if (state->fullname == 0) {
369	    state->fullname = mystrdup(buf);
370	    cleanup_out(state, type, buf, len);
371	}
372	return;
373    }
374    if (type == REC_TYPE_FROM) {
375	/* Allow only one instance. */
376	if (state->sender != 0) {
377	    msg_warn("%s: message rejected: multiple envelope sender records",
378		     state->queue_id);
379	    state->errs |= CLEANUP_STAT_BAD;
380	    return;
381	}
382	if (state->milters || cleanup_milters) {
383	    /* Remember the sender record offset. */
384	    if ((state->sender_pt_offset = vstream_ftell(state->dst)) < 0)
385		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
386	}
387	cleanup_addr_sender(state, buf);
388	if (state->milters || cleanup_milters) {
389	    /* Make room to replace sender. */
390	    if ((len = strlen(state->sender)) < REC_TYPE_PTR_PAYL_SIZE)
391		rec_pad(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_PAYL_SIZE - len);
392	    /* Remember the after-sender record offset. */
393	    if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0)
394		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
395	}
396	if (cleanup_milters != 0
397	    && state->milters == 0
398	    && CLEANUP_MILTER_OK(state))
399	    cleanup_milter_emul_mail(state, cleanup_milters, state->sender);
400	return;
401    }
402    if (mapped_type == REC_TYPE_DSN_ENVID) {
403	/* Allow only one instance. */
404	if (state->dsn_envid != 0) {
405	    msg_warn("%s: message rejected: multiple DSN envelope ID records",
406		     state->queue_id);
407	    state->errs |= CLEANUP_STAT_BAD;
408	    return;
409	}
410	if (!allprint(mapped_buf)) {
411	    msg_warn("%s: message rejected: bad DSN envelope ID record",
412		     state->queue_id);
413	    state->errs |= CLEANUP_STAT_BAD;
414	    return;
415	}
416	state->dsn_envid = mystrdup(mapped_buf);
417	cleanup_out(state, type, buf, len);
418	return;
419    }
420    if (mapped_type == REC_TYPE_DSN_RET) {
421	/* Allow only one instance. */
422	if (state->dsn_ret != 0) {
423	    msg_warn("%s: message rejected: multiple DSN RET records",
424		     state->queue_id);
425	    state->errs |= CLEANUP_STAT_BAD;
426	    return;
427	}
428	if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
429	    || DSN_RET_OK(junk) == 0) {
430	    msg_warn("%s: message rejected: bad DSN RET record <%.200s>",
431		     state->queue_id, buf);
432	    state->errs |= CLEANUP_STAT_BAD;
433	    return;
434	}
435	state->dsn_ret = junk;
436	cleanup_out(state, type, buf, len);
437	return;
438    }
439    if (type == REC_TYPE_WARN) {
440	/* First instance wins. */
441	if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0) {
442	    state->flags |= CLEANUP_FLAG_WARN_SEEN;
443	    cleanup_out(state, type, buf, len);
444	}
445	return;
446    }
447    /* XXX Needed for cleanup_bounce(); sanity check usage. */
448    if (type == REC_TYPE_VERP) {
449	if (state->verp_delims == 0) {
450	    if (state->sender == 0 || state->sender[0] == 0) {
451		msg_warn("%s: ignoring VERP request for null sender",
452			 state->queue_id);
453	    } else if (verp_delims_verify(buf) != 0) {
454		msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
455			 state->queue_id, buf);
456	    } else {
457		state->verp_delims = mystrdup(buf);
458		cleanup_out(state, type, buf, len);
459	    }
460	}
461	return;
462    }
463    if (type == REC_TYPE_ATTR) {
464	if (state->attr->used >= var_qattr_count_limit) {
465	    msg_warn("%s: message rejected: attribute count exceeds limit %d",
466		     state->queue_id, var_qattr_count_limit);
467	    state->errs |= CLEANUP_STAT_BAD;
468	    return;
469	}
470	if (strcmp(attr_name, MAIL_ATTR_RWR_CONTEXT) == 0) {
471	    /* Choose header rewriting context. See also cleanup_addr.c. */
472	    if (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)) {
473		state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL;
474	    } else if (STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)) {
475		state->hdr_rewrite_context =
476		    (*var_remote_rwr_domain ? MAIL_ATTR_RWR_REMOTE : 0);
477	    } else {
478		msg_warn("%s: message rejected: bad rewriting context: %.100s",
479			 state->queue_id, attr_value);
480		state->errs |= CLEANUP_STAT_BAD;
481		return;
482	    }
483	}
484	nvtable_update(state->attr, attr_name, attr_value);
485	cleanup_out(state, type, buf, len);
486	return;
487    } else {
488	cleanup_out(state, type, buf, len);
489	return;
490    }
491}
492