SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|

2010-05-15(Sat) PAMのモジュールを小改造する

  ひょんなことから、特定のパスワードで、全ユーザのメールボックスにPOPアクセスしたい、という要望が生じた。で、いろいろと考えた末、PAMモジュールの改造に手を出すことにした。

  いわゆる、パスワードによる「マスターキー」の実現である。PAMモジュールなので、PAM認証をサポートしている機能なら、すべての機能で「マスターキー」機能が利用可能である。うっしっし、PAM万歳、である。

  なお、今回はdovecotを利用例に挙げているが、dovecotには、独自の「マスターキー」機能が存在するようである(詳細は未確認)。しかしながら、私の場合、UW-IMAPで実現したいという都合上、PAMから攻める必要があることに何らかわりはないのであった。

  しかし、UW-IMAPの野郎、コンフィグファイルが存在しない、とはなんたるフザけた仕様であることよ。すべてコンパイル時のオプションによって指定する仕様とのこと。まぁ、コンフィグファイル(sendmail.cf)側に、メールの操作アルゴリズムのほとんどを記載する仕様のsendmailといい、ほとんど共有ライブラリを使わずに、バグらしいバグが皆無というqmailといい、MUAながらいまどきCUIウィンドウ操作というmaveといい(?)、メール関係者は変態揃いなんだけども……。

  余談はさておき、今回はFedora12を前提に作業してみる。

  まず、pamのソースをダウンロード、インストール、ソースビルドする。

# 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

  以下、BUILDの下で作業する。

# cd rpmbuild/BUILD

  rpmbuildを利用する場合、差分をpatchに仕立てる必要があるので、orgとmyのソースツリーを作る。

# 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

  そのうえで、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 */

  rpmbuild用にpatchを仕立てる。

# diff -r -N -U 3 Linux-PAM-1.1.0.org Linux-PAM-1.1.0.my > ../SOURCES/pam-1.1.0-masterkey.patch

  specファイルに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 

  ここからはPAMの設定。ユーザDBにマスタパスワードを登録する。なお「master1」と「password1」の間は半角スペース、入力後はCtrl+Dで終了。

# 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.

  dovecotのPAM設定に1行を追加する。まずは、デバッグ表示を入れて様子を見る。

# 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.

  うまくいかなければ、/var/log/secureをノゾくといい。デバッグログが出ている。

  以上で、一応の目的は達せられたが、実はもう少し続くのであった。というのも、DB中に生パスワードが記録されているのが気持ち悪いので、それを是正するのだ。pam_userdbには、DB内にcryptでハッシュ化されたパスワードを記録しておき、それで認証させる機能があるのでそれを利用する。

  しかしながら、pam_userdbのソースを読む限り、crypt対応とはいえ、最も基本的なDESにのみ対応であり、パスワードは8文字以内に制限される。強度の高いSHA-512を使うことはできないことに注意。まぁ、それでもやらないよりはマシであるが。

  以下で、パスワードのハッシュを求める。「aA」はsaltであり、任意の2文字の英数字。

# ruby -e "p 'passwd2'.crypt('aA')"
"aApZ.8h6A9nPQ"

  ハッシュをパスワードDBに登録する。忘れずに、パスワードDBのパーミッションも600にしておこう。

# 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

  PAMの再設定。「crypt=crypt」を追加する。イカにも「crypt=SHA-512」などとできるような書式だが、現状「crypt=crypt」しか指定できない。

# 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.

  これにて目的を達成。

  あ。ちゃんと動いたら、PAMの設定から「debug dump」を抜いておくこと。では。