This page is about how I managed to get the WordPress plugin WP-Mail-SMTP to send emails using my GMail account, which has 2 factor authentication enabled, using SSL/TLS.

This information is about how I managed to get the WordPress plugin WP-Mail-SMTP to send emails using my GMail account, which has 2 factor authentication enabled, using SSL/TLS.

You may not be able to do this on a hosted server if you have limited control of the PHP service.

It took a few hours to get this working. The biggest problem was with the certificates that are needed to connect to the GMail SMTP server.

My set up is as follows:-

  • Windows 10 64Bit
  • PHP 7.0.13 NTS (Not Thread Safe)
  • WordPress 4.6.1
  • MySQL 5.7 [Not relevant for this process]

1. Install the WP-Mail-SMTP plugin

Install the plugin through the WordPress plugin function. Once the plugin is installed, configure it with the following options:-

From Email <email address that messages will appear to come from>
From Name <name of the person that will appear to send emails>
Mailer Send all WordPress emails via SMTP
Return Path <set if required>
SMTP Host smtp.gmail.com
SMTP Port 587
Encryption Use TLS encyption
Authentication Yes: Use SMTP authentication
Username <GMail email account that will be used to send emails>
Password <password for GMail account>
If using 2 factor authentication, see below.

 


2. Enable the openssl extension in PHP

Because sending email using GMail requires SSL, you have to enable the openssl extension in PHP. I had downloaded and set up PHP on my personal machine, and the extension was disabled by default; but it may already be enabled on your system.

In your php.ini file, look for the openssl extension, and remove the semicolon from the beginning of the line:-

;extension=php_openssl.dll

You should do an iisreset after making any changes to the php.ini file.


3. Acquire SSL certificates for smtp.gmail.com

a. Download the openssl CLI

The way that I acquired the certificates for smtp.gmail.com was to use the openssl command line tool.

I originally found this, older, version for Windows:-

http://vorboss.dl.sourceforge.net/project/gnuwin32/openssl/0.9.8h-1/openssl-0.9.8h-1-bin.zip

But there is a more up to date version here:-

https://sourceforge.net/projects/openssl/

After unzipping the downloaded file, move the openssl folder to somewhere easily accessible. I moved it to c:\openssl.

b. Acquire the certificate

Using a command window, run the following command. You will need to use the path to where you put the openssl folder:-

c:\openssl\bin\openssl s_client -connect smtp.gmail.com:465

This will output a lot of information. In the middle of this information will be the server certificate that we need:-

openssl-smtp-gmail

The certificate is contained within the section between the —–BEGIN CERTIFICATE—– and —–END CERTIFICATE—– lines.

You need to copy this section, including the BEGIN and END lines mentioned, in to a file. The name of the file is not imporant, but give it the extension .cer. I saved the file to I:\certificates\smtp.gmail.com.cer:-

certfile


4. Import the certificate in to the certificate store

Use Start/Run (or Windows-Key R) to run the mmc (Microsoft Management Console) application. Add the Certificates snap-in, for the Computer account, for the Local computer: –

mmcaddsnapin mmccomputeracc mmclocalcomp

Once the snap-in has been added to the MMC, expand the Certificates node, right click on the Personal node, and select All Tasks and Import… options. Browse to and select the .cer file saved earlier. Select the Place all certificates in the following store option, and select the Personal Certificate store:-

mmcimportmmcselectcertmmccertstore

This will actually import 3 certificates, if they don't already exist;

  1. The smtp.gmail.com certificate:-

mmcpersonalcert

Please note the expiry date of this certificate. The certificate I retrieved was only valid for 3 months, so this certificate may need updating at frequent intervals.

  1. The Intermediate Certificate Authority certificate:-

mmcicacert

  1. The Trusted Root Certificate Authority certificate:-

mmctrcacert

These will be needed later.


5. First test

In the [openssl] section of the php.ini file, it suggests that openssl would be able to use Windows certificate store to locate any certificates that is required for authentication:-

[openssl]
; The location of a Certificate Authority (CA) file on the local filesystem
; to use when verifying the identity of SSL/TLS peers. Most users should
; not specify a value for this directive as PHP will attempt to use the
; OS-managed cert stores in its absence. If specified, this value may still
; be overridden on a per-stream basis via the "cafile" SSL stream context
; option.
;openssl.cafile=

I thought this was all of the set up required.

