2
* Copyright 2004-2005 Paul Querna
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
8
* http://www.apache.org/licenses/LICENSE-2.0
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.
18
#include "mod_gnutls.h"
21
* Describe how the GnuTLS Filter system works here
22
* - Basicly the same as what mod_ssl does with OpenSSL.
26
#define HTTP_ON_HTTPS_PORT \
29
#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
30
apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
31
sizeof(HTTP_ON_HTTPS_PORT) - 1, \
34
static apr_status_t gnutls_io_filter_error(ap_filter_t * f,
35
apr_bucket_brigade * bb,
38
mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
42
case HTTP_BAD_REQUEST:
43
/* log the situation */
44
ap_log_error(APLOG_MARK, APLOG_INFO, 0,
46
"GnuTLS handshake failed: HTTP spoken on HTTPS port; "
47
"trying to send HTML error page");
51
/* fake the request line */
52
bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
59
APR_BRIGADE_INSERT_TAIL(bb, bucket);
60
bucket = apr_bucket_eos_create(f->c->bucket_alloc);
61
APR_BRIGADE_INSERT_TAIL(bb, bucket);
66
static int char_buffer_read(mgs_char_buffer_t * buffer, char *in,
69
if (!buffer->length) {
73
if (buffer->length > inl) {
74
/* we have have enough to fill the caller's buffer */
75
memcpy(in, buffer->value, inl);
77
buffer->length -= inl;
80
/* swallow remainder of the buffer */
81
memcpy(in, buffer->value, buffer->length);
90
static int char_buffer_write(mgs_char_buffer_t * buffer, char *in,
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.
103
static apr_status_t brigade_consume(apr_bucket_brigade * bb,
104
apr_read_type_e block,
105
char *c, apr_size_t * len)
107
apr_size_t actual = 0;
108
apr_status_t status = APR_SUCCESS;
110
while (!APR_BRIGADE_EMPTY(bb)) {
111
apr_bucket *b = APR_BRIGADE_FIRST(bb);
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.
121
if (APR_BUCKET_IS_EOS(b)) {
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
135
status = apr_bucket_read(b, &str, &str_len, block);
137
if (status != APR_SUCCESS) {
138
if (APR_STATUS_IS_EOF(status)) {
139
/* This stream bucket was consumed */
140
apr_bucket_delete(b);
147
/* Do not block once some data has been consumed */
148
block = APR_NONBLOCK_READ;
150
/* Assure we don't overflow. */
151
consume = (str_len + actual > *len) ? *len - actual : str_len;
153
memcpy(c, str, consume);
158
if (consume >= b->length) {
159
/* This physical bucket was consumed */
160
apr_bucket_delete(b);
163
/* Only part of this physical bucket was consumed */
165
b->length -= consume;
168
else if (b->length == 0) {
169
apr_bucket_delete(b);
172
/* This could probably be actual == *len, but be safe from stray
174
if (actual >= *len) {
184
static apr_status_t gnutls_io_input_read(mgs_handle_t * ctxt,
185
char *buf, apr_size_t * len)
187
apr_size_t wanted = *len;
188
apr_size_t bytes = 0;
193
/* If we have something leftover from last time, try that first. */
194
if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) {
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;
203
char_buffer_write(&ctxt->input_cbuf, buf, (int) bytes);
207
/* This could probably be *len == wanted, but be safe from stray
210
if (*len >= wanted) {
213
if (ctxt->input_mode == AP_MODE_GETLINE) {
214
if (memchr(buf, APR_ASCII_LF, *len)) {
219
/* Down to a nonblock pattern as we have some data already
221
ctxt->input_block = APR_NONBLOCK_READ;
227
rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes);
231
if (ctxt->input_mode == AP_MODE_SPECULATIVE) {
232
/* We want to rollback this read. */
233
char_buffer_write(&ctxt->input_cbuf, buf, rc);
235
return ctxt->input_rc;
238
/* If EAGAIN, we will loop given a blocking read,
239
* otherwise consider ourselves at EOF.
241
if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
242
|| APR_STATUS_IS_EINTR(ctxt->input_rc)) {
243
/* Already read something, return APR_SUCCESS instead.
244
* On win32 in particular, but perhaps on other kernels,
245
* a blocking call isn't 'always' blocking.
248
ctxt->input_rc = APR_SUCCESS;
251
if (ctxt->input_block == APR_NONBLOCK_READ) {
257
ctxt->input_rc = APR_SUCCESS;
260
ctxt->input_rc = APR_EOF;
265
else { /* (rc < 0) */
267
if (rc == GNUTLS_E_REHANDSHAKE) {
268
/* A client has asked for a new Hankshake. Currently, we don't do it */
269
ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
270
ctxt->c->base_server,
271
"GnuTLS: Error reading data. Client Requested a New Handshake."
272
" (%d) '%s'", rc, gnutls_strerror(rc));
274
else if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
275
rc = gnutls_alert_get(ctxt->session);
276
ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
277
ctxt->c->base_server,
278
"GnuTLS: Warning Alert From Client: "
279
" (%d) '%s'", rc, gnutls_alert_get_name(rc));
281
else if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
282
rc = gnutls_alert_get(ctxt->session);
283
ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
284
ctxt->c->base_server,
285
"GnuTLS: Fatal Alert From Client: "
286
"(%d) '%s'", rc, gnutls_alert_get_name(rc));
287
ctxt->input_rc = APR_EGENERAL;
291
/* Some Other Error. Report it. Die. */
292
if(gnutls_error_is_fatal(rc)) {
293
ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc,
294
ctxt->c->base_server,
295
"GnuTLS: Error reading data. (%d) '%s'", rc,
296
gnutls_strerror(rc));
299
ctxt->input_rc = APR_SUCCESS;
304
if (ctxt->input_rc == APR_SUCCESS) {
305
ctxt->input_rc = APR_EGENERAL;
310
return ctxt->input_rc;
313
static apr_status_t gnutls_io_input_getline(mgs_handle_t * ctxt,
314
char *buf, apr_size_t * len)
316
const char *pos = NULL;
318
apr_size_t tmplen = *len, buflen = *len, offset = 0;
323
status = gnutls_io_input_read(ctxt, buf + offset, &tmplen);
325
if (status != APR_SUCCESS) {
331
if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
336
tmplen = buflen - offset;
342
apr_size_t bytes = pos - buf;
346
length = *len - bytes;
348
char_buffer_write(&ctxt->input_cbuf, value, length);
356
#define HANDSHAKE_MAX_TRIES 100
357
static int gnutls_do_handshake(mgs_handle_t * ctxt)
361
int maxtries = HANDSHAKE_MAX_TRIES;
363
if (ctxt->status != 0) {
369
ret = gnutls_handshake(ctxt->session);
371
} while (ret == GNUTLS_E_AGAIN && maxtries > 0);
376
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
377
"GnuTLS: Handshake Failed. Hit Maximum Attempts");
379
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
380
"GnuTLS: Handshake Failed. Hit Maximum Attempts");
382
gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
383
gnutls_error_to_alert(ret, NULL));
384
gnutls_deinit(ctxt->session);
389
if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED
390
|| ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
391
errcode = gnutls_alert_get(ctxt->session);
392
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
393
"GnuTLS: Hanshake Alert (%d) '%s'.", errcode,
394
gnutls_alert_get_name(errcode));
397
if (!gnutls_error_is_fatal(ret)) {
398
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctxt->c->base_server,
399
"GnuTLS: Non-Fatal Handshake Error: (%d) '%s'", ret,
400
gnutls_strerror(ret));
404
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
405
"GnuTLS: Handshake Failed (%d) '%s'", ret,
406
gnutls_strerror(ret));
408
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
409
"GnuTLS: Handshake Failed (%d) '%s'", ret,
410
gnutls_strerror(ret));
413
gnutls_alert_send(ctxt->session, GNUTLS_AL_FATAL,
414
gnutls_error_to_alert(ret, NULL));
415
gnutls_deinit(ctxt->session);
419
/* all done with the handshake */
421
/* If the session was resumed, we did not set the correct
422
* server_rec in ctxt->sc. Go Find it. (ick!)
424
if (gnutls_session_is_resumed(ctxt->session)) {
426
sc = mgs_find_sni_server(ctxt->session);
435
int mgs_rehandshake(mgs_handle_t * ctxt)
439
rv = gnutls_rehandshake(ctxt->session);
442
/* the client did not want to rehandshake. goodbye */
443
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server,
444
"GnuTLS: Client Refused Rehandshake request.");
450
rv = gnutls_do_handshake(ctxt);
456
apr_status_t mgs_filter_input(ap_filter_t* f,
457
apr_bucket_brigade * bb,
458
ap_input_mode_t mode,
459
apr_read_type_e block,
462
apr_status_t status = APR_SUCCESS;
463
mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
464
apr_size_t len = sizeof(ctxt->input_buffer);
467
apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
468
APR_BRIGADE_INSERT_TAIL(bb, bucket);
469
return APR_ECONNABORTED;
472
if (ctxt->status == 0) {
473
gnutls_do_handshake(ctxt);
476
if (ctxt->status < 0) {
477
return ap_get_brigade(f->next, bb, mode, block, readbytes);
480
/* XXX: we don't currently support anything other than these modes. */
481
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
482
mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
486
ctxt->input_mode = mode;
487
ctxt->input_block = block;
489
if (ctxt->input_mode == AP_MODE_READBYTES ||
490
ctxt->input_mode == AP_MODE_SPECULATIVE) {
491
/* Err. This is bad. readbytes *can* be a 64bit int! len.. is NOT */
492
if (readbytes < len) {
493
len = (apr_size_t) readbytes;
495
status = gnutls_io_input_read(ctxt, ctxt->input_buffer, &len);
497
else if (ctxt->input_mode == AP_MODE_GETLINE) {
498
status = gnutls_io_input_getline(ctxt, ctxt->input_buffer, &len);
501
/* We have no idea what you are talking about, so return an error. */
505
if (status != APR_SUCCESS) {
506
return gnutls_io_filter_error(f, bb, status);
509
/* Create a transient bucket out of the decrypted data. */
512
apr_bucket_transient_create(ctxt->input_buffer, len,
514
APR_BRIGADE_INSERT_TAIL(bb, bucket);
520
apr_status_t mgs_filter_output(ap_filter_t * f,
521
apr_bucket_brigade * bb)
525
mgs_handle_t *ctxt = (mgs_handle_t *) f->ctx;
526
apr_status_t status = APR_SUCCESS;
527
apr_read_type_e rblock = APR_NONBLOCK_READ;
530
apr_brigade_cleanup(bb);
531
return APR_ECONNABORTED;
534
if (ctxt->status == 0) {
535
gnutls_do_handshake(ctxt);
538
if (ctxt->status < 0) {
539
return ap_pass_brigade(f->next, bb);
542
while (!APR_BRIGADE_EMPTY(bb)) {
543
apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
544
if (AP_BUCKET_IS_EOC(bucket)) {
546
ret = gnutls_bye( ctxt->session, GNUTLS_SHUT_WR);
547
} while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
549
apr_bucket_copy(bucket, &e);
550
APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
552
if ((status = ap_pass_brigade(f->next, ctxt->output_bb)) != APR_SUCCESS) {
553
apr_brigade_cleanup(ctxt->output_bb);
557
apr_brigade_cleanup(ctxt->output_bb);
558
gnutls_deinit(ctxt->session);
561
} else if (APR_BUCKET_IS_FLUSH(bucket) || APR_BUCKET_IS_EOS(bucket)) {
563
apr_bucket_copy(bucket, &e);
564
APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
565
if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
566
apr_brigade_cleanup(ctxt->output_bb);
570
apr_brigade_cleanup(ctxt->output_bb);
578
status = apr_bucket_read(bucket, &data, &len, rblock);
580
if (APR_STATUS_IS_EAGAIN(status)) {
581
rblock = APR_BLOCK_READ;
582
continue; /* and try again with a blocking read. */
585
rblock = APR_NONBLOCK_READ;
587
if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) {
592
ret = gnutls_record_send(ctxt->session, data, len);
594
while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
597
/* error sending output */
598
ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->output_rc,
599
ctxt->c->base_server,
600
"GnuTLS: Error writing data."
601
" (%d) '%s'", (int)ret, gnutls_strerror(ret));
602
if (ctxt->output_rc == APR_SUCCESS) {
603
ctxt->output_rc = APR_EGENERAL;
606
else if (ret != len) {
607
/* Not able to send the entire bucket,
608
split it and send it again. */
609
apr_bucket_split(bucket, ret);
612
apr_bucket_delete(bucket);
614
if (ctxt->output_rc != APR_SUCCESS) {
623
ssize_t mgs_transport_read(gnutls_transport_ptr_t ptr,
624
void *buffer, size_t len)
626
mgs_handle_t *ctxt = ptr;
629
apr_read_type_e block = ctxt->input_block;
631
ctxt->input_rc = APR_SUCCESS;
633
/* If Len = 0, we don't do anything. */
637
if (!ctxt->input_bb) {
638
ctxt->input_rc = APR_EOF;
642
if (APR_BRIGADE_EMPTY(ctxt->input_bb)) {
644
rc = ap_get_brigade(ctxt->input_filter->next, ctxt->input_bb,
645
AP_MODE_READBYTES, ctxt->input_block, in);
647
/* Not a problem, there was simply no data ready yet.
649
if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
650
|| (rc == APR_SUCCESS && APR_BRIGADE_EMPTY(ctxt->input_bb))) {
654
if (rc != APR_SUCCESS) {
655
/* Unexpected errors discard the brigade */
656
apr_brigade_cleanup(ctxt->input_bb);
657
ctxt->input_bb = NULL;
662
ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len);
664
if (ctxt->input_rc == APR_SUCCESS) {
665
return (ssize_t) len;
668
if (APR_STATUS_IS_EAGAIN(ctxt->input_rc)
669
|| APR_STATUS_IS_EINTR(ctxt->input_rc)) {
670
return (ssize_t) len;
673
/* Unexpected errors and APR_EOF clean out the brigade.
674
* Subsequent calls will return APR_EOF.
676
apr_brigade_cleanup(ctxt->input_bb);
677
ctxt->input_bb = NULL;
679
if (APR_STATUS_IS_EOF(ctxt->input_rc) && len) {
680
/* Provide the results of this read pass,
681
* without resetting the BIO retry_read flag
683
return (ssize_t) len;
690
static ssize_t write_flush(mgs_handle_t * ctxt)
694
if (!(ctxt->output_blen || ctxt->output_length)) {
695
ctxt->output_rc = APR_SUCCESS;
699
if (ctxt->output_blen) {
700
e = apr_bucket_transient_create(ctxt->output_buffer,
702
ctxt->output_bb->bucket_alloc);
703
/* we filled this buffer first so add it to the
704
* head of the brigade
706
APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e);
707
ctxt->output_blen = 0;
710
ctxt->output_length = 0;
711
e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc);
712
APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e);
714
ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next,
716
/* clear the brigade to be ready for next time */
717
apr_brigade_cleanup(ctxt->output_bb);
719
return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1;
722
ssize_t mgs_transport_write(gnutls_transport_ptr_t ptr,
723
const void *buffer, size_t len)
725
mgs_handle_t *ctxt = ptr;
727
/* pass along the encrypted data
728
* need to flush since we're using SSL's malloc-ed buffer
729
* which will be overwritten once we leave here
731
apr_bucket *bucket = apr_bucket_transient_create(buffer, len,
732
ctxt->output_bb->bucket_alloc);
733
ctxt->output_length += len;
734
APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket);
736
if (write_flush(ctxt) < 0) {