Hosted Email Provider compatibility with 3rd party Email Security Providers

I currently host my own Email server at home and have done for over 20 years. I’m thinking about replacing the home based server with a hosted solution out there – but I don’t want to lose some of the functionality I have today.

Generally, the requirements are pretty standard – so a short list:

  • Send SMTP Outbound through port 25 or 587
  • SMTP Auth
  • TLS support
  • IMAPS mailbox
  • Up to 6 users
  • Ability to integrate with 3rd party Email Security Provider

So, it turns out that locating all this information is really hard. I’m going to publish with lots of gaps or unknown answers. I’m happy to take contributions if you know about providers and their capabilities.

ServiceInfoSMTP SendSMTP AuthSTARTTLSOutbound IPDKIM Signing3rd Party Outbound RelaySPF ValidationDKIM ValidationDMARC Policy Enforcement3rd Party In Relay*IMAPS / POP3 mailboxMail StorageAliases
Hetzner Webmail
Port 465 TLS/SSL
Zoho Mail (free up to 5 users)

* 3rd Party In Relay – In order to support this – the service must trust a ‘Man in the middle’ Security service to do SPF/DKIM/DMARC policy enforcement, and crucially not perform SPF/DKIM/DMARC checking on incoming email.

Raspberry Pi Central Heating Controller, part 2 – Software

If you haven’t seen part 1, the hardware build, you can find that at

Software installation (Incomplete: I really need to figure out how to use wordpress properly)

  • Raspian Lite
  • python3-dev

When the Pi boots up, the GPIO pins will not be configured – but the relay board is getting 5V and 3V3 feeds.
This causes a little bleed on current – so we can fix this by pulling the GPIO pins we are using High (3.3V) – you’ll see this on the little leds on the relay board – they will be very slightly lit.

cat - > /usr/local/bin/
#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM) # GPIO Numbers instead of board numbers



chmod a+x /usr/local/bin/

HomeAssistant Install:

sudo apt-get install python3 python3-venv python3-pip python3-dev
sudo useradd -rm homeassistant -G dialout,gpio
cd /srv
sudo mkdir homeassistant
sudo chown homeassistant:homeassistant homeassistant

sudo -u homeassistant -H -s
cd /srv/homeassistant
python3 -m venv .
source bin/activate

python3 -m pip install wheel
pip3 install homeassistant

And run HomeAssistant:

Or Install hass as a service:
cat - > /etc/systemd/system/home-assistant@homeassistant.service
Description=Home Assistant
ExecStart=/srv/homeassistant/bin/hass -c "/home/homeassistant/.homeassistant"

If you prefer systemV style init scripts:


Make it run at startup:
sudo systemctl --system daemon-reload
sudo systemctl enable home-assistant@homeassistant

Start the hass service:
sudo systemctl start home-assistant@homeassistant

View the homeassistant logs:
sudo journalctl -f -u home-assistant@homeassistant

Visit the hass web interface:

Updating HomeAssistant
sudo -u homeassistant -H -s
source /srv/homeassistant/bin/activate
pip3 install --upgrade homeassistant

I used a Google Calendar as a scheduler.

TODO: More config on homeassistant.

Buy cheap, buy twice.

Just sharing some photos of some garbage ethernet splitters I picked up cheap.

What? every pin is wired to every other pin!

That’s not going to work.

Ok, I can fix this….

Just need to split it so each half has their own 1,2,3 and 6 pins…

And this is where I realised I fucked up. 1,2,3 and 6 are from the plug side, not the socket side. I’d done it backwards. 🙁

Into the bin.

How to make the Phisher’s job easier, or clean up your DNS records folks.

Business is increasingly outsourcing non-core functionality through buying services in ‘the cloud’ rather than hosting on-prem. This has many advantages for the company, not least, because someone else is tasked with keeping the system operating 24/7. It doesn’t always work perfectly, but on the whole Software as a Service is great for both the clients and the service providers.

Now, one of the steps in connecting your business domain to these SaaS is Domain Verification – proving that you actually own the domain that you want them to provide service for. Because this process is often automated, one of the favoured options of providing this proof is for the SaaS provider to provide you with a special token or string that you need to put into the DNS record for your domain as a TXT record.

Simple enough, right? Well yes. Let’s look at an example.

Want to have Microsoft o365 handle your company email? Awesome. Go sign up and tell Microsoft to handle mail for your domain, e.g. Microsoft isn’t sure that I actually own – so they generate a random code and tell me to add a TXT record under with the (randomised) value “MS=ms12345678”. When I confirm that I’ve made that change to the DNS zone, Microsoft will make a TXT DNS query looking for the string they told me to add. If it is there, then I’ve proved that I own the domain and Microsoft will happily provide service for the domain.

