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