BEGIN {
if
(CHECK_UTF8);
}
our
$VERSION
=
'0.27'
;
my
%modes
=
qw(r < r+ +< w > w+ +> a >> a+ +>>)
;
sub
new {
my
$pkg
=
shift
;
my
$file
=
shift
||
'-'
;
my
$mode
=
@_
% 2 ?
shift
||
'r'
:
'r'
;
my
%opt
=
@_
;
my
$fh
;
my
$opened_fh
= 0;
$mode
=
$modes
{
$mode
}
if
(
defined
(
$modes
{
$mode
}));
if
(
ref
(
$file
)) {
$fh
=
$file
;
}
else
{
if
(
$file
eq
'-'
) {
(
$file
,
$fh
) = (
$mode
eq
'<'
)
? (
'STDIN'
, \
*STDIN
)
: (
'STDOUT'
,\
*STDOUT
);
if
(
$mode
=~ /(:.*$)/) {
my
$layer
= $1;
binmode
(
$file
,
$layer
);
}
}
else
{
$opened_fh
= (
$file
=~ /^\| | \|$/x)
?
open
(
$fh
,
$file
)
:
open
(
$fh
,
$mode
,
$file
);
return
unless
(
$opened_fh
);
}
}
$opt
{encode} =
'none'
unless
(
exists
$opt
{encode});
$opt
{onerror} =
'die'
unless
(
exists
$opt
{onerror});
$opt
{lowercase} ||= 0;
$opt
{change} ||= 0;
$opt
{
sort
} ||= 0;
$opt
{version} ||= 0;
my
$self
= {
changetype
=>
'modify'
,
modify
=>
'add'
,
wrap
=> 78,
%opt
,
fh
=>
$fh
,
file
=>
"$file"
,
opened_fh
=>
$opened_fh
,
_eof
=> 0,
write_count
=> (
$mode
=~ /^\s*\+?>>/ and
tell
(
$fh
) > 0) ? 1 : 0,
};
bless
$self
,
$pkg
;
}
sub
_read_lines {
my
$self
=
shift
;
my
$fh
=
$self
->{fh};
my
@ldif
= ();
my
$entry
=
''
;
my
$in_comment
= 0;
my
$entry_completed
= 0;
my
$ln
;
return
@ldif
if
(
$self
->
eof
());
while
(
defined
(
$ln
=
$self
->{_buffered_line} ||
scalar
<
$fh
>)) {
delete
(
$self
->{_buffered_line});
if
(
$ln
=~ /^
$in_comment
= 1;
}
else
{
if
(
$ln
=~ /^[ \t]/o) {
$entry
.=
$ln
if
(!
$in_comment
);
}
else
{
$in_comment
= 0;
if
(
$ln
=~ /^\r?\n$/o) {
$entry_completed
++
if
(
length
(
$entry
));
}
else
{
if
(
$entry_completed
) {
$self
->{_buffered_line} =
$ln
;
last
;
}
else
{
$entry
.=
$ln
;
}
}
}
}
}
$self
->
eof
(1)
if
(!
defined
(
$ln
));
$self
->{_current_lines} =
$entry
;
$entry
=~ s/\r?\n //sgo;
$entry
=~ s/\r?\n\t/ /sgo;
@ldif
=
split
(/^/,
$entry
);
map
{ s/\r?\n$//; }
@ldif
;
@ldif
;
}
sub
_read_url_attribute {
my
$self
=
shift
;
my
$url
=
shift
;
my
@ldif
=
@_
;
my
$line
;
if
(
$url
=~ s/^file:(?:\/\/)?//) {
open
(
my
$fh
,
'<'
,
$url
)
or
return
$self
->_error(
"can't open $url: $!"
,
@ldif
);
binmode
(
$fh
);
{
local
$/;
$line
= <
$fh
>;
}
close
(
$fh
);
}
elsif
(
$url
=~ /^(https?|ftp|gopher|news:)/ and
my
$ua
= LWP::UserAgent->new();
my
$response
=
$ua
->get(
$url
);
return
$self
->_error(
"can't get data from $url: $!"
,
@ldif
)
if
(!
$response
->is_success);
$line
=
$response
->decoded_content();
return
$self
->error(
"decoding data from $url failed: $@"
,
@ldif
)
if
(!
defined
(
$line
));
}
else
{
return
$self
->_error(
'unsupported URL type'
,
@ldif
);
}
$line
;
}
sub
_read_attribute_value {
my
$self
=
shift
;
my
$type
=
shift
;
my
$value
=
shift
;
my
@ldif
=
@_
;
if
(
$type
&&
$type
eq
':'
) {
$value
= MIME::Base64::decode(
$value
);
}
elsif
(
$type
&&
$type
eq
'<'
and
$value
=~ s/^(.*?)\s*$/$1/) {
$value
=
$self
->_read_url_attribute(
$value
,
@ldif
);
return
if
(!
defined
(
$value
));
}
$value
;
}
*_read_one
= \
&_read_entry
;
sub
_read_entry {
my
$self
=
shift
;
my
@ldif
;
$self
->_clear_error();
@ldif
=
$self
->_read_lines;
unless
(
@ldif
) {
$self
->_error(
'illegal empty LDIF entry'
)
if
(!
$self
->
eof
());
return
;
}
if
(
@ldif
and
$ldif
[0] =~ /^version:\s+(\d+)/) {
$self
->{version} = $1;
shift
@ldif
;
return
$self
->_read_entry
unless
(
@ldif
);
}
if
(
@ldif
< 1) {
return
$self
->_error(
'LDIF entry is not valid'
,
@ldif
);
}
elsif
(
$ldif
[0] !~ /^dn::? */) {
return
$self
->_error(
'First line of LDIF entry does not begin with "dn:"'
,
@ldif
);
}
my
$dn
=
shift
@ldif
;
my
$xattr
= $1
if
(
$dn
=~ s/^dn:(:?) *//);
$dn
=
$self
->_read_attribute_value(
$xattr
,
$dn
,
@ldif
);
my
$entry
= Net::LDAP::Entry->new;
$dn
= Encode::decode_utf8(
$dn
)
if
(CHECK_UTF8 &&
$self
->{raw} && (
'dn'
!~ /
$self
->{raw}/));
$entry
->dn(
$dn
);
my
@controls
= ();
while
(
@ldif
&& (
$ldif
[0] =~ /^control:\s*/)) {
my
$control
=
shift
(
@ldif
);
if
(
$control
=~ /^control:\s*(\d+(?:\.\d+)*)(?:\s+(?i)(true|false))?(?:\s*:([:<])?\s*(.*))?$/) {
my
(
$oid
,
$critical
,
$type
,
$value
) = ($1,$2,$3, $4);
$critical
= (
$critical
&&
$critical
=~ /true/i) ? 1 : 0;
if
(
defined
(
$value
)) {
if
(
$type
) {
$value
=
$self
->_read_attribute_value(
$type
,
$value
,
@ldif
);
return
$self
->_error(
'Illegal value in control line given'
,
@ldif
)
if
!
defined
(
$value
);
}
}
my
$ctrl
= Net::LDAP::Control->new(
type
=>
$oid
,
value
=>
$value
,
critical
=>
$critical
);
push
(
@controls
,
$ctrl
);
return
$self
->_error(
'Illegally formatted control line given'
,
@ldif
)
if
(!
@ldif
);
}
else
{
return
$self
->_error(
'Illegally formatted control line given'
,
@ldif
);
}
}
if
((
scalar
@ldif
) && (
$ldif
[0] =~ /^changetype:\s*/)) {
my
$changetype
=
$ldif
[0] =~ s/^changetype:\s*//
?
shift
(
@ldif
) :
$self
->{changetype};
$entry
->changetype(
$changetype
);
if
(
$changetype
eq
'delete'
) {
return
$self
->_error(
'LDIF "delete" entry is not valid'
,
@ldif
)
if
(
@ldif
);
return
wantarray
? (
$entry
,
@controls
) :
$entry
;
}
return
$self
->_error(
'LDAP entry is not valid'
,
@ldif
)
unless
(
@ldif
);
while
(
@ldif
) {
my
$action
=
$self
->{modify};
my
$modattr
;
my
$lastattr
;
my
@values
;
if
(
$changetype
eq
'modify'
) {
unless
((
my
$tmp
=
shift
@ldif
) =~ s/^(add|
delete
|replace|increment):\s*([-;\w]+)//) {
return
$self
->_error(
'LDAP entry is not valid'
,
@ldif
);
}
$lastattr
=
$modattr
= $2;
$action
= $1;
}
while
(
@ldif
) {
my
$line
=
shift
@ldif
;
if
(
$line
eq
'-'
) {
return
$self
->_error(
'LDAP entry is not valid'
,
@ldif
)
if
(!
defined
(
$modattr
) || !
defined
(
$lastattr
));
last
;
}
if
(
$line
=~ /^([-;\w]+):([\<\:]?)\s*(.*)$/o) {
my
(
$attr
,
$xattr
,
$val
) = ($1,$2,$3);
return
$self
->_error(
'LDAP entry is not valid'
,
@ldif
)
if
(
defined
(
$modattr
) &&
$attr
ne
$modattr
);
$val
=
$self
->_read_attribute_value(
$xattr
,
$val
,
$line
)
if
(
$xattr
);
return
if
!
defined
(
$val
);
$val
= Encode::decode_utf8(
$val
)
if
(CHECK_UTF8 &&
$self
->{raw} && (
$attr
!~ /
$self
->{raw}/));
if
(!
defined
(
$lastattr
) ||
$lastattr
ne
$attr
) {
$entry
->
$action
(
$lastattr
=> \
@values
)
if
(
defined
$lastattr
);
$lastattr
=
$attr
;
@values
= ();
}
push
(
@values
,
$val
);
}
else
{
return
$self
->_error(
'LDAP entry is not valid'
,
@ldif
);
}
}
$entry
->
$action
(
$lastattr
=> \
@values
)
if
(
defined
$lastattr
);
}
}
else
{
my
$last
=
''
;
my
@values
;
return
$self
->_error(
'Controls only allowed with LDIF change entries'
,
@ldif
)
if
(
@controls
);
foreach
my
$line
(
@ldif
) {
if
(
$line
=~ /^([-;\w]+):([\<\:]?)\s*(.*)$/o) {
my
(
$attr
,
$xattr
,
$val
) = ($1,$2,$3);
$last
=
$attr
if
(!
$last
);
$val
=
$self
->_read_attribute_value(
$xattr
,
$val
,
$line
)
if
(
$xattr
);
return
if
!
defined
(
$val
);
$val
= Encode::decode_utf8(
$val
)
if
(CHECK_UTF8 &&
$self
->{raw} && (
$attr
!~ /
$self
->{raw}/));
if
(
$attr
ne
$last
) {
$entry
->add(
$last
=> \
@values
);
@values
= ();
$last
=
$attr
;
}
push
(
@values
,
$val
);
}
else
{
return
$self
->_error(
"illegal LDIF line '$line'"
,
@ldif
);
}
}
$entry
->add(
$last
=> \
@values
);
}
$self
->{_current_entry} =
$entry
;
return
wantarray
? (
$entry
,
@controls
) :
$entry
;
}
sub
read_entry {
my
$self
=
shift
;
return
$self
->_error(
'LDIF file handle not valid'
)
unless
(
$self
->{fh});
$self
->_read_entry();
}
sub
read
{
my
$self
=
shift
;
return
$self
->read_entry()
unless
wantarray
;
my
(
$entry
,
@entries
);
push
(
@entries
,
$entry
)
while
(
$entry
=
$self
->read_entry);
@entries
;
}
sub
eof
{
my
$self
=
shift
;
my
$eof
=
shift
;
$self
->{_eof} =
$eof
if
(
$eof
);
$self
->{_eof};
}
sub
_wrap {
my
$len
=
int
(
$_
[1]);
return
$_
[0]
if
(
length
(
$_
[0]) <=
$len
or
$len
<= 40);
my
$l2
=
$len
- 1;
my
$x
= (
length
(
$_
[0]) -
$len
) /
$l2
;
my
$extra
= (
length
(
$_
[0]) == (
$l2
*
$x
+
$len
)) ?
''
:
'a*'
;
join
(
"\n "
,
unpack
(
"a$len"
.
"a$l2"
x
$x
.
$extra
,
$_
[0]));
}
sub
_write_attr {
my
(
$self
,
$attr
,
$val
) =
@_
;
my
$lower
=
$self
->{lowercase};
my
$fh
=
$self
->{fh};
my
$res
= 1;
foreach
my
$v
(
@$val
) {
my
$ln
=
$lower
?
lc
$attr
:
$attr
;
$v
= Encode::encode_utf8(
$v
)
if
(CHECK_UTF8 and Encode::is_utf8(
$v
));
if
(
$v
=~ /(^[ :<]|[\x00-\x1f\x7f-\xff]| $)/) {
$ln
.=
':: '
. MIME::Base64::encode(
$v
,
''
);
}
else
{
$ln
.=
': '
.
$v
;
}
$res
&&=
print
$fh
_wrap(
$ln
,
$self
->{wrap}),
"\n"
;
}
$res
;
}
sub
_cmpAttrs {
(
$a
=~ /^objectclass$/io)
? -1 : ((
$b
=~ /^objectclass$/io) ? 1 : (
$a
cmp
$b
));
}
sub
_write_attrs {
my
(
$self
,
$entry
) =
@_
;
my
@attributes
=
$entry
->attributes();
my
$res
= 1;
@attributes
=
sort
_cmpAttrs
@attributes
if
(
$self
->{
sort
});
foreach
my
$attr
(
@attributes
) {
my
$val
=
$entry
->get_value(
$attr
,
asref
=> 1);
$res
&&=
$self
->_write_attr(
$attr
,
$val
);
}
$res
;
}
sub
_write_controls {
my
(
$self
,
@ctrls
) =
@_
;
my
$res
= 1;
my
$fh
=
$self
->{fh};
foreach
my
$ctrl
(
@ctrls
) {
my
$ln
=
'control: '
.
$ctrl
->type . (
$ctrl
->critical ?
' true'
:
' false'
);
my
$v
=
$ctrl
->value;
if
(
defined
(
$v
)) {
$v
= Encode::encode_utf8(
$v
)
if
(CHECK_UTF8 and Encode::is_utf8(
$v
));
if
(
$v
=~ /(^[ :<]|[\x00-\x1f\x7f-\xff]| $)/) {
$v
= MIME::Base64::encode(
$v
,
''
);
$ln
.=
':'
;
}
$ln
.=
': '
.
$v
;
}
$res
&&=
print
$fh
_wrap(
$ln
,
$self
->{wrap}),
"\n"
;
}
$res
;
}
sub
_write_dn {
my
(
$self
,
$dn
) =
@_
;
my
$encode
=
$self
->{encode};
my
$fh
=
$self
->{fh};
$dn
= Encode::encode_utf8(
$dn
)
if
(CHECK_UTF8 and Encode::is_utf8(
$dn
));
if
(
$dn
=~ /^[ :<]|[\x00-\x1f\x7f-\xff]/) {
if
(
$encode
=~ /canonical/i) {
$dn
= Net::LDAP::Util::canonical_dn(
$dn
,
mbcescape
=> 1);
$dn
=~ s/^([ :<])/\\$1/;
$dn
=
"dn: $dn"
;
}
elsif
(
$encode
=~ /base64/i) {
$dn
=
'dn:: '
. MIME::Base64::encode(
$dn
,
''
);
}
else
{
$dn
=
"dn: $dn"
;
}
}
else
{
$dn
=
"dn: $dn"
;
}
print
$fh
_wrap(
$dn
,
$self
->{wrap}),
"\n"
;
}
sub
write
{
my
$self
=
shift
;
$self
->_write_entry(0,
@_
);
}
sub
write_entry {
my
$self
=
shift
;
$self
->_write_entry(
$self
->{change},
@_
);
}
sub
write_version {
my
$self
=
shift
;
my
$fh
=
$self
->{fh};
my
$res
= 1;
$res
&&=
print
$fh
"version: $self->{version}\n"
if
(
$self
->{version} && !
$self
->{version_written}++);
return
$res
;
}
sub
_write_entry {
my
$self
=
shift
;
my
$change
=
shift
;
my
$res
= 1;
my
@args
= ();
return
$self
->_error(
'LDIF file handle not valid'
)
unless
(
$self
->{fh});
foreach
my
$elem
(
@_
) {
if
(
ref
(
$elem
)) {
if
(
scalar
(
@args
) % 2) {
$res
&&=
$self
->_write_one(
$change
,
@args
);
@args
= ();
}
}
elsif
(!
@args
) {
$self
->_error(
"Entry '$elem' is not a valid Net::LDAP::Entry object."
);
$res
= 0;
@args
= ();
next
;
}
push
(
@args
,
$elem
);
}
if
(
scalar
(
@args
) % 2) {
$res
&&=
$self
->_write_one(
$change
,
@args
);
}
elsif
(
@args
) {
$self
->error(
"Illegal argument list passed"
);
$res
= 0;
}
$self
->_error($!)
if
(!
$res
&& $!);
$res
;
}
sub
_write_one
{
my
$self
=
shift
;
my
$change
=
shift
;
my
$entry
=
shift
;
my
%opt
=
@_
;
my
$fh
=
$self
->{fh};
my
$res
= 1;
local
($\, $,);
if
(
$change
) {
my
@changes
=
$entry
->changes;
my
$type
=
$entry
->changetype;
return
$res
if
(
$type
eq
'modify'
and !
@changes
);
$res
&&=
$self
->write_version()
unless
(
$self
->{write_count}++);
$res
&&=
print
$fh
"\n"
;
$res
&&=
$self
->_write_dn(
$entry
->dn);
$res
&&=
$self
->_write_controls(
ref
(
$opt
{control}) eq
'ARRAY'
? @{
$opt
{control}}
: (
$opt
{control} ))
if
(
$opt
{control});
$res
&&=
print
$fh
"changetype: $type\n"
;
if
(
$type
eq
'delete'
) {
return
$res
;
}
elsif
(
$type
eq
'add'
) {
$res
&&=
$self
->_write_attrs(
$entry
);
return
$res
;
}
elsif
(
$type
=~ /modr?dn/o) {
my
$deleteoldrdn
=
$entry
->get_value(
'deleteoldrdn'
) || 0;
$res
&&=
$self
->_write_attr(
'newrdn'
,
$entry
->get_value(
'newrdn'
,
asref
=> 1));
$res
&&=
print
$fh
'deleteoldrdn: '
,
$deleteoldrdn
,
"\n"
;
my
$ns
=
$entry
->get_value(
'newsuperior'
,
asref
=> 1);
$res
&&=
$self
->_write_attr(
'newsuperior'
,
$ns
)
if
(
defined
$ns
);
return
$res
;
}
my
$dash
= 0;
while
(
my
(
$action
,
$attrs
) =
splice
(
@changes
, 0, 2)) {
my
@attrs
=
@$attrs
;
while
(
my
(
$attr
,
$val
) =
splice
(
@attrs
, 0, 2)) {
$res
&&=
print
$fh
"-\n"
if
(!
$self
->{version} &&
$dash
++);
$res
&&=
print
$fh
"$action: $attr\n"
;
$res
&&=
$self
->_write_attr(
$attr
,
$val
);
$res
&&=
print
$fh
"-\n"
if
(
$self
->{version});
}
}
}
else
{
$res
&&=
$self
->write_version()
unless
(
$self
->{write_count}++);
$res
&&=
print
$fh
"\n"
;
$res
&&=
$self
->_write_dn(
$entry
->dn);
$res
&&=
$self
->_write_attrs(
$entry
);
}
$res
;
}
sub
read_cmd {
my
$self
=
shift
;
return
$self
->read_entry()
unless
wantarray
;
my
(
$entry
,
@entries
);
push
(
@entries
,
$entry
)
while
(
$entry
=
$self
->read_entry);
@entries
;
}
*_read_one_cmd
= \
&_read_entry
;
sub
write_cmd {
my
$self
=
shift
;
$self
->_write_entry(1,
@_
);
}
sub
done {
my
$self
=
shift
;
my
$res
= 1;
if
(
$self
->{fh}) {
if
(
$self
->{opened_fh}) {
$res
=
close
(
$self
->{fh});
undef
$self
->{opened_fh};
}
delete
$self
->{fh};
}
$res
;
}
sub
handle {
my
$self
=
shift
;
return
$self
->{fh};
}
my
%onerror
= (
die
=>
sub
{
my
$self
=
shift
;
$self
->done;
Carp::croak(
$self
->error(
@_
));
},
warn
=>
sub
{
my
$self
=
shift
;
Carp::carp(
$self
->error(
@_
));
},
undef
=>
sub
{
my
$self
=
shift
;
Carp::carp(
$self
->error(
@_
))
if
($^W);
},
);
sub
_error {
my
(
$self
,
$errmsg
,
@errlines
) =
@_
;
$self
->{_err_msg} =
$errmsg
;
$self
->{_err_lines} =
join
(
"\n"
,
@errlines
);
scalar
&{
$onerror
{
$self
->{onerror} } }(
$self
,
$self
->{_err_msg})
if
(
$self
->{onerror});
return
;
}
sub
_clear_error {
my
$self
=
shift
;
undef
$self
->{_err_msg};
undef
$self
->{_err_lines};
}
sub
error {
my
$self
=
shift
;
$self
->{_err_msg};
}
sub
error_lines {
my
$self
=
shift
;
$self
->{_err_lines};
}
sub
current_entry {
my
$self
=
shift
;
$self
->{_current_entry};
}
sub
current_lines {
my
$self
=
shift
;
$self
->{_current_lines};
}
sub
version {
my
$self
=
shift
;
return
$self
->{version}
unless
(
@_
);
$self
->{version} =
shift
|| 0;
}
sub
next_lines {
my
$self
=
shift
;
$self
->{_next_lines};
}
sub
DESTROY {
my
$self
=
shift
;
$self
->done();
}
1;