Home | History | Annotate | Line # | Download | only in cleanup
      1 /*	$NetBSD: cleanup_out.c,v 1.4 2025/02/25 19:15:44 christos Exp $	*/
      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 /*	Wietse Venema
     71 /*	Google, Inc.
     72 /*	111 8th Avenue
     73 /*	New York, NY 10011, USA
     74 /*
     75 /*	Wietse Venema
     76 /*	porcupine.org
     77 /*--*/
     78 
     79 /* System library. */
     80 
     81 #include <sys_defs.h>
     82 #include <errno.h>
     83 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
     84 #include <stdarg.h>
     85 #include <string.h>
     86 
     87 /* Utility library. */
     88 
     89 #include <msg.h>
     90 #include <vstring.h>
     91 #include <vstream.h>
     92 #include <split_at.h>
     93 #include <stringops.h>
     94 
     95 /* Global library. */
     96 
     97 #include <record.h>
     98 #include <rec_type.h>
     99 #include <cleanup_user.h>
    100 #include <mail_params.h>
    101 #include <lex_822.h>
    102 #include <smtputf8.h>
    103 
    104 /* Application-specific. */
    105 
    106 #include "cleanup.h"
    107 
    108 #define STR	vstring_str
    109 
    110 /* cleanup_out - output one single record */
    111 
    112 void    cleanup_out(CLEANUP_STATE *state, int type, const char *string, ssize_t len)
    113 {
    114     int     err = 0;
    115 
    116     /*
    117      * Long message header lines have to be read and written as multiple
    118      * records. Other header/body content, and envelope data, is copied one
    119      * record at a time. Be sure to not skip a zero-length request.
    120      *
    121      * XXX We don't know if we're writing a message header or not, but that is
    122      * not a problem. A REC_TYPE_NORM or REC_TYPE_CONT record can always be
    123      * chopped up into an equivalent set of REC_TYPE_CONT plus REC_TYPE_NORM
    124      * records.
    125      */
    126     if (CLEANUP_OUT_OK(state) == 0)
    127 	return;
    128 
    129 #define TEXT_RECORD(t)	((t) == REC_TYPE_NORM || (t) == REC_TYPE_CONT)
    130 
    131     if (msg_verbose && !TEXT_RECORD(type))
    132 	msg_info("cleanup_out: %c %.*s", type, (int) len, string);
    133 
    134     if (var_line_limit <= 0)
    135 	msg_panic("cleanup_out: bad line length limit: %d", var_line_limit);
    136     do {
    137 	if (len > var_line_limit && TEXT_RECORD(type)) {
    138 	    err = rec_put(state->dst, REC_TYPE_CONT, string, var_line_limit);
    139 	    string += var_line_limit;
    140 	    len -= var_line_limit;
    141 	} else {
    142 	    err = rec_put(state->dst, type, string, len);
    143 	    break;
    144 	}
    145     } while (len > 0 && err >= 0);
    146 
    147     if (err < 0) {
    148 	if (errno == EFBIG) {
    149 	    msg_warn("%s: queue file size limit exceeded",
    150 		     state->queue_id);
    151 	    state->errs |= CLEANUP_STAT_SIZE;
    152 	} else {
    153 	    msg_warn("%s: write queue file: %m", state->queue_id);
    154 	    state->errs |= CLEANUP_STAT_WRITE;
    155 	}
    156     }
    157 }
    158 
    159 /* cleanup_out_string - output string to one single record */
    160 
    161 void    cleanup_out_string(CLEANUP_STATE *state, int type, const char *string)
    162 {
    163     cleanup_out(state, type, string, strlen(string));
    164 }
    165 
    166 /* cleanup_out_format - output one formatted record */
    167 
    168 void    cleanup_out_format(CLEANUP_STATE *state, int type, const char *fmt,...)
    169 {
    170     static VSTRING *vp;
    171     va_list ap;
    172 
    173     if (vp == 0)
    174 	vp = vstring_alloc(100);
    175     va_start(ap, fmt);
    176     vstring_vsprintf(vp, fmt, ap);
    177     va_end(ap);
    178     CLEANUP_OUT_BUF(state, type, vp);
    179 }
    180 
    181 /* cleanup_out_header - output one multi-line header as a bunch of records */
    182 
    183 void    cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf)
    184 {
    185     char   *start = vstring_str(header_buf);
    186     char   *line;
    187     char   *next_line;
    188     ssize_t line_len;
    189 
    190     /*
    191      * Fix 20140711: Auto-detect the presence of a non-ASCII header.
    192      */
    193     if (var_smtputf8_enable && *STR(header_buf) && !allascii(STR(header_buf))) {
    194 	state->sendopts |= SMTPUTF8_FLAG_HEADER;
    195 	/* Fix 20140713: request SMTPUTF8 support selectively. */
    196 	if (state->flags & CLEANUP_FLAG_AUTOUTF8)
    197 	    state->sendopts |= SMTPUTF8_FLAG_REQUESTED;
    198     }
    199 
    200     /*
    201      * Prepend a tab to continued header lines that went through the address
    202      * rewriting machinery. See cleanup_fold_header(state) below for the form
    203      * of such header lines. NB: This code destroys the header. We could try
    204      * to avoid clobbering it, but we're not going to use the data any
    205      * further.
    206      *
    207      * XXX We prefer to truncate a header at the last line boundary before the
    208      * header size limit. If this would undershoot the limit by more than
    209      * 10%, we truncate between line boundaries to avoid losing too much
    210      * text. This "unkind cut" may result in syntax errors and may trigger
    211      * warnings from down-stream MTAs.
    212      *
    213      * If Milter is enabled, pad a short header record with a dummy record so
    214      * that a header record can safely be overwritten by a pointer record.
    215      * This simplifies header modification enormously.
    216      */
    217     for (line = start; line; line = next_line) {
    218 	next_line = split_at(line, '\n');
    219 	line_len = next_line ? next_line - 1 - line : strlen(line);
    220 	if (line + line_len > start + var_header_limit) {
    221 	    if (line - start > 0.9 * var_header_limit)	/* nice cut */
    222 		break;
    223 	    start[var_header_limit] = 0;	/* unkind cut */
    224 	    next_line = 0;
    225 	}
    226 	if (line == start) {
    227 	    cleanup_out_string(state, REC_TYPE_NORM, line);
    228 	    if ((state->milters || cleanup_milters)
    229 		&& line_len < REC_TYPE_PTR_PAYL_SIZE)
    230 		rec_pad(state->dst, REC_TYPE_DTXT,
    231 			REC_TYPE_PTR_PAYL_SIZE - line_len);
    232 	} else if (IS_SPACE_TAB(*line)) {
    233 	    cleanup_out_string(state, REC_TYPE_NORM, line);
    234 	} else {
    235 	    cleanup_out_format(state, REC_TYPE_NORM, "\t%s", line);
    236 	}
    237     }
    238 }
    239