/[virtual-ldap]/lib/LDAP/Virtual.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /lib/LDAP/Virtual.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6 - (show annotations)
Sat Mar 14 18:21:29 2009 UTC (15 years, 1 month ago) by dpavlin
File size: 5623 byte(s)
save search queries to YAML files

1 package LDAP::Virtual;
2
3 use strict;
4 use warnings;
5
6 use Net::LDAP::Constant qw(
7 LDAP_SUCCESS
8 LDAP_STRONG_AUTH_NOT_SUPPORTED
9 LDAP_UNAVAILABLE
10 LDAP_OPERATIONS_ERROR
11 );
12 use Net::LDAP::Server;
13 use Net::LDAP::Filter;
14 use base qw(Net::LDAP::Server);
15 use fields qw(upstream);
16
17 use Net::LDAP;
18
19 use URI::Escape; # uri_escape
20 use IO::Socket::INET;
21 use IO::Select;
22
23 use YAML qw/DumpFile/;
24
25 use Data::Dump qw/dump/;
26
27 =head1 NAME
28
29 LDAP::Virtual
30
31 =cut
32
33 =head1 DESCRIPTION
34
35 Provide LDAP server functionality somewhat similar to C<slapo-rwm>
36
37 =head1 METHODS
38
39 =head2 run
40
41 my $pid = LDAP::Virtual->run({ port => 1389, fork => 0 });
42
43 =cut
44
45 our $pids;
46 our $cache;
47
48 sub cache {
49 return $cache;
50 }
51
52 sub run {
53 my $self = shift;
54
55 my $args = shift;
56 # default LDAP port
57 my $port = $args->{port} ||= 1389;
58
59 if ( $args->{fork} ) {
60 defined(my $pid = fork()) or die "Can't fork: $!";
61 if ( $pid ) {
62 $pids->{ $port } = $pid;
63 warn "# pids = ",dump( $pids );
64 sleep 1;
65 return $pid;
66 }
67 }
68
69 my $sock = IO::Socket::INET->new(
70 Listen => 5,
71 Proto => 'tcp',
72 Reuse => 1,
73 LocalPort => $port,
74 ) or die "can't listen on port $port: $!\n";
75
76 warn "LDAP server listening on port $port\n";
77
78 my $sel = IO::Select->new($sock) or die "can't select socket: $!\n";
79 my %Handlers;
80 while (my @ready = $sel->can_read) {
81 foreach my $fh (@ready) {
82 if ($fh == $sock) {
83 # let's create a new socket
84 my $psock = $sock->accept;
85 $sel->add($psock);
86 $Handlers{*$psock} = LDAP::Virtual->new($psock);
87 } else {
88 my $result = $Handlers{*$fh}->handle;
89 if ($result) {
90 # we have finished with the socket
91 $sel->remove($fh);
92 $fh->close;
93 delete $Handlers{*$fh};
94 }
95 }
96 }
97 }
98 }
99
100 =head2 stop
101
102 my $stopped_pids = LDAP::Virtual->stop;
103
104 =cut
105
106 sub stop {
107 warn "## stop pids = ",dump( $pids );
108 return unless $pids;
109 my $stopped = 0;
110 foreach my $port ( keys %$pids ) {
111 my $pid = delete($pids->{$port}) or die "no pid?";
112 warn "# Shutdown LDAP server at port $port pid $pid\n";
113 kill(9,$pid) or die "can't kill $pid: $!";
114 waitpid($pid,0) or die "waitpid $pid: $!";
115 $stopped++;
116 }
117 warn "## stopped $stopped processes\n";
118 return $stopped;
119 }
120
121 use constant RESULT_OK => {
122 'matchedDN' => '',
123 'errorMessage' => '',
124 'resultCode' => LDAP_SUCCESS
125 };
126
127 # constructor
128 sub new {
129 my ($class, $sock) = @_;
130 my $self = $class->SUPER::new($sock);
131 printf "Accepted connection from: %s\n", $sock->peerhost();
132 return $self;
133 }
134
135 # the bind operation
136 sub bind {
137 my ($self,$req) = @_;
138
139 warn "## bind req = ",dump($req);
140
141 defined($req->{authentication}->{simple}) or return {
142 matchedDN => '',
143 errorMessage => '',
144 resultCode => LDAP_STRONG_AUTH_NOT_SUPPORTED,
145 };
146
147 $self->{upstream} ||= Net::LDAP->new( 'ldaps://ldap.ffzg.hr/' ) or return {
148 matchedDN => '',
149 errorMessage => $@,
150 resultCode => LDAP_UNAVAILABLE,
151 };
152
153 warn "## upstream = ",dump( $self->{upstream} );
154 warn "upstream not Net::LDAP but ",ref($self->{upstream}) unless ref($self->{upstream}) eq 'Net::LDAP';
155
156 my $msg;
157
158 # FIXME we would need to unbind because VLDAP binds us automatically, but that doesn't really work
159 #$msg = $self->{upstream}->unbind;
160 #warn "# unbind msg = ",dump( $msg );
161
162 my $bind;
163 $bind->{dn} = $req->{name} if $req->{name};
164 $bind->{password} = $req->{authentication}->{simple} if $req->{authentication}->{simple};
165 warn "# bind ",dump( $bind );
166 $msg = $self->{upstream}->bind( %$bind );
167
168 #warn "# bind msg = ",dump( $msg );
169 if ( $msg->code != LDAP_SUCCESS ) {
170 warn "ERROR: ", $msg->code, ": ", $msg->server_error, "\n";
171 return {
172 matchedDN => '',
173 errorMessage => $msg->server_error,
174 resultCode => $msg->code,
175 };
176 }
177
178 return RESULT_OK;
179 }
180
181 # the search operation
182 sub search {
183 my ($self,$req) = @_;
184
185 warn "## search req = ",dump( $req );
186
187 if ( ! $self->{upstream} ) {
188 warn "search without bind";
189 return {
190 matchedDN => '',
191 errorMessage => 'dude, bind first',
192 resultCode => LDAP_OPERATIONS_ERROR,
193 };
194 }
195
196 my $filter;
197 if (defined $req->{filter}) {
198 # $req->{filter} is a ASN1-decoded tree; luckily, this is exactly the
199 # internal representation Net::LDAP::Filter uses. [FIXME] Eventually
200 # Net::LDAP::Filter should provide a corresponding constructor.
201 bless($req->{filter}, 'Net::LDAP::Filter');
202 $filter = $req->{filter}->as_string;
203 # $filter = '(&' . $req->{filter}->as_string
204 # . '(objectClass=hrEduPerson)(host=aai.irb.hr))';
205 }
206
207 warn "search upstream for $filter\n";
208
209 my $search = $self->{upstream}->search(
210 base => $req->{baseObject},
211 scope => $req->{scope},
212 deref => $req->{derefAliases},
213 sizelimit => $req->{sizeLimit},
214 timelimit => $req->{timeLimit},
215 typesonly => $req->{typesOnly},
216 filter => $filter,
217 attrs => $req->{attributes},
218 raw => qr/.*/,
219 );
220
221 # warn "# search = ",dump( $search );
222
223 if ( $search->code != LDAP_SUCCESS ) {
224 warn "ERROR: ",$search->code,": ",$search->server_error;
225 return {
226 matchedDN => '',
227 errorMessage => $search->server_error,
228 resultCode => $search->code,
229 };
230 };
231
232 my @entries = $search->entries;
233 warn "## got ", $search->count, " entries for $filter\n";
234 foreach my $entry (@entries) {
235 # $entry->changetype('add'); # Don't record changes.
236 # foreach my $attr ($entry->attributes) {
237 # if ($attr =~ /;lang-en$/) {
238 # $entry->delete($attr);
239 # }
240 # }
241 }
242
243 warn "## entries = ",dump( @entries );
244
245 # $self->cache->write_cache( \@entries, uri_escape( $filter ));
246
247 my $path = uri_escape( $filter );
248 DumpFile( "var/${path}.yml", \@entries );
249 warn "# created $path ", -s $path, " bytes";
250
251 return RESULT_OK, @entries;
252 }
253
254 # the rest of the operations will return an "unwilling to perform"
255
256 1;

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.26