1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	postscreen_send 3
6/* SUMMARY
7/*	postscreen low-level output
8/* SYNOPSIS
9/*	#include <postscreen.h>
10/*
11/*	int	psc_send_reply(state, text)
12/*	PSC_STATE *state;
13/*	const char *text;
14/*
15/*	int	PSC_SEND_REPLY(state, text)
16/*	PSC_STATE *state;
17/*	const char *text;
18/*
19/*	void	psc_send_socket(state)
20/*	PSC_STATE *state;
21/* DESCRIPTION
22/*	psc_send_reply() sends the specified text to the specified
23/*	remote SMTP client.  In case of an immediate error, it logs
24/*	a warning (except EPIPE) with the client address and port,
25/*	and returns a non-zero result (all errors including EPIPE).
26/*
27/*	psc_send_reply() does a best effort to send the reply, but
28/*	it won't block when the output is throttled by a hostile
29/*	peer.
30/*
31/*	PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
32/*	It will eventually be replaced by its expansion.
33/*
34/*	psc_send_socket() sends the specified socket to the real
35/*	Postfix SMTP server. The socket is delivered in the background.
36/*	This function must be called after all other session-related
37/*	work is finished including postscreen cache updates.
38/*
39/*	In case of an immediate error, psc_send_socket() sends a 421
40/*	reply to the remote SMTP client and closes the connection.
41/* LICENSE
42/* .ad
43/* .fi
44/*	The Secure Mailer license must be distributed with this software.
45/* AUTHOR(S)
46/*	Wietse Venema
47/*	IBM T.J. Watson Research
48/*	P.O. Box 704
49/*	Yorktown Heights, NY 10598, USA
50/*--*/
51
52/* System library. */
53
54#include <sys_defs.h>
55#include <string.h>
56#include <errno.h>
57
58/* Utility library. */
59
60#include <msg.h>
61#include <iostuff.h>
62#include <connect.h>
63
64/* Global library. */
65
66#include <mail_params.h>
67#include <smtp_reply_footer.h>
68
69/* Application-specific. */
70
71#include <postscreen.h>
72
73 /*
74  * This program screens all inbound SMTP connections, so it better not waste
75  * time.
76  */
77#define PSC_SEND_SOCK_CONNECT_TIMEOUT	1
78#define PSC_SEND_SOCK_NOTIFY_TIMEOUT	100
79
80/* psc_send_reply - send reply to remote SMTP client */
81
82int     psc_send_reply(PSC_STATE *state, const char *text)
83{
84    ssize_t start;
85    int     ret;
86
87    if (msg_verbose)
88	msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
89		 state->smtp_client_port, (int) strlen(text) - 2, text);
90
91    /*
92     * Append the new text to earlier text that could not be sent because the
93     * output was throttled.
94     */
95    start = VSTRING_LEN(state->send_buf);
96    vstring_strcat(state->send_buf, text);
97
98    /*
99     * For soft_bounce support, we also fix the REJECT logging before the
100     * dummy SMTP engine calls the psc_send_reply() output routine. We do
101     * some double work, but it is for debugging only.
102     */
103    if (var_soft_bounce) {
104	if (text[0] == '5')
105	    STR(state->send_buf)[start + 0] = '4';
106	if (text[4] == '5')
107	    STR(state->send_buf)[start + 4] = '4';
108    }
109
110    /*
111     * Append the optional reply footer.
112     */
113    if (*var_psc_rej_footer && (*text == '4' || *text == '5'))
114	smtp_reply_footer(state->send_buf, start, var_psc_rej_footer,
115			  STR(psc_expand_filter), psc_expand_lookup,
116			  (char *) state);
117
118    /*
119     * Do a best effort sending text, but don't block when the output is
120     * throttled by a hostile peer.
121     */
122    ret = write(vstream_fileno(state->smtp_client_stream),
123		STR(state->send_buf), LEN(state->send_buf));
124    if (ret > 0)
125	vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
126    if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
127	msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
128		 state->smtp_client_port);
129    return (ret < 0 && errno != EAGAIN);
130}
131
132/* psc_send_socket_close_event - file descriptor has arrived or timeout */
133
134static void psc_send_socket_close_event(int event, char *context)
135{
136    const char *myname = "psc_send_socket_close_event";
137    PSC_STATE *state = (PSC_STATE *) context;
138
139    if (msg_verbose > 1)
140	msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
141		 myname, psc_post_queue_length, psc_check_queue_length,
142		 event, state->smtp_server_fd, state->smtp_client_addr,
143		 state->smtp_client_port);
144
145    /*
146     * The real SMTP server has closed the local IPC channel, or we have
147     * reached the limit of our patience. In the latter case it is still
148     * possible that the real SMTP server will receive the socket so we
149     * should not interfere.
150     */
151    PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
152			    context);
153    if (event == EVENT_TIME)
154	msg_warn("timeout sending connection to service %s",
155		 psc_smtpd_service_name);
156    psc_free_session_state(state);
157}
158
159/* psc_send_socket - send socket to real SMTP server process */
160
161void    psc_send_socket(PSC_STATE *state)
162{
163    const char *myname = "psc_send_socket";
164    int     server_fd;
165
166    if (msg_verbose > 1)
167	msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
168		 myname, psc_post_queue_length, psc_check_queue_length,
169		 vstream_fileno(state->smtp_client_stream),
170		 state->smtp_client_addr, state->smtp_client_port);
171
172    /*
173     * Connect to the real SMTP service over a local IPC channel, send the
174     * file descriptor, and close the file descriptor to save resources.
175     * Experience has shown that some systems will discard information when
176     * we close a channel immediately after writing. Thus, we waste resources
177     * waiting for the remote side to close the local IPC channel first. The
178     * good side of waiting is that we learn when the real SMTP server is
179     * falling behind.
180     *
181     * This is where we would forward the connection to an SMTP server that
182     * provides an appropriate level of service for this client class. For
183     * example, a server that is more forgiving, or one that is more
184     * suspicious. Alternatively, we could send attributes along with the
185     * socket with client reputation information, making everything even more
186     * Postfix-specific.
187     */
188    if ((server_fd =
189	 PASS_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
190		      PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
191	msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
192	/* Best effort: after sending 220-, hang up without sending 421. */
193	if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) == 0)
194	    PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
195	psc_free_session_state(state);
196	return;
197    }
198    PSC_ADD_SERVER_STATE(state, server_fd);
199    if (LOCAL_SEND_FD(state->smtp_server_fd,
200		      vstream_fileno(state->smtp_client_stream)) < 0) {
201	msg_warn("cannot pass connection to service %s: %m",
202		 psc_smtpd_service_name);
203	/* Best effort: after sending 220-, hang up without sending 421. */
204	if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) == 0)
205	    PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
206	psc_free_session_state(state);
207	return;
208    } else {
209
210	/*
211	 * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
212	 * where smtp-source sometimes sees the connection being closed after
213	 * it has already received the real SMTP server's 220 greeting!
214	 */
215#if 0
216	PSC_DEL_CLIENT_STATE(state);
217#endif
218	PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
219			       (char *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
220	return;
221    }
222}
223