Compare commits

...

2 Commits

  1. 2
      Makefile
  2. 80
      bogofilter-smtpd.code-workspace
  3. 56
      include/log.hpp
  4. 15
      include/log/common.hpp
  5. 48
      include/log/internal.hpp
  6. 34
      include/mail.hpp
  7. 68
      include/protocol/protocol.hpp
  8. 33
      include/protocol/token.hpp
  9. 22
      include/smtpd/iosprotocol.hpp
  10. 17
      include/smtpd/mail.hpp
  11. 23
      include/smtpd/message.hpp
  12. 47
      include/smtpd/protocol.hpp
  13. 27
      include/smtpd/request.hpp
  14. 15
      include/smtpd/session.hpp
  15. 14
      include/smtpd/smtpd.hpp
  16. 32
      include/smtpd/token.hpp
  17. 17
      include/smtpd/types.hpp
  18. 132
      include/sparsevector.hpp
  19. 16
      include/util.hpp
  20. 13
      protocol-test.txt
  21. 128
      src/bogofilter-smtpd.cpp
  22. 29
      src/log.cpp
  23. 139
      src/protocol/protocol.cpp
  24. 111
      src/protocol/token.cpp
  25. 24
      src/smtpd/iosprotocol.cpp
  26. 17
      src/smtpd/message.cpp
  27. 108
      src/smtpd/protocol.cpp
  28. 36
      src/smtpd/request.cpp
  29. 5
      src/smtpd/session.cpp
  30. 35
      src/smtpd/token.cpp
  31. 55
      src/util.cpp

@ -3,7 +3,7 @@
EXEC := bogofilter-smtpd
CXXFLAGS=-std=gnu++17 -Iinclude
CXXFLAGS=-std=gnu++17 -Iinclude -Wall -Wextra -Wunused -g
CPPFLAGS=-MMD -MP
SRCDIR := src

@ -0,0 +1,80 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.associations": {
"__debug": "cpp",
"__locale": "cpp",
"algorithm": "cpp",
"cstddef": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"ios": "cpp",
"iterator": "cpp",
"limits": "cpp",
"list": "cpp",
"locale": "cpp",
"memory": "cpp",
"streambuf": "cpp",
"string": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"map": "cpp",
"variant": "cpp",
"iosfwd": "cpp",
"vector": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"set": "cpp",
"unordered_map": "cpp",
"exception": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"slist": "cpp",
"fstream": "cpp",
"iomanip": "cpp",
"iostream": "cpp",
"istream": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"any": "cpp",
"unordered_set": "cpp"
},
"C_Cpp.loggingLevel": "Error"
}
}

@ -0,0 +1,56 @@
#ifndef _LOG_
#define _LOG_
#include <log/common.hpp>
#include <log/internal.hpp>
#include <util.hpp>
namespace log {
void setLevel(Level toLevel);
// Grouping by indentation
void open();
void close();
template <typename... Ts>
void log(Level atLevel, const char * format, const Ts & ... args) {
if(atLevel >= internal::level) {
std::cerr << "[" << internal::LEVEL_LABELS.at(atLevel) << "] ";
internal::printFormatted(util::lpad(format, internal::group).c_str(), args...);
std::cerr << std::endl;
}
}
// Library debug
template <typename... Ts>
void ldebug(const Ts & ... args) {
log(LDEBUG, args...);
}
template <typename... Ts>
void debug(const Ts & ... args) {
log(DEBUG, args...);
}
template <typename... Ts>
void info(const Ts & ... args) {
log(INFO, args...);
}
template <typename... Ts>
void warning(const Ts &... args) {
log(WARNING, args...);
}
template <typename... Ts>
void error(const Ts & ... args) {
log(ERROR, args...);
}
template <typename... Ts>
void critical(const Ts & ... args) {
log(CRITICAL, args...);
}
}
#endif

@ -0,0 +1,15 @@
#ifndef _LOG_COMMON_
#define _LOG_COMMON_
namespace log {
using Level = int;
const Level LDEBUG = 50;
const Level DEBUG = 100;
const Level INFO = 200;
const Level WARNING = 300;
const Level ERROR = 400;
const Level CRITICAL = 500;
}
#endif

