A client that will remain nameless has a brand new Prestashop (don’t mind the pixels in the logo) that he’s really happy with. He’s been able to set it up mostly by himself, except for the mail server, which was being rather fussy.

The software supports using either the built-in PHP mail() function or to use a proper SMTP server somewhere. For reasons related to not having everything automatically marked as spam and having a Google Apps mailserver ready to go, we wanted to go for the latter.

Note: the solution is discussed in the final section.

Our options

Like any reasonable mail system, the Prestashop mailserver configuration allows a few options. You can freely specify the host, port, and encryption option (no encryption, SSL on the socket, or STARTTLS after connecting) for your server.

For the port, you can use any number, but most mail servers expect you to use 465 (SMTPS) to using SSL on the socket, or one of 25 (SMTP) and 587 (submission) for using no encryption or STARTTLS. In general, you want to be using STARTTLS as SMTPS is mostly deprecated. The choice between SMTP and submission is mostly down to whether your ISP is blocking outgoing connections on destination port 25.

Of course, you also probably need to put down a username/password combination, but we will leave that as an exercise to the reader.

The symptoms

After configuring your mail server, you can conveniently send a test email to yourself on the same page. Which is where we were greeted with the following:

Error: check your email configuration
Unable to connect with TLS encryption

By changing the port to 465, we were able to change the error message to a timeout, and by forcing SSL rather than STARTTLS (which it should be) we were able to stop it from working at all. Which I would call progress, even if it’s minimal progress.

Finding the logs

Surprisingly, the server logs were clean. Instead, Prestashop captures its down error_log file and puts it in either the document root (for public facing errors) or in the admin directory. There, we could see two things that are going wrong:

PHP Warning: “continue” targeting switch is equivalent to “break”. Did you mean to use “continue 2”? in /document/root/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 2636

and

PHP Warning: stream_socket_enable_crypto(): Peer certificate CN=`server.example.com’ did not match expected CN=`smtp.example.com’ in /document/root/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php on line 103

The former is a compatibility issue with Doctrine ORM and PHP 7.3 which apparently has a fix already, but it’s not included in Prestashop for compatibility reasons. The latter is obviously our smoking gun. But why is the Prestashop connecting to the local mail server rather than the one we configured? Time to investigate.

More configuration sources

Prestashop has a plethora of configuration files scattered everywhere. grep‘ing for words related to sending email brought me to app/config/parameters.php. This contains, among other things, the following:

1
2
3
4
5
6
7
8
9
10
11
<?php return array (
  'parameters' =>
  array (
    // SNIP
    'mailer_transport' => 'smtp',
    'mailer_host' => 'localhost',
    'mailer_user' => NULL,
    'mailer_password' => NULL,
    // SNIP
  ),
);

Well, that explains why the system is connecting to localhost right? Obviously, the certificate won’t match then. We can just put the configuration down here and be done with it.

Alas, no dice. the problem persists. The variables are used somewhere as far as I can tell since removing them breaks the application. So what’s really happening here?

Desperate times…

… call for manually trying to see what’s wrong. First step: try manually connecting to an SMTP server. This SMTP is actually a fairly understandable protocol to speak for a human, as long as you can open a socket and talk to the proper server. I did not have nice tools to do this, but I did have PHP. So here’s how you test an SMTP connection in the interactive PHP shell.

1
2
3
4
5
6
7
# php -a
Interactive shell

php > $connection = fsockopen('smtp.example.com', 587);
php > fwrite($connection, "EHLO server.example.org\n");
php > echo fgets($connection);
220 smtp.example.com ESMTP

So that works as expected. There’s more data you can extract from the EHLO, but this proves the connection can be made, even from PHP, so it’s not a PHP configuration issue. What will happen when we run this code as a different user? We can finally replicate the issue!

1
2
3
4
5
6
7
# sudo -u nobody php -a
Interactive shell

php > $connection = fsockopen('smtp.example.com', 587);
php > fwrite($connection, "EHLO server.example.org\n");
php > echo fgets($connection);
220 server.example.com ESMTP

So, if we’re not root, then our outgoing connection gets rerouted to our local mail server. Now we just need to know why.

The actual culprit

The server runs WHM so the client can do most of his systems administration by himself while never even hearing the words “secure shell”. This tool also has a feature called “SMTP Restrictions”:

This feature prevents users from bypassing the mail server to send mail, a common practice used by spammers. It will allow only the MTA, mailman, and root to connect to remote SMTP servers.

Surprisingly, the web user runs as none of those things. However, we can simply disable this “security feature” and everything works as expected. Neither my client nor I have any recollection of enabling it anyway.

Debugging an issue that should have been very simple has shown me around way too many components of the web server. We can now add yet another thing to check to the debugging checklist: user-specific firewall rules. What a time to be alive.

Some specifics like domain names, file paths, and other things that I need to fill this tricolon have been anonymised to protect the client. The things I left in are unfortunately as they are. In this post I’m slightly too negative about the “SMTP Restrictions” feature. For small, simple websites, this can be a nice stop-gap for spammers achieving code execution. For any website that actually needs to send email, the story is very different.