acl.pm
branchfoerste
changeset 64 6a6c18cddf46
parent 63 96f567261e87
child 66 a41fd3429d63
equal deleted inserted replaced
63:96f567261e87 64:6a6c18cddf46
    16 use Text::Wrap;
    16 use Text::Wrap;
    17 use password;
    17 use password;
    18 use Term::ReadKey;
    18 use Term::ReadKey;
    19 
    19 
    20 my $Cf;
    20 my $Cf;
    21 my ( $ldap, $ubase, $abase );
    21 my ( $ldap, $ubase,        $abase );
    22 my ( $imap, $acl_password, $nspat );
    22 my ( $imap, $acl_password, $nspat );
    23 END { $imap and $imap = undef; }
    23 END { $imap and $imap = undef; }
    24 
    24 
    25 sub _list();
    25 sub _list();
    26 sub _mkpw($);
    26 sub _mkpw($);
    78       || password::ask( "IMAP (" . $Cf->acl_admin . ") password: " );
    78       || password::ask( "IMAP (" . $Cf->acl_admin . ") password: " );
    79 
    79 
    80     $imap = Mail::IMAPTalk->new(
    80     $imap = Mail::IMAPTalk->new(
    81         Server => $Cf->imap_server,
    81         Server => $Cf->imap_server,
    82         Port   => $Cf->imap_port
    82         Port   => $Cf->imap_port
    83     )
    83       )
    84         or die "Can't connect to IMAP Server '", $Cf->imap_server,
    84       or die "Can't connect to IMAP Server '", $Cf->imap_server,
    85     "', Port '", $Cf->imap_port, "': ", $@;
    85       "', Port '", $Cf->imap_port, "': ", $@;
    86     $imap->login( $Cf->acl_admin, $acl_password ) or die $@;
    86     $imap->login( $Cf->acl_admin, $acl_password ) or die $@;
    87     die "IMAP Server does not advertise acl support" unless $imap->capability->{acl};
    87     die "IMAP Server does not advertise acl support"
       
    88       unless $imap->capability->{acl};
    88 
    89 
    89     # requires an imap connection
    90     # requires an imap connection
    90     my $ns = $imap->namespace() or die "No public namespaces available: $@";
    91     my $ns = $imap->namespace() or die "No public namespaces available: $@";
    91     $nspat = [];
    92     $nspat = [];
    92     for (@{$ns->[2]}) {
    93     for ( @{ $ns->[2] } ) {
    93         (my $n = $_->[0]) =~ s/$_->[1]$//;
    94         ( my $n = $_->[0] ) =~ s/$_->[1]$//;
    94         push @{$nspat}, [qr/\Q$n\E($_->[1]|$)/, $_->[1]];
    95         push @{$nspat}, [ qr/\Q$n\E($_->[1]|$)/, $_->[1] ];
    95     }
    96     }
    96 
    97 
    97     if ( $Cf->add ) { _modify() }
    98     if    ( $Cf->add )    { _modify() }
    98     elsif ( $Cf->delete ) { $Cf->acl('delete') ; _modify() }
    99     elsif ( $Cf->delete ) { $Cf->acl('delete'); _modify() }
    99     elsif ( $Cf->list )   { _list() }
   100     elsif ( $Cf->list )   { _list() }
   100     elsif ( $Cf->modify ) { _modify() }
   101     elsif ( $Cf->modify ) { _modify() }
   101     else { die "Need action (--add|--delete|--list|--modify)\n" }
   102     else { die "Need action (--add|--delete|--list|--modify)\n" }
   102 
   103 
   103 }
   104 }
   108     # dn: uid=USER,...
   109     # dn: uid=USER,...
   109     my @users;
   110     my @users;
   110     @ARGV or die "Need user(s)\n";
   111     @ARGV or die "Need user(s)\n";
   111     $Cf->folder ~~ [] and die "Need folders(s)\n";
   112     $Cf->folder ~~ [] and die "Need folders(s)\n";
   112     $Cf->acl or die "Need acl\n";
   113     $Cf->acl or die "Need acl\n";
   113     $Cf->recursive and $Cf->create and die "Use either --recursive or --create but not both\n";
   114     $Cf->recursive
       
   115       and $Cf->create
       
   116       and die "Use either --recursive or --create but not both\n";
   114 
   117 
   115     my $r = $ldap->search(
   118     my $r = $ldap->search(
   116         base   => $ubase,
   119         base   => $ubase,
   117         filter => "(|" . join( "", map { "(uid=$_)" } @ARGV ) . ")"
   120         filter => "(|" . join( "", map { "(uid=$_)" } @ARGV ) . ")"
   118     );
   121     );
   119     die $r->error if $r->code;
   122     die $r->error if $r->code;
   120     unless ($r->count) {
   123     unless ( $r->count ) {
   121         prompt('No matching user found in ldap. Continue? (y/N)', "n\n") =~ /y/i or exit 0;
   124         prompt( 'No matching user found in ldap. Continue? (y/N)', "n\n" ) =~
       
   125           /y/i
       
   126           or exit 0;
   122         @users = @ARGV;
   127         @users = @ARGV;
   123     }
   128     }
   124 
   129 
   125     while (my $e = ($r->shift_entry or shift @users)) {
   130     while ( my $e = ( $r->shift_entry or shift @users ) ) {
   126 
   131 
   127         my ($user, $dn);
   132         my ( $user, $dn );
   128 
   133 
   129         if (ref $e eq 'Net::LDAP::Entry') {
   134         if ( ref $e eq 'Net::LDAP::Entry' ) {
   130             $user = $e->get_value("uid");
   135             $user = $e->get_value("uid");
   131             $dn   = $e->dn;
   136             $dn   = $e->dn;
   132         } else {
   137         } else {
   133             $user = $e;
   138             $user = $e;
   134             $dn   = '[dn not available]';
   139             $dn   = '[dn not available]';
   136 
   141 
   137         my $modified = 0;
   142         my $modified = 0;
   138         verbose "$user:\n";
   143         verbose "$user:\n";
   139         verbose "\t$dn...\n";
   144         verbose "\t$dn...\n";
   140 
   145 
   141         for my $folder (@{$Cf->folder}) {
   146         for my $folder ( @{ $Cf->folder } ) {
   142 
   147 
   143             $imap->create($folder) or die "Can't create folder '$folder': $@" if $Cf->create;
   148             $imap->create($folder)
   144 
   149               or die "Can't create folder '$folder': $@"
   145             for my $f (@{acl_folders($folder)}) {
   150               if $Cf->create;
   146 
   151 
   147                 if ($Cf->acl eq 'delete') {
   152             for my $f ( @{ acl_folders($folder) } ) {
   148                     $imap->deleteacl($f, $user) or die "Can't delete acl: $@";
   153 
       
   154                 if ( $Cf->acl eq 'delete' ) {
       
   155                     $imap->deleteacl( $f, $user ) or die "Can't delete acl: $@";
   149                     verbose "\t$f: none\n";
   156                     verbose "\t$f: none\n";
   150                 } else {
   157                 } else {
   151                     $imap->setacl($f, $user, $Cf->acl) or die "Can't set acl: $@";
   158                     $imap->setacl( $f, $user, $Cf->acl )
       
   159                       or die "Can't set acl: $@";
   152                     verbose "\t$f: " . $Cf->acl . "\n";
   160                     verbose "\t$f: " . $Cf->acl . "\n";
   153                 }
   161                 }
   154 
   162 
   155             }
   163             }
   156 
   164 
   157         }
   165         }
       
   166 
   158         # Fix: iusMailOptions wurde erst später eingeführt, bei Bedarf also hinzufügen
   167         # Fix: iusMailOptions wurde erst später eingeführt, bei Bedarf also hinzufügen
   159         #if (!grep /^iusMailOptions$/, @{$e->get("objectClass")}) {
   168         #if (!grep /^iusMailOptions$/, @{$e->get("objectClass")}) {
   160         #$e->add(objectClass => "iusMailOptions");
   169         #$e->add(objectClass => "iusMailOptions");
   161         #}
   170         #}
   162 
   171 
   176 
   185 
   177     #@ARGV = ("*") unless @ARGV;
   186     #@ARGV = ("*") unless @ARGV;
   178 
   187 
   179     die "option acl_admin required\n" unless $Cf->acl_admin;
   188     die "option acl_admin required\n" unless $Cf->acl_admin;
   180 
   189 
   181     if ($Cf->aclgroups) {
   190     if ( $Cf->aclgroups ) {
   182 
   191 
   183         warn "--folder option ignored when listing groups" unless $Cf->folder ~~ [];
   192         warn "--folder option ignored when listing groups"
       
   193           unless $Cf->folder ~~ [];
   184         list_groups(@ARGV);
   194         list_groups(@ARGV);
   185 
   195 
   186     } elsif (@ARGV) {
   196     } elsif (@ARGV) {
   187 
   197 
   188 #        my $uid = $ARGV[0];
   198         #        my $uid = $ARGV[0];
   189 #        # searching by more than use user may be too expensive
   199         #        # searching by more than use user may be too expensive
   190 #        die "Searching by more than one user not supported" unless @ARGV == 1 or $uid =~ /\*/;
   200         #        die "Searching by more than one user not supported" unless @ARGV == 1 or $uid =~ /\*/;
   191         #list_by_user($_) for @ARGV;
   201         #list_by_user($_) for @ARGV;
   192 
   202 
   193         warn "--folder option ignored when listing by user" unless $Cf->folder ~~ [];
   203         warn "--folder option ignored when listing by user"
   194         list_by_user($imap, @ARGV);
   204           unless $Cf->folder ~~ [];
   195 
   205         list_by_user( $imap, @ARGV );
   196     } elsif (not $Cf->folder ~~ []) {
   206 
   197 
   207     } elsif ( not $Cf->folder ~~ [] ) {
   198         list_by_folder($_) for @{$Cf->folder};
   208 
       
   209         list_by_folder($_) for @{ $Cf->folder };
   199 
   210 
   200     } else {
   211     } else {
   201 
   212 
   202         die "Need either user or --folder. If you really want to search all users then supply the pattern '*'.";
   213         die
       
   214           "Need either user or --folder. If you really want to search all users then supply the pattern '*'.";
   203 
   215 
   204     }
   216     }
   205 
   217 
   206 }
   218 }
   207 
   219 
   208 sub list_groups(@) {
   220 sub list_groups(@) {
   209 
   221 
   210     @_ = ('*') unless @_;
   222     @_ = ('*') unless @_;
   211     my @ag = split ',', $Cf->imap_aclgroups;
   223     my @ag = split ',', $Cf->imap_aclgroups;
   212     my $ag_att = AT_ACLGROUPS;
   224     my $ag_att = AT_ACLGROUPS;
   213     my $filter = "(&($ag_att=*)"
   225     my $filter =
   214                . "(|" . join( "", map { "(uid=$_)" } @_ ) . "))";
   226       "(&($ag_att=*)" . "(|" . join( "", map { "(uid=$_)" } @_ ) . "))";
   215     my $r = $ldap->search(
   227     my $r = $ldap->search(
   216         attrs  => ['uid', AT_ACLGROUPS],
   228         attrs  => [ 'uid', AT_ACLGROUPS ],
   217         filter => $filter,
   229         filter => $filter,
   218         base   => $ubase,
   230         base   => $ubase,
   219     );
   231     );
   220     die $r->error if $r->code;
   232     die $r->error if $r->code;
   221 
   233 
   222     unless ($r->count) {
   234     unless ( $r->count ) {
   223         print ("No aclgroups found in ldap\n");
   235         print("No aclgroups found in ldap\n");
   224         exit 0;
   236         exit 0;
   225     }
   237     }
   226 
   238 
   227     my $users;
   239     my $users;
   228     while (my $e = ($r->shift_entry)) {
   240     while ( my $e = ( $r->shift_entry ) ) {
   229         my $uid = $e->get_value('uid');
   241         my $uid = $e->get_value('uid');
   230         my @ag_cur = split ',', $e->get_value($ag_att);
   242         my @ag_cur = split ',', $e->get_value($ag_att);
   231         for (@ag) {
   243         for (@ag) {
   232             $users->{$_} = defined $users->{$_}
   244             $users->{$_} =
   233             ? [@{$users->{$_}}, $uid]
   245               defined $users->{$_}
   234             : [ $uid ]
   246               ? [ @{ $users->{$_} }, $uid ]
   235             if $_ ~~ @ag_cur
   247               : [$uid]
   236         }
   248               if $_ ~~ @ag_cur;
   237     }
   249         }
   238 
   250     }
   239     print "$_:\n\t", join("\n\t", @{$users->{$_}}), "\n\n" for keys %{$users};
   251 
       
   252     print "$_:\n\t", join( "\n\t", @{ $users->{$_} } ), "\n\n"
       
   253       for keys %{$users};
   240 
   254 
   241 }
   255 }
   242 
   256 
   243 sub list_by_user($@) {
   257 sub list_by_user($@) {
   244 
   258 
   245     my $imap = shift;
   259     my $imap = shift;
   246     my $filter = "(|" . join( "", map { "(uid=$_)" } @_ ) . ")";
   260     my $filter = "(|" . join( "", map { "(uid=$_)" } @_ ) . ")";
       
   261 
   247     #my $filter = "(uid=$uid)";
   262     #my $filter = "(uid=$uid)";
   248     my $r = $ldap->search(
   263     my $r = $ldap->search(
   249         filter => $filter,
   264         filter => $filter,
   250         base   => $ubase,
   265         base   => $ubase,
   251     );
   266     );
   252     die $r->error if $r->code;
   267     die $r->error if $r->code;
   253     my @users;
   268     my @users;
   254     unless ($r->count) {
   269     unless ( $r->count ) {
   255         verbose("No matching users found in ldap.\n");
   270         verbose("No matching users found in ldap.\n");
   256         @users = @_;
   271         @users = @_;
   257     }
   272     }
   258 
   273 
   259     while (my $e = ($r->shift_entry or shift @users)) {
   274     while ( my $e = ( $r->shift_entry or shift @users ) ) {
   260 
   275 
   261         my ($uid, $cn, $mr);
   276         my ( $uid, $cn, $mr );
   262         if (ref $e eq 'Net::LDAP::Entry') {
   277         if ( ref $e eq 'Net::LDAP::Entry' ) {
   263             $uid  = $e->get_value("uid");
   278             $uid = $e->get_value("uid");
   264             $cn   = join( ", ", $e->get_value("cn") );
   279             $cn  = join( ", ", $e->get_value("cn") );
   265             $mr   = $e->get_value(AT_PRIMARYADDRESS) || "";                  # ??
   280             $mr  = $e->get_value(AT_PRIMARYADDRESS) || "";    # ??
   266         } else {
   281         } else {
   267             $uid = $e;
   282             $uid = $e;
   268             $cn  = '[cn not available]';
   283             $cn  = '[cn not available]';
   269             $mr  = '[address not available]';
   284             $mr  = '[address not available]';
   270         }
   285         }
   273 
   288 
   274         #if (($e->get_value("iusRestrictedMail")||"") eq "TRUE") {
   289         #if (($e->get_value("iusRestrictedMail")||"") eq "TRUE") {
   275         #print " INTERNAL";
   290         #print " INTERNAL";
   276         #}
   291         #}
   277 
   292 
   278         die "IMAP Server does not advertise acl support" unless $imap->capability->{acl};
   293         die "IMAP Server does not advertise acl support"
       
   294           unless $imap->capability->{acl};
       
   295 
   279         # namespace() result looks like this
   296         # namespace() result looks like this
   280         # [
   297         # [
   281         #   [   # list of private namespace(s)
   298         #   [   # list of private namespace(s)
   282         #       [
   299         #       [
   283         #           prefix,
   300         #           prefix,
   298         #       ],
   315         #       ],
   299         #       ...
   316         #       ...
   300         #   ]
   317         #   ]
   301         my $hasacl;
   318         my $hasacl;
   302         my $ns = $imap->namespace() or die "No public namespaces available: $@";
   319         my $ns = $imap->namespace() or die "No public namespaces available: $@";
       
   320 
   303         # uns interessieren nur 'public' namespaces
   321         # uns interessieren nur 'public' namespaces
   304         for my $n (@{$ns->[2]}) {
   322         for my $n ( @{ $ns->[2] } ) {
   305 
   323 
   306             my $folders = imap_rlist( '', $n->[0], $n->[1] );
   324             my $folders = imap_rlist( '', $n->[0], $n->[1] );
   307             for my $f ( @{$folders} ) {
   325             for my $f ( @{$folders} ) {
   308 
   326 
   309                 #next if '\\Noselect' ~~ $f->[0];
   327                 #next if '\\Noselect' ~~ $f->[0];
   310                 my $perms = $imap->getacl( $f ) or die "Can't getacl '$f': $@";
   328                 my $perms = $imap->getacl($f) or die "Can't getacl '$f': $@";
   311                 my ($u, $p);
   329                 my ( $u, $p );
   312                 while ($u = shift @{$perms} and $p = shift @{$perms}) { 
   330                 while ( $u = shift @{$perms} and $p = shift @{$perms} ) {
   313                     next unless $u eq $uid;
   331                     next unless $u eq $uid;
   314                     $hasacl = 1;
   332                     $hasacl = 1;
   315                     print "\t$f: $u [$p]\n";
   333                     print "\t$f: $u [$p]\n";
   316                 }
   334                 }
   317 
   335 
   318             }
   336             }
   319 
   337 
   320         }
   338         }
   321 
   339 
   322         print "\tno acl found on listable folders in shared namespaces\n" unless $hasacl;
   340         print "\tno acl found on listable folders in shared namespaces\n"
       
   341           unless $hasacl;
   323         print "\n";
   342         print "\n";
   324 
   343 
   325     }
   344     }
   326 
   345 
   327 }
   346 }
   328 
   347 
   329 sub list_by_folder($) {
   348 sub list_by_folder($) {
   330 
   349 
   331     my ($folder) = @_;
   350     my ($folder) = @_;
   332 
   351 
   333     for my $f ( @{acl_folders($folder)} ) {
   352     for my $f ( @{ acl_folders($folder) } ) {
   334 
   353 
   335         my $hasacl;
   354         my $hasacl;
   336         print "$f\n";
   355         print "$f\n";
   337 
   356 
   338         my $perms = $imap->getacl( $f ) or die $@;
   357         my $perms = $imap->getacl($f) or die $@;
   339         my ($u, $p);
   358         my ( $u, $p );
   340         while ($u = shift @{$perms} and $p = shift @{$perms}) {
   359         while ( $u = shift @{$perms} and $p = shift @{$perms} ) {
   341 
   360 
   342             # '#user' will be listed when we have a global acl for 'user'
   361             # '#user' will be listed when we have a global acl for 'user'
   343             my $gl = $u =~ /^#/ ? ' [global acl]' : '';
   362             my $gl = $u =~ /^#/  ? ' [global acl]' : '';
   344             my $gr = $u =~ /^\$/ ? ' [group acl]' : '';
   363             my $gr = $u =~ /^\$/ ? ' [group acl]'  : '';
   345             $hasacl = 1;
   364             $hasacl = 1;
   346             print "\t$u [$p]$gr$gl\n";
   365             print "\t$u [$p]$gr$gl\n";
   347         }
   366         }
   348 
   367 
   349         print "\tno acl found\n" unless $hasacl;
   368         print "\tno acl found\n" unless $hasacl;
   380     }
   399     }
   381 }
   400 }
   382 
   401 
   383 sub imap_list($$) {
   402 sub imap_list($$) {
   384 
   403 
   385     my ($ref, $folder) = @_;
   404     my ( $ref, $folder ) = @_;
   386 
   405 
   387     my $list = $imap->list($ref, $folder) or die "Can't list('$ref', '$folder'): $@";
   406     my $list = $imap->list( $ref, $folder )
       
   407       or die "Can't list('$ref', '$folder'): $@";
       
   408 
   388     # single folder sieht wie folgt aus: [[flag1, flag2, ...], separator, foldername]
   409     # single folder sieht wie folgt aus: [[flag1, flag2, ...], separator, foldername]
   389     ref $list and return [ map $_->[2], @{$list} ];
   410     ref $list and return [ map $_->[2], @{$list} ];
       
   411 
   390     # assuming empty result list otherwise
   412     # assuming empty result list otherwise
   391     return [];
   413     return [];
   392 
   414 
   393 }
   415 }
   394 
   416 
   395 sub imap_rlist($$$) {
   417 sub imap_rlist($$$) {
   396 
   418 
   397     my ($ref, $folder, $sep) = @_;
   419     my ( $ref, $folder, $sep ) = @_;
   398     $folder =~ s/$sep+$//;
   420     $folder =~ s/$sep+$//;
   399 
   421 
   400     my $list = imap_list($ref, $folder);
   422     my $list = imap_list( $ref, $folder );
   401     push @{$list}, @{imap_list($ref, "$folder$sep*")} if $Cf->recursive;
   423     push @{$list}, @{ imap_list( $ref, "$folder$sep*" ) } if $Cf->recursive;
   402     return $list;
   424     return $list;
   403 }
   425 }
   404 
   426 
   405 sub acl_folders($) {
   427 sub acl_folders($) {
   406 
   428 
   407     my ($f) = @_;
   429     my ($f) = @_;
   408     my $folders;
   430     my $folders;
   409 
   431 
   410     for my $np (@{$nspat}) {
   432     for my $np ( @{$nspat} ) {
       
   433 
   411         # don't modify $f!
   434         # don't modify $f!
   412         (my $ft = $f) =~ s/$np->[1]$//;
   435         ( my $ft = $f ) =~ s/$np->[1]$//;
   413         return imap_rlist('', $f, $np->[1]) if ($ft =~ /$np->[0]/);
   436         return imap_rlist( '', $f, $np->[1] ) if ( $ft =~ /$np->[0]/ );
   414     }
   437     }
   415 
   438 
   416     die "Foldername '$f' must begin with the name of a shared namespace\n";
   439     die "Foldername '$f' must begin with the name of a shared namespace\n";
   417 
   440 
   418 }
   441 }
   419 
   442 
   420 sub prompt($$) {
   443 sub prompt($$) {
   421     my ($prompt, $default) = @_;
   444     my ( $prompt, $default ) = @_;
   422     print $prompt, substr($default, 0, 1), "\b";
   445     print $prompt, substr( $default, 0, 1 ), "\b";
   423     ReadMode 4; my $r = ReadKey(0); ReadMode 0;
   446     ReadMode 4;
   424     if ($r eq "\n") { $r = $default }
   447     my $r = ReadKey(0);
   425     else { $r .= substr($default, 1) }
   448     ReadMode 0;
       
   449     if ( $r eq "\n" ) { $r = $default }
       
   450     else              { $r .= substr( $default, 1 ) }
   426     print $r;
   451     print $r;
   427     return $r;
   452     return $r;
   428 }
   453 }
   429 
   454 
   430 1;
   455 1;