summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_send.c
blob: 53714b15dc1b9240d9c72eb9fe7f6a4e53bbfcd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*++
/* NAME
/*	postscreen_send 3
/* SUMMARY
/*	postscreen low-level output
/* SYNOPSIS
/*	#include <postscreen.h>
/*
/*	void	pcs_send_pre_jail_init(void)
/*
/*	int	psc_send_reply(state, text)
/*	PSC_STATE *state;
/*	const char *text;
/*
/*	int	PSC_SEND_REPLY(state, text)
/*	PSC_STATE *state;
/*	const char *text;
/*
/*	void	psc_send_socket(state)
/*	PSC_STATE *state;
/* DESCRIPTION
/*	pcs_send_pre_jail_init() performs one-time initialization.
/*
/*	psc_send_reply() sends the specified text to the specified
/*	remote SMTP client.  In case of an immediate error, it logs
/*	a warning (except EPIPE) with the client address and port,
/*	and returns a non-zero result (all errors including EPIPE).
/*
/*	psc_send_reply() does a best effort to send the reply, but
/*	it won't block when the output is throttled by a hostile
/*	peer.
/*
/*	PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
/*	It will eventually be replaced by its expansion.
/*
/*	psc_send_socket() sends the specified socket to the real
/*	Postfix SMTP server. The socket is delivered in the background.
/*	This function must be called after all other session-related
/*	work is finished including postscreen cache updates.
/*
/*	In case of an immediate error, psc_send_socket() sends a 421
/*	reply to the remote SMTP client and closes the connection.
/*	If the 220- greeting was sent, sending 421 would be invalid;
/*	instead, the client is redirected to the dummy SMTP engine
/*	which sends the 421 reply at the first legitimate opportunity.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <string.h>
#include <errno.h>

/* Utility library. */

#include <msg.h>
#include <iostuff.h>
#include <connect.h>
#include <attr.h>
#include <vstream.h>

/* Global library. */

#include <mail_params.h>
#include <smtp_reply_footer.h>
#include <mail_proto.h>
#include <maps.h>

/* Application-specific. */

#include <postscreen.h>

static MAPS *psc_rej_ftr_maps;

 /*
  * This program screens all inbound SMTP connections, so it better not waste
  * time.
  */
#define PSC_SEND_SOCK_CONNECT_TIMEOUT	1
#define PSC_SEND_SOCK_NOTIFY_TIMEOUT	100

/* pcs_send_pre_jail_init - initialize */

void    pcs_send_pre_jail_init(void)
{
    static int init_count = 0;

    if (init_count++ != 0)
	msg_panic("pcs_send_pre_jail_init: multiple calls");

    /*
     * SMTP server reject footer.
     */
    if (*var_psc_rej_ftr_maps)
	psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
				       var_psc_rej_ftr_maps,
				       DICT_FLAG_LOCK);
}

/* psc_get_footer - find that footer */

static const char *psc_get_footer(const char *text, ssize_t text_len)
{
    static VSTRING *footer_buf = 0;

    if (footer_buf == 0)
	footer_buf = vstring_alloc(100);
    /* Strip the \r\n for consistency with smtpd. */
    vstring_strncpy(footer_buf, text, text_len);
    return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
}

/* psc_send_reply - send reply to remote SMTP client */

