line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::LDAP::Server::Test; |
2
|
|
|
|
|
|
|
|
3
|
15
|
|
|
15
|
|
1377120
|
use warnings; |
|
15
|
|
|
|
|
25
|
|
|
15
|
|
|
|
|
543
|
|
4
|
15
|
|
|
15
|
|
52
|
use strict; |
|
15
|
|
|
|
|
21
|
|
|
15
|
|
|
|
|
236
|
|
5
|
15
|
|
|
15
|
|
41
|
use Carp; |
|
15
|
|
|
|
|
19
|
|
|
15
|
|
|
|
|
810
|
|
6
|
15
|
|
|
15
|
|
2101
|
use IO::Socket; |
|
15
|
|
|
|
|
81145
|
|
|
15
|
|
|
|
|
79
|
|
7
|
15
|
|
|
15
|
|
8426
|
use IO::Select; |
|
15
|
|
|
|
|
5601
|
|
|
15
|
|
|
|
|
492
|
|
8
|
15
|
|
|
15
|
|
6376
|
use Data::Dump (); |
|
15
|
|
|
|
|
80256
|
|
|
15
|
|
|
|
|
325
|
|
9
|
15
|
|
|
15
|
|
6840
|
use Net::LDAP::SID; |
|
15
|
|
|
|
|
6961
|
|
|
15
|
|
|
|
|
721
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.21'; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 NAME |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
Net::LDAP::Server::Test - test Net::LDAP code |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 SYNOPSIS |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
use Test::More tests => 10; |
20
|
|
|
|
|
|
|
use Net::LDAP::Server::Test; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
ok( my $server = Net::LDAP::Server::Test->new(8080), |
23
|
|
|
|
|
|
|
"test LDAP server spawned"); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# connect to port 8080 with your Net::LDAP code. |
26
|
|
|
|
|
|
|
ok(my $ldap = Net::LDAP->new( 'localhost', port => 8080 ), |
27
|
|
|
|
|
|
|
"new LDAP connection" ); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# ... test stuff with $ldap ... |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# server will exit when you call final LDAP unbind(). |
32
|
|
|
|
|
|
|
ok($ldap->unbind(), "LDAP server unbound"); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 DESCRIPTION |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Now you can test your Net::LDAP code without having a real |
37
|
|
|
|
|
|
|
LDAP server available. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head1 METHODS |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Only one user-level method is implemented: new(). |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=cut |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
{ |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
package # fool Pause |
48
|
|
|
|
|
|
|
MyLDAPServer; |
49
|
|
|
|
|
|
|
|
50
|
15
|
|
|
15
|
|
68
|
use strict; |
|
15
|
|
|
|
|
19
|
|
|
15
|
|
|
|
|
232
|
|
51
|
15
|
|
|
15
|
|
43
|
use warnings; |
|
15
|
|
|
|
|
12
|
|
|
15
|
|
|
|
|
332
|
|
52
|
15
|
|
|
15
|
|
142
|
use Carp; |
|
15
|
|
|
|
|
14
|
|
|
15
|
|
|
|
|
745
|
|
53
|
15
|
|
|
|
|
1439
|
use Net::LDAP::Constant qw( |
54
|
|
|
|
|
|
|
LDAP_SUCCESS |
55
|
|
|
|
|
|
|
LDAP_NO_SUCH_OBJECT |
56
|
|
|
|
|
|
|
LDAP_CONTROL_PAGED |
57
|
|
|
|
|
|
|
LDAP_OPERATIONS_ERROR |
58
|
|
|
|
|
|
|
LDAP_UNWILLING_TO_PERFORM |
59
|
|
|
|
|
|
|
LDAP_ALREADY_EXISTS |
60
|
|
|
|
|
|
|
LDAP_TYPE_OR_VALUE_EXISTS |
61
|
|
|
|
|
|
|
LDAP_NO_SUCH_ATTRIBUTE |
62
|
15
|
|
|
15
|
|
2445
|
); |
|
15
|
|
|
|
|
18726
|
|
63
|
15
|
|
|
15
|
|
35984
|
use Net::LDAP::Util qw(ldap_explode_dn canonical_dn); |
|
15
|
|
|
|
|
662
|
|
|
15
|
|
|
|
|
871
|
|
64
|
15
|
|
|
15
|
|
5875
|
use Net::LDAP::Entry; |
|
15
|
|
|
|
|
377193
|
|
|
15
|
|
|
|
|
372
|
|
65
|
15
|
|
|
15
|
|
6989
|
use Net::LDAP::Filter; |
|
15
|
|
|
|
|
27653
|
|
|
15
|
|
|
|
|
397
|
|
66
|
15
|
|
|
15
|
|
6219
|
use Net::LDAP::FilterMatch; |
|
15
|
|
|
|
|
59927
|
|
|
15
|
|
|
|
|
105
|
|
67
|
15
|
|
|
15
|
|
52058
|
use Net::LDAP::Control; |
|
15
|
|
|
|
|
11575
|
|
|
15
|
|
|
|
|
496
|
|
68
|
15
|
|
|
15
|
|
86
|
use Net::LDAP::ASN qw(LDAPRequest LDAPResponse); |
|
15
|
|
|
|
|
21
|
|
|
15
|
|
|
|
|
97
|
|
69
|
15
|
|
|
15
|
|
1021
|
use Convert::ASN1 qw(asn_read); |
|
15
|
|
|
|
|
19
|
|
|
15
|
|
|
|
|
649
|
|
70
|
|
|
|
|
|
|
|
71
|
15
|
|
|
15
|
|
64
|
use base 'Net::LDAP::Server'; |
|
15
|
|
|
|
|
20
|
|
|
15
|
|
|
|
|
7650
|
|
72
|
15
|
|
|
15
|
|
114755
|
use fields qw( _flags ); |
|
15
|
|
|
|
|
25
|
|
|
15
|
|
|
|
|
55
|
|
73
|
|
|
|
|
|
|
|
74
|
15
|
|
|
|
|
874
|
use constant RESULT_OK => { |
75
|
|
|
|
|
|
|
'matchedDN' => '', |
76
|
|
|
|
|
|
|
'errorMessage' => '', |
77
|
|
|
|
|
|
|
'resultCode' => LDAP_SUCCESS |
78
|
15
|
|
|
15
|
|
733
|
}; |
|
15
|
|
|
|
|
22
|
|
79
|
|
|
|
|
|
|
|
80
|
15
|
|
|
|
|
707
|
use constant RESULT_NO_SUCH_OBJECT => { |
81
|
|
|
|
|
|
|
'matchedDN' => '', |
82
|
|
|
|
|
|
|
'errorMessage' => '', |
83
|
|
|
|
|
|
|
'resultCode' => LDAP_NO_SUCH_OBJECT, |
84
|
15
|
|
|
15
|
|
69
|
}; |
|
15
|
|
|
|
|
17
|
|
85
|
|
|
|
|
|
|
|
86
|
15
|
|
|
|
|
690
|
use constant RESULT_ALREADY_EXISTS => { |
87
|
|
|
|
|
|
|
'matchedDN' => '', |
88
|
|
|
|
|
|
|
'errorMessage' => '', |
89
|
|
|
|
|
|
|
'resultCode' => LDAP_ALREADY_EXISTS, |
90
|
15
|
|
|
15
|
|
54
|
}; |
|
15
|
|
|
|
|
19
|
|
91
|
|
|
|
|
|
|
|
92
|
15
|
|
|
|
|
695
|
use constant RESULT_TYPE_OR_VALUE_EXISTS => { |
93
|
|
|
|
|
|
|
'matchedDN' => '', |
94
|
|
|
|
|
|
|
'errorMessage' => '', |
95
|
|
|
|
|
|
|
'resultCode' => LDAP_TYPE_OR_VALUE_EXISTS, |
96
|
15
|
|
|
15
|
|
62
|
}; |
|
15
|
|
|
|
|
28
|
|
97
|
|
|
|
|
|
|
|
98
|
15
|
|
|
|
|
40194
|
use constant RESULT_NO_SUCH_ATTRIBUTE => { |
99
|
|
|
|
|
|
|
'matchedDN' => '', |
100
|
|
|
|
|
|
|
'errorMessage' => '', |
101
|
|
|
|
|
|
|
'resultCode' => LDAP_NO_SUCH_ATTRIBUTE, |
102
|
15
|
|
|
15
|
|
47
|
}; |
|
15
|
|
|
|
|
20
|
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
our %Data; # package data lasts as long as $$ does. |
105
|
|
|
|
|
|
|
our $Cookies = 0; |
106
|
|
|
|
|
|
|
our %Searches; |
107
|
|
|
|
|
|
|
my @Scopes = qw(base one sub); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# constructor |
110
|
|
|
|
|
|
|
sub new { |
111
|
7
|
|
|
7
|
|
41
|
my ( $class, $sock, %args ) = @_; |
112
|
7
|
|
|
|
|
262
|
my $self = $class->SUPER::new($sock); |
113
|
|
|
|
|
|
|
warn sprintf "Accepted connection from: %s\n", $sock->peerhost() |
114
|
7
|
50
|
|
|
|
28109
|
if $ENV{LDAP_DEBUG}; |
115
|
7
|
|
|
|
|
25
|
$self->{_flags} = \%args; |
116
|
7
|
|
|
|
|
130
|
return $self; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub unbind { |
120
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
121
|
0
|
|
|
|
|
0
|
my $reqData = shift; |
122
|
0
|
|
|
|
|
0
|
return RESULT_OK; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# the bind operation |
126
|
|
|
|
|
|
|
sub bind { |
127
|
5
|
|
|
5
|
|
12
|
my $self = shift; |
128
|
5
|
|
|
|
|
9
|
my $reqData = shift; |
129
|
5
|
|
|
|
|
15
|
return RESULT_OK; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# the search operation |
133
|
|
|
|
|
|
|
sub search { |
134
|
17
|
|
|
17
|
|
30
|
my $self = shift; |
135
|
|
|
|
|
|
|
|
136
|
17
|
100
|
|
|
|
84
|
if ( defined $self->{_flags}->{data} ) { |
|
|
100
|
|
|
|
|
|
137
|
1
|
|
|
|
|
5
|
return $self->_search_user_supplied_data(@_); |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
elsif ( defined $self->{_flags}->{auto_schema} ) { |
140
|
14
|
|
|
|
|
62
|
return $self->_search_auto_schema_data(@_); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
else { |
143
|
2
|
|
|
|
|
23
|
return $self->_search_default_test_data(@_); |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub _search_user_supplied_data { |
148
|
1
|
|
|
1
|
|
2
|
my ( $self, $reqData ) = @_; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# TODO?? |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
#warn 'SEARCH USER DATA: ' . Data::Dump::dump \@_; |
153
|
1
|
|
|
|
|
1
|
return RESULT_OK, @{ $self->{_flags}->{data} }; |
|
1
|
|
|
|
|
4
|
|
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub _search_auto_schema_data { |
157
|
14
|
|
|
14
|
|
21
|
my ( $self, $reqData, $reqMsg ) = @_; |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
#warn 'SEARCH SCHEMA: ' . Data::Dump::dump \@_; |
160
|
|
|
|
|
|
|
|
161
|
14
|
|
|
|
|
17
|
my @results; |
162
|
14
|
|
|
|
|
29
|
my $base = $reqData->{baseObject}; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# $reqData->{scope} is a enum but we want a word |
165
|
|
|
|
|
|
|
my $scope |
166
|
14
|
50
|
|
|
|
82
|
= $Scopes[ defined $reqData->{scope} ? $reqData->{scope} : 2 ]; |
167
|
14
|
50
|
|
|
|
16
|
my @attrs = @{ $reqData->{attributes} || [] }; |
|
14
|
|
|
|
|
54
|
|
168
|
14
|
|
|
|
|
21
|
my @filters = (); |
169
|
|
|
|
|
|
|
|
170
|
14
|
50
|
|
|
|
43
|
if ( exists $reqData->{filter} ) { |
171
|
|
|
|
|
|
|
push( @filters, |
172
|
14
|
|
|
|
|
65
|
bless( $reqData->{filter}, 'Net::LDAP::Filter' ) ); |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
14
|
50
|
|
|
|
67
|
if ( $ENV{LDAP_DEBUG} ) { |
177
|
0
|
|
|
|
|
0
|
warn "search for '$base' with scope '$scope' in Data: " |
178
|
|
|
|
|
|
|
. Data::Dump::dump \%Data; |
179
|
0
|
|
|
|
|
0
|
warn "filters: " . Data::Dump::dump \@filters; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# support paged results |
183
|
14
|
|
|
|
|
20
|
my ( $page_size, $cookie, $controls, $offset ); |
184
|
14
|
50
|
|
|
|
54
|
if ( exists $reqMsg->{controls} ) { |
185
|
0
|
|
|
|
|
0
|
for my $control ( @{ $reqMsg->{controls} } ) { |
|
0
|
|
|
|
|
0
|
|
186
|
|
|
|
|
|
|
|
187
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
188
|
0
|
|
|
|
|
0
|
warn "control: " . Data::Dump::dump($control) . "\n"; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
0
|
0
|
|
|
|
0
|
if ( $control->{type} eq LDAP_CONTROL_PAGED ) { |
192
|
0
|
|
|
|
|
0
|
my $asn = Net::LDAP::Control->from_asn($control); |
193
|
|
|
|
|
|
|
|
194
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
195
|
0
|
|
|
|
|
0
|
warn "asn: " . Data::Dump::dump($asn) . "\n"; |
196
|
|
|
|
|
|
|
} |
197
|
0
|
|
|
|
|
0
|
$page_size = $asn->size; |
198
|
|
|
|
|
|
|
|
199
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
200
|
0
|
|
|
|
|
0
|
warn "size == $page_size"; |
201
|
0
|
|
|
|
|
0
|
warn "cookie == " . $asn->cookie; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# assign a cookie if this is the first page of paged search |
205
|
0
|
0
|
|
|
|
0
|
if ( !$asn->cookie ) { |
206
|
0
|
|
|
|
|
0
|
$asn->cookie( ++$Cookies ); |
207
|
0
|
|
|
|
|
0
|
$asn->value; # IMPORTANT!! encode value with cookie |
208
|
|
|
|
|
|
|
|
209
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
210
|
0
|
|
|
|
|
0
|
warn "no cookie assigned. setting to $Cookies"; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# keep track of offset |
214
|
0
|
|
|
|
|
0
|
$Searches{ $asn->cookie } = 0; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
0
|
$offset = $Searches{ $asn->cookie }; |
218
|
0
|
|
|
|
|
0
|
$cookie = $asn->cookie; |
219
|
|
|
|
|
|
|
|
220
|
0
|
|
|
|
|
0
|
push( @$controls, $asn ); |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# loop over all keys looking for match |
226
|
|
|
|
|
|
|
# we sort in order for paged control to work |
227
|
14
|
|
|
|
|
108
|
ENTRY: for my $dn ( sort keys %Data ) { |
228
|
|
|
|
|
|
|
|
229
|
32
|
100
|
|
|
|
287
|
next unless $dn =~ m/$base$/; |
230
|
|
|
|
|
|
|
|
231
|
24
|
100
|
|
|
|
66
|
if ( $scope eq 'base' ) { |
|
|
100
|
|
|
|
|
|
232
|
12
|
100
|
|
|
|
166
|
next unless $dn eq $base; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
elsif ( $scope eq 'one' ) { |
235
|
6
|
|
|
|
|
4
|
my $dn_depth = scalar @{ ldap_explode_dn($dn) }; |
|
6
|
|
|
|
|
28
|
|
236
|
6
|
|
|
|
|
788
|
my $base_depth = scalar @{ ldap_explode_dn($base) }; |
|
6
|
|
|
|
|
16
|
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# We're guaranteed to be at or under $base thanks to the m// above |
239
|
6
|
100
|
|
|
|
366
|
next unless $dn_depth == $base_depth + 1; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
16
|
|
|
|
|
31
|
my $entry = $Data{$dn}; |
243
|
|
|
|
|
|
|
|
244
|
16
|
50
|
|
|
|
95
|
if ( $ENV{LDAP_DEBUG} ) { |
245
|
0
|
|
|
|
|
0
|
warn "trying to match $dn : " . Data::Dump::dump $entry; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
16
|
|
|
|
|
91
|
my $match = 0; |
249
|
16
|
|
|
|
|
25
|
for my $filter (@filters) { |
250
|
|
|
|
|
|
|
|
251
|
16
|
100
|
|
|
|
127
|
if ( $filter->match($entry) ) { |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
#warn "$f matches entry $dn"; |
254
|
11
|
|
|
|
|
1293
|
$match++; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
#warn "matched $match"; |
259
|
16
|
100
|
|
|
|
800
|
if ( $match == scalar(@filters) ) { |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# clone the entry so that client cannot modify %Data |
262
|
11
|
|
|
|
|
41
|
my $result = $entry->clone; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# filter returned attributes to those requested |
265
|
11
|
50
|
|
|
|
958
|
if (@attrs) { |
266
|
0
|
|
|
|
|
0
|
my %wanted = map { $_ => 1 } @attrs; |
|
0
|
|
|
|
|
0
|
|
267
|
|
|
|
|
|
|
$result->delete($_) |
268
|
0
|
|
|
|
|
0
|
for grep { not $wanted{$_} } $result->attributes; |
|
0
|
|
|
|
|
0
|
|
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
11
|
|
|
|
|
25
|
push( @results, $result ); |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# for paged results we find everything then take a slice. |
277
|
|
|
|
|
|
|
# this is less how a Real Server would do it but does |
278
|
|
|
|
|
|
|
# work for the simple case where we want to make sure our offset |
279
|
|
|
|
|
|
|
# and page size are accurate and we're not returning the same results |
280
|
|
|
|
|
|
|
# in multiple pages. |
281
|
|
|
|
|
|
|
# the $page_size -1 is because we're zero-based. |
282
|
|
|
|
|
|
|
|
283
|
14
|
|
|
|
|
29
|
my $total_found = scalar(@results); |
284
|
14
|
50
|
|
|
|
39
|
if ( $ENV{LDAP_DEBUG} ) { |
285
|
0
|
|
|
|
|
0
|
warn "found $total_found total results for filters:" |
286
|
|
|
|
|
|
|
. Data::Dump::dump( \@filters ); |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
#warn Data::Dump::dump( \@results ); |
289
|
0
|
0
|
|
|
|
0
|
if ($page_size) { |
290
|
0
|
|
|
|
|
0
|
warn "page_size == $page_size offset == $offset\n"; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
14
|
100
|
100
|
|
|
157
|
if ( $scope eq 'base' and $total_found == 0 ) { |
295
|
3
|
|
|
|
|
12
|
return RESULT_NO_SUCH_OBJECT; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
11
|
50
|
33
|
|
|
86
|
if ( $page_size && $offset > $#results ) { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
299
|
|
|
|
|
|
|
|
300
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
301
|
0
|
|
|
|
|
0
|
warn "exceeded end of results\n"; |
302
|
|
|
|
|
|
|
} |
303
|
0
|
|
|
|
|
0
|
@results = (); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# IMPORTANT!! must set pager cookie to false |
306
|
|
|
|
|
|
|
# to indicate no more results |
307
|
0
|
|
|
|
|
0
|
for my $control (@$controls) { |
308
|
0
|
0
|
|
|
|
0
|
if ( $control->isa('Net::LDAP::Control::Paged') ) { |
309
|
0
|
|
|
|
|
0
|
$control->cookie(undef); |
310
|
0
|
|
|
|
|
0
|
$control->value; # IMPORTANT!! re-encode |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
elsif ( $page_size && @results ) { |
315
|
|
|
|
|
|
|
|
316
|
0
|
|
|
|
|
0
|
my $limit = $offset + $page_size - 1; |
317
|
0
|
0
|
|
|
|
0
|
if ( $limit > $#results ) { |
318
|
0
|
|
|
|
|
0
|
$limit = $#results; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
322
|
0
|
|
|
|
|
0
|
warn "slice \@results[ $offset .. $limit ]\n"; |
323
|
|
|
|
|
|
|
} |
324
|
0
|
|
|
|
|
0
|
@results = @results[ $offset .. $limit ]; |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# update our global marker |
327
|
0
|
|
|
|
|
0
|
$Searches{$cookie} = $limit + 1; |
328
|
|
|
|
|
|
|
|
329
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
330
|
0
|
|
|
|
|
0
|
warn "returning " . scalar(@results) . " total results\n"; |
331
|
0
|
|
|
|
|
0
|
warn "next offset start is $Searches{$cookie}\n"; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
#warn Data::Dump::dump( \@results ); |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# special case. client is telling server to abort. |
339
|
|
|
|
|
|
|
elsif ( defined $page_size && $page_size == 0 ) { |
340
|
|
|
|
|
|
|
|
341
|
0
|
|
|
|
|
0
|
@results = (); |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
#warn "search results for " . Data::Dump::dump($reqData) . "\n: " |
346
|
|
|
|
|
|
|
# . Data::Dump::dump \@results; |
347
|
|
|
|
|
|
|
|
348
|
11
|
|
|
|
|
47
|
return ( RESULT_OK, \@results, $controls ); |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
sub _search_default_test_data { |
353
|
2
|
|
|
2
|
|
7
|
my ( $self, $reqData ) = @_; |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
#warn 'SEARCH DEFAULT: ' . Data::Dump::dump \@_; |
356
|
|
|
|
|
|
|
|
357
|
2
|
|
|
|
|
9
|
my $base = $reqData->{'baseObject'}; |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# plain die if dn contains 'dying' |
360
|
2
|
50
|
|
|
|
12
|
die("panic") if $base =~ /dying/; |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# return a correct LDAPresult, but an invalid entry |
363
|
2
|
50
|
|
|
|
13
|
return RESULT_OK, { test => 1 } if $base =~ /invalid entry/; |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# return an invalid LDAPresult |
366
|
2
|
50
|
|
|
|
4
|
return { test => 1 } if $base =~ /invalid result/; |
367
|
|
|
|
|
|
|
|
368
|
2
|
|
|
|
|
4
|
my @entries; |
369
|
2
|
50
|
|
|
|
9
|
if ( $reqData->{'scope'} ) { |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# onelevel or subtree |
372
|
2
|
|
|
|
|
11
|
for ( my $i = 1; $i < 11; $i++ ) { |
373
|
20
|
|
|
|
|
44
|
my $dn = "ou=test $i,$base"; |
374
|
20
|
|
|
|
|
95
|
my $entry = Net::LDAP::Entry->new; |
375
|
20
|
|
|
|
|
206
|
$entry->dn($dn); |
376
|
20
|
|
|
|
|
157
|
$entry->add( |
377
|
|
|
|
|
|
|
dn => $dn, |
378
|
|
|
|
|
|
|
sn => 'value1', |
379
|
|
|
|
|
|
|
cn => [qw(value1 value2)] |
380
|
|
|
|
|
|
|
); |
381
|
20
|
|
|
|
|
672
|
push @entries, $entry; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
2
|
|
|
|
|
12
|
my $entry1 = Net::LDAP::Entry->new; |
385
|
2
|
|
|
|
|
21
|
$entry1->dn("cn=dying entry,$base"); |
386
|
2
|
|
|
|
|
13
|
$entry1->add( |
387
|
|
|
|
|
|
|
cn => 'dying entry', |
388
|
|
|
|
|
|
|
description => |
389
|
|
|
|
|
|
|
'This entry will result in a dying error when queried' |
390
|
|
|
|
|
|
|
); |
391
|
2
|
|
|
|
|
52
|
push @entries, $entry1; |
392
|
|
|
|
|
|
|
|
393
|
2
|
|
|
|
|
5
|
my $entry2 = Net::LDAP::Entry->new; |
394
|
2
|
|
|
|
|
17
|
$entry2->dn("cn=invalid entry,$base"); |
395
|
2
|
|
|
|
|
31
|
$entry2->add( |
396
|
|
|
|
|
|
|
cn => 'invalid entry', |
397
|
|
|
|
|
|
|
description => |
398
|
|
|
|
|
|
|
'This entry will result in ASN1 error when queried' |
399
|
|
|
|
|
|
|
); |
400
|
2
|
|
|
|
|
57
|
push( @entries, $entry2 ); |
401
|
|
|
|
|
|
|
|
402
|
2
|
|
|
|
|
8
|
my $entry3 = Net::LDAP::Entry->new; |
403
|
2
|
|
|
|
|
23
|
$entry3->dn("cn=invalid result,$base"); |
404
|
2
|
|
|
|
|
11
|
$entry3->add( |
405
|
|
|
|
|
|
|
cn => 'invalid result', |
406
|
|
|
|
|
|
|
description => |
407
|
|
|
|
|
|
|
'This entry will result in ASN1 error when queried' |
408
|
|
|
|
|
|
|
); |
409
|
2
|
|
|
|
|
54
|
push @entries, $entry3; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
else { |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# base |
414
|
0
|
|
|
|
|
0
|
my $entry = Net::LDAP::Entry->new; |
415
|
0
|
|
|
|
|
0
|
$entry->dn($base); |
416
|
0
|
|
|
|
|
0
|
$entry->add( |
417
|
|
|
|
|
|
|
dn => $base, |
418
|
|
|
|
|
|
|
sn => 'value1', |
419
|
|
|
|
|
|
|
cn => [qw(value1 value2)] |
420
|
|
|
|
|
|
|
); |
421
|
0
|
|
|
|
|
0
|
push @entries, $entry; |
422
|
|
|
|
|
|
|
} |
423
|
2
|
|
|
|
|
13
|
return RESULT_OK, @entries; |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
sub add { |
427
|
10
|
|
|
10
|
|
14
|
my ( $self, $reqData, $reqMsg ) = @_; |
428
|
|
|
|
|
|
|
|
429
|
10
|
|
|
|
|
17
|
my $key = $reqData->{objectName}; |
430
|
10
|
50
|
|
|
|
28
|
if ( $ENV{LDAP_DEBUG} ) { |
431
|
0
|
|
|
|
|
0
|
warn 'ADD: ' . Data::Dump::dump \@_; |
432
|
0
|
|
|
|
|
0
|
warn "key: $key"; |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
10
|
100
|
|
|
|
23
|
if ( exists $Data{$key} ) { |
436
|
1
|
|
|
|
|
8
|
return RESULT_ALREADY_EXISTS; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
9
|
|
|
|
|
72
|
my $entry = Net::LDAP::Entry->new; |
440
|
9
|
|
|
|
|
112
|
$entry->dn($key); |
441
|
9
|
|
|
|
|
56
|
for my $attr ( @{ $reqData->{attributes} } ) { |
|
9
|
|
|
|
|
38
|
|
442
|
18
|
|
|
|
|
215
|
$entry->add( $attr->{type} => \@{ $attr->{vals} } ); |
|
18
|
|
|
|
|
70
|
|
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
9
|
|
|
|
|
128
|
$Data{$key} = $entry; |
446
|
|
|
|
|
|
|
|
447
|
9
|
50
|
|
|
|
24
|
if ( exists $self->{_flags}->{active_directory} ) { |
448
|
0
|
|
|
|
|
0
|
$self->_add_AD( $reqData, $reqMsg, $key, $entry, \%Data ); |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
9
|
|
|
|
|
18
|
return RESULT_OK; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
sub modify { |
455
|
8
|
|
|
8
|
|
14
|
my ( $self, $reqData, $reqMsg ) = @_; |
456
|
|
|
|
|
|
|
|
457
|
8
|
50
|
|
|
|
24
|
if ( $ENV{LDAP_DEBUG} ) { |
458
|
0
|
|
|
|
|
0
|
warn 'MODIFY: ' . Data::Dump::dump \@_; |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
8
|
|
|
|
|
11
|
my $key = $reqData->{object}; |
462
|
8
|
100
|
|
|
|
23
|
if ( !exists $Data{$key} ) { |
463
|
1
|
|
|
|
|
2
|
return RESULT_NO_SUCH_OBJECT; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
7
|
|
|
|
|
12
|
my @mods = @{ $reqData->{modification} }; |
|
7
|
|
|
|
|
16
|
|
467
|
7
|
|
|
|
|
17
|
for my $mod (@mods) { |
468
|
7
|
|
|
|
|
10
|
my $attr = $mod->{modification}->{type}; |
469
|
7
|
|
|
|
|
9
|
my $vals = $mod->{modification}->{vals}; |
470
|
7
|
|
|
|
|
9
|
my $entry = $Data{$key}; |
471
|
|
|
|
|
|
|
|
472
|
7
|
|
|
|
|
29
|
my $current_value = $entry->get_value( $attr, asref => 1 ); |
473
|
|
|
|
|
|
|
|
474
|
7
|
100
|
|
|
|
84
|
if ( $mod->{operation} == 0 ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
475
|
3
|
100
|
|
|
|
11
|
if ( defined $current_value ) { |
476
|
2
|
|
|
|
|
6
|
for my $v (@$current_value) { |
477
|
2
|
100
|
|
|
|
4
|
if ( grep { $_ eq $v } @$vals ) { |
|
2
|
|
|
|
|
12
|
|
478
|
1
|
|
|
|
|
3
|
return RESULT_TYPE_OR_VALUE_EXISTS; |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
2
|
|
|
|
|
14
|
$entry->add( $attr => $vals ); |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
elsif ( $mod->{operation} == 1 ) { |
486
|
3
|
100
|
|
|
|
20
|
if ( !defined $current_value ) { |
487
|
1
|
|
|
|
|
3
|
return RESULT_NO_SUCH_ATTRIBUTE; |
488
|
|
|
|
|
|
|
} |
489
|
2
|
|
|
|
|
26
|
$entry->delete( $attr => $vals ); |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
elsif ( $mod->{operation} == 2 ) { |
492
|
1
|
|
|
|
|
9
|
$entry->replace( $attr => $vals ); |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
else { |
495
|
0
|
|
|
|
|
0
|
croak "unknown modify operation: $mod->{operation}"; |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
5
|
50
|
|
|
|
118
|
if ( $self->{_flags}->{active_directory} ) { |
500
|
0
|
|
|
|
|
0
|
$self->_modify_AD( $reqData, $reqMsg, \%Data ); |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
|
503
|
5
|
|
|
|
|
12
|
return RESULT_OK; |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
sub delete { |
508
|
0
|
|
|
0
|
|
0
|
my ( $self, $reqData, $reqMsg ) = @_; |
509
|
|
|
|
|
|
|
|
510
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
511
|
0
|
|
|
|
|
0
|
warn 'DELETE: ' . Data::Dump::dump \@_; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
0
|
my $key = $reqData; |
515
|
0
|
0
|
|
|
|
0
|
if ( !exists $Data{$key} ) { |
516
|
0
|
|
|
|
|
0
|
return RESULT_NO_SUCH_OBJECT; |
517
|
|
|
|
|
|
|
} |
518
|
0
|
|
|
|
|
0
|
delete $Data{$key}; |
519
|
|
|
|
|
|
|
|
520
|
0
|
|
|
|
|
0
|
return RESULT_OK; |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
sub modifyDN { |
525
|
3
|
|
|
3
|
|
5
|
my ( $self, $reqData, $reqMsg ) = @_; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
#warn "modifyDN: " . Data::Dump::dump \@_; |
528
|
|
|
|
|
|
|
#warn "modifyDN: " . Data::Dump::dump($reqData); |
529
|
|
|
|
|
|
|
#warn "existing: " . Data::Dump::dump( \%Data ); |
530
|
|
|
|
|
|
|
#warn "existing DNs: " . Data::Dump::dump([keys %Data]); |
531
|
|
|
|
|
|
|
|
532
|
3
|
|
|
|
|
6
|
my $oldkey = $reqData->{entry}; |
533
|
3
|
|
|
|
|
5
|
my $newkey = $reqData->{newrdn}; |
534
|
3
|
50
|
|
|
|
6
|
if ( defined $reqData->{newSuperior} ) { |
535
|
0
|
|
|
|
|
0
|
$newkey .= ',' . $reqData->{newSuperior}; |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
else { |
538
|
|
|
|
|
|
|
# As we only have the new relative DN, we still |
539
|
|
|
|
|
|
|
# need the base for it. We'll take it from $oldkey |
540
|
3
|
|
|
|
|
26
|
my $exploded_dn = ldap_explode_dn( $oldkey, casefold => 'none' ); |
541
|
3
|
|
|
|
|
393
|
shift @$exploded_dn; |
542
|
3
|
|
|
|
|
12
|
$newkey .= ',' . canonical_dn( $exploded_dn, casefold => 'none' ); |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
3
|
100
|
|
|
|
94
|
if ( !exists $Data{$oldkey} ) { |
546
|
1
|
|
|
|
|
2
|
return RESULT_NO_SUCH_OBJECT; |
547
|
|
|
|
|
|
|
} |
548
|
2
|
100
|
|
|
|
5
|
if ( exists $Data{$newkey} ) { |
549
|
1
|
|
|
|
|
2
|
return RESULT_ALREADY_EXISTS; |
550
|
|
|
|
|
|
|
} |
551
|
1
|
|
|
|
|
2
|
my $entry = $Data{$oldkey}; |
552
|
1
|
|
|
|
|
4
|
my $newentry = $entry->clone; |
553
|
1
|
|
|
|
|
81
|
$newentry->dn($newkey); |
554
|
1
|
|
|
|
|
4
|
$Data{$newkey} = $newentry; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
#warn "created new entry: $newkey"; |
557
|
1
|
50
|
|
|
|
4
|
if ( $reqData->{deleteoldrdn} ) { |
558
|
1
|
|
|
|
|
2
|
delete $Data{$oldkey}; |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
#warn "deleted old entry: $oldkey"; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
1
|
|
|
|
|
4
|
return RESULT_OK; |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
sub compare { |
567
|
0
|
|
|
0
|
|
0
|
my ( $self, $reqData, $reqMsg ) = @_; |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
#warn "compare: " . Data::Dump::dump \@_; |
570
|
|
|
|
|
|
|
|
571
|
0
|
|
|
|
|
0
|
return RESULT_OK; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
sub abandon { |
575
|
0
|
|
|
0
|
|
0
|
my ( $self, $reqData, $reqMsg ) = @_; |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
#warn "abandon: " . Data::Dump::dump \@_; |
578
|
|
|
|
|
|
|
|
579
|
0
|
|
|
|
|
0
|
return RESULT_OK; |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
my $token_counter = 100; |
583
|
|
|
|
|
|
|
my $sid_str = 'S-1-2-3-4-5-6-1234'; |
584
|
|
|
|
|
|
|
|
585
|
0
|
|
|
0
|
|
0
|
sub _get_server_sid_string { return $sid_str } |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
sub _add_AD { |
588
|
0
|
|
|
0
|
|
0
|
my ( $server, $reqData, $reqMsg, $key, $entry, $data ) = @_; |
589
|
|
|
|
|
|
|
|
590
|
0
|
|
|
|
|
0
|
for my $attr ( @{ $reqData->{attributes} } ) { |
|
0
|
|
|
|
|
0
|
|
591
|
0
|
0
|
|
|
|
0
|
if ( $attr->{type} eq 'objectClass' ) { |
592
|
0
|
0
|
|
|
|
0
|
if ( grep { $_ eq 'group' } @{ $attr->{vals} } ) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# groups |
595
|
0
|
|
|
|
|
0
|
$token_counter++; |
596
|
0
|
|
|
|
|
0
|
( my $group_sid_str = _get_server_sid_string() ) |
597
|
|
|
|
|
|
|
=~ s/-1234$/-$token_counter/; |
598
|
0
|
0
|
|
|
|
0
|
if ( $ENV{LDAP_DEBUG} ) { |
599
|
0
|
|
|
|
|
0
|
carp "group_sid_str = $group_sid_str"; |
600
|
|
|
|
|
|
|
} |
601
|
0
|
|
|
|
|
0
|
$entry->add( 'primaryGroupToken' => $token_counter ); |
602
|
0
|
|
|
|
|
0
|
$entry->add( 'objectSID' => "$group_sid_str" ); |
603
|
0
|
|
|
|
|
0
|
$entry->add( 'distinguishedName' => $key ); |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
else { |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# users |
609
|
0
|
|
|
|
|
0
|
my $gid = $entry->get_value('primaryGroupID'); |
610
|
0
|
0
|
|
|
|
0
|
$gid = '1234' unless ( defined $gid ); |
611
|
0
|
|
|
|
|
0
|
( my $user_sid_str = _get_server_sid_string() ) |
612
|
|
|
|
|
|
|
=~ s/-1234$/-$gid/; |
613
|
|
|
|
|
|
|
|
614
|
0
|
|
|
|
|
0
|
my $user_sid = Net::LDAP::SID->new($user_sid_str); |
615
|
0
|
|
|
|
|
0
|
$entry->add( 'objectSID' => $user_sid->as_binary ); |
616
|
0
|
|
|
|
|
0
|
$entry->add( 'distinguishedName' => $key ); |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
0
|
|
|
|
|
0
|
_update_groups($data); |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
#dump $reqData; |
626
|
|
|
|
|
|
|
#dump $data; |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# AD stores group assignments in 'member' attribute |
631
|
|
|
|
|
|
|
# of each group. 'memberOf' is linked internally to that |
632
|
|
|
|
|
|
|
# attribute. We set 'memberOf' here if mimicing AD. |
633
|
|
|
|
|
|
|
sub _update_groups { |
634
|
0
|
|
|
0
|
|
0
|
my $data = shift; |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# all groups |
637
|
0
|
|
|
|
|
0
|
for my $key ( keys %$data ) { |
638
|
0
|
|
|
|
|
0
|
my $entry = $data->{$key}; |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
#warn "groups: update groups for $key"; |
641
|
0
|
0
|
|
|
|
0
|
if ( !$entry->get_value('sAMAccountName') ) { |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
#dump $entry; |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# group entry. |
646
|
|
|
|
|
|
|
# are the users listed in member |
647
|
|
|
|
|
|
|
# still assigned in their memberOf? |
648
|
0
|
|
|
|
|
0
|
my %users = map { $_ => 1 } $entry->get_value('member'); |
|
0
|
|
|
|
|
0
|
|
649
|
0
|
|
|
|
|
0
|
for my $dn ( keys %users ) { |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
#warn "User $dn is a member in $key"; |
652
|
0
|
|
|
|
|
0
|
my $user = $data->{$dn}; |
653
|
0
|
|
|
|
|
0
|
my %groups = map { $_ => 1 } $user->get_value('memberOf'); |
|
0
|
|
|
|
|
0
|
|
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
# if $user does not list $key (group) as a memberOf, |
656
|
|
|
|
|
|
|
# then add it. |
657
|
0
|
0
|
0
|
|
|
0
|
if ( !exists $groups{$key} && exists $users{$dn} ) { |
658
|
0
|
|
|
|
|
0
|
$groups{$key}++; |
659
|
0
|
|
|
|
|
0
|
$user->replace( memberOf => [ keys %groups ] ); |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# all users |
668
|
|
|
|
|
|
|
|
669
|
0
|
|
|
|
|
0
|
for my $key ( keys %$data ) { |
670
|
0
|
|
|
|
|
0
|
my $entry = $data->{$key}; |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
#warn "users: update groups for $key"; |
673
|
0
|
0
|
|
|
|
0
|
if ( $entry->get_value('sAMAccountName') ) { |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
#dump $entry; |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
# user entry |
678
|
|
|
|
|
|
|
# get its groups and add this user to each of them. |
679
|
0
|
|
|
|
|
0
|
my %groups = map { $_ => 1 } $entry->get_value('memberOf'); |
|
0
|
|
|
|
|
0
|
|
680
|
0
|
|
|
|
|
0
|
for my $dn ( keys %groups ) { |
681
|
0
|
|
|
|
|
0
|
my $group = $data->{$dn}; |
682
|
|
|
|
|
|
|
my %users |
683
|
0
|
|
|
|
|
0
|
= map { $_ => 1 } ( $group->get_value('member') ); |
|
0
|
|
|
|
|
0
|
|
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
# if group no longer lists this user as a member, |
686
|
|
|
|
|
|
|
# remove group from memberOf |
687
|
0
|
0
|
|
|
|
0
|
if ( !exists $users{$key} ) { |
688
|
0
|
|
|
|
|
0
|
delete $groups{$dn}; |
689
|
0
|
|
|
|
|
0
|
$entry->replace( memberOf => [ keys %groups ] ); |
690
|
|
|
|
|
|
|
} |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
sub _modify_AD { |
699
|
0
|
|
|
0
|
|
0
|
my ( $server, $reqData, $reqMsg, $data ) = @_; |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
#dump $data; |
702
|
0
|
|
|
|
|
0
|
_update_groups($data); |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
#Data::Dump::dump $data; |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
# override the default behaviour to support controls |
709
|
|
|
|
|
|
|
sub handle { |
710
|
50
|
|
|
50
|
|
79
|
my $self = shift; |
711
|
50
|
|
|
|
|
63
|
my $socket; |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
#warn "$Net::LDAP::Server::VERSION"; |
714
|
50
|
50
|
|
|
|
159
|
if ( $Net::LDAP::Server::VERSION ge '0.43' ) { |
715
|
50
|
|
|
|
|
126
|
$socket = $self->{in}; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
else { |
718
|
0
|
|
|
|
|
0
|
$socket = $self->{socket}; |
719
|
|
|
|
|
|
|
} |
720
|
|
|
|
|
|
|
|
721
|
50
|
|
|
|
|
243
|
asn_read( $socket, my $pdu ); |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
#print '-' x 80,"\n"; |
724
|
|
|
|
|
|
|
#print "Received:\n"; |
725
|
|
|
|
|
|
|
#Convert::ASN1::asn_dump(\*STDOUT,$pdu); |
726
|
50
|
|
|
|
|
2229
|
my $request = $LDAPRequest->decode($pdu); |
727
|
50
|
100
|
|
|
|
15868
|
my $mid = $request->{'messageID'} |
728
|
|
|
|
|
|
|
or return 1; |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
#print "messageID: $mid\n"; |
731
|
|
|
|
|
|
|
#use Data::Dumper; print Dumper($request); |
732
|
|
|
|
|
|
|
|
733
|
48
|
|
|
|
|
57
|
my $reqType; |
734
|
48
|
|
|
|
|
129
|
foreach my $type (@Net::LDAP::Server::reqTypes) { |
735
|
305
|
100
|
|
|
|
511
|
if ( defined $request->{$type} ) { |
736
|
48
|
|
|
|
|
67
|
$reqType = $type; |
737
|
48
|
|
|
|
|
68
|
last; |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
} |
740
|
48
|
100
|
|
|
|
278
|
my $respType = $Net::LDAP::Server::respTypes{$reqType} |
741
|
|
|
|
|
|
|
or |
742
|
|
|
|
|
|
|
return 1; # if no response type is present hangup the connection |
743
|
|
|
|
|
|
|
|
744
|
43
|
|
|
|
|
53
|
my $reqData = $request->{$reqType}; |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
# here we can do something with the request of type $reqType |
747
|
43
|
|
|
|
|
144
|
my $method = $Net::LDAP::Server::functions{$reqType}; |
748
|
43
|
|
|
|
|
55
|
my ( $result, $controls ); |
749
|
43
|
50
|
|
|
|
315
|
if ( $self->can($method) ) { |
750
|
43
|
100
|
|
|
|
93
|
if ( $method eq 'search' ) { |
751
|
17
|
|
|
|
|
31
|
my @entries; |
752
|
17
|
|
|
|
|
37
|
eval { |
753
|
17
|
|
|
|
|
68
|
( $result, @entries ) |
754
|
|
|
|
|
|
|
= $self->search( $reqData, $request ); |
755
|
17
|
100
|
|
|
|
79
|
if ( ref( $entries[0] ) eq 'ARRAY' ) { |
756
|
11
|
|
|
|
|
16
|
$controls = pop(@entries); |
757
|
11
|
|
|
|
|
13
|
@entries = @{ shift(@entries) }; |
|
11
|
|
|
|
|
25
|
|
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
#warn "got controls"; |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
}; |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
# rethrow |
764
|
17
|
50
|
|
|
|
138
|
if ($@) { |
765
|
0
|
|
|
|
|
0
|
croak $@; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
17
|
|
|
|
|
181
|
foreach my $entry (@entries) { |
769
|
38
|
|
|
|
|
37
|
my $data; |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
# default is to return a searchResEntry |
772
|
38
|
|
|
|
|
71
|
my $sResType = 'searchResEntry'; |
773
|
38
|
50
|
|
|
|
96
|
if ( ref $entry eq 'Net::LDAP::Entry' ) { |
|
|
0
|
|
|
|
|
|
774
|
38
|
|
|
|
|
54
|
$data = $entry->{'asn'}; |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
elsif ( ref $entry eq 'Net::LDAP::Reference' ) { |
777
|
0
|
|
|
|
|
0
|
$data = $entry->{'asn'}; |
778
|
0
|
|
|
|
|
0
|
$sResType = 'searchResRef'; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
else { |
781
|
0
|
|
|
|
|
0
|
$data = $entry; |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
38
|
|
|
|
|
39
|
my $response; |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
# is the full message specified? |
787
|
38
|
50
|
|
|
|
73
|
if ( defined $data->{'protocolOp'} ) { |
788
|
0
|
|
|
|
|
0
|
$response = $data; |
789
|
0
|
|
|
|
|
0
|
$response->{'messageID'} = $mid; |
790
|
|
|
|
|
|
|
} |
791
|
|
|
|
|
|
|
else { |
792
|
38
|
|
|
|
|
126
|
$response = { |
793
|
|
|
|
|
|
|
'messageID' => $mid, |
794
|
|
|
|
|
|
|
'protocolOp' => { $sResType => $data }, |
795
|
|
|
|
|
|
|
}; |
796
|
|
|
|
|
|
|
} |
797
|
38
|
|
|
|
|
119
|
my $pdu = $LDAPResponse->encode($response); |
798
|
38
|
50
|
|
|
|
10607
|
if ($pdu) { |
799
|
38
|
|
|
|
|
41
|
print {$socket} $pdu; |
|
38
|
|
|
|
|
674
|
|
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
else { |
802
|
0
|
|
|
|
|
0
|
$result = undef; |
803
|
0
|
|
|
|
|
0
|
last; |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
else { |
808
|
26
|
|
|
|
|
32
|
eval { $result = $self->$method( $reqData, $request ) }; |
|
26
|
|
|
|
|
91
|
|
809
|
|
|
|
|
|
|
} |
810
|
43
|
50
|
|
|
|
167
|
$result = Net::LDAP::Server::_operations_error() unless $result; |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
else { |
813
|
0
|
|
|
|
|
0
|
$result = { |
814
|
|
|
|
|
|
|
'matchedDN' => '', |
815
|
|
|
|
|
|
|
'errorMessage' => sprintf( |
816
|
|
|
|
|
|
|
"%s operation is not supported by %s", |
817
|
|
|
|
|
|
|
$method, ref $self |
818
|
|
|
|
|
|
|
), |
819
|
|
|
|
|
|
|
'resultCode' => LDAP_UNWILLING_TO_PERFORM |
820
|
|
|
|
|
|
|
}; |
821
|
|
|
|
|
|
|
} |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
# and now send the result to the client |
824
|
43
|
|
|
|
|
43
|
print {$socket} _encode_result( $mid, $respType, $result, $controls ); |
|
43
|
|
|
|
|
109
|
|
825
|
|
|
|
|
|
|
|
826
|
43
|
|
|
|
|
264
|
return 0; |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub _encode_result { |
830
|
43
|
|
|
43
|
|
75
|
my ( $mid, $respType, $result, $controls ) = @_; |
831
|
|
|
|
|
|
|
|
832
|
43
|
|
|
|
|
160
|
my $response = { |
833
|
|
|
|
|
|
|
'messageID' => $mid, |
834
|
|
|
|
|
|
|
'protocolOp' => { $respType => $result }, |
835
|
|
|
|
|
|
|
}; |
836
|
43
|
50
|
|
|
|
101
|
if ( defined $controls ) { |
837
|
0
|
|
|
|
|
0
|
$response->{'controls'} = $controls; |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
#warn "response: " . Data::Dump::dump($response) . "\n"; |
841
|
|
|
|
|
|
|
|
842
|
43
|
|
|
|
|
145
|
my $pdu = $LDAPResponse->encode($response); |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
# if response encoding failed return the error |
845
|
43
|
50
|
|
|
|
6882
|
if ( !$pdu ) { |
846
|
0
|
|
|
|
|
0
|
$response->{'protocolOp'}->{$respType} |
847
|
|
|
|
|
|
|
= Net::LDAP::Server::_operations_error(); |
848
|
0
|
|
|
|
|
0
|
delete $response->{'controls'}; # just in case |
849
|
0
|
|
|
|
|
0
|
$pdu = $LDAPResponse->encode($response); |
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
|
852
|
43
|
|
|
|
|
987
|
return $pdu; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
} # end MyLDAPServer |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
=head2 new( I, I ) |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
Create a new server. Basically this just fork()s a child process |
860
|
|
|
|
|
|
|
listing on I and handling requests using Net::LDAP::Server. |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
I defaults to 10636. |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
I may be an IO::Socket object listening to a local port. |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
I may be: |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
=over |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
=item data |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
I is optional data to return from the Net::LDAP search() function. |
873
|
|
|
|
|
|
|
Typically it would be an array ref of Net::LDAP::Entry objects. |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=item auto_schema |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
A true value means the add(), modify() and delete() methods will |
878
|
|
|
|
|
|
|
store internal in-memory data based on DN values, so that search() |
879
|
|
|
|
|
|
|
will mimic working on a real LDAP schema. |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
=item active_directory |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
Work in Active Directory mode. This means that entries are automatically |
884
|
|
|
|
|
|
|
assigned a objectSID, and some effort is made to mimic the member/memberOf |
885
|
|
|
|
|
|
|
linking between AD Users and Groups. |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
=back |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
new() will croak() if there was a problem fork()ing a new server. |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
Returns a Net::LDAP::Server::Test object, which is just a |
892
|
|
|
|
|
|
|
blessed reference to the PID of the forked server. |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
=cut |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
my %PORTS; # inside-out tracking of port-per-server |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
# this snippet matches what Net::LDAP does: |
899
|
|
|
|
|
|
|
# check for IPv6 support: prefer IO::Socket::IP 0.20+ over IO::Socket::INET6 |
900
|
15
|
|
|
|
|
29
|
use constant CAN_IPV6 => do { |
901
|
15
|
|
|
|
|
44
|
local $SIG{__DIE__}; |
902
|
|
|
|
|
|
|
|
903
|
15
|
50
|
|
|
|
17
|
eval { require IO::Socket::INET6; } |
|
15
|
|
|
|
|
7034
|
|
904
|
|
|
|
|
|
|
? 'IO::Socket::INET6' |
905
|
|
|
|
|
|
|
: ''; |
906
|
15
|
|
|
15
|
|
93
|
}; |
|
15
|
|
|
|
|
17
|
|
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
sub new { |
909
|
16
|
|
|
16
|
1
|
4611
|
my $class = shift; |
910
|
16
|
|
50
|
|
|
65
|
my $port = shift || 10636; |
911
|
16
|
|
|
|
|
49
|
my %arg = @_; |
912
|
|
|
|
|
|
|
|
913
|
16
|
50
|
66
|
|
|
68
|
if ( $arg{data} and $arg{auto_schema} ) { |
914
|
0
|
|
|
|
|
0
|
croak |
915
|
|
|
|
|
|
|
"cannot handle both 'data' and 'auto_schema' features. Pick one."; |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
16
|
|
|
|
|
374
|
pipe( my $r_fh, my $w_fh ); |
919
|
|
|
|
|
|
|
|
920
|
16
|
|
|
|
|
14775
|
my $pid = fork(); |
921
|
|
|
|
|
|
|
|
922
|
16
|
50
|
|
|
|
795
|
if ( !defined $pid ) { |
|
|
100
|
|
|
|
|
|
923
|
0
|
|
|
|
|
0
|
croak "can't fork a LDAP test server: $!"; |
924
|
|
|
|
|
|
|
} |
925
|
|
|
|
|
|
|
elsif ( $pid == 0 ) { |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
warn "Creating new LDAP server on port " |
928
|
|
|
|
|
|
|
. ( ref $port ? $port->sockport : $port ) |
929
|
|
|
|
|
|
|
. " ... \n" |
930
|
7
|
0
|
|
|
|
322
|
if $ENV{LDAP_DEBUG}; |
|
|
50
|
|
|
|
|
|
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
# the child (server) |
933
|
7
|
|
|
|
|
214
|
my $class = _io_socket_class(); |
934
|
7
|
50
|
|
|
|
565
|
my $sock = ref $port ? $port : $class->new( |
|
|
50
|
|
|
|
|
|
935
|
|
|
|
|
|
|
Listen => 5, |
936
|
|
|
|
|
|
|
Proto => 'tcp', |
937
|
|
|
|
|
|
|
Reuse => 1, |
938
|
|
|
|
|
|
|
LocalPort => $port |
939
|
|
|
|
|
|
|
) or die "Unable to listen on port $port: $! [$@]"; |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
# tickle the pipe to show we've opened ok |
942
|
7
|
|
|
|
|
9883
|
syswrite $w_fh, "Ready\n"; |
943
|
7
|
|
|
|
|
184
|
undef $w_fh; |
944
|
|
|
|
|
|
|
|
945
|
7
|
|
|
|
|
237
|
my $sel = IO::Select->new($sock); |
946
|
7
|
|
|
|
|
808
|
my %Handlers; |
947
|
7
|
|
|
|
|
63
|
while ( my @ready = $sel->can_read ) { |
948
|
57
|
|
|
|
|
581618
|
foreach my $fh (@ready) { |
949
|
57
|
100
|
|
|
|
225
|
if ( $fh == $sock ) { |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
# let's create a new socket |
952
|
7
|
|
|
|
|
58
|
my $psock = $sock->accept; |
953
|
7
|
|
|
|
|
1261
|
$sel->add($psock); |
954
|
7
|
|
|
|
|
497
|
$Handlers{*$psock} = MyLDAPServer->new( $psock, %arg ); |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
#warn "new socket created"; |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
else { |
959
|
50
|
|
|
|
|
434
|
my $result = $Handlers{*$fh}->handle; |
960
|
50
|
100
|
|
|
|
319
|
if ($result) { |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
# we have finished with the socket |
963
|
7
|
|
|
|
|
70
|
$sel->remove($fh); |
964
|
7
|
|
|
|
|
418
|
$fh->close; |
965
|
7
|
|
|
|
|
957
|
delete $Handlers{*$fh}; |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
# if there are no open connections, |
968
|
|
|
|
|
|
|
# exit the child process. |
969
|
7
|
50
|
|
|
|
49
|
if ( !keys %Handlers ) { |
970
|
|
|
|
|
|
|
warn " ... shutting down server\n" |
971
|
7
|
50
|
|
|
|
38
|
if $ENV{LDAP_DEBUG}; |
972
|
7
|
|
|
|
|
1228
|
exit(0); |
973
|
|
|
|
|
|
|
} |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
} |
977
|
|
|
|
|
|
|
} |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
# if we get here, we had some kinda problem. |
980
|
0
|
|
|
|
|
0
|
croak "reached the end of while() loop prematurely"; |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
} |
983
|
|
|
|
|
|
|
else { |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
# this is the child |
986
|
9
|
50
|
|
|
|
339
|
warn "child pid=$pid" if $ENV{LDAP_DEBUG}; |
987
|
|
|
|
|
|
|
|
988
|
9
|
50
|
|
|
|
14362
|
return unless <$r_fh> =~ /Ready/; # newline varies |
989
|
9
|
|
|
|
|
120
|
close($r_fh); |
990
|
9
|
|
|
|
|
198
|
my $self = bless( \$pid, $class ); |
991
|
9
|
|
|
|
|
265
|
$PORTS{"$self"} = $port; |
992
|
9
|
|
|
|
|
644
|
return $self; |
993
|
|
|
|
|
|
|
} |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
} |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
=head2 stop |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
Calls waitpid() on the server's associated child process. |
1000
|
|
|
|
|
|
|
You may find it helpful to call this method explicitly, |
1001
|
|
|
|
|
|
|
especially if you are creating multiple |
1002
|
|
|
|
|
|
|
servers in the same test. Otherwise, this method is typically not |
1003
|
|
|
|
|
|
|
needed and may even cause your tests to hang indefinitely if |
1004
|
|
|
|
|
|
|
they die prematurely. YMMV. |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
To prevent waitpid() from blocking and hanging your test server, |
1007
|
|
|
|
|
|
|
it is wrapped in an alarm() call, which will wait 2 seconds |
1008
|
|
|
|
|
|
|
and then call kill() on the reluctant pid. You have been warned. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=cut |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
sub stop { |
1013
|
3
|
|
|
3
|
1
|
142722
|
my $server = shift; |
1014
|
3
|
|
|
|
|
16
|
my $pid = $$server; |
1015
|
3
|
50
|
|
|
|
16
|
warn "\$pid = $pid" if $ENV{LDAP_DEBUG}; |
1016
|
3
|
|
|
|
|
10
|
eval { |
1017
|
|
|
|
|
|
|
local $SIG{ALRM} |
1018
|
3
|
|
|
1
|
|
98
|
= sub { die "waitpid($pid, 0) took too long\n" }; # NB: \n required |
|
1
|
|
|
|
|
34
|
|
1019
|
3
|
|
|
|
|
16
|
alarm 2; |
1020
|
3
|
|
|
|
|
3222471
|
my $ret = waitpid( $pid, 0 ); |
1021
|
2
|
50
|
|
|
|
34
|
warn "waitpid returned $ret" if $ENV{LDAP_DEBUG}; |
1022
|
2
|
|
|
|
|
78
|
alarm 0; |
1023
|
|
|
|
|
|
|
}; |
1024
|
3
|
100
|
|
|
|
47
|
if ($@) { |
1025
|
1
|
|
|
|
|
131
|
warn "$@"; |
1026
|
1
|
|
|
|
|
32
|
my $cnt = kill( 9, $pid ); |
1027
|
1
|
|
|
|
|
577
|
warn "kill(9,$pid) returned $cnt\n"; |
1028
|
|
|
|
|
|
|
} |
1029
|
|
|
|
|
|
|
else { |
1030
|
2
|
50
|
|
|
|
12
|
warn "waitpid($pid, 0) worked" if $ENV{LDAP_DEBUG}; |
1031
|
|
|
|
|
|
|
} |
1032
|
3
|
|
|
|
|
14
|
my $tries = 0; |
1033
|
3
|
|
|
|
|
26
|
while ( $server->port_is_open() ) { |
1034
|
0
|
|
|
|
|
0
|
warn "Waiting for port to close...\n"; |
1035
|
0
|
|
|
|
|
0
|
sleep(1); |
1036
|
0
|
0
|
|
|
|
0
|
if ( $tries++ > 10 ) { |
1037
|
0
|
|
|
|
|
0
|
warn "Failed to determine that port closed. Giving up.\n"; |
1038
|
0
|
|
|
|
|
0
|
last; |
1039
|
|
|
|
|
|
|
} |
1040
|
|
|
|
|
|
|
} |
1041
|
3
|
|
|
|
|
2844
|
return $pid; |
1042
|
|
|
|
|
|
|
} |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=head2 port_is_open |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
Returns an IO::Socket (or subclass) for the current server port. |
1047
|
|
|
|
|
|
|
If the port is already in use, this is a false value. |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
=cut |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
sub port_is_open { |
1052
|
3
|
|
|
3
|
1
|
5
|
my $self = shift; |
1053
|
3
|
|
33
|
|
|
48
|
my $port = shift || $PORTS{"$self"}; |
1054
|
|
|
|
|
|
|
|
1055
|
3
|
|
|
|
|
16
|
return _io_socket_class()->new( |
1056
|
|
|
|
|
|
|
PeerAddr => 'localhost', |
1057
|
|
|
|
|
|
|
PeerPort => $port, |
1058
|
|
|
|
|
|
|
Proto => 'tcp', |
1059
|
|
|
|
|
|
|
Type => SOCK_STREAM, |
1060
|
|
|
|
|
|
|
); |
1061
|
|
|
|
|
|
|
} |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
sub _io_socket_class { |
1064
|
10
|
|
|
10
|
|
247
|
return CAN_IPV6 ? CAN_IPV6 : 'IO::Socket::INET'; |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
=head1 AUTHOR |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
Peter Karman, C<< >> |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
=head1 BUGS |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
1074
|
|
|
|
|
|
|
C, or through the web interface at |
1075
|
|
|
|
|
|
|
L. |
1076
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
1077
|
|
|
|
|
|
|
your bug as I make changes. |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
=head1 SUPPORT |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
perldoc Net::LDAP::Server::Test |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
You can also look for information at: |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
=over 4 |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
L |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
=item * CPAN Ratings |
1094
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
L |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
L |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
=item * Search CPAN |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
L |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=back |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
The Minnesota Supercomputing Institute C<< http://www.msi.umn.edu/ >> |
1110
|
|
|
|
|
|
|
sponsored the development of this software. |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
Copyright 2007 by the Regents of the University of Minnesota. |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
1117
|
|
|
|
|
|
|
under the same terms as Perl itself. |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
=head1 SEE ALSO |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
Net::LDAP::Server |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
=cut |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
1; |