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
pamFunctionConversation(int numMsg,const struct pam_message ** msg,struct pam_response ** resp,void * appdataPtr)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. */
pamAuthenticateUser(std::string_view username,std::string_view password)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
pamUpdatePassword(const std::string & username,const std::string & password)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