<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[MacFrog]]></title><description><![CDATA[Just a blog.]]></description><link>https://blog.macfrog.de/</link><image><url>https://blog.macfrog.de/favicon.png</url><title>MacFrog</title><link>https://blog.macfrog.de/</link></image><generator>Ghost 1.9</generator><lastBuildDate>Tue, 26 Jun 2018 15:34:24 GMT</lastBuildDate><atom:link href="https://blog.macfrog.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[dehydrated eingedampft]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Vor ein paar Tagen erreichte mich ein Kommentar  zu meinem <a href="https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/2016/01/10/lets-encrypt/">Let's-Encrypt-Setup</a>: die Datei <code>hook.sh</code> muss um die Kommandos <code>unchanged_cert</code> und <code>exit_hook</code> ergänzt werden.</p>
<p>Ja, ich weiß - es ist schon mehr als nur ein paar Tage her, dass <a href="http://letsencrypt.sh">letsencrypt.sh</a> zu <a href="https://github.com/lukas2511/dehydrated">dehydrated</a> umbenannt wurde. Und ich habe</p></div>]]></description><link>https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c2c</guid><category><![CDATA[Let's Encrypt]]></category><category><![CDATA[SSL]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Tue, 25 Apr 2017 12:48:11 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Vor ein paar Tagen erreichte mich ein Kommentar  zu meinem <a href="https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/2016/01/10/lets-encrypt/">Let's-Encrypt-Setup</a>: die Datei <code>hook.sh</code> muss um die Kommandos <code>unchanged_cert</code> und <code>exit_hook</code> ergänzt werden.</p>
<p>Ja, ich weiß - es ist schon mehr als nur ein paar Tage her, dass <a href="http://letsencrypt.sh">letsencrypt.sh</a> zu <a href="https://github.com/lukas2511/dehydrated">dehydrated</a> umbenannt wurde. Und ich habe die dafür notwendigen Änderungen bei mir lokal auch vorgenommen. Aber ich habe sie hier nicht dokumentiert. Schande über mich.</p>
<p>Jedenfalls hat mich <a href="https://square.wf/">Freddy Leitner</a><sup class="footnote-ref"><a href="https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/#fn1" id="fnref1">[1]</a></sup> netterweise auf diesen Lapsus aufmerksam gemacht. Er hat außerdem angemerkt, dass man die gesamte Infrastruktur noch ein wenig verschlanken kann:</p>
<h5 id="hooksh"><a href="http://hook.sh">hook.sh</a></h5>
<p>Die <code>hook.sh</code> muss bei <a href="https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/2016/01/10/lets-encrypt/">unserem Setup</a> nur auf genau einen <em>Hook</em> reagieren, nämlich <code>deploy_cert</code>. Alle anderen können wir getrost ignorieren. Um genau zu sein, müssen wir das sogar - für den Fall, dass wie oben beschrieben weitere <em>Hooks</em> hinzukommen. Damit schnurrt die Datei auf folgende sechs Zeilen zusammen:</p>
<pre><code class="language-language-bash">#!/usr/bin/env bash
if [ &quot;${1}&quot; == &quot;deploy_cert&quot; ] ; then
  uberspace-add-certificate -k ${3} -c ${4}
else
  exit 0
fi
</code></pre>
<p>Bei der Gelegenheit habe ich gleich noch das veraltete <code>uberspace-prepare-certificate</code> in den aktuellen Befehl <code>uberspace-add-certificate</code> geändert. Außerdem konnte ich auch die Parameter-Überprüfung entfernen. Der Rückgabewert eines Shell-Skripts ist ja gleich dem Rückgabewert des letzten ausgeführten Befehls. Und im Zweifel macht sich <code>uberspace-add-certificate</code> schon passend bemerkbar.</p>
<h5 id="configsh"><a href="http://config.sh">config.sh</a></h5>
<p>Bei der <code>config.sh</code> ist das Verhältnis von Kommentaren zu Nutzlast noch größer. Hier kommen wir mit insgesamt nur drei Zeilen aus:</p>
<pre><code class="language-language-">WELLKNOWN=&quot;/var/www/virtual/${USER}/html/.well-known/acme-challenge&quot;
HOOK=&quot;${BASEDIR}/hook.sh&quot;
CONTACT_EMAIL=&quot;${USER}@${HOSTNAME}&quot;
</code></pre>
<p>Diese Version der Konfigurationsdatei hat jetzt darüber hinaus den Vorteil, dass sie unverändert mit jedem beliebigen Account funktionieren sollte. Die verwendeten Variablen werden jedenfalls beim Aufruf über <a href="https://wiki.uberspace.de/system:runwhen">runwhen</a>  korrekt gesetzt. Ob das auch als <a href="https://wiki.uberspace.de/system:cron">Cronjob</a> funktioniert, kann ich mangels passendem Setup im Moment nicht überprüfen. Your mileage may vary.</p>
<p>An dieser Stelle nochmal vielen Dank an Freddy! Und natürlich an alle anderen Leser - die, die mir Kommentare schreiben, und die, die einfach nur so vorbeischauen und meine Notizen nützlich finden.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Ich wollte mich bei Freddy ja gerne mit einem Link revanchieren, aber sein Blog ist wohl momentan noch in Arbeit. :-) <a href="https://blog.macfrog.de/2017/04/25/dehydrated-eingedampft/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</div>]]></content:encoded></item><item><title><![CDATA[Let's Encrypt!]]></title><description><![CDATA[<div class="kg-card-markdown"><p><strong>Update:</strong> Ich habe die <code>hook.sh</code> und die <code>config.sh</code> verschlankt und an die neue version von <code>letsencrypt.sh</code> namens <code>dehydrated</code> angepasst. Die Details findet ihr in einem <a href="https://blog.macfrog.de/2016/01/10/lets-encrypt/2017/04/25/dehydrated-eingedampft/">eigenen Blogpost</a>.</p>
<hr>
<p>Nachdem <a href="https://letsencrypt.org/">Let's Encrypt</a> auch von <a href="https://uberspace.de/">Uberspace</a> <a href="https://blog.uberspace.de/lets-encrypt-rollt-an/">unterstützt zu werden beginnt</a>, und ich ja immer nach <a href="https://blog.macfrog.de/2016/01/10/lets-encrypt/2015/04/19/ssl-zertifikate-im-batch-modus-erstellen">Wegen</a> suche, mir das Teilzeitadmin-Leben</p></div>]]></description><link>https://blog.macfrog.de/2016/01/10/lets-encrypt/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c2a</guid><category><![CDATA[Let's Encrypt]]></category><category><![CDATA[SSL]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sun, 10 Jan 2016 20:01:07 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p><strong>Update:</strong> Ich habe die <code>hook.sh</code> und die <code>config.sh</code> verschlankt und an die neue version von <code>letsencrypt.sh</code> namens <code>dehydrated</code> angepasst. Die Details findet ihr in einem <a href="https://blog.macfrog.de/2016/01/10/lets-encrypt/2017/04/25/dehydrated-eingedampft/">eigenen Blogpost</a>.</p>
<hr>
<p>Nachdem <a href="https://letsencrypt.org/">Let's Encrypt</a> auch von <a href="https://uberspace.de/">Uberspace</a> <a href="https://blog.uberspace.de/lets-encrypt-rollt-an/">unterstützt zu werden beginnt</a>, und ich ja immer nach <a href="https://blog.macfrog.de/2016/01/10/lets-encrypt/2015/04/19/ssl-zertifikate-im-batch-modus-erstellen">Wegen</a> suche, mir das Teilzeitadmin-Leben zu vereinfachen, wollte ich mir die automatische Domain-Verifikation von Let's Encrypt zunutze machen. Klingt ja fantastisch: Einfach per Konfiguration einstellen, für welche Domains man Zertifikate haben möchte, und der Let's-Encrypt-Client kümmert sich um alles. Nie wieder per E-Mail nachweisen, dass mir die Domain <a href="http://macfrog.de">macfrog.de</a> immer noch gehört - <em>juhu!</em></p>
<h3 id="welchenclienthttensdenngern">Welchen Client hätten's denn gern?</h3>
<p>Allerdings habe ich mit dem Standard-Client so meine Probleme. Das fängt damit an, dass das Teil root-Rechte haben möchte. Ich stimme <a href="https://blog.uberspace.de/zum-stand-von-lets-encrypt/">Jonas</a> da voll zu: Es gibt exakt <em>überhaupt keinen</em> Grund, warum das so sein müsste. Muss es ja auch nicht, wie man an Uberspace sieht. Da funktioniert der Standard-Client auch so.</p>
<p>Trotzdem ist mir das Programm nicht ganz geheuer. Erstens ist mein <a href="https://www.python.org/">Python</a> ein wenig... ähm... eingerostet, so dass ich nicht wirklich nachvollziehen kann, was es tut. Und das wüsste ich schon gerne - immerhin geht es hier unter anderem um die privaten Schlüssel zu meinen Website-Zertifikaten. Und außerdem ist mir das Programm für einen Code Review auch ein wenig zu lang.</p>
<p>Ich habe mich also auf die Suche nach einer Alternative gemacht, und bin bei <a href="https://github.com/lukas2511/letsencrypt.sh">letsencrypt.sh</a> fündig geworden. Das ist ein Shell-Skript, das die wesentlichen Funktionen des Original-Clients, nämlich</p>
<ul>
<li>Let's-Encrypt-Account anlegen</li>
<li>Challenge für Domain-Nachweis abwickeln</li>
<li>Zertifikat generieren und unterschreiben lassen</li>
</ul>
<p>genau so gut abwickelt, dabei keine root-Rechte braucht und noch dazu so übersichtlich ist, dass ich mir einigermaßen sicher darüber bin, was es tut.</p>
<p>Allerdings möchte ich nicht verschweigen, dass <a href="http://letsencrypt.sh">letsencrypt.sh</a> noch ziemlich in der Entwicklung steckt. Aber wie's aussieht, ist zumindest die Version vom 08.01.2016 ganz schön stabil. Mir ist zumindest noch kein Fehler aufgefallen. Und Let's Encrypt selber ist ja auch noch in der Beta-Phase.</p>
<h3 id="installation">Installation</h3>
<p>Also nichts wie los, eine Kopie der Sourcen geholt und die wichtigsten Dateien nach <code>~/opt/letsencrypt/</code> kopiert. Das Zielverzeichnis könnt Ihr selbstverständlich frei wählen - ich fand den Platz ganz passend:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">cd ~/src
git clone https://github.com/lukas2511/letsencrypt.sh.git
mkdir ~/opt/letsencrypt
cd ~/opt/letsencrypt
cp ~/src/letsencrypt/letsencrypt.sh .
cp ~/src/letsencrypt/config.sh.example config.sh
cp ~/src/letsencrypt/domains.txt.example domains.txt</code></pre>
<p>Jetzt müssen natürlich noch die Konfigurationsdateien angepasst werden:</p>
<h5 id="domainstxt">domains.txt</h5>
<p>Hier kommen alle Domainnamen rein, für die Zertifikate generiert werden sollen. Falls eine Domain mit mehreren Unterdomains vertreten ist, bietet es sich an, diese als <strong>SAN</strong> (Subject Alternate Name) anzugeben. Diese Alternativnamen kommen in eine Zeile hinter den ursprünglichen Domainnamen. Für mich sieht das dann so aus:</p>
<pre><code class="language-language-">macfrog.de blog.macfrog.de www.macfrog.de
mcfrog.de blog.mcfrog.de www.mcfrog.de
</code></pre>
<p>Bei Euch sollten selbstverständlich Eure eigenen Domainnamen stehen.</p>
<h5 id="configsh"><a href="http://config.sh">config.sh</a></h5>
<p>Hier müsst Ihr auf jeden Fall die Variable <code>WELLKNOWN</code> anpassen und könnt eine <code>CONTACT_EMAIL</code>-Adresse setzen, wenn Ihr das möchtet:</p>
<pre><code class="language-language-bash">WELLKNOWN=&quot;/var/www/virtual/&lt;username&gt;/html/.well-known/acme-challenge&quot;
CONTACT_EMAIL=&quot;&lt;username&gt;@&lt;hostname&gt;.uberspace.de&quot;
</code></pre>
<p>Vergesst nicht, die <code>#</code>-Kommentarzeichen zu entfernen, wenn Ihr Werte setzt, sonst werden die Standardwerte verwendet, und das ist ja gerade nicht das, was Ihr wollt.</p>
<p><code>&lt;username&gt;</code> und <code>&lt;hostname&gt;</code> ersetzt ihr natürlich durch Euren Uberspace-Benutzernamen und den zugehörigen Host. Zumindest was die E-Mail-Adresse angeht, könnt Ihr Euch aber offensichtlich eine beliebige Adresse aussuchen. Das oben ist nur der Wert, den der Standard-Client setzt.</p>
<p>Auch wenn es sich hier so einfach liest, muss ich zugeben, dass mir <code>WELLKNOWN</code> ein bisschen Kopfzerbrechen bereitet hat: Eigentlich wollte ich analog der <a href="https://github.com/lukas2511/letsencrypt.sh#wellknown--challenge-response">Anleitung von auf GitHub</a> den Apache von Uberspace per <code>.htaccess</code> dazu bringen, den Pfad <code>http://macfrog.de/.well-known/acme-challenge/</code> auf ein passendes Verzeichnis umzubiegen. Aber egal, was ich mit <code>.htaccess</code> anstellte - ich konnte keine Inhalte ausspielen. Bis mir klar wurde, dass Uberspace anscheinend tatsächlich wie von Jonas - zunächst nur als Konzept - <a href="https://blog.uberspace.de/zum-stand-von-lets-encrypt/">beschrieben</a> die Zugriffe für <em>jede</em> eingetragene Domain eines Benutzers vor dem Apache abfängt und grundsätzlich aus <code>/var/www/virtual/&lt;username&gt;/html/.well-known/acme-challenge</code> bedient.</p>
<p>Das ist einfach und elegant, weil der Client nur ein konstantes Verzeichnis für die ACME-Challenge bedienen muss, egal welche Domain oder Subdomain überprüft werden soll. Kein Hantieren mit unterschiedlichen Konfigurationen für Subdomains. Hab' ich schon geschrieben, dass es einfach Spaß macht, bei Uberspace zu sein? :-)</p>
<h3 id="automatischezertifikatsinstallation">Automatische Zertifikatsinstallation</h3>
<p>Bis hierhin funktioniert alles ähnlich wie der Standard-Client. <a href="http://letsencrypt.sh">letsencrypt.sh</a> weist aber noch zwei Besonderheiten auf, die es für einen Einsatz auf Uberspace geradezu prädestinieren: <strong>Es überprüft, ob Zertifikate in den nächsten 30 Tagen ablaufen und fordert nur diese neu an.</strong> Soweit ich sehe, kann das der Standard-Client (noch) nicht. Selbstverständlich ist die Zeit bis zum Verfallsdatum einstellbar. Und <strong>es kann ein Skript aufrufen, sobald ein Zertifikat erfolgreich generiert und unterschrieben wurde.</strong></p>
<p>Das schreit doch geradezu danach, an dieser Stelle einen Umweg über <code>uberspace-prepare-certificate</code> zu machen. Zumal die Zertifikatsinstallation bei Uberspace seit neuestem <a href="https://blog.uberspace.de/automatisierter-zertifikatsimport/">vollautomatisch funktioniert</a> und keine E-Mail an den Support mehr benötigt. Und genau diesen kleinen Umweg nehmen wir jetzt auch.</p>
<p>Wie Ihr vielleicht schon in der <code>config.sh</code> gesehen habt, gibt es den Parameter <code>HOOK</code>:</p>
<pre><code class="language-language-bash"># Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: &lt;unset&gt;
HOOK=&quot;${BASEDIR}/hook.sh&quot;
</code></pre>
<p>Die ersten zwei Fälle sind uns relativ egal, aber der dritte Fall ist spannend. Wie Ihr seht, bekommt das <code>HOOK</code>-Script ein Argument übergeben, in welcher Situation es aufgerufen wurde. Uns interessiert hier der Fall <code>deploy_cert</code>, bei dem dann unter anderem die Pfade zum privaten Schlüssel und zum Zertifikat als weitere Parameter angegeben werden. Also genau das, was <code>uberspace-prepare-certificate</code> braucht.</p>
<h5 id="hooksh"><a href="http://hook.sh">hook.sh</a></h5>
<p>Wir legen daher noch folgendes Skript unter <code>~/opt/letsencrypt/</code> (bzw. Eurem Installationsverzeichnis) ab:</p>
<pre><code class="language-language-bash">#!/bin/env bash
#============================================================================
#
# DESCRIPTION
#    This script hooks into letsencrypt.sh and deploys generated
#    certificates to Uberspace.
#
# COMMANDS
#    deploy_challenge  Deploys challenge to system for ACME CA to query
#                      Currently not implemented. Might be used in future
#                      for dns-01 challenge type.
#
#    clean_challenge   Removes previously deployed challenge
#                      Currently not implemented.
#
#    deploy_cert       Deploys generated certificate to Uberspace
#
# EXAMPLES
#    hook.sh deploy_cert example.com privkey.pem cert.pem fullchain.pem
#
# IMPLEMENTATION
#    version         0.0.1
#    author          Thorsten Köster
#    copyright       Copyright (C) Thorsten Köster
#    license         GNU General Public License v3.0
#
#============================================================================
#
#  HISTORY
#     2016/01/09 : tkoester : Script creation
#
#============================================================================

if [[ ! $# -ge 1 ]] ; then
  echo &quot;No arguments given.&quot; &gt;&amp;2
  exit 2
fi

case &quot;${1}&quot; in
  deploy_challenge)
    exit 0
    ;;
  clean_challenge)
    exit 0
    ;;
  deploy_cert)
    shift 1
    if [[ ! $# -ge 3 ]] ; then
      echo &quot;Insufficient number of arguments.&quot; &gt;&amp;2
      exit 2
    fi
    uberspace-prepare-certificate -k ${2} -c ${3}
    if [[ $? = &quot;0&quot; ]] ; then
      exit 0
    else
      exit 1
    fi
    ;;
  *)
    printf &quot;Unknown command: ${1}&quot; &gt;&amp;2
    exit 2
    ;;
esac

</code></pre>
<p>Das Skript unterscheidet zunächst mal anhand des ersten Arguments, in welcher Phase es gerade aufgerufen wurde. <code>deploy_challenge</code> und <code>clean_challenge</code> ignorieren wir vorerst und tun so, als wäre die Ausführung erfolgreich gewesen. Bei <code>deploy_cert</code> prüfen wir, ob wir mindestens drei weitere Parameter übergeben bekommen haben und rufen dann entsprechend <code>uberspace-prepare-certificate</code> auf. Der Rest ist dann nur noch Fehlerbehandlung.</p>
<p>Natürlich müsst Ihr das Skript noch ausführbar machen:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">chmod u+x hook.sh</code></pre>
<p>Und vergesst nicht, die <code>config.sh</code> azupassen, falls Ihr das nicht schon oben getan habt:</p>
<pre><code class="language-language-bash">HOOK=&quot;${BASEDIR}/hook.sh&quot;
</code></pre>
<h3 id="vollerservice">Voller Service</h3>
<p>Jetzt können wir natürlich auch noch das Zertifikats-Renewal voll automatisieren, entweder per <a href="https://wiki.uberspace.de/system:cron">Cronjob</a> oder per <a href="https://wiki.uberspace.de/system:runwhen">runwhen</a>. Da ich schon bisher alle sich wiederholenden Tätigkeiten auf Uberspace per <code>runwhen</code> erledige, habe ich mir einen entsprechenden Service angelegt:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">runwhen-conf ~/etc/run-letsencrypt ~/opt/letsencrypt/letsencrypt.sh</code></pre>
<p>Die Datei <code>~/etc/run-letsencrypt/run</code> müssen wir noch ein wenig anpassen. Zum einen müssen wir natürlich noch festlegen, wann der Job laufen soll. Ich habe mich für einmal wöchentlich am Freitag, pünktlich zum Feierabend entschieden. Dann kann ich übers Wochenende versuchen, die Fehler zu beheben, falls was schief ging:</p>
<pre><code class="language-language-bash">RUNWHEN=&quot;,w=5,H=16&quot;
</code></pre>
<p>Zum anderen benötigt <code>letsencrypt.sh</code> noch den Parameter <code>-c</code> für <em>Cron</em>, um alle Zertifikate auf den neuesten Stand zu bringen. Änderungen an der <code>domains.txt</code> sollten damit ebenso abgearbeitet werden wie neue Zertifikate 30 Tage vor Ablauf der alten angefordert werden. Diese Zeitspanne lässt sich übrigens auch in der <code>config.sh</code> mit dem Parameter <code>RENEW_DAYS</code> einstellen.</p>
<p>Vollständig sieht meine <code>~/etc/run-letsencrypt/run</code> nun so aus:</p>
<pre><code class="language-language-bash">#!/bin/sh -e

RUNWHEN=&quot;,w=5,H=16&quot;

# many tools and programming languages need these e.g. to find user-installed modules
export USER=macfrog
export HOME=/home/macfrog

exec 2&gt;&amp;1 \
rw-add n d1S now1s \
rw-match \$now1s $RUNWHEN wake \
sh -c '
  echo &quot;@$wake&quot; | tai64nlocal | sed &quot;s/^/next run time: /&quot;
  exec &quot;$@&quot;' arg0 \
rw-sleep \$wake \
/home/macfrog/opt/letsencrypt/letsencrypt.sh -c
</code></pre>
<p>Dann noch der übliche Symlink nach <code>~/service</code> und den Job einmal starten:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">ln -s ~/etc/run-letsencrypt ~/service/letsencrypt
svc -a ~/service/letsencrypt</code></pre>
<p>Et voilà - die Zertifikate mitsamt Schlüssel für Eure Domains aus <code>domains.txt</code> sollten sich jetzt in <code>~/opt/letsencrypt/certs/&lt;domainname&gt;/</code> befinden und außerdem bereits bei Uberspace eingetragen sein. Und wenn die Automatik funktioniert, müsst Ihr Euch nie wieder um eine Erneuerung kümmern.</p>
<h3 id="tldr">tl;dr</h3>
<p>Wenn Ihr das hier auch in drei Monaten noch lesen könnt, dann hat das, was ich oben beschrieben habe, höchstwahrscheinlich funktioniert.</p>
</div>]]></content:encoded></item><item><title><![CDATA[SSL-Zertifikate im Batch-Modus erstellen]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Als Programmierer ist man ja grundsätzlich faul. Sich wiederholende Aufgaben überlässt man lieber dem Computer. Zumal es ja grundsätzlich interessanter ist, sie dem Computer beizubringen, anstatt sie selber abzuarbeiten. Auch wenn Letzteres viel schneller gehen würde...</p>
<p>In diesem Sinne stand mal wieder die Erneuerung meiner SSL-Zertifikate an. Nun ist dieses</p></div>]]></description><link>https://blog.macfrog.de/2015/04/19/ssl-zertifikate-im-batch-modus-erstellen/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c29</guid><category><![CDATA[SSL]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sun, 19 Apr 2015 11:11:41 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Als Programmierer ist man ja grundsätzlich faul. Sich wiederholende Aufgaben überlässt man lieber dem Computer. Zumal es ja grundsätzlich interessanter ist, sie dem Computer beizubringen, anstatt sie selber abzuarbeiten. Auch wenn Letzteres viel schneller gehen würde...</p>
<p>In diesem Sinne stand mal wieder die Erneuerung meiner SSL-Zertifikate an. Nun ist dieses Blog nicht nur unter <a href="https://blog.macfrog.de/">https://blog.macfrog.de/</a> sondern auch unter <a href="https://blog.mcfrog.de/">https://blog.mcfrog.de/</a> erreichbar (für die Leute, denen ich nicht zum hundertsten Mal erklären möchte, dass da ein &quot;a&quot; fehlt). Außerdem habe ich noch ein paar andere Domains mit jeweils nahezu gleichen Subdomains. Insgesamt ziemlich stupide Arbeit, für jede Kombination mit</p>
<pre><code class="language-language-">openssl req -new -nodes -newkey rsa:4096 -sha512 -days &lt;Gültigkeit&gt; -out &lt;CSR-Filename&gt; -keyout &lt;Key-Filename&gt;
</code></pre>
<p>einen neuen Zertifikatsantrag zu erstellen und dabei jedes Feld</p>
<pre><code class="language-language-">Country Name (2 letter code) [XX]:DE
State or Province Name (full name) []:.
Locality Name (eg, city) [Default City]:.
...
</code></pre>
<p>von Hand auszufüllen. Ein Skript musste her.</p>
<p>Voraussetzung war natürlich zunächst einmal, sich die interaktive Tipparbeit zu sparen. Zum Glück gibt es da in <code>openssl req</code> den Schalter <code>-subj</code>, der den Betreff des Zertifikats angibt. Im Prinzip werden die interaktiv abgefragten Angaben nämlich nur in einen String verpackt, den man auch genauso gut per Kommandozeile übergeben kann:</p>
<pre><code class="language-language-">openssl req -subj &quot;/C=DE/CN=www.example.com/emailAddress=hostmaster@example.com&quot; ...
</code></pre>
<p>Mehr als die paar Felder <strong>C</strong>ountry, <strong>C</strong>ommon <strong>N</strong>ame und <strong>emailAddress</strong> unterstützen die kostenlosen Zertifizierungsstellen meist sowieso nicht, da sich die anderen Angaben nicht automatisch überprüfen lassen.</p>
<p>Das war's aber auch schon fast. Jetzt kommen nur noch ein paar Schleifen drum, und schon generiert ein kleines Skript ohne Zutun sämtliche CSRs für alle Subdomains meiner Domains:</p>
<pre><code class="language-language-bash">#!/bin/bash
	
