package PVE::Auth::Plugin; use strict; use warnings; use Encode; use Digest::SHA; use PVE::Tools; use PVE::SectionConfig; use PVE::JSONSchema qw(get_standard_option); use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file); use Data::Dumper; use base qw(PVE::SectionConfig); my $domainconfigfile = "domains.cfg"; cfs_register_file($domainconfigfile, sub { __PACKAGE__->parse_config(@_); }, sub { __PACKAGE__->write_config(@_); }); sub lock_domain_config { my ($code, $errmsg) = @_; cfs_lock_file($domainconfigfile, undef, $code); my $err = $@; if ($err) { $errmsg ? die "$errmsg: $err" : die $err; } } my $realm_regex = qr/[A-Za-z][A-Za-z0-9\.\-_]+/; PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm); sub pve_verify_realm { my ($realm, $noerr) = @_; if ($realm !~ m/^${realm_regex}$/) { return undef if $noerr; die "value does not look like a valid realm\n"; } return $realm; } PVE::JSONSchema::register_standard_option('realm', { description => "Authentication domain ID", type => 'string', format => 'pve-realm', maxLength => 32, }); PVE::JSONSchema::register_format('pve-userid', \&verify_username); sub verify_username { my ($username, $noerr) = @_; $username = '' if !$username; my $len = length($username); if ($len < 3) { die "user name '$username' is too short\n" if !$noerr; return undef; } if ($len > 64) { die "user name '$username' is too long ($len > 64)\n" if !$noerr; return undef; } # we only allow a limited set of characters # colon is not allowed, because we store usernames in # colon separated lists)! # slash is not allowed because it is used as pve API delimiter # also see "man useradd" if ($username =~ m!^([^\s:/]+)\@(${realm_regex})$!) { return wantarray ? ($username, $1, $2) : $username; } die "value '$username' does not look like a valid user name\n" if !$noerr; return undef; } PVE::JSONSchema::register_standard_option('userid', { description => "User ID", type => 'string', format => 'pve-userid', maxLength => 64, }); sub encrypt_pw { my ($pw) = @_; my $time = substr(Digest::SHA::sha1_base64 (time), 0, 8); return crypt(encode("utf8", $pw), "\$5\$$time\$"); } my $defaultData = { propertyList => { type => { description => "Realm type." }, realm => get_standard_option('realm'), }, }; sub private { return $defaultData; } sub parse_section_header { my ($class, $line) = @_; if ($line =~ m/^(\S+):\s*(\S+)\s*$/) { my ($type, $realm) = (lc($1), $2); my $errmsg = undef; # set if you want to skip whole section eval { pve_verify_realm($realm); }; $errmsg = $@ if $@; my $config = {}; # to return additional attributes return ($type, $realm, $errmsg, $config); } return undef; } sub parse_config { my ($class, $filename, $raw) = @_; my $cfg = $class->SUPER::parse_config($filename, $raw); my $default; foreach my $realm (keys %{$cfg->{ids}}) { my $data = $cfg->{ids}->{$realm}; # make sure there is only one default marker if ($data->{default}) { if ($default) { delete $data->{default}; } else { $default = $realm; } } if ($data->{comment}) { $data->{comment} = PVE::Tools::decode_text($data->{comment}); } } # add default domains $cfg->{ids}->{pve} = { type => 'pve', comment => "Proxmox VE authentication server", }; $cfg->{ids}->{pam} = { type => 'pam', plugin => 'PVE::Auth::PAM', comment => "Linux PAM standard authentication", }; return $cfg; }; sub write_config { my ($class, $filename, $cfg) = @_; delete $cfg->{ids}->{pve}; delete $cfg->{ids}->{pam}; foreach my $realm (keys %{$cfg->{ids}}) { my $data = $cfg->{ids}->{$realm}; if ($data->{comment}) { $data->{comment} = PVE::Tools::encode_text($data->{comment}); } } $class->SUPER::write_config($filename, $cfg); } sub authenticate_user { my ($class, $config, $realm, $username, $password) = @_; die "overwrite me"; } sub store_password { my ($class, $config, $realm, $username, $password) = @_; my $type = $class->type(); die "can't set password on auth type '$type'\n"; } sub delete_user { my ($class, $config, $realm, $username) = @_; # do nothing by default } 1;