There are other validation processes (such as adding a meta header to your domain’s web page), but this is less convenient and so TXT records are often preferred.

Once you have proved that you own the domain, there is no further need for the record to remain in DNS.

Sounds great, what’s the downside?

Let’s look at the domain, a site that enables hackers and businesses to connect (for good).

$ host -t txt descriptive text "v=spf1 -all" descriptive text "google-site-verification=glWWhC-27LpigyjAxBsVOVUScJgNQ23GWdC4uOWC3dc" descriptive text "cloudpiercer-verification=32e7eea9d2f153b176b182626588bc77" descriptive text "MS=ms75772789" descriptive text "citrix-verification-code=9c920630-2d05-4154-b72a-1021665d3b58" descriptive text "google-site-verification=mKdqQzjtY7X20BzUFnhAmFU2pmtFJ_Zie_S22FiwubA" descriptive text "facebook-domain-verification=niq4ke9m7djq4jt36f02t093aig8a5" descriptive text "atlassian-domain-verification=JpJ4g3munTo9KsuR3Elcdpn97c+KQV7KDjj2YmE+ULiWhGlcfA5f1ivoC0W2puQk" descriptive text "ZOOM_verify_pzZpSwKqRx6pAD9lLkSl5g" descriptive text "adobe-idp-site-verification=5d77b0274800290cf193145126595b14308358f46dfe90eba5e298f99d32d2fc" descriptive text "zapier-domain-verification-challenge=d1be5c1b-f415-418f-9542-abc14d8321af" descriptive text "4b7570f2564f4074b42872e1d78668ad" descriptive text "drift-domain-verification=18fef5450d713c159ad9f6309fa338d298c11edd56fb22e343d758fb5f58437f" descriptive text "docusign=848c7864-3a91-42aa-8e30-2671086f7516" descriptive text "c06l6z7hp4vk6bzpqb1j6b8w1m64nf84" descriptive text "h1-domain-verification=LDEQA8SYNEMgdUN1kfMtjFNptDJcnjKN8LxNHCN3JNvT5Fxo" descriptive text "stripe-verification=20c821f6e4dfc5ee358ea9b8e4635cf062a2acac5751f8004d23b122f1cb5ac2" descriptive text "stripe-verification=74802599834dfbfc093c8352686c992d22e7b20a6fdcfceac2e6a846074d6936"

Wow, there’s a lot to take in there.

OK, what can we learn from this?

Pretty standard SPF record, and allows a bunch of other service providers to send mail from on their behalf.

But, hol’up for the rest.

Google, Cloudpiercer Discovery Tool (against themselves), Microsoft o365, Citrix, Facebook, Atlassian, Zoom, Adobe Enterprise IDP, Zapier, Drift, Docusign, and Stripe.

That’s a rich list of Phishing vectors to come from – businesses which the victims will expect to have communications from, significantly increasing the chances of phishing success; and you’re just handing your SaaS provider list to the attackers by not maintaining some hygiene on your DNS records.

An additional risk is Supply Chain attacks. By broadcasting many of the third parties you have working relationships with, you’re providing a supply chain vector to those attackers specifically trying to attack you.

Please delete Domain Verification TXT records when you’re done verifying.

DIY 2U Rackmount Server Vertically to Wall.

Ever bought a rackmount server only to realise that fitting it into your small-ish homelab is going to be a logistical challenge?

My “server room” also doubles as the Utility and simply doesn’t have the space to put in a large floor or wall mounted traditional rack fit for a full length 2U server.

Considered ‘hanging’ it vertically – and indeed there are wall mounted rackmount kits you can get for this… they appear to be stupidly expensive for the bit of bent steel, and to be honest, this Dell R720xd is intended for rails, not to hang off lug ears (which don’t appear to be capable of supporting the weight – they’re functional in that they provide VGA, USB and the power switch).

So I wondered if I could fix it to the wall sideways?

Enter the ‘shelf rack’. To make these, you need 4 (or 6) 5″x4″ L shaped heavy duty brackets with two mounting holes on each leg. If you want something to order, try these:

Heavy Duty Galvanised Shelf Bracket 6 Pack – 5×4″ / 125x100mm

2U is 3.5 inches which makes the 4″ bracket perfect for my needs.

Using 2 x M6 bolts, affix two of the brackets together along the 4″ side.

Z-Mount Rack

Using two of these, we can affix them to the wall and we’ve created a decent shelf upon which to set the server. I call it the Z-Mount. [ If normal Horizonal is X and Vertical (hanging down) is Y (like the racks you can buy to hang a server from the rack ears), then sideways-flat-against-the-wall is Z. ]

