SVX日記
2010-05-15(Sat) PAMのモジュールを小改造する
いわゆる、パスワードによる「マスターキー」の実現である。PAMモジュールなので、PAM認証をサポートしている機能なら、すべての機能で「マスターキー」機能が利用可能である。うっしっし、PAM万歳、である。
なお、今回はdovecotを利用例に挙げているが、dovecotには、独自の「マスターキー」機能が存在するようである(詳細は未確認)。しかしながら、私の場合、UW-IMAPで実現したいという都合上、PAMから攻める必要があることに何らかわりはないのであった。
しかし、UW-IMAPの野郎、コンフィグファイルが存在しない、とはなんたるフザけた仕様であることよ。すべてコンパイル時のオプションによって指定する仕様とのこと。まぁ、コンフィグファイル(sendmail.cf)側に、メールの操作アルゴリズムのほとんどを記載する仕様のsendmailといい、ほとんど共有ライブラリを使わずに、バグらしいバグが皆無というqmailといい、MUAながらいまどきCUIウィンドウ操作というmaveといい(?)、メール関係者は変態揃いなんだけども……。
# yumdownloader --source pam
# rpm -ivh pam-1.1.0-7.fc12.src.rpm
# rpmbuild -bp rpmbuild/SPECS/pam.spec
# yum install cracklib-devel audit-libs-devel linuxdoc-tools w3m
# cd rpmbuild/BUILD
# cp -a Linux-PAM-1.1.0 Linux-PAM-1.1.0.org
# cp -a Linux-PAM-1.1.0 Linux-PAM-1.1.0.my
……と、大枠の改造方針の説明が後回しになってしまった。pamにはpam_userdbというモジュールがあるのだが、こいつは「独自のpasswd/shadowを使って認証する」みたいなモジュールである。こいつを少しイジり「実際のユーザの指定によらず、強制的に『master1』というユーザのパスワードを使って認証する」という動作モードを追加することで、マスターキー機能を実現するわけである。
diff -r -N -U 3 Linux-PAM-1.1.0.org/modules/pam_userdb/pam_userdb.c Linux-PAM-1.1.0.my/modules/pam_userdb/pam_userdb.c
--- Linux-PAM-1.1.0.org/modules/pam_userdb/pam_userdb.c 2008-12-01 02:13:58.000000000 +0900
+++ Linux-PAM-1.1.0.my/modules/pam_userdb/pam_userdb.c 2010-05-18 12:57:57.000000000 +0900
@@ -86,12 +86,13 @@
static int
_pam_parse (pam_handle_t *pamh, int argc, const char **argv,
- const char **database, const char **cryptmode)
+ const char **database, const char **cryptmode, const char **masteruser)
{
int ctrl;
*database = NULL;
*cryptmode = NULL;
+ *masteruser = NULL;
/* step through arguments */
for (ctrl = 0; argc-- > 0; ++argv)
@@ -128,6 +129,13 @@
pam_syslog(pamh, LOG_ERR,
"crypt= specification missing argument - ignored");
}
+ else if (!strncasecmp(*argv,"master=", 7))
+ {
+ *masteruser = (*argv) + 7;
+ if (**masteruser == '\0')
+ pam_syslog(pamh, LOG_ERR,
+ "master= specification missing argument - ignored");
+ }
else
{
pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
@@ -148,7 +156,7 @@
* -2 = System error
*/
static int
-user_lookup (pam_handle_t *pamh, const char *database, const char *cryptmode,
+user_lookup (pam_handle_t *pamh, const char *database, const char *cryptmode, const char *masteruser,
const char *user, const char *pass, int ctrl)
{
DBM *dbm;
@@ -193,6 +201,14 @@
free(key.dptr);
}
+ if (masteruser) {
+ key.dptr = x_strdup(masteruser);
+ key.dsize = strlen(masteruser);
+ data = dbm_fetch(dbm, key);
+ memset(key.dptr, 0, key.dsize);
+ free(key.dptr);
+ }
+
if (ctrl & PAM_DEBUG_ARG) {
pam_syslog(pamh, LOG_INFO,
"password in database is [%p]`%.*s', len is %d",
@@ -332,10 +348,11 @@
const void *password;
const char *database = NULL;
const char *cryptmode = NULL;
+ const char *masteruser = NULL;
int retval = PAM_AUTH_ERR, ctrl;
/* parse arguments */
- ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode);
+ ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode, &masteruser);
if (database == NULL) {
pam_syslog(pamh, LOG_ERR, "can not get the database name");
return PAM_SERVICE_ERR;
@@ -380,7 +397,7 @@
username);
/* Now use the username to look up password in the database file */
- retval = user_lookup(pamh, database, cryptmode, username, password, ctrl);
+ retval = user_lookup(pamh, database, cryptmode, masteruser, username, password, ctrl);
switch (retval) {
case -2:
/* some sort of system error. The log was already printed */
@@ -427,10 +444,11 @@
const char *username;
const char *database = NULL;
const char *cryptmode = NULL;
+ const char *masteruser = NULL;
int retval = PAM_AUTH_ERR, ctrl;
/* parse arguments */
- ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode);
+ ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode, &masteruser);
/* Get the username */
retval = pam_get_user(pamh, &username, NULL);
@@ -440,7 +458,7 @@
}
/* Now use the username to look up password in the database file */
- retval = user_lookup(pamh, database, cryptmode, username, "", ctrl);
+ retval = user_lookup(pamh, database, cryptmode, masteruser, username, "", ctrl);
switch (retval) {
case -2:
/* some sort of system error. The log was already printed */
# diff -r -N -U 3 Linux-PAM-1.1.0.org Linux-PAM-1.1.0.my > ../SOURCES/pam-1.1.0-masterkey.patch
zakato.itline.jp:/root/rpmbuild/BUILD # diff -c ../SPECS/pam.spec ../SPECS/pam.my.spec
*** ../SPECS/pam.spec 2009-11-02 18:15:49.000000000 +0900
--- ../SPECS/pam.my.spec 2010-05-16 00:17:26.000000000 +0900
***************
*** 3,9 ****
Summary: An extensible library which provides authentication for applications
Name: pam
Version: 1.1.0
! Release: 7%{?dist}
# The library is BSD licensed with option to relicense as GPLv2+ - this option is redundant
# as the BSD license allows that anyway. pam_timestamp and pam_console modules are GPLv2+,
License: BSD and GPLv2+
--- 3,9 ----
Summary: An extensible library which provides authentication for applications
Name: pam
Version: 1.1.0
! Release: 8%{?dist}
# The library is BSD licensed with option to relicense as GPLv2+ - this option is redundant
# as the BSD license allows that anyway. pam_timestamp and pam_console modules are GPLv2+,
License: BSD and GPLv2+
***************
*** 28,33 ****
--- 28,34 ----
Patch5: pam-1.1.0-notally.patch
Patch6: pam-1.1.0-xauth-context.patch
Patch7: pam-1.1.0-console-fixes.patch
+ Patch8: pam-1.1.0-masterkey.patch
%define _sbindir /sbin
%define _moduledir /%{_lib}/security
***************
*** 95,100 ****
--- 96,102 ----
%patch5 -p1 -b .notally
%patch6 -p1 -b .xauth-context
%patch7 -p1 -b .console-fixes
+ %patch8 -p1 -b .masterkey
libtoolize -f
autoreconf
# rpmbuild -ba ../SPECS/pam.my.spec
# rpm -Uvh ../RPMS/x86_64/pam-1.1.0-8.fc12.x86_64.rpm
# perl Linux-PAM-1.1.0.my/modules/pam_userdb/create.pl /etc/pam_userdb_master.db
Using database: /etc/pam_userdb_master.db
master1 password1
# telnet localhost 110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
user mitsu
+OK
pass password1
-ERR Authentication failed.
quit
+OK Logging out
Connection closed by foreign host.
# vi /etc/pam.d/dovecot
#%PAM-1.0
auth required pam_nologin.so
auth sufficient pam_userdb.so debug dump db=/etc/pam_userdb_master master=master1
auth include password-auth
account include password-auth
session include password-auth
# telnet localhost 110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
user mitsu
+OK
pass password1
+OK Logged in.
quit
+OK Logging out.
Connection closed by foreign host.
以上で、一応の目的は達せられたが、実はもう少し続くのであった。というのも、DB中に生パスワードが記録されているのが気持ち悪いので、それを是正するのだ。pam_userdbには、DB内にcryptでハッシュ化されたパスワードを記録しておき、それで認証させる機能があるのでそれを利用する。
しかしながら、pam_userdbのソースを読む限り、crypt対応とはいえ、最も基本的なDESにのみ対応であり、パスワードは8文字以内に制限される。強度の高いSHA-512を使うことはできないことに注意。まぁ、それでもやらないよりはマシであるが。
# ruby -e "p 'passwd2'.crypt('aA')"
"aApZ.8h6A9nPQ"
# perl Linux-PAM-1.1.0.my/modules/pam_userdb/create.pl /etc/pam_userdb_master.db
Using database: /etc/pam_userdb_master.db
master2 aApZ.8h6A9nPQ
# chmod 600 /etc/pam_userdb_master.db
# vi /etc/pam.d/dovecot
#%PAM-1.0
auth required pam_nologin.so
auth sufficient pam_userdb.so debug dump db=/etc/pam_userdb_master master=master2 crypt=crypt
auth include password-auth
account include password-auth
session include password-auth
# telnet localhost 110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
user mitsu
+OK
pass passwd2
+OK Logged in.
quit
+OK Logging out.
Connection closed by foreign host.