declare -a domains=(&quot;macfrog.de&quot; &quot;mcfrog.de&quot;)
declare -a subdomains=(&quot;blog&quot; &quot;www&quot;)
	
for d in &quot;${domains[@]}&quot; ; do
  for s in &quot;${subdomains[@]}&quot; ; do
    echo '***'
    echo '***' $s.$d
    echo '***'
    openssl req -subj &quot;/C=DE/CN=$s.$d/emailAddress=hostmaster@$d&quot; -nodes -new -newkey rsa:4096 -sha512 -days 395 -out $s.$d.csr.pem -keyout $s.$d.key.pem
  done
done
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Ups.]]></title><description><![CDATA[<div class="kg-card-markdown"><p>So kann man sich auch sein <a href="https://ghost.org/">Ghost</a>-Setup zerschießen...</p>
<p>Ich bin für das Update auf <a href="https://ghost.org/zip/ghost-0.5.8.zip">Version 0.5.8</a> meiner eigenen <a href="https://blog.macfrog.de/2014/09/06/meta-blogging-teil-zwei/">Anleitung</a> gefolgt, und habe mich anschließend über einen <code>503 Service Temporarily Unavailable</code> Error gewundert.</p>
<p>Und ein bisschen nervös wurde ich auch.</p>
<p>Bis mir einfiel, dass ich vor ein paar</p></div>]]></description><link>https://blog.macfrog.de/2015/01/14/ups/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c28</guid><category><![CDATA[ghost]]></category><category><![CDATA[gcc]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Wed, 14 Jan 2015 20:19:25 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>So kann man sich auch sein <a href="https://ghost.org/">Ghost</a>-Setup zerschießen...</p>
<p>Ich bin für das Update auf <a href="https://ghost.org/zip/ghost-0.5.8.zip">Version 0.5.8</a> meiner eigenen <a href="https://blog.macfrog.de/2014/09/06/meta-blogging-teil-zwei/">Anleitung</a> gefolgt, und habe mich anschließend über einen <code>503 Service Temporarily Unavailable</code> Error gewundert.</p>
<p>Und ein bisschen nervös wurde ich auch.</p>
<p>Bis mir einfiel, dass ich vor ein paar Tagen eine neue Version von <code>gcc</code> <a href="https://blog.macfrog.de/2014/12/22/gcc-per-toast-im-uberspace-installieren/">installiert</a> hatte. Damit passten offensichtlich nach dem Kommando <code>npm install --production</code> die dabei kompilierten Dateien nicht mehr richtig zueinander.</p>
<p>Die Lösung war dann folgerichtig, das entsprechende Verzeichnis zu löschen, und die Abhängigkeiten erneut vollständig bauen zu lassen:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-bash">cd ghost
rm -rf node_modules
npm install --production</code></pre></div>]]></content:encoded></item><item><title><![CDATA[GCC per toast im Uberspace installieren]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Auch mir reicht der standardmäßig auf Uberspace vorhandene <code>gcc</code> nicht mehr aus. Aber statt mich wie <a href="http://ne.crux.uberspace.de/wordpress/index.php/gcc-4-7-0-im-uberspace-kompilieren/">dieser Autor</a> darauf zu beschränken, das Kompilat nur einfach in das <code>toast</code>-Verzeichnis zu installieren, wollte ich <code>toast</code> die ganze Arbeit machen lassen.</p>
<p>Der Übersichtlichkeit halber habe ich meine Kommandos in mehrere Schritte aufgeteilt,</p></div>]]></description><link>https://blog.macfrog.de/2014/12/22/gcc-per-toast-im-uberspace-installieren/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c27</guid><category><![CDATA[gcc]]></category><category><![CDATA[toast]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Mon, 22 Dec 2014 09:21:15 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Auch mir reicht der standardmäßig auf Uberspace vorhandene <code>gcc</code> nicht mehr aus. Aber statt mich wie <a href="http://ne.crux.uberspace.de/wordpress/index.php/gcc-4-7-0-im-uberspace-kompilieren/">dieser Autor</a> darauf zu beschränken, das Kompilat nur einfach in das <code>toast</code>-Verzeichnis zu installieren, wollte ich <code>toast</code> die ganze Arbeit machen lassen.</p>
<p>Der Übersichtlichkeit halber habe ich meine Kommandos in mehrere Schritte aufgeteilt, die man aber genau so gut zu einem Aufruf von <code>toast arm</code> mit den passenden Optionen zusammenfassen kann.</p>
<p>Ich hole zunächst mal die Datei. <code>toast</code> findet die höheren <code>gcc</code>-Versionen aus irgendeinem Grund nicht, daher ist die Angabe der kompletten URL notwendig. Immerhin kann <code>toast</code> daraus alleine schlussfolgern, dass wir hier gerne <code>gcc</code> in der Version 4.8.4 hätten:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">toast get ftp://ftp.gwdg.de/pub/misc/gcc/releases/gcc-4.8.4/gcc-4.8.4.tar.bz2</code></pre>
<p>Zum Bauen müssen wir den von <code>toast</code> verwendeten Compileraufruf entsprechend umbiegen. Standardmäßig steht der auf <code>./configure --prefix=&lt;toast-Verzeichnis&gt;/armed &amp;&amp; make</code>, was für die meisten per <code>autoconf</code> / <code>automake</code> verwalteten Pakete passt. <code>gcc</code> ist aber noch von einigen separat verwalteten Paketen abhängig. Netterweise liefern uns die Compilerbauer das <code>download_prerequisites</code> Skript mit, das diese automatisch in der richtigen Version herunterlädt. Allerdings muss das vor dem <code>configure</code>-Schritt geschehen, und dann müssen <code>configure</code> auch noch die richtigen Optionen mitgegeben werden:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">toast build gcc --compilecmd="./contrib/download_prerequisites && ./configure --prefix=/home/${USER}/.toast/armed --enable-languages=c,c++ --disable-multilib && make"</code></pre>
<p>Das Gute ist: Toast räumt selbständig auf, nur das Quellpaket bleibt liegen. Wer will, kann das auch noch per <code>toast purge gcc</code> entfernen.</p>
<p>Anschließend muss das eben gebaute Paket noch aktiviert werden:</p>
<pre class="command-line" data-user="macfrog" data-host="horologium"><code class="language-">toast arm gcc</code></pre>
<p>Das war's auch schon. <code>gcc --version</code> meldet jetzt wie gewünscht</p>
<pre class="command-line" data-user="macfrog" data-host="horologium" data-output="2-6"><code class="language-">gcc --version
gcc (GCC) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying
conditions.  There is NO warranty; not even for
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</code></pre></div>]]></content:encoded></item><item><title><![CDATA[Meta-Blogging, Teil zwei]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Das ging ja schneller als befürchtet. Im Prinzip bin ich teils dem <a href="https://wiki.uberspace.de/cool:ghost">Uberspace-Wiki</a> und teils der <a href="http://support.ghost.org/how-to-upgrade/#command-line-only-on-linux-servers">Anleitung der Ghost-Macher</a> gefolgt:</p>
<ul>
<li>Temporärverzeichnis umbiegen: <code>export TMPDIR=~/tmp</code> (Das Verzeichnis hatte ich noch von früheren Aktionen rumliegen.)</li>
<li>Letzte Version von Ghost holen: <code>wget https://ghost.org/zip/ghost-0.5.1.zip</code></li>
<li>Uberspace-Dienst anhalten:</li></ul></div>]]></description><link>https://blog.macfrog.de/2014/09/06/meta-blogging-teil-zwei/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c26</guid><category><![CDATA[ghost]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sat, 06 Sep 2014 18:12:33 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Das ging ja schneller als befürchtet. Im Prinzip bin ich teils dem <a href="https://wiki.uberspace.de/cool:ghost">Uberspace-Wiki</a> und teils der <a href="http://support.ghost.org/how-to-upgrade/#command-line-only-on-linux-servers">Anleitung der Ghost-Macher</a> gefolgt:</p>
<ul>
<li>Temporärverzeichnis umbiegen: <code>export TMPDIR=~/tmp</code> (Das Verzeichnis hatte ich noch von früheren Aktionen rumliegen.)</li>
<li>Letzte Version von Ghost holen: <code>wget https://ghost.org/zip/ghost-0.5.1.zip</code></li>
<li>Uberspace-Dienst anhalten: <code>svc -d ~/service/ghost</code></li>
<li>Altes <code>core</code> Verzeichns löschen</li>
<li>ZIP-Datei per <code>unzip -uo ghost-0.5.1.zip -d path-to-ghost</code> über die Installation entpacken</li>
<li>Abhängigkeiten mittels <code>npm install --production</code> holen</li>
</ul>
<p>An dieser Stelle wird es kurz mal ein wenig holprig, da ein Binary erst lokal kompiliert werden muss. Nicht nervös werden, die Automatik hat alles im Griff.</p>
<ul>
<li>Änderungen an <code>content/themes/casper/default.hbs</code> für die Links zum Impressum und zur Datenschutzerklärung wieder herstellen</li>
<li>Uberspace-Dienst wieder starten: <code>svc -u ~/service/ghost</code></li>
</ul>
<p>Das war's auch schon...</p>
</div>]]></content:encoded></item><item><title><![CDATA[Meta-Blogging, Teil eins]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Oder wie sollte man es sonst nennen, wenn man über seine Blogging-Platform bloggt? Jedenfalls möchte ich heute meine Ghost-Installation updaten.</p>
<p>Kurze Checkliste:</p>
<ul>
<li>Backup der Daten per JSON-Export: check</li>
<li>Backup der Bilder und des Themes: check</li>
<li>Dienst angehalten: äh... sofort...</li>
</ul>
<p>Stay tuned!</p>
</div>]]></description><link>https://blog.macfrog.de/2014/09/06/meta-blogging-teil-eins/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c25</guid><category><![CDATA[ghost]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sat, 06 Sep 2014 18:12:20 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Oder wie sollte man es sonst nennen, wenn man über seine Blogging-Platform bloggt? Jedenfalls möchte ich heute meine Ghost-Installation updaten.</p>
<p>Kurze Checkliste:</p>
<ul>
<li>Backup der Daten per JSON-Export: check</li>
<li>Backup der Bilder und des Themes: check</li>
<li>Dienst angehalten: äh... sofort...</li>
</ul>
<p>Stay tuned!</p>
</div>]]></content:encoded></item><item><title><![CDATA[So langsam wird's...]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Ungefähr ein Monat ist vergangen, seit ich <a href="https://blog.macfrog.de/2014/05/10/maildrop-revisited/">DSPAM installiert</a> habe. Und allmählich fängt der Bayes-Filter an, Spam-Mails auszufiltern. Ich bin beeindruckt - ich hätte mit einer längeren Lernphase gerechnet. Zumal ich auf jegliches Vorab-Training verzichtet und nur die False-Negatives &quot;umgelernt&quot; habe. False-Positives traten wie zu erwarten nicht auf.</p></div>]]></description><link>https://blog.macfrog.de/2014/06/09/so-langsam-wirds/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c24</guid><category><![CDATA[DSPAM]]></category><category><![CDATA[mail]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Mon, 09 Jun 2014 14:51:22 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Ungefähr ein Monat ist vergangen, seit ich <a href="https://blog.macfrog.de/2014/05/10/maildrop-revisited/">DSPAM installiert</a> habe. Und allmählich fängt der Bayes-Filter an, Spam-Mails auszufiltern. Ich bin beeindruckt - ich hätte mit einer längeren Lernphase gerechnet. Zumal ich auf jegliches Vorab-Training verzichtet und nur die False-Negatives &quot;umgelernt&quot; habe. False-Positives traten wie zu erwarten nicht auf.</p>
</div>]]></content:encoded></item><item><title><![CDATA[E-Mail-Konfiguration für Ghost]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Die Anleitung auf <a href="http://docs.ghost.org/de/mail/">http://docs.ghost.org/de/mail/</a> ist ja ganz nett, aber auf <a href="https://uberspace.de/">Uberspace</a> geht das auch einfacher. Folgender Eintrag in der <code>config.js</code> teilt Ghost mit, dass es tatsächlich <code>sendmail</code> nutzen darf:</p>
<pre><code>production: {
    url: 'http://blog.example.com',
    mail: {
        transport: 'sendmail'
    },
    [...]
</code></pre>
<p>Der Absender ist dann <code>ghost@blog.</code></p></div>]]></description><link>https://blog.macfrog.de/2014/05/20/e-mail-konfiguration-fur-ghost/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c22</guid><category><![CDATA[ghost]]></category><category><![CDATA[mail]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Tue, 20 May 2014 12:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Die Anleitung auf <a href="http://docs.ghost.org/de/mail/">http://docs.ghost.org/de/mail/</a> ist ja ganz nett, aber auf <a href="https://uberspace.de/">Uberspace</a> geht das auch einfacher. Folgender Eintrag in der <code>config.js</code> teilt Ghost mit, dass es tatsächlich <code>sendmail</code> nutzen darf:</p>
<pre><code>production: {
    url: 'http://blog.example.com',
    mail: {
        transport: 'sendmail'
    },
    [...]
</code></pre>
<p>Der Absender ist dann <code>ghost@blog.example.com</code>. Für mich ist das ausreichend. Wer andere Anforderungen hat oder mehr wissen will, schaut einfach bei <a href="https://github.com/andris9/Nodemailer">Nodemailer</a> nach.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Maildrop Revisited]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Es ist gerade mal eine Woche her, dass ich über mein <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann">maildrop-Setup</a> auf <a href="https://uberspace.de/">Uberspace</a> geschrieben habe. In der Zwischenzeit habe ich allerdings meine Skripte ein wenig aufgeräumt und erweitert.</p>
<p>Insbesondere habe ich auch <a href="http://dspam.nuclearelephant.com/">DSPAM</a> so mit eingebunden, dass Spam und Ham nach dem Neulernen nochmal richtig zugestellt werden. Anders als</p></div>]]></description><link>https://blog.macfrog.de/2014/05/10/maildrop-revisited/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c23</guid><category><![CDATA[maildrop]]></category><category><![CDATA[DSPAM]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sat, 10 May 2014 17:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Es ist gerade mal eine Woche her, dass ich über mein <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann">maildrop-Setup</a> auf <a href="https://uberspace.de/">Uberspace</a> geschrieben habe. In der Zwischenzeit habe ich allerdings meine Skripte ein wenig aufgeräumt und erweitert.</p>
<p>Insbesondere habe ich auch <a href="http://dspam.nuclearelephant.com/">DSPAM</a> so mit eingebunden, dass Spam und Ham nach dem Neulernen nochmal richtig zugestellt werden. Anders als im <a href="https://uberspace.de/dokuwiki/mail:dspam">Uberspace-Wiki</a> muss man also nicht daran denken, Ham in den Trainings-Ordner zu kopieren anstatt ihn zu verschieben.</p>
<p>Aber der Reihe nach.</p>
<hr>
<h3 id="filterfile20">Filterfile 2.0</h3>
<p>Nach der <a href="http://www.courier-mta.org/maildrop/maildropfilter.html">man-page</a> zur maildrop-Filtersprache ist das Verzeichnis <code>~/.mailfilters/</code> eigentlich für den sogenannten <em>embedded mode</em> vorgesehen, in dem maildrop die Mails nicht selber zustellt sondern nur für andere Programme das Filtern übernimmt.</p>
<p>Niemand hindert einen jedoch daran, hier Skripte unterzubringen, die dann per <code>include</code> in den zentralen <code>.mailfilter</code> eingebunden werden können. Auf diese Weise habe ich mein Setup modularisiert:</p>
<h5 id="checkmaildir">checkmaildir</h5>
<p>Dieses Skript ermittelt wie gewohnt das Mailverzeichnis des Empfängers.</p>
<pre><code># set default Maildir
MAILDIR=&quot;$HOME/Maildir&quot;

