]> git.proxmox.com Git - pmg-api.git/blobdiff - PMG/LDAPCache.pm
show all errors when we get an ldap connection error
[pmg-api.git] / PMG / LDAPCache.pm
index e2e9192eef650d647d3e5de6083757a5e2552c51..0d3601915acb281d2462a764e98cb7297c4a57ad 100755 (executable)
@@ -4,14 +4,17 @@ use strict;
 use warnings;
 use File::Path;
 use LockFile::Simple;
+use Data::Dumper;
 use Net::LDAP;
 use Net::LDAP::Control::Paged;
 use Net::LDAP::Constant qw (LDAP_CONTROL_PAGED);
 use DB_File;
 
 use PVE::SafeSyslog;
+use PVE::Tools qw(split_list);
 
 use PMG::Utils;
+use PMG::LDAPConfig;
 
 $DB_HASH->{'cachesize'} = 10000;
 $DB_RECNO->{'cachesize'} = 10000;
@@ -50,20 +53,20 @@ sub new {
        $self->{id} = $id;
     }
 
-    if (!$args{mailattr}) {
-       $args{mailattr} = "mail, userPrincipalName, proxyAddresses, othermailbox";
-    }
-    $args{mailattr} =~ s/[\,\;]/ /g;
-    $args{mailattr} =~ s/\s+/,/g;
+    my $config_properties = PMG::LDAPConfig::properties();
 
