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