One thing I didn’t count on was the hole offsets – which made the distance beween the wall and the outer bracket closer to 4.5″. I was concerned that this was too big a gap for the server to remain secure. The solution here was to use a couple of Staple on Plate mounts and two tie-down buckle straps. Both items are very cheap.

Silverline 449682 Tie-Down Cam Buckle Straps 2.5m x 25mm
Fixman 943775 Black Chain Staple & Plate 50mm x 50mm

This has the benefit of being a failsafe against bracket failure (highly unlikely) and provides a small amount of vibration insulation.

The final option is to bolt a 3rd shelf bracket to the outer arm of our mounting bracket and screw in a shelf. This provides a useful monitor and keyboard shelf.

Total costs:

Heavy Duty Galvanised Shelf Brackets£10.59
Silverline tie down buckle straps£3.41
Black Staple on Plate x 2£2.68
Total cost:£16.68

PHP strnpos function

Note: I’m moving a bunch of old web pages into my blog. This code is from 2003, it may have some use to someone still.

PHP provides a few similar functions, but not this specific one.

If you want to find the first occurrence of a substring in a string you have strpos()

If you want to find the last occurrence of a substring in a string you have strrpos()

But what if you want to find the nth occurrence? Enter strnpos():


 * Find the nth occurance of a string in another string
 * Paul Gregg <>
 * 23 September 2003
 * Open Source Code:   If you use this code on your site for public
 * access (i.e. on the Internet) then you must attribute the author and
 * source web site:

