1 /*-
2 * Copyright 2021 Vsevolod Stakhov
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 #ifndef RSPAMD_RECEIVED_HXX
19 #define RSPAMD_RECEIVED_HXX
20 #pragma once
21
22 #include "config.h"
23 #include "received.h"
24 #include "mime_string.hxx"
25 #include "libmime/email_addr.h"
26 #include "libserver/task.h"
27 #include "contrib/robin-hood/robin_hood.h"
28 #include <vector>
29 #include <string_view>
30 #include <utility>
31 #include <optional>
32
33 namespace rspamd::mime {
34
35 static inline auto
received_char_filter(UChar32 uc)36 received_char_filter(UChar32 uc) -> UChar32
37 {
38 if (u_isprint(uc)) {
39 return u_tolower(uc);
40 }
41
42 return 0;
43 }
44
45 enum class received_flags {
46 DEFAULT = 0,
47 SMTP = 1u << 0u,
48 ESMTP = 1u << 1u,
49 ESMTPA = 1u << 2u,
50 ESMTPS = 1u << 3u,
51 ESMTPSA = 1u << 4u,
52 LMTP = 1u << 5u,
53 IMAP = 1u << 6u,
54 LOCAL = 1u << 7u,
55 HTTP = 1u << 8u,
56 MAPI = 1u << 9u,
57 UNKNOWN = 1u << 10u,
58 ARTIFICIAL = (1u << 11u),
59 SSL = (1u << 12u),
60 AUTHENTICATED = (1u << 13u),
61 };
62
operator |(received_flags lhs,received_flags rhs)63 constexpr received_flags operator |(received_flags lhs, received_flags rhs)
64 {
65 using ut = std::underlying_type<received_flags>::type;
66 return static_cast<received_flags>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
67 }
68
operator |=(received_flags & lhs,const received_flags rhs)69 constexpr received_flags operator |=(received_flags &lhs, const received_flags rhs)
70 {
71 using ut = std::underlying_type<received_flags>::type;
72 lhs = static_cast<received_flags>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
73 return lhs;
74 }
75
operator &(received_flags lhs,received_flags rhs)76 constexpr received_flags operator &(received_flags lhs, received_flags rhs)
77 {
78 using ut = std::underlying_type<received_flags>::type;
79 return static_cast<received_flags>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
80 }
81
operator !(received_flags fl)82 constexpr bool operator !(received_flags fl)
83 {
84 return fl == received_flags::DEFAULT;
85 }
86
received_type_apply_protocols_mask(received_flags fl)87 constexpr received_flags received_type_apply_protocols_mask(received_flags fl) {
88 return fl & (received_flags::SMTP|
89 received_flags::ESMTP|
90 received_flags::ESMTPA|
91 received_flags::ESMTPS|
92 received_flags::ESMTPSA|
93 received_flags::IMAP|
94 received_flags::HTTP|
95 received_flags::LOCAL|
96 received_flags::MAPI|
97 received_flags::LMTP);
98 }
99
received_protocol_to_string(received_flags fl)100 constexpr const char *received_protocol_to_string(received_flags fl) {
101 const auto *proto = "unknown";
102
103 switch (received_type_apply_protocols_mask(fl)) {
104 case received_flags::SMTP:
105 proto = "smtp";
106 break;
107 case received_flags::ESMTP:
108 proto = "esmtp";
109 break;
110 case received_flags::ESMTPS:
111 proto = "esmtps";
112 break;
113 case received_flags::ESMTPA:
114 proto = "esmtpa";
115 break;
116 case received_flags::ESMTPSA:
117 proto = "esmtpsa";
118 break;
119 case received_flags::LMTP:
120 proto = "lmtp";
121 break;
122 case received_flags::IMAP:
123 proto = "imap";
124 break;
125 case received_flags::HTTP:
126 proto = "http";
127 break;
128 case received_flags::LOCAL:
129 proto = "local";
130 break;
131 case received_flags::MAPI:
132 proto = "mapi";
133 break;
134 default:
135 break;
136 }
137
138 return proto;
139 }
140
141 struct received_header {
142 mime_string from_hostname;
143 mime_string real_hostname;
144 mime_string real_ip;
145 mime_string by_hostname;
146 mime_string for_mbox;
147 struct rspamd_email_address *for_addr = nullptr;
148 rspamd_inet_addr_t *addr = nullptr;
149 struct rspamd_mime_header *hdr = nullptr;
150 time_t timestamp = 0;
151 received_flags flags = received_flags::DEFAULT; /* See enum rspamd_received_type */
152
received_headerrspamd::mime::received_header153 received_header() noexcept
154 : from_hostname(received_char_filter),
155 real_hostname(received_char_filter),
156 real_ip(received_char_filter),
157 by_hostname(received_char_filter),
158 for_mbox() {}
159 /* We have raw C pointers, so copy is explicitly disabled */
160 received_header(const received_header &other) = delete;
received_headerrspamd::mime::received_header161 received_header(received_header &&other) noexcept {
162 *this = std::move(other);
163 }
164
operator =rspamd::mime::received_header165 received_header& operator=(received_header &&other) noexcept {
166 if (this != &other) {
167 from_hostname = std::move(other.from_hostname);
168 real_hostname = std::move(other.real_hostname);
169 real_ip = std::move(other.real_ip);
170 by_hostname = std::move(other.by_hostname);
171 for_mbox = std::move(other.for_mbox);
172 timestamp = other.timestamp;
173 flags = other.flags;
174 std::swap(for_addr, other.for_addr);
175 std::swap(addr, other.addr);
176 std::swap(hdr, other.hdr);
177 }
178 return *this;
179 }
180
181 /* Unit tests helper */
from_maprspamd::mime::received_header182 static auto from_map(const robin_hood::unordered_flat_map<std::string_view, std::string_view> &map) -> received_header {
183 using namespace std::string_view_literals;
184 received_header rh;
185
186 if (map.contains("from_hostname")) {
187 rh.from_hostname.assign_copy(map.at("from_hostname"sv));
188 }
189 if (map.contains("real_hostname")) {
190 rh.real_hostname.assign_copy(map.at("real_hostname"sv));
191 }
192 if (map.contains("by_hostname")) {
193 rh.by_hostname.assign_copy(map.at("by_hostname"sv));
194 }
195 if (map.contains("real_ip")) {
196 rh.real_ip.assign_copy(map.at("real_ip"sv));
197 }
198 if (map.contains("for_mbox")) {
199 rh.for_mbox.assign_copy(map.at("for_mbox"sv));
200 }
201
202 return rh;
203 }
204
as_maprspamd::mime::received_header205 auto as_map() const -> robin_hood::unordered_flat_map<std::string_view, std::string_view>
206 {
207 robin_hood::unordered_flat_map<std::string_view, std::string_view> map;
208
209 if (!from_hostname.empty()) {
210 map["from_hostname"] = from_hostname.as_view();
211 }
212 if (!real_hostname.empty()) {
213 map["real_hostname"] = real_hostname.as_view();
214 }
215 if (!by_hostname.empty()) {
216 map["by_hostname"] = by_hostname.as_view();
217 }
218 if (!real_ip.empty()) {
219 map["real_ip"] = real_ip.as_view();
220 }
221 if (!for_mbox.empty()) {
222 map["for_mbox"] = for_mbox.as_view();
223 }
224
225 return map;
226 }
227
~received_headerrspamd::mime::received_header228 ~received_header() {
229 if (for_addr) {
230 rspamd_email_address_free(for_addr);
231 }
232 }
233 };
234
235 class received_header_chain {
236 public:
received_header_chain(struct rspamd_task * task)237 explicit received_header_chain(struct rspamd_task *task) {
238 headers.reserve(2);
239 rspamd_mempool_add_destructor(task->task_pool,
240 received_header_chain::received_header_chain_pool_dtor, this);
241 }
received_header_chain()242 explicit received_header_chain() {
243 headers.reserve(2);
244 }
245
246 enum class append_type {
247 append_tail,
248 append_head
249 };
250
new_received(append_type how=append_type::append_tail)251 auto new_received(append_type how = append_type::append_tail) -> received_header & {
252 if (how == append_type::append_tail) {
253 headers.emplace_back();
254
255 return headers.back();
256 }
257 else {
258 headers.insert(std::begin(headers), received_header());
259
260 return headers.front();
261 }
262 }
new_received(received_header && hdr,append_type how=append_type::append_tail)263 auto new_received(received_header &&hdr, append_type how = append_type::append_tail) -> received_header & {
264 if (how == append_type::append_tail) {
265 headers.emplace_back(std::move(hdr));
266
267 return headers.back();
268 }
269 else {
270 headers.insert(std::begin(headers), std::move(hdr));
271
272 return headers.front();
273 }
274 }
get_received(std::size_t nth)275 auto get_received(std::size_t nth) -> std::optional<std::reference_wrapper<received_header>>{
276 if (nth < headers.size()) {
277 return headers[nth];
278 }
279
280 return std::nullopt;
281 }
size() const282 auto size() const -> std::size_t {
283 return headers.size();
284 }
as_vector() const285 constexpr auto as_vector() const -> const std::vector<received_header>& {
286 return headers;
287 }
288 private:
received_header_chain_pool_dtor(void * ptr)289 static auto received_header_chain_pool_dtor(void *ptr) -> void {
290 delete static_cast<received_header_chain *>(ptr);
291 }
292 std::vector<received_header> headers;
293 };
294
295 } // namespace rspamd::mime
296
297 #endif //RSPAMD_RECEIVED_HXX
298