aboutsummaryrefslogtreecommitdiffstats
path: root/src/mod_gnutls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_gnutls.c')
-rw-r--r--src/mod_gnutls.c465
1 files changed, 465 insertions, 0 deletions
diff --git a/src/mod_gnutls.c b/src/mod_gnutls.c
new file mode 100644
index 0000000..bf0d9c3
--- /dev/null
+++ b/src/mod_gnutls.c
@@ -0,0 +1,465 @@
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 "httpd.h"
19#include "http_config.h"
20#include "http_protocol.h"
21#include "http_connection.h"
22#include "http_core.h"
23#include "http_log.h"
24#include "apr_buckets.h"
25#include "apr_strings.h"
26#include "apr_tables.h"
27
28#include <gcrypt.h>
29#include <gnutls/gnutls.h>
30
31#if APR_HAS_THREADS
32GCRY_THREAD_OPTION_PTHREAD_IMPL;
33#endif
34
35module AP_MODULE_DECLARE_DATA gnutls_module;
36
37#define GNUTLS_OUTPUT_FILTER_NAME "GnuTLS Output Filter"
38#define GNUTLS_INPUT_FILTER_NAME "GnuTLS Input Filter"
39
40#define GNUTLS_ENABLED_FALSE 0
41#define GNUTLS_ENABLED_TRUE 1
42
43
44typedef struct gnutls_srvconf_t gnutls_srvconf_t;
45struct gnutls_srvconf_t
46{
47 gnutls_certificate_credentials_t certs;
48 char *key_file;
49 char *cert_file;
50 int enabled;
51};
52
53typedef struct gnutls_handle_t gnutls_handle_t;
54struct gnutls_handle_t
55{
56 gnutls_srvconf_t *sc;
57 gnutls_session_t session;
58 ap_filter_t *input_filter;
59 apr_bucket_brigade *input_bb;
60 apr_read_type_e input_block;
61};
62
63static apr_status_t gnutls_filter_input(ap_filter_t * f,
64 apr_bucket_brigade * bb,
65 ap_input_mode_t mode,
66 apr_read_type_e block,
67 apr_off_t readbytes)
68{
69 apr_status_t status = APR_SUCCESS;
70 gnutls_handle_t *ctxt = (gnutls_handle_t *) f->ctx;
71
72 if (f->c->aborted) {
73 apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
74 APR_BRIGADE_INSERT_TAIL(bb, bucket);
75 return APR_ECONNABORTED;
76 }
77
78 return status;
79}
80
81static apr_status_t gnutls_filter_output(ap_filter_t * f,
82 apr_bucket_brigade * bb)
83{
84 apr_bucket *b;
85 const char *buf = 0;
86 apr_size_t bytes = 0;
87 gnutls_handle_t *ctxt = (gnutls_handle_t *) f->ctx;
88 apr_status_t status = APR_SUCCESS;
89
90 if (!ctxt) {
91 /* first run. */
92 }
93
94 for (b = APR_BRIGADE_FIRST(bb);
95 b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
96 if (APR_BUCKET_IS_EOS(b)) {
97 /* end of connection */
98 }
99 else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
100 == APR_SUCCESS) {
101 /* more data */
102 }
103 }
104
105 return status;
106}
107
108static apr_status_t gnutls_cleanup_pre_config(void *data)
109{
110 gnutls_global_deinit();
111 return APR_SUCCESS;
112}
113
114static int gnutls_hook_pre_config(apr_pool_t * pconf,
115 apr_pool_t * plog, apr_pool_t * ptemp)
116{
117
118#if APR_HAS_THREADS
119 gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
120#endif
121
122 gnutls_global_init();
123
124 apr_pool_cleanup_register(pconf, NULL, gnutls_cleanup_pre_config,
125 apr_pool_cleanup_null);
126
127 return OK;
128}
129
130#define DH_BITS 1024
131#define RSA_BITS 512
132
133static int gnutls_hook_post_config(apr_pool_t * p, apr_pool_t * plog,
134 apr_pool_t * ptemp,
135 server_rec * base_server)
136{
137 gnutls_srvconf_t *sc;
138 server_rec *s;
139 gnutls_dh_params_t dh_params;
140 gnutls_rsa_params_t rsa_params;
141
142
143 /* TODO: Should we regenerate these after X requests / X time ? */
144 gnutls_dh_params_init(&dh_params);
145 gnutls_dh_params_generate2(dh_params, DH_BITS);
146 gnutls_rsa_params_init(&rsa_params);
147 gnutls_rsa_params_generate2(rsa_params, RSA_BITS);
148
149 for (s = base_server; s; s = s->next) {
150 sc = (gnutls_srvconf_t *) ap_get_module_config(s->module_config,
151 &gnutls_module);
152 if (sc->cert_file != NULL && sc->key_file != NULL) {
153 gnutls_certificate_set_x509_key_file(sc->certs, sc->cert_file,
154 sc->key_file,
155 GNUTLS_X509_FMT_PEM);
156 }
157 else {
158 ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
159 "[GnuTLS] - Host '%s' is missing a Cert and Key File!",
160 s->server_hostname);
161 }
162
163 /**
164 * TODO: Is it okay for all virtual hosts to
165 * share the same DH/RSAparams?
166 */
167 gnutls_certificate_set_dh_params(sc->certs, dh_params);
168 gnutls_certificate_set_rsa_export_params(sc->certs, rsa_params);
169 }
170
171 ap_add_version_component(p, "GnuTLS/" LIBGNUTLS_VERSION);
172 return OK;
173}
174
175static const char *gnutls_hook_http_method(const request_rec * r)
176{
177 gnutls_srvconf_t *sc =
178 (gnutls_srvconf_t *) ap_get_module_config(r->server->module_config,
179 &gnutls_module);
180
181 if (sc->enabled == GNUTLS_ENABLED_FALSE) {
182 return NULL;
183 }
184
185 return "https";
186}
187
188static apr_port_t gnutls_hook_default_port(const request_rec * r)
189{
190 gnutls_srvconf_t *sc =
191 (gnutls_srvconf_t *) ap_get_module_config(r->server->module_config,
192 &gnutls_module);
193
194 if (sc->enabled == GNUTLS_ENABLED_FALSE) {
195 return 0;
196 }
197
198 return 443;
199}
200
201/**
202 * From mod_ssl / ssl_engine_io.c
203 * This function will read from a brigade and discard the read buckets as it
204 * proceeds. It will read at most *len bytes.
205 */
206static apr_status_t brigade_consume(apr_bucket_brigade * bb,
207 apr_read_type_e block,
208 char *c, apr_size_t * len)
209{
210 apr_size_t actual = 0;
211 apr_status_t status = APR_SUCCESS;
212
213 while (!APR_BRIGADE_EMPTY(bb)) {
214 apr_bucket *b = APR_BRIGADE_FIRST(bb);
215 const char *str;
216 apr_size_t str_len;
217 apr_size_t consume;
218
219 /* Justin points out this is an http-ism that might
220 * not fit if brigade_consume is added to APR. Perhaps
221 * apr_bucket_read(eos_bucket) should return APR_EOF?
222 * Then this becomes mainline instead of a one-off.
223 */
224 if (APR_BUCKET_IS_EOS(b)) {
225 status = APR_EOF;
226 break;
227 }
228
229 /* The reason I'm not offering brigade_consume yet
230 * across to apr-util is that the following call
231 * illustrates how borked that API really is. For
232 * this sort of case (caller provided buffer) it
233 * would be much more trivial for apr_bucket_consume
234 * to do all the work that follows, based on the
235 * particular characteristics of the bucket we are
236 * consuming here.
237 */
238 status = apr_bucket_read(b, &str, &str_len, block);
239
240 if (status != APR_SUCCESS) {
241 if (APR_STATUS_IS_EOF(status)) {
242 /* This stream bucket was consumed */
243 apr_bucket_delete(b);
244 continue;
245 }
246 break;
247 }
248
249 if (str_len > 0) {
250 /* Do not block once some data has been consumed */
251 block = APR_NONBLOCK_READ;
252
253 /* Assure we don't overflow. */
254 consume = (str_len + actual > *len) ? *len - actual : str_len;
255
256 memcpy(c, str, consume);
257
258 c += consume;
259 actual += consume;
260
261 if (consume >= b->length) {
262 /* This physical bucket was consumed */
263 apr_bucket_delete(b);
264 }
265 else {
266 /* Only part of this physical bucket was consumed */
267 b->start += consume;
268 b->length -= consume;
269 }
270 }
271 else if (b->length == 0) {
272 apr_bucket_delete(b);
273 }
274
275 /* This could probably be actual == *len, but be safe from stray
276 * photons. */
277 if (actual >= *len) {
278 break;
279 }
280 }
281
282 *len = actual;
283 return status;
284}
285
286
287static ssize_t gnutls_transport_read(gnutls_transport_ptr_t ptr,
288 void *buffer, size_t len)
289{
290 gnutls_handle_t *ctxt = ptr;
291 apr_status_t rc;
292 apr_size_t in = len;
293 /* If Len = 0, we don't do anything. */
294 if (!len)
295 return 0;
296
297 if (APR_BRIGADE_EMPTY(ctxt->input_bb)) {
298
299 rc = ap_get_brigade(ctxt->input_filter->next, ctxt->input_bb,
300 AP_MODE_READBYTES, ctxt->input_block, in);
301
302 /* Not a problem, there was simply no data ready yet.
303 */
304 if (APR_STATUS_IS_EAGAIN(rc) || APR_STATUS_IS_EINTR(rc)
305 || (rc == APR_SUCCESS && APR_BRIGADE_EMPTY(ctxt->input_bb))) {
306 return 0;
307 }
308
309 if (rc != APR_SUCCESS) {
310 /* Unexpected errors discard the brigade */
311 apr_brigade_cleanup(ctxt->input_bb);
312 ctxt->input_bb = NULL;
313 return -1;
314 }
315 }
316
317// brigade_consume(ctxt->input_bb, ctxt->input_block, buffer, &len);
318
319
320 ap_get_brigade(ctxt->input_filter->next, ctxt->input_bb,
321 AP_MODE_READBYTES, ctxt->input_block, len);
322
323 return len;
324}
325
326static ssize_t gnutls_transport_write(gnutls_transport_ptr_t ptr,
327 const void *buffer, size_t len)
328{
329 gnutls_handle_t *ctxt = ptr;
330
331// apr_bucket *bucket = apr_bucket_transient_create(in, inl,
332// outctx->bb->
333// bucket_alloc);
334
335 // outctx->length += inl;
336 //APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket);
337 return 0;
338}
339
340static int gnutls_hook_pre_connection(conn_rec * c, void *csd)
341{
342#ifndef GNUTLS_AS_FILTER
343 int cfd;
344#endif
345 gnutls_handle_t *ctxt;
346 gnutls_srvconf_t *sc =
347 (gnutls_srvconf_t *) ap_get_module_config(c->base_server->
348 module_config,
349 &gnutls_module);
350
351 if (!(sc && (sc->enabled == GNUTLS_ENABLED_TRUE))) {
352 return DECLINED;
353 }
354
355 ctxt = apr_pcalloc(c->pool, sizeof(*ctxt));
356
357 ctxt->sc = sc;
358 gnutls_init(&ctxt->session, GNUTLS_SERVER);
359
360 gnutls_set_default_priority(ctxt->session);
361
362 gnutls_credentials_set(ctxt->session, GNUTLS_CRD_CERTIFICATE, sc->certs);
363
364 gnutls_certificate_server_set_request(ctxt->session, GNUTLS_CERT_REQUEST);
365
366 gnutls_dh_set_prime_bits(ctxt->session, DH_BITS);
367
368 ap_set_module_config(c->conn_config, &gnutls_module, ctxt);
369
370#ifdef GNUTLS_AS_FILTER
371 gnutls_transport_set_pull_function(ctxt->session, gnutls_transport_read);
372 gnutls_transport_set_push_function(ctxt->session, gnutls_transport_write);
373 gnutls_transport_set_ptr(ctxt->session, ctxt);
374
375 ap_add_input_filter(GNUTLS_INPUT_FILTER_NAME, ctxt, NULL, c);
376 ap_add_output_filter(GNUTLS_OUTPUT_FILTER_NAME, ctxt, NULL, c);
377#else
378 apr_os_sock_get(&cfd, csd);
379 gnutls_transport_set_ptr(ctxt->session, (gnutls_transport_ptr)cfd);
380#endif
381 return OK;
382}
383
384static const char *gnutls_set_ca_file(cmd_parms * parms, void *dummy,
385 const char *arg)
386{
387 gnutls_srvconf_t *sc =
388 (gnutls_srvconf_t *) ap_get_module_config(parms->server->
389 module_config,
390 &gnutls_module);
391/* TODO: CRL, CAFile */
392// gnutls_certificate_set_x509_trust_file(sc->certs, CAFILE,
393// GNUTLS_X509_FMT_PEM);
394 return NULL;
395}
396
397static const command_rec gnutls_cmds[] = {
398 AP_INIT_FLAG("GnuTLSEnable", ap_set_flag_slot,
399 (void *) APR_OFFSETOF(gnutls_srvconf_t, enabled), RSRC_CONF,
400 "Whether this server has GnuTLS Enabled. Default: Off"),
401 AP_INIT_TAKE1("GnuTLSCertificateFile", ap_set_string_slot,
402 (void *) APR_OFFSETOF(gnutls_srvconf_t, cert_file),
403 RSRC_CONF,
404 "SSL Server Key file"),
405 AP_INIT_TAKE1("GnuTLSKeyFile", ap_set_string_slot,
406 (void *) APR_OFFSETOF(gnutls_srvconf_t, key_file),
407 RSRC_CONF,
408 "SSL Server Certificate file"),
409 {NULL}
410};
411
412/* TODO: CACertificateFile & Client Authentication
413 * AP_INIT_TAKE1("GnuTLSCACertificateFile", ap_set_server_string_slot,
414 * (void *) APR_OFFSETOF(gnutls_srvconf_t, key_file), NULL,
415 * RSRC_CONF,
416 * "CA"),
417 */
418
419static void gnutls_hooks(apr_pool_t * p)
420{
421 ap_hook_pre_connection(gnutls_hook_pre_connection, NULL, NULL,
422 APR_HOOK_MIDDLE);
423 ap_hook_post_config(gnutls_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
424 ap_hook_http_method(gnutls_hook_http_method, NULL, NULL, APR_HOOK_MIDDLE);
425 ap_hook_default_port(gnutls_hook_default_port, NULL, NULL,
426 APR_HOOK_MIDDLE);
427 ap_hook_pre_config(gnutls_hook_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
428
429 /* TODO: HTTP Upgrade Filter */
430 /* ap_register_output_filter ("UPGRADE_FILTER",
431 * ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5);
432 */
433
434 ap_register_input_filter(GNUTLS_INPUT_FILTER_NAME, gnutls_filter_input,
435 NULL, AP_FTYPE_CONNECTION + 5);
436 ap_register_output_filter(GNUTLS_OUTPUT_FILTER_NAME, gnutls_filter_output,
437 NULL, AP_FTYPE_CONNECTION + 5);
438
439}
440
441static void *gnutls_config_server_create(apr_pool_t * p, server_rec * s)
442{
443 gnutls_srvconf_t *sc = apr_pcalloc(p, sizeof *sc);
444
445 sc->enabled = GNUTLS_ENABLED_FALSE;
446
447 gnutls_certificate_allocate_credentials(&sc->certs);
448
449 sc->key_file = NULL;
450 sc->cert_file = NULL;
451 return sc;
452}
453
454
455
456module AP_MODULE_DECLARE_DATA gnutls_module = {
457 STANDARD20_MODULE_STUFF,
458 NULL,
459 NULL,
460 gnutls_config_server_create,
461 NULL,
462/* gnutls_config_server_merge, */
463 gnutls_cmds,
464 gnutls_hooks
465};