blob: 358c9d85e874afc69fc949e0d9c343fc0409f3ac [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// misc-include-cleaner complains if this isn't included,
11// modernize-deprecated-headers complains if it is included.
12// NOLINTNEXTLINE(modernize-deprecated-headers)
13#include <string.h>
14
15#include <algorithm>
Ed Tanous911ac312017-08-15 09:37:42 -070016#include <cstring>
Ed Tanouse0d918b2018-03-27 17:41:04 -070017#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080018#include <optional>
Patrick Williamsad7fa902023-05-10 19:57:29 -050019#include <span>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <string>
Ed Tanous5b904292024-04-16 11:10:17 -070021#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080022#include <vector>
Ed Tanousf3d847c2017-06-12 16:01:42 -070023
Ravi Teja2ccce1f2024-08-10 04:05:36 -050024struct PasswordData
25{
Abhilash Rajud193e002024-08-26 00:27:01 -050026 struct Response
27 {
28 std::string_view prompt;
29 std::string value;
30 };
31
32 std::vector<Response> responseData;
33
34 int addPrompt(std::string_view prompt, std::string_view value)
35 {
36 if (value.size() + 1 > PAM_MAX_MSG_SIZE)
37 {
38 BMCWEB_LOG_ERROR("value length error", prompt);
39 return PAM_CONV_ERR;
40 }
41 responseData.emplace_back(prompt, std::string(value));
42 return PAM_SUCCESS;
43 }
Ed Tanous82f49fa2024-08-27 11:37:09 -070044
45 int makeResponse(const pam_message& msg, pam_response& response)
46 {
47 switch (msg.msg_style)
48 {
49 case PAM_PROMPT_ECHO_ON:
50 break;
51 case PAM_PROMPT_ECHO_OFF:
52 {
Abhilash Rajud193e002024-08-26 00:27:01 -050053 std::string prompt(msg.msg);
54 auto iter = std::ranges::find_if(
55 responseData, [&prompt](const Response& data) {
56 return prompt.starts_with(data.prompt);
57 });
58 if (iter == responseData.end())
Ed Tanous82f49fa2024-08-27 11:37:09 -070059 {
60 return PAM_CONV_ERR;
61 }
Abhilash Rajud193e002024-08-26 00:27:01 -050062 response.resp = strdup(iter->value.c_str());
63 return PAM_SUCCESS;
Ed Tanous82f49fa2024-08-27 11:37:09 -070064 }
65 break;
66 case PAM_ERROR_MSG:
67 {
68 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
69 }
70 break;
71 case PAM_TEXT_INFO:
72 {
73 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
74 }
75 break;
76 default:
77 {
78 return PAM_CONV_ERR;
79 }
80 }
Ed Tanous82f49fa2024-08-27 11:37:09 -070081 return PAM_SUCCESS;
82 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050083};
84
Ed Tanousf3d847c2017-06-12 16:01:42 -070085// function used to get user input
Ed Tanous05ecd3a2024-02-16 08:13:57 -080086inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
Ed Tanous1abe55e2018-09-05 08:30:59 -070087 struct pam_response** resp, void* appdataPtr)
88{
Ed Tanous05ecd3a2024-02-16 08:13:57 -080089 if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
Ed Tanous1abe55e2018-09-05 08:30:59 -070090 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053091 return PAM_CONV_ERR;
Ed Tanous1abe55e2018-09-05 08:30:59 -070092 }
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053093
94 if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
Ed Tanousf1eebf02019-03-04 15:57:09 -080095 {
P Dheeraj Srujan Kumarba95fcc2021-07-12 21:47:59 +053096 return PAM_CONV_ERR;
Ed Tanousf1eebf02019-03-04 15:57:09 -080097 }
Ravi Teja2ccce1f2024-08-10 04:05:36 -050098 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
99 PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
Patrick Williamsad7fa902023-05-10 19:57:29 -0500100 auto msgCount = static_cast<size_t>(numMsg);
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800101 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
102 auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
103 auto responses = std::span(responseArrPtr.get(), msgCount);
104 auto messagePtrs = std::span(msgs, msgCount);
Patrick Williamsad7fa902023-05-10 19:57:29 -0500105 for (size_t i = 0; i < msgCount; ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700106 {
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800107 const pam_message& msg = *(messagePtrs[i]);
108
109 pam_response& response = responses[i];
110 response.resp_retcode = 0;
111 response.resp = nullptr;
112
Ed Tanous82f49fa2024-08-27 11:37:09 -0700113 int r = appPass->makeResponse(msg, response);
114 if (r != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700115 {
Ed Tanous82f49fa2024-08-27 11:37:09 -0700116 return r;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700117 }
Ed Tanous911ac312017-08-15 09:37:42 -0700118 }
Ed Tanousf3d847c2017-06-12 16:01:42 -0700119
Ed Tanous05ecd3a2024-02-16 08:13:57 -0800120 *resp = responseArrPtr.release();
121 return PAM_SUCCESS;
Ed Tanousf3d847c2017-06-12 16:01:42 -0700122}
123
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600124/**
125 * @brief Attempt username/password authentication via PAM.
126 * @param username The provided username aka account name.
127 * @param password The provided password.
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500128 * @param token The provided MFA token.
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600129 * @returns PAM error code or PAM_SUCCESS for success. */
Ed Tanous26ccae32023-02-16 10:28:44 -0800130inline int pamAuthenticateUser(std::string_view username,
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500131 std::string_view password,
132 std::optional<std::string> token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700133{
134 std::string userStr(username);
Abhilash Rajud193e002024-08-26 00:27:01 -0500135 PasswordData data;
136 if (int ret = data.addPrompt("Password: ", password); ret != PAM_SUCCESS)
137 {
138 return ret;
139 }
140 if (token)
141 {
142 if (int ret = data.addPrompt("Verification code: ", *token);
143 ret != PAM_SUCCESS)
144 {
145 return ret;
146 }
147 }
148
Ravi Teja2ccce1f2024-08-10 04:05:36 -0500149 const struct pam_conv localConversation = {pamFunctionConversation, &data};
Ed Tanous99131cd2019-10-24 11:12:47 -0700150 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousf3d847c2017-06-12 16:01:42 -0700151
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600152 int retval = pam_start("webserver", userStr.c_str(), &localConversation,
153 &localAuthHandle);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700154 if (retval != PAM_SUCCESS)
155 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600156 return retval;
157 }
158
159 retval = pam_authenticate(localAuthHandle,
160 PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
161 if (retval != PAM_SUCCESS)
162 {
163 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
164 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 }
Ed Tanous911ac312017-08-15 09:37:42 -0700166
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 /* check that the account is healthy */
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600168 retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
169 if (retval != PAM_SUCCESS)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700170 {
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600171 pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
172 return retval;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 }
Ed Tanous911ac312017-08-15 09:37:42 -0700174
Joseph Reynoldsd887fff2020-01-14 16:34:09 -0600175 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanous911ac312017-08-15 09:37:42 -0700176}
Ed Tanousa8408792018-09-05 16:08:38 -0700177
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000178inline int pamUpdatePassword(const std::string& username,
179 const std::string& password)
Ed Tanousa8408792018-09-05 16:08:38 -0700180{
Abhilash Rajud193e002024-08-26 00:27:01 -0500181 PasswordData data;
182 if (int ret = data.addPrompt("New password: ", password);
183 ret != PAM_SUCCESS)
184 {
185 return ret;
186 }
187 if (int ret = data.addPrompt("Retype new password: ", password);
188 ret != PAM_SUCCESS)
189 {
190 return ret;
191 }
192 const struct pam_conv localConversation = {pamFunctionConversation, &data};
Ed Tanous99131cd2019-10-24 11:12:47 -0700193 pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
Ed Tanousa8408792018-09-05 16:08:38 -0700194
Joseph Reynolds96b39e02019-12-05 17:53:35 -0600195 int retval = pam_start("webserver", username.c_str(), &localConversation,
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000196 &localAuthHandle);
Ed Tanousa8408792018-09-05 16:08:38 -0700197
198 if (retval != PAM_SUCCESS)
199 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000200 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700201 }
202
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000203 retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
204 if (retval != PAM_SUCCESS)
Ed Tanousa8408792018-09-05 16:08:38 -0700205 {
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000206 pam_end(localAuthHandle, PAM_SUCCESS);
207 return retval;
Ed Tanousa8408792018-09-05 16:08:38 -0700208 }
209
jayaprakash Mutyala66b5ca72019-08-07 20:26:37 +0000210 return pam_end(localAuthHandle, PAM_SUCCESS);
Ed Tanousa8408792018-09-05 16:08:38 -0700211}