1 /* $NetBSD: postscreen_send.c,v 1.3 2020/03/18 19:05:19 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 /* void pcs_send_pre_jail_init(void)
12 /*
13 /* int psc_send_reply(state, text)
14 /* PSC_STATE *state;
15 /* const char *text;
16 /*
17 /* int PSC_SEND_REPLY(state, text)
18 /* PSC_STATE *state;
19 /* const char *text;
20 /*
21 /* void psc_send_socket(state)
22 /* PSC_STATE *state;
23 /* DESCRIPTION
24 /* pcs_send_pre_jail_init() performs one-time initialization.
25 /*
26 /* psc_send_reply() sends the specified text to the specified
27 /* remote SMTP client. In case of an immediate error, it logs
28 /* a warning (except EPIPE) with the client address and port,
29 /* and returns a non-zero result (all errors including EPIPE).
30 /*
31 /* psc_send_reply() does a best effort to send the reply, but
32 /* it won't block when the output is throttled by a hostile
33 /* peer.
34 /*
35 /* PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
36 /* It will eventually be replaced by its expansion.
37 /*
38 /* psc_send_socket() sends the specified socket to the real
39 /* Postfix SMTP server. The socket is delivered in the background.
40 /* This function must be called after all other session-related
41 /* work is finished including postscreen cache updates.
42 /*
43 /* In case of an immediate error, psc_send_socket() sends a 421
44 /* reply to the remote SMTP client and closes the connection.
45 /* If the 220- greeting was sent, sending 421 would be invalid;
46 /* instead, the client is redirected to the dummy SMTP engine
47 /* which sends the 421 reply at the first legitimate opportunity.
48 /* LICENSE
49 /* .ad
50 /* .fi
51 /* The Secure Mailer license must be distributed with this software.
52 /* AUTHOR(S)
53 /* Wietse Venema
54 /* IBM T.J. Watson Research
55 /* P.O. Box 704
56 /* Yorktown Heights, NY 10598, USA
57 /*
58 /* Wietse Venema
59 /* Google, Inc.
60 /* 111 8th Avenue
61 /* New York, NY 10011, USA
62 /*--*/
63
64 /* System library. */
65
66 #include <sys_defs.h>
67 #include <string.h>
68 #include <errno.h>
69
70 /* Utility library. */
71
72 #include <msg.h>
73 #include <iostuff.h>
74 #include <connect.h>
75 #include <attr.h>
76 #include <vstream.h>
77
78 /* Global library. */
79
80 #include <mail_params.h>
81 #include <smtp_reply_footer.h>
82 #include <mail_proto.h>
83 #include <maps.h>
84
85 /* Application-specific. */
86
87 #include <postscreen.h>
88
89 static MAPS *psc_rej_ftr_maps;
90
91 /*
92 * This program screens all inbound SMTP connections, so it better not waste
93 * time.
94 */
95 #define PSC_SEND_SOCK_CONNECT_TIMEOUT 1
96 #define PSC_SEND_SOCK_NOTIFY_TIMEOUT 100
97
98 /* pcs_send_pre_jail_init - initialize */
99
pcs_send_pre_jail_init(void)100 void pcs_send_pre_jail_init(void)
101 {
102 static int init_count = 0;
103
104 if (init_count++ != 0)
105 msg_panic("pcs_send_pre_jail_init: multiple calls");
106
107 /*
108 * SMTP server reject footer.
109 */
110 if (*var_psc_rej_ftr_maps)
111 psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
112 var_psc_rej_ftr_maps,
113 DICT_FLAG_LOCK);
114 }
115
116 /* psc_get_footer - find that footer */
117
psc_get_footer(const char * text,ssize_t text_len)118 static const char *psc_get_footer(const char *text, ssize_t text_len)
119 {
120 static VSTRING *footer_buf = 0;
121
122 if (footer_buf == 0)
123 footer_buf = vstring_alloc(100);
124 /* Strip the \r\n for consistency with smtpd. */
125 vstring_strncpy(footer_buf, text, text_len);
126 return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
127 }
128
129 /* psc_send_reply - send reply to remote SMTP client */
130
psc_send_reply(PSC_STATE * state,const char * text)131 int psc_send_reply(PSC_STATE *state, const char *text)
132 {
133 ssize_t start;
134 int ret;
135 const char *footer;
136 ssize_t text_len = strlen(text) - 2;
137
138 if (msg_verbose)
139 msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
140 state->smtp_client_port, (int) text_len, text);
141
142 /*
143 * Append the new text to earlier text that could not be sent because the
144 * output was throttled.
145 */
146 start = VSTRING_LEN(state->send_buf);
147 vstring_strcat(state->send_buf, text);
148
149 /*
150 * For soft_bounce support, we also fix the REJECT logging before the
151 * dummy SMTP engine calls the psc_send_reply() output routine. We do
152 * some double work, but it is for debugging only.
153 */
154 if (var_soft_bounce) {
155 if (text[0] == '5')
156 STR(state->send_buf)[start + 0] = '4';
157 if (text[4] == '5')
158 STR(state->send_buf)[start + 4] = '4';
159 }
160
161 /*
162 * Append the optional reply footer.
163 */
164 if ((*text == '4' || *text == '5')
165 && ((psc_rej_ftr_maps != 0
166 && (footer = psc_get_footer(text, text_len)) != 0)
167 || *(footer = var_psc_rej_footer) != 0))
168 smtp_reply_footer(state->send_buf, start, footer,
169 STR(psc_expand_filter), psc_expand_lookup,
170 (void *) state);
171
172 /*
173 * Do a best effort sending text, but don't block when the output is
174 * throttled by a hostile peer.
175 */
176 ret = write(vstream_fileno(state->smtp_client_stream),
177 STR(state->send_buf), LEN(state->send_buf));
178 if (ret > 0)
179 vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
180 if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
181 msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
182 state->smtp_client_port);
183 return (ret < 0 && errno != EAGAIN);
184 }
185
186 /* psc_send_socket_close_event - file descriptor has arrived or timeout */
187
psc_send_socket_close_event(int event,void * context)188 static void psc_send_socket_close_event(int event, void *context)
189 {
190 const char *myname = "psc_send_socket_close_event";
191 PSC_STATE *state = (PSC_STATE *) context;
192
193 if (msg_verbose > 1)
194 msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
195 myname, psc_post_queue_length, psc_check_queue_length,
196 event, state->smtp_server_fd, state->smtp_client_addr,
197 state->smtp_client_port);
198
199 /*
200 * The real SMTP server has closed the local IPC channel, or we have
201 * reached the limit of our patience. In the latter case it is still
202 * possible that the real SMTP server will receive the socket so we
203 * should not interfere.
204 */
205 PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
206 context);
207 if (event == EVENT_TIME)
208 msg_warn("timeout sending connection to service %s",
209 psc_smtpd_service_name);
210 psc_free_session_state(state);
211 }
212
213 /* psc_send_socket - send socket to real SMTP server process */
214
psc_send_socket(PSC_STATE * state)215 void psc_send_socket(PSC_STATE *state)
216 {
217 const char *myname = "psc_send_socket";
218 int server_fd;
219 int pass_err;
220 VSTREAM *fp;
221
222 if (msg_verbose > 1)
223 msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
224 myname, psc_post_queue_length, psc_check_queue_length,
225 vstream_fileno(state->smtp_client_stream),
226 state->smtp_client_addr, state->smtp_client_port);
227
228 /*
229 * Connect to the real SMTP service over a local IPC channel, send the
230 * file descriptor, and close the file descriptor to save resources.
231 * Experience has shown that some systems will discard information when
232 * we close a channel immediately after writing. Thus, we waste resources
233 * waiting for the remote side to close the local IPC channel first. The
234 * good side of waiting is that we learn when the real SMTP server is
235 * falling behind.
236 *
237 * This is where we would forward the connection to an SMTP server that
238 * provides an appropriate level of service for this client class. For
239 * example, a server that is more forgiving, or one that is more
240 * suspicious. Alternatively, we could send attributes along with the
241 * socket with client reputation information, making everything even more
242 * Postfix-specific.
243 */
244 if ((server_fd =
245 LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
246 PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
247 msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
248 if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
249 PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
250 } else {
251 PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
252 psc_free_session_state(state);
253 }
254 return;
255 }
256 /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
257 fp = vstream_fdopen(server_fd, O_RDWR);
258 pass_err =
259 (LOCAL_SEND_FD(server_fd,
260 vstream_fileno(state->smtp_client_stream)) < 0
261 || (attr_print(fp, ATTR_FLAG_NONE,
262 SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
263 SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
264 SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
265 SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
266 ATTR_TYPE_END) || vstream_fflush(fp)));
267 /* XXX Note: no read between attr_print() and vstream_fdclose(). */
268 (void) vstream_fdclose(fp);
269 if (pass_err != 0) {
270 msg_warn("cannot pass connection to service %s: %m",
271 psc_smtpd_service_name);
272 (void) close(server_fd);
273 if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
274 PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
275 } else {
276 PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
277 psc_free_session_state(state);
278 }
279 return;
280 } else {
281
282 /*
283 * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
284 * where smtp-source sometimes sees the connection being closed after
285 * it has already received the real SMTP server's 220 greeting!
286 */
287 #if 0
288 PSC_DEL_CLIENT_STATE(state);
289 #endif
290 PSC_ADD_SERVER_STATE(state, server_fd);
291 PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
292 (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
293 return;
294 }
295 }
296