1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "logging.hpp" 6 7 #include <security/_pam_types.h> 8 #include <security/pam_appl.h> 9 10 #include <algorithm> 11 #include <cstddef> 12 #include <cstring> 13 #include <memory> 14 #include <optional> 15 #include <span> 16 #include <string> 17 #include <string_view> 18 #include <vector> 19 20 struct PasswordData 21 { 22 struct Response 23 { 24 std::string_view prompt; 25 std::string value; 26 }; 27 28 std::vector<Response> responseData; 29 30 int addPrompt(std::string_view prompt, std::string_view value) 31 { 32 if (value.size() + 1 > PAM_MAX_MSG_SIZE) 33 { 34 BMCWEB_LOG_ERROR("value length error", prompt); 35 return PAM_CONV_ERR; 36 } 37 responseData.emplace_back(prompt, std::string(value)); 38 return PAM_SUCCESS; 39 } 40 41 int makeResponse(const pam_message& msg, pam_response& response) 42 { 43 switch (msg.msg_style) 44 { 45 case PAM_PROMPT_ECHO_ON: 46 break; 47 case PAM_PROMPT_ECHO_OFF: 48 { 49 std::string prompt(msg.msg); 50 auto iter = std::ranges::find_if( 51 responseData, [&prompt](const Response& data) { 52 return prompt.starts_with(data.prompt); 53 }); 54 if (iter == responseData.end()) 55 { 56 return PAM_CONV_ERR; 57 } 58 // NOLINTNEXTLINE(misc-include-cleaner) 59 response.resp = strdup(iter->value.c_str()); 60 return PAM_SUCCESS; 61 } 62 break; 63 case PAM_ERROR_MSG: 64 { 65 BMCWEB_LOG_ERROR("Pam error {}", msg.msg); 66 } 67 break; 68 case PAM_TEXT_INFO: 69 { 70 BMCWEB_LOG_ERROR("Pam info {}", msg.msg); 71 } 72 break; 73 default: 74 { 75 return PAM_CONV_ERR; 76 } 77 } 78 return PAM_SUCCESS; 79 } 80 }; 81 82 // function used to get user input 83 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs, 84 struct pam_response** resp, void* appdataPtr) 85 { 86 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr)) 87 { 88 return PAM_CONV_ERR; 89 } 90 91 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) 92 { 93 return PAM_CONV_ERR; 94 } 95 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 96 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr); 97 auto msgCount = static_cast<size_t>(numMsg); 98 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 99 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount); 100 auto responses = std::span(responseArrPtr.get(), msgCount); 101 auto messagePtrs = std::span(msgs, msgCount); 102 for (size_t i = 0; i < msgCount; ++i) 103 { 104 const pam_message& msg = *(messagePtrs[i]); 105 106 pam_response& response = responses[i]; 107 response.resp_retcode = 0; 108 response.resp = nullptr; 109 110 int r = appPass->makeResponse(msg, response); 111 if (r != PAM_SUCCESS) 112 { 113 return r; 114 } 115 } 116 117 *resp = responseArrPtr.release(); 118 return PAM_SUCCESS; 119 } 120 121 /** 122 * @brief Attempt username/password authentication via PAM. 123 * @param username The provided username aka account name. 124 * @param password The provided password. 125 * @param token The provided MFA token. 126 * @returns PAM error code or PAM_SUCCESS for success. */ 127 inline int pamAuthenticateUser(std::string_view username, 128 std::string_view password, 129 std::optional<std::string> token) 130 { 131 std::string userStr(username); 132 PasswordData data; 133 if (int ret = data.addPrompt("Password: ", password); ret != PAM_SUCCESS) 134 { 135 return ret; 136 } 137 if (token) 138 { 139 if (int ret = data.addPrompt("Verification code: ", *token); 140 ret != PAM_SUCCESS) 141 { 142 return ret; 143 } 144 } 145 146 const struct pam_conv localConversation = {pamFunctionConversation, &data}; 147 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 148 149 int retval = pam_start("webserver", userStr.c_str(), &localConversation, 150 &localAuthHandle); 151 if (retval != PAM_SUCCESS) 152 { 153 return retval; 154 } 155 156 retval = pam_authenticate(localAuthHandle, 157 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); 158 if (retval != PAM_SUCCESS) 159 { 160 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 161 return retval; 162 } 163 164 /* check that the account is healthy */ 165 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK); 166 if (retval != PAM_SUCCESS) 167 { 168 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 169 return retval; 170 } 171 172 return pam_end(localAuthHandle, PAM_SUCCESS); 173 } 174 175 inline int pamUpdatePassword(const std::string& username, 176 const std::string& password) 177 { 178 PasswordData data; 179 if (int ret = data.addPrompt("New password: ", password); 180 ret != PAM_SUCCESS) 181 { 182 return ret; 183 } 184 if (int ret = data.addPrompt("Retype new password: ", password); 185 ret != PAM_SUCCESS) 186 { 187 return ret; 188 } 189 const struct pam_conv localConversation = {pamFunctionConversation, &data}; 190 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 191 192 int retval = pam_start("webserver", username.c_str(), &localConversation, 193 &localAuthHandle); 194 195 if (retval != PAM_SUCCESS) 196 { 197 return retval; 198 } 199 200 retval = pam_chauthtok(localAuthHandle, PAM_SILENT); 201 if (retval != PAM_SUCCESS) 202 { 203 pam_end(localAuthHandle, PAM_SUCCESS); 204 return retval; 205 } 206 207 return pam_end(localAuthHandle, PAM_SUCCESS); 208 } 209