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