For quite some time I've been wanting to compile PHP for the sole purpose of using the http://us.php.net/ftp_ssl_connect function.
According to the PHP manual:
ftp_ssl_connect() is only available if OpenSSL support is enabled into your version of PHP. If it's undefined and you've compiled FTP support then this is why. For Windows you must compile your own PHP binaries to support this function.
When I first saw this I was disappointed. I thought I would never be able to compile PHP myself. It just felt like a daunting task to get my system configured correctly.
...Well, it's not perfect, but I have been able to get it to work, and I've even been able to patch up the PHP code itself!
PART 1 - COMPILING PHP
UPDATE: 2010-05-27 - Please see this link for compiling on http://wiki.php.net/internals/windows/stepbystepbuild
Install Visual C++ 6 (Visual C++ Express OR Visual Studio 2005 are OK too)
Luckily I already had a copy of C++ 6 left over from my college days. Only problem I had was to find the cd. With some minor changes this will also work for VC++ Express/Visual Studio 2005. I chose to use version 6 because using the newer versions requires you distribute other files along with PHP. If all you have is VCE or VS2k5 go ahead and work with that, I will show you where the differences are.
Download PHP 5.2 Source
The sources from the PHP snapshots worked for me. I downloaded php-5.2-dev (tar.gz) (8.9M) at the time of this writing. Link to the snapshots page: http://snaps.php.net/
Download Needed Libraries
From what I've been able to find, Edin KadribaĊĦic seems to have a nice zip file with everything in it. I downloaded zip.zip from his site. Link to Edin's files: http://files.edin.dk/
You need to go into subfolder /php/win32 and click on zip.zip. (If this link is down try this one: http://perisama.net/ )
Create a Working Directory
I did the following:
Created directory c:\work\
Extracted source to c:\work\php5\
Extracted php_build from zip.zip to c:\work\php_build\
This is what it looked like after this step:
123
c:\work
|---php5
|---php_build
Set Environment Variables
Version 6:
I added these lines to C:\Program Files\Microsoft Visual Studio\VC98\Bin\vcvars32.bat.
123
set PATH=%PATH%;C:\work\php_build\bin;
set BISON_SIMPLE=C:\work\php_build\bin\bison.simple
%SystemRoot%\system32\cmd.exe <-- Makes things a little easier, but it's not required.
Versions > 6:
Add the first two lines above to C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat.
I put them under the line starting
1
@set LIBPATH=C:\WINDOWS\Microsoft.NET.
Open Up a Command Prompt
Version 6:
I created a shortcut to the vcvars32.bat file and whenever I needed to get into the development environment with the proper variables set, I simply double clicked it to launch a command window. (That is what the third line above is for.)
Versions > 6:
There should be a shortcut created with your install. For Visual Studio 2005 there is shortcut named Visual Studio 2005 Command Prompt.
Switch to the PHP Source Directory
Basically, this command at the prompt assuming you have the same directory structure:
1
cd /d c:\work\php5
Run Buildconf
This command at the prompt:
1
C:\work\php5>buildconf
Run Configure.js
PHP configuration line. More information http://us2.php.net/configure
This part takes a little while depending on the speed of your pc.
At the end of the process I got a build complete message. Something that I wasn't sure about here, were all the warnings that were generated during the compile. Most of them were about type conversion and possible loss of data. I would think that these need to be fixed or maybe there is a compiler switch to turn them off. Since PHP worked anyway, I ignored them and continued... Update: 10/30/2007 - Apparently these warnings are ok. Dreaming of Dawn
PART 2 - PATCHING PHP
After compiling PHP I was able to use the ftp_ssl_connect() function without getting that awful function undefined message. I set out to use my new function and custom built PHP, only to discover that there was yet another problem...
It is important to note that all of the testing was done with Serv-U FTP Server. I don't know if this error would occur with other servers.
Here is the piece of code I've been using for my initial testing. Keep in mind that this was code ran from the command line:
<?php$ftp_server='server.onlan.local';$ftp_user_name='username';$ftp_user_pass='password';// set up basic ssl connection$conn_id=ftp_ssl_connect($ftp_server,2222,5);if($conn_id){$login_result=ftp_login($conn_id,$ftp_user_name,$ftp_user_pass);if($login_result){echo'ok\n';ftp_pasv($conn_id,true);$buff=ftp_rawlist($conn_id,'.');print_r($buff);}elseecho'error\n';ftp_close($conn_id);}else{echo'no workie\n';}?>
When I ran this code with PHP after the initial compile it executed with the fallowing output:
12
PHP Warning: ftp_login(): SSL/TLS handshake failed in C:\Documents and Settings\user\Desktop\php_with_ftp_ssl_connect\ftpssltest.php on line 11
PHP Warning: ftp_login(): AUTH command OK. Initializing SSL connection. in C:\Documents and Settings\user\Desktop\php_with_ftp_ssl_connect\ftpssltest.php on line 11
It connected to the server with ftp_ssl_connect(), but then failed when it was time to authenticate with ftp_login(). Below is a portion of the FTP server log with this session.
As you can see it failed with SSL_ERROR_SSL - wrong version number. I still don't know what this means, but searching online ultimately lead me to the PHP bugs site. There I did a search for ftp_ssl_connect() and found two interesting bugs:
http://bugs.php.net/bug.php?id=36103 #36103
http://bugs.php.net/bug.php?id=41021 #41021
In bug #36103 there is a link to a patch by tony2001 that looked promising.
I opened up c:\Work\php5\ext\ftp\ftp.c. Right after the opening bracket of the ftp_login() function I replaced these lines of code:
I recompiled and executed the same code with the patched PHP and I was able to get a little further. Here is the FTP server log with this patch in effect:
As you can see, this time the SSL connection was initialized, the private data connection was opened, the user was successfully authenticated, Pasv mode was set, and the command LIST to get a directory listing was sent. Unfortunately, that is where the script failed.
At this point I wasn't sure what to do, but I decided I'd give ftp.c another look just in case I had missed something. I started at the top of the file and worked my way down. At first it didn't occur to me, but then I realized that the ftp_login() function might not be the only place where this patch was needed. Sure enough, towards the bottom of the file, I found another function: data_accept(). This made perfect sense, since after the LIST command is sent to the ftp server the actual information is sent through the data channel which is handled by this function.
In the data_accept() function I replaced this code:
After this last patch, I once again recompiled the source and ran the script. This time everything worked as expected, but after some testing I found another issue.
Every few directory listings that I pulled down, the connection to the server would be lost. After further investigation I discovered that the cause of this was long directory listings in PASV mode. Every time I pulled a directory listing with more than approximately 5 to 10 files or folders everything was fine, but as soon as the listing contained more than that the connection would unexpectedly close.
Start Update: 10/30/2007I again set out to find the problem. Since this only happened when the directory listing was long, I knew it had something to do with data not fitting inside a container. I started looking for areas in the code where this would be the case, and the first one that I found that made sense to me was this line of code: data = ecalloc(1, sizeof(data)); in the ftp_getdata() function located in the ftp.c file. data is of type databuf_t which is defined in the ftp.h file. I changed the size of this buffer to twice the size from 4096 to 8192. (I was surprised to find out that there was a comment in the code that stated this buffer size should be editable at runtime.) After I recompiled, the error was gone and everything seemed to work normally.
After further testing I discovered that I didn't need to have the buffer be twice the size, but simply one byte bigger. From 4096 to 4097. (This is very odd, and I'm sure there is a way to fix this in the code, but I just don't know where)
After some testing I discovered that the above is wrong. Changing the buffer caused my SSL communication to break, but because the server was set to handle both ssl or non-ssl connections I did not notice it. I changed the buffer back to the original size of 4096, and I was able to trace the problem to something else:
In the my_recv() function at added these lines of code at the beginning of the function:
123
#if HAVE_OPENSSL_EXTintno_error;#endif
and replaced this line of code:
1
nr_bytes=SSL_read(ftp->data->ssl_handle,buf,len);
with these lines:
1 2 3 4 5 6 7 8 910111213141516171819
no_error=1;// there is no errordo{nr_bytes=SSL_read(ftp->data->ssl_handle,buf,len);switch(SSL_get_error(ftp->data->ssl_handle,nr_bytes)){caseSSL_ERROR_NONE:caseSSL_ERROR_ZERO_RETURN:caseSSL_ERROR_WANT_WRITE:caseSSL_ERROR_WANT_X509_LOOKUP:caseSSL_ERROR_WANT_READ:break;caseSSL_ERROR_WANT_CONNECT:caseSSL_ERROR_WANT_ACCEPT:caseSSL_ERROR_SSL:caseSSL_ERROR_SYSCALL:default:no_error=0;//major error, get outbreak;}}while(nr_bytes<0&&no_error);
After SSL_read() was executed I got -1 as the return. A call to SSL_get_error() returned SSL_ERROR_WANT_READ. From what I've been able to find online and the OpenSSL documentation, when this happens, you need to retry the SSL_read() function. After a few retries, the function succeeded and was able to get the data.
(I've done some testing and this patch seems to be working reliably.) End Update: 10/30/2007
I ran a session with Ghisler's Total Commander which uses the same OpenSSL libraries to make a secure FTP connection, and the server logs looked almost identical to the log generated when connecting with PHP.
Here are the main concerns I have:
PHP Compile
What other configuration lines do I really need to get a production ready PHP like what you get from the main PHP downloads site with OpenSSL support included? Is --with-openssl enough?
Why do I get all those warnings when compiling PHP? Update: 10/30/2007 - Apparently these warnings are ok. Dreaming of Dawn
PHP Patch
Why hasn't Tony's patch made it into the official release? (Perhaps I can add mine as well...)
Why are there several SSL_ERROR_WANT_READ errors? These show up in both the PHP and the Total Commander FTP logs. Update: 10/30/2007 - SSL_ERROR_WANT_READ is normal. It just means "there is no data available yet."
While changing the data buffer works, I wonder what other side effects it may have on functionality. Why is increasing it by one byte enough? Update: 10/30/2007 - Changing the data buffer breaks SSL communication.