source: mod_gnutls/src/gnutls_io.c @ cebb74a

debian/masterdebian/stretch-backportsupstream
Last change on this file since cebb74a was cebb74a, checked in by Thomas Klute <thomas2.klute@…>, 3 years ago

Remove broken SNI/session resumption workaround

By specification, the server name should be available to the post
client hello function, which uses it to select the virtual host. The
deleted "workaround" doesn't actually help if GnuTLS does not parse
the Server Name Indication extension correctly. As of this writing
there is a bug in GnuTLS that prevents SNI during resumption from
cache, while session tickets work (use "GnuTLSSessionTickets On").

  • Property mode set to 100644
File size: 29.4 KB
Line 
1/*
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2016 Thomas Klute
6 *
7 *  Licensed under the Apache License, Version 2.0 (the "License");
8 *  you may not use this file except in compliance with the License.
9 *  You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 *  Unless required by applicable law or agreed to in writing, software
14 *  distributed under the License is distributed on an "AS IS" BASIS,
15 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 *  See the License for the specific language governing permissions and
17 *  limitations under the License.
18 */
19
20#include "mod_gnutls.h"
21
22#ifdef APLOG_USE_MODULE
23APLOG_USE_MODULE(gnutls);
24#endif
25
26#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
27#include <inttypes.h>
28#endif
29
30/**
31 * @file
32 * Describe how the GnuTLS Filter system works here
33 *  - Basicly the same as what mod_ssl does with OpenSSL.
34 *
35 */
36
37#define HTTP_ON_HTTPS_PORT \
38    "GET /" CRLF
39
40#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
41    apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
42                               sizeof(HTTP_ON_HTTPS_PORT) - 1, \
43                               alloc)
44
45#define IS_PROXY_STR(c) \
46    ((c->is_proxy == GNUTLS_ENABLED_TRUE) ? "proxy " : "")
47
48/**
49 * Convert `APR_EINTR` or `APR_EAGAIN` to the matching errno. Needed
50 * to pass the status on to GnuTLS from the pull and push functions.
51 */
52#define EAI_APR_TO_RAW(s) (APR_STATUS_IS_EAGAIN(s) ? EAGAIN : EINTR)
53
54
55
56static apr_status_t gnutls_io_filter_error(ap_filter_t * f,
57        apr_bucket_brigade * bb,
58        apr_status_t status) {
59    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
60    apr_bucket *bucket;
61
62    switch (status) {
63    case HTTP_BAD_REQUEST:
64        /* log the situation */
65        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
66                      "GnuTLS handshake failed: HTTP spoken on HTTPS port; "
67                      "trying to send HTML error page");
68        mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
69            ap_get_module_config(f->c->base_server->module_config,
70                                 &gnutls_module);
71        ctxt->status = -1;
72        sc->non_ssl_request = 1;
73
74        /* fake the request line */
75        bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
76        break;
77
78    default:
79        return status;
80    }
81
82    APR_BRIGADE_INSERT_TAIL(bb, bucket);
83    bucket = apr_bucket_eos_create(f->c->bucket_alloc);
84    APR_BRIGADE_INSERT_TAIL(bb, bucket);
85
86    return APR_SUCCESS;
87}
88
89static int char_buffer_read(mgs_char_buffer_t * buffer, char *in, int inl) {
90    if (!buffer->length) {
91        return 0;
92    }
93
94    if (buffer->length > inl) {
95        /* we have have enough to fill the caller's buffer */
96        memmove(in, buffer->value, inl);
97        buffer->value += inl;
98        buffer->length -= inl;
99    } else {
100        /* swallow remainder of the buffer */
101        memmove(in, buffer->value, buffer->length);
102        inl = buffer->length;
103        buffer->value = NULL;
104        buffer->length = 0;
105    }
106
107    return inl;
108}
109
110static int char_buffer_write(mgs_char_buffer_t * buffer, char *in, int inl) {
111    buffer->value = in;
112    buffer->length = inl;
113    return inl;
114}
115
116/**
117 * From mod_ssl / ssl_engine_io.c
118 * This function will read from a brigade and discard the read buckets as it
119 * proceeds.  It will read at most *len bytes.
120 */
121static apr_status_t brigade_consume(apr_bucket_brigade * bb,
122        apr_read_type_e block,
123        char *c, apr_size_t * len) {
124    apr_size_t actual = 0;
125    apr_status_t status = APR_SUCCESS;
126
127    while (!APR_BRIGADE_EMPTY(bb)) {
128        apr_bucket *b = APR_BRIGADE_FIRST(bb);
129        const char *str;
130        apr_size_t str_len;
131        apr_size_t consume;
132
133        /* Justin points out this is an http-ism that might
134         * not fit if brigade_consume is added to APR.  Perhaps
135         * apr_bucket_read(eos_bucket) should return APR_EOF?
136         * Then this becomes mainline instead of a one-off.
137         */
138        if (APR_BUCKET_IS_EOS(b)) {
139            status = APR_EOF;
140            break;
141        }
142
143        /* The reason I'm not offering brigade_consume yet
144         * across to apr-util is that the following call
145         * illustrates how borked that API really is.  For
146         * this sort of case (caller provided buffer) it
147         * would be much more trivial for apr_bucket_consume
148         * to do all the work that follows, based on the
149         * particular characteristics of the bucket we are
150         * consuming here.
151         */
152        status = apr_bucket_read(b, &str, &str_len, block);
153
154        if (status != APR_SUCCESS) {
155            if (APR_STATUS_IS_EOF(status)) {
156                /* This stream bucket was consumed */
157                apr_bucket_delete(b);
158                continue;
159            }
160            break;
161        }
162
163        if (str_len > 0) {
164            /* Do not block once some data has been consumed */
165            block = APR_NONBLOCK_READ;
166
167            /* Assure we don't overflow. */
168            consume =
169                    (str_len + actual >
170                    *len) ? *len - actual : str_len;
171
172            memcpy(c, str, consume);
173
174            c += consume;
175            actual += consume;
176
177            if (consume >= b->length) {
178                /* This physical bucket was consumed */
179                apr_bucket_delete(b);
180            } else {
181                /* Only part of this physical bucket was consumed */
182                b->start += consume;
183                b->length -= consume;
184            }
185        } else if (b->length == 0) {
186            apr_bucket_delete(b);
187        }
188
189        /* This could probably be actual == *len, but be safe from stray
190         * photons. */
191        if (actual >= *len) {
192            break;
193        }
194    }
195
196    *len = actual;
197    return status;
198}
199
200static apr_status_t gnutls_io_input_read(mgs_handle_t * ctxt,
201        char *buf, apr_size_t * len)
202{
203    apr_size_t wanted = *len;
204    apr_size_t bytes = 0;
205    int rc;
206
207    *len = 0;
208
209    /* If we have something leftover from last time, try that first. */
210    if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) {
211        *len = bytes;
212        if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
213            /* We want to rollback this read. */
214            if (ctxt->input_cbuf.length > 0) {
215                ctxt->input_cbuf.value -= bytes;
216                ctxt->input_cbuf.length += bytes;
217            } else {
218                char_buffer_write(&ctxt->input_cbuf, buf,
219                        (int) bytes);
220            }
221            return APR_SUCCESS;
222        }
223        /* This could probably be *len == wanted, but be safe from stray
224         * photons.
225         */
226        if (*len >= wanted) {
227            return APR_SUCCESS;
228        }
229        if (ctxt->input_mode == AP_MODE_GETLINE) {
230            if (memchr(buf, APR_ASCII_LF, *len)) {
231                return APR_SUCCESS;
232            }
233        } else {
234            /* Down to a nonblock pattern as we have some data already
235             */
236            ctxt->input_block = APR_NONBLOCK_READ;
237        }
238    }
239
240    if (ctxt->session == NULL) {
241        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
242                      "%s: GnuTLS session is NULL!", __func__);
243        return APR_EGENERAL;
244    }
245
246    while (1)
247    {
248        rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes);
249
250        if (rc == GNUTLS_E_INTERRUPTED)
251            ctxt->input_rc = APR_EINTR;
252        else if (rc == GNUTLS_E_AGAIN)
253            ctxt->input_rc = APR_EAGAIN;
254
255        if (rc > 0) {
256            *len += rc;
257            if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
258                /* We want to rollback this read. */
259                char_buffer_write(&ctxt->input_cbuf, buf,
260                        rc);
261            }
262            return ctxt->input_rc;
263        } else if (rc == 0) {
264            /* If EAGAIN, we will loop given a blocking read,
265             * otherwise consider ourselves at EOF.
266             */
267            if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
268                    || APR_STATUS_IS_EINTR(ctxt->input_rc)) {
269                /* Already read something, return APR_SUCCESS instead.
270                 * On win32 in particular, but perhaps on other kernels,
271                 * a blocking call isn't 'always' blocking.
272                 */
273                if (*len > 0) {
274                    ctxt->input_rc = APR_SUCCESS;
275                    break;
276                }
277                if (ctxt->input_block == APR_NONBLOCK_READ) {
278                    break;
279                }
280            } else {
281                if (*len > 0) {
282                    ctxt->input_rc = APR_SUCCESS;
283                } else {
284                    ctxt->input_rc = APR_EOF;
285                }
286                break;
287            }
288        } else { /* (rc < 0) */
289
290            if (rc == GNUTLS_E_REHANDSHAKE) {
291                /* A client has asked for a new Hankshake. Currently, we don't do it */
292                ap_log_cerror(APLOG_MARK, APLOG_INFO,
293                        ctxt->input_rc,
294                        ctxt->c,
295                        "GnuTLS: Error reading data. Client Requested a New Handshake."
296                        " (%d) '%s'", rc,
297                        gnutls_strerror(rc));
298            } else if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
299                rc = gnutls_alert_get(ctxt->session);
300                ap_log_cerror(APLOG_MARK, APLOG_INFO,
301                        ctxt->input_rc,
302                        ctxt->c,
303                        "GnuTLS: Warning Alert From Client: "
304                        " (%d) '%s'", rc,
305                        gnutls_alert_get_name(rc));
306            } else if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
307                rc = gnutls_alert_get(ctxt->session);
308                ap_log_cerror(APLOG_MARK, APLOG_INFO,
309                        ctxt->input_rc,
310                        ctxt->c,
311                        "GnuTLS: Fatal Alert From Client: "
312                        "(%d) '%s'", rc,
313                        gnutls_alert_get_name(rc));
314                ctxt->input_rc = APR_EGENERAL;
315                break;
316            } else {
317                /* Some Other Error. Report it. Die. */
318                if (gnutls_error_is_fatal(rc)) {
319                    ap_log_cerror(APLOG_MARK,
320                            APLOG_INFO,
321                            ctxt->input_rc,
322                            ctxt->c,
323                            "GnuTLS: Error reading data. (%d) '%s'",
324                            rc,
325                            gnutls_strerror(rc));
326                } else if (*len > 0) {
327                    ctxt->input_rc = APR_SUCCESS;
328                    break;
329                }
330            }
331
332            if (ctxt->input_rc == APR_SUCCESS) {
333                ap_log_cerror(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c,
334                              "%s: GnuTLS error: %s (%d)",
335                              __func__, gnutls_strerror(rc), rc);
336                ctxt->input_rc = APR_EGENERAL;
337            }
338            break;
339        }
340    }
341    return ctxt->input_rc;
342}
343
344static apr_status_t gnutls_io_input_getline(mgs_handle_t * ctxt,
345        char *buf, apr_size_t * len) {
346    const char *pos = NULL;
347    apr_status_t status;
348    apr_size_t tmplen = *len, buflen = *len, offset = 0;
349
350    *len = 0;
351
352    while (tmplen > 0) {
353        status = gnutls_io_input_read(ctxt, buf + offset, &tmplen);
354
355        if (status != APR_SUCCESS) {
356            return status;
357        }
358
359        *len += tmplen;
360
361        if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
362            break;
363        }
364
365        offset += tmplen;
366        tmplen = buflen - offset;
367    }
368
369    if (pos) {
370        char *value;
371        int length;
372        apr_size_t bytes = pos - buf;
373
374        bytes += 1;
375        value = buf + bytes;
376        length = *len - bytes;
377
378        char_buffer_write(&ctxt->input_cbuf, value, length);
379
380        *len = bytes;
381    }
382
383    return APR_SUCCESS;
384}
385
386#define HANDSHAKE_MAX_TRIES 1024
387
388static int gnutls_do_handshake(mgs_handle_t * ctxt) {
389    int ret;
390    int errcode;
391    int maxtries = HANDSHAKE_MAX_TRIES;
392
393    if (ctxt->status != 0 || ctxt->session == NULL) {
394        return -1;
395    }
396
397tryagain:
398    do {
399        ret = gnutls_handshake(ctxt->session);
400        maxtries--;
401    } while ((ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
402            && maxtries > 0);
403
404    if (maxtries < 1) {
405        ctxt->status = -1;
406        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
407                "GnuTLS: Handshake Failed. Hit Maximum Attempts");
408        if (ctxt->session) {
409            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
410                    gnutls_error_to_alert
411                    (GNUTLS_E_INTERNAL_ERROR, NULL));
412            gnutls_deinit(ctxt->session);
413        }
414        ctxt->session = NULL;
415        return -1;
416    }
417
418    if (ret < 0) {
419        if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED
420                || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
421            errcode = gnutls_alert_get(ctxt->session);
422            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
423                          "GnuTLS: Handshake Alert (%d) '%s'.",
424                          errcode, gnutls_alert_get_name(errcode));
425        }
426
427        if (!gnutls_error_is_fatal(ret)) {
428            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
429                          "GnuTLS: Non-Fatal Handshake Error: (%d) '%s'",
430                          ret, gnutls_strerror(ret));
431            goto tryagain;
432        }
433        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
434                "GnuTLS: Handshake Failed (%d) '%s'", ret,
435                gnutls_strerror(ret));
436        ctxt->status = -1;
437        if (ctxt->session) {
438            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
439                    gnutls_error_to_alert(ret,
440                    NULL));
441            gnutls_deinit(ctxt->session);
442        }
443        ctxt->session = NULL;
444        return ret;
445    } else {
446        /* all done with the handshake */
447        ctxt->status = 1;
448        if (gnutls_session_is_resumed(ctxt->session))
449        {
450            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
451                          "%s: TLS session resumed.", __func__);
452        }
453        return GNUTLS_E_SUCCESS;
454    }
455}
456
457int mgs_rehandshake(mgs_handle_t * ctxt) {
458    int rv;
459
460    if (ctxt->session == NULL)
461        return -1;
462
463    rv = gnutls_rehandshake(ctxt->session);
464
465    if (rv != 0) {
466        /* the client did not want to rehandshake. goodbye */
467        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
468                      "GnuTLS: Client Refused Rehandshake request.");
469        return -1;
470    }
471
472    ctxt->status = 0;
473
474    rv = gnutls_do_handshake(ctxt);
475
476    return rv;
477}
478
479
480
481/**
482 * Close the TLS session associated with the given connection
483 * structure and free its resources
484 *
485 * @param ctxt the mod_gnutls session context
486 *
487 * @return a GnuTLS status code, hopefully `GNUTLS_E_SUCCESS`
488 */
489static int mgs_bye(mgs_handle_t* ctxt)
490{
491    int ret = GNUTLS_E_SUCCESS;
492    /* End Of Connection */
493    if (ctxt->session != NULL)
494    {
495        /* Try A Clean Shutdown */
496        do {
497            ret = gnutls_bye(ctxt->session, GNUTLS_SHUT_WR);
498        } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
499        if (ret != GNUTLS_E_SUCCESS)
500            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
501                          "%s: Error while closing TLS %sconnection: "
502                          "'%s' (%d)",
503                          __func__, IS_PROXY_STR(ctxt),
504                          gnutls_strerror(ret), (int) ret);
505        else
506            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
507                          "%s: TLS %sconnection closed.",
508                          __func__, IS_PROXY_STR(ctxt));
509        /* De-Initialize Session */
510        gnutls_deinit(ctxt->session);
511        ctxt->session = NULL;
512    }
513    return ret;
514}
515
516
517
518apr_status_t mgs_filter_input(ap_filter_t * f,
519        apr_bucket_brigade * bb,
520        ap_input_mode_t mode,
521        apr_read_type_e block, apr_off_t readbytes)
522{
523    apr_status_t status = APR_SUCCESS;
524    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
525    apr_size_t len = sizeof (ctxt->input_buffer);
526
527    if (f->c->aborted) {
528        apr_bucket *bucket =
529                apr_bucket_eos_create(f->c->bucket_alloc);
530        APR_BRIGADE_INSERT_TAIL(bb, bucket);
531        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
532                      "%s: %sconnection aborted",
533                      __func__, IS_PROXY_STR(ctxt));
534        return APR_ECONNABORTED;
535    }
536
537    if (ctxt->status == 0) {
538        int ret = gnutls_do_handshake(ctxt);
539        if (ret == GNUTLS_E_SUCCESS)
540            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
541                          "%s: TLS %sconnection opened.",
542                          __func__, IS_PROXY_STR(ctxt));
543    }
544
545    if (ctxt->status < 0) {
546        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
547                      "%s %s: ap_get_brigade", __func__, IS_PROXY_STR(ctxt));
548        return ap_get_brigade(f->next, bb, mode, block, readbytes);
549    }
550
551    /* XXX: we don't currently support anything other than these modes. */
552    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
553            mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
554        return APR_ENOTIMPL;
555    }
556
557    ctxt->input_mode = mode;
558    ctxt->input_block = block;
559
560    if (ctxt->input_mode == AP_MODE_READBYTES ||
561            ctxt->input_mode == AP_MODE_SPECULATIVE) {
562        if (readbytes < 0) {
563            /* you're asking us to speculatively read a negative number of bytes! */
564            return APR_ENOTIMPL;
565        }
566        /* 'readbytes' and 'len' are of different integer types, which
567         * might have different lengths. Read sizes should be too
568         * small for 32 or 64 bit to matter, but we have to make
569         * sure. */
570#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
571        if ((apr_size_t) readbytes < len)
572        {
573            /* If readbytes is negative the function fails in the
574             * check above, but the compiler doesn't get that. */
575            if (__builtin_expect(imaxabs(readbytes) > SIZE_MAX, 0))
576            {
577                ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_EINVAL, ctxt->c,
578                              "%s: prevented buffer length overflow",
579                              __func__);
580                return APR_EINVAL;
581            }
582            len = (apr_size_t) readbytes;
583        }
584#else
585        if ((apr_size_t) readbytes < len
586            && __builtin_add_overflow(readbytes, 0, &len))
587        {
588            ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_EINVAL, ctxt->c,
589                          "%s: prevented buffer length overflow",
590                          __func__);
591            return APR_EINVAL;
592        }
593#endif
594        status =
595                gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
596    } else if (ctxt->input_mode == AP_MODE_GETLINE) {
597        status =
598                gnutls_io_input_getline(ctxt, ctxt->input_buffer,
599                &len);
600    } else {
601        /* We have no idea what you are talking about, so return an error. */
602        return APR_ENOTIMPL;
603    }
604
605    if (status != APR_SUCCESS)
606    {
607        /* no data for nonblocking read, return APR_EAGAIN */
608        if ((block == APR_NONBLOCK_READ) && APR_STATUS_IS_EINTR(status))
609            return APR_EAGAIN;
610
611        /* Close TLS session and free resources on EOF,
612         * gnutls_io_filter_error will add an EOS bucket */
613        if (APR_STATUS_IS_EOF(status))
614            mgs_bye(ctxt);
615
616        return gnutls_io_filter_error(f, bb, status);
617    }
618
619    /* Create a transient bucket out of the decrypted data. */
620    if (len > 0) {
621        apr_bucket *bucket =
622                apr_bucket_transient_create(ctxt->input_buffer, len,
623                f->c->bucket_alloc);
624        APR_BRIGADE_INSERT_TAIL(bb, bucket);
625    }
626
627    return status;
628}
629
630/**
631 * Try to flush the output bucket brigade.
632 *
633 * @param ctxt the mod_gnutls session context
634 *
635 * @return `1` on success, `-1` on failure.
636 */
637static ssize_t write_flush(mgs_handle_t * ctxt) {
638    apr_bucket *e;
639
640    if (!(ctxt->output_blen || ctxt->output_length)) {
641        ctxt->output_rc = APR_SUCCESS;
642        return 1;
643    }
644
645    if (ctxt->output_blen) {
646        e = apr_bucket_transient_create(ctxt->output_buffer,
647                ctxt->output_blen,
648                ctxt->output_bb->
649                bucket_alloc);
650        /* we filled this buffer first so add it to the
651         *               * head of the brigade
652         *                               */
653        APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e);
654        ctxt->output_blen = 0;
655    }
656
657    ctxt->output_length = 0;
658    e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc);
659    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
660
661    ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next,
662            ctxt->output_bb);
663    /* clear the brigade to be ready for next time */
664    apr_brigade_cleanup(ctxt->output_bb);
665
666    return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1;
667}
668
669apr_status_t mgs_filter_output(ap_filter_t * f, apr_bucket_brigade * bb) {
670    int ret;
671    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
672    apr_status_t status = APR_SUCCESS;
673    apr_read_type_e rblock = APR_NONBLOCK_READ;
674
675    if (f->c->aborted) {
676        apr_brigade_cleanup(bb);
677        return APR_ECONNABORTED;
678    }
679
680    if (ctxt->status == 0) {
681        ret = gnutls_do_handshake(ctxt);
682        if (ret == GNUTLS_E_SUCCESS)
683            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
684                          "%s: TLS %sconnection opened.",
685                          __func__, IS_PROXY_STR(ctxt));
686    }
687
688    if (ctxt->status < 0) {
689        return ap_pass_brigade(f->next, bb);
690    }
691
692    while (!APR_BRIGADE_EMPTY(bb)) {
693        apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
694
695        if (APR_BUCKET_IS_EOS(bucket)) {
696            return ap_pass_brigade(f->next, bb);
697        } else if (APR_BUCKET_IS_FLUSH(bucket)) {
698            /* Try Flush */
699            if (write_flush(ctxt) < 0) {
700                /* Flush Error */
701                return ctxt->output_rc;
702            }
703            /* cleanup! */
704            apr_bucket_delete(bucket);
705        } else if (AP_BUCKET_IS_EOC(bucket)) {
706            /* End Of Connection, close TLS session and free
707             * resources */
708            mgs_bye(ctxt);
709            /* cleanup! */
710            apr_bucket_delete(bucket);
711            /* Pass next brigade! */
712            return ap_pass_brigade(f->next, bb);
713        } else {
714            /* filter output */
715            const char *data;
716            apr_size_t len;
717
718            status = apr_bucket_read(bucket, &data, &len, rblock);
719
720            if (APR_STATUS_IS_EAGAIN(status)) {
721                /* No data available so Flush! */
722                if (write_flush(ctxt) < 0) {
723                    return ctxt->output_rc;
724                }
725                /* Try again with a blocking read. */
726                rblock = APR_BLOCK_READ;
727                continue;
728            }
729
730            rblock = APR_NONBLOCK_READ;
731
732            if (!APR_STATUS_IS_EOF(status)
733                    && (status != APR_SUCCESS)) {
734                return status;
735            }
736
737            if (len > 0) {
738
739                if (ctxt->session == NULL) {
740                    ret = GNUTLS_E_INVALID_REQUEST;
741                } else {
742                    do {
743                        ret =
744                                gnutls_record_send
745                                (ctxt->session, data,
746                                len);
747                    } while (ret == GNUTLS_E_INTERRUPTED
748                            || ret == GNUTLS_E_AGAIN);
749                }
750
751                if (ret < 0) {
752                    /* error sending output */
753                    ap_log_cerror(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
754                                  ctxt->c,
755                                  "GnuTLS: Error writing data. (%d) '%s'",
756                                  ret, gnutls_strerror(ret));
757                    if (ctxt->output_rc == APR_SUCCESS) {
758                        ctxt->output_rc =
759                                APR_EGENERAL;
760                        return ctxt->output_rc;
761                    }
762                } else if ((apr_size_t)(ret) != len) {
763                    /* we know the above cast is OK because len > 0 and ret >= 0 */
764                    /* Not able to send the entire bucket,
765                       split it and send it again. */
766                    apr_bucket_split(bucket, ret);
767                }
768            }
769
770            apr_bucket_delete(bucket);
771        }
772    }
773
774    return status;
775}
776
777/**
778 * Pull function for GnuTLS
779 *
780 * Generic errnos used for `gnutls_transport_set_errno()`:
781 * * `EAGAIN`: no data available at the moment, try again (maybe later)
782 * * `EINTR`: read was interrupted, try again
783 * * `EIO`: Unknown I/O error
784 * * `ECONNABORTED`: Input BB does not exist (`NULL`)
785 *
786 * The reason we are not using `APR_TO_OS_ERROR` to map `apr_status_t`
787 * to errnos is this warning [in the APR documentation][apr-warn]:
788 *
789 * > If the statcode was not created by apr_get_os_error or
790 * > APR_FROM_OS_ERROR, the results are undefined.
791 *
792 * We cannot know if this applies to any error we might encounter.
793 *
794 * @param ptr GnuTLS session data pointer (the mod_gnutls context
795 * structure)
796 *
797 * @param buffer buffer for the read data
798 *
799 * @param len maximum number of bytes to read (must fit into the
800 * buffer)
801 *
802 * @return The number of bytes read (may be zero on EOF), or `-1` on
803 * error. Note that some errors may warrant another try (see above).
804 *
805 * [apr-warn]: https://apr.apache.org/docs/apr/1.4/group__apr__errno.html#ga2385cae04b04afbdcb65f1a45c4d8506 "Apache Portable Runtime: Error Codes"
806 */
807ssize_t mgs_transport_read(gnutls_transport_ptr_t ptr,
808                           void *buffer, size_t len)
809{
810    mgs_handle_t *ctxt = ptr;
811    apr_status_t rc;
812    apr_size_t in = len;
813    apr_read_type_e block = ctxt->input_block;
814
815    ctxt->input_rc = APR_SUCCESS;
816
817    /* If Len = 0, we don't do anything. */
818    if (!len || buffer == NULL)
819    {
820        return 0;
821    }
822    /* Input bucket brigade is missing, EOF */
823    if (!ctxt->input_bb)
824    {
825        ctxt->input_rc = APR_EOF;
826        gnutls_transport_set_errno(ctxt->session, ECONNABORTED);
827        return -1;
828    }
829
830    if (APR_BRIGADE_EMPTY(ctxt->input_bb))
831    {
832        rc = ap_get_brigade(ctxt->input_filter->next,
833                            ctxt->input_bb, AP_MODE_READBYTES,
834                            ctxt->input_block, in);
835
836        /* Not a problem, there was simply no data ready yet.
837         */
838        if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
839            || (rc == APR_SUCCESS
840                && APR_BRIGADE_EMPTY(ctxt->input_bb)))
841        {
842            if (APR_STATUS_IS_EOF(ctxt->input_rc))
843            {
844                return 0;
845            }
846            else
847            {
848                gnutls_transport_set_errno(ctxt->session,
849                                           EAI_APR_TO_RAW(ctxt->input_rc));
850                return -1;
851            }
852        }
853
854        if (rc != APR_SUCCESS)
855        {
856            /* Unexpected errors discard the brigade */
857            apr_brigade_cleanup(ctxt->input_bb);
858            ctxt->input_bb = NULL;
859            gnutls_transport_set_errno(ctxt->session, EIO);
860            return -1;
861        }
862    }
863
864    ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len);
865
866    if (ctxt->input_rc == APR_SUCCESS)
867    {
868        return (ssize_t) len;
869    }
870
871    if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
872        || APR_STATUS_IS_EINTR(ctxt->input_rc))
873    {
874        if (len == 0)
875        {
876            gnutls_transport_set_errno(ctxt->session,
877                                       EAI_APR_TO_RAW(ctxt->input_rc));
878            return -1;
879        }
880
881        return (ssize_t) len;
882    }
883
884    /* Unexpected errors and APR_EOF clean out the brigade.
885     * Subsequent calls will return APR_EOF. */
886    apr_brigade_cleanup(ctxt->input_bb);
887    ctxt->input_bb = NULL;
888
889    if (APR_STATUS_IS_EOF(ctxt->input_rc) && len)
890    {
891        /* Some data has been received before EOF, return it. */
892        return (ssize_t) len;
893    }
894
895    gnutls_transport_set_errno(ctxt->session, EIO);
896    return -1;
897}
898
899/**
900 * Push function for GnuTLS
901 *
902 * `gnutls_transport_set_errno()` will be called with `EAGAIN` or
903 * `EINTR` on recoverable errors, or `EIO` in case of unexpected
904 * errors. See the description of mgs_transport_read() for details on
905 * possible error codes.
906 *
907 * @param ptr GnuTLS session data pointer (the mod_gnutls context
908 * structure)
909 *
910 * @param buffer buffer containing the data to send
911 *
912 * @param len length of the data
913 * buffer)
914 *
915 * @return The number of written bytes, or `-1` on error. Note that
916 * some errors may warrant another try (see above).
917 */
918ssize_t mgs_transport_write(gnutls_transport_ptr_t ptr,
919                            const void *buffer, size_t len)
920{
921    mgs_handle_t *ctxt = ptr;
922
923    /* pass along the encrypted data
924     * need to flush since we're using SSL's malloc-ed buffer
925     * which will be overwritten once we leave here
926     */
927    apr_bucket *bucket = apr_bucket_transient_create(buffer, len,
928            ctxt->output_bb->
929            bucket_alloc);
930    ctxt->output_length += len;
931    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket);
932
933    if (write_flush(ctxt) < 0)
934    {
935        /* We encountered an error. APR_EINTR or APR_EAGAIN can be
936         * handled, treat everything else as a generic I/O error. */
937        int err = EIO;
938        if (APR_STATUS_IS_EAGAIN(ctxt->output_rc)
939            || APR_STATUS_IS_EINTR(ctxt->output_rc))
940            err = EAI_APR_TO_RAW(ctxt->output_rc);
941
942        gnutls_transport_set_errno(ctxt->session, err);
943        return -1;
944    }
945    return len;
946}
Note: See TracBrowser for help on using the repository browser.