1 #pragma once
2 
3 #include <security/pam_appl.h>
4 
5 #include <boost/utility/string_view.hpp>
6 
7 #include <cstring>
8 #include <memory>
9 
10 // function used to get user input
11 inline int pamFunctionConversation(int numMsg, const struct pam_message** msg,
12                                    struct pam_response** resp, void* appdataPtr)
13 {
14     if (appdataPtr == 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 
24     for (int i = 0; i < numMsg; ++i)
25     {
26         /* Ignore all PAM messages except prompting for hidden input */
27         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
28         if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
29         {
30             continue;
31         }
32 
33         /* Assume PAM is only prompting for the password as hidden input */
34         /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */
35 
36         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
37         char* appPass = reinterpret_cast<char*>(appdataPtr);
38         size_t appPassSize = std::strlen(appPass);
39 
40         if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
41         {
42             return PAM_CONV_ERR;
43         }
44         // IDeally we'd like to avoid using malloc here, but because we're
45         // passing off ownership of this to a C application, there aren't a lot
46         // of sane ways to avoid it.
47 
48         // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)'
49         void* passPtr = malloc(appPassSize + 1);
50         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
51         char* pass = reinterpret_cast<char*>(passPtr);
52         if (pass == nullptr)
53         {
54             return PAM_BUF_ERR;
55         }
56 
57         std::strncpy(pass, appPass, appPassSize + 1);
58 
59         size_t numMsgSize = static_cast<size_t>(numMsg);
60         // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
61         void* ptr = calloc(numMsgSize, sizeof(struct pam_response));
62         if (ptr == nullptr)
63         {
64             // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
65             free(pass);
66             return PAM_BUF_ERR;
67         }
68 
69         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
70         *resp = reinterpret_cast<pam_response*>(ptr);
71 
72         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
73         resp[i]->resp = pass;
74 
75         return PAM_SUCCESS;
76     }
77 
78     return PAM_CONV_ERR;
79 }
80 
81 /**
82  * @brief Attempt username/password authentication via PAM.
83  * @param username The provided username aka account name.
84  * @param password The provided password.
85  * @returns PAM error code or PAM_SUCCESS for success. */
86 inline int pamAuthenticateUser(const std::string_view username,
87                                const std::string_view password)
88 {
89     std::string userStr(username);
90     std::string passStr(password);
91 
92     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
93     char* passStrNoConst = const_cast<char*>(passStr.c_str());
94     const struct pam_conv localConversation = {pamFunctionConversation,
95                                                passStrNoConst};
96     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
97 
98     int retval = pam_start("webserver", userStr.c_str(), &localConversation,
99                            &localAuthHandle);
100     if (retval != PAM_SUCCESS)
101     {
102         return retval;
103     }
104 
105     retval = pam_authenticate(localAuthHandle,
106                               PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
107     if (retval != PAM_SUCCESS)
108     {
109         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
110         return retval;
111     }
112 
113     /* check that the account is healthy */
114     retval = pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK);
115     if (retval != PAM_SUCCESS)
116     {
117         pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
118         return retval;
119     }
120 
121     return pam_end(localAuthHandle, PAM_SUCCESS);
122 }
123 
124 inline int pamUpdatePassword(const std::string& username,
125                              const std::string& password)
126 {
127     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
128     char* passStrNoConst = const_cast<char*>(password.c_str());
129     const struct pam_conv localConversation = {pamFunctionConversation,
130                                                passStrNoConst};
131     pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
132 
133     int retval = pam_start("webserver", username.c_str(), &localConversation,
134                            &localAuthHandle);
135 
136     if (retval != PAM_SUCCESS)
137     {
138         return retval;
139     }
140 
141     retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
142     if (retval != PAM_SUCCESS)
143     {
144         pam_end(localAuthHandle, PAM_SUCCESS);
145         return retval;
146     }
147 
148     return pam_end(localAuthHandle, PAM_SUCCESS);
149 }
150