Apache模块开发/用C语言扩展apache(4:一个生产环境使用的apache module– viewvc权限控制)
by linux_prog
下面公布一个目前在我们公司使用的apache module的源代码。
我们公司开发人员很多,使用了SVN和viewvc来进行版本控制和查看,通过web界面,SVN能够根据每个用户的权限来控制能够
浏览某个项目下的代码,但是viewvc只要你在SVN中有用户,你就可以看当前SVN中所有项目的代码。这个风险比较大,因此,我们
开发了一个apache module,用来读取SVN的权限配置文件,把相应的权限集成到VIEWVC中。
源代码:
#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 */
};
安装方法:
install the module:
./bin/apachectl stop
./bin/apxs -c mod_authSVN.c
./bin/apxs -a -i -n authSVN mod_authSVN.la
./bin/apachectl start
add the following to httpd.conf(insite the viewvc location directory):
EnableAuthSVN on
DebugAuthSVN off
SVNAccessFile /usr/local/apache2.2.4/access
AuthSVNPrefixPath /viewvc/
AuthSVNStopPattern ^\/viewvc\/\*docroot\*\/