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