package PVE::APIServer::AnyEvent;
+# Note 1: interactions with Crypt::OpenSSL::RSA
+#
+# Some handlers (auth_handler) use Crypt::OpenSSL::RSA, which seems to
+# set the openssl error variable. We need to clear that here, else
+# AnyEvent::TLS aborts the connection.
+# Net::SSLeay::ERR_clear_error();
+
use strict;
use warnings;
use Time::HiRes qw(usleep ualarm gettimeofday tv_interval);
use Fcntl;
use IO::File;
use File::stat qw();
+use File::Find;
use MIME::Base64;
use Digest::MD5;
use Digest::SHA;
use AnyEvent::HTTP;
use Fcntl ();
use Compress::Zlib;
+use Encode;
use PVE::SafeSyslog;
use PVE::INotify;
use PVE::Tools;
+use PVE::APIServer::Formatter;
use Net::IP;
use URI;
my $limit_max_headers = 30;
my $limit_max_header_size = 8*1024;
-my $limit_max_post = 16*1024;
-
+my $limit_max_post = 64*1024;
my $known_methods = {
GET => 1,
return wantarray ? ($rel_uri, $format) : $rel_uri;
};
-# generic formatter support
-
-my $formatter_hash = {};
-
-sub register_formatter {
- my ($format, $func) = @_;
-
- die "formatter '$format' already defined" if $formatter_hash->{$format};
-
- $formatter_hash->{$format} = {
- func => $func,
- };
-}
-
-sub get_formatter {
- my ($format) = @_;
-
- return undef if !$format;
-
- my $info = $formatter_hash->{$format};
- return undef if !$info;
-
- return $info->{func};
-}
-
-my $login_formatter_hash = {};
-
-sub register_login_formatter {
- my ($format, $func) = @_;
-
- die "login formatter '$format' already defined" if $login_formatter_hash->{$format};
-
- $login_formatter_hash->{$format} = {
- func => $func,
- };
-}
-
-sub get_login_formatter {
- my ($format) = @_;
-
- return undef if !$format;
-
- my $info = $login_formatter_hash->{$format};
- return undef if !$info;
-
- return $info->{func};
-}
-
-
-# server implementation
-
sub log_request {
my ($self, $reqstate) = @_;
my $content_length = defined($loginfo->{content_length}) ? $loginfo->{content_length} : '-';
my $code = $loginfo->{code} || 500;
my $requestline = $loginfo->{requestline} || '-';
- my $timestr = strftime("%d/%b/%Y:%H:%M:%S %z", localtime());
+ my $timestr = strftime("%d/%m/%Y:%H:%M:%S %z", localtime());
my $msg = "$peerip - $userid [$timestr] \"$requestline\" $code $content_length\n";
$self->log_request($reqstate);
}
-sub extract_auth_cookie {
- my ($cookie, $cookie_name) = @_;
-
- return undef if !$cookie;
-
- my $ticket = ($cookie =~ /(?:^|\s)\Q$cookie_name\E=([^;]*)/)[0];
-
- if ($ticket && $ticket =~ m/^PVE%3A/) {
- $ticket = uri_unescape($ticket);
- }
-
- return $ticket;
-}
-
-sub create_auth_cookie {
- my ($ticket, $cookie_name) = @_;
-
- my $encticket = uri_escape($ticket);
-
- return "${cookie_name}=$encticket; path=/; secure;";
-}
-
sub cleanup_reqstate {
my ($reqstate) = @_;
sub finish_response {
my ($self, $reqstate) = @_;
- my $hdl = $reqstate->{hdl};
-
cleanup_reqstate($reqstate);
+ my $hdl = $reqstate->{hdl};
+ return if !$hdl; # already disconnected
+
if (!$self->{end_loop} && $reqstate->{keep_alive} > 0) {
# print "KEEPALIVE $reqstate->{keep_alive}\n" if $self->{debug};
$hdl->on_read(sub {
$reqstate->{hdl}->timeout_reset();
$reqstate->{hdl}->timeout($self->{timeout});
+ $nocomp //= !$self->{compression};
$nocomp = 1 if !$reqstate->{accept_gzip};
my $code = $resp->code;
$res .= $content if $content;
$self->log_request($reqstate, $reqstate->{request});
-
+
if ($delay && $delay > 0) {
my $w; $w = AnyEvent->timer(after => $delay, cb => sub {
undef $w; # delete reference
css => { ct => 'text/css' },
html => { ct => 'text/html' },
js => { ct => 'application/javascript' },
+ json => { ct => 'application/json' },
+ map => { ct => 'application/json' },
png => { ct => 'image/png' , nocomp => 1 },
ico => { ct => 'image/x-icon', nocomp => 1},
gif => { ct => 'image/gif', nocomp => 1},
+ svg => { ct => 'image/svg+xml' },
jar => { ct => 'application/java-archive', nocomp => 1},
woff => { ct => 'application/font-woff', nocomp => 1},
woff2 => { ct => 'application/font-woff2', nocomp => 1},
ttf => { ct => 'application/font-snft', nocomp => 1},
pdf => { ct => 'application/pdf', nocomp => 1},
epub => { ct => 'application/epub+zip', nocomp => 1},
+ mp3 => { ct => 'audio/mpeg', nocomp => 1},
+ oga => { ct => 'audio/ogg', nocomp => 1},
+ tgz => { ct => 'application/x-compressed-tar', nocomp => 1},
};
sub send_file_start {
die "$!\n";
my $stat = File::stat::stat($fh) ||
die "$!\n";
-
+
my $mtime = $stat->mtime;
if (my $ifmod = $r->header('if-modified-since')) {
my $remhost;
my $remport;
- my $max_payload_size = 65536;
+ my $max_payload_size = 128*1024;
my $binary;
if ($wsproto eq 'binary') {
}
tcp_connect $remhost, $remport, sub {
- my ($fh) = @_
+ my ($fh) = @_
or die "connect to '$remhost:$remport' failed: $!";
-
+
print "$$: CONNECTed to '$remhost:$remport'\n" if $self->{debug};
$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 $proxyhdlreader = sub {
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;
if ($payload_len <= 125) {
$string .= pack 'C', $payload_len;
} elsif ($payload_len <= 0xffff) {
- $string .= pack 'C', 126;
+ $string .= pack 'C', 126;
$string .= pack 'n', $payload_len;
} else {
- $string .= pack 'C', 127;
+ $string .= pack 'C', 127;
$string .= pack 'Q>', $payload_len;
}
$string .= $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 @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 $data = substr($hdl->{rbuf}, 0, $offset + 4 + $payload_len, ''); # now consume data
- $offset += 4;
+ 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 $payload = substr($data, $offset, $payload_len);
+ $offset += 4;
- 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));
- }
+ my $payload = substr($data, $offset, $payload_len);
- $payload = decode_base64($payload) if !$binary;
+ 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));
+ }
- if ($opcode == 1 || $opcode == 2) {
- $reqstate->{proxyhdl}->push_write($payload) if $reqstate->{proxyhdl};
- } elsif ($opcode == 8) {
- print "websocket received close\n" if $self->{debug};
+ $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 ($reqstate->{proxyhdl}) {
- $reqstate->{proxyhdl}->push_write($payload);
- $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";
}
};
PVEClientIP => $clientip,
};
- $headers->{'cookie'} = create_auth_cookie($ticket, $self->{cookie_name}) if $ticket;
+ $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});
my $content;
}
# return arrays as \0 separated strings (like CGI.pm)
+# assume data is UTF8 encoded
sub decode_urlencoded {
my ($data) = @_;
$v =~s/\+/ /g;
$v =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
+ $v = Encode::decode('utf8', $v);
+
if (defined(my $old = $res->{$k})) {
$res->{$k} = "$old\0$v";
} else {
$res->{$k} = $v;
- }
+ }
}
return $res;
}
$params->{$k} = $query_params->{$k};
}
- return PVE::Tools::decode_utf8_parameters($params);
+ return $params;
}
sub handle_api2_request {
my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
- my $formatter = get_formatter($format);
+ my $formatter = PVE::APIServer::Formatter::get_formatter($format, $method, $rel_uri);
if (!defined($formatter)) {
- $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri $rel_uri, $format");
+ $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no formatter for uri $rel_uri, $format");
return;
}
#print Dumper($upload_state) if $upload_state;
- my $rpcenv = $self->{rpcenv};
-
my $params;
if ($upload_state) {
my $clientip = $reqstate->{peer_host};
- $rpcenv->init_request();
+ my $res = $self->rest_handler($clientip, $method, $rel_uri, $auth, $params, $format);
- my $res = $self->rest_handler($clientip, $method, $rel_uri, $auth, $params);
+ # HACK: see Note 1
+ Net::SSLeay::ERR_clear_error();
AnyEvent->now_update(); # in case somebody called sleep()
- $rpcenv->set_user(undef); # clear after request
-
my $upgrade = $r->header('upgrade');
$upgrade = lc($upgrade) if $upgrade;
$delay = 0 if $delay < 0;
}
- if ($res->{info} && $res->{info}->{formatter}) {
- if (defined(my $func = $res->{info}->{formatter}->{$format})) {
- $formatter = $func;
- }
+ 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);
+ return;
}
- my ($raw, $ct, $nocomp) = &$formatter($res, $res->{data}, $params, $path, $auth);
+ my ($raw, $ct, $nocomp) = $formatter->($res, $res->{data}, $params, $path,
+ $auth, $self->{formatter_config});
my $resp;
if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
die "Port $spiceport is not allowed" if ($spiceport < 61000 || $spiceport > 61099);
- my $rpcenv = $self->{rpcenv};
- $rpcenv->init_request();
-
my $clientip = $reqstate->{peer_host};
my $r = $reqstate->{request};
my $remport = $remip ? 3128 : $spiceport;
tcp_connect $remhost, $remport, sub {
- my ($fh) = @_
+ my ($fh) = @_
or die "connect to '$remhost:$remport' failed: $!";
print "$$: CONNECTed to '$remhost:$remport'\n" if $self->{debug};
# 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
$reqstate->{proxyhdl}->push_write($header);
$reqstate->{proxyhdl}->push_read(line => sub {
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);
eval {
my $r = $reqstate->{request};
-
+
# disable timeout on handle (we already have all data we need)
# we re-enable timeout in response()
$reqstate->{hdl}->timeout(0);
if (ref($handler) eq 'CODE') {
my $params = decode_urlencoded($r->url->query());
my ($resp, $userid) = &$handler($self, $reqstate->{request}, $params);
+ # HACK: see Note 1
+ Net::SSLeay::ERR_clear_error();
$self->response($reqstate, $resp);
} elsif (ref($handler) eq 'HASH') {
if (my $filename = $handler->{file}) {
}
}
- die "no such file '$path'";
+ die "no such file '$path'\n";
};
if (my $err = $@) {
$self->error($reqstate, 501, $err);
}
} else {
my $len = length($hdl->{rbuf});
- substr($hdl->{rbuf}, 0, $len - $rstate->{maxheader}, '')
+ substr($hdl->{rbuf}, 0, $len - $rstate->{maxheader}, '')
if $len > $rstate->{maxheader}; # skip garbage
}
} elsif ($rstate->{phase} == 1) { # inside file - dump until end marker
if ($hdl->{rbuf} =~ s/^(.*?)\015?\012(--\Q$boundary\E(--)? \015?\012(.*))$/$2/xs) {
my ($rest, $eof) = ($1, $3);
my $len = length($rest);
- die "write to temporary file failed - $!"
+ die "write to temporary file failed - $!"
if syswrite($rstate->{outfh}, $rest) != $len;
$rstate->{ctx}->add($rest);
$rstate->{params}->{filename} = $rstate->{filename};
my $wlen = $len - $rstate->{boundlen};
if ($wlen > 0) {
my $data = substr($hdl->{rbuf}, 0, $wlen, '');
- die "write to temporary file failed - $!"
+ die "write to temporary file failed - $!"
if syswrite($rstate->{outfh}, $data) != $wlen;
$rstate->{bytes} += $wlen;
$rstate->{ctx}->add($data);
$rstate->{phase} = -1; # skip
}
}
- } else { # skip
+ } else { # skip
my $len = length($hdl->{rbuf});
substr($hdl->{rbuf}, 0, $len, ''); # empty rbuf
}
$rstate->{read} += ($startlen - length($hdl->{rbuf}));
if (!$rstate->{done} && ($rstate->{read} + length($hdl->{rbuf})) >= $rstate->{size}) {
- $rstate->{done} = 1; # make sure we dont get called twice
+ $rstate->{done} = 1; # make sure we dont get called twice
if ($rstate->{phase} < 0 || !$rstate->{md5sum}) {
- die "upload failed\n";
+ die "upload failed\n";
} else {
my $elapsed = tv_interval($rstate->{starttime});
my $rate = int($rstate->{bytes}/($elapsed*1024*1024));
- syslog('info', "multipart upload complete " .
- "(size: %d time: %ds rate: %.2fMiB/s md5sum: $rstate->{md5sum})",
+ syslog('info', "multipart upload complete " .
+ "(size: %d time: %ds rate: %.2fMiB/s md5sum: $rstate->{md5sum})",
$rstate->{bytes}, $elapsed, $rate);
$self->handle_api2_request($reqstate, $auth, $method, $path, $rstate);
}
my ($ctype) = @_;
my ($ct, @params) = split(/\s*[;,]\s*/o, $ctype);
-
+
foreach my $v (@params) {
if ($v =~ m/^\s*boundary\s*=\s*(\S+?)\s*$/o) {
return wantarray ? ($ct, $1) : $ct;
}
}
-
+
return wantarray ? ($ct) : $ct;
}
$filename =~ s/^"(.*)"$/$1/;
}
}
-
+
return wantarray ? ($disp, $name, $filename) : $disp;
}
sub get_upload_filename {
# choose unpredictable tmpfile name
-
+
$tmpfile_seq_no++;
return "/var/tmp/pveupload-" . Digest::MD5::md5_hex($tmpfile_seq_no . time() . $$);
}
} elsif ($path =~ m/^\Q$base_uri\E/) {
my $token = $r->header('CSRFPreventionToken');
my $cookie = $r->header('Cookie');
- my $ticket = extract_auth_cookie($cookie, $self->{cookie_name});
+ my $ticket = PVE::APIServer::Formatter::extract_auth_cookie($cookie, $self->{cookie_name});
my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
if (!$format) {
return;
}
- my $rpcenv = $self->{rpcenv};
- # set environment variables
- $rpcenv->set_user(undef);
- $rpcenv->set_language('C');
- $rpcenv->set_client_ip($reqstate->{peer_host});
-
eval {
- $auth = $self->auth_handler($method, $rel_uri, $ticket, $token);
+ $auth = $self->auth_handler($method, $rel_uri, $ticket, $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 (my $formatter = get_login_formatter($format)) {
- my ($raw, $ct, $nocomp) = &$formatter($path, $auth);
+
+ 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;
$resp->header("Content-Type" => $ct);
$resp->content($raw);
}
- $self->response($reqstate, $resp, undef, $nocomp, 3);
+ $self->response($reqstate, $resp, undef, $nocomp, $delay);
} else {
my $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, $err);
$self->response($reqstate, $resp, undef, 0, $delay);
my ($ct, $boundary) = parse_content_type($ctype) if $ctype;
if ($auth->{isUpload} && !$self->{trusted_env}) {
- die "upload 'Content-Type '$ctype' not implemented\n"
+ 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;
sub check_host_access {
my ($self, $clientip) = @_;
-
+
my $cip = Net::IP->new($clientip);
my $match_allow = 0;
my $class = ref($this) || $this;
- foreach my $req (qw(base_handler_class socket lockfh lockfile)) {
+ foreach my $req (qw(socket lockfh lockfile)) {
die "misssing required argument '$req'" if !defined($args{$req});
}
$self->{cookie_name} //= 'PVEAuthCookie';
$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} = {};
+ foreach my $p (qw(cookie_name base_uri title)) {
+ $self->{formatter_config}->{$p} = $self->{$p};
+ }
+ $self->{formatter_config}->{csrfgen_func} =
+ $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/");
# init inotify
PVE::INotify::inotify_init();
$self->{end_cond} = AnyEvent->condvar;
if ($self->{ssl}) {
+ 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}});
- # TODO : openssl >= 1.0.2 supports SSL_CTX_set_ecdh_auto to select a curve depending on
- # server and client availability from SSL_CTX_set1_curves.
- # that way other curves like 25519 can be used.
- # openssl 1.0.1 can only support 1 curve at a time.
- my $curve = Net::SSLeay::OBJ_txt2nid('prime256v1');
- my $ecdh = Net::SSLeay::EC_KEY_new_by_curve_name($curve);
- 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_tmp_ecdh($self->{tls_ctx}->{ctx}, $ecdh);
- Net::SSLeay::EC_KEY_free($ecdh);
+ Net::SSLeay::CTX_set_options($self->{tls_ctx}->{ctx}, $tls_ctx_flags);
}
if ($self->{spiceproxy}) {
return $self;
}
+# static helper to add directory including all subdirs
+# This can be used to setup $self->{dirs}
+sub add_dirs {
+ my ($result_hash, $alias, $subdir) = @_;
+
+ $result_hash->{$alias} = $subdir;
+
+ my $wanted = sub {
+ my $dir = $File::Find::dir;
+ if ($dir =~m!^$subdir(.*)$!) {
+ my $name = "$alias$1/";
+ $result_hash->{$name} = "$dir/";
+ }
+ };
+
+ find({wanted => $wanted, follow => 0, no_chdir => 1}, $subdir);
+}
+
# abstract functions - subclass should overwrite/implement them
sub verify_spice_connect_url {
#return ($vmid, $node, $port);
}
+# formatters can call this when the generate a new page
+sub generate_csrf_prevention_token {
+ my ($username) = @_;
+
+ return undef; # do nothing by default
+}
+
sub auth_handler {
- my ($self, $method, $rel_uri, $ticket, $token) = @_;
+ my ($self, $method, $rel_uri, $ticket, $token, $peer_host) = @_;
die "implement me";
# userid => $username,
# age => $age,
# isUpload => $isUpload,
- # cookie_name => $self->{cookie_name},
#};
}
-
sub rest_handler {
- my ($self, $clientip, $method, $rel_uri, $auth, $params) = @_;
+ my ($self, $clientip, $method, $rel_uri, $auth, $params, $format) = @_;
+
+ # please do not raise exceptions here (always return a result).
return {
status => HTTP_NOT_IMPLEMENTED,
message => "Method '$method $rel_uri' not implemented",
};
+
+ # this should return the following properties, which
+ # are then passed to the Formatter
+
+ # status: HTTP status code
+ # message: Error message
+ # errors: more detailed error hash (per parameter)
+ # info: reference to JSON schema definition - useful to format output
+ # data: result data
+
+ # total: additional info passed to output
+ # changes: additional info passed to output
+
+ # if you want to proxy the request to another node return this
+ # { proxy => $remip, proxynode => $node, proxy_params => $params };
+
+ # to pass the request to the local priviledged daemon use:
+ # { proxy => 'localhost' , proxy_params => $params };
+
+ # to download aspecific file use:
+ # { download => "/path/to/file" };
}
sub check_cert_fingerprint {