The first thing developed for Mailjoe was its TCP server. There are many examples out there, but we didn’t find any that was multi-protocol, non-blocking and multi-process.
The snippets below are fairly small and self explanatory. Please keep in mind that all of this code worked fine during the Mailjoe beta, but is by no means finished or necessarily suitable for other purposes.
mailjoe.php, the main loop:
set_time_limit (0);
require_once("config.php");
require_once(INCL_PATH.'Daemon.php');
$__daemon_listening = true;
$__daemon_childs = 0;
declare(ticks = 1);
pcntl_signal(SIGTERM, 'daemon_sig_handler');
pcntl_signal(SIGINT, 'daemon_sig_handler');
pcntl_signal(SIGCHLD, 'daemon_sig_handler');
daemonize();
$sockPOP = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sockPOP, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind( $sockPOP, $listen_address, $listen_port_pop3) or die('Could not bind to POP3 port/address!');
socket_listen( $sockPOP, $max_clients);
socket_set_nonblock($sockPOP);
$sockIMAP = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sockIMAP, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind( $sockIMAP, $listen_address, $listen_port_imap) or die('Could not bind to IMAP port/address!');
socket_listen( $sockIMAP, $max_clients);
socket_set_nonblock($sockIMAP);
$sockSMTP = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sockSMTP, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind( $sockSMTP, $listen_address, $listen_port_smtp) or die('Could not bind to SMTP port/address!');
socket_listen( $sockSMTP, $max_clients);
socket_set_nonblock($sockSMTP);
posix_setuid($posix_uid);
function debug($s) {
syslog(LOG_INFO, "[".posix_getpid()."] ".$s);
}
while ($__daemon_listening) {
$newsock = @socket_accept($sockIMAP);
if ($newsock === false) {
} elseif ($newsock > 0) {
daemon_client($sockIMAP, $newsock, "imap");
} else {
die(socket_strerror($newsock));
}
$newsock = @socket_accept($sockPOP);
if ($newsock === false) {
} elseif ($newsock > 0) {
debug("Accepted POP connection");
daemon_client($sockPOP, $newsock, "pop");
} else {
die(socket_strerror($newsock));
}
$newsock = @socket_accept($sockSMTP);
if ($newsock === false) {
} elseif ($newsock > 0) {
daemon_client($sockSMTP, $newsock, "smtp");
} else {
die(socket_strerror($newsock));
}
usleep(10000);
}
The relevant part from config.php:
define(BASE_PATH,'/home/mailjoe/');
define(INCL_PATH,BASE_PATH.'lib/');
define(TEMP_PATH,'/tmp/');
$listen_address = '127.0.0.1';
$listen_port_pop3 = 110;
$listen_port_smtp = 25;
$listen_port_imap = 143;
As you can see, our process listens on localhost only. We were using Perdition to enable SSL as well as do some basic sanity checks for POP3 and IMAP4. SSLTunnel was used to enable SSL on the SMTP port.
We did initially use PHP’s built-in SSL capabilities but ran into a number of limitations.
daemon.php, which is based on several examples found on php.net:
function daemon_sig_handler($sig) {
global $__daemon_childs;
switch($sig) {
case SIGTERM:
case SIGINT:
debug("SIGTERM received");
exit();
break;
case SIGCHLD:
debug("SIGCHLD received");
while(pcntl_waitpid(-1, $status, WNOHANG)>0)
$__daemon_childs--;
debug("SIGCHLD finished");
break;
}
}
function daemon_client($sock, $newsock, $type) {
global $__daemon_listening, $__daemon_childs;
while($__daemon_childs >= MAX_CHILD) {
usleep(10000);
}
$pid = pcntl_fork();
if ($pid == -1) {
debug("Failed to fork!");
die();
} elseif ($pid == 0) {
$__daemon_listening = false;
socket_close($sock);
socket_set_block($newsock);
$client = new $type($newsock);
while($client->connected) {
$sockets[0] = $newsock;
socket_select($sockets, $write = null, $except = null, $tv_sec = NULL);
$client->Check();
}
socket_close($newsock);
} else {
socket_close($newsock);
$__daemon_childs++;
}
}
function daemonize() {
$pid = pcntl_fork();
if ($pid == -1) {
debug("Failed to fork!");
exit();
} elseif ($pid) {
exit();
} else {
posix_setsid();
chdir('/');
umask(0);
return posix_getpid();
}
}
Important to note here is that daemon.php tries to create a new instance of $type, which could be POP3, SMTP or IMAP based on the socket used. You’ll have to put your own class here that can handle the connection.