#pragma once #include #include #include #include #include // function used to get user input inline int pamFunctionConversation(int numMsg, const struct pam_message** msg, struct pam_response** resp, void* appdataPtr) { if ((appdataPtr == nullptr) || (msg == nullptr) || (resp == nullptr)) { return PAM_CONV_ERR; } if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) { return PAM_CONV_ERR; } auto msgCount = static_cast(numMsg); auto messages = std::span(msg, msgCount); auto responses = std::span(resp, msgCount); for (size_t i = 0; i < msgCount; ++i) { /* Ignore all PAM messages except prompting for hidden input */ if (messages[i]->msg_style != PAM_PROMPT_ECHO_OFF) { continue; } /* Assume PAM is only prompting for the password as hidden input */ /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) char* appPass = reinterpret_cast(appdataPtr); size_t appPassSize = std::strlen(appPass); if ((appPassSize + 1) > PAM_MAX_RESP_SIZE) { return PAM_CONV_ERR; } // IDeally we'd like to avoid using malloc here, but because we're // passing off ownership of this to a C application, there aren't a lot // of sane ways to avoid it. // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) void* passPtr = malloc(appPassSize + 1); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) char* pass = reinterpret_cast(passPtr); if (pass == nullptr) { return PAM_BUF_ERR; } std::strncpy(pass, appPass, appPassSize + 1); size_t numMsgSize = static_cast(numMsg); // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) void* ptr = calloc(numMsgSize, sizeof(struct pam_response)); if (ptr == nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) free(pass); return PAM_BUF_ERR; } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) *resp = reinterpret_cast(ptr); responses[i]->resp = pass; return PAM_SUCCESS; } return PAM_CONV_ERR; } /** * @brief Attempt username/password authentication via PAM. * @param username The provided username aka account name. * @param password The provided password. * @returns PAM error code or PAM_SUCCESS for success. */ inline int pamAuthenticateUser(std::string_view username, std::string_view password) { std::string userStr(username); std::string passStr(password); char* passStrNoConst = passStr.data(); const struct pam_conv localConversation = {pamFunctionConversation, passStrNoConst}; pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start int retval = pam_start("webserver", userStr.c_str(), &localConversation, &localAuthHandle); if (retval != PAM_SUCCESS) { return retval; } retval = pam_authenticate(localAuthHandle, PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); if (retval != PAM_SUCCESS) { pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval return retval; } /* check that the account is healthy */ retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK); if (retval != PAM_SUCCESS) { pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval return retval; } return pam_end(localAuthHandle, PAM_SUCCESS); } inline int pamUpdatePassword(const std::string& username, const std::string& password) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) char* passStrNoConst = const_cast(password.c_str()); const struct pam_conv localConversation = {pamFunctionConversation, passStrNoConst}; pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start int retval = pam_start("webserver", username.c_str(), &localConversation, &localAuthHandle); if (retval != PAM_SUCCESS) { return retval; } retval = pam_chauthtok(localAuthHandle, PAM_SILENT); if (retval != PAM_SUCCESS) { pam_end(localAuthHandle, PAM_SUCCESS); return retval; } return pam_end(localAuthHandle, PAM_SUCCESS); }