group.pm
branchfoerste
changeset 48 36aca6fb0ab8
parent 44 35441e1ae083
child 75 63b7c7fcd0cb
equal deleted inserted replaced
47:05d5ada37387 48:36aca6fb0ab8
     1 package group;
     1 package group;
       
     2 
     2 # © Heiko Schlittermann
     3 # © Heiko Schlittermann
     3 # $Id$
     4 # $Id$
     4 # $URL$
     5 # $URL$
     5 
     6 
     6 use strict;
     7 use strict;
     7 use warnings;
     8 use warnings;
     8 use Net::LDAP;
     9 use Net::LDAP;
     9 use Net::LDAP::Constant qw(LDAP_ALREADY_EXISTS LDAP_NO_SUCH_OBJECT LDAP_TYPE_OR_VALUE_EXISTS);
    10 use Net::LDAP::Constant
       
    11   qw(LDAP_ALREADY_EXISTS LDAP_NO_SUCH_OBJECT LDAP_TYPE_OR_VALUE_EXISTS);
    10 use Net::LDAP::Entry;
    12 use Net::LDAP::Entry;
       
    13 
    11 #use Cyrus::IMAP::Admin;
    14 #use Cyrus::IMAP::Admin;
    12 use Text::Wrap;
    15 use Text::Wrap;
    13 use password;
    16 use password;
    14 
    17 
    15 
       
    16 my $Cf;
    18 my $Cf;
    17 my ($ldap, $ubase, $abase, $gbase);
    19 my ( $ldap, $ubase, $abase, $gbase );
    18 my ($imap);
    20 my ($imap);
    19 END { $imap and $imap = undef; };
    21 END { $imap and $imap = undef; }
    20 
       
    21 
    22 
    22 sub _add();
    23 sub _add();
    23 sub _list();
    24 sub _list();
    24 sub _delete();
    25 sub _delete();
    25 sub uniq(@);
    26 sub uniq(@);
    38 
    39 
    39 sub import(@) {
    40 sub import(@) {
    40     $Cf = shift;
    41     $Cf = shift;
    41 
    42 
    42     require constant;
    43     require constant;
    43     import constant OU_ACCOUNTS => $Cf->ldap_ou_accounts;
    44     import constant OU_ACCOUNTS          => $Cf->ldap_ou_accounts;
    44     import constant OU_ALIASES => $Cf->ldap_ou_aliases;
    45     import constant OU_ALIASES           => $Cf->ldap_ou_aliases;
    45     import constant OU_GROUPS => $Cf->ldap_ou_groups;
    46     import constant OU_GROUPS            => $Cf->ldap_ou_groups;
    46     import constant OC_RECIPIENT => $Cf->ldap_oc_recipient;
    47     import constant OC_RECIPIENT         => $Cf->ldap_oc_recipient;
    47     import constant OC_ACCESSGROUP => $Cf->ldap_oc_accessgroup;
    48     import constant OC_ACCESSGROUP       => $Cf->ldap_oc_accessgroup;
    48     import constant AT_PRIMARYADDRESS => $Cf->ldap_at_primaryaddress;
    49     import constant AT_PRIMARYADDRESS    => $Cf->ldap_at_primaryaddress;
    49     import constant AT_ADDRESS => $Cf->ldap_at_address;
    50     import constant AT_ADDRESS           => $Cf->ldap_at_address;
    50     import constant AT_GROUP => $Cf->ldap_at_group;
    51     import constant AT_GROUP             => $Cf->ldap_at_group;
    51     import constant AT_FORWARDINGADDRESS => $Cf->ldap_at_forwardingaddress;
    52     import constant AT_FORWARDINGADDRESS => $Cf->ldap_at_forwardingaddress;
    52     import constant AT_MEMBERUID => "memberUid";
    53     import constant AT_MEMBERUID         => "memberUid";
    53 
    54 
    54     $gbase = OU_GROUPS . "," . $Cf->ldap_base;
    55     $gbase = OU_GROUPS . "," . $Cf->ldap_base;
    55     $ubase = OU_ACCOUNTS . "," . $Cf->ldap_base;
    56     $ubase = OU_ACCOUNTS . "," . $Cf->ldap_base;
    56     $abase = OU_ALIASES . "," . $Cf->ldap_base;
    57     $abase = OU_ALIASES . "," . $Cf->ldap_base;
    57 }
    58 }
    58 
    59 
    59 sub run($) {
    60 sub run($) {
       
    61 
    60     # Eigentlich brauchen wir für alles imap und ldap
    62     # Eigentlich brauchen wir für alles imap und ldap
    61     $ldap = new Net::LDAP $Cf->ldap_server or die;
    63     $ldap = new Net::LDAP $Cf->ldap_server or die;
    62     my $r = $ldap->bind($Cf->ldap_bind_dn,
    64     my $r = $ldap->bind( $Cf->ldap_bind_dn,
    63 	password => $Cf->ldap_password || $ENV{LDAP_PASS} || password::ask("LDAP (". $Cf->ldap_bind_dn .") password: "));
    65              password => $Cf->ldap_password
       
    66           || $ENV{LDAP_PASS}
       
    67           || password::ask( "LDAP (" . $Cf->ldap_bind_dn . ") password: " ) );
    64     die $r->error, "\n" if $r->code;
    68     die $r->error, "\n" if $r->code;
    65 
    69 
    66     $imap = new Cyrus::IMAP::Admin or die $@;
    70     $imap = new Cyrus::IMAP::Admin or die $@;
    67     $imap->authenticate(-server => $Cf->imap_server, -user => $Cf->imap_admin, 
    71     $imap->authenticate(
    68 	-password => $Cf->imap_password || $ENV{IMAP_PASS} || password::ask("IMAP (". $Cf->imap_admin .") password: "))
    72         -server   => $Cf->imap_server,
    69     or die $@;
    73         -user     => $Cf->imap_admin,
    70 
    74         -password => $Cf->imap_password
    71 
    75           || $ENV{IMAP_PASS}
    72     if ($Cf->list) { _list() }
    76           || password::ask( "IMAP (" . $Cf->imap_admin . ") password: " )
    73     elsif ($Cf->add) { _add() }
    77     ) or die $@;
    74     elsif ($Cf->delete) { _delete() }
    78 
    75     elsif ($Cf->modify) { _modify() }
    79     if    ( $Cf->list )   { _list() }
    76     else { die "Need action (--add|--modify|--list|--delete)\n" };
    80     elsif ( $Cf->add )    { _add() }
       
    81     elsif ( $Cf->delete ) { _delete() }
       
    82     elsif ( $Cf->modify ) { _modify() }
       
    83     else { die "Need action (--add|--modify|--list|--delete)\n" }
    77 
    84 
    78 }
    85 }
    79 
    86 
    80 sub _add() {
    87 sub _add() {
    81 # Beim Hinzufügen tragen wir nur das unbedingt notwendige
    88 
    82 # ein.  Wenn es schon eine mailPrimaryAddress gibt oder eine
    89     # Beim Hinzufügen tragen wir nur das unbedingt notwendige
    83 # mail, machen wir gar nichts.
    90     # ein.  Wenn es schon eine mailPrimaryAddress gibt oder eine
    84 # Ansonsten:
    91     # mail, machen wir gar nichts.
    85 # uid wird hinzugefügt
    92     # Ansonsten:
    86 # cn, sn bleiben unangetastet
    93     # uid wird hinzugefügt
    87 # Wenn die mailbox-Option gesetzt ist, wird die
    94     # cn, sn bleiben unangetastet
    88 # IMAP-Mailbox angelegt.
    95     # Wenn die mailbox-Option gesetzt ist, wird die
    89 
    96     # IMAP-Mailbox angelegt.
    90 
    97 
    91     die "Need group name for creation\n" if not @ARGV;
    98     die "Need group name for creation\n" if not @ARGV;
    92     my $group = shift @ARGV;
    99     my $group = shift @ARGV;
    93     my @members = split /,/, $Cf->members||"";
   100     my @members = split /,/, $Cf->members || "";
    94 
       
    95 
   101 
    96     my $dn = "cn=$group,$gbase";
   102     my $dn = "cn=$group,$gbase";
    97     my $r;
   103     my $r;
    98 
   104 
    99     verbose("$group:\n");
   105     verbose("$group:\n");
   100 
   106 
   101     verbose("\t$dn...");
   107     verbose("\t$dn...");
   102 
   108 
   103     $r = $ldap->search(base => $gbase, filter => "(cn=$group)");
   109     $r = $ldap->search( base => $gbase, filter => "(cn=$group)" );
   104 
   110 
   105     die $r->error if $r->code;
   111     die $r->error if $r->code;
   106     die "entries not expected" if $r->count > 1;
   112     die "entries not expected" if $r->count > 1;
   107 
   113 
   108     my $e;
   114     my $e;
   109     if ($r->count) {
   115     if ( $r->count ) {
   110 	$e = $r->shift_entry;
   116         $e = $r->shift_entry;
   111     } else {
   117     } else {
   112 	$e = new Net::LDAP::Entry;
   118         $e = new Net::LDAP::Entry;
   113 	# Jetzt eine neue ID finden
   119 
   114 	foreach ($Cf->gid_min .. $Cf->gid_max) {
   120         # Jetzt eine neue ID finden
   115 	    # ist einfach eine lineare Suche, im Augenblick weiß ich nichts
   121         foreach ( $Cf->gid_min .. $Cf->gid_max ) {
   116 	    # clevereres
   122 
   117 	    my $r = $ldap->search(base => $gbase,
   123             # ist einfach eine lineare Suche, im Augenblick weiß ich nichts
   118 		filter => "(gidNumber=$_)",
   124             # clevereres
   119 		attrs => []);
   125             my $r = $ldap->search(
   120 	    if ($r->count == 0) {
   126                 base   => $gbase,
   121 		$e->add(gidNumber => $_);
   127                 filter => "(gidNumber=$_)",
   122 		last;
   128                 attrs  => []
   123 	    }
   129             );
   124 	}
   130             if ( $r->count == 0 ) {
   125 	$e->dn($dn);
   131                 $e->add( gidNumber => $_ );
   126 	$e->add(cn => $group);
   132                 last;
   127     }
   133             }
   128 
   134         }
   129     grep /^CYRUS MAIL ACCESS GROUP/, $e->get("description") or $e->add(description => "CYRUS MAIL ACCESS GROUP");
   135         $e->dn($dn);
   130 
   136         $e->add( cn => $group );
   131     if (defined $Cf->description) {
   137     }
   132 	my @d = map { s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg; $_ } $e->get("description");
   138 
   133 	$e->replace(description => \@d);
   139     grep /^CYRUS MAIL ACCESS GROUP/, $e->get("description")
   134     }
   140       or $e->add( description => "CYRUS MAIL ACCESS GROUP" );
   135 
   141 
   136     $e->replace(objectClass => [uniq $e->get("objectClass"), OC_ACCESSGROUP, "posixGroup"]);
   142     if ( defined $Cf->description ) {
   137     $e->replace((AT_MEMBERUID) => [uniq $e->get(AT_MEMBERUID), @members]) if @members;
   143         my @d =
       
   144           map { s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg; $_ }
       
   145           $e->get("description");
       
   146         $e->replace( description => \@d );
       
   147     }
       
   148 
       
   149     $e->replace( objectClass =>
       
   150           [ uniq $e->get("objectClass"), OC_ACCESSGROUP, "posixGroup" ] );
       
   151     $e->replace( (AT_MEMBERUID) => [ uniq $e->get(AT_MEMBERUID), @members ] )
       
   152       if @members;
   138 
   153 
   139     $r = $e->update($ldap);
   154     $r = $e->update($ldap);
   140     die $r->error if $r->code;
   155     die $r->error if $r->code;
   141 
   156 
   142     verbose("ok");
   157     verbose("ok");
   143     verbose("\n");
   158     verbose("\n");
   144 }
   159 }
   145 
   160 
   146 sub _modify() {
   161 sub _modify() {
   147 # Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt:
   162 
   148 # dn: uid=USER,...
   163     # Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt:
       
   164     # dn: uid=USER,...
   149     my (@groups) = @ARGV or die "Need groupname(s)\n";
   165     my (@groups) = @ARGV or die "Need groupname(s)\n";
   150 
   166 
   151     my $r = $ldap->search(base => $gbase, 
   167     my $r = $ldap->search(
   152 	filter => $_ = "(&(objectClass=".OC_ACCESSGROUP.")(|" . join("", map { "(cn=$_)" } @ARGV) . "))");
   168         base   => $gbase,
       
   169         filter => $_ =
       
   170             "(&(objectClass="
       
   171           . OC_ACCESSGROUP . ")(|"
       
   172           . join( "", map { "(cn=$_)" } @ARGV ) . "))"
       
   173     );
   153     die $r->error if $r->code;
   174     die $r->error if $r->code;
   154     die "No entries found.\n" if $r->count == 0;
   175     die "No entries found.\n" if $r->count == 0;
   155 
   176 
   156     while (my $e = $r->shift_entry) {
   177     while ( my $e = $r->shift_entry ) {
   157 	my $r;
   178         my $r;
   158 
   179 
   159 	my $group = $e->get_value("cn");
   180         my $group = $e->get_value("cn");
   160 	my $dn = $e->dn;
   181         my $dn    = $e->dn;
   161 
   182 
   162 	my $modified = 0;
   183         my $modified = 0;
   163 	verbose "$group:";
   184         verbose "$group:";
   164 
   185 
   165 	verbose "\n\t$dn...";
   186         verbose "\n\t$dn...";
   166 
   187 
   167 	if (defined $Cf->members) {
   188         if ( defined $Cf->members ) {
   168 	    my @m = split /,/, $Cf->members;
   189             my @m = split /,/, $Cf->members;
   169 	    grep { /^[+-]/ } @m or $e->delete(AT_MEMBERUID)
   190             grep { /^[+-]/ } @m
   170 		if $e->get_value(AT_MEMBERUID);
   191               or $e->delete(AT_MEMBERUID)
   171 
   192               if $e->get_value(AT_MEMBERUID);
   172 	    foreach my $m (@m) {
   193 
   173 		if ($m =~ s/^-//) {
   194             foreach my $m (@m) {
   174 		    $e->delete((AT_MEMBERUID) => [$m])
   195                 if ( $m =~ s/^-// ) {
   175 		} else {
   196                     $e->delete( (AT_MEMBERUID) => [$m] );
   176 		    $m =~ s/^\+//;
   197                 } else {
   177 		    $e->add((AT_MEMBERUID) => [$m])
   198                     $m =~ s/^\+//;
   178 		}
   199                     $e->add( (AT_MEMBERUID) => [$m] );
   179 	    }
   200                 }
   180 	    $modified++;
   201             }
   181 	}
   202             $modified++;
   182 
   203         }
   183 	if (defined $Cf->description) {
   204 
   184 	    my @d = map { s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg; $_ } $e->get("description");
   205         if ( defined $Cf->description ) {
   185 	    $e->replace(description => \@d);
   206             my @d = map {
   186 	    $modified++;
   207                 s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg;
   187 	}
   208                 $_
   188 
   209             } $e->get("description");
   189 	$e->dump if $Cf->debug;
   210             $e->replace( description => \@d );
   190 
   211             $modified++;
   191 	if ($modified) {
   212         }
   192 	    $r = $e->update($ldap);
   213 
   193 	    die $r->error.$r->code if $r->code;
   214         $e->dump if $Cf->debug;
   194 	}
   215 
   195 
   216         if ($modified) {
   196 	verbose "ok\n";
   217             $r = $e->update($ldap);
   197 
   218             die $r->error . $r->code if $r->code;
   198 	print "\n";
   219         }
       
   220 
       
   221         verbose "ok\n";
       
   222 
       
   223         print "\n";
   199     }
   224     }
   200 }
   225 }
   201 
   226 
   202 sub _delete() {
   227 sub _delete() {
   203 # Wir gehen davon aus, daß es einen dn uid=USER,ou=.... gibt, den wir löschen können.
   228 
   204 # Wir löschen den kompletten Container.  Es kann natürlich sein, daß er noch jemand anders gehört.  
   229     # Wir gehen davon aus, daß es einen dn uid=USER,ou=.... gibt, den wir löschen können.
   205 # Dann ist das Pech.  Um es besser zu haben, müßten wir für alles unsere eigenen
   230     # Wir löschen den kompletten Container.  Es kann natürlich sein, daß er noch jemand anders gehört.
   206 # Objektklassen haben...
   231     # Dann ist das Pech.  Um es besser zu haben, müßten wir für alles unsere eigenen
   207 
   232     # Objektklassen haben...
   208     if (!@ARGV) {
   233 
   209 	print "Group: ";
   234     if ( !@ARGV ) {
   210 	chomp($_ = <>);
   235         print "Group: ";
   211 	@ARGV = ($_);
   236         chomp( $_ = <> );
       
   237         @ARGV = ($_);
   212     }
   238     }
   213 
   239 
   214     #my $filter = "(&((cn=%s)(objectClass=".OC_ACCESSGROUP.")))";
   240     #my $filter = "(&((cn=%s)(objectClass=".OC_ACCESSGROUP.")))";
   215 
   241 
   216     my $r = $ldap->search(base => $gbase,
   242     my $r = $ldap->search(
   217 	filter => "(&(objectClass=".OC_ACCESSGROUP.")(|" . join("", map { "(cn=$_)" } @ARGV) . "))",
   243         base   => $gbase,
   218 	attrs => [AT_MEMBERUID, "cn"]); 
   244         filter => "(&(objectClass="
   219 
   245           . OC_ACCESSGROUP . ")(|"
   220     if ($r->count == 0) {
   246           . join( "", map { "(cn=$_)" } @ARGV ) . "))",
   221 	verbose "No objects found\n";
   247         attrs => [ AT_MEMBERUID, "cn" ]
   222 	return;
   248     );
   223     }
   249 
   224 
   250     if ( $r->count == 0 ) {
   225     while (my $e = $r->shift_entry) {
   251         verbose "No objects found\n";
   226 	my $dn = $e->dn;
   252         return;
   227 	verbose $dn;
   253     }
   228 	my $r = $ldap->delete($dn);
   254 
   229 
   255     while ( my $e = $r->shift_entry ) {
   230 	if ($r->code == LDAP_NO_SUCH_OBJECT) {
   256         my $dn = $e->dn;
   231 	    verbose("doesn't exist");
   257         verbose $dn;
   232 	} elsif ($r->code == 0) {
   258         my $r = $ldap->delete($dn);
   233 	    verbose(" ok");
   259 
   234 	} else {
   260         if ( $r->code == LDAP_NO_SUCH_OBJECT ) {
   235 	    die $r->error;
   261             verbose("doesn't exist");
   236 	}
   262         } elsif ( $r->code == 0 ) {
   237 	verbose("\n");
   263             verbose(" ok");
       
   264         } else {
       
   265             die $r->error;
       
   266         }
       
   267         verbose("\n");
   238     }
   268     }
   239 }
   269 }
   240 
   270 
   241 sub _list() {
   271 sub _list() {
   242     my $filter;
   272     my $filter;
   243     @ARGV = ("*") unless @ARGV;
   273     @ARGV = ("*") unless @ARGV;
       
   274 
   244     #$filter = "(|" . join("", map { "(uid=$_)" } @ARGV) . ")";
   275     #$filter = "(|" . join("", map { "(uid=$_)" } @ARGV) . ")";
   245     $filter = "(objectClass=".OC_ACCESSGROUP.")";
   276     $filter = "(objectClass=" . OC_ACCESSGROUP . ")";
   246 
   277 
   247     my $r = $ldap->search(
   278     my $r = $ldap->search(
   248 	filter => $filter,
   279         filter => $filter,
   249 	base => $gbase,
   280         base   => $gbase,
   250 	attrs => [AT_MEMBERUID, qw/cn description/],
   281         attrs  => [ AT_MEMBERUID, qw/cn description/ ],
   251     );
   282     );
   252     die $r->error if $r->code;
   283     die $r->error if $r->code;
   253 
   284 
   254 
   285     while ( my $e = $r->shift_entry ) {
   255     while (my $e = $r->shift_entry) {
   286         my $cn    = $e->get_value("cn");
   256 	my $cn = $e->get_value("cn");
   287         my $descr = $e->get_value("description");
   257 	my $descr = $e->get_value("description");
   288         my @uids  = $e->get_value(AT_MEMBERUID);
   258 	my @uids = $e->get_value(AT_MEMBERUID);
   289 
   259 
   290         print "$cn: ($descr)\n";
   260 	print "$cn: ($descr)\n";
   291         print "\t", join "\n\t", @uids;
   261 	print "\t", join "\n\t", @uids;
   292         print "\n";
   262 	print "\n";
       
   263     }
   293     }
   264 }
   294 }
   265 
   295 
   266 sub verbose(@) {
   296 sub verbose(@) {
   267     printf STDERR @_;
   297     printf STDERR @_;
   271     my %x;
   301     my %x;
   272     @x{@_} = ();
   302     @x{@_} = ();
   273     return keys %x;
   303     return keys %x;
   274 }
   304 }
   275 
   305 
   276 {   my @pw;
   306 {
   277 sub _mkpw($) {
   307     my @pw;
   278     my $in = $_[0];
   308 
   279 
   309     sub _mkpw($) {
   280     return $in unless $in and $in eq "{pwgen}";
   310         my $in = $_[0];
   281 
   311 
   282     if (!@pw) {
   312         return $in unless $in and $in eq "{pwgen}";
   283 	chomp(@pw = `pwgen 8 10 2>/dev/null|| mkpasswd`);
   313 
   284 	die "pwgen/mkpasswd: $!" if $?;
   314         if ( !@pw ) {
   285     }
   315             chomp( @pw = `pwgen 8 10 2>/dev/null|| mkpasswd` );
   286     return shift @pw;
   316             die "pwgen/mkpasswd: $!" if $?;
   287     
   317         }
   288 } }
   318         return shift @pw;
       
   319 
       
   320     }
       
   321 }
   289 
   322 
   290 1;
   323 1;
       
   324 
   291 # vim:sts=4 sw=4 aw ai sm nohlsearch:
   325 # vim:sts=4 sw=4 aw ai sm nohlsearch: