# HG changeset patch # User heiko # Date 1131085766 0 # Node ID 2a5f2464f8c6c87d24e8cd810abe3b54ca2127fa unser neuer mailadmin 2. Versuch diff -r 000000000000 -r 2a5f2464f8c6 Installed --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Installed Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,1 @@ +Autohaus Dresden Reick diff -r 000000000000 -r 2a5f2464f8c6 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,77 @@ +# © 2005 Heiko Schlittermann +# $Id$ +# $URL$ + +package = ma +prefix = /usr/local +sbindir = $(prefix)/sbin +libdir = $(prefix)/lib/ma +mandir = $(prefix)/man +man8dir = $(mandir)/man8 + +DESTDIR ?= + +stowdir=/usr/local/stow/$(package) + + +SCRIPTS = ma +PM = account.pm password.pm ldapBase.pm alias.pm +MAN = ma.8.gz + +CHECKED = $(addprefix .ok.,$(SCRIPTS) $(PM)) +RUBBER_FLAGS = -s -I ${HOME}/Office/lib -I ${HOME}/Office/lib/pictures + +PDF = manual.pdf +DVI = manual.dvi +DOC = $(PDF) $(DVI) + +.PHONY: all install uninstall clean distclean dvi pdf + +all: $(CHECKED) man + +man: $(MAN) + +doc: $(DOC) + +install: all + install -m755 -d $(DESTDIR)$(sbindir) + install -m755 -d $(DESTDIR)$(libdir) + install -m755 -d $(DESTDIR)$(man8dir) + install -m755 $(SCRIPTS) $(DESTDIR)$(sbindir)/ + install -m644 $(PM) $(DESTDIR)$(libdir)/ + install -m644 $(MAN) $(DESTDIR)$(man8dir)/ + +clean: + -rm -f $(CHECKED) $(MAN) + -rm -f $(CLEANFILES) + +distclean: clean + -rm -f $(DOC) + + +stow: all + make DESTDIR=$(stowdir) prefix=/ install + stow -v -d $(dir $(stowdir)) -R $(package) + + +unstow: + stow -v -d $(dir $(stowdir)) -D $(package) + rm -rv $(stowdir) + +### + +dvi: $(DVI) +pdf: $(PDF) + +.ok.%: % + @perl -c $< + @touch $@ + +%.pdf: %.tex + rubber --pdf ${RUBBER_FLAGS} $< + +%.dvi: %.tex + rubber ${RUBBER_FLAGS} $< + +%.gz: %.pod + pod2man --section $(subst .,,$(suffix $@)) $< | gzip >$@ diff -r 000000000000 -r 2a5f2464f8c6 Steps --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Steps Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,127 @@ +Damit cyrus22 mit LDAP funktioniert. + +__LDAP aufsetzen (logisch) + + - nicht erforderlich sollten ldap_pam und ldap_nss sein + - Den Cyrus-Admin eintragen, z.B: + + Als Anregung 'populate.ldif'... + + + + +__cyrus22 + - sources.list: deb http://mail.incase.de/cyrus22/i386/ ./ + - auch den cyrus22-imapd (Der wird für cyradm gebraucht. Wie wollen die + Mailboxen sonst angelegt werden?) + + - virtual Domain support. Achtung, die 'default_domain' wird immer + abgeschnitten und dann werden ldap usw gefragt ohne diese Domain + hinten dran. Scheint ein anderes Konzept zu sein... + + - unixhierarchysep: yes + - altnamespace: yes + + Offenbar muss auxprop wissen, dass ldap genutzt werden soll. Wie das? + Oder der saslauthd muss es wissen. + + (Oder aber über pam? Vielleicht auch das als Lösung. Aber kann Cyrus + mit pam? - das wird als die krankeste Lösung dargestellt.) + + Jetzt mit saslauthd (aus sasl2-bin) - der muß in + /etc/default/saslauthd ldap eingetragen gekommen und natürlich eine + Config-Datei haben. + + In /etc/saslauthd.conf + + Wenn über ein LDAP-Bind geprüft werden soll, dann muss dort + kein Nutzer und kein Passwort drinstehen. + + Mit der Methode 'custom' wird nach userPassword gesucht und dann + offenbar wirklich das Passwort verglichen. (?) + + ,---[ saslauthd.conf ]--- + ldap_auth_method: bind + #ldap_auth_method: custom + #ldap_bind_dn: cn=admin,dc=ddc-consult,dc=de + #ldap_bind_pw: x + + ldap_search_base: dc=ddc-consult,dc=de + #ldap_servers: ldap://localhost:389/ + #ldap_default_realm: ddc-dresden.de + `-------------------------- + + => APOP geht nicht (siehe auch Manual-Seite: dazu müsste auxprop + Klartext-Passworte haben, die's aber nicht hat, wenn es saslauthd + nutzt. + +__exim4 + + Exim soll mit Cyrus über LMTP sprechen, also muss ein exim-Transport + her (und gleich noch einer für Amavis): + begin transports + ... + + cyrdeliver: + driver = lmtp + socket = /var/run/cyrus/socket/lmtp + transport_filter = /usr/bin/spamc -u $local_part@$domain + group = mail + + amavis: + driver = smtp + port = 10024 + allow_localhost + + + Und natürlich müssen die Router auch Bescheid wissen: + ldap_default_servers = localhost::389 + LDAP_USERS = ou=MailAccounts,dc=ddc-consult,dc=de + LDAP_ALIASES = ou=MailAliases,dc=ddc-consult,dc=de + + local_interfaces = 0.0.0.0.25 : 127.0.0.1.10025 + + begin router + + amavis: + driver = manualroute + condition = ${if eq{interface_port}{10025} {0}{1}} + route_list = * localhost byname + transport = amavis + self = send + no_verify + + # Ein Router der so wie die alten Aliase funktioniert + ldap_aliases: + driver = redirect + data = ${lookup ldap{ldap:///cn=${quote_ldap:$local_part},LDAP_ALIASES?rfc822MailMember?base?}} + + ... + # Ein Router, der die Alternativen Namen der + # Nutzer findet + alternative_names: + driver = redirect + data = ${lookup ldap{ldap:///LDAP_USERS?uid?sub?(mail=${quote_ldap:$local_part@$domain})}} + + + # Ein Router, der dann wirklich nur an Cyrus übergibt, wenn LDAP + # vermuten läßt, dass das funktionieren wird. + # nach den local users + cyrus: + driver = accept + condition = ${lookup ldapdn{ldap:///uid=${quote_ldap:$local_part},LDAP_USERS??base?}} + transport = cyrdeliver + + + +__SpamAssassin + + Das PID-File geht nicht zu schreiben, wenn er als Nutzer spamd + läuft -> /etc/default/spamassassin + (dort auch gleich noch 'export LANG=de_DE' eintragen, damit die + Meldungen eventuell in Deutsch kommen) + + +# $Id$ +# $URL$ +# vim:sts=2 sw=2 aw ai sm fo+=n tw=72: diff -r 000000000000 -r 2a5f2464f8c6 account.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/account.pm Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,415 @@ +package account; +# © 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); +my ($imap); +END { $imap and $imap = undef; }; + + +sub _add(); +sub _list(); +sub _delete(); +sub _mkpw($); +sub uniq(@); +sub verbose(@); + +sub OU_ACCOUNTS(); +sub OU_ALIASES(); +sub AT_PRIMARYADDRESS(); +sub OC_RECIPIENT(); +sub AT_ADDRESS(); +sub AT_GROUP(); +sub AT_FORWARDINGADDRESS(); + +sub import(@) { + $Cf = shift; + + require constant; + import constant OU_ACCOUNTS => $Cf->ldap_ou_accounts; + import constant OU_ALIASES => $Cf->ldap_ou_aliases; + import constant OC_RECIPIENT => $Cf->ldap_oc_recipient; + 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; + + $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 user name for creation\n" if not @ARGV; + my $user = shift @ARGV; + my $mbox = "user/$user"; + my $cn = $Cf->fullname || $user; + my $sn = (reverse split " ", $cn)[0]; + my $mailPrimaryAddress = $Cf->primary || $user; + my $mailAddress = [$user, split /,/, $Cf->other || ""]; + my $mailGroup = [split /,/, $Cf->group || ""]; + my $pw = _mkpw($Cf->password || "{pwgen}"); + + + my $dn = "uid=$user,$ubase"; + my $r; + + verbose("$user:\n"); + + verbose("\t$dn..."); + + $r = $ldap->search(base => $ubase, filter => "(uid=$user)"); + die $r->error if $r->code; + die "Multiple entries not expected" if $r->count > 1; + + my $e; + if ($r->count) { + $e = $r->shift_entry; + } else { + $e = new Net::LDAP::Entry; + $e->dn($dn); + } + + if ($e->exists("mail") || $e->exists(AT_PRIMARYADDRESS) || $e->exists("userPassword")) { + verbose "exists\n"; + } else { + + # Bevor wir ans Werk gehen, noch ein paar Tests (mailPrimaryAddress und mail darf + # darf noch nicht vergeben sein) + foreach my $a ($mailPrimaryAddress, @$mailAddress) { + $a =~ s/!$// and next; # wenn ein ! am Ende steht, dann ist es so gewollt und wird + # nicht geprüft + $r = $ldap->search(filter => "(mail=$a)", base => $ubase); + die $r->error if $r->code; + die "$a ist schon vergeben\n" if $r->count; + } + + $e->replace(objectClass => [uniq $e->get("objectClass"), qw/uidObject person/, OC_RECIPIENT]); + $e->replace(uid => [uniq $e->get("uid"), $user]); + + $e->add((AT_ADDRESS) => $mailAddress); + $e->add((AT_PRIMARYADDRESS) => $mailPrimaryAddress); + $e->add(userPassword => $pw); + $e->add((AT_GROUP) => $mailGroup) if @$mailGroup; + # $e->add(iusRestrictedMail => $Cf->internal) if $Cf->internal ne ":"; + + $e->exists("sn") or $e->add(sn => $sn); + $e->exists("cn") or $e->add(cn => $cn); + + + $r = $e->update($ldap); + die $r->error if $r->code; + + verbose("ok"); + verbose(" Password: $pw") if not $Cf->password or $Cf->password eq "{pwgen}"; + } + + if($Cf->mbox) { + verbose("\n\t$mbox..."); + + if ($imap->list($mbox)) { verbose("exists") } + else { + $imap->create($mbox) and verbose("ok") or die $@; + $imap->setacl($mbox, $Cf->imap_admin => "lrswipcda") or die $@; + $imap->setquota($mbox, STORAGE => 1024 * $Cf->imap_quota) or die $@; + } + } + + + verbose("\n"); +} + +sub _modify() { +# Auch hier gehen wir davon aus, daß die dn direkt aus dem User-Namen folgt: +# dn: uid=USER,... + my (@users) = @ARGV or die "Need username(s)\n"; + my @dns; + + my $r = $ldap->search(base => $ubase, + filter => "(|" . join("", map { "(uid=$_)" } @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 $user = $e->get_value("uid"); + my $dn = $e->dn; + my $mbox = "user/$user"; + + my $modified = 0; + verbose "$user:"; + + verbose "\n\t$dn..."; + + # Fix: iusMailOptions wurde erst später eingeführt, bei Bedarf also hinzufügen + #if (!grep /^iusMailOptions$/, @{$e->get("objectClass")}) { + #$e->add(objectClass => "iusMailOptions"); + #} + + if (my $cn = $Cf->fullname) { + # Aus dem Fullnamen leiten wir cn und sn ab. + my $sn = (reverse split " ", $cn)[0]; + + if ($cn =~ s/^\+//) { + $e->replace( + cn => [uniq $e->get("cn"), $cn], + sn => [uniq $e->get("sn"), $sn]); + } elsif ($cn =~ s/^-//) { + $e->delete(cn => [$cn], sn => [$sn]); + } else { $e->replace(cn => $cn, sn => $sn); } + $modified++; + } + + if (defined $Cf->other) { + my @o = split /,/, $Cf->other; + grep { /^[+-]/ } @o or $e->delete(AT_ADDRESS); + + foreach my $a (split /,/, $Cf->other) { + if ($a =~ s/^-//) { + $e->delete((AT_ADDRESS) => [$a]) + } else { + $a =~ s/^\+//; + + # Darf noch nicht woanders sein + $r = $ldap->search(base => $ubase, filter => "(mail=$a)"); + die $r->error if $r->code; + die "$a ist schon vergeben\n" if $r->count; + + $e->add((AT_ADDRESS) => [$a]) + } + } + $modified++; + } + + if (defined $Cf->group) { + my @g = split /,/, $Cf->group; + grep { /^[+-]/ } @g or $e->delete(AT_GROUP); + + foreach my $g (@g) { + if ($g =~ s/^-//) { + $e->delete((AT_GROUP) => [$g]) + } else { + $g =~ s/^\+//; + $e->add((AT_GROUP) => [$g]) + } + } + $modified++; + } + + if (my $a = $Cf->primary) { + $r = $ldap->search(base => $ubase, + # filter => "(|(mailPrimaryAddress=$a)(mail=$a))"); + filter => "(mail=$a)"); + die $r->error if $r->code; + die "$a ist schon vergeben\n" if $r->count; + + $e->replace((AT_PRIMARYADDRESS) => $Cf->primary); + $modified++; + } + + if (my $pw = _mkpw($Cf->password)) { + $e->replace(userPassword => $pw); + $modified++; + } + + #if ($Cf->internal ne ":") { + #$e->replace(iusRestrictedMail => $Cf->internal ? "TRUE" : "FALSE"); + #$modified++; + #} + + $e->dump if $Cf->debug; + + if ($modified) { + $r = $e->update($ldap); + die $r->error.$r->code if $r->code; + } + + # FIXME: Wenn keine Mailbox existiert, gibt es hier ein Problem + if (defined $Cf->imap_quota) { + $imap->setquota($mbox, STORAGE => $Cf->imap_quota * 1024) + or die $@; + } + + 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 "User: "; + chomp($_ = <>); + @ARGV = ($_); + } + + + foreach (@ARGV) { + my $user = $_; + my $dn = "uid=$user,$ubase"; + my $mbox = "user/$user"; + + verbose("$user:\n"); + + # Nachsehen, ob es noch aliase gibt, in denen dieser Nutzer steht: + my $r = $ldap->search(base => $abase, + filter => "(".AT_FORWARDINGADDRESS."=$_)", + attrs => ["mail", AT_FORWARDINGADDRESS]); + while (my $e = $r->shift_entry) { + verbose("\tdeleting $user from alias ".$e->get_value("mail")."..."); + $e->delete((AT_FORWARDINGADDRESS) => [$user]); + + my $r = $e->update($ldap); + if ($r->code == 0) { verbose("ok\n") } + else { die $r->error } + } + + verbose("\tdeleting $dn..."); + $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"); + + if ($Cf->mbox) { + verbose("\tdeleting mbox $mbox..."); + $imap->delete($mbox) and verbose("ok") + or verbose($imap->error); + } + + verbose("\n"); + + } +} + +sub _list() { + my $filter; + @ARGV = ("*") unless @ARGV; + $filter = "(|" . join("", map { "(uid=$_)" } @ARGV) . ")"; + + my $r = $ldap->search( + filter => $filter, + base => $ubase, + attrs => [qw/uid cn mail userPassword/] + ); + die $r->error if $r->code; + + + while (my $e = $r->shift_entry) { + my $uid = $e->get_value("uid"); + my $cn = join(", ", $e->get_value("cn")); + my $mr = $e->get_value(AT_PRIMARYADDRESS) || "??"; + my $ml = join(", ", $e->get_value(AT_ADDRESS)) || "??"; + my $mg = join(", ", $e->get_value(AT_GROUP)) || "??"; + my $mbox = "user/$uid"; + + print "$uid: $cn <$mr>"; + + #if (($e->get_value("iusRestrictedMail")||"") eq "TRUE") { + #print " INTERNAL"; + #} + + MBOX: { + if (!$imap->list($mbox)) { + print ", no mbox"; + last MBOX; + } + print ", mbox"; + my %q = $imap->listquota($mbox); + my ($used, $max) = map { int($_ / 1024) } @{$q{STORAGE}}; + + if (!$max) { + print ", no quota"; + last MBOX; + } + print ", quota ($used/$max): " . int(100 * $used/$max) . "%"; + } + print "\n"; + + print "\tPassword: ", $> == 0 ? $e->get_value("userPassword") : "*", "\n"; + + print wrap("\t", "\t\t", "Local Adresses: $ml\n") if $ml; + print wrap("\t", "\t\t", "Mail Groups: $mg\n") if $mg; + + } +} + +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 000000000000 -r 2a5f2464f8c6 alias.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/alias.pm Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,215 @@ +package alias; +# © Heiko Schlittermann +# $Id$ +# $URL$ + +use strict; +use warnings; +use Net::LDAP; +use Net::LDAP::Constant qw( + LDAP_ALREADY_EXISTS + LDAP_NO_SUCH_OBJECT + LDAP_NO_SUCH_ATTRIBUTE + LDAP_TYPE_OR_VALUE_EXISTS); +use Net::LDAP::Entry; +use Text::Wrap; + +use password; + +my $Cf; +my ($ldap, $abase); + +sub _add(); +sub _list(); +sub _delete(); +sub uniq(@); +sub verbose(@); +sub columns(); + +sub OU_ALIASES(); +sub OC_ALIAS(); +sub AT_FORWARDINGADDRESS(); + +sub import(@) { + $Cf = shift; + + require constant; + import constant OU_ALIASES => $Cf->ldap_ou_aliases; + import constant OC_ALIAS => $Cf->ldap_oc_alias; + import constant AT_FORWARDINGADDRESS => $Cf->ldap_at_forwardingaddress; + + $abase = OU_ALIASES . "," . $Cf->ldap_base; +} + +sub run($) { + # Eigentlich brauchen wir für alles 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; + + + 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() { +# Wenn's den Alias schon gibt, wird er nicht mehr +# angelegt + + die "Need alias name for creation\n" if not @ARGV; + die "Need members\n" if not defined $Cf->members; + my $alias = shift @ARGV; + my @members = split /,/, $Cf->members; + my $dn = "mail=$alias,$abase"; + + my $r; + + verbose("$alias:\n"); + verbose("\t$dn..."); + + $r = $ldap->search(base => $abase, filter => "(mail=$alias)"); + die $r->error if $r->code; + die "Multiple entries not expected" if $r->count > 1; + + $r = $ldap->add($dn, attrs => [ + objectClass => OC_ALIAS, + mail => $alias, + (AT_FORWARDINGADDRESS) => \@members + ]); + if ($r->code == LDAP_ALREADY_EXISTS) { verbose "exists" } + elsif ($r->code) { die $r->error } + else { verbose "ok" } + + verbose("\n"); +} + +sub _modify() { +# Auch hier gehen wir davon aus, daß die dn direkt aus dem Alias-Namen folgt: +# dn: cn=USER,... +# Jetzt behandeln wir lediglich die Modifikation auf Basis eines +# alias-Namens! + + my (@users) = @ARGV or die "Need alias names(s)\n"; + my @members = split /,/, $Cf->members; + my @add = grep { s/^\+// } @_ = @members; + my @del = grep { s/^-// } @_ = @members; + my @set = grep { !/^[\+-]/ } @members; + + + foreach my $alias (@ARGV) { + my $dn = "mail=$alias,$abase"; + verbose "$alias:"; + + my $r = $ldap->search(base => $abase, filter => "(mail=$alias)"); + die $r->error if $r->code; + + if ($r->count == 0) { + verbose " not found\n"; + next; + } + + while (my $e = $r->shift_entry) { + + verbose "\n\t" . $e->dn . " "; + + if (@set) { + $e->replace((AT_FORWARDINGADDRESS) => \@set); + } else { + @add and $e->replace((AT_FORWARDINGADDRESS) => [uniq $e->get(AT_FORWARDINGADDRESS), @add]); + @del and $e->delete((AT_FORWARDINGADDRESS) => \@del); + } + + $e->dump if $Cf->debug; + + my $r = $e->update($ldap); + if ($r->code == LDAP_NO_SUCH_ATTRIBUTE) { + verbose "no member"; + } elsif ($r->code) { + die $r->error . "/" . $r->code; + } else { + verbose "ok"; + } + } + + print "\n"; + } +} + +sub _delete() { +# Wir gehen davon aus, daß es einen dn mail=ALIAS,ou=MailAliases,... +# gibt und löschen diesen gnadenlos. + + if (!@ARGV) { + print "User: "; + chomp($_ = <>); + @ARGV = ($_); + } + + foreach (@ARGV) { + my $dn = "mail=$_,$abase"; + + verbose("$_:\n"); + verbose("\tdeleting $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 { "(mail=$_)" } @ARGV) . ")"; + + my $r = $ldap->search( + filter => $filter, + base => $abase, + attrs => [qw/mail/, AT_FORWARDINGADDRESS], + ); + + die $r->error if $r->code; + + $Text::Wrap::columns = columns() || 80; + + while (my $e = $r->shift_entry) { + my $mail = $e->get("mail"); + + print wrap("", "\t", $e->get_value("mail") + . ": " + . join(", ", $e->get(AT_FORWARDINGADDRESS)) + . "\n"); + + } +} + +sub verbose(@) { + printf STDERR @_; +} + +sub uniq(@) { + my %x; + @x{@_} = (); + return keys %x; +} + +sub columns() { + `stty -a` =~ /columns\s+(\d+)/; + $1; +} + +1; +# vim:sts=4 sw=4 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 im --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/im Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,53 @@ +#! /usr/bin/perl +# © 2005 Heiko Schlittermann +# $Id$ +# $URL$ + +# small template for action on all imap folders +# or simlar quatsch + +use strict; +use warnings; +use AppConfig; + +use Mail::IMAPClient; +use Cyrus::IMAP::Admin; + +use constant QUOTA => 200000; # kiloByte + +use constant CONFIG => ( + { CASE => 1 }, + quota => { DEFAULT => 200000, ARGS => "=i" }, + force => { DEFAULT => undef, ARGS => "!" }, +); + +my $Cf = new AppConfig CONFIG; + $Cf->getopt(\@ARGV); + +my $imap = new Cyrus::IMAP::Admin("localhost") or die; +# $imap->authenticate(-user => "root", -password => "hallo") or die; +$imap->authenticate(-user => "root") or die; + +foreach ($imap->list("user/*")) { + my ($mb, $att, $sep) = @$_; + + next unless $mb =~ /^user\/[^\/]+$/; + print "$mb"; + + my ($qroot, $quota) = $imap->listquotaroot($mb); + if ($qroot && $qroot ne $mb) { + print " QuotaRoot; $qroot\n"; + next; + } + + my %q = $imap->listquota($mb); + if (exists $q{STORAGE} and !$Cf->force) { + my ($used, $max) = @{$q{STORAGE}}; + print " Quota $used/$max " . int($used/$max*100) . "%"; + } else { + $imap->setquota($mb, "STORAGE" => $Cf->quota) or die; + } + + print "\n"; + +} diff -r 000000000000 -r 2a5f2464f8c6 imap.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imap.pm Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,44 @@ + +sub createMbox($) { + my $user = shift; + my $mbox = "user/$user"; + + verbose(" imap:"); + my $imap = connectImap(); + + $imap->setacl($mbox, $Cf->imap_admin => "lrswipcda"); + if ($imap->list($mbox)) { + verbose("(exists)"); + } else { + $imap->create($mbox) or die ":$@: $mbox\n"; + $imap->setquota($mbox, STORAGE => 1024 * $Cf->imap_quota) or die ":$@: $mbox\n"; + } + verbose("ok"); + +} + +sub removeMbox($) { + my $user = shift; + my $mbox = "user/$user"; + verbose(" imap:"); + my $imap = connectImap(); + + $imap->setacl($mbox, $Cf->imap_admin, "rc"); + + if (not $imap->exists($mbox)) { + verbose("does not exist"); + } else { + $imap->delete($mbox) or die "$@"; + } + verbose("ok"); +} + + +sub connectImap() { + my $imap = new Cyrus::IMAP::Admin($Cf->imap_server) or die "$@"; + $imap->authenticate(-user => $Cf->imap_admin, + -password => $ENV{IMAP_PASS} || askPass("IMAP (" . $Cf->imap_admin .") password: ")); + + return $imap; +} + diff -r 000000000000 -r 2a5f2464f8c6 injectMail --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/injectMail Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,90 @@ +#! /usr/bin/perl +# $Id$ +# $URL$ +my $USAGE = <<'#'; +{{ME}} [options] --user={USER} [mboxfile] + --domain=s default domain for mailbox id [{{$Cf->domain}}] + + Version: $Id$ +# + +use strict; +use warnings; + +use Mail::IMAPClient; +use File::Basename; +use AppConfig; + +use constant CONFIG => ( + { CASE => 1 }, + domain => { ARGS => "=s", DEFAULT => "ddc-consult.de" }, + user => { ARGS => "=s", DEFAULT => undef }, + help => { ARGS => "!", DEFAULT => undef }, +); + +use constant ME => basename $0; + +use constant IMAP_SERVER => "localhost"; +use constant IMAP_ADMIN => "root"; +use constant IMAP_PASS => $ENV{IMAP_PASS}; + +sub askPass($); + +my $Cf = new AppConfig CONFIG or die; + $Cf->getopt(\@ARGV) or die; + +HELP: { + last unless $Cf->help; + $USAGE =~ s/{{(.*?)}}/eval $1/eg; + print $USAGE; + exit; +} + +MAIN: { + $Cf->user or die "Need user..."; + my $mbox = $Cf->user; + $mbox .= "@" . $Cf->domain unless $mbox =~ /@/; + $mbox = "user/$mbox"; + + + my $imap = new Mail::IMAPClient + Server => IMAP_SERVER, + User => IMAP_ADMIN, + Password => IMAP_PASS || askPass("IMAP (". IMAP_ADMIN .") password: ") + or die "Can't connect: $@"; + + $imap->setacl($mbox, IMAP_ADMIN, "lrswipcda") or die "$@: $mbox"; + $imap->select($mbox) or die "$@: $mbox"; + + { + my $message; + while (<>) { + if (/^From / or eof) { + if ($message) { + print "Appending message (" . length($message) . ") bytes\n"; + $imap->append($mbox, $message) or die "$@"; + $message = ""; + } + } else { + $message .= $_; + } + } + } + +} + +sub askPass($) { + local $| = 1; + return undef if not -t; + + print $_[0]; + system(stty => "-echo"); + my $ans = ; + system(stty => "echo"); + print "\n"; + + chomp $ans; + return $ans; +} + +# vim:sts=4 sw=4 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 ldap.schema --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ldap.schema Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,27 @@ + +# Output (to LDAP-Server) +# Normaler Mail-Account: +# ,--- +# |dn: uid=hh,ou=[MailAccounts],... +# |objectClass: person +# |objectClass: uidObject +# |objectClass: [mailRecipient] +# |[mailPrimaryAddress]: hans.hanson (from --address or the user) +# |mail: h.hanson (from --alias) +# |mail: hanson (from --alias) +# |mailGroup: edv, postmaster +# |uid: hh +# |userPassword: {crypt}* +# |sn: Hanson +# |cn: Hans Hanson +# `--- + +# ,--- +# |dn: mail=root,ou=[MailAliases],... +# |objectClass: [MailAlias] +# |mail: root +# |[MailForwardingAddress]: rudi +# |[MailForwardingAddress]: hans +# `--- + +Die [..] sind konfigurierbar. diff -r 000000000000 -r 2a5f2464f8c6 ldapBase.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ldapBase.pm Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,21 @@ +package ldapBase; +# © Heiko Schlittermann +# $Id$ +# $URL$ + +use strict; +use warnings; +use Exporter(); +our @ISA = qw/Exporter/; +our @EXPORT = qw/&ldapBase/; + + +sub ldapBase(@) { + no warnings 'once'; + local @ARGV = grep { -f } @_; + die "Can't find ldap.conf (searched @_)\n" if !@ARGV; + my $r = (reverse grep { /^\s*BASE\s+(.*?)\s*$/ and $_ = $1 } <>)[0]; + return $r; +}; + +# vim:sts=4 sw=4 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 ma --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ma Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,163 @@ +#! /usr/bin/perl +# Usage: +# © 2005 Heiko Schlittermann +# $URL$ +# $Id$ +# +use constant USAGE => <<'#'; +Usage: !ME! account|alias --add|--list|--modify|--delete [options] [user|alias] + * common options * + --ldap_server=s LDAP-Server [!$Cf->ldap_server!] + --ldap_base=s LDAP-Basis [!$Cf->ldap_base!] + --ldap_admin=s LDAP BIND DN [!$Cf->ldap_admin!] + --ldap_password=s [!$Cf->ldap_password!] + + --imap_server=s IMAP Server [!$Cf->imap_server!] + --imap_admin=s IMAP Server [!$Cf->imap_admin!] + --imap_password=s [!$Cf->imap_password!] + + * account options * + --[no]mbox Create MBox [!$Cf->mbox!] + --imap_quota=i Mail Quota (MB) [!$Cf->imap_quota!] + --address=s Primary Mail [!$Cf->address!] + --other:s Alternative Mail addresses + (comma sep.) [!$Cf->other!] + --group:s Mail Group(s) this account is member of + (comma sep.) [!$Cf->group!] + --fullname=s Real Name [!$Cf->fullname!] + --password=s Passwort [!$Cf->password!] + + * alias options * + --members=s List of Members [!$Cf->members!] + +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}!]. + +$Id$ +$URL$ +© 2005 Heiko Schlittermann + +# + +use strict; +use warnings; + +use IO::File; +use Cyrus::IMAP::Admin; +use AppConfig qw(:expand); +use File::Basename; +use Carp; + +use lib qw(. /usr/local/lib/ma); +use ldapBase; + +use constant ME => basename $0; +use constant CONFIG => ( + { CASE => 1 }, + GLOBAL => { DEFAULT => undef }, + + # * common * + add => { ARGS => "!", ALIAS => [qw/new create/] }, + list => { ARGS => "!", ALIAS => "ls" }, + modify => { ARGS => "!", ALIAS => "change" }, + delete => { ARGS => "!", ALIAS => "remove" }, + + ldap_base => { ARGS => "=s", DEFAULT => ldapBase(qw(/etc/openldap/ldap.conf /etc/ldap/ldap.conf)) }, + ldap_server => { ARGS => "=s", DEFAULT => "localhost" }, + ldap_bind_dn => { ARGS => "=s", DEFAULT => "cn=admin", ALIAS => "ldap_admin" }, + ldap_password =>{ ARGS => "=s" }, + + help => { ARGS => "!" }, + debug => { ARGS => "!" }, + + + # * account * + imap_server => { ARGS => "=s", DEFAULT => "localhost" }, + imap_admin => { ARGS => "=s", DEFAULT => $ENV{USER} }, + imap_password =>{ ARGS => "=s" }, + imap_quota => { ARGS => "=i", DEFAULT => 300, ALIAS => "quota" }, + + mbox => { ARGS => "!", DEFAULT => 1 }, + password => { ARGS => "=s" }, +# internal => { ARGS => "!", DEFAULT => ":", ALIAS => "restricted" }, + + other => { ARGS => ":s" }, + group => { ARGS => ":s" }, + fullname => { ARGS => "=s", ALIAS => "realname" }, + address => { ARGS => "=s", ALIAS => "primary" }, + + # * alias * + members => { ARGS => ":s" }, + + # * ldap intern * + ldap_ou_aliases => { ARGS => "=s", DEFAULT => "ou=MailAliases" }, + ldap_ou_accounts => { ARGS => "=s", DEFAULT => "ou=MailAccounts" }, + + ldap_oc_alias => { ARGS => "=s", DEFAULT => "XXXmailAlias" }, + ldap_oc_recipient => { ARGS => "=s", DEFAULT => "XXXmailRecipient" }, + + ldap_at_address => { ARGS => "=s", DEFAULT => "XXXmailAddress" }, + ldap_at_group => { ARGS => "=s", DEFAULT => "XXXmailGroup" }, + ldap_at_forwardingaddress => + { ARGS => "=s", DEFAULT => "XXXmailForwardingAddress" }, + ldap_at_primaryaddress => + { ARGS => "=s", DEFAULT => "XXXmailPrimaryAddress" }, + +); +our $Cf; + +sub help(); + +my $Module = shift if @ARGV && $ARGV[0] !~ /^-/; + $Module ||= "UNKNOWN"; + + +$SIG{__DIE__} = sub { die "\n".ME.": ", @_ }; + + +MAIN: { + + $Cf = new AppConfig CONFIG or die; + + if (exists $ENV{MA_CONF} and -f $ENV{MA_CONF}) { + my $f = $ENV{MA_CONF}; + die ": $f is group/world readable/writeable\n" if 077 & (stat _)[2]; + $Cf->file($f) or die; + } + $Cf->getopt(\@ARGV) or die "Bad Usage. Try --help.\n"; + + die "Need ldap base.\n" if not $Cf->ldap_base; + if ($Cf->ldap_admin !~ /\Q$Cf->ldap_base/) { + $Cf->ldap_admin($Cf->ldap_admin . "," . $Cf->ldap_base); + } + + print help() and exit 0 if $Cf->help; + + @_ = grep { $_ =~ /^\Q$Module\E/ } qw/account alias/; + die "Need module. Try --help\n" if @_ == 0; + die "Module ambigous. (@_)\n" if @_ > 1; + + if ($_[0] eq 'account') { + require account; + account::import($Cf); + account::run(); + } elsif ($_[0] eq 'alias') { + require alias; + alias::import($Cf); + alias::run(); + } else { + die "Shit"; + } + +} + +sub verbose(@) { + print STDERR @_; +} + +sub help() { + ($_ = USAGE) =~ s/!(.*?)!/(eval $1) || ""/eg; + return $_; +} + +# vim:sts=4 sw=4 aw ai sm nohlsearch incsearch: diff -r 000000000000 -r 2a5f2464f8c6 ma.8.pod --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ma.8.pod Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,164 @@ +=head1 NAME + +ma -- mailadmin tool + +=head1 SYNOPSIS + +B account [--add|--modify|--delete|--list] +[--address=s] +[--fullname=s] +[--group=s] +[--[no]mbox] +[--other=s] +[--password=s] +I + +B alias [--add|--modify|--delete|--list] [--members=s] I + +B --help + + +=head1 DESCRIPTION + +B ist ein Tool zur Administration vom Mailnutzern, deren Einstellungen im LDAP +gespeichert sind. + +Es gliedert sich in zwei Sub-Kommands: C und C. + +=head2 account + +Mit diesem Subkommando werden die Details des Accounts (Nutzer) modifiziert. +Das sind vor allem Dinge wie + +=over 4 + +=item . Name + +=item . Password + +=item . Gruppenzugehörigkeit + +=item . alternative Mailadressen + +=back + +Beeinflußt wird sowohl das LDAP als auch der Cyrus-Mailserver. Im LDAP werden die Nutzerspezifischen +Daten gehalten, im Cyrus die Mailboxen. Bei LDAP-Eintragungen wird davon ausgegangen, daß +keine anderen Elemente in den entsprechenden Subbäumen liegen. Besonders beim Löschen ist das zu berücksichtigen, +denn z.B. das Entfernen eines Nutzer entfernt dessen B LDAP-Objekt! + +=head1 OPTIONS + +=over 4 + +=head2 OPTIONS für beide Sub-Kommandos + +=item B<--add>|B<--modify>|B<--delete>|B<--list> + +Ist eigentlich selbsterklärend :) + +=back + +=head2 OPTIONS für C + +=over 4 + +=item B<--address>=I + +Die primäre Mailadresse des Nutzers. Wird u.U. für das Masquerading bei ausgehender Mail +genutzt. Diese Adresse darf nicht schon existieren. Mit einem C am Ende der Adresse läßt sich +dieser Test ausschalten. + +=item B<--fullname>=I + +Der volle Klartextname des Nutzers. Wird nur für Dokumentationszwecke genutzt. + +=item B<--group>=I + +Die Liste der Mailgruppen, in denen der Nutzer Mitglied ist. Mail an diese Gruppen werden dann +auch an diesen Nutzer zugestellt. Beim Neuanlegen genügt eine kommangetrennte Liste (keine Leerzeichen!). +Beim Modifizieren können mit dem Präfix C<+> bzw. C<-> einzelne Gruppen hinzugefügt bzw. entfernt werden: + + ma account --modify --group=+groupA,-groupB,+groupC hans + +entfernt C aus der Gruppe C und fügt ihn in C und C ein. + + ma account --modify --group= hans + +entfernt alle Gruppenzugehörigkeiten. + + ma account --modify --group=groupA,groupB hans + +setzt genau die Zugehörigkeiten C und C. + +=item B<--[no]mbox> + +Mailbox-Anlegen oder auch nicht. (Nur beim Neu-Anlegen.) + +=item B<--other=>=I + +Wenn der Nutzer unter alternativen Mailadressen erreichbar sein soll, dann sind diese hier erwähnt. +Es lassen sich Adressen hinzufügen und auch wieder löschen. Die Syntax dazu entspricht der bei der +Gruppenzugehörigkeit. + +=item B<--password>=I + +Wenn beim Nutzeranlegen das Passwort nicht generiert werden soll, dann ist es hier anzugeben. +Nachträglich läßt es sich auch hier ändern. + +=back + +=head2 Options für C + +=over 4 + +=item B<--members>=I + +Es können Alias-Mitglieder ein- und ausgetragen werden. Die Syntax entspricht der oben bei der +Gruppenzugehörigkeit angegebenen. + +=back + +=head1 EXAMPLES + +Anlegen, Anzeigen, Verändern und Löschen eines Nutzers: + + ma account --add --fullname "Hans Hanson" --other=h.hanson,hh --group=edv hans + ma account --list + ma account --modify --password=x --group=-edv,+it hans + ma account --delete hans + +Beim Löschen wird die B! Eventuelle Mitgliedschaften des Nutzers in +Aliasen werden B berücksichtigt. Die Nutzung +der Gruppen ist hier jedoch deutlich einfacher. + +Anlegen, Anzeigen, Verändern und Löschen eines Alias: + + ma alias --add --members=hans,rudi operators + ma alias --list + ma alias --modify --members=+fritz operators + ma alias --delete operators + +=head1 Umgebungsvariablen + +=over 4 + +=item MA_CONF + +Konfigurations-Datei-Name. Diese Variable B gesetzt werden, damit +B sich überhaupt für eine Konfigurations-Datei interessiert. + +=item LDAP_PASS + +Passwort für den LDAP-Administrator. Optional. + +=item IMAP_PASS + +Passwort für den IMAP-Administrator. Optional. + +=back + + +=head1 AUTHOR + +Heiko Schlittermann diff -r 000000000000 -r 2a5f2464f8c6 man8/ma.8.gz --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man8/ma.8.gz Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,1 @@ +../ma.8.gz \ No newline at end of file diff -r 000000000000 -r 2a5f2464f8c6 manual.tex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/manual.tex Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,467 @@ +\documentclass[final]{article} +% © Heiko Schlittermann +% $Id$ +% $URL$ + +\usepackage{ius} +\usepackage{makeidx} +\newcommand{\N}[1]{\textit{#1\/}} % name +\newcommand{\T}[1]{\texttt{#1}} % typed +\newcommand{\U}[1]{\underline{#1}} +\newcommand{\C}[1]{} + +\title{Mailsystem --- Deutsche Post AG / TCB --- Systemdokumentation} +\author{$<$hs@schlittermann.de$>$} +\date{\today} +%\makeindex + +\begin{document} +\maketitle + +%\begin{abstract} +\subsubsection*{Zusammenfassung} + Ein Mailserver. Post wird per \N{fetchmail} geholt, an \N{exim} + (SMTP) übergeben und dann in den \N{cyrus}-Mailstore gelegt. Dort + ist dann alles per POP/IMAP verfügbar. +%\end{abstract} + +\parskip0mm +\tableofcontents +\parskip0.5em + +%\setlength{\parindent}{0mm} +%\setlength{\parskip}{1em} + +\section{Verwendete Komponenten} + + \begin{tabular}{ll} + Post-Abholer & fetchmail \\ + SMTP-Server & exim 4.5x \\ + POP3/IMAP-Server & cyrus 2.2 \\ + OpenLDAP & 2.2 \\ + \end{tabular} + +\section{LDAP} + + Im LDAP sind die Nutzerdaten (Accounts, Passworte, \ldots) hinterlegt. + Mit einem LDAP-Browser ist jederzeit der Zugang zum LDAP-Server + möglich, so sind Änderungen relativ einfach und schnell möglich.\index{LDAP-Tools} + + \subsection{SSL/TLS} + + Der LDAP-Server stellt seine Dienste unverschlüsselt zur Verfügung. Verschlüsselung über + STARTTLS bzw. \texttt{ldaps:///} ist möglich und konfiguriert. Die Herausforderung ist + aber, daß dann der Client das auch unterstützen muß und vor allem mit den von einer nicht + offiziellen CA signierten Zertifikaten auskommen muß. + + \subsection{LDAP-Browser} + + Es gibt einige GUI-LDAP-Browser. + + \begin{description} + \item{\N{ldapbrowser}} Bei Google man nach \qq{ldapbrowser java} + suchen. Er läuft unter Linux und unter Win32 mit einer aktuellen + Java-Runtime. + + \item{\N{JXplorer}} Alternative zu dem o.g. + \end{description} + + Und es gibt natürlich noch für die (Linux)-Kommandozeile einiges. + Am besten geeignet: \N{ldapvi}. Für die Kontrolle über die + letzten Schrauben dann \N{ldap\{add,delete,modify\}}. Für + \N{ldapvi} ist ein Alias \T{ldapvi} in der \T{~.bashrc} eingetragen, + der sich versucht, als Admin an den LDAP-Server zu binden. + + Die Zugangsdaten $\rightarrow$ Seite~\pageref{p:Zugangsdaten}. + + \subsection{Struktur} + + Unterhalb des Knotens \N{ou=MailAccounts} sind Nutzer-Objekte angelegt, die + für die Authentifizierung notwendig sind. Außerdem enthalten diese + Objekte auch alternative Mail-Adressen und die Mailgruppen-Zugehörigkeit + für die jeweiligen Nutzer. + + \begin{sloppypar} + Unterhalb von \N{ou=MailAliases} sind Objekte, die den bisherigen + Aliasen/Verteilerlisten aus \T{/etc/aliases} entsprechen. + \end{sloppypar} + + Es existieren also zwei Mechanismen für eine Realisierung von Alias-ähnlichen + Verteilern: über \N{ou=MailAliases} und über das \N{MailGroup}-Attribut. + + \fussy + Wichtige Bestandteile der Mail-Accounts sind: + + \begin{tabular}{ll} + objectClass & \N{uidObject}, \N{tcbMailRecipient}, \N{person} \\ + uid & Nutzer-ID (Anmeldung am POP3/IMAP-Server, Name der Mailbox) \\ + userPassword & Passwort für SMTP/POP3/IMAP \\ + tcbMailPrimaryAddress & primäre Mailadresse \\ + tcbMailAddress & alternative Adressen \\ + tcbMailGroup & Mitgliedschaft in Mailgruppen \\ + tcbMailForwardingAddress & Weiterleitungsadresse (analog .forward) \\ + sn & Nachname (ungenutzt, muß aber gesetzt sein) \\ + cn & allgemeiner Name (ungenutzt, muß aber gesetzt sein) \\ + \end{tabular} + + Die Aliase sind einfacher aufgebaut: + + \begin{tabular}{ll} + objectClass & \N{tcbMailAlias} \\ + mail & allgemeiner Name des Aliases (hier: \qq{linke Seite}) \\ + tcbMailForwardingAddress & Alias, als der Empfänger \\ + \end{tabular} + + \subsection{Files} + + \begin{tabular}{ll} + Konfiguration & \T{/etc/ldap/*} \\ + Logfiles & \T{/var/log/syslog} \\ + Datenbank & \T{/var/lib/ldap/*} \\ + Backup & \T{/var/backups/ldap.week*} \\ + \end{tabular} + + \subsection{Administration} + + Am LDAP-Server selbst ist nicht viel zu administrieren. Nutzer an- + und ablegen ist auf Seite~\pageref{p:admin} beschrieben. + + \subsection{Backup/Recovery} + + Sollte das LDAP sich über Datenbank-Probleme beschweren, gibt es + etwa folgenden Weg: + + \begin{enumerate} + \item LDAP-Server neustarten: \T{service lap restart} + \item Datenbank-Recovery versuchen: + \begin{quote}\T{% + service ldap stop \\ + cd /var/lib/ldap\\ + db41\_recover\\ + service ldap start\\ + }\end{quote} + \item Backup wieder einspielen: + \begin{quote}\T{% + service ldap stop \\ + rm -r /var/lib/ldap/* \\ + bzcat $} gesetzt und die \N{From:} und \N{Sender:}-Header + bleiben unverändert. Mit dieser Einstellung sollte die Mail dann + (wegen des leeren Return-Path) die meisten Mailserver passieren + können.\footnote{Die meisten. Einige prüfen inzwischen auch die + Verfügbarkeit von brauchbaren Header-Zeilen \N{From:} und + \N{Sender:}.} + + In \N{CC:}, \N{Reply-To:}, \N{To:}-Headern wird, wenn sie eine + Adresse \N{@tcb.deutschepost.de} enthalten, diese ersetzt durch die + primäre Adresse des entsprechenden Nutzers (gesucht wird im Ldap + nach Einträgen, deren \N{tcbMailAddress} dem Nutzer lokalen Teil der + fraglichen Adresse entspricht). + + \subsection{Files} + + \begin{tabular}{ll} + Konfiguration \N{Exim} & \T{/etc/exim/*} \\ + SMTP-Spool & \T{/var/spool/exim/*} \\ + SMTP-Log & \T{/var/log/exim/\{main,reject,panic\}.log} \\ + \end{tabular} + + \subsection{Administration} + + Viel zu administrieren sollte es am \N{Exim} nicht geben. Ab und zu + kann man die Warteschlange mit \T{mailq} oder \T{exipick} kontrollieren. + Eingefrorene Mails sollten nach einigen (7) Tagen verschwinden. + + Im Zweifelsfall ist ein Blick in die Logfiles (vor allem + \T{panic.log} und \T{reject.log}) interessant. + + Zum Adress-Test biete sich folgendes an: + + \begin{quote}\T{exim -v -bv hans@domain.de}\end{quote} + + Damit können lokale und auch externe Adressen getestet werden und es + ist zu erkennen, wie der Mailserver die jeweilige Adresse routen + würde. + + +\section{POP3/IMAP} + + Vom SMTP-Server \N{Exim} kommt die Mail per LMTP zum \N{Cyrus}. Der + verwaltet sein eigenes Mailstore in \T{/var/spool/cyrus}. Dort + manuell einzugreifen ist nicht die beste Idee, da der \N{Cyrus} sich + Index-Dateien erzeugt, über die er dann den Zustand der Mailboxen zu + kennen glaubt. + + + \subsection{Clients} + + Die Clients können per POP3 und/oder IMAP auf ihre Postfächer + zugreifen. Allerdings steht per POP3 nur die Inbox zur Verfügung. + + Der übertragenen User-Name wird im LDAP unter \N{ou=MailAccounts} + gesucht und dann wird über ein LDAP-Bind die Korrektheit des + Passworts geprüft. + + \subsection{Files} + + \begin{tabular}{ll} + Konfiguration & \T{/etc/imapd.conf} \\ + & \T{/etc/cyrus.conf} \\ + Spool & \T{/var/spool/cyrus} \\ + Logfiles & \T{/var/log/mail.log} + \end{tabular} + + \subsection{Administration} + + Sollten die unten (S.~\pageref{p:admin}) beschriebenen Werkzeuge + nicht ausreichen, ist mit \T{cyradm} noch einiges zu retten: + \T{cyradm localhost}: + + \begin{verbatim} + lm # alle Mailboxen anzeigen + cm user/hannes # Mailbox anlegen für hannes + lm user/hannes # Hannes Mailbox + lam user/hannes # Berechtigungen zeigen + sam user/hannes root all # Berechtigungen setzten + lq user/hannes # Quota anzeigen + sq user/hannes STORAGE 200 # Quota auf 200k setzen + \end{verbatim} + + Eine direkte Verbindung mit den Mailboxen ist mit jedem beliebigen + IMAP-Client möglich, z.B. auch mit Mutt: + + \begin{quote} + \T{mutt -f imap://cyrus@localhost/user/hannes} + \end{quote} + + bzw.\ direkt als Hannes: + + \begin{quote} + \T{mutt -f imap://hannes@localhost/} + \end{quote} + + +\section{Administration}\label{p:admin} + + Zur Administration des Mailsystem liegt ein einfach(!) zu benutzender + Script \T{ma} vor. Die aktuelle Dokumentation dazu ist \N{ma(8)} zu entnehmen. + + + \subsection{Accounts} + + \subsubsection{Anzeigen} + + Vorhandene Accounts können angezeigt werden: + + \begin{quote} + \T{ma \U{account} --ls}\\ + \T{ma \U{account} --ls `h*'} + \end{quote} + + \subsubsection{Anlegen}\label{ss:account} + + Beim Anlegen werden nur Accounts und Mailboxen angelegt, wenn sie + noch nicht da sind. In bestehende Accounts werden nur die + Mail-Attribute (\N{tcbMailPrimaryAddress}, \N{tcbMailAddress}) eingetragen, + wenn Sie noch nicht vorhanden sind. Andernfalls gehen wir davon + aus, daß es kein Neueintrag ist. + + \begin{quote} + \T{ma \U{account} \U{--add} --fullname='Hans Hanson' --other='hh' hans.hanson} + \end{quote} + + Weitere Optionen sind: + + \begin{description} + \item[\T{--address=s}] Primäre Mailadresse (default: entspricht dem Account-Namen) + \item[\T{--fullname=s}] Klartext-Name (default: -) + \item[\T{--other:s}] Weitere alternative Mailadressen für \emph{diesen\/} Nutzer (default: -) + \item[\T{--imap\_quota=i}] Imap-Quota in MegaByte (default: 300\,MB) + \item[\T{--[no]mbox}] Anlegen der Mailbox (default: Mailbox wird angelegt) + \item[\T{--password=s}] Passwort (default: \{pwgen\}, d.h.\ es wird generiert und + dann angezeigt) + \item[\T{--group:s}] Zugehörigkeit zu Mailgruppen. + \end{description} + + + \subsubsection{Verändern}\label{ss:change} + + Vorhandene Einträge können natürlich auch verändert werden: + + \begin{quote} + \T{ma \U{account} \U{--modify} --fullname='Hans-Georg Hanson' hans.hanson}\\ + \end{quote} + + Für einige Attribute ist eine kumulative Änderung möglich (z.B. + Hinzufügen von alternativen Namen), bei anderen (primary + Mailadresse) nur ein Verändern. Der Präfix \T{+} oder \T{-} ist + wichtig, ohne den Präfix wird das Attribut \emph{genau\/} auf den + angegebenen Wert gesetzt: + + \begin{quote} + \T{ma \U{account} \U{--modify} --also=+hans,-hh hans.hanson} \\ + \T{ma \U{account} \U{--modify} --also=h.hanson hans.hanson} + \end{quote} + + Veränderbar sind die selben Optionen wie oben + (\ref{ss:account}).\footnote{Außer die Mailbox-Option, das ist + noch nicht implementiert.} + + \subsubsection{Löschen} + + \emph{Vorsicht.\/} Es wird rigoros der komplette LDAP-Eintrag + gelöscht! Und bei Bedarf die Mailbox. + + \begin{quote} + \T{ma \U{account} \U{--delete} hans.hanson}\\ + \T{ma \U{account} \U{--delete} --nombox hans.hanson}\\ + \end{quote} + + Die zweite Zeile würde die Mailbox bestehen lassen. + Optionen gibt es keine weiteren. + + \subsection{Aliase} + + \subsubsection{Anzeigen} + + Alle vorhandenen Aliase werden in einem bequemen + \T{/etc/aliases}-Format ausgegeben: + + \begin{quote} + \T{ma \U{alias} --list} \\ + \T{ma \U{alias} --list `h*'} + \end{quote} + + \subsubsection{Anlegen} + + Beim Anlegen wird der Eintrag nur gemacht, wenn er noch nicht + vorhanden ist. + + \begin{quote} + \T{ma \U{alias} \U{--add} --members=hans,suse fam.ente} + \end{quote} + + Das trägt einen Alias \N{fam.ente} ein, der als Verteiler wirkt + und die Mails an \N{hans} und \N{suse} weiterleitet. + + Weitere Optionen gibt es nicht. + + \subsubsection{Verändern} + + Zum Verändern gilt das selbe wie schon in~\ref{ss:change} zu den + Accounts. Hinzufügen von Mitgliedern und Entfernen von + Mitgliedern ist möglich. Dabei sind auch \qq{Masseneintragungen} + möglich: + + \begin{quote} + \T{ma \U{alias} \U{--modify} --members=+big.brother '*'} + \end{quote} + + \subsubsection{Löschen} + + Der gesamt Alias-Eintrag wird entfernt. + + \begin{quote} + \T{ma \U{alias} \U{--delete} fam.ente} + \end{quote} + + +\section{Zugangsdaten}\label{p:Zugangsdaten} +\index{Passwort} + + \subsection{LDAP} + + \begin{tabular}{ll} + Server:Port & mail.tcb.deutschepost.de:389 (Plain + STARTTLS)\\ + & mail.tcb.deutschepost.de:636 (SSL) \\ + Base-DN & \T{dc=tcb,dc=deutschepost,dc=de} \\ + Admin-User (Bind-DN) & \T{cn=admin,dc=tcb,dc=deutschepost,dc=de}\\ + Admin-Passwort & \T{anfang} \\ + \end{tabular} + + \subsection{IMAP} + + \begin{tabular}{ll} + Server:Port & mail.tcb.deutschepost.de:143 \\ + Admin-User & \T{cyrus} \\ + Admin-Passwort & \T{anfang} \\ + Korrespondierender LDAP-Eintrag & \T{uid=cyrus,ou=Cyrus,\dots} \\ + \end{tabular} + + Um die nervigen Fragen nach Passworten\index{Passwort} für LDAP und IMAP zu umgehen, + können die Passworte in Umgebungsvariablen abgelegt werden: \T{export + IMAP\_PASS=XXX} und \T{export LDAP\_PASS=XXX}. + +\printindex + +\vfill +Stand: \verb!$Id$!\\ +Quelle: \verb!$URL$! + + +\end{document} + +% vim:sts=2 sw=2 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 mergePasswdShadowAliases --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mergePasswdShadowAliases Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,137 @@ +#! /usr/bin/perl +# $Id$ +# $URL$ + +# passwd + shadow + aliases > uid:gecos:[alias ...]:password + +use strict; +use warnings; +use IO::File; + +use AppConfig; + +use constant CONFIG => ( + { CASE => 1 }, + passwd => { ARGS => "=s", DEFAULT => "passwd" }, + shadow => { ARGS => "=s", DEFAULT => "shadow" }, + aliases => { ARGS => "=s", DEFAULT => "aliases" }, +); + + +use constant FS => ":"; +use constant RS => "\n"; + + +my $Cf = new AppConfig CONFIG or die; + $Cf->getopt(\@ARGV) or die; + +MAIN: { + + my %visible; + { + # Zuerst müssen wir die Aliase irgendwie in nutzbare Form bringen. + # Wir können nicht einfach sequentiell durch das Aliasfile rennen und die Aliase + # irgendwie verteilen, sondern wir sammen sie erstmal schön, denn am Ende müssen wir ja + # wissen, welche noch nicht aufgelöst sind. + my $aliases = new IO::File $_ = $Cf->aliases or die "Can't open $_: $!\n"; + while (<$aliases>) { chomp; + s/#.*//g; + next if /^\s*$/; + s/[:,]/ /g; + my ($visible, @real) = split; # "hans.hanson hh" + # "root wgs hh" + + foreach (@real) { + $visible{$_}->{$visible} = 1; # hh -> hans.hanson, root + } + } + } + + my %passwords; + { + my $sh = new IO::File $_ = $Cf->shadow or die "Can't open $_: $!\n"; + %passwords = map { (split /:/, $_)[0,1] } <$sh>; + @passwords{keys %passwords} = map { "{crypt}$passwords{$_}" } keys %passwords; + } + + my %users; + { + # Passwort-File ist die primäre Quelle von Information + my $pw = new IO::File $_ = $Cf->passwd or die "Can't open $_: $!\n"; + while (<$pw>) { + my ($name, undef, $uid, undef, $gecos, undef) = split /:/, $_; + + my ($cn, $sn); + ($cn = $gecos) =~ s/,.*//; + ($sn = (split " ", $cn)[-1]); + + $users{$name}->{gecos} = $gecos; + $users{$name}->{uid} = $uid; + $users{$name}->{cn} = $cn || ""; + $users{$name}->{sn} = $sn || ""; + $users{$name}->{aliases} = []; + $users{$name}->{password} = $passwords{$name}; + $users{$name}->{sorter} = $name; + + + # Gleich mal nach dem Alias sehen + if (exists $visible{$name}) { + my $visible = $visible{$name}; + + foreach (keys %$visible) { + if (/$sn/i) { + push @{$users{$name}->{aliases}}, $_; + delete $visible->{$_}; + } + } + } + } + } + + + # delete users with short password or uid < 1000 + delete @users{grep { + $users{$_}->{password} =~ /^{crypt}.$/ + or $users{$_}->{uid} < 1000 + } keys %users}; + + + # Und nun noch die Aliase, die nicht verarbeitet wurden. + { + + # Alle löschen, die nur noch leere Hashs haben + delete @visible{ grep { not scalar %{$visible{$_}} } keys %visible}; + + # keys sind die Ziele(!) aber wir müssen es wieder genau umgekehrt haben! + # so ist es jetzt: + # uid: alternatives + # wgs: bauleitung wieder.platz + + foreach my $name (keys %visible) { + my @aliases = keys %{$visible{$name}}; + foreach (keys %{$visible{$name}}) { + if (not exists $users{$_}) { + $users{$_}->{gecos} = "ALIAS"; + $users{$_}->{password} = ""; + $users{$_}->{cn} = ""; + $users{$_}->{sn} = ""; + $users{$_}->{sorter} = "00$_"; + } elsif ($users{$_}->{gecos} ne "ALIAS") { + die "ALIAS $_ and user $_ conflict!"; + } + push @{$users{$_}->{aliases}}, $name; + } + } + } + + foreach my $name (sort { $users{$a}->{sorter} cmp $users{$b}->{sorter} } keys %users) { + $users{$name}->{aliases} = join ",", @{$users{$name}->{aliases}}; + print join(FS, $name, @{$users{$name}}{qw/password gecos cn sn aliases/}), RS; + } + + print "\n"; + + +} + +# vim:sts=4 sw=4 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 password.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/password.pm Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,19 @@ +package password; + +sub ask($) { + local $| = 1; + return undef if not -t; + + print $_[0]; + system(stty => "-echo"); + my $ans = ; + system(stty => "echo"); + print "\n"; + + chomp $ans; + return $ans; +} + +1; + +# vim:sts=4 sw=4 aw ai sm: diff -r 000000000000 -r 2a5f2464f8c6 populate.ldif --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/populate.ldif Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,25 @@ +# $Id$ +# $URL$ + +dn: ou=Cyrus,o=ahreick +objectClass: organizationalUnit +ou: Cyrus +description: cyrus mail administration objects + +dn: uid=root,ou=Cyrus,o=ahreick +objectClass: simpleSecurityObject +objectClass: account +uid: root +userPassword: {crypt}KycC6akPBSoYY +description: cyrus administrator + +dn: ou=MailAccounts,o=ahreick +objectClass: OrganizationalUnit +ou: MailAccounts +description: all mail users + +dn: ou=MailAliases,o=ahreick +objectClass: OrganizationalUnit +ou: MailAliases +description: global e-mail aliases + diff -r 000000000000 -r 2a5f2464f8c6 split --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/split Fri Nov 04 06:29:26 2005 +0000 @@ -0,0 +1,105 @@ +#! /usr/bin/perl +# © 2005 Heiko Schlittermann +# $Id$ +# $URL$ +# +# Einlesen von Schubis mail.csv und aufteilen in fetchmailrc/users/aliases +# zur bequemen Weiterverarbeitung +my $USAGE = <<'#'; +Usage: {{ME}} [options] ... + --fetchmailrc=s Name of the fetchmailrc file [{{$::Cf->fetchmailrc}}] + --users=s Name of the fetchmailrc file [{{$::Cf->users}}] + --aliases=s Name of the fetchmailrc file [{{$::Cf->aliases}}] + --help + + Hint: use '/dev/null' as file name to supress the generation + $Id$ +# + + +use strict; +use warnings; +use AppConfig; +use File::Basename; +use IO::File; + +use constant ME => basename $0; + +use constant CONFIG => ( + { CASE => 1 }, + fetchmailrc => { DEFAULT => "fetchmailrc", ARGS => "=s" }, + users => { DEFAULT => "users", ARGS => "=s" }, + aliases => { DEFAULT => "aliases", ARGS => "=s" }, + help => { DEFAULT => 0, ARGS => "!" } + +); + +$::Cf = new AppConfig CONFIG; +$::Cf->getopt(\@ARGV); + +if ($::Cf->help) { + $USAGE =~ s/{{(.*?)}}/eval $1/eg; + print $USAGE; + exit; +} + + + +MAIN: { + our $Cf; + my %groups; + + my $fetchmailrc = new IO::File $_ = $Cf->fetchmailrc, "w" or die ME.": Can't open >$_: $!\n" + if $Cf->fetchmailrc; + my $users = new IO::File $_ = $Cf->users, "w" or die ME.": Can't open >$_: $!\n" + if $Cf->users; + + + while (<>) { + $. == 1 and next; + s/\s*$//; + my ($mailto, $popbox, $poppass, $intern, $group, $imapuser, $imappass) + = split /;/; + + # externes Postfach? + if ($fetchmailrc and $popbox) { + print $fetchmailrc "user \"$popbox\" with password \"$poppass\" is $mailto here\n"; + } + + # realser Nutzer? + if ($mailto =~ /^\Q$imapuser\E/) { + my ($first, $last) = split /\./, $imapuser; + $last or $last = $first, $first = ""; + print $users join(":", + $imapuser, + $imappass, + join(" ", $first ? ucfirst($first) : (), ucfirst($last)), + join(",", $imapuser, $last, $first ? substr($first, 0, 1) . ".$last" : ()), + "\n"); + } else { + $mailto =~ s/\@.*//; + push @{$groups{$mailto}}, $imapuser; + } + } + + if ($Cf->aliases) { + my $aliases = new IO::File $_ = $Cf->aliases, "w" or die ME . ": Can't open >$_: $!\n"; + print $aliases map { "$_: ". join(", ", @{$groups{$_}}). "\n" } sort keys %groups; + } + +} + +__END__ + +mailadresse;popmailbox;popmailpw;intern;intern-gruppe;imapaccount;imappw +annika.henze@vw-audi-dresden-reick.de;;;intern;;annika.henze;ahenze +isabell.zeiske@vw-audi-dresden-reick.de;;;intern;;isabell.zeiske;izeiske +julia.micheel@vw-audi-dresden-reick.de;;;intern;;jilia.micheel;jmicheel +mandy.stranz@vw-audi-dresden-reick.de;;;intern;;mandy.stranz;mstranz +susann.kolodziejczyk@vw-audi-dresden-reick.de;;;intern;;susann.kolodziejczyk;skolo +renate.feurich@vw-audi-dresden-reick.de;;;intern;;renate.feurich;rfeurich +rwks01@vw-audi-dresden-reick.de;;;intern;;rwks01;rwks01 +rwks02@vw-audi-dresden-reick.de;;;intern;;rwks02;rwks02 +rwks03@vw-audi-dresden-reick.de;;;intern;;rwks03;rwks03 + +# vim:sts=4 sw=4 aw ai sm: