xref: /openbmc/phosphor-user-manager/totp.hpp (revision a1a754c22305421e859b4ffeeee49909cf11a2fa)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include <security/pam_appl.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include <unistd.h>
9 
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/elog.hpp>
12 #include <phosphor-logging/lg2.hpp>
13 
14 #include <cstring>
15 #include <memory>
16 #include <span>
17 #include <string_view>
18 
19 struct PasswordData
20 {
21     struct Response
22     {
23         std::string_view prompt;
24         std::string value;
25     };
26 
27     std::vector<Response> responseData;
28 
addPromptPasswordData29     int addPrompt(std::string_view prompt, std::string_view value)
30     {
31         if (value.size() + 1 > PAM_MAX_MSG_SIZE)
32         {
33             lg2::error("value length error{PROMPT}", "PROMPT", prompt);
34             return PAM_CONV_ERR;
35         }
36         responseData.emplace_back(prompt, std::string(value));
37         return PAM_SUCCESS;
38     }
39 
makeResponsePasswordData40     int makeResponse(const pam_message& msg, pam_response& response)
41     {
42         switch (msg.msg_style)
43         {
44             case PAM_PROMPT_ECHO_ON:
45                 break;
46             case PAM_PROMPT_ECHO_OFF:
47             {
48                 std::string prompt(msg.msg);
49                 auto iter = std::ranges::find_if(
50                     responseData, [&prompt](const Response& data) {
51                         return prompt.starts_with(data.prompt);
52                     });
53                 if (iter == responseData.end())
54                 {
55                     return PAM_CONV_ERR;
56                 }
57                 response.resp = strdup(iter->value.c_str());
58                 return PAM_SUCCESS;
59             }
60             break;
61             case PAM_ERROR_MSG:
62             {
63                 lg2::error("Pam error {MSG}", "MSG", msg.msg);
64             }
65             break;
66             case PAM_TEXT_INFO:
67             {
68                 lg2::error("Pam info {MSG}", "MSG", msg.msg);
69             }
70             break;
71             default:
72             {
73                 return PAM_CONV_ERR;
74             }
75         }
76         return PAM_SUCCESS;
77     }
78 };
79 
80 // function used to get user input
pamFunctionConversation(int numMsg,const struct pam_message ** msgs,struct pam_response ** resp,void * appdataPtr)81 inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
82                                    struct pam_response** resp, void* appdataPtr)
83 {
84     if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
85     {
86         return PAM_CONV_ERR;
87     }
88 
89     if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
90     {
91         return PAM_CONV_ERR;
92     }
93     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
94     PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
95     auto msgCount = static_cast<size_t>(numMsg);
96     // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
97     // auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
98     auto pamResponseDeleter = [](pam_response* ptr) { free(ptr); };
99 
100     std::unique_ptr<pam_response[], decltype(pamResponseDeleter)> responses(
101         static_cast<pam_response*>(calloc(msgCount, sizeof(pam_response))),
102         pamResponseDeleter);
103 
104     auto messagePtrs = std::span(msgs, msgCount);
105     for (size_t i = 0; i < msgCount; ++i)
106     {
107         const pam_message& msg = *(messagePtrs[i]);
108 
109         pam_response& response = responses[i];
110         response.resp_retcode = 0;
111         response.resp = nullptr;
112 
113         int r = appPass->makeResponse(msg, response);
114         if (r != PAM_SUCCESS)
115         {
116             return r;
117         }
118     }
119 
120     *resp = responses.release();
121     return PAM_SUCCESS;
122 }
123 
124 struct Totp
125 {
126     /**
127      * @brief Attempt username/password authentication via PAM.
128      * @param username The provided username aka account name.
129      * @param token The provided MFA token.
130      * @returns PAM error code or PAM_SUCCESS for success. */
verifyTotp131     static inline int verify(std::string_view username, std::string token)
132     {
133         std::string userStr(username);
134         PasswordData data;
135 
136         if (int ret = data.addPrompt("Verification code: ", token);
137             ret != PAM_SUCCESS)
138         {
139             return ret;
140         }
141 
142         const struct pam_conv localConversation = {pamFunctionConversation,
143                                                    &data};
144         pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
145 
146         int retval = pam_start("mfa_pam", userStr.c_str(), &localConversation,
147                                &localAuthHandle);
148         if (retval != PAM_SUCCESS)
149         {
150             return retval;
151         }
152 
153         retval = pam_authenticate(localAuthHandle,
154                                   PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
155         if (retval != PAM_SUCCESS)
156         {
157             pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
158             return retval;
159         }
160         return pam_end(localAuthHandle, PAM_SUCCESS);
161     }
162 };
163