IMAP or POP3 server implemented in PHP
Asked Answered
B

1

7

Is there any available implementation of POP3/IMAP server in PHP?

I am handling my e-mail service using sendgrid. I am going to store the messages on my server using files/db/whatever and now I'd like to provide full POP3 or IMAP (preferable) access for my users to their mailbox. Is there such implementation in PHP? Or is there any other possibility to run POP3/IMAP in the distributed environment of Windows Azure (assuming I have the mailboxes stored in shared blobs/tables/db)?

Barbiebarbieri answered 15/8, 2012 at 11:18 Comment(8)
There may be an implementation of this in PHP, but if there is for the love of god don't use it! Apart from shell scripts, PHP is about the worst language I can think of for doing this. If you want something in a language that web developers understand, look at Node.js.Bossism
@DaveRandom, you are damn right... However, my app uses PHP and I am also limited by the options provided by Windows Azure platform.Barbiebarbieri
Well POP3 and IMAP are not especially complicated protocols (depending on what you want to support) and could be implemented in PHP fairly easily, but concurrency is a big problem in PHP because it doesn't support threading and even process forking is... err... well, let's be kind and say challenging to implement on Windows. Also, efficiency-wise this would be shocking. Is there no way you can install Node.js? Poking around Google I have already found 2 POP3 and 1 IMAP server implemented with nice easy to use APIs.Bossism
I can't see that my app uses PHP is particularly relevant here - the data storage layer is (should be) abstracted from the application layer and what you are talking about is a daemon/service which would exist separately from the rest of your app(s) anyway.Bossism
In theory, the concurrency issue can be solved by the PHP script spawning a new copy of itself whenever it receives a client connection. The same trick can be use to keep the server alive: listen on the socket for 60 sec; if nothing happens, close the socket, do a fopen() on localhost to spawn (or switch to) a new server thread, and exit. This is so perverse that it's cool.Vaunting
@Vaunting The problem is that on Windows the only way to fork/spawn a new process is the exec() family of functions (or messing about in COM) and none of these mechanisms allow you to pass resource variables between the processes like pcntl_fork() does because they are created in a completely separate memory space. So you can access the client socket in the new process - which for this specific task makes it basically useless. Unless you know something I don't - I'm not infallible by any stretch of the imagination.Bossism
There's no need to pass resources at all. The script would listen on port 110. It gets a new socket from socket_accept(). It stops listening, spawn a copy of itself, and handles the session. Meanwhile, the copy starts listening on port 110 for the next incoming request.Vaunting
@DaveRandom, you are hell right. Sum up your comments as the answer and I'll accept it.Barbiebarbieri
V
12

Well, just to show that it is in fact possible to write a POP3 server in PHP, here it is. The server does no authentication--or pretty much anything else. It just keep sending the same message over and over. But it works. Thunderbird was able to retrieve messages from it. Totally useless, but sort of cool.

My setup is Apache 2 on Windows with PHP 5.2.

<?php

// echo something so fopen() would return
header("Content-type: text/plain");
echo "OK\n";
flush();

// listen for incoming connection
$listen_socket = socket_create_listen(110, 1);
$r = $w = $e = array($listen_socket);
$n = socket_select($r, $w, $e, 120);
$client_socket = ($n == 1) ? socket_accept($listen_socket) : null;
socket_close($listen_socket);

// spawn copy of myself
$internal_url = "http://{$_SERVER['HTTP_HOST']}:{$_SERVER['SERVER_PORT']}{$_SERVER['SCRIPT_NAME']}";
$stream_context_options = array (
    'http' => array (
        'method' => 'GET',
        'timeout' => 1
    )
);
$context = stream_context_create($stream_context_options);
if($f = fopen($internal_url, "rb", 0, $context)) {
    fclose($f);
}

if(!$client_socket) {
    // timed out
    exit;
}

// start handling the session
$read_buffer = "";
$write_buffer = "+OK POP3 server ready\r\n";
$active = true;