# check if we're called from a .qmail-EXT instead of .qmail
import EXT
if ( $EXT )
{
  # does a vmailmgr user named $EXT exist?
  # if yes, deliver mail to his Maildir instead
  CHECKMAILDIR = `dumpvuser $EXT | grep '^Directory' | awk '{ print $2 }'`
  if ( $CHECKMAILDIR )
  {
    MAILDIR=&quot;$HOME/$CHECKMAILDIR&quot;
  }
}
</code></pre>
<h5 id="writelog">writelog</h5>
<p>Wenn gewünscht, sorgt das folgende Skript dafür, dass alle ausgelieferten Mails in <code>~/var/log/mailfilter{-&lt;mailboxname&gt;}</code> protokolliert werden.</p>
<pre><code># set up logging if desired
if ( $WRITELOG )
{
  LOGFILENAME=&quot;$HOME/var/log/mailfilter.log&quot;

  # change logfile name if we've been called from .qmail-EXT
  import EXT
  if ( $EXT )
  {
    LOGFILENAME=&quot;$HOME/var/log/mailfilter-$EXT.log&quot;
  }
  
  logfile &quot;$LOGFILENAME&quot;
}
</code></pre>
<h5 id="handlespam">handlespam</h5>
<p>Das Spam-Handling erfolgt zunächst einmal zentral, um die Mails nach dem Training neu ausliefern zu können.</p>
<p>Wenn die Umgebungsvariable <code>REDELIVER_SPAM</code> gesetzt ist, wird die Mail direkt in den Spamordner verschoben. Wenn hingegen <code>REDELIVER_HAM</code> gesetzt ist, wird die Mail an den Spamfiltern vorbei direkt an die Filterregeln weitergereicht. Auf diese Weise landen auch <em>false positives</em> zu guter Letzt im richtigen Ordner.</p>
<pre><code># Handle Spam. Take care of re-trained messages from dspam-learn.
import REDELIVER_SPAM
if ( $REDELIVER_SPAM )
{
  DESTDIR=&quot;$MAILDIR/.Spam&quot;
  SILENT_DESTDIR=$SILENT_SPAM
  include &quot;$HOME/.mailfilters/deliver&quot;
}

