smtpd icon indicating copy to clipboard operation
smtpd copied to clipboard

Example code not working

Open zookatron opened this issue 6 years ago • 18 comments

How to reproduce:

php version

PHP 7.2.9-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Aug 19 2018 07:16:12) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.9-1+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies

packages

composer require phpmailer/phpmailer
composer require thefox/smtpd
composer require monolog/monolog

server.php

<?php

// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use TheFox\Smtp\Server;
use TheFox\Smtp\Event;
use Zend\Mail\Message;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

//Load Composer's autoloader
require 'vendor/autoload.php';

// Generate certificate
$privkey = openssl_pkey_new();
$cert = openssl_csr_new([
    'countryName' => 'UK',
    'stateOrProvinceName' => 'Isle Of Wight',
    'localityName' => 'Cowes',
    'organizationName' => 'Open Sauce Systems',
    'organizationalUnitName' => 'Dev',
    'commonName' => '127.0.0.1',
    'emailAddress' => '[email protected]',
], $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365);

// Generate PEM file
$pem = [];
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1]);
$pem = implode($pem);

// Save PEM file
$pemfile = 'server.pem';
file_put_contents($pemfile, $pem);
$listenOptions = [
    'ssl' => [
        'verify_peer' => false,
        'local_cert' => $pemfile,
        'allow_self_signed' => true,
    ],
];

// Create a Logger with Monolog.
$logger = new Logger('smtp_example');
$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));

$server = new Server([
    'ip' => '127.0.0.1',
    'port' => 587,
    'logger' => $logger,
]);
if(!$server->listen($listenOptions)) {
    print 'Server could not listen.' . "\n";
    exit(1);
}

$server->addEvent(new Event(Event::TRIGGER_NEW_MAIL, null, function(Event $event, string $from, array $rcpts, Message $mail) {
    // Do Stuff
}));

$server->addEvent(new Event(Event::TRIGGER_AUTH_ATTEMPT, null, function($event, $type, $credentials): bool {
    return true;
}));

$server->loop();

email.php

<?php

// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

//Load Composer's autoloader
require 'vendor/autoload.php';

$mail = new PHPMailer(true);                              // Passing `true` enables exceptions
try {
    //Server settings
    $mail->SMTPDebug = 2;                                 // Enable verbose debug output
    $mail->isSMTP();                                      // Set mailer to use SMTP
    $mail->Host = 'localhost';  // Specify main and backup SMTP servers
    $mail->SMTPAuth = false;                               // Enable SMTP authentication
    // $mail->Username = '[email protected]';                 // SMTP username
    // $mail->Password = 'secret';                           // SMTP password
    $mail->SMTPSecure = 'tls';                            // Enable TLS encryption, `ssl` also accepted
    $mail->Port = 587;                                    // TCP port to connect to
    $mail->SMTPOptions = array(
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false,
            'allow_self_signed' => true
        )
    );

    //Recipients
    $mail->setFrom('[email protected]', 'Mailer');
    $mail->addAddress('[email protected]', 'Joe User');     // Add a recipient
    $mail->addAddress('[email protected]');               // Name is optional
    $mail->addReplyTo('[email protected]', 'Information');
    $mail->addCC('[email protected]');
    $mail->addBCC('[email protected]');

    //Attachments
    // $mail->addAttachment('/var/tmp/file.tar.gz');         // Add attachments
    // $mail->addAttachment('/tmp/image.jpg', 'new.jpg');    // Optional name

    //Content
    $mail->isHTML(true);                                  // Set email format to HTML
    $mail->Subject = 'Here is the subject';
    $mail->Body    = 'This is the HTML message body <b>in bold!</b>';
    $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

    $mail->send();
    echo 'Message has been sent';
} catch (Exception $e) {
    echo 'Message could not be sent. Mailer Error: ', $mail->ErrorInfo;
}

run server & send email:

sudo php server.php
php email.php

server.php output:

