Fix incorrect client authentication in some cases.
authorBo Peng <pengbo@sraoss.co.jp>
Tue, 13 May 2025 08:37:06 +0000 (17:37 +0900)
committerBo Peng <pengbo@sraoss.co.jp>
Thu, 15 May 2025 02:17:02 +0000 (11:17 +0900)
If enable_pool_hba = on, it's auth method is "password", no password
is registered in pool_passwd, and auth method in pg_hba.conf is
"scram-sha-256" or "md5", for the first time when a client connects to
pgpool, authentication is performed as expected. But if a client
connects to the cached connection, any password from the client is
accepted.

authenticate_frontend() asks password to the client and stores it in
frontend->password.  When pgpool authenticate backend,
authenticate_frontend_SCRAM() or authenticate_frontend_md5() is called
depending on pg_hba.conf setting. authenticate_frontend_*() calls
get_auth_password() to get backend cached password but it mistakenly
returned frontend->password if pool_passwd does not have an entry for
the user. Then authenticate_frontend_*() tries to challenge based on
frontend->password. As a result, they compared frontend->password
itself, which always succeed. To fix this, when get_auth_password() is
called with reauth parameter being non 0, return backend->password.

Also if enable_pool_hba = off, in some cases a client is not asked
password for the first time, or when a client connects to cached
connection, even if it should be.

If pool_hba.conf is disabled, get_backend_connection() does not call
Client_authentication(), thus frontend->password is not set. Then
pool_do_reauth() calls do_clear_text_password(). It should have called
authenticate_frontend_clear_text() to get a password from the client,
but a mistake in a if statement prevented it. The mistake was fixed in
this commit.

Pgpool-II versions affected: v4.0 or later.

Also this commit does followings:

- Remove single PostgreSQL code path to simplify the authentication
  code. As a result, following cases are no more Ok.

- Remove crypt authentication support for frontend and backend. The
  feature had not been documented and never tested. Moreover crypt
  authentication was removed long time ago in PostgreSQL (8.4, 2009).

- Add new regression test "040.client_auth". The test performs
  exhaustive client authentication tests using a test specification
  file formatted in CSV.
  The csv files have 7 fields:
  username: the username used for the test case

  pool_hba.conf: takes "scram", "md5", "password", "pam", "ldap" or
  "off". If "scram", "md5" , "password", "pam" or "ldap", the user
  will have an entry in pool_hba.conf accordingly. If "off",
  enable_pool_hba.conf will be off.

  allow_clear_text_frontend_auth: takes "on" or "off".

  pool_passwd: takes "AES", "md5" or "off". If "AES" or "md5" the
  user's password will be stored in pool_passwd using ASE256 or md5
  encryption method accordingly. If "off" is specified, no entry will
  be created.

  pg_hba.conf: almost same as pool_hba.conf except this is for
  pg_hba.conf.

  expected: takes "ok" or "fail". If ok, the authentication is
  expected to be succeeded. If failed, the test is regarded as
  failed. "fail" is opposite. The authentication is expected to be
  failed. If succeeds, the test regarded as failed.

  comment: arbitrary comment

  By changing these fields, we can easily modify or add test
  cases. The merit of this method is possible higher test
  coverage. For human, it is easier to find uncovered test cases in a
  table than in a program code.

Backpatch-through: v4.2

The patch was created by Tatsuo Ishii.

13 files changed:
doc.ja/src/sgml/client-auth.sgml
doc/src/sgml/client-auth.sgml
src/auth/pool_auth.c
src/sample/pgpool.pam
src/test/regression/tests/040.client_auth/client_auth_2node.csv [new file with mode: 0644]
src/test/regression/tests/040.client_auth/create_ldap_user.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/create_pam_user.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/del_ldap_users.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/del_pam_users.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/list_ldap_user.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/list_pam_user.sh [new file with mode: 0755]
src/test/regression/tests/040.client_auth/pam_users.txt [new file with mode: 0644]
src/test/regression/tests/040.client_auth/test.sh [new file with mode: 0755]

index 996cc9f07d3fa6bfde1021f93390f9492904cbd8..571c0c65cee765cd3546b8f2efb45b7c17ba4bb4 100644 (file)
  </para>
 
  <para>
-  クライアントが<productname>Pgpool-II</productname>に接続する際の認証用のパスワードを管理するのが<filename>pool_passwd</filename>ファイルです(詳細は<xref linkend="guc-pool-passwd">をご覧ください。)<filename>pool_passwd</filename>に登録されるパスワードは、<productname>PostgreSQL</productname>に登録されるパスワードと一致している必要があります。<productname>PostgreSQL</productname>に登録されたパスワードを変更しても、<filename>pool_passwd</filename>のパスワードは自動変更されないことに注意してください。<link linkend="auth-scram">scram-shar-256認証</link>と<link linkend="auth-md5">MD5認証</link>では、<filename>pool_passwd</filename>にユーザ名とパスワードを登録するのが必須ですが、<link linkend="auth-password">clear text password認証</link>では必須ではありません。このため、<filename>pool_passwd</filename>のパスワードの保守を避けたい場合には、<link linkend="auth-password">clear text password認証</link>の検討をお勧めします。
+  クライアントが<productname>Pgpool-II</productname>に接続する際の認証用のパスワードを管理するのが<filename>pool_passwd</filename>ファイルです(詳細は<xref linkend="guc-pool-passwd">をご覧ください。)
+   <filename>pool_passwd</filename>に登録されるパスワードは、<productname>PostgreSQL</productname>に登録されるパスワードと一致している必要があります。
+   <productname>PostgreSQL</productname>に登録されたパスワードを変更しても、<filename>pool_passwd</filename>のパスワードは自動変更されないことに注意してください。
+   <link linkend="auth-scram">scram-shar-256認証</link>と<link linkend="auth-md5">MD5認証</link>では、<filename>pool_passwd</filename>にユーザ名とパスワードを登録するのが必須ですが、<link linkend="auth-password">clear text password認証</link>、あるいは<xref linkend="guc-allow-clear-text-frontend-auth">が有効である場合では必須ではありません。
+  このため、<filename>pool_passwd</filename>のパスワードの保守を避けたい場合には、<link linkend="auth-password">clear text password認証</link>あるいは<xref linkend="guc-allow-clear-text-frontend-auth">の検討をお勧めします。
  </para>
 
  <sect1 id="auth-pool-hba-conf">
    <!--
    The following subsections describe the authentication methods in more detail.
    -->
-   以下の小節では、認証方式について詳細に説明します。
+   以下の小節では、<filename>pool_hba.conf</filename>で指定する認証方式について詳細に説明します。
   </para>
 
   <sect2 id="auth-trust">
     そのため、<productname>Pgpool-II</productname>の<literal>md5</literal>認証は<xref linkend="guc-pool-passwd">認証ファイルを使ってサポートしています。
    </para>
 
-   <note>
-    <para>
-     <!--
-     If <productname>Pgpool-II</productname> is operated in raw
-     mode or there's only 1 backend configured, you don't need to
-     setup <xref linkend="guc-pool-passwd">.
-     -->
-     <productname>Pgpool-II</productname>をrawモードで使用している場合、あるいはバックエンドが1つしかない場合は、<xref linkend="guc-pool-passwd">を設定する必要はありません。
-    </para>
-   </note>
-
    <sect3 id="md5-authentication-file-format">
     <!--
     <title>Authentication file format</title>
      must contain the user password in either plain text
      <literal>md5</literal> or <literal>AES</literal> encrypted format.
      -->
-     <literal>md5</literal>認証を使用するには、<xref linkend="guc-pool-passwd">認証ファイルに平文、<literal>md5</literal>または<literal>AES</literal>暗号化形式のいずれかのユーザのパスワード
+     <literal>md5</literal>認証を使用するには、<xref linkend="guc-pool-passwd">認証ファイルに平文、<literal>AES</literal>、<literal>md5</literal>暗号化形式のいずれかのユーザのパスワード
       が含まれている必要があります。
     </para>
     <para>
      -->
      <literal>pool_passwd</literal>ファイルは以下の形式の行を含みます。
      <programlisting>