import REDELIVER_HAM
if ( ! $REDELIVER_HAM )
{
  if ( $SPAMASSASSIN_ENABLE )
  {
    # Show mail to SpamAssassin
    include &quot;$HOME/.mailfilters/spamassassin&quot;
  }

  if ( $DSPAM_ENABLE )
  {
    # Show mail to DSPAM
    include &quot;$HOME/.mailfilters/dspam&quot;
  }
}
</code></pre>
<h5 id="spamassassin">spamassassin</h5>
<p>Dieses Modul lässt die Mail vom SpamAssassin begutachten und verschiebt die Mail bei Bedarf in das <code>Spam</code>-Verzeichnis.</p>
<pre><code># show the mail to SpamAssassin
xfilter &quot;/usr/bin/spamc&quot;

# process SPAM
if ( /^X-Spam-Level: \*{$SPAMASSASSIN_MINSPAMSCORE,}$/ )
{
  DESTDIR=&quot;$MAILDIR/.Spam&quot;
  SILENT_DESTDIR=$SILENT_SPAM
  include &quot;$HOME/.mailfilters/deliver&quot;
}
</code></pre>
<h5 id="dspam">dspam</h5>
<p>Das DSPAM-Modul funktioniert genau so, nur dass hier noch zusätzlich bei Bedarf die Trainingsverzeichnisse angelegt werden. Im Gegensatz zum <a href="https://uberspace.de/dokuwiki/mail:dspam">Uberspace-Wiki</a> habe ich mich hier für englische Ordnernamen entschieden. Außerdem landet verdächtige Mail nicht in einem weiteren Unterordner, sondern direkt im Spamfilter-Ordner.</p>
<pre><code># check folder structure
`test -d &quot;$MAILDIR/.Spam&quot;`
if( $RETURNCODE == 1 )
{
  `maildirmake &quot;$MAILDIR/.Spam&quot;`
}
`test -d &quot;$MAILDIR/.Spam.Learn as Ham&quot;`
if( $RETURNCODE == 1 )
{
  `maildirmake &quot;$MAILDIR/.Spam.Learn as Ham&quot;`
}
`test -d &quot;$MAILDIR/.Spam.Learn as Spam&quot;`
if( $RETURNCODE == 1 )
{
  `maildirmake &quot;$MAILDIR/.Spam.Learn as Spam&quot;`
}

