forked from acl-dev/acl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtls_bio_ops.c
350 lines (330 loc) · 11.2 KB
/
tls_bio_ops.c
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/*++
* NAME
* tls_bio_ops 3
* SUMMARY
* TLS network BIO management
* SYNOPSIS
* #include <tls.h>
* #include <tls_private.h>
*
* int tls_bio_connect(fd, timeout, context)
* int fd;
* int timeout;
* TLS_SESS_STATE *context;
*
* int tls_bio_accept(fd, timeout, context)
* int fd;
* int timeout;
* TLS_SESS_STATE *context;
*
* int tls_bio_shutdown(fd, timeout, context)
* int fd;
* int timeout;
* TLS_SESS_STATE *context;
*
* int tls_bio_read(fd, buf, len, timeout, context)
* int fd;
* void *buf;
* int len;
* int timeout;
* TLS_SESS_STATE *context;
*
* int tls_bio_write(fd, buf, len, timeout, context)
* int fd;
* void *buf;
* int len;
* int timeout;
* TLS_SESS_STATE *context;
* DESCRIPTION
* This layer synchronizes the TLS network buffers with the network
* while performing TLS handshake or input/output operations.
*
* When the TLS layer is active, it converts plain-text
* data from Postfix into encrypted network data and vice versa.
* However, to handle network timeout conditions, Postfix
* needs to maintain control over network input/output. This
* rules out the usual approach of placing the TLS layer
* between the application and the network socket.
*
* As shown below, Postfix reads/writes plain-text data from/to
* the TLS layer. The TLS layer informs Postfix when it needs
* to read/write encrypted data from/to the network; Postfix
* then reads/writes encrypted data from/to the TLS layer and
* takes care of the network socket I/O.
*
* The TLS layer to network interface is realized with a BIO pair:
*
* Postfix | TLS layer
* |
* smtp/smtpd |
* /\ || |
* || \/ |
* vstream read/write <===> TLS read/write/etc
* | /\ ||
* | || \/
* | BIO pair (internal_bio)
* | BIO pair (network_bio)
* | /\ ||
* | || \/
* socket read/write <===> BIO read/write
* /\ || |
* || \/ |
* network |
*
* The Postfix VSTREAM read/write operations invoke the SSL
* read/write operations to send and retrieve plain-text data. Inside
* the TLS layer the data are converted to/from TLS protocol.
*
* Whenever an SSL operation reports success, or whenever it
* indicates that network input/output needs to happen, Postfix
* uses the BIO read/write routines to synchronize the
* network_bio buffer with the network. Writing data to the
* network has precedence over reading from the network. This
* is necessary to avoid deadlock.
*
* The BIO pair buffer size is set to 8192 bytes. This is much
* larger than the typical Path MTU, and avoids sending tiny TCP
* segments. It is also larger than the default VSTREAM_BUFSIZE
* (4096, see vstream.h), so that large write operations can
* be handled within one request. The internal buffer in the
* network/network_bio handling layer is set to the same
* value, since this seems to be reasonable. The code is
* however able to handle arbitrary values smaller or larger
* than the buffer size in the BIO pair.
*
* tls_bio_connect() performs the SSL_connect() operation while
* synchronizing the network_bio buffer with the network.
*
* tls_bio_accept() performs the SSL_accept() operation while
* synchronizing the network_bio buffer with the network.
*
* tls_bio_shutdown() performs the SSL_shutdown() operation while
* synchronizing the network_bio buffer with the network.
*
* tls_bio_read() performs the SSL_read() operation while
* synchronizing the network_bio buffer with the network.
*
* tls_bio_write() performs the SSL_write() operation while
* synchronizing the network_bio buffer with the network.
*
* Arguments:
* .IP fd
* Network socket.
* .IP buf
* Read/write buffer.
* .IP len
* Read/write request size.
* .IP timeout
* Read/write timeout.
* .IP TLScontext
* TLS session state.
* DIAGNOSTICS
* The result value is -1 in case of a network read/write
* error, otherwise it is the result value of the TLS operation.
* LICENSE
* .ad
* .fi
* This software is free. You can do with it whatever you want.
* The original author kindly requests that you acknowledge
* the use of his software.
* AUTHOR(S)
* Originally written by:
* Lutz Jaenicke
* BTU Cottbus
* Allgemeine Elektrotechnik
* Universitaetsplatz 3-4
* D-03044 Cottbus, Germany
*
* Updated by:
* Wietse Venema
* IBM T.J. Watson Research
* P.O. Box 704
* Yorktown Heights, NY 10598, USA
*
* Victor Duchovni
* Morgan Stanley
*--*/
/* System library. */
#include "StdAfx.h"
#ifdef USE_TLS
/* TLS library. */
#include "tls.h"
#include "tls_private.h"
/* Application-specific. */
#define NETLAYER_BUFFERSIZE 8192
/* network_biopair_interop - synchronize network with BIO pair */
static int network_biopair_interop(ACL_SOCKET fd, int timeout, BIO *network_bio)
{
const char *myname = "network_biopair_interop";
int want_write;
int num_write;
int write_pos;
int from_bio;
int want_read;
int num_read;
int to_bio;
char buffer[NETLAYER_BUFFERSIZE];
/*
* To avoid deadlock, write all pending data to the network before
* attempting to read from the network.
*/
while ((want_write = (int) BIO_ctrl_pending(network_bio)) > 0) {
if (want_write > (int) sizeof(buffer))
want_write = (int) sizeof(buffer);
from_bio = BIO_read(network_bio, buffer, want_write);
/*
* Write the complete buffer contents to the network.
*/
for (write_pos = 0; write_pos < from_bio; /* see below */ ) {
if (timeout > 0 && acl_write_wait(fd, timeout) < 0)
return (-1);
num_write = acl_socket_write(fd, buffer + write_pos, from_bio - write_pos, 0, 0, 0);
if (num_write <= 0) {
if ((num_write < 0) && (timeout > 0) && (errno == ACL_EAGAIN || errno == ACL_EINTR)) {
acl_msg_warn("%s: write() returns EAGAIN on a writable file descriptor!", myname);
acl_msg_warn("%s: pausing to avoid going into a tight select/write loop!", myname);
sleep(1);
} else {
acl_msg_warn("%s: error writing %d bytes to the network: %s",
myname, from_bio - write_pos, acl_last_serror());
return (-1);
}
} else {
write_pos += num_write;
}
}
}
/*
* Read data from the network into the BIO pair.
*/
while ((want_read = (int) BIO_ctrl_get_read_request(network_bio)) > 0) {
if (want_read > (int) sizeof(buffer))
want_read = (int) sizeof(buffer);
if (timeout > 0 && acl_read_wait(fd, timeout) < 0)
return (-1);
num_read = acl_socket_read(fd, buffer, want_read, 0, 0, 0);
if (num_read == 0)
/* FIX 200412 Cannot return a zero read count. */
return (-1);
if (num_read < 0) {
if ((num_read < 0) && (timeout > 0) && (errno == ACL_EAGAIN || errno == ACL_EINTR)) {
acl_msg_warn("%s: read() returns EAGAIN on a readable file descriptor!", myname);
acl_msg_warn("%s: pausing to avoid going into a tight select/write loop!", myname);
sleep(1);
} else {
acl_msg_warn("%s: error reading %d bytes from the network: %s",
myname, want_read, acl_last_serror());
return (-1);
}
} else {
to_bio = BIO_write(network_bio, buffer, num_read);
if (to_bio != num_read)
acl_msg_panic("%s: BIO_write error: to_bio != num_read", myname);
}
}
return (0);
}
/* tls_bio - perform SSL input/output operation with extreme prejudice */
int tls_bio(ACL_SOCKET fd, int timeout, TLS_SESS_STATE *TLScontext,
int (*hsfunc) (SSL *),
int (*rfunc) (SSL *, void *, int),
int (*wfunc) (SSL *, const void *, int),
void *buf, int num)
{
const char *myname = "tls_bio";
int status = 0;
int err;
int retval = 0;
int biop_retval;
int done;
/*
* If necessary, retry the SSL handshake or read/write operation after
* handling any pending network I/O.
*/
for (done = 0; done == 0; /* void */ ) {
if (hsfunc) {
#if 1
status = hsfunc(TLScontext->con);
#else
status = SSL_do_handshake(TLScontext->con);
#endif
} else if (rfunc)
status = rfunc(TLScontext->con, buf, num);
else if (wfunc)
status = wfunc(TLScontext->con, buf, num);
else
acl_msg_panic("%s: nothing to do here", myname);
err = SSL_get_error(TLScontext->con, status);
#if (OPENSSL_VERSION_NUMBER <= 0x0090581fL)
/*
* There is a bug up to and including OpenSSL-0.9.5a: if an error
* occurs while checking the peers certificate due to some
* certificate error (e.g. as happend with a RSA-padding error), the
* error is put onto the error stack. If verification is not
* enforced, this error should be ignored, but the error-queue is not
* cleared, so we can find this error here. The bug has been fixed on
* May 28, 2000.
*
* This bug so far has only manifested as 4800:error:0407006A:rsa
* routines:RSA_padding_check_PKCS1_type_1:block type is not
* 01:rsa_pk1.c:100: 4800:error:04067072:rsa
* routines:RSA_EAY_PUBLIC_DECRYPT:padding check
* failed:rsa_eay.c:396: 4800:error:0D079006:asn1 encoding
* routines:ASN1_verify:bad get asn1 object call:a_verify.c:109: so
* that we specifically test for this error. We print the errors to
* the logfile and automatically clear the error queue. Then we retry
* to get another error code. We cannot do better, since we can only
* retrieve the last entry of the error-queue without actually
* cleaning it on the way.
*
* This workaround is secure, as verify_result is set to "failed"
* anyway.
*/
if (err == SSL_ERROR_SSL) {
if (ERR_peek_error() == 0x0407006AL) {
tls_print_errors();
acl_msg_info("OpenSSL <= 0.9.5a workaround called: certificate errors ignored");
err = SSL_get_error(TLScontext->con, status);
}
}
#endif
/*
* Find out if we must retry the operation and/or if there is pending
* network I/O.
*
* XXX If we're the first to invoke SSL_shutdown(), then the operation
* isn't really complete when the call returns. We could hide that
* anomaly here and repeat the call.
*/
switch (err) {
case SSL_ERROR_NONE: /* success */
retval = status;
done = 1;
/* FALLTHROUGH */
case SSL_ERROR_WANT_WRITE: /* flush/update buffers */
case SSL_ERROR_WANT_READ:
biop_retval = network_biopair_interop(fd, timeout, TLScontext->network_bio);
if (biop_retval < 0)
return (-1); /* network read/write error */
break;
/*
* With tls_timed_read() and tls_timed_write() the caller is the
* VSTREAM library module which is unaware of TLS, so we log the
* TLS error stack here. In a better world, each VSTREAM I/O
* object would provide an error reporting method in addition to
* the timed_read and timed_write methods, so that we would not
* need to have ad-hoc code like this.
*/
case SSL_ERROR_SSL:
if (rfunc || wfunc)
tls_print_errors();
/* FALLTHROUGH */
default:
retval = status;
done = 1;
break;
}
}
return (retval);
}
#endif