/* ====================================================================
* Copyright (c) 1999, MeepZor Consulting.
* 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_access_identd.c,v 1.2 1999/07/12 15:14:11 coar Exp $
*
* Abstract:
*+
* A security module for the Apache Web server, supplying mandatory
* access control based upon the client username and host. The
* credentials are obtained using the identd (RFC1413) mechanism,
* so this is of limited usefulness if document access is through a
* proxy or by clients not running an RFC1413 server daemon. As a
* result, this module is best suited for intranets.
*-
*
* Package name: mod_access_identd
* Package version: 1.0.1
* Package files:
* INSTALL.mod_access_identd
* README.mod_access_identd
* mod_access_identd.c
* mod_access_identd.cfgpatch
* mod_access_identd.docpatch
* mod_access_identd.html
*
*/
/*
* Apache identd-based (RFC1413) mandatory access control module.
* Access is checked and a decision is made based upon the client's
* host name (or address) and username; this takes place before
* the point at which any user-supplied credentials are checked
* (or even requested, if not already available).
*/
#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 "fnmatch.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 mai_conf_t {
int checking[METHODS]; /* IdentCheck {On|Off} */
#define CHECK_UNSET 0 /* Used to indicated inherited value */
#define CHECK_ON 1
#define CHECK_OFF 2
int order[METHODS]; /* IdentOrder {allow,deny|deny,allow} */
#define ORDER_UNSET 0 /* Inherited value rather than locally set */
#define ORDER_ALLOW_DENY 1
#define ORDER_DENY_ALLOW 2
array_header *allowed; /* The only reason for wildecard */
array_header *allowed_wild; /* patterns to be listed separately */
array_header *denied; /* is to allow matching non-wildcards */
array_header *denied_wild; /* to be tested first (less processing) */
} mai_conf_t;
/*
* Structure for an individual pattern in an Ident directive line.
*/
typedef struct mai_irec_t {
char *pattern; /* "user@host", "user@address", or "all" */
char *user;
char *host;
int match; /* Notes about wildcards */
#define MATCH_EXACT 0;
#define MATCH_ALL 1 /* "*", "*@*", or "all" */
#define MATCH_ALL_USERS 2 /* "*@host" or "*@address" */
#define MATCH_ALL_HOSTS 3 /* "user@*" */
} mai_irec_t;
/*
* Declare ourselves so the configuration routines can find and know us.
* We'll fill it in at the end of the module.
*/
module access_identd_module;
/*
* Command handler for the "IdentCheck {On|Off}" directive.
*/
static const char *cmd_enable(cmd_parms *cmd, void *mconfig, int boolval)
{
mai_conf_t *dconf;
int i;
int setting;
dconf = (mai_conf_t *) mconfig;
setting = boolval ? CHECK_ON : CHECK_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 "IdentOrder {allow,deny|deny,allow}" directive.
*/
static const char *cmd_order(cmd_parms *cmd, void *mconfig, char *word1)
{
mai_conf_t *dconf;
int i;
int order;
dconf = (mai_conf_t *) mconfig;
if (strcasecmp(word1, "allow,deny") == 0) {
order = ORDER_ALLOW_DENY;
}
else if (strcasecmp(word1, "deny,allow") == 0) {
order = ORDER_DENY_ALLOW;
}
else {
return "unrecognised order";
}
/*
* Note the order for all methods being limited.
*/
for (i = 0; i < METHODS; ++i) {
if (cmd->limited & (1 << i)) {
dconf->order[i] = order;
}
}
return NULL;
}
/*
* Command handler for the "Ident {allow|deny} user@host ..." directive.
*/
static const char *cmd_idaccess(cmd_parms *cmd, void *mconfig, char *word1,
char *word2)
{
mai_conf_t *dconf;
mai_irec_t item;
mai_irec_t *irec;
char *lhs;
char *rhs;
array_header *list;
int wildcard = 0;
dconf = (mai_conf_t *) mconfig;
item.match = MATCH_EXACT;
item.pattern = ap_pstrdup(cmd->pool, word2);
/*
* Pick the pattern apart into its components, if any, and store them
* separately.
*/
lhs = word2;
rhs = strchr(word2, '@');
if (rhs == NULL) {
item.user = ap_pstrdup(cmd->pool, lhs);
item.host = NULL;
}
else {
item.user = ap_pstrndup(cmd->pool, lhs, (rhs - lhs));
item.host = ap_pstrdup(cmd->pool, ++rhs);
}
/*
* Find out if the pattern includes any wildcards. If it does, we can
* short-circuit some runtime processing.
*/
wildcard = (strchr(word2, '*') != NULL)
|| (strchr(word2, '?') != NULL)
|| (strchr(word2, '[') != NULL)
|| (strchr(word2, ']') != NULL)
|| (strcasecmp(word2, "all") == 0);
if (wildcard) {
if ((strcmp(word2, "*") == 0)
|| (strcmp(word2, "*@*") == 0)
|| (strcasecmp(word2, "all") == 0)) {
item.match = MATCH_ALL;
}
else if (strncmp(word2, "*@", 2) == 0) {
item.match = MATCH_ALL_USERS;
}
else if (strstr(word2, "@*") != NULL) {
item.match = MATCH_ALL_HOSTS;
}
}
/*
* Now that it's parsed, a final check for validity..
*/
if ((item.match != MATCH_ALL)
&& ((item.user == NULL) || (item.host == NULL))) {
return ap_psprintf(cmd->pool,
"%s (line %d): malformed identity pattern: '%s'",
cmd->cmd->name, cmd->config_file->line_number,
word2);
}
/*
* Figure out to which of the lists this pattern should be added.
*/
if (strcasecmp(word1, "allow") == 0) {
list = wildcard ? dconf->allowed_wild : dconf->allowed;
}
else if (strcasecmp(word1, "deny") == 0) {
list = wildcard ? dconf->denied_wild : dconf->denied;
}
else {
return "first keyword must be either 'allow' or 'deny'";
}
/*
* All set; add the information to the config record and depart.
*/
irec = (mai_irec_t *) ap_push_array(list);
irec->pattern = item.pattern;
irec->user = item.user;
irec->host = item.host;
irec->match = item.match;
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. */
/* */
/*--------------------------------------------------------------------------*/
/*
* Locate our directory configuration record for the current request.
*/
static mai_conf_t *our_dconfig(request_rec *r)
{
return (mai_conf_t *) ap_get_module_config(r->per_dir_config,
&access_identd_module);
}
/*
* 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 *mai_create_dconf(pool *p, char *dirspec)
{
mai_conf_t *dconf;
int i;
/*
* Allocate the space for our record from the pool supplied.
*/
dconf = (mai_conf_t *) ap_pcalloc(p, sizeof(mai_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->order[i] = ORDER_UNSET;
dconf->checking[i] = CHECK_UNSET;
}
dconf->allowed = ap_make_array(p, 2, sizeof(mai_irec_t));
dconf->allowed_wild = ap_make_array(p, 2, sizeof(mai_irec_t));
dconf->denied = ap_make_array(p, 2, sizeof(mai_irec_t));
dconf->denied_wild = ap_make_array(p, 2, sizeof(mai_irec_t));
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 *mai_merge_dconf(pool *p, void *parent_conf, void *newloc_conf)
{
mai_conf_t *merged_config;
mai_conf_t *pconf = (mai_conf_t *) parent_conf;
mai_conf_t *nconf = (mai_conf_t *) newloc_conf;
int i;
merged_config = (mai_conf_t *) ap_pcalloc(p, sizeof(mai_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->order[i] = (nconf->order[i] == ORDER_UNSET)
? pconf->order[i] : nconf->order[i];
merged_config->checking[i] = (nconf->checking[i] == CHECK_UNSET)
? pconf->checking[i] : nconf->checking[i];
}
/*
* Merge the patterns and allow/deny settings, giving the
* closer ones (nconf) preference by putting them first in
* the final arrays.
*/
merged_config->allowed = ap_append_arrays(p, nconf->allowed,
pconf->allowed);
merged_config->allowed_wild = ap_append_arrays(p, nconf->allowed_wild,
pconf->allowed_wild);
merged_config->denied = ap_append_arrays(p, nconf->denied,
pconf->denied);
merged_config->denied_wild = ap_append_arrays(p, nconf->denied_wild,
pconf->denied_wild);
return (void *) merged_config;
}
#define USERFLAG (0)
#define HOSTFLAG (FNM_CASE_BLIND)
/*
* Check to see if the credentials match any of the items in a list.
* If so, return the matching entry (so it can be used to construct
* a log message if necessary); otherwise return NULL.
*/
static mai_irec_t *mai_idmatch(array_header *list, const char *ruser,
const char *rhostname, char *rhostaddr)
{
int i;
mai_irec_t *irec;
mai_irec_t *items;
items = (mai_irec_t *) list->elts;
for (i = 0; i < list->nelts; ++i) {
irec = &items[i];
if ((irec->match == MATCH_ALL)
|| ((irec->match == MATCH_ALL_HOSTS)
&& (ap_fnmatch(irec->user, ruser, USERFLAG) == 0))
|| ((irec->match == MATCH_ALL_USERS)
&& ((ap_fnmatch(irec->host, rhostname, HOSTFLAG) == 0)
|| (ap_fnmatch(irec->host, rhostaddr, HOSTFLAG) == 0)))
|| ((ap_fnmatch(irec->user, ruser, USERFLAG) == 0)
&& ((ap_fnmatch(irec->host, rhostname, HOSTFLAG) == 0)
|| (ap_fnmatch(irec->host, rhostaddr, HOSTFLAG) == 0)))) {
return irec;
}
}
return NULL;
}
/*
* Check the client's RFC1413 identity against our access lists.
* The return value is OK, DECLINED, or HTTP_mumble.
*/
static int mai_idcheck(request_rec *r)
{
mai_conf_t *dconf;
mai_irec_t *match;
int result;
const char *ruser;
const char *rhostname;
char *rhostaddr;
char *errmsg = "unknown reason";
dconf = our_dconfig(r);
/*
* If our checking is turned off in this scope, don't do anything.
*/
if (dconf->checking[r->method_number] != CHECK_ON) {
return DECLINED;
}
ruser = ap_get_remote_logname(r);
/*
* If we're requiring an RFC1413 identity and we can't get one,
* deny access with no questions.
*/
if (((ruser == NULL) || (strcmp(ruser, "unknown") == 0))
&& ((ap_satisfies(r) != SATISFY_ANY)
|| (!ap_some_auth_required(r)))) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r,
"access denied to %s : identd response required "
"from client and not available", r->uri);
return HTTP_FORBIDDEN;
}
rhostname = ap_get_remote_host(r->connection, r->per_dir_config,
REMOTE_DOUBLE_REV);
rhostaddr = r->connection->remote_ip;
/*
* We use "deny,allow" if it was explicitly specified or as a
* default if no order has been chosen.
*/
result = (dconf->order[r->method_number] == ORDER_ALLOW_DENY)
? HTTP_FORBIDDEN : OK;
/*
* We have all our information, so now let's walk through the lists
* and do the actual evaluation.
*/
if (dconf->order[r->method_number] == ORDER_ALLOW_DENY) {
match = mai_idmatch(dconf->allowed, ruser, rhostname, rhostaddr);
if (match == NULL) {
/*
* Not found, check the wildcard list to see if one of those
* catches it.
*/
match = mai_idmatch(dconf->allowed_wild, ruser, rhostname,
rhostaddr);
}
/*
* We have to have at least one matched 'allow' clause, or
* it's no use going on.
*/
if (match == NULL) {
errmsg = ap_psprintf(r->pool,
"no 'allow' rule permitting ident=%s@%s",
ruser, rhostname);
result = HTTP_FORBIDDEN;
}
else {
/*
* We're allowed so far; let's see if something explicitly
* shuts us out.
*/
result = OK;
match = mai_idmatch(dconf->denied, ruser, rhostname, rhostaddr);
if (match == NULL) {
match = mai_idmatch(dconf->denied_wild, ruser, rhostname,
rhostaddr);
}
if (match != NULL) {
result = HTTP_FORBIDDEN;
errmsg = ap_psprintf(r->pool, "denied by pattern '%s'",
match->pattern);
}
}
}
else {
/*
* Check the 'deny' rules first.
*/
match = mai_idmatch(dconf->denied, ruser, rhostname, rhostaddr);
if (match == NULL) {
match = mai_idmatch(dconf->denied_wild, ruser, rhostname,
rhostaddr);
}
if (match != NULL) {
/*
* Something has blocked us out; assume it won't be overridden.
*/
result = HTTP_FORBIDDEN;
errmsg = ap_psprintf(r->pool, "denied by pattern '%s'",
match->pattern);
/*
* Only bother checking for 'allow' rules if we haven't been
* blocked yet,
*/
match = mai_idmatch(dconf->allowed, ruser, rhostname, rhostaddr);
if (match == NULL) {
match = mai_idmatch(dconf->allowed_wild, ruser, rhostname,
rhostaddr);
}
if (match != NULL) {
/*
* Found one cancelling the blockage. Issue a special
* log message for this condition, since we're allowing
* access in a primarily disallowed environment; this may be
* useful in case of an unintended pattern match. The
* message is INFO rather than ERROR severity.
*/
result = OK;
ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, r,
"access to %s granted to %s@%s by pattern '%s'",
r->uri, ruser,
(rhostname != NULL) ? rhostname : rhostaddr,
match->pattern);
}
}
}
/*
* If our decision is final (that is, it can't be overridden by
* some discretionary access checking), say so. We really ought to
* point out here that *we* said no but someone else might say yes,
* if that's the case.. unfortunately, since modules typically
* don't log a message when they *allow* access, we'd probably only
* be confusing matters. I don't think the discretionary checks
* can tell what result the mandatory ones return, so they couldn't
* even be retrofitted to play with us on this.
*/
if ((result == HTTP_FORBIDDEN)
&& ((ap_satisfies(r) != SATISFY_ANY)
|| (!ap_some_auth_required(r)))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r,
"access denied to %s : %s", r->uri, errmsg);
}
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 mai_cmds[] =
{
{
"IdentCheck", /* directive name */
cmd_enable, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
FLAG, /* arguments */
"'On' or 'Off' to control identd access checking"
/* directive argument description */
},
{
"IdentOrder", /* directive name */
cmd_order, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
TAKE1, /* arguments */
"'allow,deny' or 'deny,allow'"
/* directive argument description */
},
{
"Ident", /* directive name */
cmd_idaccess, /* config action routine */
NULL, /* argument to include in call */
OR_AUTHCFG, /* where available */
ITERATE2, /* arguments */
"{allow|deny} followed by list of host@ident pairs"
/* 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 access_identd_module =
{
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
mai_create_dconf, /* per-directory config creator */
mai_merge_dconf, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
mai_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* */
mai_idcheck, /* [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
};