source: mod_gnutls/src/gnutls_io.c

asyncio
Last change on this file was 8da0c7e, checked in by Fiona Klute <fiona.klute@…>, 7 weeks ago

Limit variable scopes in gnutls_io.c

This fixes issues found using CppCheck?.

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