diff --git a/Makefile b/Makefile index 60376c6..a8230ca 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUILD_DIR = build SOURCES_CLIENT = client.cpp OBJS_CLIENT = $(addprefix $(BUILD_DIR)/,$(SOURCES_CLIENT:.cpp=.o)) -SOURCES_SERVER = server.cpp user.cpp user_handler.cpp mail.cpp +SOURCES_SERVER = server.cpp user.cpp user_handler.cpp mail.cpp ip_ban.cpp OBJS_SERVER = $(addprefix $(BUILD_DIR)/,$(SOURCES_SERVER:.cpp=.o)) all: $(BUILD_DIR) $(TARGET) diff --git a/server/ip_ban.cpp b/server/ip_ban.cpp new file mode 100644 index 0000000..5e6ba2e --- /dev/null +++ b/server/ip_ban.cpp @@ -0,0 +1,54 @@ +#include "ip_ban.h" + +#include +#include +#include +#include + +ip_ban::ip_ban() : m_ban() +{ + this->file = fs::path(); +} + +ip_ban::~ip_ban() +{ + if (this->file.empty()) + return; + + json jsonfile = this->ban_list; + + std::ofstream ofs(this->file, std::ofstream::out | std::ofstream::trunc); + ofs << jsonfile.dump(); +} + +void ip_ban::loadFile(fs::path file) +{ + this->file = file; + + if (fs::exists(file)) { + std::ifstream ifs(file); + this->ban_list = json::parse(ifs); + } +} + +void ip_ban::failedAttempt(std::string username, std::string ip) +{ + printf("%s\n", username.c_str()); + std::unique_lock lock(this->m_ban); + + std::map, time_t>>::iterator it = this->ban_list.insert({ip, {{}, 0}}).first; + if (++it->second.first.insert({username, 0}).first->second >= MAX_ATTEMPTS) { // increase attempt count && check if reached MAX_ATTEMPTS + for (auto& pair : it->second.first ) { // reset attempts to 0 for all names + pair.second = 0; + } + (*it).second.second = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) + BAN_TIME; // set unban time to current time + BAN_TIME seconds + } +} + +bool ip_ban::checkBanned(std::string ip) +{ + std::shared_lock lock(this->m_ban); + + std::map, time_t>>::iterator it; + return (it = this->ban_list.find(ip)) != this->ban_list.end() && (*it).second.second > std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); +} \ No newline at end of file diff --git a/server/ip_ban.h b/server/ip_ban.h new file mode 100644 index 0000000..caaab5f --- /dev/null +++ b/server/ip_ban.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#define BAN_TIME 60 +#define MAX_ATTEMPTS 3 + +namespace fs = std::filesystem; + +using json = nlohmann::json; + +class ip_ban { +public: + + static ip_ban& getInstance() { + static ip_ban instance; + return instance; + }; + + ip_ban(ip_ban const&) = delete; + ip_ban(ip_ban&&) = delete; + ip_ban& operator=(ip_ban const&) = delete; + ip_ban& operator=(ip_ban &&) = delete; + + void loadFile(fs::path file); + + void failedAttempt(std::string username, std::string ip); + bool checkBanned(std::string ip); + +protected: + + ip_ban(); + ~ip_ban(); + + std::shared_mutex m_ban; + + fs::path file; + std::map, time_t>> ban_list; + +}; \ No newline at end of file diff --git a/server/mail.cpp b/server/mail.cpp index 7a2fdf9..355769d 100644 --- a/server/mail.cpp +++ b/server/mail.cpp @@ -1,5 +1,4 @@ #include "mail.h" -#include mail::mail(std::string filename, std::string subject, std::string sender) : filename(filename), diff --git a/server/server.cpp b/server/server.cpp index c51add4..b512a5d 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,3 +1,4 @@ +#include "ip_ban.h" #include "user.h" #include "user_handler.h" @@ -71,7 +72,7 @@ std::string getSha1(const std::string& p_arg); void *clientCommunication(void *data); void signalHandler(int sig); -std::string cmdLOGIN(std::vector& received, std::string& loggedInUsername); +std::string cmdLOGIN(std::vector& received, std::string& loggedInUsername, const std::string& ip); std::string cmdSEND(std::vector& received, const std::string& loggedInUsername); std::string cmdLIST(std::vector& received, const std::string& loggedInUsername); std::string cmdREAD(std::vector& received, const std::string& loggedInUsername); @@ -81,6 +82,13 @@ std::string cmdDEL(std::vector& received, const std::string& logged inline void exiting(); inline std::string read_file(std::string_view path); +struct args +{ + int socket; + std::string ip; + fs::path spool_dir; +}; + int main (int argc, char* argv[]) { if (argc < 3 || @@ -106,6 +114,8 @@ int main (int argc, char* argv[]) user_handler::getInstance().setSpoolDir(spool_dir); + ip_ban::getInstance().loadFile(spool_dir / "bans.json"); + std::atexit(exiting); char* p; @@ -181,7 +191,7 @@ int main (int argc, char* argv[]) ntohs(cliaddress.sin_port)); // clientCommunication(&new_socket); // returnValue can be ignored pthread_t tid; - pthread_create(&tid, NULL, clientCommunication, (void *)new int(new_socket)); + pthread_create(&tid, NULL, clientCommunication, static_cast(new args{new_socket, inet_ntoa(cliaddress.sin_addr), spool_dir})); threads.push_back(tid); new_socket = -1; } @@ -219,9 +229,12 @@ void printUsage() void *clientCommunication(void *data) { + args* args = (struct args*) data; + char buffer[BUF]; int size; - int *current_socket = (int *)data; + int *current_socket = &args->socket; + std::string ip = args->ip; std::string incomplete_message = ""; @@ -275,7 +288,7 @@ void *clientCommunication(void *data) switch (cmd) { case LOGIN: - response = cmdLOGIN(lines, loggedInUsername); + response = cmdLOGIN(lines, loggedInUsername, ip); break; case SEND: if (lines.size() < 5 || lines.back().compare(".") != 0) { @@ -320,7 +333,7 @@ void *clientCommunication(void *data) *current_socket = -1; } - delete(current_socket); + delete(args); return NULL; } @@ -394,7 +407,7 @@ inline void exiting() printf("Saving... \n"); } -std::string cmdLOGIN(std::vector& received, std::string& loggedInUsername) +std::string cmdLOGIN(std::vector& received, std::string& loggedInUsername, const std::string& ip) { if (received.size() < 3) { return "ERR\n"; @@ -428,8 +441,14 @@ std::string cmdLOGIN(std::vector& received, std::string& loggedInUs bindCredentials.bv_len = ldapBindPassword.length(); rc = ldap_sasl_bind_s(ldapHandle, ldapBindUser.c_str(), LDAP_SASL_SIMPLE, &bindCredentials, NULL, NULL, NULL); if (rc != LDAP_SUCCESS) { + ip_ban::getInstance().failedAttempt(received.at(1), ip); ldap_unbind_ext_s(ldapHandle, NULL, NULL); - return "ERR\n"; + return "ERR\n"; + } + + if (ip_ban::getInstance().checkBanned(ip)) { + ldap_unbind_ext_s(ldapHandle, NULL, NULL); + return "ERR\n"; } loggedInUsername = received.at(1);