diff options
Diffstat (limited to 'src/libmime/received.hxx')
-rw-r--r-- | src/libmime/received.hxx | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/src/libmime/received.hxx b/src/libmime/received.hxx new file mode 100644 index 0000000..4f423f1 --- /dev/null +++ b/src/libmime/received.hxx @@ -0,0 +1,314 @@ +/*- + * Copyright 2021 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef RSPAMD_RECEIVED_HXX +#define RSPAMD_RECEIVED_HXX +#pragma once + +#include "config.h" +#include "received.h" +#include "mime_string.hxx" +#include "libmime/email_addr.h" +#include "libserver/task.h" +#include "contrib/ankerl/unordered_dense.h" +#include <vector> +#include <string_view> +#include <utility> +#include <optional> + +namespace rspamd::mime { + +static inline auto +received_char_filter(UChar32 uc) -> UChar32 +{ + if (u_isprint(uc)) { + return u_tolower(uc); + } + + return 0; +} + +enum class received_flags { + DEFAULT = 0, + SMTP = 1u << 0u, + ESMTP = 1u << 1u, + ESMTPA = 1u << 2u, + ESMTPS = 1u << 3u, + ESMTPSA = 1u << 4u, + LMTP = 1u << 5u, + IMAP = 1u << 6u, + LOCAL = 1u << 7u, + HTTP = 1u << 8u, + MAPI = 1u << 9u, + UNKNOWN = 1u << 10u, + ARTIFICIAL = (1u << 11u), + SSL = (1u << 12u), + AUTHENTICATED = (1u << 13u), +}; + +constexpr received_flags operator|(received_flags lhs, received_flags rhs) +{ + using ut = std::underlying_type<received_flags>::type; + return static_cast<received_flags>(static_cast<ut>(lhs) | static_cast<ut>(rhs)); +} + +constexpr received_flags operator|=(received_flags &lhs, const received_flags rhs) +{ + using ut = std::underlying_type<received_flags>::type; + lhs = static_cast<received_flags>(static_cast<ut>(lhs) | static_cast<ut>(rhs)); + return lhs; +} + +constexpr received_flags operator&(received_flags lhs, received_flags rhs) +{ + using ut = std::underlying_type<received_flags>::type; + return static_cast<received_flags>(static_cast<ut>(lhs) & static_cast<ut>(rhs)); +} + +constexpr bool operator!(received_flags fl) +{ + return fl == received_flags::DEFAULT; +} + +constexpr received_flags received_type_apply_protocols_mask(received_flags fl) +{ + return fl & (received_flags::SMTP | + received_flags::ESMTP | + received_flags::ESMTPA | + received_flags::ESMTPS | + received_flags::ESMTPSA | + received_flags::IMAP | + received_flags::HTTP | + received_flags::LOCAL | + received_flags::MAPI | + received_flags::LMTP); +} + +constexpr const char *received_protocol_to_string(received_flags fl) +{ + const auto *proto = "unknown"; + + switch (received_type_apply_protocols_mask(fl)) { + case received_flags::SMTP: + proto = "smtp"; + break; + case received_flags::ESMTP: + proto = "esmtp"; + break; + case received_flags::ESMTPS: + proto = "esmtps"; + break; + case received_flags::ESMTPA: + proto = "esmtpa"; + break; + case received_flags::ESMTPSA: + proto = "esmtpsa"; + break; + case received_flags::LMTP: + proto = "lmtp"; + break; + case received_flags::IMAP: + proto = "imap"; + break; + case received_flags::HTTP: + proto = "http"; + break; + case received_flags::LOCAL: + proto = "local"; + break; + case received_flags::MAPI: + proto = "mapi"; + break; + default: + break; + } + + return proto; +} + +struct received_header { + mime_string from_hostname; + mime_string real_hostname; + mime_string real_ip; + mime_string by_hostname; + mime_string for_mbox; + struct rspamd_email_address *for_addr = nullptr; + rspamd_inet_addr_t *addr = nullptr; + struct rspamd_mime_header *hdr = nullptr; + time_t timestamp = 0; + received_flags flags = received_flags::DEFAULT; /* See enum rspamd_received_type */ + + received_header() noexcept + : from_hostname(received_char_filter), + real_hostname(received_char_filter), + real_ip(received_char_filter), + by_hostname(received_char_filter), + for_mbox() + { + } + /* We have raw C pointers, so copy is explicitly disabled */ + received_header(const received_header &other) = delete; + received_header(received_header &&other) noexcept + { + *this = std::move(other); + } + + received_header &operator=(received_header &&other) noexcept + { + if (this != &other) { + from_hostname = std::move(other.from_hostname); + real_hostname = std::move(other.real_hostname); + real_ip = std::move(other.real_ip); + by_hostname = std::move(other.by_hostname); + for_mbox = std::move(other.for_mbox); + timestamp = other.timestamp; + flags = other.flags; + std::swap(for_addr, other.for_addr); + std::swap(addr, other.addr); + std::swap(hdr, other.hdr); + } + return *this; + } + + /* Unit tests helper */ + static auto from_map(const ankerl::unordered_dense::map<std::string_view, std::string_view> &map) -> received_header + { + using namespace std::string_view_literals; + received_header rh; + + if (map.contains("from_hostname")) { + rh.from_hostname.assign_copy(map.at("from_hostname"sv)); + } + if (map.contains("real_hostname")) { + rh.real_hostname.assign_copy(map.at("real_hostname"sv)); + } + if (map.contains("by_hostname")) { + rh.by_hostname.assign_copy(map.at("by_hostname"sv)); + } + if (map.contains("real_ip")) { + rh.real_ip.assign_copy(map.at("real_ip"sv)); + } + if (map.contains("for_mbox")) { + rh.for_mbox.assign_copy(map.at("for_mbox"sv)); + } + + return rh; + } + + auto as_map() const -> ankerl::unordered_dense::map<std::string_view, std::string_view> + { + ankerl::unordered_dense::map<std::string_view, std::string_view> map; + + if (!from_hostname.empty()) { + map["from_hostname"] = from_hostname.as_view(); + } + if (!real_hostname.empty()) { + map["real_hostname"] = real_hostname.as_view(); + } + if (!by_hostname.empty()) { + map["by_hostname"] = by_hostname.as_view(); + } + if (!real_ip.empty()) { + map["real_ip"] = real_ip.as_view(); + } + if (!for_mbox.empty()) { + map["for_mbox"] = for_mbox.as_view(); + } + + return map; + } + + ~received_header() + { + if (for_addr) { + rspamd_email_address_free(for_addr); + } + } +}; + +class received_header_chain { +public: + explicit received_header_chain(struct rspamd_task *task) + { + headers.reserve(2); + rspamd_mempool_add_destructor(task->task_pool, + received_header_chain::received_header_chain_pool_dtor, this); + } + explicit received_header_chain() + { + headers.reserve(2); + } + + enum class append_type { + append_tail, + append_head + }; + + auto new_received(append_type how = append_type::append_tail) -> received_header & + { + if (how == append_type::append_tail) { + headers.emplace_back(); + + return headers.back(); + } + else { + headers.insert(std::begin(headers), received_header()); + + return headers.front(); + } + } + auto new_received(received_header &&hdr, append_type how = append_type::append_tail) -> received_header & + { + if (how == append_type::append_tail) { + headers.emplace_back(std::move(hdr)); + + return headers.back(); + } + else { + headers.insert(std::begin(headers), std::move(hdr)); + + return headers.front(); + } + } + auto get_received(std::size_t nth) -> std::optional<std::reference_wrapper<received_header>> + { + if (nth < headers.size()) { + return headers[nth]; + } + + return std::nullopt; + } + auto size() const -> std::size_t + { + return headers.size(); + } + constexpr auto as_vector() const -> const std::vector<received_header> & + { + return headers; + } + +private: + static auto received_header_chain_pool_dtor(void *ptr) -> void + { + delete static_cast<received_header_chain *>(ptr); + } + std::vector<received_header> headers; +}; + +}// namespace rspamd::mime + +#endif//RSPAMD_RECEIVED_HXX |