#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 */ };
|