You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
6.8 KiB
171 lines
6.8 KiB
<?php
|
|
require_once __DIR__ . '/../utils/Config.php';
|
|
class Pop3Handler {
|
|
private $socket;
|
|
private $clientIp;
|
|
private $logger;
|
|
private $config;
|
|
private $state = 'auth';
|
|
private $username = '';
|
|
private $password = '';
|
|
private $authenticated = false;
|
|
private $messages = [];
|
|
private $deletedMessages = [];
|
|
private $messageCount = 0;
|
|
private $mailboxSize = 0;
|
|
public function __construct($socket, $clientIp, $logger) {
|
|
$this->socket = $socket;
|
|
$this->clientIp = $clientIp;
|
|
$this->logger = $logger;
|
|
$this->config = Config::getInstance();
|
|
}
|
|
public function handle($data) {
|
|
$lines = explode("\r\n", $data);
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if ($line === '') { continue; }
|
|
$this->processCommand($line);
|
|
}
|
|
}
|
|
private function processCommand($line) {
|
|
$parts = preg_split('/\\s+/', $line, 2);
|
|
$cmd = strtoupper($parts[0]);
|
|
$arg = $parts[1] ?? '';
|
|
switch ($cmd) {
|
|
case 'USER':
|
|
$this->username = $arg;
|
|
$this->send("+OK");
|
|
break;
|
|
case 'PASS':
|
|
$this->password = $arg;
|
|
if ($this->authenticate()) {
|
|
$this->authenticated = true;
|
|
$this->state = 'transaction';
|
|
$this->initMessages($this->username);
|
|
$this->send("+OK {$this->messageCount} {$this->mailboxSize}");
|
|
} else {
|
|
$this->send("-ERR");
|
|
}
|
|
break;
|
|
case 'STAT':
|
|
if (!$this->authenticated) { $this->send("-ERR"); break; }
|
|
$this->send("+OK {$this->messageCount} {$this->mailboxSize}");
|
|
break;
|
|
case 'LIST':
|
|
if (!$this->authenticated) { $this->send("-ERR"); break; }
|
|
if (!empty($arg)) {
|
|
$id = (int)$arg;
|
|
if (isset($this->messages[$id])) { $this->send("+OK {$id} {$this->messages[$id]['size']}"); }
|
|
else { $this->send("-ERR"); }
|
|
} else {
|
|
$this->send("+OK {$this->messageCount} messages");
|
|
foreach ($this->messages as $id => $msg) { $this->send("{$id} {$msg['size']}"); }
|
|
$this->send(".");
|
|
}
|
|
break;
|
|
case 'UIDL':
|
|
if (!$this->authenticated) { $this->send("-ERR"); break; }
|
|
if (!empty($arg)) {
|
|
$id = (int)$arg;
|
|
if (isset($this->messages[$id])) { $this->send("+OK {$id} {$this->messages[$id]['uid']}"); }
|
|
else { $this->send("-ERR"); }
|
|
} else {
|
|
$this->send("+OK");
|
|
foreach ($this->messages as $id => $msg) { $this->send("{$id} {$msg['uid']}"); }
|
|
$this->send(".");
|
|
}
|
|
break;
|
|
case 'RETR':
|
|
if (!$this->authenticated) { $this->send("-ERR"); break; }
|
|
$id = (int)$arg;
|
|
if (isset($this->messages[$id])) {
|
|
$this->send("+OK {$this->messages[$id]['size']} octets");
|
|
$this->sendRaw($this->messages[$id]['content']);
|
|
$this->send(".");
|
|
} else { $this->send("-ERR"); }
|
|
break;
|
|
case 'DELE':
|
|
if (!$this->authenticated) { $this->send("-ERR"); break; }
|
|
$id = (int)$arg;
|
|
if (isset($this->messages[$id])) { $this->deletedMessages[$id] = true; $this->send("+OK"); }
|
|
else { $this->send("-ERR"); }
|
|
break;
|
|
case 'NOOP':
|
|
$this->send("+OK");
|
|
break;
|
|
case 'RSET':
|
|
$this->deletedMessages = [];
|
|
$this->send("+OK");
|
|
break;
|
|
case 'QUIT':
|
|
if ($this->authenticated) { $this->applyDeletes(); }
|
|
$this->send("+OK bye");
|
|
break;
|
|
default:
|
|
$this->send("-ERR");
|
|
break;
|
|
}
|
|
}
|
|
private function authenticate() {
|
|
try {
|
|
require_once __DIR__ . '/../utils/Database.php';
|
|
$db = Database::getInstance();
|
|
$row = $db->fetchOne("SELECT password FROM user WHERE username = ? AND is_deleted = 0", [$this->username]);
|
|
if (!$row) { return false; }
|
|
return password_verify($this->password, $row['password']);
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
private function initMessages($username) {
|
|
try {
|
|
require_once __DIR__ . '/../utils/Database.php';
|
|
$db = Database::getInstance();
|
|
$emails = $db->fetchAll("SELECT * FROM email WHERE rcpt_to = ? AND folder = 'inbox' AND is_deleted = 0 ORDER BY `date` ASC", [$username]);
|
|
$this->messages = [];
|
|
$this->messageCount = 0;
|
|
$this->mailboxSize = 0;
|
|
$this->deletedMessages = [];
|
|
if ($emails) {
|
|
$id = 1;
|
|
foreach ($emails as $email) {
|
|
$content = "From: {$email['from']}\r\n" .
|
|
"To: {$email['to']}\r\n" .
|
|
"Subject: {$email['subject']}\r\n" .
|
|
"Date: {$email['date']}\r\n\r\n" .
|
|
"{$email['data']}\r\n";
|
|
$size = strlen($content);
|
|
$this->messages[$id] = [
|
|
'uid' => $email['id'],
|
|
'size' => $size,
|
|
'content' => $content,
|
|
'email_id' => $email['id']
|
|
];
|
|
$this->mailboxSize += $size;
|
|
$id++;
|
|
}
|
|
}
|
|
$this->messageCount = count($this->messages);
|
|
} catch (Exception $e) {
|
|
$this->messages = [];
|
|
$this->messageCount = 0;
|
|
$this->mailboxSize = 0;
|
|
$this->deletedMessages = [];
|
|
}
|
|
}
|
|
private function applyDeletes() {
|
|
if (empty($this->deletedMessages)) { return; }
|
|
try {
|
|
require_once __DIR__ . '/../utils/Database.php';
|
|
$db = Database::getInstance();
|
|
foreach ($this->deletedMessages as $id => $_) {
|
|
if (isset($this->messages[$id])) {
|
|
$db->update("UPDATE email SET is_deleted = 1, folder = 'trash' WHERE id = ?", [$this->messages[$id]['email_id']]);
|
|
}
|
|
}
|
|
} catch (Exception $e) {}
|
|
}
|
|
private function send($line) { $out = $line . "\r\n"; socket_write($this->socket, $out, strlen($out)); }
|
|
private function sendRaw($raw) { socket_write($this->socket, $raw, strlen($raw)); }
|
|
}
|