roundcube / roundcubemail

The Roundcube Webmail suite
https://roundcube.net
GNU General Public License v3.0
5.77k stars 1.62k forks source link

SMTP XCLIENT support #6411

Closed lgblgblgb closed 3 years ago

lgblgblgb commented 6 years ago

Scenario: I have a fairly complex postfix configuration for SMTP mail submission server, involving many postfix table lookups (including net based ones, SQL, pcre, internal RBLs for clients - yes), policy servers and so on to try to mitigate the impact of stolen passwords exploited then by spammers to push spam out. Roundcube is configured to use this SMTP server as well. Surely, this requested feature can be useful for other scenarios as well, I guess.

However, the problem, that postfix cannot do the same checks as with bare SMTP connections, since the client is always my roundcube server's IP address. And so on. Since postfix supports the XCLIENT ESMTP extension, it's possible to "inject" the information to tell postfix what it should think about the origin of the mail submission SMTP connection (surely, usage of XCLIENT must be configured to allow a very trusted, source, ie internal behind-the-firewall roundcube server, not for the whole world ...). Without this, I would need to duplicate all the mechanisms into roundcube what postfix can check for SMTP clients from the net. If roundcube can extend its smtp session handling to optionally support XCLIENT, it would be able to pass the origin of the HTTP connection instead as the source IP (and probably other info as well) for the postfix SMTP server. I am not sure if XCLIENT is supported by other MTAs as well, but at least postfix surely knows about it.

Here is a little document also with a sample SMTP session to demonstrate the feature:

http://www.postfix.org/XCLIENT_README.html

(just to note, postfix also know XFORWARD, which is a different topic though)

lgblgblgb commented 6 years ago

By the way, I had a look on the roundcube plugin documentation if it's possible to "inject" extra commands in the SMTP session with a roundcube plugin to implement this (without a core change, as a plugin), but since it's my first try to look on the source, I am not sure if it's possible. I would avoid to hack into the basic smtp client such a feature otherwise ...

alecpl commented 6 years ago

It is not possible with the plugin API. You have to add some code around line 141 of rcube_smtp.php file, i.e. after connect() method use. You could use $this->conn->command() to send the XCLIENT command. See lines 130-140 to see how to use Net_SMTP object and handle errors. First you probably should use getServiceExtensions('XCLIENT') to check for the extension support. Pull requests are welcome.

lgblgblgb commented 6 years ago

Thanks for the tips, it really helped to figure out how I can start to deal with RC at this level. Hopefully it's ok to ask here, honestly, my PHP experience dates back about 10 years at least, without any OOP-style involved at that time (only procedural style). I've just started to implement this feature anyway, it seems to be a good start to practice things again.

However I have the following conceptional problem: after XCLIENT, the state of the SMTP connection is like after connecting to the SMTP server. Thus, EHLO/HELO phase follows again. And though I can "fake" it with $this->conn->command('EHLO ' . $this->conn->host, 220); (actually, I did, and it worked ... so at least I have a proof-of-the concept really ugly solution for this xclient support), it has the problem, that I may will get different set of ESMTP extensions than before, at the start of the SMTP session (with my ugly solution, this scenario is simply ignored, which can lead to many problems in certain configurations, though). After all, the purpose of XCLIENT also includes the scenario to have different policies from different source IPs, dictated by postfix configuration.

So I would need to re-negotiate at this point (at least, it seems the term in RC/Net_SMTP for doing the HELO/EHLO thing). However that negotiate() metod in Net_SMTP seems to be a protected method, so I cannot call it from rcube_smtp.php, from a different object. Now, I can implement that again, but it's kinda ugly. Since this is my really first attempt to even look at RC's codebase, and my PHP experience is getting really old, I am bit lost at this point, to solve the problem in a nice way.

alecpl commented 6 years ago

Right. Then I guess the only way is to put the code in Net_SMTP::negotiate() (https://github.com/pear/Net_SMTP/).

urosgruber commented 3 years ago

I was able to make this work with extending Net_SMTP and overriding negotiate class. Would this be ok to use custom Net_SMTP_Custom class instead and have this support in roundcube?

Bellow proof of concept. There is also need to add a function where we could set the real client IP gathered from roundcube.

class Net_SMTP_custom extends Net_SMTP
{
    public $remote_addr = 'IPADDR';

    function negotiate()
    {
        parent::negotiate();

        if (!isset($this->esmtp['XCLIENT'])) {
            if (PEAR::isError($error = $this->put('XCLIENT', 'ADDR=' . $this->remote_addr))) {
                return $error;
            }
            if (PEAR::isError($this->parseResponse(250))) {
                return false;
            }
        }
        return true;
    }
}
alecpl commented 3 years ago

I'd prefer not to have any wrappers.

urosgruber commented 3 years ago

Creating PR on original package might take longer to be approved. But I can try if this would work.

kwiatek6324 commented 3 years ago

@alecpl Please consider adding this part of code:

$exts = $this->conn->getServiceExtensions();
        if (isset($exts['XCLIENT']) && $rcube->config->get('smtp_xclient')=="yes") {
            $this->conn->command("XCLIENT LOGIN=".$rcube->get_user_name(),array(220));
            }

in program/lib/Roundcube/rcube_smtp.php at line 151.

With options:

$config['smtp_user'] = "";
$config['smtp_pass'] = "";
$config['smtp_auth_type'] = NULL;
$config['smtp_xclient']="yes";

It will send XCLIENT and smtp server will process message as authenticated.

alecpl commented 3 years ago

Implemented.

InputOutputZ commented 2 years ago

Heads up to those looking for a work around with MTA SMTP which doesn't support XCLIENT ADDR extension, you can use this plugin https://github.com/roundcube/roundcubemail/blob/master/plugins/additional_message_headers/additional_message_headers.php. Pass the remote IP there and handle the authentication from the MTA end through message headers.