From 2dff81806a7c1064ed59d5ef0ce569f2e2c0039b Mon Sep 17 00:00:00 2001 From: Thor Harald Johansen Date: Sat, 6 Jun 2020 02:43:52 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 ++ Makefile | 30 +++++++++ include/mail.hpp | 20 ++++++ include/protocol/protocol.hpp | 69 ++++++++++++++++++++ include/protocol/token.hpp | 33 ++++++++++ include/util.hpp | 12 ++++ src/bogofilter-smtpd.cpp | 50 +++++++++++++++ src/mail.cpp | 2 + src/protocol/protocol.cpp | 77 +++++++++++++++++++++++ src/protocol/token.cpp | 115 ++++++++++++++++++++++++++++++++++ src/util.cpp | 36 +++++++++++ 11 files changed, 448 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 include/mail.hpp create mode 100644 include/protocol/protocol.hpp create mode 100644 include/protocol/token.hpp create mode 100644 include/util.hpp create mode 100644 src/bogofilter-smtpd.cpp create mode 100644 src/mail.cpp create mode 100644 src/protocol/protocol.cpp create mode 100644 src/protocol/token.cpp create mode 100644 src/util.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ed279e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +.vscode/ + +*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f934c2d --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +# Makefile inspired by: +# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ + +EXEC := bogofilter-smtpd + +CXX=/usr/local/Cellar/gcc/9.3.0_1/bin/g++-9 +CXXFLAGS=-std=gnu++17 -Iinclude +CPPFLAGS=-MMD -MP + +SRCDIR := src +BUILDDIR := build + +SRCS := $(shell find src -name '*.cpp') +OBJS := $(SRCS:%=$(BUILDDIR)/%.o) +DEPS := $(OBJS:.o=.d) + +$(BUILDDIR)/$(EXEC) : $(OBJS) + $(CXX) -o $@ $^ + +run : $(BUILDDIR)/$(EXEC) + $< + +clean : + rm -R $(BUILDDIR) + +$(BUILDDIR)/%.cpp.o: %.cpp + mkdir -p $(@D) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + +-include $(DEPS) \ No newline at end of file diff --git a/include/mail.hpp b/include/mail.hpp new file mode 100644 index 0000000..5cfd067 --- /dev/null +++ b/include/mail.hpp @@ -0,0 +1,20 @@ +#ifndef _MAIL_ +#define _MAIL_ + +#include + +#include +#include +#include +#include + +namespace mail { + struct message_t { + std::vector header; + std::vector body; + }; + + using session_map_t = std::unordered_map; +} + +#endif \ No newline at end of file diff --git a/include/protocol/protocol.hpp b/include/protocol/protocol.hpp new file mode 100644 index 0000000..c13fc8c --- /dev/null +++ b/include/protocol/protocol.hpp @@ -0,0 +1,69 @@ +#ifndef _PROTOCOL_ +#define _PROTOCOL_ + +#include + +#include +#include +#include +#include + +namespace protocol { + struct message_t { + std::string input; + token::pattern_t pattern; + }; + + class protocol_t; + using handler_t = std::function; + using handler_map_t = std::map; + + class protocol_t { + public: + void set_handlers(const handler_map_t handlers); + void absorb(const std::string& input); + void send(const token::list_t& tokens); + + protected: + virtual void emit(const std::string& output) { }; + + private: + handler_map_t handlers; + size_t max_pattern_length; + }; + + class ios_protocol_t : public protocol_t { + public: + ios_protocol_t(std::istream& in, std::ostream& out); + void run(); + + protected: + virtual void emit(const std::string& output); + + private: + std::istream& in; + std::ostream& out; + }; + + struct session_t { + std::string session; + std::string token; + }; + + bool operator==(const session_t& l, const session_t& r); + + session_t session_from(token::list_t tokens); + session_t session_from(message_t message); +} + +namespace std { + template<> struct hash { + std::size_t operator()(protocol::session_t const& session) const noexcept { + auto session_hash = std::hash{}(session.session); + auto token_hash = std::hash{}(session.token); + return session_hash ^ (token_hash << 1); + } + }; +} + +#endif \ No newline at end of file diff --git a/include/protocol/token.hpp b/include/protocol/token.hpp new file mode 100644 index 0000000..6a39133 --- /dev/null +++ b/include/protocol/token.hpp @@ -0,0 +1,33 @@ +#ifndef _TOKEN_ +#define _TOKEN_ + +#include +#include +#include +#include + +namespace protocol::token { + using string_t = std::string; + using list_t = std::vector; + + std::string compose(const list_t& tokens); + list_t decompose(const std::string& input, size_t max_tokens = -1); + + class pattern_t : public std::map { + private: + bool wildcard; + + public: + pattern_t(); + pattern_t(list_t tokens, bool wildcard = false); + pattern_t(std::string input, bool wildcard = true); + + list_t token_list() const; + + friend std::ostream& operator<<(typeof(std::cout)& out, const pattern_t& pattern); + friend bool operator<(const pattern_t& l, const pattern_t& r); + friend bool operator==(const pattern_t& l, const pattern_t& r); + }; +} + +#endif \ No newline at end of file diff --git a/include/util.hpp b/include/util.hpp new file mode 100644 index 0000000..887ee12 --- /dev/null +++ b/include/util.hpp @@ -0,0 +1,12 @@ +#ifndef _UTIL_ +#define _UTIL_ + +#include +#include + +namespace util { + std::vector split(const std::string input, const std::string::value_type separator, size_t max_tokens = -1); + std::string join(const std::vector strings, const std::string::value_type separator); +}; + +#endif \ No newline at end of file diff --git a/src/bogofilter-smtpd.cpp b/src/bogofilter-smtpd.cpp new file mode 100644 index 0000000..6f06a5d --- /dev/null +++ b/src/bogofilter-smtpd.cpp @@ -0,0 +1,50 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +const bool debug = true; + +using namespace std; + +mail::session_map_t mail_messages; + +void on_ready(protocol::protocol_t& protocol, protocol::message_t message) { + debug && cerr << "Ready" << endl; + protocol.send({"register", "filter", "smtp-in", "data"}); + protocol.send({"register", "filter", "smtp-in", "data-line"}); + protocol.send({"register", "filter", "smtp-in", "commit"}); + protocol.send({"register", "ready"}); +} + +void on_data(protocol::protocol_t& protocol, protocol::message_t message) { + mail_messages.emplace(protocol::session_from(message), mail::message_t{}); +} + +void on_dataline(protocol::protocol_t& protocol, protocol::message_t message) { + debug && cerr << "Dataline" << endl; +} + +void on_commit(protocol::protocol_t& protocol, protocol::message_t message) { + +} + +int main(int argc, char** argv) { + protocol::ios_protocol_t protocol(cin, cout); + + protocol::handler_map_t handlers; + handlers.emplace("config|ready", on_ready); + handlers.emplace("filter|*|*|smtp-in|data", on_data); + handlers.emplace("filter|*|*|smtp-in|data-line", on_dataline); + handlers.emplace("filter|*|*|smtp-in|commit", on_commit); + protocol.set_handlers(handlers); + + protocol.run(); + + return 0; +} \ No newline at end of file diff --git a/src/mail.cpp b/src/mail.cpp new file mode 100644 index 0000000..b3ebfdb --- /dev/null +++ b/src/mail.cpp @@ -0,0 +1,2 @@ +#include + diff --git a/src/protocol/protocol.cpp b/src/protocol/protocol.cpp new file mode 100644 index 0000000..e017748 --- /dev/null +++ b/src/protocol/protocol.cpp @@ -0,0 +1,77 @@ +#include + +#include + +#include +#include + +using namespace std; +using namespace util; + + //config|smtpd-version|6.6.1 + //config|smtp-session-timeout|300 + //config|subsystem|smtp-in + +const bool debug = true; + +namespace protocol { + void protocol_t::set_handlers(const handler_map_t handlers) { + size_t length = 0; + for(auto&& [pattern, handler] : handlers) { + for(auto&& [index, token] : pattern) { + if(index + 1 > length) { + length = index + 1; + } + } + } + debug && cerr << "Maximum handler pattern length is " << length << endl; + + this->handlers = handlers; + max_pattern_length = length; + } + + void protocol_t::send(const token::list_t& tokens) { + this->emit(token::compose(tokens)); + } + + void protocol_t::absorb(const std::string& input) { + auto tokens = token::decompose(input, max_pattern_length); + auto pattern = token::pattern_t(tokens, false); + debug && cout << "Received pattern " << pattern << endl; + + auto element = handlers.find(pattern); + if(element != handlers.end()) { + element->second(*this, {input, element->first}); + } + } + + ios_protocol_t::ios_protocol_t(std::istream& in, std::ostream& out) : + in(in), out(out) { } + + void ios_protocol_t::emit(const std::string& output) { + out << output << endl; + } + + void ios_protocol_t::run() { + while(!in.eof()) { + string line; + std::getline(in, line); + absorb(line); + } + } + + bool operator==(const session_t& l, const session_t& r) { + return l.session == r.session && l.token == r.token; + } + + session_t session_from(token::list_t tokens) { + auto session_id = tokens.at(5); + auto session_token = tokens.at(6); + return {session_id, session_token}; + } + + session_t session_from(message_t message) { + auto tokens = token::decompose(message.input); + return session_from(tokens); + } +} diff --git a/src/protocol/token.cpp b/src/protocol/token.cpp new file mode 100644 index 0000000..e870e93 --- /dev/null +++ b/src/protocol/token.cpp @@ -0,0 +1,115 @@ +#include +#include + +using namespace std; +using namespace util; + +const bool debug = false; + +namespace protocol::token { + + std::string compose(const list_t& tokens) { + return join(tokens, '|'); + } + + list_t decompose(const std::string& input, size_t max_tokens) { + return split(input, '|', max_tokens); + } + + pattern_t::pattern_t() {} + + pattern_t::pattern_t(list_t tokens, bool wildcard) : wildcard(wildcard) { + for(auto index = 0; index < tokens.size(); ++index) { + auto token = tokens[index]; + if(!wildcard || token != "*") { + this->emplace(index, tokens[index]); + } + } + } + + pattern_t::pattern_t(string input, bool wildcard) : pattern_t(split(input, '|'), wildcard) {} + + list_t pattern_t::token_list() const { + list_t output; + for(auto element = begin(); element != end(); ++element) { + output.push_back(element->second); + } + return output; + } + + ostream& operator<<(typeof(cout)& out, const pattern_t& pattern) { + return out << join(pattern.token_list(), '|') << ""; + } + + bool operator<(const pattern_t& l, const pattern_t& r) { + debug && cerr << "Checking if " << l << " < " << r << endl; + + if(l == r) { + debug && cerr << "Not less because left equals right" << endl; + return false; + } + + if(l.size() < r.size()) { + debug && cerr << "Less because right has more elements" << endl; + return true; + } + + for(auto le = l.begin(); le != l.end(); ++le) { + if(r.count(le->first) == 0) { + debug && cerr << "Not less because right is missing " << le->first << " -> " << le->second << endl; + return false; + } + + if(le->second < r.at(le->first)) { + debug && cerr << "Not less because " << le->second << " < " << r.at(le->first) << endl; + return true; + } + } + + return false; + } + + bool operator==(const pattern_t& l, const pattern_t& r) { + debug && cerr << "Checking if " << l << " == " << r << endl; + + const pattern_t *subset, *superset; + if(r.wildcard == l.wildcard) { + debug && cerr << "Left and right are both (not) wildcards" << endl; + if(l.size() != r.size()) { + debug && cerr << "Not equal because sizes are unequal" << endl; + return false; + } else { + subset = &l; + superset = &r; + } + } else if(l.wildcard) { + debug && cerr << "Left is wildcard" << endl; + subset = &l; + superset = &r; + } else { + debug && cerr << "Right is wildcard" << endl; + subset = &r; + superset = &l; + } + + for(auto le : *subset) { + if(superset->count(le.first) == 0) { + debug && cerr << "Not equal because " << le.first << " -> " << le.second << " is missing in superset" << endl; + return false; + } + + auto rv = superset->at(le.first); + + if(rv != le.second) { + debug && cerr << "Not equal because " << le.first << " -> " << le.second << " != " << rv << " in superset" << endl; + return false; + } + + debug && cerr << le.first << " -> " << le.second << " == " << rv << " in superset" << endl; + } + + debug && cerr << "Patterns match" << endl; + + return true; + } +} \ No newline at end of file diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..84e22fa --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,36 @@ +#include + +std::vector util::split(const std::string input, const std::string::value_type separator, size_t max_tokens) { + std::vector tokens; + + for(auto index = 0, last = -1; index != max_tokens; ++index) { + auto current = input.find(separator, last + 1); + auto eol = current == std::string::npos; + + if(eol || index == max_tokens) { + current = input.length(); + } + + auto length = current - last - 1; + auto token = input.substr(last + 1, length); + tokens.push_back(token); + last = current; + + if(eol) { + break; + } + } + + return tokens; +} + +std::string util::join(const std::vector strings, const std::string::value_type separator) { + std::string output; + for(size_t index = 0; index < strings.size(); ++index) { + output.append(strings.at(index)); + if(index + 1 < strings.size()) { + output.push_back(separator); + } + } + return output; +} \ No newline at end of file