The symptom

WHMCS v7 is installed on server running CentOS 6.10/cPanel v70/PHP 7.1 and configured to send mail via SMTP using PHPMailer through Yandex mail server. Trying to send mail results in error:

Email Sending Failed - SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

If smtp debug is enabled by adding $smtp_debug = TRUE; in configure.php, error message will be slightly, but not much, more detailed:

SMTP ERROR: Failed to connect to server: (0)
SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

Email Sending Failed - SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

Email Queue Processing Completed

This issue also affects other PHPMailer-using applications, including WordPress confgured with SMTP plugin to send mail.

The cause

To debug this, let's first try a PHPMailer test script (modified from [1]) from the WHMCS root folder:

<?php
require './vendor/autoload.php';

$mail = new PHPMailer(TRUE);
/* enable more verbose debug messages */
$mail->SMTPDebug = 5;

try {
    $mail->setFrom('[email protected]', 'sender');
    $mail->addAddress('[email protected]', 'receiver');
    $mail->Subject = 'PHPMailer test';
    $mail->Body = 'If you receive this, it works.';

    $mail->isSMTP();
    $mail->Host = 'smtp.yandex.com';
    $mail->Port = 465;

    $mail->SMTPAuth = TRUE;
    $mail->SMTPSecure = 'ssl';
    /* This is Yandex Mail for Domain (now Yandex.Connect) login email */
    $mail->Username = '[email protected]';
    /* Use  app password for additional security (see https://yandex.com/support/passport/authorization/app-passwords.html)*/
    $mail->Password = 'passkey';

    $mail->send();
} catch (Exception $e) {
   echo $e->errorMessage();
} catch (\Exception $e) {
   echo $e->getMessage();
}

Save this as mailertest.php and run with PHP (it may be necessary to find out the PHP version and executable location set to serve WHMCS first, either through cPanel->PHP Selector or WHMCS->Utilities->System->PHP Info) in cli mode:

[email protected] [~/public_html/whmcs]:#/opt/php71/bin/php -f mailertest.php
2019-03-20 06:24:20     Connection: opening to ssl://smtp.yandex.com:465, timeout=300, options=array ()
2019-03-20 06:24:21     Connection failed. Error #2: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed [/home/reseller/public_html/whmcs/vendor/phpmailer/phpmailer/class.smtp.php line 298]
2019-03-20 06:24:21     Connection failed. Error #2: stream_socket_client(): Failed to enable crypto [/home/reseller/public_html/whmcs/vendor/phpmailer/phpmailer/class.smtp.php line 298]
2019-03-20 06:24:21     Connection failed. Error #2: stream_socket_client(): unable to connect to ssl://smtp.yandex.com:465 (Unknown error) [/home/reseller/public_html/whmcs/vendor/phpmailer/phpmailer/class.smtp.php line 298]
2019-03-20 06:24:21     SMTP ERROR: Failed to connect to server:  (0)
2019-03-20 06:24:21     SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting
<strong>SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting</strong><br />

The additional error messages point to ssl connection failure due to a certificate verification problem, which might occur either at OS level, or PHP or application level. Let's first test at the OS level using openssl directly:

[email protected] [~]:# echo QUIT | openssl s_client -crlf -connect smtp.yandex.com:465
CONNECTED(00000003)
depth=2 C = PL, O = Unizeto Technologies S.A., OU = Certum Certification Authority, CN = Certum Trusted Network CA
verify error:num=20:unable to get local issuer certificate
verify return:0
--- 
Certificate chain
 0 s:/C=RU/O=Yandex LLC/OU=ITO/L=Moscow/ST=Russian Federation/CN=smtp.yandex.ru
   i:/C=RU/O=Yandex LLC/OU=Yandex Certification Authority/CN=Yandex CA
 1 s:/C=RU/O=Yandex LLC/OU=Yandex Certification Authority/CN=Yandex CA
   i:/C=PL/O=Unizeto Technologies S.A./OU=Certum Certification Authority/CN=Certum Trusted Network CA
 2 s:/C=PL/O=Unizeto Technologies S.A./OU=Certum Certification Authority/CN=Certum Trusted Network CA
   i:/C=PL/O=Unizeto Sp. z o.o./CN=Certum CA
Server certificate
[omitted]
    Verify return code: 20 (unable to get local issuer certificate)