# show the mail to DSPAM
xfilter &quot;/package/host/localhost/dspam/bin/dspam --mode=teft --deliver=innocent,spam --stdout&quot;

# process SPAM
if ( /^X-DSPAM-Result: Spam/ )
{
  DESTDIR=&quot;$MAILDIR/.Spam&quot;
  SILENT_DESTDIR=$SILENT_SPAM
  include &quot;$HOME/.mailfilters/deliver&quot;
}
</code></pre>
<h5 id="deliver">deliver</h5>
<p>Fehlt noch die eigentliche Zustellung. Hier wird je nach Bedarf das Zielverzeichnis angelegt, und die Zustellung kann auch still erfolgen:</p>
<pre><code># Hierarchically create DESTDIR if it does not already exist.
HIER=&quot;$DESTDIR&quot;
CONTINUE=1
while ( $CONTINUE &amp;&amp; $HIER =~ /^(.*\/(\.[^.]+)*)(\.[^.]+)$/ )
{
  if ( $MATCH3 ne &quot;.INBOX&quot; )
  {
    `test -d &quot;$MATCH&quot;`
    if ( $RETURNCODE == 1 )
    {
      `maildirmake &quot;$MATCH&quot;`
    }
    else
    {
      CONTINUE=0
    }
  }
  HIER=$MATCH1
}

