mod_auth_hatena_d.cを作ってみた

誰か作ってくれないかなと期待しつつも、こういうページに触発されたりして、それっぽいのを作ってみた。 (末尾に「_d」が付くのは、本当のmod_auth_hatenaは誰かが作っているだろうと思うから)

結局、この一週間はほとんど寝不足。 の割にはソースがぼろぼろ。 まぁ一応動くっぽいけど。誰かの参考になれば嬉しかったり。

[c num=1]/ mod_auth_hatena_d.c LoadModule auth_hatena_d_module mod_auth_hatena_d.so HatenaAuthEnabled On HatenaAuthApiKey {api_key} HatenaAuthSecret {secret} HatenaAuthLoginPath /login/ HatenaAuthEntryPath /entry/ /

include "apr.h"

include "apr_tables.h"

include "httpd.h"

include "http_config.h"

include "http_log.h"

include "util_md5.h"

define BUFLEN (int) 1024*8

typedef struct hatena_auth_user_t { char name[1024]; char image_url[1024]; char thumbnail_url[1024]; } hatena_auth_user_t;

static char hostname = "www.hatena.ne.jp";

typedef struct { int enabled; char api_key; char secret; char login_path; char entry_path; } hatena_auth_config_rec;

static void create_hatena_auth_dir_config(apr_pool_t p, char d) { hatena_auth_config_rec conf = (hatena_auth_config_rec )apr_palloc(p, sizeof(hatena_auth_config_rec)); conf->enabled = 0; conf->api_key = NULL; conf->secret = NULL; conf->login_path = (char )apr_pstrdup(p, "/login"); conf->entry_path = (char *)apr_pstrdup(p, "/entry");

return conf; } static const command_rec hatena_auth_cmds = { AP_INIT_FLAG("HatenaAuthEnabled", ap_set_flag_slot, (void )APR_OFFSETOF(hatena_auth_config_rec, enabled), OR_ALL, "hatena auth enabled"), AP_INIT_TAKE1("HatenaAuthApiKey", ap_set_string_slot, (void )APR_OFFSETOF(hatena_auth_config_rec, api_key), OR_ALL, "hatena auth api_key"), AP_INIT_TAKE1("HatenaAuthSecret", ap_set_string_slot, (void )APR_OFFSETOF(hatena_auth_config_rec, secret), OR_ALL, "hatena auth secret"), AP_INIT_TAKE1("HatenaAuthLoginPath", ap_set_string_slot, (void )APR_OFFSETOF(hatena_auth_config_rec, login_path), OR_ALL, "hatena auth login path"), AP_INIT_TAKE1("HatenaAuthEntryPath", ap_set_string_slot, (void *)APR_OFFSETOF(hatena_auth_config_rec, entry_path), OR_ALL, "hatena auth entry path"), {NULL} };

static apr_table_t get_param_by_urlencoded(apr_pool_t p, char src) { apr_table_t param; char token; char args = src; if(args == NULL) return NULL; param = (apr_table_t )apr_table_make(p, 1); while (args && (token = ap_getword(p, (const char)&args, '&'))){ char key, val; key = (char *)ap_getword(p, (const char)&token, '='); val = (char *)ap_getword(p, (const char**)&token, '='); ap_unescape_url(key); ap_unescape_url(val); apr_table_set(param, key, val); } return param; }

static char get_xml_value(request_rec r, char contents, char key) { regex_t regexp; regmatch_t match[10]; char dist_string; char pattern; int i, j; char buf = NULL;

pattern = (char *)apr_pstrcat(r->pool, "<", key, ">([^<]+)</", key, ">", NULL ); regexp = ap_pregcomp(r->pool, pattern, REG_EXTENDED|REG_ICASE); if (regexp == NULL) return NULL; if (ap_regexec(regexp, contents, regexp->re_nsub + 1, match, 0) == 0) { if ( match[1].rm_so >= 0 && match[1].rm_eo > match[1].rm_so) { buf = apr_pcalloc(r->pool, match[1].rm_eo - match[1].rm_so + 1); for(i=match[1].rm_so,j=0;i<match[1].rm_eo;i++,j++) buf[j] = contents[i]; } }

return buf; }

static hatena_auth_user_t make_hatena_auth_user(request_rec r, char cert, char contents) { char key; char has_error; char name; char image_url; char *thumbnail_url;

has_error = get_xml_value(r, contents, "has_error"); if (strcmp(has_error, "false") != 0) return NULL;

name = get_xml_value(r, contents, "name"); image_url = get_xml_value(r, contents, "image_url"); thumbnail_url = get_xml_value(r, contents, "thumbnail_url");

hatena_auth_user_t p = (hatena_auth_user_t ) apr_pcalloc(r->pool, sizeof(hatena_auth_user_t)); strncpy(p->name, name, sizeof(p->name)); strncpy(p->image_url, image_url, sizeof(p->image_url)); strncpy(p->thumbnail_url, thumbnail_url, sizeof(p->thumbnail_url)); return p; }

