/* ==================================================================== * Copyright (c) 2000, Ken Coar. * All rights reserved. * * The use and distribution of this code or document is * governed by version 1.0.1 of the MeepZor Consulting Public * Licence (MCPL), which may be found on the Internet at * . * * * $Id: mod_sequester.c,v 1.8 2001/06/15 14:14:03 coar Exp $ * * Abstract: *+ * A security module for the Apache Web server, providing a 'time-lock' * capability of allowing or forbidding access based on date and time. *- * * Package name: mod_sequester * Package version: 1.0.0 * Package files: * INSTALL * README * mod_sequester.c * mod_sequester.cfgpatch * mod_sequester.docpatch * mod_sequester.html * */ /* * Apache time-lock involuntary access control module. * Access is checked and a decision is made based upon the date & time * on the server and the criteria defined by the directives. */ #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "util_date.h" #include /*--------------------------------------------------------------------------*/ /* */ /* 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 };