Windows環境のRedmineにて、RedmineのユーザとSubversionのユーザを同期して使用したい(5)

いよいよsha1でパスワードをハッシュ化する実装をmod_authn_mysqlに実装し、Redmineのユーザデータベースの内容と
一致させ、subversionの認証時にもRedmineのユーザ情報を利用するという目的を果たせる段階まできました。

問題のsha1の実装ですが、「sha1」「rfc」で検索するとhttp://www.faqs.org/rfcs/rfc3174.htmlというページがヒットします。
ここには、sha1のCの実装サンプルが含まれているので、これをそのまま利用することにします。
この文章の第七章に、C言語のサンプルが書かれているのですが、ヘッダファイルはそのままだとVCのコンパイル
通りません。stdint.hがないためです。
しかし、sha1.hには以下のようにコメントが書かれているので、

/*
 * If you do not have the ISO standard stdint.h header file, then you
 * must typdef the following:
 *    name              meaning
 *  uint32_t         unsigned 32 bit integer
 *  uint8_t          unsigned 8 bit integer (i.e., unsigned char)
 *  int_least16_t    integer of >= 16 bits
 *
 */

#include を削除して、以下のようにtypedefして回避することにします。

typedef unsigned long uint32_t;
typedef unsigned char uint8_t;
typedef short int_least16_t;

これでsha1は使用可能です。
以下のようなソースで文字列(パスワード)をsha1でハッシュ化できます。
下のソースのsha1_passwordにハッシュ化されたパスワードが格納されます。

    SHA1Context sha;
    int i, err;
    uint8_t Message_Digest[20];
    char sha1_password[60] = "";
    char tmp[10] = "";

    err = SHA1Reset(&sha);
    if (err) {
        fprintf(stderr, "SHA1Reset Error %d.\n", err);
    }
    err = SHA1Input(&sha,(const unsigned char *) password ,strlen(password));
    if (err) {
        fprintf(stderr, "SHA1Input Error %d.\n", err);
    }
    err = SHA1Result(&sha, Message_Digest);
    if (err) {
        fprintf(stderr,"SHA1Result Error %d, could not compute message digest.\n",err);
    } else {
        for (i = 0; i < 20 ; ++i) {
            sprintf(tmp, "%02x", Message_Digest[i] );
            tmp[2] = '\0';
            strcat(sha1_password, tmp);
        }
    }

肝心のmod_authn_mysqlの変更に移ります。
HTTP経由でsvnにアクセスした際にパスワードを聞かれますが、このパスワードは、mod_authn_mysqlのcheck_mysql_pwに渡されてきます。
check_mysql_pwの引数の変数[password]がユーザが入力したパスワードです。
そして、mysql_passwordがRedmineのusersテーブルから取得したハッシュ化されたパスワードです。
下に掲載したプログラムの下から数えて14行目あたりにコメントアウトされた行があると思います。
ここで比較が行われていますので、その前に変数[password]をハッシュ化すればよいと思います。

以下に、sha1のハッシュ化を組み込んだcheck_mysql_pwをすべて載せておきます。

static authn_status check_mysql_pw(request_rec *r, const char *user, const char *password)
{
    SHA1Context sha;
    int i, err;
    uint8_t Message_Digest[20];
    char sha1_password[60] = "";
    char tmp[10] = "";

    authn_status ARV = AUTH_DENIED;
    int blah = 0;
    mysql_config *conf;
    mysql_dconfig *dconf = ap_get_module_config(r->per_dir_config,&authn_mysql_module);

    char* mysql_password;
    char* query;
    MYSQL_ROW sql_row;
    MYSQL_RES *result = NULL;
    mysql_res *mysql_res; 
    const char* esc_user = mysql_escape(user, r->pool);

    conf = apr_hash_get(authn_mysql_config, dconf->id, APR_HASH_KEY_STRING);
    if(conf == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
				  "[mod_authn_mysql.c] - Server Config for \"%s\" was not found", dconf->id);
        return ARV;
    }

    apr_reslist_acquire(conf->pool,  (void **)&mysql_res);

    /* password translate from clear text to sha1 */

    err = SHA1Reset(&sha);
    if (err) {
        fprintf(stderr, "SHA1Reset Error %d.\n", err);
    }
    err = SHA1Input(&sha,(const unsigned char *) password ,strlen(password));
    if (err) {
        fprintf(stderr, "SHA1Input Error %d.\n", err);
    }
    err = SHA1Result(&sha, Message_Digest);
    if (err) {
        fprintf(stderr,"SHA1Result Error %d, could not compute message digest.\n",err);
    } else {
        for (i = 0; i < 20 ; ++i) {
            sprintf(tmp, "%02x", Message_Digest[i] );
            tmp[2] = '\0';
            strcat(sha1_password, tmp);
        }
    }

    /* make the query to get the user's password */

    if (conf->rec.isactive_field) {
        query = apr_psprintf(r->pool, "SELECT %s FROM %s WHERE %s='%s' AND %s!=0 LIMIT 0,1", 
                                         conf->rec.password_field, conf->rec.mysql_table, 
                                         conf->rec.username_field, esc_user, conf->rec.isactive_field);

    }
    else {
        query = apr_psprintf(r->pool, "SELECT %s FROM %s WHERE %s='%s' AND %s!=0 LIMIT 0,1", 
                                         conf->rec.password_field, conf->rec.mysql_table, 
                                         conf->rec.username_field, esc_user);
    }

    /* perform the query */

    if (safe_mysql_query(mysql_res,  &result,  r, query) == 0){
        /* store the query result */
        if (result && mysql_num_rows(result) == 1) {
            sql_row = mysql_fetch_row(result);

            /* ensure we have a row, and non NULL value */
            if (!sql_row || !sql_row[0]) {
                ARV = AUTH_USER_NOT_FOUND;
            } 
            else {
                mysql_password = (char *) apr_pstrcat(r->pool, sql_row[0], NULL);
//                if (strcmp(mysql_password,password) != 0) {
                if (strcmp(mysql_password,sha1_password) != 0) {
                    ARV = AUTH_DENIED;
                }
                else {
                    ARV = AUTH_GRANTED;
                }
            }
        } 
    }
    mysql_free_result(result);
    safe_mysql_rel_server(conf->pool, mysql_res, r);
    return ARV;
}

お分かりだと思いますが、十分なエラー処理など施してませんのでサンプルということで^^;

  • mod_authn_mysql.c
  • sha1.c(プロジェクトに追加)
  • sha1.h(プロジェクトの直下に配置)

でビルドを実行すれば、目的のmod_authn_mysql.soが手に入ります。

テストしてみたところ、想定どおりの動きをしてくれています。
思った通り(?!)手間がかかりましたが、目的(id:sikakura:20101124)のことができて良かったです。