# Deliver mail. If SILENT_DESTDIR is set, mark all mail in
# DESTDIR as read.
if ( $SILENT_DESTDIR )
{
  # mark as read
  cc &quot;$DESTDIR&quot;
  `find &quot;$DESTDIR/new/&quot; -mindepth 1 -maxdepth 1 -type f -printf '%f\0' | xargs -0 -I {} mv &quot;$DESTDIR/new/{}&quot; &quot;$DESTDIR/cur/{}:2,S&quot;`
  exit
}
else
{
  to &quot;$DESTDIR&quot;
}
</code></pre>
<p>Dieses Skript beendet auf jeden Fall den aktuellen Skriptdurchlauf, entweder per <code>exit</code>, nachdem die Mail mit <code>cc</code> zugestellt und als gelesen markiert wurde, oder per regulärer Zustellung mit <code>to</code>. Aus der <a href="http://www.courier-mta.org/maildrop/maildropfilter.html">maildropfilter man-page</a>:</p>
<blockquote>
<p>The to statement is the final delivery statement. maildrop delivers message, then immediately terminates, [...]</p>
</blockquote>
<p><sup>Danke an <em>LDer</em> für den Hinweis, dass dieses Verhalten in der ursprünglichen Version des Artikels zu kurz gekommen war!</sup></p>
<h5 id="mailfiltermailboxname">~/.mailfilter-&lt;mailboxname&gt;</h5>
<p>Während alle vorangegangenen Helfer-Skripte in <code>~/.mailfilters/</code> landen, kommt das eigentliche Filterfile direkt ins Hauptverzeichnis. Durch den modularen Aufbau wird das Grundgerüst relativ kurz:</p>
<pre><code># Configuration

WRITELOG=&quot;0&quot;
SPAMASSASSIN_ENABLE=&quot;1&quot;
SPAMASSASSIN_MINSPAMSCORE=&quot;5&quot;
DSPAM_ENABLE=&quot;1&quot;
SILENT_SPAM=&quot;0&quot;

# ------------------------------------------------------

# Write log file, if desrired. Set Maildir. Handle Spam.
include &quot;$HOME/.mailfilters/writelog&quot;
include &quot;$HOME/.mailfilters/checkmaildir&quot;
include &quot;$HOME/.mailfilters/handlespam&quot;


# Set default destination Maildir
DESTDIR=&quot;$MAILDIR&quot;
SILENT_DESTDIR=&quot;0&quot;

# ------------------------------------------------------

# Here go the filter rules...

# ------------------------------------------------------

# Finally, deliver mail
include &quot;$HOME/.mailfilters/deliver&quot;
</code></pre>
<p>Ganz oben lässt sich das Verhalten der Helferskripte konfigurieren. Für die Schalter gilt dabei: Werte ungleich Null sind <em>true</em>, Null oder nicht gesetzt <em>false</em>. Im gekennzeichneten Mittelteil lassen sich dann unter anderem die im <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann">letzten Artikel</a> vorgeschlagenen Filterregeln unterbringen.</p>
<p>Ein kleiner Nachteil der Modularisierung sei nicht verschwiegen: Normalerweise macht maildrop beim Start eine Syntaxprüfung des Filterfiles und steigt gegebenenfalls. sofort mit einer Fehlermeldung aus. Dies trifft aber nicht auf mit <code>include</code> eingebundene Skripte zu. Die werden erst geladen und überprüft, wenn sie tatsächlich gebraucht werden. Das kann das Debugging erschweren und zu seltsamen Effekten führen.</p>
<hr>
<h3 id="dspamlearnanpassen">dspam-learn anpassen</h3>
<p>Jetzt müssen wir nur noch dafür sorgen, dass <code>dspam-learn</code> die Mails nach dem Bearbeiten nicht einfach löscht sondern eine neue Zustellung startet.</p>
<p>Dazu übergeben wir den Nachrichtentext erneut an maildrop und setzen dabei <code>REDELIVER_HAM</code> und <code>REDELIVER_SPAM</code> entsprechend. Außerdem muss natürlich der Benutzer und sein zugehöriges Filterfile ermittelt werden. Damit das funktioniert, muss das Filterfile <strong>unbedingt</strong> unter <code>~/.mailfilter-&lt;mailboxname&gt;</code> für <code>VMailMgr</code>-Postfächer, bzw. <code>~/.mailfilter</code> für die Default-Mailbox abgelegt werden.</p>
<p>Das Skript geht übrigens stillschweigend davon aus, dass das Filterfile für jede betroffene Mailbox tatsächlich existiert. Alles andere ergibt ja auch keinen Sinn - irgendwie müssen die Spammails ja zunächst wegsortiert werden.</p>
<pre><code>#!/bin/bash
###############################################################
# 2013-07-16 Christopher Hirschmann
#            c.hirschmann@jonaspasche.com
# 2014-05-10 Modifications for re-delivery by Thorsten Koester
###############################################################
#
# This program is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public
# License along with this program.  If not, see
# &lt;http://www.gnu.org/licenses/&gt;.
#
###############################################################
#
# This script will look for a system user's primary maildir
# $HOME/Maildir and possible vMailMgr maildir under
# $HOME/users/ and search them for folders indicating a
# certain DSPAM setup:
#
# \
#  \___ Inbox
#   \
#    \___ Spam
#          \
#           \___ Learn as Ham
#            \
#             \___ Learn as Spam
#
# If it finds this folder structure in any maildir, it will
# show files from the folders 'Learn as Ham' and 'Learn as
# Spam' to DSPAM (so it can learn) and after that it will
# re-deliver them via maildrop.
#
# You can make this script more verbose with '-v'.
#
###############################################################

while getopts &quot;:hv&quot; Option
do
	case $Option in
		h       )       echo -e &quot;Usage:\n-v\tbe verbose\n-h\tthis help message&quot;; exit 0;;
		v       )       VERBOSE=1; ;;
		*       )       echo -e &quot;ERROR: Unimplemented option chosen: -${OPTARG}&quot;; exit 1;;
	esac
done

if [ ! `grep -q &quot;CentOS release 5&quot; /etc/redhat-release` ]; then
# ionice -c3 is not supported on CentOS 5 for users != root
# (needs Linux =&gt; 2.6.25)
# so let's fall back to ionice -c2 -n7 which is better than
# nothing
    BE_NICE=&quot;nice -n 19 ionice -c2 -n7&quot;
else
    BE_NICE=&quot;nice -n 19 ionice -c3&quot;
fi

for DIR in $HOME/Maildir `find ${HOME}/users/ -mindepth 1 -maxdepth 1 -type d`;
do
	if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
	then
		echo &quot;Looking for mails in ${DIR}.&quot;
	fi
	if [ -d &quot;$DIR/.Spam.Learn as Spam&quot; ];
	then
		for SUBDIR in new cur ;
		do
