unser neuer mailadmin 2. Versuch
--- /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
--- /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 >$@
--- /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:
--- /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:
--- /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:
--- /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";
+
+}
--- /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;
+}
+
--- /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 = <STDIN>;
+ system(stty => "echo");
+ print "\n";
+
+ chomp $ans;
+ return $ans;
+}
+
+# vim:sts=4 sw=4 aw ai sm:
--- /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.
--- /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:
--- /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 <hs@schlittermann.de>
+# $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 <hs@schlittermann.de>
+
+#
+
+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:
--- /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<ma> account [--add|--modify|--delete|--list]
+[--address=s]
+[--fullname=s]
+[--group=s]
+[--[no]mbox]
+[--other=s]
+[--password=s]
+I<name>
+
+B<ma> alias [--add|--modify|--delete|--list] [--members=s] I<name>
+
+B<ma> --help
+
+
+=head1 DESCRIPTION
+
+B<ma> ist ein Tool zur Administration vom Mailnutzern, deren Einstellungen im LDAP
+gespeichert sind.
+
+Es gliedert sich in zwei Sub-Kommands: C<account> und C<alias>.
+
+=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<gesamtes> 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<account>
+
+=over 4
+
+=item B<--address>=I<primäre Mailadresse>
+
+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<Klartext-Name>
+
+Der volle Klartextname des Nutzers. Wird nur für Dokumentationszwecke genutzt.
+
+=item B<--group>=I<Gruppenzuhörigkeit>
+
+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<hans> aus der Gruppe C<groupB> und fügt ihn in C<groupA> und C<groupB> ein.
+
+ ma account --modify --group= hans
+
+entfernt alle Gruppenzugehörigkeiten.
+
+ ma account --modify --group=groupA,groupB hans
+
+setzt genau die Zugehörigkeiten C<groupA> und C<groupB>.
+
+=item B<--[no]mbox>
+
+Mailbox-Anlegen oder auch nicht. (Nur beim Neu-Anlegen.)
+
+=item B<--other=>=I<alternative Mailadressen>
+
+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<Passwort>
+
+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<alias>
+
+=over 4
+
+=item B<--members>=I<Liste der Anliasangehörigen>
+
+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<Mailbox entfernt>! Eventuelle Mitgliedschaften des Nutzers in
+Aliasen werden B<auch> 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<muß> gesetzt werden, damit
+B<ma> 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 <hs@schlittermann.de>
--- /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
--- /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 </var/backups/ldap/XXX.bz2 | slapadd \\
+ service ldap start \\
+ }\end{quote}
+ \item 0351.8029981
+ \end{enumerate}
+
+\section{SMTP}
+
+ Um unerwünschtes Mailweiterleiten\index{Relaying} zu verhinden, sind Mails von
+ unauthentifizierten Nutzern nur zugelassen, wenn der Empfänger
+ in einer der bekannten lokalen Domains ist. Zum Senden von Mails an
+ andere Domains ist eine Authentifizierung\footnote{Name/Passwort wie
+ am POP3/IMAP-Server} am Mailserver notwendig.
+
+ \subsection{SMTP-Authentifizerung}
+
+ Es werden die Authentifizierungsverfahren PLAIN und
+ LOGIN unterstützt.
+
+ Authentifizierung ist nur möglich, wenn eine verschlüsselte Verbindung
+ zwischen MUA und MTA besteht. Dafür bietet der MTA \N{STARTTLS} an.
+
+ \subsubsection{Authorisierung}
+
+ Authentifizierte Nutzer dürfen Mails an beliebige Adressen versenden.
+ Dabei wird jedoch immer die \N{PrimaryMailAddress} als deren Absender
+ eingetragen.
+
+ \subsection{Mailrouting}\label{s:mailrouting}
+
+ \begin{enumerate}
+
+ \item Mail an nicht lokale Domains (lokal ist
+ \N{tcb.deutschepost.de}) wird an den Smart-Relay (\N{mimesweeper}
+ über DNS-Auflösung) weitergeleitet.
+
+ \item Es wird im LDAP nach Aliasen für die aktuelle Adresse gesucht (unterhalb
+ \N{ou=MailAliases}). Diese Aliase entsprechen den bisherigen
+ \T{/etc/aliases}. Aliase werden dort ohne angehängte Domain gesucht.
+
+ \item Es wird unterhalb von \N{ou=MailAccounts} gesucht, ob der Adressat eine
+ Mailgruppe ist. Wenn ja, werden alle Gruppenmitglieder zu neuen
+ Adressaten.
+
+ \item Es wird unterhalb von \N{ou=MailAccounts} gesucht, ob
+ es für den Adressaten eine Mailbox/UID gibt.
+
+ \item Es wird unterhalb von \N{ou=MailAccounts} gesucht, ob für den
+ Adressaten/UID Weiterleitungsadressen existieren. Wenn ja, werden diese
+ eingesetzt und das Routing beginnt von vorn.
+
+ \item Wenn dieser Schritt erfolgreich ist, wird die Mail an den
+ POP3/IMAP-Server übergeben.
+
+ \end{enumerate}
+
+
+ \subsection{Adress-Rewriting}
+
+ In ausgehenden Mails (zum Mimesweeper) werden alle Absender-Adressen
+ so umgeschrieben, daß als Absender (in \N{From:} und \N{Sender:} und auch im
+ \N{Return-Path} die primäre Adresse des authentifizierten Nutzers steht.
+
+ Enthält die Primäre Adresse kein \T{@}, dann wird der Return-Path
+ auf \T{$<>$} 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:
--- /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:
--- /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 = <STDIN>;
+ system(stty => "echo");
+ print "\n";
+
+ chomp $ans;
+ return $ans;
+}
+
+1;
+
+# vim:sts=4 sw=4 aw ai sm:
--- /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
+
--- /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 <hs@schlittermannn.de>
+# $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: