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 struct PasswordData
11 {
12 struct Response
13 {
14 std::string_view prompt;
15 std::string value;
16 };
17
18 std::vector<Response> responseData;
19
addPromptPasswordData20 int addPrompt(std::string_view prompt, std::string_view value)
21 {
22 if (value.size() + 1 > PAM_MAX_MSG_SIZE)
23 {
24 BMCWEB_LOG_ERROR("value length error", prompt);
25 return PAM_CONV_ERR;
26 }
27 responseData.emplace_back(prompt, std::string(value));
28 return PAM_SUCCESS;
29 }
30
makeResponsePasswordData31 int makeResponse(const pam_message& msg, pam_response& response)
32 {
33 switch (msg.msg_style)
34 {
35 case PAM_PROMPT_ECHO_ON:
36 break;
37 case PAM_PROMPT_ECHO_OFF:
38 {
39 std::string prompt(msg.msg);
40 auto iter = std::ranges::find_if(
41 responseData, [&prompt](const Response& data) {
42 return prompt.starts_with(data.prompt);
43 });
44 if (iter == responseData.end())
45 {
46 return PAM_CONV_ERR;
47 }
48 response.resp = strdup(iter->value.c_str());
49 return PAM_SUCCESS;
50 }
51 break;
52 case PAM_ERROR_MSG:
53 {
54 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
55 }
56 break;
57 case PAM_TEXT_INFO:
58 {
59 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
60 }
61 break;
62 default:
63 {
64 return PAM_CONV_ERR;
65 }
66 }
67 return PAM_SUCCESS;
68 }
69 };
70
71 // function used to get user input
pamFunctionConversation(int numMsg,const struct pam_message ** msgs,struct pam_response ** resp,void * appdataPtr)72 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
73 struct pam_response** resp, void* appdataPtr)
74 {
75 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
76 {
77 return PAM_CONV_ERR;
78 }
79
80 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
81 {
82 return PAM_CONV_ERR;
83 }
84 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
85 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
86 auto msgCount = static_cast<size_t>(numMsg);
87 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
88 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
89 auto responses = std::span(responseArrPtr.get(), msgCount);
90 auto messagePtrs = std::span(msgs, msgCount);
91 for (size_t i = 0; i < msgCount; ++i)
92 {
93 const pam_message& msg = *(messagePtrs[i]);
94
95 pam_response& response = responses[i];
96 response.resp_retcode = 0;
97 response.resp = nullptr;
98
99 int r = appPass->makeResponse(msg, response);
100 if (r != PAM_SUCCESS)
101 {
102 return r;
103 }
104 }
105
106 *resp = responseArrPtr.release();
107 return PAM_SUCCESS;
108 }
109
110 /**
111 * @brief Attempt username/password authentication via PAM.
112 * @param username The provided username aka account name.
113 * @param password The provided password.
114 * @param token The provided MFA token.
115 * @returns PAM error code or PAM_SUCCESS for success. */
pamAuthenticateUser(std::string_view username,std::string_view password,std::optional<std::string> token)116 inline int pamAuthenticateUser(std::string_view username,
117 std::string_view password,
118 std::optional<std::string> token)
119 {
120 std::string userStr(username);
121 PasswordData data;
122 if (int ret = data.addPrompt("Password: ", password); ret != PAM_SUCCESS)
123 {
124 return ret;
125 }
126 if (token)
127 {
128 if (int ret = data.addPrompt("Verification code: ", *token);
129 ret != PAM_SUCCESS)
130 {
131 return ret;
132 }
133 }
134
135 const struct pam_conv localConversation = {pamFunctionConversation, &data};
136 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
137
138 int retval = pam_start("webserver", userStr.c_str(), &localConversation,
139 &localAuthHandle);
140 if (retval != PAM_SUCCESS)
141 {
142 return retval;
143 }
144
145 retval = pam_authenticate(localAuthHandle,
146 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
147 if (retval != PAM_SUCCESS)
148 {
149 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
150 return retval;
151 }
152
153 /* check that the account is healthy */
154 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
155 if (retval != PAM_SUCCESS)
156 {
157 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
158 return retval;
159 }
160
161 return pam_end(localAuthHandle, PAM_SUCCESS);
162 }
163
pamUpdatePassword(const std::string & username,const std::string & password)164 inline int pamUpdatePassword(const std::string& username,
165 const std::string& password)
166 {
167 PasswordData data;
168 if (int ret = data.addPrompt("New password: ", password);
169 ret != PAM_SUCCESS)
170 {
171 return ret;
172 }
173 if (int ret = data.addPrompt("Retype new password: ", password);
174 ret != PAM_SUCCESS)
175 {
176 return ret;
177 }
178 const struct pam_conv localConversation = {pamFunctionConversation, &data};
179 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
180
181 int retval = pam_start("webserver", username.c_str(), &localConversation,
182 &localAuthHandle);
183
184 if (retval != PAM_SUCCESS)
185 {
186 return retval;
187 }
188
189 retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
190 if (retval != PAM_SUCCESS)
191 {
192 pam_end(localAuthHandle, PAM_SUCCESS);
193 return retval;
194 }
195
196 return pam_end(localAuthHandle, PAM_SUCCESS);
197 }
198