1 #pragma once 2 3 #include <security/pam_appl.h> 4 5 #include <boost/utility/string_view.hpp> 6 7 #include <cstring> 8 #include <memory> 9 #include <span> 10 11 // function used to get user input 12 inline int pamFunctionConversation(int numMsg, const struct pam_message** msg, 13 struct pam_response** resp, void* appdataPtr) 14 { 15 if ((appdataPtr == nullptr) || (msg == nullptr) || (resp == nullptr)) 16 { 17 return PAM_CONV_ERR; 18 } 19 20 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) 21 { 22 return PAM_CONV_ERR; 23 } 24 25 auto msgCount = static_cast<size_t>(numMsg); 26 auto messages = std::span(msg, msgCount); 27 auto responses = std::span(resp, msgCount); 28 29 for (size_t i = 0; i < msgCount; ++i) 30 { 31 /* Ignore all PAM messages except prompting for hidden input */ 32 if (messages[i]->msg_style != PAM_PROMPT_ECHO_OFF) 33 { 34 continue; 35 } 36 37 /* Assume PAM is only prompting for the password as hidden input */ 38 /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */ 39 40 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 41 char* appPass = reinterpret_cast<char*>(appdataPtr); 42 size_t appPassSize = std::strlen(appPass); 43 44 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE) 45 { 46 return PAM_CONV_ERR; 47 } 48 // IDeally we'd like to avoid using malloc here, but because we're 49 // passing off ownership of this to a C application, there aren't a lot 50 // of sane ways to avoid it. 51 52 // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)' 53 void* passPtr = malloc(appPassSize + 1); 54 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 55 char* pass = reinterpret_cast<char*>(passPtr); 56 if (pass == nullptr) 57 { 58 return PAM_BUF_ERR; 59 } 60 61 std::strncpy(pass, appPass, appPassSize + 1); 62 63 size_t numMsgSize = static_cast<size_t>(numMsg); 64 // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) 65 void* ptr = calloc(numMsgSize, sizeof(struct pam_response)); 66 if (ptr == nullptr) 67 { 68 // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) 69 free(pass); 70 return PAM_BUF_ERR; 71 } 72 73 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 74 *resp = reinterpret_cast<pam_response*>(ptr); 75 76 responses[i]->resp = pass; 77 78 return PAM_SUCCESS; 79 } 80 81 return PAM_CONV_ERR; 82 } 83 84 /** 85 * @brief Attempt username/password authentication via PAM. 86 * @param username The provided username aka account name. 87 * @param password The provided password. 88 * @returns PAM error code or PAM_SUCCESS for success. */ 89 inline int pamAuthenticateUser(std::string_view username, 90 std::string_view password) 91 { 92 std::string userStr(username); 93 std::string passStr(password); 94 95 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 96 char* passStrNoConst = const_cast<char*>(passStr.c_str()); 97 const struct pam_conv localConversation = {pamFunctionConversation, 98 passStrNoConst}; 99 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 100 101 int retval = pam_start("webserver", userStr.c_str(), &localConversation, 102 &localAuthHandle); 103 if (retval != PAM_SUCCESS) 104 { 105 return retval; 106 } 107 108 retval = pam_authenticate(localAuthHandle, 109 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); 110 if (retval != PAM_SUCCESS) 111 { 112 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 113 return retval; 114 } 115 116 /* check that the account is healthy */ 117 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK); 118 if (retval != PAM_SUCCESS) 119 { 120 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval 121 return retval; 122 } 123 124 return pam_end(localAuthHandle, PAM_SUCCESS); 125 } 126 127 inline int pamUpdatePassword(const std::string& username, 128 const std::string& password) 129 { 130 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 131 char* passStrNoConst = const_cast<char*>(password.c_str()); 132 const struct pam_conv localConversation = {pamFunctionConversation, 133 passStrNoConst}; 134 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start 135 136 int retval = pam_start("webserver", username.c_str(), &localConversation, 137 &localAuthHandle); 138 139 if (retval != PAM_SUCCESS) 140 { 141 return retval; 142 } 143 144 retval = pam_chauthtok(localAuthHandle, PAM_SILENT); 145 if (retval != PAM_SUCCESS) 146 { 147 pam_end(localAuthHandle, PAM_SUCCESS); 148 return retval; 149 } 150 151 return pam_end(localAuthHandle, PAM_SUCCESS); 152 } 153