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