use AnyEvent::HTTP;
use AnyEvent::Handle;
-use AnyEvent::IO;
use AnyEvent::Socket;
# use AnyEvent::Strict; # only use this for debugging
use AnyEvent::TLS;
use PVE::INotify;
use PVE::SafeSyslog;
-use PVE::Tools;
+use PVE::Tools qw(trim);
use PVE::APIServer::Formatter;
use PVE::APIServer::Utils;
$proxyhdl->{block_disconnect} = 1 if length $proxyhdl->{wbuf};
$proxyhdl->push_shutdown();
- }
+ }
$hdl->push_shutdown();
} elsif ($opcode == 9) {
# ping received, schedule pong
my $content;
if ($method eq 'POST' || $method eq 'PUT') {
- $headers->{'Content-Type'} = 'application/x-www-form-urlencoded';
- # use URI object to format application/x-www-form-urlencoded content.
- my $url = URI->new('http:');
- $url->query_form(%$params);
- $content = $url->query;
+ my $request_ct = $reqstate->{request}->header('Content-Type');
+ if (defined($request_ct) && $request_ct =~ 'application/json') {
+ $headers->{'Content-Type'} = 'application/json';
+ $content = encode_json($params);
+ } else {
+ $headers->{'Content-Type'} = 'application/x-www-form-urlencoded';
+ # use URI object to format application/x-www-form-urlencoded content.
+ my $url = URI->new('http:');
+ $url->query_form(%$params);
+ $content = $url->query;
+ }
if (defined($content)) {
$headers->{'Content-Length'} = length($content);
}
sslv2 => 0,
sslv3 => 0,
verify => 1,
+ ca_path => '/usr/lib/ssl/certs', # to avoid loading the combined CA cert file
verify_cb => sub {
my (undef, undef, undef, $depth, undef, undef, $cert) = @_;
# we don't care about intermediate or root certificates
$v = Encode::decode('utf8', $v);
if (defined(my $old = $res->{$k})) {
- $v = "$old\0$v";
+ if (ref($old) eq 'ARRAY') {
+ push @$old, $v;
+ $v = $old;
+ } else {
+ $v = [$old, $v];
+ }
}
}
$res->{proxy_params}->{tmpfilename} = $reqstate->{tmpfilename} if $upload_state;
- $self->proxy_request($reqstate, $clientip, $host, $res->{proxynode}, $method,
- $r->uri, $auth, $res->{proxy_params});
+ $self->proxy_request(
+ $reqstate, $clientip, $host, $res->{proxynode}, $method, $r->uri, $auth, $res->{proxy_params});
return;
} elsif ($upgrade && ($method eq 'GET') && ($path =~ m|websocket$|)) {
my $download = $res->{download};
$download //= $res->{data}->{download}
- if defined($res->{data}) && ref($res->{data}) eq 'HASH';
+ if defined($res->{data}) && ref($res->{data}) eq 'HASH';
if (defined($download)) {
send_file_start($self, $reqstate, $download);
return;
my $clientip = $reqstate->{peer_host};
my $r = $reqstate->{request};
- my $remip;
+ my $remip;
- if ($node ne 'localhost' && PVE::INotify::nodename() !~ m/^$node$/i) {
- $remip = $self->remote_node_ip($node);
+ if ($node ne 'localhost' && PVE::INotify::nodename() !~ m/^$node$/i) {
+ $remip = $self->remote_node_ip($node);
$self->dprint("REMOTE CONNECT $vmid, $remip, $connect_str");
- } else {
+ } else {
$self->dprint("CONNECT $vmid, $node, $spiceport");
}
$reqstate->{hdl}->push_write($line);
$self->client_do_disconnect($reqstate);
}
- });
+ });
} else {
&$startproxy();
}
sub file_upload_multipart {
my ($self, $reqstate, $auth, $method, $path, $rstate) = @_;
- my $trim = sub {
- $_[0] =~ /\s*(\S+)/;
- return $1;
- };
-
eval {
my $boundary = $rstate->{boundary};
my $hdl = $reqstate->{hdl};
my $newline_re = qr/\015?\012/;
my $delim_re = qr/--\Q$boundary\E${newline_re}/;
- my $close_delim_re = qr/--\Q$boundary\E--${newline_re}/;
+ my $close_delim_re = qr/--\Q$boundary\E--/;
# Phase 0 - preserve boundary, but remove everything before
if ($rstate->{phase} == 0 && $hdl->{rbuf} =~ s/^.*?($delim_re)/$1/s) {
$rstate->{phase} = 1;
}
+ my $remove_until_data = sub {
+ my ($hdl) = @_;
+ # remove any remaining multipart "headers" like Content-Type
+ $hdl->{rbuf} =~ s/^.*?${newline_re}{2}//s;
+ };
+
my $extract_form_disposition = sub {
my ($name) = @_;
- if ($hdl->{rbuf} =~ s/^${delim_re}Content-Disposition: (.*?); name="$name"(.*?)($delim_re)/$3/s) {
+ if ($hdl->{rbuf} =~ s/^${delim_re}.*?Content-Disposition: (.*?); name="$name"(.*?${delim_re})/$2/s) {
assert_form_disposition($1);
- $rstate->{params}->{$name} = $trim->($2);
+ $remove_until_data->($hdl);
+ $hdl->{rbuf} =~ s/^(.*?)(${delim_re})/$2/s;
+ $rstate->{params}->{$name} = trim($1);
}
};
$extract_form_disposition->('checksum-algorithm');
$extract_form_disposition->('checksum');
- if ($hdl->{rbuf} =~
- s/^${delim_re}
- Content-Disposition:\ (.*?);\ name="(.*?)";\ filename="([^"]+)"${newline_re}
- Content-Type:\ \S*\s+
- //sxx
- ) {
+ if ($hdl->{rbuf} =~ s/^${delim_re}Content-Disposition: (.*?); name="(.*?)"; filename="([^"]+)"//s) {
assert_form_disposition($1);
die "wrong field name '$2' for file upload, expected 'filename'" if $2 ne "filename";
$rstate->{phase} = 2;
- $rstate->{params}->{filename} = $trim->($3);
+ $rstate->{params}->{filename} = trim($3);
+ $remove_until_data->($hdl); # any remaining multipart "headers" like Content-Type
}
}
if ($write_length > 0) {
syswrite($rstate->{outfh}, $data) == $write_length or die "write to temporary file failed - $!\n";
$rstate->{bytes} += $write_length;
- $rstate->{ctx}->add($data);
}
}
if ($rstate->{phase} == 100) { # Phase 100 - transfer finished
- $rstate->{md5sum} = $rstate->{ctx}->hexdigest;
my $elapsed = tv_interval($rstate->{starttime});
- syslog('info', "multipart upload complete (size: %dB time: %.3fs rate: %.2fMiB/s md5sum: %s)",
- $rstate->{bytes}, $elapsed, $rstate->{bytes} / ($elapsed * 1024 * 1024), $rstate->{md5sum}
+ syslog('info', "multipart upload complete (size: %dB time: %.3fs rate: %.2fMiB/s filename: %s)",
+ $rstate->{bytes}, $elapsed, $rstate->{bytes} / ($elapsed * 1024 * 1024),
+ $rstate->{params}->{filename}
);
$self->handle_api2_request($reqstate, $auth, $method, $path, $rstate);
}
return wantarray ? ($ct) : $ct;
}
-sub parse_content_disposition {
- my ($line) = @_;
-
- my ($disp, @params) = split(/\s*[;,]\s*/o, $line);
- my $name;
- my $filename;
-
- foreach my $v (@params) {
- if ($v =~ m/^\s*name\s*=\s*(\S+?)\s*$/o) {
- $name = $1;
- $name =~ s/^"(.*)"$/$1/;
- } elsif ($v =~ m/^\s*filename\s*=\s*(.+?)\s*$/o) {
- $filename = $1;
- $filename =~ s/^"(.*)"$/$1/;
- }
- }
-
- return wantarray ? ($disp, $name, $filename) : $disp;
-}
-
my $tmpfile_seq_no = 0;
sub get_upload_filename {
my $r = $reqstate->{request};
if ($line eq '') {
- my $path = uri_unescape($r->uri->path());
- my $method = $r->method();
-
$r->push_header($state->{key}, $state->{val})
if $state->{key};
- if (!$known_methods->{$method}) {
- my $resp = HTTP::Response->new(HTTP_NOT_IMPLEMENTED, "method '$method' not available");
- $self->response($reqstate, $resp);
- return;
- }
+ return if !$self->process_header($reqstate);
+ return if !$self->ensure_tls_connection($reqstate);
- my $conn = $r->header('Connection');
- my $accept_enc = $r->header('Accept-Encoding');
- $reqstate->{accept_gzip} = ($accept_enc && $accept_enc =~ m/gzip/) ? 1 : 0;
+ $self->authenticate_and_handle_request($reqstate);
- if ($conn) {
- $reqstate->{keep_alive} = 0 if $conn =~ m/close/oi;
- } else {
- if ($reqstate->{proto}->{ver} < 1001) {
- $reqstate->{keep_alive} = 0;
- }
- }
+ } elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) {
+ $r->push_header($state->{key}, $state->{val}) if $state->{key};
+ ($state->{key}, $state->{val}) = ($1, $2);
+ $self->unshift_read_header($reqstate, $state);
+ } elsif ($line =~ /^\s+(.*)/) {
+ $state->{val} .= " $1";
+ $self->unshift_read_header($reqstate, $state);
+ } else {
+ $self->error($reqstate, 506, "unable to parse request header");
+ }
+ };
+ warn $@ if $@;
+ });
+};
- my $te = $r->header('Transfer-Encoding');
- if ($te && lc($te) eq 'chunked') {
- # Handle chunked transfer encoding
- $self->error($reqstate, 501, "chunked transfer encoding not supported");
- return;
- } elsif ($te) {
- $self->error($reqstate, 501, "Unknown transfer encoding '$te'");
- return;
- }
+# sends an (error) response and returns 0 in case of errors
+sub process_header {
+ my ($self, $reqstate) = @_;
- my $pveclientip = $r->header('PVEClientIP');
- my $base_uri = $self->{base_uri};
+ my $request = $reqstate->{request};
- # fixme: how can we make PVEClientIP header trusted?
- if ($self->{trusted_env} && $pveclientip) {
- $reqstate->{peer_host} = $pveclientip;
- } else {
- $r->header('PVEClientIP', $reqstate->{peer_host});
- }
+ my $path = uri_unescape($request->uri->path());
+ my $method = $request->method();
- my $len = $r->header('Content-Length');
+ if (!$known_methods->{$method}) {
+ my $resp = HTTP::Response->new(HTTP_NOT_IMPLEMENTED, "method '$method' not available");
+ $self->response($reqstate, $resp);
+ return 0;
+ }
- my $host_header = $r->header('Host');
- if (my $rpcenv = $self->{rpcenv}) {
- $rpcenv->set_request_host($host_header);
- }
+ my $conn = $request->header('Connection');
+ my $accept_enc = $request->header('Accept-Encoding');
+ $reqstate->{accept_gzip} = ($accept_enc && $accept_enc =~ m/gzip/) ? 1 : 0;
- # header processing complete - authenticate now
+ if ($conn) {
+ $reqstate->{keep_alive} = 0 if $conn =~ m/close/oi;
+ } else {
+ if ($reqstate->{proto}->{ver} < 1001) {
+ $reqstate->{keep_alive} = 0;
+ }
+ }
- my $auth = {};
- if ($self->{spiceproxy}) {
- my $connect_str = $host_header;
- my ($vmid, $node, $port) = $self->verify_spice_connect_url($connect_str);
- if (!(defined($vmid) && $node && $port)) {
- $self->error($reqstate, HTTP_UNAUTHORIZED, "invalid ticket");
- return;
- }
- $self->handle_spice_proxy_request($reqstate, $connect_str, $vmid, $node, $port);
- return;
- } elsif ($path =~ m/^\Q$base_uri\E/) {
- my $token = $r->header('CSRFPreventionToken');
- my $cookie = $r->header('Cookie');
- my $auth_header = $r->header('Authorization');
-
- # prefer actual cookie
- my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $self->{cookie_name});
-
- # fallback to cookie in 'Authorization' header
- $ticket = PVE::APIServer::Formatter::extract_auth_value($auth_header, $self->{cookie_name})
- if !$ticket;
-
- # finally, fallback to API token if no ticket has been provided so far
- my $api_token;
- $api_token = PVE::APIServer::Formatter::extract_auth_value($auth_header, $self->{apitoken_name})
- if !$ticket;
-
- my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
- if (!$format) {
- $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri");
- return;
- }
+ my $te = $request->header('Transfer-Encoding');
+ if ($te && lc($te) eq 'chunked') {
+ # Handle chunked transfer encoding
+ $self->error($reqstate, 501, "chunked transfer encoding not supported");
+ return 0;
+ } elsif ($te) {
+ $self->error($reqstate, 501, "Unknown transfer encoding '$te'");
+ return 0;
+ }
- eval {
- $auth = $self->auth_handler($method, $rel_uri, $ticket, $token, $api_token,
- $reqstate->{peer_host});
- };
- if (my $err = $@) {
- # HACK: see Note 1
- Net::SSLeay::ERR_clear_error();
- # always delay unauthorized calls by 3 seconds
- my $delay = 3;
-
- if (ref($err) eq "PVE::Exception") {
-
- $err->{code} ||= HTTP_INTERNAL_SERVER_ERROR,
- my $resp = HTTP::Response->new($err->{code}, $err->{msg});
- $self->response($reqstate, $resp, undef, 0, $delay);
-
- } elsif (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
- my ($raw, $ct, $nocomp) =
- $formatter->($path, $auth, $self->{formatter_config});
- my $resp;
- if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
- $resp = $raw;
- } else {
- $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, "Login Required");
- $resp->header("Content-Type" => $ct);
- $resp->content($raw);
- }
- $self->response($reqstate, $resp, undef, $nocomp, $delay);
- } else {
- my $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, $err);
- $self->response($reqstate, $resp, undef, 0, $delay);
- }
- return;
- }
- }
+ my $pveclientip = $request->header('PVEClientIP');
- $reqstate->{log}->{userid} = $auth->{userid};
+ # fixme: how can we make PVEClientIP header trusted?
+ if ($self->{trusted_env} && $pveclientip) {
+ $reqstate->{peer_host} = $pveclientip;
+ } else {
+ $request->header('PVEClientIP', $reqstate->{peer_host});
+ }
- if ($len) {
+ if (my $rpcenv = $self->{rpcenv}) {
+ $rpcenv->set_request_host($request->header('Host'));
+ }
- if (!($method eq 'PUT' || $method eq 'POST')) {
- $self->error($reqstate, 501, "Unexpected content for method '$method'");
- return;
- }
+ return 1;
+}
- my $ctype = $r->header('Content-Type');
- my ($ct, $boundary);
- ($ct, $boundary)= parse_content_type($ctype) if $ctype;
+# sends an (redirect) response, disconnects the client and returns 0 if
+# connection is not TLS-protected
+sub ensure_tls_connection {
+ my ($self, $reqstate) = @_;
- if ($auth->{isUpload} && !$self->{trusted_env}) {
- die "upload 'Content-Type '$ctype' not implemented\n"
- if !($boundary && $ct && ($ct eq 'multipart/form-data'));
+ # Skip if server doesn't use TLS
+ if (!$self->{tls_ctx}) {
+ return 1;
+ }
- die "upload without content length header not supported" if !$len;
+ # TLS session exists, so the handshake has succeeded
+ if ($reqstate->{hdl}->{tls}) {
+ return 1;
+ }
- die "upload without content length header not supported" if !$len;
+ my $request = $reqstate->{request};
+ my $method = $request->method();
- $self->dprint("start upload $path $ct $boundary");
+ my $h_host = $reqstate->{request}->header('Host');
- my $tmpfilename = get_upload_filename();
- my $outfh = IO::File->new($tmpfilename, O_RDWR|O_CREAT|O_EXCL, 0600) ||
- die "unable to create temporary upload file '$tmpfilename'";
+ die "Header field 'Host' not found in request\n"
+ if !$h_host;
- $reqstate->{keep_alive} = 0;
+ my $secure_host = "https://" . ($h_host =~ s/^http(s)?:\/\///r);
- my $boundlen = length($boundary) + 8; # \015?\012--$boundary--\015?\012
+ my $header = HTTP::Headers->new('Location' => $secure_host . $request->uri());
- my $state = {
- size => $len,
- boundary => $boundary,
- ctx => Digest::MD5->new,
- boundlen => $boundlen,
- maxheader => 2048 + $boundlen, # should be large enough
- params => decode_urlencoded($r->url->query()),
- phase => 0,
- read => 0,
- post_size => 0,
- starttime => [gettimeofday],
- outfh => $outfh,
- };
- $reqstate->{tmpfilename} = $tmpfilename;
- $reqstate->{hdl}->on_read(sub {
- $self->file_upload_multipart($reqstate, $auth, $method, $path, $state);
- });
- return;
- }
+ if ($method eq 'GET' || $method eq 'HEAD') {
+ $self->error($reqstate, 301, 'Moved Permanently', $header);
+ } else {
+ $self->error($reqstate, 308, 'Permanent Redirect', $header);
+ }
- if ($len > $limit_max_post) {
- $self->error($reqstate, 501, "for data too large");
- return;
- }
+ # disconnect the client so they may immediately connect again via HTTPS
+ $self->client_do_disconnect($reqstate);
+
+ return 0;
+}
+
+sub authenticate_and_handle_request {
+ my ($self, $reqstate) = @_;
+
+ my $request = $reqstate->{request};
+ my $method = $request->method();
+
+ my $path = uri_unescape($request->uri->path());
+ my $base_uri = $self->{base_uri};
+
+ my $auth = {};
+
+ if ($self->{spiceproxy}) {
+ my $connect_str = $request->header('Host');
+ my ($vmid, $node, $port) = $self->verify_spice_connect_url($connect_str);
+
+ if (!(defined($vmid) && $node && $port)) {
+ $self->error($reqstate, HTTP_UNAUTHORIZED, "invalid ticket");
+ return;
+ }
+
+ $self->handle_spice_proxy_request($reqstate, $connect_str, $vmid, $node, $port);
+ return;
+
+ } elsif ($path =~ m/^\Q$base_uri\E/) {
+ my $token = $request->header('CSRFPreventionToken');
+ my $cookie = $request->header('Cookie');
+ my $auth_header = $request->header('Authorization');
+
+ # prefer actual cookie
+ my $ticket = PVE::APIServer::Formatter::extract_auth_value(
+ $cookie,
+ $self->{cookie_name}
+ );
+
+ # fallback to cookie in 'Authorization' header
+ if (!$ticket) {
+ $ticket = PVE::APIServer::Formatter::extract_auth_value(
+ $auth_header,
+ $self->{cookie_name}
+ );
+ }
+
+ # finally, fallback to API token if no ticket has been provided so far
+ my $api_token;
+ if (!$ticket) {
+ $api_token = PVE::APIServer::Formatter::extract_auth_value(
+ $auth_header,
+ $self->{apitoken_name}
+ );
+ }
+
+ my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
+ if (!$format) {
+ $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri");
+ return;
+ }
+
+ eval {
+ $auth = $self->auth_handler(
+ $method,
+ $rel_uri,
+ $ticket,
+ $token,
+ $api_token,
+ $reqstate->{peer_host}
+ );
+ };
+ if (my $err = $@) {
+ # HACK: see Note 1
+ Net::SSLeay::ERR_clear_error();
+ # always delay unauthorized calls by 3 seconds
+ my $delay = 3;
+
+ if (ref($err) eq "PVE::Exception") {
+
+ $err->{code} ||= HTTP_INTERNAL_SERVER_ERROR,
+ my $resp = HTTP::Response->new($err->{code}, $err->{msg});
+ $self->response($reqstate, $resp, undef, 0, $delay);
+
+ } elsif (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
+ my ($raw, $ct, $nocomp) =
+ $formatter->($path, $auth, $self->{formatter_config});
+
+ my $resp;
+ if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
+ $resp = $raw;
- if (!$ct || $ct eq 'application/x-www-form-urlencoded' || $ct eq 'application/json') {
- $reqstate->{hdl}->unshift_read(chunk => $len, sub {
- my ($hdl, $data) = @_;
- $r->content($data);
- $self->handle_request($reqstate, $auth, $method, $path);
- });
- } else {
- $self->error($reqstate, 506, "upload 'Content-Type '$ctype' not implemented");
- }
} else {
- $self->handle_request($reqstate, $auth, $method, $path);
+ $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, "Login Required");
+ $resp->header("Content-Type" => $ct);
+ $resp->content($raw);
}
- } elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) {
- $r->push_header($state->{key}, $state->{val}) if $state->{key};
- ($state->{key}, $state->{val}) = ($1, $2);
- $self->unshift_read_header($reqstate, $state);
- } elsif ($line =~ /^\s+(.*)/) {
- $state->{val} .= " $1";
- $self->unshift_read_header($reqstate, $state);
+
+ $self->response($reqstate, $resp, undef, $nocomp, $delay);
+
} else {
- $self->error($reqstate, 506, "unable to parse request header");
+ my $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, $err);
+ $self->response($reqstate, $resp, undef, 0, $delay);
}
- };
- warn $@ if $@;
- });
-};
+
+ return;
+ }
+ }
+
+ $reqstate->{log}->{userid} = $auth->{userid};
+ my $len = $request->header('Content-Length');
+
+ if ($len) {
+
+ if (!($method eq 'PUT' || $method eq 'POST')) {
+ $self->error($reqstate, 501, "Unexpected content for method '$method'");
+ return;
+ }
+
+ my $ctype = $request->header('Content-Type');
+ my ($ct, $boundary) = $ctype ? parse_content_type($ctype) : ();
+
+ if ($auth->{isUpload} && !$self->{trusted_env}) {
+ die "upload 'Content-Type '$ctype' not implemented\n"
+ if !($boundary && $ct && ($ct eq 'multipart/form-data'));
+
+ die "upload without content length header not supported" if !$len;
+
+ die "upload without content length header not supported" if !$len;
+
+ $self->dprint("start upload $path $ct $boundary");
+
+ my $tmpfilename = get_upload_filename();
+ my $outfh = IO::File->new($tmpfilename, O_RDWR|O_CREAT|O_EXCL, 0600) ||
+ die "unable to create temporary upload file '$tmpfilename'";
+
+ $reqstate->{keep_alive} = 0;
+
+ my $boundlen = length($boundary) + 8; # \015?\012--$boundary--\015?\012
+
+ my $state = {
+ size => $len,
+ boundary => $boundary,
+ boundlen => $boundlen,
+ maxheader => 2048 + $boundlen, # should be large enough
+ params => decode_urlencoded($request->url->query()),
+ phase => 0,
+ read => 0,
+ post_size => 0,
+ starttime => [gettimeofday],
+ outfh => $outfh,
+ };
+
+ die "'tmpfilename' query parameter is not allowed for file uploads\n"
+ if exists $state->{params}->{tmpfilename};
+
+ $reqstate->{tmpfilename} = $tmpfilename;
+ $reqstate->{hdl}->on_read(sub {
+ $self->file_upload_multipart($reqstate, $auth, $method, $path, $state);
+ });
+
+ return;
+ }
+
+ if ($len > $limit_max_post) {
+ $self->error($reqstate, 501, "for data too large");
+ return;
+ }
+
+ if (!$ct || $ct eq 'application/x-www-form-urlencoded' || $ct eq 'application/json') {
+ $reqstate->{hdl}->unshift_read(chunk => $len, sub {
+ my ($hdl, $data) = @_;
+ $request->content($data);
+ $self->handle_request($reqstate, $auth, $method, $path);
+ });
+
+ } else {
+ $self->error($reqstate, 506, "upload 'Content-Type '$ctype' not implemented");
+ }
+
+ } else {
+ $self->handle_request($reqstate, $auth, $method, $path);
+ }
+}
sub push_request_header {
my ($self, $reqstate) = @_;
$reqstate->{proto}->{min} = $min;
$reqstate->{proto}->{ver} = $maj*1000+$min;
$reqstate->{request} = HTTP::Request->new($method, $url);
- $reqstate->{starttime} = [gettimeofday],
+ $reqstate->{starttime} = [gettimeofday];
$self->unshift_read_header($reqstate);
} elsif ($line eq '') {
};
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);
}
};
warn "Failed to set TLS 1.3 ciphersuites '$ciphersuites'\n"
if !Net::SSLeay::CTX_set_ciphersuites($self->{tls_ctx}->{ctx}, $ciphersuites);
}
+
+ my $opts = Net::SSLeay::CTX_get_options($self->{tls_ctx}->{ctx});
+ my $min_version = Net::SSLeay::TLS1_1_VERSION();
+ my $max_version = Net::SSLeay::TLS1_3_VERSION();
+ if ($opts & &Net::SSLeay::OP_NO_TLSv1_1) {
+ $min_version = Net::SSLeay::TLS1_2_VERSION();
+ }
+ if ($opts & &Net::SSLeay::OP_NO_TLSv1_2) {
+ $min_version = Net::SSLeay::TLS1_3_VERSION();
+ }
+ if ($opts & &Net::SSLeay::OP_NO_TLSv1_3) {
+ die "misconfigured TLS settings - cannot disable all supported TLS versions!\n"
+ if $min_version && $min_version == Net::SSLeay::TLS1_3_VERSION();
+ $max_version = Net::SSLeay::TLS1_2_VERSION();
+ }
+ Net::SSLeay::CTX_set_min_proto_version($self->{tls_ctx}->{ctx}, $min_version) if $min_version;
+ Net::SSLeay::CTX_set_max_proto_version($self->{tls_ctx}->{ctx}, $max_version);
}
if ($self->{spiceproxy}) {