git.fiddlerwoaroof.com
conversation_test.cc
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
 }