source: mod_gnutls/src/mod_gnutls.c @ 9706fc2

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

iniail makefiles and license foo

  • Property mode set to 100644
File size: 14.0 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 "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};
Note: See TracBrowser for help on using the repository browser.