source: mod_gnutls/src/gnutls_io.c @ 0378c22

debian/master
Last change on this file since 0378c22 was 0378c22, checked in by Fiona Klute <fiona.klute@…>, 16 months ago

Move mod_proxy note name definitions to gnutls_proxy.h

  • Property mode set to 100644
File size: 33.0 KB
RevLine 
[104e881]1/*
[fcb122d]2 *  Copyright 2004-2005 Paul Querna
[e183628]3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
[bac1a32]5 *  Copyright 2015-2018 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"
[0378c22]21#include "gnutls_proxy.h"
[f674424]22#include <apr_strings.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
[dae0aec]39#define HTTP_ON_HTTPS_PORT \
40    "GET /" CRLF
[7e2b223]41
[dae0aec]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)
[7e2b223]46
[265eafc]47#define IS_PROXY_STR(c) \
48    ((c->is_proxy == GNUTLS_ENABLED_TRUE) ? "proxy " : "")
49
[02a6a18]50/**
[08b821a]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.
[02a6a18]53 */
54#define EAI_APR_TO_RAW(s) (APR_STATUS_IS_EAGAIN(s) ? EAGAIN : EINTR)
55
56
57
[dae0aec]58static apr_status_t gnutls_io_filter_error(ap_filter_t * f,
[e183628]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) {
[4fefa39]65    case HTTP_BAD_REQUEST:
66        /* log the situation */
[7511bfa]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");
[4fefa39]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;
[e183628]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;
[dae0aec]85}
[7e2b223]86
[e183628]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;
[dae0aec]106}
107
[e183628]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;
[7e2b223]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,
[e183628]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        }
[7e2b223]186
[e183628]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}
[7e2b223]197
[c301152]198static apr_status_t gnutls_io_input_read(mgs_handle_t * ctxt,
[398d1a0]199        char *buf, apr_size_t * len)
200{
[e183628]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) {
[398d1a0]239        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
240                      "%s: GnuTLS session is NULL!", __func__);
[e183628]241        return APR_EGENERAL;
242    }
243
[f5a36ee]244    while (1)
245    {
[bd2b48b]246        /* Note: The pull function sets ctxt->input_rc */
[f5a36ee]247        rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes);
[e183628]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                        rc);
255            }
256            return ctxt->input_rc;
257        } else if (rc == 0) {
[bd2b48b]258            /* EOF, return code depends on whether we still have data
259             * to return. */
260            if (*len > 0) {
261                ctxt->input_rc = APR_SUCCESS;
[e183628]262            } else {
[bd2b48b]263                ctxt->input_rc = APR_EOF;
[e183628]264            }
[bd2b48b]265            break;
[e183628]266        } else { /* (rc < 0) */
267
[bd2b48b]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);
[2038b76]273                /* For a blocking read, loop and try again
274                 * immediately. Otherwise just notify the caller. */
[bd2b48b]275                if (ctxt->input_block != APR_NONBLOCK_READ)
276                    continue;
[2038b76]277                else
278                    ctxt->input_rc =
279                        (rc == GNUTLS_E_AGAIN ? APR_EAGAIN : APR_EINTR);
[bd2b48b]280            } else if (rc == GNUTLS_E_REHANDSHAKE) {
[e183628]281                /* A client has asked for a new Hankshake. Currently, we don't do it */
[398d1a0]282                ap_log_cerror(APLOG_MARK, APLOG_INFO,
[e183628]283                        ctxt->input_rc,
[398d1a0]284                        ctxt->c,
[e183628]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);
[398d1a0]290                ap_log_cerror(APLOG_MARK, APLOG_INFO,
[e183628]291                        ctxt->input_rc,
[398d1a0]292                        ctxt->c,
[e183628]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);
[398d1a0]298                ap_log_cerror(APLOG_MARK, APLOG_INFO,
[e183628]299                        ctxt->input_rc,
[398d1a0]300                        ctxt->c,
[e183628]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)) {
[398d1a0]309                    ap_log_cerror(APLOG_MARK,
[e183628]310                            APLOG_INFO,
311                            ctxt->input_rc,
[398d1a0]312                            ctxt->c,
[e183628]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) {
[4261999]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);
[e183628]326                ctxt->input_rc = APR_EGENERAL;
327            }
328            break;
329        }
330    }
331    return ctxt->input_rc;
[dae0aec]332}
333
[c301152]334static apr_status_t gnutls_io_input_getline(mgs_handle_t * ctxt,
[e183628]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;
[dae0aec]339
[e183628]340    *len = 0;
[dae0aec]341
[e183628]342    while (tmplen > 0) {
343        status = gnutls_io_input_read(ctxt, buf + offset, &tmplen);
[dae0aec]344
[e183628]345        if (status != APR_SUCCESS) {
346            return status;
347        }
[dae0aec]348
[e183628]349        *len += tmplen;
[dae0aec]350
[e183628]351        if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
352            break;
353        }
[dae0aec]354
[e183628]355        offset += tmplen;
356        tmplen = buflen - offset;
357    }
[dae0aec]358
[e183628]359    if (pos) {
360        char *value;
361        int length;
362        apr_size_t bytes = pos - buf;
[dae0aec]363
[e183628]364        bytes += 1;
365        value = buf + bytes;
366        length = *len - bytes;
[dae0aec]367
[e183628]368        char_buffer_write(&ctxt->input_cbuf, value, length);
[dae0aec]369
[e183628]370        *len = bytes;
371    }
[dae0aec]372
[e183628]373    return APR_SUCCESS;
[dae0aec]374}
375
[0106b25]376#define HANDSHAKE_MAX_TRIES 1024
[e183628]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
[265159d]387    /* Enable SNI for proxy connections */
388    if (ctxt->is_proxy == GNUTLS_ENABLED_TRUE)
389    {
390        /* Get peer hostname from note left by mod_proxy */
391        const char *peer_hostname =
392            apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
393        /* Used only as target for apr_ipsubnet_create() */
394        apr_ipsubnet_t *probe;
395        /* Check if the note is present (!= NULL) and NOT an IP
396         * address */
397        if ((peer_hostname) != NULL
398            && (apr_ipsubnet_create(&probe, peer_hostname, NULL, ctxt->c->pool)
399                != APR_SUCCESS))
400        {
401            ret = gnutls_server_name_set(ctxt->session, GNUTLS_NAME_DNS,
402                                         peer_hostname, strlen(peer_hostname));
403            if (ret != GNUTLS_E_SUCCESS)
404                ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
405                              "Could not set SNI '%s' for proxy connection: "
406                              "%s (%d)",
407                              peer_hostname, gnutls_strerror(ret), ret);
408        }
[f674424]409
410        const char *proxy_alpn =
[0378c22]411            apr_table_get(ctxt->c->notes, PROXY_ALPN_NOTE);
[f674424]412        if (proxy_alpn != NULL)
413        {
414            // TODO: mod_ssl ssl_engine_io.c does some tokenization of
415            // the input string, so it looks like the API allows
416            // multiple protocols.
417            gnutls_datum_t alpn_proto = {
418                .data = (unsigned char *) apr_pstrdup(ctxt->c->pool, proxy_alpn),
419                .size = strlen(proxy_alpn)
420            };
421            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
422                          "%s: proxy module requests ALPN proto '%s', "
423                          "length %" APR_SIZE_T_FMT ".",
424                          __func__, proxy_alpn, strlen(proxy_alpn));
425            ret = gnutls_alpn_set_protocols(ctxt->session,
426                                            &alpn_proto,
427                                            1 /* number of proposals */,
428                                            0 /* flags */);
429            if (ret != GNUTLS_E_SUCCESS)
430                ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
431                              "Could not set ALPN proposal '%s' for proxy "
432                              "connection: %s (%d)",
433                              proxy_alpn, gnutls_strerror(ret), ret);
434        }
[265159d]435    }
436
[e183628]437tryagain:
438    do {
439        ret = gnutls_handshake(ctxt->session);
440        maxtries--;
441    } while ((ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
442            && maxtries > 0);
443
444    if (maxtries < 1) {
445        ctxt->status = -1;
446        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
447                "GnuTLS: Handshake Failed. Hit Maximum Attempts");
448        if (ctxt->session) {
449            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
450                    gnutls_error_to_alert
451                    (GNUTLS_E_INTERNAL_ERROR, NULL));
452            gnutls_deinit(ctxt->session);
453        }
454        ctxt->session = NULL;
455        return -1;
456    }
457
458    if (ret < 0) {
459        if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED
460                || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
461            errcode = gnutls_alert_get(ctxt->session);
[7511bfa]462            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
463                          "GnuTLS: Handshake Alert (%d) '%s'.",
464                          errcode, gnutls_alert_get_name(errcode));
[e183628]465        }
466
467        if (!gnutls_error_is_fatal(ret)) {
[7511bfa]468            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
469                          "GnuTLS: Non-Fatal Handshake Error: (%d) '%s'",
470                          ret, gnutls_strerror(ret));
[e183628]471            goto tryagain;
472        }
473        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
474                "GnuTLS: Handshake Failed (%d) '%s'", ret,
475                gnutls_strerror(ret));
476        ctxt->status = -1;
477        if (ctxt->session) {
478            gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
479                    gnutls_error_to_alert(ret,
480                    NULL));
481            gnutls_deinit(ctxt->session);
482        }
483        ctxt->session = NULL;
484        return ret;
485    } else {
486        /* all done with the handshake */
487        ctxt->status = 1;
[cebb74a]488        if (gnutls_session_is_resumed(ctxt->session))
489        {
490            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
491                          "%s: TLS session resumed.", __func__);
[e183628]492        }
[73f6f12]493        return GNUTLS_E_SUCCESS;
[e183628]494    }
[31645b2]495}
496
[e183628]497int mgs_rehandshake(mgs_handle_t * ctxt) {
498    int rv;
[e02dd8c]499
[e183628]500    if (ctxt->session == NULL)
501        return -1;
[e02dd8c]502
[e183628]503    rv = gnutls_rehandshake(ctxt->session);
[e02dd8c]504
[e183628]505    if (rv != 0) {
506        /* the client did not want to rehandshake. goodbye */
[7511bfa]507        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
508                      "GnuTLS: Client Refused Rehandshake request.");
[e183628]509        return -1;
510    }
[e02dd8c]511
[e183628]512    ctxt->status = 0;
[e02dd8c]513
[e183628]514    rv = gnutls_do_handshake(ctxt);
[e02dd8c]515
[e183628]516    return rv;
[dae0aec]517}
518
[401a0de]519
520
521/**
522 * Close the TLS session associated with the given connection
523 * structure and free its resources
[08b821a]524 *
525 * @param ctxt the mod_gnutls session context
526 *
527 * @return a GnuTLS status code, hopefully `GNUTLS_E_SUCCESS`
[401a0de]528 */
529static int mgs_bye(mgs_handle_t* ctxt)
530{
531    int ret = GNUTLS_E_SUCCESS;
532    /* End Of Connection */
533    if (ctxt->session != NULL)
534    {
535        /* Try A Clean Shutdown */
536        do {
537            ret = gnutls_bye(ctxt->session, GNUTLS_SHUT_WR);
538        } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
539        if (ret != GNUTLS_E_SUCCESS)
[2ceb836]540            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
[401a0de]541                          "%s: Error while closing TLS %sconnection: "
542                          "'%s' (%d)",
543                          __func__, IS_PROXY_STR(ctxt),
544                          gnutls_strerror(ret), (int) ret);
545        else
[2ceb836]546            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
[401a0de]547                          "%s: TLS %sconnection closed.",
548                          __func__, IS_PROXY_STR(ctxt));
549        /* De-Initialize Session */
550        gnutls_deinit(ctxt->session);
551        ctxt->session = NULL;
552    }
553    return ret;
554}
555
556
557
[e02dd8c]558apr_status_t mgs_filter_input(ap_filter_t * f,
[e183628]559        apr_bucket_brigade * bb,
560        ap_input_mode_t mode,
[265eafc]561        apr_read_type_e block, apr_off_t readbytes)
562{
[e183628]563    apr_status_t status = APR_SUCCESS;
564    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
565    apr_size_t len = sizeof (ctxt->input_buffer);
566
567    if (f->c->aborted) {
568        apr_bucket *bucket =
569                apr_bucket_eos_create(f->c->bucket_alloc);
570        APR_BRIGADE_INSERT_TAIL(bb, bucket);
[265eafc]571        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
572                      "%s: %sconnection aborted",
573                      __func__, IS_PROXY_STR(ctxt));
[e183628]574        return APR_ECONNABORTED;
575    }
576
577    if (ctxt->status == 0) {
[73f6f12]578        int ret = gnutls_do_handshake(ctxt);
579        if (ret == GNUTLS_E_SUCCESS)
580            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
581                          "%s: TLS %sconnection opened.",
582                          __func__, IS_PROXY_STR(ctxt));
[e183628]583    }
584
[72b669e]585    if (ctxt->status < 0)
586    {
587        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
588                      "%s: %sconnection failed, cannot provide data!",
589                      __func__, IS_PROXY_STR(ctxt));
590        apr_bucket *bucket =
591                apr_bucket_eos_create(f->c->bucket_alloc);
592        APR_BRIGADE_INSERT_TAIL(bb, bucket);
593        return APR_ECONNABORTED;
[e183628]594    }
595
596    /* XXX: we don't currently support anything other than these modes. */
597    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
598            mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
599        return APR_ENOTIMPL;
600    }
601
602    ctxt->input_mode = mode;
603    ctxt->input_block = block;
604
605    if (ctxt->input_mode == AP_MODE_READBYTES ||
606            ctxt->input_mode == AP_MODE_SPECULATIVE) {
[fd82e59]607        if (readbytes < 0) {
608            /* you're asking us to speculatively read a negative number of bytes! */
609            return APR_ENOTIMPL;
610        }
[b2e6406]611        /* 'readbytes' and 'len' are of different integer types, which
612         * might have different lengths. Read sizes should be too
613         * small for 32 or 64 bit to matter, but we have to make
614         * sure. */
[ac3f500]615#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
616        if ((apr_size_t) readbytes < len)
617        {
618            /* If readbytes is negative the function fails in the
619             * check above, but the compiler doesn't get that. */
620            if (__builtin_expect(imaxabs(readbytes) > SIZE_MAX, 0))
621            {
622                ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_EINVAL, ctxt->c,
623                              "%s: prevented buffer length overflow",
624                              __func__);
625                return APR_EINVAL;
626            }
627            len = (apr_size_t) readbytes;
628        }
629#else
[b2e6406]630        if ((apr_size_t) readbytes < len
631            && __builtin_add_overflow(readbytes, 0, &len))
632        {
633            ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_EINVAL, ctxt->c,
634                          "%s: prevented buffer length overflow",
635                          __func__);
636            return APR_EINVAL;
[e183628]637        }
[ac3f500]638#endif
[e183628]639        status =
640                gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
641    } else if (ctxt->input_mode == AP_MODE_GETLINE) {
642        status =
643                gnutls_io_input_getline(ctxt, ctxt->input_buffer,
644                &len);
645    } else {
646        /* We have no idea what you are talking about, so return an error. */
647        return APR_ENOTIMPL;
648    }
649
[f5a36ee]650    if (status != APR_SUCCESS)
651    {
652        /* no data for nonblocking read, return APR_EAGAIN */
[73b0bf0]653        if ((block == APR_NONBLOCK_READ) && APR_STATUS_IS_EINTR(status))
[f5a36ee]654            return APR_EAGAIN;
655
[401a0de]656        /* Close TLS session and free resources on EOF,
657         * gnutls_io_filter_error will add an EOS bucket */
[73b0bf0]658        if (APR_STATUS_IS_EOF(status))
[401a0de]659            mgs_bye(ctxt);
660
[e183628]661        return gnutls_io_filter_error(f, bb, status);
662    }
663
664    /* Create a transient bucket out of the decrypted data. */
665    if (len > 0) {
666        apr_bucket *bucket =
667                apr_bucket_transient_create(ctxt->input_buffer, len,
668                f->c->bucket_alloc);
669        APR_BRIGADE_INSERT_TAIL(bb, bucket);
670    }
671
672    return status;
[dae0aec]673}
674
[08b821a]675/**
676 * Try to flush the output bucket brigade.
677 *
678 * @param ctxt the mod_gnutls session context
679 *
680 * @return `1` on success, `-1` on failure.
681 */
[e183628]682static ssize_t write_flush(mgs_handle_t * ctxt) {
683    apr_bucket *e;
684
685    if (!(ctxt->output_blen || ctxt->output_length)) {
686        ctxt->output_rc = APR_SUCCESS;
687        return 1;
688    }
689
690    if (ctxt->output_blen) {
691        e = apr_bucket_transient_create(ctxt->output_buffer,
692                ctxt->output_blen,
693                ctxt->output_bb->
694                bucket_alloc);
695        /* we filled this buffer first so add it to the
696         *               * head of the brigade
697         *                               */
698        APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e);
699        ctxt->output_blen = 0;
700    }
701
702    ctxt->output_length = 0;
703    e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc);
704    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
705
706    ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next,
707            ctxt->output_bb);
708    /* clear the brigade to be ready for next time */
709    apr_brigade_cleanup(ctxt->output_bb);
710
711    return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1;
[485d28e]712}
713
[e183628]714apr_status_t mgs_filter_output(ap_filter_t * f, apr_bucket_brigade * bb) {
[fd82e59]715    int ret;
[e183628]716    mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
717    apr_status_t status = APR_SUCCESS;
718    apr_read_type_e rblock = APR_NONBLOCK_READ;
[671b64f]719
[e183628]720    if (f->c->aborted) {
721        apr_brigade_cleanup(bb);
722        return APR_ECONNABORTED;
723    }
724
725    if (ctxt->status == 0) {
[73f6f12]726        ret = gnutls_do_handshake(ctxt);
727        if (ret == GNUTLS_E_SUCCESS)
728            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
729                          "%s: TLS %sconnection opened.",
730                          __func__, IS_PROXY_STR(ctxt));
[e183628]731    }
732
[72b669e]733    if (ctxt->status < 0)
734    {
735        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
736                      "%s: %sconnection failed, refusing to send.",
737                      __func__, IS_PROXY_STR(ctxt));
738        if (ctxt->is_proxy)
739        {
740            /* If mod_proxy receives an error while trying to send its
741             * request it sends an "invalid request" error to the
742             * client. By pretending we could send the request
743             * mod_proxy continues its processing and sends a proper
744             * "proxy error" message when there's no response to read. */
745            apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
746            APR_BRIGADE_INSERT_TAIL(bb, bucket);
747            return APR_SUCCESS;
748        }
749        else
750            return APR_ECONNABORTED;
[e183628]751    }
752
753    while (!APR_BRIGADE_EMPTY(bb)) {
754        apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
755
756        if (APR_BUCKET_IS_EOS(bucket)) {
757            return ap_pass_brigade(f->next, bb);
[671b64f]758        } else if (APR_BUCKET_IS_FLUSH(bucket)) {
[e183628]759            /* Try Flush */
760            if (write_flush(ctxt) < 0) {
761                /* Flush Error */
762                return ctxt->output_rc;
763            }
764            /* cleanup! */
[671b64f]765            apr_bucket_delete(bucket);
[e183628]766        } else if (AP_BUCKET_IS_EOC(bucket)) {
[401a0de]767            /* End Of Connection, close TLS session and free
768             * resources */
769            mgs_bye(ctxt);
[9a9bc1e]770            /* cleanup! */
[671b64f]771            apr_bucket_delete(bucket);
[e183628]772            /* Pass next brigade! */
773            return ap_pass_brigade(f->next, bb);
774        } else {
775            /* filter output */
776            const char *data;
777            apr_size_t len;
778
[9a9bc1e]779            status = apr_bucket_read(bucket, &data, &len, rblock);
[e183628]780
781            if (APR_STATUS_IS_EAGAIN(status)) {
782                /* No data available so Flush! */
783                if (write_flush(ctxt) < 0) {
784                    return ctxt->output_rc;
785                }
786                /* Try again with a blocking read. */
787                rblock = APR_BLOCK_READ;
788                continue;
789            }
790
791            rblock = APR_NONBLOCK_READ;
792
793            if (!APR_STATUS_IS_EOF(status)
794                    && (status != APR_SUCCESS)) {
795                return status;
796            }
797
798            if (len > 0) {
799
800                if (ctxt->session == NULL) {
801                    ret = GNUTLS_E_INVALID_REQUEST;
[b4a875b]802                } else {
[e183628]803                    do {
804                        ret =
805                                gnutls_record_send
806                                (ctxt->session, data,
807                                len);
808                    } while (ret == GNUTLS_E_INTERRUPTED
809                            || ret == GNUTLS_E_AGAIN);
810                }
811
812                if (ret < 0) {
813                    /* error sending output */
[7511bfa]814                    ap_log_cerror(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
815                                  ctxt->c,
816                                  "GnuTLS: Error writing data. (%d) '%s'",
817                                  ret, gnutls_strerror(ret));
[e183628]818                    if (ctxt->output_rc == APR_SUCCESS) {
819                        ctxt->output_rc =
820                                APR_EGENERAL;
821                        return ctxt->output_rc;
822                    }
[fd82e59]823                } else if ((apr_size_t)(ret) != len) {
824                    /* we know the above cast is OK because len > 0 and ret >= 0 */
[671b64f]825                    /* Not able to send the entire bucket,
[e183628]826                       split it and send it again. */
827                    apr_bucket_split(bucket, ret);
828                }
829            }
830
831            apr_bucket_delete(bucket);
832        }
833    }
834
835    return status;
[dae0aec]836}
837
[02a6a18]838/**
839 * Pull function for GnuTLS
[a9fa300]840 *
[08b821a]841 * Generic errnos used for `gnutls_transport_set_errno()`:
842 * * `EAGAIN`: no data available at the moment, try again (maybe later)
843 * * `EINTR`: read was interrupted, try again
844 * * `EIO`: Unknown I/O error
845 * * `ECONNABORTED`: Input BB does not exist (`NULL`)
846 *
847 * The reason we are not using `APR_TO_OS_ERROR` to map `apr_status_t`
848 * to errnos is this warning [in the APR documentation][apr-warn]:
849 *
850 * > If the statcode was not created by apr_get_os_error or
851 * > APR_FROM_OS_ERROR, the results are undefined.
852 *
853 * We cannot know if this applies to any error we might encounter.
[a9fa300]854 *
[08b821a]855 * @param ptr GnuTLS session data pointer (the mod_gnutls context
856 * structure)
857 *
858 * @param buffer buffer for the read data
859 *
860 * @param len maximum number of bytes to read (must fit into the
861 * buffer)
862 *
863 * @return The number of bytes read (may be zero on EOF), or `-1` on
864 * error. Note that some errors may warrant another try (see above).
865 *
866 * [apr-warn]: https://apr.apache.org/docs/apr/1.4/group__apr__errno.html#ga2385cae04b04afbdcb65f1a45c4d8506 "Apache Portable Runtime: Error Codes"
[02a6a18]867 */
[c301152]868ssize_t mgs_transport_read(gnutls_transport_ptr_t ptr,
[02a6a18]869                           void *buffer, size_t len)
870{
[e183628]871    mgs_handle_t *ctxt = ptr;
872    apr_status_t rc;
873    apr_size_t in = len;
874    apr_read_type_e block = ctxt->input_block;
875
876    ctxt->input_rc = APR_SUCCESS;
877
878    /* If Len = 0, we don't do anything. */
[19f2719]879    if (!len || buffer == NULL)
880    {
[e183628]881        return 0;
882    }
[19f2719]883    /* Input bucket brigade is missing, EOF */
884    if (!ctxt->input_bb)
885    {
[e183628]886        ctxt->input_rc = APR_EOF;
[a9fa300]887        gnutls_transport_set_errno(ctxt->session, ECONNABORTED);
[e183628]888        return -1;
889    }
890
[19f2719]891    if (APR_BRIGADE_EMPTY(ctxt->input_bb))
892    {
[e183628]893        rc = ap_get_brigade(ctxt->input_filter->next,
[19f2719]894                            ctxt->input_bb, AP_MODE_READBYTES,
895                            ctxt->input_block, in);
[e183628]896
897        /* Not a problem, there was simply no data ready yet.
898         */
899        if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
[02a6a18]900            || (rc == APR_SUCCESS
901                && APR_BRIGADE_EMPTY(ctxt->input_bb)))
902        {
[bd2b48b]903            /* Turning APR_SUCCESS into APR_EINTR isn't ideal, but
904             * it's the best matching error code for "didn't get data,
905             * but read didn't permanently fail either." */
906            ctxt->input_rc = (rc != APR_SUCCESS ? rc : APR_EINTR);
907            gnutls_transport_set_errno(ctxt->session,
908                                       EAI_APR_TO_RAW(ctxt->input_rc));
909            return -1;
[e183628]910        }
[e02dd8c]911
[92cb0cc]912        /* Blocking ap_get_brigade() can return a timeout status,
913         * sometimes after a very short time. "Don't give up, just
914         * return the timeout" is what mod_ssl does. */
915        if (ctxt->input_block == APR_BLOCK_READ
916            && APR_STATUS_IS_TIMEUP(rc)
917            && APR_BRIGADE_EMPTY(ctxt->input_bb))
918        {
919            ctxt->input_rc = rc;
920            gnutls_transport_set_errno(ctxt->session, EAGAIN);
921            return -1;
922        }
923
[19f2719]924        if (rc != APR_SUCCESS)
925        {
[e183628]926            /* Unexpected errors discard the brigade */
[92cb0cc]927            ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, ctxt->c,
928                          "%s: Unexpected error!", __func__);
[e183628]929            apr_brigade_cleanup(ctxt->input_bb);
930            ctxt->input_bb = NULL;
[a9fa300]931            gnutls_transport_set_errno(ctxt->session, EIO);
[e183628]932            return -1;
933        }
934    }
935
[19f2719]936    ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len);
[e183628]937
[19f2719]938    if (ctxt->input_rc == APR_SUCCESS)
939    {
[e183628]940        return (ssize_t) len;
941    }
942
943    if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
[02a6a18]944        || APR_STATUS_IS_EINTR(ctxt->input_rc))
945    {
946        if (len == 0)
947        {
[6868585]948            gnutls_transport_set_errno(ctxt->session,
949                                       EAI_APR_TO_RAW(ctxt->input_rc));
[e183628]950            return -1;
[60cf11c]951        }
[e183628]952
953        return (ssize_t) len;
954    }
955
956    /* Unexpected errors and APR_EOF clean out the brigade.
[19f2719]957     * Subsequent calls will return APR_EOF. */
[e183628]958    apr_brigade_cleanup(ctxt->input_bb);
959    ctxt->input_bb = NULL;
960
[19f2719]961    if (APR_STATUS_IS_EOF(ctxt->input_rc) && len)
962    {
963        /* Some data has been received before EOF, return it. */
[e183628]964        return (ssize_t) len;
965    }
966
[a9fa300]967    gnutls_transport_set_errno(ctxt->session, EIO);
[e183628]968    return -1;
[dae0aec]969}
970
[a9fa300]971/**
972 * Push function for GnuTLS
973 *
[08b821a]974 * `gnutls_transport_set_errno()` will be called with `EAGAIN` or
975 * `EINTR` on recoverable errors, or `EIO` in case of unexpected
976 * errors. See the description of mgs_transport_read() for details on
977 * possible error codes.
978 *
979 * @param ptr GnuTLS session data pointer (the mod_gnutls context
980 * structure)
981 *
982 * @param buffer buffer containing the data to send
983 *
984 * @param len length of the data
985 * buffer)
986 *
987 * @return The number of written bytes, or `-1` on error. Note that
988 * some errors may warrant another try (see above).
[a9fa300]989 */
[c301152]990ssize_t mgs_transport_write(gnutls_transport_ptr_t ptr,
[19f2719]991                            const void *buffer, size_t len)
992{
[e183628]993    mgs_handle_t *ctxt = ptr;
994
995    /* pass along the encrypted data
996     * need to flush since we're using SSL's malloc-ed buffer
997     * which will be overwritten once we leave here
998     */
999    apr_bucket *bucket = apr_bucket_transient_create(buffer, len,
1000            ctxt->output_bb->
1001            bucket_alloc);
1002    ctxt->output_length += len;
1003    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket);
1004
[be41ee4]1005    if (write_flush(ctxt) < 0)
1006    {
1007        /* We encountered an error. APR_EINTR or APR_EAGAIN can be
1008         * handled, treat everything else as a generic I/O error. */
1009        int err = EIO;
1010        if (APR_STATUS_IS_EAGAIN(ctxt->output_rc)
1011            || APR_STATUS_IS_EINTR(ctxt->output_rc))
1012            err = EAI_APR_TO_RAW(ctxt->output_rc);
1013
1014        gnutls_transport_set_errno(ctxt->session, err);
[e183628]1015        return -1;
1016    }
1017    return len;
[7e2b223]1018}
Note: See TracBrowser for help on using the repository browser.