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 */
};
|