source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap.pm @ 2136

Last change on this file since 2136 was 2051, checked in by nanardon, 7 years ago

Add tls support

File size: 6.3 KB
Line 
1package LATMOS::Accounts::Bases::Ldap;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use base qw(LATMOS::Accounts::Bases);
8use Net::LDAP;
9use Net::LDAP::Entry;
10use Net::LDAP::Control::Paged;
11use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
12use Net::LDAP::Util     qw( escape_filter_value );
13use Unicode::Map8;
14use LATMOS::Accounts::Log;
15
16our $VERSION = (q$Rev: 653 $ =~ /^Rev: (\d+) /)[0];
17
18=head1 NAME
19
20LATMOS::Ldap - Perl extension for blah blah blah
21
22=head1 SYNOPSIS
23
24  use LATMOS::Ldap;
25  blah blah blah
26
27=head1 DESCRIPTION
28
29Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
30author of the extension was negligent enough to leave the stub
31unedited.
32
33Blah blah blah.
34
35=head1 FUNCTIONS
36
37=cut
38
39=head2 new(%config)
40
41Create a new LATMOS::Ldap object for windows AD $domain.
42
43config:
44
45=over 4
46
47=item domain
48
49The Active directory domain
50
51=item server (optional)
52
53If set, try to connect to this server, if not set, a dns query is performed
54to find AD server, first responding is used.
55
56=item ssl
57
58If set, try to connect using ssl
59
60=item OBJECT_container
61
62The sub path where to find object type of OBJECT and where they should
63be created
64
65=back
66
67=cut
68
69sub new {
70    my ($class, %config) = @_;
71   
72    my $self = {
73        _top_dn => $config{topdn},
74    };
75
76    bless($self, $class);
77}
78
79=head2 object_base_dn ($otype)
80
81Return the dn of obecjt containers
82
83=cut
84
85sub object_base_dn {
86    my ($self, $otype) = @_;
87    return join(',',
88        ($self->config($otype . '_container') || 'cn=Users'),
89        $self->top_dn,
90    );
91}
92
93=head2 sambaSID($id)
94
95Return the base samba SID set in the config or a default one if none is set.
96
97If C<$id> is given return the full SID suitable for an object.
98
99=cut
100
101sub sambaSID {
102    my ($self, $id) = @_;
103
104    my $ssid = $self->config('sambaSID') || 'S-2016-01-07';
105    if (defined($id)) {
106        $ssid .= '-' . $id;
107    }
108    return $ssid;
109}
110
111sub load {
112    my ($self) = @_;
113
114    # If already loaded, just say ok !
115    $self->{_ldap} and return 1;
116
117    my $ldapurl = $self->_ldap_url($self->config('server'));
118    my $ldap = Net::LDAP->new($ldapurl, async => 0);
119       
120
121    if($ldap) {
122        $self->log(LA_DEBUG, "Connected to ldap server %s", $ldapurl);
123    } else {
124        $self->log(LA_ERR, "Cannot connect to %s", $ldapurl);
125        return; # cannot connect to any ldap :\
126    };
127
128    if ($self->config('tls')) {
129        $self->log(LA_DEBUG, "Running start_tls()");
130        $ldap->start_tls(
131            verify => 'none',
132        );
133    }
134
135    my $msg = $ldap->bind($self->config('login'), password => $self->config('password'));
136    $msg->code and do {
137        $self->log(LA_ERR, "Cannot bind ldap: %s", $msg->error);
138        return;
139    };
140
141    $self->{_ldap} = $ldap;
142    return 1;
143}
144
145sub _ldap_url {
146    my ($self, $host, $port) = @_;
147
148    sprintf(
149        '%s://%s%s/',
150        $self->config('ssl') ? 'ldaps' : 'ldap',
151        $host,
152        $port ? ":$port" : '',
153    )
154}
155
156=head2 ldap
157
158Return the L<Net::LDAP> handle
159
160=cut
161
162sub ldap {
163    return $_[0]->{_ldap};
164}
165
166=head2 top_dn
167
168Return the TOP DN of the AD zone.
169
170=cut
171
172sub top_dn {
173    return $_[0]->{_top_dn}
174}
175
176# _unlimited_search
177
178# By default, ldap servers limit results to avoid deni of services.
179# LDAP protocol provide a paging feature to fetch all results.
180
181#This function works like Net::LDAP::search functions, but return nothing,
182#use "callback" option to get entries.
183
184sub _unlimited_search {
185    my $self = shift;
186    my @args = @_;
187
188    my ($page, $cookie) = (Net::LDAP::Control::Paged->new( size => 100 ));
189
190    while (1) {
191        my $search = $self->ldap->search(
192            @args,
193            control => [ $page ],
194        );
195        if ($search->code) {
196            return $search;
197        }
198
199        ### After foreach loops ends, client checks LDAP server reponse of how many search results total.
200        ### This is a control to end the infinite while loop if there are no search results to go through
201        ### If there are search results, this control will always return the total number of results. It is
202        ### never decremented
203        my ($resp) = $search->control( LDAP_CONTROL_PAGED ) or last;
204
205
206        ### Obtaining the cookie from the search result. When no more results, cookie will be NULL
207        ### and infinite while loop will terminate.
208        $cookie = $resp->cookie or last;
209
210        ### Sets cookie so server knows the next search result to send
211        $page->cookie($cookie);
212    }
213    ### This is a control to check for abnormal exit of the while loop.
214    ### If this occurs ### we need to tell the LDAP server that remaining
215    ### search results are no longer needed ### by sending a search request with a page size of 0
216    if ($cookie)    {
217        $page->cookie($cookie);
218        $page->size(0);
219        $self->ldap->search(@args, control => [ $page ]);
220    }
221    return 1;
222}
223
224# _get_object_from_dn($dn)
225#
226# Return Net::LDAP::Entry for $dn
227
228sub _get_object_from_dn {
229    my ($self, $dn) = @_;
230
231    my $mesg = $self->ldap->search(
232        base => $dn,
233        scope => 'base',
234        filter => '(objectClass=*)',
235    );
236    if ($mesg->code) {
237        $self->log(LA_ERR, "Cannot get object %s: %s", $dn, $mesg->error);
238        return;
239    }
240
241    return ($mesg->entries)[0];
242}
243
244sub authenticate_user {
245    my ($self, $username, $passwd) = @_;
246    $username or return;
247
248    # Basically, connect and if bind succeed, user is authenticated !
249    my $ldapurl = $self->ldap->uri;
250    my $ldap = Net::LDAP->new($ldapurl);
251
252    my $obj = $self->get_object('user', $username) or do {
253        $self->log(LA_ERR, "Cannot find user %s", $username);
254        return;
255    };
256
257    if ($ldap) {
258        $self->log(LA_DEBUG, "Connected to ldap server %s", $ldapurl);
259    } else {
260        $self->log(LA_ERR, "Cannot connect to %s", $ldapurl);
261        return; # cannot connect to any ldap :\
262    };
263
264
265    my $msg = $ldap->bind($obj->get_attributes('dn'), password => $passwd);
266    if ($msg->code) {
267        $self->log(LA_ERR, "Cannot bind ldap: %s", $msg->error);
268        return;
269    } else {
270        return 1; # Success !! \o/
271    }
272}
273
2741;
275
276__END__
277
278=head1 SEE ALSO
279
280=head1 AUTHOR
281
282Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
283
284=head1 COPYRIGHT AND LICENSE
285
286Copyright (C) 2008 CNRS SA/CETP/LATMOS
287
288This library is free software; you can redistribute it and/or modify
289it under the same terms as Perl itself, either Perl version 5.10.0 or,
290at your option, any later version of Perl 5 you may have available.
291
292=cut
Note: See TracBrowser for help on using the repository browser.