blob: 06202a33d676b8cf758fdebeae92e4d71ae18557 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous911ac312017-08-15 09:37:42 -07003#pragma once
4
Ed Tanousd7857202025-01-28 15:32:26 -08005#include "logging.hpp"
6
7#include <security/_pam_types.h>
Ed Tanousf3d847c2017-06-12 16:01:42 -07008#include <security/pam_appl.h>
Ed Tanous1abe55e2018-09-05 08:30:59 -07009
Ed Tanousd7857202025-01-28 15:32:26 -080010#include <algorithm>
Myung Bae92e11bf2025-01-31 09:22:23 -050011#include <cstddef>
Ed Tanous911ac312017-08-15 09:37:42 -070012#include <cstring>
Ed Tanouse0d918b2018-03-27 17:41:04 -070013#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080014#include <optional>
Patrick Williamsad7fa902023-05-10 19:57:29 -050015#include <span>
Ed Tanousd7857202025-01-28 15:32:26 -080016#include <string>
Ed Tanous5b904292024-04-16 11:10:17 -070017#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080018#include <vector>
Ed Tanousf3d847c2017-06-12 16:01:42 -070019
Ravi Teja2ccce1f2024-08-10 04:05:36 -050020struct PasswordData
21{
Abhilash Rajud193e002024-08-26 00:27:01 -050022 struct Response
23 {
24 std::string_view prompt;
25 std::string value;
26 };
27
28 std::vector<Response> responseData;
29
30 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 }
Ed Tanous82f49fa2024-08-27 11:37:09 -070040
41 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 {
Abhilash Rajud193e002024-08-26 00:27:01 -050049 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())
Ed Tanous82f49fa2024-08-27 11:37:09 -070055 {
56 return PAM_CONV_ERR;
57 }
Myung Bae92e11bf2025-01-31 09:22:23 -050058 // NOLINTNEXTLINE(misc-include-cleaner)
Abhilash Rajud193e002024-08-26 00:27:01 -050059 response.resp = strdup(iter->value.c_str());
60 return PAM_SUCCESS;
Ed Tanous82f49fa2024-08-27 11:37:09 -070061 }
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 }
Ed Tanous82f49fa2024-08-27 11:37:09 -070078 return PAM_SUCCESS;
79 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050080};
81
Ed Tanousf3d847c2017-06-12 16:01:42 -070082// function used to get user input
Ed Tanous05ecd3a2024-02-16 08:13:57 -080083inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 struct pam_response** resp, void* appdataPtr)
85{
Ed Tanous05ecd3a2024-02-16 08:13:57 -080086 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
Ed Tanous1abe55e2018-09-05 08:30:59 -070087 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053088 return PAM_CONV_ERR;
Ed Tanous1abe55e2018-09-05 08:30:59 -070089 }
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053090
91 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
Ed Tanousf1eebf02019-03-04 15:57:09 -080092 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053093 return PAM_CONV_ERR;
Ed Tanousf1eebf02019-03-04 15:57:09 -080094 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050095 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
96 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
Patrick Williamsad7fa902023-05-10 19:57:29 -050097 auto msgCount = static_cast<size_t>(numMsg);
Ed Tanous05ecd3a2024-02-16 08:13:57 -080098 // 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);
Patrick Williamsad7fa902023-05-10 19:57:29 -0500102 for (size_t i = 0; i < msgCount; ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700103 {
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800104 const pam_message& msg = *(messagePtrs[i]);
105
106 pam_response& response = responses[i];
107 response.resp_retcode = 0;
108 response.resp = nullptr;
109
Ed Tanous82f49fa2024-08-27 11:37:09 -0700110 int r = appPass->makeResponse(msg, response);
111 if (r != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700112 {
Ed Tanous82f49fa2024-08-27 11:37:09 -0700113 return r;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700114 }
Ed Tanous911ac312017-08-15 09:37:42 -0700115 }
Ed Tanousf3d847c2017-06-12 16:01:42 -0700116
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800117 *resp = responseArrPtr.release();
118 return PAM_SUCCESS;
Ed Tanousf3d847c2017-06-12 16:01:42 -0700119}
120
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600121/**
122 * @brief Attempt username/password authentication via PAM.
123 * @param username The provided username aka account name.
124 * @param password The provided password.
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500125 * @param token The provided MFA token.
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600126 * @returns PAM error code or PAM_SUCCESS for success. */
Ed Tanous26ccae32023-02-16 10:28:44 -0800127inline int pamAuthenticateUser(std::string_view username,
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500128 std::string_view password,
129 std::optional<std::string> token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700130{
131 std::string userStr(username);
Abhilash Rajud193e002024-08-26 00:27:01 -0500132 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
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500146 const struct pam_conv localConversation = {pamFunctionConversation, &data};
Ed Tanous99131cd2019-10-24 11:12:47 -0700147 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousf3d847c2017-06-12 16:01:42 -0700148
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600149 int retval = pam_start("webserver", userStr.c_str(), &localConversation,
150 &localAuthHandle);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700151 if (retval != PAM_SUCCESS)
152 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600153 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;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700162 }
Ed Tanous911ac312017-08-15 09:37:42 -0700163
Ed Tanous1abe55e2018-09-05 08:30:59 -0700164 /* check that the account is healthy */
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600165 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
166 if (retval != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600168 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
169 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700170 }
Ed Tanous911ac312017-08-15 09:37:42 -0700171
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600172 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanous911ac312017-08-15 09:37:42 -0700173}
Ed Tanousa8408792018-09-05 16:08:38 -0700174
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000175inline int pamUpdatePassword(const std::string& username,
176 const std::string& password)
Ed Tanousa8408792018-09-05 16:08:38 -0700177{
Abhilash Rajud193e002024-08-26 00:27:01 -0500178 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};
Ed Tanous99131cd2019-10-24 11:12:47 -0700190 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousa8408792018-09-05 16:08:38 -0700191
Joseph Reynolds96b39e02019-12-05 17:53:35 -0600192 int retval = pam_start("webserver", username.c_str(), &localConversation,
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000193 &localAuthHandle);
Ed Tanousa8408792018-09-05 16:08:38 -0700194
195 if (retval != PAM_SUCCESS)
196 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000197 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700198 }
199
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000200 retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
201 if (retval != PAM_SUCCESS)
Ed Tanousa8408792018-09-05 16:08:38 -0700202 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000203 pam_end(localAuthHandle, PAM_SUCCESS);
204 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700205 }
206
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000207 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanousa8408792018-09-05 16:08:38 -0700208}