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;
}