source: mod_gnutls/src/gnutls_io.c @ 6be4dd0

asyncio
Last change on this file since 6be4dd0 was 70f9c00, checked in by Fiona Klute <fiona.klute@…>, 5 months ago

Clean up TLS error handling

The HTTP_BAD_REQUEST case was never actually called, because nothing
in the mod_gnutls filter functions returns such an error code. I'm
completely removing it to avoid any issues with the injected request,
instead the client should just get an alert.

On the other hand, a lot of places in gnutls_io.c can use a generic
"insert EOS bucket" function. :-)

  • Property mode set to 100644
File size: 35.5 KB
Line 
1/*
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2020 Fiona 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#include "gnutls_io.h"
22#include "gnutls_proxy.h"
23
24#ifdef APLOG_USE_MODULE
25APLOG_USE_MODULE(gnutls);
26#endif
27
28#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
29#include <inttypes.h>
30#endif
31
32/**
33 * @file
34 * Describe how the GnuTLS Filter system works here
35 *  - Basicly the same as what mod_ssl does with OpenSSL.
36 *
37 */
38
39#define IS_PROXY_STR(c) \
40    ((c->is_proxy == GNUTLS_ENABLED_TRUE) ? "proxy " : "")
41
42/**
43 * Convert `APR_EINTR` or `APR_EAGAIN` to the matching errno. Needed
44 * to pass the status on to GnuTLS from the pull and push functions.
45 */
46#define EAI_APR_TO_RAW(s) (APR_STATUS_IS_EAGAIN(s) ? EAGAIN : EINTR)
47
48
49
50/**
51 * Helper function, used mostly for error conditions: Insert an EOS (end
52 * of stream) bucket into the bucket brigade.
53 */
54static inline void gnutls_io_filter_eos(ap_filter_t *f,
55                                        apr_bucket_brigade *bb)
56{
57    apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
58    APR_BRIGADE_INSERT_TAIL(bb, bucket);
59}
60
61
62
63static int char_buffer_read(mgs_char_buffer_t * buffer, char *in, int inl) {
64    if (!buffer->length) {
65        return 0;
66    }
67
68    if (buffer->length > inl) {
69        /* we have have enough to fill the caller's buffer */
70        memmove(in, buffer->value, inl);
71        buffer->value += inl;
72        buffer->length -= inl;
73    } else {
74        /* swallow remainder of the buffer */
75        memmove(in, buffer->value, buffer->length);
76        inl = buffer->length;
77        buffer->value = NULL;
78        buffer->length = 0;
79    }
80
81    return inl;
82}
83
84static int char_buffer_write(mgs_char_buffer_t * buffer, char *in, int inl) {
85    buffer->value = in;
86    buffer->length = inl;
87    return inl;
88}
89
90/**
91 * From mod_ssl / ssl_engine_io.c
92 * This function will read from a brigade and discard the read buckets as it
93 * proceeds.  It will read at most *len bytes.
94 */
95static apr_status_t brigade_consume(apr_bucket_brigade * bb,
96        apr_read_type_e block,
97        char *c, apr_size_t * len) {
98    apr_size_t actual = 0;
99    apr_status_t status = APR_SUCCESS;
100
101    while (!APR_BRIGADE_EMPTY(bb)) {
102        apr_bucket *b = APR_BRIGADE_FIRST(bb);
103        const char *str;
104        apr_size_t str_len;
105        apr_size_t consume;
106
107        /* Justin points out this is an http-ism that might
108         * not fit if brigade_consume is added to APR.  Perhaps
109         * apr_bucket_read(eos_bucket) should return APR_EOF?
110         * Then this becomes mainline instead of a one-off.
111         */
112        if (APR_BUCKET_IS_EOS(b)) {
113            status = APR_EOF;
114            break;
115        }
116
117        /* The reason I'm not offering brigade_consume yet
118         * across to apr-util is that the following call
119         * illustrates how borked that API really is.  For
120         * this sort of case (caller provided buffer) it
121         * would be much more trivial for apr_bucket_consume
122         * to do all the work that follows, based on the
123         * particular characteristics of the bucket we are
124         * consuming here.
125         */
126        status = apr_bucket_read(b, &str, &str_len, block);
127
128        if (status != APR_SUCCESS) {
129            if (APR_STATUS_IS_EOF(status)) {
130                /* This stream bucket was consumed */
131                apr_bucket_delete(b);
132                continue;
133            }
134            break;
135        }
136
137        if (str_len > 0) {
138            /* Do not block once some data has been consumed */
139            block = APR_NONBLOCK_READ;
140
141            /* Assure we don't overflow. */
142            consume =
143                    (str_len + actual >
144                    *len) ? *len - actual : str_len;
145
146            memcpy(c, str, consume);
147
148            c += consume;
149            actual += consume;
150
151            if (consume >= b->length) {
152                /* This physical bucket was consumed */
153                apr_bucket_delete(b);
154            } else {
155                /* Only part of this physical bucket was consumed */
156                b->start += consume;
157                b->length -= consume;
158            }
159        } else if (b->length == 0) {
160            apr_bucket_delete(b);
161        }
162
163        /* This could probably be actual == *len, but be safe from stray
164         * photons. */
165        if (actual >= *len) {
166            break;
167        }
168    }
169
170    *len = actual;
171    return status;
172}
173
174static apr_status_t gnutls_io_input_read(mgs_handle_t * ctxt,
175        char *buf, apr_size_t * len)
176{
177    apr_size_t wanted = *len;
178    apr_size_t bytes = 0;
179    int rc;
180
181    *len = 0;
182
183    /* If we have something leftover from last time, try that first. */
184    if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) {
185        *len = bytes;
186        if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
187            /* We want to rollback this read. */
188            if (ctxt->input_cbuf.length > 0) {
189                ctxt->input_cbuf.value -= bytes;
190                ctxt->input_cbuf.length += bytes;
191            } else {
192                char_buffer_write(&ctxt->input_cbuf, buf,
193                        (int) bytes);
194            }
195            return APR_SUCCESS;
196        }
197        /* This could probably be *len == wanted, but be safe from stray
198         * photons.
199         */
200        if (*len >= wanted) {
201            return APR_SUCCESS;
202        }
203        if (ctxt->input_mode == AP_MODE_GETLINE) {
204            if (memchr(buf, APR_ASCII_LF, *len)) {
205                return APR_SUCCESS;
206            }
207        } else {
208            /* Down to a nonblock pattern as we have some data already
209             */
210            ctxt->input_block = APR_NONBLOCK_READ;
211        }
212    }
213
214    if (ctxt->session == NULL) {
215        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
216                      "%s: GnuTLS session is NULL!", __func__);
217        return APR_EGENERAL;
218    }
219
220    while (1)
221    {
222        /* Note: The pull function sets ctxt->input_rc */
223        rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes);
224
225        if (rc > 0) {
226            *len += rc;
227            if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
228                /* We want to rollback this read. */
229                char_buffer_write(&ctxt->input_cbuf, buf,
230                                  *len);
231            }
232            return ctxt->input_rc;
233        } else if (rc == 0) {
234            /* EOF, return code depends on whether we still have data
235             * to return. */
236            if (*len > 0) {
237                ctxt->input_rc = APR_SUCCESS;
238            } else {
239                ctxt->input_rc = APR_EOF;
240            }
241            break;
242        } else { /* (rc < 0) */
243
244            if (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN)
245            {
246                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, ctxt->input_rc, ctxt->c,
247                              "%s: looping recv after '%s' (%d)",
248                              __func__, gnutls_strerror(rc), rc);
249                /* For a blocking read, loop and try again
250                 * immediately. Otherwise just notify the caller. */
251                if (ctxt->input_block != APR_NONBLOCK_READ)
252                    continue;
253                else
254                    ctxt->input_rc =
255                        (rc == GNUTLS_E_AGAIN ? APR_EAGAIN : APR_EINTR);
256            } else if (rc == GNUTLS_E_REHANDSHAKE) {
257                /* A client has asked for a new Hankshake. Currently, we don't do it */
258                ap_log_cerror(APLOG_MARK, APLOG_INFO,
259                        ctxt->input_rc,
260                        ctxt->c,
261                        "GnuTLS: Error reading data. Client Requested a New Handshake."
262                        " (%d) '%s'", rc,
263                        gnutls_strerror(rc));
264            } else if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
265                rc = gnutls_alert_get(ctxt->session);
266                ap_log_cerror(APLOG_MARK, APLOG_INFO,
267                        ctxt->input_rc,
268                        ctxt->c,
269                        "GnuTLS: Warning Alert From Client: "
270                        " (%d) '%s'", rc,
271                        gnutls_alert_get_name(rc));
272            } else if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
273                rc = gnutls_alert_get(ctxt->session);
274                ap_log_cerror(APLOG_MARK, APLOG_INFO,
275                        ctxt->input_rc,
276                        ctxt->c,
277                        "GnuTLS: Fatal Alert From Client: "
278                        "(%d) '%s'", rc,
279                        gnutls_alert_get_name(rc));
280                ctxt->input_rc = APR_EGENERAL;
281                break;
282            } else {
283                /* Some Other Error. Report it. Die. */
284                if (gnutls_error_is_fatal(rc)) {
285                    ap_log_cerror(APLOG_MARK,
286                            APLOG_INFO,
287                            ctxt->input_rc,
288                            ctxt->c,
289                            "GnuTLS: Error reading data. (%d) '%s'",
290                            rc,
291                            gnutls_strerror(rc));
292                } else if (*len > 0) {
293                    ctxt->input_rc = APR_SUCCESS;
294                    break;
295                }
296            }
297
298            if (ctxt->input_rc == APR_SUCCESS) {
299                ap_log_cerror(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c,
300                              "%s: GnuTLS error: %s (%d)",
301                              __func__, gnutls_strerror(rc), rc);
302                ctxt->input_rc = APR_EGENERAL;
303            }
304            break;
305        }
306    }
307    return ctxt->input_rc;
308}
309
310static apr_status_t gnutls_io_input_getline(mgs_handle_t * ctxt,
311        char *buf, apr_size_t * len) {
312    const char *pos = NULL;
313    apr_status_t status;
314    apr_size_t tmplen = *len, buflen = *len, offset = 0;
315
316    *len = 0;
317
318    while (tmplen > 0) {
319        status = gnutls_io_input_read(ctxt, buf + offset, &tmplen);
320
321        if (status != APR_SUCCESS) {
322            return status;
323        }
324
325        *len += tmplen;
326
327        if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
328            break;
329        }
330
331        offset += tmplen;
332        tmplen = buflen - offset;
333    }
334
335    if (pos) {
336        char *value;
337        int length;
338        apr_size_t bytes = pos - buf;
339
340        bytes += 1;
341        value = buf + bytes;
342        length = *len - bytes;
343
344        char_buffer_write(&ctxt->input_cbuf, value, length);
345
346        *len = bytes;
347    }
348
349    return APR_SUCCESS;
350}
351
352#define HANDSHAKE_MAX_TRIES 1024
353
354static int gnutls_do_handshake(mgs_handle_t * ctxt) {
355    int ret;
356    int errcode;
357    int maxtries = HANDSHAKE_MAX_TRIES;
358
359    if (ctxt->status != 0 || ctxt->session == NULL) {
360        return -1;
361    }
362
363    /* Enable SNI and ALPN for proxy connections, and load cached
364     * session if any. */
365    if (ctxt->is_proxy == GNUTLS_ENABLED_TRUE)
366        mgs_set_proxy_handshake_ext(ctxt);
367
368tryagain:
369    do {
370        ret = gnutls_handshake(ctxt->session);
371        maxtries--;
372    } while ((ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
373            && maxtries > 0);
374
375    if (maxtries < 1) {
376        ctxt->status = -1;
377        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
378                "GnuTLS: Handshake Failed. Hit Maximum Attempts");
379        if (ctxt->session) {
380            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
381                    gnutls_error_to_alert
382                    (GNUTLS_E_INTERNAL_ERROR, NULL));
383            gnutls_deinit(ctxt->session);
384        }
385        ctxt->session = NULL;
386        return -1;
387    }
388
389    if (ret < 0) {
390        if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED
391                || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
392            errcode = gnutls_alert_get(ctxt->session);
393            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
394                          "GnuTLS: Handshake Alert (%d) '%s'.",
395                          errcode, gnutls_alert_get_name(errcode));
396        }
397
398        if (!gnutls_error_is_fatal(ret)) {
399            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
400                          "GnuTLS: Non-Fatal Handshake Error: (%d) '%s'",
401                          ret, gnutls_strerror(ret));
402            goto tryagain;
403        }
404        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
405                "GnuTLS: Handshake Failed (%d) '%s'", ret,
406                gnutls_strerror(ret));
407        ctxt->status = -1;
408        if (ctxt->session) {
409            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
410                    gnutls_error_to_alert(ret,
411                    NULL));
412            gnutls_deinit(ctxt->session);
413        }
414        ctxt->session = NULL;
415        return ret;
416    } else {
417        /* all done with the handshake */
418        ctxt->status = 1;
419        if (gnutls_session_is_resumed(ctxt->session))
420        {
421            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
422                          "%s: TLS session resumed.", __func__);
423        }
424        return GNUTLS_E_SUCCESS;
425    }
426}
427
428
429
430int mgs_reauth(mgs_handle_t *ctxt, request_rec *r)
431{
432    if (ctxt->session == NULL)
433        return GNUTLS_E_INVALID_REQUEST;
434
435    /* Initialize to error to avoid false-good return value. */
436    int rv = GNUTLS_E_INTERNAL_ERROR;
437    int tries = 0;
438
439    do
440    {
441        rv = gnutls_reauth(ctxt->session, 0);
442        tries++;
443
444        /* GNUTLS_E_GOT_APPLICATION_DATA can (randomly, depending on
445         * timing) happen with a request containing a body. According to
446         * https://tools.ietf.org/html/rfc8446#appendix-E.1.2
447         * post-handshake authentication proves that the authenticated
448         * party is the one that did the handshake, so caching the data
449         * is appropriate. */
450        if (rv == GNUTLS_E_GOT_APPLICATION_DATA)
451        {
452            /* Fill connection input buffer using a speculative read. */
453            apr_size_t len = sizeof(ctxt->input_buffer);
454            ctxt->input_mode = AP_MODE_SPECULATIVE;
455            apr_status_t status =
456                gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
457            if (status == APR_SUCCESS)
458            {
459                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
460                              "%s: cached %" APR_SIZE_T_FMT " bytes.",
461                              __func__, len);
462                /* If the cache was too small to accept all pending data
463                 * we'll get GNUTLS_E_GOT_APPLICATION_DATA again, and the
464                 * authz hook will return HTTP_REQUEST_ENTITY_TOO_LARGE to
465                 * the client. */
466                rv = gnutls_reauth(ctxt->session, 0);
467            }
468            else
469                ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r,
470                              "%s: buffering request data failed!",
471                              __func__);
472        }
473        /* Retry on GNUTLS_E_INTERRUPTED or GNUTLS_E_AGAIN, whether
474         * from initial gnutls_reauth() call or after buffering. */
475    } while (tries < HANDSHAKE_MAX_TRIES
476             && (rv == GNUTLS_E_INTERRUPTED || rv == GNUTLS_E_AGAIN));
477
478    if (rv != GNUTLS_E_SUCCESS)
479    {
480        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
481                      "%s: post-handshake authentication failed: %s (%d)",
482                      __func__, gnutls_strerror(rv), rv);
483        return rv;
484    }
485
486    return GNUTLS_E_SUCCESS;
487}
488
489
490
491/**
492 * Close the TLS session associated with the given connection
493 * structure and free its resources
494 *
495 * @param ctxt the mod_gnutls session context
496 *
497 * @return a GnuTLS status code, hopefully `GNUTLS_E_SUCCESS`
498 */
499static int mgs_bye(mgs_handle_t* ctxt)
500{
501    int ret = GNUTLS_E_SUCCESS;
502    /* End Of Connection */
503    if (ctxt->session != NULL)
504    {
505        /* Try A Clean Shutdown */
506        do {
507            ret = gnutls_bye(ctxt->session, GNUTLS_SHUT_WR);
508        } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
509        if (ret != GNUTLS_E_SUCCESS)
510            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
511                          "%s: Error while closing TLS %sconnection: "
512                          "'%s' (%d)",
513                          __func__, IS_PROXY_STR(ctxt),
514                          gnutls_strerror(ret), (int) ret);
515        else
516            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
517                          "%s: TLS %sconnection closed.",
518                          __func__, IS_PROXY_STR(ctxt));
519        /* De-Initialize Session */
520        gnutls_deinit(ctxt->session);
521        ctxt->session = NULL;
522    }
523    return ret;
524}
525
526
527
528apr_status_t mgs_filter_input(ap_filter_t * f,
529        apr_bucket_brigade * bb,
530        ap_input_mode_t mode,
531        apr_read_type_e block, apr_off_t readbytes)
532{
533    apr_status_t status = APR_SUCCESS;
534    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
535    apr_size_t len = sizeof (ctxt->input_buffer);
536
537    if (f->c->aborted) {
538        gnutls_io_filter_eos(f, bb);
539        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
540                      "%s: %sconnection aborted",
541                      __func__, IS_PROXY_STR(ctxt));
542        return APR_ECONNABORTED;
543    }
544
545    if (ctxt->status == 0) {
546        int ret = gnutls_do_handshake(ctxt);
547        if (ret == GNUTLS_E_SUCCESS)
548            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
549                          "%s: TLS %sconnection opened.",
550                          __func__, IS_PROXY_STR(ctxt));
551    }
552
553    if (ctxt->status < 0)
554    {
555        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
556                      "%s: %sconnection failed, cannot provide data!",
557                      __func__, IS_PROXY_STR(ctxt));
558        gnutls_io_filter_eos(f, bb);
559        return APR_ECONNABORTED;
560    }
561
562    /* XXX: we don't currently support anything other than these modes. */
563    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
564            mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
565        return APR_ENOTIMPL;
566    }
567
568    ctxt->input_mode = mode;
569    ctxt->input_block = block;
570
571    if (ctxt->input_mode == AP_MODE_READBYTES ||
572            ctxt->input_mode == AP_MODE_SPECULATIVE) {
573        if (readbytes < 0) {
574            /* you're asking us to speculatively read a negative number of bytes! */
575            return APR_ENOTIMPL;
576        }
577        /* 'readbytes' and 'len' are of different integer types, which
578         * might have different lengths. Read sizes should be too
579         * small for 32 or 64 bit to matter, but we have to make
580         * sure. */
581#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
582        if ((apr_size_t) readbytes < len)
583        {
584            /* If readbytes is negative the function fails in the
585             * check above, but the compiler doesn't get that. */
586            if (__builtin_expect(imaxabs(readbytes) > SIZE_MAX, 0))
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            len = (apr_size_t) readbytes;
594        }
595#else
596        if ((apr_size_t) readbytes < len
597            && __builtin_add_overflow(readbytes, 0, &len))
598        {
599            ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_EINVAL, ctxt->c,
600                          "%s: prevented buffer length overflow",
601                          __func__);
602            return APR_EINVAL;
603        }
604#endif
605        status =
606                gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
607    } else if (ctxt->input_mode == AP_MODE_GETLINE) {
608        status =
609                gnutls_io_input_getline(ctxt, ctxt->input_buffer,
610                &len);
611    } else {
612        /* We have no idea what you are talking about, so return an error. */
613        return APR_ENOTIMPL;
614    }
615
616    if (status != APR_SUCCESS)
617    {
618        /* no data for nonblocking read, return APR_EAGAIN */
619        if ((block == APR_NONBLOCK_READ) && APR_STATUS_IS_EINTR(status))
620            return APR_EAGAIN;
621
622        /* Close TLS session and free resources on EOF,
623         * gnutls_io_filter_eos will add an EOS bucket */
624        if (APR_STATUS_IS_EOF(status))
625            mgs_bye(ctxt);
626
627        gnutls_io_filter_eos(f, bb);
628        return status;
629    }
630
631    /* Create a transient bucket out of the decrypted data. */
632    if (len > 0) {
633        apr_bucket *bucket =
634                apr_bucket_transient_create(ctxt->input_buffer, len,
635                f->c->bucket_alloc);
636        APR_BRIGADE_INSERT_TAIL(bb, bucket);
637    }
638
639    return status;
640}
641
642/**
643 * Try to flush the output bucket brigade.
644 *
645 * @param ctxt the mod_gnutls session context
646 *
647 * @return `1` on success, `-1` on failure.
648 */
649static ssize_t write_flush(mgs_handle_t * ctxt) {
650    apr_bucket *e;
651
652    if (!(ctxt->output_blen || ctxt->output_length)) {
653        ctxt->output_rc = APR_SUCCESS;
654        return 1;
655    }
656
657    if (ctxt->output_blen) {
658        e = apr_bucket_transient_create(ctxt->output_buffer,
659                ctxt->output_blen,
660                ctxt->output_bb->
661                bucket_alloc);
662        /* we filled this buffer first so add it to the
663         *               * head of the brigade
664         *                               */
665        APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e);
666        ctxt->output_blen = 0;
667    }
668
669    ctxt->output_length = 0;
670    e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc);
671    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
672
673    ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next,
674            ctxt->output_bb);
675    /* clear the brigade to be ready for next time */
676    apr_brigade_cleanup(ctxt->output_bb);
677
678    return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1;
679}
680
681apr_status_t mgs_filter_output(ap_filter_t * f, apr_bucket_brigade * bb) {
682    int ret;
683    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
684    apr_status_t status = APR_SUCCESS;
685    apr_read_type_e rblock = APR_NONBLOCK_READ;
686
687    if (f->c->aborted) {
688        apr_brigade_cleanup(bb);
689        return APR_ECONNABORTED;
690    }
691
692    if (ctxt->status == 0) {
693        ret = gnutls_do_handshake(ctxt);
694        if (ret == GNUTLS_E_SUCCESS)
695            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
696                          "%s: TLS %sconnection opened.",
697                          __func__, IS_PROXY_STR(ctxt));
698        else if (ctxt->is_proxy)
699        {
700            /* If mod_proxy receives an error while trying to send its
701             * request it sends an "invalid request" error to the
702             * client. By pretending we could send the request
703             * mod_proxy continues its processing and sends a proper
704             * "proxy error" message when there's no response to read. */
705            gnutls_io_filter_eos(f, bb);
706            return APR_SUCCESS;
707        }
708        /* No final else here, the "ctxt->status < 0" check below will
709         * catch that. */
710    }
711
712    if (ctxt->status < 0)
713    {
714        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
715                      "%s: %sconnection failed, refusing to send.",
716                      __func__, IS_PROXY_STR(ctxt));
717        return APR_ECONNABORTED;
718    }
719
720    while (!APR_BRIGADE_EMPTY(bb)) {
721        apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
722
723        if (APR_BUCKET_IS_EOS(bucket)) {
724            return ap_pass_brigade(f->next, bb);
725        } else if (APR_BUCKET_IS_FLUSH(bucket)) {
726            /* Try Flush */
727            if (write_flush(ctxt) < 0) {
728                /* Flush Error */
729                return ctxt->output_rc;
730            }
731            /* cleanup! */
732            apr_bucket_delete(bucket);
733        } else if (AP_BUCKET_IS_EOC(bucket)) {
734            /* End Of Connection, close TLS session and free
735             * resources */
736            mgs_bye(ctxt);
737            /* cleanup! */
738            apr_bucket_delete(bucket);
739            /* Pass next brigade! */
740            return ap_pass_brigade(f->next, bb);
741        } else {
742            /* filter output */
743            const char *data;
744            apr_size_t len;
745
746            status = apr_bucket_read(bucket, &data, &len, rblock);
747
748            if (APR_STATUS_IS_EAGAIN(status)) {
749                /* No data available so Flush! */
750                if (write_flush(ctxt) < 0) {
751                    return ctxt->output_rc;
752                }
753                /* Try again with a blocking read. */
754                rblock = APR_BLOCK_READ;
755                continue;
756            }
757
758            rblock = APR_NONBLOCK_READ;
759
760            if (!APR_STATUS_IS_EOF(status)
761                    && (status != APR_SUCCESS)) {
762                return status;
763            }
764
765            if (len > 0) {
766
767                if (ctxt->session == NULL) {
768                    ret = GNUTLS_E_INVALID_REQUEST;
769                } else {
770                    do {
771                        ret =
772                                gnutls_record_send
773                                (ctxt->session, data,
774                                len);
775                    } while (ret == GNUTLS_E_INTERRUPTED
776                            || ret == GNUTLS_E_AGAIN);
777                }
778
779                if (ret < 0) {
780                    /* error sending output */
781                    ap_log_cerror(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
782                                  ctxt->c,
783                                  "GnuTLS: Error writing data. (%d) '%s'",
784                                  ret, gnutls_strerror(ret));
785                    if (ctxt->output_rc == APR_SUCCESS) {
786                        ctxt->output_rc =
787                                APR_EGENERAL;
788                        return ctxt->output_rc;
789                    }
790                } else if ((apr_size_t)(ret) != len) {
791                    /* we know the above cast is OK because len > 0 and ret >= 0 */
792                    /* Not able to send the entire bucket,
793                       split it and send it again. */
794                    apr_bucket_split(bucket, ret);
795                }
796            }
797
798            apr_bucket_delete(bucket);
799        }
800    }
801
802    return status;
803}
804
805int mgs_transport_read_ready(gnutls_transport_ptr_t ptr,
806                             unsigned int ms)
807{
808    mgs_handle_t *ctxt = ptr;
809    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
810                  "%s: called with %u ms wait", __func__, ms);
811
812    apr_pool_t *tmp = NULL;
813    apr_status_t rv = apr_pool_create(&tmp, ctxt->c->pool);
814    if (rv != APR_SUCCESS)
815    {
816        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, ctxt->c,
817                      "could not create temporary pool for %s",
818                     __func__);
819        return -1;
820    }
821
822    apr_bucket_brigade *bb = apr_brigade_create(tmp, ctxt->c->bucket_alloc);
823
824    /* one byte non-blocking speculative read to see if there's data
825     * in the filter chain */
826    rv = ap_get_brigade(ctxt->input_filter->next, bb, AP_MODE_SPECULATIVE,
827                        APR_NONBLOCK_READ, 1);
828
829    int result;
830    if (rv == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb))
831        result = 1;
832    else
833        result = 0;
834
835    apr_brigade_destroy(bb);
836
837    /* If GnuTLS doesn't want to wait or we already have data,
838     * return. */
839    if (ms == 0 || result == 1)
840    {
841        apr_pool_destroy(tmp);
842        return result;
843    }
844
845    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
846                  "%s: waiting for data", __func__);
847
848    /* No data yet, and we're supposed to wait, so wait for data on
849     * the socket. */
850    apr_socket_t *sock = ap_get_conn_socket(ctxt->c);
851    apr_interval_time_t timeout = -1;
852    apr_interval_time_t original_timeout;
853    rv = apr_socket_timeout_get(sock, &original_timeout);
854    if (rv != APR_SUCCESS)
855    {
856        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctxt->c,
857                      "%s: could not get socket timeout",
858                      __func__);
859        apr_pool_destroy(tmp);
860        return -1;
861    }
862
863    /* If GnuTLS requests an "indefinite" wait we do not want to mess
864     * with whatever Apache does by default. Otherwise temporarily
865     * adjust the socket timeout. */
866    if (ms != GNUTLS_INDEFINITE_TIMEOUT)
867    {
868        /* apr_interval_time_t is in microseconds */
869        if (__builtin_mul_overflow(ms, 1000, &timeout))
870        {
871            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, ctxt->c,
872                          "%s: overflow while calculating timeout!",
873                          __func__);
874            apr_pool_destroy(tmp);
875            return -1;
876        }
877        rv = apr_socket_timeout_set(sock, timeout);
878        if (rv != APR_SUCCESS)
879        {
880            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctxt->c,
881                          "%s: could not set socket timeout",
882                          __func__);
883            apr_pool_destroy(tmp);
884            return -1;
885        }
886    }
887
888#if APR_MAJOR_VERSION < 2
889    apr_pollfd_t pollset;
890    apr_int32_t nsds;
891    pollset.p = tmp;
892    pollset.desc_type = APR_POLL_SOCKET;
893    pollset.reqevents = APR_POLLIN | APR_POLLHUP;
894    pollset.desc.s = sock;
895    rv = apr_poll(&pollset, 1, &nsds, timeout);
896#else
897    rv = apr_socket_wait(sock, APR_WAIT_READ);
898#endif
899    apr_pool_destroy(tmp);
900
901    if (ms != GNUTLS_INDEFINITE_TIMEOUT)
902    {
903        /* We still need "rv" below, so new variable. */
904        apr_status_t rc = apr_socket_timeout_set(sock, original_timeout);
905        if (rc != APR_SUCCESS)
906        {
907            ap_log_cerror(APLOG_MARK, APLOG_CRIT, rc, ctxt->c,
908                          "%s: could not restore socket timeout",
909                          __func__);
910            return -1;
911        }
912    }
913
914    if (rv == APR_SUCCESS)
915        return 1;
916    else if (APR_STATUS_IS_TIMEUP(rv))
917        return 0;
918    else
919    {
920        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctxt->c,
921                      "%s: waiting for data on connection socket failed",
922                      __func__);
923        return -1;
924    }
925}
926
927/**
928 * Pull function for GnuTLS
929 *
930 * Generic errnos used for `gnutls_transport_set_errno()`:
931 * * `EAGAIN`: no data available at the moment, try again (maybe later)
932 * * `EINTR`: read was interrupted, try again
933 * * `EIO`: Unknown I/O error
934 * * `ECONNABORTED`: Input BB does not exist (`NULL`)
935 *
936 * The reason we are not using `APR_TO_OS_ERROR` to map `apr_status_t`
937 * to errnos is this warning [in the APR documentation][apr-warn]:
938 *
939 * > If the statcode was not created by apr_get_os_error or
940 * > APR_FROM_OS_ERROR, the results are undefined.
941 *
942 * We cannot know if this applies to any error we might encounter.
943 *
944 * @param ptr GnuTLS session data pointer (the mod_gnutls context
945 * structure)
946 *
947 * @param buffer buffer for the read data
948 *
949 * @param len maximum number of bytes to read (must fit into the
950 * buffer)
951 *
952 * @return The number of bytes read (may be zero on EOF), or `-1` on
953 * error. Note that some errors may warrant another try (see above).
954 *
955 * [apr-warn]: https://apr.apache.org/docs/apr/1.4/group__apr__errno.html#ga2385cae04b04afbdcb65f1a45c4d8506 "Apache Portable Runtime: Error Codes"
956 */
957ssize_t mgs_transport_read(gnutls_transport_ptr_t ptr,
958                           void *buffer, size_t len)
959{
960    mgs_handle_t *ctxt = ptr;
961    apr_status_t rc;
962    apr_size_t in = len;
963    apr_read_type_e block = ctxt->input_block;
964
965    ctxt->input_rc = APR_SUCCESS;
966
967    /* If Len = 0, we don't do anything. */
968    if (!len || buffer == NULL)
969    {
970        return 0;
971    }
972    /* Input bucket brigade is missing, EOF */
973    if (!ctxt->input_bb)
974    {
975        ctxt->input_rc = APR_EOF;
976        gnutls_transport_set_errno(ctxt->session, ECONNABORTED);
977        return -1;
978    }
979
980    if (APR_BRIGADE_EMPTY(ctxt->input_bb))
981    {
982        rc = ap_get_brigade(ctxt->input_filter->next,
983                            ctxt->input_bb, AP_MODE_READBYTES,
984                            ctxt->input_block, in);
985
986        /* Not a problem, there was simply no data ready yet.
987         */
988        if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
989            || (rc == APR_SUCCESS
990                && APR_BRIGADE_EMPTY(ctxt->input_bb)))
991        {
992            /* Turning APR_SUCCESS into APR_EINTR isn't ideal, but
993             * it's the best matching error code for "didn't get data,
994             * but read didn't permanently fail either." */
995            ctxt->input_rc = (rc != APR_SUCCESS ? rc : APR_EINTR);
996            gnutls_transport_set_errno(ctxt->session,
997                                       EAI_APR_TO_RAW(ctxt->input_rc));
998            return -1;
999        }
1000
1001        /* Blocking ap_get_brigade() can return a timeout status,
1002         * sometimes after a very short time. "Don't give up, just
1003         * return the timeout" is what mod_ssl does. */
1004        if (ctxt->input_block == APR_BLOCK_READ
1005            && APR_STATUS_IS_TIMEUP(rc)
1006            && APR_BRIGADE_EMPTY(ctxt->input_bb))
1007        {
1008            ctxt->input_rc = rc;
1009            gnutls_transport_set_errno(ctxt->session, EAGAIN);
1010            return -1;
1011        }
1012
1013        if (rc != APR_SUCCESS)
1014        {
1015            /* Unexpected errors discard the brigade */
1016            ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, ctxt->c,
1017                          "%s: Unexpected error!", __func__);
1018            apr_brigade_cleanup(ctxt->input_bb);
1019            ctxt->input_bb = NULL;
1020            gnutls_transport_set_errno(ctxt->session, EIO);
1021            return -1;
1022        }
1023    }
1024
1025    ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len);
1026
1027    if (ctxt->input_rc == APR_SUCCESS)
1028    {
1029        return (ssize_t) len;
1030    }
1031
1032    if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
1033        || APR_STATUS_IS_EINTR(ctxt->input_rc))
1034    {
1035        if (len == 0)
1036        {
1037            gnutls_transport_set_errno(ctxt->session,
1038                                       EAI_APR_TO_RAW(ctxt->input_rc));
1039            return -1;
1040        }
1041
1042        return (ssize_t) len;
1043    }
1044
1045    /* Unexpected errors and APR_EOF clean out the brigade.
1046     * Subsequent calls will return APR_EOF. */
1047    apr_brigade_cleanup(ctxt->input_bb);
1048    ctxt->input_bb = NULL;
1049
1050    if (APR_STATUS_IS_EOF(ctxt->input_rc) && len)
1051    {
1052        /* Some data has been received before EOF, return it. */
1053        return (ssize_t) len;
1054    }
1055
1056    gnutls_transport_set_errno(ctxt->session, EIO);
1057    return -1;
1058}
1059
1060/**
1061 * Push function for GnuTLS
1062 *
1063 * `gnutls_transport_set_errno()` will be called with `EAGAIN` or
1064 * `EINTR` on recoverable errors, or `EIO` in case of unexpected
1065 * errors. See the description of mgs_transport_read() for details on
1066 * possible error codes.
1067 *
1068 * @param ptr GnuTLS session data pointer (the mod_gnutls context
1069 * structure)
1070 *
1071 * @param buffer buffer containing the data to send
1072 *
1073 * @param len length of the data
1074 * buffer)
1075 *
1076 * @return The number of written bytes, or `-1` on error. Note that
1077 * some errors may warrant another try (see above).
1078 */
1079ssize_t mgs_transport_write(gnutls_transport_ptr_t ptr,
1080                            const void *buffer, size_t len)
1081{
1082    mgs_handle_t *ctxt = ptr;
1083
1084    /* pass along the encrypted data
1085     * need to flush since we're using SSL's malloc-ed buffer
1086     * which will be overwritten once we leave here
1087     */
1088    apr_bucket *bucket = apr_bucket_transient_create(buffer, len,
1089            ctxt->output_bb->
1090            bucket_alloc);
1091    ctxt->output_length += len;
1092    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket);
1093
1094    if (write_flush(ctxt) < 0)
1095    {
1096        /* We encountered an error. APR_EINTR or APR_EAGAIN can be
1097         * handled, treat everything else as a generic I/O error. */
1098        int err = EIO;
1099        if (APR_STATUS_IS_EAGAIN(ctxt->output_rc)
1100            || APR_STATUS_IS_EINTR(ctxt->output_rc))
1101            err = EAI_APR_TO_RAW(ctxt->output_rc);
1102
1103        gnutls_transport_set_errno(ctxt->session, err);
1104        return -1;
1105    }
1106    return len;
1107}
Note: See TracBrowser for help on using the repository browser.