Apache 2.2 Basic Auth
最近需要修改一下Apache的BasicAuth模块,顺便简单分析一下。
首先是 mod_authn_file
, 这个模块从文件中读取用户名和密码组合,并逐一check看用户名和密码是否符合。代码在 modules/aaa/mod_authn_file.c
。
static const authn_provider authn_file_provider =
{
&check_password,
&get_realm_hash,
};
//注册一个叫file的AuthProvider,主要是两个函数check_password和get_realm_hash
static void register_hooks(apr_pool_t *p)
{
ap_register_provider(p, AUTHN_PROVIDER_GROUP, "file", "0",
&authn_file_provider);
}
static authn_status check_password(request_rec *r, const char *user,
const char *password)
{
authn_file_config_rec *conf = ap_get_module_config(r->per_dir_config,
&authn_file_module);
ap_configfile_t *f;
char l[MAX_STRING_LEN];
apr_status_t status;
char *file_password = NULL;
if (!conf->pwfile) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"AuthUserFile not specified in the configuration");
return AUTH_GENERAL_ERROR;
}
//打开配置的AuthUserFile文件
status = ap_pcfg_openfile(&f, r->pool, conf->pwfile);
if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
"Could not open password file: %s", conf->pwfile);
return AUTH_GENERAL_ERROR;
}
//一行一行读取
while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
const char *rpw, *w;
/* Skip # or blank lines. */
if ((l[0] == '#') || (!l[0])) {
continue;
}
rpw = l;
w = ap_getword(r->pool, &rpw, ':');
//UserName相同,把密码记上
if (!strcmp(user, w)) {
file_password = ap_getword(r->pool, &rpw, ':');
break;
}
}
ap_cfg_closefile(f);
//没有找到密码,说明用户名不存在
if (!file_password) {
return AUTH_USER_NOT_FOUND;
}
//调用apr_password_validate,验证密码正确性。
status = apr_password_validate(password, file_password);
if (status != APR_SUCCESS) {
return AUTH_DENIED;
}
return AUTH_GRANTED;
}
其中apr_password_validate的实现在 srclib/apr-util/crypto/apr_passwd.c
APU_DECLARE(apr_status_t) apr_password_validate(const char *passwd,
const char *hash)
{
char sample[200];
#if !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
char *crypt_pw;
#endif
if (hash[0] == '$'
&& hash[1] == '2'
&& (hash[2] == 'a' || hash[2] == 'y')
&& hash[3] == '$') {
if (_crypt_blowfish_rn(passwd, hash, sample, sizeof(sample)) == NULL)
return APR_FROM_OS_ERROR(errno);
}
else if (!strncmp(hash, apr1_id, strlen(apr1_id))) {
/*
* The hash was created using our custom algorithm.
*/
apr_md5_encode(passwd, hash, sample, sizeof(sample));
}
else if (!strncmp(hash, APR_SHA1PW_ID, APR_SHA1PW_IDLEN)) {
apr_sha1_base64(passwd, (int)strlen(passwd), sample);
}
else {
/*
* It's not our algorithm, so feed it to crypt() if possible.
*/
#if defined(WIN32) || defined(BEOS) || defined(NETWARE)
return (strcmp(passwd, hash) == 0) ? APR_SUCCESS : APR_EMISMATCH;
#elif defined(CRYPT_R_CRYPTD)
apr_status_t rv;
CRYPTD *buffer = malloc(sizeof(*buffer));
if (buffer == NULL)
return APR_ENOMEM;
crypt_pw = crypt_r(passwd, hash, buffer);
if (!crypt_pw)
rv = APR_EMISMATCH;
else
rv = (strcmp(crypt_pw, hash) == 0) ? APR_SUCCESS : APR_EMISMATCH;
free(buffer);
return rv;
#elif defined(CRYPT_R_STRUCT_CRYPT_DATA)
apr_status_t rv;
struct crypt_data *buffer = malloc(sizeof(*buffer));
if (buffer == NULL)
return APR_ENOMEM;
#ifdef __GLIBC_PREREQ
/*
* For not too old glibc (>= 2.3.2), it's enough to set
* buffer.initialized = 0. For < 2.3.2 and for other platforms,
* we need to zero the whole struct.
*/
#if __GLIBC_PREREQ(2,4)
#define USE_CRYPT_DATA_INITALIZED
#endif
#endif
#ifdef USE_CRYPT_DATA_INITALIZED
buffer->initialized = 0;
#else
memset(buffer, 0, sizeof(*buffer));
#endif
crypt_pw = crypt_r(passwd, hash, buffer);
if (!crypt_pw)
rv = APR_EMISMATCH;
else
rv = (strcmp(crypt_pw, hash) == 0) ? APR_SUCCESS : APR_EMISMATCH;
free(buffer);
return rv;
#else
/* Do a bit of sanity checking since we know that crypt_r()
* should always be used for threaded builds on AIX, and
* problems in configure logic can result in the wrong
* choice being made.
*/
#if defined(_AIX) && APR_HAS_THREADS
#error Configuration error! crypt_r() should have been selected!
#endif
{
apr_status_t rv;
/* Handle thread safety issues by holding a mutex around the
* call to crypt().
*/
crypt_mutex_lock();
crypt_pw = crypt(passwd, hash);
if (!crypt_pw) {
rv = APR_EMISMATCH;
}
else {
rv = (strcmp(crypt_pw, hash) == 0) ? APR_SUCCESS : APR_EMISMATCH;
}
crypt_mutex_unlock();
return rv;
}
#endif
}
return (strcmp(sample, hash) == 0) ? APR_SUCCESS : APR_EMISMATCH;
}
上面的实现根据平台的不同,以及传进来的hash的格式不一样,会调用不一样的算法,主要是有MD5,SHA,crypt等几种方式,每种方式生成的hash的格式不一样,从hash的格式既可以区分。
下面看一下 mod_auth_basic
,文件在 modules/aaa/mod_auth_basic.c
。
static void register_hooks(apr_pool_t *p)
{
//注册一个authenticate_basic_user方法
ap_hook_check_user_id(authenticate_basic_user,NULL,NULL,APR_HOOK_MIDDLE);
}
static int authenticate_basic_user(request_rec *r)
{
auth_basic_config_rec *conf = ap_get_module_config(r->per_dir_config,
&auth_basic_module);
const char *sent_user, *sent_pw, *current_auth;
int res;
authn_status auth_result;
authn_provider_list *current_provider;
/* Are we configured to be Basic auth? */
current_auth = ap_auth_type(r);
if (!current_auth || strcasecmp(current_auth, "Basic")) {
return DECLINED;
}
/* We need an authentication realm. */
if (!ap_auth_name(r)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR,
0, r, "need AuthName: %s", r->uri);
return HTTP_INTERNAL_SERVER_ERROR;
}
r->ap_auth_type = "Basic";
//拿到用户和密码
res = get_basic_auth(r, &sent_user, &sent_pw);
if (res) {
return res;
}
current_provider = conf->providers;
do {
const authn_provider *provider;
/* For now, if a provider isn't set, we'll be nice and use the file
* provider.
*/
//找到配置的AuthProvider
if (!current_provider) {
provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
AUTHN_DEFAULT_PROVIDER, "0");
if (!provider || !provider->check_password) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"No Authn provider configured");
auth_result = AUTH_GENERAL_ERROR;
break;
}
apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
}
else {
provider = current_provider->provider;
apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
}
//调用provider的check_password函数,也就是上面的check_password
auth_result = provider->check_password(r, sent_user, sent_pw);
apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
/* Something occured. Stop checking. */
if (auth_result != AUTH_USER_NOT_FOUND) {
break;
}
/* If we're not really configured for providers, stop now. */
if (!conf->providers) {
break;
}
current_provider = current_provider->next;
} while (current_provider);
//根据情况返回对应的状态码,记录错误信息。
if (auth_result != AUTH_GRANTED) {
int return_code;
/* If we're not authoritative, then any error is ignored. */
if (!(conf->authoritative) && auth_result != AUTH_DENIED) {
return DECLINED;
}
switch (auth_result) {
case AUTH_DENIED:
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"user %s: authentication failure for \"%s\": "
"Password Mismatch",
sent_user, r->uri);
return_code = HTTP_UNAUTHORIZED;
break;
case AUTH_USER_NOT_FOUND:
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"user %s not found: %s", sent_user, r->uri);
return_code = HTTP_UNAUTHORIZED;
break;
case AUTH_GENERAL_ERROR:
default:
/* We'll assume that the module has already said what its error
* was in the logs.
*/
return_code = HTTP_INTERNAL_SERVER_ERROR;
break;
}
/* If we're returning 403, tell them to try again. */
if (return_code == HTTP_UNAUTHORIZED) {
note_basic_auth_failure(r);
}
return return_code;
}
return OK;
}
其中get_basic_auth
static int get_basic_auth(request_rec *r, const char **user,
const char **pw)
{
const char *auth_line;
char *decoded_line;
int length;
/* Get the appropriate header */
//根据请求类型获取请求头值
auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq)
? "Proxy-Authorization"
: "Authorization");
if (!auth_line) {
note_basic_auth_failure(r);
return HTTP_UNAUTHORIZED;
}
//判断是不是Basic认证
if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
/* Client tried to authenticate using wrong auth scheme */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"client used wrong authentication scheme: %s", r->uri);
note_basic_auth_failure(r);
return HTTP_UNAUTHORIZED;
}
/* Skip leading spaces. */
while (apr_isspace(*auth_line)) {
auth_line++;
}
//Baisc认证是base64 encode的,decode一下,拿到用户名和密码
decoded_line = apr_palloc(r->pool, apr_base64_decode_len(auth_line) + 1);
length = apr_base64_decode(decoded_line, auth_line);
/* Null-terminate the string. */
decoded_line[length] = '\0';
*user = ap_getword_nulls(r->pool, (const char**)&decoded_line, ':');
*pw = decoded_line;
/* set the user, even though the user is unauthenticated at this point */
r->user = (char *) *user;
return OK;
}