However, after using the Send a Test Email function of WP-Mail-SMTP, I got this error:-

2016-11-20 08:58:25	CLIENT -> SERVER: STARTTLS
2016-11-20 08:58:25	SMTP -> get_lines(): $data is ""
2016-11-20 08:58:25	SMTP -> get_lines(): $str is  "220 2.0.0 Ready to start TLS
                   	                  "
2016-11-20 08:58:25	SERVER -> CLIENT: 220 2.0.0 Ready to start TLS

Warning:  stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in I:\Projects\WordPress\wordpress-4.6.1\wp-includes\class-smtp.php on line 343

2016-11-20 08:58:25	SMTP Error: Could not connect to SMTP host.
2016-11-20 08:58:25	CLIENT -> SERVER: QUIT

I spent a while trying to find out what the error error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed meant, but could not really find anything. I appears that the php_openssl extension does not look in the certificate store for certificates, or couldn't because of something I have done wrong.


6. openssl and hashed certificates

I then looked at the openssl.capath option in the php.ini file, which has the following comment associated with it:-

; If openssl.cafile is not specified or if the CA file is not found, the
; directory pointed to by openssl.capath is searched for a suitable
; certificate. This value must be a correctly hashed certificate directory.
; Most users should not specify a value for this directive as PHP will
; attempt to use the OS-managed cert stores in its absence. If specified,
; this value may still be overridden on a per-stream basis via the "capath"
; SSL stream context option.
; openssl.capath=

I did some digging to find out what "hashed certificate directory" meant. What this means is a directory where the certificates are stored in files, with the file name being a hashed value generated by openssl.

