commit
2dff81806a
11 changed files with 448 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||||
|
build/ |
||||||
|
.vscode/ |
||||||
|
|
||||||
|
*.swp |
@ -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) |
@ -0,0 +1,20 @@ |
|||||||
|
#ifndef _MAIL_ |
||||||
|
#define _MAIL_ |
||||||
|
|
||||||
|
#include <protocol/protocol.hpp> |
||||||
|
|
||||||
|
#include <functional> |
||||||
|
#include <vector> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
|
||||||
|
namespace mail { |
||||||
|
struct message_t { |
||||||
|
std::vector<std::string> header; |
||||||
|
std::vector<std::string> body; |
||||||
|
}; |
||||||
|
|
||||||
|
using session_map_t = std::unordered_map<protocol::session_t, message_t>; |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,69 @@ |
|||||||
|
#ifndef _PROTOCOL_ |
||||||
|
#define _PROTOCOL_ |
||||||
|
|
||||||
|
#include <protocol/token.hpp> |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include <map> |
||||||
|
#include <functional> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
namespace protocol { |
||||||
|
struct message_t { |
||||||
|
std::string input; |
||||||
|
token::pattern_t pattern; |
||||||
|
}; |
||||||
|
|
||||||
|
class protocol_t; |
||||||
|
using handler_t = std::function<void(protocol_t&, const message_t&)>; |
||||||
|
using handler_map_t = std::map<token::pattern_t, handler_t>; |
||||||
|
|
||||||
|
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<protocol::session_t> { |
||||||
|
std::size_t operator()(protocol::session_t const& session) const noexcept { |
||||||
|
auto session_hash = std::hash<std::string>{}(session.session); |
||||||
|
auto token_hash = std::hash<std::string>{}(session.token); |
||||||
|
return session_hash ^ (token_hash << 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,33 @@ |
|||||||
|
#ifndef _TOKEN_ |
||||||
|
#define _TOKEN_ |
||||||
|
|
||||||
|
#include <vector> |
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
namespace protocol::token { |
||||||
|
using string_t = std::string; |
||||||
|
using list_t = std::vector<string_t>; |
||||||
|
|
||||||
|
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<size_t, string_t> { |
||||||
|
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 |
@ -0,0 +1,12 @@ |
|||||||
|
#ifndef _UTIL_ |
||||||
|
#define _UTIL_ |
||||||
|
|
||||||
|
#include <vector> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
namespace util { |
||||||
|
std::vector<std::string> split(const std::string input, const std::string::value_type separator, size_t max_tokens = -1); |
||||||
|
std::string join(const std::vector<std::string> strings, const std::string::value_type separator); |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,50 @@ |
|||||||
|
#include <protocol/protocol.hpp> |
||||||
|
#include <mail.hpp> |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
#include <map> |
||||||
|
#include <unordered_map> |
||||||
|
#include <iostream> |
||||||
|
#include <functional> |
||||||
|
|
||||||
|
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; |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
#include <mail.hpp> |
||||||
|
|
@ -0,0 +1,77 @@ |
|||||||
|
#include <util.hpp> |
||||||
|
|
||||||
|
#include <protocol/protocol.hpp> |
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
#include <protocol/token.hpp> |
||||||
|
#include <util.hpp> |
||||||
|
|
||||||
|
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(), '|') << "<wildcard=" << pattern.wildcard << ">"; |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
#include <util.hpp> |
||||||
|
|
||||||
|
std::vector<std::string> util::split(const std::string input, const std::string::value_type separator, size_t max_tokens) { |
||||||
|
std::vector<std::string> 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<std::string> 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; |
||||||
|
} |
Loading…
Reference in new issue