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)); } }