-    if ($args{mode} && ($args{mode} eq 'ldap' ||  $args{mode} eq 'ldaps')) {
-       $self->{mode} = $args{mode};
-    } else {
-       $self->{mode} = 'ldap';
+    # set defaults for the fields that have one
+    foreach my $property (keys %$config_properties) {
+       my $d = $config_properties->{$property};
+       next if !defined($d->{default});
+       $self->{$property} = $args{$property} || $d->{default};
     }
 
-    $self->{accountattr} = $args{accountattr} || 'sAMAccountName';
-    @{$self->{mailattr}} = split(/,/, $args{mailattr});
+    # split list returns an array not a reference
+    $self->{accountattr} = [split_list($self->{accountattr})];
+    $self->{mailattr} = [split_list($self->{mailattr})];
+    $self->{groupclass} = [split_list($self->{groupclass})];
+
     $self->{server1} = $args{server1};
     $self->{server2} = $args{server2};
     $self->{binddn} = $args{binddn};
@@ -155,7 +158,7 @@ sub queryusers {
        scope    => "subtree",
        filter   => $filter,
        control  => [ $page ],
-       attrs  => [ @{$self->{mailattr}}, $self->{accountattr}, 'memberOf' ]
+       attrs  => [ @{$self->{mailattr}}, @{$self->{accountattr}}, 'memberOf' ]
        );
 
     my $cookie;
@@ -206,17 +209,16 @@ sub queryusers {
                $self->{dbstat}->{dnames}->{dbh}->put($dn, $cuid);
            }
 
-           my $account = $entry->get_value($self->{accountattr});
-           if ($account && ($account =~ m/^\S+$/s)) {
-               $account = lc($account);
-               $self->{dbstat}->{accounts}->{dbh}->put($account, $cuid);
-           } else {
-               $account = '';
+           foreach my $attr (@{$self->{accountattr}}) {
+               my $account = $entry->get_value($attr);
+               if ($account && ($account =~ m/^\S+$/s)) {
+                   $account = lc($account);
+                   $self->{dbstat}->{accounts}->{dbh}->put($account, $cuid);
+                   my $data = pack('n/a* n/a* n/a*', $pmail, $account, $dn);
+                   $self->{dbstat}->{users}->{dbh}->put($cuid, $data);
+               }
            }
 
-           my $data = pack('n/a* n/a* n/a*', $pmail, $account, $dn);
-           $self->{dbstat}->{users}->{dbh}->put($cuid, $data);
-
            foreach my $mail (@$addresses) {
                $self->{dbstat}->{mails}->{dbh}->put($mail, $cuid);
            }
@@ -237,14 +239,16 @@ sub queryusers {
 
        # Get cookie from paged control
        my ($resp) = $mesg->control(LDAP_CONTROL_PAGED) or last;
-       $cookie = $resp->cookie or last;
+       $cookie = $resp->cookie;
+
+       last if (!defined($cookie) || !length($cookie));
 
        # Set cookie in paged control
        $page->cookie($cookie);
     }
 
 
-    if ($cookie) {
+    if (defined($cookie) && length($cookie)) {
        # We had an abnormal exit, so let the server know we do not want any more
        $page->cookie($cookie);
        $page->size(0);
@@ -260,7 +264,13 @@ sub querygroups {
 
     return undef if !$self->{groupbasedn};
 
-    my $filter = "(objectclass=group)";
+    my $filter = "(|";
+
+    for my $class (@{$self->{groupclass}}) {
+       $filter .= "(objectclass=$class)";
+    }
+
+    $filter .= ")";
 
     my $page = Net::LDAP::Control::Paged->new(size => 100);
 
@@ -268,7 +278,7 @@ sub querygroups {
                 scope    => "subtree",
                 filter   => $filter,
                 control  => [ $page ],
-                attrs  => [ 'member' ]
+                attrs  => [ 'member', 'uniqueMember' ],
                 );
 
     my $cookie;
@@ -287,7 +297,9 @@ sub querygroups {
        foreach my $entry ( $mesg->entries ) {
            my $group = $entry->dn;
            my @members = $entry->get_value('member');
-
+           if (!scalar(@members)) {
+               @members = $entry->get_value('uniqueMember');
+           }
            my $cgid;
            $self->{dbstat}->{groups}->{dbh}->get($group, $cgid);
            if (!$cgid) {
@@ -330,25 +342,23 @@ sub querygroups {
 sub ldap_connect {
     my ($self) = @_;
 
-    my $mode = $self->{mode};
-    my $portstr = '';
-    $portstr = ':' . $self->{port} if $self->{port};
+    my $hosts = [ $self->{server1} ];
 
-    my $serverstr = "$mode://$self->{server1}${portstr}/";
-    my $ldap = Net::LDAP->new($serverstr);
-    if(!$ldap && $self->{server2} && $self->{server2} ne '127.0.0.1')  {
-       $serverstr = "$mode://$self->{server2}${portstr}/";
-       $ldap = Net::LDAP->new($serverstr);
-    }
+    push @$hosts, $self->{server2} if $self->{server2};
 
-    return $ldap;
+    my $opts = { timeout => 10, onerror => 'die' };
+
+    $opts->{port} = $self->{port} if $self->{port};
+    $opts->{schema} = $self->{mode};
+
+    return Net::LDAP->new($hosts, %$opts);
 }
 
 sub ldap_connect_and_bind {
      my ($self) = @_;
 
      my $ldap = $self->ldap_connect() ||
-        die "Can't bind to ldap server '$self->{id}': $!\n";
+        die "Can't bind to ldap server '$self->{id}': $! $@\n";
 
      my $mesg;
 
@@ -376,8 +386,6 @@ sub sync_database {
 
     # open ldap connection
 
-    syslog('info', "syncing ldap database '$self->{id}'");
-
     my $ldap;
 
     eval { $ldap = $self->ldap_connect_and_bind(); };
@@ -396,6 +404,15 @@ sub sync_database {
        $olddbh->{$db} = $self->{dbstat}->{$db}->{dbh};
     }
 
+    my $error_cleanup = sub {
+       # close and delete all files
+       foreach my $db (@dbs) {
+           undef $self->{dbstat}->{$db}->{dbh};
+           unlink $self->{dbstat}->{$db}->{tmpfilename};
+           $self->{dbstat}->{$db}->{dbh} = $olddbh->{$db};
+       }
+    };
+
     eval {
        foreach my $db (@dbs) {
            my $filename = $self->{dbstat}->{$db}->{tmpfilename};
@@ -416,71 +433,48 @@ sub sync_database {
                if !$self->{dbstat}->{$db}->{dbh};
        }
     };
-
-    my $err = $@;
-
-    if ($err) {
-       # close and delete all files
-       foreach my $db (@dbs) {
-           undef $self->{dbstat}->{$db}->{dbh};
-           unlink $self->{dbstat}->{$db}->{tmpfilename};
-           $self->{dbstat}->{$db}->{dbh} = $olddbh->{$db};
-       }
+    if (my $err = $@) {
+       $error_cleanup->();
        $self->{errors} .= $err;
        syslog('err', $err);
-
        return;
     }
 
     $self->querygroups ($ldap) if $self->{groupbasedn};
 
-    if (!$self->{errors}) {
-       $self->queryusers($ldap);
-    }
+    $self->queryusers($ldap) if !$self->{errors};
 
     $ldap->unbind;
 
     if ($self->{errors}) {
-       # close and delete all files
-       foreach my $db (@dbs) {
-           undef $self->{dbstat}->{$db}->{dbh};
-           unlink $self->{dbstat}->{$db}->{tmpfilename};
-           $self->{dbstat}->{$db}->{dbh} = $olddbh->{$db};
-       }
-    } else {
-
-       my $lock = lockdir($self->{id});
+       $error_cleanup->();
+       return;
+    }
 
-       if (!$lock) {
-           my $err = "unable to get database lock for ldap database '$self->{id}'";
-           $self->{errors} .= "$err\n";
-           syslog('err', $err);
+    my $lock = lockdir($self->{id});
 
-           # close and delete all files
-           foreach my $db (@dbs) {
-               undef $self->{dbstat}->{$db}->{dbh};
-               unlink $self->{dbstat}->{$db}->{tmpfilename};
-               $self->{dbstat}->{$db}->{dbh} = $olddbh->{$db};
-           }
-       } else {
-           foreach my $db (@dbs) {
-               my $filename = $self->{dbstat}->{$db}->{filename} =
-                   "$cachedir/$dir/${db}.db";
-               $self->{dbstat}->{$db}->{dbh}->sync(); # flush everything
-               rename $self->{dbstat}->{$db}->{tmpfilename}, $filename;
-           }
+    if (!$lock) {
+       my $err = "unable to get database lock for ldap database '$self->{id}'";
+       $self->{errors} .= "$err\n";
+       syslog('err', $err);
+       $error_cleanup->();
+       return;
+    }
 
-           $lock->release;
+    foreach my $db (@dbs) {
+       my $filename = $self->{dbstat}->{$db}->{filename} =
+           "$cachedir/$dir/${db}.db";
+       $self->{dbstat}->{$db}->{dbh}->sync(); # flush everything
+       rename $self->{dbstat}->{$db}->{tmpfilename}, $filename;
+    }
 
-           $last_atime->{$self->{id}} = time();
+    $lock->release;
 
-           $self->{gcount} = $self->{dbstat}->{groups}->{idcount};
-           $self->{ucount} = __count_entries($self->{dbstat}->{accounts}->{dbh});
-           $self->{mcount} = __count_entries($self->{dbstat}->{mails}->{dbh});
+    $last_atime->{$self->{id}} = time();
 
-           syslog('info', "ldap sync '$self->{id}' successful ($self->{mcount})");
-       }
-    }
+    $self->{gcount} = $self->{dbstat}->{groups}->{idcount};
+    $self->{ucount} = __count_entries($self->{dbstat}->{accounts}->{dbh});
+    $self->{mcount} = __count_entries($self->{dbstat}->{mails}->{dbh});
 }
 
 sub __count_entries {
@@ -568,11 +562,35 @@ sub loaddata {
     }
 }
 
-sub groups {
+sub get_groups {
     my ($self) = @_;
 
+    my $res = {};
+
     my $dbh = $self->{dbstat}->{groups}->{dbh};
-    return [] if !$dbh;
+
+    return $res if !$dbh;
+
+    my $key = 0 ;
+    my $value = "" ;
+    my $status = $dbh->seq($key, $value, R_FIRST());
+
+    while ($status == 0) {
+       $res->{$value} = $key;
+        $status = $dbh->seq($key, $value, R_NEXT());
+    }
+
+    return $res;
+}
+
+sub get_users {
+    my ($self) = @_;
+
+    my $res = {};
+
+    my $dbh = $self->{dbstat}->{users}->{dbh};
+
+    return $res if !$dbh;
 
     my $key = 0 ;
     my $value = "" ;
@@ -580,11 +598,116 @@ sub groups {
     my $keys;
 
     while ($status == 0) {
-        push @$keys, $key;
+       my ($pmail, $account, $dn) = unpack('n/a* n/a* n/a*', $value);
+       $res->{$key} = {
+           pmail => $pmail,
+           account => $account,
+           dn => $dn,
+       };
         $status = $dbh->seq($key, $value, R_NEXT());
     }
 
-    return $keys;
+    return $res;
+}
+
+sub get_gid_uid_map {
+    my ($self) = @_;
+
+    my $dbh = $self->{dbstat}->{memberof}->{dbh};
+
+    return [] if !$dbh;
+
+    my $key = 0 ;
+    my $value = "" ;
+
+    my $map = {};
+
+    if($dbh->seq($key, $value, R_FIRST()) == 0) {
+       do {
+           push @{$map->{$value}}, $key;
+       } while($dbh->seq($key, $value, R_NEXT()) == 0);
+    }
+
+    return $map;
+}
+
+sub list_groups {
+    my ($self) = @_;
+
+    my $res = [];
+
+    my $groups = $self->get_groups();
+
+    for my $gid (sort keys %$groups) {
+       push @$res, {
+           dn => $groups->{$gid},
+           gid => $gid,
+       };
+    }
+
+    return $res;
+}
+
+sub list_users {
+    my ($self, $gid) = @_;
+
+    my $res = [];
+
+    my $users = $self->get_users();
+
+    if (!defined($gid)) {
+       $res = [values %$users];
+    } else {
+       my $gid_uid_map = $self->get_gid_uid_map();
+       my $groups = $self->get_groups();
+       die "No such Group ID\n"
+           if !defined($groups->{$gid});
+       my $memberuids = $gid_uid_map->{$gid};
+       for my $uid (@$memberuids) {
+           next if !defined($users->{$uid});
+           push @$res, $users->{$uid};
+       }
+    }
+
+    return $res;
+}
+
+sub list_addresses {
+    my ($self, $mail) = @_;
+
+    my $dbhmails = $self->{dbstat}->{mails}->{dbh};
+    my $dbhusers = $self->{dbstat}->{users}->{dbh};
+
+    return undef if !$dbhmails || !$dbhusers;
+
+    $mail = lc($mail);
+
+    my $res = [];
+
+    my $cuid;
+    $dbhmails->get($mail, $cuid);
+    return undef if !$cuid;
+
+    my $rdata;
+    $dbhusers->get($cuid, $rdata);
+    return undef if !$rdata;
+
+    my ($pmail, $account, $dn) = unpack('n/a* n/a* n/a*', $rdata);
+
+    push @$res, { primary => 1, email => $pmail };
+
+    my $key = 0 ;
+    my $value = "" ;
+    my $status = $dbhmails->seq($key, $value, R_FIRST());
+
+    while ($status == 0) {
+       if ($value == $cuid && $key ne $pmail) {
+           push @$res, { primary => 0, email => $key };
+       }
+       $status = $dbhmails->seq($key, $value, R_NEXT());
+    }
+
+    return $res;
 }
 
 sub mail_exists {
@@ -613,6 +736,17 @@ sub account_exists {
     return $res;
 }
 
+sub group_exists {
+    my ($self, $group) = @_;
+
+    my $dbh = $self->{dbstat}->{groups}->{dbh};
+    return 0 if !$dbh;
+
+    my $res;
+    $dbh->get($group, $res);
+    return $res;
+}
+
 sub account_has_address {
     my ($self, $account, $mail) = @_;