#include <httpd.h>
#include <http_config.h>
#include <http_core.h>
#include <http_request.h>
#include <http_protocol.h>
#include <http_log.h>
#include <http_config.h>
#include <ap_config.h>
#include <ap_provider.h>
#include <ap_mmn.h>
#include <apr_uri.h>
#include <apr_lib.h>
#include <mod_dav.h>
#include "mod_dav_svn.h"
#include "mod_authz_svn.h"
#include "svn_path.h"
#include "svn_config.h"
#include "svn_string.h"
#include "svn_repos.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "private/svn_fspath.h"
#ifdef PACKAGE_BUGREPORT
#undef PACKAGE_BUGREPORT
#endif
#ifdef PACKAGE_NAME
#undef PACKAGE_NAME
#endif
#ifdef PACKAGE_STRING
#undef PACKAGE_STRING
#endif
#ifdef PACKAGE_TARNAME
#undef PACKAGE_TARNAME
#endif
#ifdef PACKAGE_VERSION
#undef PACKAGE_VERSION
#endif
#include "svn_private_config.h"
#ifdef APLOG_USE_MODULE
APLOG_USE_MODULE(authz_svn);
#else
extern module AP_MODULE_DECLARE_DATA authz_svn_module;
#endif
typedef struct authz_svn_config_rec {
int authoritative;
int anonymous;
int no_auth_when_anon_ok;
const char *base_path;
const char *access_file;
const char *repo_relative_access_file;
const char *groups_file;
const char *force_username_case;
} authz_svn_config_rec;
#if AP_MODULE_MAGIC_AT_LEAST(20060110,0)
# if AP_MODULE_MAGIC_AT_LEAST(20120211,47) || defined(SVN_USE_FORCE_AUTHN)
# define USE_FORCE_AUTHN 1
# define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn"
# define FORCE_AUTHN_NOTE "authz_svn-force-authn"
# else
# ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH
# error This Apache httpd has broken auth (CVE-2015-3184)
# else
# define USE_FORCE_AUTHN 0
# endif
# endif
#else
# define USE_FORCE_AUTHN 0
#endif
static void *
create_authz_svn_dir_config(apr_pool_t *p, char *d)
{
authz_svn_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
conf->base_path = d;
if (d)
conf->base_path = svn_urlpath__canonicalize(d, p);
conf->authoritative = 1;
conf->anonymous = 1;
return conf;
}
static const char *
canonicalize_access_file(const char *access_file,
svn_boolean_t server_relative,
apr_pool_t *pool)
{
if (svn_path_is_url(access_file))
{
access_file = svn_uri_canonicalize(access_file, pool);
}
else if (!svn_path_is_repos_relative_url(access_file))
{
if (server_relative)
{
access_file = ap_server_root_relative(pool, access_file);
if (access_file == NULL)
return NULL;
}
access_file = svn_dirent_internal_style(access_file, pool);
}
return access_file;
}
static const char *
AuthzSVNAccessFile_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
authz_svn_config_rec *conf = config;
if (conf->repo_relative_access_file != NULL)
return "AuthzSVNAccessFile and AuthzSVNReposRelativeAccessFile "
"directives are mutually exclusive.";
conf->access_file = canonicalize_access_file(arg1, TRUE, cmd->pool);
if (!conf->access_file)
return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
return NULL;
}
static const char *
AuthzSVNReposRelativeAccessFile_cmd(cmd_parms *cmd,
void *config,
const char *arg1)
{
authz_svn_config_rec *conf = config;
if (conf->access_file != NULL)
return "AuthzSVNAccessFile and AuthzSVNReposRelativeAccessFile "
"directives are mutually exclusive.";
conf->repo_relative_access_file = canonicalize_access_file(arg1, FALSE,
cmd->pool);
if (!conf->repo_relative_access_file)
return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
return NULL;
}
static const char *
AuthzSVNGroupsFile_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
authz_svn_config_rec *conf = config;
conf->groups_file = canonicalize_access_file(arg1, TRUE, cmd->pool);
if (!conf->groups_file)
return apr_pstrcat(cmd->pool, "Invalid file path ", arg1, SVN_VA_NULL);
return NULL;
}
static const command_rec authz_svn_cmds[] =
{
AP_INIT_FLAG("AuthzSVNAuthoritative", ap_set_flag_slot,
(void *)APR_OFFSETOF(authz_svn_config_rec, authoritative),
OR_AUTHCFG,
"Set to 'Off' to allow access control to be passed along to "
"lower modules. (default is On.)"),
AP_INIT_TAKE1("AuthzSVNAccessFile", AuthzSVNAccessFile_cmd,
NULL,
OR_AUTHCFG,
"Path to text file containing permissions of repository "
"paths. Path may be an repository relative URL (^/) or "
"absolute file:// URL to a text file in a Subversion "
"repository."),
AP_INIT_TAKE1("AuthzSVNReposRelativeAccessFile",
AuthzSVNReposRelativeAccessFile_cmd,
NULL,
OR_AUTHCFG,
"Path (relative to repository 'conf' directory) to text "
"file containing permissions of repository paths. Path may "
"be an repository relative URL (^/) or absolute file:// URL "
"to a text file in a Subversion repository."),
AP_INIT_TAKE1("AuthzSVNGroupsFile",
AuthzSVNGroupsFile_cmd,
NULL,
OR_AUTHCFG,
"Path to text file containing group definitions for all "
"repositories. Path may be an repository relative URL (^/) "
"or absolute file:// URL to a text file in a Subversion "
"repository."),
AP_INIT_FLAG("AuthzSVNAnonymous", ap_set_flag_slot,
(void *)APR_OFFSETOF(authz_svn_config_rec, anonymous),
OR_AUTHCFG,
"Set to 'Off' to disable two special-case behaviours of "
"this module: (1) interaction with the 'Satisfy Any' "
"directive, and (2) enforcement of the authorization "
"policy even when no 'Require' directives are present. "
"(default is On.)"),
AP_INIT_FLAG("AuthzSVNNoAuthWhenAnonymousAllowed", ap_set_flag_slot,
(void *)APR_OFFSETOF(authz_svn_config_rec,
no_auth_when_anon_ok),
OR_AUTHCFG,
"Set to 'On' to suppress authentication and authorization "
"for requests which anonymous users are allowed to perform. "
"(default is Off.)"),
AP_INIT_TAKE1("AuthzForceUsernameCase", ap_set_string_slot,
(void *)APR_OFFSETOF(authz_svn_config_rec,
force_username_case),
OR_AUTHCFG,
"Set to 'Upper' or 'Lower' to convert the username before "
"checking for authorization."),
{ NULL }
};
#if AP_MODULE_MAGIC_AT_LEAST(20100606,0)
#define LOG_ARGS_SIGNATURE const char *file, int line, int module_index
#define LOG_ARGS_CASCADE file, line, module_index
#else
#define LOG_ARGS_SIGNATURE const char *file, int line
#define LOG_ARGS_CASCADE file, line
#endif
static void
log_access_verdict(LOG_ARGS_SIGNATURE,
const request_rec *r, int allowed, int is_subreq_bypass,
const char *repos_path, const char *dest_repos_path)
{
int level = allowed ? APLOG_INFO : APLOG_ERR;
const char *verdict = allowed ? "granted" : "denied";
if (is_subreq_bypass)
level = APLOG_INFO;
else if (r->main && r->method_number == M_GET)
level = APLOG_INFO;
if (r->user)
{
if (dest_repos_path)
ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
"Access %s: '%s' %s %s %s", verdict, r->user,
r->method, repos_path, dest_repos_path);
else
ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
"Access %s: '%s' %s %s", verdict, r->user,
r->method, repos_path);
}
else
{
if (dest_repos_path)
ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
"Access %s: - %s %s %s", verdict,
r->method, repos_path, dest_repos_path);
else
ap_log_rerror(LOG_ARGS_CASCADE, level, 0, r,
"Access %s: - %s %s", verdict,
r->method, repos_path);
}
}
static void
log_svn_error(LOG_ARGS_SIGNATURE,
request_rec *r, const char *prefix,
svn_error_t *err, apr_pool_t *scratch_pool)
{
svn_error_t *err_pos = svn_error_purge_tracing(err);
svn_stringbuf_t *buff = svn_stringbuf_create(prefix, scratch_pool);
while (err_pos)
{
svn_stringbuf_appendbyte(buff, ' ');
if (err_pos->message)
{
svn_stringbuf_appendcstr(buff, err_pos->message);
}
else
{
char strerr[256];
svn_stringbuf_appendcstr(buff, svn_strerror(err->apr_err, strerr,
sizeof(strerr)));
}
err_pos = err_pos->child;
}
ap_log_rerror(LOG_ARGS_CASCADE, APLOG_ERR,
((err->apr_err >= APR_OS_START_USERERR &&
err->apr_err < APR_OS_START_CANONERR) ?
0 : err->apr_err),
r, "%s", buff->data);
svn_error_clear(err);
}
static svn_error_t *
resolve_repos_relative_url(const char **path, const char **repos_url,
const char *repos_path, apr_pool_t *pool)
{
if (svn_path_is_repos_relative_url(*path))
{
if (!*repos_url)
SVN_ERR(svn_uri_get_file_url_from_dirent(repos_url, repos_path, pool));
SVN_ERR(svn_path_resolve_repos_relative_url(path, *path,
*repos_url, pool));
*path = svn_uri_canonicalize(*path, pool);
}
return SVN_NO_ERROR;
}
static svn_authz_t *
get_access_conf(request_rec *r, authz_svn_config_rec *conf,
apr_pool_t *scratch_pool)
{
const char *cache_key = NULL;
const char *access_file;
const char *groups_file;
const char *repos_path;
const char *repos_url = NULL;
void *user_data = NULL;
svn_authz_t *access_conf = NULL;
svn_error_t *svn_err = SVN_NO_ERROR;
dav_error *dav_err;
dav_err = dav_svn_get_repos_path2(r, conf->base_path, &repos_path, scratch_pool);
if (dav_err)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", dav_err->desc);
return NULL;
}
if (conf->repo_relative_access_file)
{
access_file = conf->repo_relative_access_file;
if (!svn_path_is_repos_relative_url(access_file) &&
!svn_path_is_url(access_file))
{
access_file = svn_dirent_join_many(scratch_pool, repos_path, "conf",
conf->repo_relative_access_file,
SVN_VA_NULL);
}
}
else
{
access_file = conf->access_file;
}
groups_file = conf->groups_file;
svn_err = resolve_repos_relative_url(&access_file, &repos_url, repos_path,
scratch_pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
conf->repo_relative_access_file ?
"Failed to load the AuthzSVNReposRelativeAccessFile:" :
"Failed to load the AuthzSVNAccessFile:",
svn_err, scratch_pool);
return NULL;
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Path to authz file is %s", access_file);
if (groups_file)
{
svn_err = resolve_repos_relative_url(&groups_file, &repos_url, repos_path,
scratch_pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
"Failed to load the AuthzSVNGroupsFile:",
svn_err, scratch_pool);
return NULL;
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Path to groups file is %s", groups_file);
}
cache_key = apr_pstrcat(scratch_pool, "mod_authz_svn:",
access_file, groups_file, SVN_VA_NULL);
apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
access_conf = user_data;
if (access_conf == NULL)
{
svn_err = svn_repos_authz_read2(&access_conf, access_file,
groups_file, TRUE,
r->connection->pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
"Failed to load the mod_authz_svn config:",
svn_err, scratch_pool);
access_conf = NULL;
}
else
{
apr_pool_userdata_set(access_conf, cache_key,
NULL, r->connection->pool);
}
}
return access_conf;
}
static void
convert_case(char *text, svn_boolean_t to_uppercase)
{
char *c = text;
while (*c)
{
*c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
++c;
}
}
static char *
get_username_to_authorize(request_rec *r, authz_svn_config_rec *conf,
apr_pool_t *pool)
{
char *username_to_authorize = r->user;
if (username_to_authorize && conf->force_username_case)
{
username_to_authorize = apr_pstrdup(pool, r->user);
convert_case(username_to_authorize,
strcasecmp(conf->force_username_case, "upper") == 0);
}
return username_to_authorize;
}
static int
req_check_access(request_rec *r,
authz_svn_config_rec *conf,
const char **repos_path_ref,
const char **dest_repos_path_ref)
{
const char *dest_uri;
apr_uri_t parsed_dest_uri;
const char *cleaned_uri;
int trailing_slash;
const char *repos_name;
const char *dest_repos_name;
const char *relative_path;
const char *repos_path;
const char *dest_repos_path = NULL;
dav_error *dav_err;
svn_repos_authz_access_t authz_svn_type = svn_authz_none;
svn_boolean_t authz_access_granted = FALSE;
svn_authz_t *access_conf = NULL;
svn_error_t *svn_err;
const char *username_to_authorize = get_username_to_authorize(r, conf,
r->pool);
switch (r->method_number)
{
case M_COPY:
authz_svn_type |= svn_authz_recursive;
case M_OPTIONS:
case M_GET:
case M_PROPFIND:
case M_REPORT:
authz_svn_type |= svn_authz_read;
break;
case M_MOVE:
case M_DELETE:
authz_svn_type |= svn_authz_recursive;
case M_MKCOL:
case M_PUT:
case M_PROPPATCH:
case M_CHECKOUT:
case M_MERGE:
case M_MKACTIVITY:
case M_LOCK:
case M_UNLOCK:
authz_svn_type |= svn_authz_write;
break;
default:
authz_svn_type |= svn_authz_write | svn_authz_recursive;
break;
}
if (strcmp(svn_urlpath__canonicalize(r->uri, r->pool), conf->base_path) == 0)
{
return OK;
}
dav_err = dav_svn_split_uri(r,
r->uri,
conf->base_path,
&cleaned_uri,
&trailing_slash,
&repos_name,
&relative_path,
&repos_path);
if (dav_err)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s [%d, #%d]",
dav_err->desc, dav_err->status, dav_err->error_id);
return (dav_err->status != OK && dav_err->status != DECLINED) ?
dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
}
if (r->method_number == M_MERGE)
repos_path = NULL;
if (repos_path)
repos_path = svn_fspath__canonicalize(repos_path, r->pool);
*repos_path_ref = apr_pstrcat(r->pool, repos_name, ":", repos_path,
SVN_VA_NULL);
if (r->method_number == M_MOVE || r->method_number == M_COPY)
{
apr_status_t status;
dest_uri = apr_table_get(r->headers_in, "Destination");
if (!dest_uri)
return DECLINED;
status = apr_uri_parse(r->pool, dest_uri, &parsed_dest_uri);
if (status)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
"Invalid URI in Destination header");
return HTTP_BAD_REQUEST;
}
if (!parsed_dest_uri.path)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Invalid URI in Destination header");
return HTTP_BAD_REQUEST;
}
ap_unescape_url(parsed_dest_uri.path);
dest_uri = parsed_dest_uri.path;
if (strncmp(dest_uri, conf->base_path, strlen(conf->base_path)))
{
return HTTP_BAD_REQUEST;
}
dav_err = dav_svn_split_uri(r,
dest_uri,
conf->base_path,
&cleaned_uri,
&trailing_slash,
&dest_repos_name,
&relative_path,
&dest_repos_path);
if (dav_err)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s [%d, #%d]",
dav_err->desc, dav_err->status, dav_err->error_id);
return (dav_err->status != OK && dav_err->status != DECLINED) ?
dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
}
if (dest_repos_path)
dest_repos_path = svn_fspath__canonicalize(dest_repos_path, r->pool);
*dest_repos_path_ref = apr_pstrcat(r->pool, dest_repos_name, ":",
dest_repos_path, SVN_VA_NULL);
}
access_conf = get_access_conf(r,conf, r->pool);
if (access_conf == NULL)
return DECLINED;
if (repos_path
|| (!repos_path && (authz_svn_type & svn_authz_write)))
{
svn_err = svn_repos_authz_check_access(access_conf, repos_name,
repos_path,
username_to_authorize,
authz_svn_type,
&authz_access_granted,
r->pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
"Failed to perform access control:",
svn_err, r->pool);
return DECLINED;
}
if (!authz_access_granted)
return DECLINED;
}
if (r->method_number != M_MOVE && r->method_number != M_COPY)
return OK;
if (repos_path)
{
svn_err = svn_repos_authz_check_access(access_conf,
dest_repos_name,
dest_repos_path,
username_to_authorize,
svn_authz_write
|svn_authz_recursive,
&authz_access_granted,
r->pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
"Failed to perform access control:",
svn_err, r->pool);
return DECLINED;
}
if (!authz_access_granted)
return DECLINED;
}
return OK;
}
static int
subreq_bypass2(request_rec *r,
const char *repos_path,
const char *repos_name,
apr_pool_t *scratch_pool)
{
svn_error_t *svn_err = NULL;
svn_authz_t *access_conf = NULL;
authz_svn_config_rec *conf = NULL;
svn_boolean_t authz_access_granted = FALSE;
const char *username_to_authorize;
conf = ap_get_module_config(r->per_dir_config,
&authz_svn_module);
username_to_authorize = get_username_to_authorize(r, conf, scratch_pool);
if (!conf->anonymous
|| (! (conf->access_file || conf->repo_relative_access_file)))
{
log_access_verdict(APLOG_MARK, r, 0, TRUE, repos_path, NULL);
return HTTP_FORBIDDEN;
}
access_conf = get_access_conf(r, conf, scratch_pool);
if (access_conf == NULL)
return HTTP_FORBIDDEN;
if (repos_path)
{
svn_err = svn_repos_authz_check_access(access_conf, repos_name,
repos_path,
username_to_authorize,
svn_authz_none|svn_authz_read,
&authz_access_granted,
scratch_pool);
if (svn_err)
{
log_svn_error(APLOG_MARK, r,
"Failed to perform access control:",
svn_err, scratch_pool);
return HTTP_FORBIDDEN;
}
if (!authz_access_granted)
{
log_access_verdict(APLOG_MARK, r, 0, TRUE, repos_path, NULL);
return HTTP_FORBIDDEN;
}
}
log_access_verdict(APLOG_MARK, r, 1, TRUE, repos_path, NULL);
return OK;
}
static int
subreq_bypass(request_rec *r,
const char *repos_path,
const char *repos_name)
{
int status;
apr_pool_t *scratch_pool;
scratch_pool = svn_pool_create(r->pool);
status = subreq_bypass2(r, repos_path, repos_name, scratch_pool);
svn_pool_destroy(scratch_pool);
return status;
}
static int
access_checker(request_rec *r)
{
authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
&authz_svn_module);
const char *repos_path = NULL;
const char *dest_repos_path = NULL;
int status, authn_required;
#if USE_FORCE_AUTHN
int authn_configured;
if (!conf->anonymous || apr_table_get(r->notes, IN_SOME_AUTHN_NOTE)
|| (! (conf->access_file || conf->repo_relative_access_file)))
return DECLINED;
authn_configured = ap_auth_type(r) != NULL;
if (authn_configured)
{
if (apr_table_get(r->headers_in,
(PROXYREQ_PROXY == r->proxyreq)
? "Proxy-Authorization" : "Authorization"))
{
apr_table_setn(r->notes, FORCE_AUTHN_NOTE, (const char*)1);
if (ap_satisfies(r) != SATISFY_ANY)
return OK;
else
return HTTP_FORBIDDEN;
}
}
#else
if (!conf->anonymous
|| (! (conf->access_file || conf->repo_relative_access_file)))
return DECLINED;
authn_required = ap_some_auth_required(r);
if (authn_required)
{
if (ap_satisfies(r) != SATISFY_ANY)
return DECLINED;
if (apr_table_get(r->headers_in,
(PROXYREQ_PROXY == r->proxyreq)
? "Proxy-Authorization" : "Authorization"))
{
return HTTP_FORBIDDEN;
}
}
#endif
status = req_check_access(r, conf, &repos_path, &dest_repos_path);
if (status == DECLINED)
{
if (!conf->authoritative)
return DECLINED;
#if USE_FORCE_AUTHN
if (authn_configured) {
apr_table_setn(r->notes, IN_SOME_AUTHN_NOTE, (const char*)1);
authn_required = ap_some_authn_required(r);
apr_table_unset(r->notes, IN_SOME_AUTHN_NOTE);
if (authn_required)
return DECLINED;
}
#else
if (!authn_required)
#endif
log_access_verdict(APLOG_MARK, r, 0, FALSE, repos_path, dest_repos_path);
return HTTP_FORBIDDEN;
}
if (status != OK)
return status;
log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
return OK;
}
static int
check_user_id(request_rec *r)
{
authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
&authz_svn_module);
const char *repos_path = NULL;
const char *dest_repos_path = NULL;
int status;
if (!conf->no_auth_when_anon_ok || r->user
|| (! (conf->access_file || conf->repo_relative_access_file)))
return DECLINED;
status = req_check_access(r, conf, &repos_path, &dest_repos_path);
if (status == OK)
{
apr_table_setn(r->notes, "authz_svn-anon-ok", (const char*)1);
log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
return OK;
}
return status;
}
static int
auth_checker(request_rec *r)
{
authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
&authz_svn_module);
const char *repos_path = NULL;
const char *dest_repos_path = NULL;
int status;
if (! (conf->access_file || conf->repo_relative_access_file))
return DECLINED;
if (!r->user && apr_table_get(r->notes, "authz_svn-anon-ok"))
return OK;
status = req_check_access(r, conf, &repos_path, &dest_repos_path);
if (status == DECLINED)
{
if (conf->authoritative)
{
log_access_verdict(APLOG_MARK, r, 0, FALSE, repos_path, dest_repos_path);
ap_note_auth_failure(r);
return HTTP_FORBIDDEN;
}
return DECLINED;
}
if (status != OK)
return status;
log_access_verdict(APLOG_MARK, r, 1, FALSE, repos_path, dest_repos_path);
return OK;
}
#if USE_FORCE_AUTHN
static int
force_authn(request_rec *r)
{
if (apr_table_get(r->notes, FORCE_AUTHN_NOTE))
return OK;
return DECLINED;
}
#endif
static void
register_hooks(apr_pool_t *p)
{
static const char * const mod_ssl[] = { "mod_ssl.c", NULL };
ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_LAST);
ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST);
ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST);
#if USE_FORCE_AUTHN
ap_hook_force_authn(force_authn, NULL, NULL, APR_HOOK_FIRST);
#endif
ap_register_provider(p,
AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP,
AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME,
AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER,
(void*)subreq_bypass);
}
module AP_MODULE_DECLARE_DATA authz_svn_module =
{
STANDARD20_MODULE_STUFF,
create_authz_svn_dir_config,
NULL,
NULL,
NULL,
authz_svn_cmds,
register_hooks
};