RSS Atom Add a new post titled:
Running irssi as an interactive service

I revamped my server-side IRC setup a bit. I run irssi there, mainly for logging, so I want it to always run, but only really use it interactively from time to time.

I used to have it running in a separate tmux session under my own user, started at boot-time through a crontab entry like

@reboot tmux -u new-session -d -s irssi /usr/local/bin/irssi

Now I wanted it to

  • run under the irc user
  • be controlled by runit
  • have all relevant files in /srv

This is on a new server running Debian Wheezy but should apply roughly on all UNIXoid systems.

Setting up a service with runit is quite simple, but a bit different to traditional (self-backgrounding) services: There is a run script that performs all necessary setup, and then execs the actual program. This is necessary to keep the same PID so the stop/restart functions work properly. Additionally, the service itself must not fork; it should just keep running. Optionally, stdout is piped into a dependent log service. Should either ever crash or exit, they will be restarted automatically.

The multi-user support in tmux is a bit weak, and it lacks any way to synchronously wait for the session to end without attaching to it. I settled on using screen instead– since they (by default) use different shortcuts it is quite convenient to attach to a screen session within my normal tmux.

The default Debian irc user has a home directory /var/run/ircd which does not exist unless ircd is installed (which I don't need), so just symlink this:

ln -s /srv/irc /var/run/ircd

Install the packages:

apt-get install runit irssi screen

And create the scaffolding for the service:

mkdir -p /etc/sv/irssi /etc/sv/irssi/log/main /etc/sv/irssi/supervise /etc/sv/irssi/log/supervise
cat >/etc/sv/irssi/log/run <<EOF
exec svlogd -tt ./main
chmod +x /etc/sv/irssi/log/run

Finally, create the run script for irssi itself:


exec 2>&1

export HOME=/srv/irc
export LANG=en_US.UTF-8

echo "Starting irssi..."
exec chpst -uirc screen -S irssi -m -D irssi

Explanation of the steps:

  • exec 2>&1: fold stderr into stdout so it is captured in the logs (just in case; I do this in all run-scripts)
  • exports: the run script, and subsequently the service, have an almost empty environment. Set $HOME so screen can find .screenrc, and $LANG to work correctly with UTF-8 characters
  • echo: a marker to track restarts, as screen won't produce any output
  • chpst: a tool that comes with runit to run the service in the context of another user. Easier to use than su and does not interfere with runit
  • screen -S irssi -m -D: set the session name to irssi so there is a fixed name to attach to, start detached but wait until the session finishes

Make it executable (chmod +x /etc/sv/irssi/run), and add /srv/irc/.screenrc to enable multiuser operation:

multiuser on
acladd <your username>

Then enable the service, it will start automatically:

ln -s ../sv/irssi /etc/service/irssi

and attach to it

screen -r irc/irssi

To detach without exiting, press ^A d.

OpenVPN with IPv6 and OpenBSD on a cheap VPS

One day after the Kongress I finally finished my VPN setup. The problem with most "standard" VPN setups (including mine when I went to Hamburg) is that they are IPv4 only, leaving your IPv6 traffic unencrypted unless you block it completely. OpenVPN finally supports IPv6 over TUN devices as of 2.3.0.

I have a cheap VPS from Netcup. Since they moved to KVM installing any OS is relatively easy, for this machine I chose OpenBSD. The setup should be similar on FreeBSD and DragonFly since they also have PF, although their PF version may be older and the syntax therefore slightly different.

Netcup provide one IPv6 /64, but setting up IPv6 for OpenVPN requires a separate network block for the upstream internet connection and the VPN. One option is buying another /64, but this is relatively expensive (given the whole VPS is less than 10EUR/month) and requires a fax. Instead I used a SiXXS tunnel where IPv6 addresses are free. IPv6 traffic for the VPS itself uses the native IPv6.


  • a Netcup VPS with OpenBSD (5.4 or higher, otherwise OpenVPN is too old) and working networking
  • a client (tested with Debian Wheezy)

SiXXS setup

If you do not already have a SixXS account, sign up and request a tunnel and extra subnet. Let the subnet be routed to the tunnel. Note if you're signing up new, your tunnel may need to be up for a while so you have sufficient ISK to request a subnet.

Set the tunnel to 6in4-static and enter the IPv4 address of your server. Set the MTU to 1480, 6in4 has less overhead than the other methods. On the server, add /etc/hostname.gif0:

tunnel <server IPv4> <PoP IP>
inet6 <Your IPv6> 128
dest <PoP IPv6>
mtu 1480
group egress

Bring it up with

# sh /etc/netstart gif0

your should now be able to ping6 <PoP IPv6>. Note that we did not add a default route to this interface, so normal IPv6 traffic will still use your VPS' native connection (try ping6

VPN setup

DNS recursor

To send all DNS requests through the VPN, set up a DNS recursor. To install it

# pkg_add unbound

and make sure the VPN can access the DNS recursor. To do this, look for the access-control: lines in /var/unbound/unbound.conf and add

access-control: allow
access-control: <your SixXS subnet> allow

To have it started, add the following lines to /etc/rc.conf.local:

syslogd_flags="${syslogd_flags} -a /var/unbound/dev/log"
pkg_scripts="${pkg_scripts} unbound"

Then restart syslogd and start unbound

# /etc/rc.d/syslogd restart
# /etc/rc.d/unbound start

OpenVPN and system configuration

Install OpenVPN:

# pkg_add openvpn

Generate keys following the HOWTO. Create /etc/openvpn/tun0.conf:

port 1194
proto tcp # or udp, then change client config accordingly
dev tun0
# generate following the PKI howto
ca ca.crt
cert server.crt
key server.key  # This file should be kept secret
dh dh4096.pem # I use 4096-bit stuff
server # the IPv4 VPN range
server-ipv6 <your SixXS subnet>
push "route-ipv6 2000::/3" # route internet traffic through the VPN
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS"
keepalive 10 120
tls-auth ta.key 0 # This file is secret
user _openvpn
group _openvpn
status /var/log/openvpn-status.log
log-append  /var/log/openvpn.log
verb 3

Create /etc/hostname.tun0:

!cd /etc/openvpn; /usr/local/sbin/openvpn --config tun0.conf --daemon openvpn/tun0
group vpn

Enable IPv4 and IPv6 forwarding in /etc/sysctl.conf


and for the running system run

# sysctl net.inet.ip.forwarding=1
# sysctl net.inet6.ip6.forwarding=1

Set the following in /etc/pf.conf

set block-policy return
set skip on lo

# NAT for IPv4 VPN
match out on egress from to any nat-to egress:0

# default rules
block in
pass out

# normal traffic rules
block in on egress
# allow SSH
pass in on egress proto tcp from any to (egress) port 22
pass in proto icmp
pass in proto icmp6
pass in on vpn

# OpenVPN
pass in on egress proto { tcp udp } from any to (egress) port 1194

# IPv6 routing for VPN
pass in on vpn from <your SixXS IPv6 block> to ! (egress) route-to (gif0 <PoP IPv6>)

# allow incoming tunnel traffic
pass in on egress proto 41 from <PoP IPv4> to (egress)

Adjust the last lines to your SixXS blocks. It makes sure that IPv6 traffic from the VPN is routed out through SixXS. Incoming traffic is not allowed, if you want this add a line like

pass in on gif0 from any to <your IPv6 block> <further limits> route-to (tun0 <Your IPv6>)

(untested). Incoming Proto41 traffic on the other hand is explicitly allowed, otherwise the packets never make it to the gif interface unless there has been outgoing traffic recently which created an entry in the state table.

Load the firewall configuration with

# pfctl -f /etc/pf.conf

and bring the VPN up with

# sh /etc/netstart tun0

You should now be able to connect to it. The client configuration is nothing special:

dev tun
proto tcp
remote <your server> 1194
resolv-retry infinite
ca ca.crt
cert client.crt
key client.key
ns-cert-type server
tls-auth ta.key 1
verb 3

# Debian/Ubuntu: set name server
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf

If your client has OpenVPN 2.3 or higher, IPv6 will be set up correctly now. Try

# ping6

on the client.


  • Added the pass in proto 41 line. This is necessary so the tunnel can work even if the VPN is not in use.
  • removed redirection of port 443, it's not relevant to this
Matthias Rampke

There's a new BSD in town (via). The first snapshot doesn't work out of the box, but there's a workaround:

When booting from the installer CD you will get errors like uid 0 on /: file system full clogging up the screen right after the disk setup. Press ctrl+Z to get to the shell, then

mv /tmp/* /mnt/tmp
rm -r /tmp
ln -s /mnt/tmp /tmp

This has to be done after the disk setup so the filesystems are already created and mounted.

and installation will continue. Choose http as install source and enter the server name It complained about the SHA256 sums of the downloaded files not matching what is expected, but it worked anyway.

EDIT: Snapshots seem to be gone, can't find any currently. Dunno what's up with that…

Matthias Rampke
DIY VPN with DragonFly, PF and OpenVPN

I have a server running DragonFly that I wanted to use as a VPN endpoint so I no longer have to rely on third-party VPNs jsut to get out of an insecure WiFi. These instructions, as they stand, will probably only work on Dragonfly (NAT syntax changed in OpenBSD recently; DragonFly uses pf from OpenBSD 4.4, FreeBSD 9 uses pf from OpenBSD 4.5).

The first step was getting pf up and running. All in all, the following ruleset worked for me:

int_if= "{ tun0 tun1 }"

service_ports="{ http https xmpp-server xmpp-client auth ftp >49151 }"

#table <spamd> persist
#table <spamd-white> persist

# don't touch machine-local traffic
set skip on lo
set block-policy return

scrub in

nat on $ext_if inet from to any -> ($ext_if:0)

# filtering
block in
pass out quick inet keep state
pass out quick inet6 keep state

pass in on $ext_if proto tcp to ($ext_if) port ssh keep state

pass in on $ext_if proto tcp to ($ext_if) port $service_ports keep state

# allow ICMP
pass proto icmp keep state
pass proto icmp6 keep state

# OpenVPN
pass in on $ext_if proto udp to ($ext_if) port 1194:1195 keep state
pass in on $int_if keep state
pass on $int_if proto icmp keep state

This already includes the rules to make the two VPNs work later on. Also, this ruleset it very lenient when it comes to both outgoing traffic and ICMP – everything is allowed there. I may or may not restrict this further in the future, but for now I mostly needed the NAT capabilities of pf. For some reason I needed to specify IPv4 and IPv6 rules separately, otherwise I'd get no IPv6 traffic out and lists didn't work either. Also, DragonFly's pf seems to have no state as default.

To activate pf, set


in /etc/rc.conf and load it with

sudo /etc/rc.d/pf start

When changing the ruleset, update it with

sudo /etc/rc.d/pf reload

To get the server to actually do routing, set the sysctl net.inet.ip.forwarding=1 both in /etc/sysctl.conf (so it is persistent) and (to avoid rebooting) directly:

sudo sysctl net.inet.ip.forwarding=1

Next up is configuring the VPNs. I decided to create two separate networks, one being NATed through the server to the outside world, one just for connecting to services I don't want to expose publicly on the server (or possibly in the future, other VPN clients).

First, install OpenVPN:

cd /usr/pkgsrc/net/openvpn; bmake install clean

assuming you have pkgsrc already set up. Copy /usr/pkg/share/examples/rc.d/openvpn to /usr/pkg/etc/rc.d and set openvpnenable="YES"_ in rc.conf.

Create the OpenVPN keys using easy-rsa. Copy /usr/pkg/share/openvpn/easy-rsa somewhere else, edit vars in that copy to your liking, then run

. ./vars
./build-key-server <server hostname>
./build-key <client hostname>

there to create a server and a client certificate. Also generate a TLS Auth key with openvpn --genkey --secret ta.key and copy the dh????.pem, .crt, ca.crt and ta.key to /usr/pkg/etc/openvpn. If your client is a Mac with Tunnelblick, copy the client certificate, client key, ta.key and ca.crt into a folder called .tblk along with this configuration file (call it config.ovpn):


dev tun
proto udp

remote 1194

resolv-retry infinite
ca ca.crt
cert <client name>.crt
key <client name>.key
ns-cert-type server

tls-auth ta.key 1

verb 3

The server-side side configuration goes into /usr/pkg/etc/openvpn/server.conf and contains:

local <server IP> #this is optional

port 1194

proto udp

dev tun0

ca ca.crt
cert <server name>.crt
key <server name>.key

dh dh2048.pem
;dh dh1024.pem # if you went for 1024 bit RSA

# this is the client IP range
# note that this is the same as the 'nat' line in pf.conf

ifconfig-pool-persist ipp.txt

# push default gateway to clients,
# telling them to redirect all traffic through the VPN
push "redirect-gateway def1 bypass-dhcp"

# push a good DNS server too
# if you don't the local, un-VPNed one might still be in use
push "dhcp-option DNS"

# we want clients to see each other

keepalive 10 120

tls-auth ta.key 0


user nobody
group nobody


status /var/log/openvpn-status.log

verb 3

Start OpenVPN with sudo /usr/pkg/etc/rc.d/openvpn start and you should be able to connect to the internet through the VPN (after you installed the client configuration of course).

The configuration for the second VPN is very simple – just change the port to 1195 in both client and server config (create copies), and in the server configuration file replace

# push default gateway to clients,
# telling them to redirect all traffic through the VPN
push "redirect-gateway def1 bypass-dhcp"

# push a good DNS server too
# if you don't the local, un-VPNed one might still be in use
push "dhcp-option DNS"


# push only routes to other VPN clients and server IPs
push "route"

so the clients know that everything in should go through the VPN. With this VPN you can connect to services bound e.g. to on the server which you don't want to be reachable for outsiders.

Matthias Rampke

Es ist also wieder mal ein Bundespräsident zurückgetreten. Schwarz-Gelb hat die Mehrheit, also ruft Rot-Grün nach einem parteiübergreifenden Kandidaten; ihr Kandidat von 2010 hat dem Rassisten Sarrazin Mut bescheinigt und fährt auch sonst recht eigenartige Einstellungen auf. Frauen sind sowieso von vorneherein raus, weil nach 54 Jahren Männerherrschaft eine weibliche Doppelspitze undenkbar ist.

Unter Piraten und meine Timeline rauf und runter wird nun Georg Schramm als Kandidat gehandelt. Einer der wenigen bekannten politischen Satiriker in Deutschland, einmal auf der gnz großen Bühne. Realistische Chancen: Null, aber eine Kandidatur kann ja auch schon ein Zeichen sein. Und trotzdem habe ich damit grade sehr große Bauchschmerzen. Wie hier im besten radikalselbstgerechten Tonfall, aber sehr zurecht angemerkt wird: was Schramm in seiner (meines Wissens) einzigen in einem ernsthaft politischen Umfeld gehaltenen Rede so von sich gibt, ist völlig untragbar. Bei aller Vorsicht mit der Struktureller-Antisemitismus-Keule – wer von dem unchristlich-schmutzigen Verleiher faselt, der die Weltherrschaft übernommen habe, und dann so gut wie zum Lynchmord aufruft, muss das Judentum nicht beim Namen nennen um ganz un-strukturell und direkt antisemitische Kackscheiße von sich zu geben.

Gibt es keine anderen Kandidat_innen, hinter die mensch sich schmeißen könnte? Was spricht eigentlich gegen Gesine Schwan?

PS: Nein, politischer Kabarettist zu sein reicht nicht als Qualifikation. Pispers ist zwar nicht so daneben wie Schramm, aber polemisiert und pauschalisiert auch recht umfassend.

PPS: Was macht eigentlich Hagen Rether grade so?

PPPS: Julia hat mehr Verbindungen von Schramm zu kruden Verschwörungstheoretikern ausgegraben.


CyanogenMod auf dem Motorola Milestone installieren

Archiv: das hier hatte ich mal in meinen ersten Gehversuch mit ikiwiki gepostet, der inzwischen den Weg alles zeitlichen gegangen ist. Da es aber anscheinend immer noch Menschen gibt, die noch nicht auf CyanogenMod umgestiegen sind, aber es doch mal tun wollen, hab ich das mal wieder ausgegraben. Die Infos da drin, insbesondere die Softwareversionen, sind nicht mehr alle aktuell. Ich fahre im Moment mit Cyanogenmod 7, die Builds dazu liegen hier, die aktuellen Google-Apps-Bündel sind immer auf der CM7-Seite verlinkt. Allerdings ist das Milestone mit CyanogenMod 7 auch ein bisschen überfordert – mit der heute aktuellen Version (7.1.1) geht die Kamera wieder halbwegs, vorher hat sie nach minutenlangem Einfrieren regelmäßig sich oder das ganze System zum Absturz gebracht. Gleichzeitig Musik hören und Google Maps aufrufen geht immer noch nicht. Aber dafür ist es eben der neueste heiße Scheiß :)

Diese Anleitung basiert im wesentlichen auf jener, soll aber weniger (möglichst kein) Vorwissen voraussetzen.

Disclaimer: Ich schreibe hier auf, was für mich funktioniert hat, ohne Anspruch auf Vollständigkeit oder Korrektheit. Theoretisch ist es möglich, damit aus dem Milestone einen sehr teuren Briefbeschwerer zu machen ("bricken", von engl. brick: Ziegelstein). Praktisch sollte das ziemlich schwer sein, und da wir den Bootloader selbst nicht überschreiben kann in aller Regel immer noch zumindest eine komplett neue Software geflasht werden. Wie immer gilt also: Alles Ohne Gewähr und auf eigene Gefahr!

Achtung: das Folgende spielt sich im Wesentlichen im Bootloader und Recovery-Modus ab. Dabei nimmt das Milestone keinen Strom über das Kabel an, der Akku sollte also besser voll geladen sein!

Wichtig: Es werden alle Daten auf dem Telefon (aber nicht die auf der SD-Karte) gelöscht, insbesondere alle installierten Programme und deren Einstellungen und Daten, alle Kontakte (sofern nicht mit dem Google-Konto gesynct) usw. usf. – gegebenenfalls sollten sie gesichert werden, etwa mit Titanium Backup aus dem Market. Dafür muss das Telefon gerootet werden, was aus dem OpenRecovery-Menü möglich ist. Ich habe mich aber dafür entschieden, alles neu einzurichten.

Die Situation

Nach endlos langer Zeit ist Motorola endlich mit dem Update auf Android 2.2 (Froyo) für das Milestone fertig geworden – und es ist Schrott. Gleichzeitig gibt es aber endlich eine (inoffizielle) Version von CyanogenMod für das Milestone. Da der Bootloader des Milestone nur signierte Kernel akzeptiert, muss hier ein bisschen getrickst werden, weshalb es wohl vorerst auch keinen offiziellen CyanogenMod geben wird.

Mit dem Update auf CyanogenMod 6 läuft mein Telefon schneller, stabiler, länger und kann mehr (etwa Apps auf die SD-Karte installieren). Das einzige Problem, das ich hatte, war dass der WLAN-Treiber in CM6 einen Fehler hat und unter gewissen Umständen beim Verbindungsaufbau ein Paket zwei mal schickt, mit dem die Fähigkeiten von Telefon und Basisstation ausgehandelt werden sollen. Dadurch konnte ich mich nicht mit meinem OpenWRT-Router verbinden, da dieser den Verbindungsaufbau daraufhin sofort abbrach. Als Workaround kann im Router WMM/WME abgeschaltet werden1.

OpenRecovery installieren

Vulnerable Recovery einspielen

Neben dem eigentlichen Android hat das Milestone auch einen Recovery-Modus, aus dem heraus unter anderem Over-The-Air-Updates eingespielt werden. Der Recovery-Modus kann gestartet werden, indem man das Telefon ausschaltet und dann (je nach Version) die x- oder die Kamerataste hält und es wieder einschaltet. Wenn das Warndreieck erscheint, diese Taste loslassen, die Lautstärke-nach-oben-Taste halten und die Kamerataste drücken. Dann erscheint das Recovery-Menü mit der Option apply lässt sich ein signiertes Update einspielen, das zuvor als ins Hauptverzeichnis der SD-Karte gelegt wurde. Die Menüs lassen sich über die Richtungstasten der Hardwaretastatur (D-Pad) bedienen und durch den Mittelknopf der selben auswählen.

Die ersten Android-Versionen für das Motorola Milestone (2.0.0 und 2.0.1) hatten einen Fehler in dieser Recovery-Software, der es u.A. ermöglichte das Telefon zu rooten. Dabei wurde die Signatur des von vorne bis zur Endmarkierung des zip-Formats geprüft, das Archiv aber (bedingt durch das zip-Format) von hinten entpackt – es war also möglich, ein beliebiges "Update" einzuspielen, solange ihm ein signiertes echtes Update voranging, und so beispielsweise den Root-Zugang herzustellen.

In den letzten Android-Versionen ist diese Lücke geschlossen, lässt sich aber durch Einspielen einer älteren Recovery-Version wiederherstellen. Dazu benötigt man einen Windows-Rechner (oder eine virtuelle Maschine mit Windows und USB-Zugriff), die Motorola USB Drivers (for Windows®), das Motorola-Flash-Tool RSD Lite2 und das Recovery-Only-SBF, das das verwundbare Recovery-Image im Motorola-Flash-Image-Format enthält, aber ein komplettes Downgrade auf eine ältere Android-Version erspart.

Dann kann's los gehen:

  1. die Treiber installieren
  2. RSD Lite installieren
  3. Milestone ausschalten
  4. auf dem D-Pad die Hoch-Taste (in der Orientierung der Tastatur) festhalten und Telefon einschalten. Damit wird es in den Bootloader-Modus gebracht, in dem Flash-Images von außen eingespielt werden können.
  5. wenn auf dem Bildschirm erscheint OK to Program, Connect USB Data Cable das Telefon an den Computer anschließen und dort RSD Lite starten. Achtung: RSD Lite muss mit Administratorrechten gestartet werden, dazu vor dem Starten auf die Programmverknüpfung rehtsklicken und unter Eigenschaften->Kompatibilität das entsprechende Häkchen setzen.
  6. unter ... das Recovery-SBF auswählen, in der Liste unten das Milestone, und auf "Starten" klicken
  7. im Laufe des Flashens muss das Telefon einmal in den Bootloader-Modus neustarten. In der Theorie soll das wohl von selbst passieren, bei mir bootete das Telefon aber immer normal. In diesem Fall einfach noch einmal auschalten und wie oben beschrieben den Bootloader starten. Daraufhin wird der Erfolg des Flashens verifiziert und die Anzeige in der Progress-Spalte der Geräteliste wechselt zu Finished.

Damit ist die (zu unseren Gunsten) fehlerhafte Recovery-Software wieder eingespielt.


Als nächstes wird Androidiani OpenRecovery installiert. Dazu einfach die .zip-Datei auf die SD-Karte installieren. Dort sollten dann ein und ein Verzeichnis OpenRecovery existieren. Bei der Gelegenheit kopieren wir auch gleich das [CyanogenMod-6-Update][cm6-update] (update-cm-6.….zip) und die Google Apps (die aus Lizenzgründen nicht mit CyanogenMod verteilt werden dürfen). Wir benötigen die HDPI-Version für CM6. Beide Dateien müssen in das Verzeichnis OpenRecovery/updates.

Um OpenRecovery zu starten booten wir zuerst in den (nun verletzlichen) Recovery-Modus (x oder Kamerataste halten, Lauter-Taste halten und Kamerataste drücken) und wählen apply aus. Hierbei wird nicht wirklich ein Update eingespielt, sondern durch das untergeschobene OpenRecovery gestartet3.

CyanogenMod einspielen

Wenn die .zip-Dateien für CyanogenMod 6 und die Google Apps in OpenRecovery/updates liegen, kann das eigentliche Update beginnen.

Nun zum updaten folgende Menüpunkte auswählen:

  1. Wipe Dalvik Cache
  2. Wipe Cache Partition
  3. Wipe Data / Factory Reset Hierbei werden alle Daten gelöscht!
  4. Apply Update
  5. update-cm-6.….zip
  6. gapps-hdpi-…

Damit sind CyanogenMod 6 und die Google Apps installiert!

Tastaturlayout anpassen

Für das deutsche Milestone stimmt allerdings die Tastaturbelegung noch nicht, auch das lässt sich aber aus OpenRecovery heraus ändern. Dazu muss man zurück ins Hauptmenü (Go Back) und dort unter Settings, Keyboard Layout qwertz auswählen.


Das war's, jetzt einfach im Hauptmenü "Reboot" auswählen und CyanogenMod startet.

  1. Dazu in /lib/wifi/ die Zeile wmm_enabled=1 auf wmm_enabled=0 ändern. ↩

  2. Es gibt wohl auch eine, die ich aber nicht ausprobiert habe. ↩

  3. Falls nun eine Fehlermeldung betreffend eines EOCD marker erscheint, wurde das verwundbare Recovery-Image nicht richtig eingespielt. Try again! ↩

Matthias Rampke

Das Problem

Bisher liefen alle meine Mails über Google Mail, was an sich funktioniert, und ich wollte schon immer wissen wie man so seinen eigenen Mailserver aufsetzt und betreibt. Andererseits hatte ich davor immer einen Heidenrespekt – zurecht. Hier also was ich so rausgefunden und letztenendes getan habe; die ganzen gescheiterten Zwischenschritte werde ich nicht im Detail reproduzieren.

Die Situation

Ich habe einen eigenen vServer von Netcup, nicht besonders dicke, aber tut gut seine Dienste. Darüber habe ich bisher schon Web- und Jabberserver betrieben. Es läuft Debian Testing.

Bisher lief auch ein sendmail, der die Weiterleitungen (via virtusertable einiger an meine Schwester, meinen Vater und meine Oma erledigt hat. Alles andere wurde via Catch-All-Regeln an meine Google-Mail-Adresse weitergeleitet, auf dem Server selbst fand keine Filterung oder Speicherung statt. Alles in allem ein relativ einfaches Setup, weil die komplette Spamfilterungsproblematik bei den Empfänger_innen erledigt wird.

Das neue Setup

Nachdem ich ein bisschen hin- und hergelesen habe bin ich zu dem Schluss gekommen, dass es postfix als zentraler Mailserver sein soll. Dazu kommen Dovecot als IMAP-Server und Roundcube als Webmail-Client sein. Roundcube spricht IMAP mit Dovecot, das auf die Maildirs zugreift, die postfix befüllt.

Die Spamfilterung brauchte ein wenig Experimentierung und hat auch am längsten gedauert. Letzten Endes lief es auf postgrey für Greylisting und SpamAssassin für weitergehende Spamfilterung hinaus. DSpam hatte ich kurz am laufen, aber da hat sich beim Training eine mehrere Gigabyte große Datenbank angesammelt – und das pro spamgefilterter Mailadresse, und beim Versuch das einzudämmen habe ich das ganze irgendwie so zerschossen, dass gar nichts mehr ging. Amavis braucht unglaublich viel Speicher, was mir dafür, dass es letztlich nur die Anbindung für SpamAssassin sein sollte, doch etwas viel war. Policyd habe ich nie zum laufen bekommen.


Das hier ist ein Aufschrieb von dem, was ich auf meinem Server getan habe um meinen Mailserver-Ansprüchen gerecht zu werden, soweit ich es im Nachhinein und abzüglich aller Irrwege rekonstruieren konnte. Benutzernamen, Pfade etc. müssen auf jeden Fall angepasst werden, auch sonst kann ich nicht für Vollständigkeit und/oder Richtigkeit garantieren. Nehmt es als Hinweis, lest die Dokumentation dazu, und macht mir keine Vorwürfe wenn die Hausaufgaben den Hund fressen.


Postfix an sich zu installieren und einzurichten ist relativ einfach. Ich habe die Pakete postfix, postfix-doc und postfix-pcre installiert. Letzteres bringt Unterstützung für Perl-kompatible reguläre Ausdrücke. Postfix arbeitet viel mit Mappings (etwa: Mails an werden weitergeleitet an und den lokalen Benutzer Z). Normalerweise werden dafür Hashtables verwendet, die nach jeder Änderung neu kompiliert werden müssen (postmap <Datei>) und relativ unflexibel sind; PCRE erlauben genauere Kontrolle was gematcht wird (reguläre Ausdrücke eben) und die eigentliche Quelldatei wird jedes mal auf's neue eingelesen, der Übersetzungsschritt entfällt also. Außerdem wird in verschiedenen Anleitungen zur Einbindung von Filtern so, dass sie nur auf eingehende Mails angewendet werden (etwas der Spamfilter) ein Umweg über eine PCRE-Tabelle gewählt; ob und warum das mit Hashtables nicht geht habe ich nicht untersucht.

Postfix hat zwei Haupt-Konfigurationsdateien: und gibt an, aus welchen Diensten der gesamte Postfix-Komplex besteht; das umfasst etwa den smtpd, der eingehende Mail entgegen nimmt, den qmgr, der die internen Mail-Queues verwaltet und einige mehr. Eine ganze Reihe davon ist in Debian schon vorkonfiguriert und die lassen wir auch tunlichst in Ruhe. Nur für den Spamfilter werden wir später noch einen Dienst dort hinzufügen. ist die allgemeine Postfix-Konfigurationsdatei.


Die ist eine sehr gute Ausgangsbasis. Debian-üblich wird in /etc/mailname der für E-Mails relevante Domainname des Systems festgelegt; bei mir ist das, während der Haupt-Domainname des Servers ist. In /etc/mailname ist also eingetragen. Dies wird dann auch von Postfix eingelesen.

Bei smtpd_tls_cert_file und smtpd_tls_key_file habe ich die Schlüssel- und Zertifikatsdateien eingetragen, die ich mir schon für Mail- und Jabberserver von StartSSL geholt hatte. Als mydestination habe ich nur localhost, weil alle Domains durch virtuelle Benutzer abgedeckt werden sollen.

Da mein Server eine IPv6-Adresse hat, die auch im DNS steht, musste ich mit inet_protocols = all auch die IPv6-Unterstützung in Postfix aktivieren.

Mailboxes und virtuelle Adressen

Das System für virtuelle Adressen gibt es bei Postfix in zwei Ausprägungen:

  1. virtuelle Adressen, aber alle lokalen Benutzer entsprechen UNIX-Nutzerkonten
  2. virtuelle Adressen und virtuelle Mailboxen

Da letzteres mehr Kontrolle bietet, wo die Mails letzten Endes abgeworfen werden, habe ich das gewählt. Dazu wird in definiert:

virtual_mailbox_domains = pcre:/etc/postfix/hosts
virtual_mailbox_base = /var/mail/vhosts
virtual_alias_maps = pcre:/etc/postfix/virtual
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_minimum_uid = 1000
virtual_uid_maps = hash:/etc/postfix/vmailbox.uids
virtual_gid_maps = hash:/etc/postfix/vmailbox.gids

hosts verwende ich mit PCRE, da ich auch alle Subdomains erfassen möchte, das sieht dann so aus:

/^(.+.)?$/ NONE /^(.+.)?$/ NONE /^(.+.)?$/ NONE /^(.+.)?$/ NONE

Wenn das nicht nötig ist, genügt auch virtual_mailbox_domains = /etc/postfix/hosts und hosts ist dann einfach eine Liste der Domainnamen, einer pro Zeile. Das NONE hat keine Funktion, außer hässliche Warnungen im Log zu ruhigzustellen.

Mail, die lokal zugestellt wird, soll Postfix im Maildir-Format ablegen. Was wohin kommt, definiere ich in der Hashtabelle vmailboxes – nicht vergessen: nach jeder Änderung mit postmap vmailboxes die Datenbank dazu aktualisieren. Das sieht dann so aus:

archive matthias/.Archive/
local   matthias/
matthias  matthias/
spam    matthias/.Junk/

archive entspricht einem zusätzlichen Maildir, in dem jede Mail, die ich bekomme, noch einmal abgelegt wird. Ich bin mit GMail sozialisiert und gewöhnt, dass eine Mail nicht wirklich weg ist, wenn ich sie aus der Inbox lösche. local und matthias zeigen schlicht auf das Haupt-Maildir. Die Pfade sind relativ zu virtual_mailbox_base.

In virtual werden die vielfältigen Weiterleitungen kreuz und quer durch die Weltgeschichte festgelegt:

/^matthias([+-_].+)?@(.+\.)?rampke\.de$/, matthias, archive
/^matthias([+-_].+)?@(.+\.)?2pktfkt\.de$/, matthias, archive
/^matthias([+-_].+)?@(.+\.)?2pktfkt\.net$/, matthias, archive
/^matthias([+-_].+)?@(.+\.)?grade\.so$/, matthias, archive

/^.*\+spam@.*$/ spam

# /etc/aliases
/^mailer-daemon@/ postmaster
/^postmaster@/ root
/^nobody@/ root
/^hostmaster@/ root
/^usenet@/ root
/^news@/ root
/^webmaster@/ root
/^www@/ root
/^ftp@/ root
/^abuse@/ root
/^noc@/ root
/^security@/ root
/^root@/ matthias
/^m@/ matthias
/^clamav@/ root

Der erste Teil legt fest, dass alle Mails, die an matthias@... bei einer meine Domains gehen, in meinem Google-Mail-Postfach, der Inbox und dem Archiv-Maildir abgelegt werden. Der zweite Teil leitet eine Reihe von Standard-Adressen an matthias weiter. Was an geht wird direkt im Junk-Ordner abgelegt.

In vmailbox.uids und vmailbox.gids sind die Benutzer-IDs festgelegt, mit denen die Mails in die Mailboxen einsortiert werden:

matthias    1000
local       1000
archive     1000
spam        1000  65534

65534 ist der Benutzer nobody; da sollte niemals Mail ankommen und die soll auch nirgends hingeschrieben werden.

Noch ein sudo /etc/init.d/postfix restart und der Mailserver läuft. Falls etwas schiefgegangen ist, steht das in /var/log/mail.log.


Zuerst muss Dovecot wissen, wo es die Mails findet; dazu setze ich in /etc/dovecot/conf.d/10-mail.conf

mail_location = maildir:/var/mail/vhosts/%u

Die anderen Einstellungen dort können bleiben. In 10-auth.conf wird festgelegt, wer sich per IMAP einloggen darf und worüber; bis auf weiteres sollen sich nur Systembenutzer einloggen können, das ist die Standardeinstellung. In 10-ssl.conf konnte ich wieder SSL-Schlüssel und -Zertifikat eintragen:

ssl_cert = </etc/ssl/certs/
ssl_key = </etc/ssl/private/

Damit ist auch festgelegt, dass ich in meinen Mailprogrammen immer als IMAP- und SMTP-Server eintrage, damit sich die Clients nicht über unpassende Zertifikate beschweren. Achtung: nicht in allen Android-Versionen ist StartSSL als Certificate Authority eingetragen, mein Telefon beschwert sich also immer bzw. ich muss die Zertifikatsüberprüfung im Mailprogramm abschalten. -Isebensokammanixmachen.

Dovecot ist damit schon fertig konfiguriert, aber wir können Postfix jetzt auch Authentifizierung und Verschlüsselung für SMTP-Verbindungen per SASL beibringen. Dazu kommt in /etc/postfix/

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes

und in /etc/dovecot/conf.d/10-master.conf wird im Bereich service auth hinzugefügt

unix_listener /var/spool/postfix/private/auth {
  mode = 0666

Derart gegenüber Postfix authentifizierte Clients können dann auch Mails nach außerhalb verschicken.

Eine letzte Runde Neustarts

/etc/init.d/dovecot restart
/etc/init.d/postfix restart

und auch der IMAP-Zugriff sollte klappen. (Jetzt ist der Zeitpunkt gekommen, den Mailclient einzurichten und ein paar Testmails hin- und herzuschicken. Immer gut ist auch, die Abläufe per tail -f /var/log/mail.log zu beobachten.)


An Roundcube selbst ist nicht viel zu konfigurieren, ich habe lediglich das roundcube-Paket installiert und in die entsprechende Apache-Site

Alias /mail/program/js/tiny_mce/ /usr/share/tinymce/www/
Alias /mail /var/lib/roundcube


/etc/init.d/apache2 reload

nicht vergessen.


Greylisting ist eine relativ einfache und halbwegs effektive Methode, um zumindest sehr primitiv versendeten Spam abzuhalten: wenn der Absender nicht bekannt ist, wird er erst einmal mit einer temporären Fehlermeldung wieder weggeschickt; korrekterweise probiert er es dann später noch einmal und wird zugelassen. Für die meisten Spammer ist das schon zu viel Aufwand und lohnt sich nicht mehr.

Postgrey kommt auf Debian im Paket postgrey und ist an sich schon weitgestehend fertig konfiguriert. Da ich aber wollte, dass möglichst alle interne Kommunikation per UNIX-Sockets, nicht per TCP/IP abläuft, habe ich in /etc/default/postgrey die POSTGREY_OPTS zu


geändert. Damit Postgrey dort hin schreiben darf, muss der Benutzer postgrey aber noch zur Gruppe postfix hinzugefügt werden

adduser postgrey postfix

und die Verzeichnisrechte angepasst werden

chmod 770 /var/spool/postfix/private

Nun muss Postgrey nur noch in Postfix eingebunden werden; dazu werden in /etc/postfix/ explizit Regeln für den Empfang von E-Mails angegeben:

smtpd_recipient_restrictions =
    check_policy_service unix:private/postgrey

permit_mynetworks erlaubt lokalen Benutzern (oder, falls weiter oben geändert, IPs aus bestimmten Subnetzen) den uneingeschränkten Zugriff, also u.A. Mails nach außen zu verschicken. permit_sasl_authenticated legt das selbe für per SASL-authentifizierte Verbindungen fest. Sollte keiner dieser Fälle greifen, wird per reject_unauth_destination jede Mail, für die sich Postfix nicht explizit (durch Festlegung in /etc/postfix/hosts) zuständig fühlt, verworfen. Was dann noch durchkommt, sind also Mails von außen, die empfangen werden sollen – nach dem Greylisting eben.


/etc/init.d/postgrey restart
/etc/init.d/postfix restart

werden die Änderungen aktiv.


Nachdem ich einiges rumprobiert habe hat sich SpamAssassin als einfachste und praktikabelste Lösung rausgestellt. Die Installation erfolgt via dem spamassassin-Paket; spamc wird gleich als Abhängigkeit mit installiert. Die Einrichtung folgt lose dieser Anleitung


Zunächst muss in /etc/default/spamassassin der spamd aktiviert werden:


SpamAssassin liefert einen Cronjob mit, der täglich aktualisierte Regellisten herunterlädt und die Spamdatenbank aufräumt. Um den zu aktivieren, in der selben Datei


setzen. spamd soll ebenfalls unter einem eigenen Benutzer laufen, was diesmal von Debian nicht automatisch eingerichtet wird. Also legen wir einen an:

adduser --system --home /var/lib/spamassassin --disabled-password --no-create-home --group spamd2Adding
chown -R spamd:spamd /var/lib/spamassassin

und passen /etc/default/spamassassin an

OPTIONS="--create-prefs --max-children 1 --username spamd --helper-home-dir"

--max-children 1 begrenzt die Anzahl von Prozessen, die Spamd gleichzeitig startet, auf das Minimum, da jeder davon ziemlich viel Speicher frisst und bei dem Mailaufkommen auf meinem Server das allemal reicht.

An der eigentlichen SpamAssassin-Konfiguration in /etc/spamassassin muss erstmal nichts geändert werden.

Einbindung in Postfix

Um SpamAssassin in Postfix einzubinden, kommt ans Ende von /etc/postfix/

spamassassin unix -     n       n       -       -       pipe
    user=spamd argv=/usr/bin/spamc -f -e
    /usr/sbin/sendmail -oi -f ${sender} ${recipient}

und in die neu anzulegende Datei /etc/postfix/spamassassin

/./ FILTER spamassassin:spamassassin

/./ ist wieder eine PCRE, die festlegt wann der Spamfilter anspringt. In dieser Variante wird er dann für jede eingehende Mail verwendet, z.B. mit /^matthias([+-_].+)?@/ nur für die Mails an mich. Im ist festgelegt, dass auch spamc als Benutzer spamd ausgeführt wird und die gefilterten Mails via sendmail wieder in die Verarbeitung eingeschleust werden. sendmail wird von Postfix bereitgestellt und fügt die Mails nach allen Filtern ein, es entsteht also keine Endlosschleife.

Zu guter Letzt muss dieser Filter noch in /etc/postfix/ eingebunden werden, indem die smtpd_recipient_restrictions erweitert werden:

smtpd_recipient_restrictions =
    check_policy_service unix:private/postgrey
    check_recipient_access pcre:/etc/postfix/spamassassin


SpamAssassin benutzt auch Bayes-Filter, die mit Spam- und Nicht-Spam-('Ham'-)Mails trainiert werden sollten. Dazu braucht man einen vernünftigen Korpus an ebensolchen, etwa den 2006 TREC Public Spam Corpus. Darin sind einige zehntausend Spam- und Ham-Mails enthalten, die wir aber erstmal in verschiedene Ordner auseinandersortieren:

tar xzf trec06p.tgz
cd trec06p/full
awk 'BEGIN { print "rm -rf spam ham; mkdir -p ham spam" } /ham.*/ { split($2,a,"/"); print( "ln " $2 " ham/" a[3]a[4]); } /spam.*/ { split($2,a,"/"); print( "ln " $2 " spam/" a[3]a[4]); }' index | sh
cd ../..

Die Mails liegen jetzt als einzelne, nummerierte Dateien in trec06p/full/spam und trec06p/full/ham, nun kann SpamAssassin damit gefüttert werden:

su spamd -s /bin/sh -c "/usr/bin/sa-learn --ham --progress trec06p/full/ham/"
su spamd -s /bin/sh -c "/usr/bin/sa-learn --spam --progress trec06p/full/spam/"

Kaffeepausenzeit. Ich habe zusätzlich auch mittels getmail (gleichnamiges Paket) meine Mails von GMail in mein Archiv-Maildir geladen. Dazu kommt in /home/matthias/.getmail/getmailrc

type = SimplePOP3SSLRetriever
server =
username = matthias.rampke
password = PASSWORT

type = Maildir
path = /var/mail/vhosts/matthias/.Archive

und in den GMail-Einstellungen muss natürlich POP3 für alle Mails aktiviert sein. Dann einfach mit mit getmail laden (so sind sie dann auch per IMAP und Roundcube wieder verfügbar) und SpamAssassin damit füttern:

su matthias -c getmail
chmod -R g+r /var/mail/vhosts/matthias/.Archive/new /var/mail/vhosts/matthias/.Archive/cur
adduser spamd matthias
su spamd -s /bin/sh -c "/usr/bin/sa-learn --ham --progress /var/mail/vhosts/matthias/.Archive/new /var/mail/vhosts/matthias/.Archive/cur"
chmod -R g-r /var/mail/vhosts/matthias/.Archive/new /var/mail/vhosts/matthias/.Archive/cur
deluser spamd matthias

Die chown-/adduser-Zirkelei ist nötig, damit sa-learn auch als Benutzer spamd die Mails einlesen kann. Vorsicht mit dem vielen Kaffee! Das kann jetzt alles im Hintergrund weiterlaufen, braucht aber vergleichsweise viel RAM.

DomainKeys Identified Mail

DKIM ist ein Verfahren, um durch im DNS hinterlegte öffentliche Schlüssel zu beweisen, dass man berechtigt ist, Mails von einer bestimmten Domain (bspw. zu versenden. SpamAssassin bezieht das in die Spam-Bewertung bereits mit ein, hier geht es nun darum, ausgehende Mail zu signieren.

Warnung: Falls Mails von dieser Domain auch über andere SMTP-Server verschickt werden, kann die Deklaration der Author Domain Signing Practices dazu führen, dass diese mit erhöhter Wahrscheinlichkeit als Spam klassifiziert werden. Google Mail erlaubt, andere Mailadressen (nach Verifikation) als Absender zu verwenden, gibt aber im Sender:-Header die GMail-Adresse an und fügt eine DKIM-Signatur für bzw. hinzu, so dass die Mails als gültig signiert durchgehen; ich weiß nicht wie das bei anderen Webmail-Diensten ist.

Die Signierung wird durch das Paket dkim-filter erledigt. Der DKIM-Dienst spricht dann das (ursprünglich für Sendmail entwickelte) milter-Protokoll mit Postfix. Die Einrichtung folgt lose dieser und jener Anleitung.

Zuerst stelle ich in /etc/default/dkim-filter ein, dass die Kommunikation über einen UNIX-Socket laufen soll:


und füge dkim-filter zur postfix-Gruppe hinzu, wie auch schon postgrey:

adduser dkim-filter postfix

Dann erzeuge ich die DKIM-Keys für meine Domains:

mkdir -p /etc/dkim; cd /etc/dkim
for d in; do
    mkdir $d; cd $d
    dkim-genkey -t -s mail -d $d
    mv mail.private mail
    chmod 0400 mail
    cd ..

Die privaten Schlüssel liegen jetzt jeweils in /etc/dkim/<Domain>/mail, wobei mail der DKIM-Selektor ist. dkim-filter braucht nun eine Liste dieser Schlüssel, die ich in /etc/dkim/keylist ablege:


Jetzt kann das ganze auch in /etc/dkim-filter.conf eingetragen werden:

UMask                   000
Domain        ,,,
KeyList                 /etc/dkim/keylist

UMask 000 ist notwendig, damit Postfix den Socket benutzen kann. Der Rest von dkim-filter.conf kann so bleiben. Schlussendlich erzählen wir Postfix in /etc/postfix/ noch davon

milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:private/dkim
non_smtpd_mitlers = unix:private/dkim

Und starten dkim-filter und postfix neu

/etc/init.d/dkim-filter restart
/etc/init.d/postfix restart

Von nun an sollten Mails, die über diesen SMTP-Server verschickt werden, signiert sein, d.h. einen DKIM-Signature:-Header haben. Damit andere die Signatur auch überprüfen können, muss für jede Domain ein TXT-Record mail._domainkey.<Domain> eingerichtet werden; was genau da rein muss steht jeweils in /etc/dkim/<Domain>/mail.txt. Optional kann auch noch mitgeteilt werden, dass Empfänger erwarten sollen, dass alle Mails von dieser Domain signiert sind; dazu wird im TXT-Record für _adsp._domainkey.<Domain>



Um DKIM zu testen, kann man eine Mail an schicken (Betreff und Inhalt egal) und bekommt das Ergebnis der Signaturüberprüfung zurückgemailt.


Einen Mailserver mit allem drum und dran aufsetzen geht, aber ist definitiv nicht einfach. Am längsten hat für mich die Suche nach einer funktionierenden Spamfilterlösung gedauert, alles in allem waren es knapp drei Tage. Beim nächsten Mal geht's dann hoffentlich schneller. Falls irgendwas so nicht stimmt oder nicht funktioniert, mailt mir:

Matthias Rampke
Realnamenzwang oder: Wie heißt du eigentlich im Internet?

Zur Klarnamendebatte ist schon viel Text den Stream heruntergeflossen, trotzdem kommt mir ein Aspekt dabei etwas zu kurz. Häufig wird auf die Notwendigkeit von Anonymität für die abgezielt, die es sich nicht leisten können unter ihrem Realnamen aufzutreten (und das ist nicht so weit weg wie man denkt). Ich habe mit dem Realnamenzwang aber auch ein first world problem:

Mein Umfeld ist sehr stark durch Twitter geprägt: fast alle Menschen mit denen ich außerhalb der Uni zu tun habe twittern, kenne ich von Twitter oder kenne ich über Twitterer bzw. von Twittererveranstaltungen. Das heißt für mich: ich kenne sie primär über ihren Twitternamen oder andere, selbstgewählte Nicknames. Bei einigen kenne ich auch ihren Geburtsnamen ganz oder teilweise, aber bei den meisten muss ich zumindest kurz überlegen, bevor er mit einfällt. Früher oder später werde ich mir auch eine Liste machen müssen, bisher geht's aber auch noch so – weil es im Alltag keine Rolle spielt, wenn ich mal jemandes elterngewählten Vornamen nicht weiß.

Ich kannte mspro schon bevor er zu Michael Seemann wurde, ob Mario Sixtus "wirklich" so heißt – I don't know and I don't care. Meine Exfreundin habe ich 8 Monate lang nicht mit ihrem Geburtsnamen angesprochen.

Insofern sind soziale Netze mit Realnamenzwang für mich regelmäßig irritierend: Wer bist du? Erkenne ich deinen Avatar? klick klick wo ist der Link zu deinem Twitterprofil?

Google verlangt, man solle den Namen verwenden, den Freunde, Familie und Kolleg_innen verwenden. Da wo ich lebe, ist das nicht der Name, der auf der Geburtsurkunde steht (außer für die Familien vielleicht, aber die sind ja wiederum (noch) nicht bei Google+). Das ist es, was Google respektieren sollte.


Mein Telefon ist ein Motorola Milestone – als ich mir das Ende 2009 geholt habe hat sich das gerade auch mein halber Bekanntenkreis angeschafft und (damals) für gut befunden. Sonst hätte ich's auch wahrscheinlich nicht genommen.

Mein UPTÄCKA-Rucksack ist unglaublich praktisch – zuerst gesehen habe ich ihn bei Philip, der ihn gekauft hat, weil er ihn bei Julian gesehen hat.

Heute verschickt wird mein nächster MP3-Player, mal sehen ob er meinen leider etwas zu grob behandelten iPod ersetzen können wird. Darauf, dass er dafür geeignet sein könnte, hat mich Svenja gebracht.

Ich kaufe, was ich bei anderen Leuten gesehen und für interessant gefunden habe. Bin ich ein Technikfashionvictim?

Wenn jemensch Klamotten anzieht, die er_sie irgendwo gesehen und für schön befunden hat, ist er_sie dann ein Fashion victim? Hört doch einfach auf, Leute nach euren Ansprüchen an Originalität und Individualität zu messen, und lasst sie einfach machen, ja? Bittedanke.


This blog is powered by ikiwiki.