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