Archiv der Kategorie: Linux

SRS / DKIM per qmail-remote wrapper Script

Ohne SPF/SRS und DKIM ist es inzwischen fast unmöglich Mail an Google oder GMX zu senden. Support für SRS soll im nächsten sqmail Release nativ kommen aber nachdem ich für DKIM sowieso einen Wrapper brauchte habe ich gleich beides integiert. Das nachfolgende Script wird an die Stelle des Original qmail-remote binary kopiert.

Ich DKIM nutze ich nur EINEN Schlüssel für alle Domains und SRS verwendet anstatt der Originaldomain ebenfalls eine systemweite Domain weil es einfacher war das dem vorgeschalteten Spam-Filter beizubringen.

#!/usr/bin/perl 
#
# Based on dkimsign.pl written by Jason Long, jlong@messiah.edu.

use strict;
use warnings;

use Net::DNS;
use Data::Dumper;
use Mail::SRS;
use Mail::DKIM::Signer;
use Mail::DKIM::TextWrap;
use Pod::Usage;
use File::Temp;
use Log::Log4perl qw(:easy);
my $expiration;
my $identity;

my @args = @ARGV;

Log::Log4perl->easy_init( { level => $INFO,
                            file    => ">>/tmp/qmail-remote.log" } );
 
# check if sender is from a local domain
my $sender = $args[1] || '';
#my ($nn, $domain) = $sender =~ m{ \@([\w-]+\.)*([\w-]+\.[\w-]+) \z}xms;
my $local;
my $domain = '';
($local, $domain) = split /\@/, $sender;

eval{
DEBUG("Sender is $sender - Domain is $domain");
if (!is_local_domain($domain)) {
    # Not a local domain -> add srs but not for postmaster
    if ($sender =~ /^postmaster\@/) {
        DEBUG("Remote postmaster address $sender");
        $domain = "";
    } elsif(need_spf($domain)) {
        $domain = "serverpilot.net";
        my $srs = new Mail::SRS( Secret  => "ReplaceMe" );
        # sender is second argument in @args
        $args[1] = $srs->forward($sender, 'mail@srs.serverpilot.net');
        INFO("SRS $sender -> " . $args[1]);
    }
}

my $cmd;
my $fh_msg = new File::Temp();
my $fh_head; # must be defined here to avoid early deletion of tempfile
# disables DKIM for forwarded postmaster mail 
if (!$domain) {
    DEBUG("No DKIM");
    while ()
    {
        print $fh_msg $_;
    }
    $cmd = "/var/qmail/bin/qmail-remote.orig @ARGV < $fh_msg";
} else {
    my $dkim = new Mail::DKIM::Signer(
        Policy => \&signer_policy,
        Algorithm => 'rsa-sha1',
        Method => 'relaxed',
        Selector => 'serverpilot',
        KeyFile => "/var/qmail/dkim/rsa.private",
    );

    while ()
    {
        print $fh_msg $_;
        {
            chomp $_;
            s/\015?$/\015\012/s;
        }
    $dkim->PRINT($_);
    } 

    $dkim->CLOSE;

    $fh_head = new File::Temp();
    my $dkim_sig = $dkim->signature->as_string;
    $dkim_sig =~ s{\r}{}gxms;
    print $fh_head $dkim_sig . "\n";
    close $fh_head;
    INFO("DKIM added for $sender");
    DEBUG("DKIM Sig: " . $dkim_sig );

    $cmd = "cat $fh_head $fh_msg | /var/qmail/bin/qmail-remote.orig @args";
}

die "Error handling message" unless($cmd);
system($cmd) && die "DKIM Error ($cmd)";
};

if ($@) {
    die "Z$@";
}

sub signer_policy
{
    my $dkim = shift;

    use Mail::DKIM::DkSignature;

    $dkim->domain($domain);

    my $sig = Mail::DKIM::Signature->new(
            Algorithm => $dkim->algorithm,
            Method => $dkim->method,
            Headers => $dkim->headers,
            Domain => $dkim->domain,
            Selector => $dkim->selector,
            defined($expiration) ? (Expiration => time() + $expiration) : (),
            defined($identity) ? (Identity => $identity) : (),
        );
    $dkim->add_signature($sig);
    return;
}

