Page 1 of 1

SSL SMTP over cURL -- hacked solution

Posted: Fri Oct 28, 2011 2:40 pm
by Eric!
Before you ask, I'm doing this so that on a shared host environment that doesn't have a lot of supporting libraries installed and denies shell access, etc., I want to provide a curl ssl method to remote SMTP hosts for sending mail. I think it can be done, but I'm not sure.

Anyway I know it's stupid, but I'm trying to find a way to do it. So far I've tried it using a telnet socket and then trying to start up SSL. Then the idea is to send the authentication/SMTP commands. However when trying to connect to say, yahoo's smtp server I'm constantly denied.

Code: Select all

    public function openSMTP($host,$user, $password, $port, $debug=0) {
        $this->curl_handle = curl_init();
        $this->user = $user;
        $this->password = $password;
        $this->host = $host;
        if ($debug == 1) {
            $this->dbg = fopen("debug.txt", "w");
            curl_setopt($this->curl_handle, CURLOPT_VERBOSE, TRUE);
            curl_setopt($this->curl_handle, CURLOPT_STDERR, $this->dbg);
            $this->debug = 1;
            fwrite($this->dbg,"Opening debug file from openSMTP\n");
        }
        curl_setopt($this->curl_handle, CURLOPT_URL, "telnet://$host:$port");
            curl_setopt($this->curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE);
            curl_setopt($this->curl_handle, CURLOPT_SSL_VERIFYHOST, TRUE);        
        curl_setopt($this->curl_handle, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($this->curl_handle, CURLOPT_PROTOCOLS, CURLPROTO_TELNET);

        curl_exec($this->curl_handle);
        $error_no = curl_errno($this->curl_handle);
        if ($error_no != 0) {
            echo 'Problem opening connection.  CURL Error: ' . $error_no;
        }
    }
Output: [text]Problem opening connection. CURL Error: 56

DEBUG INFO:
Opening debug file from openSMTP
* About to connect() to smtp.mail.yahoo.com port 465 (#0)
* Trying 98.139.212.139... * connected
* Connected to smtp.mail.yahoo.com (98.139.212.139) port 465 (#0)
* Recv failure: Connection reset by peer
* Closing connection #0[/text]

Now I know I have the port setting and everything correct because I can openssh into the server and I've managed to send messages via php with a normal SSL/SMTP connection using the PEAR libraries. I'm thinking there must be something wrong with how the SSL is being handled, however connecting without SSL enabled cause the same type refusal.

Does anyone know a better way I could debug this connection or see something I'm doing wrong?

Re: SSL SMTP over cURL -- debugging problems

Posted: Sun Oct 30, 2011 8:32 am
by Eric!
Ok, maybe I should back up a little. Does anyone know the difference between using fsockopen("ssl:host") and opening the same host via cURL with ssl?

For example some shared hosts with fsockopen produce:
[text]Unable to find the socket transport "ssl" - did you forget to enable it when you configured PHP? (0)[/text]
But on the same host cURL with ssl over HTTP can be used without any problems. PHPINFO shows openssl is supported. Are these accessing the same ssl libraries?

EDIT: clarified that curl with HTTPS works, but I still have not been able to get curl with telnet SSL to work.

Re: SSL SMTP over cURL -- debugging problems

Posted: Sun Oct 30, 2011 8:47 am
by Benjamin
Recv failure: Connection reset by peer
Seems like an error you would receive if the connection was being blocked by a firewall.

Re: SSL SMTP over cURL -- debugging problems

Posted: Sun Oct 30, 2011 9:12 am
by Eric!
On my own machine there's no firewall and I can open the connection via [text]openssl s_client -crlf -connect smtp.mail.yahoo.com:465[/text] and via fsockopen (the failed example of fsockopen above was on a shared server), but I can't get it to connect via curl with ssl over telnet.

Re: SSL SMTP over cURL -- debugging problems

Posted: Sun Oct 30, 2011 6:56 pm
by Eric!
While playing around I found that wget works. It of course has no idea what to do with the connection.
[text]wget https://smtp.mail.yahoo.com:465
--2011-10-30 16:53:48-- https://smtp.mail.yahoo.com:465/
Resolving smtp.mail.yahoo.com... 98.136.185.95, 98.139.212.139, 98.138.84.55
Connecting to smtp.mail.yahoo.com|98.136.185.95|:465... connected.
HTTP request sent, awaiting response... 200 No headers, assuming HTTP/0.9
Length: unspecified
Saving to: `index.html'

[ <=> ] 162 1.03K/s in 0.2s

2011-10-30 16:53:50 (1.03 KB/s) - `index.html' saved [162][/text]
I think I'll try a few other methods with curl and see if I can figure out how to get it to work. Using just http over ssl I can almost get it to work, but it does the same thing as wget by trying to fetch HTML. So maybe I can modify it with customrequests for SMTP. Too bad I can't figure out how to get the telnet connection to work with curl, because it would take less hacking.
[text]DEBUG INFO:
Opening debug file from openSMTP
* About to connect() to smtp.mail.yahoo.com port 465 (#0)
* Trying 98.136.185.95... * connected
* Connected to smtp.mail.yahoo.com (98.136.185.95) port 465 (#0)
* successfully set certificate verify locations:
* CAfile: none
CApath: /etc/ssl/certs
* SSL connection using AES256-SHA
* Server certificate:
* subject: C=US; ST=CA; L=Sunnyvale; O=Yahoo! Inc.; OU=Yahoo; CN=smtp.mail.yahoo.com
* start date: 2011-10-03 00:00:00 GMT
* expire date: 2013-10-07 12:00:00 GMT
* subjectAltName: smtp.mail.yahoo.com matched
* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert High Assurance CA-3
* SSL certificate verify ok.
> GET / HTTP/1.1
Host: smtp.mail.yahoo.com:465
Accept: */*

* Connection #0 to host smtp.mail.yahoo.com left intact
* Closing connection #0[/text]

Re: SSL SMTP over cURL -- debugging problems

Posted: Mon Oct 31, 2011 12:16 am
by Eric!
I got it working, even on the shared host that doesn't allow ssl fsockopen ports.

I had to get the CA certs file and put it locally in order to verify them. Then I had to write the entire SMTP command string into a single customrequest header because CURL in http/https mode adds in the " / HTTP/1.1 \nAccept: */*" stuff which causes the server to drop the connection.

I still don't know why telnet and tsl won't work. It would be a real solution, instead of this hack. Here's how I got it to work (the code is mess right now but the idea is there):

Code: Select all

    public function openSMTP($host, $user, $password, $port, $debug=0) {
        $this->curl_handle = curl_init();
        $this->user = $user;
        $this->password = $password;
        $this->host = $host;
        $this->port = $port;
        if ($debug == 1) {
            $this->dbg = fopen("debug.txt", "w");
            curl_setopt($this->curl_handle, CURLOPT_VERBOSE, TRUE);
            curl_setopt($this->curl_handle, CURLOPT_STDERR, $this->dbg);
            $this->debug = 1;
            fwrite($this->dbg, "Opening debug file from openSMTP\n");
        }
        curl_setopt($this->curl_handle, CURLOPT_URL, "https://$host:$port");
        curl_setopt($this->curl_handle, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($this->curl_handle, CURLOPT_SSL_VERIFYHOST, true);
        curl_setopt($this->curl_handle, CURLOPT_CAINFO,"cacert.pem");
        curl_setopt($this->curl_handle, CURLOPT_CAPATH,"./");

        curl_setopt($this->curl_handle, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($this->curl_handle, CURLOPT_HTTPHEADER, "");
        
        $out = "AUTH LOGIN\r\n";
        $out .= base64_encode("USERNAME") . "\r\n";
        $out .= base64_encode("PASSWORD") . "\r\n";
        $out .= "MAIL FROM: <EMAIL@yahoo.com>\r\n";
        $out .= "RCPT TO: <EMAIL@domain.com>\r\n";
        $out .= "DATA\r\n";
        $out .= "To: EMAIL@domain.com\r\n";
        $out .="From: EMAIL@yahoo.com\r\n";
        $out .="Subject:  Test Message\r\n\r\n";
        $out .="Message here\r\n";
        $out .=".\r\n";
        $out .="QUIT\r\n";
        
        curl_setopt($this->curl_handle, CURLOPT_CUSTOMREQUEST, $out . "\r\n");
        curl_exec($this->curl_handle);

        $error_no = curl_errno($this->curl_handle);
        if ($error_no != 0) {
            echo 'Problem opening connection.  CURL Error: ' . $error_no;
        }
    }
Example curl output:
[text]DEBUG INFO:Opening debug file from openSMTP
* About to connect() to smtp.mail.yahoo.com port 465 (#0)
* Trying 98.136.185.95... * connected
* Connected to smtp.mail.yahoo.com (98.136.185.95) port 465 (#0)
* successfully set certificate verify locations:
* CAfile: cacert.pem
CApath: ./
* SSL connection using AES256-SHA

* Server certificate:
* subject: C=US; ST=CA; L=Sunnyvale; O=Yahoo! Inc.; OU=Yahoo; CN=smtp.mail.yahoo.com
* start date: 2011-10-03 00:00:00 GMT
* expire date: 2013-10-07 12:00:00 GMT
* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert High Assurance CA-3
* SSL certificate verify ok.
> AUTH LOGIN
[base64-encoded username]
[base64-encoded password]
MAIL FROM: <email@yahoo.com>
RCPT TO: <email@domain.com>
DATA
To: email@domain.com
From: email@yahoo.com
Subject: Test Message

Message here
.
QUIT

/ HTTP/1.1
Host: smtp.mail.yahoo.com:465
Accept: */*

* Connection #0 to host smtp.mail.yahoo.com left intact
* Closing connection #0
[/text]

If someone knows how to get telnet and tsl (ssl) working over curl, please let me know. But if you need a way to send SMTP messages to remote servers with SSL encryption and your host blocks fsockopen or other SSL methods, this curl hack might work for you with simple messages.

Re: SSL SMTP over cURL -- debugging problems

Posted: Mon Oct 31, 2011 12:46 am
by Benjamin
Excellent work. How fast is it? Do you need a separate cert for every host?

Re: SSL SMTP over cURL -- debugging problems

Posted: Mon Oct 31, 2011 7:43 am
by Eric!
I don't know how fast it is, but curl is a fast library and it does all the work. I haven't rewritten the code properly to profile it.

Since yahoo uses a root cert signature, I just used the full root CA file that I got from curl's automatic PEM cert list which they say is automatically converted from mozilla's x.509 public certs. You could probably speed it up some with your own cert file. I don't know much about certs, but I was surprised how small the full PEM root cert file was (~230K). FYI here's curl's docs on ssl certs.