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