package AddressBook::DB::LDIF; =head1 NAME AddressBook::DB::LDIF - Backend for AddressBook to use LDIF files. =head1 SYNOPSIS use AddressBook; $a = AddressBook->new(source => "LDIF", filename => "/tmp/ldif") =head1 DESCRIPTION AddressBook::DB::LDIF supports sequential backend database methods. AddressBook::DB::LDIF behavior can be modified using the following options: =over 4 =item key_fields A list of LDIF attribute names (not cannonical names) which can be used to uniquely identify an entry. =item base The LDAP base for all entries =item objectclass The LDAP objectclass for entries =item dn_calculate A perl expression which, when eval'd returns a valid LDAP "dn" (omitting the "base" part of the dn). Other attributes may be referenced as "$". For example, if LDIF entries have a dn like: "cn=John Doe,mail=jdoe@mail.com", then use the following: dn_calculate="'cn=' . $cn . ',mail=' . $mail" =back Any of these options can be specified in the constructor, or in the configuration file. =cut use strict; use AddressBook; use Carp; use Net::LDAP::LDIF; use Net::LDAP::Entry; use IO::File; use Date::Manip; use vars qw($VERSION @ISA); $VERSION = '0.13'; @ISA = qw(AddressBook); =head2 new The LDIF file is specified using the "filename" parameter: $a = AddressBook->new(source => "LDIF", filename => "/tmp/ldif") The filename may also be specified in the configuration file. =cut sub new { my $class = shift; my $self = {}; bless ($self,$class); my %args = @_; foreach (keys %args) { $self->{$_} = $args{$_}; } $self->{mode} = ""; if (defined $self->{filename}) { $self->{fh} = IO::File->new($self->{filename},O_RDWR | O_CREAT) || croak "Couldn't open `" . $self->{filename} . "': $@"; $self->{ldif} = Net::LDAP::LDIF->new($self->{fh}); } return $self; } sub DESTROY {$_[0]->fh->close} sub reset { my $self = shift; my $class = ref $self || croak "Not a method call."; $self->{fh}->seek(0,0); $self->{mode} = ""; } sub truncate { my $self = shift; my $class = ref $self || croak "Not a method call."; $self->{fh}->truncate(0); } sub read { my $self = shift; my $class = ref $self || croak "Not a method call."; if ($self->{mode} eq "w") { croak "Error: After writing, do a reset before reading"; } $self->{mode} = "r"; if (my $ldap_entry = $self->{ldif}->read) { my $ret = AddressBook::Entry->new(config=>$self->{config}); foreach ($ldap_entry->attributes) { if (exists $self->{config}->{db2generic}->{$self->{db_name}}->{$_}) { $ret->add(db=>$self->{db_name},attr=>{$_=>[$ldap_entry->get_value($_)]}); } } $ret->{timestamp} = $self->_get_timestamp; return $ret; } else { return undef; } } sub write { my $self = shift; my $class = ref $self || croak "Not a method call"; $self->{fh}->seek(0,2); # jump to the end of the file $self->{mode} = "w"; my $entry = shift; $entry->calculate; my $dn = $self->_dn_from_entry($entry); my %attr = %{$entry->get(db=>$self->{db_name},values_only=>'1')}; $attr{objectclass} = [$self->{objectclass}]; my $ldap_entry = Net::LDAP::Entry->new(); $ldap_entry->dn($dn); $ldap_entry->add(%attr); $self->{ldif}->write($ldap_entry); } sub _dn_from_entry { my $self = shift; my $class = ref $self || croak "Not a method call"; my $entry = shift || croak "Need an entry"; my ($dn,$dn_calculate); my %attr = %{$entry->get(db=>$self->{db_name},values_only=>'1')}; ($dn_calculate=$self->{dn_calculate}) =~ s/\$(\w*)/\$attr{$1}->[0]/g; eval qq{\$dn = $dn_calculate}; warn "Syntax error in dn_calculate: $@" if $@; $dn .= "," . $self->{base}; return $dn; } sub _get_timestamp { my $self = shift; my $class = ref $self || croak "Not a method call"; my @stat = stat($self->{filename}); return ParseDateString("epoch $stat[9]"); } 1; =head2 Timestamps For syncronization purposes, all records are timestamped with the modification date of the LDIF file. =head1 AUTHOR David L. Leigh, =head1 SEE ALSO L, L, L. Net::LDAP =cut