package LISM::Storage::LDAP;

use strict;
use base qw(LISM::Storage);
use Net::LDAP;
use Net::LDAP::Constant qw(:all);
use MIME::Base64;
use Encode;
use Data::Dumper;

our $rawattrs = '^(jpegphoto|photo|objectSid|objectGUID|.*;binary)$';

=head1 NAME

LISM::Storage::LDAP - LDAP storage for LISM

=head1 DESCRIPTION

This class implements the L<LISM::Storage> interface for LDAP directory.

=head1 METHODS

=head2 init

Connect LDAP server.

=cut

sub init
{
    my $self = shift;
    my $conf = $self->{_config};

    $self->SUPER::init();

    if (!Encode::is_utf8($conf->{uri}[0])) {
        $conf->{uri}[0] = decode('utf8', $conf->{uri}[0]);
    }

    if (!defined($conf->{nc})) {
        ($conf->{nc}) = ($conf->{uri}[0] =~ /^ldaps?:\/\/[^\/]+\/(.+)$/i);
    }

    return 0;
}

=pod

=head2 commit

Do nothing.

=cut

sub commit
{
    my $self = shift;
    my $conf = $self->{_config};

    if ($conf->{transaction}[0] !~ /^on$/i) {
        return 0;
    }

    undef($self->{transaction});

    return 0;
}

=pod

=head2 rollback

Do nothing.

=cut

sub rollback
{
    my $self = shift;
    my $conf = $self->{_config};

    if ($conf->{transaction}[0] !~ /^on$/i) {
        return 0;
    }

    if (!defined($self->{transaction}->{op}) || !defined($self->{transaction}->{entryStr})) {
        return 0;
    }

    my $op = $self->{transaction}->{op};
    my $entryStr = $self->{transaction}->{entryStr};
    my $dn;
    my $rc = 0;

    if ($op eq 'add') {
        $dn = $entryStr;
        $rc = $self->delete($dn);
    } elsif ($op eq 'modify') {
        my @list = @{$self->{transaction}->{args}};
        ($dn) = ($entryStr =~ /^dn:{1,2} (.*)$/m);
        $entryStr =~ s/^dn:.*\n//;

        my @info;
        while ( @list > 0) {
            my $action = shift @list;
            my $key    = lc(shift @list);

            while (@list > 0 && $list[0] ne "ADD" && $list[0] ne "DELETE" && $list[0] ne "REPLACE") {
                shift @list;
            }

            my @values = $entryStr =~ /^$key: (.*)$/gmi;
            if (@values) {
                push(@info, "REPLACE", $key, @values);
            } else {
                push(@info, "DELETE", $key);
            } 
        }

        $rc = $self->modify($dn, @info);
    } elsif ($op eq 'delete') {
        ($dn) = ($entryStr =~ /^dn:{1,2} (.*)$/m);
        $entryStr =~ s/^dn:.*\n//;

        # Delete Active Directory internal attributes
        $entryStr =~ s/^(distinguishedName|instantType|whenCreated|whenChanged|uSNCreated|uSNChanged|objectSid|objectGUID|groupType|objectCategory|dSCorePropagationData|lastLogon|lastLogoff|logonCount|accountExpires|badPwdCount|pwdLastSet|badPasswordTime):.*\n//i;

        $rc = $self->add($dn, $entryStr);
    }

    undef($self->{transaction});

    if ($rc) {
        $self->log(level => 'err', message => "Rollback of $op operation($dn) failed($rc)");
    }

    return $rc;
}

=pod

=head2 bind($binddn, $passwd)

Bind to LDAP server.

=cut

sub bind
{
    my $self = shift;
    my($binddn, $passwd) = @_;
    my $conf = $self->{_config};

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # DN mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($binddn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $binddn =~ /$ldapmap->{dn}/i)) {
                $binddn = $self->_rewriteDn($ldapmap, 'request', $binddn);
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $binddn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
        }
    }
    $binddn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my $msg = $self->{bind}->bind($binddn, password => $passwd);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 search($base, $scope, $deref, $sizeLim, $timeLim, $filterStr, $attrOnly, @attrs)

