source: mod_gnutls/src/gnutls_io.c @ dae0aec

debian/masterdebian/stretch-backportsjessie-backportsmsvaupstream
Last change on this file since dae0aec was dae0aec, checked in by Paul Querna <chip@…>, 15 years ago

input and output filters

  • Property mode set to 100644
File size: 20.3 KB
Line 
1/* ====================================================================
2 *  Copyright 2004 Paul Querna
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 *
16 */
17
18#include "mod_gnutls.h"
19
20/**
21 * Describe how the GnuTLS Filter system works here
22 *  - Basicly the same as what mod_ssl does with OpenSSL.
23 *
24 */
25
26#define HTTP_ON_HTTPS_PORT \
27    "GET /" CRLF
28
29#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
30    apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
31                               sizeof(HTTP_ON_HTTPS_PORT) - 1, \
32                               alloc)
33
34static apr_status_t gnutls_io_filter_error(ap_filter_t * f,
35                                           apr_bucket_brigade * bb,
36                                           apr_status_t status)
37{
38    mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx;
39    apr_bucket *bucket;
40
41    switch (status) {
42    case HTTP_BAD_REQUEST:
43        /* log the situation */
44        ap_log_error(APLOG_MARK, APLOG_INFO, 0,
45                     f->c->base_server,
46                     "GnuTLS handshake failed: HTTP spoken on HTTPS port; "
47                     "trying to send HTML error page");
48
49        ctxt->status = -1;
50
51        /* fake the request line */
52        bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
53        break;
54
55    default:
56        return status;
57    }
58
59    APR_BRIGADE_INSERT_TAIL(bb, bucket);
60    bucket = apr_bucket_eos_create(f->c->bucket_alloc);
61    APR_BRIGADE_INSERT_TAIL(bb, bucket);
62
63    return APR_SUCCESS;
64}
65
66static int char_buffer_read(mod_gnutls_char_buffer_t * buffer, char *in,
67                            int inl)
68{
69    if (!buffer->length) {
70        return 0;
71    }
72
73    if (buffer->length > inl) {
74        /* we have have enough to fill the caller's buffer */
75        memcpy(in, buffer->value, inl);
76        buffer->value += inl;
77        buffer->length -= inl;
78    }
79    else {
80        /* swallow remainder of the buffer */
81        memcpy(in, buffer->value, buffer->length);
82        inl = buffer->length;
83        buffer->value = NULL;
84        buffer->length = 0;
85    }
86
87    return inl;
88}
89
90static int char_buffer_write(mod_gnutls_char_buffer_t * buffer, char *in,
91                             int inl)
92{
93    buffer->value = in;
94    buffer->length = inl;
95    return inl;
96}
97
98/**
99 * From mod_ssl / ssl_engine_io.c
100 * This function will read from a brigade and discard the read buckets as it
101 * proceeds.  It will read at most *len bytes.
102 */
103static apr_status_t brigade_consume(apr_bucket_brigade * bb,
104                                    apr_read_type_e block,
105                                    char *c, apr_size_t * len)
106{
107    apr_size_t actual = 0;
108    apr_status_t status = APR_SUCCESS;
109
110    while (!APR_BRIGADE_EMPTY(bb)) {
111        apr_bucket *b = APR_BRIGADE_FIRST(bb);
112        const char *str;
113        apr_size_t str_len;
114        apr_size_t consume;
115
116        /* Justin points out this is an http-ism that might
117         * not fit if brigade_consume is added to APR.  Perhaps
118         * apr_bucket_read(eos_bucket) should return APR_EOF?
119         * Then this becomes mainline instead of a one-off.
120         */
121        if (APR_BUCKET_IS_EOS(b)) {
122            status = APR_EOF;
123            break;
124        }
125
126        /* The reason I'm not offering brigade_consume yet
127         * across to apr-util is that the following call
128         * illustrates how borked that API really is.  For
129         * this sort of case (caller provided buffer) it
130         * would be much more trivial for apr_bucket_consume
131         * to do all the work that follows, based on the
132         * particular characteristics of the bucket we are
133         * consuming here.
134         */
135        status = apr_bucket_read(b, &str, &str_len, block);
136
137        if (status != APR_SUCCESS) {
138            if (APR_STATUS_IS_EOF(status)) {
139                /* This stream bucket was consumed */
140                apr_bucket_delete(b);
141                continue;
142            }
143            break;
144        }
145
146        if (str_len > 0) {
147            /* Do not block once some data has been consumed */
148            block = APR_NONBLOCK_READ;
149
150            /* Assure we don't overflow. */
151            consume = (str_len + actual > *len) ? *len - actual : str_len;
152
153            memcpy(c, str, consume);
154
155            c += consume;
156            actual += consume;
157
158            if (consume >= b->length) {
159                /* This physical bucket was consumed */
160                apr_bucket_delete(b);
161            }
162            else {
163                /* Only part of this physical bucket was consumed */
164                b->start += consume;
165                b->length -= consume;
166            }
167        }
168        else if (b->length == 0) {
169            apr_bucket_delete(b);
170        }
171
172        /* This could probably be actual == *len, but be safe from stray
173         * photons. */
174        if (actual >= *len) {
175            break;
176        }
177    }
178
179    *len = actual;
180    return status;
181}
182
183
184static apr_status_t gnutls_io_input_read(mod_gnutls_handle_t * ctxt,
185                                         char *buf, apr_size_t * len)
186{
187    apr_size_t wanted = *len;
188    apr_size_t bytes = 0;
189    int rc;
190
191    *len = 0;
192
193    /* If we have something leftover from last time, try that first. */
194    if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) {
195        *len = bytes;
196        if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
197            /* We want to rollback this read. */
198            if (ctxt->input_cbuf.length > 0) {
199                ctxt->input_cbuf.value -= bytes;
200                ctxt->input_cbuf.length += bytes;
201            }
202            else {
203                char_buffer_write(&ctxt->input_cbuf, buf, (int) bytes);
204            }
205            return APR_SUCCESS;
206        }
207        /* This could probably be *len == wanted, but be safe from stray
208         * photons.
209         */
210        if (*len >= wanted) {
211            return APR_SUCCESS;
212        }
213        if (ctxt->input_mode == AP_MODE_GETLINE) {
214            if (memchr(buf, APR_ASCII_LF, *len)) {
215                return APR_SUCCESS;
216            }
217        }
218        else {
219            /* Down to a nonblock pattern as we have some data already
220             */
221            ctxt->input_block = APR_NONBLOCK_READ;
222        }
223    }
224
225    while (1) {
226
227        if (ctxt->status < 0) {
228            /* Ensure a non-zero error code is returned */
229            if (ctxt->input_rc == APR_SUCCESS) {
230                ctxt->input_rc = APR_EGENERAL;
231            }
232            break;
233        }
234
235        rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes);
236
237        if (rc > 0) {
238            *len += rc;
239            if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
240                /* We want to rollback this read. */
241                char_buffer_write(&ctxt->input_cbuf, buf, rc);
242            }
243            return ctxt->input_rc;
244        }
245        else if (rc == 0) {
246            /* If EAGAIN, we will loop given a blocking read,
247             * otherwise consider ourselves at EOF.
248             */
249            if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
250                || APR_STATUS_IS_EINTR(ctxt->input_rc)) {
251                /* Already read something, return APR_SUCCESS instead.
252                 * On win32 in particular, but perhaps on other kernels,
253                 * a blocking call isn't 'always' blocking.
254                 */
255                if (*len > 0) {
256                    ctxt->input_rc = APR_SUCCESS;
257                    break;
258                }
259                if (ctxt->input_block == APR_NONBLOCK_READ) {
260                    break;
261                }
262            }
263            else {
264                if (*len > 0) {
265                    ctxt->input_rc = APR_SUCCESS;
266                }
267                else {
268                    ctxt->input_rc = APR_EOF;
269                }
270                break;
271            }
272        }
273        else {                  /* (rc < 0) */
274
275            if (rc == GNUTLS_E_REHANDSHAKE) {
276                /* A client has asked for a new Hankshake. Currently, we don't do it */
277                ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
278                             ctxt->c->base_server,
279                             "GnuTLS: Error reading data. Client Requested a New Handshake."
280                             " (%d) '%s'", rc, gnutls_strerror(rc));
281            }
282            else {
283                /* Some Other Error. Report it. Die. */
284                ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
285                             ctxt->c->base_server,
286                             "GnuTLS: Error reading data. (%d) '%s'", rc,
287                             gnutls_strerror(rc));
288            }
289
290            if (ctxt->input_rc == APR_SUCCESS) {
291                ctxt->input_rc = APR_EGENERAL;
292            }
293            break;
294        }
295    }
296    return ctxt->input_rc;
297}
298
299static apr_status_t gnutls_io_input_getline(mod_gnutls_handle_t * ctxt,
300                                            char *buf, apr_size_t * len)
301{
302    const char *pos = NULL;
303    apr_status_t status;
304    apr_size_t tmplen = *len, buflen = *len, offset = 0;
305
306    *len = 0;
307
308    while (tmplen > 0) {
309        status = gnutls_io_input_read(ctxt, buf + offset, &tmplen);
310
311        if (status != APR_SUCCESS) {
312            return status;
313        }
314
315        *len += tmplen;
316
317        if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
318            break;
319        }
320
321        offset += tmplen;
322        tmplen = buflen - offset;
323    }
324
325    if (pos) {
326        char *value;
327        int length;
328        apr_size_t bytes = pos - buf;
329
330        bytes += 1;
331        value = buf + bytes;
332        length = *len - bytes;
333
334        char_buffer_write(&ctxt->input_cbuf, value, length);
335
336        *len = bytes;
337    }
338
339    return APR_SUCCESS;
340}
341
342
343#define GNUTLS_HANDSHAKE_ATTEMPTS 10
344
345static void gnutls_do_handshake(mod_gnutls_handle_t * ctxt)
346{
347    int i, ret;
348
349    if (ctxt->status != 0)
350        return;
351
352    for (i = GNUTLS_HANDSHAKE_ATTEMPTS; i > 0; i--) {
353        ret = gnutls_handshake(ctxt->session);
354        if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) {
355            continue;
356        }
357
358        if (ret < 0) {
359            if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED
360                || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
361                ret = gnutls_alert_get(ctxt->session);
362                ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
363                             "GnuTLS: Hanshake Alert (%d) '%s'.\n", ret,
364                             gnutls_alert_get_name(ret));
365            }
366
367            if (gnutls_error_is_fatal(ret) != 0) {
368                gnutls_deinit(ctxt->session);
369                ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
370                             "GnuTLS: Handshake Failed (%d) '%s'", ret,
371                             gnutls_strerror(ret));
372                ctxt->status = -1;
373                return;
374            }
375        }
376        else {
377            ctxt->status = 1;
378            return;             /* all done with the handshake */
379        }
380    }
381    ctxt->status = -1;
382    return;
383}
384
385
386apr_status_t mod_gnutls_filter_input(ap_filter_t * f,
387                                     apr_bucket_brigade * bb,
388                                     ap_input_mode_t mode,
389                                     apr_read_type_e block,
390                                     apr_off_t readbytes)
391{
392    apr_status_t status = APR_SUCCESS;
393    mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx;
394    apr_size_t len = sizeof(ctxt->input_buffer);
395
396    if (f->c->aborted) {
397        apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
398        APR_BRIGADE_INSERT_TAIL(bb, bucket);
399        return APR_ECONNABORTED;
400    }
401
402    if (ctxt->status == 0) {
403        gnutls_do_handshake(ctxt);
404    }
405
406    if (ctxt->status < 0) {
407        return ap_get_brigade(f->next, bb, mode, block, readbytes);
408    }
409
410    /* XXX: we don't currently support anything other than these modes. */
411    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
412        mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
413        return APR_ENOTIMPL;
414    }
415
416    ctxt->input_mode = mode;
417    ctxt->input_block = block;
418
419    if (ctxt->input_mode == AP_MODE_READBYTES ||
420        ctxt->input_mode == AP_MODE_SPECULATIVE) {
421        /* Err. This is bad. readbytes *can* be a 64bit int! len.. is NOT */
422        if (readbytes < len) {
423            len = (apr_size_t) readbytes;
424        }
425        status = gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
426    }
427    else if (ctxt->input_mode == AP_MODE_GETLINE) {
428        status = gnutls_io_input_getline(ctxt, ctxt->input_buffer, &len);
429    }
430    else {
431        /* We have no idea what you are talking about, so return an error. */
432        return APR_ENOTIMPL;
433    }
434
435    if (status != APR_SUCCESS) {
436        return gnutls_io_filter_error(f, bb, status);
437    }
438
439    /* Create a transient bucket out of the decrypted data. */
440    if (len > 0) {
441        apr_bucket *bucket =
442            apr_bucket_transient_create(ctxt->input_buffer, len,
443                                        f->c->bucket_alloc);
444        APR_BRIGADE_INSERT_TAIL(bb, bucket);
445    }
446
447    return status;
448}
449
450apr_status_t mod_gnutls_filter_output(ap_filter_t * f,
451                                      apr_bucket_brigade * bb)
452{
453    int ret;
454    mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx;
455    apr_status_t status = APR_SUCCESS;
456    apr_read_type_e rblock = APR_NONBLOCK_READ;
457
458    if (f->c->aborted) {
459        apr_brigade_cleanup(bb);
460        return APR_ECONNABORTED;
461    }
462
463    if (ctxt->status == 0) {
464        gnutls_do_handshake(ctxt);
465    }
466
467    if (ctxt->status < 0) {
468        return ap_pass_brigade(f->next, bb);
469    }
470
471    while (!APR_BRIGADE_EMPTY(bb)) {
472        apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
473        if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
474            /** TODO: GnuTLS doesn't have a special flush method? **/
475            if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
476                return status;
477            }
478            break;
479        }
480        else if (AP_BUCKET_IS_EOC(bucket)) {
481            gnutls_bye(ctxt->session, GNUTLS_SHUT_WR);
482
483            if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
484                return status;
485            }
486            break;
487        }
488        else {
489            /* filter output */
490            const char *data;
491            apr_size_t len;
492
493            status = apr_bucket_read(bucket, &data, &len, rblock);
494
495            if (APR_STATUS_IS_EAGAIN(status)) {
496                rblock = APR_BLOCK_READ;
497                continue;       /* and try again with a blocking read. */
498            }
499
500            rblock = APR_NONBLOCK_READ;
501
502            if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) {
503                break;
504            }
505
506            ret = gnutls_record_send(ctxt->session, data, len);
507
508            if (ret < 0) {
509                /* error sending output */
510                ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
511                             ctxt->c->base_server,
512                             "GnuTLS: Error writing data."
513                             " (%d) '%s'", ret, gnutls_strerror(ret));
514                if (ctxt->output_rc == APR_SUCCESS) {
515                    ctxt->output_rc = APR_EGENERAL;
516                }
517            }
518            else if ((apr_size_t) ret != len) {
519                /* not all of the data was sent. */
520                /* mod_ssl basicly errors out here.. this doesn't seem right? */
521                ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
522                             ctxt->c->base_server,
523                             "GnuTLS: failed to write %" APR_SSIZE_T_FMT
524                             " of %" APR_SIZE_T_FMT " bytes.",
525                             len - (apr_size_t) ret, len);
526                if (ctxt->output_rc == APR_SUCCESS) {
527                    ctxt->output_rc = APR_EGENERAL;
528                }
529            }
530
531            apr_bucket_delete(bucket);
532
533            if (ctxt->output_rc != APR_SUCCESS) {
534                break;
535            }
536        }
537    }
538
539    return status;
540}
541
542ssize_t mod_gnutls_transport_read(gnutls_transport_ptr_t ptr,
543                                  void *buffer, size_t len)
544{
545    mod_gnutls_handle_t *ctxt = ptr;
546    apr_status_t rc;
547    apr_size_t in = len;
548    apr_read_type_e block = ctxt->input_block;
549
550    ctxt->input_rc = APR_SUCCESS;
551
552    /* If Len = 0, we don't do anything. */
553    if (!len)
554        return 0;
555
556    if (!ctxt->input_bb) {
557        ctxt->input_rc = APR_EOF;
558        return -1;
559    }
560
561    if (APR_BRIGADE_EMPTY(ctxt->input_bb)) {
562
563        rc = ap_get_brigade(ctxt->input_filter->next, ctxt->input_bb,
564                            AP_MODE_READBYTES, ctxt->input_block, in);
565
566        /* Not a problem, there was simply no data ready yet.
567         */
568        if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
569            || (rc == APR_SUCCESS && APR_BRIGADE_EMPTY(ctxt->input_bb))) {
570            return 0;
571        }
572
573        if (rc != APR_SUCCESS) {
574            /* Unexpected errors discard the brigade */
575            apr_brigade_cleanup(ctxt->input_bb);
576            ctxt->input_bb = NULL;
577            return -1;
578        }
579    }
580
581    ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len);
582
583    if (ctxt->input_rc == APR_SUCCESS) {
584        return (ssize_t) len;
585    }
586
587    if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
588        || APR_STATUS_IS_EINTR(ctxt->input_rc)) {
589        return (ssize_t) len;
590    }
591
592    /* Unexpected errors and APR_EOF clean out the brigade.
593     * Subsequent calls will return APR_EOF.
594     */
595    apr_brigade_cleanup(ctxt->input_bb);
596    ctxt->input_bb = NULL;
597
598    if (APR_STATUS_IS_EOF(ctxt->input_rc) && len) {
599        /* Provide the results of this read pass,
600         * without resetting the BIO retry_read flag
601         */
602        return (ssize_t) len;
603    }
604
605    return -1;
606}
607
608
609static ssize_t write_flush(mod_gnutls_handle_t * ctxt)
610{
611    apr_bucket *e;
612
613    if (!(ctxt->output_blen || ctxt->output_length)) {
614        ctxt->output_rc = APR_SUCCESS;
615        return 1;
616    }
617
618    if (ctxt->output_blen) {
619        e = apr_bucket_transient_create(ctxt->output_buffer,
620                                        ctxt->output_blen,
621                                        ctxt->output_bb->bucket_alloc);
622        /* we filled this buffer first so add it to the
623         * head of the brigade
624         */
625        APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e);
626        ctxt->output_blen = 0;
627    }
628
629    ctxt->output_length = 0;
630    e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc);
631    APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
632
633    ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next,
634                                      ctxt->output_bb);
635    /* create new brigade ready for next time through */
636    ctxt->output_bb =
637        apr_brigade_create(ctxt->c->pool, ctxt->c->bucket_alloc);
638    return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1;
639}
640
641ssize_t mod_gnutls_transport_write(gnutls_transport_ptr_t ptr,
642                                   const void *buffer, size_t len)
643{
644    mod_gnutls_handle_t *ctxt = ptr;
645
646    if (!ctxt->output_length
647        && (len + ctxt->output_blen < sizeof(ctxt->output_buffer))) {
648        /* the first two SSL_writes (of 1024 and 261 bytes)
649         * need to be in the same packet (vec[0].iov_base)
650         */
651        /* XXX: could use apr_brigade_write() to make code look cleaner 
652         * but this way we avoid the malloc(APR_BUCKET_BUFF_SIZE)
653         * and free() of it later
654         */
655        memcpy(&ctxt->output_buffer[ctxt->output_blen], buffer, len);
656        ctxt->output_blen += len;
657    }
658    else {
659        /* pass along the encrypted data
660         * need to flush since we're using SSL's malloc-ed buffer
661         * which will be overwritten once we leave here
662         */
663        apr_bucket *bucket = apr_bucket_transient_create(buffer, len,
664                                                         ctxt->output_bb->
665                                                         bucket_alloc);
666
667        ctxt->output_length += len;
668        APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket);
669
670        if (write_flush(ctxt) < 0) {
671            return -1;
672        }
673    }
674
675    return len;
676}
Note: See TracBrowser for help on using the repository browser.