I’ve put a pause on Erlang for a bit to play with PHP in order to write an email server. I stumbled upon Guerillamail’s code on github and thought this might be an example I could start with. Guerillamail is a very well known temporary email address site that I have seen before. It gets a lot of traffic. I assumed their code was war tested. I’d contemplated using Postfix or Exim and driving a Mail Transfer Agent with PHP for my project but the code Guerillamail has on their github site looked battle hardened & able to cope with the C10k problem (when I see a new language I always look at how it copes with this). GM had dropped Exim for their PHP script. My curiosity was piqued and I tried to try to run the code.
Of course Sod’s law meant they’d chosen MySql as their database. To say that I hate MySql would be something of an understatement: it’s always a pig to configure from a fresh install with guaranteed errors that take ages to fix. Unlike PostgreSQL et al it doesn’t Just Work™, and it’s shit by comparison anyway. My solution to this on OSX is just to use MAMP which is a preconfigured set of packages including PHP and MySQL. It generally works well and is a good hack to get a web development environment working: leave it to the devops guys to build a production environment.
What’s nice about Guerillasmtp, and how it solves the C10k problem, is to use Libevent for high performance network socket handling. It all looked good. Too good. I execute the code at the command like thusly:
Guerillamail$ php smtp.php -l log.txt
Guerillamail$
Nada, I just get the command prompt back.
Here is the sorry, unsolved, tale for those Googleing – you are not alone:
MAMP doesn’t have the libevent extension so there’s no libevent.so in /Applications/MAMP/bin/php/php5.6.2/lib/php/extensions/no-debug-non-zts-20131226/. ARRGHH.
Download libevent-0.1.0.tgz and do the Configure Make dance:
“phpize; ./configure, make, make install
…
no php.h“
Oh shit. I need the source code for PHP. The sky darkens & I hear the sound of rolling thunder in the distance; an ominous unseen presence seems to enter the room; the log fire dies inexplicably; in the distance a wolf howls. I have been here before: Configuring Open Sores software.
So it begins. Download & unpack the PHP source code. Configure Make Dance & the source is now in /usr/local/src/php. I read a blogpost that says I can put the source in MAMP by executing:
tar xzvf php-5.6.2.tar.gz -C /Applications/MAMP/bin/php/php5.6.2/include
Done. Now grab the source to libevent configure & compile:
pecl install libevent-0.1.0
cd libevent-0.1.0
phpize
./configure; make
cp modules/libevent.so /Applications/MAMP/bin/php/php5.6.2/lib/php/extensions/no-debug-non-zts-20131226/
This all looks good, libevent is in the extension directory so we are good to go! Let’s get this script running:
Guerillamail$ smptd.php -l log.txt
Guerillamail$
Err, wait, whut? It returned to the CLI prompt? Why did it not just sit there all daemony to serve clients? The log file said nothing. Running the code with the rather useful PHPStorm its console also exits but additionally says ‘exited with code 255‘. Google tells me exit code 255 is an unassigned PHP error code. So that’s as illuminating as a torch with a flat battery. Spiffy. Step though the code then. Great. I got to this bit of code:
283 //log_line("stream base set up"); 284 stream_set_blocking($socket, NONBLOCKING); 285 //log_line("stream base - stub0"); 286 $base = event_base_new(); 287 //log_line("stream base - stub1"); 288 $event = event_new();
The script hits line 286 calls event_base_new() and exits (with code 255). The php error log in /Application/MAMP/logs helps somewhat:
[27-Jan-2015 14:47:42 Europe/Berlin] PHP Warning: PHP Startup: libevent: Unable to initialize module
Module compiled with module API=20131226
PHP compiled with module API=20121212
These options need to match
in Unknown on line 0
[27-Jan-2015 14:47:42 Europe/Berlin] PHP Fatal error: Call to undefined function event_base_new() in /Users/david/BTSync/projects/php/PhpstormProjects/whispermailPHP/smtpd.php on line 286
I didn’t realise PHP API calls were versioned. That’s pretty cool I reckon. So API=20131226 and API=20121212 refer to the PHP interpreter API code and the extension API code. I know nothing about compiling PHP extensions but I wonder if there is a mechanism to blow up the extension rather more loudly on an API mismatch like this. Ideally PHP would barf on invocation and before it runs the script, e.g.:
$PHP buggyscript.php
Error module API mismatch: xxxx
Silently failing (stuffing errors in the error log doesn’t count) is less than helpful, especially if code 255 is unspecific. Dunno, just a thought.
By a curious coincidence php5.6.2 has an extensions directory called ‘no-debug-non-zts-20131226’ and that number20131226 looks jolly familiar. I have no idea at this point how the API version number gets in into libevent. At this point I have one of those inspired genius ideas that comes once in a blue-moon: since it’s very unlikely the version number is internally checked against the contents of the module by the module I can load libevent.so in a hex editor and text search for API; and YES, I find ‘API=20131226‘. This I edit it to ‘API20121212’ and reload and run again. Guess what happens!?
Yea, fuck all. OK, so this is going to need more slog less Commando.
Then I just happen to look at the PHP5.5.18 directories an see that the extensions directory is called ‘no-debug-non-zts-20121212‘ WAIT!? 20121212? This is number is the same as the API error. So working hypothesis is that libevent grabs this number and makes it the API version. Fragile. I need to get the source to PHP 5.5.18. Fuck. It can be grabbed from us2.php.net/releases/ in zip form by guessing version numbers. Unpack the zip file convert it to a tar file (erm, yea, I know) into the MAMP directory and rename:
tar zcvf php5.5.18.tar.gz php5.5.18/
tar xzvf php5.5.18.tar.gz -C /Applications/MAMP/bin/php/php5.5.18/include
mv /Applications/MAMP/bin/php/php5.5.18/include/php5.5.18 /Applications/MAMP/bin/php/php5.5.18/include/php
cd /Applications/MAMP/bin/php/php5.5.18/include/php
So we have the PHP source in the right MAMP directory. Time to Compile libevent. Easy:
[Edit: set the $Path variable or weirdness happens
$set PATH=/Applications/MAMP/bin/php/php5.5.18/include/php/bin:$PATH
]
cd libevent-0.1.0/ libevent-0.1.0
phpize
./configure; make; make install
This leave you with libevent in libevent-0.1.0/ libevent-0.1.0/modules/libevent.so.
cp modules/libevent.so /Applications/MAMP/bin/php/php5.5.18/lib/php/extensions/no-debug-non-zts-20121212/
Fire up a new shell bash shell (not sure why some sort of environment change needed?):
Guerillamail$ smptd.php -l log.txt
(waiting)
Yeeesss. I hope, dear random Googler, that that helped.