// Optimal solution
Function strnpos($haystack, $needle, $nth=1, $offset=0) {
  if ($nth < 1) $nth = 1;
  $loop1 = TRUE;
  while ($nth > 0) {
    $offset = strpos($haystack, $needle, $loop1 ? $offset : $offset+1);
    if ($offset === FALSE) break;
    $loop1 = FALSE;
  return $offset;

// Interesting solution without using strpos (without offset capability)
Function strnpos2($haystack, $needle, $nth=1) {
  if ($nth < 1) $nth = 1;
  $arr = explode($needle, $haystack);
  if ($nth > (count($arr)-1)) return FALSE;
  $str = implode($needle, array_slice($arr, 0, $nth));
  return strlen($str);

Source code can be found here.

Legacy PHP: str_split function

This stems from 2003 when str_split() did not exist in PHP and was just being added. It showed how to implement a compatible function if your host didn’t have a newer version of PHP.

 * split a string up into equal sized chunks
 * Paul Gregg <>
 * 23 September 2003
 * Open Source Code:   If you use this code on your site for public
 * access (i.e. on the Internet) then you must attribute the author and
 * source web site:
 * str_split is available from PHP version 5 by default

if (!function_exists('str_split')) {
  Function str_split($string, $chunksize=1) {
    preg_match_all('/('.str_repeat('.', $chunksize).')/Us', $string, $matches);
    return $matches[1];

Source code can be found here.

Note: This page is legacy – you won’t ever use this now. PHP has had str_split() for 15 years.

Building a Raspberry Pi based Central Heating Controller, part 1 – Hardware build

This is the story of how I came to build my own Central Heating controller.

I bought the house I’m presently living in over 15 years ago. It was basically a L shaped bungalow with 3 heating areas: Living, Bedrooms, and Hot Water.

It came with a Horstmann H37XL. And all was well for about 10 years.

Horstmann H37XL 3 Channel Programmer

One day, said Horstmann stopped working, so doing what a good hacker would do, I took it apart.   There on the inside was a button cell 2032 battery. Let’s replace that – simple? Not so much.  Horstmann in their infinite wisdom had soldered the battery in – it was a non-replaceable part.

Not to be outdone, I brute forced that battery out. Jerry rigged a new CR2032 in place – and the box came alive. Great! So I soldered that in and put it all back together again. The unit soldiered on for another couple of years.

Then came the roof conversion.  Upstairs in the bungalow was an additional 700 sq ft of open space, so late 2016 we converted that to a couple of extra bedrooms, bathrooms and a playstation/xbox area.

But – Upstairs needed to be a new heating zone – I needed a 4 zone heating controller.

Lots of research later, I settled on a ‘smart’ miGenie Wish 3 by Drayton.

Drayton are (were) a well respected brand of heating controllers in the UK – so I thought it would be a safe choice.   But, I wouldn’t be writing this article if that was true!

The pack came with a 4 zone controller, 2 remote thermostats and a ‘internet’ connector box.  The iphone wasn’t included.

The electrician working on the upstairs professionally installed it in October 2016 towards the end of the roof conversion. And then we had 3 areas + hot water, plus I could monitor and switch zones on and off from my phone. Awesome.

For 2 years. Then it crashed and would constantly reboot anytime a zone turned on.

Contacted Drayton – and long story short – it was out of warranty and I was out of luck.

They did offer me a discount on a replacement – thanks, but no thanks. Your products should last more than 2 years.

So now I had no heating and needed something urgently – cue the DIY manual switch box.

So this dumb switch box allowed me to turn on each zone individually – and this is how I learned how the wiring worked. It’s basically a whole bunch of mains 240V live wires.

From here it was a small step to realise I could use a Raspberry PI and a 4-way relay to control the live zone switching – and I could fit them both in a double-gang pattress box.

I ordered a Pi Zero W (with the header pins pre-soldered because I would probably make a mess of doing that myself) and a 4 channel relay module. I already had a few pattress boxes lying around so grabbed one of them and began to tinker.

As you can see – plenty of space in the box – tho I haven’t affixed anything yet – we’ll need to make holes for power and the mains cabling later.

Put together a small python script to walk the pinouts switching the relay – and connected a multimeter in short detector mode to beep when relay one was enabled.

I’ve other videos, but this is the most interesting one (at this stage).

Now I have to put it all together. Note the PI to the left is running on low voltage DC while the relay to the right has 240V Mains – so I figured best to put an insulator in there keeping the wiring apart.

The next video shows using Fauxmo – a Python module to simulate Belkin WeMo devices. Here you can see both the Pi and Relay screwed into their final location, with an insulator to keep the DC and AC cables away from each other.

(so I wasn’t very accurate in screwing the relay in perfectly aligned)

Now comes the hard part – putting this on the wall and wiring it all up – and I have a huge ‘please don’t do what I did’ – I need 5 core mains wire – but I couldn’t get 5 core – all ‘live’ brown (thinking about that it might be hard to match the ends properly)…. so I’ve used 5 core (with neutral, earth and others) and used all the colours as live…. So not up to code…

You can see the old mounting plate for the Drayton – so that has to go, and we’ll use another double pattress box and tidy up the wiring inside that.

That’s tidy, right?

Add a Amazon Fire HD 7″ Tablet as a controller and, just in case it all crashes an burns, a manual rotary timer for the hot water and I present my new Central Heating control system.

So what you’re looking at here is a redundant central heating controller platform with rotary timer for hot water and manual switches to turn on and off all 4 zones.

Above that we have the Raspberry Pi Zero with 4 channel relay switch.

Above that we have am Amazon Fire HD tablet (because they are super cheap) to be used as an interface to the Pi controller software.

Hardware Requirements:

  • Raspberry Pi Zero W with Header pins
  • 4 Channel Relay Module (5VDC / 230VAC)
  • Amazon Fire HD tablet, any size.
  • Optional: Xiaomi Mi BLE Thermostat (qty 1 to 3) – though range on these are not great especially trying to hit the tiny Pi Zero ‘air’ antenna – so depending on the wall construction of your house – they may not work for you. Mine go through 2 solid walls and that’s it.

Part 2 of this, the software installation, can be found at

Compiling ZoneMinder with libjpeg-turbo and JPEG_LIB_VERSION error

If you’ve ever tried to build ZoneMinder from source and been frustrated by the following compile error, then I hope this helps you.

[ 30%] Building CXX object src/CMakeFiles/zm.dir/zm_image.cpp.o
 /u1/src/ZoneMinder/src/zm_image.cpp: In member function ‘bool Image::ReadRaw(const char*)’:
 /u1/src/ZoneMinder/src/zm_image.cpp:616:27: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
 /u1/src/ZoneMinder/src/zm_image.cpp: In member function ‘bool Image::ReadJpeg(const char*, unsigned int, unsigned int)’:
 /u1/src/ZoneMinder/src/zm_image.cpp:664:5: error: ‘JPEG_LIB_VERSION’ was not declared in this scope
 /u1/src/ZoneMinder/src/zm_image.cpp: In member function ‘bool Image::WriteJpeg(const char*, int, timeval) const’:
 /u1/src/ZoneMinder/src/zm_image.cpp:825:5: error: ‘JPEG_LIB_VERSION’ was not declared in this scope
 /u1/src/ZoneMinder/src/zm_image.cpp: In member function ‘bool Image::DecodeJpeg(const JOCTET*, int, unsigned int, unsigned int)’:
 /u1/src/ZoneMinder/src/zm_image.cpp:956:5: error: ‘JPEG_LIB_VERSION’ was not declared in this scope
 /u1/src/ZoneMinder/src/zm_image.cpp: In member function ‘bool Image::EncodeJpeg(JOCTET*, int*, int) const’:
 /u1/src/ZoneMinder/src/zm_image.cpp:1090:5: error: ‘JPEG_LIB_VERSION’ was not declared in this scope
 make[2]: *** [src/CMakeFiles/zm.dir/zm_image.cpp.o] Error 1
 make[1]: *** [src/CMakeFiles/zm.dir/all] Error 2
 make: *** [all] Error 2

jpeglib-turbo/include/jconfig.h (from your installed jpeglib-turbo-dev) has:


And to avoid including this more than once the wrapper libjpeg-turbo/include/jpeglib.h has:

#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */
#include "jconfig.h" /* widely used configuration options */

So far so good… The problem comes when zoneminder wants to compile zm_image.cpp

the include path is:

 -> zm_image.h
   -> zm_jpeg.h
     -> jinclude.h
       -> jconfig.h   (from jpeglib-turbo)
     -> jpeglib.h     (from jpeglib-turbo)

Now, this *should* all work properly! I suspect it has something to do with cmake and scope and by the time we get back to jinclude.h it no longer has the define.

My fix was to take the version from my jpeglib-turbo/include/jconfig.h and simply add it to zoneminder’s jinclude.h:

#include "jconfig.h" /* auto configuration options */
#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */


Add the 3 blue lines after the jconfig.h include. Once done, compile should complete properly.

Hope this helps other get past this hurdle.



My first bad ebay experience from happyapple-devices

Opinions in this article are just that, my opinions.

Decided on October 9 to purchase an iPhone 5S from ebay business seller happyapple-devices.  Used Best Offer and paypal to complete the transaction.

The phone is described as:

Apple iPhone 5s ✔️Unlocked ✔️32GB Space Grey ✔️LIKE NEW CONDITION ✔️WARRARNTY

With description:

Seller refurbished :
Like new condition, and bezel shows no light typical wear and tear, and bezel surround is great condition. 12 months warranty plus 5 free accessories – USB Car Charger, Data Sync Cable, New Box with instructions, Sim Tray Removal Tool, Screen Protector and Cleaning Cloth, plus recorded delivery.

Link to the actual item sold via ebay.

Unfortunately when the phone arrived it was obvious that it was not ‘like new’.  It was in a very good almost excellent condition – certainly Grade A – but not ‘like new’.  There were some minor scuffs on the bezel, and a couple of dents in two corners – not major. However the screen and back is perfect.

The first thing I tried doing was charging it with the supplied ‘new’ charging cable – 2-3 hours later, phone still wouldn’t turn on. Crap! I thought I’d bought a duff phone. Then when I tried to unplug the charging cable – the end came apart in my hand exposing the electronic chip and circuity within these lightning cables.  I thought I’d try the daughter’s iPad charging cable and it did successfully charge the phone and it turned on OK. Yay!

Then I tried to raise the issue with the seller happyapple-devices.
Oct 15, I open a ticket with ebay on the item to highlight the inaccurate description and broken power cable. I included photos of the corner dents in the bezel as proof.

20151015_200915 20151015_200951

These guys tell a great story on customer service:
“We care about our customers and your experience.”
“Amazing customer services via phone, email or even text message.”

My experience is that these claims are entirely superficial and these guys could not care less about your experience.

I received a phone call from happyapple-devices the next day – keen to resolve the situation. Eventually we agreed that it would be appropriate (and cheaper for them and me) to re-grade the item as Grade A (instead of like new) and they would refund me £20. He would also send me a new “apple original” charging cable.

Then came the killer line from happyapple-devices: “The system won’t let me perform the refund while the ticket is open.”  He asked me to close the ticket so he could perform the £20 refund.  Not knowing any better – I’ve never had to do this before – I thought I needed to close the ticket to proceed.

I closed the ticket.   ProTip: Don’t close the tickets people until you’ve actually received satisfactory resolution to the issue.

It’s now 3 weeks and 2 days later and I still have no paypal refund. I also have not received a new cable.

Basically, my interpretation from happyapple-devices is a big fuck-you.

All I can do in response is warn others about their behaviour and file a complaint with ebay. When I post this I’m going to leave them negative feedback (also the first time I’ve done this to any seller) and if I can, link to this article. ebay UK already has my complaint (acknowledged by ebay twitter team) – but it may take a while to get a response.


All content © Paul Gregg, 1994 - 2024
This site has been online since 5th October 2000
Previous websites live at various URLs since 1994