@ -0,0 +1,48 @@
#ifndef _LOG_INTERNAL_
#define _LOG_INTERNAL_
// Internal formatting functions
#include <log/common.hpp>
#include <unordered_map>
#include <iostream>
namespace log {
namespace internal {
extern Level level;
extern int group;
const std::unordered_map<Level, std::string> LEVEL_LABELS {
{ LDEBUG, "LDB" },
{ DEBUG, "DEB" },
{ INFO, "INF" },
{ WARNING, "WAR" },
{ ERROR, "ERR" },
{ CRITICAL, "CRI" }
};
void printFormatted(const char * format);
template<typename T, typename... Ts>
void printFormatted(const char * format, const T & first, const Ts & ... rest) {
// Iterate characters in (rest of) format string
for (; *format != '\0'; ++format) {
// Check for placeholder character
if (*format == '%') {
// Output first parameter
std::cerr << first;
// Tail recurseion with rest of string and parameters
printFormatted(format + 1, rest...);
return;
}
// If not found, output unformatted character
std::cerr << *format;
}
}
}
}
#endif

@ -1,34 +0,0 @@
#ifndef _MAIL_
#define _MAIL_
#include <protocol/protocol.hpp>
#include <functional>
#include <vector>
#include <string>
#include <unordered_map>
namespace mail {
namespace status {
const int SPAM = 0;
const int HAM = 1;
const int UNSURE = 2;
const int ERROR = 3;
}
struct message_t {
std::vector<std::string> header;
std::vector<std::string> body;
};
struct session_t {
bool has_header = 0;
protocol::token::string_t token;
message_t message;
int status = status::UNSURE;
};
using session_map_t = std::unordered_map<protocol::session_key_t, session_t>;
}
#endif

@ -1,68 +0,0 @@
#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>;
using session_key_t = token::string_t;
session_key_t session_key_from(const token::list_t& tokens);
session_key_t session_key_from(const message_t& message);
token::string_t session_token_from(const token::list_t& tokens);
token::string_t session_token_from(const message_t& message);
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);
// TODO: Move these methods to a separate session_t class!
void send_result(const session_key_t& session_key, const token::string_t& session_token, token::list_t tokens);
void proceed(const session_key_t& session_key, const token::string_t& session_token);
void junk(const session_key_t& session_key, const token::string_t& session_token);
void fail(const session_key_t& session_key, const token::string_t& session_token);
void submit_message(const session_key_t& session_key, const token::string_t& session_token, const std::vector<std::string>& header, const std::vector<std::string>& body);
protected:
virtual void emit(const std::string& output) { };
private:
handler_map_t handlers;
size_t max_pattern_length;
std::string version;
void send_data_line(const session_key_t& session_key, const token::string_t& session_token, const std::string& data_line);
};
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;
};
}
#endif

@ -1,33 +0,0 @@
#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,22 @@
#ifndef _SMTPD_IOS_PROTOCOL_
#define _SMTPD_IOS_PROTOCOL_
#include <smtpd/protocol.hpp>
#include <smtpd/types.hpp>
namespace smtpd {
class IOSProtocol : public Protocol {
public:
IOSProtocol(std::istream & in, std::ostream & out);
void run();
protected:
virtual void emit(std::string output);
private:
std::istream & in;
std::ostream & out;
};
}
#endif

@ -0,0 +1,17 @@
#ifndef _SMTPD_MAIL_
#define _SMTPD_MAIL_
#include <smtpd/types.hpp>
#include <vector>
#include <string>
namespace smtpd {
struct Mail {
bool hasHeader = false;
std::vector<std::string> header;
std::vector<std::string> body;
};
};
#endif

@ -0,0 +1,23 @@
#ifndef _SMTPD_MESSAGE_
#define _SMTPD_MESSAGE_
#include <smtpd/token.hpp>
namespace smtpd {
class Message {
public:
Message(std::string input, TokenPattern pattern);
// Raw protocol string
const std::string input;
// TokenPattern that matched this message
const TokenPattern pattern;
// Retrieve message metadata
SessionID sessionID() const;
RequestToken requestToken() const;
};
}
#endif