Search LDAP information.

=cut

sub search
{
    my $self = shift;

    return $self->_do_search(undef, @_);
}

=pod

=head2 compare($dn, $avaStr)

Compare the value of attribute in LDAP information.

=cut

sub compare
{
    my $self = shift;
    my ($dn, $avaStr) = @_;
    my $conf = $self->{_config};

    my ($key, $val) = split(/=/, $avaStr);

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # Attribute Mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($dn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                $dn = $self->_rewriteDn($ldapmap, 'request', $dn);
            }
        } elsif ($ldapmap->{type} =~ /^objectclass$/i) {
            if ($key =~ /^objectClass$/i) {
                $avaStr =~ s/^$ldapmap->{local}$/$ldapmap->{foreign}/i;
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $dn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
            if ($key =~ /^$ldapmap->{local}$/i) {
                $key = $ldapmap->{foreign};
            }
        }
    }

    $dn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my $msg = $self->{ldap}->compare($dn, attr => $key, value => $val);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 modify($dn, @list)

Modify LDAP information.

=cut

sub modify
{
    my $self = shift;
    my ($dn, @list) = @_;
    my $conf = $self->{_config};
    my $rc = LDAP_SUCCESS;
    my $foreign_rdnattr;

    if (defined($conf->{noop}) && grep(/^modify$/i, @{$conf->{noop}})) {
        return $rc;
    }

    $rc = $self->_beginWork('modify', $dn, @list);
    if ($rc) {
        return $rc;
    }

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # DN mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($dn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                $dn = $self->_rewriteDn($ldapmap, 'request', $dn);
                $foreign_rdnattr = lc($ldapmap->{foreign});
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $dn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
        }
    }
    $dn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my @changes;
    while ( @list > 0) {
        my $action = shift @list;
        my $key    = lc(shift @list);
        my @values;

        while (@list > 0 && $list[0] ne "ADD" && $list[0] ne "DELETE" && $list[0] ne "REPLACE") {
            push(@values, shift @list);
        }

        if ($key =~ /^(modifyTimestamp|plainPassword)$/i) {
            next;
        }

        if ($foreign_rdnattr && $key eq $foreign_rdnattr) {
            next;
        }

        if ($key eq 'entrycsn') {
            last;
        }

        # Attribute Mapping
        foreach my $ldapmap (@{$conf->{ldapmap}}) {
            if ($ldapmap->{type} =~ /^objectclass$/i) {
                if ($key =~ /^objectClass$/i) {
                    for (my $i = 0; $i < @values; $i++) {
                        $values[$i] =~ s/^$ldapmap->{local}$/$ldapmap->{foreign}/i;
                    }
                }
            } elsif ($ldapmap->{type} =~ /^attribute$/i) {
                if ($key =~ /^$ldapmap->{local}$/i) {
                    $key = $ldapmap->{foreign};
                }
                if ($key =~ /^userCertificate;binary$/i) {
                    for (my $i = 0; $i < @values; $i++) {
                        $values[$i] = decode_base64($values[$i]);
                    }
                }
            }
        }

        for (my $i = 0; $i < @values; $i++) {
            $values[$i] =~ s/$self->{suffix}/$conf->{nc}/i;
            if ($key !~ /$rawattrs/ && $values[$i]) {
                # replace carriage return to linefeed
                $values[$i] =~ s/\r/$conf->{breakchar}/g;
            }
        }

        if ($action eq "DELETE" && !$values[0]) {
            push(@changes, lc($action) => [$key => []]);
        } else {
            push(@changes, lc($action) => [$key => \@values]);
        }
    }

    my $msg = $self->{ldap}->modify($dn, changes => [@changes]);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 add($dn, $entryStr)

Add information in LDAP directory.

=cut

sub add
{
    my $self = shift;
    my ($dn,  $entryStr) = @_;
    my $conf = $self->{_config};
    my $rc = LDAP_SUCCESS;

    if (defined($conf->{noop}) && grep(/^add$/i, @{$conf->{noop}})) {
        return $rc;
    }

    $rc = $self->_beginWork('add', $dn);
    if ($rc) {
        return $rc;
    }

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # DN mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($dn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                my ($rdn_val) = ($entryStr =~ /^$ldapmap->{foreign}: (.*$)/mi);
                if ($rdn_val) {
                    $dn =~ s/^[^,]+/$ldapmap->{foreign}=$rdn_val/i;
                }
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $dn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
        }
    }
    $dn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my %attrs;
    my @info = split(/\n/, $entryStr);
    foreach my $attr (@info) {
        my ($key, $val) = split(/: /, $attr);
        if ($key =~ /^(createTimestamp|modifyTimestamp|plainpassword)$/i) {
            next;
        }

        if ($key eq 'structuralObjectClass') {
            last;
        }

        # Attribute Mapping
        foreach my $ldapmap (@{$conf->{ldapmap}}) {
            if ($ldapmap->{type} =~ /^objectclass$/i) {
                if ($key =~ /^objectClass$/i) {
                    $val =~ s/^$ldapmap->{local}$/$ldapmap->{foreign}/i;
                }
            } elsif ($ldapmap->{type} =~ /^attribute$/i) {
                if ($key =~ /^$ldapmap->{local}$/i) {
                    $key = $ldapmap->{foreign};
                }
            }
        }

        $val =~ s/$self->{suffix}/$conf->{nc}/i;

        # replace carriage return to linefeed
        $val =~ s/\r/$conf->{breakchar}/g;

        push(@{$attrs{$key}}, $val);
    }

    my $msg = $self->{ldap}->add($dn, attrs => [%attrs]);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 modrdn($dn, $newrdn, $delFlag)

move information in LDAP directory.

=cut

sub modrdn
{
    my $self = shift;
    my ($dn, $newrdn, $delFlag) = @_;

    my $conf = $self->{_config};

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # DN mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($dn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                $dn = $self->_rewriteDn($ldapmap, 'request', $dn);
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $dn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
        }
    }

    $dn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my $msg = $self->{ldap}->modrdn($dn, newrdn => $newrdn, deleteoldrdn => $delFlag);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 delete($dn)

Delete information from LDAP directory.

=cut

sub delete
{
    my $self = shift;
    my ($dn) = @_;
    my $conf = $self->{_config};
    my $rc = LDAP_SUCCESS;

    if (defined($conf->{noop}) && grep(/^delete$/i, @{$conf->{noop}})) {
        return $rc;
    }

    if (defined($conf->{deleteflag})) {
        my $entryStr;
        ($rc, $entryStr) = $self->_do_search(undef, $dn, 0, 0, 1, 0, '(objectClass=*)');
        if ($rc) {
            return $rc;
        }

        my $match = 0;
        foreach my $key (keys %{$conf->{deleteflag}}) {
            my $deleteflag = $conf->{deleteflag}{$key};
            if (!defined($deleteflag->{filter}) || $self->parseFilter($deleteflag->{filterobj}, $entryStr)) {
                $rc = $self->modify($dn, "REPLACE", $key, $deleteflag->{value});
                $match = 1;
            }
        }
        if ($match) {
            return $rc;
        }
    }

    $rc = $self->_beginWork('delete', $dn);
    if ($rc) {
        return $rc;
    }

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    # DN mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($dn =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                $dn = $self->_rewriteDn($ldapmap, 'request', $dn);
            }
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $dn =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
        }
    }

    $dn =~ s/$self->{suffix}$/$conf->{nc}/i;

    my $msg = $self->{ldap}->delete($dn);

    $self->_freeConnect($msg);

    return $msg->code;
}

=pod

=head2 hashPasswd($passwd, $salt)

add hash schema at the head of hashed password.

=cut