-      "username:plain_text_passwd"
+      "username:TEXT_plain_text_passwd"
+     </programlisting>
+     <programlisting>
+      "username:AES_encrypted_passwd"
      </programlisting>
      <programlisting>
-      "username:encrypted_passwd"
+      "username:md5_encrypted_passwd"
      </programlisting>
+      (実際には、"TEXT"、"AES"、"md5"の後の"_"は存在しません。)
     </para>
    </sect3>
 
      -->
      2. <filename>pool_hba.conf</filename>にmd5認証のエントリを作成します。
      詳細については<xref linkend="auth-pool-hba-conf">を参照してください。
+     <filename>pool_hba.conf</filename>を有効にしない場合は、<productname>PostgreSQL</productname>の<filename>pg_hba.conf</filename>でmd5認証が指定されていることを確認してください。
     </para>
     <para>
      <!--
      <literal>SCRAM</literal>認証を使用する場合、<xref linkend="guc-pool-passwd">認証ファイルは平文もしくは<literal>AES</literal>暗号化ファーマットのユーザパスワードを含んでる必要があります。
 
       <programlisting>
-       "username:plain_text_passwd"
+       "username:TEXT_plain_text_passwd"
       </programlisting>
       <programlisting>
        "username:AES_encrypted_passwd"
       </programlisting>
+      (実際には、"TEXT"あるいは"AES"の後の"_"は存在しません。)
       <note>
        <para>
        <!--
      -->
      2- <filename>pool_hba.conf</filename>に適切なscram-sha-256エントリを追加します。
      詳細は<xref linkend="auth-pool-hba-conf">を参照してください。
+     <filename>pool_hba.conf</filename>を有効にしない場合は、<productname>PostgreSQL</productname>の<filename>pg_hba.conf</filename>でscram-sha-256認証が指定されていることを確認してください。
     </para>
     <para>
      <!--
     To enable PAM authentication, you need to create a service-configuration
     file for <productname>Pgpool-II</productname> in the system's
     PAM configuration directory (which is usually at <literal>"/etc/pam.d"</literal>).
-    A sample service-configuration file is installed as
-    <filename>"share/pgpool-II/pgpool.pam"</filename> under the install directory.
     -->
     PAM認証を有効にするには、<productname>Pgpool-II</productname>のサービス設定ファイルをシステムのPAM設定ディレクトリ(通常は<literal>"/etc/pam.d"</literal>にあります)に作成しなければなりません。
-    サービス設定ファイルのサンプルはインストールディレクトリ下に<literal>"share/pgpool-II/pgpool.pam"</literal>としてインストールされています。
    </para>
 
    <note>
index 5096c8718d781062a55f2782e59f32864ea43b66..7918557439df91280cf645cdc1c8d51567613db6 100644 (file)
   authentication</link> require that the user name and the password
   have been already registered on <filename>pool_passwd</filename>,
   while <link linkend="auth-password">clear text password
-  authentication</link> does not require that. Therefore, if you want
-  to avoid maintaining the <filename>pool_passwd</filename>, it would
-  be worth to check <link linkend="auth-password">clear text password
-  authentication</link>.
+  authentication</link>
+  or <xref linkend="guc-allow-clear-text-frontend-auth"> does not
+  require that.  Therefore, if you want to avoid maintaining
+  the <filename>pool_passwd</filename>, it would be worth to
+  check <link linkend="auth-password">clear text password
+  authentication</link>
+  or <xref linkend="guc-allow-clear-text-frontend-auth">.
  </para>
 
  <sect1 id="auth-pool-hba-conf">
  <sect1 id="auth-methods">
   <title>Authentication Methods</title>
   <para>
-   The following subsections describe the authentication methods in more detail.
+   The following subsections describe the authentication methods
+   specified by <filename>pool_hba.conf</filename> in more detail.
   </para>
 
   <sect2 id="auth-trust">
     <xref linkend="guc-pool-passwd"> authentication file.
    </para>
 
-   <note>
-    <para>
-     If <productname>Pgpool-II</productname> is operated in raw
-     mode or there's only 1 backend configured, you don't need to
-     setup <xref linkend="guc-pool-passwd">.
-    </para>
-   </note>
-
    <sect3 id="md5-authentication-file-format">
     <title>Authentication file format</title>
     <para>
      To use the <literal>md5</literal> authentication
-     <xref linkend="guc-pool-passwd"> authentication file
-      must contain the user password in either plain text
-      <literal>md5</literal> or <literal>AES</literal> encrypted format.
+     <xref linkend="guc-pool-passwd"> authentication file must contain
+      the user password in either plain text, <literal>AES</literal>
+      or <literal>md5</literal> encrypted format.
     </para>
     <para>
      The <xref linkend="guc-pool-passwd"> file should contain lines in the following format:
       <programlisting>
-       "username:plain_text_passwd"
+       "username:TEXT_plain_text_passwd"
+      </programlisting>
+      <programlisting>
+       "username:AES_encrypted_passwd"
       </programlisting>
       <programlisting>
-       "username:encrypted_passwd"
+       "username:md5_encrypted_passwd"
       </programlisting>
+      (Actually "_" after "TEXT", "AES" or "md5" does not exist.)
     </para>
    </sect3>
 
     <para>
      2- Add an appropriate md5 entry to <filename>pool_hba.conf</filename>.
      See <xref linkend="auth-pool-hba-conf"> for more details.
+     If <filename>pool_hba.conf</filename> is not enabled, make sure
+     that md5 authentication is specified
+     in <filename>pg_hba.conf</filename>
+     of <productname>PostgreSQL</productname>.
     </para>
     <para>
      3- After changing md5 password (in both pool_passwd
       or <literal>AES</literal> encrypted format.
 
       <programlisting>
-       "username:plain_text_passwd"
+       "username:TEXT_plain_text_passwd"
       </programlisting>
       <programlisting>
        "username:AES_encrypted_passwd"
       </programlisting>
+      (Actually "_" after "TEXT" or "AES" does not exist.)
       <note>
        <para>
        <literal>md5</literal> type user passwords in
     <para>
      2- Add an appropriate scram-sha-256 entry to <filename>pool_hba.conf</filename>.
      See <xref linkend="auth-pool-hba-conf"> for more details.
+     If <filename>pool_hba.conf</filename> is not enabled, make sure that md5 authentication is specified in <filename>pg_hba.conf</filename> of <productname>PostgreSQL</productname>.
     </para>
     <para>
      3- After changing SCRAM password (in both pool_passwd
 
    <para>
     To enable PAM authentication, you need to create a service-configuration
-    file for <productname>Pgpool-II</productname> in the system's
+    file named for <productname>Pgpool-II</productname> in the system's
     PAM configuration directory (which is usually at <literal>"/etc/pam.d"</literal>).
-    A sample service-configuration file is installed as
-    <filename>"share/pgpool-II/pgpool.pam"</filename> under the install directory.
    </para>
 
    <note>
index 862e2f733d4d5165098a1b9c5edeb99ef942402b..33d887f784917d000b3e73afb7c76270a7b59a8c 100644 (file)
@@ -3,7 +3,7 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2020     PgPool Global Development Group
+ * Copyright (c) 2003-2025     PgPool Global Development Group
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -37,9 +37,6 @@
 #include "auth/md5.h"
 #include <unistd.h>
 
-#ifdef HAVE_CRYPT_H
-#include <crypt.h>
-#endif
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
 #endif
 static POOL_STATUS pool_send_backend_key_data(POOL_CONNECTION * frontend, int pid, int key, int protoMajor);
 static int     do_clear_text_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor);
 static void pool_send_auth_fail(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp);
-static int     do_crypt(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor);
 static int do_md5(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor,
           char *storedPassword, PasswordType passwordType);
-static int     do_md5_single_backend(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor);
 static void send_md5auth_request(POOL_CONNECTION * frontend, int protoMajor, char *salt);
 static int     read_password_packet(POOL_CONNECTION * frontend, int protoMajor, char *password, int *pwdSize);
 static int     send_password_packet(POOL_CONNECTION * backend, int protoMajor, char *password);
@@ -464,30 +459,6 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
                }
        }
 
