How to Run ASSP As A Systemd Service With Systemctl

Running the Anti-Spam SMTP Proxy (ASSP) reliably on a modern Linux server requires a proper service manager. By default, ASSP needs to start as root to bind to privileged mail ports (like port 25), after which it forks and spawns its main worker processes under a non-privileged user like nobody.

Using a custom systemd service file allows Linux to seamlessly handle this handoff, manage automatic restarts on failure, and handle process tracking without dealing with legacy PID files.


The Complete systemd Service File

Create a new service unit configuration file using your preferred text editor:

sudo nano /etc/systemd/system/assp.service

Paste the following optimized configuration into the file:

[Unit]
Description=ASSP (Anti-Spam SMTP Proxy)
After=network.target

[Service]
Type=forking
WorkingDirectory=/usr/local/assp
ExecStart=/usr/bin/perl /usr/local/assp/assp.pl
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Why This Configuration Works

  • Type=forking: Crucial for ASSP. It tells systemd that the initial process (started as root) will spawn a child process and then exit. systemd will wait for this handoff to complete rather than assuming the application crashed.
  • Root Execution: By omitting a User= directive in the service file, systemd launches the Perl script as root. This gives ASSP the authority to bind to port 25 before it internally drops its own privileges down to the nobody process.
  • No PID File Required: Modern systemd uses Linux control groups (cgroups) to track process trees. It will automatically detect and monitor the spawned nobody worker process without needing a legacy PIDFile directive.
  • Automatic Restarts: The Restart=always and RestartSec=5 directives ensure that if ASSP experiences a memory leak or crash, the system will automatically spin it back up after 5 seconds.

Activating and Managing ASSP

Once your service file is saved, run the following sequence to notify the system manager, enable the boot hook, and launch the proxy:

# 1. Reload systemd to detect the new service file
sudo systemctl daemon-reload

# 2. Enable ASSP to start automatically on system boot
sudo systemctl enable assp

# 3. Start the ASSP proxy service immediately
sudo systemctl start assp

Verifying the Service Status

To ensure that systemd is accurately tracking the background process tree, check the service status:

sudo systemctl status assp

You should see an active (running) status, and the CGroup section at the bottom will display the active Perl processes running under the nobody user.

Monitoring Real-Time Logs

Because systemd manages the execution environment, all standard output from ASSP is sent straight to the system journal. You can follow live spam-filtering logs using journalctl:

sudo journalctl -u assp -f

Why Tailscale + Firewalld Is My Ultimate Remote Access Stack

Setting up secure remote access usually feels like a balancing act between security and sheer frustration. For a long time, my server setup relied on Fail2ban throwing up dynamic walls via traditional iptables. It worked, but it was messy, cluttered, and stopping the service felt like waiting for water to boil as thousands of rules were torn down one by one.

Recently, I made two major upgrades to my network architecture: migrating my firewall backend entirely to Firewalld (using optimized ipsets), and rolling out Tailscale for zero-config mesh networking.

Here is why this combination has completely transformed how I manage my servers and devices.


Clean Infrastructure: The Firewalld Advantage

If you are still managing raw iptables chains for services like Fail2ban, do yourself a favor and migrate to Firewalld. Moving my setup to Firewalld made overall server configuration infinitely easier.

Instead of dealing with an unreadable wall of text when checking active blocks, Firewalld handles everything through structured zones and clean, dynamic kernel ipsets. Fail2ban now behaves itself flawlessly in the background, keeping my main firewall rules clean and freeing up system resources.


Tailscale: Secure Networking That Just Clicks

Tailscale is built on top of WireGuard®, creating a secure, encrypted mesh network (a “Tailnet”) across your devices, no matter where they are in the world. You can download the client directly from the Tailscale Website.

There is no port forwarding required on your home router, and Firewalld makes it incredibly simple to handle your security permissions. To keep your network segmented, Firewalld allows you to isolate your virtual private network interface into its own strict, custom security perimeter.

Rather than dumping your mesh traffic into a generic, open “trusted” zone, you can create a custom zone that explicitly allows only the services you choose. Assuming your virtual mesh network interface is named vpn-mesh0, here are the three commands to lock it down:

bash

# 1. Create a dedicated firewall zone for your secure network mesh
sudo firewall-cmd --permanent --new-zone=tailmesh

# 2. Assign your virtual network interface directly to this new zone
sudo firewall-cmd --permanent --zone=tailmesh --add-interface=vpn-mesh0

# 3. Permit ONLY explicit services (e.g., SSH) through the mesh
sudo firewall-cmd --permanent --zone=tailmesh --add-service=ssh

# 4. Reload firewalld to activate the changes
sudo firewall-cmd --reload

