source: mod_gnutls/src/gnutls_io.c @ 154db29

debian/masterdebian/stretch-backportsproxy-ticketupstream mod_gnutls/0.8.1
Last change on this file since 154db29 was 08b821a, checked in by Thomas Klute <thomas2.klute@…>, 4 years ago

gnutls_io.c: API documentation

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