sub hashPasswd
{
    my $self = shift;
    my ($passwd, $salt) =@_;
    my $conf = $self->{_config};
    my $hashpw;

    my ($htype, $otype) = split(/:/, $conf->{hash});

    my $hashpw = $self->SUPER::hashPasswd($passwd, $salt);

    if ($htype =~ /^AD$/i) {
        # encoding for Active Directory
        $hashpw = '';
        map {$hashpw .= "$_\000"} split(//, "\"$passwd\"");
    } elsif (defined($hashpw) && $htype =~ /^CRYPT|MD5|SHA$/i) {
        $hashpw = "{$htype}$hashpw";
    }

    return $hashpw;
}

sub manageDIT
{
    return 1;
}

sub _getConnect
{
    my $self = shift;
    my $conf = $self->{_config};

    if (defined($self->{ldap}) && defined($self->{bind}) &&
        (!defined($conf->{connection}[0]->{type}) || $conf->{connection}[0]->{type}[0] ne 'every')) {
        my $msg = $self->{ldap}->bind($conf->{binddn}[0], password => $conf->{bindpw}[0]);
        if ($msg->code) {
            $self->log(level => 'err', message => "Connection check failed");
            $self->{ldap}->unbind();
            $self->{bind}->unbind();
            undef($self->{ldap});
            undef($self->{bind});
        } else {
            return 0;
        }
    }

    $self->{ldap} = Net::LDAP->new($conf->{uri}[0]);
    $self->{bind} = Net::LDAP->new($conf->{uri}[0]);

    if (!defined($self->{ldap}) || !defined($self->{bind})) {
        $self->log(level => 'alert', message => "Can't connect $conf->{uri}[0]");
        return -1;
    }

    my $msg = $self->{ldap}->bind($conf->{binddn}[0], password => $conf->{bindpw}[0]);
    if ($msg->code) {
        $self->log(level => 'alert', message => "Can't bind $conf->{uri}[0] by $conf->{binddn}[0]");
        return -1;
    }

    return 0;
}

sub _freeConnect
{
    my $self =shift;
    my $conf = $self->{_config};
    my ($msg) = @_;

    if (!$msg) {
        return -1;
    }

    if ($msg->code == LDAP_SERVER_DOWN || $msg->code == -1 ||
        (defined($conf->{connection}[0]->{type}) && $conf->{connection}[0]->{type}[0] eq 'every')) {
        $self->{ldap}->unbind();
        $self->{bind}->unbind();

        undef($self->{ldap});
        undef($self->{bind});
    }

    return 0;
}

sub _checkConfig
{
    my $self = shift;
    my $conf = $self->{_config};
    my $rc = 0;

    if ($rc = $self->SUPER::_checkConfig()) {
        return $rc;
    }

    if (!defined($conf->{transaction})) {
        $conf->{transaction}[0] = 'off';
    }

    if (defined($conf->{deleteflag})) {
        foreach my $key (keys %{$conf->{deleteflag}}) {
            if ($conf->{deleteflag}{$key}->{filter}) {
                $conf->{deleteflag}{$key}->{filterobj} = Net::LDAP::Filter->new($conf->{deleteflag}{$key}->{filter});
            }
        }
    }

    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if (!defined($ldapmap->{type})) {
            $ldapmap->{type} = 'attribute';
        }
    }

    return $rc;
}

sub _beginWork
{
    my $self = shift;
    my ($op, $dn, @args) = @_;
    my $conf = $self->{_config};
    my $entryStr;
    my $rc = LDAP_SUCCESS;

    if ($conf->{transaction}[0] !~ /^on$/i) {
        return $rc;
    }

    if ($op eq 'add') {
        $entryStr = $dn;
    } else {
        ($rc, $entryStr) = $self->_do_search(undef, $dn, 0, 0, 1, 0, '(objectClass=*)');
        if ($rc) {
            $self->log(level => 'err', message => "Can't get $dn in beginning transaction");
            return $rc;
        }
    }

    $self->{transaction}->{op} = $op;
    $self->{transaction}->{entryStr} = $entryStr;
    $self->{transaction}->{args} = \@args;

    return $rc;
}

