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 std::string password; 13 std::optional<std::string> token; 14 }; 15 16 // function used to get user input 17 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs, 18 struct pam_response** resp, void* appdataPtr) 19 { 20 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr)) 21 { 22 return PAM_CONV_ERR; 23 } 24 25 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) 26 { 27 return PAM_CONV_ERR; 28 } 29 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 30 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr); 31 auto msgCount = static_cast<size_t>(numMsg); 32 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 33 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount); 34 auto responses = std::span(responseArrPtr.get(), msgCount); 35 auto messagePtrs = std::span(msgs, msgCount); 36 for (size_t i = 0; i < msgCount; ++i) 37 { 38 const pam_message& msg = *(messagePtrs[i]); 39 40 pam_response& response = responses[i]; 41 response.resp_retcode = 0; 42 response.resp = nullptr; 43 44 switch (msg.msg_style) 45 { 46 case PAM_PROMPT_ECHO_ON: 47 break; 48 case PAM_PROMPT_ECHO_OFF: 49 { 50 // Assume PAM is only prompting for the password as hidden input 51 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered 52 size_t appPassSize = appPass->password.size(); 53 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE) 54 { 55 return PAM_CONV_ERR; 56 } 57 std::string_view message(msg.msg); 58 constexpr std::string_view passwordPrompt = "Password: "; 59 // String used by Google authenticator to ask for one time code 60 constexpr std::string_view totpPrompt = "Verification code: "; 61 if (message.starts_with(passwordPrompt)) 62 { 63 response.resp = 64 strdup(appPass->password.c_str()); // Password input 65 } 66 else if (message.starts_with(totpPrompt)) 67 { 68 if (!appPass->token) 69 { 70 return PAM_CONV_ERR; 71 } 72 response.resp = 73 strdup(appPass->token->c_str()); // TOTP input 74 } 75 else 76 { 77 return PAM_CONV_ERR; 78 } 79 } 80 break; 81 case PAM_ERROR_MSG: 82 BMCWEB_LOG_ERROR("Pam error {}", msg.msg); 83 break; 84 case PAM_TEXT_INFO: 85 BMCWEB_LOG_ERROR("Pam info {}", msg.msg); 86 break; 87 default: 88 return PAM_CONV_ERR; 89 } 90 } 91 92 *resp = responseArrPtr.release(); 93 return PAM_SUCCESS; 94 } 95 96 /** 97 * @brief Attempt username/password authentication via PAM. 98 * @param username The provided username aka account name. 99 * @param password The provided password. 100 * @param token The provided MFA token. 101 * @returns PAM error code or PAM_SUCCESS for success. */ 102 inline int pamAuthenticateUser(std::string_view username, 103 std::string_view password, 104 std::optional<std::string> token) 105 { 106 std::string userStr(username); 107 PasswordData data{std::string(password), std::move(token)}; 108 const struct pam_conv localConversation = {pamFunctionConversation, &data}; 109 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 110 111 int retval = pam_start("webserver", userStr.c_str(), &localConversation, 112 &localAuthHandle); 113 if (retval != PAM_SUCCESS) 114 { 115 return retval; 116 } 117 118 retval = pam_authenticate(localAuthHandle, 119 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); 120 if (retval != PAM_SUCCESS) 121 { 122 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 123 return retval; 124 } 125 126 /* check that the account is healthy */ 127 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK); 128 if (retval != PAM_SUCCESS) 129 { 130 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 131 return retval; 132 } 133 134 return pam_end(localAuthHandle, PAM_SUCCESS); 135 } 136 137 inline int pamUpdatePasswordFunctionConversation( 138 int numMsg, const struct pam_message** msgs, struct pam_response** resp, 139 void* appdataPtr) 140 { 141 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr)) 142 { 143 return PAM_CONV_ERR; 144 } 145 146 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) 147 { 148 return PAM_CONV_ERR; 149 } 150 auto msgCount = static_cast<size_t>(numMsg); 151 152 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 153 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount); 154 auto responses = std::span(responseArrPtr.get(), msgCount); 155 auto messagePtrs = std::span(msgs, msgCount); 156 for (size_t i = 0; i < msgCount; ++i) 157 { 158 const pam_message& msg = *(messagePtrs[i]); 159 160 pam_response& response = responses[i]; 161 response.resp_retcode = 0; 162 response.resp = nullptr; 163 164 switch (msg.msg_style) 165 { 166 case PAM_PROMPT_ECHO_ON: 167 break; 168 case PAM_PROMPT_ECHO_OFF: 169 { 170 // Assume PAM is only prompting for the password as hidden input 171 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred 172 char* appPass = static_cast<char*>(appdataPtr); 173 size_t appPassSize = std::strlen(appPass); 174 175 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE) 176 { 177 return PAM_CONV_ERR; 178 } 179 // Create an array for pam to own 180 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 181 auto passPtr = std::make_unique<char[]>(appPassSize + 1); 182 std::strncpy(passPtr.get(), appPass, appPassSize + 1); 183 184 responses[i].resp = passPtr.release(); 185 } 186 break; 187 case PAM_ERROR_MSG: 188 BMCWEB_LOG_ERROR("Pam error {}", msg.msg); 189 break; 190 case PAM_TEXT_INFO: 191 BMCWEB_LOG_ERROR("Pam info {}", msg.msg); 192 break; 193 default: 194 return PAM_CONV_ERR; 195 } 196 } 197 *resp = responseArrPtr.release(); 198 return PAM_SUCCESS; 199 } 200 201 inline int pamUpdatePassword(const std::string& username, 202 const std::string& password) 203 { 204 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 205 char* passStrNoConst = const_cast<char*>(password.c_str()); 206 const struct pam_conv localConversation = { 207 pamUpdatePasswordFunctionConversation, passStrNoConst}; 208 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 209 210 int retval = pam_start("webserver", username.c_str(), &localConversation, 211 &localAuthHandle); 212 213 if (retval != PAM_SUCCESS) 214 { 215 return retval; 216 } 217 218 retval = pam_chauthtok(localAuthHandle, PAM_SILENT); 219 if (retval != PAM_SUCCESS) 220 { 221 pam_end(localAuthHandle, PAM_SUCCESS); 222 return retval; 223 } 224 225 return pam_end(localAuthHandle, PAM_SUCCESS); 226 } 227