source: mod_gnutls/src/gnutls_io.c @ f674424

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

First prototype of proxy ALPN support

The current code assumes that the "proxy-request-alpn-protos"
connection note will always contain exactly one protocol if present,
which is what mod_proxy_http2 does.

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