blob: e7c7d8d1072c197da9dfa319ecb9677cf2da73aa [file] [log] [blame]
Ed Tanous911ac312017-08-15 09:37:42 -07001#pragma once
2
Ed Tanousf3d847c2017-06-12 16:01:42 -07003#include <security/pam_appl.h>
Ed Tanous1abe55e2018-09-05 08:30:59 -07004
Ed Tanous911ac312017-08-15 09:37:42 -07005#include <cstring>
Ed Tanouse0d918b2018-03-27 17:41:04 -07006#include <memory>
Patrick Williamsad7fa902023-05-10 19:57:29 -05007#include <span>
Ed Tanous5b904292024-04-16 11:10:17 -07008#include <string_view>
Ed Tanousf3d847c2017-06-12 16:01:42 -07009
Ravi Teja2ccce1f2024-08-10 04:05:36 -050010struct PasswordData
11{
12 std::string password;
13 std::optional<std::string> token;
Ed Tanous82f49fa2024-08-27 11:37:09 -070014
15 int makeResponse(const pam_message& msg, pam_response& response)
16 {
17 switch (msg.msg_style)
18 {
19 case PAM_PROMPT_ECHO_ON:
20 break;
21 case PAM_PROMPT_ECHO_OFF:
22 {
23 // Assume PAM is only prompting for the password as hidden input
24 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
25 size_t appPassSize = password.size();
26 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
27 {
28 return PAM_CONV_ERR;
29 }
30 std::string_view message(msg.msg);
31 constexpr std::string_view passwordPrompt = "Password: ";
32 // String used by Google authenticator to ask for one time code
33 constexpr std::string_view totpPrompt = "Verification code: ";
34 if (message.starts_with(passwordPrompt))
35 {
36 response.resp = strdup(password.c_str()); // Password input
37 }
38 else if (message.starts_with(totpPrompt))
39 {
40 if (!token)
41 {
42 return PAM_CONV_ERR;
43 }
44 // TOTP input
45 response.resp = strdup(token->c_str());
46 }
47 else
48 {
49 return PAM_CONV_ERR;
50 }
51 }
52 break;
53 case PAM_ERROR_MSG:
54 {
55 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
56 }
57 break;
58 case PAM_TEXT_INFO:
59 {
60 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
61 }
62 break;
63 default:
64 {
65 return PAM_CONV_ERR;
66 }
67 }
68
69 return PAM_SUCCESS;
70 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050071};
72
Ed Tanousf3d847c2017-06-12 16:01:42 -070073// function used to get user input
Ed Tanous05ecd3a2024-02-16 08:13:57 -080074inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
Ed Tanous1abe55e2018-09-05 08:30:59 -070075 struct pam_response** resp, void* appdataPtr)
76{
Ed Tanous05ecd3a2024-02-16 08:13:57 -080077 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
Ed Tanous1abe55e2018-09-05 08:30:59 -070078 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053079 return PAM_CONV_ERR;
Ed Tanous1abe55e2018-09-05 08:30:59 -070080 }
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053081
82 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
Ed Tanousf1eebf02019-03-04 15:57:09 -080083 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053084 return PAM_CONV_ERR;
Ed Tanousf1eebf02019-03-04 15:57:09 -080085 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050086 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
87 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
Patrick Williamsad7fa902023-05-10 19:57:29 -050088 auto msgCount = static_cast<size_t>(numMsg);
Ed Tanous05ecd3a2024-02-16 08:13:57 -080089 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
90 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
91 auto responses = std::span(responseArrPtr.get(), msgCount);
92 auto messagePtrs = std::span(msgs, msgCount);
Patrick Williamsad7fa902023-05-10 19:57:29 -050093 for (size_t i = 0; i < msgCount; ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -070094 {
Ed Tanous05ecd3a2024-02-16 08:13:57 -080095 const pam_message& msg = *(messagePtrs[i]);
96
97 pam_response& response = responses[i];
98 response.resp_retcode = 0;
99 response.resp = nullptr;
100
Ed Tanous82f49fa2024-08-27 11:37:09 -0700101 int r = appPass->makeResponse(msg, response);
102 if (r != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700103 {
Ed Tanous82f49fa2024-08-27 11:37:09 -0700104 return r;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700105 }
Ed Tanous911ac312017-08-15 09:37:42 -0700106 }
Ed Tanousf3d847c2017-06-12 16:01:42 -0700107
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800108 *resp = responseArrPtr.release();
109 return PAM_SUCCESS;
Ed Tanousf3d847c2017-06-12 16:01:42 -0700110}
111
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600112/**
113 * @brief Attempt username/password authentication via PAM.
114 * @param username The provided username aka account name.
115 * @param password The provided password.
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500116 * @param token The provided MFA token.
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600117 * @returns PAM error code or PAM_SUCCESS for success. */
Ed Tanous26ccae32023-02-16 10:28:44 -0800118inline int pamAuthenticateUser(std::string_view username,
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500119 std::string_view password,
120 std::optional<std::string> token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121{
122 std::string userStr(username);
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500123 PasswordData data{std::string(password), std::move(token)};
124 const struct pam_conv localConversation = {pamFunctionConversation, &data};
Ed Tanous99131cd2019-10-24 11:12:47 -0700125 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousf3d847c2017-06-12 16:01:42 -0700126
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600127 int retval = pam_start("webserver", userStr.c_str(), &localConversation,
128 &localAuthHandle);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700129 if (retval != PAM_SUCCESS)
130 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600131 return retval;
132 }
133
134 retval = pam_authenticate(localAuthHandle,
135 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
136 if (retval != PAM_SUCCESS)
137 {
138 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
139 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700140 }
Ed Tanous911ac312017-08-15 09:37:42 -0700141
Ed Tanous1abe55e2018-09-05 08:30:59 -0700142 /* check that the account is healthy */
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600143 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
144 if (retval != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700145 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600146 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
147 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700148 }
Ed Tanous911ac312017-08-15 09:37:42 -0700149
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600150 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanous911ac312017-08-15 09:37:42 -0700151}
Ed Tanousa8408792018-09-05 16:08:38 -0700152
Ed Tanous9be245e2024-08-26 09:06:45 -0700153inline int pamUpdatePasswordFunctionConversation(
154 int numMsg, const struct pam_message** msgs, struct pam_response** resp,
155 void* appdataPtr)
156{
157 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
158 {
159 return PAM_CONV_ERR;
160 }
161
162 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
163 {
164 return PAM_CONV_ERR;
165 }
166 auto msgCount = static_cast<size_t>(numMsg);
167
168 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
169 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
170 auto responses = std::span(responseArrPtr.get(), msgCount);
171 auto messagePtrs = std::span(msgs, msgCount);
172 for (size_t i = 0; i < msgCount; ++i)
173 {
174 const pam_message& msg = *(messagePtrs[i]);
175
176 pam_response& response = responses[i];
177 response.resp_retcode = 0;
178 response.resp = nullptr;
179
180 switch (msg.msg_style)
181 {
182 case PAM_PROMPT_ECHO_ON:
183 break;
184 case PAM_PROMPT_ECHO_OFF:
185 {
186 // Assume PAM is only prompting for the password as hidden input
187 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred
188 char* appPass = static_cast<char*>(appdataPtr);
189 size_t appPassSize = std::strlen(appPass);
190
191 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
192 {
193 return PAM_CONV_ERR;
194 }
195 // Create an array for pam to own
196 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
197 auto passPtr = std::make_unique<char[]>(appPassSize + 1);
198 std::strncpy(passPtr.get(), appPass, appPassSize + 1);
199
200 responses[i].resp = passPtr.release();
201 }
202 break;
203 case PAM_ERROR_MSG:
204 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
205 break;
206 case PAM_TEXT_INFO:
207 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
208 break;
209 default:
210 return PAM_CONV_ERR;
211 }
212 }
213 *resp = responseArrPtr.release();
214 return PAM_SUCCESS;
215}
216
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000217inline int pamUpdatePassword(const std::string& username,
218 const std::string& password)
Ed Tanousa8408792018-09-05 16:08:38 -0700219{
Ed Tanous4ecc6182022-01-07 09:36:26 -0800220 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
221 char* passStrNoConst = const_cast<char*>(password.c_str());
Ed Tanous9be245e2024-08-26 09:06:45 -0700222 const struct pam_conv localConversation = {
223 pamUpdatePasswordFunctionConversation, passStrNoConst};
Ed Tanous99131cd2019-10-24 11:12:47 -0700224 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousa8408792018-09-05 16:08:38 -0700225
Joseph Reynolds96b39e02019-12-05 17:53:35 -0600226 int retval = pam_start("webserver", username.c_str(), &localConversation,
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000227 &localAuthHandle);
Ed Tanousa8408792018-09-05 16:08:38 -0700228
229 if (retval != PAM_SUCCESS)
230 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000231 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700232 }
233
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000234 retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
235 if (retval != PAM_SUCCESS)
Ed Tanousa8408792018-09-05 16:08:38 -0700236 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000237 pam_end(localAuthHandle, PAM_SUCCESS);
238 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700239 }
240
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000241 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanousa8408792018-09-05 16:08:38 -0700242}