Redfish Session: Implement MFA "Token" property

This commit implements multi-factor authentication "Token" property to
create redfish sessions when multi-factor token authentication enabled.

Tested by:

Verified redfish session and login redfish commands with or without
TOTP token for MFA enabled/disabled  users.

User authentication with MFA token:
POST https://${bmc}/redfish/v1/SessionService/Sessions -d '{"UserName"
:"root", "Password": "0penBmc","Token":"510760"}'

User authentication without MFA token:
POST https://${bmc}/login -d '{"username" : "newuser", "password"
:"0penBmc"}'

POST https://${bmc}/redfish/v1/SessionService/Sessions -d '{"UserName"
:"newuser", "Password": "0penBmc"}'

In case of invalid MFA token or password then authentication fails and
returns "ResourceAtUriUnauthorized" error message.

Change-Id: I639163dd3d49ff8ed886f72c99ad264317d59c34
Signed-off-by: Ravi Teja <raviteja28031990@gmail.com>
diff --git a/include/authentication.hpp b/include/authentication.hpp
index 1690550..1a05efd 100644
--- a/include/authentication.hpp
+++ b/include/authentication.hpp
@@ -54,7 +54,7 @@
     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
                      clientIp.to_string());
 
-    int pamrc = pamAuthenticateUser(user, pass);
+    int pamrc = pamAuthenticateUser(user, pass, std::nullopt);
     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
     {
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index 7664592..7675066 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -153,7 +153,7 @@
 
     if (!username.empty() && !password.empty())
     {
-        int pamrc = pamAuthenticateUser(username, password);
+        int pamrc = pamAuthenticateUser(username, password, std::nullopt);
         bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
         if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
         {
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
index 22c8695..36d307e 100644
--- a/include/pam_authenticate.hpp
+++ b/include/pam_authenticate.hpp
@@ -7,6 +7,12 @@
 #include <span>
 #include <string_view>
 
+struct PasswordData
+{
+    std::string password;
+    std::optional<std::string> token;
+};
+
 // function used to get user input
 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
                                    struct pam_response** resp, void* appdataPtr)
@@ -20,8 +26,9 @@
     {
         return PAM_CONV_ERR;
     }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
     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);
@@ -41,20 +48,34 @@
             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);
-
+                // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
+                size_t appPassSize = appPass->password.size();
                 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();
+                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(appPass->password.c_str()); // Password input
+                }
+                else if (message.starts_with(totpPrompt))
+                {
+                    if (!appPass->token)
+                    {
+                        return PAM_CONV_ERR;
+                    }
+                    response.resp =
+                        strdup(appPass->token->c_str()); // TOTP input
+                }
+                else
+                {
+                    return PAM_CONV_ERR;
+                }
             }
             break;
             case PAM_ERROR_MSG:
@@ -76,16 +97,15 @@
  * @brief Attempt username/password authentication via PAM.
  * @param username The provided username aka account name.
  * @param password The provided password.
+ * @param token The provided MFA token.
  * @returns PAM error code or PAM_SUCCESS for success. */
 inline int pamAuthenticateUser(std::string_view username,
-                               std::string_view password)
+                               std::string_view password,
+                               std::optional<std::string> token)
 {
     std::string userStr(username);
-    std::string passStr(password);
-
-    char* passStrNoConst = passStr.data();
-    const struct pam_conv localConversation = {pamFunctionConversation,
-                                               passStrNoConst};
+    PasswordData data{std::string(password), std::move(token)};
+    const struct pam_conv localConversation = {pamFunctionConversation, &data};
     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
 
     int retval = pam_start("webserver", userStr.c_str(), &localConversation,
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index 225e872..d449b59 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -211,12 +211,13 @@
     std::string username;
     std::string password;
     std::optional<std::string> clientId;
+    std::optional<std::string> token;
     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
-                                  "Password", password, "Context", clientId))
+                                  "Password", password, "Token", token,
+                                  "Context", clientId))
     {
         return;
     }
-
     if (password.empty() || username.empty() ||
         asyncResp->res.result() != boost::beast::http::status::ok)
     {
@@ -233,7 +234,7 @@
         return;
     }
 
-    int pamrc = pamAuthenticateUser(username, password);
+    int pamrc = pamAuthenticateUser(username, password, token);
     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
     {