1 /*	$NetBSD: postscreen_send.c,v 1.2 2017/02/14 01:16:47 christos Exp $	*/
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 /*	If the 220- greeting was sent, sending 421 would be invalid;
42 /*	instead, the client is redirected to the dummy SMTP engine
43 /*	which sends the 421 reply at the first legitimate opportunity.
44 /* LICENSE
45 /* .ad
46 /* .fi
47 /*	The Secure Mailer license must be distributed with this software.
48 /* AUTHOR(S)
49 /*	Wietse Venema
50 /*	IBM T.J. Watson Research
51 /*	P.O. Box 704
52 /*	Yorktown Heights, NY 10598, USA
53 /*--*/
54 
55 /* System library. */
56 
57 #include <sys_defs.h>
58 #include <string.h>
59 #include <errno.h>
60 
61 /* Utility library. */
62 
63 #include <msg.h>
64 #include <iostuff.h>
65 #include <connect.h>
66 #include <attr.h>
67 #include <vstream.h>
68 
69 /* Global library. */
70 
71 #include <mail_params.h>
72 #include <smtp_reply_footer.h>
73 #include <mail_proto.h>
74 
75 /* Application-specific. */
76 
77 #include <postscreen.h>
78 
79  /*
80   * This program screens all inbound SMTP connections, so it better not waste
81   * time.
82   */
83 #define PSC_SEND_SOCK_CONNECT_TIMEOUT	1
84 #define PSC_SEND_SOCK_NOTIFY_TIMEOUT	100
85 
86 /* psc_send_reply - send reply to remote SMTP client */
87 
88 int     psc_send_reply(PSC_STATE *state, const char *text)
89 {
90     ssize_t start;
91     int     ret;
92 
93     if (msg_verbose)
94 	msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
95 		 state->smtp_client_port, (int) strlen(text) - 2, text);
96 
97     /*
98      * Append the new text to earlier text that could not be sent because the
99      * output was throttled.
100      */
101     start = VSTRING_LEN(state->send_buf);
102     vstring_strcat(state->send_buf, text);
103 
104     /*
105      * For soft_bounce support, we also fix the REJECT logging before the
106      * dummy SMTP engine calls the psc_send_reply() output routine. We do
107      * some double work, but it is for debugging only.
108      */
109     if (var_soft_bounce) {
110 	if (text[0] == '5')
111 	    STR(state->send_buf)[start + 0] = '4';
112 	if (text[4] == '5')
113 	    STR(state->send_buf)[start + 4] = '4';
114     }
115 
116     /*
117      * Append the optional reply footer.
118      */
119     if (*var_psc_rej_footer && (*text == '4' || *text == '5'))
120 	smtp_reply_footer(state->send_buf, start, var_psc_rej_footer,
121 			  STR(psc_expand_filter), psc_expand_lookup,
122 			  (void *) state);
123 
124     /*
125      * Do a best effort sending text, but don't block when the output is
126      * throttled by a hostile peer.
127      */
128     ret = write(vstream_fileno(state->smtp_client_stream),
129 		STR(state->send_buf), LEN(state->send_buf));
130     if (ret > 0)
131 	vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
132     if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
133 	msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
134 		 state->smtp_client_port);
135     return (ret < 0 && errno != EAGAIN);
136 }
137 
138 /* psc_send_socket_close_event - file descriptor has arrived or timeout */
139 
140 static void psc_send_socket_close_event(int event, void *context)
141 {
142     const char *myname = "psc_send_socket_close_event";
143     PSC_STATE *state = (PSC_STATE *) context;
144 
145     if (msg_verbose > 1)
146 	msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
147 		 myname, psc_post_queue_length, psc_check_queue_length,
148 		 event, state->smtp_server_fd, state->smtp_client_addr,
149 		 state->smtp_client_port);
150 
151     /*
152      * The real SMTP server has closed the local IPC channel, or we have
153      * reached the limit of our patience. In the latter case it is still
154      * possible that the real SMTP server will receive the socket so we
155      * should not interfere.
156      */
157     PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
158 			    context);
159     if (event == EVENT_TIME)
160 	msg_warn("timeout sending connection to service %s",
161 		 psc_smtpd_service_name);
162     psc_free_session_state(state);
163 }
164 
165 /* psc_send_socket - send socket to real SMTP server process */
166 
167 void    psc_send_socket(PSC_STATE *state)
168 {
169     const char *myname = "psc_send_socket";
170     int     server_fd;
171     int     pass_err;
172     VSTREAM *fp;
173 
174     if (msg_verbose > 1)
175 	msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
176 		 myname, psc_post_queue_length, psc_check_queue_length,
177 		 vstream_fileno(state->smtp_client_stream),
178 		 state->smtp_client_addr, state->smtp_client_port);
179 
180     /*
181      * Connect to the real SMTP service over a local IPC channel, send the
182      * file descriptor, and close the file descriptor to save resources.
183      * Experience has shown that some systems will discard information when
184      * we close a channel immediately after writing. Thus, we waste resources
185      * waiting for the remote side to close the local IPC channel first. The
186      * good side of waiting is that we learn when the real SMTP server is
187      * falling behind.
188      *
189      * This is where we would forward the connection to an SMTP server that
190      * provides an appropriate level of service for this client class. For
191      * example, a server that is more forgiving, or one that is more
192      * suspicious. Alternatively, we could send attributes along with the
193      * socket with client reputation information, making everything even more
194      * Postfix-specific.
195      */
196     if ((server_fd =
197 	 LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
198 		       PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
199 	msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
200 	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
201 	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
202 	} else {
203 	    PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
204 	    psc_free_session_state(state);
205 	}
206 	return;
207     }
208     /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
209     fp = vstream_fdopen(server_fd, O_RDWR);
210     pass_err =
211 	(LOCAL_SEND_FD(server_fd,
212 		       vstream_fileno(state->smtp_client_stream)) < 0
213 	 || (attr_print(fp, ATTR_FLAG_NONE,
214 	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
215 	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
216 	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
217 	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
218 			ATTR_TYPE_END) || vstream_fflush(fp)));
219     /* XXX Note: no read between attr_print() and vstream_fdclose(). */
220     (void) vstream_fdclose(fp);
221     if (pass_err != 0) {
222 	msg_warn("cannot pass connection to service %s: %m",
223 		 psc_smtpd_service_name);
224 	(void) close(server_fd);
225 	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
226 	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
227 	} else {
228 	    PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
229 	    psc_free_session_state(state);
230 	}
231 	return;
232     } else {
233 
234 	/*
235 	 * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
236 	 * where smtp-source sometimes sees the connection being closed after
237 	 * it has already received the real SMTP server's 220 greeting!
238 	 */
239 #if 0
240 	PSC_DEL_CLIENT_STATE(state);
241 #endif
242 	PSC_ADD_SERVER_STATE(state, server_fd);
243 	PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
244 			       (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
245 	return;
246     }
247 }
248