commit 4a5784c5460c4b2dd6bf93e177230f2db3817d25 Author: upstream Date: Fri Sep 25 10:05:55 2026 +0200 Upstream proxmox-git/pve-access-control state of the touched files before 0e97972568 diff --git a/src/PVE/Auth/PAM.pm b/src/PVE/Auth/PAM.pm new file mode 100644 index 0000000..8586da5 --- /dev/null +++ b/src/PVE/Auth/PAM.pm @@ -0,0 +1,95 @@ +package PVE::Auth::PAM; + +use strict; +use warnings; + +use PVE::Tools qw(run_command); +use PVE::Auth::Plugin; +use Authen::PAM qw(:constants); + +use base qw(PVE::Auth::Plugin); + +sub type { + return 'pam'; +} + +sub options { + return { + default => { optional => 1 }, + comment => { optional => 1 }, + tfa => { optional => 1 }, + }; +} + +sub authenticate_user { + my ($class, $config, $realm, $username, $password) = @_; + + # user (www-data) need to be able to read /etc/passwd /etc/shadow + die "no password\n" if !$password; + + # PAM modules may temporarily override $SIG{CHLD}, causing SIGCHLDs from + # RESTEnvironment workers to be lost. Running the PAM interaction in a fork + # isolates any such handler manipulation from the parent process. + my $client_ip; + if (my $rpcenv = PVE::RPCEnvironment::get()) { + $client_ip = $rpcenv->get_client_ip(); + } + + PVE::Tools::run_fork(sub { + my $pamh = Authen::PAM->new( + 'proxmox-ve-auth', + $username, + sub { + my @res; + while (@_) { + my $msg_type = shift; + my $msg = shift; + push @res, (0, $password); + } + push @res, 0; + return @res; + }, + ); + + if (!ref($pamh)) { + my $err = $pamh->pam_strerror($pamh); + die "error during PAM init: $err"; + } + + if ($client_ip) { + $pamh->pam_set_item(PAM_RHOST(), $client_ip); + } + + my $res; + + if (($res = $pamh->pam_authenticate(0)) != PAM_SUCCESS) { + my $err = $pamh->pam_strerror($res); + die "$err\n"; + } + + if (($res = $pamh->pam_acct_mgmt(0)) != PAM_SUCCESS) { + my $err = $pamh->pam_strerror($res); + die "$err\n"; + } + + $pamh = 0; # call destructor + + return 1; + }); + + return 1; +} + +sub store_password { + my ($class, $config, $realm, $username, $password) = @_; + + my $cmd = ['usermod']; + + my $epw = PVE::Tools::encrypt_pw($password); + + push @$cmd, '-p', $epw, $username; + + run_command($cmd, errmsg => 'change password failed'); +} + +1; diff --git a/src/PVE/Auth/PVE.pm b/src/PVE/Auth/PVE.pm new file mode 100644 index 0000000..ab439a8 --- /dev/null +++ b/src/PVE/Auth/PVE.pm @@ -0,0 +1,114 @@ +package PVE::Auth::PVE; + +use strict; +use warnings; +use Encode; + +use PVE::Tools; +use PVE::Auth::Plugin; +use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); + +use base qw(PVE::Auth::Plugin); + +my $shadowconfigfile = "priv/shadow.cfg"; + +cfs_register_file($shadowconfigfile, \&parse_shadow_passwd, \&write_shadow_config); + +sub parse_shadow_passwd { + my ($filename, $raw) = @_; + + my $shadow = {}; + + return $shadow if !defined($raw); + + while ($raw =~ /^\s*(.+?)\s*$/gm) { + my $line = $1; + + if ($line !~ m/^\S+:\S+:$/) { + warn "pve shadow password: ignore invalid line $.\n"; + next; + } + + my ($userid, $crypt_pass) = split(/:/, $line); + $shadow->{users}->{$userid}->{shadow} = $crypt_pass; + } + + return $shadow; +} + +sub write_shadow_config { + my ($filename, $cfg) = @_; + + my $data = ''; + foreach my $userid (keys %{ $cfg->{users} }) { + my $crypt_pass = $cfg->{users}->{$userid}->{shadow}; + $data .= "$userid:$crypt_pass:\n"; + } + + return $data; +} + +sub lock_shadow_config { + my ($code, $errmsg) = @_; + + cfs_lock_file($shadowconfigfile, undef, $code); + my $err = $@; + if ($err) { + $errmsg ? die "$errmsg: $err" : die $err; + } +} + +sub type { + return 'pve'; +} + +sub options { + return { + default => { optional => 1 }, + comment => { optional => 1 }, + tfa => { optional => 1 }, + }; +} + +sub authenticate_user { + my ($class, $config, $realm, $username, $password) = @_; + + die "no password\n" if !$password; + + my $shadow_cfg = cfs_read_file($shadowconfigfile); + + if ($shadow_cfg->{users}->{$username}) { + my $encpw = + crypt(Encode::encode('utf8', $password), $shadow_cfg->{users}->{$username}->{shadow}); + die "invalid credentials\n" if ($encpw ne $shadow_cfg->{users}->{$username}->{shadow}); + } else { + die "no password set\n"; + } + + return 1; +} + +sub store_password { + my ($class, $config, $realm, $username, $password) = @_; + + lock_shadow_config(sub { + my $shadow_cfg = cfs_read_file($shadowconfigfile); + my $epw = PVE::Tools::encrypt_pw($password); + $shadow_cfg->{users}->{$username}->{shadow} = $epw; + cfs_write_file($shadowconfigfile, $shadow_cfg); + }); +} + +sub delete_user { + my ($class, $config, $realm, $username) = @_; + + lock_shadow_config(sub { + my $shadow_cfg = cfs_read_file($shadowconfigfile); + + delete $shadow_cfg->{users}->{$username}; + + cfs_write_file($shadowconfigfile, $shadow_cfg); + }); +} + +1;