1 #pragma once
2 
3 #include <security/pam_appl.h>
4 
5 #include <cstring>
6 #include <memory>
7 #include <span>
8 #include <string_view>
9 
10 struct PasswordData
11 {
12     std::string password;
13     std::optional<std::string> token;
14 };
15 
16 // function used to get user input
17 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
18                                    struct pam_response** resp, void* appdataPtr)
19 {
20     if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
21     {
22         return PAM_CONV_ERR;
23     }
24 
25     if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
26     {
27         return PAM_CONV_ERR;
28     }
29     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
30     PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
31     auto msgCount = static_cast<size_t>(numMsg);
32     // 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);
36     for (size_t i = 0; i < msgCount; ++i)
37     {
38         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)
45         {
46             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
51                 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
52                 size_t appPassSize = appPass->password.size();
53                 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
54                 {
55                     return PAM_CONV_ERR;
56                 }
57                 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                 }
79             }
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;
89         }
90     }
91 
92     *resp = responseArrPtr.release();
93     return PAM_SUCCESS;
94 }
95 
96 /**
97  * @brief Attempt username/password authentication via PAM.
98  * @param username The provided username aka account name.
99  * @param password The provided password.
100  * @param token The provided MFA token.
101  * @returns PAM error code or PAM_SUCCESS for success. */
102 inline int pamAuthenticateUser(std::string_view username,
103                                std::string_view password,
104                                std::optional<std::string> token)
105 {
106     std::string userStr(username);
107     PasswordData data{std::string(password), std::move(token)};
108     const struct pam_conv localConversation = {pamFunctionConversation, &data};
109     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
110 
111     int retval = pam_start("webserver", userStr.c_str(), &localConversation,
112                            &localAuthHandle);
113     if (retval != PAM_SUCCESS)
114     {
115         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;
124     }
125 
126     /* check that the account is healthy */
127     retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
128     if (retval != PAM_SUCCESS)
129     {
130         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
131         return retval;
132     }
133 
134     return pam_end(localAuthHandle, PAM_SUCCESS);
135 }
136 
137 inline 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 
201 inline int pamUpdatePassword(const std::string& username,
202                              const std::string& password)
203 {
204     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
205     char* passStrNoConst = const_cast<char*>(password.c_str());
206     const struct pam_conv localConversation = {
207         pamUpdatePasswordFunctionConversation, passStrNoConst};
208     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
209 
210     int retval = pam_start("webserver", username.c_str(), &localConversation,
211                            &localAuthHandle);
212 
213     if (retval != PAM_SUCCESS)
214     {
215         return retval;
216     }
217 
218     retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
219     if (retval != PAM_SUCCESS)
220     {
221         pam_end(localAuthHandle, PAM_SUCCESS);
222         return retval;
223     }
224 
225     return pam_end(localAuthHandle, PAM_SUCCESS);
226 }
227