xref: /openbmc/bmcweb/include/pam_authenticate.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include <security/pam_appl.h>
6 
7 #include <cstring>
8 #include <memory>
9 #include <span>
10 #include <string_view>
11 
12 struct PasswordData
13 {
14     struct Response
15     {
16         std::string_view prompt;
17         std::string value;
18     };
19 
20     std::vector<Response> responseData;
21 
addPromptPasswordData22     int addPrompt(std::string_view prompt, std::string_view value)
23     {
24         if (value.size() + 1 > PAM_MAX_MSG_SIZE)
25         {
26             BMCWEB_LOG_ERROR("value length error", prompt);
27             return PAM_CONV_ERR;
28         }
29         responseData.emplace_back(prompt, std::string(value));
30         return PAM_SUCCESS;
31     }
32 
makeResponsePasswordData33     int makeResponse(const pam_message& msg, pam_response& response)
34     {
35         switch (msg.msg_style)
36         {
37             case PAM_PROMPT_ECHO_ON:
38                 break;
39             case PAM_PROMPT_ECHO_OFF:
40             {
41                 std::string prompt(msg.msg);
42                 auto iter = std::ranges::find_if(
43                     responseData, [&prompt](const Response& data) {
44                         return prompt.starts_with(data.prompt);
45                     });
46                 if (iter == responseData.end())
47                 {
48                     return PAM_CONV_ERR;
49                 }
50                 response.resp = strdup(iter->value.c_str());
51                 return PAM_SUCCESS;
52             }
53             break;
54             case PAM_ERROR_MSG:
55             {
56                 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
57             }
58             break;
59             case PAM_TEXT_INFO:
60             {
61                 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
62             }
63             break;
64             default:
65             {
66                 return PAM_CONV_ERR;
67             }
68         }
69         return PAM_SUCCESS;
70     }
71 };
72 
73 // function used to get user input
pamFunctionConversation(int numMsg,const struct pam_message ** msgs,struct pam_response ** resp,void * appdataPtr)74 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
75                                    struct pam_response** resp, void* appdataPtr)
76 {
77     if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
78     {
79         return PAM_CONV_ERR;
80     }
81 
82     if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
83     {
84         return PAM_CONV_ERR;
85     }
86     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
87     PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
88     auto msgCount = static_cast<size_t>(numMsg);
89     // 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);
93     for (size_t i = 0; i < msgCount; ++i)
94     {
95         const pam_message& msg = *(messagePtrs[i]);
96 
97         pam_response& response = responses[i];
98         response.resp_retcode = 0;
99         response.resp = nullptr;
100 
101         int r = appPass->makeResponse(msg, response);
102         if (r != PAM_SUCCESS)
103         {
104             return r;
105         }
106     }
107 
108     *resp = responseArrPtr.release();
109     return PAM_SUCCESS;
110 }
111 
112 /**
113  * @brief Attempt username/password authentication via PAM.
114  * @param username The provided username aka account name.
115  * @param password The provided password.
116  * @param token The provided MFA token.
117  * @returns PAM error code or PAM_SUCCESS for success. */
pamAuthenticateUser(std::string_view username,std::string_view password,std::optional<std::string> token)118 inline int pamAuthenticateUser(std::string_view username,
119                                std::string_view password,
120                                std::optional<std::string> token)
121 {
122     std::string userStr(username);
123     PasswordData data;
124     if (int ret = data.addPrompt("Password: ", password); ret != PAM_SUCCESS)
125     {
126         return ret;
127     }
128     if (token)
129     {
130         if (int ret = data.addPrompt("Verification code: ", *token);
131             ret != PAM_SUCCESS)
132         {
133             return ret;
134         }
135     }
136 
137     const struct pam_conv localConversation = {pamFunctionConversation, &data};
138     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
139 
140     int retval = pam_start("webserver", userStr.c_str(), &localConversation,
141                            &localAuthHandle);
142     if (retval != PAM_SUCCESS)
143     {
144         return retval;
145     }
146 
147     retval = pam_authenticate(localAuthHandle,
148                               PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
149     if (retval != PAM_SUCCESS)
150     {
151         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
152         return retval;
153     }
154 
155     /* check that the account is healthy */
156     retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
157     if (retval != PAM_SUCCESS)
158     {
159         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
160         return retval;
161     }
162 
163     return pam_end(localAuthHandle, PAM_SUCCESS);
164 }
165 
pamUpdatePassword(const std::string & username,const std::string & password)166 inline int pamUpdatePassword(const std::string& username,
167                              const std::string& password)
168 {
169     PasswordData data;
170     if (int ret = data.addPrompt("New password: ", password);
171         ret != PAM_SUCCESS)
172     {
173         return ret;
174     }
175     if (int ret = data.addPrompt("Retype new password: ", password);
176         ret != PAM_SUCCESS)
177     {
178         return ret;
179     }
180     const struct pam_conv localConversation = {pamFunctionConversation, &data};
181     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
182 
183     int retval = pam_start("webserver", username.c_str(), &localConversation,
184                            &localAuthHandle);
185 
186     if (retval != PAM_SUCCESS)
187     {
188         return retval;
189     }
190 
191     retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
192     if (retval != PAM_SUCCESS)
193     {
194         pam_end(localAuthHandle, PAM_SUCCESS);
195         return retval;
196     }
197 
198     return pam_end(localAuthHandle, PAM_SUCCESS);
199 }
200