summaryrefslogtreecommitdiffstats
path: root/src/tls/tls_bio_ops.c
blob: 9b661954721563bfa2e252359f6cbfe06ad828fa (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
294
295
296
/*++
/* NAME
/*	tls_bio_ops 3
/* SUMMARY
/*	TLS network basic I/O management
/* SYNOPSIS
/*	#define TLS_INTERNAL
/*	#include <tls.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 module enforces VSTREAM-style timeouts on non-blocking
/*	I/O while performing TLS handshake or input/output operations.
/*
/*	The Postfix VSTREAM read/write routines invoke the
/*	tls_bio_read/write routines to send and receive plain-text
/*	data.  In addition, this module provides tls_bio_connect/accept
/*	routines that trigger the initial TLS handshake.  The
/*	tls_bio_xxx routines invoke the corresponding SSL routines
/*	that translate the requests into TLS protocol messages.
/*
/*	Whenever an SSL operation indicates that network input (or
/*	output) needs to happen, the tls_bio_xxx routines wait for
/*	the network to become readable (or writable) within the
/*	timeout limit, then retry the SSL operation. This works
/*	because the network socket is in non-blocking mode.
/*
/*	tls_bio_connect() performs the SSL_connect() operation.
/*
/*	tls_bio_accept() performs the SSL_accept() operation.
/*
/*	tls_bio_shutdown() performs the SSL_shutdown() operation.
/*
/*	tls_bio_read() performs the SSL_read() operation.
/*
/*	tls_bio_write() performs the SSL_write() operation.
/*
/*	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
/*	A result value > 0 means successful completion.
/*
/*	A result value < 0 means that the requested operation did
/*	not complete due to TLS protocol failure, system call
/*	failure, or for any reason described under "in addition"
/*	below.
/*
/*	A result value of 0 from tls_bio_shutdown() means that the
/*	operation is in progress. A result value of 0 from other
/*	tls_bio_ops(3) operations means that the remote party either
/*	closed the network connection or that it sent a TLS shutdown
/*	request.
/*
/*	Upon return from the tls_bio_ops(3) routines the global
/*	errno value is non-zero when the requested operation did not
/*	complete due to system call failure.
/*
/*	In addition, the result value is set to -1, and the global
/*	errno value is set to ETIMEDOUT, when some network read/write
/*	operation did not complete within the time limit.
/* 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 <sys_defs.h>
#include <sys/time.h>

#ifndef timersub
/* res = a - b */
#define timersub(a, b, res) do { \
	(res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
	(res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
	if ((res)->tv_usec < 0) { \
		(res)->tv_sec--; \
		(res)->tv_usec += 1000000; \
	} \
    } while (0)
#endif

#ifdef USE_TLS

/* Utility library. */

#include <msg.h>
#include <iostuff.h>

/* TLS library. */

#define TLS_INTERNAL
#include <tls.h>

/* tls_bio - perform SSL input/output operation with extreme prejudice */

int     tls_bio(int 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;
    int     err;
    int     enable_deadline;
    struct timeval time_left;		/* amount of time left */
    struct timeval time_deadline;	/* time of deadline */
    struct timeval time_now;		/* time after SSL_mumble() call */

    /*
     * Compensation for interface mis-match: With VSTREAMs, timeout <= 0
     * means wait forever; with the read/write_wait() calls below, we need to
     * specify timeout < 0 instead.
     * 
     * Safety: no time limit means no deadline.
     */
    if (timeout <= 0) {
	timeout = -1;
	enable_deadline = 0;
    }

    /*
     * Deadline management is simpler than with VSTREAMs, because we don't
     * need to decrement a per-stream time limit. We just work within the
     * budget that is available for this tls_bio() call.
     */
    else {
	enable_deadline =
	    vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE);
	if (enable_deadline) {
	    GETTIMEOFDAY(&time_deadline);
	    time_deadline.tv_sec += timeout;
	}
    }

    /*
     * If necessary, retry the SSL handshake or read/write operation after
     * handling any pending network I/O.
     */
    for (;;) {

	/*
	 * Flush the per-thread SSL error queue. Otherwise, errors from other
	 * code that also uses TLS may confuse SSL_get_error(3).
	 */
	ERR_clear_error();

	if (hsfunc)
	    status = hsfunc(TLScontext->con);
	else if (rfunc)
	    status = rfunc(TLScontext->con, buf, num);
	else if (wfunc)
	    status = wfunc(TLScontext->con, buf, num);
	else
	    msg_panic("%s: nothing to do here", myname);
	err = SSL_get_error(TLScontext->con, status);

	/*
	 * Correspondence between SSL_ERROR_* error codes and tls_bio_(read,
	 * write, accept, connect, shutdown) return values (for brevity:
	 * retval).
	 * 
	 * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write)
	 * this is the number of plaintext bytes sent or received. With
	 * SSL_(accept, connect, shutdown) this means that the operation was
	 * completed successfully.
	 * 
	 * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force
	 * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded.
	 * 
	 * All other SSL_ERROR_* cases correspond with retval <= 0. With
	 * SSL_(read, write, accept, connect) retval == 0 means that the
	 * remote party either closed the network connection or that it
	 * requested TLS shutdown; with SSL_shutdown() retval == 0 means that
	 * our own shutdown request is in progress. With all operations
	 * retval < 0 means that there was an error. In the latter case,
	 * SSL_ERROR_SYSCALL means that error details are returned via the
	 * errno value.
	 * 
	 * 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_WANT_WRITE:
	case SSL_ERROR_WANT_READ:
	    if (enable_deadline) {
		GETTIMEOFDAY(&time_now);
		timersub(&time_deadline, &time_now, &time_left);
		timeout = time_left.tv_sec + (time_left.tv_usec > 0);
		if (timeout <= 0) {
		    errno = ETIMEDOUT;
		    return (-1);
		}
	    }
	    if (err == SSL_ERROR_WANT_WRITE) {
		if (write_wait(fd, timeout) < 0)
		    return (-1);		/* timeout error */
	    } else {
		if (read_wait(fd, timeout) < 0)
		    return (-1);		/* timeout error */
	    }
	    break;

	    /*
	     * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP)
	     * etc. Historically, Postfix silently treated these as ordinary
	     * I/O errors so we don't really know how common they are. For
	     * now, we just log a warning.
	     */
	default:
	    msg_warn("%s: unexpected SSL_ERROR code %d", myname, err);
	    /* FALLTHROUGH */

	    /*
	     * 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 */
	case SSL_ERROR_ZERO_RETURN:
	case SSL_ERROR_NONE:
	    errno = 0;				/* avoid bogus warnings */
	    /* FALLTHROUGH */
	case SSL_ERROR_SYSCALL:
	    return (status);
	}
    }
}

#endif