# HG changeset patch # User heiko # Date 1134108338 0 # Node ID 5e9d468635889b2ac229d9dbb9f2fca267ba6751 # Parent c7c3cac0a89bccff80d672dfb1a1977d2664b290 Nun kann man auch Mailgruppen anlegen (sie gehen im LDAP als ou=Groups ein) diff -r c7c3cac0a89b -r 5e9d46863588 completion.bash --- 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 } 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: diff -r c7c3cac0a89b -r 5e9d46863588 ma --- 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"; } diff -r c7c3cac0a89b -r 5e9d46863588 ma.conf.ex --- 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 diff -r c7c3cac0a89b -r 5e9d46863588 manual.tex --- 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} diff -r c7c3cac0a89b -r 5e9d46863588 shared.pm --- 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(@);