branch | foerste |
changeset 56 | 722cdb1321c7 |
parent 54 | 1f74755c407e |
child 58 | dd04534fe595 |
55:ef65e9adf0f6 | 56:722cdb1321c7 |
---|---|
4 # $Id$ |
4 # $Id$ |
5 # $URL$ |
5 # $URL$ |
6 |
6 |
7 use strict; |
7 use strict; |
8 use warnings; |
8 use warnings; |
9 require 5.10.0; |
|
9 use File::Path qw(remove_tree); |
10 use File::Path qw(remove_tree); |
10 use Net::LDAP; |
11 use Net::LDAP; |
11 use Net::LDAP::Constant |
12 use Net::LDAP::Constant |
12 qw(LDAP_ALREADY_EXISTS LDAP_NO_SUCH_OBJECT LDAP_TYPE_OR_VALUE_EXISTS); |
13 qw(LDAP_ALREADY_EXISTS LDAP_NO_SUCH_OBJECT LDAP_TYPE_OR_VALUE_EXISTS); |
13 use Net::LDAP::Entry; |
14 use Net::LDAP::Entry; |
14 use Mail::IMAPTalk; |
15 use Mail::IMAPTalk; |
15 use Text::Wrap; |
16 use Text::Wrap; |
16 use password; |
17 use password; |
18 use Term::ReadKey; |
|
17 |
19 |
18 my $Cf; |
20 my $Cf; |
19 my ( $ldap, $ubase, $abase ); |
21 my ( $ldap, $ubase, $abase ); |
20 my ( $imap, $acl_password ); |
22 my ( $imap, $acl_password, $nspat ); |
21 END { $imap and $imap = undef; } |
23 END { $imap and $imap = undef; } |
22 |
24 |
23 sub _list(); |
25 sub _list(); |
24 sub _mkpw($); |
26 sub _mkpw($); |
25 |
27 |
26 sub list_by_user($@); |
28 sub list_by_user($@); |
27 sub list_by_folder($$$); |
29 sub list_by_folder($); |
28 sub uniq(@); |
30 sub uniq(@); |
29 sub verbose(@); |
31 sub verbose(@); |
32 sub prompt($$); |
|
33 sub imap_list($$); |
|
34 sub imap_rlist($$$); |
|
35 sub acl_folders($); |
|
30 |
36 |
31 sub OU_ACCOUNTS(); |
37 sub OU_ACCOUNTS(); |
32 sub OU_ALIASES(); |
38 sub OU_ALIASES(); |
33 sub AT_PRIMARYADDRESS(); |
39 sub AT_PRIMARYADDRESS(); |
34 sub OC_RECIPIENT(); |
40 sub OC_RECIPIENT(); |
66 $acl_password = |
72 $acl_password = |
67 $Cf->acl_password |
73 $Cf->acl_password |
68 || $ENV{IMAP_PASS} |
74 || $ENV{IMAP_PASS} |
69 || password::ask( "IMAP (" . $Cf->acl_admin . ") password: " ); |
75 || password::ask( "IMAP (" . $Cf->acl_admin . ") password: " ); |
70 |
76 |
71 if ( $Cf->list ) { _list() } |
|
72 elsif ( $Cf->modify ) { _modify() } |
|
73 else { die "Need action (--modify|--list)\n" } |
|
74 |
|
75 } |
|
76 |
|
77 sub _modify() { |
|
78 |
|
79 die 'Not yet implemented'; |
|
80 |
|
81 # Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt: |
|
82 # dn: uid=USER,... |
|
83 my (@user) = @ARGV or die "Need user(s)\n"; |
|
84 $Cf->user or die "Need user(s)\n"; |
|
85 $Cf->acl or die "Need acl\n"; |
|
86 my @dns; |
|
87 |
|
88 my $r = $ldap->search( |
|
89 base => $ubase, |
|
90 filter => "(|" . join( "", map { "(uid=$_)" } @ARGV ) . ")" |
|
91 ); |
|
92 die $r->error if $r->code; |
|
93 die "No entries found.\n" if $r->count == 0; |
|
94 |
|
95 while ( my $e = $r->shift_entry ) { |
|
96 my $r; |
|
97 |
|
98 my $user = $e->get_value("uid"); |
|
99 my $dn = $e->dn; |
|
100 |
|
101 my $modified = 0; |
|
102 verbose "$user:"; |
|
103 |
|
104 verbose "\n\t$dn..."; |
|
105 |
|
106 # Fix: iusMailOptions wurde erst später eingeführt, bei Bedarf also hinzufügen |
|
107 #if (!grep /^iusMailOptions$/, @{$e->get("objectClass")}) { |
|
108 #$e->add(objectClass => "iusMailOptions"); |
|
109 #} |
|
110 |
|
111 if ( my $cn = $Cf->fullname ) { |
|
112 |
|
113 # Aus dem Fullnamen leiten wir cn und sn ab. |
|
114 my $sn = ( reverse split " ", $cn )[0]; |
|
115 |
|
116 if ( $cn =~ s/^\+// ) { |
|
117 $e->replace( |
|
118 cn => [ uniq $e->get("cn"), $cn ], |
|
119 sn => [ uniq $e->get("sn"), $sn ] |
|
120 ); |
|
121 } elsif ( $cn =~ s/^-// ) { |
|
122 $e->delete( cn => [$cn], sn => [$sn] ); |
|
123 } else { |
|
124 $e->replace( cn => $cn, sn => $sn ); |
|
125 } |
|
126 $modified++; |
|
127 } |
|
128 |
|
129 if ( defined $Cf->other ) { |
|
130 my @o = split /,/, $Cf->other; |
|
131 grep { /^[+-]/ } @o or $e->delete(AT_ADDRESS); |
|
132 |
|
133 foreach my $a ( split /,/, $Cf->other ) { |
|
134 if ( $a =~ s/^-// ) { |
|
135 $e->delete( (AT_ADDRESS) => [$a] ); |
|
136 } else { |
|
137 $a =~ s/^\+//; |
|
138 |
|
139 # Darf noch nicht woanders sein |
|
140 $r = $ldap->search( base => $ubase, filter => "(mail=$a)" ); |
|
141 die $r->error if $r->code; |
|
142 die "$a ist schon vergeben\n" if $r->count; |
|
143 |
|
144 $e->add( (AT_ADDRESS) => [$a] ); |
|
145 } |
|
146 } |
|
147 $modified++; |
|
148 } |
|
149 |
|
150 if ( defined $Cf->group ) { |
|
151 my @g = split /,/, $Cf->group; |
|
152 grep { /^[+-]/ } @g |
|
153 or $e->delete(AT_GROUP) |
|
154 if $e->get_value(AT_GROUP); |
|
155 |
|
156 foreach my $g (@g) { |
|
157 if ( $g =~ s/^-// ) { |
|
158 $e->delete( (AT_GROUP) => [$g] ); |
|
159 } else { |
|
160 $g =~ s/^\+//; |
|
161 $e->add( (AT_GROUP) => [$g] ); |
|
162 } |
|
163 } |
|
164 $modified++; |
|
165 } |
|
166 |
|
167 if ( defined $Cf->forward ) { |
|
168 my @f = split /,/, $Cf->forward; |
|
169 grep { /^[+-]/ } @f |
|
170 or $e->delete(AT_FORWARDINGADDRESS) |
|
171 if $e->get_value(AT_FORWARDINGADDRESS); |
|
172 |
|
173 foreach my $f (@f) { |
|
174 if ( $f =~ s/^-// ) { |
|
175 $e->delete( (AT_FORWARDINGADDRESS) => [$f] ); |
|
176 } else { |
|
177 $f =~ s/^\+//; |
|
178 $e->add( (AT_FORWARDINGADDRESS) => [$f] ); |
|
179 } |
|
180 } |
|
181 $modified++; |
|
182 } |
|
183 |
|
184 if ( my $a = $Cf->primary ) { |
|
185 $r = $ldap->search( |
|
186 base => $ubase, |
|
187 |
|
188 # filter => "(|(mailPrimaryAddress=$a)(mail=$a))"); |
|
189 filter => "(mail=$a)" |
|
190 ); |
|
191 die $r->error if $r->code; |
|
192 die "$a ist schon vergeben\n" if $r->count; |
|
193 |
|
194 $e->replace( (AT_PRIMARYADDRESS) => $Cf->primary ); |
|
195 $modified++; |
|
196 } |
|
197 |
|
198 if ( my $pw = _mkpw( $Cf->password ) ) { |
|
199 $e->replace( userPassword => $pw ); |
|
200 $modified++; |
|
201 } |
|
202 |
|
203 #if ($Cf->internal ne ":") { |
|
204 #$e->replace(iusRestrictedMail => $Cf->internal ? "TRUE" : "FALSE"); |
|
205 #$modified++; |
|
206 #} |
|
207 |
|
208 $e->dump if $Cf->debug; |
|
209 |
|
210 if ($modified) { |
|
211 $r = $e->update($ldap); |
|
212 die $r->error . $r->code if $r->code; |
|
213 } |
|
214 |
|
215 verbose "ok\n"; |
|
216 |
|
217 print "\n"; |
|
218 } |
|
219 |
|
220 } |
|
221 |
|
222 sub _list() { |
|
223 |
|
224 #@ARGV = ("*") unless @ARGV; |
|
225 |
|
226 die "option acl_admin required\n" unless $Cf->acl_admin; |
|
227 |
|
228 $imap = Mail::IMAPTalk->new( |
77 $imap = Mail::IMAPTalk->new( |
229 Server => $Cf->imap_server, |
78 Server => $Cf->imap_server, |
230 Port => $Cf->imap_port |
79 Port => $Cf->imap_port |
231 ) |
80 ) |
232 or die "Can't connect to IMAP Server '", $Cf->imap_server, |
81 or die "Can't connect to IMAP Server '", $Cf->imap_server, |
233 "', Port '", $Cf->imap_port, "': ", $@; |
82 "', Port '", $Cf->imap_port, "': ", $@; |
234 $imap->login( $Cf->acl_admin, $acl_password ) or die $@; |
83 $imap->login( $Cf->acl_admin, $acl_password ) or die $@; |
235 die "IMAP Server does not advertise acl support" unless $imap->capability->{acl}; |
84 die "IMAP Server does not advertise acl support" unless $imap->capability->{acl}; |
236 |
85 |
86 # requires an imap connection |
|
87 my $ns = $imap->namespace() or die "No public namespaces available: $@"; |
|
88 $nspat = []; |
|
89 for (@{$ns->[2]}) { |
|
90 (my $n = $_->[0]) =~ s/$_->[1]$//; |
|
91 push @{$nspat}, [qr/\Q$n\E($_->[1]|$)/, $_->[1]]; |
|
92 } |
|
93 |
|
94 if ( $Cf->list ) { _list() } |
|
95 elsif ( $Cf->modify ) { _modify() } |
|
96 elsif ( $Cf->delete ) { $Cf->acl('delete') ; _modify() } |
|
97 else { die "Need action (--modify|--list)\n" } |
|
98 |
|
99 } |
|
100 |
|
101 sub _modify() { |
|
102 |
|
103 # Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt: |
|
104 # dn: uid=USER,... |
|
105 my @users; |
|
106 @ARGV or die "Need user(s)\n"; |
|
107 $Cf->folder ~~ [] and die "Need folders(s)\n"; |
|
108 $Cf->acl or die "Need acl\n"; |
|
109 $Cf->recursive and $Cf->create and die "Use either --recursive or --create but not both\n"; |
|
110 |
|
111 my $r = $ldap->search( |
|
112 base => $ubase, |
|
113 filter => "(|" . join( "", map { "(uid=$_)" } @ARGV ) . ")" |
|
114 ); |
|
115 die $r->error if $r->code; |
|
116 unless ($r->count) { |
|
117 prompt('No matching user found in ldap. Continue? (y/N)', "n\n") =~ /y/i or exit 0; |
|
118 @users = @ARGV; |
|
119 } |
|
120 |
|
121 while (my $e = ($r->shift_entry or shift @users)) { |
|
122 |
|
123 my ($user, $dn); |
|
124 |
|
125 if (ref $e eq 'Net::LDAP::Entry') { |
|
126 $user = $e->get_value("uid"); |
|
127 $dn = $e->dn; |
|
128 } else { |
|
129 $user = $e; |
|
130 $dn = '[dn not available]'; |
|
131 } |
|
132 |
|
133 my $modified = 0; |
|
134 verbose "$user:\n"; |
|
135 verbose "\t$dn...\n"; |
|
136 |
|
137 for my $folder (@{$Cf->folder}) { |
|
138 |
|
139 $imap->create($folder) or die "Can't create folder '$folder': $@" if $Cf->create; |
|
140 |
|
141 for my $f (@{acl_folders($folder)}) { |
|
142 |
|
143 if ($Cf->acl eq 'delete') { |
|
144 $imap->deleteacl($f, $user) or die "Can't delete acl: $@"; |
|
145 verbose "\t$f: none\n"; |
|
146 } else { |
|
147 $imap->setacl($f, $user, $Cf->acl) or die "Can't set acl: $@"; |
|
148 verbose "\t$f: " . $Cf->acl . "\n"; |
|
149 } |
|
150 |
|
151 } |
|
152 |
|
153 } |
|
154 # Fix: iusMailOptions wurde erst später eingeführt, bei Bedarf also hinzufügen |
|
155 #if (!grep /^iusMailOptions$/, @{$e->get("objectClass")}) { |
|
156 #$e->add(objectClass => "iusMailOptions"); |
|
157 #} |
|
158 |
|
159 #if ($Cf->internal ne ":") { |
|
160 #$e->replace(iusRestrictedMail => $Cf->internal ? "TRUE" : "FALSE"); |
|
161 #$modified++; |
|
162 #} |
|
163 |
|
164 verbose "ok\n"; |
|
165 print "\n"; |
|
166 |
|
167 } |
|
168 |
|
169 } |
|
170 |
|
171 sub _list() { |
|
172 |
|
173 #@ARGV = ("*") unless @ARGV; |
|
174 |
|
175 die "option acl_admin required\n" unless $Cf->acl_admin; |
|
176 |
|
237 if (@ARGV) { |
177 if (@ARGV) { |
238 |
178 |
239 # my $uid = $ARGV[0]; |
179 # my $uid = $ARGV[0]; |
240 # # searching by more than use user may be too expensive |
180 # # searching by more than use user may be too expensive |
241 # die "Searching by more than one user not supported" unless @ARGV == 1 or $uid =~ /\*/; |
181 # die "Searching by more than one user not supported" unless @ARGV == 1 or $uid =~ /\*/; |
242 #list_by_user($_) for @ARGV; |
182 #list_by_user($_) for @ARGV; |
183 |
|
184 warn "--folder option ignored when listing by user" unless $Cf->folder ~~ []; |
|
243 list_by_user($imap, @ARGV); |
185 list_by_user($imap, @ARGV); |
244 |
186 |
245 } elsif ($Cf->folder) { |
187 } elsif (not $Cf->folder ~~ []) { |
246 |
188 |
247 list_by_folder($imap, $_, $Cf->recursive) for @{$Cf->folder}; |
189 list_by_folder($_) for @{$Cf->folder}; |
248 |
190 |
249 } else { |
191 } else { |
250 |
192 |
251 die "Need either user or --folder. If you really want to search all users then supply the pattern '*'."; |
193 die "Need either user or --folder. If you really want to search all users then supply the pattern '*'."; |
252 |
194 |
262 my $r = $ldap->search( |
204 my $r = $ldap->search( |
263 filter => $filter, |
205 filter => $filter, |
264 base => $ubase, |
206 base => $ubase, |
265 ); |
207 ); |
266 die $r->error if $r->code; |
208 die $r->error if $r->code; |
267 verbose("No matching users found\n") unless $r->count; |
209 my @users; |
268 |
210 unless ($r->count) { |
269 while (my $e = $r->shift_entry) { |
211 verbose("No matching users found in ldap.\n"); |
270 |
212 @users = @_; |
271 my $uid = $e->get_value("uid"); |
213 } |
272 my $cn = join( ", ", $e->get_value("cn") ); |
214 |
273 my $mr = $e->get_value(AT_PRIMARYADDRESS) || ""; # ?? |
215 while (my $e = ($r->shift_entry or shift @users)) { |
216 |
|
217 my ($uid, $cn, $mr); |
|
218 if (ref $e eq 'Net::LDAP::Entry') { |
|
219 $uid = $e->get_value("uid"); |
|
220 $cn = join( ", ", $e->get_value("cn") ); |
|
221 $mr = $e->get_value(AT_PRIMARYADDRESS) || ""; # ?? |
|
222 } else { |
|
223 $uid = $e; |
|
224 $cn = '[cn not available]'; |
|
225 $mr = '[address not available]'; |
|
226 } |
|
274 |
227 |
275 print "$uid: $cn <$mr>\n"; |
228 print "$uid: $cn <$mr>\n"; |
276 |
229 |
277 #if (($e->get_value("iusRestrictedMail")||"") eq "TRUE") { |
230 #if (($e->get_value("iusRestrictedMail")||"") eq "TRUE") { |
278 #print " INTERNAL"; |
231 #print " INTERNAL"; |
304 my $hasacl; |
257 my $hasacl; |
305 my $ns = $imap->namespace() or die "No public namespaces available: $@"; |
258 my $ns = $imap->namespace() or die "No public namespaces available: $@"; |
306 # uns interessieren nur 'public' namespaces |
259 # uns interessieren nur 'public' namespaces |
307 for my $n (@{$ns->[2]}) { |
260 for my $n (@{$ns->[2]}) { |
308 |
261 |
309 my $folders = $imap->list( '', "$n->[0]*" ) or die $@; |
262 my $folders = imap_rlist( '', $n->[0], $n->[1] ); |
310 ref $folders or die "Got empty folder list. Does '$n->[0]' actually exist? Is it readable?"; |
|
311 |
|
312 for my $f ( @{$folders} ) { |
263 for my $f ( @{$folders} ) { |
313 |
264 |
314 # single folder sieht wie folgt aus: [[flag1, flag2, ...], separator, foldername] |
|
315 #next if '\\Noselect' ~~ $f->[0]; |
265 #next if '\\Noselect' ~~ $f->[0]; |
316 my $perms = $imap->getacl( $f->[2] ) or die $@; |
266 my $perms = $imap->getacl( $f ) or die $@; |
317 my ($u, $p); |
267 my ($u, $p); |
318 while ($u = shift @{$perms} and $p = shift @{$perms} and $u eq $uid) { |
268 while ($u = shift @{$perms} and $p = shift @{$perms}) { |
269 next unless $u eq $uid; |
|
319 $hasacl = 1; |
270 $hasacl = 1; |
320 print "\t$f->[2]: $u [$p]\n"; |
271 print "\t$f: $u [$p]\n"; |
321 } |
272 } |
322 |
273 |
323 } |
274 } |
324 |
275 |
325 } |
276 } |
329 |
280 |
330 } |
281 } |
331 |
282 |
332 } |
283 } |
333 |
284 |
334 sub list_by_folder($$$) { |
285 sub list_by_folder($) { |
335 |
286 |
336 my ($imap, $folder, $recursive) = @_; |
287 my ($folder) = @_; |
337 |
288 |
338 $folder .= '/' unless $folder =~ m,/$,; |
289 for my $f ( @{acl_folders($folder)} ) { |
339 my $folders = $recursive |
|
340 ? ($imap->list('', "$folder*") or die $@) |
|
341 : [[ undef, undef, $folder ]]; |
|
342 |
|
343 ref $folders or die "Got empty folder list. Does '$folder' actually exist? Is it readable?"; |
|
344 |
|
345 for my $f ( @{$folders} ) { |
|
346 |
290 |
347 my $hasacl; |
291 my $hasacl; |
348 print "$f->[2]\n"; |
292 print "$f\n"; |
349 |
293 |
350 # single folder sieht wie folgt aus: [[flag1, flag2, ...], separator, foldername] |
294 my $perms = $imap->getacl( $f ) or die $@; |
351 #next if '\\Noselect' ~~ $f->[0]; |
|
352 my $perms = $imap->getacl( $f->[2] ) or die $@; |
|
353 my ($u, $p); |
295 my ($u, $p); |
354 while ($u = shift @{$perms} |
296 while ($u = shift @{$perms} and $p = shift @{$perms}) { |
355 and $p = shift @{$perms}) { |
297 |
356 next if $u eq $Cf->acl_admin or $u eq $Cf->imap_admin; |
298 # use Data::Dumper; |
299 # warn Dumper([ $Cf->acl_admin, $Cf->imap_admin, '#' . $Cf->acl_admin, '#' . $Cf->imap_admin ]); |
|
300 |
|
301 # '#user' will be listed when we have a global acl for 'user' |
|
302 next if $u ~~ [ $Cf->acl_admin, $Cf->imap_admin, '#' . $Cf->acl_admin, '#' . $Cf->imap_admin ]; |
|
357 $hasacl = 1; |
303 $hasacl = 1; |
358 print "\t$u [$p]\n"; |
304 print "\t$u [$p]\n"; |
359 } |
305 } |
360 |
306 |
361 print "\tno acl found\n" unless $hasacl; |
307 print "\tno acl found\n" unless $hasacl; |
390 return shift @pw; |
336 return shift @pw; |
391 |
337 |
392 } |
338 } |
393 } |
339 } |
394 |
340 |
341 sub imap_list($$) { |
|
342 |
|
343 my ($ref, $folder) = @_; |
|
344 |
|
345 my $list = $imap->list($ref, $folder) or die "Can't list('$ref', '$folder'): $@"; |
|
346 ref $list or die "Got empty folder list. Does '$folder' actually exist? Is it readable?"; |
|
347 # single folder sieht wie folgt aus: [[flag1, flag2, ...], separator, foldername] |
|
348 return [ map $_->[2], @{$list} ]; |
|
349 |
|
350 } |
|
351 |
|
352 sub imap_rlist($$$) { |
|
353 |
|
354 my ($ref, $folder, $sep) = @_; |
|
355 $folder =~ s/$sep+$//; |
|
356 |
|
357 my $list = imap_list($ref, $folder); |
|
358 push @{$list}, @{imap_list($ref, "$folder$sep*")} if $Cf->recursive; |
|
359 return $list; |
|
360 } |
|
361 |
|
362 sub acl_folders($) { |
|
363 |
|
364 my ($f) = @_; |
|
365 my $folders; |
|
366 |
|
367 for my $np (@{$nspat}) { |
|
368 # don't modify $f! |
|
369 (my $ft = $f) =~ s/$np->[1]$//; |
|
370 return imap_rlist('', $f, $np->[1]) if ($ft =~ /$np->[0]/); |
|
371 } |
|
372 |
|
373 die "Foldername '$f' must begin with the name of a shared namespace\n"; |
|
374 |
|
375 } |
|
376 |
|
377 sub prompt($$) { |
|
378 my ($prompt, $default) = @_; |
|
379 print $prompt, substr($default, 0, 1), "\b"; |
|
380 ReadMode 4; my $r = ReadKey(0); ReadMode 0; |
|
381 if ($r eq "\n") { $r = $default } |
|
382 else { $r .= substr($default, 1) } |
|
383 print $r; |
|
384 return $r; |
|
385 } |
|
386 |
|
395 1; |
387 1; |
396 |
388 |
397 # vim:sts=4 sw=4 aw ai sm nohlsearch: |
389 # vim:sts=4 sw=4 aw ai sm nohlsearch: |