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