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