--- 
DONE

The error message confirms the certificate problem: at the first step along the cert chain (depth 2 in this case), issuer cert with common name (CN) 'Certum CA' required for verifying intermediary cert with CN 'Certum Trusted Network CA' is not found in local cert store. The same test on a Debian 9.7 server will instead give this result:

CONNECTED(00000003)
depth=2 C = PL, O = Unizeto Technologies S.A., OU = Certum Certification Authority, CN = Certum Trusted Network CA
verify return:1
depth=1 C = RU, O = Yandex LLC, OU = Yandex Certification Authority, CN = Yandex CA
verify return:1
depth=0 C = RU, O = Yandex LLC, OU = ITO, L = Moscow, ST = Russian Federation, CN = smtp.yandex.ru
verify return:1
---
Certificate chain
 0 s:/C=RU/O=Yandex LLC/OU=ITO/L=Moscow/ST=Russian Federation/CN=smtp.yandex.ru
   i:/C=RU/O=Yandex LLC/OU=Yandex Certification Authority/CN=Yandex CA
 1 s:/C=RU/O=Yandex LLC/OU=Yandex Certification Authority/CN=Yandex CA
   i:/C=PL/O=Unizeto Technologies S.A./OU=Certum Certification Authority/CN=Certum Trusted Network CA
 2 s:/C=PL/O=Unizeto Technologies S.A./OU=Certum Certification Authority/CN=Certum Trusted Network CA
   i:/C=PL/O=Unizeto Sp. z o.o./CN=Certum CA
---
Server certificate
[omitted]
    Verify return code: 0 (ok)
    Extended master secret: no
---
DONE

Let's find out why CentOS is lacking 'Certum CA' cert. First, locate the system default cert store:

[email protected] [~]:# openssl version -d

OPENSSLDIR: "/etc/pki/tls"

This means openssl (and other programs using it) will use /etc/pki/tls/cert.pem as store of trusted CA certificates. Let's take a look at this file:

[email protected] [~]:# grep Certum /etc/pki/tls/cert.pem
        Issuer: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Trusted Network CA 2
        Subject: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Trusted Network CA 2
        Issuer: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Trusted Network CA
        Subject: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Trusted Network CA

There is no "CN=Certum CA" matches, and that is the reason why ssl connection to smtp.yandex.com:465 fails.

For reference purpose, in Debian/Ubuntu, this cert is contained in /etc/ssl/certs/Certum_Root_CA.pem:

[email protected] [~]:# openssl x509 -in /etc/ssl/certs/Certum_Root_CA.pem -text -noout | grep Certum
        Issuer: C = PL, O = Unizeto Sp. z o.o., CN = Certum CA
        Subject: C = PL, O = Unizeto Sp. z o.o., CN = Certum CA

The solution

In short, add the missing cert and make openssl trust it. The cert is available here.

  • If you have root or sudo access to the server, trust the downloaded root ca certs by following the many guides online (e.g. [2]).

  • If you are a restricted client and your hosting provider is willing to help, ask them to do the above.

  • If neither routes are possible, then the problem can not be fixed at system level. Fortunately, PHP allows users to specify cert file they want to trust through simply setting configuration values in php.ini. First, concatenate system cert.pem and the downloaded Certum Root CA cert file together:

    wget --no-check-certificate https://www.certum.pl/CA.pem -o /home/reseller/etc/cert.pem
    cat /etc/pki/tls/cert.pem >> /home/reseller/etc/cert.pem
    

    Next, make sure you have custom php.ini set up in cPanel for your application. This usually sits in the application root folder and can be checked using php_info() , e.g. WHMCS->Utilities->System->PHP Info.

    Edit the custom php.ini, and change the value of openssl.cafile:

    openssl.cafile="/home/reseller/etc/cert.pem"
    

    After this, everything should work fine.

    The problem with this solution is that other certs in the hand-made bundle will not be updated if the hosting provider updates /etc/pki/tls/cert.pem. So if errors possibly related to certs occur, remember to update this bundle first before going in other directions.


References

[1] https://alexwebdevelop.com/phpmailer-tutorial/

[2] https://www.happyassassin.net/2015/01/14/trusting-additional-cas-in-fedora-rhel-centos-dont-append-to-etcpkitlscertsca-bundle-crt-or-etcpkitlscert-pem/


Comments

comments powered by Disqus