TheFox / smtpd

SMTP server (library) for receiving emails, written in pure PHP.
https://fox21.at
MIT License
121 stars 30 forks source link

Example code not working #17

Open zookatron opened 6 years ago

zookatron commented 6 years ago

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' => 'info@opensauce.systems',
], $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 = 'user@example.com';                 // 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('from@example.com', 'Mailer');
    $mail->addAddress('joe@example.net', 'Joe User');     // Add a recipient
    $mail->addAddress('ellen@example.com');               // Name is optional
    $mail->addReplyTo('info@example.com', 'Information');
    $mail->addCC('cc@example.com');
    $mail->addBCC('bcc@example.com');

    //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.

jonevance commented 5 years ago

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...

TheFox commented 5 years ago

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.

jonevance commented 5 years ago

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 commented 5 years ago

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.

TheFox commented 5 years ago

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

jonevance commented 5 years ago

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.

TheFox commented 5 years ago

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.

jonevance commented 5 years ago

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 commented 5 years ago

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

openssl.log

jonevance commented 5 years ago

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.

TheFox commented 5 years ago

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

jonevance commented 5 years ago

0.3

TheFox commented 5 years ago

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

jonevance commented 5 years ago

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.

zookatron commented 5 years ago

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?

jonevance commented 5 years ago

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

joseleperez commented 5 years ago

@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
groovenectar commented 2 years ago

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