$messages = array(
    "From: [email protected]\r\nSubject: This is a test\r\n\r\nHello world!\r\n"
);


$idle_start = time();
while(true) {
    $r = $w = $e = array($client_socket);
    $n = socket_select($r, $w, $e, 60);
    if($n) {
        if($r) {
            // read from the socket
            $read_buffer .= socket_read($client_socket, 128);
            $idle_start = time();
        }
        if($w) {
            if($write_buffer) {
                // write to the socket
                $written = socket_write($client_socket, $write_buffer);
                $write_buffer = substr($write_buffer, $written);
                $idle_start = time();
            } else if($active) {
                $now = time();
                $idle_time = $now - $idle_start;
                if($idle_time > 10) {
                    // exit if nothing happened for 10 seconds
                    break;
                } else if($idle_time > 2) {
                    // start napping when the client is too slow
                    sleep(1);
                }
            } else {
                break;
            }
        }
        if($e) {
            break;
        }
        if($read_buffer) {
            if(preg_match('/(.*?)(?:\s+(.*?))?[\r\n]+/', $read_buffer, $matches)) {
                $read_buffer = substr($read_buffer, strlen($matches[0]));
                $command = $matches[1];
                $argument = $matches[2];
                switch($command) {
                    case 'USER':
                        $username = $argument;
                        $write_buffer .= "+OK $username is welcome here\r\n";
                        break;
                    case 'PASS':
                        $message_count = count($messages);
                        $write_buffer .= "+OK mailbox has $message_count message(s)\r\n";
                        break;
                    case 'QUIT': 
                        $write_buffer .= "+OK POP3 server signing off\r\n";
                        $active = false;
                        break;
                    case 'STAT':
                        $message_count = count($messages);
                        $mailbox_size = 0;
                        foreach($messages as $message) {
                            $mailbox_size += strlen($message);
                        }
                        $write_buffer .= "+OK $message_count $mailbox_size\r\n";
                        break;
                    case 'LIST':
                        $start_index = (int) $argument;
                        $message_count = count($messages) - $start_index;
                        $total_size = 0;
                        for($i = $start_index; $i < count($messages); $i++) {
                            $total_size += strlen($messages[$i]);
                        }
                        $write_buffer .= "+OK $message_count messages ($total_size octets)\r\n";
                        for($i = $start_index; $i < count($messages); $i++) {
                            $message_id = $i + 1;
                            $message_size = strlen($messages[$i]);
                            $write_buffer .= "$message_id $message_size\r\n";
                        }
                        $write_buffer .= ".\r\n";
                        break;
                    case 'RETR':
                        $message_id = (int) $argument;
                        $message = $messages[$message_id - 1];
                        $message_size = strlen($message);
                        $write_buffer .= "+OK $message_size octets\r\n";
                        $write_buffer .= "$message\r\n";
                        $write_buffer .= ".\r\n";
                        break;
                    case 'DELE':
                        $write_buffer .= "+OK\r\n";
                        break;
                    case 'NOOP':
                        $write_buffer .= "+OK\r\n";
                        break;
                    case 'LAST':
                        $message_count = count($messages) - $start_index;
                        $write_buffer .= "+OK $message_count\r\n";
                        break;
                    case 'RSET':
                        $write_buffer .= "+OK\r\n";
                        break;
                    default:
                        $write_buffer .= "-ERR Unknown command '$command'\r\n";
                }
            }
        }
    } else {
        break;
    }
}

?>
Vaunting answered 15/8, 2012 at 16:49 Comment(4)
Yeh, what's the source? Have you created it by yourself?Barbiebarbieri
If you were on a non-windows host you could use PCNTL to fork a new process.Bennet
I feel awed, looking at this code ^ ^ I feel a lack of words, I feel awe. This is so awesome and perverse in the same time.Scholasticism
@JaidenSnow sarcasm! it will get you no where :PNovel

© 2022 - 2024 — McMap. All rights reserved.