Tag Debian

  • A Mail Server on Debian

    After decades of (essentially) using other people's smarthosts to send mail, I have recently needed to run a full-blown, deliver-to-the-world mail server (cf. Das Fürchten lernen; it's in German, though).

    While I had expected this to be a major nightmare, it turns out it's not so bad at all. Therefore I thought I'd write up a little how-to-like thing – perhaps it will help someone to set up their own mail server. Which would be a good thing. Don't leave mail to the bigshots, it's too important for that.

    Preparation

    You'll want to at least skim the exim4 page on the Debian wiki as well as /usr/share/doc/exim4/README.Debian.gz on your box. Don't worry if any of that talks about things you've never heard about at this point and come back here.

    The most important thing to work out in advance is to get your DNS to look attractive to the various spam estimators; I didn't have that (mostly because I moved “secondary” domains first), which caused a lot of headache (that article again is in German).

    How do you look attractive? Well, in your DNS make sure the PTR for your IP is to mail.<your-domain>, and make sure mail.<your-domain> exists and resolves to that IP or a CNAME pointing there. Note that this means that you can forget about running a mail server on a dynamic IP. But then dynamic IPs are a pain anyway.

    Before doing anything else, wait until the TTL of any previous records of this kind has expired. Don't take this lightly, and if you don't unterstand what I've been saying here, read up on DNS in the meantime. You won't have much joy with your mail server without a reasonable grasp of reverse DNS, DNS caching, and MX records.

    Use the opportunity to set the TTL of the MX record(s) for your domain(s) to a few minutes perhaps. Once you have configured your mail system, you can then quickly change where other hosts will deliver their mail for your domain(s) and raise the TTLs again.

    Exim4

    Debian has come with the mail transfer agent (MTA; the mail server proper if you will) exim4 installed by default for a long, long time, and I've been using it on many boxes to feed the smart hosts for as long as I've been using Debian. So, I'll not migrate to something else just because my mail server will talk to the world now. Still, you'll have to dpkg-reconfigure exim4-config. Much of what's being asked by that is well explained in the help texts. Just a few hints:

    • “General type of mail configuration” would obviously be “internet site“.
    • Mail name ought to be <your domain>; if you have multiple domains, choose the one you'd like to see if someone mails without choosing any.
    • Keep the IP addresses to listen on empty – you want other hosts to deliver mail on port 25. Technically, it would be enough to listen only on the address your MX record points to, but that's a complication that's rarely useful.
    • Relaying mail for non-local domains is what you want if you want to be a smart host yourself. You'll pretty certainly want to keep this empty as it's easy to mess it up, and it's easy to configure authenticated SMTP even on clients (also see client connections on avoiding authenticated SMTP on top).
    • Exim also is a mail delivery agent (MDA), i.e., something that will put mail for domains it handles into people's mail boxes. I'll assume below that you select Maildir format in home directory as the delivery method. Maildir is so much cooler than the ancient mboxes, and whoever wants something else can still use .forward or procmail.
    • And do split your configuration into small files. Sure, you'll have to remember to run update-exim4.conf after your edits, but that litte effort will be totally worth it after your next dist-upgrade, when you won't have to merge the (large) exim4 config file manually and figure out what changes you did where.

    DNS Edits

    With this, you should be in business for receiving mail. Hence, make your MX record point to your new mail server. In an NSD zone file (and NSD is my choice for running my DNS server), this could look like:

    <your domain>.  IN MX 10 <your domain>.
    

    (as usual in such files: Don't forget the trailing dots).

    A couple of years ago, it was all the craze to play games with having multiple MX records to fend off spam. It's definitely not worth it any more.

    While I personally think SPF is a really bad idea, some spam filters will regard your mail more kindly if they find an SPF record. So, unless you have stronger reasons to not have one than just “SPF is a bad concept and breaks sane mailing list practices, .forward files and simple mail bouncing”, add a record like this:

    <your domain>.                3600    IN      TXT     "v=spf1" "+mx" "+a" "+ip4:127.0.0.1" "-all"
    

    – where you have to replace the 127.0.0.1 with your IP and perhaps add a similar ip6 clause. What this means: Mail coming from senders in <your domain> ought to originate at the IP(s) given, and when it comes from somewhere else it's fishy. Which is why this breaks good mailing list practices. But forunately most spam filters know that and don't interpret these SPF clauses to narrow-mindedly.

    SSL

    I'm not a huge fan of SSL as a base for cryptography – X.509 alone is scary and a poor defense against state actors –, but since it's 2021, having non-SSL services doesn't look good. Since it's important to look good so people accept your mail, add SSL to your exim installation.

    Unless you can get longer-living, generally-trusted SSL certificates from somewhere else, use letsencrypt certificates. Since (possibly among others) the folks from t-online.de want to see some declaration who is behind a mail server on such a web site, set up a web server for mail.<your-domain> and obtain letsencrypt SSL certificates for them in whatever way you do that.

    Then, in the post-update script of your letsencrypt updater, run something like:

    /bin/cp mail.crt mail.key /etc/exim4/ssl/
    /usr/sbin/service exim4 restart
    

    (which of course assumes that script runs as root or at least with sufficient privileges). /etc/exim4/ssl you'll have to create yourself, and to keep your key material at least a bit secret, do a:

    chown root:Debian-exim /etc/exim4/ssl
    chmod 750 /etc/exim4/ssl
    

    – that way, exim can read it even if it's already dropped its privileges, but ordinary users on your box cannot.

    Then tell exim about your keys. For that, use some file in /etc/exim4/conf.d/main; such files are the main way of configuring the exim4 package in non-trivial ways. I have 00_localmacros, which contains:

    MAIN_TLS_ENABLE = yes
    MAIN_TLS_CERTIFICATE = /etc/exim4/ssl/mail.crt
    MAIN_TLS_PRIVATEKEY = /etc/exim4/ssl/mail.key
    

    – that ought to work for you, too.

    Then, do the usual update-exim4.conf && service exim4 restart, and you should be able to speak SSL with your exim. The easiest way to test this is to install the swaks package (which will come in useful when you want to run authenticated SMTP or similar, too) and then run:

    swaks -a -tls -q HELO -s mail.<your domain> -au test -ap '<>'
    

    This will spit out the dialogue with your mail server and at some point say 220 TLS go ahead or so if things work, some more or less helpful error message if not.

    Aliases

    Exim comes with the most important aliases (e.g., postmaster) pre-configured in /etc/aliases. If you want to accept mail for people not in your /etc/passwd, add them there.

    The way this is set up, exim ignores domains; if you told exim to accept mails for domain1.de and domain2.fi, then mail to both user@domain1.de and user@domain2.fi will end up in /home/user/Maildir (or be rejected if user doesn't exist and there's no alias either). If you want to have domain-specific handling, add a file /etc/exim4/forwards that contains pairs like:

    drjekyll@example.org: mrhyde@otherexample.org
    

    The standard Debian configuration of Exim will not evaluate this file; to make it do that, drop a file wil something like:

    # Route using a global incoming -> outgoing alias file
    
    global_aliases:
      debug_print = "R: global_aliases for $local_part@$domain"
      driver = redirect
      domains = +local_domains
      allow_fail
      allow_defer
      data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/forwards}}
    

    into (say) /etc/exim4/conf.d/router/450_other-aliases. After the usual update-exim4.conf, you should be good to go.

    Client Connections

    This setup only accepts mail for transport locally, and it will only deliver locally. That is: This isn't a smarthost setup yet.

    For delivery from remote systems, we're using ssh because pubkey auth is cool. This even works from an exim on the remote system …

  • Perhaps I should be moving to gentoo

    I'm reading PDFs quite a bit, most of them in my beloved zathura. So, I was dismayed when today I paged through a book that showed in zathura as on the left side of this figure:

    Renderings of a PDF in poppler and mupdf.

    The metrics are off so badly that readability suffers.

    Rather than try to fix the PDF, I remembered I had for a long time wanted to look into using mupdf as a backend for zathura rather than its default poppler, if only because poppler used to have a shocking amount of rather serious bugs a couple of years ago (now that I think of it: It's been a while since I last heard of any – hm).

    Bringing up the PDF in mupdf looked a lot better (the right panel in the above figure). Which then led to a bout of yak shaving, because there is a plugin for zathura that promised to do what I wanted, zathura-pdf-mupdf, but of course nobody has bothered to package it up for Debian yet. So… let's try to build it.

    It's probably not a good sign that the package's README talks about make to build the thing, whereas the web page talks about a build system with commands meson and ninja (that, frankly, I had never heard about before, but at least it's in Debian). But, never mind, let's do meson build && cd build && ninjia (oh wow).

    Of course, building fails with something like:

    ../zathura-pdf-mupdf/index.c: In function ‘build_index’:
    ../zathura-pdf-mupdf/index.c:68:7: error: unknown type name ‘fz_location’; did you mean ‘fz_catch’?
           fz_location location = fz_resolve_link(ctx, document, outline->uri, &x, &y);
           ^~~~~~~~~~~
    

    A quick web search shows that this fz_location is part of the mupdf API and has indeed undergone an API change. So, I backported libmupdf from Debian testing (I'm on Debian stable almost always), and because that needs mujs, I backported that, too. Mujs sounds a lot like javascript in PDF, and that's where I first think gentoo: with its USE flags it would proabably make it easier to just keep javascript out of my PDF rendering engines altogether. Which is something I'd consider an excellent idea.

    Anyway, with a bit of hacking around – I don't have a libmupdf-third library that the meson build file mentions but perhaps doesn't need any more – I then got the plugin to build.

    Regrettably, zathura still would not use mupdf to render, saying:

    error: Could not load plugin '/usr/lib/i386-linux-gnu/zathura/libpdf-mupdf.so'
    (/usr/lib/i386-linux-gnu/zathura/libpdf-mupdf.so:
    undefined symbol: jpeg_resync_to_restart).
    

    Again asking a search engine about typical scenearios that would lead to this failure when loading a plugin, there's quite a bit of speculation, one of it being about using libjpeg-turbo instead of libjpeg. Which made me see what this plugin links again. Fasten your seat belts:

    $ ldd /usr/lib/i386-linux-gnu/zathura/libpdf-mupdf.so
            linux-gate.so.1 (0xf7fa7000)
            libgirara-gtk3.so.3 => /usr/lib/i386-linux-gnu/libgirara-gtk3.so.3 (0xf5d23000)
            libcairo.so.2 => /usr/lib/i386-linux-gnu/libcairo.so.2 (0xf5bd3000)
            libglib-2.0.so.0 => /usr/lib/i386-linux-gnu/libglib-2.0.so.0 (0xf5a9a000)
            libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf58bc000)
            libgtk-3.so.0 => /usr/lib/i386-linux-gnu/libgtk-3.so.0 (0xf50bc000)
            libgdk-3.so.0 => /usr/lib/i386-linux-gnu/libgdk-3.so.0 (0xf4fae000)
            libpango-1.0.so.0 => /usr/lib/i386-linux-gnu/libpango-1.0.so.0 (0xf4f5f000)
            libgio-2.0.so.0 => /usr/lib/i386-linux-gnu/libgio-2.0.so.0 (0xf4d57000)
            libgobject-2.0.so.0 => /usr/lib/i386-linux-gnu/libgobject-2.0.so.0 (0xf4cf2000)
            libjson-c.so.3 => /usr/lib/i386-linux-gnu/libjson-c.so.3 (0xf4ce5000)
            libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xf4cc2000)
            libpixman-1.so.0 => /usr/lib/i386-linux-gnu/libpixman-1.so.0 (0xf4c12000)
            libfontconfig.so.1 => /usr/lib/i386-linux-gnu/libfontconfig.so.1 (0xf4bc5000)
            libfreetype.so.6 => /usr/lib/i386-linux-gnu/libfreetype.so.6 (0xf4b02000)
            libpng16.so.16 => /usr/lib/i386-linux-gnu/libpng16.so.16 (0xf4ac3000)
            libxcb-shm.so.0 => /usr/lib/i386-linux-gnu/libxcb-shm.so.0 (0xf4abe000)
            libxcb.so.1 => /usr/lib/i386-linux-gnu/libxcb.so.1 (0xf4a90000)
            libxcb-render.so.0 => /usr/lib/i386-linux-gnu/libxcb-render.so.0 (0xf4a81000)
            libXrender.so.1 => /usr/lib/i386-linux-gnu/libXrender.so.1 (0xf4a75000)
            libX11.so.6 => /usr/lib/i386-linux-gnu/libX11.so.6 (0xf4926000)
            libXext.so.6 => /usr/lib/i386-linux-gnu/libXext.so.6 (0xf4911000)
            libz.so.1 => /lib/i386-linux-gnu/libz.so.1 (0xf48f0000)
            librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xf48e5000)
            libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xf47df000)
            libpcre.so.3 => /lib/i386-linux-gnu/libpcre.so.3 (0xf4766000)
            /lib/ld-linux.so.2 (0xf7fa9000)
            libgmodule-2.0.so.0 => /usr/lib/i386-linux-gnu/libgmodule-2.0.so.0 (0xf4760000)
            libpangocairo-1.0.so.0 => /usr/lib/i386-linux-gnu/libpangocairo-1.0.so.0 (0xf4750000)
            libXi.so.6 => /usr/lib/i386-linux-gnu/libXi.so.6 (0xf473d000)
            libXcomposite.so.1 => /usr/lib/i386-linux-gnu/libXcomposite.so.1 (0xf4739000)
            libXdamage.so.1 => /usr/lib/i386-linux-gnu/libXdamage.so.1 (0xf4734000)
            libXfixes.so.3 => /usr/lib/i386-linux-gnu/libXfixes.so.3 (0xf472d000)
            libcairo-gobject.so.2 => /usr/lib/i386-linux-gnu/libcairo-gobject.so.2 (0xf4721000)
            libgdk_pixbuf-2.0.so.0 => /usr/lib/i386-linux-gnu/libgdk_pixbuf-2.0.so.0 (0xf46f4000)
            libatk-1.0.so.0 => /usr/lib/i386-linux-gnu/libatk-1.0.so.0 (0xf46cb000)
            libatk-bridge-2.0.so.0 => /usr/lib/i386-linux-gnu/libatk-bridge-2.0.so.0 (0xf4693000)
            libxkbcommon.so.0 => /usr/lib/i386-linux-gnu/libxkbcommon.so.0 (0xf464d000)
            libwayland-cursor.so.0 => /usr/lib/i386-linux-gnu/libwayland-cursor.so.0 (0xf4644000)
            libwayland-egl.so.1 => /usr/lib/i386-linux-gnu/libwayland-egl.so.1 (0xf463f000)
            libwayland-client.so.0 => /usr/lib/i386-linux-gnu/libwayland-client.so.0 (0xf4630000)
            libepoxy.so.0 => /usr/lib/i386-linux-gnu/libepoxy.so.0 (0xf451e000)
            libharfbuzz.so.0 => /usr/lib/i386-linux-gnu/libharfbuzz.so.0 (0xf4407000)
            libpangoft2-1.0.so.0 => /usr/lib/i386-linux-gnu/libpangoft2-1.0.so.0 (0xf43ee000)
            libXinerama.so.1 => /usr/lib/i386-linux-gnu/libXinerama.so.1 (0xf43e7000)
            libXrandr.so.2 => /usr/lib/i386-linux-gnu/libXrandr.so.2 (0xf43da000)
            libXcursor.so.1 => /usr/lib/i386-linux-gnu/libXcursor.so.1 (0xf43cd000)
            libthai.so.0 => /usr/lib/i386-linux-gnu/libthai.so.0 (0xf43c1000)
            libfribidi.so.0 => /usr/lib/i386-linux-gnu/libfribidi.so.0 (0xf43a5000)
            libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xf439f000)
            libmount.so.1 => /lib/i386-linux-gnu/libmount.so.1 (0xf4333000)
            libselinux.so.1 => /lib/i386-linux-gnu/libselinux.so.1 (0xf4306000)
            libresolv.so.2 => /lib/i386-linux-gnu/libresolv.so.2 (0xf42ec000)
            libffi.so.6 => /usr/lib/i386-linux-gnu/libffi.so.6 (0xf42e2000)
            libexpat.so.1 => /lib/i386-linux-gnu/libexpat.so.1 (0xf42a5000)
            libuuid.so.1 => /lib/i386-linux-gnu/libuuid.so.1 (0xf429b000)
            libXau.so.6 => /usr/lib/i386-linux-gnu/libXau.so.6 (0xf4296000)
            libXdmcp.so.6 => /usr/lib/i386-linux-gnu/libXdmcp.so.6 (0xf428f000)
            libdbus-1.so.3 => /lib/i386-linux-gnu/libdbus-1.so.3 (0xf4230000)
            libatspi.so.0 => /usr/lib/i386-linux-gnu/libatspi.so.0 (0xf41fb000)
            libgraphite2.so.3 => /usr/lib/i386-linux-gnu/libgraphite2.so.3 (0xf41cd000)
            libdatrie.so.1 => /usr/lib/i386-linux-gnu/libdatrie.so.1 (0xf41c3000)
            libblkid.so.1 => /lib/i386-linux-gnu/libblkid.so.1 (0xf4163000)
            libbsd.so.0 => /usr/lib/i386-linux-gnu/libbsd.so.0 (0xf4144000)
            libsystemd.so.0 => /lib/i386-linux-gnu/libsystemd.so.0 (0xf4099000)
            liblzma.so.5 => /lib/i386-linux-gnu/liblzma.so.5 (0xf406d000)
            liblz4.so.1 => /usr/lib/i386-linux-gnu/liblz4.so.1 (0xf404d000)
            libgcrypt.so.20 => /lib/i386-linux-gnu/libgcrypt.so.20 (0xf3f6a000)
            libgpg-error.so.0 => /lib/i386-linux-gnu/libgpg-error.so.0 (0xf3f45000)
    

    Now, I appreciate that glueing two pieces of relatively complex code together can be a bit involved, but: 69 libraries!? Among them Xrandr, Xinerama, wayland (which I don't use), systemd (even if I used it: what would that plugin have to do with it?), gpg-error, selinux, and then some things I've never heard about.

    I'm sorry, but this is wrong. Which makes me think hard if gentoo's USE flags might not really be the way to go in this day and age of exploding dependencies.

    Holy cow.

    In case you came here from a search engine that hit on one of the error messages: After writing this, I was hungry and let it sit. The one thing I can tell you is that the elusive jpeg_resync_to_restart is in libjpeg, indeed. What I don't know yet is why that library hasn't made it into the heap of libraries that the plugin links to.

    I'd suspect that zathura and mupdf are built against different libjegs – but then I'd have to explain how that would have happened. Hm.

    Nachtrag (2021-02-13)

    Well, I couldn't let it sit, so here's what I needed to do (and I suspect I'd have found it in one of the upstream branches):

    1. The libjpeg thing really is that the libjpeg library needs to be linked into the plugin in Debian; the details I can't quite work out, because I'd say the linker should be able to work that out, but clearly it's not, because the situation is actually being cared for in the plugin's meson file. However, you need to manually flip a switch: this would be a ./configure run in autoconf, but here, the command line is:

      meson setup --wipe -D link-external=true  build
      
    2. However, one link-time dependency is missing with the mupdf from Debian bullseye, and that's the spooky mujs. To fix this, patch the build file like so:

      diff --git a/meson.build b/meson.build
      index 23cdc6a..24929ca 100644
      --- a/meson.build
      +++ b/meson.build
      @@ -19,8 +19,8 @@ zathura = dependency('zathura', version: '>=0.3.9')
       girara = dependency('girara-gtk3')
       glib = dependency('glib-2.0')
       cairo = dependency('cairo' …

« Seite 2 / 2

Letzte Ergänzungen