static char get_hatena_file_name(request_rec r,char cert) { return (char )apr_pstrcat(r->pool, "/tmp/hatenaauth.", ap_md5(r->pool, cert), NULL); }

static void save_hatena_auth(request_rec r, char cert, hatena_auth_user_t p ) { apr_file_t fp; apr_size_t len; apr_status_t rv;

rv = apr_file_open(&fp, get_hatena_file_name(r, cert), APR_WRITE|APR_CREATE|APR_BINARY, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) return; len = sizeof(hatena_auth_user_t); apr_file_write(fp, (const char*)p, &len); apr_file_close(fp);

return; }

static hatena_auth_user_t load_hatena_auth(request_rec r, char cert) { apr_file_t fp; size_t len; apr_status_t rv; hatena_auth_user_t *p;

len = sizeof(hatena_auth_user_t); p = (hatena_auth_user_t *)apr_pcalloc(r->pool, len);

rv = apr_file_open(&fp, get_hatena_file_name(r, cert), APR_READ|APR_BINARY, APR_OS_DEFAULT, r->pool); if(rv != APR_SUCCESS) return NULL;

apr_file_read(fp, p, &len); apr_file_close(fp);

return p; }

static void make_cert_cookie(request_rec r,char cert) { char buf[1024]; char new_cookie; new_cookie = (char )apr_psprintf(r->pool, "%s=%s; path=/", "cert", cert ); apr_table_addn(r->headers_out, "Set-Cookie", new_cookie); return; }

define NUM_SUBS 3

static char get_cert_cookie(request_rec r) { char cookie_header; char cookie_val = NULL; char *p = NULL; regmatch_t regm[NUM_SUBS];

cookie_header = (char *)apr_table_get(r->headers_in, "Cookie"); if (cookie_header == NULL) return NULL;

regex_t regexp = ap_pregcomp(r->pool, "^cert=([^;]+)|;[ t]+cert=([^;]+)", REG_EXTENDED); if (!ap_regexec(regexp, cookie_header, NUM_SUBS, regm, 0)) { char cookieval; if (regm[1].rm_so != -1) cookieval = ap_pregsub(r->pool, "$1", cookie_header, NUM_SUBS, regm); if (regm[2].rm_so != -1) cookieval = ap_pregsub(r->pool, "$2", cookie_header, NUM_SUBS, regm); p = (char *)apr_pstrdup(r->pool, cookieval, NULL); } return p; }

static char get_http_contents(request_rec r, char hostname, unsigned short port, char path) { apr_socket_t sock; apr_sockaddr_t sa; apr_status_t status;

status = apr_sockaddr_info_get(&sa, hostname, APR_INET, port, 0, r->pool); if (status) { ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,"apr_sockaddr_info_get error %d",status); return NULL; } status = apr_socket_create(&sock, APR_INET, SOCK_STREAM, r->pool); if (status) { ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,"apr_socket_create error %d",status); return NULL; }

status = apr_socket_connect(sock, sa); if (status) { ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,"apr_socket_connect error %d",status); return NULL; }

char msg = (char )apr_pstrcat(r->pool, "GET ", path, " HTTP/1.1rn", "Host: ", hostname, "rnrn", NULL); int wlen = strlen(msg); status=apr_socket_send(sock,msg,&wlen); if (status) { ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,"apr_socket_connect error %d",status); return NULL; } char data = (char )apr_pstrdup(r->pool, ""); while (1) { char buf[BUFLEN]; apr_size_t len = sizeof(buf) - 1; apr_status_t status = apr_socket_recv(sock,buf,&len); if (status == APR_EOF || len == 0) { break; } buf[len] = ''; data = (char )apr_pstrcat(r->pool, (char )data, (char *)buf, NULL); }

apr_socket_close(sock);

return data; }

static char uri_to_login(request_rec r,char api_key,char secret){ char uri_auth[] ="http://auth.hatena.ne.jp/auth";

char sig = (char) apr_pstrcat( r->pool, secret, "api_key", api_key, NULL ); sig = ap_md5(r->pool,sig); char uri = (char)apr_pstrcat(r->pool,uri_auth,"?", "api_key=", api_key, "&api_sig=", sig, NULL); return uri; }

static char login(request_rec r,char api_key, char secret, char *cert) {

char hostname="auth.hatena.ne.jp"; char path_json="/api/auth.xml"; char sig = (char) apr_pstrcat(r->pool,secret,"api_key",api_key,"cert",cert,NULL); sig=ap_md5(r->pool,sig); char path=(char) apr_pstrcat(r->pool,path_json,"?", "api_key=", api_key, "&cert=", cert, "&api_sig=", sig, NULL ); return get_http_contents(r,hostname,80,path); }

static int redirect_hatena_auth_form(request_rec r, hatena_auth_config_rec conf) { char uri = (char )uri_to_login(r,conf->api_key,conf->secret); apr_table_setn(r->headers_out, "Location", uri ); return HTTP_TEMPORARY_REDIRECT; }

module AP_MODULE_DECLARE_DATA auth_hatena_d_module;

static int auth_hatena_d_handler(request_rec r) { hatena_auth_user_t hatena_auth_user = NULL;

hatena_auth_config_rec *conf = ap_get_module_config(r->per_dir_config, &auth_hatena_d_module);

if (!(conf->enabled)) return OK;

if(conf->api_key == NULL || conf->secret == NULL) return HTTP_UNAUTHORIZED;

if (strcmp(r->uri, conf->login_path) == 0) return redirect_hatena_auth_form(r, conf);

char p; char cert; if (strcmp(r->uri, conf->entry_path) == 0) { apr_table_t param = get_param_by_urlencoded(r->pool, r->args); if (param == NULL) return HTTP_UNAUTHORIZED; cert = (char ) apr_table_get(param, "cert"); if (cert == NULL) return HTTP_UNAUTHORIZED;

char *contents; contents = login(r, conf->api_key, conf->secret, cert); if (contents == NULL) return HTTP_UNAUTHORIZED; hatena_auth_user = make_hatena_auth_user(r, cert, contents); if (hatena_auth_user != NULL) save_hatena_auth(r, cert, hatena_auth_user); make_cert_cookie(r, cert); } else { cert = get_cert_cookie(r); if (cert != NULL) { hatena_auth_user = load_hatena_auth(r, cert); } } if (hatena_auth_user != NULL) { apr_table_set(r->subprocess_env, "HATENA_AUTH_NAME", hatena_auth_user->name); apr_table_set(r->subprocess_env, "HATENA_AUTH_IMAGE_URL", hatena_auth_user->image_url); apr_table_set(r->subprocess_env, "HATENA_AUTH_THUMBNAIL_URL", hatena_auth_user->thumbnail_url); return OK; } return HTTP_UNAUTHORIZED; }

static void auth_hatena_d_register_hooks(apr_pool_t *p) { ap_hook_access_checker(auth_hatena_d_handler, NULL, NULL, APR_HOOK_LAST); }

/ Dispatch list for API hooks / module AP_MODULE_DECLARE_DATA auth_hatena_d_module = { STANDARD20_MODULE_STUFF, create_hatena_auth_dir_config, / create per-dir config structures / NULL, / merge per-dir config structures / NULL, / create per-server config structures / NULL, / merge per-server config structures / hatena_auth_cmds, / table of config file commands / auth_hatena_d_register_hooks / register hooks / };[/c]

コンパイルはとりあえず「apxs -g -n auth_hatena_d」でスケルトン作って、そこに流し込んでやってしまってください。 また、コンパイルして任意ディレクトリへに配置後の使い方ですが、 [c num=1] / LoadModule auth_hatena_d_module mod_auth_hatena_d.so HatenaAuthEnabled On ・・・ On だとモジュールが有効 HatenaAuthApiKey {api_key} ・・・ api_key を設定 HatenaAuthSecret {secret} ・・・ secret を設定 HatenaAuthLoginPath /login/ ・・・ ログインのパス。実際ははてなの認証フォームへリダイレクト。 HatenaAuthEntryPath /entry/ ・・・ はてな認証から戻ってくるパス **/[/c] で大体察して。(なげやり)

あと、HatenaAuthEntryPath はなんらかのページ(index.htmlとかね)を置いておかないと、Cookie が設定されません。 (ページが表示されないとCookieが設定されない気がする)

参考にしたのは、DSAS開発者の部屋とパンダ本(「Apacheモジュールプログラミングガイド」)とGoogleCodeSearchとか、はてな認証API PHP 版ライブラリも参考にした。 (本当はもっと参考にしたソースとかあるのだけども細かいのは忘れてしまった)

思ったことは、久しぶりにC言語触りましたが、Apacheモジュールはちょっとしたボリュームのモノを作るには丁度いいかもしれない。 新人の勉強用にはいいのかも・・・、ってさ、Apacheの汎用ライブラリの仕様とか全然どこにも見つからんの。 Cookieライブラリとか結局見つけられなかったし。 (あってもよさそうなのだけどなぁ。。。)

またユーザ情報を実ファイルに落としてしまっているのでダサダサなんだけども、mod_session みたいなやつを利用すればよかったかもね。 (そこまで作りこむ気力ねーや)

mod_authとは殆ど絡まない仕組みになってしまった。 どうやって絡ませればいいのかちょっと思いつかなかったし。 bucket brigadeも結局理解できなかったなぁ。。。

mod_openidもここから頑張れば作れるのかもよ。 (にしても、OpenIDの場合、ユーザIDを受け取る部分をどうしたものか) でも、先も書いた実ファイルに保存しちゃっている箇所とか、get_http_contents の部分とか、XML解析箇所もどうにかしないといけないのだろうけど。