You can use the openssl command line utility to get the hash for a certificate, but as we will see later, different versions/builds of openssl, including the php_openssl extension, seem to generate different hashes for the same certificate. So, although this step was not ultimately necessary to get this working, it is probably handy to know. (I don't know if the reason the different versions of openssl returned different hashes was down to configuration, or something else).

We already have the .cer file for the main server certificate, but we also need the intermediate and root certificate authority certificates as .cer files.

From within the MMC, right click on the certificate and select the All Tasks and Export… options. Select the Base 64 encoded X.509 option. Enter the name of the file to save the certifcate to:-

mmcexportmmcexportb64mmcexportfile

You need to export both the Intermediate Certificate Authority certifice – Google Internet Authority G2 – and the Trusted Root Certificate Authority certificate – GeoTrust Global CA. I saved mine to GoogleIAG2.cer and GeoTrustGlobalCA.cer.

Once you have the .cer files, you have to generate the openssl hashes for each of them. As mentioned earlier, this turned out to be futile, but lead me on to other things.

The hash for a file is generated using the following command:-

I:\Certificates>c:\openssl\bin\openssl x509 -noout -hash -in smtp.gmail.com.cer

This returns an 8 character hash:-

2a9f2b08

Now you need to copy the .cer file to a directory for the hashed certificates, using the hash as the file name and giving it the extension .0 (dot zero). I don't know why .0. I created a subfolder in my Certificates folder, and copied the file to the folder, giving it the hashed name: –

I:\Certificates>copy smtp.gmail.com.cer hashed\2a9f2b08.0

(unix/linux users can just use symbolic links).

The same process needs to be performed for the other 2 .cer files.

Now the openssl.capath option in the php.ini file needs to be configured with the name of the folder with the hashed certificate files. Don't forget to remove the semicolon from the start of the option:-

openssl.capath=I:\Certificates\hashed

Do an iisreset.


7. Second Test

I used the Send a Test Email function of WP-Mail-SMTP to send another email, and got the same error as before:-

2016-11-20 17:28:07	SERVER -> CLIENT: 220 2.0.0 Ready to start TLS

Warning:  stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in I:\Projects\WordPress\wordpress-4.6.1\wp-includes\class-smtp.php on line 343

2016-11-20 17:28:08	SMTP Error: Could not connect to SMTP host.
2016-11-20 17:28:08	CLIENT -> SERVER: QUIT

8. Trying to find out what is happening

I wondered if if it was trying to use the hashed certificate files that I had created. To do this, I used the Process Monitor utility (which can be downloaded from here https://technet.microsoft.com/en-gb/sysinternals/processmonitor. (There are a number of other handy utilities which you can get from the sysinternals site).

I set up a filter in Process Monitor to look for any activity within my I:\Certificates folder, and then sent another email from WP-Mail-SMTP. The output showed that php attempted to open 3 files, but they had different names to the ones that I had created:-

pmcerts

It was trying to open files I:\Certificates\hashed\578d5c04.0, I:\Certificates\hashed\2c543cd1.0 and I:\Certificates\hashed\c4c7a654.0. The three files that it tries to open are, in order, the Trusted Root Certificate Authority certificate, the Intermediary Certificate Authority certificate, and finally the smtp.gmail.com certificate. So I recopied the .cer files in to the hashed folder, using these hashed file names instead of the ones I had been given by the openssl command line utility.


9. Test three

I resent another test email using the Send a Test Email function of WP-Mail-SMTP, and this time it was able to connect, but I got an error back from the SMTP server:-

2016-11-20 17:54:19	SMTP -> get_lines(): $str is  "534-5.7.9 Application-specific password required. Learn more at
                   	                  "
2016-11-20 17:54:19	SMTP -> get_lines(): $data is "534-5.7.9 Application-specific password required. Learn more at
                   	                  "
2016-11-20 17:54:19	SMTP -> get_lines(): $str is  "534 5.7.9  https://support.google.com/mail/?p=InvalidSecondFactor f126sm14950770wme.22 - gsmtp
                   	                  "
2016-11-20 17:54:19	SERVER -> CLIENT: 534-5.7.9 Application-specific password required. Learn more at
                   	                  534 5.7.9  https://support.google.com/mail/?p=InvalidSecondFactor f126sm14950770wme.22 - gsmtp
2016-11-20 17:54:19	SMTP ERROR: Password command failed: 534-5.7.9 Application-specific password required. Learn more at
                   	                  534 5.7.9  https://support.google.com/mail/?p=InvalidSecondFactor f126sm14950770wme.22 - gsmtp
2016-11-20 17:54:19	SMTP Error: Could not authenticate.
2016-11-20 17:54:19	CLIENT -> SERVER: QUIT

So this time it was able to connect to the SMTP server, but there was an issue with authentication. When I had configured WP-Mail-SMTP, I had used my normal GMail login password. However, I also have 2 factor authentication enabled on my email account, and this is what the error is referring to.


10. Google Application Specific Password

If you follow the link shown in the error message – https://support.google.com/mail/?p=InvalidSecondFactor – you will discover that when 2 factor authentication is enabled, to allow applications to connect to the server you need to generate an application specific password. Your application will use this password instead of your normal password.

To generate an application specific password, go to https://security.google.com/settings/security/apppasswords:-

googleapppasswords

In the Select app drop down, select the Mail option. In the Select device drop down, select the Windows Computer option, and click the GENERATE button. This will generate a 16 character password which you can use within WP-Mail-SMTP:-

googlegenapppwd

Take the generated password (no spaces), and enter it to the WP-Mail-SMTP configuration, and click the Save Changes button:-

wpupdpwd


11. Final test

Sent another test email using the Send a Test Email function of WP-Mail-SMTP. Success:-

2016-11-20 18:20:29	SERVER -> CLIENT: 354  Go ahead 81sm15106915wmw.7 - gsmtp
2016-11-20 18:20:29	CLIENT -> SERVER: Date: Sun, 20 Nov 2016 18:20:29 +0000
2016-11-20 18:20:29	CLIENT -> SERVER: To: someone@someserver.net
2016-11-20 18:20:29	CLIENT -> SERVER: From: Marc <marcssymonds@gmail.com>
2016-11-20 18:20:29	CLIENT -> SERVER: Subject: WP Mail SMTP: Test mail to </marcssymonds@gmail.com>someone@someserver.net<marcssymonds@gmail.com>
2016-11-20 18:20:29	CLIENT -> SERVER: Message-ID: 
2016-11-20 18:20:29	CLIENT -> SERVER: X-Mailer: PHPMailer 5.2.14 (https://github.com/PHPMailer/PHPMailer)
2016-11-20 18:20:29	CLIENT -> SERVER: MIME-Version: 1.0
2016-11-20 18:20:29	CLIENT -> SERVER: Content-Type: text/plain; charset=UTF-8
2016-11-20 18:20:29	CLIENT -> SERVER: Content-Transfer-Encoding: 8bit
2016-11-20 18:20:29	CLIENT -> SERVER:
2016-11-20 18:20:29	CLIENT -> SERVER: This is a test email generated by the WP Mail SMTP WordPress plugin.
2016-11-20 18:20:29	CLIENT -> SERVER:
2016-11-20 18:20:29	CLIENT -> SERVER: .
2016-11-20 18:20:30	SMTP -> get_lines(): $data is ""
2016-11-20 18:20:30	SMTP -> get_lines(): $str is  "250 2.0.0 OK 1479666046 81sm15106915wmw.7 - gsmtp
                   	                  "
2016-11-20 18:20:30	SERVER -> CLIENT: 250 2.0.0 OK 1479666046 81sm15106915wmw.7 - gsmtp
2016-11-20 18:20:30	CLIENT -> SERVER: QUIT
2016-11-20 18:20:30	SMTP -> get_lines(): $data is ""
2016-11-20 18:20:30	SMTP -> get_lines(): $str is  "221 2.0.0 closing connection 81sm15106915wmw.7 - gsmtp
                   	                  "
2016-11-20 18:20:30	SERVER -> CLIENT: 221 2.0.0 closing connection 81sm15106915wmw.7 - gsmtp
2016-11-20 18:20:30	Connection: closed

Job Done.

Had a go at creating a simple widget that displays pages related to the current page in the sidebar. WordPress has some pretty good documentation and there are plenty of examples around, so it was pretty simple:-

<?php
/*
Plugin Name: Show Related Pages
Description: Menu widget to show child pages of the current page.
Version: 0.1
Author: Marc Symonds
Date: July 2014
*/

class mss_showchildpageswidget extends WP_Widget
{
  // Constructor.
  
  function __construct()
  {
    parent::__construct(
        'mss_showchildpageswidget',
        'Show Child Pages',
        array('description' => 'Show child pages related to the current page')
      );
  }
  
  // Output front-end markup.
  
  public function widget($args, $instance)
  {
    global $post;

    // Only output something if viewing a page.
    
    if (is_singular() && $post->post_type == 'page')
    {
      // Get the ancestor pages for the current page.
      
      $elders = get_post_ancestors($post->ID);
      $ecount = count($elders);

      // The list we are going to build will contain the two most recent ancestors (parent and grandparent),
      // the current page with two levels of descendants (child and grandchild) and the siblings of the current
      // page with one level of descendant (child):-
      //
      // Grandparent
      //   Parent
      //     This page
      //       Child1
      //       Child2
      //         Grandchild1
      //     Sibling1
      //     Sibling2
      //       Child1
      // 

      $t = get_the_title($post->ID);
      $l = get_permalink($post->ID);

      $before_child = '<ul class="children"><li><a href="' . $l . '"><b>' . $t . '</b></a>';
      $after_child = '</li></ul>';

      $i = 2;
      $j = 0;
      while ($i > 0 && $j < $ecount)
      {
        $p = $elders[$j];
        $t = get_the_title($p);
        $l = get_permalink($p);
        
        $before_child = '<ul class="children"><li><a href="' . $l . '">' . $t . '</a>' . $before_child;
        $after_child = '</li></ul>' . $after_child;
        $i--;
        $j++;
      }

      // Get list of published pages that are children of the current page.

      $opts = array(
         'child_of'    => $post->ID,
         'depth'       => 2,
         'echo'        => 0,
         'post_type'   => 'page',
         'post_status' => 'publish',
         'show_date'   => '',
         'sort_column' => 'menu_order, post_title',
         'sort_order'  => '',
         'title_li'    => '', 
         'walker'      => '');

      $children = wp_list_pages($opts);
    
      // Get list of sibling pages of the current page.
      
      if ($ecount > 0)
      {
        $opts['child_of'] = $elders[0];
        $opts['depth'] = 2;
        $opts['exclude_tree'] = $post->ID;
        
        $siblings = wp_list_pages($opts);
      }
      else
        $siblings = '';

      // Output.
            
      echo $args['before_widget'];

      $t = $instance['title'];
      $t = apply_filters('widget_title', empty($t) ? 'Related Pages' : $t);

      if (! empty($t))
        echo $args['before_title'] . $t . $args['after_title'];

      echo $before_child;
      echo '<ul class="children">' . $children . '</ul>';
     
      echo $siblings;
         
      echo $after_child;      
        
      echo $args['after_widget'];     
    }
  }
} 


// Register my widget.

function mss_showchildpageswidget_load()
{
  register_widget('mss_showchildpageswidget');
}
add_action( 'widgets_init', 'mss_showchildpageswidget_load' );
?>