diff -r c7c3cac0a89b -r 5e9d46863588 group.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/group.pm Fri Dec 09 06:05:38 2005 +0000 @@ -0,0 +1,292 @@ +package group; +# © Heiko Schlittermann +# $Id$ +# $URL$ + +use strict; +use warnings; +use Net::LDAP; +use Net::LDAP::Constant qw(LDAP_ALREADY_EXISTS LDAP_NO_SUCH_OBJECT LDAP_TYPE_OR_VALUE_EXISTS); +use Net::LDAP::Entry; +use Cyrus::IMAP::Admin; +use Text::Wrap; +use password; + + +my $Cf; +my ($ldap, $ubase, $abase, $gbase); +my ($imap); +END { $imap and $imap = undef; }; + + +sub _add(); +sub _list(); +sub _delete(); +sub uniq(@); +sub verbose(@); + +sub OU_ACCOUNTS(); +sub OU_ALIASES(); +sub OU_GROUPS(); +sub OC_RECIPIENT(); +sub OC_ACCESSGROUP(); +sub AT_ADDRESS(); +sub AT_PRIMARYADDRESS(); +sub AT_GROUP(); +sub AT_FORWARDINGADDRESS(); +sub AT_MEMBERUID(); + +sub import(@) { + $Cf = shift; + + require constant; + import constant OU_ACCOUNTS => $Cf->ldap_ou_accounts; + import constant OU_ALIASES => $Cf->ldap_ou_aliases; + import constant OU_GROUPS => $Cf->ldap_ou_groups; + import constant OC_RECIPIENT => $Cf->ldap_oc_recipient; + import constant OC_ACCESSGROUP => $Cf->ldap_oc_accessgroup; + import constant AT_PRIMARYADDRESS => $Cf->ldap_at_primaryaddress; + import constant AT_ADDRESS => $Cf->ldap_at_address; + import constant AT_GROUP => $Cf->ldap_at_group; + import constant AT_FORWARDINGADDRESS => $Cf->ldap_at_forwardingaddress; + import constant AT_MEMBERUID => "memberUid"; + + $gbase = OU_GROUPS . "," . $Cf->ldap_base; + $ubase = OU_ACCOUNTS . "," . $Cf->ldap_base; + $abase = OU_ALIASES . "," . $Cf->ldap_base; +} + +sub run($) { + # Eigentlich brauchen wir für alles imap und ldap + $ldap = new Net::LDAP $Cf->ldap_server or die; + my $r = $ldap->bind($Cf->ldap_bind_dn, + password => $Cf->ldap_password || $ENV{LDAP_PASS} || password::ask("LDAP (". $Cf->ldap_bind_dn .") password: ")); + die $r->error, "\n" if $r->code; + + $imap = new Cyrus::IMAP::Admin or die $@; + $imap->authenticate(-server => $Cf->imap_server, -user => $Cf->imap_admin, + -password => $Cf->imap_password || $ENV{IMAP_PASS} || password::ask("IMAP (". $Cf->imap_admin .") password: ")) + or die $@; + + + if ($Cf->list) { _list() } + elsif ($Cf->add) { _add() } + elsif ($Cf->delete) { _delete() } + elsif ($Cf->modify) { _modify() } + else { die "Need action (--add|--modify|--list|--delete)\n" }; + +} + +sub _add() { +# Beim Hinzufügen tragen wir nur das unbedingt notwendige +# ein. Wenn es schon eine mailPrimaryAddress gibt oder eine +# mail, machen wir gar nichts. +# Ansonsten: +# uid wird hinzugefügt +# cn, sn bleiben unangetastet +# Wenn die mailbox-Option gesetzt ist, wird die +# IMAP-Mailbox angelegt. + + + die "Need group name for creation\n" if not @ARGV; + my $group = shift @ARGV; + my @members = split /,/, $Cf->members||""; + + + my $dn = "cn=$group,$gbase"; + my $r; + + verbose("$group:\n"); + + verbose("\t$dn..."); + + $r = $ldap->search(base => $gbase, filter => "(cn=$group)"); + + die $r->error if $r->code; + die "entries not expected" if $r->count > 1; + + my $e; + if ($r->count) { + $e = $r->shift_entry; + } else { + $e = new Net::LDAP::Entry; + # Jetzt eine neue ID finden + foreach ($Cf->gid_min .. $Cf->gid_max) { + # ist einfach eine lineare Suche, im Augenblick weiß ich nichts + # clevereres + my $r = $ldap->search(base => $gbase, + filter => "(gidNumber=$_)", + attrs => []); + if ($r->count == 0) { + $e->add(gidNumber => $_); + last; + } + } + $e->dn($dn); + $e->add(cn => $group); + } + + grep /^CYRUS MAIL ACCESS GROUP/, $e->get("description") or $e->add(description => "CYRUS MAIL ACCESS GROUP"); + + if (defined $Cf->description) { + my @d = map { s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg; $_ } $e->get("description"); + $e->replace(description => \@d); + } + + $e->replace(objectClass => [uniq $e->get("objectClass"), OC_ACCESSGROUP, "posixGroup"]); + $e->replace(AT_MEMBERUID => [uniq $e->get(AT_MEMBERUID), @members]) if @members; + + + $r = $e->update($ldap); + die $r->error if $r->code; + + verbose("ok"); + verbose("\n"); +} + +sub _modify() { +# Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt: +# dn: uid=USER,... + my (@groups) = @ARGV or die "Need groupname(s)\n"; + + my $r = $ldap->search(base => $gbase, + filter => $_ = "(&(objectClass=".OC_ACCESSGROUP.")(|" . join("", map { "(cn=$_)" } @ARGV) . "))"); + die $r->error if $r->code; + die "No entries found.\n" if $r->count == 0; + + while (my $e = $r->shift_entry) { + my $r; + + my $group = $e->get_value("cn"); + my $dn = $e->dn; + + my $modified = 0; + verbose "$group:"; + + verbose "\n\t$dn..."; + + if (defined $Cf->members) { + my @m = split /,/, $Cf->members; + grep { /^[+-]/ } @m or $e->delete(AT_MEMBERUID) + if $e->get_value(AT_MEMBERUID); + + foreach my $m (@m) { + if ($m =~ s/^-//) { + $e->delete((AT_MEMBERUID) => [$m]) + } else { + $m =~ s/^\+//; + $e->add((AT_MEMBERUID) => [$m]) + } + } + $modified++; + } + + if (defined $Cf->description) { + my @d = map { s/^(CYRUS MAIL ACCESS GROUP).*/"$1: ".$Cf->description/eg; $_ } $e->get("description"); + $e->replace(description => \@d); + $modified++; + } + + $e->dump if $Cf->debug; + + if ($modified) { + $r = $e->update($ldap); + die $r->error.$r->code if $r->code; + } + + verbose "ok\n"; + + print "\n"; + } +} + +sub _delete() { +# Wir gehen davon aus, daß es einen dn uid=USER,ou=.... gibt, den wir löschen können. +# Wir löschen den kompletten Container. Es kann natürlich sein, daß er noch jemand anders gehört. +# Dann ist das Pech. Um es besser zu haben, müßten wir für alles unsere eigenen +# Objektklassen haben... + + if (!@ARGV) { + print "Group: "; + chomp($_ = <>); + @ARGV = ($_); + } + + #my $filter = "(&((cn=%s)(objectClass=".OC_ACCESSGROUP.")))"; + + my $r = $ldap->search(base => $gbase, + filter => "(&(objectClass=".OC_ACCESSGROUP.")(|" . join("", map { "(cn=$_)" } @ARGV) . "))", + attrs => [AT_MEMBERUID, "cn"]); + + if ($r->count == 0) { + verbose "No objects found\n"; + return; + } + + while (my $e = $r->shift_entry) { + my $dn = $e->dn; + verbose $dn; + my $r = $ldap->delete($dn); + + if ($r->code == LDAP_NO_SUCH_OBJECT) { + verbose("doesn't exist"); + } elsif ($r->code == 0) { + verbose(" ok"); + } else { + die $r->error; + } + verbose("\n"); + } +} + +sub _list() { + my $filter; + @ARGV = ("*") unless @ARGV; + #$filter = "(|" . join("", map { "(uid=$_)" } @ARGV) . ")"; + $filter = "(objectClass=".OC_ACCESSGROUP.")"; + + my $r = $ldap->search( + filter => $filter, + base => $gbase, + attrs => [AT_MEMBERUID, qw/cn description/], + ); + die $r->error if $r->code; + + + while (my $e = $r->shift_entry) { + my $cn = $e->get_value("cn"); + my $descr = $e->get_value("description"); + my @uids = $e->get_value(AT_MEMBERUID); + + print "$cn: ($descr)\n"; + print "\t", join "\n\t", @uids; + print "\n"; + } +} + +sub verbose(@) { + printf STDERR @_; +} + +sub uniq(@) { + my %x; + @x{@_} = (); + return keys %x; +} + +{ my @pw; +sub _mkpw($) { + my $in = $_[0]; + + return $in unless $in and $in eq "{pwgen}"; + + if (!@pw) { + chomp(@pw = `pwgen 8 10 2>/dev/null|| mkpasswd`); + die "pwgen/mkpasswd: $!" if $?; + } + return shift @pw; + +} } + +1; +# vim:sts=4 sw=4 aw ai sm nohlsearch: