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