Use code with caution.

By explicitly isolating the mesh traffic to its own zone, you enforce zero-trust security. Even if a client device on your network is compromised, the attacker cannot scan or access any unapproved ports on your server, keeping your environment perfectly locked down.


Smarter Connectivity: VPN On-Demand & Local WiFi Exceptions

A common issue with traditional VPNs is the “always-on” headache. You want your traffic encrypted when you are sitting in a sketchy coffee shop, but keeping the VPN tunnel active when you are at home or work can break local casting, slow down file transfers, or create routing loops.

Tailscale solves this beautifully by allowing you to configure VPN On-Demand with smart exceptions.

Using Tailscale’s client configurations (which you can dive deep into via the official Tailscale Documentation), you can set up smart triggers based on the Wi-Fi network you are currently connected to:

  • The Untrusted Network Trigger: The moment your phone or laptop connects to an open public Wi-Fi network, Tailscale automatically spins up your connection and routes your traffic through your home “Exit Node” for complete encryption.
  • The Trusted Local Exception: When you walk through your front door and connect to your home Wi-Fi, the client recognizes the SSID. It immediately disables the heavy routing or exit-node tunneling, granting you seamless, full-speed access to local network storage, smart home devices, and local media servers without unnecessary overhead.

The Verdict

By pairing Firewalld on the backend with Tailscale on the frontend, I’ve achieved the holy grail of homelabbing and system administration: total security without sacrificing convenience. The server stays tightly locked down and easy to manage, while my client devices adapt intelligently to whatever network environment I throw them into.

If you’re looking to simplify your remote access without cutting corners on your firewall, this is the blueprint to follow.


I Finally Dropped My Custom 1996 iptables Firewall Script—And Why It Was Time

Thirty years. That is how long my home network routing infrastructure relied on a monolithic shell script I wrote back in 1996. I crafted it back in the golden era of early Linux packet filtering, packed it with raw configuration lines, and tweaked it over decades as a hobby project to survive moving from ipfwadm to ipchains, and eventually to iptables.

It was a personal point of pride. Whenever I set up a new tool in my home lab, I didn’t click buttons; I opened a massive text file, added a manual tracking line, flushed the kernel tables, and reloaded the whole script.

But this week, on my Fedora router, the limitations finally caught up. It was time to pull the plug, learn the modern way of doing things, and migrate my entire home routing setup to firewalld and native nftables.

Here is the story of how I transitioned my home lab, the weird architectural traps I fell into, and how I built a modern, locked-down zone security posture.


The Catalyst: Tracking Down the Ghosts in the Logs

The transition started where all good home lab projects begin: digging through log streams. By default, firewalld drops packets silently. If you want to see what is actually happening behind the scenes, you have to tell it to start logging denied traffic.

I turned on global drop logging using this quick command:

bash

sudo firewall-cmd --set-log-denied=all

Use code with caution.

(Pro-tip: If your logs get too flooded later on after everything is working, you can easily turn this back off by running sudo firewall-cmd --set-log-denied=off).

With logging activated, my journalctl stream instantly exposed the ghosts haunting my network. My internal server was actively throwing network blocks:

text

filter_IN_internal_REJECT: IN=eth1 OUT= MAC=... SRC=10.0.10.55 DST=10.0.10.200 PROTO=TCP DPT=443 SYN

Use code with caution.

Because of these newly introduced blocks from the transition to firewalld, the computers inside my house suddenly couldn’t load local secure web pages, my email client setups were dropping synchronization steps, and my smart devices were choking on network discovery packets. While my legacy 1996 script had seamlessly allowed this internal traffic for years, firewalld‘s strict, out-of-the-box zone behaviors completely locked down the single machine from serving both functions until I configured it correctly.


Step 1: Restoring the Internet Gateway (Masquerading)

The first order of business was transforming my Fedora server into an efficient edge gateway router for the house. In the old days, this meant writing explicit POSTROUTING -o eth0 -j MASQUERADE loops.

With firewalld, we instead split the hardware interfaces into discrete logical security boundaries: external (the WAN modem link on eth0) and internal (the private LAN switch on eth1). To feed the household devices out to the web, we built a dedicated outbound routing policy:

bash

# Set up a clean outbound forwarding pipeline with NAT masquerading
sudo firewall-cmd --permanent --new-policy=internalToExternal
sudo firewall-cmd --permanent --policy=internalToExternal --add-ingress-zone=internal
sudo firewall-cmd --permanent --policy=internalToExternal --add-egress-zone=public
sudo firewall-cmd --permanent --policy=internalToExternal --set-target=ACCEPT
sudo firewall-cmd --permanent --policy=internalToExternal --add-masquerade
sudo firewall-cmd --reload