[2018-10-23 19:22:54] smtp_example.INFO: start [] []
[2018-10-23 19:22:54] smtp_example.INFO: ip = "127.0.0.1" [] []
[2018-10-23 19:22:54] smtp_example.INFO: port = "587" [] []
[2018-10-23 19:22:54] smtp_example.INFO: hostname = "localhost.localdomain" [] []
[2018-10-23 19:22:54] smtp_example.NOTICE: listen ok [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1 data send: "220 localhost.localdomain SMTP Service Ready" [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1 data send: "250-localhost.localdomain\n250-AUTH PLAIN LOGIN\n250-STARTTLS\n250 HELP" [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1 data send: "220 Ready to start TLS" [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
[2018-10-23 19:23:06] smtp_example.DEBUG: client 1: collect data [] []
... (repeats forever) ...

email.php output:

2018-10-24 00:23:06	SERVER -> CLIENT: 220 localhost.localdomain SMTP Service Ready
2018-10-24 00:23:06	CLIENT -> SERVER: EHLO tim-workstation
2018-10-24 00:23:06	SERVER -> CLIENT: 250-localhost.localdomain
                   	                  250-AUTH PLAIN LOGIN
                   	                  250-STARTTLS
                   	                  250 HELP
2018-10-24 00:23:06	CLIENT -> SERVER: STARTTLS
2018-10-24 00:23:06	SERVER -> CLIENT: 220 Ready to start TLS
2018-10-24 00:23:06	CLIENT -> SERVER: EHLO tim-workstation
2018-10-24 00:23:08	SERVER -> CLIENT:
2018-10-24 00:23:08	SMTP ERROR: EHLO command failed:
2018-10-24 00:23:08	SMTP NOTICE: EOF caught while checking if connected

The server does not appear to be receiving the "EHLO tim-workstation" command that is being sent by PHPMailer and so it just continually spins with no received data. Are you able to reproduce this? Do you know why this might be happening? Your library appears to be using a custom networking library and I'm not familiar enough with the low-level networking functions to debug effectively. If you could point me in the right direction I could probably write up a fix.

zookatron avatar Oct 24 '18 00:10 zookatron

Having the same problem, and I've narrowed it down a bit. If I turn off TLS on the sending side, the server works as expected. Not sure where to go from here to debug further, though...

jonevance avatar Feb 19 '19 17:02 jonevance

Hi, I'm sorry for this. I have to debug the code line-by-line. Maybe rewriting the example from scratch with a better approach.

Creating the certificate in the example code is not a good idea. This script should not focus on creating an SSL certificate, rather on handling the SMTP server.

TheFox avatar Feb 19 '19 22:02 TheFox

Nothing to be sorry for! Bugs are part of the process (assuming it even is a bug). If it helps, the stream_socket_enable_crypto call at StreamSocket.php:170 does not return an error or warning, but once it has been called the stream_socket_recvfrom call at line 135 starts returning FALSE. Of course this should be returning a string, but even worse is that the dataRecv method of the Client class does not know what to do with a FALSE response, so it keeps looping and looping.

jonevance avatar Feb 19 '19 22:02 jonevance

More info, in case it may help: I removed the self-signed certificate and purchased a real one. The problem still exists, so I don't believe the self-signing is the issue (but of course I may have screwed up the certificate somehow). If there's anything else I can do to help track this down, please let me know.

jonevance avatar Feb 21 '19 16:02 jonevance

Technically there is no different between a self-signed certificate and a purchased real one.

TheFox avatar Feb 21 '19 16:02 TheFox

Indeed; I interpreted the second line of your original response to indicate you thought the certificate creation in the example code may be a problem, so I tried to verify that it is not. If I misinterpreted your comment, I apologize.

jonevance avatar Feb 21 '19 16:02 jonevance

Ah, I see. This was a miscommunication.

I meant that the example script and this project should not focus on creating certificates at all. Only take the path to an existing certificate. I'll remove the example in the next version and create an extra examples directory with different example files.

TheFox avatar Feb 21 '19 17:02 TheFox

Gotcha. Note, though, that this issue definitely seems to be irrespective of the example code. Any attempt I've made to use the SMTP server with TLS is failing in the manner described above.

jonevance avatar Feb 21 '19 17:02 jonevance

Testing with OpenSSL command line doesn't give me any hints, but perhaps you will understand the attached log better.

openssl.log

jonevance avatar Feb 21 '19 17:02 jonevance

Progress, maybe. At least some information, if not a fix. Replace stream_socket_recvfrom and stream_socket_sendto with fread and fwrite, respectively, and it works.

jonevance avatar Feb 22 '19 17:02 jonevance

@jonevance @zookatron Are you on master, or a specific version?

TheFox avatar Feb 22 '19 18:02 TheFox

0.3

jonevance avatar Feb 22 '19 18:02 jonevance

That's really old. We are already at 0.7.

TheFox avatar Feb 22 '19 19:02 TheFox

Sorry about that. Your README.md instructs to install via: composer.phar require "thefox/smtpd=~0.3"

I've upgraded to 0.7 and the problem persists, and is still "fixable" by swapping the recv and send with fread and fwrite.

jonevance avatar Feb 22 '19 19:02 jonevance

Hi all,

@TheFox I've tried several different versions of the library and they all appear to have the same bug for me, including version 0.7.

@jonevance Your "replace stream_socket_recvfrom and stream_socket_sendto with fread and fwrite" fix is working perfectly for me, thanks, that is a huge help! @TheFox Any chance we could get this fix merged into master? Would it help to create a PR?

zookatron avatar Feb 24 '19 17:02 zookatron

Prefer to have the stream functions work, of course, but I've tried everything; the problem really seems to be in the underlying system.

jonevance avatar Feb 24 '19 18:02 jonevance

@TheFox Same happens here with the current master.

[2019-04-10 15:22:33] smtp_example.DEBUG: client 1: collect data [] []
[2019-04-10 15:22:33] smtp_example.DEBUG: client 1: collect data [] []
[2019-04-10 15:22:33] smtp_example.DEBUG: client 1: collect data [] []
[2019-04-10 15:22:33] smtp_example.DEBUG: client 1: collect data [] []
$ php -v
PHP 7.2.14 (cli) (built: Jan 31 2019 00:51:06) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

joseleperez avatar Apr 11 '19 05:04 joseleperez

I ran into the same issue and can confirm that https://github.com/TheFox/network/pull/1 from @aaronschmied fixes it

groovenectar avatar Jan 28 '22 19:01 groovenectar