From a451843961cc8e92a3cc07fddb56db76b2246937 Mon Sep 17 00:00:00 2001 From: Frank Ueberschar Date: Tue, 16 Oct 2018 18:39:32 +0200 Subject: [PATCH] pam: pam interactive is working with the server handshake protokol - added some functions to convert messages into command strings and vice versa - added tests for the convert functions - traymon does not work yet --- core/src/console/console.cc | 24 ++++--- core/src/dird/authenticate.cc | 84 ++++++++++-------------- core/src/lib/ascii_control_characters.h | 6 +- core/src/lib/bnet.cc | 84 ++++++++++++++++++------ core/src/lib/bnet.h | 12 +++- core/src/lib/bsock.cc | 86 ++++++++++++++++++++----- core/src/lib/bsock.h | 7 ++ core/src/lib/util.cc | 15 ++++- core/src/lib/util.h | 3 + core/src/tests/bsock_test.cc | 1 - core/src/tests/lib_tests.cc | 50 ++++++++++++-- 11 files changed, 262 insertions(+), 110 deletions(-) diff --git a/core/src/console/console.cc b/core/src/console/console.cc index 9cb12190f67..6ad8319eaf3 100644 --- a/core/src/console/console.cc +++ b/core/src/console/console.cc @@ -858,7 +858,7 @@ static bool SelectDirector(const char *director, DirectorResource **ret_dir, Con } namespace console { -BareosSocket *ConnectToDirector(JobControlRecord &jcr, utime_t heart_beat, char *errmsg, int errmsg_len) +BareosSocket *ConnectToDirector(JobControlRecord &jcr, utime_t heart_beat, char *errmsg, int errmsg_len, uint32_t &response) { BareosSocketTCP *UA_sock = New(BareosSocketTCP); if (!UA_sock->connect(NULL, 5, 15, heart_beat, "Director daemon", director_resource->address, NULL, @@ -903,7 +903,8 @@ BareosSocket *ConnectToDirector(JobControlRecord &jcr, utime_t heart_beat, char return nullptr; } - if (!UA_sock->AuthenticateWithDirector(&jcr, name, *password, errmsg, errmsg_len, director_resource)) { + if (!UA_sock->ConsoleAuthenticateWithDirector(&jcr, name, *password, errmsg, + errmsg_len, director_resource, response)) { ConsoleOutput(errmsg); TerminateConsole(0); return nullptr; @@ -1086,24 +1087,27 @@ int main(int argc, char *argv[]) heart_beat = 0; } - UA_sock = ConnectToDirector(jcr, heart_beat, errmsg, errmsg_len); + uint32_t response; + UA_sock = ConnectToDirector(jcr, heart_beat, errmsg, errmsg_len, response); if (!UA_sock) { return 1; } UA_sock->OutputCipherMessageString(ConsoleOutput); ConsoleOutput(errmsg); -#if 0 // Ueb + if (response == kMessageIdPamRequired) { #if defined(HAVE_PAM) -// UA_sock->fsend("@@username:bareos-pam"); -// UA_sock->fsend("@@password:linuxlinux"); - Bmicrosleep(1,0); - if (!ConsolePamAuthenticate(stdin, UA_sock)) { + FormatAndSendResponseMessage(UA_sock, kMessageIdPamInteractive, "OK"); + if (!ConsolePamAuthenticate(stdin, UA_sock)) { + TerminateConsole(0); + return 1; + } +#else + Dmsg0(100, "This Console program does not have the pam feature\n"); TerminateConsole(0); return 1; - } #endif /* HAVE_PAM */ -#endif + } Dmsg0(40, "Opened connection with Director daemon\n"); diff --git a/core/src/dird/authenticate.cc b/core/src/dird/authenticate.cc index f127384ee4f..9057fd80f4c 100644 --- a/core/src/dird/authenticate.cc +++ b/core/src/dird/authenticate.cc @@ -254,7 +254,14 @@ static void SendErrorMessage(std::string console_name, UaContext *ua) static void SendOkMessage(UaContext *ua) { - ua->UA_sock->fsend(_("1000 OK: %s Version: %s (%s)\n"), my_name, VERSION, BDATE); + char buffer[100]; + ::snprintf(buffer, 100, "OK: %s Version: %s (%s)", my_name, VERSION, BDATE); + + if (ua->cons && ua->cons->use_pam_authentication_) { + FormatAndSendResponseMessage(ua->UA_sock, kMessageIdPamRequired, std::string(buffer)); + } else { + FormatAndSendResponseMessage(ua->UA_sock, kMessageIdOk, std::string(buffer)); + } } static bool OptionalAuthenticateRootConsole(std::string console_name, UaContext *ua, bool &auth_success) @@ -284,49 +291,21 @@ static void AuthenticateNamedConsole(std::string console_name, UaContext *ua, bo } } -#if defined(HAVE_PAM) -static void LookupTokenFromSocketStream(BareosSocket *ua_sock, const std::string& token, std::string& output) -{ - std::unique_ptr buffer(new char[token.size()]); - memset(buffer.get(), 0, token.size()); - - int flags = ua_sock->SetNonblocking(); - - int tries = 3; - bool ready = false; - - do { - Bmicrosleep(1,0); - int ret = ::recv(ua_sock->fd_, buffer.get(), token.size(), MSG_PEEK); - if (ret == (int)token.size()) { - if (ua_sock->recv() <= 0) { return; } - std::string temp(ua_sock->msg); - output = temp.substr(temp.find(':')+1); - ready = true; - } - } while (tries-- && !ready); - - ua_sock->RestoreBlocking(flags); -} - -static void LookupOptionalUsername(BareosSocket *ua_sock, std::string& pam_username) -{ - const std::string token {"@@username:"}; - LookupTokenFromSocketStream(ua_sock, token, pam_username); -} - -static void LookupOptionalPassword(BareosSocket *ua_sock, std::string& pam_password) -{ - const std::string token {"@@password:"}; - LookupTokenFromSocketStream(ua_sock, token, pam_password); -} -#endif /* HAVE PAM */ - static bool OptionalAuthenticatePamUser(std::string console_name, UaContext *ua, bool &auth_success) { ConsoleResource *cons = (ConsoleResource *)my_config->GetResWithName(R_CONSOLE, console_name.c_str()); -#if defined(HAVE_PAM) +#if !defined(HAVE_PAM) +{ + if (cons && cons->use_pam_authentication_) { + Emsg0(M_ERROR, 0, _("PAM is not available on this director\n")); + auth_success = false; + return true; + } else { + return false; /* auth_success can be ignored */ + } +} +#else /* HAVE_PAM */ { if (!cons) { /* if console resource cannot be obtained is treated as an error */ auth_success = false; @@ -336,11 +315,24 @@ static bool OptionalAuthenticatePamUser(std::string console_name, UaContext *ua, /* no need to evaluate auth_success if no pam is required */ if (!cons->use_pam_authentication_) { return false; } + uint32_t response; + std::string message; + + if (!ReceiveAndEvaluateResponseMessage(ua->UA_sock, response, message)) { + Dmsg2(100, "Could not evaluate response: %d - %d", response, message.c_str()); + auth_success = false; + return true; + } + std::string pam_username; std::string pam_password; - LookupOptionalUsername(ua->UA_sock, pam_username); - LookupOptionalPassword(ua->UA_sock, pam_password); + if (response == kMessageIdPamUserCredentials) { + /* Ueb: receive username and password */ + Dmsg0(200, "Console chooses Pam direct credentials\n"); + } else if (response == kMessageIdPamInteractive) { + Dmsg0(200, "Console chooses Pam interactive\n"); + } std::string authenticated_username; if (!PamAuthenticateUser(ua->UA_sock, pam_username, pam_password, authenticated_username)) { @@ -359,14 +351,6 @@ static bool OptionalAuthenticatePamUser(std::string console_name, UaContext *ua, } return true; } /* HAVE PAM */ -#else /* !HAVE_PAM */ - if (cons && cons->use_pam_authentication_) { - Emsg0(M_ERROR, 0, _("PAM is not available on this director\n")); - auth_success = false; - return true; - } else { - return false; /* auth_success can be ignored */ - } #endif /* !HAVE_PAM */ } diff --git a/core/src/lib/ascii_control_characters.h b/core/src/lib/ascii_control_characters.h index a92389a81aa..081d5ff9b47 100644 --- a/core/src/lib/ascii_control_characters.h +++ b/core/src/lib/ascii_control_characters.h @@ -24,9 +24,9 @@ class AsciiControlCharacters { public: - static char UnitSeparator() { return unit_separator_; } - static char RecordSeparator() { return record_separator_; } - static char GroupSeparator() { return group_separator_; } + static char UnitSeparator() { return unit_separator_; } /* smallest data item separator */ + static char RecordSeparator() { return record_separator_; } /* data record separator within a group */ + static char GroupSeparator() { return group_separator_; } /* group separator to separate datasets */ private: static constexpr char unit_separator_ = 0x1f; diff --git a/core/src/lib/bnet.cc b/core/src/lib/bnet.cc index 1bbfdb01a55..6472cfe9c95 100644 --- a/core/src/lib/bnet.cc +++ b/core/src/lib/bnet.cc @@ -37,6 +37,7 @@ #include "jcr.h" #include "lib/bnet.h" #include "lib/bsys.h" +#include "lib/ascii_control_characters.h" #include #include "lib/tls.h" @@ -590,50 +591,93 @@ const char *BnetSigToAscii(BareosSocket * bs) } } -bool ReadoutCommandIdFromString(const std::string &message, uint32_t &id_out) +bool ReadoutCommandIdFromMessage(const std::string &message, uint32_t &id_out) { - const char delimiter = ' '; - size_t pos = message.find(delimiter); - if (pos == std::string::npos) { + const char delimiter = AsciiControlCharacters::RecordSeparator(); + + size_t delimiter_position = message.find(delimiter); + if (delimiter_position == std::string::npos) { id_out = kMessageIdProtokollError; return false; } uint32_t id; - size_t pos1; + size_t position_after_number; - try { - id = std::stoul(message, &pos1); + try { /* "1000 OK: ..." */ + id = std::stoul(message, &position_after_number); } catch (const std::exception &e) { id_out = kMessageIdProtokollError; return false; } - if (pos == pos1) { - id_out = id; - return true; - } else { + + if (position_after_number != delimiter_position) { id_out = kMessageIdProtokollError; return false; + } else { + id_out = id; + return true; } } -bool ReceiveAndEvaluateResponse(BareosSocket *bsock, uint32_t &id_out, std::string &message_out) +bool EvaluateResponseMessage(std::string &message, uint32_t &id_out, std::string &human_readable_message_out) { - int recv_return_value = bsock->recv(); + uint32_t id = kMessageIdUnknown; + bool ok = ReadoutCommandIdFromMessage(message, id); + + id_out = id; + SwapSeparatorsInString(message); + human_readable_message_out = message; + + return ok; + +} + +bool ReceiveAndEvaluateResponseMessage(BareosSocket *bsock, uint32_t &id_out, std::string &human_readable_message_out) +{ + int ret = bsock->recv(); bsock->StopTimer(); - if (recv_return_value <= 0) { + if (ret <= 0) { + Dmsg1(100, "Error while receiving response message: %s", bsock->msg); return false; } Dmsg1(10, "msg); - const std::string message(bsock->msg); - uint32_t id; - bool ok = ReadoutCommandIdFromString(message, id); + std::string message(bsock->msg); - id_out = id; - message_out = message; + if (message.empty()) { + Dmsg0(100, "Received message is empty\n"); + return false; + } - return ok; + return EvaluateResponseMessage(message, id_out, human_readable_message_out); +} + +bool FormatAndSendResponseMessage(BareosSocket *bsock, uint32_t id, std::vector optional_arguments) +{ + std::string message; + message += std::to_string(id); + + for (auto s : optional_arguments) { + message += AsciiControlCharacters::RecordSeparator(); + message += s; + } + + message += '\n'; + + const char *m = message.c_str(); + + if (bsock->send(m, message.size()) <=0 ) { + Dmsg1(100, "Could not send response message: %d\n", message.c_str()); + return false; + } + return true; +} + +bool FormatAndSendResponseMessage(BareosSocket *bsock, uint32_t id, const std::string &str) +{ + std::vector vec { str }; + FormatAndSendResponseMessage(bsock, id, vec); } diff --git a/core/src/lib/bnet.h b/core/src/lib/bnet.h index 85bddf0c939..ec072bceed9 100644 --- a/core/src/lib/bnet.h +++ b/core/src/lib/bnet.h @@ -59,7 +59,15 @@ enum : uint32_t { kMessageIdPamUserCredentials = 4002 }; -bool ReadoutCommandIdFromString(const std::string &message, uint32_t &id_out); -bool ReceiveAndEvaluateResponse(BareosSocket *bsock, uint32_t &id_out, std::string &message_out); +#ifdef BAREOS_TEST_LIB +bool ReadoutCommandIdFromMessage(const std::string &message, uint32_t &id_out); +bool EvaluateResponseMessage(std::string &message, uint32_t &id_out, std::string &human_readable_message_out); +#endif + +bool ReceiveAndEvaluateResponseMessage(BareosSocket *bsock, uint32_t &id_out, std::string &human_readable_message_out); +bool FormatAndSendResponseMessage(BareosSocket *bsock, uint32_t id, const std::string &str); +bool FormatAndSendResponseMessage(BareosSocket *bsock, + uint32_t id, + std::vector optional_arguments = std::vector()); #endif // BAREOS_LIB_BNET_H_ diff --git a/core/src/lib/bsock.cc b/core/src/lib/bsock.cc index 0e201858d37..3d5f55d29d5 100644 --- a/core/src/lib/bsock.cc +++ b/core/src/lib/bsock.cc @@ -320,8 +320,44 @@ void BareosSocket::SetKillable(bool killable) /** Commands sent to Director */ static char hello[] = "Hello %s calling\n"; -/** Response from Director */ -static char OKhello[] = "1000 OK:"; + +bool BareosSocket::ConsoleAuthenticateWithDirector(JobControlRecord *jcr, + const char *identity, + s_password &password, + char *response, + int response_len, + TlsResource *tls_resource, + uint32_t &response_id) +{ + char bashed_name[MAX_NAME_LENGTH]; + BareosSocket *dir = this; /* for readability */ + + response[0] = 0; + + bstrncpy(bashed_name, identity, sizeof(bashed_name)); + BashSpaces(bashed_name); + + dir->StartTimer(60 * 5); /* 5 minutes */ + dir->fsend(hello, bashed_name); + + if (!AuthenticateOutboundConnection(jcr, "Director", identity, password, tls_resource)) { + Dmsg0(100, "Authenticate outbound connection failed\n"); + dir->StopTimer(); + return false; + } + + Dmsg1(6, ">dird: %s", dir->msg); + + uint32_t message_id; + std::string received_message; + if (ReceiveAndEvaluateResponseMessage(dir, message_id, received_message)) { + response_id = message_id; + Bsnprintf(response, response_len, "%s\n", received_message.c_str()); + return true; + } + Dmsg0(100, "Wrong Message Protocol ID\n"); + return false; +} bool BareosSocket::AuthenticateWithDirector(JobControlRecord *jcr, const char *identity, @@ -330,6 +366,8 @@ bool BareosSocket::AuthenticateWithDirector(JobControlRecord *jcr, int response_len, TlsResource *tls_resource) { + static char OKAnswerFromDirector[] = "1000 OK:"; + char bashed_name[MAX_NAME_LENGTH]; BareosSocket *dir = this; /* for readability */ @@ -341,27 +379,43 @@ bool BareosSocket::AuthenticateWithDirector(JobControlRecord *jcr, bstrncpy(bashed_name, identity, sizeof(bashed_name)); BashSpaces(bashed_name); - /* - * Timeout Hello after 5 mins - */ - dir->StartTimer(60 * 5); + dir->StartTimer(60 * 5); /* 5 minutes */ dir->fsend(hello, bashed_name); - if (!AuthenticateOutboundConnection(jcr, "Director", identity, password, tls_resource)) { + if (!AuthenticateOutboundConnection(jcr, "Director", identity, password, tls_resource)) { goto bail_out; } + + Dmsg1(6, ">dird: %s", dir->msg); + if (dir->recv() <= 0) { dir->StopTimer(); + Bsnprintf(response, response_len, + _("Bad response to Hello command: ERR=%s\n" + "The Director at \"%s:%d\" is probably not running.\n"), + dir->bstrerror(), dir->host(), dir->port()); return false; } - Dmsg1(6, ">dird: %s", dir->msg); - - uint32_t message_id; - std::string received_message; - if (ReceiveAndEvaluateResponse(dir, message_id, received_message)) { - if (message_id == kMessageIdOk) { - Bsnprintf(response, response_len, "%s\n", received_message.c_str()); - return true; - } + dir->StopTimer(); + Dmsg1(10, "msg); + if (!bstrncmp(dir->msg, OKAnswerFromDirector, sizeof(OKAnswerFromDirector) - 1)) { + Bsnprintf(response, response_len, _("Director at \"%s:%d\" rejected Hello command\n"), dir->host(), + dir->port()); + return false; + } else { + Bsnprintf(response, response_len, "%s", dir->msg); } + + return true; + +bail_out: + dir->StopTimer(); + Bsnprintf(response, response_len, + _("Authorization problem with Director at \"%s:%d\"\n" + "Most likely the passwords do not agree.\n" + "If you are using TLS, there may have been a certificate " + "validation error during the TLS handshake.\n" + "Please see %s for help.\n"), + dir->host(), dir->port(), MANUAL_AUTH_URL); + return false; } diff --git a/core/src/lib/bsock.h b/core/src/lib/bsock.h index 64e50f66583..1ef33290de3 100644 --- a/core/src/lib/bsock.h +++ b/core/src/lib/bsock.h @@ -163,6 +163,13 @@ class BareosSocket : public SmartAlloc { char *response, int response_len, TlsResource *tls_resource); + bool ConsoleAuthenticateWithDirector(JobControlRecord *jcr, + const char *name, + s_password &password, + char *response, + int response_len, + TlsResource *tls_resource, + uint32_t &response_id); bool ParameterizeAndInitTlsConnection(TlsResource *tls_resource, const char *identity, const char *password, diff --git a/core/src/lib/util.cc b/core/src/lib/util.cc index b9d6f0eee42..3817d189f06 100644 --- a/core/src/lib/util.cc +++ b/core/src/lib/util.cc @@ -2,7 +2,7 @@ BAREOSĀ® - Backup Archiving REcovery Open Sourced Copyright (C) 2000-2011 Free Software Foundation Europe e.V. - Copyright (C) 2016-2016 Bareos GmbH & Co. KG + Copyright (C) 2016-2018 Bareos GmbH & Co. KG This program is Free Software; you can redistribute it and/or modify it under the terms of version three of the GNU Affero General Public @@ -28,6 +28,7 @@ #include "include/bareos.h" #include "include/jcr.h" #include "lib/edit.h" +#include "lib/ascii_control_characters.h" /* * Various BAREOS Utility subroutines @@ -178,6 +179,18 @@ void UnbashSpaces(PoolMem &pm) } } +void SwapSeparatorsInString(std::string &str, + char separator, + char new_separator) +{ + std::string::iterator it = str.begin(); + while( it != str.end() ) { + if (*it == separator) { + *it = new_separator; + } + it++; + } +} /* * Parameter: diff --git a/core/src/lib/util.h b/core/src/lib/util.h index a8e62824197..a79adf6480b 100644 --- a/core/src/lib/util.h +++ b/core/src/lib/util.h @@ -21,6 +21,8 @@ #ifndef BAREOS_LIB_UTIL_H_ #define BAREOS_LIB_UTIL_H_ +#include "lib/ascii_control_characters.h" + void EscapeString(PoolMem &snew, char *old, int len); bool IsBufZero(char *buf, int len); void lcase(char *str); @@ -28,6 +30,7 @@ void BashSpaces(char *str); void BashSpaces(PoolMem &pm); void UnbashSpaces(char *str); void UnbashSpaces(PoolMem &pm); +void SwapSeparatorsInString(std::string &str, char separator = AsciiControlCharacters::RecordSeparator(), char new_separator = ' '); const char* IndentMultilineString(PoolMem &resultbuffer, const char *multilinestring, const char *separator); char *encode_time(utime_t time, char *buf); bool ConvertTimeoutToTimespec(timespec &timeout, int timeout_in_seconds); diff --git a/core/src/tests/bsock_test.cc b/core/src/tests/bsock_test.cc index 759f330383a..e028556e363 100644 --- a/core/src/tests/bsock_test.cc +++ b/core/src/tests/bsock_test.cc @@ -264,7 +264,6 @@ bool connect_to_server(std::string console_name, std::string console_password, Dmsg0(10, "socket connect failed\n"); } else { Dmsg0(10, "socket connect OK\n"); - if (!UA_sock->AuthenticateWithDirector(&jcr, name, *password, errmsg, errmsg_len, cons_dir_config.get())) { Emsg0(M_ERROR, 0, "Authenticate Failed\n"); } else { diff --git a/core/src/tests/lib_tests.cc b/core/src/tests/lib_tests.cc index cd38c36692c..a819d3d3553 100644 --- a/core/src/tests/lib_tests.cc +++ b/core/src/tests/lib_tests.cc @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "include/bareos.h" +#define BAREOS_TEST_LIB #include "lib/bnet.h" TEST(BNet, ReadoutCommandIdFromStringTest) @@ -28,20 +29,55 @@ TEST(BNet, ReadoutCommandIdFromStringTest) bool ok; uint32_t id; - const std::string message1 {"1000 OK: Version: "}; - ok = ReadoutCommandIdFromString(message1, id); + std::string message1 = "1000"; + message1 += 0x1e; + message1 += "OK: Version: "; + ok = ReadoutCommandIdFromMessage(message1, id); EXPECT_EQ(id, kMessageIdOk); EXPECT_EQ(ok, true); - const std::string message2 {"1001 OK: Version: "}; - ok = ReadoutCommandIdFromString(message2, id); + std::string message2 = "1001"; + message2 += 0x1e; + message2 += "OK: Version: "; + ok = ReadoutCommandIdFromMessage(message2, id); EXPECT_NE(id, kMessageIdOk); EXPECT_EQ(ok, true); +} + +TEST(BNet, EvaluateResponseMessage_Wrong_Id) +{ + bool ok; + uint32_t id; + + std::string message3 = "10A1"; + message3 += 0x1e; + message3 += "OK: Version: "; + + std::string human_readable_message; + ok = EvaluateResponseMessage(message3, id, human_readable_message); - const char *m3 {"10A1 OK: Version: "}; - const std::string message3 (m3); - ok = ReadoutCommandIdFromString(message3, id); EXPECT_EQ(id, kMessageIdProtokollError); EXPECT_EQ(ok, false); + + const char *m3 {"10A1 OK: Version: "}; EXPECT_STREQ(message3.c_str(), m3); } + +TEST(BNet, EvaluateResponseMessage_Correct_Id) +{ + bool ok; + uint32_t id; + + std::string message4 = "1001"; + message4 += 0x1e; + message4 += "OK: Version: "; + + std::string human_readable_message; + ok = EvaluateResponseMessage(message4, id, human_readable_message); + + EXPECT_EQ(id, kMessageIdPamRequired); + EXPECT_EQ(ok, true); + + const char *m3 {"1001 OK: Version: "}; + EXPECT_STREQ(message4.c_str(), m3); +}