sub _do_search
{
    my $self = shift;
    my($control, $base, $scope, $deref, $sizeLim, $timeLim, $filterStr, $attrOnly, @attrs) = @_;
    my $conf = $self->{_config};
    my @match_entries = ();
    my $rc = LDAP_SUCCESS;

    my $filter = Net::LDAP::Filter->new($filterStr);
    if (!defined($filter)) {
        return (LDAP_FILTER_ERROR, ());
    }

    # get entry of data container
    if ($base =~ /^$self->{suffix}$/i) {
        if ($scope != 1) {
            my $entry = $self->{contentrystr};
            if ($self->parseFilter($filter, $entry)) {
                push (@match_entries, $entry);
                $sizeLim--;
            }
        }
    }
    $sizeLim = $sizeLim - @match_entries;

    if ($self->_getConnect()) {
        return LDAP_SERVER_DOWN;
    }

    $filterStr = decode('utf8', $filterStr);

    # Attribute mapping
    foreach my $ldapmap (@{$conf->{ldapmap}}) {
        if ($ldapmap->{type} =~ /^dn$/i) {
            if ($base =~ /^$ldapmap->{local}=/i && (!defined($ldapmap->{dn}) || $base =~ /$ldapmap->{dn}/i)) {
                $base = $self->_rewriteDn($ldapmap, 'request', $base);
            }
        } elsif ($ldapmap->{type} =~ /^objectclass$/i) {
            $filterStr =~ s/objectClass=$ldapmap->{local}/objectClass=$ldapmap->{foreign}/mi;
        } elsif ($ldapmap->{type} =~ /^attribute$/i) {
            $base =~ s/^$ldapmap->{local}=/$ldapmap->{foreign}=/i;
            $filterStr =~ s/\($ldapmap->{local}=/\($ldapmap->{foreign}=/gmi;
        }
    }

    $base =~ s/$self->{suffix}$/$conf->{nc}/i;
    $filterStr =~ s/$self->{suffix}(\)*)/$conf->{nc}$1/gi;
    $filterStr = encode('utf8', $filterStr);

    my $msg;
    my $class = $control ? ref(${$control}[0]) : undef;
    while (1) {
        if ($class) {
            $msg = $self->{ldap}->search(base => $base, scope => $scope, deref => $deref, sizelimit => $sizeLim, timelimit => $timeLim, filter => $filterStr, control => $control);
        } else {
            $msg = $self->{ldap}->search(base => $base, scope => $scope, deref => $deref, sizelimit => $sizeLim, timelimit => $timeLim, filter => $filterStr);
        }

        if ($msg->code) {
            last;
        }

        for (my $i = 0; $i < $msg->count; $i++) {
            my $entry = $msg->entry($i);
            my $dn = decode('utf8', $entry->dn);

            $dn =~ s/$conf->{nc}$/$self->{suffix}/i;
            my $entryStr = "dn: $dn\n";
            if ($dn =~ /^$self->{suffix}$/i) {
                next;
            } else {
                foreach my $attr ($entry->attributes) {
                    foreach my $value ($entry->get_value($attr)) {
                        if ($attr =~ /$rawattrs/i) {
                            $value = encode_base64($value, '');
                            $entryStr = $entryStr.$attr.":: $value\n";
                        } else {
                            if ($value =~ /\n/) {
                                $value = encode_base64($value, '');
                                $entryStr = $entryStr.$attr.":: $value\n";
                            } else {
                                $value = decode('utf8', $value);
                                $value =~ s/$conf->{nc}$/$self->{suffix}/i;
                                $entryStr = $entryStr.$attr.": $value\n";
                            }
                        }
                    }
                }
            }

            if (!$self->_checkEntry($entryStr)) {
                next;
            }

            # Attribute mapping
            foreach my $ldapmap (@{$conf->{ldapmap}}) {
                if ($ldapmap->{type} =~ /^objectclass$/i) {
                    $entryStr =~ s/^objectClass: $ldapmap->{foreign}$/objectClass: $ldapmap->{local}/mi;
                } elsif ($ldapmap->{type} =~ /^attribute$/i) {
                    $entryStr =~ s/^$ldapmap->{foreign}:/$ldapmap->{local}:/gmi;
                    $entryStr =~ s/: $ldapmap->{foreign}=/: $ldapmap->{local}=/gi;
                }
            }

            # DN mapping
            foreach my $ldapmap (@{$conf->{ldapmap}}) {
                if ($ldapmap->{type} =~ /^dn$/i) {
                    my ($dn) = ($entryStr =~ /^dn: (.*)$/m);
                    if ($dn =~ /^$ldapmap->{foreign}=/i && (!defined($ldapmap->{dn}) || $dn =~ /$ldapmap->{dn}/i)) {
                        my ($rdn, $rdn_val) = ($entryStr =~ /^($ldapmap->{local}): (.*$)/mi);
                        if ($rdn && $rdn_val) {
                            $entryStr =~ s/^dn: [^,]+/dn: $rdn=$rdn_val/i;
                        }
                    }
                }
            }

            push(@match_entries, $entryStr);
        }

        if ($class eq 'Net::LDAP::Control::Paged') {
            # Get cookie from paged control
            my($resp) = $msg->control(LDAP_CONTROL_PAGED) or last;
            my $cookie = $resp->cookie or last;

            # Set cookie in paged control
            ${$control}[0]->cookie($cookie);
        } else {
            last;
        }
    }

    $self->_freeConnect($msg);

    return ($msg->code , @match_entries);
}

sub _checkEntry
{
    my $self = shift;
    my ($entryStr) = @_;

    if ($entryStr !~ /^dn: .+\n.+/) {
        return 0;
    }

    return 1;
}

sub _rewriteDn
{
    my $self = shift;
    my ($map, $context, $dn) = @_;
    my $conf = $self->{_config};
    my $attr;
    my $msg;

    my ($filterStr) = ($dn =~ /^([^,]+),/);

    if ($context eq 'request') {
        $attr = $map->{foreign};
        $filterStr = "(&($map->{foreign}=*)($filterStr))";

        # Attribute mapping
        foreach my $ldapmap (@{$conf->{ldapmap}}) {
            if ($ldapmap->{type} =~ /^attribute$/i) {
                $filterStr =~ s/$ldapmap->{local}=/$ldapmap->{foreign}=/i;
                $attr =~ s/^$ldapmap->{local}$/$ldapmap->{foreign}/i;
            }
        }
    } else {
        $attr = $map->{local};
        $filterStr = "(&($map->{local}=*)($filterStr))";

        # Attribute mapping
        foreach my $ldapmap (@{$conf->{ldapmap}}) {
            if ($ldapmap->{type} =~ /^attribute$/i) {
                $filterStr =~ s/$ldapmap->{foreign}=/$ldapmap->{local}=/i;
                $attr =~ s/^$ldapmap->{foreign}$/$ldapmap->{local}/i;
            }
        }
    }
    $filterStr = encode('utf8', $filterStr);

    $msg = $self->{ldap}->search(base => $conf->{nc}, scope => 'sub', filter => $filterStr, attrs => [$attr]);

    if ($msg->code) {
        $self->log(level => 'err', message => "search by $filterStr failed(".$msg->code.") in rewriteDn");
    } else {
        if ($msg->count) {
            my $entry = $msg->entry(0);
            my @values = $entry->get_value($attr);

            if (@values) {
                my $rdn_val = $values[0];
                $rdn_val = Encode::is_utf8($rdn_val) ? $rdn_val : decode('utf8', $rdn_val);
                $dn =~ s/^[^,]+/$attr=$rdn_val/i;
            }
        }
    }

    return $dn;
}

=head1 SEE ALSO

L<LISM>,
L<LISM::Storage>

=head1 AUTHOR

Kaoru Sekiguchi, <sekiguchi.kaoru@secioss.co.jp>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Kaoru Sekiguchi

This library is free software; you can redistribute it and/or modify
it under the GNU LGPL.

=cut

1;
