]> git.proxmox.com Git - pve-common.git/blame - src/PVE/LDAP.pm
fix whitespaces
[pve-common.git] / src / PVE / LDAP.pm
CommitLineData
261ea3ca
DC
1package PVE::LDAP;
2
3use strict;
4use warnings;
5
6use Net::IP;
7use Net::LDAP;
8use Net::LDAP::Control::Paged;
9use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED);
10
11sub ldap_connect {
12 my ($servers, $scheme, $port, $opts) = @_;
13
14 my $start_tls = 0;
15
16 if ($scheme eq 'ldap+starttls') {
17 $scheme = 'ldap';
18 $start_tls = 1;
19 }
20
21 my %ldap_opts = (
22 scheme => $scheme,
23 port => $port,
24 timeout => 10,
25 onerror => 'die',
26 );
27
28 my $hosts = [];
29 for my $host (@$servers) {
30 if (Net::IP::ip_is_ipv6($host)) {
31 push @$hosts, "[$host]";
32 } else {
33 push @$hosts, $host;
34 }
35 }
36
37 for my $opt (qw(clientcert clientkey capath cafile sslversion verify)) {
38 $ldap_opts{$opt} = $opts->{$opt} if $opts->{$opt};
39 }
40
1175979f 41 my $ldap = Net::LDAP->new($hosts, %ldap_opts) || die "$@\n";
261ea3ca
DC
42
43 if ($start_tls) {
44 $ldap->start_tls(%$opts);
45 }
46
47 return $ldap;
48}
49
50sub ldap_bind {
51 my ($ldap, $dn, $pw) = @_;
52
53 my $res;
54 if (defined($dn) && defined($pw)) {
55 $res = $ldap->bind($dn, password => $pw);
56 } else { # anonymous bind
57 $res = $ldap->bind();
58 }
59
60 my $code = $res->code;
61 my $err = $res->error;
62
63 die "ldap bind failed: $err\n" if $code;
64}
65
66sub get_user_dn {
67 my ($ldap, $name, $attr, $base_dn) = @_;
68
69 # search for dn
70 my $result = $ldap->search(
71 base => $base_dn // "",
72 scope => "sub",
73 filter => "$attr=$name",
74 attrs => ['dn']
75 );
76 return undef if !$result->entries;
77 my @entries = $result->entries;
78 return $entries[0]->dn;
79}
80
81sub auth_user_dn {
82 my ($ldap, $dn, $pw, $noerr) = @_;
20369902
FW
83
84 if (!$dn) {
85 return undef if $noerr;
86 die "user dn is empty\n";
87 }
88
261ea3ca
DC
89 my $res = $ldap->bind($dn, password => $pw);
90
91 my $code = $res->code;
92 my $err = $res->error;
93
94 if ($code) {
95 return undef if $noerr;
96 die $err;
97 }
98
99 return 1;
100}
101
102sub query_users {
340e0881 103 my ($ldap, $filter, $attributes, $base_dn, $classes) = @_;
261ea3ca
DC
104
105 # build filter from given filter and attribute list
106 my $tmp = "(|";
107 foreach my $att (@$attributes) {
108 $tmp .= "($att=*)";
109 }
110 $tmp .= ")";
111
340e0881
DC
112 if ($classes) {
113 $tmp = "(&$tmp(|";
114 for my $class (@$classes) {
115 $tmp .= "(objectclass=$class)";
116 }
117 $tmp .= "))";
118 }
119
261ea3ca
DC
120 if ($filter) {
121 $filter = "($filter)" if $filter !~ m/^\(.*\)$/;
122 $filter = "(&${filter}${tmp})"
123 } else {
124 $filter = $tmp;
125 }
126
127 my $page = Net::LDAP::Control::Paged->new(size => 900);
128
129 my @args = (
130 base => $base_dn // "",
131 scope => "subtree",
132 filter => $filter,
133 control => [ $page ],
134 attrs => [ @$attributes, 'memberOf'],
135 );
136
137 my $cookie;
138 my $err;
139 my $users = [];
140
141 while(1) {
142
143 my $mesg = $ldap->search(@args);
144
145 # stop on error
146 if ($mesg->code) {
147 $err = "ldap user search error: " . $mesg->error;
148 last;
149 }
150
151 #foreach my $entry ($mesg->entries) { $entry->dump; }
152 foreach my $entry ($mesg->entries) {
153 my $user = {
154 dn => $entry->dn,
155 attributes => {},
156 groups => [$entry->get_value('memberOf')],
157 };
158
159 foreach my $attr (@$attributes) {
160 my $vals = [$entry->get_value($attr)];
161 if (scalar(@$vals)) {
162 $user->{attributes}->{$attr} = $vals;
163 }
164 }
165
166 push @$users, $user;
167 }
168
169 # Get cookie from paged control
170 my ($resp) = $mesg->control(LDAP_CONTROL_PAGED) or last;
171 $cookie = $resp->cookie;
172
173 last if (!defined($cookie) || !length($cookie));
174
175 # Set cookie in paged control
176 $page->cookie($cookie);
177 }
178
179 if (defined($cookie) && length($cookie)) {
180 # We had an abnormal exit, so let the server know we do not want any more
181 $page->cookie($cookie);
182 $page->size(0);
183 $ldap->search(@args);
184 $err = "LDAP user query unsuccessful" if !$err;
185 }
186
187 die $err if $err;
188
189 return $users;
190}
191
192sub query_groups {
3c775763 193 my ($ldap, $base_dn, $classes, $filter, $group_name_attr) = @_;
261ea3ca
DC
194
195 my $tmp = "(|";
196 for my $class (@$classes) {
197 $tmp .= "(objectclass=$class)";
198 }
199 $tmp .= ")";
200
201 if ($filter) {
202 $filter = "($filter)" if $filter !~ m/^\(.*\)$/;
203 $filter = "(&${filter}${tmp})"
204 } else {
205 $filter = $tmp;
206 }
207
208 my $page = Net::LDAP::Control::Paged->new(size => 100);
209
3c775763
DC
210 my $attrs = [ 'member', 'uniqueMember' ];
211 push @$attrs, $group_name_attr if $group_name_attr;
261ea3ca
DC
212 my @args = (
213 base => $base_dn,
214 scope => "subtree",
215 filter => $filter,
216 control => [ $page ],
3c775763 217 attrs => $attrs,
261ea3ca
DC
218 );
219
220 my $cookie;
221 my $err;
222 my $groups = [];
223
224 while(1) {
225
226 my $mesg = $ldap->search(@args);
227
228 # stop on error
229 if ($mesg->code) {
230 $err = "ldap group search error: " . $mesg->error;
231 last;
232 }
233
234 foreach my $entry ( $mesg->entries ) {
235 my $group = {
236 dn => $entry->dn,
237 members => []
238 };
239 my $members = [$entry->get_value('member')];
240 if (!scalar(@$members)) {
241 $members = [$entry->get_value('uniqueMember')];
242 }
243 $group->{members} = $members;
3c775763
DC
244 if ($group_name_attr && (my $name = $entry->get_value($group_name_attr))) {
245 $group->{name} = $name;
246 }
261ea3ca
DC
247 push @$groups, $group;
248 }
249
250 # Get cookie from paged control
251 my ($resp) = $mesg->control(LDAP_CONTROL_PAGED) or last;
252 $cookie = $resp->cookie;
253
254 last if (!defined($cookie) || !length($cookie));
255
256 # Set cookie in paged control
257 $page->cookie($cookie);
258 }
259
260 if ($cookie) {
261 # We had an abnormal exit, so let the server know we do not want any more
262 $page->cookie($cookie);
263 $page->size(0);
264 $ldap->search(@args);
265 $err = "LDAP group query unsuccessful" if !$err;
266 }
267
268 die $err if $err;
269
270 return $groups;
271}
272
2731;