forked from digitalbazaar/forge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod_fsp.c
415 lines (377 loc) · 13.1 KB
/
mod_fsp.c
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/**
* Flash Socket Policy Apache Module.
*
* This module provides a flash socket policy file on the same port that
* serves HTTP on Apache. This can help simplify setting up a server that
* supports cross-domain communication with flash.
*
* Quick note about Apache memory handling: Data is allocated from pools and
* is not manually returned to those pools. The pools are typically considered
* short-lived and will be cleaned up automatically by Apache.
*
* @author Dave Longley
*
* Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
*/
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "ap_compat.h"
#include <string.h>
// length of a policy file request
#define PFR_LENGTH 23
// declare main module
module AP_MODULE_DECLARE_DATA fsp_module;
// configuration for the module
typedef struct fsp_config
{
// the cross-domain policy to serve
char* policy;
apr_size_t policy_length;
} fsp_config;
// filter state for keeping track of detected policy file requests
typedef struct filter_state
{
fsp_config* cfg;
int checked;
int found;
} filter_state;
// for registering hooks, filters, etc.
static void fsp_register_hooks(apr_pool_t *p);
static int fsp_pre_connection(conn_rec *c, void *csd);
// filter handler declarations
static apr_status_t fsp_input_filter(
ap_filter_t* f, apr_bucket_brigade* bb,
ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes);
static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb);
/**
* Registers the hooks for this module.
*
* @param p the pool to allocate from, if necessary.
*/
static void fsp_register_hooks(apr_pool_t* p)
{
// registers the pre-connection hook to handle adding filters
ap_hook_pre_connection(
fsp_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
// will parse a policy file request, to be added in pre_connection
ap_register_input_filter(
"fsp_request", fsp_input_filter,
NULL, AP_FTYPE_CONNECTION);
// will emit a cross-domain policy response to be added in pre_connection
ap_register_output_filter(
"fsp_response", fsp_output_filter,
NULL, AP_FTYPE_CONNECTION);
}
/**
* A hook that is called before a connection is handled. This function will
* get the module configuration and add the flash socket policy filters if
* a cross-domain policy has been specified in the configuration.
*
* @param c the connection.
* @param csd the connection socket descriptor.
*
* @return OK on success.
*/
static int fsp_pre_connection(conn_rec* c, void* csd)
{
// only install filters if a policy was specified in the module config
fsp_config* cfg = ap_get_module_config(
c->base_server->module_config, &fsp_module);
if(cfg->policy != NULL)
{
// allocate filter state
filter_state* state = apr_palloc(c->pool, sizeof(filter_state));
if(state != NULL)
{
// initialize state
state->cfg = cfg;
state->checked = state->found = 0;
// add filters
ap_add_input_filter("fsp_request", state, NULL, c);
ap_add_output_filter("fsp_response", state, NULL, c);
}
}
return OK;
}
/**
* Searches the input request for a flash socket policy request. This request,
* unfortunately, does not follow the HTTP protocol and cannot be handled
* via a special HTTP handler. Instead, it is a short xml string followed by
* a null character:
*
* '<policy-file-request/>\0'
*
* A peek into the incoming data checks the first character of the stream to
* see if it is '<' (as opposed to typically something else for HTTP). If it
* is not, then this function returns and HTTP input is read normally. If it
* is, then the remaining bytes in the policy-file-request are read and
* checked. If a match is found, then the filter state will be updated to
* inform the output filter to send a cross-domain policy as a response. If
* no match is found, HTTP traffic will proceed as usual.
*
* @param f the input filter.
* @param state the filter state.
*
* @return APR_SUCCESS on success, some other status on failure.
*/
static apr_status_t find_policy_file_request(
ap_filter_t* f, filter_state* state)
{
apr_status_t rval = APR_SUCCESS;
// create a temp buffer for speculative reads
apr_bucket_brigade* tmp = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
// FIXME: not sure how blocking mode works ... can it return fewer than
// the number of specified bytes?
// peek at the first PFR_LENGTH bytes
rval = ap_get_brigade(
f->next, tmp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, PFR_LENGTH);
if(rval == APR_SUCCESS)
{
// quickly check the first bucket for the beginning of a pfr
const char* data;
apr_size_t length;
apr_bucket* b = APR_BRIGADE_FIRST(tmp);
rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
if(rval == APR_SUCCESS && length > 0 && data[0] == '<')
{
// possible policy file request, fill local buffer
char pfr[PFR_LENGTH];
char* ptr = pfr;
memcpy(ptr, data, length);
ptr += length;
memset(ptr, '\0', PFR_LENGTH - length);
b = APR_BUCKET_NEXT(b);
while(rval == APR_SUCCESS && b != APR_BRIGADE_SENTINEL(tmp))
{
rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
if(rval == APR_SUCCESS)
{
memcpy(ptr, data, length);
ptr += length;
b = APR_BUCKET_NEXT(b);
}
}
if(rval == APR_SUCCESS)
{
// see if pfr is a policy file request: '<policy-file-request/>\0'
if((ptr - pfr == PFR_LENGTH) && (pfr[PFR_LENGTH - 1] == '\0') &&
(strncmp(pfr, "<policy-file-request/>", PFR_LENGTH -1) == 0))
{
// pfr found
state->found = 1;
}
}
}
}
return rval;
}
/**
* Handles incoming data. If an attempt has not yet been made to look for
* a policy request (it is the beginning of the connection), then one is
* made. Otherwise this filter does nothing.
*
* If an attempt is made to find a policy request and one is not found, then
* reads proceed as normal. If one is found, then the filter state is modified
* to inform the output filter to send a policy request and the return value
* of this filter is EOF indicating that the connection should close after
* sending the cross-domain policy.
*
* @param f the input filter.
* @param bb the brigate to fill with input from the next filters in the chain
* and then process (look for a policy file request).
* @param mode the type of read requested (ie: AP_MODE_GETLINE means read until
* a CRLF is found, AP_MODE_GETBYTES means 'nbytes' of data, etc).
* @param block APR_BLOCK_READ or APR_NONBLOCK_READ, indicates the type of
* blocking to do when trying to read.
* @param nbytes used if the read mode is appropriate to specify the number of
* bytes to read (set to 0 for AP_MODE_GETLINE).
*
* @return the status of the input (ie: APR_SUCCESS for read success, APR_EOF
* for end of stream, APR_EAGAIN to read again when non-blocking).
*/
static apr_status_t fsp_input_filter(
ap_filter_t* f, apr_bucket_brigade* bb,
ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
{
apr_status_t rval = APR_SUCCESS;
filter_state* state = f->ctx;
if(state->checked == 1)
{
// already checked for policy file request, just read from other filters
rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
}
else
{
// try to find a policy file request
rval = find_policy_file_request(f, state);
state->checked = 1;
if(rval == APR_SUCCESS)
{
if(state->found)
{
// do read of PFR_LENGTH bytes, consider end of stream
rval = ap_get_brigade(
f->next, bb, AP_MODE_READBYTES, APR_BLOCK_READ, PFR_LENGTH);
rval = APR_EOF;
}
else
{
// do normal read
rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
}
}
}
return rval;
}
/**
* Handles outgoing data. If the filter state indicates that a cross-domain
* policy should be sent then it is added to the outgoing brigade of data. If
* a policy request was not detected, then this filter makes no changes to
* the outgoing data.
*
* @param f the output filter.
* @param bb the outgoing brigade of data.
*
* @return APR_SUCCESS on success, some other status on error.
*/
static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb)
{
apr_status_t rval = APR_SUCCESS;
filter_state* state = f->ctx;
if(state->found)
{
// found policy-file-request, add response bucket
// bucket is immortal because the data is stored in the configuration
// and doesn't need to be copied
apr_bucket* head = apr_bucket_immortal_create(
state->cfg->policy, state->cfg->policy_length, bb->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(bb, head);
}
if(rval == APR_SUCCESS)
{
// pass brigade to next filter
rval = ap_pass_brigade(f->next, bb);
}
return rval;
}
/**
* Creates the configuration for this module.
*
* @param p the pool to allocate from.
* @param s the server the configuration is for.
*
* @return the configuration data.
*/
static void* fsp_create_config(apr_pool_t* p, server_rec* s)
{
// allocate config
fsp_config* cfg = apr_palloc(p, sizeof(fsp_config));
// no default policy
cfg->policy = NULL;
cfg->policy_length = 0;
return cfg;
}
/**
* Sets the policy file to use from the configuration.
*
* @param parms the command directive parameters.
* @param userdata NULL, not used.
* @param arg the string argument to the command directive (the file with
* the cross-domain policy to serve as content).
*
* @return NULL on success, otherwise an error string to display.
*/
static const char* fsp_set_policy_file(
cmd_parms* parms, void* userdata, const char* arg)
{
const char* rval = NULL;
apr_pool_t* pool = (apr_pool_t*)parms->pool;
fsp_config* cfg = ap_get_module_config(
parms->server->module_config, &fsp_module);
// ensure command is in the correct context
rval = ap_check_cmd_context(parms, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
if(rval == NULL)
{
// get canonical file name
char* fname = ap_server_root_relative(pool, arg);
if(fname == NULL)
{
rval = (const char*)apr_psprintf(
pool, "%s: Invalid policy file '%s'",
parms->cmd->name, arg);
}
else
{
// try to open the file
apr_status_t rv;
apr_file_t* fd;
apr_finfo_t finfo;
rv = apr_file_open(&fd, fname, APR_READ, APR_OS_DEFAULT, pool);
if(rv == APR_SUCCESS)
{
// stat file
rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd);
if(rv == APR_SUCCESS)
{
// ensure file is not empty
apr_size_t length = (apr_size_t)finfo.size;
if(length <= 0)
{
rval = (const char*)apr_psprintf(
pool, "%s: policy file '%s' is empty",
parms->cmd->name, fname);
}
// read file
else
{
char* buf = (char*)apr_palloc(pool, length + 1);
buf[length] = '\0';
rv = apr_file_read_full(fd, buf, length, NULL);
if(rv == APR_SUCCESS)
{
// TODO: validate file
// save policy string
cfg->policy = buf;
cfg->policy_length = length + 1;
}
}
// close the file
apr_file_close(fd);
}
}
// handle error case
if(rv != APR_SUCCESS)
{
char errmsg[120];
rval = (const char*)apr_psprintf(
pool, "%s: Invalid policy file '%s' (%s)",
parms->cmd->name, fname,
apr_strerror(rv, errmsg, sizeof(errmsg)));
}
}
}
return rval;
}
// table of configuration directives
static const command_rec fsp_cmds[] =
{
AP_INIT_TAKE1(
"FSPPolicyFile", /* the directive */
fsp_set_policy_file, /* function to call when directive is found */
NULL, /* user data to pass to function, not used */
RSRC_CONF, /* indicates the directive appears outside of <Location> */
"FSPPolicyFile (string) The cross-domain policy file to use"), /* docs */
{NULL}
};
// module setup
module AP_MODULE_DECLARE_DATA fsp_module =
{
STANDARD20_MODULE_STUFF, /* stuff declared in every 2.0 mod */
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
fsp_create_config, /* create per-server config structure */
NULL, /* merge per-server config structures */
fsp_cmds, /* command apr_table_t */
fsp_register_hooks /* register hooks */
};