# In order to be able to process filenames regardless of any
# kind of weird characters we need to list these files
# separated by null bytes and process them accordingly.
# To achieve this we use bash process substitution (look it
# up, if this looks weird for you).
# The relevant steps are these:
#
#   while IFS= read -d $'\0' -r file;
#   # do stuff
#   done &lt; &lt;(find &quot;$DIR/.Spam.Learn as Spam/$SUBDIR&quot; -type f -print0)

			IFSSAVE=$IFS;
			while IFS= read -d $'\0' -r file;
			do
				if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
				then
					echo &quot;Eating \&quot;$file\&quot;. Yuk!&quot;;
				fi
				$BE_NICE /package/host/localhost/dspam/bin/dspam --class=spam --source=error --stdout &lt; &quot;${file}&quot;;
				if [ &quot;${DIR}&quot; == &quot;$HOME/Maildir&quot; ];
				then
					if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
					then
						echo &quot;Re-Delivering Spam to default mailbox.&quot;;
					fi
					REDELIVER_SPAM=1 /usr/bin/maildrop $HOME/.mailfilter &lt; &quot;${file}&quot;;
				else
					if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
					then
						echo &quot;Re-Delivering Spam to user \&quot;${DIR##*/}\&quot;.&quot;;
					fi
					EXT=${DIR##*/} REDELIVER_SPAM=1 /usr/bin/maildrop $HOME/.mailfilter-${DIR##*/} &lt; &quot;${file}&quot;;
				fi
				rm -f &quot;${file}&quot;;
			done &lt; &lt;(find &quot;$DIR/.Spam.Learn as Spam/$SUBDIR&quot; -type f -print0)
			IFS=$IFSSAVE;
		done
	fi
	if [ -d &quot;$DIR/.Spam.Learn as Ham&quot; ];
	then
		for SUBDIR in new cur ;
		do
			IFSSAVE=$IFS;
			while IFS= read -d $'\0' -r file;
			do
				if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
				then
					echo &quot;Eating \&quot;$file\&quot;. Yum!&quot;;
				fi
				$BE_NICE /package/host/localhost/dspam/bin/dspam --class=innocent --source=error --stdout &lt; &quot;${file}&quot;;
				if [ &quot;${DIR}&quot; == &quot;$HOME/Maildir&quot; ];
				then
					if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
					then
						echo &quot;Re-Delivering Ham to default mailbox.&quot;;
					fi
					REDELIVER_HAM=1 /usr/bin/maildrop $HOME/.mailfilter &lt; &quot;${file}&quot;;
				else
					if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
					then
						echo &quot;Re-Delivering Ham to user \&quot;${DIR##*/}\&quot;.&quot;;
					fi
					EXT=${DIR##*/} REDELIVER_HAM=1 /usr/bin/maildrop $HOME/.mailfilter-${DIR##*/} &lt; &quot;${file}&quot;;
				fi
				rm -f &quot;${file}&quot;;
			done &lt; &lt;(find &quot;$DIR/.Spam.Learn as Ham/$SUBDIR&quot; -type f -print0)
			IFS=$IFSSAVE;
		done
	fi
done

if [ &quot;${VERBOSE}&quot; == &quot;1&quot; ];
then
	echo &quot;Done looking for mails.&quot;
fi
</code></pre>
<h3 id="inbetriebnahme">Inbetriebnahme</h3>
<p>Wie aus dem <a href="https://uberspace.de/dokuwiki/mail:dspam#dspam_sagen_was_es_falsch_und_richtig_gemacht_hat">Uberspace-Wiki</a> bekannt, muss <code>dspam-learn</code> regelmäßig als Service aufgerufen werden. Selbstverständlich verwenden wir hier die eben modifizierte Version:</p>
<pre><code>test -d ~/service || uberspace-setup-svscan
runwhen-conf ~/etc/run-dspam-learn &quot;$HOME/bin/dspam-learn&quot;
sed -i -e &quot;s/^RUNWHEN=.*/RUNWHEN=\&quot;,M=`awk 'BEGIN { srand(); printf(&quot;%d\n&quot;,rand()*60) }'`\&quot;/&quot; ~/etc/run-dspam-learn/run
ln -s ~/etc/run-dspam-learn ~/service/dspam-learn
</code></pre>
<p>Auch sollte man seine DSPAM-Datenbank regelmäßig bereinigen lassen:</p>
<pre><code>test -d ~/service || uberspace-setup-svscan
runwhen-conf ~/etc/run-dspam_clean_hashdb &quot;/usr/local/bin/dspam_clean_hashdb&quot;
sed -i -e &quot;s/^RUNWHEN=.*/RUNWHEN=\&quot;,H=`awk 'BEGIN { srand(); printf(&quot;%d\n&quot;,rand()*24) }'`\&quot;/&quot; ~/etc/run-dspam_clean_hashdb/run
ln -s ~/etc/run-dspam_clean_hashdb ~/service/dspam_clean_hashdb
</code></pre>
<p>Damit müsste DSPAM funktionieren. Allerdings dürfte es einige Spammails lang dauern, bis der Filter soweit angelernt ist, dass er korrekt arbeitet.</p>
<p>Im laufenden Betrieb ist es wichtig zu wissen, dass bereits von SpamAssassin ausgefilterte Mails nicht DSPAM zum Umlernen vorgelegt werden können. DSPAM hat diese Mail ja nie gesehen und kann auch gar nicht mehr in die Filterung eingreifen.</p>
<p>Im Uberspace-Standard-Setup werden Mails, die SpamAssassin für verdächtig hält, ab einer Spam-Punktzahl von fünf mit <code>[SPAM]</code> im Betreff gekennzeichnet. Normalerweise ist SpamAssassin damit, zumindest bei mir, eher auf der sicheren Seite. Es bietet sich daher an, <code>$SPAMASSASSIN_MINSPAMSCORE=&quot;5&quot;</code> zu setzen, SpamAssassin als Werkzeug fürs Grobe zu nutzen, und die Feinarbeit dann DSPAM zu überlassen. Wenn man <a href="https://uberspace.de/dokuwiki/mail:spamassassin#betrieb_eines_eigenen_spamd">sein eigenes SpamAssassin-Setup</a> eingerichtet hat, sollte man die Einstellungen natürlich entsprechend anpassen.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Was man mit maildrop alles anstellen kann...]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Seit knapp zwei Monaten bin ich jetzt bei <a href="https://uberspace.de/">Uberspace</a>. Zeit, meinen Mailfilter aufzusetzen.</p>
<p>Wenn man IMAP nutzt, bietet es sich ja geradezu an, seine Mails in Ordner vorzusortieren. So kann man hinterher die ganzen Newsletter, die abzubestellen man zu faul ist, auf einen Schlag löschen. :-)</p>
<p>Für meine Experimente war</p></div>]]></description><link>https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c20</guid><category><![CDATA[maildrop]]></category><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Sat, 03 May 2014 12:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Seit knapp zwei Monaten bin ich jetzt bei <a href="https://uberspace.de/">Uberspace</a>. Zeit, meinen Mailfilter aufzusetzen.</p>
<p>Wenn man IMAP nutzt, bietet es sich ja geradezu an, seine Mails in Ordner vorzusortieren. So kann man hinterher die ganzen Newsletter, die abzubestellen man zu faul ist, auf einen Schlag löschen. :-)</p>
<p>Für meine Experimente war die <a href="https://uberspace.de/dokuwiki/mail:maildrop">maildrop Dokumentation</a> von Uberspace zusammen mit der <a href="http://www.courier-mta.org/maildrop/maildropfilter.html">man page</a> für die maildrop Filter-Syntax ein guter Ausgangspunkt.</p>
<hr>
<h2 id="meingrundgerst">Mein Grundgerüst</h2>
<p>Zunächst einmal habe ich das <a href="https://uberspace.de/dokuwiki/mail:maildrop#grundgeruest">Grundgerüst</a> ein wenig überarbeitet. Am Anfang sollte auf jeden Fall die Variable <code>$MAILDIR</code> gesetzt werden.  Wir sind ja schließlich paranoid und wollen auf jeden Fall einen gültigen Wert haben, auch wenn <code>dumpvuser</code> aus irgendwelchen Gründen kein Verzeichnis zurückliefert:</p>
<pre><code># set default Maildir
MAILDIR=&quot;$HOME/Maildir&quot;

# check if we're called from a .qmail-EXT  instead of .qmail
import EXT
if ( $EXT )
{
  # does a vmailmgr user named $EXT exist?
  # if yes, deliver mail to his Maildir instead
  CHECKMAILDIR = `dumpvuser $EXT | grep '^Directory' | awk '{ print $2 }'`
  if ( $CHECKMAILDIR )
  {
    MAILDIR=$CHECKMAILDIR
  }
}
</code></pre>
<p>Damit ist <code>$MAILDIR</code> festgelegt und zeigt auf das eigentliche Postfach. Ankommende Mails sollen nach <code>$DESTDIR</code> ausgeliefert werden. So können wir in aufeinanderfolgenden Filterregeln immer wieder auf <code>$MAILDIR</code> zurückgreifen. Daher ist vor allen weiteren Filterregeln folgende Zuweisung als Defaultwert notwendig:</p>
<pre><code># Set default destination Maildir
DESTDIR=&quot;$MAILDIR&quot;
</code></pre>
<p>Danach kommen wie gesagt die Filterregeln. Und weil wir zu faul sind, alle Unterordner von Hand anzulegen, lassen wir das bei Bedarf auch den Computer machen, kurz bevor die Mail ausgeliefert wird:</p>
<pre><code># Hierarchically create DESTDIR if it does not already exist,
# and deliver mail

