/*--------------------------------------------------------------------------*/
/* */
/* Private data declarations. */
/* */
/*--------------------------------------------------------------------------*/
/*
* Per-directory configuration record identifying the settings of
* allowed clients. Note that this layout is currently tightly
* coupled to the method bit-vector model of Apache 1.3; if and
* when Apache deals with extension methods in some other way,
* this will need to be reworked.
*
*/
typedef struct mseq_conf_t {
int checking[METHODS]; /* TimeLock {On|Off} */
int explain; /* TimeLockReport {On|Off} */
#define FLAG_UNSET 0 /* Used to indicated inherited value */
#define FLAG_ON 1
#define FLAG_OFF 2
char *config_line; /* Complete line from config file */
time_t begin; /* Start of window */
time_t end; /* End of window */
int pos; /* Where is the window positioned? */
#define WINDOW_ATBEGINNING 1
#define WINDOW_BOUNDED 2
#define WINDOW_ATEND 3
int window_is_open; /* Access granted or denied during window? */
} mseq_conf_t;
typedef struct mseq_cond_t {
} mseq_cond_t;
#define ACTION_ALLOW (NULL)
#define ACTION_DENY (&sequester_module)
#define EXPLANATION "\n \n Forbidden\n" \
" \n \n Forbidden
\n" \
" \n Access to the document you have requested is currently\n" \
" forbidden.\n
\n \n %s\n
\n \n\n"
#define TIMEFMT "%a, %d %b %Y %H:%M:%S %Z"
/*
* Declare ourselves so the configuration routines can find and know us.
* We'll fill it in at the end of the module.
*/
module sequester_module;
/*
* Common routine to store a config directive line.
*/
static void save_directive(cmd_parms *cmd, char *args, mseq_conf_t *dconf)
{
dconf->config_line = ap_pstrcat(cmd->pool, cmd->cmd->name, " ",
args, NULL);
}
/*
* Command handler for the "TimeLock {On|Off}" directive.
*/
static const char *cmd_enable(cmd_parms *cmd, void *mconfig, int boolval)
{
mseq_conf_t *dconf;
int i;
int setting;
dconf = (mseq_conf_t *) mconfig;
setting = boolval ? FLAG_ON : FLAG_OFF;
/*
* Record the setting only for the methods to which the current
* directive applies (such as if it appears inside a
* container).
*/
for (i = 0; i < METHODS; ++i) {
if (cmd->limited & (1 << i)) {
dconf->checking[i] = setting;
}
}
return NULL;
}
/*
* Command handler for the "TimeLockReport {On|Off}" directive.
*/
static const char *cmd_report(cmd_parms *cmd, void *mconfig, int boolval)
{
mseq_conf_t *dconf;
int setting;
dconf = (mseq_conf_t *) mconfig;
setting = boolval ? FLAG_ON : FLAG_OFF;
dconf->explain = setting;
return NULL;
}
/*
* Command handler for the "{AllowAfter,DenyUntil} datetime" directives.
*/
static const char *cmd_close_at_beginning(cmd_parms *cmd, void *mconfig,
char *word1)
{
mseq_conf_t *dconf;
time_t endpoint;
dconf = (mseq_conf_t *) mconfig;
endpoint = ap_parseHTTPdate(word1);
if (endpoint == 0) {
return ap_pstrcat(cmd->pool, "Invalid date: \"", word1, "\"", NULL);
}
save_directive(cmd, word1, dconf);
dconf->begin = 0;
dconf->end = endpoint;
dconf->window_is_open = 0;
dconf->pos = WINDOW_ATBEGINNING;
return NULL;
}
/*
* Command handler for the "{AllowUntil,DenyAfter} datetime" directives.
*/
static const char *cmd_close_at_end(cmd_parms *cmd, void *mconfig, char *word1)
{
mseq_conf_t *dconf;
time_t endpoint;
dconf = (mseq_conf_t *) mconfig;
endpoint = ap_parseHTTPdate(word1);
if (endpoint == 0) {
return ap_pstrcat(cmd->pool, "Invalid date: \"", word1, "\"", NULL);
}
save_directive(cmd, word1, dconf);
dconf->begin = endpoint;
dconf->end = -1;
dconf->window_is_open = 0;
dconf->pos = WINDOW_ATEND;
return NULL;
}
/*
* Command handler for the AllowBetween and DenyBetween directives.
*/
static const char *cmd_mark_range(cmd_parms *cmd, void *mconfig,
char *word1, char *word2, char *word3)
{
mseq_conf_t *dconf;
char *start_time;
char *end_time;
time_t begin;
time_t end;
dconf = (mseq_conf_t *) mconfig;
start_time = word1;
end_time = word2;
/*
* Deal with the 'date and date' syntax.
*/
if (word3 != NULL) {
if (strcasecmp(word2, "and") != 0) {
return ap_pstrcat(cmd->pool, "Invalid syntax for ",
cmd->cmd->name, " directive", NULL);
}
end_time = word3;
}
/*
* Make sure the dates are valid.
*/
begin = ap_parseHTTPdate(start_time);
if (begin == 0) {
return ap_pstrcat(cmd->pool, "Invalid date: \"", word1, "\"", NULL);
}
end = ap_parseHTTPdate(end_time);
if (end == 0) {
return ap_pstrcat(cmd->pool, "Invalid date: \"", word2, "\"", NULL);
}
save_directive(cmd,
ap_psprintf(cmd->pool,
(word3 != NULL)
? "\"%s\" %s \"%s\""
: "\"%s\" \"%s\"",
word1, word2, word3),
dconf);
if (begin <= end) {
dconf->begin = begin;
dconf->end = end;
}
else {
dconf->begin = end;
dconf->end = begin;
}
dconf->window_is_open = (cmd->info == ACTION_ALLOW);
dconf->pos = WINDOW_BOUNDED;
return NULL;
}
/*--------------------------------------------------------------------------*/
/* */
/* These routines are strictly internal to this module, and support its */
/* operation. They are not referenced by any external portion of the */
/* server. */
/* */
/*--------------------------------------------------------------------------*/
/*
* This function gets called to create up a per-directory configuration
* record. This will be called for the "default" server environment, and for
* each directory for which the parser finds any of our directives applicable.
* If a directory doesn't have any of our directives involved (i.e., they
* aren't in the .htaccess file, or a , , or related
* block), this routine will *not* be called - the configuration for the
* closest ancestor is used.
*
* The return value is a pointer to the created module-specific
* structure.
*/
static void *mseq_create_dconf(pool *p, char *dirspec)
{
mseq_conf_t *dconf;
int i;
/*
* Allocate the space for our record from the pool supplied.
*/
dconf = (mseq_conf_t *) ap_pcalloc(p, sizeof(mseq_conf_t));
/*
* Now fill in the defaults. If there are any `parent' configuration
* records, they'll get merged as part of a separate callback.
*/
for (i = 0; i < METHODS; ++i) {
dconf->checking[i] = FLAG_UNSET;
}
dconf->explain = FLAG_UNSET;
return (void *) dconf;
}
/*
* This function gets called to merge two per-directory configuration
* records. This is typically done to cope with things like .htaccess files
* or directives for directories that are beneath one for which a
* configuration record was already created. The routine has the
* responsibility of creating a new record and merging the contents of the
* other two into it appropriately. If the module doesn't declare a merge
* routine, the record for the closest ancestor location (that has one) is
* used exclusively.
*
* The routine MUST NOT modify any of its arguments!
*
* The return value is a pointer to the created module-specific structure
* containing the merged values.
*/
static void *mseq_merge_dconf(pool *p, void *parent_conf, void *newloc_conf)
{
mseq_conf_t *merged_config;
mseq_conf_t *pconf = (mseq_conf_t *) parent_conf;
mseq_conf_t *nconf = (mseq_conf_t *) newloc_conf;
int i;
merged_config = (mseq_conf_t *) ap_pcalloc(p, sizeof(mseq_conf_t));
/*
* Inherit the order and activation setting from the closest config,
* or from the parent if the closest one doesn't have an explicit
* setting.
*/
for (i = 0; i < METHODS; ++i) {
merged_config->checking[i] = (nconf->checking[i] == FLAG_UNSET)
? pconf->checking[i] : nconf->checking[i];
}
merged_config->explain = (nconf->explain == FLAG_UNSET)
? pconf->explain : nconf->explain;
/*
* Directly inherit the vault window settings from the closest ancestor.
* No merging here..
*/
merged_config->begin = nconf->begin;
merged_config->end = nconf->end;
merged_config->window_is_open = nconf->window_is_open;
merged_config->pos = nconf->pos;
return (void *) merged_config;
}
/*
* Construct the detail message describing the interdiction. The
* possibilities depend upon whether the request was made:
*
* 1. before the beginning of an indefinite open period,
* 2. outside a bounded blackout period,
* 3. after the beginning of an indefinite closed period, or
* 4. during a bounded open period.
*
* We assume we're only called if the test failed.
*/
static char *detail(request_rec *r, mseq_conf_t *dconf)
{
time_t rtime;
time_t begin;
time_t end;
char *msg;
rtime = r->request_time;
begin = dconf->begin;
end = dconf->end;
switch (dconf->pos) {
case WINDOW_ATBEGINNING:
msg = ap_pstrcat(r->pool, "The resource will become available at ",
ap_ht_time(r->pool, end, TIMEFMT ".", 1), NULL);
break;
case WINDOW_ATEND:
msg = ap_pstrcat(r->pool, "The resource became unavailable at ",
ap_ht_time(r->pool, begin, TIMEFMT ".", 1), NULL);
break;
case WINDOW_BOUNDED:
/*
* Are we being accessed outside of a bounded window when the
* document is available?
*/
if (dconf->window_is_open) {
msg = ap_pstrcat(r->pool,
"The resource ",
(rtime >= begin) ? "was only" : "will only be",
" available between %s and %s.", NULL);
}
/*
* Or is the client trying to get it during a blackout period?
*
else {
msg = "The resource is unavailable between %s and %s.";
}
msg = ap_psprintf(r->pool, msg,
ap_ht_time(r->pool, begin, TIMEFMT, 1),
ap_ht_time(r->pool, end, TIMEFMT, 1));
break;
default:
/*
* Obviously this shouldn't happen.. but it's useful for
* debugging if changes are ever made to the algorithms.
*/
msg = "I don't know why!";
}
return msg;
}
/*
* Check to see if the current request falls into a period when the
* vault is open.
*/
static int mseq_checklock(request_rec *r)
{
mseq_conf_t *dconf;
int result;
time_t rtime;
char *message;
result = OK;
message = NULL;
dconf = (mseq_conf_t *) ap_get_module_config(r->per_dir_config,
&sequester_module);
/*
* If we're not explicitly supposed to be checking, punt so other
* handlers can.
*/
if (dconf->checking[r->method_number] != FLAG_ON) {
return DECLINED;
}
rtime = r->request_time;
/*
* The window is one of opportunity. See if it knocked.
*/
if (dconf->window_is_open) {
if ((rtime < dconf->begin)
|| (rtime >= (unsigned) dconf->end)) {
if (dconf->explain == FLAG_ON) {
message = detail(r, dconf);
}
result = HTTP_FORBIDDEN;
}
}
/*
* The window is a blackout period.
*/
else {
if ((rtime >= dconf->begin)
&& (rtime < (unsigned) dconf->end)) {
if (dconf->explain == FLAG_ON) {
message = detail(r, dconf);
}
result = HTTP_FORBIDDEN;
}
}
/*
* If we're supposed to supply an informative error page,
* whip it up. Otherwise don't waste the memory and cycles.
*/
if ((result != OK) && (dconf->explain == FLAG_ON)) {
message = ap_psprintf(r->pool, EXPLANATION, message);
ap_custom_response(r, result, message);
}
return result;
}
/*--------------------------------------------------------------------------*/
/* */
/* All of the routines have been declared now. Here's the list of */
/* directives specific to our module, and information about where they */
/* may appear and how the command parser should pass them to us for */
/* processing. Note that care must be taken to ensure that there are NO */
/* collisions of directive names between modules. */
/* */
/*--------------------------------------------------------------------------*/
/*
* List of directives specific to our module.
*/
static const command_rec mseq_cmds[] =
{
{
"TimeLock", /* directive name */
cmd_enable, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
FLAG, /* arguments */
"'On' or 'Off' to control sequestration checking"
/* directive argument description */
},
{
"TimeLockReport", /* directive name */
cmd_report, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
FLAG, /* arguments */
"'On' or 'Off' to control descriptive denial messages"
/* directive argument description */
},
{
"AllowAfter", /* directive name */
cmd_close_at_beginning, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE1, /* arguments */
"a date-time string" /* directive argument description */
},
{
"AllowBetween", /* directive name */
cmd_mark_range, /* config action routine */
ACTION_ALLOW, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE23, /* arguments */
"\"start-datetime\" [and] \"end-datetime\""
/* directive argument description */
},
{
"AllowUntil", /* directive name */
cmd_close_at_end, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE1, /* arguments */
"a date-time string" /* directive argument description */
},
{
"DenyAfter", /* directive name */
cmd_close_at_end, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE1, /* arguments */
"a date-time string" /* directive argument description */
},
{
"DenyBetween", /* directive name */
cmd_mark_range, /* config action routine */
ACTION_DENY, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE23, /* arguments */
"\"start-datetime\" [and] \"end-datetime\""
/* directive argument description */
},
{
"DenyUntil", /* directive name */
cmd_close_at_beginning, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE1, /* arguments */
"a date-time string" /* directive argument description */
},
{ NULL }
};
/*--------------------------------------------------------------------------*/
/* */
/* Finally, the list of callback routines and data structures that */
/* provide the hooks into our module from the other parts of the server. */
/* */
/*--------------------------------------------------------------------------*/
/*
* Module definition for configuration. If a particular callback is not
* needed, its slot contains NULL.
*
*/
module sequester_module =
{
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
mseq_create_dconf, /* per-directory config creator */
mseq_merge_dconf, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
mseq_cmds, /* command table */
NULL, /* [7] list of handlers */
NULL, /* [2] filename-to-URI translation */
NULL, /* [5] check/validate user_id */
NULL, /* [6] check user_id is valid *here* */
mseq_checklock, /* [4] check access by host address */
NULL, /* [7] MIME type checker/setter */
NULL, /* [8] fixups */
NULL, /* [10] logger */
#if MODULE_MAGIC_NUMBER >= 19970103
NULL, /* [3] header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
NULL, /* process initializer */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
NULL, /* process exit/cleanup */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
NULL /* [1] post read_request handling */
#endif
};