Merge branch 'master' of git@vc.linet-services.de:public/lx-office-erp
[kivitendo-erp.git] / SL / Auth / LDAP.pm
1 package SL::Auth::LDAP;
2
3 use English '-no_match_vars';
4
5 use Scalar::Util qw(weaken);
6 use SL::Auth::Constants qw(:all);
7
8 use strict;
9
10 sub new {
11   $main::lxdebug->enter_sub();
12
13   if (!defined eval "require Net::LDAP;") {
14     die 'The module "Net::LDAP" is not installed.';
15   }
16
17   my $type = shift;
18   my $self = {};
19
20   $self->{auth} = shift;
21   weaken $self->{auth};
22
23   bless $self, $type;
24
25   $main::lxdebug->leave_sub();
26
27   return $self;
28 }
29
30 sub _connect {
31   $main::lxdebug->enter_sub();
32
33   my $self = shift;
34   my $cfg  = $self->{auth}->{LDAP_config};
35
36   if ($self->{ldap}) {
37     $main::lxdebug->leave_sub();
38
39     return $self->{ldap};
40   }
41
42   my $port      = $cfg->{port} || 389;
43   $self->{ldap} = Net::LDAP->new($cfg->{host}, 'port' => $port);
44
45   if (!$self->{ldap}) {
46     $main::form->error($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/lx_office.conf.', $cfg->{host}, $port));
47   }
48
49   if ($cfg->{tls}) {
50     my $mesg = $self->{ldap}->start_tls('verify' => 'none');
51     if ($mesg->is_error()) {
52       $main::form->error($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/lx_office.conf.'));
53     }
54   }
55
56   if ($cfg->{bind_dn}) {
57     my $mesg = $self->{ldap}->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password});
58     if ($mesg->is_error()) {
59       $main::form->error($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/lx_office.conf.', $cfg->{bind_dn}));
60     }
61   }
62
63   $main::lxdebug->leave_sub();
64
65   return $self->{ldap};
66 }
67
68 sub _get_filter {
69   $main::lxdebug->enter_sub();
70
71   my $self   = shift;
72   my $login  = shift;
73
74   my ($cfg, $filter);
75
76   $cfg    =  $self->{auth}->{LDAP_config};
77
78   $filter =  "$cfg->{filter}";
79   $filter =~ s|^\s+||;
80   $filter =~ s|\s+$||;
81
82   $login  =~ s|\\|\\\\|g;
83   $login  =~ s|\(|\\\(|g;
84   $login  =~ s|\)|\\\)|g;
85   $login  =~ s|\*|\\\*|g;
86   $login  =~ s|\x00|\\00|g;
87
88   if ($filter =~ m|<\%login\%>|) {
89     substr($filter, $LAST_MATCH_START[0], $LAST_MATCH_END[0] - $LAST_MATCH_START[0]) = $login;
90
91   } elsif ($filter) {
92     if ((substr($filter, 0, 1) ne '(') || (substr($filter, -1, 1) ne ')')) {
93       $filter = "($filter)";
94     }
95
96     $filter = "(&${filter}($cfg->{attribute}=${login}))";
97
98   } else {
99     $filter = "$cfg->{attribute}=${login}";
100
101   }
102
103   $main::lxdebug->leave_sub();
104
105   return $filter;
106 }
107
108 sub _get_user_dn {
109   $main::lxdebug->enter_sub();
110
111   my $self   = shift;
112   my $ldap   = shift;
113   my $login  = shift;
114
115   $self->{dn_cache} ||= { };
116
117   if ($self->{dn_cache}->{$login}) {
118     $main::lxdebug->leave_sub();
119     return $self->{dn_cache}->{$login};
120   }
121
122   my $cfg    = $self->{auth}->{LDAP_config};
123
124   my $filter = $self->_get_filter($login);
125
126   my $mesg   = $ldap->search('base' => $cfg->{base_dn}, 'scope' => 'sub', 'filter' => $filter);
127
128   if ($mesg->is_error() || (0 == $mesg->count())) {
129     $main::lxdebug->leave_sub();
130     return undef;
131   }
132
133   my $entry                   = $mesg->entry(0);
134   $self->{dn_cache}->{$login} = $entry->dn();
135
136   $main::lxdebug->leave_sub();
137
138   return $self->{dn_cache}->{$login};
139 }
140
141 sub authenticate {
142   $main::lxdebug->enter_sub();
143
144   my $self       = shift;
145   my $login      = shift;
146   my $password   = shift;
147   my $is_crypted = shift;
148
149   if ($is_crypted) {
150     $main::lxdebug->leave_sub();
151     return ERR_BACKEND;
152   }
153
154   my $ldap = $self->_connect();
155
156   if (!$ldap) {
157     $main::lxdebug->leave_sub();
158     return ERR_BACKEND;
159   }
160
161   my $dn = $self->_get_user_dn($ldap, $login);
162
163   $main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: dn $dn");
164
165   if (!$dn) {
166     $main::lxdebug->leave_sub();
167     return ERR_BACKEND;
168   }
169
170   my $mesg = $ldap->bind($dn, 'password' => $password);
171
172   $main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: bind mesg " . $mesg->error());
173
174   $main::lxdebug->leave_sub();
175
176   return $mesg->is_error() ? ERR_PASSWORD : OK;
177 }
178
179 sub can_change_password {
180   return 0;
181 }
182
183 sub requires_cleartext_password {
184   return 1;
185 }
186
187 sub change_password {
188   return ERR_BACKEND;
189 }
190
191 sub verify_config {
192   $main::lxdebug->enter_sub();
193
194   my $form   = $main::form;
195   my $locale = $main::locale;
196
197   my $self = shift;
198   my $cfg  = $self->{auth}->{LDAP_config};
199
200   if (!$cfg) {
201     $form->error($locale->text('config/lx_office.conf: Key "authentication/ldap" is missing.'));
202   }
203
204   if (!$cfg->{host} || !$cfg->{attribute} || !$cfg->{base_dn}) {
205     $form->error($locale->text('config/lx_office.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".'));
206   }
207
208   $main::lxdebug->leave_sub();
209 }
210
211 1;