sub is_local_domain {

    my $domain  = shift || '';
return 1;
    return unless ($domain);

    $domain =~ s/\./\\./;
    my $pattern = '\A\+'.$domain.'-';

    open(my $users, "    my $found = 0;
    while (<$users>) {
        if ($_ =~ qr/$pattern/) {
            $found = 1;
            last;
        }
    }
    close $users;
    return $found;
}


sub need_spf {

    my $domain  = shift || '';

    # fail safe
    return 1 unless ($domain);

    my $res = Net::DNS::Resolver->new(
        tcp_timeout => 2,
        udp_timeout => 2,
    );
    my $reply = $res->search( $domain, "TXT");
    if (!$reply) {
        WARN('DNS Query failed for domain ' . $domain);
 DEBUG('DNS Error: ' . $res->errorstring);
        return 1;
    }
       
    my $need_srs = 0;
    foreach my $rr ($reply->answer) {
        if ($rr->can('txtdata')) {
            if ($rr->txtdata =~ /\Av=spf/) {
 DEBUG('SPF Record found ' . $rr->txtdata);

                # if rackport is in spf record stop checks - its ok
 return 0 if ($rr->txtdata =~ /spf\.rackport\.net/);
 
 # non rackport spf record found, so srs is needed
                $need_srs = 1;
            }
        }
    }
    return $need_srs;

}

__END__

Um die SRS Rückläufer wieder an den Originalempfänger zuzustellen, wird für die SRS Domain ein .qmail-default Eintrag angelegt der alle eingehenden Mails an ein Maildrop Filter weiterleitet (| maildrop ~vpopmail/etc/mailfilter.srs-reverse ).
Das Filterscript stellt die Mails per qmail-inject zu bzw. schiebt diese in eine Sammelmailbox falls das SRS Decoding schief geht:

NEWRCPT=/usr/local/sbin/srsr.pl $EXT
if ($NEWRCPT) {
to "| /var/qmail/bin/qmail-inject $NEWRCPT"
}
to "/var/vpopmail/domains/5/srs.serverpilot.net/bounces"

Das SRS Script selber verwendet wieder Mail::SRS:

#!/usr/bin/perl

use strict;
use Mail::SRS;

my $rcpt = shift || '';
eval {
    my $srs = new Mail::SRS( Secret  => "SRSSecret" );
    print $srs->reverse($rcpt.'@srs.serverpilot.net');
} if ($rcpt =~ /\ASRS/);
# Do not handle non SRS Mail

vpopmail

Vpopmail ist zwar auch schon steinalt und etwas „rostig“ aber es funktioniert noch immer 😉
Die Quellen gibt es auf Sourceforge zum Download: https://sourceforge.net/projects/vpopmail/

# Benutzer und Gruppe anlegen
groupadd -g 89 vpopmail
useradd -g vpopmail -u 89 vpopmail

# Da ich ein Altsystem mit Bestand migriere benötige ich einen abweichenden Verzeichnisnamen für das Maildir:
sed -i -e 's|Maildir|.maildir|g' \
      vchkpw.c vconvert.c vdelivermail.c \
      vpopbull.c vpopmail.c vqmaillocal.c \
      vuserinfo.c maildirquota.c || die

# Konfigurieren
./configure\
--enable-auth-module=mysql\
--enable-sql-logging\
--enable-valias\
--enable-mysql-limits\
--enable-qmaildir=/var/qmail\
--enable-qmail-newu=/var/qmail/bin/qmail-newu\
--enable-qmail-inject=/var/qmail/bin/qmail-inject\
--enable-qmail-newmrh=/var/qmail/bin/qmail-newmrh\
--enable-vpopuser=vpopmail\
--enable-vpopgroup=vpopmail\
--enable-file-locking\
--enable-md5-passwords\
--enable-logging\
--enable-auth-logging\
--enable-log-name=vpopmail\
--enable-qmail-ext\
--disable-tcpserver-file\
--disable-roaming-users\
--disable-clear-passwd\
--enable-libdir=/usr/lib\
--enable-domainquotas\
--disable-users-big-dir

# Die lokale Mailzustellung soll per dovecot-lda erfolgen - das ginge zwar auch über alias Einträge und/oder maildrop, geht aber auch über einen kleinen Trick nativ aus vdelivermail und reduziert dabei auch noch die Prozessanzahl.
echo '
#define MAILDROP 1
#define MAILDROP_PROG "-f /usr/lib/dovecot/dovecot-lda -d $EXT@$HOST"
' >> config.h

# Jetzt noch bauen und installieren
make && make install

# Da wir eine mysql Datenbank als Backend benutzen wollen, müssen wir nun noch die SQL Zugangsdaten in der Datei /home/vpopmail/etc/vpopmail.mysql eintragen. Der User muß zumindest zum Start das Recht haben Tabellen anzulegen.

# Jetzt Domain und User anlegen und eine Testmail senden
vadddomain -r acme.test
vadduser -r mail@acme.test
echo "Hallo" | /var/qmail/bin/sendmail  -v mail@acme.test

# Die Mail sollte nun unter /home/vpopmail/domains/acme.test/mail/.maildir ankommen


Dovecot

Die verschiedenen Dovecot-Komponenten erledigen hier folgenden Aufgaben:
* POP/IMAP Server
* lokale Mailzustellung mit dovecot-lda
* Mailfilter / Abwesenheitsnachrichten per Sieve
* Authentication Backend für SMTP User
cat > /etc/dovecot/dovecot-sql.conf.ext <<EOF
driver = mysql 
connect = host=dbserver dbname=vpopmail user=vpopmail password=secret
default_pass_scheme = SSHA256 

password_query = \
  SELECT pw_name as username, pw_domain as domain, pw_passwd as password, pw_dir as userdb_home \
  FROM vpopmail WHERE pw_name = '%n' AND pw_domain = '%d'

user_query = SELECT pw_dir as home \
  FROM vpopmail WHERE pw_name = '%n' AND pw_domain = '%d'

iterate_query = SELECT concat(pw_name,'@',pw_domain) AS user FROM vpopmail
cat > /etc/dovecot/conf.d/99-local.conf <<EOF
# 2.2.27 (c0f36b0): /etc/dovecot/dovecot.conf
# Pigeonhole version 0.4.16 (fed8554)
doveconf: Warning: service auth { client_limit=1000 } is lower than required under max. load (1250)
doveconf: Warning: service anvil { client_limit=1000 } is lower than required under max. load (1003)
# OS: Linux 4.15.18-9-pve x86_64 Debian 9.6 
auth_cache_negative_ttl = 0
auth_cache_size = 50 M
auth_debug = yes
auth_mechanisms = plain cram-md5
auth_verbose = yes
auth_verbose_passwords = sha1:8
debug_log_path = /var/log/dovecot/debug.log
default_process_limit = 250
disable_plaintext_auth = no
first_valid_uid = 89
last_valid_uid = 89
log_path = /var/log/dovecot/dovecot.log
login_greeting = Serverpilot.net Mail Service
mail_gid = 89
mail_home = maildir:/var/mail/vhosts/%d/%n
mail_location = maildir:~/.maildir
mail_plugins = quota
mail_uid = 89
maildir_very_dirty_syncs = yes
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext
namespace inbox {
  inbox = yes
  location = 
  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }
  prefix = 
}
passdb {
  driver = pam
}
passdb {
  args = /etc/dovecot/dovecot-sql.conf.ext
  driver = sql
}
plugin {
  quota = maildir:User quota
  quota_rule = *:storage=10M
  quota_rule2 = Trash:storage=+1M
  sieve = /home/vpopmail/domains/%d/%n/sieve/active-script.sieve
  sieve_default = /home/vpopmail/sieve/default.sieve
  sieve_default_name = Standardfilter
  sieve_dir = /home/vpopmail/domains/%d/%n/sieve/scripts/
}
protocols = imap pop3 sieve
service auth {
  unix_listener auth-master {
    group = vpopmail
    mode = 0600
    user = vpopmail
  }
  unix_listener auth-qmail {
    group = nofiles
    mode = 0600
    user = qmaild
  }
}
service imap-login {
  process_min_avail = 5
  service_count = 0
}
ssl = no
ssl_cert = </etc/dovecot/ssl/serverpilot.net.crt
ssl_cipher_list = ALL:!EXPORT:!LOW:!MEDIUM:!aNULL:+RC4:@STRENGTH
ssl_dh_parameters_length = 2048
ssl_protocols = TLSv1.2 TLSv1.1 SSLv3 TLSv1
userdb {
  driver = passwd
}
userdb {
  args = /etc/dovecot/dovecot-sql.conf.ext
  driver = sql
}
protocol lda {
  auth_socket_path = /var/run/dovecot/auth-master
  debug_log_path = /var/log/dovecot/lda-debug.log
  hostname = mail.serverpilot.net
  log_path = /var/log/dovecot/lda.log
  mail_plugin_dir = /usr/lib/dovecot/modules
  mail_plugins = quota sieve
  postmaster_address = nic@rackport.net
  sendmail_path = /var/qmail/bin/sendmail
}
protocol imap {
  mail_max_userip_connections = 40
}

EOF
# dovecot-lda läuft als Benutzer vpopmail und muss hier ein log schreiben!
mkdir -m775 /var/log/dovecot
chown vpopmail /var/log/dovecot

Sieve

Zum Vorsortieren von Spam bzw. zum Einrichten von Abwesenheitsnachrichten soll die Filtersprache Sieve zum Einsatz kommen. Über den ManageSieve Dienst können die Nutzer dies selber einrichten (per Plugin in Roundcube) – damit die User einen Startpunkt haben, stellen wir ein Muster-Script bereit.

mkdir /home/vpopmail/sieve
cat > /home/vpopmail/sieve/default.sieve <<EOF
require ["fileinto","vacation"];
# rule:[Urlaub]
if false # true
{
	vacation :subject "Abwesenheitsnachricht" "Ich befinde mich derzeit im Urlaub, ihre Mail wird nicht gelesen oder weitergeleitet.";
}
# rule:[Spam]
if false # header :is "X-Spam-Status" "Yes"
{
	fileinto "Junk";
}
EOF
# Das Script muss dann noch kompiliert werden
sievec /home/vpopmail/sieve/default.sieve

Weiter gehts danach mit der Einrichtung von vpopmail

Basissystem und sqmail

System vorbereiten, sqmail mit Abhängigkeiten installieren

# Repos und Pakete aktualisieren
aptitude update && aptitude upgrade

# Postfix entfernen
aptitude remove postfix

# Abhängigkeiten und ein paar Tools installieren
aptitude install git vim build-essentials libssl1.0-dev libperl-dev libmysql++-dev

# Daemontools und Dovecot Pakete
aptitude install daemontools daemontools-run \
    dovecot-core dovecot-imapd dovecot-pop3d \
    dovecot-mysql dovecot-managesieved  dovecot-sieve 

# Quellpakete von sqmail runterladen
mkdir /usr/local/src && cd /usr/local/src
wget --no-check-certificate \
   https://www.fehcom.de/sqmail/sqmail-3.3.22.tgz \
   https://www.fehcom.de/ipnet/ucspi-tcp6/ucspi-tcp6-1.10.5.tgz \
   https://www.fehcom.de/ipnet/ucspi-ssl/ucspi-ssl-0.10.6.tgz \
   https://www.fehcom.de/ipnet/fehQlibs/fehQlibs-10.tgz

# Das Debianpaket benutzt /etc/service - sqmail erwartet die Dateien aber in /service also machen wir einen symlink
ln -s /etc/service /service

# fehQlibs installieren
cd /usr/local
tar -axf fehQlibs-10.tgz
ln -s fehQlibs-10 /usr/local/qlibs
cd qlibs && make && make install

# ucspi und sqmail
cd /package
tar -axf /usr/local/src/ucspi-tcp6-1.10.5.tgz
tar -axf /usr/local/src/ucspi-ssl-0.10.9.tgz 
tar -axf /usr/local/src/sqmail-3.3.23.tgz
cd /package/net/ucspi-tcp6/ucspi-tcp6-1.10.5/
package/install
cd /package/host/superscript.com/net/ucspi-ssl-0.10.9/
package/install
cd /package/mail/sqmail/sqmail-3.3.23/
package/install
# Wer bin ich - das muss der FQDN sein!
cd /var/qmail/control
hostname -f > me 
# FQDN und Kurzname als lokale Adressen qualifzieren
hostname >> locals 
hostname -f >> locals
# root und postmaster Konto einem lokalen User zuweisen und leeres maildir anlegen
echo "myuser" > /var/qmail/alias/.qmail-root
echo "myuser" > /var/qmail/alias/.qmail-postmaster
maildirmake.dovecot ~myuser/.maildir myuser

# Script für qmail-send anpassen (/service/qmail-send/run)
#!/bin/sh
exec env - PATH="/var/qmail/bin:$PATH" \
     qmail-start ./.maildir/

# Daemontools starten und qmail-send hochfahren
service daemontools start
svc -u /service/qmail-send

# Ausprobieren ;)
echo "Hallo" | /var/qmail/bin/sendmail  -v myuser

Weiter gehts danach mit der Einrichtung von Dovecot

Resize lvm/drbd

Vergrößern eines ext-Filesystems im laufenden Betrieb:

lvresize -L40G main/www1 (auf beiden Knoten)
drbdadm resize www1 (auf dem primary Knoten)

Jetzt wird die Bitmap neu syncronisiert, dauert also!

Wenn aufs im Einsatz: Gast-Server beenden, Partition aufs backup mounten und resize2fs /dev/drbd/www1 – ohne mount wird ein offline resize gemacht, das erfordert einen fsck und das dauert ewig.

Wenn aufs nicht im Einsatz ist kann direkt am Live-Server ein resize gemacht werden.

Elster und Java Verschlüsselung

Aufgrund der immer noch herrschenden Export-Regulierungen der USA wird bei der Installation von Java nur ein eingeschränktes Verschlüsselungsmodul mitgeliefert. Es gibt zwar eine automatische Update Funktion, die bei mir aber nie wirklich funktioniert….

Die typische Fehlermeldung beim Verbindungsaufbau mit dem ELSTER Server lautet „Die Verschlüsselungsleistung der installierten Java-Runtime (JavaVM) ist nicht ausreichend für den Einsaz der aktuellen ELSTER-Version. Um die Steuerdaten per ELSTER übertragen zu können, ist eine Aktualisierung der Java-Runtime notwendig.“.

Der manuelle Trick ist denkbar einfach, man muss nur die beiden jar-Files „local_policy“ und „US_export_policy“ im Ordner “ Programme/Java/JRE6/lib/Security“ durch eine Version mit starker Verschlüsselung ersetzen., die meisten Produkte die ELSTER einsetzen liefern passende Dateien bei der Installation mit. Bei der Software „Monkey Office/Monkey Bilanz“ von Prosaldo (Super Software und toller Support im Übrigen!) liegen die Dateien unter C:\Programme\ProSaldo\MonKey Bilanz 2010\Elster\JAVA1.6\jce.

Bulk Import von Usern nach Joomla

Bulk-Import von Usern in die Joomla Datenbank:

insert into jos_users (id , name , username , email , password , usertype , block , sendEmail , gid)
select id, company, kdnr, email, concat_ws(':',md5(concat(pass,md5('SALT'))), md5('SALT')), 'Registered', 0,0,18 from company
insert into jos_core_acl_aro (id, section_value, value, name)
select id, 'users', id, company from company
insert into jos_core_acl_groups_aro_map (group_id, aro_id)
select 18, id from company

Das Salt für das gehashte Passwort ist unschönerweise für jeden User gleich, anstatt dem festen String ‚SALT‘ kann auch eine Spalte aus der Tabelle verwendet werden.

Vorschaubilder und Wasserzeichen mit Image Magick

Erzeugen von Vorschaubildern fester Größe durch auffüllen mit einer Hintergrundfarbe und Einbindung eines Wasserzeichens. Die Orginalbilder liegen im Ordner raw/, die Thumbnails kommen nach resized/ und die Fullsize Daten nach full/.

#!/bin/bash
# Klappt auch mit Leerzeichen im Dateinamen!
BASE=$@
SRC=`basename "$BASE"`
TGT=${SRC%.*}".jpg"
echo -n "Processing $SRC";
convert "raw/$SRC" -resize 90x90 -background white -gravity center -extent 90x90 "resized/$TGT"
echo -n .
composite -watermark 5% -gravity center watermark_small.png "resized/$TGT" "resized/$TGT"
echo -n .
convert "raw/$SRC" -resize 800x800 "full/$TGT"
echo -n .
composite -watermark 10% -gravity center watermark.png "full/$TGT" "full/$TGT"
echo !

neue vServer bauen

Das hier wird vermutlich keinem weiterhelfen, da es sich auf meinen recht speziellen vServer/Cluster Setup bezieht.

1: Config für vServer anlegen

/usr/sbin/vserver <name> build -m skeleton --context <=letztes Byte der internen IP> --hostname <name>.rackport.net --interface eth1:192.168.x.x/24 --initstyle plain --
mkdir /vservers/_overlays/<name>
mkdir /vservers/_rootfs/<name>

Das Script /vservers/build.sh <name> <id> macht das alles in einem Rutsch.

Anschliessend nach Bedarf externe Interfaces anlegen (Verzeichnisse 3x für vlan3 und 4x für vlan4), rules Datei für Firewall anlegen/kopieren!

2. Overlay Device anlegen

  • lvcreate main -L5G -n<name>
  • Device Eintrag in drbd.conf anlegen
  • Disk Alias in /etc/udev/rules.d/99-diskalias.rules
  • drbd config reload
  • drbdsetup /dev/drbd/<name> primary -o (das -o = „force“ und fährt das Device auch bei fehlendem Peer hoch)
  • udevadm trigger
  • mkfs.ext3 /dev/drbd/<name>

3. Hostname vergeben:

Overlay Device mounten und /etc/conf.d/hostname bearbeiten: hostname=“wwwdev.rackport.net“