parent
481da73b16
commit
4ca867ceab
30 changed files with 904 additions and 488 deletions
@ -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, 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(Ts... args) { |
||||
log(LDEBUG, args...); |
||||
} |
||||
|
||||
template <typename... Ts> |
||||
void debug(Ts... args) { |
||||
log(DEBUG, args...); |
||||
} |
||||
|
||||
template <typename... Ts> |
||||
void info(Ts... args) { |
||||
log(INFO, args...); |
||||
} |
||||
|
||||
template <typename... Ts> |
||||
void warning(Ts... args) { |
||||
log(WARNING, args...); |
||||
} |
||||
|
||||
template <typename... Ts> |
||||
void error(Ts... args) { |
||||
log(ERROR, args...); |
||||
} |
||||
template <typename... Ts> |
||||
void critical(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, T first, 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,41 @@ |
||||
#ifndef _SMTPD_REQUEST_ |
||||
#define _SMTPD_REQUEST_ |
||||
|
||||
#include <smtpd/session.hpp> |
||||
#include <smtpd/message.hpp> |
||||
#include <smtpd/mail.hpp> |
||||
#include <smtpd/types.hpp> |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
#include <any> |
||||
|
||||
namespace smtpd { |
||||
class FilterRequest { |
||||
public: |
||||
FilterRequest(Session & session, RequestToken requestToken); |
||||
|
||||
// Responses
|
||||
|
||||
void sendFilterResult(TokenVector tokenVector); |
||||
|
||||
void submitMail(const Mail & mail); |
||||
|
||||
void proceed(); |
||||
void junk(); |
||||
void fail(String response = "451 Aborted"); |
||||
void commit(); |
||||
|
||||
Session & session; |
||||
const RequestToken requestToken; |
||||
std::any data; |
||||
|
||||
private: |
||||
TokenVector withAddress(TokenVector tokenVector); |
||||
|
||||
void submitMailSection(const StringVector & lines, bool isBody); |
||||
void submitDataLine(const String & line, bool inBody); |
||||
}; |
||||
} |
||||
|
||||
#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(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; |
||||
StringVector header; |
||||
StringVector body; |
||||
}; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,23 @@ |
||||
#ifndef _SMTPD_MESSAGE_ |
||||
#define _SMTPD_MESSAGE_ |
||||
|
||||
#include <smtpd/token.hpp> |
||||
|
||||
namespace smtpd { |
||||
class Message { |
||||
public: |
||||
Message(String input, TokenPattern pattern); |
||||
|
||||
// Raw protocol string
|
||||
const String input; |
||||
|
||||
// TokenPattern that matched this message
|
||||
const TokenPattern pattern; |
||||
|
||||
// Retrieve message metadata
|
||||
SessionID sessionID() const; |
||||
RequestToken token() const; |
||||
}; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,37 @@ |
||||
#ifndef _SMTPD_PROTOCOL_ |
||||
#define _SMTPD_PROTOCOL_ |
||||
|
||||
#include <smtpd/message.hpp> |
||||
#include <smtpd/token.hpp> |
||||
#include <smtpd/types.hpp> |
||||
|
||||
#include <string> |
||||
#include <memory> |
||||
#include <functional> |
||||
#include <map> |
||||
|
||||
namespace smtpd { |
||||
class Protocol; |
||||
using ProtocolListener = std::function<void(Protocol & pProtocol, const Message & message)>; |
||||
|
||||
class Protocol { |
||||
public: |
||||
String version; |
||||
|
||||
Protocol(); |
||||
|
||||
void addListener(TokenPattern pattern, ProtocolListener listener); |
||||
void send(TokenVector tokens); |
||||
|
||||
protected: |
||||
void absorb(String input); |
||||
virtual void emit(String output) = 0; |
||||
|
||||
private: |
||||
std::multimap<TokenPattern, ProtocolListener> listeners; |
||||
|
||||
static void configListener(Protocol & protocol, const Message & message); |
||||
}; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,28 @@ |
||||
#ifndef _SMTPD_SESSION_ |
||||
#define _SMTPD_SESSION_ |
||||
|
||||
#include <smtpd/protocol.hpp> |
||||
#include <smtpd/types.hpp> |
||||
|
||||
#include <unordered_map> |
||||
|
||||
namespace smtpd { |
||||
class Session; |
||||
|
||||
using SessionMap = std::unordered_map<SessionID, Session>; |
||||
using ProtocolMap = std::unordered_map<size_t, SessionMap>; |
||||
|
||||
class Session { |
||||
public: |
||||
Protocol & protocol; |
||||
const SessionID id; |
||||
|
||||
Session(Protocol & protocol, const SessionID & sessionID); |
||||
static void sessionListener(Protocol & protocol, const Message & message); |
||||
|
||||
private: |
||||
static ProtocolMap protocolMap; |
||||
}; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,16 @@ |
||||
#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/filter_request.hpp> |
||||
#include <smtpd/session.hpp> |
||||
#include <smtpd/ios_protocol.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,38 @@ |
||||
#ifndef _SMTPD_TOKEN_ |
||||
#define _SMTPD_TOKEN_ |
||||
|
||||
#include <smtpd/types.hpp> |
||||
|
||||
#include <vector> |
||||
#include <map> |
||||
#include <string> |
||||
#include <iostream> |
||||
#include <memory> |
||||
|
||||
namespace smtpd { |
||||
using Token = String; |
||||
using TokenVector = std::vector<Token>; |
||||
|
||||
TokenVector toTokenVector(String input, size_t maxTokens = -1); |
||||
String toString(TokenVector tokenVector); |
||||
class TokenPattern; |
||||
String toString(TokenPattern pattern); |
||||
|
||||
class TokenPattern : public std::map<size_t, Token> { |
||||
private: |
||||
bool wildcard; |
||||
|
||||
public: |
||||
TokenPattern(); |
||||
TokenPattern(TokenVector tokens, bool wildcard = false); |
||||
TokenPattern(String input, bool wildcard = true); |
||||
|
||||
TokenVector toTokens() const; |
||||
|
||||
friend std::ostream& operator<<(std::ostream & out, const TokenPattern& pattern); |
||||
friend bool operator<(const TokenPattern& l, const TokenPattern& r); |
||||
friend bool operator==(const TokenPattern& l, const TokenPattern& r); |
||||
}; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,18 @@ |
||||
#ifndef _SMTPD_TYPES_ |
||||
#define _SMTPD_TYPES_ |
||||
|
||||
#include <vector> |
||||
#include <string> |
||||
#include <memory> |
||||
#include <functional> |
||||
|
||||
namespace smtpd { |
||||
using String = std::string; |
||||
using StringVector = std::vector<String>; |
||||
|
||||
using SessionID = String; |
||||
using RequestToken = String; |
||||
} |
||||
|
||||
|
||||
#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 |
@ -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,53 @@ |
||||
#include <smtpd/filter_request.hpp> |
||||
|
||||
using namespace std; |
||||
|
||||
namespace smtpd { |
||||
FilterRequest::FilterRequest(Session & session, RequestToken requestToken) : |
||||
session(session), requestToken(requestToken) { } |
||||
|
||||
void FilterRequest::sendFilterResult(TokenVector tokenVector) { |
||||
tokenVector.insert(tokenVector.begin(), {"filter-result", "", ""}); |
||||
session.protocol.send(withAddress(tokenVector)); |
||||
} |
||||
|
||||
void FilterRequest::proceed() { |
||||
sendFilterResult({"proceed"}); |
||||
} |
||||
|
||||
void FilterRequest::junk() { |
||||
sendFilterResult({"junk"}); |
||||
} |
||||
|
||||
void FilterRequest::fail(String response) { |
||||
sendFilterResult({"disconnect", response}); |
||||
} |
||||
|
||||
void FilterRequest::submitMail(const Mail & mail) { |
||||
submitMailSection(mail.header, false); |
||||
submitMailSection(mail.body, true); |
||||
} |
||||
|
||||
void FilterRequest::submitMailSection(const StringVector & lines, bool isBody) { |
||||
for(auto line : lines) { |
||||
submitDataLine(line, isBody); |
||||
} |
||||
submitDataLine(isBody ? "." : "", false); |
||||
} |
||||
|
||||
void FilterRequest::submitDataLine(const String & line, bool inBody) { |
||||
session.protocol.send(withAddress({"filter-dataline", "?", "?", |
||||
inBody && line == "." ? ".." : line})); |
||||
} |
||||
|
||||
TokenVector FilterRequest::withAddress(TokenVector tokenVector) { |
||||
if(session.protocol.version < "0.5") { |
||||
tokenVector.at(1) = requestToken; |
||||
tokenVector.at(2) = session.id; |
||||
} else { |
||||
tokenVector.at(1) = session.id; |
||||
tokenVector.at(2) = requestToken; |
||||
} |
||||
return tokenVector; |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
#include <smtpd/ios_protocol.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::token() const { |
||||
return pattern.at(6); |
||||
} |
||||
} |
@ -0,0 +1,73 @@ |
||||
// OpenSMTPD filter protocol documentation:
|
||||
// https://man7.org/linux/man-pages/man7/smtpd-filters.7.html
|
||||
|
||||
#include <util.hpp> |
||||
#include <log.hpp> |
||||
|
||||
#include <smtpd/types.hpp> |
||||
#include <smtpd/message.hpp> |
||||
#include <smtpd/protocol.hpp> |
||||
#include <smtpd/session.hpp> |
||||
|
||||
#include <iostream> |
||||
#include <set> |
||||
|
||||
using namespace std; |
||||
using namespace util; |
||||
|
||||
namespace smtpd { |
||||
Protocol::Protocol() { |
||||
addListener({"config|ready"}, Protocol::configListener); |
||||
addListener({"filter|*|*|smtp-in"}, Session::sessionListener); |
||||
} |
||||
|
||||
void Protocol::configListener(Protocol & protocol, const Message & message) { |
||||
// Find unique SMTPD filters in listener map
|
||||
set<TokenVector> uniqueFilters; |
||||
for(auto&& [pattern, listener] : protocol.listeners) { |
||||
log::ldebug("Found listener for: %", toString(pattern)); |
||||
if(pattern.at(0) == "filter" && pattern.count(3) > 0 && pattern.count(4) > 0) { |
||||
uniqueFilters.insert({"register", "filter", pattern.at(3), pattern.at(4)}); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
// Register filters with SMTPD
|
||||
for(TokenVector message : uniqueFilters) { |
||||
protocol.send(message); |
||||
} |
||||
|
||||
protocol.send({"register", "ready"}); |
||||
} |
||||
|
||||
void Protocol::addListener(TokenPattern pattern, ProtocolListener listener) { |
||||
log::ldebug("Adding passed-as-arguments listener for pattern %", toString(pattern)); |
||||
listeners.insert({pattern, listener}); |
||||
} |
||||
|
||||
void Protocol::send(TokenVector tokens) { |
||||
log::ldebug("Sending tokens: %", toString(tokens)); |
||||
this->emit(toString(tokens)); |
||||
} |
||||
|
||||
void Protocol::absorb(String input) { |
||||
auto tokens = toTokenVector(input); |
||||
log::ldebug("Received tokens: %", toString(tokens)); |
||||
|
||||
if(tokens.size() >= 2 && tokens.at(0) == "filter") { |
||||
version = tokens.at(1); |
||||
} |
||||
|
||||
auto pattern = TokenPattern(tokens); |
||||
auto range = listeners.equal_range(pattern); |
||||
if(range.first == range.second) { |
||||
log::error("Unsupported message: %", toString(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}); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
#include <smtpd/protocol.hpp> |
||||
#include <smtpd/session.hpp> |
||||
|
||||
using namespace std; |
||||
|
||||
namespace smtpd { |
||||
ProtocolMap Session::protocolMap{}; |
||||
|
||||
Session::Session(Protocol & protocol, const SessionID & sessionID) : |
||||
protocol(protocol), id(sessionID) { } |
||||
|
||||
void Session::sessionListener(Protocol & protocol, const Message & message) { |
||||
size_t protocolID = hash<Protocol *>{}(&protocol); |
||||
SessionMap * pSessionMap; |
||||
try { |
||||
pSessionMap = &protocolMap.at(protocolID); |
||||
} catch(out_of_range & e) { |
||||
auto element = protocolMap.emplace(protocolID, SessionMap{}); |
||||
pSessionMap = &element.first->second; |
||||
} |
||||
|
||||
Session * pSession; |
||||
auto sessionID = message.sessionID(); |
||||
try { |
||||
pSession = &pSessionMap->at(sessionID); |
||||
} catch(out_of_range & e) { |
||||
auto element = pSessionMap->emplace(sessionID, Session{protocol, sessionID}); |
||||
pSession = &element.first->second; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,131 @@ |
||||
#include <smtpd/token.hpp> |
||||
#include <util.hpp> |
||||
#include <log.hpp> |
||||
|
||||
using namespace std; |
||||
|
||||
namespace smtpd { |
||||
TokenVector toTokenVector(String input, size_t maxTokens) { |
||||
return util::split(input, '|', maxTokens); |
||||
} |
||||
|
||||
String toString(TokenVector tokenVector) { |
||||
return util::join(tokenVector, '|'); |
||||
} |
||||
|
||||
String toString(TokenPattern pattern) { |
||||
return toString(pattern.toTokens()); |
||||
} |
||||
|
||||
TokenPattern::TokenPattern() {} |
||||
|
||||
TokenPattern::TokenPattern(TokenVector tokens, bool wildcard) : wildcard(wildcard) { |
||||
for(size_t index = 0; index < tokens.size(); ++index) { |
||||
auto token = tokens.at(index); |
||||
if(wildcard && token == "*") { |
||||
continue; |
||||
} |
||||
|
||||
this->emplace(index, token); |
||||
} |
||||
} |
||||
|
||||
TokenPattern::TokenPattern(String input, bool wildcard) : TokenPattern(toTokenVector(input), wildcard) {} |
||||
|
||||
TokenVector TokenPattern::toTokens() const { |
||||
size_t length = (--this->end())->first + 1; |
||||
TokenVector output{length}; |
||||
for(size_t index = 0; index < length; ++index) { |
||||
if(count(index) == 0) { |
||||
output.at(index) = "*"; |
||||
continue; |
||||
} |
||||
output.at(index) = at(index); |
||||
} |
||||
return output; |
||||
} |
||||
|
||||
bool operator<(const TokenPattern& l, const TokenPattern& r) { |
||||
log::ldebug("Checking if % < %", toString(l), toString(r)); |
||||
log::open(); |
||||
|
||||
if(l == r) { |
||||
log::ldebug("Not less becsuse left equals right"); |
||||
log::close(); |
||||
return false; |
||||
} |
||||
|
||||
for(auto le = l.begin(); le != l.end(); ++le) { |
||||
if(r.count(le->first) == 0) { |
||||
log::ldebug("Not less because right is missing % -> %", le->first, toString(le->second)); |
||||
log::close(); |
||||
return false; |
||||
} |
||||
|
||||
if(le->second < r.at(le->first)) { |
||||
log::ldebug("Less because % < %", toString(le->second), toString(r.at(le->first))); |
||||
log::close(); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
if(l.size() < r.size()) { |
||||
log::ldebug("Less because size of % < size of %", toString(l), toString(r)); |
||||
log::close(); |
||||
return true; |
||||
} |
||||
|
||||
log::error("Less-than operator fell through!"); |
||||
log::close(); |
||||
throw; |
||||
} |
||||
|
||||
bool operator==(const TokenPattern& l, const TokenPattern& r) { |
||||
log::ldebug("Checking if % == %", toString(l), toString(r)); |
||||
log::open(); |
||||
|
||||
const TokenPattern *subset, *superset; |
||||
if(r.wildcard == l.wildcard) { |
||||
log::ldebug("Left and right are both (not) wildcards"); |
||||
if(l.size() != r.size()) { |
||||
log::ldebug("Not equal because sizes are unequal"); |
||||
log::close(); |
||||
return false; |
||||
} else { |
||||
subset = &l; |
||||
superset = &r; |
||||
} |
||||
} else if(l.wildcard) { |
||||
log::ldebug("Left is wildcard"); |
||||
subset = &l; |
||||
superset = &r; |
||||
} else { |
||||
log::ldebug("Right is wildcard"); |
||||
subset = &r; |
||||
superset = &l; |
||||
} |
||||
|
||||
for(auto le : *subset) { |
||||
if(superset->count(le.first) == 0) { |
||||
log::ldebug("Not equal because % -> % is missing in superset", le.first, toString(le.second)); |
||||
log::close(); |
||||
return false; |
||||
} |
||||
|
||||
auto rv = superset->at(le.first); |
||||
|
||||
if(rv != le.second) { |
||||
log::ldebug("Not equal because % -> % != % in superset", le.first, toString(le.second), rv); |
||||
log::close(); |
||||
return false; |
||||
} |
||||
|
||||
log::ldebug("% -> % == % in superset", le.first, toString(le.second), rv); |
||||
} |
||||
|
||||
log::ldebug("Patterns match"); |
||||
log::close(); |
||||
|
||||
return true; |
||||
} |
||||
} |
@ -1,36 +1,48 @@ |
||||
#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; |
||||
using namespace std; |
||||
|
||||
for(auto index = 0, last = -1; index != max_tokens; ++index) { |
||||
auto current = input.find(separator, last + 1); |
||||
auto eol = current == std::string::npos; |
||||
namespace util { |
||||
const std::vector<string> split(const string input, const string::value_type separator, const size_t max_tokens) { |
||||
vector<string> tokens; |
||||
|
||||
if(eol || index == max_tokens) { |
||||
current = input.length(); |
||||
} |
||||
for(size_t index = 0, last = -1; index != max_tokens; ++index) { |
||||
auto current = input.find(separator, last + 1); |
||||
auto eol = current == 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; |
||||
auto length = current - last - 1; |
||||
auto token = input.substr(last + 1, length); |
||||
tokens.push_back(token); |
||||
last = current; |
||||
|
||||
if(eol) { |
||||
break; |
||||
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 join(const vector<string> strings, const 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; |
||||
} |
||||
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…
Reference in new issue