xref: /openbmc/bmcweb/include/pam_authenticate.hpp (revision 92e11bf8fe87746f6981b9c416e6f2d26c86169f)
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