blob: fbb3f6165c5883c84a80b3ec929026aedca75aaf [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;
14};
15
Ed Tanousf3d847c2017-06-12 16:01:42 -070016// function used to get user input
Ed Tanous05ecd3a2024-02-16 08:13:57 -080017inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
Ed Tanous1abe55e2018-09-05 08:30:59 -070018 struct pam_response** resp, void* appdataPtr)
19{
Ed Tanous05ecd3a2024-02-16 08:13:57 -080020 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
Ed Tanous1abe55e2018-09-05 08:30:59 -070021 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053022 return PAM_CONV_ERR;
Ed Tanous1abe55e2018-09-05 08:30:59 -070023 }
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053024
25 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
Ed Tanousf1eebf02019-03-04 15:57:09 -080026 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053027 return PAM_CONV_ERR;
Ed Tanousf1eebf02019-03-04 15:57:09 -080028 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050029 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
30 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
Patrick Williamsad7fa902023-05-10 19:57:29 -050031 auto msgCount = static_cast<size_t>(numMsg);
Ed Tanous05ecd3a2024-02-16 08:13:57 -080032 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
33 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
34 auto responses = std::span(responseArrPtr.get(), msgCount);
35 auto messagePtrs = std::span(msgs, msgCount);
Patrick Williamsad7fa902023-05-10 19:57:29 -050036 for (size_t i = 0; i < msgCount; ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -070037 {
Ed Tanous05ecd3a2024-02-16 08:13:57 -080038 const pam_message& msg = *(messagePtrs[i]);
39
40 pam_response& response = responses[i];
41 response.resp_retcode = 0;
42 response.resp = nullptr;
43
44 switch (msg.msg_style)
Ed Tanous1abe55e2018-09-05 08:30:59 -070045 {
Ed Tanous05ecd3a2024-02-16 08:13:57 -080046 case PAM_PROMPT_ECHO_ON:
47 break;
48 case PAM_PROMPT_ECHO_OFF:
49 {
50 // Assume PAM is only prompting for the password as hidden input
Ravi Teja2ccce1f2024-08-10 04:05:36 -050051 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
52 size_t appPassSize = appPass->password.size();
Ed Tanous05ecd3a2024-02-16 08:13:57 -080053 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
54 {
55 return PAM_CONV_ERR;
56 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050057 std::string_view message(msg.msg);
58 constexpr std::string_view passwordPrompt = "Password: ";
59 // String used by Google authenticator to ask for one time code
60 constexpr std::string_view totpPrompt = "Verification code: ";
61 if (message.starts_with(passwordPrompt))
62 {
63 response.resp =
64 strdup(appPass->password.c_str()); // Password input
65 }
66 else if (message.starts_with(totpPrompt))
67 {
68 if (!appPass->token)
69 {
70 return PAM_CONV_ERR;
71 }
72 response.resp =
73 strdup(appPass->token->c_str()); // TOTP input
74 }
75 else
76 {
77 return PAM_CONV_ERR;
78 }
Ed Tanous05ecd3a2024-02-16 08:13:57 -080079 }
80 break;
81 case PAM_ERROR_MSG:
82 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
83 break;
84 case PAM_TEXT_INFO:
85 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
86 break;
87 default:
88 return PAM_CONV_ERR;
Ed Tanous1abe55e2018-09-05 08:30:59 -070089 }
Ed Tanous911ac312017-08-15 09:37:42 -070090 }
Ed Tanousf3d847c2017-06-12 16:01:42 -070091
Ed Tanous05ecd3a2024-02-16 08:13:57 -080092 *resp = responseArrPtr.release();
93 return PAM_SUCCESS;
Ed Tanousf3d847c2017-06-12 16:01:42 -070094}
95
Joseph Reynoldsd887fff2020-01-14 16:34:09 -060096/**
97 * @brief Attempt username/password authentication via PAM.
98 * @param username The provided username aka account name.
99 * @param password The provided password.
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500100 * @param token The provided MFA token.
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600101 * @returns PAM error code or PAM_SUCCESS for success. */
Ed Tanous26ccae32023-02-16 10:28:44 -0800102inline int pamAuthenticateUser(std::string_view username,
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500103 std::string_view password,
104 std::optional<std::string> token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700105{
106 std::string userStr(username);
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500107 PasswordData data{std::string(password), std::move(token)};
108 const struct pam_conv localConversation = {pamFunctionConversation, &data};
Ed Tanous99131cd2019-10-24 11:12:47 -0700109 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousf3d847c2017-06-12 16:01:42 -0700110
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600111 int retval = pam_start("webserver", userStr.c_str(), &localConversation,
112 &localAuthHandle);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700113 if (retval != PAM_SUCCESS)
114 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600115 return retval;
116 }
117
118 retval = pam_authenticate(localAuthHandle,
119 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
120 if (retval != PAM_SUCCESS)
121 {
122 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
123 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700124 }
Ed Tanous911ac312017-08-15 09:37:42 -0700125
Ed Tanous1abe55e2018-09-05 08:30:59 -0700126 /* check that the account is healthy */
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600127 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
128 if (retval != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700129 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600130 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
131 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700132 }
Ed Tanous911ac312017-08-15 09:37:42 -0700133
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600134 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanous911ac312017-08-15 09:37:42 -0700135}
Ed Tanousa8408792018-09-05 16:08:38 -0700136
Ed Tanous9be245e2024-08-26 09:06:45 -0700137inline int pamUpdatePasswordFunctionConversation(
138 int numMsg, const struct pam_message** msgs, struct pam_response** resp,
139 void* appdataPtr)
140{
141 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
142 {
143 return PAM_CONV_ERR;
144 }
145
146 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
147 {
148 return PAM_CONV_ERR;
149 }
150 auto msgCount = static_cast<size_t>(numMsg);
151
152 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
153 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
154 auto responses = std::span(responseArrPtr.get(), msgCount);
155 auto messagePtrs = std::span(msgs, msgCount);
156 for (size_t i = 0; i < msgCount; ++i)
157 {
158 const pam_message& msg = *(messagePtrs[i]);
159
160 pam_response& response = responses[i];
161 response.resp_retcode = 0;
162 response.resp = nullptr;
163
164 switch (msg.msg_style)
165 {
166 case PAM_PROMPT_ECHO_ON:
167 break;
168 case PAM_PROMPT_ECHO_OFF:
169 {
170 // Assume PAM is only prompting for the password as hidden input
171 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred
172 char* appPass = static_cast<char*>(appdataPtr);
173 size_t appPassSize = std::strlen(appPass);
174
175 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
176 {
177 return PAM_CONV_ERR;
178 }
179 // Create an array for pam to own
180 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
181 auto passPtr = std::make_unique<char[]>(appPassSize + 1);
182 std::strncpy(passPtr.get(), appPass, appPassSize + 1);
183
184 responses[i].resp = passPtr.release();
185 }
186 break;
187 case PAM_ERROR_MSG:
188 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
189 break;
190 case PAM_TEXT_INFO:
191 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
192 break;
193 default:
194 return PAM_CONV_ERR;
195 }
196 }
197 *resp = responseArrPtr.release();
198 return PAM_SUCCESS;
199}
200
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000201inline int pamUpdatePassword(const std::string& username,
202 const std::string& password)
Ed Tanousa8408792018-09-05 16:08:38 -0700203{
Ed Tanous4ecc6182022-01-07 09:36:26 -0800204 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
205 char* passStrNoConst = const_cast<char*>(password.c_str());
Ed Tanous9be245e2024-08-26 09:06:45 -0700206 const struct pam_conv localConversation = {
207 pamUpdatePasswordFunctionConversation, passStrNoConst};
Ed Tanous99131cd2019-10-24 11:12:47 -0700208 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousa8408792018-09-05 16:08:38 -0700209
Joseph Reynolds96b39e02019-12-05 17:53:35 -0600210 int retval = pam_start("webserver", username.c_str(), &localConversation,
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000211 &localAuthHandle);
Ed Tanousa8408792018-09-05 16:08:38 -0700212
213 if (retval != PAM_SUCCESS)
214 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000215 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700216 }
217
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000218 retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
219 if (retval != PAM_SUCCESS)
Ed Tanousa8408792018-09-05 16:08:38 -0700220 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000221 pam_end(localAuthHandle, PAM_SUCCESS);
222 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700223 }
224
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000225 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanousa8408792018-09-05 16:08:38 -0700226}