1 /*!
2  * @file
3  * @brief The implementation of authentification_handler.
4  */
5 
6 #include <arataga/acl_handler/handlers/http/basics.hpp>
7 #include <arataga/acl_handler/handlers/http/factories.hpp>
8 #include <arataga/acl_handler/handlers/http/helpers.hpp>
9 #include <arataga/acl_handler/handlers/http/responses.hpp>
10 
11 #include <arataga/utils/overloaded.hpp>
12 
13 #include <restinio/helpers/http_field_parsers/authorization.hpp>
14 #include <restinio/helpers/http_field_parsers/basic_auth.hpp>
15 #include <restinio/helpers/http_field_parsers/host.hpp>
16 
17 namespace arataga::acl_handler
18 {
19 
20 namespace handlers::http
21 {
22 
23 //
24 // authentification_handler_t
25 //
26 /*!
27  * @brief Connection-handler that performs authentification.
28  */
29 class authentification_handler_t final : public basic_http_handler_t
30 {
31 	//! The result of successful extraction of username/password
32 	//! from header fields of HTTP-request.
33 	struct username_password_t
34 	{
35 		std::string m_username;
36 		std::string m_password;
37 	};
38 
39 	//! The result for the case when username/password weren't set.
40 	struct no_username_password_provided_t {};
41 
42 	//! The result for the case of an error during username/password
43 	//! extraction.
44 	struct username_password_extraction_failure_t
45 	{
46 		//! The description of the error.
47 		std::string m_description;
48 	};
49 
50 	//! The generic result of username/password extraction from a HTTP-request.
51 	using username_password_extraction_result_t = std::variant<
52 			username_password_t,
53 			no_username_password_provided_t,
54 			username_password_extraction_failure_t
55 		>;
56 
57 	//! The result of successful extraction of host/port from a HTTP-request.
58 	struct target_host_and_port_t
59 	{
60 		std::string m_host;
61 		std::uint16_t m_port;
62 	};
63 
64 	//! The result of a failed extraction of host/port from a HTTP-request.
65 	struct target_host_and_port_extraction_failure_t
66 	{
67 		//! The description of the error.
68 		std::string m_description;
69 	};
70 
71 	//! Generic result of host/port extraction from a HTTP-request.
72 	using target_host_and_port_extraction_result_t = std::variant<
73 			target_host_and_port_t,
74 			target_host_and_port_extraction_failure_t
75 		>;
76 
77 	//! The result of successful transformation of request-target.
78 	struct update_request_target_success_t {};
79 
80 	//! The result of failed transformation of request-target.
81 	struct update_request_target_failure_t
82 	{
83 		//! The description of the error.
84 		std::string m_description;
85 	};
86 
87 	//! Generic result of request-target transformation.
88 	using update_request_target_result_t = std::variant<
89 			update_request_target_success_t,
90 			update_request_target_failure_t
91 		>;
92 
93 	//! The state of HTTP-request parsing.
94 	http_handling_state_unique_ptr_t m_request_state;
95 
96 	//! Additional info for the HTTP-request.
97 	/*!
98 	 * In the case of successful analisys of request-target and Host
99 	 * header field the actual target-host and target-port will be
100 	 * stored here.
101 	 */
102 	request_info_t m_request_info;
103 
104 	//! The timepoint of the start of authentification.
105 	std::chrono::steady_clock::time_point m_created_at;
106 
107 public:
authentification_handler_t(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info)108 	authentification_handler_t(
109 		handler_context_holder_t ctx,
110 		handler_context_t::connection_id_t id,
111 		asio::ip::tcp::socket connection,
112 		http_handling_state_unique_ptr_t request_state,
113 		request_info_t request_info )
114 		:	basic_http_handler_t{ std::move(ctx), id, std::move(connection) }
115 		,	m_request_state{ std::move(request_state) }
116 		,	m_request_info{ std::move(request_info) }
117 		,	m_created_at{ std::chrono::steady_clock::now() }
118 	{
119 	}
120 
121 protected:
122 	void
on_start_impl(delete_protector_t delete_protector)123 	on_start_impl( delete_protector_t delete_protector ) override
124 	{
125 		wrap_action_and_handle_exceptions(
126 			delete_protector,
127 			[this]( delete_protector_t delete_protector, can_throw_t can_throw )
128 			{
129 				// If username/password are set, they have to be extracted.
130 				auto username_password_extraction_result =
131 						try_extract_username_and_password( can_throw );
132 				// There is no sense to continue in the case of an error.
133 				if( auto * err = std::get_if<username_password_extraction_failure_t>(
134 						&username_password_extraction_result); err )
135 				{
136 					::arataga::logging::wrap_logging(
137 							proxy_logging_mode,
138 							spdlog::level::err,
139 							[this, can_throw, err]( auto level )
140 							{
141 								log_message_for_connection(
142 										can_throw,
143 										level,
144 										fmt::format(
145 												"username/password extraction failure: {}",
146 												err->m_description ) );
147 							} );
148 
149 					send_negative_response_then_close_connection(
150 							delete_protector,
151 							can_throw,
152 							remove_reason_t::protocol_error,
153 							response_bad_request_auth_params_extraction_failure );
154 
155 					return;
156 				}
157 
158 				// Detect the target host and port.
159 				auto target_host_and_port_extraction_result =
160 						try_extract_target_host_and_port( can_throw );
161 				if( auto * err = std::get_if<target_host_and_port_extraction_failure_t>(
162 						&target_host_and_port_extraction_result); err )
163 				{
164 					::arataga::logging::wrap_logging(
165 							proxy_logging_mode,
166 							spdlog::level::err,
167 							[this, can_throw, err]( auto level )
168 							{
169 								log_message_for_connection(
170 										can_throw,
171 										level,
172 										fmt::format(
173 												"target-host+port extraction failure: {}",
174 												err->m_description ) );
175 							} );
176 
177 					send_negative_response_then_close_connection(
178 							delete_protector,
179 							can_throw,
180 							remove_reason_t::protocol_error,
181 							response_bad_request_target_host_extraction_failure );
182 
183 					return;
184 				}
185 
186 				// If request-target is in absolute-form it should be
187 				// transformed into origin-form.
188 				auto update_request_target_result =
189 						try_update_request_target( can_throw );
190 				if( auto * err = std::get_if<update_request_target_failure_t>(
191 						&update_request_target_result); err )
192 				{
193 					::arataga::logging::wrap_logging(
194 							proxy_logging_mode,
195 							spdlog::level::err,
196 							[this, can_throw, err]( auto level )
197 							{
198 								log_message_for_connection(
199 										can_throw,
200 										level,
201 										fmt::format(
202 												"update request-target failure: {}",
203 												err->m_description ) );
204 							} );
205 
206 					send_negative_response_then_close_connection(
207 							delete_protector,
208 							can_throw,
209 							remove_reason_t::protocol_error,
210 							response_bad_request_invalid_request_target );
211 
212 					return;
213 				}
214 
215 
216 				// Now we can initiate the authentification.
217 				initiate_authentification(
218 						can_throw,
219 						username_password_extraction_result,
220 						target_host_and_port_extraction_result );
221 			} );
222 	}
223 
224 	void
on_timer_impl(delete_protector_t delete_protector)225 	on_timer_impl( delete_protector_t delete_protector ) override
226 	{
227 		if( std::chrono::steady_clock::now() >= m_created_at +
228 				context().config().authentification_timeout() )
229 		{
230 			wrap_action_and_handle_exceptions(
231 				delete_protector,
232 				[this]( delete_protector_t delete_protector, can_throw_t can_throw )
233 				{
234 					::arataga::logging::wrap_logging(
235 							proxy_logging_mode,
236 							spdlog::level::warn,
237 							[this, can_throw]( auto level )
238 							{
239 								log_message_for_connection(
240 										can_throw,
241 										level,
242 										"authentification timed out" );
243 							} );
244 
245 					// We can only send the response and close the connection.
246 					send_negative_response_then_close_connection(
247 							delete_protector,
248 							can_throw,
249 							remove_reason_t::current_operation_timed_out,
250 							response_proxy_auth_required_auth_timeout );
251 				} );
252 		}
253 	}
254 
255 public:
256 	arataga::utils::string_literal_t
name() const257 	name() const noexcept override
258 	{
259 		using namespace arataga::utils::string_literals;
260 		return "http-authenitification-handler"_static_str;
261 	}
262 
263 private:
264 	[[nodiscard]]
265 	username_password_extraction_result_t
try_extract_username_and_password(can_throw_t)266 	try_extract_username_and_password(
267 		can_throw_t /*can_throw*/ )
268 	{
269 		auto opt_proxy_auth_value = m_request_info.m_headers.opt_value_of(
270 				restinio::http_field_t::proxy_authorization );
271 		if( !opt_proxy_auth_value )
272 			return no_username_password_provided_t{};
273 
274 		using namespace restinio::http_field_parsers;
275 		const auto auth_value_result = authorization_value_t::try_parse(
276 				*opt_proxy_auth_value );
277 		if( !auth_value_result )
278 			return username_password_extraction_failure_t{
279 					make_error_description(
280 							auth_value_result.error(),
281 							*opt_proxy_auth_value )
282 			};
283 
284 		auto & auth_value = *auth_value_result;
285 		if( "basic" != auth_value.auth_scheme )
286 			return username_password_extraction_failure_t{
287 					fmt::format( "unsupported auth-scheme: {}",
288 							auth_value.auth_scheme )
289 			};
290 
291 		auto basic_auth_result = basic_auth::try_extract_params(
292 				auth_value );
293 		if( !basic_auth_result )
294 			return username_password_extraction_failure_t{
295 					fmt::format( "basic-auth param extraction failed: {}",
296 							static_cast<int>(basic_auth_result.error()) )
297 			};
298 
299 		// The Proxy-Authorization header field isn't needed anymore
300 		// and should be removed.
301 		m_request_info.m_headers.remove_all_of(
302 				restinio::http_field_t::proxy_authorization );
303 
304 		auto & basic_auth = *basic_auth_result;
305 		return username_password_t{
306 				std::move(basic_auth.username),
307 				std::move(basic_auth.password)
308 		};
309 	}
310 
311 	[[nodiscard]]
312 	target_host_and_port_extraction_result_t
try_extract_target_host_and_port(can_throw_t can_throw)313 	try_extract_target_host_and_port( can_throw_t can_throw )
314 	{
315 		auto extraction_result =
316 				try_extract_target_host_and_port_from_request_target( can_throw );
317 
318 		if( std::holds_alternative<target_host_and_port_extraction_failure_t>(
319 				extraction_result ) )
320 		{
321 			extraction_result =
322 					try_extract_target_host_and_port_from_host_field( can_throw );
323 		}
324 
325 		// The Host header field should be removed after the extraction.
326 		m_request_info.m_headers.remove_all_of( restinio::http_field_t::host );
327 
328 		return extraction_result;
329 	}
330 
331 	[[nodiscard]]
332 	target_host_and_port_extraction_result_t
try_extract_target_host_and_port_from_request_target(can_throw_t)333 	try_extract_target_host_and_port_from_request_target(
334 		can_throw_t /*can_throw*/ )
335 	{
336 		// Try to deconstruct the URL.
337 		http_parser_url parser_url;
338 		http_parser_url_init( &parser_url );
339 
340 		const auto & value_to_process = m_request_info.m_request_target;
341 
342 		const auto parse_url_result = http_parser_parse_url(
343 				value_to_process.data(),
344 				value_to_process.size(),
345 				HTTP_CONNECT == m_request_state->m_parser.method,
346 				&parser_url );
347 		if( parse_url_result )
348 			return target_host_and_port_extraction_failure_t{
349 					fmt::format( "unable to parse request-target, "
350 							"http_parser_parse_url result: {}",
351 							parse_url_result )
352 			};
353 
354 		const auto is_component_present = [&]( unsigned int component ) -> bool
355 			{
356 				return parser_url.field_set & (1u << component);
357 			};
358 
359 		const auto try_extract_url_component =
360 			[&]( unsigned int component ) -> std::string_view
361 			{
362 				std::string_view result;
363 				if( is_component_present(component) )
364 					result = std::string_view{
365 							value_to_process.data() +
366 									parser_url.field_data[component].off,
367 							parser_url.field_data[component].len
368 					};
369 				return result;
370 			};
371 
372 		const auto schema_sv = try_extract_url_component( UF_SCHEMA );
373 		const auto host_sv = try_extract_url_component( UF_HOST );
374 
375 		std::optional< std::uint16_t > opt_port{ 80u };
376 		if( is_component_present( UF_PORT ) )
377 			opt_port = parser_url.port;
378 
379 		if( !host_sv.empty() && opt_port )
380 			// We already have the result.
381 			return target_host_and_port_t{
382 					std::string{ host_sv },
383 					*opt_port
384 			};
385 
386 		// If there are 'schema' and 'host' then the port number can be detected.
387 		if( !schema_sv.empty() && !host_sv.empty() )
388 		{
389 			if( "http" == schema_sv )
390 				return target_host_and_port_t{
391 						std::string{ host_sv },
392 						80u
393 				};
394 			else if( "https" == schema_sv )
395 				return target_host_and_port_t{
396 						std::string{ host_sv },
397 						443u
398 				};
399 			else
400 				// Unsupported scheme found.
401 				return target_host_and_port_extraction_failure_t{
402 						fmt::format( "unsupported schema in request-target: {}",
403 								schema_sv )
404 				};
405 		}
406 
407 		// target-host and port are not extracted.
408 		return target_host_and_port_extraction_failure_t{
409 				fmt::format( "no target-host and port in request-target" )
410 		};
411 	}
412 
413 	[[nodiscard]]
414 	target_host_and_port_extraction_result_t
try_extract_target_host_and_port_from_host_field(can_throw_t)415 	try_extract_target_host_and_port_from_host_field(
416 		can_throw_t /*can_throw*/ )
417 	{
418 		// If there are more than one Host header fields then the request
419 		// should be rejected. So count the fields.
420 		std::optional< std::string_view > opt_host;
421 		std::size_t host_occurrences{ 0u };
422 
423 		m_request_info.m_headers.for_each_value_of(
424 				restinio::http_field_t::host,
425 				[&]( std::string_view value )
426 				{
427 					++host_occurrences;
428 					if( 1u == host_occurrences )
429 					{
430 						opt_host = value;
431 					}
432 
433 					return restinio::http_header_fields_t::continue_enumeration();
434 				} );
435 
436 		if( 0u == host_occurrences )
437 			return target_host_and_port_extraction_failure_t{
438 					"no Host http-field"
439 			};
440 		else if( 1u != host_occurrences )
441 			return target_host_and_port_extraction_failure_t{
442 					fmt::format( "too many Host http-fields: {}",
443 							host_occurrences )
444 			};
445 
446 		// We have to parse the value.
447 		// The parser from RESTinio is used because http_parser_parse_url
448 		// can't handle values like "localhost:9090".
449 		using namespace restinio::http_field_parsers;
450 
451 		auto parse_result = raw_host_value_t::try_parse( opt_host.value() );
452 		if( !parse_result )
453 			return target_host_and_port_extraction_failure_t{
454 					fmt::format( "unable to parse Host http-field: {}",
455 							make_error_description(
456 									parse_result.error(),
457 									opt_host.value() ) )
458 			};
459 
460 		std::string target_host = std::visit(
461 				::arataga::utils::overloaded{
462 					[]( raw_host_value_t::reg_name_t & n ) -> std::string
463 					{
464 						return std::move(n.v);
465 					},
466 					[]( raw_host_value_t::ipv4_address_t & n ) -> std::string
467 					{
468 						return std::move(n.v);
469 					},
470 					[]( raw_host_value_t::ipv6_address_t & n ) -> std::string
471 					{
472 						return std::move(n.v);
473 					}
474 				},
475 				parse_result->host );
476 
477 		return target_host_and_port_t{
478 				std::move(target_host),
479 				parse_result->port ? *(parse_result->port) : std::uint16_t{80u}
480 		};
481 	}
482 
483 	[[nodiscard]]
484 	update_request_target_result_t
try_update_request_target(can_throw_t)485 	try_update_request_target(
486 		can_throw_t /*can_throw*/ )
487 	{
488 		// The value of request-target should be borrowed into
489 		// a separate object because we need a reference to that value
490 		// during the construction of new m_request_target value.
491 		std::string value_to_process{
492 				std::move(m_request_info.m_request_target)
493 			};
494 
495 		// Try to deconstruct URL.
496 		http_parser_url parser_url;
497 		http_parser_url_init( &parser_url );
498 
499 		const auto parse_url_result = http_parser_parse_url(
500 				value_to_process.data(),
501 				value_to_process.size(),
502 				HTTP_CONNECT == m_request_state->m_parser.method,
503 				&parser_url );
504 		if( parse_url_result )
505 			return update_request_target_failure_t{
506 					fmt::format( "unable to parse request-target, "
507 							"http_parser_parse_url result: {}",
508 							parse_url_result )
509 			};
510 
511 		const auto is_component_present = [&]( unsigned int component ) -> bool
512 			{
513 				return parser_url.field_set & (1u << component);
514 			};
515 
516 		const auto try_extract_url_component =
517 			[&]( unsigned int component ) -> std::string_view
518 			{
519 				std::string_view result;
520 				if( is_component_present(component) )
521 					result = std::string_view{
522 							value_to_process.data() +
523 									parser_url.field_data[component].off,
524 							parser_url.field_data[component].len
525 					};
526 				return result;
527 			};
528 
529 		const auto path = try_extract_url_component( UF_PATH );
530 		const auto query = try_extract_url_component( UF_QUERY );
531 		const auto fragment = try_extract_url_component( UF_FRAGMENT );
532 
533 		m_request_info.m_request_target.clear();
534 
535 		// If request-target is specified in authority-form then 'path'
536 		// could be empty after the parsing.
537 		if( !path.empty() )
538 			m_request_info.m_request_target.append( path.data(), path.size() );
539 		else
540 			m_request_info.m_request_target += '/';
541 
542 		if( !query.empty() )
543 		{
544 			m_request_info.m_request_target += '?';
545 			m_request_info.m_request_target.append( query.data(), query.size() );
546 		}
547 
548 		if( !fragment.empty() )
549 		{
550 			m_request_info.m_request_target += '#';
551 			m_request_info.m_request_target.append( fragment.data(), fragment.size() );
552 		}
553 
554 		return update_request_target_success_t{};
555 	}
556 
557 	void
initiate_authentification(can_throw_t,username_password_extraction_result_t & username_password_info,target_host_and_port_extraction_result_t & target_host_and_port_info)558 	initiate_authentification(
559 		can_throw_t /*can_throw*/,
560 		username_password_extraction_result_t & username_password_info,
561 		target_host_and_port_extraction_result_t & target_host_and_port_info )
562 	{
563 		std::optional< std::string > username;
564 		std::optional< std::string > password;
565 		if( auto * upi = std::get_if<username_password_t>(
566 				&username_password_info ) )
567 		{
568 			username = std::move(upi->m_username);
569 			password = std::move(upi->m_password);
570 		}
571 
572 		// Info about target-host and target-port should be stored into
573 		// request_info because it'll necessary later.
574 		{
575 			auto & host_port =
576 				std::get< target_host_and_port_t >( target_host_and_port_info );
577 			m_request_info.m_target_host = std::move(host_port.m_host);
578 			m_request_info.m_target_port = host_port.m_port;
579 		}
580 
581 		context().async_authentificate(
582 				m_id,
583 				authentification::request_params_t {
584 					// We work with IPv4 addresses only so don't expect
585 					// something else.
586 					m_connection.remote_endpoint().address().to_v4(),
587 					std::move(username),
588 					std::move(password),
589 					m_request_info.m_target_host,
590 					m_request_info.m_target_port
591 				},
592 				with<authentification::result_t>().make_handler(
593 					[this](
594 						delete_protector_t delete_protector,
595 						can_throw_t can_throw,
596 						authentification::result_t result )
597 					{
598 						on_authentification_result(
599 								delete_protector, can_throw, result );
600 					} )
601 			);
602 	}
603 
604 	void
on_authentification_result(delete_protector_t delete_protector,can_throw_t can_throw,authentification::result_t & result)605 	on_authentification_result(
606 		delete_protector_t delete_protector,
607 		can_throw_t can_throw,
608 		authentification::result_t & result )
609 	{
610 		std::visit( ::arataga::utils::overloaded{
611 				[&]( authentification::success_t & info )
612 				{
613 					replace_handler(
614 							delete_protector,
615 							can_throw,
616 							[this, &info]( can_throw_t )
617 							{
618 								return make_dns_lookup_handler(
619 										std::move(m_ctx),
620 										m_id,
621 										std::move(m_connection),
622 										std::move(m_request_state),
623 										std::move(m_request_info),
624 										std::move(info.m_traffic_limiter) );
625 							} );
626 				},
627 				[&]( const authentification::failure_t & info )
628 				{
629 					::arataga::logging::wrap_logging(
630 							proxy_logging_mode,
631 							spdlog::level::warn,
632 							[this, can_throw, &info]( auto level )
633 							{
634 								log_message_for_connection(
635 										can_throw,
636 										level,
637 										fmt::format(
638 												"user is not authentificated, reason: {}",
639 												authentification::to_string_literal(
640 														info.m_reason ) ) );
641 							} );
642 
643 					send_negative_response_then_close_connection(
644 							delete_protector,
645 							can_throw,
646 							remove_reason_t::access_denied,
647 							response_proxy_auth_required_not_authorized );
648 				}
649 			},
650 			result );
651 	}
652 };
653 
654 //
655 // make_authentification_handler
656 //
657 [[nodiscard]]
658 connection_handler_shptr_t
make_authentification_handler(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info)659 make_authentification_handler(
660 	handler_context_holder_t ctx,
661 	handler_context_t::connection_id_t id,
662 	asio::ip::tcp::socket connection,
663 	http_handling_state_unique_ptr_t request_state,
664 	request_info_t request_info )
665 {
666 	return std::make_shared< authentification_handler_t >(
667 			std::move(ctx),
668 			id,
669 			std::move(connection),
670 			std::move(request_state),
671 			std::move(request_info) );
672 }
673 
674 } /* namespace arataga::acl_handler */
675 
676 } /* namespace handlers::http */
677 
678