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