6 use Net::LDAP::Constant qw(
8 LDAP_STRONG_AUTH_NOT_SUPPORTED
12 use Net::LDAP::Server;
13 use Net::LDAP::Filter;
14 use base qw(Net::LDAP::Server);
15 use fields qw(upstream);
17 use URI::Escape; # uri_escape
21 use Data::Dump qw/dump/;
31 Provide LDAP server functionality for L<A3C> somewhat similar to C<slapo-rwm>
37 my $pid = A3C::LDAP::Server->run({ port => 1389, fork => 0 });
45 return $cache if $cache;
46 $cache = new A3C::Cache->new({ instance => '', dir => 'ldap' });
54 my $port = $args->{port} ||= 1389;
56 if ( $args->{fork} ) {
57 defined(my $pid = fork()) or die "Can't fork: $!";
59 $pids->{ $port } = $pid;
60 warn "# pids = ",dump( $pids );
66 my $sock = IO::Socket::INET->new(
71 ) or die "can't listen on port $port: $!\n";
73 warn "LDAP server listening on port $port\n";
75 my $sel = IO::Select->new($sock) or die "can't select socket: $!\n";
77 while (my @ready = $sel->can_read) {
78 foreach my $fh (@ready) {
80 # let's create a new socket
81 my $psock = $sock->accept;
83 $Handlers{*$psock} = A3C::LDAP::Server->new($psock);
85 my $result = $Handlers{*$fh}->handle;
87 # we have finished with the socket
90 delete $Handlers{*$fh};
99 my $stopped_pids = A3C::LDAP::Server->stop;
104 warn "## stop pids = ",dump( $pids );
107 foreach my $port ( keys %$pids ) {
108 my $pid = delete($pids->{$port}) or die "no pid?";
109 warn "# Shutdown LDAP server at port $port pid $pid\n";
110 kill(9,$pid) or die "can't kill $pid: $!";
111 waitpid($pid,0) or die "waitpid $pid: $!";
114 warn "## stopped $stopped processes\n";
118 use constant RESULT_OK => {
120 'errorMessage' => '',
121 'resultCode' => LDAP_SUCCESS
126 my ($class, $sock) = @_;
127 my $self = $class->SUPER::new($sock);
128 printf "Accepted connection from: %s\n", $sock->peerhost();
134 my ($self,$req) = @_;
136 warn "## bind req = ",dump($req);
138 defined($req->{authentication}->{simple}) or return {
141 resultCode => LDAP_STRONG_AUTH_NOT_SUPPORTED,
144 $self->{upstream} ||= A3C::LDAP->new->ldap or return {
147 resultCode => LDAP_UNAVAILABLE,
150 # warn "## upstream = ",dump( $self->{upstream} );
151 # warn "upstream not Net::LDAP but ",ref($self->{upstream}) unless ref($self->{upstream}) eq 'Net::LDAP';
155 # FIXME we would need to unbind because A3C::LDAP binds us automatically, but that doesn't really work
156 #$msg = $self->{upstream}->unbind;
157 #warn "# unbind msg = ",dump( $msg );
159 $msg = $self->{upstream}->bind(
161 password => $req->{authentication}->{simple}
164 #warn "# bind msg = ",dump( $msg );
165 if ( $msg->code != LDAP_SUCCESS ) {
166 warn "ERROR: ", $msg->code, ": ", $msg->server_error, "\n";
169 errorMessage => $msg->server_error,
170 resultCode => $msg->code,
177 # the search operation
179 my ($self,$req) = @_;
181 warn "## search req = ",dump( $req );
183 if ( ! $self->{upstream} ) {
184 warn "search without bind";
187 errorMessage => 'dude, bind first',
188 resultCode => LDAP_OPERATIONS_ERROR,
193 if (defined $req->{filter}) {
194 # $req->{filter} is a ASN1-decoded tree; luckily, this is exactly the
195 # internal representation Net::LDAP::Filter uses. [FIXME] Eventually
196 # Net::LDAP::Filter should provide a corresponding constructor.
197 bless($req->{filter}, 'Net::LDAP::Filter');
198 $filter = $req->{filter}->as_string;
199 # $filter = '(&' . $req->{filter}->as_string
200 # . '(objectClass=hrEduPerson)(host=aai.irb.hr))';
203 warn "search upstream for $filter\n";
205 my $search = $self->{upstream}->search(
206 base => $req->{baseObject},
207 scope => $req->{scope},
208 deref => $req->{derefAliases},
209 sizelimit => $req->{sizeLimit},
210 timelimit => $req->{timeLimit},
211 typesonly => $req->{typesOnly},
213 attrs => $req->{attributes},
217 # warn "# search = ",dump( $search );
219 if ( $search->code != LDAP_SUCCESS ) {
220 warn "ERROR: ",$search->code,": ",$search->server_error;
223 errorMessage => $search->server_error,
224 resultCode => $search->code,
228 my @entries = $search->entries;
229 warn "## got ", $search->count, " entries for $filter\n";
230 foreach my $entry (@entries) {
231 # $entry->changetype('add'); # Don't record changes.
232 # foreach my $attr ($entry->attributes) {
233 # if ($attr =~ /;lang-en$/) {
234 # $entry->delete($attr);
239 warn "## entries = ",dump( @entries );
241 $self->cache->write_cache( \@entries, uri_escape( $filter ));
243 return RESULT_OK, @entries;
246 # the rest of the operations will return an "unwilling to perform"