X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=data%2FPVE%2FDaemon.pm;h=264f8be5e2a840aa7969a47ef95fbda017d7cbc8;hp=9ba05dc20949225d63fb49ad3e81058ae9f5cbcf;hb=309d99e71818b7af5092152d02dff39894ecf432;hpb=ce0ae1edec5f7c6e818077ebdd76694e7541520a diff --git a/data/PVE/Daemon.pm b/data/PVE/Daemon.pm index 9ba05dc..264f8be 100644 --- a/data/PVE/Daemon.pm +++ b/data/PVE/Daemon.pm @@ -12,14 +12,20 @@ package PVE::Daemon; # * handle worker processes (option 'max_workers') # * allow to restart while workers are still runningl # (option 'leave_children_open_on_reload') +# * run as different user using setuid/setgid use strict; use warnings; +use English; + use PVE::SafeSyslog; use PVE::INotify; use POSIX ":sys_wait_h"; use Fcntl ':flock'; +use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN); +use IO::Socket::INET; + use Getopt::Long; use Time::HiRes qw (gettimeofday); @@ -178,7 +184,7 @@ my $start_workers = sub { }; my $terminate_server = sub { - my ($self) = @_; + my ($self, $allow_open_children) = @_; $self->{terminate} = 1; # set flag to avoid worker restart @@ -198,7 +204,7 @@ my $terminate_server = sub { } # if configured, leave children running on HUP - return if $self->{got_hup_signal} && + return if $allow_open_children && $self->{leave_children_open_on_reload}; # else, send TERM to old workers @@ -290,7 +296,7 @@ my $server_run = sub { local $SIG{TERM} = sub { local ($@, $!, $?); # do not overwrite error vars syslog('info', "received signal TERM"); - &$terminate_server($self); + &$terminate_server($self, 0); &$server_cleanup($self); &$old_sig_term(@_) if $old_sig_term; }; @@ -299,7 +305,7 @@ my $server_run = sub { local $SIG{QUIT} = sub { local ($@, $!, $?); # do not overwrite error vars syslog('info', "received signal QUIT"); - &$terminate_server($self); + &$terminate_server($self, 0); &$server_cleanup($self); &$old_sig_quit(@_) if $old_sig_quit; }; @@ -309,7 +315,7 @@ my $server_run = sub { local ($@, $!, $?); # do not overwrite error vars syslog('info', "received signal INT"); $SIG{INT} = 'DEFAULT'; # allow to terminate now - &$terminate_server($self); + &$terminate_server($self, 0); &$server_cleanup($self); &$old_sig_int(@_) if $old_sig_int; }; @@ -319,7 +325,7 @@ my $server_run = sub { syslog('info', "received signal HUP"); $self->{got_hup_signal} = 1; if ($self->{max_workers}) { - &$terminate_server($self); + &$terminate_server($self, 1); } elsif ($self->can('hup')) { eval { $self->hup() }; warn $@ if $@; @@ -355,7 +361,7 @@ my $server_run = sub { if ($err) { syslog ('err', "ERROR: $err"); - &$terminate_server($self); + &$terminate_server($self, 1); if (my $wait_time = $self->{restart_on_error}) { $self->restart_daemon($wait_time); @@ -405,30 +411,50 @@ sub new { $self = bless { name => $name, - run_dir => '/var/run', + pidfile => "/var/run/${name}.pid", env_restart_pve_daemon => $restart, env_pve_lock_fd => $lockfd, workers => {}, old_workers => {}, }, $class; + foreach my $opt (keys %params) { my $value = $params{$opt}; if ($opt eq 'restart_on_error') { $self->{$opt} = $value; } elsif ($opt eq 'stop_wait_time') { $self->{$opt} = $value; - } elsif ($opt eq 'run_dir') { + } elsif ($opt eq 'pidfile') { $self->{$opt} = $value; } elsif ($opt eq 'max_workers') { $self->{$opt} = $value; } elsif ($opt eq 'leave_children_open_on_reload') { $self->{$opt} = $value; + } elsif ($opt eq 'setgid') { + $self->{$opt} = $value; + } elsif ($opt eq 'setuid') { + $self->{$opt} = $value; } else { die "unknown daemon option '$opt'\n"; } } + if (my $gidstr = $self->{setgid}) { + my $gid = getgrnam($gidstr) || die "getgrnam failed - $!\n"; + POSIX::setgid($gid) || die "setgid $gid failed - $!\n"; + $EGID = "$gid $gid"; # this calls setgroups + # just to be sure + die "detected strange gid\n" if !($GID eq "$gid $gid" && $EGID eq "$gid $gid"); + } + + if (my $uidstr = $self->{setuid}) { + my $uid = getpwnam($uidstr) || die "getpwnam failed - $!\n"; + POSIX::setuid($uid) || die "setuid $uid failed - $!\n"; + # just to be sure + die "detected strange uid\n" if !($UID == $uid && $EUID == $uid); + } + if ($restart && $self->{max_workers}) { if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) { foreach my $pid (split(':', $wpids)) { @@ -439,8 +465,6 @@ sub new { } } - $self->{pidfile} = "$self->{run_dir}/${name}.pid"; - $self->{nodename} = PVE::INotify::nodename(); $self->{cmdline} = []; @@ -755,5 +779,49 @@ sub register_status_command { }}); } +# some useful helper + +sub create_reusable_socket { + my ($self, $port, $host) = @_; + + die "no port specifed" if !$port; + + my ($socket, $sockfd); + + if (defined($sockfd = $ENV{"PVE_DAEMON_SOCKET_$port"}) && + $self->{env_restart_pve_daemon}) { + + die "unable to parse socket fd '$sockfd'\n" + if $sockfd !~ m/^(\d+)$/; + $sockfd = $1; # untaint + + $socket = IO::Socket::INET->new; + $socket->fdopen($sockfd, 'w') || + die "cannot fdopen file descriptor '$sockfd' - $!\n"; + + } else { + + $socket = IO::Socket::INET->new( + LocalAddr => $host, + LocalPort => $port, + Listen => SOMAXCONN, + Proto => 'tcp', + ReuseAddr => 1) || + die "unable to create socket - $@\n"; + + # we often observe delays when using Nagle algorithm, + # so we disable that to maximize performance + setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1); + + $ENV{"PVE_DAEMON_SOCKET_$port"} = $socket->fileno; + } + + # remove FD_CLOEXEC bit to reuse on exec + $socket->fcntl(Fcntl::F_SETFD(), 0); + + return $socket; +} + + 1;