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