Use code with caution.

Suddenly, outbound data—including seamless features like Wi-Fi Calling on our phones—slid out to the internet natively without requiring a single manually defined high-number port mapping.


Step 2: Falling Into the “Policy Object” Trap

My first instinct as a hobbyist was to consolidate everything into elegant inter-zone Policy Objects. I attempted to route all inbound public web, mail, and application traffic through a custom externalToInternal forward policy.

The firewall immediately threw a massive architectural roadblock:
Error: INVALID_ZONE: 'forward-port' cannot be used because egress zone 'internal' has assigned interfaces

The lesson learned:firewalld Policy Objects completely forbid port-forwarding rules if the destination zone is attached to a real, physical network card. Policies are designed for virtual spaces (like isolated Docker networks or hypervisor VM bridges).

To achieve a pristine setup, the correct move was to put forwarding rules back onto the zone itself, but keep the raw underlying system ports completely closed off to the public router space.


Step 3: Embracing “Option B” (Strict Local Lockdown)

When configuring internal communication, I faced a fork in the road. Option A was a loose, blanket ACCEPT target for the whole internal network. Option B was a hardened, granular lockdown where only verified application services could move data.

I chose Option B.

Because firewalld drops negative priority policies when traffic is destined for the host itself, trying to manage local server loops through a policy wrapper caused endless rejections. The correct architectural solution was to map my application signatures directly onto our physical internal zone gate.

We stripped out manual numeric ports and used pre-configured service wrappers to expose exactly what we needed, keeping everything else locked tight against unauthorized lateral movement:

bash

# Lock down the internal gate to ONLY trusted local infrastructure services
sudo firewall-cmd --permanent --zone=internal --add-service=http
sudo firewall-cmd --permanent --zone=internal --add-service=https
sudo firewall-cmd --permanent --zone=internal --add-service=ssh
sudo firewall-cmd --permanent --zone=internal --add-service=dns
sudo firewall-cmd --permanent --zone=internal --add-service=smtp
sudo firewall-cmd --permanent --zone=internal --add-service=smtps
sudo firewall-cmd --permanent --zone=internal --add-service=imaps
sudo firewall-cmd --permanent --zone=internal --add-service=plex
sudo firewall-cmd --permanent --zone=internal --add-service=samba
sudo firewall-cmd --permanent --zone=internal --add-port=8123/tcp  # Home Assistant
sudo firewall-cmd --reload

Use code with caution.


The Final Verdict: A Pristine Architecture

After sweeping out legacy clutter (like default desktop samba-client wrappers and unneeded dhcpv6-client daemons on the inner switch interface), the firewall configuration achieved absolute stability.

Running a diagnostic audit prints out an incredibly clean, legible system baseline:

text

external (active)
  interfaces: eth0
  services: http https imaps smtp smtps ssh

internal (active)
  interfaces: eth1
  services: dns http https imaps plex samba smtp smtps ssh
  ports: 8123/tcp

Use code with caution.

Everything is fully persistent and gracefully survives cold reboots. Local devices can map storage drives over Samba, stream Plex, resolve local DNS queries, and view internal pages. Meanwhile, the public web interface remains blind to everything except our secure edge proxies—and public pings remain active to satisfy network path diagnostics and MTU discovery boundaries as recommended by universal best practices.

It took me thirty years to finally retire that old text script. But seeing a silent journalctl stream and a flawless, granular zone layout?

It was an awesome weekend project upgrade.

Installed Radicale3 for hosting my own calendar and contacts data

So I finally got around to installing something so I can host my own calendar and contacts so that data is not shared with the big four corporate companies.

As I just need basic calendar and contacts functionality I chose the open source project Radicale. As I have run Fedora to control the house and my private data since 1998 I firstly installed the Radicale rpm via DNF.

dnf install radicale

This should install radicale3 as that is the latest version at the time of writing this blog post.

I will be running it off a sub domain of my public domain so I have set that up in the DNS with my main domain hosting company.

I have used a sub domain with a reverse proxy rather than opening another port [5232] on the firewall. I initially set it up with the port open but then rethought my approach as its more secure than to open the port on the firewall.

One other good thing using a sub domain is that you just need to have the proxy settings for that host rather than using location settings with the pathing.

Apache virtual host looks like this

<VirtualHost *:443>
 
 ServerName <subdomain.domain>
 DocumentRoot <pathtodomain>

 ProxyRequests Off
 ProxyPreserveHost On

 <Proxy *>
 Order deny,allow
 Allow from all
 </Proxy>

 # Forward requests to the backend Radicale server
 ProxyPass / http://localhost:5232/
 ProxyPassReverse / http://localhost:5232/

 # Inform Radicale about its location behind the proxy
 RequestHeader set X-Script-Name /radicale

 SSLCertificateFile <path to public key>
 SSLCertificateKeyFile <path to private key>

</VirtualHost>

Set up the config file for radicale to use it locally running on localhost on port 5232 and I am using my dovecot server to authenticate the users.

Then you should just be able to surf to the subdomain and be presented with the login page of the radicale server. Login and add your collections for the calendars and contacts.

Then configuring all the clients should be pretty straight forward for PC, Mac, iOS and Android.

MMEncode is not available on Linux distros any more.

Not sure what happened to this or why but its no longer available. I used to have a script that used it to convert binary data to text for attachments on emails. Having searched high and low I came up with a new solution – use openssl.

You can do the same that you were doing with mmencode by doing the following

openssl base64 -e < $FILE

Enjoy.

MySQL pegging CPU after the leap second adjustment.

So I find that my MySQL database is running high on CPU all of a sudden. I optimize the tables etc and no difference. I hunt around the internet to find that there seems to be a problem with this years leap second adjustment which sends MySQL into orbit.

The solution reset the date

date -s "`date`"

and it dropped back to normal.

Credit goes to this lady here http://www.sheeri.com/content/mysql-and-leap-second-high-cpu-and-fix

BackupPC Has qw(…) Warnings Since Upgrading Perl

So since upgrading Perl I am presented with qw warnings coming out of the cron job checking that BackupPC is running.

Use of qw(...) as parentheses is deprecated at /usr/share/BackupPC/lib/BackupPC/Storage/Text.pm line 301.
Use of qw(...) as parentheses is deprecated at /usr/share/BackupPC/lib/BackupPC/Lib.pm line 1412.

The way to get rid of these warnings is to enclose qw in parentheses and Perl processes the foreach parameters without warnings.

Like so

Text.pm (Line 301)

#
# Promote BackupFilesOnly and BackupFilesExclude to hashes
#
foreach my $param (qw(BackupFilesOnly BackupFilesExclude)) {
next if ( !defined($conf->{$param}) || ref($conf->{$param}) eq "HASH" );
$conf->{$param} = [ $conf->{$param} ]
if ( ref($conf->{$param}) ne "ARRAY" );
$conf->{$param} = { "*" => $conf->{$param} };
}

Lib.pm (Line 1412)

foreach my $param (qw(BackupFilesOnly BackupFilesExclude)) {
next if ( !defined($conf->{$param}) );
if ( ref($conf->{$param}) eq "HASH" ) {

Barclays Stockbrokers Have An Invalid Security Certificate

if you go to the barclaysstockbrokers.com url and try to login you get presented with an invalid security certificate error by firefox and ie 8.  The certificate is for www.stockbrokers.barclays.com not www.barclaysstockbrokers.com.

I have rang them and emailed them but no one seems that interested in getting fixed they dont see it as a problem.  Well in these days of phishing and spoofing I would say its a problem.  We will see how long they take to fix it, if at all.

UPDATE: 9 July 2009

Well eventually someone got back to me about it.  Looks like they have a problem with the login on the page as its hardcoded to stockbrokers.barclays.co.uk instead of the using the url that you actually come from.  I have been told by there technical bods that if you use the login at the top of the page it uses the correct url.  And it does work.  Come on Barclays sort it out so that all the logins use the correct URL.  I cant believe it took so long for you to actually come back to me with a sensible answer but to still see the site broken in this way is really unbelievable.  I expect quite a few people are having this problem as your eyes go to the login button within the site not the black bar at the top.

Looking at the stats on my page quite a few people are getting this error as the number of people coming to my site with the search words ‘barclays stockbrokers’ and ‘invalid certificate’ are quite high.

Accessing Vista with smbclient

Just bought a new machine and decided to put VISTA on it.  Now biggest problem was accessing the shares via smbclient or Samba.  Hunted high and low on the net couldnt find anything not specific for VISTA and smbclient.

I then looked for windows xp and smbclient and found a post about changing a registry entry.  Up to this point I had tried everything everyone else was saying like enable file sharing, private sharing etc and couldnt get any of that too work at all.

Probably works via another vista machine but not from linux or my apple.

I upgraded samba to the latest version before doing this.  I then added a registry entry in \HKEY_LOCAL_MACHINE

\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System

called LocalAccountTokenFilterPolicy which a hexadecimal value of 1 and bobs your uncle you should be able to access shares and admin shares such as C$ on the vista machine from smbclient or mounting it in the fstab.

Hope that helped you, it helped me no end as I was getting very frustrated about it.  And samba is key to my machines here.