$reqstate->{hdl}->timeout_reset();
$reqstate->{hdl}->timeout($self->{timeout});
+ $nocomp = 1 if !$self->{compression};
$nocomp = 1 if !$reqstate->{accept_gzip};
my $code = $resp->code;
};
sub send_file_start {
- my ($self, $reqstate, $filename) = @_;
+ my ($self, $reqstate, $download) = @_;
eval {
# print "SEND FILE $filename\n";
my $r = $reqstate->{request};
- my $fh = IO::File->new($filename, '<') ||
- die "$!\n";
+ my $fh;
+ my $nocomp;
+ my $mime;
+
+ if (ref($download) eq 'HASH') {
+ $fh = $download->{fh};
+ $mime = $download->{'content-type'};
+ } else {
+ my $filename = $download;
+ $fh = IO::File->new($filename, '<') ||
+ die "unable to open file '$filename' - $!\n";
+
+ my ($ext) = $filename =~ m/\.([^.]*)$/;
+ my $ext_info = $file_extension_info->{$ext};
+
+ die "unable to detect content type" if !$ext_info;
+ $mime = $ext_info->{ct};
+ $nocomp = $ext_info->{nocomp};
+ }
+
my $stat = File::stat::stat($fh) ||
die "$!\n";
my $len = sysread($fh, $data, $stat->size);
die "got short file\n" if !defined($len) || $len != $stat->size;
- my ($ext) = $filename =~ m/\.([^.]*)$/;
- my $ext_info = $file_extension_info->{$ext};
-
- die "unable to detect content type" if !$ext_info;
-
- my $header = HTTP::Headers->new(Content_Type => $ext_info->{ct});
+ my $header = HTTP::Headers->new(Content_Type => $mime);
my $resp = HTTP::Response->new(200, "OK", $header, $data);
- $self->response($reqstate, $resp, $mtime, $ext_info->{nocomp});
+ $self->response($reqstate, $resp, $mtime, $nocomp);
};
if (my $err = $@) {
$self->error($reqstate, 501, $err);
my $remhost;
my $remport;
- my $max_payload_size = 65536;
+ my $max_payload_size = 128*1024;
my $binary;
if ($wsproto eq 'binary') {
$reqstate->{proxyhdl} = AnyEvent::Handle->new(
fh => $fh,
- rbuf_max => 64*1024,
- wbuf_max => 64*10*1024,
+ rbuf_max => $max_payload_size,
+ wbuf_max => $max_payload_size*5,
timeout => 5,
on_eof => sub {
my ($hdl) = @_;
my ($hdl) = @_;
my $len = length($hdl->{rbuf});
- my $data = substr($hdl->{rbuf}, 0, $len, '');
+ my $data = substr($hdl->{rbuf}, 0, $len > $max_payload_size ? $max_payload_size : $len, '');
my $string;
my $payload;
my $hdlreader = sub {
my ($hdl) = @_;
- my $len = length($hdl->{rbuf});
- return if $len < 2;
+ while (my $len = length($hdl->{rbuf})) {
+ return if $len < 2;
- my $hdr = unpack('C', substr($hdl->{rbuf}, 0, 1));
- my $opcode = $hdr & 0b00001111;
- my $fin = $hdr & 0b10000000;
+ my $hdr = unpack('C', substr($hdl->{rbuf}, 0, 1));
+ my $opcode = $hdr & 0b00001111;
+ my $fin = $hdr & 0b10000000;
- die "received fragmented websocket frame\n" if !$fin;
+ die "received fragmented websocket frame\n" if !$fin;
- my $rsv = $hdr & 0b01110000;
- die "received websocket frame with RSV flags\n" if $rsv;
+ my $rsv = $hdr & 0b01110000;
+ die "received websocket frame with RSV flags\n" if $rsv;
- my $payload_len = unpack 'C', substr($hdl->{rbuf}, 1, 1);
+ my $payload_len = unpack 'C', substr($hdl->{rbuf}, 1, 1);
- my $masked = $payload_len & 0b10000000;
- die "received unmasked websocket frame from client\n" if !$masked;
+ my $masked = $payload_len & 0b10000000;
+ die "received unmasked websocket frame from client\n" if !$masked;
- my $offset = 2;
- $payload_len = $payload_len & 0b01111111;
- if ($payload_len == 126) {
- return if $len < 4;
- $payload_len = unpack('n', substr($hdl->{rbuf}, $offset, 2));
- $offset += 2;
- } elsif ($payload_len == 127) {
- return if $len < 10;
- $payload_len = unpack('Q>', substr($hdl->{rbuf}, $offset, 8));
- $offset += 8;
- }
+ my $offset = 2;
+ $payload_len = $payload_len & 0b01111111;
+ if ($payload_len == 126) {
+ return if $len < 4;
+ $payload_len = unpack('n', substr($hdl->{rbuf}, $offset, 2));
+ $offset += 2;
+ } elsif ($payload_len == 127) {
+ return if $len < 10;
+ $payload_len = unpack('Q>', substr($hdl->{rbuf}, $offset, 8));
+ $offset += 8;
+ }
- die "received too large websocket frame (len = $payload_len)\n"
- if ($payload_len > $max_payload_size) || ($payload_len < 0);
+ die "received too large websocket frame (len = $payload_len)\n"
+ if ($payload_len > $max_payload_size) || ($payload_len < 0);
- return if $len < ($offset + 4 + $payload_len);
+ return if $len < ($offset + 4 + $payload_len);
- my $data = substr($hdl->{rbuf}, 0, $len, ''); # now consume data
+ my $data = substr($hdl->{rbuf}, 0, $offset + 4 + $payload_len, ''); # now consume data
- my @mask = (unpack('C', substr($data, $offset+0, 1)),
- unpack('C', substr($data, $offset+1, 1)),
- unpack('C', substr($data, $offset+2, 1)),
- unpack('C', substr($data, $offset+3, 1)));
+ my @mask = (unpack('C', substr($data, $offset+0, 1)),
+ unpack('C', substr($data, $offset+1, 1)),
+ unpack('C', substr($data, $offset+2, 1)),
+ unpack('C', substr($data, $offset+3, 1)));
- $offset += 4;
+ $offset += 4;
- my $payload = substr($data, $offset, $payload_len);
+ my $payload = substr($data, $offset, $payload_len);
- for (my $i = 0; $i < $payload_len; $i++) {
- my $d = unpack('C', substr($payload, $i, 1));
- my $n = $d ^ $mask[$i % 4];
- substr($payload, $i, 1, pack('C', $n));
- }
+ for (my $i = 0; $i < $payload_len; $i++) {
+ my $d = unpack('C', substr($payload, $i, 1));
+ my $n = $d ^ $mask[$i % 4];
+ substr($payload, $i, 1, pack('C', $n));
+ }
- $payload = decode_base64($payload) if !$binary;
+ $payload = decode_base64($payload) if !$binary;
- if ($opcode == 1 || $opcode == 2) {
- $reqstate->{proxyhdl}->push_write($payload) if $reqstate->{proxyhdl};
- } elsif ($opcode == 8) {
- my $statuscode = unpack ("n", $payload);
- print "websocket received close. status code: '$statuscode'\n" if $self->{debug};
+ if ($opcode == 1 || $opcode == 2) {
+ $reqstate->{proxyhdl}->push_write($payload) if $reqstate->{proxyhdl};
+ } elsif ($opcode == 8) {
+ my $statuscode = unpack ("n", $payload);
+ print "websocket received close. status code: '$statuscode'\n" if $self->{debug};
if ($reqstate->{proxyhdl}) {
- $reqstate->{proxyhdl}->push_shutdown();
+ $reqstate->{proxyhdl}->push_shutdown();
+ }
+ $hdl->push_shutdown();
+ } else {
+ die "received unhandled websocket opcode $opcode\n";
}
- $hdl->push_shutdown();
- } else {
- die "received unhandled websocket opcode $opcode\n";
}
};
$headers->{'cookie'} = PVE::APIServer::Formatter::create_auth_cookie($ticket, $self->{cookie_name}) if $ticket;
$headers->{'CSRFPreventionToken'} = $token if $token;
- $headers->{'Accept-Encoding'} = 'gzip' if $reqstate->{accept_gzip};
+ $headers->{'Accept-Encoding'} = 'gzip' if ($reqstate->{accept_gzip} && $self->{compression});
+
+ if (defined(my $host = $reqstate->{request}->header('Host'))) {
+ $headers->{Host} = $host;
+ }
my $content;
my ($k, $v) = split(/=/, $kv);
$k =~s/\+/ /g;
$k =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
- $v =~s/\+/ /g;
- $v =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
- $v = Encode::decode('utf8', $v);
+ if (defined($v)) {
+ $v =~s/\+/ /g;
+ $v =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
- if (defined(my $old = $res->{$k})) {
- $res->{$k} = "$old\0$v";
- } else {
- $res->{$k} = $v;
+ $v = Encode::decode('utf8', $v);
+
+ if (defined(my $old = $res->{$k})) {
+ $v = "$old\0$v";
+ }
}
+
+ $res->{$k} = $v;
}
return $res;
}
$res->{proxy_params}->{tmpfilename} = $reqstate->{tmpfilename} if $upload_state;
$self->proxy_request($reqstate, $clientip, $host, $res->{proxynode}, $method,
- $r->uri, $auth->{ticket}, $auth->{token}, $res->{proxy_params}, $res->{proxynode});
+ $r->uri, $auth->{ticket}, $auth->{token}, $res->{proxy_params});
return;
} elsif ($upgrade && ($method eq 'GET') && ($path =~ m|websocket$|)) {
$delay = 0 if $delay < 0;
}
- if (defined(my $filename = $res->{download})) {
- my $fh = IO::File->new($filename) ||
- die "unable to open file '$filename' - $!\n";
- send_file_start($self, $reqstate, $filename);
+ if (defined(my $download = $res->{download})) {
+ send_file_start($self, $reqstate, $download);
return;
}
# todo: use stop_read/start_read if write buffer grows to much
- my $res = "$proto 200 OK\015\012"; # hope this is the right answer?
+ # a response must be followed by an empty line
+ my $res = "$proto 200 OK\015\012\015\012";
$reqstate->{hdl}->push_write($res);
# log early
my ($hdl, $line) = @_;
if ($line =~ m!^$proto 200 OK$!) {
- &$startproxy();
+ # read the empty line after the 200 OK
+ $reqstate->{proxyhdl}->unshift_read(line => sub{
+ &$startproxy();
+ });
} else {
$reqstate->{hdl}->push_write($line);
$self->client_do_disconnect($reqstate);
my $len = $r->header('Content-Length');
+ my $host_header = $r->header('Host');
+ if (my $rpcenv = $self->{rpcenv}) {
+ $rpcenv->set_request_host($host_header);
+ }
+
# header processing complete - authenticate now
my $auth = {};
if ($self->{spiceproxy}) {
- my $connect_str = $r->header('Host');
+ 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");
Net::SSLeay::ERR_clear_error();
# always delay unauthorized calls by 3 seconds
my $delay = 3;
- if (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
+
+ 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;
$self->{base_uri} //= "/api2";
$self->{dirs} //= {};
$self->{title} //= 'API Inspector';
+ $self->{compression} //= 1;
# formatter_config: we pass some configuration values to the Formatter
$self->{formatter_config} = {};
$self->can('generate_csrf_prevention_token');
# add default dirs which includes jquery and bootstrap
- my $base = '/usr/share/libpve-http-server-perl';
- add_dirs($self->{dirs}, '/css/' => "$base/css/");
- add_dirs($self->{dirs}, '/js/' => "$base/js/");
- add_dirs($self->{dirs}, '/fonts/' => "$base/fonts/");
+ my $jsbase = '/usr/share/javascript';
+ add_dirs($self->{dirs}, '/js/' => "$jsbase/");
+ # libjs-bootstrap uses symlinks for this, which we do not want to allow..
+ my $glyphicons = '/usr/share/fonts/truetype/glyphicons/';
+ add_dirs($self->{dirs}, '/js/bootstrap/fonts/' => "$glyphicons");
# init inotify
PVE::INotify::inotify_init();
$self->{end_cond} = AnyEvent->condvar;
if ($self->{ssl}) {
+ my $ssl_defaults = {
+ # Note: older versions are considered insecure, for example
+ # search for "Poodle"-Attack
+ method => 'any',
+ sslv2 => 0,
+ sslv3 => 0,
+ cipher_list => 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256',
+ honor_cipher_order => 1,
+ };
+
+ foreach my $k (keys %$ssl_defaults) {
+ $self->{ssl}->{$k} //= $ssl_defaults->{$k};
+ }
+
+ if (!defined($self->{ssl}->{dh_file})) {
+ $self->{ssl}->{dh} = 'skip2048';
+ }
+
+ my $tls_ctx_flags = &Net::SSLeay::OP_NO_COMPRESSION | &Net::SSLeay::OP_SINGLE_ECDH_USE | &Net::SSLeay::OP_SINGLE_DH_USE;
+ if ( delete $self->{ssl}->{honor_cipher_order} ) {
+ $tls_ctx_flags |= &Net::SSLeay::OP_CIPHER_SERVER_PREFERENCE;
+ }
+
$self->{tls_ctx} = AnyEvent::TLS->new(%{$self->{ssl}});
- Net::SSLeay::CTX_set_options($self->{tls_ctx}->{ctx}, &Net::SSLeay::OP_NO_COMPRESSION | &Net::SSLeay::OP_SINGLE_ECDH_USE | &Net::SSLeay::OP_SINGLE_DH_USE);
+ Net::SSLeay::CTX_set_options($self->{tls_ctx}->{ctx}, $tls_ctx_flags);
}
if ($self->{spiceproxy}) {