--- /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: