use Crypt::OpenSSL::Random;
use Crypt::OpenSSL::RSA;
use Net::SSLeay;
+use Net::IP;
use MIME::Base64;
use Digest::SHA;
use Digest::HMAC_SHA1;
my $cacert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192);
$cacert =~ s/\n/\\n/g;
+ $proxy = "[$proxy]" if Net::IP::ip_is_ipv6($proxy);
my $config = {
'secure-attention' => "Ctrl+Alt+Ins",
'toggle-fullscreen' => "Shift+F11",
return 1 if $data->{enable};
- return 1 if $username eq 'root@pam'; # root is always enabled
-
die "user '$username' is disabled\n" if !$noerr;
return undef;
if ($type eq 'yubico') {
my $keys = $usercfg->{users}->{$username}->{keys};
yubico_verify_otp($otp, $keys, $tfa_cfg->{url}, $tfa_cfg->{id}, $tfa_cfg->{key}, $proxy);
+ } elsif ($type eq 'oath') {
+ my $keys = $usercfg->{users}->{$username}->{keys};
+ oath_verify_otp($otp, $keys, $tfa_cfg->{step}, $tfa_cfg->{digits});
} else {
die "unknown tfa type '$type'\n";
}
$cfg->{roles}->{$r} = $special_roles->{$r};
}
- # fixme: remove 'root' group (not required)?
-
- # add root user
- $cfg->{users}->{'root@pam'}->{enable} = 1;
+ # add root user if not exists
+ if (!$cfg->{users}->{'root@pam'}) {
+ $cfg->{users}->{'root@pam'}->{enable} = 1;
+ }
}
sub parse_user_config {
userconfig_force_defaults($cfg);
- while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+ while ($raw =~ /^\s*(.+?)\s*$/gm) {
my $line = $1;
-
- next if $line =~ m/^\s*$/; # skip empty lines
-
my @data;
foreach my $d (split (/:/, $line)) {
$cfg->{users}->{$user}->{email} = $email;
$cfg->{users}->{$user}->{comment} = PVE::Tools::decode_text($comment) if $comment;
$cfg->{users}->{$user}->{expire} = $expire;
- $cfg->{users}->{$user}->{keys} = $keys if $keys; # allowed yubico key ids
+ # keys: allowed yubico key ids or oath secrets (base32 encoded)
+ $cfg->{users}->{$user}->{keys} = $keys if $keys;
#$cfg->{users}->{$user}->{groups}->{$group} = 1;
#$cfg->{groups}->{$group}->{$user} = 1;
return $result;
}
+sub oath_verify_otp {
+ my ($otp, $keys, $step, $digits) = @_;
+
+ die "oath: missing password\n" if !defined($otp);
+ die "oath: no associated oath keys\n" if $keys =~ m/^\s+$/;
+
+ $step = 30 if !$step;
+ $digits = 6 if !$digits;
+
+ my $found;
+
+ my $parser = sub {
+ my $line = shift;
+
+ if ($line =~ m/^\d{6}$/) {
+ $found = 1 if $otp eq $line;
+ }
+ };
+
+ foreach my $k (PVE::Tools::split_list($keys)) {
+ # Note: we generate 3 values to allow small time drift
+ my $now = localtime(time() - $step);
+ my $cmd = ['oathtool', '--totp', '--digits', $digits, '-N', $now, '-s', $step, '-w', '2', '-b', $k];
+ eval { run_command($cmd, outfunc => $parser, errfunc => sub {}); };
+ last if $found;
+ }
+
+ die "oath auth failed\n" if !$found;
+}
+
1;