group.pm
changeset 8 5e9d46863588
child 12 a843f602e011
--- /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: