眠る開発屋blog ある開発屋の雑感。日々勉強。

2006/11/14 火曜日

mod_auth_hatena_d.cを作ってみた

Filed under: 私的試み — dev0000 @ 1:26:24

誰か作ってくれないかなと期待しつつも、こういうページに触発されたりして、それっぽいのを作ってみた。
(末尾に「_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, ">([^<]+)“, 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;ipool, 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解析箇所もどうにかしないといけないのだろうけど。

Powered by WordPress