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