source: mod_gnutls/src/gnutls_io.c @ 3c6645b

asynciodebian/masterproxy-ticket
Last change on this file since 3c6645b was bac1a32, checked in by Fiona Klute <fiona.klute@…>, 2 years ago

Order, log message, and style fixes

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