~ben-links/apache-mod-digest/bensbranch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/* Apache headers */
#include "util_filter.h"
#include "http_config.h"
#include "apr_strings.h"

/* OpenSSL headers */
#include <openssl/evp.h>

module AP_MODULE_DECLARE_DATA digest_module;

typedef struct digest_struct_t {
    EVP_MD_CTX md_ctx;
    unsigned char txt_md[EVP_MAX_MD_SIZE*2 + 1];
    apr_bucket_brigade *tmp_bb;
} digest_struct;

typedef struct {
    const char *digest_type;
} digest_dir_config;

static apr_status_t digest_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    digest_struct *ctx = f->ctx;
    apr_bucket *b;
    apr_read_type_e mode;
    apr_status_t rv;

    // Well-behaved filters should not pass empty brigades down the line.
    if (APR_BRIGADE_EMPTY(bb)) {
        return APR_SUCCESS;
    }

    /* If this is the first invocation for this request, then we should
     * make sure we want to work on it, and create the necessary state.  */
    if (ctx == NULL) {
        digest_dir_config *conf =
            ap_get_module_config(f->r->per_dir_config, &digest_module);
	const EVP_MD *evp_md = EVP_get_digestbyname(conf->digest_type);
	ap_assert(evp_md);

        // We only work on main request, not on subrequests.
        if (f->r->main != NULL) {
            ap_remove_output_filter(f);
            return ap_pass_brigade(f->next, bb);
        }

	/* Actually, we are interested in ranges.
        // We are not interested in digesting content-ranges.
        if (apr_table_get(f->r->headers_out, "Content-Range") != NULL) {
            ap_remove_output_filter(f);
            return ap_pass_brigade(f->next, bb);
        }
	*/

        /* For a 304 or 204 response there is no entity included in
         * the response and hence nothing to digest. */
        if (f->r->status == HTTP_NOT_MODIFIED ||
            f->r->status == HTTP_NO_CONTENT) {
            ap_remove_output_filter(f);
            return ap_pass_brigade(f->next, bb);
        }

        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));

	// Initialise the message digest
	EVP_MD_CTX_init(&ctx->md_ctx);
	EVP_DigestInit_ex(&ctx->md_ctx, evp_md, NULL);

	// Allocate a temporary bucket brigade for later use
	ctx->tmp_bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
    }

    mode = APR_NONBLOCK_READ;
    while ((b = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
	// Only digest non-metadata buckets.
	if (!APR_BUCKET_IS_METADATA(b)) {
	    const char *str;
	    apr_size_t len;

	    rv = apr_bucket_read(b, &str, &len, mode);

	    // Handle a read that will block.
	    if (rv == APR_EAGAIN && mode == APR_NONBLOCK_READ) {
		/* Pass down the temp brigade with a flush bucket appended */
		APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb,
			    apr_bucket_flush_create(ctx->tmp_bb->bucket_alloc));
		rv = ap_pass_brigade(f->next, ctx->tmp_bb);
		apr_brigade_cleanup(ctx->tmp_bb);
		if (rv != APR_SUCCESS)
		    return rv;
		/* Retry, using a blocking read. */
		mode = APR_BLOCK_READ;
		continue;
	    } else if (rv != APR_SUCCESS) {
		// Just give up and return
		return rv;
	    }

	    // Make sure we don't get surprised by truncation.
	    ap_assert(sizeof len <= sizeof(unsigned int));

	    EVP_DigestUpdate(&ctx->md_ctx, str, len);

	    // Always switch back to non-blocking reads
	    mode = APR_NONBLOCK_READ;
	}

	// Now queue the bucket to pass downstream
	APR_BUCKET_REMOVE(b);
	APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);

	if (APR_BUCKET_IS_METADATA(b)) {
	    rv = ap_pass_brigade(f->next, ctx->tmp_bb);
	    apr_brigade_cleanup(ctx->tmp_bb);
	    if (rv != APR_SUCCESS)
		return rv;
	}

        // When the content is finished, we finalize the digest, make a
        // note of it and stop processing
        if (APR_BUCKET_IS_EOS(b)) {
	    int md_length;
	    unsigned char md[EVP_MAX_MD_SIZE];
	    int i;

	    EVP_DigestFinal_ex(&ctx->md_ctx, md, &md_length);
	    ap_assert(md_length <= sizeof md);
	    EVP_MD_CTX_cleanup(&ctx->md_ctx);

            for (i = 0; i < md_length; i++)
               snprintf(&(ctx->txt_md[i*2]), 3, "%02x", (int) md[i]);
            apr_table_setn(f->r->notes, "DIGEST", ctx->txt_md);

	    break;
        }

    }

    // Pass down any queued buckets
    if (!APR_BRIGADE_EMPTY(ctx->tmp_bb)) {
	rv = ap_pass_brigade(f->next, ctx->tmp_bb);
	if (rv != APR_SUCCESS)
	    return rv;
	apr_brigade_cleanup(ctx->tmp_bb);
    }

    return APR_SUCCESS;
}

static void digest_register_hook(apr_pool_t *p)
{
    // Initialise OpenSSL.
    OpenSSL_add_all_algorithms();

    ap_register_output_filter("DIGEST", digest_filter, NULL,
                              AP_FTYPE_RESOURCE);
}

static const char *set_digest_type(cmd_parms *cmd, void *mconfig,
				   const char *type)
{
    digest_dir_config *conf = mconfig;
    if (!EVP_get_digestbyname(type)) {
	return "Unknown digest type";
    }
    conf->digest_type = apr_pstrdup(cmd->pool, type);
    return NULL;
}

static void *create_digest_dir_config(apr_pool_t *p, char *dummy)
{
    digest_dir_config *result = apr_palloc(p, sizeof(digest_dir_config));
    result->digest_type = "sha1";
    return result;
}

static const command_rec digest_cmds[] =
{
    AP_INIT_TAKE1("DigestType", set_digest_type, NULL, OR_OPTIONS,
		  "Any OpenSSL digest name"
		  " (run \"openssl help\" to see a list)"),
    { NULL }
};

module AP_MODULE_DECLARE_DATA digest_module =

{
    STANDARD20_MODULE_STUFF,
    create_digest_dir_config, /* create per-directory config structure */
    NULL,                     /* merge per-directory config structures */
    NULL,                     /* create per-server config structure */
    NULL,                     /* merge per-server config structures */
    digest_cmds,              /* command apr_table_t */
    digest_register_hook      /* register hooks */
};