]> git.proxmox.com Git - pve-http-server.git/blobdiff - PVE/APIServer/AnyEvent.pm
allow 'download' to be passed from API handler
[pve-http-server.git] / PVE / APIServer / AnyEvent.pm
index e5f2cb4d200dff06bd2d6ee925f0f488cc6b039f..60a2a1c5f64628acd0cb1ea6beadc07cc565de18 100644 (file)
@@ -46,7 +46,7 @@ use HTTP::Response;
 use Data::Dumper;
 use JSON;
 
-my $limit_max_headers = 30;
+my $limit_max_headers = 64;
 my $limit_max_header_size = 8*1024;
 my $limit_max_post = 64*1024;
 
@@ -66,6 +66,16 @@ my $split_abs_uri = sub {
     return wantarray ? ($rel_uri, $format) : $rel_uri;
 };
 
+sub dprint {
+    my ($self, $message) = @_;
+
+    return if !$self->{debug};
+
+    my ($pkg, $pkgfile, $line, $sub) = caller(1);
+    $sub =~ s/^(?:.+::)+//;
+    print "worker[$$]: $pkg +$line: $sub: $message\n";
+}
+
 sub log_request {
     my ($self, $reqstate) = @_;
 
@@ -143,13 +153,15 @@ sub client_do_disconnect {
        return;
     }
 
-    print "close connection $hdl\n" if $self->{debug};
+    $self->dprint("close connection $hdl");
 
     &$shutdown_hdl($hdl);
 
+    warn "connection count <= 0!\n" if $self->{conn_count} <= 0;
+
     $self->{conn_count}--;
 
-    print "$$: CLOSE FH" .  $hdl->{fh}->fileno() . " CONN$self->{conn_count}\n" if $self->{debug};
+    $self->dprint("CLOSE FH" .  $hdl->{fh}->fileno() . " CONN$self->{conn_count}");
 }
 
 sub finish_response {
@@ -412,7 +424,7 @@ sub websocket_proxy {
            my ($fh) = @_
                or die "connect to '$remhost:$remport' failed: $!";
 
-           print "$$: CONNECTed to '$remhost:$remport'\n" if $self->{debug};
+           $self->dprint("CONNECTed to '$remhost:$remport'");
 
            $reqstate->{proxyhdl} = AnyEvent::Handle->new(
                fh => $fh,
@@ -507,7 +519,7 @@ sub websocket_proxy {
                        $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};
+                       $self->dprint("websocket received close. status code: '$statuscode'");
                        if ($reqstate->{proxyhdl}) {
                            $reqstate->{proxyhdl}->push_shutdown();
                        }
@@ -538,7 +550,7 @@ sub websocket_proxy {
                "Sec-WebSocket-Protocol: $wsproto\015\012" .
                "\015\012";
 
-           print $res if $self->{debug};
+           $self->dprint($res);
 
            $reqstate->{hdl}->push_write($res);
 
@@ -800,7 +812,10 @@ sub handle_api2_request {
            $delay = 0 if $delay < 0;
        }
 
-       if (defined(my $download = $res->{download})) {
+       my $download = $res->{download};
+       $download //= $res->{data}->{download}
+            if defined($res->{data}) && ref($res->{data}) eq 'HASH';
+       if (defined($download)) {
            send_file_start($self, $reqstate, $download);
            return;
        }
@@ -840,9 +855,9 @@ sub handle_spice_proxy_request {
 
         if ($node ne 'localhost' && PVE::INotify::nodename() !~ m/^$node$/i) {
             $remip = $self->remote_node_ip($node);
-           print "REMOTE CONNECT $vmid, $remip, $connect_str\n" if $self->{debug};
+           $self->dprint("REMOTE CONNECT $vmid, $remip, $connect_str");
         } else {
-           print "$$: CONNECT $vmid, $node, $spiceport\n" if $self->{debug};
+           $self->dprint("CONNECT $vmid, $node, $spiceport");
        }
 
        if ($remip && $r->header('PVEDisableProxy')) {
@@ -860,7 +875,7 @@ sub handle_spice_proxy_request {
            my ($fh) = @_
                or die "connect to '$remhost:$remport' failed: $!";
 
-           print "$$: CONNECTed to '$remhost:$remport'\n" if $self->{debug};
+           $self->dprint("CONNECTed to '$remhost:$remport'");
            $reqstate->{proxyhdl} = AnyEvent::Handle->new(
                fh => $fh,
                rbuf_max => 64*1024,
@@ -1184,7 +1199,7 @@ sub unshift_read_header {
        eval {
            # print "$$: got header: $line\n" if $self->{debug};
 
-           die "to many http header lines\n" if ++$state->{count} >= $limit_max_headers;
+           die "too many http header lines (> $limit_max_headers)\n" if ++$state->{count} >= $limit_max_headers;
            die "http header too large\n" if ($state->{size} += length($line)) >= $limit_max_header_size;
 
            my $r = $reqstate->{request};
@@ -1322,7 +1337,8 @@ sub unshift_read_header {
                    }
 
                    my $ctype = $r->header('Content-Type');
-                   my ($ct, $boundary) = parse_content_type($ctype) if $ctype;
+                   my ($ct, $boundary);
+                   ($ct, $boundary)= parse_content_type($ctype) if $ctype;
 
                    if ($auth->{isUpload} && !$self->{trusted_env}) {
                        die "upload 'Content-Type '$ctype' not implemented\n"
@@ -1332,7 +1348,7 @@ sub unshift_read_header {
 
                        die "upload without content length header not supported" if !$len;
 
-                       print "start upload $path $ct $boundary\n" if $self->{debug};
+                       $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) ||
@@ -1478,8 +1494,6 @@ sub accept {
 
     fh_nonblocking $clientfh, 1;
 
-    $self->{conn_count}++;
-
     return $clientfh;
 }
 
@@ -1521,6 +1535,11 @@ sub check_host_access {
 
     my $cip = Net::IP->new($clientip);
 
+    if (!$cip) {
+       $self->dprint("client IP not parsable: $@");
+       return 0;
+    }
+
     my $match_allow = 0;
     my $match_deny = 0;
 
@@ -1528,6 +1547,7 @@ sub check_host_access {
        foreach my $t (@{$self->{allow_from}}) {
            if ($t->overlaps($cip)) {
                $match_allow = 1;
+               $self->dprint("client IP allowed: ". $t->prefix());
                last;
            }
        }
@@ -1536,6 +1556,7 @@ sub check_host_access {
     if ($self->{deny_from}) {
        foreach my $t (@{$self->{deny_from}}) {
            if ($t->overlaps($cip)) {
+               $self->dprint("client IP denied: ". $t->prefix());
                $match_deny = 1;
                last;
            }
@@ -1553,29 +1574,40 @@ sub check_host_access {
 sub accept_connections {
     my ($self) = @_;
 
+    my ($clientfh, $handle_creation);
     eval {
 
-       while (my $clientfh = $self->accept()) {
+       while ($clientfh = $self->accept()) {
 
            my $reqstate = { keep_alive => $self->{keep_alive} };
 
            # stop keep-alive when there are many open connections
-           if ($self->{conn_count} >= $self->{max_conn_soft_limit}) {
+           if ($self->{conn_count} + 1 >= $self->{max_conn_soft_limit}) {
                $reqstate->{keep_alive} = 0;
            }
 
            if (my $sin = getpeername($clientfh)) {
                my ($pfamily, $pport, $phost) = PVE::Tools::unpack_sockaddr_in46($sin);
                ($reqstate->{peer_port}, $reqstate->{peer_host}) = ($pport,  Socket::inet_ntop($pfamily, $phost));
+           } else {
+               $self->dprint("getpeername failed: $!");
+               close($clientfh);
+               next;
            }
 
            if (!$self->{trusted_env} && !$self->check_host_access($reqstate->{peer_host})) {
-               print "$$: ABORT request from $reqstate->{peer_host} - access denied\n" if $self->{debug};
+               $self->dprint("ABORT request from $reqstate->{peer_host} - access denied");
                $reqstate->{log}->{code} = 403;
                $self->log_request($reqstate);
+               close($clientfh);
                next;
            }
 
+           # Increment conn_count before creating new handle, since creation
+           # triggers callbacks, which can potentialy decrement (e.g.
+           # on_error) conn_count before AnyEvent::Handle->new() returns.
+           $handle_creation = 1;
+           $self->{conn_count}++;
            $reqstate->{hdl} = AnyEvent::Handle->new(
                fh => $clientfh,
                rbuf_max => 64*1024,
@@ -1598,8 +1630,9 @@ sub accept_connections {
                    if (my $err = $@) { syslog('err', "$err"); }
                },
                ($self->{tls_ctx} ? (tls => "accept", tls_ctx => $self->{tls_ctx}) : ()));
+           $handle_creation = 0;
 
-           print "$$: ACCEPT FH" .  $clientfh->fileno() . " CONN$self->{conn_count}\n" if $self->{debug};
+           $self->dprint("ACCEPT FH" .  $clientfh->fileno() . " CONN$self->{conn_count}");
 
            $self->push_request_header($reqstate);
        }
@@ -1607,6 +1640,15 @@ sub accept_connections {
 
     if (my $err = $@) {
        syslog('err', $err);
+       $self->dprint("connection accept error: $err");
+       close($clientfh);
+       if ($handle_creation) {
+           if ($self->{conn_count} <= 0) {
+               warn "connection count <= 0 not decrementing!\n";
+           } else {
+               $self->{conn_count}--;
+           }
+       }
        $self->{end_loop} = 1;
     }