fix #4494: redirect HTTP to HTTPS

Allow HTTP connections up until the request's header has been
parsed and processed. If no TLS handshake has been completed
beforehand, the server now responds with either a
'301 Moved Permanently' or a '308 Permanent Redirect' as noted in the
MDN web docs[1].

This is done after the header was parsed; for the redirect to work,
the `Host` header field of the request is used to create the
`Location` field of the response. This makes redirections independent
of how the server is accessed (e.g. via IP, localhost, FQDN, ...)
possible.

Upon redirection the client is immediately disconnected; otherwise,
they would have to wait for the connection to time out until
they may reconnect via TLS again.

[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
This commit is contained in:
Max Carrara 2023-03-03 18:29:50 +01:00 committed by Fabian Grünbichler
parent f2e54bb78a
commit 933a4dbbaf

View File

@ -1314,7 +1314,7 @@ sub unshift_read_header {
if $state->{key};
$self->process_header($reqstate) or return;
# header processing complete - authenticate now
$self->ensure_tls_connection($reqstate) or return;
$self->authenticate_and_handle_request($reqstate) or return;
} elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) {
@ -1384,6 +1384,43 @@ sub process_header {
return 1;
}
sub ensure_tls_connection {
my ($self, $reqstate) = @_;
# Skip if server doesn't use TLS
if (!$self->{tls_ctx}) {
return 1;
}
# TLS session exists, so the handshake has succeeded
if ($reqstate->{hdl}->{tls}) {
return 1;
}
my $request = $reqstate->{request};
my $method = $request->method();
my $h_host = $reqstate->{request}->header('Host');
die "Header field 'Host' not found in request\n"
if !$h_host;
my $secure_host = "https://" . ($h_host =~ s/^http(s)?:\/\///r);
my $header = HTTP::Headers->new('Location' => $secure_host . $request->uri());
if ($method eq 'GET' || $method eq 'HEAD') {
$self->error($reqstate, 301, 'Moved Permanently', $header);
} else {
$self->error($reqstate, 308, 'Permanent Redirect', $header);
}
# disconnect the client so they may immediately connect again via HTTPS
$self->client_do_disconnect($reqstate);
return;
}
sub authenticate_and_handle_request {
my ($self, $reqstate) = @_;
@ -1791,11 +1828,16 @@ sub accept_connections {
};
if (my $err = $@) { syslog('err', "$err"); }
},
($self->{tls_ctx} ? (tls => "accept", tls_ctx => $self->{tls_ctx}) : ()));
);
$handle_creation = 0;
$self->dprint("ACCEPT FH" . $clientfh->fileno() . " CONN$self->{conn_count}");
if ($self->{tls_ctx}) {
$self->dprint("Setting TLS to autostart");
$reqstate->{hdl}->unshift_read(tls_autostart => $self->{tls_ctx}, "accept");
}
$self->push_request_header($reqstate);
}
};