source: mod_gnutls/src/gnutls_io.c @ 7e29705

asyncio
Last change on this file since 7e29705 was 132eadc, checked in by Fiona Klute <fiona.klute@…>, 6 months ago

Implement gnutls_pull_timeout_func

According to GnuTLS documentation it's required when using a custom
pull function. It's not called by the current mod_gnutls use of
GnuTLS, but the documentation on client-side resumption explicitly
mentions it as a requirement.

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