@ -0,0 +1,47 @@
#ifndef _SMTPD_PROTOCOL_
#define _SMTPD_PROTOCOL_
#include <smtpd/session.hpp>
#include <smtpd/message.hpp>
#include <smtpd/token.hpp>
#include <smtpd/types.hpp>
#include <string>
#include <memory>
#include <functional>
#include <map>
#include <any>
namespace smtpd {
class Protocol;
class Request;
using ProtocolListener = std::function<void(Protocol & protocol, const Message & message)>;
using SessionMap = std::unordered_map<SessionID, Session>;
class Protocol {
public:
std::string version;
Protocol();
void addListener(const TokenPattern & pattern, ProtocolListener listener);
void send(const TokenPattern & pattern);
SessionMap sessions;
protected:
virtual void emit(std::string output) = 0;
void absorb(std::string input);
private:
std::multimap<TokenPattern, ProtocolListener> listeners;
static void configListener(Protocol & protocol, const Message & message);
static void sessionListener(Protocol & protocol, const Message & message);
static void requestListener(Protocol & protocol, const Message & message);
};
}
#endif

@ -0,0 +1,27 @@
#include <smtpd/mail.hpp>
#include <smtpd/types.hpp>
#include <smtpd/token.hpp>
#include <any>
#include <functional>
namespace smtpd {
class Request {
public:
void proceed();
void junk();
void fail(std::string response = "451 Aborted");
void commit();
void sendFilterResult(TokenPattern pattern);
void submitMail(const Mail & mail);
std::any payload;
std::function<void(Token command, TokenPattern arguments)> send;
private:
void submitMailSection(const std::vector<std::string> & lines, bool isBody);
void submitDataLine(std::string line, bool inBody);
};
}

@ -0,0 +1,15 @@
#include <smtpd/request.hpp>
#include <smtpd/types.hpp>
#include <functional>
namespace smtpd {
using RequestMap = std::unordered_map<RequestToken, Request>;
class Session {
public:
RequestMap requests;
std::function<void(Token command, Token requestID, TokenPattern arguments)> send;
};
}

@ -0,0 +1,14 @@
#ifndef _SMTPD_
#define _SMTPD_
/* In this file, and the library in general, headers are always in reverse order
* of dependence. This helps weed out missing includes to some degree. */
#include <smtpd/iosprotocol.hpp>
#include <smtpd/protocol.hpp>
#include <smtpd/message.hpp>
#include <smtpd/mail.hpp>
#include <smtpd/token.hpp>
#include <smtpd/types.hpp>
#endif

@ -0,0 +1,32 @@
#ifndef _SMTPD_TOKEN_
#define _SMTPD_TOKEN_
#include <smtpd/types.hpp>
#include <sparsevector.hpp>
#include <vector>
#include <map>
#include <string>
#include <iostream>
#include <memory>
namespace smtpd {
class TokenPattern : public sparse_vector<Token> {
public:
TokenPattern();
TokenPattern(const TokenPattern & pattern);
TokenPattern(const std::vector<std::string> & input, bool wildcard = false);
TokenPattern(const std::string & input, bool wildcard = false);
TokenPattern(std::initializer_list<Token> list);
operator std::string() const;
private:
bool wildcard;
};
bool operator<(const TokenPattern & left, const TokenPattern & right);
std::ostream & operator<<(std::ostream & os, const TokenPattern & pattern);
}
#endif

@ -0,0 +1,17 @@
#ifndef _SMTPD_TYPES_
#define _SMTPD_TYPES_
#include <vector>
#include <string>
#include <memory>
#include <functional>
namespace smtpd {
using Token = std::string;
using SessionID = std::string;
using RequestToken = std::string;
}
#endif

