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 // function used to get user input
11 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
12                                    struct pam_response** resp, void* appdataPtr)
13 {
14     if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
15     {
16         return PAM_CONV_ERR;
17     }
18 
19     if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
20     {
21         return PAM_CONV_ERR;
22     }
23     auto msgCount = static_cast<size_t>(numMsg);
24 
25     // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
26     auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
27     auto responses = std::span(responseArrPtr.get(), msgCount);
28     auto messagePtrs = std::span(msgs, msgCount);
29     for (size_t i = 0; i < msgCount; ++i)
30     {
31         const pam_message& msg = *(messagePtrs[i]);
32 
33         pam_response& response = responses[i];
34         response.resp_retcode = 0;
35         response.resp = nullptr;
36 
37         switch (msg.msg_style)
38         {
39             case PAM_PROMPT_ECHO_ON:
40                 break;
41             case PAM_PROMPT_ECHO_OFF:
42             {
43                 // Assume PAM is only prompting for the password as hidden input
44                 // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred
45                 char* appPass = static_cast<char*>(appdataPtr);
46                 size_t appPassSize = std::strlen(appPass);
47 
48                 if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
49                 {
50                     return PAM_CONV_ERR;
51                 }
52                 // Create an array for pam to own
53                 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
54                 auto passPtr = std::make_unique<char[]>(appPassSize + 1);
55                 std::strncpy(passPtr.get(), appPass, appPassSize + 1);
56 
57                 responses[i].resp = passPtr.release();
58             }
59             break;
60             case PAM_ERROR_MSG:
61                 BMCWEB_LOG_ERROR("Pam error {}", msg.msg);
62                 break;
63             case PAM_TEXT_INFO:
64                 BMCWEB_LOG_ERROR("Pam info {}", msg.msg);
65                 break;
66             default:
67                 return PAM_CONV_ERR;
68         }
69     }
70 
71     *resp = responseArrPtr.release();
72     return PAM_SUCCESS;
73 }
74 
75 /**
76  * @brief Attempt username/password authentication via PAM.
77  * @param username The provided username aka account name.
78  * @param password The provided password.
79  * @returns PAM error code or PAM_SUCCESS for success. */
80 inline int pamAuthenticateUser(std::string_view username,
81                                std::string_view password)
82 {
83     std::string userStr(username);
84     std::string passStr(password);
85 
86     char* passStrNoConst = passStr.data();
87     const struct pam_conv localConversation = {pamFunctionConversation,
88                                                passStrNoConst};
89     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
90 
91     int retval = pam_start("webserver", userStr.c_str(), &localConversation,
92                            &localAuthHandle);
93     if (retval != PAM_SUCCESS)
94     {
95         return retval;
96     }
97 
98     retval = pam_authenticate(localAuthHandle,
99                               PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
100     if (retval != PAM_SUCCESS)
101     {
102         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
103         return retval;
104     }
105 
106     /* check that the account is healthy */
107     retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
108     if (retval != PAM_SUCCESS)
109     {
110         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
111         return retval;
112     }
113 
114     return pam_end(localAuthHandle, PAM_SUCCESS);
115 }
116 
117 inline int pamUpdatePassword(const std::string& username,
118                              const std::string& password)
119 {
120     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
121     char* passStrNoConst = const_cast<char*>(password.c_str());
122     const struct pam_conv localConversation = {pamFunctionConversation,
123                                                passStrNoConst};
124     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
125 
126     int retval = pam_start("webserver", username.c_str(), &localConversation,
127                            &localAuthHandle);
128 
129     if (retval != PAM_SUCCESS)
130     {
131         return retval;
132     }
133 
134     retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
135     if (retval != PAM_SUCCESS)
136     {
137         pam_end(localAuthHandle, PAM_SUCCESS);
138         return retval;
139     }
140 
141     return pam_end(localAuthHandle, PAM_SUCCESS);
142 }
143