int     psc_send_reply(PSC_STATE *state, const char *text)
{
    ssize_t start;
    int     ret;
    const char *footer;
    ssize_t text_len = strlen(text) - 2;

    if (msg_verbose)
	msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
		 state->smtp_client_port, (int) text_len, text);

    /*
     * Append the new text to earlier text that could not be sent because the
     * output was throttled.
     */
    start = VSTRING_LEN(state->send_buf);
    vstring_strcat(state->send_buf, text);

    /*
     * For soft_bounce support, we also fix the REJECT logging before the
     * dummy SMTP engine calls the psc_send_reply() output routine. We do
     * some double work, but it is for debugging only.
     */
    if (var_soft_bounce) {
	if (text[0] == '5')
	    STR(state->send_buf)[start + 0] = '4';
	if (text[4] == '5')
	    STR(state->send_buf)[start + 4] = '4';
    }

    /*
     * Append the optional reply footer.
     */
    if ((*text == '4' || *text == '5')
	&& ((psc_rej_ftr_maps != 0
	     && (footer = psc_get_footer(text, text_len)) != 0)
	    || *(footer = var_psc_rej_footer) != 0))
	smtp_reply_footer(state->send_buf, start, footer,
			  STR(psc_expand_filter), psc_expand_lookup,
			  (void *) state);

    /*
     * Do a best effort sending text, but don't block when the output is
     * throttled by a hostile peer.
     */
    ret = write(vstream_fileno(state->smtp_client_stream),
		STR(state->send_buf), LEN(state->send_buf));
    if (ret > 0)
	vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
    if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
	msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
		 state->smtp_client_port);
    return (ret < 0 && errno != EAGAIN);
}

/* psc_send_socket_close_event - file descriptor has arrived or timeout */

static void psc_send_socket_close_event(int event, void *context)
{
    const char *myname = "psc_send_socket_close_event";
    PSC_STATE *state = (PSC_STATE *) context;

    if (msg_verbose > 1)
	msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
		 myname, psc_post_queue_length, psc_check_queue_length,
		 event, state->smtp_server_fd, state->smtp_client_addr,
		 state->smtp_client_port);

    /*
     * The real SMTP server has closed the local IPC channel, or we have
     * reached the limit of our patience. In the latter case it is still
     * possible that the real SMTP server will receive the socket so we
     * should not interfere.
     */
    PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
			    context);
    if (event == EVENT_TIME)
	msg_warn("timeout sending connection to service %s",
		 psc_smtpd_service_name);
    psc_free_session_state(state);
}

/* psc_send_socket - send socket to real SMTP server process */

void    psc_send_socket(PSC_STATE *state)
{
    const char *myname = "psc_send_socket";
    int     server_fd;
    int     pass_err;
    VSTREAM *fp;

    if (msg_verbose > 1)
	msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
		 myname, psc_post_queue_length, psc_check_queue_length,
		 vstream_fileno(state->smtp_client_stream),
		 state->smtp_client_addr, state->smtp_client_port);

    /*
     * Connect to the real SMTP service over a local IPC channel, send the
     * file descriptor, and close the file descriptor to save resources.
     * Experience has shown that some systems will discard information when
     * we close a channel immediately after writing. Thus, we waste resources
     * waiting for the remote side to close the local IPC channel first. The
     * good side of waiting is that we learn when the real SMTP server is
     * falling behind.
     * 
     * This is where we would forward the connection to an SMTP server that
     * provides an appropriate level of service for this client class. For
     * example, a server that is more forgiving, or one that is more
     * suspicious. Alternatively, we could send attributes along with the
     * socket with client reputation information, making everything even more
     * Postfix-specific.
     */
    if ((server_fd =
	 LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
		       PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
	msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
	} else {
	    PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
	    psc_free_session_state(state);
	}
	return;
    }
    /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
    fp = vstream_fdopen(server_fd, O_RDWR);
    pass_err =
	(LOCAL_SEND_FD(server_fd,
		       vstream_fileno(state->smtp_client_stream)) < 0
	 || (attr_print(fp, ATTR_FLAG_NONE,
	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
			ATTR_TYPE_END) || vstream_fflush(fp)));
    /* XXX Note: no read between attr_print() and vstream_fdclose(). */
    (void) vstream_fdclose(fp);
    if (pass_err != 0) {
	msg_warn("cannot pass connection to service %s: %m",
		 psc_smtpd_service_name);
	(void) close(server_fd);
	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
	} else {
	    PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
	    psc_free_session_state(state);
	}
	return;
    } else {

	/*
	 * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
	 * where smtp-source sometimes sees the connection being closed after
	 * it has already received the real SMTP server's 220 greeting!
	 */
#if 0
	PSC_DEL_CLIENT_STATE(state);
#endif
	PSC_ADD_SERVER_STATE(state, server_fd);
	PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
			       (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
	return;
    }
}