-       /* crypt authentication? */
-       else if (authkind == AUTH_REQ_CRYPT)
-       {
-               for (i = 0; i < NUM_BACKENDS; i++)
-               {
-                       if (!VALID_BACKEND(i))
-                               continue;
-
-                       ereport(DEBUG1,
-                                       (errmsg("authentication backend"),
-                                        errdetail("trying crypt authentication")));
-
-                       authkind = do_crypt(CONNECTION(cp, i), frontend, 0, protoMajor);
-
-                       if (authkind < 0)
-                       {
-                               pool_send_auth_fail(frontend, cp);
-                               ereport(ERROR,
-                                               (errmsg("failed to authenticate with backend"),
-                                                errdetail("do_crypt_text_password failed in slot %d", i)));
-                       }
-               }
-       }
-
        /* md5 authentication? */
        else if (authkind == AUTH_REQ_MD5)
        {
@@ -497,41 +468,38 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
                /*
                 * check if we can use md5 authentication.
                 */
-               if (!RAW_MODE && NUM_BACKENDS > 1)
+               if (get_auth_password(MAIN(cp), frontend, 0,
+                                                         &password, &passwordType) == false)
                {
-                       if (get_auth_password(MAIN(cp), frontend, 0,
-                                                                 &password, &passwordType) == false)
+                       /*
+                        * We do not have any password, we can still get the password
+                        * from client using plain text authentication if it is
+                        * allowed by user
+                        */
+                       if (frontend->pool_hba == NULL && pool_config->allow_clear_text_frontend_auth)
                        {
-                               /*
-                                * We do not have any password, we can still get the password
-                                * from client using plain text authentication if it is
-                                * allowed by user
-                                */
-                               if (frontend->pool_hba == NULL && pool_config->allow_clear_text_frontend_auth)
+                               ereport(LOG,
+                                               (errmsg("using clear text authentication with frontend"),
+                                                errdetail("backend will still use md5 auth"),
+                                                errhint("you can disable this behavior by setting allow_clear_text_frontend_auth to off")));
+                               authenticate_frontend_clear_text(frontend);
+                               /* now check again if we have a password now */
+                               if (get_auth_password(MAIN(cp), frontend, 0,
+                                                                         &password, &passwordType) == false)
                                {
-                                       ereport(LOG,
-                                                       (errmsg("using clear text authentication with frontend"),
-                                                        errdetail("backend will still use md5 auth"),
-                                                        errhint("you can disable this behavior by setting allow_clear_text_frontend_auth to off")));
-                                       authenticate_frontend_clear_text(frontend);
-                                       /* now check again if we have a password now */
-                                       if (get_auth_password(MAIN(cp), frontend, 0,
-                                                                                 &password, &passwordType) == false)
-                                       {
-                                               ereport(ERROR,
-                                                               (errmsg("failed to authenticate with backend using md5"),
-                                                                errdetail("unable to get the password")));
-                                       }
+                                       ereport(ERROR,
+                                                       (errmsg("failed to authenticate with backend using md5"),
+                                                        errdetail("unable to get the password")));
                                }
                        }
-                       /* we have a password to use, validate the password type */
-                       if (passwordType != PASSWORD_TYPE_PLAINTEXT && passwordType != PASSWORD_TYPE_MD5
-                               && passwordType != PASSWORD_TYPE_AES)
-                       {
-                               ereport(ERROR,
-                                               (errmsg("failed to authenticate with backend using md5"),
-                                                errdetail("valid password not found")));
-                       }
+               }
+               /* we have a password to use, validate the password type */
+               if (passwordType != PASSWORD_TYPE_PLAINTEXT && passwordType != PASSWORD_TYPE_MD5
+                       && passwordType != PASSWORD_TYPE_AES)
+               {
+                       ereport(ERROR,
+                                       (errmsg("failed to authenticate with backend using md5"),
+                                        errdetail("valid password not found")));
                }
 
                for (i = 0; i < NUM_BACKENDS; i++)
@@ -838,19 +806,16 @@ pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
                                do_clear_text_password(MAIN(cp), frontend, 1, protoMajor);
                                break;
 
-                       case AUTH_REQ_CRYPT:
-                               /* crypt password */
-                               do_crypt(MAIN(cp), frontend, 1, protoMajor);
-                               break;
-
                        case AUTH_REQ_MD5:
                                /* md5 password */
                                authenticate_frontend_md5(MAIN(cp), frontend, 1, protoMajor);
                                break;
+
                        case AUTH_REQ_SASL:
                                /* SCRAM */
                                authenticate_frontend_SCRAM(MAIN(cp), frontend, 1);
                                break;
+
                        default:
                                ereport(ERROR,
                                                (errmsg("authentication failed"),
@@ -1017,6 +982,7 @@ do_clear_text_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, in
        char       *pwd = NULL;
        int                     kind;
        PasswordType passwordType = PASSWORD_TYPE_UNKNOWN;
+       bool            rtn;
 
        if (reauth && frontend->frontend_authenticated)
        {
@@ -1024,7 +990,23 @@ do_clear_text_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, in
                return 0;
        }
 
-       if (get_auth_password(backend, frontend, reauth, &pwd, &passwordType) == false)
+       rtn = get_auth_password(backend, frontend, reauth, &pwd, &passwordType);
+
+       /*
+        * If pool_hba.conf == "password", we should already have frontend
+        * password.  When reauth, we want the password, rather than
+        * backend->password, which is returned by get_auth_password() if
+        * pool_passwd does not have an entry for the user.
+        */
+       if (reauth && rtn && frontend->passwordMapping == NULL &&
+               frontend->pwd_size > 0 &&
+               frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
+       {
+               pwd = frontend->password;
+               passwordType = frontend->passwordType;
+       }
+
+       else if (!rtn || frontend->pwd_size == 0)
        {
                /*
                 * We do not have any password, we can still get the password
@@ -1043,8 +1025,7 @@ do_clear_text_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, in
                        authenticate_frontend_clear_text(frontend);
 
                        /* now check again if we have a password now */
-
-                       if (get_auth_password(backend, frontend, reauth, &pwd, &passwordType) == false)
+                       if (get_auth_password(backend, frontend, 0, &pwd, &passwordType) == false)
                        {
                                ereport(FATAL,
                                                (return_code(2),
@@ -1114,157 +1095,6 @@ do_clear_text_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, in
        return kind;
 }
 
-/*
- * perform crypt authentication
- */
-static int
-do_crypt(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor)
-{
-       char            salt[2];
-       static int      size;
-       static char password[MAX_PASSWORD_SIZE];
-       char            response;
-       int                     kind;
-       int                     len;
-
-       if (!reauth)
-       {
-               /* read salt */
-               pool_read(backend, salt, sizeof(salt));
-       }
-       else
-       {
-               memcpy(salt, backend->salt, sizeof(salt));
-       }
-
-       /* main? */
-       if (IS_MAIN_NODE_ID(backend->db_node_id))
-       {
-               pool_write(frontend, "R", 1);   /* authentication */
-               if (protoMajor == PROTO_MAJOR_V3)
-               {
-                       len = htonl(10);
-                       pool_write(frontend, &len, sizeof(len));
-               }
-               kind = htonl(4);                /* crypt authentication */
-               pool_write(frontend, &kind, sizeof(kind));      /* indicating crypt
-                                                                                                        * authentication */
-               pool_write_and_flush(frontend, salt, sizeof(salt)); /* salt */
-
-               /* read password packet */
-               if (protoMajor == PROTO_MAJOR_V2)
-               {
-                       pool_read(frontend, &size, sizeof(size));
-               }
-               else
-               {
-                       char            k;
-
-                       pool_read(frontend, &k, sizeof(k));
-                       if (k != 'p')
-                       {
-                               ereport(ERROR,
-                                               (errmsg("crypt authentication failed"),
-                                                errdetail("invalid password packet. Packet does not starts with \"p\"")));
-                       }
-                       pool_read(frontend, &size, sizeof(size));
-               }
-
-               if ((ntohl(size) - 4) > sizeof(password))
-               {
-                       ereport(ERROR,
-                                       (errmsg("crypt authentication failed"),
-                                        errdetail("password is too long, password size is %d", ntohl(size) - 4)));
-               }
-
-               pool_read(frontend, password, ntohl(size) - 4);
-       }
-
-       /* connection reusing? */
-       if (reauth)
-       {
-               ereport(DEBUG1,
-                               (errmsg("performing crypt authentication"),
-                                errdetail("size: %d saved_size: %d", (ntohl(size) - 4), backend->pwd_size)));
-
-               if ((ntohl(size) - 4) != backend->pwd_size)
-                       ereport(ERROR,
-                                       (errmsg("crypt authentication failed"),
-                                        errdetail("password size does not match")));
-
-
-               if (memcmp(password, backend->password, backend->pwd_size) != 0)
-                       ereport(ERROR,
-                                       (errmsg("crypt authentication failed"),
-                                        errdetail("password does not match")));
-
-               return 0;
-       }
-
-       /* send password packet to backend */
-       if (protoMajor == PROTO_MAJOR_V3)
-               pool_write(backend, "p", 1);
-       pool_write(backend, &size, sizeof(size));
-       pool_write_and_flush(backend, password, ntohl(size) - 4);
-       pool_read(backend, &response, sizeof(response));
-
-       if (response != 'R')
-       {
-               if (response == 'E')    /* Backend has thrown an error instead */
-               {
-                       char       *message = NULL;
-
-                       if (pool_extract_error_message(false, backend, protoMajor, false, &message) == 1)
-                       {
-                               ereport(ERROR,
-                                               (errmsg("crypt authentication failed"),
-                                                errdetail("%s", message ? message : "backend throws authentication error")));
-                       }
-                       if (message)
-                               pfree(message);
-               }
-               ereport(ERROR,
-                               (errmsg("crypt authentication failed"),
-                                errdetail("invalid packet from backend. backend does not return R while processing clear text password authentication")));
-       }
-
-       if (protoMajor == PROTO_MAJOR_V3)
-       {
-               pool_read(backend, &len, sizeof(len));
-
-               if (ntohl(len) != 8)
-                       ereport(ERROR,
-                                       (errmsg("crypt authentication failed"),
-                                        errdetail("invalid packet from backend. incorrect authentication packet size (%d)", ntohl(len))));
-       }
-
-       /* expect to read "Authentication OK" response. kind should be 0... */
-       pool_read(backend, &kind, sizeof(kind));
-
-       /* if authenticated, save info */
-       if (kind == 0)
-       {
-               int                     msglen;
-
-               pool_write(frontend, "R", 1);
-
-               if (protoMajor == PROTO_MAJOR_V3)
-               {
-                       msglen = htonl(8);
-                       pool_write(frontend, &msglen, sizeof(msglen));
-               }
-
-               msglen = htonl(0);
-               pool_write_and_flush(frontend, &msglen, sizeof(msglen));
-
-               backend->auth_kind = 4;
-               backend->pwd_size = ntohl(size) - 4;
-               memcpy(backend->password, password, backend->pwd_size);
-               memcpy(backend->salt, salt, sizeof(salt));
-       }
-       return kind;
-}
-
 /*
  * Do the SCRAM authentication with the frontend using the stored
  * password in the pool_passwd file.
@@ -1540,13 +1370,6 @@ authenticate_frontend_md5(POOL_CONNECTION * backend, POOL_CONNECTION * frontend,
        PasswordType storedPasswordType = PASSWORD_TYPE_UNKNOWN;
        char       *storedPassword = NULL;
 
-       if (RAW_MODE || NUM_BACKENDS == 1)
-       {
-               if (backend)
-                       do_md5_single_backend(backend, frontend, reauth, protoMajor);
-               return;                                 /* This will be handled later */
-       }
-
        if (get_auth_password(backend, frontend, reauth,&storedPassword, &storedPasswordType) == false)
        {
                ereport(FATAL,
@@ -1616,79 +1439,12 @@ authenticate_frontend_md5(POOL_CONNECTION * backend, POOL_CONNECTION * frontend,
 }
 
 /*
- * perform MD5 authentication
+ * Get user's password from various sources. Firstly try pool_passwd and
+ * return it if the user's password is there. If reauth == 0 (first time the
+ * user connect to pgpool) and the password is in frontend, return it. If
+ * reauth != 0 (reuse connection pool) and the password is in backendm return
+ * it.
  */
-static int
-do_md5_single_backend(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int protoMajor)
-{
-       char            salt[4];
-       static int      size;
-       static char password[MAX_PASSWORD_SIZE];
-       int                     kind;
-
-       if (!reauth)
-       {
-               /* read salt from backend */
-               pool_read(backend, salt, sizeof(salt));
-               ereport(DEBUG1,
-                               (errmsg("performing md5 authentication"),
-                                errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id,
-                                                  salt[0], salt[1], salt[2], salt[3])));
-       }
-       else
-       {
-               /* Use the saved salt */
-               memcpy(salt, backend->salt, sizeof(salt));
-       }
-
-       /* Send md5 auth request to frontend */
-       send_md5auth_request(frontend, protoMajor, salt);
-
-       /* Read password packet */
-       read_password_packet(frontend, protoMajor, password, &size);
-
-       /* connection reusing? compare it with saved password */
-       if (reauth)
-       {
-               if (backend->passwordType != PASSWORD_TYPE_MD5)
-                       ereport(ERROR,
-                                       (errmsg("md5 authentication failed"),
-                                        errdetail("invalid password type")));
-
-               if (size != backend->pwd_size)
-                       ereport(ERROR,
-                                       (errmsg("md5 authentication failed"),
-                                        errdetail("password does not match")));
-
-               if (memcmp(password, backend->password, backend->pwd_size) != 0)
-                       ereport(ERROR,
-                                       (errmsg("md5 authentication failed"),
-                                        errdetail("password does not match")));
-               return 0;
-       }
-       else
-       {
-               /* Send password packet to backend and receive auth response */
-               kind = send_password_packet(backend, protoMajor, password);
-               if (kind < 0)
-                       ereport(ERROR,
-                                       (errmsg("md5 authentication failed"),
-                                        errdetail("backend replied with invalid kind")));
-
-               /* If authenticated, reply back to frontend and save info */
-               if (kind == AUTH_REQ_OK)
-               {
-                       send_auth_ok(frontend, protoMajor);
-                       backend->passwordType = PASSWORD_TYPE_MD5;
-                       backend->auth_kind = AUTH_REQ_MD5;
-                       backend->pwd_size = size;
-                       memcpy(backend->password, password, backend->pwd_size);
-                       memcpy(backend->salt, salt, sizeof(salt));
-               }
-       }
-       return kind;
-}
-
 static bool
 get_auth_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth,
                                  char **password, PasswordType *passwordType)
@@ -1703,7 +1459,8 @@ get_auth_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int rea
                 * check if we have password stored in the frontend connection. that
                 * could come by using the clear text auth
                 */
-               if (frontend->pwd_size > 0 && frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
+               if (!reauth &&
+                       frontend->pwd_size > 0 && frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
                {
                        *password = frontend->password;
                        *passwordType = frontend->passwordType;
@@ -1748,9 +1505,6 @@ do_md5(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int pr
        char            encbuf[POOL_PASSWD_LEN + 1];
        char       *pool_passwd = NULL;
 
-       if (RAW_MODE || NUM_BACKENDS == 1)
-               return do_md5_single_backend(backend, frontend, reauth, protoMajor);
-
        if (passwordType == PASSWORD_TYPE_AES)
        {
                /*
@@ -1792,8 +1546,8 @@ do_md5(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth, int pr
                 * already received the password from frontend using the clear text
                 * auth, we may not need to authenticate it
                 */
-               if (pool_config->allow_clear_text_frontend_auth &&
-                       frontend->auth_kind == AUTH_REQ_PASSWORD &&
+               if ((pool_config->allow_clear_text_frontend_auth ||
+                        frontend->auth_kind == AUTH_REQ_PASSWORD) &&
                        frontend->pwd_size > 0 &&
                        frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
                {
@@ -2119,8 +1873,8 @@ do_SCRAM(POOL_CONNECTION * frontend, POOL_CONNECTION * backend, int protoMajor,
                 * already received the password from frontend using the clear text
                 * auth, we may not need to authenticate it
                 */
-               if (pool_config->allow_clear_text_frontend_auth &&
-                       frontend->auth_kind == AUTH_REQ_PASSWORD &&
+               if ((pool_config->allow_clear_text_frontend_auth ||
+                        frontend->auth_kind == AUTH_REQ_PASSWORD) &&
                        frontend->pwd_size > 0 &&
                        frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
                {
index 320f086f56ec8d680d278e6e920c08d124908e27..b31d5e2b281d68f38d024e83988c837435faf085 100644 (file)
@@ -1,3 +1,2 @@
 #%PAM-1.0
-auth           required        pam_permit.so
-account                required        pam_permit.so
+# create your own PAM configurations
diff --git a/src/test/regression/tests/040.client_auth/client_auth_2node.csv b/src/test/regression/tests/040.client_auth/client_auth_2node.csv
new file mode 100644 (file)
index 0000000..71a47e3
--- /dev/null
@@ -0,0 +1,126 @@
+username,pool_hba.conf,allow_clear_text_frontend_auth,pool_passwd,pg_hba.conf,expected,comment
+scram1,scram,off,AES,scram,ok,
+scram2,scram,off,AES,md5,ok,
+scram3,scram,off,AES,password,ok,
+scram4,scram,off,AES,pam,ok,
+scram5,scram,off,AES,ldap,ok,
+scram6,scram,off,md5,scram,fail,
+scram7,scram,off,md5,md5,fail,
+scram8,scram,off,md5,password,fail,
+scram9,scram,off,md5,pam,fail,
+scram10,scram,off,md5,ldap,fail,
+scram11,scram,off,text,scram,ok,
+scram12,scram,off,text,md5,ok,
+scram13,scram,off,text,password,ok,
+scram14,scram,off,text,pam,ok,
+scram15,scram,off,text,ldap,ok,
+scram16,scram,off,off,scram,fail,
+scram17,scram,off,off,md5,fail,
+scram18,scram,off,off,password,fail,
+scram19,scram,off,off,pam,fail,
+scram20,scram,off,off,ldap,fail,
+md51,md5,off,AES,scram,ok,
+md52,md5,off,AES,md5,ok,
+md53,md5,off,AES,password,ok,
+md54,md5,off,AES,pam,ok,
+md55,md5,off,AES,ldap,ok,
+md56,md5,off,md5,scram,fail,
+md57,md5,off,md5,md5,ok,
+md58,md5,off,md5,password,fail,
+md59,md5,off,md5,pam,fail,
+md510,md5,off,md5,ldap,fail,
+md511,md5,off,text,scram,ok,
+md512,md5,off,text,md5,ok,
+md513,md5,off,text,password,ok,
+md514,md5,off,text,pam,ok,
+md515,md5,off,text,ldap,ok,
+md516,md5,off,off,scram,fail,
+md517,md5,off,off,md5,fail,
+md518,md5,off,off,password,fail,
+md519,md5,off,off,pam,fail,
+md520,md5,off,off,ldap,fail,
+p1,password,off,AES,scram,ok,
+p2,password,off,AES,md5,ok,
+p3,password,off,AES,password,ok,
+p4,password,off,AES,pam,ok,
+p5,password,off,AES,ldap,ok,
+p6,password,off,md5,scram,fail,
+p7,password,off,md5,md5,ok,
+p8,password,off,md5,password,fail,
+p9,password,off,md5,pam,fail,
+p10,password,off,md5,ldap,fail,
+p11,password,off,text,scram,ok,
+p12,password,off,text,md5,ok,
+p13,password,off,text,password,ok,
+p14,password,off,text,pam,ok,
+p15,password,off,text,ldap,ok,
+p16,password,off,off,scram,ok,
+p17,password,off,off,md5,ok,
+p18,password,off,off,password,ok,
+p19,password,off,off,pam,ok,
+p20,password,off,off,ldap,ok,
+o1,off,on,off,scram,ok,
+o2,off,on,off,md5,ok,
+o3,off,on,off,password,ok,
+o4,off,on,off,pam,ok,
+o5,off,on,off,ldap,ok,
+o6,off,off,AES,scram,ok,
+o7,off,off,AES,md5,ok,
+o8,off,off,AES,password,ok,
+o9,off,off,AES,pam,ok,
+o10,off,off,AES,ldap,ok,
+o11,off,off,md5,scram,fail,
+o12,off,off,md5,md5,ok,
+o13,off,off,md5,password,fail,
+o14,off,off,md5,pam,fail,
+o15,off,off,md5,ldap,fail,
+o16,off,off,text,scram,ok,
+o17,off,off,text,md5,ok,
+o18,off,off,text,password,ok,
+o19,off,off,text,pam,ok,
+o20,off,off,text,ldap,ok,
+o21,off,off,off,scram,fail,
+o22,off,off,off,md5,fail,
+o23,off,off,off,password,ok,
+o24,off,off,off,pam,ok,
+o25,off,off,off,ldap,ok,
+pam1,pam,off,AES,scram,ok,
+pam2,pam,off,AES,md5,ok,
+pam3,pam,off,AES,password,ok,
+pam4,pam,off,AES,pam,ok,
+pam5,pam,off,AES,ldap,ok,
+pam6,pam,off,md5,scram,fail,
+pam7,pam,off,md5,md5,ok,
+pam8,pam,off,md5,password,fail,
+pam9,pam,off,md5,pam,fail,
+pam10,pam,off,md5,ldap,fail,
+pam11,pam,off,text,scram,ok,
+pam12,pam,off,text,md5,ok,
+pam13,pam,off,text,password,ok,
+pam14,pam,off,text,pam,ok,
+pam15,pam,off,text,ldap,ok,
+pam16,pam,off,off,scram,fail,
+pam17,pam,off,off,md5,fail,
+pam18,pam,off,off,password,fail,
+pam19,pam,off,off,pam,fail,
+pam20,pam,off,off,ldap,fail,
+ldap1,ldap,off,AES,scram,ok,
+ldap2,ldap,off,AES,md5,ok,
+ldap3,ldap,off,AES,password,ok,
+ldap4,ldap,off,AES,pam,ok,
+ldap5,ldap,off,AES,ldap,ok,
+ldap6,ldap,off,md5,scram,fail,
+ldap7,ldap,off,md5,md5,ok,
+ldap8,ldap,off,md5,password,fail,
+ldap9,ldap,off,md5,pam,fail,
+ldap10,ldap,off,md5,ldap,fail,
+ldap11,ldap,off,text,scram,ok,
+ldap12,ldap,off,text,md5,ok,
+ldap13,ldap,off,text,password,ok,
+ldap14,ldap,off,text,pam,ok,
+ldap15,ldap,off,text,ldap,ok,
+ldap16,ldap,off,off,scram,fail,
+ldap17,ldap,off,off,md5,fail,
+ldap18,ldap,off,off,password,fail,
+ldap19,ldap,off,off,pam,fail,
+ldap20,ldap,off,off,ldap,fail,
diff --git a/src/test/regression/tests/040.client_auth/create_ldap_user.sh b/src/test/regression/tests/040.client_auth/create_ldap_user.sh
new file mode 100755 (executable)
index 0000000..0d15835
--- /dev/null
@@ -0,0 +1,20 @@
+#! /usr/bin/bash
+# Create input file for ldapadd command to stdout.
+# Users' list must be provided from stdin.
+if [ $# -ne 0 ];then
+    echo "usage: $0"
+    exit 1
+fi
+
+while read username
+do
+    cat <<EOF
+dn: uid=$username,ou=people,dc=nodomain
+cn: $username
+sn: $username
+objectClass: inetOrgPerson
+objectClass: simpleSecurityObject
+userPassword: $username
+
+EOF
+done
diff --git a/src/test/regression/tests/040.client_auth/create_pam_user.sh b/src/test/regression/tests/040.client_auth/create_pam_user.sh
new file mode 100755 (executable)
index 0000000..b65c06e
--- /dev/null
@@ -0,0 +1,17 @@
+#! /usr/bin/bash
+# Create input file (newusers_input.txt) for newusers command.
+# Users' list must be provided as $1.
+if [ $# -ne 1 ];then
+    echo "usage: $0 pam_users_list_file"
+    exit 1
+fi
+
+USERS=newusers_input.txt
+
+spec=$1
+
+cp /dev/null $USERS
+cat $spec|while read username
+do
+    echo "$username:$username::::/nonexistent:/usr/sbin/nologin" >> $USERS
+done
diff --git a/src/test/regression/tests/040.client_auth/del_ldap_users.sh b/src/test/regression/tests/040.client_auth/del_ldap_users.sh
new file mode 100755 (executable)
index 0000000..af77529
--- /dev/null
@@ -0,0 +1,8 @@
+#! /usr/bin/bash
+# read users list from stdin and perform ldapdelete command.
+PASSWORD=ldapadmin
+set -e
+while read i
+do
+       ldapdelete -x -w $PASSWORD -D cn=admin,dc=nodomain uid=$i,ou=people,dc=nodomain
+done
diff --git a/src/test/regression/tests/040.client_auth/del_pam_users.sh b/src/test/regression/tests/040.client_auth/del_pam_users.sh
new file mode 100755 (executable)
index 0000000..4336c01
--- /dev/null
@@ -0,0 +1,7 @@
+#! /usr/bin/bash
+# read users list from stdin and perform userdel command.
+set -e
+while read i
+do
+    /usr/sbin/userdel $i
+done
diff --git a/src/test/regression/tests/040.client_auth/list_ldap_user.sh b/src/test/regression/tests/040.client_auth/list_ldap_user.sh
new file mode 100755 (executable)
index 0000000..dffa928
--- /dev/null
@@ -0,0 +1,17 @@
+#! /usr/bin/bash
+# List up user names used in the LDAP authentication test.
+# The result is put to stdout.
+spec=client_auth_2node.csv
+IFS=","
+cat $spec|while read line
+do
+    set $line
+    if [ $1 != 'username' ];then
+       username=$1
+       pool_hba=$2
+       pg_hba=$5
+       if [ $pool_hba = "ldap" -o $pg_hba = "ldap" ];then
+           echo $username
+       fi
+    fi
+done
diff --git a/src/test/regression/tests/040.client_auth/list_pam_user.sh b/src/test/regression/tests/040.client_auth/list_pam_user.sh
new file mode 100755 (executable)
index 0000000..cbcfe9e
--- /dev/null
@@ -0,0 +1,16 @@
+#! /usr/bin/bash
+# List up user names used in the PAM authentication test.
+spec=client_auth_2node.csv
+IFS=","
+cat $spec|while read line
+do
+    set $line
+    if [ $1 != 'username' ];then
+       username=$1
+       pool_hba=$2
+       pg_hba=$5
+       if [ $pool_hba = "pam" -o $pg_hba = "pam" ];then
+           echo $username
+       fi
+    fi
+done
diff --git a/src/test/regression/tests/040.client_auth/pam_users.txt b/src/test/regression/tests/040.client_auth/pam_users.txt
new file mode 100644 (file)
index 0000000..e1f5a95
--- /dev/null
@@ -0,0 +1,41 @@
+scram4
+scram9
+scram14
+scram19
+md54
+md59
+md514
+md519
+p4
+p9
+p14
+p19
+o4
+o9
+o14
+o19
+o24
+pam1
+pam2
+pam3
+pam4
+pam5
+pam6
+pam7
+pam8
+pam9
+pam10
+pam11
+pam12
+pam13
+pam14
+pam15
+pam16
+pam17
+pam18
+pam19
+pam20
+ldap4
+ldap9
+ldap14
+ldap19
diff --git a/src/test/regression/tests/040.client_auth/test.sh b/src/test/regression/tests/040.client_auth/test.sh
new file mode 100755 (executable)
index 0000000..ad5f693
--- /dev/null
@@ -0,0 +1,474 @@
+#!/usr/bin/bash
+#-------------------------------------------------------------------
+# test script for client authentication
+#
+
+# This test is only valid with PostgreSQL 10 or later.
+if [ $PGVERSION -le 9 ];then
+    echo "all tests skipped due to PostgreSQL version: $PGVERSION"
+    exit 0
+fi
+
+# --------------------------------------------------------------------
+# PAM authentication tests are not performed by default.  In order to
+# test PAM authentication (both frontend and backend), run following
+# scripts here (040.client_auth).
+#
+# Create pam users list.
+# $ ./list_pam_user.sh > pam_users.txt
+#
+# Create input file for newusers command.
+# The result file is newusers_input.txt.
+# $ ./create_pam_user.sh pam_users.txt
+#
+# Finally run newusers command as root.
+# the users are not allowed to login (login shell is /usr/sbin/nologin)
+# /usr/sbin/newusers newusers_input.txt
+#
+# Note that newusers just ignore users that are already registered in
+# the system. So it is harmless to generate new newusers_input.txt and
+# run newusers command. It will happily register new users.
+#
+# Then set the environment variable DO_PAM_TEST to "true".
+#
+if [ "$DO_PAM_TEST" != "true" ];then
+    echo "all pam tests will be skipped"
+fi
+# --------------------------------------------------------------------
+# LDAP authentication tests are not performed by default.  In order to
+# test LDAP authentication (both frontend and backend), run following
+# scripts here (040.client_auth). The procedure below assumes that
+# LDAP server is created on localhost, with LDAP admin name is
+# "admin", domain is "nodomain".
+#
+# To create LDAP ldif file, run:
+# $ cat client_auth_2node.csv|./list_ldap_user.sh|./create_ldap_user.sh > ldap_users.ldif
+#
+# Then run ldapadd command.
+# ldapadd -x -D cn=admin,dc=nodomain -W -f ldap_users.ldif
+#
+# Set the environment variable DO_LDAP_TEST to "true".
+#
+if [ "$DO_LDAP_TEST" != "true" ];then
+    echo "all ldap tests will be skipped"
+fi
+# --------------------------------------------------------------------
+# Function declarations
+
+# create user
+# $1: username
+# $2: "scram", "md5", "password", "pam" or "ldap".
+# If "password" is specified, encryption method becomes 'scram-sha-256'.
+function add_user()
+{
+    createuser $1
+    if [ $2 = "scram" ];then
+       psql -c "SET password_encryption = 'scram-sha-256'; ALTER USER $1 WITH ENCRYPTED PASSWORD '$1'"
+    elif [ $2 = "md5" ];then
+       psql -c "SET password_encryption = 'md5'; ALTER USER $1 WITH ENCRYPTED PASSWORD '$1'"
+    elif [ $2 = "password" ];then
+       psql -c "SET password_encryption = 'scram-sha-256'; ALTER USER $1 WITH ENCRYPTED PASSWORD '$1'"
+    elif [ "$DO_PAM_TEST" = "true" -a $2 = "pam" ];then
+       psql -c "SET password_encryption = 'scram-sha-256'; ALTER USER $1 WITH ENCRYPTED PASSWORD '$1'"
+    elif [ "$DO_PAM_TEST" != "true" -a $2 = "pam" ];then
+       echo "do nothing because DO_PAM_TEST is not true and auth method is pam"
+    elif [ "$DO_LDAP_TEST" = "true" -a $2 = "ldap" ];then
+       psql -c "SET password_encryption = 'scram-sha-256'; ALTER USER $1 WITH ENCRYPTED PASSWORD '$1'"
+    elif [ "$DO_LDAP_TEST" != "true" -a $2 = "ldap" ];then
+       echo "do nothing because DO_LDAP_TEST is not true and auth method is ldap"
+    else
+       echo "wrong auth method \"$2\" for add_user $1"
+       exit 1
+    fi
+}
+
+# create pool_hba.conf entry
+# $1: username
+# $2: auth method for pool_hba.conf
+# "scram", "md5" or "password"
+# $3: pgpool major version first digit (e.g. "4")
+function add_pool_hba()
+{
+    # "scram-sha-256" is only supported in pgpool version 4.0 or later
+    if [ $3 -gt 3 -a $2 = "scram" ];then
+       echo "host      all     $1      127.0.0.1/32    scram-sha-256" >> $POOL_HBA
+       echo "host      all     $1      ::1/128 scram-sha-256" >> $POOL_HBA
+    elif [ $2 = "md5" ];then
+       echo "host      all     $1      127.0.0.1/32    md5" >> $POOL_HBA
+       echo "host      all     $1      ::1/128 md5" >> $POOL_HBA
+    elif [ "$DO_PAM_TEST" = "true" -a $2 = "pam" ];then
+       echo "host      all     $1      127.0.0.1/32    pam" >> $POOL_HBA
+       echo "host      all     $1      ::1/128 pam" >> $POOL_HBA
+    elif [ "$DO_PAM_TEST" != "true" -a $2 = "pam" ];then
+       echo "do nothing because DO_PAM_TEST is not true and auth method is pam"
+    elif [ "$DO_LDAP_TEST" = "true" -a $2 = "ldap" ];then
+       echo "host      all     $1      127.0.0.1/32    ldap ldapserver=localhost ldapbasedn="dc=nodomain" ldapsearchattribute=uid" >> $POOL_HBA
+       echo "host      all     $1      ::1/128 ldap ldapserver=localhost ldapbasedn="dc=nodomain" ldapsearchattribute=uid" >> $POOL_HBA
+    elif [ "$DO_LDAP_TEST" != "true" -a $2 = "ldap" ];then
+       echo "do nothing because DO_LDAP_TEST is not true and auth method is ldap"
+    # "password" is only supported in pgpool version 4.0 or later
+    elif [ $3 -gt 3 -a $2 = "password" ];then
+       echo "host      all     $1      127.0.0.1/32    password" >> $POOL_HBA
+       echo "host      all     $1      ::1/128 password" >> $POOL_HBA
+    else
+       echo "skip adding to pool_hba.conf"
+    fi
+}
+
+# create pool_passwd entry
+# $1: username (also password)
+# $2: encryption method (AES, md5 or text)
+# $3: pgpool major version first digit (e.g. "4")
+function add_pool_passwd()
+{
+    # "AES" is only supported in pgpool version 4.0 or later
+    if [ $3 -gt 3 -a $2 = "AES" ];then
+       echo $1|$PG_ENC -m -f $PGPOOL_CONF -u $1 $1
+    elif [ $2 = "md5" ];then
+       echo $1|$PG_MD5 -m -f $PGPOOL_CONF -u $1 $1
+    # "text" is only supported in pgpool version 4.0 or later
+    elif [ $3 -gt 3 -a $2 = "text" ];then
+       if [ $3 -gt 3 ];then
+           echo "$1:TEXT$1" >>  $POOL_PASSWD
+       else
+           echo "$1:$1" >>  $POOL_PASSWD
+       fi
+    else
+       echo "skip adding to pool_passwd"
+    fi
+}
+
+# create pg_hba.conf entry
+# $1: username
+# $2: "scram", "md5", "password" or "pam".
+function add_pg_hba()
+{
+    for i in data0 data1
+    do
+       if [ ! -d $i ];then
+           continue
+       fi
+
+       PG_HBA=$i/pg_hba.conf
+       if [ $2 = "scram" ];then
+           echo "host  all     $1      127.0.0.1/32    scram-sha-256" >> $PG_HBA
+           echo "host  all     $1      ::1/128 scram-sha-256" >> $PG_HBA
+           echo "local all     $1      scram-sha-256" >> $PG_HBA
+       elif [ $2 = "md5" ];then
+           echo "host  all     $1      127.0.0.1/32    md5" >> $PG_HBA
+           echo "host  all     $1      ::1/128 md5" >> $PG_HBA
+           echo "local all     $1      md5" >> $PG_HBA
+       elif [ $2 = "password" ];then
+           echo "host  all     $1      127.0.0.1/32    password" >> $PG_HBA
+           echo "host  all     $1      ::1/128 password" >> $PG_HBA
+           echo "local all     $1      password" >> $PG_HBA
+       elif [ "$DO_PAM_TEST" = "true" -a $2 = "pam" ];then
+           echo "host  all     $1      127.0.0.1/32    pam" >> $PG_HBA
+           echo "host  all     $1      ::1/128 pam" >> $PG_HBA
+           echo "local all     $1      pam" >> $PG_HBA
+       elif [ "$DO_PAM_TEST" != "true" -a $2 = "pam" ];then
+           echo "do nothing because DO_PAM_TEST is not true and auth method is pam"
+       elif [ "$DO_LDAP_TEST" = "true" -a $2 = "ldap" ];then
+           echo "host  all     $1      127.0.0.1/32    ldap ldapserver=localhost ldapbasedn="dc=nodomain" ldapsearchattribute=uid" >> $PG_HBA
+           echo "host  all     $1      ::1/128 ldap ldapserver=localhost ldapbasedn="dc=nodomain" ldapsearchattribute=uid" >> $PG_HBA
+           echo "local all     $1      ldap ldapserver=localhost ldapbasedn="dc=nodomain" ldapsearchattribute=uid" >> $PG_HBA
+       elif [ "$DO_LDAP_TEST" != "true" -a $2 = "ldap" ];then
+           echo "do nothing because DO_LDAP_TEST is not true and auth method is ldap"
+       else
+           echo "wrong auth method \"$2\" for add_pg_hba $1"
+           exit 1
+       fi
+    done
+}
+
+# create pgpass entry
+# $1: username
+function add_pgpass()
+{
+    echo "127.0.0.1:$PGPORT:$PGDATABASE:$1:$1" >> pgpass
+    echo "127.0.0.1:$PGPORT:$PGDATABASE:$1:$1foo" >> pgpasswrong
+}
+
+# Perform actual tests
+# $1: test spec csv file
+# $2: number of PostgreSQL nodes
+# $3: clustering mode
+function do_auth
+{
+    rm -fr $TESTDIR
+    mkdir $TESTDIR
+    cd $TESTDIR
+
+    cp /dev/null $failed_usernames
+    mode=$3
+
+    # create test environment
+    echo -n "creating test environment..."
+    $PGPOOL_SETUP -m $mode -n $2 || exit 1
+    #$PGPOOL_SETUP -m $mode -n 1 || exit 1
+    echo "done."
+
+    source ./bashrc.ports
+    export PGPORT=$PGPOOL_PORT
+    export PGDATABASE=test
+
+    # Set max_init_children to 1 to make sure we reuse the
+    # connection to test wrong password rejection
+    echo "num_init_children = 1" >> etc/pgpool.conf
+
+    PGPOOL_CONF=etc/pgpool.conf
+    POOL_HBA=etc/pool_hba.conf
+    if [ ! -f $POOL_HBA ];then
+       cp ../../../../../sample/pool_hba.conf.sample $POOL_HBA
+    fi
+    POOL_PASSWD=etc/pool_passwd
+
+    #
+    # replace trust auth for all users with superuser in pg_hba.conf
+    for i in data0 data1
+    do
+       if [ ! -d $i ];then
+           continue
+       fi
+
+       sed -i "s/local *all *all *trust/local  all     $superuser      trust/" $i/pg_hba.conf
+       sed -i "s/host *all *all  *all *trust/host      all   $superuser       all    trust/" $i/pg_hba.conf
+    done
+
+    #
+    # replace trust auth for all users with superuser in pool_hba.conf
+    sed -i "s@host    all         all         127.0.0.1/32          trust@host      all   $superuser       all    trust@" $POOL_HBA
+    sed -i "s@host    all         all         ::1/128               trust@host    all         $superuser         ::1/128               trust@" $POOL_HBA
+
+    # set up pgpass
+    cp /dev/null pgpass
+    chmod 0600 pgpass
+    cp /dev/null pgpasswrong
+    chmod 0600 pgpasswrong
+
+    # pgpool.conf opt
+    cp /dev/null $PGPOOL_CONF_OPT
+#    echo "include pgpool.conf.opt" >> etc/pgpool.conf
+    # add last line marker to pgpool.conf
+    echo "#last" >> etc/pgpool.conf
+
+    # start pgpool
+    ./startall
+    wait_for_pgpool_startup
+
+    IFS=","
+
+    #
+    # setup each user
+    #
+    cat $spec|while read line
+    do
+       set $line
+       if [ $1 = 'username' ];then
+           echo "skip title row"
+       else
+           username=$1
+           echo "==== $username ===="
+           pool_hba=$2
+           allow_clear=$3
+           pool_passwd=$4
+           pg_hba=$5
+           expected=$6
+
+           add_user $username $pg_hba
+           add_pool_hba $username $pool_hba $PGPOOL_VERSION_DIGIT
+           add_pool_passwd $username $pool_passwd $PGPOOL_VERSION_DIGIT
+           add_pg_hba $username $pg_hba
+           add_pgpass $username
+       fi
+    done
+    ./shutdownall
+
+    #
+    # do auth tests
+    #
+    cat $spec|while read line
+    do
+       set $line
+       username=$1
+       echo "==== $username ===="
+       pool_hba=$2
+       allow_clear=$3
+       pool_passwd=$4
+       pg_hba=$5
+       expected=$6
+
+       if [ $1 = 'username' ];then
+           echo "skip tile row"
+       elif [ "$DO_PAM_TEST" != "true" ] && [ $pool_hba = "pam" -o $pg_hba = "pam" ];then
+            echo "skip pam test"
+       elif [ "$DO_LDAP_TEST" != "true" ] && [ $pool_hba = "ldap" -o $pg_hba = "ldap" ];then
+            echo "skip ldap test"
+       else
+           cp $PGPOOL_CONF_OPT $PGPOOL_CONF_OPT.old
+           if [ $pool_hba = "scram" -o $pool_hba = "md5" -o $pool_hba = "password" -o $pool_hba = "pam" -o $pool_hba = "ldap" ];then
+               echo "enable_pool_hba = on" > $PGPOOL_CONF_OPT
+               last=`tail -1 etc/pgpool.conf`
+               if [ "$last" != "#last" ];then
+                   # remove the last line if it's not a marker
+                   echo "remove the last line in pgpool.conf because \"$last\""
+                   sed -i '$d' etc/pgpool.conf
+               fi
+               cat $PGPOOL_CONF_OPT >> etc/pgpool.conf
+               echo "add \"`cat $PGPOOL_CONF_OPT`\""
+           else
+               if [ $allow_clear = "off" ];then
+                   cp /dev/null $PGPOOL_CONF_OPT
+               else
+                   echo "allow_clear_text_frontend_auth = on" > $PGPOOL_CONF_OPT
+               fi
+               last=`tail -1 etc/pgpool.conf`
+               if [ $last != "#last" ];then
+                   # remove the last line if it's not a marker
+                   echo "remove the last line in pgpool.conf because \"$last\""
+                   sed -i '$d' etc/pgpool.conf
+               fi
+               cat $PGPOOL_CONF_OPT >> etc/pgpool.conf
+               echo "add \"`cat $PGPOOL_CONF_OPT`\""
+           fi
+
+           # if pgpool.conf.opt was changed, restart pgpool
+           cmp $PGPOOL_CONF_OPT $PGPOOL_CONF_OPT.old
+           if [ $? != 0 ];then
+               ./shutdownall
+               ./startall
+               wait_for_pgpool_startup
+           fi
+
+           result=""
+           PGPASSFILE=pgpass $PSQL -h 127.0.0.1 -U $username -c "SELECT user" >/dev/null 2>&1
+           rtn=$?
+           if [ $expected = "ok" ];then
+               echo -n "checking $username auth expecting success..."
+               if [ $rtn = 0 ];then
+                   echo "ok."
+                   echo -n "checking $username auth expecting success in reauth..."
+                   PGPASSFILE=pgpass $PSQL -h 127.0.0.1 -U $username -c "SELECT user" >/dev/null 2>&1
+                   if [ $? != 0 ];then
+                       echo "$username: password verification on reauth failed."
+                   else
+                       # reauth was Ok.
+                       echo "ok."
+                       echo -n "$username: try with wrong password expecting rejected..."
+                       PGPASSFILE=pgpasswrong $PSQL -h 127.0.0.1 -U $username -c "SELECT user">/dev/null 2>&1
+                       if [ $? = 0 ];then
+                           echo "$username: wrong password verification failed."
+                           echo "$mode $num_backends $username" >> $unexpected_passwd_verifi
+                       else
+                           echo "ok."
+                           result=ok
+                       fi
+                   fi
+               else
+                   echo "$username: password verification failed."
+               fi
+           else        # expecting fail
+               echo -n "checking $username auth expecting failure..."
+               if [ $rtn != 0 ];then
+                   echo "ok."
+                   result=ok
+               else
+                   # make sure that wrong password is rejected
+                   PGPASSFILE=pgpasswrong $PSQL -h 127.0.0.1 -U $username -c "SELECT user">/dev/null 2>&1
+                   if [ $? = 0 ];then
+                       echo "$username: unexpected successfull password verification"
+                       echo "$mode $num_backends $username" >> $unexpected_passwd_verifi
+                   else
+                       # maybe this test case should have been "ok", not "fail"?
+                       echo "$username: while expecting fail, wrong password was rejected but proper password was accepted"
+                   fi
+               fi
+           fi
+
+           if [ "$result" != "ok" ];then
+               echo -n " $username" >> $failed_usernames
+           fi
+       fi
+    done
+
+    ./shutdownall
+    cd ..
+
+    if [ -s $failed_usernames ];then
+       echo "failed tests:"
+       cat $failed_usernames
+       echo
+    fi
+}
+
+#
+#----------------------------------------
+# Misc preparations
+
+# pgpool major version first digit (e.g. "4")
+PGPOOL_VERSION_DIGIT=`echo $PGPOOL_VERSION|awk '{print $3}'|sed 's/\([1-9]\).*$/\1/'`
+failed_usernames=/tmp/failed_usernames
+failed_modes=/tmp/failed_modes
+unexpected_passwd_verifi=/tmp/unexpected_passwd_verificatio
+total_failed_usernames=/tmp/total_failed_usernames
+trap "rm $failed_usernames $failed_modes $unexpected_passwd_verifi $total_failed_usernames" EXIT
+cp /dev/null $failed_usernames
+cp /dev/null $failed_modes
+cp /dev/null $unexpected_passwd_verifi
+cp /dev/null $total_failed_usernames
+
+source $TESTLIBS
+TESTDIR=testdir
+PSQL=$PGBIN/psql
+PG_ENC=$PGPOOL_INSTALL_DIR/bin/pg_enc
+PG_MD5=$PGPOOL_INSTALL_DIR/bin/pg_md5
+export CREATEUSER=$PGBIN/createuser
+superuser=`whoami`
+PGPOOL_CONF_OPT=etc/pgpool.conf.opt
+# .pgpoolkey
+export PGPOOLKEYFILE=`pwd`/pgpoolkey
+echo "secret" > $PGPOOLKEYFILE
+chmod 0600 $PGPOOLKEYFILE
+#
+#----------------------------------------
+# Test execution starts here
+
+# pgpool_setup does not create a replication environment for logical
+# replication mode and slony mode, which makes the test failed
+# horribly because PostgreSQL roles are not replicated.  Therefore we
+# skip the tests for these modes.
+
+for mode in s r i n
+#for mode in s r i n l y
+do
+    echo "==== testing mode: $mode ==="
+    anyfail=""
+    spec=../client_auth_2node.csv
+    num_backends=2
+    do_auth $spec $num_backends $mode
+    if [ -s $failed_usernames ];then
+       anyfail="yes"
+       echo "$mode $num_backends `cat $failed_usernames`" >> $total_failed_usernames
+    fi
+
+    if [ -n "$anyfail" ];then
+       echo -n "$mode " >> $failed_modes
+    fi
+done
+
+if [ -s $unexpected_passwd_verifi ];then
+    echo
+    echo "IMPORTANT: unexpected successful password verfication found:"
+    cat $unexpected_passwd_verifi
+fi
+
+if [ -s $failed_modes ];then
+    echo
+    echo "failed mode: `cat $failed_modes`"
+    echo
+    echo "failed tests:"
+    cat $total_failed_usernames
+    exit 1
+fi
+
+exit 0