Pam refactoring: To support multiple prompts

The commit refactors pam_authenticated.cpp to support newer prompts
which may come in future for various MFA options. Now the support
restricted to unix Password and google authenticator Verification Code.

Tested by:

1: Successful session creation
2: Successful patch operation for password change using below
curl -k -H "Content-Type: application/json" -H
"X-Auth-Token: $bmc_token" -X
PATCH https://${bmc}/redfish/v1/AccountService/Accounts/root
-d '{"Password":"xxxxxxxx"}'

Change-Id: Iea8696c8a28adefcd5bf62e22978010f38ce8084
Signed-off-by: Abhilash Raju <abhilash.kollam@gmail.com>
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
index e7c7d8d..7e970f0 100644
--- a/include/pam_authenticate.hpp
+++ b/include/pam_authenticate.hpp
@@ -9,8 +9,24 @@
 
 struct PasswordData
 {
-    std::string password;
-    std::optional<std::string> token;
+    struct Response
+    {
+        std::string_view prompt;
+        std::string value;
+    };
+
+    std::vector<Response> responseData;
+
+    int addPrompt(std::string_view prompt, std::string_view value)
+    {
+        if (value.size() + 1 > PAM_MAX_MSG_SIZE)
+        {
+            BMCWEB_LOG_ERROR("value length error", prompt);
+            return PAM_CONV_ERR;
+        }
+        responseData.emplace_back(prompt, std::string(value));
+        return PAM_SUCCESS;
+    }
 
     int makeResponse(const pam_message& msg, pam_response& response)
     {
@@ -20,34 +36,17 @@
                 break;
             case PAM_PROMPT_ECHO_OFF:
             {
-                // Assume PAM is only prompting for the password as hidden input
-                // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
-                size_t appPassSize = password.size();
-                if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
+                std::string prompt(msg.msg);
+                auto iter = std::ranges::find_if(
+                    responseData, [&prompt](const Response& data) {
+                        return prompt.starts_with(data.prompt);
+                    });
+                if (iter == responseData.end())
                 {
                     return PAM_CONV_ERR;
                 }
-                std::string_view message(msg.msg);
-                constexpr std::string_view passwordPrompt = "Password: ";
-                // String used by Google authenticator to ask for one time code
-                constexpr std::string_view totpPrompt = "Verification code: ";
-                if (message.starts_with(passwordPrompt))
-                {
-                    response.resp = strdup(password.c_str()); // Password input
-                }
-                else if (message.starts_with(totpPrompt))
-                {
-                    if (!token)
-                    {
-                        return PAM_CONV_ERR;
-                    }
-                    // TOTP input
-                    response.resp = strdup(token->c_str());
-                }
-                else
-                {
-                    return PAM_CONV_ERR;
-                }
+                response.resp = strdup(iter->value.c_str());
+                return PAM_SUCCESS;
             }
             break;
             case PAM_ERROR_MSG:
@@ -65,7 +64,6 @@
                 return PAM_CONV_ERR;
             }
         }
-
         return PAM_SUCCESS;
     }
 };
@@ -120,7 +118,20 @@
                                std::optional<std::string> token)
 {
     std::string userStr(username);
-    PasswordData data{std::string(password), std::move(token)};
+    PasswordData data;
+    if (int ret = data.addPrompt("Password: ", password); ret != PAM_SUCCESS)
+    {
+        return ret;
+    }
+    if (token)
+    {
+        if (int ret = data.addPrompt("Verification code: ", *token);
+            ret != PAM_SUCCESS)
+        {
+            return ret;
+        }
+    }
+
     const struct pam_conv localConversation = {pamFunctionConversation, &data};
     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
 
@@ -150,77 +161,21 @@
     return pam_end(localAuthHandle, PAM_SUCCESS);
 }
 
-inline int pamUpdatePasswordFunctionConversation(
-    int numMsg, const struct pam_message** msgs, struct pam_response** resp,
-    void* appdataPtr)
-{
-    if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
-    {
-        return PAM_CONV_ERR;
-    }
-
-    if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
-    {
-        return PAM_CONV_ERR;
-    }
-    auto msgCount = static_cast<size_t>(numMsg);
-
-    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
-    auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
-    auto responses = std::span(responseArrPtr.get(), msgCount);
-    auto messagePtrs = std::span(msgs, msgCount);
-    for (size_t i = 0; i < msgCount; ++i)
-    {
-        const pam_message& msg = *(messagePtrs[i]);
-
-        pam_response& response = responses[i];
-        response.resp_retcode = 0;
-        response.resp = nullptr;
-
-        switch (msg.msg_style)
-        {
-            case PAM_PROMPT_ECHO_ON:
-                break;
-            case PAM_PROMPT_ECHO_OFF:
-            {
-                // Assume PAM is only prompting for the password as hidden input
-                // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred
-                char* appPass = static_cast<char*>(appdataPtr);
-                size_t appPassSize = std::strlen(appPass);
-
-                if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
-                {
-                    return PAM_CONV_ERR;
-                }
-                // Create an array for pam to own
-                // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
-                auto passPtr = std::make_unique<char[]>(appPassSize + 1);
-                std::strncpy(passPtr.get(), appPass, appPassSize + 1);
-
-                responses[i].resp = passPtr.release();
-            }
-            break;
-            case PAM_ERROR_MSG:
-                BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
-                break;
-            case PAM_TEXT_INFO:
-                BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
-                break;
-            default:
-                return PAM_CONV_ERR;
-        }
-    }
-    *resp = responseArrPtr.release();
-    return PAM_SUCCESS;
-}
-
 inline int pamUpdatePassword(const std::string& username,
                              const std::string& password)
 {
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
-    char* passStrNoConst = const_cast<char*>(password.c_str());
-    const struct pam_conv localConversation = {
-        pamUpdatePasswordFunctionConversation, passStrNoConst};
+    PasswordData data;
+    if (int ret = data.addPrompt("New password: ", password);
+        ret != PAM_SUCCESS)
+    {
+        return ret;
+    }
+    if (int ret = data.addPrompt("Retype new password: ", password);
+        ret != PAM_SUCCESS)
+    {
+        return ret;
+    }
+    const struct pam_conv localConversation = {pamFunctionConversation, &data};
     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
 
     int retval = pam_start("webserver", username.c_str(), &localConversation,