@ -0,0 +1,132 @@
#include <vector>
#include <map>
#include <iterator>
template <class T>
class sparse_vector {
std::map<size_t, T> map;
size_t _size = 0;
public:
const T empty;
template <class U>
class iterator : public std::iterator<std::input_iterator_tag, U> {
sparse_vector<U> & vector;
size_t index;
public:
iterator(sparse_vector<U> & vector, size_t index) : vector(vector), index(index) {}
iterator(const iterator<U> & from) : vector(from.vector), index(from.index) {}
// Prefix increment operator
iterator<U> & operator++() {
++index;
return *this;
}
// Postfix increment operator
iterator<U> operator++(int) {
iterator<U> tmp(*this);
operator++();
return tmp;
}
bool operator==(const iterator<U> & rhs) const {
return index == rhs.index;
}
bool operator!=(const iterator<U> & rhs) const {
return index != rhs.index;
}
U & operator*() const {
return vector.at(index);
}
};
sparse_vector() {}
// Construct from element list given as arguments
sparse_vector(std::initializer_list<T> list) {
size_t index = 0;
for(auto element : list) {
map[index] = element;
}
}
// Copy constructor
sparse_vector(const sparse_vector<T> & svector) {
map = svector.map;
_size = svector._size;
}
// Move constructor
sparse_vector(sparse_vector<T> && svector) {
map = std::move(svector.map);
_size = svector._size;
svector._size = 0;
}
// Construct from regular vector
sparse_vector(const std::vector<T> vector) {
for(size_t index = 0; index < vector.size(); ++index) {
map[index] = vector.at(index);
}
_size = vector.size();
}
// Move assignment operator
sparse_vector<T> & operator=(sparse_vector<T> && svector) {
map = std::move(svector.map);
size = svector._size;
svector._size = 0;
return *this;
}
size_t size() const {
return _size;
}
const T & at(size_t index) const {
return map.at(index);
}
bool has(size_t index) const {
return map.count(index) == 1;
}
void push_back(const T & element) {
map[_size++] = element;
}
template <class Range>
void push_back(Range range) {
for(T & element : range) {
push_back(element);
}
}
T pop_back() {
--_size;
if(has(_size)) {
T element = map->at(_size);
map.erase(_size);
return element;
}
return empty;
}
iterator<T> begin() const {
return iterator<T>(*this, 0);
}
iterator<T> end() const {
return iterator<T>(*this, size());
}
T & operator[](size_t index) {
return map[index];
}
};

@ -5,8 +5,20 @@
#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);
const std::vector<std::string> split(const std::string input, const std::string::value_type separator, const size_t max_tokens = -1);
const std::string lpad(const std::string input, const int length = 1, const std::string str = " ");
template <class RangeT>
const std::string join(RangeT & range, const std::string::value_type separator) {
std::string output;
for(auto element = range.begin(); element != range.end(); ++element) {
if(element != range.begin()) {
output.push_back(separator);
}
output.append(*element);
}
return output;
}
};
#endif

@ -0,0 +1,13 @@
filter|0.5|1576146008.006099|smtp-in|data|a9e57d0a1a1eca0d|faec7151cc9fa53b
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|From: <test@test.com>
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|To: <thj@thj.no>
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|Subject: Hello, World!
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|This is a message to you.
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|..
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|It contains nothing important.
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|Regards,
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|Mr. Test
filter|0.5|1591207508.350020|smtp-in|data-line|a9e57d0a1a1eca0d|faec7151cc9fa53b|.
filter|0.5|1591207508.350020|smtp-in|commit|a9e57d0a1a1eca0d|faec7151cc9fa53b

