blob: 12cb9251d91be09f0800b6bfd0d9c723ded94cf6 [file] [log] [blame]
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001#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
19struct PasswordData
20{
21 struct Response
22 {
23 std::string_view prompt;
24 std::string value;
25 };
26
27 std::vector<Response> responseData;
28
29 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
40 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
81inline 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
124struct 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. */
131 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};