7684972a |
/* Copyright (C) CJ Affiliate
*
* You may use, distribute and modify this code under the
* terms of the GNU General Public License version 2 or
* later.
*
* You should have received a copy of the license with this
* file. If not, you will find a copy in the "LICENSE" file
* at https://github.com/cjdev/dual-control.
*/
|
01c00cfe |
#include <vector>
#include <algorithm>
#include <string>
|
2fade7af |
#include <algorithm>
#include <memory>
#include <cstring>
|
0b6e39a6 |
#include <security/pam_modules.h>
|
2fade7af |
#include "request.h"
|
0b6e39a6 |
#include "conversation.h"
#include "test_util.h"
|
d4af7e88 |
#include "sys_pam.h"
|
0b6e39a6 |
|
2fade7af |
struct conversation_data {
std::vector<pam_message> expected_prompts;
std::vector<pam_response> responses;
int return_value;
|
01c00cfe |
};
|
f3049f9f |
const std::string token_prompt ("Dual control token: ");
const std::string reason_prompt ("Reason: ");
|
11111aa0 |
|
2fade7af |
bool same_prompts (const std::vector<pam_message> &expected,
int num_prompts, const pam_message **actual)
|
cdf7fd74 |
{
|
2fade7af |
if (expected.size() != num_prompts) {
return false;
|
cdf7fd74 |
}
|
1bbd05bc |
|
2fade7af |
for (int i=0; i< num_prompts; ++i) {
if (expected[i].msg_style != actual[i]->msg_style) {
return false;
|
12570d65 |
}
|
b017a4d2 |
|
2fade7af |
if (std::strcmp (expected[i].msg, actual[i]->msg)) {
return false;
|
f9c4622b |
}
|
cdf7fd74 |
}
|
f9c4622b |
|
2fade7af |
return true;
}
|
f9c4622b |
|
2fade7af |
template<class T>
T *address_of (T &t)
|
cdf7fd74 |
{
|
2fade7af |
return &t;
}
int fake_conv (int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
conversation_data *data = reinterpret_cast<conversation_data *>
(appdata_ptr);
|
f3049f9f |
|
2fade7af |
if (data->return_value != PAM_SUCCESS) {
return data->return_value;
|
cdf7fd74 |
}
|
c9d7e817 |
|
2fade7af |
if (!same_prompts (data->expected_prompts, num_msg, msg)) {
throw std::string ("unexpected prompts");
}
std::vector<pam_response> &responses = data->responses;
std::transform (responses.begin(), responses.end(), resp,
address_of<pam_response>);
return data->return_value;
}
|
c9d7e817 |
|
2fade7af |
class fake_pam : public pam_ifc
|
cdf7fd74 |
{
private:
|
2fade7af |
pam_handle *expected_handle_;
|
11111aa0 |
std::vector<conversation_data> conversations_;
|
6f31174c |
int get_response_;
|
11111aa0 |
mutable pam_conv token_conv_;
mutable pam_conv reason_conv_;
mutable int count_;
|
cdf7fd74 |
public:
|
2ff5dc7c |
fake_pam (pam_handle *expected_handle,
|
11111aa0 |
const std::vector<conversation_data> &conversations)
|
2ff5dc7c |
: expected_handle_ (expected_handle),
|
11111aa0 |
conversations_ (conversations),
get_response_ (PAM_SUCCESS),
|
f3049f9f |
count_ (0)
|
2fade7af |
{}
|
6f31174c |
fake_pam (int get_response) : get_response_ (get_response) {}
|
bb80149c |
int get_conv (pam_handle *handle, const pam_conv **out) const
|
cdf7fd74 |
{
|
6f31174c |
if (get_response_ != PAM_SUCCESS) {
return get_response_;
}
|
2fade7af |
if (expected_handle_ != handle) {
|
2ff5dc7c |
throw std::string ("unexpected handle");
|
01c00cfe |
}
|
2ff5dc7c |
|
8148dd0f |
pam_conv *conv_p;
|
e59efb19 |
conv_p = count_ == 0 ? &token_conv_ : &reason_conv_;
|
8148dd0f |
conv_p->appdata_ptr = (void *) (&conversations_[count_]);
conv_p->conv = fake_conv;
*out = conv_p;
|
11111aa0 |
count_ += 1;
|
2fade7af |
return PAM_SUCCESS;
|
cdf7fd74 |
}
|
2fade7af |
};
|
0b6e39a6 |
|
2fade7af |
template<class T>
|
2ff5dc7c |
std::shared_ptr<T> share (T *t)
{
return std::shared_ptr<T> (t);
|
0b6e39a6 |
}
|
8148dd0f |
std::vector<pam_message> create_messages (const std::string &prompt)
|
6de3846a |
{
|
11111aa0 |
pam_message message;
|
e59efb19 |
message.msg_style = prompt == token_prompt ? PAM_PROMPT_ECHO_OFF :
PAM_PROMPT_ECHO_ON;
|
11111aa0 |
message.msg = const_cast<char *> (prompt.c_str());
|
c8d91f67 |
std::vector<pam_message> messages;
|
11111aa0 |
messages.push_back (message);
|
c8d91f67 |
|
cac488ec |
return messages;
}
|
8148dd0f |
std::vector<pam_response> create_responses (const std::string &answer,
|
11111aa0 |
int retcode)
|
6de3846a |
{
|
11111aa0 |
pam_response response;
response.resp_retcode = retcode;
response.resp = const_cast<char *> (answer.c_str());
|
cac488ec |
|
c8d91f67 |
std::vector<pam_response> responses;
|
11111aa0 |
responses.push_back (response);
|
c8d91f67 |
|
cac488ec |
return responses;
}
|
8148dd0f |
conversation successful_conversation (pam_handle *expected_handle,
|
e59efb19 |
const std::string &token_answer,
const std::string &reason_answer)
|
cac488ec |
{
|
8148dd0f |
std::vector<pam_message> token_messages (create_messages (
|
f3049f9f |
token_prompt));
|
8148dd0f |
std::vector<pam_response> token_responses (create_responses (
|
f3049f9f |
token_answer,
|
11111aa0 |
PAM_SUCCESS));
|
8148dd0f |
std::vector<pam_message> reason_messages (create_messages (
|
f3049f9f |
reason_prompt));
|
8148dd0f |
std::vector<pam_response> reason_responses (create_responses (
|
f3049f9f |
reason_answer,
|
11111aa0 |
PAM_SUCCESS));
conversation_data token_conv_data = {
token_messages,
token_responses,
|
2fade7af |
PAM_SUCCESS
};
|
11111aa0 |
conversation_data reason_conv_data = {
reason_messages,
reason_responses,
PAM_SUCCESS
};
std::vector<conversation_data> conversations;
|
f3049f9f |
conversations.push_back (token_conv_data);
conversations.push_back (reason_conv_data);
|
11111aa0 |
pam pam (share (new fake_pam (expected_handle, conversations)));
|
0252cd89 |
return conversation::create (pam);
|
be7b0e04 |
}
|
6de3846a |
bool check_conversation_response (const std::string &token_answer,
const std::string &expected_reason,
|
11111aa0 |
const std::string &expected_user,
const std::string &expected_token)
|
6f31174c |
{
|
3e8b9587 |
// given
pam_handle *handle = reinterpret_cast<pam_handle *> (29039);
|
8148dd0f |
conversation conversation (successful_conversation (handle, token_answer,
|
6de3846a |
expected_reason));
|
3e8b9587 |
pam_request request (handle, 0, 0, 0);
// when
conversation_result actual = conversation.initiate (request);
// then
|
a9ede8a3 |
check (actual.user_name == expected_user, "user name does not match");
check (actual.token == expected_token, "token does not match");
|
c8d91f67 |
check (actual.reason == expected_reason, "reason does not match");
|
3e8b9587 |
succeed();
}
|
cac488ec |
bool returns_user_token_and_reason()
|
a9ede8a3 |
{
std::string user ("user");
std::string token ("token");
|
8148dd0f |
std::string reason ("test reason");
|
6de3846a |
return check_conversation_response (user + ":" + token, reason, user,
token);
|
a9ede8a3 |
}
|
9eeff687 |
|
6f31174c |
int returns_empty_user_and_token_when_no_colon()
{
|
8148dd0f |
return check_conversation_response ("nocolon", "test reason", "", "");
|
6f31174c |
}
|
9eeff687 |
|
8148dd0f |
int returns_empty_user_and_token_when_empty_token_answer()
|
6f31174c |
{
|
8148dd0f |
return check_conversation_response ("", "test reason", "", "");
|
6f31174c |
}
|
c8a98157 |
|
6f31174c |
int returns_empty_token_when_colon_end()
{
|
a9ede8a3 |
std::string user ("user");
|
8148dd0f |
std::string reason ("test reason");
|
cac488ec |
return check_conversation_response (user + ":", reason, user, "");
|
c8a98157 |
}
|
6f31174c |
int returns_empty_user_when_colon_start()
{
|
a9ede8a3 |
std::string token ("token");
|
8148dd0f |
std::string reason ("test reason");
|
cac488ec |
return check_conversation_response (":" + token, reason, "", token);
|
6f31174c |
}
|
cac488ec |
int returns_empty_conversation_result_when_pam_cant_create_conversation()
|
6f31174c |
{
// given
pam pam (share (new fake_pam (PAM_SERVICE_ERR)));
|
0252cd89 |
conversation conversation = conversation::create (pam);
|
6f31174c |
pam_request request (0, 0, 0, 0);
// when
conversation_result actual = conversation.initiate (request);
// then
|
cac488ec |
check (actual.user_name == "", "user name should be empty");
check (actual.token == "", "token should be empty");
check (actual.reason == "", "reason should be empty");
|
6f31174c |
succeed();
|
e7cf340a |
}
|
9eeff687 |
|
cac488ec |
int returns_empty_user_and_token_when_token_answer_fails()
|
fa74284e |
{
|
cac488ec |
//given
|
8148dd0f |
const std::string user ("user");
const std::string token ("token");
|
e59efb19 |
const std::string token_answer (user + ":" + token);
|
8148dd0f |
const std::string reason ("test reason");
|
cac488ec |
int token_retcode = PAM_CONV_ERR;
int reason_retcode = PAM_SUCCESS;
|
8148dd0f |
std::vector<pam_message> token_messages (create_messages (
|
f3049f9f |
token_prompt));
|
8148dd0f |
std::vector<pam_response> token_responses (create_responses (
|
f3049f9f |
user + ":" + token,
token_retcode));
|
8148dd0f |
std::vector<pam_message> reason_messages (create_messages (
|
f3049f9f |
reason_prompt));
|
8148dd0f |
std::vector<pam_response> reason_responses (create_responses (reason,
|
f3049f9f |
reason_retcode));
|
11111aa0 |
conversation_data token_conversation_data = {
token_messages,
token_responses,
token_retcode
|
cac488ec |
};
|
11111aa0 |
conversation_data reason_conversation_data = {
reason_messages,
reason_responses,
reason_retcode
};
std::vector<conversation_data> conversations;
|
f3049f9f |
conversations.push_back (token_conversation_data);
conversations.push_back (reason_conversation_data);
pam pam (share (new fake_pam (0, conversations)));
|
cac488ec |
conversation conversation = conversation::create (pam);
pam_request request (0, 0, 0, 0);
|
c8d91f67 |
|
cac488ec |
// when
conversation_result actual = conversation.initiate (request);
// then
|
6de3846a |
check (actual.user_name == "", "user name should be empty");
check (actual.token == "", "token should be empty");
|
8148dd0f |
check (actual.reason == "test reason", "reason does not match");
|
cac488ec |
succeed();
}
|
c8d91f67 |
|
cac488ec |
int returns_empty_reason_when_reason_answer_fails()
{
|
8148dd0f |
const std::string user ("user");
const std::string token ("token");
|
e59efb19 |
const std::string token_answer (user + ":" + token);
|
8148dd0f |
const std::string reason ("test reason");
|
cac488ec |
int token_retcode = PAM_SUCCESS;
int reason_retcode = PAM_CONV_ERR;
|
8148dd0f |
std::vector<pam_message> token_messages (create_messages (
|
f3049f9f |
token_prompt));
|
8148dd0f |
std::vector<pam_response> token_responses (create_responses (
token_answer,
|
f3049f9f |
token_retcode));
|
8148dd0f |
std::vector<pam_message> reason_messages (create_messages (
|
f3049f9f |
reason_prompt));
|
8148dd0f |
std::vector<pam_response> reason_responses (create_responses (reason,
|
f3049f9f |
reason_retcode));
|
11111aa0 |
conversation_data token_conversation_data = {
token_messages,
token_responses,
token_retcode
};
conversation_data reason_conversation_data = {
reason_messages,
reason_responses,
reason_retcode
|
aea12a9e |
};
|
11111aa0 |
std::vector<conversation_data> conversations;
|
f3049f9f |
conversations.push_back (token_conversation_data);
conversations.push_back (reason_conversation_data);
pam pam (share (new fake_pam (0, conversations)));
|
0252cd89 |
conversation conversation = conversation::create (pam);
|
fa74284e |
pam_request request (0, 0, 0, 0);
|
aea12a9e |
|
cac488ec |
// when
|
aea12a9e |
conversation_result actual = conversation.initiate (request);
|
cac488ec |
// then
|
6de3846a |
check (actual.user_name == user, "user name does not match");
check (actual.token == token, "token does not match");
check (actual.reason == "", "reason should be empty");
|
aea12a9e |
succeed();
}
|
2fade7af |
int run_tests()
|
cdf7fd74 |
{
|
cac488ec |
test (returns_user_token_and_reason);
|
9eeff687 |
test (returns_empty_user_and_token_when_no_colon);
|
8148dd0f |
test (returns_empty_user_and_token_when_empty_token_answer);
|
c8a98157 |
test (returns_empty_token_when_colon_end);
test (returns_empty_user_when_colon_start);
|
cac488ec |
test (returns_empty_conversation_result_when_pam_cant_create_conversation);
test (returns_empty_user_and_token_when_token_answer_fails);
test (returns_empty_reason_when_reason_answer_fails);
|
26830ef5 |
succeed();
}
|
2fade7af |
int main (int argc, char **argv)
|
cdf7fd74 |
{
|
2fade7af |
return !run_tests();
|
231e2d6e |
}
|