1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	cleanup_out 3
6/* SUMMARY
7/*	record output support
8/* SYNOPSIS
9/*	#include "cleanup.h"
10/*
11/*	int	CLEANUP_OUT_OK(state)
12/*	CLEANUP_STATE *state;
13/*
14/*	void	cleanup_out(state, type, data, len)
15/*	CLEANUP_STATE *state;
16/*	int	type;
17/*	const char *data;
18/*	ssize_t	len;
19/*
20/*	void	cleanup_out_string(state, type, str)
21/*	CLEANUP_STATE *state;
22/*	int	type;
23/*	const char *str;
24/*
25/*	void	CLEANUP_OUT_BUF(state, type, buf)
26/*	CLEANUP_STATE *state;
27/*	int	type;
28/*	VSTRING	*buf;
29/*
30/*	void	cleanup_out_format(state, type, format, ...)
31/*	CLEANUP_STATE *state;
32/*	int	type;
33/*	const char *format;
34/*
35/*	void	cleanup_out_header(state, buf)
36/*	CLEANUP_STATE *state;
37/*	VSTRING	*buf;
38/* DESCRIPTION
39/*	This module writes records to the output stream.
40/*
41/*	CLEANUP_OUT_OK() is a macro that evaluates to non-zero
42/*	as long as it makes sense to produce output. All output
43/*	routines below check for this condition.
44/*
45/*	cleanup_out() is the main record output routine. It writes
46/*	one record of the specified type, with the specified data
47/*	and length to the output stream.
48/*
49/*	cleanup_out_string() outputs one string as a record.
50/*
51/*	CLEANUP_OUT_BUF() is an unsafe macro that outputs
52/*	one string buffer as a record.
53/*
54/*	cleanup_out_format() formats its arguments and writes
55/*	the result as a record.
56/*
57/*	cleanup_out_header() outputs a multi-line header as records
58/*	of the specified type. The input is expected to be newline
59/*	separated (not newline terminated), and is modified.
60/* LICENSE
61/* .ad
62/* .fi
63/*	The Secure Mailer license must be distributed with this software.
64/* AUTHOR(S)
65/*	Wietse Venema
66/*	IBM T.J. Watson Research
67/*	P.O. Box 704
68/*	Yorktown Heights, NY 10598, USA
69/*--*/
70
71/* System library. */
72
73#include <sys_defs.h>
74#include <errno.h>
75#include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
76#include <stdarg.h>
77#include <string.h>
78
79/* Utility library. */
80
81#include <msg.h>
82#include <vstring.h>
83#include <vstream.h>
84#include <split_at.h>
85
86/* Global library. */
87
88#include <record.h>
89#include <rec_type.h>
90#include <cleanup_user.h>
91#include <mail_params.h>
92#include <lex_822.h>
93
94/* Application-specific. */
95
96#include "cleanup.h"
97
98/* cleanup_out - output one single record */
99
100void    cleanup_out(CLEANUP_STATE *state, int type, const char *string, ssize_t len)
101{
102    int     err = 0;
103
104    /*
105     * Long message header lines have to be read and written as multiple
106     * records. Other header/body content, and envelope data, is copied one
107     * record at a time. Be sure to not skip a zero-length request.
108     *
109     * XXX We don't know if we're writing a message header or not, but that is
110     * not a problem. A REC_TYPE_NORM or REC_TYPE_CONT record can always be
111     * chopped up into an equivalent set of REC_TYPE_CONT plus REC_TYPE_NORM
112     * records.
113     */
114    if (CLEANUP_OUT_OK(state) == 0)
115	return;
116
117#define TEXT_RECORD(t)	((t) == REC_TYPE_NORM || (t) == REC_TYPE_CONT)
118
119    if (var_line_limit <= 0)
120	msg_panic("cleanup_out: bad line length limit: %d", var_line_limit);
121    do {
122	if (len > var_line_limit && TEXT_RECORD(type)) {
123	    err = rec_put(state->dst, REC_TYPE_CONT, string, var_line_limit);
124	    string += var_line_limit;
125	    len -= var_line_limit;
126	} else {
127	    err = rec_put(state->dst, type, string, len);
128	    break;
129	}
130    } while (len > 0 && err >= 0);
131
132    if (err < 0) {
133	if (errno == EFBIG) {
134	    msg_warn("%s: queue file size limit exceeded",
135		     state->queue_id);
136	    state->errs |= CLEANUP_STAT_SIZE;
137	} else {
138	    msg_warn("%s: write queue file: %m", state->queue_id);
139	    state->errs |= CLEANUP_STAT_WRITE;
140	}
141    }
142}
143
144/* cleanup_out_string - output string to one single record */
145
146void    cleanup_out_string(CLEANUP_STATE *state, int type, const char *string)
147{
148    cleanup_out(state, type, string, strlen(string));
149}
150
151/* cleanup_out_format - output one formatted record */
152
153void    cleanup_out_format(CLEANUP_STATE *state, int type, const char *fmt,...)
154{
155    static VSTRING *vp;
156    va_list ap;
157
158    if (vp == 0)
159	vp = vstring_alloc(100);
160    va_start(ap, fmt);
161    vstring_vsprintf(vp, fmt, ap);
162    va_end(ap);
163    CLEANUP_OUT_BUF(state, type, vp);
164}
165
166/* cleanup_out_header - output one multi-line header as a bunch of records */
167
168void    cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf)
169{
170    char   *start = vstring_str(header_buf);
171    char   *line;
172    char   *next_line;
173    ssize_t line_len;
174
175    /*
176     * Prepend a tab to continued header lines that went through the address
177     * rewriting machinery. See cleanup_fold_header(state) below for the form
178     * of such header lines. NB: This code destroys the header. We could try
179     * to avoid clobbering it, but we're not going to use the data any
180     * further.
181     *
182     * XXX We prefer to truncate a header at the last line boundary before the
183     * header size limit. If this would undershoot the limit by more than
184     * 10%, we truncate between line boundaries to avoid losing too much
185     * text. This "unkind cut" may result in syntax errors and may trigger
186     * warnings from down-stream MTAs.
187     *
188     * If Milter is enabled, pad a short header record with a dummy record so
189     * that a header record can safely be overwritten by a pointer record.
190     * This simplifies header modification enormously.
191     */
192    for (line = start; line; line = next_line) {
193	next_line = split_at(line, '\n');
194	line_len = next_line ? next_line - 1 - line : strlen(line);
195	if (line + line_len > start + var_header_limit) {
196	    if (line - start > 0.9 * var_header_limit)	/* nice cut */
197		break;
198	    start[var_header_limit] = 0;	/* unkind cut */
199	    next_line = 0;
200	}
201	if (line == start) {
202	    cleanup_out_string(state, REC_TYPE_NORM, line);
203	    if ((state->milters || cleanup_milters)
204		&& line_len < REC_TYPE_PTR_PAYL_SIZE)
205		rec_pad(state->dst, REC_TYPE_DTXT,
206			REC_TYPE_PTR_PAYL_SIZE - line_len);
207	} else if (IS_SPACE_TAB(*line)) {
208	    cleanup_out_string(state, REC_TYPE_NORM, line);
209	} else {
210	    cleanup_out_format(state, REC_TYPE_NORM, "\t%s", line);
211	}
212    }
213}
214