@ -1,5 +1,5 @@
#include <protocol/protocol.hpp>
#include <mail.hpp>
#include <smtpd/smtpd.hpp>
#include <log.hpp>
#include <functional>
#include <string>
@ -13,37 +13,31 @@ const bool debug = false;
using namespace std;
mail::session_map_t sessions;
smtpd::SessionMap sessions;
void on_ready(protocol::protocol_t& protocol, protocol::message_t message) {
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_ready(smtpd::PProtocol pProtocol, smtpd::Message message) {
cerr << "Ready to accept messages from SMTPD" << endl;
}
void on_data(protocol::protocol_t& protocol, protocol::message_t message) {
void on_data(const smtpd::PMessage message) {
cerr << "Accepting new message from SMTPD" << endl;
auto session_key = protocol::session_key_from(message);
auto session_token = protocol::session_token_from(message);
sessions.emplace(session_key, mail::session_t{});
protocol.proceed(session_key, session_token);
smtpd::PSession session = make_shared<smtpd::Session>(message->protocol, message->sessionID(), message->sessionToken());
sessions.insert({message->sessionID(), session});
session->proceed(true);
}
void on_data_line(protocol::protocol_t& protocol, protocol::message_t message) {
void on_data_line(const smtpd::PMessage message) {
debug && cerr << "Received data line from SMTPD" << endl;
auto tokens = protocol::token::decompose(message.input);
auto session_key = protocol::session_key_from(tokens);
auto session_token = protocol::session_token_from(tokens);
auto tokens = smtpd::split(message->input);
try {
auto& session = sessions.at(session_key);
auto& session = sessions.at(message->sessionID());
auto& data_line = tokens.at(7);
if(!session.has_header) {
if(!session->mail.hasHeader) {
if(data_line == "") {
session.has_header = true;
session->mail.hasHeader = true;
return;
}
@ -59,99 +53,83 @@ void on_data_line(protocol::protocol_t& protocol, protocol::message_t message) {
cerr << data_line << endl;
}
session.message.header.push_back(data_line);
session->mail.header.push_back(data_line);
return;
}
if(data_line == ".") {
FILE* f = popen("bogofilter -v 1>&2", "w");
for(string& line : session.message.header) {
FILE* f = popen("true", "w");
//FILE* f = popen("bogofilter -v 1>&2", "w");
for(string& line : session->mail.header) {
fprintf(f, "%s\n", line.c_str());
}
fwrite("\n", 1, 1, f);
for(string& line : session.message.body) {
for(string& line : session->mail.body) {
fprintf(f, "%s\n", line.c_str());
}
int status = pclose(f);
session.status = WEXITSTATUS(status);
if(session.status == 0) {
session.message.header.push_back("X-Spam: Yes");
switch(WEXITSTATUS(status)) {
case 0:
debug && cerr << "Classified as spam" << endl;
session->mail.header.push_back("X-Spam: Yes");
session->proceed(false);
break;
case 1:
case 2:
debug && cerr << "Classified as ham or unsure" << endl;
session->proceed(false);
break;
case 3:
debug && cerr << "Bogofilter error" << endl;
session->fail(false);
return;
}
protocol.submit_message(session_key, session_token, session.message.header, session.message.body);
session->submit_mail(session->mail.header, session->mail.body);
return;
}
if(data_line == "..") {
session.message.body.push_back(".");
session->mail.body.push_back(".");
return;
}
session.message.body.push_back(data_line);
session->mail.body.push_back(data_line);
} catch(out_of_range& e) {
cerr << "Unknown session key from SMTPD" << endl;
protocol.fail(session_key, session_token);
smtpd::Session{message->protocol, message->sessionID(), message->sessionToken()}.fail(false);
}
}
void on_commit(protocol::protocol_t& protocol, protocol::message_t message) {
void on_commit(smtpd::PMessage message) {
debug && cerr << "Commit request from SMTPD" << endl;
auto session_key = protocol::session_key_from(message);
auto session_token = protocol::session_token_from(message);
try {
auto& session = sessions.at(session_key);
/*
switch(session.status) {
case mail::status::SPAM:
debug && cerr << "Classified as spam" << endl;
protocol.junk(session_key, session_token);
break;
case mail::status::HAM:
case mail::status::UNSURE:
debug && cerr << "Classified as ham or unsure" << endl;
protocol.proceed(session_key, session_token);
break;
case mail::status::ERROR:
debug && cerr << "Bogofilter error" << endl;
protocol.fail(session_key, session_token);
break;
}
*/
protocol.proceed(session_key, session_token);
auto& session = sessions.at(message->sessionID());
session->proceed(true);
sessions.erase(message->sessionID());
} catch(out_of_range& e) {
cerr << "Unknown session key from SMTPD" << endl;
protocol.fail(session_key, session_token);
smtpd::Session{message->protocol, message->sessionID(), message->sessionToken()}.fail(true);
return;
}
sessions.erase(session_key);
}
*/
int main(int argc, char** argv) {
protocol::ios_protocol_t protocol(cin, cout);
protocol::handler_map_t handlers;
//int main(int argc, char** argv) {
int main() {
log::setLevel(log::DEBUG);
handlers.emplace("config|ready", on_ready);
handlers.emplace("filter|*|*|smtp-in|data", on_data);
handlers.emplace("filter|*|*|smtp-in|data-line", on_data_line);
handlers.emplace("filter|*|*|smtp-in|commit", on_commit);
if(debug) {
cerr << "Handler patterns:" << endl;
for(auto&& [first, second] : handlers) {
cerr << "\t" << first << endl;
}
}
smtpd::IOSProtocol protocol{cin, cout};
cerr << "Bogofilter-SMTPD started" << endl;
protocol.set_handlers(handlers);
protocol.run();
cerr << "Bogofilter-SMTP stopped" << endl;
return 0;
}

@ -0,0 +1,29 @@
#include <log.hpp>
#include <string>
#include <iostream>
using namespace std;
namespace log::internal {
Level level = INFO;
int group = 0;
void printFormatted(const char * format) {
cerr << format;
}
}
namespace log {
void open() {
++internal::group;
}
void close() {
--internal::group;
}
void setLevel(Level toLevel) {
internal::level = toLevel;
}
}

@ -1,139 +0,0 @@
// OpenSMTPD filter protocol documentation:
// https://man7.org/linux/man-pages/man7/smtpd-filters.7.html
#include <util.hpp>
#include <protocol/protocol.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace util;
const bool debug = false;
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;
}
}
}
max_pattern_length = length;
debug && cerr << "Maximum handler pattern length is " << length << endl;
this->handlers = handlers;
}
void protocol_t::send(const token::list_t& tokens) {
debug && cerr << "Sent pattern " << protocol::token::pattern_t(tokens) << endl;
this->emit(token::compose(tokens));
}
void protocol_t::absorb(const std::string& input) {
auto tokens = token::decompose(input, debug ? -1 : max_pattern_length);
auto pattern = token::pattern_t(tokens, false);
debug && cerr << "Received pattern " << pattern << endl;
if(tokens.size() >= 2 && tokens.at(0) == "filter") {
version = tokens.at(1);
}
auto element = handlers.find(pattern);
if(element == handlers.end()) {
debug && cerr << "Unknown pattern " << pattern << endl;
} else {
element->second(*this, {input, element->first});
}
}
void protocol_t::send_result(const session_key_t& session_key, const token::string_t& session_token, token::list_t tokens) {
token::list_t result{"filter-result"};
if(version < "0.5") {
result.insert(result.end(), {session_token, session_key});
} else {
result.insert(result.end(), {session_key, session_token});
}
for(auto& token : tokens) {
result.push_back(token);
}
send(result);
}
void protocol_t::proceed(const session_key_t& session_key, const token::string_t& session_token) {
send_result(session_key, session_token, {"proceed"});
}
void protocol_t::junk(const session_key_t& session_key, const token::string_t& session_token) {
send_result(session_key, session_token, {"junk"});
}
void protocol_t::fail(const session_key_t& session_key, const token::string_t& session_token) {
send_result(session_key, session_token, {"disconnect", "451 Aborted"});
}
void protocol_t::send_data_line(const session_key_t& session_key, const token::string_t& session_token, const string& data_line) {
if(version < "0.5") {
send({"filter-dataline", session_token, session_key, data_line});
} else {
send({"filter-dataline", session_key, session_token, data_line});
}
}
void protocol_t::submit_message(const session_key_t& session_key, const token::string_t& session_token, const vector<string>& header, const vector<string>& body) {
for(auto& data_line : header) {
send_data_line(session_key, session_token, data_line);
}
send_data_line(session_key, session_token, "");
for(auto& data_line : body) {
if(data_line == ".") {
send_data_line(session_key, session_token, "..");
continue;
}
send_data_line(session_key, session_token, data_line);
}
send_data_line(session_key, session_token, ".");
}
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);
}
cerr << "EOF reached" << endl;
}
session_key_t session_key_from(const token::list_t& tokens) {
return tokens.at(5);
}
session_key_t session_key_from(const message_t& message) {
auto tokens = token::decompose(message.input, 6);
return session_key_from(tokens);
}
token::string_t session_token_from(const token::list_t& tokens) {
return tokens.at(6);
}
token::string_t session_token_from(const message_t& message) {
auto tokens = token::decompose(message.input, 7);
return session_token_from(tokens);
}
}

@ -1,111 +0,0 @@
#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 << endl;
debug && cerr << "Checking if " << l << " < " << r << endl;
if(l == r) {
debug && cerr << "Not less because left equals right" << endl;
return false;
}
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,24 @@
#include <smtpd/iosprotocol.hpp>
#include <log.hpp>
#include <iostream>
using namespace std;
namespace smtpd {
IOSProtocol::IOSProtocol(std::istream & in, std::ostream & out) :
in(in), out(out) { }
void IOSProtocol::emit(string output) {
out << output << endl;
}
void IOSProtocol::run() {
while(!in.eof()) {
string line;
std::getline(in, line);
absorb(line);
}
cerr << "EOF reached" << endl;
}
}

@ -0,0 +1,17 @@
#include <smtpd/message.hpp>
#include <smtpd/token.hpp>
using namespace std;
namespace smtpd {
Message::Message(string input, TokenPattern pattern) :
input(input), pattern(pattern) { }
SessionID Message::sessionID() const {
return pattern.at(5);
}
RequestToken Message::requestToken() const {
return pattern.at(6);
}
}

@ -0,0 +1,108 @@
// OpenSMTPD filter protocol documentation:
// https://man7.org/linux/man-pages/man7/smtpd-filters.7.html
#include <util.hpp>
#include <log.hpp>
#include <smtpd/protocol.hpp>
#include <smtpd/message.hpp>
#include <smtpd/types.hpp>
#include <iostream>
#include <set>
using namespace std;
using namespace util;
namespace smtpd {
Protocol::Protocol() {
addListener({"config|ready"}, Protocol::configListener);
addListener({"filter|"}, Protocol::sessionListener);
addListener({"filter|*|*|smtp-in"}, Protocol::requestListener);
}
void Protocol::addListener(const TokenPattern & pattern, ProtocolListener listener) {
log::ldebug("Adding passed-as-arguments listener for pattern %", pattern);
listeners.insert({pattern, listener});
listeners.insert(std::pair<TokenPattern, ProtocolListener>{pattern, listener});
}
void Protocol::send(const TokenPattern & pattern) {
log::ldebug("Sending pattern: %", pattern);
this->emit(pattern);
}
void Protocol::absorb(string input) {
TokenPattern pattern(input);
log::ldebug("Received pattern: %", pattern);
if(pattern.size() >= 2 && pattern.at(0) == "filter") {
version = pattern.at(1);
}
auto range = listeners.equal_range(pattern);
if(range.first == range.second) {
log::error("Unsupported message: %", pattern);
return;
}
for_each(range.first, range.second, [this, input](const auto & listenerPair) {
log::ldebug("Matched listener for: %", toString(listenerPair.first));
listenerPair.second(*this, Message{input, listenerPair.first});
});
}
void Protocol::configListener(Protocol & protocol, const Message & message) {
// Find unique SMTPD filters in listener map
set<TokenPattern> uniqueFilters;
for(auto&& [pattern, listener] : protocol.listeners) {
log::ldebug("Found listener for: %", pattern);
if(pattern.at(0) == "filter" && pattern.has(3) && pattern.has(4) > 0) {
uniqueFilters.insert({"register", "filter", pattern.at(3), pattern.at(4)});
continue;
}
}
// Register filters with SMTPD
for(TokenPattern message : uniqueFilters) {
protocol.send(message);
}
protocol.send({"register", "ready"});
}
void Protocol::sessionListener(Protocol & protocol, const Message & message) {
auto sessionID = message.sessionID();
if(protocol.sessions.count(sessionID) == 0) {
auto & session = protocol.sessions[sessionID];
session.send = [&protocol, &sessionID](Token command, RequestToken requestToken, TokenPattern arguments) {
TokenPattern pattern = {command};
if(protocol.version < "0.5") {
pattern.push_back(requestToken);
pattern.push_back(sessionID);
} else {
pattern.push_back(sessionID);
pattern.push_back(requestToken);
}
for(auto & argument : arguments) {
pattern.push_back(argument);
}
protocol.send(pattern);
};
}
}
void Protocol::requestListener(Protocol & protocol, const Message & message) {
Session & session = protocol.sessions.at(message.sessionID());
auto requestToken = message.requestToken();
if(session.requests.count(requestToken) == 0) {
auto & request = session.requests[requestToken];
request.send = [&session, requestToken](Token command, TokenPattern arguments) {
session.send(command, requestToken, arguments);
};
}
}
}

@ -0,0 +1,36 @@
#include <smtpd/request.hpp>
#include <vector>
#include <string>
using namespace std;
namespace smtpd {
void Request::proceed() {
send("filter-result", {"proceed"});
}
void Request::junk() {
send("filter-result", {"junk"});
}
void Request::fail(string response) {
send("filter-result", {"disconnect", response});
}
void Request::submitMail(const Mail & mail) {
submitMailSection(mail.header, false);
submitMailSection(mail.body, true);
}
void Request::submitMailSection(const vector<string> & lines, bool isBody) {
for(auto line : lines) {
submitDataLine(line, isBody);
}
submitDataLine(isBody ? "." : "", false);
}
void Request::submitDataLine(string line, bool inBody) {
send("filter-dataline", {inBody && line == "." ? ".." : line});
}
}

@ -0,0 +1,5 @@
#include <smtpd/session.hpp>
namespace smtpd {
}

@ -0,0 +1,35 @@
#include <smtpd/token.hpp>
#include <util.hpp>
#include <log.hpp>
using namespace std;
namespace smtpd {
TokenPattern::TokenPattern() :
sparse_vector<Token>() {}
TokenPattern::TokenPattern(const TokenPattern & pattern) :
sparse_vector<Token>(pattern), wildcard(pattern.wildcard) {};
TokenPattern::TokenPattern(const std::vector<std::string> & vector, bool wildcard) :
sparse_vector<Token>(vector), wildcard(wildcard) {}
TokenPattern::TokenPattern(const string & input, bool wildcard) :
TokenPattern(util::split(input, '|'), wildcard) {}
TokenPattern::TokenPattern(std::initializer_list<Token> list) :
sparse_vector<Token>(list) {}
TokenPattern::operator std::string() const {
return util::join(*this, '|');
}
bool operator<(const TokenPattern& left, const TokenPattern& right) {
return true;
}
ostream & operator<<(ostream & out, const TokenPattern & pattern) {
return out;
}
}

@ -1,36 +1,41 @@
#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;
#include <string>
#include <vector>
for(auto index = 0, last = -1; index != max_tokens; ++index) {
auto current = input.find(separator, last + 1);
auto eol = current == std::string::npos;
using namespace std;
if(eol || index == max_tokens) {
current = input.length();
}
namespace util {
const vector<string> split(const string input, const string::value_type separator, const size_t max_tokens) {
vector<string> tokens;
for(size_t index = 0, last = -1; index != max_tokens; ++index) {
auto current = input.find(separator, last + 1);
auto eol = current == string::npos;
auto length = current - last - 1;
auto token = input.substr(last + 1, length);
tokens.push_back(token);
last = current;
if(eol || index == max_tokens) {
current = input.length();
}
if(eol) {
break;
auto length = current - last - 1;
auto token = input.substr(last + 1, length);
tokens.push_back(token);
last = current;
if(eol) {
break;
}
}
}
return tokens;
}
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);
const string lpad(const string input, const int length, const string str) {
string output;
for(int i = 0; i < length; ++i) {
output += str;
}
output += input;
return output.c_str();
}
return output;
}
}

Loading…
Cancel
Save