source: mod_gnutls/src/gnutls_io.c @ 272833c

Last change on this file since 272833c was 272833c, checked in by Fiona Klute <fiona.klute@…>, 2 months ago

Adjust log level for read error messages

These are most likely caused by clients and useful for debugging, but
shouldn't show up in regular operation.

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