mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-08-02 23:35:59 +00:00

- improve variable definition/use locality - avoid some if's for some (mostly boolean) assignments, just use an expression As long as we don't go overboard with code golfing it to extremely terse, shorter code is always more readable, especially if definition/use happens not dozens of lines apart. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
275 lines
8.0 KiB
Perl
Executable File
275 lines
8.0 KiB
Perl
Executable File
package PVE::Service::pveproxy;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Data::Dumper;
|
|
use Encode;
|
|
use HTTP::Response;
|
|
use Template;
|
|
use URI::QueryParam;
|
|
use URI;
|
|
|
|
use PVE::API2;
|
|
use PVE::APIServer::AnyEvent;
|
|
use PVE::APIServer::Formatter::HTML;
|
|
use PVE::APIServer::Formatter::Standard;
|
|
use PVE::APIServer::Formatter;
|
|
use PVE::APIServer::Utils;
|
|
use PVE::Cluster;
|
|
use PVE::Daemon;
|
|
use PVE::DataCenterConfig;
|
|
use PVE::HTTPServer;
|
|
use PVE::SafeSyslog;
|
|
use PVE::pvecfg;
|
|
use PVE::Tools;
|
|
|
|
use base qw(PVE::Daemon);
|
|
|
|
my $cmdline = [$0, @ARGV];
|
|
|
|
my %daemon_options = (
|
|
max_workers => 3,
|
|
restart_on_error => 5,
|
|
stop_wait_time => 15,
|
|
leave_children_open_on_reload => 1,
|
|
setuid => 'www-data',
|
|
setgid => 'www-data',
|
|
pidfile => '/var/run/pveproxy/pveproxy.pid',
|
|
);
|
|
|
|
my $daemon = __PACKAGE__->new('pveproxy', $cmdline, %daemon_options);
|
|
|
|
sub add_dirs {
|
|
my ($result_hash, $alias, $subdir) = @_;
|
|
|
|
PVE::APIServer::AnyEvent::add_dirs($result_hash, $alias, $subdir);
|
|
}
|
|
|
|
my $basedirs = {
|
|
novnc => '/usr/share/novnc-pve',
|
|
extjs => '/usr/share/javascript/extjs',
|
|
manager => '/usr/share/pve-manager',
|
|
i18n => '/usr/share/pve-i18n',
|
|
docs => '/usr/share/pve-docs',
|
|
fontawesome => '/usr/share/fonts-font-awesome',
|
|
xtermjs => '/usr/share/pve-xtermjs',
|
|
widgettoolkit => '/usr/share/javascript/proxmox-widget-toolkit',
|
|
};
|
|
|
|
sub init {
|
|
my ($self) = @_;
|
|
|
|
# we use same ALLOW/DENY/POLICY as pveproxy
|
|
my $proxyconf = PVE::APIServer::Utils::read_proxy_config($self->{name});
|
|
|
|
my $accept_lock_fn = "/var/lock/pveproxy.lck";
|
|
|
|
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
|
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
|
|
|
my $listen_ip = $proxyconf->{LISTEN_IP};
|
|
my $socket = $self->create_reusable_socket(8006, $listen_ip);
|
|
|
|
my $dirs = {};
|
|
|
|
add_dirs($dirs, '/novnc/' => "$basedirs->{novnc}/");
|
|
add_dirs($dirs, '/pve-docs/' => "$basedirs->{docs}/");
|
|
add_dirs($dirs, '/pve-docs/api-viewer/extjs/' => "$basedirs->{extjs}/");
|
|
add_dirs($dirs, '/pve2/css/' => "$basedirs->{manager}/css/");
|
|
add_dirs($dirs, '/pve2/ext6/', "$basedirs->{extjs}/");
|
|
add_dirs($dirs, '/pve2/fa/css/' => "$basedirs->{fontawesome}/css/");
|
|
add_dirs($dirs, '/pve2/fa/fonts/' => "$basedirs->{fontawesome}/fonts/");
|
|
add_dirs($dirs, '/pve2/images/' => "$basedirs->{manager}/images/");
|
|
add_dirs($dirs, '/pve2/js/' => "$basedirs->{manager}/js/");
|
|
add_dirs($dirs, '/pve2/locale/', "$basedirs->{i18n}/");
|
|
add_dirs($dirs, '/pve2/touch/', "$basedirs->{manager}/touch/");
|
|
add_dirs($dirs, '/pwt/css/' => "$basedirs->{widgettoolkit}/css/");
|
|
add_dirs($dirs, '/pwt/images/' => "$basedirs->{widgettoolkit}/images/");
|
|
add_dirs($dirs, '/pwt/themes/' => "$basedirs->{widgettoolkit}/themes/");
|
|
add_dirs($dirs, '/xtermjs/' => "$basedirs->{xtermjs}/");
|
|
|
|
$self->{server_config} = {
|
|
title => 'Proxmox VE API',
|
|
keep_alive => 100,
|
|
max_conn => 500,
|
|
max_requests => 1000,
|
|
lockfile => $accept_lock_fn,
|
|
socket => $socket,
|
|
lockfh => $lockfh,
|
|
debug => $self->{debug},
|
|
trusted_env => 0, # not trusted, anyone can connect
|
|
logfile => '/var/log/pveproxy/access.log',
|
|
allow_from => $proxyconf->{ALLOW_FROM},
|
|
deny_from => $proxyconf->{DENY_FROM},
|
|
policy => $proxyconf->{POLICY},
|
|
ssl => {
|
|
cipher_list => $proxyconf->{CIPHERS},
|
|
ciphersuites => $proxyconf->{CIPHERSUITES},
|
|
key_file => '/etc/pve/local/pve-ssl.key',
|
|
cert_file => '/etc/pve/local/pve-ssl.pem',
|
|
honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER},
|
|
},
|
|
compression => $proxyconf->{COMPRESSION},
|
|
# Note: there is no authentication for those pages and dirs!
|
|
pages => {
|
|
'/' => sub { get_index($self->{nodename}, @_) },
|
|
# avoid authentication when accessing favicon
|
|
'/favicon.ico' => {
|
|
file => "$basedirs->{manager}/images/favicon.ico",
|
|
},
|
|
'/proxmoxlib.js' => {
|
|
file => "$basedirs->{widgettoolkit}/proxmoxlib.js",
|
|
},
|
|
'/qrcode.min.js' => {
|
|
file => '/usr/share/javascript/qrcodejs/qrcode.min.js',
|
|
},
|
|
},
|
|
dirs => $dirs,
|
|
};
|
|
|
|
if (defined($proxyconf->{DHPARAMS})) {
|
|
$self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS};
|
|
}
|
|
if (defined($proxyconf->{DISABLE_TLS_1_2})) {
|
|
$self->{server_config}->{ssl}->{tlsv1_2} = !$proxyconf->{DISABLE_TLS_1_2};
|
|
}
|
|
if (defined($proxyconf->{DISABLE_TLS_1_3})) {
|
|
$self->{server_config}->{ssl}->{tlsv1_3} = !$proxyconf->{DISABLE_TLS_1_3};
|
|
}
|
|
my $custom_key_path = '/etc/pve/local/pveproxy-ssl.key';
|
|
if (defined($proxyconf->{TLS_KEY_FILE})) {
|
|
$custom_key_path = $proxyconf->{TLS_KEY_FILE};
|
|
}
|
|
if (-f '/etc/pve/local/pveproxy-ssl.pem' && -f $custom_key_path) {
|
|
$self->{server_config}->{ssl}->{cert_file} = '/etc/pve/local/pveproxy-ssl.pem';
|
|
$self->{server_config}->{ssl}->{key_file} = $custom_key_path;
|
|
syslog('info', 'Using \'/etc/pve/local/pveproxy-ssl.pem\' as certificate for the web interface.');
|
|
}
|
|
}
|
|
|
|
sub run {
|
|
my ($self) = @_;
|
|
|
|
my $server = PVE::HTTPServer->new(%{$self->{server_config}});
|
|
$server->run();
|
|
}
|
|
|
|
$daemon->register_start_command();
|
|
$daemon->register_restart_command(1);
|
|
$daemon->register_stop_command();
|
|
$daemon->register_status_command();
|
|
|
|
our $cmddef = {
|
|
start => [ __PACKAGE__, 'start', []],
|
|
restart => [ __PACKAGE__, 'restart', []],
|
|
stop => [ __PACKAGE__, 'stop', []],
|
|
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
|
};
|
|
|
|
sub is_phone {
|
|
my ($ua) = @_;
|
|
|
|
return 0 if !$ua;
|
|
|
|
return 1 if $ua =~ m/(iPhone|iPod|Windows Phone)/;
|
|
|
|
if ($ua =~ m/Mobile(\/|\s)/) {
|
|
return 1 if $ua =~ m/(BlackBerry|BB)/;
|
|
return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
# NOTE: Requests to those pages are not authenticated
|
|
# so we must be very careful here
|
|
|
|
sub get_index {
|
|
my ($nodename, $server, $r, $args) = @_;
|
|
|
|
my $lang;
|
|
my $username;
|
|
my $token = 'null';
|
|
my $theme = "auto";
|
|
|
|
if (my $cookie = $r->header('Cookie')) {
|
|
if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) {
|
|
if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) {
|
|
$lang = $newlang;
|
|
}
|
|
}
|
|
|
|
if (my $newtheme = ($cookie =~ /(?:^|\s)PVEThemeCookie=([^;]*)/)[0]) {
|
|
# theme names need to be kebab case, with each segment a maximum of 10 characters long
|
|
# and at most 6 segments
|
|
if ($newtheme =~ m/^[a-z]{1,10}(-[a-z]{1,10}){0,5}$/) {
|
|
$theme = $newtheme;
|
|
}
|
|
}
|
|
|
|
my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name});
|
|
if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) {
|
|
$token = PVE::AccessControl::assemble_csrf_prevention_token($username);
|
|
}
|
|
}
|
|
|
|
if (!$lang) {
|
|
my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
|
$lang = $dc_conf->{language} // 'en';
|
|
}
|
|
|
|
my $mobile = is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile});
|
|
|
|
my $novnc = defined($args->{console}) && $args->{novnc};
|
|
my $xtermjs = defined($args->{console}) && $args->{xtermjs};
|
|
|
|
my $langfile = -f "$basedirs->{i18n}/pve-lang-$lang.js" ? 1 : 0;
|
|
|
|
my $version = PVE::pvecfg::version();
|
|
|
|
my $wtversionraw = PVE::Tools::file_read_firstline("$basedirs->{widgettoolkit}/proxmoxlib.js");
|
|
my $wtversion = $wtversionraw =~ m|^// (.*)$| ? $1 : '';
|
|
|
|
my $debug = $server->{debug};
|
|
if (exists $args->{debug}) {
|
|
$debug = !defined($args->{debug}) || $args->{debug};
|
|
}
|
|
|
|
my $vars = {
|
|
lang => $lang,
|
|
langfile => $langfile,
|
|
username => $username || '',
|
|
token => $token,
|
|
console => $args->{console},
|
|
nodename => $nodename,
|
|
debug => $debug,
|
|
version => "$version",
|
|
wtversion => $wtversion,
|
|
theme => $theme,
|
|
};
|
|
|
|
# by default, load the normal index
|
|
my $dir = $basedirs->{manager};
|
|
|
|
if ($novnc) {
|
|
$dir = $basedirs->{novnc};
|
|
} elsif ($xtermjs) {
|
|
$dir = $basedirs->{xtermjs};
|
|
} elsif ($mobile) {
|
|
$dir = "$basedirs->{manager}/touch";
|
|
}
|
|
|
|
my $page = '';
|
|
my $template = Template->new({ABSOLUTE => 1});
|
|
|
|
$template->process("$dir/index.html.tpl", $vars, \$page) || die $template->error(), "\n";
|
|
|
|
my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8");
|
|
my $resp = HTTP::Response->new(200, "OK", $headers, $page);
|
|
|
|
return $resp;
|
|
}
|
|
|
|
1;
|