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