#include ”apr_strings.h” #include ”apr_hash.h” #include ”apr_tables.h” #include ”apr_md5.h”
/* for apr_password_validate */ #include ”apr_lib.h”
/* for apr_isspace */ #include ”apr_base64.h”
/* for apr_base64_decode et al */ #define APR_WANT_STRFUNC
/* for strcasecmp */ #include ”apr_want.h”
#include ”ap_config.h” #include ”httpd.h” #include ”http_config.h” #include ”http_core.h” #include ”http_log.h” #include ”http_protocol.h” #include ”http_request.h” #include ”ap_provider.h” #include
#define ENABLED 1 #define DISABLED 0
/* data need by our module */ typedef struct {
short enabled;
short debug;
char *dir;
// the starting path
char *prefixPath;
// the stop url pattern
char *stopPattern;
// the svn access file
char *accessFile; } authSVN_rec;
struct access_rec {
// 0: group
// 1: user
// 2: all
short type;
// the group or user name
char *name;
// 0: don’t have read access
// 1: have read access
short access;
// the next access record
struct access_rec
*next; };
module AP_MODULE_DECLARE_DATA authSVN_module;
// src starts with start
static short start_with(const
char *src,
const char
*start) {
int i = 0;
if(strlen(src)
< strlen(start))
return 0;
i = strlen(start)
- 1;
while(i
>= 0)
{
if(src[i]
!= start[i])
return 0;
i–;
}
return 1; }
// parse the SVN access file
static short parse_access_file(request_rec
*r,
const char*
file,
const authSVN_rec
*conf,
apr_hash_t* ugMap,
apr_hash_t* accessMap) {
ap_configfile_t *f
= NULL;
apr_status_t status;
char l[MAX_STRING_LEN], dir[256];
status = ap_pcfg_openfile(&f, r->pool,
file);
short flag = 0;
if (status
!= APR_SUCCESS)
return 0;
while(!(ap_cfg_getline(l, MAX_STRING_LEN,
f)))
{
const char
*w =
NULL;
char *last
= NULL;
apr_table_t *apt
= NULL;
struct access_rec
*arec = NULL,
*arecp =
NULL;
if ((l[0]
== ’#') || (!l[0])) {
continue;
}
if(start_with(l, ”[groups]”)) {
flag = 1;
continue;
}
if(l[0] == ’[')
{
flag = 2;
w = apr_strtok(l, ”[]:\n”,
&last);
if(w
&& w[0]
== ’/') {
// the root directory
snprintf(dir, sizeof(dir), ”%s”, conf->prefixPath);
dir[strlen(dir) - 1] = ’\0′;
}
else if(w && w[0] != ’/')
{
const
char *project
= w;
w = apr_strtok(NULL, ”[]:\n”,
&last);
if(w)
{
snprintf(dir,
sizeof(dir), ”%s%s%s”,
conf->prefixPath, project, w);
// make sure the dir is not end with /
int len
= strlen(dir);
if(dir[len
- 1]
== ’/') dir[len - 1] = ’\0′;
}
else
dir[0] = ’\0′;
}
else
{
dir[0] = ’\0′;
}
continue;
}
if(flag == 1) {
// this is the groups and users definition
w = apr_strtok(l, ”=, \n”, &last);
if(w == NULL)
// group name not found
continue;
apt = (apr_table_t *)apr_hash_get(ugMap, (const void *)w, APR_HASH_KEY_STRING);
if(apt == NULL) {
apt = apr_table_make(r->pool, 10);
apr_hash_set(ugMap, (const void *)apr_pstrdup(r->pool, w),
APR_HASH_KEY_STRING, (const void *)apt);
}
while((w = apr_strtok(NULL, ”=, \n”, &last)) != NULL) {
// this is group name or user name
if(w[0] == ‘@’) {
w++;
if(w) {
apr_table_setn(apt, apr_pstrdup(r->pool, w), ”0″);
}
}
else
{
// this is user name
apr_table_setn(apt, apr_pstrdup(r->pool, w), ”1″);
}
}
}
if(flag == 2) {
if(dir[0] == ’\0′) continue;
w = apr_strtok(l, ”= \n”, &last);
if(w)
{
arec = (struct access_rec *)apr_pcalloc(r->pool, sizeof(struct access_rec));
arec->access = 0;
if(w[0] == ‘@’) {
w++;
if(w) {
arec->name = apr_pstrdup(r->pool, w);
arec->type = 0;
}
else continue;
}
else if(w[0] == ’*')
{
arec->name
= apr_pstrdup(r->pool, ”*”);
// this is all
arec->type
= 2;
}
else
{
arec->name
= apr_pstrdup(r->pool, w);
// this is user name
arec->type
= 1;
}
}
else continue;
w = apr_strtok(NULL, ”= \n”,
&last);
if(!w)
{
arec->access
= 0;
}
else
{
arec->access
= 1;
}
arecp =
(struct access_rec
*)apr_hash_get(accessMap,
(const
void *)dir, APR_HASH_KEY_STRING);
if(arecp
==
NULL) {
arec->next
= NULL;
apr_hash_set(accessMap,
(const
void *)apr_pstrdup(r->pool, dir),
APR_HASH_KEY_STRING,
(const
void *)arec);
}
else
{
while(arecp->next
!=
NULL) arecp
= arecp->next;
arecp->next
= arec;
}
}
}
ap_cfg_closefile(f);
return 1; } /* init per dir */ static void
*create_authSVN_dir_config(apr_pool_t
*p,
char *d) {
authSVN_rec *conf
= (authSVN_rec
*)apr_pcalloc(p,
sizeof(*conf));
if(conf
==
NULL) return
NULL;
conf->enabled
= DISABLED;
conf->debug
= DISABLED;
conf->dir
= d;
conf->prefixPath
= NULL;
conf->stopPattern
= NULL;
conf->accessFile
= NULL;
return conf; }
/* hex to int */ static char hex2int(char c) {
if( c>=’0′
&& c<='9'
) return
(c - '0');
return (c
- 'A' + 10); }
/* url decode */ static void url_decode(char
*url) {
char *p
= url;
char *p1
= NULL;
while(*p)
{
if(*p
==
'+') *p
= ' ';
/* %AB */
if(*p=='%'
&&
*(p+1)
&&
*(p+2))
{
*p = hex2int(toupper(*(p+1)))
* 16 + hex2int(toupper(*(p+
2)));
strcpy(p
+ 1, p
+ 3);
}
/* \xAB */
if(*p=='\\'
&&
*(p+1)
&&
*(p+2)
&&
*(p+3))
{
p1 = p
+ 1;
if(*p1
&&
*p1=='x')
{
*p
= hex2int(toupper(*(p+2)))
* 16 + hex2int(toupper(*(p+3)));
strcpy(p+1, p+4);
}
}
p++;
}
return; }
static void parent_path(char
*url) {
char *p
= url +
strlen(url)
- 1;
while(p
!= url
&& *p
!=
'/') {
*p =
'\0'; p--;
}
if(p
!= url &&
*p=='/')
*p =
'\0';
return; }
// return
// 0: the user don't belong to this group
// 1: the user belong to this group
static short find_user_in_group(const
char* user,
const char
*group, apr_hash_t* ugMap) {
apr_table_t *apt
= (apr_table_t
*)apr_hash_get(ugMap,
(const
void *)group,
APR_HASH_KEY_STRING);
if(apt
== NULL)
return 0;
apr_array_header_t *arr;
apr_table_entry_t *elts;
int i;
arr = (apr_array_header_t
*)apr_table_elts(apt);
elts = (apr_table_entry_t
*)arr->elts;
for(i=0; inelts; i++)
{
if(elts[i].key
==
NULL || elts[i].val
==
NULL) continue;
if(elts[i].val[0]
== ’1′
&& strcmp(elts[i].key,
user) == 0)
{
return 1;
}
if(elts[i].val[0]
== ’0′)
{
if(find_user_in_group(user, elts[i].key,
ugMap))
return 1;
}
}
return 0; }
// return
// 0:don’t have access
// 1:have read access
// 2:access not found
static short find_access(const
char* user,
const char* url,
apr_hash_t* ugMap, apr_hash_t* accessMap) {
struct access_rec
*arec= (struct access_rec
*)apr_hash_get(accessMap,
(const
void *)url, APR_HASH_KEY_STRING);
short access = 2;
while(arec
!=
NULL)
{
if(strcmp(arec->name,
”*”)
== 0)
{
// specified access to all users and groups on this url
access = arec->access;
}
if(arec->type
== 1
&& strcmp(arec->name, user)
== 0)
{
// specified user access on this url
access = arec->access;
}
if(arec->type
== 0)
{
// this is group access
if(find_user_in_group(user, arec->name,
ugMap))
access = arec->access;
}
// if this user have access, we return
if(access
== 1)
return access;
arec = arec->next;
}
return access; } static short estimate_access( request_rec
*r,
const authSVN_rec* conf,
char* url, apr_hash_t* ugMap,
apr_hash_t* accessMap
) {
const char* user
= r->user;
// unauthorized
if(!user
||
!user[0])
return 0;
short access = find_access(user, url, ugMap,
accessMap);
if(access
< 2)
return access;
if(url[0]
==
'/' && url[1]
==
'\0') return 0;
parent_path(url);
return estimate_access(r, conf, url, ugMap,
accessMap); }
// do regexp matching
static short regexp_match(char
*str,
char *pattern) {
regex_t reg;
regmatch_t pm[1];
const size_t nmatch
= 1;
int res = 0;
short r = 0;
char ebuf[MAX_STRING_LEN];
res = regcomp(®, pattern, REG_EXTENDED);
if(res
!= 0)
{
regfree(®);
return 0;
}
res = regexec(®, str, nmatch, pm,
0);
if(res
== REG_NOMATCH)
r = 0;
else
r = 1;
regfree(®);
return r; }
/* all pages need to pass from this handler */ static int authSVN_handler(request_rec
*r) {
authSVN_rec *conf
= ap_get_module_config(r->per_dir_config,
&authSVN_module);
if(!conf
||
!conf->enabled)
return DECLINED;
if(conf->prefixPath
==
NULL ||
!start_with(r->uri, conf->prefixPath))
return DECLINED;
if(conf->stopPattern
!=NULL
&& regexp_match(r->uri, conf->stopPattern))
return DECLINED;
apr_hash_t* ugMap
= apr_hash_make(r->pool);
apr_hash_t* accessMap
= apr_hash_make(r->pool);
if(!parse_access_file(r, conf->accessFile,
conf, ugMap, accessMap))
return 403;
if(conf->debug)
{
// run in debug mode
// print all users/groups and access information
apr_hash_index_t* hi;
char *key;
apr_table_t *val;
struct access_rec
*arec;
apr_array_header_t *arr;
apr_table_entry_t *elts;
int i;
r->content_type=”text/plain”;
ap_rprintf(r, ”Parsed Users
and Groups:\n”);
hi = apr_hash_first(r->pool, ugMap);
while(hi
!=
NULL)
{
apr_hash_this(hi,
(void
*)&key,
NULL,
(void *)&val);
ap_rprintf(r, ”%s: ”, key);
arr =
(apr_array_header_t *)apr_table_elts(val);
elts =
(apr_table_entry_t *)arr->elts;
for(i=0; inelts; i++)
{
if(elts[i].key
==
NULL || elts[i].val
==
NULL) continue;
if(elts[i].val[0]
== ’0′)
{
ap_rprintf(r, ”@”);
}
ap_rprintf(r, ”%s ”, elts[i].key);
}
ap_rprintf(r, ”\n”);
hi = apr_hash_next(hi);
}
ap_rprintf(r, ”Parsed Path Access:\n”);
hi = apr_hash_first(r->pool, accessMap);
while(hi
!=
NULL)
{
apr_hash_this(hi,
(void
*)&key,
NULL,
(void *)&arec);
ap_rprintf(r, ”%s:\n”, key);
while(arec
!=
NULL)
{
if(arec->type
== 0)
ap_rprintf(r, ”group:%s ”, arec->name);
else
if(arec->type
== 1)
ap_rprintf(r, ”user:%s ”, arec->name);
else
ap_rprintf(r, ”all ”);
ap_rprintf(r, ”access:%d ”, arec->access);
ap_rprintf(r, ”\n”);
arec = arec->next;
}
ap_rprintf(r, ”\n”);
hi = apr_hash_next(hi);
}
}
char *url
= apr_pstrdup(r->pool, r->uri);
// decode the url for some chinese characters
url_decode(url);
// analyze the access
if(estimate_access(r, conf, url, ugMap,
accessMap))
{
if(conf->debug)
{
ap_rprintf(r, ”%s have access on:%s\n”,
r->user, r->uri);
return OK;
}
return DECLINED;
}
else
{
if(conf->debug)
{
ap_rprintf(r, ”%s don’t have access on:%s\n”,
r->user, r->uri);
return OK;
}
}
return 403; }
/* enable this module or not */ static const
char *set_authSVN_enable(cmd_parms
*cmd,
void
*mconfig,
int
arg) {
authSVN_rec *conf
= (authSVN_rec
*) mconfig;
conf->enabled
= arg;
return NULL; }
/* debug this module or not */ static const
char *set_authSVN_debug( cmd_parms
*cmd,
void
*mconfig,
int
arg) {
authSVN_rec *conf
= (authSVN_rec
*) mconfig;
conf->debug
= arg;
return NULL; }
/* setting prefix path */ static const
char *set_prefix_path(cmd_parms
*cmd,
void
*mconfig,
const
char *name) {
authSVN_rec *conf
= (authSVN_rec
*) mconfig;
if(strlen(name)
<= 0)
return "AuthSVNPrefixPath can not be null.";
if(name[0]
!=
'/' || name[strlen(name)
- 1]
!= '/')
return "AuthSVNPrefixPath must start and end with '/'.";
conf->prefixPath
= apr_pstrdup(cmd->pool, name);
return NULL; }
/* setting stop url pattern */ static const
char *set_stop_pattern(cmd_parms
*cmd,
void
*mconfig,
const
char *name) {
authSVN_rec *conf
= (authSVN_rec
*) mconfig;
if(strlen(name)
<= 0)
return "AuthSVNStopPattern can not be null.";
conf->stopPattern
= apr_pstrdup(cmd->pool, name);
return NULL; }
/* setting SVN access file */ static const
char *set_authSVN_accessFile(cmd_parms
*cmd,
void
*mconfig,
const
char *name) {
authSVN_rec *conf
= (authSVN_rec
*) mconfig;
ap_configfile_t *f
= NULL;
apr_status_t status;
if(strlen(name)
<= 0)
return "SVNAccessFile can not be null.";
status = ap_pcfg_openfile(&f, cmd->pool,
name);
if (status
!= APR_SUCCESS)
{
return ”Can
not open given SVN access
file.”;
}
ap_cfg_closefile(f);
conf->accessFile
= apr_pstrdup(cmd->pool, name);
return NULL; } static const command_rec auth_cmds[]
= {
AP_INIT_FLAG(”EnableAuthSVN”, set_authSVN_enable,
NULL, OR_FILEINFO,
”enable authSVN or
not.”),
AP_INIT_FLAG(”DebugAuthSVN”, set_authSVN_debug,
NULL, OR_FILEINFO,
”debug authSVN or
not.”),
AP_INIT_TAKE1(”AuthSVNPrefixPath”, set_prefix_path,
NULL, OR_FILEINFO,
”set prefix path.”),
AP_INIT_TAKE1(”AuthSVNStopPattern”, set_stop_pattern,
NULL, OR_FILEINFO,
”the url pattern we do
not do the access checking.”),
AP_INIT_TAKE1(”SVNAccessFile”, set_authSVN_accessFile,
NULL, OR_FILEINFO,
”set SVN access file.”),
{ NULL
} };
static void register_hooks(apr_pool_t
*p) {
ap_hook_handler(authSVN_handler,
NULL,
NULL, APR_HOOK_FIRST); }
module AP_MODULE_DECLARE_DATA authSVN_module = {
STANDARD20_MODULE_STUFF,
create_authSVN_dir_config,
/* dir config creater */
NULL,
/* dir merger — default is to override */
NULL,
/* server config */
NULL,
/* merge server config */
auth_cmds, /* command apr_table_t */
register_hooks /* register hooks */ };
|