--- a/completion.bash Wed Dec 07 05:16:20 2005 +0000
+++ b/completion.bash Fri Dec 09 06:05:38 2005 +0000
@@ -6,8 +6,8 @@
local cur=${COMP_WORDS[COMP_CWORD]}
local pre=${COMP_WORDS[COMP_CWORD - 1]}
case "$COMP_CWORD" in
- 1) COMPREPLY=($(compgen -W "account alias shared" -- $cur));;
- 2) COMPREPLY=($(compgen -W "--add --del --list --mod" -- $cur));;
+ 1) COMPREPLY=($(compgen -W "account alias group shared" -- $cur));;
+ 2) COMPREPLY=($(compgen -W "--add --del --list --modify" -- $cur));;
esac
}
--- /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:
--- a/ma Wed Dec 07 05:16:20 2005 +0000
+++ b/ma Fri Dec 09 06:05:38 2005 +0000
@@ -31,8 +31,10 @@
--members=s List of Members [!$Cf->members!]
* shared mailbox options *
- # --access=s List of users having access
- [!$Cf->access!]
+
+ * group options *
+ --members=s List of Members [!$Cf->members!]
+ --description=s Descripton [!$Cf->description!]
Passwords for LDAP and IMAP can be read from environment LDAP_PASS resp. IMAP_PASS.
Options can be read from config file named in $MA_CONF [!$ENV{MA_CONF}!].
@@ -74,6 +76,8 @@
help => { ARGS => "!" },
debug => { ARGS => "!" },
+ description => { ARGS => "=s" },
+
# * account *
imap_server => { ARGS => "=s", DEFAULT => "localhost" },
@@ -90,18 +94,24 @@
fullname => { ARGS => "=s", ALIAS => "realname" },
address => { ARGS => "=s", ALIAS => "primary" },
- # * alias *
+ # * alias * group *
members => { ARGS => ":s" },
# * shared *
#access => { ARGS => ":s" },
+ # * group *
+ gid_min => { ARGS => "=i", DEFAULT => 60000 },
+ gid_max => { ARGS => "=i", DEFAULT => 60100 },
+
# * ldap intern *
ldap_ou_aliases => { ARGS => "=s", DEFAULT => "ou=MailAliases" },
ldap_ou_accounts => { ARGS => "=s", DEFAULT => "ou=MailAccounts" },
+ ldap_ou_groups => { ARGS => "=s", DEFAULT => "ou=Groups" },
ldap_oc_alias => { ARGS => "=s", DEFAULT => "XXXmailAlias" },
ldap_oc_recipient => { ARGS => "=s", DEFAULT => "XXXmailRecipient" },
+ ldap_oc_accessgroup => { ARGS => "=s", DEFAULT => "XXXmailAccessGroup" },
ldap_at_address => { ARGS => "=s", DEFAULT => "XXXmailAddress" },
ldap_at_group => { ARGS => "=s", DEFAULT => "XXXmailGroup" },
@@ -140,7 +150,7 @@
print help() and exit 0 if $Cf->help;
- @_ = grep { $_ =~ /^\Q$Module\E/ } qw/account alias shared/;
+ @_ = grep { $_ =~ /^\Q$Module\E/ } qw/account alias shared group/;
die "Need module. Try --help\n" if @_ == 0;
die "Module ambigous. (@_)\n" if @_ > 1;
@@ -156,6 +166,10 @@
require shared;
shared::import($Cf);
shared::run();
+ } elsif ($_[0] eq 'group') {
+ require group;
+ group::import($Cf);
+ group::run();
} else {
die "Shit";
}
--- a/ma.conf.ex Wed Dec 07 05:16:20 2005 +0000
+++ b/ma.conf.ex Fri Dec 09 06:05:38 2005 +0000
@@ -10,6 +10,7 @@
oc_recipient = XXXMailRecipient
oc_alias = XXXMailAlias
+oc_accessgroup = XXXMailAccessGroup
at_address = XXXMailAddress
at_forwardingaddress = XXXMailForwardingAddress
at_primaryaddress = XXXMailPrimaryAddress
--- a/manual.tex Wed Dec 07 05:16:20 2005 +0000
+++ b/manual.tex Fri Dec 09 06:05:38 2005 +0000
@@ -428,6 +428,23 @@
\T{ma \U{alias} \U{--delete} fam.ente}
\end{quote}
+ \subsection{Shared Mailboxen}
+
+ \subsubsection{Anzeigen/Anlegen/Löschen}
+
+ Alle vorhandenen Shared Mailboxen können mit einem einfachen
+
+ \begin{quote}
+ \T{ma \U{shared} --list} \\
+ \T{ma \U{shared} --list `h*'}
+ \end{quote}
+
+ angezeigt werden.
+ Anlegen und Löschen können mit \T{--add} bzw. \T{--del} erledigt werden.
+
+ Die Zugriffsrechte zu den Mailboxen müssen derzeit per Hand geändert werden
+ mit \T{cyradm}.
+
\section{Zugangsdaten}\label{p:Zugangsdaten}
\index{Passwort}
--- a/shared.pm Wed Dec 07 05:16:20 2005 +0000
+++ b/shared.pm Fri Dec 09 06:05:38 2005 +0000
@@ -22,7 +22,6 @@
sub _add();
sub _list();
sub _delete();
-sub _mkpw($);
sub uniq(@);
sub verbose(@);