HIER=&quot;$DESTDIR&quot;
CONTINUE=1
while ( $CONTINUE &amp;&amp; $HIER =~ /^(.*\/(\.[^.]+)*)(\.[^.]+)$/ )
{
  if ( $MATCH3 ne &quot;.INBOX&quot; )
  {
    `test -d &quot;$MATCH&quot;`
    if ( $RETURNCODE == 1 )
    {
      `maildirmake &quot;$MATCH&quot;`
    }
    else
    {
      CONTINUE=0
    }
  }
  HIER=$MATCH1
}
to &quot;$DESTDIR&quot;
</code></pre>
<p>Das Skript hangelt sich im Verzeichnisbaum so lange nach oben, bis es ein bereits angelegtes Mailverzeichnis findet. Außerdem spart es den <em>virtuellen</em> Ordner <code>.INBOX</code> aus: Der muss zwar in der IMAP-Hierarchie angegeben werden, wenn ein Ordner unterhalb des Posteingangsordners liegen soll, aber die Mails an sich liegen ja in <code>$MAILDIR</code>.</p>
<hr>
<h2 id="meinefilterregeln">Meine Filterregeln</h2>
<h3 id="grundprinzip">Grundprinzip</h3>
<p>Eigentlich muss eine Filterregel jetzt &quot;nur&quot; noch <code>$DESTDIR</code> in Abhängigkeit von irgendwelchen Eigenschaften der auszuliefernden Mail setzen:</p>
<pre><code>DESTDIR=&quot;$MAILDIR/.INBOX.&lt;Unterordner&gt;&quot;
</code></pre>
<p>Und da wir <code>$MAILDIR</code> nicht überschrieben haben, können wir es weiter unten wiederverwenden, wenn wir festgestellt haben, dass die Mail nicht nach <code>.INBOX.&lt;Unterordner&gt;</code> sondern anderswohin gehört:</p>
<pre><code>DESTDIR=&quot;$MAILDIR/.INBOX.&lt;Unterordner&gt;.&lt;Unter-Unterordner&gt;&quot;
</code></pre>
<p>So weit, so gut. Und wie filtern wir jetzt eigentlich? Momentan komme ich mit vier Typen von Filterregeln aus, nämlich: <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann/#localpart">Local Part</a>, <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann/#mailingliste">Mailingliste</a>, <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann/#empfnger">Empfänger</a> und <a href="https://blog.macfrog.de/2014/05/03/was-man-mit-maildrop-alles-anstellen-kann/#absender">Absender</a>.</p>
<h3 id="localpart">Local Part</h3>
<p>Falls mehrere Adressen per <code>.qmail-...</code> im selben Postfach landen, kann man ein Feature von qmail ausnutzen, das eigentlich Mail-Loops verhindern soll: den <code>Delivered-To:</code> Header:</p>
<pre><code># Filter by incoming local part
if ( /^Delivered-To:.*-adresse1@.*$/:h )
{
  DESTDIR=&quot;$MAILDIR/.INBOX.Adresse1&quot;
}
elsif ( /^Delivered-To:.*-adresse2@.*$/:h )
{
  DESTDIR=&quot;$MAILDIR/.INBOX.Adresse2&quot;
}
elsif ( /^Delivered-To:.*-adresse3@.*$/:h )
{
  DESTDIR=&quot;$MAILDIR/.INBOX.Unterordner.Adresse3&quot;
}
</code></pre>
<p>Dabei ist zu beachten, dass vor der eigentlichen Adresse auf jeden Fall der Uberspace-Benutzername und ggf. noch der Namensraum stehen. Ein vollständiger <code>Delivered-To:</code> Header sieht auf Uberspace im Allgemeinen so aus:</p>
<pre><code>Delivered-To: &lt;User&gt;-&lt;Namespace&gt;-&lt;Address&gt;@&lt;domain&gt;.&lt;tld&gt;
</code></pre>
<p>Richtig interessant wird das, wenn mehrere Umleitungen per <code>.qmail-...</code> ins Spiel kommen. Grundsätzlich fügt qmail nämlich für jede Umleitung eine <code>Delivered-To:</code> Zeile in den Header ein. Normalerweise sollte aber auch in so einem Fall ein einziger Patternvergleich reichen.</p>
<h3 id="mailingliste">Mailingliste</h3>
<p>Wenn ein Mailinglisten-Administrator so nett war, und seine Mailingliste mit einer <code>List-ID:</code> gemäß <a href="http://www.ietf.org/rfc/rfc2919.txt">RFC 2919</a> ausgestattet hat, kann man solche Mails zum Beispiel folgendermaßen filtern:</p>
<pre><code># Filter by originating mailing list
if ( /^List-ID:.*&lt;(.*)&gt;/:h )
{
  LIST=tolower($MATCH1)
  if ( $LIST =~ /liste\.erstedomain\.tld/ )
  {
    DESTDIR=&quot;$MAILDIR/.INBOX.ErsteDomain&quot;
  }
  elsif ( $LIST =~ /(.*)\.zweitedomain\.tld/ )
  {
    DESTDIR=&quot;$MAILDIR/.INBOX.MehrereListen&quot;
    LIST=tolower($MATCH1)
    if ( $LIST =~ /erste-liste/ )
    {
      DESTDIR=&quot;$MAILDIR/.INBOX.MehrereListen.EineListe&quot;
    }
    elsif ( $LIST =~ /zweite-liste/ )
    {
      DESTDIR=&quot;$MAILDIR/.INBOX.MehrereListen.AndereListe&quot;
    }
  }
  elsif ( $LIST =~ /(.*)\.drittedomain\.tld/ )
  {
      DESTDIR=&quot;$MAILDIR/.INBOX.ListenVonDritterDomain&quot;
  }
}
</code></pre>
<h3 id="empfnger">Empfänger</h3>
<p>Manchmal gibt es aber auch nicht ganz so nette Listen-Admins. Dann fehlt der <code>List-ID:</code> Header. In dem Fall kann man sich aber möglicherweise den Umstand zu Nutze machen, dass die Mailingliste als <em>Empfänger</em> einer Mail eingetragen ist. maildrop stellt hier sogar die Funktion <code>hasaddr()</code> zur Verfügung, die einem das Parsen eines Headers nach <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a> abnimmt:</p>
<pre><code># Filter by destination
if ( hasaddr(&quot;liste1@listen-ohne-id.tld&quot;) || \
     hasaddr(&quot;liste2@listen-ohne-id.tld&quot;) )
{
  DESTDIR=&quot;$MAILDIR/.INBOX.ListenOhneID&quot;
}
</code></pre>
<p>Aber Achtung: Auch Mails, die z.B. per <code>CC:</code> an die Liste gingen, werden auf diese Weise wegsortiert. <code>hasaddr()</code> sucht nämlich in den <code>To:</code>, <code>Cc:</code>, <code>Resent-To:</code> und <code>Resent-Cc:</code> Headern nach der angegebenen Adresse.</p>
<h3 id="absender">Absender</h3>
<p>Man kann natürlich seine Mails auch ganz klassisch nach ihrem Absender sortieren:</p>
<pre><code># Filter by sender
if ( /^From: (.*)/:h )
{
  ADDR=getaddr($MATCH1)
  if ( $ADDR =~ /.*@example\.com/ )
  {
    DESTDIR=&quot;$MAILDIR/.INBOX.Example_com&quot;
  }
  elsif ( $ADDR =~ /.*@example\.org/ )
  {
    DESTDIR=&quot;$MAILDIR/.INBOX.Example_org&quot;
  }
}</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Uberspace: The Final Frontier]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Noch vor 48 Stunden hätte ich nicht im Traum daran gedacht, einen Blog aufzumachen. Was soll ich schon groß schreiben? Doch dann...</p>
<p>Eigentlich fing die Geschichte schon vor längerer Zeit an: Ich war auf der Suche nach einer Möglichkeit, meine Mails, Kontakte und Kalender selber zu hosten. Ein Freund machte</p></div>]]></description><link>https://blog.macfrog.de/2014/05/02/uberspace-the-final-frontier/</link><guid isPermaLink="false">59d17fce83017c1cd0d52c21</guid><category><![CDATA[Uberspace]]></category><dc:creator><![CDATA[Thorsten Köster]]></dc:creator><pubDate>Fri, 02 May 2014 14:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Noch vor 48 Stunden hätte ich nicht im Traum daran gedacht, einen Blog aufzumachen. Was soll ich schon groß schreiben? Doch dann...</p>
<p>Eigentlich fing die Geschichte schon vor längerer Zeit an: Ich war auf der Suche nach einer Möglichkeit, meine Mails, Kontakte und Kalender selber zu hosten. Ein Freund machte mich damals auf <a href="https://uberspace.de/">Uberspace</a> aufmerksam, und irgendwann habe ich dann einfach mal ein Konto für mich angelegt. Besondere Erwartungen hatte ich eigentlich keine. Aber was soll ich sagen: ich war hin und weg, von Anfang an.</p>
<p>Nicht nur, dass einem ein vollwertiger Unix-Account zur Verfügung steht. Bei den Tools merkt man zum Beispiel, dass die Jungs sie so eingerichtet haben, wie sie selber gerne damit arbeiten. Und nicht zuletzt der Support: Jede Anfrage wird freundlich und kompetent beantwortet, oft auch außerhalb der &quot;üblichen&quot; Bürozeiten.</p>
<p>Letztlich führte eine solche Anfrage dazu, dass Moritz vom Uberspace-Team meinte, er würde gerne Teile meiner <a href="https://uberspace.de/dokuwiki/mail:maildrop">Mailfilter-Konfiguration</a> den anderen Ubernauten zugänglich machen. Oder ob ich das tun wolle, und ob ich nicht einen <a href="https://uberspace.de/dokuwiki/cool:ghost">Blog</a> hätte, in dem ich dazu was schreiben könnte. Hatte ich natürlich nicht.</p>
<p>Das war vorgestern.</p>
<p>In diesem Sinne: To boldly go where no man has gone before...</p>
<p><a href="https://uberspace.de/"><img src="https://blog.macfrog.de/content/images/2014/May/badge-white-88f1e734498842c81ca1f4e07fa0bc97e525b60c.png" alt="MacFrog - Hosted on Asteroids"></a></p>
</div>]]></content:encoded></item></channel></rss>